Before this patch, when the cell dimensions changed (i.e. when the
font size changes), sixel images were either removed (the new cell
dimensions are smaller than the old), or simply kept at their original
size (new cell dimensions are larger).
With this patch, sixels are instead resized. This means a
sixel *always* occupies the same number of rows and columns,
regardless of how much the font size is changed.
This is done by maintaining two sets of image data and pixman images,
as well as their dimensions. These two sets are the new ‘original’ and
‘scaled’ members of the sixel struct.
The "top-level" pixman image pointer, and the ‘width’ and ‘height’
members either point to the "original", or the "scaled" version.
They are invalidated as soon as the cell dimensions change. They, and
the ‘scaled’ image is updated on-demand (when we need to render a
sixel).
Note that the ‘scaled’ image is always NULL when the current cell
dimensions matches the ones used when emitting the sixel (to save
run-time memory).
Closes#1383
Images with an aspect ratio of 1:1 are by far the most common (though
not the default).
It makes a lot of sense, performance wise, to special case
them.
Specifically, the sixel_add() function benefits greatly from this, as
it is the inner most, most heavily executed function when parsing a
sixel image.
sixel_add_many() also benefits, since allows us to drop a
multiplication. Since sixel_add_many() always called first (no other
call sites call sixel_add() directly), this has a noticeable effect on
performance.
Another thing that helps (though not as much), and not specifically
with AR 1:1 images, is special casing DECGRI a bit.
Up until now, it simply updated the current sixel parameter value. The
problem is that the default parameter value is 0. But, a value of 0
should be treated as 1. By adding a special ‘repeat_count’ variable to
the sixel struct, we can initialize it to ‘1’ when we see DECGRI, and
then simply overwrite it as the parameter value gets updated. This
allows us to drop an if..else when emitting the sixel.
This function reflows all sixels in the specified grid.
The pre-existing sixel_reflow() function is a shortcut for
sixel_reflow_grid(term, &term->normal)
sixel_reflow_grid(term, &term->alt);
When P2=1, empty pixels are transparent.
This patch also changes the behavior of P2=0|2, from setting empty
pixels to the default background color, to instead use the *current*
background color.
To implement this, a couple of changes are needed:
* Sixel pixels always use alpha=1.0, except for *empty* cells when
P2=1 (i.e. transparent pixels).
* The renderer draws sixels with the OVER operator, instead of the SRC
operator.
* The renderer *must* now render the cells beneath the sixel. As an
optimization, this is only done for sixels where P2=1. I.e. for
fully opaque sixels, there’s no need to render the cells beneath.
The sixel renderer isn’t yet hooked into the multi-threaded
renderer. This means *rows* (not just the cells) beneath
maybe-transparent sixels are rendered single-threaded.
Closes#391.
Move sixel reflow from grid_reflow() to sixel_reflow(). This lets us
use internal sixel functions to update the sixels.
Note that grid_reflow() still needs to remap the sixelss coordinates.
The logic that breaks out of sixel loops does not work for rows that
has already wrapped around.
Thus, we need to destroy sixels that are about to be scrolled
out *before* we actually scroll.
Since this is the *only* time we destroy sixels (instead of
overwriting it), rename the sixel functions. And, since they now do a
very specific thing, they can be greatly simplified (and thus faster).
If changing the font size causes the cell size to decrease, either
horizontally or vertically (or both), then delete all sixels since the
grid space they allocated no longer is enough to hold the images.
Handle these on a higher abstraction level. The low level functions
that detect sixel intersections now assume the specified rectangle (or
line region) does *not* cross the wrap-around.
This is ensured by detecting a wrap-around region before hand, and
splitting it up into two, non wrapping regions.
This function splits a sixel image into up to four pieces. The four
pieces correspond to the relative complement (set difference) of the
sixel image and the specified rectangle.
Use this function when (possibly) overwriting existing sixel images
when generating a new one, i.e. in sixel_unhook().
Instead of completely erasing a sixel image when it is being
"overwritten" (text is printed somewhere within the image, or another
sixel image is emitted within the first image), split it up into up to
four pieces: 'above', 'below', 'to-the-left' and 'to-the-right'.
This is currently very un-optimized, but seems to produce correct
results.
A client can re-use the palette between images. Resetting the palette
breaks this.
Now we initialize the palette on demand, and resets it when the
palette size is changed (by the client).
This implements basic parsing of sixel data. Lots of limitations and
temporary solutions as this is still work-in-progress:
* Maximum image size hardcoded to 800x800
* No HLS color format support
* Image is always rendered at 0x0 in the terminal