Redraw is currently in technical preview, available to start-react-native.dev subscribers. API is unstable.
Custom Colors
A color function is the same fn(...) you saw in Stroke, but
it writes to canvas.color (a vec4f) instead of canvas.field. The same
geometry context is available (ctx.t, ctx.sdf, ctx.tan,
ctx.grad), so colors can react to where on the path they're being painted.
Anatomy
import { fn, Color, interpolateColors } from "redraw";
import { d, std } from "typegpu";
const PathGradient = fn(
(canvas, ctx, props) => {
"use gpu";
const a = Color("#3FCEBC");
const b = Color("#DE589F");
canvas.color = d.vec4f(std.mix(a, b, ctx.t), 1.0);
},
{ /* default props */ },
);
Pass an instance to brush.addStroke(colorFn, width) or
brush.addFill(colorFn). With SingleStrokeBrush (see
Brushes) the full geometry context is populated; with
Brush you have ctx.sdf, ctx.pos, and shape bounds.
What you write
canvas.color = d.vec4f(r, g, b, a); // pre-multiplied RGBA
Anything from (0, 0, 0, 0) (transparent) to (1, 1, 1, 1) (opaque white).
Values outside [0, 1] are valid for HDR-style blending but get clamped on
output.
Helpers
Color("#hex") parses a string into a vec3f for use inside a fn:
const palette = [Color("#3FCEBC"), Color("#DE589F"), Color("#FAEC54")];
interpolateColors(t, colors) walks a palette and returns a struct with
.rgb:
const rgb = interpolateColors(ctx.t, palette).rgb;
canvas.color = d.vec4f(rgb, 1.0);
Both are exported from redraw. They generate WGSL inline; no runtime
overhead beyond the actual color math.
Recipes
Palette along the path
The Hello example walks an 11-color palette using ctx.t. A colorShift
prop animates the offset:
const ColorNode = fn(
(canvas, ctx, props) => {
"use gpu";
const palette = [
Color("#3FCEBC"), Color("#3CBCEB"), Color("#5F96E7"),
Color("#816FE3"), Color("#9F5EE2"), Color("#DE589F"),
Color("#FF645E"), Color("#FDA859"), Color("#FAEC54"),
Color("#9EE671"), Color("#41E08D"),
];
const rgb = interpolateColors(ctx.t + props.colorShift, palette).rgb;
canvas.color = d.vec4f(rgb, 1.0);
},
{ colorShift: 0 },
);
// Animate every frame:
colorNode.props.colorShift = ctx.time * 0.2;
Centerline highlight from ctx.sdf
ctx.sdf is negative inside the stroke and zero at the centerline. Combined
with canvas.field (the local half-width), you can compute distance from
the center as a normalized [0, 1]:
const distFromCenter = ctx.sdf + canvas.field * 0.5;
const centerFactor = std.saturate(1.0 - distFromCenter / (canvas.field * 0.5));
const finalRgb = std.mix(rgb, d.vec3f(1), centerFactor * props.progress);
This produces a white "ridge" along the spine of the stroke that fades to the underlying color at the edges.
Tangent sidedness
The cross product ctx.tan.x * ctx.grad.y - ctx.tan.y * ctx.grad.x is
positive on one side of the centerline and negative on the other:
direction-of-travel "left vs right". Use it for two-tone strokes or
gradients that span the stroke perpendicular to the path:
const RainbowColor = fn(
(canvas, ctx, props) => {
"use gpu";
const distFromCenter = ctx.sdf + canvas.field * 0.5;
const side = std.sign(ctx.tan.x * ctx.grad.y - ctx.tan.y * ctx.grad.x);
const signedDist = distFromCenter * side;
const normalized = (signedDist / (canvas.field * 0.5)) * 0.5 + 0.5;
const rgb = interpolateColors(normalized + props.colorShift, palette).rgb;
canvas.color = d.vec4f(rgb, 1.0);
},
{ colorShift: 0 },
);
Multi-palette + cel shading
Conditional palette selection plus smoothstep posterization gives a
crisp, illustrative look: