@pixerate/editor 0.259.0 → 0.261.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/agent/index.cjs +2 -2
- package/dist/agent/index.js +1 -1
- package/dist/animation-editor/index.cjs +61 -59
- package/dist/animation-editor/index.js +5 -3
- package/dist/brushes/index.cjs +52 -52
- package/dist/brushes/index.js +6 -6
- package/dist/{brushstrokeRender-JI4W6F2G.js → brushstrokeRender-25Q5BEY6.js} +17 -7
- package/dist/{brushstrokeRender-MQ6PSDM5.cjs → brushstrokeRender-N6UPZSQS.cjs} +31 -21
- package/dist/canvas/brushTypes.d.cts +5 -0
- package/dist/canvas/brushTypes.d.ts +5 -0
- package/dist/canvas/drawController.d.cts +15 -1
- package/dist/canvas/drawController.d.ts +15 -1
- package/dist/canvas/index.cjs +136 -102
- package/dist/canvas/index.js +35 -1
- package/dist/{chunk-3PPJRMIG.js → chunk-3O47TPGX.js} +197 -18
- package/dist/{chunk-5Q5MLXAD.cjs → chunk-3TQW5MPW.cjs} +23 -23
- package/dist/{chunk-JBBJE76O.cjs → chunk-3ZRNK3RO.cjs} +1138 -85
- package/dist/{chunk-YSGW7VV4.cjs → chunk-43NT242D.cjs} +1401 -228
- package/dist/{chunk-SMTUQWGY.js → chunk-5IW65VQ6.js} +1 -1
- package/dist/{chunk-YQ4PQTBB.js → chunk-6NZL24W4.js} +9 -1
- package/dist/{chunk-O3ZVZSNJ.cjs → chunk-7AUQ2UJ3.cjs} +137 -129
- package/dist/{chunk-4M4E3N6X.js → chunk-CQVTGBQI.js} +1 -1
- package/dist/{chunk-SSFMWJDK.js → chunk-D4TE5JNW.js} +1192 -19
- package/dist/{chunk-AORZ5M5C.js → chunk-EHALWBBD.js} +1 -1
- package/dist/{chunk-6WCQ35GI.cjs → chunk-NHA26O5X.cjs} +2 -2
- package/dist/{chunk-DPYAO5U6.cjs → chunk-PVJMTLPJ.cjs} +197 -18
- package/dist/{chunk-MQGQTKVW.cjs → chunk-PYTJBVW5.cjs} +29 -29
- package/dist/{chunk-IXQZHCOF.js → chunk-QAR2N7HZ.js} +1 -1
- package/dist/{chunk-5P4JEOIV.js → chunk-TMDDMJCR.js} +2 -2
- package/dist/{chunk-RSIKDWOP.cjs → chunk-TZOX7KWV.cjs} +15 -15
- package/dist/{chunk-IPUTIUGQ.cjs → chunk-UVZ7RQOE.cjs} +1 -1
- package/dist/{chunk-A7GJ6BXI.js → chunk-WJ4WQJCC.js} +1062 -9
- package/dist/{chunk-ICTPLVSR.js → chunk-X2EXYY4J.js} +1 -1
- package/dist/{chunk-WQCNC4DK.js → chunk-XC6DPRYI.js} +695 -4
- package/dist/{chunk-KBYB7OWB.cjs → chunk-YHDHFJUC.cjs} +776 -85
- package/dist/{chunk-P3PZ63M3.cjs → chunk-YLUIFXSL.cjs} +35 -35
- package/dist/commands/index.cjs +2 -2
- package/dist/commands/index.js +1 -1
- package/dist/image-editor/index.cjs +1385 -2484
- package/dist/image-editor/index.js +139 -1238
- package/dist/image-editor/selection-worker.cjs +3 -4
- package/dist/image-editor/selection-worker.js +1 -2
- package/dist/paint/index.cjs +3 -5
- package/dist/paint/index.js +40 -42
- package/dist/paint-editor/contract.d.cts +29 -0
- package/dist/paint-editor/contract.d.ts +29 -0
- package/dist/paint-editor/controller.d.cts +17 -0
- package/dist/paint-editor/controller.d.ts +17 -0
- package/dist/paint-editor/dab-stroke.d.cts +5 -0
- package/dist/paint-editor/dab-stroke.d.ts +5 -0
- package/dist/paint-editor/index.cjs +610 -327
- package/dist/paint-editor/index.d.cts +2 -0
- package/dist/paint-editor/index.d.ts +2 -0
- package/dist/paint-editor/index.js +337 -54
- package/dist/paint-editor/path-samples.d.cts +19 -0
- package/dist/paint-editor/path-samples.d.ts +19 -0
- package/dist/paint-editor/stamp-tip.d.cts +17 -0
- package/dist/paint-editor/stamp-tip.d.ts +17 -0
- package/dist/paint-editor/ui.d.cts +27 -1
- package/dist/paint-editor/ui.d.ts +27 -1
- package/dist/raster/index.cjs +2 -4
- package/dist/raster/index.js +6 -8
- package/dist/vector/brushstroke.d.cts +6 -0
- package/dist/vector/brushstroke.d.ts +6 -0
- package/dist/vector/controller.d.cts +11 -2
- package/dist/vector/controller.d.ts +11 -2
- package/dist/vector/index.cjs +990 -973
- package/dist/vector/index.js +28 -11
- package/package.json +1 -1
- package/skills/pixerate-brushes/SKILL.md +1 -1
- package/skills/pixerate-canvas/SKILL.md +3 -1
- package/skills/pixerate-paint-editor/SKILL.md +2 -0
- package/skills/pixerate-paint-sketch-to-finish/SKILL.md +5 -1
- package/skills/pixerate-vector/SKILL.md +2 -2
- package/dist/chunk-2VJAVXLI.js +0 -192
- package/dist/chunk-62Z5WLRA.js +0 -700
- package/dist/chunk-BOHDJJ2O.cjs +0 -192
- package/dist/chunk-NKGB64CW.cjs +0 -700
- package/dist/chunk-SS53NNZO.cjs +0 -1066
- package/dist/chunk-ZNA3IQZF.js +0 -1066
|
@@ -57,3 +57,5 @@ export type { StickerProvider, StickerRef, StickerSummary } from '../stickers/ty
|
|
|
57
57
|
export * from './sticker-art.cjs';
|
|
58
58
|
export * from './collaboration.cjs';
|
|
59
59
|
export * from './collaboration-tools.cjs';
|
|
60
|
+
export { samplePath, MAX_PATH_SAMPLES, type PathSampleOptions } from './path-samples.cjs';
|
|
61
|
+
export { paintContracts, PAINT_CONTRACT_FORMAT_VERSION, PAINT_DOCUMENT_JSON_SCHEMA } from './contract.cjs';
|
|
@@ -57,3 +57,5 @@ export type { StickerProvider, StickerRef, StickerSummary } from '../stickers/ty
|
|
|
57
57
|
export * from './sticker-art.js';
|
|
58
58
|
export * from './collaboration.js';
|
|
59
59
|
export * from './collaboration-tools.js';
|
|
60
|
+
export { samplePath, MAX_PATH_SAMPLES, type PathSampleOptions } from './path-samples.js';
|
|
61
|
+
export { paintContracts, PAINT_CONTRACT_FORMAT_VERSION, PAINT_DOCUMENT_JSON_SCHEMA } from './contract.js';
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
mergeBrushPatch,
|
|
12
12
|
multiColorSliders,
|
|
13
13
|
patchBrushValue
|
|
14
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-X2EXYY4J.js";
|
|
15
15
|
import {
|
|
16
16
|
createStickerTools,
|
|
17
17
|
describeStickerRef
|
|
@@ -20,20 +20,18 @@ import {
|
|
|
20
20
|
toVideoFfmpeg
|
|
21
21
|
} from "../chunk-MWUZCEFT.js";
|
|
22
22
|
import {
|
|
23
|
+
BRUSH_OPTION_CONTROLS,
|
|
23
24
|
createLayerEffectTools,
|
|
24
25
|
deserializeLayers,
|
|
25
26
|
exportPsd,
|
|
26
27
|
resolveExternalAssets,
|
|
27
28
|
serializeLayers,
|
|
28
29
|
serializeLayersExternal
|
|
29
|
-
} from "../chunk-
|
|
30
|
-
import "../chunk-
|
|
31
|
-
import {
|
|
32
|
-
brushColourHex
|
|
33
|
-
} from "../chunk-FJBQTZS4.js";
|
|
34
|
-
import "../chunk-3CI7FRMK.js";
|
|
30
|
+
} from "../chunk-D4TE5JNW.js";
|
|
31
|
+
import "../chunk-WJ4WQJCC.js";
|
|
35
32
|
import {
|
|
36
33
|
LAYER_EFFECT_KINDS,
|
|
34
|
+
LAYER_EFFECT_MENU,
|
|
37
35
|
LayerEffectError,
|
|
38
36
|
RasterCompositor,
|
|
39
37
|
defaultLayerEffect,
|
|
@@ -42,12 +40,13 @@ import {
|
|
|
42
40
|
insertLayer,
|
|
43
41
|
isLayerEffectKind,
|
|
44
42
|
isLayerLocked,
|
|
43
|
+
layerEffectControls,
|
|
45
44
|
layerEffectLabel,
|
|
46
45
|
mapLayer,
|
|
47
46
|
mergeLayerEffect,
|
|
48
47
|
newEffectId,
|
|
49
48
|
validateLayerEffects
|
|
50
|
-
} from "../chunk-
|
|
49
|
+
} from "../chunk-3O47TPGX.js";
|
|
51
50
|
import "../chunk-OHKOFKBY.js";
|
|
52
51
|
import "../chunk-ZIF4UJJ6.js";
|
|
53
52
|
import {
|
|
@@ -74,7 +73,8 @@ import {
|
|
|
74
73
|
pigmentOf,
|
|
75
74
|
pointerPressure,
|
|
76
75
|
wetSupport
|
|
77
|
-
} from "../chunk-
|
|
76
|
+
} from "../chunk-XC6DPRYI.js";
|
|
77
|
+
import "../chunk-VWEWY4FY.js";
|
|
78
78
|
import "../chunk-HVQVHUTV.js";
|
|
79
79
|
import {
|
|
80
80
|
writeZip
|
|
@@ -85,7 +85,7 @@ import {
|
|
|
85
85
|
realPresenceClock,
|
|
86
86
|
tweenPresenceCursor
|
|
87
87
|
} from "../chunk-IVSWRVB4.js";
|
|
88
|
-
import "../chunk-
|
|
88
|
+
import "../chunk-6NZL24W4.js";
|
|
89
89
|
import {
|
|
90
90
|
BRUSH_ID,
|
|
91
91
|
BRUSH_PRESETS,
|
|
@@ -140,7 +140,7 @@ import {
|
|
|
140
140
|
parseBrushLibrary,
|
|
141
141
|
resolveBrush,
|
|
142
142
|
usesPaintExchange
|
|
143
|
-
} from "../chunk-
|
|
143
|
+
} from "../chunk-5IW65VQ6.js";
|
|
144
144
|
import {
|
|
145
145
|
STAMP_KINDS,
|
|
146
146
|
StickersSlot,
|
|
@@ -150,8 +150,16 @@ import {
|
|
|
150
150
|
stickerIdFor
|
|
151
151
|
} from "../chunk-APX63ILU.js";
|
|
152
152
|
import "../chunk-M6DNZNVY.js";
|
|
153
|
-
import
|
|
153
|
+
import {
|
|
154
|
+
bezierPathToSvg,
|
|
155
|
+
flattenChain,
|
|
156
|
+
pathToContours
|
|
157
|
+
} from "../chunk-WEIK3UYB.js";
|
|
154
158
|
import "../chunk-PWBZH3FK.js";
|
|
159
|
+
import {
|
|
160
|
+
brushColourHex
|
|
161
|
+
} from "../chunk-FJBQTZS4.js";
|
|
162
|
+
import "../chunk-3CI7FRMK.js";
|
|
155
163
|
import {
|
|
156
164
|
StickerError
|
|
157
165
|
} from "../chunk-SZTI6EER.js";
|
|
@@ -268,6 +276,7 @@ import "../chunk-SIFAIGB3.js";
|
|
|
268
276
|
import "../chunk-CIQIM2IP.js";
|
|
269
277
|
import "../chunk-VDGBHYC7.js";
|
|
270
278
|
import {
|
|
279
|
+
EDITOR_DOCUMENT_FORMAT,
|
|
271
280
|
createEditorAssetRegistry,
|
|
272
281
|
defineEditorDocument
|
|
273
282
|
} from "../chunk-XBQX76CT.js";
|
|
@@ -653,6 +662,18 @@ function brushStickerRefs(brush) {
|
|
|
653
662
|
const refs = brush.engine === "sticker" ? [...brush.sticker.stamps] : brush.engine === "particle" ? [...brush.particle.dotTextures] : brush.engine === "dab" && brush.stamp ? [brush.stamp] : [];
|
|
654
663
|
return [...new Set(refs.filter((r) => r.includes(":") && !r.startsWith("editor:")))];
|
|
655
664
|
}
|
|
665
|
+
function sprayDroplets(d, spray, seed) {
|
|
666
|
+
const count = 1 + Math.round(spray.density * 31);
|
|
667
|
+
const key = d.index * 131 + Math.round(d.travel * 8);
|
|
668
|
+
const out = [];
|
|
669
|
+
for (let i = 0; i < count; i++) {
|
|
670
|
+
const n = key * 64 + i;
|
|
671
|
+
const a = rand01(seed, n, 61) * Math.PI * 2;
|
|
672
|
+
const rr = Math.sqrt(rand01(seed, n, 67)) * d.radius;
|
|
673
|
+
out.push({ ...d, x: d.x + Math.cos(a) * rr, y: d.y + Math.sin(a) * rr, radius: Math.max(0.5, d.radius * spray.size * (0.6 + 0.8 * rand01(seed, n, 71))), roundness: 1, angle: rand01(seed, n, 73) * Math.PI * 2 });
|
|
674
|
+
}
|
|
675
|
+
return out;
|
|
676
|
+
}
|
|
656
677
|
|
|
657
678
|
// src/paint-editor/engine.ts
|
|
658
679
|
function penOrientation(p) {
|
|
@@ -921,14 +942,7 @@ var DryStroke = class {
|
|
|
921
942
|
this.mark(d, d, rects);
|
|
922
943
|
continue;
|
|
923
944
|
}
|
|
924
|
-
const
|
|
925
|
-
const key = d.index * 131 + Math.round(d.travel * 8);
|
|
926
|
-
for (let i = 0; i < count; i++) {
|
|
927
|
-
const a = rand01(seed, key * 64 + i, 61) * Math.PI * 2;
|
|
928
|
-
const rr = Math.sqrt(rand01(seed, key * 64 + i, 67)) * d.radius;
|
|
929
|
-
const drop = { ...d, x: d.x + Math.cos(a) * rr, y: d.y + Math.sin(a) * rr, radius: Math.max(0.5, d.radius * spray.size * (0.6 + 0.8 * rand01(seed, key * 64 + i, 71))), roundness: 1, angle: rand01(seed, key * 64 + i, 73) * Math.PI * 2 };
|
|
930
|
-
this.mark(drop, d, rects);
|
|
931
|
-
}
|
|
945
|
+
for (const drop of sprayDroplets(d, spray, seed)) this.mark(drop, d, rects);
|
|
932
946
|
}
|
|
933
947
|
return rects;
|
|
934
948
|
}
|
|
@@ -1019,6 +1033,7 @@ var PaintDabStroke = class {
|
|
|
1019
1033
|
constructor(brush, options) {
|
|
1020
1034
|
this.brush = brush;
|
|
1021
1035
|
this.options = options;
|
|
1036
|
+
this.stamp = brush.engine === "dab" && brush.stamp ? stampSampler(brush.stamp, options.stampArt) : null;
|
|
1022
1037
|
this.smoother = new PenSmoother(brush.tip.smoothing, brush.pressureSmoothing);
|
|
1023
1038
|
this.copies = options.copies?.length ? options.copies : [IDENTITY_COPY];
|
|
1024
1039
|
const exchanging = brush.engine === "mixer" || brush.engine === "dab";
|
|
@@ -1062,6 +1077,8 @@ var PaintDabStroke = class {
|
|
|
1062
1077
|
paint = [0, 0, 0];
|
|
1063
1078
|
lastRaw = null;
|
|
1064
1079
|
lastFed = null;
|
|
1080
|
+
/** The stamp tip's shape (dab brushes), or `null` for the round computed tip. */
|
|
1081
|
+
stamp;
|
|
1065
1082
|
/** What copy 0's brush carries after the dabs so far (mixer and wet-capable dab brushes). */
|
|
1066
1083
|
get load() {
|
|
1067
1084
|
return this.states[0].exchange?.load ?? null;
|
|
@@ -1094,8 +1111,9 @@ var PaintDabStroke = class {
|
|
|
1094
1111
|
return rects;
|
|
1095
1112
|
}
|
|
1096
1113
|
paintDabs(copy, dabs, rects) {
|
|
1097
|
-
|
|
1098
|
-
|
|
1114
|
+
const spray = this.brush.engine === "dab" ? this.brush.spray : null;
|
|
1115
|
+
for (const d of spray ? dabs.flatMap((dab) => sprayDroplets(dab, spray, (this.options.seed ?? 0) + copy * 7919)) : dabs) {
|
|
1116
|
+
const rect = this.stamp ? stampDabRect(d) : dabRect(d, this.brush.tip.hardness);
|
|
1099
1117
|
if (rect.width <= 0 || rect.height <= 0) continue;
|
|
1100
1118
|
this.dab(copy, d, rect);
|
|
1101
1119
|
rects.push(rect);
|
|
@@ -1111,7 +1129,8 @@ var PaintDabStroke = class {
|
|
|
1111
1129
|
}
|
|
1112
1130
|
const tip = this.tip.subarray(0, n);
|
|
1113
1131
|
tip.fill(0);
|
|
1114
|
-
|
|
1132
|
+
if (this.stamp) rasterizeStampDab({ ...d, alpha: 1 }, this.stamp, d.index, rect, tip);
|
|
1133
|
+
else rasterizeDab({ ...d, alpha: 1 }, this.brush.tip.hardness, rect, tip);
|
|
1115
1134
|
if (!(this.depth > 0 && this.options.grain)) return { tip, cov: tip };
|
|
1116
1135
|
const cov = this.cov.subarray(0, n);
|
|
1117
1136
|
const grain = this.options.grain;
|
|
@@ -2958,7 +2977,8 @@ var PaintStrokeSession = class _PaintStrokeSession {
|
|
|
2958
2977
|
seed: init.seed,
|
|
2959
2978
|
copies: this.copies,
|
|
2960
2979
|
paint: init.paint ?? null,
|
|
2961
|
-
effect
|
|
2980
|
+
effect,
|
|
2981
|
+
stampArt: init.stampArt
|
|
2962
2982
|
});
|
|
2963
2983
|
} else {
|
|
2964
2984
|
const opts = { grain: init.grain, paperDepth: init.paperDepth, seed: init.seed, copies: this.copies, paint: init.paint ?? null, effect, stampArt: init.stampArt };
|
|
@@ -5205,6 +5225,55 @@ function engineChanges(base, next) {
|
|
|
5205
5225
|
return rest;
|
|
5206
5226
|
}
|
|
5207
5227
|
|
|
5228
|
+
// src/paint-editor/path-samples.ts
|
|
5229
|
+
var FLATTEN_TOLERANCE = 0.1;
|
|
5230
|
+
var MAX_PATH_SAMPLES = 2e4;
|
|
5231
|
+
function profileAt(profile, t) {
|
|
5232
|
+
if (profile.length === 1) return profile[0];
|
|
5233
|
+
const at = t * (profile.length - 1);
|
|
5234
|
+
const i = Math.min(profile.length - 2, Math.floor(at));
|
|
5235
|
+
return profile[i] + (profile[i + 1] - profile[i]) * (at - i);
|
|
5236
|
+
}
|
|
5237
|
+
function samplePath(d, options = {}) {
|
|
5238
|
+
const spacing = options.spacing ?? 2;
|
|
5239
|
+
if (!(spacing >= 0.25 && spacing <= 100)) throw new Error(`spacing ${spacing} is out of range; use 0.25..100 document pixels.`);
|
|
5240
|
+
const profile = options.pressure;
|
|
5241
|
+
if (profile && (!profile.length || profile.some((p) => !(p >= 0 && p <= 1)))) throw new Error("pressure needs one or more values from 0 to 1, e.g. [0.2, 1, 0.2] for a taper.");
|
|
5242
|
+
let contours;
|
|
5243
|
+
try {
|
|
5244
|
+
contours = pathToContours(d);
|
|
5245
|
+
} catch (e) {
|
|
5246
|
+
throw new Error(`path is not SVG path data (${e instanceof Error ? e.message : String(e)}); give e.g. "M 10 10 C 40 0 80 60 120 30".`);
|
|
5247
|
+
}
|
|
5248
|
+
const out = [];
|
|
5249
|
+
for (const contour of contours) {
|
|
5250
|
+
if (!contour.segments.length) continue;
|
|
5251
|
+
const line = flattenChain(contour.segments, FLATTEN_TOLERANCE);
|
|
5252
|
+
if (contour.closed && line.length) line.push(line[0]);
|
|
5253
|
+
const lengths = [0];
|
|
5254
|
+
for (let i = 1; i < line.length; i++) lengths.push(lengths[i - 1] + Math.hypot(line[i][0] - line[i - 1][0], line[i][1] - line[i - 1][1]));
|
|
5255
|
+
const total = lengths[lengths.length - 1];
|
|
5256
|
+
const n = Math.max(1, Math.ceil(total / spacing));
|
|
5257
|
+
if (n + 1 > MAX_PATH_SAMPLES) throw new Error(`The path is ${Math.round(total)} px long: too many samples at spacing ${spacing}; use a larger spacing (at least ${Math.ceil(total / (MAX_PATH_SAMPLES - 1))}).`);
|
|
5258
|
+
const points = [];
|
|
5259
|
+
let j = 0;
|
|
5260
|
+
for (let k = 0; k <= n; k++) {
|
|
5261
|
+
const at = total * k / n;
|
|
5262
|
+
while (j < line.length - 2 && lengths[j + 1] < at) j++;
|
|
5263
|
+
const span = lengths[j + 1] - lengths[j] || 1;
|
|
5264
|
+
const f = line.length > 1 ? Math.min(1, Math.max(0, (at - lengths[j]) / span)) : 0;
|
|
5265
|
+
const a = line[j];
|
|
5266
|
+
const b = line[Math.min(j + 1, line.length - 1)];
|
|
5267
|
+
const x = Math.round((a[0] + (b[0] - a[0]) * f) * 1e3) / 1e3;
|
|
5268
|
+
const y = Math.round((a[1] + (b[1] - a[1]) * f) * 1e3) / 1e3;
|
|
5269
|
+
points.push(profile ? { x, y, pressure: Math.round(profileAt(profile, k / n) * 1e3) / 1e3 } : { x, y });
|
|
5270
|
+
}
|
|
5271
|
+
out.push(points);
|
|
5272
|
+
}
|
|
5273
|
+
if (!out.length) throw new Error('path has no segments to brush; give SVG path data such as "M 10 10 L 200 80".');
|
|
5274
|
+
return out;
|
|
5275
|
+
}
|
|
5276
|
+
|
|
5208
5277
|
// src/paint-editor/live-strokes.ts
|
|
5209
5278
|
var LIVE_STROKE_ENGINES = ["dab", "particle", "bristle", "mixer"];
|
|
5210
5279
|
function liveStrokeRefusal(brush) {
|
|
@@ -7841,6 +7910,47 @@ var PaintEditorController = class _PaintEditorController {
|
|
|
7841
7910
|
else this.emit([]);
|
|
7842
7911
|
return result;
|
|
7843
7912
|
}
|
|
7913
|
+
/**
|
|
7914
|
+
* Brushes vector path data (SVG `d`, document pixels) with the active brush, or `brushId` /
|
|
7915
|
+
* `brush`, as if drawn by hand: one recorded stroke per subpath, sampled every `spacing` px
|
|
7916
|
+
* with an optional `pressure` profile along each. All of it is one undo step. A given `seed`
|
|
7917
|
+
* seeds the first subpath; the next ones take the following seeds.
|
|
7918
|
+
*/
|
|
7919
|
+
strokePath(init) {
|
|
7920
|
+
this.requireIdle("painting");
|
|
7921
|
+
if (typeof init.path !== "string" || !init.path.trim()) throw new PaintEditorError('path is empty; give SVG path data in document pixels, e.g. "M 10 10 C 40 0 80 60 120 30".');
|
|
7922
|
+
let subpaths;
|
|
7923
|
+
try {
|
|
7924
|
+
subpaths = samplePath(init.path, init);
|
|
7925
|
+
} catch (e) {
|
|
7926
|
+
throw new PaintEditorError(e instanceof Error ? e.message : String(e));
|
|
7927
|
+
}
|
|
7928
|
+
const { path: _path, spacing: _spacing, pressure: _pressure, ...stroke } = init;
|
|
7929
|
+
const before = this.doc;
|
|
7930
|
+
let doc = this.doc;
|
|
7931
|
+
let bounds = null;
|
|
7932
|
+
let dabs = 0;
|
|
7933
|
+
const strokeIds = [];
|
|
7934
|
+
let last = null;
|
|
7935
|
+
try {
|
|
7936
|
+
subpaths.forEach((points, i) => {
|
|
7937
|
+
last = this.runStroke(doc, { ...stroke, ...stroke.seed !== void 0 ? { seed: stroke.seed + i } : {}, start: points[0] }, points);
|
|
7938
|
+
doc = last.doc;
|
|
7939
|
+
bounds = unionRect(bounds, last.result.bounds);
|
|
7940
|
+
dabs += last.result.dabs;
|
|
7941
|
+
if (last.result.strokeId) strokeIds.push(last.result.strokeId);
|
|
7942
|
+
});
|
|
7943
|
+
} catch (e) {
|
|
7944
|
+
this.wetOp = null;
|
|
7945
|
+
throw e;
|
|
7946
|
+
}
|
|
7947
|
+
const run = last;
|
|
7948
|
+
if (run?.result.mixerLoad) this.keepLoad(run.brush, run.result.mixerLoad);
|
|
7949
|
+
if (doc !== before || this.wetOp) this.commit(`${run?.brush.name ?? "Brush"} path stroke`, this.withWetEvents(doc), before);
|
|
7950
|
+
else this.emit([]);
|
|
7951
|
+
const layerId = run?.result.layerId ?? stroke.layerId ?? this.doc.activeLayerId ?? "";
|
|
7952
|
+
return { layerId, bounds, dabs, strokeIds, ...run?.result.mixerLoad ? { mixerLoad: run.result.mixerLoad } : {}, ...run?.result.wet !== void 0 ? { wet: run.result.wet } : {} };
|
|
7953
|
+
}
|
|
7844
7954
|
/** Checks stroke samples (finite x and y, pressure 0..1) and returns them; throws a sentence naming the bad point. */
|
|
7845
7955
|
checkPoints(points) {
|
|
7846
7956
|
if (!Array.isArray(points) || !points.length) throw new PaintEditorError("points is empty; give at least one {x, y} in document pixels.");
|
|
@@ -11563,6 +11673,41 @@ function createPaintEditorToolProvider(controller, options = {}) {
|
|
|
11563
11673
|
return r.mixerLoad ? { ...r, mixerLoad: loadResult(r.mixerLoad) } : r;
|
|
11564
11674
|
}
|
|
11565
11675
|
});
|
|
11676
|
+
add({
|
|
11677
|
+
name: "stroke_path",
|
|
11678
|
+
title: "Brush along a path",
|
|
11679
|
+
description: "Brushes vector path data (SVG path d in document pixels: M, L, H, V, C, S, Q, T, A and Z) on a pixel layer with a library brush or the active one, as if drawn by hand along it: the outline of a shape, a lettering guide, a curve from a vector tool. Each subpath becomes one recorded stroke, sampled every spacing px, with an optional pressure profile spread along each subpath (a taper such as [0.2, 1, 0.2]); closed subpaths end where they start. Brushes behave as in paint_stroke (grain, pick-up, wet and thick paint, guides off). One undo step for the whole path. Returns the layer id, changed bounds, dab count and the recorded strokeIds (for replay_strokes, update_stroke).",
|
|
11680
|
+
inputSchema: {
|
|
11681
|
+
type: "object",
|
|
11682
|
+
properties: {
|
|
11683
|
+
path: { type: "string", minLength: 1, description: 'SVG path data in document pixels, e.g. "M 40 200 C 120 40 280 40 360 200". Several subpaths paint several strokes.' },
|
|
11684
|
+
spacing: { type: "number", minimum: 0.25, maximum: 100, description: "Distance between samples along the path in document pixels (default 2); the brush spaces its own dabs, so this only sets how closely curves are followed." },
|
|
11685
|
+
pressure: { type: "array", minItems: 1, maxItems: 64, items: { type: "number", minimum: 0, maximum: 1 }, description: "Pen pressure along each subpath, spread evenly from start to end (default full pressure); [0.2, 1, 0.2] tapers both ends." },
|
|
11686
|
+
layerId: LAYER_ID4,
|
|
11687
|
+
brushId: { type: "string", description: "Brush id from list_brush_presets (default the active brush)." },
|
|
11688
|
+
brush: { type: "object", description: "Facets to change for this stroke only, laid over brushId or the active brush (as set_brush takes them).", properties: BRUSH_PATCH_PROPERTIES, additionalProperties: false },
|
|
11689
|
+
color: { ...COLOR3, description: "Paint colour #rrggbb (default the paint colour)." },
|
|
11690
|
+
paint: { ...PAINT_SOURCE_JSON_SCHEMA, description: "What the strokes paint with instead of the editor's paint: a colour, or gradient paint that changes colour as each stroke goes." },
|
|
11691
|
+
seed: { type: "integer", minimum: 0, description: "Jitter seed of the first subpath, the next ones take the following seeds (default new ones); the same seed and path give the same marks." }
|
|
11692
|
+
},
|
|
11693
|
+
required: ["path"],
|
|
11694
|
+
additionalProperties: false
|
|
11695
|
+
},
|
|
11696
|
+
annotations: { title: "Brush along a path", ...WRITE5 },
|
|
11697
|
+
coordinates: { out: ["bounds"] },
|
|
11698
|
+
handler: async (a) => {
|
|
11699
|
+
await c.wetIdle();
|
|
11700
|
+
const base = a.brushId ? c.getBrush(a.brushId) : c.getState().brush;
|
|
11701
|
+
const brush = a.brush ? c.brushChanges(a.brush, a.brushId) : void 0;
|
|
11702
|
+
if (base) {
|
|
11703
|
+
const refs = brushStickerRefs(normalizePaintBrush({ ...brush ?? {}, id: base.id }, base));
|
|
11704
|
+
if (refs.length) await c.prepareStickers(refs);
|
|
11705
|
+
}
|
|
11706
|
+
const path = options.origin && typeof a.path === "string" && a.path.trim() ? bezierPathToSvg(a.path, { offset: { x: -options.origin.x, y: -options.origin.y } }) : a.path;
|
|
11707
|
+
const r = c.strokePath({ path, spacing: a.spacing, pressure: a.pressure, layerId: a.layerId, brushId: a.brushId, brush, color: a.color, paint: a.paint, seed: a.seed });
|
|
11708
|
+
return r.mixerLoad ? { ...r, mixerLoad: loadResult(r.mixerLoad) } : r;
|
|
11709
|
+
}
|
|
11710
|
+
});
|
|
11566
11711
|
add({
|
|
11567
11712
|
name: "get_live_stroke",
|
|
11568
11713
|
title: "Get the stroke being painted",
|
|
@@ -12178,44 +12323,47 @@ var PaintGesture = class {
|
|
|
12178
12323
|
}
|
|
12179
12324
|
};
|
|
12180
12325
|
var round2 = (v) => Math.round(v * 100) / 100;
|
|
12326
|
+
var PAINT_SHORTCUTS = [
|
|
12327
|
+
{ id: "undo", label: "Undo", group: "edit", keys: ["Mod+Z"], tool: "undo_paint" },
|
|
12328
|
+
{ id: "redo", label: "Redo", group: "edit", keys: ["Mod+Shift+Z", "Mod+Y"], tool: "redo_paint" },
|
|
12329
|
+
...PAINT_TOOLS.map((t) => ({ id: `tool.${t.id}`, label: t.label, group: "tools", keys: [t.key.toUpperCase()] })),
|
|
12330
|
+
{ id: "brush.smaller", label: "Smaller brush", group: "brush", keys: ["["], tool: "set_brush" },
|
|
12331
|
+
{ id: "brush.larger", label: "Larger brush", group: "brush", keys: ["]"], tool: "set_brush" },
|
|
12332
|
+
{ id: "colour.swap", label: "Swap colours", group: "colour", keys: ["X"] },
|
|
12333
|
+
{ id: "view.mirror", label: "Mirror the view", group: "view", keys: ["M"], tool: "set_view" },
|
|
12334
|
+
{ id: "view.reset", label: "Reset the view", group: "view", keys: ["Escape"], tool: "set_view" }
|
|
12335
|
+
];
|
|
12336
|
+
function paintShortcutFor(e) {
|
|
12337
|
+
if (!e.key) return null;
|
|
12338
|
+
const parts = [...e.ctrlKey || e.metaKey ? ["Mod"] : [], ...e.shiftKey && (e.ctrlKey || e.metaKey) ? ["Shift"] : []];
|
|
12339
|
+
const key = e.key.length === 1 ? e.key.toUpperCase() : e.key[0].toUpperCase() + e.key.slice(1).toLowerCase();
|
|
12340
|
+
const combo = [...parts, key].join("+");
|
|
12341
|
+
return PAINT_SHORTCUTS.find((s) => s.keys.includes(combo)) ?? null;
|
|
12342
|
+
}
|
|
12181
12343
|
function handlePaintShortcut(controller, gesture, e) {
|
|
12182
|
-
const
|
|
12183
|
-
|
|
12184
|
-
|
|
12185
|
-
|
|
12186
|
-
if (
|
|
12187
|
-
|
|
12188
|
-
|
|
12189
|
-
}
|
|
12190
|
-
if (mod && key === "y") {
|
|
12191
|
-
controller.redo();
|
|
12344
|
+
const shortcut = paintShortcutFor(e);
|
|
12345
|
+
if (!shortcut) return false;
|
|
12346
|
+
const id = shortcut.id;
|
|
12347
|
+
if (id === "undo" || id === "redo") {
|
|
12348
|
+
if (controller.getState().stroking && !(id === "redo" && e.key.toLowerCase() === "y")) return true;
|
|
12349
|
+
if (id === "undo") controller.undo();
|
|
12350
|
+
else controller.redo();
|
|
12192
12351
|
return true;
|
|
12193
12352
|
}
|
|
12194
|
-
if (
|
|
12195
|
-
|
|
12196
|
-
if (tool) {
|
|
12197
|
-
gesture.tool = tool.id;
|
|
12353
|
+
if (id.startsWith("tool.")) {
|
|
12354
|
+
gesture.tool = id.slice(5);
|
|
12198
12355
|
return true;
|
|
12199
12356
|
}
|
|
12200
|
-
|
|
12201
|
-
|
|
12357
|
+
if (id === "brush.smaller" || id === "brush.larger") {
|
|
12358
|
+
const brush = controller.getState().brush;
|
|
12202
12359
|
const step = Math.max(1, Math.round(brush.tip.size * 0.15));
|
|
12203
|
-
controller.setBrush({ tip: { size: Math.max(1, brush.tip.size + (
|
|
12204
|
-
return true;
|
|
12205
|
-
}
|
|
12206
|
-
if (key === "x") {
|
|
12207
|
-
controller.swapColors();
|
|
12208
|
-
return true;
|
|
12209
|
-
}
|
|
12210
|
-
if (key === "m") {
|
|
12211
|
-
controller.setView({ mirror: !controller.getState().view.mirror });
|
|
12360
|
+
controller.setBrush({ tip: { size: Math.max(1, brush.tip.size + (id === "brush.larger" ? step : -step)) } });
|
|
12212
12361
|
return true;
|
|
12213
12362
|
}
|
|
12214
|
-
if (
|
|
12215
|
-
|
|
12216
|
-
|
|
12217
|
-
|
|
12218
|
-
return false;
|
|
12363
|
+
if (id === "colour.swap") controller.swapColors();
|
|
12364
|
+
else if (id === "view.mirror") controller.setView({ mirror: !controller.getState().view.mirror });
|
|
12365
|
+
else if (id === "view.reset") controller.resetView();
|
|
12366
|
+
return true;
|
|
12219
12367
|
}
|
|
12220
12368
|
var PaintCanvasPresenter = class {
|
|
12221
12369
|
constructor(controller) {
|
|
@@ -12461,6 +12609,12 @@ function toggleDotTexture(textures, stamp) {
|
|
|
12461
12609
|
if (textures.includes(stamp)) return textures.filter((s) => s !== stamp);
|
|
12462
12610
|
return textures.length < MAX_DOT_TEXTURES ? [...textures, stamp] : [...textures];
|
|
12463
12611
|
}
|
|
12612
|
+
var PATH_STROKE_TAPERS = [
|
|
12613
|
+
{ id: "even", label: "Even" },
|
|
12614
|
+
{ id: "both", label: "Taper both ends", pressure: [0.2, 1, 0.2] },
|
|
12615
|
+
{ id: "in", label: "Fade in", pressure: [0.2, 1] },
|
|
12616
|
+
{ id: "out", label: "Fade out", pressure: [1, 0.2] }
|
|
12617
|
+
];
|
|
12464
12618
|
var STROKE_SLIDERS = [
|
|
12465
12619
|
{ path: "tip.size", label: "Size", min: 1, max: 400, step: 1 },
|
|
12466
12620
|
{ path: "tip.opacity", label: "Opacity", min: 0, max: 1, step: 0.01 },
|
|
@@ -13171,6 +13325,127 @@ function createPaintProposalToolProvider(proposals, options = {}) {
|
|
|
13171
13325
|
tools: [proposeTool, listTool, acceptTool, rejectTool]
|
|
13172
13326
|
};
|
|
13173
13327
|
}
|
|
13328
|
+
|
|
13329
|
+
// src/paint-editor/contract.ts
|
|
13330
|
+
var PAINT_CONTRACT_FORMAT_VERSION = 1;
|
|
13331
|
+
var POINT2 = { type: "object", properties: { x: { type: "number", description: "Document x in pixels." }, y: { type: "number", description: "Document y in pixels." } }, required: ["x", "y"] };
|
|
13332
|
+
var PAINT_DOCUMENT_JSON_SCHEMA = {
|
|
13333
|
+
type: "object",
|
|
13334
|
+
description: "A saved painting, as the paint editor's save tools and autosave write it.",
|
|
13335
|
+
properties: {
|
|
13336
|
+
format: { type: "string", const: EDITOR_DOCUMENT_FORMAT, description: `Always "${EDITOR_DOCUMENT_FORMAT}".` },
|
|
13337
|
+
kind: { type: "string", const: "paint", description: 'Always "paint".' },
|
|
13338
|
+
formatVersion: { type: "integer", minimum: 1, description: "Version of the envelope for this kind; readers migrate older ones." },
|
|
13339
|
+
name: { type: "string", description: "Display name, when it has one." },
|
|
13340
|
+
document: {
|
|
13341
|
+
type: "object",
|
|
13342
|
+
description: "The painting (`SerializedPaintDocument`).",
|
|
13343
|
+
properties: {
|
|
13344
|
+
format: { type: "string", const: PAINT_FORMAT, description: `Always "${PAINT_FORMAT}".` },
|
|
13345
|
+
version: { type: "integer", minimum: 1, maximum: PAINT_SCHEMA_VERSION, description: `Painting version (${PAINT_SCHEMA_VERSION}; version 1 strokes hold flat engine brushes and are migrated).` },
|
|
13346
|
+
width: { type: "integer", minimum: 1, description: "Canvas width in pixels." },
|
|
13347
|
+
height: { type: "integer", minimum: 1, description: "Canvas height in pixels." },
|
|
13348
|
+
dpi: { type: "number", exclusiveMinimum: 0, description: "Pixels per inch." },
|
|
13349
|
+
paper: {
|
|
13350
|
+
type: "object",
|
|
13351
|
+
description: "The paper settings; the paper is regenerated from them, never stored as pixels.",
|
|
13352
|
+
properties: {
|
|
13353
|
+
preset: { type: "string", description: "The paper preset it started from (custom when none)." },
|
|
13354
|
+
name: { type: "string", description: "Display name." },
|
|
13355
|
+
pattern: { type: "string", description: "Grain pattern id." },
|
|
13356
|
+
seed: { type: "number", description: "Grain seed." },
|
|
13357
|
+
color: { type: "string", description: "Paper colour #rrggbb." },
|
|
13358
|
+
scale: { type: "number", description: "Grain scale (0.25 to 8)." },
|
|
13359
|
+
depth: { type: "number", minimum: 0, maximum: 1, description: "Grain depth for dry media." }
|
|
13360
|
+
},
|
|
13361
|
+
required: ["preset", "pattern", "seed", "color"]
|
|
13362
|
+
},
|
|
13363
|
+
layers: {
|
|
13364
|
+
type: "array",
|
|
13365
|
+
description: "Bottom to top: the image editor's serialized layers (pixel layers keep their pixels in `assets`).",
|
|
13366
|
+
items: { type: "object", properties: { id: { type: "string", description: "Layer id." }, name: { type: "string", description: "Layer name." }, type: { type: "string", description: "pixel, group, \u2026" } }, required: ["id", "type"] }
|
|
13367
|
+
},
|
|
13368
|
+
assets: { type: "object", additionalProperties: { type: "string" }, description: "Asset id \u2192 PNG data URL or an external reference." },
|
|
13369
|
+
activeLayerId: { type: ["string", "null"], description: "The active layer." },
|
|
13370
|
+
strokes: {
|
|
13371
|
+
type: "array",
|
|
13372
|
+
description: "Recorded strokes, oldest first (missing when saved without them).",
|
|
13373
|
+
items: {
|
|
13374
|
+
type: "object",
|
|
13375
|
+
properties: {
|
|
13376
|
+
id: { type: "string", description: "Stroke id." },
|
|
13377
|
+
layerId: { type: "string", description: "The layer it painted on." },
|
|
13378
|
+
color: { type: "string", description: "The paint colour as recorded at stroke start." },
|
|
13379
|
+
seed: { type: "number", description: "Seed of its jitter and grain, so a replay is exact." },
|
|
13380
|
+
points: { type: "array", items: POINT2, description: "The samples, with pressure, tilt and time when recorded." },
|
|
13381
|
+
brush: { $ref: "../brushes/brush.schema.json", description: "The facet brush it painted with." }
|
|
13382
|
+
},
|
|
13383
|
+
required: ["id", "layerId", "points", "brush"]
|
|
13384
|
+
}
|
|
13385
|
+
}
|
|
13386
|
+
},
|
|
13387
|
+
required: ["format", "version", "width", "height", "dpi", "paper", "layers", "assets", "activeLayerId"]
|
|
13388
|
+
}
|
|
13389
|
+
},
|
|
13390
|
+
required: ["format", "kind", "formatVersion", "document"]
|
|
13391
|
+
};
|
|
13392
|
+
function toolList(provider) {
|
|
13393
|
+
const tools = typeof provider.tools === "function" ? provider.tools() : provider.tools;
|
|
13394
|
+
return {
|
|
13395
|
+
id: provider.id,
|
|
13396
|
+
title: provider.title,
|
|
13397
|
+
...provider.skill ? { skill: provider.skill } : {},
|
|
13398
|
+
tools: tools.map((t) => ({ name: t.name, title: t.title, description: t.description, inputSchema: t.inputSchema, annotations: t.annotations }))
|
|
13399
|
+
};
|
|
13400
|
+
}
|
|
13401
|
+
function uiTables() {
|
|
13402
|
+
return {
|
|
13403
|
+
tools: PAINT_TOOLS,
|
|
13404
|
+
brushOptions: BRUSH_OPTION_CONTROLS,
|
|
13405
|
+
blendMenu: BLEND_MENU_GROUPS,
|
|
13406
|
+
layerEffects: LAYER_EFFECT_MENU.map(({ kind, label }) => ({ kind, label, controls: layerEffectControls(defaultLayerEffect(kind, kind)) })),
|
|
13407
|
+
sliders: { paper: PAPER_SLIDERS, wet: WET_PARAM_SLIDERS, wetTilt: WET_TILT_SLIDERS, wetSpeed: WET_SPEED_SLIDER, light: LIGHT_SLIDERS, stroke: STROKE_SLIDERS, sticker: STICKER_SLIDERS },
|
|
13408
|
+
pathStrokeTapers: PATH_STROKE_TAPERS,
|
|
13409
|
+
padSampleSizes: PAD_SAMPLE_SIZES,
|
|
13410
|
+
wetGpu: WET_GPU_OPTIONS,
|
|
13411
|
+
labels: {
|
|
13412
|
+
wetTools: WET_TOOL_LABELS,
|
|
13413
|
+
wetMedia: WET_MEDIUM_LABELS,
|
|
13414
|
+
impastoTools: IMPASTO_TOOL_LABELS,
|
|
13415
|
+
particleKinds: PARTICLE_KIND_LABELS,
|
|
13416
|
+
particleRenders: PARTICLE_RENDER_LABELS,
|
|
13417
|
+
ribbonPairs: RIBBON_PAIR_LABELS,
|
|
13418
|
+
dotTextureOrders: DOT_TEXTURE_ORDER_LABELS,
|
|
13419
|
+
dotRotations: DOT_ROTATION_LABELS,
|
|
13420
|
+
stamps: STAMP_LABELS
|
|
13421
|
+
}
|
|
13422
|
+
};
|
|
13423
|
+
}
|
|
13424
|
+
function paintContracts(options) {
|
|
13425
|
+
const controller = new PaintEditorController({ width: 64, height: 64 });
|
|
13426
|
+
controller.setEffectsProvider(options.effects);
|
|
13427
|
+
const providers = [
|
|
13428
|
+
createPaintEditorToolProvider(controller),
|
|
13429
|
+
createPaintEffectsToolProvider(controller),
|
|
13430
|
+
createPaintBrushLibraryToolProvider(controller),
|
|
13431
|
+
createPaintProposalToolProvider(createPaintProposals({ controller }))
|
|
13432
|
+
].map(toolList);
|
|
13433
|
+
const document = createPaintEditorDocument(controller);
|
|
13434
|
+
const files = {
|
|
13435
|
+
"document.schema.json": { $schema: "https://json-schema.org/draft/2020-12/schema", $id: "document.schema.json", ...PAINT_DOCUMENT_JSON_SCHEMA },
|
|
13436
|
+
"tools.json": { providers },
|
|
13437
|
+
"commands.json": PAINT_SHORTCUTS.map((s) => ({ ...s, params: { type: "object", properties: {}, additionalProperties: false } })),
|
|
13438
|
+
"ui.json": uiTables()
|
|
13439
|
+
};
|
|
13440
|
+
files["manifest.json"] = {
|
|
13441
|
+
domain: "paint",
|
|
13442
|
+
formatVersion: PAINT_CONTRACT_FORMAT_VERSION,
|
|
13443
|
+
document: { kind: document.kind, formatVersion: document.formatVersion, format: PAINT_FORMAT, version: PAINT_SCHEMA_VERSION },
|
|
13444
|
+
counts: { providers: providers.length, tools: providers.reduce((n, p) => n + p.tools.length, 0), commands: PAINT_SHORTCUTS.length, layerEffects: LAYER_EFFECT_KINDS.length },
|
|
13445
|
+
files: Object.keys(files).sort()
|
|
13446
|
+
};
|
|
13447
|
+
return files;
|
|
13448
|
+
}
|
|
13174
13449
|
export {
|
|
13175
13450
|
BACKLIGHT_SPILL,
|
|
13176
13451
|
BLEND_MENU_GROUPS,
|
|
@@ -13225,6 +13500,7 @@ export {
|
|
|
13225
13500
|
MAX_DOT_TEXTURES,
|
|
13226
13501
|
MAX_IMAGE_SIDE,
|
|
13227
13502
|
MAX_LIVE_EFFECT_SOURCES,
|
|
13503
|
+
MAX_PATH_SAMPLES,
|
|
13228
13504
|
MAX_STICKER_STAMPS,
|
|
13229
13505
|
MAX_ZOOM,
|
|
13230
13506
|
MIN_CANVAS_SIDE,
|
|
@@ -13238,6 +13514,8 @@ export {
|
|
|
13238
13514
|
PAINT_BRUSH_CATEGORIES,
|
|
13239
13515
|
PAINT_BRUSH_ENGINES,
|
|
13240
13516
|
PAINT_COLOR_JSON_SCHEMA,
|
|
13517
|
+
PAINT_CONTRACT_FORMAT_VERSION,
|
|
13518
|
+
PAINT_DOCUMENT_JSON_SCHEMA,
|
|
13241
13519
|
PAINT_EFFECT_PLACES,
|
|
13242
13520
|
PAINT_FORMAT,
|
|
13243
13521
|
PAINT_GRADIENT_FULL_SPEED,
|
|
@@ -13251,6 +13529,7 @@ export {
|
|
|
13251
13529
|
PAINT_GRADIENT_SPACES,
|
|
13252
13530
|
PAINT_GRADIENT_SPACE_LABELS,
|
|
13253
13531
|
PAINT_SCHEMA_VERSION,
|
|
13532
|
+
PAINT_SHORTCUTS,
|
|
13254
13533
|
PAINT_SOURCE_JSON_SCHEMA,
|
|
13255
13534
|
PAINT_TARGET,
|
|
13256
13535
|
PAINT_TOOLS,
|
|
@@ -13265,6 +13544,7 @@ export {
|
|
|
13265
13544
|
PARTICLE_KIND_LABELS,
|
|
13266
13545
|
PARTICLE_RENDERS,
|
|
13267
13546
|
PARTICLE_RENDER_LABELS,
|
|
13547
|
+
PATH_STROKE_TAPERS,
|
|
13268
13548
|
PaintCanvasPresenter,
|
|
13269
13549
|
PaintDabStroke,
|
|
13270
13550
|
PaintDirector,
|
|
@@ -13425,9 +13705,11 @@ export {
|
|
|
13425
13705
|
overPixels,
|
|
13426
13706
|
padSampleRadius,
|
|
13427
13707
|
paintAgentStrokePoints,
|
|
13708
|
+
paintContracts,
|
|
13428
13709
|
paintGradientMappingFor,
|
|
13429
13710
|
paintPlaybackPanel,
|
|
13430
13711
|
paintProposalGhost,
|
|
13712
|
+
paintShortcutFor,
|
|
13431
13713
|
paintSourceColor,
|
|
13432
13714
|
paintSourceCss,
|
|
13433
13715
|
paintToMultiColor,
|
|
@@ -13473,6 +13755,7 @@ export {
|
|
|
13473
13755
|
sampleArt,
|
|
13474
13756
|
sampleEffectAt,
|
|
13475
13757
|
sampleEffectField,
|
|
13758
|
+
samplePath,
|
|
13476
13759
|
samplePressure,
|
|
13477
13760
|
scalePaintGradient,
|
|
13478
13761
|
scaleStencil,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pen samples along vector path data, so a path can be brushed like a hand-drawn stroke
|
|
3
|
+
* (`strokePath`, the `stroke_path` tool): each subpath becomes one stroke, sampled at an even
|
|
4
|
+
* spacing, with an optional pressure profile spread along its length (a taper).
|
|
5
|
+
*/
|
|
6
|
+
import type { PenPoint } from './engine.cjs';
|
|
7
|
+
/** The most samples one subpath may have (the stroke tools' point limit). */
|
|
8
|
+
export declare const MAX_PATH_SAMPLES = 20000;
|
|
9
|
+
export interface PathSampleOptions {
|
|
10
|
+
/** Distance between samples in document pixels (default 2). */
|
|
11
|
+
spacing?: number;
|
|
12
|
+
/** Pressure along each subpath, spread evenly from its start to its end (default full pressure). */
|
|
13
|
+
pressure?: readonly number[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Samples SVG path data (document pixels) into pen points, one array per subpath. Closed
|
|
17
|
+
* subpaths end where they start. Throws a sentence when the path has nothing to brush.
|
|
18
|
+
*/
|
|
19
|
+
export declare function samplePath(d: string, options?: PathSampleOptions): PenPoint[][];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pen samples along vector path data, so a path can be brushed like a hand-drawn stroke
|
|
3
|
+
* (`strokePath`, the `stroke_path` tool): each subpath becomes one stroke, sampled at an even
|
|
4
|
+
* spacing, with an optional pressure profile spread along its length (a taper).
|
|
5
|
+
*/
|
|
6
|
+
import type { PenPoint } from './engine.js';
|
|
7
|
+
/** The most samples one subpath may have (the stroke tools' point limit). */
|
|
8
|
+
export declare const MAX_PATH_SAMPLES = 20000;
|
|
9
|
+
export interface PathSampleOptions {
|
|
10
|
+
/** Distance between samples in document pixels (default 2). */
|
|
11
|
+
spacing?: number;
|
|
12
|
+
/** Pressure along each subpath, spread evenly from its start to its end (default full pressure). */
|
|
13
|
+
pressure?: readonly number[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Samples SVG path data (document pixels) into pen points, one array per subpath. Closed
|
|
17
|
+
* subpaths end where they start. Throws a sentence when the path has nothing to brush.
|
|
18
|
+
*/
|
|
19
|
+
export declare function samplePath(d: string, options?: PathSampleOptions): PenPoint[][];
|
|
@@ -43,3 +43,20 @@ export declare function brushStickerRefs(brush: {
|
|
|
43
43
|
dotTextures: readonly string[];
|
|
44
44
|
};
|
|
45
45
|
}): string[];
|
|
46
|
+
/**
|
|
47
|
+
* Spray marks: the droplets of dab `d`, spread evenly over its disc, each a fraction `size` of
|
|
48
|
+
* the tip (with ±40% variation) and turned at random. Seeded by the stroke `seed`, the dab's
|
|
49
|
+
* index and its travel, so replays lay the same droplets.
|
|
50
|
+
*/
|
|
51
|
+
export declare function sprayDroplets<D extends {
|
|
52
|
+
x: number;
|
|
53
|
+
y: number;
|
|
54
|
+
radius: number;
|
|
55
|
+
roundness: number;
|
|
56
|
+
angle: number;
|
|
57
|
+
index: number;
|
|
58
|
+
travel: number;
|
|
59
|
+
}>(d: D, spray: {
|
|
60
|
+
density: number;
|
|
61
|
+
size: number;
|
|
62
|
+
}, seed: number): D[];
|