# Shapes

> Rects, squircles with per-corner radii, pills, circles, ellipses, hearts, stars, polygons and rounded triangles, each an exact anti-aliased distance field.

Source: https://pixi-silk.schmooky.dev/docs/shapes/

Every shape method adds a primitive to the **active path**. Nothing is drawn until you call
`fill()` or `stroke()`, which paint everything on the path. Pixi's `Graphics` works the same way.

Live demo `shapes` (source):

```ts
import { SilkGraphics } from 'pixi-silk';
import { defineDemo } from './runtime';

// Every shape is centred on (0, 0) and moved into its cell with the transform stack.
const SHAPES: { draw: (g: SilkGraphics) => SilkGraphics; color: number; open?: boolean }[] = [
    { draw: (g) => g.rect(-30, -22, 60, 44), color: 0x0a84ff },
    { draw: (g) => g.roundRect(-30, -22, 60, 44, 14, 0.6), color: 0x5e5ce6 },
    { draw: (g) => g.pill(-34, -14, 68, 28), color: 0xbf5af2 },
    { draw: (g) => g.circle(0, 0, 26), color: 0xff375f },
    { draw: (g) => g.ellipse(0, 0, 34, 20), color: 0xff9f0a },
    { draw: (g) => g.arc(0, 0, 26, -2.5, 0.8), color: 0xffd60a, open: true },
    { draw: (g) => g.sector(0, 0, 30, -2.2, 1.6, 14, 4), color: 0x30d158 },
    { draw: (g) => g.heart(0, 2, 56, 0.12), color: 0xff453a },
    { draw: (g) => g.star(0, 0, 5, 30, undefined, 0, 3), color: 0xffd60a },
    { draw: (g) => g.regularPoly(0, 0, 28, 6, 0, 5), color: 0x64d2ff },
    { draw: (g) => g.triangle(-30, 22, 30, 22, 0, -28, 6), color: 0xac8e68 },
    { draw: (g) => g.polyline([-34, 14, -12, -16, 10, 10, 34, -18]), color: 0xffffff, open: true },
];

export default defineDemo({
    size: [520, 250],
    controls: {
        outline: { type: 'toggle', label: 'outline', value: false },
        spin: { type: 'toggle', label: 'rotate', value: false },
    },
    setup({ stage, params, tick }) {
        const g = new SilkGraphics();

        stage.addChild(g);
        tick((t) => {
            g.clear();
            SHAPES.forEach((shape, i) => {
                g.save()
                    .translateTransform(50 + (i % 6) * 84, 65 + Math.floor(i / 6) * 120)
                    .rotateTransform(params.spin ? t * 0.6 : 0);
                const path = shape.draw(g);

                if (shape.open) path.stroke({ width: 5, cap: 'round', color: shape.color });
                else if (params.outline) path.stroke({ width: 2, color: shape.color });
                else path.fill(shape.color);
                g.restore();
            });
        });
    },
});
```

## Rectangles and squircles

```ts
g.rect(x, y, width, height);
g.roundRect(x, y, width, height, radius);                 // circular corners
g.roundRect(x, y, width, height, radius, 0.6);            // squircle: continuous curvature
g.roundRect(x, y, width, height, [24, 24, 8, 8]);         // per corner: tl, tr, br, bl
g.pill(x, y, width, height);                               // fully round ends
```

The `smoothing` argument (0 to 1) turns corners into superellipse arcs whose curvature grows
gradually. App icons and cards on iOS have these corners. iOS uses about `0.6`. A circular corner
meets the straight side with a jump in curvature, which the eye reads as a slight kink. At the 45°
point, a smoothed corner is exactly as deep as a circular corner of the same `radius`. So switching
is a drop-in change.

Live demo `squircle` (source):

```ts
import { SilkGraphics } from 'pixi-silk';
import { defineDemo } from './runtime';

// A squircle keeps curvature continuous where the side meets the corner (iOS icons, cards).
export default defineDemo({
    size: [420, 220],
    controls: {
        radius: { type: 'range', label: 'radius', min: 0, max: 80, step: 1, value: 48, unit: 'px' },
        smoothing: { type: 'range', label: 'smoothing', min: 0, max: 1, step: 0.01, value: 0.6 },
        compare: { type: 'toggle', label: 'circular overlay', value: true },
    },
    setup({ stage, params, tick }) {
        const g = new SilkGraphics();

        stage.addChild(g);
        tick(() => {
            g.clear();
            g.roundRect(110, 20, 200, 180, params.radius, params.smoothing)
                .fill(0x2c2c2e)
                .stroke({ width: 2, color: 0x64d2ff });
            if (params.compare)
                g.roundRect(110, 20, 200, 180, params.radius).stroke({ width: 1, color: 0xff375f, alpha: 0.8 });
        });
    },
});
```

## Circles, ellipses and hearts

```ts
g.circle(cx, cy, radius);
g.ellipse(cx, cy, radiusX, radiusY);          // exact ellipse distance (Newton solve, no approximation)
g.heart(cx, cy, width, rounding);             // rounding 0..0.3 puffs it up
```

## Polygons

```ts
g.regularPoly(cx, cy, radius, sides, rotation, cornerRadius);   // first vertex points up
g.star(cx, cy, points, radius, innerRadius, rotation, cornerRadius);
g.triangle(x0, y0, x1, y1, x2, y2, cornerRadius);
```

Rounded corners on polygons, stars and triangles are exact, not approximations.

## Rotated shapes

Shapes drawn inside a rotated transform stay exact, because the rotation is part of the primitive.
See [Transforms](/docs/transforms/).

```ts
g.save().translateTransform(100, 100).rotateTransform(Math.PI / 6);
g.roundRect(-40, -20, 80, 40, 12, 0.6).fill(0x5e5ce6);
g.restore();
```

## Fill and stroke together

```ts
g.roundRect(0, 0, 200, 80, 20, 0.6).fill(0x1c1c1e).stroke({ width: 2, color: 0x3a3a3c });
```

Calling `stroke()` right after `fill()` merges both into one primitive. The shader splits each
pixel's coverage exactly between the fill and the stroke band. So there is never a seam or a halo
between them. See [Fills & strokes](/docs/fills-and-strokes/).
