From 3555e81fee4a069bf4886007082f676b8777f6f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 27 Jun 2023 14:25:55 +0200 Subject: [PATCH] sixel: special case parsing of images with an aspect ratio of 1:1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Images with an aspect ratio of 1:1 are by far the most common (though not the default). It makes a lot of sense, performance wise, to special case them. Specifically, the sixel_add() function benefits greatly from this, as it is the inner most, most heavily executed function when parsing a sixel image. sixel_add_many() also benefits, since allows us to drop a multiplication. Since sixel_add_many() always called first (no other call sites call sixel_add() directly), this has a noticeable effect on performance. Another thing that helps (though not as much), and not specifically with AR 1:1 images, is special casing DECGRI a bit. Up until now, it simply updated the current sixel parameter value. The problem is that the default parameter value is 0. But, a value of 0 should be treated as 1. By adding a special ‘repeat_count’ variable to the sixel struct, we can initialize it to ‘1’ when we see DECGRI, and then simply overwrite it as the parameter value gets updated. This allows us to drop an if..else when emitting the sixel. --- dcs.c | 3 +- sixel.c | 171 ++++++++++++++++++++++++++++++++++++++++++++++------- sixel.h | 5 +- terminal.h | 1 + 4 files changed, 153 insertions(+), 27 deletions(-) diff --git a/dcs.c b/dcs.c index fb4a14b6..7ce1a868 100644 --- a/dcs.c +++ b/dcs.c @@ -427,8 +427,7 @@ dcs_hook(struct terminal *term, uint8_t final) int p2 = vt_param_get(term, 1,0); int p3 = vt_param_get(term, 2, 0); - sixel_init(term, p1, p2, p3); - term->vt.dcs.put_handler = &sixel_put; + term->vt.dcs.put_handler = sixel_init(term, p1, p2, p3); term->vt.dcs.unhook_handler = &sixel_unhook; break; } diff --git a/sixel.c b/sixel.c index b0746a9a..e454032c 100644 --- a/sixel.c +++ b/sixel.c @@ -16,6 +16,9 @@ static size_t count; +static void sixel_put_generic(struct terminal *term, uint8_t c); +static void sixel_put_ar_11(struct terminal *term, uint8_t c); + void sixel_fini(struct terminal *term) { @@ -24,7 +27,7 @@ sixel_fini(struct terminal *term) free(term->sixel.shared_palette); } -void +sixel_put sixel_init(struct terminal *term, int p1, int p2, int p3) { /* @@ -119,6 +122,7 @@ sixel_init(struct terminal *term, int p1, int p2, int p3) : bg; count = 0; + return pan == 1 && pad == 1 ? &sixel_put_ar_11 : &sixel_put_generic; } void @@ -1327,7 +1331,8 @@ resize(struct terminal *term, int new_width, int new_height) } static void -sixel_add(struct terminal *term, int col, int width, uint32_t color, uint8_t sixel) +sixel_add_generic(struct terminal *term, int col, int width, uint32_t color, + uint8_t sixel) { xassert(term->sixel.pos.col < term->sixel.image.width); xassert(term->sixel.pos.row < term->sixel.image.height); @@ -1348,7 +1353,26 @@ sixel_add(struct terminal *term, int col, int width, uint32_t color, uint8_t six } static void -sixel_add_many(struct terminal *term, uint8_t c, unsigned count) +sixel_add_ar_11(struct terminal *term, int col, int width, uint32_t color, + uint8_t sixel) +{ + xassert(term->sixel.pos.col < term->sixel.image.width); + xassert(term->sixel.pos.row < term->sixel.image.height); + xassert(term->sixel.pan == 1); + + size_t ofs = term->sixel.row_byte_ofs + col; + uint32_t *data = &term->sixel.image.data[ofs]; + + for (int i = 0; i < 6; i++, sixel >>= 1, data += width) { + if (sixel & 1) + *data = color; + } + + xassert(sixel == 0); +} + +static void +sixel_add_many_generic(struct terminal *term, uint8_t c, unsigned count) { int col = term->sixel.pos.col; int width = term->sixel.image.width; @@ -1362,14 +1386,38 @@ sixel_add_many(struct terminal *term, uint8_t c, unsigned count) } uint32_t color = term->sixel.color; - for (unsigned i = 0; i < count; i++, col++) - sixel_add(term, col, width, color, c); + for (unsigned i = 0; i < count; i++, col++) { + /* TODO: is it worth dynamically dispatching to either generic or AR-11? */ + sixel_add_generic(term, col, width, color, c); + } term->sixel.pos.col = col; } static void -decsixel(struct terminal *term, uint8_t c) +sixel_add_many_ar_11(struct terminal *term, uint8_t c, unsigned count) +{ + xassert(term->sixel.pan == 1); + xassert(term->sixel.pad == 1); + + int col = term->sixel.pos.col; + int width = term->sixel.image.width; + + if (unlikely(col + count - 1 >= width)) { + resize_horizontally(term, col + count); + width = term->sixel.image.width; + count = min(count, max(width - col, 0)); + } + + uint32_t color = term->sixel.color; + for (unsigned i = 0; i < count; i++, col++) + sixel_add_ar_11(term, col, width, color, c); + + term->sixel.pos.col = col; +} + +static void +decsixel_generic(struct terminal *term, uint8_t c) { switch (c) { case '"': @@ -1382,6 +1430,7 @@ decsixel(struct terminal *term, uint8_t c) term->sixel.state = SIXEL_DECGRI; term->sixel.param = 0; term->sixel.param_idx = 0; + term->sixel.repeat_count = 1; break; case '#': @@ -1424,7 +1473,7 @@ decsixel(struct terminal *term, uint8_t c) case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}': case '~': - sixel_add_many(term, c - 63, 1); + sixel_add_many_generic(term, c - 63, 1); break; case ' ': @@ -1438,6 +1487,29 @@ decsixel(struct terminal *term, uint8_t c) } } +static void +decsixel_ar_11(struct terminal *term, uint8_t c) +{ + switch (c) { + case '?': case '@': case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': + case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': + case '[': case '\\': case ']': case '^': case '_': case '`': case 'a': + case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': + case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': + case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': + case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}': + case '~': + sixel_add_many_ar_11(term, c - 63, 1); + break; + + default: + decsixel_generic(term, c); + break; + } +} + static void decgra(struct terminal *term, uint8_t c) { @@ -1483,21 +1555,34 @@ decgra(struct terminal *term, uint8_t c) } term->sixel.state = SIXEL_DECSIXEL; - decsixel(term, c); + + /* Update DCS put handler, since pan/pad may have changed */ + term->vt.dcs.put_handler = pan == 1 && pad == 1 + ? &sixel_put_ar_11 + : &sixel_put_generic; + + if (likely(pan == 1 && pad == 1)) + decsixel_ar_11(term, c); + else + decsixel_generic(term, c); + break; } } } static void -decgri(struct terminal *term, uint8_t c) +decgri_generic(struct terminal *term, uint8_t c) { switch (c) { case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - term->sixel.param *= 10; - term->sixel.param += c - '0'; + case '5': case '6': case '7': case '8': case '9': { + unsigned param = term->sixel.param; + param *= 10; + param += c - '0'; + term->sixel.repeat_count = term->sixel.param = param; break; + } case '?': case '@': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': @@ -1509,18 +1594,41 @@ decgri(struct terminal *term, uint8_t c) case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}': case '~': { - unsigned count = term->sixel.param; - if (likely(count > 0)) - sixel_add_many(term, c - 63, count); - else if (unlikely(count == 0)) - sixel_add_many(term, c - 63, 1); + const unsigned count = term->sixel.repeat_count; + sixel_add_many_generic(term, c - 63, count); term->sixel.state = SIXEL_DECSIXEL; break; } default: term->sixel.state = SIXEL_DECSIXEL; - sixel_put(term, c); + term->vt.dcs.put_handler(term, c); + break; + } +} + +static void +decgri_ar_11(struct terminal *term, uint8_t c) +{ + switch (c) { + case '?': case '@': case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': + case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': + case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': + case '[': case '\\': case ']': case '^': case '_': case '`': case 'a': + case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': + case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': + case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': + case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}': + case '~': { + const unsigned count = term->sixel.repeat_count; + sixel_add_many_ar_11(term, c - 63, count); + term->sixel.state = SIXEL_DECSIXEL; + break; + } + + default: + decgri_generic(term, c); break; } } @@ -1601,19 +1709,36 @@ decgci(struct terminal *term, uint8_t c) term->sixel.color = term->sixel.palette[term->sixel.color_idx]; term->sixel.state = SIXEL_DECSIXEL; - decsixel(term, c); + + if (likely(term->sixel.pan == 1 && term->sixel.pad == 1)) + decsixel_ar_11(term, c); + else + decsixel_generic(term, c); break; } } } -void -sixel_put(struct terminal *term, uint8_t c) +static void +sixel_put_generic(struct terminal *term, uint8_t c) { switch (term->sixel.state) { - case SIXEL_DECSIXEL: decsixel(term, c); break; + case SIXEL_DECSIXEL: decsixel_generic(term, c); break; case SIXEL_DECGRA: decgra(term, c); break; - case SIXEL_DECGRI: decgri(term, c); break; + case SIXEL_DECGRI: decgri_generic(term, c); break; + case SIXEL_DECGCI: decgci(term, c); break; + } + + count++; +} + +static void +sixel_put_ar_11(struct terminal *term, uint8_t c) +{ + switch (term->sixel.state) { + case SIXEL_DECSIXEL: decsixel_ar_11(term, c); break; + case SIXEL_DECGRA: decgra(term, c); break; + case SIXEL_DECGRI: decgri_ar_11(term, c); break; case SIXEL_DECGCI: decgci(term, c); break; } diff --git a/sixel.h b/sixel.h index f72b4dc4..efd64908 100644 --- a/sixel.h +++ b/sixel.h @@ -6,10 +6,11 @@ #define SIXEL_MAX_WIDTH 10000u #define SIXEL_MAX_HEIGHT 10000u +typedef void (*sixel_put)(struct terminal *term, uint8_t c); + void sixel_fini(struct terminal *term); -void sixel_init(struct terminal *term, int p1, int p2, int p3); -void sixel_put(struct terminal *term, uint8_t c); +sixel_put sixel_init(struct terminal *term, int p1, int p2, int p3); void sixel_unhook(struct terminal *term); void sixel_destroy(struct sixel *sixel); diff --git a/terminal.h b/terminal.h index 220070e1..1cb62686 100644 --- a/terminal.h +++ b/terminal.h @@ -649,6 +649,7 @@ struct terminal { unsigned params[5]; /* Collected parameters, for RASTER, COLOR_SPEC */ unsigned param; /* Currently collecting parameter, for RASTER, COLOR_SPEC and REPEAT */ unsigned param_idx; /* Parameters seen */ + unsigned repeat_count; bool transparent_bg; uint32_t default_bg;