diff options
Diffstat (limited to '_site/log/matrix-digital-rain/index.html')
| -rw-r--r-- | _site/log/matrix-digital-rain/index.html | 105 |
1 files changed, 78 insertions, 27 deletions
diff --git a/_site/log/matrix-digital-rain/index.html b/_site/log/matrix-digital-rain/index.html index cf04bde..6007da5 100644 --- a/_site/log/matrix-digital-rain/index.html +++ b/_site/log/matrix-digital-rain/index.html @@ -42,45 +42,96 @@ <div class="container"> <div class="container-2"> <h2 class="center" id="title">RECREATING THE MATRIX RAIN WITH ANSI ESCAPE SEQUENCES</h2> - <h6 class="center">22 AUGUST 2022</h5> + <h6 class="center">21 DECEMBER 2025</h5> <br> - <div class="twocol justify"><p>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.</p> - -<p>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:</p> + <div class="twocol justify"><p>The Matrix digital rain implemented in raw C using ANSI escape sequences with +zero dependencies—not even ncurses.</p> <video style="max-width:100%;" controls="" poster="poster.png"> <source src="matrix.mp4" type="video/mp4" /> </video> -<p>Adding Unicode support via <code class="language-plaintext highlighter-rouge">wchar_t</code> and <code class="language-plaintext highlighter-rouge">wprintf()</code> 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:</p> +<p>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.</p> + +<h2 id="unicode-support">Unicode support</h2> + +<p>Unicode support in the 2022 version lacked flexibility. The charset used in the +rain had to be a single contiguous block defined by <code class="language-plaintext highlighter-rouge">UNICODE_MIN</code> and +<code class="language-plaintext highlighter-rouge">UNICODE_MAX</code> settings:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#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; +} +</code></pre></div></div> + +<p>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 <code class="language-plaintext highlighter-rouge">glyphs</code> array. In fact, the default rain now includes +both ASCII and half-width Katakana characters:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#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]); -<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>static void mat_shade(matrix *mat, size_t row, size_t col) +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; +} +</code></pre></div></div> + +<p>Entries in the <code class="language-plaintext highlighter-rouge">glyphs</code> 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.</p> + +<h2 id="phosphor-decay">Phosphor decay</h2> + +<p>The dim afterglow of monochrome CRT displays is achieved by carefully scaling +the RGB channels individually and mixing them:</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#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; } </code></pre></div></div> -<p>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.</p> +<p>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.</p> + +<h2 id="the-algorithm">The algorithm</h2> <p>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 |
