Mosaik

Mosaik is a browser-based generative art engine to convert any image or video into dithered dot art, ASCII animations, or halftone graphics using real-time canvas rendering with interactive physics.

Mosaik
The Problem With Canvas Performance

The core challenge was performance. Dithering algorithms like Floyd-Steinberg iterate over every pixel sequentially, which blocks the main thread and freezes the UI on large images. The solution was offloading all processing to Web Workers using OffscreenCanvas: the main thread stays free, canvas rendering holds 60fps, and the UI stays responsive even mid-computation. GIF export from video frames reuses the same pipeline. The harder part was OffscreenCanvas browser support, as transferControlToOffscreen() isn't available in all environments. A fallback path uses createImageBitmap() to pass image data to the Worker without the OffscreenCanvas transfer, at a slight performance cost. The fallback is transparent to the user.

Built using Next.js, TypeScript, Tailwind CSS, Web Workers, OffscreenCanvas

Technical Decisions
Why four dithering algorithms?

Floyd-Steinberg, Atkinson, Bayer ordered, and Hard Threshold each produce visually distinct results, representing fundamentally different aesthetics. Floyd-Steinberg diffuses error to four neighbours and produces smooth, photographic results. Atkinson only diffuses 75% of the error, which creates harder edges and a more graphic feel. Bayer uses a threshold matrix and produces a regular, mechanical pattern with no error diffusion at all. Offering all four means the tool produces genuinely different outputs, not variations on the same look.

Why repulsion physics?

Standard dithering maps pixel brightness to dot size or presence. Repulsion physics adds a second layer where dots push each other apart based on proximity, creating an organic, irregular spacing that no dithering algorithm produces on its own. It's computationally expensive (O(n²) naive, approximated with a spatial grid) but it's the feature that makes Mosaik outputs visually distinctive. You can tell a Mosaik image from a Photoshop halftone immediately.

Why export as a React component?

PNG and SVG are obvious. React component export was added because developers using Mosaik for creative projects don't want to host an image file; they want to drop the pattern directly into their codebase as a self-contained component. The export encodes the dot positions and sizes as JSX, with Tailwind-compatible props for color and scale. No image files, no external dependencies.

Key Decisions & Outcomes
  • Moved dithering computation to Web Workers via OffscreenCanvas, ensuring the main thread stays free and canvas rendering holds at 60fps even on high-resolution inputs
  • Implemented four dithering algorithms (Floyd-Steinberg, Atkinson, Bayer, Hard Threshold) so the output aesthetic can shift from smooth gradients to sharp pixel clusters
  • Added repulsion physics where dots push each other apart, creating outputs that no standard dithering tool can replicate
  • Export to PNG, SVG, WebM, and drop-in React component so the output works as art, animation, or production-ready code

If you're curious, feel free to explore: