# 3a: A Format and Tooling for Creating ASCII Animations

Most people in tech have probably seen ASCII art at some point, and it has been written about plenty of times already, but let’s do the obligatory intro anyway.

ASCII art is a way to draw using plain text. Instead of pixels, you have characters. Instead of a graphics editor, any text editor will do. And instead of a viewer, there is usually `cat` and a terminal.

```text
 /\_/\
( o.o )
 > ^ <
```

Historically, "ASCII art" is often used as an umbrella term for almost any kind of text-based art, even when it has long since escaped the boundaries of ASCII itself and started using Unicode, ANSI colors, box-drawing characters, Braille symbols, terminal control sequences, and so on. I will use the term in that broader sense too: an image or animation that can be stored, edited, and displayed as text.

## Why Yet Another Format?

About five years ago, before my interest in ricing and OS customization had fully evaporated, I really wanted to add an animated ASCII logo of my distro to `neofetch`, mostly for the sake of nice-looking GIFs on [r/unixporn](https://www.reddit.com/r/unixporn/).

I had already seen examples of animated fetch tools, and for some reason I assumed there must be some common format for that kind of thing.

As it turned out, it wasnt. (Well, technically it was, but we will get to that near the end).

The examples I found were usually custom scripts with hardcoded strings containing [ANSI colors](https://en.wikipedia.org/wiki/ANSI_escape_code#colors) and a `sleep` between frames. There was no "standard". And if there is no such format that it should be created.

That is how `Animated ASCII Art` appeared. The characteristic difference between `3a` and a good old plaintext file with ANSI escape codes is that `3a` preserves formatting in a text editor: the art looks the same in the editor as it does when rendered. This means you can work with it directly, without needing a specialized editor like [DurDraw](https://github.com/cmang/durdraw/).

For a long time, the project existed as a somewhat ambiguous format description, a minimal implementation without many features, and literally a couple of artworks for which the whole thing had been created in the first place.

Recently I finally cleaned it all up: rewrote the specification, brought the CLI tool into a more usable state, split out libraries, added conversion to different formats, and organized a [small collection](https://github.com/asciimoth/openascii) of openly licensed ASCII animations.

## File Structure

A `3a` file consists of blocks.

Each block starts with a line containing the block name, beginning with `@`. The first block is always `@3a`; it is the header with metadata. The main block with frames is called `@body`.

A minimal file without colors or metadata may look like this:

```text
@3a

@body
  <=>\      
  ,..\\..,  
 ' //     '
|          |
|          |
 '.__.~._.'
```

If there are multiple frames, they are separated by blank lines:

```text
@3a
title just an apple
delay 300
loop yes

@body
  <=>\      
  ,..\\..,  
 ' //     '
|          |
|          |
 '.__.~._.'

  <=>\      
  ,..\\..,  
 ' //,_.--'
|   /  {    
|   \_,''-.
 '.__.~._.'

  <=>\      
  ,..\\..,  
 ',--,_.--'
    }  {    
  ,-,_,''-.
 '.__.~._.'
```

![](https://cdn.hashnode.com/uploads/covers/69ff3f1af239332df4b947ff/0ea913db-206b-48e5-b29e-6cb699b7129f.gif align="center")

A few fields appeared in the header here:

*   `title` - the title of the art;
    
*   `delay` - delay between frames in milliseconds;
    
*   `loop` - whether the animation should loop.
    

If `delay` is not specified, the default is 50 ms. If `loop` is not specified, the animation is considered looped. So simple artworks do not need to be bloated with metadata unless there is a reason for it.

The header can also contain authors, original author, source link, license, editor, preview frame, tags, and so on. For example:

```text
@3a
title just an apple
author ASCIIMoth
license CC0-1.0
src https://github.com/asciimoth/openascii
delay 300
loop yes
preview 0
#apple #fruit #food
```

Comments, in places where they are allowed, start with `;;` and take the whole line.

## Colors

The most important idea behind colored `3a` is that text and colors are stored in separate columns, not mixed together.

Instead of inserting ANSI escape codes directly into the art, each text line is followed by a color line of the same length, consisting of color names.

```text
@3a
colors yes

@body
  <=>\      112228111111
  ,..\\..,  111118811111
 ' //     ' 111991111111
|          |111111111111
|          |111111111111
 '.__.~._.' 111111811111
```

![](https://cdn.hashnode.com/uploads/covers/69ff3f1af239332df4b947ff/f7a08190-be59-4a66-ac2e-f7a36fa88d62.gif align="center")

The built-in names represent standard 4-bit ANSI colors:

```text
0 black
1 red
2 green
3 yellow
4 blue
5 magenta
6 cyan
7 white
8 bright black / gray
9 bright red
a bright green
b bright yellow
c bright blue
d bright magenta
e bright cyan
f bright white
_ default (depends on the terminal)
```

You can also define custom colors:

```text
@3a
colors yes
col r fg:196
col l fg:bright-green
col b fg:94
```

A color can be an ANSI color by name, a 256-color code, or an RGB hex value. For terminal output, this is later mapped to ANSI sequences; for SVG, PNG, GIF, WebP, and MP4, it is mapped to the corresponding graphical representation.

If the color channel is the same for all frames, it does not have to be repeated every time. For that there is the `@color-pin` block, which defines one "pinned" color-channel frame for the whole animation.

And conversely, if the text stays the same and only colors change, `@text-pin` can be used.

```text
@3a
title apple with pinned colors
colors yes
delay 300

@color-pin
112228111111
111118811111
111991111111
111111111111
111111111111
111111811111

@body
  <=>\      
  ,..\\..,  
 ' //     '
|          |
|          |
 '.__.~._.'

  <=>\      
  ,..\\..,  
 ' //,_.--'
|   /  {    
|   \_,''-.
 '.__.~._.'

  <=>\      
  ,..\\..,  
 ',--,_.--'
    }  {    
  ,-,_,''-.
 '.__.~._.'
```

![](https://cdn.hashnode.com/uploads/covers/69ff3f1af239332df4b947ff/0410fc90-5003-4a50-a760-dff53cfdfe40.gif align="center")

## aaa

The main tool for working with `3a` is the [aaa](https://github.com/asciimoth/aaa) CLI utility.

At the top level, it has several command groups:

```text
list         List builtin art
gen          Generate new art
play         Play art in terminal
fetch        Show system info side by side with animated logo
preview      Show art preview
edit         Editing subcommands
convert      Format conversion subcommands
from-text    Constructs art from plain text with ANSI color escape codes
completions  Generate shell completions
```

To simply play a `.3a` file in the terminal:

```bash
aaa play ./apple.3a
```

If the file has colors, `aaa` converts the color channel into ANSI escape sequences at output time.

To show a preview - one frame, by default frame zero:

```bash
aaa preview apple.3a
```

And the fetch command, the reason this whole thing started:

```bash
aaa fetch distro_nixos_big.3a
```

%[https://asciinema.org/a/ZtzhhTOmVWCAmrfz] 

By default, `aaa` tries to use one of the already installed fetch tools: `neofetch`, `fastfetch`, `screenfetch`, `nitch`, `profetch`, `leaf`, or `fetch-scm`. So the system information comes from the usual fetch tooling, while the logo comes from the `3a` file.

### Generation and Editing

A template can be created with `gen`:

```bash
aaa gen > apple.3a
```

After that, the file can be opened and edited manually. But some operations can also be done through `aaa edit`, which is especially useful from scripts.

Set the title:

```bash
aaa edit ./apple.3a title "just an apple" > apple2.3a
```

Add tags:

```bash
aaa edit ./apple.3a tag-add apple fruit food > apple2.3a
```

Change the license:

```bash
aaa edit ./apple.3a license CC0-1.0 > apple2.3a
```

Besides metadata, there are frame operations too: duplicate a frame, delete one, swap frames, reverse frame order, cut out a range, deduplicate repeated frames, shift the animation forward or backward, and so on.

## Conversion

The second major part of `aaa` is conversion.

The list of currently supported targets looks like this:

```text
to-frames  ANSI-colored frames separated by blank lines
to-cast    asciicast v2
to-dur     durdraw format
to-json    JSON document
to-ttyrec  ttyrec
to-png     PNG image
to-gif     GIF animation
to-webp    WebP animation
to-mp4     MP4 video
to-svg     SVG animation
to3a       print art back in 3a format
```

For example, to make a GIF:

```bash
aaa convert apple.3a to-gif > apple.gif
```

Or an [asciinema](https://asciinema.org/) cast:

```bash
aaa convert apple.3a to-cast > apple.cast
asciinema play apple.cast
```

The normalization command is useful on its own:

```bash
aaa convert apple.3a to3a > normalized.3a
```

It reads the file and prints it back in the `3a` format. This is convenient for checking how the parser understands the file and for bringing artworks to a more uniform shape.

Another practical scenario is taking an existing artwork with ANSI sequences and converting it into `3a`:

```bash
cat old-logo.txt | aaa from-text > logo.3a
```

## Libraries

`aaa` is built on top of the [rs3a](https://github.com/asciimoth/rs3a) Rust library.

It can read and write the new `3a` format, partially read the legacy version, edit art programmatically, and export it to SVG, asciicast v2, and ANSI-colored text.

```rust
use rs3a::{Art, font::Font, CSSColorMap};
use std::fs::File;
use std::io::Write;

fn main() {
    let mut art = Art::from_file("./examples/dna.3a").unwrap();

    let color_pair = "fg:black bg:yellow".parse().unwrap();
    let color = art.search_or_create_color_map(color_pair);

    for frame in 0..art.frames() {
        art.print(frame, 0, 0, &format!("{}", frame), Some(Some(color)));
    }

    art.to_file("./examples/edited_dna.3a").unwrap();

    let mut output = File::create("./examples/dna.svg").unwrap();
    write!(
        output,
        "{}",
        art.to_svg_frames(&CSSColorMap::default(), &Font::default())
    ).unwrap();
}
```

There are also [py3a](https://github.com/asciimoth/py3a) and [go3a](https://github.com/asciimoth/go3a), but they are less feature-complete and are mostly useful for parsing.

## OpenASCII

The last part of the "ecosystem" is [openascii](https://github.com/asciimoth/openascii).

It is a tiny collection of ASCII art, mostly mine, in the `3a` format, under permissive or copyleft licenses, with a web player based on [asciinema](https://asciinema.org/).

I would be happy if something from the collection turns out useful for your [CLI tools / ASCII games / etc.](https://openascii.moth.contact/#resources), or if you happen to have [something to contribute](https://github.com/asciimoth/openascii/pulls).

## Alternatives

After the fact, I did eventually discover that several other attempts to do something similar had already existed. But they either solved a slightly different problem, or never got far enough.

The most noteworthy one is the terminal ASCII animation editor [DurDraw](https://github.com/cmang/durdraw/). It has its own format (gzipped JSON) and can use it in its built-in fetch command.

Still:

*   the format has exactly one very tangled implementation;
    
*   the format documentation contradicts the de facto implementation;
    
*   editing that spaghetti of nested JSON by hand in a normal text editor is not much easier than the "reference" plaintext-plus-ANSI-codes approach.
    

That said, had DurDraw been a little more famous and a little less buggy back then, maybe `3a` would never have happened. However, `aaa` now supports conversion between `.3a` and `.dur`.
