@pixerate/editor 0.108.0 → 0.110.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/dist/animation/index.cjs +5 -5
  2. package/dist/animation/index.cjs.map +1 -1
  3. package/dist/animation/index.js +9 -9
  4. package/dist/animation-editor/index.cjs +59 -59
  5. package/dist/animation-editor/index.cjs.map +1 -1
  6. package/dist/animation-editor/index.d.cts +2 -1
  7. package/dist/animation-editor/index.d.ts +2 -1
  8. package/dist/animation-editor/index.js +29 -29
  9. package/dist/blending-DXl06NSx.d.ts +486 -0
  10. package/dist/blending-aOE6Y3UE.d.cts +486 -0
  11. package/dist/canvas/index.cjs +27 -27
  12. package/dist/canvas/index.cjs.map +1 -1
  13. package/dist/canvas/index.js +11 -11
  14. package/dist/{chunk-2FGRTYJN.cjs → chunk-477UVMNR.cjs} +3 -3
  15. package/dist/{chunk-2FGRTYJN.cjs.map → chunk-477UVMNR.cjs.map} +1 -1
  16. package/dist/chunk-7VOHOPLW.cjs +1136 -0
  17. package/dist/chunk-7VOHOPLW.cjs.map +1 -0
  18. package/dist/{chunk-HUQ7EHVI.js → chunk-BLWCAQCW.js} +723 -1975
  19. package/dist/chunk-BLWCAQCW.js.map +1 -0
  20. package/dist/{chunk-SOROZ2BF.js → chunk-DMRSJBJF.js} +1939 -318
  21. package/dist/chunk-DMRSJBJF.js.map +1 -0
  22. package/dist/chunk-FTBZPI77.cjs +1 -0
  23. package/dist/chunk-FTBZPI77.cjs.map +1 -0
  24. package/dist/{chunk-WMBIJCHA.js → chunk-HNS7UHCM.js} +5 -1
  25. package/dist/{chunk-WMBIJCHA.js.map → chunk-HNS7UHCM.js.map} +1 -1
  26. package/dist/chunk-JLYX4V57.cjs +2001 -0
  27. package/dist/chunk-JLYX4V57.cjs.map +1 -0
  28. package/dist/chunk-KB2YBCVS.js +2001 -0
  29. package/dist/chunk-KB2YBCVS.js.map +1 -0
  30. package/dist/{chunk-G4BIYE6R.js → chunk-LQEEDHOP.js} +40 -2
  31. package/dist/chunk-LQEEDHOP.js.map +1 -0
  32. package/dist/{chunk-FRD3FUXI.cjs → chunk-MGL4U5WA.cjs} +2144 -523
  33. package/dist/chunk-MGL4U5WA.cjs.map +1 -0
  34. package/dist/{chunk-T3QZ6S47.cjs → chunk-POGBUYWE.cjs} +131 -93
  35. package/dist/chunk-POGBUYWE.cjs.map +1 -0
  36. package/dist/chunk-VKK5VS2T.js +1 -0
  37. package/dist/{chunk-UTEVPQTS.cjs → chunk-W3GXSUSQ.cjs} +746 -1998
  38. package/dist/chunk-W3GXSUSQ.cjs.map +1 -0
  39. package/dist/{chunk-SZ6KMZNM.cjs → chunk-XIOMMGPD.cjs} +6 -2
  40. package/dist/chunk-XIOMMGPD.cjs.map +1 -0
  41. package/dist/{chunk-JK6M6V7G.js → chunk-Z3YWVUWT.js} +4 -4
  42. package/dist/{chunk-3PHA3UKI.js → chunk-Z4BC6M74.js} +523 -121
  43. package/dist/chunk-Z4BC6M74.js.map +1 -0
  44. package/dist/{export-a9bnF7Cl.d.ts → export-CO2ryu3I.d.ts} +419 -18
  45. package/dist/{export-JGWQEPJn.d.cts → export-DRiLzP_j.d.cts} +419 -18
  46. package/dist/image-editor/index.cjs +1665 -1113
  47. package/dist/image-editor/index.cjs.map +1 -1
  48. package/dist/image-editor/index.d.cts +78 -14
  49. package/dist/image-editor/index.d.ts +78 -14
  50. package/dist/image-editor/index.js +843 -291
  51. package/dist/image-editor/index.js.map +1 -1
  52. package/dist/paint/index.cjs +28 -4
  53. package/dist/paint/index.cjs.map +1 -1
  54. package/dist/paint/index.d.cts +3 -89
  55. package/dist/paint/index.d.ts +3 -89
  56. package/dist/paint/index.js +27 -3
  57. package/dist/paint-editor/index.cjs +1056 -287
  58. package/dist/paint-editor/index.cjs.map +1 -1
  59. package/dist/paint-editor/index.d.cts +304 -22
  60. package/dist/paint-editor/index.d.ts +304 -22
  61. package/dist/paint-editor/index.js +852 -83
  62. package/dist/paint-editor/index.js.map +1 -1
  63. package/dist/raster/index.cjs +7 -3
  64. package/dist/raster/index.cjs.map +1 -1
  65. package/dist/raster/index.d.cts +3 -129
  66. package/dist/raster/index.d.ts +3 -129
  67. package/dist/raster/index.js +6 -2
  68. package/dist/{compositor-D92URMlm.d.cts → render-BEriFuqK.d.cts} +136 -2
  69. package/dist/{compositor-Kt_M4ruo.d.ts → render-tjzKaik-.d.ts} +136 -2
  70. package/dist/{surface-CkKOHGPm.d.cts → surface-C8RRg_Bh.d.cts} +109 -1
  71. package/dist/{surface-CkKOHGPm.d.ts → surface-C8RRg_Bh.d.ts} +109 -1
  72. package/dist/{svg-SseJTIrA.d.ts → svg-D_cfkhlB.d.cts} +3 -629
  73. package/dist/{svg-ClVIG4T7.d.cts → svg-wIaKLrU0.d.ts} +3 -629
  74. package/dist/types-DtL_qLFz.d.ts +630 -0
  75. package/dist/types-DyQq-WPY.d.cts +630 -0
  76. package/dist/vector/index.cjs +563 -563
  77. package/dist/vector/index.cjs.map +1 -1
  78. package/dist/vector/index.d.cts +4 -2
  79. package/dist/vector/index.d.ts +4 -2
  80. package/dist/vector/index.js +69 -69
  81. package/dist/video-editor/index.cjs +3 -3
  82. package/dist/video-editor/index.js +2 -2
  83. package/package.json +1 -1
  84. package/skills/pixerate-paint-editor/SKILL.md +49 -13
  85. package/dist/chunk-3PHA3UKI.js.map +0 -1
  86. package/dist/chunk-6BU5WU4R.cjs +0 -1
  87. package/dist/chunk-6BU5WU4R.cjs.map +0 -1
  88. package/dist/chunk-DWGZUSES.cjs +0 -694
  89. package/dist/chunk-DWGZUSES.cjs.map +0 -1
  90. package/dist/chunk-FRD3FUXI.cjs.map +0 -1
  91. package/dist/chunk-G4BIYE6R.js.map +0 -1
  92. package/dist/chunk-HUFQD4CB.cjs +0 -734
  93. package/dist/chunk-HUFQD4CB.cjs.map +0 -1
  94. package/dist/chunk-HUQ7EHVI.js.map +0 -1
  95. package/dist/chunk-KKREERP2.js +0 -694
  96. package/dist/chunk-KKREERP2.js.map +0 -1
  97. package/dist/chunk-SOROZ2BF.js.map +0 -1
  98. package/dist/chunk-SZ6KMZNM.cjs.map +0 -1
  99. package/dist/chunk-T3QZ6S47.cjs.map +0 -1
  100. package/dist/chunk-UTEVPQTS.cjs.map +0 -1
  101. package/dist/chunk-XYBJKUAU.js +0 -1
  102. package/dist/mixer-5ziHU8OS.d.cts +0 -282
  103. package/dist/mixer-DNqiYXYQ.d.ts +0 -282
  104. /package/dist/{chunk-XYBJKUAU.js.map → chunk-VKK5VS2T.js.map} +0 -0
  105. /package/dist/{chunk-JK6M6V7G.js.map → chunk-Z3YWVUWT.js.map} +0 -0
@@ -1,12 +1,15 @@
1
- import { R as RasterLayer, a as RasterGroupLayer, b as RasterPixelLayer, C as CompositorLayer, c as RasterCompositor, d as RasterSources, L as LayerLocks } from '../compositor-D92URMlm.cjs';
2
- import { B as BrushSettings, M as MixerSettings, C as CoverageMap, D as Dab, S as StrokePoint, a as MixerLoad } from '../mixer-5ziHU8OS.cjs';
3
- export { b as MAX_BRUSH_SIZE } from '../mixer-5ziHU8OS.cjs';
4
- import { R as RasterRect, a as TiledSurface, B as BlendMode, b as BlendModeInfo } from '../surface-CkKOHGPm.cjs';
5
- export { c as BLEND_MODES, d as PAINTERLY_BLEND_MODES, e as Tile, i as isBlendMode, f as isPainterlyBlendMode, s as standardBlendMode } from '../surface-CkKOHGPm.cjs';
6
- import { I as ImageEditorAssetStore, S as SerializedLayer, P as PsdExportResult } from '../export-JGWQEPJn.cjs';
1
+ import { R as RasterLayer, a as RasterGroupLayer, b as RasterPixelLayer, C as CompositorLayer, c as RasterCompositor, d as RasterSources, L as LayerLocks } from '../render-BEriFuqK.cjs';
2
+ import { B as BrushSettings, M as MixerSettings, a as MixingModel, b as BlenderSettings, S as SmudgeSettings, C as CoverageMap, D as Dab, c as StrokePoint, d as MixerLoad, P as PaintTileTarget, R as RgbTriple } from '../blending-aOE6Y3UE.cjs';
3
+ export { e as MAX_BRUSH_SIZE } from '../blending-aOE6Y3UE.cjs';
4
+ import { R as RasterRect, a as TiledSurface, B as BlendMode, c as BlendModeInfo } from '../surface-C8RRg_Bh.cjs';
5
+ export { d as BLEND_MODES, e as PAINTERLY_BLEND_MODES, f as Tile, i as isBlendMode, g as isPainterlyBlendMode, s as standardBlendMode } from '../surface-C8RRg_Bh.cjs';
6
+ import { I as ImageEditorAssetStore, S as SerializedLayer, P as PsdExportResult } from '../export-DRiLzP_j.cjs';
7
7
  import { g as PresenceStore, e as PresenceClock, d as PresenceActorInfo, c as PresenceActor, f as PresenceState } from '../presence-Cb6WhcrQ.cjs';
8
8
  import { a as AgentToolProvider } from '../registry-BqA1AZt0.cjs';
9
9
  import '../types-CwMPoRMU.cjs';
10
+ import '../types-DyQq-WPY.cjs';
11
+ import '../bezier-BfwIbVTl.cjs';
12
+ import '../types-BFkYA4eF.cjs';
10
13
  import '../warp-D5jz_tN7.cjs';
11
14
  import '../schema-Di0CS6Pe.cjs';
12
15
 
@@ -147,11 +150,49 @@ declare function renderPaper(paper: PaperSettings, rect: {
147
150
  * the blending brushes. The built-in presets are our own settings; none come from another product.
148
151
  */
149
152
 
150
- /** How a brush makes marks: dabs into a stroke buffer, removing alpha, or the Mixer Brush engine. */
151
- type PaintBrushEngine = 'dab' | 'eraser' | 'mixer';
153
+ /**
154
+ * How a brush makes marks: `dab` (dabs into a stroke buffer, or dab by dab through the pick-up /
155
+ * mix exchange when its behaviour asks for it), `eraser` (removes alpha), `mixer` (the Mixer
156
+ * Brush), `blender` (mixes the colours under and near the tip) or `smudge` (drags paint along).
157
+ */
158
+ type PaintBrushEngine = 'dab' | 'eraser' | 'mixer' | 'blender' | 'smudge';
152
159
  declare const PAINT_BRUSH_ENGINES: readonly PaintBrushEngine[];
153
- type PaintBrushCategory = 'pencil' | 'pastel' | 'charcoal' | 'airbrush' | 'marker' | 'ink' | 'eraser' | 'blender' | 'smudge' | 'mixer' | 'custom';
160
+ type PaintBrushCategory = 'pencil' | 'pastel' | 'charcoal' | 'airbrush' | 'marker' | 'ink' | 'paint' | 'eraser' | 'blender' | 'smudge' | 'mixer' | 'custom';
154
161
  declare const PAINT_BRUSH_CATEGORIES: readonly PaintBrushCategory[];
162
+ /** Multi-coloured brush: `tip` lays the colours across the tip, `dab` changes colour from dab to dab. */
163
+ type MultiColorMode = 'off' | 'tip' | 'dab';
164
+ declare const MULTI_COLOR_MODES: readonly MultiColorMode[];
165
+ interface MultiColorSettings {
166
+ /** `off` (default), `tip` (the colours side by side across the tip, like a brush loaded with several paints; the stripes follow the stroke) or `dab` (the colour changes along the stroke). */
167
+ mode: MultiColorMode;
168
+ /** 2..8 colours #rrggbb. Fewer than 2: the paint colour and the secondary colour. */
169
+ colors: string[];
170
+ /** `dab` mode: `sequence` (default) runs through the colours in order, blending between them; `random` picks one per run of dabs (seeded, so replays match). */
171
+ order: 'sequence' | 'random';
172
+ /** `dab` mode: dabs per colour, 1..500 (default 12). */
173
+ length: number;
174
+ /** How softly neighbouring colours blend, 0 (hard stripes or steps) .. 1 (smooth gradient, default). */
175
+ softness: number;
176
+ }
177
+ declare const DEFAULT_MULTI_COLOR: Readonly<MultiColorSettings>;
178
+ /**
179
+ * How a brush handles paint on the canvas. The pick-up / mix ratios are the shared exchange's
180
+ * (`@pixerate/editor/paint` `PaintExchange`); wet media will use the same ones. On a Mixer Brush
181
+ * they are its `mixer.wet`, `mixer.mix`, `!mixer.cleanEachStroke` and `mixer.mixing`, kept in sync.
182
+ */
183
+ interface BrushBehavior {
184
+ /** Pick-up ratio, 0..1: how much of the paint under each dab the brush lifts and carries (0: none, default). */
185
+ pickup: number;
186
+ /** Mix ratio, 0..1: how much of the carried paint goes into what the brush lays down, against its own colour (default 0.5). */
187
+ mix: number;
188
+ /** Dirty brush: keep the carried paint after the stroke, so the next stroke starts with the last colour the brush touched (default false). */
189
+ dirty: boolean;
190
+ /** How picked-up paint, multi-colour gradients and the deposit mix: `pigment` (default, Kubelka-Munk) or `rgb`. */
191
+ mixing: MixingModel;
192
+ /** Multi-coloured brush. */
193
+ multicolor: MultiColorSettings;
194
+ }
195
+ declare const DEFAULT_BEHAVIOR: Readonly<BrushBehavior>;
155
196
  /** What turns the tip: a fixed angle, the direction of the stroke, or the pen's barrel rotation. */
156
197
  type BrushRotation = 'fixed' | 'stroke' | 'barrel';
157
198
  declare const BRUSH_ROTATIONS: readonly BrushRotation[];
@@ -199,17 +240,39 @@ interface PaintBrush {
199
240
  mixer: MixerSettings;
200
241
  /** Mixer engine only: load the current colour each stroke (false: a smudge or blender that only moves paint). */
201
242
  loadsColor: boolean;
243
+ /** Pick-up and mix ratios, dirty brush and multi-colour (dab and mixer engines). */
244
+ behavior: BrushBehavior;
245
+ /** Blender engine only: strength, blend distance and mixing. */
246
+ blender: BlenderSettings;
247
+ /** Smudge engine only: strength, length and mixing. */
248
+ smudge: SmudgeSettings;
202
249
  /** True for the built-in presets (they cannot be removed or overwritten). */
203
250
  builtin?: boolean;
204
251
  }
205
252
  declare function normalizeDynamics(raw?: Partial<BrushDynamics>, base?: BrushDynamics): BrushDynamics;
206
253
  declare const BRUSH_ID: RegExp;
207
- /** Brush settings to change or read: any subset of a brush, its tip, dynamics and mixer. */
208
- type PaintBrushInput = Partial<Omit<PaintBrush, 'tip' | 'dynamics' | 'mixer'>> & {
254
+ declare function normalizeMultiColor(raw?: Partial<MultiColorSettings>, base?: MultiColorSettings): MultiColorSettings;
255
+ /** Behaviour input: any subset, with a partial multi-colour block. */
256
+ type BrushBehaviorInput = Partial<Omit<BrushBehavior, 'multicolor'>> & {
257
+ multicolor?: Partial<MultiColorSettings>;
258
+ };
259
+ declare function normalizeBehavior(raw?: BrushBehaviorInput, base?: BrushBehavior): BrushBehavior;
260
+ /** Brush settings to change or read: any subset of a brush and of its blocks. */
261
+ type PaintBrushInput = Partial<Omit<PaintBrush, 'tip' | 'dynamics' | 'mixer' | 'behavior' | 'blender' | 'smudge'>> & {
209
262
  tip?: Partial<BrushSettings>;
210
263
  dynamics?: Partial<BrushDynamics>;
211
264
  mixer?: Partial<MixerSettings>;
265
+ behavior?: BrushBehaviorInput;
266
+ blender?: Partial<BlenderSettings>;
267
+ smudge?: Partial<SmudgeSettings>;
212
268
  };
269
+ /** Whether a brush can pick paint up from the canvas (the pick-up / mix ratios apply): dab and mixer brushes. */
270
+ declare function isWetCapable(brush: Pick<PaintBrush, 'engine'>): boolean;
271
+ /**
272
+ * Whether a dab brush paints dab by dab through the exchange (it picks up paint or is
273
+ * multi-coloured) instead of building one coverage buffer of one colour.
274
+ */
275
+ declare function usesPaintExchange(brush: PaintBrush): boolean;
213
276
  /** A brush with every field in range; missing fields come from `base` (default: a plain round dab brush). */
214
277
  declare function normalizePaintBrush(raw?: PaintBrushInput, base?: PaintBrush): PaintBrush;
215
278
  /** The built-in brush library. */
@@ -303,6 +366,10 @@ declare const IDENTITY_COPY: StrokeCopy;
303
366
  /** A dab with the pen state it was made at (the shared `Dab` plus pressure for the grain). */
304
367
  interface PenDab extends Dab {
305
368
  pressure: number;
369
+ /** Direction of travel at the dab, radians counter-clockwise with y up (as `angle`). */
370
+ direction: number;
371
+ /** The dab's index within its copy of the stroke (0, 1, 2 …). */
372
+ index: number;
306
373
  }
307
374
  /**
308
375
  * Walks pen samples into dabs, incrementally (any chunking gives the same dabs). Dab angles are in
@@ -387,6 +454,8 @@ interface RecordedStroke {
387
454
  seed: number;
388
455
  /** Extra copies painted with the stroke (mirror / kaleidoscope), as affine maps of the points. */
389
456
  copies?: Affine[];
457
+ /** What the brush carried at the start (Mixer Brush and wet-capable brushes), so a replay mixes the same. */
458
+ load?: MixerLoad;
390
459
  }
391
460
  declare function applyAffine(m: Affine, x: number, y: number): {
392
461
  x: number;
@@ -482,15 +551,91 @@ declare function createPaintDocument(options?: Partial<PaintCanvas> & {
482
551
  paper?: Partial<PaperSettings>;
483
552
  }): PaintDocumentState;
484
553
 
554
+ /**
555
+ * The paint editor's dab-by-dab stroke engine: every brush whose dabs read what earlier dabs laid
556
+ * down. The Mixer Brush, the blender, the smudge, and dab brushes that pick up paint or are
557
+ * multi-coloured all run here, with the same pen input as dry media (`PenDabWalker`: pressure,
558
+ * tilt, barrel rotation, velocity, rotation modes, jitter), paper grain and symmetry copies.
559
+ *
560
+ * - `mixer` and wet-capable `dab` brushes go through the shared pick-up / mix exchange
561
+ * (`PaintExchange` over a `TileMedium`): each dab picks up from the layer, mixes its paint from
562
+ * the brush colour (or the multi-colour gradient) and the carried paint, and lays it down. A
563
+ * Mixer Brush deposits per dab (photocraft's model); a dab brush keeps the stroke's opacity
564
+ * ceiling: a shared `CoverageMap` builds up as for dry media and each dab lays only the
565
+ * increase, `k = (c₁ − c₀)·o / (1 − c₀·o)`, so a one-colour Normal stroke matches the dry engine.
566
+ * - `blender` calls `blenderDab`, `smudge` a `SmudgeTip` per copy.
567
+ *
568
+ * Pixels go through the session's live tiles (`PaintTileTarget`). Pure data in, pixels out:
569
+ * points, seed, colours and the starting load replay exactly.
570
+ */
571
+
572
+ interface PaintDabStrokeOptions {
573
+ /** Where the stroke reads and writes pixels. */
574
+ target: PaintTileTarget;
575
+ /** The paint colour, sRGB 0..1. */
576
+ color: RgbTriple;
577
+ /** The secondary colour (a multi-colour brush without its own colours uses both). */
578
+ secondary: RgbTriple;
579
+ /** What the brush carries at the start (mixer and wet-capable dab brushes). */
580
+ load: MixerLoad;
581
+ grain: PaperGrain | null;
582
+ paperDepth: number;
583
+ seed?: number;
584
+ copies?: readonly StrokeCopy[];
585
+ }
586
+ /** Colours of a multi-colour brush, resolved against the paint and secondary colours. */
587
+ declare function multiColorPalette(mc: MultiColorSettings, color: RgbTriple, secondary: RgbTriple): RgbTriple[];
588
+ /** The tip gradient of a multi-colour brush: `GRADIENT_STEPS` colours across the tip. */
589
+ declare function multiColorGradient(palette: readonly RgbTriple[], softness: number, mixing: MixingModel): RgbTriple[];
590
+ /** The colour of dab `index` of a `dab`-mode multi-colour brush. */
591
+ declare function multiColorForDab(mc: MultiColorSettings, palette: readonly RgbTriple[], index: number, seed: number, mixing: MixingModel): RgbTriple;
592
+ declare class PaintDabStroke {
593
+ readonly brush: PaintBrush;
594
+ private readonly options;
595
+ dabCount: number;
596
+ private readonly smoother;
597
+ private readonly copies;
598
+ private readonly states;
599
+ private readonly medium;
600
+ private readonly depth;
601
+ private readonly coverage;
602
+ private readonly palette;
603
+ private readonly gradient;
604
+ private readonly fieldLut;
605
+ private readonly scratch;
606
+ private readonly mixing;
607
+ private tip;
608
+ private cov;
609
+ private field;
610
+ private readonly paint;
611
+ private lastRaw;
612
+ private lastFed;
613
+ constructor(brush: PaintBrush, options: PaintDabStrokeOptions);
614
+ /** What copy 0's brush carries after the dabs so far (mixer and wet-capable dab brushes). */
615
+ get load(): MixerLoad | null;
616
+ push(points: readonly PenPoint[]): RasterRect[];
617
+ /** Ends the stroke: the smoothed path catches up with the last raw sample; a tap paints one dab. */
618
+ finish(): RasterRect[];
619
+ private paintDabs;
620
+ /** The tip's pure coverage (`tip`) and, with grain, what the paper catches of it (`cov`). */
621
+ private rasterize;
622
+ private dab;
623
+ /** Fills `this.field` with the tip gradient (mixed with the carried paint) per pixel; returns the paint weight. */
624
+ private tipField;
625
+ /** A dab brush's deposit: only what this dab adds to the stroke's coverage, under its opacity ceiling. */
626
+ private depositWithCeiling;
627
+ }
628
+
485
629
  /**
486
630
  * A live stroke on one pixel layer. Points arrive while the pen moves; the layer shows the stroke
487
631
  * so far through a live surface (no history); `end()` commits it as one undo step.
488
632
  *
489
633
  * - Dab and eraser brushes: the coverage is cumulative, so each update recomposites the touched
490
634
  * region from the pre-stroke tile (`applyStrokeToTile` with `into` and `region`).
491
- * - Mixer engine brushes (Mixer Brush, blender, smudge): dabs read what earlier dabs laid down, so
492
- * they paint into live tile copies; every tile written since the last update gets a new `Tile`
493
- * object, because the compositor caches by tile identity.
635
+ * - Dab-by-dab brushes (`PaintDabStroke`: the Mixer Brush, blender, smudge, and dab brushes that
636
+ * pick up paint or are multi-coloured): dabs read what earlier dabs laid down, so they paint
637
+ * into live tile copies; every tile written since the last update gets a new `Tile` object,
638
+ * because the compositor caches by tile identity.
494
639
  */
495
640
 
496
641
  interface StrokeSessionHost {
@@ -522,8 +667,10 @@ interface StrokeSessionInit {
522
667
  copies: readonly StrokeCopy[];
523
668
  /** Applied to each raw sample before it is painted and recorded (perspective snapping). */
524
669
  inputFilter?: (p: PenPoint) => PenPoint;
525
- /** Mixer engine: the brush load at the start of the stroke. */
670
+ /** Mixer and wet-capable dab brushes: what the brush carries at the start of the stroke. */
526
671
  mixerLoad: MixerLoad;
672
+ /** The secondary colour 0..255 RGBA (a multi-colour brush without its own colours uses it). */
673
+ secondary?: readonly [number, number, number, number];
527
674
  }
528
675
  interface PaintStrokeResult {
529
676
  layerId: string;
@@ -532,7 +679,7 @@ interface PaintStrokeResult {
532
679
  dabs: number;
533
680
  /** Id of the recorded stroke (see `list_strokes` / `replay_strokes`), or `null` when nothing changed. */
534
681
  strokeId: string | null;
535
- /** Mixer engine brushes: what the brush carries after the stroke. */
682
+ /** Mixer and wet-capable dab brushes: what the brush carries after the stroke (cleaned unless it is dirty). */
536
683
  mixerLoad?: MixerLoad;
537
684
  }
538
685
  declare class PaintStrokeSession {
@@ -546,8 +693,7 @@ declare class PaintStrokeSession {
546
693
  private readonly touched;
547
694
  private readonly fresh;
548
695
  private readonly dry;
549
- private readonly mixers;
550
- private readonly mixerSmoother;
696
+ private readonly wet;
551
697
  private readonly apply;
552
698
  private readonly copies;
553
699
  private bounds;
@@ -555,7 +701,7 @@ declare class PaintStrokeSession {
555
701
  private closed;
556
702
  result: PaintStrokeResult | null;
557
703
  constructor(host: StrokeSessionHost, init: StrokeSessionInit);
558
- private mixerTarget;
704
+ private tileTarget;
559
705
  get open(): boolean;
560
706
  /** Adds raw pen samples (document pixels). */
561
707
  push(points: readonly PenPoint[]): void;
@@ -567,6 +713,12 @@ declare class PaintStrokeSession {
567
713
  }
568
714
  /** The Mixer Brush load a stroke starts with. */
569
715
  declare function mixerLoadForStroke(brush: PaintBrush, current: MixerLoad, color: readonly [number, number, number]): MixerLoad;
716
+ /**
717
+ * What any brush carries at the start of a stroke: the Mixer Brush as `mixerLoadForStroke`; a
718
+ * wet-capable dab brush the paint colour, full, plus the carried paint when it is dirty; other
719
+ * brushes nothing.
720
+ */
721
+ declare function brushLoadForStroke(brush: PaintBrush, current: MixerLoad, color: readonly [number, number, number]): MixerLoad;
570
722
 
571
723
  /**
572
724
  * Compositing for the paint editor, on the shared `RasterCompositor`: the paper is a content layer
@@ -665,6 +817,8 @@ interface SerializeOptions {
665
817
  /** Painting guides (symmetry, perspective, tracing and reference images as PNG data URLs). */
666
818
  guides?: unknown;
667
819
  swatches?: unknown;
820
+ /** The mixing pad: `{ current?: png data URL, saved: [{ id, name, png }] }`. */
821
+ mixingPad?: unknown;
668
822
  }
669
823
  /** The document as JSON with inline PNG data URLs. */
670
824
  declare function serializePaintDocument(state: PaintDocumentState, options?: SerializeOptions): SerializedPaintDocument;
@@ -676,6 +830,8 @@ interface LoadedPaintDocument {
676
830
  /** The saved guides and swatches, as stored (the controller validates them). */
677
831
  guides?: unknown;
678
832
  swatches?: unknown;
833
+ /** The saved mixing pad, as stored (the controller validates it). */
834
+ mixingPad?: unknown;
679
835
  /** What could not be read (the rest still loads). */
680
836
  problems: string[];
681
837
  }
@@ -1033,13 +1189,33 @@ declare const DEFAULT_SWATCHES: readonly Swatch[];
1033
1189
  /**
1034
1190
  * The mixing pad: a small canvas beside the painting to put paint on, smear it with a palette
1035
1191
  * knife and pick the mixed colour from. Paint is laid with the Mixer Brush in pigment mode and the
1036
- * knife is a pigment smudge, so blue and yellow pushed together make green. It is scratch space:
1037
- * not part of the painting and not in its undo history.
1192
+ * knife smears it with pigment mixing, so blue and yellow pushed together make green. It is
1193
+ * scratch space: not part of the painting's undo history, but saved with the document, and pads
1194
+ * can be kept by name (`MixingPad.toPng` / `fromPng`).
1195
+ *
1196
+ * `mix` puts measured blobs of paint down (areas in proportion to their parts) and works them
1197
+ * together with the knife, the way a painter mixes a pile on the palette.
1038
1198
  */
1039
1199
 
1040
1200
  type MixingPadTool = 'paint' | 'knife';
1041
1201
  declare const MIXING_PAD_WIDTH = 360;
1042
1202
  declare const MIXING_PAD_HEIGHT = 220;
1203
+ /** One paint in a `mix`: a colour and its parts. */
1204
+ interface PadMixPart {
1205
+ color: string;
1206
+ parts: number;
1207
+ }
1208
+ interface PadMixOptions {
1209
+ /** Centre of the pile in pad pixels (default the pad's centre). */
1210
+ x?: number;
1211
+ y?: number;
1212
+ /** Radius of the pile in pad pixels, 12..100 (default 40). */
1213
+ radius?: number;
1214
+ /** Knife passes over the pile, 1..20 (default 3): more passes, finer streaks around the mixed core. */
1215
+ passes?: number;
1216
+ /** Wipe the pile's area first (default true); false mixes into what is already there. */
1217
+ clear?: boolean;
1218
+ }
1043
1219
  declare class MixingPad {
1044
1220
  readonly width = 360;
1045
1221
  readonly height = 220;
@@ -1059,9 +1235,36 @@ declare class MixingPad {
1059
1235
  height: number;
1060
1236
  } | null;
1061
1237
  };
1238
+ /** Paints `points` with `brush` in `color` on the pad. */
1239
+ private run;
1062
1240
  /** The average colour within `radius` pixels of (x, y), or `null` where the pad is bare. */
1063
1241
  pick(x: number, y: number, radius?: number): string | null;
1064
1242
  clear(): void;
1243
+ /**
1244
+ * Mixes paints the way a painter does on a palette: blobs whose areas are in proportion to
1245
+ * their parts, side by side around (x, y), spiral knife passes that fold them together, and then the centre worked until it is fully mixed: a soft-edged core
1246
+ * of the Kubelka-Munk mix of the parts (`mixPigments`), with partly mixed streaks around it.
1247
+ * Returns that colour (picked back from the centre) and the pile's bounds.
1248
+ *
1249
+ * The core is laid, not smeared, because smearing alone drifts: the knife samples in RGB and a
1250
+ * mixed colour's scattering is re-estimated from its luminance, so repeated small mixes favour
1251
+ * the lighter paint (see GOTCHAS).
1252
+ */
1253
+ mix(paints: readonly PadMixPart[], options?: PadMixOptions): {
1254
+ color: string | null;
1255
+ bounds: {
1256
+ x: number;
1257
+ y: number;
1258
+ width: number;
1259
+ height: number;
1260
+ };
1261
+ };
1262
+ /** The pad's pixels as a PNG data URL (with alpha), for saving. */
1263
+ toPng(): string;
1264
+ /** Replaces the pad with a PNG (data URL or base64) of the pad's size, as `toPng` writes. */
1265
+ fromPng(png: string): void;
1266
+ /** Whether nothing is on the pad. */
1267
+ get empty(): boolean;
1065
1268
  /** The pad over white, `width × height` RGBA. */
1066
1269
  pixels(): Uint8ClampedArray;
1067
1270
  }
@@ -1122,6 +1325,8 @@ interface PaintEditorState {
1122
1325
  swatches: readonly Swatch[];
1123
1326
  /** Changes whenever the mixing pad does. */
1124
1327
  mixingPadVersion: number;
1328
+ /** Mixing pads kept by name (saved with the document). */
1329
+ savedMixingPads: readonly SavedMixingPadInfo[];
1125
1330
  /** A stroke is in progress. */
1126
1331
  stroking: boolean;
1127
1332
  /** Increments on every change. */
@@ -1163,6 +1368,8 @@ interface PaintStrokeInit {
1163
1368
  x: number;
1164
1369
  y: number;
1165
1370
  };
1371
+ /** What the brush carries at the start (default: from the editor's brush load); replays pass the recorded one. */
1372
+ load?: MixerLoad;
1166
1373
  }
1167
1374
  interface LayerUpdate {
1168
1375
  name?: string;
@@ -1207,6 +1414,11 @@ interface PixelImage {
1207
1414
  height: number;
1208
1415
  data: Uint8ClampedArray;
1209
1416
  }
1417
+ /** A mixing pad kept by name. */
1418
+ interface SavedMixingPadInfo {
1419
+ id: string;
1420
+ name: string;
1421
+ }
1210
1422
  declare class PaintEditorController {
1211
1423
  private doc;
1212
1424
  private extensions;
@@ -1215,6 +1427,8 @@ declare class PaintEditorController {
1215
1427
  private readonly historyLimit;
1216
1428
  private brush;
1217
1429
  private userBrushes;
1430
+ /** The library list in the state (the same array until the user brushes change, so thumbnails are not redrawn). */
1431
+ private library;
1218
1432
  private color;
1219
1433
  private secondaryColor;
1220
1434
  private mixerLoad;
@@ -1233,6 +1447,8 @@ declare class PaintEditorController {
1233
1447
  private guides;
1234
1448
  private swatches;
1235
1449
  private readonly pad;
1450
+ private savedPads;
1451
+ private nextPadId;
1236
1452
  private tracingVersion;
1237
1453
  private tracingCache;
1238
1454
  private readonly imagePixels;
@@ -1369,6 +1585,11 @@ declare class PaintEditorController {
1369
1585
  resetView(): PaintView;
1370
1586
  setHand(hand: Hand): Hand;
1371
1587
  private strokeBrush;
1588
+ /**
1589
+ * After a stroke: a Mixer Brush that loads colour keeps its whole load; any other dirty brush
1590
+ * keeps only the paint it carries (the reservoir stays the Mixer Brush's).
1591
+ */
1592
+ private keepLoad;
1372
1593
  /** Extra copies every new stroke paints (the painting guides add mirror and kaleidoscope copies). */
1373
1594
  protected strokeCopies(): Affine[];
1374
1595
  private sessionFor;
@@ -1528,6 +1749,35 @@ declare class PaintEditorController {
1528
1749
  setColor?: boolean;
1529
1750
  }): string | null;
1530
1751
  clearMixingPad(): void;
1752
+ /**
1753
+ * Mixes paints on the pad like a painter: blobs in proportion to their `parts`, knife passes,
1754
+ * and a fully mixed core of the Kubelka-Munk mix (see `MixingPad.mix`). With `setColor` the
1755
+ * mix becomes the paint colour. Returns the mixed colour and the pile's bounds.
1756
+ */
1757
+ mixOnPad(paints: readonly {
1758
+ color: string;
1759
+ parts: number;
1760
+ }[], options?: PadMixOptions & {
1761
+ setColor?: boolean;
1762
+ }): {
1763
+ color: string;
1764
+ bounds: RasterRect;
1765
+ };
1766
+ /** The mixing pad as a PNG data URL (with alpha). */
1767
+ mixingPadPng(): string;
1768
+ /** Replaces the mixing pad with a PNG (data URL or base64, 360 × 220). */
1769
+ setMixingPadPng(png: string): void;
1770
+ /** The mixing pads kept by name. */
1771
+ listMixingPads(): SavedMixingPadInfo[];
1772
+ /** Keeps the mixing pad as it is now under `name` (saved with the document). Returns its id. */
1773
+ saveMixingPad(name?: string): SavedMixingPadInfo;
1774
+ private requirePad;
1775
+ /** Puts a kept pad back on the mixing pad (replacing what is there). */
1776
+ loadMixingPad(id: string): SavedMixingPadInfo;
1777
+ /** Forgets a kept pad. */
1778
+ removeMixingPad(id: string): SavedMixingPadInfo;
1779
+ private mixingPadJSON;
1780
+ private readMixingPad;
1531
1781
  /** A layer's tile surface (live while stroking), for thumbnails. */
1532
1782
  layerSurface(id: string): TiledSurface | null;
1533
1783
  }
@@ -1781,5 +2031,37 @@ interface BlendMenuGroup {
1781
2031
  * description as a tooltip. Both bindings render this as `<optgroup>`s.
1782
2032
  */
1783
2033
  declare const BLEND_MENU_GROUPS: readonly BlendMenuGroup[];
2034
+ /** The engine-specific part of the brush panel: which sliders and toggles the active brush has. */
2035
+ interface BrushEngineControls {
2036
+ /** Section heading. */
2037
+ title: string;
2038
+ sliders: readonly SliderControl[];
2039
+ /** Where the RGB / pigment choice is stored, or `null` when the engine has none. */
2040
+ mixingPath: 'behavior.mixing' | 'blender.mixing' | 'smudge.mixing' | null;
2041
+ /** The dirty-brush toggle (`behavior.dirty`). */
2042
+ dirty: boolean;
2043
+ /** The multi-colour controls (`behavior.multicolor`). */
2044
+ multicolor: boolean;
2045
+ /** Load / Clean buttons and a swatch of what the brush carries. */
2046
+ load: boolean;
2047
+ }
2048
+ /**
2049
+ * The controls the active brush's engine adds to the brush panel (`null` for the eraser):
2050
+ * blender strength and blend distance; smudge strength and length; the Mixer Brush's pick-up,
2051
+ * mix and load; a dab brush's pick-up and mix ratios. Every engine but the eraser has an RGB /
2052
+ * pigment choice; dab and mixer brushes also have the dirty and multi-colour controls. Pick-up
2053
+ * and mix always go through `behavior`, which keeps a Mixer Brush's `mixer` block in sync.
2054
+ */
2055
+ declare function brushEngineControls(brush: Pick<PaintBrush, 'engine'>): BrushEngineControls | null;
2056
+ /** The multi-colour sliders for a mode: softness, and dabs per colour in `dab` mode. */
2057
+ declare function multiColorSliders(mode: string): readonly SliderControl[];
2058
+ /** A brush change setting `path` (e.g. `smudge.mixing`, `behavior.dirty`) to any value, keeping the rest of its block. */
2059
+ declare function patchBrushValue(brush: PaintBrush, path: string, value: unknown): PaintBrushInput;
2060
+ /** What the brush carries (picked-up paint, else loaded paint) as #rrggbb, or `null` when it is clean and empty. */
2061
+ declare function brushLoadColor(load: MixerLoad): string | null;
2062
+ /** Sample sizes (square, in pad pixels) for picking from the mixing pad. */
2063
+ declare const PAD_SAMPLE_SIZES: readonly number[];
2064
+ /** The averaging radius `mixingPadPick` takes for a sample size: 1 → 0, 3 → 1, 5 → 2 … */
2065
+ declare function padSampleRadius(size: number): number;
1784
2066
 
1785
- export { type Affine, BLEND_MENU_GROUPS, BRUSH_ID, BRUSH_ROTATIONS, BRUSH_SLIDERS, BUILTIN_BRUSHES, type BlendMenuGroup, BlendMode, BlendModeInfo, type BrushDynamics, type BrushLibraryFile, type BrushRotation, type BrushThumbnail, type ColorWheel, DEFAULT_BRUSH_ID, DEFAULT_DPI, DEFAULT_DYNAMICS, DEFAULT_PAPER, DEFAULT_SWATCHES, DEFAULT_VIEW, DryStroke, type DryStrokeOptions, type FillLayerOptions, GRAIN_PATTERNS, GRAIN_TILE, type GrainPattern, type GrainTuning, HARMONY_KINDS, type Hand, type HarmonyColor, type HarmonyKind, IDENTITY_AFFINE, IDENTITY_COPY, type ImageEncoder, type ImageFormat, type ImageNode, type LayerUpdate, type Linear2, type LoadedPaintDocument, MAX_CANVAS_SIDE, MAX_COORDINATE, MAX_IMAGE_SIDE, MAX_ZOOM, MIN_CANVAS_SIDE, MIN_ZOOM, MIXING_PAD_HEIGHT, MIXING_PAD_WIDTH, MixingPad, type MixingPadTool, PAINT_BRUSH_CATEGORIES, PAINT_BRUSH_ENGINES, PAINT_FORMAT, PAINT_SCHEMA_VERSION, PAINT_TOOLS, PAPER_FAMILIES, PAPER_LIBRARY, PAPER_NODE_ID, PAPER_SLIDERS, type Paint2DContext, type PaintAgentCommand, type PaintAssetStore, type PaintBrush, type PaintBrushCategory, type PaintBrushEngine, type PaintBrushInput, type PaintCanvas, PaintCanvasPresenter, PaintDirector, type PaintDirectorOptions, PaintDocumentError, type PaintDocumentExtensions, type PaintDocumentState, PaintEditorController, PaintEditorError, type PaintEditorOptions, type PaintEditorState, type PaintEditorToolProviderOptions, PaintGesture, type PaintGroupLayer, type PaintGuides, type PaintLayer, type PaintLayerInfo, type PaintPerformanceResult, type PaintPixelLayer, type PaintPointerEvent, PaintPresence, type PaintPresenceActor, type PaintPresenceState, type PaintStrokeInit, type PaintStrokeResult, PaintStrokeSession, type PaintTool, type PaintView, type PaperFamily, PaperGrain, type PaperNode, type PaperSettings, type PaperTiling, type PenDab, PenDabWalker, type PenPoint, PenSmoother, type PerspectiveGuide, PerspectiveSnapper, type PixelImage, type Point2, type PointerSample, type RecordedStroke, type ReferenceImage, type RenderOptions, type ReplayOptions, type Rgb255, SNAP_DECIDE_DISTANCE, SYMMETRY_MODES, type SerializeOptions, type SerializedPaintDocument, type SliderControl, type StrokeCopy, type StrokeSessionHost, type StrokeSessionInit, type StrokeVariation, type Swatch, type SymmetryGuide, type SymmetryMode, TRACING_NODE_ID, TileableNoise, type TracingImage, type ViewLayer, type ViewportBox, affineCopy, applyAffine, brushToJSON, clonePaper, colorHarmony, composeAffine, createPaintCompositor, createPaintDocument, createPaintEditorToolProvider, createPaintLayer, decodeImageBlob, defaultGuides, defaultImageEncoder, deserializePaintDocument, deserializePaintDocumentAsync, documentExtensions, documentToScreen, downloadBytes, downsampleRgba, fitZoom, grainCatch, grainHeights, guideOverlay, handlePaintShortcut, hash2, hexToRgb8, hslToRgb, hsvToRgb, hueToWheel, invertAffine, isPalmTouch, mapPenPoint, mix32, mixerLoadForStroke, mixerTip, newPaintLayerId, normalizeBrushStroke, normalizeCanvas, normalizeDynamics, normalizePaintBrush, normalizePaper, normalizePerspective, normalizeRotation, normalizeSymmetry, normalizeView, paperGrain, paperKey, paperNode, paperPreset, parseBrushLibrary, parseHexColor, patchPath, penOrientation, perspectiveDirections, perspectiveGuideLines, pointerToPenPoint, rand01, randSigned, readPath, renderBrushThumbnail, renderColorWheel, renderPaper, rgbToHexColor, rgbToHsl, rgbToHsv, samplePressure, screenToDocument, seedFromString, serializePaintDocument, serializePaintDocumentExternal, strokeBounds, strokeDuration, strokeSchedule, sweepPath, symmetryAxisAngles, symmetryAxisSegments, symmetryCopies, thumbnailStroke, tiltToOrientation, toStrokePoint, variationAffine, varyStroke, viewCssTransform, viewMatrix, viewPenToDocument, wheelAngle, wheelPick, wheelPoint, wheelToHue };
2067
+ export { type Affine, BLEND_MENU_GROUPS, BRUSH_ID, BRUSH_ROTATIONS, BRUSH_SLIDERS, BUILTIN_BRUSHES, type BlendMenuGroup, BlendMode, BlendModeInfo, type BrushBehavior, type BrushBehaviorInput, type BrushDynamics, type BrushEngineControls, type BrushLibraryFile, type BrushRotation, type BrushThumbnail, type ColorWheel, DEFAULT_BEHAVIOR, DEFAULT_BRUSH_ID, DEFAULT_DPI, DEFAULT_DYNAMICS, DEFAULT_MULTI_COLOR, DEFAULT_PAPER, DEFAULT_SWATCHES, DEFAULT_VIEW, DryStroke, type DryStrokeOptions, type FillLayerOptions, GRAIN_PATTERNS, GRAIN_TILE, type GrainPattern, type GrainTuning, HARMONY_KINDS, type Hand, type HarmonyColor, type HarmonyKind, IDENTITY_AFFINE, IDENTITY_COPY, type ImageEncoder, type ImageFormat, type ImageNode, type LayerUpdate, type Linear2, type LoadedPaintDocument, MAX_CANVAS_SIDE, MAX_COORDINATE, MAX_IMAGE_SIDE, MAX_ZOOM, MIN_CANVAS_SIDE, MIN_ZOOM, MIXING_PAD_HEIGHT, MIXING_PAD_WIDTH, MULTI_COLOR_MODES, MixingPad, type MixingPadTool, type MultiColorMode, type MultiColorSettings, PAD_SAMPLE_SIZES, PAINT_BRUSH_CATEGORIES, PAINT_BRUSH_ENGINES, PAINT_FORMAT, PAINT_SCHEMA_VERSION, PAINT_TOOLS, PAPER_FAMILIES, PAPER_LIBRARY, PAPER_NODE_ID, PAPER_SLIDERS, type PadMixOptions, type PadMixPart, type Paint2DContext, type PaintAgentCommand, type PaintAssetStore, type PaintBrush, type PaintBrushCategory, type PaintBrushEngine, type PaintBrushInput, type PaintCanvas, PaintCanvasPresenter, PaintDabStroke, type PaintDabStrokeOptions, PaintDirector, type PaintDirectorOptions, PaintDocumentError, type PaintDocumentExtensions, type PaintDocumentState, PaintEditorController, PaintEditorError, type PaintEditorOptions, type PaintEditorState, type PaintEditorToolProviderOptions, PaintGesture, type PaintGroupLayer, type PaintGuides, type PaintLayer, type PaintLayerInfo, type PaintPerformanceResult, type PaintPixelLayer, type PaintPointerEvent, PaintPresence, type PaintPresenceActor, type PaintPresenceState, type PaintStrokeInit, type PaintStrokeResult, PaintStrokeSession, type PaintTool, type PaintView, type PaperFamily, PaperGrain, type PaperNode, type PaperSettings, type PaperTiling, type PenDab, PenDabWalker, type PenPoint, PenSmoother, type PerspectiveGuide, PerspectiveSnapper, type PixelImage, type Point2, type PointerSample, type RecordedStroke, type ReferenceImage, type RenderOptions, type ReplayOptions, type Rgb255, SNAP_DECIDE_DISTANCE, SYMMETRY_MODES, type SavedMixingPadInfo, type SerializeOptions, type SerializedPaintDocument, type SliderControl, type StrokeCopy, type StrokeSessionHost, type StrokeSessionInit, type StrokeVariation, type Swatch, type SymmetryGuide, type SymmetryMode, TRACING_NODE_ID, TileableNoise, type TracingImage, type ViewLayer, type ViewportBox, affineCopy, applyAffine, brushEngineControls, brushLoadColor, brushLoadForStroke, brushToJSON, clonePaper, colorHarmony, composeAffine, createPaintCompositor, createPaintDocument, createPaintEditorToolProvider, createPaintLayer, decodeImageBlob, defaultGuides, defaultImageEncoder, deserializePaintDocument, deserializePaintDocumentAsync, documentExtensions, documentToScreen, downloadBytes, downsampleRgba, fitZoom, grainCatch, grainHeights, guideOverlay, handlePaintShortcut, hash2, hexToRgb8, hslToRgb, hsvToRgb, hueToWheel, invertAffine, isPalmTouch, isWetCapable, mapPenPoint, mix32, mixerLoadForStroke, mixerTip, multiColorForDab, multiColorGradient, multiColorPalette, multiColorSliders, newPaintLayerId, normalizeBehavior, normalizeBrushStroke, normalizeCanvas, normalizeDynamics, normalizeMultiColor, normalizePaintBrush, normalizePaper, normalizePerspective, normalizeRotation, normalizeSymmetry, normalizeView, padSampleRadius, paperGrain, paperKey, paperNode, paperPreset, parseBrushLibrary, parseHexColor, patchBrushValue, patchPath, penOrientation, perspectiveDirections, perspectiveGuideLines, pointerToPenPoint, rand01, randSigned, readPath, renderBrushThumbnail, renderColorWheel, renderPaper, rgbToHexColor, rgbToHsl, rgbToHsv, samplePressure, screenToDocument, seedFromString, serializePaintDocument, serializePaintDocumentExternal, strokeBounds, strokeDuration, strokeSchedule, sweepPath, symmetryAxisAngles, symmetryAxisSegments, symmetryCopies, thumbnailStroke, tiltToOrientation, toStrokePoint, usesPaintExchange, variationAffine, varyStroke, viewCssTransform, viewMatrix, viewPenToDocument, wheelAngle, wheelPick, wheelPoint, wheelToHue };