This is to optimize sixel_add(). It already checks ‘pos’, to see if it
needs to resize the image, and if the resize fails (either due to
allocation failures, or because we’ve reached the maximum width), we
bail out.
We need to do the same thing for height. But, we don’t want to add
another check to sixel_add() since its’s performance critical.
By setting ‘col’ outside the image boundaries when ‘row’ reaches the
maximum image height, we can “re-use” the existing code in
sixel_add().
This reverts commit 5a9d70da837c20a9641d6cbadccc962a8e5a4283.
This broke jexer.
Comparing with what XTerm does, we can see that it updates its image
height for *each* pixel in *each* sixel. I.e. empty pixels at the
bottom of a sixel will not increase the image size.
Foot currently bumps the image height 6 pixels at a time, i.e. always
a whole pixel.
Given this, always rounding up the height to the nearest multiple of
6 (say, for example, when responding to a DECGRA command), is wrong.
Now, we use the image size specified in DECGRA as is, while still
allowing the image to grow beyond that if necessary.
What’s left to do, if we want to behave *exactly* like XTerm, is stop
growing the image with 6 pixels at a time when dynamically adjusting
the image size.
By storing the current row’s byte offset into the backing image in the
terminal struct.
This replaces the ‘imul’ with a load, which can potentially be
slow. But, this data should already be in the cache.
When we allocate the backing buffer, the number of allocated rows is a
multiple of 6.
When persisting the image, make sure its height does not exceed the
current maximum height.
Foot has, up until now, used a fixed image size when the application
used DECGRA (Raster Attributes) to “configure” the image size.
This was based on a misunderstanding, that this was how you emitted
sixels where the height was *not* a multiple of 6.
This isn’t the case. The VT340 documentation is actually pretty clear
about this:
Ph and Pv do not limit the size of the image defined by the sixel
data. However, Ph and Pv let you omit background sixel data from the
image definition and still have a color background. They also
provide a concise way for the application or terminal to encode the
size of an image.
This is also how XTerm behaves. Test image:
\EPq
"1;1;1;1
#0;2;0;0;0#1;2;100;100;0#2;2;0;100;0
#1~~@@vv@@~~@@~~$
#2??}}GG}}??}}??-
#1!14@
\E\
This uses DECGRA to set the image size to 1x1. The sixel however
is *not* clipped to 1x1, but is resized to 14x12
When sixel scrolling is disabled (private mode 80 is off), and scroll
margins have been set, XTerm seems to ignore the top margin (sixel
still begins at (0,0)), but does not go past the bottom margin.
This patch implements the same behavior in foot.
When enabled (the default), sixels behave much like normal output; the
start where the cursor is, and the cursor moves with the
sixel. I.e. after emitting a sixel the cursor is left after the image;
either to the right, if private mode 8452 is enabled, or otherwise on
the next line. Terminal content is scrolled up if the sixel is larger
than the screen.
When disabled, sixels *always* start at (0,0), the cursor never moves,
and the terminal content never scrolls.
In other words, the ‘disabled’ mode is a much simpler mode.
All we need to do to support both modes is re-write the sixel-emitting
loop to:
* break early if we’re “out of rows”, i.e. we’ve reached the bottom of
the screen.
* not linefeed, or move the cursor when scrolling is disabled
This patch also fixes a bug in the (new) implementation of private
mode 8452.
When emitting a sixel, we may break it up into smaller pieces, to
ensure a single sixel (as tracked internally) does not cross the
scrollback wrap-around.
The code that checked if we should do a linefeed or not, would skip
the linefeed on the last row of *each* such sixel piece. The correct
thing to do is to skip it only on the last row of the *last* piece.
I chose not to fix this bug in a separate patch since doing so would
have meant re-writing it again when implementing private mode 80.
When disabled (the default), the cursor is positioned on a new line
after emitting a sixel image.
When enabled, the cursor is positioned to the right of the sixel
image.
Closes#363
When enabled (the default), sixels use private color registers. That
is, the color palette from the last sixel is *not* re-used.
When disabled, sixels share (i.e. re-use) the same color palette.
Closes#362
* New function: rgb_to_hsl()
* New function: hsl_to_rgb()
* Replace XTerm’s hls_to_rgb() with our own, hsl_to_rgb().
* Ensure hue/lum/sat values are within range before calling
hsl_to_rgb()
Note that sixels’ use the following primary hues:
* blue: 0°
* red: 120
* green: 240°
While “standard” HSL uses:
* red: 0°
* green: 120°
* blue: 240°
Thus, we need to adjust the sixel’s hue value before calling
hsl_to_rgb().
Up until now, we’ve always re-rendered the entire image (the part of
it that is visible at least), *every* time we render a frame.
This is not really needed. In many cases, the cells covered by the
image hasn’t been touched.
Rewrite the sixel rendering code to only render the part of the sixel
image that covers dirty cells.
This is done on a per-row basis. I.e. Each *row* of the image that
covers at least one dirty cell is re-rendered. For this to work, we
now also dirty all cells covered by the image when we emit the image.
Finally, for this to work, the sixels need to be rendered *before* we
do the normal grid render pass (since that will clear all dirty bits).
The state after this function is an intermediate state and isn’t
necessarily valid.
This sixels needs to be ‘reflowed’ to ensure a valid state. This is
something that should be done by the caller after the text grid has
been reflowed and the sixel coordinates have been re-mapped to the new
grid.
TODO: can/should we update the sixel cols/rows in sixel_reflow()
instead?
This function loops the list of sixels, and discards those that would
be scrolled out if the grid offset is moved forward by the specified
number of rows.
The criteria is when the rebased row value is less than the number of
rows to scroll.
A rebased row number is a zero-based number starting at the beginning
of the scrollback. Thus, when scrolling 5 rows, rows with a rebased
row number between 0-4 will be scrolled out.
For performance reasons, we used to break out of the loop as soon as a
row number *larger* than the scroll count.
This however does not work. The sixels are sorted by their *end*
row. While this works in most cases (think images outputted in the
shell in the normal screen), it doesn’t always.
In the alt screen, where applications can print images just about
anywhere, it is possible to have *any* start row number anywhere in
the sixel list. Just as long as their *end* row numbers are sorted.
For example, a huuuge sixel that covers the entire scrollback. This
sixel will naturally be first in the list (and thus sixel_scroll_up()
will visit it *last*, since it iterates the list in reverse), but
should still be destroyed when scrolling.