]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blob - bitmaptools/__init__.py
implement readinto
[hackapet/Adafruit_Blinka_Displayio.git] / bitmaptools / __init__.py
1 import math
2 import struct
3 from typing import Optional, Tuple, BinaryIO
4
5 from displayio import Bitmap
6 import circuitpython_typing
7
8
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
13
14
15 def draw_line(dest_bitmap: Bitmap, x1: int, y1: int, x2: int, y2: int, value: int):
16     dx = abs(x2 - x1)
17     sx = 1 if x1 < x2 else -1
18     dy = -abs(y2 - y1)
19     sy = 1 if y1 < y2 else -1
20     error = dx + dy
21
22     while True:
23         dest_bitmap[x1, y1] = value
24         if x1 == x2 and y1 == y2:
25             break
26         e2 = 2 * error
27         if e2 >= dy:
28             error += dy
29             x1 += sx
30         if e2 <= dx:
31             error += dx
32             y1 += sy
33
34
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))
38
39     xb = 0
40     yb = radius
41     d = 3 - 2 * radius
42
43     # Bresenham's circle algorithm
44     while xb <= yb:
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
53
54         if d <= 0:
55             d = d + (4 * xb) + 6
56         else:
57             d = d + 4 * (xb - yb) + 10
58             yb = yb - 1
59         xb += 1
60
61
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.")
68
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],
76                   value=value)
77
78     if close:
79         print(f"close: {(xs[0], ys[0])} - {(xs[-1], ys[-1])}")
80         draw_line(dest_bitmap=dest_bitmap,
81                   x1=xs[0], y1=ys[0],
82                   x2=xs[-1], y2=ys[-1],
83                   value=value)
84
85
86 def blit(dest_bitmap: Bitmap, source_bitmap: Bitmap,
87          x: int, y: int, *,
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
94     if x2 is None:
95         x2 = source_bitmap.width
96     if y2 is None:
97         y2 = source_bitmap.height
98
99     # Rearrange so that x1 < x2 and y1 < y2
100     if x1 > x2:
101         x1, x2 = x2, x1
102     if y1 > y2:
103         y1, y2 = y2, y1
104
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)
108
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
113
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
120                     ]
121
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
127                             ] = this_pixel_color
128             elif y_placement > dest_bitmap.height:
129                 break
130
131
132 def rotozoom(
133         dest_bitmap,
134         source_bitmap,
135         *,
136         ox: int,
137         oy: int,
138         dest_clip0: Tuple[int, int],
139         dest_clip1: Tuple[int, int],
140         px: int,
141         py: int,
142         source_clip0: Tuple[int, int],
143         source_clip1: Tuple[int, int],
144         angle: float,
145         scale: float,
146         skip_index: int,
147 ):
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
152
153     minx = dest_clip1_x
154     miny = dest_clip1_y
155     maxx = dest_clip0_x
156     maxy = dest_clip0_y
157
158     sin_angle = math.sin(angle)
159     cos_angle = math.cos(angle)
160
161     def update_bounds(dx, dy):
162         nonlocal minx, maxx, miny, maxy
163         if dx < minx:
164             minx = int(dx)
165         if dx > maxx:
166             maxx = int(dx)
167         if dy < miny:
168             miny = int(dy)
169         if dy > maxy:
170             maxy = int(dy)
171
172     w = source_bitmap.width
173     h = source_bitmap.height
174
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)
178
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)
182
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)
186
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)
190
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)
196
197     dv_col = cos_angle / scale
198     du_col = sin_angle / scale
199     du_row = dv_col
200     dv_row = -du_col
201
202     startu = px - (ox * dv_col + oy * du_col)
203     startv = py - (ox * dv_row + oy * du_row)
204
205     rowu = startu + miny * du_col
206     rowv = startv + miny * dv_col
207
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
216             u += du_row
217             v += dv_row
218         rowu += du_col
219         rowv += dv_col
220
221
222 def arrayblit(
223         bitmap: Bitmap,
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):
228     if x2 is None:
229         x2 = bitmap.width
230     if y2 is None:
231         y2 = bitmap.height
232
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:
239                 bitmap[x, y] = value
240
241
242 def readinto(bitmap: Bitmap,
243              file: BinaryIO,
244              bits_per_pixel: int,
245              element_size: int = 1,
246              reverse_pixels_in_element: bool = False,
247              swap_bytes: bool = False,
248              reverse_rows: bool = False):
249
250     width = bitmap.width
251     height = bitmap.height
252     bits_per_value = bitmap._bits_per_value
253     mask = (1 << bits_per_value) - 1
254
255     elements_per_row = (width * bits_per_pixel + element_size * 8 - 1) // (element_size * 8)
256     rowsize = element_size * elements_per_row
257
258     for y in range(height):
259         row_bytes = file.read(rowsize)
260         if len(row_bytes) != rowsize:
261             raise EOFError()
262
263         # Convert the raw bytes into the appropriate type array for processing
264         rowdata = bytearray(row_bytes)
265
266         if swap_bytes:
267             if element_size == 2:
268                 rowdata = bytearray(
269                     b''.join(
270                         struct.pack('<H', struct.unpack('>H', rowdata[i:i + 2])[0])
271                         for i in range(0, len(rowdata), 2)
272                     )
273                 )
274             elif element_size == 4:
275                 rowdata = bytearray(
276                     b''.join(
277                         struct.pack('<I', struct.unpack('>I', rowdata[i:i + 4])[0])
278                         for i in range(0, len(rowdata), 4)
279                     )
280                 )
281
282         y_draw = height - 1 - y if reverse_rows else y
283
284         for x in range(width):
285             value = 0
286             if bits_per_pixel == 1:
287                 byte_offset = x // 8
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:
291                 byte_offset = x // 4
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:
296                 byte_offset = x // 2
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:
301                 value = rowdata[x]
302             elif bits_per_pixel == 16:
303                 value = struct.unpack_from('<H', rowdata, x * 2)[0]
304             elif bits_per_pixel == 24:
305                 offset = x * 3
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]
309
310             bitmap[x, y_draw] = value & mask