- def _refresh_display_area(self, rectangle):
- """Loop through dirty rectangles and redraw that area."""
- img = self._buffer.convert("RGB").crop(astuple(rectangle))
- img = img.rotate(360 - self._rotation, expand=True)
-
- display_rectangle = self._apply_rotation(rectangle)
- img = img.crop(astuple(self._clip(display_rectangle)))
-
- 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()
- )
-
- self._send(
- self._set_column_command,
- self._encode_pos(
- display_rectangle.x1 + self._colstart,
- display_rectangle.x2 + self._colstart - 1,
- ),
- )
- self._send(
- self._set_row_command,
- self._encode_pos(
- display_rectangle.y1 + self._rowstart,
- display_rectangle.y2 + self._rowstart - 1,
- ),
- )
-
- self._core.begin_transaction()
- self._send_pixels(pixels)
- self._core.end_transaction()
-
- def _clip(self, rectangle):
- if self._rotation in (90, 270):
- width, height = self._height, self._width
- else:
- width, height = self._width, self._height
-
- 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 _apply_rotation(self, rectangle):
- """Adjust the rectangle coordinates based on rotation"""
- if self._rotation == 90:
- return RectangleStruct(
- self._height - rectangle.y2,
- rectangle.x1,
- self._height - rectangle.y1,
- rectangle.x2,
- )
- if self._rotation == 180:
- return RectangleStruct(
- self._width - rectangle.x2,
- self._height - rectangle.y2,
- self._width - rectangle.x1,
- self._height - rectangle.y1,
- )
- if self._rotation == 270:
- return RectangleStruct(
- rectangle.y1,
- self._width - rectangle.x2,
- rectangle.y2,
- self._width - rectangle.x1,
+ def _refresh_area(self, area) -> bool:
+ """Loop through dirty areas and redraw that area."""
+ # pylint: disable=too-many-locals
+ 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
+
+ rows_per_buffer = clipped.height()
+ pixels_per_word = (struct.calcsize("I") * 8) // self._core.colorspace.depth
+ 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
+ elif clipped.size() > buffer_size * pixels_per_word:
+ 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
+ ):
+ pixels_per_byte = 8 // self._core.colorspace.depth
+ if rows_per_buffer % pixels_per_byte != 0:
+ rows_per_buffer -= rows_per_buffer % pixels_per_byte
+ subrectangles = clipped.height() // rows_per_buffer
+ if clipped.height() % rows_per_buffer != 0:
+ subrectangles += 1
+ pixels_per_buffer = rows_per_buffer * clipped.width()
+ buffer_size = pixels_per_buffer // pixels_per_word
+ if pixels_per_buffer % pixels_per_word:
+ buffer_size += 1
+
+ # TODO: Optimize with memoryview
+ buffer = bytearray([0] * (buffer_size * struct.calcsize("I")))
+ mask_length = (pixels_per_buffer // 32) + 1
+ mask = array("L", [0] * mask_length)
+ remaining_rows = clipped.height()
+
+ for subrect_index in range(subrectangles):
+ subrectangle = Area(
+ clipped.x1,
+ clipped.y1 + rows_per_buffer * subrect_index,
+ clipped.x2,
+ clipped.y1 + rows_per_buffer * (subrect_index + 1),