Inline terminal image rendering — Kitty graphics protocol / iTerm2 inline images.
The pixel-free logic (capability detection, intrinsic dimension sniffing,
cell-box sizing, escape encoding, base64/PNG transcoding) now lives in the
lanterna fork's Java class
com.googlecode.lanterna.terminal.image.TerminalImage. This namespace is the
thin Clojure adapter that keeps vis's map-shaped API ({:images …},
{:w :h}, {:cols :rows}) plus the attachment-aware paste probe, which
reaches back into vis internals and so stays here.
The escape strings are emitted DIRECTLY to the tty AFTER Lanterna's delta refresh (the screen loop owns that), placed over rows the renderer reserved as blanks. Lanterna never sees the graphics bytes, so its cell diff stays intact and the image survives subsequent delta frames.
Inline terminal image rendering — Kitty graphics protocol / iTerm2 inline
images.
The pixel-free logic (capability detection, intrinsic dimension sniffing,
cell-box sizing, escape encoding, base64/PNG transcoding) now lives in the
lanterna fork's Java class
`com.googlecode.lanterna.terminal.image.TerminalImage`. This namespace is the
thin Clojure adapter that keeps vis's map-shaped API (`{:images …}`,
`{:w :h}`, `{:cols :rows}`) plus the attachment-aware paste probe, which
reaches back into vis internals and so stays here.
The escape strings are emitted DIRECTLY to the tty AFTER Lanterna's delta
refresh (the screen loop owns that), placed over rows the renderer
reserved as blanks. Lanterna never sees the graphics bytes, so its cell
diff stays intact and the image survives subsequent delta frames.(attachment-cache-dir)Durable cache dir (~/.vis/cache/tui-attachments) for persisted image
attachments, so a re-rendered history image survives its original (often
OS-temp) source path vanishing. Created on demand.
Durable cache dir (`~/.vis/cache/tui-attachments`) for persisted image attachments, so a re-rendered history image survives its original (often OS-temp) source path vanishing. Created on demand.
(cell-size {:keys [w h]} max-cols max-rows)Fit an image of {:w :h} px into max-cols × max-rows cells,
aspect-preserving. Returns {:cols :rows} (>= 1).
Fit an image of `{:w :h}` px into `max-cols` × `max-rows` cells,
aspect-preserving. Returns `{:cols :rows}` (>= 1).(detect-capabilities)(detect-capabilities env)Sniff which inline-image protocol the host terminal speaks from env
(defaults to the process environment). Returns {:images :kitty|:iterm2|nil}.
tmux and screen report nil — they don't reliably forward graphics.
Sniff which inline-image protocol the host terminal speaks from `env`
(defaults to the process environment). Returns `{:images :kitty|:iterm2|nil}`.
tmux and screen report `nil` — they don't reliably forward graphics.(encode-iterm2 data {:keys [cols]})iTerm2 \x1b]1337;File= inline-image sequence for base64 data.
iTerm2 `\x1b]1337;File=` inline-image sequence for base64 `data`.
(encode-kitty data {:keys [cols rows crop-top crop-bottom img-w img-h]})Kitty graphics \x1b_G transmit+display sequence for base64 data, sized to
cols×rows cells. C=1 keeps the cursor put after placement. When
crop-top/crop-bottom (cell rows scrolled past the band's top/bottom edge)
are positive, only the visible vertical slice is shown at native scale via the
Kitty source rectangle, sized from the transmitted image's img-w×img-h
pixel dimensions.
Kitty graphics `\x1b_G` transmit+display sequence for base64 `data`, sized to `cols`×`rows` cells. `C=1` keeps the cursor put after placement. When `crop-top`/`crop-bottom` (cell rows scrolled past the band's top/bottom edge) are positive, only the visible vertical slice is shown at native scale via the Kitty source rectangle, sized from the transmitted image's `img-w`×`img-h` pixel dimensions.
(graphical-terminal?)(graphical-terminal? env)Whether we're in a GRAPHICAL terminal — one that speaks an inline-image
protocol (kitty/Ghostty/WezTerm/Warp → Kitty, iTerm2 → iTerm2) — as opposed
to a plain/non-graphical terminal (or tmux/screen, which mangle graphics
pass-through). Callers branch on this to draw an inline image vs. a text-card
fallback. The 0-arity reuses the memoized capability detection; pass env to
probe an explicit environment (delegates to TerminalImage/isGraphicalTerminal).
Whether we're in a GRAPHICAL terminal — one that speaks an inline-image protocol (kitty/Ghostty/WezTerm/Warp → Kitty, iTerm2 → iTerm2) — as opposed to a plain/non-graphical terminal (or tmux/screen, which mangle graphics pass-through). Callers branch on this to draw an inline image vs. a text-card fallback. The 0-arity reuses the memoized capability detection; pass `env` to probe an explicit environment (delegates to `TerminalImage/isGraphicalTerminal`).
(image-dimensions b mime)Intrinsic {:w :h} pixel size from the leading bytes of an image, or nil.
Intrinsic `{:w :h}` pixel size from the leading bytes of an image, or nil.
(images-protocol):kitty, :iterm2, or nil for the current terminal.
`:kitty`, `:iterm2`, or nil for the current terminal.
(kitty-delete-placement id)Kitty sequence removing image id's placement while KEEPING its uploaded data,
so an image scrolled off screen leaves no ghost yet needs no re-upload if it
scrolls back into view.
Kitty sequence removing image `id`'s placement while KEEPING its uploaded data, so an image scrolled off screen leaves no ghost yet needs no re-upload if it scrolls back into view.
(kitty-free-image id)Kitty sequence deleting image id AND freeing its uploaded data — used when the
transmit cache evicts a long-off-screen image to bound terminal-side memory.
Kitty sequence deleting image `id` AND freeing its uploaded data — used when the transmit cache evicts a long-off-screen image to bound terminal-side memory.
(kitty-place {:keys [id cols rows crop-top crop-bottom img-w img-h]})Kitty a=p placement sequence for an ALREADY-transmitted image id at the
cursor: draw it into a cols×rows cell box, optionally cropped to the visible
vertical slice (crop-top/crop-bottom cell rows over an img-w×img-h px
image) via the protocol's source rectangle — the SAME x/y/w/h math the fork's
crop encodeKitty uses. Reusing placement id p=1 REPLACES the prior placement,
so a scroll moves the picture atomically: no delete-all, no re-upload, no flash.
Kitty `a=p` placement sequence for an ALREADY-transmitted image `id` at the cursor: draw it into a `cols`×`rows` cell box, optionally cropped to the visible vertical slice (`crop-top`/`crop-bottom` cell rows over an `img-w`×`img-h` px image) via the protocol's source rectangle — the SAME `x/y/w/h` math the fork's crop `encodeKitty` uses. Reusing placement id `p=1` REPLACES the prior placement, so a scroll moves the picture atomically: no delete-all, no re-upload, no flash.
(kitty-png path mime {:keys [width height] :as box})PNG base64 + transmitted pixel dims {:data :w :h} for the Kitty wire. A PNG
file rides through verbatim — transmitted at its intrinsic width×height
(and works in the native image too); anything else is transcoded via ImageIO.
PNG base64 + transmitted pixel dims `{:data :w :h}` for the Kitty wire. A PNG
file rides through verbatim — transmitted at its intrinsic `width`×`height`
(and works in the native image too); anything else is transcoded via ImageIO.(kitty-transmit data id)Kitty a=t transmit-ONLY sequence: upload base64 PNG data under client image
id id WITHOUT displaying it, chunked into m=1…m=0 pieces like the fork's
encodeKitty. A later kitty-place draws it with NO re-upload — the key to
flicker-free scrolling (transmit once, then re-place cheaply).
Kitty `a=t` transmit-ONLY sequence: upload base64 PNG `data` under client image id `id` WITHOUT displaying it, chunked into `m=1`…`m=0` pieces like the fork's `encodeKitty`. A later `kitty-place` draws it with NO re-upload — the key to flicker-free scrolling (transmit once, then re-place cheaply).
(materialize-attachment att)Decode ONE persisted user image attachment (canonical wire map, STRING keys
id/base64/media_type/filename) into a STABLE cache file keyed by its
row id and return the descriptor a vis-image fence needs -
{:path :mime :filename :size :size-label :width :height} - or nil when it is
not a usable still image. Idempotent: an already-written cache file is reused,
never rewritten, so a resumed session re-renders the picture from DB-owned
bytes even after the original source path is gone. Never throws.
Decode ONE persisted user image attachment (canonical wire map, STRING keys
`id`/`base64`/`media_type`/`filename`) into a STABLE cache file keyed by its
row id and return the descriptor a `vis-image` fence needs -
`{:path :mime :filename :size :size-label :width :height}` - or nil when it is
not a usable still image. Idempotent: an already-written cache file is reused,
never rewritten, so a resumed session re-renders the picture from DB-owned
bytes even after the original source path is gone. Never throws.(parse-cell-size-report s)Parse a terminal window-report reply into {:w :h} CELL pixel dimensions, or
nil. Recognises the CSI 16 t cell-size reply ESC[6;<h>;<w>t directly, and
falls back to deriving the cell size from a CSI 14 t text-area-pixels reply
ESC[4;<hpx>;<wpx>t paired with a CSI 18 t text-area-cells reply
ESC[8;<rows>;<cols>t (cell = px / cells). Tolerant of the replies arriving
concatenated in any order / interleaved with other bytes.
Parse a terminal window-report reply into `{:w :h}` CELL pixel dimensions, or
nil. Recognises the `CSI 16 t` cell-size reply `ESC[6;<h>;<w>t` directly, and
falls back to deriving the cell size from a `CSI 14 t` text-area-pixels reply
`ESC[4;<hpx>;<wpx>t` paired with a `CSI 18 t` text-area-cells reply
`ESC[8;<rows>;<cols>t` (cell = px / cells). Tolerant of the replies arriving
concatenated in any order / interleaved with other bytes.(probe-dimensions path mime)Read path's head and sniff its {:w :h} pixel dimensions. nil on failure.
Read `path`'s head and sniff its `{:w :h}` pixel dimensions. nil on failure.
(probe-paste-image text {:keys [workspace-root]})Detect the FIRST image the pasted text points at (a dropped file path).
Returns {:path :mime :filename :size :size-label :width :height} or nil.
workspace-root anchors relative candidates. Never throws.
Detect the FIRST image the pasted `text` points at (a dropped file path).
Returns `{:path :mime :filename :size :size-label :width :height}` or nil.
`workspace-root` anchors relative candidates. Never throws.(read-base64 path)Read path and base64-encode its bytes, or nil on failure. Cached by
path + mtime + size so an unchanged file is encoded at most once.
Read `path` and base64-encode its bytes, or nil on failure. Cached by path + mtime + size so an unchanged file is encoded at most once.
(render-sequence path mime box)Full escape sequence that draws the image at path (mime) into a
cols×rows cell box for the current terminal, or nil when the terminal
can't render inline images / the file can't be decoded. box may carry
:crop-top/:crop-bottom (cell rows scrolled past the band edge) plus the
image's intrinsic :width/:height, so a partly-scrolled Kitty image renders
its visible slice at native scale instead of vanishing.
Full escape sequence that draws the image at `path` (`mime`) into a `cols`×`rows` cell box for the current terminal, or nil when the terminal can't render inline images / the file can't be decoded. `box` may carry `:crop-top`/`:crop-bottom` (cell rows scrolled past the band edge) plus the image's intrinsic `:width`/`:height`, so a partly-scrolled Kitty image renders its visible slice at native scale instead of vanishing.
(transcode->png-base64 path {:keys [cols rows]})Decode path (any ImageIO-readable format) and re-encode it as a PNG
base64 string, downscaled so it fits the cols×rows cell box in pixels.
The Kitty protocol's f=100 only accepts PNG, so a JPEG/GIF/BMP drop must
pass through here first. Uses AWT/ImageIO (JVM-only — unavailable in the
macOS native image); returns nil on any failure so callers fall back to a
text card.
Decode `path` (any ImageIO-readable format) and re-encode it as a PNG base64 string, downscaled so it fits the `cols`×`rows` cell box in pixels. The Kitty protocol's `f=100` only accepts PNG, so a JPEG/GIF/BMP drop must pass through here first. Uses AWT/ImageIO (JVM-only — unavailable in the macOS native image); returns nil on any failure so callers fall back to a text card.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |