Ink
inkOutline(samples, options) turns the FreehandSamples of a freehand drawable into a closed polygon. It is the outline of a pen line, wider where the pen pressed harder. The paint layer smooths the polygon and fills it with the stroke colour. Ink is filled, not stroked. The algorithm is a port of the look prototype in ADR 0005 (docs/decisions/0005-hand-drawn-canvas-renderer.md), which matched perfect-freehand 1.2.2 by eye with Excalidraw’s settings.
type InkOptions = { readonly strokeWidth: number; readonly simulatePressure: boolean;};| Option | Meaning |
|---|---|
strokeWidth |
The shape’s stroke width. The pen size is 4.25 times it, as in Excalidraw |
simulatePressure |
true ignores each sample’s pressure and derives one from speed. Use it for mouse input |
How it draws
- Streamline. Each point moves 57.5 percent of the way from the previous output point towards its sample, which irons out the jitter of the pointer. Points are dropped until the stroke has run one pen size, because a stroke starts slowly and those first points would make a fat blob. The last raw sample is kept as it is, so the line reaches the pointer. Points closer than 1e-6 to the previous one are dropped. This is
getStrokePointsfrom perfect-freehand 1.2.2 with streamline 0.5. - Pressure. With real pressure, each point keeps the pressure of its sample, from 0 to 1. With simulated pressure, each step eases the pressure towards
1 - speedat a rate ofspeed × 0.275, where speed is the step length divided by the pen size, capped at 1. A fast stroke thins and a slow one thickens. The starting pressure is the average of the first 10 steps, warmed up from 0.25, as perfect-freehand does. On evenly spaced test strokes the widths match perfect-freehand 1.2.2 with Excalidraw’s settings to two decimals. - Radius. The radius at a point is
size × sin((0.5 - 0.6 × (0.5 - pressure)) × π / 2), with thinning 0.6, and never less than 0.5. Pressure 0.5 gives about 0.71 × size, pressure 1 gives 0.95 × size and pressure 0 gives 0.31 × size. - Sides. Each point is offset by its radius along the normal of the line through its neighbours. This gives a left side and a right side.
- Caps. The polygon runs down the left side, around a half circle at the end, back up the right side and around a half circle at the start. The ends are round.
A single sample, or samples that all sit on one point, gives a dot: a 16-point circle of the radius at that point.
Degenerate input
Repeated samples are dropped by the streamline step. A stroke width of 0 or less uses the smallest pen size, 0.5, so the outline stays finite.
Which pressure is real
FreehandSample.pressure is always a number, so inkOutline can’t tell a pen from a mouse by itself. The caller says so in simulatePressure.