X-Git-Url: https://git.ayoreis.com/hackapet/Adafruit_Blinka_Displayio.git/blobdiff_plain/0d243252f0fb3a3e200df404884e3cc7d872ee13..a53c930973a24a7c55db9448e45d7ca7974cb56e:/displayio/_tilegrid.py diff --git a/displayio/_tilegrid.py b/displayio/_tilegrid.py index 0cfc6d3..2ee8d54 100644 --- a/displayio/_tilegrid.py +++ b/displayio/_tilegrid.py @@ -19,6 +19,7 @@ displayio for Blinka import struct from typing import Union, Optional, Tuple +from circuitpython_typing import WriteableBuffer from ._bitmap import Bitmap from ._colorconverter import ColorConverter from ._ondiskbitmap import OnDiskBitmap @@ -73,6 +74,8 @@ class TileGrid: if isinstance(self._pixel_shader, ColorConverter): self._pixel_shader._rgba = True # pylint: disable=protected-access self._hidden_tilegrid = False + self._hidden_by_parent = False + self._rendered_hidden = False self._x = x self._y = y self._width_in_tiles = width @@ -98,16 +101,21 @@ class TileGrid: raise ValueError("Default Tile is out of range") self._pixel_width = width * tile_width self._pixel_height = height * tile_height - self._tiles = (self._width_in_tiles * self._height_in_tiles) * [default_tile] + self._tiles = bytearray( + (self._width_in_tiles * self._height_in_tiles) * [default_tile] + ) self._in_group = False self._absolute_transform = TransformStruct(0, 0, 1, 1, 1, False, False, False) self._current_area = Area(0, 0, self._pixel_width, self._pixel_height) + self._dirty_area = Area(0, 0, 0, 0) + self._previous_area = Area(0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF) self._moved = False + self._full_change = True + self._partial_change = True self._bitmap_width_in_tiles = bitmap_width // tile_width self._tiles_in_bitmap = self._bitmap_width_in_tiles * ( bitmap_height // tile_height ) - self.inline_tiles = False # We have plenty of memory def _update_transform(self, absolute_transform): """Update the parent transform and child transforms""" @@ -121,6 +129,7 @@ class TileGrid: width = self._pixel_height else: width = self._pixel_width + if self._absolute_transform.transpose_xy: self._current_area.y1 = ( self._absolute_transform.y + self._absolute_transform.dy * self._x @@ -153,6 +162,7 @@ class TileGrid: height = self._pixel_width else: height = self._pixel_height + if self._absolute_transform.transpose_xy: self._current_area.x1 = ( self._absolute_transform.x + self._absolute_transform.dx * self._y @@ -202,7 +212,11 @@ class TileGrid: image.putalpha(alpha.convert("L")) def _fill_area( - self, colorspace: Colorspace, area: Area, mask: bytearray, buffer: bytearray + self, + colorspace: Colorspace, + area: Area, + mask: WriteableBuffer, + buffer: WriteableBuffer, ) -> bool: """Draw onto the image""" # pylint: disable=too-many-locals,too-many-branches,too-many-statements @@ -210,7 +224,7 @@ class TileGrid: # If no tiles are present we have no impact tiles = self._tiles - if self._hidden_tilegrid: + if self._hidden_tilegrid or self._hidden_by_parent: return False overlap = Area() @@ -230,10 +244,10 @@ class TileGrid: start = 0 if (self._absolute_transform.dx < 0) != flip_x: - start += (area.width() - 1) * x_stride + start += (area.x2 - area.x1 - 1) * x_stride x_stride *= -1 if (self._absolute_transform.dy < 0) != flip_y: - start += (area.height() - 1) * y_stride + start += (area.y2 - area.y1 - 1) * y_stride y_stride *= -1 full_coverage = area == overlap @@ -298,7 +312,11 @@ class TileGrid: input_pixel.tile // self._bitmap_width_in_tiles ) * self._tile_height + local_y % self._tile_height - input_pixel.pixel = self.bitmap[input_pixel.tile_x, input_pixel.tile_y] + input_pixel.pixel = ( + self._bitmap._get_pixel( # pylint: disable=protected-access + input_pixel.tile_x, input_pixel.tile_y + ) + ) output_pixel.opaque = True if self._pixel_shader is None: @@ -317,16 +335,18 @@ class TileGrid: else: mask[offset // 32] |= 1 << (offset % 32) if colorspace.depth == 16: - buffer = ( - buffer[:offset] - + struct.pack("H", output_pixel.pixel) - + buffer[offset + 2 :] + struct.pack_into( + "H", + buffer, + offset * 2, + output_pixel.pixel, ) elif colorspace.depth == 32: - buffer = ( - buffer[:offset] - + struct.pack("I", output_pixel.pixel) - + buffer[offset + 4 :] + struct.pack_into( + "I", + buffer, + offset * 4, + output_pixel.pixel, ) elif colorspace.depth == 8: buffer[offset] = output_pixel.pixel & 0xFF @@ -352,7 +372,125 @@ class TileGrid: return full_coverage def _finish_refresh(self): - pass + first_draw = self._previous_area.x1 == self._previous_area.x2 + hidden = self._hidden_tilegrid or self._hidden_by_parent + if not first_draw and hidden: + self._previous_area.x2 = self._previous_area.x1 + elif self._moved or first_draw: + self._current_area.copy_into(self._previous_area) + + self._moved = False + self._full_change = False + self._partial_change = False + if isinstance(self._pixel_shader, (Palette, ColorConverter)): + self._pixel_shader._finish_refresh() # pylint: disable=protected-access + if isinstance(self._bitmap, (Bitmap, Shape)): + self._bitmap._finish_refresh() # pylint: disable=protected-access + + def _get_refresh_areas(self, areas: list[Area]) -> None: + # pylint: disable=invalid-name, too-many-branches, too-many-statements + first_draw = self._previous_area.x1 == self._previous_area.x2 + hidden = self._hidden_tilegrid or self._hidden_by_parent + + # Check hidden first because it trumps all other changes + if hidden: + self._rendered_hidden = True + if not first_draw: + areas.append(self._previous_area) + return + if self._moved and not first_draw: + self._previous_area.union(self._current_area, self._dirty_area) + if self._dirty_area.size() < 2 * self._pixel_width * self._pixel_height: + areas.append(self._dirty_area) + return + areas.append(self._current_area) + areas.append(self._previous_area) + return + + tail = areas[-1] if areas else None + # If we have an in-memory bitmap, then check it for modifications + if isinstance(self._bitmap, Bitmap): + self._bitmap._get_refresh_areas(areas) # pylint: disable=protected-access + refresh_area = areas[-1] if areas else None + if tail != refresh_area: + # Special case a TileGrid that shows a full bitmap and use its + # dirty area. Copy it to ours so we can transform it. + if self._tiles_in_bitmap == 1: + refresh_area.copy_into(self._dirty_area) + self._partial_change = True + else: + self._full_change = True + elif isinstance(self._bitmap, Shape): + self._bitmap._get_refresh_areas(areas) # pylint: disable=protected-access + refresh_area = areas[-1] if areas else None + if refresh_area != tail: + refresh_area.copy_into(self._dirty_area) + self._partial_change = True + + self._full_change = self._full_change or ( + isinstance(self._pixel_shader, (Palette, ColorConverter)) + and self._pixel_shader._needs_refresh # pylint: disable=protected-access + ) + if self._full_change or first_draw: + areas.append(self._current_area) + return + + if self._partial_change: + x = self._x + y = self._y + if self._absolute_transform.transpose_xy: + x, y = y, x + x1 = self._dirty_area.x1 + x2 = self._dirty_area.x2 + if self._flip_x: + x1 = self._pixel_width - x1 + x2 = self._pixel_width - x2 + y1 = self._dirty_area.y1 + y2 = self._dirty_area.y2 + if self._flip_y: + y1 = self._pixel_height - y1 + y2 = self._pixel_height - y2 + if self._transpose_xy != self._absolute_transform.transpose_xy: + x1, y1 = y1, x1 + x2, y2 = y2, x2 + self._dirty_area.x1 = ( + self._absolute_transform.x + self._absolute_transform.dx * (x + x1) + ) + self._dirty_area.y1 = ( + self._absolute_transform.y + self._absolute_transform.dy * (y + y1) + ) + self._dirty_area.x2 = ( + self._absolute_transform.x + self._absolute_transform.dx * (x + x2) + ) + self._dirty_area.y2 = ( + self._absolute_transform.y + self._absolute_transform.dy * (y + y2) + ) + if self._dirty_area.y2 < self._dirty_area.y1: + self._dirty_area.y1, self._dirty_area.y2 = ( + self._dirty_area.y2, + self._dirty_area.y1, + ) + if self._dirty_area.x2 < self._dirty_area.x1: + self._dirty_area.x1, self._dirty_area.x2 = ( + self._dirty_area.x2, + self._dirty_area.x1, + ) + areas.append(self._dirty_area) + + def _set_hidden(self, hidden: bool) -> None: + self._hidden_tilegrid = hidden + self._rendered_hidden = False + if not hidden: + self._full_change = True + + def _set_hidden_by_parent(self, hidden: bool) -> None: + self._hidden_by_parent = hidden + self._rendered_hidden = False + if not hidden: + self._full_change = True + + def _get_rendered_hidden(self) -> bool: + return self._rendered_hidden @property def hidden(self) -> bool: @@ -364,7 +502,8 @@ class TileGrid: def hidden(self, value: bool): if not isinstance(value, (bool, int)): raise ValueError("Expecting a boolean or integer value") - self._hidden_tilegrid = bool(value) + value = bool(value) + self._set_hidden(value) @property def x(self) -> int: