]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blobdiff - displayio/_display.py
More bug fixes
[hackapet/Adafruit_Blinka_Displayio.git] / displayio / _display.py
index 8e33803a7293df2df6e27eac7650ac30c0638080..74ee0957b697e3a494dfec6fa29ed009916fac3a 100644 (file)
@@ -19,16 +19,15 @@ displayio for Blinka
 
 import time
 import struct
 
 import time
 import struct
+from array import array
 from typing import Optional
 import digitalio
 from typing import Optional
 import digitalio
-from PIL import Image
 import microcontroller
 import microcontroller
-import circuitpython_typing
+from circuitpython_typing import WriteableBuffer, ReadableBuffer
 from ._displaycore import _DisplayCore
 from ._displaybus import _DisplayBus
 from ._colorconverter import ColorConverter
 from ._displaycore import _DisplayCore
 from ._displaybus import _DisplayBus
 from ._colorconverter import ColorConverter
-from ._group import Group
-from ._structs import RectangleStruct
+from ._group import Group, circuitpython_splash
 from ._area import Area
 from ._constants import (
     CHIP_SELECT_TOGGLE_EVERY_BYTE,
 from ._area import Area
 from ._constants import (
     CHIP_SELECT_TOGGLE_EVERY_BYTE,
@@ -57,7 +56,7 @@ class Display:
     def __init__(
         self,
         display_bus: _DisplayBus,
     def __init__(
         self,
         display_bus: _DisplayBus,
-        init_sequence: circuitpython_typing.ReadableBuffer,
+        init_sequence: ReadableBuffer,
         *,
         width: int,
         height: int,
         *,
         width: int,
         height: int,
@@ -76,16 +75,14 @@ class Display:
         backlight_pin: Optional[microcontroller.Pin] = None,
         brightness_command: Optional[int] = None,
         brightness: float = 1.0,
         backlight_pin: Optional[microcontroller.Pin] = None,
         brightness_command: Optional[int] = None,
         brightness: float = 1.0,
-        auto_brightness: bool = False,
         single_byte_bounds: bool = False,
         data_as_commands: bool = False,
         auto_refresh: bool = True,
         native_frames_per_second: int = 60,
         backlight_on_high: bool = True,
         SH1107_addressing: bool = False,
         single_byte_bounds: bool = False,
         data_as_commands: bool = False,
         auto_refresh: bool = True,
         native_frames_per_second: int = 60,
         backlight_on_high: bool = True,
         SH1107_addressing: bool = False,
-        set_vertical_scroll: int = 0,
     ):
     ):
-        # pylint: disable=unused-argument,too-many-locals,invalid-name
+        # pylint: disable=too-many-locals,invalid-name
         """Create a Display object on the given display bus (`displayio.FourWire` or
         `paralleldisplay.ParallelBus`).
 
         """Create a Display object on the given display bus (`displayio.FourWire` or
         `paralleldisplay.ParallelBus`).
 
@@ -156,17 +153,14 @@ class Display:
         self._native_frames_per_second = native_frames_per_second
         self._native_ms_per_frame = 1000 // native_frames_per_second
 
         self._native_frames_per_second = native_frames_per_second
         self._native_ms_per_frame = 1000 // native_frames_per_second
 
-        self._auto_brightness = auto_brightness
         self._brightness = brightness
         self._auto_refresh = auto_refresh
 
         self._initialize(init_sequence)
         self._brightness = brightness
         self._auto_refresh = auto_refresh
 
         self._initialize(init_sequence)
-        self._buffer = Image.new("RGB", (width, height))
+
         self._current_group = None
         self._last_refresh_call = 0
         self._refresh_thread = None
         self._current_group = None
         self._last_refresh_call = 0
         self._refresh_thread = None
-        if self._auto_refresh:
-            self.auto_refresh = True
         self._colorconverter = ColorConverter()
 
         self._backlight_type = None
         self._colorconverter = ColorConverter()
 
         self._backlight_type = None
@@ -184,15 +178,19 @@ class Display:
                 self._backlight_type = BACKLIGHT_IN_OUT
                 self._backlight = digitalio.DigitalInOut(backlight_pin)
                 self._backlight.switch_to_output()
                 self._backlight_type = BACKLIGHT_IN_OUT
                 self._backlight = digitalio.DigitalInOut(backlight_pin)
                 self._backlight.switch_to_output()
-            self.brightness = brightness
+        self.brightness = brightness
+        if not circuitpython_splash._in_group:
+            self._set_root_group(circuitpython_splash)
+        self.auto_refresh = auto_refresh
 
     def __new__(cls, *args, **kwargs):
         from . import (  # pylint: disable=import-outside-toplevel, cyclic-import
             allocate_display,
         )
 
 
     def __new__(cls, *args, **kwargs):
         from . import (  # pylint: disable=import-outside-toplevel, cyclic-import
             allocate_display,
         )
 
-        allocate_display(cls)
-        return super().__new__(cls)
+        display_instance = super().__new__(cls)
+        allocate_display(display_instance)
+        return display_instance
 
     def _initialize(self, init_sequence):
         i = 0
 
     def _initialize(self, init_sequence):
         i = 0
@@ -239,7 +237,14 @@ class Display:
         """Switches to displaying the given group of layers. When group is None, the
         default CircuitPython terminal will be shown.
         """
         """Switches to displaying the given group of layers. When group is None, the
         default CircuitPython terminal will be shown.
         """
-        self._core.show(group)
+        if group is None:
+            group = circuitpython_splash
+        self._core.set_root_group(group)
+
+    def _set_root_group(self, root_group: Group) -> None:
+        ok = self._core.set_root_group(root_group)
+        if not ok:
+            raise ValueError("Group already used")
 
     def refresh(
         self,
 
     def refresh(
         self,
@@ -290,23 +295,10 @@ class Display:
         return True
 
     def _refresh_display(self):
         return True
 
     def _refresh_display(self):
-        # pylint: disable=protected-access
         if not self._core.start_refresh():
             return False
 
         if not self._core.start_refresh():
             return False
 
-        # TODO: Likely move this to _refresh_area()
-        # Go through groups and and add each to buffer
-        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._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)
-
         areas_to_refresh = self._get_refresh_areas()
         areas_to_refresh = self._get_refresh_areas()
-
         for area in areas_to_refresh:
             self._refresh_area(area)
 
         for area in areas_to_refresh:
             self._refresh_area(area)
 
@@ -317,9 +309,12 @@ class Display:
     def _get_refresh_areas(self) -> list[Area]:
         """Get a list of areas to be refreshed"""
         areas = []
     def _get_refresh_areas(self) -> list[Area]:
         """Get a list of areas to be refreshed"""
         areas = []
-        if self._core.current_group is not None:
-            # Eventually calculate dirty rectangles here
-            areas.append(Area(0, 0, self._core.width, self._core.height))
+        if self._core.full_refresh:
+            areas.append(self._core.area)
+        elif self._core.current_group is not None:
+            self._core.current_group._get_refresh_areas(  # pylint: disable=protected-access
+                areas
+            )
         return areas
 
     def background(self):
         return areas
 
     def background(self):
@@ -337,6 +332,8 @@ class Display:
         buffer_size = 128
 
         clipped = Area()
         buffer_size = 128
 
         clipped = Area()
+        # Clip the area to the display by overlapping the areas.
+        # If there is no overlap then we're done.
         if not self._core.clip_area(area, clipped):
             return True
 
         if not self._core.clip_area(area, clipped):
             return True
 
@@ -345,7 +342,8 @@ class Display:
         pixels_per_buffer = clipped.size()
 
         subrectangles = 1
         pixels_per_buffer = clipped.size()
 
         subrectangles = 1
-
+        # for SH1107 and other boundary constrained controllers
+        #      write one single row at a time
         if self._core.sh1107_addressing:
             subrectangles = rows_per_buffer // 8
             rows_per_buffer = 8
         if self._core.sh1107_addressing:
             subrectangles = rows_per_buffer // 8
             rows_per_buffer = 8
@@ -353,6 +351,7 @@ class Display:
             rows_per_buffer = buffer_size * pixels_per_word // clipped.width()
             if rows_per_buffer == 0:
                 rows_per_buffer = 1
             rows_per_buffer = buffer_size * pixels_per_word // clipped.width()
             if rows_per_buffer == 0:
                 rows_per_buffer = 1
+            # If pixels are packed by column then ensure rows_per_buffer is on a byte boundary
             if (
                 self._core.colorspace.depth < 8
                 and self._core.colorspace.pixels_in_byte_share_row
             if (
                 self._core.colorspace.depth < 8
                 and self._core.colorspace.pixels_in_byte_share_row
@@ -368,9 +367,10 @@ class Display:
             if pixels_per_buffer % pixels_per_word:
                 buffer_size += 1
 
             if pixels_per_buffer % pixels_per_word:
                 buffer_size += 1
 
-        buffer = bytearray(buffer_size)
+        # TODO: Optimize with memoryview
+        buffer = bytearray([0] * (buffer_size * struct.calcsize("I")))
         mask_length = (pixels_per_buffer // 32) + 1
         mask_length = (pixels_per_buffer // 32) + 1
-        mask = bytearray(mask_length)
+        mask = array("L", [0] * mask_length)
         remaining_rows = clipped.height()
 
         for subrect_index in range(subrectangles):
         remaining_rows = clipped.height()
 
         for subrect_index in range(subrectangles):
@@ -382,6 +382,7 @@ class Display:
             )
             if remaining_rows < rows_per_buffer:
                 subrectangle.y2 = subrectangle.y1 + remaining_rows
             )
             if remaining_rows < rows_per_buffer:
                 subrectangle.y2 = subrectangle.y1 + remaining_rows
+            remaining_rows -= rows_per_buffer
             self._core.set_region_to_update(subrectangle)
             if self._core.colorspace.depth >= 8:
                 subrectangle_size_bytes = subrectangle.size() * (
             self._core.set_region_to_update(subrectangle)
             if self._core.colorspace.depth >= 8:
                 subrectangle_size_bytes = subrectangle.size() * (
@@ -393,45 +394,27 @@ class Display:
                 )
 
             self._core.fill_area(subrectangle, mask, buffer)
                 )
 
             self._core.fill_area(subrectangle, mask, buffer)
-
             self._core.begin_transaction()
             self._send_pixels(buffer[:subrectangle_size_bytes])
             self._core.end_transaction()
         return True
 
             self._core.begin_transaction()
             self._send_pixels(buffer[:subrectangle_size_bytes])
             self._core.end_transaction()
         return True
 
-    def _apply_rotation(self, rectangle):
-        """Adjust the rectangle coordinates based on rotation"""
-        if self._core.rotation == 90:
-            return RectangleStruct(
-                self._core.height - rectangle.y2,
-                rectangle.x1,
-                self._core.height - rectangle.y1,
-                rectangle.x2,
-            )
-        if self._core.rotation == 180:
-            return RectangleStruct(
-                self._core.width - rectangle.x2,
-                self._core.height - rectangle.y2,
-                self._core.width - rectangle.x1,
-                self._core.height - rectangle.y1,
-            )
-        if self._core.rotation == 270:
-            return RectangleStruct(
-                rectangle.y1,
-                self._core.width - rectangle.x2,
-                rectangle.y2,
-                self._core.width - rectangle.x1,
-            )
-        return rectangle
-
-    def fill_row(
-        self, y: int, buffer: circuitpython_typing.WriteableBuffer
-    ) -> circuitpython_typing.WriteableBuffer:
+    def fill_row(self, y: int, buffer: WriteableBuffer) -> WriteableBuffer:
         """Extract the pixels from a single row"""
         """Extract the pixels from a single row"""
-        for x in range(0, self._core.width):
-            _rgb_565 = self._colorconverter.convert(self._buffer.getpixel((x, y)))
-            buffer[x * 2] = (_rgb_565 >> 8) & 0xFF
-            buffer[x * 2 + 1] = _rgb_565 & 0xFF
+        if self._core.colorspace.depth != 16:
+            raise ValueError("Display must have a 16 bit colorspace.")
+
+        area = Area(0, y, self._core.width, y + 1)
+        pixels_per_word = (struct.calcsize("I") * 8) // self._core.colorspace.depth
+        buffer_size = self._core.width // pixels_per_word
+        pixels_per_buffer = area.size()
+        if pixels_per_buffer % pixels_per_word:
+            buffer_size += 1
+
+        buffer = bytearray([0] * (buffer_size * struct.calcsize("I")))
+        mask_length = (pixels_per_buffer // 32) + 1
+        mask = array("L", [0x00000000] * mask_length)
+        self._core.fill_area(area, mask, buffer)
         return buffer
 
     def release(self) -> None:
         return buffer
 
     def release(self) -> None:
@@ -442,6 +425,10 @@ class Display:
     def reset(self) -> None:
         """Reset the display"""
         self.auto_refresh = True
     def reset(self) -> None:
         """Reset the display"""
         self.auto_refresh = True
+        circuitpython_splash.x = 0
+        circuitpython_splash.y = 0
+        if not circuitpython_splash._in_group:  # pylint: disable=protected-access
+            self._set_root_group(circuitpython_splash)
 
     @property
     def auto_refresh(self) -> bool:
 
     @property
     def auto_refresh(self) -> bool:
@@ -455,10 +442,7 @@ class Display:
 
     @property
     def brightness(self) -> float:
 
     @property
     def brightness(self) -> float:
-        """The brightness of the display as a float. 0.0 is off and 1.0 is full `brightness`.
-        When `auto_brightness` is True, the value of `brightness` will change automatically.
-        If `brightness` is set, `auto_brightness` will be disabled and will be set to False.
-        """
+        """The brightness of the display as a float. 0.0 is off and 1.0 is full `brightness`."""
         return self._brightness
 
     @brightness.setter
         return self._brightness
 
     @brightness.setter
@@ -493,19 +477,6 @@ class Display:
         else:
             raise ValueError("Brightness must be between 0.0 and 1.0")
 
         else:
             raise ValueError("Brightness must be between 0.0 and 1.0")
 
-    @property
-    def auto_brightness(self) -> bool:
-        """True when the display brightness is adjusted automatically, based on an ambient
-        light sensor or other method. Note that some displays may have this set to True by
-        default, but not actually implement automatic brightness adjustment.
-        `auto_brightness` is set to False if `brightness` is set manually.
-        """
-        return self._auto_brightness
-
-    @auto_brightness.setter
-    def auto_brightness(self, value: bool):
-        self._auto_brightness = value
-
     @property
     def width(self) -> int:
         """Display Width"""
     @property
     def width(self) -> int:
         """Display Width"""