@pixerate/editor 0.112.0 → 0.114.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 +11 -0
- package/dist/animation-editor/index.cjs +60 -54
- package/dist/animation-editor/index.cjs.map +1 -1
- package/dist/animation-editor/index.d.cts +3 -3
- package/dist/animation-editor/index.d.ts +3 -3
- package/dist/animation-editor/index.js +14 -8
- package/dist/animation-editor/index.js.map +1 -1
- package/dist/builtin-CY97ppKM.d.cts +354 -0
- package/dist/builtin-CY97ppKM.d.ts +354 -0
- package/dist/canvas/index.cjs +3 -3
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.js +9 -9
- package/dist/chunk-26PGXHY7.cjs +753 -0
- package/dist/chunk-26PGXHY7.cjs.map +1 -0
- package/dist/chunk-2OJACTPR.cjs +929 -0
- package/dist/chunk-2OJACTPR.cjs.map +1 -0
- package/dist/chunk-3UFGNHSP.js +929 -0
- package/dist/chunk-3UFGNHSP.js.map +1 -0
- package/dist/{chunk-YGIMK4NX.cjs → chunk-5OVJHMZQ.cjs} +757 -825
- package/dist/chunk-5OVJHMZQ.cjs.map +1 -0
- package/dist/chunk-643B2HKC.cjs +534 -0
- package/dist/chunk-643B2HKC.cjs.map +1 -0
- package/dist/chunk-6FCKOS2B.js +534 -0
- package/dist/chunk-6FCKOS2B.js.map +1 -0
- package/dist/chunk-BRLYOPLT.js +2574 -0
- package/dist/chunk-BRLYOPLT.js.map +1 -0
- package/dist/{chunk-LQEEDHOP.js → chunk-C6C5PDKU.js} +759 -761
- package/dist/chunk-C6C5PDKU.js.map +1 -0
- package/dist/chunk-DFQEX3AM.cjs +781 -0
- package/dist/chunk-DFQEX3AM.cjs.map +1 -0
- package/dist/{chunk-CZOXTEMN.js → chunk-EUVSQ25B.js} +336 -404
- package/dist/chunk-EUVSQ25B.js.map +1 -0
- package/dist/chunk-G5NMLUVF.cjs +2574 -0
- package/dist/chunk-G5NMLUVF.cjs.map +1 -0
- package/dist/chunk-LKCYANMW.js +781 -0
- package/dist/chunk-LKCYANMW.js.map +1 -0
- package/dist/{chunk-W3GXSUSQ.cjs → chunk-LPLCWZTM.cjs} +5087 -1283
- package/dist/chunk-LPLCWZTM.cjs.map +1 -0
- package/dist/chunk-MMVHBKMM.js +753 -0
- package/dist/chunk-MMVHBKMM.js.map +1 -0
- package/dist/{chunk-POGBUYWE.cjs → chunk-ST7E3NEE.cjs} +857 -859
- package/dist/chunk-ST7E3NEE.cjs.map +1 -0
- package/dist/{chunk-7VOHOPLW.cjs → chunk-TCKIXI46.cjs} +59 -59
- package/dist/{chunk-7VOHOPLW.cjs.map → chunk-TCKIXI46.cjs.map} +1 -1
- package/dist/{chunk-XIOMMGPD.cjs → chunk-TFOPHZ3U.cjs} +1 -1
- package/dist/chunk-TFOPHZ3U.cjs.map +1 -0
- package/dist/{chunk-Z4BC6M74.js → chunk-UBIXVXPR.js} +2 -2
- package/dist/{chunk-BLWCAQCW.js → chunk-VXR7XWMF.js} +4574 -770
- package/dist/chunk-VXR7XWMF.js.map +1 -0
- package/dist/{chunk-HNS7UHCM.js → chunk-XK5LZKBD.js} +1 -1
- package/dist/chunk-XK5LZKBD.js.map +1 -0
- package/dist/cms/index.cjs +146 -0
- package/dist/cms/index.cjs.map +1 -0
- package/dist/cms/index.d.cts +51 -0
- package/dist/cms/index.d.ts +51 -0
- package/dist/cms/index.js +146 -0
- package/dist/{export-DNu_Jr8p.d.ts → export-_1dUKwpF.d.ts} +326 -195
- package/dist/{export-DBba-buV.d.cts → export-b6NjCZ5-.d.cts} +326 -195
- package/dist/image-editor/index.cjs +2377 -1291
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +149 -97
- package/dist/image-editor/index.d.ts +149 -97
- package/dist/image-editor/index.js +1343 -257
- package/dist/image-editor/index.js.map +1 -1
- package/dist/opentype-BZEOSOX3.d.ts +188 -0
- package/dist/opentype-Dpe75vL5.d.cts +188 -0
- package/dist/paint/index.cjs +63 -4
- package/dist/paint/index.cjs.map +1 -1
- package/dist/paint/index.d.cts +30 -3
- package/dist/paint/index.d.ts +30 -3
- package/dist/paint/index.js +62 -3
- package/dist/paint-editor/index.cjs +146 -145
- package/dist/paint-editor/index.cjs.map +1 -1
- package/dist/paint-editor/index.d.cts +9 -8
- package/dist/paint-editor/index.d.ts +9 -8
- package/dist/paint-editor/index.js +6 -5
- package/dist/paint-editor/index.js.map +1 -1
- package/dist/raster/index.cjs +53 -3
- package/dist/raster/index.cjs.map +1 -1
- package/dist/raster/index.d.cts +62 -91
- package/dist/raster/index.d.ts +62 -91
- package/dist/raster/index.js +59 -9
- package/dist/{render-BEriFuqK.d.cts → render-B1K_OoLy.d.cts} +87 -2
- package/dist/{render-tjzKaik-.d.ts → render-CrmHOgpo.d.ts} +87 -2
- package/dist/{blending-aOE6Y3UE.d.cts → retouch-Bxkuc0KH.d.cts} +219 -2
- package/dist/{blending-DXl06NSx.d.ts → retouch-Do6tBs2H.d.ts} +219 -2
- package/dist/{surface-C8RRg_Bh.d.ts → surface-Cn30xIV6.d.cts} +6 -1
- package/dist/{surface-C8RRg_Bh.d.cts → surface-Cn30xIV6.d.ts} +6 -1
- package/dist/{svg-D_cfkhlB.d.cts → svg-BnRj0q8i.d.cts} +16 -4
- package/dist/{svg-wIaKLrU0.d.ts → svg-CmmBFfGT.d.ts} +16 -4
- package/dist/{types-DtL_qLFz.d.ts → types-1SUQKDAw.d.ts} +273 -4
- package/dist/{types-DyQq-WPY.d.cts → types-yINF17Ta.d.cts} +273 -4
- package/dist/vector/index.cjs +12170 -7343
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +1579 -16
- package/dist/vector/index.d.ts +1579 -16
- package/dist/vector/index.js +6303 -1476
- package/dist/vector/index.js.map +1 -1
- package/dist/warp-B39VsOtd.d.cts +348 -0
- package/dist/warp-DMN30aY6.d.ts +348 -0
- package/dist/zlib-dxQdgHyN.d.cts +95 -0
- package/dist/zlib-dxQdgHyN.d.ts +95 -0
- package/package.json +6 -1
- package/skills/pixerate-image-editor/SKILL.md +34 -34
- package/skills/pixerate-image-editor/references/color-management.md +37 -0
- package/skills/pixerate-image-editor/references/photoshop-files.md +39 -0
- package/skills/pixerate-image-editor/references/retouching.md +36 -0
- package/skills/pixerate-vector/SKILL.md +154 -5
- package/dist/chunk-BLWCAQCW.js.map +0 -1
- package/dist/chunk-CZOXTEMN.js.map +0 -1
- package/dist/chunk-FTBZPI77.cjs +0 -1
- package/dist/chunk-FTBZPI77.cjs.map +0 -1
- package/dist/chunk-HNS7UHCM.js.map +0 -1
- package/dist/chunk-JLYX4V57.cjs +0 -2001
- package/dist/chunk-JLYX4V57.cjs.map +0 -1
- package/dist/chunk-KB2YBCVS.js +0 -2001
- package/dist/chunk-KB2YBCVS.js.map +0 -1
- package/dist/chunk-LQEEDHOP.js.map +0 -1
- package/dist/chunk-POGBUYWE.cjs.map +0 -1
- package/dist/chunk-VKK5VS2T.js +0 -1
- package/dist/chunk-W3GXSUSQ.cjs.map +0 -1
- package/dist/chunk-XIOMMGPD.cjs.map +0 -1
- package/dist/chunk-YGIMK4NX.cjs.map +0 -1
- package/dist/homography-CZ-tFjay.d.cts +0 -50
- package/dist/homography-Ctfjoc1b.d.ts +0 -50
- package/dist/warp-CWHZzE3x.d.ts +0 -118
- package/dist/warp-D5jz_tN7.d.cts +0 -118
- /package/dist/{chunk-Z4BC6M74.js.map → chunk-UBIXVXPR.js.map} +0 -0
- /package/dist/{chunk-VKK5VS2T.js.map → cms/index.js.map} +0 -0
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
createCaptureTool
|
|
6
|
-
} from "../chunk-REOJJIY3.js";
|
|
7
|
-
import "../chunk-VKK5VS2T.js";
|
|
2
|
+
loadOpenTypeFont,
|
|
3
|
+
parseOpenTypeFont
|
|
4
|
+
} from "../chunk-3UFGNHSP.js";
|
|
8
5
|
import {
|
|
9
6
|
ADJUSTMENT_KEYS,
|
|
10
7
|
ADJUSTMENT_KINDS,
|
|
@@ -17,10 +14,8 @@ import {
|
|
|
17
14
|
D50,
|
|
18
15
|
D65,
|
|
19
16
|
DEFAULT_ADJUSTMENTS_VALUES,
|
|
20
|
-
DEFAULT_AUTO_LEADING,
|
|
21
17
|
DEFAULT_DEPTH_MASK_VALUES,
|
|
22
18
|
DEFAULT_MITER_LIMIT,
|
|
23
|
-
DEFAULT_TEXT_STYLE,
|
|
24
19
|
DEFAULT_TRANSFORM_VALUES,
|
|
25
20
|
DEVELOP_FILTER_ID,
|
|
26
21
|
EMPTY_DISTANCE,
|
|
@@ -72,6 +67,7 @@ import {
|
|
|
72
67
|
annotationOfShape,
|
|
73
68
|
annotationOfType,
|
|
74
69
|
applyAdjustmentLayers,
|
|
70
|
+
applyAdjustmentLayersFloat,
|
|
75
71
|
applyAdjustments,
|
|
76
72
|
applyBoxBlur,
|
|
77
73
|
applyDepthMask,
|
|
@@ -91,12 +87,12 @@ import {
|
|
|
91
87
|
buildToneLut,
|
|
92
88
|
calculateCropDrag,
|
|
93
89
|
canvasToImagePoint,
|
|
94
|
-
caretAt,
|
|
95
90
|
clampCropToImage,
|
|
96
91
|
colorBalanceChannel,
|
|
97
92
|
combineSelections,
|
|
98
93
|
compileAdjustmentLayer,
|
|
99
94
|
componentPath,
|
|
95
|
+
compositeDeep,
|
|
100
96
|
computeSelectionBounds,
|
|
101
97
|
contentsBox,
|
|
102
98
|
contentsSurface,
|
|
@@ -110,6 +106,7 @@ import {
|
|
|
110
106
|
createShapeLayer,
|
|
111
107
|
createTypeLayer,
|
|
112
108
|
curveLutFromLevels,
|
|
109
|
+
decodeDeepSurface,
|
|
113
110
|
decodeLayerChannel,
|
|
114
111
|
decodeLayerMask,
|
|
115
112
|
decodeMergedImage,
|
|
@@ -132,6 +129,7 @@ import {
|
|
|
132
129
|
distanceTransform,
|
|
133
130
|
documentContents,
|
|
134
131
|
duplicateLayer,
|
|
132
|
+
encodeDeepSurface,
|
|
135
133
|
encodeRle,
|
|
136
134
|
encodeSelectionRle,
|
|
137
135
|
encodeSurface,
|
|
@@ -140,11 +138,8 @@ import {
|
|
|
140
138
|
exportPsd,
|
|
141
139
|
featherCoverage,
|
|
142
140
|
filterMaskCoverage,
|
|
143
|
-
fitParagraphRuns,
|
|
144
|
-
fitRuns,
|
|
145
141
|
flattenPath,
|
|
146
142
|
flipCorners,
|
|
147
|
-
fontOf,
|
|
148
143
|
fullSelection,
|
|
149
144
|
geometryHomography,
|
|
150
145
|
getAnnotationBounds,
|
|
@@ -195,7 +190,6 @@ import {
|
|
|
195
190
|
layerTree,
|
|
196
191
|
layerTypeLabel,
|
|
197
192
|
layersFromLegacy,
|
|
198
|
-
layoutText,
|
|
199
193
|
legacyAdjustmentLayer,
|
|
200
194
|
legacyMaskOf,
|
|
201
195
|
legacyView,
|
|
@@ -232,7 +226,6 @@ import {
|
|
|
232
226
|
offsetLayer,
|
|
233
227
|
offsetShape,
|
|
234
228
|
offsetSmartObject,
|
|
235
|
-
paragraphRanges,
|
|
236
229
|
parseAdjustmentBlock,
|
|
237
230
|
parseBlend,
|
|
238
231
|
parseCssColor,
|
|
@@ -287,7 +280,6 @@ import {
|
|
|
287
280
|
rerenderText,
|
|
288
281
|
resizeSelection,
|
|
289
282
|
resolveExternalAssets,
|
|
290
|
-
restyleRange,
|
|
291
283
|
rgbToGray,
|
|
292
284
|
rgbToHsl,
|
|
293
285
|
runLayerFilters,
|
|
@@ -323,12 +315,9 @@ import {
|
|
|
323
315
|
smartContentsFile,
|
|
324
316
|
smartObjectLayers,
|
|
325
317
|
smoothCoverage,
|
|
326
|
-
spliceRuns,
|
|
327
318
|
srgbToLinear,
|
|
328
|
-
styleAt,
|
|
329
319
|
tempTintToXy,
|
|
330
320
|
temperatureToKelvin,
|
|
331
|
-
textIndexAt,
|
|
332
321
|
textMatrix,
|
|
333
322
|
textOutlines,
|
|
334
323
|
textRange,
|
|
@@ -369,8 +358,15 @@ import {
|
|
|
369
358
|
writePsd,
|
|
370
359
|
xyToTempTint,
|
|
371
360
|
xyToUv
|
|
372
|
-
} from "../chunk-
|
|
361
|
+
} from "../chunk-EUVSQ25B.js";
|
|
362
|
+
import {
|
|
363
|
+
loadDocument
|
|
364
|
+
} from "../chunk-U5HZSFCS.js";
|
|
373
365
|
import {
|
|
366
|
+
createCaptureTool
|
|
367
|
+
} from "../chunk-REOJJIY3.js";
|
|
368
|
+
import {
|
|
369
|
+
BIT_DEPTHS,
|
|
374
370
|
DEFAULT_GLOBAL_LIGHT,
|
|
375
371
|
DIRECT_GAUSSIAN_MAX_SIGMA,
|
|
376
372
|
INTERPOLATIONS,
|
|
@@ -381,8 +377,6 @@ import {
|
|
|
381
377
|
PatternPlacement,
|
|
382
378
|
PatternTile,
|
|
383
379
|
TransformTooLargeError,
|
|
384
|
-
ZlibError,
|
|
385
|
-
adler32,
|
|
386
380
|
base64ToBytes,
|
|
387
381
|
blendTile,
|
|
388
382
|
builtinPattern,
|
|
@@ -390,12 +384,14 @@ import {
|
|
|
390
384
|
bytesToBase64,
|
|
391
385
|
changedTiles,
|
|
392
386
|
compositeSurfaceOver,
|
|
387
|
+
convertSurfaceDepth,
|
|
393
388
|
curveLut,
|
|
394
389
|
decodePng,
|
|
390
|
+
deepBytes,
|
|
391
|
+
deepTile,
|
|
395
392
|
defaultEffectGradient,
|
|
396
393
|
defaultLayerEffect,
|
|
397
|
-
|
|
398
|
-
deflateRaw,
|
|
394
|
+
depthOf,
|
|
399
395
|
effectColorHex,
|
|
400
396
|
effectReach,
|
|
401
397
|
effectsFingerprint,
|
|
@@ -407,9 +403,8 @@ import {
|
|
|
407
403
|
gaussianBlur,
|
|
408
404
|
hasActiveEffects,
|
|
409
405
|
hashNoise,
|
|
410
|
-
inflate,
|
|
411
|
-
inflateRaw,
|
|
412
406
|
insertLayer,
|
|
407
|
+
isBitDepth,
|
|
413
408
|
isInterpolation,
|
|
414
409
|
isLayerEffectKind,
|
|
415
410
|
isLayerLocked,
|
|
@@ -424,16 +419,33 @@ import {
|
|
|
424
419
|
parseDataUrl,
|
|
425
420
|
parseEffectColor,
|
|
426
421
|
pngDataUrl,
|
|
422
|
+
proxyOf,
|
|
423
|
+
readDeepRegion,
|
|
427
424
|
reduceSurface,
|
|
428
425
|
renderLayerEffects,
|
|
429
426
|
scaleLayerEffect,
|
|
427
|
+
srgbDecode,
|
|
428
|
+
srgbEncode,
|
|
429
|
+
surfaceDepth,
|
|
430
|
+
surfaceFromDeepPixels,
|
|
431
|
+
tileSamples,
|
|
432
|
+
tileSamples16,
|
|
433
|
+
tileSamples32,
|
|
434
|
+
to16From32,
|
|
435
|
+
to16From8,
|
|
436
|
+
to32From16,
|
|
437
|
+
to32From8,
|
|
438
|
+
to8From16,
|
|
439
|
+
to8From32,
|
|
430
440
|
transformSurface,
|
|
431
441
|
transformedBounds,
|
|
432
442
|
validateEffectGradient,
|
|
433
443
|
validateLayerEffect,
|
|
434
444
|
validateLayerEffects,
|
|
435
|
-
warpSurface
|
|
436
|
-
|
|
445
|
+
warpSurface,
|
|
446
|
+
workingToSamples,
|
|
447
|
+
writeDeepRegion
|
|
448
|
+
} from "../chunk-C6C5PDKU.js";
|
|
437
449
|
import {
|
|
438
450
|
applyHomography,
|
|
439
451
|
boxQuad,
|
|
@@ -443,6 +455,37 @@ import {
|
|
|
443
455
|
multiplyHomographies,
|
|
444
456
|
rectToQuad
|
|
445
457
|
} from "../chunk-GX332W2C.js";
|
|
458
|
+
import {
|
|
459
|
+
DEFAULT_RETOUCH,
|
|
460
|
+
RETOUCH_SAMPLES,
|
|
461
|
+
RegionPixelSource,
|
|
462
|
+
RetouchDabStroke,
|
|
463
|
+
SPOT_HEAL_TYPES,
|
|
464
|
+
TONE_RANGES,
|
|
465
|
+
TilePixelSource,
|
|
466
|
+
applyRetouchToTile,
|
|
467
|
+
bestOffset,
|
|
468
|
+
dilateMask,
|
|
469
|
+
dodgeBurn,
|
|
470
|
+
dodgeBurnCurve,
|
|
471
|
+
healPixels,
|
|
472
|
+
luma,
|
|
473
|
+
maskBounds,
|
|
474
|
+
membraneFill,
|
|
475
|
+
normalizeRetouch,
|
|
476
|
+
perDabStrength,
|
|
477
|
+
readSourceRegion,
|
|
478
|
+
rgbaViewOfGrey,
|
|
479
|
+
saturationOf,
|
|
480
|
+
seamlessClone,
|
|
481
|
+
smudgeEffect,
|
|
482
|
+
solveMembrane,
|
|
483
|
+
sponge,
|
|
484
|
+
spongeEffect,
|
|
485
|
+
spotHealPixels,
|
|
486
|
+
spotHealWindow,
|
|
487
|
+
toneOf
|
|
488
|
+
} from "../chunk-LKCYANMW.js";
|
|
446
489
|
import {
|
|
447
490
|
CoverageMap,
|
|
448
491
|
DEFAULT_BLENDER,
|
|
@@ -482,7 +525,7 @@ import {
|
|
|
482
525
|
tipReach,
|
|
483
526
|
tipShape,
|
|
484
527
|
walkDabs
|
|
485
|
-
} from "../chunk-
|
|
528
|
+
} from "../chunk-UBIXVXPR.js";
|
|
486
529
|
import {
|
|
487
530
|
BLEND_MODES,
|
|
488
531
|
PAINTERLY_BLEND_MODES,
|
|
@@ -536,21 +579,62 @@ import {
|
|
|
536
579
|
translateSurface,
|
|
537
580
|
uniformTile,
|
|
538
581
|
writeRegion
|
|
539
|
-
} from "../chunk-
|
|
582
|
+
} from "../chunk-XK5LZKBD.js";
|
|
583
|
+
import {
|
|
584
|
+
BUILTIN_PROFILES,
|
|
585
|
+
CmsError,
|
|
586
|
+
GamutCheck,
|
|
587
|
+
INTENTS,
|
|
588
|
+
Transform,
|
|
589
|
+
builtinProfile,
|
|
590
|
+
builtinProfileId,
|
|
591
|
+
canBeDestination,
|
|
592
|
+
encodeProfile,
|
|
593
|
+
parseProfile,
|
|
594
|
+
sameColors,
|
|
595
|
+
srgbProfile,
|
|
596
|
+
writeJpegIcc,
|
|
597
|
+
writePngIcc
|
|
598
|
+
} from "../chunk-BRLYOPLT.js";
|
|
540
599
|
import {
|
|
600
|
+
ZlibError,
|
|
601
|
+
adler32,
|
|
602
|
+
deflate,
|
|
603
|
+
deflateRaw,
|
|
604
|
+
inflate,
|
|
605
|
+
inflateRaw
|
|
606
|
+
} from "../chunk-6FCKOS2B.js";
|
|
607
|
+
import {
|
|
608
|
+
DEFAULT_AUTO_LEADING,
|
|
609
|
+
DEFAULT_TEXT_STYLE,
|
|
541
610
|
WARP_STYLES,
|
|
542
611
|
WARP_STYLE_LABELS,
|
|
543
612
|
applyMatrix,
|
|
544
613
|
bezierToSubpaths,
|
|
614
|
+
caretAt,
|
|
615
|
+
createCanvasTextShaper,
|
|
616
|
+
createMetricTextShaper,
|
|
617
|
+
createOutlineTextShaper,
|
|
618
|
+
cssFont,
|
|
619
|
+
cssFontFamily,
|
|
545
620
|
dragWarpMeshPoint,
|
|
546
621
|
evalWarpMesh,
|
|
622
|
+
fitParagraphRuns,
|
|
623
|
+
fitRuns,
|
|
624
|
+
fontOf,
|
|
547
625
|
identityWarpMesh,
|
|
548
626
|
invertMatrix,
|
|
627
|
+
layoutText,
|
|
549
628
|
multiplyMatrices,
|
|
629
|
+
paragraphRanges,
|
|
550
630
|
pullWarpMesh,
|
|
551
631
|
resizeWarpMesh,
|
|
632
|
+
restyleRange,
|
|
552
633
|
rotateMatrix,
|
|
553
634
|
roundSubpaths,
|
|
635
|
+
spliceRuns,
|
|
636
|
+
styleAt,
|
|
637
|
+
textIndexAt,
|
|
554
638
|
translateMatrix,
|
|
555
639
|
warpMeshColumns,
|
|
556
640
|
warpMeshEditPoints,
|
|
@@ -558,7 +642,7 @@ import {
|
|
|
558
642
|
warpMeshParamAt,
|
|
559
643
|
warpMeshRows,
|
|
560
644
|
warpPresetMesh
|
|
561
|
-
} from "../chunk-
|
|
645
|
+
} from "../chunk-VXR7XWMF.js";
|
|
562
646
|
import "../chunk-YCSFS3GK.js";
|
|
563
647
|
import "../chunk-ZRJMJIQU.js";
|
|
564
648
|
import "../chunk-TGD52FEZ.js";
|
|
@@ -913,6 +997,17 @@ var ImageEditorRenderer = class {
|
|
|
913
997
|
/** Which path the last base render took. */
|
|
914
998
|
lastBaseRoute = null;
|
|
915
999
|
comp = null;
|
|
1000
|
+
/** Colour transform from the document to the display (see `color-management`), or none. */
|
|
1001
|
+
display = null;
|
|
1002
|
+
displayKey = 0;
|
|
1003
|
+
/** Sets the viewport's display transform (document profile → sRGB, or a soft proof); `null` for none. */
|
|
1004
|
+
setDisplayTransform(t) {
|
|
1005
|
+
if (t === this.display) return;
|
|
1006
|
+
this.display = t;
|
|
1007
|
+
this.displayKey++;
|
|
1008
|
+
this.comp = null;
|
|
1009
|
+
this.cachedBaseKey = "";
|
|
1010
|
+
}
|
|
916
1011
|
depthMaps = /* @__PURE__ */ new Map();
|
|
917
1012
|
assetVersion = 0;
|
|
918
1013
|
scratch = null;
|
|
@@ -982,13 +1077,15 @@ var ImageEditorRenderer = class {
|
|
|
982
1077
|
`${projection.angle},${transform.flipH},${transform.flipV}`,
|
|
983
1078
|
`${adjustments.brightness},${adjustments.contrast},${adjustments.saturation},${adjustments.exposure},${adjustments.temperature},${adjustments.tint ?? 0},${adjustments.blur},${adjustments.opacity},p${normalizeProcessVersion(adjustments.process)}`,
|
|
984
1079
|
adjustmentLayersKey(adjustments.layers),
|
|
985
|
-
`${depthMask.enabled},${depthMask.maskSource},${depthMask.depthRange?.[0]}-${depthMask.depthRange?.[1]},${depthMask.softness},${depthMask.invert},${depthMask.mode}
|
|
1080
|
+
`${depthMask.enabled},${depthMask.maskSource},${depthMask.depthRange?.[0]}-${depthMask.depthRange?.[1]},${depthMask.softness},${depthMask.invert},${depthMask.mode}`,
|
|
1081
|
+
`d${this.displayKey}`
|
|
986
1082
|
].join("|");
|
|
987
1083
|
let baseCanvas = this.cachedBaseCanvas;
|
|
988
1084
|
if (!baseCanvas || this.cachedBaseKey !== baseKey) {
|
|
989
1085
|
let histogram = null;
|
|
990
1086
|
baseCanvas = await this.renderBaseImage(img, naturalWidth, naturalHeight, region, transform, state, (pixels) => {
|
|
991
1087
|
histogram = computeHistogram(pixels);
|
|
1088
|
+
this.display?.(pixels.data);
|
|
992
1089
|
});
|
|
993
1090
|
this.cachedBaseCanvas = baseCanvas;
|
|
994
1091
|
this.cachedBaseKey = baseKey;
|
|
@@ -1339,6 +1436,7 @@ var ImageEditorRenderer = class {
|
|
|
1339
1436
|
if (x1 <= x0 || y1 <= y0) return;
|
|
1340
1437
|
const img = ctx.createImageData(TILE, TILE);
|
|
1341
1438
|
img.data.set(data);
|
|
1439
|
+
this.display?.(img.data);
|
|
1342
1440
|
ctx.putImageData(img, ox, oy, x0, y0, x1 - x0, y1 - y0);
|
|
1343
1441
|
}
|
|
1344
1442
|
/**
|
|
@@ -1381,7 +1479,7 @@ var ImageEditorRenderer = class {
|
|
|
1381
1479
|
const rotated = getRotatedSize(region.width, region.height, transform);
|
|
1382
1480
|
const baseW = Math.max(1, Math.round(rotated.width));
|
|
1383
1481
|
const baseH = Math.max(1, Math.round(rotated.height));
|
|
1384
|
-
const key = `${comp.version}|${region.x},${region.y},${region.width},${region.height}|${normalizeAngle(transform.rotate)},${transform.flipH},${transform.flipV}|${opacity}|${blur}`;
|
|
1482
|
+
const key = `${this.displayKey}:${comp.version}|${region.x},${region.y},${region.width},${region.height}|${normalizeAngle(transform.rotate)},${transform.flipH},${transform.flipV}|${opacity}|${blur}`;
|
|
1385
1483
|
if (this.cachedPostCanvas && this.cachedPostKey === key) return { canvas: this.cachedPostCanvas, histogram: this.cachedPostHistogram };
|
|
1386
1484
|
const canvas = this.cachedPostCanvas && this.cachedPostCanvas.width === baseW && this.cachedPostCanvas.height === baseH ? this.cachedPostCanvas : this.createCanvas(baseW, baseH);
|
|
1387
1485
|
const ctx = canvas.getContext("2d");
|
|
@@ -2518,159 +2616,6 @@ function refineSelection(pixels, mask, params = {}) {
|
|
|
2518
2616
|
return createSelectionMask(mask.width, mask.height, out);
|
|
2519
2617
|
}
|
|
2520
2618
|
|
|
2521
|
-
// src/image-editor/layers/text-shapers.ts
|
|
2522
|
-
var GENERIC = /* @__PURE__ */ new Set(["serif", "sans-serif", "monospace", "cursive", "fantasy", "system-ui", "ui-serif", "ui-sans-serif", "ui-monospace", "ui-rounded", "math", "emoji", "fangsong"]);
|
|
2523
|
-
function cssFontFamily(family) {
|
|
2524
|
-
const f = family.trim();
|
|
2525
|
-
if (GENERIC.has(f.toLowerCase()) || f.includes(",") || /^["']/.test(f)) return f;
|
|
2526
|
-
return /^[A-Za-z_-][\w -]*$/.test(f) && !/\s{2}/.test(f) ? f : `"${f.replace(/"/g, '\\"')}"`;
|
|
2527
|
-
}
|
|
2528
|
-
function cssFont(font, family = font.family) {
|
|
2529
|
-
return `${font.italic ? "italic " : ""}${font.weight} ${font.size}px ${cssFontFamily(family)}`;
|
|
2530
|
-
}
|
|
2531
|
-
function createCanvasTextShaper(options = {}) {
|
|
2532
|
-
const make = options.createCanvas ?? ((w, h) => {
|
|
2533
|
-
if (typeof OffscreenCanvas !== "undefined") return new OffscreenCanvas(w, h);
|
|
2534
|
-
if (typeof document !== "undefined") {
|
|
2535
|
-
const c = document.createElement("canvas");
|
|
2536
|
-
c.width = w;
|
|
2537
|
-
c.height = h;
|
|
2538
|
-
return c;
|
|
2539
|
-
}
|
|
2540
|
-
return null;
|
|
2541
|
-
});
|
|
2542
|
-
const canvas = make(1, 1);
|
|
2543
|
-
const ctx = canvas?.getContext("2d");
|
|
2544
|
-
if (!ctx || typeof ctx.measureText !== "function") return null;
|
|
2545
|
-
const fallback = options.fallbackFamily ?? "sans-serif";
|
|
2546
|
-
const available = /* @__PURE__ */ new Map();
|
|
2547
|
-
const widths = /* @__PURE__ */ new Map();
|
|
2548
|
-
const metrics = /* @__PURE__ */ new Map();
|
|
2549
|
-
const probe = "mmmmmmmmmmlli1WQ@#";
|
|
2550
|
-
const width = (font, text) => {
|
|
2551
|
-
ctx.font = font;
|
|
2552
|
-
return ctx.measureText(text).width;
|
|
2553
|
-
};
|
|
2554
|
-
const isAvailable = (family) => {
|
|
2555
|
-
const f = family.trim();
|
|
2556
|
-
if (GENERIC.has(f.toLowerCase()) || f.includes(",")) return true;
|
|
2557
|
-
let hit = available.get(f);
|
|
2558
|
-
if (hit === void 0) {
|
|
2559
|
-
const q = cssFontFamily(f);
|
|
2560
|
-
hit = width(`72px ${q}, monospace`, probe) !== width("72px monospace", probe) || width(`72px ${q}, serif`, probe) !== width("72px serif", probe);
|
|
2561
|
-
available.set(f, hit);
|
|
2562
|
-
}
|
|
2563
|
-
return hit;
|
|
2564
|
-
};
|
|
2565
|
-
return {
|
|
2566
|
-
id: "canvas",
|
|
2567
|
-
resolve(font) {
|
|
2568
|
-
const ok = isAvailable(font.family);
|
|
2569
|
-
return { family: ok ? font.family : fallback, available: ok };
|
|
2570
|
-
},
|
|
2571
|
-
metrics(font) {
|
|
2572
|
-
const key = cssFont(font);
|
|
2573
|
-
let m = metrics.get(key);
|
|
2574
|
-
if (!m) {
|
|
2575
|
-
ctx.font = key;
|
|
2576
|
-
ctx.textBaseline = "alphabetic";
|
|
2577
|
-
const t = ctx.measureText("Hg");
|
|
2578
|
-
const ascent = t.emHeightAscent ?? t.fontBoundingBoxAscent ?? font.size * 0.8;
|
|
2579
|
-
const descent = t.emHeightDescent ?? t.fontBoundingBoxDescent ?? font.size * 0.2;
|
|
2580
|
-
m = { ascent: Number.isFinite(ascent) ? ascent : font.size * 0.8, descent: Number.isFinite(descent) ? descent : font.size * 0.2 };
|
|
2581
|
-
metrics.set(key, m);
|
|
2582
|
-
}
|
|
2583
|
-
return m;
|
|
2584
|
-
},
|
|
2585
|
-
measure(text, font) {
|
|
2586
|
-
const key = `${cssFont(font)}|${text}`;
|
|
2587
|
-
let w = widths.get(key);
|
|
2588
|
-
if (!w) {
|
|
2589
|
-
ctx.font = cssFont(font);
|
|
2590
|
-
w = [0];
|
|
2591
|
-
for (let i = 1; i <= text.length; i++) w.push(ctx.measureText(text.slice(0, i)).width);
|
|
2592
|
-
if (widths.size > 2e3) widths.clear();
|
|
2593
|
-
widths.set(key, w);
|
|
2594
|
-
}
|
|
2595
|
-
return w;
|
|
2596
|
-
},
|
|
2597
|
-
draw(target, text, font, x, y) {
|
|
2598
|
-
target.font = cssFont(font);
|
|
2599
|
-
target.textBaseline = "alphabetic";
|
|
2600
|
-
target.textAlign = "left";
|
|
2601
|
-
target.fillText(text, x, y);
|
|
2602
|
-
},
|
|
2603
|
-
families() {
|
|
2604
|
-
const out = /* @__PURE__ */ new Set(["sans-serif", "serif", "monospace"]);
|
|
2605
|
-
const fonts = typeof document !== "undefined" ? document.fonts : void 0;
|
|
2606
|
-
if (fonts && typeof fonts.forEach === "function") fonts.forEach((f) => out.add(f.family.replace(/^["']|["']$/g, "")));
|
|
2607
|
-
return [...out];
|
|
2608
|
-
}
|
|
2609
|
-
};
|
|
2610
|
-
}
|
|
2611
|
-
function createOutlineTextShaper(fonts, options = {}) {
|
|
2612
|
-
if (!fonts.length) throw new Error("createOutlineTextShaper needs at least one font.");
|
|
2613
|
-
const entries = fonts.map((f) => {
|
|
2614
|
-
const e = "face" in f ? f : { face: f };
|
|
2615
|
-
const names = e.face.names;
|
|
2616
|
-
const family = e.family ?? names?.fontFamily?.en ?? Object.values(names?.fontFamily ?? {})[0] ?? "Font";
|
|
2617
|
-
const weight = e.weight ?? e.face.tables?.os2?.usWeightClass ?? 400;
|
|
2618
|
-
const italic = e.italic ?? Boolean((e.face.tables?.os2?.fsSelection ?? 0) & 1);
|
|
2619
|
-
const postScript = names?.postScriptName?.en ?? Object.values(names?.postScriptName ?? {})[0];
|
|
2620
|
-
return { face: e.face, family, key: family.toLowerCase(), weight, italic, postScript };
|
|
2621
|
-
});
|
|
2622
|
-
const fallbackKey = (options.fallback ?? entries[0].family).toLowerCase();
|
|
2623
|
-
const pick2 = (font) => {
|
|
2624
|
-
if (font.postScriptName) {
|
|
2625
|
-
const exact = entries.find((e) => e.postScript === font.postScriptName);
|
|
2626
|
-
if (exact) return { entry: exact, available: true };
|
|
2627
|
-
}
|
|
2628
|
-
const key = font.family.trim().toLowerCase();
|
|
2629
|
-
let pool = entries.filter((e) => e.key === key);
|
|
2630
|
-
const available = pool.length > 0;
|
|
2631
|
-
if (!available) pool = entries.filter((e) => e.key === fallbackKey);
|
|
2632
|
-
if (!pool.length) pool = entries;
|
|
2633
|
-
const styled = pool.filter((e) => e.italic === font.italic);
|
|
2634
|
-
const list = styled.length ? styled : pool;
|
|
2635
|
-
const entry = list.reduce((best, e) => Math.abs(e.weight - font.weight) < Math.abs(best.weight - font.weight) ? e : best);
|
|
2636
|
-
return { entry, available };
|
|
2637
|
-
};
|
|
2638
|
-
const toPath = (cmds) => {
|
|
2639
|
-
const out = [];
|
|
2640
|
-
for (const c of cmds) {
|
|
2641
|
-
if (c.type === "M" || c.type === "L") out.push({ type: c.type, x: c.x, y: c.y });
|
|
2642
|
-
else if (c.type === "C") out.push({ type: "C", x1: c.x1, y1: c.y1, x2: c.x2, y2: c.y2, x: c.x, y: c.y });
|
|
2643
|
-
else if (c.type === "Q") out.push({ type: "Q", x1: c.x1, y1: c.y1, x: c.x, y: c.y });
|
|
2644
|
-
else out.push({ type: "Z" });
|
|
2645
|
-
}
|
|
2646
|
-
return out;
|
|
2647
|
-
};
|
|
2648
|
-
return {
|
|
2649
|
-
id: options.id ?? `outline:${entries.map((e) => `${e.family}/${e.weight}${e.italic ? "i" : ""}`).join(",")}`,
|
|
2650
|
-
resolve(font) {
|
|
2651
|
-
const { entry, available } = pick2(font);
|
|
2652
|
-
return { family: entry.family, available };
|
|
2653
|
-
},
|
|
2654
|
-
metrics(font) {
|
|
2655
|
-
const { face } = pick2(font).entry;
|
|
2656
|
-
const k = font.size / face.unitsPerEm;
|
|
2657
|
-
return { ascent: face.ascender * k, descent: -face.descender * k };
|
|
2658
|
-
},
|
|
2659
|
-
measure(text, font) {
|
|
2660
|
-
const { face } = pick2(font).entry;
|
|
2661
|
-
const out = [0];
|
|
2662
|
-
for (let i = 1; i <= text.length; i++) out.push(face.getAdvanceWidth(text.slice(0, i), font.size, { kerning: true }));
|
|
2663
|
-
return out;
|
|
2664
|
-
},
|
|
2665
|
-
outline(text, font, x, y) {
|
|
2666
|
-
return toPath(pick2(font).entry.face.getPath(text, x, y, font.size, { kerning: true }).commands);
|
|
2667
|
-
},
|
|
2668
|
-
families() {
|
|
2669
|
-
return [...new Set(entries.map((e) => e.family))];
|
|
2670
|
-
}
|
|
2671
|
-
};
|
|
2672
|
-
}
|
|
2673
|
-
|
|
2674
2619
|
// src/image-editor/free-transform.ts
|
|
2675
2620
|
var FREE_TRANSFORM_CONTENTS = ["auto", "layer", "selection", "selection-copy", "selection-outline"];
|
|
2676
2621
|
var fail = (message) => {
|
|
@@ -3151,8 +3096,146 @@ var FreeTransformSession = class {
|
|
|
3151
3096
|
}
|
|
3152
3097
|
};
|
|
3153
3098
|
|
|
3099
|
+
// src/image-editor/color-management.ts
|
|
3100
|
+
var DEFAULT_PROOF = { intent: "relative", bpc: true, simulatePaper: false, gamutWarning: false };
|
|
3101
|
+
var resolved = /* @__PURE__ */ new WeakMap();
|
|
3102
|
+
var fromBytes = /* @__PURE__ */ new Map();
|
|
3103
|
+
function resolveProfile(doc) {
|
|
3104
|
+
if (!doc) return srgbProfile();
|
|
3105
|
+
const hit = resolved.get(doc);
|
|
3106
|
+
if (hit) return hit;
|
|
3107
|
+
let p;
|
|
3108
|
+
if (doc.builtin) p = builtinProfile(doc.builtin);
|
|
3109
|
+
else if (doc.icc) {
|
|
3110
|
+
const key = bytesToBase64(doc.icc);
|
|
3111
|
+
p = fromBytes.get(key) ?? parseProfile(doc.icc);
|
|
3112
|
+
if (fromBytes.size > 32) fromBytes.clear();
|
|
3113
|
+
fromBytes.set(key, p);
|
|
3114
|
+
} else p = srgbProfile();
|
|
3115
|
+
resolved.set(doc, p);
|
|
3116
|
+
return p;
|
|
3117
|
+
}
|
|
3118
|
+
function profileBytes(doc) {
|
|
3119
|
+
if (doc?.icc) return doc.icc;
|
|
3120
|
+
const p = resolveProfile(doc);
|
|
3121
|
+
return p.bytes ?? encodeProfile(p);
|
|
3122
|
+
}
|
|
3123
|
+
function documentProfile(input) {
|
|
3124
|
+
if (typeof input === "string") {
|
|
3125
|
+
const id = builtinProfileId(input);
|
|
3126
|
+
if (!id) throw new CmsError(`Unknown colour profile "${input}"; use one of ${BUILTIN_PROFILES.map((b) => b.id).join(", ")}, or ICC bytes.`);
|
|
3127
|
+
return { builtin: id, name: BUILTIN_PROFILES.find((b) => b.id === id).description };
|
|
3128
|
+
}
|
|
3129
|
+
const p = parseProfile(input);
|
|
3130
|
+
return { name: p.description || "Embedded profile", icc: input.slice() };
|
|
3131
|
+
}
|
|
3132
|
+
function isSrgb(doc) {
|
|
3133
|
+
if (!doc || doc.builtin === "srgb") return true;
|
|
3134
|
+
try {
|
|
3135
|
+
return sameColors(resolveProfile(doc), srgbProfile(), builtinProfile("lab-d50"));
|
|
3136
|
+
} catch {
|
|
3137
|
+
return false;
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
function serializeColorProfile(doc) {
|
|
3141
|
+
return doc.builtin ? { builtin: doc.builtin, name: doc.name } : { name: doc.name, icc: bytesToBase64(doc.icc ?? new Uint8Array(0)) };
|
|
3142
|
+
}
|
|
3143
|
+
function deserializeColorProfile(raw, problems) {
|
|
3144
|
+
const r = raw;
|
|
3145
|
+
if (!r || typeof r !== "object") return null;
|
|
3146
|
+
try {
|
|
3147
|
+
if (r.builtin) {
|
|
3148
|
+
const id = builtinProfileId(r.builtin);
|
|
3149
|
+
if (!id) throw new CmsError(`unknown built-in profile "${r.builtin}"`);
|
|
3150
|
+
return documentProfile(id);
|
|
3151
|
+
}
|
|
3152
|
+
if (typeof r.icc === "string") {
|
|
3153
|
+
const icc = base64ToBytes(r.icc);
|
|
3154
|
+
parseProfile(icc);
|
|
3155
|
+
return { name: typeof r.name === "string" ? r.name : "Embedded profile", icc };
|
|
3156
|
+
}
|
|
3157
|
+
} catch (e) {
|
|
3158
|
+
problems.push(`The colour profile could not be read (${e instanceof Error ? e.message : String(e)}); the document is treated as sRGB`);
|
|
3159
|
+
}
|
|
3160
|
+
return null;
|
|
3161
|
+
}
|
|
3162
|
+
function displayTransform(doc, proof) {
|
|
3163
|
+
const src = resolveProfile(doc);
|
|
3164
|
+
const display = srgbProfile();
|
|
3165
|
+
let t = null;
|
|
3166
|
+
let gamut = null;
|
|
3167
|
+
if (proof) {
|
|
3168
|
+
const p = resolveProfile(proof.profile);
|
|
3169
|
+
t = Transform.proof(src, p, display, proof.intent, proof.bpc, proof.simulatePaper);
|
|
3170
|
+
if (proof.gamutWarning) gamut = new GamutCheck(src, p, builtinProfile("lab-d50"));
|
|
3171
|
+
} else if (!isSrgb(doc)) t = Transform.create(src, display, { intent: "relative", bpc: true });
|
|
3172
|
+
if (!t) return null;
|
|
3173
|
+
const tr = t;
|
|
3174
|
+
const g = gamut;
|
|
3175
|
+
return (rgba) => {
|
|
3176
|
+
if (g) {
|
|
3177
|
+
const px2 = [0, 0, 0];
|
|
3178
|
+
const flags = new Uint8Array(rgba.length >> 2);
|
|
3179
|
+
for (let i = 0, p = 0; i < rgba.length; i += 4, p++) {
|
|
3180
|
+
if (rgba[i + 3] === 0) continue;
|
|
3181
|
+
px2[0] = rgba[i] / 255;
|
|
3182
|
+
px2[1] = rgba[i + 1] / 255;
|
|
3183
|
+
px2[2] = rgba[i + 2] / 255;
|
|
3184
|
+
if (g.outOfGamut(px2)) flags[p] = 1;
|
|
3185
|
+
}
|
|
3186
|
+
tr.convert8(rgba, 4, rgba, 4);
|
|
3187
|
+
for (let i = 0, p = 0; i < rgba.length; i += 4, p++) if (flags[p]) rgba[i] = rgba[i + 1] = rgba[i + 2] = 128;
|
|
3188
|
+
return;
|
|
3189
|
+
}
|
|
3190
|
+
tr.convert8(rgba, 4, rgba, 4);
|
|
3191
|
+
};
|
|
3192
|
+
}
|
|
3193
|
+
function convertHex(hex, from, to, intent = "relative", bpc = true) {
|
|
3194
|
+
const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim());
|
|
3195
|
+
if (!m) return hex;
|
|
3196
|
+
const n = parseInt(m[1], 16);
|
|
3197
|
+
const out = new Float32Array(4);
|
|
3198
|
+
Transform.create(from, to, { intent, bpc }).eval([(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255], out);
|
|
3199
|
+
const c = (v) => Math.round(Math.min(1, Math.max(0, v)) * 255).toString(16).padStart(2, "0");
|
|
3200
|
+
return `#${c(out[0])}${c(out[1])}${c(out[2])}`;
|
|
3201
|
+
}
|
|
3202
|
+
function convertSurfaceColors(surface, t) {
|
|
3203
|
+
if (surface.channels !== 4 || t.inputs !== 3 || t.outputs !== 3) return surface;
|
|
3204
|
+
const tiles = /* @__PURE__ */ new Map();
|
|
3205
|
+
const memo = /* @__PURE__ */ new Map();
|
|
3206
|
+
for (const [key, tile] of surface.tiles) {
|
|
3207
|
+
let next = memo.get(tile);
|
|
3208
|
+
if (next === void 0) {
|
|
3209
|
+
if (tile.deep instanceof Uint16Array) {
|
|
3210
|
+
const out = new Uint16Array(tile.deep.length);
|
|
3211
|
+
t.convert16(tile.deep, 4, out, 4);
|
|
3212
|
+
next = deepTile(out, 4, surface.defaultValue);
|
|
3213
|
+
} else if (tile.deep instanceof Float32Array) {
|
|
3214
|
+
const enc = new Float32Array(tile.deep.length);
|
|
3215
|
+
for (let i = 0; i < enc.length; i += 4) {
|
|
3216
|
+
for (let c = 0; c < 3; c++) enc[i + c] = srgbEncode(tile.deep[i + c]);
|
|
3217
|
+
enc[i + 3] = tile.deep[i + 3];
|
|
3218
|
+
}
|
|
3219
|
+
t.convertFloat(enc, 4, enc, 4);
|
|
3220
|
+
for (let i = 0; i < enc.length; i += 4) for (let c = 0; c < 3; c++) enc[i + c] = srgbDecode(enc[i + c]);
|
|
3221
|
+
next = deepTile(enc, 4, surface.defaultValue);
|
|
3222
|
+
} else {
|
|
3223
|
+
const out = new Uint8ClampedArray(tile.data.length);
|
|
3224
|
+
t.convert8(tile.data, 4, out, 4);
|
|
3225
|
+
next = finishTile(out, 4, surface.defaultValue);
|
|
3226
|
+
}
|
|
3227
|
+
memo.set(tile, next);
|
|
3228
|
+
}
|
|
3229
|
+
if (next) tiles.set(key, next);
|
|
3230
|
+
}
|
|
3231
|
+
return new TiledSurface(4, tiles, surface.defaultValue);
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3154
3234
|
// src/image-editor/controller.ts
|
|
3155
3235
|
var IMAGE_EDITOR_SCHEMA_VERSION = LAYERED_SCHEMA_VERSION;
|
|
3236
|
+
var RETOUCH_TOOLS = ["clone", "heal", "spot-heal", "smudge", "dodge", "burn", "sponge"];
|
|
3237
|
+
var isRetouchTool = (tool) => RETOUCH_TOOLS.includes(tool);
|
|
3238
|
+
var STROKE_TOOLS = ["brush", "eraser", "mixer", ...RETOUCH_TOOLS];
|
|
3156
3239
|
function paintCoverageInto(base, coverage, apply, rects) {
|
|
3157
3240
|
const tiles = new Map(base.tiles);
|
|
3158
3241
|
const done = /* @__PURE__ */ new Set();
|
|
@@ -3186,6 +3269,18 @@ var PaintStrokeSession = class {
|
|
|
3186
3269
|
if (stroke instanceof StrokeCoverage) {
|
|
3187
3270
|
this.stroke = stroke;
|
|
3188
3271
|
this.mixer = null;
|
|
3272
|
+
} else if ("kind" in stroke && stroke.kind === "coverage") {
|
|
3273
|
+
this.stroke = stroke.coverage;
|
|
3274
|
+
this.mixer = null;
|
|
3275
|
+
this.retouch = stroke;
|
|
3276
|
+
} else if ("kind" in stroke) {
|
|
3277
|
+
this.stroke = null;
|
|
3278
|
+
this.retouch = stroke;
|
|
3279
|
+
if (this.base.channels === 1) {
|
|
3280
|
+
const view = rgbaViewOfGrey(this.mixerPixels());
|
|
3281
|
+
this.mixer = stroke.create(view.target);
|
|
3282
|
+
this.flushView = view.flush;
|
|
3283
|
+
} else this.mixer = stroke.create(this.mixerPixels());
|
|
3189
3284
|
} else {
|
|
3190
3285
|
this.stroke = null;
|
|
3191
3286
|
this.mixer = stroke.create(this.mixerPixels());
|
|
@@ -3202,10 +3297,20 @@ var PaintStrokeSession = class {
|
|
|
3202
3297
|
stroke;
|
|
3203
3298
|
mixer;
|
|
3204
3299
|
mixerInit = null;
|
|
3300
|
+
retouch = null;
|
|
3301
|
+
/** Writes a mask's RGBA view back (sequential retouching on a mask). */
|
|
3302
|
+
flushView = null;
|
|
3303
|
+
started = false;
|
|
3205
3304
|
/** Mixer tiles written since the last update: they get new tile objects so the compositor sees them change. */
|
|
3206
3305
|
fresh = /* @__PURE__ */ new Set();
|
|
3207
3306
|
last = null;
|
|
3208
3307
|
closed = false;
|
|
3308
|
+
/** The new tile of a coverage stroke over the pre-stroke tile `src`. */
|
|
3309
|
+
compositeTile(src, tx, ty, options = {}, paint) {
|
|
3310
|
+
const r = this.retouch;
|
|
3311
|
+
if (r && r.kind === "coverage") return applyRetouchToTile(this.stroke.coverage, src, this.base.channels, tx, ty, paint ?? r.paint(), r.apply, options);
|
|
3312
|
+
return applyStrokeToTile(this.stroke.coverage, src, this.base.channels, tx, ty, this.apply, options);
|
|
3313
|
+
}
|
|
3209
3314
|
/** The live pixels a mixer stroke reads and writes; base tiles are copied on first write. */
|
|
3210
3315
|
mixerPixels() {
|
|
3211
3316
|
const a = this.apply;
|
|
@@ -3232,8 +3337,14 @@ var PaintStrokeSession = class {
|
|
|
3232
3337
|
/** Adds input points (document pixels); the stack shows the stroke so far. */
|
|
3233
3338
|
push(points) {
|
|
3234
3339
|
if (this.closed || !points.length) return;
|
|
3340
|
+
if (!this.started) {
|
|
3341
|
+
this.started = true;
|
|
3342
|
+
if (this.retouch?.kind === "coverage") this.retouch.start(points[0]);
|
|
3343
|
+
}
|
|
3235
3344
|
this.last = points[points.length - 1];
|
|
3236
|
-
this.
|
|
3345
|
+
const rects = this.mixer ? this.mixer.push(points) : this.stroke.push(points);
|
|
3346
|
+
this.flushView?.();
|
|
3347
|
+
this.update(rects);
|
|
3237
3348
|
}
|
|
3238
3349
|
update(rects) {
|
|
3239
3350
|
if (!rects.length) return;
|
|
@@ -3257,7 +3368,7 @@ var PaintStrokeSession = class {
|
|
|
3257
3368
|
const src = this.base.tiles.get(key)?.data ?? defaultTile(this.base);
|
|
3258
3369
|
const prev = this.touched.has(key) ? this.live.get(key)?.data : void 0;
|
|
3259
3370
|
this.touched.add(key);
|
|
3260
|
-
const data =
|
|
3371
|
+
const data = this.compositeTile(src, tx, ty, prev ? { into: prev, region: r } : {});
|
|
3261
3372
|
this.live.set(key, { data, uniform: null });
|
|
3262
3373
|
}
|
|
3263
3374
|
this.controller._strokeUpdate(this.target.id, this.target.channel, new TiledSurface(ch, new Map(this.live), this.base.defaultValue), rects);
|
|
@@ -3266,9 +3377,22 @@ var PaintStrokeSession = class {
|
|
|
3266
3377
|
end() {
|
|
3267
3378
|
if (this.closed) throw new LayerOperationError("This stroke has already ended.");
|
|
3268
3379
|
const last = this.last ?? void 0;
|
|
3269
|
-
this.
|
|
3380
|
+
const rects = this.mixer ? this.mixer.finish(last) : this.stroke.finish(last);
|
|
3381
|
+
this.flushView?.();
|
|
3382
|
+
this.update(rects);
|
|
3270
3383
|
this.closed = true;
|
|
3271
3384
|
const ch = this.base.channels;
|
|
3385
|
+
const r = this.retouch;
|
|
3386
|
+
if (r?.kind === "coverage" && r.finalPaint) {
|
|
3387
|
+
const final = r.finalPaint(this.stroke.coverage);
|
|
3388
|
+
if (final) {
|
|
3389
|
+
for (const key of this.touched) {
|
|
3390
|
+
const { tx, ty } = tileCoord(key);
|
|
3391
|
+
const src = this.base.tiles.get(key)?.data ?? defaultTile(this.base);
|
|
3392
|
+
this.live.set(key, { data: this.compositeTile(src, tx, ty, {}, final), uniform: null });
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
}
|
|
3272
3396
|
const tiles = new Map(this.base.tiles);
|
|
3273
3397
|
let changed = false;
|
|
3274
3398
|
for (const key of this.touched) {
|
|
@@ -3280,11 +3404,12 @@ var PaintStrokeSession = class {
|
|
|
3280
3404
|
if (!sameTile(prev, tile)) changed = true;
|
|
3281
3405
|
}
|
|
3282
3406
|
const surface = changed ? new TiledSurface(ch, tiles, this.base.defaultValue) : null;
|
|
3283
|
-
const mixerLoad = this.
|
|
3407
|
+
const mixerLoad = this.mixerInit ? this.mixerInit.end(this.mixer.load) : void 0;
|
|
3284
3408
|
this.controller._strokeCommit(this.before, this.target.id, this.target.channel, surface);
|
|
3285
3409
|
const b = this.mixer ? this.mixer.bounds : this.stroke.coverage.bounds;
|
|
3286
3410
|
const result = { layerId: this.target.id, channel: this.target.channel, bounds: changed ? b : null, dabs: (this.mixer ?? this.stroke).dabCount };
|
|
3287
3411
|
if (mixerLoad) result.mixerLoad = mixerLoad;
|
|
3412
|
+
if (r?.result) Object.assign(result, r.result());
|
|
3288
3413
|
return result;
|
|
3289
3414
|
}
|
|
3290
3415
|
/** Abandons the stroke and restores the layer. */
|
|
@@ -3423,7 +3548,7 @@ var ImageEditorController = class {
|
|
|
3423
3548
|
activeLayerId: null,
|
|
3424
3549
|
activeTarget: "pixels",
|
|
3425
3550
|
outputOpacity: 1,
|
|
3426
|
-
paint: { brush: { ...DEFAULT_BRUSH }, foreground: "#000000", background: "#ffffff", mixer: { ...DEFAULT_MIXER }, mixerLoad: EMPTY_MIXER_LOAD },
|
|
3551
|
+
paint: { brush: { ...DEFAULT_BRUSH }, foreground: "#000000", background: "#ffffff", mixer: { ...DEFAULT_MIXER }, mixerLoad: EMPTY_MIXER_LOAD, retouch: { ...DEFAULT_RETOUCH }, cloneSource: null },
|
|
3427
3552
|
fit: options.fit ?? "margin"
|
|
3428
3553
|
};
|
|
3429
3554
|
if (options.image instanceof Object && "width" in options.image) {
|
|
@@ -3518,10 +3643,23 @@ var ImageEditorController = class {
|
|
|
3518
3643
|
if (strokeRects && this.pendingDirty !== "all") this.pendingDirty.push(...strokeRects);
|
|
3519
3644
|
else if (!strokeRects) this.pendingDirty = "all";
|
|
3520
3645
|
this.syncViews();
|
|
3646
|
+
this.syncDisplay();
|
|
3521
3647
|
const currentState = { ...this.state };
|
|
3522
3648
|
this.onStateChange?.(currentState);
|
|
3523
3649
|
this.listeners.forEach((l) => l(currentState));
|
|
3524
3650
|
}
|
|
3651
|
+
displayFor = null;
|
|
3652
|
+
/** Keeps the renderer's display transform in step with the document profile and Proof Setup. */
|
|
3653
|
+
syncDisplay() {
|
|
3654
|
+
const s = this.state;
|
|
3655
|
+
if (this.displayFor && this.displayFor[0] === s.colorProfile && this.displayFor[1] === s.proofSetup) return;
|
|
3656
|
+
this.displayFor = [s.colorProfile, s.proofSetup];
|
|
3657
|
+
try {
|
|
3658
|
+
this.renderer.setDisplayTransform(displayTransform(s.colorProfile, s.proofSetup));
|
|
3659
|
+
} catch {
|
|
3660
|
+
this.renderer.setDisplayTransform(null);
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3525
3663
|
/** Recomputes the compatibility views (`adjustments`, `depthMask`, `annotations`) when the stack changed. */
|
|
3526
3664
|
syncViews() {
|
|
3527
3665
|
const s = this.state;
|
|
@@ -3559,6 +3697,8 @@ var ImageEditorController = class {
|
|
|
3559
3697
|
if (s.legacyOutputBlur !== void 0) entry.legacyOutputBlur = s.legacyOutputBlur;
|
|
3560
3698
|
if (s.globalLight) entry.globalLight = { ...s.globalLight };
|
|
3561
3699
|
if (s.patterns) entry.patterns = s.patterns;
|
|
3700
|
+
if (s.colorProfile) entry.colorProfile = s.colorProfile;
|
|
3701
|
+
if (s.bitDepth && s.bitDepth !== 8) entry.bitDepth = s.bitDepth;
|
|
3562
3702
|
return entry;
|
|
3563
3703
|
}
|
|
3564
3704
|
restoreHistoryEntry(entry) {
|
|
@@ -3575,6 +3715,10 @@ var ImageEditorController = class {
|
|
|
3575
3715
|
else delete s.globalLight;
|
|
3576
3716
|
if (entry.patterns) s.patterns = entry.patterns;
|
|
3577
3717
|
else delete s.patterns;
|
|
3718
|
+
if (entry.bitDepth) s.bitDepth = entry.bitDepth;
|
|
3719
|
+
else delete s.bitDepth;
|
|
3720
|
+
if (entry.colorProfile) s.colorProfile = entry.colorProfile;
|
|
3721
|
+
else delete s.colorProfile;
|
|
3578
3722
|
s.selection = entry.selection;
|
|
3579
3723
|
if (this.imageElement !== entry.source.element && !this.samplePixelsOverride) this.samplePixels = null;
|
|
3580
3724
|
this.imageElement = entry.source.element;
|
|
@@ -4858,9 +5002,28 @@ var ImageEditorController = class {
|
|
|
4858
5002
|
throw new LayerOperationError("Merging annotation layers needs a canvas (a browser); hide or delete them first, or merge in the browser.");
|
|
4859
5003
|
}
|
|
4860
5004
|
await this.renderer.prepareLayerAssets(layers, width, height);
|
|
4861
|
-
const
|
|
5005
|
+
const sources = this.renderer.compositorSources(width, height);
|
|
5006
|
+
const depth = this.bitDepth;
|
|
5007
|
+
if (depth !== 8) {
|
|
5008
|
+
const samples = workingToSamples(compositeDeep(layers, rect, sources, depth), depth);
|
|
5009
|
+
return surfaceFromDeepPixels({ width: rect.width, height: rect.height, data: samples }, 4, rect.x, rect.y);
|
|
5010
|
+
}
|
|
5011
|
+
const data = new LayerCompositor().composite(layers, rect, sources);
|
|
4862
5012
|
return surfaceFromPixels({ width: rect.width, height: rect.height, data }, 4, rect.x, rect.y);
|
|
4863
5013
|
}
|
|
5014
|
+
/**
|
|
5015
|
+
* The composite of the layer stack at the document's bit depth (as `compositePixels`, no
|
|
5016
|
+
* canvas): 16-bit sRGB samples, or 32-bit linear float, over `rect` (default the canvas). At 8
|
|
5017
|
+
* bits, the 8-bit composite widened to 16 bits.
|
|
5018
|
+
*/
|
|
5019
|
+
compositeDeepPixels(rect) {
|
|
5020
|
+
const { width, height } = this.naturalSize;
|
|
5021
|
+
const r = rect ?? { x: 0, y: 0, width, height };
|
|
5022
|
+
const depth = this.bitDepth;
|
|
5023
|
+
if (depth === 8) return { width: r.width, height: r.height, depth: 16, data: to16From8(this.compositePixels(r).data) };
|
|
5024
|
+
const data = workingToSamples(compositeDeep(this.state.layers, r, this.compositorSources(), depth), depth);
|
|
5025
|
+
return { width: r.width, height: r.height, depth, data };
|
|
5026
|
+
}
|
|
4864
5027
|
/** Document bounds a layer can paint into (pixel content, annotations, or the canvas for the rest). */
|
|
4865
5028
|
paintBounds(layer) {
|
|
4866
5029
|
const { width, height } = this.naturalSize;
|
|
@@ -5018,12 +5181,16 @@ var ImageEditorController = class {
|
|
|
5018
5181
|
/** Brush settings, colours and tool options of the paint tools (view state, no undo). */
|
|
5019
5182
|
setPaintOptions(options) {
|
|
5020
5183
|
const cur = this.state.paint;
|
|
5184
|
+
const retouch = options.retouch ? normalizeRetouch(options.retouch, cur.retouch) : cur.retouch;
|
|
5185
|
+
const cloneSource = cur.cloneSource && retouch.aligned !== cur.retouch.aligned ? { ...cur.cloneSource, offset: null } : cur.cloneSource;
|
|
5021
5186
|
const next = {
|
|
5022
5187
|
brush: options.brush ? normalizeBrush({ ...cur.brush, ...options.brush }) : cur.brush,
|
|
5023
5188
|
foreground: options.foreground !== void 0 ? toHex(parseColor(options.foreground)) : cur.foreground,
|
|
5024
5189
|
background: options.background !== void 0 ? toHex(parseColor(options.background)) : cur.background,
|
|
5025
5190
|
mixer: options.mixer ? normalizeMixer({ ...cur.mixer, ...options.mixer }) : cur.mixer,
|
|
5026
|
-
mixerLoad: cur.mixerLoad
|
|
5191
|
+
mixerLoad: cur.mixerLoad,
|
|
5192
|
+
retouch,
|
|
5193
|
+
cloneSource
|
|
5027
5194
|
};
|
|
5028
5195
|
this.state.paint = next;
|
|
5029
5196
|
this.notify();
|
|
@@ -5053,6 +5220,17 @@ var ImageEditorController = class {
|
|
|
5053
5220
|
this.notify();
|
|
5054
5221
|
return EMPTY_MIXER_LOAD;
|
|
5055
5222
|
}
|
|
5223
|
+
/**
|
|
5224
|
+
* Sets (Alt-click) or clears the Clone Stamp and Healing Brush source point, in document
|
|
5225
|
+
* pixels. The next stroke fixes the offset from its first point. View state, no undo.
|
|
5226
|
+
*/
|
|
5227
|
+
setCloneSource(point) {
|
|
5228
|
+
if (point && (!Number.isFinite(point.x) || !Number.isFinite(point.y))) throw new LayerOperationError("The clone source needs finite x and y (document pixels).");
|
|
5229
|
+
const source = point ? { x: Math.round(point.x), y: Math.round(point.y), offset: null } : null;
|
|
5230
|
+
this.state.paint = { ...this.state.paint, cloneSource: source };
|
|
5231
|
+
this.notify();
|
|
5232
|
+
return source;
|
|
5233
|
+
}
|
|
5056
5234
|
/** D: black foreground, white background. */
|
|
5057
5235
|
resetPaintColors() {
|
|
5058
5236
|
this.setPaintOptions({ foreground: "#000000", background: "#ffffff" });
|
|
@@ -5107,8 +5285,8 @@ var ImageEditorController = class {
|
|
|
5107
5285
|
*/
|
|
5108
5286
|
beginStroke(init = {}) {
|
|
5109
5287
|
const tool = init.tool ?? "brush";
|
|
5110
|
-
if (tool
|
|
5111
|
-
const target = this.paintTarget(init.layerId, init.channel, tool === "eraser" ? "erase" : "paint");
|
|
5288
|
+
if (!STROKE_TOOLS.includes(tool)) throw new LayerOperationError(`Unknown paint tool "${String(tool)}"; use one of ${STROKE_TOOLS.join(", ")}.`);
|
|
5289
|
+
const target = this.paintTarget(init.layerId, init.channel, tool === "eraser" ? "erase" : isRetouchTool(tool) ? "retouch" : "paint");
|
|
5112
5290
|
if (tool === "mixer" && target.channel !== "pixels") {
|
|
5113
5291
|
throw new LayerOperationError("The Mixer Brush paints colour on pixels, not on masks; target pixels (channel pixels), or paint the mask with the brush.");
|
|
5114
5292
|
}
|
|
@@ -5130,8 +5308,101 @@ var ImageEditorController = class {
|
|
|
5130
5308
|
offset: target.offset
|
|
5131
5309
|
};
|
|
5132
5310
|
if (tool === "mixer") return new PaintStrokeSession(this, target, this.mixerStroke(init, brush), apply);
|
|
5311
|
+
if (isRetouchTool(tool)) return new PaintStrokeSession(this, target, this.retouchStroke(tool, init, brush, target, apply), apply);
|
|
5133
5312
|
return new PaintStrokeSession(this, target, new StrokeCoverage(brush), apply);
|
|
5134
5313
|
}
|
|
5314
|
+
/** A retouching stroke's start: its settings, what it samples and how it composites. */
|
|
5315
|
+
retouchStroke(tool, init, brush, target, apply) {
|
|
5316
|
+
const settings = normalizeRetouch(init.retouch ?? {}, this.state.paint.retouch);
|
|
5317
|
+
const mask = target.channel !== "pixels";
|
|
5318
|
+
if (tool === "sponge" && mask) throw new LayerOperationError("The Sponge changes saturation, and a mask has none; target pixels (channel pixels), or use dodge or burn on the mask.");
|
|
5319
|
+
if (tool === "smudge") return { kind: "dabs", create: (pixels) => new RetouchDabStroke(brush, pixels, smudgeEffect(brush, settings.strength)) };
|
|
5320
|
+
if (tool === "sponge") return { kind: "dabs", create: (pixels) => new RetouchDabStroke(brush, pixels, spongeEffect(brush, settings.spongeMode === "saturate", settings.vibrance)) };
|
|
5321
|
+
const retouchApply = {
|
|
5322
|
+
opacity: tool === "clone" || tool === "heal" ? brush.opacity : 1,
|
|
5323
|
+
blend: tool === "clone" && !mask ? brush.blend : "normal",
|
|
5324
|
+
lockTransparency: target.lockTransparency,
|
|
5325
|
+
selection: apply.selection,
|
|
5326
|
+
offset: target.offset
|
|
5327
|
+
};
|
|
5328
|
+
const coverage = new StrokeCoverage(brush);
|
|
5329
|
+
if (tool === "dodge" || tool === "burn") {
|
|
5330
|
+
const paint2 = { kind: "tone", tone: toneOf(tool, settings) };
|
|
5331
|
+
return { kind: "coverage", coverage, apply: retouchApply, start: () => {
|
|
5332
|
+
}, paint: () => paint2 };
|
|
5333
|
+
}
|
|
5334
|
+
const sample = mask ? "current" : settings.sample;
|
|
5335
|
+
const own = this.surfaceSource(target.surface, target.offset);
|
|
5336
|
+
const source = sample === "current" ? own : this.compositeSource(sample === "all" ? null : target.id);
|
|
5337
|
+
const pad = (r, m) => ({ x: r.x - m, y: r.y - m, width: r.width + 2 * m, height: r.height + 2 * m });
|
|
5338
|
+
const covered = (cov, r) => {
|
|
5339
|
+
const m = new Uint8Array(r.width * r.height);
|
|
5340
|
+
for (let y = 0; y < r.height; y++) for (let x = 0; x < r.width; x++) m[y * r.width + x] = cov.get(r.x + x, r.y + y) > 0 ? 1 : 0;
|
|
5341
|
+
return m;
|
|
5342
|
+
};
|
|
5343
|
+
if (tool === "spot-heal") {
|
|
5344
|
+
const { width, height } = this.naturalSize;
|
|
5345
|
+
let patch = null;
|
|
5346
|
+
const trail = { kind: "color", color: [0, 0, 0, 96] };
|
|
5347
|
+
return {
|
|
5348
|
+
kind: "coverage",
|
|
5349
|
+
coverage,
|
|
5350
|
+
apply: retouchApply,
|
|
5351
|
+
start: () => {
|
|
5352
|
+
},
|
|
5353
|
+
paint: () => trail,
|
|
5354
|
+
finalPaint: (cov) => {
|
|
5355
|
+
if (!cov.bounds) return null;
|
|
5356
|
+
const win = spotHealWindow(cov.bounds, { width, height });
|
|
5357
|
+
if (win.width <= 0 || win.height <= 0) return null;
|
|
5358
|
+
const img = readSourceRegion(source, win);
|
|
5359
|
+
const hole = covered(cov, win);
|
|
5360
|
+
const healed = spotHealPixels(win.width, win.height, img, hole, brush.size, settings.spotType, (off) => {
|
|
5361
|
+
patch = off ? { x: off[0], y: off[1] } : null;
|
|
5362
|
+
});
|
|
5363
|
+
return { kind: "source", source: new RegionPixelSource(win, healed), dx: 0, dy: 0 };
|
|
5364
|
+
},
|
|
5365
|
+
result: () => ({ spotHeal: { offset: patch } })
|
|
5366
|
+
};
|
|
5367
|
+
}
|
|
5368
|
+
if (init.source) this.setCloneSource(init.source);
|
|
5369
|
+
const cs = this.state.paint.cloneSource;
|
|
5370
|
+
if (!cs) throw new LayerOperationError(`The ${tool === "clone" ? "Clone Stamp" : "Healing Brush"} needs a source point; Alt-click the canvas, call set_clone_source, or pass source { x, y }.`);
|
|
5371
|
+
let offset = { x: 0, y: 0 };
|
|
5372
|
+
let first = { x: 0, y: 0 };
|
|
5373
|
+
const paint = () => ({ kind: "source", source, dx: offset.x, dy: offset.y });
|
|
5374
|
+
return {
|
|
5375
|
+
kind: "coverage",
|
|
5376
|
+
coverage,
|
|
5377
|
+
apply: retouchApply,
|
|
5378
|
+
start: (p) => {
|
|
5379
|
+
first = { x: Math.round(p.x), y: Math.round(p.y) };
|
|
5380
|
+
offset = settings.aligned && cs.offset ? { ...cs.offset } : { x: cs.x - first.x, y: cs.y - first.y };
|
|
5381
|
+
if (settings.aligned) this.state.paint = { ...this.state.paint, cloneSource: { x: cs.x, y: cs.y, offset: { ...offset } } };
|
|
5382
|
+
},
|
|
5383
|
+
paint,
|
|
5384
|
+
finalPaint: tool === "heal" ? (cov) => {
|
|
5385
|
+
if (!cov.bounds) return null;
|
|
5386
|
+
const g = pad(cov.bounds, 2);
|
|
5387
|
+
const src = readSourceRegion(source, g, offset.x, offset.y);
|
|
5388
|
+
const dst = readSourceRegion(source, g);
|
|
5389
|
+
return { kind: "source", source: new RegionPixelSource(g, healPixels(g.width, g.height, src, dst, covered(cov, g))), dx: 0, dy: 0 };
|
|
5390
|
+
} : void 0,
|
|
5391
|
+
result: () => ({ clone: { offset: { ...offset }, source: { x: first.x + offset.x, y: first.y + offset.y }, aligned: settings.aligned } })
|
|
5392
|
+
};
|
|
5393
|
+
}
|
|
5394
|
+
/** A pixel source over a surface (layer pixels or a mask) in layer space at `offset`. */
|
|
5395
|
+
surfaceSource(surface, offset) {
|
|
5396
|
+
return new TilePixelSource((tx, ty) => surface.tiles.get(tileKey(tx, ty))?.data ?? (surface.defaultValue ? defaultTile(surface) : null), surface.channels, offset);
|
|
5397
|
+
}
|
|
5398
|
+
/** A pixel source over the composite of the current layers (through `throughId` and below, or all of them), as it is now. */
|
|
5399
|
+
compositeSource(throughId) {
|
|
5400
|
+
const stack = withoutAnnotations(this.state.layers);
|
|
5401
|
+
const layers = throughId ? layersThrough(stack, throughId) : stack;
|
|
5402
|
+
const compositor = new LayerCompositor();
|
|
5403
|
+
const sources = this.compositorSources();
|
|
5404
|
+
return new TilePixelSource((tx, ty) => compositor.tile(layers, tx, ty, sources), 4);
|
|
5405
|
+
}
|
|
5135
5406
|
/** A Mixer Brush stroke's start: the settings, and the load (refilled when asked or set to). */
|
|
5136
5407
|
mixerStroke(init, brush) {
|
|
5137
5408
|
const settings = normalizeMixer({ ...this.state.paint.mixer, ...init.mixer ?? {} });
|
|
@@ -5955,16 +6226,37 @@ var ImageEditorController = class {
|
|
|
5955
6226
|
const canvas = await this.renderExport(options);
|
|
5956
6227
|
const format = options.format || "image/png";
|
|
5957
6228
|
const quality = options.quality ?? 0.92;
|
|
5958
|
-
|
|
6229
|
+
const url = canvas.toDataURL(format, quality);
|
|
6230
|
+
if (!this.state.colorProfile) return url;
|
|
6231
|
+
const parsed = parseDataUrl(url);
|
|
6232
|
+
if (!parsed) return url;
|
|
6233
|
+
const bytes = this.withEmbeddedProfile(parsed.bytes, parsed.mime);
|
|
6234
|
+
return bytes === parsed.bytes ? url : `data:${parsed.mime};base64,${bytesToBase64(bytes)}`;
|
|
6235
|
+
}
|
|
6236
|
+
/** PNG or JPEG bytes with the document's ICC profile embedded (other formats unchanged). */
|
|
6237
|
+
withEmbeddedProfile(bytes, mime) {
|
|
6238
|
+
const icc = profileBytes(this.state.colorProfile);
|
|
6239
|
+
try {
|
|
6240
|
+
if (mime === "image/png") return writePngIcc(bytes, icc, this.getColorProfile().name);
|
|
6241
|
+
if (mime === "image/jpeg") return writeJpegIcc(bytes, icc);
|
|
6242
|
+
} catch {
|
|
6243
|
+
}
|
|
6244
|
+
return bytes;
|
|
5959
6245
|
}
|
|
5960
6246
|
async toBlob(options = {}) {
|
|
5961
6247
|
const canvas = await this.renderExport(options);
|
|
5962
6248
|
const format = options.format || "image/png";
|
|
5963
6249
|
const quality = options.quality ?? 0.92;
|
|
6250
|
+
const tag = async (blob) => {
|
|
6251
|
+
if (!this.state.colorProfile || typeof blob.arrayBuffer !== "function") return blob;
|
|
6252
|
+
const bytes = new Uint8Array(await blob.arrayBuffer());
|
|
6253
|
+
const out = this.withEmbeddedProfile(bytes, blob.type || format);
|
|
6254
|
+
return out === bytes ? blob : new Blob([out], { type: blob.type || format });
|
|
6255
|
+
};
|
|
5964
6256
|
return new Promise((resolve, reject) => {
|
|
5965
6257
|
if (canvas.toBlob) {
|
|
5966
6258
|
canvas.toBlob((blob) => {
|
|
5967
|
-
if (blob)
|
|
6259
|
+
if (blob) tag(blob).then(resolve, reject);
|
|
5968
6260
|
else reject(new Error("Failed to create Blob from canvas"));
|
|
5969
6261
|
}, format, quality);
|
|
5970
6262
|
} else {
|
|
@@ -5977,7 +6269,7 @@ var ImageEditorController = class {
|
|
|
5977
6269
|
for (let i = 0; i < byteStr.length; i++) {
|
|
5978
6270
|
ia[i] = byteStr.charCodeAt(i);
|
|
5979
6271
|
}
|
|
5980
|
-
|
|
6272
|
+
tag(new Blob([ab], { type: format })).then(resolve, reject);
|
|
5981
6273
|
} catch (err) {
|
|
5982
6274
|
reject(err);
|
|
5983
6275
|
}
|
|
@@ -6497,6 +6789,162 @@ var ImageEditorController = class {
|
|
|
6497
6789
|
return id;
|
|
6498
6790
|
}
|
|
6499
6791
|
// --- PSD ---
|
|
6792
|
+
// --- Colour management ---
|
|
6793
|
+
/** The document's colour profile: its name, built-in id (or `null` for an embedded profile) and colour space. */
|
|
6794
|
+
getColorProfile() {
|
|
6795
|
+
const doc = this.state.colorProfile;
|
|
6796
|
+
const p = resolveProfile(doc);
|
|
6797
|
+
return { name: doc?.name ?? p.description, builtin: doc ? doc.builtin ?? null : "srgb", colorSpace: p.colorSpace, srgb: isSrgb(doc) };
|
|
6798
|
+
}
|
|
6799
|
+
/** Resolves a profile argument (built-in id or alias, or ICC bytes) as an RGB document profile. */
|
|
6800
|
+
rgbProfileArg(profile, action) {
|
|
6801
|
+
let doc;
|
|
6802
|
+
try {
|
|
6803
|
+
doc = documentProfile(profile);
|
|
6804
|
+
} catch (e) {
|
|
6805
|
+
throw new LayerOperationError(e instanceof Error ? e.message : String(e));
|
|
6806
|
+
}
|
|
6807
|
+
const cs = resolveProfile(doc).colorSpace;
|
|
6808
|
+
if (cs !== "rgb") throw new LayerOperationError(`${action} needs an RGB profile; "${doc.name}" is ${cs.toUpperCase()}. The document is RGB: soft-proof CMYK with set_proof_setup, or export to it.`);
|
|
6809
|
+
return doc;
|
|
6810
|
+
}
|
|
6811
|
+
/**
|
|
6812
|
+
* Edit › Assign Profile (one undo step): the document's numbers now mean colours in
|
|
6813
|
+
* `profile` (a built-in id such as `display-p3`, or ICC bytes); no pixel changes, so colours
|
|
6814
|
+
* look different unless the profiles match. Returns the new profile.
|
|
6815
|
+
*/
|
|
6816
|
+
assignProfile(profile) {
|
|
6817
|
+
const doc = this.rgbProfileArg(profile, "Assign Profile");
|
|
6818
|
+
this.recordHistory();
|
|
6819
|
+
if (doc.builtin === "srgb") delete this.state.colorProfile;
|
|
6820
|
+
else this.state.colorProfile = doc;
|
|
6821
|
+
this.notify();
|
|
6822
|
+
return this.getColorProfile();
|
|
6823
|
+
}
|
|
6824
|
+
/**
|
|
6825
|
+
* Edit › Convert to Profile (one undo step): every pixel layer's numbers are converted so its
|
|
6826
|
+
* colours stay the same in `profile` (with `intent` and black point compensation), and
|
|
6827
|
+
* `profile` becomes the document's. Shape and type layers, layer effects and adjustment layers
|
|
6828
|
+
* keep their numbers (they are listed in `unconvertedLayerIds`). Returns the profile and the
|
|
6829
|
+
* layers changed.
|
|
6830
|
+
*/
|
|
6831
|
+
convertToProfile(profile, options = {}) {
|
|
6832
|
+
const doc = this.rgbProfileArg(profile, "Convert to Profile");
|
|
6833
|
+
const intent = options.intent ?? "relative";
|
|
6834
|
+
if (!INTENTS.includes(intent)) throw new LayerOperationError(`Unknown rendering intent "${String(intent)}"; use ${INTENTS.join(", ")}.`);
|
|
6835
|
+
const t = Transform.create(resolveProfile(this.state.colorProfile), resolveProfile(doc), { intent, bpc: options.bpc ?? true });
|
|
6836
|
+
const converted = [];
|
|
6837
|
+
const unconverted = [];
|
|
6838
|
+
const walk = (layers) => layers.map((l) => {
|
|
6839
|
+
if (l.type === "group") return { ...l, children: walk(l.children) };
|
|
6840
|
+
if (l.type === "pixel" && l.source !== "document") {
|
|
6841
|
+
converted.push(l.id);
|
|
6842
|
+
return { ...l, surface: convertSurfaceColors(l.surface, t) };
|
|
6843
|
+
}
|
|
6844
|
+
if (l.type === "pixel" && l.source === "document") {
|
|
6845
|
+
converted.push(l.id);
|
|
6846
|
+
return { ...l, surface: convertSurfaceColors(this.surfaceOf(l), t), source: void 0 };
|
|
6847
|
+
}
|
|
6848
|
+
if (l.type === "shape" || l.type === "type" || l.type === "smart" || l.effects?.items.length) unconverted.push(l.id);
|
|
6849
|
+
return l;
|
|
6850
|
+
});
|
|
6851
|
+
const next = walk(this.state.layers);
|
|
6852
|
+
this.commitLayers(next, () => {
|
|
6853
|
+
if (doc.builtin === "srgb") delete this.state.colorProfile;
|
|
6854
|
+
else this.state.colorProfile = doc;
|
|
6855
|
+
});
|
|
6856
|
+
return { profile: this.getColorProfile(), convertedLayerIds: converted, unconvertedLayerIds: unconverted };
|
|
6857
|
+
}
|
|
6858
|
+
/**
|
|
6859
|
+
* View › Proof Setup (view state, not an undo step): shows the document as `profile` would
|
|
6860
|
+
* reproduce it, or turns proofing off with `null`. Returns the setup.
|
|
6861
|
+
*/
|
|
6862
|
+
setProofSetup(setup) {
|
|
6863
|
+
if (!setup) {
|
|
6864
|
+
this.state.proofSetup = null;
|
|
6865
|
+
this.notify();
|
|
6866
|
+
return null;
|
|
6867
|
+
}
|
|
6868
|
+
let doc;
|
|
6869
|
+
try {
|
|
6870
|
+
doc = documentProfile(setup.profile);
|
|
6871
|
+
} catch (e) {
|
|
6872
|
+
throw new LayerOperationError(e instanceof Error ? e.message : String(e));
|
|
6873
|
+
}
|
|
6874
|
+
const intent = setup.intent ?? DEFAULT_PROOF.intent;
|
|
6875
|
+
if (!INTENTS.includes(intent)) throw new LayerOperationError(`Unknown rendering intent "${String(intent)}"; use ${INTENTS.join(", ")}.`);
|
|
6876
|
+
const next = { ...DEFAULT_PROOF, ...setup, intent, profile: doc };
|
|
6877
|
+
if (!canBeDestination(resolveProfile(doc))) throw new LayerOperationError(`"${doc.name}" describes a source only, so colours cannot be proofed through it.`);
|
|
6878
|
+
this.state.proofSetup = next;
|
|
6879
|
+
this.notify();
|
|
6880
|
+
return next;
|
|
6881
|
+
}
|
|
6882
|
+
/**
|
|
6883
|
+
* Converts colours (`#rrggbb`) from one profile to another (default from the document's), with
|
|
6884
|
+
* `intent`. For RGB targets returns hex; for others the channel values 0..1 (CMYK, grey, Lab
|
|
6885
|
+
* encoded as L/100, (a+128)/255, (b+128)/255) and, for Lab, L*a*b*.
|
|
6886
|
+
*/
|
|
6887
|
+
convertColors(colors, to, options = {}) {
|
|
6888
|
+
let src;
|
|
6889
|
+
let dst;
|
|
6890
|
+
try {
|
|
6891
|
+
src = options.from !== void 0 ? resolveProfile(documentProfile(options.from)) : resolveProfile(this.state.colorProfile);
|
|
6892
|
+
dst = resolveProfile(documentProfile(to));
|
|
6893
|
+
} catch (e) {
|
|
6894
|
+
throw new LayerOperationError(e instanceof Error ? e.message : String(e));
|
|
6895
|
+
}
|
|
6896
|
+
if (src.colorSpace !== "rgb") throw new LayerOperationError("Colours are given as #rrggbb, so the source profile must be RGB.");
|
|
6897
|
+
const t = Transform.create(src, dst, { intent: options.intent ?? "relative", bpc: options.bpc ?? true });
|
|
6898
|
+
const toLab = Transform.create(src, builtinProfile("lab-d50"), { intent: "relative", bpc: false });
|
|
6899
|
+
return colors.map((input) => {
|
|
6900
|
+
const m = /^#?([0-9a-f]{6})$/i.exec(input.trim());
|
|
6901
|
+
if (!m) throw new LayerOperationError(`Invalid colour "${input}"; use #rrggbb.`);
|
|
6902
|
+
const n = parseInt(m[1], 16);
|
|
6903
|
+
const rgb = [(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255];
|
|
6904
|
+
const out = new Float32Array(16);
|
|
6905
|
+
t.eval(rgb, out);
|
|
6906
|
+
const values = Array.from(out.subarray(0, t.outputs), (v) => Math.round(Math.min(1, Math.max(0, v)) * 1e4) / 1e4);
|
|
6907
|
+
const l = new Float32Array(3);
|
|
6908
|
+
toLab.eval(rgb, l);
|
|
6909
|
+
const lab = [Math.round(l[0] * 1e4) / 100, Math.round((l[1] * 255 - 128) * 100) / 100, Math.round((l[2] * 255 - 128) * 100) / 100];
|
|
6910
|
+
const res = { input, values, lab };
|
|
6911
|
+
if (dst.colorSpace === "rgb") res.hex = `#${values.map((v) => Math.round(v * 255).toString(16).padStart(2, "0")).join("")}`;
|
|
6912
|
+
return res;
|
|
6913
|
+
});
|
|
6914
|
+
}
|
|
6915
|
+
/** The document's bits per channel (8 unless set to 16 or 32). */
|
|
6916
|
+
get bitDepth() {
|
|
6917
|
+
return this.state.bitDepth ?? 8;
|
|
6918
|
+
}
|
|
6919
|
+
/**
|
|
6920
|
+
* Image › Mode › 8 / 16 / 32 Bits/Channel (one undo step). Going up costs nothing: layers keep
|
|
6921
|
+
* their pixels and read them at the new depth, and edits that understand depth keep the extra
|
|
6922
|
+
* precision from then on. Going down rounds every pixel layer's deep samples to the new depth
|
|
6923
|
+
* (8 bits: their 8-bit rendering; 32 → 16: colour above 1 is clipped). Returns the depth and
|
|
6924
|
+
* the ids of the layers whose pixels changed.
|
|
6925
|
+
*/
|
|
6926
|
+
setBitDepth(depth) {
|
|
6927
|
+
if (!isBitDepth(depth)) throw new LayerOperationError(`Bit depth must be 8, 16 or 32, not ${String(depth)}.`);
|
|
6928
|
+
const changed = [];
|
|
6929
|
+
const convert = (layers) => layers.map((l) => {
|
|
6930
|
+
if (l.type === "group") {
|
|
6931
|
+
const children = convert(l.children);
|
|
6932
|
+
return children.some((c, i) => c !== l.children[i]) ? { ...l, children } : l;
|
|
6933
|
+
}
|
|
6934
|
+
if (l.type !== "pixel") return l;
|
|
6935
|
+
const surface = convertSurfaceDepth(l.surface, depth);
|
|
6936
|
+
if (surface === l.surface) return l;
|
|
6937
|
+
changed.push(l.id);
|
|
6938
|
+
return { ...l, surface };
|
|
6939
|
+
});
|
|
6940
|
+
if (depth === this.bitDepth) return { bitDepth: depth, changedLayerIds: [] };
|
|
6941
|
+
const next = convert(this.state.layers);
|
|
6942
|
+
this.commitLayers(next, () => {
|
|
6943
|
+
if (depth === 8) delete this.state.bitDepth;
|
|
6944
|
+
else this.state.bitDepth = depth;
|
|
6945
|
+
});
|
|
6946
|
+
return { bitDepth: depth, changedLayerIds: changed };
|
|
6947
|
+
}
|
|
6500
6948
|
/** Pixels per inch of the last opened PSD (written back by `toPsd`). */
|
|
6501
6949
|
psdResolution;
|
|
6502
6950
|
/**
|
|
@@ -6529,6 +6977,20 @@ var ImageEditorController = class {
|
|
|
6529
6977
|
else delete s.patterns;
|
|
6530
6978
|
if (result.globalLight) s.globalLight = result.globalLight;
|
|
6531
6979
|
else delete s.globalLight;
|
|
6980
|
+
if (result.bitDepth !== 8) s.bitDepth = result.bitDepth;
|
|
6981
|
+
else delete s.bitDepth;
|
|
6982
|
+
delete s.colorProfile;
|
|
6983
|
+
if (result.iccProfile) {
|
|
6984
|
+
try {
|
|
6985
|
+
const doc = documentProfile(result.iccProfile);
|
|
6986
|
+
const cs = resolveProfile(doc).colorSpace;
|
|
6987
|
+
if (cs === "rgb") {
|
|
6988
|
+
if (!isSrgb(doc)) s.colorProfile = doc;
|
|
6989
|
+
} else result.warnings.push(`The file's ${cs.toUpperCase()} profile "${doc.name}" was not used; its colours were converted to sRGB numerically.`);
|
|
6990
|
+
} catch (e) {
|
|
6991
|
+
result.warnings.push(`The embedded ICC profile could not be read (${e instanceof Error ? e.message : String(e)}); colours are read as sRGB.`);
|
|
6992
|
+
}
|
|
6993
|
+
}
|
|
6532
6994
|
this.lastLoadProblems = result.warnings;
|
|
6533
6995
|
this.notify();
|
|
6534
6996
|
return {
|
|
@@ -6564,7 +7026,7 @@ var ImageEditorController = class {
|
|
|
6564
7026
|
return layers;
|
|
6565
7027
|
}
|
|
6566
7028
|
/**
|
|
6567
|
-
* The document as an
|
|
7029
|
+
* The document as an RGB PSD at its bit depth (8, 16 or 32) (PSB above 30 000 px) with real layers, groups, adjustment
|
|
6568
7030
|
* layers and masks, in the source frame. Layer filters and parametric (depth) masks are baked
|
|
6569
7031
|
* into pixels and masks, annotation layers are rasterized (in a browser; elsewhere they are left
|
|
6570
7032
|
* out), and the merged image is our composite. `warnings` lists every such change.
|
|
@@ -6690,7 +7152,9 @@ var ImageEditorController = class {
|
|
|
6690
7152
|
if (s.legacyOutputBlur) warnings.push("The document-level blur of an old document is not written.");
|
|
6691
7153
|
for (const [msg, names] of note) warnings.push(`${msg} (${names.length === 1 ? "layer" : "layers"} ${names.map((n) => `"${n}"`).join(", ")}).`);
|
|
6692
7154
|
const merged = compositor.composite(prepared, canvas, { ...sources, version: `psd:${Date.now()}` });
|
|
6693
|
-
const
|
|
7155
|
+
const depth = this.bitDepth;
|
|
7156
|
+
const mergedDeep = depth === 8 ? void 0 : workingToSamples(compositeDeep(prepared, canvas, sources, depth), depth);
|
|
7157
|
+
const result = exportPsd({ width, height, layers: prepared, merged, mergedDeep, resolution: this.psdResolution, patterns: this.state.patterns, globalLight: this.state.globalLight, depth, iccProfile: profileBytes(this.state.colorProfile) });
|
|
6694
7158
|
warnings.push(...result.warnings);
|
|
6695
7159
|
return { bytes: result.bytes, warnings, psb: result.psb, width, height };
|
|
6696
7160
|
}
|
|
@@ -6836,6 +7300,11 @@ var ImageEditorController = class {
|
|
|
6836
7300
|
const patterns = deserializePatterns(data.patterns, problems);
|
|
6837
7301
|
if (patterns.length) s.patterns = patterns;
|
|
6838
7302
|
else delete s.patterns;
|
|
7303
|
+
if (data.bitDepth === 16 || data.bitDepth === 32) s.bitDepth = data.bitDepth;
|
|
7304
|
+
else delete s.bitDepth;
|
|
7305
|
+
const profile = data.colorProfile ? deserializeColorProfile(data.colorProfile, problems) : null;
|
|
7306
|
+
if (profile) s.colorProfile = profile;
|
|
7307
|
+
else delete s.colorProfile;
|
|
6839
7308
|
if (!this.imageElement && typeof data.width === "number" && typeof data.height === "number" && data.width > 0 && data.height > 0) {
|
|
6840
7309
|
s.imageDimensions = { width: data.width, height: data.height };
|
|
6841
7310
|
}
|
|
@@ -6851,6 +7320,8 @@ var ImageEditorController = class {
|
|
|
6851
7320
|
s.activeLayerId = null;
|
|
6852
7321
|
delete s.globalLight;
|
|
6853
7322
|
delete s.patterns;
|
|
7323
|
+
delete s.bitDepth;
|
|
7324
|
+
delete s.colorProfile;
|
|
6854
7325
|
}
|
|
6855
7326
|
s.activeTarget = "pixels";
|
|
6856
7327
|
this.viewKey = [];
|
|
@@ -6860,7 +7331,7 @@ var ImageEditorController = class {
|
|
|
6860
7331
|
serializeState() {
|
|
6861
7332
|
const { width, height } = this.naturalSize;
|
|
6862
7333
|
const view = legacyView(this.stack(), width, height, null);
|
|
6863
|
-
if (view.exact && !this.state.globalLight && !this.state.patterns?.length) {
|
|
7334
|
+
if (view.exact && !this.state.globalLight && !this.state.patterns?.length && this.bitDepth === 8 && !this.state.colorProfile) {
|
|
6864
7335
|
return {
|
|
6865
7336
|
// States without adjustment layers keep schema 1, so they round-trip unchanged.
|
|
6866
7337
|
version: view.adjustments.layers?.length ? 2 : 1,
|
|
@@ -6890,6 +7361,8 @@ var ImageEditorController = class {
|
|
|
6890
7361
|
if (this.state.globalLight) out.globalLight = { ...this.state.globalLight };
|
|
6891
7362
|
if (this.state.patterns?.length) out.patterns = serializePatterns(this.state.patterns);
|
|
6892
7363
|
if (smartContents) out.smartContents = smartContents;
|
|
7364
|
+
if (this.state.bitDepth === 16 || this.state.bitDepth === 32) out.bitDepth = this.state.bitDepth;
|
|
7365
|
+
if (this.state.colorProfile) out.colorProfile = serializeColorProfile(this.state.colorProfile);
|
|
6893
7366
|
return out;
|
|
6894
7367
|
}
|
|
6895
7368
|
};
|
|
@@ -7033,6 +7506,27 @@ function collectStateTiles(layers, into) {
|
|
|
7033
7506
|
}
|
|
7034
7507
|
}
|
|
7035
7508
|
}
|
|
7509
|
+
function layersThrough(layers, id) {
|
|
7510
|
+
const walk = (list) => {
|
|
7511
|
+
const out = [];
|
|
7512
|
+
for (const l of list) {
|
|
7513
|
+
if (l.id === id) {
|
|
7514
|
+
out.push(l);
|
|
7515
|
+
return { out, found: true };
|
|
7516
|
+
}
|
|
7517
|
+
if (l.type === "group") {
|
|
7518
|
+
const sub2 = walk(l.children);
|
|
7519
|
+
if (sub2.found) {
|
|
7520
|
+
out.push({ ...l, children: sub2.out });
|
|
7521
|
+
return { out, found: true };
|
|
7522
|
+
}
|
|
7523
|
+
}
|
|
7524
|
+
out.push(l);
|
|
7525
|
+
}
|
|
7526
|
+
return { out, found: false };
|
|
7527
|
+
};
|
|
7528
|
+
return walk(layers).out;
|
|
7529
|
+
}
|
|
7036
7530
|
function pixelsOnly(layers) {
|
|
7037
7531
|
const out = [];
|
|
7038
7532
|
for (const l of layers) {
|
|
@@ -8077,10 +8571,23 @@ function createImageEditTools(controller) {
|
|
|
8077
8571
|
const getImageInfo = {
|
|
8078
8572
|
name: "get_image_info",
|
|
8079
8573
|
title: "Read image geometry",
|
|
8080
|
-
description: "Returns the source image size in pixels, the crop box (natural image pixels, or null for the whole image), rotation in degrees, flips, the output size after crop and rotation, the number of adjustment layers and whether undo / redo are available.",
|
|
8574
|
+
description: "Returns the source image size in pixels, the crop box (natural image pixels, or null for the whole image), rotation in degrees, flips, the output size after crop and rotation, the bits per channel (bitDepth 8, 16 or 32), the number of adjustment layers and whether undo / redo are available.",
|
|
8081
8575
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
8082
8576
|
annotations: { title: "Read image geometry", ...READ },
|
|
8083
|
-
handler: () => ({ ...geometry2(), layers: controller.getAdjustmentLayers().length, ...history() })
|
|
8577
|
+
handler: () => ({ ...geometry2(), bitDepth: controller.bitDepth, layers: controller.getAdjustmentLayers().length, ...history() })
|
|
8578
|
+
};
|
|
8579
|
+
const setBitDepth = {
|
|
8580
|
+
name: "set_bit_depth",
|
|
8581
|
+
title: "Set the bit depth",
|
|
8582
|
+
description: "Image \u203A Mode \u203A 8 / 16 / 32 Bits/Channel. 16-bit keeps smooth gradients and survives strong adjustments without banding; 32-bit holds linear-light float values (HDR, colour above 1). Going up costs nothing and changes no pixels; going down rounds every pixel layer's extra precision away (32 \u2192 16 also clips colour above 1). The viewport always shows an 8-bit rendering. One undo step. Returns the bitDepth and changedLayerIds (the layers whose samples were rounded).",
|
|
8583
|
+
inputSchema: {
|
|
8584
|
+
type: "object",
|
|
8585
|
+
properties: { depth: { type: "number", enum: [8, 16, 32], description: "Bits per channel: 8, 16 or 32 (float, linear light)." } },
|
|
8586
|
+
required: ["depth"],
|
|
8587
|
+
additionalProperties: false
|
|
8588
|
+
},
|
|
8589
|
+
annotations: { title: "Set the bit depth", readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
8590
|
+
handler: (args) => ({ ...controller.setBitDepth(args.depth), ...history() })
|
|
8084
8591
|
};
|
|
8085
8592
|
const getHistogram = {
|
|
8086
8593
|
name: "get_histogram",
|
|
@@ -8256,7 +8763,7 @@ function createImageEditTools(controller) {
|
|
|
8256
8763
|
return history();
|
|
8257
8764
|
}
|
|
8258
8765
|
};
|
|
8259
|
-
return [getImageInfo, getHistogram, updateLayer, removeLayer2, moveLayer2, setCrop, resetCrop, setRotation, flipImage, undo, redo];
|
|
8766
|
+
return [getImageInfo, getHistogram, updateLayer, removeLayer2, moveLayer2, setCrop, resetCrop, setRotation, flipImage, setBitDepth, undo, redo];
|
|
8260
8767
|
}
|
|
8261
8768
|
|
|
8262
8769
|
// src/image-editor/layer-tools.ts
|
|
@@ -9175,9 +9682,73 @@ var PAINT_TOOLS = [
|
|
|
9175
9682
|
{ id: "move", label: "Move", shortcut: "V" },
|
|
9176
9683
|
{ id: "brush", label: "Brush", shortcut: "B" },
|
|
9177
9684
|
{ id: "mixer", label: "Mixer", shortcut: "Shift+B" },
|
|
9178
|
-
{ id: "eraser", label: "Eraser", shortcut: "E" }
|
|
9685
|
+
{ id: "eraser", label: "Eraser", shortcut: "E" },
|
|
9686
|
+
{ id: "clone", label: "Clone", shortcut: "S" },
|
|
9687
|
+
{ id: "heal", label: "Heal", shortcut: "Shift+J" },
|
|
9688
|
+
{ id: "spot-heal", label: "Spot Heal", shortcut: "J" },
|
|
9689
|
+
{ id: "smudge", label: "Smudge", shortcut: "" },
|
|
9690
|
+
{ id: "dodge", label: "Dodge", shortcut: "O" },
|
|
9691
|
+
{ id: "burn", label: "Burn", shortcut: "Shift+O" },
|
|
9692
|
+
{ id: "sponge", label: "Sponge", shortcut: "Shift+O" }
|
|
9693
|
+
];
|
|
9694
|
+
var isPaintTool = (tool) => PAINT_TOOLS.some((t) => t.id === tool);
|
|
9695
|
+
var isRetouchPaintTool = (tool) => tool === "clone" || tool === "heal" || tool === "spot-heal" || tool === "smudge" || tool === "dodge" || tool === "burn" || tool === "sponge";
|
|
9696
|
+
var RETOUCH_OPTION_CONTROLS = [
|
|
9697
|
+
{ id: "exposure", label: "Exposure", section: "layer", min: 0, max: 1, step: 0.01, default: 0.5, decimals: 0, track: plain2, displayScale: 100, unit: "%", tools: ["dodge", "burn"] },
|
|
9698
|
+
{ id: "strength", label: "Strength", section: "layer", min: 0, max: 1, step: 0.01, default: 0.5, decimals: 0, track: plain2, displayScale: 100, unit: "%", tools: ["smudge"] }
|
|
9699
|
+
];
|
|
9700
|
+
var RETOUCH_CHOICES = [
|
|
9701
|
+
{
|
|
9702
|
+
id: "sample",
|
|
9703
|
+
label: "Sample",
|
|
9704
|
+
tools: ["clone", "heal", "spot-heal"],
|
|
9705
|
+
options: [
|
|
9706
|
+
{ id: "current", label: "Current", title: "Sample the layer being retouched" },
|
|
9707
|
+
{ id: "current-below", label: "Current & Below", title: "Sample this layer and the layers under it" },
|
|
9708
|
+
{ id: "all", label: "All", title: "Sample every layer (retouch onto an empty layer)" }
|
|
9709
|
+
]
|
|
9710
|
+
},
|
|
9711
|
+
{
|
|
9712
|
+
id: "spotType",
|
|
9713
|
+
label: "Type",
|
|
9714
|
+
tools: ["spot-heal"],
|
|
9715
|
+
options: [
|
|
9716
|
+
{ id: "proximity-match", label: "Proximity Match", title: "Copy the best matching patch nearby" },
|
|
9717
|
+
{ id: "smooth", label: "Smooth", title: "Fill smoothly from the edges" }
|
|
9718
|
+
]
|
|
9719
|
+
},
|
|
9720
|
+
{
|
|
9721
|
+
id: "range",
|
|
9722
|
+
label: "Range",
|
|
9723
|
+
tools: ["dodge", "burn"],
|
|
9724
|
+
options: [
|
|
9725
|
+
{ id: "shadows", label: "Shadows", title: "Change the dark tones most" },
|
|
9726
|
+
{ id: "midtones", label: "Midtones", title: "Change the middle tones most" },
|
|
9727
|
+
{ id: "highlights", label: "Highlights", title: "Change the light tones most" }
|
|
9728
|
+
]
|
|
9729
|
+
},
|
|
9730
|
+
{
|
|
9731
|
+
id: "spongeMode",
|
|
9732
|
+
label: "Mode",
|
|
9733
|
+
tools: ["sponge"],
|
|
9734
|
+
options: [
|
|
9735
|
+
{ id: "desaturate", label: "Desaturate", title: "Take colour out" },
|
|
9736
|
+
{ id: "saturate", label: "Saturate", title: "Add colour" }
|
|
9737
|
+
]
|
|
9738
|
+
}
|
|
9739
|
+
];
|
|
9740
|
+
var RETOUCH_TOGGLES = [
|
|
9741
|
+
{ id: "aligned", label: "Aligned", tools: ["clone", "heal"] },
|
|
9742
|
+
{ id: "protectTones", label: "Protect tones", tools: ["dodge", "burn"] },
|
|
9743
|
+
{ id: "vibrance", label: "Vibrance", tools: ["sponge"] }
|
|
9179
9744
|
];
|
|
9180
|
-
|
|
9745
|
+
function setRetouchChoice(controller, id, value) {
|
|
9746
|
+
controller.setPaintOptions({ retouch: { [id]: value } });
|
|
9747
|
+
}
|
|
9748
|
+
function cloneSourceLabel(controller) {
|
|
9749
|
+
const cs = controller.getState().paint.cloneSource;
|
|
9750
|
+
return cs ? `Source ${cs.x}, ${cs.y}${cs.offset ? ` (offset ${cs.offset.x}, ${cs.offset.y})` : ""}` : "Alt-click to set the source";
|
|
9751
|
+
}
|
|
9181
9752
|
function rgbUnitToHex(c) {
|
|
9182
9753
|
return `#${c.map((v) => Math.round(Math.min(1, Math.max(0, v)) * 255).toString(16).padStart(2, "0")).join("")}`;
|
|
9183
9754
|
}
|
|
@@ -9203,6 +9774,18 @@ function handlePaintShortcut(controller, e) {
|
|
|
9203
9774
|
case "v":
|
|
9204
9775
|
controller.setTool("move");
|
|
9205
9776
|
return true;
|
|
9777
|
+
case "s":
|
|
9778
|
+
controller.setTool("clone");
|
|
9779
|
+
return true;
|
|
9780
|
+
case "j":
|
|
9781
|
+
controller.setTool(e.shiftKey || key === "J" ? controller.getState().activeTool === "heal" ? "spot-heal" : "heal" : "spot-heal");
|
|
9782
|
+
return true;
|
|
9783
|
+
case "o": {
|
|
9784
|
+
const cur = controller.getState().activeTool;
|
|
9785
|
+
const next = e.shiftKey || key === "O" ? cur === "dodge" ? "burn" : cur === "burn" ? "sponge" : "dodge" : "dodge";
|
|
9786
|
+
controller.setTool(next);
|
|
9787
|
+
return true;
|
|
9788
|
+
}
|
|
9206
9789
|
case "d":
|
|
9207
9790
|
controller.resetPaintColors();
|
|
9208
9791
|
return true;
|
|
@@ -9258,9 +9841,13 @@ var PaintGesture = class {
|
|
|
9258
9841
|
this.move = { id: layer.id, start: { x: sample.x, y: sample.y }, origin, moved: false };
|
|
9259
9842
|
return null;
|
|
9260
9843
|
}
|
|
9261
|
-
if (tool === "
|
|
9844
|
+
if ((tool === "clone" || tool === "heal") && sample.altKey) {
|
|
9845
|
+
this.controller.setCloneSource({ x: sample.x, y: sample.y });
|
|
9846
|
+
return null;
|
|
9847
|
+
}
|
|
9848
|
+
if (isPaintTool(tool)) {
|
|
9262
9849
|
this.stroke = this.controller.beginStroke({ tool });
|
|
9263
|
-
this.stroke.push([sample]);
|
|
9850
|
+
this.stroke.push([{ x: sample.x, y: sample.y, pressure: sample.pressure }]);
|
|
9264
9851
|
return null;
|
|
9265
9852
|
}
|
|
9266
9853
|
} catch (err) {
|
|
@@ -9491,8 +10078,396 @@ function createImagePaintTools(controller) {
|
|
|
9491
10078
|
return [paintStroke, fill, clear, getMixer, loadMixerTool, cleanMixerTool];
|
|
9492
10079
|
}
|
|
9493
10080
|
|
|
9494
|
-
// src/image-editor/
|
|
10081
|
+
// src/image-editor/color-tools.ts
|
|
9495
10082
|
var READ5 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
10083
|
+
var PROFILE = {
|
|
10084
|
+
type: "string",
|
|
10085
|
+
description: `A built-in profile id (${BUILTIN_PROFILES.map((b) => b.id).join(", ")}; names like "Adobe RGB (1998)" work too), or "icc:<base64>" for ICC profile bytes.`
|
|
10086
|
+
};
|
|
10087
|
+
var INTENT = {
|
|
10088
|
+
type: "string",
|
|
10089
|
+
enum: [...INTENTS],
|
|
10090
|
+
description: "Rendering intent (default relative): relative (colorimetric, white to white, clips out-of-gamut colours: best for most photos), perceptual (compresses the whole gamut smoothly), saturation (keeps colours vivid, for graphics), absolute (keeps the source white too: for proofing a paper colour)."
|
|
10091
|
+
};
|
|
10092
|
+
function profileArg(v) {
|
|
10093
|
+
const s = String(v);
|
|
10094
|
+
if (s.startsWith("icc:")) {
|
|
10095
|
+
try {
|
|
10096
|
+
return base64ToBytes(s.slice(4));
|
|
10097
|
+
} catch {
|
|
10098
|
+
throw new LayerOperationError("The icc: value is not valid base64.");
|
|
10099
|
+
}
|
|
10100
|
+
}
|
|
10101
|
+
return s;
|
|
10102
|
+
}
|
|
10103
|
+
function createImageColorTools(controller) {
|
|
10104
|
+
const getProfile = {
|
|
10105
|
+
name: "get_color_profile",
|
|
10106
|
+
title: "Get the colour profile",
|
|
10107
|
+
description: "Reads the document's colour profile (its working space: name, builtin id or null for an embedded profile, colorSpace, srgb true when it means sRGB), the Proof Setup (profile, intent, bpc, simulatePaper, gamutWarning, or null) and the built-in profiles you can assign, convert to or proof with ({id, description, colorSpace}).",
|
|
10108
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
10109
|
+
annotations: { title: "Get the colour profile", ...READ5 },
|
|
10110
|
+
handler: () => {
|
|
10111
|
+
const proof2 = controller.getState().proofSetup;
|
|
10112
|
+
return {
|
|
10113
|
+
profile: controller.getColorProfile(),
|
|
10114
|
+
proof: proof2 ? { profile: proof2.profile.name, builtin: proof2.profile.builtin ?? null, intent: proof2.intent, bpc: proof2.bpc, simulatePaper: proof2.simulatePaper, gamutWarning: proof2.gamutWarning } : null,
|
|
10115
|
+
builtins: BUILTIN_PROFILES.map((b) => ({ ...b }))
|
|
10116
|
+
};
|
|
10117
|
+
}
|
|
10118
|
+
};
|
|
10119
|
+
const assign = {
|
|
10120
|
+
name: "assign_profile",
|
|
10121
|
+
title: "Assign a colour profile",
|
|
10122
|
+
description: "Edit \u203A Assign Profile: the document's RGB numbers now mean colours in `profile`; no pixel changes, so the image looks different unless the profiles match. Use it when a file was saved without its profile, or with the wrong one. To keep the look and change the numbers, use convert_to_profile. RGB profiles only. One undo step. Returns the new profile.",
|
|
10123
|
+
inputSchema: { type: "object", properties: { profile: PROFILE }, required: ["profile"], additionalProperties: false },
|
|
10124
|
+
annotations: { title: "Assign a colour profile", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
10125
|
+
handler: (args) => controller.assignProfile(profileArg(args.profile))
|
|
10126
|
+
};
|
|
10127
|
+
const convert = {
|
|
10128
|
+
name: "convert_to_profile",
|
|
10129
|
+
title: "Convert to a colour profile",
|
|
10130
|
+
description: "Edit \u203A Convert to Profile: converts every pixel layer's numbers so its colours stay the same in `profile`, then makes `profile` the document's. Colours outside the new space are mapped by `intent`. Shape and type layers, layer effects and smart objects keep their numbers and are listed in unconvertedLayerIds. RGB profiles only (proof CMYK with set_proof_setup). One undo step. Returns the profile, convertedLayerIds and unconvertedLayerIds.",
|
|
10131
|
+
inputSchema: {
|
|
10132
|
+
type: "object",
|
|
10133
|
+
properties: {
|
|
10134
|
+
profile: PROFILE,
|
|
10135
|
+
intent: INTENT,
|
|
10136
|
+
bpc: { type: "boolean", description: "Black point compensation: map black to black (default true)." }
|
|
10137
|
+
},
|
|
10138
|
+
required: ["profile"],
|
|
10139
|
+
additionalProperties: false
|
|
10140
|
+
},
|
|
10141
|
+
annotations: { title: "Convert to a colour profile", readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
10142
|
+
handler: (args) => controller.convertToProfile(profileArg(args.profile), { intent: args.intent, bpc: args.bpc })
|
|
10143
|
+
};
|
|
10144
|
+
const proof = {
|
|
10145
|
+
name: "set_proof_setup",
|
|
10146
|
+
title: "Set up soft proofing",
|
|
10147
|
+
description: "View \u203A Proof Setup: shows the document in the viewport as `profile` would reproduce it (for example coated-cmyk for a press), with `intent`; `simulatePaper` shows the paper white and ink black; `gamutWarning` greys out colours the profile cannot reproduce. Pass off true to stop proofing. View state, not an undo step; pixels never change. Check the result with capture_view. Returns the setup (or null).",
|
|
10148
|
+
inputSchema: {
|
|
10149
|
+
type: "object",
|
|
10150
|
+
properties: {
|
|
10151
|
+
profile: PROFILE,
|
|
10152
|
+
intent: INTENT,
|
|
10153
|
+
bpc: { type: "boolean", description: "Black point compensation (default true)." },
|
|
10154
|
+
simulatePaper: { type: "boolean", description: "Simulate paper colour and black ink (default false)." },
|
|
10155
|
+
gamutWarning: { type: "boolean", description: "Grey out out-of-gamut colours (default false)." },
|
|
10156
|
+
off: { type: "boolean", description: "Turn proofing off instead (default false)." }
|
|
10157
|
+
},
|
|
10158
|
+
additionalProperties: false
|
|
10159
|
+
},
|
|
10160
|
+
annotations: { title: "Set up soft proofing", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
10161
|
+
handler: (args) => {
|
|
10162
|
+
if (args.off === true) {
|
|
10163
|
+
if (args.profile !== void 0) throw new LayerOperationError("Give either off true or a profile, not both.");
|
|
10164
|
+
return controller.setProofSetup(null);
|
|
10165
|
+
}
|
|
10166
|
+
if (args.profile === void 0) throw new LayerOperationError("Pass the profile to proof with (e.g. coated-cmyk), or off true.");
|
|
10167
|
+
const s = controller.setProofSetup({
|
|
10168
|
+
profile: profileArg(args.profile),
|
|
10169
|
+
...args.intent !== void 0 ? { intent: args.intent } : {},
|
|
10170
|
+
...args.bpc !== void 0 ? { bpc: args.bpc } : {},
|
|
10171
|
+
...args.simulatePaper !== void 0 ? { simulatePaper: args.simulatePaper } : {},
|
|
10172
|
+
...args.gamutWarning !== void 0 ? { gamutWarning: args.gamutWarning } : {}
|
|
10173
|
+
});
|
|
10174
|
+
return { profile: s.profile.name, builtin: s.profile.builtin ?? null, intent: s.intent, bpc: s.bpc, simulatePaper: s.simulatePaper, gamutWarning: s.gamutWarning };
|
|
10175
|
+
}
|
|
10176
|
+
};
|
|
10177
|
+
const convertColors = {
|
|
10178
|
+
name: "convert_colors",
|
|
10179
|
+
title: "Convert colours between profiles",
|
|
10180
|
+
description: 'Converts 1 to 64 #rrggbb colours from the document\'s profile (or `from`) into `to` with `intent`: returns per colour the channel values 0..1 (RGB, CMYK, grey; Lab as L/100, (a+128)/255, (b+128)/255), a hex for RGB targets, and L*a*b* (D50). Use it to answer "what are these CMYK values" or to pick colours that survive printing. Changes nothing.',
|
|
10181
|
+
inputSchema: {
|
|
10182
|
+
type: "object",
|
|
10183
|
+
properties: {
|
|
10184
|
+
colors: { type: "array", minItems: 1, maxItems: 64, items: { type: "string", description: "A colour #rrggbb." }, description: "Colours in the source profile." },
|
|
10185
|
+
to: PROFILE,
|
|
10186
|
+
from: { ...PROFILE, description: `Source profile (default the document's). ${PROFILE.description}` },
|
|
10187
|
+
intent: INTENT,
|
|
10188
|
+
bpc: { type: "boolean", description: "Black point compensation (default true)." }
|
|
10189
|
+
},
|
|
10190
|
+
required: ["colors", "to"],
|
|
10191
|
+
additionalProperties: false
|
|
10192
|
+
},
|
|
10193
|
+
annotations: { title: "Convert colours between profiles", ...READ5 },
|
|
10194
|
+
handler: (args) => ({
|
|
10195
|
+
colors: controller.convertColors(args.colors, profileArg(args.to), {
|
|
10196
|
+
...args.from !== void 0 ? { from: profileArg(args.from) } : {},
|
|
10197
|
+
intent: args.intent,
|
|
10198
|
+
bpc: args.bpc
|
|
10199
|
+
})
|
|
10200
|
+
})
|
|
10201
|
+
};
|
|
10202
|
+
return [getProfile, assign, convert, proof, convertColors];
|
|
10203
|
+
}
|
|
10204
|
+
|
|
10205
|
+
// src/image-editor/retouch-tools.ts
|
|
10206
|
+
var READ6 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
10207
|
+
var WRITE5 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
10208
|
+
var POINTS = {
|
|
10209
|
+
type: "array",
|
|
10210
|
+
minItems: 1,
|
|
10211
|
+
maxItems: 1e4,
|
|
10212
|
+
description: "The stroke path in document pixels; one point is one dab.",
|
|
10213
|
+
items: {
|
|
10214
|
+
type: "object",
|
|
10215
|
+
properties: {
|
|
10216
|
+
x: { type: "number", description: "Document x in px." },
|
|
10217
|
+
y: { type: "number", description: "Document y in px." },
|
|
10218
|
+
pressure: { type: "number", minimum: 0, maximum: 1, description: "Pen pressure (default 1)." }
|
|
10219
|
+
},
|
|
10220
|
+
required: ["x", "y"],
|
|
10221
|
+
additionalProperties: false
|
|
10222
|
+
}
|
|
10223
|
+
};
|
|
10224
|
+
var BRUSH2 = {
|
|
10225
|
+
type: "object",
|
|
10226
|
+
description: "Brush tip; missing fields use the current paint options (default round, hard, 20 px).",
|
|
10227
|
+
properties: {
|
|
10228
|
+
size: { type: "number", minimum: 1, maximum: MAX_BRUSH_SIZE, description: "Diameter in px (default 20)." },
|
|
10229
|
+
hardness: { type: "number", minimum: 0, maximum: 1, description: "Edge hardness 0 (soft) .. 1 (hard, default). Retouching usually wants a soft tip (0..0.5)." },
|
|
10230
|
+
spacing: { type: "number", minimum: 0.01, maximum: 10, description: "Dab spacing as a fraction of the diameter (default 0.1)." },
|
|
10231
|
+
opacity: { type: "number", minimum: 0, maximum: 1, description: "Stroke opacity (Clone Stamp and Healing Brush only; default 1)." },
|
|
10232
|
+
flow: { type: "number", minimum: 0, maximum: 1, description: "Build-up per dab (default 1). For the Sponge, how strongly each pass moves saturation." },
|
|
10233
|
+
smoothing: { type: "number", minimum: 0, maximum: 0.95, description: "Point smoothing 0..0.95 (default 0)." },
|
|
10234
|
+
pressureSize: { type: "boolean", description: "Point pressure scales the size (default true)." },
|
|
10235
|
+
pressureOpacity: { type: "boolean", description: "Point pressure scales the opacity (default false)." }
|
|
10236
|
+
},
|
|
10237
|
+
additionalProperties: false
|
|
10238
|
+
};
|
|
10239
|
+
var TARGET2 = {
|
|
10240
|
+
layerId: { type: "string", description: "Layer to retouch (default the active layer; see list_layers)." },
|
|
10241
|
+
channel: {
|
|
10242
|
+
type: "string",
|
|
10243
|
+
enum: ["pixels", "mask", "filter-mask"],
|
|
10244
|
+
description: "pixels (default for a pixel layer), the layer's mask, or a smart object's filter mask. Masks are grey values and sample only themselves."
|
|
10245
|
+
}
|
|
10246
|
+
};
|
|
10247
|
+
var SAMPLE = {
|
|
10248
|
+
type: "string",
|
|
10249
|
+
enum: [...RETOUCH_SAMPLES],
|
|
10250
|
+
description: "What is sampled: current (default: the target layer), current-below (the composite of this layer and everything under it), or all (the composite of every layer). Sample current-below or all to retouch onto an empty layer above the image."
|
|
10251
|
+
};
|
|
10252
|
+
function readTarget2(args) {
|
|
10253
|
+
return { layerId: args.layerId, channel: args.channel };
|
|
10254
|
+
}
|
|
10255
|
+
function checkSource(controller, source) {
|
|
10256
|
+
if (source === void 0) return void 0;
|
|
10257
|
+
const p = source;
|
|
10258
|
+
const { width, height } = controller.getState().imageDimensions;
|
|
10259
|
+
if (!(p.x >= 0 && p.y >= 0 && p.x < width && p.y < height)) {
|
|
10260
|
+
throw new LayerOperationError(`source (${p.x}, ${p.y}) is outside the ${width} \xD7 ${height} canvas; pick a point on the image to sample.`);
|
|
10261
|
+
}
|
|
10262
|
+
return p;
|
|
10263
|
+
}
|
|
10264
|
+
var SOURCE = {
|
|
10265
|
+
type: "object",
|
|
10266
|
+
description: "Source point in document pixels (an Alt-click). Given, it replaces the clone source and the offset starts afresh at this stroke; missing, the current clone source is used (set_clone_source).",
|
|
10267
|
+
properties: { x: { type: "number", description: "Source x in px." }, y: { type: "number", description: "Source y in px." } },
|
|
10268
|
+
required: ["x", "y"],
|
|
10269
|
+
additionalProperties: false
|
|
10270
|
+
};
|
|
10271
|
+
function createImageRetouchTools(controller) {
|
|
10272
|
+
const stroke = (tool, args, retouch) => controller.paintStroke({
|
|
10273
|
+
tool,
|
|
10274
|
+
points: args.points,
|
|
10275
|
+
brush: args.brush,
|
|
10276
|
+
retouch,
|
|
10277
|
+
source: checkSource(controller, args.source),
|
|
10278
|
+
...readTarget2(args)
|
|
10279
|
+
});
|
|
10280
|
+
const cloneStamp = {
|
|
10281
|
+
name: "clone_stamp",
|
|
10282
|
+
title: "Clone Stamp stroke",
|
|
10283
|
+
description: "Copies pixels from the clone source along a stroke (Photoshop's Clone Stamp): each point paints what lies at point + offset, where the offset is source \u2212 the stroke's first point. With aligned on, later strokes keep that offset; off, every stroke starts again at the source point. Samples the layer, the layers up to it, or all layers, from before the stroke. The selection limits it; a transparency-locked layer keeps its alpha. One undo step. Returns the layerId, channel, changed bounds, dab count and clone {offset, source, aligned}.",
|
|
10284
|
+
inputSchema: {
|
|
10285
|
+
type: "object",
|
|
10286
|
+
properties: {
|
|
10287
|
+
points: POINTS,
|
|
10288
|
+
source: SOURCE,
|
|
10289
|
+
aligned: { type: "boolean", description: "Keep the offset for the next strokes (default the current option, true)." },
|
|
10290
|
+
sample: SAMPLE,
|
|
10291
|
+
brush: BRUSH2,
|
|
10292
|
+
...TARGET2
|
|
10293
|
+
},
|
|
10294
|
+
required: ["points"],
|
|
10295
|
+
additionalProperties: false
|
|
10296
|
+
},
|
|
10297
|
+
annotations: { title: "Clone Stamp stroke", ...WRITE5 },
|
|
10298
|
+
handler: (args) => stroke("clone", args, pick(args, ["aligned", "sample"]))
|
|
10299
|
+
};
|
|
10300
|
+
const healBrush = {
|
|
10301
|
+
name: "heal_brush",
|
|
10302
|
+
title: "Healing Brush stroke",
|
|
10303
|
+
description: "Heals along a stroke from the clone source (Photoshop's Healing Brush): the source's texture is cloned, then blended so its colour and light match the pixels around the stroke (Poisson seamless cloning, applied when the stroke ends). Use it for blemishes and dust where the area around them is the right tone; use clone_stamp to copy exactly. Same source, offset and aligned rules as clone_stamp. One undo step. Returns the layerId, channel, changed bounds, dab count and clone {offset, source, aligned}.",
|
|
10304
|
+
inputSchema: {
|
|
10305
|
+
type: "object",
|
|
10306
|
+
properties: {
|
|
10307
|
+
points: POINTS,
|
|
10308
|
+
source: SOURCE,
|
|
10309
|
+
aligned: { type: "boolean", description: "Keep the offset for the next strokes (default the current option, true)." },
|
|
10310
|
+
sample: SAMPLE,
|
|
10311
|
+
brush: BRUSH2,
|
|
10312
|
+
...TARGET2
|
|
10313
|
+
},
|
|
10314
|
+
required: ["points"],
|
|
10315
|
+
additionalProperties: false
|
|
10316
|
+
},
|
|
10317
|
+
annotations: { title: "Healing Brush stroke", ...WRITE5 },
|
|
10318
|
+
handler: (args) => stroke("heal", args, pick(args, ["aligned", "sample"]))
|
|
10319
|
+
};
|
|
10320
|
+
const spotHeal = {
|
|
10321
|
+
name: "spot_heal",
|
|
10322
|
+
title: "Spot Healing stroke",
|
|
10323
|
+
description: "Removes what a stroke covers without a source point (Photoshop's Spot Healing Brush): proximity-match copies the best matching patch nearby and blends it in; smooth fills from the edges inwards (for spots on smooth areas). Paint just over the spot with a brush a little larger than it. One undo step. Returns the layerId, channel, changed bounds, dab count and spotHeal {offset} (the patch offset copied, or null for a smooth fill).",
|
|
10324
|
+
inputSchema: {
|
|
10325
|
+
type: "object",
|
|
10326
|
+
properties: {
|
|
10327
|
+
points: POINTS,
|
|
10328
|
+
type: { type: "string", enum: [...SPOT_HEAL_TYPES], description: "proximity-match (default) or smooth." },
|
|
10329
|
+
sample: SAMPLE,
|
|
10330
|
+
brush: BRUSH2,
|
|
10331
|
+
...TARGET2
|
|
10332
|
+
},
|
|
10333
|
+
required: ["points"],
|
|
10334
|
+
additionalProperties: false
|
|
10335
|
+
},
|
|
10336
|
+
annotations: { title: "Spot Healing stroke", ...WRITE5 },
|
|
10337
|
+
handler: (args) => stroke("spot-heal", args, { ...pick(args, ["sample"]), ...args.type !== void 0 ? { spotType: args.type } : {} })
|
|
10338
|
+
};
|
|
10339
|
+
const smudge = {
|
|
10340
|
+
name: "smudge",
|
|
10341
|
+
title: "Smudge stroke",
|
|
10342
|
+
description: "Drags pixels along a stroke like a finger through wet paint (Photoshop's Smudge): each dab lays what the finger carries and picks up what was under it, so colour is pulled in the stroke's direction and fades with distance. One undo step. Returns the layerId, channel, changed bounds and dab count.",
|
|
10343
|
+
inputSchema: {
|
|
10344
|
+
type: "object",
|
|
10345
|
+
properties: {
|
|
10346
|
+
points: POINTS,
|
|
10347
|
+
strength: { type: "number", minimum: 0, maximum: 1, description: "How far colour is dragged, 0..1 (default the current option, 0.5)." },
|
|
10348
|
+
brush: BRUSH2,
|
|
10349
|
+
...TARGET2
|
|
10350
|
+
},
|
|
10351
|
+
required: ["points"],
|
|
10352
|
+
additionalProperties: false
|
|
10353
|
+
},
|
|
10354
|
+
annotations: { title: "Smudge stroke", ...WRITE5 },
|
|
10355
|
+
handler: (args) => stroke("smudge", args, pick(args, ["strength"]))
|
|
10356
|
+
};
|
|
10357
|
+
const toneSchema = (verb) => ({
|
|
10358
|
+
type: "object",
|
|
10359
|
+
properties: {
|
|
10360
|
+
points: POINTS,
|
|
10361
|
+
range: { type: "string", enum: [...TONE_RANGES], description: `Which tones to ${verb} most: shadows, midtones (default the current option, midtones) or highlights.` },
|
|
10362
|
+
exposure: { type: "number", minimum: 0, maximum: 1, description: "Strength 0..1 (default the current option, 0.5). Overlapping dabs in one stroke never go past it; another stroke builds on it." },
|
|
10363
|
+
protectTones: { type: "boolean", description: "Change luma only, keeping hue and saturation (default true)." },
|
|
10364
|
+
brush: BRUSH2,
|
|
10365
|
+
...TARGET2
|
|
10366
|
+
},
|
|
10367
|
+
required: ["points"],
|
|
10368
|
+
additionalProperties: false
|
|
10369
|
+
});
|
|
10370
|
+
const dodge = {
|
|
10371
|
+
name: "dodge",
|
|
10372
|
+
title: "Dodge stroke",
|
|
10373
|
+
description: "Lightens the pixels a stroke covers (Photoshop's Dodge), most in the chosen tonal range. Alpha is kept. One undo step. Returns the layerId, channel, changed bounds and dab count. Inspect the result with capture_view or get_histogram; several light strokes look more natural than one strong one.",
|
|
10374
|
+
inputSchema: toneSchema("lighten"),
|
|
10375
|
+
annotations: { title: "Dodge stroke", ...WRITE5 },
|
|
10376
|
+
handler: (args) => stroke("dodge", args, pick(args, ["range", "exposure", "protectTones"]))
|
|
10377
|
+
};
|
|
10378
|
+
const burn = {
|
|
10379
|
+
name: "burn",
|
|
10380
|
+
title: "Burn stroke",
|
|
10381
|
+
description: "Darkens the pixels a stroke covers (Photoshop's Burn), most in the chosen tonal range. Alpha is kept. One undo step. Returns the layerId, channel, changed bounds and dab count.",
|
|
10382
|
+
inputSchema: toneSchema("darken"),
|
|
10383
|
+
annotations: { title: "Burn stroke", ...WRITE5 },
|
|
10384
|
+
handler: (args) => stroke("burn", args, pick(args, ["range", "exposure", "protectTones"]))
|
|
10385
|
+
};
|
|
10386
|
+
const spongeTool = {
|
|
10387
|
+
name: "sponge",
|
|
10388
|
+
title: "Sponge stroke",
|
|
10389
|
+
description: "Saturates or desaturates the pixels a stroke covers (Photoshop's Sponge); passes compound, and brush.flow sets how much each moves. Pixels only (a mask has no colour). One undo step. Returns the layerId, channel, changed bounds and dab count.",
|
|
10390
|
+
inputSchema: {
|
|
10391
|
+
type: "object",
|
|
10392
|
+
properties: {
|
|
10393
|
+
points: POINTS,
|
|
10394
|
+
mode: { type: "string", enum: ["desaturate", "saturate"], description: "desaturate (default the current option, desaturate) or saturate." },
|
|
10395
|
+
vibrance: { type: "boolean", description: "Damp the change where colours would clip or collapse to grey (default true)." },
|
|
10396
|
+
brush: BRUSH2,
|
|
10397
|
+
layerId: TARGET2.layerId
|
|
10398
|
+
},
|
|
10399
|
+
required: ["points"],
|
|
10400
|
+
additionalProperties: false
|
|
10401
|
+
},
|
|
10402
|
+
annotations: { title: "Sponge stroke", ...WRITE5 },
|
|
10403
|
+
handler: (args) => stroke("sponge", { ...args, channel: "pixels" }, { ...pick(args, ["vibrance"]), ...args.mode !== void 0 ? { spongeMode: args.mode } : {} })
|
|
10404
|
+
};
|
|
10405
|
+
const getOptions = {
|
|
10406
|
+
name: "get_retouch_options",
|
|
10407
|
+
title: "Get retouch options",
|
|
10408
|
+
description: "Reads the retouching tools' options (aligned, sample, spotType, range, exposure, protectTones, spongeMode, vibrance, strength) and the clone source ({x, y, offset} or null; offset is the aligned offset the last clone or heal stroke fixed). Call it before clone_stamp or heal_brush without a source to see where they will sample.",
|
|
10409
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
10410
|
+
annotations: { title: "Get retouch options", ...READ6 },
|
|
10411
|
+
handler: () => {
|
|
10412
|
+
const { retouch, cloneSource } = controller.getState().paint;
|
|
10413
|
+
return { options: { ...retouch }, cloneSource: cloneSource ? { ...cloneSource } : null };
|
|
10414
|
+
}
|
|
10415
|
+
};
|
|
10416
|
+
const setOptions = {
|
|
10417
|
+
name: "set_retouch_options",
|
|
10418
|
+
title: "Set retouch options",
|
|
10419
|
+
description: "Sets the retouching tools' defaults (view state, not an undo step); each stroke tool can also override them per call. Changing aligned clears the aligned offset. Returns the options after the change.",
|
|
10420
|
+
inputSchema: {
|
|
10421
|
+
type: "object",
|
|
10422
|
+
properties: {
|
|
10423
|
+
aligned: { type: "boolean", description: "Clone / heal: keep the offset from stroke to stroke (default true)." },
|
|
10424
|
+
sample: SAMPLE,
|
|
10425
|
+
spotType: { type: "string", enum: [...SPOT_HEAL_TYPES], description: "Spot heal: proximity-match (default) or smooth." },
|
|
10426
|
+
range: { type: "string", enum: [...TONE_RANGES], description: "Dodge / burn: shadows, midtones (default) or highlights." },
|
|
10427
|
+
exposure: { type: "number", minimum: 0, maximum: 1, description: "Dodge / burn strength 0..1 (default 0.5)." },
|
|
10428
|
+
protectTones: { type: "boolean", description: "Dodge / burn: keep hue and saturation (default true)." },
|
|
10429
|
+
spongeMode: { type: "string", enum: ["desaturate", "saturate"], description: "Sponge: desaturate (default) or saturate." },
|
|
10430
|
+
vibrance: { type: "boolean", description: "Sponge: damp clipping (default true)." },
|
|
10431
|
+
strength: { type: "number", minimum: 0, maximum: 1, description: "Smudge strength 0..1 (default 0.5)." }
|
|
10432
|
+
},
|
|
10433
|
+
additionalProperties: false
|
|
10434
|
+
},
|
|
10435
|
+
annotations: { title: "Set retouch options", ...WRITE5, idempotentHint: true },
|
|
10436
|
+
handler: (args) => ({ ...controller.setPaintOptions({ retouch: args }).retouch })
|
|
10437
|
+
};
|
|
10438
|
+
const setSource = {
|
|
10439
|
+
name: "set_clone_source",
|
|
10440
|
+
title: "Set the clone source",
|
|
10441
|
+
description: "Sets the Clone Stamp and Healing Brush source point (Photoshop's Alt-click), in document pixels, or clears it with clear true. The next clone_stamp or heal_brush stroke fixes the offset from its first point. View state, not an undo step. Returns the clone source {x, y, offset} (or null).",
|
|
10442
|
+
inputSchema: {
|
|
10443
|
+
type: "object",
|
|
10444
|
+
properties: {
|
|
10445
|
+
x: { type: "number", description: "Source x in px (required unless clear)." },
|
|
10446
|
+
y: { type: "number", description: "Source y in px (required unless clear)." },
|
|
10447
|
+
clear: { type: "boolean", description: "Remove the source point instead (default false)." }
|
|
10448
|
+
},
|
|
10449
|
+
additionalProperties: false
|
|
10450
|
+
},
|
|
10451
|
+
annotations: { title: "Set the clone source", ...WRITE5, idempotentHint: true },
|
|
10452
|
+
handler: (args) => {
|
|
10453
|
+
if (args.clear === true) {
|
|
10454
|
+
if (args.x !== void 0 || args.y !== void 0) throw new LayerOperationError("Give either clear true or x and y, not both.");
|
|
10455
|
+
return controller.setCloneSource(null);
|
|
10456
|
+
}
|
|
10457
|
+
if (typeof args.x !== "number" || typeof args.y !== "number") throw new LayerOperationError("Pass x and y (document pixels) for the source point, or clear true to remove it.");
|
|
10458
|
+
return controller.setCloneSource(checkSource(controller, { x: args.x, y: args.y }));
|
|
10459
|
+
}
|
|
10460
|
+
};
|
|
10461
|
+
return [cloneStamp, healBrush, spotHeal, smudge, dodge, burn, spongeTool, getOptions, setOptions, setSource];
|
|
10462
|
+
}
|
|
10463
|
+
function pick(args, keys) {
|
|
10464
|
+
const out = {};
|
|
10465
|
+
for (const k of keys) if (args[k] !== void 0) out[k] = args[k];
|
|
10466
|
+
return out;
|
|
10467
|
+
}
|
|
10468
|
+
|
|
10469
|
+
// src/image-editor/psd-tools.ts
|
|
10470
|
+
var READ7 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
9496
10471
|
var DESTRUCTIVE4 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
|
|
9497
10472
|
var DATA = {
|
|
9498
10473
|
type: "string",
|
|
@@ -9513,7 +10488,7 @@ function createImagePsdTools(controller) {
|
|
|
9513
10488
|
title: "Inspect a PSD",
|
|
9514
10489
|
description: "Reads a PSD / PSB without opening it: size, bit depth, colour mode, resolution, and every layer (bottom first, a group before its children) with its PSD kind, the layer type it would become and its status (mapped, approximated or dropped, with notes), plus the import warnings. Changes nothing. Call before import_psd to see what would be lost.",
|
|
9515
10490
|
inputSchema: { type: "object", properties: { data: DATA }, required: ["data"], additionalProperties: false },
|
|
9516
|
-
annotations: { title: "Inspect a PSD", ...
|
|
10491
|
+
annotations: { title: "Inspect a PSD", ...READ7 },
|
|
9517
10492
|
handler: ({ data }) => {
|
|
9518
10493
|
const res = importPsd(psdBytesFromBase64(data), { skipPixels: true });
|
|
9519
10494
|
return {
|
|
@@ -9548,9 +10523,9 @@ function createImagePsdTools(controller) {
|
|
|
9548
10523
|
const save = {
|
|
9549
10524
|
name: "export_psd",
|
|
9550
10525
|
title: "Export the document as a PSD",
|
|
9551
|
-
description: "Writes the document as an
|
|
10526
|
+
description: "Writes the document as an RGB PSD at its bit depth (8, 16 or 32; see set_bit_depth; a PSB above 30000 px) with real layers, groups, adjustment layers, masks and layer effects (an effects block opened from a PSD is written back unchanged while its effects are; the patterns effects use are embedded), in the source frame (crop and rotation are not applied). Smart objects are written as Photoshop smart objects (contents embedded once per linked set, placement, warp, smart filters and filter mask; a Develop smart filter has no Photoshop filter and is left out of the list). Shape layers are written as Photoshop shape layers (path, fill, stroke, live shapes) and type layers as Photoshop type layers (text, runs, paragraphs, frame; blocks opened from a PSD are written back unchanged while the layer is). Layer filters and depth masks are baked, annotation layers are rasterized (in a browser; elsewhere left out), and the merged image is the editor's composite. Changes nothing. Returns the file as base64, its size in bytes, whether it is a PSB, and warnings for everything that was changed or left out.",
|
|
9552
10527
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
9553
|
-
annotations: { title: "Export the document as a PSD", ...
|
|
10528
|
+
annotations: { title: "Export the document as a PSD", ...READ7 },
|
|
9554
10529
|
handler: async () => {
|
|
9555
10530
|
const res = await controller.toPsd();
|
|
9556
10531
|
return {
|
|
@@ -9568,8 +10543,8 @@ function createImagePsdTools(controller) {
|
|
|
9568
10543
|
}
|
|
9569
10544
|
|
|
9570
10545
|
// src/image-editor/transform-tools.ts
|
|
9571
|
-
var
|
|
9572
|
-
var
|
|
10546
|
+
var READ8 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
10547
|
+
var WRITE6 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
9573
10548
|
var round4 = (n, digits = 3) => Math.round(n * 10 ** digits) / 10 ** digits;
|
|
9574
10549
|
var roundPoint = (p) => ({ x: round4(p.x), y: round4(p.y) });
|
|
9575
10550
|
var POINT = {
|
|
@@ -9578,7 +10553,7 @@ var POINT = {
|
|
|
9578
10553
|
required: ["x", "y"],
|
|
9579
10554
|
additionalProperties: false
|
|
9580
10555
|
};
|
|
9581
|
-
var
|
|
10556
|
+
var TARGET3 = {
|
|
9582
10557
|
layerId: { type: "string", description: "Layer to transform (from list_layers; default the active layer)." },
|
|
9583
10558
|
content: {
|
|
9584
10559
|
type: "string",
|
|
@@ -9660,15 +10635,15 @@ function createImageTransformTools(controller) {
|
|
|
9660
10635
|
inputSchema: {
|
|
9661
10636
|
type: "object",
|
|
9662
10637
|
properties: {
|
|
9663
|
-
layerId:
|
|
9664
|
-
content: { type: "string", enum: FREE_TRANSFORM_CONTENTS, description: `${
|
|
9665
|
-
channel:
|
|
10638
|
+
layerId: TARGET3.layerId,
|
|
10639
|
+
content: { type: "string", enum: FREE_TRANSFORM_CONTENTS, description: `${TARGET3.content.description} selection-outline frames the selection outline alone.` },
|
|
10640
|
+
channel: TARGET3.channel,
|
|
9666
10641
|
meshColumns: { type: "integer", minimum: 1, maximum: 16, description: "Patch columns of an identity warp mesh to return (omit for none)." },
|
|
9667
10642
|
meshRows: { type: "integer", minimum: 1, maximum: 16, description: "Patch rows of an identity warp mesh to return (default meshColumns)." }
|
|
9668
10643
|
},
|
|
9669
10644
|
additionalProperties: false
|
|
9670
10645
|
},
|
|
9671
|
-
annotations: { title: "Get the Free Transform frame", ...
|
|
10646
|
+
annotations: { title: "Get the Free Transform frame", ...READ8 },
|
|
9672
10647
|
handler: (args) => {
|
|
9673
10648
|
const init = targetFrom(args);
|
|
9674
10649
|
if (init.content === "selection-copy") init.content = "selection";
|
|
@@ -9698,10 +10673,10 @@ function createImageTransformTools(controller) {
|
|
|
9698
10673
|
description: "Edit \u203A Free Transform: scales, rotates, skews, moves, distorts or puts in perspective a pixel layer (with its linked mask), the selected pixels, a copy of them on a new layer, or an unlinked mask. Give the transform one way: the numeric fields (as Photoshop\u2019s options bar, about a reference point), a matrix, or the frame\u2019s new corners (distort / perspective; read the frame with get_transform_frame). Resamples once, tile by tile; one undo step. Returns layerId (the new layer for selection-copy), what moved, the frame, the corners and the new document bounds.",
|
|
9699
10674
|
inputSchema: {
|
|
9700
10675
|
type: "object",
|
|
9701
|
-
properties: { ...
|
|
10676
|
+
properties: { ...TARGET3, ...GEOMETRY },
|
|
9702
10677
|
additionalProperties: false
|
|
9703
10678
|
},
|
|
9704
|
-
annotations: { title: "Free Transform a layer", ...
|
|
10679
|
+
annotations: { title: "Free Transform a layer", ...WRITE6 },
|
|
9705
10680
|
handler: (args) => summary2(controller.transformLayer({ ...targetFrom(args), ...geometryFrom(args) }))
|
|
9706
10681
|
};
|
|
9707
10682
|
const warpLayer = {
|
|
@@ -9711,7 +10686,7 @@ function createImageTransformTools(controller) {
|
|
|
9711
10686
|
inputSchema: {
|
|
9712
10687
|
type: "object",
|
|
9713
10688
|
properties: {
|
|
9714
|
-
...
|
|
10689
|
+
...TARGET3,
|
|
9715
10690
|
style: { type: "string", enum: WARP_STYLES, description: "Preset warp (Photoshop\u2019s menu): arc, arcLower, arcUpper, arch, bulge, shellLower, shellUpper, flag, wave, fish, rise, fisheye, inflate, squeeze, twist." },
|
|
9716
10691
|
bend: { type: "number", minimum: -100, maximum: 100, description: "Preset bend in percent, \u2212100..100 (default 50)." },
|
|
9717
10692
|
horizontal: { type: "number", minimum: -100, maximum: 100, description: "Preset horizontal distortion in percent, \u2212100..100 (default 0)." },
|
|
@@ -9731,7 +10706,7 @@ function createImageTransformTools(controller) {
|
|
|
9731
10706
|
},
|
|
9732
10707
|
additionalProperties: false
|
|
9733
10708
|
},
|
|
9734
|
-
annotations: { title: "Warp a layer", ...
|
|
10709
|
+
annotations: { title: "Warp a layer", ...WRITE6 },
|
|
9735
10710
|
handler: (args) => {
|
|
9736
10711
|
const hasPreset = ["style", "bend", "horizontal", "vertical", "orientation"].some((k) => args[k] !== void 0);
|
|
9737
10712
|
if (hasPreset && args.mesh !== void 0) throw new LayerOperationError("Give a preset (style, bend, horizontal, vertical, orientation) or a mesh, not both.");
|
|
@@ -9762,10 +10737,10 @@ function createImageTransformTools(controller) {
|
|
|
9762
10737
|
description: "Select \u203A Transform Selection: scales, rotates, skews, moves or distorts only the selection outline; no pixels change. Give the transform one way: the numeric fields (about a reference point of the selection\u2019s bounds), a matrix, or the new corners of its bounds. Needs a selection. One undo step. Returns the new selection bounds.",
|
|
9763
10738
|
inputSchema: {
|
|
9764
10739
|
type: "object",
|
|
9765
|
-
properties: { interpolation: { ...
|
|
10740
|
+
properties: { interpolation: { ...TARGET3.interpolation, description: "Resampling of soft edges: nearest, bilinear or bicubic (default)." }, ...GEOMETRY },
|
|
9766
10741
|
additionalProperties: false
|
|
9767
10742
|
},
|
|
9768
|
-
annotations: { title: "Transform the selection outline", ...
|
|
10743
|
+
annotations: { title: "Transform the selection outline", ...WRITE6 },
|
|
9769
10744
|
handler: (args) => {
|
|
9770
10745
|
const res = controller.transformLayer({ content: "selection-outline", interpolation: args.interpolation, ...geometryFrom(args) });
|
|
9771
10746
|
return { changed: res.changed, frame: { ...res.box }, corners: res.corners.map(roundPoint), bounds: res.bounds };
|
|
@@ -9779,14 +10754,14 @@ function createImageTransformTools(controller) {
|
|
|
9779
10754
|
type: "object",
|
|
9780
10755
|
properties: {
|
|
9781
10756
|
axis: { type: "string", enum: ["horizontal", "vertical"], description: "horizontal mirrors left \u2194 right, vertical top \u2194 bottom." },
|
|
9782
|
-
layerId:
|
|
9783
|
-
content:
|
|
9784
|
-
channel:
|
|
10757
|
+
layerId: TARGET3.layerId,
|
|
10758
|
+
content: TARGET3.content,
|
|
10759
|
+
channel: TARGET3.channel
|
|
9785
10760
|
},
|
|
9786
10761
|
required: ["axis"],
|
|
9787
10762
|
additionalProperties: false
|
|
9788
10763
|
},
|
|
9789
|
-
annotations: { title: "Flip a layer", ...
|
|
10764
|
+
annotations: { title: "Flip a layer", ...WRITE6 },
|
|
9790
10765
|
handler: (args) => summary2(controller.flipLayer(args.axis, targetFrom(args)))
|
|
9791
10766
|
};
|
|
9792
10767
|
const rotate90 = {
|
|
@@ -9797,22 +10772,22 @@ function createImageTransformTools(controller) {
|
|
|
9797
10772
|
type: "object",
|
|
9798
10773
|
properties: {
|
|
9799
10774
|
turn: { type: "integer", enum: [90, -90, 180], description: "Degrees: 90 clockwise, -90 counter-clockwise, or 180." },
|
|
9800
|
-
layerId:
|
|
9801
|
-
content:
|
|
9802
|
-
channel:
|
|
10775
|
+
layerId: TARGET3.layerId,
|
|
10776
|
+
content: TARGET3.content,
|
|
10777
|
+
channel: TARGET3.channel
|
|
9803
10778
|
},
|
|
9804
10779
|
required: ["turn"],
|
|
9805
10780
|
additionalProperties: false
|
|
9806
10781
|
},
|
|
9807
|
-
annotations: { title: "Rotate a layer by quarter turns", ...
|
|
10782
|
+
annotations: { title: "Rotate a layer by quarter turns", ...WRITE6 },
|
|
9808
10783
|
handler: (args) => summary2(controller.rotateLayer90(args.turn, targetFrom(args)))
|
|
9809
10784
|
};
|
|
9810
10785
|
return [getFrame, transformLayer, warpLayer, transformSelection, flipLayer, rotate90];
|
|
9811
10786
|
}
|
|
9812
10787
|
|
|
9813
10788
|
// src/image-editor/smart-tools.ts
|
|
9814
|
-
var
|
|
9815
|
-
var
|
|
10789
|
+
var READ9 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
10790
|
+
var WRITE7 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
9816
10791
|
var WRITE_IDEMPOTENT4 = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
9817
10792
|
var DESTRUCTIVE5 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
|
|
9818
10793
|
var round5 = (n, digits = 3) => Math.round(n * 10 ** digits) / 10 ** digits;
|
|
@@ -9870,7 +10845,7 @@ function createImageSmartObjectTools(controller) {
|
|
|
9870
10845
|
title: "Read a smart object",
|
|
9871
10846
|
description: "Returns a smart object\u2019s contents (id shared by linked instances, name, kind raster or document, pixel size, the file type it came from, the instances placed from it), its placement (corners in document pixels, warp mesh in contents pixels, interpolation, and scale / rotate / skew in percent and degrees of the contents\u2019 own size when affine), its smart filters bottom to top (ids, kind, settings, enabled, blend, opacity; kind psd marks a Photoshop filter kept but not drawn) with the filter mask, and the rendered bounds. Changes nothing.",
|
|
9872
10847
|
inputSchema: { type: "object", properties: { layerId: LAYER_ID4 }, required: ["layerId"], additionalProperties: false },
|
|
9873
|
-
annotations: { title: "Read a smart object", ...
|
|
10848
|
+
annotations: { title: "Read a smart object", ...READ9 },
|
|
9874
10849
|
handler: ({ layerId }) => info(smartId(layerId))
|
|
9875
10850
|
};
|
|
9876
10851
|
const convert = {
|
|
@@ -9885,7 +10860,7 @@ function createImageSmartObjectTools(controller) {
|
|
|
9885
10860
|
},
|
|
9886
10861
|
additionalProperties: false
|
|
9887
10862
|
},
|
|
9888
|
-
annotations: { title: "Convert to Smart Object", ...
|
|
10863
|
+
annotations: { title: "Convert to Smart Object", ...WRITE7 },
|
|
9889
10864
|
handler: ({ layerIds, name }) => {
|
|
9890
10865
|
for (const id2 of layerIds ?? []) requireLayer(controller.getLayers(), id2);
|
|
9891
10866
|
const id = controller.convertToSmartObject(layerIds, { name });
|
|
@@ -10004,7 +10979,7 @@ function createImageSmartObjectTools(controller) {
|
|
|
10004
10979
|
title: "Export a smart object\u2019s contents",
|
|
10005
10980
|
description: "Layer \u203A Smart Objects \u203A Export Contents: the contents as a file, base64: the file they came from while unedited (a PSB, PNG, JPEG\u2026), else a PNG for raster contents or a PSD / PSB of a nested document. Returns name, mimeType, byteLength and base64. Changes nothing.",
|
|
10006
10981
|
inputSchema: { type: "object", properties: { layerId: LAYER_ID4 }, required: ["layerId"], additionalProperties: false },
|
|
10007
|
-
annotations: { title: "Export a smart object\u2019s contents", ...
|
|
10982
|
+
annotations: { title: "Export a smart object\u2019s contents", ...READ9 },
|
|
10008
10983
|
handler: ({ layerId }) => {
|
|
10009
10984
|
const f = controller.exportSmartObjectContents(smartId(layerId));
|
|
10010
10985
|
return { name: f.name, mimeType: f.mimeType, byteLength: f.bytes.length, base64: bytesToBase64(f.bytes) };
|
|
@@ -10024,7 +10999,7 @@ function createImageSmartObjectTools(controller) {
|
|
|
10024
10999
|
required: ["layerId"],
|
|
10025
11000
|
additionalProperties: false
|
|
10026
11001
|
},
|
|
10027
|
-
annotations: { title: "Copy a smart object", ...
|
|
11002
|
+
annotations: { title: "Copy a smart object", ...WRITE7 },
|
|
10028
11003
|
handler: ({ layerId, linked, name }) => {
|
|
10029
11004
|
const id = controller.copySmartObject(smartId(layerId), linked === true, name);
|
|
10030
11005
|
return { layer: layerSummary(id), contentsId: controller.getSmartObject(id).contents.id };
|
|
@@ -10035,7 +11010,7 @@ function createImageSmartObjectTools(controller) {
|
|
|
10035
11010
|
title: "Edit a smart object\u2019s contents",
|
|
10036
11011
|
description: 'Layer \u203A Smart Objects \u203A Edit Contents: opens the contents as a document of their own (a layer document, or raster contents as one pixel layer "Layer 0") with their own undo history. Every other tool (list_layers, paint, adjustments\u2026) then works inside the contents until close_smart_object_contents saves them back. Contents can nest. Returns the contents\u2019 size and layers.',
|
|
10037
11012
|
inputSchema: { type: "object", properties: { layerId: LAYER_ID4 }, required: ["layerId"], additionalProperties: false },
|
|
10038
|
-
annotations: { title: "Edit a smart object\u2019s contents", ...
|
|
11013
|
+
annotations: { title: "Edit a smart object\u2019s contents", ...WRITE7 },
|
|
10039
11014
|
handler: ({ layerId }) => {
|
|
10040
11015
|
const res = controller.editSmartObjectContents(smartId(layerId));
|
|
10041
11016
|
const s = controller.getState();
|
|
@@ -10051,7 +11026,7 @@ function createImageSmartObjectTools(controller) {
|
|
|
10051
11026
|
properties: { save: { type: "boolean", description: "true (default) saves the edits into the smart object; false discards them." } },
|
|
10052
11027
|
additionalProperties: false
|
|
10053
11028
|
},
|
|
10054
|
-
annotations: { title: "Close a smart object\u2019s contents", ...
|
|
11029
|
+
annotations: { title: "Close a smart object\u2019s contents", ...WRITE7 },
|
|
10055
11030
|
handler: ({ save }) => ({ ...controller.closeSmartObjectContents(save !== false), editing: controller.getEditingContents() })
|
|
10056
11031
|
};
|
|
10057
11032
|
const addFilter = {
|
|
@@ -10069,7 +11044,7 @@ function createImageSmartObjectTools(controller) {
|
|
|
10069
11044
|
required: ["layerId", "kind"],
|
|
10070
11045
|
additionalProperties: false
|
|
10071
11046
|
},
|
|
10072
|
-
annotations: { title: "Add a smart filter", ...
|
|
11047
|
+
annotations: { title: "Add a smart filter", ...WRITE7 },
|
|
10073
11048
|
handler: (args) => {
|
|
10074
11049
|
const id = smartId(args.layerId);
|
|
10075
11050
|
const filter = controller.addSmartFilter(id, { kind: args.kind, ...settingsFrom(args) }, args.index);
|
|
@@ -10158,7 +11133,7 @@ function createImageSmartObjectTools(controller) {
|
|
|
10158
11133
|
required: ["layerId", "from"],
|
|
10159
11134
|
additionalProperties: false
|
|
10160
11135
|
},
|
|
10161
|
-
annotations: { title: "Set the smart filter mask", ...
|
|
11136
|
+
annotations: { title: "Set the smart filter mask", ...WRITE7 },
|
|
10162
11137
|
handler: (args) => {
|
|
10163
11138
|
const id = smartId(args.layerId);
|
|
10164
11139
|
const props = {};
|
|
@@ -10171,8 +11146,8 @@ function createImageSmartObjectTools(controller) {
|
|
|
10171
11146
|
}
|
|
10172
11147
|
|
|
10173
11148
|
// src/image-editor/shape-tools.ts
|
|
10174
|
-
var
|
|
10175
|
-
var
|
|
11149
|
+
var READ10 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
11150
|
+
var WRITE8 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
10176
11151
|
var WRITE_IDEMPOTENT5 = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
10177
11152
|
var DESTRUCTIVE6 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
|
|
10178
11153
|
var LAYER_ID5 = { type: "string", description: "Shape layer id from list_layers (type shape) or add_shape_layer." };
|
|
@@ -10425,7 +11400,7 @@ function createImageShapeTools(controller) {
|
|
|
10425
11400
|
title: "Read a shape layer",
|
|
10426
11401
|
description: "Returns a shape layer\u2019s components bottom to top (index, path operation, the live shape it was drawn as or null, its subpaths with absolute anchors and handles, bounds), the fill rule, fill and stroke paints and settings, the path bounds and the rendered bounds (stroke included). Changes nothing.",
|
|
10427
11402
|
inputSchema: { type: "object", properties: { layerId: LAYER_ID5 }, required: ["layerId"], additionalProperties: false },
|
|
10428
|
-
annotations: { title: "Read a shape layer", ...
|
|
11403
|
+
annotations: { title: "Read a shape layer", ...READ10 },
|
|
10429
11404
|
handler: ({ layerId }) => controller.getShapeLayer(shapeId(layerId))
|
|
10430
11405
|
};
|
|
10431
11406
|
const add2 = {
|
|
@@ -10456,7 +11431,7 @@ function createImageShapeTools(controller) {
|
|
|
10456
11431
|
},
|
|
10457
11432
|
additionalProperties: false
|
|
10458
11433
|
},
|
|
10459
|
-
annotations: { title: "Add a shape layer", ...
|
|
11434
|
+
annotations: { title: "Add a shape layer", ...WRITE8 },
|
|
10460
11435
|
handler: (args) => {
|
|
10461
11436
|
const init = { ...geometry(args) };
|
|
10462
11437
|
if (args.components !== void 0) init.components = args.components.map(geometry);
|
|
@@ -10551,7 +11526,7 @@ function createImageShapeTools(controller) {
|
|
|
10551
11526
|
required: ["layerId"],
|
|
10552
11527
|
additionalProperties: false
|
|
10553
11528
|
},
|
|
10554
|
-
annotations: { title: "Add a shape component", ...
|
|
11529
|
+
annotations: { title: "Add a shape component", ...WRITE8 },
|
|
10555
11530
|
handler: ({ layerId, index, ...rest }) => controller.addShapeComponent(shapeId(layerId), geometry(rest), index)
|
|
10556
11531
|
};
|
|
10557
11532
|
const removeComponent = {
|
|
@@ -10574,8 +11549,8 @@ function createImageShapeTools(controller) {
|
|
|
10574
11549
|
}
|
|
10575
11550
|
|
|
10576
11551
|
// src/image-editor/type-tools.ts
|
|
10577
|
-
var
|
|
10578
|
-
var
|
|
11552
|
+
var READ11 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
11553
|
+
var WRITE9 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
10579
11554
|
var WRITE_IDEMPOTENT6 = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
10580
11555
|
var DESTRUCTIVE7 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
|
|
10581
11556
|
var LAYER_ID6 = { type: "string", description: "Type layer id from list_layers (type type) or add_text_layer." };
|
|
@@ -10603,7 +11578,7 @@ var PARAGRAPH = {
|
|
|
10603
11578
|
spaceBefore: { type: ["number", "null"], description: "Space above the paragraph in pixels (not before the first)." },
|
|
10604
11579
|
spaceAfter: { type: ["number", "null"], description: "Space below the paragraph in pixels." }
|
|
10605
11580
|
};
|
|
10606
|
-
function
|
|
11581
|
+
function pick2(args, keys) {
|
|
10607
11582
|
const out = {};
|
|
10608
11583
|
for (const k of keys) if (args[k] !== void 0) out[k] = args[k];
|
|
10609
11584
|
return out;
|
|
@@ -10625,7 +11600,7 @@ function createImageTypeTools(controller) {
|
|
|
10625
11600
|
title: "List fonts",
|
|
10626
11601
|
description: "Returns the font families type layers can use here (generic families such as sans-serif always work). Changes nothing.",
|
|
10627
11602
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
10628
|
-
annotations: { title: "List fonts", ...
|
|
11603
|
+
annotations: { title: "List fonts", ...READ11 },
|
|
10629
11604
|
handler: () => ({ families: controller.listFonts(), shaper: controller.getTextShaper()?.id ?? null })
|
|
10630
11605
|
};
|
|
10631
11606
|
const get = {
|
|
@@ -10633,7 +11608,7 @@ function createImageTypeTools(controller) {
|
|
|
10633
11608
|
title: "Read a type layer",
|
|
10634
11609
|
description: "Returns a type layer\u2019s text, its character runs and paragraphs as [start, end) ranges in UTF-16 code units with their styles, the frame (point text, or a paragraph box with its size), the text-to-document transform, the laid-out lines (range, baseline, width), the rendered bounds, missing fonts and whether the pixels are live or stored from a file. Call before editing text you did not write, to get the ranges. Changes nothing.",
|
|
10635
11610
|
inputSchema: { type: "object", properties: { layerId: LAYER_ID6 }, required: ["layerId"], additionalProperties: false },
|
|
10636
|
-
annotations: { title: "Read a type layer", ...
|
|
11611
|
+
annotations: { title: "Read a type layer", ...READ11 },
|
|
10637
11612
|
handler: ({ layerId }) => controller.getTextLayer(typeId(layerId))
|
|
10638
11613
|
};
|
|
10639
11614
|
const add2 = {
|
|
@@ -10667,9 +11642,9 @@ function createImageTypeTools(controller) {
|
|
|
10667
11642
|
required: ["text", "x", "y"],
|
|
10668
11643
|
additionalProperties: false
|
|
10669
11644
|
},
|
|
10670
|
-
annotations: { title: "Add a type layer", ...
|
|
11645
|
+
annotations: { title: "Add a type layer", ...WRITE9 },
|
|
10671
11646
|
handler: (args) => {
|
|
10672
|
-
const init =
|
|
11647
|
+
const init = pick2(args, ["text", "x", "y", "box", "style", "paragraph", "rotation", "name", "opacity", "blend", "aboveId", "belowId", "parentId", "index"]);
|
|
10673
11648
|
if (init.blend !== void 0) init.blend = init.blend;
|
|
10674
11649
|
const id = controller.addTextLayer(init);
|
|
10675
11650
|
return { layer: layerSummary(id), text: controller.getTextLayer(id) };
|
|
@@ -10680,7 +11655,7 @@ function createImageTypeTools(controller) {
|
|
|
10680
11655
|
title: "Edit text",
|
|
10681
11656
|
description: "Replaces the text in [start, end) (default all of it) with text; inserted text takes the style of the character at start. Use start = end to insert. Returns the text layer with its new runs.",
|
|
10682
11657
|
inputSchema: { type: "object", properties: { layerId: LAYER_ID6, text: { type: "string", description: "Replacement text (\\n starts a paragraph; empty deletes the range)." }, start: START, end: END }, required: ["layerId", "text"], additionalProperties: false },
|
|
10683
|
-
annotations: { title: "Edit text", ...
|
|
11658
|
+
annotations: { title: "Edit text", ...WRITE9 },
|
|
10684
11659
|
handler: (args) => controller.setText(typeId(args.layerId), args.text, range(args))
|
|
10685
11660
|
};
|
|
10686
11661
|
const setStyle = {
|
|
@@ -10690,7 +11665,7 @@ function createImageTypeTools(controller) {
|
|
|
10690
11665
|
inputSchema: { type: "object", properties: { layerId: LAYER_ID6, start: START, end: END, ...STYLE }, required: ["layerId"], additionalProperties: false },
|
|
10691
11666
|
annotations: { title: "Style a range of text", ...WRITE_IDEMPOTENT6 },
|
|
10692
11667
|
handler: (args) => {
|
|
10693
|
-
const style =
|
|
11668
|
+
const style = pick2(args, Object.keys(STYLE));
|
|
10694
11669
|
if (!Object.keys(style).length) throw new LayerOperationError(`Give at least one style field: ${Object.keys(STYLE).join(", ")}.`);
|
|
10695
11670
|
return controller.setTextStyle(typeId(args.layerId), style, range(args));
|
|
10696
11671
|
}
|
|
@@ -10702,7 +11677,7 @@ function createImageTypeTools(controller) {
|
|
|
10702
11677
|
inputSchema: { type: "object", properties: { layerId: LAYER_ID6, start: START, end: END, ...PARAGRAPH }, required: ["layerId"], additionalProperties: false },
|
|
10703
11678
|
annotations: { title: "Style paragraphs", ...WRITE_IDEMPOTENT6 },
|
|
10704
11679
|
handler: (args) => {
|
|
10705
|
-
const style =
|
|
11680
|
+
const style = pick2(args, Object.keys(PARAGRAPH));
|
|
10706
11681
|
if (!Object.keys(style).length) throw new LayerOperationError(`Give at least one paragraph field: ${Object.keys(PARAGRAPH).join(", ")}.`);
|
|
10707
11682
|
return controller.setParagraphStyle(typeId(args.layerId), style, range(args));
|
|
10708
11683
|
}
|
|
@@ -10986,6 +11961,8 @@ function createImageEditorToolProvider(controller, options = {}) {
|
|
|
10986
11961
|
...createImageTypeTools(controller),
|
|
10987
11962
|
...createImageEffectTools(controller),
|
|
10988
11963
|
...createImagePaintTools(controller),
|
|
11964
|
+
...createImageColorTools(controller),
|
|
11965
|
+
...createImageRetouchTools(controller),
|
|
10989
11966
|
...createPigmentTools(),
|
|
10990
11967
|
...createImagePsdTools(controller)
|
|
10991
11968
|
]
|
|
@@ -11980,6 +12957,11 @@ function extractNormalizedLayerItems(controller, fallbackImageUrl = "") {
|
|
|
11980
12957
|
};
|
|
11981
12958
|
});
|
|
11982
12959
|
}
|
|
12960
|
+
var BIT_DEPTH_OPTIONS = [
|
|
12961
|
+
{ depth: 8, label: "8-bit", title: "8 bits per channel" },
|
|
12962
|
+
{ depth: 16, label: "16-bit", title: "16 bits per channel: smooth gradients, no banding under strong adjustments" },
|
|
12963
|
+
{ depth: 32, label: "32-bit", title: "32-bit float per channel, linear light (HDR)" }
|
|
12964
|
+
];
|
|
11983
12965
|
|
|
11984
12966
|
// src/image-editor/layers/effects-ui.ts
|
|
11985
12967
|
function layerEffectsPanel(layer) {
|
|
@@ -12154,6 +13136,25 @@ function effectControlChange(effect, key, value, patterns = []) {
|
|
|
12154
13136
|
return { [top]: setPath(getPath(effect, top), rest, value) };
|
|
12155
13137
|
}
|
|
12156
13138
|
|
|
13139
|
+
// src/image-editor/layers/color-ui.ts
|
|
13140
|
+
var WORKING_SPACE_OPTIONS = BUILTIN_PROFILES.filter((b) => b.colorSpace === "rgb").map((b) => ({ id: b.id, label: b.description }));
|
|
13141
|
+
var PROOF_PROFILE_OPTIONS = BUILTIN_PROFILES.map((b) => ({ id: b.id, label: b.description }));
|
|
13142
|
+
var INTENT_OPTIONS = [
|
|
13143
|
+
{ id: "relative", label: "Relative Colorimetric", title: "Keep in-gamut colours exact, clip the rest (photos)" },
|
|
13144
|
+
{ id: "perceptual", label: "Perceptual", title: "Compress the whole gamut smoothly" },
|
|
13145
|
+
{ id: "saturation", label: "Saturation", title: "Keep colours vivid (graphics)" },
|
|
13146
|
+
{ id: "absolute", label: "Absolute Colorimetric", title: "Keep the source white too (paper proofs)" }
|
|
13147
|
+
];
|
|
13148
|
+
function colorPanelView(controller) {
|
|
13149
|
+
const profile = controller.getColorProfile();
|
|
13150
|
+
const proof = controller.getState().proofSetup ?? null;
|
|
13151
|
+
return {
|
|
13152
|
+
profileName: profile.name,
|
|
13153
|
+
workingSpace: profile.builtin,
|
|
13154
|
+
proof: proof ? { id: proof.profile.builtin ?? null, name: proof.profile.name, intent: proof.intent, simulatePaper: proof.simulatePaper, gamutWarning: proof.gamutWarning, bpc: proof.bpc } : null
|
|
13155
|
+
};
|
|
13156
|
+
}
|
|
13157
|
+
|
|
12157
13158
|
// src/image-editor/psd/ui.ts
|
|
12158
13159
|
var PSD_FILE_ACCEPT = ".psd,.psb,image/vnd.adobe.photoshop,application/x-photoshop";
|
|
12159
13160
|
function blobBytes(file) {
|
|
@@ -12766,9 +13767,12 @@ export {
|
|
|
12766
13767
|
AS_SHOT_KELVIN,
|
|
12767
13768
|
BACKGROUND_LAYER_ID,
|
|
12768
13769
|
BACKGROUND_LAYER_NAME,
|
|
13770
|
+
BIT_DEPTHS,
|
|
13771
|
+
BIT_DEPTH_OPTIONS,
|
|
12769
13772
|
BLEND_MODES,
|
|
12770
13773
|
BLUR_FILTER_ID,
|
|
12771
13774
|
BRUSH_OPTION_CONTROLS,
|
|
13775
|
+
BUILTIN_PROFILES,
|
|
12772
13776
|
CoverageMap,
|
|
12773
13777
|
D50,
|
|
12774
13778
|
D65,
|
|
@@ -12780,7 +13784,9 @@ export {
|
|
|
12780
13784
|
DEFAULT_GLOBAL_LIGHT,
|
|
12781
13785
|
DEFAULT_MITER_LIMIT,
|
|
12782
13786
|
DEFAULT_MIXER,
|
|
13787
|
+
DEFAULT_PROOF,
|
|
12783
13788
|
DEFAULT_REFINE_EDGE_PARAMS,
|
|
13789
|
+
DEFAULT_RETOUCH,
|
|
12784
13790
|
DEFAULT_SELECTION_TOOL_OPTIONS,
|
|
12785
13791
|
DEFAULT_SHAPE_TOOL_OPTIONS,
|
|
12786
13792
|
DEFAULT_SMUDGE,
|
|
@@ -12808,6 +13814,8 @@ export {
|
|
|
12808
13814
|
IMAGE_EDITOR_SCHEMA_VERSION,
|
|
12809
13815
|
IMAGE_EDITOR_STATE_VERSION,
|
|
12810
13816
|
IMAGE_FILTER_PRESETS,
|
|
13817
|
+
INTENTS,
|
|
13818
|
+
INTENT_OPTIONS,
|
|
12811
13819
|
INTERPOLATIONS,
|
|
12812
13820
|
ImageEditorController,
|
|
12813
13821
|
ImageEditorRenderer,
|
|
@@ -12841,6 +13849,7 @@ export {
|
|
|
12841
13849
|
PIGMENT_LAMBDA_MIN,
|
|
12842
13850
|
PIGMENT_LAMBDA_STEP,
|
|
12843
13851
|
PIGMENT_WAVELENGTHS,
|
|
13852
|
+
PROOF_PROFILE_OPTIONS,
|
|
12844
13853
|
PSD_BLEND_KEYS,
|
|
12845
13854
|
PSD_FILE_ACCEPT,
|
|
12846
13855
|
PaintExchange,
|
|
@@ -12850,6 +13859,13 @@ export {
|
|
|
12850
13859
|
PatternTile,
|
|
12851
13860
|
PsdError,
|
|
12852
13861
|
REFINE_EDGE_CONTROLS,
|
|
13862
|
+
RETOUCH_CHOICES,
|
|
13863
|
+
RETOUCH_OPTION_CONTROLS,
|
|
13864
|
+
RETOUCH_SAMPLES,
|
|
13865
|
+
RETOUCH_TOGGLES,
|
|
13866
|
+
RETOUCH_TOOLS,
|
|
13867
|
+
RegionPixelSource,
|
|
13868
|
+
RetouchDabStroke,
|
|
12853
13869
|
SELECTION_MODES,
|
|
12854
13870
|
SELECTION_MODE_LABELS,
|
|
12855
13871
|
SELECTION_MODIFY_OPS,
|
|
@@ -12859,6 +13875,7 @@ export {
|
|
|
12859
13875
|
SHAPE_OPERATIONS,
|
|
12860
13876
|
SHAPE_RENDER_MARGIN,
|
|
12861
13877
|
SMART_RENDER_MARGIN,
|
|
13878
|
+
SPOT_HEAL_TYPES,
|
|
12862
13879
|
SUPPORTED_ADJUSTMENT_KEYS,
|
|
12863
13880
|
SelectionGesture,
|
|
12864
13881
|
ShapeDrawGesture,
|
|
@@ -12872,10 +13889,12 @@ export {
|
|
|
12872
13889
|
TILE_SHIFT,
|
|
12873
13890
|
TINT_SCALE,
|
|
12874
13891
|
TONE_LUT_SIZE,
|
|
13892
|
+
TONE_RANGES,
|
|
12875
13893
|
TONE_RANGE_PRESETS,
|
|
12876
13894
|
TRANSFORM_ANCHORS,
|
|
12877
13895
|
TextEditSession,
|
|
12878
13896
|
TileMedium,
|
|
13897
|
+
TilePixelSource,
|
|
12879
13898
|
TiledSurface,
|
|
12880
13899
|
TransformTooLargeError,
|
|
12881
13900
|
TypeToolGesture,
|
|
@@ -12884,6 +13903,7 @@ export {
|
|
|
12884
13903
|
WARP_STYLES,
|
|
12885
13904
|
WARP_STYLE_LABELS,
|
|
12886
13905
|
WARP_STYLE_OPTIONS,
|
|
13906
|
+
WORKING_SPACE_OPTIONS,
|
|
12887
13907
|
XYZ_D50_TO_SRGB,
|
|
12888
13908
|
ZlibError,
|
|
12889
13909
|
activeAdjustmentLayers,
|
|
@@ -12905,6 +13925,7 @@ export {
|
|
|
12905
13925
|
annotationOfShape,
|
|
12906
13926
|
annotationOfType,
|
|
12907
13927
|
applyAdjustmentLayers,
|
|
13928
|
+
applyAdjustmentLayersFloat,
|
|
12908
13929
|
applyAdjustments,
|
|
12909
13930
|
applyBoxBlur,
|
|
12910
13931
|
applyDepthMask,
|
|
@@ -12917,12 +13938,14 @@ export {
|
|
|
12917
13938
|
applyProcess2Adjustments,
|
|
12918
13939
|
applyRegionBlur,
|
|
12919
13940
|
applyRegionMosaic,
|
|
13941
|
+
applyRetouchToTile,
|
|
12920
13942
|
applyShapeOptions,
|
|
12921
13943
|
applySmartFilters,
|
|
12922
13944
|
applyStrokeToTile,
|
|
12923
13945
|
backgroundLayer,
|
|
12924
13946
|
base64ToBytes,
|
|
12925
13947
|
baseProjection,
|
|
13948
|
+
bestOffset,
|
|
12926
13949
|
blackWhiteGray,
|
|
12927
13950
|
blendChannel,
|
|
12928
13951
|
blendImageData,
|
|
@@ -12952,12 +13975,15 @@ export {
|
|
|
12952
13975
|
clampControlValue,
|
|
12953
13976
|
clampCropToImage,
|
|
12954
13977
|
cleanMixer,
|
|
13978
|
+
cloneSourceLabel,
|
|
12955
13979
|
collectTiles,
|
|
12956
13980
|
colorBalanceChannel,
|
|
13981
|
+
colorPanelView,
|
|
12957
13982
|
colorRange,
|
|
12958
13983
|
combineSelections,
|
|
12959
13984
|
compileAdjustmentLayer,
|
|
12960
13985
|
componentPath,
|
|
13986
|
+
compositeDeep,
|
|
12961
13987
|
compositeSurfaceOver,
|
|
12962
13988
|
computeHistogram,
|
|
12963
13989
|
computeSelectionBounds,
|
|
@@ -12967,6 +13993,9 @@ export {
|
|
|
12967
13993
|
contractCoverage,
|
|
12968
13994
|
controlPosition,
|
|
12969
13995
|
controlTrackBackground,
|
|
13996
|
+
convertHex,
|
|
13997
|
+
convertSurfaceColors,
|
|
13998
|
+
convertSurfaceDepth,
|
|
12970
13999
|
convertToSmartObject,
|
|
12971
14000
|
copySmartObject,
|
|
12972
14001
|
cornersFromMatrix,
|
|
@@ -12977,17 +14006,20 @@ export {
|
|
|
12977
14006
|
createBlenderScratch,
|
|
12978
14007
|
createCanvasTextShaper,
|
|
12979
14008
|
createImageCaptureSurface,
|
|
14009
|
+
createImageColorTools,
|
|
12980
14010
|
createImageEditTools,
|
|
12981
14011
|
createImageEditorToolProvider,
|
|
12982
14012
|
createImageEffectTools,
|
|
12983
14013
|
createImageLayerTools,
|
|
12984
14014
|
createImagePaintTools,
|
|
12985
14015
|
createImagePsdTools,
|
|
14016
|
+
createImageRetouchTools,
|
|
12986
14017
|
createImageSelectionTools,
|
|
12987
14018
|
createImageShapeTools,
|
|
12988
14019
|
createImageSmartObjectTools,
|
|
12989
14020
|
createImageTransformTools,
|
|
12990
14021
|
createImageTypeTools,
|
|
14022
|
+
createMetricTextShaper,
|
|
12991
14023
|
createOutlineTextShaper,
|
|
12992
14024
|
createPigment,
|
|
12993
14025
|
createPigmentTools,
|
|
@@ -13002,6 +14034,7 @@ export {
|
|
|
13002
14034
|
curveSvgPath,
|
|
13003
14035
|
dabCoverage,
|
|
13004
14036
|
dabRect,
|
|
14037
|
+
decodeDeepSurface,
|
|
13005
14038
|
decodeLayerChannel,
|
|
13006
14039
|
decodeLayerMask,
|
|
13007
14040
|
decodeMergedImage,
|
|
@@ -13009,6 +14042,8 @@ export {
|
|
|
13009
14042
|
decodePng,
|
|
13010
14043
|
decodePngDataUrl,
|
|
13011
14044
|
decodeSelectionRle,
|
|
14045
|
+
deepBytes,
|
|
14046
|
+
deepTile,
|
|
13012
14047
|
defaultAdjustmentParams,
|
|
13013
14048
|
defaultEffectGradient,
|
|
13014
14049
|
defaultLayerEffect,
|
|
@@ -13016,11 +14051,13 @@ export {
|
|
|
13016
14051
|
deflate,
|
|
13017
14052
|
deflateRaw,
|
|
13018
14053
|
depthMaskAlpha,
|
|
14054
|
+
depthOf,
|
|
13019
14055
|
depthRasterMask,
|
|
13020
14056
|
describeLayer,
|
|
13021
14057
|
describeLayerEffects,
|
|
13022
14058
|
describeSelection,
|
|
13023
14059
|
describeTransform,
|
|
14060
|
+
deserializeColorProfile,
|
|
13024
14061
|
deserializeEffects,
|
|
13025
14062
|
deserializeLayer,
|
|
13026
14063
|
deserializeLayers,
|
|
@@ -13029,8 +14066,13 @@ export {
|
|
|
13029
14066
|
developFilter,
|
|
13030
14067
|
developFromAdjustments,
|
|
13031
14068
|
developTarget,
|
|
14069
|
+
dilateMask,
|
|
14070
|
+
displayTransform,
|
|
13032
14071
|
distanceTransform,
|
|
13033
14072
|
documentContents,
|
|
14073
|
+
documentProfile,
|
|
14074
|
+
dodgeBurn,
|
|
14075
|
+
dodgeBurnCurve,
|
|
13034
14076
|
downloadPsd,
|
|
13035
14077
|
dragWarpMeshPoint,
|
|
13036
14078
|
dropPlacement,
|
|
@@ -13047,6 +14089,7 @@ export {
|
|
|
13047
14089
|
ellipseSelection,
|
|
13048
14090
|
emptyHistogram,
|
|
13049
14091
|
emptySurface,
|
|
14092
|
+
encodeDeepSurface,
|
|
13050
14093
|
encodePng,
|
|
13051
14094
|
encodeRle,
|
|
13052
14095
|
encodeSelectionRle,
|
|
@@ -13092,6 +14135,7 @@ export {
|
|
|
13092
14135
|
hasWarp,
|
|
13093
14136
|
hashMaskContent,
|
|
13094
14137
|
hashNoise,
|
|
14138
|
+
healPixels,
|
|
13095
14139
|
hexToRgb,
|
|
13096
14140
|
histogramPath,
|
|
13097
14141
|
hitTestAnnotation,
|
|
@@ -13121,6 +14165,7 @@ export {
|
|
|
13121
14165
|
invertSelection,
|
|
13122
14166
|
isActiveFilter,
|
|
13123
14167
|
isAdjustmentKind,
|
|
14168
|
+
isBitDepth,
|
|
13124
14169
|
isBlendMode,
|
|
13125
14170
|
isContentsDecoded,
|
|
13126
14171
|
isDefaultDepthMask,
|
|
@@ -13135,10 +14180,13 @@ export {
|
|
|
13135
14180
|
isQuarterTurn,
|
|
13136
14181
|
isQuarterTurnSwapped,
|
|
13137
14182
|
isRenderedLayer,
|
|
14183
|
+
isRetouchPaintTool,
|
|
14184
|
+
isRetouchTool,
|
|
13138
14185
|
isSelectionEmpty,
|
|
13139
14186
|
isSelectionMode,
|
|
13140
14187
|
isSeparableBlendMode,
|
|
13141
14188
|
isSpatialFilter,
|
|
14189
|
+
isSrgb,
|
|
13142
14190
|
isTransformAnchor,
|
|
13143
14191
|
isValidWarp,
|
|
13144
14192
|
kelvinToTemperature,
|
|
@@ -13176,13 +14224,17 @@ export {
|
|
|
13176
14224
|
liveShapeSubpaths,
|
|
13177
14225
|
loadImageEditorDocument,
|
|
13178
14226
|
loadMixer,
|
|
14227
|
+
loadOpenTypeFont,
|
|
14228
|
+
luma,
|
|
13179
14229
|
magicWand,
|
|
13180
14230
|
mapCropHandle,
|
|
13181
14231
|
mapLayer,
|
|
13182
14232
|
mapSubpaths,
|
|
14233
|
+
maskBounds,
|
|
13183
14234
|
maskSummary,
|
|
13184
14235
|
matrixFromCorners,
|
|
13185
14236
|
matrixFromParams,
|
|
14237
|
+
membraneFill,
|
|
13186
14238
|
mergeLayerEffect,
|
|
13187
14239
|
mergeShapeComponents,
|
|
13188
14240
|
migrateSerializedImageEditorState,
|
|
@@ -13217,6 +14269,7 @@ export {
|
|
|
13217
14269
|
normalizeProcessVersion,
|
|
13218
14270
|
normalizeRect,
|
|
13219
14271
|
normalizeRefineEdgeParams,
|
|
14272
|
+
normalizeRetouch,
|
|
13220
14273
|
normalizeShapePaint,
|
|
13221
14274
|
normalizeShapeStroke,
|
|
13222
14275
|
normalizeSmartFilter,
|
|
@@ -13238,11 +14291,13 @@ export {
|
|
|
13238
14291
|
parseEffectColor,
|
|
13239
14292
|
parseLfx2,
|
|
13240
14293
|
parseLrfx,
|
|
14294
|
+
parseOpenTypeFont,
|
|
13241
14295
|
parsePatternBlock,
|
|
13242
14296
|
parseSelectionMode,
|
|
13243
14297
|
parseVersionedDescriptor,
|
|
13244
14298
|
patchParagraphStyle,
|
|
13245
14299
|
patchTextStyle,
|
|
14300
|
+
perDabStrength,
|
|
13246
14301
|
photoFilterMatrix,
|
|
13247
14302
|
pigment8,
|
|
13248
14303
|
pixelLayerBounds,
|
|
@@ -13259,10 +14314,12 @@ export {
|
|
|
13259
14314
|
posterizeValue,
|
|
13260
14315
|
presetWarp,
|
|
13261
14316
|
previewStep,
|
|
14317
|
+
profileBytes,
|
|
13262
14318
|
projectImagePoint,
|
|
13263
14319
|
projectImageRect,
|
|
13264
14320
|
projectMaskCoverage,
|
|
13265
14321
|
projectQuarterTurn,
|
|
14322
|
+
proxyOf,
|
|
13266
14323
|
psdBytesFromBase64,
|
|
13267
14324
|
psdFileName,
|
|
13268
14325
|
psdPatternToEffectPattern,
|
|
@@ -13276,9 +14333,11 @@ export {
|
|
|
13276
14333
|
rasterizeDab,
|
|
13277
14334
|
rasterizeSmartObject,
|
|
13278
14335
|
readAdjustmentControls,
|
|
14336
|
+
readDeepRegion,
|
|
13279
14337
|
readPixel,
|
|
13280
14338
|
readPsd,
|
|
13281
14339
|
readRegion,
|
|
14340
|
+
readSourceRegion,
|
|
13282
14341
|
rectSelection,
|
|
13283
14342
|
rectToQuad,
|
|
13284
14343
|
reduceSurface,
|
|
@@ -13315,16 +14374,20 @@ export {
|
|
|
13315
14374
|
resizeWarpMesh,
|
|
13316
14375
|
resolveExternalAssets,
|
|
13317
14376
|
resolveMosaicRegionBounds,
|
|
14377
|
+
resolveProfile,
|
|
13318
14378
|
restyleRange,
|
|
13319
14379
|
rgbToGray,
|
|
13320
14380
|
rgbToHsl,
|
|
13321
14381
|
rgbToReflectance,
|
|
13322
14382
|
rgbUnitToHex,
|
|
14383
|
+
rgbaViewOfGrey,
|
|
13323
14384
|
runLayerFilters,
|
|
13324
14385
|
runLayerMenuAction,
|
|
13325
14386
|
sampleLut,
|
|
13326
14387
|
sampleToneLut,
|
|
14388
|
+
saturationOf,
|
|
13327
14389
|
scaleLayerEffect,
|
|
14390
|
+
seamlessClone,
|
|
13328
14391
|
selectionArea,
|
|
13329
14392
|
selectionFromCoverage,
|
|
13330
14393
|
selectionFromPixels,
|
|
@@ -13333,6 +14396,7 @@ export {
|
|
|
13333
14396
|
selectionToCoverage,
|
|
13334
14397
|
selectionToPixels,
|
|
13335
14398
|
selectionValueAt,
|
|
14399
|
+
serializeColorProfile,
|
|
13336
14400
|
serializeContents,
|
|
13337
14401
|
serializeEffects,
|
|
13338
14402
|
serializeLayer,
|
|
@@ -13347,6 +14411,7 @@ export {
|
|
|
13347
14411
|
setLayerProperties,
|
|
13348
14412
|
setLum,
|
|
13349
14413
|
setPigmentColor,
|
|
14414
|
+
setRetouchChoice,
|
|
13350
14415
|
setSmartFilterMask,
|
|
13351
14416
|
setSmartFiltersEnabled,
|
|
13352
14417
|
setSmartPlacement,
|
|
@@ -13361,15 +14426,25 @@ export {
|
|
|
13361
14426
|
smartObjectLayers,
|
|
13362
14427
|
smoothCoverage,
|
|
13363
14428
|
smoothPoints,
|
|
14429
|
+
smudgeEffect,
|
|
13364
14430
|
solidSurface,
|
|
14431
|
+
solveMembrane,
|
|
13365
14432
|
spliceRuns,
|
|
13366
14433
|
splitOverlayAnnotations,
|
|
14434
|
+
sponge,
|
|
14435
|
+
spongeEffect,
|
|
14436
|
+
spotHealPixels,
|
|
14437
|
+
spotHealWindow,
|
|
13367
14438
|
srgb8ToLab,
|
|
14439
|
+
srgbDecode,
|
|
14440
|
+
srgbEncode,
|
|
13368
14441
|
srgbToLinear,
|
|
13369
14442
|
standardBlendMode,
|
|
13370
14443
|
styleAt,
|
|
13371
14444
|
sumHistograms,
|
|
13372
14445
|
surfaceBytes,
|
|
14446
|
+
surfaceDepth,
|
|
14447
|
+
surfaceFromDeepPixels,
|
|
13373
14448
|
surfaceFromPixels,
|
|
13374
14449
|
surfacesEqual,
|
|
13375
14450
|
tempTintToXy,
|
|
@@ -13381,14 +14456,24 @@ export {
|
|
|
13381
14456
|
tileCoord,
|
|
13382
14457
|
tileKey,
|
|
13383
14458
|
tileRect,
|
|
14459
|
+
tileSamples,
|
|
14460
|
+
tileSamples16,
|
|
14461
|
+
tileSamples32,
|
|
13384
14462
|
tilesInRect,
|
|
13385
14463
|
tipFalloff,
|
|
13386
14464
|
tipReach,
|
|
13387
14465
|
tipShape,
|
|
14466
|
+
to16From32,
|
|
14467
|
+
to16From8,
|
|
14468
|
+
to32From16,
|
|
14469
|
+
to32From8,
|
|
14470
|
+
to8From16,
|
|
14471
|
+
to8From32,
|
|
13388
14472
|
toHex,
|
|
13389
14473
|
toTextSpace,
|
|
13390
14474
|
toneCurve,
|
|
13391
14475
|
toneLuts,
|
|
14476
|
+
toneOf,
|
|
13392
14477
|
toneRange,
|
|
13393
14478
|
transformHomography,
|
|
13394
14479
|
transformMap,
|
|
@@ -13433,6 +14518,7 @@ export {
|
|
|
13433
14518
|
withFilterMaskSurface,
|
|
13434
14519
|
withNewIds,
|
|
13435
14520
|
writeAdjustmentBlock,
|
|
14521
|
+
writeDeepRegion,
|
|
13436
14522
|
writeLfx2,
|
|
13437
14523
|
writePatternBlock,
|
|
13438
14524
|
writePsd,
|