@pixerate/editor 0.123.0 → 0.124.1
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.cjs.map +1 -1
- package/dist/agent/index.js +3 -3
- package/dist/animation/index.d.cts +2 -1
- package/dist/animation/index.d.ts +2 -1
- package/dist/animation-editor/index.cjs.map +1 -1
- package/dist/animation-editor/index.d.cts +11 -733
- package/dist/animation-editor/index.d.ts +11 -733
- package/dist/animation-editor/index.js.map +1 -1
- package/dist/{bezier-C-W9LFyD.d.cts → bezier-CBzKKDjl.d.cts} +2 -66
- package/dist/{bezier-DQZE5dxA.d.ts → bezier-CWv6LQal.d.ts} +2 -66
- package/dist/{brushstrokeRender-M4S65DHT.cjs → brushstrokeRender-EELTV7U4.cjs} +8 -8
- package/dist/{brushstrokeRender-M4S65DHT.cjs.map → brushstrokeRender-EELTV7U4.cjs.map} +1 -1
- package/dist/{brushstrokeRender-CDDRSV2W.js → brushstrokeRender-LRV7GXGD.js} +3 -3
- package/dist/canvas/index.cjs +6 -6
- package/dist/canvas/index.d.cts +10 -6
- package/dist/canvas/index.d.ts +10 -6
- package/dist/canvas/index.js +1 -1
- package/dist/{chunk-SMJGE7HM.cjs → chunk-4QQZV5Y5.cjs} +512 -39
- package/dist/chunk-4QQZV5Y5.cjs.map +1 -0
- package/dist/{chunk-TVXFHE4T.js → chunk-64PYNEHJ.js} +45 -12
- package/dist/chunk-64PYNEHJ.js.map +1 -0
- package/dist/{chunk-2IW3BWRT.cjs → chunk-7LQO6OHH.cjs} +2 -2
- package/dist/{chunk-2IW3BWRT.cjs.map → chunk-7LQO6OHH.cjs.map} +1 -1
- package/dist/chunk-CN4PJHUG.cjs +234 -0
- package/dist/chunk-CN4PJHUG.cjs.map +1 -0
- package/dist/chunk-FHYDQOPC.cjs +215 -0
- package/dist/chunk-FHYDQOPC.cjs.map +1 -0
- package/dist/{chunk-KMBC4HNV.js → chunk-J67OLQUQ.js} +2 -2
- package/dist/chunk-JRXJTPES.js +215 -0
- package/dist/chunk-JRXJTPES.js.map +1 -0
- package/dist/{chunk-IPFRWILU.js → chunk-ORHMVUR6.js} +488 -15
- package/dist/chunk-ORHMVUR6.js.map +1 -0
- package/dist/{chunk-6CJRDJCU.cjs → chunk-PEHYWUVV.cjs} +176 -111
- package/dist/chunk-PEHYWUVV.cjs.map +1 -0
- package/dist/chunk-QEG3GRN2.js +234 -0
- package/dist/chunk-QEG3GRN2.js.map +1 -0
- package/dist/{chunk-6ZBR4J62.js → chunk-WATAQ2Z2.js} +79 -14
- package/dist/chunk-WATAQ2Z2.js.map +1 -0
- package/dist/{chunk-RLRUAPBJ.cjs → chunk-Z6OUCS2K.cjs} +47 -14
- package/dist/chunk-Z6OUCS2K.cjs.map +1 -0
- package/dist/effects/index.cjs +605 -0
- package/dist/effects/index.cjs.map +1 -0
- package/dist/effects/index.d.cts +163 -0
- package/dist/effects/index.d.ts +163 -0
- package/dist/effects/index.js +605 -0
- package/dist/effects/index.js.map +1 -0
- package/dist/evaluate-2S3vSPLy.d.cts +55 -0
- package/dist/evaluate-CfmT6xA3.d.ts +55 -0
- package/dist/{export-DfjzHAJI.d.ts → export-BGtXgMJ3.d.ts} +536 -128
- package/dist/{export-Dv0-ZVrV.d.cts → export-BdfY_lH0.d.ts} +392 -5
- package/dist/{export-Dv0-ZVrV.d.ts → export-CbpjwxaI.d.cts} +392 -5
- package/dist/{export-lU5qSpHE.d.cts → export-WmYoWmLM.d.cts} +536 -128
- package/dist/flair-CxB3fOcU.d.ts +739 -0
- package/dist/flair-D7BxyBwL.d.cts +739 -0
- package/dist/gradient-KDfCvhuj.d.cts +66 -0
- package/dist/gradient-KDfCvhuj.d.ts +66 -0
- package/dist/helpers-777mC6dc.d.cts +253 -0
- package/dist/helpers-DX2_7L5t.d.ts +253 -0
- package/dist/image-editor/index.cjs +1296 -805
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +39 -182
- package/dist/image-editor/index.d.ts +39 -182
- package/dist/image-editor/index.js +497 -6
- package/dist/image-editor/index.js.map +1 -1
- package/dist/{opentype-p8Xxf-Wh.d.ts → opentype-14PDI5D2.d.ts} +1 -1
- package/dist/{opentype-BV7wWatY.d.cts → opentype-D80AVmMu.d.cts} +1 -1
- package/dist/paint/index.cjs +2 -2
- package/dist/paint/index.d.cts +32 -10
- package/dist/paint/index.d.ts +32 -10
- package/dist/paint/index.js +1 -1
- package/dist/paint-editor/index.cjs +10 -8
- package/dist/paint-editor/index.cjs.map +1 -1
- package/dist/paint-editor/index.d.cts +11 -5
- package/dist/paint-editor/index.d.ts +11 -5
- package/dist/paint-editor/index.js +4 -2
- package/dist/paint-editor/index.js.map +1 -1
- package/dist/parameters/index.d.cts +4 -184
- package/dist/parameters/index.d.ts +4 -184
- package/dist/{pathfinder-IxcyhO00.d.cts → pathfinder-C0UoFypx.d.cts} +1 -1
- package/dist/{pathfinder-StVChJOT.d.ts → pathfinder-DjISxkUv.d.ts} +1 -1
- package/dist/{player-DrcP2xqj.d.cts → player-CBtEoU2x.d.cts} +2 -54
- package/dist/{player-DV4Jujdq.d.ts → player-cGLrngL_.d.ts} +2 -54
- package/dist/{svg-BtND4TJu.d.cts → svg-BDrjRnL1.d.cts} +3 -2
- package/dist/{svg-BfVXG7hL.d.ts → svg-C-vZ3q50.d.ts} +3 -2
- package/dist/tools/index.cjs +2 -2
- package/dist/tools/index.cjs.map +1 -1
- package/dist/tools/index.js +3 -3
- package/dist/tools-C7JtLYzo.d.ts +126 -0
- package/dist/tools-DHKGHiSK.d.cts +126 -0
- package/dist/{types-fuXEme9j.d.ts → types-BcX7sCsn.d.ts} +2 -2
- package/dist/{types-DJF3Ghn5.d.cts → types-CznabG7m.d.cts} +2 -2
- package/dist/types-D3-J3TFf.d.ts +185 -0
- package/dist/types-DMcf_z4J.d.cts +185 -0
- package/dist/vector/index.d.cts +10 -10
- package/dist/vector/index.d.ts +10 -10
- package/dist/video-editor/index.cjs +749 -277
- package/dist/video-editor/index.cjs.map +1 -1
- package/dist/video-editor/index.d.cts +265 -138
- package/dist/video-editor/index.d.ts +265 -138
- package/dist/video-editor/index.js +490 -18
- package/dist/video-editor/index.js.map +1 -1
- package/dist/{warp-BjmAgQwq.d.cts → warp-CH9MM435.d.cts} +1 -1
- package/dist/{warp-Ca5fedzS.d.ts → warp-k_PdtXOG.d.ts} +1 -1
- package/package.json +6 -1
- package/skills/pixerate-image-editor/SKILL.md +7 -0
- package/skills/pixerate-image-editor/references/provider-effects.md +35 -0
- package/skills/pixerate-video-editor/SKILL.md +39 -1
- package/dist/chunk-6CJRDJCU.cjs.map +0 -1
- package/dist/chunk-6ZBR4J62.js.map +0 -1
- package/dist/chunk-IPFRWILU.js.map +0 -1
- package/dist/chunk-RLRUAPBJ.cjs.map +0 -1
- package/dist/chunk-SMJGE7HM.cjs.map +0 -1
- package/dist/chunk-TVXFHE4T.js.map +0 -1
- /package/dist/{brushstrokeRender-CDDRSV2W.js.map → brushstrokeRender-LRV7GXGD.js.map} +0 -0
- /package/dist/{chunk-KMBC4HNV.js.map → chunk-J67OLQUQ.js.map} +0 -0
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CoverageMap,
|
|
3
|
+
PaintGradientSampler,
|
|
4
|
+
TIP_GRADIENT_STEPS,
|
|
3
5
|
applyStrokeToTile,
|
|
4
6
|
dabRect,
|
|
7
|
+
gradientDabColor,
|
|
8
|
+
hexToPaintRgb,
|
|
5
9
|
normalizeBrush,
|
|
6
|
-
rasterizeDab
|
|
10
|
+
rasterizeDab,
|
|
11
|
+
resolvePaintGradient,
|
|
12
|
+
scalePaintGradient
|
|
7
13
|
} from "./chunk-EM3FMJWE.js";
|
|
8
14
|
import {
|
|
9
15
|
BRUSHSTROKE_MAX_SCALE,
|
|
@@ -43,7 +49,9 @@ function pathStrokeDabs(samples, options) {
|
|
|
43
49
|
if (!pts.length) return [];
|
|
44
50
|
const tilt = options.tilt ?? {};
|
|
45
51
|
const minSize = Math.min(1, Math.max(0, options.minSize ?? 0));
|
|
46
|
-
|
|
52
|
+
let index = 0;
|
|
53
|
+
const t0 = pts.find((s) => typeof s.t === "number" && Number.isFinite(s.t))?.t ?? null;
|
|
54
|
+
const dab = (x, y, p, l, dir, travel = 0, time = null, speed = null) => {
|
|
47
55
|
const pr = Math.min(1, Math.max(0, p));
|
|
48
56
|
const lean0 = l?.amount ?? 0;
|
|
49
57
|
const size = brush.size * (brush.pressureSize ? minSize + (1 - minSize) * pr : 1) * (1 + 1.5 * (tilt.size ?? 0) * lean0);
|
|
@@ -52,7 +60,8 @@ function pathStrokeDabs(samples, options) {
|
|
|
52
60
|
if (tilt.followAzimuth && l) angle = -l.azimuth;
|
|
53
61
|
const roundness = Math.max(0.05, brush.roundness * (1 - 0.85 * (tilt.roundness ?? 0) * lean0));
|
|
54
62
|
const op = brush.pressureOpacity ? pr : 1;
|
|
55
|
-
|
|
63
|
+
const params = { travel, index: index++, pressure: pr, lean: lean0, speed, direction: dir, time: time === null || t0 === null ? null : time - t0 };
|
|
64
|
+
return { x, y, radius: Math.max(0.5, size / 2), alpha: brush.flow, opacity: brush.opacity * op, angle, roundness, params };
|
|
56
65
|
};
|
|
57
66
|
const out = [];
|
|
58
67
|
const pressure = (s) => typeof s.pressure === "number" && Number.isFinite(s.pressure) ? s.pressure : 1;
|
|
@@ -61,7 +70,9 @@ function pathStrokeDabs(samples, options) {
|
|
|
61
70
|
return out;
|
|
62
71
|
}
|
|
63
72
|
let carry = 0;
|
|
64
|
-
|
|
73
|
+
let along = 0;
|
|
74
|
+
const timeOf = (s) => typeof s.t === "number" && Number.isFinite(s.t) ? s.t : null;
|
|
75
|
+
out.push(dab(pts[0].x, pts[0].y, pressure(pts[0]), lean(pts[0]), Math.atan2(-(pts[1].y - pts[0].y), pts[1].x - pts[0].x), 0, timeOf(pts[0])));
|
|
65
76
|
for (let i = 0; i + 1 < pts.length; i++) {
|
|
66
77
|
const a = pts[i];
|
|
67
78
|
const b = pts[i + 1];
|
|
@@ -72,6 +83,9 @@ function pathStrokeDabs(samples, options) {
|
|
|
72
83
|
const dir = Math.atan2(-dy, dx);
|
|
73
84
|
const la = lean(a);
|
|
74
85
|
const lb = lean(b);
|
|
86
|
+
const ta = timeOf(a);
|
|
87
|
+
const tb = timeOf(b);
|
|
88
|
+
const speed = ta !== null && tb !== null && tb > ta ? len / (tb - ta) : null;
|
|
75
89
|
let t = carry;
|
|
76
90
|
for (; ; ) {
|
|
77
91
|
const f = t / len;
|
|
@@ -80,10 +94,11 @@ function pathStrokeDabs(samples, options) {
|
|
|
80
94
|
const l = la && lb ? { amount: la.amount + (lb.amount - la.amount) * f, azimuth: la.azimuth + (lb.azimuth - la.azimuth) * f } : la ?? lb;
|
|
81
95
|
const size = brush.size * (brush.pressureSize ? minSize + (1 - minSize) * p : 1);
|
|
82
96
|
const step = Math.max(0.5, size * brush.spacing);
|
|
83
|
-
if (t > 0 || i > 0) out.push(dab(a.x + dx * f, a.y + dy * f, p, l, dir));
|
|
97
|
+
if (t > 0 || i > 0) out.push(dab(a.x + dx * f, a.y + dy * f, p, l, dir, along + t, ta !== null && tb !== null ? ta + (tb - ta) * f : null, speed));
|
|
84
98
|
t += step;
|
|
85
99
|
}
|
|
86
100
|
carry = t - len;
|
|
101
|
+
along += len;
|
|
87
102
|
}
|
|
88
103
|
return out;
|
|
89
104
|
}
|
|
@@ -91,6 +106,10 @@ function pathStrokeCoverage(samples, options) {
|
|
|
91
106
|
const brush = normalizeBrush(options.brush);
|
|
92
107
|
const coverage = new CoverageMap();
|
|
93
108
|
const dabs = pathStrokeDabs(samples, options);
|
|
109
|
+
const g = options.gradient?.paint.type === "gradient" ? options.gradient : null;
|
|
110
|
+
const sampler = g ? new PaintGradientSampler(resolvePaintGradient(g.paint, g.colors ?? { paint: "#000000", secondary: "#ffffff" }), g.seed ?? 0) : null;
|
|
111
|
+
const tipTable = sampler?.acrossTip ? sampler.tipTable(TIP_GRADIENT_STEPS) : null;
|
|
112
|
+
const scratch = { field: new Float32Array(0), sample: { rgb: [0, 0, 0], alpha: 1 } };
|
|
94
113
|
let buf = new Float32Array(0);
|
|
95
114
|
let bounds = null;
|
|
96
115
|
for (const d of dabs) {
|
|
@@ -100,7 +119,10 @@ function pathStrokeCoverage(samples, options) {
|
|
|
100
119
|
if (buf.length < n) buf = new Float32Array(n);
|
|
101
120
|
const vals = buf.subarray(0, n);
|
|
102
121
|
rasterizeDab(d, brush.hardness, rect, vals);
|
|
103
|
-
|
|
122
|
+
if (sampler) {
|
|
123
|
+
const { color, field } = gradientDabColor(sampler, tipTable, d, d.params, rect, vals, scratch);
|
|
124
|
+
coverage.accumulate(rect, vals, d.opacity, color, field);
|
|
125
|
+
} else coverage.accumulate(rect, vals, d.opacity);
|
|
104
126
|
if (!bounds) bounds = { ...rect };
|
|
105
127
|
else {
|
|
106
128
|
const x = Math.min(bounds.x, rect.x);
|
|
@@ -113,12 +135,19 @@ function pathStrokeCoverage(samples, options) {
|
|
|
113
135
|
function paintPathStroke(surface, samples, options) {
|
|
114
136
|
if (surface.channels !== 4) throw new Error("paintPathStroke paints RGBA surfaces (4 channels).");
|
|
115
137
|
const brush = normalizeBrush(options.brush);
|
|
116
|
-
const
|
|
138
|
+
const hex = (c) => "#" + [0, 1, 2].map((i) => Math.round(Math.min(255, Math.max(0, c[i]))).toString(16).padStart(2, "0")).join("");
|
|
139
|
+
let color = options.color;
|
|
140
|
+
if (options.paint?.paint.type === "color") {
|
|
141
|
+
const rgb = hexToPaintRgb(options.paint.paint.color).map((v) => Math.round(v * 255));
|
|
142
|
+
color = [rgb[0], rgb[1], rgb[2], color[3]];
|
|
143
|
+
}
|
|
144
|
+
const gradient = options.paint?.paint.type === "gradient" ? { ...options.paint, colors: options.paint.colors ?? { paint: hex(color), secondary: "#ffffff" } } : null;
|
|
145
|
+
const { coverage, bounds, dabs } = pathStrokeCoverage(samples, { ...options, gradient });
|
|
117
146
|
if (!bounds) return { surface, bounds: null, dabs };
|
|
118
147
|
const offset = options.offset ?? { x: 0, y: 0 };
|
|
119
148
|
const apply = {
|
|
120
149
|
mode: options.mode ?? "paint",
|
|
121
|
-
color
|
|
150
|
+
color,
|
|
122
151
|
background: [0, 0, 0, 0],
|
|
123
152
|
opacity: brush.opacity,
|
|
124
153
|
blend: options.blend ?? brush.blend,
|
|
@@ -199,7 +228,9 @@ function renderBrushstroke(input, brush, options = {}) {
|
|
|
199
228
|
const strokeOptions = brushstrokeStrokeOptions(brush);
|
|
200
229
|
const samples = brushstrokeSamples(input);
|
|
201
230
|
for (const s of samples) if (!finite(s.x) || !finite(s.y)) throw new Error("Brushstroke samples need finite x and y.");
|
|
202
|
-
const
|
|
231
|
+
const solid = options.paint && !("type" in options.paint) ? options.paint.color : null;
|
|
232
|
+
const source = options.paint && "type" in options.paint ? options.paint : null;
|
|
233
|
+
const color = solid ?? options.color ?? [0, 0, 0, 255];
|
|
203
234
|
let scale = finite(options.scale) && options.scale > 0 ? Math.min(BRUSHSTROKE_MAX_SCALE, Math.max(BRUSHSTROKE_MIN_SCALE, options.scale)) : 1;
|
|
204
235
|
if (!samples.length) return { image: { width: 0, height: 0, data: new Uint8ClampedArray(0) }, bounds: null, scale, dabs: 0 };
|
|
205
236
|
const tip = strokeOptions.brush;
|
|
@@ -219,7 +250,9 @@ function renderBrushstroke(input, brush, options = {}) {
|
|
|
219
250
|
...strokeOptions,
|
|
220
251
|
brush: { ...tip, size: tip.size * scale },
|
|
221
252
|
closed: options.closed ?? strokeOptions.closed,
|
|
222
|
-
color
|
|
253
|
+
color,
|
|
254
|
+
// Gradient lengths are in path units: scale them with the image.
|
|
255
|
+
...source ? { paint: { paint: source.type === "gradient" ? scalePaintGradient(source, scale) : source, seed: options.seed ?? 0 } } : {}
|
|
223
256
|
});
|
|
224
257
|
if (!bounds) return { image: { width: 0, height: 0, data: new Uint8ClampedArray(0) }, bounds: null, scale, dabs };
|
|
225
258
|
const rect = bounds;
|
|
@@ -256,7 +289,7 @@ function hashString(s, h = 2166136261) {
|
|
|
256
289
|
function brushstrokeKey(input, brush, options = {}) {
|
|
257
290
|
const path = JSON.stringify(input);
|
|
258
291
|
const { brush: tip, ...rest } = brushstrokeStrokeOptions(brush);
|
|
259
|
-
const rest2 = JSON.stringify([tip, rest, options.paint
|
|
292
|
+
const rest2 = JSON.stringify([tip, rest, options.paint ?? options.color ?? null, options.seed ?? 0, options.closed ?? false, options.maxPixels ?? null]);
|
|
260
293
|
const scale = finite(options.scale) ? options.scale : 1;
|
|
261
294
|
return `${hashString(path).toString(16)}:${path.length}:${hashString(rest2).toString(16)}:${scale}`;
|
|
262
295
|
}
|
|
@@ -304,4 +337,4 @@ export {
|
|
|
304
337
|
brushstrokeKey,
|
|
305
338
|
BrushstrokeCache
|
|
306
339
|
};
|
|
307
|
-
//# sourceMappingURL=chunk-
|
|
340
|
+
//# sourceMappingURL=chunk-64PYNEHJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/paint/path-stroke.ts","../src/paint/brushstroke.ts"],"sourcesContent":["/**\n * A brush stroke along a path: polyline samples, each with an optional pressure and pen tilt, are\n * walked at the brush's spacing (pressure and tilt interpolated between samples) into dabs, built\n * up in a coverage map and composited onto a layer. This is what a vector editor calls to draw a\n * path with a natural-media brush (\"brushstrokes on vector paths\"): the path is the data, the\n * pixels are rendered from it.\n *\n * - Pressure scales size and / or opacity as the brush says.\n * - Tilt (`tiltX` / `tiltY` in degrees, or `altitude` / `azimuth` in radians, as Pointer Events give\n * them) widens the tip (`tilt.size`), flattens it along the lean (`tilt.roundness`), and with\n * `tilt.followAzimuth` turns the tip to the lean, as a pen laid on its side does.\n * - With `followPath` the tip turns with the path (a calligraphic nib along a curve).\n *\n * Deterministic and DOM-free: the same samples and brush give the same pixels everywhere.\n */\n\nimport { applyStrokeToTile, CoverageMap, dabRect, gradientDabColor, normalizeBrush, rasterizeDab, TIP_GRADIENT_STEPS, type BrushSettings, type Dab } from './brush';\nimport { hexToPaintRgb, PaintGradientSampler, resolvePaintGradient, type PaintGradientSample, type PaintSource } from './gradient-paint';\nimport { TiledSurface, finishTile, tileKey, tilesInRect, TILE, type RasterRect } from '../raster/surface';\nimport type { BlendMode } from '../raster/blend';\n\n/** One sample of the path (document pixels). */\nexport interface PathSample {\n x: number;\n y: number;\n /** 0..1 (default 1). */\n pressure?: number;\n /** Pointer Events tilt in degrees (−90..90). */\n tiltX?: number;\n tiltY?: number;\n /** Radians above the surface (π/2 upright); overrides tiltX / tiltY. */\n altitude?: number;\n /** Radians clockwise from +x: where the pen leans (with altitude). */\n azimuth?: number;\n /** Milliseconds (gradient paint mapped by speed or time reads it). */\n t?: number;\n}\n\nexport interface PathStrokeOptions {\n brush: Partial<BrushSettings>;\n /** How tilt shapes the tip (each 0..1; default none). */\n tilt?: { size?: number; roundness?: number; followAzimuth?: boolean };\n /** Turn the tip with the direction of the path (added to the brush angle). */\n followPath?: boolean;\n /** Size at zero pressure as a fraction of the full size, when pressure controls size (default 0). */\n minSize?: number;\n /** Join the last sample back to the first. */\n closed?: boolean;\n}\n\n/** A pen's lean from a sample: 0 upright .. 1 flat, and the direction it leans (radians, clockwise from +x). */\nfunction lean(s: PathSample): { amount: number; azimuth: number } | null {\n if (typeof s.altitude === 'number' && Number.isFinite(s.altitude)) {\n return { amount: 1 - Math.min(1, Math.max(0, s.altitude / (Math.PI / 2))), azimuth: s.azimuth ?? 0 };\n }\n if (typeof s.tiltX === 'number' || typeof s.tiltY === 'number') {\n const tx = ((s.tiltX ?? 0) * Math.PI) / 180;\n const ty = ((s.tiltY ?? 0) * Math.PI) / 180;\n if (tx === 0 && ty === 0) return { amount: 0, azimuth: 0 };\n const az = Math.atan2(Math.tan(ty), Math.tan(tx));\n const alt = Math.atan(1 / Math.hypot(Math.tan(tx), Math.tan(ty)));\n return { amount: 1 - alt / (Math.PI / 2), azimuth: az };\n }\n return null;\n}\n\n/** The dabs of a brush stroke along `samples`, with pressure and tilt interpolated between them. */\nexport function pathStrokeDabs(samples: readonly PathSample[], options: PathStrokeOptions): Dab[] {\n const brush = normalizeBrush(options.brush);\n const pts = options.closed && samples.length > 2 ? [...samples, samples[0]] : [...samples];\n if (!pts.length) return [];\n const tilt = options.tilt ?? {};\n const minSize = Math.min(1, Math.max(0, options.minSize ?? 0));\n let index = 0;\n const t0 = pts.find((s) => typeof s.t === 'number' && Number.isFinite(s.t))?.t ?? null;\n const dab = (x: number, y: number, p: number, l: { amount: number; azimuth: number } | null, dir: number, travel = 0, time: number | null = null, speed: number | null = null): Dab => {\n const pr = Math.min(1, Math.max(0, p));\n const lean0 = l?.amount ?? 0;\n const size = brush.size * (brush.pressureSize ? minSize + (1 - minSize) * pr : 1) * (1 + 1.5 * (tilt.size ?? 0) * lean0);\n let angle = (brush.angle * Math.PI) / 180;\n if (options.followPath) angle += dir;\n if (tilt.followAzimuth && l) angle = -l.azimuth;\n const roundness = Math.max(0.05, brush.roundness * (1 - 0.85 * (tilt.roundness ?? 0) * lean0));\n const op = brush.pressureOpacity ? pr : 1;\n const params = { travel, index: index++, pressure: pr, lean: lean0, speed, direction: dir, time: time === null || t0 === null ? null : time - t0 };\n return { x, y, radius: Math.max(0.5, size / 2), alpha: brush.flow, opacity: brush.opacity * op, angle, roundness, params };\n };\n const out: Dab[] = [];\n const pressure = (s: PathSample) => (typeof s.pressure === 'number' && Number.isFinite(s.pressure) ? s.pressure : 1);\n if (pts.length === 1) {\n out.push(dab(pts[0].x, pts[0].y, pressure(pts[0]), lean(pts[0]), 0));\n return out;\n }\n let carry = 0;\n let along = 0;\n const timeOf = (s: PathSample) => (typeof s.t === 'number' && Number.isFinite(s.t) ? s.t : null);\n out.push(dab(pts[0].x, pts[0].y, pressure(pts[0]), lean(pts[0]), Math.atan2(-(pts[1].y - pts[0].y), pts[1].x - pts[0].x), 0, timeOf(pts[0])));\n for (let i = 0; i + 1 < pts.length; i++) {\n const a = pts[i];\n const b = pts[i + 1];\n const dx = b.x - a.x;\n const dy = b.y - a.y;\n const len = Math.hypot(dx, dy);\n if (len === 0) continue;\n // Dab angles are counter-clockwise with y up, as the shared rasterizer reads them.\n const dir = Math.atan2(-dy, dx);\n const la = lean(a);\n const lb = lean(b);\n const ta = timeOf(a);\n const tb = timeOf(b);\n const speed = ta !== null && tb !== null && tb > ta ? len / (tb - ta) : null;\n let t = carry;\n for (;;) {\n const f = t / len;\n if (f > 1) break;\n const p = pressure(a) + (pressure(b) - pressure(a)) * f;\n const l = la && lb ? { amount: la.amount + (lb.amount - la.amount) * f, azimuth: la.azimuth + (lb.azimuth - la.azimuth) * f } : (la ?? lb);\n const size = brush.size * (brush.pressureSize ? minSize + (1 - minSize) * p : 1);\n const step = Math.max(0.5, size * brush.spacing);\n if (t > 0 || i > 0) out.push(dab(a.x + dx * f, a.y + dy * f, p, l, dir, along + t, ta !== null && tb !== null ? ta + (tb - ta) * f : null, speed));\n t += step;\n }\n carry = t - len;\n along += len;\n }\n return out;\n}\n\n/**\n * Gradient paint for a path stroke: a `PaintSource` gradient sampled dab by dab (by distance along\n * the path, pressure, tilt, speed, direction, time, across the tip or at random). `paint` /\n * `secondary` stops resolve to `colors` (default: the stroke colour and white).\n */\nexport interface PathStrokePaint {\n paint: PaintSource;\n /** Seed of the `random` mapping (default 0). */\n seed?: number;\n colors?: { paint: string; secondary: string };\n}\n\n/** The coverage of a brush stroke along `samples` (build-up towards each dab's opacity) and its bounds. */\nexport function pathStrokeCoverage(samples: readonly PathSample[], options: PathStrokeOptions & { gradient?: PathStrokePaint | null }): { coverage: CoverageMap; bounds: RasterRect | null; dabs: number } {\n const brush = normalizeBrush(options.brush);\n const coverage = new CoverageMap();\n const dabs = pathStrokeDabs(samples, options);\n const g = options.gradient?.paint.type === 'gradient' ? options.gradient : null;\n const sampler = g ? new PaintGradientSampler(resolvePaintGradient(g.paint as Extract<PaintSource, { type: 'gradient' }>, g.colors ?? { paint: '#000000', secondary: '#ffffff' }), g.seed ?? 0) : null;\n const tipTable = sampler?.acrossTip ? sampler.tipTable(TIP_GRADIENT_STEPS) : null;\n const scratch: { field: Float32Array; sample: PaintGradientSample } = { field: new Float32Array(0), sample: { rgb: [0, 0, 0], alpha: 1 } };\n let buf = new Float32Array(0);\n let bounds: RasterRect | null = null;\n for (const d of dabs) {\n const rect = dabRect(d, brush.hardness);\n const n = rect.width * rect.height;\n if (n <= 0) continue;\n if (buf.length < n) buf = new Float32Array(n);\n const vals = buf.subarray(0, n);\n rasterizeDab(d, brush.hardness, rect, vals);\n if (sampler) {\n const { color, field } = gradientDabColor(sampler, tipTable, d, d.params!, rect, vals, scratch);\n coverage.accumulate(rect, vals, d.opacity, color, field);\n } else coverage.accumulate(rect, vals, d.opacity);\n if (!bounds) bounds = { ...rect };\n else {\n const x = Math.min(bounds.x, rect.x);\n const y = Math.min(bounds.y, rect.y);\n bounds = { x, y, width: Math.max(bounds.x + bounds.width, rect.x + rect.width) - x, height: Math.max(bounds.y + bounds.height, rect.y + rect.height) - y };\n }\n }\n return { coverage, bounds, dabs: dabs.length };\n}\n\nexport interface PaintPathOptions extends PathStrokeOptions {\n /** RGBA 0..255 of the paint (with gradient paint: its alpha, and the colour of `paint` stops). */\n color: readonly [number, number, number, number];\n /** Gradient paint (a `PaintSource`); a colour source replaces `color`'s RGB. */\n paint?: PathStrokePaint | null;\n /** `erase` removes paint instead (default `paint`). */\n mode?: 'paint' | 'erase';\n /** Blend mode (default the brush's). */\n blend?: BlendMode;\n /** Layer offset: layer pixel = document pixel − offset (default 0, 0). */\n offset?: { x: number; y: number };\n lockTransparency?: boolean;\n /** Where paint may land (canvas-sized coverage 0..255), or none. */\n selection?: { width: number; height: number; data: ArrayLike<number> } | null;\n}\n\n/**\n * Paints a brush stroke along `samples` onto an RGBA layer surface and returns the new surface\n * (the input is not changed; untouched tiles are shared) with the bounds it touched.\n */\nexport function paintPathStroke(surface: TiledSurface, samples: readonly PathSample[], options: PaintPathOptions): { surface: TiledSurface; bounds: RasterRect | null; dabs: number } {\n if (surface.channels !== 4) throw new Error('paintPathStroke paints RGBA surfaces (4 channels).');\n const brush = normalizeBrush(options.brush);\n const hex = (c: readonly number[]) => '#' + [0, 1, 2].map((i) => Math.round(Math.min(255, Math.max(0, c[i]))).toString(16).padStart(2, '0')).join('');\n let color = options.color;\n if (options.paint?.paint.type === 'color') {\n const rgb = hexToPaintRgb(options.paint.paint.color).map((v) => Math.round(v * 255));\n color = [rgb[0], rgb[1], rgb[2], color[3]];\n }\n const gradient = options.paint?.paint.type === 'gradient' ? { ...options.paint, colors: options.paint.colors ?? { paint: hex(color), secondary: '#ffffff' } } : null;\n const { coverage, bounds, dabs } = pathStrokeCoverage(samples, { ...options, gradient });\n if (!bounds) return { surface, bounds: null, dabs };\n const offset = options.offset ?? { x: 0, y: 0 };\n const apply = {\n mode: options.mode ?? 'paint',\n color,\n background: [0, 0, 0, 0] as const,\n opacity: brush.opacity,\n blend: options.blend ?? brush.blend,\n lockTransparency: options.lockTransparency ?? false,\n selection: options.selection ?? null,\n offset,\n };\n const tiles = new Map(surface.tiles);\n for (const { tx, ty } of tilesInRect({ x: bounds.x - offset.x, y: bounds.y - offset.y, width: bounds.width, height: bounds.height })) {\n const key = tileKey(tx, ty);\n const src = surface.tiles.get(key)?.data;\n const base = src && src.length === TILE * TILE * 4 ? src : new Uint8ClampedArray(TILE * TILE * 4);\n const t = finishTile(applyStrokeToTile(coverage, base, 4, tx, ty, apply), 4, surface.defaultValue);\n if (t) tiles.set(key, t);\n else tiles.delete(key);\n }\n return { surface: new TiledSurface(4, tiles, surface.defaultValue), bounds, dabs };\n}\n","/**\n * A brushstroke rendered on its own: a path (polyline samples with pressure, tilt and time, or\n * points plus pressure / tilt profiles along the length) and a dry brush in, an RGBA image and its\n * bounds out. The canvas renders brushed freehand strokes with it, and the vector editor\n * brushstrokes on vector paths; both keep the path as the data and the pixels as a cache.\n *\n * - Built on {@link paintPathStroke}: the same dabs, coverage and build-up as everywhere else.\n * - Rendered at `scale` image pixels per path unit (both the samples and the brush size scale), so\n * a stroke looks the same at any zoom with more or fewer pixels. Snap zooms to\n * {@link brushstrokeScaleBucket} so panning and small zoom changes reuse the same image.\n * - Dry media only. A wet brush needs an area to flow in (the paint editor's wet layer); mixer,\n * blender and smudge brushes need paint under them. Those are refused with an error.\n * - Deterministic and DOM-free: the image is plain RGBA bytes. Turn it into a PNG data URL with\n * {@link brushstrokeDataUrl}, or into a canvas through an injected factory\n * ({@link brushstrokeToCanvas}).\n */\n\nimport { normalizeBrush, type BrushSettings } from './brush';\nimport { paintPathStroke, type PathSample, type PathStrokeOptions } from './path-stroke';\nimport { scalePaintGradient, type PaintSource } from './gradient-paint';\nimport { emptySurface, readRegion, type RasterRect } from '../raster/surface';\nimport { pngDataUrl } from '../raster/png';\nimport { BRUSHSTROKE_MAX_SCALE as MAX_SCALE, BRUSHSTROKE_MIN_SCALE as MIN_SCALE } from './brushstroke-scale';\n\nexport { brushstrokeScaleBucket, BRUSHSTROKE_MAX_SCALE, BRUSHSTROKE_MIN_SCALE } from './brushstroke-scale';\n\n/** One sample of a brushstroke: a {@link PathSample} plus an optional time stamp (milliseconds). */\nexport interface BrushstrokeSample extends PathSample {\n t?: number;\n}\n\n/**\n * A path plus pen profiles along it. Each profile is a list of values spread evenly over the\n * path's length (first at the start, last at the end) and interpolated in between; one value is\n * constant. Missing profiles leave the samples upright at full pressure.\n */\nexport interface BrushstrokeProfilePath {\n points: ReadonlyArray<{ x: number; y: number }>;\n /** Pressure 0..1 along the length. */\n pressure?: readonly number[];\n /** Pointer Events tilt in degrees (−90..90) along the length. */\n tiltX?: readonly number[];\n tiltY?: readonly number[];\n}\n\nexport type BrushstrokeInput = readonly BrushstrokeSample[] | BrushstrokeProfilePath;\n\n/**\n * A brush for a brushstroke: plain tip settings, or a paint-editor brush (`PaintBrush` matches this\n * shape structurally). Only dab-engine brushes render; `rotation: 'stroke'` turns the tip with the\n * path, and `dynamics` give the size at zero pressure and how tilt shapes the tip. Grain, jitter and\n * velocity dynamics are not applied (there is no paper, and the result must be deterministic).\n */\nexport type BrushstrokeBrush =\n | Partial<BrushSettings>\n | {\n id?: string;\n name?: string;\n engine?: string;\n tip: Partial<BrushSettings>;\n rotation?: string;\n dynamics?: { minSize?: number; tiltSize?: number; tiltRoundness?: number };\n };\n\n/**\n * What the stroke is painted with: a solid RGBA colour, or a `PaintSource` (one `#rrggbb` colour,\n * or gradient paint sampled dab by dab along the path: by distance, pressure, tilt, speed,\n * direction, time, across the tip or at random).\n */\nexport type BrushstrokePaint = { color: readonly [number, number, number, number] } | PaintSource;\n\nexport interface BrushstrokeOptions {\n /** Image pixels per path unit (default 1). Use {@link brushstrokeScaleBucket} for a zoom. */\n scale?: number;\n /** RGBA 0..255 (default opaque black). Shorthand for `paint: { color }`. */\n color?: readonly [number, number, number, number];\n /** What to paint with (wins over `color`; with gradient paint, `color` fills `paint` stops and gives the alpha). */\n paint?: BrushstrokePaint;\n /** Seed of gradient paint's `random` mapping (default 0). */\n seed?: number;\n /** Join the last sample back to the first. */\n closed?: boolean;\n /**\n * The largest image to render, in pixels (default 16 777 216, 4096²). A stroke that would be\n * bigger at `scale` renders at a lower scale instead; the result's `scale` says which.\n */\n maxPixels?: number;\n}\n\n/** A rendered brushstroke. */\nexport interface BrushstrokeImage {\n /** Straight-alpha RGBA bytes, `width × height × 4` (the same layout as `ImageData`). */\n image: { width: number; height: number; data: Uint8ClampedArray };\n /** Where the image goes, in path units: draw `image` stretched over this rectangle. Null for an empty stroke. */\n bounds: { x: number; y: number; width: number; height: number } | null;\n /** Image pixels per path unit it was rendered at. */\n scale: number;\n /** How many dabs the stroke laid down. */\n dabs: number;\n}\n\nconst DEFAULT_MAX_PIXELS = 4096 * 4096;\n\nconst isProfilePath = (input: BrushstrokeInput): input is BrushstrokeProfilePath => !Array.isArray(input) && typeof input === 'object' && input !== null && 'points' in input;\n\n/** A profile's value at `f` (0..1 along the length), or undefined without one. */\nfunction profileAt(profile: readonly number[] | undefined, f: number): number | undefined {\n if (!profile || !profile.length) return undefined;\n if (profile.length === 1) return profile[0];\n const u = Math.min(1, Math.max(0, f)) * (profile.length - 1);\n const i = Math.min(profile.length - 2, Math.floor(u));\n return profile[i] + (profile[i + 1] - profile[i]) * (u - i);\n}\n\n/** The samples of a brushstroke input: profile paths are sampled at their points by arc length. */\nexport function brushstrokeSamples(input: BrushstrokeInput): BrushstrokeSample[] {\n if (!isProfilePath(input)) return input.map((s) => ({ ...s }));\n const pts = input.points;\n const lengths = [0];\n for (let i = 1; i < pts.length; i++) lengths.push(lengths[i - 1] + Math.hypot(pts[i].x - pts[i - 1].x, pts[i].y - pts[i - 1].y));\n const total = lengths.at(-1) ?? 0;\n return pts.map((p, i) => {\n const f = total > 0 ? lengths[i] / total : pts.length > 1 ? i / (pts.length - 1) : 0;\n const s: BrushstrokeSample = { x: p.x, y: p.y };\n const pressure = profileAt(input.pressure, f);\n const tiltX = profileAt(input.tiltX, f);\n const tiltY = profileAt(input.tiltY, f);\n if (pressure !== undefined) s.pressure = pressure;\n if (tiltX !== undefined) s.tiltX = tiltX;\n if (tiltY !== undefined) s.tiltY = tiltY;\n return s;\n });\n}\n\n/** Engines that cannot render a brushstroke on its own, and why. */\nconst REFUSED_ENGINES: Record<string, string> = {\n wet: 'is a wet brush, and wet paint needs an area to flow in (paint it on a wet layer in the paint editor)',\n mixer: 'is a Mixer Brush, which picks up paint from under the tip and has none on a standalone stroke',\n blender: 'is a blender, which only moves paint that is already there',\n smudge: 'is a smudge brush, which only moves paint that is already there',\n eraser: 'is an eraser, which has nothing to remove on a standalone stroke',\n};\n\n/**\n * The tip settings and path-stroke options of a brushstroke brush. Throws for brushes that are not\n * dry dab brushes (wet, mixer, blender, smudge, eraser, impasto and the procedural engines).\n */\nexport function brushstrokeStrokeOptions(brush: BrushstrokeBrush): PathStrokeOptions {\n if (!brush || typeof brush !== 'object') throw new Error('A brushstroke needs a brush: tip settings ({ size, hardness, … }) or a paint brush preset.');\n if (!('tip' in brush) || !brush.tip) return { brush: normalizeBrush(brush as Partial<BrushSettings>) };\n const engine = brush.engine ?? 'dab';\n if (engine !== 'dab') {\n const label = brush.name ?? brush.id ?? 'This brush';\n const why = REFUSED_ENGINES[engine] ?? `uses the '${engine}' engine, which renderBrushstroke does not draw`;\n throw new Error(`Brushstrokes render dry media only: '${label}' ${why}. Pick a dry brush (a dab-engine preset such as a pencil, pastel, charcoal, marker, ink or airbrush) or plain tip settings.`);\n }\n const d = brush.dynamics ?? {};\n const tilt = { size: d.tiltSize ?? 0, roundness: d.tiltRoundness ?? 0 };\n return {\n brush: normalizeBrush(brush.tip),\n followPath: brush.rotation === 'stroke',\n minSize: d.minSize ?? 0,\n ...(tilt.size || tilt.roundness ? { tilt } : {}),\n };\n}\n\n/** Whether a brush renders as a brushstroke (a dry dab brush, or plain tip settings). */\nexport function isBrushstrokeBrush(brush: BrushstrokeBrush): boolean {\n return !('tip' in brush) || !brush.tip || (brush.engine ?? 'dab') === 'dab';\n}\n\nconst finite = (v: unknown): v is number => typeof v === 'number' && Number.isFinite(v);\n\n/**\n * Renders a dry brushstroke along `input` with `brush` to an RGBA image. The same input, brush and\n * options always give the same bytes. Throws for a wet (or other non-dry) brush and for samples\n * that are not finite.\n */\nexport function renderBrushstroke(input: BrushstrokeInput, brush: BrushstrokeBrush, options: BrushstrokeOptions = {}): BrushstrokeImage {\n const strokeOptions = brushstrokeStrokeOptions(brush);\n const samples = brushstrokeSamples(input);\n for (const s of samples) if (!finite(s.x) || !finite(s.y)) throw new Error('Brushstroke samples need finite x and y.');\n const solid = options.paint && !('type' in options.paint) ? options.paint.color : null;\n const source = options.paint && 'type' in options.paint ? options.paint : null;\n const color = solid ?? options.color ?? [0, 0, 0, 255];\n let scale = finite(options.scale) && options.scale > 0 ? Math.min(MAX_SCALE, Math.max(MIN_SCALE, options.scale)) : 1;\n if (!samples.length) return { image: { width: 0, height: 0, data: new Uint8ClampedArray(0) }, bounds: null, scale, dabs: 0 };\n\n // Keep the image under maxPixels: estimate it from the samples and the widest the tip can get.\n const tip = strokeOptions.brush as BrushSettings;\n const reach = tip.size * (1 + 1.5 * (strokeOptions.tilt?.size ?? 0)) + 4;\n let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;\n for (const s of samples) {\n x0 = Math.min(x0, s.x); y0 = Math.min(y0, s.y); x1 = Math.max(x1, s.x); y1 = Math.max(y1, s.y);\n }\n const maxPixels = finite(options.maxPixels) && options.maxPixels > 0 ? options.maxPixels : DEFAULT_MAX_PIXELS;\n const area = (x1 - x0 + reach) * (y1 - y0 + reach);\n if (area * scale * scale > maxPixels) scale = Math.max(MIN_SCALE, Math.sqrt(maxPixels / area));\n\n const scaled = samples.map((s) => ({ ...s, x: s.x * scale, y: s.y * scale }));\n const { surface, bounds, dabs } = paintPathStroke(emptySurface(4), scaled, {\n ...strokeOptions,\n brush: { ...tip, size: tip.size * scale },\n closed: options.closed ?? strokeOptions.closed,\n color,\n // Gradient lengths are in path units: scale them with the image.\n ...(source ? { paint: { paint: source.type === 'gradient' ? scalePaintGradient(source, scale) : source, seed: options.seed ?? 0 } } : {}),\n });\n if (!bounds) return { image: { width: 0, height: 0, data: new Uint8ClampedArray(0) }, bounds: null, scale, dabs };\n const rect: RasterRect = bounds;\n const data = readRegion(surface, rect);\n return {\n image: { width: rect.width, height: rect.height, data },\n bounds: { x: rect.x / scale, y: rect.y / scale, width: rect.width / scale, height: rect.height / scale },\n scale,\n dabs,\n };\n}\n\n/** A rendered brushstroke as a PNG data URL (for an SVG `<image>`, an `<img>` or an export). Empty strokes give ''. */\nexport function brushstrokeDataUrl(stroke: BrushstrokeImage): string {\n const { width, height, data } = stroke.image;\n return width && height ? pngDataUrl(width, height, data) : '';\n}\n\n/** The 2D-canvas surface {@link brushstrokeToCanvas} draws on (an `HTMLCanvasElement` or `OffscreenCanvas`). */\nexport interface BrushstrokeCanvasLike {\n getContext(type: '2d'): { createImageData(w: number, h: number): { data: Uint8ClampedArray }; putImageData(img: never, x: number, y: number): void } | null;\n}\n\n/**\n * A rendered brushstroke on a canvas made by `createCanvas(width, height)` (injected, so this\n * module stays DOM-free). Returns null for an empty stroke or a canvas without a 2D context.\n */\nexport function brushstrokeToCanvas<C extends BrushstrokeCanvasLike>(stroke: BrushstrokeImage, createCanvas: (width: number, height: number) => C): C | null {\n const { width, height, data } = stroke.image;\n if (!width || !height) return null;\n const canvas = createCanvas(width, height);\n const ctx = canvas.getContext('2d');\n if (!ctx) return null;\n const img = ctx.createImageData(width, height);\n img.data.set(data);\n ctx.putImageData(img as never, 0, 0);\n return canvas;\n}\n\n// ---------------------------------------------------------------------------\n// Cache\n// ---------------------------------------------------------------------------\n\n/** FNV-1a over a string, as 8 hex digits. */\nfunction hashString(s: string, h = 0x811c9dc5): number {\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i);\n h = Math.imul(h, 0x01000193) >>> 0;\n }\n return h;\n}\n\n/**\n * A key for a brushstroke: the path (samples or profiles), the brush, the colour and the scale.\n * Two calls with equal arguments give equal keys; any change to them changes the key.\n */\nexport function brushstrokeKey(input: BrushstrokeInput, brush: BrushstrokeBrush, options: BrushstrokeOptions = {}): string {\n const path = JSON.stringify(input);\n const { brush: tip, ...rest } = brushstrokeStrokeOptions(brush);\n const rest2 = JSON.stringify([tip, rest, options.paint ?? options.color ?? null, options.seed ?? 0, options.closed ?? false, options.maxPixels ?? null]);\n const scale = finite(options.scale) ? options.scale : 1;\n return `${hashString(path).toString(16)}:${path.length}:${hashString(rest2).toString(16)}:${scale}`;\n}\n\n/**\n * A small least-recently-used cache of rendered brushstrokes keyed by {@link brushstrokeKey}: path,\n * brush, colour and scale. Pass a bucketed scale ({@link brushstrokeScaleBucket}) so zooming inside\n * a bucket hits the cache, and a changed path, brush or bucket re-renders.\n */\nexport class BrushstrokeCache {\n private readonly entries = new Map<string, BrushstrokeImage>();\n /** Renders that missed the cache (for tests and stats). */\n misses = 0;\n\n constructor(readonly capacity = 64) {}\n\n get size(): number {\n return this.entries.size;\n }\n\n /** The cached stroke for these arguments, rendering (and caching) it on a miss. */\n render(input: BrushstrokeInput, brush: BrushstrokeBrush, options: BrushstrokeOptions = {}): BrushstrokeImage {\n const key = brushstrokeKey(input, brush, options);\n const hit = this.entries.get(key);\n if (hit) {\n this.entries.delete(key);\n this.entries.set(key, hit);\n return hit;\n }\n this.misses++;\n const out = renderBrushstroke(input, brush, options);\n this.entries.set(key, out);\n while (this.entries.size > Math.max(1, this.capacity)) this.entries.delete(this.entries.keys().next().value as string);\n return out;\n }\n\n clear(): void {\n this.entries.clear();\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -74,7 +74,7 @@ function profileAlongPolyline(points, values, count = 32) {
|
|
|
74
74
|
var round3 = (v) => Math.round(v * 1e3) / 1e3;
|
|
75
75
|
var loading = null;
|
|
76
76
|
function loadCanvasBrushstrokeRenderer() {
|
|
77
|
-
loading ??= Promise.resolve().then(() => _interopRequireWildcard(require("./brushstrokeRender-
|
|
77
|
+
loading ??= Promise.resolve().then(() => _interopRequireWildcard(require("./brushstrokeRender-EELTV7U4.cjs"))).catch((error) => {
|
|
78
78
|
loading = null;
|
|
79
79
|
throw error;
|
|
80
80
|
});
|
|
@@ -105,4 +105,4 @@ async function getBrushedShapeSvg(shape, size, options = {}) {
|
|
|
105
105
|
|
|
106
106
|
|
|
107
107
|
exports.isBrushedShape = isBrushedShape; exports.pathDataToPolyline = pathDataToPolyline; exports.profileAlongPolyline = profileAlongPolyline; exports.loadCanvasBrushstrokeRenderer = loadCanvasBrushstrokeRenderer; exports.listCanvasBrushes = listCanvasBrushes; exports.validateCanvasBrush = validateCanvasBrush; exports.renderShapeBrushstroke = renderShapeBrushstroke; exports.getBrushedShapeSvg = getBrushedShapeSvg;
|
|
108
|
-
//# sourceMappingURL=chunk-
|
|
108
|
+
//# sourceMappingURL=chunk-7LQO6OHH.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/chunk-2IW3BWRT.cjs","../src/canvas/brushstroke.ts"],"names":[],"mappings":"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","file":"/home/runner/work/editor/editor/packages/core/dist/chunk-2IW3BWRT.cjs","sourcesContent":[null,"/**\n * Brushed freehand strokes: a path shape with a `brush` keeps its path (selection, editing, eraser\n * hit-testing, grouping and export work as for any path) and is drawn with the paint engine's\n * dry brushstroke renderer instead of an SVG stroke. The renderer, the paint presets and the colour\n * parser live in a separate chunk, loaded the first time a brushed stroke is drawn, so canvases\n * without brushes never load the paint engine.\n */\nimport { brushstrokeScaleBucket } from '../paint/brushstroke-scale.js';\nimport { parseSvgPath, type PathShape, type ShapeDescription, type ShapeSize } from './shapeGeometry.js';\nimport type { Point } from './types.js';\nimport type { CanvasBrush, CanvasBrushPreset, CanvasBrushstroke } from './brushTypes.js';\n\nexport type { CanvasBrush, CanvasBrushPreset, CanvasBrushSettings, CanvasBrushstroke } from './brushTypes.js';\n\nexport { brushstrokeScaleBucket };\n\n/** A brushed stroke's image, placed in the path's coordinates (the shape's view box). */\nexport interface CanvasBrushstrokeImage {\n\t/** PNG data URL. */\n\thref: string;\n\tx: number;\n\ty: number;\n\twidth: number;\n\theight: number;\n\t/** Image pixels per path unit. */\n\tscale: number;\n}\n\n/** A path shape with a brush. */\nexport type BrushedPathShape = PathShape & { brush: CanvasBrushstroke };\n\n/** Whether a shape is a brushed stroke. */\nexport function isBrushedShape(shape: ShapeDescription | null | undefined): shape is BrushedPathShape {\n\treturn !!shape && shape.kind === 'path' && !!shape.brush && typeof shape.brush === 'object' && shape.brush.brush != null;\n}\n\n/**\n * The open polyline of a path's first subpath, in path coordinates: lines kept, curves sampled\n * every `step` units or so. Brushed strokes are drawn along it.\n */\nexport function pathDataToPolyline(d: string, step = 2): Point[] {\n\tconst out: Point[] = [];\n\tlet x = 0;\n\tlet y = 0;\n\tlet started = false;\n\tconst push = (px: number, py: number) => {\n\t\tconst last = out[out.length - 1];\n\t\tif (!last || last[0] !== px || last[1] !== py) out.push([px, py]);\n\t};\n\tfor (const c of parseSvgPath(d)) {\n\t\tif (c.type === 'M') {\n\t\t\tif (started) break;\n\t\t\tstarted = true;\n\t\t\tx = c.x;\n\t\t\ty = c.y;\n\t\t\tpush(x, y);\n\t\t} else if (c.type === 'Z') {\n\t\t\tif (out.length) push(out[0][0], out[0][1]);\n\t\t\tbreak;\n\t\t} else if (c.type === 'C' || c.type === 'Q') {\n\t\t\tconst pts = c.type === 'C' ? [[x, y], [c.x1, c.y1], [c.x2, c.y2], [c.x, c.y]] : [[x, y], [c.x1, c.y1], [c.x, c.y]];\n\t\t\tlet hull = 0;\n\t\t\tfor (let i = 1; i < pts.length; i++) hull += Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]);\n\t\t\tconst n = Math.max(1, Math.min(256, Math.ceil(hull / Math.max(0.25, step))));\n\t\t\tfor (let k = 1; k <= n; k++) {\n\t\t\t\tconst t = k / n;\n\t\t\t\tconst u = 1 - t;\n\t\t\t\tif (c.type === 'C') {\n\t\t\t\t\tpush(\n\t\t\t\t\t\tu * u * u * x + 3 * u * u * t * c.x1 + 3 * u * t * t * c.x2 + t * t * t * c.x,\n\t\t\t\t\t\tu * u * u * y + 3 * u * u * t * c.y1 + 3 * u * t * t * c.y2 + t * t * t * c.y\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tpush(u * u * x + 2 * u * t * c.x1 + t * t * c.x, u * u * y + 2 * u * t * c.y1 + t * t * c.y);\n\t\t\t\t}\n\t\t\t}\n\t\t\tx = c.x;\n\t\t\ty = c.y;\n\t\t} else {\n\t\t\t// Lines, and arcs as their chords (freehand paths have none).\n\t\t\tx = c.x;\n\t\t\ty = c.y;\n\t\t\tpush(x, y);\n\t\t}\n\t}\n\treturn out;\n}\n\n/**\n * Resamples per-point values into `count` values spread evenly along the polyline's length (the\n * profile a brushed stroke stores). Values are rounded to three decimals.\n */\nexport function profileAlongPolyline(points: readonly Point[], values: readonly number[], count = 32): number[] {\n\tconst n = Math.min(points.length, values.length);\n\tif (n === 0) return [];\n\tif (n === 1) return [round3(values[0])];\n\tconst lengths = [0];\n\tfor (let i = 1; i < n; i++) lengths.push(lengths[i - 1] + Math.hypot(points[i][0] - points[i - 1][0], points[i][1] - points[i - 1][1]));\n\tconst total = lengths[n - 1];\n\tconst m = Math.max(2, Math.min(count, n));\n\tconst out: number[] = [];\n\tlet j = 0;\n\tfor (let k = 0; k < m; k++) {\n\t\tconst at = total > 0 ? (total * k) / (m - 1) : 0;\n\t\twhile (j < n - 2 && lengths[j + 1] < at) j++;\n\t\tconst span = lengths[j + 1] - lengths[j];\n\t\tconst f = span > 0 ? Math.min(1, Math.max(0, (at - lengths[j]) / span)) : 0;\n\t\tout.push(round3(values[j] + (values[j + 1] - values[j]) * f));\n\t}\n\treturn out;\n}\n\nconst round3 = (v: number) => Math.round(v * 1000) / 1000;\n\n// ---------------------------------------------------------------------------\n// The lazily loaded renderer\n// ---------------------------------------------------------------------------\n\n/** The renderer chunk (`./brushstrokeRender.ts`). */\nexport interface CanvasBrushstrokeRenderer {\n\tlistCanvasBrushes(): CanvasBrushPreset[];\n\t/** Throws for an unknown or non-dry brush; the result is the paint engine's brush. */\n\tresolveCanvasBrush(brush: CanvasBrush, width: number): unknown;\n\trenderShapeBrushstrokeNow(shape: BrushedPathShape, scale: number): CanvasBrushstrokeImage | null;\n\tbrushedShapeSvgNow(shape: BrushedPathShape, size: ShapeSize, scale: number): string;\n}\n\nlet loading: Promise<CanvasBrushstrokeRenderer> | null = null;\n\n/**\n * Loads the brushstroke renderer (the paint engine's dry brushes, the paint presets and a CSS\n * colour parser). Called for you by the functions below; call it early to warm up.\n */\nexport function loadCanvasBrushstrokeRenderer(): Promise<CanvasBrushstrokeRenderer> {\n\tloading ??= (import('./brushstrokeRender.js') as Promise<CanvasBrushstrokeRenderer>).catch((error) => {\n\t\tloading = null;\n\t\tthrow error;\n\t});\n\treturn loading;\n}\n\n/** The dry paint presets a freehand stroke can use (wet, mixer, blender, smudge and eraser presets are left out). */\nexport async function listCanvasBrushes(): Promise<CanvasBrushPreset[]> {\n\treturn (await loadCanvasBrushstrokeRenderer()).listCanvasBrushes();\n}\n\n/**\n * Checks a brush: an unknown preset id, a wet (or other non-dry) preset, or settings that are not\n * an object throw an error saying which presets can be used.\n */\nexport async function validateCanvasBrush(brush: CanvasBrush): Promise<void> {\n\t(await loadCanvasBrushstrokeRenderer()).resolveCanvasBrush(brush, 1);\n}\n\nexport interface RenderShapeBrushstrokeOptions {\n\t/** Image pixels per path unit; pass `brushstrokeScaleBucket(zoom × node scale, devicePixelRatio)` (default 1). */\n\tscale?: number;\n}\n\n/**\n * Renders a brushed path shape to a cached PNG placed in path coordinates, or null for a shape\n * without a brush or an empty path. Throws for an unknown or non-dry brush.\n */\nexport async function renderShapeBrushstroke(shape: ShapeDescription, options: RenderShapeBrushstrokeOptions = {}): Promise<CanvasBrushstrokeImage | null> {\n\tif (!isBrushedShape(shape)) return null;\n\treturn (await loadCanvasBrushstrokeRenderer()).renderShapeBrushstrokeNow(shape, options.scale ?? 1);\n}\n\n/**\n * The brushed stroke as a standalone SVG document of `size` (the node box), with the image\n * embedded as a PNG data URL: what an SVG export writes for the node. Shapes without a brush give\n * null (export them as paths).\n */\nexport async function getBrushedShapeSvg(shape: ShapeDescription, size: ShapeSize, options: RenderShapeBrushstrokeOptions = {}): Promise<string | null> {\n\tif (!isBrushedShape(shape)) return null;\n\treturn (await loadCanvasBrushstrokeRenderer()).brushedShapeSvgNow(shape, size, options.scale ?? 2);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/chunk-7LQO6OHH.cjs","../src/canvas/brushstroke.ts"],"names":[],"mappings":"AAAA;AACE;AACF,wDAA6B;AAC7B;AACA;AC4BO,SAAS,cAAA,CAAe,KAAA,EAAuE;AACrG,EAAA,OAAO,CAAC,CAAC,MAAA,GAAS,KAAA,CAAM,KAAA,IAAS,OAAA,GAAU,CAAC,CAAC,KAAA,CAAM,MAAA,GAAS,OAAO,KAAA,CAAM,MAAA,IAAU,SAAA,GAAY,KAAA,CAAM,KAAA,CAAM,MAAA,GAAS,IAAA;AACrH;AAMO,SAAS,kBAAA,CAAmB,CAAA,EAAW,KAAA,EAAO,CAAA,EAAY;AAChE,EAAA,MAAM,IAAA,EAAe,CAAC,CAAA;AACtB,EAAA,IAAI,EAAA,EAAI,CAAA;AACR,EAAA,IAAI,EAAA,EAAI,CAAA;AACR,EAAA,IAAI,QAAA,EAAU,KAAA;AACd,EAAA,MAAM,KAAA,EAAO,CAAC,EAAA,EAAY,EAAA,EAAA,GAAe;AACxC,IAAA,MAAM,KAAA,EAAO,GAAA,CAAI,GAAA,CAAI,OAAA,EAAS,CAAC,CAAA;AAC/B,IAAA,GAAA,CAAI,CAAC,KAAA,GAAQ,IAAA,CAAK,CAAC,EAAA,IAAM,GAAA,GAAM,IAAA,CAAK,CAAC,EAAA,IAAM,EAAA,EAAI,GAAA,CAAI,IAAA,CAAK,CAAC,EAAA,EAAI,EAAE,CAAC,CAAA;AAAA,EACjE,CAAA;AACA,EAAA,IAAA,CAAA,MAAW,EAAA,GAAK,4CAAA,CAAc,CAAA,EAAG;AAChC,IAAA,GAAA,CAAI,CAAA,CAAE,KAAA,IAAS,GAAA,EAAK;AACnB,MAAA,GAAA,CAAI,OAAA,EAAS,KAAA;AACb,MAAA,QAAA,EAAU,IAAA;AACV,MAAA,EAAA,EAAI,CAAA,CAAE,CAAA;AACN,MAAA,EAAA,EAAI,CAAA,CAAE,CAAA;AACN,MAAA,IAAA,CAAK,CAAA,EAAG,CAAC,CAAA;AAAA,IACV,EAAA,KAAA,GAAA,CAAW,CAAA,CAAE,KAAA,IAAS,GAAA,EAAK;AAC1B,MAAA,GAAA,CAAI,GAAA,CAAI,MAAA,EAAQ,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA,CAAE,CAAC,CAAA,EAAG,GAAA,CAAI,CAAC,CAAA,CAAE,CAAC,CAAC,CAAA;AACzC,MAAA,KAAA;AAAA,IACD,EAAA,KAAA,GAAA,CAAW,CAAA,CAAE,KAAA,IAAS,IAAA,GAAO,CAAA,CAAE,KAAA,IAAS,GAAA,EAAK;AAC5C,MAAA,MAAM,IAAA,EAAM,CAAA,CAAE,KAAA,IAAS,IAAA,EAAM,CAAC,CAAC,CAAA,EAAG,CAAC,CAAA,EAAG,CAAC,CAAA,CAAE,EAAA,EAAI,CAAA,CAAE,EAAE,CAAA,EAAG,CAAC,CAAA,CAAE,EAAA,EAAI,CAAA,CAAE,EAAE,CAAA,EAAG,CAAC,CAAA,CAAE,CAAA,EAAG,CAAA,CAAE,CAAC,CAAC,EAAA,EAAI,CAAC,CAAC,CAAA,EAAG,CAAC,CAAA,EAAG,CAAC,CAAA,CAAE,EAAA,EAAI,CAAA,CAAE,EAAE,CAAA,EAAG,CAAC,CAAA,CAAE,CAAA,EAAG,CAAA,CAAE,CAAC,CAAC,CAAA;AACjH,MAAA,IAAI,KAAA,EAAO,CAAA;AACX,MAAA,IAAA,CAAA,IAAS,EAAA,EAAI,CAAA,EAAG,EAAA,EAAI,GAAA,CAAI,MAAA,EAAQ,CAAA,EAAA,EAAK,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,CAAE,CAAC,EAAA,EAAI,GAAA,CAAI,EAAA,EAAI,CAAC,CAAA,CAAE,CAAC,CAAA,EAAG,GAAA,CAAI,CAAC,CAAA,CAAE,CAAC,EAAA,EAAI,GAAA,CAAI,EAAA,EAAI,CAAC,CAAA,CAAE,CAAC,CAAC,CAAA;AAC5G,MAAA,MAAM,EAAA,EAAI,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,IAAA,CAAK,IAAA,CAAK,KAAA,EAAO,IAAA,CAAK,GAAA,CAAI,IAAA,EAAM,IAAI,CAAC,CAAC,CAAC,CAAA;AAC3E,MAAA,IAAA,CAAA,IAAS,EAAA,EAAI,CAAA,EAAG,EAAA,GAAK,CAAA,EAAG,CAAA,EAAA,EAAK;AAC5B,QAAA,MAAM,EAAA,EAAI,EAAA,EAAI,CAAA;AACd,QAAA,MAAM,EAAA,EAAI,EAAA,EAAI,CAAA;AACd,QAAA,GAAA,CAAI,CAAA,CAAE,KAAA,IAAS,GAAA,EAAK;AACnB,UAAA,IAAA;AAAA,YACC,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,CAAA,CAAE,GAAA,EAAK,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,CAAA,CAAE,GAAA,EAAK,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,CAAA,CAAE,CAAA;AAAA,YAC5E,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,CAAA,CAAE,GAAA,EAAK,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,CAAA,CAAE,GAAA,EAAK,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,CAAA,CAAE;AAAA,UAC7E,CAAA;AAAA,QACD,EAAA,KAAO;AACN,UAAA,IAAA,CAAK,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,CAAA,CAAE,GAAA,EAAK,EAAA,EAAI,EAAA,EAAI,CAAA,CAAE,CAAA,EAAG,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,EAAA,EAAI,CAAA,CAAE,GAAA,EAAK,EAAA,EAAI,EAAA,EAAI,CAAA,CAAE,CAAC,CAAA;AAAA,QAC5F;AAAA,MACD;AACA,MAAA,EAAA,EAAI,CAAA,CAAE,CAAA;AACN,MAAA,EAAA,EAAI,CAAA,CAAE,CAAA;AAAA,IACP,EAAA,KAAO;AAEN,MAAA,EAAA,EAAI,CAAA,CAAE,CAAA;AACN,MAAA,EAAA,EAAI,CAAA,CAAE,CAAA;AACN,MAAA,IAAA,CAAK,CAAA,EAAG,CAAC,CAAA;AAAA,IACV;AAAA,EACD;AACA,EAAA,OAAO,GAAA;AACR;AAMO,SAAS,oBAAA,CAAqB,MAAA,EAA0B,MAAA,EAA2B,MAAA,EAAQ,EAAA,EAAc;AAC/G,EAAA,MAAM,EAAA,EAAI,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,MAAA,EAAQ,MAAA,CAAO,MAAM,CAAA;AAC/C,EAAA,GAAA,CAAI,EAAA,IAAM,CAAA,EAAG,OAAO,CAAC,CAAA;AACrB,EAAA,GAAA,CAAI,EAAA,IAAM,CAAA,EAAG,OAAO,CAAC,MAAA,CAAO,MAAA,CAAO,CAAC,CAAC,CAAC,CAAA;AACtC,EAAA,MAAM,QAAA,EAAU,CAAC,CAAC,CAAA;AAClB,EAAA,IAAA,CAAA,IAAS,EAAA,EAAI,CAAA,EAAG,EAAA,EAAI,CAAA,EAAG,CAAA,EAAA,EAAK,OAAA,CAAQ,IAAA,CAAK,OAAA,CAAQ,EAAA,EAAI,CAAC,EAAA,EAAI,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,CAAE,CAAC,EAAA,EAAI,MAAA,CAAO,EAAA,EAAI,CAAC,CAAA,CAAE,CAAC,CAAA,EAAG,MAAA,CAAO,CAAC,CAAA,CAAE,CAAC,EAAA,EAAI,MAAA,CAAO,EAAA,EAAI,CAAC,CAAA,CAAE,CAAC,CAAC,CAAC,CAAA;AACtI,EAAA,MAAM,MAAA,EAAQ,OAAA,CAAQ,EAAA,EAAI,CAAC,CAAA;AAC3B,EAAA,MAAM,EAAA,EAAI,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,KAAA,EAAO,CAAC,CAAC,CAAA;AACxC,EAAA,MAAM,IAAA,EAAgB,CAAC,CAAA;AACvB,EAAA,IAAI,EAAA,EAAI,CAAA;AACR,EAAA,IAAA,CAAA,IAAS,EAAA,EAAI,CAAA,EAAG,EAAA,EAAI,CAAA,EAAG,CAAA,EAAA,EAAK;AAC3B,IAAA,MAAM,GAAA,EAAK,MAAA,EAAQ,EAAA,EAAK,MAAA,EAAQ,EAAA,EAAA,CAAM,EAAA,EAAI,CAAA,EAAA,EAAK,CAAA;AAC/C,IAAA,MAAA,CAAO,EAAA,EAAI,EAAA,EAAI,EAAA,GAAK,OAAA,CAAQ,EAAA,EAAI,CAAC,EAAA,EAAI,EAAA,EAAI,CAAA,EAAA;AACzC,IAAA,MAAM,KAAA,EAAO,OAAA,CAAQ,EAAA,EAAI,CAAC,EAAA,EAAI,OAAA,CAAQ,CAAC,CAAA;AACvC,IAAA,MAAM,EAAA,EAAI,KAAA,EAAO,EAAA,EAAI,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,CAAA,EAAA,CAAI,GAAA,EAAK,OAAA,CAAQ,CAAC,CAAA,EAAA,EAAK,IAAI,CAAC,EAAA,EAAI,CAAA;AAC1E,IAAA,GAAA,CAAI,IAAA,CAAK,MAAA,CAAO,MAAA,CAAO,CAAC,EAAA,EAAA,CAAK,MAAA,CAAO,EAAA,EAAI,CAAC,EAAA,EAAI,MAAA,CAAO,CAAC,CAAA,EAAA,EAAK,CAAC,CAAC,CAAA;AAAA,EAC7D;AACA,EAAA,OAAO,GAAA;AACR;AAEA,IAAM,OAAA,EAAS,CAAC,CAAA,EAAA,GAAc,IAAA,CAAK,KAAA,CAAM,EAAA,EAAI,GAAI,EAAA,EAAI,GAAA;AAerD,IAAI,QAAA,EAAqD,IAAA;AAMlD,SAAS,6BAAA,CAAA,EAAoE;AACnF,EAAA,QAAA,IAAa,4DAAA,CAAO,kCAAwB,GAAA,CAAyC,KAAA,CAAM,CAAC,KAAA,EAAA,GAAU;AACrG,IAAA,QAAA,EAAU,IAAA;AACV,IAAA,MAAM,KAAA;AAAA,EACP,CAAC,CAAA;AACD,EAAA,OAAO,OAAA;AACR;AAGA,MAAA,SAAsB,iBAAA,CAAA,EAAkD;AACvE,EAAA,OAAA,CAAQ,MAAM,6BAAA,CAA8B,CAAA,CAAA,CAAG,iBAAA,CAAkB,CAAA;AAClE;AAMA,MAAA,SAAsB,mBAAA,CAAoB,KAAA,EAAmC;AAC5E,EAAA,CAAC,MAAM,6BAAA,CAA8B,CAAA,CAAA,CAAG,kBAAA,CAAmB,KAAA,EAAO,CAAC,CAAA;AACpE;AAWA,MAAA,SAAsB,sBAAA,CAAuB,KAAA,EAAyB,QAAA,EAAyC,CAAC,CAAA,EAA2C;AAC1J,EAAA,GAAA,CAAI,CAAC,cAAA,CAAe,KAAK,CAAA,EAAG,OAAO,IAAA;AACnC,EAAA,OAAA,CAAQ,MAAM,6BAAA,CAA8B,CAAA,CAAA,CAAG,yBAAA,CAA0B,KAAA,mBAAO,OAAA,CAAQ,KAAA,UAAS,GAAC,CAAA;AACnG;AAOA,MAAA,SAAsB,kBAAA,CAAmB,KAAA,EAAyB,IAAA,EAAiB,QAAA,EAAyC,CAAC,CAAA,EAA2B;AACvJ,EAAA,GAAA,CAAI,CAAC,cAAA,CAAe,KAAK,CAAA,EAAG,OAAO,IAAA;AACnC,EAAA,OAAA,CAAQ,MAAM,6BAAA,CAA8B,CAAA,CAAA,CAAG,kBAAA,CAAmB,KAAA,EAAO,IAAA,mBAAM,OAAA,CAAQ,KAAA,UAAS,GAAC,CAAA;AAClG;ADhFA;AACA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,iaAAC","file":"/home/runner/work/editor/editor/packages/core/dist/chunk-7LQO6OHH.cjs","sourcesContent":[null,"/**\n * Brushed freehand strokes: a path shape with a `brush` keeps its path (selection, editing, eraser\n * hit-testing, grouping and export work as for any path) and is drawn with the paint engine's\n * dry brushstroke renderer instead of an SVG stroke. The renderer, the paint presets and the colour\n * parser live in a separate chunk, loaded the first time a brushed stroke is drawn, so canvases\n * without brushes never load the paint engine.\n */\nimport { brushstrokeScaleBucket } from '../paint/brushstroke-scale.js';\nimport { parseSvgPath, type PathShape, type ShapeDescription, type ShapeSize } from './shapeGeometry.js';\nimport type { Point } from './types.js';\nimport type { CanvasBrush, CanvasBrushPreset, CanvasBrushstroke } from './brushTypes.js';\n\nexport type { CanvasBrush, CanvasBrushPreset, CanvasBrushSettings, CanvasBrushstroke } from './brushTypes.js';\n\nexport { brushstrokeScaleBucket };\n\n/** A brushed stroke's image, placed in the path's coordinates (the shape's view box). */\nexport interface CanvasBrushstrokeImage {\n\t/** PNG data URL. */\n\thref: string;\n\tx: number;\n\ty: number;\n\twidth: number;\n\theight: number;\n\t/** Image pixels per path unit. */\n\tscale: number;\n}\n\n/** A path shape with a brush. */\nexport type BrushedPathShape = PathShape & { brush: CanvasBrushstroke };\n\n/** Whether a shape is a brushed stroke. */\nexport function isBrushedShape(shape: ShapeDescription | null | undefined): shape is BrushedPathShape {\n\treturn !!shape && shape.kind === 'path' && !!shape.brush && typeof shape.brush === 'object' && shape.brush.brush != null;\n}\n\n/**\n * The open polyline of a path's first subpath, in path coordinates: lines kept, curves sampled\n * every `step` units or so. Brushed strokes are drawn along it.\n */\nexport function pathDataToPolyline(d: string, step = 2): Point[] {\n\tconst out: Point[] = [];\n\tlet x = 0;\n\tlet y = 0;\n\tlet started = false;\n\tconst push = (px: number, py: number) => {\n\t\tconst last = out[out.length - 1];\n\t\tif (!last || last[0] !== px || last[1] !== py) out.push([px, py]);\n\t};\n\tfor (const c of parseSvgPath(d)) {\n\t\tif (c.type === 'M') {\n\t\t\tif (started) break;\n\t\t\tstarted = true;\n\t\t\tx = c.x;\n\t\t\ty = c.y;\n\t\t\tpush(x, y);\n\t\t} else if (c.type === 'Z') {\n\t\t\tif (out.length) push(out[0][0], out[0][1]);\n\t\t\tbreak;\n\t\t} else if (c.type === 'C' || c.type === 'Q') {\n\t\t\tconst pts = c.type === 'C' ? [[x, y], [c.x1, c.y1], [c.x2, c.y2], [c.x, c.y]] : [[x, y], [c.x1, c.y1], [c.x, c.y]];\n\t\t\tlet hull = 0;\n\t\t\tfor (let i = 1; i < pts.length; i++) hull += Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]);\n\t\t\tconst n = Math.max(1, Math.min(256, Math.ceil(hull / Math.max(0.25, step))));\n\t\t\tfor (let k = 1; k <= n; k++) {\n\t\t\t\tconst t = k / n;\n\t\t\t\tconst u = 1 - t;\n\t\t\t\tif (c.type === 'C') {\n\t\t\t\t\tpush(\n\t\t\t\t\t\tu * u * u * x + 3 * u * u * t * c.x1 + 3 * u * t * t * c.x2 + t * t * t * c.x,\n\t\t\t\t\t\tu * u * u * y + 3 * u * u * t * c.y1 + 3 * u * t * t * c.y2 + t * t * t * c.y\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tpush(u * u * x + 2 * u * t * c.x1 + t * t * c.x, u * u * y + 2 * u * t * c.y1 + t * t * c.y);\n\t\t\t\t}\n\t\t\t}\n\t\t\tx = c.x;\n\t\t\ty = c.y;\n\t\t} else {\n\t\t\t// Lines, and arcs as their chords (freehand paths have none).\n\t\t\tx = c.x;\n\t\t\ty = c.y;\n\t\t\tpush(x, y);\n\t\t}\n\t}\n\treturn out;\n}\n\n/**\n * Resamples per-point values into `count` values spread evenly along the polyline's length (the\n * profile a brushed stroke stores). Values are rounded to three decimals.\n */\nexport function profileAlongPolyline(points: readonly Point[], values: readonly number[], count = 32): number[] {\n\tconst n = Math.min(points.length, values.length);\n\tif (n === 0) return [];\n\tif (n === 1) return [round3(values[0])];\n\tconst lengths = [0];\n\tfor (let i = 1; i < n; i++) lengths.push(lengths[i - 1] + Math.hypot(points[i][0] - points[i - 1][0], points[i][1] - points[i - 1][1]));\n\tconst total = lengths[n - 1];\n\tconst m = Math.max(2, Math.min(count, n));\n\tconst out: number[] = [];\n\tlet j = 0;\n\tfor (let k = 0; k < m; k++) {\n\t\tconst at = total > 0 ? (total * k) / (m - 1) : 0;\n\t\twhile (j < n - 2 && lengths[j + 1] < at) j++;\n\t\tconst span = lengths[j + 1] - lengths[j];\n\t\tconst f = span > 0 ? Math.min(1, Math.max(0, (at - lengths[j]) / span)) : 0;\n\t\tout.push(round3(values[j] + (values[j + 1] - values[j]) * f));\n\t}\n\treturn out;\n}\n\nconst round3 = (v: number) => Math.round(v * 1000) / 1000;\n\n// ---------------------------------------------------------------------------\n// The lazily loaded renderer\n// ---------------------------------------------------------------------------\n\n/** The renderer chunk (`./brushstrokeRender.ts`). */\nexport interface CanvasBrushstrokeRenderer {\n\tlistCanvasBrushes(): CanvasBrushPreset[];\n\t/** Throws for an unknown or non-dry brush; the result is the paint engine's brush. */\n\tresolveCanvasBrush(brush: CanvasBrush, width: number): unknown;\n\trenderShapeBrushstrokeNow(shape: BrushedPathShape, scale: number): CanvasBrushstrokeImage | null;\n\tbrushedShapeSvgNow(shape: BrushedPathShape, size: ShapeSize, scale: number): string;\n}\n\nlet loading: Promise<CanvasBrushstrokeRenderer> | null = null;\n\n/**\n * Loads the brushstroke renderer (the paint engine's dry brushes, the paint presets and a CSS\n * colour parser). Called for you by the functions below; call it early to warm up.\n */\nexport function loadCanvasBrushstrokeRenderer(): Promise<CanvasBrushstrokeRenderer> {\n\tloading ??= (import('./brushstrokeRender.js') as Promise<CanvasBrushstrokeRenderer>).catch((error) => {\n\t\tloading = null;\n\t\tthrow error;\n\t});\n\treturn loading;\n}\n\n/** The dry paint presets a freehand stroke can use (wet, mixer, blender, smudge and eraser presets are left out). */\nexport async function listCanvasBrushes(): Promise<CanvasBrushPreset[]> {\n\treturn (await loadCanvasBrushstrokeRenderer()).listCanvasBrushes();\n}\n\n/**\n * Checks a brush: an unknown preset id, a wet (or other non-dry) preset, or settings that are not\n * an object throw an error saying which presets can be used.\n */\nexport async function validateCanvasBrush(brush: CanvasBrush): Promise<void> {\n\t(await loadCanvasBrushstrokeRenderer()).resolveCanvasBrush(brush, 1);\n}\n\nexport interface RenderShapeBrushstrokeOptions {\n\t/** Image pixels per path unit; pass `brushstrokeScaleBucket(zoom × node scale, devicePixelRatio)` (default 1). */\n\tscale?: number;\n}\n\n/**\n * Renders a brushed path shape to a cached PNG placed in path coordinates, or null for a shape\n * without a brush or an empty path. Throws for an unknown or non-dry brush.\n */\nexport async function renderShapeBrushstroke(shape: ShapeDescription, options: RenderShapeBrushstrokeOptions = {}): Promise<CanvasBrushstrokeImage | null> {\n\tif (!isBrushedShape(shape)) return null;\n\treturn (await loadCanvasBrushstrokeRenderer()).renderShapeBrushstrokeNow(shape, options.scale ?? 1);\n}\n\n/**\n * The brushed stroke as a standalone SVG document of `size` (the node box), with the image\n * embedded as a PNG data URL: what an SVG export writes for the node. Shapes without a brush give\n * null (export them as paths).\n */\nexport async function getBrushedShapeSvg(shape: ShapeDescription, size: ShapeSize, options: RenderShapeBrushstrokeOptions = {}): Promise<string | null> {\n\tif (!isBrushedShape(shape)) return null;\n\treturn (await loadCanvasBrushstrokeRenderer()).brushedShapeSvgNow(shape, size, options.scale ?? 2);\n}\n"]}
|
|
@@ -0,0 +1,234 @@
|
|
|
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;
|
|
2
|
+
|
|
3
|
+
var _chunkRIWPYSKUcjs = require('./chunk-RIWPYSKU.cjs');
|
|
4
|
+
|
|
5
|
+
// src/effects/errors.ts
|
|
6
|
+
var EFFECT_ERROR_CODES = ["unknown_effect", "invalid_values", "unsupported", "disposed", "no_provider"];
|
|
7
|
+
var EffectError = class extends Error {
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
constructor(code, message, effectId) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = "EffectError";
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.effectId = effectId;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var UnknownEffectError = class extends EffectError {
|
|
18
|
+
constructor(effectId, providerName) {
|
|
19
|
+
super(
|
|
20
|
+
"unknown_effect",
|
|
21
|
+
`Unknown effect "${effectId}"${providerName ? ` in ${providerName}` : ""}. Call search_effects or list() for valid ids.`,
|
|
22
|
+
effectId
|
|
23
|
+
);
|
|
24
|
+
this.name = "UnknownEffectError";
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
var EffectControlError = class extends EffectError {
|
|
28
|
+
|
|
29
|
+
constructor(effectId, details) {
|
|
30
|
+
const lines = details.map((d) => `- ${d.path || "/"}: ${d.message}`).join("\n");
|
|
31
|
+
super("invalid_values", `Invalid values for effect "${effectId}":
|
|
32
|
+
${lines}`, effectId);
|
|
33
|
+
this.name = "EffectControlError";
|
|
34
|
+
this.details = details;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var EffectUnsupportedError = class extends EffectError {
|
|
38
|
+
constructor(message, effectId) {
|
|
39
|
+
super("unsupported", message, effectId);
|
|
40
|
+
this.name = "EffectUnsupportedError";
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var EffectDisposedError = class extends EffectError {
|
|
44
|
+
constructor(effectId) {
|
|
45
|
+
super("disposed", `The renderer for "${effectId}" was disposed. Create a new renderer.`, effectId);
|
|
46
|
+
this.name = "EffectDisposedError";
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var NoEffectsProviderError = class extends EffectError {
|
|
50
|
+
constructor() {
|
|
51
|
+
super("no_provider", "No effects provider is registered with this editor. The host must call setEffectsProvider(provider) first.");
|
|
52
|
+
this.name = "NoEffectsProviderError";
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
function isEffectError(error) {
|
|
56
|
+
if (error instanceof EffectError) return true;
|
|
57
|
+
const code = error instanceof Error ? error.code : void 0;
|
|
58
|
+
return typeof code === "string" && EFFECT_ERROR_CODES.includes(code);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/effects/helpers.ts
|
|
62
|
+
function effectValueErrors(description, values) {
|
|
63
|
+
if (values === void 0) return [];
|
|
64
|
+
return _chunkRIWPYSKUcjs.validateJsonSchema.call(void 0, values, { ...description.controls, required: [] });
|
|
65
|
+
}
|
|
66
|
+
function resolveEffectValues(description, values) {
|
|
67
|
+
const errors = effectValueErrors(description, values);
|
|
68
|
+
if (errors.length) throw new EffectControlError(description.id, errors);
|
|
69
|
+
return { ...description.defaults, ..._nullishCoalesce(values, () => ( {})) };
|
|
70
|
+
}
|
|
71
|
+
var matchesFilters = (e, o) => (!o.kind || e.kind === o.kind) && (!o.category || e.category === o.category) && (!o.use || e.uses.includes(o.use));
|
|
72
|
+
function searchEffectSummaries(catalog, query = "", options = {}) {
|
|
73
|
+
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
74
|
+
const scored = [];
|
|
75
|
+
catalog.forEach((e, i) => {
|
|
76
|
+
if (!matchesFilters(e, options)) return;
|
|
77
|
+
const name = e.name.toLowerCase();
|
|
78
|
+
const hay = [e.id, e.name, e.category, ..._nullishCoalesce(e.tags, () => ( [])), _nullishCoalesce(e.description, () => ( ""))].join(" ").toLowerCase();
|
|
79
|
+
let score = 0;
|
|
80
|
+
for (const w of words) {
|
|
81
|
+
if (!hay.includes(w)) return;
|
|
82
|
+
score += name.startsWith(w) ? 3 : name.includes(w) ? 2 : 1;
|
|
83
|
+
}
|
|
84
|
+
scored.push({ e, score, i });
|
|
85
|
+
});
|
|
86
|
+
scored.sort((a, b) => b.score - a.score || a.i - b.i);
|
|
87
|
+
const out = scored.map((s) => s.e);
|
|
88
|
+
return options.limit !== void 0 ? out.slice(0, Math.max(0, options.limit)) : out;
|
|
89
|
+
}
|
|
90
|
+
function createEffectPixels(width, height, float = false) {
|
|
91
|
+
const n = Math.max(0, width * height * 4);
|
|
92
|
+
return { width, height, data: new Uint8ClampedArray(n), ...float ? { float: new Float32Array(n) } : {} };
|
|
93
|
+
}
|
|
94
|
+
function isEffectPixels(source) {
|
|
95
|
+
return !!source && typeof source === "object" && source.data instanceof Uint8ClampedArray && typeof source.width === "number" && typeof source.height === "number";
|
|
96
|
+
}
|
|
97
|
+
var srgbToLinear = (v) => v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
98
|
+
var linearToSrgb = (v) => v > 0 ? v <= 31308e-7 ? v * 12.92 : 1.055 * Math.pow(v, 1 / 2.4) - 0.055 : 0;
|
|
99
|
+
function encodeEffectFloat(pixels) {
|
|
100
|
+
const f = pixels.float;
|
|
101
|
+
if (!f) return pixels;
|
|
102
|
+
const d = pixels.data;
|
|
103
|
+
for (let i = 0; i < d.length; i += 4) {
|
|
104
|
+
d[i] = Math.round(linearToSrgb(f[i]) * 255);
|
|
105
|
+
d[i + 1] = Math.round(linearToSrgb(f[i + 1]) * 255);
|
|
106
|
+
d[i + 2] = Math.round(linearToSrgb(f[i + 2]) * 255);
|
|
107
|
+
d[i + 3] = Math.round(Math.min(1, Math.max(0, f[i + 3])) * 255);
|
|
108
|
+
}
|
|
109
|
+
return pixels;
|
|
110
|
+
}
|
|
111
|
+
function applyEffectMask(result, mask, source) {
|
|
112
|
+
const { data } = result;
|
|
113
|
+
const m = mask.data;
|
|
114
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
115
|
+
const k = m[i + 3] / 255;
|
|
116
|
+
if (k >= 1) continue;
|
|
117
|
+
if (source) {
|
|
118
|
+
for (let c = 0; c < 4; c++) data[i + c] = source.data[i + c] + (data[i + c] - source.data[i + c]) * k;
|
|
119
|
+
if (result.float && source.float) {
|
|
120
|
+
for (let c = 0; c < 4; c++) result.float[i + c] = source.float[i + c] + (result.float[i + c] - source.float[i + c]) * k;
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
data[i + 3] = data[i + 3] * k;
|
|
124
|
+
if (result.float) result.float[i + 3] *= k;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
var EffectsSlot = (_class = class {constructor() { _class.prototype.__init.call(this);_class.prototype.__init2.call(this); }
|
|
130
|
+
__init() {this.current = null}
|
|
131
|
+
__init2() {this.listeners = /* @__PURE__ */ new Set()}
|
|
132
|
+
get provider() {
|
|
133
|
+
return this.current;
|
|
134
|
+
}
|
|
135
|
+
set(provider) {
|
|
136
|
+
if (provider === this.current) return;
|
|
137
|
+
this.current = provider;
|
|
138
|
+
for (const l of [...this.listeners]) l(provider);
|
|
139
|
+
}
|
|
140
|
+
/** Sets `provider` and returns a function that clears it again (if it is still the one set). */
|
|
141
|
+
use(provider) {
|
|
142
|
+
this.set(provider);
|
|
143
|
+
return () => {
|
|
144
|
+
if (this.current === provider) this.set(null);
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/** The provider, or a `NoEffectsProviderError`. */
|
|
148
|
+
require() {
|
|
149
|
+
if (!this.current) throw new NoEffectsProviderError();
|
|
150
|
+
return this.current;
|
|
151
|
+
}
|
|
152
|
+
subscribe(listener) {
|
|
153
|
+
this.listeners.add(listener);
|
|
154
|
+
return () => this.listeners.delete(listener);
|
|
155
|
+
}
|
|
156
|
+
}, _class);
|
|
157
|
+
|
|
158
|
+
// src/effects/pool.ts
|
|
159
|
+
var EffectRendererPool = (_class2 = class {
|
|
160
|
+
__init3() {this.entries = /* @__PURE__ */ new Map()}
|
|
161
|
+
|
|
162
|
+
constructor(options = {}) {;_class2.prototype.__init3.call(this);
|
|
163
|
+
this.maxRenderers = Math.max(1, _nullishCoalesce(options.maxRenderers, () => ( 4)));
|
|
164
|
+
}
|
|
165
|
+
/** Live renderers now. */
|
|
166
|
+
get size() {
|
|
167
|
+
return this.entries.size;
|
|
168
|
+
}
|
|
169
|
+
/** Renders `request` with the provider's renderer for this effect, size and depth (made on first use). */
|
|
170
|
+
render(provider, id, size, request) {
|
|
171
|
+
const depth = _nullishCoalesce(size.depth, () => ( 8));
|
|
172
|
+
const key = `${provider.id}:${id}@${size.width}x${size.height}x${depth}`;
|
|
173
|
+
let entry = this.entries.get(key);
|
|
174
|
+
if (entry) {
|
|
175
|
+
this.entries.delete(key);
|
|
176
|
+
this.entries.set(key, entry);
|
|
177
|
+
} else {
|
|
178
|
+
const renderer = Promise.resolve().then(() => provider.createRenderer(id, { width: size.width, height: size.height, depth }));
|
|
179
|
+
renderer.catch(() => {
|
|
180
|
+
if (_optionalChain([this, 'access', _ => _.entries, 'access', _2 => _2.get, 'call', _3 => _3(key), 'optionalAccess', _4 => _4.renderer]) === renderer) this.entries.delete(key);
|
|
181
|
+
});
|
|
182
|
+
entry = { renderer, queue: Promise.resolve() };
|
|
183
|
+
this.entries.set(key, entry);
|
|
184
|
+
this.evict();
|
|
185
|
+
}
|
|
186
|
+
const e = entry;
|
|
187
|
+
const next = e.queue.then(() => e.renderer).then((r) => r.render(request));
|
|
188
|
+
e.queue = next.catch(() => void 0);
|
|
189
|
+
return next;
|
|
190
|
+
}
|
|
191
|
+
/** Disposes the least recently used renderers over the limit, after their queued renders. */
|
|
192
|
+
evict() {
|
|
193
|
+
while (this.entries.size > this.maxRenderers) {
|
|
194
|
+
const [key, entry] = this.entries.entries().next().value;
|
|
195
|
+
this.entries.delete(key);
|
|
196
|
+
dispose(entry);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/** Disposes every renderer (after its queued renders). */
|
|
200
|
+
clear() {
|
|
201
|
+
for (const entry of this.entries.values()) dispose(entry);
|
|
202
|
+
this.entries.clear();
|
|
203
|
+
}
|
|
204
|
+
}, _class2);
|
|
205
|
+
function dispose(entry) {
|
|
206
|
+
entry.queue.then(() => entry.renderer).then(
|
|
207
|
+
(r) => r.dispose(),
|
|
208
|
+
() => {
|
|
209
|
+
}
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
exports.EFFECT_ERROR_CODES = EFFECT_ERROR_CODES; exports.EffectError = EffectError; exports.UnknownEffectError = UnknownEffectError; exports.EffectControlError = EffectControlError; exports.EffectUnsupportedError = EffectUnsupportedError; exports.EffectDisposedError = EffectDisposedError; exports.NoEffectsProviderError = NoEffectsProviderError; exports.isEffectError = isEffectError; exports.effectValueErrors = effectValueErrors; exports.resolveEffectValues = resolveEffectValues; exports.searchEffectSummaries = searchEffectSummaries; exports.createEffectPixels = createEffectPixels; exports.isEffectPixels = isEffectPixels; exports.srgbToLinear = srgbToLinear; exports.linearToSrgb = linearToSrgb; exports.encodeEffectFloat = encodeEffectFloat; exports.applyEffectMask = applyEffectMask; exports.EffectsSlot = EffectsSlot; exports.EffectRendererPool = EffectRendererPool;
|
|
234
|
+
//# sourceMappingURL=chunk-CN4PJHUG.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/chunk-CN4PJHUG.cjs","../src/effects/errors.ts","../src/effects/helpers.ts","../src/effects/pool.ts"],"names":[],"mappings":"AAAA;AACE;AACF,wDAA6B;AAC7B;AACA;ACAO,IAAM,mBAAA,EAAiD,CAAC,gBAAA,EAAkB,gBAAA,EAAkB,aAAA,EAAe,UAAA,EAAY,aAAa,CAAA;AAMpI,IAAM,YAAA,EAAN,MAAA,QAA0B,MAAM;AAAA,EAC7B;AAAA,EACA;AAAA,EAET,WAAA,CAAY,IAAA,EAAuB,OAAA,EAAiB,QAAA,EAAmB;AACtE,IAAA,KAAA,CAAM,OAAO,CAAA;AACb,IAAA,IAAA,CAAK,KAAA,EAAO,aAAA;AACZ,IAAA,IAAA,CAAK,KAAA,EAAO,IAAA;AACZ,IAAA,IAAA,CAAK,SAAA,EAAW,QAAA;AAAA,EACjB;AACD,CAAA;AAGO,IAAM,mBAAA,EAAN,MAAA,QAAiC,YAAY;AAAA,EACnD,WAAA,CAAY,QAAA,EAAkB,YAAA,EAAuB;AACpD,IAAA,KAAA;AAAA,MACC,gBAAA;AAAA,MACA,CAAA,gBAAA,EAAmB,QAAQ,CAAA,CAAA,EAAI,aAAA,EAAe,CAAA,IAAA,EAAO,YAAY,CAAA,EAAA;AACjE,MAAA;AACD,IAAA;AACY,IAAA;AACb,EAAA;AACD;AAGoD;AAC1C,EAAA;AAEgE,EAAA;AACL,IAAA;AACL,IAAA;AAAwB;AAC1E,IAAA;AACG,IAAA;AAChB,EAAA;AACD;AAGwD;AACP,EAAA;AACT,IAAA;AAC1B,IAAA;AACb,EAAA;AACD;AAGqD;AACtB,EAAA;AACkB,IAAA;AACnC,IAAA;AACb,EAAA;AACD;AAGwD;AACzC,EAAA;AACQ,IAAA;AACT,IAAA;AACb,EAAA;AACD;AAGoE;AAC1B,EAAA;AAC6C,EAAA;AACA,EAAA;AACvF;ADhBwE;AACA;AEtCsD;AAC3F,EAAA;AAC2B,EAAA;AAC9D;AAOuK;AAClH,EAAA;AACkB,EAAA;AAClB,EAAA;AACrD;AAOuC;AAOmG;AAC5E,EAAA;AACP,EAAA;AAC5B,EAAA;AACQ,IAAA;AACD,IAAA;AAC4B,IAAA;AAChD,IAAA;AACW,IAAA;AACA,MAAA;AACmC,MAAA;AAC1D,IAAA;AAC2B,IAAA;AAC3B,EAAA;AACmD,EAAA;AACnB,EAAA;AACqC,EAAA;AACvE;AAO+F;AACtD,EAAA;AAC8B,EAAA;AACvE;AAG4F;AAIjE,EAAA;AAI3B;AAGqF;AAEK;AAGpB;AACpD,EAAA;AACF,EAAA;AACE,EAAA;AACqB,EAAA;AACK,IAAA;AACQ,IAAA;AACA,IAAA;AACY,IAAA;AAC/D,EAAA;AACO,EAAA;AACR;AAQ+G;AAC7F,EAAA;AACF,EAAA;AAC0B,EAAA;AACnB,IAAA;AACT,IAAA;AACA,IAAA;AACqD,MAAA;AAC9B,MAAA;AAC8B,QAAA;AAChE,MAAA;AACM,IAAA;AACsB,MAAA;AACa,MAAA;AAC1C,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAUyB;AACiB,iBAAA;AACuC,kBAAA;AAE1C,EAAA;AACzB,IAAA;AACb,EAAA;AAE2C,EAAA;AACX,IAAA;AAChB,IAAA;AACgC,IAAA;AAChD,EAAA;AAAA;AAG0C,EAAA;AACxB,IAAA;AACJ,IAAA;AACgC,MAAA;AAC7C,IAAA;AACD,EAAA;AAAA;AAG0B,EAAA;AAC2B,IAAA;AACxC,IAAA;AACb,EAAA;AAE2E,EAAA;AAC/C,IAAA;AACgB,IAAA;AAC5C,EAAA;AACD;AFlBwE;AACA;AGvIxC;AACuB,kBAAA;AACrC,EAAA;AAEoC,EAAA;AACK,IAAA;AAC1D,EAAA;AAAA;AAGmB,EAAA;AACE,IAAA;AACrB,EAAA;AAAA;AAGgK,EAAA;AACnI,IAAA;AACqC,IAAA;AACjC,IAAA;AACrB,IAAA;AACa,MAAA;AACI,MAAA;AACrB,IAAA;AACiD,MAAA;AAClC,MAAA;AAC2C,QAAA;AAC/D,MAAA;AAC4C,MAAA;AAClB,MAAA;AAChB,MAAA;AACZ,IAAA;AACU,IAAA;AACuD,IAAA;AAC7B,IAAA;AAC7B,IAAA;AACR,EAAA;AAAA;AAGsB,EAAA;AACyB,IAAA;AACM,MAAA;AAC5B,MAAA;AACV,MAAA;AACd,IAAA;AACD,EAAA;AAAA;AAGc,EAAA;AAC2C,IAAA;AACrC,IAAA;AACpB,EAAA;AACD;AAEmC;AAGhC,EAAA;AACiB,IAAA;AACX,IAAA;AAAC,IAAA;AACR,EAAA;AACF;AHkIwE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/chunk-CN4PJHUG.cjs","sourcesContent":[null,"import type { SchemaValidationError } from '../tools/schema.js';\n\nexport type EffectErrorCode = 'unknown_effect' | 'invalid_values' | 'unsupported' | 'disposed' | 'no_provider';\n\nexport const EFFECT_ERROR_CODES: readonly EffectErrorCode[] = ['unknown_effect', 'invalid_values', 'unsupported', 'disposed', 'no_provider'];\n\n/**\n * Base of every effects error. Check `code` (or use `isEffectError`) rather than `instanceof`\n * across packages, since a provider may be bundled with its own copy of this class.\n */\nexport class EffectError extends Error {\n\treadonly code: EffectErrorCode;\n\treadonly effectId?: string;\n\n\tconstructor(code: EffectErrorCode, message: string, effectId?: string) {\n\t\tsuper(message);\n\t\tthis.name = 'EffectError';\n\t\tthis.code = code;\n\t\tthis.effectId = effectId;\n\t}\n}\n\n/** No effect has this id. */\nexport class UnknownEffectError extends EffectError {\n\tconstructor(effectId: string, providerName?: string) {\n\t\tsuper(\n\t\t\t'unknown_effect',\n\t\t\t`Unknown effect \"${effectId}\"${providerName ? ` in ${providerName}` : ''}. Call search_effects or list() for valid ids.`,\n\t\t\teffectId\n\t\t);\n\t\tthis.name = 'UnknownEffectError';\n\t}\n}\n\n/** Control values do not match the effect's control schema (unknown control, wrong type, out of range). */\nexport class EffectControlError extends EffectError {\n\treadonly details: readonly SchemaValidationError[];\n\n\tconstructor(effectId: string, details: readonly SchemaValidationError[]) {\n\t\tconst lines = details.map((d) => `- ${d.path || '/'}: ${d.message}`).join('\\n');\n\t\tsuper('invalid_values', `Invalid values for effect \"${effectId}\":\\n${lines}`, effectId);\n\t\tthis.name = 'EffectControlError';\n\t\tthis.details = details;\n\t}\n}\n\n/** The provider cannot do this (a vector effect asked for pixels, a size above `maxSize`, a missing input...). */\nexport class EffectUnsupportedError extends EffectError {\n\tconstructor(message: string, effectId?: string) {\n\t\tsuper('unsupported', message, effectId);\n\t\tthis.name = 'EffectUnsupportedError';\n\t}\n}\n\n/** The renderer was disposed. Create a new one. */\nexport class EffectDisposedError extends EffectError {\n\tconstructor(effectId: string) {\n\t\tsuper('disposed', `The renderer for \"${effectId}\" was disposed. Create a new renderer.`, effectId);\n\t\tthis.name = 'EffectDisposedError';\n\t}\n}\n\n/** No effects provider is registered with this editor. */\nexport class NoEffectsProviderError extends EffectError {\n\tconstructor() {\n\t\tsuper('no_provider', 'No effects provider is registered with this editor. The host must call setEffectsProvider(provider) first.');\n\t\tthis.name = 'NoEffectsProviderError';\n\t}\n}\n\n/** True for any effects error, including one thrown by another copy of this module. */\nexport function isEffectError(error: unknown): error is EffectError {\n\tif (error instanceof EffectError) return true;\n\tconst code = error instanceof Error ? (error as unknown as { code?: unknown }).code : undefined;\n\treturn typeof code === 'string' && EFFECT_ERROR_CODES.includes(code as EffectErrorCode);\n}\n","/**\n * Helpers for providers and editors: value validation, catalog search, pixel buffers, masking,\n * and a provider slot editors hold.\n */\n\nimport { validateJsonSchema, type SchemaValidationError } from '../tools/schema.js';\nimport { EffectControlError, NoEffectsProviderError } from './errors.js';\nimport type {\n\tEffectDescription,\n\tEffectImageSource,\n\tEffectPixels,\n\tEffectProvider,\n\tEffectSearchOptions,\n\tEffectSummary,\n\tEffectValues\n} from './types.js';\n\n// ---------------------------------------------------------------------------\n// Values\n// ---------------------------------------------------------------------------\n\n/** Problems with `values` against an effect's control schema (unknown controls, types, ranges). Partial values are fine. */\nexport function effectValueErrors(description: Pick<EffectDescription, 'controls'>, values: unknown): SchemaValidationError[] {\n\tif (values === undefined) return [];\n\treturn validateJsonSchema(values, { ...description.controls, required: [] });\n}\n\n/**\n * `values` over the effect's defaults, validated against its control schema. Throws\n * `EffectControlError` naming every bad control. Providers call this in `render`; editors call\n * it before saving values.\n */\nexport function resolveEffectValues(description: Pick<EffectDescription, 'id' | 'controls' | 'defaults'>, values?: EffectValues): Record<string, EffectValues[string]> {\n\tconst errors = effectValueErrors(description, values);\n\tif (errors.length) throw new EffectControlError(description.id, errors);\n\treturn { ...description.defaults, ...(values ?? {}) };\n}\n\n// ---------------------------------------------------------------------------\n// Search\n// ---------------------------------------------------------------------------\n\nconst matchesFilters = (e: EffectSummary, o: EffectSearchOptions) =>\n\t(!o.kind || e.kind === o.kind) && (!o.category || e.category === o.category) && (!o.use || e.uses.includes(o.use));\n\n/**\n * Filters a catalog and ranks it against `query` (every word must appear in the id, name,\n * category, tags or description; name matches rank first). An empty query keeps catalog order.\n * Providers can implement `list` and `search` with it.\n */\nexport function searchEffectSummaries<T extends EffectSummary>(catalog: readonly T[], query = '', options: EffectSearchOptions = {}): T[] {\n\tconst words = query.toLowerCase().split(/\\s+/).filter(Boolean);\n\tconst scored: { e: T; score: number; i: number }[] = [];\n\tcatalog.forEach((e, i) => {\n\t\tif (!matchesFilters(e, options)) return;\n\t\tconst name = e.name.toLowerCase();\n\t\tconst hay = [e.id, e.name, e.category, ...(e.tags ?? []), e.description ?? ''].join(' ').toLowerCase();\n\t\tlet score = 0;\n\t\tfor (const w of words) {\n\t\t\tif (!hay.includes(w)) return;\n\t\t\tscore += name.startsWith(w) ? 3 : name.includes(w) ? 2 : 1;\n\t\t}\n\t\tscored.push({ e, score, i });\n\t});\n\tscored.sort((a, b) => b.score - a.score || a.i - b.i);\n\tconst out = scored.map((s) => s.e);\n\treturn options.limit !== undefined ? out.slice(0, Math.max(0, options.limit)) : out;\n}\n\n// ---------------------------------------------------------------------------\n// Pixels\n// ---------------------------------------------------------------------------\n\n/** A transparent picture, with float samples when `float` is set. */\nexport function createEffectPixels(width: number, height: number, float = false): EffectPixels {\n\tconst n = Math.max(0, width * height * 4);\n\treturn { width, height, data: new Uint8ClampedArray(n), ...(float ? { float: new Float32Array(n) } : {}) };\n}\n\n/** True for an `EffectPixels` (rather than a host image object). */\nexport function isEffectPixels(source: EffectImageSource | unknown): source is EffectPixels {\n\treturn (\n\t\t!!source &&\n\t\ttypeof source === 'object' &&\n\t\t(source as EffectPixels).data instanceof Uint8ClampedArray &&\n\t\ttypeof (source as EffectPixels).width === 'number' &&\n\t\ttypeof (source as EffectPixels).height === 'number'\n\t);\n}\n\n/** sRGB-encoded 0–1 to linear light. */\nexport const srgbToLinear = (v: number) => (v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4));\n/** Linear light to sRGB-encoded (clamped below at 0; may exceed 1). */\nexport const linearToSrgb = (v: number) => (v > 0 ? (v <= 0.0031308 ? v * 12.92 : 1.055 * Math.pow(v, 1 / 2.4) - 0.055) : 0);\n\n/** Fills `pixels.data` from `pixels.float` (linear float to clamped 8-bit sRGB). */\nexport function encodeEffectFloat(pixels: EffectPixels): EffectPixels {\n\tconst f = pixels.float;\n\tif (!f) return pixels;\n\tconst d = pixels.data;\n\tfor (let i = 0; i < d.length; i += 4) {\n\t\td[i] = Math.round(linearToSrgb(f[i]) * 255);\n\t\td[i + 1] = Math.round(linearToSrgb(f[i + 1]) * 255);\n\t\td[i + 2] = Math.round(linearToSrgb(f[i + 2]) * 255);\n\t\td[i + 3] = Math.round(Math.min(1, Math.max(0, f[i + 3])) * 255);\n\t}\n\treturn pixels;\n}\n\n/**\n * Blends an effect `result` back over its `source` by `mask` coverage (alpha channel), in place\n * on `result`: where the mask is 0 the source shows, where it is 255 the result. Without a\n * source (generators), the result's alpha is multiplied by coverage. Editors use it when the\n * provider does not `supportsMask`. All three must have the same size.\n */\nexport function applyEffectMask(result: EffectPixels, mask: EffectPixels, source?: EffectPixels): EffectPixels {\n\tconst { data } = result;\n\tconst m = mask.data;\n\tfor (let i = 0; i < data.length; i += 4) {\n\t\tconst k = m[i + 3] / 255;\n\t\tif (k >= 1) continue;\n\t\tif (source) {\n\t\t\tfor (let c = 0; c < 4; c++) data[i + c] = source.data[i + c] + (data[i + c] - source.data[i + c]) * k;\n\t\t\tif (result.float && source.float) {\n\t\t\t\tfor (let c = 0; c < 4; c++) result.float[i + c] = source.float[i + c] + (result.float[i + c] - source.float[i + c]) * k;\n\t\t\t}\n\t\t} else {\n\t\t\tdata[i + 3] = data[i + 3] * k;\n\t\t\tif (result.float) result.float[i + 3] *= k;\n\t\t}\n\t}\n\treturn result;\n}\n\n// ---------------------------------------------------------------------------\n// The slot an editor keeps\n// ---------------------------------------------------------------------------\n\n/**\n * Holds an editor's effects provider. Controllers expose it as `effectsProvider`,\n * `setEffectsProvider(provider | null)` and `useEffects(provider)`; listeners hear every change.\n */\nexport class EffectsSlot {\n\tprivate current: EffectProvider | null = null;\n\tprivate readonly listeners = new Set<(provider: EffectProvider | null) => void>();\n\n\tget provider(): EffectProvider | null {\n\t\treturn this.current;\n\t}\n\n\tset(provider: EffectProvider | null): void {\n\t\tif (provider === this.current) return;\n\t\tthis.current = provider;\n\t\tfor (const l of [...this.listeners]) l(provider);\n\t}\n\n\t/** Sets `provider` and returns a function that clears it again (if it is still the one set). */\n\tuse(provider: EffectProvider): () => void {\n\t\tthis.set(provider);\n\t\treturn () => {\n\t\t\tif (this.current === provider) this.set(null);\n\t\t};\n\t}\n\n\t/** The provider, or a `NoEffectsProviderError`. */\n\trequire(): EffectProvider {\n\t\tif (!this.current) throw new NoEffectsProviderError();\n\t\treturn this.current;\n\t}\n\n\tsubscribe(listener: (provider: EffectProvider | null) => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => this.listeners.delete(listener);\n\t}\n}\n","/**\n * `EffectRendererPool`: keeps a few live renderers, reused per effect, size and depth.\n *\n * GPU providers hold one graphics context per renderer, and browsers allow only about sixteen\n * WebGL contexts at a time (the oldest is lost after that). So the pool keeps at most\n * `maxRenderers` renderers. It disposes the least recently used one once its renders finish, and it\n * runs one render at a time per renderer, as the contract requires.\n */\n\nimport type { EffectPixels, EffectProvider, EffectRenderer, EffectRenderRequest } from './types.js';\n\ninterface PoolEntry {\n\trenderer: Promise<EffectRenderer>;\n\t/** The last render queued on it. */\n\tqueue: Promise<unknown>;\n}\n\nexport interface EffectRendererPoolOptions {\n\t/** Most live renderers (default 4). */\n\tmaxRenderers?: number;\n}\n\nexport class EffectRendererPool {\n\tprivate readonly entries = new Map<string, PoolEntry>();\n\tprivate readonly maxRenderers: number;\n\n\tconstructor(options: EffectRendererPoolOptions = {}) {\n\t\tthis.maxRenderers = Math.max(1, options.maxRenderers ?? 4);\n\t}\n\n\t/** Live renderers now. */\n\tget size(): number {\n\t\treturn this.entries.size;\n\t}\n\n\t/** Renders `request` with the provider's renderer for this effect, size and depth (made on first use). */\n\trender(provider: EffectProvider, id: string, size: { width: number; height: number; depth?: 8 | 16 | 32 }, request: EffectRenderRequest): Promise<EffectPixels> {\n\t\tconst depth = size.depth ?? 8;\n\t\tconst key = `${provider.id}:${id}@${size.width}x${size.height}x${depth}`;\n\t\tlet entry = this.entries.get(key);\n\t\tif (entry) {\n\t\t\tthis.entries.delete(key);\n\t\t\tthis.entries.set(key, entry);\n\t\t} else {\n\t\t\tconst renderer = Promise.resolve().then(() => provider.createRenderer(id, { width: size.width, height: size.height, depth }));\n\t\t\trenderer.catch(() => {\n\t\t\t\tif (this.entries.get(key)?.renderer === renderer) this.entries.delete(key);\n\t\t\t});\n\t\t\tentry = { renderer, queue: Promise.resolve() };\n\t\t\tthis.entries.set(key, entry);\n\t\t\tthis.evict();\n\t\t}\n\t\tconst e = entry;\n\t\tconst next = e.queue.then(() => e.renderer).then((r) => r.render(request));\n\t\te.queue = next.catch(() => undefined);\n\t\treturn next;\n\t}\n\n\t/** Disposes the least recently used renderers over the limit, after their queued renders. */\n\tprivate evict(): void {\n\t\twhile (this.entries.size > this.maxRenderers) {\n\t\t\tconst [key, entry] = this.entries.entries().next().value!;\n\t\t\tthis.entries.delete(key);\n\t\t\tdispose(entry);\n\t\t}\n\t}\n\n\t/** Disposes every renderer (after its queued renders). */\n\tclear(): void {\n\t\tfor (const entry of this.entries.values()) dispose(entry);\n\t\tthis.entries.clear();\n\t}\n}\n\nfunction dispose(entry: PoolEntry) {\n\tentry.queue\n\t\t.then(() => entry.renderer)\n\t\t.then(\n\t\t\t(r) => r.dispose(),\n\t\t\t() => {}\n\t\t);\n}\n"]}
|