Skip to main content
Technical preview

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

Paints

A Paint describes how a draw is shaded: its color, optional color filters, an optional stroke, an optional feather, and a blend mode. You pass one to every drawing call (draw, drawPath, fill).

import { Feather, Paint } from "redraw";

const paint = new Paint()
.setColor("#3FCEBC")
.setFeather(Feather.glow(20));
canvas.draw(new Circle([200, 200], 80), paint);

Bindings

Every slot of a Paint holds a binding: a GPU function paired with the props it is invoked with. There are three equivalent ways to set one:

// Sugar for solid colors (shorthand for addShader(new ColorFill("black"))):
paint.setColor("black");

// A prebuilt binding object:
paint.addShader(new GradientAlongPath(["red", "blue"]));

// Or the raw (function, props) pair, for functions you authored yourself.
// 0.2 palette cycles per second (time is in milliseconds):
paint.addShader(PathGradient, { shift: time * 0.0002 });
paint.setStroke(HelloStroke, { width: 25 });

Each setter checks the function's kind: addShader takes color functions, setStroke takes a strokeWidth function, and setFeather takes a feather function. Any custom function must also be declared in the canvas's Library.

API

MethodWhat it does
setColor(color)Sugar for a solid base color: any CSS-style color or [r, g, b, a] array (shorthand for addShader(new ColorFill(color))).
addShader(binding) or addShader(fn, props)Append a color shader: the base color first (a ColorFill, a gradient, an ImageShader, or a custom color function), then any filters, each reading and overwriting the running color. Every draw needs at least one.
setStroke(...)Stroke instead of filling: the shape's SDF is banded by the returned width. See custom strokes.
setFeather(...)Feather the coverage analytically. See Vector Feathering.
blendMode = BlendMode.ScreenComposite mode (SrcOver default, Multiply, Screen, ...).

All setters return this, so they chain.

Solid colors

setColor accepts any CSS-style color (hex, rgb(), rgba(), named colors) or an [r, g, b, a] array:

const background = new Paint().setColor("rgb(36,43,56)");
canvas.fill(background);

Paints are plain objects: build the constant ones once at module scope and reuse them across frames.

Color filters

Color filters are color-kind functions that compose in order after the base color: append them with addShader. The built-ins mirror the usual suspects:

import { ColorMatrix, Duotone, Grain, Posterize } from "redraw";

paint.addShader(ColorMatrix.grayscale());
paint.addShader(new Grain(0.1));
FilterWhat it does
new ColorMatrix(values)A 4x5 color matrix. Factories: grayscale(), sepia(), saturate(s), hueRotate(a), brightness(b), contrast(c), invert().
new Grain(intensity, seed?)Film grain hashed by screen position.
new Posterize(levels)Reduce each channel to levels bands.
new Duotone(dark, light)Map luminance onto a two-color ramp.

Unlike ColorFill, the color-filter functions are not part of std: pass the ones you use to createLibrary (each class exposes its function as .fn, and colorFilters exports them all):

import { colorFilters, createLibrary } from "redraw";

const library = createLibrary(device, [...colorFilters]);
// or just the one you need: createLibrary(device, [Grain.fn])

You can also author your own with the color() helper.

Blend modes

paint.blendMode selects how the draw composites over what's already on the canvas. The breathe demo screen-blends glowing circles so the overlaps bloom:

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

const paint = new Paint()
.setColor(i % 2 ? c1 : c2)
.setFeather(Feather.glow(100));
paint.blendMode = BlendMode.Screen;
canvas.draw(new Circle([x, y], radius), paint);
Screen-blended glows: overlaps bloom instead of darkeningOpen in editor →

Culling and maxCullDistance

A stroke or a feather spreads coverage past the geometry's bounds. Each binding declares how far, in device pixels, through maxCullDistance; the draw's bounding box is padded by the paint's total so tile binning never culls the spilled band. The built-in Feather factories compute it from sigma automatically; a custom feather sets maxCullDistance in its options, and a custom stroke declares maxStrokeWidth (the pad is half of it; see Custom Effects). If the edge of a wide stroke or glow looks clipped along tile boundaries, this is the knob.