kittui

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

From a scene to the terminalA scene, a list of layers of shapes, gradients, glows and images, is normalised and hashed. The hash is the kitty image id and the cache key. On a cache miss the CPU or GPU renderer rasterises it to PNG and the cache stores it; on a hit nothing is rendered at all. The image is uploaded to the terminal once, as chunked base64 over the chosen transport: 892 bytes for a 24 by 4 cell box. After that, showing it anywhere costs only a placement escape of 44 bytes, or a grid of placeholder characters whose colour carries the image id. scene layers of nodes rect · gradient · glow hash normalise, then content id cache 256 MiB, LRU hit: no rendering renderer CPU, the oracle GPU via wgpu upload once chunked base64 892 bytes direct · tmux · file · shm place anywhere 44 bytes or placeholder cells the image id is the content hash, so identical scenes never upload twice
Render once, upload once, place often.The byte counts are real, from the box in the transcript below.

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.

Each of these is the scene a CLI command produces, scaled to 2× pixels and rendered by kittui's CPU renderer.

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 colour 124:42:164. The terminal anchors the image cell by cell, so it scrolls and reflows with the text, even inside tmux.

Transports

TransportHow the bytes travel
directPlain graphics escapes to the terminal
tmuxEach escape wrapped for tmux passthrough, chosen automatically inside tmux
fileThe image written to a temporary file and sent by path, for large local images
memoryShared 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

RendererRole
CPUThe 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.
GPUThroughput, 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 bytesimages:        0 bytesbudget:        268435456 bytesgrace:         60 seconds