GCC Code Coverage Report
Directory: ../src/ Exec Total Coverage
File: /home/joels/Current/lispbm/src/extensions/display_extensions.c Lines: 0 1598 0.0 %
Date: 2025-01-19 11:10:47 Branches: 0 1259 0.0 %

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