+ output_pixel.opaque = True
+
+ if self._pixel_shader is None:
+ output_pixel.pixel = input_pixel.pixel
+ elif isinstance(self._pixel_shader, Palette):
+ self._pixel_shader._get_color( # pylint: disable=protected-access
+ colorspace, input_pixel, output_pixel
+ )
+ elif isinstance(self._pixel_shader, ColorConverter):
+ self._pixel_shader._convert( # pylint: disable=protected-access
+ colorspace, input_pixel, output_pixel
+ )
+
+ if not output_pixel.opaque:
+ full_coverage = False
+ else:
+ mask[offset // 32] |= 1 << (offset % 32)
+ if colorspace.depth == 16:
+ struct.pack_into(
+ "H",
+ buffer,
+ offset * 2,
+ output_pixel.pixel,
+ )
+ elif colorspace.depth == 32:
+ struct.pack_into(
+ "I",
+ buffer,
+ offset * 4,
+ output_pixel.pixel,
+ )
+ elif colorspace.depth == 8:
+ buffer[offset] = output_pixel.pixel & 0xFF
+ elif colorspace.depth < 8:
+ # Reorder the offsets to pack multiple rows into
+ # a byte (meaning they share a column).
+ if not colorspace.pixels_in_byte_share_row:
+ width = area.width()
+ row = offset // width
+ col = offset % width
+ # Dividing by pixels_per_byte does truncated division
+ # even if we multiply it back out
+ offset = (
+ col * pixels_per_byte
+ + (row // pixels_per_byte) * width
+ + (row % pixels_per_byte)
+ )
+ shift = (offset % pixels_per_byte) * colorspace.depth
+ if colorspace.reverse_pixels_in_byte:
+ # Reverse the shift by subtracting it from the leftmost shift
+ shift = (pixels_per_byte - 1) * colorspace.depth - shift
+ buffer[offset // pixels_per_byte] |= output_pixel.pixel << shift
+ return full_coverage
+
+ def _finish_refresh(self):
+ first_draw = self._previous_area.x1 == self._previous_area.x2
+ hidden = self._hidden_tilegrid or self._hidden_by_parent
+ if not first_draw and hidden:
+ self._previous_area.x2 = self._previous_area.x1
+ elif self._moved or first_draw:
+ self._current_area.copy_into(self._previous_area)
+
+ self._moved = False
+ self._full_change = False
+ self._partial_change = False
+ if isinstance(self._pixel_shader, (Palette, ColorConverter)):
+ self._pixel_shader._finish_refresh() # pylint: disable=protected-access
+ if isinstance(self._bitmap, (Bitmap, Shape)):
+ self._bitmap._finish_refresh() # pylint: disable=protected-access
+
+ def _get_refresh_areas(self, areas: list[Area]) -> None:
+ # pylint: disable=invalid-name, too-many-branches, too-many-statements
+ first_draw = self._previous_area.x1 == self._previous_area.x2
+ hidden = self._hidden_tilegrid or self._hidden_by_parent
+
+ # Check hidden first because it trumps all other changes
+ if hidden:
+ self._rendered_hidden = True
+ if not first_draw:
+ areas.append(self._previous_area)
+ return
+ if self._moved and not first_draw:
+ self._previous_area.union(self._current_area, self._dirty_area)
+ if self._dirty_area.size() < 2 * self._pixel_width * self._pixel_height:
+ areas.append(self._dirty_area)
+ return
+ areas.append(self._current_area)
+ areas.append(self._previous_area)
+ return
+
+ tail = areas[-1] if areas else None
+ # If we have an in-memory bitmap, then check it for modifications
+ if isinstance(self._bitmap, Bitmap):
+ self._bitmap._get_refresh_areas(areas) # pylint: disable=protected-access
+ refresh_area = areas[-1] if areas else None
+ if tail != refresh_area:
+ # Special case a TileGrid that shows a full bitmap and use its
+ # dirty area. Copy it to ours so we can transform it.
+ if self._tiles_in_bitmap == 1:
+ refresh_area.copy_into(self._dirty_area)
+ self._partial_change = True
+ else:
+ self._full_change = True
+ elif isinstance(self._bitmap, Shape):
+ self._bitmap._get_refresh_areas(areas) # pylint: disable=protected-access
+ refresh_area = areas[-1] if areas else None
+ if refresh_area != tail:
+ refresh_area.copy_into(self._dirty_area)
+ self._partial_change = True
+
+ self._full_change = self._full_change or (
+ isinstance(self._pixel_shader, (Palette, ColorConverter))
+ and self._pixel_shader._needs_refresh # pylint: disable=protected-access
+ )
+ if self._full_change or first_draw:
+ areas.append(self._current_area)
+ return