+# SPDX-FileCopyrightText: 2025 Tim Cocks
+#
+# SPDX-License-Identifier: MIT
+"""
+Collection of bitmap manipulation tools
+"""
+
import math
import struct
from typing import Optional, Tuple, BinaryIO
# pylint: disable=invalid-name, too-many-arguments, too-many-locals, too-many-branches, too-many-statements
def fill_region(dest_bitmap: Bitmap, x1: int, y1: int, x2: int, y2: int, value: int):
+ """Draws the color value into the destination bitmap within the
+ rectangular region bounded by (x1,y1) and (x2,y2), exclusive.
+
+ :param bitmap dest_bitmap: Destination bitmap that will be written into
+ :param int x1: x-pixel position of the first corner of the rectangular fill region
+ :param int y1: y-pixel position of the first corner of the rectangular fill region
+ :param int x2: x-pixel position of the second corner of the rectangular fill region (exclusive)
+ :param int y2: y-pixel position of the second corner of the rectangular fill region (exclusive)
+ :param int value: Bitmap palette index that will be written into the rectangular
+ fill region in the destination bitmap"""
+
for y in range(y1, y2):
for x in range(x1, x2):
dest_bitmap[x, y] = value
def draw_line(dest_bitmap: Bitmap, x1: int, y1: int, x2: int, y2: int, value: int):
+ """Draws a line into a bitmap specified two endpoints (x1,y1) and (x2,y2).
+
+ :param bitmap dest_bitmap: Destination bitmap that will be written into
+ :param int x1: x-pixel position of the line's first endpoint
+ :param int y1: y-pixel position of the line's first endpoint
+ :param int x2: x-pixel position of the line's second endpoint
+ :param int y2: y-pixel position of the line's second endpoint
+ :param int value: Bitmap palette index that will be written into the
+ line in the destination bitmap"""
dx = abs(x2 - x1)
sx = 1 if x1 < x2 else -1
dy = -abs(y2 - y1)
def draw_circle(dest_bitmap: Bitmap, x: int, y: int, radius: int, value: int):
+ """Draws a circle into a bitmap specified using a center (x0,y0) and radius r.
+
+ :param bitmap dest_bitmap: Destination bitmap that will be written into
+ :param int x: x-pixel position of the circle's center
+ :param int y: y-pixel position of the circle's center
+ :param int radius: circle's radius
+ :param int value: Bitmap palette index that will be written into the
+ circle in the destination bitmap"""
+
x = max(0, min(x, dest_bitmap.width - 1))
y = max(0, min(y, dest_bitmap.height - 1))
value: int,
close: bool = True,
):
+ """Draw a polygon connecting points on provided bitmap with provided value
+
+ :param bitmap dest_bitmap: Destination bitmap that will be written into
+ :param ReadableBuffer xs: x-pixel position of the polygon's vertices
+ :param ReadableBuffer ys: y-pixel position of the polygon's vertices
+ :param int value: Bitmap palette index that will be written into the
+ line in the destination bitmap
+ :param bool close: (Optional) Whether to connect first and last point. (True)
+ """
if len(xs) != len(ys):
raise ValueError("Length of xs and ys must be equal.")
skip_source_index: int | None = None,
skip_dest_index: int | None = None,
):
- """Inserts the source_bitmap region defined by rectangular boundaries"""
+ """Inserts the source_bitmap region defined by rectangular boundaries
+ (x1,y1) and (x2,y2) into the bitmap at the specified (x,y) location.
+
+ :param bitmap dest_bitmap: Destination bitmap that the area will
+ be copied into.
+ :param bitmap source_bitmap: Source bitmap that contains the graphical
+ region to be copied
+ :param int x: Horizontal pixel location in bitmap where source_bitmap
+ upper-left corner will be placed
+ :param int y: Vertical pixel location in bitmap where source_bitmap upper-left
+ corner will be placed
+ :param int x1: Minimum x-value for rectangular bounding box to be
+ copied from the source bitmap
+ :param int y1: Minimum y-value for rectangular bounding box to be
+ copied from the source bitmap
+ :param int x2: Maximum x-value (exclusive) for rectangular
+ bounding box to be copied from the source bitmap.
+ If unspecified or `None`, the source bitmap width is used.
+ :param int y2: Maximum y-value (exclusive) for rectangular
+ bounding box to be copied from the source bitmap.
+ If unspecified or `None`, the source bitmap height is used.
+ :param int skip_source_index: bitmap palette index in the source that
+ will not be copied, set to None to copy all pixels
+ :param int skip_dest_index: bitmap palette index in the
+ destination bitmap that will not get overwritten by the
+ pixels from the source"""
+
# pylint: disable=invalid-name
if x2 is None:
x2 = source_bitmap.width
scale: Optional[float] = None,
skip_index: Optional[int] = None,
):
+ """Inserts the source bitmap region into the destination bitmap with rotation
+ (angle), scale and clipping (both on source and destination bitmaps).
+
+ :param bitmap dest_bitmap: Destination bitmap that will be copied into
+ :param bitmap source_bitmap: Source bitmap that contains the graphical region to be copied
+ :param int ox: Horizontal pixel location in destination bitmap where source bitmap
+ point (px,py) is placed. Defaults to None which causes it to use the horizontal
+ midway point of the destination bitmap.
+ :param int oy: Vertical pixel location in destination bitmap where source bitmap
+ point (px,py) is placed. Defaults to None which causes it to use the vertical
+ midway point of the destination bitmap.
+ :param Tuple[int,int] dest_clip0: First corner of rectangular destination clipping
+ region that constrains region of writing into destination bitmap
+ :param Tuple[int,int] dest_clip1: Second corner of rectangular destination clipping
+ region that constrains region of writing into destination bitmap
+ :param int px: Horizontal pixel location in source bitmap that is placed into the
+ destination bitmap at (ox,oy). Defaults to None which causes it to use the
+ horizontal midway point in the source bitmap.
+ :param int py: Vertical pixel location in source bitmap that is placed into the
+ destination bitmap at (ox,oy). Defaults to None which causes it to use the
+ vertical midway point in the source bitmap.
+ :param Tuple[int,int] source_clip0: First corner of rectangular source clipping
+ region that constrains region of reading from the source bitmap
+ :param Tuple[int,int] source_clip1: Second corner of rectangular source clipping
+ region that constrains region of reading from the source bitmap
+ :param float angle: Angle of rotation, in radians (positive is clockwise direction).
+ Defaults to None which gets treated as 0.0 radians or no rotation.
+ :param float scale: Scaling factor. Defaults to None which gets treated as 1.0 or same
+ as original source size.
+ :param int skip_index: Bitmap palette index in the source that will not be copied,
+ set to None to copy all pixels"""
if ox is None:
ox = dest_bitmap.width // 2
if oy is None:
y2: Optional[int] = None,
skip_index: Optional[int] = None,
):
+ """Inserts pixels from ``data`` into the rectangle
+ of width×height pixels with the upper left corner at ``(x,y)``
+
+ The values from ``data`` are taken modulo the number of color values
+ available in the destination bitmap.
+
+ If x1 or y1 are not specified, they are taken as 0. If x2 or y2
+ are not specified, or are given as None, they are taken as the width
+ and height of the image.
+
+ The coordinates affected by the blit are
+ ``x1 <= x < x2`` and ``y1 <= y < y2``.
+
+ ``data`` must contain at least as many elements as required. If it
+ contains excess elements, they are ignored.
+
+ The blit takes place by rows, so the first elements of ``data`` go
+ to the first row, the next elements to the next row, and so on.
+
+ :param displayio.Bitmap bitmap: A writable bitmap
+ :param ReadableBuffer data: Buffer containing the source pixel values
+ :param int x1: The left corner of the area to blit into (inclusive)
+ :param int y1: The top corner of the area to blit into (inclusive)
+ :param int x2: The right of the area to blit into (exclusive)
+ :param int y2: The bottom corner of the area to blit into (exclusive)
+ :param int skip_index: Bitmap palette index in the source
+ that will not be copied, set to None to copy all pixels
+
+ """
if x2 is None:
x2 = bitmap.width
if y2 is None:
swap_bytes: bool = False,
reverse_rows: bool = False,
):
+ """Reads from a binary file into a bitmap.
+
+ The file must be positioned so that it consists of ``bitmap.height``
+ rows of pixel data, where each row is the smallest multiple
+ of ``element_size`` bytes that can hold ``bitmap.width`` pixels.
+
+ The bytes in an element can be optionally swapped, and the pixels
+ in an element can be reversed. Also, therow loading direction can
+ be reversed, which may be requires for loading certain bitmap files.
+
+ This function doesn't parse image headers, but is useful to
+ speed up loading of uncompressed image formats such as PCF glyph data.
+
+ :param displayio.Bitmap bitmap: A writable bitmap
+ :param typing.BinaryIO file: A file opened in binary mode
+ :param int bits_per_pixel: Number of bits per pixel.
+ Values 1, 2, 4, 8, 16, 24, and 32 are supported;
+ :param int element_size: Number of bytes per element.
+ Values of 1, 2, and 4 are supported, except that 24
+ ``bits_per_pixel`` requires 1 byte per element.
+ :param bool reverse_pixels_in_element: If set, the first pixel in a
+ word is taken from the Most Significant Bits; otherwise,
+ it is taken from the Least Significant Bits.
+ :param bool swap_bytes_in_element: If the ``element_size`` is not 1,
+ then reverse the byte order of each element read.
+ :param bool reverse_rows: Reverse the direction of the row loading
+ (required for some bitmap images).
+ """
width = bitmap.width
height = bitmap.height
bits_per_value = bitmap._bits_per_value
class BlendMode:
+ """
+ Options for modes to use by alphablend() function.
+ """
+
Normal = "bitmaptools.BlendMode.Normal"
Screen = "bitmaptools.BlendMode.Screen"
skip_source1_index: Optional[int] = None,
skip_source2_index: Optional[int] = None,
):
- """
- colorspace should be one of: 'L8', 'RGB565', 'RGB565_SWAPPED', 'BGR565_SWAPPED'.
-
- blendmode must be one of the BlendMode constants
- """
+ """Alpha blend the two source bitmaps into the destination.
+
+ It is permitted for the destination bitmap to be one of the two
+ source bitmaps.
+
+ :param bitmap dest_bitmap: Destination bitmap that will be written into
+ :param bitmap source_bitmap_1: The first source bitmap
+ :param bitmap source_bitmap_2: The second source bitmap
+ :param float factor1: The proportion of bitmap 1 to mix in
+ :param float factor2: The proportion of bitmap 2 to mix in.
+ If specified as `None`, ``1-factor1`` is used. Usually the proportions should sum to 1.
+ :param displayio.Colorspace colorspace: The colorspace of the bitmaps.
+ They must all have the same colorspace. Only the following colorspaces are permitted:
+ ``L8``, ``RGB565``, ``RGB565_SWAPPED``, ``BGR565`` and ``BGR565_SWAPPED``.
+ :param bitmaptools.BlendMode blendmode: The blend mode to use. Default is Normal.
+ :param int skip_source1_index: Bitmap palette or luminance index in
+ source_bitmap_1 that will not be blended, set to None to blend all pixels
+ :param int skip_source2_index: Bitmap palette or luminance index in
+ source_bitmap_2 that will not be blended, set to None to blend all pixels
+
+ For the L8 colorspace, the bitmaps must have a bits-per-value of 8.
+ For the RGB colorspaces, they must have a bits-per-value of 16."""
def clamp(val, minval, maxval):
return max(minval, min(maxval, val))
class DitherAlgorithm:
+ """
+ Options for algorithm to use by dither() function.
+ """
+
Atkinson = "bitmaptools.DitherAlgorithm.Atkinson"
FloydStenberg = "bitmaptools.DitherAlgorithm.FloydStenberg"
def dither(dest_bitmap, source_bitmap, colorspace, algorithm=DitherAlgorithm.Atkinson):
+ """Convert the input image into a 2-level output image using the given dither algorithm.
+
+ :param bitmap dest_bitmap: Destination bitmap. It must have
+ a value_count of 2 or 65536. The stored values are 0 and the maximum pixel value.
+ :param bitmap source_bitmap: Source bitmap that contains the
+ graphical region to be dithered. It must have a value_count of 65536.
+ :param colorspace: The colorspace of the image. The supported colorspaces
+ are ``RGB565``, ``BGR565``, ``RGB565_SWAPPED``, and ``BGR565_SWAPPED``
+ :param algorithm: The dither algorithm to use, one of the `DitherAlgorithm` values.
+ """
SWAP_BYTES = 1 << 0
SWAP_RB = 1 << 1
height, width = dest_bitmap.width, dest_bitmap.height
fill_color_value: int,
replaced_color_value: Optional[int] = None,
):
+ """Draws the color value into the destination bitmap enclosed
+ area of pixels of the background_value color. Like "Paint Bucket"
+ fill tool.
+
+ :param bitmap dest_bitmap: Destination bitmap that will be written into
+ :param int x: x-pixel position of the first pixel to check and fill if needed
+ :param int y: y-pixel position of the first pixel to check and fill if needed
+ :param int fill_color_value: Bitmap palette index that will be written into the
+ enclosed area in the destination bitmap
+ :param int replaced_color_value: Bitmap palette index that will filled with the
+ value color in the enclosed area in the destination bitmap"""
if fill_color_value == replaced_color_value:
return
if replaced_color_value == -1: