]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blobdiff - displayio/_displaycore.py
Fix indexed bitmaps by correcting argument order at call
[hackapet/Adafruit_Blinka_Displayio.git] / displayio / _displaycore.py
index acedf6edfa3b92e274f1ffef2a09b9f4bbc2b165..286f9037673358f584ac3ff0a3c7f4859ba17ec2 100644 (file)
@@ -18,40 +18,32 @@ Super class of the display classes
 
 """
 
-__version__ = "0.0.0-auto.0"
+__version__ = "0.0.0+auto.0"
 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_Displayio.git"
 
 
-from typing import Optional, Union
-import _typing
-from ._fourwire import FourWire
+import time
+import struct
+from circuitpython_typing import WriteableBuffer, ReadableBuffer
+from paralleldisplaybus import ParallelBus
+from fourwire import FourWire
+from i2cdisplaybus import I2CDisplayBus
+from busdisplay._displaybus import _DisplayBus
 from ._group import Group
-from ._i2cdisplay import I2CDisplay
-from ._structs import ColorspaceStruct, TransformStruct, RectangleStruct
+from ._structs import ColorspaceStruct, TransformStruct
 from ._area import Area
-
-from paralleldisplay import ParallelBus
+from ._helpers import bswap16
 from ._constants import (
+    CHIP_SELECT_UNTOUCHED,
+    CHIP_SELECT_TOGGLE_EVERY_BYTE,
     DISPLAY_COMMAND,
     DISPLAY_DATA,
-    CHIP_SELECT_TOGGLE_EVERY_BYTE,
-    CHIP_SELECT_UNTOUCHED,
+    NO_COMMAND,
 )
 
-displays = []
-
-# Functions
-#* construct
-#  show
-#  set_dither
-#^ bus_reset
-#  set_region_to_update
-#* release
-#  fill_area
-#  clip_area (_clip)
 
 class _DisplayCore:
-    # pylint: disable=too-many-arguments, too-many-instance-attributes
+    # pylint: disable=too-many-arguments, too-many-instance-attributes, too-many-locals, too-many-branches, too-many-statements
 
     def __init__(
         self,
@@ -68,27 +60,48 @@ class _DisplayCore:
         pixels_in_byte_share_row: bool,
         bytes_per_cell: int,
         reverse_pixels_in_byte: bool,
-        reverse_bytes_in_word: bool
+        reverse_bytes_in_word: bool,
+        column_command: int,
+        row_command: int,
+        set_current_column_command: int,
+        set_current_row_command: int,
+        data_as_commands: bool,
+        always_toggle_chip_select: bool,
+        sh1107_addressing: bool,
+        address_little_endian: bool,
     ):
-        self._colorspace = ColorspaceStruct(
-            depth = color_depth,
-            grayscale = grayscale,
-            grayscale_bit = 8 - color_depth,
-            pixels_in_byte_share_row = pixels_in_byte_share_row,
-            bytes_per_cell = bytes_per_cell,
-            reverse_pixels_in_byte = reverse_pixels_in_byte,
-            reverse_bytes_in_word = reverse_bytes_in_word,
-            dither = False
+        self.colorspace = ColorspaceStruct(
+            depth=color_depth,
+            grayscale=grayscale,
+            grayscale_bit=8 - color_depth,
+            pixels_in_byte_share_row=pixels_in_byte_share_row,
+            bytes_per_cell=bytes_per_cell,
+            reverse_pixels_in_byte=reverse_pixels_in_byte,
+            reverse_bytes_in_word=reverse_bytes_in_word,
+            dither=False,
         )
-        self._current_group = None
-        self._colstart = colstart
-        self._rowstart = rowstart
-        self._last_refresh = 0
-        self._refresh_in_progress = False
+        self.current_group = None
+        self.colstart = colstart
+        self.rowstart = rowstart
+        self.last_refresh = 0
+
+        self.column_command = column_command
+        self.row_command = row_command
+        self.set_current_column_command = set_current_column_command
+        self.set_current_row_command = set_current_row_command
+        self.data_as_commands = data_as_commands
+        self.always_toggle_chip_select = always_toggle_chip_select
+        self.sh1107_addressing = sh1107_addressing
+        self.address_little_endian = address_little_endian
+
+        self.refresh_in_progress = False
+        self.full_refresh = False
+        self.last_refresh = 0
 
         if bus:
-            if isinstance(bus, (FourWire, I2CDisplay, ParallelBus)):
+            if isinstance(bus, (FourWire, I2CDisplayBus, ParallelBus)):
                 self._bus_reset = bus.reset
+                self._bus_free = bus._free
                 self._begin_transaction = bus._begin_transaction
                 self._send = bus._send
                 self._end_transaction = bus._end_transaction
@@ -96,312 +109,153 @@ class _DisplayCore:
                 raise ValueError("Unsupported display bus type")
 
         self._bus = bus
-        self._area = Area(0, 0, width, height)
+        self.area = Area(0, 0, width, height)
+
+        self.width = width
+        self.height = height
+        self.ram_width = ram_width
+        self.ram_height = ram_height
+        self.rotation = rotation
+        self.transform = TransformStruct()
 
-        self._width = width
-        self._height = height
-        self._ram_width = ram_width
-        self._ram_height = ram_height
-        self._rotation = rotation
-        self._transform = TransformStruct()
+        self.set_rotation(rotation)
 
     def set_rotation(self, rotation: int) -> None:
         """
         Sets the rotation of the display as an int in degrees.
         """
-        # pylint: disable=protected-access
-        transposed = self._rotation in (90, 270)
-        will_be_transposed = rotation in (90, 270)
-        if transposed != will_be_transposed:
-            self._width, self._height = self._height, self._width
-
-        height = self._height
-        width = self._width
+        # pylint: disable=protected-access, too-many-branches
+        height = self.height
+        width = self.width
 
         rotation %= 360
-        self._rotation = rotation
-        self._transform.x = 0
-        self._transform.y = 0
-        self._transform.scale = 1
-        self._transform.mirror_x = False
-        self._transform.mirror_y = False
-        self._transform.transpose_xy = False
+        self.rotation = rotation
+        self.transform.x = 0
+        self.transform.y = 0
+        self.transform.scale = 1
+        self.transform.mirror_x = False
+        self.transform.mirror_y = False
+        self.transform.transpose_xy = False
 
         if rotation in (0, 180):
             if rotation == 180:
-                self._transform.mirror_x = True
-                self._transform.mirror_y = True
+                self.transform.mirror_x = True
+                self.transform.mirror_y = True
         else:
-            self._transform.transpose_xy = True
+            self.transform.transpose_xy = True
             if rotation == 270:
-                self._transform.mirror_y = True
+                self.transform.mirror_y = True
             else:
-                self._transform.mirror_x = True
-
-        self._area.x1 = 0
-        self._area.y1 = 0
-        self._area.next = None
-
-        self._transform.dx = 1
-        self._transform.dy = 1
-        if self._transform.transpose_xy:
-            self._area.x2 = height
-            self._area.y2 = width
-            if self._transform.mirror_x:
-                self._transform.x = height
-                self._transform.dx = -1
-            if self._transform.mirror_y:
-                self._transform.y = width
-                self._transform.dy = -1
+                self.transform.mirror_x = True
+
+        self.area.x1 = 0
+        self.area.y1 = 0
+        self.area.next = None
+
+        self.transform.dx = 1
+        self.transform.dy = 1
+        if self.transform.transpose_xy:
+            self.area.x2 = height
+            self.area.y2 = width
+            if self.transform.mirror_x:
+                self.transform.x = height
+                self.transform.dx = -1
+            if self.transform.mirror_y:
+                self.transform.y = width
+                self.transform.dy = -1
         else:
-            self._area.x2 = width
-            self._area.y2 = height
-            if self._transform.mirror_x:
-                self._transform.x = width
-                self._transform.dx = -1
-            if self._transform.mirror_y:
-                self._transform.y = height
-                self._transform.dy = -1
-
-        if self._current_group is not None:
-            self._current_group._update_transform(self._transform)
-
-    def show(self, root_group: Group) -> bool:
-        # pylint: disable=protected-access
-
+            self.area.x2 = width
+            self.area.y2 = height
+            if self.transform.mirror_x:
+                self.transform.x = width
+                self.transform.dx = -1
+            if self.transform.mirror_y:
+                self.transform.y = height
+                self.transform.dy = -1
+
+    def set_root_group(self, root_group: Group) -> bool:
         """
         Switches to displaying the given group of layers. When group is `None`, the
         default CircuitPython terminal will be shown.
 
         :param Optional[displayio.Group] root_group: The group to show.
         """
+        # pylint: disable=protected-access
 
-        """
-        # TODO: Implement Supervisor
-        if root_group is None:
-            circuitpython_splash = _Supervisor().circuitpython_splash
-            if not circuitpython_splash._in_group:
-                root_group = circuitpython_splash
-            elif self._current_group == circuitpython_splash:
-                return True
-        """
-
-        if root_group == self._current_group:
+        if root_group == self.current_group:
             return True
 
         if root_group is not None and root_group._in_group:
             return False
 
-        if self._current_group is not None:
-            self._current_group._in_group = False
+        if self.current_group is not None:
+            self.current_group._in_group = False
 
         if root_group is not None:
-            root_group._update_transform(self._transform)
+            root_group._update_transform(self.transform)
             root_group._in_group = True
 
-        self._current_group = root_group
-        self._full_refresh = True
+        self.current_group = root_group
+        self.full_refresh = True
 
         return True
 
-    def set_region_to_update(
-        self,
-        column_command: int,
-        row_command: int,
-        set_current_column_command: Optional[int],
-        set_current_row_command: Optional[int],
-        data_as_commands: bool,
-        always_toggle_chip_select: bool,
-        area: Area,
-        SH1107_addressing: bool,
-    ) -> None:
-        # pylint: disable=invalid-name, too-many-arguments, too-many-locals, too-many-branches,
-        # pylint: disable=too-many-statements
-
-        big_endian = True  # default is True # TODO ????
-
-        x1 = area.x1 + self._colstart
-        x2 = area.x2 + self._colstart
-        y1 = area.y1 + self._rowstart
-        y2 = area.y2 + self._rowstart
-
-        # Collapse down the dimension where multiple pixels are in a byte.
-        if self._colorspace.depth < 8:
-            pixels_per_byte = 8 // self._colorspace.depth
-            if self._colorspace.pixels_in_byte_share_row:
-                x1 //= pixels_per_byte * self._colorspace.bytes_per_cell
-                x2 //= pixels_per_byte * self._colorspace.bytes_per_cell
-            else:
-                y1 //= pixels_per_byte * self._colorspace.bytes_per_cell
-                y2 //= pixels_per_byte * self._colorspace.bytes_per_cell
-
-        x2 -= 1
-        y2 -= 1
-
-        chip_select = CHIP_SELECT_UNTOUCHED
-        if always_toggle_chip_select or data_as_commands:
-            chip_select = CHIP_SELECT_TOGGLE_EVERY_BYTE
-
-        # Set column
-        self._begin_transaction()
-        data = bytearray(5)
-        data[0] = column_command
-        if not data_as_commands:
-            self._send(DISPLAY_COMMAND, CHIP_SELECT_UNTOUCHED, data, 1)
-            data_type = DISPLAY_DATA
-            data_length = 0
-        else:
-            data_type = DISPLAY_COMMAND
-            data_length = 1
-
-        if self._ram_width < 0x100:
-            data[data_length] = x1
-            data_length += 1
-            data[data_length] = x2
-            data_length += 1
-        else:
-            if big_endian:
-                data[data_length] = x1 >> 8
-                data_length += 1
-                data[data_length] = x1 & 0xFF
-                data_length += 1
-                data[data_length] = x2 >> 8
-                data_length += 1
-                data[data_length] = x2 & 0xFF
-                data_length += 1
-            else:
-                data[data_length] = x1 & 0xFF
-                data_length += 1
-                data[data_length] = x1 >> 8
-                data_length += 1
-                data[data_length] = x2 & 0xFF
-                data_length += 1
-                data[data_length] = x2 >> 8
-                data_length += 1
-
-        # Quirk for "SH1107_addressing"
-        #     Column lower command = 0x00, Column upper command = 0x10
-        if SH1107_addressing:
-            data[0] = ((x1 >> 4) & 0x0F) | 0x10  # 0x10 to 0x17
-            data[1] = x1 & 0x0F  # 0x00 to 0x0F
-            data_length = 2
-
-        self._send(data_type, chip_select, data, data_length)
-        self._end_transaction()
-
-        if set_current_column_command is not None:
-            command = bytearray(1)
-            command[0] = set_current_column_command
-            self._begin_transaction()
-            self._send(DISPLAY_COMMAND, chip_select, command, 1)
-            self._send(DISPLAY_DATA, chip_select, data, data_length // 2)
-            self._end_transaction()
-
-        # Set row
-        self._begin_transaction()
-        data[0] = row_command
-        data_length = 1
-        if not data_as_commands:
-            self._send(DISPLAY_COMMAND, CHIP_SELECT_UNTOUCHED, data, 1)
-            data_length = 0
-
-        if self._ram_height < 0x100:
-            data[data_length] = y1
-            data_length += 1
-            data[data_length] = y2
-            data_length += 1
-        else:
-            if big_endian:
-                data[data_length] = y1 >> 8
-                data_length += 1
-                data[data_length] = y1 & 0xFF
-                data_length += 1
-                data[data_length] = y2 >> 8
-                data_length += 1
-                data[data_length] = y2 & 0xFF
-                data_length += 1
-                # TODO Which is right? The core uses above
-            else:
-                data[data_length] = y1 & 0xFF
-                data_length += 1
-                data[data_length] = y1 >> 8
-                data_length += 1
-                data[data_length] = y2 & 0xFF
-                data_length += 1
-                data[data_length] = y2 >> 8
-                data_length += 1
-
-        # Quirk for "SH1107_addressing"
-        #  Page address command = 0xB0
-        if SH1107_addressing:
-            # Set the page to out y value
-            data[0] = 0xB0 | y1
-            data_length = 1
-
-        self._send(data_type, chip_select, data, data_length)
-        self._end_transaction()
-
-        if set_current_row_command is not None:
-            command = bytearray(1)
-            command[0] = set_current_row_command
-            self._begin_transaction()
-            self._send(DISPLAY_COMMAND, chip_select, command, 1)
-            self._send(DISPLAY_DATA, chip_select, data, data_length // 2)
-            self._end_transaction()
-
     def start_refresh(self) -> bool:
         # pylint: disable=protected-access
+        """Mark the display core as currently being refreshed"""
 
-        if self._refresh_in_progress:
+        if self.refresh_in_progress:
             return False
 
-        self._refresh_in_progress = True
-        #self._last_refresh = _Supervisor()._ticks_ms64()
+        self.refresh_in_progress = True
+        self.last_refresh = time.monotonic() * 1000
         return True
 
     def finish_refresh(self) -> None:
         # pylint: disable=protected-access
+        """Unmark the display core as currently being refreshed"""
 
-        if self._current_group is not None:
-            self._current_group._finish_refresh()
-
-        self._full_refresh = False
-        self._refresh_in_progress = False
-        #self._last_refresh = _Supervisor()._ticks_ms64()
+        if self.current_group is not None:
+            self.current_group._finish_refresh()
 
-    def get_refresh_areas(self):
-        subrectangles = []
-        if self._current_group is not None:
-            # Eventually calculate dirty rectangles here
-            subrectangles.append(RectangleStruct(0, 0, self._width, self._height))
-        return subrectangles
+        self.full_refresh = False
+        self.refresh_in_progress = False
+        self.last_refresh = time.monotonic() * 1000
 
-    def release(self) -> None:
+    def release_display_core(self) -> None:
+        """Release the display from the current group"""
         # pylint: disable=protected-access
 
-        if self._current_group is not None:
-            self._current_group._in_group = False
+        if self.current_group is not None:
+            self.current_group._in_group = False
 
     def fill_area(
-        self, area: Area, mask: _typing.WriteableBuffer, buffer: _typing.WriteableBuffer
+        self,
+        area: Area,
+        mask: WriteableBuffer,
+        buffer: WriteableBuffer,
     ) -> bool:
-        # pylint: disable=protected-access
-
-        return self._current_group._fill_area(self._colorspace, area, mask, buffer)
+        """Call the current group's fill area function"""
+        if self.current_group is not None:
+            return self.current_group._fill_area(  # pylint: disable=protected-access
+                self.colorspace, area, mask, buffer
+            )
+        return False
 
     def clip_area(self, area: Area, clipped: Area) -> bool:
-        # pylint: disable=protected-access
+        """Shrink the area to the region shared by the two areas"""
 
-        overlaps = self._area._compute_overlap(area, clipped)
+        overlaps = self.area.compute_overlap(area, clipped)
         if not overlaps:
             return False
 
-        # Expand the area if we have multiple pixels per byte and we need to byte align the bounds
-        if self._colorspace.depth < 8:
+        # Expand the area if we have multiple pixels per byte and we need to byte
+        # align the bounds
+        if self.colorspace.depth < 8:
             pixels_per_byte = (
-                8 // self._colorspace.depth * self._colorspace.bytes_per_cell
+                8 // self.colorspace.depth * self.colorspace.bytes_per_cell
             )
-            if self._colorspace.pixels_in_byte_share_row:
+            if self.colorspace.pixels_in_byte_share_row:
                 if clipped.x1 % pixels_per_byte != 0:
                     clipped.x1 -= clipped.x1 % pixels_per_byte
                 if clipped.x2 % pixels_per_byte != 0:
@@ -414,17 +268,121 @@ class _DisplayCore:
 
         return True
 
-    def send(self, data_type: int, chip_select: int, data: _typing.ReadableBuffer) -> None:
+    def set_region_to_update(self, area: Area) -> None:
+        """Set the region to update"""
+        region_x1 = area.x1 + self.colstart
+        region_x2 = area.x2 + self.colstart
+        region_y1 = area.y1 + self.rowstart
+        region_y2 = area.y2 + self.rowstart
+
+        if self.colorspace.depth < 8:
+            pixels_per_byte = 8 // self.colorspace.depth
+            if self.colorspace.pixels_in_byte_share_row:
+                region_x1 //= pixels_per_byte * self.colorspace.bytes_per_cell
+                region_x2 //= pixels_per_byte * self.colorspace.bytes_per_cell
+            else:
+                region_y1 //= pixels_per_byte * self.colorspace.bytes_per_cell
+                region_y2 //= pixels_per_byte * self.colorspace.bytes_per_cell
+        region_x2 -= 1
+        region_y2 -= 1
+
+        chip_select = CHIP_SELECT_UNTOUCHED
+        if self.always_toggle_chip_select or self.data_as_commands:
+            chip_select = CHIP_SELECT_TOGGLE_EVERY_BYTE
+
+        # Set column
+        self.begin_transaction()
+        data = bytearray([self.column_command])
+        data_type = DISPLAY_DATA
+        if not self.data_as_commands:
+            self.send(DISPLAY_COMMAND, CHIP_SELECT_UNTOUCHED, data)
+            data = bytearray(0)
+        else:
+            data_type = DISPLAY_COMMAND
+
+        if self.ram_width < 0x100:  # Single Byte Bounds
+            data += struct.pack(">BB", region_x1, region_x2)
+        else:
+            if self.address_little_endian:
+                region_x1 = bswap16(region_x1)
+                region_x2 = bswap16(region_x2)
+            data += struct.pack(">HH", region_x1, region_x2)
+
+        # Quirk for SH1107 "SH1107_addressing"
+        #     Column lower command = 0x00, Column upper command = 0x10
+        if self.sh1107_addressing:
+            data = struct.pack(
+                ">BB",
+                ((region_x1 >> 4) & 0xF0) | 0x10,  # 0x10 to 0x17
+                region_x1 & 0x0F,  # 0x00 to 0x0F
+            )
+
+        self.send(data_type, chip_select, data)
+        self.end_transaction()
+
+        if self.set_current_column_command != NO_COMMAND:
+            self.begin_transaction()
+            self.send(
+                DISPLAY_COMMAND, chip_select, bytes([self.set_current_column_command])
+            )
+            # Only send the first half of data because it is the first coordinate.
+            self.send(DISPLAY_DATA, chip_select, data[: len(data) // 2])
+            self.end_transaction()
+
+        # Set row
+        self.begin_transaction()
+        data = bytearray([self.row_command])
+
+        if not self.data_as_commands:
+            self.send(DISPLAY_COMMAND, CHIP_SELECT_UNTOUCHED, data)
+            data = bytearray(0)
+        if self.ram_height < 0x100:  # Single Byte Bounds
+            data += struct.pack(">BB", region_y1, region_y2)
+        else:
+            if self.address_little_endian:
+                region_y1 = bswap16(region_y1)
+                region_y2 = bswap16(region_y2)
+            data += struct.pack(">HH", region_y1, region_y2)
+
+        # Quirk for SH1107 "SH1107_addressing"
+        #     Page address command = 0xB0
+        if self.sh1107_addressing:
+            data = struct.pack(">B", 0xB0 | region_y1)
+
+        self.send(data_type, chip_select, data)
+        self.end_transaction()
+
+        if self.set_current_row_command != NO_COMMAND:
+            self.begin_transaction()
+            self.send(
+                DISPLAY_COMMAND, chip_select, bytes([self.set_current_row_command])
+            )
+            # Only send the first half of data because it is the first coordinate.
+            self.send(DISPLAY_DATA, chip_select, data[: len(data) // 2])
+            self.end_transaction()
+
+    def send(
+        self,
+        data_type: int,
+        chip_select: int,
+        data: ReadableBuffer,
+    ) -> None:
         """
         Send the data to the current bus
         """
         self._send(data_type, chip_select, data)
 
-    def begin_transaction(self) -> None:
+    def bus_free(self) -> bool:
+        """
+        Check if the bus is free
+        """
+        return self._bus_free()
+
+    def begin_transaction(self) -> bool:
         """
         Begin Bus Transaction
         """
-        self._begin_transaction()
+        return self._begin_transaction()
 
     def end_transaction(self) -> None:
         """
@@ -436,22 +394,22 @@ class _DisplayCore:
         """
         Gets the width of the display in pixels.
         """
-        return self._width
+        return self.width
 
     def get_height(self) -> int:
         """
         Gets the height of the display in pixels.
         """
-        return self._height
+        return self.height
 
     def get_rotation(self) -> int:
         """
         Gets the rotation of the display as an int in degrees.
         """
-        return self._rotation
-            
-    def get_bus(self) -> Union[FourWire,ParallelBus,I2CDisplay]:
+        return self.rotation
+
+    def get_bus(self) -> _DisplayBus:
         """
         The bus being used by the display. [readonly]
         """
-        return self._bus
\ No newline at end of file
+        return self._bus