@pixerate/editor 0.255.0 → 0.257.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 (129) hide show
  1. package/dist/agent/index.cjs +40 -38
  2. package/dist/agent/index.js +9 -7
  3. package/dist/animation/index.cjs +4 -2
  4. package/dist/animation/index.js +4 -2
  5. package/dist/animation-editor/index.cjs +63 -61
  6. package/dist/animation-editor/index.js +7 -5
  7. package/dist/brushes/colour.d.cts +11 -0
  8. package/dist/brushes/colour.d.ts +11 -0
  9. package/dist/brushes/index.cjs +51 -30
  10. package/dist/brushes/index.d.cts +1 -0
  11. package/dist/brushes/index.d.ts +1 -0
  12. package/dist/brushes/index.js +25 -4
  13. package/dist/brushes/resolve.d.cts +2 -0
  14. package/dist/brushes/resolve.d.ts +2 -0
  15. package/dist/brushes/schema.d.cts +1 -0
  16. package/dist/brushes/schema.d.ts +1 -0
  17. package/dist/brushes/tools.d.cts +7 -0
  18. package/dist/brushes/tools.d.ts +7 -0
  19. package/dist/brushes/types.d.cts +10 -2
  20. package/dist/brushes/types.d.ts +10 -2
  21. package/dist/brushes/ui.d.cts +14 -3
  22. package/dist/brushes/ui.d.ts +14 -3
  23. package/dist/{brushstrokeRender-HCOQUSWW.js → brushstrokeRender-JI4W6F2G.js} +2 -2
  24. package/dist/{brushstrokeRender-ZZOXI5O5.cjs → brushstrokeRender-MQ6PSDM5.cjs} +11 -11
  25. package/dist/canvas/index.cjs +20 -18
  26. package/dist/canvas/index.js +7 -5
  27. package/dist/chart/index.cjs +159 -157
  28. package/dist/chart/index.js +9 -7
  29. package/dist/{chunk-7272D3K2.js → chunk-2EUW2XZX.js} +27 -276
  30. package/dist/{chunk-L6Y7TSQ4.js → chunk-2VJ6OVJV.js} +1 -1
  31. package/dist/{chunk-6ZZKRH7R.js → chunk-47V3WXDG.js} +2 -2
  32. package/dist/{chunk-H7ET2ISK.cjs → chunk-4IMD2TLY.cjs} +52 -52
  33. package/dist/{chunk-VUKUTT2W.js → chunk-4M4E3N6X.js} +1 -1
  34. package/dist/{chunk-TBAVCLEQ.cjs → chunk-5Q5MLXAD.cjs} +23 -23
  35. package/dist/{chunk-MONXNVZK.js → chunk-6I23ZCGK.js} +5 -3
  36. package/dist/{chunk-GOBOCZZI.js → chunk-6NQPKNUZ.js} +1 -1
  37. package/dist/{chunk-WYGSR3IJ.js → chunk-AORZ5M5C.js} +1 -1
  38. package/dist/chunk-AVTPZO4Z.cjs +142 -0
  39. package/dist/{chunk-WKMUBATU.js → chunk-C2WEIEUB.js} +1 -1
  40. package/dist/{chunk-P3K2JCJA.js → chunk-D5BXSOSU.js} +3 -3
  41. package/dist/chunk-E2V5DA3W.js +36 -0
  42. package/dist/{chunk-WZOGDKCQ.js → chunk-F7CPSRLN.js} +0 -133
  43. package/dist/chunk-FJBQTZS4.js +17 -0
  44. package/dist/{chunk-SOSBOS5T.cjs → chunk-G23BTQA5.cjs} +22 -22
  45. package/dist/{chunk-EUIDTZNQ.cjs → chunk-I3Y2Q3PM.cjs} +59 -308
  46. package/dist/{chunk-YHUMGBZD.js → chunk-ICTPLVSR.js} +22 -4
  47. package/dist/{chunk-LRNUZKWV.cjs → chunk-IPUTIUGQ.cjs} +1 -1
  48. package/dist/{chunk-6GRUVGKU.js → chunk-ITURMC2M.js} +3 -3
  49. package/dist/{chunk-HYWRU5VX.js → chunk-IXQZHCOF.js} +1 -1
  50. package/dist/chunk-J5UQHWVL.js +142 -0
  51. package/dist/{chunk-YBAPT7NL.js → chunk-JC2SHA4A.js} +1 -23
  52. package/dist/chunk-LRTR4ASZ.cjs +17 -0
  53. package/dist/{chunk-HUAISCUX.cjs → chunk-M2HG5DJ2.cjs} +18 -18
  54. package/dist/{chunk-N76AMOKE.cjs → chunk-MDLEGVEN.cjs} +42 -42
  55. package/dist/chunk-MEGSQUQO.cjs +36 -0
  56. package/dist/chunk-MLLTWAXH.cjs +276 -0
  57. package/dist/{chunk-RHPSE2YR.js → chunk-MQ6MGIQM.js} +2 -2
  58. package/dist/{chunk-PGPFWC35.cjs → chunk-MQGQTKVW.cjs} +51 -33
  59. package/dist/{chunk-5YWWQ36H.cjs → chunk-O3ZVZSNJ.cjs} +9 -9
  60. package/dist/{chunk-GCTUOTCW.cjs → chunk-P3PZ63M3.cjs} +35 -35
  61. package/dist/{chunk-WRZ6C4TU.cjs → chunk-P6WYDBTH.cjs} +4 -4
  62. package/dist/{chunk-CJ6TBL2N.cjs → chunk-RHEMMGCN.cjs} +16 -16
  63. package/dist/{chunk-HIMGBZAM.cjs → chunk-RSIKDWOP.cjs} +201 -38
  64. package/dist/{chunk-SABJMH6M.cjs → chunk-RVGP2XLF.cjs} +2 -135
  65. package/dist/{chunk-JVRLGV3C.js → chunk-SMTUQWGY.js} +184 -21
  66. package/dist/{chunk-DMTEQ6SH.js → chunk-SSFMWJDK.js} +4 -4
  67. package/dist/{chunk-TFF2GTGN.cjs → chunk-SXQHWXEL.cjs} +2 -24
  68. package/dist/{chunk-IWEVV63O.cjs → chunk-UEYLANO3.cjs} +18 -16
  69. package/dist/chunk-VR6OGHU5.js +276 -0
  70. package/dist/{chunk-AF23QK5F.js → chunk-YQ4PQTBB.js} +1 -1
  71. package/dist/{chunk-B7V34GIH.cjs → chunk-YSGW7VV4.cjs} +88 -88
  72. package/dist/{chunk-3NS5C7FB.cjs → chunk-YYBW6DF2.cjs} +76 -76
  73. package/dist/{chunk-UFEDHACQ.js → chunk-Z7BX4TWO.js} +2 -2
  74. package/dist/{chunk-RUONPPMC.cjs → chunk-ZSE25IZA.cjs} +8 -8
  75. package/dist/color/index.cjs +7 -3
  76. package/dist/color/index.js +18 -14
  77. package/dist/export/index.cjs +8 -6
  78. package/dist/export/index.js +7 -5
  79. package/dist/extensions/index.cjs +11 -9
  80. package/dist/extensions/index.js +10 -8
  81. package/dist/gradient/index.cjs +33 -31
  82. package/dist/gradient/index.js +5 -3
  83. package/dist/image-editor/brush-presets.d.cts +4 -1
  84. package/dist/image-editor/brush-presets.d.ts +4 -1
  85. package/dist/image-editor/controller.d.cts +10 -5
  86. package/dist/image-editor/controller.d.ts +10 -5
  87. package/dist/image-editor/index.cjs +1090 -1073
  88. package/dist/image-editor/index.js +37 -20
  89. package/dist/index.cjs +17 -15
  90. package/dist/index.js +11 -9
  91. package/dist/paint-editor/brushes.d.cts +18 -1
  92. package/dist/paint-editor/brushes.d.ts +18 -1
  93. package/dist/paint-editor/collaboration-tools.d.cts +19 -0
  94. package/dist/paint-editor/collaboration-tools.d.ts +19 -0
  95. package/dist/paint-editor/collaboration.d.cts +180 -0
  96. package/dist/paint-editor/collaboration.d.ts +180 -0
  97. package/dist/paint-editor/controller.d.cts +83 -0
  98. package/dist/paint-editor/controller.d.ts +83 -0
  99. package/dist/paint-editor/engine.d.cts +7 -0
  100. package/dist/paint-editor/engine.d.ts +7 -0
  101. package/dist/paint-editor/index.cjs +2027 -1331
  102. package/dist/paint-editor/index.d.cts +2 -0
  103. package/dist/paint-editor/index.d.ts +2 -0
  104. package/dist/paint-editor/index.js +1671 -975
  105. package/dist/paint-editor/mark-settings.d.cts +1 -1
  106. package/dist/paint-editor/mark-settings.d.ts +1 -1
  107. package/dist/paint-editor/procedural.d.cts +4 -2
  108. package/dist/paint-editor/procedural.d.ts +4 -2
  109. package/dist/paint-editor/stamp-tip.d.cts +45 -0
  110. package/dist/paint-editor/stamp-tip.d.ts +45 -0
  111. package/dist/paint-editor/stroke.d.cts +2 -0
  112. package/dist/paint-editor/stroke.d.ts +2 -0
  113. package/dist/stickers/index.cjs +4 -2
  114. package/dist/stickers/index.js +6 -4
  115. package/dist/typography/index.cjs +3 -2
  116. package/dist/typography/index.js +2 -1
  117. package/dist/vector/index.cjs +971 -969
  118. package/dist/vector/index.js +8 -6
  119. package/dist/video-editor/index.cjs +4 -2
  120. package/dist/video-editor/index.js +4 -2
  121. package/package.json +1 -1
  122. package/skills/pixerate-brushes/SKILL.md +21 -11
  123. package/skills/pixerate-brushes-design/SKILL.md +6 -2
  124. package/skills/pixerate-brushes-design/evals/canvas-chalk.json +8 -8
  125. package/skills/pixerate-canvas/SKILL.md +3 -2
  126. package/skills/pixerate-image-editor/SKILL.md +1 -1
  127. package/skills/pixerate-paint-editor/SKILL.md +35 -47
  128. package/skills/pixerate-paint-editor/references/painting-alongside.md +19 -0
  129. package/skills/pixerate-vector/SKILL.md +1 -1
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Painting alongside an agent: a change feed that says what the user (or an agent) just painted,
3
+ * and proposals, the paint an agent suggests without painting it. A proposal is a few operations
4
+ * (strokes, a wash, an echo of strokes already on the canvas) that `previewPaint` renders as a
5
+ * ghost the user sees over the canvas; accepting paints them as one undo step recorded as the
6
+ * proposer's, on a layer of their own, and dismissing changes nothing. The canvas equivalents are
7
+ * `createCanvasChangeFeed` and `createCanvasProposals`.
8
+ */
9
+ import type { RasterRect } from '../raster/surface.cjs';
10
+ import { type PaintCommitEvent, type PaintEditorController } from './controller.cjs';
11
+ import type { PaintEvent } from './document.cjs';
12
+ import type { RecordedStroke } from './strokes.cjs';
13
+ /** A layer as the feed reports it. */
14
+ export interface PaintChangeLayer {
15
+ id: string;
16
+ name: string;
17
+ }
18
+ /** What changed in the painting since the feed's last report. */
19
+ export interface PaintChange {
20
+ /** Counts up from 1 for each change reported. */
21
+ seq: number;
22
+ /** `user`, or `agent:<id>` when an agent's step made it (see `PaintEditorController.withOrigin`). */
23
+ origin: string;
24
+ /** The history step behind it, when there was one. */
25
+ commit?: PaintCommitEvent;
26
+ addedStrokes: RecordedStroke[];
27
+ removedStrokes: RecordedStroke[];
28
+ /** Fills, clears and other recorded operations that aren't strokes. */
29
+ events: PaintEvent[];
30
+ addedLayers: PaintChangeLayer[];
31
+ removedLayers: PaintChangeLayer[];
32
+ /** Layers whose name, visibility, opacity or blend changed, with the fields. */
33
+ changedLayers: (PaintChangeLayer & {
34
+ fields: string[];
35
+ })[];
36
+ /** A stroke is being painted: more of the same gesture will follow. */
37
+ inGesture: boolean;
38
+ /** A stroke started since the last report (reported even when nothing else changed). */
39
+ gestureStarted?: true;
40
+ /** The stroke in progress ended (reported even when it painted nothing). */
41
+ gestureEnded?: true;
42
+ /** Milliseconds since the epoch. */
43
+ at: number;
44
+ }
45
+ export interface PaintChangeFeed {
46
+ /** Compares the painting with the last snapshot and reports the change, if any. Runs on every controller change. */
47
+ check(): PaintChange | null;
48
+ subscribe(listener: (change: PaintChange) => void): () => void;
49
+ /** Takes a new snapshot without reporting the difference (e.g. after opening a document). */
50
+ reset(): void;
51
+ dispose(): void;
52
+ }
53
+ /**
54
+ * What changed in a painting, after each controller change: strokes added or removed (undo
55
+ * included), fills and clears, layers added, removed or changed, and the start and end of each
56
+ * stroke, tagged with who made it. Agents that paint alongside the user read it.
57
+ */
58
+ export declare function createPaintChangeFeed(controller: PaintEditorController, options?: {
59
+ now?: () => number;
60
+ }): PaintChangeFeed;
61
+ /** A stroke to paint: pen samples in document pixels. */
62
+ export interface PaintProposalStrokeOp {
63
+ kind: 'stroke';
64
+ points: {
65
+ x: number;
66
+ y: number;
67
+ pressure?: number;
68
+ }[];
69
+ /** A dry brush from the library (default the editor's brush). */
70
+ brushId?: string;
71
+ /** #rrggbb (default the editor colour). */
72
+ color?: string;
73
+ /** Brush size in document pixels (default the brush's). */
74
+ size?: number;
75
+ }
76
+ /** A wash over the suggestion's whole layer, like a ground or a glaze. */
77
+ export interface PaintProposalWashOp {
78
+ kind: 'wash';
79
+ color: string;
80
+ /** 0..1 (default 0.35). */
81
+ opacity?: number;
82
+ /** Paper grain response 0..1 (default 0.4). */
83
+ grain?: number;
84
+ }
85
+ /** Strokes already on the canvas, painted again moved, turned, scaled or mirrored. */
86
+ export interface PaintProposalEchoOp {
87
+ kind: 'echo';
88
+ strokeIds: string[];
89
+ dx?: number;
90
+ dy?: number;
91
+ /** Degrees clockwise about the strokes' centre (default 0). */
92
+ rotate?: number;
93
+ scale?: number;
94
+ flipX?: boolean;
95
+ /** Repeat the move this many times, each on top of the last (default 1). */
96
+ repeat?: number;
97
+ /** Recolour (default each stroke's colour). */
98
+ color?: string;
99
+ }
100
+ export type PaintProposalOp = PaintProposalStrokeOp | PaintProposalWashOp | PaintProposalEchoOp;
101
+ /** Where accepted paint goes: a new layer of its own (above a layer, on top, or at the bottom, behind everything). */
102
+ export interface PaintProposalPlacement {
103
+ /** Default `top`. `above` needs `layerId`. */
104
+ place?: 'top' | 'bottom' | 'above';
105
+ layerId?: string;
106
+ /** The new layer's name (default the proposal's title). */
107
+ layerName?: string;
108
+ }
109
+ export interface PaintProposalInput extends PaintProposalPlacement {
110
+ /** What the suggestion adds, in a few words. Also the undo step's name once accepted. */
111
+ title: string;
112
+ /** Why it fits what the user is doing, in a sentence. */
113
+ rationale?: string;
114
+ /** Who suggests it (`agent:<id>`); defaults to the store's `origin`. */
115
+ origin?: string;
116
+ ops: PaintProposalOp[];
117
+ }
118
+ export interface PaintProposal {
119
+ id: string;
120
+ title: string;
121
+ rationale?: string;
122
+ origin: string;
123
+ ops: PaintProposalOp[];
124
+ placement: PaintProposalPlacement;
125
+ /** Where the ghost sits (document pixels), or `null` when it painted nothing. */
126
+ bounds: RasterRect | null;
127
+ /** The ghost: the picture as it would look, inside `bounds` (straight RGBA). */
128
+ ghost: Uint8ClampedArray;
129
+ /** Strokes on the canvas it builds on (echo ops). */
130
+ anchors: string[];
131
+ createdAt: number;
132
+ }
133
+ export interface PaintProposalAcceptResult {
134
+ proposalId: string;
135
+ layerId: string;
136
+ /** The strokes it painted. */
137
+ strokeIds: string[];
138
+ /** The undo step's name. */
139
+ label: string;
140
+ }
141
+ export interface PaintProposalsState {
142
+ proposals: readonly PaintProposal[];
143
+ }
144
+ export interface PaintProposals {
145
+ getState(): PaintProposalsState;
146
+ subscribe(listener: (state: PaintProposalsState) => void): () => void;
147
+ /** Validates and previews a suggestion, and adds it to the pending list. Throws, changing nothing, when it can't be painted. */
148
+ propose(input: PaintProposalInput): PaintProposal;
149
+ /** Paints a pending proposal as one undo step recorded as the proposer's. The active layer stays the user's. */
150
+ accept(id: string): PaintProposalAcceptResult;
151
+ /** Drops a pending proposal; the painting is unchanged. */
152
+ reject(id: string): PaintProposal;
153
+ /** Drops every pending proposal (or those of one origin). Returns the ids dropped. */
154
+ clear(origin?: string): string[];
155
+ get(id: string): PaintProposal | undefined;
156
+ /** Strokes an echo builds on that are no longer on the canvas (it can't be accepted until re-proposed). */
157
+ missing(id: string): string[];
158
+ }
159
+ export interface PaintProposalsOptions {
160
+ controller: PaintEditorController;
161
+ /** Origin of proposals that name none (default `agent:anonymous`). */
162
+ origin?: string;
163
+ /** The most points one proposal may paint (default 4000). */
164
+ maxPoints?: number;
165
+ now?: () => number;
166
+ idGenerator?: () => string;
167
+ }
168
+ /** A store of paint an agent suggests (see the module comment). */
169
+ export declare function createPaintProposals(options: PaintProposalsOptions): PaintProposals;
170
+ /**
171
+ * Where to draw a proposal's ghost and its pixels, for a canvas element placed at `bounds` in
172
+ * document pixels (`new ImageData(data, width, height)`), or `null` when it paints nothing.
173
+ */
174
+ export declare function paintProposalGhost(proposal: PaintProposal): {
175
+ x: number;
176
+ y: number;
177
+ width: number;
178
+ height: number;
179
+ data: Uint8ClampedArray;
180
+ } | null;
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Painting alongside an agent: a change feed that says what the user (or an agent) just painted,
3
+ * and proposals, the paint an agent suggests without painting it. A proposal is a few operations
4
+ * (strokes, a wash, an echo of strokes already on the canvas) that `previewPaint` renders as a
5
+ * ghost the user sees over the canvas; accepting paints them as one undo step recorded as the
6
+ * proposer's, on a layer of their own, and dismissing changes nothing. The canvas equivalents are
7
+ * `createCanvasChangeFeed` and `createCanvasProposals`.
8
+ */
9
+ import type { RasterRect } from '../raster/surface.js';
10
+ import { type PaintCommitEvent, type PaintEditorController } from './controller.js';
11
+ import type { PaintEvent } from './document.js';
12
+ import type { RecordedStroke } from './strokes.js';
13
+ /** A layer as the feed reports it. */
14
+ export interface PaintChangeLayer {
15
+ id: string;
16
+ name: string;
17
+ }
18
+ /** What changed in the painting since the feed's last report. */
19
+ export interface PaintChange {
20
+ /** Counts up from 1 for each change reported. */
21
+ seq: number;
22
+ /** `user`, or `agent:<id>` when an agent's step made it (see `PaintEditorController.withOrigin`). */
23
+ origin: string;
24
+ /** The history step behind it, when there was one. */
25
+ commit?: PaintCommitEvent;
26
+ addedStrokes: RecordedStroke[];
27
+ removedStrokes: RecordedStroke[];
28
+ /** Fills, clears and other recorded operations that aren't strokes. */
29
+ events: PaintEvent[];
30
+ addedLayers: PaintChangeLayer[];
31
+ removedLayers: PaintChangeLayer[];
32
+ /** Layers whose name, visibility, opacity or blend changed, with the fields. */
33
+ changedLayers: (PaintChangeLayer & {
34
+ fields: string[];
35
+ })[];
36
+ /** A stroke is being painted: more of the same gesture will follow. */
37
+ inGesture: boolean;
38
+ /** A stroke started since the last report (reported even when nothing else changed). */
39
+ gestureStarted?: true;
40
+ /** The stroke in progress ended (reported even when it painted nothing). */
41
+ gestureEnded?: true;
42
+ /** Milliseconds since the epoch. */
43
+ at: number;
44
+ }
45
+ export interface PaintChangeFeed {
46
+ /** Compares the painting with the last snapshot and reports the change, if any. Runs on every controller change. */
47
+ check(): PaintChange | null;
48
+ subscribe(listener: (change: PaintChange) => void): () => void;
49
+ /** Takes a new snapshot without reporting the difference (e.g. after opening a document). */
50
+ reset(): void;
51
+ dispose(): void;
52
+ }
53
+ /**
54
+ * What changed in a painting, after each controller change: strokes added or removed (undo
55
+ * included), fills and clears, layers added, removed or changed, and the start and end of each
56
+ * stroke, tagged with who made it. Agents that paint alongside the user read it.
57
+ */
58
+ export declare function createPaintChangeFeed(controller: PaintEditorController, options?: {
59
+ now?: () => number;
60
+ }): PaintChangeFeed;
61
+ /** A stroke to paint: pen samples in document pixels. */
62
+ export interface PaintProposalStrokeOp {
63
+ kind: 'stroke';
64
+ points: {
65
+ x: number;
66
+ y: number;
67
+ pressure?: number;
68
+ }[];
69
+ /** A dry brush from the library (default the editor's brush). */
70
+ brushId?: string;
71
+ /** #rrggbb (default the editor colour). */
72
+ color?: string;
73
+ /** Brush size in document pixels (default the brush's). */
74
+ size?: number;
75
+ }
76
+ /** A wash over the suggestion's whole layer, like a ground or a glaze. */
77
+ export interface PaintProposalWashOp {
78
+ kind: 'wash';
79
+ color: string;
80
+ /** 0..1 (default 0.35). */
81
+ opacity?: number;
82
+ /** Paper grain response 0..1 (default 0.4). */
83
+ grain?: number;
84
+ }
85
+ /** Strokes already on the canvas, painted again moved, turned, scaled or mirrored. */
86
+ export interface PaintProposalEchoOp {
87
+ kind: 'echo';
88
+ strokeIds: string[];
89
+ dx?: number;
90
+ dy?: number;
91
+ /** Degrees clockwise about the strokes' centre (default 0). */
92
+ rotate?: number;
93
+ scale?: number;
94
+ flipX?: boolean;
95
+ /** Repeat the move this many times, each on top of the last (default 1). */
96
+ repeat?: number;
97
+ /** Recolour (default each stroke's colour). */
98
+ color?: string;
99
+ }
100
+ export type PaintProposalOp = PaintProposalStrokeOp | PaintProposalWashOp | PaintProposalEchoOp;
101
+ /** Where accepted paint goes: a new layer of its own (above a layer, on top, or at the bottom, behind everything). */
102
+ export interface PaintProposalPlacement {
103
+ /** Default `top`. `above` needs `layerId`. */
104
+ place?: 'top' | 'bottom' | 'above';
105
+ layerId?: string;
106
+ /** The new layer's name (default the proposal's title). */
107
+ layerName?: string;
108
+ }
109
+ export interface PaintProposalInput extends PaintProposalPlacement {
110
+ /** What the suggestion adds, in a few words. Also the undo step's name once accepted. */
111
+ title: string;
112
+ /** Why it fits what the user is doing, in a sentence. */
113
+ rationale?: string;
114
+ /** Who suggests it (`agent:<id>`); defaults to the store's `origin`. */
115
+ origin?: string;
116
+ ops: PaintProposalOp[];
117
+ }
118
+ export interface PaintProposal {
119
+ id: string;
120
+ title: string;
121
+ rationale?: string;
122
+ origin: string;
123
+ ops: PaintProposalOp[];
124
+ placement: PaintProposalPlacement;
125
+ /** Where the ghost sits (document pixels), or `null` when it painted nothing. */
126
+ bounds: RasterRect | null;
127
+ /** The ghost: the picture as it would look, inside `bounds` (straight RGBA). */
128
+ ghost: Uint8ClampedArray;
129
+ /** Strokes on the canvas it builds on (echo ops). */
130
+ anchors: string[];
131
+ createdAt: number;
132
+ }
133
+ export interface PaintProposalAcceptResult {
134
+ proposalId: string;
135
+ layerId: string;
136
+ /** The strokes it painted. */
137
+ strokeIds: string[];
138
+ /** The undo step's name. */
139
+ label: string;
140
+ }
141
+ export interface PaintProposalsState {
142
+ proposals: readonly PaintProposal[];
143
+ }
144
+ export interface PaintProposals {
145
+ getState(): PaintProposalsState;
146
+ subscribe(listener: (state: PaintProposalsState) => void): () => void;
147
+ /** Validates and previews a suggestion, and adds it to the pending list. Throws, changing nothing, when it can't be painted. */
148
+ propose(input: PaintProposalInput): PaintProposal;
149
+ /** Paints a pending proposal as one undo step recorded as the proposer's. The active layer stays the user's. */
150
+ accept(id: string): PaintProposalAcceptResult;
151
+ /** Drops a pending proposal; the painting is unchanged. */
152
+ reject(id: string): PaintProposal;
153
+ /** Drops every pending proposal (or those of one origin). Returns the ids dropped. */
154
+ clear(origin?: string): string[];
155
+ get(id: string): PaintProposal | undefined;
156
+ /** Strokes an echo builds on that are no longer on the canvas (it can't be accepted until re-proposed). */
157
+ missing(id: string): string[];
158
+ }
159
+ export interface PaintProposalsOptions {
160
+ controller: PaintEditorController;
161
+ /** Origin of proposals that name none (default `agent:anonymous`). */
162
+ origin?: string;
163
+ /** The most points one proposal may paint (default 4000). */
164
+ maxPoints?: number;
165
+ now?: () => number;
166
+ idGenerator?: () => string;
167
+ }
168
+ /** A store of paint an agent suggests (see the module comment). */
169
+ export declare function createPaintProposals(options: PaintProposalsOptions): PaintProposals;
170
+ /**
171
+ * Where to draw a proposal's ghost and its pixels, for a canvas element placed at `bounds` in
172
+ * document pixels (`new ImageData(data, width, height)`), or `null` when it paints nothing.
173
+ */
174
+ export declare function paintProposalGhost(proposal: PaintProposal): {
175
+ x: number;
176
+ y: number;
177
+ width: number;
178
+ height: number;
179
+ data: Uint8ClampedArray;
180
+ } | null;
@@ -22,6 +22,7 @@ import { type PaintBrushInput, type PaintBrush } from './brushes.cjs';
22
22
  import { type BrushLibrary, type BrushLibraryFile } from '../brushes/library.cjs';
23
23
  import { type BrushPatch, type UnsettableBrushFacet } from '../brushes/patch.cjs';
24
24
  import type { Brush } from '../brushes/types.cjs';
25
+ import type { ColorScheme } from '../color/dual.cjs';
25
26
  import { type PaintCanvas, type PaintDocumentState, type PaintLayer } from './document.cjs';
26
27
  import { type ImageEncoder, type ImageFormat } from './encode.cjs';
27
28
  import type { PenPoint } from './engine.cjs';
@@ -67,6 +68,11 @@ export interface PaintEditorOptions extends Partial<PaintCanvas> {
67
68
  brushLibrary?: BrushLibrary;
68
69
  /** Saved user brushes to start the default library with (or to add to `brushLibrary`). */
69
70
  brushes?: readonly PaintBrush[];
71
+ /**
72
+ * The host's colour scheme (default `light`): a brush's light / dark default colour resolves to
73
+ * its side when the brush is chosen and when a stroke starts. Follow the host with `setColorScheme`.
74
+ */
75
+ colorScheme?: ColorScheme;
70
76
  /** Paint colour #rrggbb (default #1f2937). */
71
77
  color?: string;
72
78
  hand?: Hand;
@@ -114,6 +120,10 @@ export interface PaintEditorState {
114
120
  brushes: readonly PaintBrush[];
115
121
  /** Paint colour #rrggbb. */
116
122
  color: string;
123
+ /** The colour scheme light / dark brush colours resolve for. */
124
+ colorScheme: ColorScheme;
125
+ /** Locked: choosing a brush with a default colour keeps the paint colour. */
126
+ colorLocked: boolean;
117
127
  /** The second colour (swap with `swapColors`). */
118
128
  secondaryColor: string;
119
129
  /**
@@ -194,6 +204,29 @@ export interface PaintLayerInfo {
194
204
  /** How many live layer effects (glows, shadows, strokes…) it has, when any (`getLayerEffects`). */
195
205
  effects?: number;
196
206
  }
207
+ /** One change to the paint history, for hosts and change feeds (`onCommit`). */
208
+ export interface PaintCommitEvent {
209
+ /** `execute`: a new undo step. `merge`: folded into the last one (a slider drag). */
210
+ kind: 'execute' | 'merge' | 'undo' | 'redo';
211
+ /** The step's name. */
212
+ label: string;
213
+ /** Who made the step: `user`, or `agent:<id>` (see `withOrigin`). */
214
+ origin: string;
215
+ }
216
+ /** Paint a preview produced (`previewPaint`), in document coordinates, straight RGBA. */
217
+ export interface PaintPreview {
218
+ /** Where the new paint sits, or `null` when nothing was painted. */
219
+ bounds: RasterRect | null;
220
+ /** The new paint alone: `bounds.width × bounds.height` pixels (empty when nothing was painted). */
221
+ pixels: Uint8ClampedArray;
222
+ /** The whole picture as it would look with the new paint, inside `bounds` (with the paper; empty when nothing was painted). */
223
+ composite: Uint8ClampedArray;
224
+ }
225
+ /** Where `previewPaint` puts its layer: above a layer (default the active one), or at the bottom of the stack. */
226
+ export interface PaintPreviewPlacement {
227
+ aboveId?: string;
228
+ bottom?: boolean;
229
+ }
197
230
  export interface PaintStrokeInit {
198
231
  /** Default the active layer. */
199
232
  layerId?: string;
@@ -337,6 +370,13 @@ export declare class PaintEditorController {
337
370
  private stickerPreview;
338
371
  private readonly encodeImage;
339
372
  private listeners;
373
+ private commitListeners;
374
+ /** Origins set by `withOrigin`, innermost last. */
375
+ private origins;
376
+ /** `group` in progress: commits fold into one step, recorded when it ends. */
377
+ private grouping;
378
+ /** `previewPaint` in progress: nothing is emitted or recorded. */
379
+ private previewing;
340
380
  private version;
341
381
  private cached;
342
382
  private session;
@@ -393,6 +433,30 @@ export declare class PaintEditorController {
393
433
  private paperEffectNodeCache;
394
434
  constructor(options?: PaintEditorOptions);
395
435
  subscribe(listener: () => void): () => void;
436
+ /** Hears each undo step recorded, merged, undone or redone, with who made it. Returns an unsubscribe function. */
437
+ onCommit(listener: (event: PaintCommitEvent) => void): () => void;
438
+ private notifyCommit;
439
+ /** The origin steps are recorded with now: the innermost `withOrigin`, else `user`. */
440
+ getOrigin(): string;
441
+ /**
442
+ * Records the steps `fn` makes as made by `origin` (`agent:<id>` for an agent). When `fn` returns
443
+ * a promise, the origin holds until it settles. Satisfies `createOriginMiddleware`'s history target,
444
+ * so agent tool calls are recorded as the calling agent's.
445
+ */
446
+ withOrigin<T>(origin: string, fn: () => T): T;
447
+ /**
448
+ * Runs `fn` as one undo step named `label`: every change it makes (strokes, fills, new layers) is
449
+ * undone together. Wet paint can't be grouped. Nested groups join the outer one. If `fn` throws,
450
+ * its changes are put back and nothing is recorded.
451
+ */
452
+ group<T>(label: string, fn: () => T): T;
453
+ /**
454
+ * What `fn` would paint, without changing anything: it runs on a new empty layer (above the
455
+ * active layer, another one, or at the bottom), and the layer's pixels and the picture with them
456
+ * come back while the document, history and listeners stay as they were. `fn` gets that layer's id and must paint on it with dry brushes (strokes, replays and
457
+ * fills); wet paint can't be previewed. For ghost paint an agent suggests (`createPaintProposals`).
458
+ */
459
+ previewPaint(fn: (layerId: string) => void, placement?: PaintPreviewPlacement): PaintPreview;
396
460
  private emit;
397
461
  /** A snapshot of the editor (the same object until something changes). */
398
462
  getState(): PaintEditorState;
@@ -771,6 +835,25 @@ export declare class PaintEditorController {
771
835
  height?: number;
772
836
  color?: string;
773
837
  }): BrushThumbnail;
838
+ private colorScheme;
839
+ private colorLocked;
840
+ /** The brush default colour the paint colour came from (resolved again when the scheme changes), or `null`. */
841
+ private brushColour;
842
+ /**
843
+ * The host's colour scheme: a paint colour that came from a brush's light / dark default
844
+ * resolves to the new side. Editor state, not an undo step.
845
+ */
846
+ setColorScheme(scheme: ColorScheme): void;
847
+ /** Locks the paint colour: choosing a brush with a default colour keeps it. Editor state, not an undo step. */
848
+ setColorLocked(locked: boolean): void;
849
+ /**
850
+ * After the user chooses a brush: its solid or light / dark default colour becomes the paint
851
+ * colour (resolved for the colour scheme), unless the colour is locked. Gradient defaults are the
852
+ * brush's own paint already; effect defaults stay a suggestion.
853
+ */
854
+ private takeBrushColour;
855
+ /** A brush that always paints its own colour (`colour.own`): that colour, resolved for the scheme now; else `null`. */
856
+ private ownColour;
774
857
  /** Sets the paint colour (and goes back to one colour from gradient paint). */
775
858
  setColor(color: string): string;
776
859
  /**
@@ -22,6 +22,7 @@ import { type PaintBrushInput, type PaintBrush } from './brushes.js';
22
22
  import { type BrushLibrary, type BrushLibraryFile } from '../brushes/library.js';
23
23
  import { type BrushPatch, type UnsettableBrushFacet } from '../brushes/patch.js';
24
24
  import type { Brush } from '../brushes/types.js';
25
+ import type { ColorScheme } from '../color/dual.js';
25
26
  import { type PaintCanvas, type PaintDocumentState, type PaintLayer } from './document.js';
26
27
  import { type ImageEncoder, type ImageFormat } from './encode.js';
27
28
  import type { PenPoint } from './engine.js';
@@ -67,6 +68,11 @@ export interface PaintEditorOptions extends Partial<PaintCanvas> {
67
68
  brushLibrary?: BrushLibrary;
68
69
  /** Saved user brushes to start the default library with (or to add to `brushLibrary`). */
69
70
  brushes?: readonly PaintBrush[];
71
+ /**
72
+ * The host's colour scheme (default `light`): a brush's light / dark default colour resolves to
73
+ * its side when the brush is chosen and when a stroke starts. Follow the host with `setColorScheme`.
74
+ */
75
+ colorScheme?: ColorScheme;
70
76
  /** Paint colour #rrggbb (default #1f2937). */
71
77
  color?: string;
72
78
  hand?: Hand;
@@ -114,6 +120,10 @@ export interface PaintEditorState {
114
120
  brushes: readonly PaintBrush[];
115
121
  /** Paint colour #rrggbb. */
116
122
  color: string;
123
+ /** The colour scheme light / dark brush colours resolve for. */
124
+ colorScheme: ColorScheme;
125
+ /** Locked: choosing a brush with a default colour keeps the paint colour. */
126
+ colorLocked: boolean;
117
127
  /** The second colour (swap with `swapColors`). */
118
128
  secondaryColor: string;
119
129
  /**
@@ -194,6 +204,29 @@ export interface PaintLayerInfo {
194
204
  /** How many live layer effects (glows, shadows, strokes…) it has, when any (`getLayerEffects`). */
195
205
  effects?: number;
196
206
  }
207
+ /** One change to the paint history, for hosts and change feeds (`onCommit`). */
208
+ export interface PaintCommitEvent {
209
+ /** `execute`: a new undo step. `merge`: folded into the last one (a slider drag). */
210
+ kind: 'execute' | 'merge' | 'undo' | 'redo';
211
+ /** The step's name. */
212
+ label: string;
213
+ /** Who made the step: `user`, or `agent:<id>` (see `withOrigin`). */
214
+ origin: string;
215
+ }
216
+ /** Paint a preview produced (`previewPaint`), in document coordinates, straight RGBA. */
217
+ export interface PaintPreview {
218
+ /** Where the new paint sits, or `null` when nothing was painted. */
219
+ bounds: RasterRect | null;
220
+ /** The new paint alone: `bounds.width × bounds.height` pixels (empty when nothing was painted). */
221
+ pixels: Uint8ClampedArray;
222
+ /** The whole picture as it would look with the new paint, inside `bounds` (with the paper; empty when nothing was painted). */
223
+ composite: Uint8ClampedArray;
224
+ }
225
+ /** Where `previewPaint` puts its layer: above a layer (default the active one), or at the bottom of the stack. */
226
+ export interface PaintPreviewPlacement {
227
+ aboveId?: string;
228
+ bottom?: boolean;
229
+ }
197
230
  export interface PaintStrokeInit {
198
231
  /** Default the active layer. */
199
232
  layerId?: string;
@@ -337,6 +370,13 @@ export declare class PaintEditorController {
337
370
  private stickerPreview;
338
371
  private readonly encodeImage;
339
372
  private listeners;
373
+ private commitListeners;
374
+ /** Origins set by `withOrigin`, innermost last. */
375
+ private origins;
376
+ /** `group` in progress: commits fold into one step, recorded when it ends. */
377
+ private grouping;
378
+ /** `previewPaint` in progress: nothing is emitted or recorded. */
379
+ private previewing;
340
380
  private version;
341
381
  private cached;
342
382
  private session;
@@ -393,6 +433,30 @@ export declare class PaintEditorController {
393
433
  private paperEffectNodeCache;
394
434
  constructor(options?: PaintEditorOptions);
395
435
  subscribe(listener: () => void): () => void;
436
+ /** Hears each undo step recorded, merged, undone or redone, with who made it. Returns an unsubscribe function. */
437
+ onCommit(listener: (event: PaintCommitEvent) => void): () => void;
438
+ private notifyCommit;
439
+ /** The origin steps are recorded with now: the innermost `withOrigin`, else `user`. */
440
+ getOrigin(): string;
441
+ /**
442
+ * Records the steps `fn` makes as made by `origin` (`agent:<id>` for an agent). When `fn` returns
443
+ * a promise, the origin holds until it settles. Satisfies `createOriginMiddleware`'s history target,
444
+ * so agent tool calls are recorded as the calling agent's.
445
+ */
446
+ withOrigin<T>(origin: string, fn: () => T): T;
447
+ /**
448
+ * Runs `fn` as one undo step named `label`: every change it makes (strokes, fills, new layers) is
449
+ * undone together. Wet paint can't be grouped. Nested groups join the outer one. If `fn` throws,
450
+ * its changes are put back and nothing is recorded.
451
+ */
452
+ group<T>(label: string, fn: () => T): T;
453
+ /**
454
+ * What `fn` would paint, without changing anything: it runs on a new empty layer (above the
455
+ * active layer, another one, or at the bottom), and the layer's pixels and the picture with them
456
+ * come back while the document, history and listeners stay as they were. `fn` gets that layer's id and must paint on it with dry brushes (strokes, replays and
457
+ * fills); wet paint can't be previewed. For ghost paint an agent suggests (`createPaintProposals`).
458
+ */
459
+ previewPaint(fn: (layerId: string) => void, placement?: PaintPreviewPlacement): PaintPreview;
396
460
  private emit;
397
461
  /** A snapshot of the editor (the same object until something changes). */
398
462
  getState(): PaintEditorState;
@@ -771,6 +835,25 @@ export declare class PaintEditorController {
771
835
  height?: number;
772
836
  color?: string;
773
837
  }): BrushThumbnail;
838
+ private colorScheme;
839
+ private colorLocked;
840
+ /** The brush default colour the paint colour came from (resolved again when the scheme changes), or `null`. */
841
+ private brushColour;
842
+ /**
843
+ * The host's colour scheme: a paint colour that came from a brush's light / dark default
844
+ * resolves to the new side. Editor state, not an undo step.
845
+ */
846
+ setColorScheme(scheme: ColorScheme): void;
847
+ /** Locks the paint colour: choosing a brush with a default colour keeps it. Editor state, not an undo step. */
848
+ setColorLocked(locked: boolean): void;
849
+ /**
850
+ * After the user chooses a brush: its solid or light / dark default colour becomes the paint
851
+ * colour (resolved for the colour scheme), unless the colour is locked. Gradient defaults are the
852
+ * brush's own paint already; effect defaults stay a suggestion.
853
+ */
854
+ private takeBrushColour;
855
+ /** A brush that always paints its own colour (`colour.own`): that colour, resolved for the scheme now; else `null`. */
856
+ private ownColour;
774
857
  /** Sets the paint colour (and goes back to one colour from gradient paint). */
775
858
  setColor(color: string): string;
776
859
  /**
@@ -18,6 +18,7 @@ import { CoverageMap, type BrushSettings, type Dab, type StrokePoint } from '../
18
18
  import type { RasterRect } from '../raster/surface.cjs';
19
19
  import type { PaintBrush } from './brushes.cjs';
20
20
  import { type EffectPaintPixels } from './effect-paint.cjs';
21
+ import type { StickerArtLookup } from './sticker-art.cjs';
21
22
  import { type PaperGrain } from './paper.cjs';
22
23
  /** One pen sample in document pixels. Every channel but x and y is optional. */
23
24
  export interface PenPoint {
@@ -126,6 +127,8 @@ export interface DryStrokeOptions {
126
127
  copies?: readonly StrokeCopy[];
127
128
  /** Effect paint: each pixel takes its colour (and alpha) from these canvas-sized pixels. */
128
129
  effect?: EffectPaintPixels | null;
130
+ /** Loaded sticker art, for a stamp tip of a sticker from the sticker library (`brush.stamp`); without it such a tip paints round. */
131
+ stampArt?: StickerArtLookup;
129
132
  }
130
133
  /**
131
134
  * One dab-engine stroke (dab and eraser brushes): smoothing, the dab walk of every copy, grain and
@@ -144,12 +147,16 @@ export declare class DryStroke {
144
147
  private field;
145
148
  private lastRaw;
146
149
  private lastFed;
150
+ /** The stamp tip's shape, or `null` for the round computed tip. */
151
+ private readonly stamp;
147
152
  constructor(brush: PaintBrush, options: DryStrokeOptions);
148
153
  /** Feeds raw pen samples; returns the document rectangles the new dabs touched. */
149
154
  push(points: readonly PenPoint[]): RasterRect[];
150
155
  /** Ends the stroke: the smoothed path catches up with the last raw sample; a single tap paints one dab. */
151
156
  finish(): RasterRect[];
152
157
  private paint;
158
+ /** Lays one mark (a dab, or a spray droplet of dab `d`): the round tip or the stamp, the paper grain, effect paint. */
159
+ private mark;
153
160
  }
154
161
  /** The shared engine's tip for the Mixer Brush engine (it smooths and walks by itself, so smoothing is applied before). */
155
162
  export declare function mixerTip(brush: PaintBrush): BrushSettings;