3 from typing import Optional, Tuple, BinaryIO
5 from displayio import Bitmap
6 import circuitpython_typing
9 def fill_region(dest_bitmap: Bitmap, x1: int, y1: int, x2: int, y2: int, value: int):
10 for y in range(y1, y2):
11 for x in range(x1, x2):
12 dest_bitmap[x, y] = value
15 def draw_line(dest_bitmap: Bitmap, x1: int, y1: int, x2: int, y2: int, value: int):
17 sx = 1 if x1 < x2 else -1
19 sy = 1 if y1 < y2 else -1
23 dest_bitmap[x1, y1] = value
24 if x1 == x2 and y1 == y2:
35 def draw_circle(dest_bitmap: Bitmap, x: int, y: int, radius: int, value: int):
36 x = max(0, min(x, dest_bitmap.width - 1))
37 y = max(0, min(y, dest_bitmap.height - 1))
43 # Bresenham's circle algorithm
45 dest_bitmap[xb + x, yb + y] = value
46 dest_bitmap[-xb + x, -yb + y] = value
47 dest_bitmap[-xb + x, yb + y] = value
48 dest_bitmap[xb + x, -yb + y] = value
49 dest_bitmap[yb + x, xb + y] = value
50 dest_bitmap[-yb + x, xb + y] = value
51 dest_bitmap[-yb + x, -xb + y] = value
52 dest_bitmap[yb + x, -xb + y] = value
57 d = d + 4 * (xb - yb) + 10
62 def draw_polygon(dest_bitmap: Bitmap,
63 xs: circuitpython_typing.ReadableBuffer,
64 ys: circuitpython_typing.ReadableBuffer,
65 value: int, close: bool | None = True):
66 if len(xs) != len(ys):
67 raise ValueError("Length of xs and ys must be equal.")
69 for i in range(len(xs) - 1):
70 cur_point = (xs[i], ys[i])
71 next_point = (xs[i + 1], ys[i + 1])
72 print(f"cur: {cur_point}, next: {next_point}")
73 draw_line(dest_bitmap=dest_bitmap,
74 x1=cur_point[0], y1=cur_point[1],
75 x2=next_point[0], y2=next_point[1],
79 print(f"close: {(xs[0], ys[0])} - {(xs[-1], ys[-1])}")
80 draw_line(dest_bitmap=dest_bitmap,
86 def blit(dest_bitmap: Bitmap, source_bitmap: Bitmap,
88 x1: int = 0, y1: int = 0,
89 x2: int | None = None, y2: int | None = None,
90 skip_source_index: int | None = None,
91 skip_dest_index: int | None = None):
92 """Inserts the source_bitmap region defined by rectangular boundaries"""
93 # pylint: disable=invalid-name
95 x2 = source_bitmap.width
97 y2 = source_bitmap.height
99 # Rearrange so that x1 < x2 and y1 < y2
105 # Ensure that x2 and y2 are within source bitmap size
106 x2 = min(x2, source_bitmap.width)
107 y2 = min(y2, source_bitmap.height)
109 for y_count in range(y2 - y1):
110 for x_count in range(x2 - x1):
111 x_placement = x + x_count
112 y_placement = y + y_count
114 if (dest_bitmap.width > x_placement >= 0) and (
115 dest_bitmap.height > y_placement >= 0
116 ): # ensure placement is within target bitmap
117 # get the palette index from the source bitmap
118 this_pixel_color = source_bitmap[
119 y1 + (y_count * source_bitmap.width) + x1 + x_count
122 if (skip_source_index is None) or (this_pixel_color != skip_source_index):
123 if (skip_dest_index is None) or (
124 dest_bitmap[y_placement * dest_bitmap.width + x_placement] != skip_dest_index):
125 dest_bitmap[ # Direct index into a bitmap array is speedier than [x,y] tuple
126 y_placement * dest_bitmap.width + x_placement
128 elif y_placement > dest_bitmap.height:
138 dest_clip0: Tuple[int, int],
139 dest_clip1: Tuple[int, int],
142 source_clip0: Tuple[int, int],
143 source_clip1: Tuple[int, int],
148 dest_clip0_x, dest_clip0_y = dest_clip0
149 dest_clip1_x, dest_clip1_y = dest_clip1
150 source_clip0_x, source_clip0_y = source_clip0
151 source_clip1_x, source_clip1_y = source_clip1
158 sin_angle = math.sin(angle)
159 cos_angle = math.cos(angle)
161 def update_bounds(dx, dy):
162 nonlocal minx, maxx, miny, maxy
172 w = source_bitmap.width
173 h = source_bitmap.height
175 dx = -cos_angle * px * scale + sin_angle * py * scale + ox
176 dy = -sin_angle * px * scale - cos_angle * py * scale + oy
177 update_bounds(dx, dy)
179 dx = cos_angle * (w - px) * scale + sin_angle * py * scale + ox
180 dy = sin_angle * (w - px) * scale - cos_angle * py * scale + oy
181 update_bounds(dx, dy)
183 dx = cos_angle * (w - px) * scale - sin_angle * (h - py) * scale + ox
184 dy = sin_angle * (w - px) * scale + cos_angle * (h - py) * scale + oy
185 update_bounds(dx, dy)
187 dx = -cos_angle * px * scale - sin_angle * (h - py) * scale + ox
188 dy = -sin_angle * px * scale + cos_angle * (h - py) * scale + oy
189 update_bounds(dx, dy)
191 # Clip to destination area
192 minx = max(minx, dest_clip0_x)
193 maxx = min(maxx, dest_clip1_x - 1)
194 miny = max(miny, dest_clip0_y)
195 maxy = min(maxy, dest_clip1_y - 1)
197 dv_col = cos_angle / scale
198 du_col = sin_angle / scale
202 startu = px - (ox * dv_col + oy * du_col)
203 startv = py - (ox * dv_row + oy * du_row)
205 rowu = startu + miny * du_col
206 rowv = startv + miny * dv_col
208 for y in range(miny, maxy + 1):
209 u = rowu + minx * du_row
210 v = rowv + minx * dv_row
211 for x in range(minx, maxx + 1):
212 if (source_clip0_x <= u < source_clip1_x) and (source_clip0_y <= v < source_clip1_y):
213 c = source_bitmap[int(u), int(v)]
214 if skip_index is None or c != skip_index:
215 dest_bitmap[x, y] = c
224 data: circuitpython_typing.ReadableBuffer,
225 x1: int = 0, y1: int = 0,
226 x2: int | None = None, y2: int | None = None,
227 skip_index: int | None = None):
233 _value_count = 2 ** bitmap._bits_per_value
234 for y in range(y1, y2):
235 for x in range(x1, x2):
236 i = y * (x2 - x1) + x
237 value = int(data[i] % _value_count)
238 if skip_index is None or value != skip_index:
242 def readinto(bitmap: Bitmap,
245 element_size: int = 1,
246 reverse_pixels_in_element: bool = False,
247 swap_bytes: bool = False,
248 reverse_rows: bool = False):
251 height = bitmap.height
252 bits_per_value = bitmap._bits_per_value
253 mask = (1 << bits_per_value) - 1
255 elements_per_row = (width * bits_per_pixel + element_size * 8 - 1) // (element_size * 8)
256 rowsize = element_size * elements_per_row
258 for y in range(height):
259 row_bytes = file.read(rowsize)
260 if len(row_bytes) != rowsize:
263 # Convert the raw bytes into the appropriate type array for processing
264 rowdata = bytearray(row_bytes)
267 if element_size == 2:
270 struct.pack('<H', struct.unpack('>H', rowdata[i:i + 2])[0])
271 for i in range(0, len(rowdata), 2)
274 elif element_size == 4:
277 struct.pack('<I', struct.unpack('>I', rowdata[i:i + 4])[0])
278 for i in range(0, len(rowdata), 4)
282 y_draw = height - 1 - y if reverse_rows else y
284 for x in range(width):
286 if bits_per_pixel == 1:
288 bit_offset = 7 - (x % 8) if reverse_pixels_in_element else x % 8
289 value = (rowdata[byte_offset] >> bit_offset) & 0x1
290 elif bits_per_pixel == 2:
292 bit_index = 3 - (x % 4) if reverse_pixels_in_element else x % 4
293 bit_offset = 2 * bit_index
294 value = (rowdata[byte_offset] >> bit_offset) & 0x3
295 elif bits_per_pixel == 4:
297 bit_index = 1 - (x % 2) if reverse_pixels_in_element else x % 2
298 bit_offset = 4 * bit_index
299 value = (rowdata[byte_offset] >> bit_offset) & 0xF
300 elif bits_per_pixel == 8:
302 elif bits_per_pixel == 16:
303 value = struct.unpack_from('<H', rowdata, x * 2)[0]
304 elif bits_per_pixel == 24:
306 value = (rowdata[offset] << 16) | (rowdata[offset + 1] << 8) | rowdata[offset + 2]
307 elif bits_per_pixel == 32:
308 value = struct.unpack_from('<I', rowdata, x * 4)[0]
310 bitmap[x, y_draw] = value & mask