From 9ad0056ca9b74cc42ca6b3299413ef5c4b08916f Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Wed, 14 Oct 2026 21:08:35 +0100 Subject: [PATCH] Image storage and helper changes from the Kitty additions. --- image-fallback.c | 61 ++++---- image-grid.c | 50 ++++--- image-sixel.c | 65 ++++----- image.c | 296 ++++++++++++++++---------------------- regress/image-fallback.sh | 12 ++ tmux-image.h | 52 ++++--- 6 files changed, 254 insertions(+), 282 deletions(-) diff --git a/image-fallback.c b/image-fallback.c index d8888903b..e83a799e5 100644 --- a/image-fallback.c +++ b/image-fallback.c @@ -18,6 +18,7 @@ #include +#include #include #include "tmux.h" @@ -50,52 +51,47 @@ fallback_get_flags(__unused struct tty *tty) /* Get the average colour and brightness of an area. */ static int -fallback_sample(struct image *im, uint64_t px, uint64_t py, uint64_t pw, - uint64_t ph, u_char *brightness) +fallback_sample(struct image *im, u_int px, u_int py, u_int pw, u_int ph, + u_char *brightness) { - const u_char *pixels, *p; - uint64_t red = 0, green = 0, blue = 0, sum = 0, count; - u_int w, h, cw, ch, x0, x1, y0, y1, x, y; - size_t stride; + struct image_geom *ig = image_get_geometry(im); + struct image_bounds ib; + const u_char *pixels, *p; + double red = 0, green = 0, blue = 0, sum = 0; + double wx, wy, weight, area; + u_int x0, x1, y0, y1, x, y; + size_t stride; - image_get_size(im, &w, &h); - image_get_canvas_size(im, &cw, &ch); - pixels = image_get_pixels(im, &stride, NULL); - - x0 = px * cw / pw; - x1 = ((px + 1) * cw + pw - 1) / pw; - y0 = py * ch / ph; - y1 = ((py + 1) * ch + ph - 1) / ph; - if (x1 <= x0) - x1 = x0 + 1; - if (y1 <= y0) - y1 = y0 + 1; - if (x0 >= w || y0 >= h) { + if (!image_get_bounds(ig, px, py, 1, 1, pw, ph, &ib)) { if (brightness != NULL) *brightness = 0; return (colour_join_rgb(0, 0, 0)); } - count = (uint64_t)(x1 - x0) * (y1 - y0); + area = ib.area; + pixels = image_get_pixels(im, &stride, NULL); - if (x1 > w) - x1 = w; - if (y1 > h) - y1 = h; + x0 = floor(ib.left); + x1 = ceil(ib.right); + y0 = floor(ib.top); + y1 = ceil(ib.bottom); for (y = y0; y < y1; y++) { + wy = fmin(y + 1, ib.bottom) - fmax(y, ib.top); for (x = x0; x < x1; x++) { p = pixels + y * stride + x * 4; + wx = fmin(x + 1, ib.right) - fmax(x, ib.left); + weight = wx * wy; - red += p[0] * p[3] / 255; - green += p[1] * p[3] / 255; - blue += p[2] * p[3] / 255; + red += (p[0] * p[3] / 255) * weight; + green += (p[1] * p[3] / 255) * weight; + blue += (p[2] * p[3] / 255) * weight; sum += ((2126ULL * p[0] + 7152ULL * p[1] + - 722ULL * p[2]) / 10000) * p[3] / 255; + 722ULL * p[2]) / 10000) * p[3] / 255 * weight; } } if (brightness != NULL) - *brightness = sum / count; - return (colour_join_rgb(red / count, green / count, blue / count)); + *brightness = sum / area; + return (colour_join_rgb(red / area, green / area, blue / area)); } /* Set a cell to a character representing part of an image. */ @@ -105,7 +101,9 @@ fallback_get_cell(struct tty *tty, struct image *im, u_int x, u_int y, { static const char a[] = " .:-=+*#%@"; static const u_char bold_shades[8] = { 0, 1, 1, 2, 2, 3, 4, 4 }; - u_int ch, level, nlevels, sx, sy, colours; + struct image_geom *ig = image_get_geometry(im); + u_int ch, level, nlevels, colours; + u_int sx = ig->sx, sy = ig->sy; u_char brightness; if (out != gc) @@ -113,7 +111,6 @@ fallback_get_cell(struct tty *tty, struct image *im, u_int x, u_int y, out->flags &= GRID_FLAG_SELECTED; out->attr = 0; out->link = 0; - image_get_size_in_cells(im, &sx, &sy); if (x >= sx || y >= sy) { utf8_set(&out->data, ' '); return; diff --git a/image-grid.c b/image-grid.c index cd3e5d24e..3ed6ff008 100644 --- a/image-grid.c +++ b/image-grid.c @@ -354,25 +354,25 @@ void image_grid_add(struct grid *gd, struct image *im, u_int x, u_int y, u_int src_x, u_int src_y, u_int sx, u_int sy) { + struct image_geom *ig; struct image_placement *plmt; struct image_line *line; u_int rows = gd->hsize + gd->sy, row; - u_int image_sx, image_sy; if (sx == 0 || sy == 0 || x >= gd->sx || y >= rows) return; - image_get_size_in_cells(im, &image_sx, &image_sy); - if (src_x >= image_sx || src_y >= image_sy) + ig = image_get_geometry(im); + if (src_x >= ig->sx || src_y >= ig->sy) return; if (sx > gd->sx - x) sx = gd->sx - x; - if (sx > image_sx - src_x) - sx = image_sx - src_x; + if (sx > ig->sx - src_x) + sx = ig->sx - src_x; if (sy > rows - y) sy = rows - y; - if (sy > image_sy - src_y) - sy = image_sy - src_y; + if (sy > ig->sy - src_y) + sy = ig->sy - src_y; plmt = image_placement_create(gd, im); for (row = 0; row < sy; row++) { @@ -739,16 +739,14 @@ static int image_cell_covers(struct image *new, u_int nx, u_int ny, struct image *old, u_int ox, u_int oy) { - u_int npx, npy, nsx, nsy, opx, opy, osx, osy, xx, yy; - u_int ncw, nch, ocw, och, ncx, ncy, ocx, ocy; - const u_char *np, *op, *npixels, *opixels; - size_t nstride, ostride; + struct image_geom *ng = image_get_geometry(new); + struct image_geom *og = image_get_geometry(old); + u_int npx, npy, nsx, nsy, opx, opy, osx, osy, xx, yy; + const u_char *np, *op, *npixels, *opixels; + size_t nstride, ostride; - image_get_canvas_size(new, &ncw, &nch); - image_get_canvas_size(old, &ocw, &och); - image_get_size_in_cells(new, &ncx, &ncy); - image_get_size_in_cells(old, &ocx, &ocy); - if (ncw / ncx != ocw / ocx || nch / ncy != och / ocy) + if (ng->canvas_width / ng->sx != og->canvas_width / og->sx || + ng->canvas_height / ng->sy != og->canvas_height / og->sy) return (0); image_get_pixel_area(new, nx, ny, 1, 1, &npx, &npy, &nsx, &nsy); image_get_pixel_area(old, ox, oy, 1, 1, &opx, &opy, &osx, &osy); @@ -838,10 +836,11 @@ image_extend_row(struct image_line *line, struct image_placement *plmt, void image_grid_resize_width(struct grid *gd, u_int new_sx) { + struct image_geom *ig; struct image_placement *plmt; struct image_span *span; struct image_recover_row *rows; - u_int image_sx, image_sy, from, to; + u_int from, to; u_int nrows, i; if (gd->images == NULL) @@ -850,11 +849,11 @@ image_grid_resize_width(struct grid *gd, u_int new_sx) from = plmt->recover_from; if (from == 0 || plmt->x >= new_sx) continue; - image_get_size_in_cells(plmt->image, &image_sx, &image_sy); + ig = image_get_geometry(plmt->image); to = new_sx - plmt->x; - if (to > image_sx) - to = image_sx; + if (to > ig->sx) + to = ig->sx; if (to <= from) continue; @@ -877,7 +876,7 @@ image_grid_resize_width(struct grid *gd, u_int new_sx) rows[i].source_y, from, to); } free(rows); - plmt->recover_from = (to == image_sx ? 0 : to); + plmt->recover_from = (to == ig->sx ? 0 : to); } } @@ -885,18 +884,17 @@ image_grid_resize_width(struct grid *gd, u_int new_sx) void image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg) { + struct image_geom *ig = image_get_geometry(im); struct screen *s = ctx->s; struct grid *gd = s->grid; struct image_placement *plmt; struct grid_line *gl; struct image_line *line; - u_int cx = s->cx, cy = s->cy, y, sx, sy, lines; - u_int image_sx; + u_int cx = s->cx, cy = s->cy, y, lines; + u_int sx = ig->sx, sy = ig->sy; u_int origin_y = 0, h_origin_y, region_height; u_int remaining, n; - image_get_size_in_cells(im, &sx, &sy); - image_sx = sx; if (sx > screen_size_x(s) - cx) sx = screen_size_x(s) - cx; if (sx == 0) @@ -928,7 +926,7 @@ image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg) sy -= origin_y; } plmt = image_placement_create(gd, im); - if (sx < image_sx) { + if (sx < ig->sx) { plmt->x = cx; plmt->recover_from = sx; } diff --git a/image-sixel.c b/image-sixel.c index 0eb2f5eb5..0a16733ee 100644 --- a/image-sixel.c +++ b/image-sixel.c @@ -21,6 +21,7 @@ #include #include +#include #include #include @@ -165,17 +166,7 @@ struct sixel_rgb { /* Pixels and dimensions of the image being encoded. */ struct sixel_source { - /* Source pixels and number of bytes between rows. */ - const u_char *pixels; - size_t stride; - - /* Source image and canvas sizes in pixels and size in cells. */ - u_int width; - u_int height; - u_int canvas_width; - u_int canvas_height; - u_int sx; - u_int sy; + struct image *image; /* Destination canvas size in pixels. */ u_int dst_w; @@ -636,9 +627,10 @@ sixel_colour_to_rgb(u_int c, u_char *r, u_char *g, u_char *b) static struct image * sixel_to_image(struct sixel_image *si) { - struct image *im; - u_char *pixels, *pixel, r, g, b; - u_int x, y, c, sx, sy; + struct image *im; + struct image_geom ig = { 0 }; + u_char *pixels, *pixel, r, g, b; + u_int x, y, c, sx, sy; if ((uint64_t)si->x * si->y > IMAGE_SIZE_LIMIT / 4) return (NULL); @@ -669,8 +661,13 @@ sixel_to_image(struct sixel_image *si) pixel[3] = 255; } } - im = image_create(si->x, si->y, sx * si->xpixel, sy * si->ypixel, sx, - sy, pixels); + ig.src_w = ig.scaled_width = si->x; + ig.src_h = ig.scaled_height = si->y; + ig.canvas_width = sx * si->xpixel; + ig.canvas_height = sy * si->ypixel; + ig.sx = sx; + ig.sy = sy; + im = image_create(&ig, pixels); if (im == NULL) free(pixels); return (im); @@ -1077,13 +1074,11 @@ sixel_clamp_colour(int colour) /* Return a source pixel mapped to an output SIXEL pixel. */ static const u_char * -sixel_from_image_pixel(const struct sixel_source *src, u_int x, u_int y) +sixel_from_image_pixel(struct sixel_source *src, u_int x, u_int y) { - u_int px, py; + u_int px = src->dst_x + x, py = src->dst_y + y; - px = ((uint64_t)src->dst_x + x) * src->canvas_width / src->dst_w; - py = ((uint64_t)src->dst_y + y) * src->canvas_height / src->dst_h; - return (src->pixels + py * src->stride + px * 4); + return (image_get_pixel(src->image, px, py, src->dst_w, src->dst_h)); } /* Make a palette from the visible pixels in the output area. */ @@ -1170,6 +1165,7 @@ sixel_make_image(struct image *im, u_int cx, u_int cy, u_int csx, u_int csy, struct sixel_image *si; struct sixel_rgb palette[SIXEL_PALETTE_SIZE]; struct sixel_source src; + struct image_geom *ig = image_get_geometry(im); const u_char *pixel; uint16_t *cache; int *current, *next, *tmp; @@ -1179,18 +1175,19 @@ sixel_make_image(struct image *im, u_int cx, u_int cy, u_int csx, u_int csy, uint64_t left, right, top, bottom; /* Work out the requested cell crop in destination pixel coordinates. */ - src.pixels = image_get_pixels(im, &src.stride, NULL); - image_get_size(im, &src.width, &src.height); - image_get_canvas_size(im, &src.canvas_width, &src.canvas_height); - image_get_size_in_cells(im, &src.sx, &src.sy); - dst_w = (uint64_t)src.sx * cw; - dst_h = (uint64_t)src.sy * ch; + src.image = im; + dst_w = (uint64_t)ig->sx * cw; + dst_h = (uint64_t)ig->sy * ch; if (dst_w > UINT_MAX || dst_h > UINT_MAX) return (NULL); - content_width = ((uint64_t)src.width * dst_w + - src.canvas_width - 1) / src.canvas_width; - content_height = ((uint64_t)src.height * dst_h + - src.canvas_height - 1) / src.canvas_height; + content_width = ceil((ig->canvas_xoff + ig->scaled_width) * dst_w / + ig->canvas_width); + content_height = ceil((ig->canvas_yoff + ig->scaled_height) * dst_h / + ig->canvas_height); + if (content_width > dst_w) + content_width = dst_w; + if (content_height > dst_h) + content_height = dst_h; /* Clip the requested cell rectangle to output pixel bounds. */ left = (uint64_t)cx * cw; @@ -1313,8 +1310,9 @@ static struct sixel_image * sixel_get_image(struct image *im, u_int cw, u_int ch) { struct sixel_image *si; + struct image_geom *ig = image_get_geometry(im); size_t size; - u_int id = image_get_id(im), sx, sy; + u_int id = image_get_id(im); struct sixel_item *cache, find = { .id = id, .cw = cw, .ch = ch }; cache = RB_FIND(sixel_tree, &sixel_tree, &find); @@ -1323,9 +1321,8 @@ sixel_get_image(struct image *im, u_int cw, u_int ch) TAILQ_INSERT_TAIL(&sixel_cache, cache, entry); return (cache->si); } - image_get_size_in_cells(im, &sx, &sy); - si = sixel_make_image(im, 0, 0, sx, sy, cw, ch); + si = sixel_make_image(im, 0, 0, ig->sx, ig->sy, cw, ch); if (si == NULL) return (NULL); diff --git a/image.c b/image.c index 6dc5ecb9f..fdf64f533 100644 --- a/image.c +++ b/image.c @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -29,25 +30,11 @@ struct image { u_int id; u_int references; - u_int flags; -#define IMAGE_FLAG_OWN_PIXELS 0x1 + struct image_geom geom; struct image *parent; - u_int source_id; u_int order_id; - /* Size in cells. */ - u_int sx; - u_int sy; - - /* Size in pixels. */ - u_int width; - u_int height; - - /* Canvas size in pixels. */ - u_int canvas_width; - u_int canvas_height; - /* * RGBA pixels use four bytes each. Stride is the number of bytes * between the start of each row. Size is the number of bytes @@ -62,6 +49,8 @@ struct image { }; RB_HEAD(images, image); static struct images images = RB_INITIALIZER(&images); + +/* Next image ID. */ static u_int image_next_id; /* Compare images by IDs. */ @@ -95,8 +84,7 @@ image_get_id(struct image *im) /* Allocate an image and assign a unique ID. */ static struct image * -image_alloc(u_int w, u_int h, u_int cw, u_int ch, u_int sx, u_int sy, - size_t stride, u_char *pixels) +image_alloc(struct image_geom *ig, size_t stride, u_char *pixels) { struct image *im; u_int id; @@ -110,15 +98,10 @@ image_alloc(u_int w, u_int h, u_int cw, u_int ch, u_int sx, u_int sy, im = xcalloc(1, sizeof *im); im->id = id; im->references = 1; - im->source_id = im->order_id = id; - im->width = w; - im->height = h; - im->canvas_width = cw; - im->canvas_height = ch; - im->sx = sx; - im->sy = sy; + im->order_id = id; + im->geom = *ig; im->stride = stride; - im->size = (h - 1) * stride + w * 4; + im->size = (ig->src_h - 1) * stride + ig->src_w * 4; im->pixels = pixels; RB_INSERT(images, &images, im); return (im); @@ -126,107 +109,41 @@ image_alloc(u_int w, u_int h, u_int cw, u_int ch, u_int sx, u_int sy, /* Create an image with the provided pixels. */ struct image * -image_create(u_int w, u_int h, u_int cw, u_int ch, u_int sx, u_int sy, - u_char *pixels) +image_create(struct image_geom *ig, u_char *pixels) { - struct image *im; + size_t stride = ig->src_w * 4; - if (w == 0 || h == 0 || cw < w || ch < h) - return (NULL); - if (sx == 0 || sy == 0 || pixels == NULL) - return (NULL); - if ((uint64_t)w * h > IMAGE_SIZE_LIMIT / 4) - return (NULL); - if (sx > USHRT_MAX || sy > USHRT_MAX) - return (NULL); - if ((uint64_t)sx * sy > IMAGE_SIZE_LIMIT) - return (NULL); - - im = image_alloc(w, h, cw, ch, sx, sy, w * 4, pixels); - im->flags |= IMAGE_FLAG_OWN_PIXELS; - return (im); + return (image_create_with_parent(NULL, ig, stride, pixels)); } -/* Resample the crop into transparent, padded RGBA pixels. */ -static u_char * -image_view_resample(struct image *src, struct image_view *view, u_int w, - u_int h) -{ - u_char *pixels, *row, *src_row; - u_int src_x, src_y; - uint64_t x, y, off; - - pixels = xcalloc(w * h, 4); - for (y = 0; y < view->scaled_height; y++) { - src_y = view->y + y * view->height / view->scaled_height; - src_row = src->pixels + src_y * src->stride + view->x * 4; - - off = ((y + view->canvas_yoff) * w + view->canvas_xoff) * 4; - row = pixels + off; - - if (view->scaled_width == view->width) - memcpy(row, src_row, view->width * 4); - else { - for (x = 0; x < view->scaled_width; x++) { - src_x = x * view->width / view->scaled_width; - memcpy(row + x * 4, src_row + src_x * 4, 4); - } - } - } - return (pixels); -} - -/* Crop and resample an image. The view takes a reference to the image. */ +/* Create an image based on a parent image. */ struct image * -image_create_view(struct image *src, struct image_view *view) +image_create_with_parent(struct image *parent, struct image_geom *ig, + size_t stride, u_char *pixels) { struct image *im; - u_char *pixels; - u_int w, h; - if (src == NULL || - view == NULL || - view->x >= src->width || - view->y >= src->height || - view->width == 0 || - view->height == 0 || - view->scaled_width == 0 || - view->scaled_height == 0 || - view->sx == 0 || - view->sy == 0 || - view->width > src->width - view->x || - view->height > src->height - view->y || - view->sx > USHRT_MAX || - view->sy > USHRT_MAX || - (uint64_t)view->sx * view->sy > IMAGE_SIZE_LIMIT || - view->canvas_xoff > view->canvas_width || - view->scaled_width > view->canvas_width - view->canvas_xoff || - view->canvas_yoff > view->canvas_height || - view->scaled_height > view->canvas_height - view->canvas_yoff) + if (ig->src_w == 0 || ig->src_h == 0) + return (NULL); + if (ig->canvas_width == 0 || ig->canvas_height == 0) + return (NULL); + if (ig->sx == 0 || ig->sy == 0 || pixels == NULL) + return (NULL); + if ((uint64_t)ig->src_w * ig->src_h > IMAGE_SIZE_LIMIT / 4) + return (NULL); + if (ig->sx > USHRT_MAX || ig->sy > USHRT_MAX) + return (NULL); + if (ig->sx * ig->sy > IMAGE_SIZE_LIMIT || stride < ig->src_w * 4) + return (NULL); + if (ig->scaled_width <= 0 || ig->scaled_height <= 0) return (NULL); - if (view->scaled_width == view->width && - view->scaled_height == view->height && - view->canvas_xoff == 0 && - view->canvas_yoff == 0) { - pixels = src->pixels + view->y * src->stride + view->x * 4; - im = image_alloc(view->width, view->height, view->canvas_width, - view->canvas_height, view->sx, view->sy, src->stride, - pixels); - } else { - w = view->canvas_xoff + view->scaled_width; - h = view->canvas_yoff + view->scaled_height; - if ((uint64_t)w * h > IMAGE_SIZE_LIMIT / 4) - return (NULL); - pixels = image_view_resample(src, view, w, h); - im = image_alloc(w, h, view->canvas_width, view->canvas_height, - view->sx, view->sy, w * 4, pixels); - im->flags |= IMAGE_FLAG_OWN_PIXELS; + im = image_alloc(ig, stride, pixels); + im->parent = parent; + if (parent != NULL) { + im->order_id = parent->order_id; + image_ref(parent); } - im->parent = src; - im->source_id = src->source_id; - im->order_id = src->order_id; - image_ref(src); return (im); } @@ -247,47 +164,104 @@ image_free(struct image *im) fatalx("invalid image reference %u", im->id); if (--im->references == 0) { RB_REMOVE(images, &images, im); - if (im->flags & IMAGE_FLAG_OWN_PIXELS) + if (im->parent == NULL) free(im->pixels); - if (im->parent != NULL) + else image_free(im->parent); free(im); } } -/* Get image size in pixels. */ -void -image_get_size(struct image *im, u_int *w, u_int *h) +/* Get the image geometry. */ +struct image_geom * +image_get_geometry(struct image *im) { - *w = im->width; - *h = im->height; + return (&im->geom); } -/* Get canvas size in pixels. */ -void -image_get_canvas_size(struct image *im, u_int *w, u_int *h) -{ - *w = im->canvas_width; - *h = im->canvas_height; -} - -/* Get image size in cells. */ -void -image_get_size_in_cells(struct image *im, u_int *sx, u_int *sy) -{ - *sx = im->sx; - *sy = im->sy; -} - -/* Get image pixels. */ -const u_char * +/* Get the image pixels. */ +u_char * image_get_pixels(struct image *im, size_t *stride, size_t *size) { if (stride != NULL) *stride = im->stride; if (size != NULL) *size = im->size; - return (im->pixels); + return (im->pixels + im->geom.src_y * im->stride + im->geom.src_x * 4); +} + +/* Convert an output X position to a source image position in pixels. */ +static double +image_map_x(struct image_geom *ig, u_int x, u_int w) +{ + double p; + + p = (double)x * ig->canvas_width; + p -= (double)ig->canvas_xoff * w; + p *= ig->src_w; + return (p / (w * ig->scaled_width)); +} + +/* Convert an output Y position to a source image position in pixels. */ +static double +image_map_y(struct image_geom *ig, u_int y, u_int h) +{ + double p; + + p = ((double)y * ig->canvas_height) - ((double)ig->canvas_yoff * h); + return ((p * ig->src_h) / (h * ig->scaled_height)); +} + +/* Find source image bounds for part of an output. */ +int +image_get_bounds(struct image_geom *ig, u_int x, u_int y, u_int w, u_int h, + u_int sx, u_int sy, struct image_bounds *ib) +{ + if (x >= sx || y >= sy || w == 0 || h == 0) + return (0); + if (w > sx - x) + w = sx - x; + if (h > sy - y) + h = sy - y; + + ib->left = image_map_x(ig, x, sx); + ib->top = image_map_y(ig, y, sy); + ib->right = image_map_x(ig, x + w, sx); + ib->bottom = image_map_y(ig, y + h, sy); + if (ib->left >= ig->src_w || + ib->top >= ig->src_h || + ib->right <= 0 || + ib->bottom <= 0) + return (0); + ib->area = (ib->right - ib->left) * (ib->bottom - ib->top); + + if (ib->left < 0) + ib->left = 0; + if (ib->top < 0) + ib->top = 0; + if (ib->right > ig->src_w) + ib->right = ig->src_w; + if (ib->bottom > ig->src_h) + ib->bottom = ig->src_h; + return (1); +} + +/* Map a pixel in the full output canvas to the original source pixels. */ +u_char * +image_get_pixel(struct image *im, u_int x, u_int y, u_int w, u_int h) +{ + static u_char empty[4]; + struct image_geom *ig = &im->geom; + double px, py; + u_int sx, sy; + + px = image_map_x(ig, x, w); + py = image_map_y(ig, y, h); + if (px < 0 || py < 0 || px >= ig->src_w || py >= ig->src_h) + return (empty); + sx = px; + sy = py; + return (image_get_pixels(im, NULL, NULL) + sy * im->stride + sx * 4); } /* Calculate the cell size. */ @@ -303,41 +277,23 @@ image_size_in_cells(u_int w, u_int h, u_int xpixel, u_int ypixel, u_int *sx, *sy = h / ypixel + (h % ypixel != 0); } -/* Find area covered by a rectangle of cells in pixels. */ +/* Find the source pixels covered by a rectangle of placement cells. */ void image_get_pixel_area(struct image *im, u_int x, u_int y, u_int w, u_int h, u_int *px, u_int *py, u_int *pw, u_int *ph) { - u_int left, top, right, bottom; + struct image_geom *ig; + struct image_bounds ib; *px = *py = *pw = *ph = 0; - if (im == NULL || x >= im->sx || y >= im->sy || w == 0 || h == 0) - return; - if (w > im->sx - x) - w = im->sx - x; - if (h > im->sy - y) - h = im->sy - y; - - left = (uint64_t)x * im->canvas_width / im->sx; - top = (uint64_t)y * im->canvas_height / im->sy; - right = ((uint64_t)(x + w) * im->canvas_width + im->sx - 1) / im->sx; - bottom = ((uint64_t)(y + h) * im->canvas_height + im->sy - 1) / im->sy; - - if (left >= im->width || top >= im->height) - *px = *py = 0; - else { - if (right <= left) - right = left + 1; - if (bottom <= top) - bottom = top + 1; - if (right > im->width) - right = im->width; - if (bottom > im->height) - bottom = im->height; - *px = left; - *py = top; - *pw = right - left; - *ph = bottom - top; + if (im != NULL) { + ig = &im->geom; + if (image_get_bounds(ig, x, y, w, h, ig->sx, ig->sy, &ib)) { + *px = floor(ib.left); + *py = floor(ib.top); + *pw = ceil(ib.right) - *px; + *ph = ceil(ib.bottom) - *py; + } } } diff --git a/regress/image-fallback.sh b/regress/image-fallback.sh index 71ad37d7c..bcde40b27 100755 --- a/regress/image-fallback.sh +++ b/regress/image-fallback.sh @@ -105,4 +105,16 @@ for nobr in (False, True): else: assert chars == ' \u2591\u2591\u2592\u2592\u2593\u2588\u2588', chars assert bold == [False, False, True, False, True, False, False, True], bold +# Odd cell heights split a source pixel between the two fallback samples. +with image_test(sys.argv[1]) as test: + test.run('set-option', '-as', 'terminal-features', 'screen-256color:RGB') + client = test.attach(graphical=False, ch=15) + test.emit(sixel(8, 15, [(100, 0, 0), (0, 0, 100)], + [y >= 8 for y in range(15) for x in range(8)])) + test.redraw() + terminal = Terminal(ch=15) + terminal.feed(client.data) + char, fg, bg, bold = terminal.cells[0, 0] + assert char == '\u2584' and not bold, (char, bold) + assert (fg, bg) == ((17, 0, 238), (255, 0, 0)), (fg, bg) PY diff --git a/tmux-image.h b/tmux-image.h index 625a67211..2aa27d7cd 100644 --- a/tmux-image.h +++ b/tmux-image.h @@ -43,29 +43,40 @@ struct tty; /* Backend images move with terminal scrolling. */ #define IMAGE_BACKEND_SCROLLS 0x2 -/* View of all or part of an image. */ -struct image_view { - /* Position and size in the image, in pixels. */ - u_int x; - u_int y; - u_int width; - u_int height; +/* Source image and placement geometry. */ +struct image_geom { + /* Position and size within the source RGBA pixels. */ + u_int src_x; + u_int src_y; + u_int src_w; + u_int src_h; - /* Size after scaling, in pixels. */ - u_int scaled_width; - u_int scaled_height; - - /* Destination canvas size and offset in pixels. */ + /* Actual image size in pixels, including padding. */ u_int canvas_width; u_int canvas_height; + + /* Content offset within the canvas, in pixels. */ u_int canvas_xoff; u_int canvas_yoff; - /* Destination size in terminal cells. */ + /* Scaled source size in pixels, excluding padding. */ + double scaled_width; + double scaled_height; + + /* Actual image size in cells, including padding. */ u_int sx; u_int sy; }; +/* Bounds within a source image, in pixels. */ +struct image_bounds { + double left; + double top; + double right; + double bottom; + double area; +}; + /* A part of an image to draw. */ struct image_rect { struct image *image; @@ -84,17 +95,18 @@ struct image_rect { }; /* image.c */ -struct image *image_create(u_int, u_int, u_int, u_int, u_int, u_int, - u_char *); -struct image *image_create_view(struct image *, struct image_view *); +struct image *image_create(struct image_geom *, u_char *); +struct image *image_create_with_parent(struct image *, struct image_geom *, + size_t, u_char *); struct image *image_find(u_int); u_int image_get_id(struct image *); void image_ref(struct image *); void image_free(struct image *); -void image_get_size(struct image *, u_int *, u_int *); -void image_get_canvas_size(struct image *, u_int *, u_int *); -void image_get_size_in_cells(struct image *, u_int *, u_int *); -const u_char *image_get_pixels(struct image *, size_t *, size_t *); +struct image_geom *image_get_geometry(struct image *); +u_char *image_get_pixels(struct image *, size_t *, size_t *); +int image_get_bounds(struct image_geom *, u_int, u_int, u_int, + u_int, u_int, u_int, struct image_bounds *); +u_char *image_get_pixel(struct image *, u_int, u_int, u_int, u_int); void image_size_in_cells(u_int, u_int, u_int, u_int, u_int *, u_int *); void image_get_pixel_area(struct image *, u_int, u_int, u_int,