kitty: draw negative z-index images under the text

Images with a negative z-index are composited between the background
and foreground passes of xdrawline, so that text renders on top of
them with alpha blending, as the protocol requires.
This commit is contained in:
a 2026-09-06 20:47:48 -05:00
parent cb1f41c9ae
commit a4f4f96c62
No known key found for this signature in database
GPG Key ID: 2F22877AA4DFDADB
3 changed files with 134 additions and 2 deletions

129
kitty.c
View File

@ -1941,6 +1941,8 @@ kitty_absorb_diacritic(Glyph *gp, Rune u)
/* rendering */
/* ------------------------------------------------------------------ */
static int kitty_cmp(const void *a, const void *b);
/* nearest-neighbour + bilinear scaling of a source rect to w x h RGBA */
static unsigned char *
kitty_scale(const unsigned char *src, int sw, int sh, int stride,
@ -2063,6 +2065,96 @@ kitty_prepare(KittyPlacement *p, int *outw, int *outh)
return 1;
}
/* like kitty_blit but restricted to the cell span [cx1, cx2) on row cy */
static void
kitty_blit_clipped(KittyPlacement *p, int px, int py, int cx1, int cx2, int cy)
{
Picture src, dstpic;
XRenderPictFormat *fmt;
XRenderPictureAttributes pa;
int w, h, bx, by;
int x0, x1, y0, y1; /* clip rect in window pixels */
int ix0, iy0; /* image origin in window pixels */
int sxo, syo, cw, chh;
if (!kitty_prepare(p, &w, &h))
return;
kitty_border(&bx, &by);
ix0 = bx + px * win.cw + p->xoff;
iy0 = by + (py - kitty_scr()) * win.ch + p->yoff;
x0 = KMAX(ix0, bx + cx1 * win.cw);
x1 = KMIN(ix0 + w, bx + cx2 * win.cw);
y0 = KMAX(iy0, by + cy * win.ch);
y1 = KMIN(iy0 + h, by + (cy + 1) * win.ch);
x1 = KMIN(x1, bx + win.tw);
y1 = KMIN(y1, by + win.th);
if (x1 <= x0 || y1 <= y0)
return;
sxo = x0 - ix0;
syo = y0 - iy0;
cw = x1 - x0;
chh = y1 - y0;
memset(&pa, 0, sizeof pa);
fmt = XRenderFindStandardFormat(xw.dpy, PictStandardARGB32);
if (!fmt)
return;
src = XRenderCreatePicture(xw.dpy, (Drawable)p->pixmap, fmt, 0, &pa);
fmt = XRenderFindVisualFormat(xw.dpy, xw.vis);
if (fmt) {
dstpic = XRenderCreatePicture(xw.dpy, xw.buf, fmt, 0, &pa);
XRenderComposite(xw.dpy, PictOpOver, src, None, dstpic,
sxo, syo, 0, 0, x0, y0, cw, chh);
XRenderFreePicture(xw.dpy, dstpic);
}
XRenderFreePicture(xw.dpy, src);
}
/*
* Composite the negative z-index images covering the cell span
* [x1, x2) on row y. Called after the background has been painted but
* before the glyphs, so that text appears on top of the image.
*/
void
kitty_draw_under_cells(int x1, int y, int x2)
{
KittyPlacement **list = NULL, *p;
int n = 0, cap = 0, i;
int s = IS_SET(MODE_ALTSCREEN) ? 1 : 0;
int scr = kitty_scr();
for (p = placements[s]; p; p = p->next) {
int px, py;
if (p->virt || p->z >= 0)
continue;
if (!kitty_resolve_pos(p, &px, &py, 0))
continue;
py -= scr;
if (px >= x2 || px + p->cols <= x1 || py > y || py + p->rows <= y)
continue;
if (n >= cap) {
int ncap = cap ? cap * 2 : 8;
KittyPlacement **t = realloc(list, ncap * sizeof *t);
if (!t)
break;
list = t;
cap = ncap;
}
list[n++] = p;
}
if (n > 1)
qsort(list, n, sizeof *list, kitty_cmp);
for (i = 0; i < n; i++) {
int px, py;
if (kitty_resolve_pos(list[i], &px, &py, 0))
kitty_blit_clipped(list[i], px, py, x1, x2, y);
}
free(list);
}
static void
kitty_blit(KittyPlacement *p, int px, int py)
{
@ -2401,8 +2493,12 @@ kitty_draw_placeholders(void)
}
}
void
kitty_draw(void)
/*
* Draw the visible placements whose z-index falls in [zlo, zhi].
* Negative z-index images are drawn under the text (see kitty_draw_under).
*/
static void
kitty_draw_range(int zlo, int zhi)
{
KittyPlacement **list = NULL, *p;
int n = 0, cap = 0, i;
@ -2416,6 +2512,8 @@ kitty_draw(void)
int px, py;
if (p->virt)
continue;
if (p->z < zlo || p->z > zhi)
continue;
if (!kitty_resolve_pos(p, &px, &py, 0))
continue;
if (px >= term.col || py - scr >= term.row || py - scr + p->rows <= 0)
@ -2441,11 +2539,38 @@ kitty_draw(void)
}
free(list);
}
void
kitty_draw(void)
{
kitty_draw_range(0, INT_MAX);
/* images displayed via Unicode placeholders */
kitty_draw_placeholders();
}
/* is the cell (x, y) covered by an image drawn under the text? */
int
kitty_cell_under(int x, int y)
{
KittyPlacement *p;
int s = IS_SET(MODE_ALTSCREEN) ? 1 : 0;
int scr = kitty_scr();
for (p = placements[s]; p; p = p->next) {
int px, py;
if (p->virt || p->z >= 0)
continue;
if (!kitty_resolve_pos(p, &px, &py, 0))
continue;
py -= scr;
if (x >= px && x < px + p->cols && y >= py && y < py + p->rows)
return 1;
}
return 0;
}
/* ------------------------------------------------------------------ */
/* terminal lifecycle hooks */
/* ------------------------------------------------------------------ */

View File

@ -76,6 +76,8 @@ typedef struct KittyPlacement {
/* entry points used by st.c / x.c */
void kitty_apc(const char *buf, size_t len);
void kitty_draw(void);
void kitty_draw_under_cells(int x1, int y, int x2);
int kitty_cell_under(int x, int y);
void kitty_scroll(int top, int bot, int n, int save);
void kitty_deleteplacements(int alt);
void kitty_clearregion(int x1, int y1, int x2, int y2);

5
x.c
View File

@ -3065,6 +3065,11 @@ xdrawline(Line line, int x1, int y1, int x2)
/* Draw line in 2 passes: background and foreground. This way wide glyphs
won't get truncated (#223) */
for (int dmode = DRAW_BG; dmode <= DRAW_FG; dmode <<= 1) {
#if KITTY_GRAPHICS_PATCH
/* negative z-index images sit between the background and the text */
if (dmode == DRAW_FG)
kitty_draw_under_cells(x1, y1, x2);
#endif // KITTY_GRAPHICS_PATCH
specs = xw.specbuf;
numspecs = numspecs_cached;
i = ox = 0;