--- /dev/null
+# 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)
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"""
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:
"""
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,
"""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")
CHIP_SELECT_UNTOUCHED = 0
CHIP_SELECT_TOGGLE_EVERY_BYTE = 1
+
+BACKLIGHT_IN_OUT = 1
+BACKLIGHT_PWM = 2
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
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,
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
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
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,
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
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):
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 = (
),
)
- 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):
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,
@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()
--- /dev/null
+# 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
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.
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:
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)
"""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)
"""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
--- /dev/null
+# 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
"""
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
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):
):
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
Adafruit-Blinka
pillow
numpy
-recordclass
"Adafruit-Blinka",
"pillow",
"numpy",
- "recordclass",
],
# Choose your license
license="MIT",