Back to work Side Project

Bitmap Dream

A zero-dependency dither engine that turns photos and math fields into pure-SVG retro bitmap art

Role
Generative Engine、Front-end Development、Interaction Design
Period
2026.04 – 2026.06
Scope
Generative Art、SVG、TypeScript、Dithering、React Playground
The Bitmap Dreams playground — a print-shop workbench with scene, photo, and layer panels on the left and a dithered violet illustration on the canvas
6
Mark renderers
0
Runtime dependencies
186
Tests passing
15+
Preset scenes

Background & Challenge

I kept hitting the same wall: a side project needs illustration, but stock assets feel generic and a real photo rarely fits the worldview. The alternative—commissioning or hand-drawing every visual—doesn’t scale across projects. So the goal of Bitmap Dream was to stop hunting for assets and start generating them: a single engine that turns a photo, or a piece of pure math, into a retro bitmap illustration that I can re-skin, re-color, and re-seed at will.

The hard constraint was that the output had to be production-grade and portable. It must run in the browser for an interactive playground, in Node for tests, and inside a build script so a host site can inline the result as static SVG. That ruled out canvas-pixel hacks and any runtime dependency. The engine had to be a pure function: pixels in, paths out, same seed in, same image out—every time.

Approach & Craft

The pipeline reads as one sentence: a density field describes where the image runs dark or light, a quantizer snaps that continuous field into discrete levels, and a mark renderer decides what each cell actually looks like. Splitting it this way means I can swap any stage without touching the others—the same density field can come out as a halftone poster or a hatched engraving just by changing the last step.

One radiating-sun density field rendered side by side through six marks: Bayer 8×8, blue-noise, halftone dots, scanlines, hatch, crosshatch; the solid central disk is identical across all six while the outer texture differs
One density field (the footer's radiating sun) handed to all six mark renderers—the solid disk is identical; only the stroke geometry changes
One scanline-rendered sun shown side by side under three two-tone palettes: indigo-and-orange, a violet duo, and a mono indigo; the scanline geometry is identical across all three, only the color changes
One scanline render, re-skinned with three palettes—the site's indigo-and-orange, the origin violet, and a mono indigo. All three reuse the same paths; recoloring recomputes nothing

Determinism is the spine of the whole thing. Every bit of randomness is derived from a seed, so a scene is fully reproducible, version-controllable, and testable—186 tests pin the output exactly. On the rendering side, same-level cells get merged with run-length encoding so a flat region collapses into a single path instead of thousands of rectangles, keeping the SVG node count—and the file size—sane.

Outcome & Reflections

Bitmap Dream passed its first real-world test as the generative visual layer of this very portfolio. Its renderers were vendored into the site and run at build time to inline SVG directly: the dithered portrait in the hero, a riso plate on each project card, the eclipse in the footer, plus the fixed dot field behind every page—all in the site’s indigo-and-orange two-tone palette, none hand-drawn. The hero portrait in particular exercises the engine’s real photo → density branch: a photo of me is turned into a density field and handed to the mark renderer, proving the field layer isn’t only math. And because the visuals are inlined rather than <img>, their two ink colors map to CSS variables and flip with the light/dark theme.

That adoption fed straight back into the engine. The big lesson came from the host site: scanlines emit one rectangle per cell with no horizontal merge, so a full-bleed image explodes the path count and the file size. The fix on the consuming side was to keep the grid small and finish with a compact scene—and it logged a clear next step for the engine: add same-row merging to the scanline renderer. There’s a known limitation too—when hatch and crosshatch tiles are exported as a single tile and CSS-repeated, the edges clip into faint seams; normal previews are unaffected, and the proper fix is to draw the strokes wrapping around the tile, which is queued.

The through-line of this project is the shift it forced on how I make visuals: instead of sourcing art, I translate an art direction into rules a program can generate from. The engine itself becomes the asset—and once it lives in a build step, every project that adopts it gets a consistent, re-colorable, infinitely re-seedable illustration system for free.