mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-02-05 04:06:08 -05:00
sixel: add support for overlapping sixels
Writing a sixel on top of an already existing sixel currently has the following limitations in foot: * The parts of the first sixel that is covered by the new sixel are removed, completely. Even if the new sixel has transparent areas. I.e. writing a transparent sixel on top of another sixel *replaces* the first sixel with the new sixel, instead of layering them on top of each other. * The second sixel erases the first sixel cell-wise. That is, a sixel whose size isn’t a multiple of the cell dimensions will leave unsightly holes in the first sixel. This patch takes care of both issues. The first one is actually the easiest one: all we need to do is calculate the intersection, and blend the two images. To keep things relatively simple, we use the pixman image from the *new* image, and use the ‘OVER_REVERSE’ operation to blend the new image over the old one. That is, the old image is still split into four tiles (top, left, right, bottom), just like before. But instead of throwing away the fifth middle tile, we blend it with the new image. As an optimization, this is only done if the new image has transparency (P1=1). The second problem is solved by detecting when we’re erasing an area from the second image that is larger than the new image. In this case, we enlarge the new image, and copy the old image into the new one. Finally, when we enlarge the new image, there may be areas in the new image that is *not* covered by the old image. These areas are made transparent. The end result is: * Each cell is covered by at *most* 1 sixel image. I.e. the total numbers of sixels are finite. This is important for the ‘mpv --vo=sixel’ use case - we don’t want to end up with thousands of sixels layered on top of each other. * Writing an opaque sixel on top of another sixel has _almost_ zero performance impact. Especially if the two sixels have the same size, so that we don’t have to resize the new image. Again, important for the ‘mpv --vo=sixel’ use case. Closes #562
This commit is contained in:
parent
ed081f5f3c
commit
6d336fcadd
2 changed files with 225 additions and 17 deletions
|
|
@ -103,6 +103,9 @@
|
|||
colors remain unchanged.
|
||||
* Tabs (`\t`) are now preserved when the window is resized, and when
|
||||
copying text (https://codeberg.org/dnkl/foot/issues/508).
|
||||
* Writing a sixel on top of another sixel no longer erases the first
|
||||
sixel, but the two are instead blended
|
||||
(https://codeberg.org/dnkl/foot/issues/562).
|
||||
|
||||
|
||||
### Deprecated
|
||||
|
|
|
|||
239
sixel.c
239
sixel.c
|
|
@ -359,9 +359,191 @@ sixel_scroll_down(struct terminal *term, int rows)
|
|||
verify_sixels(term);
|
||||
}
|
||||
|
||||
static void
|
||||
blend_new_image_over_old(const struct terminal *term,
|
||||
const struct sixel *six, pixman_region32_t *six_rect,
|
||||
int row, int col, pixman_image_t **pix, bool *opaque)
|
||||
{
|
||||
xassert(pix != NULL);
|
||||
xassert(opaque != NULL);
|
||||
|
||||
const int six_ofs_x = six->pos.col * term->cell_width;
|
||||
const int six_ofs_y = six->pos.row * term->cell_height;
|
||||
const int img_ofs_x = col * term->cell_width;
|
||||
const int img_ofs_y = row * term->cell_height;
|
||||
const int img_width = pixman_image_get_width(*pix);
|
||||
const int img_height = pixman_image_get_height(*pix);
|
||||
|
||||
pixman_region32_t pix_rect;
|
||||
pixman_region32_init_rect(
|
||||
&pix_rect, img_ofs_x, img_ofs_y, img_width, img_height);
|
||||
|
||||
/* Blend the intersection between the old and new images */
|
||||
pixman_region32_t intersection;
|
||||
pixman_region32_init(&intersection);
|
||||
pixman_region32_intersect(&intersection, six_rect, &pix_rect);
|
||||
|
||||
int n_rects = -1;
|
||||
pixman_box32_t *boxes = pixman_region32_rectangles(
|
||||
&intersection, &n_rects);
|
||||
|
||||
if (n_rects == 0)
|
||||
goto out;
|
||||
|
||||
xassert(n_rects == 1);
|
||||
pixman_box32_t *box = &boxes[0];
|
||||
|
||||
if (!*opaque) {
|
||||
/*
|
||||
* New image is transparent - blend on top of the old
|
||||
* sixel image.
|
||||
*/
|
||||
pixman_image_composite32(
|
||||
PIXMAN_OP_OVER_REVERSE,
|
||||
six->pix, NULL, *pix,
|
||||
box->x1 - six_ofs_x, box->y1 - six_ofs_y,
|
||||
0, 0,
|
||||
box->x1 - img_ofs_x, box->y1 - img_ofs_y,
|
||||
box->x2 - box->x1, box->y2 - box->y1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Since the old image is split into sub-tiles on a
|
||||
* per-row basis, we need to enlarge the new image and
|
||||
* copy the old image if the old image extends beyond the
|
||||
* new image.
|
||||
*
|
||||
* The "bounding" coordinates are either the edges of the
|
||||
* old image, or the next cell boundary, whichever comes
|
||||
* first.
|
||||
*/
|
||||
int bounding_x = six_ofs_x + six->width > img_ofs_x + img_width
|
||||
? min(
|
||||
six_ofs_x + six->width,
|
||||
(box->x2 + term->cell_width - 1) / term->cell_width * term->cell_width)
|
||||
: box->x2;
|
||||
int bounding_y = six_ofs_y + six->height > img_ofs_y + img_height
|
||||
? min(
|
||||
six_ofs_y + six->height,
|
||||
(box->y2 + term->cell_height - 1) / term->cell_height * term->cell_height)
|
||||
: box->y2;
|
||||
|
||||
/* The required size of the new image */
|
||||
const int required_width = bounding_x - img_ofs_x;
|
||||
const int required_height = bounding_y - img_ofs_y;
|
||||
|
||||
const int new_width = max(img_width, required_width);
|
||||
const int new_height = max(img_height, required_height);
|
||||
|
||||
if (new_width <= img_width && new_height <= img_height)
|
||||
goto out;
|
||||
|
||||
//LOG_INFO("enlarging: %dx%d -> %dx%d", img_width, img_height, new_width, new_height);
|
||||
|
||||
if (!six->opaque) {
|
||||
/* Transparency is viral */
|
||||
*opaque = false;
|
||||
}
|
||||
|
||||
/* Create a new pixmap */
|
||||
int stride = new_width * sizeof(uint32_t);
|
||||
uint32_t *new_data = xmalloc(stride * new_height);
|
||||
pixman_image_t *pix2 = pixman_image_create_bits_no_clear(
|
||||
PIXMAN_a8r8g8b8, new_width, new_height, new_data, stride);
|
||||
|
||||
#if defined(_DEBUG)
|
||||
/* Fill new image with an easy-to-recognize color (green) */
|
||||
for (size_t i = 0; i < new_width * new_height; i++)
|
||||
new_data[i] = 0xff00ff00;
|
||||
#endif
|
||||
|
||||
/* Copy the new image, from its old pixmap, to the new pixmap */
|
||||
pixman_image_composite32(
|
||||
PIXMAN_OP_SRC,
|
||||
*pix, NULL, pix2, 0, 0, 0, 0, 0, 0, img_width, img_height);
|
||||
|
||||
/* Copy the bottom tile of the old sixel image into the new pixmap */
|
||||
pixman_image_composite32(
|
||||
PIXMAN_OP_SRC,
|
||||
six->pix, NULL, pix2,
|
||||
box->x1 - six_ofs_x, box->y2 - six_ofs_y,
|
||||
0, 0,
|
||||
box->x1 - img_ofs_x, box->y2 - img_ofs_y,
|
||||
bounding_x - box->x1, bounding_y - box->y2);
|
||||
|
||||
/* Copy the right tile of the old sixel image into the new pixmap */
|
||||
pixman_image_composite32(
|
||||
PIXMAN_OP_SRC,
|
||||
six->pix, NULL, pix2,
|
||||
box->x2 - six_ofs_x, box->y1 - six_ofs_y,
|
||||
0, 0,
|
||||
box->x2 - img_ofs_x, box->y1 - img_ofs_y,
|
||||
bounding_x - box->x2, bounding_y - box->y1);
|
||||
|
||||
/*
|
||||
* Ensure the newly allocated area is initialized.
|
||||
*
|
||||
* Some of it, or all, will have been initialized above, by the
|
||||
* bottom and right tiles from the old sixel image. However, there
|
||||
* may be areas in the new image that isn't covered by the old
|
||||
* image. These areas need to be made transparent.
|
||||
*/
|
||||
pixman_region32_t uninitialized;
|
||||
pixman_region32_init_rects(
|
||||
&uninitialized,
|
||||
(const pixman_box32_t []){
|
||||
/* Extended image area on the right side */
|
||||
{img_ofs_x + img_width, img_ofs_y, img_ofs_x + new_width, img_ofs_y + new_height},
|
||||
|
||||
/* Bottom */
|
||||
{img_ofs_x, img_ofs_y + img_height, img_ofs_x + new_width, img_ofs_y + new_height}},
|
||||
2);
|
||||
|
||||
/* Subtract the old sixel image, since the area(s) covered by the
|
||||
* old image has already been copied, and *must* not be
|
||||
* overwritten */
|
||||
pixman_region32_t diff;
|
||||
pixman_region32_init(&diff);
|
||||
pixman_region32_subtract(&diff, &uninitialized, six_rect);
|
||||
|
||||
if (pixman_region32_not_empty(&diff)) {
|
||||
pixman_image_t *src =
|
||||
pixman_image_create_solid_fill(&(pixman_color_t){0});
|
||||
|
||||
int count = -1;
|
||||
pixman_box32_t *rects = pixman_region32_rectangles(&diff, &count);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
pixman_image_composite32(
|
||||
PIXMAN_OP_SRC,
|
||||
src, NULL, pix2,
|
||||
0, 0, 0, 0,
|
||||
rects[i].x1 - img_ofs_x, rects[i].y1 - img_ofs_y,
|
||||
rects[i].x2 - rects[i].x1,
|
||||
rects[i].y2 - rects[i].y1);
|
||||
}
|
||||
|
||||
pixman_image_unref(src);
|
||||
*opaque = false;
|
||||
}
|
||||
|
||||
pixman_region32_fini(&diff);
|
||||
pixman_region32_fini(&uninitialized);
|
||||
|
||||
/* Use the new pixmap in place of the old one */
|
||||
free(pixman_image_get_data(*pix));
|
||||
pixman_image_unref(*pix);
|
||||
*pix = pix2;
|
||||
|
||||
out:
|
||||
pixman_region32_fini(&intersection);
|
||||
pixman_region32_fini(&pix_rect);
|
||||
}
|
||||
|
||||
static void
|
||||
sixel_overwrite(struct terminal *term, struct sixel *six,
|
||||
int row, int col, int height, int width)
|
||||
int row, int col, int height, int width,
|
||||
pixman_image_t **pix, bool *opaque)
|
||||
{
|
||||
pixman_region32_t six_rect;
|
||||
pixman_region32_init_rect(
|
||||
|
|
@ -376,13 +558,17 @@ sixel_overwrite(struct terminal *term, struct sixel *six,
|
|||
width * term->cell_width, height * term->cell_height);
|
||||
|
||||
#if defined(_DEBUG)
|
||||
pixman_region32_t intersection;
|
||||
pixman_region32_init(&intersection);
|
||||
pixman_region32_intersect(&intersection, &six_rect, &overwrite_rect);
|
||||
xassert(pixman_region32_not_empty(&intersection));
|
||||
pixman_region32_fini(&intersection);
|
||||
pixman_region32_t cell_intersection;
|
||||
pixman_region32_init(&cell_intersection);
|
||||
pixman_region32_intersect(&cell_intersection, &six_rect, &overwrite_rect);
|
||||
xassert(pixman_region32_not_empty(&cell_intersection));
|
||||
pixman_region32_fini(&cell_intersection);
|
||||
#endif
|
||||
|
||||
if (pix != NULL)
|
||||
blend_new_image_over_old(term, six, &six_rect, row, col, pix, opaque);
|
||||
|
||||
|
||||
pixman_region32_t diff;
|
||||
pixman_region32_init(&diff);
|
||||
pixman_region32_subtract(&diff, &six_rect, &overwrite_rect);
|
||||
|
|
@ -455,7 +641,8 @@ sixel_overwrite(struct terminal *term, struct sixel *six,
|
|||
/* Row numbers are absolute */
|
||||
static void
|
||||
_sixel_overwrite_by_rectangle(
|
||||
struct terminal *term, int row, int col, int height, int width)
|
||||
struct terminal *term, int row, int col, int height, int width,
|
||||
pixman_image_t **pix, bool *opaque)
|
||||
{
|
||||
verify_sixels(term);
|
||||
|
||||
|
|
@ -519,7 +706,8 @@ _sixel_overwrite_by_rectangle(
|
|||
struct sixel to_be_erased = *six;
|
||||
tll_remove(term->grid->sixel_images, it);
|
||||
|
||||
sixel_overwrite(term, &to_be_erased, start, col, height, width);
|
||||
sixel_overwrite(term, &to_be_erased, start, col, height, width,
|
||||
pix, opaque);
|
||||
sixel_erase(term, &to_be_erased);
|
||||
} else
|
||||
xassert(!collides);
|
||||
|
|
@ -551,10 +739,10 @@ sixel_overwrite_by_rectangle(
|
|||
if (wraps) {
|
||||
int rows_to_wrap_around = term->grid->num_rows - start;
|
||||
xassert(height - rows_to_wrap_around > 0);
|
||||
_sixel_overwrite_by_rectangle(term, start, col, rows_to_wrap_around, width);
|
||||
_sixel_overwrite_by_rectangle(term, 0, col, height - rows_to_wrap_around, width);
|
||||
_sixel_overwrite_by_rectangle(term, start, col, rows_to_wrap_around, width, NULL, NULL);
|
||||
_sixel_overwrite_by_rectangle(term, 0, col, height - rows_to_wrap_around, width, NULL, NULL);
|
||||
} else
|
||||
_sixel_overwrite_by_rectangle(term, start, col, height, width);
|
||||
_sixel_overwrite_by_rectangle(term, start, col, height, width, NULL, NULL);
|
||||
|
||||
term_update_ascii_printer(term);
|
||||
}
|
||||
|
|
@ -605,7 +793,7 @@ sixel_overwrite_by_row(struct terminal *term, int _row, int col, int width)
|
|||
struct sixel to_be_erased = *six;
|
||||
tll_remove(term->grid->sixel_images, it);
|
||||
|
||||
sixel_overwrite(term, &to_be_erased, row, col, 1, width);
|
||||
sixel_overwrite(term, &to_be_erased, row, col, 1, width, NULL, NULL);
|
||||
sixel_erase(term, &to_be_erased);
|
||||
}
|
||||
}
|
||||
|
|
@ -707,7 +895,17 @@ sixel_reflow(struct terminal *term)
|
|||
/* Sixels that didn’t overlap may now do so, which isn’t
|
||||
* allowed of course */
|
||||
_sixel_overwrite_by_rectangle(
|
||||
term, six->pos.row, six->pos.col, six->rows, six->cols);
|
||||
term, six->pos.row, six->pos.col, six->rows, six->cols,
|
||||
&it->item.pix, &it->item.opaque);
|
||||
|
||||
if (it->item.data != pixman_image_get_data(it->item.pix)) {
|
||||
it->item.data = pixman_image_get_data(it->item.pix);
|
||||
it->item.width = pixman_image_get_width(it->item.pix);
|
||||
it->item.height = pixman_image_get_height(it->item.pix);
|
||||
it->item.cols = (it->item.width + term->cell_width - 1) / term->cell_width;
|
||||
it->item.rows = (it->item.height + term->cell_height - 1) / term->cell_height;
|
||||
}
|
||||
|
||||
sixel_insert(term, it->item);
|
||||
}
|
||||
|
||||
|
|
@ -822,9 +1020,7 @@ sixel_unhook(struct terminal *term)
|
|||
image.pos.row, image.pos.row + image.rows);
|
||||
|
||||
image.pix = pixman_image_create_bits_no_clear(
|
||||
PIXMAN_a8r8g8b8,
|
||||
image.width, image.height,
|
||||
img_data, stride);
|
||||
PIXMAN_a8r8g8b8, image.width, image.height, img_data, stride);
|
||||
|
||||
pixel_row_idx += height;
|
||||
pixel_rows_left -= height;
|
||||
|
|
@ -865,7 +1061,16 @@ sixel_unhook(struct terminal *term)
|
|||
}
|
||||
|
||||
_sixel_overwrite_by_rectangle(
|
||||
term, image.pos.row, image.pos.col, image.rows, image.cols);
|
||||
term, image.pos.row, image.pos.col, image.rows, image.cols,
|
||||
&image.pix, &image.opaque);
|
||||
|
||||
if (image.data != pixman_image_get_data(image.pix)) {
|
||||
image.data = pixman_image_get_data(image.pix);
|
||||
image.width = pixman_image_get_width(image.pix);
|
||||
image.height = pixman_image_get_height(image.pix);
|
||||
image.cols = (image.width + term->cell_width - 1) / term->cell_width;
|
||||
image.rows = (image.height + term->cell_height - 1) / term->cell_height;
|
||||
}
|
||||
|
||||
sixel_insert(term, image);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue