Back to projects

Platform · Shipped

PixelBrix

A browser app I built that turns any photo into a buildable brick mosaic. It maps every pixel to a real brick color and generates an exact, orderable parts list, with no server involved.

Canvas APIColor ScienceTypeScript (strict)ReactTDD
Try it live →

// what i built

I wanted a photo to become something you could actually build, not just a filtered image. So I wrote the whole pipeline myself: perceptual color matching, contrast handling, and a row-by-row parts list, all running client-side on the Canvas API. The hard part was never resizing the photo, it was color, mapping every pixel onto a fixed set of real, purchasable brick colors.

// photo in, mosaic out

// every color, counted

The output: every brick color, its code, and exactly how many you need. This McLaren is 9,216 bricks on the 96×96 grid.

// the pieces

Perceptual color matching

Every pixel converts to CIE LAB, then matches the nearest brick by Delta-E against a hand-tuned palette with pre-computed LAB values (about 50ms for a full 48×48 grid). I chose CIE76 over CIEDE2000 on purpose: roughly 3× faster, with a difference that's imperceptible at brick resolution.

CLAHE contrast enhancement

Contrast-Limited Adaptive Histogram Equalization for flat, dominant-color photos, with original-mean-luminance restoration so the mosaic never comes out darker than the photo you put in.

Three art styles

Classic (clean blocks), Comic (edge-detected outlines), and Pop (bold flat colors). Each is a distinct sampling algorithm, not a filter laid on top.

Two build sizes

48×48 (2,304 bricks) and 96×96 (9,216 bricks), with a crop selector to frame the shot before it renders.

Build guide + tile animation

A parts-list generator with per-color counts and totals, plus a GPU-composited CSS flip-tile animation that runs up to 2,304 simultaneous flips on the compositor thread, a measured decision over reaching for a JS animation library.

// how it's engineered

TypeScript, strict

No any, no non-null assertions, a typed error hierarchy (PixelBrixError down to domain errors), and a Result pattern for predictable failures.

Test-driven

31 Vitest tests, with 100% coverage on the core color-matching and build-guide logic.

Dependency injection

Business logic depends on interfaces (like a StorageProvider), never directly on the AWS SDK, so the core stays unit-testable.

Clean architecture

Small single-responsibility modules and documented ADRs for the decisions that mattered.

// tech stack

Next.jsReactTypeScriptTailwindCanvas APIVitestPlaywright
EOF

Joey Schnepel · Phoenix, AZ · 2026