renderer
Scenes, pixels and escape codes
The kitty graphics protocol lets a terminal show images at cell positions. kittui turns descriptions of interface graphics into those images, and works hard to send each one as few times, and as few bytes, as possible.
The pipeline
Scenes
A scene is a cell footprint, a cell size in pixels, and a stack of layers. Each layer holds a node: a rectangle with fill, stroke and per-corner radii, a gradient, a glow, an image, or a composite of others. Every CLI command builds one, and --scene-json prints it instead of drawing:
kittui box -w 24 -h 4 --fg "#ff5d8f" --bg "#1a1030cc" --radius 10 --border 2 --scene-json{ "footprint": { "x": 0, "y": 0, "cols": 24, "rows": 4 }, "cell_size": { "width_px": 8, "height_px": 16 }, "layers": [ { "label": "background", "root": { "kind": "rect", … } }, { "label": "rounded_rect", "root": { "kind": "rect", "fill": { "kind": "solid", "color": [26, 16, 48, 204] }, "stroke": { "align": "inside", "width_px": 2.0, … }, "corners": { "tl": 10.0, "tr": 10.0, "bl": 10.0, "br": 10.0 } } } ]}
Scenes are plain data, so they can be composed, diffed, saved and rendered anywhere, including to a PNG with no terminal at all, through kittui render.
Gallery
Each of these is the scene a CLI command produces, scaled to 2× pixels and rendered by kittui's CPU renderer.

box --radius 12 --border 2
glow --intensity 0.8
gradient --direction diagonal
panel --tone tool
wm-chrome --focused
the three panel tones
On the wire
Ask for JSON instead of escapes and the CLI reports exactly what it would send:
kittui --transport direct box -w 24 -h 4 --fg "#ff5d8f" --bg "#1a1030cc" --radius 10 --border 2 --json "embed_bytes": 1152, "footprint": { "cols": 24, "rows": 4, "x": 0, "y": 0 }, "image_id": "0xc77c2aa4", "placement_bytes": 44, "upload_bytes": 892
- Upload
- The PNG, base64-encoded and split into 4 KiB chunks to fit the protocol's escape limits; the first chunk carries the action, format and image id.
- Placement
- A short escape that shows an uploaded image at a cell footprint. Moving a cached scene is a new placement, never a new upload.
- Placeholders
- A grid of the private-use character U+10EEEE whose foreground colour encodes the image id; this box's id ends in
7c2aa4, printed as colour124:42:164. The terminal anchors the image cell by cell, so it scrolls and reflows with the text, even inside tmux.
Transports
| Transport | How the bytes travel |
|---|---|
direct | Plain graphics escapes to the terminal |
tmux | Each escape wrapped for tmux passthrough, chosen automatically inside tmux |
file | The image written to a temporary file and sent by path, for large local images |
memory | Shared memory on Linux and macOS, falling back to a file on Windows |
The transport is detected from the terminal environment and tmux, or set with --transport. Raw window frames from kittwm are compressed with zlib by default; KITTUI_KITTY_COMPRESSION=auto compresses only above a size threshold, and none turns it off. kittwm currently replaces whole frames when a captured window changes. It tracks which regions changed, but sub-rectangle updates wait until a delta path is proven safe across terminals.
Animation without traffic
Animated scenes, such as a pulsing glow, are rendered into frames once, uploaded once and handed to the terminal's own animation loop. After that, the terminal plays them with no further bytes, even after the kittui process has exited. The renderer checks that the last frame meets the first, so loops are seamless by construction. Inline prompt and status-line elements, such as kittui inline chip, badge and segment, can animate the same way.
Two renderers
| Renderer | Role |
|---|---|
| CPU | The reference: one sample per pixel, pure shading functions, no allocations in the inner loop, no unsafe code, no OS calls. The default for the C ABI, because it cannot fail to initialise. |
| GPU | Throughput, through wgpu: one render pass per scene, one signed-distance pipeline per shape family, instanced draws. Scenes it cannot yet draw, such as images and masks, fall back to the CPU. |
On first use the GPU renders a reference scene and is compared with the CPU result; below the similarity threshold the GPU is not used on that machine. --renderer cpu|gpu|auto overrides the choice.
The cache
Rendered scenes live in a content-addressed cache, keyed by the same hash as the image id:
kittui cache inforoot: ~/.cache/kittuiscenes: 1 entries, 903 bytes images: 0 bytesbudget: 268435456 bytesgrace: 60 seconds
- Reads take no lock. Writes go to a temporary file and are renamed into place, so a partial entry is never visible.
- Processes cooperate through per-entry advisory locks: a writer that waited checks whether someone else already produced the entry.
- Eviction is least-recently-used within a 256 MiB budget (
KITTUI_CACHE_BUDGET). Entries being written, or younger than 60 seconds, are never evicted. kittui cache gcforces an eviction pass;kittui cache clearempties it.