1 # SPDX-FileCopyrightText: 2020 Melissa LeBlanc-Williams for Adafruit Industries
 
   3 # SPDX-License-Identifier: MIT
 
   7 ================================================================================
 
  11 **Software and Dependencies:**
 
  14   https://github.com/adafruit/Adafruit_Blinka/releases
 
  16 * Author(s): Melissa LeBlanc-Williams
 
  20 from __future__ import annotations
 
  21 from typing import Union, Tuple
 
  23 from ._structs import RectangleStruct
 
  25 __version__ = "0.0.0+auto.0"
 
  26 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
 
  30     """Stores values of a certain size in a 2D array"""
 
  32     def __init__(self, width: int, height: int, value_count: int):
 
  33         """Create a Bitmap object with the given fixed size. Each pixel stores a value that is
 
  34         used to index into a corresponding palette. This enables differently colored sprites to
 
  35         share the underlying Bitmap. value_count is used to minimize the memory used to store
 
  38         self._bmp_width = width
 
  39         self._bmp_height = height
 
  40         self._read_only = False
 
  43             raise ValueError("value_count must be > 0")
 
  46         while (value_count - 1) >> bits:
 
  52         self._bits_per_value = bits
 
  55             self._bits_per_value > 8
 
  56             and self._bits_per_value != 16
 
  57             and self._bits_per_value != 32
 
  59             raise NotImplementedError("Invalid bits per value")
 
  61         self._image = Image.new("P", (width, height), 0)
 
  62         self._dirty_area = RectangleStruct(0, 0, width, height)
 
  64     def __getitem__(self, index: Union[Tuple[int, int], int]) -> int:
 
  66         Returns the value at the given index. The index can either be
 
  67         an x,y tuple or an int equal to `y * width + x`.
 
  69         if isinstance(index, (tuple, list)):
 
  71         elif isinstance(index, int):
 
  72             x = index % self._bmp_width
 
  73             y = index // self._bmp_width
 
  75             raise TypeError("Index is not an int, list, or tuple")
 
  77         if x > self._image.width or y > self._image.height:
 
  78             raise ValueError(f"Index {index} is out of range")
 
  79         return self._image.getpixel((x, y))
 
  81     def __setitem__(self, index: Union[Tuple[int, int], int], value: int) -> None:
 
  83         Sets the value at the given index. The index can either be
 
  84         an x,y tuple or an int equal to `y * width + x`.
 
  87             raise RuntimeError("Read-only object")
 
  88         if isinstance(index, (tuple, list)):
 
  91             index = y * self._bmp_width + x
 
  92         elif isinstance(index, int):
 
  93             x = index % self._bmp_width
 
  94             y = index // self._bmp_width
 
  95         self._image.putpixel((x, y), value)
 
  96         if self._dirty_area.x1 == self._dirty_area.x2:
 
  97             self._dirty_area.x1 = x
 
  98             self._dirty_area.x2 = x + 1
 
  99             self._dirty_area.y1 = y
 
 100             self._dirty_area.y2 = y + 1
 
 102             if x < self._dirty_area.x1:
 
 103                 self._dirty_area.x1 = x
 
 104             elif x >= self._dirty_area.x2:
 
 105                 self._dirty_area.x2 = x + 1
 
 106             if y < self._dirty_area.y1:
 
 107                 self._dirty_area.y1 = y
 
 108             elif y >= self._dirty_area.y2:
 
 109                 self._dirty_area.y2 = y + 1
 
 111     def _finish_refresh(self):
 
 112         self._dirty_area.x1 = 0
 
 113         self._dirty_area.x2 = 0
 
 115     def fill(self, value: int) -> None:
 
 116         """Fills the bitmap with the supplied palette index value."""
 
 117         self._image = Image.new("P", (self._bmp_width, self._bmp_height), value)
 
 118         self._dirty_area = RectangleStruct(0, 0, self._bmp_width, self._bmp_height)
 
 124         source_bitmap: Bitmap,
 
 132         # pylint: disable=unnecessary-pass, invalid-name
 
 133         """Inserts the source_bitmap region defined by rectangular boundaries"""
 
 135             x2 = source_bitmap.width
 
 137             y2 = source_bitmap.height
 
 139         # Rearrange so that x1 < x2 and y1 < y2
 
 145         # Ensure that x2 and y2 are within source bitmap size
 
 146         x2 = min(x2, source_bitmap.width)
 
 147         y2 = min(y2, source_bitmap.height)
 
 149         for y_count in range(y2 - y1):
 
 150             for x_count in range(x2 - x1):
 
 151                 x_placement = x + x_count
 
 152                 y_placement = y + y_count
 
 154                 if (self.width > x_placement >= 0) and (
 
 155                     self.height > y_placement >= 0
 
 156                 ):  # ensure placement is within target bitmap
 
 158                     # get the palette index from the source bitmap
 
 159                     this_pixel_color = source_bitmap[
 
 162                             y_count * source_bitmap.width
 
 163                         )  # Direct index into a bitmap array is speedier than [x,y] tuple
 
 168                     if (skip_index is None) or (this_pixel_color != skip_index):
 
 169                         self[  # Direct index into a bitmap array is speedier than [x,y] tuple
 
 170                             y_placement * self.width + x_placement
 
 172                 elif y_placement > self.height:
 
 175     def dirty(self, x1: int = 0, y1: int = 0, x2: int = -1, y2: int = -1) -> None:
 
 176         # pylint: disable=unnecessary-pass, invalid-name
 
 177         """Inform displayio of bitmap updates done via the buffer protocol."""
 
 181     def width(self) -> int:
 
 182         """Width of the bitmap. (read only)"""
 
 183         return self._bmp_width
 
 186     def height(self) -> int:
 
 187         """Height of the bitmap. (read only)"""
 
 188         return self._bmp_height