]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blob - displayio/_displaycore.py
207f37a058dcb30584cceba469289e27d28fc559
[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 from circuitpython_typing import WriteableBuffer, ReadableBuffer
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._bus_free = bus._free
105                 self._begin_transaction = bus._begin_transaction
106                 self._send = bus._send
107                 self._end_transaction = bus._end_transaction
108             else:
109                 raise ValueError("Unsupported display bus type")
110
111         self._bus = bus
112         self.area = Area(0, 0, width, height)
113
114         self.width = width
115         self.height = height
116         self.ram_width = ram_width
117         self.ram_height = ram_height
118         self.rotation = rotation
119         self.transform = TransformStruct()
120
121         self.set_rotation(rotation)
122
123     def set_rotation(self, rotation: int) -> None:
124         """
125         Sets the rotation of the display as an int in degrees.
126         """
127         # pylint: disable=protected-access, too-many-branches
128         height = self.height
129         width = self.width
130
131         rotation %= 360
132         self.rotation = rotation
133         self.transform.x = 0
134         self.transform.y = 0
135         self.transform.scale = 1
136         self.transform.mirror_x = False
137         self.transform.mirror_y = False
138         self.transform.transpose_xy = False
139
140         if rotation in (0, 180):
141             if rotation == 180:
142                 self.transform.mirror_x = True
143                 self.transform.mirror_y = True
144         else:
145             self.transform.transpose_xy = True
146             if rotation == 270:
147                 self.transform.mirror_y = True
148             else:
149                 self.transform.mirror_x = True
150
151         self.area.x1 = 0
152         self.area.y1 = 0
153         self.area.next = None
154
155         self.transform.dx = 1
156         self.transform.dy = 1
157         if self.transform.transpose_xy:
158             self.area.x2 = height
159             self.area.y2 = width
160             if self.transform.mirror_x:
161                 self.transform.x = height
162                 self.transform.dx = -1
163             if self.transform.mirror_y:
164                 self.transform.y = width
165                 self.transform.dy = -1
166         else:
167             self.area.x2 = width
168             self.area.y2 = height
169             if self.transform.mirror_x:
170                 self.transform.x = width
171                 self.transform.dx = -1
172             if self.transform.mirror_y:
173                 self.transform.y = height
174                 self.transform.dy = -1
175
176     def set_root_group(self, root_group: Group) -> bool:
177         """
178         Switches to displaying the given group of layers. When group is `None`, the
179         default CircuitPython terminal will be shown.
180
181         :param Optional[displayio.Group] root_group: The group to show.
182         """
183         # pylint: disable=protected-access
184
185         if root_group == self.current_group:
186             return True
187
188         if root_group is not None and root_group._in_group:
189             return False
190
191         if self.current_group is not None:
192             self.current_group._in_group = False
193
194         if root_group is not None:
195             root_group._update_transform(self.transform)
196             root_group._in_group = True
197
198         self.current_group = root_group
199         self.full_refresh = True
200
201         return True
202
203     def start_refresh(self) -> bool:
204         # pylint: disable=protected-access
205         """Mark the display core as currently being refreshed"""
206
207         if self.refresh_in_progress:
208             return False
209
210         self.refresh_in_progress = True
211         self.last_refresh = time.monotonic() * 1000
212         return True
213
214     def finish_refresh(self) -> None:
215         # pylint: disable=protected-access
216         """Unmark the display core as currently being refreshed"""
217
218         if self.current_group is not None:
219             self.current_group._finish_refresh()
220
221         self.full_refresh = False
222         self.refresh_in_progress = False
223         self.last_refresh = time.monotonic() * 1000
224
225     def release_display_core(self) -> None:
226         """Release the display from the current group"""
227         # pylint: disable=protected-access
228
229         if self.current_group is not None:
230             self.current_group._in_group = False
231
232     def fill_area(
233         self,
234         area: Area,
235         mask: WriteableBuffer,
236         buffer: WriteableBuffer,
237     ) -> bool:
238         """Call the current group's fill area function"""
239         if self.current_group is not None:
240             return self.current_group._fill_area(  # pylint: disable=protected-access
241                 self.colorspace, area, mask, buffer
242             )
243         return False
244
245     def clip_area(self, area: Area, clipped: Area) -> bool:
246         """Shrink the area to the region shared by the two areas"""
247
248         overlaps = self.area.compute_overlap(area, clipped)
249         if not overlaps:
250             return False
251
252         # Expand the area if we have multiple pixels per byte and we need to byte
253         # align the bounds
254         if self.colorspace.depth < 8:
255             pixels_per_byte = (
256                 8 // self.colorspace.depth * self.colorspace.bytes_per_cell
257             )
258             if self.colorspace.pixels_in_byte_share_row:
259                 if clipped.x1 % pixels_per_byte != 0:
260                     clipped.x1 -= clipped.x1 % pixels_per_byte
261                 if clipped.x2 % pixels_per_byte != 0:
262                     clipped.x2 += pixels_per_byte - clipped.x2 % pixels_per_byte
263             else:
264                 if clipped.y1 % pixels_per_byte != 0:
265                     clipped.y1 -= clipped.y1 % pixels_per_byte
266                 if clipped.y2 % pixels_per_byte != 0:
267                     clipped.y2 += pixels_per_byte - clipped.y2 % pixels_per_byte
268
269         return True
270
271     def set_region_to_update(self, area: Area) -> None:
272         """Set the region to update"""
273         region_x1 = area.x1 + self.colstart
274         region_x2 = area.x2 + self.colstart
275         region_y1 = area.y1 + self.rowstart
276         region_y2 = area.y2 + self.rowstart
277
278         if self.colorspace.depth < 8:
279             pixels_per_byte = 8 // self.colorspace.depth
280             if self.colorspace.pixels_in_byte_share_row:
281                 region_x1 //= pixels_per_byte * self.colorspace.bytes_per_cell
282                 region_x2 //= pixels_per_byte * self.colorspace.bytes_per_cell
283             else:
284                 region_y1 //= pixels_per_byte * self.colorspace.bytes_per_cell
285                 region_y2 //= pixels_per_byte * self.colorspace.bytes_per_cell
286         region_x2 -= 1
287         region_y2 -= 1
288
289         chip_select = CHIP_SELECT_UNTOUCHED
290         if self.always_toggle_chip_select or self.data_as_commands:
291             chip_select = CHIP_SELECT_TOGGLE_EVERY_BYTE
292
293         # Set column
294         self.begin_transaction()
295         data = bytearray([self.column_command])
296         data_type = DISPLAY_DATA
297         if not self.data_as_commands:
298             self.send(DISPLAY_COMMAND, CHIP_SELECT_UNTOUCHED, data)
299             data = bytearray(0)
300         else:
301             data_type = DISPLAY_COMMAND
302
303         if self.ram_width < 0x100:  # Single Byte Bounds
304             data += struct.pack(">BB", region_x1, region_x2)
305         else:
306             if self.address_little_endian:
307                 region_x1 = bswap16(region_x1)
308                 region_x2 = bswap16(region_x2)
309             data += struct.pack(">HH", region_x1, region_x2)
310
311         # Quirk for SH1107 "SH1107_addressing"
312         #     Column lower command = 0x00, Column upper command = 0x10
313         if self.sh1107_addressing:
314             data = struct.pack(
315                 ">BB",
316                 ((region_x1 >> 4) & 0xF0) | 0x10,  # 0x10 to 0x17
317                 region_x1 & 0x0F,  # 0x00 to 0x0F
318             )
319
320         self.send(data_type, chip_select, data)
321         self.end_transaction()
322
323         if self.set_current_column_command != NO_COMMAND:
324             self.begin_transaction()
325             self.send(
326                 DISPLAY_COMMAND, chip_select, bytes([self.set_current_column_command])
327             )
328             # Only send the first half of data because it is the first coordinate.
329             self.send(DISPLAY_DATA, chip_select, data[: len(data) // 2])
330             self.end_transaction()
331
332         # Set row
333         self.begin_transaction()
334         data = bytearray([self.row_command])
335
336         if not self.data_as_commands:
337             self.send(DISPLAY_COMMAND, CHIP_SELECT_UNTOUCHED, data)
338             data = bytearray(0)
339         if self.ram_height < 0x100:  # Single Byte Bounds
340             data += struct.pack(">BB", region_y1, region_y2)
341         else:
342             if self.address_little_endian:
343                 region_y1 = bswap16(region_y1)
344                 region_y2 = bswap16(region_y2)
345             data += struct.pack(">HH", region_y1, region_y2)
346
347         # Quirk for SH1107 "SH1107_addressing"
348         #     Page address command = 0xB0
349         if self.sh1107_addressing:
350             data = struct.pack(">B", 0xB0 | region_y1)
351
352         self.send(data_type, chip_select, data)
353         self.end_transaction()
354
355         if self.set_current_row_command != NO_COMMAND:
356             self.begin_transaction()
357             self.send(
358                 DISPLAY_COMMAND, chip_select, bytes([self.set_current_row_command])
359             )
360             # Only send the first half of data because it is the first coordinate.
361             self.send(DISPLAY_DATA, chip_select, data[: len(data) // 2])
362             self.end_transaction()
363
364     def send(
365         self,
366         data_type: int,
367         chip_select: int,
368         data: ReadableBuffer,
369     ) -> None:
370         """
371         Send the data to the current bus
372         """
373         self._send(data_type, chip_select, data)
374
375     def bus_free(self) -> bool:
376         """
377         Check if the bus is free
378         """
379         return self._bus_free()
380
381     def begin_transaction(self) -> bool:
382         """
383         Begin Bus Transaction
384         """
385         return self._begin_transaction()
386
387     def end_transaction(self) -> None:
388         """
389         End Bus Transaction
390         """
391         self._end_transaction()
392
393     def get_width(self) -> int:
394         """
395         Gets the width of the display in pixels.
396         """
397         return self.width
398
399     def get_height(self) -> int:
400         """
401         Gets the height of the display in pixels.
402         """
403         return self.height
404
405     def get_rotation(self) -> int:
406         """
407         Gets the rotation of the display as an int in degrees.
408         """
409         return self.rotation
410
411     def get_bus(self) -> _DisplayBus:
412         """
413         The bus being used by the display. [readonly]
414         """
415         return self._bus