Skip to main content
Technical preview

Redraw is currently in technical preview, available to start-react-native.dev subscribers. API is unstable.

Custom Feathers

A feather function writes the blur sigma per pixel instead of writing a color or a stroke width. The renderer reads canvas.field as sigma during the feather pass and produces analytical blur with a different radius at every fragment.

This page is for the write your own sigma case. For ready-made feather modes (uniform, glow, sweep, radial, etc.) see Vector Feathering.

How it works

  1. You write a fn(...) that sets canvas.field to your desired sigma per pixel.
  2. You wrap it in ApplyFeather(yourFn). That's the bridge between a user fn and the feather pipeline.
  3. Pass the wrapped feather as the third argument to addStroke or addFill.
import { fn, ApplyFeather, SingleStrokeBrush } from "redraw";
import { d, std } from "typegpu";

const VariableSigma = fn(
(canvas, ctx, props) => {
"use gpu";
canvas.field = std.mix(40.0, 2.0, props.focus); // 40 blurry, 2 sharp
},
{ focus: 0 },
);

const sigma = new VariableSigma({ focus: 1 });
const brush = new SingleStrokeBrush();
brush.addStroke(color, strokeWidth, new ApplyFeather(sigma));

ApplyFeather runs your fn first (writing the sigma to canvas.field) and then applies a Feather.fromContext() blur using that sigma.

Recipe: orbiting focus

The "variable blur" demo runs a moving focal spot along the path: sharp at one point on the curve, blurry everywhere else. The function uses ctx.t to locate the current pixel along the arc length and props.time to slide the focus along it:

const OrbitingFocus = fn(
(canvas, ctx, props) => {
"use gpu";
const center = std.mod(props.time * 0.25, 1.0);
const dist1 = std.abs(ctx.t - center);
const dist2 = 1.0 - dist1;
const dist = std.min(dist1, dist2); // wrap-around distance
const focus = std.exp(-std.pow(dist * 8.0, 2.0)); // gaussian peak
canvas.field = std.mix(40.0, 2.0, focus);
},
{ time: 0 },
{ maxCullDistance: 100 },
);

const focus = new OrbitingFocus({ time: 0 });
brush.addStroke(color, strokeWidth, new ApplyFeather(focus));

// Animate every frame:
focus.props.time = ctx.time;
A moving focus point along the path; sharp at the peak, blurry elsewhereOpen in editor →

When to reach for this

Most use cases are covered by the static factories in Vector Feathering:

You wantUse
Uniform blur on a shapeFeather.blur(sigma)
Outer glow / inner shadowFeather.glow(sigma) / .inner(sigma)
Drop shadowFeather.outer(sigma)
Motion blur from a velocity vectorFeather.sweep(sigma, [dx, dy])
Radial blur (lens-like)Feather.radial(sigma, [cx, cy])
Variable per-pixel sigma from a fnnew ApplyFeather(yourFn) (this page)

If your sigma can be expressed as a function of ctx.t, ctx.sdf, or ctx.pos, the ApplyFeather path gives you per-pixel control. Common recipes: depth-of-field along a curve, focus-pull animations, sigma that ramps with arc length.