From 118e965d0c8bd9c29b5e6bcba3ffcc1dd41064a7 Mon Sep 17 00:00:00 2001 From: veltza <106755522+veltza@users.noreply.github.com> Date: Wed, 17 Apr 2024 19:04:27 +0300 Subject: [PATCH] sixel: add support for fully transparent bg (P2=1) (#132) P2 selects how the terminal draws the background color. P2 Meaning 0 or 2 (default) Pixel positions specified as 0 are set to the current background color. 1 Pixel positions specified as 0 remain at their current color. Both modes are now supported. Ref. https://www.vt100.net/docs/vt3xx-gp/chapter14.html --- patch/reflow.c | 15 ++++----------- sixel.c | 40 +++++++++++++++++++++++++++++++++++++++- sixel.h | 4 +++- st.c | 32 +++++++++++++++++--------------- st.h | 2 ++ x.c | 25 ++++++++++++++++++++----- 6 files changed, 85 insertions(+), 33 deletions(-) diff --git a/patch/reflow.c b/patch/reflow.c index 92b67f1..2fd80f0 100644 --- a/patch/reflow.c +++ b/patch/reflow.c @@ -81,7 +81,7 @@ treflow(int col, int row) int oce, nce, bot, scr; int ox = 0, oy = -term.histf, nx = 0, ny = -1, len; int cy = -1; /* proxy for new y coordinate of cursor */ - int buflen, nlines, del; + int buflen, nlines; Line *buf, bufline, line; #if SIXEL_PATCH ImageList *im, *next; @@ -219,21 +219,14 @@ treflow(int col, int row) } } - /* expand images into new text cells or - * delete images if there is text behind them */ + /* expand images into new text cells */ for (im = term.images; im; im = next) { next = im->next; if (im->x < col) { line = TLINE(im->y); x2 = MIN(im->x + im->cols, col); - for (del = 0, x = im->x; x < x2; x++) { - if ((del = line[x].mode & ATTR_SET)) - break; - line[x].u = ' '; - line[x].mode = ATTR_SIXEL; - } - if (del) - delete_image(im); + for (x = im->x; x < x2; x++) + line[x].mode |= ATTR_SIXEL; } } #endif // SIXEL_PATCH diff --git a/sixel.c b/sixel.c index de526c5..ad6f6bc 100644 --- a/sixel.c +++ b/sixel.c @@ -66,6 +66,8 @@ delete_image(ImageList *im) im->next->prev = im->prev; if (im->pixmap) XFreePixmap(xw.dpy, (Drawable)im->pixmap); + if (im->clipmask) + XFreePixmap(xw.dpy, (Drawable)im->clipmask); free(im->pixels); free(im); } @@ -217,6 +219,7 @@ sixel_image_deinit(sixel_image_t *image) int sixel_parser_init(sixel_state_t *st, + int transparent, sixel_color_t fgcolor, sixel_color_t bgcolor, unsigned char use_private_register, int cell_width, int cell_height) @@ -232,6 +235,7 @@ sixel_parser_init(sixel_state_t *st, st->attributed_pad = 1; st->attributed_ph = 0; st->attributed_pv = 0; + st->transparent = transparent; st->repeat_count = 1; st->color_index = 16; st->grid_width = cell_width; @@ -240,7 +244,7 @@ sixel_parser_init(sixel_state_t *st, st->param = 0; /* buffer initialization */ - status = sixel_image_init(&st->image, 1, 1, fgcolor, bgcolor, use_private_register); + status = sixel_image_init(&st->image, 1, 1, fgcolor, transparent ? 0 : bgcolor, use_private_register); return status; } @@ -304,8 +308,10 @@ sixel_parser_finalize(sixel_state_t *st, ImageList **newimages, int cx, int cy, im->height = MIN(h - ch * i, ch); im->pixels = malloc(im->width * im->height * 4); im->pixmap = NULL; + im->clipmask = NULL; im->cw = cw; im->ch = ch; + im->transparent = st->transparent; } if (!im || !im->pixels) { for (im = *newimages; im; im = next) { @@ -652,3 +658,35 @@ sixel_parser_deinit(sixel_state_t *st) if (st) sixel_image_deinit(&st->image); } + +Pixmap +sixel_create_clipmask(char *pixels, int width, int height) +{ + char c, *clipdata, *dst; + int b, i, n, y, w; + int msb = (XBitmapBitOrder(xw.dpy) == MSBFirst); + sixel_color_t *src = (sixel_color_t *)pixels; + Pixmap clipmask; + + clipdata = dst = malloc((width+7)/8 * height); + if (!clipdata) + return (Pixmap)None; + + for (y = 0; y < height; y++) { + for (w = width; w > 0; w -= n) { + n = MIN(w, 8); + if (msb) { + for (b = 0x80, c = 0, i = 0; i < n; i++, b >>= 1) + c |= (*src++) ? b : 0; + } else { + for (b = 0x01, c = 0, i = 0; i < n; i++, b <<= 1) + c |= (*src++) ? b : 0; + } + *dst++ = c; + } + } + + clipmask = XCreateBitmapFromData(xw.dpy, xw.win, clipdata, width, height); + free(clipdata); + return clipmask; +} diff --git a/sixel.h b/sixel.h index 7b2266e..7d14f8a 100644 --- a/sixel.h +++ b/sixel.h @@ -39,6 +39,7 @@ typedef struct parser_context { int attributed_pad; int attributed_ph; int attributed_pv; + int transparent; int repeat_count; int color_index; int bgindex; @@ -52,10 +53,11 @@ typedef struct parser_context { void scroll_images(int n); void delete_image(ImageList *im); -int sixel_parser_init(sixel_state_t *st, sixel_color_t fgcolor, sixel_color_t bgcolor, unsigned char use_private_register, int cell_width, int cell_height); +int sixel_parser_init(sixel_state_t *st, int transparent, sixel_color_t fgcolor, sixel_color_t bgcolor, unsigned char use_private_register, int cell_width, int cell_height); int sixel_parser_parse(sixel_state_t *st, const unsigned char *p, size_t len); int sixel_parser_set_default_color(sixel_state_t *st); int sixel_parser_finalize(sixel_state_t *st, ImageList **newimages, int cx, int cy, int cw, int ch); void sixel_parser_deinit(sixel_state_t *st); +Pixmap sixel_create_clipmask(char *pixels, int width, int height); #endif diff --git a/st.c b/st.c index 2830e27..b2a96f5 100644 --- a/st.c +++ b/st.c @@ -2714,14 +2714,18 @@ strhandle(void) y1 = newimages->y; x2 = x1 + newimages->cols; y2 = y1 + numimages; - for (tail = NULL, im = term.images; im; im = next) { - next = im->next; - if (im->x >= x1 && im->x + im->cols <= x2 && - im->y >= y1 && im->y <= y2) { - delete_image(im); - continue; + if (newimages->transparent) { + for (tail = term.images; tail && tail->next; tail = tail->next); + } else { + for (tail = NULL, im = term.images; im; im = next) { + next = im->next; + if (im->x >= x1 && im->x + im->cols <= x2 && + im->y >= y1 && im->y <= y2) { + delete_image(im); + continue; + } + tail = im; } - tail = im; } if (tail) { tail->next = newimages; @@ -2747,8 +2751,7 @@ strhandle(void) line = term.line[term.c.y]; } for (x = im->x; x < x2; x++) { - line[x].u = ' '; - line[x].mode = ATTR_SIXEL; + line[x].mode |= ATTR_SIXEL; } term.dirty[MIN(im->y, term.row-1)] = 1; if (!IS_SET(MODE_SIXEL_SDM) && i < numimages-1) { @@ -3097,7 +3100,7 @@ tcontrolcode(uchar ascii) void dcshandle(void) { - int bgcolor; + int bgcolor, transparent; unsigned char r, g, b, a = 255; switch (csiescseq.mode[0]) { @@ -3119,6 +3122,7 @@ dcshandle(void) break; #endif // SYNC_PATCH case 'q': /* DECSIXEL */ + transparent = (csiescseq.narg >= 2 && csiescseq.arg[1] == 1); if (IS_TRUECOL(term.c.attr.bg)) { r = term.c.attr.bg >> 16 & 255; g = term.c.attr.bg >> 8 & 255; @@ -3129,7 +3133,7 @@ dcshandle(void) a = dc.col[defaultbg].pixel >> 24 & 255; } bgcolor = a << 24 | r << 16 | g << 8 | b; - if (sixel_parser_init(&sixel_st, (255 << 24), bgcolor, 1, win.cw, win.ch) != 0) + if (sixel_parser_init(&sixel_st, transparent, (255 << 24), bgcolor, 1, win.cw, win.ch) != 0) perror("sixel_parser_init() failed"); term.mode |= MODE_SIXEL; break; @@ -3632,10 +3636,8 @@ tresize(int col, int row) line = term.line[im->y]; #endif // SCROLLBACK_PATCH x2 = MIN(im->x + im->cols, term.col); - for (x = im->x; x < x2; x++) { - line[x].u = ' '; - line[x].mode = ATTR_SIXEL; - } + for (x = im->x; x < x2; x++) + line[x].mode |= ATTR_SIXEL; } tswapscreen(); } diff --git a/st.h b/st.h index aeb4ca1..95d1be1 100644 --- a/st.h +++ b/st.h @@ -77,6 +77,7 @@ typedef struct _ImageList { struct _ImageList *next, *prev; unsigned char *pixels; void *pixmap; + void *clipmask; int width; int height; int x; @@ -87,6 +88,7 @@ typedef struct _ImageList { int cols; int cw; int ch; + int transparent; } ImageList; #endif // SIXEL_PATCH diff --git a/x.c b/x.c index 805849e..5940238 100644 --- a/x.c +++ b/x.c @@ -325,7 +325,10 @@ zoomabs(const Arg *arg) for (im = term.images; im; im = im->next) { if (im->pixmap) XFreePixmap(xw.dpy, (Drawable)im->pixmap); + if (im->clipmask) + XFreePixmap(xw.dpy, (Drawable)im->clipmask); im->pixmap = NULL; + im->clipmask = NULL; } #endif // SIXEL_PATCH @@ -3138,7 +3141,7 @@ xfinishdraw(void) XGCValues gcvalues; GC gc; int width, height; - int x, x2, del; + int x, x2, del, destx, desty; Line line; #endif // SIXEL_PATCH @@ -3161,6 +3164,8 @@ xfinishdraw(void) DefaultDepth(xw.dpy, xw.scr) #endif // ALPHA_PATCH ); + if (!im->pixmap) + continue; if (win.cw == im->cw && win.ch == im->ch) { XImage ximage = { .format = ZPixmap, @@ -3181,12 +3186,15 @@ xfinishdraw(void) #endif // ALPHA_PATCH }; XPutImage(xw.dpy, (Drawable)im->pixmap, dc.gc, &ximage, 0, 0, 0, 0, width, height); + if (im->transparent) + im->clipmask = (void *)sixel_create_clipmask((char *)im->pixels, width, height); } else { origin = imlib_create_image_using_data(im->width, im->height, (DATA32 *)im->pixels); if (!origin) continue; imlib_context_set_image(origin); imlib_image_set_has_alpha(1); + imlib_context_set_anti_alias(im->transparent ? 0 : 1); /* anti-aliasing messes up the clip mask */ scaled = imlib_create_cropped_scaled_image(0, 0, im->width, im->height, width, height); imlib_free_image_and_decache(); if (!scaled) @@ -3212,6 +3220,8 @@ xfinishdraw(void) #endif // ALPHA_PATCH }; XPutImage(xw.dpy, (Drawable)im->pixmap, dc.gc, &ximage, 0, 0, 0, 0, width, height); + if (im->transparent) + im->clipmask = (void *)sixel_create_clipmask((char *)imlib_image_get_data_for_reading_only(), width, height); imlib_free_image_and_decache(); } } @@ -3240,12 +3250,17 @@ xfinishdraw(void) gcvalues.graphics_exposures = False; gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, &gcvalues); #if ANYSIZE_PATCH - XCopyArea(xw.dpy, (Drawable)im->pixmap, xw.buf, gc, 0, 0, - width, height, win.hborderpx + im->x * win.cw, win.vborderpx + im->y * win.ch); + destx = win.hborderpx + im->x * win.cw; + desty = win.vborderpx + im->y * win.ch; #else - XCopyArea(xw.dpy, (Drawable)im->pixmap, xw.buf, gc, 0, 0, - width, height, borderpx + im->x * win.cw, borderpx + im->y * win.ch); + destx = borderpx + im->x * win.cw; + desty = borderpx + im->y * win.ch; #endif // ANYSIZE_PATCH + if (im->clipmask) { + XSetClipMask(xw.dpy, gc, (Drawable)im->clipmask); + XSetClipOrigin(xw.dpy, gc, destx, desty); + } + XCopyArea(xw.dpy, (Drawable)im->pixmap, xw.buf, gc, 0, 0, width, height, destx, desty); XFreeGC(xw.dpy, gc); } #endif // SIXEL_PATCH