GCC Code Coverage Report
Directory: ../src/ Exec Total Coverage
File: /home/joels/Current/lispbm/src/extensions/display_extensions.c Lines: 0 1612 0.0 %
Date: 2025-04-09 11:39:30 Branches: 0 1273 0.0 %

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