viuer
Rust library for displaying images directly in the terminal using Kitty, iTerm, or Sixel graphics protocols, with a half-block fallback everywhere else.
Repository Health
Technical Analysis
viuer is a Rust crate that renders images directly in a terminal window. Extracted from the viu CLI tool, it exposes a small, configurable API — a single Config struct and print/print_from_file functions — that takes an image::DynamicImage and writes it to stdout using whichever graphics capability the terminal actually supports.
Rather than committing to one rendering strategy, viuer probes the terminal’s capabilities at runtime and falls back gracefully: it tries the Kitty graphics protocol first, then iTerm’s inline images protocol, then Sixel (behind an optional feature flag), and finally drops to Unicode half-block characters with truecolor ANSI codes when no richer protocol is available. This makes it usable across a wide range of terminal emulators without the caller having to detect or configure anything.
What You Get
- A
print(img, &config)function that renders animage::DynamicImageto stdout using the best available terminal graphics protocol - Runtime detection of Kitty, iTerm, and Sixel graphics protocol support, with automatic fallback to a half-block renderer
- A
Configstruct covering offset, sizing, transparency, and protocol preference, usable via..Default::default() - Aspect-ratio-aware resizing helpers (
resize,find_best_fit) that fit an image to explicit or terminal-derived cell dimensions - An optional
print-filefeature addingprint_from_fileto decode and print image files directly without pulling in extra format dependencies by default
Common Use Cases
- Building CLI image previewers, like the
viutool this crate was extracted from - Adding inline image previews to terminal file managers or pagers
- Rendering generated charts, diffs, or screenshots inline in dev tooling and terminal dashboards
- Displaying received image attachments inline in terminal-based chat clients
Under The Hood
Architecture
viuer is organized around a Printer trait (src/printer/mod.rs) implemented by four backends — BlockPrinter, KittyPrinter, iTermPrinter, and the optional SixelPrinter/IcySixelPrinter — selected at runtime by choose_printer() in lib.rs based on Config flags and terminal capability probes (get_kitty_support, is_iterm_supported, is_sixel_supported). The public entry points print()/print_from_file() wrap the chosen printer and optionally save/restore cursor position via crossterm. The Printer trait’s default print_from_file method centralizes file decoding through the image crate so only print needs a new implementation per backend. Terminal capability probing is abstracted behind a ReadKey trait with a TestKeys mock used in tests, decoupling protocol-detection logic — which reads raw terminal escape-sequence responses — from real stdin/stdout so it stays testable without an actual terminal. Data flow is a straightforward pipeline: image plus config in, printer selection, per-backend encode/resize, raw bytes to stdout. A change to the core Printer trait signature would ripple through all backend implementations and the PrinterType dispatch match arm, but the backends themselves are fully isolated from one another.
Tech Stack
A Rust crate (edition 2021, MSRV 1.80) with no async runtime. Core dependencies are image 0.25 (default features disabled, PNG-only unless print-file is enabled) for decoding, crossterm 0.29 for cross-platform cursor control, console 0.16 for terminal capability queries, ansi_colours and termcolor for truecolor ANSI escape generation in the block printer, base64 for encoding Kitty protocol payloads, and tempfile for iTerm’s file-based image transfer path. Two optional, mutually exclusive-by-platform Sixel backends are feature-gated: icy_sixel (pure Rust, cross-platform) and sixel-rs (native bindings, Unix-only). CI runs cargo fmt --check, cargo clippy -D warnings, and cargo test across Windows, macOS, and Ubuntu, plus a weekly cargo-audit workflow for dependency vulnerabilities. There is no database, network layer, or deployment target — it’s a library published to crates.io.
Code Quality
An extensive set of unit tests is concentrated in src/printer/mod.rs, covering the resize/best-fit sizing math and cursor-offset escape-sequence generation, with additional coverage inside individual printer modules — kitty.rs is the largest file in the crate, reflecting where most of the protocol complexity and test surface lives. Error handling is explicit and typed through a single ViuError enum (src/error.rs) with From impls bridging std::io::Error, image::ImageError, and tempfile::PersistError into the crate’s ViuResult alias, avoiding stringly-typed or swallowed errors. #![deny(missing_docs)] forces every public item to carry documentation, and many doc comments include runnable or no_run examples that double as doctests. CI enforces formatting and lint checks on every pull request across three operating systems. Naming is consistent, idiomatic Rust throughout.
API Design
The public surface is intentionally minimal — one Config struct used with ..Default::default(), and two functions, print and print_from_file — so a caller can go from having a DynamicImage to an image on screen in two lines with zero required configuration. Protocol selection, capability detection, and resizing all happen automatically inside choose_printer, so the default path needs no protocol-specific knowledge, while power users can still override individual behaviors (force or disable Kitty/iTerm/Sixel, set explicit offsets, toggle transparency) through the same struct. The print-file feature is deliberately opt-in rather than default, trading a little convenience for a lighter default dependency footprint — a reasonable tradeoff for a library meant to be embedded inside other CLI tools. The one friction point is that use_sixel is a cfg-gated struct field that only exists when a sixel feature is enabled, which can surface confusing compiler errors for users who reach for it without turning the feature on.