1 #include "sixel.h"
2 
3 #include <string.h>
4 #include <limits.h>
5 
6 #define LOG_MODULE "sixel"
7 #define LOG_ENABLE_DBG 0
8 #include "log.h"
9 #include "debug.h"
10 #include "render.h"
11 #include "hsl.h"
12 #include "util.h"
13 #include "xmalloc.h"
14 #include "xsnprintf.h"
15 
16 static size_t count;
17 
18 void
sixel_fini(struct terminal * term)19 sixel_fini(struct terminal *term)
20 {
21     free(term->sixel.image.data);
22     free(term->sixel.private_palette);
23     free(term->sixel.shared_palette);
24 }
25 
26 void
sixel_init(struct terminal * term,int p1,int p2,int p3)27 sixel_init(struct terminal *term, int p1, int p2, int p3)
28 {
29     /*
30      * P1: pixel aspect ratio - unimplemented
31      * P2: background color mode
32      *  - 0|2: empty pixels use current background color
33      *  - 1:   empty pixels remain at their current color (i.e. transparent)
34      * P3: horizontal grid size - ignored
35      */
36 
37     xassert(term->sixel.image.data == NULL);
38     xassert(term->sixel.palette_size <= SIXEL_MAX_COLORS);
39 
40     term->sixel.state = SIXEL_DECSIXEL;
41     term->sixel.pos = (struct coord){0, 0};
42     term->sixel.max_non_empty_row_no = -1;
43     term->sixel.row_byte_ofs = 0;
44     term->sixel.color_idx = 0;
45     term->sixel.param = 0;
46     term->sixel.param_idx = 0;
47     memset(term->sixel.params, 0, sizeof(term->sixel.params));
48     term->sixel.transparent_bg = p2 == 1;
49     term->sixel.image.data = xmalloc(1 * 6 * sizeof(term->sixel.image.data[0]));
50     term->sixel.image.width = 1;
51     term->sixel.image.height = 6;
52 
53     /* TODO: default palette */
54 
55     if (term->sixel.use_private_palette) {
56         xassert(term->sixel.private_palette == NULL);
57         term->sixel.private_palette = xcalloc(
58             term->sixel.palette_size, sizeof(term->sixel.private_palette[0]));
59         term->sixel.palette = term->sixel.private_palette;
60     } else {
61         if (term->sixel.shared_palette == NULL) {
62             term->sixel.shared_palette = xcalloc(
63                 term->sixel.palette_size, sizeof(term->sixel.shared_palette[0]));
64         } else {
65             /* Shared palette - do *not* reset palette for new sixels */
66         }
67 
68         term->sixel.palette = term->sixel.shared_palette;
69     }
70 
71     term->sixel.default_bg = term->sixel.transparent_bg
72         ? 0x00000000u
73         : 0xffu << 24 | (term->vt.attrs.bg_src != COLOR_DEFAULT
74                          ? term->vt.attrs.bg
75                          : term->colors.bg);
76 
77     for (size_t i = 0; i < 1 * 6; i++)
78         term->sixel.image.data[i] = term->sixel.default_bg;
79 
80     count = 0;
81 }
82 
83 void
sixel_destroy(struct sixel * sixel)84 sixel_destroy(struct sixel *sixel)
85 {
86     if (sixel->pix != NULL)
87         pixman_image_unref(sixel->pix);
88 
89     free(sixel->data);
90     sixel->pix = NULL;
91     sixel->data = NULL;
92 }
93 
94 void
sixel_destroy_all(struct terminal * term)95 sixel_destroy_all(struct terminal *term)
96 {
97     tll_foreach(term->normal.sixel_images, it)
98         sixel_destroy(&it->item);
99     tll_foreach(term->alt.sixel_images, it)
100         sixel_destroy(&it->item);
101     tll_free(term->normal.sixel_images);
102     tll_free(term->alt.sixel_images);
103 }
104 
105 static void
sixel_erase(struct terminal * term,struct sixel * sixel)106 sixel_erase(struct terminal *term, struct sixel *sixel)
107 {
108     for (int i = 0; i < sixel->rows; i++) {
109         int r = (sixel->pos.row + i) & (term->grid->num_rows - 1);
110 
111         struct row *row = term->grid->rows[r];
112         if (row == NULL) {
113             /* A resize/reflow may cause row to now be unallocated */
114             continue;
115         }
116 
117         row->dirty = true;
118 
119         for (int c = sixel->pos.col; c < min(sixel->cols, term->cols); c++)
120             row->cells[c].attrs.clean = 0;
121     }
122 
123     sixel_destroy(sixel);
124 }
125 
126 /*
127  * Calculates the scrollback relative row number, given an absolute row number.
128  *
129  * The scrollback relative row number 0 is the *first*, and *oldest*
130  * row in the scrollback history (and thus the *first* row to be
131  * scrolled out). Thus, a higher number means further *down* in the
132  * scrollback, with the *highest* number being at the bottom of the
133  * screen, where new input appears.
134  */
135 static int
rebase_row(const struct terminal * term,int abs_row)136 rebase_row(const struct terminal *term, int abs_row)
137 {
138     int scrollback_start = term->grid->offset + term->rows;
139     int rebased_row = abs_row - scrollback_start + term->grid->num_rows;
140 
141     rebased_row &= term->grid->num_rows - 1;
142     return rebased_row;
143 }
144 
145 /*
146  * Verify the sixels are sorted correctly.
147  *
148  * The sixels are sorted on their *end* row, in descending order. This
149  * invariant means the most recent sixels appear first in the list.
150  */
151 static void
verify_list_order(const struct terminal * term)152 verify_list_order(const struct terminal *term)
153 {
154 #if defined(_DEBUG)
155     int prev_row = INT_MAX;
156     int prev_col = -1;
157     int prev_col_count = 0;
158 
159     /* To aid debugging */
160     size_t idx = 0;
161 
162     tll_foreach(term->grid->sixel_images, it) {
163         int row = rebase_row(term, it->item.pos.row + it->item.rows - 1);
164         int col = it->item.pos.col;
165         int col_count = it->item.cols;
166 
167         xassert(row <= prev_row);
168 
169         if (row == prev_row) {
170             /* Allowed to be on the same row only if their columns
171              * don't overlap */
172 
173             xassert(col + col_count <= prev_col ||
174                    prev_col + prev_col_count <= col);
175         }
176 
177         prev_row = row;
178         prev_col = col;
179         prev_col_count = col_count;
180         idx++;
181     }
182 #endif
183 }
184 
185 /*
186  * Verifies there aren't any sixels that cross the scrollback
187  * wrap-around. This invariant means a sixel's absolute row numbers
188  * are strictly increasing.
189  */
190 static void
verify_no_wraparound_crossover(const struct terminal * term)191 verify_no_wraparound_crossover(const struct terminal *term)
192 {
193 #if defined(_DEBUG)
194     tll_foreach(term->grid->sixel_images, it) {
195         const struct sixel *six = &it->item;
196 
197         xassert(six->pos.row >= 0);
198         xassert(six->pos.row < term->grid->num_rows);
199 
200         int end = (six->pos.row + six->rows - 1) & (term->grid->num_rows - 1);
201         xassert(end >= six->pos.row);
202     }
203 #endif
204 }
205 
206 /*
207  * Verify there aren't any sixels that cross the scrollback end. This
208  * invariant means a sixel's rebased row numbers are strictly
209  * increasing.
210  */
211 static void
verify_scrollback_consistency(const struct terminal * term)212 verify_scrollback_consistency(const struct terminal *term)
213 {
214 #if defined(_DEBUG)
215     tll_foreach(term->grid->sixel_images, it) {
216         const struct sixel *six = &it->item;
217 
218         int last_row = -1;
219         for (int i = 0; i < six->rows; i++) {
220             int row_no = rebase_row(term, six->pos.row + i);
221 
222             if (last_row != -1)
223                 xassert(last_row < row_no);
224 
225             last_row = row_no;
226         }
227     }
228 #endif
229 }
230 
231 /*
232  * Verifies no sixel overlap with any other sixels.
233  */
234 static void
verify_no_overlap(const struct terminal * term)235 verify_no_overlap(const struct terminal *term)
236 {
237 #if defined(_DEBUG)
238     tll_foreach(term->grid->sixel_images, it) {
239         const struct sixel *six1 = &it->item;
240 
241         pixman_region32_t rect1;
242         pixman_region32_init_rect(
243             &rect1, six1->pos.col, six1->pos.row, six1->cols, six1->rows);
244 
245         tll_foreach(term->grid->sixel_images, it2) {
246             const struct sixel *six2 = &it2->item;
247 
248             if (six1 == six2)
249                 continue;
250 
251             pixman_region32_t rect2;
252             pixman_region32_init_rect(
253                 &rect2, six2->pos.col,
254                 six2->pos.row, six2->cols, six2->rows);
255 
256             pixman_region32_t intersection;
257             pixman_region32_init(&intersection);
258             pixman_region32_intersect(&intersection, &rect1, &rect2);
259 
260             xassert(!pixman_region32_not_empty(&intersection));
261 
262             pixman_region32_fini(&intersection);
263             pixman_region32_fini(&rect2);
264         }
265 
266         pixman_region32_fini(&rect1);
267     }
268 #endif
269 }
270 
271 static void
verify_sixels(const struct terminal * term)272 verify_sixels(const struct terminal *term)
273 {
274     verify_no_wraparound_crossover(term);
275     verify_scrollback_consistency(term);
276     verify_no_overlap(term);
277     verify_list_order(term);
278 }
279 
280 static void
sixel_insert(struct terminal * term,struct sixel sixel)281 sixel_insert(struct terminal *term, struct sixel sixel)
282 {
283     int end_row = rebase_row(term, sixel.pos.row + sixel.rows - 1);
284 
285     tll_foreach(term->grid->sixel_images, it) {
286         if (rebase_row(term, it->item.pos.row + it->item.rows - 1) < end_row) {
287             tll_insert_before(term->grid->sixel_images, it, sixel);
288             goto out;
289         }
290     }
291 
292     tll_push_back(term->grid->sixel_images, sixel);
293 
294 out:
295 #if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG
296     LOG_DBG("sixel list after insertion:");
297     tll_foreach(term->grid->sixel_images, it) {
298         LOG_DBG("  rows=%d+%d", it->item.pos.row, it->item.rows);
299     }
300 #endif
301     verify_sixels(term);
302 }
303 
304 void
sixel_scroll_up(struct terminal * term,int rows)305 sixel_scroll_up(struct terminal *term, int rows)
306 {
307     if (likely(tll_length(term->grid->sixel_images) == 0))
308         return;
309 
310     tll_rforeach(term->grid->sixel_images, it) {
311         struct sixel *six = &it->item;
312 
313         int six_start = rebase_row(term, six->pos.row);
314 
315         if (six_start < rows) {
316             sixel_erase(term, six);
317             tll_remove(term->grid->sixel_images, it);
318         } else {
319             /*
320              * Unfortunately, we cannot break here.
321              *
322              * The sixels are sorted on their *end* row. This means
323              * there may be a sixel with a top row that will be
324              * scrolled out *anywhere* in the list (think of a huuuuge
325              * sixel that covers the entire scrollback)
326              */
327             //break;
328         }
329     }
330 
331     term_update_ascii_printer(term);
332     verify_sixels(term);
333 }
334 
335 void
sixel_scroll_down(struct terminal * term,int rows)336 sixel_scroll_down(struct terminal *term, int rows)
337 {
338     if (likely(tll_length(term->grid->sixel_images) == 0))
339         return;
340 
341     xassert(term->grid->num_rows >= rows);
342 
343     tll_foreach(term->grid->sixel_images, it) {
344         struct sixel *six = &it->item;
345 
346         int six_end = rebase_row(term, six->pos.row + six->rows - 1);
347         if (six_end >= term->grid->num_rows - rows) {
348             sixel_erase(term, six);
349             tll_remove(term->grid->sixel_images, it);
350         } else
351             break;
352     }
353 
354     term_update_ascii_printer(term);
355     verify_sixels(term);
356 }
357 
358 static void
blend_new_image_over_old(const struct terminal * term,const struct sixel * six,pixman_region32_t * six_rect,int row,int col,pixman_image_t ** pix,bool * opaque)359 blend_new_image_over_old(const struct terminal *term,
360                          const struct sixel *six, pixman_region32_t *six_rect,
361                          int row, int col, pixman_image_t **pix, bool *opaque)
362 {
363     xassert(pix != NULL);
364     xassert(opaque != NULL);
365 
366     const int six_ofs_x = six->pos.col * term->cell_width;
367     const int six_ofs_y = six->pos.row * term->cell_height;
368     const int img_ofs_x = col * term->cell_width;
369     const int img_ofs_y = row * term->cell_height;
370     const int img_width = pixman_image_get_width(*pix);
371     const int img_height = pixman_image_get_height(*pix);
372 
373     pixman_region32_t pix_rect;
374     pixman_region32_init_rect(
375         &pix_rect, img_ofs_x, img_ofs_y, img_width, img_height);
376 
377     /* Blend the intersection between the old and new images */
378     pixman_region32_t intersection;
379     pixman_region32_init(&intersection);
380     pixman_region32_intersect(&intersection, six_rect, &pix_rect);
381 
382     int n_rects = -1;
383     pixman_box32_t *boxes = pixman_region32_rectangles(
384         &intersection, &n_rects);
385 
386     if (n_rects == 0)
387         goto out;
388 
389     xassert(n_rects == 1);
390     pixman_box32_t *box = &boxes[0];
391 
392     if (!*opaque) {
393         /*
394          * New image is transparent - blend on top of the old
395          * sixel image.
396          */
397         pixman_image_composite32(
398             PIXMAN_OP_OVER_REVERSE,
399             six->pix, NULL, *pix,
400             box->x1 - six_ofs_x, box->y1 - six_ofs_y,
401             0, 0,
402             box->x1 - img_ofs_x, box->y1 - img_ofs_y,
403             box->x2 - box->x1, box->y2 - box->y1);
404     }
405 
406     /*
407      * Since the old image is split into sub-tiles on a
408      * per-row basis, we need to enlarge the new image and
409      * copy the old image if the old image extends beyond the
410      * new image.
411      *
412      * The "bounding" coordinates are either the edges of the
413      * old image, or the next cell boundary, whichever comes
414      * first.
415      */
416     int bounding_x = six_ofs_x + six->width > img_ofs_x + img_width
417         ? min(
418             six_ofs_x + six->width,
419             (box->x2 + term->cell_width - 1) / term->cell_width * term->cell_width)
420         : box->x2;
421     int bounding_y = six_ofs_y + six->height > img_ofs_y + img_height
422         ? min(
423             six_ofs_y + six->height,
424             (box->y2 + term->cell_height - 1) / term->cell_height * term->cell_height)
425         : box->y2;
426 
427     /* The required size of the new image */
428     const int required_width = bounding_x - img_ofs_x;
429     const int required_height = bounding_y - img_ofs_y;
430 
431     const int new_width = max(img_width, required_width);
432     const int new_height = max(img_height, required_height);
433 
434     if (new_width <= img_width && new_height <= img_height)
435         goto out;
436 
437     //LOG_INFO("enlarging: %dx%d -> %dx%d", img_width, img_height, new_width, new_height);
438 
439     if (!six->opaque) {
440         /* Transparency is viral */
441         *opaque = false;
442     }
443 
444     /* Create a new pixmap */
445     int stride = new_width * sizeof(uint32_t);
446     uint32_t *new_data = xmalloc(stride * new_height);
447     pixman_image_t *pix2 = pixman_image_create_bits_no_clear(
448         PIXMAN_a8r8g8b8, new_width, new_height, new_data, stride);
449 
450 #if defined(_DEBUG)
451     /* Fill new image with an easy-to-recognize color (green) */
452     for (size_t i = 0; i < new_width * new_height; i++)
453         new_data[i] = 0xff00ff00;
454 #endif
455 
456     /* Copy the new image, from its old pixmap, to the new pixmap */
457     pixman_image_composite32(
458         PIXMAN_OP_SRC,
459         *pix, NULL, pix2, 0, 0, 0, 0, 0, 0, img_width, img_height);
460 
461     /* Copy the bottom tile of the old sixel image into the new pixmap */
462     pixman_image_composite32(
463         PIXMAN_OP_SRC,
464         six->pix, NULL, pix2,
465         box->x1 - six_ofs_x, box->y2 - six_ofs_y,
466         0, 0,
467         box->x1 - img_ofs_x, box->y2 - img_ofs_y,
468         bounding_x - box->x1, bounding_y - box->y2);
469 
470     /* Copy the right tile of the old sixel image into the new pixmap */
471     pixman_image_composite32(
472         PIXMAN_OP_SRC,
473         six->pix, NULL, pix2,
474         box->x2 - six_ofs_x, box->y1 - six_ofs_y,
475         0, 0,
476         box->x2 - img_ofs_x, box->y1 - img_ofs_y,
477         bounding_x - box->x2, bounding_y - box->y1);
478 
479     /*
480      * Ensure the newly allocated area is initialized.
481      *
482      * Some of it, or all, will have been initialized above, by the
483      * bottom and right tiles from the old sixel image. However, there
484      * may be areas in the new image that isn't covered by the old
485      * image. These areas need to be made transparent.
486      */
487     pixman_region32_t uninitialized;
488     pixman_region32_init_rects(
489         &uninitialized,
490         (const pixman_box32_t []){
491             /* Extended image area on the right side */
492             {img_ofs_x + img_width, img_ofs_y, img_ofs_x + new_width, img_ofs_y + new_height},
493 
494             /* Bottom */
495             {img_ofs_x, img_ofs_y + img_height, img_ofs_x + new_width, img_ofs_y + new_height}},
496         2);
497 
498     /* Subtract the old sixel image, since the area(s) covered by the
499      * old image has already been copied, and *must* not be
500      * overwritten */
501     pixman_region32_t diff;
502     pixman_region32_init(&diff);
503     pixman_region32_subtract(&diff, &uninitialized, six_rect);
504 
505     if (pixman_region32_not_empty(&diff)) {
506         pixman_image_t *src =
507             pixman_image_create_solid_fill(&(pixman_color_t){0});
508 
509         int count = -1;
510         pixman_box32_t *rects = pixman_region32_rectangles(&diff, &count);
511 
512         for (int i = 0; i < count; i++) {
513             pixman_image_composite32(
514                 PIXMAN_OP_SRC,
515                 src, NULL, pix2,
516                 0, 0, 0, 0,
517                 rects[i].x1 - img_ofs_x, rects[i].y1 - img_ofs_y,
518                 rects[i].x2 - rects[i].x1,
519                 rects[i].y2 - rects[i].y1);
520         }
521 
522         pixman_image_unref(src);
523         *opaque = false;
524     }
525 
526     pixman_region32_fini(&diff);
527     pixman_region32_fini(&uninitialized);
528 
529     /* Use the new pixmap in place of the old one */
530     free(pixman_image_get_data(*pix));
531     pixman_image_unref(*pix);
532     *pix = pix2;
533 
534 out:
535     pixman_region32_fini(&intersection);
536     pixman_region32_fini(&pix_rect);
537 }
538 
539 static void
sixel_overwrite(struct terminal * term,struct sixel * six,int row,int col,int height,int width,pixman_image_t ** pix,bool * opaque)540 sixel_overwrite(struct terminal *term, struct sixel *six,
541                 int row, int col, int height, int width,
542                 pixman_image_t **pix, bool *opaque)
543 {
544     pixman_region32_t six_rect;
545     pixman_region32_init_rect(
546         &six_rect,
547         six->pos.col * term->cell_width, six->pos.row * term->cell_height,
548         six->width, six->height);
549 
550     pixman_region32_t overwrite_rect;
551     pixman_region32_init_rect(
552         &overwrite_rect,
553         col * term->cell_width, row * term->cell_height,
554         width * term->cell_width, height * term->cell_height);
555 
556 #if defined(_DEBUG)
557     pixman_region32_t cell_intersection;
558     pixman_region32_init(&cell_intersection);
559     pixman_region32_intersect(&cell_intersection, &six_rect, &overwrite_rect);
560     xassert(pixman_region32_not_empty(&cell_intersection));
561     pixman_region32_fini(&cell_intersection);
562 #endif
563 
564     if (pix != NULL)
565         blend_new_image_over_old(term, six, &six_rect, row, col, pix, opaque);
566 
567 
568     pixman_region32_t diff;
569     pixman_region32_init(&diff);
570     pixman_region32_subtract(&diff, &six_rect, &overwrite_rect);
571 
572     pixman_region32_fini(&six_rect);
573     pixman_region32_fini(&overwrite_rect);
574 
575     int n_rects = -1;
576     pixman_box32_t *boxes = pixman_region32_rectangles(&diff, &n_rects);
577 
578     for (int i = 0; i < n_rects; i++) {
579         LOG_DBG("box #%d: x1=%d, y1=%d, x2=%d, y2=%d", i,
580                 boxes[i].x1, boxes[i].y1, boxes[i].x2, boxes[i].y2);
581 
582         xassert(boxes[i].x1 % term->cell_width == 0);
583         xassert(boxes[i].y1 % term->cell_height == 0);
584 
585         /* New image's position, in cells */
586         const int new_col = boxes[i].x1 / term->cell_width;
587         const int new_row = boxes[i].y1 / term->cell_height;
588 
589         xassert(new_row < term->grid->num_rows);
590 
591         /* New image's width and height, in pixels */
592         const int new_width = boxes[i].x2 - boxes[i].x1;
593         const int new_height = boxes[i].y2 - boxes[i].y1;
594 
595         uint32_t *new_data = xmalloc(new_width * new_height * sizeof(uint32_t));
596         const uint32_t *old_data = six->data;
597 
598         /* Pixel offsets into old image backing memory */
599         const int x_ofs = boxes[i].x1 - six->pos.col * term->cell_width;
600         const int y_ofs = boxes[i].y1 - six->pos.row * term->cell_height;
601 
602         /* Copy image data, one row at a time */
603         for (size_t j = 0; j < new_height; j++) {
604             memcpy(
605                 &new_data[(0 + j) * new_width],
606                 &old_data[(y_ofs + j) * six->width + x_ofs],
607                 new_width * sizeof(uint32_t));
608         }
609 
610         pixman_image_t *new_pix = pixman_image_create_bits_no_clear(
611             PIXMAN_a8r8g8b8,
612             new_width, new_height, new_data, new_width * sizeof(uint32_t));
613 
614         struct sixel new_six = {
615             .data = new_data,
616             .pix = new_pix,
617             .width = new_width,
618             .height = new_height,
619             .pos = {.col = new_col, .row = new_row},
620             .cols = (new_width + term->cell_width - 1) / term->cell_width,
621             .rows = (new_height + term->cell_height - 1) / term->cell_height,
622             .opaque = six->opaque,
623         };
624 
625 #if defined(_DEBUG)
626         /* Assert we don't cross the scrollback wrap-around */
627         const int new_end = new_six.pos.row + new_six.rows - 1;
628         xassert(new_end < term->grid->num_rows);
629 #endif
630 
631         sixel_insert(term, new_six);
632     }
633 
634     pixman_region32_fini(&diff);
635 }
636 
637 /* Row numbers are absolute */
638 static void
_sixel_overwrite_by_rectangle(struct terminal * term,int row,int col,int height,int width,pixman_image_t ** pix,bool * opaque)639 _sixel_overwrite_by_rectangle(
640     struct terminal *term, int row, int col, int height, int width,
641     pixman_image_t **pix, bool *opaque)
642 {
643     verify_sixels(term);
644 
645 #if defined(_DEBUG)
646     pixman_region32_t overwrite_rect;
647     pixman_region32_init_rect(&overwrite_rect, col, row, width, height);
648 #endif
649 
650     const int start = row;
651     const int end = row + height - 1;
652 
653     /* We should never generate scrollback wrapping sixels */
654     xassert(end < term->grid->num_rows);
655 
656     const int scrollback_rel_start = rebase_row(term, start);
657 
658     bool UNUSED would_have_breaked = false;
659 
660     tll_foreach(term->grid->sixel_images, it) {
661         struct sixel *six = &it->item;
662 
663         const int six_start = six->pos.row;
664         const int six_end = (six_start + six->rows - 1);
665         const int six_scrollback_rel_end = rebase_row(term, six_end);
666 
667         /* We should never generate scrollback wrapping sixels */
668         xassert(six_end < term->grid->num_rows);
669 
670         if (six_scrollback_rel_end < scrollback_rel_start) {
671             /* All remaining sixels are *before* our rectangle */
672             would_have_breaked = true;
673             break;
674         }
675 
676 #if defined(_DEBUG)
677         pixman_region32_t six_rect;
678         pixman_region32_init_rect(&six_rect, six->pos.col, six->pos.row, six->cols, six->rows);
679 
680         pixman_region32_t intersection;
681         pixman_region32_init(&intersection);
682         pixman_region32_intersect(&intersection, &six_rect, &overwrite_rect);
683 
684         const bool collides = pixman_region32_not_empty(&intersection);
685 #else
686         const bool UNUSED collides = false;
687 #endif
688 
689         if ((start <= six_start && end >= six_start) ||  /* Crosses sixel start boundary */
690             (start <= six_end && end >= six_end) ||      /* Crosses sixel end boundary */
691             (start >= six_start && end <= six_end))      /* Fully within sixel range */
692         {
693             const int col_start = six->pos.col;
694             const int col_end = six->pos.col + six->cols - 1;
695 
696             if ((col <= col_start && col + width - 1 >= col_start) ||
697                 (col <= col_end && col + width - 1 >= col_end) ||
698                 (col >= col_start && col + width - 1 <= col_end))
699             {
700                 xassert(!would_have_breaked);
701 
702                 struct sixel to_be_erased = *six;
703                 tll_remove(term->grid->sixel_images, it);
704 
705                 sixel_overwrite(term, &to_be_erased, start, col, height, width,
706                                 pix, opaque);
707                 sixel_erase(term, &to_be_erased);
708             } else
709                 xassert(!collides);
710         } else
711             xassert(!collides);
712 
713 #if defined(_DEBUG)
714         pixman_region32_fini(&intersection);
715         pixman_region32_fini(&six_rect);
716 #endif
717     }
718 
719 #if defined(_DEBUG)
720     pixman_region32_fini(&overwrite_rect);
721 #endif
722 }
723 
724 void
sixel_overwrite_by_rectangle(struct terminal * term,int row,int col,int height,int width)725 sixel_overwrite_by_rectangle(
726     struct terminal *term, int row, int col, int height, int width)
727 {
728     if (likely(tll_length(term->grid->sixel_images) == 0))
729         return;
730 
731     const int start = (term->grid->offset + row) & (term->grid->num_rows - 1);
732     const int end = (start + height - 1) & (term->grid->num_rows - 1);
733     const bool wraps = end < start;
734 
735     if (wraps) {
736         int rows_to_wrap_around = term->grid->num_rows - start;
737         xassert(height - rows_to_wrap_around > 0);
738         _sixel_overwrite_by_rectangle(term, start, col, rows_to_wrap_around, width, NULL, NULL);
739         _sixel_overwrite_by_rectangle(term, 0, col, height - rows_to_wrap_around, width, NULL, NULL);
740     } else
741         _sixel_overwrite_by_rectangle(term, start, col, height, width, NULL, NULL);
742 
743     term_update_ascii_printer(term);
744 }
745 
746 /* Row numbers are relative to grid offset */
747 void
sixel_overwrite_by_row(struct terminal * term,int _row,int col,int width)748 sixel_overwrite_by_row(struct terminal *term, int _row, int col, int width)
749 {
750     xassert(col >= 0);
751 
752     xassert(_row >= 0);
753     xassert(_row < term->rows);
754     xassert(col >= 0);
755     xassert(col < term->grid->num_cols);
756 
757     if (likely(tll_length(term->grid->sixel_images) == 0))
758         return;
759 
760     if (col + width > term->grid->num_cols)
761         width = term->grid->num_cols - col;
762 
763     const int row = (term->grid->offset + _row) & (term->grid->num_rows - 1);
764     const int scrollback_rel_row = rebase_row(term, row);
765 
766     tll_foreach(term->grid->sixel_images, it) {
767         struct sixel *six = &it->item;
768         const int six_start = six->pos.row;
769         const int six_end = (six_start + six->rows - 1) & (term->grid->num_rows - 1);
770 
771         /* We should never generate scrollback wrapping sixels */
772         xassert(six_end >= six_start);
773 
774         const int six_scrollback_rel_end = rebase_row(term, six_end);
775 
776         if (six_scrollback_rel_end < scrollback_rel_row) {
777             /* All remaining sixels are *before* "our" row */
778             break;
779         }
780 
781         if (row >= six_start && row <= six_end) {
782             const int col_start = six->pos.col;
783             const int col_end = six->pos.col + six->cols - 1;
784 
785             if ((col <= col_start && col + width - 1 >= col_start) ||
786                 (col <= col_end && col + width - 1 >= col_end) ||
787                 (col >= col_start && col + width - 1 <= col_end))
788             {
789                 struct sixel to_be_erased = *six;
790                 tll_remove(term->grid->sixel_images, it);
791 
792                 sixel_overwrite(term, &to_be_erased, row, col, 1, width, NULL, NULL);
793                 sixel_erase(term, &to_be_erased);
794             }
795         }
796     }
797 
798     term_update_ascii_printer(term);
799 }
800 
801 void
sixel_overwrite_at_cursor(struct terminal * term,int width)802 sixel_overwrite_at_cursor(struct terminal *term, int width)
803 {
804     if (likely(tll_length(term->grid->sixel_images) == 0))
805         return;
806 
807     sixel_overwrite_by_row(
808         term, term->grid->cursor.point.row, term->grid->cursor.point.col, width);
809 }
810 
811 void
sixel_cell_size_changed(struct terminal * term)812 sixel_cell_size_changed(struct terminal *term)
813 {
814     struct grid *g = term->grid;
815 
816     term->grid = &term->normal;
817     tll_foreach(term->normal.sixel_images, it) {
818         struct sixel *six = &it->item;
819         six->rows = (six->height + term->cell_height - 1) / term->cell_height;
820         six->cols = (six->width + term->cell_width - 1) / term->cell_width;
821     }
822 
823     term->grid = &term->alt;
824     tll_foreach(term->alt.sixel_images, it) {
825         struct sixel *six = &it->item;
826         six->rows = (six->height + term->cell_height - 1) / term->cell_height;
827         six->cols = (six->width + term->cell_width - 1) / term->cell_width;
828     }
829 
830     term->grid = g;
831 }
832 
833 void
sixel_reflow(struct terminal * term)834 sixel_reflow(struct terminal *term)
835 {
836     struct grid *g = term->grid;
837 
838     for (size_t i = 0; i < 2; i++) {
839         struct grid *grid = i == 0 ? &term->normal : &term->alt;
840 
841         term->grid = grid;
842 
843         /* Need the “real” list to be empty from the beginning */
844         tll(struct sixel) copy = tll_init();
845         tll_foreach(grid->sixel_images, it)
846             tll_push_back(copy, it->item);
847         tll_free(grid->sixel_images);
848 
849         tll_rforeach(copy, it) {
850             struct sixel *six = &it->item;
851             int start = six->pos.row;
852             int end = (start + six->rows - 1) & (grid->num_rows - 1);
853 
854             if (end < start) {
855                 /* Crosses scrollback wrap-around */
856                 /* TODO: split image */
857                 sixel_destroy(six);
858                 continue;
859             }
860 
861             if (six->rows > grid->num_rows) {
862                 /* Image too large */
863                 /* TODO: keep bottom part? */
864                 sixel_destroy(six);
865                 continue;
866             }
867 
868             /* Drop sixels that now cross the current scrollback end
869              * border. This is similar to a sixel that have been
870              * scrolled out */
871             /* TODO: should be possible to optimize this */
872             bool sixel_destroyed = false;
873             int last_row = -1;
874 
875             for (int j = 0; j < six->rows; j++) {
876                 int row_no = rebase_row(term, six->pos.row + j);
877                 if (last_row != -1 && last_row >= row_no) {
878                     sixel_destroy(six);
879                     sixel_destroyed = true;
880                     break;
881                 }
882 
883                 last_row = row_no;
884             }
885 
886             if (sixel_destroyed) {
887                 LOG_WARN("destroyed sixel that now crossed history");
888                 continue;
889             }
890 
891             /* Sixels that didn’t overlap may now do so, which isn’t
892              * allowed of course */
893             _sixel_overwrite_by_rectangle(
894                 term, six->pos.row, six->pos.col, six->rows, six->cols,
895                 &it->item.pix, &it->item.opaque);
896 
897             if (it->item.data != pixman_image_get_data(it->item.pix)) {
898                 it->item.data = pixman_image_get_data(it->item.pix);
899                 it->item.width = pixman_image_get_width(it->item.pix);
900                 it->item.height = pixman_image_get_height(it->item.pix);
901                 it->item.cols = (it->item.width + term->cell_width - 1) / term->cell_width;
902                 it->item.rows = (it->item.height + term->cell_height - 1) / term->cell_height;
903             }
904 
905             sixel_insert(term, it->item);
906         }
907 
908         tll_free(copy);
909     }
910 
911     term->grid = g;
912 }
913 
914 void
sixel_unhook(struct terminal * term)915 sixel_unhook(struct terminal *term)
916 {
917     if (term->sixel.image.height > term->sixel.max_non_empty_row_no + 1) {
918         LOG_DBG(
919             "last row only partially filled, reducing image height: %d -> %d",
920             term->sixel.image.height, term->sixel.max_non_empty_row_no + 1);
921         term->sixel.image.height = term->sixel.max_non_empty_row_no + 1;
922     }
923 
924     int pixel_row_idx = 0;
925     int pixel_rows_left = term->sixel.image.height;
926     const int stride = term->sixel.image.width * sizeof(uint32_t);
927 
928     /*
929      * When sixel scrolling is enabled (the default), sixels behave
930      * pretty much like normal output; the sixel starts at the current
931      * cursor position and the cursor is moved to a point after the
932      * sixel.
933      *
934      * Furthermore, if the sixel reaches the bottom of the scrolling
935      * region, the terminal content is scrolled.
936      *
937      * When scrolling is disabled, sixels always start at (0,0), the
938      * cursor is not moved at all, and the terminal content never
939      * scrolls.
940      */
941 
942     const bool do_scroll = term->sixel.scrolling;
943 
944     /* Number of rows we're allowed to use.
945      *
946      * When scrolling is enabled, we always allow the entire sixel to
947      * be emitted.
948      *
949      * When disabled, only the number of screen rows may be used. */
950     int rows_avail = do_scroll
951         ? (term->sixel.image.height + term->cell_height - 1) / term->cell_height
952         : term->scroll_region.end;
953 
954     /* Initial sixel coordinates */
955     int start_row = do_scroll ? term->grid->cursor.point.row : 0;
956     const int start_col = do_scroll ? term->grid->cursor.point.col : 0;
957 
958     /* Total number of rows needed by image (+ optional newline at the end) */
959     const int rows_needed =
960         (term->sixel.image.height + term->cell_height - 1) / term->cell_height +
961         (term->sixel.cursor_right_of_graphics ? 0 : 1);
962 
963     bool free_image_data = true;
964 
965     /* We do not allow sixels to cross the scrollback wrap-around, as
966      * this makes intersection calculations much more complicated */
967     while (pixel_rows_left > 0 &&
968            rows_avail > 0 &&
969            rows_needed <= term->grid->num_rows)
970     {
971         const int cur_row = (term->grid->offset + start_row) & (term->grid->num_rows - 1);
972         const int rows_left_until_wrap_around = term->grid->num_rows - cur_row;
973         const int usable_rows = min(rows_avail, rows_left_until_wrap_around);
974 
975         const int pixel_rows_avail = usable_rows * term->cell_height;
976 
977         const int width = term->sixel.image.width;
978         const int height = min(pixel_rows_left, pixel_rows_avail);
979 
980         uint32_t *img_data;
981         if (pixel_row_idx == 0 && height == pixel_rows_left) {
982             /* Entire image will be emitted as a single chunk - reuse
983              * the source buffer */
984             img_data = term->sixel.image.data;
985             free_image_data = false;
986         } else {
987             xassert(free_image_data);
988             img_data = xmalloc(height * stride);
989             memcpy(
990                 img_data,
991                 &((uint8_t *)term->sixel.image.data)[pixel_row_idx * stride],
992                 height * stride);
993         }
994 
995         struct sixel image = {
996             .data = img_data,
997             .width = width,
998             .height = height,
999             .rows = (height + term->cell_height - 1) / term->cell_height,
1000             .cols = (width + term->cell_width - 1) / term->cell_width,
1001             .pos = (struct coord){start_col, cur_row},
1002             .opaque = !term->sixel.transparent_bg,
1003         };
1004 
1005         xassert(image.rows <= term->grid->num_rows);
1006         xassert(image.pos.row + image.rows - 1 < term->grid->num_rows);
1007 
1008         LOG_DBG("generating %s %dx%d pixman image at %d-%d",
1009                 image.opaque ? "opaque" : "transparent",
1010                 image.width, image.height,
1011                 image.pos.row, image.pos.row + image.rows);
1012 
1013         image.pix = pixman_image_create_bits_no_clear(
1014             PIXMAN_a8r8g8b8, image.width, image.height, img_data, stride);
1015 
1016         pixel_row_idx += height;
1017         pixel_rows_left -= height;
1018         rows_avail -= image.rows;
1019 
1020         /* Dirty touched cells, and scroll terminal content if necessary */
1021         for (size_t i = 0; i < image.rows; i++) {
1022             struct row *row = term->grid->rows[cur_row + i];
1023             row->dirty = true;
1024 
1025             for (int col = image.pos.col;
1026                  col < min(image.pos.col + image.cols, term->cols);
1027                  col++)
1028             {
1029                 row->cells[col].attrs.clean = 0;
1030             }
1031 
1032             if (do_scroll) {
1033                 /*
1034                  * Linefeed, *unless* we're on the very last row of
1035                  * the final image (not just this chunk) and private
1036                  * mode 8452 (leave cursor at the right of graphics)
1037                  * is enabled.
1038                  */
1039                 if (term->sixel.cursor_right_of_graphics &&
1040                     rows_avail == 0 &&
1041                     i >= image.rows - 1)
1042                 {
1043                     term_cursor_to(
1044                         term,
1045                         term->grid->cursor.point.row,
1046                         min(image.pos.col + image.cols, term->cols - 1));
1047                 } else {
1048                     term_linefeed(term);
1049                     term_carriage_return(term);
1050                 }
1051             }
1052         }
1053 
1054         _sixel_overwrite_by_rectangle(
1055             term, image.pos.row, image.pos.col, image.rows, image.cols,
1056             &image.pix, &image.opaque);
1057 
1058         if (image.data != pixman_image_get_data(image.pix)) {
1059             image.data = pixman_image_get_data(image.pix);
1060             image.width = pixman_image_get_width(image.pix);
1061             image.height = pixman_image_get_height(image.pix);
1062             image.cols = (image.width + term->cell_width - 1) / term->cell_width;
1063             image.rows = (image.height + term->cell_height - 1) / term->cell_height;
1064         }
1065 
1066         sixel_insert(term, image);
1067 
1068         if (do_scroll)
1069             start_row = term->grid->cursor.point.row;
1070         else
1071             start_row -= image.rows;
1072     }
1073 
1074     if (free_image_data)
1075         free(term->sixel.image.data);
1076 
1077     term->sixel.image.data = NULL;
1078     term->sixel.image.width = 0;
1079     term->sixel.image.height = 0;
1080     term->sixel.pos = (struct coord){0, 0};
1081 
1082     free(term->sixel.private_palette);
1083     term->sixel.private_palette = NULL;
1084 
1085     LOG_DBG("you now have %zu sixels in current grid",
1086             tll_length(term->grid->sixel_images));
1087 
1088     term_update_ascii_printer(term);
1089     render_refresh(term);
1090 }
1091 
1092 static bool
resize_horizontally(struct terminal * term,int new_width)1093 resize_horizontally(struct terminal *term, int new_width)
1094 {
1095     LOG_DBG("resizing image horizontally: %dx(%d) -> %dx(%d)",
1096             term->sixel.image.width, term->sixel.image.height,
1097             new_width, term->sixel.image.height);
1098 
1099     if (unlikely(new_width > term->sixel.max_width)) {
1100         LOG_WARN("maximum image dimensions reached");
1101         return false;
1102     }
1103 
1104     uint32_t *old_data = term->sixel.image.data;
1105     const int old_width = term->sixel.image.width;
1106     const int height = term->sixel.image.height;
1107 
1108     int alloc_height = (height + 6 - 1) / 6 * 6;
1109 
1110     xassert(new_width > 0);
1111     xassert(alloc_height > 0);
1112 
1113     /* Width (and thus stride) change - need to allocate a new buffer */
1114     uint32_t *new_data = xmalloc(new_width * alloc_height * sizeof(uint32_t));
1115 
1116     uint32_t bg = term->sixel.default_bg;
1117 
1118     /* Copy old rows, and initialize new columns to background color */
1119     for (int r = 0; r < height; r++) {
1120         memcpy(&new_data[r * new_width],
1121                &old_data[r * old_width],
1122                old_width * sizeof(uint32_t));
1123 
1124         for (int c = old_width; c < new_width; c++)
1125             new_data[r * new_width + c] = bg;
1126     }
1127 
1128     free(old_data);
1129 
1130     term->sixel.image.data = new_data;
1131     term->sixel.image.width = new_width;
1132     term->sixel.row_byte_ofs = term->sixel.pos.row * new_width;
1133     return true;
1134 }
1135 
1136 static bool
resize_vertically(struct terminal * term,int new_height)1137 resize_vertically(struct terminal *term, int new_height)
1138 {
1139     LOG_DBG("resizing image vertically: (%d)x%d -> (%d)x%d",
1140             term->sixel.image.width, term->sixel.image.height,
1141             term->sixel.image.width, new_height);
1142 
1143     if (unlikely(new_height > term->sixel.max_height)) {
1144         LOG_WARN("maximum image dimensions reached");
1145         return false;
1146     }
1147 
1148     uint32_t *old_data = term->sixel.image.data;
1149     const int width = term->sixel.image.width;
1150     const int old_height = term->sixel.image.height;
1151 
1152     int alloc_height = (new_height + 6 - 1) / 6 * 6;
1153 
1154     xassert(width > 0);
1155     xassert(new_height > 0);
1156 
1157     uint32_t *new_data = realloc(
1158         old_data, width * alloc_height * sizeof(uint32_t));
1159 
1160     if (new_data == NULL) {
1161         LOG_ERRNO("failed to reallocate sixel image buffer");
1162         return false;
1163     }
1164 
1165     uint32_t bg = term->sixel.default_bg;
1166 
1167     /* Initialize new rows to background color */
1168     for (int r = old_height; r < new_height; r++) {
1169         for (int c = 0; c < width; c++)
1170             new_data[r * width + c] = bg;
1171     }
1172 
1173     term->sixel.image.data = new_data;
1174     term->sixel.image.height = new_height;
1175     return true;
1176 }
1177 
1178 static bool
resize(struct terminal * term,int new_width,int new_height)1179 resize(struct terminal *term, int new_width, int new_height)
1180 {
1181     LOG_DBG("resizing image: %dx%d -> %dx%d",
1182             term->sixel.image.width, term->sixel.image.height,
1183             new_width, new_height);
1184 
1185     if (new_width > term->sixel.max_width ||
1186         new_height > term->sixel.max_height)
1187     {
1188         LOG_WARN("maximum image dimensions reached");
1189         return false;
1190     }
1191 
1192     uint32_t *old_data = term->sixel.image.data;
1193     const int old_width = term->sixel.image.width;
1194     const int old_height = term->sixel.image.height;
1195 
1196     int alloc_new_width = new_width;
1197     int alloc_new_height = (new_height + 6 - 1) / 6 * 6;
1198     xassert(alloc_new_height >= new_height);
1199     xassert(alloc_new_height - new_height < 6);
1200 
1201     uint32_t *new_data = NULL;
1202     uint32_t bg = term->sixel.default_bg;
1203 
1204     if (new_width == old_width) {
1205         /* Width (and thus stride) is the same, so we can simply
1206          * re-alloc the existing buffer */
1207 
1208         new_data = realloc(old_data, alloc_new_width * alloc_new_height * sizeof(uint32_t));
1209         if (new_data == NULL) {
1210             LOG_ERRNO("failed to reallocate sixel image buffer");
1211             return false;
1212         }
1213 
1214         xassert(new_height > old_height);
1215 
1216     } else {
1217         /* Width (and thus stride) change - need to allocate a new buffer */
1218         xassert(new_width > old_width);
1219         new_data = xmalloc(alloc_new_width * alloc_new_height * sizeof(uint32_t));
1220 
1221         /* Copy old rows, and initialize new columns to background color */
1222         for (int r = 0; r < min(old_height, new_height); r++) {
1223             memcpy(&new_data[r * new_width], &old_data[r * old_width], old_width * sizeof(uint32_t));
1224 
1225             for (int c = old_width; c < new_width; c++)
1226                 new_data[r * new_width + c] = bg;
1227         }
1228         free(old_data);
1229     }
1230 
1231     /* Initialize new rows to background color */
1232     for (int r = old_height; r < new_height; r++) {
1233         for (int c = 0; c < new_width; c++)
1234             new_data[r * new_width + c] = bg;
1235     }
1236 
1237     xassert(new_data != NULL);
1238     term->sixel.image.data = new_data;
1239     term->sixel.image.width = new_width;
1240     term->sixel.image.height = new_height;
1241     term->sixel.row_byte_ofs = term->sixel.pos.row * new_width;
1242 
1243     return true;
1244 }
1245 
1246 static void
sixel_add(struct terminal * term,int col,int width,uint32_t color,uint8_t sixel)1247 sixel_add(struct terminal *term, int col, int width, uint32_t color, uint8_t sixel)
1248 {
1249     xassert(term->sixel.pos.col < term->sixel.image.width);
1250     xassert(term->sixel.pos.row < term->sixel.image.height);
1251 
1252     size_t ofs = term->sixel.row_byte_ofs + col;
1253     uint32_t *data = &term->sixel.image.data[ofs];
1254 
1255     int max_non_empty_row = -1;
1256     int row = term->sixel.pos.row;
1257 
1258     for (int i = 0; i < 6; i++, sixel >>= 1, data += width) {
1259         if (sixel & 1) {
1260             *data = color;
1261             max_non_empty_row = row + i;
1262         }
1263     }
1264 
1265     xassert(sixel == 0);
1266 
1267     term->sixel.max_non_empty_row_no = max(
1268         term->sixel.max_non_empty_row_no,
1269         max_non_empty_row);
1270 }
1271 
1272 static void
sixel_add_many(struct terminal * term,uint8_t c,unsigned count)1273 sixel_add_many(struct terminal *term, uint8_t c, unsigned count)
1274 {
1275     int col = term->sixel.pos.col;
1276     int width = term->sixel.image.width;
1277 
1278     if (unlikely(col + count - 1 >= width)) {
1279         width = col + count;
1280         if (unlikely(!resize_horizontally(term, width)))
1281             return;
1282     }
1283 
1284     uint32_t color = term->sixel.color;
1285     for (unsigned i = 0; i < count; i++, col++)
1286         sixel_add(term, col, width, color, c);
1287 
1288     term->sixel.pos.col = col;
1289 }
1290 
1291 static void
decsixel(struct terminal * term,uint8_t c)1292 decsixel(struct terminal *term, uint8_t c)
1293 {
1294     switch (c) {
1295     case '"':
1296         term->sixel.state = SIXEL_DECGRA;
1297         term->sixel.param = 0;
1298         term->sixel.param_idx = 0;
1299         break;
1300 
1301     case '!':
1302         term->sixel.state = SIXEL_DECGRI;
1303         term->sixel.param = 0;
1304         term->sixel.param_idx = 0;
1305         break;
1306 
1307     case '#':
1308         term->sixel.state = SIXEL_DECGCI;
1309         term->sixel.color_idx = 0;
1310         term->sixel.param = 0;
1311         term->sixel.param_idx = 0;
1312         break;
1313 
1314     case '$':
1315         if (likely(term->sixel.pos.col <= term->sixel.max_width)) {
1316             /*
1317              * We set, and keep, ‘col’ outside the image boundary when
1318              * we’ve reached the maximum image height, to avoid also
1319              * having to check the row vs image height in the common
1320              * path in sixel_add().
1321              */
1322             term->sixel.pos.col = 0;
1323         }
1324         break;
1325 
1326     case '-':
1327         term->sixel.pos.row += 6;
1328         term->sixel.pos.col = 0;
1329         term->sixel.row_byte_ofs += term->sixel.image.width * 6;
1330 
1331         if (term->sixel.pos.row >= term->sixel.image.height) {
1332             if (!resize_vertically(term, term->sixel.pos.row + 6))
1333                 term->sixel.pos.col = term->sixel.max_width + 1;
1334         }
1335         break;
1336 
1337     case '?': case '@': case 'A': case 'B': case 'C': case 'D': case 'E':
1338     case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
1339     case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S':
1340     case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
1341     case '[': case '\\': case ']': case '^': case '_': case '`': case 'a':
1342     case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h':
1343     case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o':
1344     case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v':
1345     case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}':
1346     case '~':
1347         sixel_add_many(term, c - 63, 1);
1348         break;
1349 
1350     case ' ':
1351     case '\n':
1352     case '\r':
1353         break;
1354 
1355     default:
1356         LOG_WARN("invalid sixel character: '%c' at idx=%zu", c, count);
1357         break;
1358     }
1359 }
1360 
1361 static void
decgra(struct terminal * term,uint8_t c)1362 decgra(struct terminal *term, uint8_t c)
1363 {
1364     switch (c) {
1365     case '0': case '1': case '2': case '3': case '4':
1366     case '5': case '6': case '7': case '8': case '9':
1367         term->sixel.param *= 10;
1368         term->sixel.param += c - '0';
1369         break;
1370 
1371     case ';':
1372         if (term->sixel.param_idx < ALEN(term->sixel.params))
1373             term->sixel.params[term->sixel.param_idx++] = term->sixel.param;
1374         term->sixel.param = 0;
1375         break;
1376 
1377     default: {
1378         if (term->sixel.param_idx < ALEN(term->sixel.params))
1379             term->sixel.params[term->sixel.param_idx++] = term->sixel.param;
1380 
1381         int nparams = term->sixel.param_idx;
1382         unsigned pan = nparams > 0 ? term->sixel.params[0] : 0;
1383         unsigned pad = nparams > 1 ? term->sixel.params[1] : 0;
1384         unsigned ph = nparams > 2 ? term->sixel.params[2] : 0;
1385         unsigned pv = nparams > 3 ? term->sixel.params[3] : 0;
1386 
1387         pan = pan > 0 ? pan : 1;
1388         pad = pad > 0 ? pad : 1;
1389 
1390         LOG_DBG("pan=%u, pad=%u (aspect ratio = %u), size=%ux%u",
1391                 pan, pad, pan / pad, ph, pv);
1392 
1393         if (ph >= term->sixel.image.height && pv >= term->sixel.image.width &&
1394             ph <= term->sixel.max_height && pv <= term->sixel.max_width)
1395         {
1396             resize(term, ph, pv);
1397 
1398             /* This ensures the sixel’s final image size is *at least*
1399              * this large */
1400             term->sixel.max_non_empty_row_no = pv - 1;
1401         }
1402 
1403         term->sixel.state = SIXEL_DECSIXEL;
1404         decsixel(term, c);
1405         break;
1406     }
1407     }
1408 }
1409 
1410 static void
decgri(struct terminal * term,uint8_t c)1411 decgri(struct terminal *term, uint8_t c)
1412 {
1413     switch (c) {
1414     case '0': case '1': case '2': case '3': case '4':
1415     case '5': case '6': case '7': case '8': case '9':
1416         term->sixel.param *= 10;
1417         term->sixel.param += c - '0';
1418         break;
1419 
1420     case '?': case '@': case 'A': case 'B': case 'C': case 'D': case 'E':
1421     case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
1422     case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S':
1423     case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
1424     case '[': case '\\': case ']': case '^': case '_': case '`': case 'a':
1425     case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h':
1426     case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o':
1427     case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v':
1428     case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}':
1429     case '~': {
1430         unsigned count = term->sixel.param;
1431         if (likely(count > 0))
1432             sixel_add_many(term, c - 63, count);
1433         term->sixel.state = SIXEL_DECSIXEL;
1434         break;
1435     }
1436     }
1437 }
1438 
1439 static void
decgci(struct terminal * term,uint8_t c)1440 decgci(struct terminal *term, uint8_t c)
1441 {
1442     switch (c) {
1443     case '0': case '1': case '2': case '3': case '4':
1444     case '5': case '6': case '7': case '8': case '9':
1445         term->sixel.param *= 10;
1446         term->sixel.param += c - '0';
1447         break;
1448 
1449     case ';':
1450         if (term->sixel.param_idx < ALEN(term->sixel.params))
1451             term->sixel.params[term->sixel.param_idx++] = term->sixel.param;
1452         term->sixel.param = 0;
1453         break;
1454 
1455     default: {
1456         if (term->sixel.param_idx < ALEN(term->sixel.params))
1457             term->sixel.params[term->sixel.param_idx++] = term->sixel.param;
1458 
1459         int nparams = term->sixel.param_idx;
1460 
1461         if (nparams > 0)
1462             term->sixel.color_idx = min(term->sixel.params[0], term->sixel.palette_size - 1);
1463 
1464         if (nparams > 4) {
1465             unsigned format = term->sixel.params[1];
1466             int c1 = term->sixel.params[2];
1467             int c2 = term->sixel.params[3];
1468             int c3 = term->sixel.params[4];
1469 
1470             switch (format) {
1471             case 1: { /* HLS */
1472                 int hue = min(c1, 360);
1473                 int lum = min(c2, 100);
1474                 int sat = min(c3, 100);
1475 
1476                 /*
1477                  * Sixel’s HLS use the following primary color hues:
1478                  *  blue:  0°
1479                  *  red:   120°
1480                  *  green: 240°
1481                  *
1482                  * While “standard” HSL uses:
1483                  *  red:   0°
1484                  *  green: 120°
1485                  *  blue:  240°
1486                  */
1487                 hue = (hue + 240) % 360;
1488 
1489                 uint32_t rgb = hsl_to_rgb(hue, sat, lum);
1490 
1491                 LOG_DBG("setting palette #%d = HLS %hhu/%hhu/%hhu (0x%06x)",
1492                         term->sixel.color_idx, hue, lum, sat, rgb);
1493 
1494                 term->sixel.palette[term->sixel.color_idx] = 0xffu << 24 | rgb;
1495                 break;
1496             }
1497 
1498             case 2: {  /* RGB */
1499                 uint8_t r = 255 * min(c1, 100) / 100;
1500                 uint8_t g = 255 * min(c2, 100) / 100;
1501                 uint8_t b = 255 * min(c3, 100) / 100;
1502 
1503                 LOG_DBG("setting palette #%d = RGB %hhu/%hhu/%hhu",
1504                         term->sixel.color_idx, r, g, b);
1505 
1506                 term->sixel.palette[term->sixel.color_idx] =
1507                     0xffu << 24 | r << 16 | g << 8 | b;
1508                 break;
1509             }
1510             }
1511         } else
1512             term->sixel.color = term->sixel.palette[term->sixel.color_idx];
1513 
1514         term->sixel.state = SIXEL_DECSIXEL;
1515         decsixel(term, c);
1516         break;
1517     }
1518     }
1519 }
1520 
1521 void
sixel_put(struct terminal * term,uint8_t c)1522 sixel_put(struct terminal *term, uint8_t c)
1523 {
1524     switch (term->sixel.state) {
1525     case SIXEL_DECSIXEL: decsixel(term, c); break;
1526     case SIXEL_DECGRA: decgra(term, c); break;
1527     case SIXEL_DECGRI: decgri(term, c); break;
1528     case SIXEL_DECGCI: decgci(term, c); break;
1529     }
1530 
1531     count++;
1532 }
1533 
1534 void
sixel_colors_report_current(struct terminal * term)1535 sixel_colors_report_current(struct terminal *term)
1536 {
1537     char reply[24];
1538     size_t n = xsnprintf(reply, sizeof(reply), "\033[?1;0;%uS", term->sixel.palette_size);
1539     term_to_slave(term, reply, n);
1540     LOG_DBG("query response for current color count: %u", term->sixel.palette_size);
1541 }
1542 
1543 void
sixel_colors_reset(struct terminal * term)1544 sixel_colors_reset(struct terminal *term)
1545 {
1546     LOG_DBG("sixel palette size reset to %u", SIXEL_MAX_COLORS);
1547 
1548     free(term->sixel.palette);
1549     term->sixel.palette = NULL;
1550 
1551     term->sixel.palette_size = SIXEL_MAX_COLORS;
1552     sixel_colors_report_current(term);
1553 }
1554 
1555 void
sixel_colors_set(struct terminal * term,unsigned count)1556 sixel_colors_set(struct terminal *term, unsigned count)
1557 {
1558     unsigned new_palette_size = min(max(2, count), SIXEL_MAX_COLORS);
1559     LOG_DBG("sixel palette size set to %u", new_palette_size);
1560 
1561     free(term->sixel.private_palette);
1562     free(term->sixel.shared_palette);
1563     term->sixel.private_palette = NULL;
1564     term->sixel.shared_palette = NULL;
1565 
1566     term->sixel.palette_size = new_palette_size;
1567     sixel_colors_report_current(term);
1568 }
1569 
1570 void
sixel_colors_report_max(struct terminal * term)1571 sixel_colors_report_max(struct terminal *term)
1572 {
1573     char reply[24];
1574     size_t n = xsnprintf(reply, sizeof(reply), "\033[?1;0;%uS", SIXEL_MAX_COLORS);
1575     term_to_slave(term, reply, n);
1576     LOG_DBG("query response for max color count: %u", SIXEL_MAX_COLORS);
1577 }
1578 
1579 void
sixel_geometry_report_current(struct terminal * term)1580 sixel_geometry_report_current(struct terminal *term)
1581 {
1582     char reply[64];
1583     size_t n = xsnprintf(reply, sizeof(reply), "\033[?2;0;%u;%uS",
1584              min(term->cols * term->cell_width, term->sixel.max_width),
1585              min(term->rows * term->cell_height, term->sixel.max_height));
1586     term_to_slave(term, reply, n);
1587 
1588     LOG_DBG("query response for current sixel geometry: %ux%u",
1589             term->sixel.max_width, term->sixel.max_height);
1590 }
1591 
1592 void
sixel_geometry_reset(struct terminal * term)1593 sixel_geometry_reset(struct terminal *term)
1594 {
1595     LOG_DBG("sixel geometry reset to %ux%u", SIXEL_MAX_WIDTH, SIXEL_MAX_HEIGHT);
1596     term->sixel.max_width = SIXEL_MAX_WIDTH;
1597     term->sixel.max_height = SIXEL_MAX_HEIGHT;
1598     sixel_geometry_report_current(term);
1599 }
1600 
1601 void
sixel_geometry_set(struct terminal * term,unsigned width,unsigned height)1602 sixel_geometry_set(struct terminal *term, unsigned width, unsigned height)
1603 {
1604     LOG_DBG("sixel geometry set to %ux%u", width, height);
1605     term->sixel.max_width = width;
1606     term->sixel.max_height = height;
1607     sixel_geometry_report_current(term);
1608 }
1609 
1610 void
sixel_geometry_report_max(struct terminal * term)1611 sixel_geometry_report_max(struct terminal *term)
1612 {
1613     unsigned max_width = term->sixel.max_width;
1614     unsigned max_height = term->sixel.max_height;
1615 
1616     char reply[64];
1617     size_t n = xsnprintf(reply, sizeof(reply), "\033[?2;0;%u;%uS", max_width, max_height);
1618     term_to_slave(term, reply, n);
1619 
1620     LOG_DBG("query response for max sixel geometry: %ux%u",
1621             max_width, max_height);
1622 }
1623