]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blob - displayio/bitmap.py
Fix off by one issue for display drawing
[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
40 __version__ = "0.0.0-auto.0"
41 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
42
43 Rectangle = recordclass("Rectangle", "x1 y1 x2 y2")
44
45
46 class Bitmap:
47     """Stores values of a certain size in a 2D array"""
48
49     def __init__(self, width, height, value_count):
50         """Create a Bitmap object with the given fixed size. Each pixel stores a value that is
51         used to index into a corresponding palette. This enables differently colored sprites to
52         share the underlying Bitmap. value_count is used to minimize the memory used to store
53         the Bitmap.
54         """
55         self._width = width
56         self._height = height
57         self._read_only = False
58
59         if value_count < 0:
60             raise ValueError("value_count must be > 0")
61
62         bits = 1
63         while (value_count - 1) >> bits:
64             if bits < 8:
65                 bits = bits << 1
66             else:
67                 bits += 8
68
69         self._bits_per_value = bits
70
71         if (
72             self._bits_per_value > 8
73             and self._bits_per_value != 16
74             and self._bits_per_value != 32
75         ):
76             raise NotImplementedError("Invalid bits per value")
77
78         self._data = (width * height) * [0]
79         self._dirty_area = Rectangle(0, 0, width, height)
80
81     def __getitem__(self, index):
82         """
83         Returns the value at the given index. The index can either be
84         an x,y tuple or an int equal to `y * width + x`.
85         """
86         if isinstance(index, (tuple, list)):
87             index = (index[1] * self._width) + index[0]
88         if index >= len(self._data):
89             raise ValueError("Index {} is out of range".format(index))
90         return self._data[index]
91
92     def __setitem__(self, index, value):
93         """
94         Sets the value at the given index. The index can either be
95         an x,y tuple or an int equal to `y * width + x`.
96         """
97         if self._read_only:
98             raise RuntimeError("Read-only object")
99         if isinstance(index, (tuple, list)):
100             x = index[0]
101             y = index[1]
102             index = y * self._width + x
103         elif isinstance(index, int):
104             x = index % self._width
105             y = index // self._width
106         self._data[index] = value
107         if self._dirty_area.x1 == self._dirty_area.x2:
108             self._dirty_area.x1 = x
109             self._dirty_area.x2 = x + 1
110             self._dirty_area.y1 = y
111             self._dirty_area.y2 = y + 1
112         else:
113             if x < self._dirty_area.x1:
114                 self._dirty_area.x1 = x
115             elif x >= self._dirty_area.x2:
116                 self._dirty_area.x2 = x + 1
117             if y < self._dirty_area.y1:
118                 self._dirty_area.y1 = y
119             elif y >= self._dirty_area.y2:
120                 self._dirty_area.y2 = y + 1
121
122     def _finish_refresh(self):
123         self._dirty_area.x1 = 0
124         self._dirty_area.x2 = 0
125
126     def fill(self, value):
127         """Fills the bitmap with the supplied palette index value."""
128         self._data = (self._width * self._height) * [value]
129         self._dirty_area = Rectangle(0, 0, self._width, self._height)
130
131     @property
132     def width(self):
133         """Width of the bitmap. (read only)"""
134         return self._width
135
136     @property
137     def height(self):
138         """Height of the bitmap. (read only)"""
139         return self._height