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
24 from ._area import Area
26 __version__ = "0.0.0+auto.0"
27 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
31 """Stores values of a certain size in a 2D array"""
33 def __init__(self, width: int, height: int, value_count: int):
34 """Create a Bitmap object with the given fixed size. Each pixel stores a value that is
35 used to index into a corresponding palette. This enables differently colored sprites to
36 share the underlying Bitmap. value_count is used to minimize the memory used to store
39 self._bmp_width = width
40 self._bmp_height = height
41 self._read_only = False
44 raise ValueError("value_count must be > 0")
47 while (value_count - 1) >> bits:
53 self._bits_per_value = bits
56 self._bits_per_value > 8
57 and self._bits_per_value != 16
58 and self._bits_per_value != 32
60 raise NotImplementedError("Invalid bits per value")
62 self._image = Image.new("P", (width, height), 0)
63 self._dirty_area = RectangleStruct(0, 0, width, height)
65 def __getitem__(self, index: Union[Tuple[int, int], int]) -> int:
67 Returns the value at the given index. The index can either be
68 an x,y tuple or an int equal to `y * width + x`.
70 if isinstance(index, (tuple, list)):
72 elif isinstance(index, int):
73 x = index % self._bmp_width
74 y = index // self._bmp_width
76 raise TypeError("Index is not an int, list, or tuple")
78 if x > self._image.width or y > self._image.height:
79 raise ValueError(f"Index {index} is out of range")
80 return self._get_pixel(x, y)
82 def _get_pixel(self, x: int, y: int) -> int:
83 return self._image.getpixel((x, y))
85 def __setitem__(self, index: Union[Tuple[int, int], int], value: int) -> None:
87 Sets the value at the given index. The index can either be
88 an x,y tuple or an int equal to `y * width + x`.
91 raise RuntimeError("Read-only object")
92 if isinstance(index, (tuple, list)):
95 index = y * self._bmp_width + x
96 elif isinstance(index, int):
97 x = index % self._bmp_width
98 y = index // self._bmp_width
99 self._image.putpixel((x, y), value)
100 if self._dirty_area.x1 == self._dirty_area.x2:
101 self._dirty_area.x1 = x
102 self._dirty_area.x2 = x + 1
103 self._dirty_area.y1 = y
104 self._dirty_area.y2 = y + 1
106 if x < self._dirty_area.x1:
107 self._dirty_area.x1 = x
108 elif x >= self._dirty_area.x2:
109 self._dirty_area.x2 = x + 1
110 if y < self._dirty_area.y1:
111 self._dirty_area.y1 = y
112 elif y >= self._dirty_area.y2:
113 self._dirty_area.y2 = y + 1
115 def _finish_refresh(self):
116 self._dirty_area.x1 = 0
117 self._dirty_area.x2 = 0
119 def fill(self, value: int) -> None:
120 """Fills the bitmap with the supplied palette index value."""
121 self._image = Image.new("P", (self._bmp_width, self._bmp_height), value)
122 self._dirty_area = RectangleStruct(0, 0, self._bmp_width, self._bmp_height)
128 source_bitmap: Bitmap,
136 """Inserts the source_bitmap region defined by rectangular boundaries"""
137 # pylint: disable=invalid-name
139 x2 = source_bitmap.width
141 y2 = source_bitmap.height
143 # Rearrange so that x1 < x2 and y1 < y2
149 # Ensure that x2 and y2 are within source bitmap size
150 x2 = min(x2, source_bitmap.width)
151 y2 = min(y2, source_bitmap.height)
153 for y_count in range(y2 - y1):
154 for x_count in range(x2 - x1):
155 x_placement = x + x_count
156 y_placement = y + y_count
158 if (self.width > x_placement >= 0) and (
159 self.height > y_placement >= 0
160 ): # ensure placement is within target bitmap
161 # get the palette index from the source bitmap
162 this_pixel_color = source_bitmap[
165 y_count * source_bitmap.width
166 ) # Direct index into a bitmap array is speedier than [x,y] tuple
171 if (skip_index is None) or (this_pixel_color != skip_index):
172 self[ # Direct index into a bitmap array is speedier than [x,y] tuple
173 y_placement * self.width + x_placement
175 elif y_placement > self.height:
178 def dirty(self, x1: int = 0, y1: int = 0, x2: int = -1, y2: int = -1) -> None:
179 """Inform displayio of bitmap updates done via the buffer protocol."""
180 # pylint: disable=invalid-name
184 y2 = self._bmp_height
185 area = Area(x1, y1, x2, y2)
187 area.union(self._dirty_area, area)
188 bitmap_area = Area(0, 0, self._bmp_width, self._bmp_height)
189 area.compute_overlap(bitmap_area, self._dirty_area)
191 def _finish_refresh(self):
194 self._dirty_area.x1 = 0
195 self._dirty_area.x2 = 0
197 def _get_refresh_areas(self, areas: list[Area]) -> None:
198 if self._dirty_area.x1 == self._dirty_area.x2 or self._read_only:
200 areas.append(self._dirty_area)
203 def width(self) -> int:
204 """Width of the bitmap. (read only)"""
205 return self._bmp_width
208 def height(self) -> int:
209 """Height of the bitmap. (read only)"""
210 return self._bmp_height