@pixerate/editor 0.238.0 → 0.240.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/dist/chunk-KMPM5JM5.js +936 -0
- package/dist/chunk-S6OT6CTS.cjs +936 -0
- package/dist/gradient/index.cjs +102 -812
- package/dist/gradient/index.d.cts +1 -0
- package/dist/gradient/index.d.ts +1 -0
- package/dist/gradient/index.js +85 -795
- package/dist/gradient/sample.d.cts +69 -0
- package/dist/gradient/sample.d.ts +69 -0
- package/dist/image-editor/index.cjs +48 -1
- package/dist/image-editor/index.js +48 -1
- package/dist/paint-editor/controller.d.cts +91 -1
- package/dist/paint-editor/controller.d.ts +91 -1
- package/dist/paint-editor/document.d.cts +7 -2
- package/dist/paint-editor/document.d.ts +7 -2
- package/dist/paint-editor/index.cjs +743 -173
- package/dist/paint-editor/index.d.cts +1 -0
- package/dist/paint-editor/index.d.ts +1 -0
- package/dist/paint-editor/index.js +608 -38
- package/dist/paint-editor/live-strokes.d.cts +24 -0
- package/dist/paint-editor/live-strokes.d.ts +24 -0
- package/dist/paint-editor/ui.d.cts +24 -4
- package/dist/paint-editor/ui.d.ts +24 -4
- package/package.json +1 -1
- package/skills/pixerate-paint-editor/SKILL.md +30 -15
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gradients from sampled colours: `simplifyGradientStops` reduces a noisy run of colours (pixels
|
|
3
|
+
* read along a line from a photo, a paint stroke or a shape) to the few stops that reproduce it
|
|
4
|
+
* within a perceptual tolerance, and `sampleGradientFromLine` reads those colours from any pixel
|
|
5
|
+
* reader and returns a linear gradient.
|
|
6
|
+
*
|
|
7
|
+
* Distances are Euclidean in OKLab (ΔE OK, about 0.02 is a just-noticeable step), against the
|
|
8
|
+
* OKLab interpolation the returned gradients use, so what is measured is what is drawn.
|
|
9
|
+
*/
|
|
10
|
+
import type { Gradient, GradientStop } from './types.cjs';
|
|
11
|
+
export interface SimplifyGradientStopsOptions {
|
|
12
|
+
/** Largest perceptual error left between the samples and the stops, in OKLab ΔE (default 0.06). */
|
|
13
|
+
tolerance?: number;
|
|
14
|
+
/** Fewest stops to return (default 2, at least 2). */
|
|
15
|
+
minStops?: number;
|
|
16
|
+
/** Most stops to return (default 5, at least `minStops`). */
|
|
17
|
+
maxStops?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Samples on each side averaged into each one before fitting, to ignore grain, JPEG noise and
|
|
20
|
+
* brush texture (default 2; 0 fits the raw samples).
|
|
21
|
+
*/
|
|
22
|
+
smoothing?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface RawGradientSample {
|
|
25
|
+
/** Position along the line, 0..1. */
|
|
26
|
+
offset: number;
|
|
27
|
+
/** Any CSS colour, or RGBA bytes `[r, g, b, a]` (0..255). */
|
|
28
|
+
color: string | readonly [number, number, number, number];
|
|
29
|
+
}
|
|
30
|
+
export interface SimplifiedGradientStops {
|
|
31
|
+
stops: GradientStop[];
|
|
32
|
+
/** The largest OKLab distance left between a (smoothed) sample and the stops. */
|
|
33
|
+
maxError: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Like `simplifyGradientStops`, with the largest error left. Throws a sentence when the input
|
|
37
|
+
* can't make a gradient (fewer than two readable samples, or options out of range).
|
|
38
|
+
*/
|
|
39
|
+
export declare function fitGradientStops(samples: readonly RawGradientSample[], options?: SimplifyGradientStopsOptions): SimplifiedGradientStops;
|
|
40
|
+
/**
|
|
41
|
+
* Reduces colours sampled along a line (offsets 0..1) to a minimal set of gradient stops. The
|
|
42
|
+
* first and last stops sit at 0 and 1; stops are added where the colour turns most until every
|
|
43
|
+
* sample is within `tolerance` (OKLab ΔE) of the OKLab blend between its stops, or `maxStops` is
|
|
44
|
+
* reached. Samples that can't be read (a bad colour string) are skipped. Throws when fewer than two
|
|
45
|
+
* samples can be read.
|
|
46
|
+
*/
|
|
47
|
+
export declare function simplifyGradientStops(samples: readonly RawGradientSample[], options?: SimplifyGradientStopsOptions): GradientStop[];
|
|
48
|
+
export interface LineSamplingOptions extends SimplifyGradientStopsOptions {
|
|
49
|
+
/** Colours read along the line, ends included (default 32, 2..1024). */
|
|
50
|
+
sampleCount?: number;
|
|
51
|
+
/** Snap the line's angle to multiples of this many degrees (e.g. 15, for Shift), keeping its length. */
|
|
52
|
+
snapStep?: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Reads colours along the line from `start` to `end` with `readPixel` (which returns a CSS colour,
|
|
56
|
+
* RGBA bytes, or null where there is nothing to read) and returns a linear gradient (OKLab
|
|
57
|
+
* interpolation) whose angle is the line's (CSS degrees) and whose stops are the simplified
|
|
58
|
+
* colours, offsets measured along the line. Returns null when fewer than two points could be read.
|
|
59
|
+
*/
|
|
60
|
+
export declare function sampleGradientFromLine(readPixel: (point: {
|
|
61
|
+
x: number;
|
|
62
|
+
y: number;
|
|
63
|
+
}) => string | readonly [number, number, number, number] | null, start: {
|
|
64
|
+
x: number;
|
|
65
|
+
y: number;
|
|
66
|
+
}, end: {
|
|
67
|
+
x: number;
|
|
68
|
+
y: number;
|
|
69
|
+
}, options?: LineSamplingOptions): Gradient | null;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gradients from sampled colours: `simplifyGradientStops` reduces a noisy run of colours (pixels
|
|
3
|
+
* read along a line from a photo, a paint stroke or a shape) to the few stops that reproduce it
|
|
4
|
+
* within a perceptual tolerance, and `sampleGradientFromLine` reads those colours from any pixel
|
|
5
|
+
* reader and returns a linear gradient.
|
|
6
|
+
*
|
|
7
|
+
* Distances are Euclidean in OKLab (ΔE OK, about 0.02 is a just-noticeable step), against the
|
|
8
|
+
* OKLab interpolation the returned gradients use, so what is measured is what is drawn.
|
|
9
|
+
*/
|
|
10
|
+
import type { Gradient, GradientStop } from './types.js';
|
|
11
|
+
export interface SimplifyGradientStopsOptions {
|
|
12
|
+
/** Largest perceptual error left between the samples and the stops, in OKLab ΔE (default 0.06). */
|
|
13
|
+
tolerance?: number;
|
|
14
|
+
/** Fewest stops to return (default 2, at least 2). */
|
|
15
|
+
minStops?: number;
|
|
16
|
+
/** Most stops to return (default 5, at least `minStops`). */
|
|
17
|
+
maxStops?: number;
|
|
18
|
+
/**
|
|
19
|
+
* Samples on each side averaged into each one before fitting, to ignore grain, JPEG noise and
|
|
20
|
+
* brush texture (default 2; 0 fits the raw samples).
|
|
21
|
+
*/
|
|
22
|
+
smoothing?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface RawGradientSample {
|
|
25
|
+
/** Position along the line, 0..1. */
|
|
26
|
+
offset: number;
|
|
27
|
+
/** Any CSS colour, or RGBA bytes `[r, g, b, a]` (0..255). */
|
|
28
|
+
color: string | readonly [number, number, number, number];
|
|
29
|
+
}
|
|
30
|
+
export interface SimplifiedGradientStops {
|
|
31
|
+
stops: GradientStop[];
|
|
32
|
+
/** The largest OKLab distance left between a (smoothed) sample and the stops. */
|
|
33
|
+
maxError: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Like `simplifyGradientStops`, with the largest error left. Throws a sentence when the input
|
|
37
|
+
* can't make a gradient (fewer than two readable samples, or options out of range).
|
|
38
|
+
*/
|
|
39
|
+
export declare function fitGradientStops(samples: readonly RawGradientSample[], options?: SimplifyGradientStopsOptions): SimplifiedGradientStops;
|
|
40
|
+
/**
|
|
41
|
+
* Reduces colours sampled along a line (offsets 0..1) to a minimal set of gradient stops. The
|
|
42
|
+
* first and last stops sit at 0 and 1; stops are added where the colour turns most until every
|
|
43
|
+
* sample is within `tolerance` (OKLab ΔE) of the OKLab blend between its stops, or `maxStops` is
|
|
44
|
+
* reached. Samples that can't be read (a bad colour string) are skipped. Throws when fewer than two
|
|
45
|
+
* samples can be read.
|
|
46
|
+
*/
|
|
47
|
+
export declare function simplifyGradientStops(samples: readonly RawGradientSample[], options?: SimplifyGradientStopsOptions): GradientStop[];
|
|
48
|
+
export interface LineSamplingOptions extends SimplifyGradientStopsOptions {
|
|
49
|
+
/** Colours read along the line, ends included (default 32, 2..1024). */
|
|
50
|
+
sampleCount?: number;
|
|
51
|
+
/** Snap the line's angle to multiples of this many degrees (e.g. 15, for Shift), keeping its length. */
|
|
52
|
+
snapStep?: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Reads colours along the line from `start` to `end` with `readPixel` (which returns a CSS colour,
|
|
56
|
+
* RGBA bytes, or null where there is nothing to read) and returns a linear gradient (OKLab
|
|
57
|
+
* interpolation) whose angle is the line's (CSS degrees) and whose stops are the simplified
|
|
58
|
+
* colours, offsets measured along the line. Returns null when fewer than two points could be read.
|
|
59
|
+
*/
|
|
60
|
+
export declare function sampleGradientFromLine(readPixel: (point: {
|
|
61
|
+
x: number;
|
|
62
|
+
y: number;
|
|
63
|
+
}) => string | readonly [number, number, number, number] | null, start: {
|
|
64
|
+
x: number;
|
|
65
|
+
y: number;
|
|
66
|
+
}, end: {
|
|
67
|
+
x: number;
|
|
68
|
+
y: number;
|
|
69
|
+
}, options?: LineSamplingOptions): Gradient | null;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6; var _class7; var _class8; var _class9; var _class10; var _class11; var _class12; var _class13; var _class14; var _class15; var _class16; var _class17; var _class18;
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
var _chunkS6OT6CTScjs = require('../chunk-S6OT6CTS.cjs');
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
4
8
|
|
|
5
9
|
|
|
6
10
|
|
|
@@ -17538,7 +17542,50 @@ function createImagePaintTools(controller) {
|
|
|
17538
17542
|
return { color, foreground: controller.getState().paint.foreground };
|
|
17539
17543
|
}
|
|
17540
17544
|
};
|
|
17541
|
-
|
|
17545
|
+
const sampleGradient = {
|
|
17546
|
+
name: "sample_gradient",
|
|
17547
|
+
title: "Sample a gradient along a line",
|
|
17548
|
+
description: "Reads the composite colours along a line from start to end (document pixels), smooths away grain and fits the fewest gradient stops that reproduce them within tolerance. Changes nothing. Returns a linear gradient (CSS angle of the line, oklab interpolation, stops with offsets along the line), its CSS, and how many points could be read. Pass the gradient to a gradient tool (set_gradient) or its stops as paint (set_paint).",
|
|
17549
|
+
inputSchema: {
|
|
17550
|
+
type: "object",
|
|
17551
|
+
properties: {
|
|
17552
|
+
start: { type: "object", properties: { x: { type: "number", description: "Document x in pixels." }, y: { type: "number", description: "Document y in pixels." } }, required: ["x", "y"], additionalProperties: false, description: "Where the gradient starts (offset 0)." },
|
|
17553
|
+
end: { type: "object", properties: { x: { type: "number", description: "Document x in pixels." }, y: { type: "number", description: "Document y in pixels." } }, required: ["x", "y"], additionalProperties: false, description: "Where it ends (offset 1)." },
|
|
17554
|
+
sampleCount: { type: "integer", minimum: 2, maximum: 1024, description: "Points read along the line, ends included (default 32)." },
|
|
17555
|
+
sampleSize: { type: "integer", minimum: 1, maximum: 101, description: "Square averaged at each point, in pixels (default 3)." },
|
|
17556
|
+
tolerance: { type: "number", exclusiveMinimum: 0, maximum: 1, description: "Largest error left, OKLab \u0394E (default 0.06; 0.02 is a just-noticeable step)." },
|
|
17557
|
+
maxStops: { type: "integer", minimum: 2, maximum: 64, description: "Most stops (default 5)." },
|
|
17558
|
+
snapStep: { type: "number", exclusiveMinimum: 0, maximum: 180, description: "Snap the line to multiples of this many degrees, keeping its length (default no snapping)." }
|
|
17559
|
+
},
|
|
17560
|
+
required: ["start", "end"],
|
|
17561
|
+
additionalProperties: false
|
|
17562
|
+
},
|
|
17563
|
+
annotations: { title: "Sample a gradient along a line", ...READ4 },
|
|
17564
|
+
handler: (args) => {
|
|
17565
|
+
const start = args.start;
|
|
17566
|
+
const end = args.end;
|
|
17567
|
+
const size = _nullishCoalesce(args.sampleSize, () => ( 3));
|
|
17568
|
+
let read = 0;
|
|
17569
|
+
let gradient;
|
|
17570
|
+
try {
|
|
17571
|
+
gradient = _chunkS6OT6CTScjs.sampleGradientFromLine.call(void 0,
|
|
17572
|
+
(p) => {
|
|
17573
|
+
const c = controller.sampleColor(p, { size, setForeground: false });
|
|
17574
|
+
if (c) read++;
|
|
17575
|
+
return c;
|
|
17576
|
+
},
|
|
17577
|
+
start,
|
|
17578
|
+
end,
|
|
17579
|
+
{ sampleCount: args.sampleCount, tolerance: args.tolerance, maxStops: args.maxStops, snapStep: args.snapStep }
|
|
17580
|
+
);
|
|
17581
|
+
} catch (e) {
|
|
17582
|
+
throw new (0, _chunkBBUE3UJLcjs.LayerOperationError)(e.message);
|
|
17583
|
+
}
|
|
17584
|
+
if (!gradient) throw new (0, _chunkBBUE3UJLcjs.LayerOperationError)(`Fewer than two points along the line could be read: it runs outside the canvas or over transparent pixels. Read the canvas size with get_image_info and draw the line across visible pixels.`);
|
|
17585
|
+
return { gradient, css: _chunkS6OT6CTScjs.gradientToCss.call(void 0, gradient), pointsRead: read };
|
|
17586
|
+
}
|
|
17587
|
+
};
|
|
17588
|
+
return [paintStroke, fill, clear, getMixer, loadMixerTool, cleanMixerTool, setPaint, getPaint, sampleColor2, sampleGradient];
|
|
17542
17589
|
}
|
|
17543
17590
|
|
|
17544
17591
|
// src/image-editor/color-tools.ts
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
gradientToCss,
|
|
3
|
+
sampleGradientFromLine
|
|
4
|
+
} from "../chunk-KMPM5JM5.js";
|
|
1
5
|
import {
|
|
2
6
|
bendSegment,
|
|
3
7
|
loadOpenTypeFont,
|
|
@@ -17538,7 +17542,50 @@ function createImagePaintTools(controller) {
|
|
|
17538
17542
|
return { color, foreground: controller.getState().paint.foreground };
|
|
17539
17543
|
}
|
|
17540
17544
|
};
|
|
17541
|
-
|
|
17545
|
+
const sampleGradient = {
|
|
17546
|
+
name: "sample_gradient",
|
|
17547
|
+
title: "Sample a gradient along a line",
|
|
17548
|
+
description: "Reads the composite colours along a line from start to end (document pixels), smooths away grain and fits the fewest gradient stops that reproduce them within tolerance. Changes nothing. Returns a linear gradient (CSS angle of the line, oklab interpolation, stops with offsets along the line), its CSS, and how many points could be read. Pass the gradient to a gradient tool (set_gradient) or its stops as paint (set_paint).",
|
|
17549
|
+
inputSchema: {
|
|
17550
|
+
type: "object",
|
|
17551
|
+
properties: {
|
|
17552
|
+
start: { type: "object", properties: { x: { type: "number", description: "Document x in pixels." }, y: { type: "number", description: "Document y in pixels." } }, required: ["x", "y"], additionalProperties: false, description: "Where the gradient starts (offset 0)." },
|
|
17553
|
+
end: { type: "object", properties: { x: { type: "number", description: "Document x in pixels." }, y: { type: "number", description: "Document y in pixels." } }, required: ["x", "y"], additionalProperties: false, description: "Where it ends (offset 1)." },
|
|
17554
|
+
sampleCount: { type: "integer", minimum: 2, maximum: 1024, description: "Points read along the line, ends included (default 32)." },
|
|
17555
|
+
sampleSize: { type: "integer", minimum: 1, maximum: 101, description: "Square averaged at each point, in pixels (default 3)." },
|
|
17556
|
+
tolerance: { type: "number", exclusiveMinimum: 0, maximum: 1, description: "Largest error left, OKLab \u0394E (default 0.06; 0.02 is a just-noticeable step)." },
|
|
17557
|
+
maxStops: { type: "integer", minimum: 2, maximum: 64, description: "Most stops (default 5)." },
|
|
17558
|
+
snapStep: { type: "number", exclusiveMinimum: 0, maximum: 180, description: "Snap the line to multiples of this many degrees, keeping its length (default no snapping)." }
|
|
17559
|
+
},
|
|
17560
|
+
required: ["start", "end"],
|
|
17561
|
+
additionalProperties: false
|
|
17562
|
+
},
|
|
17563
|
+
annotations: { title: "Sample a gradient along a line", ...READ4 },
|
|
17564
|
+
handler: (args) => {
|
|
17565
|
+
const start = args.start;
|
|
17566
|
+
const end = args.end;
|
|
17567
|
+
const size = args.sampleSize ?? 3;
|
|
17568
|
+
let read = 0;
|
|
17569
|
+
let gradient;
|
|
17570
|
+
try {
|
|
17571
|
+
gradient = sampleGradientFromLine(
|
|
17572
|
+
(p) => {
|
|
17573
|
+
const c = controller.sampleColor(p, { size, setForeground: false });
|
|
17574
|
+
if (c) read++;
|
|
17575
|
+
return c;
|
|
17576
|
+
},
|
|
17577
|
+
start,
|
|
17578
|
+
end,
|
|
17579
|
+
{ sampleCount: args.sampleCount, tolerance: args.tolerance, maxStops: args.maxStops, snapStep: args.snapStep }
|
|
17580
|
+
);
|
|
17581
|
+
} catch (e) {
|
|
17582
|
+
throw new LayerOperationError(e.message);
|
|
17583
|
+
}
|
|
17584
|
+
if (!gradient) throw new LayerOperationError(`Fewer than two points along the line could be read: it runs outside the canvas or over transparent pixels. Read the canvas size with get_image_info and draw the line across visible pixels.`);
|
|
17585
|
+
return { gradient, css: gradientToCss(gradient), pointsRead: read };
|
|
17586
|
+
}
|
|
17587
|
+
};
|
|
17588
|
+
return [paintStroke, fill, clear, getMixer, loadMixerTool, cleanMixerTool, setPaint, getPaint, sampleColor2, sampleGradient];
|
|
17542
17589
|
}
|
|
17543
17590
|
|
|
17544
17591
|
// src/image-editor/color-tools.ts
|
|
@@ -112,6 +112,8 @@ export interface PaintEditorState {
|
|
|
112
112
|
mixerLoad: MixerLoad;
|
|
113
113
|
view: PaintView;
|
|
114
114
|
hand: Hand;
|
|
115
|
+
/** The stroke picked on a stroke layer, on the canvas or in the stroke panel (editor state, not an undo step). */
|
|
116
|
+
selectedStroke: string | null;
|
|
115
117
|
/** The sticker picked on the canvas or in the sticker panel (editor state, not an undo step). */
|
|
116
118
|
selectedSticker: string | null;
|
|
117
119
|
canUndo: boolean;
|
|
@@ -134,6 +136,19 @@ export interface PaintEditorState {
|
|
|
134
136
|
version: number;
|
|
135
137
|
}
|
|
136
138
|
/** A layer as tools and panels describe it. */
|
|
139
|
+
/** A change to a live stroke (`updateStroke`): every field is optional. */
|
|
140
|
+
export interface LiveStrokeUpdate {
|
|
141
|
+
/** Another brush preset; the stroke keeps its size. */
|
|
142
|
+
brushId?: string;
|
|
143
|
+
/** Brush settings to change (as `setBrush` takes), on top of `brushId` or the stroke's brush. */
|
|
144
|
+
brush?: Omit<PaintBrushInput, 'id'>;
|
|
145
|
+
/** A new colour #rrggbb (replaces gradient or effect paint). */
|
|
146
|
+
color?: string;
|
|
147
|
+
/** Paint: a colour, gradient paint or effect paint; `null` back to the plain colour. */
|
|
148
|
+
paint?: unknown;
|
|
149
|
+
/** Another jitter seed (particles, scatter and texture fall differently). */
|
|
150
|
+
seed?: number;
|
|
151
|
+
}
|
|
137
152
|
export interface PaintLayerInfo {
|
|
138
153
|
id: string;
|
|
139
154
|
name: string;
|
|
@@ -148,6 +163,8 @@ export interface PaintLayerInfo {
|
|
|
148
163
|
bounds: RasterRect | null;
|
|
149
164
|
parentId: string | null;
|
|
150
165
|
depth: number;
|
|
166
|
+
/** A live stroke layer (its pixels are drawn from its strokes, which stay editable): how many strokes it has. */
|
|
167
|
+
liveStrokes?: number;
|
|
151
168
|
}
|
|
152
169
|
export interface PaintStrokeInit {
|
|
153
170
|
/** Default the active layer. */
|
|
@@ -277,6 +294,7 @@ export declare class PaintEditorController {
|
|
|
277
294
|
private mixerLoad;
|
|
278
295
|
private view;
|
|
279
296
|
private hand;
|
|
297
|
+
private selectedStroke;
|
|
280
298
|
private selectedSticker;
|
|
281
299
|
/** A sticker drag in progress (`previewSticker`): the document before it, so the drop undoes in one step, and the one it showed last. */
|
|
282
300
|
private stickerPreview;
|
|
@@ -290,6 +308,8 @@ export declare class PaintEditorController {
|
|
|
290
308
|
private dirty;
|
|
291
309
|
private seedCounter;
|
|
292
310
|
private strokeCounter;
|
|
311
|
+
/** The area each stroke painted (document pixels; `null`: nothing), learnt as it is painted or drawn, so a stroke layer edit redraws only where it changes (see `drawLiveStrokes`). */
|
|
312
|
+
private paintedBounds;
|
|
293
313
|
private guides;
|
|
294
314
|
private swatches;
|
|
295
315
|
private readonly pad;
|
|
@@ -403,9 +423,10 @@ export declare class PaintEditorController {
|
|
|
403
423
|
private paintTarget;
|
|
404
424
|
setActiveLayer(id: string): void;
|
|
405
425
|
/** Adds an empty pixel layer or group above `aboveId` (default: the active layer) and makes it active. */
|
|
426
|
+
/** Adds a layer above the active one: `pixel` (default), `group`, or `strokes`, a live stroke layer whose strokes stay editable. */
|
|
406
427
|
addLayer(options?: {
|
|
407
428
|
name?: string;
|
|
408
|
-
type?: 'pixel' | 'group';
|
|
429
|
+
type?: 'pixel' | 'group' | 'strokes';
|
|
409
430
|
aboveId?: string;
|
|
410
431
|
parentId?: string | null;
|
|
411
432
|
}): PaintLayerInfo;
|
|
@@ -1000,6 +1021,75 @@ export declare class PaintEditorController {
|
|
|
1000
1021
|
removeStickers(layerId: string | undefined, ids?: readonly string[]): string[];
|
|
1001
1022
|
/** Sets a layer's stickers (replays). */
|
|
1002
1023
|
replaceStickers(layerId: string, stickers: readonly Sticker[]): void;
|
|
1024
|
+
/** The document's `liveStrokes` without these layers (as a spread: `{}` when unchanged). */
|
|
1025
|
+
private liveStrokesWithout;
|
|
1026
|
+
private liveStrokeLayer;
|
|
1027
|
+
/** Whether a layer is a live stroke layer. */
|
|
1028
|
+
isLiveStrokeLayer(layerId: string): boolean;
|
|
1029
|
+
/** Makes an empty pixel layer a live stroke layer (one undo step). */
|
|
1030
|
+
convertToLiveStrokes(layerId?: string): PaintLayerInfo;
|
|
1031
|
+
/** A stroke layer's strokes, bottom first. */
|
|
1032
|
+
listLiveStrokes(layerId?: string): RecordedStroke[];
|
|
1033
|
+
/** The stroke layer holding a live stroke, and where. */
|
|
1034
|
+
private findLiveStroke;
|
|
1035
|
+
/**
|
|
1036
|
+
* Changes a live stroke's brush (a preset by `brushId`, keeping the stroke's size, and/or brush
|
|
1037
|
+
* settings), colour, paint or seed, and redraws its layer (one undo step). A new colour replaces
|
|
1038
|
+
* gradient or effect paint. Returns the stroke and the area that changed.
|
|
1039
|
+
*/
|
|
1040
|
+
updateStroke(id: string, update: LiveStrokeUpdate): {
|
|
1041
|
+
stroke: RecordedStroke;
|
|
1042
|
+
bounds: RasterRect;
|
|
1043
|
+
};
|
|
1044
|
+
/**
|
|
1045
|
+
* Replaces a live stroke's points, or moves, scales, rotates or flips them (`transform`, about
|
|
1046
|
+
* the stroke's centre unless an origin is given; the brush size scales too), and redraws its
|
|
1047
|
+
* layer (one undo step). Returns the stroke and the area that changed.
|
|
1048
|
+
*/
|
|
1049
|
+
editStrokePoints(id: string, edit: {
|
|
1050
|
+
points?: readonly PenPoint[];
|
|
1051
|
+
transform?: StrokeVariation;
|
|
1052
|
+
}): {
|
|
1053
|
+
stroke: RecordedStroke;
|
|
1054
|
+
bounds: RasterRect;
|
|
1055
|
+
};
|
|
1056
|
+
/** Deletes live strokes (one undo step). Returns the ids removed and the area that changed. */
|
|
1057
|
+
removeStrokes(ids: readonly string[]): {
|
|
1058
|
+
removed: string[];
|
|
1059
|
+
bounds: RasterRect | null;
|
|
1060
|
+
};
|
|
1061
|
+
/** Moves a live stroke to `index` in its layer (0 = bottom) and redraws (one undo step). Returns the new order. */
|
|
1062
|
+
reorderStroke(id: string, index: number): string[];
|
|
1063
|
+
/** Turns a stroke layer into a plain pixel layer with the same pixels (one undo step). */
|
|
1064
|
+
rasterizeLiveStrokes(layerId?: string): PaintLayerInfo;
|
|
1065
|
+
/** Sets a layer's live strokes and redraws it (replays). `null` makes it a plain pixel layer. */
|
|
1066
|
+
replaceLiveStrokes(layerId: string, strokes: readonly RecordedStroke[] | null): void;
|
|
1067
|
+
/**
|
|
1068
|
+
* The top-most stroke of a stroke layer (default the active layer) that painted the pixel at
|
|
1069
|
+
* (x, y) document pixels, or `null`. Strokes whose area holds the point are drawn alone, top
|
|
1070
|
+
* first, until one covers it.
|
|
1071
|
+
*/
|
|
1072
|
+
liveStrokeAt(layerId: string | undefined, x: number, y: number): RecordedStroke | null;
|
|
1073
|
+
/** Picks a stroke for the stroke panel and canvas (`null` for none). Editor state, not an undo step. */
|
|
1074
|
+
selectStroke(id: string | null): void;
|
|
1075
|
+
/** Where a live stroke painted (document pixels), or `null`. */
|
|
1076
|
+
liveStrokeArea(id: string): RasterRect | null;
|
|
1077
|
+
/** Commits a layer's new stroke list with its redrawn pixels, recorded for replays (one undo step). */
|
|
1078
|
+
private commitLiveStrokes;
|
|
1079
|
+
private withLiveStrokes;
|
|
1080
|
+
/** Where a stroke painted (document pixels): as learnt when it was painted or drawn, else an estimate from its points and brush. */
|
|
1081
|
+
private strokeArea;
|
|
1082
|
+
/** The areas strokes painted, or `null` when one is not known (a full redraw then). */
|
|
1083
|
+
private strokeAreas;
|
|
1084
|
+
/** Draws one stroke onto `surface` of a plain copy of the layer; returns the result and the area it painted. */
|
|
1085
|
+
private drawLiveStroke;
|
|
1086
|
+
/**
|
|
1087
|
+
* A stroke layer's pixels drawn from its strokes, bottom first. With `dirty` (the areas the old
|
|
1088
|
+
* strokes that changed had painted), only the tiles there and under any stroke whose area isn't
|
|
1089
|
+
* known yet are drawn again, from the strokes that reach them (a pixel depends only on the
|
|
1090
|
+
* strokes over it); the other tiles are kept. Without it, or with `null`, every stroke is drawn.
|
|
1091
|
+
*/
|
|
1092
|
+
private drawLiveStrokes;
|
|
1003
1093
|
private stencilCache;
|
|
1004
1094
|
private nextStencilId;
|
|
1005
1095
|
/** The mask stencils' coverage over the canvas (cached), or `null` when none masks. */
|
|
@@ -112,6 +112,8 @@ export interface PaintEditorState {
|
|
|
112
112
|
mixerLoad: MixerLoad;
|
|
113
113
|
view: PaintView;
|
|
114
114
|
hand: Hand;
|
|
115
|
+
/** The stroke picked on a stroke layer, on the canvas or in the stroke panel (editor state, not an undo step). */
|
|
116
|
+
selectedStroke: string | null;
|
|
115
117
|
/** The sticker picked on the canvas or in the sticker panel (editor state, not an undo step). */
|
|
116
118
|
selectedSticker: string | null;
|
|
117
119
|
canUndo: boolean;
|
|
@@ -134,6 +136,19 @@ export interface PaintEditorState {
|
|
|
134
136
|
version: number;
|
|
135
137
|
}
|
|
136
138
|
/** A layer as tools and panels describe it. */
|
|
139
|
+
/** A change to a live stroke (`updateStroke`): every field is optional. */
|
|
140
|
+
export interface LiveStrokeUpdate {
|
|
141
|
+
/** Another brush preset; the stroke keeps its size. */
|
|
142
|
+
brushId?: string;
|
|
143
|
+
/** Brush settings to change (as `setBrush` takes), on top of `brushId` or the stroke's brush. */
|
|
144
|
+
brush?: Omit<PaintBrushInput, 'id'>;
|
|
145
|
+
/** A new colour #rrggbb (replaces gradient or effect paint). */
|
|
146
|
+
color?: string;
|
|
147
|
+
/** Paint: a colour, gradient paint or effect paint; `null` back to the plain colour. */
|
|
148
|
+
paint?: unknown;
|
|
149
|
+
/** Another jitter seed (particles, scatter and texture fall differently). */
|
|
150
|
+
seed?: number;
|
|
151
|
+
}
|
|
137
152
|
export interface PaintLayerInfo {
|
|
138
153
|
id: string;
|
|
139
154
|
name: string;
|
|
@@ -148,6 +163,8 @@ export interface PaintLayerInfo {
|
|
|
148
163
|
bounds: RasterRect | null;
|
|
149
164
|
parentId: string | null;
|
|
150
165
|
depth: number;
|
|
166
|
+
/** A live stroke layer (its pixels are drawn from its strokes, which stay editable): how many strokes it has. */
|
|
167
|
+
liveStrokes?: number;
|
|
151
168
|
}
|
|
152
169
|
export interface PaintStrokeInit {
|
|
153
170
|
/** Default the active layer. */
|
|
@@ -277,6 +294,7 @@ export declare class PaintEditorController {
|
|
|
277
294
|
private mixerLoad;
|
|
278
295
|
private view;
|
|
279
296
|
private hand;
|
|
297
|
+
private selectedStroke;
|
|
280
298
|
private selectedSticker;
|
|
281
299
|
/** A sticker drag in progress (`previewSticker`): the document before it, so the drop undoes in one step, and the one it showed last. */
|
|
282
300
|
private stickerPreview;
|
|
@@ -290,6 +308,8 @@ export declare class PaintEditorController {
|
|
|
290
308
|
private dirty;
|
|
291
309
|
private seedCounter;
|
|
292
310
|
private strokeCounter;
|
|
311
|
+
/** The area each stroke painted (document pixels; `null`: nothing), learnt as it is painted or drawn, so a stroke layer edit redraws only where it changes (see `drawLiveStrokes`). */
|
|
312
|
+
private paintedBounds;
|
|
293
313
|
private guides;
|
|
294
314
|
private swatches;
|
|
295
315
|
private readonly pad;
|
|
@@ -403,9 +423,10 @@ export declare class PaintEditorController {
|
|
|
403
423
|
private paintTarget;
|
|
404
424
|
setActiveLayer(id: string): void;
|
|
405
425
|
/** Adds an empty pixel layer or group above `aboveId` (default: the active layer) and makes it active. */
|
|
426
|
+
/** Adds a layer above the active one: `pixel` (default), `group`, or `strokes`, a live stroke layer whose strokes stay editable. */
|
|
406
427
|
addLayer(options?: {
|
|
407
428
|
name?: string;
|
|
408
|
-
type?: 'pixel' | 'group';
|
|
429
|
+
type?: 'pixel' | 'group' | 'strokes';
|
|
409
430
|
aboveId?: string;
|
|
410
431
|
parentId?: string | null;
|
|
411
432
|
}): PaintLayerInfo;
|
|
@@ -1000,6 +1021,75 @@ export declare class PaintEditorController {
|
|
|
1000
1021
|
removeStickers(layerId: string | undefined, ids?: readonly string[]): string[];
|
|
1001
1022
|
/** Sets a layer's stickers (replays). */
|
|
1002
1023
|
replaceStickers(layerId: string, stickers: readonly Sticker[]): void;
|
|
1024
|
+
/** The document's `liveStrokes` without these layers (as a spread: `{}` when unchanged). */
|
|
1025
|
+
private liveStrokesWithout;
|
|
1026
|
+
private liveStrokeLayer;
|
|
1027
|
+
/** Whether a layer is a live stroke layer. */
|
|
1028
|
+
isLiveStrokeLayer(layerId: string): boolean;
|
|
1029
|
+
/** Makes an empty pixel layer a live stroke layer (one undo step). */
|
|
1030
|
+
convertToLiveStrokes(layerId?: string): PaintLayerInfo;
|
|
1031
|
+
/** A stroke layer's strokes, bottom first. */
|
|
1032
|
+
listLiveStrokes(layerId?: string): RecordedStroke[];
|
|
1033
|
+
/** The stroke layer holding a live stroke, and where. */
|
|
1034
|
+
private findLiveStroke;
|
|
1035
|
+
/**
|
|
1036
|
+
* Changes a live stroke's brush (a preset by `brushId`, keeping the stroke's size, and/or brush
|
|
1037
|
+
* settings), colour, paint or seed, and redraws its layer (one undo step). A new colour replaces
|
|
1038
|
+
* gradient or effect paint. Returns the stroke and the area that changed.
|
|
1039
|
+
*/
|
|
1040
|
+
updateStroke(id: string, update: LiveStrokeUpdate): {
|
|
1041
|
+
stroke: RecordedStroke;
|
|
1042
|
+
bounds: RasterRect;
|
|
1043
|
+
};
|
|
1044
|
+
/**
|
|
1045
|
+
* Replaces a live stroke's points, or moves, scales, rotates or flips them (`transform`, about
|
|
1046
|
+
* the stroke's centre unless an origin is given; the brush size scales too), and redraws its
|
|
1047
|
+
* layer (one undo step). Returns the stroke and the area that changed.
|
|
1048
|
+
*/
|
|
1049
|
+
editStrokePoints(id: string, edit: {
|
|
1050
|
+
points?: readonly PenPoint[];
|
|
1051
|
+
transform?: StrokeVariation;
|
|
1052
|
+
}): {
|
|
1053
|
+
stroke: RecordedStroke;
|
|
1054
|
+
bounds: RasterRect;
|
|
1055
|
+
};
|
|
1056
|
+
/** Deletes live strokes (one undo step). Returns the ids removed and the area that changed. */
|
|
1057
|
+
removeStrokes(ids: readonly string[]): {
|
|
1058
|
+
removed: string[];
|
|
1059
|
+
bounds: RasterRect | null;
|
|
1060
|
+
};
|
|
1061
|
+
/** Moves a live stroke to `index` in its layer (0 = bottom) and redraws (one undo step). Returns the new order. */
|
|
1062
|
+
reorderStroke(id: string, index: number): string[];
|
|
1063
|
+
/** Turns a stroke layer into a plain pixel layer with the same pixels (one undo step). */
|
|
1064
|
+
rasterizeLiveStrokes(layerId?: string): PaintLayerInfo;
|
|
1065
|
+
/** Sets a layer's live strokes and redraws it (replays). `null` makes it a plain pixel layer. */
|
|
1066
|
+
replaceLiveStrokes(layerId: string, strokes: readonly RecordedStroke[] | null): void;
|
|
1067
|
+
/**
|
|
1068
|
+
* The top-most stroke of a stroke layer (default the active layer) that painted the pixel at
|
|
1069
|
+
* (x, y) document pixels, or `null`. Strokes whose area holds the point are drawn alone, top
|
|
1070
|
+
* first, until one covers it.
|
|
1071
|
+
*/
|
|
1072
|
+
liveStrokeAt(layerId: string | undefined, x: number, y: number): RecordedStroke | null;
|
|
1073
|
+
/** Picks a stroke for the stroke panel and canvas (`null` for none). Editor state, not an undo step. */
|
|
1074
|
+
selectStroke(id: string | null): void;
|
|
1075
|
+
/** Where a live stroke painted (document pixels), or `null`. */
|
|
1076
|
+
liveStrokeArea(id: string): RasterRect | null;
|
|
1077
|
+
/** Commits a layer's new stroke list with its redrawn pixels, recorded for replays (one undo step). */
|
|
1078
|
+
private commitLiveStrokes;
|
|
1079
|
+
private withLiveStrokes;
|
|
1080
|
+
/** Where a stroke painted (document pixels): as learnt when it was painted or drawn, else an estimate from its points and brush. */
|
|
1081
|
+
private strokeArea;
|
|
1082
|
+
/** The areas strokes painted, or `null` when one is not known (a full redraw then). */
|
|
1083
|
+
private strokeAreas;
|
|
1084
|
+
/** Draws one stroke onto `surface` of a plain copy of the layer; returns the result and the area it painted. */
|
|
1085
|
+
private drawLiveStroke;
|
|
1086
|
+
/**
|
|
1087
|
+
* A stroke layer's pixels drawn from its strokes, bottom first. With `dirty` (the areas the old
|
|
1088
|
+
* strokes that changed had painted), only the tiles there and under any stroke whose area isn't
|
|
1089
|
+
* known yet are drawn again, from the strokes that reach them (a pixel depends only on the
|
|
1090
|
+
* strokes over it); the other tiles are kept. Without it, or with `null`, every stroke is drawn.
|
|
1091
|
+
*/
|
|
1092
|
+
private drawLiveStrokes;
|
|
1003
1093
|
private stencilCache;
|
|
1004
1094
|
private nextStencilId;
|
|
1005
1095
|
/** The mask stencils' coverage over the canvas (cached), or `null` when none masks. */
|
|
@@ -43,6 +43,11 @@ export interface PaintDocumentState {
|
|
|
43
43
|
paintEvents?: PaintEvent[];
|
|
44
44
|
/** Sticker layers: each layer's stickers, bottom first (its pixels are drawn from them). */
|
|
45
45
|
stickers?: Readonly<Record<string, Sticker[]>>;
|
|
46
|
+
/**
|
|
47
|
+
* Live stroke layers: each layer's strokes, bottom first. Its pixels are drawn from them, so each
|
|
48
|
+
* stroke's brush, colour and points stay editable (`updateStroke`).
|
|
49
|
+
*/
|
|
50
|
+
liveStrokes?: Readonly<Record<string, RecordedStroke[]>>;
|
|
46
51
|
/** Thick paint: each layer's height and material channels (layers without relief are missing). */
|
|
47
52
|
thick?: Readonly<Record<string, ThickChannels>>;
|
|
48
53
|
/** Effect layers: the provider effect each one is drawn from (layers that are gone are ignored). */
|
|
@@ -61,9 +66,9 @@ export interface PaintDocumentState {
|
|
|
61
66
|
/** A change a replay repeats besides strokes, recorded with how many strokes came before it. */
|
|
62
67
|
export interface PaintEvent {
|
|
63
68
|
at: number;
|
|
64
|
-
kind: 'fill' | 'clear' | 'flatten' | 'stickers';
|
|
69
|
+
kind: 'fill' | 'clear' | 'flatten' | 'stickers' | 'live-strokes';
|
|
65
70
|
layerId: string;
|
|
66
|
-
/** The fill's options (colour, opacity, grain, pressure, blend), or the layer's stickers after an edit. */
|
|
71
|
+
/** The fill's options (colour, opacity, grain, pressure, blend), or the layer's stickers or live strokes after an edit. */
|
|
67
72
|
data?: Record<string, unknown>;
|
|
68
73
|
}
|
|
69
74
|
/** A new layer id, unique in this session. */
|
|
@@ -43,6 +43,11 @@ export interface PaintDocumentState {
|
|
|
43
43
|
paintEvents?: PaintEvent[];
|
|
44
44
|
/** Sticker layers: each layer's stickers, bottom first (its pixels are drawn from them). */
|
|
45
45
|
stickers?: Readonly<Record<string, Sticker[]>>;
|
|
46
|
+
/**
|
|
47
|
+
* Live stroke layers: each layer's strokes, bottom first. Its pixels are drawn from them, so each
|
|
48
|
+
* stroke's brush, colour and points stay editable (`updateStroke`).
|
|
49
|
+
*/
|
|
50
|
+
liveStrokes?: Readonly<Record<string, RecordedStroke[]>>;
|
|
46
51
|
/** Thick paint: each layer's height and material channels (layers without relief are missing). */
|
|
47
52
|
thick?: Readonly<Record<string, ThickChannels>>;
|
|
48
53
|
/** Effect layers: the provider effect each one is drawn from (layers that are gone are ignored). */
|
|
@@ -61,9 +66,9 @@ export interface PaintDocumentState {
|
|
|
61
66
|
/** A change a replay repeats besides strokes, recorded with how many strokes came before it. */
|
|
62
67
|
export interface PaintEvent {
|
|
63
68
|
at: number;
|
|
64
|
-
kind: 'fill' | 'clear' | 'flatten' | 'stickers';
|
|
69
|
+
kind: 'fill' | 'clear' | 'flatten' | 'stickers' | 'live-strokes';
|
|
65
70
|
layerId: string;
|
|
66
|
-
/** The fill's options (colour, opacity, grain, pressure, blend), or the layer's stickers after an edit. */
|
|
71
|
+
/** The fill's options (colour, opacity, grain, pressure, blend), or the layer's stickers or live strokes after an edit. */
|
|
67
72
|
data?: Record<string, unknown>;
|
|
68
73
|
}
|
|
69
74
|
/** A new layer id, unique in this session. */
|