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