GCC Code Coverage Report


Directory: ../src/
File: /home/joels/Current/lispbm/src/extensions/display_extensions.c
Date: 2024-11-05 17:11:09
Exec Total Coverage
Lines: 0 1616 0.0%
Functions: 0 68 0.0%
Branches: 0 1255 0.0%

Line Branch Exec Source
1 /*
2 Copyright 2023, 2024 Benjamin Vedder benjamin@vedder.se
3 Copyright 2023, 2024 Joel Svensson svenssonjoel@yahoo.se
4 Copyright 2023 Rasmus Söderhielm rasmus.soderhielm@gmail.com
5
6 This file is part of LispBM. (Originally a part of the vesc_express FW)
7
8 LispBM is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 LispBM is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include "tjpgd.h"
23
24 #include <math.h>
25
26 #include <extensions/display_extensions.h>
27 #include <lbm_utils.h>
28 #include <lbm_custom_type.h>
29 #include <lbm_defrag_mem.h>
30
31 #define MAX_WIDTH 32000
32 #define MAX_HEIGHT 32000
33
34 static const uint8_t cos_tab_256[] = {
35 255, 255, 255, 255, 254, 254, 254, 253, 253, 252, 251,
36 250, 250, 249, 248, 246, 245, 244, 243, 241, 240, 238, 237, 235, 234,
37 232, 230, 228, 226, 224, 222, 220, 218, 215, 213, 211, 208, 206, 203,
38 201, 198, 196, 193, 190, 188, 185, 182, 179, 176, 173, 170, 167, 165,
39 162, 158, 155, 152, 149, 146, 143, 140, 137, 134, 131, 127, 124, 121,
40 118, 115, 112, 109, 106, 103, 100, 97, 93, 90, 88, 85, 82, 79, 76, 73,
41 70, 67, 65, 62, 59, 57, 54, 52, 49, 47, 44, 42, 40, 37, 35, 33, 31, 29,
42 27, 25, 23, 21, 20, 18, 17, 15, 14, 12, 11, 10, 9, 7, 6, 5, 5, 4, 3, 2,
43 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 3, 4, 5, 5, 6, 7, 9, 10,
44 11, 12, 14, 15, 17, 18, 20, 21, 23, 25, 27, 29, 31, 33, 35, 37, 40, 42,
45 44, 47, 49, 52, 54, 57, 59, 62, 65, 67, 70, 73, 76, 79, 82, 85, 88, 90,
46 93, 97, 100, 103, 106, 109, 112, 115, 118, 121, 124, 128, 131, 134, 137,
47 140, 143, 146, 149, 152, 155, 158, 162, 165, 167, 170, 173, 176, 179,
48 182, 185, 188, 190, 193, 196, 198, 201, 203, 206, 208, 211, 213, 215,
49 218, 220, 222, 224, 226, 228, 230, 232, 234, 235, 237, 238, 240, 241,
50 243, 244, 245, 246, 248, 249, 250, 250, 251, 252, 253, 253, 254, 254,
51 254, 255, 255, 255
52 };
53
54 uint32_t lbm_display_rgb888_from_color(color_t color, int x, int y) {
55 switch (color.type) {
56 case COLOR_REGULAR:
57 return (uint32_t)color.color1;
58
59 case COLOR_GRADIENT_X:
60 case COLOR_GRADIENT_Y: {
61 uint32_t res;
62 uint32_t r1 = (uint32_t)color.color1 >> 16;
63 uint32_t g1 = (uint32_t)color.color1 >> 8 & 0xFF;
64 uint32_t b1 = (uint32_t)color.color1 & 0xff;
65
66 uint32_t r2 = (uint32_t)color.color2 >> 16;
67 uint32_t g2 = (uint32_t)color.color2 >> 8 & 0xFF;
68 uint32_t b2 = (uint32_t)color.color2 & 0xff;
69
70 int used_len = color.mirrored ? 256 : 128;
71
72 int pos = color.type == COLOR_GRADIENT_X ? x : y;
73 // int tab_pos = ((pos * 256) / color.param1 + color.param2) % 256;
74 int tab_pos = (((pos - color.param2) * 256) / color.param1 / 2) % used_len;
75 if (tab_pos < 0) {
76 tab_pos += used_len;
77 }
78
79 uint32_t tab_val = (uint32_t)cos_tab_256[tab_pos];
80
81 uint32_t r = (r1 * tab_val + r2 * (255 - tab_val)) / 255;
82 uint32_t g = (g1 * tab_val + g2 * (255 - tab_val)) / 255;
83 uint32_t b = (b1 * tab_val + b2 * (255 - tab_val)) / 255;
84
85 res = r << 16 | g << 8 | b;
86 return res;
87 }
88
89 default:
90 return 0;
91 }
92 }
93
94 static const char *color_desc = "Color";
95
96 static lbm_uint symbol_indexed2 = 0;
97 static lbm_uint symbol_indexed4 = 0;
98 static lbm_uint symbol_indexed16 = 0;
99 static lbm_uint symbol_rgb332 = 0;
100 static lbm_uint symbol_rgb565 = 0;
101 static lbm_uint symbol_rgb888 = 0;
102
103 static lbm_uint symbol_thickness = 0;
104 static lbm_uint symbol_filled = 0;
105 static lbm_uint symbol_rounded = 0;
106 static lbm_uint symbol_dotted = 0;
107 static lbm_uint symbol_scale = 0;
108 static lbm_uint symbol_rotate = 0;
109 static lbm_uint symbol_resolution = 0;
110
111 static lbm_uint symbol_regular = 0;
112 static lbm_uint symbol_gradient_x = 0;
113 static lbm_uint symbol_gradient_y = 0;
114 static lbm_uint symbol_gradient_x_pre = 0;
115 static lbm_uint symbol_gradient_y_pre = 0;
116 static lbm_uint symbol_repeat = 0;
117 static lbm_uint symbol_mirrored = 0;
118
119 static lbm_uint symbol_color_0 = 0;
120 static lbm_uint symbol_color_1 = 0;
121 static lbm_uint symbol_width = 0;
122 static lbm_uint symbol_offset = 0;
123 static lbm_uint symbol_repeat_type = 0;
124
125 static lbm_uint symbol_down = 0;
126 static lbm_uint symbol_up = 0;
127
128 static color_format_t sym_to_color_format(lbm_value v) {
129 lbm_uint s = lbm_dec_sym(v);
130 if (s == symbol_indexed2) return indexed2;
131 if (s == symbol_indexed4) return indexed4;
132 if (s == symbol_indexed16) return indexed16;
133 if (s == symbol_rgb332) return rgb332;
134 if (s == symbol_rgb565) return rgb565;
135 if (s == symbol_rgb888) return rgb888;
136 return format_not_supported;
137 }
138
139 static uint32_t image_dims_to_size_bytes(color_format_t fmt, uint16_t width, uint16_t height) {
140 uint32_t num_pix = (uint32_t)width * (uint32_t)height;
141 switch(fmt) {
142 case indexed2:
143 if (num_pix % 8 != 0) return (num_pix / 8) + 1;
144 else return (num_pix / 8);
145 break;
146 case indexed4:
147 if (num_pix % 4 != 0) return (num_pix / 4) + 1;
148 else return (num_pix / 4);
149 break;
150 case indexed16: // Two pixels per byte
151 if (num_pix % 2 != 0) return (num_pix / 2) + 1;
152 else return (num_pix / 2);
153 case rgb332:
154 return num_pix;
155 break;
156 case rgb565:
157 return num_pix * 2;
158 break;
159 case rgb888:
160 return num_pix * 3;
161 default:
162 return 0;
163 }
164 }
165
166 static lbm_value image_buffer_lift(uint8_t *buf, color_format_t fmt, uint16_t width, uint16_t height) {
167 lbm_value res = ENC_SYM_MERROR;
168 lbm_uint size = image_dims_to_size_bytes(fmt, width, height);
169 if ( lbm_lift_array(&res, (char*)buf, IMAGE_BUFFER_HEADER_SIZE + size)) {
170 buf[0] = (uint8_t)(width >> 8);
171 buf[1] = (uint8_t)width;
172 buf[2] = (uint8_t)(height >> 8);
173 buf[3] = (uint8_t)height;
174 buf[4] = color_format_to_byte(fmt);
175 }
176 return res;
177 }
178
179 static bool color_destructor(lbm_uint value) {
180 color_t *color = (color_t*)value;
181 if (color->precalc) {
182 lbm_free((void*)color->precalc);
183 }
184 lbm_free((void*)color);
185 return true;
186 }
187
188 static lbm_value color_allocate(COLOR_TYPE type, int32_t color1, int32_t color2, uint16_t param1, uint16_t param2, bool mirrored) {
189 color_t *color = lbm_malloc(sizeof(color_t));
190 if (!color) {
191 return ENC_SYM_MERROR;
192 }
193
194 uint32_t *pre = 0;
195 if (type == COLOR_PRE_X || type == COLOR_PRE_Y) {
196 pre = lbm_malloc(COLOR_PRECALC_LEN * sizeof(uint32_t));
197 if (!pre) {
198 lbm_free(color);
199 return ENC_SYM_MERROR;
200 }
201 }
202
203 lbm_value res;
204 if (!lbm_custom_type_create((lbm_uint)color,
205 color_destructor, color_desc, &res)) {
206 lbm_free(color);
207 if (pre) {
208 lbm_free(pre);
209 }
210 return ENC_SYM_MERROR;
211 }
212
213 color->type = type;
214 color->color1 = color1;
215 color->color2 = color2;
216 color->param1 = param1;
217 color->param2 = param2;
218 color->mirrored = mirrored;
219 color->precalc = pre;
220
221 if (pre) {
222 COLOR_TYPE type_old = color->type;
223 if (type == COLOR_PRE_X) {
224 color->type = COLOR_GRADIENT_X;
225 } else if (type == COLOR_PRE_Y) {
226 color->type = COLOR_GRADIENT_Y;
227 }
228
229 if (color->param1 > COLOR_PRECALC_LEN) {
230 color->param1 = COLOR_PRECALC_LEN;
231 }
232
233 for (int i = 0;i < color->param1;i++) {
234 pre[i] = lbm_display_rgb888_from_color(*color, i + color->param2, i + color->param2);
235 }
236
237 color->type = type_old;
238 }
239
240 return res;
241 }
242
243 static lbm_value image_buffer_allocate(color_format_t fmt, uint16_t width, uint16_t height) {
244 uint32_t size_bytes = image_dims_to_size_bytes(fmt, width, height);
245
246 uint8_t *buf = lbm_malloc(IMAGE_BUFFER_HEADER_SIZE + size_bytes);
247 if (!buf) {
248 return ENC_SYM_MERROR;
249 }
250 memset(buf, 0, size_bytes + IMAGE_BUFFER_HEADER_SIZE);
251 lbm_value res = image_buffer_lift(buf, fmt, width, height);
252 if (lbm_is_symbol(res)) { /* something is wrong, free */
253 lbm_free(buf);
254 }
255 return res;
256 }
257
258 static lbm_value image_buffer_allocate_dm(lbm_uint *dm, color_format_t fmt, uint16_t width, uint16_t height) {
259 uint32_t size_bytes = image_dims_to_size_bytes(fmt, width, height);
260
261 lbm_value res = lbm_defrag_mem_alloc(dm, IMAGE_BUFFER_HEADER_SIZE + size_bytes);
262 if (lbm_is_symbol(res)) {
263 return res;
264 }
265 lbm_array_header_t *arr = (lbm_array_header_t*)lbm_car(res);
266 uint8_t *buf = (uint8_t*)arr->data;
267 buf[0] = (uint8_t)(width >> 8);
268 buf[1] = (uint8_t)width;
269 buf[2] = (uint8_t)(height >> 8);
270 buf[3] = (uint8_t)height;
271 buf[4] = color_format_to_byte(fmt);
272 return res;
273 }
274
275 // Exported interface
276 bool display_is_color(lbm_value v) {
277 return (lbm_is_custom(v) && ((lbm_uint)lbm_get_custom_descriptor(v) == (lbm_uint)color_desc));
278 }
279
280 // Register symbols
281
282 static bool register_symbols(void) {
283 bool res = true;
284 res = res && lbm_add_symbol_const("indexed2", &symbol_indexed2);
285 res = res && lbm_add_symbol_const("indexed4", &symbol_indexed4);
286 res = res && lbm_add_symbol_const("indexed16", &symbol_indexed16);
287 res = res && lbm_add_symbol_const("rgb332", &symbol_rgb332);
288 res = res && lbm_add_symbol_const("rgb565", &symbol_rgb565);
289 res = res && lbm_add_symbol_const("rgb888", &symbol_rgb888);
290
291 res = res && lbm_add_symbol_const("thickness", &symbol_thickness);
292 res = res && lbm_add_symbol_const("filled", &symbol_filled);
293 res = res && lbm_add_symbol_const("rounded", &symbol_rounded);
294 res = res && lbm_add_symbol_const("dotted", &symbol_dotted);
295 res = res && lbm_add_symbol_const("scale", &symbol_scale);
296 res = res && lbm_add_symbol_const("rotate", &symbol_rotate);
297 res = res && lbm_add_symbol_const("resolution", &symbol_resolution);
298
299 res = res && lbm_add_symbol_const("regular", &symbol_regular);
300 res = res && lbm_add_symbol_const("gradient_x", &symbol_gradient_x);
301 res = res && lbm_add_symbol_const("gradient_y", &symbol_gradient_y);
302 res = res && lbm_add_symbol_const("gradient_x_pre", &symbol_gradient_x_pre);
303 res = res && lbm_add_symbol_const("gradient_y_pre", &symbol_gradient_y_pre);
304 res = res && lbm_add_symbol_const("mirrored", &symbol_mirrored);
305 res = res && lbm_add_symbol_const("repeat", &symbol_repeat);
306
307 res = res && lbm_add_symbol_const("color-0", &symbol_color_0);
308 res = res && lbm_add_symbol_const("color-1", &symbol_color_1);
309 res = res && lbm_add_symbol_const("width", &symbol_width);
310 res = res && lbm_add_symbol_const("offset", &symbol_offset);
311 res = res && lbm_add_symbol_const("repeat-type", &symbol_repeat_type);
312
313 res = res && lbm_add_symbol_const("down", &symbol_down);
314 res = res && lbm_add_symbol_const("up", &symbol_up);
315
316 return res;
317 }
318
319 // Internal functions
320
321 static int sign(int v) {
322 if (v > 0) {
323 return 1;
324 } else if (v < 0) {
325 return -1;
326 } else {
327 return 0;
328 }
329 }
330
331 // Geometry utility functions
332
333 // Checks if a point is past a line formed by the given end and start points.
334 // The returned value is 1 if it is past, -1 if it's on the other side of the
335 // line, or 0 if it's exactly on the line.
336 // Don't ask me what is considered the "positive" side of the line ;)
337 //
338 // It would probably be more logical if the sign of the result was flipped...
339 static int point_past_line(int x, int y, int line_start_x, int line_start_y, int line_end_x, int line_end_y) {
340 // source: https://stackoverflow.com/a/11908158/15507414
341
342 // this is not really a cross product, but whatever...
343 int cross_prod = (x - line_start_x) * (line_end_y - line_start_y)
344 - (y - line_start_y) * (line_end_x - line_start_x);
345
346 if (cross_prod > 0) {
347 return 1;
348 } else if (cross_prod < 0) {
349 return -1;
350 } else {
351 return 0;
352 }
353 }
354
355 static bool points_same_quadrant(int x0, int y0, int x1, int y1) {
356 return (sign(x0) == sign(x1) || sign(x0) == 0 || sign(x1) == 0)
357 && (sign(y0) == sign(y1) || sign(y0) == 0 || sign(y1) == 0);
358 }
359
360 static inline void norm_angle(float *angle) {
361 while (*angle < -M_PI) { *angle += 2.0f * (float)M_PI; }
362 while (*angle >= M_PI) { *angle -= 2.0f * (float)M_PI; }
363 }
364
365 static inline void norm_angle_0_2pi(float *angle) {
366 while (*angle < 0) { *angle += 2.0f * (float)M_PI; }
367 while (*angle >= 2.0 * M_PI) { *angle -= 2.0f * (float)M_PI; }
368 }
369
370 static uint8_t rgb888to332(uint32_t rgb) {
371 uint8_t r = (uint8_t)(rgb >> (16 + 5));
372 uint8_t g = (uint8_t)(rgb >> (8 + 5));
373 uint8_t b = (uint8_t)(rgb >> 6);
374 r = (uint8_t)(r << 5);
375 g = (g & 0x7) << 2; ;
376 b = (b & 0x3);
377 uint8_t res_rgb332 = r | g | b;
378 return res_rgb332;
379 }
380
381 static uint16_t rgb888to565(uint32_t rgb) {
382 uint16_t r = (uint16_t)(rgb >> (16 + 3));
383 uint16_t g = (uint16_t)(rgb >> (8 + 2));
384 uint16_t b = (uint16_t)(rgb >> 3);
385 r = (uint8_t)(r << 11);
386 g = (g & 0x3F) << 5;
387 b = (b & 0x1F);
388 uint16_t res_rgb565 = r | g | b;
389 return res_rgb565;
390 }
391
392 static uint32_t rgb332to888(uint8_t rgb) {
393 uint32_t r = (uint32_t)((rgb>>5) & 0x7);
394 uint32_t g = (uint32_t)((rgb>>2) & 0x7);
395 uint32_t b = (uint32_t)(rgb & 0x3);
396 uint32_t res_rgb888 = r << (16 + 5) | g << (8 + 5) | b << 6;
397 return res_rgb888;
398 }
399
400 static uint32_t rgb565to888(uint16_t rgb) {
401 uint32_t r = (uint32_t)(rgb >> 11);
402 uint32_t g = (uint32_t)((rgb >> 5) & 0x3F);
403 uint32_t b = (uint32_t)(rgb & 0x1F);
404 uint32_t res_rgb888 = r << (16 + 3) | g << (8 + 2) | b << 3;
405 return res_rgb888;
406 }
407
408 void image_buffer_clear(image_buffer_t *img, uint32_t cc) {
409 color_format_t fmt = img->fmt;
410 uint32_t w = img->width;
411 uint32_t h = img->height;
412 uint32_t img_size = w * h;
413 uint8_t *data = img->data;
414 switch (fmt) {
415 case indexed2: {
416 uint32_t bytes = (img_size / 8) + (img_size % 8 ? 1 : 0);
417 uint8_t c8 = (uint8_t)((cc & 1) ? 0xFF : 0x0);
418 memset(data, c8, bytes);
419 }
420 break;
421 case indexed4: {
422 static const uint8_t index4_table[4] = {0x00, 0x55, 0xAA, 0xFF};
423 uint32_t bytes = (img_size / 4) + (img_size % 4 ? 1 : 0);
424 uint8_t ix = (uint8_t)(cc & 0x3);
425 memset(data, index4_table[ix], bytes);
426 }
427 break;
428 case indexed16: {
429 uint32_t bytes = (img_size / 2) + (img_size % 2 ? 1 : 0);
430 uint8_t ix = (uint8_t)(cc & 0xF);
431 uint8_t color = (uint8_t)(ix | ix << 4); // create a color based on duplication of index
432 memset(data, color, bytes);
433 }
434 break;
435 case rgb332: {
436 memset(data, rgb888to332(cc), img_size);
437 }
438 break;
439 case rgb565: {
440 uint16_t c = rgb888to565(cc);
441 uint8_t *dp = (uint8_t*)data;
442 for (unsigned int i = 0; i < img_size/2; i +=2) {
443 dp[i] = (uint8_t)(c >> 8);
444 dp[i+1] = (uint8_t)c;
445 }
446 }
447 break;
448 case rgb888: {
449 uint8_t *dp = (uint8_t*)data;
450 for (unsigned int i = 0; i < img_size * 3; i+= 3) {
451 dp[i] = (uint8_t)(cc >> 16);
452 dp[i+1] = (uint8_t)(cc >> 8);
453 dp[i+2] = (uint8_t)cc;
454 }
455 }
456 break;
457 default:
458 break;
459 }
460 }
461
462 static const uint8_t indexed4_mask[4] = {0x03, 0x0C, 0x30, 0xC0};
463 static const uint8_t indexed4_shift[4] = {0, 2, 4, 6};
464 static const uint8_t indexed16_mask[4] = {0x0F, 0xF0};
465 static const uint8_t indexed16_shift[4] = {0, 4};
466
467
468 static void putpixel(image_buffer_t* img, int x_i, int y_i, uint32_t c) {
469 color_format_t fmt = img->fmt;
470 uint16_t w = img->width;
471 uint16_t h = img->height;
472 uint16_t x = (uint16_t)x_i; // negative numbers become really large.
473 uint16_t y = (uint16_t)y_i;
474
475 if (x < w && y < h) {
476 uint8_t *data = img->data;
477 switch(fmt) {
478 case indexed2: {
479 uint32_t pos = (uint32_t)y * (uint32_t)w + (uint32_t)x;
480 uint32_t byte = pos >> 3;
481 uint32_t bit = 7 - (pos & 0x7);
482 if (c) {
483 data[byte] |= (uint8_t)(1 << bit);
484 } else {
485 data[byte] &= (uint8_t)~(1 << bit);
486 }
487 break;
488 }
489 case indexed4: {
490 uint32_t pos = (uint32_t)y*w + x;
491 uint32_t byte = pos >> 2;
492 uint32_t ix = 3 - (pos & 0x3);
493 data[byte] = (uint8_t)((uint8_t)(data[byte] & ~indexed4_mask[ix]) | (uint8_t)(c << indexed4_shift[ix]));
494 break;
495 }
496 case indexed16: {
497 uint32_t pos = (uint32_t)y*w + x;
498 uint32_t byte = pos >> 1;
499 uint32_t ix = 1 - (pos & 0x1);
500 data[byte] = (uint8_t)((uint8_t)(data[byte] & ~indexed16_mask[ix]) | (uint8_t)(c << indexed16_shift[ix]));
501 break;
502 }
503 case rgb332: {
504 int pos = y*w + x;
505 data[pos] = rgb888to332(c);
506 break;
507 }
508 case rgb565: {
509 int pos = y*(w<<1) + (x<<1) ;
510 uint16_t color = rgb888to565(c);
511 data[pos] = (uint8_t)(color >> 8);
512 data[pos+1] = (uint8_t)color;
513 break;
514 }
515 case rgb888: {
516 int pos = y*(w*3) + (x*3);
517 data[pos] = (uint8_t)(c>>16);
518 data[pos+1] = (uint8_t)(c>>8);
519 data[pos+2] = (uint8_t)c;
520 break;
521 }
522 default:
523 break;
524 }
525 }
526 }
527
528 static uint32_t getpixel(image_buffer_t* img, int x_i, int y_i) {
529 color_format_t fmt = img->fmt;
530 uint16_t w = img->width;
531 uint16_t h = img->height;
532 uint16_t x = (uint16_t)x_i;
533 uint16_t y = (uint16_t)y_i;
534
535 if (x < w && y < h) {
536 uint8_t *data = img->data;
537 switch(fmt) {
538 case indexed2: {
539 uint32_t pos = (uint32_t)y * w + x;
540 uint32_t byte = pos >> 3;
541 uint32_t bit = 7 - (pos & 0x7);
542 return (uint32_t)(data[byte] >> bit) & 0x1;
543 }
544 case indexed4: {
545 uint32_t pos = (uint32_t)y*w + x;
546 uint32_t byte = pos >> 2;
547 uint32_t ix = 3 - (pos & 0x3);
548 return (uint32_t)((data[byte] & indexed4_mask[ix]) >> indexed4_shift[ix]);
549 }
550 case indexed16: {
551 uint32_t pos = (uint32_t)y*w + x;
552 uint32_t byte = pos >> 1;
553 uint32_t ix = 1 - (pos & 0x1);
554 return (uint32_t)((data[byte] & indexed16_mask[ix]) >> indexed16_shift[ix]);
555 }
556 case rgb332: {
557 int pos = y*w + x;
558 return rgb332to888(data[pos]);
559 }
560 case rgb565: {
561 int pos = y*(w<<1) + (x<<1);
562 uint16_t c = (uint16_t)(((uint16_t)data[pos] << 8) | (uint16_t)data[pos+1]);
563 return rgb565to888(c);
564 }
565 case rgb888: {
566 int pos = y*(w*3) + (x*3);
567 uint32_t r = data[pos];
568 uint32_t g = data[pos+1];
569 uint32_t b = data[pos+2];
570 return (r << 16 | g << 8 | b);
571 }
572 default:
573 break;
574 }
575 }
576 return 0;
577 }
578
579 static void h_line(image_buffer_t* img, int x, int y, int len, uint32_t c) {
580 for (int i = 0; i < len; i ++) {
581 putpixel(img, x+i, y, c);
582 }
583 }
584
585 static void v_line(image_buffer_t* img, int x, int y, int len, uint32_t c) {
586 for (int i = 0; i < len; i ++) {
587 putpixel(img, x, y+i, c);
588 }
589 }
590
591 static void fill_circle(image_buffer_t *img, int x, int y, int radius, uint32_t color) {
592 switch (radius) {
593 case 0:
594 break;
595
596 case 1:
597 putpixel(img, x - 1, y - 1, color);
598 putpixel(img, x, y - 1, color);
599 putpixel(img, x - 1, y, color);
600 putpixel(img, x, y, color);
601 break;
602
603 case 2:
604 h_line(img, x - 1, y - 2, 2, color);
605 h_line(img, x - 2, y - 1, 4, color);
606 h_line(img, x - 2, y, 4, color);
607 h_line(img, x - 1, y + 1, 2, color);
608 break;
609
610 case 3:
611 h_line(img, x - 2, y - 3, 4, color);
612 h_line(img, x - 3, y - 2, 6, color);
613 h_line(img, x - 3, y - 1, 6, color);
614 h_line(img, x - 3, y, 6, color);
615 h_line(img, x - 3, y + 1, 6, color);
616 h_line(img, x - 2, y + 2, 4, color);
617 break;
618
619 case 4:
620 h_line(img, x - 2, y - 4, 4, color);
621 h_line(img, x - 3, y - 3, 6, color);
622 h_line(img, x - 4, y - 2, 8, color);
623 h_line(img, x - 4, y - 1, 8, color);
624 h_line(img, x - 4, y, 8, color);
625 h_line(img, x - 4, y + 1, 8, color);
626 h_line(img, x - 3, y + 2, 6, color);
627 h_line(img, x - 2, y + 3, 4, color);
628 break;
629
630 default: {
631 int r_sq = radius * radius;
632 for (int y1 = -radius; y1 <= radius; y1++) {
633 for (int x1 = -radius; x1 <= radius; x1++) {
634 if (x1 * x1 + y1 * y1 <= r_sq) {
635 // Compute the start and end position for x axis
636 int x_left = x1;
637 while ((x1 + 1) <= radius && ((x1 + 1) * (x1 + 1) + y1 * y1) <= r_sq) {
638 x1++;
639 }
640 int x_right = x1;
641
642 // Draw line at this level y
643 int length = x_right - x_left + 1;
644 h_line(img, x + x_left, y + y1, length, color);
645
646 // Break out of innter loop for this level y
647 break;
648 }
649 }
650 }
651 } break;
652 }
653 }
654
655 // Circle helper function, to draw a circle with an inner and outer radius.
656 // Draws the slice at the given outer radius point.
657 static void handle_circle_slice(int outer_x, int outer_y, image_buffer_t *img, int c_x, int c_y, int radius_inner, uint32_t color, int radius_inner_dbl_sq) {
658 int width;
659
660 bool slice_filled;
661 if (outer_y < 0) {
662 slice_filled = -outer_y > radius_inner;
663 } else {
664 slice_filled = outer_y >= radius_inner;
665 }
666
667 if (slice_filled) {
668 if (outer_x < 0) {
669 width = -outer_x;
670 } else {
671 width = outer_x + 1;
672 outer_x = 0;
673 }
674 } else {
675 int cur_x = outer_x;
676 int delta = outer_x > 0 ? -1 : 1;
677
678 // TODO: this could probably be binary searched
679 int y_dbl_off = outer_y * 2 + 1;
680 int y_dbl_off_sq = y_dbl_off * y_dbl_off;
681 while (true) {
682 cur_x += delta;
683 int x_dbl_off = cur_x * 2 + 1;
684 if (x_dbl_off * x_dbl_off + y_dbl_off_sq <= radius_inner_dbl_sq
685 || abs(cur_x) > 2000) { // failsafe
686 break;
687 }
688 }
689 width = abs(cur_x - outer_x);
690 if (outer_x > 0) {
691 outer_x = cur_x + 1;
692 }
693 }
694
695 h_line(img, outer_x + c_x, outer_y + c_y, width, color);
696 }
697
698 // thickness extends inwards from the given radius circle
699 static void circle(image_buffer_t *img, int x, int y, int radius, int thickness, uint32_t color) {
700 if (thickness <= 0) {
701 int x0 = 0;
702 int y0 = radius;
703 int d = 5 - 4*radius;
704 int da = 12;
705 int db = 20 - 8*radius;
706
707 while (x0 < y0) {
708 putpixel(img, x + x0, y + y0, color);
709 putpixel(img, x + x0, y - y0, color);
710 putpixel(img, x - x0, y + y0, color);
711 putpixel(img, x - x0, y - y0, color);
712 putpixel(img, x + y0, y + x0, color);
713 putpixel(img, x + y0, y - x0, color);
714 putpixel(img, x - y0, y + x0, color);
715 putpixel(img, x - y0, y - x0, color);
716 if (d < 0) { d = d + da; db = db+8; }
717 else { y0 = y0 - 1; d = d+db; db = db + 16; }
718 x0 = x0+1;
719 da = da + 8;
720 }
721 } else {
722 int radius_inner = radius - thickness;
723
724 int radius_outer_dbl_sq = radius * radius * 4;
725 int radius_inner_dbl_sq = radius_inner * radius_inner * 4;
726
727 for (int y0 = 0; y0 < radius; y0++) {
728 int y_dbl_offs = 2 * y0 + 1;
729 int y_dbl_offs_sq = y_dbl_offs * y_dbl_offs;
730
731 for (int x0 = -radius; x0 <= 0; x0++) {
732 int x_dbl_offs = 2 * x0 + 1;
733 if (x_dbl_offs * x_dbl_offs + y_dbl_offs_sq <= radius_outer_dbl_sq) {
734 // This is horrible...
735 handle_circle_slice(x0, y0,
736 img, x, y, radius_inner, color, radius_inner_dbl_sq);
737 handle_circle_slice(-x0 - 1, y0,
738 img, x, y, radius_inner, color, radius_inner_dbl_sq);
739 handle_circle_slice(x0, -y0 - 1,
740 img, x, y, radius_inner, color, radius_inner_dbl_sq);
741 handle_circle_slice(-x0 - 1, -y0 - 1,
742 img, x, y, radius_inner, color, radius_inner_dbl_sq);
743 break;
744 }
745 }
746 }
747 }
748 }
749
750 // Thickness extends outwards and inwards from the given line equally, resulting
751 // in double the total thickness.
752 // TODO: This should be more efficient
753 // http://homepages.enterprise.net/murphy/thickline/index.html
754 // https://github.com/ArminJo/STMF3-Discovery-Demos/blob/master/lib/BlueDisplay/LocalGUI/ThickLine.hpp
755 static void line(image_buffer_t *img, int x0, int y0, int x1, int y1, int thickness, int dot1, int dot2, uint32_t c) {
756 int dx = abs(x1 - x0);
757 int sx = x0 < x1 ? 1 : -1;
758 int dy = -abs(y1 - y0);
759 int sy = y0 < y1 ? 1 : -1;
760 int error = dx + dy;
761
762 if (dot1 > 0) {
763 // These are used to deal with consecutive calls with
764 // possibly overlapping pixels.
765 static int dotcnt = 0;
766 static int x_last = 0;
767 static int y_last = 0;
768
769 while (true) {
770 if (dotcnt <= dot1) {
771 if (thickness > 1) {
772 fill_circle(img, x0, y0, thickness, c);
773 } else {
774 putpixel(img, x0, y0, c);
775 }
776 }
777
778 if (x0 != x_last || y0 != y_last) {
779 dotcnt++;
780 }
781
782 x_last = x0;
783 y_last = y0;
784
785 if (dotcnt >= (dot1 + dot2)) {
786 dotcnt = 0;
787 }
788
789 if (x0 == x1 && y0 == y1) {
790 break;
791 }
792 if ((error * 2) >= dy) {
793 if (x0 == x1) {
794 break;
795 }
796 error += dy;
797 x0 += sx;
798 }
799 if ((error * 2) <= dx) {
800 if (y0 == y1) {
801 break;
802 }
803 error += dx;
804 y0 += sy;
805 }
806 }
807 } else {
808 while (true) {
809 if (thickness > 1) {
810 fill_circle(img, x0, y0, thickness, c);
811 } else {
812 putpixel(img, x0, y0, c);
813 }
814
815 if (x0 == x1 && y0 == y1) {
816 break;
817 }
818 if ((error * 2) >= dy) {
819 if (x0 == x1) {
820 break;
821 }
822 error += dy;
823 x0 += sx;
824 }
825 if ((error * 2) <= dx) {
826 if (y0 == y1) {
827 break;
828 }
829 error += dx;
830 y0 += sy;
831 }
832 }
833 }
834 }
835
836 // thickness extends inwards from the given rectangle edge.
837 static void rectangle(image_buffer_t *img, int x, int y, int width, int height,
838 bool fill, int thickness, int dot1, int dot2, uint32_t color) {
839 thickness /= 2;
840
841 if (fill) {
842 for (int i = y; i < (y + height);i++) {
843 h_line(img, x, i, width, color);
844 }
845 } else {
846 if (thickness <= 0 && dot1 == 0) {
847 h_line(img, x, y, width, color);
848 h_line(img, x, y + height, width, color);
849 v_line(img, x, y, height, color);
850 v_line(img, x + width, y, height, color);
851 } else {
852 x += thickness;
853 y += thickness;
854 width -= thickness * 2;
855 height -= thickness * 2;
856 // top
857 line(img, x, y, x + width, y, thickness, dot1, dot2, color);
858 // bottom
859 line(img, x, y + height, x + width, y + height, thickness, dot1, dot2, color);
860 // left
861 line(img, x, y, x, y + height, thickness, dot1, dot2, color);
862 // right
863 line(img, x + width, y, x + width, y + height, thickness, dot1, dot2, color);
864 }
865 }
866 }
867
868 #define NMIN(a, b) ((a) < (b) ? (a) : (b))
869 #define NMAX(a, b) ((a) > (b) ? (a) : (b))
870
871 static void fill_triangle(image_buffer_t *img, int x0, int y0,
872 int x1, int y1, int x2, int y2, uint32_t color) {
873 int x_min = NMIN(x0, NMIN(x1, x2));
874 int x_max = NMAX(x0, NMAX(x1, x2));
875 int y_min = NMIN(y0, NMIN(y1, y2));
876 int y_max = NMAX(y0, NMAX(y1, y2));
877
878 for (int y = y_min;y <= y_max;y++) {
879 for (int x = x_min;x <= x_max;x++) {
880 int w0 = point_past_line(x, y, x1, y1, x2, y2);
881 int w1 = point_past_line(x, y, x2, y2, x0, y0);
882 int w2 = point_past_line(x, y, x0, y0, x1, y1);
883
884 if ((w0 >= 0 && w1 >= 0 && w2 >= 0)
885 || (w0 <= 0 && w1 <= 0 && w2 <= 0)) {
886 putpixel(img, x, y, color);
887 }
888 }
889 }
890 }
891
892 static void generic_arc(image_buffer_t *img, int x, int y, int rad, float ang_start, float ang_end,
893 int thickness, bool filled, int dot1, int dot2, int res, bool sector, bool segment, uint32_t color) {
894 ang_start *= (float)M_PI / 180.0f;
895 ang_end *= (float)M_PI / 180.0f;
896
897 norm_angle(&ang_start);
898 norm_angle(&ang_end);
899
900 float ang_range = ang_end - ang_start;
901
902 if (ang_range < 0.0) {
903 ang_range += 2.0f * (float)M_PI;
904 }
905
906 if (res <= 0) {
907 res = 80;
908 }
909
910 float steps = ceilf((float)res * ang_range * (0.5f / (float)M_PI));
911
912 float ang_step = ang_range / steps;
913 float sa = sinf(ang_step);
914 float ca = cosf(ang_step);
915
916 float px_start = cosf(ang_start) * (float)rad;
917 float py_start = sinf(ang_start) * (float)rad;
918
919
920 float px = px_start;
921 float py = py_start;
922
923 for (int i = 0;i < steps;i++) {
924 float px_before = px;
925 float py_before = py;
926
927 px = px * ca - py * sa;
928 py = py * ca + px_before * sa;
929
930 if (filled) {
931 if (sector) {
932 fill_triangle(img,
933 x + (int)px_before, y + (int)py_before,
934 x + (int)px, y + (int)py,
935 x, y,
936 color);
937 } else {
938 fill_triangle(img,
939 x + (int)px_before, y + (int)py_before,
940 x + (int)px, y + (int)py,
941 x + (int)px_start, y + (int)py_start,
942 color);
943 }
944 } else {
945 line(img, x + (int)px_before, y + (int)py_before,
946 x + (int)px, y + (int)py, thickness, dot1, dot2, color);
947 }
948 }
949
950 if (!filled && sector) {
951 line(img, x + (int)px, y + (int)py,
952 x, y,
953 thickness, dot1, dot2, color);
954 line(img, x, y,
955 x + (int)px_start, y + (int)py_start,
956 thickness, dot1, dot2, color);
957 }
958
959 if (!filled && segment) {
960 line(img, x + (int)px, y + (int)py,
961 x + (int)px_start, y + (int)py_start,
962 thickness, dot1, dot2, color);
963 }
964 }
965
966 // thin arc helper function
967 // handles a single pixel in the complete circle, checking if the pixel is part
968 // of the arc.
969 static void handle_thin_arc_pixel(image_buffer_t *img, int x, int y,
970 int c_x, int c_y, int cap0_x, int cap0_y, int cap1_x, int cap1_y, int min_y, int max_y, bool angle_is_closed, uint32_t color) {
971 if (y > max_y || y < min_y) {
972 return;
973 }
974
975 int line_is_past_0 = point_past_line(x, y, 0, 0, cap0_x, cap0_y);
976 int line_is_past_1 = -point_past_line(x, y, 0, 0, cap1_x, cap1_y);
977
978 bool in_cap0_quadrant = points_same_quadrant(
979 x, y, cap0_x, cap0_y);
980 bool in_cap1_quadrant = points_same_quadrant(
981 x, y, cap1_x, cap1_y);
982
983 if (angle_is_closed) {
984 if (line_is_past_0 == 1 && line_is_past_1 == 1) {
985 return;
986 }
987 } else {
988 if (line_is_past_0 == 1 || line_is_past_1 == 1
989 || (line_is_past_0 == 0 && !in_cap0_quadrant)
990 || (line_is_past_1 == 0 && !in_cap1_quadrant)) {
991 return;
992 }
993 }
994
995 putpixel(img, c_x + x, c_y + y, color);
996 }
997
998 // single pixel wide arc
999 static void thin_arc(image_buffer_t *img, int c_x, int c_y, int radius, float angle0, float angle1, bool sector, bool segment, uint32_t color) {
1000 if (radius == 0) {
1001 return;
1002 }
1003
1004 angle0 *= (float)M_PI / 180.0f;
1005 angle1 *= (float)M_PI / 180.0f;
1006 norm_angle_0_2pi(&angle0);
1007 norm_angle_0_2pi(&angle1);
1008
1009 if (angle0 == angle1) {
1010 return;
1011 }
1012
1013 bool angle_is_closed;
1014 // if the angle of the filled in part of the arc is greater than 180°
1015 // honestly unsure if it'd be better if this was called angle_is_open
1016 if (angle1 - angle0 > 0.0) {
1017 angle_is_closed = fabsf(angle1 - angle0) > M_PI;
1018 } else {
1019 angle_is_closed = fabsf(angle1 - angle0) < M_PI;
1020 }
1021
1022 int cap0_x = (int)(cosf(angle0) * (float)(radius));
1023 int cap0_y = (int)(sinf(angle0) * (float)(radius));
1024
1025 int cap1_x = (int)(cosf(angle1) * (float)(radius));
1026 int cap1_y = (int)(sinf(angle1) * (float)(radius));
1027
1028 // Highest and lowest (y coord wise) drawn line of the base arc (excluding
1029 // the circular end caps). This range is *inclusive*!
1030 // Note that these might be slightly off due to inconsistent rounding between
1031 // my circle drawing algorithm and point rotation.
1032 int min_y = MIN(cap0_y, cap1_y);
1033 int max_y = MAX(cap0_y, cap1_y);
1034 if (angle0 < angle1) {
1035 if (angle0 < M_PI_2 && angle1 >= M_3PI_2) {
1036 min_y = -radius;
1037 max_y = radius;
1038 } else if (angle0 < M_3PI_2 && angle1 > M_3PI_2) {
1039 min_y = -radius;
1040 } else if (angle0 < M_PI_2 && angle1 > M_PI_2) {
1041 max_y = radius;
1042 }
1043 } else {
1044 if ((angle0 < M_3PI_2 && angle1 >= M_PI_2)
1045 || (angle0 < M_PI_2)
1046 || (angle1 > M_3PI_2)) {
1047 min_y = -radius;
1048 max_y = radius;
1049 } else if (angle0 < M_3PI_2 && angle1 < M_PI_2) {
1050 min_y = -radius;
1051 } else if (angle0 > M_PI_2 && angle1 > M_PI_2) {
1052 max_y = radius;
1053 }
1054 }
1055
1056 int radius_dbl_sq = radius * radius * 4;
1057
1058 int last_x = 0;
1059 for (int y = radius - 1; y >= 0; y--) {
1060 int y_dbl_offs = 2 * y + 1;
1061 int y_dbl_offs_sq = y_dbl_offs * y_dbl_offs;
1062
1063 for (int x = -radius; x <= 0; x++) {
1064 int x_dbl_offs = 2 * x + 1;
1065 if (x_dbl_offs * x_dbl_offs + y_dbl_offs_sq <= radius_dbl_sq) {
1066 if (last_x - x < 2) {
1067 // This is horrible...
1068 handle_thin_arc_pixel(img, x, y,
1069 c_x, c_y, cap0_x, cap0_y, cap1_x, cap1_y, min_y, max_y, angle_is_closed, color);
1070 handle_thin_arc_pixel(img, -x - 1, y,
1071 c_x, c_y, cap0_x, cap0_y, cap1_x, cap1_y, min_y, max_y, angle_is_closed, color);
1072
1073 handle_thin_arc_pixel(img, x, -y - 1,
1074 c_x, c_y, cap0_x, cap0_y, cap1_x, cap1_y, min_y, max_y, angle_is_closed, color);
1075 handle_thin_arc_pixel(img, -x - 1, -y - 1,
1076 c_x, c_y, cap0_x, cap0_y, cap1_x, cap1_y, min_y, max_y, angle_is_closed, color);
1077 } else {
1078 for (int x0 = x; x0 < last_x; x0++) {
1079 handle_thin_arc_pixel(img, x0, y,
1080 c_x, c_y, cap0_x, cap0_y, cap1_x, cap1_y, min_y, max_y, angle_is_closed, color);
1081 handle_thin_arc_pixel(img, -x0 - 1, y,
1082 c_x, c_y, cap0_x, cap0_y, cap1_x, cap1_y, min_y, max_y, angle_is_closed, color);
1083
1084 handle_thin_arc_pixel(img, x0, -y - 1,
1085 c_x, c_y, cap0_x, cap0_y, cap1_x, cap1_y, min_y, max_y, angle_is_closed, color);
1086 handle_thin_arc_pixel(img, -x0 - 1, -y - 1,
1087 c_x, c_y, cap0_x, cap0_y, cap1_x, cap1_y, min_y, max_y, angle_is_closed, color);
1088 }
1089 }
1090
1091 last_x = x;
1092 break;
1093 }
1094 }
1095 }
1096
1097 if (sector) {
1098 line(img, c_x, c_y, c_x + cap0_x, c_y + cap0_y, 1, 0, 0, color);
1099 line(img, c_x, c_y, c_x + cap1_x, c_y + cap1_y, 1, 0, 0, color);
1100 }
1101
1102 if (segment) {
1103 line(img, c_x + cap0_x, c_y + cap0_y, c_x + cap1_x, c_y + cap1_y, 1, 0, 0, color);
1104 }
1105 }
1106
1107 // arc helper function
1108 // handles a horizontal slice at the given outer arc point
1109 static void handle_arc_slice(image_buffer_t *img, int outer_x, int outer_y, int c_x, int c_y, uint32_t color,
1110 int outer_x0, int outer_y0, int outer_x1, int outer_y1,
1111 int cap0_min_y, int cap0_max_y, int cap1_min_y, int cap1_max_y,
1112 int radius_outer, int radius_inner,
1113 int min_y, int max_y,
1114 float angle0, float angle1, bool angle_is_closed,
1115 bool filled, bool segment,
1116 int radius_inner_dbl_sq) {
1117 (void) radius_outer;
1118 if (outer_y > max_y || outer_y < min_y) {
1119 return;
1120 }
1121
1122 int line_is_past_0, line_is_past_1;
1123 line_is_past_0 = point_past_line(outer_x, outer_y, 0, 0, outer_x0, outer_y0);
1124 line_is_past_1 = -point_past_line(outer_x, outer_y, 0, 0, outer_x1, outer_y1);
1125
1126 int outer_x_sign = sign(outer_x);
1127 int outer_y_sign = sign(outer_y);
1128
1129 int outer_x0_sign = sign(outer_x0);
1130 int outer_y0_sign = sign(outer_y0);
1131
1132 int outer_x1_sign = sign(outer_x1);
1133 int outer_y1_sign = sign(outer_y1);
1134
1135 bool in_cap0, in_cap1, in_both_caps;
1136 if (segment && filled) {
1137 in_cap0 = outer_y <= MAX(outer_y0, outer_y1)
1138 && outer_y >= MIN(outer_y0, outer_y1);
1139 in_cap1 = false;
1140 }
1141 else if (filled) {
1142 in_cap0 = outer_y <= cap0_max_y
1143 && outer_x0_sign == outer_x_sign
1144 && outer_y0_sign == outer_y_sign;
1145 in_cap1 = outer_y <= cap1_max_y
1146 && outer_x1_sign == outer_x_sign
1147 && outer_y1_sign == outer_y_sign;
1148 } else {
1149 in_cap0 = outer_y >= cap0_min_y
1150 && outer_y <= cap0_max_y
1151 && outer_x_sign == outer_x0_sign;
1152 in_cap1 = outer_y >= cap1_min_y
1153 && outer_y <= cap1_max_y
1154 && outer_x_sign == outer_x1_sign;
1155 }
1156 in_both_caps = in_cap0 && in_cap1;
1157
1158 bool in_cap0_quadrant = points_same_quadrant(outer_x, outer_y, outer_x0, outer_y0);
1159 bool in_cap1_quadrant = points_same_quadrant(outer_x, outer_y, outer_x1, outer_y1);
1160
1161 bool caps_in_same_quadrant = points_same_quadrant(outer_x0, outer_y0, outer_x1, outer_y1);
1162
1163 // Check if slice is outside caps and drawn sections of the arc.
1164 if (!in_cap0 && !in_cap1) {
1165 if (angle_is_closed) {
1166 if (line_is_past_0 == 1 && line_is_past_1 == 1
1167 // Failsafe for closed angles with a very small difference.
1168 // Otherwise a tiny section at the opposite side of the arc
1169 // might get skipped.
1170 && (!caps_in_same_quadrant || (in_cap0_quadrant && in_cap1_quadrant))) {
1171 return;
1172 }
1173 } else {
1174 if (line_is_past_0 == 1 || line_is_past_1 == 1
1175 || (line_is_past_0 == 0 && !in_cap0_quadrant)
1176 || (line_is_past_1 == 0 && !in_cap1_quadrant)) {
1177 return;
1178 }
1179 }
1180 }
1181
1182 // Find slice width if arc spanned the complete circle.
1183 int x, x1;
1184 int width = 0;
1185 int width1 = 0;
1186 bool slice_is_split = false;
1187
1188 bool slice_filled;
1189 if (filled) {
1190 slice_filled = true;
1191 } else {
1192 if (outer_y < 0) {
1193 slice_filled = -outer_y > radius_inner;
1194 } else {
1195 slice_filled = outer_y >= radius_inner;
1196 }
1197 }
1198
1199 if (slice_filled) {
1200 if (outer_x < 0) {
1201 x = outer_x;
1202 width = -x;
1203 } else {
1204 x = 0;
1205 width = outer_x + 1;
1206 }
1207 } else {
1208 x = outer_x;
1209 int cur_x = outer_x;
1210 int delta = outer_x > 0 ? -1 : 1;
1211
1212 // TODO: this could probably be binary searched
1213 int y_dbl_off = outer_y * 2 + 1;
1214 int y_dbl_off_sq = y_dbl_off * y_dbl_off;
1215 while (true) {
1216 cur_x += delta;
1217 int x_dbl_off = cur_x * 2 + 1;
1218 if (x_dbl_off * x_dbl_off + y_dbl_off_sq <= radius_inner_dbl_sq
1219 || abs(x) > 2000) { // failsafe
1220 break;
1221 }
1222 }
1223 width = abs(cur_x - x);
1224 if (outer_x > 0) {
1225 x = cur_x + 1;
1226 }
1227 }
1228
1229 // Check which cap lines intersects this slice
1230 if ((in_cap0 || in_cap1) && !(segment && filled)) {
1231 // the range from x_start to x_end is *inclusive*
1232 int x_start = x;
1233 int x_end = x_start + width - 1;
1234
1235 int x_start1;
1236 int x_end1;
1237
1238 // when a point is "past" a line, it is on the wrong cleared side of it
1239 int start_is_past0 = point_past_line(x_start, outer_y,
1240 0, 0,
1241 outer_x0, outer_y0);
1242 int end_is_past0 = point_past_line(x_end, outer_y,
1243 0, 0,
1244 outer_x0, outer_y0);
1245
1246 int start_is_past1 = -point_past_line(x_start, outer_y,
1247 0, 0,
1248 outer_x1, outer_y1);
1249 int end_is_past1 = -point_past_line(x_end, outer_y,
1250 0, 0,
1251 outer_x1, outer_y1);
1252
1253 bool slice_overlaps0 = start_is_past0 != end_is_past0
1254 && (start_is_past0 != 0 || end_is_past0 != 0);
1255 bool slice_overlaps1 = start_is_past1 != end_is_past1
1256 && (start_is_past1 != 0 || end_is_past1 != 0);
1257
1258 if ((in_cap0 && !in_cap1 && start_is_past0 == 1 && end_is_past0 == 1)
1259 || (!in_cap0 && in_cap1 && start_is_past1 == 1 && end_is_past1 == 1)
1260 || (in_both_caps && !angle_is_closed && (
1261 (start_is_past0 == 1 && end_is_past0 == 1)
1262 || (start_is_past1 == 1 && end_is_past1 == 1)
1263 ))
1264 || (in_both_caps && angle_is_closed && (
1265 (start_is_past0 == 1 && end_is_past0 == 1)
1266 && (start_is_past1 == 1 && end_is_past1 == 1)
1267 ))) {
1268 return;
1269 }
1270
1271 // The repetition in all these cases could probably be reduced...
1272 if ((in_both_caps && slice_overlaps0 && !slice_overlaps1)
1273 || (in_cap0 && !in_cap1 && slice_overlaps0)) {
1274 // intersect with cap line 0
1275 if (start_is_past0 != -1 && end_is_past0 != 1) {
1276 while (start_is_past0 == 1) {
1277 x_start += 1;
1278 start_is_past0 = point_past_line(x_start, outer_y,
1279 0, 0,
1280 outer_x0, outer_y0);
1281 }
1282 } else {
1283 while (end_is_past0 == 1) {
1284 x_end -= 1;
1285 end_is_past0 = point_past_line(x_end, outer_y,
1286 0, 0,
1287 outer_x0, outer_y0);
1288 }
1289 }
1290 } else if ((in_both_caps && !slice_overlaps0 && slice_overlaps1)
1291 || (!in_cap0 && in_cap1 && slice_overlaps1)) {
1292 // intersect with cap line 1
1293 if (start_is_past1 != -1 && end_is_past1 != 1) {
1294 while (start_is_past1 == 1) {
1295 x_start += 1;
1296 start_is_past1 = -point_past_line(x_start, outer_y,
1297 0, 0,
1298 outer_x1, outer_y1);
1299 }
1300 } else {
1301 while (end_is_past1 == 1) {
1302 x_end -= 1;
1303 end_is_past1 = -point_past_line(x_end, outer_y,
1304 0, 0,
1305 outer_x1, outer_y1);
1306 }
1307 }
1308 } else if (in_both_caps && slice_overlaps0 && slice_overlaps1) {
1309 // intersect with both cap lines
1310 if (angle0 < angle1) {
1311 if (angle0 < M_PI) {
1312 while (start_is_past1 == 1) {
1313 x_start += 1;
1314 start_is_past1 = -point_past_line(x_start, outer_y,
1315 0, 0,
1316 outer_x1, outer_y1);
1317 }
1318 while (end_is_past0 == 1) {
1319 x_end -= 1;
1320 end_is_past0 = point_past_line(x_end, outer_y,
1321 0, 0,
1322 outer_x0, outer_y0);
1323 }
1324 } else {
1325 while (start_is_past0 == 1) {
1326 x_start += 1;
1327 start_is_past0 = point_past_line(x_start, outer_y,
1328 0, 0,
1329 outer_x0, outer_y0);
1330 }
1331 while (end_is_past1 == 1) {
1332 x_end -= 1;
1333 end_is_past1 = -point_past_line(x_end, outer_y,
1334 0, 0,
1335 outer_x1, outer_y1);
1336 }
1337 }
1338 } else {
1339 // split the slice into two
1340
1341 slice_is_split = true;
1342
1343 x_start1 = x_start;
1344 x_end1 = x_end;
1345
1346 if (angle0 < M_PI) {
1347 while (end_is_past0 == 1) {
1348 x_end -= 1;
1349 end_is_past0 = point_past_line(x_end, outer_y,
1350 0, 0,
1351 outer_x0, outer_y0);
1352 }
1353 while (start_is_past1 == 1) {
1354 x_start1 += 1;
1355 start_is_past1 = -point_past_line(x_start1, outer_y,
1356 0, 0,
1357 outer_x1, outer_y1);
1358 }
1359 } else {
1360 while (end_is_past1 == 1) {
1361 x_end1 -= 1;
1362 end_is_past1 = -point_past_line(x_end1, outer_y,
1363 0, 0,
1364 outer_x1, outer_y1);
1365 }
1366 while (start_is_past0 == 1) {
1367 x_start += 1;
1368 start_is_past0 = point_past_line(x_start, outer_y,
1369 0, 0,
1370 outer_x0, outer_y0);
1371 }
1372 }
1373
1374 x1 = x_start1;
1375 width1 = x_end1 + 1 - x_start1 ;
1376 }
1377 }
1378 x = x_start;
1379 width = x_end + 1 - x_start;
1380 } else if (in_cap0 && segment && filled) {
1381 // the range from x_start to x_end is *inclusive*
1382 int x_start = x;
1383 int x_end = x_start + width - 1;
1384
1385 // when a point is "past" a line, it is on the wrong cleared side of it
1386 int start_is_past = -point_past_line(x_start, outer_y,
1387 outer_x0, outer_y0, outer_x1, outer_y1);
1388 int end_is_past = -point_past_line(x_end, outer_y,
1389 outer_x0, outer_y0, outer_x1, outer_y1);
1390
1391 bool slice_overlaps = start_is_past != end_is_past
1392 && (start_is_past != 0 || end_is_past != 0);
1393
1394 if (start_is_past == 1 && end_is_past == 1) {
1395 return;
1396 }
1397
1398 if (slice_overlaps) {
1399 if (start_is_past != -1 && end_is_past != 1) {
1400 while (start_is_past == 1) {
1401 x_start += 1;
1402 start_is_past = -point_past_line(x_start, outer_y,
1403 outer_x0, outer_y0, outer_x1, outer_y1);
1404 }
1405 } else {
1406 while (end_is_past == 1) {
1407 x_end -= 1;
1408 end_is_past = -point_past_line(x_end, outer_y,
1409 outer_x0, outer_y0, outer_x1, outer_y1);
1410 }
1411 }
1412 }
1413
1414 x = x_start;
1415 width = x_end + 1 - x_start;
1416 }
1417
1418 h_line(img, c_x + x, c_y + outer_y, width, color);
1419 if (slice_is_split) {
1420 h_line(img, c_x + x1, c_y + outer_y, width1, color);
1421 }
1422 }
1423
1424 // TODO: Fix unwanted slice with angles 130 to 115 (I think, angles might be
1425 // slightly off).
1426 // TODO: Look into buggy rendering with angles around 180°-270°. This seems to
1427 // affect arcs, sectors, and segments likewise.
1428 static void arc(image_buffer_t *img, int c_x, int c_y, int radius, float angle0, float angle1,
1429 int thickness, bool rounded, bool filled, bool sector, bool segment, int dot1, int dot2, int resolution, uint32_t color) {
1430 if (dot1 > 0 && !filled) {
1431 thickness /= 2;
1432
1433 radius -= thickness;
1434
1435 if (thickness == 0) {
1436 thickness = 1;
1437 }
1438
1439 generic_arc(img, c_x, c_y, radius, angle0, angle1, thickness, false, dot1, dot2, resolution, sector, segment, color);
1440
1441 return;
1442 }
1443
1444 if (thickness <= 1 && !filled) {
1445 thin_arc(img, c_x, c_y, radius, angle0, angle1, sector, segment, color);
1446
1447 return;
1448 }
1449
1450 if (radius == 0) {
1451 return;
1452 }
1453
1454 angle0 *= (float)M_PI / 180.0f;
1455 angle1 *= (float)M_PI / 180.0f;
1456 norm_angle_0_2pi(&angle0); // theses are probably unecessary?
1457 norm_angle_0_2pi(&angle1); // but who knows with floating point imprecision...
1458
1459 if (angle0 == angle1) {
1460 return;
1461 }
1462
1463 bool angle_is_closed;
1464 // if the angle of the filled in part of the arc is greater than 180°
1465 if (angle1 - angle0 > 0.0) {
1466 angle_is_closed = fabsf(angle1 - angle0) > M_PI;
1467 } else {
1468 angle_is_closed = fabsf(angle1 - angle0) < M_PI;
1469 }
1470
1471 // angles smaller than 1 degree seem to cause issues (with a radius of 62)
1472 // this is kinda ugly though, and it will probably still break at larger
1473 // radii or something...
1474 if (!angle_is_closed && fabsf(angle1 - angle0) < 0.0174532925) { // one degree in radians
1475 if (rounded) {
1476 float rad_f = (float)radius - ((float)thickness / 2.0f);
1477
1478 float angle = (angle0 + angle1) / 2.0f;
1479
1480 int cap_center_x = (int)floorf(cosf(angle) * rad_f);
1481 int cap_center_y = (int)floorf(sinf(angle) * rad_f);
1482
1483 fill_circle(img, c_x + cap_center_x, c_y + cap_center_y, thickness / 2, color);
1484 }
1485 return;
1486 }
1487
1488 if (thickness >= radius) {
1489 filled = true;
1490 }
1491
1492 int radius_outer, radius_inner;
1493 if (filled) {
1494 radius_outer = radius;
1495 radius_inner = 0;
1496 } else {
1497 radius_outer = radius;
1498 radius_inner = radius - thickness;
1499 }
1500 int radius_outer_dbl_sq = radius_outer * radius_outer * 4;
1501 int radius_inner_dbl_sq = radius_inner * radius_inner * 4;
1502
1503 float angle0_cos = cosf(angle0);
1504 float angle0_sin = sinf(angle0);
1505 float angle1_cos = cosf(angle1);
1506 float angle1_sin = sinf(angle1);
1507
1508 int outer_x0 = (int)(angle0_cos * (float)radius_outer);
1509 int outer_y0 = (int)(angle0_sin * (float)radius_outer);
1510
1511 int outer_x1 = (int)(angle1_cos * (float)radius_outer);
1512 int outer_y1 = (int)(angle1_sin * (float)radius_outer);
1513
1514 int inner_y0;
1515 int inner_y1;
1516
1517 if (filled) {
1518 inner_y0 = 0;
1519
1520 inner_y1 = 0;
1521 } else {
1522 inner_y0 = (int)(angle0_sin * (float)radius_inner);
1523
1524 inner_y1 = (int)(angle1_sin * (float)radius_inner);
1525 }
1526
1527 int cap0_min_y = MIN(inner_y0, outer_y0);
1528 int cap0_max_y = MAX(inner_y0, outer_y0);
1529
1530 int cap1_min_y = MIN(inner_y1, outer_y1);
1531 int cap1_max_y = MAX(inner_y1, outer_y1);
1532
1533 // Highest and lowest (y coord wise) drawn line of the base arc (excluding
1534 // the circular end caps). This range is *inclusive*!
1535 // Note that these might be slightly off due to inconsistent rounding between
1536 // Bresenhamn's algorithm and point rotation. (I don't think the point about
1537 // Bresenhamn is relevant as we don't use it anymore. Still wouldn't trust
1538 // them completely though...)
1539 int min_y = MIN(outer_y0, MIN(outer_y1, MIN(inner_y0, inner_y1)));
1540 int max_y = MAX(outer_y0, MAX(outer_y1, MAX(inner_y0, inner_y1)));
1541 if (angle0 < angle1) {
1542 if (angle0 < M_PI_2 && angle1 >= M_3PI_2) {
1543 min_y = -radius_outer;
1544 max_y = radius_outer;
1545 } else if (angle0 < M_3PI_2 && angle1 > M_3PI_2) {
1546 min_y = -radius_outer;
1547 } else if (angle0 < M_PI_2 && angle1 > M_PI_2) {
1548 max_y = radius_outer;
1549 }
1550 } else {
1551 if ((angle0 < M_3PI_2 && angle1 >= M_PI_2)
1552 || (angle0 < M_PI_2)
1553 || (angle1 > M_3PI_2)) {
1554 min_y = -radius_outer;
1555 max_y = radius_outer;
1556 } else if (angle0 < M_3PI_2 && angle1 < M_PI_2) {
1557 min_y = -radius_outer;
1558 } else if (angle0 > M_PI_2 && angle1 > M_PI_2) {
1559 max_y = radius_outer;
1560 }
1561 }
1562
1563 for (int y = 0; y < radius_outer; y++) {
1564 int y_dbl_offs = 2 * (y + 1);
1565 int y_dbl_offs_sq = y_dbl_offs * y_dbl_offs;
1566
1567 for (int x = -radius_outer; x <= 0; x++) {
1568 int x_dbl_offs = 2 * (x + 1);
1569 if (x_dbl_offs * x_dbl_offs + y_dbl_offs_sq <= radius_outer_dbl_sq) {
1570 // This is horrible...
1571 handle_arc_slice(img, x, y,
1572 c_x, c_y, color, outer_x0, outer_y0, outer_x1, outer_y1,
1573 cap0_min_y, cap0_max_y, cap1_min_y, cap1_max_y, radius_outer, radius_inner, min_y, max_y,
1574 angle0, angle1, angle_is_closed, filled, segment, radius_inner_dbl_sq);
1575 handle_arc_slice(img, -x - 1, y,
1576 c_x, c_y, color, outer_x0, outer_y0, outer_x1, outer_y1,
1577 cap0_min_y, cap0_max_y, cap1_min_y, cap1_max_y, radius_outer, radius_inner, min_y, max_y,
1578 angle0, angle1, angle_is_closed, filled, segment, radius_inner_dbl_sq);
1579
1580 handle_arc_slice(img, x, -y - 1,
1581 c_x, c_y, color, outer_x0, outer_y0, outer_x1, outer_y1,
1582 cap0_min_y, cap0_max_y, cap1_min_y, cap1_max_y, radius_outer, radius_inner, min_y, max_y,
1583 angle0, angle1, angle_is_closed, filled, segment, radius_inner_dbl_sq);
1584 handle_arc_slice(img, -x - 1, -y - 1,
1585 c_x, c_y, color, outer_x0, outer_y0, outer_x1, outer_y1,
1586 cap0_min_y, cap0_max_y, cap1_min_y, cap1_max_y, radius_outer, radius_inner, min_y, max_y,
1587 angle0, angle1, angle_is_closed, filled, segment, radius_inner_dbl_sq);
1588
1589 break;
1590 }
1591 }
1592 }
1593
1594 // draw rounded line corners
1595 if (rounded && !filled && !sector && !segment) {
1596 float rad_f = (float)radius - ((float)thickness / 2.0f);
1597
1598 int cap0_center_x = (int)floorf(angle0_cos * rad_f);
1599 int cap0_center_y = (int)floorf(angle0_sin * rad_f);
1600
1601 int cap1_center_x = (int)floorf(angle1_cos * rad_f);
1602 int cap1_center_y = (int)floorf(angle1_sin * rad_f);
1603
1604 thickness /= 2;
1605
1606 fill_circle(img, c_x + cap0_center_x, c_y + cap0_center_y, thickness, color);
1607 fill_circle(img, c_x + cap1_center_x, c_y + cap1_center_y, thickness, color);
1608 }
1609
1610 // draw sector arc cap to center lines
1611 // (sectors are always rounded)
1612 if (sector && !filled) {
1613 float rad_f = (float)radius - ((float)thickness / 2.0f);
1614
1615 int cap0_center_x = (int)floorf(angle0_cos * rad_f);
1616 int cap0_center_y = (int)floorf(angle0_sin * rad_f);
1617
1618 int cap1_center_x = (int)floorf(angle1_cos * rad_f);
1619 int cap1_center_y = (int)floorf(angle1_sin * rad_f);
1620
1621 thickness /= 2;
1622
1623 line(img, c_x + cap0_center_x, c_y + cap0_center_y,
1624 c_x, c_y, thickness, 0, 0, color);
1625 line(img, c_x + cap1_center_x, c_y + cap1_center_y,
1626 c_x, c_y, thickness, 0, 0, color);
1627 }
1628
1629 if (segment && !filled) {
1630 float rad_f = (float)radius - ((float)thickness / 2.0f);
1631
1632 int cap0_center_x = (int)floorf(angle0_cos * rad_f);
1633 int cap0_center_y = (int)floorf(angle0_sin * rad_f);
1634
1635 int cap1_center_x = (int)floorf(angle1_cos * rad_f);
1636 int cap1_center_y = (int)floorf(angle1_sin * rad_f);
1637
1638 thickness /= 2;
1639
1640 line(img, c_x + cap0_center_x, c_y + cap0_center_y,
1641 c_x + cap1_center_x, c_y + cap1_center_y, thickness, 0, 0, color);
1642 }
1643 }
1644
1645 static void img_putc(image_buffer_t *img, int x, int y, uint32_t *colors, int num_colors,
1646 uint8_t *font_data, uint8_t ch, bool up, bool down) {
1647 uint8_t w = font_data[0];
1648 uint8_t h = font_data[1];
1649 uint8_t char_num = font_data[2];
1650 uint8_t bits_per_pixel = font_data[3];
1651
1652 int pixels_per_byte = (int)(8 / bits_per_pixel);
1653 int bytes_per_char = (int)((w * h) / pixels_per_byte);
1654 if ((w * h) % pixels_per_byte != 0) {
1655 bytes_per_char += 1;
1656 }
1657
1658 // There are some expectations on ch that are not documented here.
1659 if (char_num == 10) {
1660 ch = (uint8_t)(ch - '0');
1661 } else {
1662 ch = (uint8_t)(ch - ' ');
1663 }
1664
1665 if (ch >= char_num) {
1666 return;
1667 }
1668
1669 if (bits_per_pixel == 2) {
1670 if (num_colors < 4) {
1671 return;
1672 }
1673
1674 for (int i = 0; i < w * h; i++) {
1675 uint8_t byte = font_data[4 + bytes_per_char * ch + (i / 4)];
1676 uint8_t bit_pos = (uint8_t)(i % pixels_per_byte);
1677 uint8_t pixel_value = (byte >> (bit_pos * 2)) & 0x03;
1678 int x0 = i % w;
1679 int y0 = i / w;
1680 if (up) {
1681 putpixel(img, x + y0, y - x0, colors[pixel_value]);
1682 } else if (down) {
1683 putpixel(img, x - y0, y + x0, colors[pixel_value]);
1684 } else {
1685 putpixel(img, x + x0, y + y0, colors[pixel_value]);
1686 }
1687 }
1688 } else {
1689 if (num_colors < 1) {
1690 return;
1691 }
1692
1693 int32_t fg = (int32_t)colors[0];
1694 int32_t bg = -1;
1695
1696 if (num_colors > 1) {
1697 bg = (int32_t)colors[1];
1698 }
1699
1700 for (int i = 0; i < w * h; i++) {
1701 uint8_t byte = font_data[4 + bytes_per_char * ch + (i / 8)];
1702 uint8_t bit_pos = (uint8_t)(i % 8);
1703 uint8_t bit = (uint8_t)(byte & (1 << bit_pos));
1704 if (bit || bg >= 0) {
1705 int x0 = i % w;
1706 int y0 = i / w;
1707
1708 if (up) {
1709 putpixel(img, x + y0, y - x0, bit ? (uint32_t)fg : (uint32_t)bg);
1710 } else if (down) {
1711 putpixel(img, x - y0, y + x0, bit ? (uint32_t)fg : (uint32_t)bg);
1712 } else {
1713 putpixel(img, x + x0, y + y0, bit ? (uint32_t)fg : (uint32_t)bg);
1714 }
1715 }
1716 }
1717 }
1718 }
1719
1720 static void blit_rot_scale(
1721 image_buffer_t *img_dest,
1722 image_buffer_t *img_src,
1723 int x, int y, // Where on display
1724 float xr, float yr, // Pixel to rotate around
1725 float rot, // Rotation angle in degrees
1726 float scale, // Scale factor
1727 int32_t transparent_color) {
1728
1729 int src_w = img_src->width;
1730 int src_h = img_src->height;
1731 int des_w = img_dest->width;
1732 int des_h = img_dest->height;
1733
1734 int des_x_start = 0;
1735 int des_y_start = 0;
1736 int des_x_end = (des_x_start + des_w);
1737 int des_y_end = (des_y_start + des_h);
1738
1739 if (des_x_start < 0) des_x_start = 0;
1740 if (des_x_end > des_w) des_x_end = des_w;
1741 if (des_y_start < 0) des_y_start = 0;
1742 if (des_y_end > des_h) des_y_end = des_h;
1743
1744 if (rot == 0.0 && scale == 1.0) {
1745 if (x > 0) des_x_start += x;
1746 if (y > 0) des_y_start += y;
1747 if ((des_x_end - x) > src_w) des_x_end = src_w + x;
1748 if ((des_y_end - y) > src_h) des_y_end = src_h + y;
1749
1750 for (int j = des_y_start; j < des_y_end; j++) {
1751 for (int i = des_x_start; i < des_x_end; i++) {
1752 int px = i - x;
1753 int py = j - y;
1754
1755 if (px >= 0 && px < src_w && py >= 0 && py < src_h) {
1756 uint32_t p = getpixel(img_src, px, py);
1757
1758 if (p != (uint32_t) transparent_color) {
1759 putpixel(img_dest, i, j, p);
1760 }
1761 }
1762 }
1763 }
1764 } else if (rot == 0.0) {
1765 xr *= scale;
1766 yr *= scale;
1767
1768 const int fp_scale = 1000;
1769
1770 int xr_i = (int)xr;
1771 int yr_i = (int)yr;
1772 int scale_i = (int)(scale * (float) fp_scale);
1773
1774 for (int j = des_y_start; j < des_y_end; j++) {
1775 for (int i = des_x_start; i < des_x_end; i++) {
1776 int px = (i - x - xr_i) * fp_scale;
1777 int py = (j - y - yr_i) * fp_scale;
1778
1779 px += xr_i * fp_scale;
1780 py += yr_i * fp_scale;
1781
1782 px /= scale_i;
1783 py /= scale_i;
1784
1785 if (px >= 0 && px < src_w && py >= 0 && py < src_h) {
1786 uint32_t p = getpixel(img_src, px, py);
1787
1788 if (p != (uint32_t) transparent_color) {
1789 putpixel(img_dest, i, j, p);
1790 }
1791 }
1792 }
1793 }
1794 } else {
1795 float sr = sinf(-rot * (float)M_PI / 180.0f);
1796 float cr = cosf(-rot * (float)M_PI / 180.0f);
1797
1798 xr *= scale;
1799 yr *= scale;
1800
1801 const int fp_scale = 1000;
1802
1803 int sr_i = (int)(sr * (float)fp_scale);
1804 int cr_i = (int)(cr * (float)fp_scale);
1805 int xr_i = (int)xr;
1806 int yr_i = (int)yr;
1807 int scale_i = (int)(scale * (float) fp_scale);
1808
1809 for (int j = des_y_start; j < des_y_end; j++) {
1810 for (int i = des_x_start; i < des_x_end; i++) {
1811 int px = (i - x - xr_i) * cr_i + (j - y - yr_i) * sr_i;
1812 int py = -(i - x - xr_i) * sr_i + (j - y - yr_i) * cr_i;
1813
1814 px += xr_i * fp_scale;
1815 py += yr_i * fp_scale;
1816
1817 px /= scale_i;
1818 py /= scale_i;
1819
1820 if (px >= 0 && px < src_w && py >= 0 && py < src_h) {
1821 uint32_t p = getpixel(img_src, px, py);
1822
1823 if (p != (uint32_t) transparent_color) {
1824 putpixel(img_dest, i, j, p);
1825 }
1826 }
1827 }
1828 }
1829 }
1830 }
1831
1832 // Extensions
1833
1834 #define ATTR_MAX_ARGS 3
1835 #define ARG_MAX_NUM 8
1836
1837 typedef struct {
1838 bool is_valid;
1839 uint16_t arg_num;
1840 lbm_value args[ATTR_MAX_ARGS];
1841 } attr_t;
1842
1843 typedef struct {
1844 bool is_valid;
1845 image_buffer_t img;
1846 lbm_value args[ARG_MAX_NUM];
1847 attr_t attr_thickness;
1848 attr_t attr_filled;
1849 attr_t attr_rounded;
1850 attr_t attr_dotted;
1851 attr_t attr_scale;
1852 attr_t attr_rotate;
1853 attr_t attr_resolution;
1854 } img_args_t;
1855
1856 static img_args_t decode_args(lbm_value *args, lbm_uint argn, int num_expected) {
1857 img_args_t res;
1858 memset(&res, 0, sizeof(res));
1859
1860 if (!lbm_is_array_r(args[0])) {
1861 return res;
1862 }
1863 lbm_array_header_t *arr = (lbm_array_header_t*)lbm_car(args[0]);
1864
1865 if (!image_buffer_is_valid((uint8_t*)arr->data, arr->size)) {
1866 res.is_valid = false;
1867 return res;
1868 }
1869
1870 res.img.width = image_buffer_width((uint8_t*)arr->data);
1871 res.img.height = image_buffer_height((uint8_t*)arr->data);
1872 res.img.fmt = image_buffer_format((uint8_t*)arr->data);
1873 res.img.mem_base = (uint8_t*)arr->data;
1874 res.img.data = image_buffer_data((uint8_t*)arr->data);
1875
1876
1877 int num_dec = 0;
1878 for (unsigned int i = 1;i < argn;i++) {
1879 if (!lbm_is_number(args[i]) && !lbm_is_cons(args[i])) {
1880 return res;
1881 }
1882
1883 if (lbm_is_number(args[i])) {
1884 res.args[num_dec] = args[i];
1885 num_dec++;
1886
1887 if (num_dec > ARG_MAX_NUM) {
1888 return res;
1889 }
1890 } else {
1891 lbm_value curr = args[i];
1892 int attr_ind = 0;
1893 attr_t *attr_now = 0;
1894 while (lbm_is_cons(curr)) {
1895 lbm_value arg = lbm_car(curr);
1896
1897 if (attr_ind == 0) {
1898 if (!lbm_is_symbol(arg)) {
1899 return res;
1900 }
1901
1902 if (lbm_dec_sym(arg) == symbol_thickness) {
1903 attr_now = &res.attr_thickness;
1904 attr_now->arg_num = 1;
1905 } else if (lbm_dec_sym(arg) == symbol_filled) {
1906 attr_now = &res.attr_filled;
1907 attr_now->arg_num = 0;
1908 } else if (lbm_dec_sym(arg) == symbol_rounded) {
1909 attr_now = &res.attr_rounded;
1910 attr_now->arg_num = 1;
1911 } else if (lbm_dec_sym(arg) == symbol_dotted) {
1912 attr_now = &res.attr_dotted;
1913 attr_now->arg_num = 2;
1914 } else if (lbm_dec_sym(arg) == symbol_scale) {
1915 attr_now = &res.attr_scale;
1916 attr_now->arg_num = 1;
1917 } else if (lbm_dec_sym(arg) == symbol_rotate) {
1918 attr_now = &res.attr_rotate;
1919 attr_now->arg_num = 3;
1920 } else if (lbm_dec_sym(arg) == symbol_resolution) {
1921 attr_now = &res.attr_resolution;
1922 attr_now->arg_num = 1;
1923 } else {
1924 return res;
1925 }
1926 } else {
1927 if (!lbm_is_number(arg)) {
1928 return res;
1929 }
1930
1931 attr_now->args[attr_ind - 1] = arg;
1932 }
1933
1934 attr_ind++;
1935 if (attr_ind > (ATTR_MAX_ARGS + 1)) {
1936 return res;
1937 }
1938
1939 curr = lbm_cdr(curr);
1940 }
1941
1942 // does this really compare the pointer addresses?
1943 if (attr_now == &res.attr_rounded && attr_ind == 1) {
1944 attr_now->arg_num = 0; // the `rounded` attribute may be empty
1945 }
1946
1947
1948 if ((attr_ind - 1) == attr_now->arg_num) {
1949 attr_now->is_valid = true;
1950 } else {
1951 return res;
1952 }
1953 }
1954 }
1955
1956 if (num_dec != num_expected) {
1957 return res;
1958 }
1959
1960 res.is_valid = true;
1961 return res;
1962 }
1963
1964 static lbm_value ext_image_dims(lbm_value *args, lbm_uint argn) {
1965 img_args_t arg_dec = decode_args(args, argn, 0);
1966
1967 if (!arg_dec.is_valid) {
1968 return ENC_SYM_TERROR;
1969 }
1970
1971 lbm_value dims = lbm_heap_allocate_list(2);
1972 if (lbm_is_symbol(dims)) {
1973 return dims;
1974 }
1975 lbm_value curr = dims;
1976 lbm_set_car(curr, lbm_enc_i(arg_dec.img.width));
1977 curr = lbm_cdr(curr);
1978 lbm_set_car(curr, lbm_enc_i(arg_dec.img.height));
1979 return dims;
1980 }
1981
1982 static lbm_value ext_image_buffer(lbm_value *args, lbm_uint argn) {
1983 lbm_value res = ENC_SYM_TERROR;
1984 bool args_ok = false;
1985 color_format_t fmt = indexed2;
1986 lbm_uint w = 0;
1987 lbm_uint h = 0;
1988
1989 if (argn == 4 &&
1990 lbm_is_defrag_mem(args[0]) &&
1991 lbm_is_symbol(args[1]) &&
1992 lbm_is_number(args[2]) &&
1993 lbm_is_number(args[3])) {
1994 fmt = sym_to_color_format(args[1]);
1995 w = lbm_dec_as_u32(args[2]);
1996 h = lbm_dec_as_u32(args[3]);
1997 args_ok = true;
1998 } else if (argn == 3 &&
1999 lbm_is_symbol(args[0]) &&
2000 lbm_is_number(args[1]) &&
2001 lbm_is_number(args[2])) {
2002 fmt = sym_to_color_format(args[0]);
2003 w = lbm_dec_as_u32(args[1]);
2004 h = lbm_dec_as_u32(args[2]);
2005 args_ok = true;
2006 }
2007
2008 if (args_ok && fmt != format_not_supported && w > 0 && h > 0 && w < MAX_WIDTH && h < MAX_HEIGHT) {
2009 if (argn == 3) {
2010 res = image_buffer_allocate(fmt, (uint16_t)w, (uint16_t)h);
2011 } else {
2012 res = image_buffer_allocate_dm((lbm_uint*)lbm_car(args[0]), fmt, (uint16_t)w, (uint16_t)h);
2013 }
2014 }
2015 return res;
2016 }
2017
2018
2019 static lbm_value ext_is_image_buffer(lbm_value *args, lbm_uint argn) {
2020 lbm_value res = ENC_SYM_TERROR;
2021
2022 if (argn == 1) {
2023 res = ENC_SYM_NIL;
2024 if (lbm_is_array_r(args[0])) {
2025 lbm_value arr = args[0];
2026 lbm_array_header_t *array = (lbm_array_header_t *)lbm_car(arr);
2027 uint8_t *data = (uint8_t*)array->data;
2028 if (image_buffer_is_valid(data, array->size)) {
2029 res = ENC_SYM_TRUE;;
2030 }
2031 }
2032 }
2033 return res;
2034 }
2035
2036 static lbm_value ext_color(lbm_value *args, lbm_uint argn) {
2037 lbm_value res = ENC_SYM_TERROR;
2038
2039 if (argn >= 2 && argn <= 6 &&
2040 lbm_is_symbol(args[0]) &&
2041 lbm_is_number(args[1])) {
2042
2043 // Color1 and color2 are int in the struct and decoded as i32, why
2044 // where they stored in uint32_t?
2045 int32_t color1 = lbm_dec_as_i32(args[1]);
2046
2047 int32_t color2 = 0;
2048 if (argn >= 3) {
2049 if (lbm_is_number(args[2])) {
2050 color2 = lbm_dec_as_i32(args[2]);
2051 } else {
2052 return ENC_SYM_TERROR;
2053 }
2054 }
2055
2056 int32_t param1 = 0;
2057 if (argn >= 4) {
2058 if (lbm_is_number(args[3])) {
2059 param1 = lbm_dec_as_i32(args[3]);
2060 } else {
2061 return ENC_SYM_TERROR;
2062 }
2063 }
2064
2065 int32_t param2 = 0;
2066 if (argn >= 5) {
2067 if (lbm_is_number(args[4])) {
2068 param2 = lbm_dec_as_i32(args[4]);
2069 } else {
2070 return ENC_SYM_TERROR;
2071 }
2072 }
2073
2074 bool mirrored = false;
2075 if (argn >= 6) {
2076 if (lbm_is_symbol(args[5])) {
2077 lbm_uint sym = lbm_dec_sym(args[5]);
2078 if (sym == symbol_repeat) {
2079 mirrored = false;
2080 } else if (sym == symbol_mirrored) {
2081 mirrored = true;
2082 } else {
2083 return ENC_SYM_TERROR;
2084 }
2085 } else {
2086 return ENC_SYM_TERROR;
2087 }
2088 }
2089
2090 COLOR_TYPE t;
2091 if (lbm_dec_sym(args[0]) == symbol_regular) {
2092 t = COLOR_REGULAR;
2093 } else if (lbm_dec_sym(args[0]) == symbol_gradient_x) {
2094 t = COLOR_GRADIENT_X;
2095 } else if (lbm_dec_sym(args[0]) == symbol_gradient_y) {
2096 t = COLOR_GRADIENT_Y;
2097 } else if (lbm_dec_sym(args[0]) == symbol_gradient_x_pre) {
2098 t = COLOR_PRE_X;
2099 } else if (lbm_dec_sym(args[0]) == symbol_gradient_y_pre) {
2100 t = COLOR_PRE_Y;
2101 } else {
2102 return ENC_SYM_TERROR;
2103 }
2104
2105 // Maybe check if param is in ranges first ?
2106 res = color_allocate(t, color1, color2, (uint16_t)param1, (uint16_t)param2, mirrored);
2107 }
2108
2109 return res;
2110 }
2111
2112 static lbm_value ext_color_set(lbm_value *args, lbm_uint argn) {
2113 if (argn != 3 || !display_is_color(args[0]) ||
2114 !lbm_is_symbol(args[1])) {
2115 return ENC_SYM_TERROR;
2116 }
2117
2118 color_t *color = (color_t*)lbm_get_custom_value(args[0]);
2119
2120 bool is_regular = color->type == COLOR_REGULAR;
2121 bool is_gradient = color->type == COLOR_GRADIENT_X || color->type == COLOR_GRADIENT_Y;
2122 bool is_pre = color->type == COLOR_PRE_X || color->type == COLOR_PRE_Y;
2123
2124 lbm_uint prop = lbm_dec_sym(args[1]);
2125 if (prop == symbol_color_0) {
2126 if (!lbm_is_number(args[2]) || !(is_regular || is_gradient)) {
2127 return ENC_SYM_TERROR;
2128 }
2129 color->color1 = lbm_dec_as_i32(args[2]);
2130 } else if (prop == symbol_color_1) {
2131 if (!lbm_is_number(args[2]) || !is_gradient) {
2132 return ENC_SYM_TERROR;
2133 }
2134 color->color2 = lbm_dec_as_i32(args[2]);
2135 } else if (prop == symbol_width) {
2136 if (!lbm_is_number(args[2]) || !is_gradient) {
2137 return ENC_SYM_TERROR;
2138 }
2139 color->param1 = (uint16_t)lbm_dec_as_u32(args[2]);
2140 } else if (prop == symbol_offset) {
2141 if (!lbm_is_number(args[2]) || !(is_gradient || is_pre)) {
2142 return ENC_SYM_TERROR;
2143 }
2144 color->param2 = (uint16_t)lbm_dec_as_u32(args[2]);
2145 } else if (prop == symbol_repeat_type) {
2146 if (!lbm_is_symbol(args[2]) || !(is_gradient || is_pre)) {
2147 return ENC_SYM_TERROR;
2148 }
2149 lbm_uint sym = lbm_dec_sym(args[2]);
2150 if (sym == symbol_repeat) {
2151 color->mirrored = false;
2152 } else if (sym == symbol_mirrored) {
2153 color->mirrored = true;
2154 } else {
2155 return ENC_SYM_TERROR;
2156 }
2157 } else {
2158 return ENC_SYM_TERROR;
2159 }
2160
2161 return ENC_SYM_TRUE;
2162 }
2163
2164 static lbm_value ext_color_get(lbm_value *args, lbm_uint argn) {
2165 if (argn != 2 || !display_is_color(args[0]) ||
2166 !lbm_is_symbol(args[1])) {
2167 return ENC_SYM_TERROR;
2168 }
2169
2170 color_t *color = (color_t*)lbm_get_custom_value(args[0]);
2171
2172 bool is_gradient = color->type == COLOR_GRADIENT_X || color->type == COLOR_GRADIENT_Y;
2173 bool is_pre = color->type == COLOR_PRE_X || color->type == COLOR_PRE_Y;
2174
2175 lbm_uint prop = lbm_dec_sym(args[1]);
2176 if (prop == symbol_color_0) {
2177 // always allowed
2178 return lbm_enc_u32((uint32_t)color->color1);
2179 } else if (prop == symbol_color_1) {
2180 if (!is_gradient && !is_pre) {
2181 return ENC_SYM_TERROR;
2182 }
2183 return lbm_enc_u32((uint32_t)color->color2);
2184 } else if (prop == symbol_width) {
2185 if (!is_gradient && !is_pre) {
2186 return ENC_SYM_TERROR;
2187 }
2188 return lbm_enc_i32((int32_t)color->param1);
2189 } else if (prop == symbol_offset) {
2190 if (!is_gradient && !is_pre) {
2191 return ENC_SYM_TERROR;
2192 }
2193 return lbm_enc_i32((int32_t)color->param2);
2194 } else if (prop == symbol_repeat_type) {
2195 if (!is_gradient && !is_pre) {
2196 return ENC_SYM_TERROR;
2197 }
2198 return lbm_enc_sym(color->mirrored ? symbol_mirrored : symbol_repeat);
2199 } else {
2200 return ENC_SYM_TERROR;
2201 }
2202
2203 return ENC_SYM_TRUE;
2204 }
2205
2206 static lbm_value ext_color_setpre(lbm_value *args, lbm_uint argn) {
2207 if (argn != 3 || !display_is_color(args[0]) ||
2208 !lbm_is_number(args[1]) || !lbm_is_number(args[2])) {
2209 return ENC_SYM_TERROR;
2210 }
2211
2212 color_t *color = (color_t*)lbm_get_custom_value(args[0]);
2213
2214 uint32_t pos = lbm_dec_as_u32(args[1]);
2215 int new_color = lbm_dec_as_i32(args[2]);
2216
2217 if (color->precalc == 0 || pos >= COLOR_PRECALC_LEN) {
2218 return ENC_SYM_EERROR;
2219 }
2220
2221 color->precalc[pos] = (uint32_t)new_color;
2222
2223 return ENC_SYM_TRUE;
2224 }
2225
2226 static lbm_value ext_color_getpre(lbm_value *args, lbm_uint argn) {
2227 if (argn != 2 || !display_is_color(args[0]) ||
2228 !lbm_is_number(args[1])) {
2229 return ENC_SYM_TERROR;
2230 }
2231
2232 color_t *color = (color_t*)lbm_get_custom_value(args[0]);
2233
2234 uint32_t pos = lbm_dec_as_u32(args[1]);
2235
2236 if (color->precalc == 0 || pos >= COLOR_PRECALC_LEN) {
2237 return ENC_SYM_EERROR;
2238 }
2239
2240 return lbm_enc_u32(color->precalc[pos]);
2241 }
2242
2243 static lbm_value ext_clear(lbm_value *args, lbm_uint argn) {
2244 if ((argn != 1 && argn != 2) ||
2245 !array_is_image_buffer(args[0]) ||
2246 (argn == 2 && !lbm_is_number(args[1]))) {
2247 return ENC_SYM_TERROR;
2248 }
2249
2250 lbm_array_header_t *arr = (lbm_array_header_t *)lbm_car(args[0]);
2251 image_buffer_t img_buf;
2252 img_buf.width = image_buffer_width((uint8_t*)arr->data);
2253 img_buf.height = image_buffer_height((uint8_t*)arr->data);
2254 img_buf.fmt = image_buffer_format((uint8_t*)arr->data);
2255 img_buf.mem_base = (uint8_t*)arr->data;
2256 img_buf.data = image_buffer_data((uint8_t*)arr->data);
2257
2258 uint32_t color = 0;
2259 if (argn == 2) {
2260 color = lbm_dec_as_u32(args[1]);
2261 }
2262
2263 image_buffer_clear(&img_buf, color);
2264
2265 return ENC_SYM_TRUE;
2266 }
2267
2268 static lbm_value ext_putpixel(lbm_value *args, lbm_uint argn) {
2269 img_args_t arg_dec = decode_args(args, argn, 3);
2270
2271 if (!arg_dec.is_valid) {
2272 return ENC_SYM_TERROR;
2273 }
2274
2275 putpixel(&arg_dec.img,
2276 lbm_dec_as_i32(arg_dec.args[0]),
2277 lbm_dec_as_i32(arg_dec.args[1]),
2278 lbm_dec_as_u32(arg_dec.args[2]));
2279 return ENC_SYM_TRUE;
2280 }
2281
2282 // lisp args: img x1 y1 x2 y2 color opt-attr1 ... opt-attrN
2283 static lbm_value ext_line(lbm_value *args, lbm_uint argn) {
2284 img_args_t arg_dec = decode_args(args, argn, 5);
2285
2286 if (!arg_dec.is_valid) {
2287 return ENC_SYM_TERROR;
2288 }
2289
2290 line(&arg_dec.img,
2291 lbm_dec_as_i32(arg_dec.args[0]),
2292 lbm_dec_as_i32(arg_dec.args[1]),
2293 lbm_dec_as_i32(arg_dec.args[2]),
2294 lbm_dec_as_i32(arg_dec.args[3]),
2295 lbm_dec_as_i32(arg_dec.attr_thickness.args[0]),
2296 lbm_dec_as_i32(arg_dec.attr_dotted.args[0]),
2297 lbm_dec_as_i32(arg_dec.attr_dotted.args[1]),
2298 lbm_dec_as_u32(arg_dec.args[4]));
2299
2300 return ENC_SYM_TRUE;
2301 }
2302
2303 // lisp args: img cx cy r color opt-attr1 ... opt-attrN
2304 static lbm_value ext_circle(lbm_value *args, lbm_uint argn) {
2305 img_args_t arg_dec = decode_args(args, argn, 4);
2306
2307 if (!arg_dec.is_valid) {
2308 return ENC_SYM_TERROR;
2309 }
2310
2311 if (arg_dec.attr_filled.is_valid) {
2312 fill_circle(&arg_dec.img,
2313 lbm_dec_as_i32(arg_dec.args[0]),
2314 lbm_dec_as_i32(arg_dec.args[1]),
2315 lbm_dec_as_i32(arg_dec.args[2]),
2316 lbm_dec_as_u32(arg_dec.args[3]));
2317 } if (arg_dec.attr_dotted.is_valid) {
2318 arc(&arg_dec.img,
2319 lbm_dec_as_i32(arg_dec.args[0]),
2320 lbm_dec_as_i32(arg_dec.args[1]),
2321 lbm_dec_as_i32(arg_dec.args[2]),
2322 0, 359.9f,
2323 lbm_dec_as_i32(arg_dec.attr_thickness.args[0]),
2324 arg_dec.attr_rounded.is_valid, // currently does nothing as the line function doesn't support square ends.
2325 false,
2326 false, false,
2327 lbm_dec_as_i32(arg_dec.attr_dotted.args[0]),
2328 lbm_dec_as_i32(arg_dec.attr_dotted.args[1]),
2329 lbm_dec_as_i32(arg_dec.attr_resolution.args[0]),
2330 lbm_dec_as_u32(arg_dec.args[3]));
2331 } else {
2332 circle(&arg_dec.img,
2333 lbm_dec_as_i32(arg_dec.args[0]),
2334 lbm_dec_as_i32(arg_dec.args[1]),
2335 lbm_dec_as_i32(arg_dec.args[2]),
2336 lbm_dec_as_i32(arg_dec.attr_thickness.args[0]),
2337 lbm_dec_as_u32(arg_dec.args[3]));
2338 }
2339
2340 return ENC_SYM_TRUE;
2341 }
2342
2343 // lisp args: img cx cy r ang-s ang-e color opt-attr1 ... opt-attrN
2344 static lbm_value ext_arc(lbm_value *args, lbm_uint argn) {
2345 img_args_t arg_dec = decode_args(args, argn, 6);
2346
2347 if (!arg_dec.is_valid) {
2348 return ENC_SYM_TERROR;
2349 }
2350
2351 arc(&arg_dec.img,
2352 lbm_dec_as_i32(arg_dec.args[0]),
2353 lbm_dec_as_i32(arg_dec.args[1]),
2354 lbm_dec_as_i32(arg_dec.args[2]),
2355 lbm_dec_as_float(arg_dec.args[3]),
2356 lbm_dec_as_float(arg_dec.args[4]),
2357 lbm_dec_as_i32(arg_dec.attr_thickness.args[0]),
2358 arg_dec.attr_rounded.is_valid,
2359 arg_dec.attr_filled.is_valid,
2360 false, false,
2361 lbm_dec_as_i32(arg_dec.attr_dotted.args[0]),
2362 lbm_dec_as_i32(arg_dec.attr_dotted.args[1]),
2363 lbm_dec_as_i32(arg_dec.attr_resolution.args[0]),
2364 lbm_dec_as_u32(arg_dec.args[5]));
2365
2366 return ENC_SYM_TRUE;
2367 }
2368
2369 // lisp args: img cx cy r ang-s ang-e color opt-attr1 ... opt-attrN
2370 static lbm_value ext_circle_sector(lbm_value *args, lbm_uint argn) {
2371 img_args_t arg_dec = decode_args(args, argn, 6);
2372
2373 if (!arg_dec.is_valid) {
2374 return ENC_SYM_TERROR;
2375 }
2376
2377 arc(&arg_dec.img,
2378 lbm_dec_as_i32(arg_dec.args[0]),
2379 lbm_dec_as_i32(arg_dec.args[1]),
2380 lbm_dec_as_i32(arg_dec.args[2]),
2381 lbm_dec_as_float(arg_dec.args[3]),
2382 lbm_dec_as_float(arg_dec.args[4]),
2383 lbm_dec_as_i32(arg_dec.attr_thickness.args[0]),
2384 true,
2385 arg_dec.attr_filled.is_valid,
2386 true, false,
2387 lbm_dec_as_i32(arg_dec.attr_dotted.args[0]),
2388 lbm_dec_as_i32(arg_dec.attr_dotted.args[1]),
2389 lbm_dec_as_i32(arg_dec.attr_resolution.args[0]),
2390 lbm_dec_as_u32(arg_dec.args[5]));
2391
2392 return ENC_SYM_TRUE;
2393 }
2394
2395 // lisp args: img cx cy r ang-s ang-e color opt-attr1 ... opt-attrN
2396 static lbm_value ext_circle_segment(lbm_value *args, lbm_uint argn) {
2397 img_args_t arg_dec = decode_args(args, argn, 6);
2398
2399 if (!arg_dec.is_valid) {
2400 return ENC_SYM_TERROR;
2401 }
2402
2403 arc(&arg_dec.img,
2404 lbm_dec_as_i32(arg_dec.args[0]),
2405 lbm_dec_as_i32(arg_dec.args[1]),
2406 lbm_dec_as_i32(arg_dec.args[2]),
2407 lbm_dec_as_float(arg_dec.args[3]),
2408 lbm_dec_as_float(arg_dec.args[4]),
2409 lbm_dec_as_i32(arg_dec.attr_thickness.args[0]),
2410 true,
2411 arg_dec.attr_filled.is_valid,
2412 false, true,
2413 lbm_dec_as_i32(arg_dec.attr_dotted.args[0]),
2414 lbm_dec_as_i32(arg_dec.attr_dotted.args[1]),
2415 lbm_dec_as_i32(arg_dec.attr_resolution.args[0]),
2416 lbm_dec_as_u32(arg_dec.args[5]));
2417
2418
2419 return ENC_SYM_TRUE;
2420 }
2421
2422 // lisp args: img x y width height color opt-attr1 ... opt-attrN
2423 static lbm_value ext_rectangle(lbm_value *args, lbm_uint argn) {
2424 img_args_t arg_dec = decode_args(args, argn, 5);
2425
2426 if (!arg_dec.is_valid) {
2427 return ENC_SYM_TERROR;
2428 }
2429
2430 image_buffer_t *img = &arg_dec.img;
2431 int x = lbm_dec_as_i32(arg_dec.args[0]);
2432 int y = lbm_dec_as_i32(arg_dec.args[1]);
2433 int width = lbm_dec_as_i32(arg_dec.args[2]);
2434 int height = lbm_dec_as_i32(arg_dec.args[3]);
2435 int rad = lbm_dec_as_i32(arg_dec.attr_rounded.args[0]);
2436 int thickness = lbm_dec_as_i32(arg_dec.attr_thickness.args[0]);
2437 uint32_t color = lbm_dec_as_u32(arg_dec.args[4]);
2438 int dot1 = lbm_dec_as_i32(arg_dec.attr_dotted.args[0]);
2439 int dot2 = lbm_dec_as_i32(arg_dec.attr_dotted.args[1]);
2440 int resolution = lbm_dec_as_i32(arg_dec.attr_resolution.args[0]);
2441
2442 if (arg_dec.attr_rounded.is_valid) {
2443 if (arg_dec.attr_filled.is_valid) {
2444 rectangle(img, x + rad, y, width - 2 * rad, rad, 1, 1, 0, 0, color);
2445 rectangle(img, x + rad, y + height - rad, width - 2 * rad, rad, 1, 1, 0, 0, color);
2446 rectangle(img, x, y + rad, width, height - 2 * rad, 1, 1, 0, 0, color);
2447 fill_circle(img, x + rad, y + rad, rad, color);
2448 fill_circle(img, x + rad, y + height - rad, rad, color);
2449 fill_circle(img, x + width - rad, y + rad, rad, color);
2450 fill_circle(img, x + width - rad, y + height - rad, rad, color);
2451 } else {
2452 // Remember to change these to use the rounded attribute,
2453 // when/if line supports it!
2454
2455 int line_thickness = thickness / 2;
2456 thickness = line_thickness * 2; // round it to even for consistency.
2457
2458 // top
2459 line(img, x + rad, y + line_thickness, x + width - rad, y + line_thickness, line_thickness, dot1, dot2, color);
2460 // bottom
2461 line(img, x + rad, y + height - line_thickness, x + width - rad, y + height - line_thickness, line_thickness, dot1, dot2, color);
2462 // left
2463 line(img, x + line_thickness, y + rad, x + line_thickness, y + height - rad, line_thickness, dot1, dot2, color);
2464 // right
2465 line(img, x + width - line_thickness, y + rad, x + width - line_thickness, y + height - rad, line_thickness, dot1, dot2, color);
2466
2467 // upper left
2468 arc(img, x + rad, y + rad, rad, 180, 270, thickness, false, false, false, false, dot1, dot2, resolution, color);
2469 // upper right
2470 arc(img, x + width - rad, y + rad, rad, 270, 0, thickness, false, false, false, false, dot1, dot2, resolution, color);
2471 // bottom left
2472 arc(img, x + rad, y + height - rad, rad, 90, 180, thickness, false, false, false, false, dot1, dot2, resolution, color);
2473 // bottom right
2474 arc(img, x + width - rad, y + height - rad, rad, 0, 90, thickness, false, false, false, false, dot1, dot2, resolution, color);
2475 }
2476 } else {
2477 rectangle(img,
2478 x, y,
2479 width, height,
2480 arg_dec.attr_filled.is_valid,
2481 thickness,
2482 dot1, dot2,
2483 color);
2484 }
2485
2486 return ENC_SYM_TRUE;
2487 }
2488
2489 // lisp args: img x1 y1 x2 y2 x3 y3 color opt-attr1 ... opt-attrN
2490 static lbm_value ext_triangle(lbm_value *args, lbm_uint argn) {
2491 img_args_t arg_dec = decode_args(args, argn, 7);
2492
2493 if (!arg_dec.is_valid) {
2494 return ENC_SYM_TERROR;
2495 }
2496
2497 image_buffer_t *img = &arg_dec.img;
2498 int x0 = lbm_dec_as_i32(arg_dec.args[0]);
2499 int y0 = lbm_dec_as_i32(arg_dec.args[1]);
2500 int x1 = lbm_dec_as_i32(arg_dec.args[2]);
2501 int y1 = lbm_dec_as_i32(arg_dec.args[3]);
2502 int x2 = lbm_dec_as_i32(arg_dec.args[4]);
2503 int y2 = lbm_dec_as_i32(arg_dec.args[5]);
2504 int thickness = lbm_dec_as_i32(arg_dec.attr_thickness.args[0]);
2505 int dot1 = lbm_dec_as_i32(arg_dec.attr_dotted.args[0]);
2506 int dot2 = lbm_dec_as_i32(arg_dec.attr_dotted.args[1]);
2507 uint32_t color = lbm_dec_as_u32(arg_dec.args[6]);
2508
2509 if (arg_dec.attr_filled.is_valid) {
2510 fill_triangle(img, x0, y0, x1, y1, x2, y2, color);
2511 } else {
2512 line(img, x0, y0, x1, y1, thickness, dot1, dot2, color);
2513 line(img, x1, y1, x2, y2, thickness, dot1, dot2, color);
2514 line(img, x2, y2, x0, y0, thickness, dot1, dot2, color);
2515 }
2516
2517 return ENC_SYM_TRUE;
2518 }
2519
2520 // lisp args: img x y fg bg font str
2521 static lbm_value ext_text(lbm_value *args, lbm_uint argn) {
2522 bool up = false;
2523 bool down = false;
2524
2525 if (argn >= 7 && lbm_is_symbol(args[argn - 1])) {
2526 if (lbm_dec_sym(args[argn - 1]) == symbol_up) {
2527 up = true;
2528 argn--;
2529 }
2530
2531 if (lbm_dec_sym(args[argn - 1]) == symbol_down) {
2532 down = true;
2533 argn--;
2534 }
2535 }
2536
2537 if (argn != 6 && argn != 7) {
2538 return ENC_SYM_TERROR;
2539 }
2540
2541 int x = lbm_dec_as_i32(args[1]);
2542 int y = lbm_dec_as_i32(args[2]);
2543
2544 int32_t colors[4] = {-1, -1, -1, -1}; // how big? int vs int32
2545 if (argn == 7) {
2546 if (!lbm_is_number(args[3]) || !lbm_is_number(args[4])) {
2547 return ENC_SYM_TERROR;
2548 }
2549 colors[0] = lbm_dec_as_i32(args[3]);
2550 colors[1] = lbm_dec_as_i32(args[4]);
2551 } else {
2552 lbm_value curr = args[3];
2553 int ind = 0;
2554 while (lbm_is_cons(curr)) {
2555 lbm_value arg = lbm_car(curr);
2556 if (lbm_is_number(arg)) {
2557 colors[ind++] = lbm_dec_as_i32(arg);
2558 } else {
2559 return ENC_SYM_TERROR;
2560 }
2561
2562 if (ind == 4) {
2563 break;
2564 }
2565
2566 curr = lbm_cdr(curr);
2567 }
2568 }
2569
2570 if (!array_is_image_buffer(args[0])) {
2571 return ENC_SYM_TERROR;
2572 }
2573 lbm_array_header_t *arr = (lbm_array_header_t *)lbm_car(args[0]);
2574 image_buffer_t img_buf;
2575 img_buf.width = image_buffer_width((uint8_t*)arr->data);
2576 img_buf.height = image_buffer_height((uint8_t*)arr->data);
2577 img_buf.fmt = image_buffer_format((uint8_t*)arr->data);
2578 img_buf.mem_base = (uint8_t*)arr->data;
2579 img_buf.data = image_buffer_data((uint8_t*)arr->data);
2580
2581 lbm_array_header_t *font = 0;
2582 if (lbm_type_of(args[5]) == LBM_TYPE_ARRAY) {
2583 font = (lbm_array_header_t *)lbm_car(args[argn - 2]);
2584 }
2585
2586 char *txt = lbm_dec_str(args[argn - 1]);
2587
2588 if (!font || !txt || font->size < (4 + 5 * 5 * 10)) {
2589 return ENC_SYM_TERROR;
2590 }
2591
2592 uint8_t *font_data = (uint8_t*)font->data;
2593 uint8_t w = font_data[0];
2594 uint8_t h = font_data[1];
2595
2596 int incx = 1;
2597 int incy = 0;
2598 if (up) {
2599 incx = 0;
2600 incy = -1;
2601 } else if (down) {
2602 incx = 0;
2603 incy = 1;
2604 }
2605
2606 int ind = 0;
2607 while (txt[ind] != 0) {
2608 img_putc(&img_buf, x + ind * w * incx, y + ind * h * incy,
2609 (uint32_t *)colors, 4, font_data, (uint8_t)txt[ind], up, down);
2610 ind++;
2611 }
2612
2613 return ENC_SYM_TRUE;
2614 }
2615
2616 static lbm_value ext_blit(lbm_value *args, lbm_uint argn) {
2617 img_args_t arg_dec = decode_args(args + 1, argn - 1, 3);
2618
2619 if (!arg_dec.is_valid) {
2620 return ENC_SYM_TERROR;
2621 }
2622
2623 if (!array_is_image_buffer(args[0])) {
2624 return ENC_SYM_TERROR;
2625 }
2626 lbm_array_header_t *arr = (lbm_array_header_t *)lbm_car(args[0]);
2627 image_buffer_t dest_buf;
2628 dest_buf.width = image_buffer_width((uint8_t*)arr->data);
2629 dest_buf.height = image_buffer_height((uint8_t*)arr->data);
2630 dest_buf.fmt = image_buffer_format((uint8_t*)arr->data);
2631 dest_buf.mem_base = (uint8_t*)arr->data;
2632 dest_buf.data = image_buffer_data((uint8_t*)arr->data);
2633
2634 float scale = 1.0;
2635 if (arg_dec.attr_scale.is_valid) {
2636 scale = lbm_dec_as_float(arg_dec.attr_scale.args[0]);
2637 }
2638
2639 blit_rot_scale(
2640 &dest_buf,
2641 &arg_dec.img,
2642 lbm_dec_as_i32(arg_dec.args[0]),
2643 lbm_dec_as_i32(arg_dec.args[1]),
2644 lbm_dec_as_float(arg_dec.attr_rotate.args[0]),
2645 lbm_dec_as_float(arg_dec.attr_rotate.args[1]),
2646 lbm_dec_as_float(arg_dec.attr_rotate.args[2]),
2647 scale,
2648 lbm_dec_as_i32(arg_dec.args[2]));
2649
2650 return ENC_SYM_TRUE;
2651 }
2652
2653 void display_dummy_reset(void) {
2654 return;
2655 }
2656
2657 void display_dummy_clear(uint32_t color) {
2658 (void) color;
2659 return;
2660 }
2661
2662 bool display_dummy_render_image(image_buffer_t *img, uint16_t x, uint16_t y, color_t *colors) {
2663 (void) img;
2664 (void) x;
2665 (void) y;
2666 (void) colors;
2667 return false;
2668 }
2669
2670 static bool(* volatile disp_render_image)(image_buffer_t *img, uint16_t x, uint16_t y, color_t *colors) = display_dummy_render_image;
2671 static void(* volatile disp_clear)(uint32_t color) = display_dummy_clear;
2672 static void(* volatile disp_reset)(void) = display_dummy_reset;
2673
2674 static char *msg_not_supported = "Command not supported or display driver not initialized";
2675
2676 static lbm_value ext_disp_reset(lbm_value *args, lbm_uint argn) {
2677 (void) args;
2678 (void) argn;
2679
2680 if (disp_reset == NULL) {
2681 lbm_set_error_reason(msg_not_supported);
2682 return ENC_SYM_EERROR;
2683 }
2684
2685 disp_reset();
2686
2687 return ENC_SYM_TRUE;
2688 }
2689
2690 static lbm_value ext_disp_clear(lbm_value *args, lbm_uint argn) {
2691 if (disp_clear == NULL) {
2692 lbm_set_error_reason(msg_not_supported);
2693 return ENC_SYM_EERROR;
2694 }
2695
2696 if (argn > 1) {
2697 return ENC_SYM_TERROR;
2698 }
2699
2700 uint32_t clear_color = 0;
2701
2702 if (argn == 1) {
2703 if (!lbm_is_number(args[0])) {
2704 return ENC_SYM_TERROR;
2705 }
2706
2707 clear_color = lbm_dec_as_u32(args[0]);
2708 }
2709
2710 disp_clear(clear_color);
2711
2712 return ENC_SYM_TRUE;
2713 }
2714
2715 static lbm_value ext_disp_render(lbm_value *args, lbm_uint argn) {
2716 if (disp_render_image == NULL) {
2717 lbm_set_error_reason(msg_not_supported);
2718 return ENC_SYM_EERROR;
2719 }
2720
2721 if ((argn != 3 && argn != 4) ||
2722 !array_is_image_buffer(args[0]) ||
2723 !lbm_is_number(args[1]) ||
2724 !lbm_is_number(args[2])) {
2725 return ENC_SYM_TERROR;
2726 }
2727
2728 lbm_array_header_t *arr = (lbm_array_header_t *)lbm_car(args[0]);
2729
2730 image_buffer_t img_buf;
2731 img_buf.fmt = image_buffer_format((uint8_t*)arr->data);
2732 img_buf.width = image_buffer_width((uint8_t*)arr->data);
2733 img_buf.height = image_buffer_height((uint8_t*)arr->data);
2734 img_buf.mem_base = (uint8_t*)arr->data;
2735 img_buf.data = image_buffer_data((uint8_t*)arr->data);
2736
2737 color_t colors[16];
2738 memset(colors, 0, sizeof(color_t) * 16);
2739
2740 if (argn == 4 && lbm_is_list(args[3])) {
2741 int i = 0;
2742 lbm_value curr = args[3];
2743 while (lbm_is_cons(curr) && i < 16) {
2744 lbm_value arg = lbm_car(curr);
2745
2746 if (lbm_is_number(arg)) {
2747 colors[i].color1 = (int)lbm_dec_as_u32(arg);
2748 } else if (display_is_color(arg)) {
2749 colors[i] = *((color_t*)lbm_get_custom_value(arg));
2750 } else {
2751 return ENC_SYM_TERROR;
2752 }
2753
2754 curr = lbm_cdr(curr);
2755 i++;
2756 }
2757 }
2758
2759 // img_buf is a stack allocated image_buffer_t.
2760 bool render_res = disp_render_image(&img_buf, (uint16_t)lbm_dec_as_u32(args[1]), (uint16_t)lbm_dec_as_u32(args[2]), colors);
2761
2762 if (!render_res) {
2763 lbm_set_error_reason("Could not render image. Check if the format and location is compatible with the display.");
2764 return ENC_SYM_EERROR;
2765 }
2766
2767 return ENC_SYM_TRUE;
2768 }
2769
2770 // Jpg decoder
2771
2772 typedef struct {
2773 uint8_t *data;
2774 int pos;
2775 int size;
2776 int ofs_x;
2777 int ofs_y;
2778 } jpg_bufdef;
2779
2780 size_t jpg_input_func (JDEC* jd, uint8_t* buff, size_t ndata) {
2781 jpg_bufdef *dev = (jpg_bufdef*)jd->device;
2782
2783 if ((int)ndata > (dev->size - dev->pos)) {
2784 ndata = (size_t)(dev->size - dev->pos);
2785 }
2786
2787 if (buff) {
2788 memcpy(buff, dev->data + dev->pos, ndata);
2789 }
2790 dev->pos += (int)ndata;
2791 return ndata;
2792 }
2793
2794 int jpg_output_func ( /* 1:Ok, 0:Aborted */
2795 JDEC* jd, /* Decompression object */
2796 void* bitmap, /* Bitmap data to be output */
2797 JRECT* rect /* Rectangular region to output */
2798 ) {
2799 jpg_bufdef *dev = (jpg_bufdef*)jd->device;
2800
2801 image_buffer_t img;
2802 img.mem_base = (uint8_t*)bitmap;
2803 img.data = (uint8_t*)bitmap;
2804 img.width = (uint16_t)(rect->right - rect->left + 1);
2805 img.height = (uint16_t)(rect->bottom - rect->top + 1);
2806 img.fmt = rgb888;
2807
2808 disp_render_image(&img, (uint16_t)(rect->left + dev->ofs_x), (uint16_t)(rect->top + dev->ofs_y), 0);
2809
2810 return 1;
2811 }
2812
2813 static lbm_value ext_disp_render_jpg(lbm_value *args, lbm_uint argn) {
2814
2815 if (argn != 3 ||
2816 !lbm_is_array_r(args[0]) ||
2817 !lbm_is_number(args[1]) ||
2818 !lbm_is_number(args[2])) {
2819 return ENC_SYM_TERROR;
2820 }
2821
2822 JDEC jd;
2823 void *jdwork;
2824 // make a bit of room before the buffer.
2825 const size_t sz_work = 4096 + IMAGE_BUFFER_HEADER_SIZE;
2826
2827 jdwork = lbm_malloc(sz_work);
2828 if (!jdwork) {
2829 return ENC_SYM_MERROR;
2830 }
2831
2832 lbm_array_header_t *array = (lbm_array_header_t *)lbm_car(args[0]);
2833
2834 jpg_bufdef iodev;
2835 iodev.data = (uint8_t*)(array->data);
2836 iodev.size = (int)array->size;
2837 iodev.pos = 0;
2838 iodev.ofs_x = lbm_dec_as_i32(args[1]);
2839 iodev.ofs_y = lbm_dec_as_i32(args[2]);
2840 jd_prepare(&jd, jpg_input_func, jdwork, sz_work + IMAGE_BUFFER_HEADER_SIZE, &iodev);
2841 jd_decomp(&jd, jpg_output_func, 0);
2842 lbm_free(jdwork);
2843 return ENC_SYM_TRUE;
2844 }
2845
2846 void lbm_display_extensions_init(void) {
2847 register_symbols();
2848
2849 disp_render_image = NULL;
2850 disp_clear = NULL;
2851 disp_reset = NULL;
2852
2853 lbm_add_extension("img-buffer", ext_image_buffer);
2854 lbm_add_extension("img-buffer?", ext_is_image_buffer);
2855 lbm_add_extension("img-color", ext_color);
2856 lbm_add_extension("img-color-set", ext_color_set);
2857 lbm_add_extension("img-color-get", ext_color_get);
2858 lbm_add_extension("img-color-setpre", ext_color_setpre);
2859 lbm_add_extension("img-color-getpre", ext_color_getpre);
2860 lbm_add_extension("img-dims", ext_image_dims);
2861 lbm_add_extension("img-setpix", ext_putpixel);
2862 lbm_add_extension("img-line", ext_line);
2863 lbm_add_extension("img-text", ext_text);
2864 lbm_add_extension("img-clear", ext_clear);
2865 lbm_add_extension("img-circle", ext_circle);
2866 lbm_add_extension("img-arc", ext_arc);
2867 lbm_add_extension("img-circle-sector", ext_circle_sector);
2868 lbm_add_extension("img-circle-segment", ext_circle_segment);
2869 lbm_add_extension("img-rectangle", ext_rectangle);
2870 lbm_add_extension("img-triangle", ext_triangle);
2871 lbm_add_extension("img-blit", ext_blit);
2872
2873 lbm_add_extension("disp-reset", ext_disp_reset);
2874 lbm_add_extension("disp-clear", ext_disp_clear);
2875 lbm_add_extension("disp-render", ext_disp_render);
2876 lbm_add_extension("disp-render-jpg", ext_disp_render_jpg);
2877 }
2878
2879 void lbm_display_extensions_set_callbacks(
2880 bool(* volatile render_image)(image_buffer_t *img, uint16_t x, uint16_t y, color_t *colors),
2881 void(* volatile clear)(uint32_t color),
2882 void(* volatile reset)(void)
2883 ) {
2884 disp_render_image = render_image ? render_image : display_dummy_render_image;
2885 disp_clear = clear ? clear : display_dummy_clear;
2886 disp_reset = reset ? reset : display_dummy_reset;
2887 }
2888