# Smooth graphics in PixiJS v8

> Find the cause and fix for jagged edges, faceted circles, flickering thin lines, blurry retina canvases, blurry filters and banded gradients in PixiJS v8.

Source: https://pixi-silk.schmooky.dev/docs/smooth-graphics-pixijs/

Graphics in PixiJS v8 look rough for several reasons. Find your symptom below. Each section gives
the cause, then the fix. Most fixes work with plain `Graphics`, and each ends with the pixi-silk
equivalent.

## Jagged, stair-stepped edges

Anti-aliasing is off. `antialias` is `false` unless you set it, so every edge pixel is fully in or
fully out.

Turn on MSAA with `await app.init({ antialias: true })`. Or draw with `SilkGraphics`, which
computes exact edge coverage without MSAA. See [Anti-aliasing in PixiJS v8](/docs/antialiasing-in-pixijs/).

## Circles and curves look like polygons when zoomed

Graphics turns curves into a fixed number of segments when you draw them. Scaling the object up
later reveals the facets.

Raise `bezierSmoothness` (0 to 1, default 0.5) in `app.init()`, or redraw at the final scale. Or
use SilkGraphics. It evaluates circles, arcs and rounded corners exactly per pixel, at any zoom.

## Thin lines flicker, look dotted or disappear

A line thinner than one device pixel covers only a fraction of each pixel. Point samples (and
MSAA's four samples) hit it on some pixels and miss it on others. The pattern changes as the line
moves.

Keep strokes at least one device pixel wide and fade them instead. SilkGraphics does this for you
with energy-conserving hairlines:

Live demo `hairlines` (source):

```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,
                });
            }
        });
    },
});
```

## Everything is slightly blurry on retina or HiDPI screens

The canvas has fewer pixels than the screen. Either `resolution` is 1, or the browser scales a
canvas sized in CSS pixels. Or the size was rounded at a fractional DPR (1.25, 1.5), so the
browser resamples it.

Call `app.init({ resolution: window.devicePixelRatio, autoDensity: true })` and size the backing
store from `devicePixelContentBoxSize`. `createSilkApp` does both and follows DPR changes:

Live demo `resolution` (source):

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

// A Siemens star is the hardest test for anti-aliasing: every wedge narrows below a pixel.
export default defineDemo({
    size: [480, 240],
    controls: {
        resolution: { type: 'select', label: 'resolution', options: ['device', '1x', '2x', '3x'], value: 'device' },
    },
    setup({ silk, stage, params, tick }) {
        const g = new SilkGraphics();
        let applied = '';

        stage.addChild(g);
        for (let i = 0; i < 36; i++) {
            const a = (i / 36) * Math.PI * 2;

            g.sector(240, 120, 110, a, a + Math.PI / 36).fill(0xffffff);
        }
        g.circle(240, 120, 110).stroke({ width: 1, color: 0x636366 });
        tick(() => {
            if (params.resolution === applied) return;
            applied = params.resolution;
            // createSilkApp sizes the backing store in device pixels; force 1x/2x/3x to compare
            silk.setResolution(applied === 'device' ? null : Number(applied[0]));
        });
    },
});
```

## Content behind a filter is blurry

Filters render into their own texture at `resolution: 1` by default.

Set `Filter.defaultOptions.resolution = 'inherit'` before you create filters, or pass `resolution`
to each filter. `createSilkApp` sets this default for you.

## Gradients show visible bands

The cause is 8-bit colour. A dark gradient across 1000 pixels may have only 20 distinct values.

The fix is dithering. Pixi's `FillGradient` has no dither option. pixi-silk bakes gradients in half
floats, interpolates in OKLab and dithers every sample by plus or minus half an 8-bit step. See
[Gradients](/docs/gradients/#no-banding).

## Slow motion snaps from pixel to pixel

`roundPixels: true` (on the renderer or the object) rounds positions to whole pixels.

Keep `roundPixels: false` for smoothly animated vector content. With analytic anti-aliasing,
sub-pixel positions render correctly.

## Checklist

```ts
import { createSilkApp, SilkGraphics } from 'pixi-silk';

const { app } = await createSilkApp({ parent: el });   // DPR-exact canvas, filters at screen resolution
const g = new SilkGraphics();                          // exact anti-aliasing, hairlines, dithered gradients
app.stage.addChild(g);
```
