Engineering Notes

FaceFizz: Building a Privacy-First Camera Inside the Browser

An engineering retrospective on FaceFizz, from camera permissions and real-time Canvas effects to local MediaPipe segmentation and explicit resource cleanup.

HOUHUIYANG.COM

Scan to continue reading

Generating…

FaceFizz: Building a Privacy-First Camera Inside the Browser

houhuiyang.com/en/notes/facefizz-privacy-first-browser-camera

I have made the source code for FaceFizz.fun publicly available on GitHub. FaceFizz is a browser photo booth: no app installation and no account are required. After granting camera access, a user can preview live warps, clones, stickers, frames, art filters, festival looks, and background scenes, then save a photo locally.

The product is compact, but its main path combines browser media APIs, real-time graphics, on-device AI, privacy boundaries, responsive design, localization, and discoverability. This article explains the decisions behind that path rather than repeating a feature list.

Start with the experience boundary

The value of a funny camera is not the number of filters. It is how quickly a first-time visitor reaches a result worth keeping. FaceFizz therefore follows several constraints:

  1. The camera comes before registration—there is no required account.
  2. The purpose of camera permission is explained, and the stream stops when the camera closes.
  3. Live frames and generated photos are not uploaded by default.
  4. The homepage presents eight curated entry effects instead of overwhelming users with quantity.
  5. Desktop supports exploration, while mobile keeps permission, effect selection, capture, and save within a simple one-handed flow.

These constraints lead to a local browser pipeline rather than an upload–process–download architecture.

The interface

The desktop homepage uses an editorial layout that combines effect categories, a preview mosaic, privacy messaging, and a clear camera action.

FaceFizz desktop homepage

The mobile version reorganizes information density instead of merely shrinking the desktop grid.

FaceFizz mobile homepage

The browser processing pipeline

Camera permission
  → getUserMedia MediaStream
  → live video frames
  → mirror and cover crop
  → Canvas 2D effect pipeline
      ├─ warps / mirrors / mosaics
      ├─ color / pixel / art filters
      ├─ stickers / frames / festival assets
      └─ MediaPipe segmentation → background compositing
  → requestAnimationFrame preview
  → countdown capture
  → JPEG export
  → local download

The browser requests the user-facing camera with an ideal 1280×1280 resolution. Effects are rendered into a consistent 720×720 Canvas. A fixed internal canvas simplifies differences in device video dimensions and lets the live preview and saved result share the same rendering path.

The render loop is driven by requestAnimationFrame. Frames are cover-cropped, mirrored through an intermediate Canvas when necessary, and passed to the selected effect. Capture does not reproduce those effects in a second implementation; it exports the current output Canvas as JPEG at quality 0.92. That avoids drift between what the user previews and what they save.

Why Canvas 2D was the right MVP choice

Canvas 2D covers the first product scope well: region stretching, slice displacement, mirrors, grids, pixelation, color treatment, image overlays, frames, and mask compositing. It is easy to debug, integrates naturally with image assets, and works across modern desktop and mobile browsers.

The trade-off is main-thread pressure. Complex mesh warps, high-resolution pixel work, and many compositing layers will eventually need WebGL2 shaders, Workers, and capability-based degradation. Canvas 2D is the right validation tool, not necessarily the final rendering engine.

MediaPipe enters only when needed

Background replacement uses MediaPipe ImageSegmenter and a locally hosted Selfie Segmenter model. Camera frames do not need to be sent to a remote inference API.

Initialization prefers a GPU delegate and falls back to CPU when GPU setup fails. Segmentation also does not run on every display frame. It is throttled to roughly 80 ms, and the latest mask is reused between inference passes. Separating display refresh from model inference reduces main-thread work and device heat.

Video frames ─────────────→ real-time Canvas rendering
      └─ about every 80 ms → ImageSegmenter → person mask
                                              ↓
background + source frame + mask → composite result

Edge refinement, hair detail, low-end latency, and custom-background memory use remain areas for further device testing.

Privacy is a resource lifecycle

“Processed locally” must be visible in code, not only in copy. FaceFizz currently:

This boundary also removes backend complexity: the MVP needs no face-photo storage, retention workflow, or gallery authorization model. Privacy engineering directly reduces system cost.

Type safety before plug-in architecture

The current implementation defines explicit CategoryId, EffectId, and Effect types. Categories, effect metadata, colors, and localized names are checked by TypeScript. Curated effects shape the homepage while broader categories live in the picker.

The honest limitation is that much of this logic still lives in one large client page, and renderer behavior is organized mainly through branches. That is efficient for validating the product, but it will not scale indefinitely. A declarative template protocol should eventually separate effect metadata, assets, capability requirements, and renderer selection:

type EffectTemplate = {
  id: string;
  version: number;
  category: "warp" | "sticker" | "frame" | "filter" | "background";
  preview: string;
  assets: string[];
  renderer: "canvas2d" | "webgl" | "segmentation";
  capture: { aspectRatio: "1:1" | "3:4" | "9:16" };
  capability: { webgl2?: boolean; segmentation?: boolean };
};

The page can then own interaction state, renderers can own execution, and templates can be registered and tested independently.

Seven languages and machine discoverability

FaceFizz supports Simplified Chinese, Traditional Chinese, English, Japanese, Korean, Thai, and Bahasa Melayu. Localization affects much more than strings: title length, card height, mobile wrapping, font fallback, and metadata all change.

The current typed dictionaries are appropriately lightweight for a single-page product. Growth would justify split language packs, lazy loading, missing-key checks, and screenshot regression tests. The project also publishes structured data, a sitemap, robots.txt, llms.txt, and llms-full.txt to make the product understandable to search engines and AI retrieval systems.

What should improve next

The project uses React 19, TypeScript, Vinext, Vite, Tailwind CSS 4, and MediaPipe Tasks Vision. ESLint, production builds, and rendered-HTML tests provide a baseline. The next engineering priorities are clearer:

What I learned

On-device AI is not merely a way to avoid API cost. It shortens feedback, reduces network dependency, and creates a privacy boundary users can understand.

Real-time products also cannot be judged only by whether a feature works. Camera startup, first-frame latency, render FPS, inference cadence, heat, and cleanup together define the experience.

Finally, an MVP does not need the most advanced architecture. Canvas 2D, local inference, and a single-page state model are enough to validate the core experience—as long as the boundaries that must evolve are recorded honestly.

The real north-star metric is not page views. It is the number of people who successfully create, save, or choose to share a photo. Technology ultimately serves the moment that makes someone smile.

Try it and read the source

License note: as of August 11, 2026, the repository exposes its source but the README still says “All rights reserved” and no standalone open-source license is present. Strictly speaking, it is currently source-available rather than an OSI-licensed open-source project. Community reuse requires an explicit license such as MIT or Apache-2.0.

Back to Engineering Notes