Commit graph

164 commits

Author SHA1 Message Date
Daniel Eklöf
58bbbb0a31
sixel: revert to default state when an invalid DECGRI character is received 2022-02-04 18:14:25 +01:00
Daniel Eklöf
9e8d553a21
sixel: DECGRI (repeat) with a count of 0 should emit a single sixel 2022-02-04 18:14:24 +01:00
Daniel Eklöf
9150507209
sixel: resize: truncate instead of failing, when new size exceeds max size
If the size we’re trying to set exceeds the configured max size,
truncate instead of failing.
2022-02-04 18:14:24 +01:00
Daniel Eklöf
b218b8cfb0
sixel: VT state’s bg color may now be an index, rather than an actual color value 2021-12-26 12:37:48 +01:00
Daniel Eklöf
d46af6bd7a
term: track cell color source
Each cell now tracks it’s current color source:

* default fg/bg
* base16 fg/bg (maps to *both* the regular and bright colors)
* base256 fg/bg
* RGB

Note that we don’t have enough bits to separate the regular from the
bright colors. These _shouldn’t_ be the same, so we ought to be
fine...
2021-11-20 16:46:38 +01:00
Daniel Eklöf
b1a4d43845
sixel: call decsixel() directly, instead of going through sixel_put() 2021-09-05 11:09:45 +02:00
Daniel Eklöf
47c32d5913
sixel: avoid looking up color from palette for each sixel
Instead, do the palette lookup when we receive the DECGCI (i.e. when
the palette entry is selected), and store the actual color value in
our sixel struct.
2021-09-05 11:08:13 +02:00
Daniel Eklöf
f9642e9597
sixel: calculate default bg once, in init
We have all information we need to calculate the default background
color in sixel_init():

* Whether the image have transparency or not
* The current ANSI background color
2021-09-05 10:27:13 +02:00
Daniel Eklöf
f65e062ce4
sixel: destroy(): don’t unref a NULL pixman image 2021-07-18 16:30:42 +02:00
Daniel Eklöf
a52d867947
sixel: fix crash when splitting up an image across the scrollback
If an image was split up across the scrollback, and the first image
chunk was resized due to it being blended with an already existing
sixel, we crashed.

The reason being the second chunk, emitted *after* the scrollback,
tried to memcpy() image data from a now-free:d image buffer.

The fix is pretty simple: create copies for *all* chunks when an image
has to be split up across the scrollback. Previously, the first chunk
re-used the source image buffer.

Closes #608
2021-06-24 17:08:33 +02:00
Daniel Eklöf
6d336fcadd
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
2021-06-09 10:00:19 +02:00
Daniel Eklöf
e0f1a4ae33
sixel: don’t *ever* shrink image below its SRA size
If the image was accompanied with a “Set Raster Attributes” (SRA)
command, make sure we *never* shrink the image below the size
specified in the SRA.

Images are normally shrunk when their bottom rows are fully
transparent. This enables sixels that aren’t a multiple of 6 to be
emitted, without also emitting an SRA command.

But if there *is* an SRA command, obey it.

Verified against XTerm-367
2021-05-16 11:00:32 +02:00
Daniel Eklöf
ba451af5c7
sixel: don’t emit sixels that will end up covering more than the entire scrollback
This limit can be reached by the sixel alone, or the sixel + the final
newline.

Closes #494
2021-05-08 20:28:16 +02:00
Daniel Eklöf
9bd14c0fd2
sixel: fix assertion: image is allowed to cover the entire scrollback 2021-05-08 20:28:16 +02:00
Daniel Eklöf
5f8a8951c7
sixel: unhook: tighten up the condition needed for us to free image data pre-maturely 2021-04-07 19:08:29 +02:00
Daniel Eklöf
f9a730f33b
sixel: sixel_fini(): free image data
Normally, this data is always free:d indirectly, when the sixel image
that took over ownership is destroyed. But let’s play it safe.
2021-04-07 19:07:43 +02:00
Daniel Eklöf
00083125a1
sixel: fix double free caused by bad free() in sixel_colors_set()
sixel_color_set() is called when the number of (sixel) color registers
is changed.

It frees the current palette, and changes the “palette size” variable.

Originally, we only had a single palette. This is the one free:d by
sixel_color_set().

Later, we added support for private vs. shared palettes. With this
change, we now have one palette that is “never” free:d (the shared
one), and a private palette that is always free:d after a sixel has
been emitted.

‘sixel.palette’ is a pointer to the palette currently in use, and
should only be accessed **while emitting a sixel**.

This is the pointer sixel_color_set() free:d. So for example, if
‘sixel.palette’ pointed to the shared palette, we’d free the shared
palette. But, we didn’t reset ‘sixel.shared_palette’, causing a double
free later on.

Closes #427
2021-03-30 11:08:03 +02:00
Daniel Eklöf
19289bad5e
sixel: free backing buffer if final image size is zero 2021-03-28 13:22:10 +02:00
Daniel Eklöf
3566be591a
sixel: initialize max_non_empty_row_no to -1, not 0
0 is a perfectly valid row number, and if max_non_empty_row_no==0,
that means we have *1* sixel row, and after trimming the image, the
image will have a height of 6 pixels.

If the sixel sequence is empty (or at least doesn’t emit any non-empty
pixels), then trimming the image should result in an image height of
0.

When max_non_empty_row_no is initialized to -1, it will still have
that value in unhook(), which makes the final image height 0.
2021-03-28 13:22:09 +02:00
Daniel Eklöf
60b3ccc641
term: runtime switch between a ‘fast’ and a ‘generic’ ASCII print function
term_print() is called whenever the client application “prints”
something to the grid. It is called for both ASCII and UTF-8
characters, and needs to handle sixels, insert mode and ASCII
vs. graphical charsets.

Since it’s on the hot path, this becomes unnecessarily slow.

This patch adds a “fast” version of term_print(), tailored for the
common case: ASCII characters in non-insert mode, without any sixels
and non-graphical charsets.

A new function, term_update_ascii_printer(), has been added, and must
be called whenever:

* The currently selected charset *index* changes
* The currently selected charset changes (from ASCII to graphical, or
  vice verse)
* Sixels are added to the grid
* Sixels are removed from the grid
* Insert mode is enabled/disabled
2021-03-16 08:45:18 +01:00
Daniel Eklöf
c5c3447ca8
sixel: sixel_overwrite_at_cursor(): early exit when the image list is empty
This avoids a call to sixel_overwrite_by_row() (where we also exit
early if the image list is empty).

This saves a couple of instructions to set up the arguments for
sixel_overwrite_by_row().
2021-03-14 14:19:12 +01:00
Daniel Eklöf
6eb68ffbd9
sixel: max geometry should be max geometry, not current window size
“current geometry” will report whatever value is the smallest; the max
geometry or the current window size.

But “max geometry” always returns the configured max geometry.

This aligns foot’s behavior with XTerm.
2021-03-14 10:58:13 +01:00
Daniel Eklöf
7d315d7bf9
sixel: implement P2=1 - transparent pixels
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.
2021-03-11 17:34:19 +01:00
Daniel Eklöf
6f6bcbc1bc
sixel: decgra: set max-non-empty-row-no when resizing the image
This ensures we don’t trim off bottom rows in unhook().

This could happen either because the application used “Set Raster
Attributes” to configure an image size larger than the sixels later
emitted.

Or, the last sixel row contains empty pixel rows.

In either case, the size set with “Set Raster Attributes” defines
the *minimum* image size; the image may still end up being larger.
2021-03-11 17:33:01 +01:00
Daniel Eklöf
ae86043780
sixel: decgri: handle a repeat count of 0, by ignoring it 2021-03-11 17:32:59 +01:00
Daniel Eklöf
6d208fa5e0
sixel: add: add sixel_add_many(), improving performance of DECGRI
DECGRI, i.e. repeat sixel character, only need to do image resizing,
and updating the current ‘column’ value *once*.

By adding sixel_add_many(), and doing the size/resize checking there,
the performance of sixel_add() is made much simpler.

This boosts performance quite noticeably when the application is
emitting many and/or long repeat sequences.
2021-03-11 17:32:59 +01:00
Daniel Eklöf
6e963dbf68
sixel: add: calculate absolute row no inside the loop
This results in the same number of instructions inside the loop, with
a ‘lea’ instead of a ‘mov’, but simplifies the post-loop logic to
update the global state.
2021-03-11 17:32:59 +01:00
Daniel Eklöf
751ccf5316
sixel: add: increase data pointer instead of offset
Same ‘add’ instruction to increase the offset, but simpler ‘mov’
instruction when writing the pixel data.
2021-03-11 17:32:58 +01:00
Daniel Eklöf
777576b66b
sixel: decgri: avoid load inside for-loop 2021-03-11 17:32:58 +01:00
Daniel Eklöf
9f224a13df
sixel: add optimized resize_horizontally() and resize_vertically()
The resize operations performed in the “hot” path either change the
width, or the height, but not both at the same time.
2021-03-11 17:32:58 +01:00
Daniel Eklöf
c181eb2bf6
sixel: empty pixels in the last sixel row doesn’t contribute to the image height
All-empty pixels rows in the last sixel row should not be included in
the final sixel image.

This allows applications to emit sixels whose height is not a multiple
of 6, and is how XTerm works.

This is done by tracking the largest row number that contains
non-empty pixels.

In unhook, when emitting the image, the image height is adjusted based
on this value.
2021-03-11 17:32:58 +01:00
Daniel Eklöf
d35963f584
sixel: resize: width is no longer a multiple of 6 2021-03-11 17:32:58 +01:00
Daniel Eklöf
6ab7052be4
sixel: set ‘col’ outside image boundaries when we’ve reached max height
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().
2021-03-11 17:32:58 +01:00
Daniel Eklöf
4b0e9a6bee
sixel: remove ‘max_col’
The image width *is* the maximum number of columns we’ve seen.
2021-03-11 17:32:57 +01:00
Daniel Eklöf
1c9c1aafc8
sixel: adjust image height when processing ‘-’ 2021-03-11 17:32:57 +01:00
Daniel Eklöf
891e0819f0
sixel: resize: check new width/height against max geometry early 2021-03-11 17:32:57 +01:00
Daniel Eklöf
6416319a99
Revert "sixel: resize: always round up height to a multiple of 6"
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.
2021-03-11 17:32:57 +01:00
Daniel Eklöf
ab70b4f16a
sixel: add: use de-reference the term struct for each access to the backing image 2021-03-11 17:32:57 +01:00
Daniel Eklöf
8c65c68b73
sixel: get rid of an ‘imul’ in sixel_add()
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.
2021-03-11 17:32:57 +01:00
Daniel Eklöf
dfdb42138d
sixel: add: resize is already checking against the current max geometry 2021-03-11 17:32:56 +01:00
Daniel Eklöf
839b7dd32e
sixel: resize: don’t resize beyond the current max geometry 2021-03-11 17:32:56 +01:00
Daniel Eklöf
8ec0f15a34
sixel: unhook: make sure image height is within bounds
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.
2021-03-11 17:32:56 +01:00
Daniel Eklöf
5a93fc30ca
sixel: add: simplify check for resize needed
Since the image height is always a multiple of 6, there’s no need to
round up the image height.
2021-03-11 17:32:56 +01:00
Daniel Eklöf
e94f108572
sixel: resize: always round up height to a multiple of 6
Sixels are always a multiple of six.
2021-03-11 17:32:56 +01:00
Daniel Eklöf
f175575c09
sixel: fixup row is multiple of 6 2021-03-11 17:32:55 +01:00
Daniel Eklöf
f143efb999
sixel: calculate alpha when updating the palette
Calculate color, with alpha, when updating the palette instead of
every time we *use* the palette.
2021-03-11 17:32:55 +01:00
Daniel Eklöf
869743060e
sixel: pre-calculate color before calling sixel_add()
This improves performance of DECGRI, since we now only need to
calculate the color once for the entire repeat sequence.
2021-03-11 17:32:55 +01:00
Daniel Eklöf
47e4cfbf5c
sixel: ignore invalid sixel characters in DECGRI (repeat) 2021-03-11 17:32:55 +01:00
Daniel Eklöf
7603ae5dc3
sixel: avoid multiplication inside the inner sixel emitter loop 2021-03-11 17:32:55 +01:00
Daniel Eklöf
6658740982
sixel: store current row position in pixels, not characters 2021-03-11 17:32:55 +01:00