From 48c85cdcf5ec739c22e9fb9a5b1f9e13702eb047 Mon Sep 17 00:00:00 2001 From: veltza <106755522+veltza@users.noreply.github.com> Date: Fri, 5 Jul 2024 12:30:55 +0300 Subject: [PATCH 1/2] sixel: refactor the sixel display mode (#142) This does not change the current behavior of SDM. --- st.c | 62 ++++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/st.c b/st.c index e643239..f1fc668 100644 --- a/st.c +++ b/st.c @@ -220,6 +220,9 @@ static void tsetattr(const int *, int); static void tsetchar(Rune, const Glyph *, int, int); static void tsetdirt(int, int); static void tsetscroll(int, int); +#if SIXEL_PATCH +static inline void tsetsixelattr(Line line, int x1, int x2); +#endif // SIXEL_PATCH static void tswapscreen(void); static void tsetmode(int, int, const int *, int); static int twrite(const char *, int, int); @@ -1149,6 +1152,15 @@ tsetdirtattr(int attr) } } +#if SIXEL_PATCH +void +tsetsixelattr(Line line, int x1, int x2) +{ + for (; x1 <= x2; x1++) + line[x1].mode |= ATTR_SIXEL; +} +#endif // SIXEL_PATCH + void tfulldirt(void) { @@ -2592,7 +2604,7 @@ strhandle(void) }; #if SIXEL_PATCH ImageList *im, *newimages, *next, *tail; - int i, x, y, x1, y1, x2, y2, numimages; + int i, x1, y1, x2, y2, numimages; int cx, cy; Line line; #if SCROLLBACK_PATCH || REFLOW_PATCH @@ -2736,36 +2748,40 @@ strhandle(void) } else { term.images = newimages; } - x2 = MIN(x2, term.col); - for (i = 0, im = newimages; im; im = next, i++) { - next = im->next; - #if SCROLLBACK_PATCH || REFLOW_PATCH - scr = IS_SET(MODE_ALTSCREEN) ? 0 : term.scr; - #endif // SCROLLBACK_PATCH - if (IS_SET(MODE_SIXEL_SDM)) { + x2 = MIN(x2, term.col) - 1; + if (IS_SET(MODE_SIXEL_SDM)) { + /* Sixel display mode: put the sixel in the upper left corner of + * the screen, disable scrolling (the sixel will be truncated if + * it is too long) and do not change the cursor position. */ + for (i = 0, im = newimages; im; im = next, i++) { + next = im->next; if (i >= term.row) { delete_image(im); continue; } im->y = i + scr; - line = term.line[i]; - } else { + tsetsixelattr(term.line[i], x1, x2); + term.dirty[MIN(im->y, term.row-1)] = 1; + } + } else { + for (i = 0, im = newimages; im; im = next, i++) { + next = im->next; + #if SCROLLBACK_PATCH || REFLOW_PATCH + scr = IS_SET(MODE_ALTSCREEN) ? 0 : term.scr; + #endif // SCROLLBACK_PATCH im->y = term.c.y + scr; - line = term.line[term.c.y]; - } - for (x = im->x; x < x2; x++) { - line[x].mode |= ATTR_SIXEL; - } - term.dirty[MIN(im->y, term.row-1)] = 1; - if (!IS_SET(MODE_SIXEL_SDM) && i < numimages-1) { - im->next = NULL; - tnewline(0); - im->next = next; + tsetsixelattr(term.line[term.c.y], x1, x2); + term.dirty[MIN(im->y, term.row-1)] = 1; + if (i < numimages-1) { + im->next = NULL; + tnewline(0); + im->next = next; + } } + /* if mode 8452 is set, sixel scrolling leaves cursor to right of graphic */ + if (IS_SET(MODE_SIXEL_CUR_RT)) + term.c.x = MIN(term.c.x + newimages->cols, term.col-1); } - /* if mode 8452 is set, sixel scrolling leaves cursor to right of graphic */ - if (!IS_SET(MODE_SIXEL_SDM) && IS_SET(MODE_SIXEL_CUR_RT)) - term.c.x = MIN(term.c.x + newimages->cols, term.col-1); } #endif // SIXEL_PATCH #if SYNC_PATCH From 7a581fe4e15b538d3440562aa7f89d775187f250 Mon Sep 17 00:00:00 2001 From: veltza <106755522+veltza@users.noreply.github.com> Date: Sun, 7 Jul 2024 22:18:09 +0300 Subject: [PATCH 2/2] sixel: improve the renderer (#143) In the current implementation, when text is written over an image, we have to cut the entire text line out of the image, regardless of how long the text is. It doesn't look good, but it was a design choice for the following reasons: 1) To keep the sixel engine as fast as possible 2) Most applications do not write text on the images anyway To bring the st terminal in line with other terminals that support sixels, I have now improved the sixel renderer so that the images can now have gaps, which allows the text to be printed inside the images. The changes should not affect performance in normal cases. Only when the renderer has to deal with the text there might be some performance hits depending on how many gaps there are in the images. --- patch/reflow.c | 15 +++++----- st.c | 15 ++++++---- x.c | 78 +++++++++++++++++++++++++++++--------------------- 3 files changed, 61 insertions(+), 47 deletions(-) diff --git a/patch/reflow.c b/patch/reflow.c index 2fd80f0..bf145a6 100644 --- a/patch/reflow.c +++ b/patch/reflow.c @@ -77,7 +77,7 @@ treflow_moveimages(int oldy, int newy) void treflow(int col, int row) { - int i, j, x, x2; + int i, j; 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 */ @@ -220,13 +220,12 @@ treflow(int col, int row) } /* 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 (x = im->x; x < x2; x++) - line[x].mode |= ATTR_SIXEL; + for (im = term.images; im; im = im->next) { + j = MIN(im->x + im->cols, col); + line = TLINE(im->y); + for (i = im->x; i < j; i++) { + if (!(line[i].mode & ATTR_SET)) + line[i].mode |= ATTR_SIXEL; } } #endif // SIXEL_PATCH diff --git a/st.c b/st.c index f1fc668..02725e9 100644 --- a/st.c +++ b/st.c @@ -2748,7 +2748,11 @@ strhandle(void) } else { term.images = newimages; } + #if COLUMNS_PATCH && !REFLOW + x2 = MIN(x2, term.maxcol) - 1; + #else x2 = MIN(x2, term.col) - 1; + #endif // COLUMNS_PATCH if (IS_SET(MODE_SIXEL_SDM)) { /* Sixel display mode: put the sixel in the upper left corner of * the screen, disable scrolling (the sixel will be truncated if @@ -3521,7 +3525,7 @@ tresize(int col, int row) #endif // COLUMNS_PATCH int *bp; #if SIXEL_PATCH - int x, x2; + int x2; Line line; ImageList *im, *next; #endif // SIXEL_PATCH @@ -3628,8 +3632,7 @@ tresize(int col, int row) } #if SIXEL_PATCH - /* expand images into new text cells to prevent them from being deleted in - * xfinishdraw() that draws the images */ + /* expand images into new text cells */ for (i = 0; i < 2; i++) { for (im = term.images; im; im = next) { next = im->next; @@ -3654,9 +3657,9 @@ 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].mode |= ATTR_SIXEL; + x2 = MIN(im->x + im->cols, col) - 1; + if (mincol < col && x2 >= mincol && im->x < col) + tsetsixelattr(line, MAX(im->x, mincol), x2); } tswapscreen(); } diff --git a/x.c b/x.c index bf403ff..16eb15c 100644 --- a/x.c +++ b/x.c @@ -3167,9 +3167,14 @@ xfinishdraw(void) ImageList *im, *next; Imlib_Image origin, scaled; XGCValues gcvalues; - GC gc; + GC gc = NULL; int width, height; - int x, x2, del, destx, desty; + int del, desty, mode, x1, x2, xend; + #if ANYSIZE_PATCH + int bw = win.hborderpx, bh = win.vborderpx; + #else + int bw = borderpx, bh = borderpx; + #endif // ANYSIZE_PATCH Line line; #endif // SIXEL_PATCH @@ -3260,43 +3265,50 @@ xfinishdraw(void) } } - /* clip the image so it does not go over to borders */ - x2 = MIN(im->x + im->cols, term.col); - width = MIN(width, (x2 - im->x) * win.cw); - - /* delete the image if the text cells behind it have been changed */ - #if SCROLLBACK_PATCH || REFLOW_PATCH - line = TLINE(im->y); - #else - line = term.line[im->y]; - #endif // SCROLLBACK_PATCH | REFLOW_PATCH - for (del = 0, x = im->x; x < x2; x++) { - if ((del = !(line[x].mode & ATTR_SIXEL))) - break; - } - if (del) { - delete_image(im); - continue; + /* create GC */ + if (!gc) { + memset(&gcvalues, 0, sizeof(gcvalues)); + gcvalues.graphics_exposures = False; + gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, &gcvalues); } - /* draw the image */ - memset(&gcvalues, 0, sizeof(gcvalues)); - gcvalues.graphics_exposures = False; - gc = XCreateGC(xw.dpy, xw.win, GCGraphicsExposures, &gcvalues); - #if ANYSIZE_PATCH - destx = win.hborderpx + im->x * win.cw; - desty = win.vborderpx + im->y * win.ch; - #else - destx = borderpx + im->x * win.cw; - desty = borderpx + im->y * win.ch; - #endif // ANYSIZE_PATCH + /* set the clip mask */ + desty = bh + im->y * win.ch; if (im->clipmask) { XSetClipMask(xw.dpy, gc, (Drawable)im->clipmask); - XSetClipOrigin(xw.dpy, gc, destx, desty); + XSetClipOrigin(xw.dpy, gc, bw + im->x * win.cw, desty); } - XCopyArea(xw.dpy, (Drawable)im->pixmap, xw.buf, gc, 0, 0, width, height, destx, desty); - XFreeGC(xw.dpy, gc); + + /* draw only the parts of the image that are not erased */ + #if SCROLLBACK_PATCH || REFLOW_PATCH + line = TLINE(im->y) + im->x; + #else + line = term.line[im->y] + im->x; + #endif // SCROLLBACK_PATCH || REFLOW_PATCH + xend = MIN(im->x + im->cols, term.col); + for (del = 1, x1 = im->x; x1 < xend; x1 = x2) { + mode = line->mode & ATTR_SIXEL; + for (x2 = x1 + 1; x2 < xend; x2++) { + if (((++line)->mode & ATTR_SIXEL) != mode) + break; + } + if (mode) { + XCopyArea(xw.dpy, (Drawable)im->pixmap, xw.buf, gc, + (x1 - im->x) * win.cw, 0, + MIN((x2 - x1) * win.cw, width - (x1 - im->x) * win.cw), height, + bw + x1 * win.cw, desty); + del = 0; + } + } + if (im->clipmask) + XSetClipMask(xw.dpy, gc, None); + + /* if all the parts are erased, we can delete the entire image */ + if (del && im->x + im->cols <= term.col) + delete_image(im); } + if (gc) + XFreeGC(xw.dpy, gc); #endif // SIXEL_PATCH #if !SINGLE_DRAWABLE_BUFFER_PATCH