Shape operations
Union, subtract, intersect and exclude shapes and SVG paths in PixiJS with fill({ op }) and boolean(). Results take strokes, gradients, blur and hit tests.
Updated pixi-silk 1.0.1View as Markdown
Shape operations combine several shapes into one: cut a hole in a card, make a ring from two circles or build an icon from a few primitives.
g.circle(60, 60, 50).circle(60, 60, 30).fill({ color: 0x30d158, op: 'subtract' });
That draws a ring: the first circle minus the second. The result is painted once, so a translucent fill has no darker overlaps.
Combine the shapes of a path#
Add op to fill() or stroke(). It combines every closed shape on the active path.
g.rect(0, 0, 80, 80).circle(80, 80, 40).fill({ color: 0x0a84ff, op: 'intersect' }); // a quarter disc
g.circle(40, 40, 30).circle(70, 40, 30).fill({ color: 0xff375f, op: 'exclude' }); // two lenses
op | Result |
|---|---|
'union' | Everything any shape covers. The same as union: true. |
'subtract' | The first shape minus every later one |
'intersect' | Only what every shape covers |
'exclude' | What an odd number of shapes cover |
A stroke() right after the fill follows the outline of the result. A stroke with its own op
strokes the outline without a fill.
g.roundRect(0, 0, 120, 80, 16)
.circle(60, 40, 24)
.fill({ color: 0x1c1c1e, op: 'subtract' })
.stroke({ width: 2, color: 0xffffff });
Open shapes take no part. Arcs and areas in the path stroke on their own.
Nest operations with boolean()#
boolean(op, a, b) adds one shape to the active path: what callback a draws, combined by op
with what callback b draws. The shapes inside each callback are merged first. A callback can call
boolean() again, so any tree of operations works.
// a ring with a notch, and a dot in the middle
g.boolean(
'union',
(g) =>
g.boolean(
'subtract',
(g) => g.circle(50, 50, 40),
(g) => g.circle(50, 50, 24).rect(46, 0, 8, 30),
),
(g) => g.circle(50, 50, 8),
).fill(0xff9f0a);
The callbacks only add shapes. Do not call fill() or stroke() inside them. Up to 8 operands
can wait on each other at once. A deeper tree is skipped with a warning.
Cut with SVG paths#
g.svgPath('M12 2 L22 22 H2 Z').fill(0xffd60a);
g.svgPath('M4 4 H20 V20 H4 Z').circle(12, 12, 4).fill({ color: 0xffffff, op: 'subtract' });
svgPath(d) reads the d attribute of an SVG path into the active path. It supports M, L,
H, V, C, S, Q, T, A and Z, absolute and relative. A fill draws it as one even-odd
shape, so the inner square of M0 0 H60 V60 H0 Z M20 20 V40 H40 V20 Z is a hole.
In a shape operation, a path from svgPath() or moveTo() is one operand with all its subpaths.
With 'union', each subpath counts on its own.
Paint, effects and hit tests#
Everything that paints a closed shape works on the result: colours, gradients, blur, a
progressive blur, spread, inner shadows and blend: 'add'. Gradients in shape units span the
bounds of the result.
The outline of the result is exact. Inside the shape and far outside it, the combined distance is a close bound instead. A blur, spread or inner shadow wider than a corner can round that corner a little more than on a plain shape. Keep blurs in proportion for crisp cutouts.
containsPoint() follows the result. A ring is hollow, a cutout lets clicks through and an
intersection only hits where the shapes overlap.
Cost#
A combined shape is one instance. Each pixel evaluates every shape in it once, so ten shapes in an operation cost about what ten separate shapes cost. A path in an operation costs one step per edge.
API reference: SilkGraphics, ShapeOp