]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blobdiff - displayio/_bitmap.py
Added typing and missing CP7 functions
[hackapet/Adafruit_Blinka_Displayio.git] / displayio / _bitmap.py
diff --git a/displayio/_bitmap.py b/displayio/_bitmap.py
new file mode 100644 (file)
index 0000000..63125aa
--- /dev/null
@@ -0,0 +1,151 @@
+# SPDX-FileCopyrightText: 2020 Melissa LeBlanc-Williams for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+"""
+`displayio.bitmap`
+================================================================================
+
+displayio for Blinka
+
+**Software and Dependencies:**
+
+* Adafruit Blinka:
+  https://github.com/adafruit/Adafruit_Blinka/releases
+
+* Author(s): Melissa LeBlanc-Williams
+
+"""
+
+from __future__ import annotations
+from typing import Union, Tuple
+from recordclass import recordclass
+from PIL import Image
+
+__version__ = "0.0.0-auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
+
+Rectangle = recordclass("Rectangle", "x1 y1 x2 y2")
+
+
+class Bitmap:
+    """Stores values of a certain size in a 2D array"""
+
+    def __init__(self, width: int, height: int, value_count: int):
+        """Create a Bitmap object with the given fixed size. Each pixel stores a value that is
+        used to index into a corresponding palette. This enables differently colored sprites to
+        share the underlying Bitmap. value_count is used to minimize the memory used to store
+        the Bitmap.
+        """
+        self._bmp_width = width
+        self._bmp_height = height
+        self._read_only = False
+
+        if value_count < 0:
+            raise ValueError("value_count must be > 0")
+
+        bits = 1
+        while (value_count - 1) >> bits:
+            if bits < 8:
+                bits = bits << 1
+            else:
+                bits += 8
+
+        self._bits_per_value = bits
+
+        if (
+            self._bits_per_value > 8
+            and self._bits_per_value != 16
+            and self._bits_per_value != 32
+        ):
+            raise NotImplementedError("Invalid bits per value")
+
+        self._image = Image.new("P", (width, height), 0)
+        self._dirty_area = Rectangle(0, 0, width, height)
+
+    def __getitem__(self, index: Union[Tuple[int, int], int]) -> int:
+        """
+        Returns the value 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, y = index
+        elif isinstance(index, int):
+            x = index % self._bmp_width
+            y = index // self._bmp_width
+        else:
+            raise TypeError("Index is not an int, list, or tuple")
+
+        if x > self._image.width or y > self._image.height:
+            raise ValueError(f"Index {index} is out of range")
+        return self._image.getpixel((x, y))
+
+    def __setitem__(self, index: Union[Tuple[int, int], int], value: int) -> None:
+        """
+        Sets the value at the given index. The index can either be
+        an x,y tuple or an int equal to `y * width + x`.
+        """
+        if self._read_only:
+            raise RuntimeError("Read-only object")
+        if isinstance(index, (tuple, list)):
+            x = index[0]
+            y = index[1]
+            index = y * self._bmp_width + x
+        elif isinstance(index, int):
+            x = index % self._bmp_width
+            y = index // self._bmp_width
+        self._image.putpixel((x, y), value)
+        if self._dirty_area.x1 == self._dirty_area.x2:
+            self._dirty_area.x1 = x
+            self._dirty_area.x2 = x + 1
+            self._dirty_area.y1 = y
+            self._dirty_area.y2 = y + 1
+        else:
+            if x < self._dirty_area.x1:
+                self._dirty_area.x1 = x
+            elif x >= self._dirty_area.x2:
+                self._dirty_area.x2 = x + 1
+            if y < self._dirty_area.y1:
+                self._dirty_area.y1 = y
+            elif y >= self._dirty_area.y2:
+                self._dirty_area.y2 = y + 1
+
+    def _finish_refresh(self):
+        self._dirty_area.x1 = 0
+        self._dirty_area.x2 = 0
+
+    def fill(self, value: int) -> None:
+        """Fills the bitmap with the supplied palette index value."""
+        self._image = Image.new("P", (self._bmp_width, self._bmp_height), value)
+        self._dirty_area = Rectangle(0, 0, self._bmp_width, self._bmp_height)
+
+    def blit(
+        self,
+        x: int,
+        y: int,
+        source_bitmap: Bitmap,
+        *,
+        x1: int,
+        y1: int,
+        x2: int,
+        y2: int,
+        skip_index: int,
+    ) -> None:
+        # pylint: disable=unnecessary-pass
+        """Inserts the source_bitmap region defined by rectangular boundaries"""
+        pass
+
+    def dirty(self, x1: int = 0, y1: int = 0, x2: int = -1, y2: int = -1) -> None:
+        # pylint: disable=unnecessary-pass
+        """Inform displayio of bitmap updates done via the buffer protocol."""
+        pass
+
+    @property
+    def width(self) -> int:
+        """Width of the bitmap. (read only)"""
+        return self._bmp_width
+
+    @property
+    def height(self) -> int:
+        """Height of the bitmap. (read only)"""
+        return self._bmp_height