From 6658740982782daadfc52eb032e8223cddb495c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 6 Mar 2021 19:41:28 +0100 Subject: [PATCH 01/38] sixel: store current row position in pixels, not characters --- sixel.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/sixel.c b/sixel.c index 6d57b6c4..bab34015 100644 --- a/sixel.c +++ b/sixel.c @@ -902,21 +902,20 @@ sixel_add(struct terminal *term, uint32_t color, uint8_t sixel) //LOG_DBG("adding sixel %02hhx using color 0x%06x", sixel, color); if (term->sixel.pos.col >= term->sixel.max_width || - term->sixel.pos.row * 6 + 5 >= term->sixel.max_height) + term->sixel.pos.row + 5 >= term->sixel.max_height) { return; } if (term->sixel.pos.col >= term->sixel.image.width || - term->sixel.pos.row * 6 + 5 >= (term->sixel.image.height + 6 - 1) / 6 * 6) + term->sixel.pos.row + 5 >= (term->sixel.image.height + 6 - 1) / 6 * 6) { int width = max( term->sixel.image.width, max(term->sixel.max_col, term->sixel.pos.col + 1)); int height = max( - term->sixel.image.height, - (term->sixel.pos.row + 1) * 6); + term->sixel.image.height, term->sixel.pos.row + 1); if (!resize(term, width, height)) return; @@ -924,7 +923,7 @@ sixel_add(struct terminal *term, uint32_t color, uint8_t sixel) for (int i = 0; i < 6; i++, sixel >>= 1) { if (sixel & 1) { - size_t pixel_row = term->sixel.pos.row * 6 + i; + size_t pixel_row = term->sixel.pos.row + i; size_t stride = term->sixel.image.width; size_t idx = pixel_row * stride + term->sixel.pos.col; term->sixel.image.data[idx] = color_with_alpha(term, color); @@ -967,7 +966,7 @@ decsixel(struct terminal *term, uint8_t c) case '-': if (term->sixel.pos.col > term->sixel.max_col) term->sixel.max_col = term->sixel.pos.col; - term->sixel.pos.row++; + term->sixel.pos.row += 6; term->sixel.pos.col = 0; break; From 7603ae5dc3aed92663cd8f999bb8159f0e4e46ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 6 Mar 2021 19:44:26 +0100 Subject: [PATCH 02/38] sixel: avoid multiplication inside the inner sixel emitter loop --- sixel.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sixel.c b/sixel.c index bab34015..40c5d938 100644 --- a/sixel.c +++ b/sixel.c @@ -921,13 +921,13 @@ sixel_add(struct terminal *term, uint32_t color, uint8_t sixel) return; } + size_t ofs = term->sixel.pos.row * term->sixel.image.width; + ofs += term->sixel.pos.col; + for (int i = 0; i < 6; i++, sixel >>= 1) { - if (sixel & 1) { - size_t pixel_row = term->sixel.pos.row + i; - size_t stride = term->sixel.image.width; - size_t idx = pixel_row * stride + term->sixel.pos.col; - term->sixel.image.data[idx] = color_with_alpha(term, color); - } + if (sixel & 1) + term->sixel.image.data[ofs] = color_with_alpha(term, color); + ofs += term->sixel.image.width; } xassert(sixel == 0); From 47e4cfbf5c5f016fd1051ae811e246b3b9e25579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 6 Mar 2021 19:48:37 +0100 Subject: [PATCH 03/38] sixel: ignore invalid sixel characters in DECGRI (repeat) --- sixel.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/sixel.c b/sixel.c index 40c5d938..f37c5c92 100644 --- a/sixel.c +++ b/sixel.c @@ -1049,13 +1049,23 @@ decgri(struct terminal *term, uint8_t c) term->sixel.param += c - '0'; break; - default: + 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 '~': { //LOG_DBG("repeating '%c' %u times", c, term->sixel.param); for (unsigned i = 0; i < term->sixel.param; i++) decsixel(term, c); term->sixel.state = SIXEL_DECSIXEL; break; } + } } static void From 869743060ec57133ac8c13ec7484d8c9b237f02a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 6 Mar 2021 19:49:04 +0100 Subject: [PATCH 04/38] sixel: pre-calculate color before calling sixel_add() This improves performance of DECGRI, since we now only need to calculate the color once for the entire repeat sequence. --- sixel.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/sixel.c b/sixel.c index f37c5c92..6fd74a86 100644 --- a/sixel.c +++ b/sixel.c @@ -926,7 +926,7 @@ sixel_add(struct terminal *term, uint32_t color, uint8_t sixel) for (int i = 0; i < 6; i++, sixel >>= 1) { if (sixel & 1) - term->sixel.image.data[ofs] = color_with_alpha(term, color); + term->sixel.image.data[ofs] = color; ofs += term->sixel.image.width; } @@ -980,7 +980,11 @@ 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(term, term->sixel.palette[term->sixel.color_idx], c - 63); + sixel_add( + term, + color_with_alpha( + term, term->sixel.palette[term->sixel.color_idx]), + c - 63); break; case ' ': @@ -1060,8 +1064,12 @@ decgri(struct terminal *term, uint8_t c) case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}': case '~': { //LOG_DBG("repeating '%c' %u times", c, term->sixel.param); + uint32_t color = color_with_alpha( + term, term->sixel.palette[term->sixel.color_idx]); + for (unsigned i = 0; i < term->sixel.param; i++) - decsixel(term, c); + sixel_add(term, color, c - 63); + term->sixel.state = SIXEL_DECSIXEL; break; } From f143efb99991df61b0c2edda3aef078f429ad1d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 6 Mar 2021 19:52:01 +0100 Subject: [PATCH 05/38] sixel: calculate alpha when updating the palette Calculate color, with alpha, when updating the palette instead of every time we *use* the palette. --- sixel.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/sixel.c b/sixel.c index 6fd74a86..f51d7aa1 100644 --- a/sixel.c +++ b/sixel.c @@ -980,11 +980,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( - term, - color_with_alpha( - term, term->sixel.palette[term->sixel.color_idx]), - c - 63); + sixel_add(term, term->sixel.palette[term->sixel.color_idx], c - 63); break; case ' ': @@ -1064,8 +1060,7 @@ decgri(struct terminal *term, uint8_t c) case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}': case '~': { //LOG_DBG("repeating '%c' %u times", c, term->sixel.param); - uint32_t color = color_with_alpha( - term, term->sixel.palette[term->sixel.color_idx]); + uint32_t color = term->sixel.palette[term->sixel.color_idx]; for (unsigned i = 0; i < term->sixel.param; i++) sixel_add(term, color, c - 63); @@ -1131,7 +1126,8 @@ decgci(struct terminal *term, uint8_t c) LOG_DBG("setting palette #%d = HLS %hhu/%hhu/%hhu (0x%06x)", term->sixel.color_idx, hue, lum, sat, rgb); - term->sixel.palette[term->sixel.color_idx] = rgb; + term->sixel.palette[term->sixel.color_idx] = + color_with_alpha(term, rgb); break; } @@ -1143,7 +1139,8 @@ decgci(struct terminal *term, uint8_t c) LOG_DBG("setting palette #%d = RGB %hhu/%hhu/%hhu", term->sixel.color_idx, r, g, b); - term->sixel.palette[term->sixel.color_idx] = r << 16 | g << 8 | b; + term->sixel.palette[term->sixel.color_idx] = + color_with_alpha(term, r << 16 | g << 8 | b); break; } } From f175575c0920f0992a2842502d1ea3e284338c01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 6 Mar 2021 21:09:55 +0100 Subject: [PATCH 06/38] sixel: fixup row is multiple of 6 --- sixel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sixel.c b/sixel.c index f51d7aa1..d89e7a87 100644 --- a/sixel.c +++ b/sixel.c @@ -915,7 +915,7 @@ sixel_add(struct terminal *term, uint32_t color, uint8_t sixel) max(term->sixel.max_col, term->sixel.pos.col + 1)); int height = max( - term->sixel.image.height, term->sixel.pos.row + 1); + term->sixel.image.height, term->sixel.pos.row + 6); if (!resize(term, width, height)) return; From e94f1085727c0450563a05154c6485fb7d32ec8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 6 Mar 2021 21:18:11 +0100 Subject: [PATCH 07/38] sixel: resize: always round up height to a multiple of 6 Sixels are always a multiple of six. --- sixel.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sixel.c b/sixel.c index d89e7a87..7cc9f63c 100644 --- a/sixel.c +++ b/sixel.c @@ -848,10 +848,8 @@ resize(struct terminal *term, int new_width, int new_height) const int old_width = term->sixel.image.width; const int old_height = term->sixel.image.height; - int alloc_new_width = new_width; - int alloc_new_height = (new_height + 6 - 1) / 6 * 6; - xassert(alloc_new_height >= new_height); - xassert(alloc_new_height - new_height < 6); + new_height = (new_height + 6 - 1) / 6 * 6; + xassert(new_height % 6 == 0); uint32_t *new_data = NULL; @@ -859,7 +857,7 @@ resize(struct terminal *term, int new_width, int new_height) /* Width (and thus stride) is the same, so we can simply * re-alloc the existing buffer */ - new_data = realloc(old_data, alloc_new_width * alloc_new_height * sizeof(uint32_t)); + new_data = realloc(old_data, new_width * new_height * sizeof(uint32_t)); if (new_data == NULL) { LOG_ERRNO("failed to reallocate sixel image buffer"); return false; @@ -870,7 +868,7 @@ resize(struct terminal *term, int new_width, int new_height) } else { /* Width (and thus stride) change - need to allocate a new buffer */ xassert(new_width > old_width); - new_data = xmalloc(alloc_new_width * alloc_new_height * sizeof(uint32_t)); + new_data = xmalloc(new_width * new_height * sizeof(uint32_t)); /* Copy old rows, and initialize new columns to background color */ for (int r = 0; r < min(old_height, new_height); r++) { From 5a93fc30ca035658268c6bb5e6a4527ebd60b76d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 6 Mar 2021 21:18:43 +0100 Subject: [PATCH 08/38] sixel: add: simplify check for resize needed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since the image height is always a multiple of 6, there’s no need to round up the image height. --- sixel.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sixel.c b/sixel.c index 7cc9f63c..115d2139 100644 --- a/sixel.c +++ b/sixel.c @@ -906,7 +906,7 @@ sixel_add(struct terminal *term, uint32_t color, uint8_t sixel) } if (term->sixel.pos.col >= term->sixel.image.width || - term->sixel.pos.row + 5 >= (term->sixel.image.height + 6 - 1) / 6 * 6) + term->sixel.pos.row >= term->sixel.image.height) { int width = max( term->sixel.image.width, From add30a38f3aaa702c065ed0e267f72371df24313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 6 Mar 2021 21:20:11 +0100 Subject: [PATCH 09/38] scripts: generate-alt-random: sixel: emit DECGRI - Repeat Character --- scripts/generate-alt-random-writes.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scripts/generate-alt-random-writes.py b/scripts/generate-alt-random-writes.py index 2e51082b..6e7b25ab 100755 --- a/scripts/generate-alt-random-writes.py +++ b/scripts/generate-alt-random-writes.py @@ -184,17 +184,18 @@ def main(): six_width = random.randrange(width // 2) # Sixel size. Without this, sixels will be - # auto-resized on cell-boundaries. We expect programs - # to emit this sequence since otherwise you cannot get - # correctly sized images. + # auto-resized on cell-boundaries. out.write(f'"0;0;{six_width};{six_height}') for row in range(six_height // 6): # Each sixel is 6 pixels # Choose a random color out.write(f'#{random.randrange(256)}') - for col in range(six_width): - out.write(f'{random.choice(sixels)}') + if random.randrange(2): + for col in range(six_width): + out.write(f'{random.choice(sixels)}') + else: + out.write(f'!{six_width}{random.choice(sixels)}') # Next line out.write('-') From 8ec0f15a3443765bda6540de7019ef0fe1e96b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 6 Mar 2021 21:31:16 +0100 Subject: [PATCH 10/38] sixel: unhook: make sure image height is within bounds When we allocate the backing buffer, the number of allocated rows is a multiple of 6. When persisting the image, make sure its height does not exceed the current maximum height. --- sixel.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sixel.c b/sixel.c index 115d2139..eca920de 100644 --- a/sixel.c +++ b/sixel.c @@ -695,6 +695,10 @@ sixel_reflow(struct terminal *term) void sixel_unhook(struct terminal *term) { + /* The internal buffer always as a row number that is a multiple of 6 */ + term->sixel.image.height = min( + term->sixel.image.height, term->sixel.max_height); + int pixel_row_idx = 0; int pixel_rows_left = term->sixel.image.height; const int stride = term->sixel.image.width * sizeof(uint32_t); From 839b7dd32e13c7cae260007cd061a47209674994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 6 Mar 2021 21:37:16 +0100 Subject: [PATCH 11/38] =?UTF-8?q?sixel:=20resize:=20don=E2=80=99t=20resize?= =?UTF-8?q?=20beyond=20the=20current=20max=20geometry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sixel.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sixel.c b/sixel.c index eca920de..498d18f2 100644 --- a/sixel.c +++ b/sixel.c @@ -855,6 +855,20 @@ resize(struct terminal *term, int new_width, int new_height) new_height = (new_height + 6 - 1) / 6 * 6; xassert(new_height % 6 == 0); + if (new_width > term->sixel.max_width) + return false; + + /* + * Last row may be cropped by the max height, but don’t skip that + * last partial row entirely. + * + * I.e if max height is ‘4’, then allow resizing up to 6, to allow + * us to emit that last sixel row. The final image will be cropped + * to the current max geometry in unhook. + */ + if (new_height > (term->sixel.max_height + 5) / 6 * 6) + return false; + uint32_t *new_data = NULL; if (new_width == old_width) { From dfdb42138d4ab7b0328a0e3b189dbbb83daa0023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 6 Mar 2021 21:37:37 +0100 Subject: [PATCH 12/38] sixel: add: resize is already checking against the current max geometry --- sixel.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/sixel.c b/sixel.c index 498d18f2..6e7002d0 100644 --- a/sixel.c +++ b/sixel.c @@ -917,12 +917,6 @@ sixel_add(struct terminal *term, uint32_t color, uint8_t sixel) { //LOG_DBG("adding sixel %02hhx using color 0x%06x", sixel, color); - if (term->sixel.pos.col >= term->sixel.max_width || - term->sixel.pos.row + 5 >= term->sixel.max_height) - { - return; - } - if (term->sixel.pos.col >= term->sixel.image.width || term->sixel.pos.row >= term->sixel.image.height) { From 8c65c68b734b3a0711e8d0a7282d6c7d9def5ddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 6 Mar 2021 22:05:40 +0100 Subject: [PATCH 13/38] =?UTF-8?q?sixel:=20get=20rid=20of=20an=20=E2=80=98i?= =?UTF-8?q?mul=E2=80=99=20in=20sixel=5Fadd()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By storing the current row’s byte offset into the backing image in the terminal struct. This replaces the ‘imul’ with a load, which can potentially be slow. But, this data should already be in the cache. --- sixel.c | 6 ++++-- terminal.h | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/sixel.c b/sixel.c index 6e7002d0..e0d5933a 100644 --- a/sixel.c +++ b/sixel.c @@ -37,6 +37,7 @@ sixel_init(struct terminal *term) term->sixel.state = SIXEL_DECSIXEL; term->sixel.pos = (struct coord){0, 0}; + term->sixel.row_byte_ofs = 0; term->sixel.color_idx = 0; term->sixel.max_col = 0; term->sixel.param = 0; @@ -908,6 +909,7 @@ resize(struct terminal *term, int new_width, int new_height) term->sixel.image.data = new_data; term->sixel.image.width = new_width; term->sixel.image.height = new_height; + term->sixel.row_byte_ofs = term->sixel.pos.row * new_width; return true; } @@ -931,8 +933,7 @@ sixel_add(struct terminal *term, uint32_t color, uint8_t sixel) return; } - size_t ofs = term->sixel.pos.row * term->sixel.image.width; - ofs += term->sixel.pos.col; + size_t ofs = term->sixel.row_byte_ofs + term->sixel.pos.col; for (int i = 0; i < 6; i++, sixel >>= 1) { if (sixel & 1) @@ -978,6 +979,7 @@ decsixel(struct terminal *term, uint8_t c) term->sixel.max_col = term->sixel.pos.col; term->sixel.pos.row += 6; term->sixel.pos.col = 0; + term->sixel.row_byte_ofs += term->sixel.image.width * 6; break; case '?': case '@': case 'A': case 'B': case 'C': case 'D': case 'E': diff --git a/terminal.h b/terminal.h index e4e662fb..2a8bb6f9 100644 --- a/terminal.h +++ b/terminal.h @@ -521,6 +521,7 @@ struct terminal { } state; struct coord pos; /* Current sixel coordinate */ + size_t row_byte_ofs; /* Byte position into image, for current row */ int color_idx; /* Current palette index */ int max_col; /* Largest column index we've seen (aka the image width) */ uint32_t *private_palette; /* Private palette, used when private mode 1070 is enabled */ From ab70b4f16a59aedd170b6bdc01dca971c6358df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sat, 6 Mar 2021 22:07:39 +0100 Subject: [PATCH 14/38] sixel: add: use de-reference the term struct for each access to the backing image --- sixel.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/sixel.c b/sixel.c index e0d5933a..48d89a9e 100644 --- a/sixel.c +++ b/sixel.c @@ -919,14 +919,17 @@ sixel_add(struct terminal *term, uint32_t color, uint8_t sixel) { //LOG_DBG("adding sixel %02hhx using color 0x%06x", sixel, color); - if (term->sixel.pos.col >= term->sixel.image.width || - term->sixel.pos.row >= term->sixel.image.height) + int height = term->sixel.image.height; + int width = term->sixel.image.width; + + if (term->sixel.pos.col >= width || + term->sixel.pos.row >= height) { - int width = max( + width = max( term->sixel.image.width, max(term->sixel.max_col, term->sixel.pos.col + 1)); - int height = max( + height = max( term->sixel.image.height, term->sixel.pos.row + 6); if (!resize(term, width, height)) @@ -934,11 +937,11 @@ sixel_add(struct terminal *term, uint32_t color, uint8_t sixel) } size_t ofs = term->sixel.row_byte_ofs + term->sixel.pos.col; + uint32_t *data = term->sixel.image.data; - for (int i = 0; i < 6; i++, sixel >>= 1) { + for (int i = 0; i < 6; i++, sixel >>= 1, ofs += width) { if (sixel & 1) - term->sixel.image.data[ofs] = color; - ofs += term->sixel.image.width; + data[ofs] = color; } xassert(sixel == 0); From 6416319a998917149d2fb13aea9d8ae2b86e4a86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 7 Mar 2021 00:03:34 +0100 Subject: [PATCH 15/38] Revert "sixel: resize: always round up height to a multiple of 6" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 5a9d70da837c20a9641d6cbadccc962a8e5a4283. This broke jexer. Comparing with what XTerm does, we can see that it updates its image height for *each* pixel in *each* sixel. I.e. empty pixels at the bottom of a sixel will not increase the image size. Foot currently bumps the image height 6 pixels at a time, i.e. always a whole pixel. Given this, always rounding up the height to the nearest multiple of 6 (say, for example, when responding to a DECGRA command), is wrong. Now, we use the image size specified in DECGRA as is, while still allowing the image to grow beyond that if necessary. What’s left to do, if we want to behave *exactly* like XTerm, is stop growing the image with 6 pixels at a time when dynamically adjusting the image size. --- sixel.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sixel.c b/sixel.c index 48d89a9e..ae05ec45 100644 --- a/sixel.c +++ b/sixel.c @@ -853,8 +853,10 @@ resize(struct terminal *term, int new_width, int new_height) const int old_width = term->sixel.image.width; const int old_height = term->sixel.image.height; - new_height = (new_height + 6 - 1) / 6 * 6; - xassert(new_height % 6 == 0); + int alloc_new_width = new_width; + int alloc_new_height = (new_height + 6 - 1) / 6 * 6; + xassert(alloc_new_height >= new_height); + xassert(alloc_new_height - new_height < 6); if (new_width > term->sixel.max_width) return false; @@ -876,7 +878,7 @@ resize(struct terminal *term, int new_width, int new_height) /* Width (and thus stride) is the same, so we can simply * re-alloc the existing buffer */ - new_data = realloc(old_data, new_width * new_height * sizeof(uint32_t)); + new_data = realloc(old_data, alloc_new_width * alloc_new_height * sizeof(uint32_t)); if (new_data == NULL) { LOG_ERRNO("failed to reallocate sixel image buffer"); return false; @@ -887,7 +889,7 @@ resize(struct terminal *term, int new_width, int new_height) } else { /* Width (and thus stride) change - need to allocate a new buffer */ xassert(new_width > old_width); - new_data = xmalloc(new_width * new_height * sizeof(uint32_t)); + new_data = xmalloc(alloc_new_width * alloc_new_height * sizeof(uint32_t)); /* Copy old rows, and initialize new columns to background color */ for (int r = 0; r < min(old_height, new_height); r++) { From 891e0819f0ed1f3812cf54c9071c7ff38715610c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 7 Mar 2021 11:08:01 +0100 Subject: [PATCH 16/38] sixel: resize: check new width/height against max geometry early --- sixel.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sixel.c b/sixel.c index ae05ec45..9b68391c 100644 --- a/sixel.c +++ b/sixel.c @@ -849,15 +849,6 @@ resize(struct terminal *term, int new_width, int new_height) term->sixel.image.width, term->sixel.image.height, new_width, new_height); - uint32_t *old_data = term->sixel.image.data; - const int old_width = term->sixel.image.width; - const int old_height = term->sixel.image.height; - - int alloc_new_width = new_width; - int alloc_new_height = (new_height + 6 - 1) / 6 * 6; - xassert(alloc_new_height >= new_height); - xassert(alloc_new_height - new_height < 6); - if (new_width > term->sixel.max_width) return false; @@ -872,6 +863,15 @@ resize(struct terminal *term, int new_width, int new_height) if (new_height > (term->sixel.max_height + 5) / 6 * 6) return false; + uint32_t *old_data = term->sixel.image.data; + const int old_width = term->sixel.image.width; + const int old_height = term->sixel.image.height; + + int alloc_new_width = new_width; + int alloc_new_height = (new_height + 6 - 1) / 6 * 6; + xassert(alloc_new_height >= new_height); + xassert(alloc_new_height - new_height < 6); + uint32_t *new_data = NULL; if (new_width == old_width) { From 1c9c1aafc892a50300f3cfc81c06d107fbe6d840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 7 Mar 2021 11:22:08 +0100 Subject: [PATCH 17/38] =?UTF-8?q?sixel:=20adjust=20image=20height=20when?= =?UTF-8?q?=20processing=20=E2=80=98-=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sixel.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sixel.c b/sixel.c index 9b68391c..4807f2a8 100644 --- a/sixel.c +++ b/sixel.c @@ -921,23 +921,20 @@ sixel_add(struct terminal *term, uint32_t color, uint8_t sixel) { //LOG_DBG("adding sixel %02hhx using color 0x%06x", sixel, color); - int height = term->sixel.image.height; int width = term->sixel.image.width; - if (term->sixel.pos.col >= width || - term->sixel.pos.row >= height) - { + if (term->sixel.pos.col >= width) { width = max( term->sixel.image.width, max(term->sixel.max_col, term->sixel.pos.col + 1)); - height = max( - term->sixel.image.height, term->sixel.pos.row + 6); - - if (!resize(term, width, height)) + if (!resize(term, width, term->sixel.image.height)) return; } + /* Height adjustment done while processing ‘-’ */ + xassert(term->sixel.pos.row < term->sixel.image.height); + size_t ofs = term->sixel.row_byte_ofs + term->sixel.pos.col; uint32_t *data = term->sixel.image.data; @@ -985,6 +982,9 @@ decsixel(struct terminal *term, uint8_t c) term->sixel.pos.row += 6; term->sixel.pos.col = 0; term->sixel.row_byte_ofs += term->sixel.image.width * 6; + + if (term->sixel.pos.row >= term->sixel.image.height) + resize(term, term->sixel.image.width, term->sixel.pos.row + 6); break; case '?': case '@': case 'A': case 'B': case 'C': case 'D': case 'E': From 4b0e9a6bee9699b9edce6b9afc6a66734ce00a49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 7 Mar 2021 11:49:15 +0100 Subject: [PATCH 18/38] =?UTF-8?q?sixel:=20remove=20=E2=80=98max=5Fcol?= =?UTF-8?q?=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The image width *is* the maximum number of columns we’ve seen. --- sixel.c | 15 +++------------ terminal.h | 1 - 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/sixel.c b/sixel.c index 4807f2a8..507caeee 100644 --- a/sixel.c +++ b/sixel.c @@ -39,7 +39,6 @@ sixel_init(struct terminal *term) term->sixel.pos = (struct coord){0, 0}; term->sixel.row_byte_ofs = 0; term->sixel.color_idx = 0; - term->sixel.max_col = 0; term->sixel.param = 0; term->sixel.param_idx = 0; memset(term->sixel.params, 0, sizeof(term->sixel.params)); @@ -830,7 +829,6 @@ sixel_unhook(struct terminal *term) term->sixel.image.data = NULL; term->sixel.image.width = 0; term->sixel.image.height = 0; - term->sixel.max_col = 0; term->sixel.pos = (struct coord){0, 0}; free(term->sixel.private_palette); @@ -923,12 +921,9 @@ sixel_add(struct terminal *term, uint32_t color, uint8_t sixel) int width = term->sixel.image.width; - if (term->sixel.pos.col >= width) { - width = max( - term->sixel.image.width, - max(term->sixel.max_col, term->sixel.pos.col + 1)); - - if (!resize(term, width, term->sixel.image.height)) + if (unlikely(term->sixel.pos.col >= width)) { + width = term->sixel.pos.col + 1; + if (unlikely(!resize(term, width, term->sixel.image.height))) return; } @@ -971,14 +966,10 @@ decsixel(struct terminal *term, uint8_t c) break; case '$': - if (term->sixel.pos.col > term->sixel.max_col) - term->sixel.max_col = term->sixel.pos.col; term->sixel.pos.col = 0; break; case '-': - if (term->sixel.pos.col > term->sixel.max_col) - term->sixel.max_col = term->sixel.pos.col; term->sixel.pos.row += 6; term->sixel.pos.col = 0; term->sixel.row_byte_ofs += term->sixel.image.width * 6; diff --git a/terminal.h b/terminal.h index 2a8bb6f9..81893e34 100644 --- a/terminal.h +++ b/terminal.h @@ -523,7 +523,6 @@ struct terminal { struct coord pos; /* Current sixel coordinate */ size_t row_byte_ofs; /* Byte position into image, for current row */ int color_idx; /* Current palette index */ - int max_col; /* Largest column index we've seen (aka the image width) */ uint32_t *private_palette; /* Private palette, used when private mode 1070 is enabled */ uint32_t *shared_palette; /* Shared palette, used when private mode 1070 is disabled */ uint32_t *palette; /* Points to either private_palette or shared_palette */ From 6ab7052be4932ae52df9aea550c2a8c5d1373f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 7 Mar 2021 11:51:24 +0100 Subject: [PATCH 19/38] =?UTF-8?q?sixel:=20set=20=E2=80=98col=E2=80=99=20ou?= =?UTF-8?q?tside=20image=20boundaries=20when=20we=E2=80=99ve=20reached=20m?= =?UTF-8?q?ax=20height?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is to optimize sixel_add(). It already checks ‘pos’, to see if it needs to resize the image, and if the resize fails (either due to allocation failures, or because we’ve reached the maximum width), we bail out. We need to do the same thing for height. But, we don’t want to add another check to sixel_add() since its’s performance critical. By setting ‘col’ outside the image boundaries when ‘row’ reaches the maximum image height, we can “re-use” the existing code in sixel_add(). --- sixel.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/sixel.c b/sixel.c index 507caeee..6003783c 100644 --- a/sixel.c +++ b/sixel.c @@ -966,7 +966,15 @@ decsixel(struct terminal *term, uint8_t c) break; case '$': - term->sixel.pos.col = 0; + if (likely(term->sixel.pos.col <= term->sixel.max_width)) { + /* + * We set, and keep, ‘col’ outside the image boundary when + * we’ve reached the maximum image height, to avoid also + * having to check the row vs image height in the common + * path in sixel_add(). + */ + term->sixel.pos.col = 0; + } break; case '-': @@ -974,8 +982,10 @@ decsixel(struct terminal *term, uint8_t c) term->sixel.pos.col = 0; term->sixel.row_byte_ofs += term->sixel.image.width * 6; - if (term->sixel.pos.row >= term->sixel.image.height) - resize(term, term->sixel.image.width, term->sixel.pos.row + 6); + if (term->sixel.pos.row >= term->sixel.image.height) { + if (!resize(term, term->sixel.image.width, term->sixel.pos.row + 6)) + term->sixel.pos.col = term->sixel.max_width + 1; + } break; case '?': case '@': case 'A': case 'B': case 'C': case 'D': case 'E': From d35963f584acce3d2493e24348215d60345c861e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 7 Mar 2021 12:02:12 +0100 Subject: [PATCH 20/38] sixel: resize: width is no longer a multiple of 6 --- sixel.c | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/sixel.c b/sixel.c index 6003783c..55586df5 100644 --- a/sixel.c +++ b/sixel.c @@ -695,10 +695,6 @@ sixel_reflow(struct terminal *term) void sixel_unhook(struct terminal *term) { - /* The internal buffer always as a row number that is a multiple of 6 */ - term->sixel.image.height = min( - term->sixel.image.height, term->sixel.max_height); - int pixel_row_idx = 0; int pixel_rows_left = term->sixel.image.height; const int stride = term->sixel.image.width * sizeof(uint32_t); @@ -847,19 +843,11 @@ resize(struct terminal *term, int new_width, int new_height) term->sixel.image.width, term->sixel.image.height, new_width, new_height); - if (new_width > term->sixel.max_width) - return false; - - /* - * Last row may be cropped by the max height, but don’t skip that - * last partial row entirely. - * - * I.e if max height is ‘4’, then allow resizing up to 6, to allow - * us to emit that last sixel row. The final image will be cropped - * to the current max geometry in unhook. - */ - if (new_height > (term->sixel.max_height + 5) / 6 * 6) + if (new_width > term->sixel.max_width || + new_height > term->sixel.max_height) + { return false; + } uint32_t *old_data = term->sixel.image.data; const int old_width = term->sixel.image.width; From c181eb2bf68e2e776cff96b36489de5d45e67fe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 7 Mar 2021 14:44:16 +0100 Subject: [PATCH 21/38] =?UTF-8?q?sixel:=20empty=20pixels=20in=20the=20last?= =?UTF-8?q?=20sixel=20row=20doesn=E2=80=99t=20contribute=20to=20the=20imag?= =?UTF-8?q?e=20height?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All-empty pixels rows in the last sixel row should not be included in the final sixel image. This allows applications to emit sixels whose height is not a multiple of 6, and is how XTerm works. This is done by tracking the largest row number that contains non-empty pixels. In unhook, when emitting the image, the image height is adjusted based on this value. --- sixel.c | 18 +++++++++++++++++- terminal.h | 1 + 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/sixel.c b/sixel.c index 55586df5..6216523a 100644 --- a/sixel.c +++ b/sixel.c @@ -37,6 +37,7 @@ sixel_init(struct terminal *term) term->sixel.state = SIXEL_DECSIXEL; term->sixel.pos = (struct coord){0, 0}; + term->sixel.max_non_empty_row_no = 0; term->sixel.row_byte_ofs = 0; term->sixel.color_idx = 0; term->sixel.param = 0; @@ -695,6 +696,13 @@ sixel_reflow(struct terminal *term) void sixel_unhook(struct terminal *term) { + if (term->sixel.image.height > term->sixel.max_non_empty_row_no + 1) { + LOG_DBG( + "last row only partially filled, reducing image height: %d -> %d", + term->sixel.image.height, term->sixel.max_non_empty_row_no + 1); + term->sixel.image.height = term->sixel.max_non_empty_row_no + 1; + } + int pixel_row_idx = 0; int pixel_rows_left = term->sixel.image.height; const int stride = term->sixel.image.width * sizeof(uint32_t); @@ -846,6 +854,7 @@ resize(struct terminal *term, int new_width, int new_height) if (new_width > term->sixel.max_width || new_height > term->sixel.max_height) { + LOG_WARN("maximum image dimensions reached"); return false; } @@ -921,13 +930,20 @@ sixel_add(struct terminal *term, uint32_t color, uint8_t sixel) size_t ofs = term->sixel.row_byte_ofs + term->sixel.pos.col; uint32_t *data = term->sixel.image.data; + int max_non_empty_row = 0; for (int i = 0; i < 6; i++, sixel >>= 1, ofs += width) { - if (sixel & 1) + if (sixel & 1) { data[ofs] = color; + max_non_empty_row = i; + } } xassert(sixel == 0); term->sixel.pos.col++; + + term->sixel.max_non_empty_row_no = max( + term->sixel.max_non_empty_row_no, + term->sixel.pos.row + max_non_empty_row); } static void diff --git a/terminal.h b/terminal.h index 81893e34..8be5dd74 100644 --- a/terminal.h +++ b/terminal.h @@ -521,6 +521,7 @@ struct terminal { } state; struct coord pos; /* Current sixel coordinate */ + int max_non_empty_row_no; size_t row_byte_ofs; /* Byte position into image, for current row */ int color_idx; /* Current palette index */ uint32_t *private_palette; /* Private palette, used when private mode 1070 is enabled */ From 9f224a13df87d0c2db9057ce2ab531013c2ae6f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 7 Mar 2021 14:44:28 +0100 Subject: [PATCH 22/38] sixel: add optimized resize_horizontally() and resize_vertically() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The resize operations performed in the “hot” path either change the width, or the height, but not both at the same time. --- sixel.c | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 2 deletions(-) diff --git a/sixel.c b/sixel.c index 6216523a..7ff5780d 100644 --- a/sixel.c +++ b/sixel.c @@ -844,6 +844,82 @@ sixel_unhook(struct terminal *term) render_refresh(term); } +static bool +resize_horizontally(struct terminal *term, int new_width) +{ + LOG_DBG("resizing image horizontally: %dx(%d) -> %dx(%d)", + term->sixel.image.width, term->sixel.image.height, + new_width, term->sixel.image.height); + + if (unlikely(new_width > term->sixel.max_width)) { + LOG_WARN("maximum image dimensions reached"); + return false; + } + + uint32_t *old_data = term->sixel.image.data; + const int old_width = term->sixel.image.width; + const int height = term->sixel.image.height; + + int alloc_height = (height + 6 - 1) / 6 * 6; + + /* Width (and thus stride) change - need to allocate a new buffer */ + uint32_t *new_data = xmalloc(new_width * alloc_height * sizeof(uint32_t)); + + /* Copy old rows, and initialize new columns to background color */ + for (int r = 0; r < height; r++) { + memcpy(&new_data[r * new_width], + &old_data[r * old_width], + old_width * sizeof(uint32_t)); + + for (int c = old_width; c < new_width; c++) + new_data[r * new_width + c] = color_with_alpha(term, term->colors.bg); + } + + free(old_data); + + term->sixel.image.data = new_data; + term->sixel.image.width = new_width; + term->sixel.row_byte_ofs = term->sixel.pos.row * new_width; + return true; +} + +static bool +resize_vertically(struct terminal *term, int new_height) +{ + LOG_DBG("resizing image vertically: (%d)x%d -> (%d)x%d", + term->sixel.image.width, term->sixel.image.height, + term->sixel.image.width, new_height); + + if (unlikely(new_height > term->sixel.max_height)) { + LOG_WARN("maximum image dimensions reached"); + return false; + } + + uint32_t *old_data = term->sixel.image.data; + const int width = term->sixel.image.width; + const int old_height = term->sixel.image.height; + + int alloc_height = (new_height + 6 - 1) / 6 * 6; + + uint32_t *new_data = realloc( + old_data, width * alloc_height * sizeof(uint32_t)); + + if (new_data == NULL) { + LOG_ERRNO("failed to reallocate sixel image buffer"); + return false; + } + + /* Initialize new rows to background color */ + for (int r = old_height; r < new_height; r++) { + for (int c = 0; c < width; c++) + new_data[r * width + c] = color_with_alpha(term, term->colors.bg); + } + + term->sixel.image.data = new_data; + term->sixel.image.height = new_height; + return true; +} + static bool resize(struct terminal *term, int new_width, int new_height) { @@ -920,7 +996,7 @@ sixel_add(struct terminal *term, uint32_t color, uint8_t sixel) if (unlikely(term->sixel.pos.col >= width)) { width = term->sixel.pos.col + 1; - if (unlikely(!resize(term, width, term->sixel.image.height))) + if (unlikely(!resize_horizontally(term, width))) return; } @@ -987,7 +1063,7 @@ decsixel(struct terminal *term, uint8_t c) term->sixel.row_byte_ofs += term->sixel.image.width * 6; if (term->sixel.pos.row >= term->sixel.image.height) { - if (!resize(term, term->sixel.image.width, term->sixel.pos.row + 6)) + if (!resize_vertically(term, term->sixel.pos.row + 6)) term->sixel.pos.col = term->sixel.max_width + 1; } break; From 777576b66b1fff8d6da5696aac062aaa0dea1bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 7 Mar 2021 14:59:10 +0100 Subject: [PATCH 23/38] sixel: decgri: avoid load inside for-loop --- sixel.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sixel.c b/sixel.c index 7ff5780d..10aa1f94 100644 --- a/sixel.c +++ b/sixel.c @@ -1158,9 +1158,10 @@ decgri(struct terminal *term, uint8_t c) case 'w': case 'x': case 'y': case 'z': case '{': case '|': case '}': case '~': { //LOG_DBG("repeating '%c' %u times", c, term->sixel.param); + unsigned count = term->sixel.param; uint32_t color = term->sixel.palette[term->sixel.color_idx]; - for (unsigned i = 0; i < term->sixel.param; i++) + for (unsigned i = 0; i < count; i++) sixel_add(term, color, c - 63); term->sixel.state = SIXEL_DECSIXEL; From 751ccf53162db0daf1f0318e421da31e337041e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 7 Mar 2021 15:44:23 +0100 Subject: [PATCH 24/38] sixel: add: increase data pointer instead of offset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same ‘add’ instruction to increase the offset, but simpler ‘mov’ instruction when writing the pixel data. --- sixel.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sixel.c b/sixel.c index 10aa1f94..4f03143d 100644 --- a/sixel.c +++ b/sixel.c @@ -1004,13 +1004,13 @@ sixel_add(struct terminal *term, uint32_t color, uint8_t sixel) xassert(term->sixel.pos.row < term->sixel.image.height); size_t ofs = term->sixel.row_byte_ofs + term->sixel.pos.col; - uint32_t *data = term->sixel.image.data; + uint32_t *data = &term->sixel.image.data[ofs]; int max_non_empty_row = 0; - for (int i = 0; i < 6; i++, sixel >>= 1, ofs += width) { + for (int i = 0; i < 6; i++, sixel >>= 1, data += width) { if (sixel & 1) { - data[ofs] = color; max_non_empty_row = i; + *data = color; } } From 6e963dbf6837094f93921f47cdc83aada244be6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 7 Mar 2021 15:45:39 +0100 Subject: [PATCH 25/38] sixel: add: calculate absolute row no inside the loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This results in the same number of instructions inside the loop, with a ‘lea’ instead of a ‘mov’, but simplifies the post-loop logic to update the global state. --- sixel.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sixel.c b/sixel.c index 4f03143d..7fb9be0d 100644 --- a/sixel.c +++ b/sixel.c @@ -1007,10 +1007,11 @@ sixel_add(struct terminal *term, uint32_t color, uint8_t sixel) uint32_t *data = &term->sixel.image.data[ofs]; int max_non_empty_row = 0; + int row = term->sixel.pos.row; for (int i = 0; i < 6; i++, sixel >>= 1, data += width) { if (sixel & 1) { - max_non_empty_row = i; *data = color; + max_non_empty_row = row + i; } } @@ -1019,7 +1020,7 @@ sixel_add(struct terminal *term, uint32_t color, uint8_t sixel) term->sixel.max_non_empty_row_no = max( term->sixel.max_non_empty_row_no, - term->sixel.pos.row + max_non_empty_row); + max_non_empty_row); } static void From 6d208fa5e0dbb603ef87f83b825de36e35f7c1a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 7 Mar 2021 16:30:48 +0100 Subject: [PATCH 26/38] sixel: add: add sixel_add_many(), improving performance of DECGRI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DECGRI, i.e. repeat sixel character, only need to do image resizing, and updating the current ‘column’ value *once*. By adding sixel_add_many(), and doing the size/resize checking there, the performance of sixel_add() is made much simpler. This boosts performance quite noticeably when the application is emitting many and/or long repeat sequences. --- sixel.c | 51 +++++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/sixel.c b/sixel.c index 7fb9be0d..7c83edf9 100644 --- a/sixel.c +++ b/sixel.c @@ -988,26 +988,17 @@ resize(struct terminal *term, int new_width, int new_height) } static void -sixel_add(struct terminal *term, uint32_t color, uint8_t sixel) +sixel_add(struct terminal *term, int col, int width, uint32_t color, uint8_t sixel) { - //LOG_DBG("adding sixel %02hhx using color 0x%06x", sixel, color); - - int width = term->sixel.image.width; - - if (unlikely(term->sixel.pos.col >= width)) { - width = term->sixel.pos.col + 1; - if (unlikely(!resize_horizontally(term, width))) - return; - } - - /* Height adjustment done while processing ‘-’ */ + xassert(term->sixel.pos.col < term->sixel.image.width); xassert(term->sixel.pos.row < term->sixel.image.height); - size_t ofs = term->sixel.row_byte_ofs + term->sixel.pos.col; + size_t ofs = term->sixel.row_byte_ofs + col; uint32_t *data = &term->sixel.image.data[ofs]; int max_non_empty_row = 0; int row = term->sixel.pos.row; + for (int i = 0; i < 6; i++, sixel >>= 1, data += width) { if (sixel & 1) { *data = color; @@ -1016,13 +1007,32 @@ sixel_add(struct terminal *term, uint32_t color, uint8_t sixel) } xassert(sixel == 0); - term->sixel.pos.col++; term->sixel.max_non_empty_row_no = max( term->sixel.max_non_empty_row_no, max_non_empty_row); } +static void +sixel_add_many(struct terminal *term, uint8_t c, unsigned count) +{ + uint32_t color = term->sixel.palette[term->sixel.color_idx]; + + int col = term->sixel.pos.col; + int width = term->sixel.image.width; + + if (unlikely(col + count - 1 >= width)) { + width = col + count; + if (unlikely(!resize_horizontally(term, width))) + return; + } + + for (unsigned i = 0; i < count; i++, col++) + sixel_add(term, col, width, color, c); + + term->sixel.pos.col = col; +} + static void decsixel(struct terminal *term, uint8_t c) { @@ -1079,7 +1089,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(term, term->sixel.palette[term->sixel.color_idx], c - 63); + sixel_add_many(term, c - 63, 1); break; case ' ': @@ -1157,18 +1167,11 @@ decgri(struct terminal *term, uint8_t c) 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 '~': { - //LOG_DBG("repeating '%c' %u times", c, term->sixel.param); - unsigned count = term->sixel.param; - uint32_t color = term->sixel.palette[term->sixel.color_idx]; - - for (unsigned i = 0; i < count; i++) - sixel_add(term, color, c - 63); - + case '~': + sixel_add_many(term, c - 63, term->sixel.param); term->sixel.state = SIXEL_DECSIXEL; break; } - } } static void From eee216f5feef19ac51c11cd55e64d141f7612a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 7 Mar 2021 16:52:53 +0100 Subject: [PATCH 27/38] generate-alt-random: sixel: pan/pad must not be 0 --- scripts/generate-alt-random-writes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate-alt-random-writes.py b/scripts/generate-alt-random-writes.py index 6e7b25ab..2513cbe9 100755 --- a/scripts/generate-alt-random-writes.py +++ b/scripts/generate-alt-random-writes.py @@ -185,7 +185,7 @@ def main(): # Sixel size. Without this, sixels will be # auto-resized on cell-boundaries. - out.write(f'"0;0;{six_width};{six_height}') + out.write(f'"1;1;{six_width};{six_height}') for row in range(six_height // 6): # Each sixel is 6 pixels # Choose a random color From ae86043780df36d4dce8f38effd5023c4605b4ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 7 Mar 2021 16:53:27 +0100 Subject: [PATCH 28/38] sixel: decgri: handle a repeat count of 0, by ignoring it --- sixel.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/sixel.c b/sixel.c index 7c83edf9..2c440a6d 100644 --- a/sixel.c +++ b/sixel.c @@ -862,6 +862,9 @@ resize_horizontally(struct terminal *term, int new_width) int alloc_height = (height + 6 - 1) / 6 * 6; + xassert(new_width > 0); + xassert(alloc_height > 0); + /* Width (and thus stride) change - need to allocate a new buffer */ uint32_t *new_data = xmalloc(new_width * alloc_height * sizeof(uint32_t)); @@ -901,6 +904,9 @@ resize_vertically(struct terminal *term, int new_height) int alloc_height = (new_height + 6 - 1) / 6 * 6; + xassert(width > 0); + xassert(new_height > 0); + uint32_t *new_data = realloc( old_data, width * alloc_height * sizeof(uint32_t)); @@ -1167,11 +1173,14 @@ decgri(struct terminal *term, uint8_t c) 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(term, c - 63, term->sixel.param); + case '~': { + unsigned count = term->sixel.param; + if (likely(count > 0)) + sixel_add_many(term, c - 63, count); term->sixel.state = SIXEL_DECSIXEL; break; } + } } static void From f8e51fcb178924dd63c2ce185b1b1ab5daa09362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 7 Mar 2021 17:31:42 +0100 Subject: [PATCH 29/38] generate-alt-random: emit multiple sixel bands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This ensures we’re getting the ‘$’ command PGO:d --- scripts/generate-alt-random-writes.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/scripts/generate-alt-random-writes.py b/scripts/generate-alt-random-writes.py index 2513cbe9..770ba038 100755 --- a/scripts/generate-alt-random-writes.py +++ b/scripts/generate-alt-random-writes.py @@ -165,7 +165,7 @@ def main(): # The sixel 'alphabet' sixels = '?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~' - for _ in range(200): + for _ in range(100): # Offset image out.write(' ' * (random.randrange(cols // 2))) @@ -188,17 +188,24 @@ def main(): out.write(f'"1;1;{six_width};{six_height}') for row in range(six_height // 6): # Each sixel is 6 pixels - # Choose a random color - out.write(f'#{random.randrange(256)}') + band_count = random.randrange(32) + for band in range(band_count): + # Choose a random color + out.write(f'#{random.randrange(256)}') - if random.randrange(2): - for col in range(six_width): - out.write(f'{random.choice(sixels)}') - else: - out.write(f'!{six_width}{random.choice(sixels)}') + if random.randrange(2): + for col in range(six_width): + out.write(f'{random.choice(sixels)}') + else: + out.write(f'!{six_width}{random.choice(sixels)}') - # Next line - out.write('-') + # Next line + if band + 1 < band_count: + # Move cursor to beginning of current row + out.write('$') + elif row + 1 < six_height // 6: + # Newline + out.write('-') # End sixel out.write('\033\\') From 4a86cd7475f43c5bc44c3309a9caa7bd1ee5a453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 9 Mar 2021 11:35:31 +0100 Subject: [PATCH 30/38] generate-alt-random: emit sixels on the alt screen --- scripts/generate-alt-random-writes.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/generate-alt-random-writes.py b/scripts/generate-alt-random-writes.py index 770ba038..8c49c045 100755 --- a/scripts/generate-alt-random-writes.py +++ b/scripts/generate-alt-random-writes.py @@ -158,8 +158,8 @@ def main(): reset_actions = ['\033[m', '\033[39m', '\033[49m'] out.write(random.choice(reset_actions)) - # Leave alt screen - out.write('\033[m\033[r\033[?1049l') + # Reset colors + out.write('\033[m\033[r') if opts.sixel: # The sixel 'alphabet' @@ -210,6 +210,9 @@ def main(): # End sixel out.write('\033\\') + # Leave alt screen + out.write('\033[?1049l') + if __name__ == '__main__': sys.exit(main()) From a3f2e2220ad0448bddb853eca339962f7d64732a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 9 Mar 2021 11:36:26 +0100 Subject: [PATCH 31/38] generate-alt-random: 50% chance of overwriting the last sixel --- scripts/generate-alt-random-writes.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/scripts/generate-alt-random-writes.py b/scripts/generate-alt-random-writes.py index 8c49c045..ec671fd3 100755 --- a/scripts/generate-alt-random-writes.py +++ b/scripts/generate-alt-random-writes.py @@ -165,9 +165,21 @@ def main(): # The sixel 'alphabet' sixels = '?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~' + last_pos = None + last_size = None + for _ in range(100): - # Offset image - out.write(' ' * (random.randrange(cols // 2))) + if last_pos is not None and random.randrange(2): + # Overwrite last sixel. I.e. use same position and + # size as last sixel + pass + else: + # Random origin in upper left quadrant + last_pos = random.randrange(lines // 2) + 1, random.randrange(cols // 2) + 1 + last_size = random.randrange(height // 2), random.randrange(width // 2) + + out.write(f'\033[{last_pos[0]};{last_pos[1]}H') + six_height, six_width = last_size # Begin sixel out.write('\033Pq') @@ -179,10 +191,6 @@ def main(): # (except 'hue' which is 0..360) out.write(f'#{idx};2;{random.randrange(101)};{random.randrange(101)};{random.randrange(101)}') - # Randomize image width/height - six_height = random.randrange(height // 2) - six_width = random.randrange(width // 2) - # Sixel size. Without this, sixels will be # auto-resized on cell-boundaries. out.write(f'"1;1;{six_width};{six_height}') From 0bc98877f35ef56e127aecc3df3aff986a4c3c93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 9 Mar 2021 11:36:56 +0100 Subject: [PATCH 32/38] =?UTF-8?q?generate-alt-random:=20emit=20=E2=80=9CSe?= =?UTF-8?q?t=20Raster=20Attributes=E2=80=9D=20before=20color=20definitions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/generate-alt-random-writes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/generate-alt-random-writes.py b/scripts/generate-alt-random-writes.py index ec671fd3..30b7e9c9 100755 --- a/scripts/generate-alt-random-writes.py +++ b/scripts/generate-alt-random-writes.py @@ -184,6 +184,10 @@ def main(): # Begin sixel out.write('\033Pq') + # Sixel size. Without this, sixels will be + # auto-resized on cell-boundaries. + out.write(f'"1;1;{six_width};{six_height}') + # Set up 256 random colors for idx in range(256): # param 2: 1=HLS, 2=RGB. @@ -191,10 +195,6 @@ def main(): # (except 'hue' which is 0..360) out.write(f'#{idx};2;{random.randrange(101)};{random.randrange(101)};{random.randrange(101)}') - # Sixel size. Without this, sixels will be - # auto-resized on cell-boundaries. - out.write(f'"1;1;{six_width};{six_height}') - for row in range(six_height // 6): # Each sixel is 6 pixels band_count = random.randrange(32) for band in range(band_count): From 660a7f9345f83c0ea3b1b92ec98d3ab5cbc06e7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 9 Mar 2021 11:37:35 +0100 Subject: [PATCH 33/38] =?UTF-8?q?generate-alt-random:=20don=E2=80=99t=20sk?= =?UTF-8?q?ip=20that=20last=20partial=20row?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/generate-alt-random-writes.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/generate-alt-random-writes.py b/scripts/generate-alt-random-writes.py index 30b7e9c9..93a542a7 100755 --- a/scripts/generate-alt-random-writes.py +++ b/scripts/generate-alt-random-writes.py @@ -180,6 +180,7 @@ def main(): out.write(f'\033[{last_pos[0]};{last_pos[1]}H') six_height, six_width = last_size + six_rows = (six_height + 5) // 6 # Round up; each sixel is 6 pixels # Begin sixel out.write('\033Pq') @@ -195,7 +196,7 @@ def main(): # (except 'hue' which is 0..360) out.write(f'#{idx};2;{random.randrange(101)};{random.randrange(101)};{random.randrange(101)}') - for row in range(six_height // 6): # Each sixel is 6 pixels + for row in range(six_rows): band_count = random.randrange(32) for band in range(band_count): # Choose a random color @@ -211,7 +212,7 @@ def main(): if band + 1 < band_count: # Move cursor to beginning of current row out.write('$') - elif row + 1 < six_height // 6: + elif row + 1 < six_rows: # Newline out.write('-') From 8ba455f70e0acfaa4ee61fb71a415e796e27dfce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 9 Mar 2021 11:37:47 +0100 Subject: [PATCH 34/38] generate-alt-random: DECGRI: random repeat lengths --- scripts/generate-alt-random-writes.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/generate-alt-random-writes.py b/scripts/generate-alt-random-writes.py index 93a542a7..0923501b 100755 --- a/scripts/generate-alt-random-writes.py +++ b/scripts/generate-alt-random-writes.py @@ -206,7 +206,11 @@ def main(): for col in range(six_width): out.write(f'{random.choice(sixels)}') else: - out.write(f'!{six_width}{random.choice(sixels)}') + pix_left = six_width + while pix_left > 0: + pix_count = random.randrange(pix_left + 1) + out.write(f'!{pix_count}{random.choice(sixels)}') + pix_left -= pix_count # Next line if band + 1 < band_count: From f3bc5a95b553f41de794db7d3351b79ffcd64efd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 9 Mar 2021 12:01:18 +0100 Subject: [PATCH 35/38] =?UTF-8?q?generate-alt-random:=20DECGRI:=20don?= =?UTF-8?q?=E2=80=99t=20emit=20zero-length=20repeat=20sequences?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/generate-alt-random-writes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/generate-alt-random-writes.py b/scripts/generate-alt-random-writes.py index 0923501b..54a34c01 100755 --- a/scripts/generate-alt-random-writes.py +++ b/scripts/generate-alt-random-writes.py @@ -208,9 +208,9 @@ def main(): else: pix_left = six_width while pix_left > 0: - pix_count = random.randrange(pix_left + 1) - out.write(f'!{pix_count}{random.choice(sixels)}') - pix_left -= pix_count + repeat_count = random.randrange(1, pix_left + 1) + out.write(f'!{repeat_count}{random.choice(sixels)}') + pix_left -= repeat_count # Next line if band + 1 < band_count: From a8186351d16f6daba770ddfb0a850fd0f4d11adf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 9 Mar 2021 12:02:31 +0100 Subject: [PATCH 36/38] generate-alt-random: sixel: emit at least 4 color bands This fixes an issue where there was a relatively high chance of not emitting any color bands at all, causing the final image size to be too small. --- scripts/generate-alt-random-writes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/generate-alt-random-writes.py b/scripts/generate-alt-random-writes.py index 54a34c01..2a3b23a5 100755 --- a/scripts/generate-alt-random-writes.py +++ b/scripts/generate-alt-random-writes.py @@ -197,7 +197,7 @@ def main(): out.write(f'#{idx};2;{random.randrange(101)};{random.randrange(101)};{random.randrange(101)}') for row in range(six_rows): - band_count = random.randrange(32) + band_count = random.randrange(4, 33) for band in range(band_count): # Choose a random color out.write(f'#{random.randrange(256)}') From 6f6bcbc1bc5a9378bd16bd300ec89ddb35d9f863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Wed, 10 Mar 2021 20:31:21 +0100 Subject: [PATCH 37/38] sixel: decgra: set max-non-empty-row-no when resizing the image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This ensures we don’t trim off bottom rows in unhook(). This could happen either because the application used “Set Raster Attributes” to configure an image size larger than the sixels later emitted. Or, the last sixel row contains empty pixel rows. In either case, the size set with “Set Raster Attributes” defines the *minimum* image size; the image may still end up being larger. --- sixel.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sixel.c b/sixel.c index 2c440a6d..776750a2 100644 --- a/sixel.c +++ b/sixel.c @@ -1145,6 +1145,7 @@ decgra(struct terminal *term, uint8_t c) ph <= term->sixel.max_height && pv <= term->sixel.max_width) { resize(term, ph, pv); + term->sixel.max_non_empty_row_no = pv - 1; } term->sixel.state = SIXEL_DECSIXEL; From 5e852f148ebbbfc56e1ba83d1e29504f480c54b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Thu, 11 Mar 2021 17:32:38 +0100 Subject: [PATCH 38/38] changelog: empty pixel rows at the bottom of a sixel is now trimmed --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1d9629e..2832979e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,6 +82,7 @@ (https://codeberg.org/dnkl/foot/issues/376). * The minimum version requirement for the libxkbcommon dependency is now 1.0.0. +* Empty pixel rows at the bottom of a sixel is now trimmed. ### Deprecated