From: Melissa LeBlanc-Williams Date: Tue, 4 Jan 2022 00:14:45 +0000 (-0800) Subject: Start splitting display functions into display core X-Git-Tag: 0.9.0^2~1 X-Git-Url: https://git.ayoreis.com/hackapet/Adafruit_Blinka_Displayio.git/commitdiff_plain/03b2e1008f33585652fad5ecedad65d44694b2bc Start splitting display functions into display core --- diff --git a/displayio/_area.py b/displayio/_area.py new file mode 100644 index 0000000..8e50c85 --- /dev/null +++ b/displayio/_area.py @@ -0,0 +1,143 @@ +# SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries +# SPDX-FileCopyrightText: 2021 James Carr +# +# SPDX-License-Identifier: MIT + +""" +`displayio._area` +================================================================================ + +Area for Blinka Displayio + +**Software and Dependencies:** + +* Adafruit Blinka: + https://github.com/adafruit/Adafruit_Blinka/releases + +* Author(s): James Carr, Melissa LeBlanc-Williams + +""" + +from __future__ import annotations + +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_Blinka_Displayio.git" + +class Area: + # pylint: disable=invalid-name,missing-function-docstring + def __init__(self, x1: int = 0, y1: int = 0, x2: int = 0, y2: int = 0): + self.x1 = x1 + self.y1 = y1 + self.x2 = x2 + self.y2 = y2 + self.next = None + + def __str__(self): + return f"Area TL({self.x1},{self.y1}) BR({self.x2},{self.y2})" + + def _copy_into(self, dst) -> None: + dst.x1 = self.x1 + dst.y1 = self.y1 + dst.x2 = self.x2 + dst.y2 = self.y2 + + def _scale(self, scale: int) -> None: + self.x1 *= scale + self.y1 *= scale + self.x2 *= scale + self.y2 *= scale + + def _shift(self, dx: int, dy: int) -> None: + self.x1 += dx + self.y1 += dy + self.x2 += dx + self.y2 += dy + + def _compute_overlap(self, other, overlap) -> bool: + a = self + overlap.x1 = max(a.x1, other.x1) + overlap.x2 = min(a.x2, other.x2) + + if overlap.x1 >= overlap.x2: + return False + + overlap.y1 = max(a.y1, other.y1) + overlap.y2 = min(a.y2, other.y2) + + return overlap.y1 < overlap.y2 + + def _empty(self): + return (self.x1 == self.x2) or (self.y1 == self.y2) + + def _canon(self): + if self.x1 > self.x2: + self.x1, self.x2 = self.x2, self.x1 + if self.y1 > self.y2: + self.y1, self.y2 = self.y2, self.y1 + + def _union(self, other, union): + # pylint: disable=protected-access + if self._empty(): + self._copy_into(union) + return + if other._empty(): + other._copy_into(union) + return + + union.x1 = min(self.x1, other.x1) + union.y1 = min(self.y1, other.y1) + union.x2 = max(self.x2, other.x2) + union.y2 = max(self.y2, other.y2) + + def width(self) -> int: + return self.x2 - self.x1 + + def height(self) -> int: + return self.y2 - self.y1 + + def size(self) -> int: + return self.width() * self.height() + + def __eq__(self, other): + if not isinstance(other, Area): + return False + + return ( + self.x1 == other.x1 + and self.y1 == other.y1 + and self.x2 == other.x2 + and self.y2 == other.y2 + ) + + @staticmethod + def _transform_within( + mirror_x: bool, + mirror_y: bool, + transpose_xy: bool, + original: Area, + whole: Area, + transformed: Area, + ): + # pylint: disable=too-many-arguments + # Original and whole must be in the same coordinate space. + if mirror_x: + transformed.x1 = whole.x1 + (whole.x2 - original.x2) + transformed.x2 = whole.x2 - (original.x1 - whole.x1) + else: + transformed.x1 = original.x1 + transformed.x2 = original.x2 + + if mirror_y: + transformed.y1 = whole.y1 + (whole.y2 - original.y2) + transformed.y2 = whole.y2 - (original.y1 - whole.y1) + else: + transformed.y1 = original.y1 + transformed.y2 = original.y2 + + if transpose_xy: + y1 = transformed.y1 + y2 = transformed.y2 + transformed.y1 = whole.y1 + (transformed.x1 - whole.x1) + transformed.y2 = whole.y1 + (transformed.x2 - whole.x1) + transformed.x1 = whole.x1 + (y1 - whole.y1) + transformed.x2 = whole.x1 + (y2 - whole.y1) diff --git a/displayio/_bitmap.py b/displayio/_bitmap.py index f28e72a..9d06a63 100644 --- a/displayio/_bitmap.py +++ b/displayio/_bitmap.py @@ -19,14 +19,12 @@ displayio for Blinka from __future__ import annotations from typing import Union, Tuple -from recordclass import recordclass from PIL import Image +from ._structs import RectangleStruct __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git" -Rectangle = recordclass("Rectangle", "x1 y1 x2 y2") - class Bitmap: """Stores values of a certain size in a 2D array""" @@ -61,7 +59,7 @@ class Bitmap: raise NotImplementedError("Invalid bits per value") self._image = Image.new("P", (width, height), 0) - self._dirty_area = Rectangle(0, 0, width, height) + self._dirty_area = RectangleStruct(0, 0, width, height) def __getitem__(self, index: Union[Tuple[int, int], int]) -> int: """ @@ -117,7 +115,7 @@ class Bitmap: def fill(self, value: int) -> None: """Fills the bitmap with the supplied palette index value.""" self._image = Image.new("P", (self._bmp_width, self._bmp_height), value) - self._dirty_area = Rectangle(0, 0, self._bmp_width, self._bmp_height) + self._dirty_area = RectangleStruct(0, 0, self._bmp_width, self._bmp_height) def blit( self, diff --git a/displayio/_colorspace.py b/displayio/_colorspace.py index 5aacb24..b5322a3 100644 --- a/displayio/_colorspace.py +++ b/displayio/_colorspace.py @@ -26,8 +26,8 @@ class Colorspace: """The colorspace for a ColorConverter to operate in.""" # pylint: disable=too-few-public-methods - def __init__(self, colorspace): - self._type = colorspace + def __init__(self, colorspace_type): + self._colorspace_type = colorspace_type Colorspace.RGB888 = Colorspace("RGB888") diff --git a/displayio/_constants.py b/displayio/_constants.py index d0a00b7..11b20c6 100644 --- a/displayio/_constants.py +++ b/displayio/_constants.py @@ -15,3 +15,6 @@ DISPLAY_DATA = 1 CHIP_SELECT_UNTOUCHED = 0 CHIP_SELECT_TOGGLE_EVERY_BYTE = 1 + +BACKLIGHT_IN_OUT = 1 +BACKLIGHT_PWM = 2 diff --git a/displayio/_display.py b/displayio/_display.py index e0c4026..8038722 100644 --- a/displayio/_display.py +++ b/displayio/_display.py @@ -21,31 +21,31 @@ import time import struct import threading from typing import Optional +from dataclasses import astuple import digitalio from PIL import Image import numpy import microcontroller -from recordclass import recordclass import _typing +from ._displaycore import _DisplayCore from ._displaybus import _DisplayBus from ._colorconverter import ColorConverter from ._group import Group +from ._structs import RectangleStruct from ._constants import ( CHIP_SELECT_TOGGLE_EVERY_BYTE, CHIP_SELECT_UNTOUCHED, DISPLAY_COMMAND, DISPLAY_DATA, + BACKLIGHT_IN_OUT, + BACKLIGHT_PWM, ) __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git" -Rectangle = recordclass("Rectangle", "x1 y1 x2 y2") displays = [] -BACKLIGHT_IN_OUT = 1 -BACKLIGHT_PWM = 2 - class Display: # pylint: disable=too-many-instance-attributes @@ -72,6 +72,7 @@ class Display: pixels_in_byte_share_row: bool = True, bytes_per_cell: int = 1, reverse_pixels_in_byte: bool = False, + reverse_bytes_in_word: bool = True, set_column_command: int = 0x2A, set_row_command: int = 0x2B, write_ram_command: int = 0x2C, @@ -117,7 +118,28 @@ class Display: The initialization sequence should always leave the display memory access inline with the scan of the display to minimize tearing artifacts. """ - self._bus = display_bus + ram_width = 0x100 + ram_height = 0x100 + if single_byte_bounds: + ram_width = 0xFF + ram_height = 0xFF + self._core = _DisplayCore( + bus=display_bus, + width=width, + height=height, + ram_width=ram_width, + ram_height=ram_height, + colstart=colstart, + rowstart=rowstart, + rotation=rotation, + color_depth=color_depth, + grayscale=grayscale, + 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, + ) + self._set_column_command = set_column_command self._set_row_command = set_row_command self._write_ram_command = write_ram_command @@ -168,7 +190,21 @@ class Display: delay = (data_size & 0x80) > 0 data_size &= ~0x80 - self._send(command, init_sequence[i + 2 : i + 2 + data_size]) + if self._data_as_commands: + self._core.send( + DISPLAY_COMMAND, + CHIP_SELECT_TOGGLE_EVERY_BYTE, + bytes([command]) + init_sequence[i + 2 : i + 2 + data_size], + ) + else: + self._core.send( + DISPLAY_COMMAND, CHIP_SELECT_TOGGLE_EVERY_BYTE, bytes([command]) + ) + self._core.send( + DISPLAY_DATA, + CHIP_SELECT_UNTOUCHED, + init_sequence[i + 2 : i + 2 + data_size], + ) delay_time_ms = 10 if delay: data_size += 1 @@ -179,38 +215,32 @@ class Display: i += 2 + data_size def _send(self, command, data): - # pylint: disable=protected-access - self._bus._begin_transaction() + self._core.begin_transaction() if self._data_as_commands: - self._bus._send( + self._core.send( DISPLAY_COMMAND, CHIP_SELECT_TOGGLE_EVERY_BYTE, bytes([command]) + data ) else: - self._bus._send( + self._core.send( DISPLAY_COMMAND, CHIP_SELECT_TOGGLE_EVERY_BYTE, bytes([command]) ) - self._bus._send(DISPLAY_DATA, CHIP_SELECT_UNTOUCHED, data) - self._bus._end_transaction() + self._core.send(DISPLAY_DATA, CHIP_SELECT_UNTOUCHED, data) + self._core.end_transaction() def _send_pixels(self, data): - # pylint: disable=protected-access if not self._data_as_commands: - self._bus._send( + self._core.send( DISPLAY_COMMAND, CHIP_SELECT_TOGGLE_EVERY_BYTE, bytes([self._write_ram_command]), ) - self._bus._send(DISPLAY_DATA, CHIP_SELECT_UNTOUCHED, data) - - def _release(self): - self._bus._release() # pylint: disable=protected-access - self._bus = None + self._core.send(DISPLAY_DATA, CHIP_SELECT_UNTOUCHED, data) def show(self, group: Group) -> None: """Switches to displaying the given group of layers. When group is None, the default CircuitPython terminal will be shown. """ - self._current_group = group + self._core.show(group) def refresh( self, @@ -218,7 +248,7 @@ class Display: target_frames_per_second: Optional[int] = None, minimum_frames_per_second: int = 0, ) -> bool: - # pylint: disable=unused-argument + # pylint: disable=unused-argument, protected-access """When auto refresh is off, waits for the target frame rate and then refreshes the display, returning True. If the call has taken too long since the last refresh call for the given target frame rate, then the refresh returns False immediately without @@ -230,23 +260,26 @@ class Display: When auto refresh is on, updates the display immediately. (The display will also update without calls to this.) """ - self._subrectangles = [] + if not self._core.start_refresh(): + return False # Go through groups and and add each to buffer - if self._current_group is not None: - buffer = Image.new("RGBA", (self._width, self._height)) + if self._core._current_group is not None: + buffer = Image.new("RGBA", (self._core._width, self._core._height)) # Recursively have everything draw to the image - self._current_group._fill_area(buffer) # pylint: disable=protected-access + self._core._current_group._fill_area( + buffer + ) # pylint: disable=protected-access # save image to buffer (or probably refresh buffer so we can compare) self._buffer.paste(buffer) - if self._current_group is not None: - # Eventually calculate dirty rectangles here - self._subrectangles.append(Rectangle(0, 0, self._width, self._height)) + self._subrectangles = self._core.get_refresh_areas() for area in self._subrectangles: self._refresh_display_area(area) + self._core.finish_refresh() + return True def _refresh_loop(self): @@ -255,12 +288,11 @@ class Display: def _refresh_display_area(self, rectangle): """Loop through dirty rectangles and redraw that area.""" - - img = self._buffer.convert("RGB").crop(rectangle) + img = self._buffer.convert("RGB").crop(astuple(rectangle)) img = img.rotate(self._rotation, expand=True) display_rectangle = self._apply_rotation(rectangle) - img = img.crop(self._clip(display_rectangle)) + img = img.crop(astuple(self._clip(display_rectangle))) data = numpy.array(img).astype("uint16") color = ( @@ -288,9 +320,9 @@ class Display: ), ) - self._bus._begin_transaction() # pylint: disable=protected-access + self._core.begin_transaction() self._send_pixels(pixels) - self._bus._end_transaction() # pylint: disable=protected-access + self._core.end_transaction() def _clip(self, rectangle): if self._rotation in (90, 270): @@ -308,21 +340,21 @@ class Display: def _apply_rotation(self, rectangle): """Adjust the rectangle coordinates based on rotation""" if self._rotation == 90: - return Rectangle( + return RectangleStruct( self._height - rectangle.y2, rectangle.x1, self._height - rectangle.y1, rectangle.x2, ) if self._rotation == 180: - return Rectangle( + return RectangleStruct( self._width - rectangle.x2, self._height - rectangle.y2, self._width - rectangle.x1, self._height - rectangle.y1, ) if self._rotation == 270: - return Rectangle( + return RectangleStruct( rectangle.y1, self._width - rectangle.x2, rectangle.y2, @@ -400,25 +432,23 @@ class Display: @property def width(self) -> int: """Display Width""" - return self._width + return self._core.get_width() @property def height(self) -> int: """Display Height""" - return self._height + return self._core.get_height() @property def rotation(self) -> int: """The rotation of the display as an int in degrees.""" - return self._rotation + return self._core.get_rotation() @rotation.setter def rotation(self, value: int): - if value not in (0, 90, 180, 270): - raise ValueError("Rotation must be 0/90/180/270") - self._rotation = value + self._core.set_rotation(value) @property def bus(self) -> _DisplayBus: """Current Display Bus""" - return self._bus + return self._core.get_bus() diff --git a/displayio/_displaycore.py b/displayio/_displaycore.py new file mode 100644 index 0000000..acedf6e --- /dev/null +++ b/displayio/_displaycore.py @@ -0,0 +1,457 @@ +# SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries +# SPDX-FileCopyrightText: 2021 James Carr +# +# SPDX-License-Identifier: MIT + +""" +`displayio._displaycore` +================================================================================ + +Super class of the display classes + +**Software and Dependencies:** + +* Adafruit Blinka: + https://github.com/adafruit/Adafruit_Blinka/releases + +* Author(s): James Carr, Melissa LeBlanc-Williams + +""" + +__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 +from ._group import Group +from ._i2cdisplay import I2CDisplay +from ._structs import ColorspaceStruct, TransformStruct, RectangleStruct +from ._area import Area + +from paralleldisplay import ParallelBus +from ._constants import ( + DISPLAY_COMMAND, + DISPLAY_DATA, + CHIP_SELECT_TOGGLE_EVERY_BYTE, + CHIP_SELECT_UNTOUCHED, +) + +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 + + def __init__( + self, + bus, + width: int, + height: int, + ram_width: int, + ram_height: int, + colstart: int, + rowstart: int, + rotation: int, + color_depth: int, + grayscale: bool, + pixels_in_byte_share_row: bool, + bytes_per_cell: int, + reverse_pixels_in_byte: bool, + reverse_bytes_in_word: 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._current_group = None + self._colstart = colstart + self._rowstart = rowstart + self._last_refresh = 0 + self._refresh_in_progress = False + + if bus: + if isinstance(bus, (FourWire, I2CDisplay, ParallelBus)): + self._bus_reset = bus.reset + self._begin_transaction = bus._begin_transaction + self._send = bus._send + self._end_transaction = bus._end_transaction + else: + raise ValueError("Unsupported display bus type") + + self._bus = bus + 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() + + 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 + + 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 + + if rotation in (0, 180): + if rotation == 180: + self._transform.mirror_x = True + self._transform.mirror_y = True + else: + self._transform.transpose_xy = True + if rotation == 270: + 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 + 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 + + """ + 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. + """ + + """ + # 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: + 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 root_group is not None: + root_group._update_transform(self._transform) + root_group._in_group = 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 + + if self._refresh_in_progress: + return False + + self._refresh_in_progress = True + #self._last_refresh = _Supervisor()._ticks_ms64() + return True + + def finish_refresh(self) -> None: + # pylint: disable=protected-access + + 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() + + 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 + + def release(self) -> None: + # pylint: disable=protected-access + + 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 + ) -> bool: + # pylint: disable=protected-access + + return self._current_group._fill_area(self._colorspace, area, mask, buffer) + + def clip_area(self, area: Area, clipped: Area) -> bool: + # pylint: disable=protected-access + + 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: + pixels_per_byte = ( + 8 // self._colorspace.depth * self._colorspace.bytes_per_cell + ) + 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: + clipped.x2 += pixels_per_byte - clipped.x2 % pixels_per_byte + else: + if clipped.y1 % pixels_per_byte != 0: + clipped.y1 -= clipped.y1 % pixels_per_byte + if clipped.y2 % pixels_per_byte != 0: + clipped.y2 += pixels_per_byte - clipped.y2 % pixels_per_byte + + return True + + def send(self, data_type: int, chip_select: int, data: _typing.ReadableBuffer) -> None: + """ + Send the data to the current bus + """ + self._send(data_type, chip_select, data) + + def begin_transaction(self) -> None: + """ + Begin Bus Transaction + """ + self._begin_transaction() + + def end_transaction(self) -> None: + """ + End Bus Transaction + """ + self._end_transaction() + + def get_width(self) -> int: + """ + Gets the width of the display in pixels. + """ + return self._width + + def get_height(self) -> int: + """ + Gets the height of the display in pixels. + """ + 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]: + """ + The bus being used by the display. [readonly] + """ + return self._bus \ No newline at end of file diff --git a/displayio/_group.py b/displayio/_group.py index 6be7c6f..b38eaff 100644 --- a/displayio/_group.py +++ b/displayio/_group.py @@ -19,16 +19,13 @@ displayio for Blinka from __future__ import annotations from typing import Union, Callable -from recordclass import recordclass +from ._structs import TransformStruct from ._tilegrid import TileGrid __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git" -Transform = recordclass("Transform", "x y dx dy scale transpose_xy mirror_x mirror_y") - - class Group: """ Manage a group of sprites and groups and how they are inter-related. @@ -52,14 +49,14 @@ class Group: self._hidden_group = False self._layers = [] self._supported_types = (TileGrid, Group) - self.in_group = False - self._absolute_transform = Transform(0, 0, 1, 1, 1, False, False, False) + self._in_group = False + self._absolute_transform = TransformStruct(0, 0, 1, 1, 1, False, False, False) self._set_scale(scale) # Set the scale via the setter def _update_transform(self, parent_transform): """Update the parent transform and child transforms""" - self.in_group = parent_transform is not None - if self.in_group: + self._in_group = parent_transform is not None + if self._in_group: x = self._group_x y = self._group_y if parent_transform.transpose_xy: @@ -76,7 +73,7 @@ class Group: def _update_child_transforms(self): # pylint: disable=protected-access - if self.in_group: + if self._in_group: for layer in self._layers: layer._update_transform(self._absolute_transform) @@ -100,7 +97,7 @@ class Group: """Insert a layer into the group.""" if not isinstance(layer, self._supported_types): raise ValueError("Invalid Group Member") - if layer.in_group: + if layer._in_group: # pylint: disable=protected-access raise ValueError("Layer already in a group.") self._layers.insert(index, layer) self._layer_update(index) @@ -156,6 +153,11 @@ class Group: """Sort the members of the group.""" self._layers.sort(key=key, reverse=reverse) + def _finish_refresh(self): + for layer in self._layers: + if isinstance(layer, (Group, TileGrid)): + layer._finish_refresh() # pylint: disable=protected-access + @property def hidden(self) -> bool: """True when the Group and all of it's layers are not visible. When False, the diff --git a/displayio/_structs.py b/displayio/_structs.py new file mode 100644 index 0000000..2f82bb0 --- /dev/null +++ b/displayio/_structs.py @@ -0,0 +1,55 @@ +# SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams +# +# SPDX-License-Identifier: MIT + +""" +`displayio._structs` +================================================================================ + +Struct Data Classes for Blinka Displayio + +**Software and Dependencies:** + +* Adafruit Blinka: + https://github.com/adafruit/Adafruit_Blinka/releases + +* Author(s): Melissa LeBlanc-Williams + +""" + +from dataclasses import dataclass + +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_Blinka_Displayio.git" + +@dataclass +class RectangleStruct: + x1: int + y1: int + x2: int + y2: int + +@dataclass +class TransformStruct: + x: int = 0 + y: int = 0 + dx: int = 1 + dy: int = 1 + scale: int = 1 + transpose_xy: bool = False + mirror_x: bool = False + mirror_y: bool = False + +@dataclass +class ColorspaceStruct: + depth: int + bytes_per_cell: int = 0 + tricolor_hue: int = 0 + tricolor_luma: int = 0 + grayscale_bit: int = 0 + grayscale: bool = False + tricolor: bool = False + pixels_in_byte_share_row: bool = False + reverse_pixels_in_byte: bool = False + reverse_bytes_in_word: bool = False + dither: bool = False diff --git a/displayio/_tilegrid.py b/displayio/_tilegrid.py index f2ccde2..25b0c73 100644 --- a/displayio/_tilegrid.py +++ b/displayio/_tilegrid.py @@ -18,20 +18,17 @@ displayio for Blinka """ from typing import Union, Optional, Tuple -from recordclass import recordclass from PIL import Image from ._bitmap import Bitmap from ._colorconverter import ColorConverter from ._ondiskbitmap import OnDiskBitmap from ._shape import Shape from ._palette import Palette +from ._structs import RectangleStruct, TransformStruct __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git" -Rectangle = recordclass("Rectangle", "x1 y1 x2 y2") -Transform = recordclass("Transform", "x y dx dy scale transpose_xy mirror_x mirror_y") - class TileGrid: # pylint: disable=too-many-instance-attributes @@ -100,9 +97,11 @@ class TileGrid: self._pixel_width = width * tile_width self._pixel_height = height * tile_height self._tiles = (self._width * self._height) * [default_tile] - self.in_group = False - self._absolute_transform = Transform(0, 0, 1, 1, 1, False, False, False) - self._current_area = Rectangle(0, 0, self._pixel_width, self._pixel_height) + self._in_group = False + self._absolute_transform = TransformStruct(0, 0, 1, 1, 1, False, False, False) + self._current_area = RectangleStruct( + 0, 0, self._pixel_width, self._pixel_height + ) self._moved = False def _update_transform(self, absolute_transform): @@ -296,6 +295,9 @@ class TileGrid: ): buffer.alpha_composite(image, (x, y), source=(source_x, source_y)) + def _finish_refresh(self): + pass + @property def hidden(self) -> bool: """True when the TileGrid is hidden. This may be False even diff --git a/requirements.txt b/requirements.txt index 1355dce..10356cb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,3 @@ Adafruit-Blinka pillow numpy -recordclass diff --git a/setup.py b/setup.py index fa87d91..64f2711 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,6 @@ setup( "Adafruit-Blinka", "pillow", "numpy", - "recordclass", ], # Choose your license license="MIT",