1 # SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries
 
   2 # SPDX-FileCopyrightText: 2021 James Carr
 
   4 # SPDX-License-Identifier: MIT
 
   7 `displayio._displaycore`
 
   8 ================================================================================
 
  10 Super class of the display classes
 
  12 **Software and Dependencies:**
 
  15   https://github.com/adafruit/Adafruit_Blinka/releases
 
  17 * Author(s): James Carr, Melissa LeBlanc-Williams
 
  21 __version__ = "0.0.0+auto.0"
 
  22 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_Displayio.git"
 
  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,
 
  46     # pylint: disable=too-many-arguments, too-many-instance-attributes, too-many-locals, too-many-branches, too-many-statements
 
  60         pixels_in_byte_share_row: bool,
 
  62         reverse_pixels_in_byte: bool,
 
  63         reverse_bytes_in_word: bool,
 
  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,
 
  73         self.colorspace = ColorspaceStruct(
 
  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,
 
  83         self.current_group = None
 
  84         self.colstart = colstart
 
  85         self.rowstart = rowstart
 
  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
 
  97         self.refresh_in_progress = False
 
  98         self.full_refresh = False
 
 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
 
 108                 raise ValueError("Unsupported display bus type")
 
 111         self.area = Area(0, 0, width, height)
 
 115         self.ram_width = ram_width
 
 116         self.ram_height = ram_height
 
 117         self.rotation = rotation
 
 118         self.transform = TransformStruct()
 
 120     def set_rotation(self, rotation: int) -> None:
 
 122         Sets the rotation of the display as an int in degrees.
 
 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
 
 134         self.rotation = rotation
 
 137         self.transform.scale = 1
 
 138         self.transform.mirror_x = False
 
 139         self.transform.mirror_y = False
 
 140         self.transform.transpose_xy = False
 
 142         if rotation in (0, 180):
 
 144                 self.transform.mirror_x = True
 
 145                 self.transform.mirror_y = True
 
 147             self.transform.transpose_xy = True
 
 149                 self.transform.mirror_y = True
 
 151                 self.transform.mirror_x = True
 
 155         self.area.next = None
 
 157         self.transform.dx = 1
 
 158         self.transform.dy = 1
 
 159         if self.transform.transpose_xy:
 
 160             self.area.x2 = height
 
 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
 
 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
 
 178         if self.current_group is not None:
 
 179             self.current_group._update_transform(self.transform)
 
 181     def set_root_group(self, root_group: Group) -> bool:
 
 183         Switches to displaying the given group of layers. When group is `None`, the
 
 184         default CircuitPython terminal will be shown.
 
 186         :param Optional[displayio.Group] root_group: The group to show.
 
 188         # pylint: disable=protected-access
 
 190         if root_group == self.current_group:
 
 193         if root_group is not None and root_group._in_group:
 
 196         if self.current_group is not None:
 
 197             self.current_group._in_group = False
 
 199         if root_group is not None:
 
 200             root_group._update_transform(self.transform)
 
 201             root_group._in_group = True
 
 203         self.current_group = root_group
 
 204         self.full_refresh = True
 
 208     def start_refresh(self) -> bool:
 
 209         # pylint: disable=protected-access
 
 210         """Mark the display core as currently being refreshed"""
 
 212         if self.refresh_in_progress:
 
 215         self.refresh_in_progress = True
 
 216         self.last_refresh = time.monotonic() * 1000
 
 219     def finish_refresh(self) -> None:
 
 220         # pylint: disable=protected-access
 
 221         """Unmark the display core as currently being refreshed"""
 
 223         if self.current_group is not None:
 
 224             self.current_group._finish_refresh()
 
 226         self.full_refresh = False
 
 227         self.refresh_in_progress = False
 
 228         self.last_refresh = time.monotonic() * 1000
 
 230     def release_display_core(self) -> None:
 
 231         """Release the display from the current group"""
 
 232         # pylint: disable=protected-access
 
 234         if self.current_group is not None:
 
 235             self.current_group._in_group = False
 
 240         mask: WriteableBuffer,
 
 241         buffer: WriteableBuffer,
 
 243         """Call the current group's fill area function"""
 
 244         if self.current_group is not None:
 
 245             return self.current_group._fill_area(  # pylint: disable=protected-access
 
 246                 self.colorspace, area, mask, buffer
 
 250     def clip_area(self, area: Area, clipped: Area) -> bool:
 
 251         """Shrink the area to the region shared by the two areas"""
 
 253         overlaps = self.area.compute_overlap(area, clipped)
 
 257         # Expand the area if we have multiple pixels per byte and we need to byte
 
 259         if self.colorspace.depth < 8:
 
 261                 8 // self.colorspace.depth * self.colorspace.bytes_per_cell
 
 263             if self.colorspace.pixels_in_byte_share_row:
 
 264                 if clipped.x1 % pixels_per_byte != 0:
 
 265                     clipped.x1 -= clipped.x1 % pixels_per_byte
 
 266                 if clipped.x2 % pixels_per_byte != 0:
 
 267                     clipped.x2 += pixels_per_byte - clipped.x2 % pixels_per_byte
 
 269                 if clipped.y1 % pixels_per_byte != 0:
 
 270                     clipped.y1 -= clipped.y1 % pixels_per_byte
 
 271                 if clipped.y2 % pixels_per_byte != 0:
 
 272                     clipped.y2 += pixels_per_byte - clipped.y2 % pixels_per_byte
 
 276     def set_region_to_update(self, area: Area) -> None:
 
 277         """Set the region to update"""
 
 278         region_x1 = area.x1 + self.colstart
 
 279         region_x2 = area.x2 + self.colstart
 
 280         region_y1 = area.y1 + self.rowstart
 
 281         region_y2 = area.y2 + self.rowstart
 
 283         if self.colorspace.depth < 8:
 
 284             pixels_per_byte = 8 // self.colorspace.depth
 
 285             if self.colorspace.pixels_in_byte_share_row:
 
 286                 region_x1 //= pixels_per_byte * self.colorspace.bytes_per_cell
 
 287                 region_x2 //= pixels_per_byte * self.colorspace.bytes_per_cell
 
 289                 region_y1 //= pixels_per_byte * self.colorspace.bytes_per_cell
 
 290                 region_y2 //= pixels_per_byte * self.colorspace.bytes_per_cell
 
 295         chip_select = CHIP_SELECT_UNTOUCHED
 
 296         if self.always_toggle_chip_select or self.data_as_commands:
 
 297             chip_select = CHIP_SELECT_TOGGLE_EVERY_BYTE
 
 300         self.begin_transaction()
 
 301         data = bytearray([self.column_command])
 
 302         data_type = DISPLAY_DATA
 
 303         if not self.data_as_commands:
 
 304             self.send(DISPLAY_COMMAND, CHIP_SELECT_UNTOUCHED, data)
 
 307             data_type = DISPLAY_COMMAND
 
 309         if self.ram_width < 0x100:  # Single Byte Bounds
 
 310             data += struct.pack(">BB", region_x1, region_x2)
 
 312             if self.address_little_endian:
 
 313                 region_x1 = bswap16(region_x1)
 
 314                 region_x2 = bswap16(region_x2)
 
 315             data += struct.pack(">HH", region_x1, region_x2)
 
 317         # Quirk for SH1107 "SH1107_addressing"
 
 318         #     Column lower command = 0x00, Column upper command = 0x10
 
 319         if self.sh1107_addressing:
 
 322                 ((region_x1 >> 4) & 0xF0) | 0x10,  # 0x10 to 0x17
 
 323                 region_x1 & 0x0F,  # 0x00 to 0x0F
 
 326         self.send(data_type, chip_select, data)
 
 327         self.end_transaction()
 
 329         if self.set_current_column_command != NO_COMMAND:
 
 330             self.begin_transaction()
 
 332                 DISPLAY_COMMAND, chip_select, bytes([self.set_current_column_command])
 
 334             # Only send the first half of data because it is the first coordinate.
 
 335             self.send(DISPLAY_DATA, chip_select, data[: len(data) // 2])
 
 336             self.end_transaction()
 
 339         self.begin_transaction()
 
 340         data = bytearray([self.row_command])
 
 342         if not self.data_as_commands:
 
 343             self.send(DISPLAY_COMMAND, CHIP_SELECT_UNTOUCHED, data)
 
 345         if self.ram_width < 0x100:  # Single Byte Bounds
 
 346             data += struct.pack(">BB", region_y1, region_y2)
 
 348             if self.address_little_endian:
 
 349                 region_y1 = bswap16(region_y1)
 
 350                 region_y2 = bswap16(region_y2)
 
 351             data += struct.pack(">HH", region_y1, region_y2)
 
 353         # Quirk for SH1107 "SH1107_addressing"
 
 354         #     Page address command = 0xB0
 
 355         if self.sh1107_addressing:
 
 356             data = struct.pack(">B", 0xB0 | region_y1)
 
 358         self.send(data_type, chip_select, data)
 
 359         self.end_transaction()
 
 361         if self.set_current_row_command != NO_COMMAND:
 
 362             self.begin_transaction()
 
 364                 DISPLAY_COMMAND, chip_select, bytes([self.set_current_row_command])
 
 366             # Only send the first half of data because it is the first coordinate.
 
 367             self.send(DISPLAY_DATA, chip_select, data[: len(data) // 2])
 
 368             self.end_transaction()
 
 374         data: ReadableBuffer,
 
 377         Send the data to the current bus
 
 380         if isinstance(data, memoryview):
 
 381             data = data.tobytes()
 
 383         self._send(data_type, chip_select, data)
 
 385     def begin_transaction(self) -> bool:
 
 387         Begin Bus Transaction
 
 389         return self._begin_transaction()
 
 391     def end_transaction(self) -> None:
 
 395         self._end_transaction()
 
 397     def get_width(self) -> int:
 
 399         Gets the width of the display in pixels.
 
 403     def get_height(self) -> int:
 
 405         Gets the height of the display in pixels.
 
 409     def get_rotation(self) -> int:
 
 411         Gets the rotation of the display as an int in degrees.
 
 415     def get_bus(self) -> _DisplayBus:
 
 417         The bus being used by the display. [readonly]