chafa — image → terminal graphics (image-engine library shortlist candidate)
Batch 8 — rendering / screensaver / ambient-display cluster. The strongest library-adoption candidate in the corpus for KN-86 image rendering.
What it is
Section titled “What it is”chafa converts image data — including animated GIFs — into terminal graphics. It targets everything “from historical teleprinters to modern terminal emulators,” choosing between pixel-graphics protocols (sixel, Kitty, iTerm2) when the terminal supports them and falling back to character-cell approximation (“ANSI/Unicode art”) when it doesn’t. It ships as the chafa CLI and, critically, as libchafa — a public, well-documented, stable C API (the CLI is a thin wrapper over the library). Bindings exist for Python and for JavaScript via WebAssembly.
KN-86 cannot use sixel/Kitty/iTerm2 — those are terminal-emulator escape protocols; the deck renders its own cell grid. The relevant mode is CHAFA_PIXEL_MODE_SYMBOLS: approximate the image using character symbols. And within that, the single-color path is exactly what the deck needs.
KN-86 rendering relevance — the symbol-selection engine
Section titled “KN-86 rendering relevance — the symbol-selection engine”chafa’s symbol mode is a principled version of the image→cell pipeline libcaca sketches and the ASCII Effect Spec calls for. The deck-relevant primitives from the public headers:
-
ChafaSymbolMap+chafa_symbol_map_add_by_tags (map, tags)— the symbol set is chosen by tags, a rich Unicode-range taxonomy (ChafaSymbolTagsinchafa-symbol-map.h). The tags directly enumerate the density/shape vocabulary this whole cluster is about:Tag What it adds CHAFA_SYMBOL_TAG_BLOCKBlock elements ▁▂▃▄▅▆▇█ and shades ░▒▓ CHAFA_SYMBOL_TAG_HALF/HHALF/VHALFHalf-blocks ▀▄▌▐ (the 1×2 pseudo-pixel) CHAFA_SYMBOL_TAG_QUADQuadrant blocks (2×2 sub-cell) CHAFA_SYMBOL_TAG_SEXTANTSextant blocks (2×3 sub-cell) CHAFA_SYMBOL_TAG_OCTANTOctant blocks (2×4 sub-cell) CHAFA_SYMBOL_TAG_BRAILLEBraille ⠿ (2×4 dot density) CHAFA_SYMBOL_TAG_BORDERBox-drawing CHAFA_SYMBOL_TAG_STIPPLE/DOT/SOLID/SPACEDither/fill density steps CHAFA_SYMBOL_TAG_ASCIIPlain 7-bit ramp An author picks the vocabulary — “blocks + half + braille only,” or “ASCII only,” or “everything” (
CHAFA_SYMBOL_TAG_ALL) — and chafa’s matcher chooses, per cell, the symbol whose foreground/background coverage pattern best fits that cell’s pixels. This is the shape matcher from react-video-ascii’scharMode: shape, implemented in C against a tagged Unicode symbol database. This is the reference design for KN-86’s image-to-cell quantizer. -
ChafaCanvasMode(the color/single-color axis):CHAFA_CANVAS_MODE_TRUECOLOR · INDEXED_256 · INDEXED_240 · INDEXED_16· INDEXED_16_8 · INDEXED_8 · FGBG_BGFG · FGBGThe two bottom modes are the single-color path (see below).
-
Core call sequence (from
chafa-canvas.h/chafa-canvas-config.h):config = chafa_canvas_config_new ();chafa_canvas_config_set_geometry (config, width, height); /* cell grid */chafa_canvas_config_set_pixel_mode (config, CHAFA_PIXEL_MODE_SYMBOLS);chafa_canvas_config_set_canvas_mode (config, CHAFA_CANVAS_MODE_FGBG);chafa_canvas_config_set_symbol_map (config, map); /* tag-selected */canvas = chafa_canvas_new (config);chafa_canvas_draw_all_pixels (canvas, CHAFA_PIXEL_TYPE_RGBA8_UNASSOCIATED,src_pixels, w, h, rowstride);rows = chafa_canvas_print_rows_strv (canvas, term_info); /* one string/row */Note
chafa_canvas_print_rows_strv— it emits one string per row, which maps cleanly onto writing into a termbox2 row buffer (ADR-0027) rather than blasting ANSI to a tty.
Single-color adaptation
Section titled “Single-color adaptation”This is the heart of the chafa case for KN-86, and it is first-class in the library, not bolted on:
CHAFA_CANVAS_MODE_FGBG— header description: “Default foreground and background colors. No ANSI codes will be used.” chafa emits only the chosen symbols, no color escapes at all. The image is reproduced purely by which glyph lands in each cell — shape and coverage carry the entire image. This is precisely KN-86’s monochrome amber-on-black contract (Canonical Hardware Specification): one foreground (amber #E6A020), one background (black), and a Unicode/block/braille symbol per cell doing all the work.CHAFA_CANVAS_MODE_FGBG_BGFGadds one bit — symbol plus inversion (fg/bg swap) — giving a free extra coverage state per cell at still effectively one color. Useful where pure FGBG loses a tone.- The brightness channel becomes coverage. In FGBG mode, what was luminance in a color render becomes fractional cell coverage: a dim region picks a sparse glyph (space,
░, a 1-dot braille), a bright region picks a dense one (█, full braille, solid block). chafa’s matcher is choosing coverage per cell exactly as astroterm chooses glyph density per star and neo chooses brightness per cell — same single-color lever, applied to a raster. Pair with block/half/quad/sextant/octant + braille tags and FGBG mode and you have the full monochrome-density toolkit the deck wants, driven by a maintained matcher.
So the verdict on the technique: chafa is the most complete single-color image-rendering reference in the corpus — it formalizes the exact symbol-coverage-as-brightness model the deck needs, with a tag taxonomy that enumerates every density glyph family (block / shade / half / quad / sextant / octant / braille / box) in CLAUDE.md’s character-set story.
C-API embeddability verdict (the adoption question)
Section titled “C-API embeddability verdict (the adoption question)”Strong technique reference; conditional adoption candidate — the blockers are dependency weight and license, not API quality.
In favor of linking libchafa directly:
- Genuinely stable, documented public C API — the call sequence above is small and clean. The CLI being a thin wrapper proves the library is the real surface.
print_rows_strvfits nOSh — per-row strings drop into termbox2’s row model without an ANSI round-trip.- FGBG mode is exactly the deck’s render target — no adaptation layer needed for the color story.
- Animated GIF support comes for free (
ChafaImage/ChafaFrame/ChafaPlacement), covering cart preview thumbnails and boot art.
Against, and why this is candidate not commit:
- GLib dependency.
chafa.hincludes<glib.h>; the API traffics inGString/gchar/gint. Pulling GLib into the nOSh runtime is a meaningful footprint increase on a Pi Zero 2 W image and a real link-surface decision. This is the single biggest objection — quantify the added image size and memory before committing. - SIMD is x86-only. chafa’s optimized matcher paths are SSE4.1 / AVX2 / MMX / popcnt (
chafa/internal/chafa-{sse41,avx2,mmx,popcnt}.c). The Pi Zero 2 W is ARM Cortex-A53 — none of these fire, so on-device chafa runs its generic C fallback. It will still be correct and reasonably fast, but the headline “SIMD-optimized” advantage evaporates on the target silicon. Budget for the scalar path and measure. - Dual LGPLv3+ / GPLv3+. LGPL permits dynamic linking from a differently-licensed binary, but it imposes relinking/notice obligations that the rest of the KN-86 stack (which leans permissive) doesn’t. A license/linking review is a precondition to vendoring.
Recommendation: treat chafa as the design reference for KN-86’s own symbol-coverage quantizer — adopt its model (tag-selected symbol map + FGBG coverage matcher) and reimplement the matcher against the deck’s CP437/Press Start 2P symbol set, sidestepping GLib and the license question, the same posture taken with libcaca. Reserve direct linking as a fast-path option for the desktop emulator / cart-authoring toolchain only (where GLib weight and x86 SIMD are both fine), keeping the on-device runtime dependency-light. If on-device image rendering ever needs more fidelity than the homegrown matcher delivers, revisit direct linking with the GLib-footprint and license reviews done.
- chafa is the engine-reference upgrade to libcaca — same image→cell job, but with a far richer symbol taxonomy (sextant/octant/braille that libcaca predates) and a stable modern C API. Where the ASCII Effect Spec names libcaca as the engine north star, chafa is the better-resolved 2020s reference for the symbol-selection half specifically.
- The tag taxonomy belongs in the character-set doc. chafa’s density-glyph families (block / quad / sextant / octant / braille) map onto KN-86’s character-set grammar; worth citing there as the precedent for which Unicode density ranges to prioritize in Layer 2.
- Cross-link neo and astroterm — those generate fields procedurally; chafa converts raster imagery. The deck wants both rendering modes; chafa is the raster half’s reference engine.
- Cross-link react-video-ascii — chafa’s symbol matcher is the C implementation of
charMode: shape; the FGBG coverage model issaturation: 0taken to its logical conclusion. - Cross-link reveal-styles.md — a chafa-rendered surface is a
draw-ascii-class surface;(reveal …)animates its appearance. - Bindings note: Python + JS/WASM bindings make chafa viable for web-side KN-86 surfaces too (docs site image previews, marketing pages) — the same split react-video-ascii occupies for web, but raster-input rather than video.