Minecraft Mosaic
A lunchbreak project that hit Reddit
View Live Project
Browser-based image-to-Minecraft-block mosaic generator
Minecraft Mosaic converts any uploaded image into a mosaic built entirely from Minecraft block textures. Drag a slider to control the resolution, from 16 to 256 blocks wide, and compare the result against the original with a side-by-side split view.
Everything runs locally in the browser with no server round-trips.
Technical Approach
Rust to WebAssembly Pipeline:
The image processing core is written in Rust and compiled to WebAssembly via wasm-pack, producing a ~63KB module that loads instantly. When a user adjusts the block-width slider, the browser passes raw pixel data directly into WASM memory with no
serialization overhead and gets back a rendered RGBA buffer of the mosaic. This keeps resize-and-rerender cycles fast enough to feel interactive even at 256-block widths, while keeping all image data on the client. No images ever leave the browser.
Perceptual Color Matching in CIE LAB:
Naively matching pixels to blocks in RGB space produces visually jarring results because RGB distances don't correspond to how humans perceive color differences. Instead, each pixel region is averaged and converted through sRGB linearization and a
D65-illuminant XYZ transform into CIE LAB, a perceptually uniform color space where Euclidean distance reliably reflects what the eye sees. The 689-block palette ships with precomputed LAB values, so matching is a single pass of squared-distance
comparisons with no square roots.
Dynamic Texture Atlas:
On first load, all 689 block textures (16×16 PNGs) are fetched in parallel and composited into a single atlas on an OffscreenCanvas. This atlas is passed to WASM as a flat pixel buffer, letting the renderer copy block textures into the output image with
direct memory slicing rather than per-pixel drawing calls.
Key Decisions
- Rust + WASM over JavaScript for the inner loop. The mosaic generator iterates every pixel of the source image and compares against 689 palette entries, a workload where WASM's predictable performance and typed memory access outperform JIT-compiled JS by a meaningful margin, especially on mobile devices with constrained thermal budgets.
- No framework, no bundler. The frontend is a single HTML file with a module script that imports the pre-compiled WASM package directly. Deploying to Cloudflare Pages requires no build step, just static files. This eliminates an entire class of toolchain issues and keeps the dependency surface at zero.
- Canvas stacking for the compare view instead of re-rendering on drag. The original image and the mosaic each occupy their own canvas, layered with CSS Grid. The split handle simply adjusts a clip-path: inset() on the foreground canvas, so dragging the slider is a pure CSS operation with no recomputation.
- LAB over Delta-E 2000. Full CIEDE2000 is more accurate for edge-case color pairs but involves trigonometric operations per comparison. With 689 palette entries evaluated per cell, the simpler squared LAB distance keeps matching fast while still producing results that look correct to the eye.
- 689-block palette, curated by hand. Every block face in modern Minecraft was evaluated and a handful (like dried kelp) were removed for producing poor results, blocks whose average color misrepresented their visual texture. The palette is large enough to cover the full color gamut without introducing blocks that degrade output quality.
Stack
Rust, WebAssembly (wasm-bindgen), vanilla JavaScript, Canvas API, CSS Grid, Cloudflare Pages
Project Gallery
Other Projects

Photo Jam - Photography Community App
Cross Platform Mobile App with Flutter/Appwrite

Automated Slide Digitizer
High-resolution film slide scanning system using modified projector

Water Bath Temperature Control
Precision temperature controller for film development chemistry

Bib Tagger
Automatically Update Photo Metadata with Athlete Bib Number

