Clean up image support and add regression tests

This commit is contained in:
Michael Grant
2026-10-04 11:44:00 +01:00
parent 91441d6ef2
commit 90ee6a89cc
31 changed files with 465 additions and 633 deletions

View File

@@ -524,7 +524,6 @@ sixel_size_in_cells(struct sixel_image *si, u_int *x, u_int *y)
image_size_in_cells(si->sx, si->sy, si->cell_w, si->cell_h, x, y);
}
#ifdef ENABLE_IMAGES
/* Convert one HLS component to RGB. */
static double
sixel_hue(double p, double q, double t)
@@ -619,7 +618,6 @@ sixel_to_image(struct sixel_image *si)
image_set_sixel(im, si);
return (im);
}
#endif
/* Scale or crop an indexed SIXEL image. */
struct sixel_image *
@@ -629,7 +627,8 @@ sixel_scale(struct sixel_image *si, u_int cell_w, u_int cell_h, u_int ox,
struct sixel_image *new;
u_int cx, cy, raster_sx, raster_sy;
u_int pox, poy, psx, psy, tsx, tsy, px, py;
uint64_t x0, x1, y0, y1, tx0, tx1, ty0, ty1;
uint64_t source_left, source_right, source_top, source_bottom;
uint64_t target_left, target_right, target_top, target_bottom;
u_int x, y, i;
/*
@@ -663,42 +662,42 @@ sixel_scale(struct sixel_image *si, u_int cell_w, u_int cell_h, u_int ox,
* only the final partial cell to the raster. Dividing the raster evenly
* between cells would stretch every complete cell and squash the last.
*/
x0 = (uint64_t)ox * si->cell_w;
x1 = (uint64_t)(ox + sx) * si->cell_w;
y0 = (uint64_t)oy * si->cell_h;
y1 = (uint64_t)(oy + sy) * si->cell_h;
if (x1 > raster_sx)
x1 = raster_sx;
if (y1 > raster_sy)
y1 = raster_sy;
if (x1 <= x0 || y1 <= y0)
source_left = (uint64_t)ox * si->cell_w;
source_right = (uint64_t)(ox + sx) * si->cell_w;
source_top = (uint64_t)oy * si->cell_h;
source_bottom = (uint64_t)(oy + sy) * si->cell_h;
if (source_right > raster_sx)
source_right = raster_sx;
if (source_bottom > raster_sy)
source_bottom = raster_sy;
if (source_right <= source_left || source_bottom <= source_top)
return (NULL);
pox = x0;
poy = y0;
psx = x1 - x0;
psy = y1 - y0;
pox = source_left;
poy = source_top;
psx = source_right - source_left;
psy = source_bottom - source_top;
/*
* Preserve any partial final source cell. The grid still covers whole
* cells, but the SIXEL raster must end at the corresponding pixel offset
* rather than stretching to the cell boundary.
*/
tx1 = ((uint64_t)raster_sx * cell_w + si->cell_w - 1) /
target_right = ((uint64_t)raster_sx * cell_w + si->cell_w - 1) /
si->cell_w;
ty1 = ((uint64_t)raster_sy * cell_h + si->cell_h - 1) /
target_bottom = ((uint64_t)raster_sy * cell_h + si->cell_h - 1) /
si->cell_h;
if (tx1 > UINT_MAX || ty1 > UINT_MAX)
if (target_right > UINT_MAX || target_bottom > UINT_MAX)
return (NULL);
tx0 = (uint64_t)ox * cell_w;
ty0 = (uint64_t)oy * cell_h;
if (tx0 >= tx1 || ty0 >= ty1)
target_left = (uint64_t)ox * cell_w;
target_top = (uint64_t)oy * cell_h;
if (target_left >= target_right || target_top >= target_bottom)
return (NULL);
if ((uint64_t)(ox + sx) * cell_w < tx1)
tx1 = (uint64_t)(ox + sx) * cell_w;
if ((uint64_t)(oy + sy) * cell_h < ty1)
ty1 = (uint64_t)(oy + sy) * cell_h;
tsx = tx1 - tx0;
tsy = ty1 - ty0;
if ((uint64_t)(ox + sx) * cell_w < target_right)
target_right = (uint64_t)(ox + sx) * cell_w;
if ((uint64_t)(oy + sy) * cell_h < target_bottom)
target_bottom = (uint64_t)(oy + sy) * cell_h;
tsx = target_right - target_left;
tsy = target_bottom - target_top;
if (tsx == 0 || tsy == 0)
return (NULL);
@@ -1214,15 +1213,14 @@ 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 *source, u_int sourcex0,
u_int sourcey0,
u_int sourcewidth, u_int sourceheight, u_int sx, u_int sy, u_int x,
u_int y)
sixel_from_image_pixel(const struct sixel_source *source, u_int source_x,
u_int source_y, u_int source_width, u_int source_height, u_int output_sx,
u_int output_sy, u_int x, u_int y)
{
u_int sourcex, sourcey;
sourcex = sourcex0 + (uint64_t)x * sourcewidth / sx;
sourcey = sourcey0 + (uint64_t)y * sourceheight / sy;
sourcex = source_x + (uint64_t)x * source_width / output_sx;
sourcey = source_y + (uint64_t)y * source_height / output_sy;
if (sourcex >= source->width)
sourcex = source->width - 1;
if (sourcey >= source->height)
@@ -1232,7 +1230,7 @@ sixel_from_image_pixel(const struct sixel_source *source, u_int sourcex0,
/* Render an image rectangle as an indexed SIXEL image. */
static struct sixel_image *
sixel_from_image(struct image *im, u_int ox, u_int oy, u_int cells_x,
sixel_from_image(struct image *im, u_int cell_x, u_int cell_y, u_int cells_x,
u_int cells_y, u_int cell_w, u_int cell_h)
{
struct sixel_image *si;
@@ -1244,10 +1242,11 @@ sixel_from_image(struct image *im, u_int ox, u_int oy, u_int cells_x,
int *current, *next, *tmp;
int red_error, green_error, blue_error, alpha_error;
u_int x, y, sx, sy, index, error_index;
u_int sourcex0, sourcey0, sourcewidth, sourceheight;
u_int source_x, source_y, source_width, source_height;
u_int red, green, blue, alpha, colour, i, ncolours;
uint64_t destination_width, destination_height;
uint64_t content_width, content_height, x0, x1, y0, y1;
uint64_t content_width, content_height;
uint64_t left, right, top, bottom;
/* Work out the requested cell crop in destination pixel coordinates. */
source.pixels = image_get_pixels(im, &source.stride, NULL);
@@ -1265,36 +1264,36 @@ sixel_from_image(struct image *im, u_int ox, u_int oy, u_int cells_x,
source.canvas_height - 1) / source.canvas_height;
/* Convert the requested cell rectangle to clipped output pixel bounds. */
x0 = (uint64_t)ox * cell_w;
y0 = (uint64_t)oy * cell_h;
x1 = ((uint64_t)ox + cells_x) * cell_w;
y1 = ((uint64_t)oy + cells_y) * cell_h;
if (x1 > content_width)
x1 = content_width;
if (y1 > content_height)
y1 = content_height;
if (x1 <= x0 || y1 <= y0)
left = (uint64_t)cell_x * cell_w;
top = (uint64_t)cell_y * cell_h;
right = ((uint64_t)cell_x + cells_x) * cell_w;
bottom = ((uint64_t)cell_y + cells_y) * cell_h;
if (right > content_width)
right = content_width;
if (bottom > content_height)
bottom = content_height;
if (right <= left || bottom <= top)
return (NULL);
/* The clipped output bounds determine the SIXEL image dimensions. */
sx = x1 - x0;
sy = y1 - y0;
sx = right - left;
sy = bottom - top;
if (sx == 0 || sy == 0 || sx > SIXEL_WIDTH_LIMIT ||
sy > SIXEL_HEIGHT_LIMIT)
return (NULL);
/* Map the requested cell crop to the source image's pixel rectangle. */
image_get_pixel_rect(im, ox, oy, cells_x, cells_y, &sourcex0,
&sourcey0, &sourcewidth, &sourceheight);
if (sourcewidth == 0 || sourceheight == 0)
image_get_pixel_rect(im, cell_x, cell_y, cells_x, cells_y, &source_x,
&source_y, &source_width, &source_height);
if (source_width == 0 || source_height == 0)
return (NULL);
/* Build an adaptive palette from the visible nontransparent pixels. */
hg = xcalloc(SIXEL_HISTOGRAM_SIZE, sizeof *hg);
for (y = 0; y < sy; y++) {
for (x = 0; x < sx; x++) {
pixel = sixel_from_image_pixel(&source, sourcex0, sourcey0,
sourcewidth, sourceheight, sx, sy, x, y);
pixel = sixel_from_image_pixel(&source, source_x, source_y,
source_width, source_height, sx, sy, x, y);
if (pixel[3] == 0)
continue;
@@ -1338,8 +1337,8 @@ sixel_from_image(struct image *im, u_int ox, u_int oy, u_int cells_x,
next = xcalloc(((size_t)sx + 2) * 4, sizeof *next);
for (y = 0; y < sy; y++) {
for (x = 0; x < sx; x++) {
pixel = sixel_from_image_pixel(&source, sourcex0, sourcey0,
sourcewidth, sourceheight, sx, sy, x, y);
pixel = sixel_from_image_pixel(&source, source_x, source_y,
source_width, source_height, sx, sy, x, y);
error_index = (x + 1) * 4;
/* SIXEL pixels are binary, so dither alpha separately. */
alpha = sixel_clamp_colour((int)pixel[3] +
@@ -1488,15 +1487,17 @@ sixel_free_output(struct tty *tty, __unused int send)
static struct sixel_image *
sixel_render_image(struct image *im, u_int cell_w, u_int cell_h)
{
struct sixel_image *original;
struct sixel_image *original, *si;
u_int sx, sy;
image_get_size_in_cells(im, &sx, &sy);
/* Preserve SIXEL's original palette and indexed pixels when possible. */
original = image_get_sixel(im);
if (original != NULL)
return (sixel_fit(original, cell_w, cell_h, sx, sy));
return (sixel_from_image(im, 0, 0, sx, sy, cell_w, cell_h));
si = sixel_fit(original, cell_w, cell_h, sx, sy);
else
si = sixel_from_image(im, 0, 0, sx, sy, cell_w, cell_h);
return (si);
}
/* Return a rendered image from the SIXEL output cache. */
@@ -1613,8 +1614,7 @@ sixel_flush_output(struct tty *tty)
* other terminal output is reordered across a pending run.
*/
void
sixel_draw_rect(struct tty *tty, const struct image_rect *rectangle,
__unused const struct tty_style_ctx *style_ctx)
sixel_draw_rect(struct tty *tty, const struct image_rect *rectangle)
{
struct sixel_output *so = sixel_get_output(tty);
struct sixel_pending *sp = &so->pending;
@@ -1659,38 +1659,3 @@ sixel_redraw_start(struct tty *tty, u_int x, u_int y, u_int sx, u_int sy)
tty_repeat_space(tty, sx);
}
}
/* Convert a SIXEL image to a fallback screen. */
struct screen *
sixel_to_screen(struct sixel_image *si)
{
struct screen *s;
struct screen_write_ctx ctx;
struct grid_cell gc;
u_int x, y, sx, sy;
sixel_size_in_cells(si, &sx, &sy);
s = xmalloc(sizeof *s);
screen_init(s, sx, sy, 0);
memcpy(&gc, &grid_default_cell, sizeof gc);
gc.attr |= (GRID_ATTR_CHARSET|GRID_ATTR_DIM);
utf8_set(&gc.data, '~');
screen_write_start(&ctx, s);
if (sx == 1 || sy == 1) {
for (y = 0; y < sy; y++) {
for (x = 0; x < sx; x++)
grid_view_set_cell(s->grid, x, y, &gc);
}
} else {
screen_write_box(&ctx, sx, sy, BOX_LINES_DEFAULT, NULL, NULL);
for (y = 1; y < sy - 1; y++) {
for (x = 1; x < sx - 1; x++)
grid_view_set_cell(s->grid, x, y, &gc);
}
}
screen_write_stop(&ctx);
return (s);
}