Files
tmux/image-grid.c
Nicholas Marriott 1e294b175f SIXEL draw bits.
2026-10-14 11:40:55 +01:00

966 lines
24 KiB
C

/* $OpenBSD$ */
/*
* Copyright (c) 2026 Michael Grant <mgrant@grant.org>
*
* 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 <sys/types.h>
#include <limits.h>
#include <stdlib.h>
#include "tmux.h"
/* A run of cells on one grid line. */
struct image_span {
u_int x;
u_int sx;
u_int source_x;
u_int source_y;
struct image_line *line;
struct image_placement *placement;
TAILQ_ENTRY(image_span) line_entry;
TAILQ_ENTRY(image_span) placement_entry;
};
TAILQ_HEAD(image_spans, image_span);
/* Spans on one grid line. */
struct image_line {
struct image_spans spans;
};
/* A placement of an image, may be across multiple lines */
struct image_placement {
struct image_store *store;
struct image *image;
struct image_spans spans;
/* Original column and first source column not yet placed. */
u_int x;
u_int recover_from;
TAILQ_ENTRY(image_placement) entry;
};
TAILQ_HEAD(image_placements, image_placement);
/* Placements owned by one grid. */
struct image_store {
struct image_placements placements;
};
/* A shared placements copied when duplicating lines. */
struct image_copy_map {
struct image_placement *source;
struct image_placement *destination;
};
/* Part of a span waiting to be moved. */
struct image_move {
struct image_placement *placement;
u_int x;
u_int sx;
u_int source_x;
u_int source_y;
};
/* One image row to extend after a pane grows. */
struct image_recover_row {
struct image_line *line;
u_int source_y;
};
/* A backend to draw an image. */
struct image_backend {
const char *name;
int flags;
int (*select)(struct tty *);
const struct grid_cell *(*map_cell)(struct tty *, struct grid *, u_int,
u_int, const struct grid_cell *, struct grid_cell *,
int *);
void (*draw_rect)(struct tty *, struct image_rect *);
void (*start_draw)(struct tty *, u_int, u_int, u_int, u_int);
void (*flush)(struct tty *);
void (*free)(struct tty *);
};
/* Backend definitions. */
static struct image_backend image_backend_fallback = {
"fallback",
IMAGE_BACKEND_SCROLLS,
fallback_select,
fallback_map_cell,
NULL,
NULL,
NULL,
NULL
};
static struct image_backend image_backend_sixel = {
"sixel",
IMAGE_BACKEND_GRAPHICAL,
sixel_select,
NULL,
sixel_draw_rect,
sixel_start_draw,
sixel_flush_pending,
sixel_free_pending
};
/* Backends in order of preference. */
static struct image_backend *image_backends[] = {
&image_backend_sixel,
&image_backend_fallback
};
/* Update the image backend. */
int
image_tty_update(struct tty *tty)
{
struct image_backend *backend;
u_int i;
for (i = 0; i < nitems(image_backends); i++) {
backend = image_backends[i];
if (backend->select(tty))
break;
}
if (tty->image_backend == backend)
return (0);
if (tty->image_backend != NULL && tty->image_backend->free != NULL)
tty->image_backend->free(tty);
tty->image_data = NULL;
tty->image_backend = backend;
return (1);
}
/* Map an image grid cell to text for the selected backend. */
const struct grid_cell *
image_map_cell(struct tty *tty, struct grid *gd, u_int x, u_int y,
const struct grid_cell *gc, struct grid_cell *out, int *empty)
{
image_tty_update(tty);
if (tty->image_backend->map_cell == NULL)
return (gc);
return (tty->image_backend->map_cell(tty, gd, x, y, gc, out, empty));
}
/* Draw a clipped part of one image span. */
static void
image_draw_span(struct image_backend *backend, struct tty *tty,
struct image_span *span, u_int start, u_int end, u_int px, u_int atx,
u_int aty)
{
struct image_rect rect;
rect.image = span->placement->image;
rect.src_x = span->source_x + start - span->x;
rect.src_y = span->source_y;
rect.sx = end - start;
rect.sy = 1;
rect.dst_x = atx + start - px;
rect.dst_y = aty;
backend->draw_rect(tty, &rect);
}
/* Draw images in the visible part of one screen line. */
void
image_draw_line(struct tty *tty, struct screen *s, u_int px, u_int py,
u_int nx, u_int atx, u_int aty)
{
struct image_backend *backend;
struct image_line *line;
struct image_span *span;
u_int start, end, span_end;
image_tty_update(tty);
backend = tty->image_backend;
if (backend->draw_rect == NULL)
return;
if (py >= s->grid->sy)
return;
line = s->grid->linedata[s->grid->hsize + py].images;
if (line == NULL)
return;
end = px + nx;
TAILQ_FOREACH(span, &line->spans, line_entry) {
span_end = span->x + span->sx;
start = span->x;
if (start < px)
start = px;
if (start >= end || span_end <= px)
continue;
if (span_end > end)
span_end = end;
image_draw_span(backend, tty, span, start, span_end, px, atx,
aty);
}
}
/* Clear old image pixels before redrawing a terminal area. */
void
image_redraw_start(struct tty *tty, u_int x, u_int y, u_int w, u_int h)
{
image_tty_update(tty);
if (tty->image_backend->start_draw != NULL)
tty->image_backend->start_draw(tty, x, y, w, h);
}
/* Write pending image output to the terminal. */
void
image_draw_flush(struct tty *tty)
{
image_tty_update(tty);
if (tty->image_backend->flush != NULL)
tty->image_backend->flush(tty);
}
/* Discard image backend state after a terminal size change. */
void
image_tty_size_changed(struct tty *tty)
{
if (!image_tty_update(tty)) {
if (tty->image_backend->free != NULL)
tty->image_backend->free(tty);
}
}
/* Free image backend state for a terminal. */
void
image_tty_free(struct tty *tty)
{
if (tty->image_backend != NULL) {
if (tty->image_backend->free != NULL)
tty->image_backend->free(tty);
tty->image_backend = NULL;
tty->image_data = NULL;
}
}
/* Get the placement store for a grid. */
static struct image_store *
image_store_get(struct grid *gd)
{
if (gd->images == NULL) {
gd->images = xcalloc(1, sizeof *gd->images);
TAILQ_INIT(&gd->images->placements);
}
return (gd->images);
}
/* Get the spans for a grid line. */
static struct image_line *
image_line_get(struct grid_line *gl)
{
if (gl->images == NULL) {
gl->images = xcalloc(1, sizeof *gl->images);
TAILQ_INIT(&gl->images->spans);
}
return (gl->images);
}
/* Create a placement. Takes a reference to the image. */
static struct image_placement *
image_placement_create(struct grid *gd, struct image *im)
{
struct image_store *store = image_store_get(gd);
struct image_placement *plmt;
plmt = xcalloc(1, sizeof *plmt);
plmt->store = store;
plmt->image = im;
TAILQ_INIT(&plmt->spans);
TAILQ_INSERT_TAIL(&store->placements, plmt, entry);
image_ref(im);
return (plmt);
}
/* Free a placement after all its spans have gone. */
static void
image_placement_free(struct image_placement *plmt)
{
TAILQ_REMOVE(&plmt->store->placements, plmt, entry);
image_free(plmt->image);
free(plmt);
}
/* Attach a span to both its grid line and its placement. */
static void
image_span_add(struct image_line *line, struct image_placement *plmt, u_int x,
u_int sx, u_int src_x, u_int src_y)
{
struct image_span *span;
if (sx == 0)
return;
span = xcalloc(1, sizeof *span);
span->x = x;
span->sx = sx;
span->source_x = src_x;
span->source_y = src_y;
span->line = line;
span->placement = plmt;
TAILQ_INSERT_TAIL(&line->spans, span, line_entry);
TAILQ_INSERT_TAIL(&plmt->spans, span, placement_entry);
}
/* Detach and free one span. */
static void
image_span_free(struct image_span *span)
{
TAILQ_REMOVE(&span->line->spans, span, line_entry);
TAILQ_REMOVE(&span->placement->spans, span, placement_entry);
free(span);
}
/* Free placements with no remaining spans. */
static void
image_store_prune(struct image_store *store)
{
struct image_placement *plmt, *next;
if (store == NULL)
return;
TAILQ_FOREACH_SAFE(plmt, &store->placements, entry, next) {
if (TAILQ_EMPTY(&plmt->spans))
image_placement_free(plmt);
}
}
/* Add a source rectangle at absolute grid coordinates. */
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_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)
return;
if (sx > gd->sx - x)
sx = gd->sx - x;
if (sx > image_sx - src_x)
sx = image_sx - src_x;
if (sy > rows - y)
sy = rows - y;
if (sy > image_sy - src_y)
sy = image_sy - src_y;
plmt = image_placement_create(gd, im);
for (row = 0; row < sy; row++) {
line = image_line_get(&gd->linedata[y + row]);
image_span_add(line, plmt, x, sx, src_x, src_y + row);
}
}
/* Remove the covered part of one span. */
static void
image_span_remove(struct image_span *span, u_int x, u_int end)
{
u_int span_end = span->x + span->sx;
if (span_end <= x || span->x >= end)
return;
if (span->x < x && span_end > end) {
span->sx = x - span->x;
image_span_add(span->line, span->placement, end, span_end - end,
span->source_x + end - span->x, span->source_y);
return;
}
if (span->x < x) {
span->sx = x - span->x;
return;
}
if (span_end > end) {
span->source_x += end - span->x;
span->sx = span_end - end;
span->x = end;
return;
}
image_span_free(span);
}
/* Remove a range of cells from every span on a line. */
static void
image_line_remove(struct image_line *line, u_int x, u_int w)
{
struct image_span *span, *next;
u_int end;
if (line == NULL || w == 0)
return;
end = x + w;
if (end < x)
end = UINT_MAX;
TAILQ_FOREACH_SAFE(span, &line->spans, line_entry, next)
image_span_remove(span, x, end);
}
/* Remove a rectangle of image spans from a grid. */
void
image_grid_remove(struct grid *gd, u_int x, u_int y, u_int sx, u_int sy)
{
u_int rows = gd->hsize + gd->sy;
u_int row;
if (gd->images == NULL || sx == 0 || sy == 0 || y >= rows)
return;
if (sy > rows - y)
sy = rows - y;
for (row = y; row < y + sy; row++)
image_line_remove(gd->linedata[row].images, x, sx);
image_store_prune(gd->images);
}
/* Free all spans on a grid line. */
void
image_grid_free_line(struct grid *gd, struct grid_line *gl)
{
struct image_line *line = gl->images;
struct image_span *span, *next;
if (line == NULL)
return;
TAILQ_FOREACH_SAFE(span, &line->spans, line_entry, next)
image_span_free(span);
free(line);
gl->images = NULL;
image_store_prune(gd->images);
}
/* Free a grid's empty placement store. */
void
image_grid_free(struct grid *gd)
{
if (gd->images != NULL) {
image_store_prune(gd->images);
free(gd->images);
gd->images = NULL;
}
}
/* Move the image spans belonging to a range of text cells. */
void
image_grid_move_cells(struct grid *gd, u_int dx, u_int px, u_int py, u_int nx)
{
struct image_line *line;
struct image_span *span;
struct image_move *moves = NULL;
size_t n = 0, i;
u_int start, end, span_end;
if (gd->images == NULL ||
nx == 0 ||
px == dx ||
py >= gd->hsize + gd->sy)
return;
line = gd->linedata[py].images;
if (line == NULL)
return;
end = px + nx;
TAILQ_FOREACH(span, &line->spans, line_entry) {
span_end = span->x + span->sx;
start = (span->x > px ? span->x : px);
if (start >= end || span_end <= px)
continue;
if (span_end > end)
span_end = end;
moves = xreallocarray(moves, n + 1, sizeof *moves);
moves[n].placement = span->placement;
moves[n].x = dx + start - px;
moves[n].sx = span_end - start;
moves[n].source_x = span->source_x + start - span->x;
moves[n].source_y = span->source_y;
span->placement->recover_from = 0;
n++;
}
image_line_remove(line, px, nx);
image_line_remove(line, dx, nx);
for (i = 0; i < n; i++) {
image_span_add(line, moves[i].placement, moves[i].x,
moves[i].sx, moves[i].source_x, moves[i].source_y);
}
free(moves);
image_store_prune(gd->images);
}
/* Map one source placement to one destination placement during a copy. */
static struct image_placement *
image_copy_placement(struct grid *dst, struct image_placement *src,
struct image_copy_map **maps, u_int *nmaps)
{
struct image_placement *plmt;
struct image_copy_map *map = *maps;
u_int i, n = *nmaps;
for (i = 0; i < n; i++) {
if (map[i].source == src)
return (map[i].destination);
}
plmt = image_placement_create(dst, src->image);
plmt->x = src->x;
plmt->recover_from = src->recover_from;
map = xreallocarray(map, n + 1, sizeof *map);
map[n].source = src;
map[n].destination = plmt;
*maps = map;
*nmaps = n + 1;
return (plmt);
}
/* Duplicate image spans and their placements between grid lines. */
void
image_grid_duplicate_lines(struct grid *dst, u_int dy, struct grid *src,
u_int sy, u_int ny)
{
struct image_copy_map *maps = NULL;
struct image_line *src_line, *dst_line;
struct image_span *span;
struct image_placement *plmt;
u_int nmaps = 0, row;
for (row = 0; row < ny; row++) {
src_line = src->linedata[sy + row].images;
if (src_line == NULL)
continue;
dst_line = image_line_get(&dst->linedata[dy + row]);
TAILQ_FOREACH(span, &src_line->spans, line_entry) {
plmt = image_copy_placement(dst, span->placement, &maps,
&nmaps);
image_span_add(dst_line, plmt, span->x, span->sx,
span->source_x, span->source_y);
}
}
free(maps);
}
/* Copy clipped image spans between grid areas. */
void
image_grid_copy_area(struct grid *dst, u_int dx, u_int dy, struct grid *src,
u_int px, u_int py, u_int sx, u_int sy)
{
struct image_copy_map *maps = NULL;
struct image_placement *plmt;
struct image_line *src_line, *dst_line;
struct grid_line *gl;
struct image_span *span;
u_int nmaps = 0, row, start, end, span_end;
if (dst == src || sx == 0 || sy == 0)
return;
if (dy >= dst->hsize + dst->sy || py >= src->hsize + src->sy)
return;
if (sy > dst->hsize + dst->sy - dy)
sy = dst->hsize + dst->sy - dy;
if (sy > src->hsize + src->sy - py)
sy = src->hsize + src->sy - py;
end = px + sx;
if (end < px)
end = UINT_MAX;
for (row = 0; row < sy; row++) {
src_line = src->linedata[py + row].images;
if (src_line == NULL)
continue;
dst_line = NULL;
TAILQ_FOREACH(span, &src_line->spans, line_entry) {
span_end = span->x + span->sx;
start = (span->x > px ? span->x : px);
if (start >= end || span_end <= px)
continue;
if (span_end > end)
span_end = end;
if (dst_line == NULL) {
gl = &dst->linedata[dy + row];
dst_line = image_line_get(gl);
}
plmt = image_copy_placement(dst, span->placement, &maps,
&nmaps);
plmt->recover_from = 0;
image_span_add(dst_line, plmt, dx + start - px,
span_end - start, span->source_x + start - span->x,
span->source_y);
}
}
free(maps);
}
/* Check whether a grid line has any image spans. */
int
image_grid_line_has_images(struct grid_line *gl)
{
return (gl->images != NULL && !TAILQ_EMPTY(&gl->images->spans));
}
/* Check whether a grid area has any image spans. */
int
image_grid_area_has_images(struct grid *gd, u_int x, u_int y, u_int sx,
u_int sy)
{
struct image_line *line;
struct image_span *span;
u_int row, end;
if (gd->images == NULL)
return (0);
if (sx == 0 || sy == 0 || y >= gd->hsize + gd->sy)
return (0);
end = x + sx;
if (end < x)
end = UINT_MAX;
if (sy > gd->hsize + gd->sy - y)
sy = gd->hsize + gd->sy - y;
for (row = y; row < y + sy; row++) {
line = gd->linedata[row].images;
if (line == NULL)
continue;
TAILQ_FOREACH(span, &line->spans, line_entry) {
if (span->x < end && span->x + span->sx > x)
return (1);
}
}
return (0);
}
/* Record window damage when an area containing images changes. */
void
image_redraw_area(struct screen_write_ctx *ctx, u_int x, u_int y, u_int sx,
u_int sy)
{
struct window_pane *wp = ctx->wp;
struct grid *gd = ctx->s->grid;
if (wp == NULL)
return;
if (!image_grid_area_has_images(gd, x, gd->hsize + y, sx, sy))
return;
redraw_damage_window(wp->window, wp->xoff + x, wp->yoff + y, sx, sy);
}
/* Record window damage for all visible images. */
void
image_redraw_all(struct screen_write_ctx *ctx)
{
struct screen *s = ctx->s;
u_int sx = screen_size_x(s), sy = screen_size_y(s);
image_redraw_area(ctx, 0, 0, sx, sy);
}
/* Record window damage for images in the scroll region. */
void
image_redraw_scroll(struct screen_write_ctx *ctx)
{
struct screen *s = ctx->s;
u_int sx = screen_size_x(s), sy = s->rlower - s->rupper + 1;
image_redraw_area(ctx, 0, s->rupper, sx, sy);
}
/* Redraw images under changed cells. */
void
image_damage_screen(struct screen_write_ctx *ctx, u_int x, u_int y, u_int sx,
u_int sy)
{
struct screen *s = ctx->s;
image_redraw_area(ctx, x, y, sx, sy);
if (ctx->flags & SCREEN_WRITE_INPUT)
image_grid_remove(s->grid, x, s->grid->hsize + y, sx, sy);
}
/* Check whether an image cell has any visible pixels. */
static int
image_cell_has_visible_pixels(struct image *im, u_int x, u_int y)
{
u_int px, py, sx, sy, xx, yy;
const u_char *pixels;
size_t stride;
image_get_pixel_area(im, x, y, 1, 1, &px, &py, &sx, &sy);
if (sx == 0 || sy == 0)
return (0);
pixels = image_get_pixels(im, &stride, NULL);
for (yy = py; yy < py + sy; yy++) {
for (xx = px; xx < px + sx; xx++) {
if (pixels[yy * stride + xx * 4 + 3] != 0)
return (1);
}
}
return (0);
}
/* Return whether a new cell's pixels completely cover an old cell. */
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;
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)
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);
npixels = image_get_pixels(new, &nstride, NULL);
opixels = image_get_pixels(old, &ostride, NULL);
for (yy = 0; yy < osy; yy++) {
op = opixels + (opy + yy) * ostride + opx * 4;
if (yy < nsy) {
np = npixels + (npy + yy) * nstride + npx * 4;
}
for (xx = 0; xx < osx; xx++) {
if (op[xx * 4 + 3] == 0)
continue;
if (yy >= nsy || xx >= nsx || np[xx * 4 + 3] != 255)
return (0);
}
}
return (1);
}
/* Discard cells of older placements fully covered by a new image. */
static void
image_line_cover(struct image_line *line, struct image_placement *plmt,
u_int x, u_int src_x, u_int src_y)
{
struct image_span *span, *next;
struct image_placement *old;
TAILQ_FOREACH_SAFE(span, &line->spans, line_entry, next) {
old = span->placement;
if (old == plmt || x < span->x || x >= span->x + span->sx)
continue;
if (!image_cell_covers(plmt->image, src_x, src_y,
old->image, span->source_x + x - span->x,
span->source_y))
continue;
image_span_remove(span, x, x + 1);
}
}
/* Attach the visible runs from one image row. */
static void
image_add_row(struct image_line *line, struct image_placement *plmt,
u_int cx, u_int src_y, u_int sx)
{
struct image *im = plmt->image;
u_int x, i, run;
for (x = 0; x < sx; x += run) {
if (!image_cell_has_visible_pixels(im, x, src_y)) {
run = 1;
continue;
}
for (run = 1; x + run < sx; run++) {
if (!image_cell_has_visible_pixels(im, x + run, src_y))
break;
}
for (i = 0; i < run; i++)
image_line_cover(line, plmt, cx + x + i, x + i, src_y);
image_span_add(line, plmt, cx + x, run, x, src_y);
}
}
/* Add newly visible columns from one image row. */
static void
image_extend_row(struct image_line *line, struct image_placement *plmt,
u_int cx, u_int src_y, u_int from, u_int to)
{
struct image *im = plmt->image;
u_int x, run;
for (x = from; x < to; x += run) {
if (!image_cell_has_visible_pixels(im, x, src_y)) {
run = 1;
continue;
}
for (run = 1; x + run < to; run++) {
if (!image_cell_has_visible_pixels(im, x + run, src_y))
break;
}
image_span_add(line, plmt, cx + x, run, x, src_y);
}
}
/* Extend image rows when grid is resized. */
void
image_grid_resize_width(struct grid *gd, u_int new_sx)
{
struct image_placement *plmt;
struct image_span *span;
struct image_recover_row *rows;
u_int image_sx, image_sy, from, to;
u_int nrows, i;
if (gd->images == NULL)
return;
TAILQ_FOREACH(plmt, &gd->images->placements, entry) {
from = plmt->recover_from;
if (from == 0 || plmt->x >= new_sx)
continue;
image_get_size_in_cells(plmt->image, &image_sx, &image_sy);
to = new_sx - plmt->x;
if (to > image_sx)
to = image_sx;
if (to <= from)
continue;
rows = NULL;
nrows = 0;
TAILQ_FOREACH(span, &plmt->spans, placement_entry) {
for (i = 0; i < nrows; i++) {
if (rows[i].line == span->line)
break;
}
if (i != nrows)
continue;
rows = xreallocarray(rows, nrows + 1, sizeof *rows);
rows[nrows].line = span->line;
rows[nrows].source_y = span->source_y;
nrows++;
}
for (i = 0; i < nrows; i++) {
image_extend_row(rows[i].line, plmt, plmt->x,
rows[i].source_y, from, to);
}
free(rows);
plmt->recover_from = (to == image_sx ? 0 : to);
}
}
/* Place a SIXEL image at the cursor. */
void
image_write(struct screen_write_ctx *ctx, struct image *im, u_int bg)
{
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 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)
return;
/* Scroll images that move the cursor. */
if (screen_size_y(s) - cy <= sy) {
lines = sy - (screen_size_y(s) - cy) + 1;
region_height = s->rlower - s->rupper + 1;
remaining = lines;
while (remaining > 0) {
if (remaining < region_height)
n = remaining;
else
n = region_height;
screen_write_scrollup(ctx, n, bg);
remaining -= n;
}
if (lines > cy) {
origin_y = lines - cy;
screen_write_cursormove(ctx, -1, 0, 0);
} else {
origin_y = 0;
screen_write_cursormove(ctx, -1, cy - lines, 0);
}
cy = s->cy;
sy -= origin_y;
}
plmt = image_placement_create(gd, im);
if (sx < image_sx) {
plmt->x = cx;
plmt->recover_from = sx;
}
/* Attach scrolled image rows to the available history lines. */
h_origin_y = origin_y;
if (h_origin_y > gd->hsize)
h_origin_y = gd->hsize;
for (y = 0; y < h_origin_y; y++) {
gl = &gd->linedata[gd->hsize - h_origin_y + y];
line = image_line_get(gl);
image_add_row(line, plmt, cx, origin_y - h_origin_y + y, sx);
}
/* Attach visible image rows. */
for (y = 0; y < sy; y++) {
line = image_line_get(&gd->linedata[gd->hsize + cy + y]);
image_add_row(line, plmt, cx, origin_y + y, sx);
}
/* Schedule a redraw and advance the cursor past the image. */
image_store_prune(gd->images);
image_redraw_area(ctx, cx, cy, sx, sy);
screen_write_cursormove(ctx, 0, cy + sy, 0);
}
/* Find the cell within an image at a grid position. */
int
image_grid_get_at(struct grid *gd, u_int x, u_int y, struct image **im,
u_int *src_x, u_int *src_y)
{
struct image_line *line;
struct image_span *span, *found = NULL;
if (y >= gd->hsize + gd->sy)
return (0);
line = gd->linedata[y].images;
if (line == NULL)
return (0);
TAILQ_FOREACH(span, &line->spans, line_entry) {
if (x >= span->x && x < span->x + span->sx)
found = span;
}
if (found == NULL)
return (-1);
*im = found->placement->image;
*src_x = found->source_x + x - found->x;
*src_y = found->source_y;
return (1);
}