The worst ANSI art renderer, except for all the others

Chafa (github) started out as a small piece of supporting code for an obscure personal project I may announce at some indefinite point in the future. Then I decided to release it as a tongue-in-cheek thing for the VT100 anniversary last year, and, well… it gathered a bit of steam.

Chafa 1.0

Since I'm not one to leave well enough alone, I packaged it up over the holidays for the 1.0 release. It brings a pile of improvements, e.g. new symbol ranges like ASCII and Braille, better image preprocessing and a new --fill option for halftone/dithering. It's also, like, real fast now, and the build is much less brittle.

Big thanks to everyone who contributed to this release: Adam Borowski, Felix Yan, Lajos Papp, Mo Zhou, Ricardo Arguello, Robert-André Mauchin, @dcb314 and @medusacle.

You'll find packages out there for Arch, Debian, Fedora, Gentoo and Ubuntu now; check your repositories. Extra big thanks to the package maintainers.

As the post title implies, I think Chafa is now the least bad tool in this tiny but tradition-rich niche. So what's so not-quite-terrible about it?

Only the best for your aixterm

If you've been around text terminals for a while, you'll know what this means:

16 colors

Up until fairly recently, the most colorful terminal applications would operate within the confines of some variant of the above palette, and many still do. It's a nice and distinct (not to mention cheerful) set of colors, but it's not a great match for most image material you'll come across, which makes legible Chafa output a challenge. And that is precisely why I had to try (here with a nice woodblock print):

ANSI art variations

The top left image is the best reproduction you can get with Chafa using a modern 24-bit color terminal emulator (in this case, GNOME Terminal) at 60 characters wide. At top right is the 16-color mapping you get without applying any tricks; this is pretty close to the mark given the muted moonlight colors of the input image, but it doesn't make good use of our palette, nor does it convey the essence of the scene very well. Blue sky, rolling hills, green grass. A shady waterfront pavilion. Given our limitations, the output will look nothing like the original anyway, so we're better off trying to capture the gist of it.

We do this simply by cranking up the contrast and saturation to levels where our cheerful old palette can do a decent job (bottom left). Chafa no longer relies on ImageMagick for this, so it's available in the library API, and integer-only math makes it performant enough to run in real time on animations and video (more on this in a later post, perhaps).

It gets even better if you do color assignment in DIN99d space (bottom right), but that's way slow, so you have to explicitly enable it.

No ANSI? No problem

Braille and ASCII art

You can generate varied output without any color codes at all. The above examples also demonstrate use of the --fg option, which, along with --bg, exists to tell Chafa what your terminal's default colors look like so it can target those, but is equally useful for tweaking monochrome output thresholds.

So if for some reason your terminal can't handle ANSI or you still have one of those warm & fuzzy monochrome tubes sitting around, we've got you covered. Or break out your civil rights-era dot-matrix printer and make some precision banners to hang in your lab!


    1. It's referring to ANSI X3.64 plus almost 40 years of oddball extensions. For a long time, "ANSI codes" or even just "ANSI" was almost synonymous with that (increasingly loosely defined) standard, at least in the context of terminal graphics.

      Due to all the additions, Chafa output would not be compliant with ANSI X3.64, but I don't think there's a good name for the whole extended mess, and "image processor targeting CSI sequence-based environments" is a bit of a mouthful and unlikely to clarify things for most people.

  1. I had to toss in:
    run_magickwand (const gchar *filename, gboolean is_first_file, gboolean is_first_frame, gboolean quiet)
    MagickWand *wand = NULL;
    + PixelWand *color;
    guint8 *pixels;
    gboolean is_animation = FALSE;
    gdouble anim_elapsed_s = 0.0;
    @@ -1072,6 +1073,11 @@
    timer = g_timer_new ();

    wand = NewMagickWand();
    + color = NewPixelWand();
    + PixelSetColor(color, "none");
    + MagickSetBackgroundColor(wand, color);

    Because Imagemagick default is kinda dumb WRT assuming transparent background, and many of the SVGs I was checking in terminal were white paths with a background assumed to be transparent. I'm sure this should be flagged though. With SVGs maybe it should be assumed? It seems to be the default these days. In imagemagick convert this would be "convert -background transparent" Figured I'd mentioned it as a reference for others.
    Apart from that, ♥ this tool, quality is amazing, and super useful.
    I've been using:
    ~/hg/chafa/tools/chafa/chafa –clear –symbols=all –fill=all -c full -s $(tput cols)x –font-ratio 2/5
    With the default Monospace font on Devuan.

    1. Glad to hear you're putting it to use! I'll look into the issue wrt. white-path SVGs and transparency. Long term I'm hoping to move away from ImageMagick entirely for most image types. It would improve throughput too.

      Edit: Fixed in 1.2.0. Thanks for the tip.

  2. > Or break out your civil rights-era dot-matrix printer and make some precision banners to hang in your lab!

    There is no civil-rights era dot matrix printer, as the civil rights movement is considered to be an event of the 1950s and 1960s, while dot matrix printers did not appear until 1970. World War II also did not take place in the 1920s. And Jesus was not born in 50 BC. Thank you for coming to my TED Talk.

Leave a Reply

Your email address will not be published. Required fields are marked *