GitHub

Rust License Platform Dependencies Stay Amazing

Display images inline in the terminal using the kitty graphics protocol, sixel, or w3m — and where none of those exist, draw the picture out of text. Auto-detects the best route for your terminal. Feature clone of termpix.

Used by pointer for file preview images.

Quick Start

[dependencies]
glow = { version = "0.1", path = "../glow" }
use glow::Display;
let mut display = Display::new();  // Auto-detects protocol
if display.supported() {
    display.show("photo.png", 10, 5, 60, 30);  // x, y, max_w, max_h
    // ... later ...
    display.clear(10, 5, 60, 30, 80, 24);      // Clear region
}

Supported Protocols

Protocol Terminals Detection
Kitty kitty, WezTerm TERM=xterm-kitty, KITTY_WINDOW_ID, TERM_PROGRAM=WezTerm
Sixel xterm, mlterm, foot TERM starts with xterm/mlterm/foot
W3m Any X11 terminal /usr/lib/w3m/w3mimgdisplay exists
HalfBlock anything with 24-bit colour COLORTERM says truecolor
Chafa anything, if installed chafa on PATH
Braille anything with a Unicode font last resort with colour
Ascii the Linux console TERM=linux

Display::with_mode("halfblock" | "braille" | "chafa" | "ascii" | "kitty" | "sixel" | "auto" | "off") forces one.

Drawing a picture out of text

When no graphics protocol is available, glow draws the image itself. No subprocess, no external tool: the picture is decoded, scaled and written in the same process.

Half blocks are the default. A cell gets , the foreground colour painting the top half and the background the bottom, so every cell carries two full-colour pixels. Since a cell is about twice as tall as it is wide, both come out square. On a photograph this is close to what a graphics protocol gives you.

Braille packs 2×4 dots into a cell, at one colour for all eight. Which dots light is decided by an ordered dither, so the density of ink inside a cell tracks the brightness there — a fixed threshold loses every mid-tone, leaving a bright photo empty and a dark one solid.

Scaling happens in linear light. Averaging sRGB bytes directly makes every downscale come out darker than the original: half black and half white gives 128 that way, where the honest answer is 188. glow converts to linear, box-averages the source rectangle each output pixel covers, and converts back.

Try them side by side:

cargo run --release --example show -- halfblock photo.jpg 100 34
cargo run --release --example show -- braille   photo.jpg 100 34

Or all three renderings at once, in three panels: the real pixels through the kitty protocol, glow's half blocks, and chafa for comparison. Each panel is timed.

cargo run --release --example imgcompare -- photo.jpg

On a 4000×3000 photograph in a 1920×1200 window, that comes out at 27 ms for the kitty protocol (with the scaled image already cached), 221 ms for half blocks, and 358 ms for chafa.

API

pub struct Display { ... }
impl Display {
    pub fn new() -> Self;                    // Auto-detect protocol
    pub fn supported(&self) -> bool;         // Check if display works
    pub fn protocol(&self) -> Option<Protocol>; // Which protocol
    pub fn show(&mut self,
        image_path: &str,                    // Path to image file
        x: u16, y: u16,                     // Character position
        max_width: u16, max_height: u16     // Max size in chars
    ) -> bool;                               // Success
    pub fn clear(&mut self,
        x: u16, y: u16,                     // Region position
        width: u16, height: u16,            // Region size
        term_width: u16, term_height: u16   // Terminal size
    );
}

How It Works

  • Kitty: Scales image with ImageMagick convert, base64 encodes, transmits in 4KB chunks via escape sequences. Caches processed images by path+dimensions+mtime.
  • Sixel: Uses convert to generate sixel output directly.
  • W3m: Calculates pixel coordinates from cell size, communicates with w3mimgdisplay.
  • HalfBlock / Braille: decoded and scaled in-process by the image crate, with ImageMagick only as a fallback for formats it cannot read (HEIC, SVG, odd CMYK JPEGs).

Runtime Requirements

  • ImageMagick (convert) - required for kitty and sixel protocols
  • w3mimgdisplay - required for w3m protocol only
  • xdotool + xwininfo - required for w3m protocol only
  • The text renderers need nothing at all for PNG / JPEG / GIF / WebP / BMP / TIFF

Part of the Fe2O3 Rust Terminal Suite

See the Fe₂O₃ suite overview and the landing page for the full list of projects.

Tool Clones Type
rush rsh Shell
crust rcurses TUI library
glow termpix Image display
plot termchart Charts
pointer RTFM File manager

License

Unlicense - public domain.

Credits

Created by Geir Isene (https://isene.org) with extensive pair-programming with Claude Code.

Read the original on github.com ↗