]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blobdiff - displayio/tilegrid.py
Ran black
[hackapet/Adafruit_Blinka_Displayio.git] / displayio / tilegrid.py
index 2a2222a5e9395edcb271f0453cc8723e4218577d..552277b73551ef148435a5d5fea1b34cd9b71367 100644 (file)
@@ -21,7 +21,7 @@
 # THE SOFTWARE.
 
 """
 # THE SOFTWARE.
 
 """
-`displayio`
+`displayio.tilegrid`
 ================================================================================
 
 displayio for Blinka
 ================================================================================
 
 displayio for Blinka
@@ -35,18 +35,20 @@ displayio for Blinka
 
 """
 
 
 """
 
+from recordclass import recordclass
 from PIL import Image
 from displayio.bitmap import Bitmap
 from displayio.colorconverter import ColorConverter
 from displayio.ondiskbitmap import OnDiskBitmap
 from displayio.shape import Shape
 from displayio.palette import Palette
 from PIL import Image
 from displayio.bitmap import Bitmap
 from displayio.colorconverter import ColorConverter
 from displayio.ondiskbitmap import OnDiskBitmap
 from displayio.shape import Shape
 from displayio.palette import Palette
-from displayio import Rectangle
-from displayio import Transform
 
 __version__ = "0.0.0-auto.0"
 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
 
 
 __version__ = "0.0.0-auto.0"
 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
 
+Rectangle = recordclass("Rectangle", "x1 y1 x2 y2")
+Transform = recordclass("Transform", "x y dx dy scale transpose_xy mirror_x mirror_y")
+
 # pylint: disable=too-many-instance-attributes
 class TileGrid:
     """Position a grid of tiles sourced from a bitmap and pixel_shader combination. Multiple
 # pylint: disable=too-many-instance-attributes
 class TileGrid:
     """Position a grid of tiles sourced from a bitmap and pixel_shader combination. Multiple
@@ -80,10 +82,14 @@ class TileGrid:
         bitmap_width = bitmap.width
         bitmap_height = bitmap.height
 
         bitmap_width = bitmap.width
         bitmap_height = bitmap.height
 
-        if not isinstance(pixel_shader, (ColorConverter, Palette)):
+        if pixel_shader is not None and not isinstance(
+            pixel_shader, (ColorConverter, Palette)
+        ):
             raise ValueError("Unsupported Pixel Shader type")
         self._pixel_shader = pixel_shader
             raise ValueError("Unsupported Pixel Shader type")
         self._pixel_shader = pixel_shader
-        self._hidden = False
+        if isinstance(self._pixel_shader, ColorConverter):
+            self._pixel_shader._rgba = True  # pylint: disable=protected-access
+        self._hidden_tilegrid = False
         self._x = x
         self._y = y
         self._width = width  # Number of Tiles Wide
         self._x = x
         self._y = y
         self._width = width  # Number of Tiles Wide
@@ -91,10 +97,16 @@ class TileGrid:
         self._transpose_xy = False
         self._flip_x = False
         self._flip_y = False
         self._transpose_xy = False
         self._flip_x = False
         self._flip_y = False
-        if tile_width is None:
+        self._top_left_x = 0
+        self._top_left_y = 0
+        if tile_width is None or tile_width == 0:
             tile_width = bitmap_width
             tile_width = bitmap_width
-        if tile_height is None:
+        if tile_height is None or tile_width == 0:
             tile_height = bitmap_height
             tile_height = bitmap_height
+        if tile_width < 1:
+            tile_width = 1
+        if tile_height < 1:
+            tile_height = 1
         if bitmap_width % tile_width != 0:
             raise ValueError("Tile width must exactly divide bitmap width")
         self._tile_width = tile_width
         if bitmap_width % tile_width != 0:
             raise ValueError("Tile width must exactly divide bitmap width")
         self._tile_width = tile_width
@@ -182,72 +194,137 @@ class TileGrid:
                     self._current_area.y1,
                 )
 
                     self._current_area.y1,
                 )
 
-    # pylint: disable=too-many-locals
+    def _shade(self, pixel_value):
+        if isinstance(self._pixel_shader, Palette):
+            return self._pixel_shader[pixel_value]["rgba"]
+        if isinstance(self._pixel_shader, ColorConverter):
+            return self._pixel_shader.convert(pixel_value)
+        return pixel_value
+
+    def _apply_palette(self, image):
+        image.putpalette(
+            self._pixel_shader._get_palette()  # pylint: disable=protected-access
+        )
+
+    def _add_alpha(self, image):
+        alpha = self._bitmap._image.copy().convert(  # pylint: disable=protected-access
+            "P"
+        )
+        alpha.putpalette(
+            self._pixel_shader._get_alpha_palette()  # pylint: disable=protected-access
+        )
+        image.putalpha(alpha.convert("L"))
+
+    # pylint: disable=too-many-locals,too-many-branches,too-many-statements
     def _fill_area(self, buffer):
         """Draw onto the image"""
     def _fill_area(self, buffer):
         """Draw onto the image"""
-        if self._hidden:
+        if self._hidden_tilegrid:
             return
 
             return
 
+        if self._bitmap.width <= 0 or self._bitmap.height <= 0:
+            return
+
+        # Copy class variables to local variables in case something changes
+        x = self._x
+        y = self._y
+        width = self._width
+        height = self._height
+        tile_width = self._tile_width
+        tile_height = self._tile_height
+        bitmap_width = self._bitmap.width
+        pixel_width = self._pixel_width
+        pixel_height = self._pixel_height
+        tiles = self._tiles
+        absolute_transform = self._absolute_transform
+        pixel_shader = self._pixel_shader
+        bitmap = self._bitmap
+        tiles = self._tiles
+
+        tile_count_x = bitmap_width // tile_width
+
         image = Image.new(
             "RGBA",
         image = Image.new(
             "RGBA",
-            (self._width * self._tile_width, self._height * self._tile_height),
+            (width * tile_width, height * tile_height),
             (0, 0, 0, 0),
         )
 
             (0, 0, 0, 0),
         )
 
-        tile_count_x = self._bitmap.width // self._tile_width
-        x = self._x
-        y = self._y
-
-        for tile_x in range(0, self._width):
-            for tile_y in range(0, self._height):
-                tile_index = self._tiles[tile_y * self._width + tile_x]
+        for tile_x in range(width):
+            for tile_y in range(height):
+                tile_index = tiles[tile_y * width + tile_x]
                 tile_index_x = tile_index % tile_count_x
                 tile_index_y = tile_index // tile_count_x
                 tile_index_x = tile_index % tile_count_x
                 tile_index_y = tile_index // tile_count_x
-                for pixel_x in range(self._tile_width):
-                    for pixel_y in range(self._tile_height):
-                        image_x = tile_x * self._tile_width + pixel_x
-                        image_y = tile_y * self._tile_height + pixel_y
-                        bitmap_x = tile_index_x * self._tile_width + pixel_x
-                        bitmap_y = tile_index_y * self._tile_height + pixel_y
-                        pixel_color = self._pixel_shader[
-                            self._bitmap[bitmap_x, bitmap_y]
-                        ]
-                        if not pixel_color["transparent"]:
-                            image.putpixel((image_x, image_y), pixel_color["rgb888"])
-        if self._absolute_transform is not None:
-            if self._absolute_transform.scale > 1:
+                tile_image = bitmap._image  # pylint: disable=protected-access
+                if isinstance(pixel_shader, Palette):
+                    tile_image = tile_image.copy().convert("P")
+                    self._apply_palette(tile_image)
+                    tile_image = tile_image.convert("RGBA")
+                    self._add_alpha(tile_image)
+                elif isinstance(pixel_shader, ColorConverter):
+                    # This will be needed for eInks, grayscale, and monochrome displays
+                    pass
+                image.alpha_composite(
+                    tile_image,
+                    dest=(tile_x * tile_width, tile_y * tile_height),
+                    source=(
+                        tile_index_x * tile_width,
+                        tile_index_y * tile_height,
+                    ),
+                )
+
+        if absolute_transform is not None:
+            if absolute_transform.scale > 1:
                 image = image.resize(
                     (
                 image = image.resize(
                     (
-                        self._pixel_width * self._absolute_transform.scale,
-                        self._pixel_height * self._absolute_transform.scale,
+                        int(pixel_width * absolute_transform.scale),
+                        int(
+                            pixel_height * absolute_transform.scale,
+                        ),
                     ),
                     resample=Image.NEAREST,
                 )
                     ),
                     resample=Image.NEAREST,
                 )
-            if self._absolute_transform.mirror_x:
+            if absolute_transform.mirror_x:
                 image = image.transpose(Image.FLIP_LEFT_RIGHT)
                 image = image.transpose(Image.FLIP_LEFT_RIGHT)
-            if self._absolute_transform.mirror_y:
+            if absolute_transform.mirror_y:
                 image = image.transpose(Image.FLIP_TOP_BOTTOM)
                 image = image.transpose(Image.FLIP_TOP_BOTTOM)
-            if self._absolute_transform.transpose_xy:
+            if absolute_transform.transpose_xy:
                 image = image.transpose(Image.TRANSPOSE)
                 image = image.transpose(Image.TRANSPOSE)
-            x *= self._absolute_transform.dx
-            y *= self._absolute_transform.dy
-            x += self._absolute_transform.x
-            y += self._absolute_transform.y
-        buffer.alpha_composite(image, (x, y))
-
-    # pylint: enable=too-many-locals
+            x *= absolute_transform.dx
+            y *= absolute_transform.dy
+            x += absolute_transform.x
+            y += absolute_transform.y
+
+        source_x = source_y = 0
+        if x < 0:
+            source_x = round(0 - x)
+            x = 0
+        if y < 0:
+            source_y = round(0 - y)
+            y = 0
+
+        x = round(x)
+        y = round(y)
+
+        if (
+            x <= buffer.width
+            and y <= buffer.height
+            and source_x <= image.width
+            and source_y <= image.height
+        ):
+            buffer.alpha_composite(image, (x, y), source=(source_x, source_y))
+
+    # pylint: enable=too-many-locals,too-many-branches
 
     @property
     def hidden(self):
         """True when the TileGrid is hidden. This may be False even
         when a part of a hidden Group."""
 
     @property
     def hidden(self):
         """True when the TileGrid is hidden. This may be False even
         when a part of a hidden Group."""
-        return self._hidden
+        return self._hidden_tilegrid
 
     @hidden.setter
     def hidden(self, value):
         if not isinstance(value, (bool, int)):
             raise ValueError("Expecting a boolean or integer value")
 
     @hidden.setter
     def hidden(self, value):
         if not isinstance(value, (bool, int)):
             raise ValueError("Expecting a boolean or integer value")
-        self._hidden = bool(value)
+        self._hidden_tilegrid = bool(value)
 
     @property
     def x(self):
 
     @property
     def x(self):
@@ -320,10 +397,7 @@ class TileGrid:
         """The pixel shader of the tilegrid."""
         return self._pixel_shader
 
         """The pixel shader of the tilegrid."""
         return self._pixel_shader
 
-    def __getitem__(self, index):
-        """Returns the tile index at the given index. The index can either be
-        an x,y tuple or an int equal to ``y * width + x``'.
-        """
+    def _extract_and_check_index(self, index):
         if isinstance(index, (tuple, list)):
             x = index[0]
             y = index[1]
         if isinstance(index, (tuple, list)):
             x = index[0]
             y = index[1]
@@ -333,21 +407,20 @@ class TileGrid:
             y = index // self._width
         if x > self._width or y > self._height or index >= len(self._tiles):
             raise ValueError("Tile index out of bounds")
             y = index // self._width
         if x > self._width or y > self._height or index >= len(self._tiles):
             raise ValueError("Tile index out of bounds")
+        return index
+
+    def __getitem__(self, index):
+        """Returns the tile index at the given index. The index can either be
+        an x,y tuple or an int equal to ``y * width + x``'.
+        """
+        index = self._extract_and_check_index(index)
         return self._tiles[index]
 
     def __setitem__(self, index, value):
         """Sets the tile index at the given index. The index can either be
         an x,y tuple or an int equal to ``y * width + x``.
         """
         return self._tiles[index]
 
     def __setitem__(self, index, value):
         """Sets the tile index at the given index. The index can either be
         an x,y tuple or an int equal to ``y * width + x``.
         """
-        if isinstance(index, (tuple, list)):
-            x = index[0]
-            y = index[1]
-            index = y * self._width + x
-        elif isinstance(index, int):
-            x = index % self._width
-            y = index // self._width
-        if x > self._width or y > self._height or index >= len(self._tiles):
-            raise ValueError("Tile index out of bounds")
+        index = self._extract_and_check_index(index)
         if not 0 <= value <= 255:
             raise ValueError("Tile value out of bounds")
         self._tiles[index] = value
         if not 0 <= value <= 255:
             raise ValueError("Tile value out of bounds")
         self._tiles[index] = value