Making art in the terminal window

Mona Lisa in the Console

© Lead Image © Gennadiy Poznyakov, 123RF.com

© Lead Image © Gennadiy Poznyakov, 123RF.com

Article from Issue 305/2026
Author(s):

You can create living, breathing art using nothing but C++ code, 16.7 million colors, and the Linux console.

This article invites readers into the world of generative art – but not the kind rendered by GPUs or displayed through glassy GUI frameworks. In this case, the canvas is the Linux terminal, and the brush is pure C++ code. You'll learn how to breathe life into grids of characters, how patterns emerge from logic, and how randomness itself can become rhythm. I don't have the space to print all the code used for this article, but you'll find it all at my GitHub site [1].

Using a text terminal, and a deep respect for the expressive power of simplicity, I'll construct living systems made entirely of text and shades of color. By the end, your terminal will have transcended its humble role as a command prompt, becoming a living canvas of color, movement, and form. From oscillating plasma to self-replicating cellular automata, you'll watch as computation turns into motion, emotion, and art.

The Canvas

Before you can draw, you must understand the medium. The Linux terminal, often dismissed as a relic of textual computing, is actually a grid-based display system, capable of far more nuance than it first appears. At its heart, every terminal window is a matrix of character cells, each with a foreground and background color.

The contents of each cell is rendered by your terminal emulator, which translates ANSI escape codes (short textual commands) into color, brightness, and character output. Modern terminal emulators, descendants of the green-on-black glass plates of the past, have undergone a remarkable transformation from mundane monochrome. Where once you had a proud but limited 16-green palette, you can now find the opulence of 256-color modes and even 24-bit True Color when correctly picking your terminal emulator. That's right – a staggering 16.7 million shades of vibrant decadence.

ASCII art emerged in the 1960s and 70s on teletype machines and early terminals, where engineers and bored scientists used letters, numbers, and punctuation marks to draw faces, logos, and scenes. As technology advanced, so did the art form – ANSI art arrived in the 1980s alongside colorful bulletin board systems (BBS). Using ANSI escape codes, artists could finally add 16 colors and cursor positioning to their creations, giving rise to dazzling, animated text compositions that defined the underground digital aesthetic of that era.

But those days are gone, and gone, too, are the restrictions that existed when the terminal was shackled to the spartan 80x25 grid. Modern emulators like Terminator, Alacritty, and kitty allow you to stretch out and breathe. Your "canvas" is now limited mainly by your screen resolution and how far you dare to zoom out before the text turns into microscopic ants.

In short, the modern terminal has evolved from a mere command-line type-enter-repeat sequence into a full-fledged digital atelier – capable not only of executing commands but of painting with them.

Every time you run the following command:

printf "\033[38;2;255;0;0m\033[48;2;0;0;255m\u2588\033[0m\n"

you are not merely printing a character; you are painting a pixel – the Unicode glyph 2588 acts as a full-height block, or, artistically viewed: a pixel.

Gentle Introduction to ANSI Sequences

Before you panic at the sight of that wild-looking printf incantation and flee for your life, take a moment to read this short introduction to ANSI escape sequences – because that's all that mysterious black colorful magic really is.

The ANSI escape sequences are those curious little runes that make your terminal abundant with color, move the cursor about, and occasionally reduce you to tears when you forget to reset them properly.

An ANSI escape sequence starts, fittingly, with an escape character – \033 (or \x1B in hex, also known as ESC). The escape character is followed by a left square bracket [ and then a series of parameters and commands.

For example:

\033[31m

means "switch the text color to red".

At the end of the day, it's just printed text – the terminal interprets these sequences and changes its behavior accordingly.

The original ANSI color palette offered eight base colors (Table 1), each with a bright variant – making a total of 16.

Table 1

Original ANSI Color Palette

Code

Color

Bright

30-37

Foreground (Black to White)

90-97

40-47

Background (Black to White)

100-107

In the following example:

std::cout << "\033[31mRed text\033[0m" << std::endl;

the 31 sets the foreground to red. The \033[0m at the end resets all attributes (like all well-behaving ANSI sequences do after they have finished painting the world).

Backgrounds are similar:

std::cout << "\033[42mGreen background\033[0m" << std::endl;

And if you want both (because who doesn't?):

std::cout << "\033[93;44mBright yellow on blue\033[0m" << std::endl;

The 256-Color Mode

Eventually, someone decided 16 colors weren't enough to express the full emotional range of a C++ developer, so terminals introduced the 256-color mode. To use it:

\033[38;5;<n>m<C> : Foreground color
:\033[48;5;<n>m<C> : Background color

Where <n> is between 0 and 255, and m is just the character "m." The 256 colors are distributed as follows:

  • 0-15: the original 16 ANSI colors
  • 16-231: a 6x6x6 RGB cube (216 colors)
  • 232-255: 24 shades of grayscale (from dark to light)

The C++ program in Listing 1 exemplifies this colorful palette and showcases the result in Figure 1.

Listing 1

The Colorful Palette

#include <iostream>
#include <iomanip>
int main() {
  std::cout << "\033[0m"; // Reset colors
  for (int row = 0; row < 16; ++row) {
      for (int col = 0; col < 16; ++col) {
          int color = row * 16 + col;
          std::cout << "\033[48;5;" << color << "m"   // Set background color
                    << std::setw(4) << color          // Print color index
                    << "\033[0m ";                    // Reset after each
      }
      std::cout << "\n";
  }
  std::cout << "\033[0m"; // Final reset
  return 0;
}
Figure 1: The 256-color palette.

Buy this article as PDF

Download Article PDF now with Express Checkout
Price $2.95
(incl. VAT)

Buy Linux Magazine

Related content

  • Animation with OpenToonz

    OpenToonz is a professional animation tool for comic and manga artists.

  • FOSSPicks

    Graham has just recorded a one-off podcast featuring the wonderful open source VCV Eurorack emulator, often written about here. He's now strongly considering doing a more regular synth-related podcast.

  • Next Steps in Vector Graphics

    What are vector graphics and how could we make them better?

  • Graphics in Python with Cairo

    Build graphic elements into your Python programs with the Cairo graphics library. We'll show you how to draw an analog clock face that displays the current time.

  • FOSSPicks

    This month Graham looks at Plasma System Monitor, projectM audio visualizer, yt-dlg downloader GUI, and more.

comments powered by Disqus
Subscribe to our Linux Newsletters
Find Linux and Open Source Jobs
Subscribe to our ADMIN Newsletters

Support Our Work

Linux Magazine content is made possible with support from readers like you. Please consider contributing when you’ve found an article to be beneficial.

Learn More

News