]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blob - displayio/_displaycore.py
Fewer bugs, more code, shape done
[hackapet/Adafruit_Blinka_Displayio.git] / displayio / _displaycore.py
1 # SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries
2 # SPDX-FileCopyrightText: 2021 James Carr
3 #
4 # SPDX-License-Identifier: MIT
5
6 """
7 `displayio._displaycore`
8 ================================================================================
9
10 Super class of the display classes
11
12 **Software and Dependencies:**
13
14 * Adafruit Blinka:
15   https://github.com/adafruit/Adafruit_Blinka/releases
16
17 * Author(s): James Carr, Melissa LeBlanc-Williams
18
19 """
20
21 __version__ = "0.0.0+auto.0"
22 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_Displayio.git"
23
24
25 import time
26 import struct
27 import circuitpython_typing
28 from paralleldisplay import ParallelBus
29 from ._fourwire import FourWire
30 from ._group import Group
31 from ._i2cdisplay import I2CDisplay
32 from ._structs import ColorspaceStruct, TransformStruct
33 from ._area import Area
34 from ._displaybus import _DisplayBus
35 from ._helpers import bswap16
36 from ._constants import (
37     CHIP_SELECT_UNTOUCHED,
38     CHIP_SELECT_TOGGLE_EVERY_BYTE,
39     DISPLAY_COMMAND,
40     DISPLAY_DATA,
41     NO_COMMAND,
42 )
43
44
45 class _DisplayCore:
46     # pylint: disable=too-many-arguments, too-many-instance-attributes, too-many-locals, too-many-branches, too-many-statements
47
48     def __init__(
49         self,
50         bus,
51         width: int,
52         height: int,
53         ram_width: int,
54         ram_height: int,
55         colstart: int,
56         rowstart: int,
57         rotation: int,
58         color_depth: int,
59         grayscale: bool,
60         pixels_in_byte_share_row: bool,
61         bytes_per_cell: int,
62         reverse_pixels_in_byte: bool,
63         reverse_bytes_in_word: bool,
64         column_command: int,
65         row_command: int,
66         set_current_column_command: int,
67         set_current_row_command: int,
68         data_as_commands: bool,
69         always_toggle_chip_select: bool,
70         sh1107_addressing: bool,
71         address_little_endian: bool,
72     ):
73         self.colorspace = ColorspaceStruct(
74             depth=color_depth,
75             grayscale=grayscale,
76             grayscale_bit=8 - color_depth,
77             pixels_in_byte_share_row=pixels_in_byte_share_row,
78             bytes_per_cell=bytes_per_cell,
79             reverse_pixels_in_byte=reverse_pixels_in_byte,
80             reverse_bytes_in_word=reverse_bytes_in_word,
81             dither=False,
82         )
83         self.current_group = None
84         self.colstart = colstart
85         self.rowstart = rowstart
86         self.last_refresh = 0
87
88         self.column_command = column_command
89         self.row_command = row_command
90         self.set_current_column_command = set_current_column_command
91         self.set_current_row_command = set_current_row_command
92         self.data_as_commands = data_as_commands
93         self.always_toggle_chip_select = always_toggle_chip_select
94         self.sh1107_addressing = sh1107_addressing
95         self.address_little_endian = address_little_endian
96
97         self.refresh_in_progress = False
98         self.full_refresh = False
99         self.last_refresh = 0
100
101         if bus:
102             if isinstance(bus, (FourWire, I2CDisplay, ParallelBus)):
103                 self._bus_reset = bus.reset
104                 self._begin_transaction = bus._begin_transaction
105                 self._send = bus._send
106                 self._end_transaction = bus._end_transaction
107             else:
108                 raise ValueError("Unsupported display bus type")
109
110         self._bus = bus
111         self.area = Area(0, 0, width, height)
112
113         self.width = width
114         self.height = height
115         self.ram_width = ram_width
116         self.ram_height = ram_height
117         self.rotation = rotation
118         self.transform = TransformStruct()
119
120     def set_rotation(self, rotation: int) -> None:
121         """
122         Sets the rotation of the display as an int in degrees.
123         """
124         # pylint: disable=protected-access, too-many-branches
125         transposed = self.rotation in (90, 270)
126         will_be_transposed = rotation in (90, 270)
127         if transposed != will_be_transposed:
128             self.width, self.height = self.height, self.width
129
130         height = self.height
131         width = self.width
132
133         rotation %= 360
134         self.rotation = rotation
135         self.transform.x = 0
136         self.transform.y = 0
137         self.transform.scale = 1
138         self.transform.mirror_x = False
139         self.transform.mirror_y = False
140         self.transform.transpose_xy = False
141
142         if rotation in (0, 180):
143             if rotation == 180:
144                 self.transform.mirror_x = True
145                 self.transform.mirror_y = True
146         else:
147             self.transform.transpose_xy = True
148             if rotation == 270:
149                 self.transform.mirror_y = True
150             else:
151                 self.transform.mirror_x = True
152
153         self.area.x1 = 0
154         self.area.y1 = 0
155         self.area.next = None
156
157         self.transform.dx = 1
158         self.transform.dy = 1
159         if self.transform.transpose_xy:
160             self.area.x2 = height
161             self.area.y2 = width
162             if self.transform.mirror_x:
163                 self.transform.x = height
164                 self.transform.dx = -1
165             if self.transform.mirror_y:
166                 self.transform.y = width
167                 self.transform.dy = -1
168         else:
169             self.area.x2 = width
170             self.area.y2 = height
171             if self.transform.mirror_x:
172                 self.transform.x = width
173                 self.transform.dx = -1
174             if self.transform.mirror_y:
175                 self.transform.y = height
176                 self.transform.dy = -1
177
178         if self.current_group is not None:
179             self.current_group._update_transform(self.transform)
180
181     def show(self, root_group: Group) -> bool:
182         # pylint: disable=protected-access
183
184         """
185         Switches to displaying the given group of layers. When group is `None`, the
186         default CircuitPython terminal will be shown.
187
188         :param Optional[displayio.Group] root_group: The group to show.
189         """
190
191         """
192         # TODO: Implement Supervisor
193         if root_group is None:
194             circuitpython_splash = _Supervisor().circuitpython_splash
195             if not circuitpython_splash._in_group:
196                 root_group = circuitpython_splash
197             elif self.current_group == circuitpython_splash:
198                 return True
199         """
200
201         if root_group == self.current_group:
202             return True
203
204         if root_group is not None and root_group._in_group:
205             return False
206
207         if self.current_group is not None:
208             self.current_group._in_group = False
209
210         if root_group is not None:
211             root_group._update_transform(self.transform)
212             root_group._in_group = True
213
214         self.current_group = root_group
215         self.full_refresh = True
216
217         return True
218
219     def start_refresh(self) -> bool:
220         # pylint: disable=protected-access
221         """Mark the display core as currently being refreshed"""
222
223         if self.refresh_in_progress:
224             return False
225
226         self.refresh_in_progress = True
227         self.last_refresh = time.monotonic() * 1000
228         return True
229
230     def finish_refresh(self) -> None:
231         # pylint: disable=protected-access
232         """Unmark the display core as currently being refreshed"""
233
234         if self.current_group is not None:
235             self.current_group._finish_refresh()
236
237         self.full_refresh = False
238         self.refresh_in_progress = False
239         self.last_refresh = time.monotonic() * 1000
240
241     def release_display_core(self) -> None:
242         """Release the display from the current group"""
243         # pylint: disable=protected-access
244
245         if self.current_group is not None:
246             self.current_group._in_group = False
247
248     def fill_area(
249         self,
250         area: Area,
251         mask: circuitpython_typing.WriteableBuffer,
252         buffer: circuitpython_typing.WriteableBuffer,
253     ) -> bool:
254         """Call the current group's fill area function"""
255         if self.current_group is not None:
256             return self.current_group._fill_area(  # pylint: disable=protected-access
257                 self.colorspace, area, mask, buffer
258             )
259         return False
260
261     def clip_area(self, area: Area, clipped: Area) -> bool:
262         """Shrink the area to the region shared by the two areas"""
263
264         overlaps = self.area.compute_overlap(area, clipped)
265         if not overlaps:
266             return False
267
268         # Expand the area if we have multiple pixels per byte and we need to byte align the bounds
269         if self.colorspace.depth < 8:
270             pixels_per_byte = (
271                 8 // self.colorspace.depth * self.colorspace.bytes_per_cell
272             )
273             if self.colorspace.pixels_in_byte_share_row:
274                 if clipped.x1 % pixels_per_byte != 0:
275                     clipped.x1 -= clipped.x1 % pixels_per_byte
276                 if clipped.x2 % pixels_per_byte != 0:
277                     clipped.x2 += pixels_per_byte - clipped.x2 % pixels_per_byte
278             else:
279                 if clipped.y1 % pixels_per_byte != 0:
280                     clipped.y1 -= clipped.y1 % pixels_per_byte
281                 if clipped.y2 % pixels_per_byte != 0:
282                     clipped.y2 += pixels_per_byte - clipped.y2 % pixels_per_byte
283
284         return True
285
286     def set_region_to_update(self, area: Area) -> None:
287         """Set the region to update"""
288         region_x1 = area.x1 + self.colstart
289         region_x2 = area.x2 + self.colstart
290         region_y1 = area.y1 + self.rowstart
291         region_y2 = area.y2 + self.rowstart
292
293         if self.colorspace.depth < 8:
294             pixels_per_byte = 8 // self.colorspace.depth
295             if self.colorspace.pixels_in_byte_share_row:
296                 region_x1 //= pixels_per_byte * self.colorspace.bytes_per_cell
297                 region_x2 //= pixels_per_byte * self.colorspace.bytes_per_cell
298             else:
299                 region_y1 //= pixels_per_byte * self.colorspace.bytes_per_cell
300                 region_y2 //= pixels_per_byte * self.colorspace.bytes_per_cell
301
302         region_x2 -= 1
303         region_y2 -= 1
304
305         chip_select = CHIP_SELECT_UNTOUCHED
306         if self.always_toggle_chip_select or self.data_as_commands:
307             chip_select = CHIP_SELECT_TOGGLE_EVERY_BYTE
308
309         # Set column
310         self.begin_transaction()
311         data_type = DISPLAY_DATA
312         if not self.data_as_commands:
313             self.send(
314                 DISPLAY_COMMAND, CHIP_SELECT_UNTOUCHED, bytes([self.column_command])
315             )
316         else:
317             data_type = DISPLAY_COMMAND
318
319         if self.ram_width < 0x100:  # Single Byte Bounds
320             data = struct.pack(">BB", region_x1, region_x2)
321         else:
322             if self.address_little_endian:
323                 region_x1 = bswap16(region_x1)
324                 region_x2 = bswap16(region_x2)
325             data = struct.pack(">HH", region_x1, region_x2)
326
327         # Quirk for SH1107 "SH1107_addressing"
328         #     Column lower command = 0x00, Column upper command = 0x10
329         if self.sh1107_addressing:
330             data = struct.pack(
331                 ">BB",
332                 ((region_x1 >> 4) & 0xF0) | 0x10,  # 0x10 to 0x17
333                 region_x1 & 0x0F,  # 0x00 to 0x0F
334             )
335
336         self.send(data_type, chip_select, data)
337         self.end_transaction()
338
339         if self.set_current_column_command != NO_COMMAND:
340             self.begin_transaction()
341             self.send(
342                 DISPLAY_COMMAND, chip_select, bytes([self.set_current_column_command])
343             )
344             # Only send the first half of data because it is the first coordinate.
345             self.send(DISPLAY_DATA, chip_select, data[: len(data) // 2])
346             self.end_transaction()
347
348         # Set row
349         self.begin_transaction()
350
351         if not self.data_as_commands:
352             self.send(DISPLAY_COMMAND, CHIP_SELECT_UNTOUCHED, bytes([self.row_command]))
353
354         if self.ram_width < 0x100:  # Single Byte Bounds
355             data = struct.pack(">BB", region_y1, region_y2)
356         else:
357             if self.address_little_endian:
358                 region_y1 = bswap16(region_y1)
359                 region_y2 = bswap16(region_y2)
360             data = struct.pack(">HH", region_y1, region_y2)
361
362         # Quirk for SH1107 "SH1107_addressing"
363         #     Page address command = 0xB0
364         if self.sh1107_addressing:
365             data = struct.pack(">B", 0xB0 | region_y1)
366
367         self.send(data_type, chip_select, data)
368         self.end_transaction()
369
370         if self.set_current_row_command != NO_COMMAND:
371             self.begin_transaction()
372             self.send(
373                 DISPLAY_COMMAND, chip_select, bytes([self.set_current_row_command])
374             )
375             # Only send the first half of data because it is the first coordinate.
376             self.send(DISPLAY_DATA, chip_select, data[: len(data) // 2])
377             self.end_transaction()
378
379         """
380         img = self._buffer.convert("RGB").crop(astuple(area))
381         img = img.rotate(360 - self._core.rotation, expand=True)
382
383         display_area = self._apply_rotation(area)
384
385         img = img.crop(astuple(display_area))
386
387         data = numpy.array(img).astype("uint16")
388         color = (
389             ((data[:, :, 0] & 0xF8) << 8)
390             | ((data[:, :, 1] & 0xFC) << 3)
391             | (data[:, :, 2] >> 3)
392         )
393
394         pixels = bytes(
395             numpy.dstack(((color >> 8) & 0xFF, color & 0xFF)).flatten().tolist()
396         )
397         """
398
399     def send(
400         self,
401         data_type: int,
402         chip_select: int,
403         data: circuitpython_typing.ReadableBuffer,
404     ) -> None:
405         """
406         Send the data to the current bus
407         """
408         print(data_type, chip_select, data)
409         self._send(data_type, chip_select, data)
410
411     def begin_transaction(self) -> None:
412         """
413         Begin Bus Transaction
414         """
415         self._begin_transaction()
416
417     def end_transaction(self) -> None:
418         """
419         End Bus Transaction
420         """
421         self._end_transaction()
422
423     def get_width(self) -> int:
424         """
425         Gets the width of the display in pixels.
426         """
427         return self.width
428
429     def get_height(self) -> int:
430         """
431         Gets the height of the display in pixels.
432         """
433         return self.height
434
435     def get_rotation(self) -> int:
436         """
437         Gets the rotation of the display as an int in degrees.
438         """
439         return self.rotation
440
441     def get_bus(self) -> _DisplayBus:
442         """
443         The bus being used by the display. [readonly]
444         """
445         return self._bus