]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blob - displayio/_bitmap.py
Added typing and missing CP7 functions
[hackapet/Adafruit_Blinka_Displayio.git] / displayio / _bitmap.py
1 # SPDX-FileCopyrightText: 2020 Melissa LeBlanc-Williams for Adafruit Industries
2 #
3 # SPDX-License-Identifier: MIT
4
5 """
6 `displayio.bitmap`
7 ================================================================================
8
9 displayio for Blinka
10
11 **Software and Dependencies:**
12
13 * Adafruit Blinka:
14   https://github.com/adafruit/Adafruit_Blinka/releases
15
16 * Author(s): Melissa LeBlanc-Williams
17
18 """
19
20 from __future__ import annotations
21 from typing import Union, Tuple
22 from recordclass import recordclass
23 from PIL import Image
24
25 __version__ = "0.0.0-auto.0"
26 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
27
28 Rectangle = recordclass("Rectangle", "x1 y1 x2 y2")
29
30
31 class Bitmap:
32     """Stores values of a certain size in a 2D array"""
33
34     def __init__(self, width: int, height: int, value_count: int):
35         """Create a Bitmap object with the given fixed size. Each pixel stores a value that is
36         used to index into a corresponding palette. This enables differently colored sprites to
37         share the underlying Bitmap. value_count is used to minimize the memory used to store
38         the Bitmap.
39         """
40         self._bmp_width = width
41         self._bmp_height = height
42         self._read_only = False
43
44         if value_count < 0:
45             raise ValueError("value_count must be > 0")
46
47         bits = 1
48         while (value_count - 1) >> bits:
49             if bits < 8:
50                 bits = bits << 1
51             else:
52                 bits += 8
53
54         self._bits_per_value = bits
55
56         if (
57             self._bits_per_value > 8
58             and self._bits_per_value != 16
59             and self._bits_per_value != 32
60         ):
61             raise NotImplementedError("Invalid bits per value")
62
63         self._image = Image.new("P", (width, height), 0)
64         self._dirty_area = Rectangle(0, 0, width, height)
65
66     def __getitem__(self, index: Union[Tuple[int, int], int]) -> int:
67         """
68         Returns the value at the given index. The index can either be
69         an x,y tuple or an int equal to `y * width + x`.
70         """
71         if isinstance(index, (tuple, list)):
72             x, y = index
73         elif isinstance(index, int):
74             x = index % self._bmp_width
75             y = index // self._bmp_width
76         else:
77             raise TypeError("Index is not an int, list, or tuple")
78
79         if x > self._image.width or y > self._image.height:
80             raise ValueError(f"Index {index} is out of range")
81         return self._image.getpixel((x, y))
82
83     def __setitem__(self, index: Union[Tuple[int, int], int], value: int) -> None:
84         """
85         Sets the value at the given index. The index can either be
86         an x,y tuple or an int equal to `y * width + x`.
87         """
88         if self._read_only:
89             raise RuntimeError("Read-only object")
90         if isinstance(index, (tuple, list)):
91             x = index[0]
92             y = index[1]
93             index = y * self._bmp_width + x
94         elif isinstance(index, int):
95             x = index % self._bmp_width
96             y = index // self._bmp_width
97         self._image.putpixel((x, y), value)
98         if self._dirty_area.x1 == self._dirty_area.x2:
99             self._dirty_area.x1 = x
100             self._dirty_area.x2 = x + 1
101             self._dirty_area.y1 = y
102             self._dirty_area.y2 = y + 1
103         else:
104             if x < self._dirty_area.x1:
105                 self._dirty_area.x1 = x
106             elif x >= self._dirty_area.x2:
107                 self._dirty_area.x2 = x + 1
108             if y < self._dirty_area.y1:
109                 self._dirty_area.y1 = y
110             elif y >= self._dirty_area.y2:
111                 self._dirty_area.y2 = y + 1
112
113     def _finish_refresh(self):
114         self._dirty_area.x1 = 0
115         self._dirty_area.x2 = 0
116
117     def fill(self, value: int) -> None:
118         """Fills the bitmap with the supplied palette index value."""
119         self._image = Image.new("P", (self._bmp_width, self._bmp_height), value)
120         self._dirty_area = Rectangle(0, 0, self._bmp_width, self._bmp_height)
121
122     def blit(
123         self,
124         x: int,
125         y: int,
126         source_bitmap: Bitmap,
127         *,
128         x1: int,
129         y1: int,
130         x2: int,
131         y2: int,
132         skip_index: int,
133     ) -> None:
134         # pylint: disable=unnecessary-pass
135         """Inserts the source_bitmap region defined by rectangular boundaries"""
136         pass
137
138     def dirty(self, x1: int = 0, y1: int = 0, x2: int = -1, y2: int = -1) -> None:
139         # pylint: disable=unnecessary-pass
140         """Inform displayio of bitmap updates done via the buffer protocol."""
141         pass
142
143     @property
144     def width(self) -> int:
145         """Width of the bitmap. (read only)"""
146         return self._bmp_width
147
148     @property
149     def height(self) -> int:
150         """Height of the bitmap. (read only)"""
151         return self._bmp_height