Skip to content
GitHub repository
Drawing

Fills & strokes

Paint styles, stroke alignment (inside, center or outside), fill and stroke without a seam, and hairlines that fade instead of breaking up.

Updated pixi-silk 0.1.0

Paint styles#

fill() and stroke() accept a colour, a gradient or a style object:

g.circle(0, 0, 20).fill(0xff375f);                                   // any Pixi ColorSource
g.circle(0, 0, 20).fill('rgba(255, 55, 95, 0.5)');
g.circle(0, 0, 20).fill(linear([0x0a84ff, 0xbf5af2]));               // a gradient
g.circle(0, 0, 20).fill({ color: 0xff375f, alpha: 0.5, blur: 6 });   // FillStyle
g.circle(0, 0, 20).stroke({ width: 4, color: 0xffffff, cap: 'round', alignment: 'inside' });
FillStyle
colorany Pixi ColorSource (with a gradient, it tints the ramp)
alphamultiplies the colour’s alpha
gradienta linear, radial, conic or along gradient
blurgaussian standard deviation in local units (Blur)

StrokeStyle adds width (a number, or an array for tapering), alignment, cap, dash, dashOffset and dashFit.

Alignment#

Source src/demos/alignment.demo.ts
import { Text } from 'pixi.js';
import { SilkGraphics } from 'pixi-silk';
import { defineDemo } from './runtime';

const MODES = ['inside', 'center', 'outside'] as const;

export default defineDemo({
    size: [480, 200],
    controls: {
        width: { type: 'range', label: 'stroke width', min: 1, max: 24, step: 1, value: 12, unit: 'px' },
    },
    setup({ stage, params, tick }) {
        const g = new SilkGraphics();

        stage.addChild(g);
        MODES.forEach((mode, i) => {
            const label = new Text({ text: mode, style: { fill: 0x8e8e93, fontSize: 13, fontFamily: 'system-ui' } });

            label.anchor.set(0.5, 0);
            label.position.set(90 + i * 150, 168);
            stage.addChild(label);
        });
        tick(() => {
            g.clear();
            MODES.forEach((alignment, i) => {
                const x = 40 + i * 150;

                // fill + stroke on the same shape share one instance and never leave a seam
                g.roundRect(x, 40, 100, 100, 22, 0.6)
                    .fill(0x2c2c2e)
                    .stroke({ width: params.width, color: 0x30d158, alpha: 0.85, alignment });
                g.roundRect(x, 40, 100, 100, 22, 0.6).stroke({ width: 1, color: 0xffffff, alpha: 0.6 }); // the geometric edge
            });
        });
    },
});
The white hairline is the shape's geometric edge.

Strokes on closed shapes can sit inside the edge, centred on it, or outside. alignment takes the words or Pixi’s numbers (1 inside, 0.5 center, 0 outside). Strokes on open shapes (lines, arcs) are always centred.

Fill + stroke in one primitive#

When stroke() directly follows fill(), both paints go into the same primitive. The fragment shader splits each pixel’s coverage exactly into three parts: the stroke band, the fill outside it, and the fill under it. This avoids the halo you get when you draw an inner stroke on top of an anti-aliased fill edge.

Hairlines#

Source src/demos/hairlines.demo.ts
import { SilkGraphics } from 'pixi-silk';
import { defineDemo } from './runtime';

// Lines thinner than a device pixel keep one pixel of width and fade instead of breaking up.
export default defineDemo({
    size: [480, 220],
    controls: {
        scale: { type: 'range', label: 'width scale', min: 0.05, max: 2, step: 0.01, value: 0.5 },
    },
    setup({ stage, params, tick }) {
        const g = new SilkGraphics();

        stage.addChild(g);
        tick((t) => {
            g.clear();
            for (let i = 0; i < 24; i++) {
                const a = -Math.PI / 2 + (i / 23) * Math.PI + Math.sin(t * 0.3) * 0.05;
                const w = (0.1 + i * 0.12) * params.scale;

                g.line(240, 200, 240 + Math.cos(a) * 220, 200 + Math.sin(a) * 190).stroke({
                    width: w,
                    color: 0xffffff,
                });
            }
        });
    },
});
Widths from 0.005 to 5.6 px. Thin lines fade instead of breaking up.

A stroke cannot be drawn thinner than one device pixel. So Silk keeps a thinner stroke one pixel wide and scales its alpha by width / 1px. The total ink stays proportional to the width. Lines never break into dots, and zooming out keeps every line visible. The threshold is the minStrokePx setting (default 1).

Per-object settings#

const g = new SilkGraphics({ dither: 1, aaWidth: 1, minStrokePx: 1 });

g.aaWidth = 1.5;     // softer edges (device pixels of AA ramp)
g.dither = 0;        // turn gradient / blur dithering off

Defaults live in SilkGraphics.defaults.

API reference: SilkGraphics, FillStyle, StrokeStyle