Welcome back to Let’s Reinvent the Wheel. In this project, we explore how terminal text effects like rainbow coloring and simple animation work. We build a tiny silly Zig program that prints colorful text from stdin.
What You’ll Learn
This project is all about exploring how colorful terminal output works:
- How to manually generate ANSI escape codes for 24 bit color
- How to apply RGB gradients across characters
- How to read and process stdin in Zig
- How to animate terminal output using carriage returns and redraws
We implement all of this with Zig’s standard library, keeping the code small, clean, and educational.
Project Code
You’ll find the complete source code here: silly-cat
How It Works
Reading Input from Stdin
silly-cat
reads from standard input line by line using Zig’s streamUntilDelimiter
, capturing one line at a time into a buffer:
reader.streamUntilDelimiter(fbs.writer(), '\n', fbs.buffer.len)
This makes it compatible with piped input like:
|
Each line is processed independently, with a short delay between lines to support the animation effect.
Generating Rainbow Colors
For every character on the line, silly-cat
calculates a rainbow color using a sine wave offset:
const red = @sin(p / 10.0) * 127.0 + 128.0;
const green = @sin(p / 10.0 + 2π/3) * 127.0 + 128.0;
const blue = @sin(p / 10.0 + 4π/3) * 127.0 + 128.0;
This produces a smooth hue transition across characters, giving it a “rainbow stream” appearance.
The position
of the character and a global offset
(which changes slightly between lines) are used to animate the color wave over time.
Writing Colored Output
Each character is wrapped with a 24 bit ANSI color escape sequence:
try writer.print("\x1b[38;2;{d};{d};{d}m{c}", .{ color.r, color.g, color.b, c });
At the end of each line, it resets the color with \x1b[0m
.
The cursor is hidden at the start (\x1b[?25l
) to reduce flicker during animation, and re-shown at the end (\x1b[?25h
).
Animation Control
The animation is handled by a short sleep (std.time.sleep
) between line renders, combined with a moving offset
in the color calculation.
This creates a subtle horizontal shimmer effect across multiple lines.
Tweak and Expand
- UTF-8 support: Right now, the code assumes single byte ASCII characters. Supporting multibyte UTF-8 characters (like emojis or accented letters) requires iterating over codepoints correctly.
- File input: Currently, input is read only from stdin. Add support for reading from files (via command line arguments).
- Multiple animation styles: You could implement vertical “wiggle”, character jitter, or scrolling.
- Custom gradients: Try using HSV interpolation, or let users pass in a color palette.
External Resources
- Everything you never wanted to know about ANSI escape codes
- ANSI Escape Codes GIST
- lolcat - a terminal colorizer that inspired this project
- graphtoy - easy graphing of functions
- Zig language documentation