"""
-__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
+import time
+import struct
+import circuitpython_typing
+from paralleldisplay import ParallelBus
from ._fourwire import FourWire
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 ._displaybus import _DisplayBus
+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,
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)):
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()
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)
+ # pylint: disable=protected-access, too-many-branches
+ 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
+ self.width, self.height = self.height, self.width
- height = self._height
- width = 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
+ 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)
+ 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
circuitpython_splash = _Supervisor().circuitpython_splash
if not circuitpython_splash._in_group:
root_group = circuitpython_splash
- elif self._current_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: circuitpython_typing.WriteableBuffer,
+ buffer: circuitpython_typing.WriteableBuffer,
) -> bool:
- # pylint: disable=protected-access
+ """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(self, rectangle):
+ if self._core.rotation in (90, 270):
+ width, height = self._core.height, self._core.width
+ else:
+ width, height = self._core.width, self._core.height
- return self._current_group._fill_area(self._colorspace, area, mask, buffer)
+ rectangle.x1 = max(rectangle.x1, 0)
+ rectangle.y1 = max(rectangle.y1, 0)
+ rectangle.x2 = min(rectangle.x2, width)
+ rectangle.y2 = min(rectangle.y2, height)
+
+ return rectangle
+ """
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:
+ 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:
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_type = DISPLAY_DATA
+ if not self.data_as_commands:
+ self.send(
+ DISPLAY_COMMAND, CHIP_SELECT_UNTOUCHED, bytes([self.column_command])
+ )
+ else:
+ data_type = DISPLAY_COMMAND
+ """
+ self._core.send(
+ DISPLAY_COMMAND, CHIP_SELECT_TOGGLE_EVERY_BYTE, bytes([command]) + data
+ )
+ self._core.send(DISPLAY_DATA, CHIP_SELECT_UNTOUCHED, data)
+ """
+
+ 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()
+
+ if not self.data_as_commands:
+ self.send(DISPLAY_COMMAND, CHIP_SELECT_UNTOUCHED, bytes([self.row_command]))
+
+ if self.ram_width < 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()
+
+ """
+ img = self._buffer.convert("RGB").crop(astuple(area))
+ img = img.rotate(360 - self._core.rotation, expand=True)
+
+ display_area = self._apply_rotation(area)
+
+ img = img.crop(astuple(display_area))
+
+ data = numpy.array(img).astype("uint16")
+ color = (
+ ((data[:, :, 0] & 0xF8) << 8)
+ | ((data[:, :, 1] & 0xFC) << 3)
+ | (data[:, :, 2] >> 3)
+ )
+
+ pixels = bytes(
+ numpy.dstack(((color >> 8) & 0xFF, color & 0xFF)).flatten().tolist()
+ )
+ """
+
+ def send(
+ self,
+ data_type: int,
+ chip_select: int,
+ data: circuitpython_typing.ReadableBuffer,
+ ) -> None:
"""
Send the data to the current bus
"""
"""
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