@pixerate/editor 0.242.0 → 0.244.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 (78) hide show
  1. package/dist/animation-editor/index.cjs +59 -59
  2. package/dist/animation-editor/index.js +3 -3
  3. package/dist/{brushstrokeRender-MOYGEXOC.js → brushstrokeRender-4542T5HP.js} +3 -3
  4. package/dist/{brushstrokeRender-UKADOV5D.cjs → brushstrokeRender-QA5WVIDT.cjs} +5 -5
  5. package/dist/canvas/index.cjs +8 -8
  6. package/dist/canvas/index.js +1 -1
  7. package/dist/{chunk-ILM6GYAX.js → chunk-4EQY4TZJ.js} +1 -1
  8. package/dist/{chunk-5BR7LC24.cjs → chunk-4T47Z4J3.cjs} +68 -6
  9. package/dist/{chunk-ZDPLZDTT.js → chunk-5RFENYO5.js} +1 -1
  10. package/dist/{chunk-NE6OOKWJ.js → chunk-7YQNKSQ7.js} +1 -1
  11. package/dist/{chunk-AFLMXITL.cjs → chunk-BDIH4DYR.cjs} +23 -23
  12. package/dist/{chunk-HGGJ36HR.cjs → chunk-HZ547SGQ.cjs} +1 -1
  13. package/dist/{chunk-KCMV2534.cjs → chunk-IZL5V5RK.cjs} +2 -2
  14. package/dist/{chunk-QP2B3MID.js → chunk-L5PB7EG3.js} +1 -1
  15. package/dist/{chunk-HW72ABGR.cjs → chunk-M6JORA5Z.cjs} +86 -86
  16. package/dist/{chunk-UDBDYZET.js → chunk-OSTKS3YN.js} +1 -1
  17. package/dist/{chunk-HLBTRSQJ.js → chunk-P3NIPIKS.js} +263 -63
  18. package/dist/{chunk-7F4G74UQ.cjs → chunk-SD23XMVJ.cjs} +35 -35
  19. package/dist/{chunk-XBZK47LV.js → chunk-TBJEMELS.js} +67 -5
  20. package/dist/{chunk-XFYLA3ZO.cjs → chunk-VMROIWLL.cjs} +290 -90
  21. package/dist/image-editor/index.cjs +785 -777
  22. package/dist/image-editor/index.js +13 -5
  23. package/dist/kernel-wasm-4KVONDAU.js +9 -0
  24. package/dist/kernel-wasm-TTYRBGAK.cjs +9 -0
  25. package/dist/paint/index.cjs +10 -2
  26. package/dist/paint/index.js +9 -1
  27. package/dist/paint/wet/film.d.cts +19 -1
  28. package/dist/paint/wet/film.d.ts +19 -1
  29. package/dist/paint/wet/gpu-kernels.d.cts +2 -2
  30. package/dist/paint/wet/gpu-kernels.d.ts +2 -2
  31. package/dist/paint/wet/kernel-wasm.d.cts +5 -5
  32. package/dist/paint/wet/kernel-wasm.d.ts +5 -5
  33. package/dist/paint/wet/kernel.d.cts +1 -1
  34. package/dist/paint/wet/kernel.d.ts +1 -1
  35. package/dist/paint/wet/medium.d.cts +3 -1
  36. package/dist/paint/wet/medium.d.ts +3 -1
  37. package/dist/paint/wet/model.d.cts +19 -4
  38. package/dist/paint/wet/model.d.ts +19 -4
  39. package/dist/paint/wet/world.d.cts +34 -8
  40. package/dist/paint/wet/world.d.ts +34 -8
  41. package/dist/paint-editor/backlight.d.cts +77 -0
  42. package/dist/paint-editor/backlight.d.ts +77 -0
  43. package/dist/paint-editor/brushes.d.cts +18 -2
  44. package/dist/paint-editor/brushes.d.ts +18 -2
  45. package/dist/paint-editor/controller.d.cts +39 -0
  46. package/dist/paint-editor/controller.d.ts +39 -0
  47. package/dist/paint-editor/document.d.cts +3 -0
  48. package/dist/paint-editor/document.d.ts +3 -0
  49. package/dist/paint-editor/index.cjs +1335 -529
  50. package/dist/paint-editor/index.d.cts +1 -0
  51. package/dist/paint-editor/index.d.ts +1 -0
  52. package/dist/paint-editor/index.js +856 -50
  53. package/dist/paint-editor/light.d.cts +74 -0
  54. package/dist/paint-editor/light.d.ts +74 -0
  55. package/dist/paint-editor/paper.d.cts +7 -0
  56. package/dist/paint-editor/paper.d.ts +7 -0
  57. package/dist/paint-editor/stroke.d.cts +14 -0
  58. package/dist/paint-editor/stroke.d.ts +14 -0
  59. package/dist/paint-editor/thick-tools.d.cts +2 -2
  60. package/dist/paint-editor/thick-tools.d.ts +2 -2
  61. package/dist/paint-editor/thick.d.cts +5 -1
  62. package/dist/paint-editor/thick.d.ts +5 -1
  63. package/dist/paint-editor/ui.d.cts +2 -0
  64. package/dist/paint-editor/ui.d.ts +2 -0
  65. package/dist/paint-editor/wet.d.cts +9 -0
  66. package/dist/paint-editor/wet.d.ts +9 -0
  67. package/dist/vector/index.cjs +962 -962
  68. package/dist/vector/index.js +4 -4
  69. package/package.json +1 -1
  70. package/skills/pixerate-paint-editor/SKILL.md +11 -11
  71. package/skills/pixerate-paint-editor/references/canvas-hosted.md +8 -0
  72. package/skills/pixerate-paint-editor/references/light-paint.md +48 -0
  73. package/skills/pixerate-paint-oil-impasto/SKILL.md +1 -0
  74. package/skills/pixerate-paint-watercolour/SKILL.md +10 -1
  75. package/skills/pixerate-paint-watercolour/evals/glowing-wash.json +22 -0
  76. package/skills/pixerate-paint-watercolour/evals/stained-glass.json +16 -0
  77. package/dist/kernel-wasm-6FPOLVUZ.cjs +0 -9
  78. package/dist/kernel-wasm-WIMMJUF2.js +0 -9
@@ -17,6 +17,10 @@
17
17
  * - **Display.** Each lane has a `base` surface: the layer's pixels under the wet paint. `display`
18
18
  * gives the base with every live tile shaded (`filmPixel`), as an ordinary RGBA surface for the
19
19
  * compositor. Tiles that dry are baked into the base and their slots freed.
20
+ * - **Light paint.** Light is a fifth mass channel (suspended and settled), moving exactly as
21
+ * pigment does. A lane's `light` surface (one channel, see `paint-editor/light.ts`) is the
22
+ * light under the wet paint; `lightDisplay` shades the live light over it and drying bakes it
23
+ * in, as the colour is.
20
24
  * - **Engine B (WebGPU).** With a GPU engine attached (`attachGpu`) and at least `gpuMinTiles` live
21
25
  * tiles, the GPU keeps a mirror of the slots: each edit uploads the rows it changed, each batch
22
26
  * runs on the GPU too, and `displayGpu` shades every stale tile there at once. In lead mode
@@ -30,8 +34,8 @@ import { type WetWorkerLayout } from './wasm.js';
30
34
  import type { WetGpuEngine } from './gpu.js';
31
35
  /** Wet layers a world can hold at once. */
32
36
  export declare const MAX_WET_LANES = 8;
33
- /** Tile slots at most (about 3.8 GB, inside wasm32's 4 GB). Water reaching a tile past this is lost. */
34
- export declare const MAX_WET_SLOTS = 352;
37
+ /** Tile slots at most (about 3.75 GB, inside wasm32's 4 GB). Water reaching a tile past this is lost. */
38
+ export declare const MAX_WET_SLOTS = 328;
35
39
  /** A flow map: a vector per document pixel, in cells per step² at strength 1. */
36
40
  export type WetFlowField = (x: number, y: number) => readonly [number, number];
37
41
  /** The paper as the simulation reads it. */
@@ -62,6 +66,8 @@ export interface WetLaneOptions {
62
66
  flow?: WetFlowField | null;
63
67
  /** Flow map strength (default 1 when a flow map is given). */
64
68
  flowStrength?: number;
69
+ /** The layer's light paint under the wet paint (one channel; default none). */
70
+ light?: TiledSurface | null;
65
71
  }
66
72
  /** Totals for one wet layer. */
67
73
  export interface WetLaneStats {
@@ -87,6 +93,8 @@ export interface WetSnapshot {
87
93
  flow: WetFlowField | null;
88
94
  flowStrength: number;
89
95
  base: TiledSurface;
96
+ light?: TiledSurface;
97
+ lit?: boolean;
90
98
  }>;
91
99
  /** Per active key: the current set and the deposit planes; and its last flags. */
92
100
  readonly slots: ReadonlyMap<number, {
@@ -177,6 +185,12 @@ export declare class WetWorld {
177
185
  getBase(id: string): TiledSurface;
178
186
  /** Replaces the pixels under a layer's wet paint. */
179
187
  setBase(id: string, base: TiledSurface): void;
188
+ /** The light paint under a layer's wet paint (one channel). */
189
+ getLight(id: string): TiledSurface;
190
+ /** Whether a layer has light paint, under its wet paint or in it. */
191
+ isLit(id: string): boolean;
192
+ /** Replaces the light paint under a layer's wet paint. */
193
+ setLight(id: string, light: TiledSurface): void;
180
194
  private pack;
181
195
  private touchAll;
182
196
  private laneIndexOf;
@@ -247,10 +261,10 @@ export declare class WetWorld {
247
261
  private addWaterAt;
248
262
  /**
249
263
  * Lays paint: `water` depth and pigment mass `pigment` (K r, g, b, S) per unit of coverage, or
250
- * per pixel from `field` (4 values per pixel of `rect`). Pigment without water lands straight in
251
- * the paper (settled).
264
+ * per pixel from `field` (4 values per pixel of `rect`), and `light` (light paint mass) per unit
265
+ * of coverage. Pigment and light without water land straight in the paper (settled).
252
266
  */
253
- paint(id: string, rect: RasterRect, cov: ArrayLike<number> | null, water: number, pigment: ArrayLike<number> | null, field?: ArrayLike<number> | null): void;
267
+ paint(id: string, rect: RasterRect, cov: ArrayLike<number> | null, water: number, pigment: ArrayLike<number> | null, field?: ArrayLike<number> | null, light?: number): void;
254
268
  /** Damp paper: fills the paper's water towards its capacity by `amount` (0..1) × coverage, plus a thin film of surface water. */
255
269
  wetPaper(id: string, rect: RasterRect, cov: ArrayLike<number> | null, amount: number): void;
256
270
  /**
@@ -261,16 +275,17 @@ export declare class WetWorld {
261
275
  remove(id: string, rect: RasterRect, cov: ArrayLike<number> | null, water: number, suspended: number, settled: number, paperWater?: number): void;
262
276
  /** Pushes the water: adds velocity (`ux`, `uy` cells per step, each at most 0.1) × coverage. */
263
277
  push(id: string, rect: RasterRect, cov: ArrayLike<number> | null, ux: number, uy: number): void;
264
- /** Blends wet paint: each wet cell's suspended pigment moves `strength` × coverage towards the footprint's mean. */
278
+ /** Blends wet paint: each wet cell's suspended pigment (and light) moves `strength` × coverage towards the footprint's mean. */
265
279
  blend(id: string, rect: RasterRect, cov: ArrayLike<number> | null, strength: number): void;
266
280
  /**
267
281
  * Drags wet paint by (`dx`, `dy`) cells: each footprint cell takes `strength` × coverage of the
268
- * water and suspended pigment of the cell it came from (a smudge through wet paint).
282
+ * water and suspended pigment (and light) of the cell it came from (a smudge through wet paint).
269
283
  */
270
284
  drag(id: string, rect: RasterRect, cov: ArrayLike<number> | null, dx: number, dy: number, strength: number): void;
271
285
  /**
272
286
  * Re-wetting: lifts `amount` × coverage of the dried paint in the layer's pixels (`base`) back into
273
- * the paper as settled pigment, so water can move it again. Only where there is surface water.
287
+ * the paper as settled pigment, so water can move it again, and the same share of its light
288
+ * paint (`light`) as settled light. Only where there is surface water.
274
289
  */
275
290
  rewet(id: string, rect: RasterRect, cov: ArrayLike<number> | null, amount: number, paper: readonly [number, number, number]): void;
276
291
  /**
@@ -284,6 +299,8 @@ export declare class WetWorld {
284
299
  * Returns whether any pixel changed.
285
300
  */
286
301
  private bakeKey;
302
+ /** `shadeSlot` for light paint: one channel over a light tile (see `shadeLightTile`); `null` when nothing would change. */
303
+ private shadeLightSlot;
287
304
  /**
288
305
  * Shades one slot over a base tile. `display`: the wet look (suspended + settled, wet paper a
289
306
  * little darker); `bake`: settled pigment only; `bake-all`: suspended and settled (dry now).
@@ -292,6 +309,8 @@ export declare class WetWorld {
292
309
  private shadeSlot;
293
310
  /** The layer's pixels with all its wet paint dried where it is (the bake of "dry on save"); changes nothing. */
294
311
  bakedSurface(id: string): TiledSurface;
312
+ /** The layer's light paint with all its wet paint dried where it is (`bakedSurface` for light); changes nothing. */
313
+ bakedLight(id: string): TiledSurface;
295
314
  /**
296
315
  * Sums over a footprint of a lane, weighted by coverage: `weight` (coverage), `water` (surface
297
316
  * depth), `wet` (coverage of wet cells), `pigment` (suspended, 4 channels), `settled` (4 channels).
@@ -323,6 +342,12 @@ export declare class WetWorld {
323
342
  changed: RasterRect[];
324
343
  pending: number;
325
344
  };
345
+ /**
346
+ * The layer's light paint with its live light shaded in (`null` when the layer has none): the
347
+ * light under the wet paint with each live tile's light over it. Like `display`, every stale
348
+ * tile is reshaded (light tiles are cheap: one channel, no colour maths).
349
+ */
350
+ lightDisplay(id: string): TiledSurface | null;
326
351
  /**
327
352
  * The water as an overlay tile (RGBA, blue, alpha from surface depth and damp paper) for a
328
353
  * document tile, or `null` where nothing is wet.
@@ -388,6 +413,7 @@ export declare class WetWorld {
388
413
  /** A layer's base and flow map in a moved snapshot (see `moveWetSnapshot`). */
389
414
  export interface WetSnapshotLaneMove {
390
415
  base?: TiledSurface;
416
+ light?: TiledSurface;
391
417
  flow?: WetFlowField | null;
392
418
  }
393
419
  /**
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Backlit paper: the paper as a light source behind the paint (a lightbox, stained glass). The
3
+ * light that makes it through the paint is drawn as one Linear Dodge (Add) layer, "Backlight", at
4
+ * the top of the stack (opaque pixels whose colour is the light added, so the sRGB blend adds
5
+ * exactly that; the linear light value is added as is, an approximation of adding light) (it shines through everything, so nothing composited above it may cover
6
+ * it), with a soft spill where it comes through paint, so a lit glaze lights its surroundings.
7
+ *
8
+ * Per pixel, the light starts as `backlight × backlightColor` (linear), a little stronger in the
9
+ * paper's valleys (thinner paper, by its grain depth), and passes through each visible pixel layer
10
+ * from the bottom up:
11
+ *
12
+ * - **Paint as a film over the paper.** A layer pixel (colour `c`, alpha `a`) over the paper `p`
13
+ * shows `e = a·c + (1 − a)·p`. Its transmission is `T = min(1, e / p)` per channel (linear):
14
+ * exact for a transparent glaze, whose film over the paper is `T·p` (the wet bake writes
15
+ * watercolour that way). Alpha above the least the colour needs over the paper
16
+ * (`aMin`, as `film.ts` computes it) is hiding: `hide = (a − aMin) / (1 − aMin)`, and the light
17
+ * through is `(1 − hide)·T`. The wet bake writes gouache and oil with their hiding in alpha, and
18
+ * opaque dry paint (alpha 1) hides by this rule too, so glazes glow in their own colour and
19
+ * opaque paint blocks the light.
20
+ * - **Thick paint** blocks the light by its height; impasto clarity (material A) lets some through.
21
+ * - **Layer opacity** mixes towards no change. Blend modes are not modelled (every layer is read
22
+ * as a film); over other layers rather than the paper the film rule is an approximation.
23
+ *
24
+ * Exact for watercolour straight over the paper; approximate for stacks of layers and for blend
25
+ * modes. Changing the backlight never repaints: it is a few numbers on the paper.
26
+ */
27
+ import { TiledSurface } from '../raster/surface.cjs';
28
+ import { type LightSpill } from './light.cjs';
29
+ import { type PaperGrain, type PaperSettings } from './paper.cjs';
30
+ import type { ThickChannels } from './thick.cjs';
31
+ /** Default backlight colour: warm white, like a lightbox. */
32
+ export declare const DEFAULT_BACKLIGHT_COLOR = "#fff1d6";
33
+ /** Spill of the light that comes through paint (px, strength), at backlight 1. */
34
+ export declare const BACKLIGHT_SPILL: Readonly<LightSpill>;
35
+ /** One visible pixel layer as the backlight reads it, bottom first. */
36
+ export interface BacklitLayer {
37
+ surface: TiledSurface;
38
+ offset: {
39
+ x: number;
40
+ y: number;
41
+ };
42
+ /** Effective opacity (its groups' opacities multiplied in). */
43
+ opacity: number;
44
+ thick?: ThickChannels;
45
+ }
46
+ /** The paper's backlight (0 when it has none). */
47
+ export declare function backlightOf(paper: PaperSettings): number;
48
+ /**
49
+ * Computes and caches the backlight per 256 px document tile: a tile is recomputed only when one of
50
+ * the layer tiles over it (or the paper, or the layer list) changed.
51
+ */
52
+ export declare class BacklightCache {
53
+ private tiles;
54
+ private key;
55
+ private spillOf;
56
+ private out;
57
+ /**
58
+ * The backlight layer's pixels for `layers` (bottom first) on `paper` over a `width` × `height`
59
+ * canvas, or `null` when the paper has no backlight. `spillFrom` is the committed painting (the
60
+ * spill is drawn from it, so a stroke in progress does not redo the blur every frame).
61
+ */
62
+ surface(paper: PaperSettings, grain: PaperGrain, width: number, height: number, layers: readonly BacklitLayer[], spillFrom: BacklightCache | null): {
63
+ light: TiledSurface;
64
+ color: TiledSurface;
65
+ painted: TiledSurface;
66
+ } | null;
67
+ /**
68
+ * The full Backlight layer: the light through the paint (`current`) plus the spill of the light
69
+ * through the committed paint (`committed`), both Linear Dodge. Cached on their tiles.
70
+ */
71
+ layer(current: {
72
+ light: TiledSurface;
73
+ }, committed: {
74
+ color: TiledSurface;
75
+ painted: TiledSurface;
76
+ } | null, amount: number): TiledSurface;
77
+ }
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Backlit paper: the paper as a light source behind the paint (a lightbox, stained glass). The
3
+ * light that makes it through the paint is drawn as one Linear Dodge (Add) layer, "Backlight", at
4
+ * the top of the stack (opaque pixels whose colour is the light added, so the sRGB blend adds
5
+ * exactly that; the linear light value is added as is, an approximation of adding light) (it shines through everything, so nothing composited above it may cover
6
+ * it), with a soft spill where it comes through paint, so a lit glaze lights its surroundings.
7
+ *
8
+ * Per pixel, the light starts as `backlight × backlightColor` (linear), a little stronger in the
9
+ * paper's valleys (thinner paper, by its grain depth), and passes through each visible pixel layer
10
+ * from the bottom up:
11
+ *
12
+ * - **Paint as a film over the paper.** A layer pixel (colour `c`, alpha `a`) over the paper `p`
13
+ * shows `e = a·c + (1 − a)·p`. Its transmission is `T = min(1, e / p)` per channel (linear):
14
+ * exact for a transparent glaze, whose film over the paper is `T·p` (the wet bake writes
15
+ * watercolour that way). Alpha above the least the colour needs over the paper
16
+ * (`aMin`, as `film.ts` computes it) is hiding: `hide = (a − aMin) / (1 − aMin)`, and the light
17
+ * through is `(1 − hide)·T`. The wet bake writes gouache and oil with their hiding in alpha, and
18
+ * opaque dry paint (alpha 1) hides by this rule too, so glazes glow in their own colour and
19
+ * opaque paint blocks the light.
20
+ * - **Thick paint** blocks the light by its height; impasto clarity (material A) lets some through.
21
+ * - **Layer opacity** mixes towards no change. Blend modes are not modelled (every layer is read
22
+ * as a film); over other layers rather than the paper the film rule is an approximation.
23
+ *
24
+ * Exact for watercolour straight over the paper; approximate for stacks of layers and for blend
25
+ * modes. Changing the backlight never repaints: it is a few numbers on the paper.
26
+ */
27
+ import { TiledSurface } from '../raster/surface.js';
28
+ import { type LightSpill } from './light.js';
29
+ import { type PaperGrain, type PaperSettings } from './paper.js';
30
+ import type { ThickChannels } from './thick.js';
31
+ /** Default backlight colour: warm white, like a lightbox. */
32
+ export declare const DEFAULT_BACKLIGHT_COLOR = "#fff1d6";
33
+ /** Spill of the light that comes through paint (px, strength), at backlight 1. */
34
+ export declare const BACKLIGHT_SPILL: Readonly<LightSpill>;
35
+ /** One visible pixel layer as the backlight reads it, bottom first. */
36
+ export interface BacklitLayer {
37
+ surface: TiledSurface;
38
+ offset: {
39
+ x: number;
40
+ y: number;
41
+ };
42
+ /** Effective opacity (its groups' opacities multiplied in). */
43
+ opacity: number;
44
+ thick?: ThickChannels;
45
+ }
46
+ /** The paper's backlight (0 when it has none). */
47
+ export declare function backlightOf(paper: PaperSettings): number;
48
+ /**
49
+ * Computes and caches the backlight per 256 px document tile: a tile is recomputed only when one of
50
+ * the layer tiles over it (or the paper, or the layer list) changed.
51
+ */
52
+ export declare class BacklightCache {
53
+ private tiles;
54
+ private key;
55
+ private spillOf;
56
+ private out;
57
+ /**
58
+ * The backlight layer's pixels for `layers` (bottom first) on `paper` over a `width` × `height`
59
+ * canvas, or `null` when the paper has no backlight. `spillFrom` is the committed painting (the
60
+ * spill is drawn from it, so a stroke in progress does not redo the blur every frame).
61
+ */
62
+ surface(paper: PaperSettings, grain: PaperGrain, width: number, height: number, layers: readonly BacklitLayer[], spillFrom: BacklightCache | null): {
63
+ light: TiledSurface;
64
+ color: TiledSurface;
65
+ painted: TiledSurface;
66
+ } | null;
67
+ /**
68
+ * The full Backlight layer: the light through the paint (`current`) plus the spill of the light
69
+ * through the committed paint (`committed`), both Linear Dodge. Cached on their tiles.
70
+ */
71
+ layer(current: {
72
+ light: TiledSurface;
73
+ }, committed: {
74
+ color: TiledSurface;
75
+ painted: TiledSurface;
76
+ } | null, amount: number): TiledSurface;
77
+ }
@@ -18,7 +18,7 @@ import { type BristleSettings, type ParticleSettings } from './procedural.cjs';
18
18
  */
19
19
  export type PaintBrushEngine = 'dab' | 'eraser' | 'mixer' | 'blender' | 'smudge' | 'wet' | 'impasto' | 'particle' | 'bristle' | 'sticker';
20
20
  export declare const PAINT_BRUSH_ENGINES: readonly PaintBrushEngine[];
21
- export type PaintBrushCategory = 'pencil' | 'pastel' | 'charcoal' | 'airbrush' | 'marker' | 'ink' | 'paint' | 'eraser' | 'blender' | 'smudge' | 'mixer' | 'watercolor' | 'wet-tool' | 'oil' | 'impasto' | 'procedural' | 'bristle' | 'sticker' | 'custom';
21
+ export type PaintBrushCategory = 'pencil' | 'pastel' | 'charcoal' | 'airbrush' | 'marker' | 'ink' | 'paint' | 'eraser' | 'blender' | 'smudge' | 'mixer' | 'watercolor' | 'wet-tool' | 'oil' | 'impasto' | 'procedural' | 'bristle' | 'sticker' | 'light' | 'custom';
22
22
  export declare const PAINT_BRUSH_CATEGORIES: readonly PaintBrushCategory[];
23
23
  /**
24
24
  * What a wet brush does to the wet layer: `paint` (water and pigment), `water` (clear water;
@@ -81,8 +81,21 @@ export interface ImpastoSettings {
81
81
  thinner: number;
82
82
  /** Knife and brush: how much of the paint under the tool is dragged along (0..1). */
83
83
  drag: number;
84
+ /** Light paint: the bead's light is stronger where its surface turns away, as on backlit glass (0..1, default 0). */
85
+ rim: number;
86
+ /** Light paint: the middle of a bead lets the layers below show through, its edges stay solid (0..1, default 0). */
87
+ clarity: number;
84
88
  }
85
89
  export declare const DEFAULT_IMPASTO: Readonly<ImpastoSettings>;
90
+ /** Light paint: how much light a brush lays with its colour (see `doc.light`). */
91
+ export interface LightSettings {
92
+ /** Light each dab lays, scaled by its coverage and flow (0..1, default 0: none). */
93
+ amount: number;
94
+ /** Pressure scales the light (default false). */
95
+ pressure: boolean;
96
+ }
97
+ export declare const DEFAULT_LIGHT: Readonly<LightSettings>;
98
+ export declare function normalizeLight(raw?: Partial<LightSettings>, base?: LightSettings): LightSettings;
86
99
  export declare function normalizeImpasto(raw?: Partial<ImpastoSettings>, base?: ImpastoSettings): ImpastoSettings;
87
100
  /** Multi-coloured brush: `tip` lays the colours across the tip, `dab` changes colour from dab to dab. */
88
101
  export type MultiColorMode = 'off' | 'tip' | 'dab';
@@ -181,6 +194,8 @@ export interface PaintBrush {
181
194
  bristle: BristleSettings;
182
195
  /** Sticker engine only: the stamps and how they are sprayed. */
183
196
  sticker: StickerSettings;
197
+ /** Light paint: the light it lays with its colour (amount 0: none). */
198
+ light: LightSettings;
184
199
  /**
185
200
  * The brush's own gradient paint (`@pixerate/editor/paint` `PaintGradientSource`), used instead
186
201
  * of the editor's paint; `null` (default): the editor's paint. Stops may be `paint` / `secondary`
@@ -199,7 +214,8 @@ export type BrushBehaviorInput = Partial<Omit<BrushBehavior, 'multicolor'>> & {
199
214
  };
200
215
  export declare function normalizeBehavior(raw?: BrushBehaviorInput, base?: BrushBehavior): BrushBehavior;
201
216
  /** Brush settings to change or read: any subset of a brush and of its blocks. */
202
- export type PaintBrushInput = Partial<Omit<PaintBrush, 'tip' | 'dynamics' | 'mixer' | 'behavior' | 'blender' | 'smudge' | 'wet' | 'impasto' | 'particle' | 'bristle' | 'sticker' | 'paint'>> & {
217
+ export type PaintBrushInput = Partial<Omit<PaintBrush, 'tip' | 'dynamics' | 'mixer' | 'behavior' | 'blender' | 'smudge' | 'wet' | 'impasto' | 'particle' | 'bristle' | 'sticker' | 'paint' | 'light'>> & {
218
+ light?: Partial<LightSettings>;
203
219
  /** A gradient (or `null` for the editor's paint); a plain colour source clears it. */
204
220
  paint?: PaintSource | null;
205
221
  tip?: Partial<BrushSettings>;
@@ -18,7 +18,7 @@ import { type BristleSettings, type ParticleSettings } from './procedural.js';
18
18
  */
19
19
  export type PaintBrushEngine = 'dab' | 'eraser' | 'mixer' | 'blender' | 'smudge' | 'wet' | 'impasto' | 'particle' | 'bristle' | 'sticker';
20
20
  export declare const PAINT_BRUSH_ENGINES: readonly PaintBrushEngine[];
21
- export type PaintBrushCategory = 'pencil' | 'pastel' | 'charcoal' | 'airbrush' | 'marker' | 'ink' | 'paint' | 'eraser' | 'blender' | 'smudge' | 'mixer' | 'watercolor' | 'wet-tool' | 'oil' | 'impasto' | 'procedural' | 'bristle' | 'sticker' | 'custom';
21
+ export type PaintBrushCategory = 'pencil' | 'pastel' | 'charcoal' | 'airbrush' | 'marker' | 'ink' | 'paint' | 'eraser' | 'blender' | 'smudge' | 'mixer' | 'watercolor' | 'wet-tool' | 'oil' | 'impasto' | 'procedural' | 'bristle' | 'sticker' | 'light' | 'custom';
22
22
  export declare const PAINT_BRUSH_CATEGORIES: readonly PaintBrushCategory[];
23
23
  /**
24
24
  * What a wet brush does to the wet layer: `paint` (water and pigment), `water` (clear water;
@@ -81,8 +81,21 @@ export interface ImpastoSettings {
81
81
  thinner: number;
82
82
  /** Knife and brush: how much of the paint under the tool is dragged along (0..1). */
83
83
  drag: number;
84
+ /** Light paint: the bead's light is stronger where its surface turns away, as on backlit glass (0..1, default 0). */
85
+ rim: number;
86
+ /** Light paint: the middle of a bead lets the layers below show through, its edges stay solid (0..1, default 0). */
87
+ clarity: number;
84
88
  }
85
89
  export declare const DEFAULT_IMPASTO: Readonly<ImpastoSettings>;
90
+ /** Light paint: how much light a brush lays with its colour (see `doc.light`). */
91
+ export interface LightSettings {
92
+ /** Light each dab lays, scaled by its coverage and flow (0..1, default 0: none). */
93
+ amount: number;
94
+ /** Pressure scales the light (default false). */
95
+ pressure: boolean;
96
+ }
97
+ export declare const DEFAULT_LIGHT: Readonly<LightSettings>;
98
+ export declare function normalizeLight(raw?: Partial<LightSettings>, base?: LightSettings): LightSettings;
86
99
  export declare function normalizeImpasto(raw?: Partial<ImpastoSettings>, base?: ImpastoSettings): ImpastoSettings;
87
100
  /** Multi-coloured brush: `tip` lays the colours across the tip, `dab` changes colour from dab to dab. */
88
101
  export type MultiColorMode = 'off' | 'tip' | 'dab';
@@ -181,6 +194,8 @@ export interface PaintBrush {
181
194
  bristle: BristleSettings;
182
195
  /** Sticker engine only: the stamps and how they are sprayed. */
183
196
  sticker: StickerSettings;
197
+ /** Light paint: the light it lays with its colour (amount 0: none). */
198
+ light: LightSettings;
184
199
  /**
185
200
  * The brush's own gradient paint (`@pixerate/editor/paint` `PaintGradientSource`), used instead
186
201
  * of the editor's paint; `null` (default): the editor's paint. Stops may be `paint` / `secondary`
@@ -199,7 +214,8 @@ export type BrushBehaviorInput = Partial<Omit<BrushBehavior, 'multicolor'>> & {
199
214
  };
200
215
  export declare function normalizeBehavior(raw?: BrushBehaviorInput, base?: BrushBehavior): BrushBehavior;
201
216
  /** Brush settings to change or read: any subset of a brush and of its blocks. */
202
- export type PaintBrushInput = Partial<Omit<PaintBrush, 'tip' | 'dynamics' | 'mixer' | 'behavior' | 'blender' | 'smudge' | 'wet' | 'impasto' | 'particle' | 'bristle' | 'sticker' | 'paint'>> & {
217
+ export type PaintBrushInput = Partial<Omit<PaintBrush, 'tip' | 'dynamics' | 'mixer' | 'behavior' | 'blender' | 'smudge' | 'wet' | 'impasto' | 'particle' | 'bristle' | 'sticker' | 'paint' | 'light'>> & {
218
+ light?: Partial<LightSettings>;
203
219
  /** A gradient (or `null` for the editor's paint); a plain colour source clears it. */
204
220
  paint?: PaintSource | null;
205
221
  tip?: Partial<BrushSettings>;
@@ -32,6 +32,7 @@ import { type PaintGuides, type PerspectiveGuide, type ReferenceImage, type Symm
32
32
  import { MixingPad, type MixingPadTool, type PadMixOptions } from './mixing-pad.cjs';
33
33
  import { type Stencil, type StencilInput } from './stencils.cjs';
34
34
  import { type Sticker } from './stickers.cjs';
35
+ import { type LightSpill } from './light.cjs';
35
36
  import { type PaintView } from './view.cjs';
36
37
  import { PaintWetLayers, type PaintWetOptions, type WetEngineInfo, type WetGpuMode, type WetLayerInfo } from './wet.cjs';
37
38
  import { type WetParams, type WetTilt } from '../paint/wet/model.cjs';
@@ -314,6 +315,12 @@ export declare class PaintEditorController {
314
315
  private dirty;
315
316
  private seedCounter;
316
317
  private strokeCounter;
318
+ /** Light paint: each layer's spill settings (saved with the document, not undo steps; missing = the defaults). */
319
+ private lightSpills;
320
+ private readonly lightCaches;
321
+ /** Backlit paper: the light through the paint as drawn (live strokes too), and as committed (its spill). */
322
+ private readonly backlightView;
323
+ private readonly backlightCommitted;
317
324
  /** Live effect paint: playing and the frame rate cap (editor state), and the time the view shows. */
318
325
  private effectPlaybackState;
319
326
  private effectTime;
@@ -731,6 +738,24 @@ export declare class PaintEditorController {
731
738
  bounds: RasterRect | null;
732
739
  layerIds: string[];
733
740
  };
741
+ /** The document's `light` without these layers (as a spread: `{}` when unchanged). */
742
+ private lightWithout;
743
+ private lightSpillOf;
744
+ /** A layer's light paint: whether it has any, where (document pixels), its brightest value 0..255, and its spill. */
745
+ lightOf(layerId?: string): {
746
+ layerId: string;
747
+ hasLight: boolean;
748
+ bounds: RasterRect | null;
749
+ peak: number;
750
+ spill: LightSpill;
751
+ };
752
+ /** How far a layer's light spills (px; 0 turns it off) and how strongly (0..2). Saved with the document, not an undo step; nothing is repainted. */
753
+ setLightSpill(layerId: string | undefined, update: Partial<LightSpill>): LightSpill;
754
+ /** Takes a layer's light away; its paint keeps its colour. One undo step. Returns whether there was any. */
755
+ clearLight(layerId?: string): {
756
+ layerId: string;
757
+ cleared: boolean;
758
+ };
734
759
  /** The document's `thick` without these layers (as a spread: `{}` when unchanged). */
735
760
  private thickWithout;
736
761
  /** Where the light comes from for thick paint and metallic paper. */
@@ -889,6 +914,20 @@ export declare class PaintEditorController {
889
914
  private driedLayers;
890
915
  /** The layers with thick paint lit (each thick layer's pixels replaced by its lit tiles). */
891
916
  private litLayers;
917
+ /**
918
+ * Light paint over the (lit) layers: glow, rim and clarity in each layer's pixels, and its spill
919
+ * as a Linear Dodge (Add) layer directly above it (from the painting as committed, so a stroke in
920
+ * progress does not redraw the blur every frame).
921
+ */
922
+ private lightLayers;
923
+ /** Visible pixel layers bottom first, as the backlight reads them (group opacities multiplied in). */
924
+ private backlitLayers;
925
+ /**
926
+ * Backlit paper: the light that makes it through `base` (the layers as drawn), with the spill of
927
+ * the light through the committed painting, as a Linear Dodge (Add) layer for the top of the
928
+ * stack; `null` when the paper has no backlight.
929
+ */
930
+ private backlightLayer;
892
931
  private renderLayers;
893
932
  /**
894
933
  * Composites the viewport stack (paper, view-only underlays, layers with the live stroke) tile by
@@ -32,6 +32,7 @@ import { type PaintGuides, type PerspectiveGuide, type ReferenceImage, type Symm
32
32
  import { MixingPad, type MixingPadTool, type PadMixOptions } from './mixing-pad.js';
33
33
  import { type Stencil, type StencilInput } from './stencils.js';
34
34
  import { type Sticker } from './stickers.js';
35
+ import { type LightSpill } from './light.js';
35
36
  import { type PaintView } from './view.js';
36
37
  import { PaintWetLayers, type PaintWetOptions, type WetEngineInfo, type WetGpuMode, type WetLayerInfo } from './wet.js';
37
38
  import { type WetParams, type WetTilt } from '../paint/wet/model.js';
@@ -314,6 +315,12 @@ export declare class PaintEditorController {
314
315
  private dirty;
315
316
  private seedCounter;
316
317
  private strokeCounter;
318
+ /** Light paint: each layer's spill settings (saved with the document, not undo steps; missing = the defaults). */
319
+ private lightSpills;
320
+ private readonly lightCaches;
321
+ /** Backlit paper: the light through the paint as drawn (live strokes too), and as committed (its spill). */
322
+ private readonly backlightView;
323
+ private readonly backlightCommitted;
317
324
  /** Live effect paint: playing and the frame rate cap (editor state), and the time the view shows. */
318
325
  private effectPlaybackState;
319
326
  private effectTime;
@@ -731,6 +738,24 @@ export declare class PaintEditorController {
731
738
  bounds: RasterRect | null;
732
739
  layerIds: string[];
733
740
  };
741
+ /** The document's `light` without these layers (as a spread: `{}` when unchanged). */
742
+ private lightWithout;
743
+ private lightSpillOf;
744
+ /** A layer's light paint: whether it has any, where (document pixels), its brightest value 0..255, and its spill. */
745
+ lightOf(layerId?: string): {
746
+ layerId: string;
747
+ hasLight: boolean;
748
+ bounds: RasterRect | null;
749
+ peak: number;
750
+ spill: LightSpill;
751
+ };
752
+ /** How far a layer's light spills (px; 0 turns it off) and how strongly (0..2). Saved with the document, not an undo step; nothing is repainted. */
753
+ setLightSpill(layerId: string | undefined, update: Partial<LightSpill>): LightSpill;
754
+ /** Takes a layer's light away; its paint keeps its colour. One undo step. Returns whether there was any. */
755
+ clearLight(layerId?: string): {
756
+ layerId: string;
757
+ cleared: boolean;
758
+ };
734
759
  /** The document's `thick` without these layers (as a spread: `{}` when unchanged). */
735
760
  private thickWithout;
736
761
  /** Where the light comes from for thick paint and metallic paper. */
@@ -889,6 +914,20 @@ export declare class PaintEditorController {
889
914
  private driedLayers;
890
915
  /** The layers with thick paint lit (each thick layer's pixels replaced by its lit tiles). */
891
916
  private litLayers;
917
+ /**
918
+ * Light paint over the (lit) layers: glow, rim and clarity in each layer's pixels, and its spill
919
+ * as a Linear Dodge (Add) layer directly above it (from the painting as committed, so a stroke in
920
+ * progress does not redraw the blur every frame).
921
+ */
922
+ private lightLayers;
923
+ /** Visible pixel layers bottom first, as the backlight reads them (group opacities multiplied in). */
924
+ private backlitLayers;
925
+ /**
926
+ * Backlit paper: the light that makes it through `base` (the layers as drawn), with the spill of
927
+ * the light through the committed painting, as a Linear Dodge (Add) layer for the top of the
928
+ * stack; `null` when the paper has no backlight.
929
+ */
930
+ private backlightLayer;
892
931
  private renderLayers;
893
932
  /**
894
933
  * Composites the viewport stack (paper, view-only underlays, layers with the live stroke) tile by
@@ -8,6 +8,7 @@
8
8
  * same layer tree loads in both editors), with the paper as plain settings.
9
9
  */
10
10
  import type { RasterGroupLayer, RasterLayer, RasterPixelLayer } from '../raster/layers.cjs';
11
+ import { type TiledSurface } from '../raster/surface.cjs';
11
12
  import { type PaperSettings } from './paper.cjs';
12
13
  import type { RecordedStroke } from './strokes.cjs';
13
14
  import type { WetEvent } from './wet.cjs';
@@ -48,6 +49,8 @@ export interface PaintDocumentState {
48
49
  * stroke's brush, colour and points stay editable (`updateStroke`).
49
50
  */
50
51
  liveStrokes?: Readonly<Record<string, RecordedStroke[]>>;
52
+ /** Light paint: each layer's light channel, 0..255 per pixel in layer space (layers without light are missing). See `light.ts`. */
53
+ light?: Readonly<Record<string, TiledSurface>>;
51
54
  /** Thick paint: each layer's height and material channels (layers without relief are missing). */
52
55
  thick?: Readonly<Record<string, ThickChannels>>;
53
56
  /** Effect layers: the provider effect each one is drawn from (layers that are gone are ignored). */
@@ -8,6 +8,7 @@
8
8
  * same layer tree loads in both editors), with the paper as plain settings.
9
9
  */
10
10
  import type { RasterGroupLayer, RasterLayer, RasterPixelLayer } from '../raster/layers.js';
11
+ import { type TiledSurface } from '../raster/surface.js';
11
12
  import { type PaperSettings } from './paper.js';
12
13
  import type { RecordedStroke } from './strokes.js';
13
14
  import type { WetEvent } from './wet.js';
@@ -48,6 +49,8 @@ export interface PaintDocumentState {
48
49
  * stroke's brush, colour and points stay editable (`updateStroke`).
49
50
  */
50
51
  liveStrokes?: Readonly<Record<string, RecordedStroke[]>>;
52
+ /** Light paint: each layer's light channel, 0..255 per pixel in layer space (layers without light are missing). See `light.ts`. */
53
+ light?: Readonly<Record<string, TiledSurface>>;
51
54
  /** Thick paint: each layer's height and material channels (layers without relief are missing). */
52
55
  thick?: Readonly<Record<string, ThickChannels>>;
53
56
  /** Effect layers: the provider effect each one is drawn from (layers that are gone are ignored). */