aboutsummaryrefslogtreecommitdiffstats
path: root/src/graphics.c
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-14 23:25:46 +0200
committeruser <user@clank>2026-08-14 23:25:46 +0200
commitaa01b72770d83091a53a43722ecc9a527bc18931 (patch)
tree04a35b6ba2566a154cc344ebc0e031941cbce873 /src/graphics.c
parentsl0ppty: carry the terminal's cell size, so images can have a size (diff)
downloadslosh-aa01b72770d83091a53a43722ecc9a527bc18931.tar.gz
slosh-aa01b72770d83091a53a43722ecc9a527bc18931.tar.xz
slosh-aa01b72770d83091a53a43722ecc9a527bc18931.zip
sl0ppty: re-emit what the program meant, not what it covers
Images rendered after the last commit, and were still wrong twice. Both mistakes are invisible in a still picture, which is what every test was looking at, and obvious the moment anything moves. **The sub-cell offset was dropped.** A placement says where the image starts *inside* its first cell (`X=`/`Y=`); that is what makes motion smooth rather than a slideshow. We tracked the placement and re-emitted it without those, so a bouncing logo moved in steps of a whole cell -- eight pixels across, sixteen down. **`c=`/`r=` were passed on unconditionally.** They mean *scale the image into this many cells*, and the count a natural-size image happens to cover goes up by one whenever it straddles one more boundary -- so the picture changed size as it slid along. They are now sent only when the program asked for them, which lib-vt reports as 0 when it did not. Cropping splits the same way, and getting it wrong took a test failure to notice: at natural size a lost cell costs the screen pixels the pane has left (measured from the offset, not the cell edge, or an offset image hangs up to a cell over the border); when the program asked to scale, a lost cell is worth source/cells pixels and the crop is proportional. Cropping a 4-pixel image scaled across 40 cells by screen pixels removes all of it. gfx_place() took twenty positional arguments, half of them uint32_t pixel quantities where a transposed pair compiles perfectly and lands the picture slightly wrong -- the exact bug class this file exists for. It takes a struct now. `{"cmd":"graphics","format":"bytes"}` answers with the escape sequences the client is actually sent. The model being right is not the same as the bytes being right, and this whole episode was a lesson in asserting on the first while the second was wrong. The new tests assert on the bytes.
Diffstat (limited to 'src/graphics.c')
-rw-r--r--src/graphics.c49
1 files changed, 32 insertions, 17 deletions
diff --git a/src/graphics.c b/src/graphics.c
index bf9aa1d..d3fc07c 100644
--- a/src/graphics.c
+++ b/src/graphics.c
@@ -127,11 +127,10 @@ static void transmit(graphics_t *g, gfx_image_t *img, uint32_t px_w,
img->sent = true;
}
-void gfx_place(graphics_t *g, uint32_t pane, uint32_t src_id, uint64_t gen,
- uint32_t place_id, uint16_t col, uint16_t row, uint16_t cols,
- uint16_t rows, uint32_t sx, uint32_t sy, uint32_t sw,
- uint32_t sh, uint32_t px_w, uint32_t px_h, int format,
- int compression, const uint8_t *data, size_t data_len) {
+void gfx_place(graphics_t *g, const gfx_req_t *req) {
+ if (!g || !req) return;
+ const uint32_t pane = req->pane, src_id = req->src_id;
+ const uint64_t gen = req->gen;
gfx_image_t *img = img_find(g, pane, src_id);
if (!img) {
if (g->nimgs == g->imgcap) {
@@ -147,9 +146,10 @@ void gfx_place(graphics_t *g, uint32_t pane, uint32_t src_id, uint64_t gen,
img->sent = false;
}
if (!img->sent)
- transmit(g, img, px_w, px_h, format, compression, data, data_len);
+ transmit(g, img, req->px_w, req->px_h, req->format, req->compression,
+ req->data, req->data_len);
- uint32_t pid = place_id ? place_id : 1;
+ uint32_t pid = req->place_id ? req->place_id : 1;
gfx_place_t *slot = NULL;
for (size_t i = 0; i < g->nplaces; i++)
if (g->places[i].out_id == img->out_id && g->places[i].place_id == pid)
@@ -164,14 +164,18 @@ void gfx_place(graphics_t *g, uint32_t pane, uint32_t src_id, uint64_t gen,
slot->out_id = img->out_id;
slot->place_id = pid;
}
- slot->col = col;
- slot->row = row;
- slot->cols = cols;
- slot->rows = rows;
- slot->sx = sx;
- slot->sy = sy;
- slot->sw = sw;
- slot->sh = sh;
+ slot->col = req->col;
+ slot->row = req->row;
+ slot->cols = req->cols;
+ slot->rows = req->rows;
+ slot->x_off = req->x_off;
+ slot->y_off = req->y_off;
+ slot->scale_cols = req->scale_cols;
+ slot->scale_rows = req->scale_rows;
+ slot->sx = req->sx;
+ slot->sy = req->sy;
+ slot->sw = req->sw;
+ slot->sh = req->sh;
slot->live = true;
}
@@ -194,8 +198,19 @@ char *gfx_flush(graphics_t *g, size_t *out_len) {
for (size_t i = 0; i < g->nplaces; i++) {
gfx_place_t *p = &g->places[i];
out_fmt(g, "\x1b[%u;%uH", p->row + 1, p->col + 1);
- out_fmt(g, "\x1b_Ga=p,q=2,C=1,i=%u,p=%u,c=%u,r=%u", p->out_id, p->place_id,
- p->cols, p->rows);
+ out_fmt(g, "\x1b_Ga=p,q=2,C=1,i=%u,p=%u", p->out_id, p->place_id);
+ /* c=/r= mean *scale into this many cells*, so they are passed on only
+ * when the program asked for them. Sending the cell count a natural-size
+ * image happens to cover looks identical in a still picture and makes a
+ * moving one change size, because that count goes up by one whenever the
+ * image straddles one more cell boundary. */
+ if (p->scale_cols || p->scale_rows)
+ out_fmt(g, ",c=%u,r=%u", p->scale_cols, p->scale_rows);
+ /* Where the image sits inside its first cell. Without these an image can
+ * only ever land on a cell boundary, so anything moving smoothly arrives
+ * in steps of a whole cell -- eight pixels across, sixteen down. */
+ if (p->x_off) out_fmt(g, ",X=%u", p->x_off);
+ if (p->y_off) out_fmt(g, ",Y=%u", p->y_off);
/* The source rectangle is what actually crops; c/r alone would scale. */
if (p->sw || p->sh)
out_fmt(g, ",x=%u,y=%u,w=%u,h=%u", p->sx, p->sy, p->sw, p->sh);