DEV Community

Cover image for Painting the Terminal: Building a True-Color ASCII Art Generator in Python
Madhura Ravishan
Madhura Ravishan

Posted on

Painting the Terminal: Building a True-Color ASCII Art Generator in Python

Painting the Terminal: Building a True-Color ASCII Art Generator in Python

We've all seen ASCII art—those nostalgic, text-based images that hark back to the early days of computing. But usually, they are monochromatic and a bit flat. What if we could bring them into the modern era by injecting true RGB colors directly into the terminal window?

I recently built a Terminal True-Color ASCII Art Generator using Python, and I wanted to share the engineering mindset behind it. In this deep dive, we'll explore the implementation flow, the logic behind mapping pixels to text, and how we can hack the terminal using ANSI escape sequences.

If you are looking for a fun weekend project that touches on image processing, basic math, and terminal rendering, grab a coffee, and let's dive in. ☕

The Tech Stack

To keep things lightweight, I relied mostly on Python's built-in modules alongside one external library for image handling:

  • Python 3
  • Pillow (PIL) for image processing
  • os, sys, time (Built-in)

1. The Math: Fixing the Aspect Ratio

The very first hurdle in converting an image to ASCII is the aspect ratio. Pixels in a digital image are perfectly square (1:1). Terminal characters, however, are not. They are typically about twice as tall as they are wide.

If you map one pixel to one character directly, your resulting ASCII art will look distorted and vertically stretched—like looking in a funhouse mirror.

To fix this, we have to compress the image vertically before we even start converting it. Here is the core logic:

def resize_image(image, new_width=130):
    width, height = image.size
    # Adjusting the ratio by dividing by 2.2 to counter terminal font height
    ratio = height / width / 2.2  
    new_height = int(new_width * ratio)
    return image.resize((new_width, new_height))
Enter fullscreen mode Exit fullscreen mode

By calculating the standard ratio and explicitly dividing it by 2.2 (a magic number that closely matches most default terminal line-heights), we ensure the final output retains the visual proportions of the original photograph.

2. Brightness Mapping: From Pixels to Characters

Once the image is properly sized, we need to convert the visual density of the image into text. The core idea is simple: Darker pixels become dense characters (like @), and lighter pixels become sparse characters (like .).

First, we define our palette, sorted from darkest to lightest:

ASCII_CHARS = ["@", "#", "S", "%", "?", "*", "+", ";", ":", ",", "."]
Enter fullscreen mode Exit fullscreen mode

Next, we loop through every pixel in our resized image. For each pixel, we extract the Red, Green, and Blue (RGB) values and calculate its average brightness:

Brightness = (R + G + B) / 3

This gives us a grayscale value between 0 and 255. We then map this value to an index in our ASCII_CHARS array.

r, g, b = image.getpixel((x, y))
gray = int((r + g + b) / 3)
char = ASCII_CHARS[gray * len(ASCII_CHARS) // 256]
Enter fullscreen mode Exit fullscreen mode

Note: We divide by 256 to ensure our index stays perfectly within the bounds of the array length.

3. The Magic: ANSI Escape Sequences

If we stopped at the previous step, we'd have a perfectly fine grayscale ASCII image. But we promised true color.

Terminals don't process standard HTML/CSS color codes. Instead, modern terminals use ANSI escape codes—special strings of characters that tell the terminal emulator how to format the text that follows.

To print true 24-bit RGB colors in the terminal, the sequence looks like this:
\033[38;2;{r};{g};{b}m

Let's break that down:

  • \033[ : The escape character that tells the terminal "a formatting command is coming."
  • 38;2; : The specific code telling the terminal we are defining a foreground true-color RGB value.
  • {r};{g};{b} : Our exact pixel colors injected into the string.
  • m : The end of the formatting sequence.

We wrap our chosen ASCII character in this sequence, and follow it up with a reset code (\033[0m) so the color doesn't bleed into the next character.

color_char = f"\033[38;2;{r};{g};{b}m{char}\033[0m"
Enter fullscreen mode Exit fullscreen mode

4. The Final Implementation Flow

When we put it all together, the flow is beautifully sequential:

  1. Load the target JPG/PNG using Pillow.
  2. Resize the image, applying our / 2.2 math to correct the vertical stretch.
  3. Iterate through the Y (rows) and X (columns) coordinates of the pixels.
  4. Calculate brightness, select the character, and inject the original pixel's RGB data into an ANSI string.
  5. Print the final string, flushing the standard output for a smooth rendering effect.

💡 Why this matters

It's easy to blindly write code ("vibe coding") without understanding what is happening under the hood. Taking the time to parse out why a terminal stretches text, or how RGB array math translates to a visual palette, makes you a better engineer. It bridges the gap between high-level logic and low-level rendering.

🇱🇰 සිංහල පැහැදිලි කිරීම (Sinhala Explanation)

මෙම ව්‍යාපෘතිය පිටුපස ඇති ඉංජිනේරුමය සංකල්පය (Engineering Mindset) සරලව තේරුම් ගනිමු:

Aspect Ratio ගැටළුව: සාමාන්‍යයෙන් අපි රූපයක් (Image) Terminal එකක මුද්‍රණය කරන්න හැදුවොත් එය දික් වෙලා (vertically stretched) පෙනෙනවා. මීට හේතුව Terminal එකේ අකුරක (character) පළලට වඩා උස වැඩි වීමයි. මෙය නිවැරදි කිරීමට අපි රූපයේ උස 2.2 න් බෙදා Aspect Ratio එක නිවැරදිව සකස් කරගන්නවා.

Pixel එකක් අකුරක් බවට පත් කිරීම: ඉන්පසු සෑම Pixel එකකම RGB අගයන්හි සාමාන්‍යය (R+G+B)/3 ගෙන, අඳුරු ස්ථාන සඳහා @, # වැනි ඝනකම් අකුරුත්, දීප්තිමත් ස්ථාන සඳහා ., , වැනි තුනී අකුරුත් ආදේශ කරනවා.

ANSI කේත මගින් වර්ණ ගැන්වීම: වැදගත්ම කොටස තමයි Terminal එකට සැබෑ වර්ණ (True Colors) ලබා දීම. මේ සඳහා සාමාන්‍ය HTML/CSS වෙනුවට ANSI escape codes (\033[38;2;R;G;Bm) භාවිතා කර, Pixel එකේ සැබෑ වර්ණයම අකුරට ලබා දෙනවා.

නිකම්ම අන්තර්ජාලයෙන් කේතයක් (code) කොපි කරනවාට වඩා, මේ ආකාරයට පසුබිම් තර්කය (underlying logic) සහ ගණිතමය සංකල්ප අවබෝධ කර ගැනීම සාර්ථක මෘදුකාංග ඉංජිනේරුවරයෙකු වීමට ඉතා වැදගත්.

Top comments (0)