Background Remover
- Skia
- Reanimated
- Gesture Handler
A photo goes in and its background drops out, with a divider to drag across and compare against the original. The photo is studied once to choose the settings; after that a shader decides for every pixel how much to keep, and export draws the same tree again at full size.
Reading the photo once
analyze shrinks the photo onto a CPU raster surface over opaque white, so reading the pixels back is a memory copy, not a GPU flush that would stall JavaScript. Histograms of that thumbnail pick the cut. Two clear humps are split at the valley between them; a lopsided spread, the usual shape of thin line art on white, gets a triangle fit instead.
export function makeThumbPixels(srcImage: SkImage, N = 256): ThumbPixels { // … const surface = Skia.Surface.Make(tw, th); // CPU surface — SkSurface | null // … const canvas = surface.getCanvas(); canvas.clear(Skia.Color('white'));Whiteness is the smallest channel
On a white backdrop the shader keys on min(R, G, B) rather than brightness, so a bright yellow, whose blue channel is low, is kept. Saturation pulls keyness down further, and strongly colored pixels are always kept. smoothstep around the threshold gives the soft edge, and the result is returned premultiplied, because straight color would brighten the feathered edge over the checkerboard.
half mn = min(min(P.r, P.g), P.b);half mx = max(max(P.r, P.g), P.b);half sat = (mx > 1e-4) ? (mx - mn) / mx : 0.0;// white keys on the smallest channel, black on 1 - largesthalf keyn = (mode < 0.5) ? mn : (1.0 - mx);// …keep = 1.0 - smoothstep(threshold - feather, threshold + feather, keyn);Cleaning up the edge
An edge pixel is part subject, part backdrop. Against white, defringe solves for the subject color with (P + keep - 1) / keep, skipping pixels that are nearly gone, where the division would amplify noise. Edge shrink then runs as image filters on the keyed layer: erode, then dilate back one pixel less. Skia runs a filter's input first, so the erode has to be the innermost one.
if (erode > 0) f = Skia.ImageFilter.MakeErode(erode, erode, f ?? undefined); // runs firstif (dilate > 0) f = Skia.ImageFilter.MakeDilate(dilate, dilate, f ?? undefined);if (speck > 0) { f = Skia.ImageFilter.MakeErode(speck, speck, f ?? undefined); f = Skia.ImageFilter.MakeDilate(speck, speck, f ?? undefined);}Tuning
- 0.92
- 0.04
- 0
- 0
- 0
- 0