Repository navigation
Conversation
6901e89 to
534391c
Compare
b6a2f46 to
444309f
Compare
A suggestion for improvement: The SIXEL protocol is the most inferior protocol supported by Vim right now since it's limited to 255 colors. For good results any non trivial image must be properly quantized to 255 colors. While it would be nice to have quantization in Vim, I understand that quantization is a non-trivial problem and not in the scope of the project. After all Vim isn't an image viewer. I've experimented with quantization through pngquant (properly the best quantization implementation out there) and was able to yield good results. This image is just 'resize by ImageMagick -> convert to RGBA -> display by Vim'. You can see the artifacts caused by missing quantization:
This one was rendered by 'resize by ImageMagick -> quantize with
Source code of my highly experimental plugin implementing this can be found here: https://codeberg.org/yamagi/vim-imagepopup Long story short: It would be very appreciated if Vim provided a way to detect the color depth supported by the active image backend. Or at least what image backend is active. That way a plugin could determine automatically how the image should be processed before it's displayed by Vim. |
I suppose there can be a |
bfdfe56 to
d61484e
Compare
Good point. Thinking about it the only real advantage of a colordepth variable would be the addition of new backends existing plugins don't know about. But I would suspect that any new backend, be it in GUI or some new terminal protocol, would likely support 24 bit colors. So knowing what backend is used would be enough for plugin authors to special case SIXELs. |
|
I've decided to switch to using the libsixel library for the sixel support. Sixel is honestly really complex and has a bunch of edge cases that does not seem right for a text editor to have to implement. Additionally libsixel seems to be very well known and well distributed, and supported on all relevant platforms. This makes maintainance in the future easier |
73df265 to
79572c1
Compare
|
I'm going to put off creating the tests in this PR. I've thought of several solutions, but I think the most suitable one would to literally implement sixel in the builtin terminal (libvterm). Then there could be a The initial sixel support can be very rudimentary, possibly without scrollback support or sixel scrolling (DECSDM). This all sounds a bit overkill for just a test suite, but it looks like a fun challenge for me 😄 |
a6a50c5 to
fb74b04
Compare
|
I have done a big refactor, now pixman is a dependency for the image feature. This is because I need to handle having some parts of the image being under some text (e.g. border). For sixel this is simply a matter of ordering how you draw everything to the screen. However for the kitty graphics protocol and GTK4, images are actual "objects", not just pixels blit to the screen, meaning there has to be a way to figure out what rectangles in an image are visible. Pixman does that, its very fast (SIMD optimized) and well tested, and has no dependencies. Additionally pixman has some image manipulation features that can we possibly expose to the user (e.g. cropping, scaling). Before: recording_2026-09-08_23-14-49.mp4After: recording_2026-09-08_23-15-23.mp4 |
fb5acb6 to
43d0efc
Compare
The kitty and sixel tests are still goneThe branch still deletes test_image.vim is not the same thing. It checks the arguments of I reminded you about this, didn't I? And you reacted with a thumbs-up emoji. #21234 (comment) DocumentationMistakes I noticed while reading:
Comments in the codeimage.h has two empty comment blocks: /*
*
*/
typedef struct image_S image_T;The one above This one says the opposite of what the flag does: #define IMAGEF_FORCE 4 // If image backend should use a cacheimage_sixel.c uses it the other way around,
|
The GTK4 image removal is replaced by work in screen_char()9.2.1079 dropped the images that a fill or a scroll writes over, inside vim_draw_area_remove_images_in(self, row1, col1, row2, col2);This branch deletes DrawImage, mark_dirty_region_for_images(row, col, 1, 1);The placement is marked dirty and drawn again rather than dropped, which It also calls I have no test to point at for this one, 9.2.1079 came without one. |
|
I do not think the performance hit of screen_char is an issue. It only queues a screen redraw if the drawn cell intersects an image. Though I may be wrong, I'll perform a benchmark when I get back to my laptop to verify. Also can you clarify what you mean by "bounce", thanks As for the removed tests, sorry 😅 . I'm guessing I just removed them because they were failing then just forgot about it. Thank you for pointing out the many small issues though, I will fix them. Edit:
Can you elaborate on this? A popup can not have four image placements. |
|
I meant the check itself, not the redraw. For every cell that is Sorry, "bounce" was a poor word. I meant it might loop. The pty tests are the ones I want back. They cover 9.2.1074 and 9.2.1109 |
|
@h-east I have performed some benchmarks (with some unpushed optimizations). I ran with a 100 image placements of a 512x512 rgba image, which increased the redraw time by ~6 ms. However if i put an early return in Tested with -O2 (default makefile), foot terminal with sixel backend, and just held Ctrl-L to test in a syntax color heavy file. |
|
Tried adding the pty tests back but they are failing (despite manual testing being fine), so I'll work on that later. However I think these pty tests seem awfully hacky and fragile. Once we get testing infrastructure for the image feature then I think we can remove those (and reimplement them of course). |
4cd7811 to
3f2a372
Compare
|
I have only added back one pty test |
663a2ab to
75d3b3b
Compare
Hmm, Testing by hand does not carry over to the next person who touches this
|
|
I put the two tests back, adapted to how this PR works. Both pass
diff --git a/src/testdir/test_image.vim b/src/testdir/test_image.vim
index 62b455403..40c5bb855 100644
--- a/src/testdir/test_image.vim
+++ b/src/testdir/test_image.vim
@@ -198,6 +198,11 @@ let s:kitty_place = "\<Esc>_Ga=p,"
" a=d,d=i: delete the placement of an image, the terminal keeps the pixels.
let s:kitty_delete = "\<Esc>_Ga=d,d=i,"
+" The start of a sixel image, a DCS with the "q" command.
+let s:sixel_start_pat = "\<Esc>P[0-9;]*q"
+" The tab line of the second tab page, drawn after ":tabedit".
+let s:tabline = "[No Name] "
+
" The script for the Vim on the pty: create a popup with an image.
let s:image_popup_script =<< trim END
set imageprotocol=.*:kitty
@@ -206,6 +211,14 @@ let s:image_popup_script =<< trim END
redraw
END
+" The same with the sixel backend.
+let s:image_popup_script_sixel =<< trim END
+ set imageprotocol=.*:sixel
+ let img = repeat([0xff, 0, 0], 16 * 32)->list2blob()
+ call popup_create('', #{image: #{data: img, width: 16, height: 32}})
+ redraw
+END
+
" Collect what the Vim on the pty writes in s:pty_out.
func s:PtyOutput(job, msg)
let s:pty_out ..= a:msg
@@ -255,4 +268,74 @@ func Test_popup_image_kitty_leave_tabpage()
endtry
endfunc
+func Test_popup_image_kitty_covered()
+ CheckUnix
+ CheckFeature job
+ CheckFeature image_popup
+
+ let lines =<< trim END
+ set imageprotocol=.*:kitty
+ let img = repeat([0xff, 0, 0], 64 * 64)->list2blob()
+ call popup_create('', #{image: #{data: img, width: 64, height: 64},
+ \ line: 2, col: 2, zindex: 50})
+ let g:cover = popup_create(['xx', 'xx'], #{line: 3, col: 6, zindex: 100})
+ redraw
+ END
+ call writefile(lines, 'XpopupImageTab', 'D')
+ let job = s:StartVimWithImageOnPty()
+ try
+ call s:WaitForPtyOutput(',p=4,', 0)
+ " The row above the cover, the parts left and right of it, the row below.
+ for seq in [',p=1,x=0,y=0,w=64,h=16,', ',p=2,x=0,y=16,w=32,h=32,',
+ \ ',p=3,x=48,y=16,w=16,h=32,', ',p=4,x=0,y=48,w=64,h=16,']
+ call assert_notequal(-1, stridx(s:pty_out, seq), seq)
+ endfor
+
+ " Without the cover one placement is enough, the others are deleted. The
+ " screen may be drawn once more before that, with the four placements
+ " again: wait for the deletion of the last one.
+ let start = len(s:pty_out)
+ call ch_sendraw(job, ":call popup_close(g:cover)\<CR>")
+ call WaitForAssert({-> assert_match(s:kitty_delete .. 'i=\d\+,p=4,',
+ \ s:pty_out[start :])})
+ call assert_notequal(-1, stridx(s:pty_out, ',p=1,x=0,y=0,w=64,h=64,',
+ \ start))
+ for p in [2, 3, 4]
+ call assert_match(s:kitty_delete .. 'i=\d\+,p=' .. p .. ',',
+ \ s:pty_out[start :])
+ endfor
+ finally
+ call job_stop(job, 'kill')
+ call WaitForAssert({-> assert_equal('dead', job_status(job))})
+ endtry
+endfunc
+
+func Test_popup_image_sixel_leave_tabpage()
+ CheckUnix
+ CheckFeature job
+ CheckFeature image_popup
+
+ call writefile(s:image_popup_script_sixel, 'XpopupImageTab', 'D')
+ let job = s:StartVimWithImageOnPty()
+ try
+ call WaitForAssert({-> assert_match(s:sixel_start_pat, s:pty_out)})
+
+ " Sixel pixels cannot be deleted, the cells they cover are drawn over
+ " instead. Leaving the tab page does not draw the image again.
+ let start = len(s:pty_out)
+ call ch_sendraw(job, ":tabedit\<CR>")
+ call s:WaitForPtyOutput(s:tabline, start)
+ let mid = len(s:pty_out)
+ call assert_equal(-1, match(s:pty_out[start : mid], s:sixel_start_pat))
+
+ " Entering the tab page draws it again.
+ call ch_sendraw(job, ":tabnext\<CR>")
+ call WaitForAssert({-> assert_notequal(-1,
+ \ match(s:pty_out[mid :], s:sixel_start_pat))})
+ finally
+ call job_stop(job, 'kill')
+ call WaitForAssert({-> assert_equal('dead', job_status(job))})
+ endtry
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
|
|
Thank you I have applied your patches. I've also made it so that DECSDM is only sent when sixel backend is being used. |
|
Check the following CI errors. Linux / linux (normal, gcc, vimtags, proto, preproc_indent, encoding, codestyle) |
|
When displaying a popup image in GUI Vim (GTK3) and changing the window size, a SEGV or ASAN error occurs. (ADD:) diff --git a/src/image.c b/src/image.c
index 2e70c5b14..2247a455f 100644
--- a/src/image.c
+++ b/src/image.c
@@ -648,10 +648,16 @@ redraw_region(pixman_region32_t *region, bool now, bool restore)
if (now)
{
+ // The region may be from before a resize, when the screen was
+ // bigger.
+ int height = MIN(rect.y2, screen_Rows) - rect.y1;
+ int width = MIN(rect.x2, screen_Columns) - rect.x1;
+
// Tip: if you want to debug stale pixels appearing, set "inverse"
// of screen_draw_rectangle() to TRUE.
- screen_draw_rectangle(rect.y1, rect.x1,
- rect.y2 - rect.y1, rect.x2 - rect.x1, FALSE, TRUE);
+ if (height > 0 && width > 0)
+ screen_draw_rectangle(rect.y1, rect.x1, height, width,
+ FALSE, TRUE);
continue;
}
diff --git a/src/testdir/test_image.vim b/src/testdir/test_image.vim
index 40c5bb855..1feaaec88 100644
--- a/src/testdir/test_image.vim
+++ b/src/testdir/test_image.vim
@@ -338,4 +338,34 @@ func Test_popup_image_sixel_leave_tabpage()
endtry
endfunc
+func Test_popup_image_sixel_screen_shrinks()
+ CheckUnix
+ CheckFeature job
+ CheckFeature image_popup
+
+ " The image covers cells that are outside a smaller screen.
+ let lines =<< trim END
+ set imageprotocol=.*:sixel
+ let img = repeat([0xff, 0, 0], 400 * 400)->list2blob()
+ call popup_create('', #{image: #{data: img, width: 400, height: 400},
+ \ line: 3, col: 20})
+ redraw
+ END
+ call writefile(lines, 'XpopupImageTab', 'D')
+ let job = s:StartVimWithImageOnPty()
+ try
+ call WaitForAssert({-> assert_match(s:sixel_start_pat, s:pty_out)})
+
+ " Clearing the smaller screen must not look at the cells the image had
+ " on the bigger one.
+ let start = len(s:pty_out)
+ call ch_sendraw(job, ":set columns=40 lines=10\<CR>")
+ call WaitForAssert({-> assert_match(s:sixel_start_pat, s:pty_out[start :])})
+ call assert_equal('run', job_status(job))
+ finally
+ call job_stop(job, 'kill')
+ call WaitForAssert({-> assert_equal('dead', job_status(job))})
+ endtry
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab |
|
I have applied your patch, thanks |
|
thanks. Can you squash and fix the wrong preproc indentation? |
4e2381f to
0c510f0
Compare
Done |
|
Okay, this is a bit massiv to review. But one thing I noticed. The new dependency for pixman does not appear inside ./configure --help. Can you please add it there along with an optional switch to enable/disable it? |
|
I have included it now with a few more changes: I updated the configure script, I documented the new error codes, I updated optwin.vim, quickref.txt and version9.txt and the vim syntax script, I added those changes from 89c0f36 and re-generated the pot file. Thanks for working on that and image feature. BTW: I tried to reach you, but you did not reply, can you please contact me privately? |
Thank you
Sorry, I guess I missed your email (I am busy with university right now). I have responded back to your latest however |
Problem: Compiler warnings in image_sixel.c
(Tony Mechelynck, after v9.2.1161)
Solution: Make sixel_image8_crop() return success/error
status (Foxe Chen).
related: #21234
closes: #21436
Signed-off-by: Foxe Chen <[email protected]>
Signed-off-by: Christian Brabandt <[email protected]>





Refactor the popup image feature to fix various issues. I've separated the image logic from the popup window logic, which is more maintainable in my opinion. This should also allow for other possible uses of images (graphical icons in the statusbar?).
The only backwards incompatible changes is that
imageprotocoloption is used instead of detecting the terminal graphics protocol automatically (similar tokeyprotocol). Additionally, I have made all the+image_*feature flags into just+imageand+image_popup. The GTK2 GUI is not supported anymore, because while it's easy to implement support for it now, it may complicate new changes in the future.popup_create()now does not silently ignore invalid "image" dictionary.Improved the documentation as well, since currently its very technical with unnecessary information.
I used Claude to modify mattn's existing sixel code to work with the refactor and cleaned up its results.