From 8ff65230e112ace6bf2602f64c3cc9ff9c1a81a8 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 14 Oct 2024 11:05:59 +0100 Subject: [PATCH] Optimize sixel_print. Previously, the algorithm scanned each pixel several times; once to find out which colors are active, and then once for every single active color to actually construct the output string. Now it constructs the compressed sixel patterns in the first pass (now x * 12 iters), so we can reduce the second pass (the really expensive part, at active colors * x * 6 iters) to just appending these to the output buffer. From nincsnevem662 at gmail dot com in GitHub issue 4184. --- image-sixel.c | 131 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 91 insertions(+), 40 deletions(-) diff --git a/image-sixel.c b/image-sixel.c index 8fa02a82..a03c8619 100644 --- a/image-sixel.c +++ b/image-sixel.c @@ -52,6 +52,19 @@ struct sixel_image { struct sixel_line *lines; }; +struct sixel_chunk { + u_int next_x; + u_int next_y; + + u_int count; + char pattern; + char next_pattern; + + size_t len; + size_t used; + char *data; +}; + static int sixel_parse_expand_lines(struct sixel_image *si, u_int y) { @@ -496,13 +509,65 @@ sixel_print_repeat(char **buf, size_t *len, size_t *used, u_int count, char ch) } } +static void +sixel_print_compress_colors(struct sixel_image *si, struct sixel_chunk *chunks, + u_int y, u_int *active, u_int *nactive) +{ + u_int i, x, c, dx, colors[6]; + struct sixel_chunk *chunk = NULL; + struct sixel_line *sl; + + for (x = 0; x < si->x; x++) { + for (i = 0; i < 6; i++) { + colors[i] = 0; + if (y + i < si->y) { + sl = &si->lines[y + i]; + if (x < sl->x && sl->data[x] != 0) { + colors[i] = sl->data[x]; + c = sl->data[x] - 1; + chunks[c].next_pattern |= 1 << i; + } + } + } + + for (i = 0; i < 6; i++) { + if (colors[i] == 0) + continue; + + c = colors[i] - 1; + chunk = &chunks[c]; + if (chunk->next_x == x + 1) + continue; + + if (chunk->next_y < y + 1) { + chunk->next_y = y + 1; + active[(*nactive)++] = c; + } + + dx = x - chunk->next_x; + if (chunk->pattern != chunk->next_pattern || dx != 0) { + sixel_print_repeat(&chunk->data, &chunk->len, + &chunk->used, chunk->count, + chunk->pattern + 0x3f); + sixel_print_repeat(&chunk->data, &chunk->len, + &chunk->used, dx, '?'); + chunk->pattern = chunk->next_pattern; + chunk->count = 0; + } + chunk->count++; + chunk->next_pattern = 0; + chunk->next_x = x + 1; + } + } +} + char * sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size) { - char *buf, tmp[64], *contains, data, last = 0; + char *buf, tmp[64]; size_t len, used = 0, tmplen; - u_int *colours, ncolours, i, c, x, y, count; - struct sixel_line *sl; + u_int *colours, ncolours, i, c, y, *active, nactive; + struct sixel_chunk *chunks, *chunk; if (map != NULL) { colours = map->colours; @@ -514,7 +579,6 @@ sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size) if (ncolours == 0) return (NULL); - contains = xcalloc(1, ncolours); len = 8192; buf = xmalloc(len); @@ -528,59 +592,42 @@ sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size) sixel_print_add(&buf, &len, &used, tmp, tmplen); } + chunks = xcalloc(ncolours, sizeof *chunks); + active = xcalloc(ncolours, sizeof *active); + for (i = 0; i < ncolours; i++) { c = colours[i]; tmplen = xsnprintf(tmp, sizeof tmp, "#%u;%u;%u;%u;%u", i, c >> 24, (c >> 16) & 0xff, (c >> 8) & 0xff, c & 0xff); sixel_print_add(&buf, &len, &used, tmp, tmplen); + + chunk = &chunks[i]; + chunk->len = 8; + chunk->data = xmalloc(chunk->len); } for (y = 0; y < si->y; y += 6) { - memset(contains, 0, ncolours); - for (x = 0; x < si->x; x++) { - for (i = 0; i < 6; i++) { - if (y + i >= si->y) - break; - sl = &si->lines[y + i]; - if (x < sl->x && sl->data[x] != 0) - contains[sl->data[x] - 1] = 1; - } - } + nactive = 0; + sixel_print_compress_colors(si, chunks, y, active, &nactive); - for (c = 0; c < ncolours; c++) { - if (!contains[c]) - continue; + for (i = 0; i < nactive; i++) { + c = active[i]; + chunk = &chunks[c]; tmplen = xsnprintf(tmp, sizeof tmp, "#%u", c); sixel_print_add(&buf, &len, &used, tmp, tmplen); - - count = 0; - for (x = 0; x < si->x; x++) { - data = 0; - for (i = 0; i < 6; i++) { - if (y + i >= si->y) - break; - sl = &si->lines[y + i]; - if (x < sl->x && sl->data[x] == c + 1) - data |= (1 << i); - } - data += 0x3f; - if (data != last) { - sixel_print_repeat(&buf, &len, &used, - count, last); - last = data; - count = 1; - } else - count++; - } - sixel_print_repeat(&buf, &len, &used, count, data); + sixel_print_add(&buf, &len, &used, chunk->data, + chunk->used); + sixel_print_repeat(&buf, &len, &used, chunk->count, + chunk->pattern + 0x3f); sixel_print_add(&buf, &len, &used, "$", 1); + chunk->used = chunk->next_x = chunk->count = 0; } if (buf[used - 1] == '$') used--; sixel_print_add(&buf, &len, &used, "-", 1); } - if (buf[used - 1] == '$' || buf[used - 1] == '-') + if (buf[used - 1] == '-') used--; sixel_print_add(&buf, &len, &used, "\033\\", 2); @@ -589,7 +636,11 @@ sixel_print(struct sixel_image *si, struct sixel_image *map, size_t *size) if (size != NULL) *size = used; - free(contains); + for (i = 0; i < ncolours; i++) + free(chunks[i].data); + free(active); + free(chunks); + return (buf); }