]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blob - displayio/bitmap.py
41979cafa392381ef7e2b2368adbceb2d92b9796
[hackapet/Adafruit_Blinka_Displayio.git] / displayio / bitmap.py
1 # The MIT License (MIT)
2 #
3 # Copyright (c) 2020 Melissa LeBlanc-Williams for Adafruit Industries
4 #
5 # Permission is hereby granted, free of charge, to any person obtaining a copy
6 # of this software and associated documentation files (the "Software"), to deal
7 # in the Software without restriction, including without limitation the rights
8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 # copies of the Software, and to permit persons to whom the Software is
10 # furnished to do so, subject to the following conditions:
11 #
12 # The above copyright notice and this permission notice shall be included in
13 # all copies or substantial portions of the Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 # THE SOFTWARE.
22
23 """
24 `displayio.bitmap`
25 ================================================================================
26
27 displayio for Blinka
28
29 **Software and Dependencies:**
30
31 * Adafruit Blinka:
32   https://github.com/adafruit/Adafruit_Blinka/releases
33
34 * Author(s): Melissa LeBlanc-Williams
35
36 """
37
38 from recordclass import recordclass
39 from PIL import Image
40
41 __version__ = "0.0.0-auto.0"
42 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
43
44 Rectangle = recordclass("Rectangle", "x1 y1 x2 y2")
45
46
47 class Bitmap:
48     """Stores values of a certain size in a 2D array"""
49
50     def __init__(self, width, height, value_count):
51         """Create a Bitmap object with the given fixed size. Each pixel stores a value that is
52         used to index into a corresponding palette. This enables differently colored sprites to
53         share the underlying Bitmap. value_count is used to minimize the memory used to store
54         the Bitmap.
55         """
56         self._bmp_width = width
57         self._bmp_height = height
58         self._read_only = False
59
60         if value_count < 0:
61             raise ValueError("value_count must be > 0")
62
63         bits = 1
64         while (value_count - 1) >> bits:
65             if bits < 8:
66                 bits = bits << 1
67             else:
68                 bits += 8
69
70         self._bits_per_value = bits
71
72         if (
73             self._bits_per_value > 8
74             and self._bits_per_value != 16
75             and self._bits_per_value != 32
76         ):
77             raise NotImplementedError("Invalid bits per value")
78
79         self._image = Image.new("P", (width, height), 0)
80         self._dirty_area = Rectangle(0, 0, width, height)
81
82     def __getitem__(self, index):
83         """
84         Returns the value at the given index. The index can either be
85         an x,y tuple or an int equal to `y * width + x`.
86         """
87         if isinstance(index, (tuple, list)):
88             x, y = index
89         elif isinstance(index, int):
90             x = index % self._bmp_width
91             y = index // self._bmp_width
92         else:
93             raise TypeError("Index is not an int, list, or tuple")
94
95         if x > self._image.width or y > self._image.height:
96             raise ValueError("Index {} is out of range".format(index))
97         return self._image.getpixel((x, y))
98
99     def __setitem__(self, index, value):
100         """
101         Sets the value at the given index. The index can either be
102         an x,y tuple or an int equal to `y * width + x`.
103         """
104         if self._read_only:
105             raise RuntimeError("Read-only object")
106         if isinstance(index, (tuple, list)):
107             x = index[0]
108             y = index[1]
109             index = y * self._bmp_width + x
110         elif isinstance(index, int):
111             x = index % self._bmp_width
112             y = index // self._bmp_width
113         self._image.putpixel((x, y), value)
114         if self._dirty_area.x1 == self._dirty_area.x2:
115             self._dirty_area.x1 = x
116             self._dirty_area.x2 = x + 1
117             self._dirty_area.y1 = y
118             self._dirty_area.y2 = y + 1
119         else:
120             if x < self._dirty_area.x1:
121                 self._dirty_area.x1 = x
122             elif x >= self._dirty_area.x2:
123                 self._dirty_area.x2 = x + 1
124             if y < self._dirty_area.y1:
125                 self._dirty_area.y1 = y
126             elif y >= self._dirty_area.y2:
127                 self._dirty_area.y2 = y + 1
128
129     def _finish_refresh(self):
130         self._dirty_area.x1 = 0
131         self._dirty_area.x2 = 0
132
133     def fill(self, value):
134         """Fills the bitmap with the supplied palette index value."""
135         self._image = Image.new("P", (self._bmp_width, self._bmp_height), value)
136         self._dirty_area = Rectangle(0, 0, self._bmp_width, self._bmp_height)
137
138     @property
139     def width(self):
140         """Width of the bitmap. (read only)"""
141         return self._bmp_width
142
143     @property
144     def height(self):
145         """Height of the bitmap. (read only)"""
146         return self._bmp_height