FilterCam
Status — shipped. The camera filter module behind dona.uz live-shopping streams — face-tracked beauty filters written natively in Kotlin and Swift, behind one JS component.
Status: shipped, and it was never a side project. FilterCam is the camera filter module we added to the streaming section of dona.uz — the live-shopping marketplace where sellers go on air and show a product in real time while buyers ask questions and order inside the app.
Why it exists
On a live-shopping marketplace the scarce resource isn’t buyers, it’s sellers who actually go live. A stream that doesn’t happen is inventory that doesn’t move. And the most common reason a seller doesn’t press “go live” has nothing to do with the product — it’s that they don’t want to spend an hour looking at themselves on camera.
So the filter isn’t decoration on top of the camera. It’s supply-side retention: it lowers the cost of being on air. That’s the whole business case, and it’s the reason this got built properly instead of being bolted on.
One thing I want to be precise about, because I framed it wrong to myself at first: this was not a JavaScript feature. Nothing interesting about it lives in the JS layer. JS opens the camera and moves sliders; everything that matters happens per frame on the GPU, in native code.
What it does
- Beauty — GPU skin smoothing applied only to the face: MediaPipe Face Mesh (478 landmarks) builds a mask from the face oval with the eyes, brows and lips punched out, then an OpenGL blur/composite pass smooths just that region. Alongside it: glow, clarity (even tone / redness) and warmth.
- Reshape — eye-enlarge, nose-slim and face-slim liquify warps, each scaled to the tracked face.
- Sprite overlays — the same landmark pipeline anchors an image to the face and follows position, scale and head roll in real time (the demo app wears a mustache).
- Captures save the filtered frame — what the seller sees is what goes out.
Android runs Kotlin + OpenGL ES, iOS runs Swift + Metal + Vision, both behind the same JS component, tracking up to five faces at once.
What it cost me to learn
- The frame budget is the spec. At 30fps you have ~33ms for capture, landmark detection, mask, blur, warp and composite. Every design decision is downstream of that number.
- Two platforms, two shader languages, one API. Parity between Metal and OpenGL is harder than either pipeline on its own — the same slider has to mean the same thing on both, and it still drifts.
- Native modules changed how I read React Native. See React Native for the concept map.
- I built the iOS half without owning a Mac, from NixOS — see NixOS & Linux and the write-up.
Next
- Close the remaining Metal ↔ OpenGL drift.
- Filtered video recording, not just stills.
- Presets as data, so the marketplace team can ship a new look without a release.
comments