Skip to main content
Technical preview

Redraw is currently in technical preview, available to wcandillon.dev subscribers. API is unstable.

Vector Feathering

Most 2D pipelines blur things in raster: sample neighboring pixels, weight them, sum. That's an expensive operation, and quality degrades at small radii.

Redraw blurs analytically. The feathering step modulates the alpha coverage applied to a shape, computed per pixel from the shape's signed distance: no offscreen layers, no sampling, O(1) per pixel. That's what makes a few effects raster pipelines reach to post-process for practical at interactive frame rates: motion blur from a velocity vector, backdrop filters that compose, glow that scales without artifacting.

import { Circle, Feather, Paint } from "redraw";

const paint = new Paint()
.setColor("#3FCEBC")
.setFeather(Feather.glow(50));
canvas.draw(new Circle([cx, cy], 120), paint);

A feather is a falloff curve (gaussian or linear) combined with a mode (where the falloff is applied around the edge).

There are two curves:

enum FeatherCurve {
Gaussian, // erf-based bell, most natural
Linear, // simple linear falloff
}

Default is Gaussian. Every factory below accepts a FeatherCurve as its last argument: Feather.glow(50, FeatherCurve.Linear).

And seven modes:

FactoryModeWhat it produces
Feather.blur(sigma, curve?)UniformSymmetric blur on both sides of the edge.
Feather.glow(sigma, curve?)GlowSharp inside, soft outward (outer halo).
Feather.inner(sigma, curve?)InnerSharp outside, soft inward (inner shadow).
Feather.outer(sigma, curve?)OuterTransparent inside, soft outside (drop-shadow shape).
Feather.inset(sigma, curve?)InsetSoft outward, clipped to the original boundary (inset highlight).
Feather.radial(sigma, [cx, cy], curve?)RadialBlur radius grows with distance from a center point.
Feather.sweep(sigma, [dx, dy], curve?)SweepSigma varies along a direction vector (motion blur, glass).

(Feather.linear(sigma) is shorthand for a uniform linear-falloff feather.)

The feather is part of the paint, and the canvas is immediate mode: to animate a blur, build the feather with the new sigma when you record the frame. For sigma that varies per pixel, author your own feather function instead: see Custom Effects: Feather.

Analytical motion blur

Motion blur is normally a post-processing pass. In Redraw it's a property of the paint: set the direction to your velocity vector and sigma to its magnitude, and the renderer produces motion blur at the correct intensity per pixel:

export function render(canvas: Canvas, info: FrameInfo) {
// ... integrate the velocity (vx, vy) ...
const speed = Math.hypot(vx, vy);
const paint = new Paint()
.setColor("#0a0a1a")
.setFeather(
Feather.sweep(Math.min(speed * 0.25, 50), [-vx / speed, -vy / speed]),
);
canvas.draw(new Circle([cx, cy], 120), paint);
}

Fused backdrop filters

canvas.pushLayer({ clip, feather, tint }) opens a layer: a region whose draws are feathered only where the region reads inside, plus an optional tint filled over the region when the layer pops. The region is an analytic field re-evaluated per pixel, so a layer costs no offscreen surface and no rasterization; nested layers intersect and their feathers compose.

canvas.pushLayer({
clip: { fn: CircleClip, props: { center: clipCenter, radius } },
feather: 50,
tint: new ColorFill("rgba(255, 255, 255, 0.3)"),
});

// ... draw the content this layer feathers ...

canvas.popLayer(); // applies the tint and pops

The LayerDescriptor:

FieldTypeWhat it does
clipa clip-kind function bindingThe layer region. Same contract as canvas.clip: props are read under each draw's transform.
feathernumberGaussian sigma feathering the draws inside the layer, applied only where the region reads inside. 0 disables it.
tinta color-kind binding (optional)Filled over the region (antialias-clipped) when the layer pops, e.g. new ColorFill("rgba(0, 0, 255, 0.5)").

The region is a clip-kind function; author one with the clip() helper (see Drawings). The demo below drifts a gradient disc and the layer region apart, which makes the backdrop machinery visible: the feather applies only where the region reads inside.

Frosted glass without rasterization: a feathered, tinted layer region over a moving discOpen in editor →