/* $OpenBSD$ */ /* * Copyright (c) 2007 Nicholas Marriott * Copyright (c) 2026 Michael Grant * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include "tmux.h" /* An image. */ struct image { u_int id; u_int references; struct image_geom geom; struct image *parent; u_int order_id; /* * RGBA pixels use four bytes each. Stride is the number of bytes * between the start of each row. Size is the number of bytes * containing all of this image's pixels - it may include pixels that * are part of the parent image but not part of this image. */ size_t stride; size_t size; u_char *pixels; RB_ENTRY(image) entry; }; RB_HEAD(images, image); static struct images images = RB_INITIALIZER(&images); /* Next image ID. */ static u_int image_next_id; /* Compare images by IDs. */ static int image_cmp(struct image *a, struct image *b) { if (a->id < b->id) return (-1); if (a->id > b->id) return (1); return (0); } RB_GENERATE_STATIC(images, image, entry, image_cmp); /* Find an image by its ID. */ struct image * image_find(u_int id) { struct image find; find.id = id; return (RB_FIND(images, &images, &find)); } /* Get the ID of an image. */ u_int image_get_id(struct image *im) { return (im->id); } /* Allocate an image and assign a unique ID. */ static struct image * image_alloc(struct image_geom *ig, size_t stride, u_char *pixels) { struct image *im; u_int id; do { if (++image_next_id == 0) image_next_id++; id = image_next_id; } while (image_find(id) != NULL); im = xcalloc(1, sizeof *im); im->id = id; im->references = 1; im->order_id = id; im->geom = *ig; im->stride = stride; im->size = (ig->src_h - 1) * stride + ig->src_w * 4; im->pixels = pixels; RB_INSERT(images, &images, im); return (im); } /* Create an image with the provided pixels. */ struct image * image_create(struct image_geom *ig, u_char *pixels) { size_t stride = ig->src_w * 4; return (image_create_with_parent(NULL, ig, stride, pixels)); } /* Create an image based on a parent image. */ struct image * image_create_with_parent(struct image *parent, struct image_geom *ig, size_t stride, u_char *pixels) { struct image *im; 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); im = image_alloc(ig, stride, pixels); im->parent = parent; if (parent != NULL) { im->order_id = parent->order_id; image_ref(parent); } return (im); } /* Add a reference to an image. */ void image_ref(struct image *im) { if (im->references == UINT_MAX) fatalx("invalid image reference %u", im->id); im->references++; } /* Free an image. */ void image_free(struct image *im) { if (im->references == 0) fatalx("invalid image reference %u", im->id); if (--im->references == 0) { RB_REMOVE(images, &images, im); if (im->parent == NULL) free(im->pixels); else image_free(im->parent); free(im); } } /* Get the image geometry. */ struct image_geom * image_get_geometry(struct image *im) { return (&im->geom); } /* 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 + 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. */ void image_size_in_cells(u_int w, u_int h, u_int xpixel, u_int ypixel, u_int *sx, u_int *sy) { if (xpixel == 0) xpixel = 8; if (ypixel == 0) ypixel = 16; *sx = w / xpixel + (w % xpixel != 0); *sy = h / ypixel + (h % ypixel != 0); } /* 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) { struct image_geom *ig; struct image_bounds ib; *px = *py = *pw = *ph = 0; 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; } } }