-
-
Notifications
You must be signed in to change notification settings - Fork 6.1k
feat(img): implement image API for absolute positions #31399
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
All reactions
-
👍 2 reactions -
🎉 16 reactions -
❤️ 6 reactions -
🚀 23 reactions -
👀 4 reactions
Heads up, I know there is formatting of commit messages needed and linting for preferences in Lua style guides. The current code is me migrating over my working code from a private repo - not a fork of neovim - to be a pull request here. I'll work on updating the PR to be compliant, but wanted the code to be visible for comments. In particular, I could use help in rewriting that parts of the PR that make use of Lua's io library - assuming we want to use a neovim equivalent - and to refactor parts of the code that could be improved. So looking for stronger critique, challenges, and suggestions 😄 This was an example-turned-PR, so not all of the code is high quality! An example of doing this with the current PR: local file = vim.img.load({
filename = "/Users/senkwich/Pictures/org-roam-logo.png",
})
vim.img.show(file, {
pos = { row = 8, col = 8 },
backend = "iterm2",
}) |
All reactions
-
🚀 5 reactions
Sorry, something went wrong.
9964ad6
to
630f852
Compare
vim.img.protocol()
to detect preferred graphics protocol
vim.img.protocol()
to detect preferred graphics protocolfe0d5a8
to
33ee581
Compare
babd349
to
dc51bf3
Compare
@justinmk heads up, one complexity that we'll punt for now is supporting non-PNG images. I think we can write a pretty straightforward decoder for BMP & GIF, but JPEG is very complex and would /probably/ need a specialized C function to do it with the assistance of a JPEG-oriented library. This is in order to get RGB or RGBA data. @kovidgoyal I'm assuming my understanding of pixel formats is correct in that if we fed in any other image format that was not PNG, using I don't know what iterm2's graphics protocol supports as I've only tested with png and I don't see anything mentioned on their doc page. I also don't know what sixel supports or how it works since I haven't read the documentation yet, but I imagine given the age of sixel that we'd need to support image decoding of some kind to break out rgb/rgba data. |
All reactions
-
👍 1 reaction
Sorry, something went wrong.
On Sat, Nov 30, 2024 at 01:29:25PM -0800, Chip Senkbeil wrote:
@justinmk heads up, one complexity that we'll punt for now is supporting non-PNG images. I think we can write a pretty straightforward decoder for BMP & GIF, but JPEG is very complex and would /probably/ need a specialized C function to do it with the assistance of a JPEG-oriented library. This is in order to get RGB or RGBA data.
@kovidgoyal I'm assuming my understanding of pixel formats is correct in that if we fed in any other image format that was not PNG, using `f=100` would not work, and we'd need to instead decode the base64 image data, figure out the format (i.e. bmp, jpeg, etc) and then extract a 24-bit RGB or a 32-bit RGBA set of data to feed in order for your protocol to work.
Yes, correct. You can use either imagemagick or the statically compiled
kitten binary that comes as part of kitty to do this.
I don't know what iterm2's graphics protocol supports as I've only tested with png and I don't see anything mentioned on their doc page. I also don't know what sixel supports or how it works since I haven't read the documentation yet, but I imagine given the age of sixel that we'd need to support image decoding of some kind to break out rgb/rgba data.
sixel supports nothing, you need to convert every image format to the
sixel format and transmit that.
|
All reactions
Sorry, something went wrong.
dc51bf3
to
ce818a9
Compare
Commenting here that I'm fine and most likely moving forward with removing iterm2 and just using kitty. I was already aware of limitations in iterm2 with cropping. My first thought is to farm externally to a process like image magick to crop, which I "think" can be done without creating a temporary image. So if we ever revisit supporting iterm2, that would be the approach I'd take. |
All reactions
-
👍 1 reaction
Sorry, something went wrong.
Just FYI, on terminals with level 4 capabilities, you can crop an image by rendering it to an offscreen page, and then copying the relevant segments back to the main page. This can also serve as a way to cache images to a certain extent. I'm not sure about the iterm image protocol, but I do know this works with Sixel. The only catch is that DEC pages may not interoperate very well with the Xterm alt buffer mode, assuming that's a requirement. |
All reactions
Sorry, something went wrong.
…query logic for now, default to kitty provider
@gpanders I figured out why If you switch to local filesystem access via a file transfer (not escape codes), then it works fine to use |
All reactions
Sorry, something went wrong.
…ursor logic to kitty provider, and remove unneeded terminal helpers by switching to io.stdout:write()
@justinmk @fredizzimo I've rewritten the provider interface and implemented basic kitty graphics logic to hide an image. This provides a bit of an abstraction between the image (the data) and the placement by having two separate ids. Whenever you show an image, the provider is expected to generate some id that can be passed back to it later to hide/remove the image. Thoughts? @gpanders I've been able to fully remove the terminal helper code and just use Still got some open questions in this code at this point, but ready for another skim to get thoughts on this one. -- Load the image from disk. We assume all images are loaded from disk right now, and are PNGs
local img = vim.ui.img.load("/Users/senkwich/projects/neovim-img-test/org-roam-logo.png")
-- Calls the underlying provider (kitty) to show the image, returning an id that can hide it later
local id = img:show({
pos = { x = 8, y = 8 },
provider = "kitty",
})
-- For the test, as soon as any key is pressed, the image is hidden
vim.on_key(function()
img:hide(id)
end)
example-of-deleting-image.mp4 |
All reactions
-
👍 2 reactions
Sorry, something went wrong.
d6993c7
to
5584c0b
Compare
…support registering new providers
I added in some additional options as an experiment to mirror a bit of what it looks like the floating window api can do when it comes to the relative position of the image, now supporting Here's a silly preview of an image being displayed where the mouse is presently, and then on move it hides the only image (in kitty, by deleting the placement) and then showing a new image where the cursor is. Seems fairly quick, which is nice. The reason I did this was to potentially set up what the config might look like to set relative to a buffer, which would then rely on something like the kitty implementation using the unicode placement functionality. neovim-image-mouse-move.mp4 |
All reactions
-
❤️ 1 reaction
Sorry, something went wrong.
…ting an image; fix placement not being cleared from cache in kitty provider
…we need ffi, ioctl, and TIOCGWINSZ
@gpanders does Reason I ask is that one issue popped up with trying to support converting between pixel and cell units, and that's getting the screen size in pixels. I was trying to do this via I'm assuming it's filtered out as Line 577 in 0862c10
FFI alternative@justinmk the alternative way I've seen this done is using function M.size()
if size then
return size
end
local ffi = require("ffi")
ffi.cdef([[
typedef struct {
unsigned short row;
unsigned short col;
unsigned short xpixel;
unsigned short ypixel;
} winsize;
int ioctl(int, int, ...);
]])
local TIOCGWINSZ = nil
if vim.fn.has("linux") == 1 then
TIOCGWINSZ = 0x5413
elseif vim.fn.has("mac") == 1 or vim.fn.has("bsd") == 1 then
TIOCGWINSZ = 0x40087468
end
local dw, dh = 9, 18
---@class snacks.image.terminal.Dim
size = {
width = vim.o.columns * dw,
height = vim.o.lines * dh,
columns = vim.o.columns,
rows = vim.o.lines,
cell_width = dw,
cell_height = dh,
scale = dw / 8,
}
pcall(function()
---@type { row: number, col: number, xpixel: number, ypixel: number }
local sz = ffi.new("winsize")
if ffi.C.ioctl(1, TIOCGWINSZ, sz) ~= 0 or sz.col == 0 or sz.row == 0 then
return
end
size = {
width = sz.xpixel,
height = sz.ypixel,
columns = sz.col,
rows = sz.row,
cell_width = sz.xpixel / sz.col,
cell_height = sz.ypixel / sz.row,
-- try to guess dpi scale
scale = math.max(1, sz.xpixel / sz.col / 8),
}
end)
return size
end |
All reactions
Sorry, something went wrong.
justinmk
lewis6991
gpanders
ribru17
Successfully merging this pull request may close these issues.
None yet
Task List
Out of Scope for this PR
OLDER INFORMATION
Alright, let's try this again without the massive amount of pull requests. 😄 Each commit here should be a standalone change, and I'll document the processes here.
This is geared towards tackling #30889, specifically on supporting
Things for later PRs would include
Breakdown of commits
1. Loading an image into memory
Implements
vim.img.load()
to load from a file or wrap base64 encoded bytes as avim.img.Image
instance.2. Implement skeleton of vim.img.show() without backends
Implements the skeleton of
vim.img.show()
with any backend implemented.3. Implement vim.img._terminal to support basic functionality needed for backends
Implements a
vim.img._terminal
module that supports writing to the tty tied to neovim as well as basic operations to manipulate the cursor, needed for backend implementations.4. Implement
vim.img.Image
methodfor_each_chunk
to streamline backend processingImplements a method
image:for_each_chunk
for instances ofvim.img.Image
. This method streamlines chunked iteration of image bytes, which is important when working with ssh or tmux and a protocol that supports chunked image rendering such asiterm2
orkitty
.5. Implement iterm2 backend
Implements the iterm2 backend, supporting both iTerm 3.5+ support for multipart images, and falling back to older protocol that sends the entire image at once, which is needed for support on other terminals such as WezTerm.
6. Implement kitty backend
Implements the kitty graphics protocol as a backend, using kitty's chunked image rendering, which should work within tmux and ssh if we keep the chunks small enough.
7. Implement
vim.img.protocol()
to detect preferred graphics protocolImplements
vim.img.protocol()
that can be used to detect the preferred graphics protocol.This is a reverse-engineered copy of how
timg
implements graphics protocol support, and relies on a couple of terminal queries, hence we implementvim.img._terminal.query()
andvim.img._terminal.graphics.detect()
to support figuring out if the terminal supports iterm2, kitty, or sixel protocols and mirrors the logic fromtimg
.