]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blob - displayio/_shape.py
Fewer bugs, more code, shape done
[hackapet/Adafruit_Blinka_Displayio.git] / displayio / _shape.py
1 # SPDX-FileCopyrightText: 2020 Melissa LeBlanc-Williams for Adafruit Industries
2 #
3 # SPDX-License-Identifier: MIT
4
5
6 """
7 `displayio.shape`
8 ================================================================================
9
10 displayio for Blinka
11
12 **Software and Dependencies:**
13
14 * Adafruit Blinka:
15   https://github.com/adafruit/Adafruit_Blinka/releases
16
17 * Author(s): Melissa LeBlanc-Williams
18
19 """
20
21 import struct
22 from ._bitmap import Bitmap
23 from ._area import Area
24 from ._helpers import clamp
25
26 __version__ = "0.0.0+auto.0"
27 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
28
29
30 class Shape(Bitmap):
31     """Create a Shape object with the given fixed size. Each pixel is one bit and is stored
32     by the column boundaries of the shape on each row. Each row’s boundary defaults to the
33     full row.
34     """
35
36     def __init__(
37         self, width: int, height: int, *, mirror_x: bool = False, mirror_y: bool = False
38     ):
39         """Create a Shape object with the given fixed size. Each pixel is one bit and is
40         stored by the column boundaries of the shape on each row. Each row’s boundary
41         defaults to the full row.
42         """
43         self._mirror_x = mirror_x
44         self._mirror_y = mirror_y
45         self._width = width
46         self._height = height
47         if self._mirror_x:
48             width //= 2
49             width += self._width % 2
50         self._half_width = width
51         if self._mirror_y:
52             height //= 2
53             height += self._height % 2
54         self._half_height = height
55         self._data = bytearray(height * struct.calcsize("HH"))
56         for i in range(height):
57             self._data[2 * i] = 0
58             self._data[2 * i + 1] = width
59
60         self._dirty_area = Area(0, 0, width, height)
61         super().__init__(width, height, 2)
62
63     def set_boundary(self, y: int, start_x: int, end_x: int) -> None:
64         """Loads pre-packed data into the given row."""
65         max_y = self._height - 1
66         if self._mirror_y:
67             max_y = self._half_height - 1
68         y = clamp(y, 0, max_y)
69         max_x = self._width - 1
70         if self._mirror_x:
71             max_x = self._half_width - 1
72         start_x = clamp(start_x, 0, max_x)
73         end_x = clamp(end_x, 0, max_x)
74
75         # find x-boundaries for updating based on current data and start_x, end_x, and mirror_x
76         lower_x = min(start_x, self._data[2 * y])
77
78         if self._mirror_x:
79             upper_x = (
80                 self._width - lower_x + 1
81             )  # dirty rectangles are treated with max value exclusive
82         else:
83             upper_x = max(
84                 end_x, self._data[2 * y + 1]
85             )  # dirty rectangles are treated with max value exclusive
86
87         # find y-boundaries based on y and mirror_y
88         lower_y = y
89
90         if self._mirror_y:
91             upper_y = (
92                 self._height - lower_y + 1
93             )  # dirty rectangles are treated with max value exclusive
94         else:
95             upper_y = y + 1  # dirty rectangles are treated with max value exclusive
96
97         self._data[2 * y] = start_x  # update the data array with the new boundaries
98         self._data[2 * y + 1] = end_x
99
100         if self._dirty_area.x1 == self._dirty_area.x2:  # dirty region is empty
101             self._dirty_area.x1 = lower_x
102             self._dirty_area.x2 = upper_x
103             self._dirty_area.y1 = lower_y
104             self._dirty_area.y2 = upper_y
105         else:
106             self._dirty_area.x1 = min(lower_x, self._dirty_area.x1)
107             self._dirty_area.x2 = max(upper_x, self._dirty_area.x2)
108             self._dirty_area.y1 = min(lower_y, self._dirty_area.y1)
109             self._dirty_area.y2 = max(upper_y, self._dirty_area.y2)
110
111     def _get_pixel(self, x: int, y: int) -> int:
112         if x >= self._width or x < 0 or y >= self._height or y < 0:
113             return 0
114         if self._mirror_x and x >= self._half_width:
115             x = self._width - x - 1
116         if self._mirror_y and y >= self._half_height:
117             y = self._height - y - 1
118         start_x = self._data[2 * y]
119         end_x = self._data[2 * y + 1]
120         if x < start_x or x >= end_x:
121             return 0
122         return 1
123
124     def _finish_refresh(self):
125         self._dirty_area.x1 = 0
126         self._dirty_area.x2 = 0
127
128     def _get_refresh_areas(self, areas: list[Area]) -> None:
129         if self._dirty_area.x1 != self._dirty_area.x2:
130             areas.append(self._dirty_area)