Skip to content

Support both kitty and sixel images - #5445

Open
mgrant0 wants to merge 63 commits into
masterfrom
4902-image-support
Open

Support both kitty and sixel images#5445
mgrant0 wants to merge 63 commits into
masterfrom
4902-image-support

Conversation

@mgrant0

@mgrant0 mgrant0 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

This PR adds generalised support for both kitty and sixel images. Basically it breaks up images into grid size rectangles. If the terminal is kitty then it sends kitty the image using the special unicode character encoding and kitty displays it locally. If the terminal supports sixel images, the image is sent to the terminal using sixel escape codes. This uses the new screen-redraw scene and span drawing, so it works in and around floating panes. Images can be scrolled into the copy buffer without issue.

@mgrant0 mgrant0 linked an issue Jul 30, 2026 that may be closed by this pull request
@github-project-automation github-project-automation Bot moved this to Not Started in Open Issues & PRs Jul 30, 2026
@senthil-instrumentl

Copy link
Copy Markdown

Was looking for kitty support in tmux and saw this PR. Not sure if i'm doing something wrong, but getting:

❯ ./configure --with-image-support
...
❯ make
make: *** No rule to make target `image-kitty.c', needed by `image-kitty.o'.  Stop.

@mgrant0

mgrant0 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

Oops sorry, forgot to add the new files. Added now. Please try again.

@mgrant0

mgrant0 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author
image image

Both kitty and sixel images seem to work. Plese let me know. There's also a rudimentary fallback to ascii in terminals with no image support at all.

@mgrant0

mgrant0 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

@senthil-instrumentl found another missing .h file which is now fixed (included inside image-kitty.h). Please give it a try. Feedback welcome. Thanks!

@mgrant0

mgrant0 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

I am using it inside Windows Terminal for development. I tried it briefly in kitty term. I fixed a flashing issue but that was already pushed yesterday. I can try it in wezterm probably tomorrow.

Can you attach the png file or tell me where to get it?

Hopefully we can fix the flashing everywhere. We will get this to work. Thanks for your help testing it!

@paranoidi

Copy link
Copy Markdown

I Deleted my previous comments. Apologies for the email noise. My previous tests were invalid.

However the PR sill does not work as expected for me. The images are rendered at incorrect position and do not move as the terminal scrolls. It also causes occasionally the statusbar to disappear when rendering or replaced by newline symbol.

.tmux.conf has

set -ga terminal-features ',*:RGB:sixel:sync:kitty'

Example:

image

Using chafa produces similar results.

I can not get kitty to work either. It will either do nothing or report unsupported.

❯ kitty +kitten icat ~/Pictures/bitwig_layer_transparency.png

~/Pictures
❯ kitty +kitten icat ~/Pictures/bitwig_layer_transparency.png
Error: This terminal does not support the graphics protocol use a terminal such as kitty, WezTerm or Konsole that does. If you are running inside a terminal multiplexer such as tmux or screen that might be interfering as well.

I am using WezTerm built from master couple days ago. All of these work flawlessly outside tmux.

mgrant0 added 3 commits August 3, 2026 09:13
Enable the sixel feature when WezTerm is detected and clarify that the kitty terminal feature requires Unicode placeholder support rather than only the basic graphics protocol.
@mgrant0

mgrant0 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

I reproduced this in wezterm locally. I've added a fix for this to this branch, please pull it and recompile.

IMPORTANT: you should remove this from your .tmux.conf

set -ga terminal-features ',*:RGB:sixel:sync:kitty'

Then fully restart the tmux server. tmux will detect each attached terminal separately:

  • WezTerm: RGB plus sixel.
  • Kitty: RGB plus Kitty placeholders.
  • Non-image terminals: text fallback.

If you have a separate reason to force sync, it should be configured only for the appropriate terminal pattern, not with *. The global line incorrectly claims every client supports both image protocols, which defeats per-client backend selection.

A resize may temporarily leave the terminal cell pixel dimensions unknown while tmux queries the terminal. Schedule another client redraw when the pixel geometry response arrives so the graphical backend replaces the temporary text fallback.
@bptato

bptato commented Aug 3, 2026

Copy link
Copy Markdown

I like the initiative, but there are a few bugs.
First, a minor one: HLS conversion has the usual bug where it doesn't match Sixel semantics. (Hue is rotated -120 degrees compared to common use today, test with img2sixel -t hls ...). Also, rounding.

diff --git a/image-sixel.c b/image-sixel.c
index 577ba51a..595c3836 100644
--- a/image-sixel.c
+++ b/image-sixel.c
@@ -429,9 +429,9 @@ sixel_colour_to_rgb(u_int colour, u_char *r, u_char *g, u_char *b)
 	double	h, l, s, p, q;
 
 	if (type == 2) {
-		*r = ((colour >> 16) & 0x1ff) * 255 / 100;
-		*g = ((colour >> 8) & 0xff) * 255 / 100;
-		*b = (colour & 0xff) * 255 / 100;
+		*r = (((colour >> 16) & 0xff) * 255 + 50) / 100;
+		*g = (((colour >> 8) & 0xff) * 255 + 50) / 100;
+		*b = ((colour & 0xff) * 255 + 50) / 100;
 		return;
 	}
 	if (type != 1) {
@@ -448,9 +448,9 @@ sixel_colour_to_rgb(u_int colour, u_char *r, u_char *g, u_char *b)
 	}
 	q = l < 0.5 ? l * (1 + s) : l + s - l * s;
 	p = 2 * l - q;
-	*r = sixel_hue(p, q, h + 1.0 / 3) * 255;
-	*g = sixel_hue(p, q, h) * 255;
-	*b = sixel_hue(p, q, h - 1.0 / 3) * 255;
+	*r = sixel_hue(p, q, h) * 255 + 0.5;
+	*g = sixel_hue(p, q, h - 1.0 / 3) * 255 + 0.5;
+	*b = sixel_hue(p, q, h + 1.0 / 3) * 255 + 0.5;
 }
 
 /* Convert decoded SIXEL data into the protocol-neutral immutable image. */

Second, sixel_from_image is... not pretty. The way you'd normally go about this is some kind of image quantization algorithm (I've had luck with octrees), with the current nearest neighbor approach the output looks awful, as demonstrated by your own screenshot. Or at the very least you should apply some error diffusion, ref.

Third, sizing is broken. For example,

printf '\033P0;1q"1;1;26;26#0;2;50;50;50#1;2;0;0;0#2;2;49;49;49#3;2;33;33;33#4;2;47;47;47#5;2;44;44;44#6;2;48;48;48#2?O?C!5?S!4?__?_?__$#0?_wW!5KGwo_-#0?~~!8?!10@BB}w$#2!12?a_A?A?A?A??@E-#0?~~!7?KK~~KK!7?~~$#2!9?K!6?K-#0?F^[owowowowowowowowooS^F$#2?G!10?@@!7?G??G-\033\'

should print a 26x26 image, but it gets scaled up to the cell size, so if I have 20x10 pixel cells then it blows up to 40x30. From a quick look at the code, draw_rectangle only gets sizes in cells, so perhaps that's related.

Finally, maybe I'm wrong but it looks like the cropping Kitty parameters (X/Y/w/h (edit: sorry, I meant x/y, although I use X/Y too but it's less important)) aren't respected? Those are kind of necessary for efficient TUIs, e.g. I use them extensively in chawan so that I don't have to re-send images on scroll.

mgrant0 added 4 commits August 3, 2026 12:22
Use the DEC hue origin and channel order, and round percentage and HLS conversions to the nearest byte value.
Retain the padded canonical pixel canvas separately from the image content. Scale only the populated portion of partial edge cells so an image keeps its exact raster size on the originating terminal while retaining its cell footprint.
Build an adaptive 256-colour palette with median cut instead of using a fixed colour cube. Apply Floyd-Steinberg error diffusion while mapping pixels to reduce banding and preserve image detail.
@mgrant0

mgrant0 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author
  • Correct DEC SIXEL HLS hue ordering and rounding in image-sixel.c.
  • Preserve original pixel dimensions so partial edge cells are not stretched.
  • Adaptive median-cut colour quantization with Floyd–Steinberg error diffusion.
  • Kitty’s actual source-cropping keys, lowercase x/y/w/h, plus c/r placement sizing.
    Uppercase X/Y are destination offsets within the first cell, not crop coordinates,
    according to the Kitty protocol (https://sw.kovidgoyal.net/kitty/graphics-protocol/).
  • Cropped Kitty placements use zero-copy immutable pixel views in image.c, and row-
    strided views are correctly streamed when uploaded.

@bptato, Did this address all your concerns? The sixel image definitely looks better to me now, thanks!

@mgrant0

mgrant0 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

@nicm

  • A lot of functions are missing header comments. They should all have them.

Added.

  • sixel_from_image is clearly a disaster and needs to be tidied up. Could probably do with some comments.

done.

  • Some of the naming could be better image_rect would be much neater than image_rectangle. Maybe sixel_hgram not sixel_histogram.

Ok shortened these up, also dimensions renamed to size. histogram to hgram in the struct, and hg in var, but left it histogram in the flags.

  • Instead of image_tty_is_graphical and image_tty_scrolls I would move the flags into tmux.h and add image_backend_flags.

Exposed image backend_flags now.

  • image_cmp/RB_GENERATE_STATIC should be at the head of the file.

relocated.

  • There is some crazy casting going on here eg in image_size_in_cells.

u_int64 was necessary in case of overflow which is highly unlikely. removed cast all together now, using width / xpixel + (width % xpixel != 0).

  • SIXEL should definitely not imply Sync.

SIXEL and synchronized updates must now be enabled independently.

  • tty_draw_line should not know so much about images. The ENABLE_IMAGES bit in tty-draw.c should call a image helper to get the cell.

image cell drawing now moved into image.c

@bptato

bptato commented Aug 8, 2026

Copy link
Copy Markdown

OK, now Kitty seems to work right, but Sixel broke. (It's why I posted that link, but anyway...)
The semantics differ for the two. With Kitty (at least the original protocol - not sure about Unicode placeholders...), the images are a separate layer from text, and neither affects the other.
Meanwhile with Sixel, printing any text on top of an image erases it from the cells affected. Note that the inverse is not true, i.e., printing images on top of text does not remove the text:

#!/bin/sh
reset
printf '\033P0;1q"1;1;26;26#0;2;50;50;50#1;2;0;0;0#2;2;49;49;49#3;2;33;33;33#4;2;47;47;47#5;2;44;44;44#6;2;48;48;48#2?O?C!5?S!4?__?_?__$#0?_wW!5KGwo_-#0?~~!8?!10@BB}w$#2!12?a_A?A?A?A??@E-#0?~~!7?KK~~KK!7?~~$#2!9?K!6?K-#0?F^[owowowowowowowowooS^F$#2?G!10?@@!7?G??G-\033\'
tput cup 0 0
printf 'this line damages the first image\n'
printf '\n\n'
printf 'but on this line, the image will appear on top the text\r'
printf '\033P0;1q"1;1;26;26#0;2;50;50;50#1;2;0;0;0#2;2;49;49;49#3;2;33;33;33#4;2;47;47;47#5;2;44;44;44#6;2;48;48;48#2?O?C!5?S!4?__?_?__$#0?_wW!5KGwo_-#0?~~!8?!10@BB}w$#2!12?a_A?A?A?A??@E-#0?~~!7?KK~~KK!7?~~$#2!9?K!6?K-#0?F^[owowowowowowowowooS^F$#2?G!10?@@!7?G??G-\033\'
printf '\n\n'

@mgrant0

mgrant0 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

Here is what I see:
image
If you look closely you can see that the folder icon is transparent but the edge looks like it is acting as you describe in both cases. What am I missing?

@mgrant0

mgrant0 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

wait, i think i see what you mean, the second line is right, the first line is wrong.

mgrant0 added 2 commits August 8, 2026 22:16
…ne later overwritten by text. On a SIXEL terminal, damaged cells are omitted from image output; on Kitty they remain ordinary positional placements.
@mgrant0

mgrant0 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

Try the latest push please.

@mgrant0

mgrant0 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

@bptato I was thinking about this last night, the current patch is not perfect, i may be able to do better. The issue is that tmux stores what the tty looks like internally so that when another client connects it can redraw it. tmux does not store the history of a cell (the temporal state of the cell). Storing a complete history of a cell (back to when it was empty) does not seem worth it. The current implementation does not store the state of overlapping sixel images. For example, if you try to composit 2 sixel images on top of one another, tmux would draw it, but if you reconnect or redraw the screen, the lower image would go away because it's no longer in the cell. The cell tracks which image (which part of which image) goes there, not a bit image of the cell.

So what can I do? I can leave it as it is now which is once a cell has been overwritten by a character, any bit image in that cell is gone. This is definitely different than outside of tmux on ms term and probably other terminals. Or I can track a single image layer that basically tracks if there were bits written there before, don't clear first and just drop the text on top of it. That will make your test look correct but it won't work for more complex things like image1...image2...text where image2 has some transparent parts or does not fully cover image1 and would get composited on image1.

Since we already store a single image ref and character for each cell, I am willing to store an order flag in the cell:

  • flag clear: the retained image was drawn after the cell’s text, so redraw text then image;
  • flag set: the text was drawn after the retained image, so redraw image then text.

And if multiple images overlap, only the most recent image reference is retained for that cell. Any earlier image underneath is no longer represented there. This I think is quite easy to implement.

I realise this isn't perfect but it seems like trying to preserve arbitrary z-ordering compositing of multiple images is going to add a ton of complexity for something which will never be used. What do you think?

(edit) I have prepared a version that does the above. It wasn't super easy and it affected a bunch of files outside image-sixel.c. Please let me know how important is it to preserve this semantic even partially? My preference is the current behaviour because it is simple, but it is clearly not identical to other terminals.

(edit 2) when there is both an image ref and a character in the cell, it was always drawing them both but first the character then the image. with what i just did, it preserves an order and more specifically does not clear the cell before ploping a character in it so that any remnant there remains there on purpose. It also fixed another bug. I am going to push this now, after some thinking, this really is the better of the two worlds, but it only preserves an image layer and text layer and which one was written first, not n-layers. if preserving n-layers is really important, let me know.

@bptato

bptato commented Aug 9, 2026

Copy link
Copy Markdown

The issue is that tmux stores what the tty looks like internally so that when another client connects it can redraw it. tmux does not store the history of a cell (the temporal state of the cell). Storing a complete history of a cell (back to when it was empty) does not seem worth it.

You don't have to store cell history. You "simply" have to store the images in the order they were received. (I know it's not simple...)

The current implementation does not store the state of overlapping sixel images. For example, if you try to composit 2 sixel images on top of one another, tmux would draw it, but if you reconnect or redraw the screen, the lower image would go away because it's no longer in the cell. The cell tracks which image (which part of which image) goes there, not a bit image of the cell.

Yeah, at this point I'm skeptical of any approach that directly associates images with cells. At the very least, that association must be an ordered list, and that gets expensive (or complex) very fast.

(Of course, it's a tradeoff, storing images in a separate list makes Sixel invalidation complex. Really the core issue is that Sixel was designed to be dumped onto the terminal's framebuffer, while Kitty intentionally decouples images from text, so combining them into a single model is difficult whatever you do.)

So what can I do? I can leave it as it is now which is once a cell has been overwritten by a character, any bit image in that cell is gone. This is definitely different than outside of tmux on ms term and probably other terminals. Or I can track a single image layer that basically tracks if there were bits written there before, don't clear first and just drop the text on top of it. That will make your test look correct but it won't work for more complex things like image1...image2...text where image2 has some transparent parts or does not fully cover image1 and would get composited on image1.

Since we already store a single image ref and character for each cell, I am willing to store an order flag in the cell:

  • flag clear: the retained image was drawn after the cell’s text, so redraw text then image;
  • flag set: the text was drawn after the retained image, so redraw image then text.

And if multiple images overlap, only the most recent image reference is retained for that cell. Any earlier image underneath is no longer represented there. This I think is quite easy to implement.

I realise this isn't perfect but it seems like trying to preserve arbitrary z-ordering compositing of multiple images is going to add a ton of complexity for something which will never be used. What do you think?

Well, it's broken, and I have no idea how the 'z' Kitty parameter can ever be implemented under these constraints (AFAICT right now it isn't?)
Although it might work in practice in most cases, but as you mention that's only because the internal state does not match what the terminal actually sees. So I'm definitely not a fan.

The implementation now distinguishes a newly placed image cell from one later overwritten by text. On a SIXEL terminal, damaged cells are omitted from image output; on Kitty they remain ordinary positional placements.

This makes no sense. Image/text mixing semantics are decided by the input format, not the output. The app sending the image has no idea about the outer terminal's output format.

@mgrant0

mgrant0 commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Perhaps cell history is not the proper way to describe it. Do I need to store each layer is really what I meant. By history, I mean that if you don't specify a z-index, the next image is above the previous one and if there are transparent bits you see through the one on top, down to older ones or ones lower in the z index. I can definitely do this and my question to you was is this necessary? and it sounds like from your throwing up your arms in disgust the answer sounds like yes. I was hoping to avoid doing this but if i have to i will.

Your test cases have been super helpful. If you could prepare one or more that exercise the zindex more that would be helpful and I hope to get closer. I don't know how close I can get to working for you but hopefully I can get close enough.

This makes no sense. Image/text mixing semantics are decided by the input format, not the output. The app sending the image has no idea about the outer terminal's output format.

Is this a comment again about the z-index? I thought by implementing this single pane I had more or less fixed this but it looks like I am going to have to go all the way and implement multiple and it's not the output dictating it, it's the input and z-index. Since sixel images do not support any sort of z-index placement, they are temporal, as in, the next one is on top of the previous. I have that info already, it's just a matter of wiring that to the cells. That should also make kitty z-index work. This all doesn't feel impossible, just a bit fiddly. Maybe I'm deluded?

@bptato

bptato commented Aug 9, 2026

Copy link
Copy Markdown

Do I need to store each layer is really what I meant.

What do you want to achieve?

  1. If you just want to dump images to the CLI and marvel at the result, the patch is fine as it is.
  2. If you want to implement Kitty correctly so that, for example, TUI apps that use it work well, then yes, to my knowledge, this is what the protocol requires.

As for me, my main priority is, selfishly, that my TUI app does not work much worse after this patch than before. Admittedly, this doesn't affect many people, but it's a weaker version of 2, so I thought it'd be a useful direction for now.
In any case, I don't know if what you're proposing would cause significant problems in practice (again, for me...), because the thing you've implemented is broken in a different way:

Is this a comment again about the z-index?

No, it is a comment about how, according to the commit message, text & image interaction now depends on the outer terminal, and from a quick test, the commit message appears to be accurate. I'm saying this makes no sense, because the app that sent the image does not have information about the outer terminal.
For example, in the scripts I posted above, the result ought to be the same no matter if the outer terminal supports Sixel or Kitty.

Hint: I think the image struct should have a "received from Sixel" flag that affects what happens after some text is printed over it.

Edit: re-reading the conversation, maybe I wasn't clear enough about this: Sixel cannot be displayed behind text. If you print any text over a Sixel image, then the image disappears from the cells where you printed the text. So any logic of whether the image is "behind" the text is redundant; if it's behind the text, then it's already gone.
(I think it's because the original DEC terminals just printed the image to the framebuffer directly, which would preserve text in the background. But when you printed text over images, the (presumably) generic text drawing routine would clear the cell and then draw the text.)

Since sixel images do not support any sort of z-index placement, they are temporal, as in, the next one is on top of the previous. I have that info already, it's just a matter of wiring that to the cells.

...by creating an array of images, then assigning an id to that array, and linking the array to the cell?
I mean, yes, it can work, I'm just not entirely convinced this is any simpler than handling images separately from the grid :P

Replace the per-cell image marker with sparse placement spans attached to
grid lines. A placement owns all of its spans and records the input
protocol, application image and placement IDs, z-index, and creation order.

This retains overlapping image layers without storing a list in every grid
cell. Grid operations move, split, clip, and remove only the affected spans.

Use the input protocol to determine image/text interaction: later text
damages SIXEL spans, while Kitty placements remain and are ordered by their
z-index. Rendering then adapts that one logical scene for each client,
rather than changing its semantics according to whether the outer terminal
uses Kitty or SIXEL.
@mgrant0

mgrant0 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

z-index hopefully now works. Thanks, your hint was right. The grid now retains every image placement as a separate ordered layer, represented sparsely as spans on the affected grid lines rather than as an array per cell. Each placement records its input protocol, IDs, z-index, and order.

Previously, an image cell contained only one image reference. If image B overlapped image A, the cell could remember only B; tmux lost the fact that A should still be drawn underneath through B’s transparent pixels.

Now the grid does not store an image list inside every cell. Instead:

  • An image placement is one logical object: “this image is placed here,” with its Kitty/SIXEL origin, image and placement IDs, Kitty z-index, and creation order.
  • For every affected grid row, that placement has a horizontal span—for example, columns 20–39 on row 8.
  • Those spans attach to the grid lines the image crosses. A 10×5-cell image therefore has five spans, not 50 per-cell image records.
  • Multiple placements may have spans over the same cells, so the full stack is retained.

On redraw, tmux finds the spans touching the area being rendered and orders them:

  1. Kitty images with negative z-index: behind text.
  2. SIXEL images: temporal framebuffer-style layer.
  3. Kitty images with zero or positive z-index: above text.

Within compatible layers, creation order breaks ties. This lets transparent overlapping images retain the layers below them, while avoiding an expensive per-cell linked list or array.

Text writes now damage only SIXEL-origin spans. Kitty-origin placements retain their z semantics, including overlapping placements; the outer client’s Kitty/SIXEL capability no longer decides the logical image/text ordering. Hoping that makes sense now.

The unavoidable output limitation is that SIXEL cannot draw an image beneath a text glyph. When rendering the same logical scene to SIXEL, tmux preserves text in those cells rather than emitting the underlying image portion.

I installed Chawan to test tmux image scrolling and found that I needed -o buffer.images=true before inline images were fetched. Is keeping this opt-in intentional? Since it controls downloading images even when they cannot be displayed, I can see the bandwidth/privacy rationale; but would enabling it by default, or making it more discoverable, better match the expected browser experience?

I tested the normal, non-passthrough path with Chawan on the WebP gallery, including scrolling, resizing, and Kitty/SIXEL clients attached to the same server. Could you please retry the original gallery-scrolling case?

One separate Microsoft Terminal observation: its large SIXEL redraws need the independently advertised sync terminal feature to avoid visible intermediate repainting. This remains separate from SIXEL support; SIXEL does not imply Sync.

Here is what I did in Windows Terminal which inhibits the flashing you might have seen:

./tmux -Lchawan-ms start-server \; set -as terminal-features ',xterm-256color:sync' \; new

then in tmux:

cha -o buffer.images=true https://developers.google.com/speed/webp/gallery

It scrolls smoothly directly in Windows Terminal and Kitty (outside of tmux). I also tested this in Wezterm. Albeit with this single web gallery page. How close are we to getting this working? Please do let me know if you find some issues.

@bptato

bptato commented Aug 10, 2026

Copy link
Copy Markdown

z-index hopefully now works. Thanks, your hint was right. The grid now retains every image placement as a separate ordered layer, represented sparsely as spans on the affected grid lines rather than as an array per cell. Each placement records its input protocol, IDs, z-index, and order.

Now the grid does not store an image list inside every cell. Instead:

  • An image placement is one logical object: “this image is placed here,” with its Kitty/SIXEL origin, image and placement IDs, Kitty z-index, and creation order.
  • For every affected grid row, that placement has a horizontal span—for example, columns 20–39 on row 8.
  • Those spans attach to the grid lines the image crosses. A 10×5-cell image therefore has five spans, not 50 per-cell image records.
  • Multiple placements may have spans over the same cells, so the full stack is retained.

On redraw, tmux finds the spans touching the area being rendered and orders them:

  • Kitty images with negative z-index: behind text.
  • SIXEL images: temporal framebuffer-style layer.
  • Kitty images with zero or positive z-index: above text.

Within compatible layers, creation order breaks ties. This lets transparent overlapping images retain the layers below them, while avoiding an expensive per-cell linked list or array.

That sounds like a correct solution, thanks.

I installed Chawan to test tmux image scrolling and found that I needed -o buffer.images=true before inline images were fetched. Is keeping this opt-in intentional? Since it controls downloading images even when they cannot be displayed, I can see the bandwidth/privacy rationale; but would enabling it by default, or making it more discoverable, better match the expected browser experience?

Yes, it's horrible UX. I wish it was enabled by default too, but I couldn't do that for a multitude of reasons - some still relevant, others less so.
There are other similar discoverability issues too; eventually I hope to alleviate these by adding a setup screen to the first launch.
(Do note that you don't have to pass the flag every time, you can also set it to true in the config.)

I tested the normal, non-passthrough path with Chawan on the WebP gallery, including scrolling, resizing, and Kitty/SIXEL clients attached to the same server. Could you please retry the original gallery-scrolling case?

Indeed, it seems to be working correctly now. Thank you!

One separate Microsoft Terminal observation: its large SIXEL redraws need the independently advertised sync terminal feature to avoid visible intermediate repainting. This remains separate from SIXEL support; SIXEL does not imply Sync.

I also see it with xterm (which has no Sync), it's what I alluded to with the "performance characteristics" comment before.
But I think some jank is acceptable for now, as long as the semantics are correct.

@mgrant0

mgrant0 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor Author

Oh great, glad it works finally!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: For Review

Development

Successfully merging this pull request may close these issues.

Support kitty image protocol

5 participants