@pixerate/editor 0.115.0 → 0.116.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD_PARTY_NOTICES.md +3 -0
- package/dist/animation-editor/index.cjs +54 -54
- package/dist/animation-editor/index.d.cts +2 -2
- package/dist/animation-editor/index.d.ts +2 -2
- package/dist/animation-editor/index.js +2 -2
- package/dist/{chunk-DFQEX3AM.cjs → chunk-2UT4GXYA.cjs} +133 -8
- package/dist/chunk-2UT4GXYA.cjs.map +1 -0
- package/dist/{chunk-VXR7XWMF.js → chunk-7LNZAWSJ.js} +437 -41
- package/dist/chunk-7LNZAWSJ.js.map +1 -0
- package/dist/{chunk-LKCYANMW.js → chunk-ARQ7PQ6Q.js} +129 -4
- package/dist/chunk-ARQ7PQ6Q.js.map +1 -0
- package/dist/chunk-COAMRKVI.cjs +159 -0
- package/dist/chunk-COAMRKVI.cjs.map +1 -0
- package/dist/{chunk-C6C5PDKU.js → chunk-D53KSJUV.js} +3 -322
- package/dist/chunk-D53KSJUV.js.map +1 -0
- package/dist/chunk-D53VQGEJ.js +3262 -0
- package/dist/chunk-D53VQGEJ.js.map +1 -0
- package/dist/chunk-EJVDBITD.cjs +501 -0
- package/dist/chunk-EJVDBITD.cjs.map +1 -0
- package/dist/{chunk-LPLCWZTM.cjs → chunk-FODKPD44.cjs} +555 -159
- package/dist/chunk-FODKPD44.cjs.map +1 -0
- package/dist/chunk-HOCPSFGA.cjs +3262 -0
- package/dist/chunk-HOCPSFGA.cjs.map +1 -0
- package/dist/chunk-IPFRWILU.js +1908 -0
- package/dist/chunk-IPFRWILU.js.map +1 -0
- package/dist/{chunk-FV5AXTSF.cjs → chunk-JSSBHMMU.cjs} +36 -188
- package/dist/chunk-JSSBHMMU.cjs.map +1 -0
- package/dist/chunk-LF4WOZXZ.js +159 -0
- package/dist/chunk-LF4WOZXZ.js.map +1 -0
- package/dist/{chunk-26PGXHY7.cjs → chunk-M7O4BJRH.cjs} +36 -36
- package/dist/{chunk-26PGXHY7.cjs.map → chunk-M7O4BJRH.cjs.map} +1 -1
- package/dist/chunk-N3467VD6.js +501 -0
- package/dist/chunk-N3467VD6.js.map +1 -0
- package/dist/{chunk-ST7E3NEE.cjs → chunk-PAOWHJ5O.cjs} +18 -337
- package/dist/chunk-PAOWHJ5O.cjs.map +1 -0
- package/dist/{chunk-XICA4PVO.js → chunk-SEBYNBHW.js} +10 -10
- package/dist/chunk-SMJGE7HM.cjs +1908 -0
- package/dist/chunk-SMJGE7HM.cjs.map +1 -0
- package/dist/{chunk-MMVHBKMM.js → chunk-T2P5TIZ6.js} +2 -2
- package/dist/{chunk-FGW5OWJ4.js → chunk-VJUML33I.js} +4 -156
- package/dist/chunk-VJUML33I.js.map +1 -0
- package/dist/{chunk-I7FQBWMQ.cjs → chunk-ZYZGEILO.cjs} +239 -239
- package/dist/{chunk-I7FQBWMQ.cjs.map → chunk-ZYZGEILO.cjs.map} +1 -1
- package/dist/export/index.cjs +4 -2
- package/dist/export/index.cjs.map +1 -1
- package/dist/export/index.js +5 -3
- package/dist/{export-BJE8T0YA.d.cts → export-BXxSGBCq.d.cts} +2 -2
- package/dist/{export-Con3UKkm.d.ts → export-Dm44YUvS.d.ts} +2 -2
- package/dist/export-Dv0-ZVrV.d.cts +501 -0
- package/dist/export-Dv0-ZVrV.d.ts +501 -0
- package/dist/{retouch-Do6tBs2H.d.ts → flow-BMtHAmg4.d.ts} +561 -2
- package/dist/{retouch-Bxkuc0KH.d.cts → flow-COCB42TP.d.cts} +561 -2
- package/dist/image-editor/index.cjs +957 -795
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +6 -6
- package/dist/image-editor/index.d.ts +6 -6
- package/dist/image-editor/index.js +180 -18
- package/dist/image-editor/index.js.map +1 -1
- package/dist/index.cjs +4 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/kernel-wasm-WYCGNZR4.js +10 -0
- package/dist/kernel-wasm-WYCGNZR4.js.map +1 -0
- package/dist/kernel-wasm-YPVAEMVN.cjs +10 -0
- package/dist/kernel-wasm-YPVAEMVN.cjs.map +1 -0
- package/dist/paint/index.cjs +165 -3
- package/dist/paint/index.cjs.map +1 -1
- package/dist/paint/index.d.cts +225 -3
- package/dist/paint/index.d.ts +225 -3
- package/dist/paint/index.js +166 -4
- package/dist/paint-editor/index.cjs +4690 -845
- package/dist/paint-editor/index.cjs.map +1 -1
- package/dist/paint-editor/index.d.cts +1525 -277
- package/dist/paint-editor/index.d.ts +1525 -277
- package/dist/paint-editor/index.js +4560 -715
- package/dist/paint-editor/index.js.map +1 -1
- package/dist/raster/index.cjs +4 -3
- package/dist/raster/index.cjs.map +1 -1
- package/dist/raster/index.js +10 -9
- package/dist/{svg-BnRj0q8i.d.cts → svg-CVdFc80-.d.cts} +2 -2
- package/dist/{svg-CmmBFfGT.d.ts → svg-DlEuhwoX.d.ts} +2 -2
- package/dist/{types-1SUQKDAw.d.ts → types-CDLGhAZp.d.ts} +40 -2
- package/dist/{types-yINF17Ta.d.cts → types-Tfly4Aya.d.cts} +40 -2
- package/dist/vector/index.cjs +1897 -975
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +292 -32
- package/dist/vector/index.d.ts +292 -32
- package/dist/vector/index.js +928 -6
- package/dist/vector/index.js.map +1 -1
- package/dist/video-editor/index.cjs +502 -2165
- package/dist/video-editor/index.cjs.map +1 -1
- package/dist/video-editor/index.d.cts +3 -501
- package/dist/video-editor/index.d.ts +3 -501
- package/dist/video-editor/index.js +151 -1814
- package/dist/video-editor/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/pixerate-image-editor/references/photoshop-files.md +7 -3
- package/skills/pixerate-vector/SKILL.md +3 -0
- package/dist/chunk-C6C5PDKU.js.map +0 -1
- package/dist/chunk-DFQEX3AM.cjs.map +0 -1
- package/dist/chunk-FGW5OWJ4.js.map +0 -1
- package/dist/chunk-FV5AXTSF.cjs.map +0 -1
- package/dist/chunk-GX332W2C.js +0 -181
- package/dist/chunk-GX332W2C.js.map +0 -1
- package/dist/chunk-LKCYANMW.js.map +0 -1
- package/dist/chunk-LPLCWZTM.cjs.map +0 -1
- package/dist/chunk-MUUXNRU4.cjs +0 -181
- package/dist/chunk-MUUXNRU4.cjs.map +0 -1
- package/dist/chunk-ST7E3NEE.cjs.map +0 -1
- package/dist/chunk-TCKIXI46.cjs +0 -1136
- package/dist/chunk-TCKIXI46.cjs.map +0 -1
- package/dist/chunk-UBIXVXPR.js +0 -1136
- package/dist/chunk-UBIXVXPR.js.map +0 -1
- package/dist/chunk-VXR7XWMF.js.map +0 -1
- /package/dist/{chunk-XICA4PVO.js.map → chunk-SEBYNBHW.js.map} +0 -0
- /package/dist/{chunk-MMVHBKMM.js.map → chunk-T2P5TIZ6.js.map} +0 -0
package/dist/vector/index.js
CHANGED
|
@@ -10,11 +10,15 @@ import {
|
|
|
10
10
|
} from "../chunk-3UFGNHSP.js";
|
|
11
11
|
import {
|
|
12
12
|
applyHomography,
|
|
13
|
+
decodePng,
|
|
13
14
|
homographyFromPoints,
|
|
14
15
|
homographyKeepsQuad,
|
|
16
|
+
isPng,
|
|
15
17
|
orderQuad,
|
|
18
|
+
parseDataUrl,
|
|
16
19
|
solve8
|
|
17
|
-
} from "../chunk-
|
|
20
|
+
} from "../chunk-N3467VD6.js";
|
|
21
|
+
import "../chunk-6FCKOS2B.js";
|
|
18
22
|
import {
|
|
19
23
|
correspondMorphContours,
|
|
20
24
|
createVectorMorph,
|
|
@@ -33,7 +37,7 @@ import {
|
|
|
33
37
|
reverseMorphContour,
|
|
34
38
|
subpathToMorphContour,
|
|
35
39
|
turnMorphContour
|
|
36
|
-
} from "../chunk-
|
|
40
|
+
} from "../chunk-T2P5TIZ6.js";
|
|
37
41
|
import {
|
|
38
42
|
DEFAULT_VECTOR_FONT,
|
|
39
43
|
IDENTITY_MATRIX,
|
|
@@ -67,6 +71,7 @@ import {
|
|
|
67
71
|
VECTOR_HORIZONTAL_RESIZING_LABELS,
|
|
68
72
|
VECTOR_MASK_TYPES,
|
|
69
73
|
VECTOR_MERGE_MODES,
|
|
74
|
+
VECTOR_MESH_MAX,
|
|
70
75
|
VECTOR_PARAGRAPH_STYLE_KEYS,
|
|
71
76
|
VECTOR_PATHFINDER_OPERATIONS,
|
|
72
77
|
VECTOR_PATH_TEXT_BASELINES,
|
|
@@ -100,6 +105,7 @@ import {
|
|
|
100
105
|
arrowheadInset,
|
|
101
106
|
arrowheadPath,
|
|
102
107
|
arrowheadWeight,
|
|
108
|
+
autoVectorFreeformGradient,
|
|
103
109
|
bakeVectorTextGeometry,
|
|
104
110
|
bezierToSubpaths,
|
|
105
111
|
boundsFromPoints,
|
|
@@ -122,6 +128,7 @@ import {
|
|
|
122
128
|
createOutlineTextShaper,
|
|
123
129
|
createVectorEffect,
|
|
124
130
|
createVectorFilter,
|
|
131
|
+
createVectorMeshGradient,
|
|
125
132
|
cssFontFamily,
|
|
126
133
|
dashIntervals,
|
|
127
134
|
decomposeRigid,
|
|
@@ -182,11 +189,14 @@ import {
|
|
|
182
189
|
isVectorConnector,
|
|
183
190
|
isVectorFilterType,
|
|
184
191
|
isVectorFrame,
|
|
192
|
+
isVectorFreeformGradient,
|
|
185
193
|
isVectorGradient,
|
|
186
194
|
isVectorLiveBoolean,
|
|
187
195
|
isVectorMaskActive,
|
|
188
196
|
isVectorMaskGroup,
|
|
189
197
|
isVectorMaskType,
|
|
198
|
+
isVectorMeshGradient,
|
|
199
|
+
isVectorShadedPaint,
|
|
190
200
|
knifePath,
|
|
191
201
|
layoutVectorText,
|
|
192
202
|
lineSubpath,
|
|
@@ -195,6 +205,7 @@ import {
|
|
|
195
205
|
mapVectorGradient,
|
|
196
206
|
mapVectorPaint,
|
|
197
207
|
mapVectorPoints,
|
|
208
|
+
mapVectorShadedPaint,
|
|
198
209
|
mapVectorSubpaths,
|
|
199
210
|
mapWarpMeshPoints,
|
|
200
211
|
matrixAbout,
|
|
@@ -209,6 +220,7 @@ import {
|
|
|
209
220
|
nearestSegment,
|
|
210
221
|
nearestStrokePosition,
|
|
211
222
|
nearestVectorConnectionSite,
|
|
223
|
+
nearestVectorShadingPoint,
|
|
212
224
|
needsVectorPaintLayers,
|
|
213
225
|
newVectorStrokeLayer,
|
|
214
226
|
nextVectorCornerKind,
|
|
@@ -236,6 +248,7 @@ import {
|
|
|
236
248
|
pullWarpMesh,
|
|
237
249
|
rectSubpath,
|
|
238
250
|
removeAnchors,
|
|
251
|
+
removeVectorFreeformPoint,
|
|
239
252
|
removeVectorGradientStop,
|
|
240
253
|
removeWarpMeshSplitU,
|
|
241
254
|
removeWarpMeshSplitV,
|
|
@@ -253,6 +266,7 @@ import {
|
|
|
253
266
|
roundSubpaths,
|
|
254
267
|
routeVectorConnector,
|
|
255
268
|
samplePlainStops,
|
|
269
|
+
sampleVectorFreeform,
|
|
256
270
|
sampleVectorGradient,
|
|
257
271
|
sampleVectorGradientAt,
|
|
258
272
|
sampleVectorPaintDefAt,
|
|
@@ -264,6 +278,7 @@ import {
|
|
|
264
278
|
sanitizeVectorGradient,
|
|
265
279
|
sanitizeVectorMask,
|
|
266
280
|
sanitizeVectorResizing,
|
|
281
|
+
sanitizeVectorShadedPaint,
|
|
267
282
|
sanitizeVectorStrokeSettings,
|
|
268
283
|
sanitizeVectorStrokes,
|
|
269
284
|
sanitizeVectorTextFields,
|
|
@@ -297,6 +312,7 @@ import {
|
|
|
297
312
|
transformVectorGradient,
|
|
298
313
|
transformVectorNode,
|
|
299
314
|
transformVectorPaint,
|
|
315
|
+
transformVectorShadedPaint,
|
|
300
316
|
translateAnchor,
|
|
301
317
|
translateMatrix,
|
|
302
318
|
trimIntervals,
|
|
@@ -338,6 +354,9 @@ import {
|
|
|
338
354
|
vectorFrameDocument,
|
|
339
355
|
vectorFrameDrawn,
|
|
340
356
|
vectorFrameRectPath,
|
|
357
|
+
vectorFreeformField,
|
|
358
|
+
vectorFreeformLineSegments,
|
|
359
|
+
vectorFreeformSpreadScale,
|
|
341
360
|
vectorGradientArea,
|
|
342
361
|
vectorGradientAspect,
|
|
343
362
|
vectorGradientFocalParam,
|
|
@@ -368,8 +387,11 @@ import {
|
|
|
368
387
|
vectorMaskedGeometry,
|
|
369
388
|
vectorMaskedLocalBounds,
|
|
370
389
|
vectorMaskedShapes,
|
|
390
|
+
vectorMeshColorAt,
|
|
371
391
|
vectorMeshFromOutline,
|
|
392
|
+
vectorMeshLines,
|
|
372
393
|
vectorMeshOutline,
|
|
394
|
+
vectorMeshPointAt,
|
|
373
395
|
vectorNodeBounds,
|
|
374
396
|
vectorNodeEnvelope,
|
|
375
397
|
vectorNodeFilterSvg,
|
|
@@ -388,6 +410,8 @@ import {
|
|
|
388
410
|
vectorPathTextPositions,
|
|
389
411
|
vectorPathWorldBezier,
|
|
390
412
|
vectorRepeaterCopies,
|
|
413
|
+
vectorRgbaToCss,
|
|
414
|
+
vectorShadedSvgDefs,
|
|
391
415
|
vectorSpatialIndex,
|
|
392
416
|
vectorStacksHaveGradient,
|
|
393
417
|
vectorStrokeGradientDefs,
|
|
@@ -440,7 +464,7 @@ import {
|
|
|
440
464
|
withVectorStrokes,
|
|
441
465
|
withVectorTargetPaint,
|
|
442
466
|
zigzagPaths
|
|
443
|
-
} from "../chunk-
|
|
467
|
+
} from "../chunk-7LNZAWSJ.js";
|
|
444
468
|
import {
|
|
445
469
|
bezierPathBounds,
|
|
446
470
|
bezierPathToSvg,
|
|
@@ -452,11 +476,13 @@ import {
|
|
|
452
476
|
fitCubics,
|
|
453
477
|
pathBoolean,
|
|
454
478
|
pathToContours,
|
|
479
|
+
pointInBezierPath,
|
|
455
480
|
segmentDerivative,
|
|
456
481
|
segmentIntersections,
|
|
457
482
|
segmentNearest,
|
|
458
483
|
segmentPoint,
|
|
459
484
|
segmentTangent,
|
|
485
|
+
simplifyRing,
|
|
460
486
|
transformBezierPath
|
|
461
487
|
} from "../chunk-YCSFS3GK.js";
|
|
462
488
|
import {
|
|
@@ -556,7 +582,11 @@ function resolveVectorBlendOptions(options) {
|
|
|
556
582
|
}
|
|
557
583
|
function paintColors(p) {
|
|
558
584
|
if (typeof p === "string") return [p];
|
|
559
|
-
if (p && typeof p === "object")
|
|
585
|
+
if (p && typeof p === "object") {
|
|
586
|
+
if (p.type === "mesh") return p.colors;
|
|
587
|
+
if (p.type === "freeform") return p.points.map((q) => q.color);
|
|
588
|
+
return p.stops.map((s) => s.color);
|
|
589
|
+
}
|
|
560
590
|
return [];
|
|
561
591
|
}
|
|
562
592
|
function vectorBlendColorDistance(a, b) {
|
|
@@ -765,6 +795,293 @@ function releaseVectorBlend(group, newId) {
|
|
|
765
795
|
return [...keys, group.transform ? transformVectorNode(path, m) : path];
|
|
766
796
|
}
|
|
767
797
|
|
|
798
|
+
// src/vector/trace.ts
|
|
799
|
+
var VECTOR_TRACE_MAX_PIXELS = 4e6;
|
|
800
|
+
function signedArea(p) {
|
|
801
|
+
let a = 0;
|
|
802
|
+
for (let i = 0; i < p.length; i++) {
|
|
803
|
+
const [x0, y0] = p[i];
|
|
804
|
+
const [x1, y1] = p[(i + 1) % p.length];
|
|
805
|
+
a += x0 * y1 - x1 * y0;
|
|
806
|
+
}
|
|
807
|
+
return a / 2;
|
|
808
|
+
}
|
|
809
|
+
function pointInPoly(p, poly) {
|
|
810
|
+
let inside = false;
|
|
811
|
+
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
|
|
812
|
+
const [xi, yi] = poly[i];
|
|
813
|
+
const [xj, yj] = poly[j];
|
|
814
|
+
if (yi > p[1] !== yj > p[1] && p[0] < (xj - xi) * (p[1] - yi) / (yj - yi) + xi) inside = !inside;
|
|
815
|
+
}
|
|
816
|
+
return inside;
|
|
817
|
+
}
|
|
818
|
+
function vectorTraceContours(field, w, h, threshold) {
|
|
819
|
+
const gw = w + 2;
|
|
820
|
+
const gh = h + 2;
|
|
821
|
+
const val = (i, j) => i === 0 || j === 0 || i > w || j > h ? -Infinity : field[(j - 1) * w + (i - 1)];
|
|
822
|
+
const inside = (i, j) => val(i, j) >= threshold;
|
|
823
|
+
const pos = (i, j) => [i - 0.5, j - 0.5];
|
|
824
|
+
const cross2 = (a, b) => {
|
|
825
|
+
const va = val(a[0], a[1]);
|
|
826
|
+
const vb = val(b[0], b[1]);
|
|
827
|
+
let t;
|
|
828
|
+
if (Number.isFinite(va) && Number.isFinite(vb) && Math.abs(vb - va) > 1e-12) t = Math.min(1, Math.max(0, (threshold - va) / (vb - va)));
|
|
829
|
+
else if (Number.isFinite(va)) t = va >= threshold ? 0.5 : 0;
|
|
830
|
+
else t = vb >= threshold ? 0.5 : 1;
|
|
831
|
+
const pa = pos(a[0], a[1]);
|
|
832
|
+
const pb = pos(b[0], b[1]);
|
|
833
|
+
return [pa[0] + (pb[0] - pa[0]) * t, pa[1] + (pb[1] - pa[1]) * t];
|
|
834
|
+
};
|
|
835
|
+
const hid = (i, j) => 2 * (j * gw + i);
|
|
836
|
+
const vid = (i, j) => 2 * (j * gw + i) + 1;
|
|
837
|
+
const next = /* @__PURE__ */ new Map();
|
|
838
|
+
const point = /* @__PURE__ */ new Map();
|
|
839
|
+
for (let j = 0; j < gh - 1; j++) {
|
|
840
|
+
for (let i = 0; i < gw - 1; i++) {
|
|
841
|
+
const tl = inside(i, j);
|
|
842
|
+
const tr = inside(i + 1, j);
|
|
843
|
+
const br = inside(i + 1, j + 1);
|
|
844
|
+
const bl = inside(i, j + 1);
|
|
845
|
+
const code = +tl << 3 | +tr << 2 | +br << 1 | +bl;
|
|
846
|
+
if (code === 0 || code === 15) continue;
|
|
847
|
+
const top = hid(i, j);
|
|
848
|
+
const bottom = hid(i, j + 1);
|
|
849
|
+
const left = vid(i, j);
|
|
850
|
+
const right = vid(i + 1, j);
|
|
851
|
+
const seg = (a, b) => next.set(a, b);
|
|
852
|
+
const mean = () => (val(i, j) + val(i + 1, j) + val(i + 1, j + 1) + val(i, j + 1)) / 4;
|
|
853
|
+
switch (code) {
|
|
854
|
+
case 1:
|
|
855
|
+
seg(left, bottom);
|
|
856
|
+
break;
|
|
857
|
+
case 2:
|
|
858
|
+
seg(bottom, right);
|
|
859
|
+
break;
|
|
860
|
+
case 3:
|
|
861
|
+
seg(left, right);
|
|
862
|
+
break;
|
|
863
|
+
case 4:
|
|
864
|
+
seg(right, top);
|
|
865
|
+
break;
|
|
866
|
+
case 5:
|
|
867
|
+
if (mean() >= threshold) {
|
|
868
|
+
seg(left, top);
|
|
869
|
+
seg(right, bottom);
|
|
870
|
+
} else {
|
|
871
|
+
seg(left, bottom);
|
|
872
|
+
seg(right, top);
|
|
873
|
+
}
|
|
874
|
+
break;
|
|
875
|
+
case 6:
|
|
876
|
+
seg(bottom, top);
|
|
877
|
+
break;
|
|
878
|
+
case 7:
|
|
879
|
+
seg(left, top);
|
|
880
|
+
break;
|
|
881
|
+
case 8:
|
|
882
|
+
seg(top, left);
|
|
883
|
+
break;
|
|
884
|
+
case 9:
|
|
885
|
+
seg(top, bottom);
|
|
886
|
+
break;
|
|
887
|
+
case 10:
|
|
888
|
+
if (mean() >= threshold) {
|
|
889
|
+
seg(top, right);
|
|
890
|
+
seg(bottom, left);
|
|
891
|
+
} else {
|
|
892
|
+
seg(top, left);
|
|
893
|
+
seg(bottom, right);
|
|
894
|
+
}
|
|
895
|
+
break;
|
|
896
|
+
case 11:
|
|
897
|
+
seg(top, right);
|
|
898
|
+
break;
|
|
899
|
+
case 12:
|
|
900
|
+
seg(right, left);
|
|
901
|
+
break;
|
|
902
|
+
case 13:
|
|
903
|
+
seg(right, bottom);
|
|
904
|
+
break;
|
|
905
|
+
case 14:
|
|
906
|
+
seg(bottom, left);
|
|
907
|
+
break;
|
|
908
|
+
}
|
|
909
|
+
const edges = [
|
|
910
|
+
[top, [i, j], [i + 1, j]],
|
|
911
|
+
[bottom, [i, j + 1], [i + 1, j + 1]],
|
|
912
|
+
[left, [i, j], [i, j + 1]],
|
|
913
|
+
[right, [i + 1, j], [i + 1, j + 1]]
|
|
914
|
+
];
|
|
915
|
+
for (const [e, a, b] of edges) if (inside(a[0], a[1]) !== inside(b[0], b[1]) && !point.has(e)) point.set(e, cross2(a, b));
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
const out = [];
|
|
919
|
+
for (const start of [...next.keys()].sort((a, b) => a - b)) {
|
|
920
|
+
if (!next.has(start)) continue;
|
|
921
|
+
const poly = [];
|
|
922
|
+
let e = start;
|
|
923
|
+
for (; ; ) {
|
|
924
|
+
const n = next.get(e);
|
|
925
|
+
if (n === void 0) break;
|
|
926
|
+
next.delete(e);
|
|
927
|
+
const p = point.get(e);
|
|
928
|
+
if (p) poly.push(p);
|
|
929
|
+
e = n;
|
|
930
|
+
if (e === start) break;
|
|
931
|
+
}
|
|
932
|
+
if (poly.length >= 3) out.push(poly);
|
|
933
|
+
}
|
|
934
|
+
return out;
|
|
935
|
+
}
|
|
936
|
+
function loopsOf(field, w, h, threshold, tolerance, minArea) {
|
|
937
|
+
const loops = [];
|
|
938
|
+
for (const raw of vectorTraceContours(field, w, h, threshold)) {
|
|
939
|
+
const area = signedArea(raw);
|
|
940
|
+
if (Math.abs(area) < Math.max(0, minArea)) continue;
|
|
941
|
+
const simple = simplifyRing(raw, Math.max(0.01, tolerance));
|
|
942
|
+
if (simple.length < 3) continue;
|
|
943
|
+
loops.push({ raw, simple, area, depth: 0 });
|
|
944
|
+
}
|
|
945
|
+
for (const l of loops) l.depth = loops.filter((o) => o !== l && Math.abs(o.area) > Math.abs(l.area) && pointInPoly(l.simple[0], o.simple)).length;
|
|
946
|
+
return loops.sort((a, b) => a.depth - b.depth || Math.abs(b.area) - Math.abs(a.area));
|
|
947
|
+
}
|
|
948
|
+
function loopSubpath(l, tolerance, roundness) {
|
|
949
|
+
if (roundness <= 0) return { closed: true, anchors: l.simple.map(([x, y]) => ({ x, y })) };
|
|
950
|
+
const cornerAngleDeg = 25 + roundness * 125;
|
|
951
|
+
const cubics = fitCubics(l.raw, Math.max(0.05, tolerance), {
|
|
952
|
+
closed: true,
|
|
953
|
+
cornerAngleDeg
|
|
954
|
+
});
|
|
955
|
+
if (!cubics.length) return { closed: true, anchors: l.simple.map(([x, y]) => ({ x, y })) };
|
|
956
|
+
const anchors = cubics.map((c, k) => {
|
|
957
|
+
const prev = cubics[(k - 1 + cubics.length) % cubics.length];
|
|
958
|
+
return {
|
|
959
|
+
x: c.p0[0],
|
|
960
|
+
y: c.p0[1],
|
|
961
|
+
in: { x: prev.p2[0], y: prev.p2[1] },
|
|
962
|
+
out: { x: c.p1[0], y: c.p1[1] }
|
|
963
|
+
};
|
|
964
|
+
});
|
|
965
|
+
return { closed: true, anchors };
|
|
966
|
+
}
|
|
967
|
+
function traceField(field, w, h, threshold, o) {
|
|
968
|
+
const tolerance = o.tolerance ?? 1;
|
|
969
|
+
const roundness = Math.min(1, Math.max(0, o.cornerRoundness ?? 0.5));
|
|
970
|
+
return loopsOf(field, w, h, threshold, tolerance, o.minArea ?? 10).map((l) => loopSubpath(l, tolerance, roundness));
|
|
971
|
+
}
|
|
972
|
+
function quantizeVectorTraceColors(pixels, count) {
|
|
973
|
+
const n = pixels.width * pixels.height;
|
|
974
|
+
const d = pixels.data;
|
|
975
|
+
const opaque = [];
|
|
976
|
+
for (let i = 0; i < n; i++) if (d[i * 4 + 3] >= 128) opaque.push(i);
|
|
977
|
+
const boxes = opaque.length ? [opaque] : [];
|
|
978
|
+
const range = (box, c) => {
|
|
979
|
+
let lo = 255;
|
|
980
|
+
let hi = 0;
|
|
981
|
+
for (const i of box) {
|
|
982
|
+
const v = d[i * 4 + c];
|
|
983
|
+
if (v < lo) lo = v;
|
|
984
|
+
if (v > hi) hi = v;
|
|
985
|
+
}
|
|
986
|
+
return hi - lo;
|
|
987
|
+
};
|
|
988
|
+
while (boxes.length < count) {
|
|
989
|
+
let best = -1;
|
|
990
|
+
let bestRange = 0;
|
|
991
|
+
let bestC = 0;
|
|
992
|
+
boxes.forEach((b, k) => {
|
|
993
|
+
if (b.length < 2) return;
|
|
994
|
+
for (let c = 0; c < 3; c++) {
|
|
995
|
+
const r = range(b, c);
|
|
996
|
+
if (r > bestRange) {
|
|
997
|
+
bestRange = r;
|
|
998
|
+
best = k;
|
|
999
|
+
bestC = c;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
if (best < 0) break;
|
|
1004
|
+
const box = boxes[best].slice().sort((a, b) => d[a * 4 + bestC] - d[b * 4 + bestC]);
|
|
1005
|
+
const key = (k) => d[box[k] * 4 + bestC];
|
|
1006
|
+
const v = key(box.length >> 1);
|
|
1007
|
+
let mid = box.findIndex((_, k) => key(k) >= v);
|
|
1008
|
+
if (mid <= 0) mid = box.findIndex((_, k) => key(k) > v);
|
|
1009
|
+
boxes.splice(best, 1, box.slice(0, mid), box.slice(mid));
|
|
1010
|
+
}
|
|
1011
|
+
const palette = boxes.map((b) => {
|
|
1012
|
+
const s = [0, 0, 0];
|
|
1013
|
+
for (const i of b) for (let c = 0; c < 3; c++) s[c] += d[i * 4 + c];
|
|
1014
|
+
return [Math.round(s[0] / b.length), Math.round(s[1] / b.length), Math.round(s[2] / b.length)];
|
|
1015
|
+
});
|
|
1016
|
+
const index = new Int16Array(n).fill(-1);
|
|
1017
|
+
const counts = palette.map(() => 0);
|
|
1018
|
+
for (const i of opaque) {
|
|
1019
|
+
let best = 0;
|
|
1020
|
+
let bestD = Infinity;
|
|
1021
|
+
palette.forEach((p, k) => {
|
|
1022
|
+
const dd = (d[i * 4] - p[0]) ** 2 + (d[i * 4 + 1] - p[1]) ** 2 + (d[i * 4 + 2] - p[2]) ** 2;
|
|
1023
|
+
if (dd < bestD) {
|
|
1024
|
+
bestD = dd;
|
|
1025
|
+
best = k;
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
1028
|
+
index[i] = best;
|
|
1029
|
+
counts[best]++;
|
|
1030
|
+
}
|
|
1031
|
+
return { palette, index, counts };
|
|
1032
|
+
}
|
|
1033
|
+
var hex = (c) => `#${c.map((v) => Math.round(v).toString(16).padStart(2, "0")).join("")}`;
|
|
1034
|
+
function traceVectorImage(pixels, options = {}) {
|
|
1035
|
+
const { width: w, height: h, data } = pixels;
|
|
1036
|
+
if (!(w > 0 && h > 0) || data.length < w * h * 4) throw new Error("The image has no pixels (or fewer than width \xD7 height \xD7 4 values).");
|
|
1037
|
+
if (w * h > VECTOR_TRACE_MAX_PIXELS) throw new Error(`The image has ${w * h} pixels; scale it down to at most ${VECTOR_TRACE_MAX_PIXELS} to trace it.`);
|
|
1038
|
+
const n = w * h;
|
|
1039
|
+
if ((options.mode ?? "threshold") === "threshold") {
|
|
1040
|
+
const field = new Float32Array(n);
|
|
1041
|
+
const byAlpha = options.channel === "alpha";
|
|
1042
|
+
for (let i = 0; i < n; i++) {
|
|
1043
|
+
const a = data[i * 4 + 3] / 255;
|
|
1044
|
+
field[i] = byAlpha ? a : a * (1 - (0.2126 * data[i * 4] + 0.7152 * data[i * 4 + 1] + 0.0722 * data[i * 4 + 2]) / 255);
|
|
1045
|
+
}
|
|
1046
|
+
const subpaths = traceField(field, w, h, Math.min(1, Math.max(0, options.threshold ?? 0.5)), options);
|
|
1047
|
+
return subpaths.length ? [
|
|
1048
|
+
{
|
|
1049
|
+
id: "trace-1",
|
|
1050
|
+
type: "path",
|
|
1051
|
+
name: "Trace",
|
|
1052
|
+
fill: options.color ?? "#000000",
|
|
1053
|
+
stroke: null,
|
|
1054
|
+
fillRule: "evenodd",
|
|
1055
|
+
subpaths
|
|
1056
|
+
}
|
|
1057
|
+
] : [];
|
|
1058
|
+
}
|
|
1059
|
+
const count = Math.min(32, Math.max(2, Math.round(options.colors ?? 6)));
|
|
1060
|
+
const q = quantizeVectorTraceColors(pixels, count);
|
|
1061
|
+
const order = q.palette.map((_, k) => k).filter((k) => q.counts[k] > 0).sort((a, b) => q.counts[b] - q.counts[a]);
|
|
1062
|
+
const rank = new Map(order.map((k, r) => [k, r]));
|
|
1063
|
+
const out = [];
|
|
1064
|
+
order.forEach((k, r) => {
|
|
1065
|
+
const field = new Uint8Array(n);
|
|
1066
|
+
for (let i = 0; i < n; i++) {
|
|
1067
|
+
const ix = q.index[i];
|
|
1068
|
+
field[i] = ix >= 0 && rank.get(ix) >= r ? 1 : 0;
|
|
1069
|
+
}
|
|
1070
|
+
const subpaths = traceField(field, w, h, 0.5, options);
|
|
1071
|
+
if (subpaths.length)
|
|
1072
|
+
out.push({
|
|
1073
|
+
id: `trace-${out.length + 1}`,
|
|
1074
|
+
type: "path",
|
|
1075
|
+
name: hex(q.palette[k]),
|
|
1076
|
+
fill: hex(q.palette[k]),
|
|
1077
|
+
stroke: null,
|
|
1078
|
+
fillRule: "evenodd",
|
|
1079
|
+
subpaths
|
|
1080
|
+
});
|
|
1081
|
+
});
|
|
1082
|
+
return out;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
768
1085
|
// src/vector/constraints/lm.ts
|
|
769
1086
|
var MAX_ROWS = 800;
|
|
770
1087
|
var maxAbs = (v) => v.reduce((m, a) => Number.isFinite(a) ? Math.max(m, Math.abs(a)) : Infinity, 0);
|
|
@@ -4403,6 +4720,7 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
4403
4720
|
constraintIssues: this.constraintIssues,
|
|
4404
4721
|
trackingPoints: this.trackingPoints,
|
|
4405
4722
|
typedEntry: this.typed,
|
|
4723
|
+
shadingPoint: this.getShadingSelection(),
|
|
4406
4724
|
canUndo: h.canUndo,
|
|
4407
4725
|
canRedo: h.canRedo,
|
|
4408
4726
|
undoLabel: h.undoName,
|
|
@@ -5100,6 +5418,14 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
5100
5418
|
}
|
|
5101
5419
|
downGradient(screen, p, input) {
|
|
5102
5420
|
const before = this.snapshot();
|
|
5421
|
+
const shaded = this.shadedUnder(screen);
|
|
5422
|
+
if (shaded) {
|
|
5423
|
+
this.shadingSel = shaded.index >= 0 ? { nodeId: shaded.nodeId, index: shaded.index } : null;
|
|
5424
|
+
this.drag = { kind: "shading", nodeId: shaded.nodeId, index: shaded.index, start: screen, before, moved: false, alt: !!input.alt };
|
|
5425
|
+
this.interaction = { type: "gradient", part: "handle" };
|
|
5426
|
+
this.notify();
|
|
5427
|
+
return;
|
|
5428
|
+
}
|
|
5103
5429
|
const a = this.getGradientAnnotator();
|
|
5104
5430
|
let part = a ? hitVectorGradientAnnotator(a, screen) : null;
|
|
5105
5431
|
if (a && part) {
|
|
@@ -6698,6 +7024,306 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
6698
7024
|
return { ok: true, id };
|
|
6699
7025
|
}
|
|
6700
7026
|
// -----------------------------------------------------------------------------------------
|
|
7027
|
+
// Image tracing
|
|
7028
|
+
/**
|
|
7029
|
+
* Image Trace: traces RGBA `pixels` into paths (see `traceVectorImage`) and adds them as one
|
|
7030
|
+
* group, the image placed with its top-left at `x, y` (default 0, 0) and `width` wide (default:
|
|
7031
|
+
* one unit per pixel; the height follows). One undo step; selects the group. Returns its id and
|
|
7032
|
+
* the traced paths' ids, bottom to top.
|
|
7033
|
+
*/
|
|
7034
|
+
traceImage(pixels, options = {}) {
|
|
7035
|
+
if (options.threshold !== void 0 && !(options.threshold >= 0 && options.threshold <= 1)) return { ok: false, message: "threshold must be from 0 to 1." };
|
|
7036
|
+
if (options.colors !== void 0 && !(Number.isInteger(options.colors) && options.colors >= 2 && options.colors <= 32)) return { ok: false, message: "colors must be a whole number from 2 to 32." };
|
|
7037
|
+
if (options.tolerance !== void 0 && !(options.tolerance > 0)) return { ok: false, message: "tolerance must be above 0 (pixels)." };
|
|
7038
|
+
if (options.width !== void 0 && !(options.width > 0)) return { ok: false, message: "width must be above 0." };
|
|
7039
|
+
if (options.color !== void 0 && !parseVectorColor(options.color)) return { ok: false, message: `"${options.color}" is not a colour.` };
|
|
7040
|
+
let traced;
|
|
7041
|
+
try {
|
|
7042
|
+
traced = traceVectorImage(pixels, options);
|
|
7043
|
+
} catch (e) {
|
|
7044
|
+
return { ok: false, message: e.message };
|
|
7045
|
+
}
|
|
7046
|
+
if (!traced.length) return { ok: false, message: "Nothing to trace: no shape reached the threshold (try another threshold, the alpha channel, or colour mode)." };
|
|
7047
|
+
const k = (options.width ?? pixels.width) / pixels.width;
|
|
7048
|
+
const m = [k, 0, 0, k, options.x ?? 0, options.y ?? 0];
|
|
7049
|
+
const children = traced.map((n) => ({ ...transformVectorNode(n, m), id: this.newId() }));
|
|
7050
|
+
const id = this.newId();
|
|
7051
|
+
this.change("Image Trace", () => {
|
|
7052
|
+
this.setDoc([...this.doc.nodes, { id, type: "group", name: options.name ?? "Image Trace", children }]);
|
|
7053
|
+
this.selection = [id];
|
|
7054
|
+
this.anchors = [];
|
|
7055
|
+
this.editingId = null;
|
|
7056
|
+
});
|
|
7057
|
+
return { ok: true, id, ids: children.map((c) => c.id) };
|
|
7058
|
+
}
|
|
7059
|
+
// -----------------------------------------------------------------------------------------
|
|
7060
|
+
// Gradient meshes and freeform gradients
|
|
7061
|
+
/** The selected point of a mesh or freeform fill (Gradient tool), or null. */
|
|
7062
|
+
shadingSel = null;
|
|
7063
|
+
/** Selected paths with a mesh or freeform fill. */
|
|
7064
|
+
shadedPaths() {
|
|
7065
|
+
return this.gradientPaths().filter((n) => isVectorShadedPaint(n.fill));
|
|
7066
|
+
}
|
|
7067
|
+
/** The shaded fill's point under `screen` (index), or a freeform shape under it (index -1), or null. */
|
|
7068
|
+
shadedUnder(screen) {
|
|
7069
|
+
const p = this.screenToDocument(screen);
|
|
7070
|
+
for (const n of this.shadedPaths()) {
|
|
7071
|
+
const world = this.worldOf(n.id);
|
|
7072
|
+
const fill = n.fill;
|
|
7073
|
+
const z = this.viewport.zoom;
|
|
7074
|
+
let best = -1;
|
|
7075
|
+
let bestD = 8;
|
|
7076
|
+
fill.points.forEach((q, k) => {
|
|
7077
|
+
const w = applyMatrix(world, q);
|
|
7078
|
+
const d = Math.hypot(w.x - p.x, w.y - p.y) * z;
|
|
7079
|
+
if (d <= bestD) {
|
|
7080
|
+
best = k;
|
|
7081
|
+
bestD = d;
|
|
7082
|
+
}
|
|
7083
|
+
});
|
|
7084
|
+
if (best >= 0) return { nodeId: n.id, index: best };
|
|
7085
|
+
if (isVectorFreeformGradient(fill) && hitTestVectorNode(n, p, 0, this.parentMatrixOf(n.id))) return { nodeId: n.id, index: -1 };
|
|
7086
|
+
}
|
|
7087
|
+
return null;
|
|
7088
|
+
}
|
|
7089
|
+
parentMatrixOf(id) {
|
|
7090
|
+
return vectorParentMatrix(this.doc.nodes, id) ?? IDENTITY_MATRIX;
|
|
7091
|
+
}
|
|
7092
|
+
moveShading(drag, screen, p) {
|
|
7093
|
+
if (drag.index < 0) return;
|
|
7094
|
+
if (!drag.moved && Math.hypot(screen.x - drag.start.x, screen.y - drag.start.y) < 3) return;
|
|
7095
|
+
drag.moved = true;
|
|
7096
|
+
const node = this.getNode(drag.nodeId);
|
|
7097
|
+
if (!node || !isVectorShadedPaint(node.fill)) return;
|
|
7098
|
+
const local = this.toLocal(drag.nodeId, p);
|
|
7099
|
+
const fill = mapVectorShadedPaint(node.fill, (q) => q);
|
|
7100
|
+
const moved = isVectorMeshGradient(fill) ? { ...fill, points: fill.points.map((q, k) => k === drag.index ? { x: local.x, y: local.y } : q) } : { ...fill, points: fill.points.map((q, k) => k === drag.index ? { ...q, x: local.x, y: local.y } : q) };
|
|
7101
|
+
this.replaceNode(drag.nodeId, normalizeVectorPaintStacks({ ...node, fill: moved }, "shorthand"));
|
|
7102
|
+
this.notify(true);
|
|
7103
|
+
}
|
|
7104
|
+
upShading(drag) {
|
|
7105
|
+
if (drag.moved) return this.commit(this.getNode(drag.nodeId) && isVectorMeshGradient(this.getNode(drag.nodeId).fill) ? "Move Mesh Point" : "Move Freeform Point", drag.before);
|
|
7106
|
+
const at = this.screenToDocument(drag.start);
|
|
7107
|
+
if (drag.index < 0) {
|
|
7108
|
+
const r = this.addFreeformPoint(drag.nodeId, at);
|
|
7109
|
+
if (r.ok) {
|
|
7110
|
+
this.shadingSel = { nodeId: drag.nodeId, index: r.index };
|
|
7111
|
+
this.notify();
|
|
7112
|
+
}
|
|
7113
|
+
return;
|
|
7114
|
+
}
|
|
7115
|
+
if (drag.alt && isVectorFreeformGradient(this.getNode(drag.nodeId).fill)) {
|
|
7116
|
+
if (this.removeFreeformPoint(drag.nodeId, drag.index).ok) {
|
|
7117
|
+
this.shadingSel = null;
|
|
7118
|
+
this.notify();
|
|
7119
|
+
}
|
|
7120
|
+
return;
|
|
7121
|
+
}
|
|
7122
|
+
this.notify();
|
|
7123
|
+
}
|
|
7124
|
+
/** The selected point of a mesh or freeform fill (Gradient tool), with its paint's kind, or null. */
|
|
7125
|
+
getShadingSelection() {
|
|
7126
|
+
const sel = this.shadingSel;
|
|
7127
|
+
const node = sel ? this.getNode(sel.nodeId) : void 0;
|
|
7128
|
+
if (!sel || node?.type !== "path" || !isVectorShadedPaint(node.fill) || sel.index >= node.fill.points.length) return null;
|
|
7129
|
+
return { ...sel, kind: node.fill.type };
|
|
7130
|
+
}
|
|
7131
|
+
/** The path `id` (or a message why it cannot take a mesh or freeform fill). */
|
|
7132
|
+
shadingTarget(id) {
|
|
7133
|
+
const n = this.getNode(id);
|
|
7134
|
+
if (!n) return `There is no node "${id}".`;
|
|
7135
|
+
if (n.type !== "path") return `"${id}" is a ${n.type}: meshes and freeform gradients fill paths.`;
|
|
7136
|
+
if (n.locked) return `"${id}" is locked; unlock it first.`;
|
|
7137
|
+
return n;
|
|
7138
|
+
}
|
|
7139
|
+
/** Sets each node's fill in one undo step. */
|
|
7140
|
+
setFills(label, fills, merge) {
|
|
7141
|
+
this.change(
|
|
7142
|
+
label,
|
|
7143
|
+
() => {
|
|
7144
|
+
this.setDoc(mapTree(this.doc.nodes, (n) => fills.has(n.id) && n.type === "path" ? normalizeVectorPaintStacks({ ...n, fill: fills.get(n.id) }, "shorthand") : n));
|
|
7145
|
+
},
|
|
7146
|
+
merge
|
|
7147
|
+
);
|
|
7148
|
+
}
|
|
7149
|
+
/**
|
|
7150
|
+
* Gives paths (default: the selection) a gradient mesh over their own bounds: `rows` ×
|
|
7151
|
+
* `columns` patches (1–32) with points evenly spread, coloured by `colors` (one per point, row by
|
|
7152
|
+
* row), the four `corners` (top-left, top-right, bottom-right, bottom-left) blended, or the
|
|
7153
|
+
* current fill colour. One undo step.
|
|
7154
|
+
*/
|
|
7155
|
+
setMeshGradient(input, options = {}) {
|
|
7156
|
+
const { rows, columns } = input;
|
|
7157
|
+
if (!Number.isInteger(rows) || !Number.isInteger(columns) || rows < 1 || columns < 1 || rows > VECTOR_MESH_MAX || columns > VECTOR_MESH_MAX)
|
|
7158
|
+
return { ok: false, message: `rows and columns must be whole numbers from 1 to ${VECTOR_MESH_MAX}.` };
|
|
7159
|
+
const n = (rows + 1) * (columns + 1);
|
|
7160
|
+
if (input.colors && input.colors.length !== n) return { ok: false, message: `colors needs ${n} colours ((rows + 1) \xD7 (columns + 1), row by row); got ${input.colors.length}.` };
|
|
7161
|
+
if (input.corners && input.corners.length !== 4) return { ok: false, message: "corners needs four colours: top-left, top-right, bottom-right, bottom-left." };
|
|
7162
|
+
const bad = [...input.colors ?? [], ...input.corners ?? []].find((c) => !parseVectorColor(c));
|
|
7163
|
+
if (bad !== void 0) return { ok: false, message: `"${bad}" is not a colour.` };
|
|
7164
|
+
const ids = options.ids ?? this.selection;
|
|
7165
|
+
if (!ids.length) return { ok: false, message: "Select a path first." };
|
|
7166
|
+
const fills = /* @__PURE__ */ new Map();
|
|
7167
|
+
for (const id of ids) {
|
|
7168
|
+
const t = this.shadingTarget(id);
|
|
7169
|
+
if (typeof t === "string") return { ok: false, message: t };
|
|
7170
|
+
const b = vectorPathLocalBounds(t);
|
|
7171
|
+
if (!b || !(b.width > 0 && b.height > 0)) return { ok: false, message: `"${id}" has no area to fill.` };
|
|
7172
|
+
const base = typeof t.fill === "string" && parseVectorColor(t.fill) ? t.fill : "#ffffff";
|
|
7173
|
+
fills.set(id, createVectorMeshGradient(b, rows, columns, input.colors ? { colors: input.colors } : input.corners ? { corners: input.corners } : { colors: Array.from({ length: n }, () => base) }));
|
|
7174
|
+
}
|
|
7175
|
+
this.setFills("Gradient Mesh", fills);
|
|
7176
|
+
return { ok: true };
|
|
7177
|
+
}
|
|
7178
|
+
/**
|
|
7179
|
+
* Gives paths (default: the selection) a freeform gradient: `points` in document units (each
|
|
7180
|
+
* `{ x, y, color, opacity?, spread? }`) with optional `lines` through them, or, without points,
|
|
7181
|
+
* four or more points placed inside each path and coloured along `colors` (default the current
|
|
7182
|
+
* fill to white). One undo step.
|
|
7183
|
+
*/
|
|
7184
|
+
setFreeformGradient(input = {}, options = {}) {
|
|
7185
|
+
const ids = options.ids ?? this.selection;
|
|
7186
|
+
if (!ids.length) return { ok: false, message: "Select a path first." };
|
|
7187
|
+
const fills = /* @__PURE__ */ new Map();
|
|
7188
|
+
for (const id of ids) {
|
|
7189
|
+
const t = this.shadingTarget(id);
|
|
7190
|
+
if (typeof t === "string") return { ok: false, message: t };
|
|
7191
|
+
const b = vectorPathLocalBounds(t);
|
|
7192
|
+
if (!b) return { ok: false, message: `"${id}" has no area to fill.` };
|
|
7193
|
+
let f;
|
|
7194
|
+
if (input.points?.length) {
|
|
7195
|
+
f = sanitizeVectorShadedPaint({ type: "freeform", points: input.points.map((q) => ({ ...q, ...this.toLocal(id, q) })), ...input.lines ? { lines: input.lines } : {} });
|
|
7196
|
+
if (!f) return { ok: false, message: "Each point needs x, y (numbers) and a colour." };
|
|
7197
|
+
if (input.lines && (f.lines?.length ?? 0) !== input.lines.length) return { ok: false, message: `Each line needs two or more point indices from 0 to ${input.points.length - 1}, none twice in a row.` };
|
|
7198
|
+
} else {
|
|
7199
|
+
const bad = (input.colors ?? []).find((c) => !parseVectorColor(c));
|
|
7200
|
+
if (bad !== void 0) return { ok: false, message: `"${bad}" is not a colour.` };
|
|
7201
|
+
const outline = subpathsToBezier(t.subpaths);
|
|
7202
|
+
const inside = (q) => pointInBezierPath(outline, [q.x, q.y], t.fillRule ?? "nonzero");
|
|
7203
|
+
const base = typeof t.fill === "string" && parseVectorColor(t.fill) ? t.fill : "#000000";
|
|
7204
|
+
f = autoVectorFreeformGradient(b, input.colors?.length ? input.colors : [base, "#ffffff"], inside);
|
|
7205
|
+
}
|
|
7206
|
+
fills.set(id, f);
|
|
7207
|
+
}
|
|
7208
|
+
this.setFills("Freeform Gradient", fills);
|
|
7209
|
+
return { ok: true };
|
|
7210
|
+
}
|
|
7211
|
+
/** The mesh or freeform fill of path `id` with its points in document units, or null. */
|
|
7212
|
+
getShading(id) {
|
|
7213
|
+
const n = this.getNode(id);
|
|
7214
|
+
if (n?.type !== "path" || !isVectorShadedPaint(n.fill)) return null;
|
|
7215
|
+
const world = this.worldOf(id);
|
|
7216
|
+
return mapVectorShadedPaint(n.fill, (q) => applyMatrix(world, q));
|
|
7217
|
+
}
|
|
7218
|
+
/** Checks a shading point edit: the path, its shaded fill and the index. */
|
|
7219
|
+
shadingPoint(id, index, kind) {
|
|
7220
|
+
const t = this.shadingTarget(id);
|
|
7221
|
+
if (typeof t === "string") return t;
|
|
7222
|
+
const paint = t.fill;
|
|
7223
|
+
if (kind === "mesh" ? !isVectorMeshGradient(paint) : !isVectorFreeformGradient(paint))
|
|
7224
|
+
return `"${id}" has no ${kind === "mesh" ? "gradient mesh" : "freeform gradient"} fill (add one with ${kind === "mesh" ? "vector.setMeshGradient" : "vector.setFreeformGradient"}).`;
|
|
7225
|
+
const p = paint;
|
|
7226
|
+
if (!Number.isInteger(index) || index < 0 || index >= p.points.length) return `Point ${index} is out of range: the ${kind} has ${p.points.length} points (0 to ${p.points.length - 1}).`;
|
|
7227
|
+
return { node: t, paint: p };
|
|
7228
|
+
}
|
|
7229
|
+
/**
|
|
7230
|
+
* Changes point `index` (row by row) of path `id`'s gradient mesh: moves it to `x, y` (document
|
|
7231
|
+
* units) and / or sets its `color` and `opacity`. One undo step (merged while `merge` is the same key).
|
|
7232
|
+
*/
|
|
7233
|
+
setMeshPoint(id, index, patch, options = {}) {
|
|
7234
|
+
const r = this.shadingPoint(id, index, "mesh");
|
|
7235
|
+
if (typeof r === "string") return { ok: false, message: r };
|
|
7236
|
+
if (patch.color !== void 0 && !parseVectorColor(patch.color)) return { ok: false, message: `"${patch.color}" is not a colour.` };
|
|
7237
|
+
if (patch.x === void 0 !== (patch.y === void 0)) return { ok: false, message: "Give both x and y to move the point." };
|
|
7238
|
+
const m = r.paint;
|
|
7239
|
+
const at = patch.x !== void 0 ? this.toLocal(id, { x: patch.x, y: patch.y }) : m.points[index];
|
|
7240
|
+
const next = {
|
|
7241
|
+
...m,
|
|
7242
|
+
points: m.points.map((q, k) => k === index ? { x: at.x, y: at.y } : q),
|
|
7243
|
+
colors: m.colors.map((c, k) => k === index && patch.color !== void 0 ? patch.color : c)
|
|
7244
|
+
};
|
|
7245
|
+
if (patch.opacity !== void 0) {
|
|
7246
|
+
const ops = m.opacities ?? m.points.map(() => 1);
|
|
7247
|
+
next.opacities = ops.map((o, k) => k === index ? Math.min(1, Math.max(0, patch.opacity)) : o);
|
|
7248
|
+
}
|
|
7249
|
+
this.setFills("Edit Mesh Point", /* @__PURE__ */ new Map([[id, next]]), options.merge);
|
|
7250
|
+
return { ok: true };
|
|
7251
|
+
}
|
|
7252
|
+
/** Adds a point to path `id`'s freeform gradient at `x, y` (document units), coloured `color` (default: the colour there now). One undo step. Returns its index. */
|
|
7253
|
+
addFreeformPoint(id, point) {
|
|
7254
|
+
const t = this.shadingTarget(id);
|
|
7255
|
+
if (typeof t === "string") return { ok: false, message: t };
|
|
7256
|
+
if (!isVectorFreeformGradient(t.fill)) return { ok: false, message: `"${id}" has no freeform gradient fill (add one with vector.setFreeformGradient).` };
|
|
7257
|
+
if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) return { ok: false, message: "x and y must be numbers." };
|
|
7258
|
+
if (point.color !== void 0 && !parseVectorColor(point.color)) return { ok: false, message: `"${point.color}" is not a colour.` };
|
|
7259
|
+
const local = this.toLocal(id, point);
|
|
7260
|
+
const f = t.fill;
|
|
7261
|
+
const b = vectorPathLocalBounds(t);
|
|
7262
|
+
const here = sampleVectorFreeform(vectorFreeformField(f, b ? vectorFreeformSpreadScale(b) : 1), local);
|
|
7263
|
+
const next = {
|
|
7264
|
+
...f,
|
|
7265
|
+
points: [
|
|
7266
|
+
...f.points,
|
|
7267
|
+
{
|
|
7268
|
+
x: local.x,
|
|
7269
|
+
y: local.y,
|
|
7270
|
+
color: point.color ?? vectorRgbaToCss([here[0], here[1], here[2], 1]),
|
|
7271
|
+
...point.opacity !== void 0 && point.opacity < 1 ? { opacity: Math.max(0, point.opacity) } : {},
|
|
7272
|
+
...point.spread ? { spread: Math.min(1, Math.max(0, point.spread)) } : {}
|
|
7273
|
+
}
|
|
7274
|
+
]
|
|
7275
|
+
};
|
|
7276
|
+
this.setFills("Add Freeform Point", /* @__PURE__ */ new Map([[id, next]]));
|
|
7277
|
+
return { ok: true, index: next.points.length - 1 };
|
|
7278
|
+
}
|
|
7279
|
+
/** Changes point `index` of path `id`'s freeform gradient: position (document units), colour, opacity, spread. One undo step (merged while `merge` is the same key). */
|
|
7280
|
+
setFreeformPoint(id, index, patch, options = {}) {
|
|
7281
|
+
const r = this.shadingPoint(id, index, "freeform");
|
|
7282
|
+
if (typeof r === "string") return { ok: false, message: r };
|
|
7283
|
+
if (patch.color !== void 0 && !parseVectorColor(patch.color)) return { ok: false, message: `"${patch.color}" is not a colour.` };
|
|
7284
|
+
if (patch.x === void 0 !== (patch.y === void 0)) return { ok: false, message: "Give both x and y to move the point." };
|
|
7285
|
+
const f = r.paint;
|
|
7286
|
+
const at = patch.x !== void 0 ? this.toLocal(id, { x: patch.x, y: patch.y }) : null;
|
|
7287
|
+
const next = {
|
|
7288
|
+
...f,
|
|
7289
|
+
points: f.points.map((q, k) => {
|
|
7290
|
+
if (k !== index) return q;
|
|
7291
|
+
const out = { ...q, ...at ? { x: at.x, y: at.y } : {}, ...patch.color !== void 0 ? { color: patch.color } : {} };
|
|
7292
|
+
if (patch.opacity !== void 0) {
|
|
7293
|
+
if (patch.opacity >= 1) delete out.opacity;
|
|
7294
|
+
else out.opacity = Math.max(0, patch.opacity);
|
|
7295
|
+
}
|
|
7296
|
+
if (patch.spread !== void 0) {
|
|
7297
|
+
if (patch.spread <= 0) delete out.spread;
|
|
7298
|
+
else out.spread = Math.min(1, patch.spread);
|
|
7299
|
+
}
|
|
7300
|
+
return out;
|
|
7301
|
+
})
|
|
7302
|
+
};
|
|
7303
|
+
this.setFills("Edit Freeform Point", /* @__PURE__ */ new Map([[id, next]]), options.merge);
|
|
7304
|
+
return { ok: true };
|
|
7305
|
+
}
|
|
7306
|
+
/** Removes point `index` of path `id`'s freeform gradient (never the last one); lines through it close up. One undo step. */
|
|
7307
|
+
removeFreeformPoint(id, index) {
|
|
7308
|
+
const r = this.shadingPoint(id, index, "freeform");
|
|
7309
|
+
if (typeof r === "string") return { ok: false, message: r };
|
|
7310
|
+
const next = removeVectorFreeformPoint(r.paint, index);
|
|
7311
|
+
if (!next) return { ok: false, message: "A freeform gradient keeps at least one point." };
|
|
7312
|
+
this.setFills("Remove Freeform Point", /* @__PURE__ */ new Map([[id, next]]));
|
|
7313
|
+
return { ok: true };
|
|
7314
|
+
}
|
|
7315
|
+
/** Replaces the lines of path `id`'s freeform gradient: each a list of two or more point indices it runs through. One undo step. */
|
|
7316
|
+
setFreeformLines(id, lines) {
|
|
7317
|
+
const t = this.shadingTarget(id);
|
|
7318
|
+
if (typeof t === "string") return { ok: false, message: t };
|
|
7319
|
+
if (!isVectorFreeformGradient(t.fill)) return { ok: false, message: `"${id}" has no freeform gradient fill (add one with vector.setFreeformGradient).` };
|
|
7320
|
+
const f = sanitizeVectorShadedPaint({ ...t.fill, lines });
|
|
7321
|
+
if ((f.lines?.length ?? 0) !== lines.length) return { ok: false, message: `Each line needs two or more point indices from 0 to ${t.fill.points.length - 1}, none twice in a row.` };
|
|
7322
|
+
const { lines: _l, ...rest } = t.fill;
|
|
7323
|
+
this.setFills("Freeform Lines", /* @__PURE__ */ new Map([[id, lines.length ? { ...rest, lines: f.lines } : rest]]));
|
|
7324
|
+
return { ok: true };
|
|
7325
|
+
}
|
|
7326
|
+
// -----------------------------------------------------------------------------------------
|
|
6701
7327
|
// Object snap tracking and typed entry (after cadcraft's snap/tracking.rs and its direct
|
|
6702
7328
|
// distance entry)
|
|
6703
7329
|
trackingPoints = [];
|
|
@@ -8914,6 +9540,8 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
8914
9540
|
return;
|
|
8915
9541
|
case "gradient":
|
|
8916
9542
|
return this.moveGradient(drag, screen, p, input);
|
|
9543
|
+
case "shading":
|
|
9544
|
+
return this.moveShading(drag, screen, p);
|
|
8917
9545
|
case "envelope":
|
|
8918
9546
|
return this.moveEnvelope(drag, p);
|
|
8919
9547
|
case "move": {
|
|
@@ -9316,6 +9944,8 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
9316
9944
|
return this.upBuilder(drag);
|
|
9317
9945
|
case "gradient":
|
|
9318
9946
|
return this.upGradient(drag);
|
|
9947
|
+
case "shading":
|
|
9948
|
+
return this.upShading(drag);
|
|
9319
9949
|
case "stroke": {
|
|
9320
9950
|
this.notify();
|
|
9321
9951
|
if (drag.tool === "knife") this.knife(drag.points);
|
|
@@ -11109,6 +11739,40 @@ function outlinePaths(node, parent, entered) {
|
|
|
11109
11739
|
if ((hasOwnVectorAppearance(node) || isVectorMaskActive(node)) && !entered.has(node.id)) return [transformBezierPath(vectorAppearanceOutline(node), m)];
|
|
11110
11740
|
return node.children.filter((c) => c.visible !== false).flatMap((c) => outlinePaths(c, m, entered));
|
|
11111
11741
|
}
|
|
11742
|
+
function shadingOverlay(state) {
|
|
11743
|
+
if (state.tool !== "gradient") return [];
|
|
11744
|
+
const out = [];
|
|
11745
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11746
|
+
for (const id of state.selection) {
|
|
11747
|
+
const root = findVectorNode(state.document.nodes, id);
|
|
11748
|
+
if (!root) continue;
|
|
11749
|
+
for (const n of collectPathNodes(root)) {
|
|
11750
|
+
if (seen.has(n.id) || !isVectorShadedPaint(n.fill)) continue;
|
|
11751
|
+
seen.add(n.id);
|
|
11752
|
+
const world = vectorWorldMatrix(state.document.nodes, n.id);
|
|
11753
|
+
if (!world) continue;
|
|
11754
|
+
const sc = (q) => toScreen(state, applyMatrix(world, q));
|
|
11755
|
+
const curve = (c) => {
|
|
11756
|
+
const [a, b, d, e] = c.map(sc);
|
|
11757
|
+
return `M${fmt2(a.x)} ${fmt2(a.y)}C${fmt2(b.x)} ${fmt2(b.y)} ${fmt2(d.x)} ${fmt2(d.y)} ${fmt2(e.x)} ${fmt2(e.y)}`;
|
|
11758
|
+
};
|
|
11759
|
+
const fill = n.fill;
|
|
11760
|
+
const lines = [];
|
|
11761
|
+
if (isVectorMeshGradient(fill)) {
|
|
11762
|
+
const l = vectorMeshLines(fill);
|
|
11763
|
+
for (const row of [...l.rows, ...l.columns]) lines.push(row.map(curve).join(""));
|
|
11764
|
+
} else (fill.lines ?? []).forEach((_, k) => lines.push(vectorFreeformLineSegments(fill, k).map(curve).join("")));
|
|
11765
|
+
const sel = state.shadingPoint && state.shadingPoint.nodeId === n.id ? state.shadingPoint.index : -1;
|
|
11766
|
+
out.push({
|
|
11767
|
+
nodeId: n.id,
|
|
11768
|
+
kind: fill.type,
|
|
11769
|
+
lines,
|
|
11770
|
+
points: fill.points.map((q, index) => ({ index, ...sc(q), color: isVectorMeshGradient(fill) ? fill.colors[index] : fill.points[index].color, selected: index === sel }))
|
|
11771
|
+
});
|
|
11772
|
+
}
|
|
11773
|
+
}
|
|
11774
|
+
return out;
|
|
11775
|
+
}
|
|
11112
11776
|
function connectorSiteOverlay(state) {
|
|
11113
11777
|
if (state.tool !== "connector" || !state.pointer) return [];
|
|
11114
11778
|
const drawing = state.interaction?.type === "draw" && state.interaction.tool === "connector" ? state.interaction.nodeId : void 0;
|
|
@@ -11380,6 +12044,7 @@ function getVectorOverlayRenderData(state) {
|
|
|
11380
12044
|
constraints: constraintOverlay(state),
|
|
11381
12045
|
frames: frameLabels(state),
|
|
11382
12046
|
connectorSites: connectorSiteOverlay(state),
|
|
12047
|
+
shading: shadingOverlay(state),
|
|
11383
12048
|
trackingPoints: (state.trackingPoints ?? []).map((p) => toScreen(state, p)),
|
|
11384
12049
|
typedEntry: state.typedEntry ? { text: state.typedEntry, ...state.pointer ? toScreen(state, state.pointer) : { x: 0, y: 0 } } : null
|
|
11385
12050
|
};
|
|
@@ -11687,8 +12352,8 @@ function vectorPaintToHex(paint) {
|
|
|
11687
12352
|
if (typeof paint !== "string") return null;
|
|
11688
12353
|
const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(paint.trim());
|
|
11689
12354
|
if (!m) return null;
|
|
11690
|
-
const
|
|
11691
|
-
return `#${
|
|
12355
|
+
const hex2 = m[1].length === 3 ? m[1].replace(/./g, (c) => c + c) : m[1];
|
|
12356
|
+
return `#${hex2.toLowerCase()}`;
|
|
11692
12357
|
}
|
|
11693
12358
|
var VECTOR_GRADIENT_TYPE_LABELS = { linear: "Linear", radial: "Radial", conic: "Conic", reflected: "Reflected", diamond: "Diamond" };
|
|
11694
12359
|
var VECTOR_GRADIENT_SPREAD_LABELS = { pad: "Pad", reflect: "Reflect", repeat: "Repeat" };
|
|
@@ -11975,6 +12640,8 @@ var STYLE_PROPS = {
|
|
|
11975
12640
|
fillRule: { type: "string", enum: ["nonzero", "evenodd"], description: "Which areas count as inside (default nonzero)." }
|
|
11976
12641
|
};
|
|
11977
12642
|
var checkPaint = (paint, where) => {
|
|
12643
|
+
if (paint && typeof paint === "object" && (paint.type === "mesh" || paint.type === "freeform"))
|
|
12644
|
+
return sanitizeVectorShadedPaint(paint) ?? fail(`${where} is not a valid mesh or freeform gradient (see vector.setMeshGradient / vector.setFreeformGradient).`);
|
|
11978
12645
|
if (paint && typeof paint === "object") return sanitizeVectorGradient(paint) ?? fail(`${where} is not a valid gradient: give a type and at least one stop { offset, color }.`);
|
|
11979
12646
|
return paint;
|
|
11980
12647
|
};
|
|
@@ -12928,6 +13595,235 @@ function createVectorCommands(controller, options = {}) {
|
|
|
12928
13595
|
issues: state.constraintIssues
|
|
12929
13596
|
};
|
|
12930
13597
|
};
|
|
13598
|
+
const traceCommands = () => [
|
|
13599
|
+
{
|
|
13600
|
+
...base,
|
|
13601
|
+
id: `${prefix}traceImage`,
|
|
13602
|
+
label: "Image Trace\u2026",
|
|
13603
|
+
keywords: ["trace", "image trace", "live trace", "vectorize", "bitmap to vector", "png to svg", "autotrace", "posterize"],
|
|
13604
|
+
description: "Traces a PNG (base64 data URL) into vector paths and adds them as one group: `threshold` mode makes one even-odd path where the image is dark (or opaque with channel alpha); `color` mode makes a stacked path per colour (median-cut to `colors`, the most common at the bottom). Placed with its top-left at x, y and `width` wide (default one unit per pixel). One undo step. Returns { id, paths: [{ id, name, fill, subpaths }] }.",
|
|
13605
|
+
params: {
|
|
13606
|
+
type: "object",
|
|
13607
|
+
properties: {
|
|
13608
|
+
image: { type: "string", description: "The image as a data:image/png;base64,\u2026 URL (up to 4 million pixels)." },
|
|
13609
|
+
mode: { type: "string", enum: ["threshold", "color"], description: "threshold (one colour, default) or color (a path per colour)." },
|
|
13610
|
+
channel: { type: "string", enum: ["luminance", "alpha"], description: "Threshold mode: trace dark pixels (luminance, default) or opaque ones (alpha)." },
|
|
13611
|
+
threshold: { type: "number", minimum: 0, maximum: 1, description: "Threshold mode: inside where darkness (or alpha) reaches this (default 0.5)." },
|
|
13612
|
+
color: { type: "string", description: "Threshold mode: the fill (default black)." },
|
|
13613
|
+
colors: { type: "integer", minimum: 2, maximum: 32, description: "Colour mode: how many colours (default 6)." },
|
|
13614
|
+
tolerance: { type: "number", exclusiveMinimum: 0, description: "How far the outline may stray from the pixels, in pixels (default 1; larger is simpler)." },
|
|
13615
|
+
minArea: { type: "number", minimum: 0, description: "Drop shapes smaller than this many square pixels (default 10)." },
|
|
13616
|
+
cornerRoundness: { type: "number", minimum: 0, maximum: 1, description: "0: polygons with sharp corners; 1: smooth curves (default 0.5)." },
|
|
13617
|
+
x: { type: "number", description: "Left edge in document units (default 0)." },
|
|
13618
|
+
y: { type: "number", description: "Top edge in document units (default 0)." },
|
|
13619
|
+
width: { type: "number", exclusiveMinimum: 0, description: "Width in document units (default: the image width in pixels)." },
|
|
13620
|
+
name: { type: "string", description: 'Group name (default "Image Trace").' }
|
|
13621
|
+
},
|
|
13622
|
+
required: ["image"],
|
|
13623
|
+
additionalProperties: false
|
|
13624
|
+
},
|
|
13625
|
+
undoable: true,
|
|
13626
|
+
run: (_ctx, params) => {
|
|
13627
|
+
const { image, ...options2 } = params;
|
|
13628
|
+
const parsed = parseDataUrl(image);
|
|
13629
|
+
if (!parsed || !isPng(parsed.bytes)) fail("image must be a PNG data URL (data:image/png;base64,\u2026).");
|
|
13630
|
+
let decoded;
|
|
13631
|
+
try {
|
|
13632
|
+
decoded = decodePng(parsed.bytes, { maxPixels: VECTOR_TRACE_MAX_PIXELS });
|
|
13633
|
+
} catch (e) {
|
|
13634
|
+
return fail(`The PNG could not be read: ${e.message}`);
|
|
13635
|
+
}
|
|
13636
|
+
const r = controller.traceImage(decoded, options2);
|
|
13637
|
+
check(r);
|
|
13638
|
+
return {
|
|
13639
|
+
id: r.id,
|
|
13640
|
+
paths: r.ids.map((id) => {
|
|
13641
|
+
const n = controller.getNode(id);
|
|
13642
|
+
return { id, name: n.name, fill: n.fill, subpaths: n.subpaths.length };
|
|
13643
|
+
})
|
|
13644
|
+
};
|
|
13645
|
+
}
|
|
13646
|
+
}
|
|
13647
|
+
];
|
|
13648
|
+
const shadingResult = (id) => ({ id, shading: controller.getShading(id) ?? fail(`"${id}" has no mesh or freeform fill.`) });
|
|
13649
|
+
const COLOR = { type: "string", description: "A CSS colour." };
|
|
13650
|
+
const FREEFORM_POINT = {
|
|
13651
|
+
type: "object",
|
|
13652
|
+
properties: {
|
|
13653
|
+
x: { type: "number", description: "Document units." },
|
|
13654
|
+
y: { type: "number", description: "Document units." },
|
|
13655
|
+
color: COLOR,
|
|
13656
|
+
opacity: { type: "number", minimum: 0, maximum: 1, description: "Opacity 0\u20131 (default 1)." },
|
|
13657
|
+
spread: { type: "number", minimum: 0, maximum: 1, description: "Radius of pure colour round the point, as a fraction of half the shape's longer side (default 0)." }
|
|
13658
|
+
},
|
|
13659
|
+
required: ["x", "y", "color"],
|
|
13660
|
+
additionalProperties: false
|
|
13661
|
+
};
|
|
13662
|
+
const LINES = { type: "array", items: { type: "array", items: { type: "integer", minimum: 0 }, minItems: 2 }, description: "Lines, each the point indices (two or more) a smooth curve runs through; the colours flow along it." };
|
|
13663
|
+
const shadingCommands = () => [
|
|
13664
|
+
{
|
|
13665
|
+
...base,
|
|
13666
|
+
id: `${prefix}getShading`,
|
|
13667
|
+
label: "Get Mesh or Freeform Gradient",
|
|
13668
|
+
readOnly: true,
|
|
13669
|
+
keywords: ["mesh", "gradient mesh", "freeform", "freeform gradient", "points"],
|
|
13670
|
+
description: `Returns a path's mesh or freeform fill with its points in document units: { id, shading } where shading is { type: "mesh", rows, columns, points (row by row), colors, opacities? } or { type: "freeform", points: [{ x, y, color, opacity?, spread? }], lines? }. Point indices are what the edit commands take.`,
|
|
13671
|
+
params: { type: "object", properties: { id: { type: "string", description: "The path (from vector.list)." } }, required: ["id"], additionalProperties: false },
|
|
13672
|
+
run: (_ctx, params) => shadingResult(params.id)
|
|
13673
|
+
},
|
|
13674
|
+
{
|
|
13675
|
+
...base,
|
|
13676
|
+
id: `${prefix}setMeshGradient`,
|
|
13677
|
+
label: "Create Gradient Mesh\u2026",
|
|
13678
|
+
keywords: ["mesh", "gradient mesh", "coons", "patch", "shading", "realistic", "smooth shading"],
|
|
13679
|
+
description: "Fills paths (`ids`, default the selection) with a gradient mesh over their bounds: rows \xD7 columns smooth patches whose points blend colours. Colour it with `colors` (one per point, (rows + 1) \xD7 (columns + 1), row by row) or the four `corners`, else the current fill colour, then move and recolour points with vector.setMeshPoint. One undo step. Returns { meshes: [{ id, shading }] }.",
|
|
13680
|
+
params: {
|
|
13681
|
+
type: "object",
|
|
13682
|
+
properties: {
|
|
13683
|
+
ids: { ...IDS, description: "Paths to fill (default: the selection)." },
|
|
13684
|
+
rows: { type: "integer", minimum: 1, maximum: 32, description: "Patch rows." },
|
|
13685
|
+
columns: { type: "integer", minimum: 1, maximum: 32, description: "Patch columns." },
|
|
13686
|
+
colors: { type: "array", items: COLOR, description: "A colour per point, row by row ((rows + 1) \xD7 (columns + 1) of them)." },
|
|
13687
|
+
corners: { type: "array", items: COLOR, minItems: 4, maxItems: 4, description: "Top-left, top-right, bottom-right and bottom-left colours, blended across." }
|
|
13688
|
+
},
|
|
13689
|
+
required: ["rows", "columns"],
|
|
13690
|
+
additionalProperties: false
|
|
13691
|
+
},
|
|
13692
|
+
undoable: true,
|
|
13693
|
+
enabled: enabledFor,
|
|
13694
|
+
run: (_ctx, params) => {
|
|
13695
|
+
const list2 = ids(params);
|
|
13696
|
+
check(controller.setMeshGradient({ rows: params.rows, columns: params.columns, ...params.colors ? { colors: params.colors } : {}, ...params.corners ? { corners: params.corners } : {} }, { ids: list2 }));
|
|
13697
|
+
return { meshes: list2.map(shadingResult) };
|
|
13698
|
+
}
|
|
13699
|
+
},
|
|
13700
|
+
{
|
|
13701
|
+
...base,
|
|
13702
|
+
id: `${prefix}setMeshPoint`,
|
|
13703
|
+
label: "Edit Mesh Point",
|
|
13704
|
+
keywords: ["mesh", "mesh point", "colour", "move point"],
|
|
13705
|
+
description: "Moves point `index` (row by row, from vector.getShading) of a path's gradient mesh to x, y (document units, both or neither) and / or sets its colour and opacity. One undo step. Returns { id, shading }.",
|
|
13706
|
+
params: {
|
|
13707
|
+
type: "object",
|
|
13708
|
+
properties: {
|
|
13709
|
+
id: { type: "string", description: "The path." },
|
|
13710
|
+
index: { type: "integer", minimum: 0, description: "Point index, row by row." },
|
|
13711
|
+
x: { type: "number", description: "New position (document units)." },
|
|
13712
|
+
y: { type: "number", description: "New position (document units)." },
|
|
13713
|
+
color: COLOR,
|
|
13714
|
+
opacity: { type: "number", minimum: 0, maximum: 1, description: "Opacity 0\u20131." }
|
|
13715
|
+
},
|
|
13716
|
+
required: ["id", "index"],
|
|
13717
|
+
additionalProperties: false
|
|
13718
|
+
},
|
|
13719
|
+
undoable: true,
|
|
13720
|
+
run: (_ctx, params) => {
|
|
13721
|
+
const { id, index, ...patch } = params;
|
|
13722
|
+
if (!Object.keys(patch).length) fail("Pass at least one of x / y, color, opacity.");
|
|
13723
|
+
check(controller.setMeshPoint(id, index, patch));
|
|
13724
|
+
return shadingResult(id);
|
|
13725
|
+
}
|
|
13726
|
+
},
|
|
13727
|
+
{
|
|
13728
|
+
...base,
|
|
13729
|
+
id: `${prefix}setFreeformGradient`,
|
|
13730
|
+
label: "Create Freeform Gradient\u2026",
|
|
13731
|
+
keywords: ["freeform", "freeform gradient", "colour points", "shading", "blend colours"],
|
|
13732
|
+
description: "Fills paths (`ids`, default the selection) with a freeform gradient: colour `points` placed anywhere (document units) blending into each other, with optional `lines` that carry colour along smooth curves. Without points, four or more are placed inside each path, coloured along `colors` (default its fill to white). One undo step. Returns { gradients: [{ id, shading }] }.",
|
|
13733
|
+
params: {
|
|
13734
|
+
type: "object",
|
|
13735
|
+
properties: {
|
|
13736
|
+
ids: { ...IDS, description: "Paths to fill (default: the selection)." },
|
|
13737
|
+
points: { type: "array", items: FREEFORM_POINT, description: "The colour points (document units)." },
|
|
13738
|
+
lines: LINES,
|
|
13739
|
+
colors: { type: "array", items: COLOR, description: "Without points: colours the placed points take, first to last." }
|
|
13740
|
+
},
|
|
13741
|
+
additionalProperties: false
|
|
13742
|
+
},
|
|
13743
|
+
undoable: true,
|
|
13744
|
+
enabled: enabledFor,
|
|
13745
|
+
run: (_ctx, params) => {
|
|
13746
|
+
const { ids: _i, ...input } = params ?? {};
|
|
13747
|
+
const list2 = ids(params);
|
|
13748
|
+
check(controller.setFreeformGradient(input, { ids: list2 }));
|
|
13749
|
+
return { gradients: list2.map(shadingResult) };
|
|
13750
|
+
}
|
|
13751
|
+
},
|
|
13752
|
+
{
|
|
13753
|
+
...base,
|
|
13754
|
+
id: `${prefix}addFreeformPoint`,
|
|
13755
|
+
label: "Add Freeform Point",
|
|
13756
|
+
keywords: ["freeform", "colour point", "add point"],
|
|
13757
|
+
description: "Adds a colour point to a path's freeform gradient at x, y (document units), coloured `color` (default: the colour there now). One undo step. Returns { id, index, shading }.",
|
|
13758
|
+
params: {
|
|
13759
|
+
type: "object",
|
|
13760
|
+
properties: { id: { type: "string", description: "The path." }, x: { type: "number", description: "Document units." }, y: { type: "number", description: "Document units." }, color: COLOR, opacity: FREEFORM_POINT.properties.opacity, spread: FREEFORM_POINT.properties.spread },
|
|
13761
|
+
required: ["id", "x", "y"],
|
|
13762
|
+
additionalProperties: false
|
|
13763
|
+
},
|
|
13764
|
+
undoable: true,
|
|
13765
|
+
run: (_ctx, params) => {
|
|
13766
|
+
const { id, ...point } = params;
|
|
13767
|
+
const r = controller.addFreeformPoint(id, point);
|
|
13768
|
+
check(r);
|
|
13769
|
+
return { ...shadingResult(id), index: r.index };
|
|
13770
|
+
}
|
|
13771
|
+
},
|
|
13772
|
+
{
|
|
13773
|
+
...base,
|
|
13774
|
+
id: `${prefix}setFreeformPoint`,
|
|
13775
|
+
label: "Edit Freeform Point",
|
|
13776
|
+
keywords: ["freeform", "colour point", "move point", "spread"],
|
|
13777
|
+
description: "Changes point `index` (from vector.getShading) of a path's freeform gradient: x, y (document units, both or neither), color, opacity, spread. One undo step. Returns { id, shading }.",
|
|
13778
|
+
params: {
|
|
13779
|
+
type: "object",
|
|
13780
|
+
properties: {
|
|
13781
|
+
id: { type: "string", description: "The path." },
|
|
13782
|
+
index: { type: "integer", minimum: 0, description: "Point index." },
|
|
13783
|
+
x: { type: "number", description: "New position (document units)." },
|
|
13784
|
+
y: { type: "number", description: "New position (document units)." },
|
|
13785
|
+
color: COLOR,
|
|
13786
|
+
opacity: FREEFORM_POINT.properties.opacity,
|
|
13787
|
+
spread: FREEFORM_POINT.properties.spread
|
|
13788
|
+
},
|
|
13789
|
+
required: ["id", "index"],
|
|
13790
|
+
additionalProperties: false
|
|
13791
|
+
},
|
|
13792
|
+
undoable: true,
|
|
13793
|
+
run: (_ctx, params) => {
|
|
13794
|
+
const { id, index, ...patch } = params;
|
|
13795
|
+
if (!Object.keys(patch).length) fail("Pass at least one of x / y, color, opacity, spread.");
|
|
13796
|
+
check(controller.setFreeformPoint(id, index, patch));
|
|
13797
|
+
return shadingResult(id);
|
|
13798
|
+
}
|
|
13799
|
+
},
|
|
13800
|
+
{
|
|
13801
|
+
...base,
|
|
13802
|
+
id: `${prefix}removeFreeformPoint`,
|
|
13803
|
+
label: "Remove Freeform Point",
|
|
13804
|
+
keywords: ["freeform", "colour point", "delete point"],
|
|
13805
|
+
description: "Removes point `index` of a path's freeform gradient (never the last one); lines through it close up and later indices shift down. One undo step. Returns { id, shading }.",
|
|
13806
|
+
params: { type: "object", properties: { id: { type: "string", description: "The path." }, index: { type: "integer", minimum: 0, description: "Point index." } }, required: ["id", "index"], additionalProperties: false },
|
|
13807
|
+
undoable: true,
|
|
13808
|
+
run: (_ctx, params) => {
|
|
13809
|
+
check(controller.removeFreeformPoint(params.id, params.index));
|
|
13810
|
+
return shadingResult(params.id);
|
|
13811
|
+
}
|
|
13812
|
+
},
|
|
13813
|
+
{
|
|
13814
|
+
...base,
|
|
13815
|
+
id: `${prefix}setFreeformLines`,
|
|
13816
|
+
label: "Freeform Lines",
|
|
13817
|
+
keywords: ["freeform", "lines", "colour lines", "curve"],
|
|
13818
|
+
description: "Replaces the lines of a path's freeform gradient (an empty list removes them): each line is the point indices a smooth curve runs through, carrying the colours along it. One undo step. Returns { id, shading }.",
|
|
13819
|
+
params: { type: "object", properties: { id: { type: "string", description: "The path." }, lines: LINES }, required: ["id", "lines"], additionalProperties: false },
|
|
13820
|
+
undoable: true,
|
|
13821
|
+
run: (_ctx, params) => {
|
|
13822
|
+
check(controller.setFreeformLines(params.id, params.lines));
|
|
13823
|
+
return shadingResult(params.id);
|
|
13824
|
+
}
|
|
13825
|
+
}
|
|
13826
|
+
];
|
|
12931
13827
|
const snapResult = () => {
|
|
12932
13828
|
const { grid, objects, guides, threshold, angles, tracking, trackPolar, polarRelative } = controller.getState().snap;
|
|
12933
13829
|
return { grid, objects, guides, threshold, angles: [...angles], tracking: tracking !== false, trackPolar: !!trackPolar, polarRelative: !!polarRelative };
|
|
@@ -13453,6 +14349,8 @@ function createVectorCommands(controller, options = {}) {
|
|
|
13453
14349
|
...frameCommands(),
|
|
13454
14350
|
...connectorCommands(),
|
|
13455
14351
|
...trackingCommands(),
|
|
14352
|
+
...shadingCommands(),
|
|
14353
|
+
...traceCommands(),
|
|
13456
14354
|
{
|
|
13457
14355
|
...base,
|
|
13458
14356
|
id: `${prefix}setCorners`,
|
|
@@ -14426,6 +15324,7 @@ export {
|
|
|
14426
15324
|
VECTOR_MAX_CONSTRAINTS,
|
|
14427
15325
|
VECTOR_MAX_TRACKING_POINTS,
|
|
14428
15326
|
VECTOR_MERGE_MODES,
|
|
15327
|
+
VECTOR_MESH_MAX,
|
|
14429
15328
|
VECTOR_OVERLAY_THEME,
|
|
14430
15329
|
VECTOR_PARAGRAPH_STYLE_KEYS,
|
|
14431
15330
|
VECTOR_PATHFINDER_ACTIONS,
|
|
@@ -14449,6 +15348,7 @@ export {
|
|
|
14449
15348
|
VECTOR_TOOLS,
|
|
14450
15349
|
VECTOR_TOOL_GLYPHS,
|
|
14451
15350
|
VECTOR_TOOL_SHORTCUTS,
|
|
15351
|
+
VECTOR_TRACE_MAX_PIXELS,
|
|
14452
15352
|
VECTOR_TRANSFORM_HANDLES,
|
|
14453
15353
|
VECTOR_VERTICAL_RESIZING,
|
|
14454
15354
|
VECTOR_VERTICAL_RESIZING_LABELS,
|
|
@@ -14484,6 +15384,7 @@ export {
|
|
|
14484
15384
|
arrowheadPath,
|
|
14485
15385
|
arrowheadWeight,
|
|
14486
15386
|
autoConstrainVector,
|
|
15387
|
+
autoVectorFreeformGradient,
|
|
14487
15388
|
bakeVectorTextGeometry,
|
|
14488
15389
|
bendSegment,
|
|
14489
15390
|
bezierToSubpaths,
|
|
@@ -14511,6 +15412,7 @@ export {
|
|
|
14511
15412
|
createVectorCommands,
|
|
14512
15413
|
createVectorEffect,
|
|
14513
15414
|
createVectorFilter,
|
|
15415
|
+
createVectorMeshGradient,
|
|
14514
15416
|
createVectorMorph,
|
|
14515
15417
|
cssFontFamily,
|
|
14516
15418
|
curvatureMoveAnchor,
|
|
@@ -14617,11 +15519,14 @@ export {
|
|
|
14617
15519
|
isVectorConnector,
|
|
14618
15520
|
isVectorFilterType,
|
|
14619
15521
|
isVectorFrame,
|
|
15522
|
+
isVectorFreeformGradient,
|
|
14620
15523
|
isVectorGradient,
|
|
14621
15524
|
isVectorLiveBoolean,
|
|
14622
15525
|
isVectorMaskActive,
|
|
14623
15526
|
isVectorMaskGroup,
|
|
14624
15527
|
isVectorMaskType,
|
|
15528
|
+
isVectorMeshGradient,
|
|
15529
|
+
isVectorShadedPaint,
|
|
14625
15530
|
isVectorTextTarget,
|
|
14626
15531
|
joinSubpaths,
|
|
14627
15532
|
knifePath,
|
|
@@ -14634,6 +15539,7 @@ export {
|
|
|
14634
15539
|
mapWarpMeshPoints as mapVectorMeshPoints,
|
|
14635
15540
|
mapVectorPaint,
|
|
14636
15541
|
mapVectorPoints,
|
|
15542
|
+
mapVectorShadedPaint,
|
|
14637
15543
|
mapVectorSubpaths,
|
|
14638
15544
|
matrixRotation,
|
|
14639
15545
|
measureVectorConstraint,
|
|
@@ -14649,6 +15555,7 @@ export {
|
|
|
14649
15555
|
nearestSegment,
|
|
14650
15556
|
nearestStrokePosition,
|
|
14651
15557
|
nearestVectorConnectionSite,
|
|
15558
|
+
nearestVectorShadingPoint,
|
|
14652
15559
|
needsVectorPaintLayers,
|
|
14653
15560
|
newVectorStrokeLayer,
|
|
14654
15561
|
nextVectorConstraintName,
|
|
@@ -14679,6 +15586,7 @@ export {
|
|
|
14679
15586
|
puckerBloatPaths,
|
|
14680
15587
|
pullWarpMesh as pullVectorMesh,
|
|
14681
15588
|
purgeVectorConstraints,
|
|
15589
|
+
quantizeVectorTraceColors,
|
|
14682
15590
|
recognizeInkShape,
|
|
14683
15591
|
rectSubpath,
|
|
14684
15592
|
refineMorphContour,
|
|
@@ -14688,6 +15596,7 @@ export {
|
|
|
14688
15596
|
removeAnchors,
|
|
14689
15597
|
removeAnchorsKeepingShape,
|
|
14690
15598
|
removeRedundantPoints,
|
|
15599
|
+
removeVectorFreeformPoint,
|
|
14691
15600
|
removeVectorGradientStop,
|
|
14692
15601
|
removeWarpMeshSplitU as removeVectorMeshSplitU,
|
|
14693
15602
|
removeWarpMeshSplitV as removeVectorMeshSplitV,
|
|
@@ -14709,6 +15618,7 @@ export {
|
|
|
14709
15618
|
routeVectorConnector,
|
|
14710
15619
|
samplePlainStops,
|
|
14711
15620
|
samplePolyline,
|
|
15621
|
+
sampleVectorFreeform,
|
|
14712
15622
|
sampleVectorGradient,
|
|
14713
15623
|
sampleVectorGradientAt,
|
|
14714
15624
|
sampleVectorPaintDefAt,
|
|
@@ -14720,6 +15630,7 @@ export {
|
|
|
14720
15630
|
sanitizeVectorGradient,
|
|
14721
15631
|
sanitizeVectorMask,
|
|
14722
15632
|
sanitizeVectorResizing,
|
|
15633
|
+
sanitizeVectorShadedPaint,
|
|
14723
15634
|
sanitizeVectorStrokeSettings,
|
|
14724
15635
|
sanitizeVectorStrokes,
|
|
14725
15636
|
sanitizeVectorTextFields,
|
|
@@ -14759,10 +15670,12 @@ export {
|
|
|
14759
15670
|
toVectorKeyInput,
|
|
14760
15671
|
toVectorModifiers,
|
|
14761
15672
|
toVectorPointerInput,
|
|
15673
|
+
traceVectorImage,
|
|
14762
15674
|
transformVectorEffects,
|
|
14763
15675
|
transformVectorGradient,
|
|
14764
15676
|
transformVectorNode,
|
|
14765
15677
|
transformVectorPaint,
|
|
15678
|
+
transformVectorShadedPaint,
|
|
14766
15679
|
translateAnchor,
|
|
14767
15680
|
translateMatrix,
|
|
14768
15681
|
trimIntervals,
|
|
@@ -14827,6 +15740,9 @@ export {
|
|
|
14827
15740
|
vectorFrameDocument,
|
|
14828
15741
|
vectorFrameDrawn,
|
|
14829
15742
|
vectorFrameRectPath,
|
|
15743
|
+
vectorFreeformField,
|
|
15744
|
+
vectorFreeformLineSegments,
|
|
15745
|
+
vectorFreeformSpreadScale,
|
|
14830
15746
|
vectorGradientArea,
|
|
14831
15747
|
vectorGradientAspect,
|
|
14832
15748
|
vectorGradientBarPosition,
|
|
@@ -14863,13 +15779,16 @@ export {
|
|
|
14863
15779
|
vectorMaskedGeometry,
|
|
14864
15780
|
vectorMaskedLocalBounds,
|
|
14865
15781
|
vectorMaskedShapes,
|
|
15782
|
+
vectorMeshColorAt,
|
|
14866
15783
|
warpMeshColumns as vectorMeshColumns,
|
|
14867
15784
|
warpMeshControlBounds as vectorMeshControlBounds,
|
|
14868
15785
|
warpMeshEditPoints as vectorMeshEditPoints,
|
|
14869
15786
|
vectorMeshFromOutline,
|
|
14870
15787
|
warpMeshGridCurves as vectorMeshGridCurves,
|
|
15788
|
+
vectorMeshLines,
|
|
14871
15789
|
vectorMeshOutline,
|
|
14872
15790
|
warpMeshParamAt as vectorMeshParamAt,
|
|
15791
|
+
vectorMeshPointAt,
|
|
14873
15792
|
warpMeshRows as vectorMeshRows,
|
|
14874
15793
|
vectorNodeBounds,
|
|
14875
15794
|
vectorNodeEnvelope,
|
|
@@ -14898,6 +15817,8 @@ export {
|
|
|
14898
15817
|
vectorRepeaterCopies,
|
|
14899
15818
|
vectorResizeCursor,
|
|
14900
15819
|
vectorResultStyle,
|
|
15820
|
+
vectorRgbaToCss,
|
|
15821
|
+
vectorShadedSvgDefs,
|
|
14901
15822
|
vectorShapeBuilderHit,
|
|
14902
15823
|
vectorShapeBuilderMap,
|
|
14903
15824
|
vectorShearForSide,
|
|
@@ -14935,6 +15856,7 @@ export {
|
|
|
14935
15856
|
vectorTextSpans,
|
|
14936
15857
|
vectorTextStyleAt,
|
|
14937
15858
|
vectorToolLabel,
|
|
15859
|
+
vectorTraceContours,
|
|
14938
15860
|
vectorTrackingPaths,
|
|
14939
15861
|
warpPresetGrid as vectorWarpPresetGrid,
|
|
14940
15862
|
warpPresetMesh as vectorWarpPresetMesh,
|