1 /* $OpenBSD$ */ 2 3 /* 4 * Copyright (c) 2019 Nicholas Marriott <nicholas.marriott@gmail.com> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/types.h> 20 21 #include <stdlib.h> 22 #include <string.h> 23 24 #include "tmux.h" 25 26 #define SIXEL_WIDTH_LIMIT 10000 27 #define SIXEL_HEIGHT_LIMIT 10000 28 29 struct sixel_line { 30 u_int x; 31 uint16_t *data; 32 }; 33 34 struct sixel_image { 35 u_int x; 36 u_int y; 37 u_int xpixel; 38 u_int ypixel; 39 40 u_int *colours; 41 u_int ncolours; 42 43 u_int dx; 44 u_int dy; 45 u_int dc; 46 47 struct sixel_line *lines; 48 }; 49 50 static int 51 sixel_parse_expand_lines(struct sixel_image *si, u_int y) 52 { 53 if (y <= si->y) 54 return (0); 55 if (y > SIXEL_HEIGHT_LIMIT) 56 return (1); 57 si->lines = xrecallocarray(si->lines, si->y, y, sizeof *si->lines); 58 si->y = y; 59 return (0); 60 } 61 62 static int 63 sixel_parse_expand_line(struct sixel_image *si, struct sixel_line *sl, u_int x) 64 { 65 if (x <= sl->x) 66 return (0); 67 if (x > SIXEL_WIDTH_LIMIT) 68 return (1); 69 if (x > si->x) 70 si->x = x; 71 sl->data = xrecallocarray(sl->data, sl->x, si->x, sizeof *sl->data); 72 sl->x = si->x; 73 return (0); 74 } 75 76 static u_int 77 sixel_get_pixel(struct sixel_image *si, u_int x, u_int y) 78 { 79 struct sixel_line *sl; 80 81 if (y >= si->y) 82 return (0); 83 sl = &si->lines[y]; 84 if (x >= sl->x) 85 return (0); 86 return (sl->data[x]); 87 } 88 89 static int 90 sixel_set_pixel(struct sixel_image *si, u_int x, u_int y, u_int c) 91 { 92 struct sixel_line *sl; 93 94 if (sixel_parse_expand_lines(si, y + 1) != 0) 95 return (1); 96 sl = &si->lines[y]; 97 if (sixel_parse_expand_line(si, sl, x + 1) != 0) 98 return (1); 99 sl->data[x] = c; 100 return (0); 101 } 102 103 static int 104 sixel_parse_write(struct sixel_image *si, u_int ch) 105 { 106 struct sixel_line *sl; 107 u_int i; 108 109 if (sixel_parse_expand_lines(si, si->dy + 6) != 0) 110 return (1); 111 sl = &si->lines[si->dy]; 112 113 for (i = 0; i < 6; i++) { 114 if (sixel_parse_expand_line(si, sl, si->dx + 1) != 0) 115 return (1); 116 if (ch & (1 << i)) 117 sl->data[si->dx] = si->dc; 118 sl++; 119 } 120 return (0); 121 } 122 123 static const char * 124 sixel_parse_attributes(struct sixel_image *si, const char *cp, const char *end) 125 { 126 const char *last; 127 char *endptr; 128 u_int x, y; 129 130 last = cp; 131 while (last != end) { 132 if (*last != ';' && (*last < '0' || *last > '9')) 133 break; 134 last++; 135 } 136 strtoul(cp, &endptr, 10); 137 if (endptr == last || *endptr != ';') 138 return (last); 139 strtoul(endptr + 1, &endptr, 10); 140 if (endptr == last) 141 return (last); 142 if (*endptr != ';') { 143 log_debug("%s: missing ;", __func__); 144 return (NULL); 145 } 146 147 x = strtoul(endptr + 1, &endptr, 10); 148 if (endptr == last || *endptr != ';') { 149 log_debug("%s: missing ;", __func__); 150 return (NULL); 151 } 152 if (x > SIXEL_WIDTH_LIMIT) { 153 log_debug("%s: image is too wide", __func__); 154 return (NULL); 155 } 156 y = strtoul(endptr + 1, &endptr, 10); 157 if (endptr != last) { 158 log_debug("%s: extra ;", __func__); 159 return (NULL); 160 } 161 if (y > SIXEL_HEIGHT_LIMIT) { 162 log_debug("%s: image is too tall", __func__); 163 return (NULL); 164 } 165 166 si->x = x; 167 sixel_parse_expand_lines(si, y); 168 169 return (last); 170 } 171 172 static const char * 173 sixel_parse_colour(struct sixel_image *si, const char *cp, const char *end) 174 { 175 const char *last; 176 char *endptr; 177 u_int c, type, r, g, b; 178 179 last = cp; 180 while (last != end) { 181 if (*last != ';' && (*last < '0' || *last > '9')) 182 break; 183 last++; 184 } 185 186 c = strtoul(cp, &endptr, 10); 187 if (c > SIXEL_COLOUR_REGISTERS) { 188 log_debug("%s: too many colours", __func__); 189 return (NULL); 190 } 191 si->dc = c + 1; 192 if (endptr == last || *endptr != ';') 193 return (last); 194 195 type = strtoul(endptr + 1, &endptr, 10); 196 if (endptr == last || *endptr != ';') { 197 log_debug("%s: missing ;", __func__); 198 return (NULL); 199 } 200 r = strtoul(endptr + 1, &endptr, 10); 201 if (endptr == last || *endptr != ';') { 202 log_debug("%s: missing ;", __func__); 203 return (NULL); 204 } 205 g = strtoul(endptr + 1, &endptr, 10); 206 if (endptr == last || *endptr != ';') { 207 log_debug("%s: missing ;", __func__); 208 return (NULL); 209 } 210 b = strtoul(endptr + 1, &endptr, 10); 211 if (endptr != last) { 212 log_debug("%s: missing ;", __func__); 213 return (NULL); 214 } 215 216 if (type != 1 && type != 2) { 217 log_debug("%s: invalid type %d", __func__, type); 218 return (NULL); 219 } 220 if (c + 1 > si->ncolours) { 221 si->colours = xrecallocarray(si->colours, si->ncolours, c + 1, 222 sizeof *si->colours); 223 si->ncolours = c + 1; 224 } 225 si->colours[c] = (type << 24) | (r << 16) | (g << 8) | b; 226 return (last); 227 } 228 229 static const char * 230 sixel_parse_repeat(struct sixel_image *si, const char *cp, const char *end) 231 { 232 const char *last; 233 char tmp[32], ch; 234 u_int n = 0, i; 235 const char *errstr = NULL; 236 237 last = cp; 238 while (last != end) { 239 if (*last < '0' || *last > '9') 240 break; 241 tmp[n++] = *last++; 242 if (n == (sizeof tmp) - 1) { 243 log_debug("%s: repeat not terminated", __func__); 244 return (NULL); 245 } 246 } 247 if (n == 0 || last == end) { 248 log_debug("%s: repeat not terminated", __func__); 249 return (NULL); 250 } 251 tmp[n] = '\0'; 252 253 n = strtonum(tmp, 1, SIXEL_WIDTH_LIMIT, &errstr); 254 if (n == 0 || errstr != NULL) { 255 log_debug("%s: repeat too wide", __func__); 256 return (NULL); 257 } 258 259 ch = (*last++) - 0x3f; 260 for (i = 0; i < n; i++) { 261 if (sixel_parse_write(si, ch) != 0) { 262 log_debug("%s: width limit reached", __func__); 263 return (NULL); 264 } 265 si->dx++; 266 } 267 return (last); 268 } 269 270 struct sixel_image * 271 sixel_parse(const char *buf, size_t len, u_int xpixel, u_int ypixel) 272 { 273 struct sixel_image *si; 274 const char *cp = buf, *end = buf + len; 275 char ch; 276 277 if (len == 0 || len == 1 || *cp++ != 'q') { 278 log_debug("%s: empty image", __func__); 279 return (NULL); 280 } 281 282 si = xcalloc (1, sizeof *si); 283 si->xpixel = xpixel; 284 si->ypixel = ypixel; 285 286 while (cp != end) { 287 ch = *cp++; 288 switch (ch) { 289 case '"': 290 cp = sixel_parse_attributes(si, cp, end); 291 if (cp == NULL) 292 goto bad; 293 break; 294 case '#': 295 cp = sixel_parse_colour(si, cp, end); 296 if (cp == NULL) 297 goto bad; 298 break; 299 case '!': 300 cp = sixel_parse_repeat(si, cp, end); 301 if (cp == NULL) 302 goto bad; 303 break; 304 case '-': 305 si->dx = 0; 306 si->dy += 6; 307 break; 308 case '$': 309 si->dx = 0; 310 break; 311 default: 312 if (ch < 0x20) 313 break; 314 if (ch < 0x3f || ch > 0x7e) 315 goto bad; 316 if (sixel_parse_write(si, ch - 0x3f) != 0) { 317 log_debug("%s: width limit reached", __func__); 318 goto bad; 319 } 320 si->dx++; 321 break; 322 } 323 } 324 325 if (si->x == 0 || si->y == 0) 326 goto bad; 327 return (si); 328 329 bad: 330 free(si); 331 return (NULL); 332 } 333 334 void 335 sixel_free(struct sixel_image *si) 336 { 337 u_int y; 338 339 for (y = 0; y < si->y; y++) 340 free(si->lines[y].data); 341 free(si->lines); 342 343 free(si->colours); 344 free(si); 345 } 346 347 void 348 sixel_log(struct sixel_image *si) 349 { 350 struct sixel_line *sl; 351 char s[SIXEL_WIDTH_LIMIT + 1]; 352 u_int i, x, y, cx, cy; 353 354 sixel_size_in_cells(si, &cx, &cy); 355 log_debug("%s: image %ux%u (%ux%u)", __func__, si->x, si->y, cx, cy); 356 for (i = 0; i < si->ncolours; i++) 357 log_debug("%s: colour %u is %07x", __func__, i, si->colours[i]); 358 for (y = 0; y < si->y; y++) { 359 sl = &si->lines[y]; 360 for (x = 0; x < si->x; x++) { 361 if (x >= sl->x) 362 s[x] = '_'; 363 else if (sl->data[x] != 0) 364 s[x] = '0' + (sl->data[x] - 1) % 10; 365 else 366 s[x] = '.'; 367 } 368 s[x] = '\0'; 369 log_debug("%s: %4u: %s", __func__, y, s); 370 } 371 } 372 373 void 374 sixel_size_in_cells(struct sixel_image *si, u_int *x, u_int *y) 375 { 376 if ((si->x % si->xpixel) == 0) 377 *x = (si->x / si->xpixel); 378 else 379 *x = 1 + (si->x / si->xpixel); 380 if ((si->y % si->ypixel) == 0) 381 *y = (si->y / si->ypixel); 382 else 383 *y = 1 + (si->y / si->ypixel); 384 } 385 386 struct sixel_image * 387 sixel_scale(struct sixel_image *si, u_int xpixel, u_int ypixel, u_int ox, 388 u_int oy, u_int sx, u_int sy, int colours) 389 { 390 struct sixel_image *new; 391 u_int cx, cy, pox, poy, psx, psy, tsx, tsy, px, py; 392 u_int x, y, i; 393 394 /* 395 * We want to get the section of the image at ox,oy in image cells and 396 * map it onto the same size in terminal cells, remembering that we 397 * can only draw vertical sections of six pixels. 398 */ 399 400 sixel_size_in_cells(si, &cx, &cy); 401 if (ox >= cx) 402 return (NULL); 403 if (oy >= cy) 404 return (NULL); 405 if (ox + sx >= cx) 406 sx = cx - ox; 407 if (oy + sy >= cy) 408 sy = cy - oy; 409 410 if (xpixel == 0) 411 xpixel = si->xpixel; 412 if (ypixel == 0) 413 ypixel = si->ypixel; 414 415 pox = ox * si->xpixel; 416 poy = oy * si->ypixel; 417 psx = sx * si->xpixel; 418 psy = sy * si->ypixel; 419 420 tsx = sx * xpixel; 421 tsy = ((sy * ypixel) / 6) * 6; 422 423 new = xcalloc (1, sizeof *si); 424 new->xpixel = xpixel; 425 new->ypixel = ypixel; 426 427 for (y = 0; y < tsy; y++) { 428 py = poy + ((double)y * psy / tsy); 429 for (x = 0; x < tsx; x++) { 430 px = pox + ((double)x * psx / tsx); 431 sixel_set_pixel(new, x, y, sixel_get_pixel(si, px, py)); 432 } 433 } 434 435 if (colours) { 436 new->colours = xmalloc(si->ncolours * sizeof *new->colours); 437 for (i = 0; i < si->ncolours; i++) 438 new->colours[i] = si->colours[i]; 439 new->ncolours = si->ncolours; 440 } 441 return (new); 442 } 443 444 static void 445 sixel_print_add(char **buf, size_t *len, size_t *used, const char *s, 446 size_t slen) 447 { 448 if (*used + slen >= *len + 1) { 449 (*len) *= 2; 450 *buf = xrealloc(*buf, *len); 451 } 452 memcpy(*buf + *used, s, slen); 453 (*used) += slen; 454 } 455 456 static void 457 sixel_print_repeat(char **buf, size_t *len, size_t *used, u_int count, char ch) 458 { 459 char tmp[16]; 460 size_t tmplen; 461 462 if (count == 1) 463 sixel_print_add(buf, len, used, &ch, 1); 464 else if (count == 2) { 465 sixel_print_add(buf, len, used, &ch, 1); 466 sixel_print_add(buf, len, used, &ch, 1); 467 } else if (count == 3) { 468 sixel_print_add(buf, len, used, &ch, 1); 469 sixel_print_add(buf, len, used, &ch, 1); 470 sixel_print_add(buf, len, used, &ch, 1); 471 } else if (count != 0) { 472 tmplen = xsnprintf(tmp, sizeof tmp, "!%u%c", count, ch); 473 sixel_print_add(buf, len, used, tmp, tmplen); 474 } 475 } 476 477 char * 478 sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size) 479 { 480 char *buf, tmp[64], *contains, data = 0, last = 0; 481 size_t len, used = 0, tmplen; 482 u_int *colours, ncolours, i, c, x, y, count; 483 struct sixel_line *sl; 484 485 if (map != NULL) { 486 colours = map->colours; 487 ncolours = map->ncolours; 488 } else { 489 colours = si->colours; 490 ncolours = si->ncolours; 491 } 492 493 if (ncolours == 0) 494 return (NULL); 495 contains = xcalloc(1, ncolours); 496 497 len = 8192; 498 buf = xmalloc(len); 499 500 sixel_print_add(&buf, &len, &used, "\033Pq", 3); 501 502 tmplen = xsnprintf(tmp, sizeof tmp, "\"1;1;%u;%u", si->x, si->y); 503 sixel_print_add(&buf, &len, &used, tmp, tmplen); 504 505 for (i = 0; i < ncolours; i++) { 506 c = colours[i]; 507 tmplen = xsnprintf(tmp, sizeof tmp, "#%u;%u;%u;%u;%u", 508 i, c >> 24, (c >> 16) & 0xff, (c >> 8) & 0xff, c & 0xff); 509 sixel_print_add(&buf, &len, &used, tmp, tmplen); 510 } 511 512 for (y = 0; y < si->y; y += 6) { 513 memset(contains, 0, ncolours); 514 for (x = 0; x < si->x; x++) { 515 for (i = 0; i < 6; i++) { 516 if (y + i >= si->y) 517 break; 518 sl = &si->lines[y + i]; 519 if (x < sl->x && sl->data[x] != 0) 520 contains[sl->data[x] - 1] = 1; 521 } 522 } 523 524 for (c = 0; c < ncolours; c++) { 525 if (!contains[c]) 526 continue; 527 tmplen = xsnprintf(tmp, sizeof tmp, "#%u", c); 528 sixel_print_add(&buf, &len, &used, tmp, tmplen); 529 530 count = 0; 531 for (x = 0; x < si->x; x++) { 532 data = 0; 533 for (i = 0; i < 6; i++) { 534 if (y + i >= si->y) 535 break; 536 sl = &si->lines[y + i]; 537 if (x < sl->x && sl->data[x] == c + 1) 538 data |= (1 << i); 539 } 540 data += 0x3f; 541 if (data != last) { 542 sixel_print_repeat(&buf, &len, &used, 543 count, last); 544 last = data; 545 count = 1; 546 } else 547 count++; 548 } 549 sixel_print_repeat(&buf, &len, &used, count, data); 550 sixel_print_add(&buf, &len, &used, "$", 1); 551 } 552 553 if (buf[used - 1] == '$') 554 used--; 555 if (buf[used - 1] != '-') 556 sixel_print_add(&buf, &len, &used, "-", 1); 557 } 558 if (buf[used - 1] == '$' || buf[used - 1] == '-') 559 used--; 560 561 sixel_print_add(&buf, &len, &used, "\033\\", 2); 562 563 buf[used] = '\0'; 564 if (size != NULL) 565 *size = used; 566 567 free(contains); 568 return (buf); 569 } 570 571 struct screen * 572 sixel_to_screen(struct sixel_image *si) 573 { 574 struct screen *s; 575 struct screen_write_ctx ctx; 576 struct grid_cell gc; 577 u_int x, y, sx, sy; 578 579 sixel_size_in_cells(si, &sx, &sy); 580 581 s = xmalloc(sizeof *s); 582 screen_init(s, sx, sy, 0); 583 584 memcpy(&gc, &grid_default_cell, sizeof gc); 585 gc.attr |= (GRID_ATTR_CHARSET|GRID_ATTR_DIM); 586 utf8_set(&gc.data, '~'); 587 588 screen_write_start(&ctx, s); 589 if (sx == 1 || sy == 1) { 590 for (y = 0; y < sy; y++) { 591 for (x = 0; x < sx; x++) 592 grid_view_set_cell(s->grid, x, y, &gc); 593 } 594 } else { 595 screen_write_box(&ctx, sx, sy, BOX_LINES_DEFAULT, NULL, NULL); 596 for (y = 1; y < sy - 1; y++) { 597 for (x = 1; x < sx - 1; x++) 598 grid_view_set_cell(s->grid, x, y, &gc); 599 } 600 } 601 screen_write_stop(&ctx); 602 return (s); 603 } 604