GCC Code Coverage Report
Directory: ../src/ Exec Total Coverage
File: /home/joels/Current/lispbm/src/extensions/display_extensions.c Lines: 0 1600 0.0 %
Date: 2024-12-05 14:36:58 Branches: 0 1263 0.0 %

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