@pixerate/editor 0.102.0 → 0.105.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.
- package/THIRD_PARTY_NOTICES.md +3 -0
- package/dist/agent/index.cjs +7 -7
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.js +7 -7
- package/dist/canvas/index.cjs +3 -3
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.js +4 -4
- package/dist/chunk-4X5MIVCE.js +8036 -0
- package/dist/chunk-4X5MIVCE.js.map +1 -0
- package/dist/chunk-6BU5WU4R.cjs +1 -0
- package/dist/chunk-6BU5WU4R.cjs.map +1 -0
- package/dist/{chunk-IB53LVGV.js → chunk-DS7KDKPX.js} +1 -1
- package/dist/{chunk-ZZOTHJ4K.cjs → chunk-PV42JITT.cjs} +1 -1
- package/dist/{chunk-ZZOTHJ4K.cjs.map → chunk-PV42JITT.cjs.map} +1 -1
- package/dist/chunk-XYBJKUAU.js +1 -0
- package/dist/chunk-XYBJKUAU.js.map +1 -0
- package/dist/chunk-ZID226ZG.cjs +8036 -0
- package/dist/chunk-ZID226ZG.cjs.map +1 -0
- package/dist/compositor-Bc3PNP3C.d.cts +835 -0
- package/dist/compositor-BsYYllvR.d.ts +835 -0
- package/dist/export-_m3Es4ru.d.cts +3154 -0
- package/dist/export-iMrop3Jm.d.ts +3154 -0
- package/dist/homography-CZ-tFjay.d.cts +50 -0
- package/dist/homography-Ctfjoc1b.d.ts +50 -0
- package/dist/image-editor/index.cjs +2873 -9450
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +31 -3150
- package/dist/image-editor/index.d.ts +31 -3150
- package/dist/image-editor/index.js +2550 -9127
- package/dist/image-editor/index.js.map +1 -1
- package/dist/mixer-C1M1Nua_.d.cts +282 -0
- package/dist/mixer-Cu5a9Bzd.d.ts +282 -0
- package/dist/paint/index.cjs +4 -3
- package/dist/paint/index.cjs.map +1 -1
- package/dist/paint/index.d.cts +3 -281
- package/dist/paint/index.d.ts +3 -281
- package/dist/paint/index.js +2 -1
- package/dist/paint-editor/index.cjs +4733 -0
- package/dist/paint-editor/index.cjs.map +1 -0
- package/dist/paint-editor/index.d.cts +1680 -0
- package/dist/paint-editor/index.d.ts +1680 -0
- package/dist/paint-editor/index.js +4733 -0
- package/dist/paint-editor/index.js.map +1 -0
- package/dist/raster/index.d.cts +6 -835
- package/dist/raster/index.d.ts +6 -835
- package/dist/{surface-DIA-XatS.d.cts → surface-BBJN9wxg.d.cts} +1 -1
- package/dist/{surface-DIA-XatS.d.ts → surface-BBJN9wxg.d.ts} +1 -1
- package/dist/tools/index.cjs +3 -3
- package/dist/tools/index.cjs.map +1 -1
- package/dist/tools/index.js +3 -3
- package/dist/vector/index.d.cts +2 -1
- package/dist/vector/index.d.ts +2 -1
- package/dist/{warp-B8rAguzs.d.ts → warp-CWHZzE3x.d.ts} +2 -49
- package/dist/{warp-nYs2DPrx.d.cts → warp-D5jz_tN7.d.cts} +2 -49
- package/package.json +6 -1
- package/skills/README.md +1 -0
- package/skills/pixerate-image-editor/SKILL.md +14 -5
- package/skills/pixerate-paint-editor/SKILL.md +203 -0
- /package/dist/{chunk-IB53LVGV.js.map → chunk-DS7KDKPX.js.map} +0 -0
|
@@ -0,0 +1,1680 @@
|
|
|
1
|
+
import { R as RasterLayer, a as RasterGroupLayer, b as RasterPixelLayer, C as CompositorLayer, c as RasterCompositor, d as RasterSources, L as LayerLocks } from '../compositor-Bc3PNP3C.cjs';
|
|
2
|
+
import { B as BrushSettings, M as MixerSettings, C as CoverageMap, D as Dab, S as StrokePoint, a as MixerLoad } from '../mixer-C1M1Nua_.cjs';
|
|
3
|
+
export { b as MAX_BRUSH_SIZE } from '../mixer-C1M1Nua_.cjs';
|
|
4
|
+
import { R as RasterRect, a as TiledSurface, B as BlendMode } from '../surface-BBJN9wxg.cjs';
|
|
5
|
+
export { b as BLEND_MODES, c as Tile, i as isBlendMode } from '../surface-BBJN9wxg.cjs';
|
|
6
|
+
import { I as ImageEditorAssetStore, S as SerializedLayer, P as PsdExportResult } from '../export-_m3Es4ru.cjs';
|
|
7
|
+
import { a as AgentToolProvider } from '../registry-BqA1AZt0.cjs';
|
|
8
|
+
import '../types-CwMPoRMU.cjs';
|
|
9
|
+
import '../warp-D5jz_tN7.cjs';
|
|
10
|
+
import '../schema-Di0CS6Pe.cjs';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Seeded, deterministic noise for paper grain and brush jitter (photocraft's counter-based
|
|
14
|
+
* randomness and tileable value noise, on 32-bit integer hashes instead of 64-bit ones so it runs
|
|
15
|
+
* fast in JavaScript). Every value is a pure function of its inputs, so a paper or a stroke
|
|
16
|
+
* regenerates identically on every machine.
|
|
17
|
+
*/
|
|
18
|
+
/** A 32-bit finaliser (murmur3's `fmix32`). */
|
|
19
|
+
declare function mix32(h: number): number;
|
|
20
|
+
/** Hash of integer coordinates and a salt to [0, 1). */
|
|
21
|
+
declare function hash2(x: number, y: number, salt: number): number;
|
|
22
|
+
/** Uniform in [0, 1) from a seed, an index and a stream (one stream per random quantity). */
|
|
23
|
+
declare function rand01(seed: number, index: number, stream: number): number;
|
|
24
|
+
/** Uniform in [-1, 1). */
|
|
25
|
+
declare function randSigned(seed: number, index: number, stream: number): number;
|
|
26
|
+
/** A seed from a string (FNV-1a, then mixed). */
|
|
27
|
+
declare function seedFromString(text: string): number;
|
|
28
|
+
/**
|
|
29
|
+
* Tileable fractal value noise in 0..1 over a `size`-pixel square: `cellsX × cellsY` lattice cells
|
|
30
|
+
* at the base octave (unequal counts give fibres and streaks), doubling per octave, so the result
|
|
31
|
+
* repeats exactly every `size` pixels.
|
|
32
|
+
*/
|
|
33
|
+
declare class TileableNoise {
|
|
34
|
+
readonly size: number;
|
|
35
|
+
private readonly octaves;
|
|
36
|
+
constructor(size: number, cellsX: number, cellsY: number, octaves: number, salt: number);
|
|
37
|
+
/** Fractal noise at pixel (x, y). */
|
|
38
|
+
at(x: number, y: number): number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Paper: a generated grain height map (0 = valley, 1 = peak) that tiles across the canvas, a paper
|
|
43
|
+
* colour, grain depth, and the three properties wet media will read later (absorption,
|
|
44
|
+
* permeability, roughness). Dry media catch the peaks: a light stroke only marks the high grain, a
|
|
45
|
+
* firm one fills the valleys too. Everything is generated from a pattern and a seed, so a paper is
|
|
46
|
+
* a few numbers in the document, never a bitmap, and no third-party texture is bundled.
|
|
47
|
+
*/
|
|
48
|
+
/** Grain generators. Each is our own procedural pattern (no scanned textures). */
|
|
49
|
+
type GrainPattern = 'smooth' | 'cold-pressed' | 'hot-pressed' | 'rough' | 'vellum' | 'laid' | 'ingres' | 'sanded' | 'canvas' | 'linen' | 'gesso';
|
|
50
|
+
declare const GRAIN_PATTERNS: readonly GrainPattern[];
|
|
51
|
+
/** Paper families, as a library groups them. */
|
|
52
|
+
type PaperFamily = 'watercolor' | 'drawing' | 'pastel' | 'canvas' | 'panel' | 'smooth';
|
|
53
|
+
declare const PAPER_FAMILIES: readonly PaperFamily[];
|
|
54
|
+
/** How a height map is tuned for one kind of media. */
|
|
55
|
+
interface GrainTuning {
|
|
56
|
+
/** Height contrast around the middle, 0..3 (default 1). */
|
|
57
|
+
contrast: number;
|
|
58
|
+
/** Swap peaks and valleys (default false). */
|
|
59
|
+
invert: boolean;
|
|
60
|
+
}
|
|
61
|
+
type PaperTiling = 'repeat' | 'mirror';
|
|
62
|
+
interface PaperSettings {
|
|
63
|
+
/** The library preset this paper started from (`custom` when it matches none). */
|
|
64
|
+
preset: string;
|
|
65
|
+
name: string;
|
|
66
|
+
family: PaperFamily;
|
|
67
|
+
pattern: GrainPattern;
|
|
68
|
+
/** Grain seed: another seed gives another sheet of the same paper. */
|
|
69
|
+
seed: number;
|
|
70
|
+
/** Paper colour #rrggbb. */
|
|
71
|
+
color: string;
|
|
72
|
+
/** Grain scale: 1 is the pattern's natural size, 2 twice as coarse (0.25..8). */
|
|
73
|
+
scale: number;
|
|
74
|
+
/** How the grain tile repeats: `repeat` or `mirror` (alternate tiles flipped, so seams vanish). */
|
|
75
|
+
tiling: PaperTiling;
|
|
76
|
+
/** Grain depth 0..1: how much grain-responsive dry media skip the valleys (0: none). */
|
|
77
|
+
depth: number;
|
|
78
|
+
/** How much the grain shades the paper colour on screen and in exports, 0..1. */
|
|
79
|
+
shading: number;
|
|
80
|
+
/** The height map as dry media read it. */
|
|
81
|
+
dry: GrainTuning;
|
|
82
|
+
/** The height map as wet media will read it (stored now; wet media is a later phase). */
|
|
83
|
+
wet: GrainTuning;
|
|
84
|
+
/** Wet media: how fast the paper takes up water, 0..1. */
|
|
85
|
+
absorption: number;
|
|
86
|
+
/** Wet media: how far water spreads inside the paper, 0..1. */
|
|
87
|
+
permeability: number;
|
|
88
|
+
/** Wet media: how much the grain catches pigment, 0..1. */
|
|
89
|
+
roughness: number;
|
|
90
|
+
}
|
|
91
|
+
/** Side of the generated grain tile in pixels (at scale 1). */
|
|
92
|
+
declare const GRAIN_TILE = 256;
|
|
93
|
+
type PaperPreset = PaperSettings;
|
|
94
|
+
/** The built-in paper library: generated papers in families. */
|
|
95
|
+
declare const PAPER_LIBRARY: readonly PaperPreset[];
|
|
96
|
+
declare const DEFAULT_PAPER: Readonly<PaperSettings>;
|
|
97
|
+
/** A paper from the library by id, or `undefined`. */
|
|
98
|
+
declare function paperPreset(id: string): PaperSettings | undefined;
|
|
99
|
+
declare function clonePaper(p: PaperSettings): PaperSettings;
|
|
100
|
+
/**
|
|
101
|
+
* A paper with every field in range. Missing fields come from `base` (default: the library preset
|
|
102
|
+
* named by `raw.preset`, else cold pressed).
|
|
103
|
+
*/
|
|
104
|
+
declare function normalizePaper(raw?: Partial<PaperSettings> | Record<string, unknown>, base?: PaperSettings): PaperSettings;
|
|
105
|
+
/** The raw height map of a pattern (`GRAIN_TILE` square, 0..1), generated once per pattern and seed. */
|
|
106
|
+
declare function grainHeights(pattern: GrainPattern, seed: number, size?: number): Float32Array;
|
|
107
|
+
/**
|
|
108
|
+
* A paper's height field over the canvas (document pixels), tuned for one kind of media. Read it
|
|
109
|
+
* with `at(x, y)`: bilinear, tiled by `paper.tiling`, at `paper.scale`.
|
|
110
|
+
*/
|
|
111
|
+
declare class PaperGrain {
|
|
112
|
+
readonly size = 256;
|
|
113
|
+
private readonly heights;
|
|
114
|
+
private readonly scale;
|
|
115
|
+
private readonly mirror;
|
|
116
|
+
readonly flat: boolean;
|
|
117
|
+
constructor(paper: PaperSettings, media?: 'dry' | 'wet');
|
|
118
|
+
/** Tile cell index for a coordinate in grain pixels (mirrored on odd tiles when mirroring). */
|
|
119
|
+
private wrap;
|
|
120
|
+
/** Height 0..1 at document position (x, y). */
|
|
121
|
+
at(x: number, y: number): number;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* How much of a dry dab lands at grain height `h` (0..1): the media catches the peaks first, and
|
|
125
|
+
* pressure pushes it into the valleys. `depth` is the paper's grain depth times the brush's grain
|
|
126
|
+
* response; at 0 everything lands (1).
|
|
127
|
+
*/
|
|
128
|
+
declare function grainCatch(h: number, depth: number, pressure: number): number;
|
|
129
|
+
/** `#rrggbb` → [r, g, b] 0..255. */
|
|
130
|
+
declare function hexToRgb8(hex: string): [number, number, number];
|
|
131
|
+
/**
|
|
132
|
+
* The paper as opaque RGBA over `rect` (document pixels): its colour, darkened in the valleys by
|
|
133
|
+
* `shading`.
|
|
134
|
+
*/
|
|
135
|
+
declare function renderPaper(paper: PaperSettings, rect: {
|
|
136
|
+
x: number;
|
|
137
|
+
y: number;
|
|
138
|
+
width: number;
|
|
139
|
+
height: number;
|
|
140
|
+
}, grain?: PaperGrain): Uint8ClampedArray;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Paint editor brushes: the shared engine's tip settings (`@pixerate/editor/paint`) plus what a
|
|
144
|
+
* natural-media brush adds: paper grain response, pen dynamics (pressure, tilt, barrel rotation,
|
|
145
|
+
* velocity), separate pressure smoothing, a rotation mode, jitter, and Mixer Brush settings for
|
|
146
|
+
* the blending brushes. The built-in presets are our own settings; none come from another product.
|
|
147
|
+
*/
|
|
148
|
+
|
|
149
|
+
/** How a brush makes marks: dabs into a stroke buffer, removing alpha, or the Mixer Brush engine. */
|
|
150
|
+
type PaintBrushEngine = 'dab' | 'eraser' | 'mixer';
|
|
151
|
+
declare const PAINT_BRUSH_ENGINES: readonly PaintBrushEngine[];
|
|
152
|
+
type PaintBrushCategory = 'pencil' | 'pastel' | 'charcoal' | 'airbrush' | 'marker' | 'ink' | 'eraser' | 'blender' | 'smudge' | 'mixer' | 'custom';
|
|
153
|
+
declare const PAINT_BRUSH_CATEGORIES: readonly PaintBrushCategory[];
|
|
154
|
+
/** What turns the tip: a fixed angle, the direction of the stroke, or the pen's barrel rotation. */
|
|
155
|
+
type BrushRotation = 'fixed' | 'stroke' | 'barrel';
|
|
156
|
+
declare const BRUSH_ROTATIONS: readonly BrushRotation[];
|
|
157
|
+
/** Pen dynamics and jitter. Every amount is 0 (off) .. 1 unless noted. */
|
|
158
|
+
interface BrushDynamics {
|
|
159
|
+
/** Size at zero pressure as a fraction of the full size, when pressure controls size (default 0). */
|
|
160
|
+
minSize: number;
|
|
161
|
+
/** How much pressure scales flow (default 0). */
|
|
162
|
+
pressureFlow: number;
|
|
163
|
+
/** Laying the pen down widens the tip, up to 2.5× (default 0). */
|
|
164
|
+
tiltSize: number;
|
|
165
|
+
/** Laying the pen down flattens the tip along the tilt direction (default 0). */
|
|
166
|
+
tiltRoundness: number;
|
|
167
|
+
/** Laying the pen down lightens the mark, as when shading with the side of a pencil (default 0). */
|
|
168
|
+
tiltOpacity: number;
|
|
169
|
+
/** -1..1: positive grows the tip when the pen moves fast, negative thins it (default 0). */
|
|
170
|
+
velocitySize: number;
|
|
171
|
+
/** -1..1: positive darkens fast marks, negative lightens them (default 0). */
|
|
172
|
+
velocityOpacity: number;
|
|
173
|
+
/** Random size change per dab (default 0). */
|
|
174
|
+
sizeJitter: number;
|
|
175
|
+
/** Random angle per dab, as a fraction of ±180° (default 0). */
|
|
176
|
+
angleJitter: number;
|
|
177
|
+
/** Random opacity drop per dab (default 0). */
|
|
178
|
+
opacityJitter: number;
|
|
179
|
+
/** Random offset across the stroke in diameters, 0..4 (default 0). */
|
|
180
|
+
scatter: number;
|
|
181
|
+
}
|
|
182
|
+
declare const DEFAULT_DYNAMICS: Readonly<BrushDynamics>;
|
|
183
|
+
interface PaintBrush {
|
|
184
|
+
/** Library id (lowercase letters, digits and dashes). */
|
|
185
|
+
id: string;
|
|
186
|
+
name: string;
|
|
187
|
+
category: PaintBrushCategory;
|
|
188
|
+
engine: PaintBrushEngine;
|
|
189
|
+
/** The shared engine's tip: size, hardness, spacing, angle, roundness, opacity, flow, position smoothing, pressure to size / opacity, blend. */
|
|
190
|
+
tip: BrushSettings;
|
|
191
|
+
/** How strongly the mark follows the paper grain, 0 (ignores it) .. 1 (catches only the peaks at light pressure). */
|
|
192
|
+
grain: number;
|
|
193
|
+
/** Pressure smoothing 0..0.95, separate from the tip's position smoothing (default 0). */
|
|
194
|
+
pressureSmoothing: number;
|
|
195
|
+
rotation: BrushRotation;
|
|
196
|
+
dynamics: BrushDynamics;
|
|
197
|
+
/** Mixer engine only: the Mixer Brush settings. */
|
|
198
|
+
mixer: MixerSettings;
|
|
199
|
+
/** Mixer engine only: load the current colour each stroke (false: a smudge or blender that only moves paint). */
|
|
200
|
+
loadsColor: boolean;
|
|
201
|
+
/** True for the built-in presets (they cannot be removed or overwritten). */
|
|
202
|
+
builtin?: boolean;
|
|
203
|
+
}
|
|
204
|
+
declare function normalizeDynamics(raw?: Partial<BrushDynamics>, base?: BrushDynamics): BrushDynamics;
|
|
205
|
+
declare const BRUSH_ID: RegExp;
|
|
206
|
+
/** Brush settings to change or read: any subset of a brush, its tip, dynamics and mixer. */
|
|
207
|
+
type PaintBrushInput = Partial<Omit<PaintBrush, 'tip' | 'dynamics' | 'mixer'>> & {
|
|
208
|
+
tip?: Partial<BrushSettings>;
|
|
209
|
+
dynamics?: Partial<BrushDynamics>;
|
|
210
|
+
mixer?: Partial<MixerSettings>;
|
|
211
|
+
};
|
|
212
|
+
/** A brush with every field in range; missing fields come from `base` (default: a plain round dab brush). */
|
|
213
|
+
declare function normalizePaintBrush(raw?: PaintBrushInput, base?: PaintBrush): PaintBrush;
|
|
214
|
+
/** The built-in brush library. */
|
|
215
|
+
declare const BUILTIN_BRUSHES: readonly PaintBrush[];
|
|
216
|
+
declare const DEFAULT_BRUSH_ID = "pencil-6b";
|
|
217
|
+
|
|
218
|
+
/** A brush as plain JSON (for export, agents and saved libraries): no `builtin` flag. */
|
|
219
|
+
declare function brushToJSON(brush: PaintBrush): Omit<PaintBrush, 'builtin'>;
|
|
220
|
+
/** The JSON file a brush library exports to and imports from. */
|
|
221
|
+
interface BrushLibraryFile {
|
|
222
|
+
format: 'pixerate-brushes';
|
|
223
|
+
version: 1;
|
|
224
|
+
brushes: Array<Omit<PaintBrush, 'builtin'>>;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Brushes read from a brush library file (or a bare array of brushes). Each is normalised; ones
|
|
228
|
+
* without a valid id or name are reported in `problems` and skipped.
|
|
229
|
+
*/
|
|
230
|
+
declare function parseBrushLibrary(raw: unknown, problems?: string[]): PaintBrush[];
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* The paint editor's dry-media stroke engine, on the shared brush engine (`@pixerate/editor/paint`):
|
|
234
|
+
*
|
|
235
|
+
* - Pen input: pressure, tilt (`tiltX` / `tiltY` or `altitude` / `azimuth`), barrel rotation
|
|
236
|
+
* (`twist`) and time (for velocity). Position and pressure are smoothed separately.
|
|
237
|
+
* - `PenDabWalker` walks the points into the shared `Dab`s at `spacing × diameter`, with every
|
|
238
|
+
* pen channel interpolated, and applies the brush's dynamics, rotation mode and jitter.
|
|
239
|
+
* Jitter is a pure hash of the stroke seed and the dab index, so a stroke replays exactly.
|
|
240
|
+
* - `DryStroke` rasterizes each dab with the shared round tip, multiplies it by how much of it the
|
|
241
|
+
* paper grain catches (`grainCatch`), and builds it up in the shared `CoverageMap`; the session
|
|
242
|
+
* then composites the coverage with the shared `applyStrokeToTile`.
|
|
243
|
+
* - Symmetry: one stroke can paint several copies of its points (mirror and kaleidoscope), each
|
|
244
|
+
* with its own walker, into the same coverage, so the copies are one stroke and one undo step.
|
|
245
|
+
*
|
|
246
|
+
* Pure data in, pixels out: no DOM, no framework.
|
|
247
|
+
*/
|
|
248
|
+
|
|
249
|
+
/** One pen sample in document pixels. Every channel but x and y is optional. */
|
|
250
|
+
interface PenPoint {
|
|
251
|
+
x: number;
|
|
252
|
+
y: number;
|
|
253
|
+
/** 0..1 (missing: 1, a mouse). */
|
|
254
|
+
pressure?: number;
|
|
255
|
+
/** Milliseconds since any fixed origin (for velocity; missing: no velocity dynamics). */
|
|
256
|
+
t?: number;
|
|
257
|
+
/** Pointer Events tilt in degrees, -90..90 (used when `altitude` / `azimuth` are missing). */
|
|
258
|
+
tiltX?: number;
|
|
259
|
+
tiltY?: number;
|
|
260
|
+
/** Radians from the surface: π/2 is upright, 0 lies flat. */
|
|
261
|
+
altitude?: number;
|
|
262
|
+
/** Radians clockwise from the +x axis (screen y points down): where the pen leans. */
|
|
263
|
+
azimuth?: number;
|
|
264
|
+
/** Barrel rotation in degrees clockwise, 0..359. */
|
|
265
|
+
twist?: number;
|
|
266
|
+
}
|
|
267
|
+
/** A pen's lean as altitude and azimuth, or `null` when the sample has no tilt. */
|
|
268
|
+
declare function penOrientation(p: PenPoint): {
|
|
269
|
+
altitude: number;
|
|
270
|
+
azimuth: number;
|
|
271
|
+
} | null;
|
|
272
|
+
/** Pointer Events `tiltX` / `tiltY` (degrees) → altitude and azimuth (radians). */
|
|
273
|
+
declare function tiltToOrientation(tiltX: number, tiltY: number): {
|
|
274
|
+
altitude: number;
|
|
275
|
+
azimuth: number;
|
|
276
|
+
};
|
|
277
|
+
/**
|
|
278
|
+
* Exponential smoothing of position and of pressure, each by its own amount (0..0.95). Feed raw
|
|
279
|
+
* samples; the first sample passes through.
|
|
280
|
+
*/
|
|
281
|
+
declare class PenSmoother {
|
|
282
|
+
private pos;
|
|
283
|
+
private pressure;
|
|
284
|
+
private readonly a;
|
|
285
|
+
private readonly ap;
|
|
286
|
+
constructor(position: number, pressure: number);
|
|
287
|
+
push(p: PenPoint): PenPoint;
|
|
288
|
+
/** Where the smoothed position is now (or `null` before the first sample). */
|
|
289
|
+
get position(): {
|
|
290
|
+
x: number;
|
|
291
|
+
y: number;
|
|
292
|
+
} | null;
|
|
293
|
+
}
|
|
294
|
+
/** A linear map of document vectors `[a, b, c, d]`: (x, y) → (a·x + b·y, c·x + d·y). */
|
|
295
|
+
type Linear2 = readonly [number, number, number, number];
|
|
296
|
+
/** How one copy of a stroke maps the input: a point map and its linear part (for dab angles). */
|
|
297
|
+
interface StrokeCopy {
|
|
298
|
+
map(p: PenPoint): PenPoint;
|
|
299
|
+
linear: Linear2;
|
|
300
|
+
}
|
|
301
|
+
declare const IDENTITY_COPY: StrokeCopy;
|
|
302
|
+
/** A dab with the pen state it was made at (the shared `Dab` plus pressure for the grain). */
|
|
303
|
+
interface PenDab extends Dab {
|
|
304
|
+
pressure: number;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Walks pen samples into dabs, incrementally (any chunking gives the same dabs). Dab angles are in
|
|
308
|
+
* the shared rasterizer's convention: radians, counter-clockwise with y up.
|
|
309
|
+
*/
|
|
310
|
+
declare class PenDabWalker {
|
|
311
|
+
readonly brush: PaintBrush;
|
|
312
|
+
private readonly seed;
|
|
313
|
+
private readonly linear;
|
|
314
|
+
private last;
|
|
315
|
+
private nextAt;
|
|
316
|
+
private pendingFirst;
|
|
317
|
+
private index;
|
|
318
|
+
private dir;
|
|
319
|
+
private lastT;
|
|
320
|
+
private speed;
|
|
321
|
+
constructor(brush: PaintBrush, seed?: number, linear?: Linear2);
|
|
322
|
+
private sample;
|
|
323
|
+
private diameter;
|
|
324
|
+
private stepLen;
|
|
325
|
+
/** A rasterizer angle (CCW, y up) mapped through this copy's linear part. */
|
|
326
|
+
private mapAngle;
|
|
327
|
+
private dab;
|
|
328
|
+
push(p: PenPoint, out: PenDab[]): void;
|
|
329
|
+
finish(out: PenDab[]): void;
|
|
330
|
+
}
|
|
331
|
+
interface DryStrokeOptions {
|
|
332
|
+
/** The paper's dry height field, or `null` for no grain. */
|
|
333
|
+
grain: PaperGrain | null;
|
|
334
|
+
/** The paper's grain depth, 0..1 (multiplied by the brush's grain response). */
|
|
335
|
+
paperDepth: number;
|
|
336
|
+
/** Jitter seed (record it to replay the stroke exactly). */
|
|
337
|
+
seed?: number;
|
|
338
|
+
/** Copies of the stroke (symmetry); default one identity copy. */
|
|
339
|
+
copies?: readonly StrokeCopy[];
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* One dab-engine stroke (dab and eraser brushes): smoothing, the dab walk of every copy, grain and
|
|
343
|
+
* build-up in a shared coverage map.
|
|
344
|
+
*/
|
|
345
|
+
declare class DryStroke {
|
|
346
|
+
readonly brush: PaintBrush;
|
|
347
|
+
private readonly options;
|
|
348
|
+
readonly coverage: CoverageMap;
|
|
349
|
+
dabCount: number;
|
|
350
|
+
private readonly smoother;
|
|
351
|
+
private readonly walkers;
|
|
352
|
+
private readonly copies;
|
|
353
|
+
private readonly depth;
|
|
354
|
+
private buf;
|
|
355
|
+
private lastRaw;
|
|
356
|
+
private lastFed;
|
|
357
|
+
constructor(brush: PaintBrush, options: DryStrokeOptions);
|
|
358
|
+
/** Feeds raw pen samples; returns the document rectangles the new dabs touched. */
|
|
359
|
+
push(points: readonly PenPoint[]): RasterRect[];
|
|
360
|
+
/** Ends the stroke: the smoothed path catches up with the last raw sample; a single tap paints one dab. */
|
|
361
|
+
finish(): RasterRect[];
|
|
362
|
+
private paint;
|
|
363
|
+
}
|
|
364
|
+
/** The shared engine's tip for the Mixer Brush engine (it smooths and walks by itself, so smoothing is applied before). */
|
|
365
|
+
declare function mixerTip(brush: PaintBrush): BrushSettings;
|
|
366
|
+
/** Pen samples → the shared engine's stroke points (pressure only). */
|
|
367
|
+
declare function toStrokePoint(p: PenPoint): StrokePoint;
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Strokes as data. Every committed stroke is recorded with the brush, colour, raw pen samples,
|
|
371
|
+
* jitter seed and symmetry copies it was painted with, so it replays to the same pixels, and can
|
|
372
|
+
* be replayed moved, scaled, rotated, recoloured or with another brush (`replay_strokes`).
|
|
373
|
+
*/
|
|
374
|
+
|
|
375
|
+
/** An affine map [a, b, c, d, e, f]: (x, y) → (a·x + b·y + e, c·x + d·y + f). */
|
|
376
|
+
type Affine = readonly [number, number, number, number, number, number];
|
|
377
|
+
declare const IDENTITY_AFFINE: Affine;
|
|
378
|
+
interface RecordedStroke {
|
|
379
|
+
id: string;
|
|
380
|
+
layerId: string;
|
|
381
|
+
brush: PaintBrush;
|
|
382
|
+
/** #rrggbb */
|
|
383
|
+
color: string;
|
|
384
|
+
/** Raw pen samples in document pixels, before smoothing. */
|
|
385
|
+
points: PenPoint[];
|
|
386
|
+
seed: number;
|
|
387
|
+
/** Extra copies painted with the stroke (mirror / kaleidoscope), as affine maps of the points. */
|
|
388
|
+
copies?: Affine[];
|
|
389
|
+
}
|
|
390
|
+
declare function applyAffine(m: Affine, x: number, y: number): {
|
|
391
|
+
x: number;
|
|
392
|
+
y: number;
|
|
393
|
+
};
|
|
394
|
+
/** `a ∘ b` (b first). */
|
|
395
|
+
declare function composeAffine(a: Affine, b: Affine): Affine;
|
|
396
|
+
declare function invertAffine(m: Affine): Affine;
|
|
397
|
+
/** The pen sample mapped by `m` (tilt and twist are kept: they describe the pen, not the path). */
|
|
398
|
+
declare function mapPenPoint(m: Affine, p: PenPoint): PenPoint;
|
|
399
|
+
/** An affine copy as the engine's `StrokeCopy`. */
|
|
400
|
+
declare function affineCopy(m: Affine): StrokeCopy;
|
|
401
|
+
/** A recorded stroke read from JSON, or `null` when it is not one. */
|
|
402
|
+
declare function normalizeBrushStroke(raw: unknown): RecordedStroke | null;
|
|
403
|
+
/** The bounding box of a stroke's points. */
|
|
404
|
+
declare function strokeBounds(points: readonly PenPoint[]): {
|
|
405
|
+
x: number;
|
|
406
|
+
y: number;
|
|
407
|
+
width: number;
|
|
408
|
+
height: number;
|
|
409
|
+
};
|
|
410
|
+
interface StrokeVariation {
|
|
411
|
+
/** Move by this many document pixels (default 0). */
|
|
412
|
+
dx?: number;
|
|
413
|
+
dy?: number;
|
|
414
|
+
/** Scale about the origin (default 1); brush size scales with it. */
|
|
415
|
+
scale?: number;
|
|
416
|
+
/** Rotate about the origin, degrees clockwise (default 0). */
|
|
417
|
+
rotate?: number;
|
|
418
|
+
/** Mirror horizontally about the origin (default false). */
|
|
419
|
+
flipX?: boolean;
|
|
420
|
+
/** The origin for scale, rotate and flip (default: the centre of the strokes' bounds). */
|
|
421
|
+
origin?: {
|
|
422
|
+
x: number;
|
|
423
|
+
y: number;
|
|
424
|
+
};
|
|
425
|
+
/** Multiply every pressure by this (default 1). */
|
|
426
|
+
pressureScale?: number;
|
|
427
|
+
}
|
|
428
|
+
/** The affine map of a variation about `origin`. */
|
|
429
|
+
declare function variationAffine(v: StrokeVariation, origin: {
|
|
430
|
+
x: number;
|
|
431
|
+
y: number;
|
|
432
|
+
}): Affine;
|
|
433
|
+
/** A stroke varied by `m` (points mapped; symmetry copies conjugated so they stay symmetric). */
|
|
434
|
+
declare function varyStroke(stroke: RecordedStroke, m: Affine, v: StrokeVariation): RecordedStroke;
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* The paint editor's document: a canvas (size and resolution), a paper, and the shared raster
|
|
438
|
+
* layer stack (`@pixerate/editor/raster`: pixel layers and groups with masks, clipping, locks and
|
|
439
|
+
* blend modes). Layers are immutable and share tiles, so a history entry is a reference to a
|
|
440
|
+
* whole document state.
|
|
441
|
+
*
|
|
442
|
+
* Saved documents are JSON plus PNG assets, through the image editor's layer serializer (so the
|
|
443
|
+
* same layer tree loads in both editors), with the paper as plain settings.
|
|
444
|
+
*/
|
|
445
|
+
|
|
446
|
+
type PaintPixelLayer = RasterPixelLayer;
|
|
447
|
+
type PaintGroupLayer = RasterGroupLayer<PaintLayer>;
|
|
448
|
+
/** A layer of the paint editor: the shared stack's pixel layers and groups. */
|
|
449
|
+
type PaintLayer = RasterLayer;
|
|
450
|
+
/** Canvas size limits (pixels per side). */
|
|
451
|
+
declare const MIN_CANVAS_SIDE = 1;
|
|
452
|
+
declare const MAX_CANVAS_SIDE = 16384;
|
|
453
|
+
declare const DEFAULT_DPI = 300;
|
|
454
|
+
interface PaintCanvas {
|
|
455
|
+
width: number;
|
|
456
|
+
height: number;
|
|
457
|
+
/** Pixels per inch, for print sizes and PSD resolution (default 300). */
|
|
458
|
+
dpi: number;
|
|
459
|
+
}
|
|
460
|
+
/** Everything one undo step restores. */
|
|
461
|
+
interface PaintDocumentState {
|
|
462
|
+
canvas: PaintCanvas;
|
|
463
|
+
paper: PaperSettings;
|
|
464
|
+
/** Bottom to top. */
|
|
465
|
+
layers: PaintLayer[];
|
|
466
|
+
activeLayerId: string | null;
|
|
467
|
+
/** Every committed stroke, oldest first (strokes are data: replay, vary, time-lapse later). */
|
|
468
|
+
strokes: RecordedStroke[];
|
|
469
|
+
}
|
|
470
|
+
/** A new layer id, unique in this session. */
|
|
471
|
+
declare function newPaintLayerId(): string;
|
|
472
|
+
/** An empty, visible, normal pixel layer. */
|
|
473
|
+
declare function createPaintLayer(name: string, id?: string): PaintPixelLayer;
|
|
474
|
+
declare class PaintDocumentError extends Error {
|
|
475
|
+
constructor(message: string);
|
|
476
|
+
}
|
|
477
|
+
/** Validates and rounds a canvas size; throws a `PaintDocumentError` that says what is wrong. */
|
|
478
|
+
declare function normalizeCanvas(raw: Partial<PaintCanvas>): PaintCanvas;
|
|
479
|
+
/** A blank document: one empty layer over the paper. */
|
|
480
|
+
declare function createPaintDocument(options?: Partial<PaintCanvas> & {
|
|
481
|
+
paper?: Partial<PaperSettings>;
|
|
482
|
+
}): PaintDocumentState;
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* A live stroke on one pixel layer. Points arrive while the pen moves; the layer shows the stroke
|
|
486
|
+
* so far through a live surface (no history); `end()` commits it as one undo step.
|
|
487
|
+
*
|
|
488
|
+
* - Dab and eraser brushes: the coverage is cumulative, so each update recomposites the touched
|
|
489
|
+
* region from the pre-stroke tile (`applyStrokeToTile` with `into` and `region`).
|
|
490
|
+
* - Mixer engine brushes (Mixer Brush, blender, smudge): dabs read what earlier dabs laid down, so
|
|
491
|
+
* they paint into live tile copies; every tile written since the last update gets a new `Tile`
|
|
492
|
+
* object, because the compositor caches by tile identity.
|
|
493
|
+
*/
|
|
494
|
+
|
|
495
|
+
interface StrokeSessionHost {
|
|
496
|
+
/** Shows the layer's live surface (no history); `rects` are where it changed. */
|
|
497
|
+
update(layerId: string, surface: TiledSurface, rects: RasterRect[]): void;
|
|
498
|
+
/** Ends the stroke: `surface` is the new pixels, or `null` when nothing changed / it was cancelled. */
|
|
499
|
+
commit(session: PaintStrokeSession, surface: TiledSurface | null, cancelled: boolean): void;
|
|
500
|
+
}
|
|
501
|
+
interface StrokeSessionInit {
|
|
502
|
+
layerId: string;
|
|
503
|
+
surface: TiledSurface;
|
|
504
|
+
offset: {
|
|
505
|
+
x: number;
|
|
506
|
+
y: number;
|
|
507
|
+
};
|
|
508
|
+
lockTransparency: boolean;
|
|
509
|
+
brush: PaintBrush;
|
|
510
|
+
/** Paint colour 0..255 RGBA. */
|
|
511
|
+
color: readonly [number, number, number, number];
|
|
512
|
+
/** What the eraser paints on a transparency-locked layer (the paper colour). */
|
|
513
|
+
background: readonly [number, number, number, number];
|
|
514
|
+
grain: PaperGrain | null;
|
|
515
|
+
paperDepth: number;
|
|
516
|
+
seed: number;
|
|
517
|
+
/** The paint colour as #rrggbb (recorded with the stroke). */
|
|
518
|
+
colorHex: string;
|
|
519
|
+
/** The stroke's extra copies as affine maps (recorded with the stroke); `copies` holds all of them as engine copies. */
|
|
520
|
+
affineCopies: readonly Affine[];
|
|
521
|
+
copies: readonly StrokeCopy[];
|
|
522
|
+
/** Applied to each raw sample before it is painted and recorded (perspective snapping). */
|
|
523
|
+
inputFilter?: (p: PenPoint) => PenPoint;
|
|
524
|
+
/** Mixer engine: the brush load at the start of the stroke. */
|
|
525
|
+
mixerLoad: MixerLoad;
|
|
526
|
+
}
|
|
527
|
+
interface PaintStrokeResult {
|
|
528
|
+
layerId: string;
|
|
529
|
+
/** Document rectangle the stroke touched, or `null` when no pixel changed. */
|
|
530
|
+
bounds: RasterRect | null;
|
|
531
|
+
dabs: number;
|
|
532
|
+
/** Id of the recorded stroke (see `list_strokes` / `replay_strokes`), or `null` when nothing changed. */
|
|
533
|
+
strokeId: string | null;
|
|
534
|
+
/** Mixer engine brushes: what the brush carries after the stroke. */
|
|
535
|
+
mixerLoad?: MixerLoad;
|
|
536
|
+
}
|
|
537
|
+
declare class PaintStrokeSession {
|
|
538
|
+
private readonly host;
|
|
539
|
+
readonly init: StrokeSessionInit;
|
|
540
|
+
readonly layerId: string;
|
|
541
|
+
/** The raw input so far (what a replay feeds back in). */
|
|
542
|
+
readonly points: PenPoint[];
|
|
543
|
+
private readonly base;
|
|
544
|
+
private readonly live;
|
|
545
|
+
private readonly touched;
|
|
546
|
+
private readonly fresh;
|
|
547
|
+
private readonly dry;
|
|
548
|
+
private readonly mixers;
|
|
549
|
+
private readonly mixerSmoother;
|
|
550
|
+
private readonly apply;
|
|
551
|
+
private readonly copies;
|
|
552
|
+
private bounds;
|
|
553
|
+
private lastRaw;
|
|
554
|
+
private closed;
|
|
555
|
+
result: PaintStrokeResult | null;
|
|
556
|
+
constructor(host: StrokeSessionHost, init: StrokeSessionInit);
|
|
557
|
+
private mixerTarget;
|
|
558
|
+
get open(): boolean;
|
|
559
|
+
/** Adds raw pen samples (document pixels). */
|
|
560
|
+
push(points: readonly PenPoint[]): void;
|
|
561
|
+
private update;
|
|
562
|
+
/** Ends the stroke (one undo step) and returns what changed. */
|
|
563
|
+
end(): PaintStrokeResult;
|
|
564
|
+
/** Abandons the stroke and restores the layer. */
|
|
565
|
+
cancel(): void;
|
|
566
|
+
}
|
|
567
|
+
/** The Mixer Brush load a stroke starts with. */
|
|
568
|
+
declare function mixerLoadForStroke(brush: PaintBrush, current: MixerLoad, color: readonly [number, number, number]): MixerLoad;
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Compositing for the paint editor, on the shared `RasterCompositor`: the paper is a content layer
|
|
572
|
+
* at the bottom of the stack (its tiles are generated from the paper settings), and view-only
|
|
573
|
+
* layers (the tracing image) sit between it and the paint. No compositor of our own.
|
|
574
|
+
*/
|
|
575
|
+
|
|
576
|
+
/** The paper as a layer of the compositor (generated, never stored). */
|
|
577
|
+
interface PaperNode extends CompositorLayer {
|
|
578
|
+
type: 'paper';
|
|
579
|
+
id: string;
|
|
580
|
+
name: string;
|
|
581
|
+
visible: boolean;
|
|
582
|
+
opacity: number;
|
|
583
|
+
blend: 'normal';
|
|
584
|
+
paper: PaperSettings;
|
|
585
|
+
}
|
|
586
|
+
/** A view-only RGBA image under the paint (the tracing image), drawn at its opacity. */
|
|
587
|
+
interface ImageNode extends CompositorLayer {
|
|
588
|
+
type: 'image';
|
|
589
|
+
id: string;
|
|
590
|
+
name: string;
|
|
591
|
+
visible: boolean;
|
|
592
|
+
opacity: number;
|
|
593
|
+
blend: 'normal';
|
|
594
|
+
/** Canvas-sized RGBA. */
|
|
595
|
+
image: {
|
|
596
|
+
width: number;
|
|
597
|
+
height: number;
|
|
598
|
+
data: Uint8ClampedArray;
|
|
599
|
+
};
|
|
600
|
+
/** Changes when the pixels change. */
|
|
601
|
+
version: number;
|
|
602
|
+
}
|
|
603
|
+
type ViewLayer = PaintLayer | PaperNode | ImageNode;
|
|
604
|
+
declare const PAPER_NODE_ID = "__paper";
|
|
605
|
+
declare const TRACING_NODE_ID = "__tracing";
|
|
606
|
+
declare function paperKey(p: PaperSettings): string;
|
|
607
|
+
/** The paper as a compositor layer (one object per paper settings object, so caches hold). */
|
|
608
|
+
declare function paperNode(paper: PaperSettings): PaperNode;
|
|
609
|
+
/** The paper's dry height field, built once per settings. */
|
|
610
|
+
declare function paperGrain(paper: PaperSettings): PaperGrain;
|
|
611
|
+
/** A compositor for paint editor stacks (paper and view-only images included). */
|
|
612
|
+
declare function createPaintCompositor(): RasterCompositor<ViewLayer, RasterSources>;
|
|
613
|
+
/**
|
|
614
|
+
* Area-averaged downscale of straight-alpha RGBA (colours weighted by alpha). Sizes at or above
|
|
615
|
+
* the source copy it.
|
|
616
|
+
*/
|
|
617
|
+
declare function downsampleRgba(data: ArrayLike<number>, width: number, height: number, tw: number, th: number): Uint8ClampedArray;
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Saved paint documents: JSON plus PNG assets. Layers go through the image editor's layer
|
|
621
|
+
* serializer (`serializeLayers` / `deserializeLayers`), so pixel layers and groups are stored the
|
|
622
|
+
* same way in both editors, including external asset stores. The paper is plain settings: it is
|
|
623
|
+
* regenerated on load, never stored as pixels.
|
|
624
|
+
*
|
|
625
|
+
* Forward compatibility: readers keep fields they do not know at the top level (`extensions`) and
|
|
626
|
+
* write them back, so a later version can add per-layer wet state, height or material channels
|
|
627
|
+
* as new assets keyed by layer id without breaking older readers (see `PaintDocumentExtensions`).
|
|
628
|
+
*/
|
|
629
|
+
|
|
630
|
+
declare const PAINT_FORMAT: "pixerate-paint";
|
|
631
|
+
declare const PAINT_SCHEMA_VERSION: 1;
|
|
632
|
+
/** Where a host keeps layer PNGs outside the JSON (the image editor's asset store contract). */
|
|
633
|
+
type PaintAssetStore = ImageEditorAssetStore;
|
|
634
|
+
/**
|
|
635
|
+
* Fields a later version adds. Phase 1 writes none of them and keeps any it reads. Planned:
|
|
636
|
+
* `layerChannels: { [layerId]: { height?: asset, material?: asset } }` for impasto and
|
|
637
|
+
* metallic paint. Wet paint is never saved (saving dries it), so there is no wet field.
|
|
638
|
+
*/
|
|
639
|
+
type PaintDocumentExtensions = Record<string, unknown>;
|
|
640
|
+
interface SerializedPaintDocument {
|
|
641
|
+
format: typeof PAINT_FORMAT;
|
|
642
|
+
version: typeof PAINT_SCHEMA_VERSION;
|
|
643
|
+
width: number;
|
|
644
|
+
height: number;
|
|
645
|
+
dpi: number;
|
|
646
|
+
paper: PaperSettings;
|
|
647
|
+
/** Bottom to top: pixel layers and groups, as the image editor stores them. */
|
|
648
|
+
layers: SerializedLayer[];
|
|
649
|
+
/** Asset id → PNG data URL (or an external reference). */
|
|
650
|
+
assets: Record<string, string>;
|
|
651
|
+
activeLayerId: string | null;
|
|
652
|
+
/** Recorded strokes (strokes are data), oldest first; missing when saved without them. */
|
|
653
|
+
strokes?: RecordedStroke[];
|
|
654
|
+
/** Everything else (document-level state of later phases), written back as read. */
|
|
655
|
+
[extension: string]: unknown;
|
|
656
|
+
}
|
|
657
|
+
/** Fields of a saved document this version does not read, kept for writing back. */
|
|
658
|
+
declare function documentExtensions(raw: Record<string, unknown>): PaintDocumentExtensions;
|
|
659
|
+
interface SerializeOptions {
|
|
660
|
+
/** Include the recorded strokes (default true). */
|
|
661
|
+
strokes?: boolean;
|
|
662
|
+
/** Later-phase fields to write as they were read. */
|
|
663
|
+
extensions?: PaintDocumentExtensions;
|
|
664
|
+
/** Painting guides (symmetry, perspective, tracing and reference images as PNG data URLs). */
|
|
665
|
+
guides?: unknown;
|
|
666
|
+
swatches?: unknown;
|
|
667
|
+
}
|
|
668
|
+
/** The document as JSON with inline PNG data URLs. */
|
|
669
|
+
declare function serializePaintDocument(state: PaintDocumentState, options?: SerializeOptions): SerializedPaintDocument;
|
|
670
|
+
/** The document as JSON with each PNG uploaded to `store` once (by content). */
|
|
671
|
+
declare function serializePaintDocumentExternal(state: PaintDocumentState, store: PaintAssetStore, options?: SerializeOptions): Promise<SerializedPaintDocument>;
|
|
672
|
+
interface LoadedPaintDocument {
|
|
673
|
+
state: PaintDocumentState;
|
|
674
|
+
extensions: PaintDocumentExtensions;
|
|
675
|
+
/** The saved guides and swatches, as stored (the controller validates them). */
|
|
676
|
+
guides?: unknown;
|
|
677
|
+
swatches?: unknown;
|
|
678
|
+
/** What could not be read (the rest still loads). */
|
|
679
|
+
problems: string[];
|
|
680
|
+
}
|
|
681
|
+
/** Reads a saved document with inline assets. Throws when it is not a paint document at all. */
|
|
682
|
+
declare function deserializePaintDocument(raw: unknown): LoadedPaintDocument;
|
|
683
|
+
/** Reads a saved document whose assets may be external references in `store`. */
|
|
684
|
+
declare function deserializePaintDocumentAsync(raw: unknown, store?: PaintAssetStore): Promise<LoadedPaintDocument>;
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* The canvas view: zoom, pan, rotation and mirroring. View state only: the pixels never resample,
|
|
688
|
+
* the view maps document pixels to the screen and pointer positions back.
|
|
689
|
+
*/
|
|
690
|
+
|
|
691
|
+
interface PaintView {
|
|
692
|
+
/** Screen pixels per document pixel (0.02..64, default 1). */
|
|
693
|
+
zoom: number;
|
|
694
|
+
/** Offset of the canvas centre from the viewport centre, in screen pixels. */
|
|
695
|
+
pan: {
|
|
696
|
+
x: number;
|
|
697
|
+
y: number;
|
|
698
|
+
};
|
|
699
|
+
/** Rotation in degrees clockwise, -180..180 (default 0). */
|
|
700
|
+
rotation: number;
|
|
701
|
+
/** Mirror the view left to right (default false). */
|
|
702
|
+
mirror: boolean;
|
|
703
|
+
}
|
|
704
|
+
declare const MIN_ZOOM = 0.02;
|
|
705
|
+
declare const MAX_ZOOM = 64;
|
|
706
|
+
declare const DEFAULT_VIEW: Readonly<PaintView>;
|
|
707
|
+
/** Degrees into -180..180. */
|
|
708
|
+
declare function normalizeRotation(deg: number): number;
|
|
709
|
+
declare function normalizeView(raw: Partial<PaintView>, base?: PaintView): PaintView;
|
|
710
|
+
/**
|
|
711
|
+
* Document → screen for a canvas of `canvas` size shown in a viewport of `viewport` size:
|
|
712
|
+
* the canvas centre goes to the viewport centre plus `pan`, then zoom, rotation and mirroring
|
|
713
|
+
* apply about it.
|
|
714
|
+
*/
|
|
715
|
+
declare function viewMatrix(view: PaintView, canvas: {
|
|
716
|
+
width: number;
|
|
717
|
+
height: number;
|
|
718
|
+
}, viewport: {
|
|
719
|
+
width: number;
|
|
720
|
+
height: number;
|
|
721
|
+
}): Affine;
|
|
722
|
+
/** Screen position → document position. */
|
|
723
|
+
declare function screenToDocument(view: PaintView, canvas: {
|
|
724
|
+
width: number;
|
|
725
|
+
height: number;
|
|
726
|
+
}, viewport: {
|
|
727
|
+
width: number;
|
|
728
|
+
height: number;
|
|
729
|
+
}, x: number, y: number): {
|
|
730
|
+
x: number;
|
|
731
|
+
y: number;
|
|
732
|
+
};
|
|
733
|
+
/** Document position → screen position. */
|
|
734
|
+
declare function documentToScreen(view: PaintView, canvas: {
|
|
735
|
+
width: number;
|
|
736
|
+
height: number;
|
|
737
|
+
}, viewport: {
|
|
738
|
+
width: number;
|
|
739
|
+
height: number;
|
|
740
|
+
}, x: number, y: number): {
|
|
741
|
+
x: number;
|
|
742
|
+
y: number;
|
|
743
|
+
};
|
|
744
|
+
/** A CSS `matrix(...)` for an element of the canvas size placed at the viewport's top-left. */
|
|
745
|
+
declare function viewCssTransform(view: PaintView, canvas: {
|
|
746
|
+
width: number;
|
|
747
|
+
height: number;
|
|
748
|
+
}, viewport: {
|
|
749
|
+
width: number;
|
|
750
|
+
height: number;
|
|
751
|
+
}): string;
|
|
752
|
+
/** The zoom that fits the canvas in the viewport (with `margin` screen pixels around it). */
|
|
753
|
+
declare function fitZoom(canvas: {
|
|
754
|
+
width: number;
|
|
755
|
+
height: number;
|
|
756
|
+
}, viewport: {
|
|
757
|
+
width: number;
|
|
758
|
+
height: number;
|
|
759
|
+
}, margin?: number): number;
|
|
760
|
+
/**
|
|
761
|
+
* Pen rotation as the document sees it: a rotated or mirrored view turns the pen's azimuth and
|
|
762
|
+
* barrel rotation too, so a tilted pencil shades the same way on screen however the canvas is turned.
|
|
763
|
+
*/
|
|
764
|
+
declare function viewPenToDocument<P extends {
|
|
765
|
+
azimuth?: number;
|
|
766
|
+
twist?: number;
|
|
767
|
+
}>(view: PaintView, p: P): P;
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Pen input helpers for the bindings: Pointer Events (structurally typed, no DOM import) into
|
|
771
|
+
* `PenPoint`s, palm rejection by hand, and which hand the user draws with. The bindings feed
|
|
772
|
+
* coalesced events into the stroke and draw predicted ones as a throwaway preview.
|
|
773
|
+
*/
|
|
774
|
+
|
|
775
|
+
/** The Pointer Event fields the paint editor reads. */
|
|
776
|
+
interface PointerSample {
|
|
777
|
+
pointerType?: string;
|
|
778
|
+
pressure?: number;
|
|
779
|
+
tiltX?: number;
|
|
780
|
+
tiltY?: number;
|
|
781
|
+
/** Radians (Pointer Events Level 3). */
|
|
782
|
+
altitudeAngle?: number;
|
|
783
|
+
azimuthAngle?: number;
|
|
784
|
+
/** Degrees 0..359. */
|
|
785
|
+
twist?: number;
|
|
786
|
+
timeStamp?: number;
|
|
787
|
+
}
|
|
788
|
+
/** Which hand holds the pen: palm rejection ignores touches on that side of it. */
|
|
789
|
+
type Hand = 'right' | 'left';
|
|
790
|
+
/**
|
|
791
|
+
* Pressure of a pointer sample: pens report it; mice report 0.5 while pressed and pens 0 on
|
|
792
|
+
* `pointerup`, both of which mean "use full" (see GOTCHAS).
|
|
793
|
+
*/
|
|
794
|
+
declare function samplePressure(e: PointerSample): number;
|
|
795
|
+
/**
|
|
796
|
+
* A pointer sample at document position (x, y) as a pen point. Only pens report tilt and twist;
|
|
797
|
+
* a pen's lean and barrel rotation are turned with the view, so marks look the same on screen
|
|
798
|
+
* whatever the canvas rotation and mirroring.
|
|
799
|
+
*/
|
|
800
|
+
declare function pointerToPenPoint(e: PointerSample, x: number, y: number, view?: PaintView): PenPoint;
|
|
801
|
+
/**
|
|
802
|
+
* Palm rejection: whether a touch at `touch` (screen pixels) is probably the drawing hand resting
|
|
803
|
+
* on the screen while the pen is at `pen`. A right hand rests below and to the right of the pen, a
|
|
804
|
+
* left hand below and to the left.
|
|
805
|
+
*/
|
|
806
|
+
declare function isPalmTouch(touch: {
|
|
807
|
+
x: number;
|
|
808
|
+
y: number;
|
|
809
|
+
}, pen: {
|
|
810
|
+
x: number;
|
|
811
|
+
y: number;
|
|
812
|
+
} | null, hand: Hand): boolean;
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* JPEG and WebP encoding. PNG is encoded in pure TypeScript (`encodePng`); the lossy formats need
|
|
816
|
+
* the platform's codecs, so they go through an `OffscreenCanvas` (browsers and Workers) or a host
|
|
817
|
+
* encoder. Nothing is imported: the globals are looked up at call time.
|
|
818
|
+
*/
|
|
819
|
+
type ImageFormat = 'image/png' | 'image/jpeg' | 'image/webp';
|
|
820
|
+
/** Encodes straight-alpha RGBA as `format` at `quality` (0..1). */
|
|
821
|
+
type ImageEncoder = (image: {
|
|
822
|
+
width: number;
|
|
823
|
+
height: number;
|
|
824
|
+
data: Uint8ClampedArray;
|
|
825
|
+
}, format: 'image/jpeg' | 'image/webp', quality: number) => Promise<Uint8Array>;
|
|
826
|
+
/** An encoder through `OffscreenCanvas`, or `null` where there is none (Node, tests). */
|
|
827
|
+
declare function defaultImageEncoder(): ImageEncoder | null;
|
|
828
|
+
/**
|
|
829
|
+
* Decodes an image file (PNG, JPEG, WebP, … whatever the browser reads) into RGBA pixels through
|
|
830
|
+
* `createImageBitmap` and an `OffscreenCanvas`. Browser and Worker only.
|
|
831
|
+
*/
|
|
832
|
+
declare function decodeImageBlob(blob: Blob): Promise<{
|
|
833
|
+
width: number;
|
|
834
|
+
height: number;
|
|
835
|
+
data: Uint8ClampedArray;
|
|
836
|
+
}>;
|
|
837
|
+
/** Saves bytes as a file through a temporary link. Browser only. */
|
|
838
|
+
declare function downloadBytes(bytes: Uint8Array | string, fileName: string, mimeType: string): void;
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Brush thumbnails: a test stroke (an S-curve with pressure swelling and fading, at a steady
|
|
842
|
+
* speed) painted with the brush on the paper. Brushes that only move paint (blender, smudge) are
|
|
843
|
+
* shown dragged across two bands of colour. Pure TypeScript, so thumbnails render in Node too.
|
|
844
|
+
*/
|
|
845
|
+
|
|
846
|
+
interface BrushThumbnail {
|
|
847
|
+
width: number;
|
|
848
|
+
height: number;
|
|
849
|
+
/** RGBA, opaque (the paper is under the stroke). */
|
|
850
|
+
data: Uint8ClampedArray;
|
|
851
|
+
}
|
|
852
|
+
/** The test stroke's samples for a `width × height` thumbnail. */
|
|
853
|
+
declare function thumbnailStroke(width: number, height: number): PenPoint[];
|
|
854
|
+
declare function renderBrushThumbnail(brush: PaintBrush, options: {
|
|
855
|
+
paper: PaperSettings;
|
|
856
|
+
width?: number;
|
|
857
|
+
height?: number;
|
|
858
|
+
color: string;
|
|
859
|
+
}): BrushThumbnail;
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Painting guides: mirror and kaleidoscope symmetry (every stroke paints its reflected or rotated
|
|
863
|
+
* copies as one stroke), perspective guides with one to three vanishing points that strokes snap
|
|
864
|
+
* to, a tracing image under the paint (optionally the source of the stroke colour), and floating
|
|
865
|
+
* reference images to pick colours from. Geometry only: no pixels, no DOM.
|
|
866
|
+
*/
|
|
867
|
+
|
|
868
|
+
type SymmetryMode = 'off' | 'vertical' | 'horizontal' | 'dual' | 'diagonal' | 'kaleidoscope';
|
|
869
|
+
declare const SYMMETRY_MODES: readonly SymmetryMode[];
|
|
870
|
+
interface SymmetryGuide {
|
|
871
|
+
mode: SymmetryMode;
|
|
872
|
+
/** Centre of the axes in document pixels (default the canvas centre). */
|
|
873
|
+
center: {
|
|
874
|
+
x: number;
|
|
875
|
+
y: number;
|
|
876
|
+
};
|
|
877
|
+
/** Turns the axes, degrees clockwise (default 0). */
|
|
878
|
+
rotation: number;
|
|
879
|
+
/** Kaleidoscope: number of segments, 2..32 (default 6). */
|
|
880
|
+
segments: number;
|
|
881
|
+
/** Kaleidoscope: reflect every other segment as well as rotating (default true), like a real kaleidoscope. */
|
|
882
|
+
mirror: boolean;
|
|
883
|
+
}
|
|
884
|
+
interface Point2 {
|
|
885
|
+
x: number;
|
|
886
|
+
y: number;
|
|
887
|
+
}
|
|
888
|
+
interface PerspectiveGuide {
|
|
889
|
+
enabled: boolean;
|
|
890
|
+
/** One to three vanishing points in document pixels (they may lie outside the canvas). */
|
|
891
|
+
points: Point2[];
|
|
892
|
+
/** Strokes snap to the guide lines (default true when enabled). */
|
|
893
|
+
snap: boolean;
|
|
894
|
+
}
|
|
895
|
+
/** A view-only image under the paint, placed over the canvas. */
|
|
896
|
+
interface TracingImage {
|
|
897
|
+
/** PNG or JPEG data URL. */
|
|
898
|
+
src: string;
|
|
899
|
+
/** Natural size of the image in pixels. */
|
|
900
|
+
width: number;
|
|
901
|
+
height: number;
|
|
902
|
+
/** Where it is drawn, in document pixels (default: fitted to the canvas). */
|
|
903
|
+
rect: {
|
|
904
|
+
x: number;
|
|
905
|
+
y: number;
|
|
906
|
+
width: number;
|
|
907
|
+
height: number;
|
|
908
|
+
};
|
|
909
|
+
/** 0..1 (default 0.35). */
|
|
910
|
+
opacity: number;
|
|
911
|
+
visible: boolean;
|
|
912
|
+
/** Strokes without an explicit colour take the tracing image's colour where they start. */
|
|
913
|
+
sampleColors: boolean;
|
|
914
|
+
}
|
|
915
|
+
/** A floating image to look at and pick colours from (shown beside the canvas, never painted). */
|
|
916
|
+
interface ReferenceImage {
|
|
917
|
+
id: string;
|
|
918
|
+
name: string;
|
|
919
|
+
src: string;
|
|
920
|
+
width: number;
|
|
921
|
+
height: number;
|
|
922
|
+
/** Where the panel floats over the viewport, in screen pixels, and its displayed width. */
|
|
923
|
+
x: number;
|
|
924
|
+
y: number;
|
|
925
|
+
displayWidth: number;
|
|
926
|
+
visible: boolean;
|
|
927
|
+
}
|
|
928
|
+
interface PaintGuides {
|
|
929
|
+
symmetry: SymmetryGuide;
|
|
930
|
+
perspective: PerspectiveGuide;
|
|
931
|
+
tracing: TracingImage | null;
|
|
932
|
+
references: ReferenceImage[];
|
|
933
|
+
}
|
|
934
|
+
declare function defaultGuides(canvas: {
|
|
935
|
+
width: number;
|
|
936
|
+
height: number;
|
|
937
|
+
}): PaintGuides;
|
|
938
|
+
declare const MAX_COORDINATE = 1000000;
|
|
939
|
+
declare function normalizeSymmetry(raw: Partial<SymmetryGuide> | Record<string, unknown>, base: SymmetryGuide): SymmetryGuide;
|
|
940
|
+
/** The axis directions of a mirror mode (radians), as photocraft's `directions`. */
|
|
941
|
+
declare function symmetryAxisAngles(g: SymmetryGuide): number[];
|
|
942
|
+
/**
|
|
943
|
+
* The extra copies a stroke paints under the symmetry guide, as affine maps (the stroke itself is
|
|
944
|
+
* not included). Vertical, horizontal and diagonal: one reflection. Dual: two reflections and the
|
|
945
|
+
* point reflection. Kaleidoscope: rotations by 360°/n, and with `mirror` the reflections between them.
|
|
946
|
+
*/
|
|
947
|
+
declare function symmetryCopies(g: SymmetryGuide): Affine[];
|
|
948
|
+
/** The axes clipped to the canvas, for drawing (photocraft's `segments`). */
|
|
949
|
+
declare function symmetryAxisSegments(g: SymmetryGuide, size: {
|
|
950
|
+
width: number;
|
|
951
|
+
height: number;
|
|
952
|
+
}): Array<[Point2, Point2]>;
|
|
953
|
+
declare function normalizePerspective(raw: Partial<PerspectiveGuide> | Record<string, unknown>, base: PerspectiveGuide): PerspectiveGuide;
|
|
954
|
+
/** The line families a perspective guide snaps to at `p`: unit directions. */
|
|
955
|
+
declare function perspectiveDirections(g: PerspectiveGuide, p: Point2): Point2[];
|
|
956
|
+
/** How far the pen must move before a stroke chooses its perspective line (document pixels). */
|
|
957
|
+
declare const SNAP_DECIDE_DISTANCE = 6;
|
|
958
|
+
/**
|
|
959
|
+
* Snaps one stroke to a perspective guide: once the pen has moved `SNAP_DECIDE_DISTANCE` from
|
|
960
|
+
* where it started, the stroke picks the guide line through its start that best matches its
|
|
961
|
+
* direction, and every sample after that is projected onto the line.
|
|
962
|
+
*/
|
|
963
|
+
declare class PerspectiveSnapper {
|
|
964
|
+
private readonly guide;
|
|
965
|
+
private start;
|
|
966
|
+
private dir;
|
|
967
|
+
constructor(guide: PerspectiveGuide);
|
|
968
|
+
/** The line the stroke follows, once chosen. */
|
|
969
|
+
get line(): {
|
|
970
|
+
from: Point2;
|
|
971
|
+
direction: Point2;
|
|
972
|
+
} | null;
|
|
973
|
+
snap<P extends Point2>(p: P): P;
|
|
974
|
+
}
|
|
975
|
+
/** Guide lines to draw for a perspective guide: rays from each vanishing point through the canvas. */
|
|
976
|
+
declare function perspectiveGuideLines(g: PerspectiveGuide, size: {
|
|
977
|
+
width: number;
|
|
978
|
+
height: number;
|
|
979
|
+
}, count?: number): Array<[Point2, Point2]>;
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Colour for painters: hex / RGB / HSV / HSL conversions, a colour wheel that is either the
|
|
983
|
+
* painter's red-yellow-blue wheel (where red's complement is green) or the RGB wheel (where it is
|
|
984
|
+
* cyan), harmonies on it, and swatches.
|
|
985
|
+
*/
|
|
986
|
+
type Rgb255 = [number, number, number];
|
|
987
|
+
declare function parseHexColor(value: unknown, what?: string): Rgb255;
|
|
988
|
+
declare function rgbToHexColor(r: number, g: number, b: number): string;
|
|
989
|
+
/** RGB 0..255 → HSV with h in degrees 0..360, s and v 0..1. */
|
|
990
|
+
declare function rgbToHsv(r: number, g: number, b: number): {
|
|
991
|
+
h: number;
|
|
992
|
+
s: number;
|
|
993
|
+
v: number;
|
|
994
|
+
};
|
|
995
|
+
declare function hsvToRgb(h: number, s: number, v: number): Rgb255;
|
|
996
|
+
/** RGB 0..255 → HSL with h in degrees, s and l 0..1. */
|
|
997
|
+
declare function rgbToHsl(r: number, g: number, b: number): {
|
|
998
|
+
h: number;
|
|
999
|
+
s: number;
|
|
1000
|
+
l: number;
|
|
1001
|
+
};
|
|
1002
|
+
declare function hslToRgb(h: number, s: number, l: number): Rgb255;
|
|
1003
|
+
/** Which wheel hues live on: `ryb` (painter's: red ↔ green complements) or `rgb` (red ↔ cyan). */
|
|
1004
|
+
type ColorWheel = 'ryb' | 'rgb';
|
|
1005
|
+
/** A wheel angle (degrees) → an RGB hue. */
|
|
1006
|
+
declare function wheelToHue(angle: number, wheel: ColorWheel): number;
|
|
1007
|
+
/** An RGB hue → its wheel angle (degrees). */
|
|
1008
|
+
declare function hueToWheel(hue: number, wheel: ColorWheel): number;
|
|
1009
|
+
type HarmonyKind = 'complementary' | 'split-complementary' | 'analogous' | 'triadic' | 'tetradic' | 'square' | 'monochromatic';
|
|
1010
|
+
declare const HARMONY_KINDS: readonly HarmonyKind[];
|
|
1011
|
+
interface HarmonyColor {
|
|
1012
|
+
color: string;
|
|
1013
|
+
/** Position on the wheel, degrees. */
|
|
1014
|
+
angle: number;
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* The colours of a harmony around `base`, the base first. Hue-based harmonies keep the base's
|
|
1018
|
+
* saturation and value and turn it around the wheel; `analogous` spreads by `spread` degrees
|
|
1019
|
+
* (default 30); `monochromatic` keeps the hue and steps the lightness.
|
|
1020
|
+
*/
|
|
1021
|
+
declare function colorHarmony(base: string, kind: HarmonyKind, options?: {
|
|
1022
|
+
wheel?: ColorWheel;
|
|
1023
|
+
spread?: number;
|
|
1024
|
+
}): HarmonyColor[];
|
|
1025
|
+
interface Swatch {
|
|
1026
|
+
color: string;
|
|
1027
|
+
name?: string;
|
|
1028
|
+
}
|
|
1029
|
+
/** A starting palette of our own generic paint colours. */
|
|
1030
|
+
declare const DEFAULT_SWATCHES: readonly Swatch[];
|
|
1031
|
+
|
|
1032
|
+
/**
|
|
1033
|
+
* The mixing pad: a small canvas beside the painting to put paint on, smear it with a palette
|
|
1034
|
+
* knife and pick the mixed colour from. Paint is laid with the Mixer Brush in pigment mode and the
|
|
1035
|
+
* knife is a pigment smudge, so blue and yellow pushed together make green. It is scratch space:
|
|
1036
|
+
* not part of the painting and not in its undo history.
|
|
1037
|
+
*/
|
|
1038
|
+
|
|
1039
|
+
type MixingPadTool = 'paint' | 'knife';
|
|
1040
|
+
declare const MIXING_PAD_WIDTH = 360;
|
|
1041
|
+
declare const MIXING_PAD_HEIGHT = 220;
|
|
1042
|
+
declare class MixingPad {
|
|
1043
|
+
readonly width = 360;
|
|
1044
|
+
readonly height = 220;
|
|
1045
|
+
private surface;
|
|
1046
|
+
/** Increments on every change (for redrawing). */
|
|
1047
|
+
version: number;
|
|
1048
|
+
/** Paints (`paint`, with `color`) or smears (`knife`) through `points` in pad pixels. */
|
|
1049
|
+
stroke(points: readonly PenPoint[], options: {
|
|
1050
|
+
tool: MixingPadTool;
|
|
1051
|
+
color: string;
|
|
1052
|
+
size?: number;
|
|
1053
|
+
}): {
|
|
1054
|
+
bounds: {
|
|
1055
|
+
x: number;
|
|
1056
|
+
y: number;
|
|
1057
|
+
width: number;
|
|
1058
|
+
height: number;
|
|
1059
|
+
} | null;
|
|
1060
|
+
};
|
|
1061
|
+
/** The average colour within `radius` pixels of (x, y), or `null` where the pad is bare. */
|
|
1062
|
+
pick(x: number, y: number, radius?: number): string | null;
|
|
1063
|
+
clear(): void;
|
|
1064
|
+
/** The pad over white, `width × height` RGBA. */
|
|
1065
|
+
pixels(): Uint8ClampedArray;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
/**
|
|
1069
|
+
* `PaintEditorController`: the natural-media paint editor's state, framework-free.
|
|
1070
|
+
*
|
|
1071
|
+
* - The document (canvas, paper, the shared raster layer stack, recorded strokes) is immutable
|
|
1072
|
+
* and tiled copy-on-write; each undo step is a reference to a whole document state.
|
|
1073
|
+
* - Strokes: `beginStroke` gives a live session for pointer input (one undo step at `end`);
|
|
1074
|
+
* `paintStroke` paints a whole stroke from data (agents, tests, replays).
|
|
1075
|
+
* - The brush, colour, Mixer Brush load, view and hand are editor state, not undo steps.
|
|
1076
|
+
* - Rendering goes through the shared compositor with the paper as its bottom layer; exports
|
|
1077
|
+
* are PNG (pure TypeScript), JPEG / WebP (through a canvas or a host encoder) and PSD (through
|
|
1078
|
+
* the shared PSD writer, with the paper as a pixel layer).
|
|
1079
|
+
*/
|
|
1080
|
+
|
|
1081
|
+
/** A refusal with a message that says what is wrong and how to fix it. */
|
|
1082
|
+
declare class PaintEditorError extends Error {
|
|
1083
|
+
constructor(message: string);
|
|
1084
|
+
}
|
|
1085
|
+
interface PaintEditorOptions extends Partial<PaintCanvas> {
|
|
1086
|
+
/** Paper settings, or a library preset id (default cold pressed). */
|
|
1087
|
+
paper?: Partial<PaperSettings> | string;
|
|
1088
|
+
/** Active brush id (default `pencil-6b`). */
|
|
1089
|
+
brushId?: string;
|
|
1090
|
+
/** Saved user brushes to start the library with. */
|
|
1091
|
+
brushes?: readonly PaintBrush[];
|
|
1092
|
+
/** Paint colour #rrggbb (default #1f2937). */
|
|
1093
|
+
color?: string;
|
|
1094
|
+
hand?: Hand;
|
|
1095
|
+
/** Encodes JPEG / WebP (default: a canvas, when there is one). */
|
|
1096
|
+
encodeImage?: ImageEncoder;
|
|
1097
|
+
/** Undo steps kept (default 200). */
|
|
1098
|
+
historyLimit?: number;
|
|
1099
|
+
}
|
|
1100
|
+
interface PaintEditorState {
|
|
1101
|
+
document: PaintDocumentState;
|
|
1102
|
+
/** The active brush (possibly edited from its preset). */
|
|
1103
|
+
brush: PaintBrush;
|
|
1104
|
+
/** The brush library: built-in presets, then the user's brushes. */
|
|
1105
|
+
brushes: readonly PaintBrush[];
|
|
1106
|
+
/** Paint colour #rrggbb. */
|
|
1107
|
+
color: string;
|
|
1108
|
+
/** The second colour (swap with `swapColors`). */
|
|
1109
|
+
secondaryColor: string;
|
|
1110
|
+
/** What the Mixer Brush carries between strokes. */
|
|
1111
|
+
mixerLoad: MixerLoad;
|
|
1112
|
+
view: PaintView;
|
|
1113
|
+
hand: Hand;
|
|
1114
|
+
canUndo: boolean;
|
|
1115
|
+
canRedo: boolean;
|
|
1116
|
+
undoLabel: string | null;
|
|
1117
|
+
redoLabel: string | null;
|
|
1118
|
+
/** Mirror / kaleidoscope, perspective, tracing image and reference images (saved with the document, not undo steps). */
|
|
1119
|
+
guides: PaintGuides;
|
|
1120
|
+
/** The colour swatches (saved with the document, not undo steps). */
|
|
1121
|
+
swatches: readonly Swatch[];
|
|
1122
|
+
/** Changes whenever the mixing pad does. */
|
|
1123
|
+
mixingPadVersion: number;
|
|
1124
|
+
/** A stroke is in progress. */
|
|
1125
|
+
stroking: boolean;
|
|
1126
|
+
/** Increments on every change. */
|
|
1127
|
+
version: number;
|
|
1128
|
+
}
|
|
1129
|
+
/** A layer as tools and panels describe it. */
|
|
1130
|
+
interface PaintLayerInfo {
|
|
1131
|
+
id: string;
|
|
1132
|
+
name: string;
|
|
1133
|
+
type: 'pixel' | 'group';
|
|
1134
|
+
visible: boolean;
|
|
1135
|
+
opacity: number;
|
|
1136
|
+
blend: string;
|
|
1137
|
+
clipped: boolean;
|
|
1138
|
+
locks: LayerLocks;
|
|
1139
|
+
active: boolean;
|
|
1140
|
+
/** Painted content (document pixels), or `null` when empty. */
|
|
1141
|
+
bounds: RasterRect | null;
|
|
1142
|
+
parentId: string | null;
|
|
1143
|
+
depth: number;
|
|
1144
|
+
}
|
|
1145
|
+
interface PaintStrokeInit {
|
|
1146
|
+
/** Default the active layer. */
|
|
1147
|
+
layerId?: string;
|
|
1148
|
+
/** Default the editor colour. */
|
|
1149
|
+
color?: string;
|
|
1150
|
+
/** Default the active brush. */
|
|
1151
|
+
brushId?: string;
|
|
1152
|
+
/** Changes to the brush for this stroke only. */
|
|
1153
|
+
brush?: PaintBrushInput;
|
|
1154
|
+
/** Jitter seed (default: a new one per stroke). */
|
|
1155
|
+
seed?: number;
|
|
1156
|
+
/** Extra copies of the stroke as affine maps (default: the symmetry guide's). */
|
|
1157
|
+
copies?: readonly Affine[];
|
|
1158
|
+
/** Snap the samples to the perspective guide (default true for `beginStroke`, false for `paintStroke`). */
|
|
1159
|
+
snapToPerspective?: boolean;
|
|
1160
|
+
/** Where the stroke starts, for taking its colour from the tracing image before the first sample (`beginStroke`). */
|
|
1161
|
+
start?: {
|
|
1162
|
+
x: number;
|
|
1163
|
+
y: number;
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
interface LayerUpdate {
|
|
1167
|
+
name?: string;
|
|
1168
|
+
visible?: boolean;
|
|
1169
|
+
opacity?: number;
|
|
1170
|
+
blend?: BlendMode;
|
|
1171
|
+
clipped?: boolean;
|
|
1172
|
+
locks?: LayerLocks;
|
|
1173
|
+
}
|
|
1174
|
+
interface FillLayerOptions {
|
|
1175
|
+
layerId?: string;
|
|
1176
|
+
/** Default the editor colour. */
|
|
1177
|
+
color?: string;
|
|
1178
|
+
/** 0..1 (default 1). */
|
|
1179
|
+
opacity?: number;
|
|
1180
|
+
/** Paper grain response 0..1 (default 0: a flat fill); above 0 the fill catches the grain like pastel rubbed across the paper. */
|
|
1181
|
+
grain?: number;
|
|
1182
|
+
/** With grain: pressure 0..1, how far the fill reaches into the valleys (default 0.5). */
|
|
1183
|
+
pressure?: number;
|
|
1184
|
+
blend?: BlendMode;
|
|
1185
|
+
}
|
|
1186
|
+
interface ReplayOptions extends StrokeVariation {
|
|
1187
|
+
/** Recorded stroke ids (see `getStrokes`). */
|
|
1188
|
+
strokeIds: readonly string[];
|
|
1189
|
+
/** Paint on this layer instead of each stroke's own. */
|
|
1190
|
+
layerId?: string;
|
|
1191
|
+
/** Recolour (default each stroke's colour). */
|
|
1192
|
+
color?: string;
|
|
1193
|
+
/** Replay with this brush instead (default each stroke's brush). */
|
|
1194
|
+
brushId?: string;
|
|
1195
|
+
/** Repeat the variation this many times, each one applied on top of the last (default 1). */
|
|
1196
|
+
repeat?: number;
|
|
1197
|
+
}
|
|
1198
|
+
interface RenderOptions {
|
|
1199
|
+
/** Include the paper (default true); without it the result is transparent where nothing is painted. */
|
|
1200
|
+
paper?: boolean;
|
|
1201
|
+
/** Only these layers (a group with its children). */
|
|
1202
|
+
layerIds?: readonly string[];
|
|
1203
|
+
}
|
|
1204
|
+
interface PixelImage {
|
|
1205
|
+
width: number;
|
|
1206
|
+
height: number;
|
|
1207
|
+
data: Uint8ClampedArray;
|
|
1208
|
+
}
|
|
1209
|
+
declare class PaintEditorController {
|
|
1210
|
+
private doc;
|
|
1211
|
+
private extensions;
|
|
1212
|
+
private undoStack;
|
|
1213
|
+
private redoStack;
|
|
1214
|
+
private readonly historyLimit;
|
|
1215
|
+
private brush;
|
|
1216
|
+
private userBrushes;
|
|
1217
|
+
private color;
|
|
1218
|
+
private secondaryColor;
|
|
1219
|
+
private mixerLoad;
|
|
1220
|
+
private view;
|
|
1221
|
+
private hand;
|
|
1222
|
+
private readonly encodeImage;
|
|
1223
|
+
private listeners;
|
|
1224
|
+
private version;
|
|
1225
|
+
private cached;
|
|
1226
|
+
private session;
|
|
1227
|
+
private live;
|
|
1228
|
+
private liveLayers;
|
|
1229
|
+
private dirty;
|
|
1230
|
+
private seedCounter;
|
|
1231
|
+
private strokeCounter;
|
|
1232
|
+
private guides;
|
|
1233
|
+
private swatches;
|
|
1234
|
+
private readonly pad;
|
|
1235
|
+
private tracingVersion;
|
|
1236
|
+
private tracingCache;
|
|
1237
|
+
private readonly imagePixels;
|
|
1238
|
+
private nextReferenceId;
|
|
1239
|
+
private readonly viewCompositor;
|
|
1240
|
+
private readonly exportCompositor;
|
|
1241
|
+
constructor(options?: PaintEditorOptions);
|
|
1242
|
+
subscribe(listener: () => void): () => void;
|
|
1243
|
+
private emit;
|
|
1244
|
+
/** A snapshot of the editor (the same object until something changes). */
|
|
1245
|
+
getState(): PaintEditorState;
|
|
1246
|
+
get document(): PaintDocumentState;
|
|
1247
|
+
/**
|
|
1248
|
+
* Where the rendered stack changed since the last call: rectangles (a stroke's dabs) or `'all'`.
|
|
1249
|
+
* The viewport passes it to `compositeTiles` as `dirty`.
|
|
1250
|
+
*/
|
|
1251
|
+
takeDirty(): RasterRect[] | 'all';
|
|
1252
|
+
private requireIdle;
|
|
1253
|
+
private commit;
|
|
1254
|
+
undo(): string | null;
|
|
1255
|
+
redo(): string | null;
|
|
1256
|
+
/** Labels of the undo steps, oldest first. */
|
|
1257
|
+
historyLabels(): {
|
|
1258
|
+
undo: string[];
|
|
1259
|
+
redo: string[];
|
|
1260
|
+
};
|
|
1261
|
+
private resolvePaper;
|
|
1262
|
+
/** Starts a new canvas: one empty layer over the paper (one undo step; undo brings the old one back). */
|
|
1263
|
+
newCanvas(options?: Partial<PaintCanvas> & {
|
|
1264
|
+
paper?: Partial<PaperSettings> | string;
|
|
1265
|
+
}): PaintDocumentState;
|
|
1266
|
+
/** Changes the canvas size (keeping the pixels where they are, anchored top-left) or resolution. */
|
|
1267
|
+
setCanvas(update: Partial<PaintCanvas>): PaintCanvas;
|
|
1268
|
+
/** Changes the paper: a library preset (`preset`), and / or any of its settings (one undo step). */
|
|
1269
|
+
setPaper(update: Partial<PaperSettings> & {
|
|
1270
|
+
preset?: string;
|
|
1271
|
+
}): PaperSettings;
|
|
1272
|
+
getLayer(id: string): PaintLayer | undefined;
|
|
1273
|
+
/** The active pixel layer (or `undefined`). */
|
|
1274
|
+
getActiveLayer(): PaintLayer | undefined;
|
|
1275
|
+
/** Every layer, top first, as panels and tools describe them. */
|
|
1276
|
+
listLayers(): PaintLayerInfo[];
|
|
1277
|
+
describeLayer(l: PaintLayer, parentId?: string | null, depth?: number): PaintLayerInfo;
|
|
1278
|
+
private requireLayer;
|
|
1279
|
+
/** The pixel layer a paint operation targets, checked for locks and visibility. */
|
|
1280
|
+
private paintTarget;
|
|
1281
|
+
setActiveLayer(id: string): void;
|
|
1282
|
+
/** Adds an empty pixel layer or group above `aboveId` (default: the active layer) and makes it active. */
|
|
1283
|
+
addLayer(options?: {
|
|
1284
|
+
name?: string;
|
|
1285
|
+
type?: 'pixel' | 'group';
|
|
1286
|
+
aboveId?: string;
|
|
1287
|
+
parentId?: string | null;
|
|
1288
|
+
}): PaintLayerInfo;
|
|
1289
|
+
updateLayer(id: string, update: LayerUpdate): PaintLayerInfo;
|
|
1290
|
+
removeLayer(id: string): {
|
|
1291
|
+
removed: string;
|
|
1292
|
+
activeLayerId: string | null;
|
|
1293
|
+
};
|
|
1294
|
+
duplicateLayer(id: string): PaintLayerInfo;
|
|
1295
|
+
/** Moves a layer to `index` (0 = bottom) within `parentId` (default its own parent; `null` = top level). */
|
|
1296
|
+
moveLayer(id: string, index: number, parentId?: string | null): PaintLayerInfo;
|
|
1297
|
+
/** Merges a pixel layer into the pixel layer below it (keeping the lower one's settings). */
|
|
1298
|
+
mergeDown(id: string): PaintLayerInfo;
|
|
1299
|
+
/** Removes every pixel of a layer. */
|
|
1300
|
+
clearLayer(layerId?: string): {
|
|
1301
|
+
layerId: string;
|
|
1302
|
+
bounds: RasterRect | null;
|
|
1303
|
+
};
|
|
1304
|
+
/**
|
|
1305
|
+
* Fills a layer across the canvas with a colour. With `grain` above 0 the fill follows the paper
|
|
1306
|
+
* grain (it catches the peaks, as pastel or charcoal rubbed across the sheet).
|
|
1307
|
+
*/
|
|
1308
|
+
fillLayer(options?: FillLayerOptions): {
|
|
1309
|
+
layerId: string;
|
|
1310
|
+
bounds: RasterRect;
|
|
1311
|
+
};
|
|
1312
|
+
private applyCoverage;
|
|
1313
|
+
private findBrush;
|
|
1314
|
+
/** A library brush by id (`undefined` when unknown). */
|
|
1315
|
+
getBrush(id: string): PaintBrush | undefined;
|
|
1316
|
+
private requireBrush;
|
|
1317
|
+
/**
|
|
1318
|
+
* Chooses the active brush (`id`: a library brush) and / or changes its settings. Editor state,
|
|
1319
|
+
* not an undo step. Returns the active brush.
|
|
1320
|
+
*/
|
|
1321
|
+
setBrush(update?: {
|
|
1322
|
+
id?: string;
|
|
1323
|
+
} & Omit<PaintBrushInput, 'id'>): PaintBrush;
|
|
1324
|
+
/** Saves the active brush (or `brush`) into the user library under `id` / `name`. */
|
|
1325
|
+
saveBrush(options: {
|
|
1326
|
+
id: string;
|
|
1327
|
+
name?: string;
|
|
1328
|
+
brush?: PaintBrushInput;
|
|
1329
|
+
}): PaintBrush;
|
|
1330
|
+
removeBrush(id: string): void;
|
|
1331
|
+
/** The user's saved brushes. */
|
|
1332
|
+
getUserBrushes(): PaintBrush[];
|
|
1333
|
+
/** A brush library file of the given brushes (default: the user's brushes). */
|
|
1334
|
+
exportBrushes(ids?: readonly string[]): BrushLibraryFile;
|
|
1335
|
+
/**
|
|
1336
|
+
* Adds the brushes of a library file to the user library. A brush whose id is taken is skipped
|
|
1337
|
+
* unless `replace` (built-in ids are always skipped). Returns the ids added and the problems.
|
|
1338
|
+
*/
|
|
1339
|
+
importBrushes(file: unknown, options?: {
|
|
1340
|
+
replace?: boolean;
|
|
1341
|
+
}): {
|
|
1342
|
+
added: string[];
|
|
1343
|
+
skipped: string[];
|
|
1344
|
+
problems: string[];
|
|
1345
|
+
};
|
|
1346
|
+
/** A thumbnail of a brush: a test stroke on the current paper. */
|
|
1347
|
+
brushThumbnail(id: string, options?: {
|
|
1348
|
+
width?: number;
|
|
1349
|
+
height?: number;
|
|
1350
|
+
color?: string;
|
|
1351
|
+
}): BrushThumbnail;
|
|
1352
|
+
setColor(color: string): string;
|
|
1353
|
+
setSecondaryColor(color: string): string;
|
|
1354
|
+
swapColors(): void;
|
|
1355
|
+
/** The colour at a document pixel: of the composite (paper included) or of one layer. */
|
|
1356
|
+
pickColor(x: number, y: number, options?: {
|
|
1357
|
+
layerId?: string;
|
|
1358
|
+
}): {
|
|
1359
|
+
color: string;
|
|
1360
|
+
alpha: number;
|
|
1361
|
+
};
|
|
1362
|
+
/** Fills the Mixer Brush reservoir with a colour (default the editor colour). */
|
|
1363
|
+
loadMixerBrush(color?: string): MixerLoad;
|
|
1364
|
+
/** Empties the pickup well (and with `emptyReservoir` the loaded paint too). */
|
|
1365
|
+
cleanMixerBrush(emptyReservoir?: boolean): MixerLoad;
|
|
1366
|
+
/** Zoom, pan, rotation and mirroring: view state only, the pixels never resample. */
|
|
1367
|
+
setView(update: Partial<PaintView>): PaintView;
|
|
1368
|
+
resetView(): PaintView;
|
|
1369
|
+
setHand(hand: Hand): Hand;
|
|
1370
|
+
private strokeBrush;
|
|
1371
|
+
/** Extra copies every new stroke paints (the painting guides add mirror and kaleidoscope copies). */
|
|
1372
|
+
protected strokeCopies(): Affine[];
|
|
1373
|
+
private sessionFor;
|
|
1374
|
+
private record;
|
|
1375
|
+
/**
|
|
1376
|
+
* Starts a live stroke for pointer input: `push` raw pen samples (document pixels), then `end()`
|
|
1377
|
+
* (one undo step) or `cancel()`.
|
|
1378
|
+
*/
|
|
1379
|
+
beginStroke(init?: PaintStrokeInit): PaintStrokeSession;
|
|
1380
|
+
/** The current live stroke, if any. */
|
|
1381
|
+
get activeStroke(): PaintStrokeSession | null;
|
|
1382
|
+
/** Runs a whole stroke against `doc` without touching the editor; returns the new document. */
|
|
1383
|
+
private runStroke;
|
|
1384
|
+
/** Paints one stroke from data (one undo step). */
|
|
1385
|
+
paintStroke(init: PaintStrokeInit & {
|
|
1386
|
+
points: readonly PenPoint[];
|
|
1387
|
+
}): PaintStrokeResult;
|
|
1388
|
+
private checkPoints;
|
|
1389
|
+
/** Every recorded stroke, oldest first. */
|
|
1390
|
+
getStrokes(): readonly RecordedStroke[];
|
|
1391
|
+
/**
|
|
1392
|
+
* Replays recorded strokes, varied (moved, scaled, rotated, flipped, recoloured, with another
|
|
1393
|
+
* brush or layer), `repeat` times: all of it one undo step. Returns the new strokes.
|
|
1394
|
+
*/
|
|
1395
|
+
replayStrokes(options: ReplayOptions): {
|
|
1396
|
+
strokeIds: string[];
|
|
1397
|
+
bounds: RasterRect | null;
|
|
1398
|
+
layerIds: string[];
|
|
1399
|
+
};
|
|
1400
|
+
private sources;
|
|
1401
|
+
/** The layers with the live stroke's surface in place. */
|
|
1402
|
+
private currentLayers;
|
|
1403
|
+
/** View-only layers between the paper and the paint (the painting guides add the tracing image). */
|
|
1404
|
+
protected underlayLayers(): ViewLayer[];
|
|
1405
|
+
/** The tracing image resampled onto the canvas (cached until it or the canvas changes). */
|
|
1406
|
+
private tracingNode;
|
|
1407
|
+
private renderLayers;
|
|
1408
|
+
/**
|
|
1409
|
+
* Composites the viewport stack (paper, view-only underlays, layers with the live stroke) tile by
|
|
1410
|
+
* tile over `rect`, calling `onTile` with each 256 px tile's RGBA (cached: unchanged tiles come back
|
|
1411
|
+
* as the same buffer). Pass `takeDirty()` as `dirty`.
|
|
1412
|
+
*/
|
|
1413
|
+
compositeTiles(rect: RasterRect, onTile: (tx: number, ty: number, data: Uint8ClampedArray, changed: boolean) => void, options?: {
|
|
1414
|
+
dirty?: RasterRect[] | 'all';
|
|
1415
|
+
}): void;
|
|
1416
|
+
/** The canvas as canvas-sized RGBA (paper included by default). */
|
|
1417
|
+
compositePixels(options?: RenderOptions): PixelImage;
|
|
1418
|
+
/** The canvas (or some layers), scaled so the longest side is at most `maxSide`. */
|
|
1419
|
+
renderPreview(options?: RenderOptions & {
|
|
1420
|
+
maxSide?: number;
|
|
1421
|
+
}): PixelImage;
|
|
1422
|
+
/** The document as JSON with inline PNG assets. */
|
|
1423
|
+
toJSON(options?: {
|
|
1424
|
+
strokes?: boolean;
|
|
1425
|
+
}): SerializedPaintDocument;
|
|
1426
|
+
/** The document as JSON with each PNG stored in `store`. */
|
|
1427
|
+
toJSONExternal(store: PaintAssetStore, options?: {
|
|
1428
|
+
strokes?: boolean;
|
|
1429
|
+
}): Promise<SerializedPaintDocument>;
|
|
1430
|
+
private adopt;
|
|
1431
|
+
/** Opens a saved document (clears the history). Returns what could not be read. */
|
|
1432
|
+
loadJSON(raw: unknown): string[];
|
|
1433
|
+
/** Opens a saved document whose assets may live in `store`. */
|
|
1434
|
+
loadJSONAsync(raw: unknown, store?: PaintAssetStore): Promise<string[]>;
|
|
1435
|
+
/** The canvas as PNG bytes (paper included by default; `scale` below 1 shrinks it). */
|
|
1436
|
+
exportPng(options?: RenderOptions & {
|
|
1437
|
+
scale?: number;
|
|
1438
|
+
}): Uint8Array;
|
|
1439
|
+
private scaled;
|
|
1440
|
+
/**
|
|
1441
|
+
* The canvas as PNG, JPEG or WebP bytes. JPEG has no alpha, so it always includes the paper.
|
|
1442
|
+
* JPEG and WebP need a canvas (a browser or Worker) or the `encodeImage` option.
|
|
1443
|
+
*/
|
|
1444
|
+
exportImage(options?: RenderOptions & {
|
|
1445
|
+
format?: ImageFormat;
|
|
1446
|
+
quality?: number;
|
|
1447
|
+
scale?: number;
|
|
1448
|
+
}): Promise<Uint8Array>;
|
|
1449
|
+
/**
|
|
1450
|
+
* The document as an 8-bit RGB PSD through the shared PSD writer: every layer and group, with
|
|
1451
|
+
* the paper as a locked "Paper" pixel layer at the bottom (it is generated, so PSD gets its
|
|
1452
|
+
* pixels), and the composite as the merged image.
|
|
1453
|
+
*/
|
|
1454
|
+
exportPsd(): PsdExportResult;
|
|
1455
|
+
/** `render_preview` as a base64 PNG. */
|
|
1456
|
+
previewPngBase64(options?: RenderOptions & {
|
|
1457
|
+
maxSide?: number;
|
|
1458
|
+
}): {
|
|
1459
|
+
data: string;
|
|
1460
|
+
width: number;
|
|
1461
|
+
height: number;
|
|
1462
|
+
};
|
|
1463
|
+
private setGuides;
|
|
1464
|
+
/** Mirror and kaleidoscope painting: every new stroke paints its copies too (one stroke, one undo step). */
|
|
1465
|
+
setSymmetry(update: Partial<SymmetryGuide>): SymmetryGuide;
|
|
1466
|
+
/** Perspective guides with one to three vanishing points that pointer strokes snap to. */
|
|
1467
|
+
setPerspective(update: Partial<PerspectiveGuide>): PerspectiveGuide;
|
|
1468
|
+
/** An image from a PNG (base64 or data URL) or from RGBA pixels, stored as a PNG data URL. */
|
|
1469
|
+
private importImage;
|
|
1470
|
+
private decodeImage;
|
|
1471
|
+
/**
|
|
1472
|
+
* Puts an image under the paint to trace over: view only, never in exports. Placed to fit the
|
|
1473
|
+
* canvas unless `rect` is given; `sampleColors` makes strokes take their colour from it.
|
|
1474
|
+
*/
|
|
1475
|
+
setTracingImage(options: {
|
|
1476
|
+
png?: string;
|
|
1477
|
+
pixels?: PixelImage;
|
|
1478
|
+
rect?: TracingImage['rect'];
|
|
1479
|
+
opacity?: number;
|
|
1480
|
+
sampleColors?: boolean;
|
|
1481
|
+
}): TracingImage;
|
|
1482
|
+
/** Changes the tracing image's opacity, visibility, placement or colour sampling. */
|
|
1483
|
+
updateTracing(update: Partial<Pick<TracingImage, 'opacity' | 'visible' | 'sampleColors' | 'rect'>>, base?: TracingImage | null): TracingImage;
|
|
1484
|
+
clearTracingImage(): void;
|
|
1485
|
+
/** The tracing image's colour at a document pixel, or `null` outside it or where it is clear. */
|
|
1486
|
+
sampleTracingColor(x: number, y: number): string | null;
|
|
1487
|
+
/** Adds a floating reference image to look at and pick colours from. */
|
|
1488
|
+
addReferenceImage(options: {
|
|
1489
|
+
png?: string;
|
|
1490
|
+
pixels?: PixelImage;
|
|
1491
|
+
name?: string;
|
|
1492
|
+
x?: number;
|
|
1493
|
+
y?: number;
|
|
1494
|
+
displayWidth?: number;
|
|
1495
|
+
}): ReferenceImage;
|
|
1496
|
+
private requireReference;
|
|
1497
|
+
/** Moves, resizes, renames, shows or hides a reference image. */
|
|
1498
|
+
updateReferenceImage(id: string, update: Partial<Pick<ReferenceImage, 'name' | 'x' | 'y' | 'displayWidth' | 'visible'>>): ReferenceImage;
|
|
1499
|
+
removeReferenceImage(id: string): void;
|
|
1500
|
+
/** A reference image's pixels (decoded once). */
|
|
1501
|
+
referencePixels(id: string): PixelImage;
|
|
1502
|
+
/** The colour of a reference image at its pixel (x, y). */
|
|
1503
|
+
pickReferenceColor(id: string, x: number, y: number): string;
|
|
1504
|
+
private readGuides;
|
|
1505
|
+
/** Colour harmonies around a colour (default the paint colour) on the painter's RYB wheel or the RGB wheel. */
|
|
1506
|
+
harmony(kind: HarmonyKind, options?: {
|
|
1507
|
+
color?: string;
|
|
1508
|
+
wheel?: ColorWheel;
|
|
1509
|
+
spread?: number;
|
|
1510
|
+
}): HarmonyColor[];
|
|
1511
|
+
addSwatch(color: string, name?: string): Swatch[];
|
|
1512
|
+
removeSwatch(index: number): Swatch[];
|
|
1513
|
+
setSwatches(swatches: readonly Swatch[]): Swatch[];
|
|
1514
|
+
get mixingPad(): MixingPad;
|
|
1515
|
+
/** Paints (`paint`) or smears with the palette knife (`knife`) on the mixing pad, in pad pixels. */
|
|
1516
|
+
mixingPadStroke(points: readonly PenPoint[], options?: {
|
|
1517
|
+
tool?: MixingPadTool;
|
|
1518
|
+
color?: string;
|
|
1519
|
+
size?: number;
|
|
1520
|
+
}): {
|
|
1521
|
+
bounds: RasterRect | null;
|
|
1522
|
+
};
|
|
1523
|
+
/** Picks the mixed colour on the pad (averaged over `radius` px); with `setColor` it becomes the paint colour. */
|
|
1524
|
+
mixingPadPick(x: number, y: number, options?: {
|
|
1525
|
+
radius?: number;
|
|
1526
|
+
setColor?: boolean;
|
|
1527
|
+
}): string | null;
|
|
1528
|
+
clearMixingPad(): void;
|
|
1529
|
+
/** A layer's tile surface (live while stroking), for thumbnails. */
|
|
1530
|
+
layerSurface(id: string): TiledSurface | null;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
/** Largest tracing or reference image side, in pixels. */
|
|
1534
|
+
declare const MAX_IMAGE_SIDE = 8192;
|
|
1535
|
+
|
|
1536
|
+
/**
|
|
1537
|
+
* Agent tools for the paint editor (`createPaintEditorToolProvider`). Strict schemas, every
|
|
1538
|
+
* argument described, all four annotations. Each write is one undo step (or editor state, said so)
|
|
1539
|
+
* and returns what it changed; ids come from the read tools (`list_layers`, `list_brushes`,
|
|
1540
|
+
* `list_papers`, `list_strokes`).
|
|
1541
|
+
*/
|
|
1542
|
+
|
|
1543
|
+
interface PaintEditorToolProviderOptions {
|
|
1544
|
+
/** Provider id (unique per registry). Default `paint-editor`. */
|
|
1545
|
+
id?: string;
|
|
1546
|
+
/** Always prefix tool names with `<namespace>__`. */
|
|
1547
|
+
namespace?: string;
|
|
1548
|
+
}
|
|
1549
|
+
/** Agent tools for one paint editor. */
|
|
1550
|
+
declare function createPaintEditorToolProvider(controller: PaintEditorController, options?: PaintEditorToolProviderOptions): AgentToolProvider;
|
|
1551
|
+
|
|
1552
|
+
/**
|
|
1553
|
+
* Framework-free UI logic shared by the React and Svelte paint editors, so both behave the same:
|
|
1554
|
+
*
|
|
1555
|
+
* - `PaintGesture` turns pointer events (structurally typed, no DOM import) into strokes (with
|
|
1556
|
+
* coalesced samples and a predicted tail), colour picks, pans, view rotation and zoom, with palm
|
|
1557
|
+
* rejection while a pen is down.
|
|
1558
|
+
* - `PaintCanvasPresenter` keeps a document-sized 2D canvas up to date from the compositor,
|
|
1559
|
+
* uploading only the tiles that changed.
|
|
1560
|
+
* - Guide overlays as line segments, and the slider tables of the brush and paper panels.
|
|
1561
|
+
*/
|
|
1562
|
+
|
|
1563
|
+
/** The Pointer Event fields the gesture reads. */
|
|
1564
|
+
interface PaintPointerEvent extends PointerSample {
|
|
1565
|
+
pointerId: number;
|
|
1566
|
+
clientX: number;
|
|
1567
|
+
clientY: number;
|
|
1568
|
+
button?: number;
|
|
1569
|
+
buttons?: number;
|
|
1570
|
+
shiftKey?: boolean;
|
|
1571
|
+
altKey?: boolean;
|
|
1572
|
+
ctrlKey?: boolean;
|
|
1573
|
+
metaKey?: boolean;
|
|
1574
|
+
getCoalescedEvents?(): PaintPointerEvent[];
|
|
1575
|
+
getPredictedEvents?(): PaintPointerEvent[];
|
|
1576
|
+
}
|
|
1577
|
+
type PaintTool = 'brush' | 'eyedropper' | 'pan' | 'rotate';
|
|
1578
|
+
declare const PAINT_TOOLS: ReadonlyArray<{
|
|
1579
|
+
id: PaintTool;
|
|
1580
|
+
label: string;
|
|
1581
|
+
key: string;
|
|
1582
|
+
}>;
|
|
1583
|
+
/** Where the viewport element is on screen and how big it is (CSS pixels). */
|
|
1584
|
+
interface ViewportBox {
|
|
1585
|
+
left: number;
|
|
1586
|
+
top: number;
|
|
1587
|
+
width: number;
|
|
1588
|
+
height: number;
|
|
1589
|
+
}
|
|
1590
|
+
declare class PaintGesture {
|
|
1591
|
+
private readonly controller;
|
|
1592
|
+
private readonly box;
|
|
1593
|
+
tool: PaintTool;
|
|
1594
|
+
/** The last refusal (a hidden or locked layer), for the editor to show. */
|
|
1595
|
+
error: string | null;
|
|
1596
|
+
/** The predicted tail of the current stroke in document pixels (draw it as a thin preview). */
|
|
1597
|
+
predicted: PenPoint[];
|
|
1598
|
+
private mode;
|
|
1599
|
+
private penAt;
|
|
1600
|
+
private penDown;
|
|
1601
|
+
constructor(controller: PaintEditorController, box: () => ViewportBox);
|
|
1602
|
+
private doc;
|
|
1603
|
+
private pen;
|
|
1604
|
+
/** Pointer down; returns true when the gesture took the pointer (capture it). */
|
|
1605
|
+
down(e: PaintPointerEvent): boolean;
|
|
1606
|
+
private pick;
|
|
1607
|
+
move(e: PaintPointerEvent): void;
|
|
1608
|
+
up(e: PaintPointerEvent): void;
|
|
1609
|
+
cancel(): void;
|
|
1610
|
+
get active(): boolean;
|
|
1611
|
+
/** Wheel: zoom about the pointer (Ctrl / pinch or plain wheel), Shift+wheel rotates the view. */
|
|
1612
|
+
wheel(e: {
|
|
1613
|
+
clientX: number;
|
|
1614
|
+
clientY: number;
|
|
1615
|
+
deltaY: number;
|
|
1616
|
+
shiftKey?: boolean;
|
|
1617
|
+
}): void;
|
|
1618
|
+
}
|
|
1619
|
+
/** Keyboard shortcuts shared by both editors; returns true when it handled the key. */
|
|
1620
|
+
declare function handlePaintShortcut(controller: PaintEditorController, gesture: PaintGesture, e: {
|
|
1621
|
+
key: string;
|
|
1622
|
+
ctrlKey?: boolean;
|
|
1623
|
+
metaKey?: boolean;
|
|
1624
|
+
shiftKey?: boolean;
|
|
1625
|
+
target?: unknown;
|
|
1626
|
+
}): boolean;
|
|
1627
|
+
/** The 2D context methods the presenter uses (a `CanvasRenderingContext2D`). */
|
|
1628
|
+
interface Paint2DContext {
|
|
1629
|
+
createImageData(width: number, height: number): {
|
|
1630
|
+
data: Uint8ClampedArray;
|
|
1631
|
+
};
|
|
1632
|
+
putImageData(image: never, dx: number, dy: number, sx?: number, sy?: number, sw?: number, sh?: number): void;
|
|
1633
|
+
}
|
|
1634
|
+
/** Keeps a document-sized canvas in step with the controller, tile by tile. */
|
|
1635
|
+
declare class PaintCanvasPresenter {
|
|
1636
|
+
private readonly controller;
|
|
1637
|
+
private readonly uploaded;
|
|
1638
|
+
private scratch;
|
|
1639
|
+
private size;
|
|
1640
|
+
constructor(controller: PaintEditorController);
|
|
1641
|
+
/** Draws what changed into `ctx` (a canvas the size of the document); returns the tiles drawn. */
|
|
1642
|
+
render(ctx: Paint2DContext): number;
|
|
1643
|
+
}
|
|
1644
|
+
/** Lines to draw over the canvas for the guides, in document pixels, with a style key. */
|
|
1645
|
+
declare function guideOverlay(controller: PaintEditorController): Array<{
|
|
1646
|
+
from: Point2;
|
|
1647
|
+
to: Point2;
|
|
1648
|
+
kind: 'symmetry' | 'perspective' | 'snap';
|
|
1649
|
+
}>;
|
|
1650
|
+
/** One slider of a panel: a path into the brush (or paper) settings. */
|
|
1651
|
+
interface SliderControl {
|
|
1652
|
+
path: string;
|
|
1653
|
+
label: string;
|
|
1654
|
+
min: number;
|
|
1655
|
+
max: number;
|
|
1656
|
+
step: number;
|
|
1657
|
+
}
|
|
1658
|
+
declare const BRUSH_SLIDERS: readonly SliderControl[];
|
|
1659
|
+
declare const PAPER_SLIDERS: readonly SliderControl[];
|
|
1660
|
+
/** Reads `a.b` from an object. */
|
|
1661
|
+
declare function readPath(obj: unknown, path: string): number;
|
|
1662
|
+
/** `{a: {b: value}}` for `a.b`, keeping the rest of `obj`'s `a`. */
|
|
1663
|
+
declare function patchPath(obj: Record<string, unknown>, path: string, value: number): Record<string, unknown>;
|
|
1664
|
+
/** The point on a colour wheel of `radius` (centre 0, 0) for a wheel angle in degrees, red at the top. */
|
|
1665
|
+
declare function wheelPoint(angle: number, radius: number): Point2;
|
|
1666
|
+
/** The wheel angle (degrees, red at the top, clockwise) of a point relative to the wheel centre. */
|
|
1667
|
+
declare function wheelAngle(x: number, y: number): number;
|
|
1668
|
+
/**
|
|
1669
|
+
* A hue ring for a colour wheel, `size × size` RGBA: hues at full saturation and value between
|
|
1670
|
+
* `inner` and 1 of the radius, red at the top, laid out on the RYB or RGB wheel.
|
|
1671
|
+
*/
|
|
1672
|
+
declare function renderColorWheel(size: number, wheel: ColorWheel, inner?: number): {
|
|
1673
|
+
width: number;
|
|
1674
|
+
height: number;
|
|
1675
|
+
data: Uint8ClampedArray;
|
|
1676
|
+
};
|
|
1677
|
+
/** The colour for a click on the wheel ring at (dx, dy) from its centre, keeping `color`'s saturation and value. */
|
|
1678
|
+
declare function wheelPick(dx: number, dy: number, wheel: ColorWheel, color: string): string;
|
|
1679
|
+
|
|
1680
|
+
export { type Affine, BRUSH_ID, BRUSH_ROTATIONS, BRUSH_SLIDERS, BUILTIN_BRUSHES, BlendMode, type BrushDynamics, type BrushLibraryFile, type BrushRotation, type BrushThumbnail, type ColorWheel, DEFAULT_BRUSH_ID, DEFAULT_DPI, DEFAULT_DYNAMICS, DEFAULT_PAPER, DEFAULT_SWATCHES, DEFAULT_VIEW, DryStroke, type DryStrokeOptions, type FillLayerOptions, GRAIN_PATTERNS, GRAIN_TILE, type GrainPattern, type GrainTuning, HARMONY_KINDS, type Hand, type HarmonyColor, type HarmonyKind, IDENTITY_AFFINE, IDENTITY_COPY, type ImageEncoder, type ImageFormat, type ImageNode, type LayerUpdate, type Linear2, type LoadedPaintDocument, MAX_CANVAS_SIDE, MAX_COORDINATE, MAX_IMAGE_SIDE, MAX_ZOOM, MIN_CANVAS_SIDE, MIN_ZOOM, MIXING_PAD_HEIGHT, MIXING_PAD_WIDTH, MixingPad, type MixingPadTool, PAINT_BRUSH_CATEGORIES, PAINT_BRUSH_ENGINES, PAINT_FORMAT, PAINT_SCHEMA_VERSION, PAINT_TOOLS, PAPER_FAMILIES, PAPER_LIBRARY, PAPER_NODE_ID, PAPER_SLIDERS, type Paint2DContext, type PaintAssetStore, type PaintBrush, type PaintBrushCategory, type PaintBrushEngine, type PaintBrushInput, type PaintCanvas, PaintCanvasPresenter, PaintDocumentError, type PaintDocumentExtensions, type PaintDocumentState, PaintEditorController, PaintEditorError, type PaintEditorOptions, type PaintEditorState, type PaintEditorToolProviderOptions, PaintGesture, type PaintGroupLayer, type PaintGuides, type PaintLayer, type PaintLayerInfo, type PaintPixelLayer, type PaintPointerEvent, type PaintStrokeInit, type PaintStrokeResult, PaintStrokeSession, type PaintTool, type PaintView, type PaperFamily, PaperGrain, type PaperNode, type PaperSettings, type PaperTiling, type PenDab, PenDabWalker, type PenPoint, PenSmoother, type PerspectiveGuide, PerspectiveSnapper, type PixelImage, type Point2, type PointerSample, type RecordedStroke, type ReferenceImage, type RenderOptions, type ReplayOptions, type Rgb255, SNAP_DECIDE_DISTANCE, SYMMETRY_MODES, type SerializeOptions, type SerializedPaintDocument, type SliderControl, type StrokeCopy, type StrokeSessionHost, type StrokeSessionInit, type StrokeVariation, type Swatch, type SymmetryGuide, type SymmetryMode, TRACING_NODE_ID, TileableNoise, type TracingImage, type ViewLayer, type ViewportBox, affineCopy, applyAffine, brushToJSON, clonePaper, colorHarmony, composeAffine, createPaintCompositor, createPaintDocument, createPaintEditorToolProvider, createPaintLayer, decodeImageBlob, defaultGuides, defaultImageEncoder, deserializePaintDocument, deserializePaintDocumentAsync, documentExtensions, documentToScreen, downloadBytes, downsampleRgba, fitZoom, grainCatch, grainHeights, guideOverlay, handlePaintShortcut, hash2, hexToRgb8, hslToRgb, hsvToRgb, hueToWheel, invertAffine, isPalmTouch, mapPenPoint, mix32, mixerLoadForStroke, mixerTip, newPaintLayerId, normalizeBrushStroke, normalizeCanvas, normalizeDynamics, normalizePaintBrush, normalizePaper, normalizePerspective, normalizeRotation, normalizeSymmetry, normalizeView, paperGrain, paperKey, paperNode, paperPreset, parseBrushLibrary, parseHexColor, patchPath, penOrientation, perspectiveDirections, perspectiveGuideLines, pointerToPenPoint, rand01, randSigned, readPath, renderBrushThumbnail, renderColorWheel, renderPaper, rgbToHexColor, rgbToHsl, rgbToHsv, samplePressure, screenToDocument, seedFromString, serializePaintDocument, serializePaintDocumentExternal, strokeBounds, symmetryAxisAngles, symmetryAxisSegments, symmetryCopies, thumbnailStroke, tiltToOrientation, toStrokePoint, variationAffine, varyStroke, viewCssTransform, viewMatrix, viewPenToDocument, wheelAngle, wheelPick, wheelPoint, wheelToHue };
|