1+ # SPDX-FileCopyrightText: 2025 Tim Cocks
2+ #
3+ # SPDX-License-Identifier: MIT
4+ """
5+ Collection of bitmap manipulation tools
6+ """
7+
18import math
29import struct
310from typing import Optional , Tuple , BinaryIO
714
815# pylint: disable=invalid-name, too-many-arguments, too-many-locals, too-many-branches, too-many-statements
916def fill_region (dest_bitmap : Bitmap , x1 : int , y1 : int , x2 : int , y2 : int , value : int ):
17+ """Draws the color value into the destination bitmap within the
18+ rectangular region bounded by (x1,y1) and (x2,y2), exclusive.
19+
20+ :param bitmap dest_bitmap: Destination bitmap that will be written into
21+ :param int x1: x-pixel position of the first corner of the rectangular fill region
22+ :param int y1: y-pixel position of the first corner of the rectangular fill region
23+ :param int x2: x-pixel position of the second corner of the rectangular fill region (exclusive)
24+ :param int y2: y-pixel position of the second corner of the rectangular fill region (exclusive)
25+ :param int value: Bitmap palette index that will be written into the rectangular
26+ fill region in the destination bitmap"""
27+
1028 for y in range (y1 , y2 ):
1129 for x in range (x1 , x2 ):
1230 dest_bitmap [x , y ] = value
1331
1432
1533def draw_line (dest_bitmap : Bitmap , x1 : int , y1 : int , x2 : int , y2 : int , value : int ):
34+ """Draws a line into a bitmap specified two endpoints (x1,y1) and (x2,y2).
35+
36+ :param bitmap dest_bitmap: Destination bitmap that will be written into
37+ :param int x1: x-pixel position of the line's first endpoint
38+ :param int y1: y-pixel position of the line's first endpoint
39+ :param int x2: x-pixel position of the line's second endpoint
40+ :param int y2: y-pixel position of the line's second endpoint
41+ :param int value: Bitmap palette index that will be written into the
42+ line in the destination bitmap"""
1643 dx = abs (x2 - x1 )
1744 sx = 1 if x1 < x2 else - 1
1845 dy = - abs (y2 - y1 )
@@ -33,6 +60,15 @@ def draw_line(dest_bitmap: Bitmap, x1: int, y1: int, x2: int, y2: int, value: in
3360
3461
3562def draw_circle (dest_bitmap : Bitmap , x : int , y : int , radius : int , value : int ):
63+ """Draws a circle into a bitmap specified using a center (x0,y0) and radius r.
64+
65+ :param bitmap dest_bitmap: Destination bitmap that will be written into
66+ :param int x: x-pixel position of the circle's center
67+ :param int y: y-pixel position of the circle's center
68+ :param int radius: circle's radius
69+ :param int value: Bitmap palette index that will be written into the
70+ circle in the destination bitmap"""
71+
3672 x = max (0 , min (x , dest_bitmap .width - 1 ))
3773 y = max (0 , min (y , dest_bitmap .height - 1 ))
3874
@@ -66,6 +102,15 @@ def draw_polygon(
66102 value : int ,
67103 close : bool = True ,
68104):
105+ """Draw a polygon connecting points on provided bitmap with provided value
106+
107+ :param bitmap dest_bitmap: Destination bitmap that will be written into
108+ :param ReadableBuffer xs: x-pixel position of the polygon's vertices
109+ :param ReadableBuffer ys: y-pixel position of the polygon's vertices
110+ :param int value: Bitmap palette index that will be written into the
111+ line in the destination bitmap
112+ :param bool close: (Optional) Whether to connect first and last point. (True)
113+ """
69114 if len (xs ) != len (ys ):
70115 raise ValueError ("Length of xs and ys must be equal." )
71116
@@ -107,7 +152,33 @@ def blit(
107152 skip_source_index : int | None = None ,
108153 skip_dest_index : int | None = None ,
109154):
110- """Inserts the source_bitmap region defined by rectangular boundaries"""
155+ """Inserts the source_bitmap region defined by rectangular boundaries
156+ (x1,y1) and (x2,y2) into the bitmap at the specified (x,y) location.
157+
158+ :param bitmap dest_bitmap: Destination bitmap that the area will
159+ be copied into.
160+ :param bitmap source_bitmap: Source bitmap that contains the graphical
161+ region to be copied
162+ :param int x: Horizontal pixel location in bitmap where source_bitmap
163+ upper-left corner will be placed
164+ :param int y: Vertical pixel location in bitmap where source_bitmap upper-left
165+ corner will be placed
166+ :param int x1: Minimum x-value for rectangular bounding box to be
167+ copied from the source bitmap
168+ :param int y1: Minimum y-value for rectangular bounding box to be
169+ copied from the source bitmap
170+ :param int x2: Maximum x-value (exclusive) for rectangular
171+ bounding box to be copied from the source bitmap.
172+ If unspecified or `None`, the source bitmap width is used.
173+ :param int y2: Maximum y-value (exclusive) for rectangular
174+ bounding box to be copied from the source bitmap.
175+ If unspecified or `None`, the source bitmap height is used.
176+ :param int skip_source_index: bitmap palette index in the source that
177+ will not be copied, set to None to copy all pixels
178+ :param int skip_dest_index: bitmap palette index in the
179+ destination bitmap that will not get overwritten by the
180+ pixels from the source"""
181+
111182 # pylint: disable=invalid-name
112183 if x2 is None :
113184 x2 = source_bitmap .width
@@ -167,6 +238,37 @@ def rotozoom(
167238 scale : Optional [float ] = None ,
168239 skip_index : Optional [int ] = None ,
169240):
241+ """Inserts the source bitmap region into the destination bitmap with rotation
242+ (angle), scale and clipping (both on source and destination bitmaps).
243+
244+ :param bitmap dest_bitmap: Destination bitmap that will be copied into
245+ :param bitmap source_bitmap: Source bitmap that contains the graphical region to be copied
246+ :param int ox: Horizontal pixel location in destination bitmap where source bitmap
247+ point (px,py) is placed. Defaults to None which causes it to use the horizontal
248+ midway point of the destination bitmap.
249+ :param int oy: Vertical pixel location in destination bitmap where source bitmap
250+ point (px,py) is placed. Defaults to None which causes it to use the vertical
251+ midway point of the destination bitmap.
252+ :param Tuple[int,int] dest_clip0: First corner of rectangular destination clipping
253+ region that constrains region of writing into destination bitmap
254+ :param Tuple[int,int] dest_clip1: Second corner of rectangular destination clipping
255+ region that constrains region of writing into destination bitmap
256+ :param int px: Horizontal pixel location in source bitmap that is placed into the
257+ destination bitmap at (ox,oy). Defaults to None which causes it to use the
258+ horizontal midway point in the source bitmap.
259+ :param int py: Vertical pixel location in source bitmap that is placed into the
260+ destination bitmap at (ox,oy). Defaults to None which causes it to use the
261+ vertical midway point in the source bitmap.
262+ :param Tuple[int,int] source_clip0: First corner of rectangular source clipping
263+ region that constrains region of reading from the source bitmap
264+ :param Tuple[int,int] source_clip1: Second corner of rectangular source clipping
265+ region that constrains region of reading from the source bitmap
266+ :param float angle: Angle of rotation, in radians (positive is clockwise direction).
267+ Defaults to None which gets treated as 0.0 radians or no rotation.
268+ :param float scale: Scaling factor. Defaults to None which gets treated as 1.0 or same
269+ as original source size.
270+ :param int skip_index: Bitmap palette index in the source that will not be copied,
271+ set to None to copy all pixels"""
170272 if ox is None :
171273 ox = dest_bitmap .width // 2
172274 if oy is None :
@@ -277,6 +379,35 @@ def arrayblit(
277379 y2 : Optional [int ] = None ,
278380 skip_index : Optional [int ] = None ,
279381):
382+ """Inserts pixels from ``data`` into the rectangle
383+ of width×height pixels with the upper left corner at ``(x,y)``
384+
385+ The values from ``data`` are taken modulo the number of color values
386+ available in the destination bitmap.
387+
388+ If x1 or y1 are not specified, they are taken as 0. If x2 or y2
389+ are not specified, or are given as None, they are taken as the width
390+ and height of the image.
391+
392+ The coordinates affected by the blit are
393+ ``x1 <= x < x2`` and ``y1 <= y < y2``.
394+
395+ ``data`` must contain at least as many elements as required. If it
396+ contains excess elements, they are ignored.
397+
398+ The blit takes place by rows, so the first elements of ``data`` go
399+ to the first row, the next elements to the next row, and so on.
400+
401+ :param displayio.Bitmap bitmap: A writable bitmap
402+ :param ReadableBuffer data: Buffer containing the source pixel values
403+ :param int x1: The left corner of the area to blit into (inclusive)
404+ :param int y1: The top corner of the area to blit into (inclusive)
405+ :param int x2: The right of the area to blit into (exclusive)
406+ :param int y2: The bottom corner of the area to blit into (exclusive)
407+ :param int skip_index: Bitmap palette index in the source
408+ that will not be copied, set to None to copy all pixels
409+
410+ """
280411 if x2 is None :
281412 x2 = bitmap .width
282413 if y2 is None :
@@ -300,6 +431,34 @@ def readinto(
300431 swap_bytes : bool = False ,
301432 reverse_rows : bool = False ,
302433):
434+ """Reads from a binary file into a bitmap.
435+
436+ The file must be positioned so that it consists of ``bitmap.height``
437+ rows of pixel data, where each row is the smallest multiple
438+ of ``element_size`` bytes that can hold ``bitmap.width`` pixels.
439+
440+ The bytes in an element can be optionally swapped, and the pixels
441+ in an element can be reversed. Also, therow loading direction can
442+ be reversed, which may be requires for loading certain bitmap files.
443+
444+ This function doesn't parse image headers, but is useful to
445+ speed up loading of uncompressed image formats such as PCF glyph data.
446+
447+ :param displayio.Bitmap bitmap: A writable bitmap
448+ :param typing.BinaryIO file: A file opened in binary mode
449+ :param int bits_per_pixel: Number of bits per pixel.
450+ Values 1, 2, 4, 8, 16, 24, and 32 are supported;
451+ :param int element_size: Number of bytes per element.
452+ Values of 1, 2, and 4 are supported, except that 24
453+ ``bits_per_pixel`` requires 1 byte per element.
454+ :param bool reverse_pixels_in_element: If set, the first pixel in a
455+ word is taken from the Most Significant Bits; otherwise,
456+ it is taken from the Least Significant Bits.
457+ :param bool swap_bytes_in_element: If the ``element_size`` is not 1,
458+ then reverse the byte order of each element read.
459+ :param bool reverse_rows: Reverse the direction of the row loading
460+ (required for some bitmap images).
461+ """
303462 width = bitmap .width
304463 height = bitmap .height
305464 bits_per_value = bitmap ._bits_per_value
@@ -370,6 +529,10 @@ def readinto(
370529
371530
372531class BlendMode :
532+ """
533+ Options for modes to use by alphablend() function.
534+ """
535+
373536 Normal = "bitmaptools.BlendMode.Normal"
374537 Screen = "bitmaptools.BlendMode.Screen"
375538
@@ -385,11 +548,28 @@ def alphablend(
385548 skip_source1_index : Optional [int ] = None ,
386549 skip_source2_index : Optional [int ] = None ,
387550):
388- """
389- colorspace should be one of: 'L8', 'RGB565', 'RGB565_SWAPPED', 'BGR565_SWAPPED'.
390-
391- blendmode must be one of the BlendMode constants
392- """
551+ """Alpha blend the two source bitmaps into the destination.
552+
553+ It is permitted for the destination bitmap to be one of the two
554+ source bitmaps.
555+
556+ :param bitmap dest_bitmap: Destination bitmap that will be written into
557+ :param bitmap source_bitmap_1: The first source bitmap
558+ :param bitmap source_bitmap_2: The second source bitmap
559+ :param float factor1: The proportion of bitmap 1 to mix in
560+ :param float factor2: The proportion of bitmap 2 to mix in.
561+ If specified as `None`, ``1-factor1`` is used. Usually the proportions should sum to 1.
562+ :param displayio.Colorspace colorspace: The colorspace of the bitmaps.
563+ They must all have the same colorspace. Only the following colorspaces are permitted:
564+ ``L8``, ``RGB565``, ``RGB565_SWAPPED``, ``BGR565`` and ``BGR565_SWAPPED``.
565+ :param bitmaptools.BlendMode blendmode: The blend mode to use. Default is Normal.
566+ :param int skip_source1_index: Bitmap palette or luminance index in
567+ source_bitmap_1 that will not be blended, set to None to blend all pixels
568+ :param int skip_source2_index: Bitmap palette or luminance index in
569+ source_bitmap_2 that will not be blended, set to None to blend all pixels
570+
571+ For the L8 colorspace, the bitmaps must have a bits-per-value of 8.
572+ For the RGB colorspaces, they must have a bits-per-value of 16."""
393573
394574 def clamp (val , minval , maxval ):
395575 return max (minval , min (maxval , val ))
@@ -492,6 +672,10 @@ def clamp(val, minval, maxval):
492672
493673
494674class DitherAlgorithm :
675+ """
676+ Options for algorithm to use by dither() function.
677+ """
678+
495679 Atkinson = "bitmaptools.DitherAlgorithm.Atkinson"
496680 FloydStenberg = "bitmaptools.DitherAlgorithm.FloydStenberg"
497681
@@ -522,6 +706,16 @@ class DitherAlgorithm:
522706
523707
524708def dither (dest_bitmap , source_bitmap , colorspace , algorithm = DitherAlgorithm .Atkinson ):
709+ """Convert the input image into a 2-level output image using the given dither algorithm.
710+
711+ :param bitmap dest_bitmap: Destination bitmap. It must have
712+ a value_count of 2 or 65536. The stored values are 0 and the maximum pixel value.
713+ :param bitmap source_bitmap: Source bitmap that contains the
714+ graphical region to be dithered. It must have a value_count of 65536.
715+ :param colorspace: The colorspace of the image. The supported colorspaces
716+ are ``RGB565``, ``BGR565``, ``RGB565_SWAPPED``, and ``BGR565_SWAPPED``
717+ :param algorithm: The dither algorithm to use, one of the `DitherAlgorithm` values.
718+ """
525719 SWAP_BYTES = 1 << 0
526720 SWAP_RB = 1 << 1
527721 height , width = dest_bitmap .width , dest_bitmap .height
@@ -667,6 +861,17 @@ def boundary_fill(
667861 fill_color_value : int ,
668862 replaced_color_value : Optional [int ] = None ,
669863):
864+ """Draws the color value into the destination bitmap enclosed
865+ area of pixels of the background_value color. Like "Paint Bucket"
866+ fill tool.
867+
868+ :param bitmap dest_bitmap: Destination bitmap that will be written into
869+ :param int x: x-pixel position of the first pixel to check and fill if needed
870+ :param int y: y-pixel position of the first pixel to check and fill if needed
871+ :param int fill_color_value: Bitmap palette index that will be written into the
872+ enclosed area in the destination bitmap
873+ :param int replaced_color_value: Bitmap palette index that will filled with the
874+ value color in the enclosed area in the destination bitmap"""
670875 if fill_color_value == replaced_color_value :
671876 return
672877 if replaced_color_value == - 1 :
0 commit comments