Back to work Side Project
Bitmap Dream
A zero-dependency dither engine that turns photos and math fields into pure-SVG retro bitmap art
- 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.
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.