ASCII in the Terminal With chafa: Symbol Classes and Colour Modes That Stay Readable
chafa turns images into terminal ASCII with Unicode symbol classes and ANSI colour modes. Here's which combinations stay readable, and why 240-colour beats 256.
01/ ARTICLE
What is chafa and how does it turn an image into terminal text? (~180 words)
chafa is a command-line tool that turns images and animations into character output you can view straight in a terminal. It converts pixels into a mix of Unicode symbol glyphs and ANSI colour escape codes. Point it at a photo and it maps the source's brightness and colour onto whatever your terminal can actually display: sixel first, then Kitty's graphics protocol, then iTerm2's inline images, and finally plain Unicode symbol mosaics if none of those exist.
That fallback chain is why the same image still looks reasonable on a bare SSH session running on decade-old hardware. Most tools that render "ASCII art" default to a single block glyph and stop there. chafa doesn't. Its own documentation says plainly that using more symbols by default improves quality: a single half-block character alone produces visibly worse output than a mix of shapes chosen per region of the image.
If you're a designer trying to preview a look before it goes into a pipeline, that's the appeal. chafa lets you sanity-check dithering, symbol density, and colour reduction on a real image fast, with no editor and no GUI required.
Which chafa symbol classes should you use for readable ASCII? (~340 words)
chafa's --symbols and --fill flags accept 21 named classes: all, none, space, solid, stipple, block, border, diagonal, dot, quad, half, hhalf, vhalf, inverted, braille, technical, geometric, ascii, legacy, sextant, wedge, wide, narrow. List several separated by commas, or add and remove from the current set with +/- prefixes. Order matters when you do. The default set is block+border+space-wide-inverted for every output mode except none, which drops the inverted glyphs and uses block+border+space-wide instead.
Why mix classes instead of picking one glyph? Resolution. A terminal cell is a fixed rectangle, but different symbol classes subdivide that rectangle differently. Block and half characters split a cell into one or two regions, which reads cleanly on flat colour, UI screenshots, and anything with hard edges. Quad characters divide a cell into four quadrants. Sextant characters divide it into six. Braille characters, borrowed from the eight-dot braille cell, pack the most positions into a single character.
Each additional subdivision raises the effective resolution of the render. The same source image looks noticeably sharper in sextant or braille than in plain block. But it also raises the noise floor on anything that isn't high-contrast, because chafa now has more independent decisions to make per cell.
ascii and legacy sit at the other end of the trade-off. Both restrict the output to characters a 1980s terminal or line printer could render, sacrificing resolution for maximum compatibility. That matters when the output has to survive being piped into something that doesn't understand modern Unicode block characters.
The failure mode here isn't a chafa bug. It's a mismatch between symbol class and source material: braille or sextant on a low-contrast photograph reads as static, not detail, because the extra sub-cell resolution has nothing meaningful to encode. The same "why is my ASCII output unreadable" problem shows up from the character-ramp density angle in browser-based converters. chafa's symbol classes and a converter's density ramp are solving the same resolution-versus-noise trade-off with different mechanisms.
Which chafa colour mode keeps output consistent across terminals? (~300 words)
chafa's colour handling is controlled with a single --colors flag. The eight modes it accepts trade fidelity for portability in a specific, documented order:
| Mode | What it does | Best use |
|---|---|---|
| full | 24-bit truecolor, dynamic palette per image/frame — sixel mode only | Modern terminal with sixel or Kitty graphics support |
| 240 | Fixed 240-colour palette that avoids the lowest 16 ANSI slots | Cross-terminal reliability — the recommended default |
| 256 | Built-in 256-colour palette | Wide support, but less portable than 240 |
| 16 | aixterm-style 16 foreground/background colours | Older terminal emulators without a 256-colour palette |
| 16/8 | Base 8 colours plus 8 "bright" colours via bold escape codes | Legacy terminals lacking aixterm extensions |
| 8 | Standard 8-colour ANSI | Minimal-capability terminals |
| 2 | ANSI reverse-video and reset codes only, no palette | Print-safe or strictly monochrome output |
| none | No escape sequences at all — caller-specified foreground/background only | Piping into a consumer that doesn't parse ANSI |
Here's the one specific, documented reason 240 beats the built-in 256-colour mode: the lowest 16 entries in the 256-colour palette are terminal-dependent. The same source image can render with visibly different colours in two different terminal emulators, both set to --colors 256. The 240-colour palette sidesteps that range entirely. That's why it's the more reliable default, even though it technically offers fewer colours on paper.
full isn't strictly "better" than 240, just different. It only generates a dynamic palette in sixel mode, so setting --colors full in a terminal that falls back to Unicode symbols does nothing useful. Check which output format chafa actually selected — --format reports it — before assuming truecolor is in effect.
How do you stop chafa dithering and low colour counts from wrecking the image? (~230 words)
chafa's --dither flag accepts four values: none, ordered (alias bayer), diffusion (alias fs, for Floyd-Steinberg), and noise. The default is noise in sixel mode and none everywhere else. Two more flags tune the effect once you've picked a mode. --dither-grain sets the grain size in pixels (1, 2, 4, or 8 per axis, defaulting to 4x4 in symbol mode and 1x1 in sixel mode). --dither-intensity is a float where 1.0 is neutral.
Most of what looks like a "dithering problem" is actually a colour-mode problem. Banding, meaning visible steps in what should be a smooth gradient, means the colour mode doesn't have enough distinct values to represent the gradient's range. The fix is raising --colors toward 240 or full, before touching any dither setting at all. Unwanted grain or static is a different animal: usually the default noise dither mode firing on source material that didn't need it. --dither none or --dither ordered produces a flatter, more predictable result on flat-colour or UI-style source images.
--optimize (0-9, default 5) compresses the output through smarter reuse of control sequences. Leave it alone unless you're piping into something size-constrained. --preprocess, which sharpens contrast before conversion, defaults on at 16 colours or fewer. At higher colour counts, it's more likely to fight your dithering settings than help them.
What settings keep legacy-computing styles like PETSCII and ANSI art readable in chafa? (~210 words)
Both PETSCII and CP437 ANSI art were platform-bound. A PETSCII file only looked right on a Commodore terminal, and a CP437 ANSI piece depended on the viewer's code page matching the artist's. chafa's Unicode symbol classes aren't tied to a single platform, but the legacy and ascii classes exist specifically to approximate that period-accurate look on a modern terminal.
That's not an accident. chafa's legacy and ascii symbol classes exist because character-cell art has a history that predates Unicode by decades. 1990s BBS-era ANSI art used the IBM code page 437 character set, box-drawing and block characters built specifically for character-cell displays, combined with ANSI escape codes for colour and cursor control that MS-DOS interpreted through the optional ANSI.SYS driver. PETSCII occupied the same territory on Commodore systems, using its own machine-specific glyph set to do roughly the same job.
The setting that actually reproduces the look is the colour mode more than the symbol class. Pairing legacy or ascii with --colors 16 or 16/8 gets you close to how period hardware actually rendered colour. Pair the same symbol classes with --colors full and the illusion breaks immediately. The character shapes read as retro, but 24-bit colour reads as unmistakably modern.
How does chafa compare to kott's browser ASCII tool? (~190 words)
kott's browser ASCII tool trades granularity for speed. There's a live preview with no terminal required, no flags to remember, one-click export. Useful when you want to see ten variations in the time chafa would take to render two, or when the person doing the work doesn't want a terminal open at all.
chafa, on the other hand, is a real, scriptable, terminal-native tool. It's SSH-friendly, pipeable into other command-line tools, and its symbol-class and colour-mode controls are more granular than most browser-based converters. You're setting exact flags, not picking from a preset dropdown. That granularity is also the cost: getting a good result means understanding which symbol class and colour mode fit your source image instead of dragging a slider.
Neither tool makes the other pointless. A comparison of the leading browser-based ASCII converters already tested that side of this decision; this piece exists specifically to cover the terminal side that comparison didn't touch. Want to see the same source image through a symbol class and colour mode before committing to a chafa command? Load it into kott's ascii tool with the effect preloaded.
Frequently Asked Questions
What is chafa used for?
chafa is a command-line utility that converts images and animations into character-based output rendered directly in a terminal. It maps pixels to a mix of Unicode symbol blocks and ANSI colour codes, and falls back to sixel, Kitty, or iTerm2 graphics protocols when the terminal supports them.
Which chafa symbol class gives the highest resolution?
Sextant and braille classes pack the most detail per character cell — six regions for sextant, eight dot positions for braille — at the cost of looking noisier on photographic sources. Block and half classes read cleaner on flat or high-contrast source images.
Should I use 256-color or 240-color mode in chafa?
Use 240. The built-in 256-colour palette's lowest 16 entries are terminal-dependent, so the same image renders with visibly different colours across terminal emulators. The 240-colour palette avoids that range and looks consistent everywhere it's supported.
Why does my chafa output look banded or noisy?
Banding usually means the colour mode is too low for the source image's gradient range. Try 240 or full/truecolor instead of 16 or 8. Unwanted noise usually means the dithering mode is set to noise or diffusion on an image that didn't need it — try --dither none or --dither ordered for flatter source material.
Does chafa work over SSH?
Yes, as long as the remote terminal emulator supports at least Unicode symbol output. No graphics protocol is required for the basic character-cell mode, only for sixel, Kitty, or iTerm2 image output.
Conclusion
Readable terminal ASCII out of chafa comes down to two decisions, made in the right order. Pick a symbol class that matches your source material's actual detail: block or half for flat images, quad or sextant for photographic ones. Then pick 240-colour mode by default for anything that has to look right across more than one terminal emulator. Dithering is a fine-tuning step for what's left over, not a fix for a colour mode that's too narrow for the image.
Once you've found settings that work on one image, kott's ascii tool is the faster way to check whether they hold up across a whole batch, without re-running chafa flags by hand for every source.
02/ OUT
Every setting described above is a real control. Open your own image and sweep it.
All journal entries