Commit graph

203 commits

Author SHA1 Message Date
Daniel Eklöf
c41f55c3a0
sixel: default bg color is now taken from the sixel palette, not the ANSI bg color
The wording in the original VT340 documentation is vague:

    Pixel positions specified as 0 are set to the current background
    color.

What does that mean? We _thought_ it meant the current ANSI background
color, as set with e.g. CSI 4x m.

It's still all a bit vague, but seeing that we have separate palettes
for text and graphic (should we?), it doesn't make sense to use the
ANSI background color as the default sixel background color.

So, use entry 0 from the sixel palette instead.
2024-09-05 07:33:05 +02:00
Daniel Eklöf
48cf57818d
term: performance: use a bitfield to track which ascii printer to use
The things affecting which ASCII printer we use have grown...

Instead of checking everything inside term_update_ascii_printer(), use
a bitfield.

Anything affecting the printer used, must now set a bit in this
bitfield. This makes term_update_ascii_printer() much faster, since
all it needs to do is check if the bitfield is zero or not.
2024-06-26 18:39:24 +02:00
Daniel Eklöf
0bf5a7e902
sixel: comment: document the P1 parameter
(and no, it's no longer unimplemented)
2024-05-22 14:56:10 +02:00
Daniel Eklöf
3d2588edf8
sixel: don't allow pan/pad changes after sixel data has been emitted
Changing pan/pad changes the sixel's aspect ratio. While I don't know
for certain what a real VT340 would do, I suspect it would change the
aspect ratio of all subsequent sixels, but not those already emitted.

The way we implement sixels in foot, makes this behavior hard to
implement. We currently don't resize the image properly if the aspect
ratio is changed, but not the RA area. We have code that assumes all
sixel lines have the same aspect ratio, etc.

Since no "normal" applications change the aspect ratio in the middle
of a sixel, simply disallow it, and print a warning.

This also fixes a crash, when writing sixels after having modified the
aspect ratio.
2024-04-15 16:07:47 +02:00
Daniel Eklöf
0ab05f4807
sixel: also set 'alloc_height', when short-cutting a resize operation
In some cases, a sixel may be resized vertically, while still having a
zero-width. In this case, the resize operations are short-cutted, and
no actual allocations are done.

However, we forgot to set 'alloc_height' when doing so. As a result,
the trimming code (when the sixel is "done"), trimmed away the entire
sixel.
2024-04-15 16:05:56 +02:00
Daniel Eklöf
a99434929c
sixel: abuse wmemset() when initializing a freshly allocated image buffer
wmemset() is heavily optimized, and in some cases, *much* faster than
manually initializing the new image pixels.

Furthermore, assume calloc() is better at initializing memory to zero,
and use that when initializing new pixels in a transparent image.
2024-03-25 16:33:15 +01:00
Daniel Eklöf
9fcf5977c0
sixel: fix cursor positioning when image is split up into several 2024-03-18 17:12:11 +01:00
Daniel Eklöf
bce1d7313d
sixel: hopefully handle image height correctly when trimming
When trimming trailing empty rows from the final image, handle the RA
region correctly:

* If image is transparent, trim down to the last non-empty pixel row,
  regardless of whether it is inside or outside the RA region.

* If image is opaque, trim down to either the last non-empty pixel
  row, or the RA region, whatever is the largest height.
2024-03-18 17:11:27 +01:00
Daniel Eklöf
cb820a498b
sixel: RA region does not affect the text cursor position
The raster attributes is, really, just a way to erase an area. And, if
the sixel is transparent, it's a nop.

The final text cursor position depends, not on our image size (which
is based on RA), but on the final graphical cursor position.

However, we do want to continue using it as a hint of the final image
size, to be able to pre-allocate the backing buffer.

So, here's what we do:

* When trimming trailing transparent rows, only trim the *image*, if
  the graphical cursor is positioned on the last sixel row, *and* the
  sixel is transparent.
* Opaque sixels aren't trimmed at all, since RA in this acts as an
  erase that fills the RA region with the background color.
* The graphical cursor position is always adjusted (i.e. trimmed),
  since it affects the text cursor position.
* The text cursor position is now calculated from the graphical cursor
  position, instead of the image height.
2024-03-18 17:11:26 +01:00
Daniel Eklöf
cc660bc7c1
sixel: trim trailing, fully transparent sixel rows
See https://github.com/hpjansson/chafa/issues/192
2024-03-18 17:09:20 +01:00
Daniel Eklöf
282c55aa4a
sixel: place cursor on the last character row touched by the sixel
After emitting a sixel, place the cursor on the character row touched
by the last sixel. The last sixel _may_ not be a multiple of 6
pixels, *if* the sixel had an explicit width/height set via raster
attributes.

This is an intended deviation from the DEC cursor placement algorithm,
where the cursor is placed on the character row touched by the last
sixel's *upper* pixel.

The adjusted algorithm implemented here makes it much easier for
applications to handle text-after-sixels, since they can now simply
assume a single text newline is required to move the cursor to a
character row not touched by the sixel.
2024-03-18 16:58:54 +01:00
Daniel Eklöf
f17b989650
sixel: disable debug logging 2024-03-16 08:57:15 +01:00
Daniel Eklöf
60fd4a262c
sixel: initialize the color table to colors used by the VT340 2024-03-15 15:19:43 +01:00
Daniel Eklöf
3e6f0e63f3
sixel: don't try to emit a sixel if we're outside the image's boundaries
Closes #1634
2024-03-07 16:21:06 +01:00
Daniel Eklöf
75fd59df3f
sixel: debug: sixel image _may_ be zero-sized
For example, and single GNL (Graphical New Line) will result in a
sixel with a non-zero height, but a zero width.
2024-03-07 16:20:29 +01:00
Daniel Eklöf
a2fa667f45
sixel: we no longer need the extra newline
Since we never place the cursor *under* the sixel anymore.
2024-03-07 16:19:56 +01:00
Daniel Eklöf
1421ba504d
sixel: debug: fix logged width/height values when emitting sixel
image.width and image.height are the scaled dimensions, and these
haven't been set when the log message is printed.
2024-03-07 16:18:35 +01:00
Daniel Eklöf
1568518ab3
sixel: performance improvements
* Store pointer to current pixel (i.e. pixel we're about to write to),
  instead of a row-byte-offset. This way, we don't have to calculate the
  offset into the backing image every time we emit a sixel band.

* Pass data pointer directly to sixel_add_*(), to avoid having to
  calculate an offset into the backing image.

* Special case adding a single 1:1 sixel. This removes a for loop, and
  simplifies state (position) updates. It is likely LTO does this for
  us, but this way, we get it optimized in non-LTO builds as well.
2024-03-06 16:37:54 +01:00
Daniel Eklöf
8ff8ec5b70
sixel: fix row height calculation in resize_vertically()
In resize_vertically(), we assumed a sixel is 6 pixels tall. This is
mostly true, but not for non-1:1 sixels. Or, to be more precise, not
for sixels where 'pan' != 1.

This caused us to allocate too little backing memory, resulting in a
crash when we later tried to write to the image.
2024-03-06 16:36:30 +01:00
Daniel Eklöf
7999975016
Don't use fancy Unicode quotes, stick to ASCII 2024-02-06 12:36:45 +01:00
Daniel Eklöf
4aa67e464a
sixel: erase: fix clearing of cell->attrs.clean
When erasing a sixel, the cells underneath it must be marked as
'dirty', in order to be re-rendered.

This was not being done correctly; the for loop loops *from* the start
col, meaning the *end* col is *not* sixel->pos.col, as that's
the *number* of columns, not the *end* column.
2023-10-12 16:31:37 +02:00
CismonX
dbee099eeb
sixel: fix regression for DECGRI with a repeat count of 0 2023-07-11 00:51:32 +08:00
Daniel Eklöf
49fb0cf359
sixel: re-scale images when the cell dimensions change
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
2023-06-30 08:29:35 +02:00
Daniel Eklöf
5e9d68695c
sixel: add_ar_11(): manually unroll loop
This generates both smaller, and faster code
2023-06-29 15:40:00 +02:00
Daniel Eklöf
75f9bed6b6
sixel: refactor: shorten very verbose switch case statements 2023-06-29 15:40:00 +02:00
Daniel Eklöf
3555e81fee
sixel: special case parsing of images with an aspect ratio of 1:1
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.
2023-06-29 15:40:00 +02:00
Daniel Eklöf
2388015b10
sixel: assert upper pixel of last sixel maps to last image row, *or lower* 2023-06-24 07:31:08 +02:00
Daniel Eklöf
c15e75357a
sixel: ensure enough rows have been scrolled in, to fit the image
When emitting a sixel, we need to:

a) scroll terminal content to ensure the new image fits
b) position the text cursor

Recent changes in the cursor positioning logic meant we reduced the
number of linefeeds, to ensure 1) sixels could be printed to the
bottom row without scrolling the terminal contents, and 2) the cursor
was positioned on the last sixel row.

Except, we’re not actually positioning the cursor on the last sixel
row. We’re positioning it on the text row that maps to the *upper*
pixel of the last sixel.

In most cases, this _is_ the last row of the sixel. But for certain
combinations of font and image sizes, it may be higher up.

This patch fixes a regression, where the terminal contents weren’t
scrolled up enough for certain images, causing a crash when trying to
dirty a not-yet scrolled in row.

The fix is this:

* Always scroll by the number of rows occupied by the image, minus
  one. This ensures the image "fits".
* Adjust the cursor position, if necessary.
2023-06-24 07:31:08 +02:00
Daniel Eklöf
425cf894d4
sixel: resize(): handle no size change 2023-06-24 07:31:08 +02:00
Daniel Eklöf
5d576fccba
sixel: regression: linefeed count for chunked up sixel image
All image chunks but the last *should* scroll the screen content.
2023-06-24 07:31:08 +02:00
Daniel Eklöf
1eb90b2405
sixel: minor fixes after implementing support for non-1:1 aspect ratios
* Lazy initialize image height. This is necessary to prevent garbage
  from being rendered for "empty" sixels.
* Fix plotting of non-1:1 pixels
* Fix calculation of height in resize(), for non-1:1 aspect ratios
2023-06-24 07:31:08 +02:00
Daniel Eklöf
774570ec41
sixel: stop cropping images to the last non-transparent row 2023-06-24 07:31:08 +02:00
Daniel Eklöf
d6d143e2a6
sixel: respect sixel aspect ratio
That is, parse P1 when initializing a new sixel, and don’t ignore
pad/pad in the raster attributes command.

The default aspect ratio is 2:1, but most sixels will override it in
the raster attributes command (to 1:1).
2023-06-24 07:31:08 +02:00
Daniel Eklöf
66d9b8da60
sixel: fix cursor positioning logic
This adjusts the logic that positions the text cursor after emitting a
sixel, when sixel scrolling mode is *enabled*.

We’ve always mimicked XTerm’s behavior. However, XTerm recently
changed its behavior, to better match that of an VT382.

Now, the cursor is placed *on* the last row of the sixel, instead of
on a new row after the sixel.

This allows applications to print sixels to the bottom row of the
terminal, without causing the content to scroll.

Finally, there was a bug in the horizontal positioning of the cursor;
it was placed on the *first* column of the row, instead of on the
first column of the sixel.
2023-06-24 07:31:02 +02:00
Daniel Eklöf
1433a81c08
sixel: apply background alpha when P2=0 or P2=2, and current bg color is the default bg color
Closes #1360
2023-05-31 16:27:48 +02:00
Daniel Eklöf
1c16e4a575
Tag a couple variables with UNUSED, to fix warnings with clang-15
Closes #1278
2023-02-12 19:09:48 +01:00
Daniel Eklöf
43a48f53d4
sixel: don’t crash when sixel image exceeds current sixel max height
When we try to resize a sixel past the current max height, we set
col > image-width to signal this.

This means ‘width’ could be smaller than ‘col’. When calculating how
many sixels to emit in sixel_add_many(), we didnt’ account for this.

The resulting value was -1, converted to ‘unsigned’. I.e. a very large
value. This resulted in an assert triggering in sixel_add() in debug
builds, and a crash in release builds.
2022-10-13 17:52:34 +02:00
Daniel Eklöf
f70c34c5a8
sixel: add sixel_reflow_grid()
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);
2022-10-10 17:19:18 +02:00
Daniel Eklöf
1d4e1b921d
sixel/terminal: use the new grid and selection APIs
Use grid_row_abs_to_sb() instead of manually “rebasing” row numbers.

Use selection_get_{start,end}() to retrieve the current selection
coordinates.
2022-04-25 20:00:14 +02:00
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