From 7d315d7bf9ab0376468929f0e41fbac98370e035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Tue, 9 Mar 2021 17:23:55 +0100 Subject: [PATCH] sixel: implement P2=1 - transparent pixels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When P2=1, empty pixels are transparent. This patch also changes the behavior of P2=0|2, from setting empty pixels to the default background color, to instead use the *current* background color. To implement this, a couple of changes are needed: * Sixel pixels always use alpha=1.0, except for *empty* cells when P2=1 (i.e. transparent pixels). * The renderer draws sixels with the OVER operator, instead of the SRC operator. * The renderer *must* now render the cells beneath the sixel. As an optimization, this is only done for sixels where P2=1. I.e. for fully opaque sixels, there’s no need to render the cells beneath. The sixel renderer isn’t yet hooked into the multi-threaded renderer. This means *rows* (not just the cells) beneath maybe-transparent sixels are rendered single-threaded. Closes #391. --- dcs.c | 9 +++++-- render.c | 74 ++++++++++++++++++++++++++++++++---------------------- sixel.c | 53 +++++++++++++++++++++++++------------- sixel.h | 2 +- terminal.h | 3 +++ 5 files changed, 91 insertions(+), 50 deletions(-) diff --git a/dcs.c b/dcs.c index f9e191d9..ae921f4f 100644 --- a/dcs.c +++ b/dcs.c @@ -42,12 +42,17 @@ dcs_hook(struct terminal *term, uint8_t final) switch (term->vt.private) { case 0: switch (final) { - case 'q': - sixel_init(term); + case 'q': { + int p1 = vt_param_get(term, 0, 0); + 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.unhook_handler = &sixel_unhook; break; } + } break; case '=': diff --git a/render.c b/render.c index c17f7e9b..6764f6db 100644 --- a/render.c +++ b/render.c @@ -637,6 +637,14 @@ draw_cursor: return cell_cols; } +static void +render_row(struct terminal *term, pixman_image_t *pix, struct row *row, + int row_no, int cursor_col) +{ + for (int col = term->cols - 1; col >= 0; col--) + render_cell(term, pix, row, col, row_no, cursor_col == col); +} + static void render_urgency(struct terminal *term, struct buffer *buf) { @@ -924,7 +932,7 @@ render_sixel_chunk(struct terminal *term, pixman_image_t *pix, const struct sixe //LOG_DBG("sixel chunk: %dx%d %dx%d", x, y, width, height); pixman_image_composite32( - PIXMAN_OP_SRC, + PIXMAN_OP_OVER, sixel->pix, NULL, pix, @@ -938,7 +946,7 @@ render_sixel_chunk(struct terminal *term, pixman_image_t *pix, const struct sixe static void render_sixel(struct terminal *term, pixman_image_t *pix, - const struct sixel *sixel) + const struct coord *cursor, const struct sixel *sixel) { const int view_end = (term->grid->view + term->rows - 1) & (term->grid->num_rows - 1); const bool last_row_needs_erase = sixel->height % term->cell_height != 0; @@ -1011,28 +1019,41 @@ render_sixel(struct terminal *term, pixman_image_t *pix, } /* - * Loop cells and set their 'clean' bit, to prevent the grid - * rendered from overwriting the sixel + * If image contains transparent parts, render all (dirty) + * cells beneath it. + * + * If image is opaque, loop cells and set their 'clean' bit, + * to prevent the grid rendered from overwriting the sixel * * If the last sixel row only partially covers the cell row, * 'erase' the cell by rendering them. + * + * In all cases, do *not* clear the ‘dirty’ bit on the row, to + * ensure the regular renderer includes them in the damage + * rect. */ - for (int col = sixel->pos.col; - col < min(sixel->pos.col + sixel->cols, term->cols); - col++) - { - struct cell *cell = &row->cells[col]; + if (!sixel->opaque) { + /* TODO: multithreading */ + int cursor_col = cursor->row == term_row_no ? cursor->col : -1; + render_row(term, pix, row, term_row_no, cursor_col); + } else { + for (int col = sixel->pos.col; + col < min(sixel->pos.col + sixel->cols, term->cols); + col++) + { + struct cell *cell = &row->cells[col]; - if (!cell->attrs.clean) { - bool last_row = abs_row_no == sixel->pos.row + sixel->rows - 1; - bool last_col = col == sixel->pos.col + sixel->cols - 1; + if (!cell->attrs.clean) { + bool last_row = abs_row_no == sixel->pos.row + sixel->rows - 1; + bool last_col = col == sixel->pos.col + sixel->cols - 1; - if ((last_row_needs_erase && last_row) || - (last_col_needs_erase && last_col)) - { - render_cell(term, pix, row, col, term_row_no, false); - } else - cell->attrs.clean = 1; + if ((last_row_needs_erase && last_row) || + (last_col_needs_erase && last_col)) + { + render_cell(term, pix, row, col, term_row_no, false); + } else + cell->attrs.clean = 1; + } } } @@ -1050,7 +1071,8 @@ render_sixel(struct terminal *term, pixman_image_t *pix, } static void -render_sixel_images(struct terminal *term, pixman_image_t *pix) +render_sixel_images(struct terminal *term, pixman_image_t *pix, + const struct coord *cursor) { if (likely(tll_length(term->grid->sixel_images)) == 0) return; @@ -1086,7 +1108,7 @@ render_sixel_images(struct terminal *term, pixman_image_t *pix) break; } - render_sixel(term, pix, &it->item); + render_sixel(term, pix, cursor, &it->item); } } @@ -1250,14 +1272,6 @@ render_ime_preedit(struct terminal *term, struct buffer *buf) #endif } -static void -render_row(struct terminal *term, pixman_image_t *pix, struct row *row, - int row_no, int cursor_col) -{ - for (int col = term->cols - 1; col >= 0; col--) - render_cell(term, pix, row, col, row_no, cursor_col == col); -} - int render_worker_thread(void *_ctx) { @@ -2063,8 +2077,6 @@ grid_render(struct terminal *term) cursor.row &= term->grid->num_rows - 1; } - render_sixel_images(term, buf->pix[0]); - if (term->render.workers.count > 0) { mtx_lock(&term->render.workers.lock); term->render.workers.buf = buf; @@ -2074,6 +2086,8 @@ grid_render(struct terminal *term) xassert(tll_length(term->render.workers.queue) == 0); } + render_sixel_images(term, buf->pix[0], &cursor); + int first_dirty_row = -1; for (int r = 0; r < term->rows; r++) { struct row *row = grid_row_in_view(term->grid, r); diff --git a/sixel.c b/sixel.c index 776750a2..20f15cab 100644 --- a/sixel.c +++ b/sixel.c @@ -15,6 +15,16 @@ static size_t count; +static uint32_t +get_bg(const struct terminal *term) +{ + return term->sixel.transparent_bg + ? 0x00000000u + : 0xffu << 24 | (term->vt.attrs.have_bg + ? term->vt.attrs.bg + : term->colors.bg); +} + void sixel_fini(struct terminal *term) { @@ -22,16 +32,17 @@ sixel_fini(struct terminal *term) free(term->sixel.shared_palette); } -static uint32_t -color_with_alpha(const struct terminal *term, uint32_t color) -{ - uint16_t alpha = color == term->colors.bg ? term->colors.alpha : 0xffff; - return (alpha / 256u) << 24 | color; -} - void -sixel_init(struct terminal *term) +sixel_init(struct terminal *term, int p1, int p2, int p3) { + /* + * P1: pixel aspect ratio - unimplemented + * P2: background color mode + * - 0|2: empty pixels use current background color + * - 1: empty pixels remain at their current color (i.e. transparent) + * P3: horizontal grid size - ignored + */ + xassert(term->sixel.image.data == NULL); xassert(term->sixel.palette_size <= SIXEL_MAX_COLORS); @@ -43,6 +54,7 @@ sixel_init(struct terminal *term) term->sixel.param = 0; term->sixel.param_idx = 0; memset(term->sixel.params, 0, sizeof(term->sixel.params)); + term->sixel.transparent_bg = p2 == 1; term->sixel.image.data = xmalloc(1 * 6 * sizeof(term->sixel.image.data[0])); term->sixel.image.width = 1; term->sixel.image.height = 6; @@ -65,8 +77,9 @@ sixel_init(struct terminal *term) term->sixel.palette = term->sixel.shared_palette; } + uint32_t bg = get_bg(term); for (size_t i = 0; i < 1 * 6; i++) - term->sixel.image.data[i] = color_with_alpha(term, term->colors.bg); + term->sixel.image.data[i] = bg; count = 0; } @@ -106,7 +119,7 @@ sixel_erase(struct terminal *term, struct sixel *sixel) row->dirty = true; - for (int c = 0; c < term->grid->num_cols; c++) + for (int c = sixel->pos.col; c < min(sixel->cols, term->cols); c++) row->cells[c].attrs.clean = 0; } @@ -421,6 +434,7 @@ sixel_overwrite(struct terminal *term, struct sixel *six, .pos = {.col = new_col, .row = new_row}, .cols = (new_width + term->cell_width - 1) / term->cell_width, .rows = (new_height + term->cell_height - 1) / term->cell_height, + .opaque = six->opaque, }; #if defined(_DEBUG) @@ -767,6 +781,7 @@ sixel_unhook(struct terminal *term) .rows = (height + term->cell_height - 1) / term->cell_height, .cols = (width + term->cell_width - 1) / term->cell_width, .pos = (struct coord){start_col, cur_row}, + .opaque = !term->sixel.transparent_bg, }; xassert(image.rows < term->grid->num_rows); @@ -868,6 +883,8 @@ resize_horizontally(struct terminal *term, int new_width) /* Width (and thus stride) change - need to allocate a new buffer */ uint32_t *new_data = xmalloc(new_width * alloc_height * sizeof(uint32_t)); + uint32_t bg = get_bg(term); + /* Copy old rows, and initialize new columns to background color */ for (int r = 0; r < height; r++) { memcpy(&new_data[r * new_width], @@ -875,7 +892,7 @@ resize_horizontally(struct terminal *term, int new_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); + new_data[r * new_width + c] = bg; } free(old_data); @@ -915,10 +932,12 @@ resize_vertically(struct terminal *term, int new_height) return false; } + uint32_t bg = get_bg(term); + /* 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); + new_data[r * width + c] = bg; } term->sixel.image.data = new_data; @@ -950,6 +969,7 @@ resize(struct terminal *term, int new_width, int new_height) xassert(alloc_new_height - new_height < 6); uint32_t *new_data = NULL; + uint32_t bg = get_bg(term); if (new_width == old_width) { /* Width (and thus stride) is the same, so we can simply @@ -973,7 +993,7 @@ resize(struct terminal *term, int new_width, int new_height) 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); + new_data[r * new_width + c] = bg; } free(old_data); } @@ -981,7 +1001,7 @@ resize(struct terminal *term, int new_width, int new_height) /* Initialize new rows to background color */ for (int r = old_height; r < new_height; r++) { for (int c = 0; c < new_width; c++) - new_data[r * new_width + c] = color_with_alpha(term, term->colors.bg); + new_data[r * new_width + c] = bg; } xassert(new_data != NULL); @@ -1239,8 +1259,7 @@ 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] = - color_with_alpha(term, rgb); + term->sixel.palette[term->sixel.color_idx] = 0xffu << 24 | rgb; break; } @@ -1253,7 +1272,7 @@ decgci(struct terminal *term, uint8_t c) term->sixel.color_idx, r, g, b); term->sixel.palette[term->sixel.color_idx] = - color_with_alpha(term, r << 16 | g << 8 | b); + 0xffu << 24 | r << 16 | g << 8 | b; break; } } diff --git a/sixel.h b/sixel.h index f72cf260..a57957c3 100644 --- a/sixel.h +++ b/sixel.h @@ -8,7 +8,7 @@ void sixel_fini(struct terminal *term); -void sixel_init(struct terminal *term); +void sixel_init(struct terminal *term, int p1, int p2, int p3); void sixel_put(struct terminal *term, uint8_t c); void sixel_unhook(struct terminal *term); diff --git a/terminal.h b/terminal.h index 8be5dd74..0fcd97ea 100644 --- a/terminal.h +++ b/terminal.h @@ -112,6 +112,7 @@ struct sixel { int rows; int cols; struct coord pos; + bool opaque; }; struct grid { @@ -542,6 +543,8 @@ struct terminal { unsigned param; /* Currently collecting parameter, for RASTER, COLOR_SPEC and REPEAT */ unsigned param_idx; /* Parameters seen */ + bool transparent_bg; + /* Application configurable */ unsigned palette_size; /* Number of colors in palette */ unsigned max_width; /* Maximum image width, in pixels */