diff options
| author | Sadeep Madurange <sadeep@asciimx.com> | 2025-12-22 23:38:06 +0800 |
|---|---|---|
| committer | Sadeep Madurange <sadeep@asciimx.com> | 2025-12-22 23:38:06 +0800 |
| commit | 08e594268ed20c5c2355a249ac691c007e38aed9 (patch) | |
| tree | 2153e6319eb0bee4f062565858185088f2a29d82 /_log/matrix-digital-rain.md | |
| parent | 8f99ca06fd48386bee511d33d1207f8eaf8bb8f4 (diff) | |
| download | www-08e594268ed20c5c2355a249ac691c007e38aed9.tar.gz | |
Matrix post.
Diffstat (limited to '_log/matrix-digital-rain.md')
| -rw-r--r-- | _log/matrix-digital-rain.md | 111 |
1 files changed, 82 insertions, 29 deletions
diff --git a/_log/matrix-digital-rain.md b/_log/matrix-digital-rain.md index b1c5e6c..2c35361 100644 --- a/_log/matrix-digital-rain.md +++ b/_log/matrix-digital-rain.md @@ -1,51 +1,104 @@ --- title: Recreating the Matrix rain with ANSI escape sequences -date: 2022-08-22 +date: 2025-12-21 layout: post project: true thumbnail: thumb_sm.png --- -Over the weekend, I came across Domsson's <a -href="https://github.com/domsson/fakesteak" class="external" target="_blank" -rel="noopener noreferrer">Fakesteak</a>: a beautifully lean rendition of the -Matrix rain in raw C using ANSI escape sequences—zero dependencies, not even -ncurses. - -To keep things simple, Fakesteak didn't support Japanese characters and that it -used 8-bit color mode. The latter meant that the ghosting effect has to rely on -different foreground colors rather than shades of the same color. As a tip of -the hat to Domsson's impressive work, I decided to add Unicode and 24-bit -truecolor support to it, aiming to faithfully recreate the original Matrix from -the first movie during Neo and Cypher's conversation: +The Matrix digital rain implemented in raw C using ANSI escape sequences with +zero dependencies—not even ncurses. <video style="max-width:100%;" controls="" poster="poster.png"> <source src="matrix.mp4" type="video/mp4"> </video> -Adding Unicode support via `wchar_t` and `wprintf()` was easy enough. -Implementing the ghosting effect with truecolor support, however, turned out -harder than expected. To achieve the ghosting effect, I treated phosphor decay -as a multiplier, which allowed me to emulate the dim afterglow by gradually -transitioning each raindrop's color towards the background color: +This is a fork of Domsson's unique rendition of the Matrix rain: <a +href="https://github.com/domsson/fakesteak" class="external" target="_blank" +rel="noopener noreferrer">Fakesteak</a>. Three years ago, I forked his project +and added truecolor and Unicode support. I also drastically modified the +algorithm to produce a rain that resembled the original aesthetic with high +visual fidelity. + +## Unicode support + +Unicode support in the 2022 version lacked flexibility. The charset used in the +rain had to be a single contiguous block defined by `UNICODE_MIN` and +`UNICODE_MAX` settings: + +``` +#define UNICODE_MIN 0x0021 +#define UNICODE_MAX 0x007E + +static inline void insert_code(matrix *mat, + size_t row, size_t col) +{ + mat->code[index(mat, row, col)] = rand() + % (UNICODE_MAX - UNICODE_MIN) + + UNICODE_MIN; +} +``` + +There was no way, for instance, to use both ASCII and Katakana at the same +time. The user had to pick one. In the new version, the user can use any number +of Unicode blocks using `glyphs` array. In fact, the default rain now includes +both ASCII and half-width Katakana characters: + +``` +#define UNICODE(min, max) (((uint64_t)max << 32) | min) + +static uint64_t glyphs[] = { + UNICODE(0x0021, 0x007E), /* ASCII */ + UNICODE(0xFF65, 0xFF9F), /* Half-width Katakana */ +}; + +static uint8_t glyphlen = (sizeof glyphs) / (sizeof glyphs[0]); + +static inline void insert_code(matrix *mat, + size_t row, size_t col) +{ + uint64_t block; + uint32_t unicode_min, unicode_max; + + block = glyphs[(rand() % glyphlen)]; + unicode_min = (uint32_t)block; + unicode_max = (uint32_t)(block >> 32); + + mat->code[index(mat, row, col)] = rand() + % (unicode_max - unicode_min) + + unicode_min; +} +``` + +Entries in the `glyphs` array are Unicode blocks bit-packed in an 8-byte +container: the four low bytes forms the first codepoint and the four high bytes +the last. + +## Phosphor decay + +The dim afterglow of monochrome CRT displays is achieved by carefully scaling +the RGB channels individually and mixing them: ``` -static void mat_shade(matrix *mat, size_t row, size_t col) +#define DECAY_MPLIER 2 + +static inline void blend(matrix *mat, + size_t row, size_t col) { unsigned char *color; - color = mat->rgb[mat_idx(mat, row, col)].color; - color[R] = color[R] - (color[R] - COLOR_BG_RED) / 2; - color[G] = color[G] - (color[G] - COLOR_BG_GRN) / 2; - color[B] = color[B] - (color[B] - COLOR_BG_BLU) / 2; + + color = mat->rgb[index(mat, row, col)].color; + color[R] = color[R] - (color[R] - RGB_BG_RED) / DECAY_MPLIER; + color[G] = color[G] - (color[G] - RGB_BG_GRN) / DECAY_MPLIER; + color[B] = color[B] - (color[B] - RGB_BG_BLU) / DECAY_MPLIER; } ``` -Looking back at the implementation, there are still a few improvements to be -made. Instead of using a dedicated buffer, I should have bit-packed the -phosphor decay into the RGB data buffer to save memory. I'm not entirely -satisfied with the Unicode support as it's restricted to contiguous code -points. The glitch effect, which I implemented with characters unexpectedly -changing, would have been closer to the original if flashed white as well. +The blending function emulates the phosphor decay by gradually transitioning +each raindrop's color towards the background color. The multiplier is the +number of passes over the rain track needed before the afterglow disappears. + +## The algorithm Nonetheless, the rain resembles the original with high visual fidelity. It's highly customizable and gentle on the CPU. On my 14" ThinkPad T490, which has a |
