@pixerate/editor 0.115.0 → 0.117.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 (127) hide show
  1. package/THIRD_PARTY_NOTICES.md +3 -0
  2. package/dist/animation-editor/index.cjs +54 -54
  3. package/dist/animation-editor/index.d.cts +2 -2
  4. package/dist/animation-editor/index.d.ts +2 -2
  5. package/dist/animation-editor/index.js +2 -2
  6. package/dist/{chunk-I7FQBWMQ.cjs → chunk-2J3LS7JZ.cjs} +333 -333
  7. package/dist/{chunk-I7FQBWMQ.cjs.map → chunk-2J3LS7JZ.cjs.map} +1 -1
  8. package/dist/{chunk-TFOPHZ3U.cjs → chunk-7ISPWEJA.cjs} +302 -5
  9. package/dist/chunk-7ISPWEJA.cjs.map +1 -0
  10. package/dist/{chunk-VXR7XWMF.js → chunk-7LNZAWSJ.js} +437 -41
  11. package/dist/chunk-7LNZAWSJ.js.map +1 -0
  12. package/dist/{chunk-DFQEX3AM.cjs → chunk-BYAWOAST.cjs} +223 -30
  13. package/dist/chunk-BYAWOAST.cjs.map +1 -0
  14. package/dist/chunk-COAMRKVI.cjs +159 -0
  15. package/dist/chunk-COAMRKVI.cjs.map +1 -0
  16. package/dist/{chunk-ST7E3NEE.cjs → chunk-EBSYZRSW.cjs} +230 -732
  17. package/dist/chunk-EBSYZRSW.cjs.map +1 -0
  18. package/dist/chunk-EJVDBITD.cjs +501 -0
  19. package/dist/chunk-EJVDBITD.cjs.map +1 -0
  20. package/dist/{chunk-LPLCWZTM.cjs → chunk-FODKPD44.cjs} +555 -159
  21. package/dist/chunk-FODKPD44.cjs.map +1 -0
  22. package/dist/{chunk-XK5LZKBD.js → chunk-HKDTM6VC.js} +298 -1
  23. package/dist/chunk-HKDTM6VC.js.map +1 -0
  24. package/dist/chunk-IPFRWILU.js +1908 -0
  25. package/dist/chunk-IPFRWILU.js.map +1 -0
  26. package/dist/chunk-IVSNUXW7.js +3339 -0
  27. package/dist/chunk-IVSNUXW7.js.map +1 -0
  28. package/dist/{chunk-LKCYANMW.js → chunk-JHXMNOMF.js} +198 -5
  29. package/dist/chunk-JHXMNOMF.js.map +1 -0
  30. package/dist/{chunk-FV5AXTSF.cjs → chunk-JSSBHMMU.cjs} +36 -188
  31. package/dist/chunk-JSSBHMMU.cjs.map +1 -0
  32. package/dist/chunk-L2T6EJ5S.cjs +3339 -0
  33. package/dist/chunk-L2T6EJ5S.cjs.map +1 -0
  34. package/dist/chunk-LF4WOZXZ.js +159 -0
  35. package/dist/chunk-LF4WOZXZ.js.map +1 -0
  36. package/dist/{chunk-26PGXHY7.cjs → chunk-M7O4BJRH.cjs} +36 -36
  37. package/dist/{chunk-26PGXHY7.cjs.map → chunk-M7O4BJRH.cjs.map} +1 -1
  38. package/dist/chunk-N3467VD6.js +501 -0
  39. package/dist/chunk-N3467VD6.js.map +1 -0
  40. package/dist/{chunk-XICA4PVO.js → chunk-R2JE6SA7.js} +17 -17
  41. package/dist/chunk-SMJGE7HM.cjs +1908 -0
  42. package/dist/chunk-SMJGE7HM.cjs.map +1 -0
  43. package/dist/{chunk-MMVHBKMM.js → chunk-T2P5TIZ6.js} +2 -2
  44. package/dist/{chunk-C6C5PDKU.js → chunk-VD2UDVF7.js} +102 -604
  45. package/dist/chunk-VD2UDVF7.js.map +1 -0
  46. package/dist/{chunk-FGW5OWJ4.js → chunk-VJUML33I.js} +4 -156
  47. package/dist/chunk-VJUML33I.js.map +1 -0
  48. package/dist/export/index.cjs +4 -2
  49. package/dist/export/index.cjs.map +1 -1
  50. package/dist/export/index.js +5 -3
  51. package/dist/{export-BJE8T0YA.d.cts → export-DMOuzgZZ.d.cts} +9 -4
  52. package/dist/{export-Con3UKkm.d.ts → export-Dul1SbPv.d.ts} +9 -4
  53. package/dist/export-Dv0-ZVrV.d.cts +501 -0
  54. package/dist/export-Dv0-ZVrV.d.ts +501 -0
  55. package/dist/{retouch-Bxkuc0KH.d.cts → flow-4-OC-UkQ.d.cts} +582 -2
  56. package/dist/{retouch-Do6tBs2H.d.ts → flow-Cb7lkPm8.d.ts} +582 -2
  57. package/dist/image-editor/index.cjs +1345 -1095
  58. package/dist/image-editor/index.cjs.map +1 -1
  59. package/dist/image-editor/index.d.cts +8 -8
  60. package/dist/image-editor/index.d.ts +8 -8
  61. package/dist/image-editor/index.js +300 -50
  62. package/dist/image-editor/index.js.map +1 -1
  63. package/dist/index.cjs +4 -2
  64. package/dist/index.cjs.map +1 -1
  65. package/dist/index.js +5 -3
  66. package/dist/index.js.map +1 -1
  67. package/dist/kernel-wasm-WYCGNZR4.js +10 -0
  68. package/dist/kernel-wasm-WYCGNZR4.js.map +1 -0
  69. package/dist/kernel-wasm-YPVAEMVN.cjs +10 -0
  70. package/dist/kernel-wasm-YPVAEMVN.cjs.map +1 -0
  71. package/dist/paint/index.cjs +170 -4
  72. package/dist/paint/index.cjs.map +1 -1
  73. package/dist/paint/index.d.cts +225 -3
  74. package/dist/paint/index.d.ts +225 -3
  75. package/dist/paint/index.js +171 -5
  76. package/dist/paint-editor/index.cjs +4735 -890
  77. package/dist/paint-editor/index.cjs.map +1 -1
  78. package/dist/paint-editor/index.d.cts +1526 -278
  79. package/dist/paint-editor/index.d.ts +1526 -278
  80. package/dist/paint-editor/index.js +4561 -716
  81. package/dist/paint-editor/index.js.map +1 -1
  82. package/dist/raster/index.cjs +7 -4
  83. package/dist/raster/index.cjs.map +1 -1
  84. package/dist/raster/index.d.cts +2 -2
  85. package/dist/raster/index.d.ts +2 -2
  86. package/dist/raster/index.js +36 -33
  87. package/dist/{render-CrmHOgpo.d.ts → render-B05K68_h.d.ts} +9 -1
  88. package/dist/{render-B1K_OoLy.d.cts → render-DnAbIKcs.d.cts} +9 -1
  89. package/dist/{svg-BnRj0q8i.d.cts → svg-CVdFc80-.d.cts} +2 -2
  90. package/dist/{svg-CmmBFfGT.d.ts → svg-DlEuhwoX.d.ts} +2 -2
  91. package/dist/{types-1SUQKDAw.d.ts → types-CDLGhAZp.d.ts} +40 -2
  92. package/dist/{types-yINF17Ta.d.cts → types-Tfly4Aya.d.cts} +40 -2
  93. package/dist/vector/index.cjs +6536 -4525
  94. package/dist/vector/index.cjs.map +1 -1
  95. package/dist/vector/index.d.cts +426 -32
  96. package/dist/vector/index.d.ts +426 -32
  97. package/dist/vector/index.js +11397 -9386
  98. package/dist/vector/index.js.map +1 -1
  99. package/dist/video-editor/index.cjs +502 -2165
  100. package/dist/video-editor/index.cjs.map +1 -1
  101. package/dist/video-editor/index.d.cts +3 -501
  102. package/dist/video-editor/index.d.ts +3 -501
  103. package/dist/video-editor/index.js +151 -1814
  104. package/dist/video-editor/index.js.map +1 -1
  105. package/package.json +1 -1
  106. package/skills/pixerate-image-editor/references/photoshop-files.md +7 -3
  107. package/skills/pixerate-vector/SKILL.md +3 -0
  108. package/dist/chunk-C6C5PDKU.js.map +0 -1
  109. package/dist/chunk-DFQEX3AM.cjs.map +0 -1
  110. package/dist/chunk-FGW5OWJ4.js.map +0 -1
  111. package/dist/chunk-FV5AXTSF.cjs.map +0 -1
  112. package/dist/chunk-GX332W2C.js +0 -181
  113. package/dist/chunk-GX332W2C.js.map +0 -1
  114. package/dist/chunk-LKCYANMW.js.map +0 -1
  115. package/dist/chunk-LPLCWZTM.cjs.map +0 -1
  116. package/dist/chunk-MUUXNRU4.cjs +0 -181
  117. package/dist/chunk-MUUXNRU4.cjs.map +0 -1
  118. package/dist/chunk-ST7E3NEE.cjs.map +0 -1
  119. package/dist/chunk-TCKIXI46.cjs +0 -1136
  120. package/dist/chunk-TCKIXI46.cjs.map +0 -1
  121. package/dist/chunk-TFOPHZ3U.cjs.map +0 -1
  122. package/dist/chunk-UBIXVXPR.js +0 -1136
  123. package/dist/chunk-UBIXVXPR.js.map +0 -1
  124. package/dist/chunk-VXR7XWMF.js.map +0 -1
  125. package/dist/chunk-XK5LZKBD.js.map +0 -1
  126. /package/dist/{chunk-XICA4PVO.js.map → chunk-R2JE6SA7.js.map} +0 -0
  127. /package/dist/{chunk-MMVHBKMM.js.map → chunk-T2P5TIZ6.js.map} +0 -0
@@ -1,17 +1,18 @@
1
- import { a1 as RasterLayer, a0 as RasterGroupLayer, a5 as RasterPixelLayer, C as CompositorLayer, a as RasterCompositor, R as RasterSources, U as LayerLocks } from '../render-CrmHOgpo.js';
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 '../retouch-Do6tBs2H.js';
3
- export { e as MAX_BRUSH_SIZE } from '../retouch-Do6tBs2H.js';
1
+ import { a1 as RasterLayer, a0 as RasterGroupLayer, a5 as RasterPixelLayer, C as CompositorLayer, a as RasterCompositor, R as RasterSources, U as LayerLocks } from '../render-B05K68_h.js';
2
+ import { D as Dab, C as CoverageMap, B as BrushSettings, e as StrokePoint, P as PaintTileTarget, T as TileMedium, M as MixerSettings, h as MixingModel, i as BlenderSettings, j as SmudgeSettings, a as MixerLoad, W as WetWorld, k as WetTilt, l as WetParams, m as WetFlowSpec, n as RgbTriple, o as WetWorldOptions, p as WetBackendInfo, q as WetSnapshot, r as WetPaperInput } from '../flow-Cb7lkPm8.js';
3
+ export { s as DEFAULT_FLOW, t as DEFAULT_WET_PARAMS, u as MAX_BRUSH_SIZE, v as WET_FLOW_KINDS, w as WET_PARAM_KEYS, x as WetFlowKind, y as WetSupport, z as normalizeFlow, A as normalizeWetParams, E as wetSupport } from '../flow-Cb7lkPm8.js';
4
4
  import { R as RasterRect, j as TiledSurface, a as BlendMode, b as BlendModeInfo } from '../surface-Cn30xIV6.js';
5
5
  export { B as BLEND_MODES, P as PAINTERLY_BLEND_MODES, h as Tile, v as isBlendMode, w as isPainterlyBlendMode, I as standardBlendMode } from '../surface-Cn30xIV6.js';
6
- import { I as ImageEditorAssetStore, S as SerializedLayer, P as PsdExportResult } from '../export-Con3UKkm.js';
6
+ import { I as ImageEditorAssetStore, S as SerializedLayer, P as PsdExportResult } from '../export-Dul1SbPv.js';
7
+ import { V as VideoProject, A as VideoFfmpegExport } from '../export-Dv0-ZVrV.js';
7
8
  import { g as PresenceStore, e as PresenceClock, d as PresenceActorInfo, c as PresenceActor, f as PresenceState } from '../presence-Cb6WhcrQ.js';
8
- import { a as AgentToolProvider } from '../registry-D-4fit9C.js';
9
+ import { a as AgentToolProvider, A as AgentTool } from '../registry-D-4fit9C.js';
9
10
  import '../types-CwMPoRMU.js';
10
11
  import '../builtin-CY97ppKM.js';
11
12
  import '../bezier-DYBvLgUH.js';
12
13
  import '../types-BFkYA4eF.js';
13
14
  import '../warp-DMN30aY6.js';
14
- import '../types-1SUQKDAw.js';
15
+ import '../types-CDLGhAZp.js';
15
16
  import '../schema-Di0CS6Pe.js';
16
17
 
17
18
  /**
@@ -92,6 +93,8 @@ interface PaperSettings {
92
93
  permeability: number;
93
94
  /** Wet media: how much the grain catches pigment, 0..1. */
94
95
  roughness: number;
96
+ /** Metallic paper (gold or silver leaf, pearlescent card): the grain catches the light, 0..1 (default 0). */
97
+ metallic?: number;
95
98
  }
96
99
  /** Side of the generated grain tile in pixels (at scale 1). */
97
100
  declare const GRAIN_TILE = 256;
@@ -142,7 +145,365 @@ declare function renderPaper(paper: PaperSettings, rect: {
142
145
  y: number;
143
146
  width: number;
144
147
  height: number;
145
- }, grain?: PaperGrain): Uint8ClampedArray;
148
+ }, grain?: PaperGrain, light?: {
149
+ azimuth: number;
150
+ elevation: number;
151
+ }): Uint8ClampedArray;
152
+
153
+ /**
154
+ * The paint editor's dry-media stroke engine, on the shared brush engine (`@pixerate/editor/paint`):
155
+ *
156
+ * - Pen input: pressure, tilt (`tiltX` / `tiltY` or `altitude` / `azimuth`), barrel rotation
157
+ * (`twist`) and time (for velocity). Position and pressure are smoothed separately.
158
+ * - `PenDabWalker` walks the points into the shared `Dab`s at `spacing × diameter`, with every
159
+ * pen channel interpolated, and applies the brush's dynamics, rotation mode and jitter.
160
+ * Jitter is a pure hash of the stroke seed and the dab index, so a stroke replays exactly.
161
+ * - `DryStroke` rasterizes each dab with the shared round tip, multiplies it by how much of it the
162
+ * paper grain catches (`grainCatch`), and builds it up in the shared `CoverageMap`; the session
163
+ * then composites the coverage with the shared `applyStrokeToTile`.
164
+ * - Symmetry: one stroke can paint several copies of its points (mirror and kaleidoscope), each
165
+ * with its own walker, into the same coverage, so the copies are one stroke and one undo step.
166
+ *
167
+ * Pure data in, pixels out: no DOM, no framework.
168
+ */
169
+
170
+ /** One pen sample in document pixels. Every channel but x and y is optional. */
171
+ interface PenPoint {
172
+ x: number;
173
+ y: number;
174
+ /** 0..1 (missing: 1, a mouse). */
175
+ pressure?: number;
176
+ /** Milliseconds since any fixed origin (for velocity; missing: no velocity dynamics). */
177
+ t?: number;
178
+ /** Pointer Events tilt in degrees, -90..90 (used when `altitude` / `azimuth` are missing). */
179
+ tiltX?: number;
180
+ tiltY?: number;
181
+ /** Radians from the surface: π/2 is upright, 0 lies flat. */
182
+ altitude?: number;
183
+ /** Radians clockwise from the +x axis (screen y points down): where the pen leans. */
184
+ azimuth?: number;
185
+ /** Barrel rotation in degrees clockwise, 0..359. */
186
+ twist?: number;
187
+ }
188
+ /** A pen's lean as altitude and azimuth, or `null` when the sample has no tilt. */
189
+ declare function penOrientation(p: PenPoint): {
190
+ altitude: number;
191
+ azimuth: number;
192
+ } | null;
193
+ /** Pointer Events `tiltX` / `tiltY` (degrees) → altitude and azimuth (radians). */
194
+ declare function tiltToOrientation(tiltX: number, tiltY: number): {
195
+ altitude: number;
196
+ azimuth: number;
197
+ };
198
+ /**
199
+ * Exponential smoothing of position and of pressure, each by its own amount (0..0.95). Feed raw
200
+ * samples; the first sample passes through.
201
+ */
202
+ declare class PenSmoother {
203
+ private pos;
204
+ private pressure;
205
+ private readonly a;
206
+ private readonly ap;
207
+ constructor(position: number, pressure: number);
208
+ push(p: PenPoint): PenPoint;
209
+ /** Where the smoothed position is now (or `null` before the first sample). */
210
+ get position(): {
211
+ x: number;
212
+ y: number;
213
+ } | null;
214
+ }
215
+ /** A linear map of document vectors `[a, b, c, d]`: (x, y) → (a·x + b·y, c·x + d·y). */
216
+ type Linear2 = readonly [number, number, number, number];
217
+ /** How one copy of a stroke maps the input: a point map and its linear part (for dab angles). */
218
+ interface StrokeCopy {
219
+ map(p: PenPoint): PenPoint;
220
+ linear: Linear2;
221
+ }
222
+ declare const IDENTITY_COPY: StrokeCopy;
223
+ /** A dab with the pen state it was made at (the shared `Dab` plus pressure for the grain). */
224
+ interface PenDab extends Dab {
225
+ pressure: number;
226
+ /** Direction of travel at the dab, radians counter-clockwise with y up (as `angle`). */
227
+ direction: number;
228
+ /** The dab's index within its copy of the stroke (0, 1, 2 …). */
229
+ index: number;
230
+ }
231
+ /**
232
+ * Walks pen samples into dabs, incrementally (any chunking gives the same dabs). Dab angles are in
233
+ * the shared rasterizer's convention: radians, counter-clockwise with y up.
234
+ */
235
+ declare class PenDabWalker {
236
+ readonly brush: PaintBrush;
237
+ private readonly seed;
238
+ private readonly linear;
239
+ private last;
240
+ private nextAt;
241
+ private pendingFirst;
242
+ private index;
243
+ private dir;
244
+ private lastT;
245
+ private speed;
246
+ constructor(brush: PaintBrush, seed?: number, linear?: Linear2);
247
+ private sample;
248
+ private diameter;
249
+ private stepLen;
250
+ /** A rasterizer angle (CCW, y up) mapped through this copy's linear part. */
251
+ private mapAngle;
252
+ private dab;
253
+ push(p: PenPoint, out: PenDab[]): void;
254
+ finish(out: PenDab[]): void;
255
+ }
256
+ interface DryStrokeOptions {
257
+ /** The paper's dry height field, or `null` for no grain. */
258
+ grain: PaperGrain | null;
259
+ /** The paper's grain depth, 0..1 (multiplied by the brush's grain response). */
260
+ paperDepth: number;
261
+ /** Jitter seed (record it to replay the stroke exactly). */
262
+ seed?: number;
263
+ /** Copies of the stroke (symmetry); default one identity copy. */
264
+ copies?: readonly StrokeCopy[];
265
+ }
266
+ /**
267
+ * One dab-engine stroke (dab and eraser brushes): smoothing, the dab walk of every copy, grain and
268
+ * build-up in a shared coverage map.
269
+ */
270
+ declare class DryStroke {
271
+ readonly brush: PaintBrush;
272
+ private readonly options;
273
+ readonly coverage: CoverageMap;
274
+ dabCount: number;
275
+ private readonly smoother;
276
+ private readonly walkers;
277
+ private readonly copies;
278
+ private readonly depth;
279
+ private buf;
280
+ private lastRaw;
281
+ private lastFed;
282
+ constructor(brush: PaintBrush, options: DryStrokeOptions);
283
+ /** Feeds raw pen samples; returns the document rectangles the new dabs touched. */
284
+ push(points: readonly PenPoint[]): RasterRect[];
285
+ /** Ends the stroke: the smoothed path catches up with the last raw sample; a single tap paints one dab. */
286
+ finish(): RasterRect[];
287
+ private paint;
288
+ }
289
+ /** The shared engine's tip for the Mixer Brush engine (it smooths and walks by itself, so smoothing is applied before). */
290
+ declare function mixerTip(brush: PaintBrush): BrushSettings;
291
+ /** Pen samples → the shared engine's stroke points (pressure only). */
292
+ declare function toStrokePoint(p: PenPoint): StrokePoint;
293
+
294
+ /**
295
+ * Stickers: textured stamps sprayed along a stroke onto a sticker layer, where each stays an object
296
+ * that can be moved, turned, resized, recoloured or deleted afterwards. A sticker layer is a pixel
297
+ * layer whose pixels are drawn from its stickers (`renderStickers`), so the compositor, exports and
298
+ * every layer operation see ordinary pixels.
299
+ *
300
+ * The stamps are our own procedural shapes (dot, star, heart, leaf, petal flower, confetti, splat,
301
+ * sparkle, ring), each with a little shading and a seeded paper-like texture, so no artwork is
302
+ * bundled. The spray (`StickerSpray`) is deterministic: the same points, seed and brush give the
303
+ * same stickers.
304
+ */
305
+
306
+ type StampKind = 'dot' | 'star' | 'heart' | 'leaf' | 'flower' | 'confetti' | 'splat' | 'sparkle' | 'ring';
307
+ declare const STAMP_KINDS: readonly StampKind[];
308
+ /** One sticker (document pixels). */
309
+ interface Sticker {
310
+ id: string;
311
+ stamp: StampKind;
312
+ x: number;
313
+ y: number;
314
+ /** Diameter in pixels. */
315
+ size: number;
316
+ /** Degrees clockwise. */
317
+ rotation: number;
318
+ /** #rrggbb */
319
+ color: string;
320
+ /** 0..1 */
321
+ opacity: number;
322
+ /** Texture seed. */
323
+ seed: number;
324
+ }
325
+ /** Sticker spray settings (each 0..1 unless noted). */
326
+ interface StickerSettings {
327
+ /** The stamps to spray (picked at random per sticker). */
328
+ stamps: StampKind[];
329
+ /** Stickers per tip diameter of travel. */
330
+ density: number;
331
+ /** Random size change. */
332
+ sizeJitter: number;
333
+ /** Random turn, as a fraction of ±180°. */
334
+ rotationJitter: number;
335
+ /** How far from the path stickers land, in tip diameters (0..2). */
336
+ scatter: number;
337
+ /** Random colour change (hue and lightness). */
338
+ colorJitter: number;
339
+ }
340
+ declare const DEFAULT_STICKER: Readonly<StickerSettings>;
341
+ declare function normalizeStickerSettings(raw?: Partial<StickerSettings>, base?: StickerSettings): StickerSettings;
342
+ /**
343
+ * Coverage of a stamp at a point (u, v) of its unit square (−1..1, y down), 0..1, before shading.
344
+ * `t` is the sticker's seed for shapes with random parts (splat, confetti).
345
+ */
346
+ declare function stampCoverage(kind: StampKind, u: number, v: number, t: number): number;
347
+ /** The pixel rectangle a sticker covers. */
348
+ declare function stickerRect(s: Sticker): RasterRect;
349
+ /**
350
+ * Draws one sticker through a `TileMedium` (straight source-over): the stamp's coverage, lit a
351
+ * little from the top left and given a fine seeded texture, in its colour and opacity.
352
+ */
353
+ declare function drawSticker(medium: TileMedium, s: Sticker): RasterRect;
354
+ interface StickerSprayInit {
355
+ brush: PaintBrush;
356
+ target: PaintTileTarget;
357
+ /** The paint colour #rrggbb. */
358
+ color: string;
359
+ seed: number;
360
+ copies: readonly StrokeCopy[];
361
+ /** The next sticker number on the layer (ids `st<n>`). */
362
+ nextId: number;
363
+ }
364
+ /** A sticker-spray stroke: drops stickers along the path and draws them as it goes. */
365
+ declare class StickerSpray {
366
+ readonly init: StickerSprayInit;
367
+ dabCount: number;
368
+ readonly stickers: Sticker[];
369
+ private readonly smoother;
370
+ private readonly walkers;
371
+ private readonly copies;
372
+ private readonly medium;
373
+ private lastRaw;
374
+ private lastFed;
375
+ private next;
376
+ constructor(init: StickerSprayInit);
377
+ push(points: readonly PenPoint[]): RasterRect[];
378
+ finish(): RasterRect[];
379
+ private drop;
380
+ }
381
+ /** A sticker list rendered over a transparent layer, through a tile target. */
382
+ declare function renderStickers(stickers: readonly Sticker[], target: PaintTileTarget): void;
383
+ /** Checks and rounds a sticker change; throws a sentence naming the bad field. */
384
+ declare function normalizeStickerUpdate(u: Partial<Omit<Sticker, 'id'>>): Partial<Omit<Sticker, 'id'>>;
385
+ /** A sticker layer's pixels: its stickers drawn bottom first over a transparent layer. */
386
+ declare function stickerSurface(stickers: readonly Sticker[]): TiledSurface;
387
+
388
+ /**
389
+ * Procedural brush engines: marks made by small simulations driven by the pen, rather than by
390
+ * stamping the tip.
391
+ *
392
+ * - **Particle brushes** (`engine: 'particle'`): a handful of particles follow the pen by their own
393
+ * rules. `string`: strands of masses on springs, dragged behind the pen like hair or wet string;
394
+ * `spring`: each particle tied to the pen by a spring, overshooting and wobbling; `orbit`:
395
+ * particles circling the pen (spirograph curls); `flow`: particles thrown off the pen and carried
396
+ * by a swirling flow field until they fade. Each is drawn as `dot`s, a continuous `line`, or a
397
+ * `sequence` of beads spaced along its path.
398
+ * - **Bristle brushes** (`engine: 'bristle'`): a tuft of bristles, each its own thin line. Pressure
399
+ * splays the tuft, tilt flattens it, and bristles lag behind the tip by their stiffness, so they
400
+ * bend into curves at turns; each bristle carries its own paint and runs dry at its own pace, so
401
+ * long strokes break into dry-brush streaks.
402
+ *
403
+ * Both are deterministic (seeded) and step the simulation by distance travelled, not by how the
404
+ * input was sampled, so a replay paints the same marks. They build up a `CoverageMap` like the dab
405
+ * engine, so opacity, blend modes, locks, stencils and symmetry work as for any dry brush.
406
+ */
407
+
408
+ interface ProceduralStrokeOptions {
409
+ grain: PaperGrain | null;
410
+ paperDepth: number;
411
+ seed?: number;
412
+ copies?: readonly StrokeCopy[];
413
+ }
414
+ /** What a coverage-building stroke engine provides to the stroke session. */
415
+ interface CoverageStroke {
416
+ readonly coverage: CoverageMap;
417
+ dabCount: number;
418
+ push(points: readonly PenPoint[]): RasterRect[];
419
+ finish(): RasterRect[];
420
+ }
421
+ /** Shared plumbing: smoothing, the dab walk per copy (for size, pressure, direction), grain, and stamping marks. */
422
+ declare abstract class MarkStroke implements CoverageStroke {
423
+ readonly brush: PaintBrush;
424
+ protected readonly options: ProceduralStrokeOptions;
425
+ readonly coverage: CoverageMap;
426
+ dabCount: number;
427
+ protected readonly smoother: PenSmoother;
428
+ protected readonly copies: readonly StrokeCopy[];
429
+ protected readonly walkers: PenDabWalker[];
430
+ protected readonly depth: number;
431
+ private buf;
432
+ private lastRaw;
433
+ private lastFed;
434
+ protected readonly seed: number;
435
+ constructor(brush: PaintBrush, options: ProceduralStrokeOptions);
436
+ push(points: readonly PenPoint[]): RasterRect[];
437
+ finish(): RasterRect[];
438
+ /** One simulation step of copy `copy` with the pen at dab `d`. */
439
+ protected abstract advance(copy: number, d: PenDab, rects: RasterRect[]): void;
440
+ /** Stamps one round mark of radius `r` at (x, y) with flow `alpha` towards `opacity`. */
441
+ protected mark(x: number, y: number, r: number, alpha: number, opacity: number, pressure: number, rects: RasterRect[]): void;
442
+ /** Marks along a segment, spaced by a fraction of the radius. */
443
+ protected segment(x0: number, y0: number, x1: number, y1: number, r: number, alpha: number, opacity: number, pressure: number, rects: RasterRect[]): void;
444
+ }
445
+ type ParticleKind = 'string' | 'spring' | 'orbit' | 'flow';
446
+ declare const PARTICLE_KINDS: readonly ParticleKind[];
447
+ type ParticleRender = 'dot' | 'line' | 'sequence';
448
+ declare const PARTICLE_RENDERS: readonly ParticleRender[];
449
+ /** Particle engine settings (each 0..1 unless noted). */
450
+ interface ParticleSettings {
451
+ kind: ParticleKind;
452
+ render: ParticleRender;
453
+ /** Particles, 1..64. */
454
+ count: number;
455
+ /** Mark size as a fraction of the tip size. */
456
+ size: number;
457
+ /** How far apart the particles start, as a fraction of the tip size. */
458
+ spread: number;
459
+ /** Spring and string stiffness (stiff follows closely; soft swings wide). */
460
+ stiffness: number;
461
+ /** How quickly motion dies down. */
462
+ damping: number;
463
+ /** Orbit and flow speed. */
464
+ speed: number;
465
+ /** Pull downwards (−1 up .. 1 down). */
466
+ gravity: number;
467
+ /** Flow particles' lifetime. */
468
+ life: number;
469
+ /** Sequence: spacing of the beads along each path. */
470
+ interval: number;
471
+ }
472
+ declare const DEFAULT_PARTICLE: Readonly<ParticleSettings>;
473
+ declare function normalizeParticle(raw?: Partial<ParticleSettings>, base?: ParticleSettings): ParticleSettings;
474
+ /** A particle-brush stroke. */
475
+ declare class ParticleStroke extends MarkStroke {
476
+ private readonly sets;
477
+ private readonly s;
478
+ private spawn;
479
+ constructor(brush: PaintBrush, options: ProceduralStrokeOptions);
480
+ private init;
481
+ protected advance(copy: number, d: PenDab, rects: RasterRect[]): void;
482
+ }
483
+ /** Bristle engine settings (each 0..1 unless noted). */
484
+ interface BristleSettings {
485
+ /** Bristles in the tuft, 3..128. */
486
+ count: number;
487
+ /** Stiff bristles follow the tip; soft ones lag and bend into curves at turns. */
488
+ stiffness: number;
489
+ /** How far pressure spreads the tuft. */
490
+ splay: number;
491
+ /** Width of each bristle's line as a fraction of the tip size. */
492
+ thickness: number;
493
+ /** Paint per bristle: 1 never runs dry; lower, bristles run out along the stroke (dry brush). */
494
+ load: number;
495
+ /** Random variation between bristles (length, paint, position). */
496
+ jitter: number;
497
+ }
498
+ declare const DEFAULT_BRISTLE: Readonly<BristleSettings>;
499
+ declare function normalizeBristle(raw?: Partial<BristleSettings>, base?: BristleSettings): BristleSettings;
500
+ /** A bristle-brush stroke: every bristle draws its own line. */
501
+ declare class BristleStroke extends MarkStroke {
502
+ private readonly tufts;
503
+ private readonly s;
504
+ constructor(brush: PaintBrush, options: ProceduralStrokeOptions);
505
+ protected advance(copy: number, d: PenDab, rects: RasterRect[]): void;
506
+ }
146
507
 
147
508
  /**
148
509
  * Paint editor brushes: the shared engine's tip settings (`@pixerate/editor/paint`) plus what a
@@ -156,10 +517,74 @@ declare function renderPaper(paper: PaperSettings, rect: {
156
517
  * mix exchange when its behaviour asks for it), `eraser` (removes alpha), `mixer` (the Mixer
157
518
  * Brush), `blender` (mixes the colours under and near the tip) or `smudge` (drags paint along).
158
519
  */
159
- type PaintBrushEngine = 'dab' | 'eraser' | 'mixer' | 'blender' | 'smudge';
520
+ type PaintBrushEngine = 'dab' | 'eraser' | 'mixer' | 'blender' | 'smudge' | 'wet' | 'impasto' | 'particle' | 'bristle' | 'sticker';
160
521
  declare const PAINT_BRUSH_ENGINES: readonly PaintBrushEngine[];
161
- type PaintBrushCategory = 'pencil' | 'pastel' | 'charcoal' | 'airbrush' | 'marker' | 'ink' | 'paint' | 'eraser' | 'blender' | 'smudge' | 'mixer' | 'custom';
522
+ type PaintBrushCategory = 'pencil' | 'pastel' | 'charcoal' | 'airbrush' | 'marker' | 'ink' | 'paint' | 'eraser' | 'blender' | 'smudge' | 'mixer' | 'watercolor' | 'wet-tool' | 'oil' | 'impasto' | 'procedural' | 'bristle' | 'sticker' | 'custom';
162
523
  declare const PAINT_BRUSH_CATEGORIES: readonly PaintBrushCategory[];
524
+ /**
525
+ * What a wet brush does to the wet layer: `paint` (water and pigment), `water` (clear water;
526
+ * with `rewet` it lifts dried paint), `dry` (a hair dryer: takes water, the pigment settles),
527
+ * `blow` (pushes the water along the stroke), `blend` (evens out the wet pigment under the tip),
528
+ * `smudge` (drags wet paint along) or `blot` (a tissue: takes up wet paint).
529
+ */
530
+ type WetTool = 'paint' | 'water' | 'dry' | 'blow' | 'blend' | 'smudge' | 'blot';
531
+ declare const WET_TOOLS: readonly WetTool[];
532
+ /**
533
+ * The wet medium a wet brush paints: its layer takes the medium's wet settings the first time it
534
+ * holds wet paint (`watercolor`: runny, dries in seconds, granulates and darkens at edges;
535
+ * `gouache`: opaque, flatter; `oil`: thick, barely flows, stays wet for a long time and blends into
536
+ * the wet paint it meets).
537
+ */
538
+ type WetMediumKind = 'watercolor' | 'gouache' | 'oil';
539
+ declare const WET_MEDIA: readonly WetMediumKind[];
540
+ /** Wet engine settings, each 0..1. */
541
+ interface WetBrushSettings {
542
+ tool: WetTool;
543
+ /** Water the brush lays down (wetness): 0.2 a damp brush, 1 a dripping one. */
544
+ water: number;
545
+ /** Pigment concentration: 0 clear water .. 1 paint straight from the tube. */
546
+ pigment: number;
547
+ /** Body: 0 a transparent watercolour .. 1 opaque gouache that hides what is under it. */
548
+ body: number;
549
+ /** Strength of the dry, blow, blend, smudge and blot tools. */
550
+ strength: number;
551
+ /** How much dried paint the water lifts again (re-wetting): 0 none (staining) .. 1. */
552
+ rewet: number;
553
+ /** The medium (its layer's wet settings the first time it holds wet paint). */
554
+ medium: WetMediumKind;
555
+ /** Paint loading: 1 never runs out; lower, the brush runs dry along a long stroke (reload each stroke). */
556
+ load: number;
557
+ /** Thinner (water for watercolour, solvent for oil): thins the paint, so it is more transparent and runnier. */
558
+ thinner: number;
559
+ }
560
+ declare const DEFAULT_WET_BRUSH: Readonly<WetBrushSettings>;
561
+ declare function normalizeWetBrush(raw?: Partial<WetBrushSettings>, base?: WetBrushSettings): WetBrushSettings;
562
+ /**
563
+ * What an impasto brush does on a thick-paint layer: `brush` (paint with body: colour and relief),
564
+ * `tube` (squeezes blobs of paint straight from the tube), `knife` (a palette knife: scrapes the
565
+ * relief flat and smears the paint along), `roller` (a flat, even band with a stipple), `gloop`
566
+ * (a thick, glossy, rounded line).
567
+ */
568
+ type ImpastoTool = 'brush' | 'tube' | 'knife' | 'roller' | 'gloop';
569
+ declare const IMPASTO_TOOLS: readonly ImpastoTool[];
570
+ /** Impasto engine settings, each 0..1. */
571
+ interface ImpastoSettings {
572
+ tool: ImpastoTool;
573
+ /** How thick the paint stands up (relief per stroke). */
574
+ thickness: number;
575
+ /** Shine of the paint when lit (0 matte .. 1 wet-looking gloss). */
576
+ gloss: number;
577
+ /** Metallic paint: highlights take the paint's colour (gold, copper, silver). */
578
+ metallic: number;
579
+ /** Paint loading: 1 never runs out; lower, the stroke thins and fades as the brush empties. */
580
+ load: number;
581
+ /** Thinner: flatter, more transparent paint that levels out. */
582
+ thinner: number;
583
+ /** Knife and brush: how much of the paint under the tool is dragged along (0..1). */
584
+ drag: number;
585
+ }
586
+ declare const DEFAULT_IMPASTO: Readonly<ImpastoSettings>;
587
+ declare function normalizeImpasto(raw?: Partial<ImpastoSettings>, base?: ImpastoSettings): ImpastoSettings;
163
588
  /** Multi-coloured brush: `tip` lays the colours across the tip, `dab` changes colour from dab to dab. */
164
589
  type MultiColorMode = 'off' | 'tip' | 'dab';
165
590
  declare const MULTI_COLOR_MODES: readonly MultiColorMode[];
@@ -247,6 +672,16 @@ interface PaintBrush {
247
672
  blender: BlenderSettings;
248
673
  /** Smudge engine only: strength, length and mixing. */
249
674
  smudge: SmudgeSettings;
675
+ /** Wet engine only: the wet tool, water, pigment, body and strength. */
676
+ wet: WetBrushSettings;
677
+ /** Impasto engine only: the tool, thickness, gloss, metallic, loading, thinner and drag. */
678
+ impasto: ImpastoSettings;
679
+ /** Particle engine only: the particles' kind, how they are drawn, and their physics. */
680
+ particle: ParticleSettings;
681
+ /** Bristle engine only: the tuft. */
682
+ bristle: BristleSettings;
683
+ /** Sticker engine only: the stamps and how they are sprayed. */
684
+ sticker: StickerSettings;
250
685
  /** True for the built-in presets (they cannot be removed or overwritten). */
251
686
  builtin?: boolean;
252
687
  }
@@ -259,16 +694,23 @@ type BrushBehaviorInput = Partial<Omit<BrushBehavior, 'multicolor'>> & {
259
694
  };
260
695
  declare function normalizeBehavior(raw?: BrushBehaviorInput, base?: BrushBehavior): BrushBehavior;
261
696
  /** Brush settings to change or read: any subset of a brush and of its blocks. */
262
- type PaintBrushInput = Partial<Omit<PaintBrush, 'tip' | 'dynamics' | 'mixer' | 'behavior' | 'blender' | 'smudge'>> & {
697
+ type PaintBrushInput = Partial<Omit<PaintBrush, 'tip' | 'dynamics' | 'mixer' | 'behavior' | 'blender' | 'smudge' | 'wet' | 'impasto' | 'particle' | 'bristle' | 'sticker'>> & {
263
698
  tip?: Partial<BrushSettings>;
264
699
  dynamics?: Partial<BrushDynamics>;
265
700
  mixer?: Partial<MixerSettings>;
266
701
  behavior?: BrushBehaviorInput;
267
702
  blender?: Partial<BlenderSettings>;
268
703
  smudge?: Partial<SmudgeSettings>;
704
+ wet?: Partial<WetBrushSettings>;
705
+ impasto?: Partial<ImpastoSettings>;
706
+ particle?: Partial<ParticleSettings>;
707
+ bristle?: Partial<BristleSettings>;
708
+ sticker?: Partial<StickerSettings>;
269
709
  };
270
- /** Whether a brush can pick paint up from the canvas (the pick-up / mix ratios apply): dab and mixer brushes. */
710
+ /** Whether a brush can pick paint up from the canvas (the pick-up / mix ratios apply): dab, mixer and wet brushes. */
271
711
  declare function isWetCapable(brush: Pick<PaintBrush, 'engine'>): boolean;
712
+ /** Whether a brush paints wet media (on the layer's wet layer). */
713
+ declare function isWetBrush(brush: Pick<PaintBrush, 'engine'>): boolean;
272
714
  /**
273
715
  * Whether a dab brush paints dab by dab through the exchange (it picks up paint or is
274
716
  * multi-coloured) instead of building one coverage buffer of one colour.
@@ -295,145 +737,207 @@ interface BrushLibraryFile {
295
737
  declare function parseBrushLibrary(raw: unknown, problems?: string[]): PaintBrush[];
296
738
 
297
739
  /**
298
- * The paint editor's dry-media stroke engine, on the shared brush engine (`@pixerate/editor/paint`):
299
- *
300
- * - Pen input: pressure, tilt (`tiltX` / `tiltY` or `altitude` / `azimuth`), barrel rotation
301
- * (`twist`) and time (for velocity). Position and pressure are smoothed separately.
302
- * - `PenDabWalker` walks the points into the shared `Dab`s at `spacing × diameter`, with every
303
- * pen channel interpolated, and applies the brush's dynamics, rotation mode and jitter.
304
- * Jitter is a pure hash of the stroke seed and the dab index, so a stroke replays exactly.
305
- * - `DryStroke` rasterizes each dab with the shared round tip, multiplies it by how much of it the
306
- * paper grain catches (`grainCatch`), and builds it up in the shared `CoverageMap`; the session
307
- * then composites the coverage with the shared `applyStrokeToTile`.
308
- * - Symmetry: one stroke can paint several copies of its points (mirror and kaleidoscope), each
309
- * with its own walker, into the same coverage, so the copies are one stroke and one undo step.
310
- *
311
- * Pure data in, pixels out: no DOM, no framework.
740
+ * Painting guides: mirror and kaleidoscope symmetry (every stroke paints its reflected or rotated
741
+ * copies as one stroke), perspective guides with one to three vanishing points that strokes snap
742
+ * to, a tracing image under the paint (optionally the source of the stroke colour), and floating
743
+ * reference images to pick colours from. Geometry only: no pixels, no DOM.
312
744
  */
313
745
 
314
- /** One pen sample in document pixels. Every channel but x and y is optional. */
315
- interface PenPoint {
746
+ type SymmetryMode = 'off' | 'vertical' | 'horizontal' | 'dual' | 'diagonal' | 'kaleidoscope';
747
+ declare const SYMMETRY_MODES: readonly SymmetryMode[];
748
+ interface SymmetryGuide {
749
+ mode: SymmetryMode;
750
+ /** Centre of the axes in document pixels (default the canvas centre). */
751
+ center: {
752
+ x: number;
753
+ y: number;
754
+ };
755
+ /** Turns the axes, degrees clockwise (default 0). */
756
+ rotation: number;
757
+ /** Kaleidoscope: number of segments, 2..32 (default 6). */
758
+ segments: number;
759
+ /** Kaleidoscope: reflect every other segment as well as rotating (default true), like a real kaleidoscope. */
760
+ mirror: boolean;
761
+ }
762
+ interface Point2 {
316
763
  x: number;
317
764
  y: number;
318
- /** 0..1 (missing: 1, a mouse). */
319
- pressure?: number;
320
- /** Milliseconds since any fixed origin (for velocity; missing: no velocity dynamics). */
321
- t?: number;
322
- /** Pointer Events tilt in degrees, -90..90 (used when `altitude` / `azimuth` are missing). */
323
- tiltX?: number;
324
- tiltY?: number;
325
- /** Radians from the surface: π/2 is upright, 0 lies flat. */
326
- altitude?: number;
327
- /** Radians clockwise from the +x axis (screen y points down): where the pen leans. */
328
- azimuth?: number;
329
- /** Barrel rotation in degrees clockwise, 0..359. */
330
- twist?: number;
331
765
  }
332
- /** A pen's lean as altitude and azimuth, or `null` when the sample has no tilt. */
333
- declare function penOrientation(p: PenPoint): {
334
- altitude: number;
335
- azimuth: number;
336
- } | null;
337
- /** Pointer Events `tiltX` / `tiltY` (degrees) → altitude and azimuth (radians). */
338
- declare function tiltToOrientation(tiltX: number, tiltY: number): {
339
- altitude: number;
340
- azimuth: number;
341
- };
342
- /**
343
- * Exponential smoothing of position and of pressure, each by its own amount (0..0.95). Feed raw
344
- * samples; the first sample passes through.
345
- */
346
- declare class PenSmoother {
347
- private pos;
348
- private pressure;
349
- private readonly a;
350
- private readonly ap;
351
- constructor(position: number, pressure: number);
352
- push(p: PenPoint): PenPoint;
353
- /** Where the smoothed position is now (or `null` before the first sample). */
354
- get position(): {
766
+ interface PerspectiveGuide {
767
+ enabled: boolean;
768
+ /** One to three vanishing points in document pixels (they may lie outside the canvas). */
769
+ points: Point2[];
770
+ /** Strokes snap to the guide lines (default true when enabled). */
771
+ snap: boolean;
772
+ }
773
+ /** A view-only image under the paint, placed over the canvas. */
774
+ interface TracingImage {
775
+ /** PNG or JPEG data URL. */
776
+ src: string;
777
+ /** Natural size of the image in pixels. */
778
+ width: number;
779
+ height: number;
780
+ /** Where it is drawn, in document pixels (default: fitted to the canvas). */
781
+ rect: {
355
782
  x: number;
356
783
  y: number;
357
- } | null;
784
+ width: number;
785
+ height: number;
786
+ };
787
+ /** 0..1 (default 0.35). */
788
+ opacity: number;
789
+ visible: boolean;
790
+ /** Strokes without an explicit colour take the tracing image's colour where they start. */
791
+ sampleColors: boolean;
358
792
  }
359
- /** A linear map of document vectors `[a, b, c, d]`: (x, y) → (a·x + b·y, c·x + d·y). */
360
- type Linear2 = readonly [number, number, number, number];
361
- /** How one copy of a stroke maps the input: a point map and its linear part (for dab angles). */
362
- interface StrokeCopy {
363
- map(p: PenPoint): PenPoint;
364
- linear: Linear2;
793
+ /** A floating image to look at and pick colours from (shown beside the canvas, never painted). */
794
+ interface ReferenceImage {
795
+ id: string;
796
+ name: string;
797
+ src: string;
798
+ width: number;
799
+ height: number;
800
+ /** Where the panel floats over the viewport, in screen pixels, and its displayed width. */
801
+ x: number;
802
+ y: number;
803
+ displayWidth: number;
804
+ visible: boolean;
365
805
  }
366
- declare const IDENTITY_COPY: StrokeCopy;
367
- /** A dab with the pen state it was made at (the shared `Dab` plus pressure for the grain). */
368
- interface PenDab extends Dab {
369
- pressure: number;
370
- /** Direction of travel at the dab, radians counter-clockwise with y up (as `angle`). */
371
- direction: number;
372
- /** The dab's index within its copy of the stroke (0, 1, 2 …). */
373
- index: number;
806
+ interface PaintGuides {
807
+ symmetry: SymmetryGuide;
808
+ perspective: PerspectiveGuide;
809
+ tracing: TracingImage | null;
810
+ references: ReferenceImage[];
811
+ /** Rulers, arcs, straight edges and shape templates that guide or mask strokes. */
812
+ stencils: Stencil[];
374
813
  }
814
+ declare function defaultGuides(canvas: {
815
+ width: number;
816
+ height: number;
817
+ }): PaintGuides;
818
+ declare const MAX_COORDINATE = 1000000;
819
+ declare function normalizeSymmetry(raw: Partial<SymmetryGuide> | Record<string, unknown>, base: SymmetryGuide): SymmetryGuide;
820
+ /** The axis directions of a mirror mode (radians), as photocraft's `directions`. */
821
+ declare function symmetryAxisAngles(g: SymmetryGuide): number[];
375
822
  /**
376
- * Walks pen samples into dabs, incrementally (any chunking gives the same dabs). Dab angles are in
377
- * the shared rasterizer's convention: radians, counter-clockwise with y up.
823
+ * The extra copies a stroke paints under the symmetry guide, as affine maps (the stroke itself is
824
+ * not included). Vertical, horizontal and diagonal: one reflection. Dual: two reflections and the
825
+ * point reflection. Kaleidoscope: rotations by 360°/n, and with `mirror` the reflections between them.
378
826
  */
379
- declare class PenDabWalker {
380
- readonly brush: PaintBrush;
381
- private readonly seed;
382
- private readonly linear;
383
- private last;
384
- private nextAt;
385
- private pendingFirst;
386
- private index;
827
+ declare function symmetryCopies(g: SymmetryGuide): Affine[];
828
+ /** The axes clipped to the canvas, for drawing (photocraft's `segments`). */
829
+ declare function symmetryAxisSegments(g: SymmetryGuide, size: {
830
+ width: number;
831
+ height: number;
832
+ }): Array<[Point2, Point2]>;
833
+ declare function normalizePerspective(raw: Partial<PerspectiveGuide> | Record<string, unknown>, base: PerspectiveGuide): PerspectiveGuide;
834
+ /** The line families a perspective guide snaps to at `p`: unit directions. */
835
+ declare function perspectiveDirections(g: PerspectiveGuide, p: Point2): Point2[];
836
+ /** How far the pen must move before a stroke chooses its perspective line (document pixels). */
837
+ declare const SNAP_DECIDE_DISTANCE = 6;
838
+ /**
839
+ * Snaps one stroke to a perspective guide: once the pen has moved `SNAP_DECIDE_DISTANCE` from
840
+ * where it started, the stroke picks the guide line through its start that best matches its
841
+ * direction, and every sample after that is projected onto the line.
842
+ */
843
+ declare class PerspectiveSnapper {
844
+ private readonly guide;
845
+ private start;
387
846
  private dir;
388
- private lastT;
389
- private speed;
390
- constructor(brush: PaintBrush, seed?: number, linear?: Linear2);
391
- private sample;
392
- private diameter;
393
- private stepLen;
394
- /** A rasterizer angle (CCW, y up) mapped through this copy's linear part. */
395
- private mapAngle;
396
- private dab;
397
- push(p: PenPoint, out: PenDab[]): void;
398
- finish(out: PenDab[]): void;
847
+ constructor(guide: PerspectiveGuide);
848
+ /** The line the stroke follows, once chosen. */
849
+ get line(): {
850
+ from: Point2;
851
+ direction: Point2;
852
+ } | null;
853
+ snap<P extends Point2>(p: P): P;
399
854
  }
400
- interface DryStrokeOptions {
401
- /** The paper's dry height field, or `null` for no grain. */
402
- grain: PaperGrain | null;
403
- /** The paper's grain depth, 0..1 (multiplied by the brush's grain response). */
404
- paperDepth: number;
405
- /** Jitter seed (record it to replay the stroke exactly). */
406
- seed?: number;
407
- /** Copies of the stroke (symmetry); default one identity copy. */
408
- copies?: readonly StrokeCopy[];
855
+ /** Guide lines to draw for a perspective guide: rays from each vanishing point through the canvas. */
856
+ declare function perspectiveGuideLines(g: PerspectiveGuide, size: {
857
+ width: number;
858
+ height: number;
859
+ }, count?: number): Array<[Point2, Point2]>;
860
+
861
+ /**
862
+ * Stencils: shapes laid on the canvas that guide or mask strokes, like a ruler, a French curve or a
863
+ * masking template held over the paper.
864
+ *
865
+ * - `ruler` (a straight edge) and `arc` (a circle or part of one) **guide**: a stroke that starts
866
+ * within `snap` pixels of the edge follows it exactly; one that starts further away is free.
867
+ * - `line` (a straight edge across the canvas) and `shape` (rectangle, ellipse or polygon)
868
+ * **mask**: paint lands only on one side of the line, or only inside the shape (outside with
869
+ * `invert`), with an anti-aliased edge. Several masks combine (paint lands where all allow it).
870
+ * - Any stencil can be switched to the other `mode`: a shape can guide (strokes follow its
871
+ * outline) and a ruler can mask (paint on one side of it).
872
+ *
873
+ * Geometry only; `stencilMask` makes the canvas coverage the stroke engines read as a selection.
874
+ */
875
+
876
+ type StencilKind = 'ruler' | 'arc' | 'line' | 'shape';
877
+ declare const STENCIL_KINDS: readonly StencilKind[];
878
+ type StencilShape = 'rect' | 'ellipse' | 'polygon';
879
+ declare const STENCIL_SHAPES: readonly StencilShape[];
880
+ interface Stencil {
881
+ id: string;
882
+ kind: StencilKind;
883
+ /** `guide`: strokes that start near the edge follow it; `mask`: paint lands only inside (or on one side). */
884
+ mode: 'guide' | 'mask';
885
+ /** Shown and in effect (default true). */
886
+ enabled: boolean;
887
+ /** ruler / line: two points on the edge (document pixels). */
888
+ from?: Point2;
889
+ to?: Point2;
890
+ /** arc: centre and radius, and the part of the circle in degrees clockwise from +x (default the whole circle). */
891
+ center?: Point2;
892
+ radius?: number;
893
+ startAngle?: number;
894
+ endAngle?: number;
895
+ /** shape: its kind and geometry (`rect` for rect and ellipse, `points` for a polygon). */
896
+ shape?: StencilShape;
897
+ rect?: {
898
+ x: number;
899
+ y: number;
900
+ width: number;
901
+ height: number;
902
+ };
903
+ points?: Point2[];
904
+ /** mask: paint outside the shape (or on the other side of the line) instead. */
905
+ invert: boolean;
906
+ /** guide: how near the edge (px) a stroke must start to follow it (default 40). */
907
+ snap: number;
908
+ /** mask: soft edge width in pixels (default 1, anti-aliased). */
909
+ feather: number;
409
910
  }
911
+ type StencilInput = Partial<Omit<Stencil, 'id'>> & {
912
+ kind: StencilKind;
913
+ };
914
+ /** A stencil with every field in range; throws a sentence naming what is missing. */
915
+ declare function normalizeStencil(raw: StencilInput, id: string, base?: Stencil): Stencil;
916
+ /** The outline of a stencil as a polyline (document pixels), for drawing and for shape guides. */
917
+ declare function stencilOutline(s: Stencil): Point2[];
410
918
  /**
411
- * One dab-engine stroke (dab and eraser brushes): smoothing, the dab walk of every copy, grain and
412
- * build-up in a shared coverage map.
919
+ * Snaps a stroke to the guide stencils: the first point picks the nearest guide within its `snap`
920
+ * distance (or none), and every point after it is moved onto that guide's edge.
413
921
  */
414
- declare class DryStroke {
415
- readonly brush: PaintBrush;
416
- private readonly options;
417
- readonly coverage: CoverageMap;
418
- dabCount: number;
419
- private readonly smoother;
420
- private readonly walkers;
421
- private readonly copies;
422
- private readonly depth;
423
- private buf;
424
- private lastRaw;
425
- private lastFed;
426
- constructor(brush: PaintBrush, options: DryStrokeOptions);
427
- /** Feeds raw pen samples; returns the document rectangles the new dabs touched. */
428
- push(points: readonly PenPoint[]): RasterRect[];
429
- /** Ends the stroke: the smoothed path catches up with the last raw sample; a single tap paints one dab. */
430
- finish(): RasterRect[];
431
- private paint;
922
+ declare class StencilSnapper {
923
+ private readonly stencils;
924
+ private guide;
925
+ constructor(stencils: readonly Stencil[]);
926
+ /** The stencil the stroke follows, once chosen (`null`: none). */
927
+ get following(): Stencil | null;
928
+ snap<P extends Point2>(p: P): P;
432
929
  }
433
- /** The shared engine's tip for the Mixer Brush engine (it smooths and walks by itself, so smoothing is applied before). */
434
- declare function mixerTip(brush: PaintBrush): BrushSettings;
435
- /** Pen samples → the shared engine's stroke points (pressure only). */
436
- declare function toStrokePoint(p: PenPoint): StrokePoint;
930
+ /**
931
+ * The combined coverage of the enabled mask stencils over a `width × height` canvas (0..255 per
932
+ * pixel), or `null` when no stencil masks.
933
+ */
934
+ declare function stencilMask(stencils: readonly Stencil[], width: number, height: number): {
935
+ width: number;
936
+ height: number;
937
+ data: Uint8Array;
938
+ } | null;
939
+ /** A stencil scaled about the origin by `k` (for repainting at a larger size). */
940
+ declare function scaleStencil(s: Stencil, k: number): Stencil;
437
941
 
438
942
  /**
439
943
  * Strokes as data. Every committed stroke is recorded with the brush, colour, raw pen samples,
@@ -457,6 +961,10 @@ interface RecordedStroke {
457
961
  copies?: Affine[];
458
962
  /** What the brush carried at the start (Mixer Brush and wet-capable brushes), so a replay mixes the same. */
459
963
  load?: MixerLoad;
964
+ /** Wet brushes: the wet simulation's clock (steps) at which each point was applied, so a replay lets the paint flow the same. */
965
+ steps?: number[];
966
+ /** The mask stencils it was painted through (so a replay keeps to them). */
967
+ stencils?: Stencil[];
460
968
  }
461
969
  declare function applyAffine(m: Affine, x: number, y: number): {
462
970
  x: number;
@@ -496,13 +1004,339 @@ interface StrokeVariation {
496
1004
  /** Multiply every pressure by this (default 1). */
497
1005
  pressureScale?: number;
498
1006
  }
499
- /** The affine map of a variation about `origin`. */
500
- declare function variationAffine(v: StrokeVariation, origin: {
501
- x: number;
502
- y: number;
503
- }): Affine;
504
- /** A stroke varied by `m` (points mapped; symmetry copies conjugated so they stay symmetric). */
505
- declare function varyStroke(stroke: RecordedStroke, m: Affine, v: StrokeVariation): RecordedStroke;
1007
+ /** The affine map of a variation about `origin`. */
1008
+ declare function variationAffine(v: StrokeVariation, origin: {
1009
+ x: number;
1010
+ y: number;
1011
+ }): Affine;
1012
+ /** A stroke varied by `m` (points mapped; symmetry copies conjugated so they stay symmetric). */
1013
+ declare function varyStroke(stroke: RecordedStroke, m: Affine, v: StrokeVariation): RecordedStroke;
1014
+
1015
+ /**
1016
+ * Wet media in the paint editor (watercolour first): a layer painted with a wet brush gets a wet
1017
+ * layer in the document's `WetWorld` (`@pixerate/editor/paint`), and its pixels are the world's
1018
+ * display of it until the paint dries.
1019
+ *
1020
+ * - **Wet brushes** (`engine: 'wet'`) run through `WetStrokeDriver`: the same pen input as dry
1021
+ * media (`PenDabWalker`: pressure, tilt, rotation, jitter, symmetry copies), each dab's tip
1022
+ * coverage (quantised to 1/4096, so tiny floating-point differences between engines never reach
1023
+ * the simulation) applied by the brush's wet tool. `paint` goes through the shared pick-up / mix
1024
+ * exchange over a `WetMedium`, so a wet brush lifts and mixes wet paint like the phase 2 brushes.
1025
+ * - **Time** is the world's step clock. The host calls `tick` every frame (the bindings do); it
1026
+ * steps at `speed` steps per second unless paused. Agents call `advance` or `dry`.
1027
+ * - **Dabs between steps.** Edits only happen between batches: points pushed while a batch runs
1028
+ * (in Web Workers) wait for it, and every raw point records the clock it was applied at
1029
+ * (`RecordedStroke.steps`), so a stroke replays exactly.
1030
+ * - **Drying.** Tiles that have dried are baked into the layer's pixels by the world; when a whole
1031
+ * layer is dry its wet layer is dropped and it is an ordinary pixel layer again. `dry` dries at
1032
+ * once. Saving and exporting use the deterministic bake of everything still wet ("wet paint is
1033
+ * dried on save": the document holds dried pixels, never wet state).
1034
+ * - **Undo.** Each wet step of history keeps a snapshot of the world before and after it, within
1035
+ * `undoMemory` bytes; older snapshots are dropped, and undoing past them leaves that paint dry
1036
+ * as it looked.
1037
+ */
1038
+
1039
+ /** The paper as the wet simulation reads it. */
1040
+ declare function wetPaperInput(paper: PaperSettings): WetPaperInput;
1041
+ interface WetStrokeInit {
1042
+ world: WetWorld;
1043
+ laneId: string;
1044
+ brush: PaintBrush;
1045
+ /** Paint colour, sRGB 0..1. */
1046
+ color: RgbTriple;
1047
+ load: MixerLoad;
1048
+ grain: PaperGrain | null;
1049
+ paperDepth: number;
1050
+ paperColor: readonly [number, number, number];
1051
+ seed: number;
1052
+ copies: readonly StrokeCopy[];
1053
+ /** Where paint may land (canvas-sized coverage 0..255: the stencils' mask), or none. */
1054
+ mask?: {
1055
+ width: number;
1056
+ height: number;
1057
+ data: ArrayLike<number>;
1058
+ } | null;
1059
+ }
1060
+ /**
1061
+ * One wet-brush stroke: pen samples → dabs → wet tool edits. Points are applied only while the
1062
+ * world is between batches (`flush` applies the ones that waited).
1063
+ */
1064
+ declare class WetStrokeDriver {
1065
+ readonly init: WetStrokeInit;
1066
+ dabCount: number;
1067
+ bounds: RasterRect | null;
1068
+ /** The world's clock at which each raw point was applied (recorded with the stroke). */
1069
+ readonly steps: number[];
1070
+ private readonly smoother;
1071
+ private readonly states;
1072
+ private readonly medium;
1073
+ private readonly queue;
1074
+ private finishing;
1075
+ private finished;
1076
+ private lastRaw;
1077
+ private lastFed;
1078
+ private tip;
1079
+ private cov;
1080
+ private readonly paint;
1081
+ private readonly depth;
1082
+ constructor(init: WetStrokeInit);
1083
+ private readonly copies;
1084
+ /** What copy 0's brush carries after the dabs so far. */
1085
+ get load(): MixerLoad | null;
1086
+ /** Whether every point (and the end) has been applied. */
1087
+ get done(): boolean;
1088
+ /** Adds raw points; applies them now unless a batch is running. Returns where pixels changed. */
1089
+ push(points: readonly PenPoint[]): RasterRect[];
1090
+ /** Asks for the end; it happens now unless a batch is running (then at the next `flush`). */
1091
+ finish(): RasterRect[];
1092
+ /** Applies waiting points (and the end, when asked) if the world is between batches. */
1093
+ flush(): RasterRect[];
1094
+ private applyDabs;
1095
+ /** The tip's coverage over `rect`, with the grain, quantised. */
1096
+ private coverage;
1097
+ private dab;
1098
+ }
1099
+ /** The wet settings each medium gives a layer the first time it holds wet paint. */
1100
+ declare const WET_MEDIUM_PARAMS: Readonly<Record<WetMediumKind, Partial<WetParams>>>;
1101
+ /** Settings of one wet layer (saved with the document). */
1102
+ interface WetLayerSettings {
1103
+ params: WetParams;
1104
+ flow: WetFlowSpec;
1105
+ }
1106
+ /** One wet layer as tools describe it. */
1107
+ interface WetLayerInfo {
1108
+ layerId: string;
1109
+ /** Surface water anywhere on the layer. */
1110
+ wet: boolean;
1111
+ /** Cells with surface water. */
1112
+ wetCells: number;
1113
+ /** Where the surface water is, or `null`. */
1114
+ wetBounds: RasterRect | null;
1115
+ /** 256 px tiles being simulated. */
1116
+ activeTiles: number;
1117
+ params: WetParams;
1118
+ flow: WetFlowSpec;
1119
+ }
1120
+ /** An event recorded with the clock it happened at (strokes are recorded separately), for replays. */
1121
+ interface WetEvent {
1122
+ clock: number;
1123
+ /** How many strokes were recorded before it (where it falls between strokes in a replay). */
1124
+ at?: number;
1125
+ kind: 'dry' | 'tilt' | 'params' | 'flow' | 'wet-paper';
1126
+ layerId?: string;
1127
+ data?: unknown;
1128
+ }
1129
+ interface PaintWetHost {
1130
+ canvas(): {
1131
+ width: number;
1132
+ height: number;
1133
+ };
1134
+ paper(): PaperSettings;
1135
+ /** The layer's current pixels (`null` when it is not a pixel layer). */
1136
+ layerSurface(id: string): TiledSurface | null;
1137
+ /** Shows new pixels for wet layers (not an undo step); `rects` are where they changed. */
1138
+ updateSurfaces(surfaces: ReadonlyMap<string, TiledSurface>, rects: RasterRect[]): void;
1139
+ /** The paint dried while time passed (the editor records nothing, it just redraws). */
1140
+ changed(): void;
1141
+ }
1142
+ interface PaintWetOptions extends WetWorldOptions {
1143
+ /** Steps per second while time runs (default 120). */
1144
+ speed?: number;
1145
+ /** Bytes of wet snapshots kept for undo (default 512 MB). */
1146
+ undoMemory?: number;
1147
+ }
1148
+ /** The paint editor's wet layers: one `WetWorld` per canvas, its settings and time. */
1149
+ declare class PaintWetLayers {
1150
+ private readonly host;
1151
+ private readonly options;
1152
+ world: WetWorld | null;
1153
+ tilt: WetTilt;
1154
+ readonly settings: Map<string, WetLayerSettings>;
1155
+ paused: boolean;
1156
+ /** Wet strokes dry as soon as they end (one undo step with the stroke). */
1157
+ instantDry: boolean;
1158
+ speed: number;
1159
+ overlay: boolean;
1160
+ readonly undoMemory: number;
1161
+ /** The live wet stroke, if any. */
1162
+ driver: WetStrokeDriver | null;
1163
+ private lastTick;
1164
+ private ticking;
1165
+ private stepsOwed;
1166
+ constructor(host: PaintWetHost, options?: PaintWetOptions);
1167
+ /** The world (made on first use, at the canvas size). */
1168
+ ensureWorld(): WetWorld;
1169
+ /** Whether any layer (or `layerId`) has wet paint. */
1170
+ isWet(layerId?: string): boolean;
1171
+ /** Layers holding a wet layer (wet or drying). */
1172
+ wetLayerIds(): string[];
1173
+ /** Whether a batch is running (edits wait). */
1174
+ get busy(): boolean;
1175
+ /** Resolves when no batch is running. */
1176
+ idle(): Promise<void>;
1177
+ settingsOf(layerId: string, medium?: WetMediumKind): WetLayerSettings;
1178
+ /** Puts a layer in the world (its current pixels become the base under the wet paint). */
1179
+ ensureLane(layerId: string, medium?: WetMediumKind): WetWorld;
1180
+ setParams(layerId: string, update: Partial<WetParams>): WetParams;
1181
+ setFlow(layerId: string, update: Partial<WetFlowSpec>): WetFlowSpec;
1182
+ setTilt(update: Partial<WetTilt>): WetTilt;
1183
+ /** The paper changed. */
1184
+ paperChanged(): void;
1185
+ /** A layer as tools describe it. */
1186
+ info(layerId: string): WetLayerInfo;
1187
+ backend(): WetBackendInfo | null;
1188
+ /** Shows the current wet pixels (at most `maxTiles` reshaded per layer). */
1189
+ syncDisplays(maxTiles?: number): RasterRect[];
1190
+ /** Drops the wet layers whose paint has all dried (they become ordinary pixel layers). */
1191
+ private retireDry;
1192
+ /**
1193
+ * Advances time by the wall clock (`now` in ms, e.g. `performance.now()`): `speed` steps per
1194
+ * second, at most `maxSteps` per call. Call it every frame; it does nothing while paused or dry.
1195
+ * Resolves when the steps are done and the display is up to date.
1196
+ */
1197
+ tick(now: number, maxSteps?: number): Promise<void>;
1198
+ private flushDriver;
1199
+ /** Called when a live stroke that ended mid-batch has finished applying. */
1200
+ onDriverDone: ((driver: WetStrokeDriver) => void) | null;
1201
+ /** Steps `steps` times now (agents, tests). Returns the steps run. */
1202
+ advance(steps: number): Promise<number>;
1203
+ /**
1204
+ * Dries at once (everything settles where it is), on one layer or all, optionally only in a
1205
+ * rectangle. Returns the layers and rectangles that dried.
1206
+ */
1207
+ dry(layerId?: string, rect?: RasterRect): Array<{
1208
+ layerId: string;
1209
+ bounds: RasterRect | null;
1210
+ }>;
1211
+ /** The layer's pixels with all its wet paint dried (deterministic), without changing anything. */
1212
+ bakedSurface(layerId: string): TiledSurface | null;
1213
+ /** A snapshot of the world for undo (`null` when there is none). */
1214
+ snapshot(): WetSnapshot | null;
1215
+ /** Puts a snapshot back (`null`: no wet layers). Returns the layer surfaces it implies. */
1216
+ restore(snap: WetSnapshot | null): void;
1217
+ /** Forgets everything wet (a new canvas, a resize, opening a document). */
1218
+ reset(): void;
1219
+ /** Water overlay tiles (RGBA) over a document rectangle, for the view. */
1220
+ waterOverlay(rect: RasterRect): Array<{
1221
+ tx: number;
1222
+ ty: number;
1223
+ data: Uint8ClampedArray;
1224
+ }>;
1225
+ destroy(): void;
1226
+ }
1227
+ /** The pigment (K r, g, b, S) of a colour at a body, per unit mass (for tools that report it). */
1228
+ declare function wetPigmentOf(hex: string, body: number): number[];
1229
+
1230
+ /**
1231
+ * Thick paint (impasto): a pixel layer can carry two extra channels, a **height** map (how far the
1232
+ * paint stands up, 0..255) and a **material** map (R gloss, G metallic, 0..255), and it is lit at
1233
+ * render time: a normal from the height's slope, diffuse light from `PaintLighting`, and a
1234
+ * Blinn-Phong highlight whose sharpness follows the gloss, tinted by the paint where it is
1235
+ * metallic. The layer's own pixels stay the paint's colour (albedo), so relighting never repaints
1236
+ * anything, saving keeps colour, height and material apart, and only tiles whose inputs changed are
1237
+ * relit (the lit tiles are cached by the identity of their input tiles).
1238
+ *
1239
+ * Impasto strokes (`ThickStrokeDriver`) lay colour through the shared pick-up / mix exchange (paint
1240
+ * mixes into the wet paint it meets, like oil) and raise the height: `brush` with bristle ridges
1241
+ * along the stroke, `tube` in domed blobs, `knife` scraping the relief flat while it smears the
1242
+ * paint, `roller` as a flat stippled band, `gloop` as a thick glossy round bead. The paint runs out
1243
+ * along the stroke with `load` below 1, and `thinner` flattens and thins it.
1244
+ */
1245
+
1246
+ /** Where the light comes from. */
1247
+ interface PaintLighting {
1248
+ /** Degrees clockwise from the top of the canvas (315: top left, the default). */
1249
+ azimuth: number;
1250
+ /** Degrees above the canvas, 5 (grazing, deep shadows) .. 90 (straight on, no relief). Default 40. */
1251
+ elevation: number;
1252
+ /** How strongly the relief shows, 0 (flat) .. 1 (default 0.7). */
1253
+ relief: number;
1254
+ }
1255
+ declare const DEFAULT_LIGHTING: Readonly<PaintLighting>;
1256
+ declare function normalizeLighting(raw?: Partial<PaintLighting>, base?: PaintLighting): PaintLighting;
1257
+ /** A layer's thick-paint channels (layer space, like its pixels). */
1258
+ interface ThickChannels {
1259
+ /** Height 0..255 per pixel (1 channel). */
1260
+ height: TiledSurface;
1261
+ /** Material per pixel (4 channels): R gloss, G metallic, B and A unused. */
1262
+ material: TiledSurface;
1263
+ }
1264
+ declare function emptyThickChannels(): ThickChannels;
1265
+ declare function lightingKey(l: PaintLighting): string;
1266
+ /**
1267
+ * One tile of a thick layer lit: `color` (RGBA, straight), the height and material of the tile, and
1268
+ * the height of its 8 neighbours for the slopes at the border (`heightAt(dx, dy)` returns the
1269
+ * neighbouring tile's data or null). Returns a new RGBA tile.
1270
+ */
1271
+ declare function lightTile(color: Uint8ClampedArray, height: Uint8ClampedArray | null, heightAround: (dx: number, dy: number) => Uint8ClampedArray | null, material: Uint8ClampedArray | null, light: PaintLighting): Uint8ClampedArray;
1272
+ /** Cache of lit tiles for one layer, by the identity of their inputs. */
1273
+ declare class LitCache {
1274
+ private readonly tiles;
1275
+ /** The layer's pixels lit (tiles without relief nearby are the colour tiles themselves). */
1276
+ lit(color: TiledSurface, ch: ThickChannels, light: PaintLighting): TiledSurface;
1277
+ }
1278
+ interface ThickStrokeInit {
1279
+ brush: PaintBrush;
1280
+ /** Where the colour goes (the session's live tiles). */
1281
+ target: PaintTileTarget;
1282
+ /** The layer's channels before the stroke. */
1283
+ channels: ThickChannels;
1284
+ /** Paint colour, sRGB 0..1. */
1285
+ color: RgbTriple;
1286
+ load: MixerLoad;
1287
+ seed: number;
1288
+ copies: readonly StrokeCopy[];
1289
+ }
1290
+ /** One impasto stroke: colour through the exchange, height and material into live channel tiles. */
1291
+ declare class ThickStrokeDriver {
1292
+ readonly init: ThickStrokeInit;
1293
+ dabCount: number;
1294
+ private readonly smoother;
1295
+ private readonly states;
1296
+ private readonly medium;
1297
+ private readonly height;
1298
+ private readonly material;
1299
+ private readonly paint;
1300
+ private tip;
1301
+ private lastRaw;
1302
+ private lastFed;
1303
+ private version;
1304
+ constructor(init: ThickStrokeInit);
1305
+ private readonly copies;
1306
+ /** What copy 0 carries after the dabs so far. */
1307
+ get load(): MixerLoad;
1308
+ /** The channels with the stroke so far (new objects whenever they changed). */
1309
+ channels(): ThickChannels;
1310
+ private heightTile;
1311
+ private materialTile;
1312
+ private getH;
1313
+ private setH;
1314
+ push(points: readonly PenPoint[]): RasterRect[];
1315
+ finish(): RasterRect[];
1316
+ private applyDabs;
1317
+ private dab;
1318
+ /** The knife: flattens the relief under it and carries some of it along (a patch, like a smudge). */
1319
+ private knifeHeight;
1320
+ private paintMaterial;
1321
+ }
1322
+ /** Height statistics of a layer's relief (for tools). */
1323
+ declare function reliefInfo(ch: ThickChannels): {
1324
+ bounds: RasterRect | null;
1325
+ maxHeight: number;
1326
+ tiles: number;
1327
+ };
1328
+ /** A layer's thick-paint channels as saved: PNG data URLs over a rectangle (layer space). */
1329
+ interface SavedThickChannels {
1330
+ rect: RasterRect;
1331
+ /** Greyscale PNG of the height. */
1332
+ height: string;
1333
+ /** RGBA PNG of the material (R gloss, G metallic). */
1334
+ material: string;
1335
+ }
1336
+ /** Channels → saved form (`null` when there is no relief). */
1337
+ declare function thickToJSON(ch: ThickChannels): SavedThickChannels | null;
1338
+ /** Saved form → channels; throws a sentence when it cannot be read. */
1339
+ declare function thickFromJSON(raw: unknown): ThickChannels;
506
1340
 
507
1341
  /**
508
1342
  * The paint editor's document: a canvas (size and resolution), a paper, and the shared raster
@@ -537,6 +1371,22 @@ interface PaintDocumentState {
537
1371
  activeLayerId: string | null;
538
1372
  /** Every committed stroke, oldest first (strokes are data: replay, vary, time-lapse later). */
539
1373
  strokes: RecordedStroke[];
1374
+ /** Wet-media events other than strokes (drying, tilt, settings), with the clock they happened at. */
1375
+ wetEvents?: WetEvent[];
1376
+ /** Changes besides strokes that a replay repeats (fills, clears, flattened relief), in order. */
1377
+ paintEvents?: PaintEvent[];
1378
+ /** Sticker layers: each layer's stickers, bottom first (its pixels are drawn from them). */
1379
+ stickers?: Readonly<Record<string, Sticker[]>>;
1380
+ /** Thick paint: each layer's height and material channels (layers without relief are missing). */
1381
+ thick?: Readonly<Record<string, ThickChannels>>;
1382
+ }
1383
+ /** A change a replay repeats besides strokes, recorded with how many strokes came before it. */
1384
+ interface PaintEvent {
1385
+ at: number;
1386
+ kind: 'fill' | 'clear' | 'flatten' | 'stickers';
1387
+ layerId: string;
1388
+ /** The fill's options (colour, opacity, grain, pressure, blend), or the layer's stickers after an edit. */
1389
+ data?: Record<string, unknown>;
540
1390
  }
541
1391
  /** A new layer id, unique in this session. */
542
1392
  declare function newPaintLayerId(): string;
@@ -627,18 +1477,6 @@ declare class PaintDabStroke {
627
1477
  private depositWithCeiling;
628
1478
  }
629
1479
 
630
- /**
631
- * A live stroke on one pixel layer. Points arrive while the pen moves; the layer shows the stroke
632
- * so far through a live surface (no history); `end()` commits it as one undo step.
633
- *
634
- * - Dab and eraser brushes: the coverage is cumulative, so each update recomposites the touched
635
- * region from the pre-stroke tile (`applyStrokeToTile` with `into` and `region`).
636
- * - Dab-by-dab brushes (`PaintDabStroke`: the Mixer Brush, blender, smudge, and dab brushes that
637
- * pick up paint or are multi-coloured): dabs read what earlier dabs laid down, so they paint
638
- * into live tile copies; every tile written since the last update gets a new `Tile` object,
639
- * because the compositor caches by tile identity.
640
- */
641
-
642
1480
  interface StrokeSessionHost {
643
1481
  /** Shows the layer's live surface (no history); `rects` are where it changed. */
644
1482
  update(layerId: string, surface: TiledSurface, rects: RasterRect[]): void;
@@ -672,6 +1510,28 @@ interface StrokeSessionInit {
672
1510
  mixerLoad: MixerLoad;
673
1511
  /** The secondary colour 0..255 RGBA (a multi-colour brush without its own colours uses it). */
674
1512
  secondary?: readonly [number, number, number, number];
1513
+ /**
1514
+ * Wet brushes: the driver that applies the stroke to the wet layer, and the layer's current
1515
+ * pixels (the wet layer's display). The session's own surface is not used.
1516
+ */
1517
+ wet?: {
1518
+ driver: WetStrokeDriver;
1519
+ surface: () => TiledSurface;
1520
+ };
1521
+ /** Impasto brushes: the layer's thick-paint channels before the stroke. */
1522
+ thick?: {
1523
+ channels: ThickChannels;
1524
+ };
1525
+ /** Where paint may land (canvas-sized coverage 0..255: the stencils' mask), or none. */
1526
+ selection?: {
1527
+ width: number;
1528
+ height: number;
1529
+ data: ArrayLike<number>;
1530
+ } | null;
1531
+ /** Sticker brushes: the next sticker number on the layer. */
1532
+ sticker?: {
1533
+ nextId: number;
1534
+ };
675
1535
  }
676
1536
  interface PaintStrokeResult {
677
1537
  layerId: string;
@@ -682,6 +1542,8 @@ interface PaintStrokeResult {
682
1542
  strokeId: string | null;
683
1543
  /** Mixer and wet-capable dab brushes: what the brush carries after the stroke (cleaned unless it is dirty). */
684
1544
  mixerLoad?: MixerLoad;
1545
+ /** Wet brushes: the paint is wet (it keeps moving until it dries; see dry_paint / advance_wet_paint). */
1546
+ wet?: boolean;
685
1547
  }
686
1548
  declare class PaintStrokeSession {
687
1549
  private readonly host;
@@ -695,17 +1557,35 @@ declare class PaintStrokeSession {
695
1557
  private readonly fresh;
696
1558
  private readonly dry;
697
1559
  private readonly wet;
1560
+ private readonly wetDriver;
1561
+ private readonly thickDriver;
1562
+ private readonly stickerSpray;
1563
+ /** A wet stroke that ended while the simulation was mid-batch: it commits when the driver catches up. */
1564
+ private wetEndPending;
698
1565
  private readonly apply;
699
1566
  private readonly copies;
700
1567
  private bounds;
701
1568
  private lastRaw;
702
1569
  private closed;
703
1570
  result: PaintStrokeResult | null;
1571
+ /** The mask stencils this stroke was painted with when they are not the editor's (replays). */
1572
+ maskStencils: readonly Stencil[] | null;
704
1573
  constructor(host: StrokeSessionHost, init: StrokeSessionInit);
705
1574
  private tileTarget;
706
1575
  get open(): boolean;
707
1576
  /** Adds raw pen samples (document pixels). */
708
1577
  push(points: readonly PenPoint[]): void;
1578
+ /** Sticker strokes: the stickers sprayed so far. */
1579
+ stickers(): Sticker[] | null;
1580
+ /** Impasto strokes: the layer's thick-paint channels with the stroke so far. */
1581
+ thickChannels(): ThickChannels | null;
1582
+ /** Shows a wet stroke's progress (the wet layer's display). */
1583
+ wetUpdate(rects: RasterRect[]): void;
1584
+ /** Whether a wet stroke has ended but waits for the simulation to finish a batch. */
1585
+ get pendingEnd(): boolean;
1586
+ /** Completes a wet stroke whose end waited (the controller calls it when the driver is done). */
1587
+ completeWetEnd(): void;
1588
+ private finishWet;
709
1589
  private update;
710
1590
  /** Ends the stroke (one undo step) and returns what changed. */
711
1591
  end(): PaintStrokeResult;
@@ -736,6 +1616,11 @@ interface PaperNode extends CompositorLayer {
736
1616
  opacity: number;
737
1617
  blend: 'normal';
738
1618
  paper: PaperSettings;
1619
+ /** The light, for metallic paper. */
1620
+ light?: {
1621
+ azimuth: number;
1622
+ elevation: number;
1623
+ };
739
1624
  }
740
1625
  /** A view-only RGBA image under the paint (the tracing image), drawn at its opacity. */
741
1626
  interface ImageNode extends CompositorLayer {
@@ -757,9 +1642,15 @@ interface ImageNode extends CompositorLayer {
757
1642
  type ViewLayer = PaintLayer | PaperNode | ImageNode;
758
1643
  declare const PAPER_NODE_ID = "__paper";
759
1644
  declare const TRACING_NODE_ID = "__tracing";
760
- declare function paperKey(p: PaperSettings): string;
761
- /** The paper as a compositor layer (one object per paper settings object, so caches hold). */
762
- declare function paperNode(paper: PaperSettings): PaperNode;
1645
+ declare function paperKey(p: PaperSettings, light?: {
1646
+ azimuth: number;
1647
+ elevation: number;
1648
+ }): string;
1649
+ /** The paper as a compositor layer (one object per paper settings object and light, so caches hold). */
1650
+ declare function paperNode(paper: PaperSettings, light?: {
1651
+ azimuth: number;
1652
+ elevation: number;
1653
+ }): PaperNode;
763
1654
  /** The paper's dry height field, built once per settings. */
764
1655
  declare function paperGrain(paper: PaperSettings): PaperGrain;
765
1656
  /** A compositor for paint editor stacks (paper and view-only images included). */
@@ -788,7 +1679,7 @@ type PaintAssetStore = ImageEditorAssetStore;
788
1679
  /**
789
1680
  * Fields a later version adds. Phase 1 writes none of them and keeps any it reads. Planned:
790
1681
  * `layerChannels: { [layerId]: { height?: asset, material?: asset } }` for impasto and
791
- * metallic paint. Wet paint is never saved (saving dries it), so there is no wet field.
1682
+ * metallic paint. Wet paint itself is never saved (saving dries it); its settings are (`wet`).
792
1683
  */
793
1684
  type PaintDocumentExtensions = Record<string, unknown>;
794
1685
  interface SerializedPaintDocument {
@@ -820,6 +1711,8 @@ interface SerializeOptions {
820
1711
  swatches?: unknown;
821
1712
  /** The mixing pad: `{ current?: png data URL, saved: [{ id, name, png }] }`. */
822
1713
  mixingPad?: unknown;
1714
+ /** Wet media settings: `{ tilt, layers: { [layerId]: { params, flow } } }`. */
1715
+ wet?: unknown;
823
1716
  }
824
1717
  /** The document as JSON with inline PNG data URLs. */
825
1718
  declare function serializePaintDocument(state: PaintDocumentState, options?: SerializeOptions): SerializedPaintDocument;
@@ -833,6 +1726,8 @@ interface LoadedPaintDocument {
833
1726
  swatches?: unknown;
834
1727
  /** The saved mixing pad, as stored (the controller validates it). */
835
1728
  mixingPad?: unknown;
1729
+ /** The saved wet settings, as stored (the controller validates them). */
1730
+ wet?: unknown;
836
1731
  /** What could not be read (the rest still loads). */
837
1732
  problems: string[];
838
1733
  }
@@ -1016,126 +1911,6 @@ declare function renderBrushThumbnail(brush: PaintBrush, options: {
1016
1911
  color: string;
1017
1912
  }): BrushThumbnail;
1018
1913
 
1019
- /**
1020
- * Painting guides: mirror and kaleidoscope symmetry (every stroke paints its reflected or rotated
1021
- * copies as one stroke), perspective guides with one to three vanishing points that strokes snap
1022
- * to, a tracing image under the paint (optionally the source of the stroke colour), and floating
1023
- * reference images to pick colours from. Geometry only: no pixels, no DOM.
1024
- */
1025
-
1026
- type SymmetryMode = 'off' | 'vertical' | 'horizontal' | 'dual' | 'diagonal' | 'kaleidoscope';
1027
- declare const SYMMETRY_MODES: readonly SymmetryMode[];
1028
- interface SymmetryGuide {
1029
- mode: SymmetryMode;
1030
- /** Centre of the axes in document pixels (default the canvas centre). */
1031
- center: {
1032
- x: number;
1033
- y: number;
1034
- };
1035
- /** Turns the axes, degrees clockwise (default 0). */
1036
- rotation: number;
1037
- /** Kaleidoscope: number of segments, 2..32 (default 6). */
1038
- segments: number;
1039
- /** Kaleidoscope: reflect every other segment as well as rotating (default true), like a real kaleidoscope. */
1040
- mirror: boolean;
1041
- }
1042
- interface Point2 {
1043
- x: number;
1044
- y: number;
1045
- }
1046
- interface PerspectiveGuide {
1047
- enabled: boolean;
1048
- /** One to three vanishing points in document pixels (they may lie outside the canvas). */
1049
- points: Point2[];
1050
- /** Strokes snap to the guide lines (default true when enabled). */
1051
- snap: boolean;
1052
- }
1053
- /** A view-only image under the paint, placed over the canvas. */
1054
- interface TracingImage {
1055
- /** PNG or JPEG data URL. */
1056
- src: string;
1057
- /** Natural size of the image in pixels. */
1058
- width: number;
1059
- height: number;
1060
- /** Where it is drawn, in document pixels (default: fitted to the canvas). */
1061
- rect: {
1062
- x: number;
1063
- y: number;
1064
- width: number;
1065
- height: number;
1066
- };
1067
- /** 0..1 (default 0.35). */
1068
- opacity: number;
1069
- visible: boolean;
1070
- /** Strokes without an explicit colour take the tracing image's colour where they start. */
1071
- sampleColors: boolean;
1072
- }
1073
- /** A floating image to look at and pick colours from (shown beside the canvas, never painted). */
1074
- interface ReferenceImage {
1075
- id: string;
1076
- name: string;
1077
- src: string;
1078
- width: number;
1079
- height: number;
1080
- /** Where the panel floats over the viewport, in screen pixels, and its displayed width. */
1081
- x: number;
1082
- y: number;
1083
- displayWidth: number;
1084
- visible: boolean;
1085
- }
1086
- interface PaintGuides {
1087
- symmetry: SymmetryGuide;
1088
- perspective: PerspectiveGuide;
1089
- tracing: TracingImage | null;
1090
- references: ReferenceImage[];
1091
- }
1092
- declare function defaultGuides(canvas: {
1093
- width: number;
1094
- height: number;
1095
- }): PaintGuides;
1096
- declare const MAX_COORDINATE = 1000000;
1097
- declare function normalizeSymmetry(raw: Partial<SymmetryGuide> | Record<string, unknown>, base: SymmetryGuide): SymmetryGuide;
1098
- /** The axis directions of a mirror mode (radians), as photocraft's `directions`. */
1099
- declare function symmetryAxisAngles(g: SymmetryGuide): number[];
1100
- /**
1101
- * The extra copies a stroke paints under the symmetry guide, as affine maps (the stroke itself is
1102
- * not included). Vertical, horizontal and diagonal: one reflection. Dual: two reflections and the
1103
- * point reflection. Kaleidoscope: rotations by 360°/n, and with `mirror` the reflections between them.
1104
- */
1105
- declare function symmetryCopies(g: SymmetryGuide): Affine[];
1106
- /** The axes clipped to the canvas, for drawing (photocraft's `segments`). */
1107
- declare function symmetryAxisSegments(g: SymmetryGuide, size: {
1108
- width: number;
1109
- height: number;
1110
- }): Array<[Point2, Point2]>;
1111
- declare function normalizePerspective(raw: Partial<PerspectiveGuide> | Record<string, unknown>, base: PerspectiveGuide): PerspectiveGuide;
1112
- /** The line families a perspective guide snaps to at `p`: unit directions. */
1113
- declare function perspectiveDirections(g: PerspectiveGuide, p: Point2): Point2[];
1114
- /** How far the pen must move before a stroke chooses its perspective line (document pixels). */
1115
- declare const SNAP_DECIDE_DISTANCE = 6;
1116
- /**
1117
- * Snaps one stroke to a perspective guide: once the pen has moved `SNAP_DECIDE_DISTANCE` from
1118
- * where it started, the stroke picks the guide line through its start that best matches its
1119
- * direction, and every sample after that is projected onto the line.
1120
- */
1121
- declare class PerspectiveSnapper {
1122
- private readonly guide;
1123
- private start;
1124
- private dir;
1125
- constructor(guide: PerspectiveGuide);
1126
- /** The line the stroke follows, once chosen. */
1127
- get line(): {
1128
- from: Point2;
1129
- direction: Point2;
1130
- } | null;
1131
- snap<P extends Point2>(p: P): P;
1132
- }
1133
- /** Guide lines to draw for a perspective guide: rays from each vanishing point through the canvas. */
1134
- declare function perspectiveGuideLines(g: PerspectiveGuide, size: {
1135
- width: number;
1136
- height: number;
1137
- }, count?: number): Array<[Point2, Point2]>;
1138
-
1139
1914
  /**
1140
1915
  * Colour for painters: hex / RGB / HSV / HSL conversions, a colour wheel that is either the
1141
1916
  * painter's red-yellow-blue wheel (where red's complement is green) or the RGB wheel (where it is
@@ -1270,6 +2045,107 @@ declare class MixingPad {
1270
2045
  pixels(): Uint8ClampedArray;
1271
2046
  }
1272
2047
 
2048
+ /**
2049
+ * Replaying a painting from its data: every recorded stroke, and the changes recorded between them
2050
+ * (fills, clears, flattened relief, and wet-media events at the simulation clock they happened at),
2051
+ * repainted into a fresh, headless editor.
2052
+ *
2053
+ * - **Time-lapse**: frames of the painting as it is repainted (after strokes, and while wet paint
2054
+ * flows between them), as PNGs and as a `VideoProject` of stills that the video editor's export
2055
+ * path (`toVideoFfmpeg`) turns into an MP4.
2056
+ * - **High-resolution export**: the same replay at a larger scale (points, brush sizes, paper grain
2057
+ * and rectangles scaled), so the picture is painted again with more pixels instead of upscaled.
2058
+ *
2059
+ * Strokes paint exactly as recorded: same seed, load and symmetry copies, and wet strokes apply
2060
+ * each point at the clock it was recorded at, with the simulation stepped in between, so wet paint
2061
+ * flows as it did. What was not recorded as data (imported pixels, merges, transforms of layers,
2062
+ * changes made before strokes were recorded) is not repeated: those layers start empty.
2063
+ */
2064
+
2065
+ interface PaintReplayOptions {
2066
+ /** Paint at this many times the size (1 for a time-lapse; 1..8 for a high-resolution export). */
2067
+ scale?: number;
2068
+ /** Stop after this many strokes (default all). */
2069
+ strokes?: number;
2070
+ }
2071
+ /** One frame of a replay. */
2072
+ interface ReplayFrame extends PixelImage {
2073
+ /** Strokes painted so far. */
2074
+ stroke: number;
2075
+ /** Wet-media clock (steps). */
2076
+ clock: number;
2077
+ }
2078
+ interface TimeLapseOptions {
2079
+ /** Longest side of each frame in pixels (default 720). */
2080
+ maxSide?: number;
2081
+ /** A frame after every this many strokes (default: about `frames` frames over the whole painting). */
2082
+ strokesPerFrame?: number;
2083
+ /** About how many frames in all (default 120). */
2084
+ frames?: number;
2085
+ /** While wet paint flows between strokes, a frame every this many steps (default 60; 0: none). */
2086
+ wetStepsPerFrame?: number;
2087
+ /** Frames per second of the video (default 12). */
2088
+ fps?: number;
2089
+ /** Hold the finished painting this long at the end (ms, default 1500). */
2090
+ holdMs?: number;
2091
+ /** Called after each frame with how far along the replay is (0..1). */
2092
+ onProgress?: (done: number) => void;
2093
+ }
2094
+ interface TimeLapse {
2095
+ fps: number;
2096
+ width: number;
2097
+ height: number;
2098
+ /** Each frame as PNG bytes. */
2099
+ frames: Uint8Array[];
2100
+ /** The frames as stills on a timeline (assets `frame-0001.png`…; `src` is that file name). */
2101
+ video: VideoProject;
2102
+ /** The ffmpeg command that renders `video` to `timelapse.mp4` from the frame files. */
2103
+ ffmpeg: VideoFfmpegExport;
2104
+ durationMs: number;
2105
+ }
2106
+ type Item = {
2107
+ kind: 'stroke';
2108
+ stroke: RecordedStroke;
2109
+ } | {
2110
+ kind: 'wet';
2111
+ event: WetEvent;
2112
+ } | {
2113
+ kind: 'paint';
2114
+ event: NonNullable<PaintDocumentState['paintEvents']>[number];
2115
+ };
2116
+ /** Strokes and events in the order they happened. */
2117
+ declare function replayItems(doc: PaintDocumentState): Item[];
2118
+ /** The layer tree with every pixel layer empty (what a replay paints into). */
2119
+ declare function emptiedLayers(layers: readonly PaintLayer[]): PaintLayer[];
2120
+ /**
2121
+ * Replays a controller's painting into `target` (a fresh controller the caller made at the scaled
2122
+ * size; see `PaintEditorController.createReplayTarget`), calling `onFrame` where frames are due.
2123
+ */
2124
+ declare function replayPainting(source: PaintDocumentState, target: PaintEditorController, options?: PaintReplayOptions & {
2125
+ onStroke?: (index: number) => Promise<void> | void;
2126
+ onWetSteps?: (clock: number) => Promise<void> | void;
2127
+ wetChunk?: number;
2128
+ }): Promise<void>;
2129
+ /** A frame of `target` at most `maxSide` pixels on its longest side. */
2130
+ declare function replayFrame(target: PaintEditorController, maxSide: number, stroke: number): ReplayFrame;
2131
+ /** Frames as stills on a video timeline, `1 / fps` each, with the last held for `holdMs`. */
2132
+ declare function timeLapseVideo(frameCount: number, width: number, height: number, fps: number, holdMs: number, name?: string): {
2133
+ video: VideoProject;
2134
+ ffmpeg: VideoFfmpegExport;
2135
+ durationMs: number;
2136
+ };
2137
+ /** Builds the time-lapse of a painting (see `PaintEditorController.timeLapse`). */
2138
+ declare function buildTimeLapse(source: PaintDocumentState, target: PaintEditorController, options?: TimeLapseOptions): Promise<TimeLapse>;
2139
+ /** A contact sheet: up to `count` frames side by side, for a quick look. */
2140
+ declare function contactSheet(frames: readonly PixelImage[], columns?: number, cell?: number): PixelImage;
2141
+
2142
+ /**
2143
+ * A time-lapse as one ZIP: the frames (`frame-0001.png`…), the video project (`timelapse.json`,
2144
+ * which the video editor opens), and `render.sh` with the ffmpeg command that turns them into
2145
+ * `timelapse.mp4`.
2146
+ */
2147
+ declare function timeLapseZip(tl: TimeLapse): Promise<Uint8Array>;
2148
+
1273
2149
  /**
1274
2150
  * `PaintEditorController`: the natural-media paint editor's state, framework-free.
1275
2151
  *
@@ -1301,6 +2177,26 @@ interface PaintEditorOptions extends Partial<PaintCanvas> {
1301
2177
  encodeImage?: ImageEncoder;
1302
2178
  /** Undo steps kept (default 200). */
1303
2179
  historyLimit?: number;
2180
+ /** Wet media: kernel, threads, speed (steps per second) and the memory kept for undoing wet strokes. */
2181
+ wet?: PaintWetOptions;
2182
+ }
2183
+ /** Wet media as panels show it. */
2184
+ interface PaintWetStateInfo {
2185
+ /** Layers with a wet layer (paint still wet or drying). */
2186
+ layerIds: string[];
2187
+ /** Some paint is wet. */
2188
+ wet: boolean;
2189
+ /** Time is stopped (the paint stays wet). */
2190
+ paused: boolean;
2191
+ /** The water overlay is shown. */
2192
+ overlay: boolean;
2193
+ /** Wet strokes dry as soon as they end. */
2194
+ instantDry: boolean;
2195
+ /** Steps per second while time runs. */
2196
+ speed: number;
2197
+ /** Simulation steps so far. */
2198
+ clock: number;
2199
+ tilt: WetTilt;
1304
2200
  }
1305
2201
  interface PaintEditorState {
1306
2202
  document: PaintDocumentState;
@@ -1330,6 +2226,8 @@ interface PaintEditorState {
1330
2226
  savedMixingPads: readonly SavedMixingPadInfo[];
1331
2227
  /** A stroke is in progress. */
1332
2228
  stroking: boolean;
2229
+ /** Wet media. */
2230
+ wet: PaintWetStateInfo;
1333
2231
  /** Increments on every change. */
1334
2232
  version: number;
1335
2233
  }
@@ -1371,6 +2269,10 @@ interface PaintStrokeInit {
1371
2269
  };
1372
2270
  /** What the brush carries at the start (default: from the editor's brush load); replays pass the recorded one. */
1373
2271
  load?: MixerLoad;
2272
+ /** Follow guide stencils and keep to mask stencils (default true; replays pass false, their points already follow them). */
2273
+ stencils?: boolean;
2274
+ /** Mask with these stencils instead (replays pass the ones recorded with the stroke). */
2275
+ maskStencils?: readonly Stencil[];
1374
2276
  }
1375
2277
  interface LayerUpdate {
1376
2278
  name?: string;
@@ -1409,6 +2311,12 @@ interface RenderOptions {
1409
2311
  paper?: boolean;
1410
2312
  /** Only these layers (a group with its children). */
1411
2313
  layerIds?: readonly string[];
2314
+ /**
2315
+ * Wet paint as it is now (`display`: still wet, wet paper a little darker) or dried where it is
2316
+ * (`dried`, the deterministic bake that saving uses). Default `dried` for exports and `display`
2317
+ * for previews and picks.
2318
+ */
2319
+ wet?: 'display' | 'dried';
1412
2320
  }
1413
2321
  interface PixelImage {
1414
2322
  width: number;
@@ -1456,6 +2364,16 @@ declare class PaintEditorController {
1456
2364
  private nextReferenceId;
1457
2365
  private readonly viewCompositor;
1458
2366
  private readonly exportCompositor;
2367
+ /** Wet media: the simulation, its settings and time. */
2368
+ readonly wet: PaintWetLayers;
2369
+ private readonly wetOptions;
2370
+ /** Where the light comes from for thick paint (saved with the document, not undo steps). */
2371
+ private lighting;
2372
+ private readonly litCaches;
2373
+ /** The live impasto stroke's channels. */
2374
+ private liveThick;
2375
+ /** Set while an operation that changes wet paint runs: the simulation before it (for undo). */
2376
+ private wetOp;
1459
2377
  constructor(options?: PaintEditorOptions);
1460
2378
  subscribe(listener: () => void): () => void;
1461
2379
  private emit;
@@ -1469,6 +2387,24 @@ declare class PaintEditorController {
1469
2387
  takeDirty(): RasterRect[] | 'all';
1470
2388
  private requireIdle;
1471
2389
  private commit;
2390
+ /** Keeps the wet snapshots in history within the wet undo memory, dropping the oldest. */
2391
+ private trimWetHistory;
2392
+ /** Throws when wet paint is mid-step (a batch in worker threads); `wetIdle()` waits for it. */
2393
+ private requireWetIdle;
2394
+ /** Starts an operation that changes wet paint: keeps the simulation as it is now for undo. */
2395
+ private beginWetOp;
2396
+ /**
2397
+ * Before a non-wet change to layers that hold wet paint (a dry brush, a fill, a merge…): dries
2398
+ * them first, in the same undo step. Returns the ids dried.
2399
+ */
2400
+ private dryLayersFirst;
2401
+ /** Records a wet event (for replays) in the document being built. */
2402
+ private pendingWetEvents;
2403
+ private logWet;
2404
+ /** The document with a paint event appended (for replays). */
2405
+ private withPaintEvent;
2406
+ /** The document with the pending wet events appended. */
2407
+ private withWetEvents;
1472
2408
  undo(): string | null;
1473
2409
  redo(): string | null;
1474
2410
  /** Labels of the undo steps, oldest first. */
@@ -1621,6 +2557,134 @@ declare class PaintEditorController {
1621
2557
  bounds: RasterRect | null;
1622
2558
  layerIds: string[];
1623
2559
  };
2560
+ /** The document's `thick` without these layers (as a spread: `{}` when unchanged). */
2561
+ private thickWithout;
2562
+ /** Where the light comes from for thick paint and metallic paper. */
2563
+ getLighting(): PaintLighting;
2564
+ /** Moves the light (thick paint and metallic paper are relit; nothing is repainted). Saved with the document, not an undo step. */
2565
+ setLighting(update: Partial<PaintLighting>): PaintLighting;
2566
+ /** A layer's relief: where it is, its highest point (0..255) and its tiles; `null` when it has none. */
2567
+ reliefOf(layerId?: string): {
2568
+ layerId: string;
2569
+ bounds: RasterRect | null;
2570
+ maxHeight: number;
2571
+ } | null;
2572
+ /** Flattens a layer's thick paint (its colour stays; the relief and material go). One undo step. */
2573
+ flattenRelief(layerId?: string): {
2574
+ layerId: string;
2575
+ flattened: boolean;
2576
+ };
2577
+ /**
2578
+ * A fresh, headless editor for replaying this painting at `scale` (canvas, dpi and paper grain
2579
+ * scaled; every pixel layer empty; the same brushes, lighting and wet options). Dispose of it
2580
+ * when done.
2581
+ */
2582
+ createReplayTarget(scale?: number): PaintEditorController;
2583
+ /**
2584
+ * Repaints the painting from its recorded strokes and events, as a time-lapse: PNG frames (after
2585
+ * strokes, and while wet paint flows), a `VideoProject` of the frames as stills and the ffmpeg
2586
+ * command that renders it (the video editor's export path). Changes nothing here.
2587
+ */
2588
+ timeLapse(options?: TimeLapseOptions): Promise<TimeLapse>;
2589
+ /**
2590
+ * The painting repainted from its recorded strokes at `scale` times the size (brush sizes,
2591
+ * paper grain, fills and wet events scaled), wet paint dried: more detail instead of upscaled
2592
+ * pixels. Layers painted without recorded strokes (imported pixels) come out empty. Changes nothing here.
2593
+ */
2594
+ /** `renderHighRes` as PNG bytes. */
2595
+ exportHighResPng(options: {
2596
+ scale: number;
2597
+ paper?: boolean;
2598
+ }): Promise<Uint8Array>;
2599
+ renderHighRes(options: {
2600
+ scale: number;
2601
+ paper?: boolean;
2602
+ }): Promise<PixelImage>;
2603
+ /**
2604
+ * Lets wet paint flow with the wall clock: call every frame with `performance.now()` (the
2605
+ * bindings do). Steps at the wet speed unless paused; resolves when the frame's steps are done.
2606
+ */
2607
+ tickWet(now: number): Promise<void>;
2608
+ /** Resolves when the wet kernel is as fast as it will get here (`null` when nothing is wet yet). */
2609
+ wetReady(): Promise<WetBackendInfo | null>;
2610
+ /** Stops the wet simulation's worker threads (call when the editor goes away). */
2611
+ dispose(): void;
2612
+ /** Resolves when wet paint is between steps (edits, undo and exports can run). */
2613
+ wetIdle(): Promise<void>;
2614
+ private wetLayer;
2615
+ private checkRect;
2616
+ /** Wet media as panels and tools show it, with each wet layer's state. */
2617
+ wetState(): PaintWetStateInfo & {
2618
+ layers: WetLayerInfo[];
2619
+ backend: WetBackendInfo | null;
2620
+ message: string | null;
2621
+ };
2622
+ /** One layer's wet settings and state. */
2623
+ wetLayerInfo(layerId?: string): WetLayerInfo;
2624
+ /**
2625
+ * Lets the paint flow for `steps` simulation steps now (or `seconds` at the wet speed): what
2626
+ * agents call between strokes. Not an undo step (time passing never is). Returns the steps run
2627
+ * and which layers are still wet.
2628
+ */
2629
+ advanceWet(options: {
2630
+ steps?: number;
2631
+ seconds?: number;
2632
+ }): Promise<{
2633
+ steps: number;
2634
+ clock: number;
2635
+ stillWet: boolean;
2636
+ layers: WetLayerInfo[];
2637
+ }>;
2638
+ /**
2639
+ * Dries wet paint at once: everything settles where it is and the water goes (one undo step).
2640
+ * On one layer or all, optionally only inside `rect`. Returns what dried and whether anything is
2641
+ * still wet.
2642
+ */
2643
+ dryPaint(options?: {
2644
+ layerId?: string;
2645
+ rect?: RasterRect;
2646
+ }): {
2647
+ dried: Array<{
2648
+ layerId: string;
2649
+ bounds: RasterRect | null;
2650
+ }>;
2651
+ stillWet: boolean;
2652
+ };
2653
+ /**
2654
+ * Pre-wets the paper (wet-in-wet): fills the paper's water over `rect` (default the canvas) by
2655
+ * `amount` 0..1 on a layer's wet layer, so paint laid there spreads softly. One undo step.
2656
+ */
2657
+ wetPaper(options?: {
2658
+ layerId?: string;
2659
+ rect?: RasterRect;
2660
+ amount?: number;
2661
+ }): {
2662
+ layerId: string;
2663
+ rect: RasterRect;
2664
+ wet: boolean;
2665
+ };
2666
+ /** Records a settings change as a wet event (not an undo step). */
2667
+ private recordWetSetting;
2668
+ /** Changes a layer's wet settings (viscosity, evaporation, granulation…). Editor settings saved with the document, not an undo step. */
2669
+ setWetParams(layerId: string | undefined, update: Partial<WetParams>): WetParams;
2670
+ /** Sets a layer's flow map (water pushed in a pattern). Saved with the document, not an undo step. */
2671
+ setFlowMap(layerId: string | undefined, update: Partial<WetFlowSpec>): WetFlowSpec;
2672
+ /** Tilts the canvas (water runs downhill; steep tilts make drips). Saved with the document, not an undo step. */
2673
+ setCanvasTilt(update: Partial<WetTilt>): WetTilt;
2674
+ /** Stops (or restarts) time: paused paint stays wet. Editor state. */
2675
+ setWetPaused(paused: boolean): boolean;
2676
+ /** Simulation steps per second while time runs (1..2000; default 120). Editor state. */
2677
+ setWetSpeed(speed: number): number;
2678
+ /** Wet strokes dry as soon as they end (each stroke still one undo step). Editor state. */
2679
+ setInstantDry(on: boolean): boolean;
2680
+ /** Shows or hides the water overlay (where the water is, view only). */
2681
+ setWaterOverlay(visible: boolean): boolean;
2682
+ /** The water overlay's tiles over a document rectangle (RGBA, view only). */
2683
+ waterOverlayTiles(rect: RasterRect): Array<{
2684
+ tx: number;
2685
+ ty: number;
2686
+ data: Uint8ClampedArray;
2687
+ }>;
1624
2688
  private sources;
1625
2689
  /** The layers with the live stroke's surface in place. */
1626
2690
  private currentLayers;
@@ -1628,6 +2692,10 @@ declare class PaintEditorController {
1628
2692
  protected underlayLayers(): ViewLayer[];
1629
2693
  /** The tracing image resampled onto the canvas (cached until it or the canvas changes). */
1630
2694
  private tracingNode;
2695
+ /** The layers with every wet layer dried where it is (what saving and exporting see). */
2696
+ private driedLayers;
2697
+ /** The layers with thick paint lit (each thick layer's pixels replaced by its lit tiles). */
2698
+ private litLayers;
1631
2699
  private renderLayers;
1632
2700
  /**
1633
2701
  * Composites the viewport stack (paper, view-only underlays, layers with the live stroke) tile by
@@ -1644,6 +2712,15 @@ declare class PaintEditorController {
1644
2712
  maxSide?: number;
1645
2713
  }): PixelImage;
1646
2714
  /** The document as JSON with inline PNG assets. */
2715
+ /** The document as saved: wet paint dried where it is (deterministically); wet settings as data. */
2716
+ private savedState;
2717
+ /** Wet settings saved with the document (`null` when they are all defaults). */
2718
+ private wetJSON;
2719
+ private readWet;
2720
+ /** Thick paint and light as saved (`layerChannels`, `lighting`), merged into the extensions. */
2721
+ private thickExtensions;
2722
+ /** Reads `layerChannels` and `lighting` from loaded extensions into the document. */
2723
+ private readThick;
1647
2724
  toJSON(options?: {
1648
2725
  strokes?: boolean;
1649
2726
  }): SerializedPaintDocument;
@@ -1726,6 +2803,36 @@ declare class PaintEditorController {
1726
2803
  /** The colour of a reference image at its pixel (x, y). */
1727
2804
  pickReferenceColor(id: string, x: number, y: number): string;
1728
2805
  private readGuides;
2806
+ /** The document's `stickers` without these layers (as a spread: `{}` when unchanged). */
2807
+ private stickersWithout;
2808
+ /** The next free sticker number (ids `st<n>` are unique across the document). */
2809
+ private nextStickerNumber;
2810
+ private stickerLayer;
2811
+ /** A layer's stickers, bottom first. */
2812
+ listStickers(layerId?: string): Sticker[];
2813
+ /** Replaces a layer's stickers and redraws its pixels from them (one undo step). */
2814
+ private commitStickers;
2815
+ /** Moves, turns, resizes, recolours or restamps one sticker (one undo step). Returns it. */
2816
+ updateSticker(layerId: string | undefined, id: string, update: Partial<Omit<Sticker, 'id'>>): Sticker;
2817
+ /** Brings stickers to the top (`front`) or bottom of their layer (one undo step). */
2818
+ arrangeStickers(layerId: string | undefined, ids: readonly string[], to: 'front' | 'back'): string[];
2819
+ /** Deletes stickers (`ids`, or all of them on the layer) (one undo step). Returns the ids removed. */
2820
+ removeStickers(layerId: string | undefined, ids?: readonly string[]): string[];
2821
+ /** Sets a layer's stickers (replays). */
2822
+ replaceStickers(layerId: string, stickers: readonly Sticker[]): void;
2823
+ private stencilCache;
2824
+ private nextStencilId;
2825
+ /** The mask stencils' coverage over the canvas (cached), or `null` when none masks. */
2826
+ private stencilSelection;
2827
+ /** The stencils. */
2828
+ listStencils(): Stencil[];
2829
+ /** Lays a stencil on the canvas (a ruler or arc to follow, a straight edge or shape to mask). Editor state saved with the document, not an undo step. */
2830
+ addStencil(input: StencilInput): Stencil;
2831
+ private requireStencil;
2832
+ /** Moves, reshapes, switches (guide / mask, invert) or hides a stencil. */
2833
+ updateStencil(id: string, update: Partial<Omit<Stencil, 'id' | 'kind'>>): Stencil;
2834
+ /** Takes stencils away (`ids`, or all of them). Returns the ids removed. */
2835
+ removeStencils(ids?: readonly string[]): string[];
1729
2836
  /** Colour harmonies around a colour (default the paint colour) on the painter's RYB wheel or the RGB wheel. */
1730
2837
  harmony(kind: HarmonyKind, options?: {
1731
2838
  color?: string;
@@ -1990,7 +3097,7 @@ declare class PaintCanvasPresenter {
1990
3097
  declare function guideOverlay(controller: PaintEditorController): Array<{
1991
3098
  from: Point2;
1992
3099
  to: Point2;
1993
- kind: 'symmetry' | 'perspective' | 'snap';
3100
+ kind: 'symmetry' | 'perspective' | 'snap' | 'stencil';
1994
3101
  }>;
1995
3102
  /** One slider of a panel: a path into the brush (or paper) settings. */
1996
3103
  interface SliderControl {
@@ -2045,6 +3152,14 @@ interface BrushEngineControls {
2045
3152
  multicolor: boolean;
2046
3153
  /** Load / Clean buttons and a swatch of what the brush carries. */
2047
3154
  load: boolean;
3155
+ /** The wet tool menu (`wet.tool`, `WET_TOOLS`) and medium menu (`wet.medium`, `WET_MEDIA`). */
3156
+ wetTool?: boolean;
3157
+ /** The impasto tool menu (`impasto.tool`, `IMPASTO_TOOLS`). */
3158
+ impastoTool?: boolean;
3159
+ /** The particle kind and render menus (`particle.kind`, `particle.render`). */
3160
+ particleMenus?: boolean;
3161
+ /** The sticker stamp toggles (`sticker.stamps`, `STAMP_KINDS`). */
3162
+ stickerStamps?: boolean;
2048
3163
  }
2049
3164
  /**
2050
3165
  * The controls the active brush's engine adds to the brush panel (`null` for the eraser):
@@ -2053,7 +3168,9 @@ interface BrushEngineControls {
2053
3168
  * pigment choice; dab and mixer brushes also have the dirty and multi-colour controls. Pick-up
2054
3169
  * and mix always go through `behavior`, which keeps a Mixer Brush's `mixer` block in sync.
2055
3170
  */
2056
- declare function brushEngineControls(brush: Pick<PaintBrush, 'engine'>): BrushEngineControls | null;
3171
+ declare function brushEngineControls(brush: Pick<PaintBrush, 'engine'> & {
3172
+ wet?: PaintBrush['wet'];
3173
+ }): BrushEngineControls | null;
2057
3174
  /** The multi-colour sliders for a mode: softness, and dabs per colour in `dab` mode. */
2058
3175
  declare function multiColorSliders(mode: string): readonly SliderControl[];
2059
3176
  /** A brush change setting `path` (e.g. `smudge.mixing`, `behavior.dirty`) to any value, keeping the rest of its block. */
@@ -2064,5 +3181,136 @@ declare function brushLoadColor(load: MixerLoad): string | null;
2064
3181
  declare const PAD_SAMPLE_SIZES: readonly number[];
2065
3182
  /** The averaging radius `mixingPadPick` takes for a sample size: 1 → 0, 3 → 1, 5 → 2 … */
2066
3183
  declare function padSampleRadius(size: number): number;
3184
+ /** Labels of the wet tools, for the wet brush's tool menu. */
3185
+ declare const WET_TOOL_LABELS: Readonly<Record<WetTool, string>>;
3186
+ /** The wet panel's sliders for a layer's wet settings (paths into `WetParams`). */
3187
+ declare const WET_PARAM_SLIDERS: readonly SliderControl[];
3188
+ /** The canvas tilt sliders. */
3189
+ declare const WET_TILT_SLIDERS: readonly SliderControl[];
3190
+ /** The speed slider (steps per second). */
3191
+ declare const WET_SPEED_SLIDER: SliderControl;
3192
+ /**
3193
+ * Runs the wet paint's clock with the display: `controller.tickWet(now)` every animation frame
3194
+ * (it does nothing while nothing is wet or time is paused). Returns a function that stops it.
3195
+ * Bindings call it once per mounted canvas.
3196
+ */
3197
+ declare function startWetClock(controller: PaintEditorController, frame?: (cb: (t: number) => void) => number, cancel?: (id: number) => void): () => void;
3198
+ /**
3199
+ * Draws the water overlay (blue where the surface is wet, faint where the paper is damp) into a
3200
+ * document-sized 2D context, clearing it first. Draws nothing when the overlay is off.
3201
+ */
3202
+ declare function drawWaterOverlay(controller: PaintEditorController, ctx: Paint2DContext & {
3203
+ clearRect(x: number, y: number, w: number, h: number): void;
3204
+ }): number;
3205
+ /** Labels of the wet media, for the wet brush's medium menu. */
3206
+ declare const WET_MEDIUM_LABELS: Readonly<Record<WetMediumKind, string>>;
3207
+ /** Labels of the impasto tools. */
3208
+ declare const IMPASTO_TOOL_LABELS: Readonly<Record<ImpastoTool, string>>;
3209
+ /** The light sliders (thick paint and metallic paper). */
3210
+ declare const LIGHT_SLIDERS: readonly SliderControl[];
3211
+ /** Labels of the particle kinds and renders. */
3212
+ declare const PARTICLE_KIND_LABELS: Readonly<Record<ParticleKind, string>>;
3213
+ declare const PARTICLE_RENDER_LABELS: Readonly<Record<ParticleRender, string>>;
3214
+ /** Labels of the sticker stamps. */
3215
+ declare const STAMP_LABELS: Readonly<Record<StampKind, string>>;
3216
+ /** Toggles one stamp in a sticker brush's set (never leaving it empty). */
3217
+ declare function toggleStamp(stamps: readonly StampKind[], stamp: StampKind): StampKind[];
3218
+ /** The sticker panel's sliders for the selected sticker. */
3219
+ declare const STICKER_SLIDERS: readonly SliderControl[];
3220
+ /** Stencils a toolbar can lay with one click, centred on the canvas. */
3221
+ declare function quickStencil(kind: 'ruler' | 'circle' | 'ellipse-mask' | 'edge-mask', canvas: {
3222
+ width: number;
3223
+ height: number;
3224
+ }): StencilInput;
3225
+
3226
+ /**
3227
+ * Agent tools for wet media (watercolour and gouache on a simulated wet layer). Wet strokes are
3228
+ * painted with `paint_stroke` and a wet brush (`list_brushes` category `watercolor` or
3229
+ * `wet-tool`); these tools read and steer the wet paint. Every result says whether paint is still
3230
+ * wet. Settings (wet parameters, flow maps, tilt) are saved with the document but are not undo
3231
+ * steps; drying and wetting the paper are.
3232
+ */
3233
+
3234
+ declare const WET_PARAM_SCHEMA: {
3235
+ readonly viscosity: {
3236
+ readonly type: "number";
3237
+ readonly minimum: 0;
3238
+ readonly maximum: 1;
3239
+ readonly description: string;
3240
+ };
3241
+ readonly evaporation: {
3242
+ readonly type: "number";
3243
+ readonly minimum: 0;
3244
+ readonly maximum: 1;
3245
+ readonly description: string;
3246
+ };
3247
+ readonly edgeDarkening: {
3248
+ readonly type: "number";
3249
+ readonly minimum: 0;
3250
+ readonly maximum: 1;
3251
+ readonly description: string;
3252
+ };
3253
+ readonly pigmentWeight: {
3254
+ readonly type: "number";
3255
+ readonly minimum: 0;
3256
+ readonly maximum: 1;
3257
+ readonly description: string;
3258
+ };
3259
+ readonly granulation: {
3260
+ readonly type: "number";
3261
+ readonly minimum: 0;
3262
+ readonly maximum: 1;
3263
+ readonly description: string;
3264
+ };
3265
+ readonly pickup: {
3266
+ readonly type: "number";
3267
+ readonly minimum: 0;
3268
+ readonly maximum: 1;
3269
+ readonly description: string;
3270
+ };
3271
+ readonly fixture: {
3272
+ readonly type: "number";
3273
+ readonly minimum: 0;
3274
+ readonly maximum: 1;
3275
+ readonly description: string;
3276
+ };
3277
+ readonly bloom: {
3278
+ readonly type: "number";
3279
+ readonly minimum: 0;
3280
+ readonly maximum: 1;
3281
+ readonly description: string;
3282
+ };
3283
+ readonly bleed: {
3284
+ readonly type: "number";
3285
+ readonly minimum: 0;
3286
+ readonly maximum: 1;
3287
+ readonly description: string;
3288
+ };
3289
+ };
3290
+ declare function createPaintWetTools(c: PaintEditorController): AgentTool[];
3291
+
3292
+ /**
3293
+ * Agent tools for thick paint and light: impasto strokes are painted with `paint_stroke` and an
3294
+ * impasto brush; these tools move the light, read and flatten a layer's relief, and switch instant
3295
+ * drying of wet strokes.
3296
+ */
3297
+
3298
+ declare function createPaintThickTools(c: PaintEditorController): AgentTool[];
3299
+
3300
+ /**
3301
+ * Agent tools for replaying a painting from its data: a time-lapse (frames, a video project of the
3302
+ * frames and the ffmpeg command that renders it) and a high-resolution export repainted from the
3303
+ * recorded strokes.
3304
+ */
3305
+
3306
+ declare function createPaintReplayTools(c: PaintEditorController): AgentTool[];
3307
+
3308
+ /**
3309
+ * Agent tools for stickers: stamps sprayed with a sticker brush (`paint_stroke`, list_brushes
3310
+ * category sticker) stay objects on their layer, which these tools list, move, turn, resize,
3311
+ * recolour, reorder and delete. Each change is one undo step and redraws the layer.
3312
+ */
3313
+
3314
+ declare function createPaintStickerTools(c: PaintEditorController): AgentTool[];
2067
3315
 
2068
- 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 };
3316
+ export { type Affine, BLEND_MENU_GROUPS, BRUSH_ID, BRUSH_ROTATIONS, BRUSH_SLIDERS, BUILTIN_BRUSHES, type BlendMenuGroup, BlendMode, BlendModeInfo, type BristleSettings, BristleStroke, type BrushBehavior, type BrushBehaviorInput, type BrushDynamics, type BrushEngineControls, type BrushLibraryFile, type BrushRotation, type BrushThumbnail, type ColorWheel, type CoverageStroke, DEFAULT_BEHAVIOR, DEFAULT_BRISTLE, DEFAULT_BRUSH_ID, DEFAULT_DPI, DEFAULT_DYNAMICS, DEFAULT_IMPASTO, DEFAULT_LIGHTING, DEFAULT_MULTI_COLOR, DEFAULT_PAPER, DEFAULT_PARTICLE, DEFAULT_STICKER, DEFAULT_SWATCHES, DEFAULT_VIEW, DEFAULT_WET_BRUSH, 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, IMPASTO_TOOLS, IMPASTO_TOOL_LABELS, type ImageEncoder, type ImageFormat, type ImageNode, type ImpastoSettings, type ImpastoTool, LIGHT_SLIDERS, type LayerUpdate, type Linear2, LitCache, 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, PARTICLE_KINDS, PARTICLE_KIND_LABELS, PARTICLE_RENDERS, PARTICLE_RENDER_LABELS, 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, type PaintEvent, PaintGesture, type PaintGroupLayer, type PaintGuides, type PaintLayer, type PaintLayerInfo, type PaintLighting, type PaintPerformanceResult, type PaintPixelLayer, type PaintPointerEvent, PaintPresence, type PaintPresenceActor, type PaintPresenceState, type PaintReplayOptions, type PaintStrokeInit, type PaintStrokeResult, PaintStrokeSession, type PaintTool, type PaintView, type PaintWetHost, PaintWetLayers, type PaintWetOptions, type PaintWetStateInfo, type PaperFamily, PaperGrain, type PaperNode, type PaperSettings, type PaperTiling, type ParticleKind, type ParticleRender, type ParticleSettings, ParticleStroke, type PenDab, PenDabWalker, type PenPoint, PenSmoother, type PerspectiveGuide, PerspectiveSnapper, type PixelImage, type Point2, type PointerSample, type ProceduralStrokeOptions, type RecordedStroke, type ReferenceImage, type RenderOptions, type ReplayFrame, type ReplayOptions, type Rgb255, SNAP_DECIDE_DISTANCE, STAMP_KINDS, STAMP_LABELS, STENCIL_KINDS, STENCIL_SHAPES, STICKER_SLIDERS, SYMMETRY_MODES, type SavedMixingPadInfo, type SavedThickChannels, type SerializeOptions, type SerializedPaintDocument, type SliderControl, type StampKind, type Stencil, type StencilInput, type StencilKind, type StencilShape, StencilSnapper, type Sticker, type StickerSettings, StickerSpray, type StickerSprayInit, type StrokeCopy, type StrokeSessionHost, type StrokeSessionInit, type StrokeVariation, type Swatch, type SymmetryGuide, type SymmetryMode, TRACING_NODE_ID, type ThickChannels, ThickStrokeDriver, type ThickStrokeInit, TileableNoise, type TimeLapse, type TimeLapseOptions, type TracingImage, type ViewLayer, type ViewportBox, WET_MEDIA, WET_MEDIUM_LABELS, WET_MEDIUM_PARAMS, WET_PARAM_SCHEMA, WET_PARAM_SLIDERS, WET_SPEED_SLIDER, WET_TILT_SLIDERS, WET_TOOLS, WET_TOOL_LABELS, WetBackendInfo, type WetBrushSettings, type WetEvent, WetFlowSpec, type WetLayerInfo, type WetLayerSettings, type WetMediumKind, WetParams, WetStrokeDriver, type WetStrokeInit, WetTilt, type WetTool, affineCopy, applyAffine, brushEngineControls, brushLoadColor, brushLoadForStroke, brushToJSON, buildTimeLapse, clonePaper, colorHarmony, composeAffine, contactSheet, createPaintCompositor, createPaintDocument, createPaintEditorToolProvider, createPaintLayer, createPaintReplayTools, createPaintStickerTools, createPaintThickTools, createPaintWetTools, decodeImageBlob, defaultGuides, defaultImageEncoder, deserializePaintDocument, deserializePaintDocumentAsync, documentExtensions, documentToScreen, downloadBytes, downsampleRgba, drawSticker, drawWaterOverlay, emptiedLayers, emptyThickChannels, fitZoom, grainCatch, grainHeights, guideOverlay, handlePaintShortcut, hash2, hexToRgb8, hslToRgb, hsvToRgb, hueToWheel, invertAffine, isPalmTouch, isWetBrush, isWetCapable, lightTile, lightingKey, mapPenPoint, mix32, mixerLoadForStroke, mixerTip, multiColorForDab, multiColorGradient, multiColorPalette, multiColorSliders, newPaintLayerId, normalizeBehavior, normalizeBristle, normalizeBrushStroke, normalizeCanvas, normalizeDynamics, normalizeImpasto, normalizeLighting, normalizeMultiColor, normalizePaintBrush, normalizePaper, normalizeParticle, normalizePerspective, normalizeRotation, normalizeStencil, normalizeStickerSettings, normalizeStickerUpdate, normalizeSymmetry, normalizeView, normalizeWetBrush, padSampleRadius, paperGrain, paperKey, paperNode, paperPreset, parseBrushLibrary, parseHexColor, patchBrushValue, patchPath, penOrientation, perspectiveDirections, perspectiveGuideLines, pointerToPenPoint, quickStencil, rand01, randSigned, readPath, reliefInfo, renderBrushThumbnail, renderColorWheel, renderPaper, renderStickers, replayFrame, replayItems, replayPainting, rgbToHexColor, rgbToHsl, rgbToHsv, samplePressure, scaleStencil, screenToDocument, seedFromString, serializePaintDocument, serializePaintDocumentExternal, stampCoverage, startWetClock, stencilMask, stencilOutline, stickerRect, stickerSurface, strokeBounds, strokeDuration, strokeSchedule, sweepPath, symmetryAxisAngles, symmetryAxisSegments, symmetryCopies, thickFromJSON, thickToJSON, thumbnailStroke, tiltToOrientation, timeLapseVideo, timeLapseZip, toStrokePoint, toggleStamp, usesPaintExchange, variationAffine, varyStroke, viewCssTransform, viewMatrix, viewPenToDocument, wetPaperInput, wetPigmentOf, wheelAngle, wheelPick, wheelPoint, wheelToHue };