Commit graph

147 commits

Author SHA1 Message Date
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
Daniel Eklöf
c95c663989
sixel: size provided by DECGRA does *not* limit the sixel size
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
2021-03-06 15:03:47 +01:00
Daniel Eklöf
1563fecc20
sixel: don’t go past the bottom scroll margin when sixel scrolling is disabled
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.
2021-02-26 14:20:00 +01:00
Daniel Eklöf
849427bf10
sixel: implement private mode 80 - sixel scrolling
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.
2021-02-26 09:28:03 +01:00
Daniel Eklöf
03c675c6e2
sixel: implement private mode 8452 - cursor positioning after sixel
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
2021-02-23 10:36:02 +01:00
Daniel Eklöf
8c44b63938
sixel: free private color registers on unhook 2021-02-23 09:40:22 +01:00
Daniel Eklöf
4aa980a6a2
sixel: implement private mode 1070 - private color palette
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
2021-02-23 09:40:22 +01:00
Craig Barnes
e56136ce11 debug: rename assert() to xassert(), to avoid clashing with <assert.h> 2021-01-16 20:16:00 +00:00
Craig Barnes
22f25a9e4f Print stack trace on assert() failure or when calling fatal_error()
Note: this uses the __sanitizer_print_stack_trace() function from the
AddressSanitizer runtime, so it only works when AddressSanitizer is
in use.
2021-01-16 19:56:33 +00:00
Craig Barnes
3f4cfa338b Add xsnprintf() and remove some unnecessary strlen(3) calls 2021-01-14 21:30:06 +00:00
Daniel Eklöf
5efd34c3c4
sixel: current geometry: don’t exceed current window dimensions
This is similar to what XTerm does, and fixes an issue with lsix,
where the output did not wrap.
2021-01-14 14:41:34 +01:00
Daniel Eklöf
ba8b15d675
sixel: change default max size to 10000x10000
It used to be the size of the window. This caused images to be cropped
when the application emitting them didn’t change the max size.
2020-11-23 20:10:55 +01:00
Daniel Eklöf
275f97381d
sixel: fix crash when an explicit sixel size had a height less than 6 pixels 2020-11-23 19:22:40 +01:00
Daniel Eklöf
e0297daa1f
hsl: add our own implementations of rgb-to-hsl and hsl-to-rgb
* 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().
2020-11-15 19:45:33 +01:00
Daniel Eklöf
4b645376fd
render: improve sixel rendering performance
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).
2020-11-13 16:54:40 +01:00
Daniel Eklöf
468add5591
sixel: reflow: drop sixels that crosses the scrollback history’s end
This fixes various crashes that occurred after a reflow due to the
sixel image list invariants no longer being true.
2020-10-09 07:44:18 +02:00
Daniel Eklöf
2c952761f2
sixel: unhook: do overwrite *after* linefeeding
This ensures the overwrite is done when the scrollback history is in
the same state as when we then insert the new image.
2020-10-09 07:44:18 +02:00
Daniel Eklöf
93f5e743cc
sixel: overwrite: use pixman to calculate new the sixel boundaries
When punching a hole in a sixel (and thus splitting it up into up to
four new sixels), use pixman to calculate the new sixel coordinates
and sizes.
2020-10-09 07:44:18 +02:00