Skip to content

Custom Brushes

Saier supports runtime custom brush presets. A preset is data: engine id, tip id, size, opacity, spacing, hardness, mixing parameters, tags, and display metadata. The host app decides where to store that data.

Save Current Settings

Use painter.brush.createCustomPreset() to snapshot the current brush controls into a custom preset and select it immediately.

ts
import { createPainter } from 'saier'

const painter = createPainter({
  view: document.querySelector('canvas') as HTMLCanvasElement,
  size: { width: 1024, height: 1024 },
})

await painter.init()

painter.brush.setSize(18)
painter.brush.setOpacity(0.72)

const preset = painter.brush.createCustomPreset({
  name: 'Soft sketch',
  group: 'Custom',
})

console.log(preset.id)

Custom presets are available for the current painter instance until removed or the page reloads.

ts
painter.brush.removePreset(preset.id)

Register Preset Data

Use painter.brush.registerPreset() when your app already has preset data, for example from localStorage, project metadata, or a server.

ts
import type { BrushPreset } from '@saier/core'

const savedPreset: BrushPreset = {
  id: 'custom-soft-sketch',
  name: 'Soft sketch',
  group: 'Custom',
  source: 'custom',
  custom: true,
  engine: 'simple',
  tipId: 'round-soft',
  size: 18,
  opacity: 0.72,
  spacing: 0.22,
  hardness: 0.2,
  tags: ['custom'],
}

painter.brush.registerPreset(savedPreset, { select: true })

Saier does not persist custom brushes by itself. Persist the returned BrushPreset in your host app, then call registerPreset() during startup.

External Brush Engines

External engines register an engine id plus a factory. Presets that point to that id are disabled in UI summaries until the engine is available.

ts
import type { BrushEngineRegistration, BrushPreset } from '@saier/core'
import { createPainter } from 'saier'

const engine: BrushEngineRegistration = {
  id: 'my-engine',
  label: 'My Engine',
  experimental: true,
  create: ({ preset }) => createMyBrushEngine(preset),
}

const preset: BrushPreset = {
  id: 'my-engine-soft',
  name: 'My engine soft',
  group: 'Experimental',
  source: 'external',
  engine: 'my-engine',
  tipId: 'round-soft',
  size: 24,
  opacity: 0.8,
  spacing: 0.18,
  hardness: 0.4,
}

const painter = createPainter({
  view: document.querySelector('canvas') as HTMLCanvasElement,
  size: { width: 1024, height: 1024 },
  brushEngines: [engine],
  brushPresets: [preset],
})

Mark real WASM or third-party engines as experimental until their loading, backend requirements, and pixel behavior are verified.

Import .myb Settings

parseMyPaintBrushPreset() maps a MyPaint-style .myb settings file into a Saier BrushPreset draft. It covers core knobs such as radius, opacity, hardness, spacing, smudge, and smudge length; it is not pixel-parity with libmypaint.

ts
import { parseMyPaintBrushPreset } from '@saier/core'

const mybText = await file.text()
const preset = parseMyPaintBrushPreset(mybText, {
  id: 'mypaint-soft-pencil',
  name: 'Soft Pencil',
})

painter.brush.registerPreset(preset, { select: true })

Treat .myb import as a compatibility bridge for preset setup, not as a promise that Saier renders exactly like MyPaint.

Released under the MPL-2.0 License.