Skip to content

asciinator

asciinator is a small Python tool that converts images into ANSI-colored terminal art. It exposes three rendering modes and a 24-bit color path, all sharing the same core “rasterize image, quantize to per-cell color, emit ANSI” pipeline:

  • halfblock (default) — uses the Unicode upper-half-block glyph (, U+2580) with 24-bit foreground and background color, packing two image rows into every terminal line. This doubles vertical resolution compared to one-cell-per-pixel approaches and is the right answer for almost every “show me an image in the terminal” use case.
  • shade — uses Unicode shade-density glyphs (█▓▒░) with 24-bit foreground color. Useful when only one color per cell is acceptable (e.g., over a non-true-color fallback path) or for a chunkier aesthetic.
  • ascii — classic ramp @%#*+=-:. with 24-bit foreground color. Pure-ASCII fallback or deliberate retro look.
  • Halfblock with + fg/bg is the canonical “image in the terminal” technique. Any time KN-86 needs to render imagery — a splash bitmap, a cart’s title-card art, a faction sigil, an album-cover-equivalent for a music cart — the halfblock pipeline is the right call. It’s well-understood and has decades of “raster.bel”-style prior art in the demo scene.
  • Three modes map naturally onto KN-86’s aesthetic-mode story. On a monochrome amber device the shade mode (single fg color, density-based dithering) is the only one that renders correctly; the halfblock and ascii modes both assume per-cell color delta and degrade on a mono display. So the right framing for KN-86: ship shade-style rendering by default (using intensity-mapped block density against the amber palette), document halfblock as the desktop-emulator-only path, and treat ascii as a stylistic option.
  • Document the technique in NoshAPI. A (draw-image path :mode 'shade) primitive (or its FFI equivalent — see ADR-0005 for the surface conventions) lets cart authors include real raster art without each cart hand-rolling the pixel-to-cell conversion.
  • Tooling vs. runtime. asciinator itself is a Python CLI — KN-86 isn’t going to embed a Python interpreter. The value here is the technique, not the library. The 30-line core of “load image, resample to (cols × rows×2), pick fg=top-row color and bg=bottom-row color, emit \e[38;2;…;48;2;…m▀” is the reference algorithm; a C or Lisp implementation behind NoshAPI is straightforward.

Pending. The PyPI page itself doesn’t carry a clean example output worth embedding here. (Optional follow-up: run asciinator locally against a small reference image (the KN-86 logo or a CIPHER glyph) and save the terminal output as a PNG to use as the reference image.)

  • The SVG extra (asciinator[svg]) handles vector input. Useful if KN-86 cart authors author imagery as SVG (clean lines, clean color regions, no resampling artifacts) and the runtime rasterizes at cart-load time.
  • For per-frame video / animation rendering, asciinator-style halfblock output is also how chafa, imgcat, and the Kitty/iTerm/Sixel graphics protocols work conceptually. The Kitty protocol gives true-pixel rendering when the terminal supports it (and the l123 entry notes that ratatui-image already integrates Kitty/iTerm/Sixel for graphs). KN-86 likely doesn’t need this path — the framebuffer is the display, not a terminal escape sequence target — but the technique is worth knowing as a fallback.
  • The 24-bit color emit format is ANSI standard: \e[38;2;R;G;Bm for foreground, \e[48;2;R;G;Bm for background. Both Bubble Tea / Lip Gloss and termbox2 handle this transparently; cart authors never need to write the raw escape themselves.