You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1177 lines
35 KiB

  1. #include "sixel.h"
  2. #include <string.h>
  3. #include <limits.h>
  4. #define LOG_MODULE "sixel"
  5. #define LOG_ENABLE_DBG 0
  6. #include "log.h"
  7. #include "render.h"
  8. #include "sixel-hls.h"
  9. #include "util.h"
  10. #include "xmalloc.h"
  11. static size_t count;
  12. void
  13. sixel_fini(struct terminal *term)
  14. {
  15. free(term->sixel.palette);
  16. }
  17. static uint32_t
  18. color_with_alpha(const struct terminal *term, uint32_t color)
  19. {
  20. uint16_t alpha = color == term->colors.bg ? term->colors.alpha : 0xffff;
  21. return (alpha / 256u) << 24 | color;
  22. }
  23. void
  24. sixel_init(struct terminal *term)
  25. {
  26. assert(term->sixel.image.data == NULL);
  27. assert(term->sixel.palette_size <= SIXEL_MAX_COLORS);
  28. term->sixel.state = SIXEL_DECSIXEL;
  29. term->sixel.pos = (struct coord){0, 0};
  30. term->sixel.color_idx = 0;
  31. term->sixel.max_col = 0;
  32. term->sixel.param = 0;
  33. term->sixel.param_idx = 0;
  34. memset(term->sixel.params, 0, sizeof(term->sixel.params));
  35. term->sixel.image.data = xmalloc(1 * 6 * sizeof(term->sixel.image.data[0]));
  36. term->sixel.image.width = 1;
  37. term->sixel.image.height = 6;
  38. term->sixel.image.autosize = true;
  39. if (term->sixel.palette == NULL) {
  40. term->sixel.palette = xcalloc(
  41. term->sixel.palette_size, sizeof(term->sixel.palette[0]));
  42. }
  43. for (size_t i = 0; i < 1 * 6; i++)
  44. term->sixel.image.data[i] = color_with_alpha(term, term->colors.bg);
  45. count = 0;
  46. /* TODO: default palette */
  47. }
  48. void
  49. sixel_destroy(struct sixel *sixel)
  50. {
  51. pixman_image_unref(sixel->pix);
  52. free(sixel->data);
  53. sixel->pix = NULL;
  54. sixel->data = NULL;
  55. }
  56. void
  57. sixel_destroy_all(struct terminal *term)
  58. {
  59. tll_foreach(term->normal.sixel_images, it)
  60. sixel_destroy(&it->item);
  61. tll_foreach(term->alt.sixel_images, it)
  62. sixel_destroy(&it->item);
  63. tll_free(term->normal.sixel_images);
  64. tll_free(term->alt.sixel_images);
  65. }
  66. static void
  67. sixel_erase(struct terminal *term, struct sixel *sixel)
  68. {
  69. for (int i = 0; i < sixel->rows; i++) {
  70. int r = (sixel->pos.row + i) & (term->grid->num_rows - 1);
  71. struct row *row = term->grid->rows[r];
  72. if (row == NULL) {
  73. /* A resize/reflow may cause row to now be unallocated */
  74. continue;
  75. }
  76. row->dirty = true;
  77. for (int c = 0; c < term->grid->num_cols; c++)
  78. row->cells[c].attrs.clean = 0;
  79. }
  80. sixel_destroy(sixel);
  81. }
  82. /*
  83. * Calculates the scrollback relative row number, given an absolute row number.
  84. *
  85. * The scrollback relative row number 0 is the *first*, and *oldest*
  86. * row in the scrollback history (and thus the *first* row to be
  87. * scrolled out). Thus, a higher number means further *down* in the
  88. * scrollback, with the *highest* number being at the bottom of the
  89. * screen, where new input appears.
  90. */
  91. static int
  92. rebase_row(const struct terminal *term, int abs_row)
  93. {
  94. int scrollback_start = term->grid->offset + term->rows;
  95. int rebased_row = abs_row - scrollback_start + term->grid->num_rows;
  96. rebased_row &= term->grid->num_rows - 1;
  97. return rebased_row;
  98. }
  99. /*
  100. * Verify the sixels are sorted correctly.
  101. *
  102. * The sixels are sorted on their *end* row, in descending order. This
  103. * invariant means the most recent sixels appear first in the list.
  104. */
  105. static void
  106. verify_list_order(const struct terminal *term)
  107. {
  108. #if defined(_DEBUG)
  109. int prev_row = INT_MAX;
  110. int prev_col = -1;
  111. int prev_col_count = 0;
  112. /* To aid debugging */
  113. size_t idx = 0;
  114. tll_foreach(term->grid->sixel_images, it) {
  115. int row = rebase_row(term, it->item.pos.row + it->item.rows - 1);
  116. int col = it->item.pos.col;
  117. int col_count = it->item.cols;
  118. assert(row <= prev_row);
  119. if (row == prev_row) {
  120. /* Allowed to be on the same row only if their columns
  121. * don't overlap */
  122. assert(col + col_count <= prev_col ||
  123. prev_col + prev_col_count <= col);
  124. }
  125. prev_row = row;
  126. prev_col = col;
  127. prev_col_count = col_count;
  128. idx++;
  129. }
  130. #endif
  131. }
  132. /*
  133. * Verifies there aren't any sixels that cross the scrollback
  134. * wrap-around. This invariant means a sixel's absolute row numbers
  135. * are strictly increasing.
  136. */
  137. static void
  138. verify_no_wraparound_crossover(const struct terminal *term)
  139. {
  140. #if defined(_DEBUG)
  141. tll_foreach(term->grid->sixel_images, it) {
  142. const struct sixel *six = &it->item;
  143. assert(six->pos.row >= 0);
  144. assert(six->pos.row < term->grid->num_rows);
  145. int end = (six->pos.row + six->rows - 1) & (term->grid->num_rows - 1);
  146. assert(end >= six->pos.row);
  147. }
  148. #endif
  149. }
  150. /*
  151. * Verify there aren't any sixels that cross the scrollback end. This
  152. * invariant means a sixel's rebased row numbers are strictly
  153. * increasing.
  154. */
  155. static void
  156. verify_scrollback_consistency(const struct terminal *term)
  157. {
  158. #if defined(_DEBUG)
  159. tll_foreach(term->grid->sixel_images, it) {
  160. const struct sixel *six = &it->item;
  161. int last_row = -1;
  162. for (int i = 0; i < six->rows; i++) {
  163. int row_no = rebase_row(term, six->pos.row + i);
  164. if (last_row != -1)
  165. assert(last_row < row_no);
  166. last_row = row_no;
  167. }
  168. }
  169. #endif
  170. }
  171. /*
  172. * Verifies no sixel overlap with any other sixels.
  173. */
  174. static void
  175. verify_no_overlap(const struct terminal *term)
  176. {
  177. #if defined(_DEBUG)
  178. tll_foreach(term->grid->sixel_images, it) {
  179. const struct sixel *six1 = &it->item;
  180. pixman_region32_t rect1;
  181. pixman_region32_init_rect(
  182. &rect1, six1->pos.col, six1->pos.row, six1->cols, six1->rows);
  183. tll_foreach(term->grid->sixel_images, it2) {
  184. const struct sixel *six2 = &it2->item;
  185. if (six1 == six2)
  186. continue;
  187. pixman_region32_t rect2;
  188. pixman_region32_init_rect(
  189. &rect2, six2->pos.col,
  190. six2->pos.row, six2->cols, six2->rows);
  191. pixman_region32_t intersection;
  192. pixman_region32_init(&intersection);
  193. pixman_region32_intersect(&intersection, &rect1, &rect2);
  194. assert(!pixman_region32_not_empty(&intersection));
  195. pixman_region32_fini(&intersection);
  196. pixman_region32_fini(&rect2);
  197. }
  198. pixman_region32_fini(&rect1);
  199. }
  200. #endif
  201. }
  202. static void
  203. verify_sixels(const struct terminal *term)
  204. {
  205. verify_no_wraparound_crossover(term);
  206. verify_scrollback_consistency(term);
  207. verify_no_overlap(term);
  208. verify_list_order(term);
  209. }
  210. static void
  211. sixel_insert(struct terminal *term, struct sixel sixel)
  212. {
  213. int end_row = rebase_row(term, sixel.pos.row + sixel.rows - 1);
  214. tll_foreach(term->grid->sixel_images, it) {
  215. if (rebase_row(term, it->item.pos.row + it->item.rows - 1) < end_row) {
  216. tll_insert_before(term->grid->sixel_images, it, sixel);
  217. goto out;
  218. }
  219. }
  220. tll_push_back(term->grid->sixel_images, sixel);
  221. out:
  222. #if defined(LOG_ENABLE_DBG) && LOG_ENABLE_DBG
  223. LOG_DBG("sixel list after insertion:");
  224. tll_foreach(term->grid->sixel_images, it) {
  225. LOG_DBG(" rows=%d+%d", it->item.pos.row, it->item.rows);
  226. }
  227. #endif
  228. verify_sixels(term);
  229. }
  230. void
  231. sixel_scroll_up(struct terminal *term, int rows)
  232. {
  233. if (likely(tll_length(term->grid->sixel_images) == 0))
  234. return;
  235. tll_rforeach(term->grid->sixel_images, it) {
  236. struct sixel *six = &it->item;
  237. int six_start = rebase_row(term, six->pos.row);
  238. if (six_start < rows) {
  239. sixel_erase(term, six);
  240. tll_remove(term->grid->sixel_images, it);
  241. } else {
  242. /*
  243. * Unfortunately, we cannot break here.
  244. *
  245. * The sixels are sorted on their *end* row. This means
  246. * there may be a sixel with a top row that will be
  247. * scrolled out *anywhere* in the list (think of a huuuuge
  248. * sixel that covers the entire scrollback)
  249. */
  250. //break;
  251. }
  252. }
  253. verify_sixels(term);
  254. }
  255. void
  256. sixel_scroll_down(struct terminal *term, int rows)
  257. {
  258. if (likely(tll_length(term->grid->sixel_images) == 0))
  259. return;
  260. assert(term->grid->num_rows >= rows);
  261. tll_foreach(term->grid->sixel_images, it) {
  262. struct sixel *six = &it->item;
  263. int six_end = rebase_row(term, six->pos.row + six->rows - 1);
  264. if (six_end >= term->grid->num_rows - rows) {
  265. sixel_erase(term, six);
  266. tll_remove(term->grid->sixel_images, it);
  267. } else
  268. break;
  269. }
  270. verify_sixels(term);
  271. }
  272. static void
  273. sixel_overwrite(struct terminal *term, struct sixel *six,
  274. int row, int col, int height, int width)
  275. {
  276. pixman_region32_t six_rect;
  277. pixman_region32_init_rect(
  278. &six_rect,
  279. six->pos.col * term->cell_width, six->pos.row * term->cell_height,
  280. six->width, six->height);
  281. pixman_region32_t overwrite_rect;
  282. pixman_region32_init_rect(
  283. &overwrite_rect,
  284. col * term->cell_width, row * term->cell_height,
  285. width * term->cell_width, height * term->cell_height);
  286. #if defined(_DEBUG)
  287. pixman_region32_t intersection;
  288. pixman_region32_init(&intersection);
  289. pixman_region32_intersect(&intersection, &six_rect, &overwrite_rect);
  290. assert(pixman_region32_not_empty(&intersection));
  291. pixman_region32_fini(&intersection);
  292. #endif
  293. pixman_region32_t diff;
  294. pixman_region32_init(&diff);
  295. pixman_region32_subtract(&diff, &six_rect, &overwrite_rect);
  296. pixman_region32_fini(&six_rect);
  297. pixman_region32_fini(&overwrite_rect);
  298. int n_rects = -1;
  299. pixman_box32_t *boxes = pixman_region32_rectangles(&diff, &n_rects);
  300. for (int i = 0; i < n_rects; i++) {
  301. LOG_DBG("box #%d: x1=%d, y1=%d, x2=%d, y2=%d", i,
  302. boxes[i].x1, boxes[i].y1, boxes[i].x2, boxes[i].y2);
  303. assert(boxes[i].x1 % term->cell_width == 0);
  304. assert(boxes[i].y1 % term->cell_height == 0);
  305. /* New image's position, in cells */
  306. const int new_col = boxes[i].x1 / term->cell_width;
  307. const int new_row = boxes[i].y1 / term->cell_height;
  308. assert(new_row < term->grid->num_rows);
  309. /* New image's width and height, in pixels */
  310. const int new_width = boxes[i].x2 - boxes[i].x1;
  311. const int new_height = boxes[i].y2 - boxes[i].y1;
  312. uint32_t *new_data = xmalloc(new_width * new_height * sizeof(uint32_t));
  313. const uint32_t *old_data = six->data;
  314. /* Pixel offsets into old image backing memory */
  315. const int x_ofs = boxes[i].x1 - six->pos.col * term->cell_width;
  316. const int y_ofs = boxes[i].y1 - six->pos.row * term->cell_height;
  317. /* Copy image data, one row at a time */
  318. for (size_t j = 0; j < new_height; j++) {
  319. memcpy(
  320. &new_data[(0 + j) * new_width],
  321. &old_data[(y_ofs + j) * six->width + x_ofs],
  322. new_width * sizeof(uint32_t));
  323. }
  324. pixman_image_t *new_pix = pixman_image_create_bits_no_clear(
  325. PIXMAN_a8r8g8b8,
  326. new_width, new_height, new_data, new_width * sizeof(uint32_t));
  327. struct sixel new_six = {
  328. .data = new_data,
  329. .pix = new_pix,
  330. .width = new_width,
  331. .height = new_height,
  332. .pos = {.col = new_col, .row = new_row},
  333. .cols = (new_width + term->cell_width - 1) / term->cell_width,
  334. .rows = (new_height + term->cell_height - 1) / term->cell_height,
  335. };
  336. #if defined(_DEBUG)
  337. /* Assert we don't cross the scrollback wrap-around */
  338. const int new_end = new_six.pos.row + new_six.rows - 1;
  339. assert(new_end < term->grid->num_rows);
  340. #endif
  341. sixel_insert(term, new_six);
  342. }
  343. pixman_region32_fini(&diff);
  344. }
  345. /* Row numbers are absolute */
  346. static void
  347. _sixel_overwrite_by_rectangle(
  348. struct terminal *term, int row, int col, int height, int width)
  349. {
  350. verify_sixels(term);
  351. #if defined(_DEBUG)
  352. pixman_region32_t overwrite_rect;
  353. pixman_region32_init_rect(&overwrite_rect, col, row, width, height);
  354. #endif
  355. const int start = row;
  356. const int end = row + height - 1;
  357. /* We should never generate scrollback wrapping sixels */
  358. assert(end < term->grid->num_rows);
  359. const int scrollback_rel_start = rebase_row(term, start);
  360. bool UNUSED would_have_breaked = false;
  361. tll_foreach(term->grid->sixel_images, it) {
  362. struct sixel *six = &it->item;
  363. const int six_start = six->pos.row;
  364. const int six_end = (six_start + six->rows - 1);
  365. const int six_scrollback_rel_end = rebase_row(term, six_end);
  366. /* We should never generate scrollback wrapping sixels */
  367. assert(six_end < term->grid->num_rows);
  368. if (six_scrollback_rel_end < scrollback_rel_start) {
  369. /* All remaining sixels are *before* our rectangle */
  370. would_have_breaked = true;
  371. break;
  372. }
  373. #if defined(_DEBUG)
  374. pixman_region32_t six_rect;
  375. pixman_region32_init_rect(&six_rect, six->pos.col, six->pos.row, six->cols, six->rows);
  376. pixman_region32_t intersection;
  377. pixman_region32_init(&intersection);
  378. pixman_region32_intersect(&intersection, &six_rect, &overwrite_rect);
  379. const bool collides = pixman_region32_not_empty(&intersection);
  380. #else
  381. const bool UNUSED collides = false;
  382. #endif
  383. if ((start <= six_start && end >= six_start) || /* Crosses sixel start boundary */
  384. (start <= six_end && end >= six_end) || /* Crosses sixel end boundary */
  385. (start >= six_start && end <= six_end)) /* Fully within sixel range */
  386. {
  387. const int col_start = six->pos.col;
  388. const int col_end = six->pos.col + six->cols - 1;
  389. if ((col <= col_start && col + width - 1 >= col_start) ||
  390. (col <= col_end && col + width - 1 >= col_end) ||
  391. (col >= col_start && col + width - 1 <= col_end))
  392. {
  393. assert(!would_have_breaked);
  394. struct sixel to_be_erased = *six;
  395. tll_remove(term->grid->sixel_images, it);
  396. sixel_overwrite(term, &to_be_erased, start, col, height, width);
  397. sixel_erase(term, &to_be_erased);
  398. } else
  399. assert(!collides);
  400. } else
  401. assert(!collides);
  402. #if defined(_DEBUG)
  403. pixman_region32_fini(&intersection);
  404. pixman_region32_fini(&six_rect);
  405. #endif
  406. }
  407. #if defined(_DEBUG)
  408. pixman_region32_fini(&overwrite_rect);
  409. #endif
  410. }
  411. void
  412. sixel_overwrite_by_rectangle(
  413. struct terminal *term, int row, int col, int height, int width)
  414. {
  415. if (likely(tll_length(term->grid->sixel_images) == 0))
  416. return;
  417. const int start = (term->grid->offset + row) & (term->grid->num_rows - 1);
  418. const int end = (start + height - 1) & (term->grid->num_rows - 1);
  419. const bool wraps = end < start;
  420. if (wraps) {
  421. int rows_to_wrap_around = term->grid->num_rows - start;
  422. assert(height - rows_to_wrap_around > 0);
  423. _sixel_overwrite_by_rectangle(term, start, col, rows_to_wrap_around, width);
  424. _sixel_overwrite_by_rectangle(term, 0, col, height - rows_to_wrap_around, width);
  425. } else
  426. _sixel_overwrite_by_rectangle(term, start, col, height, width);
  427. }
  428. /* Row numbers are relative to grid offset */
  429. void
  430. sixel_overwrite_by_row(struct terminal *term, int _row, int col, int width)
  431. {
  432. assert(col >= 0);
  433. assert(_row >= 0);
  434. assert(_row < term->rows);
  435. assert(col >= 0);
  436. assert(col < term->grid->num_cols);
  437. if (likely(tll_length(term->grid->sixel_images) == 0))
  438. return;
  439. if (col + width > term->grid->num_cols)
  440. width = term->grid->num_cols - col;
  441. const int row = (term->grid->offset + _row) & (term->grid->num_rows - 1);
  442. const int scrollback_rel_row = rebase_row(term, row);
  443. tll_foreach(term->grid->sixel_images, it) {
  444. struct sixel *six = &it->item;
  445. const int six_start = six->pos.row;
  446. const int six_end = (six_start + six->rows - 1) & (term->grid->num_rows - 1);
  447. /* We should never generate scrollback wrapping sixels */
  448. assert(six_end >= six_start);
  449. const int six_scrollback_rel_end = rebase_row(term, six_end);
  450. if (six_scrollback_rel_end < scrollback_rel_row) {
  451. /* All remaining sixels are *before* "our" row */
  452. break;
  453. }
  454. if (row >= six_start && row <= six_end) {
  455. const int col_start = six->pos.col;
  456. const int col_end = six->pos.col + six->cols - 1;
  457. if ((col <= col_start && col + width - 1 >= col_start) ||
  458. (col <= col_end && col + width - 1 >= col_end) ||
  459. (col >= col_start && col + width - 1 <= col_end))
  460. {
  461. struct sixel to_be_erased = *six;
  462. tll_remove(term->grid->sixel_images, it);
  463. sixel_overwrite(term, &to_be_erased, row, col, 1, width);
  464. sixel_erase(term, &to_be_erased);
  465. }
  466. }
  467. }
  468. }
  469. void
  470. sixel_overwrite_at_cursor(struct terminal *term, int width)
  471. {
  472. sixel_overwrite_by_row(
  473. term, term->grid->cursor.point.row, term->grid->cursor.point.col, width);
  474. }
  475. void
  476. sixel_cell_size_changed(struct terminal *term)
  477. {
  478. struct grid *g = term->grid;
  479. term->grid = &term->normal;
  480. tll_foreach(term->normal.sixel_images, it) {
  481. struct sixel *six = &it->item;
  482. six->rows = (six->height + term->cell_height - 1) / term->cell_height;
  483. six->cols = (six->width + term->cell_width - 1) / term->cell_width;
  484. }
  485. term->grid = &term->alt;
  486. tll_foreach(term->alt.sixel_images, it) {
  487. struct sixel *six = &it->item;
  488. six->rows = (six->height + term->cell_height - 1) / term->cell_height;
  489. six->cols = (six->width + term->cell_width - 1) / term->cell_width;
  490. }
  491. term->grid = g;
  492. }
  493. void
  494. sixel_reflow(struct terminal *term)
  495. {
  496. struct grid *g = term->grid;
  497. for (size_t i = 0; i < 2; i++) {
  498. struct grid *grid = i == 0 ? &term->normal : &term->alt;
  499. term->grid = grid;
  500. /* Need the “real” list to be empty from the beginning */
  501. tll(struct sixel) copy = tll_init();
  502. tll_foreach(grid->sixel_images, it)
  503. tll_push_back(copy, it->item);
  504. tll_free(grid->sixel_images);
  505. tll_rforeach(copy, it) {
  506. struct sixel *six = &it->item;
  507. int start = six->pos.row;
  508. int end = (start + six->rows - 1) & (grid->num_rows - 1);
  509. if (end < start) {
  510. /* Crosses scrollback wrap-around */
  511. /* TODO: split image */
  512. sixel_destroy(six);
  513. continue;
  514. }
  515. if (six->rows > grid->num_rows) {
  516. /* Image too large */
  517. /* TODO: keep bottom part? */
  518. sixel_destroy(six);
  519. continue;
  520. }
  521. /* Drop sixels that now cross the current scrollback end
  522. * border. This is similar to a sixel that have been
  523. * scrolled out */
  524. /* TODO: should be possible to optimize this */
  525. bool sixel_destroyed = false;
  526. int last_row = -1;
  527. for (int j = 0; j < six->rows; j++) {
  528. int row_no = rebase_row(term, six->pos.row + j);
  529. if (last_row != -1 && last_row >= row_no) {
  530. sixel_destroy(six);
  531. sixel_destroyed = true;
  532. break;
  533. }
  534. last_row = row_no;
  535. }
  536. if (sixel_destroyed) {
  537. LOG_WARN("destroyed sixel that now crossed history");
  538. continue;
  539. }
  540. /* Sixels that didn’t overlap may now do so, which isn’t
  541. * allowed of course */
  542. _sixel_overwrite_by_rectangle(
  543. term, six->pos.row, six->pos.col, six->rows, six->cols);
  544. sixel_insert(term, it->item);
  545. }
  546. tll_free(copy);
  547. }
  548. term->grid = g;
  549. }
  550. void
  551. sixel_unhook(struct terminal *term)
  552. {
  553. int pixel_row_idx = 0;
  554. int pixel_rows_left = term->sixel.image.height;
  555. const int stride = term->sixel.image.width * sizeof(uint32_t);
  556. /*
  557. * Need to 'remember' current cursor column.
  558. *
  559. * If we split up the sixel (to avoid scrollback wrap-around), we
  560. * will emit a carriage-return (after several linefeeds), which
  561. * will reset the cursor column to 0. If we use _that_ column for
  562. * the subsequent image parts, the image will look sheared.
  563. */
  564. const int start_col = term->grid->cursor.point.col;
  565. /* We do not allow sixels to cross the scrollback wrap-around, as
  566. * this makes intersection calculations much more complicated */
  567. while (pixel_rows_left > 0) {
  568. const struct coord *cursor = &term->grid->cursor.point;
  569. const int cur_row = (term->grid->offset + cursor->row) & (term->grid->num_rows - 1);
  570. const int rows_avail = term->grid->num_rows - cur_row;
  571. const int pixel_rows_avail = rows_avail * term->cell_height;
  572. const int width = term->sixel.image.width;
  573. const int height = min(pixel_rows_left, pixel_rows_avail);
  574. uint32_t *img_data;
  575. if (pixel_row_idx == 0)
  576. img_data = term->sixel.image.data;
  577. else {
  578. img_data = xmalloc(height * stride);
  579. memcpy(
  580. img_data,
  581. &((uint8_t *)term->sixel.image.data)[pixel_row_idx * stride],
  582. height * stride);
  583. }
  584. struct sixel image = {
  585. .data = img_data,
  586. .width = width,
  587. .height = height,
  588. .rows = (height + term->cell_height - 1) / term->cell_height,
  589. .cols = (width + term->cell_width - 1) / term->cell_width,
  590. .pos = (struct coord){start_col, cur_row},
  591. };
  592. assert(image.rows < term->grid->num_rows);
  593. assert(image.pos.row + image.rows - 1 < term->grid->num_rows);
  594. LOG_DBG("generating %dx%d pixman image at %d-%d",
  595. image.width, image.height,
  596. image.pos.row, image.pos.row + image.rows);
  597. image.pix = pixman_image_create_bits_no_clear(
  598. PIXMAN_a8r8g8b8,
  599. image.width, image.height,
  600. img_data, stride);
  601. /* Allocate space *first*, then insert */
  602. for (size_t i = 0; i < image.rows; i++)
  603. term_linefeed(term);
  604. term_carriage_return(term);
  605. _sixel_overwrite_by_rectangle(
  606. term, image.pos.row, image.pos.col, image.rows, image.cols);
  607. sixel_insert(term, image);
  608. pixel_row_idx += height;
  609. pixel_rows_left -= height;
  610. }
  611. term->sixel.image.data = NULL;
  612. term->sixel.image.width = 0;
  613. term->sixel.image.height = 0;
  614. term->sixel.max_col = 0;
  615. term->sixel.pos = (struct coord){0, 0};
  616. LOG_DBG("you now have %zu sixels in current grid",
  617. tll_length(term->grid->sixel_images));
  618. render_refresh(term);
  619. }
  620. static unsigned
  621. max_width(const struct terminal *term)
  622. {
  623. /* foot extension - treat 0 to mean current terminal size */
  624. return term->sixel.max_width == 0
  625. ? term->cols * term->cell_width
  626. : term->sixel.max_width;
  627. }
  628. static unsigned
  629. max_height(const struct terminal *term)
  630. {
  631. /* foot extension - treat 0 to mean current terminal size */
  632. return term->sixel.max_height == 0
  633. ? term->rows * term->cell_height
  634. : term->sixel.max_height;
  635. }
  636. static bool
  637. resize(struct terminal *term, int new_width, int new_height)
  638. {
  639. if (!term->sixel.image.autosize)
  640. return false;
  641. LOG_DBG("resizing image: %dx%d -> %dx%d",
  642. term->sixel.image.width, term->sixel.image.height,
  643. new_width, new_height);
  644. uint32_t *old_data = term->sixel.image.data;
  645. const int old_width = term->sixel.image.width;
  646. const int old_height = term->sixel.image.height;
  647. int alloc_new_width = new_width;
  648. int alloc_new_height = (new_height + 6 - 1) / 6 * 6;
  649. assert(alloc_new_height >= new_height);
  650. assert(alloc_new_height - new_height < 6);
  651. assert(new_width >= old_width);
  652. assert(new_height >= old_height);
  653. uint32_t *new_data = NULL;
  654. if (new_width == old_width) {
  655. /* Width (and thus stride) is the same, so we can simply
  656. * re-alloc the existing buffer */
  657. new_data = realloc(old_data, alloc_new_width * alloc_new_height * sizeof(uint32_t));
  658. if (new_data == NULL) {
  659. LOG_ERRNO("failed to reallocate sixel image buffer");
  660. return false;
  661. }
  662. assert(new_height > old_height);
  663. } else {
  664. /* Width (and thus stride) change - need to allocate a new buffer */
  665. assert(new_width > old_width);
  666. new_data = xmalloc(alloc_new_width * alloc_new_height * sizeof(uint32_t));
  667. /* Copy old rows, and initialize new columns to background color */
  668. for (int r = 0; r < old_height; r++) {
  669. memcpy(&new_data[r * new_width], &old_data[r * old_width], old_width * sizeof(uint32_t));
  670. for (int c = old_width; c < new_width; c++)
  671. new_data[r * new_width + c] = color_with_alpha(term, term->colors.bg);
  672. }
  673. free(old_data);
  674. }
  675. /* Initialize new rows to background color */
  676. for (int r = old_height; r < new_height; r++) {
  677. for (int c = 0; c < new_width; c++)
  678. new_data[r * new_width + c] = color_with_alpha(term, term->colors.bg);
  679. }
  680. assert(new_data != NULL);
  681. term->sixel.image.data = new_data;
  682. term->sixel.image.width = new_width;
  683. term->sixel.image.height = new_height;
  684. return true;
  685. }
  686. static void
  687. sixel_add(struct terminal *term, uint32_t color, uint8_t sixel)
  688. {
  689. //LOG_DBG("adding sixel %02hhx using color 0x%06x", sixel, color);
  690. if (term->sixel.pos.col >= max_width(term) ||
  691. term->sixel.pos.row * 6 + 5 >= max_height(term))
  692. {
  693. return;
  694. }
  695. if (term->sixel.pos.col >= term->sixel.image.width ||
  696. term->sixel.pos.row * 6 + 5 >= (term->sixel.image.height + 6 - 1) / 6 * 6)
  697. {
  698. int width = max(
  699. term->sixel.image.width,
  700. max(term->sixel.max_col, term->sixel.pos.col + 1));
  701. int height = max(
  702. term->sixel.image.height,
  703. (term->sixel.pos.row + 1) * 6);
  704. if (!resize(term, width, height))
  705. return;
  706. }
  707. for (int i = 0; i < 6; i++, sixel >>= 1) {
  708. if (sixel & 1) {
  709. size_t pixel_row = term->sixel.pos.row * 6 + i;
  710. size_t stride = term->sixel.image.width;
  711. size_t idx = pixel_row * stride + term->sixel.pos.col;
  712. term->sixel.image.data[idx] = color_with_alpha(term, color);
  713. }
  714. }
  715. assert(sixel == 0);
  716. term->sixel.pos.col++;
  717. }
  718. static void
  719. decsixel(struct terminal *term, uint8_t c)
  720. {
  721. switch (c) {
  722. case '"':
  723. term->sixel.state = SIXEL_DECGRA;
  724. term->sixel.param = 0;
  725. term->sixel.param_idx = 0;
  726. break;
  727. case '!':
  728. term->sixel.state = SIXEL_DECGRI;
  729. term->sixel.param = 0;
  730. term->sixel.param_idx = 0;
  731. break;
  732. case '#':
  733. term->sixel.state = SIXEL_DECGCI;
  734. term->sixel.color_idx = 0;
  735. term->sixel.param = 0;
  736. term->sixel.param_idx = 0;
  737. break;
  738. case '$':
  739. if (term->sixel.pos.col > term->sixel.max_col)
  740. term->sixel.max_col = term->sixel.pos.col;
  741. term->sixel.pos.col = 0;
  742. break;
  743. case '-':
  744. if (term->sixel.pos.col > term->sixel.max_col)
  745. term->sixel.max_col = term->sixel.pos.col;
  746. term->sixel.pos.row++;
  747. term->sixel.pos.col = 0;
  748. break;
  749. case '?': case '@': case 'A': case 'B': case 'C': case 'D': case 'E':
  750. case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L':
  751. case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S':
  752. case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
  753. case '[': case '\\': case ']': case '^': case '_': case '`': case 'a':
  754. case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h':
  755. case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o':
  756. case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v':
  757. case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}':
  758. case '~':
  759. sixel_add(term, term->sixel.palette[term->sixel.color_idx], c - 63);
  760. break;
  761. case ' ':
  762. case '\n':
  763. case '\r':
  764. break;
  765. default:
  766. LOG_WARN("invalid sixel character: '%c' at idx=%zu", c, count);
  767. break;
  768. }
  769. }
  770. static void
  771. decgra(struct terminal *term, uint8_t c)
  772. {
  773. switch (c) {
  774. case '0': case '1': case '2': case '3': case '4':
  775. case '5': case '6': case '7': case '8': case '9':
  776. term->sixel.param *= 10;
  777. term->sixel.param += c - '0';
  778. break;
  779. case ';':
  780. if (term->sixel.param_idx < ALEN(term->sixel.params))
  781. term->sixel.params[term->sixel.param_idx++] = term->sixel.param;
  782. term->sixel.param = 0;
  783. break;
  784. default: {
  785. if (term->sixel.param_idx < ALEN(term->sixel.params))
  786. term->sixel.params[term->sixel.param_idx++] = term->sixel.param;
  787. int nparams = term->sixel.param_idx;
  788. unsigned pan = nparams > 0 ? term->sixel.params[0] : 0;
  789. unsigned pad = nparams > 1 ? term->sixel.params[1] : 0;
  790. unsigned ph = nparams > 2 ? term->sixel.params[2] : 0;
  791. unsigned pv = nparams > 3 ? term->sixel.params[3] : 0;
  792. pan = pan > 0 ? pan : 1;
  793. pad = pad > 0 ? pad : 1;
  794. LOG_DBG("pan=%u, pad=%u (aspect ratio = %u), size=%ux%u",
  795. pan, pad, pan / pad, ph, pv);
  796. if (ph >= term->sixel.image.height && pv >= term->sixel.image.width &&
  797. ph <= max_height(term) && pv <= max_width(term))
  798. {
  799. if (resize(term, ph, pv))
  800. term->sixel.image.autosize = false;
  801. }
  802. term->sixel.state = SIXEL_DECSIXEL;
  803. sixel_put(term, c);
  804. break;
  805. }
  806. }
  807. }
  808. static void
  809. decgri(struct terminal *term, uint8_t c)
  810. {
  811. switch (c) {
  812. case '0': case '1': case '2': case '3': case '4':
  813. case '5': case '6': case '7': case '8': case '9':
  814. term->sixel.param *= 10;
  815. term->sixel.param += c - '0';
  816. break;
  817. default:
  818. //LOG_DBG("repeating '%c' %u times", c, term->sixel.param);
  819. for (unsigned i = 0; i < term->sixel.param; i++)
  820. decsixel(term, c);
  821. term->sixel.state = SIXEL_DECSIXEL;
  822. break;
  823. }
  824. }
  825. static void
  826. decgci(struct terminal *term, uint8_t c)
  827. {
  828. switch (c) {
  829. case '0': case '1': case '2': case '3': case '4':
  830. case '5': case '6': case '7': case '8': case '9':
  831. term->sixel.param *= 10;
  832. term->sixel.param += c - '0';
  833. break;
  834. case ';':
  835. if (term->sixel.param_idx < ALEN(term->sixel.params))
  836. term->sixel.params[term->sixel.param_idx++] = term->sixel.param;
  837. term->sixel.param = 0;
  838. break;
  839. default: {
  840. if (term->sixel.param_idx < ALEN(term->sixel.params))
  841. term->sixel.params[term->sixel.param_idx++] = term->sixel.param;
  842. int nparams = term->sixel.param_idx;
  843. if (nparams > 0)
  844. term->sixel.color_idx = min(term->sixel.params[0], term->sixel.palette_size - 1);
  845. if (nparams > 4) {
  846. unsigned format = term->sixel.params[1];
  847. unsigned c1 = term->sixel.params[2];
  848. unsigned c2 = term->sixel.params[3];
  849. unsigned c3 = term->sixel.params[4];
  850. switch (format) {
  851. case 1: { /* HLS */
  852. uint32_t rgb = hls_to_rgb(c1, c2, c3);
  853. LOG_DBG("setting palette #%d = HLS %hhu/%hhu/%hhu (0x%06x)",
  854. term->sixel.color_idx, c1, c2, c3, rgb);
  855. term->sixel.palette[term->sixel.color_idx] = rgb;
  856. break;
  857. }
  858. case 2: { /* RGB */
  859. uint8_t r = 255 * c1 / 100;
  860. uint8_t g = 255 * c2 / 100;
  861. uint8_t b = 255 * c3 / 100;
  862. LOG_DBG("setting palette #%d = RGB %hhu/%hhu/%hhu",
  863. term->sixel.color_idx, r, g, b);
  864. term->sixel.palette[term->sixel.color_idx] = r << 16 | g << 8 | b;
  865. break;
  866. }
  867. }
  868. }
  869. term->sixel.state = SIXEL_DECSIXEL;
  870. sixel_put(term, c);
  871. break;
  872. }
  873. }
  874. }
  875. void
  876. sixel_put(struct terminal *term, uint8_t c)
  877. {
  878. switch (term->sixel.state) {
  879. case SIXEL_DECSIXEL: decsixel(term, c); break;
  880. case SIXEL_DECGRA: decgra(term, c); break;
  881. case SIXEL_DECGRI: decgri(term, c); break;
  882. case SIXEL_DECGCI: decgci(term, c); break;
  883. }
  884. count++;
  885. }
  886. void
  887. sixel_colors_report_current(struct terminal *term)
  888. {
  889. char reply[24];
  890. snprintf(reply, sizeof(reply), "\033[?1;0;%uS", term->sixel.palette_size);
  891. term_to_slave(term, reply, strlen(reply));
  892. LOG_DBG("query response for current color count: %u", term->sixel.palette_size);
  893. }
  894. void
  895. sixel_colors_reset(struct terminal *term)
  896. {
  897. LOG_DBG("sixel palette size reset to %u", SIXEL_MAX_COLORS);
  898. free(term->sixel.palette);
  899. term->sixel.palette = NULL;
  900. term->sixel.palette_size = SIXEL_MAX_COLORS;
  901. sixel_colors_report_current(term);
  902. }
  903. void
  904. sixel_colors_set(struct terminal *term, unsigned count)
  905. {
  906. unsigned new_palette_size = min(max(2, count), SIXEL_MAX_COLORS);
  907. LOG_DBG("sixel palette size set to %u", new_palette_size);
  908. free(term->sixel.palette);
  909. term->sixel.palette = NULL;
  910. term->sixel.palette_size = new_palette_size;
  911. sixel_colors_report_current(term);
  912. }
  913. void
  914. sixel_colors_report_max(struct terminal *term)
  915. {
  916. char reply[24];
  917. snprintf(reply, sizeof(reply), "\033[?1;0;%uS", SIXEL_MAX_COLORS);
  918. term_to_slave(term, reply, strlen(reply));
  919. LOG_DBG("query response for max color count: %u", SIXEL_MAX_COLORS);
  920. }
  921. void
  922. sixel_geometry_report_current(struct terminal *term)
  923. {
  924. char reply[64];
  925. snprintf(reply, sizeof(reply), "\033[?2;0;%u;%uS",
  926. max_width(term), max_height(term));
  927. term_to_slave(term, reply, strlen(reply));
  928. LOG_DBG("query response for current sixel geometry: %ux%u",
  929. max_width(term), max_height(term));
  930. }
  931. void
  932. sixel_geometry_reset(struct terminal *term)
  933. {
  934. LOG_DBG("sixel geometry reset to %ux%u", max_width(term), max_height(term));
  935. term->sixel.max_width = 0;
  936. term->sixel.max_height = 0;
  937. sixel_geometry_report_current(term);
  938. }
  939. void
  940. sixel_geometry_set(struct terminal *term, unsigned width, unsigned height)
  941. {
  942. LOG_DBG("sixel geometry set to %ux%u", width, height);
  943. term->sixel.max_width = width;
  944. term->sixel.max_height = height;
  945. sixel_geometry_report_current(term);
  946. }
  947. void
  948. sixel_geometry_report_max(struct terminal *term)
  949. {
  950. unsigned max_width = term->cols * term->cell_width;
  951. unsigned max_height = term->rows * term->cell_height;
  952. char reply[64];
  953. snprintf(reply, sizeof(reply), "\033[?2;0;%u;%uS", max_width, max_height);
  954. term_to_slave(term, reply, strlen(reply));
  955. LOG_DBG("query response for max sixel geometry: %ux%u",
  956. max_width, max_height);
  957. }