]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blob - bitmaptools/__init__.py
implement arrayblit
[hackapet/Adafruit_Blinka_Displayio.git] / bitmaptools / __init__.py
1 import math
2 from typing import Optional, Tuple
3
4 from displayio import Bitmap
5 import circuitpython_typing
6
7
8 def fill_region(dest_bitmap: Bitmap, x1: int, y1: int, x2: int, y2: int, value: int):
9     for y in range(y1, y2):
10         for x in range(x1, x2):
11             dest_bitmap[x, y] = value
12
13
14 def draw_line(dest_bitmap: Bitmap, x1: int, y1: int, x2: int, y2: int, value: int):
15     dx = abs(x2 - x1)
16     sx = 1 if x1 < x2 else -1
17     dy = -abs(y2 - y1)
18     sy = 1 if y1 < y2 else -1
19     error = dx + dy
20
21     while True:
22         dest_bitmap[x1, y1] = value
23         if x1 == x2 and y1 == y2:
24             break
25         e2 = 2 * error
26         if e2 >= dy:
27             error += dy
28             x1 += sx
29         if e2 <= dx:
30             error += dx
31             y1 += sy
32
33
34 def draw_circle(dest_bitmap: Bitmap, x: int, y: int, radius: int, value: int):
35     x = max(0, min(x, dest_bitmap.width - 1))
36     y = max(0, min(y, dest_bitmap.height - 1))
37
38     xb = 0
39     yb = radius
40     d = 3 - 2 * radius
41
42     # Bresenham's circle algorithm
43     while xb <= yb:
44         dest_bitmap[xb + x, yb + y] = value
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[yb + x, xb + 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
53         if d <= 0:
54             d = d + (4 * xb) + 6
55         else:
56             d = d + 4 * (xb - yb) + 10
57             yb = yb - 1
58         xb += 1
59
60
61 def draw_polygon(dest_bitmap: Bitmap,
62                  xs: circuitpython_typing.ReadableBuffer,
63                  ys: circuitpython_typing.ReadableBuffer,
64                  value: int, close: bool | None = True):
65     if len(xs) != len(ys):
66         raise ValueError("Length of xs and ys must be equal.")
67
68     for i in range(len(xs) - 1):
69         cur_point = (xs[i], ys[i])
70         next_point = (xs[i + 1], ys[i + 1])
71         print(f"cur: {cur_point}, next: {next_point}")
72         draw_line(dest_bitmap=dest_bitmap,
73                   x1=cur_point[0], y1=cur_point[1],
74                   x2=next_point[0], y2=next_point[1],
75                   value=value)
76
77     if close:
78         print(f"close: {(xs[0], ys[0])} - {(xs[-1], ys[-1])}")
79         draw_line(dest_bitmap=dest_bitmap,
80                   x1=xs[0], y1=ys[0],
81                   x2=xs[-1], y2=ys[-1],
82                   value=value)
83
84
85 def blit(dest_bitmap: Bitmap, source_bitmap: Bitmap,
86          x: int, y: int, *,
87          x1: int = 0, y1: int = 0,
88          x2: int | None = None, y2: int | None = None,
89          skip_source_index: int | None = None,
90          skip_dest_index: int | None = None):
91     """Inserts the source_bitmap region defined by rectangular boundaries"""
92     # pylint: disable=invalid-name
93     if x2 is None:
94         x2 = source_bitmap.width
95     if y2 is None:
96         y2 = source_bitmap.height
97
98     # Rearrange so that x1 < x2 and y1 < y2
99     if x1 > x2:
100         x1, x2 = x2, x1
101     if y1 > y2:
102         y1, y2 = y2, y1
103
104     # Ensure that x2 and y2 are within source bitmap size
105     x2 = min(x2, source_bitmap.width)
106     y2 = min(y2, source_bitmap.height)
107
108     for y_count in range(y2 - y1):
109         for x_count in range(x2 - x1):
110             x_placement = x + x_count
111             y_placement = y + y_count
112
113             if (dest_bitmap.width > x_placement >= 0) and (
114                     dest_bitmap.height > y_placement >= 0
115             ):  # ensure placement is within target bitmap
116                 # get the palette index from the source bitmap
117                 this_pixel_color = source_bitmap[
118                     y1 + (y_count * source_bitmap.width) + x1 + x_count
119                     ]
120
121                 if (skip_source_index is None) or (this_pixel_color != skip_source_index):
122                     if (skip_dest_index is None) or (
123                             dest_bitmap[y_placement * dest_bitmap.width + x_placement] != skip_dest_index):
124                         dest_bitmap[  # Direct index into a bitmap array is speedier than [x,y] tuple
125                             y_placement * dest_bitmap.width + x_placement
126                             ] = this_pixel_color
127             elif y_placement > dest_bitmap.height:
128                 break
129
130
131 def rotozoom(
132         dest_bitmap,
133         source_bitmap,
134         *,
135         ox: int,
136         oy: int,
137         dest_clip0: Tuple[int, int],
138         dest_clip1: Tuple[int, int],
139         px: int,
140         py: int,
141         source_clip0: Tuple[int, int],
142         source_clip1: Tuple[int, int],
143         angle: float,
144         scale: float,
145         skip_index: int,
146 ):
147     dest_clip0_x, dest_clip0_y = dest_clip0
148     dest_clip1_x, dest_clip1_y = dest_clip1
149     source_clip0_x, source_clip0_y = source_clip0
150     source_clip1_x, source_clip1_y = source_clip1
151
152     minx = dest_clip1_x
153     miny = dest_clip1_y
154     maxx = dest_clip0_x
155     maxy = dest_clip0_y
156
157     sin_angle = math.sin(angle)
158     cos_angle = math.cos(angle)
159
160     def update_bounds(dx, dy):
161         nonlocal minx, maxx, miny, maxy
162         if dx < minx:
163             minx = int(dx)
164         if dx > maxx:
165             maxx = int(dx)
166         if dy < miny:
167             miny = int(dy)
168         if dy > maxy:
169             maxy = int(dy)
170
171     w = source_bitmap.width
172     h = source_bitmap.height
173
174     dx = -cos_angle * px * scale + sin_angle * py * scale + ox
175     dy = -sin_angle * px * scale - cos_angle * py * scale + oy
176     update_bounds(dx, dy)
177
178     dx = cos_angle * (w - px) * scale + sin_angle * py * scale + ox
179     dy = sin_angle * (w - px) * scale - cos_angle * py * scale + oy
180     update_bounds(dx, dy)
181
182     dx = cos_angle * (w - px) * scale - sin_angle * (h - py) * scale + ox
183     dy = sin_angle * (w - px) * scale + cos_angle * (h - py) * scale + oy
184     update_bounds(dx, dy)
185
186     dx = -cos_angle * px * scale - sin_angle * (h - py) * scale + ox
187     dy = -sin_angle * px * scale + cos_angle * (h - py) * scale + oy
188     update_bounds(dx, dy)
189
190     # Clip to destination area
191     minx = max(minx, dest_clip0_x)
192     maxx = min(maxx, dest_clip1_x - 1)
193     miny = max(miny, dest_clip0_y)
194     maxy = min(maxy, dest_clip1_y - 1)
195
196     dv_col = cos_angle / scale
197     du_col = sin_angle / scale
198     du_row = dv_col
199     dv_row = -du_col
200
201     startu = px - (ox * dv_col + oy * du_col)
202     startv = py - (ox * dv_row + oy * du_row)
203
204     rowu = startu + miny * du_col
205     rowv = startv + miny * dv_col
206
207     for y in range(miny, maxy + 1):
208         u = rowu + minx * du_row
209         v = rowv + minx * dv_row
210         for x in range(minx, maxx + 1):
211             if (source_clip0_x <= u < source_clip1_x) and (source_clip0_y <= v < source_clip1_y):
212                 c = source_bitmap[int(u), int(v)]
213                 if skip_index is None or c != skip_index:
214                     dest_bitmap[x, y] = c
215             u += du_row
216             v += dv_row
217         rowu += du_col
218         rowv += dv_col
219
220
221 def arrayblit(
222         bitmap: Bitmap,
223         data: circuitpython_typing.ReadableBuffer,
224         x1: int = 0, y1: int = 0,
225         x2: int | None = None, y2: int | None = None,
226         skip_index: int | None = None):
227
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