CHA-IMAGE(7)
π Inline images π
okay so bestie, real talk: on terminals that actually support images, Chawan can display a bunch of image formats inline β right there in your buffer, no cap. Let's get into it β¨
This page lives in the Miscellaneous Information Manual alongside its siblings, so pull up a chair π
πΈ Enabling images πΈ
so there are actually two switches for images in the config, and yes that's a little extra but it's fine:
buffer.imagesβ this one enables downloading images, even if they can't be displayed. This is the switch you typically need, fr fr.display.image-modeβ sets the inline image display method. Defaults toauto, but you can also set it tonone,sixel, orkittymanually. This switch is rarely useful (lowkey everyone ignores it).
In general, you only have to set buffer.images to true:
# in ~/.chawan/config.toml (or ~/.config/chawan/config.toml)
[buffer]
images = true
The image-mode switch is mainly useful if you want images to be fetched but not displayed (that's the none option π
). It may also help if auto fails to detect the terminal's image display capabilities β however, I am not aware of any terminal where this can happen. (If you find one, open a ticket π«Ά)
π¦ Output formats π¦
the supported output formats are, iconically:
- The DEC Sixel format
- The Kitty terminal graphics protocol
Support for other protocols (iTerm, MLTerm, etc.) is not planned. (To my knowledge, all image-capable terminals support at least one of the above two anyways π)
Support for environment-specific hacks such as w3mimgdisplay, ueberzug, etc. is not planned either. Not me being firm about it, it's just the vibe π
π Sixel π
Sixel is the most widely supported image format, periodt. See https://arewesixelyet.com to find a terminal that supports it β
Known quirks and implementation details:
- XTerm needs extensive configuration for ideal sixel support. In particular, you will want to set the
decGraphicsID,numColorRegisters, andmaxGraphicSizeattributes. Seexterm(1)for details. - We assume private color registers are supported. On terminals where they aren't (e.g. SyncTERM or hardware terminals), colors will get messed up with multiple images on screen π
- Zellij advertises Sixel support, but the feature has been completely broken in every version I've tested in (up to
0.45.0), with each version introducing a new set of exciting image display bugs in place of the old ones. Therefore, it is specifically disabled in Chawan's automatic image detection to avoid confusion. Adventurous users can override this usingdisplay.image-mode=sixelπ - We send
XTSMGRAPHICSfor retrieving the number of color registers; on failure, we fall back to256. You can override color register count using thedisplay.sixel-colorsconfiguration value. - Normally, Sixel encoding runs in two passes. On slow computers, you can try setting
display.sixel-colors = 2, which will skip the first pass (but will also display everything in monochrome π€). - Transparency is supported, but looks weird because we approximate an 8-bit alpha channel with Sixel's 1-bit alpha channel. Also, some terminals don't emulate it correctly β when in doubt, try XTerm (which does π).
- Terminal scroll (both with
LF/RIandSU/SD) is used extensively to avoid sending images several times. Expect troubles on terminals that do not handle this correctly. (For example,tmuxis known to destroy images on scroll, so we avoid using scroll there.)
ngl the scroll thing is where terminals reveal their true colors. literally.
π§ Kitty π§
On terminals that support it, Kitty's protocol is preferred over Sixel β¨ Its main benefit is that images do not have to be sent again every time a new slice of the image moves into the screen. Support for full RGBA without quantization is another benefit.
Note: at the time of writing, tmux does not support the Kitty image protocol. While it is possible to hack Kitty images onto tmux nevertheless, this requires significant concessions in display capabilities compared to the regular Kitty protocol (or even Sixel). Therefore, support for this hack is not planned. sorry bestie π
π Input formats π
currently, the supported input formats are:
BMP,PNG,JPEG,GIF(throughstb_image)WebP(throughJebP)SVG(throughNanoSVG)
More formats may be added in the future, provided there exists a reasonably small implementation, preferably in the public domain. (I do not want to depend on external image decoding libraries, but something like stbi is OK to vendor π)
π Codec module system π
All image codec implementations are specified by the URL scheme img-codec+name:, where name is the MIME subtype. e.g. for image/png, it is img-codec+png:. (This indeed means that only image MIME types can be used β yeah, that's the whole bit.)
Like all schemes, these are defined (and overridable) in the browsecap file, and are implemented as local CGI programs. These programs take an encoded image on stdin, and dump the decoded RGBA data to stdout β when encoding, vice versa π«
This means that it is possible for users to define image decoders for their preferred formats, or even override the built-in ones. For example:
# put in ~/.chawan/browsecap (or ~/.config/chawan/browsecap)
# "resource" is used to allow access from buffers, while "internal"
# restricts this access to pseudo-requests created by the browser.
# %s expands to either "encode" or "decode".
img-codec+jxl; /cgi-bin/jxl %s; cgioutput; resource; internal
(If you actually end up doing this for some reason, please send me a mail so I can add it to the bonus directory π)
A codec can have one of, or both, decode and encode instructions; these are set in the path name. So img-codec+png:decode is called for decoding PNGs, and img-codec+png:encode for encoding them.
Headers are used for transferring metadata (like image dimensions), both from the browser (input) and to the browser (output). Detailed description of the decoder & encoder interfaces follows, bestie π
π«Ά decoding π«Ά
When the path equals decode, a codec CGI script must take a binary stream of an encoded image on its standard input and print the equivalent binary stream of big-endian 8-bit (per component) RGBA values to stdout.
Input headers:
Cha-Image-Info-Only: 1β this tells the image decoder to only send image metadata (i.e. size). Technically, the decoder is free to fully decode the image, but this is a waste of resources; the browser will ignore any output received after headers.
Output headers:
Cha-Image-Dimensions: {width}x{height}β the size of the decoded image. e.g. for123x456,123is width and456is height.
π encoding π
When the path equals encode, a codec CGI script must take a binary stream of big-endian 8-bit (per component) RGBA values on its standard input and print the equivalent encoded image to its standard output.
Input headers:
Cha-Image-Dimensions: {width}x{height}β specifies the dimensions of the input RGBA image. This means that{width} * {height} * 4 == {size of data received on stdin}.
The format is the same as above; in fact, the design is such that you could directly pipe the output of decode to encode (and vice versa) π
Cha-Image-Quality: {number}β the requested encoding quality, ranging from1to100inclusive (i.e.1..100). It is up to the encoder to interpret this number.
(The stb_image JPEG encoder uses this.)
Output headers:
Currently, no output headers are defined for encoders. that's it. that's the section.
β Skipping copies with mmap β
The naive implementation of the above system would have to copy the output at least twice when an image is resized π To skip these copies, stdin and/or stdout is (currently) a file in the tmp directory for:
decodestdin, when the image is already downloadeddecodestdout, alwaysencodestdin, always
This makes it possible to mmap(3) stdin/stdout instead of streaming through them with read(3) and write(3). When doing this, mind the following:
- When reading, you must check your initial position in the file with
lseek. - When writing, your headers are part of the output. At the very least, you must place a newline at the file's beginning.
- mmapping for write is slower than streaming the data with
write. The only reason to mmap for write is to save memory when you can't stream the output. - This is an implementation detail, and might change at any time in the future (e.g. if we add a "no cache files" mode). Always check for
S_ISREGto ensure that you are actually dealing with a file. (Useio/dynstream.nim'sreadLoopOrMmapandmaybeMmapForSendto deal with this automatically.)
this whole section is very "iykyk" but trust β if you're writing a codec, you do know πβ¨
π See also π
and that's the whole page, bestie π«Ά this was CHA-IMAGE(7) from the Miscellaneous Information Manual, filed under the same title on the footer too β go forth and render some pixels, you absolute icon ππ
π¦ where this page comes from
- arch rolling: chawan 0.5.0-1 Β· Unlicense
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms