@pixerate/editor 0.94.0 → 0.96.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 +2 -0
- package/dist/{chunk-ZOOEX3WT.cjs → chunk-CRMAXIUV.cjs} +369 -7
- package/dist/chunk-CRMAXIUV.cjs.map +1 -0
- package/dist/{chunk-D4ZCU72J.js → chunk-HEW6KDNI.js} +366 -4
- package/dist/chunk-HEW6KDNI.js.map +1 -0
- package/dist/{chunk-ISDCJHTO.js → chunk-IB53LVGV.js} +238 -285
- package/dist/chunk-IB53LVGV.js.map +1 -0
- package/dist/{chunk-747CGOT2.js → chunk-KFTQ2PKP.js} +29 -2
- package/dist/chunk-KFTQ2PKP.js.map +1 -0
- package/dist/{chunk-73L6TROJ.cjs → chunk-T422YCHR.cjs} +71 -44
- package/dist/chunk-T422YCHR.cjs.map +1 -0
- package/dist/{chunk-4F3YDIWW.cjs → chunk-ZZOTHJ4K.cjs} +264 -311
- package/dist/chunk-ZZOTHJ4K.cjs.map +1 -0
- package/dist/image-editor/index.cjs +1832 -632
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +179 -22
- package/dist/image-editor/index.d.ts +179 -22
- package/dist/image-editor/index.js +1463 -263
- package/dist/image-editor/index.js.map +1 -1
- package/dist/paint/index.cjs +22 -3
- package/dist/paint/index.cjs.map +1 -1
- package/dist/paint/index.d.cts +117 -2
- package/dist/paint/index.d.ts +117 -2
- package/dist/paint/index.js +32 -13
- package/dist/raster/index.cjs +7 -3
- package/dist/raster/index.cjs.map +1 -1
- package/dist/raster/index.d.cts +796 -5
- package/dist/raster/index.d.ts +796 -5
- package/dist/raster/index.js +6 -2
- package/dist/{surface-CuBg2c6k.d.cts → surface-DIA-XatS.d.cts} +23 -11
- package/dist/{surface-CuBg2c6k.d.ts → surface-DIA-XatS.d.ts} +23 -11
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +39 -6
- package/dist/chunk-4F3YDIWW.cjs.map +0 -1
- package/dist/chunk-73L6TROJ.cjs.map +0 -1
- package/dist/chunk-747CGOT2.js.map +0 -1
- package/dist/chunk-D4ZCU72J.js.map +0 -1
- package/dist/chunk-ISDCJHTO.js.map +0 -1
- package/dist/chunk-ZOOEX3WT.cjs.map +0 -1
- package/dist/zlib-BJ6U7LuB.d.cts +0 -795
- package/dist/zlib-BjQkpMFi.d.ts +0 -795
|
@@ -9,6 +9,9 @@ import {
|
|
|
9
9
|
DIRECT_GAUSSIAN_MAX_SIGMA,
|
|
10
10
|
LAYER_EFFECT_KINDS,
|
|
11
11
|
LayerEffectError,
|
|
12
|
+
MAX_EFFECT_REACH,
|
|
13
|
+
PatternPlacement,
|
|
14
|
+
PatternTile,
|
|
12
15
|
RasterCompositor,
|
|
13
16
|
ZlibError,
|
|
14
17
|
adler32,
|
|
@@ -20,61 +23,76 @@ import {
|
|
|
20
23
|
changedTiles,
|
|
21
24
|
curveLut,
|
|
22
25
|
decodePng,
|
|
26
|
+
defaultEffectGradient,
|
|
23
27
|
defaultLayerEffect,
|
|
24
28
|
deflate,
|
|
25
29
|
deflateRaw,
|
|
30
|
+
effectColorHex,
|
|
31
|
+
effectReach,
|
|
32
|
+
effectsFingerprint,
|
|
26
33
|
effectsMargin,
|
|
34
|
+
effectsScale,
|
|
27
35
|
encodePng,
|
|
28
36
|
findLayer,
|
|
29
37
|
flattenLayers,
|
|
30
38
|
gaussianBlur,
|
|
39
|
+
hasActiveEffects,
|
|
40
|
+
hashNoise,
|
|
31
41
|
inflate,
|
|
32
42
|
inflateRaw,
|
|
33
43
|
insertLayer,
|
|
34
44
|
isLayerEffectKind,
|
|
35
45
|
isLayerLocked,
|
|
36
46
|
isPng,
|
|
47
|
+
layerEffectFields,
|
|
37
48
|
layerEffectLabel,
|
|
38
49
|
mapLayer,
|
|
39
50
|
mergeLayerEffect,
|
|
40
51
|
newEffectId,
|
|
41
52
|
normalizeLayerEffects,
|
|
42
53
|
parseDataUrl,
|
|
54
|
+
parseEffectColor,
|
|
43
55
|
pngDataUrl,
|
|
56
|
+
renderLayerEffects,
|
|
57
|
+
scaleLayerEffect,
|
|
58
|
+
validateEffectGradient,
|
|
59
|
+
validateLayerEffect,
|
|
44
60
|
validateLayerEffects
|
|
45
|
-
} from "../chunk-
|
|
61
|
+
} from "../chunk-KFTQ2PKP.js";
|
|
46
62
|
import {
|
|
47
63
|
CoverageMap,
|
|
48
64
|
DEFAULT_BRUSH,
|
|
65
|
+
DEFAULT_MIXER,
|
|
49
66
|
DabWalker,
|
|
67
|
+
EMPTY_MIXER_LOAD,
|
|
50
68
|
MAX_BRUSH_SIZE,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
69
|
+
MIXER_DRAIN,
|
|
70
|
+
MIXER_PICKUP_RATE,
|
|
71
|
+
MIXING_MODELS,
|
|
72
|
+
MixerStroke,
|
|
55
73
|
StrokeCoverage,
|
|
56
74
|
applyStrokeToTile,
|
|
75
|
+
cleanMixer,
|
|
57
76
|
coverageTileCoord,
|
|
58
|
-
createPigment,
|
|
59
77
|
createPigmentTools,
|
|
60
78
|
dabCoverage,
|
|
61
79
|
dabRect,
|
|
62
|
-
|
|
63
|
-
lerpPigmentInto,
|
|
64
|
-
mixPigments,
|
|
80
|
+
loadMixer,
|
|
65
81
|
normalizeBrush,
|
|
82
|
+
normalizeMixer,
|
|
66
83
|
rasterizeDab,
|
|
67
|
-
reflectanceToRgb,
|
|
68
|
-
rgbToReflectance,
|
|
69
|
-
setPigmentColor,
|
|
70
84
|
smoothPoints,
|
|
71
85
|
tipFalloff,
|
|
72
86
|
tipReach,
|
|
73
87
|
tipShape,
|
|
74
88
|
walkDabs
|
|
75
|
-
} from "../chunk-
|
|
89
|
+
} from "../chunk-IB53LVGV.js";
|
|
76
90
|
import {
|
|
77
91
|
BLEND_MODES,
|
|
92
|
+
PIGMENT_BANDS,
|
|
93
|
+
PIGMENT_LAMBDA_MIN,
|
|
94
|
+
PIGMENT_LAMBDA_STEP,
|
|
95
|
+
PIGMENT_WAVELENGTHS,
|
|
78
96
|
SurfaceDraft,
|
|
79
97
|
TILE,
|
|
80
98
|
TILE_SHIFT,
|
|
@@ -87,16 +105,25 @@ import {
|
|
|
87
105
|
canvasCompositeOperation,
|
|
88
106
|
collectTiles,
|
|
89
107
|
contentBounds,
|
|
108
|
+
createPigment,
|
|
90
109
|
emptySurface,
|
|
91
110
|
fillRect,
|
|
92
111
|
finishTile,
|
|
93
112
|
isBlendMode,
|
|
94
113
|
isSeparableBlendMode,
|
|
114
|
+
lerpPigment,
|
|
115
|
+
lerpPigmentInto,
|
|
116
|
+
mixPigment8,
|
|
117
|
+
mixPigmentRgb,
|
|
118
|
+
mixPigments,
|
|
95
119
|
normalizeBlendMode,
|
|
96
120
|
packRgba,
|
|
97
121
|
readPixel,
|
|
98
122
|
readRegion,
|
|
123
|
+
reflectanceToRgb,
|
|
124
|
+
rgbToReflectance,
|
|
99
125
|
setLum,
|
|
126
|
+
setPigmentColor,
|
|
100
127
|
solidSurface,
|
|
101
128
|
surfaceBytes,
|
|
102
129
|
surfaceFromPixels,
|
|
@@ -108,7 +135,7 @@ import {
|
|
|
108
135
|
translateSurface,
|
|
109
136
|
uniformTile,
|
|
110
137
|
writeRegion
|
|
111
|
-
} from "../chunk-
|
|
138
|
+
} from "../chunk-HEW6KDNI.js";
|
|
112
139
|
|
|
113
140
|
// src/image-editor/color.ts
|
|
114
141
|
var LEGACY_PROCESS_VERSION = 1;
|
|
@@ -297,8 +324,8 @@ function whiteBalanceFromSample(rgb) {
|
|
|
297
324
|
if (lin[0] + lin[1] + lin[2] <= 1e-6) return { temperature: 0, tint: 0 };
|
|
298
325
|
const src = linearSrgbToXy(applyMatrix3(AS_SHOT_WB_INV, lin));
|
|
299
326
|
const { kelvin, tint } = xyToTempTint(src);
|
|
300
|
-
const
|
|
301
|
-
return { temperature:
|
|
327
|
+
const clamp4 = (v) => Math.max(-100, Math.min(100, v));
|
|
328
|
+
return { temperature: clamp4(kelvinToTemperature(kelvin)), tint: clamp4(tint) };
|
|
302
329
|
}
|
|
303
330
|
var TONE_LUT_SIZE = 4096;
|
|
304
331
|
var GREY = 0.18;
|
|
@@ -892,21 +919,21 @@ function toBase64(bytes, length) {
|
|
|
892
919
|
parts.push(chunk);
|
|
893
920
|
return parts.join("");
|
|
894
921
|
}
|
|
895
|
-
function fromBase64(
|
|
896
|
-
if (
|
|
922
|
+
function fromBase64(text2) {
|
|
923
|
+
if (text2.length % 4 !== 0) throw new Error("Invalid base64 length");
|
|
897
924
|
let pad = 0;
|
|
898
|
-
if (
|
|
899
|
-
else if (
|
|
900
|
-
const out = new Uint8Array(
|
|
925
|
+
if (text2.endsWith("==")) pad = 2;
|
|
926
|
+
else if (text2.endsWith("=")) pad = 1;
|
|
927
|
+
const out = new Uint8Array(text2.length / 4 * 3 - pad);
|
|
901
928
|
let o = 0;
|
|
902
929
|
const digit = (i, k) => {
|
|
903
|
-
const ch =
|
|
904
|
-
if (ch === 61 && i + 4 ===
|
|
930
|
+
const ch = text2.charCodeAt(i + k);
|
|
931
|
+
if (ch === 61 && i + 4 === text2.length && k >= 4 - pad) return 0;
|
|
905
932
|
const idx = ch < 128 ? B64_INDEX[ch] : -1;
|
|
906
933
|
if (idx < 0) throw new Error("Invalid base64 character");
|
|
907
934
|
return idx;
|
|
908
935
|
};
|
|
909
|
-
for (let i = 0; i <
|
|
936
|
+
for (let i = 0; i < text2.length; i += 4) {
|
|
910
937
|
const n = digit(i, 0) << 18 | digit(i, 1) << 12 | digit(i, 2) << 6 | digit(i, 3);
|
|
911
938
|
if (o < out.length) out[o++] = n >> 16 & 255;
|
|
912
939
|
if (o < out.length) out[o++] = n >> 8 & 255;
|
|
@@ -1919,6 +1946,10 @@ function applyLayer(c, layer, op, coverage = 1) {
|
|
|
1919
1946
|
}
|
|
1920
1947
|
const cb0 = c[0], cb1 = c[1], cb2 = c[2];
|
|
1921
1948
|
op(c);
|
|
1949
|
+
if (blend === "pigment") {
|
|
1950
|
+
mixPigmentRgb([clamp01(cb0), clamp01(cb1), clamp01(cb2)], [clamp01(c[0]), clamp01(c[1]), clamp01(c[2])], alpha, c);
|
|
1951
|
+
return;
|
|
1952
|
+
}
|
|
1922
1953
|
const b = blend === "normal" ? c : blendRgb(blend, [cb0, cb1, cb2], c);
|
|
1923
1954
|
c[0] = (1 - alpha) * cb0 + alpha * b[0];
|
|
1924
1955
|
c[1] = (1 - alpha) * cb1 + alpha * b[1];
|
|
@@ -2413,10 +2444,10 @@ function depthMaskAlpha(depth, options) {
|
|
|
2413
2444
|
}
|
|
2414
2445
|
var filterSpecCache = /* @__PURE__ */ new WeakMap();
|
|
2415
2446
|
function pixelFilterSpec(layer) {
|
|
2416
|
-
const
|
|
2417
|
-
if (!
|
|
2418
|
-
if (filterSpecCache.has(
|
|
2419
|
-
const filters =
|
|
2447
|
+
const list2 = layer.filters;
|
|
2448
|
+
if (!list2?.length) return null;
|
|
2449
|
+
if (filterSpecCache.has(list2)) return filterSpecCache.get(list2);
|
|
2450
|
+
const filters = list2.filter(isActiveFilter);
|
|
2420
2451
|
let spec = null;
|
|
2421
2452
|
if (filters.length) {
|
|
2422
2453
|
const spatial = filters.some(isSpatialFilter);
|
|
@@ -2431,7 +2462,7 @@ function pixelFilterSpec(layer) {
|
|
|
2431
2462
|
}
|
|
2432
2463
|
};
|
|
2433
2464
|
}
|
|
2434
|
-
filterSpecCache.set(
|
|
2465
|
+
filterSpecCache.set(list2, spec);
|
|
2435
2466
|
return spec;
|
|
2436
2467
|
}
|
|
2437
2468
|
var PHOTO_PROFILE = {
|
|
@@ -2648,8 +2679,8 @@ function legacyAdjustmentLayer(node, width, height) {
|
|
|
2648
2679
|
}
|
|
2649
2680
|
function developTarget(layers, activeLayerId) {
|
|
2650
2681
|
const pixels = [];
|
|
2651
|
-
const walk = (
|
|
2652
|
-
for (const l of
|
|
2682
|
+
const walk = (list2) => {
|
|
2683
|
+
for (const l of list2) {
|
|
2653
2684
|
if (l.type === "pixel") pixels.push(l);
|
|
2654
2685
|
else if (l.type === "group") walk(l.children);
|
|
2655
2686
|
}
|
|
@@ -2683,8 +2714,8 @@ function legacyView(stack, width, height, activeLayerId = null) {
|
|
|
2683
2714
|
};
|
|
2684
2715
|
const adjNodes = [];
|
|
2685
2716
|
const annotations = [];
|
|
2686
|
-
const walk = (
|
|
2687
|
-
for (const l of
|
|
2717
|
+
const walk = (list2) => {
|
|
2718
|
+
for (const l of list2) {
|
|
2688
2719
|
if (l.type === "adjustment") adjNodes.push(l);
|
|
2689
2720
|
else if (l.type === "annotation") annotations.push(l.annotation);
|
|
2690
2721
|
else if (l.type === "group") walk(l.children);
|
|
@@ -4475,6 +4506,23 @@ function refineSelection(pixels, mask, params = {}) {
|
|
|
4475
4506
|
|
|
4476
4507
|
// src/image-editor/layers/serialize.ts
|
|
4477
4508
|
var LAYERED_SCHEMA_VERSION = 3;
|
|
4509
|
+
function serializePatterns(patterns) {
|
|
4510
|
+
return patterns.map((p) => ({ id: p.id, name: p.name, png: pngDataUrl(p.width, p.height, p.data) }));
|
|
4511
|
+
}
|
|
4512
|
+
function deserializePatterns(raw, problems = []) {
|
|
4513
|
+
if (!Array.isArray(raw)) return [];
|
|
4514
|
+
const out = [];
|
|
4515
|
+
for (const p of raw) {
|
|
4516
|
+
if (!p || typeof p !== "object" || typeof p.id !== "string" || typeof p.png !== "string") continue;
|
|
4517
|
+
try {
|
|
4518
|
+
const png = decodePngDataUrl(p.png, 4);
|
|
4519
|
+
out.push({ id: p.id, name: typeof p.name === "string" ? p.name : p.id, width: png.width, height: png.height, data: png.data });
|
|
4520
|
+
} catch (e) {
|
|
4521
|
+
problems.push(`Pattern "${p.name ?? p.id}" could not be decoded (${e instanceof Error ? e.message : String(e)}).`);
|
|
4522
|
+
}
|
|
4523
|
+
}
|
|
4524
|
+
return out;
|
|
4525
|
+
}
|
|
4478
4526
|
var encodedCache = /* @__PURE__ */ new WeakMap();
|
|
4479
4527
|
function encodeSurface(surface) {
|
|
4480
4528
|
if (encodedCache.has(surface)) return encodedCache.get(surface);
|
|
@@ -4770,7 +4818,7 @@ function deserializeLayer(raw, ctx) {
|
|
|
4770
4818
|
function deserializeLayers(raw, ctx) {
|
|
4771
4819
|
if (!Array.isArray(raw)) return [];
|
|
4772
4820
|
const seen = /* @__PURE__ */ new Set();
|
|
4773
|
-
const dedupe = (
|
|
4821
|
+
const dedupe = (list2) => list2.map((l) => {
|
|
4774
4822
|
let next = l;
|
|
4775
4823
|
if (seen.has(l.id)) next = { ...l, id: newLayerId() };
|
|
4776
4824
|
seen.add(next.id);
|
|
@@ -5172,8 +5220,8 @@ var PsdReader = class _PsdReader {
|
|
|
5172
5220
|
return v;
|
|
5173
5221
|
}
|
|
5174
5222
|
/** A length field: 8 bytes in PSB where the format says so, else 4. */
|
|
5175
|
-
len(
|
|
5176
|
-
return
|
|
5223
|
+
len(long3) {
|
|
5224
|
+
return long3 ? this.u64() : this.u32();
|
|
5177
5225
|
}
|
|
5178
5226
|
bytes(n) {
|
|
5179
5227
|
this.need(n, `${n} bytes`);
|
|
@@ -5280,11 +5328,11 @@ var PsdWriter = class {
|
|
|
5280
5328
|
this.length += n;
|
|
5281
5329
|
}
|
|
5282
5330
|
/** Reserves a length field; `endLen` fills it with the bytes written since. */
|
|
5283
|
-
beginLen(
|
|
5331
|
+
beginLen(long3) {
|
|
5284
5332
|
const at = this.length;
|
|
5285
|
-
if (
|
|
5333
|
+
if (long3) this.u64(0);
|
|
5286
5334
|
else this.u32(0);
|
|
5287
|
-
return { at, long:
|
|
5335
|
+
return { at, long: long3 };
|
|
5288
5336
|
}
|
|
5289
5337
|
endLen(mark) {
|
|
5290
5338
|
const n = this.length - mark.at - (mark.long ? 8 : 4);
|
|
@@ -5444,12 +5492,12 @@ function readValue(r, depth) {
|
|
|
5444
5492
|
case "UntF":
|
|
5445
5493
|
return { type, unit: r.ascii(4), value: r.f64() };
|
|
5446
5494
|
case "UnFl": {
|
|
5447
|
-
const
|
|
5495
|
+
const unit2 = r.ascii(4);
|
|
5448
5496
|
const n = r.u32();
|
|
5449
5497
|
r.checkCount(n, 8, "Unit float list");
|
|
5450
5498
|
const values = [];
|
|
5451
5499
|
for (let i = 0; i < n; i++) values.push(r.f64());
|
|
5452
|
-
return { type, unit, values };
|
|
5500
|
+
return { type, unit: unit2, values };
|
|
5453
5501
|
}
|
|
5454
5502
|
case "TEXT":
|
|
5455
5503
|
return { type, value: r.unicode() };
|
|
@@ -5902,11 +5950,11 @@ function writeAdjustmentBlock(kind, params) {
|
|
|
5902
5950
|
case "blackWhite": {
|
|
5903
5951
|
const p = params;
|
|
5904
5952
|
const tint = p.tint ? hexToRgb(p.tint) : BW_TINT;
|
|
5905
|
-
const
|
|
5953
|
+
const dbl2 = (v) => ({ type: "doub", value: clamp2(v, 0, 1) * 255 });
|
|
5906
5954
|
const items = BW_KEYS.map(([field, key]) => [key, long(clamp2(p[field], -200, 300))]);
|
|
5907
5955
|
items.push(
|
|
5908
5956
|
["useTint", { type: "bool", value: p.tint !== null }],
|
|
5909
|
-
["tintColor", { type: "Objc", value: descriptor("RGBC", [["Rd ",
|
|
5957
|
+
["tintColor", { type: "Objc", value: descriptor("RGBC", [["Rd ", dbl2(tint[0])], ["Grn ", dbl2(tint[1])], ["Bl ", dbl2(tint[2])]]) }],
|
|
5910
5958
|
["bwPresetKind", { type: "long", value: 1 }],
|
|
5911
5959
|
["blackAndWhitePresetFileName", { type: "TEXT", value: "" }]
|
|
5912
5960
|
);
|
|
@@ -6390,11 +6438,11 @@ function writeRect(w, r) {
|
|
|
6390
6438
|
}
|
|
6391
6439
|
function writeBlocks(w, blocks, psb, alignment) {
|
|
6392
6440
|
for (const b of blocks) {
|
|
6393
|
-
const
|
|
6394
|
-
w.ascii(
|
|
6441
|
+
const long3 = psb && PSB_LONG_KEYS.has(b.key);
|
|
6442
|
+
w.ascii(long3 ? "8B64" : "8BIM");
|
|
6395
6443
|
w.ascii(b.key);
|
|
6396
6444
|
const pad = (alignment - b.data.length % alignment) % alignment;
|
|
6397
|
-
if (
|
|
6445
|
+
if (long3) w.u64(b.data.length + pad);
|
|
6398
6446
|
else w.u32(b.data.length + pad);
|
|
6399
6447
|
w.bytes(b.data);
|
|
6400
6448
|
w.zeros(pad);
|
|
@@ -6648,6 +6696,784 @@ function matteWhite(data) {
|
|
|
6648
6696
|
return out;
|
|
6649
6697
|
}
|
|
6650
6698
|
|
|
6699
|
+
// src/image-editor/psd/effects.ts
|
|
6700
|
+
var BLEND_CODES = [
|
|
6701
|
+
["normal", "Nrml"],
|
|
6702
|
+
["darken", "Drkn"],
|
|
6703
|
+
["multiply", "Mltp"],
|
|
6704
|
+
["color-burn", "CBrn"],
|
|
6705
|
+
["linear-burn", "linearBurn"],
|
|
6706
|
+
["darker-color", "darkerColor"],
|
|
6707
|
+
["lighten", "Lghn"],
|
|
6708
|
+
["screen", "Scrn"],
|
|
6709
|
+
["color-dodge", "CDdg"],
|
|
6710
|
+
["linear-dodge", "linearDodge"],
|
|
6711
|
+
["lighter-color", "lighterColor"],
|
|
6712
|
+
["overlay", "Ovrl"],
|
|
6713
|
+
["soft-light", "SftL"],
|
|
6714
|
+
["hard-light", "HrdL"],
|
|
6715
|
+
["vivid-light", "vividLight"],
|
|
6716
|
+
["linear-light", "linearLight"],
|
|
6717
|
+
["pin-light", "pinLight"],
|
|
6718
|
+
["hard-mix", "hardMix"],
|
|
6719
|
+
["difference", "Dfrn"],
|
|
6720
|
+
["exclusion", "Xclu"],
|
|
6721
|
+
["subtract", "blendSubtraction"],
|
|
6722
|
+
["divide", "blendDivide"],
|
|
6723
|
+
["hue", "H "],
|
|
6724
|
+
["saturation", "Strt"],
|
|
6725
|
+
["color", "Clr "],
|
|
6726
|
+
["luminosity", "Lmns"]
|
|
6727
|
+
];
|
|
6728
|
+
var BLEND_LONG = [
|
|
6729
|
+
["normal", "normal"],
|
|
6730
|
+
["darken", "darken"],
|
|
6731
|
+
["multiply", "multiply"],
|
|
6732
|
+
["color-burn", "colorBurn"],
|
|
6733
|
+
["lighten", "lighten"],
|
|
6734
|
+
["screen", "screen"],
|
|
6735
|
+
["color-dodge", "colorDodge"],
|
|
6736
|
+
["overlay", "overlay"],
|
|
6737
|
+
["soft-light", "softLight"],
|
|
6738
|
+
["hard-light", "hardLight"],
|
|
6739
|
+
["difference", "difference"],
|
|
6740
|
+
["exclusion", "exclusion"],
|
|
6741
|
+
["hue", "hue"],
|
|
6742
|
+
["saturation", "saturation"],
|
|
6743
|
+
["color", "color"],
|
|
6744
|
+
["luminosity", "luminosity"]
|
|
6745
|
+
];
|
|
6746
|
+
var BLEND_OF = new Map([...BLEND_CODES, ...BLEND_LONG].map(([m, id]) => [id, m]));
|
|
6747
|
+
var BLEND_ID = new Map(BLEND_CODES);
|
|
6748
|
+
var num2 = (d, key, fallback) => descNumber(d, key) ?? fallback;
|
|
6749
|
+
var pct = (d, key, fallback) => num2(d, key, fallback * 100) / 100;
|
|
6750
|
+
var bool = (d, key) => descBool(d, key) === true;
|
|
6751
|
+
var enumOf = (d, key) => {
|
|
6752
|
+
const v = descGet(d, key);
|
|
6753
|
+
return v?.type === "enum" ? v.value : void 0;
|
|
6754
|
+
};
|
|
6755
|
+
var text = (d, key) => {
|
|
6756
|
+
const v = d ? descGet(d, key) : void 0;
|
|
6757
|
+
return v?.type === "TEXT" ? v.value : "";
|
|
6758
|
+
};
|
|
6759
|
+
var list = (d, key) => {
|
|
6760
|
+
const v = descGet(d, key);
|
|
6761
|
+
if (v?.type !== "VlLs") return [];
|
|
6762
|
+
return v.items.flatMap((it) => it.type === "Objc" || it.type === "GlbO" ? [it.value] : []);
|
|
6763
|
+
};
|
|
6764
|
+
var clamp3 = (v, lo, hi) => Number.isFinite(v) ? Math.min(hi, Math.max(lo, v)) : lo;
|
|
6765
|
+
var unit = (u, value) => ({ type: "UntF", unit: u, value });
|
|
6766
|
+
var dbl = (value) => ({ type: "doub", value });
|
|
6767
|
+
var long2 = (value) => ({ type: "long", value: Math.round(value) });
|
|
6768
|
+
var boolV = (value) => ({ type: "bool", value });
|
|
6769
|
+
var enumV = (typeId, value) => ({ type: "enum", typeId, value });
|
|
6770
|
+
var textV = (value) => ({ type: "TEXT", value });
|
|
6771
|
+
var obj = (d) => ({ type: "Objc", value: d });
|
|
6772
|
+
function blendOf(d, key, fallback, notes) {
|
|
6773
|
+
const id = enumOf(d, key);
|
|
6774
|
+
if (id === void 0) return fallback;
|
|
6775
|
+
const m = BLEND_OF.get(id);
|
|
6776
|
+
if (m) return m;
|
|
6777
|
+
notes.add(id === "Dslv" || id === "dissolve" ? "Dissolve became Normal" : `an unknown blend mode "${id}" became Normal`);
|
|
6778
|
+
return "normal";
|
|
6779
|
+
}
|
|
6780
|
+
function colorOf(d, fallback = "#000000") {
|
|
6781
|
+
if (!d) return fallback;
|
|
6782
|
+
const g = (k) => descNumber(d, k);
|
|
6783
|
+
if (g("redFloat") !== void 0) return effectColorHex([g("redFloat"), g("greenFloat") ?? 0, g("blueFloat") ?? 0]);
|
|
6784
|
+
switch (d.classId) {
|
|
6785
|
+
case "HSBC": {
|
|
6786
|
+
const rgb = decodePsdColor(1, [(g("H ") ?? 0) / 360 * 65535, (g("Strt") ?? 0) / 100 * 65535, (g("Brgh") ?? 0) / 100 * 65535]);
|
|
6787
|
+
return rgb ? effectColorHex(rgb) : fallback;
|
|
6788
|
+
}
|
|
6789
|
+
case "CMYC": {
|
|
6790
|
+
const inv = (k) => (1 - (g(k) ?? 0) / 100) * 65535;
|
|
6791
|
+
const rgb = decodePsdColor(2, [inv("Cyn "), inv("Mgnt"), inv("Ylw "), inv("Blck")]);
|
|
6792
|
+
return rgb ? effectColorHex(rgb) : fallback;
|
|
6793
|
+
}
|
|
6794
|
+
case "LbCl": {
|
|
6795
|
+
const rgb = decodePsdColor(7, [(g("Lmnc") ?? 0) * 100, Math.round((g("A ") ?? 0) * 100) & 65535, Math.round((g("B ") ?? 0) * 100) & 65535]);
|
|
6796
|
+
return rgb ? effectColorHex(rgb) : fallback;
|
|
6797
|
+
}
|
|
6798
|
+
case "Grsc": {
|
|
6799
|
+
const v = 1 - (g("Gry ") ?? 0) / 100;
|
|
6800
|
+
return effectColorHex([v, v, v]);
|
|
6801
|
+
}
|
|
6802
|
+
}
|
|
6803
|
+
const r = g("Rd ");
|
|
6804
|
+
const gr = g("Grn ");
|
|
6805
|
+
const b = g("Bl ");
|
|
6806
|
+
if (r === void 0 || gr === void 0 || b === void 0) return fallback;
|
|
6807
|
+
return effectColorHex([r / 255, gr / 255, b / 255]);
|
|
6808
|
+
}
|
|
6809
|
+
function colorDesc(hex) {
|
|
6810
|
+
const [r, g, b] = parseEffectColor(hex) ?? [0, 0, 0];
|
|
6811
|
+
return descriptor("RGBC", [
|
|
6812
|
+
["Rd ", dbl(r * 255)],
|
|
6813
|
+
["Grn ", dbl(g * 255)],
|
|
6814
|
+
["Bl ", dbl(b * 255)]
|
|
6815
|
+
]);
|
|
6816
|
+
}
|
|
6817
|
+
function contourOf(d, key) {
|
|
6818
|
+
const c = descObject(d, key);
|
|
6819
|
+
if (!c) return void 0;
|
|
6820
|
+
const name = text(c, "Nm ");
|
|
6821
|
+
const points = list(c, "Crv ").map((p) => {
|
|
6822
|
+
const out = { input: clamp3(num2(p, "Hrzn", 0) / 255, 0, 1), output: clamp3(num2(p, "Vrtc", 0) / 255, 0, 1) };
|
|
6823
|
+
if (descBool(p, "Cnty") === false) out.corner = true;
|
|
6824
|
+
return out;
|
|
6825
|
+
});
|
|
6826
|
+
const linear = points.length === 2 && points[0].input === 0 && points[0].output === 0 && points[1].input === 1 && points[1].output === 1 && !points.some((p) => p.corner);
|
|
6827
|
+
if (points.length < 2 || linear && (!name || name === "Linear")) return void 0;
|
|
6828
|
+
return { name, points };
|
|
6829
|
+
}
|
|
6830
|
+
function contourDesc(c) {
|
|
6831
|
+
const name = c?.name ?? "Linear";
|
|
6832
|
+
const points = c?.points ?? [
|
|
6833
|
+
{ input: 0, output: 0 },
|
|
6834
|
+
{ input: 1, output: 1 }
|
|
6835
|
+
];
|
|
6836
|
+
const pts = {
|
|
6837
|
+
type: "VlLs",
|
|
6838
|
+
items: points.map((p) => {
|
|
6839
|
+
const items = [
|
|
6840
|
+
["Hrzn", dbl(p.input * 255)],
|
|
6841
|
+
["Vrtc", dbl(p.output * 255)]
|
|
6842
|
+
];
|
|
6843
|
+
if (p.corner) items.push(["Cnty", boolV(false)]);
|
|
6844
|
+
return obj(descriptor("CrPt", items));
|
|
6845
|
+
})
|
|
6846
|
+
};
|
|
6847
|
+
return obj(
|
|
6848
|
+
descriptor("ShpC", [
|
|
6849
|
+
["Nm ", textV(name)],
|
|
6850
|
+
["Crv ", pts]
|
|
6851
|
+
])
|
|
6852
|
+
);
|
|
6853
|
+
}
|
|
6854
|
+
var GRADIENT_STYLES = [
|
|
6855
|
+
["linear", "Lnr "],
|
|
6856
|
+
["radial", "Rdl "],
|
|
6857
|
+
["angle", "Angl"],
|
|
6858
|
+
["reflected", "Rflc"],
|
|
6859
|
+
["diamond", "Dmnd"]
|
|
6860
|
+
];
|
|
6861
|
+
function gradientOf(d, notes) {
|
|
6862
|
+
const g = {
|
|
6863
|
+
stops: [
|
|
6864
|
+
{ position: 0, color: "#000000" },
|
|
6865
|
+
{ position: 1, color: "#ffffff" }
|
|
6866
|
+
],
|
|
6867
|
+
style: GRADIENT_STYLES.find(([, id]) => id === enumOf(d, "Type"))?.[0] ?? "linear",
|
|
6868
|
+
angle: clamp3(num2(d, "Angl", 90), -360, 360),
|
|
6869
|
+
scale: clamp3(pct(d, "Scl ", 1), 0.1, 1.5),
|
|
6870
|
+
reverse: bool(d, "Rvrs"),
|
|
6871
|
+
align: descBool(d, "Algn") !== false,
|
|
6872
|
+
offset: { x: 0, y: 0 }
|
|
6873
|
+
};
|
|
6874
|
+
const off = descObject(d, "Ofst");
|
|
6875
|
+
if (off) g.offset = { x: clamp3(pct(off, "Hrzn", 0), -1, 1), y: clamp3(pct(off, "Vrtc", 0), -1, 1) };
|
|
6876
|
+
const grad = descObject(d, "Grad");
|
|
6877
|
+
if (!grad) return g;
|
|
6878
|
+
if (enumOf(grad, "GrdF") === "ClNs") {
|
|
6879
|
+
notes.add("noise gradients became black to white");
|
|
6880
|
+
return g;
|
|
6881
|
+
}
|
|
6882
|
+
const raw = list(grad, "Clrs").map((s) => {
|
|
6883
|
+
const type = enumOf(s, "Type");
|
|
6884
|
+
if (type === "FrgC" || type === "BckC") notes.add("foreground / background gradient stops became black / white");
|
|
6885
|
+
const color = type === "BckC" ? "#ffffff" : colorOf(descObject(s, "Clr "));
|
|
6886
|
+
return { position: clamp3(num2(s, "Lctn", 0) / 4096, 0, 1), color, mid: clamp3(num2(s, "Mdpn", 50) / 100, 0.05, 0.95) };
|
|
6887
|
+
}).sort((a, b) => a.position - b.position);
|
|
6888
|
+
if (raw.length) {
|
|
6889
|
+
const stops = [];
|
|
6890
|
+
raw.forEach((s, i) => {
|
|
6891
|
+
if (i > 0 && Math.abs(s.mid - 0.5) > 1e-3) {
|
|
6892
|
+
const a = raw[i - 1];
|
|
6893
|
+
const ca = parseEffectColor(a.color);
|
|
6894
|
+
const cb = parseEffectColor(s.color);
|
|
6895
|
+
stops.push({ position: a.position + (s.position - a.position) * s.mid, color: effectColorHex(ca.map((v, k) => (v + cb[k]) / 2)) });
|
|
6896
|
+
notes.add("gradient midpoints are drawn as straight segments");
|
|
6897
|
+
}
|
|
6898
|
+
stops.push({ position: s.position, color: s.color });
|
|
6899
|
+
});
|
|
6900
|
+
g.stops = stops;
|
|
6901
|
+
}
|
|
6902
|
+
const opacity = list(grad, "Trns").map((s) => ({ position: clamp3(num2(s, "Lctn", 0) / 4096, 0, 1), opacity: clamp3(num2(s, "Opct", 100) / 100, 0, 1) })).sort((a, b) => a.position - b.position);
|
|
6903
|
+
if (opacity.some((o) => o.opacity < 1)) g.opacityStops = opacity;
|
|
6904
|
+
if (num2(grad, "Intr", 0) > 0) notes.add("gradient smoothness is drawn as straight segments");
|
|
6905
|
+
return g;
|
|
6906
|
+
}
|
|
6907
|
+
function gradientItems(g) {
|
|
6908
|
+
const clrs = {
|
|
6909
|
+
type: "VlLs",
|
|
6910
|
+
items: g.stops.map(
|
|
6911
|
+
(s) => obj(
|
|
6912
|
+
descriptor("Clrt", [
|
|
6913
|
+
["Clr ", obj(colorDesc(s.color))],
|
|
6914
|
+
["Type", enumV("Clry", "UsrS")],
|
|
6915
|
+
["Lctn", long2(s.position * 4096)],
|
|
6916
|
+
["Mdpn", long2(50)]
|
|
6917
|
+
])
|
|
6918
|
+
)
|
|
6919
|
+
)
|
|
6920
|
+
};
|
|
6921
|
+
const op = g.opacityStops?.length ? g.opacityStops : g.stops.map((s) => ({ position: s.position, opacity: 1 }));
|
|
6922
|
+
const trns = {
|
|
6923
|
+
type: "VlLs",
|
|
6924
|
+
items: op.map(
|
|
6925
|
+
(s) => obj(
|
|
6926
|
+
descriptor("TrnS", [
|
|
6927
|
+
["Opct", unit("#Prc", s.opacity * 100)],
|
|
6928
|
+
["Lctn", long2(s.position * 4096)],
|
|
6929
|
+
["Mdpn", long2(50)]
|
|
6930
|
+
])
|
|
6931
|
+
)
|
|
6932
|
+
)
|
|
6933
|
+
};
|
|
6934
|
+
const grad = descriptor("Grdn", [
|
|
6935
|
+
["Nm ", textV("Custom")],
|
|
6936
|
+
["GrdF", enumV("GrdF", "CstS")],
|
|
6937
|
+
["Intr", dbl(0)],
|
|
6938
|
+
["Clrs", clrs],
|
|
6939
|
+
["Trns", trns]
|
|
6940
|
+
]);
|
|
6941
|
+
return [
|
|
6942
|
+
["Grad", obj(grad)],
|
|
6943
|
+
["Angl", unit("#Ang", g.angle)],
|
|
6944
|
+
["Type", enumV("GrdT", GRADIENT_STYLES.find(([s]) => s === g.style)[1])],
|
|
6945
|
+
["Rvrs", boolV(g.reverse)],
|
|
6946
|
+
["Algn", boolV(g.align)],
|
|
6947
|
+
["Scl ", unit("#Prc", g.scale * 100)],
|
|
6948
|
+
[
|
|
6949
|
+
"Ofst",
|
|
6950
|
+
obj(
|
|
6951
|
+
descriptor("Pnt ", [
|
|
6952
|
+
["Hrzn", unit("#Prc", g.offset.x * 100)],
|
|
6953
|
+
["Vrtc", unit("#Prc", g.offset.y * 100)]
|
|
6954
|
+
])
|
|
6955
|
+
)
|
|
6956
|
+
]
|
|
6957
|
+
];
|
|
6958
|
+
}
|
|
6959
|
+
function patternRefOf(d) {
|
|
6960
|
+
const p = descObject(d, "Ptrn");
|
|
6961
|
+
return { patternId: text(p, "Idnt") || text(p, "Nm "), name: text(p, "Nm ") };
|
|
6962
|
+
}
|
|
6963
|
+
function patternRefDesc(patternId, name) {
|
|
6964
|
+
return obj(
|
|
6965
|
+
descriptor("Ptrn", [
|
|
6966
|
+
["Nm ", textV(name ?? patternId)],
|
|
6967
|
+
["Idnt", textV(patternId)]
|
|
6968
|
+
])
|
|
6969
|
+
);
|
|
6970
|
+
}
|
|
6971
|
+
function placementOf(d) {
|
|
6972
|
+
const phase = descObject(d, "phase");
|
|
6973
|
+
return {
|
|
6974
|
+
angle: clamp3(num2(d, "Angl", 0), -360, 360),
|
|
6975
|
+
link: descBool(d, "Algn") !== false,
|
|
6976
|
+
phase: phase ? { x: num2(phase, "Hrzn", 0), y: num2(phase, "Vrtc", 0) } : { x: 0, y: 0 }
|
|
6977
|
+
};
|
|
6978
|
+
}
|
|
6979
|
+
function placementItems(angle, link, phase) {
|
|
6980
|
+
const out = [];
|
|
6981
|
+
if (angle) out.push(["Angl", unit("#Ang", angle)]);
|
|
6982
|
+
out.push(["Algn", boolV(link)]);
|
|
6983
|
+
out.push([
|
|
6984
|
+
"phase",
|
|
6985
|
+
obj(
|
|
6986
|
+
descriptor("Pnt ", [
|
|
6987
|
+
["Hrzn", dbl(phase.x)],
|
|
6988
|
+
["Vrtc", dbl(phase.y)]
|
|
6989
|
+
])
|
|
6990
|
+
)
|
|
6991
|
+
]);
|
|
6992
|
+
return out;
|
|
6993
|
+
}
|
|
6994
|
+
var KINDS = [
|
|
6995
|
+
["DrSh", "dropShadowMulti", "drop-shadow"],
|
|
6996
|
+
["IrSh", "innerShadowMulti", "inner-shadow"],
|
|
6997
|
+
["OrGl", "", "outer-glow"],
|
|
6998
|
+
["IrGl", "", "inner-glow"],
|
|
6999
|
+
["ebbl", "", "bevel-emboss"],
|
|
7000
|
+
["ChFX", "", "satin"],
|
|
7001
|
+
["SoFi", "solidFillMulti", "color-overlay"],
|
|
7002
|
+
["GrFl", "gradientFillMulti", "gradient-overlay"],
|
|
7003
|
+
["patternFill", "", "pattern-overlay"],
|
|
7004
|
+
["FrFX", "frameFXMulti", "stroke"]
|
|
7005
|
+
];
|
|
7006
|
+
function common(d, kind, notes) {
|
|
7007
|
+
const def = defaultLayerEffect(kind, "x");
|
|
7008
|
+
return { id: newEffectId(), enabled: descBool(d, "enab") !== false, blend: blendOf(d, "Md ", def.blend, notes), opacity: clamp3(pct(d, "Opct", def.opacity), 0, 1) };
|
|
7009
|
+
}
|
|
7010
|
+
function readOne(kind, d, notes) {
|
|
7011
|
+
const size = (key, fallback) => clamp3(num2(d, key, fallback), 0, 250);
|
|
7012
|
+
switch (kind) {
|
|
7013
|
+
case "drop-shadow":
|
|
7014
|
+
case "inner-shadow": {
|
|
7015
|
+
const base = {
|
|
7016
|
+
...common(d, kind, notes),
|
|
7017
|
+
color: colorOf(descObject(d, "Clr ")),
|
|
7018
|
+
angle: clamp3(num2(d, "lagl", 120), -360, 360),
|
|
7019
|
+
useGlobalLight: descBool(d, "uglg") !== false,
|
|
7020
|
+
distance: clamp3(num2(d, "Dstn", 5), 0, 3e4),
|
|
7021
|
+
size: size("blur", 5),
|
|
7022
|
+
contour: contourOf(d, "TrnS"),
|
|
7023
|
+
antiAlias: bool(d, "AntA"),
|
|
7024
|
+
noise: clamp3(pct(d, "Nose", 0), 0, 1)
|
|
7025
|
+
};
|
|
7026
|
+
const spread = clamp3(pct(d, "Ckmt", 0), 0, 1);
|
|
7027
|
+
return kind === "drop-shadow" ? { ...base, kind, spread, knocksOut: descBool(d, "layerConceals") !== false } : { ...base, kind, choke: spread };
|
|
7028
|
+
}
|
|
7029
|
+
case "outer-glow":
|
|
7030
|
+
case "inner-glow": {
|
|
7031
|
+
const fill = descGet(d, "Grad") ? { type: "gradient", gradient: gradientOf(d, notes) } : { type: "color", color: colorOf(descObject(d, "Clr "), "#ffffbe") };
|
|
7032
|
+
const jitter = clamp3(pct(d, "ShdN", 0), 0, 1);
|
|
7033
|
+
if (jitter > 0) notes.add("glow jitter is not drawn");
|
|
7034
|
+
const base = {
|
|
7035
|
+
...common(d, kind, notes),
|
|
7036
|
+
fill,
|
|
7037
|
+
technique: enumOf(d, "GlwT") === "PrBL" ? "precise" : "softer",
|
|
7038
|
+
size: size("blur", 5),
|
|
7039
|
+
contour: contourOf(d, "TrnS"),
|
|
7040
|
+
antiAlias: bool(d, "AntA"),
|
|
7041
|
+
range: clamp3(pct(d, "Inpr", 0.5), 0.01, 1),
|
|
7042
|
+
jitter,
|
|
7043
|
+
noise: clamp3(pct(d, "Nose", 0), 0, 1)
|
|
7044
|
+
};
|
|
7045
|
+
const spread = clamp3(pct(d, "Ckmt", 0), 0, 1);
|
|
7046
|
+
return kind === "outer-glow" ? { ...base, kind, spread } : { ...base, kind, choke: spread, source: enumOf(d, "glwS") === "SrcC" ? "center" : "edge" };
|
|
7047
|
+
}
|
|
7048
|
+
case "bevel-emboss": {
|
|
7049
|
+
const styles = { OtrB: "outer-bevel", InrB: "inner-bevel", Embs: "emboss", PlEb: "pillow-emboss", strokeEmboss: "stroke-emboss" };
|
|
7050
|
+
const out = {
|
|
7051
|
+
id: newEffectId(),
|
|
7052
|
+
enabled: descBool(d, "enab") !== false,
|
|
7053
|
+
kind,
|
|
7054
|
+
style: styles[enumOf(d, "bvlS") ?? ""] ?? "inner-bevel",
|
|
7055
|
+
technique: enumOf(d, "bvlT") === "PrBL" ? "chisel-hard" : enumOf(d, "bvlT") === "Slmt" ? "chisel-soft" : "smooth",
|
|
7056
|
+
depth: clamp3(pct(d, "srgR", 1), 0, 10),
|
|
7057
|
+
direction: enumOf(d, "bvlD") === "Out " ? "down" : "up",
|
|
7058
|
+
size: size("blur", 5),
|
|
7059
|
+
soften: clamp3(num2(d, "Sftn", 0), 0, 16),
|
|
7060
|
+
angle: clamp3(num2(d, "lagl", 120), -360, 360),
|
|
7061
|
+
altitude: clamp3(num2(d, "Lald", 30), 0, 90),
|
|
7062
|
+
useGlobalLight: descBool(d, "uglg") !== false,
|
|
7063
|
+
glossContour: contourOf(d, "TrnS"),
|
|
7064
|
+
highlightColor: colorOf(descObject(d, "hglC"), "#ffffff"),
|
|
7065
|
+
highlightBlend: blendOf(d, "hglM", "screen", notes),
|
|
7066
|
+
highlightOpacity: clamp3(pct(d, "hglO", 0.75), 0, 1),
|
|
7067
|
+
shadowColor: colorOf(descObject(d, "sdwC")),
|
|
7068
|
+
shadowBlend: blendOf(d, "sdwM", "multiply", notes),
|
|
7069
|
+
shadowOpacity: clamp3(pct(d, "sdwO", 0.75), 0, 1)
|
|
7070
|
+
};
|
|
7071
|
+
if (bool(d, "useShape")) out.contourElement = { contour: contourOf(d, "MpgS") ?? { name: "Linear", points: [{ input: 0, output: 0 }, { input: 1, output: 1 }] }, range: clamp3(pct(d, "Inpr", 0.5), 0, 1), antiAlias: bool(d, "AntA") };
|
|
7072
|
+
if (bool(d, "useTexture")) {
|
|
7073
|
+
const ref = patternRefOf(d);
|
|
7074
|
+
const place = placementOf(d);
|
|
7075
|
+
out.texture = { patternId: ref.patternId, name: ref.name, scale: clamp3(pct(d, "Scl ", 1), 0.01, 10), depth: clamp3(pct(d, "textureDepth", 1), -10, 10), invert: bool(d, "InvT"), link: place.link, phase: place.phase };
|
|
7076
|
+
}
|
|
7077
|
+
return out;
|
|
7078
|
+
}
|
|
7079
|
+
case "satin":
|
|
7080
|
+
return {
|
|
7081
|
+
...common(d, kind, notes),
|
|
7082
|
+
kind,
|
|
7083
|
+
color: colorOf(descObject(d, "Clr ")),
|
|
7084
|
+
angle: clamp3(num2(d, "lagl", 19), -360, 360),
|
|
7085
|
+
distance: size("Dstn", 11),
|
|
7086
|
+
size: size("blur", 14),
|
|
7087
|
+
contour: contourOf(d, "MpgS"),
|
|
7088
|
+
antiAlias: bool(d, "AntA"),
|
|
7089
|
+
invert: bool(d, "Invr")
|
|
7090
|
+
};
|
|
7091
|
+
case "color-overlay":
|
|
7092
|
+
return { ...common(d, kind, notes), kind, color: colorOf(descObject(d, "Clr ")) };
|
|
7093
|
+
case "gradient-overlay":
|
|
7094
|
+
return { ...common(d, kind, notes), kind, gradient: gradientOf(d, notes), dither: bool(d, "Dthr") };
|
|
7095
|
+
case "pattern-overlay": {
|
|
7096
|
+
const ref = patternRefOf(d);
|
|
7097
|
+
const place = placementOf(d);
|
|
7098
|
+
return { ...common(d, kind, notes), kind, patternId: ref.patternId, name: ref.name, scale: clamp3(pct(d, "Scl ", 1), 0.01, 10), angle: place.angle, link: place.link, phase: place.phase };
|
|
7099
|
+
}
|
|
7100
|
+
case "stroke": {
|
|
7101
|
+
const paint = enumOf(d, "PntT");
|
|
7102
|
+
let fill;
|
|
7103
|
+
if (paint === "GrFl") fill = { type: "gradient", gradient: gradientOf(d, notes) };
|
|
7104
|
+
else if (paint === "Ptrn") {
|
|
7105
|
+
const ref = patternRefOf(d);
|
|
7106
|
+
fill = { type: "pattern", patternId: ref.patternId, name: ref.name, scale: clamp3(pct(d, "Scl ", 1), 0.01, 10) };
|
|
7107
|
+
} else fill = { type: "color", color: colorOf(descObject(d, "Clr ")) };
|
|
7108
|
+
const pos = enumOf(d, "Styl");
|
|
7109
|
+
return { ...common(d, kind, notes), kind, size: size("Sz ", 3), position: pos === "InsF" ? "inside" : pos === "CtrF" ? "center" : "outside", fill };
|
|
7110
|
+
}
|
|
7111
|
+
}
|
|
7112
|
+
}
|
|
7113
|
+
function parseLfx2(data) {
|
|
7114
|
+
let d;
|
|
7115
|
+
try {
|
|
7116
|
+
d = parseVersionedDescriptor(data.subarray(4));
|
|
7117
|
+
} catch {
|
|
7118
|
+
return null;
|
|
7119
|
+
}
|
|
7120
|
+
const notes = /* @__PURE__ */ new Set();
|
|
7121
|
+
const items = [];
|
|
7122
|
+
for (const [single, multi, kind] of KINDS) {
|
|
7123
|
+
const many = multi ? list(d, multi) : [];
|
|
7124
|
+
const one = descObject(d, single);
|
|
7125
|
+
for (const x of many.length ? many : one ? [one] : []) {
|
|
7126
|
+
if (descBool(x, "present") === false) continue;
|
|
7127
|
+
items.push(readOne(kind, x, notes));
|
|
7128
|
+
}
|
|
7129
|
+
}
|
|
7130
|
+
const effects = { enabled: descBool(d, "masterFXSwitch") !== false, items };
|
|
7131
|
+
return { effects, notes: [...notes] };
|
|
7132
|
+
}
|
|
7133
|
+
function commonItems(e) {
|
|
7134
|
+
return [
|
|
7135
|
+
["enab", boolV(e.enabled)],
|
|
7136
|
+
["present", boolV(true)],
|
|
7137
|
+
["showInDialog", boolV(true)],
|
|
7138
|
+
["Md ", enumV("BlnM", BLEND_ID.get(e.blend) ?? "Nrml")],
|
|
7139
|
+
["Opct", unit("#Prc", e.opacity * 100)]
|
|
7140
|
+
];
|
|
7141
|
+
}
|
|
7142
|
+
function writeOne(e) {
|
|
7143
|
+
switch (e.kind) {
|
|
7144
|
+
case "drop-shadow":
|
|
7145
|
+
case "inner-shadow": {
|
|
7146
|
+
const inner = e.kind === "inner-shadow";
|
|
7147
|
+
const items = [
|
|
7148
|
+
...commonItems(e),
|
|
7149
|
+
["Clr ", obj(colorDesc(e.color))],
|
|
7150
|
+
["uglg", boolV(e.useGlobalLight)],
|
|
7151
|
+
["lagl", unit("#Ang", e.angle)],
|
|
7152
|
+
["Dstn", unit("#Pxl", e.distance)],
|
|
7153
|
+
["Ckmt", unit("#Pxl", (inner ? e.choke : e.spread) * 100)],
|
|
7154
|
+
["blur", unit("#Pxl", e.size)],
|
|
7155
|
+
["Nose", unit("#Prc", e.noise * 100)],
|
|
7156
|
+
["AntA", boolV(e.antiAlias)],
|
|
7157
|
+
["TrnS", contourDesc(e.contour)]
|
|
7158
|
+
];
|
|
7159
|
+
if (e.kind === "drop-shadow") items.push(["layerConceals", boolV(e.knocksOut)]);
|
|
7160
|
+
return descriptor(inner ? "IrSh" : "DrSh", items);
|
|
7161
|
+
}
|
|
7162
|
+
case "outer-glow":
|
|
7163
|
+
case "inner-glow": {
|
|
7164
|
+
const inner = e.kind === "inner-glow";
|
|
7165
|
+
const items = [...commonItems(e)];
|
|
7166
|
+
if (e.fill.type === "gradient") items.push(...gradientItems(e.fill.gradient));
|
|
7167
|
+
else items.push(["Clr ", obj(colorDesc(e.fill.color))]);
|
|
7168
|
+
items.push(
|
|
7169
|
+
["GlwT", enumV("BETE", e.technique === "precise" ? "PrBL" : "SfBL")],
|
|
7170
|
+
["Ckmt", unit("#Pxl", (inner ? e.choke : e.spread) * 100)],
|
|
7171
|
+
["blur", unit("#Pxl", e.size)],
|
|
7172
|
+
["Nose", unit("#Prc", e.noise * 100)],
|
|
7173
|
+
["ShdN", unit("#Prc", e.jitter * 100)],
|
|
7174
|
+
["AntA", boolV(e.antiAlias)],
|
|
7175
|
+
["TrnS", contourDesc(e.contour)],
|
|
7176
|
+
["Inpr", unit("#Prc", e.range * 100)]
|
|
7177
|
+
);
|
|
7178
|
+
if (e.kind === "inner-glow") items.push(["glwS", enumV("IGSr", e.source === "center" ? "SrcC" : "SrcE")]);
|
|
7179
|
+
return descriptor(inner ? "IrGl" : "OrGl", items);
|
|
7180
|
+
}
|
|
7181
|
+
case "bevel-emboss": {
|
|
7182
|
+
const style = { "outer-bevel": "OtrB", "inner-bevel": "InrB", emboss: "Embs", "pillow-emboss": "PlEb", "stroke-emboss": "strokeEmboss" }[e.style];
|
|
7183
|
+
const tech = { smooth: "SfBL", "chisel-hard": "PrBL", "chisel-soft": "Slmt" }[e.technique];
|
|
7184
|
+
const items = [
|
|
7185
|
+
["enab", boolV(e.enabled)],
|
|
7186
|
+
["present", boolV(true)],
|
|
7187
|
+
["showInDialog", boolV(true)],
|
|
7188
|
+
["hglM", enumV("BlnM", BLEND_ID.get(e.highlightBlend) ?? "Scrn")],
|
|
7189
|
+
["hglC", obj(colorDesc(e.highlightColor))],
|
|
7190
|
+
["hglO", unit("#Prc", e.highlightOpacity * 100)],
|
|
7191
|
+
["sdwM", enumV("BlnM", BLEND_ID.get(e.shadowBlend) ?? "Mltp")],
|
|
7192
|
+
["sdwC", obj(colorDesc(e.shadowColor))],
|
|
7193
|
+
["sdwO", unit("#Prc", e.shadowOpacity * 100)],
|
|
7194
|
+
["bvlT", enumV("bvlT", tech)],
|
|
7195
|
+
["bvlS", enumV("BESl", style)],
|
|
7196
|
+
["uglg", boolV(e.useGlobalLight)],
|
|
7197
|
+
["lagl", unit("#Ang", e.angle)],
|
|
7198
|
+
["Lald", unit("#Ang", e.altitude)],
|
|
7199
|
+
["srgR", unit("#Prc", e.depth * 100)],
|
|
7200
|
+
["blur", unit("#Pxl", e.size)],
|
|
7201
|
+
["bvlD", enumV("BESs", e.direction === "up" ? "In " : "Out ")],
|
|
7202
|
+
["TrnS", contourDesc(e.glossContour)],
|
|
7203
|
+
["Sftn", unit("#Pxl", e.soften)]
|
|
7204
|
+
];
|
|
7205
|
+
if (e.contourElement) {
|
|
7206
|
+
items.push(["useShape", boolV(true)], ["MpgS", contourDesc(e.contourElement.contour)], ["AntA", boolV(e.contourElement.antiAlias)], ["Inpr", unit("#Prc", e.contourElement.range * 100)]);
|
|
7207
|
+
} else items.push(["useShape", boolV(false)]);
|
|
7208
|
+
if (e.texture) {
|
|
7209
|
+
const t = e.texture;
|
|
7210
|
+
items.push(
|
|
7211
|
+
["useTexture", boolV(true)],
|
|
7212
|
+
["InvT", boolV(t.invert)],
|
|
7213
|
+
["Scl ", unit("#Prc", t.scale * 100)],
|
|
7214
|
+
["textureDepth", unit("#Prc", t.depth * 100)],
|
|
7215
|
+
["Ptrn", patternRefDesc(t.patternId, t.name)],
|
|
7216
|
+
...placementItems(0, t.link ?? true, t.phase ?? { x: 0, y: 0 })
|
|
7217
|
+
);
|
|
7218
|
+
} else items.push(["useTexture", boolV(false)]);
|
|
7219
|
+
return descriptor("ebbl", items);
|
|
7220
|
+
}
|
|
7221
|
+
case "satin":
|
|
7222
|
+
return descriptor("ChFX", [
|
|
7223
|
+
...commonItems(e),
|
|
7224
|
+
["Clr ", obj(colorDesc(e.color))],
|
|
7225
|
+
["AntA", boolV(e.antiAlias)],
|
|
7226
|
+
["Invr", boolV(e.invert)],
|
|
7227
|
+
["lagl", unit("#Ang", e.angle)],
|
|
7228
|
+
["Dstn", unit("#Pxl", e.distance)],
|
|
7229
|
+
["blur", unit("#Pxl", e.size)],
|
|
7230
|
+
["MpgS", contourDesc(e.contour)]
|
|
7231
|
+
]);
|
|
7232
|
+
case "color-overlay":
|
|
7233
|
+
return descriptor("SoFi", [...commonItems(e), ["Clr ", obj(colorDesc(e.color))]]);
|
|
7234
|
+
case "gradient-overlay":
|
|
7235
|
+
return descriptor("GrFl", [...commonItems(e), ...gradientItems(e.gradient), ["Dthr", boolV(e.dither)]]);
|
|
7236
|
+
case "pattern-overlay":
|
|
7237
|
+
return descriptor("patternFill", [
|
|
7238
|
+
...commonItems(e),
|
|
7239
|
+
["Ptrn", patternRefDesc(e.patternId, e.name)],
|
|
7240
|
+
["Scl ", unit("#Prc", e.scale * 100)],
|
|
7241
|
+
...placementItems(e.angle ?? 0, e.link ?? true, e.phase ?? { x: 0, y: 0 })
|
|
7242
|
+
]);
|
|
7243
|
+
case "stroke": {
|
|
7244
|
+
const items = [
|
|
7245
|
+
...commonItems(e),
|
|
7246
|
+
["Styl", enumV("FStl", e.position === "inside" ? "InsF" : e.position === "center" ? "CtrF" : "OutF")],
|
|
7247
|
+
["Sz ", unit("#Pxl", e.size)]
|
|
7248
|
+
];
|
|
7249
|
+
const f = e.fill;
|
|
7250
|
+
if (f.type === "color") items.push(["PntT", enumV("FrFl", "SClr")], ["Clr ", obj(colorDesc(f.color))]);
|
|
7251
|
+
else if (f.type === "gradient") items.push(["PntT", enumV("FrFl", "GrFl")], ...gradientItems(f.gradient));
|
|
7252
|
+
else items.push(["PntT", enumV("FrFl", "Ptrn")], ["Ptrn", patternRefDesc(f.patternId, f.name)], ["Scl ", unit("#Prc", f.scale * 100)]);
|
|
7253
|
+
return descriptor("FrFX", items);
|
|
7254
|
+
}
|
|
7255
|
+
}
|
|
7256
|
+
}
|
|
7257
|
+
function writeLfx2(effects) {
|
|
7258
|
+
const items = [
|
|
7259
|
+
["Scl ", unit("#Prc", 100)],
|
|
7260
|
+
["masterFXSwitch", boolV(effects.enabled)]
|
|
7261
|
+
];
|
|
7262
|
+
const scale = effectsScale(effects);
|
|
7263
|
+
let dropped = 0;
|
|
7264
|
+
for (const [single, multi, kind] of KINDS) {
|
|
7265
|
+
const of = effects.items.filter((e) => e.kind === kind).map((e) => writeOne(scaleLayerEffect(e, scale)));
|
|
7266
|
+
if (!of.length) continue;
|
|
7267
|
+
if (of.length === 1) items.push([single, obj(of[0])]);
|
|
7268
|
+
else if (multi) items.push([multi, { type: "VlLs", items: of.map(obj) }]);
|
|
7269
|
+
else {
|
|
7270
|
+
items.push([single, obj(of[0])]);
|
|
7271
|
+
dropped += of.length - 1;
|
|
7272
|
+
}
|
|
7273
|
+
}
|
|
7274
|
+
const body = versionedDescriptorBytes(descriptor("null", items));
|
|
7275
|
+
const w = new PsdWriter();
|
|
7276
|
+
w.u32(0);
|
|
7277
|
+
w.bytes(body);
|
|
7278
|
+
if (w.length % 2) w.u8(0);
|
|
7279
|
+
return { data: w.result(), dropped };
|
|
7280
|
+
}
|
|
7281
|
+
function parseLrfx(data) {
|
|
7282
|
+
try {
|
|
7283
|
+
const r = new PsdReader(data);
|
|
7284
|
+
r.u16();
|
|
7285
|
+
const count = r.u16();
|
|
7286
|
+
const items = [];
|
|
7287
|
+
const notes = /* @__PURE__ */ new Set();
|
|
7288
|
+
const fixed = (v) => (v | 0) / 65536;
|
|
7289
|
+
for (let i = 0; i < count; i++) {
|
|
7290
|
+
if (new TextDecoder().decode(r.bytes(4)) !== "8BIM") return null;
|
|
7291
|
+
const key = new TextDecoder().decode(r.bytes(4));
|
|
7292
|
+
const size = r.u32();
|
|
7293
|
+
const b = new PsdReader(r.bytes(size));
|
|
7294
|
+
const colorAt = (rd) => {
|
|
7295
|
+
rd.u16();
|
|
7296
|
+
const c = [rd.u16(), rd.u16(), rd.u16(), rd.u16()];
|
|
7297
|
+
return effectColorHex([c[0] >> 8, c[1] >> 8, c[2] >> 8].map((v) => v / 255));
|
|
7298
|
+
};
|
|
7299
|
+
const blendAt = (rd) => {
|
|
7300
|
+
rd.skip(4);
|
|
7301
|
+
const k = new TextDecoder().decode(rd.bytes(4));
|
|
7302
|
+
const m = PSD_BLEND_KEYS[k];
|
|
7303
|
+
return m && m !== "pass-through" ? m : "normal";
|
|
7304
|
+
};
|
|
7305
|
+
if (key === "dsdw" || key === "isdw") {
|
|
7306
|
+
b.u32();
|
|
7307
|
+
const blur = fixed(b.u32());
|
|
7308
|
+
b.u32();
|
|
7309
|
+
const angle = fixed(b.u32());
|
|
7310
|
+
const distance = fixed(b.u32());
|
|
7311
|
+
const color = colorAt(b);
|
|
7312
|
+
const blend = blendAt(b);
|
|
7313
|
+
const enabled = b.u8() !== 0;
|
|
7314
|
+
const useGlobalLight = b.u8() !== 0;
|
|
7315
|
+
const opacity = b.u8() / 255;
|
|
7316
|
+
const base = { id: newEffectId(), enabled, blend, opacity, color, angle, useGlobalLight, distance: clamp3(distance, 0, 3e4), size: clamp3(blur, 0, 250), antiAlias: false, noise: 0 };
|
|
7317
|
+
items.push(key === "dsdw" ? { ...base, kind: "drop-shadow", spread: 0, knocksOut: true } : { ...base, kind: "inner-shadow", choke: 0 });
|
|
7318
|
+
} else if (key === "oglw" || key === "iglw") {
|
|
7319
|
+
b.u32();
|
|
7320
|
+
const blur = fixed(b.u32());
|
|
7321
|
+
b.u32();
|
|
7322
|
+
const color = colorAt(b);
|
|
7323
|
+
const blend = blendAt(b);
|
|
7324
|
+
const enabled = b.u8() !== 0;
|
|
7325
|
+
const opacity = b.u8() / 255;
|
|
7326
|
+
const base = { id: newEffectId(), enabled, blend, opacity, fill: { type: "color", color }, technique: "softer", size: clamp3(blur, 0, 250), antiAlias: false, range: 0.5, jitter: 0, noise: 0 };
|
|
7327
|
+
items.push(key === "oglw" ? { ...base, kind: "outer-glow", spread: 0 } : { ...base, kind: "inner-glow", choke: 0, source: "edge" });
|
|
7328
|
+
} else if (key === "sofi") {
|
|
7329
|
+
b.u32();
|
|
7330
|
+
const blend = blendAt(b);
|
|
7331
|
+
const color = colorAt(b);
|
|
7332
|
+
const opacity = b.u8() / 255;
|
|
7333
|
+
const enabled = b.u8() !== 0;
|
|
7334
|
+
items.push({ id: newEffectId(), kind: "color-overlay", enabled, blend, opacity, color });
|
|
7335
|
+
} else if (key === "bevl") notes.add("legacy bevels are not read");
|
|
7336
|
+
}
|
|
7337
|
+
return { effects: { enabled: true, items }, notes: [...notes] };
|
|
7338
|
+
} catch {
|
|
7339
|
+
return null;
|
|
7340
|
+
}
|
|
7341
|
+
}
|
|
7342
|
+
|
|
7343
|
+
// src/image-editor/psd/patterns.ts
|
|
7344
|
+
var MAX_EDGE = 3e4;
|
|
7345
|
+
var PATTERN_BLOCK_KEYS = ["Patt", "Pat2", "Pat3"];
|
|
7346
|
+
function modeChannels(mode) {
|
|
7347
|
+
return mode === MODE_RGB || mode === MODE_LAB ? 3 : mode === MODE_CMYK ? 4 : 1;
|
|
7348
|
+
}
|
|
7349
|
+
var rowBytes2 = (width, depth) => depth === 1 ? Math.ceil(width / 8) : width * (depth / 8);
|
|
7350
|
+
function readPattern(r) {
|
|
7351
|
+
const version = r.u32();
|
|
7352
|
+
if (version !== 1) throw new PsdError(`Pattern version ${version}, expected 1.`);
|
|
7353
|
+
const mode = r.u32();
|
|
7354
|
+
r.u16();
|
|
7355
|
+
r.u16();
|
|
7356
|
+
const name = r.unicode();
|
|
7357
|
+
const idLength = r.u8();
|
|
7358
|
+
const id = new TextDecoder().decode(r.bytes(idLength));
|
|
7359
|
+
let palette;
|
|
7360
|
+
if (mode === MODE_INDEXED) {
|
|
7361
|
+
palette = r.bytes(768).slice();
|
|
7362
|
+
const next = r.peek(4);
|
|
7363
|
+
if (next.length === 4 && !(next[0] === 0 && next[1] === 0 && next[2] === 0 && next[3] === 3)) r.skip(4);
|
|
7364
|
+
}
|
|
7365
|
+
const vmaVersion = r.u32();
|
|
7366
|
+
if (vmaVersion !== 3) throw new PsdError(`Pattern virtual memory array list version ${vmaVersion}, expected 3.`);
|
|
7367
|
+
const length = r.u32();
|
|
7368
|
+
const body = new PsdReader(r.bytes(length));
|
|
7369
|
+
const [top, left, bottom, right] = [body.i32(), body.i32(), body.i32(), body.i32()];
|
|
7370
|
+
const width = right - left;
|
|
7371
|
+
const height = bottom - top;
|
|
7372
|
+
if (!(width > 0 && height > 0 && width <= MAX_EDGE && height <= MAX_EDGE)) throw new PsdError(`Pattern "${name}" has an invalid size ${width} \xD7 ${height}.`);
|
|
7373
|
+
const count = body.u32();
|
|
7374
|
+
if (count > 64) throw new PsdError(`Pattern "${name}" has ${count} channels.`);
|
|
7375
|
+
const planes = [];
|
|
7376
|
+
let depth = 8;
|
|
7377
|
+
for (let i = 0; i < count + 2 && body.remaining > 0; i++) {
|
|
7378
|
+
if (body.u32() === 0) continue;
|
|
7379
|
+
const arrayLength = body.u32();
|
|
7380
|
+
if (arrayLength === 0) continue;
|
|
7381
|
+
if (arrayLength < 23) throw new PsdError(`Pattern "${name}" has a short channel.`);
|
|
7382
|
+
const d32 = body.u32();
|
|
7383
|
+
const [ct, cl, cb, cr] = [body.i32(), body.i32(), body.i32(), body.i32()];
|
|
7384
|
+
const d16 = body.u16();
|
|
7385
|
+
const compression = body.u8();
|
|
7386
|
+
const data = body.bytes(arrayLength - 23);
|
|
7387
|
+
const d = [1, 8, 16, 32].includes(d16) ? d16 : d32;
|
|
7388
|
+
if (![1, 8, 16, 32].includes(d)) throw new PsdError(`Pattern "${name}" has depth ${d}.`);
|
|
7389
|
+
if (ct < top || cl < left || cb > bottom || cr > right || cb < ct || cr < cl) throw new PsdError(`Pattern "${name}" has a channel outside its rectangle.`);
|
|
7390
|
+
if (compression > 1) throw new PsdError(`Pattern "${name}" uses compression ${compression}.`);
|
|
7391
|
+
depth = d;
|
|
7392
|
+
const cw = cr - cl;
|
|
7393
|
+
const ch = cb - ct;
|
|
7394
|
+
const plane = decodePlanes(compression, data, { planes: 1, width: cw, height: ch, depth: d, psb: false });
|
|
7395
|
+
if (cw === width && ch === height) {
|
|
7396
|
+
planes.push(plane);
|
|
7397
|
+
continue;
|
|
7398
|
+
}
|
|
7399
|
+
if (d === 1) throw new PsdError(`Pattern "${name}" has a cropped bitmap channel.`);
|
|
7400
|
+
const bps = d / 8;
|
|
7401
|
+
const out = new Uint8Array(rowBytes2(width, d) * height);
|
|
7402
|
+
for (let y = 0; y < ch; y++) {
|
|
7403
|
+
out.set(plane.subarray(y * cw * bps, (y + 1) * cw * bps), ((ct - top + y) * width + (cl - left)) * bps);
|
|
7404
|
+
}
|
|
7405
|
+
planes.push(out);
|
|
7406
|
+
}
|
|
7407
|
+
const nc = modeChannels(mode);
|
|
7408
|
+
if (planes.length < nc) throw new PsdError(`Pattern "${name}" has fewer channels than its colour mode.`);
|
|
7409
|
+
return { mode, width, height, name, id, palette, depth, channels: planes.slice(0, nc), alpha: planes[nc] };
|
|
7410
|
+
}
|
|
7411
|
+
function parsePatternBlock(data) {
|
|
7412
|
+
const r = new PsdReader(data);
|
|
7413
|
+
const out = [];
|
|
7414
|
+
while (r.remaining >= 4) {
|
|
7415
|
+
const length = r.u32();
|
|
7416
|
+
if (length === 0) break;
|
|
7417
|
+
out.push(readPattern(new PsdReader(r.bytes(length))));
|
|
7418
|
+
r.skip(Math.min((4 - length % 4) % 4, r.remaining));
|
|
7419
|
+
}
|
|
7420
|
+
return out;
|
|
7421
|
+
}
|
|
7422
|
+
function psdPatternToEffectPattern(p) {
|
|
7423
|
+
const data = toRgba8({ width: p.width, height: p.height, depth: p.depth, mode: p.mode, color: p.channels, alpha: p.alpha, palette: p.palette });
|
|
7424
|
+
return { id: p.id, name: p.name, width: p.width, height: p.height, data };
|
|
7425
|
+
}
|
|
7426
|
+
function writePatternBlock(patterns) {
|
|
7427
|
+
const out = new PsdWriter();
|
|
7428
|
+
for (const p of patterns) {
|
|
7429
|
+
if (!(p.width > 0 && p.height > 0) || p.width > 32767 || p.height > 32767) throw new PsdError(`Pattern "${p.name}" is ${p.width} \xD7 ${p.height}; patterns are 1 to 32767 px.`);
|
|
7430
|
+
const one = new PsdWriter();
|
|
7431
|
+
one.u32(1);
|
|
7432
|
+
one.u32(MODE_RGB);
|
|
7433
|
+
one.u16(p.height);
|
|
7434
|
+
one.u16(p.width);
|
|
7435
|
+
one.unicode(p.name, true);
|
|
7436
|
+
const id = new TextEncoder().encode(p.id).subarray(0, 255);
|
|
7437
|
+
one.u8(id.length);
|
|
7438
|
+
one.bytes(id);
|
|
7439
|
+
const n = p.width * p.height;
|
|
7440
|
+
const plane = (c) => {
|
|
7441
|
+
const v = new Uint8Array(n);
|
|
7442
|
+
for (let i = 0; i < n; i++) v[i] = p.data[i * 4 + c];
|
|
7443
|
+
return v;
|
|
7444
|
+
};
|
|
7445
|
+
const alpha = plane(3);
|
|
7446
|
+
const opaque = alpha.every((v) => v === 255);
|
|
7447
|
+
const body = new PsdWriter();
|
|
7448
|
+
for (const v of [0, 0, p.height, p.width]) body.i32(v);
|
|
7449
|
+
body.u32(24);
|
|
7450
|
+
const array = (v) => {
|
|
7451
|
+
const data = encodeRle(v, { planes: 1, width: p.width, height: p.height, depth: 8, psb: false });
|
|
7452
|
+
body.u32(1);
|
|
7453
|
+
body.u32(23 + data.length);
|
|
7454
|
+
body.u32(8);
|
|
7455
|
+
for (const x of [0, 0, p.height, p.width]) body.i32(x);
|
|
7456
|
+
body.u16(8);
|
|
7457
|
+
body.u8(1);
|
|
7458
|
+
body.bytes(data);
|
|
7459
|
+
};
|
|
7460
|
+
for (let c = 0; c < 3; c++) array(plane(c));
|
|
7461
|
+
for (let c = 3; c < 24; c++) body.u32(0);
|
|
7462
|
+
if (opaque) body.u32(0);
|
|
7463
|
+
else array(alpha);
|
|
7464
|
+
body.u32(0);
|
|
7465
|
+
const b = body.result();
|
|
7466
|
+
one.u32(3);
|
|
7467
|
+
one.u32(b.length);
|
|
7468
|
+
one.bytes(b);
|
|
7469
|
+
const bytes = one.result();
|
|
7470
|
+
out.u32(bytes.length);
|
|
7471
|
+
out.bytes(bytes);
|
|
7472
|
+
for (let k = 0; k < (4 - bytes.length % 4) % 4; k++) out.u8(0);
|
|
7473
|
+
}
|
|
7474
|
+
return out.result();
|
|
7475
|
+
}
|
|
7476
|
+
|
|
6651
7477
|
// src/image-editor/psd/import.ts
|
|
6652
7478
|
var PSD_BLEND_KEYS = {
|
|
6653
7479
|
pass: "pass-through",
|
|
@@ -6685,15 +7511,14 @@ var FILL_KEYS = ["SoCo", "GdFl", "PtFl"];
|
|
|
6685
7511
|
var TYPE_KEYS = ["TySh", "tySh"];
|
|
6686
7512
|
var SMART_KEYS = ["SoLd", "SoLE", "PlLd"];
|
|
6687
7513
|
var VECTOR_KEYS = ["vmsk", "vsms"];
|
|
6688
|
-
var EFFECT_KEYS = ["lfx2", "lfxs", "lmfx"];
|
|
6689
7514
|
var percent = (v) => Math.round(v / 255 * 100) / 100;
|
|
6690
7515
|
var Warnings = class {
|
|
6691
7516
|
groups = /* @__PURE__ */ new Map();
|
|
6692
7517
|
extra = [];
|
|
6693
7518
|
add(message, layer) {
|
|
6694
|
-
const
|
|
6695
|
-
if (!
|
|
6696
|
-
this.groups.set(message,
|
|
7519
|
+
const list2 = this.groups.get(message) ?? [];
|
|
7520
|
+
if (!list2.includes(layer)) list2.push(layer);
|
|
7521
|
+
this.groups.set(message, list2);
|
|
6697
7522
|
}
|
|
6698
7523
|
list() {
|
|
6699
7524
|
const out = [...this.extra];
|
|
@@ -6800,10 +7625,6 @@ function baseProps(ctx, rec, name, notes) {
|
|
|
6800
7625
|
if (rec.clipping) props.clipped = true;
|
|
6801
7626
|
const locks = locksOf(rec);
|
|
6802
7627
|
if (locks) props.locks = locks;
|
|
6803
|
-
if (rec.blocks.some((b) => EFFECT_KEYS.includes(b.key))) {
|
|
6804
|
-
ctx.warn.add("Layer effects (lfx2) are not rendered", name);
|
|
6805
|
-
notes.push("layer effects are not rendered");
|
|
6806
|
-
}
|
|
6807
7628
|
if (rec.blocks.some((b) => b.key === "artb" || b.key === "artd" || b.key === "abdd")) {
|
|
6808
7629
|
ctx.warn.add("Artboards are not supported; they came in as plain groups, without their background colour and clipping", name);
|
|
6809
7630
|
notes.push("artboard imported as a group");
|
|
@@ -6814,6 +7635,47 @@ function baseProps(ctx, rec, name, notes) {
|
|
|
6814
7635
|
}
|
|
6815
7636
|
return props;
|
|
6816
7637
|
}
|
|
7638
|
+
function patternIds(e) {
|
|
7639
|
+
if (e.kind === "pattern-overlay") return [e.patternId];
|
|
7640
|
+
if (e.kind === "stroke" && e.fill.type === "pattern") return [e.fill.patternId];
|
|
7641
|
+
if (e.kind === "bevel-emboss" && e.texture) return [e.texture.patternId];
|
|
7642
|
+
return [];
|
|
7643
|
+
}
|
|
7644
|
+
function recordEffects(ctx, rec, name, notes, group) {
|
|
7645
|
+
const key = ["lmfx", "lfx2", "lfxs"].find((k) => layerBlock(rec, k));
|
|
7646
|
+
if (key) {
|
|
7647
|
+
const data = layerBlock(rec, key);
|
|
7648
|
+
const parsed = parseLfx2(data);
|
|
7649
|
+
if (!parsed) {
|
|
7650
|
+
ctx.warn.add("Layer effects could not be read; they are kept for export but not drawn", name);
|
|
7651
|
+
notes.push("layer effects could not be read");
|
|
7652
|
+
return { enabled: true, items: [], psd: { key, data: data.slice() } };
|
|
7653
|
+
}
|
|
7654
|
+
for (const n of parsed.notes) {
|
|
7655
|
+
ctx.warn.add(`Layer effects were approximated: ${n}`, name);
|
|
7656
|
+
notes.push(`effects: ${n}`);
|
|
7657
|
+
}
|
|
7658
|
+
for (const e of parsed.effects.items) {
|
|
7659
|
+
for (const id of patternIds(e)) {
|
|
7660
|
+
if (ctx.patterns.has(id) || builtinPattern(id)) continue;
|
|
7661
|
+
ctx.warn.add("A pattern effect uses a pattern the file does not contain, and paints nothing", name);
|
|
7662
|
+
notes.push(`pattern "${id}" is not in the file`);
|
|
7663
|
+
}
|
|
7664
|
+
}
|
|
7665
|
+
return { ...parsed.effects, psd: { key, data: data.slice() } };
|
|
7666
|
+
}
|
|
7667
|
+
const legacy = layerBlock(rec, "lrFX");
|
|
7668
|
+
if (legacy && !group) {
|
|
7669
|
+
const parsed = parseLrfx(legacy);
|
|
7670
|
+
if (!parsed) return void 0;
|
|
7671
|
+
for (const n of parsed.notes) {
|
|
7672
|
+
ctx.warn.add(`Layer effects were approximated: ${n}`, name);
|
|
7673
|
+
notes.push(`effects: ${n}`);
|
|
7674
|
+
}
|
|
7675
|
+
return parsed.effects.items.length ? parsed.effects : void 0;
|
|
7676
|
+
}
|
|
7677
|
+
return void 0;
|
|
7678
|
+
}
|
|
6817
7679
|
function importNode(ctx, node, depth) {
|
|
6818
7680
|
const rec = ctx.file.layers[node.index];
|
|
6819
7681
|
const name = layerName(rec);
|
|
@@ -6832,6 +7694,8 @@ function importNode(ctx, node, depth) {
|
|
|
6832
7694
|
if (layer2) children.push(layer2);
|
|
6833
7695
|
}
|
|
6834
7696
|
const group = { type: "group", id: ctx.newId(name), ...props2, blend: groupBlend, children };
|
|
7697
|
+
const effects2 = recordEffects(ctx, rec, name, notes, true);
|
|
7698
|
+
if (effects2) group.effects = effects2;
|
|
6835
7699
|
if (divider?.kind === SECTION_CLOSED) group.collapsed = true;
|
|
6836
7700
|
const mask2 = recordMask(ctx, rec, name);
|
|
6837
7701
|
if (mask2) group.mask = mask2;
|
|
@@ -6872,6 +7736,8 @@ function importNode(ctx, node, depth) {
|
|
|
6872
7736
|
const layer = { type: "pixel", id: ctx.newId(name), ...props, surface: recordPixels(ctx, rec, name), offset: { x: 0, y: 0 } };
|
|
6873
7737
|
const fill = u8Block(rec, "iOpa");
|
|
6874
7738
|
if (fill !== void 0 && fill < 255) layer.fillOpacity = percent(fill);
|
|
7739
|
+
const effects = recordEffects(ctx, rec, name, notes, false);
|
|
7740
|
+
if (effects) layer.effects = effects;
|
|
6875
7741
|
const mask = recordMask(ctx, rec, name);
|
|
6876
7742
|
if (mask) layer.mask = mask;
|
|
6877
7743
|
entry.type = "pixel";
|
|
@@ -6925,8 +7791,17 @@ function psdToLayers(file, options = {}) {
|
|
|
6925
7791
|
skipPixels: options.skipPixels ?? false,
|
|
6926
7792
|
summary: [],
|
|
6927
7793
|
colorCount: colorChannelCount(header.colorMode, header.channels),
|
|
6928
|
-
palette: header.colorMode === MODE_INDEXED ? file.colorModeData : void 0
|
|
7794
|
+
palette: header.colorMode === MODE_INDEXED ? file.colorModeData : void 0,
|
|
7795
|
+
patterns: /* @__PURE__ */ new Map()
|
|
6929
7796
|
};
|
|
7797
|
+
for (const block of file.globalBlocks) {
|
|
7798
|
+
if (!PATTERN_BLOCK_KEYS.includes(block.key)) continue;
|
|
7799
|
+
try {
|
|
7800
|
+
for (const p of parsePatternBlock(block.data)) ctx.patterns.set(p.id, psdPatternToEffectPattern(p));
|
|
7801
|
+
} catch (e) {
|
|
7802
|
+
warn.extra.push(`The file's patterns could not be read (${e instanceof Error ? e.message : String(e)}); pattern effects paint nothing.`);
|
|
7803
|
+
}
|
|
7804
|
+
}
|
|
6930
7805
|
const mode = COLOR_MODES[header.colorMode] ?? `mode ${header.colorMode}`;
|
|
6931
7806
|
if (mode !== "rgb" || header.depth !== 8) {
|
|
6932
7807
|
warn.extra.push(`The file is ${header.depth}-bit ${mode}; it was converted to 8-bit RGB.`);
|
|
@@ -6952,6 +7827,9 @@ function psdToLayers(file, options = {}) {
|
|
|
6952
7827
|
const bg = mergedLayer(ctx);
|
|
6953
7828
|
if (bg) layers = [bg];
|
|
6954
7829
|
}
|
|
7830
|
+
const i32 = (b) => b && b.length >= 4 ? new DataView(b.buffer, b.byteOffset, 4).getInt32(0) : void 0;
|
|
7831
|
+
const angle = i32(resource(file, 1037));
|
|
7832
|
+
const altitude = i32(resource(file, 1049));
|
|
6955
7833
|
return {
|
|
6956
7834
|
width: header.width,
|
|
6957
7835
|
height: header.height,
|
|
@@ -6959,6 +7837,8 @@ function psdToLayers(file, options = {}) {
|
|
|
6959
7837
|
warnings: warn.list(),
|
|
6960
7838
|
summary: ctx.summary,
|
|
6961
7839
|
resolution: resolution(file),
|
|
7840
|
+
patterns: [...ctx.patterns.values()],
|
|
7841
|
+
...angle !== void 0 || altitude !== void 0 ? { globalLight: { angle: Math.max(-360, Math.min(360, angle ?? 120)), altitude: Math.max(0, Math.min(90, altitude ?? 30)) } } : {},
|
|
6962
7842
|
info: { psb: header.psb, depth: header.depth, colorMode: mode, channels: header.channels }
|
|
6963
7843
|
};
|
|
6964
7844
|
}
|
|
@@ -6968,6 +7848,18 @@ function importPsd(bytes, options = {}) {
|
|
|
6968
7848
|
}
|
|
6969
7849
|
|
|
6970
7850
|
// src/image-editor/psd/export.ts
|
|
7851
|
+
function usedPatterns(e) {
|
|
7852
|
+
if (e.kind === "pattern-overlay") return [e.patternId];
|
|
7853
|
+
if (e.kind === "stroke" && e.fill.type === "pattern") return [e.fill.patternId];
|
|
7854
|
+
if (e.kind === "bevel-emboss" && e.texture) return [e.texture.patternId];
|
|
7855
|
+
return [];
|
|
7856
|
+
}
|
|
7857
|
+
var padEven = (d) => {
|
|
7858
|
+
if (d.length % 2 === 0) return d;
|
|
7859
|
+
const out = new Uint8Array(d.length + 1);
|
|
7860
|
+
out.set(d);
|
|
7861
|
+
return out;
|
|
7862
|
+
};
|
|
6971
7863
|
var FLAG_PIXELS_IRRELEVANT = 8 | 16;
|
|
6972
7864
|
var EMPTY_RECT = { top: 0, left: 0, bottom: 0, right: 0 };
|
|
6973
7865
|
function unicodeBlock(s) {
|
|
@@ -7017,9 +7909,28 @@ function exportPsd(input) {
|
|
|
7017
7909
|
const psb = width > PSD_MAX_DIMENSION || height > PSD_MAX_DIMENSION;
|
|
7018
7910
|
const records = [];
|
|
7019
7911
|
let nextId = 0;
|
|
7020
|
-
const
|
|
7912
|
+
const notes = /* @__PURE__ */ new Map();
|
|
7913
|
+
const note = (msg, name) => notes.set(msg, [...notes.get(msg) ?? [], name]);
|
|
7914
|
+
const patternIds2 = /* @__PURE__ */ new Set();
|
|
7915
|
+
const effectsBlock = (layer) => {
|
|
7916
|
+
const fx = layer.effects;
|
|
7917
|
+
if (!fx) return null;
|
|
7918
|
+
for (const e of fx.items) for (const id of usedPatterns(e)) patternIds2.add(id);
|
|
7919
|
+
const key = layer.type === "group" ? "lfxs" : "lfx2";
|
|
7920
|
+
if (fx.psd) {
|
|
7921
|
+
const original = parseLfx2(fx.psd.data);
|
|
7922
|
+
if (original ? effectsFingerprint(original.effects) === effectsFingerprint(fx) : !fx.items.length) return { key, data: padEven(fx.psd.data) };
|
|
7923
|
+
}
|
|
7924
|
+
if (!fx.items.length && fx.enabled && (fx.scale ?? 1) === 1) return null;
|
|
7925
|
+
const { data, dropped } = writeLfx2(fx);
|
|
7926
|
+
if (dropped) note("Only the first outer glow, inner glow, bevel, satin and pattern overlay of a layer can be written; the others were left out", layer.name);
|
|
7927
|
+
return { key, data };
|
|
7928
|
+
};
|
|
7929
|
+
const common2 = (layer, extra = []) => {
|
|
7021
7930
|
const name = layer.name || (layer.type === "adjustment" ? adjustmentKindLabel(layer.kind) : "");
|
|
7022
7931
|
const blocks = [{ key: "luni", data: unicodeBlock(name) }, { key: "lyid", data: u32Bytes(++nextId) }, ...extra];
|
|
7932
|
+
const fx = effectsBlock(layer);
|
|
7933
|
+
if (fx) blocks.push(fx);
|
|
7023
7934
|
const locks = lockBits(layer);
|
|
7024
7935
|
if (locks) blocks.push({ key: "lspf", data: u32Bytes(locks >>> 0) });
|
|
7025
7936
|
return {
|
|
@@ -7048,7 +7959,7 @@ function exportPsd(input) {
|
|
|
7048
7959
|
});
|
|
7049
7960
|
for (const child of layer.children) write(child);
|
|
7050
7961
|
const blendKey = BLEND_TO_PSD[layer.blend] ?? "norm";
|
|
7051
|
-
const base =
|
|
7962
|
+
const base = common2(layer, [{ key: "lsct", data: sectionBlock(layer.collapsed ? SECTION_CLOSED : SECTION_OPEN, blendKey) }]);
|
|
7052
7963
|
const rec = {
|
|
7053
7964
|
...base,
|
|
7054
7965
|
rect: EMPTY_RECT,
|
|
@@ -7067,7 +7978,7 @@ function exportPsd(input) {
|
|
|
7067
7978
|
case "adjustment": {
|
|
7068
7979
|
const block = writeAdjustmentBlock(layer.kind, layer.params);
|
|
7069
7980
|
const rec = {
|
|
7070
|
-
...
|
|
7981
|
+
...common2(layer, [{ key: block.key, data: block.data }]),
|
|
7071
7982
|
rect: EMPTY_RECT,
|
|
7072
7983
|
channels: [CHANNEL_ALPHA, 0, 1, 2].map(emptyChannel),
|
|
7073
7984
|
blendKey: BLEND_TO_PSD[layer.blend] ?? "norm"
|
|
@@ -7087,7 +7998,7 @@ function exportPsd(input) {
|
|
|
7087
7998
|
if (layer.fillOpacity !== void 0 && layer.fillOpacity < 1) {
|
|
7088
7999
|
extra.push({ key: "iOpa", data: Uint8Array.from([Math.round(Math.max(0, layer.fillOpacity) * 255), 0, 0, 0]) });
|
|
7089
8000
|
}
|
|
7090
|
-
const rec = { ...
|
|
8001
|
+
const rec = { ...common2(layer, extra), rect: EMPTY_RECT, channels: [], blendKey: BLEND_TO_PSD[layer.blend] ?? "norm" };
|
|
7091
8002
|
const bounds = contentBounds(layer.surface);
|
|
7092
8003
|
if (bounds) {
|
|
7093
8004
|
const n2 = bounds.width * bounds.height;
|
|
@@ -7140,6 +8051,25 @@ function exportPsd(input) {
|
|
|
7140
8051
|
{ id: 1005, name: "", data: rw.result() },
|
|
7141
8052
|
{ id: 1057, name: "", data: version.result() }
|
|
7142
8053
|
];
|
|
8054
|
+
if (input.globalLight) {
|
|
8055
|
+
const i32 = (v) => {
|
|
8056
|
+
const w = new PsdWriter();
|
|
8057
|
+
w.i32(Math.round(v));
|
|
8058
|
+
return w.result();
|
|
8059
|
+
};
|
|
8060
|
+
resources.push({ id: 1037, name: "", data: i32(input.globalLight.angle) }, { id: 1049, name: "", data: i32(input.globalLight.altitude) });
|
|
8061
|
+
}
|
|
8062
|
+
const globalBlocks = [];
|
|
8063
|
+
if (patternIds2.size) {
|
|
8064
|
+
const byId = new Map((input.patterns ?? []).map((p) => [p.id, p]));
|
|
8065
|
+
const embed = [];
|
|
8066
|
+
for (const id of patternIds2) {
|
|
8067
|
+
const p = byId.get(id) ?? builtinPattern(id);
|
|
8068
|
+
if (p) embed.push(p);
|
|
8069
|
+
else note(`Pattern "${id}" is not in the document and was not embedded`, id);
|
|
8070
|
+
}
|
|
8071
|
+
if (embed.length) globalBlocks.push({ key: "Patt", data: writePatternBlock(embed) });
|
|
8072
|
+
}
|
|
7143
8073
|
const bytes = writePsd(
|
|
7144
8074
|
{
|
|
7145
8075
|
header: { psb, channels: planes.length, width, height, depth: 8, colorMode: 3 },
|
|
@@ -7147,13 +8077,14 @@ function exportPsd(input) {
|
|
|
7147
8077
|
resources,
|
|
7148
8078
|
layers: records,
|
|
7149
8079
|
mergedAlpha: transparent && records.length > 0,
|
|
7150
|
-
globalBlocks
|
|
8080
|
+
globalBlocks,
|
|
7151
8081
|
imageCompression: 1,
|
|
7152
8082
|
imageData: encodeRle(merged, { planes: planes.length, width, height, depth: 8, psb })
|
|
7153
8083
|
},
|
|
7154
8084
|
encodeLegacyName
|
|
7155
8085
|
);
|
|
7156
|
-
|
|
8086
|
+
const warnings = [...notes].map(([msg, names]) => `${msg} (${names.map((n2) => `"${n2}"`).join(", ")}).`);
|
|
8087
|
+
return { bytes, psb, records: records.length, warnings };
|
|
7157
8088
|
}
|
|
7158
8089
|
|
|
7159
8090
|
// src/image-editor/controller.ts
|
|
@@ -7184,22 +8115,53 @@ var PaintStrokeSession = class {
|
|
|
7184
8115
|
constructor(controller, target, stroke, apply) {
|
|
7185
8116
|
this.controller = controller;
|
|
7186
8117
|
this.target = target;
|
|
7187
|
-
this.stroke = stroke;
|
|
7188
8118
|
this.apply = apply;
|
|
7189
8119
|
this.before = controller._captureForStroke();
|
|
7190
8120
|
this.base = target.surface;
|
|
7191
8121
|
this.live = new Map(this.base.tiles);
|
|
8122
|
+
if (stroke instanceof StrokeCoverage) {
|
|
8123
|
+
this.stroke = stroke;
|
|
8124
|
+
this.mixer = null;
|
|
8125
|
+
} else {
|
|
8126
|
+
this.stroke = null;
|
|
8127
|
+
this.mixer = stroke.create(this.mixerPixels());
|
|
8128
|
+
this.mixerInit = stroke;
|
|
8129
|
+
}
|
|
7192
8130
|
}
|
|
7193
8131
|
controller;
|
|
7194
8132
|
target;
|
|
7195
|
-
stroke;
|
|
7196
8133
|
apply;
|
|
7197
8134
|
before;
|
|
7198
8135
|
base;
|
|
7199
8136
|
live;
|
|
7200
8137
|
touched = /* @__PURE__ */ new Set();
|
|
8138
|
+
stroke;
|
|
8139
|
+
mixer;
|
|
8140
|
+
mixerInit = null;
|
|
8141
|
+
/** Mixer tiles written since the last update: they get new tile objects so the compositor sees them change. */
|
|
8142
|
+
fresh = /* @__PURE__ */ new Set();
|
|
7201
8143
|
last = null;
|
|
7202
8144
|
closed = false;
|
|
8145
|
+
/** The live pixels a mixer stroke reads and writes; base tiles are copied on first write. */
|
|
8146
|
+
mixerPixels() {
|
|
8147
|
+
const a = this.apply;
|
|
8148
|
+
return {
|
|
8149
|
+
offset: a.offset,
|
|
8150
|
+
lockTransparency: a.lockTransparency,
|
|
8151
|
+
selection: a.selection,
|
|
8152
|
+
readTile: (tx, ty) => this.live.get(tileKey(tx, ty))?.data ?? (this.base.defaultValue ? defaultTile(this.base) : null),
|
|
8153
|
+
writeTile: (tx, ty) => {
|
|
8154
|
+
const key = tileKey(tx, ty);
|
|
8155
|
+
if (!this.touched.has(key)) {
|
|
8156
|
+
const src = this.base.tiles.get(key)?.data ?? defaultTile(this.base);
|
|
8157
|
+
this.live.set(key, { data: new Uint8ClampedArray(src), uniform: null });
|
|
8158
|
+
this.touched.add(key);
|
|
8159
|
+
}
|
|
8160
|
+
this.fresh.add(key);
|
|
8161
|
+
return this.live.get(key).data;
|
|
8162
|
+
}
|
|
8163
|
+
};
|
|
8164
|
+
}
|
|
7203
8165
|
get layerId() {
|
|
7204
8166
|
return this.target.id;
|
|
7205
8167
|
}
|
|
@@ -7207,11 +8169,18 @@ var PaintStrokeSession = class {
|
|
|
7207
8169
|
push(points) {
|
|
7208
8170
|
if (this.closed || !points.length) return;
|
|
7209
8171
|
this.last = points[points.length - 1];
|
|
7210
|
-
this.update(this.stroke.push(points));
|
|
8172
|
+
this.update(this.mixer ? this.mixer.push(points) : this.stroke.push(points));
|
|
7211
8173
|
}
|
|
7212
8174
|
update(rects) {
|
|
7213
8175
|
if (!rects.length) return;
|
|
7214
8176
|
const ch = this.base.channels;
|
|
8177
|
+
if (this.mixer) {
|
|
8178
|
+
for (const key of this.fresh) this.live.set(key, { data: this.live.get(key).data, uniform: null });
|
|
8179
|
+
this.fresh.clear();
|
|
8180
|
+
this.controller._strokeUpdate(this.target.id, this.target.channel, new TiledSurface(ch, new Map(this.live), this.base.defaultValue), rects);
|
|
8181
|
+
return;
|
|
8182
|
+
}
|
|
8183
|
+
const stroke = this.stroke;
|
|
7215
8184
|
const regions = /* @__PURE__ */ new Map();
|
|
7216
8185
|
for (const r of rects) {
|
|
7217
8186
|
for (const t of tilesInRect({ x: r.x - this.apply.offset.x, y: r.y - this.apply.offset.y, width: r.width, height: r.height })) {
|
|
@@ -7224,7 +8193,7 @@ var PaintStrokeSession = class {
|
|
|
7224
8193
|
const src = this.base.tiles.get(key)?.data ?? defaultTile(this.base);
|
|
7225
8194
|
const prev = this.touched.has(key) ? this.live.get(key)?.data : void 0;
|
|
7226
8195
|
this.touched.add(key);
|
|
7227
|
-
const data = applyStrokeToTile(
|
|
8196
|
+
const data = applyStrokeToTile(stroke.coverage, src, ch, tx, ty, this.apply, prev ? { into: prev, region: r } : {});
|
|
7228
8197
|
this.live.set(key, { data, uniform: null });
|
|
7229
8198
|
}
|
|
7230
8199
|
this.controller._strokeUpdate(this.target.id, this.target.channel, new TiledSurface(ch, new Map(this.live), this.base.defaultValue), rects);
|
|
@@ -7232,7 +8201,8 @@ var PaintStrokeSession = class {
|
|
|
7232
8201
|
/** Finishes the stroke (one undo step) and returns what changed. */
|
|
7233
8202
|
end() {
|
|
7234
8203
|
if (this.closed) throw new LayerOperationError("This stroke has already ended.");
|
|
7235
|
-
this.
|
|
8204
|
+
const last = this.last ?? void 0;
|
|
8205
|
+
this.update(this.mixer ? this.mixer.finish(last) : this.stroke.finish(last));
|
|
7236
8206
|
this.closed = true;
|
|
7237
8207
|
const ch = this.base.channels;
|
|
7238
8208
|
const tiles = new Map(this.base.tiles);
|
|
@@ -7246,9 +8216,12 @@ var PaintStrokeSession = class {
|
|
|
7246
8216
|
if (!sameTile(prev, tile)) changed = true;
|
|
7247
8217
|
}
|
|
7248
8218
|
const surface = changed ? new TiledSurface(ch, tiles, this.base.defaultValue) : null;
|
|
8219
|
+
const mixerLoad = this.mixer ? this.mixerInit.end(this.mixer.load) : void 0;
|
|
7249
8220
|
this.controller._strokeCommit(this.before, this.target.id, this.target.channel, surface);
|
|
7250
|
-
const b = this.stroke.coverage.bounds;
|
|
7251
|
-
|
|
8221
|
+
const b = this.mixer ? this.mixer.bounds : this.stroke.coverage.bounds;
|
|
8222
|
+
const result = { layerId: this.target.id, channel: this.target.channel, bounds: changed ? b : null, dabs: (this.mixer ?? this.stroke).dabCount };
|
|
8223
|
+
if (mixerLoad) result.mixerLoad = mixerLoad;
|
|
8224
|
+
return result;
|
|
7252
8225
|
}
|
|
7253
8226
|
/** Abandons the stroke and restores the layer. */
|
|
7254
8227
|
cancel() {
|
|
@@ -7362,8 +8335,9 @@ var ImageEditorController = class {
|
|
|
7362
8335
|
this.renderer.layerSources = {
|
|
7363
8336
|
surfaceOf: (layer) => this.surfaceOf(layer),
|
|
7364
8337
|
sourceIsOpaque: () => this.sourceIsOpaque(),
|
|
7365
|
-
version: () => this.sourceVersion()
|
|
7366
|
-
globalLight: () => this.state.globalLight
|
|
8338
|
+
version: () => `${this.sourceVersion()}:${this.patternsVersion()}`,
|
|
8339
|
+
globalLight: () => this.state.globalLight,
|
|
8340
|
+
pattern: (id) => this.state.patterns?.find((p) => p.id === id) ?? null
|
|
7367
8341
|
};
|
|
7368
8342
|
this.state = {
|
|
7369
8343
|
sourceUrl: typeof options.image === "string" ? options.image : null,
|
|
@@ -7384,7 +8358,7 @@ var ImageEditorController = class {
|
|
|
7384
8358
|
activeLayerId: null,
|
|
7385
8359
|
activeTarget: "pixels",
|
|
7386
8360
|
outputOpacity: 1,
|
|
7387
|
-
paint: { brush: { ...DEFAULT_BRUSH }, foreground: "#000000", background: "#ffffff" },
|
|
8361
|
+
paint: { brush: { ...DEFAULT_BRUSH }, foreground: "#000000", background: "#ffffff", mixer: { ...DEFAULT_MIXER }, mixerLoad: EMPTY_MIXER_LOAD },
|
|
7388
8362
|
fit: options.fit ?? "margin"
|
|
7389
8363
|
};
|
|
7390
8364
|
if (options.image instanceof Object && "width" in options.image) {
|
|
@@ -7518,6 +8492,7 @@ var ImageEditorController = class {
|
|
|
7518
8492
|
};
|
|
7519
8493
|
if (s.legacyOutputBlur !== void 0) entry.legacyOutputBlur = s.legacyOutputBlur;
|
|
7520
8494
|
if (s.globalLight) entry.globalLight = { ...s.globalLight };
|
|
8495
|
+
if (s.patterns) entry.patterns = s.patterns;
|
|
7521
8496
|
return entry;
|
|
7522
8497
|
}
|
|
7523
8498
|
restoreHistoryEntry(entry) {
|
|
@@ -7532,6 +8507,8 @@ var ImageEditorController = class {
|
|
|
7532
8507
|
else delete s.legacyOutputBlur;
|
|
7533
8508
|
if (entry.globalLight) s.globalLight = { ...entry.globalLight };
|
|
7534
8509
|
else delete s.globalLight;
|
|
8510
|
+
if (entry.patterns) s.patterns = entry.patterns;
|
|
8511
|
+
else delete s.patterns;
|
|
7535
8512
|
s.selection = entry.selection;
|
|
7536
8513
|
if (this.imageElement !== entry.source.element && !this.samplePixelsOverride) this.samplePixels = null;
|
|
7537
8514
|
this.imageElement = entry.source.element;
|
|
@@ -7753,7 +8730,14 @@ var ImageEditorController = class {
|
|
|
7753
8730
|
}
|
|
7754
8731
|
compositorSources() {
|
|
7755
8732
|
const { width, height } = this.naturalSize;
|
|
7756
|
-
return {
|
|
8733
|
+
return {
|
|
8734
|
+
width,
|
|
8735
|
+
height,
|
|
8736
|
+
surfaceOf: (l) => this.surfaceOf(l),
|
|
8737
|
+
version: `${this.sourceVersion()}:${this.patternsVersion()}`,
|
|
8738
|
+
globalLight: this.state.globalLight,
|
|
8739
|
+
pattern: (id) => this.state.patterns?.find((p) => p.id === id) ?? null
|
|
8740
|
+
};
|
|
7757
8741
|
}
|
|
7758
8742
|
sourceIds = /* @__PURE__ */ new WeakMap();
|
|
7759
8743
|
nextSourceId = 1;
|
|
@@ -8199,6 +9183,22 @@ var ImageEditorController = class {
|
|
|
8199
9183
|
this.commitLayers(setLayerEffects(this.state.layers, id, effects), void 0, commitToHistory);
|
|
8200
9184
|
return this.getLayerEffects(id);
|
|
8201
9185
|
}
|
|
9186
|
+
patternIds = /* @__PURE__ */ new WeakMap();
|
|
9187
|
+
/** Changes whenever the document's pattern list is replaced (the compositor then drops its caches). */
|
|
9188
|
+
patternsVersion() {
|
|
9189
|
+
const list2 = this.state.patterns;
|
|
9190
|
+
if (!list2) return 0;
|
|
9191
|
+
let id = this.patternIds.get(list2);
|
|
9192
|
+
if (id === void 0) {
|
|
9193
|
+
id = this.nextSourceId++;
|
|
9194
|
+
this.patternIds.set(list2, id);
|
|
9195
|
+
}
|
|
9196
|
+
return id;
|
|
9197
|
+
}
|
|
9198
|
+
/** The document's patterns (from an opened PSD), besides the built-in ones. */
|
|
9199
|
+
getDocumentPatterns() {
|
|
9200
|
+
return this.state.patterns ?? [];
|
|
9201
|
+
}
|
|
8202
9202
|
/** The light effects with "use global light" follow. */
|
|
8203
9203
|
getGlobalLight() {
|
|
8204
9204
|
return { ...this.state.globalLight ?? DEFAULT_GLOBAL_LIGHT };
|
|
@@ -8375,12 +9375,38 @@ var ImageEditorController = class {
|
|
|
8375
9375
|
const next = {
|
|
8376
9376
|
brush: options.brush ? normalizeBrush({ ...cur.brush, ...options.brush }) : cur.brush,
|
|
8377
9377
|
foreground: options.foreground !== void 0 ? toHex2(parseColor(options.foreground)) : cur.foreground,
|
|
8378
|
-
background: options.background !== void 0 ? toHex2(parseColor(options.background)) : cur.background
|
|
9378
|
+
background: options.background !== void 0 ? toHex2(parseColor(options.background)) : cur.background,
|
|
9379
|
+
mixer: options.mixer ? normalizeMixer({ ...cur.mixer, ...options.mixer }) : cur.mixer,
|
|
9380
|
+
mixerLoad: cur.mixerLoad
|
|
8379
9381
|
};
|
|
8380
9382
|
this.state.paint = next;
|
|
8381
9383
|
this.notify();
|
|
8382
9384
|
return next;
|
|
8383
9385
|
}
|
|
9386
|
+
/**
|
|
9387
|
+
* Loads the Mixer Brush's reservoir full with `color` (default the foreground); the picked-up
|
|
9388
|
+
* colour is kept. View state, no undo. Returns the brush load.
|
|
9389
|
+
*/
|
|
9390
|
+
loadMixerBrush(color) {
|
|
9391
|
+
const c = parseColor(color ?? this.state.paint.foreground);
|
|
9392
|
+
const load = loadMixer(this.state.paint.mixerLoad, [c[0] / 255, c[1] / 255, c[2] / 255]);
|
|
9393
|
+
this.state.paint = { ...this.state.paint, mixerLoad: load };
|
|
9394
|
+
this.notify();
|
|
9395
|
+
return load;
|
|
9396
|
+
}
|
|
9397
|
+
/** Cleans the Mixer Brush: empties the pickup well (the reservoir is kept). Returns the brush load. */
|
|
9398
|
+
cleanMixerBrush() {
|
|
9399
|
+
const load = cleanMixer(this.state.paint.mixerLoad);
|
|
9400
|
+
this.state.paint = { ...this.state.paint, mixerLoad: load };
|
|
9401
|
+
this.notify();
|
|
9402
|
+
return load;
|
|
9403
|
+
}
|
|
9404
|
+
/** Empties the Mixer Brush entirely (no reservoir, nothing picked up). Returns the brush load. */
|
|
9405
|
+
emptyMixerBrush() {
|
|
9406
|
+
this.state.paint = { ...this.state.paint, mixerLoad: EMPTY_MIXER_LOAD };
|
|
9407
|
+
this.notify();
|
|
9408
|
+
return EMPTY_MIXER_LOAD;
|
|
9409
|
+
}
|
|
8384
9410
|
/** D: black foreground, white background. */
|
|
8385
9411
|
resetPaintColors() {
|
|
8386
9412
|
this.setPaintOptions({ foreground: "#000000", background: "#ffffff" });
|
|
@@ -8425,7 +9451,11 @@ var ImageEditorController = class {
|
|
|
8425
9451
|
*/
|
|
8426
9452
|
beginStroke(init = {}) {
|
|
8427
9453
|
const tool = init.tool ?? "brush";
|
|
9454
|
+
if (tool !== "brush" && tool !== "eraser" && tool !== "mixer") throw new LayerOperationError(`Unknown paint tool "${String(tool)}"; use brush, eraser or mixer.`);
|
|
8428
9455
|
const target = this.paintTarget(init.layerId, init.channel, tool === "eraser" ? "erase" : "paint");
|
|
9456
|
+
if (tool === "mixer" && target.channel === "mask") {
|
|
9457
|
+
throw new LayerOperationError("The Mixer Brush paints colour on pixels, not on masks; target pixels (channel pixels), or paint the mask with the brush.");
|
|
9458
|
+
}
|
|
8429
9459
|
const brush = normalizeBrush({ ...this.state.paint.brush, ...init.brush ?? {} });
|
|
8430
9460
|
const color = parseColor(init.color ?? this.state.paint.foreground);
|
|
8431
9461
|
const background = parseColor(init.background ?? this.state.paint.background);
|
|
@@ -8443,8 +9473,26 @@ var ImageEditorController = class {
|
|
|
8443
9473
|
selection: this.state.selection?.bounds ? this.state.selection : null,
|
|
8444
9474
|
offset: target.offset
|
|
8445
9475
|
};
|
|
9476
|
+
if (tool === "mixer") return new PaintStrokeSession(this, target, this.mixerStroke(init, brush), apply);
|
|
8446
9477
|
return new PaintStrokeSession(this, target, new StrokeCoverage(brush), apply);
|
|
8447
9478
|
}
|
|
9479
|
+
/** A Mixer Brush stroke's start: the settings, and the load (refilled when asked or set to). */
|
|
9480
|
+
mixerStroke(init, brush) {
|
|
9481
|
+
const settings = normalizeMixer({ ...this.state.paint.mixer, ...init.mixer ?? {} });
|
|
9482
|
+
let load = this.state.paint.mixerLoad;
|
|
9483
|
+
if (init.color !== void 0 || settings.loadEachStroke) {
|
|
9484
|
+
const c = parseColor(init.color ?? this.state.paint.foreground);
|
|
9485
|
+
load = loadMixer(load, [c[0] / 255, c[1] / 255, c[2] / 255]);
|
|
9486
|
+
}
|
|
9487
|
+
return {
|
|
9488
|
+
create: (pixels) => new MixerStroke(brush, settings, load, pixels),
|
|
9489
|
+
end: (after) => {
|
|
9490
|
+
const next = settings.cleanEachStroke ? cleanMixer(after) : after;
|
|
9491
|
+
this.state.paint = { ...this.state.paint, mixerLoad: next };
|
|
9492
|
+
return next;
|
|
9493
|
+
}
|
|
9494
|
+
};
|
|
9495
|
+
}
|
|
8448
9496
|
/** @internal Shows a stroke's live surface (no history). */
|
|
8449
9497
|
_strokeUpdate(layerId, channel, surface, rects) {
|
|
8450
9498
|
this.state.layers = mapLayer(this.state.layers, layerId, (l) => this.withSurface(l, channel, surface));
|
|
@@ -8468,7 +9516,7 @@ var ImageEditorController = class {
|
|
|
8468
9516
|
}
|
|
8469
9517
|
/**
|
|
8470
9518
|
* Paints a whole stroke in one call (agents, replays): `points` in document pixels with optional
|
|
8471
|
-
* pressure, through the brush or
|
|
9519
|
+
* pressure, through the brush, eraser or Mixer Brush. One undo step. Returns the layer, channel, the changed
|
|
8472
9520
|
* document bounds and the number of dabs.
|
|
8473
9521
|
*/
|
|
8474
9522
|
paintStroke(init) {
|
|
@@ -9433,6 +10481,10 @@ var ImageEditorController = class {
|
|
|
9433
10481
|
s.activeLayerId = pixels.length ? pixels[pixels.length - 1].id : null;
|
|
9434
10482
|
s.activeTarget = "pixels";
|
|
9435
10483
|
this.psdResolution = result.resolution;
|
|
10484
|
+
if (result.patterns.length) s.patterns = result.patterns;
|
|
10485
|
+
else delete s.patterns;
|
|
10486
|
+
if (result.globalLight) s.globalLight = result.globalLight;
|
|
10487
|
+
else delete s.globalLight;
|
|
9436
10488
|
this.lastLoadProblems = result.warnings;
|
|
9437
10489
|
this.notify();
|
|
9438
10490
|
return {
|
|
@@ -9477,6 +10529,11 @@ var ImageEditorController = class {
|
|
|
9477
10529
|
return baked;
|
|
9478
10530
|
};
|
|
9479
10531
|
const prepare = async (layer) => {
|
|
10532
|
+
const p = await prepareLayer(layer);
|
|
10533
|
+
if (p?.blend === "pigment") add("The Pigment blend mode has no PSD equivalent and was written as Normal", layer.name);
|
|
10534
|
+
return p;
|
|
10535
|
+
};
|
|
10536
|
+
const prepareLayer = async (layer) => {
|
|
9480
10537
|
if (layer.type === "group") {
|
|
9481
10538
|
const children = [];
|
|
9482
10539
|
for (const c of layer.children) {
|
|
@@ -9496,6 +10553,7 @@ var ImageEditorController = class {
|
|
|
9496
10553
|
const content = { ...layer, visible: true, opacity: 1, blend: "normal", annotation: { ...layer.annotation, blend: void 0 } };
|
|
9497
10554
|
delete content.mask;
|
|
9498
10555
|
delete content.clipped;
|
|
10556
|
+
delete content.effects;
|
|
9499
10557
|
const data = compositor.composite([content], canvas, sources);
|
|
9500
10558
|
const out2 = {
|
|
9501
10559
|
type: "pixel",
|
|
@@ -9509,6 +10567,7 @@ var ImageEditorController = class {
|
|
|
9509
10567
|
};
|
|
9510
10568
|
if (layer.clipped) out2.clipped = true;
|
|
9511
10569
|
if (layer.locks) out2.locks = layer.locks;
|
|
10570
|
+
if (layer.effects) out2.effects = layer.effects;
|
|
9512
10571
|
const mask2 = bakeMask(layer);
|
|
9513
10572
|
if (mask2) out2.mask = mask2;
|
|
9514
10573
|
return out2;
|
|
@@ -9525,6 +10584,7 @@ var ImageEditorController = class {
|
|
|
9525
10584
|
delete content.mask;
|
|
9526
10585
|
delete content.clipped;
|
|
9527
10586
|
delete content.fillOpacity;
|
|
10587
|
+
delete content.effects;
|
|
9528
10588
|
const data = compositor.composite([content], canvas, sources);
|
|
9529
10589
|
out = { ...out, surface: surfaceFromPixels({ width, height, data }, 4, -layer.offset.x, -layer.offset.y) };
|
|
9530
10590
|
}
|
|
@@ -9547,7 +10607,8 @@ var ImageEditorController = class {
|
|
|
9547
10607
|
if (s.legacyOutputBlur) warnings.push("The document-level blur of an old document is not written.");
|
|
9548
10608
|
for (const [msg, names] of note) warnings.push(`${msg} (${names.length === 1 ? "layer" : "layers"} ${names.map((n) => `"${n}"`).join(", ")}).`);
|
|
9549
10609
|
const merged = compositor.composite(prepared, canvas, { ...sources, version: `psd:${Date.now()}` });
|
|
9550
|
-
const result = exportPsd({ width, height, layers: prepared, merged, resolution: this.psdResolution });
|
|
10610
|
+
const result = exportPsd({ width, height, layers: prepared, merged, resolution: this.psdResolution, patterns: this.state.patterns, globalLight: this.state.globalLight });
|
|
10611
|
+
warnings.push(...result.warnings);
|
|
9551
10612
|
return { bytes: result.bytes, warnings, psb: result.psb, width, height };
|
|
9552
10613
|
}
|
|
9553
10614
|
async replaceSource(src, clearAnnotations) {
|
|
@@ -9651,6 +10712,9 @@ var ImageEditorController = class {
|
|
|
9651
10712
|
const light = data.globalLight;
|
|
9652
10713
|
if (light && Number.isFinite(light.angle) && Number.isFinite(light.altitude)) s.globalLight = { angle: light.angle, altitude: Math.min(90, Math.max(0, light.altitude)) };
|
|
9653
10714
|
else delete s.globalLight;
|
|
10715
|
+
const patterns = deserializePatterns(data.patterns, problems);
|
|
10716
|
+
if (patterns.length) s.patterns = patterns;
|
|
10717
|
+
else delete s.patterns;
|
|
9654
10718
|
if (!this.imageElement && typeof data.width === "number" && typeof data.height === "number" && data.width > 0 && data.height > 0) {
|
|
9655
10719
|
s.imageDimensions = { width: data.width, height: data.height };
|
|
9656
10720
|
}
|
|
@@ -9665,6 +10729,7 @@ var ImageEditorController = class {
|
|
|
9665
10729
|
s.transform = { ...data.transform };
|
|
9666
10730
|
s.activeLayerId = null;
|
|
9667
10731
|
delete s.globalLight;
|
|
10732
|
+
delete s.patterns;
|
|
9668
10733
|
}
|
|
9669
10734
|
s.activeTarget = "pixels";
|
|
9670
10735
|
this.viewKey = [];
|
|
@@ -9674,7 +10739,7 @@ var ImageEditorController = class {
|
|
|
9674
10739
|
serializeState() {
|
|
9675
10740
|
const { width, height } = this.naturalSize;
|
|
9676
10741
|
const view = legacyView(this.stack(), width, height, null);
|
|
9677
|
-
if (view.exact && !this.state.globalLight) {
|
|
10742
|
+
if (view.exact && !this.state.globalLight && !this.state.patterns?.length) {
|
|
9678
10743
|
return {
|
|
9679
10744
|
// States without adjustment layers keep schema 1, so they round-trip unchanged.
|
|
9680
10745
|
version: view.adjustments.layers?.length ? 2 : 1,
|
|
@@ -9702,6 +10767,7 @@ var ImageEditorController = class {
|
|
|
9702
10767
|
};
|
|
9703
10768
|
if (this.state.legacyOutputBlur !== void 0) out.legacyOutputBlur = this.state.legacyOutputBlur;
|
|
9704
10769
|
if (this.state.globalLight) out.globalLight = { ...this.state.globalLight };
|
|
10770
|
+
if (this.state.patterns?.length) out.patterns = serializePatterns(this.state.patterns);
|
|
9705
10771
|
return out;
|
|
9706
10772
|
}
|
|
9707
10773
|
};
|
|
@@ -10291,17 +11357,17 @@ function controlTrackBackground(spec) {
|
|
|
10291
11357
|
return void 0;
|
|
10292
11358
|
}
|
|
10293
11359
|
}
|
|
10294
|
-
function getControlPath(
|
|
10295
|
-
let cur =
|
|
11360
|
+
function getControlPath(obj2, path) {
|
|
11361
|
+
let cur = obj2;
|
|
10296
11362
|
for (const part of path.split(".")) {
|
|
10297
11363
|
if (cur === null || cur === void 0) return void 0;
|
|
10298
11364
|
cur = cur[part];
|
|
10299
11365
|
}
|
|
10300
11366
|
return cur;
|
|
10301
11367
|
}
|
|
10302
|
-
function setControlPath(
|
|
11368
|
+
function setControlPath(obj2, path, value) {
|
|
10303
11369
|
const parts = path.split(".");
|
|
10304
|
-
const root = Array.isArray(
|
|
11370
|
+
const root = Array.isArray(obj2) ? [...obj2] : { ...obj2 };
|
|
10305
11371
|
let cur = root;
|
|
10306
11372
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
10307
11373
|
const next = cur[parts[i]];
|
|
@@ -10359,8 +11425,8 @@ function describeSelection(mask) {
|
|
|
10359
11425
|
if (!mask || !mask.bounds) return "No selection";
|
|
10360
11426
|
let sum = 0;
|
|
10361
11427
|
for (let i = 0; i < mask.data.length; i++) sum += mask.data[i];
|
|
10362
|
-
const
|
|
10363
|
-
const shown =
|
|
11428
|
+
const pct2 = sum / 255 / mask.data.length * 100;
|
|
11429
|
+
const shown = pct2 >= 10 ? Math.round(pct2) : Math.round(pct2 * 10) / 10;
|
|
10364
11430
|
return `${mask.bounds.width} \xD7 ${mask.bounds.height} px selected (${shown}% of the image)`;
|
|
10365
11431
|
}
|
|
10366
11432
|
|
|
@@ -10370,7 +11436,7 @@ var modeSchema = {
|
|
|
10370
11436
|
enum: [...SELECTION_MODES],
|
|
10371
11437
|
description: "How the result combines with the current selection (default replace)."
|
|
10372
11438
|
};
|
|
10373
|
-
var
|
|
11439
|
+
var num3 = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
|
|
10374
11440
|
function summary(controller) {
|
|
10375
11441
|
const sel = controller.getSelection();
|
|
10376
11442
|
const { width, height } = controller.getState().imageDimensions;
|
|
@@ -10420,7 +11486,7 @@ function createImageSelectionTools(controller) {
|
|
|
10420
11486
|
},
|
|
10421
11487
|
annotations: writeAnnotations("Select a region", true),
|
|
10422
11488
|
handler: ({ shape, x, y, width, height, mode, feather }) => {
|
|
10423
|
-
const opts = { mode: parseSelectionMode(mode), feather:
|
|
11489
|
+
const opts = { mode: parseSelectionMode(mode), feather: num3(feather, 0) };
|
|
10424
11490
|
switch (shape) {
|
|
10425
11491
|
case "all":
|
|
10426
11492
|
controller.selectAll();
|
|
@@ -10433,7 +11499,7 @@ function createImageSelectionTools(controller) {
|
|
|
10433
11499
|
break;
|
|
10434
11500
|
case "rect":
|
|
10435
11501
|
case "ellipse": {
|
|
10436
|
-
const rect = { x:
|
|
11502
|
+
const rect = { x: num3(x, NaN), y: num3(y, NaN), width: num3(width, NaN), height: num3(height, NaN) };
|
|
10437
11503
|
if (![rect.x, rect.y, rect.width, rect.height].every(Number.isFinite)) {
|
|
10438
11504
|
throw new Error("rect and ellipse need numeric x, y, width and height.");
|
|
10439
11505
|
}
|
|
@@ -10472,15 +11538,15 @@ function createImageSelectionTools(controller) {
|
|
|
10472
11538
|
},
|
|
10473
11539
|
annotations: writeAnnotations("Select by colour", true),
|
|
10474
11540
|
handler: async ({ x, y, color, tolerance, contiguous, fuzziness, sampleMerged, sample, mode }) => {
|
|
10475
|
-
const
|
|
11541
|
+
const common2 = {
|
|
10476
11542
|
mode: parseSelectionMode(mode),
|
|
10477
11543
|
sampleMerged: sampleMerged === true,
|
|
10478
11544
|
sample: sample ?? "composite"
|
|
10479
11545
|
};
|
|
10480
11546
|
if (typeof color === "string") {
|
|
10481
|
-
await controller.selectColorRange([{ color }], { ...
|
|
11547
|
+
await controller.selectColorRange([{ color }], { ...common2, fuzziness: num3(fuzziness, 40) });
|
|
10482
11548
|
} else if (typeof x === "number" && typeof y === "number") {
|
|
10483
|
-
await controller.magicWand({ x, y }, { ...
|
|
11549
|
+
await controller.magicWand({ x, y }, { ...common2, tolerance: num3(tolerance, 32), contiguous: contiguous !== false });
|
|
10484
11550
|
} else {
|
|
10485
11551
|
throw new Error("Pass x and y (magic wand) or color (#rrggbb).");
|
|
10486
11552
|
}
|
|
@@ -10504,7 +11570,7 @@ function createImageSelectionTools(controller) {
|
|
|
10504
11570
|
handler: ({ op, radius }) => {
|
|
10505
11571
|
if (!SELECTION_MODIFY_OPS.includes(op)) throw new Error(`Unknown op "${String(op)}".`);
|
|
10506
11572
|
if (!controller.getSelection()) throw new Error("Nothing is selected.");
|
|
10507
|
-
controller.modifySelection(op, Math.min(500, Math.max(0,
|
|
11573
|
+
controller.modifySelection(op, Math.min(500, Math.max(0, num3(radius, 0))));
|
|
10508
11574
|
return summary(controller);
|
|
10509
11575
|
}
|
|
10510
11576
|
};
|
|
@@ -10528,12 +11594,12 @@ function createImageSelectionTools(controller) {
|
|
|
10528
11594
|
handler: async (params) => {
|
|
10529
11595
|
if (!controller.getSelection()) throw new Error("Nothing is selected.");
|
|
10530
11596
|
await controller.refineSelection({
|
|
10531
|
-
radius:
|
|
11597
|
+
radius: num3(params.radius, 8),
|
|
10532
11598
|
smartRadius: params.smartRadius === true,
|
|
10533
|
-
smooth:
|
|
10534
|
-
feather:
|
|
10535
|
-
contrast:
|
|
10536
|
-
shiftEdge:
|
|
11599
|
+
smooth: num3(params.smooth, 0),
|
|
11600
|
+
feather: num3(params.feather, 0),
|
|
11601
|
+
contrast: num3(params.contrast, 0),
|
|
11602
|
+
shiftEdge: num3(params.shiftEdge, 0)
|
|
10537
11603
|
});
|
|
10538
11604
|
return summary(controller);
|
|
10539
11605
|
}
|
|
@@ -10680,14 +11746,14 @@ function rebin(bins, count) {
|
|
|
10680
11746
|
return out;
|
|
10681
11747
|
}
|
|
10682
11748
|
function histogramSummary(h, bins) {
|
|
10683
|
-
const
|
|
11749
|
+
const pct2 = (n) => h.samples ? round(n / h.samples * 100) : 0;
|
|
10684
11750
|
const channels = { red: h.red, green: h.green, blue: h.blue, luminance: h.luminance };
|
|
10685
11751
|
return {
|
|
10686
11752
|
samples: h.samples,
|
|
10687
11753
|
transparent: h.transparent,
|
|
10688
11754
|
clipping: {
|
|
10689
|
-
shadowsPercent: { red:
|
|
10690
|
-
highlightsPercent: { red:
|
|
11755
|
+
shadowsPercent: { red: pct2(h.shadows[0]), green: pct2(h.shadows[1]), blue: pct2(h.shadows[2]) },
|
|
11756
|
+
highlightsPercent: { red: pct2(h.highlights[0]), green: pct2(h.highlights[1]), blue: pct2(h.highlights[2]) }
|
|
10691
11757
|
},
|
|
10692
11758
|
stats: Object.fromEntries(Object.entries(channels).map(([k, v]) => [k, binStats(v, h.samples)])),
|
|
10693
11759
|
...bins ? { bins: Object.fromEntries(Object.entries(channels).map(([k, v]) => [k, rebin(v, bins)])) } : {}
|
|
@@ -11442,7 +12508,7 @@ var WRITE_IDEMPOTENT3 = { readOnlyHint: false, destructiveHint: false, idempoten
|
|
|
11442
12508
|
var DESTRUCTIVE2 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
|
|
11443
12509
|
var LAYER_ID3 = { type: "string", description: "Layer id from list_layers (a pixel, annotation or group layer)." };
|
|
11444
12510
|
var EFFECT_ID = { type: "string", description: "Effect id from list_layer_effects or add_layer_effect." };
|
|
11445
|
-
var BLEND_IDS = BLEND_MODES.map((m) => m.id);
|
|
12511
|
+
var BLEND_IDS = BLEND_MODES.map((m) => m.id).filter((id) => id !== "pigment");
|
|
11446
12512
|
var DEFS = {
|
|
11447
12513
|
color: { type: "string", pattern: "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$", description: "Colour as #rrggbb." },
|
|
11448
12514
|
contour: {
|
|
@@ -11633,11 +12699,13 @@ function settingsSchema(forUpdate) {
|
|
|
11633
12699
|
additionalProperties: false
|
|
11634
12700
|
};
|
|
11635
12701
|
}
|
|
11636
|
-
function patternList() {
|
|
11637
|
-
|
|
12702
|
+
function patternList(controller) {
|
|
12703
|
+
const doc = controller.getDocumentPatterns().map((p) => ({ id: p.id, name: p.name, width: p.width, height: p.height, builtin: false }));
|
|
12704
|
+
const builtins = builtinPatternIds().map((id) => {
|
|
11638
12705
|
const p = builtinPattern(id);
|
|
11639
12706
|
return { id, name: p.name, width: p.width, height: p.height, builtin: true };
|
|
11640
12707
|
});
|
|
12708
|
+
return [...doc, ...builtins];
|
|
11641
12709
|
}
|
|
11642
12710
|
function describeLayerEffects(effects) {
|
|
11643
12711
|
return {
|
|
@@ -11655,13 +12723,13 @@ function createImageEffectTools(controller) {
|
|
|
11655
12723
|
return id;
|
|
11656
12724
|
};
|
|
11657
12725
|
const result = (layerId, extra = {}) => ({ layerId, ...extra, ...describeLayerEffects(controller.getLayerEffects(layerId)) });
|
|
11658
|
-
const
|
|
12726
|
+
const list2 = {
|
|
11659
12727
|
name: "list_layer_effects",
|
|
11660
12728
|
title: "List layer effects",
|
|
11661
12729
|
description: "Returns a layer's effects: the master switch (enabled), Scale Effects (scale, 1 = 100 %), margin (px the effects reach beyond the shape), whether they came from a PSD, and every effect with its id, kind, label and all settings. Also returns the document global light and the pattern ids pattern effects can use.",
|
|
11662
12730
|
inputSchema: { type: "object", properties: { layerId: LAYER_ID3 }, required: ["layerId"], additionalProperties: false },
|
|
11663
12731
|
annotations: { title: "List layer effects", ...READ3 },
|
|
11664
|
-
handler: ({ layerId }) => result(layerArg(layerId), { globalLight: controller.getGlobalLight(), patterns: patternList(), kinds: [...LAYER_EFFECT_KINDS] })
|
|
12732
|
+
handler: ({ layerId }) => result(layerArg(layerId), { globalLight: controller.getGlobalLight(), patterns: patternList(controller), kinds: [...LAYER_EFFECT_KINDS] })
|
|
11665
12733
|
};
|
|
11666
12734
|
const add = {
|
|
11667
12735
|
name: "add_layer_effect",
|
|
@@ -11761,10 +12829,163 @@ function createImageEffectTools(controller) {
|
|
|
11761
12829
|
return { globalLight: controller.setGlobalLight(next) };
|
|
11762
12830
|
}
|
|
11763
12831
|
};
|
|
11764
|
-
return [
|
|
12832
|
+
return [list2, add, update, remove, setEnabled, light];
|
|
11765
12833
|
}
|
|
11766
12834
|
|
|
12835
|
+
// src/image-editor/layers/paint-ui.ts
|
|
12836
|
+
var plain2 = { kind: "plain" };
|
|
12837
|
+
var BRUSH_OPTION_CONTROLS = [
|
|
12838
|
+
{ id: "size", label: "Size", section: "layer", min: 1, max: 500, step: 1, default: 20, decimals: 0, track: plain2, unit: "px" },
|
|
12839
|
+
{ id: "hardness", label: "Hardness", section: "layer", min: 0, max: 1, step: 0.01, default: 1, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
|
|
12840
|
+
{ id: "opacity", label: "Opacity", section: "layer", min: 0, max: 1, step: 0.01, default: 1, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
|
|
12841
|
+
{ id: "flow", label: "Flow", section: "layer", min: 0, max: 1, step: 0.01, default: 1, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
|
|
12842
|
+
{ id: "smoothing", label: "Smoothing", section: "layer", min: 0, max: 0.95, step: 0.01, default: 0, decimals: 0, track: plain2, displayScale: 100, unit: "%" }
|
|
12843
|
+
];
|
|
12844
|
+
var MIXER_OPTION_CONTROLS = [
|
|
12845
|
+
{ id: "wet", label: "Wet", section: "layer", min: 0, max: 1, step: 0.01, default: 0.5, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
|
|
12846
|
+
{ id: "load", label: "Load", section: "layer", min: 0, max: 1, step: 0.01, default: 0.5, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
|
|
12847
|
+
{ id: "mix", label: "Mix", section: "layer", min: 0, max: 1, step: 0.01, default: 0.5, decimals: 0, track: plain2, displayScale: 100, unit: "%" }
|
|
12848
|
+
];
|
|
12849
|
+
var MIXER_MIXING_OPTIONS = [
|
|
12850
|
+
{ id: "rgb", label: "RGB", title: "Mix colours as RGB averages" },
|
|
12851
|
+
{ id: "pigment", label: "Pigment", title: "Mix colours like paint: blue and yellow make green" }
|
|
12852
|
+
];
|
|
12853
|
+
var PAINT_TOOLS = [
|
|
12854
|
+
{ id: "move", label: "Move", shortcut: "V" },
|
|
12855
|
+
{ id: "brush", label: "Brush", shortcut: "B" },
|
|
12856
|
+
{ id: "mixer", label: "Mixer", shortcut: "Shift+B" },
|
|
12857
|
+
{ id: "eraser", label: "Eraser", shortcut: "E" }
|
|
12858
|
+
];
|
|
12859
|
+
var isPaintTool = (tool) => tool === "move" || tool === "brush" || tool === "mixer" || tool === "eraser";
|
|
12860
|
+
function rgbUnitToHex(c) {
|
|
12861
|
+
return `#${c.map((v) => Math.round(Math.min(1, Math.max(0, v)) * 255).toString(16).padStart(2, "0")).join("")}`;
|
|
12862
|
+
}
|
|
12863
|
+
function sizeStep(size) {
|
|
12864
|
+
return size < 10 ? 1 : size < 50 ? 5 : size < 100 ? 10 : size < 200 ? 25 : size < 500 ? 50 : 100;
|
|
12865
|
+
}
|
|
12866
|
+
var isTyping = (t) => {
|
|
12867
|
+
const el = t;
|
|
12868
|
+
return Boolean(el && (el.isContentEditable || el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.tagName === "SELECT"));
|
|
12869
|
+
};
|
|
12870
|
+
function handlePaintShortcut(controller, e) {
|
|
12871
|
+
if (e.ctrlKey || e.metaKey || e.altKey || isTyping(e.target)) return false;
|
|
12872
|
+
const brush = controller.getState().paint.brush;
|
|
12873
|
+
const set = (b) => controller.setPaintOptions({ brush: b });
|
|
12874
|
+
const key = e.key;
|
|
12875
|
+
switch (key.toLowerCase()) {
|
|
12876
|
+
case "b":
|
|
12877
|
+
controller.setTool(e.shiftKey || key === "B" ? "mixer" : "brush");
|
|
12878
|
+
return true;
|
|
12879
|
+
case "e":
|
|
12880
|
+
controller.setTool("eraser");
|
|
12881
|
+
return true;
|
|
12882
|
+
case "v":
|
|
12883
|
+
controller.setTool("move");
|
|
12884
|
+
return true;
|
|
12885
|
+
case "d":
|
|
12886
|
+
controller.resetPaintColors();
|
|
12887
|
+
return true;
|
|
12888
|
+
case "x":
|
|
12889
|
+
controller.swapPaintColors();
|
|
12890
|
+
return true;
|
|
12891
|
+
}
|
|
12892
|
+
if (key === "[" || key === "{") {
|
|
12893
|
+
if (e.shiftKey || key === "{") set({ hardness: Math.max(0, Math.round((brush.hardness - 0.25) * 4) / 4) });
|
|
12894
|
+
else set({ size: Math.max(1, brush.size - sizeStep(brush.size - 1)) });
|
|
12895
|
+
return true;
|
|
12896
|
+
}
|
|
12897
|
+
if (key === "]" || key === "}") {
|
|
12898
|
+
if (e.shiftKey || key === "}") set({ hardness: Math.min(1, Math.round((brush.hardness + 0.25) * 4) / 4) });
|
|
12899
|
+
else set({ size: Math.min(MAX_BRUSH_SIZE, brush.size + sizeStep(brush.size)) });
|
|
12900
|
+
return true;
|
|
12901
|
+
}
|
|
12902
|
+
if (/^[0-9]$/.test(key) && isPaintTool(controller.getState().activeTool)) {
|
|
12903
|
+
set({ opacity: key === "0" ? 1 : Number(key) / 10 });
|
|
12904
|
+
return true;
|
|
12905
|
+
}
|
|
12906
|
+
return false;
|
|
12907
|
+
}
|
|
12908
|
+
function pointerPressure(e) {
|
|
12909
|
+
if (e.pointerType === "pen" && typeof e.pressure === "number" && e.pressure > 0) return Math.min(1, e.pressure);
|
|
12910
|
+
return 1;
|
|
12911
|
+
}
|
|
12912
|
+
var PaintGesture = class {
|
|
12913
|
+
constructor(controller) {
|
|
12914
|
+
this.controller = controller;
|
|
12915
|
+
}
|
|
12916
|
+
controller;
|
|
12917
|
+
stroke = null;
|
|
12918
|
+
move = null;
|
|
12919
|
+
/** The last refusal (e.g. "layer is locked"), for the editor to show. */
|
|
12920
|
+
error = null;
|
|
12921
|
+
get active() {
|
|
12922
|
+
return Boolean(this.stroke || this.move);
|
|
12923
|
+
}
|
|
12924
|
+
down(sample) {
|
|
12925
|
+
this.error = null;
|
|
12926
|
+
const tool = this.controller.getState().activeTool;
|
|
12927
|
+
try {
|
|
12928
|
+
if (tool === "move") {
|
|
12929
|
+
const layer = this.controller.getActiveLayer();
|
|
12930
|
+
if (!layer) throw new Error("Select a layer to move in the Layers panel.");
|
|
12931
|
+
if (layer.type !== "pixel") throw new Error(`Only pixel layers move; "${layer.name}" is a ${layer.type} layer.`);
|
|
12932
|
+
if (layer.locks?.all || layer.locks?.position) throw new Error(`Layer "${layer.name}" has its position locked.`);
|
|
12933
|
+
this.move = { id: layer.id, start: { x: sample.x, y: sample.y }, origin: { ...layer.offset }, moved: false };
|
|
12934
|
+
return null;
|
|
12935
|
+
}
|
|
12936
|
+
if (tool === "brush" || tool === "eraser" || tool === "mixer") {
|
|
12937
|
+
this.stroke = this.controller.beginStroke({ tool });
|
|
12938
|
+
this.stroke.push([sample]);
|
|
12939
|
+
return null;
|
|
12940
|
+
}
|
|
12941
|
+
} catch (err) {
|
|
12942
|
+
this.error = err instanceof Error ? err.message : String(err);
|
|
12943
|
+
this.stroke = null;
|
|
12944
|
+
this.move = null;
|
|
12945
|
+
}
|
|
12946
|
+
return this.error;
|
|
12947
|
+
}
|
|
12948
|
+
/** Feeds pointer samples (all coalesced events of one move, in order). */
|
|
12949
|
+
moveTo(samples) {
|
|
12950
|
+
if (!samples.length) return;
|
|
12951
|
+
if (this.stroke) {
|
|
12952
|
+
this.stroke.push(samples);
|
|
12953
|
+
return;
|
|
12954
|
+
}
|
|
12955
|
+
const m = this.move;
|
|
12956
|
+
if (m) {
|
|
12957
|
+
const last = samples[samples.length - 1];
|
|
12958
|
+
const x = m.origin.x + Math.round(last.x - m.start.x);
|
|
12959
|
+
const y = m.origin.y + Math.round(last.y - m.start.y);
|
|
12960
|
+
try {
|
|
12961
|
+
this.controller.offsetLayer(m.id, { x, y }, !m.moved);
|
|
12962
|
+
m.moved = true;
|
|
12963
|
+
} catch (err) {
|
|
12964
|
+
this.error = err instanceof Error ? err.message : String(err);
|
|
12965
|
+
this.move = null;
|
|
12966
|
+
}
|
|
12967
|
+
}
|
|
12968
|
+
}
|
|
12969
|
+
up() {
|
|
12970
|
+
if (this.stroke) {
|
|
12971
|
+
try {
|
|
12972
|
+
this.stroke.end();
|
|
12973
|
+
} catch (err) {
|
|
12974
|
+
this.error = err instanceof Error ? err.message : String(err);
|
|
12975
|
+
}
|
|
12976
|
+
}
|
|
12977
|
+
this.stroke = null;
|
|
12978
|
+
this.move = null;
|
|
12979
|
+
}
|
|
12980
|
+
cancel() {
|
|
12981
|
+
this.stroke?.cancel();
|
|
12982
|
+
this.stroke = null;
|
|
12983
|
+
this.move = null;
|
|
12984
|
+
}
|
|
12985
|
+
};
|
|
12986
|
+
|
|
11767
12987
|
// src/image-editor/paint-tools.ts
|
|
12988
|
+
var READ4 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
11768
12989
|
var WRITE4 = { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false };
|
|
11769
12990
|
var DESTRUCTIVE3 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
|
|
11770
12991
|
var TARGET = {
|
|
@@ -11785,10 +13006,30 @@ var BRUSH = {
|
|
|
11785
13006
|
smoothing: { type: "number", minimum: 0, maximum: 0.95, description: "Point smoothing 0..0.95 (default 0)." },
|
|
11786
13007
|
pressureSize: { type: "boolean", description: "Point pressure scales the size (default true)." },
|
|
11787
13008
|
pressureOpacity: { type: "boolean", description: "Point pressure scales the opacity (default false)." },
|
|
11788
|
-
blend: { type: "string", enum: BLEND_MODES.map((m) => m.id), description: "How paint combines with the layer (default normal)." }
|
|
13009
|
+
blend: { type: "string", enum: BLEND_MODES.map((m) => m.id), description: "How paint combines with the layer (default normal). pigment mixes it in like real paint (blue into yellow gives green) instead of laying it over." }
|
|
13010
|
+
},
|
|
13011
|
+
additionalProperties: false
|
|
13012
|
+
};
|
|
13013
|
+
var MIXER = {
|
|
13014
|
+
type: "object",
|
|
13015
|
+
description: "Mixer Brush settings (tool mixer only); missing fields use the current mixer options (wet 0.5, load 0.5, mix 0.5, loadEachStroke true, cleanEachStroke true, mixing rgb).",
|
|
13016
|
+
properties: {
|
|
13017
|
+
wet: { type: "number", minimum: 0, maximum: 1, description: "How much canvas colour each dab picks up, 0 (dry: paints only the reservoir) .. 1 (default 0.5)." },
|
|
13018
|
+
load: { type: "number", minimum: 0, maximum: 1, description: "How much paint the reservoir holds, 0 .. 1 (default 0.5); below 1 it drains per dab, so long strokes run dry." },
|
|
13019
|
+
mix: { type: "number", minimum: 0, maximum: 1, description: "How far each dab's paint moves from the reservoir towards the picked-up colour, 0 .. 1 (default 0.5)." },
|
|
13020
|
+
loadEachStroke: { type: "boolean", description: "Refill the reservoir with `color` (or the foreground) before the stroke (default true). Off, the brush keeps what is left of its last load." },
|
|
13021
|
+
cleanEachStroke: { type: "boolean", description: "Empty the pickup well after the stroke (default true). Off, the next stroke starts with the colour this one picked up." },
|
|
13022
|
+
mixing: { type: "string", enum: [...MIXING_MODELS], description: "rgb (default): RGB averages. pigment: mixes like paint (Kubelka-Munk), so blue dragged through yellow makes green." }
|
|
11789
13023
|
},
|
|
11790
13024
|
additionalProperties: false
|
|
11791
13025
|
};
|
|
13026
|
+
function mixerLoadResult(load) {
|
|
13027
|
+
return {
|
|
13028
|
+
reservoir: load.reservoir ? rgbUnitToHex(load.reservoir) : null,
|
|
13029
|
+
amount: Math.round(load.amount * 1e3) / 1e3,
|
|
13030
|
+
pickup: load.pickup ? rgbUnitToHex(load.pickup) : null
|
|
13031
|
+
};
|
|
13032
|
+
}
|
|
11792
13033
|
function readTarget(args) {
|
|
11793
13034
|
return { layerId: args.layerId, channel: args.channel };
|
|
11794
13035
|
}
|
|
@@ -11796,11 +13037,11 @@ function createImagePaintTools(controller) {
|
|
|
11796
13037
|
const paintStroke = {
|
|
11797
13038
|
name: "paint_stroke",
|
|
11798
13039
|
title: "Paint a brush stroke",
|
|
11799
|
-
description: "Paints one stroke with the brush (or removes pixels with the eraser
|
|
13040
|
+
description: "Paints one stroke with the brush, smears and blends paint with the Mixer Brush (tool mixer: its reservoir mixes with colour picked up from the layer under each dab, in RGB or pigment mixing), or removes pixels with the eraser, through points in document pixels (optional pressure 0..1 each) on a pixel layer or a layer mask (the mixer paints pixels only). The selection limits the paint; a transparency-locked layer keeps its alpha (the eraser then paints the background colour). One undo step. Returns the layerId, channel, changed bounds and dab count, and for the mixer its mixerLoad after the stroke ({reservoir, amount, pickup}).",
|
|
11800
13041
|
inputSchema: {
|
|
11801
13042
|
type: "object",
|
|
11802
13043
|
properties: {
|
|
11803
|
-
tool: { type: "string", enum: ["brush", "eraser"], description: "brush (default) or
|
|
13044
|
+
tool: { type: "string", enum: ["brush", "eraser", "mixer"], description: "brush (default), eraser, or mixer (the Mixer Brush: picks up and blends the layer colour under each dab)." },
|
|
11804
13045
|
points: {
|
|
11805
13046
|
type: "array",
|
|
11806
13047
|
minItems: 1,
|
|
@@ -11817,21 +13058,66 @@ function createImagePaintTools(controller) {
|
|
|
11817
13058
|
additionalProperties: false
|
|
11818
13059
|
}
|
|
11819
13060
|
},
|
|
11820
|
-
color: {
|
|
13061
|
+
color: {
|
|
13062
|
+
type: "string",
|
|
13063
|
+
description: "Paint colour #rrggbb (default the foreground). On a mask its grey value: #000000 hides, #ffffff reveals. For the mixer, the colour its reservoir is loaded with before the stroke (given, it loads even with loadEachStroke false)."
|
|
13064
|
+
},
|
|
11821
13065
|
brush: BRUSH,
|
|
13066
|
+
mixer: MIXER,
|
|
11822
13067
|
...TARGET
|
|
11823
13068
|
},
|
|
11824
13069
|
required: ["points"],
|
|
11825
13070
|
additionalProperties: false
|
|
11826
13071
|
},
|
|
11827
13072
|
annotations: { title: "Paint a brush stroke", ...WRITE4 },
|
|
11828
|
-
handler: (args) =>
|
|
11829
|
-
tool
|
|
11830
|
-
|
|
11831
|
-
|
|
11832
|
-
|
|
11833
|
-
|
|
11834
|
-
|
|
13073
|
+
handler: (args) => {
|
|
13074
|
+
const tool = args.tool;
|
|
13075
|
+
if (args.mixer !== void 0 && tool !== "mixer") throw new LayerOperationError('mixer settings apply to the Mixer Brush only; pass tool "mixer", or drop mixer.');
|
|
13076
|
+
const result = controller.paintStroke({
|
|
13077
|
+
tool,
|
|
13078
|
+
points: args.points,
|
|
13079
|
+
color: args.color,
|
|
13080
|
+
brush: args.brush,
|
|
13081
|
+
mixer: args.mixer,
|
|
13082
|
+
...readTarget(args)
|
|
13083
|
+
});
|
|
13084
|
+
return result.mixerLoad ? { ...result, mixerLoad: mixerLoadResult(result.mixerLoad) } : result;
|
|
13085
|
+
}
|
|
13086
|
+
};
|
|
13087
|
+
const getMixer = {
|
|
13088
|
+
name: "get_mixer_brush",
|
|
13089
|
+
title: "Get the Mixer Brush",
|
|
13090
|
+
description: "Reads the Mixer Brush: its settings (wet, load, mix, loadEachStroke, cleanEachStroke, mixing) and what it carries now (reservoir #rrggbb or null, amount 0..1 left, pickup #rrggbb or null). Call it before a paint_stroke with tool mixer and loadEachStroke false, to know the brush's starting paint.",
|
|
13091
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
13092
|
+
annotations: { title: "Get the Mixer Brush", ...READ4 },
|
|
13093
|
+
handler: () => {
|
|
13094
|
+
const { mixer, mixerLoad } = controller.getState().paint;
|
|
13095
|
+
return { settings: { ...mixer }, load: mixerLoadResult(mixerLoad) };
|
|
13096
|
+
}
|
|
13097
|
+
};
|
|
13098
|
+
const loadMixerTool = {
|
|
13099
|
+
name: "load_mixer_brush",
|
|
13100
|
+
title: "Load the Mixer Brush",
|
|
13101
|
+
description: "Fills the Mixer Brush reservoir with a colour (amount 1); the picked-up colour is kept. View state, not an undo step. Use it with loadEachStroke false to carry one load across several paint_stroke calls. Returns the load {reservoir, amount, pickup}.",
|
|
13102
|
+
inputSchema: {
|
|
13103
|
+
type: "object",
|
|
13104
|
+
properties: { color: { type: "string", description: "Paint colour #rrggbb (default the foreground)." } },
|
|
13105
|
+
additionalProperties: false
|
|
13106
|
+
},
|
|
13107
|
+
annotations: { title: "Load the Mixer Brush", ...WRITE4, idempotentHint: true },
|
|
13108
|
+
handler: (args) => mixerLoadResult(controller.loadMixerBrush(args.color))
|
|
13109
|
+
};
|
|
13110
|
+
const cleanMixerTool = {
|
|
13111
|
+
name: "clean_mixer_brush",
|
|
13112
|
+
title: "Clean the Mixer Brush",
|
|
13113
|
+
description: "Cleans the Mixer Brush: empties the pickup well so the next stroke starts without canvas colour; with emptyReservoir also drops the loaded paint (the brush then only smears). View state, not an undo step. Returns the load {reservoir, amount, pickup}.",
|
|
13114
|
+
inputSchema: {
|
|
13115
|
+
type: "object",
|
|
13116
|
+
properties: { emptyReservoir: { type: "boolean", description: "Also drop the reservoir (default false)." } },
|
|
13117
|
+
additionalProperties: false
|
|
13118
|
+
},
|
|
13119
|
+
annotations: { title: "Clean the Mixer Brush", ...WRITE4, idempotentHint: true },
|
|
13120
|
+
handler: (args) => mixerLoadResult(args.emptyReservoir === true ? controller.emptyMixerBrush() : controller.cleanMixerBrush())
|
|
11835
13121
|
};
|
|
11836
13122
|
const fill = {
|
|
11837
13123
|
name: "fill_pixels",
|
|
@@ -11842,7 +13128,7 @@ function createImagePaintTools(controller) {
|
|
|
11842
13128
|
properties: {
|
|
11843
13129
|
color: { type: "string", description: "Fill colour #rrggbb or #rrggbbaa (default the foreground)." },
|
|
11844
13130
|
opacity: { type: "number", minimum: 0, maximum: 1, description: "Fill opacity 0..1 (default 1)." },
|
|
11845
|
-
blend: { type: "string", enum: BLEND_MODES.map((m) => m.id), description: "Blend mode (default normal)." },
|
|
13131
|
+
blend: { type: "string", enum: BLEND_MODES.map((m) => m.id), description: "Blend mode (default normal). pigment mixes the colour into the pixels like paint, opacity being the share of new paint." },
|
|
11846
13132
|
useSelection: { type: "boolean", description: "Limit to the selection when there is one (default true)." },
|
|
11847
13133
|
...TARGET
|
|
11848
13134
|
},
|
|
@@ -11873,11 +13159,11 @@ function createImagePaintTools(controller) {
|
|
|
11873
13159
|
annotations: { title: "Clear pixels", ...DESTRUCTIVE3 },
|
|
11874
13160
|
handler: (args) => controller.clearPixels({ opacity: args.opacity, useSelection: args.useSelection, ...readTarget(args) })
|
|
11875
13161
|
};
|
|
11876
|
-
return [paintStroke, fill, clear];
|
|
13162
|
+
return [paintStroke, fill, clear, getMixer, loadMixerTool, cleanMixerTool];
|
|
11877
13163
|
}
|
|
11878
13164
|
|
|
11879
13165
|
// src/image-editor/psd-tools.ts
|
|
11880
|
-
var
|
|
13166
|
+
var READ5 = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
11881
13167
|
var DESTRUCTIVE4 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
|
|
11882
13168
|
var DATA = {
|
|
11883
13169
|
type: "string",
|
|
@@ -11898,7 +13184,7 @@ function createImagePsdTools(controller) {
|
|
|
11898
13184
|
title: "Inspect a PSD",
|
|
11899
13185
|
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.",
|
|
11900
13186
|
inputSchema: { type: "object", properties: { data: DATA }, required: ["data"], additionalProperties: false },
|
|
11901
|
-
annotations: { title: "Inspect a PSD", ...
|
|
13187
|
+
annotations: { title: "Inspect a PSD", ...READ5 },
|
|
11902
13188
|
handler: ({ data }) => {
|
|
11903
13189
|
const res = importPsd(psdBytesFromBase64(data), { skipPixels: true });
|
|
11904
13190
|
return {
|
|
@@ -11914,7 +13200,7 @@ function createImagePsdTools(controller) {
|
|
|
11914
13200
|
const open = {
|
|
11915
13201
|
name: "import_psd",
|
|
11916
13202
|
title: "Open a PSD as the document",
|
|
11917
|
-
description: "Opens a PSD / PSB as the document, replacing the current layers, crop and rotation (one undo step; undo brings the previous document back). Pixel, group and adjustment layers, masks, blend modes, opacity, fill, clipping, visibility and
|
|
13203
|
+
description: "Opens a PSD / PSB as the document, replacing the current layers, crop and rotation (one undo step; undo brings the previous document back). Pixel, group and adjustment layers, masks, blend modes, opacity, fill, clipping, visibility, locks, layer effects (with the patterns and global light of the file) map directly; type, smart-object, fill and shape layers come in as their pixels; unsupported adjustments are left out. Returns the new layer tree with ids, the size and the warnings (each names the affected layers).",
|
|
11918
13204
|
inputSchema: { type: "object", properties: { data: DATA }, required: ["data"], additionalProperties: false },
|
|
11919
13205
|
annotations: { title: "Open a PSD as the document", ...DESTRUCTIVE4 },
|
|
11920
13206
|
handler: ({ data }) => {
|
|
@@ -11933,9 +13219,9 @@ function createImagePsdTools(controller) {
|
|
|
11933
13219
|
const save = {
|
|
11934
13220
|
name: "export_psd",
|
|
11935
13221
|
title: "Export the document as a PSD",
|
|
11936
|
-
description: "Writes the document as an 8-bit RGB PSD (a PSB above 30000 px) with real layers, groups, adjustment layers and
|
|
13222
|
+
description: "Writes the document as an 8-bit RGB PSD (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). 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.",
|
|
11937
13223
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
11938
|
-
annotations: { title: "Export the document as a PSD", ...
|
|
13224
|
+
annotations: { title: "Export the document as a PSD", ...READ5 },
|
|
11939
13225
|
handler: async () => {
|
|
11940
13226
|
const res = await controller.toPsd();
|
|
11941
13227
|
return {
|
|
@@ -12494,9 +13780,9 @@ function applyRegionBlur(imageData, regions, radius = 10) {
|
|
|
12494
13780
|
// src/image-editor/layers/panel.ts
|
|
12495
13781
|
function layerPanelRows(layers, activeId) {
|
|
12496
13782
|
const rows = [];
|
|
12497
|
-
const walk = (
|
|
12498
|
-
for (let i =
|
|
12499
|
-
const layer =
|
|
13783
|
+
const walk = (list2, depth, parentId) => {
|
|
13784
|
+
for (let i = list2.length - 1; i >= 0; i--) {
|
|
13785
|
+
const layer = list2[i];
|
|
12500
13786
|
rows.push({
|
|
12501
13787
|
layer,
|
|
12502
13788
|
depth,
|
|
@@ -12633,145 +13919,6 @@ async function runLayerMenuAction(controller, action) {
|
|
|
12633
13919
|
}
|
|
12634
13920
|
}
|
|
12635
13921
|
|
|
12636
|
-
// src/image-editor/layers/paint-ui.ts
|
|
12637
|
-
var plain2 = { kind: "plain" };
|
|
12638
|
-
var BRUSH_OPTION_CONTROLS = [
|
|
12639
|
-
{ id: "size", label: "Size", section: "layer", min: 1, max: 500, step: 1, default: 20, decimals: 0, track: plain2, unit: "px" },
|
|
12640
|
-
{ id: "hardness", label: "Hardness", section: "layer", min: 0, max: 1, step: 0.01, default: 1, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
|
|
12641
|
-
{ id: "opacity", label: "Opacity", section: "layer", min: 0, max: 1, step: 0.01, default: 1, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
|
|
12642
|
-
{ id: "flow", label: "Flow", section: "layer", min: 0, max: 1, step: 0.01, default: 1, decimals: 0, track: plain2, displayScale: 100, unit: "%" },
|
|
12643
|
-
{ id: "smoothing", label: "Smoothing", section: "layer", min: 0, max: 0.95, step: 0.01, default: 0, decimals: 0, track: plain2, displayScale: 100, unit: "%" }
|
|
12644
|
-
];
|
|
12645
|
-
var PAINT_TOOLS = [
|
|
12646
|
-
{ id: "move", label: "Move", shortcut: "V" },
|
|
12647
|
-
{ id: "brush", label: "Brush", shortcut: "B" },
|
|
12648
|
-
{ id: "eraser", label: "Eraser", shortcut: "E" }
|
|
12649
|
-
];
|
|
12650
|
-
var isPaintTool = (tool) => tool === "move" || tool === "brush" || tool === "eraser";
|
|
12651
|
-
function sizeStep(size) {
|
|
12652
|
-
return size < 10 ? 1 : size < 50 ? 5 : size < 100 ? 10 : size < 200 ? 25 : size < 500 ? 50 : 100;
|
|
12653
|
-
}
|
|
12654
|
-
var isTyping = (t) => {
|
|
12655
|
-
const el = t;
|
|
12656
|
-
return Boolean(el && (el.isContentEditable || el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.tagName === "SELECT"));
|
|
12657
|
-
};
|
|
12658
|
-
function handlePaintShortcut(controller, e) {
|
|
12659
|
-
if (e.ctrlKey || e.metaKey || e.altKey || isTyping(e.target)) return false;
|
|
12660
|
-
const brush = controller.getState().paint.brush;
|
|
12661
|
-
const set = (b) => controller.setPaintOptions({ brush: b });
|
|
12662
|
-
const key = e.key;
|
|
12663
|
-
switch (key.toLowerCase()) {
|
|
12664
|
-
case "b":
|
|
12665
|
-
controller.setTool("brush");
|
|
12666
|
-
return true;
|
|
12667
|
-
case "e":
|
|
12668
|
-
controller.setTool("eraser");
|
|
12669
|
-
return true;
|
|
12670
|
-
case "v":
|
|
12671
|
-
controller.setTool("move");
|
|
12672
|
-
return true;
|
|
12673
|
-
case "d":
|
|
12674
|
-
controller.resetPaintColors();
|
|
12675
|
-
return true;
|
|
12676
|
-
case "x":
|
|
12677
|
-
controller.swapPaintColors();
|
|
12678
|
-
return true;
|
|
12679
|
-
}
|
|
12680
|
-
if (key === "[" || key === "{") {
|
|
12681
|
-
if (e.shiftKey || key === "{") set({ hardness: Math.max(0, Math.round((brush.hardness - 0.25) * 4) / 4) });
|
|
12682
|
-
else set({ size: Math.max(1, brush.size - sizeStep(brush.size - 1)) });
|
|
12683
|
-
return true;
|
|
12684
|
-
}
|
|
12685
|
-
if (key === "]" || key === "}") {
|
|
12686
|
-
if (e.shiftKey || key === "}") set({ hardness: Math.min(1, Math.round((brush.hardness + 0.25) * 4) / 4) });
|
|
12687
|
-
else set({ size: Math.min(MAX_BRUSH_SIZE, brush.size + sizeStep(brush.size)) });
|
|
12688
|
-
return true;
|
|
12689
|
-
}
|
|
12690
|
-
if (/^[0-9]$/.test(key) && isPaintTool(controller.getState().activeTool)) {
|
|
12691
|
-
set({ opacity: key === "0" ? 1 : Number(key) / 10 });
|
|
12692
|
-
return true;
|
|
12693
|
-
}
|
|
12694
|
-
return false;
|
|
12695
|
-
}
|
|
12696
|
-
function pointerPressure(e) {
|
|
12697
|
-
if (e.pointerType === "pen" && typeof e.pressure === "number" && e.pressure > 0) return Math.min(1, e.pressure);
|
|
12698
|
-
return 1;
|
|
12699
|
-
}
|
|
12700
|
-
var PaintGesture = class {
|
|
12701
|
-
constructor(controller) {
|
|
12702
|
-
this.controller = controller;
|
|
12703
|
-
}
|
|
12704
|
-
controller;
|
|
12705
|
-
stroke = null;
|
|
12706
|
-
move = null;
|
|
12707
|
-
/** The last refusal (e.g. "layer is locked"), for the editor to show. */
|
|
12708
|
-
error = null;
|
|
12709
|
-
get active() {
|
|
12710
|
-
return Boolean(this.stroke || this.move);
|
|
12711
|
-
}
|
|
12712
|
-
down(sample) {
|
|
12713
|
-
this.error = null;
|
|
12714
|
-
const tool = this.controller.getState().activeTool;
|
|
12715
|
-
try {
|
|
12716
|
-
if (tool === "move") {
|
|
12717
|
-
const layer = this.controller.getActiveLayer();
|
|
12718
|
-
if (!layer) throw new Error("Select a layer to move in the Layers panel.");
|
|
12719
|
-
if (layer.type !== "pixel") throw new Error(`Only pixel layers move; "${layer.name}" is a ${layer.type} layer.`);
|
|
12720
|
-
if (layer.locks?.all || layer.locks?.position) throw new Error(`Layer "${layer.name}" has its position locked.`);
|
|
12721
|
-
this.move = { id: layer.id, start: { x: sample.x, y: sample.y }, origin: { ...layer.offset }, moved: false };
|
|
12722
|
-
return null;
|
|
12723
|
-
}
|
|
12724
|
-
if (tool === "brush" || tool === "eraser") {
|
|
12725
|
-
this.stroke = this.controller.beginStroke({ tool });
|
|
12726
|
-
this.stroke.push([sample]);
|
|
12727
|
-
return null;
|
|
12728
|
-
}
|
|
12729
|
-
} catch (err) {
|
|
12730
|
-
this.error = err instanceof Error ? err.message : String(err);
|
|
12731
|
-
this.stroke = null;
|
|
12732
|
-
this.move = null;
|
|
12733
|
-
}
|
|
12734
|
-
return this.error;
|
|
12735
|
-
}
|
|
12736
|
-
/** Feeds pointer samples (all coalesced events of one move, in order). */
|
|
12737
|
-
moveTo(samples) {
|
|
12738
|
-
if (!samples.length) return;
|
|
12739
|
-
if (this.stroke) {
|
|
12740
|
-
this.stroke.push(samples);
|
|
12741
|
-
return;
|
|
12742
|
-
}
|
|
12743
|
-
const m = this.move;
|
|
12744
|
-
if (m) {
|
|
12745
|
-
const last = samples[samples.length - 1];
|
|
12746
|
-
const x = m.origin.x + Math.round(last.x - m.start.x);
|
|
12747
|
-
const y = m.origin.y + Math.round(last.y - m.start.y);
|
|
12748
|
-
try {
|
|
12749
|
-
this.controller.offsetLayer(m.id, { x, y }, !m.moved);
|
|
12750
|
-
m.moved = true;
|
|
12751
|
-
} catch (err) {
|
|
12752
|
-
this.error = err instanceof Error ? err.message : String(err);
|
|
12753
|
-
this.move = null;
|
|
12754
|
-
}
|
|
12755
|
-
}
|
|
12756
|
-
}
|
|
12757
|
-
up() {
|
|
12758
|
-
if (this.stroke) {
|
|
12759
|
-
try {
|
|
12760
|
-
this.stroke.end();
|
|
12761
|
-
} catch (err) {
|
|
12762
|
-
this.error = err instanceof Error ? err.message : String(err);
|
|
12763
|
-
}
|
|
12764
|
-
}
|
|
12765
|
-
this.stroke = null;
|
|
12766
|
-
this.move = null;
|
|
12767
|
-
}
|
|
12768
|
-
cancel() {
|
|
12769
|
-
this.stroke?.cancel();
|
|
12770
|
-
this.stroke = null;
|
|
12771
|
-
this.move = null;
|
|
12772
|
-
}
|
|
12773
|
-
};
|
|
12774
|
-
|
|
12775
13922
|
// src/image-editor/psd/ui.ts
|
|
12776
13923
|
var PSD_FILE_ACCEPT = ".psd,.psb,image/vnd.adobe.photoshop,application/x-photoshop";
|
|
12777
13924
|
function blobBytes(file) {
|
|
@@ -12822,6 +13969,8 @@ export {
|
|
|
12822
13969
|
DEFAULT_ADJUSTMENTS,
|
|
12823
13970
|
DEFAULT_BRUSH,
|
|
12824
13971
|
DEFAULT_DEPTH_MASK,
|
|
13972
|
+
DEFAULT_GLOBAL_LIGHT,
|
|
13973
|
+
DEFAULT_MIXER,
|
|
12825
13974
|
DEFAULT_REFINE_EDGE_PARAMS,
|
|
12826
13975
|
DEFAULT_SELECTION_TOOL_OPTIONS,
|
|
12827
13976
|
DEFAULT_TRANSFORM,
|
|
@@ -12829,6 +13978,7 @@ export {
|
|
|
12829
13978
|
DIRECT_GAUSSIAN_MAX_SIGMA,
|
|
12830
13979
|
DabWalker,
|
|
12831
13980
|
EMPTY_DISTANCE,
|
|
13981
|
+
EMPTY_MIXER_LOAD,
|
|
12832
13982
|
GUIDED_FILTER_EPS,
|
|
12833
13983
|
HISTOGRAM_BINS,
|
|
12834
13984
|
HUE_RANGE_LABELS,
|
|
@@ -12840,15 +13990,25 @@ export {
|
|
|
12840
13990
|
ImageEditorRenderer,
|
|
12841
13991
|
LATEST_PROCESS_VERSION,
|
|
12842
13992
|
LAYERED_SCHEMA_VERSION,
|
|
13993
|
+
LAYER_EFFECT_KINDS,
|
|
12843
13994
|
LEGACY_PROCESS_VERSION,
|
|
12844
13995
|
LUMA_709,
|
|
12845
13996
|
LayerCompositor,
|
|
13997
|
+
LayerEffectError,
|
|
12846
13998
|
LayerOperationError,
|
|
12847
13999
|
MAX_BRUSH_SIZE,
|
|
14000
|
+
MAX_EFFECT_REACH,
|
|
12848
14001
|
MAX_KELVIN,
|
|
12849
14002
|
MIN_KELVIN,
|
|
12850
14003
|
MIRED_PER_TEMPERATURE_UNIT,
|
|
14004
|
+
MIXER_DRAIN,
|
|
14005
|
+
MIXER_MIXING_OPTIONS,
|
|
14006
|
+
MIXER_OPTION_CONTROLS,
|
|
14007
|
+
MIXER_PICKUP_RATE,
|
|
14008
|
+
MIXING_MODELS,
|
|
14009
|
+
MixerStroke,
|
|
12851
14010
|
PAINT_TOOLS,
|
|
14011
|
+
PATTERN_BLOCK_KEYS,
|
|
12852
14012
|
PIGMENT_BANDS,
|
|
12853
14013
|
PIGMENT_LAMBDA_MIN,
|
|
12854
14014
|
PIGMENT_LAMBDA_STEP,
|
|
@@ -12857,6 +14017,8 @@ export {
|
|
|
12857
14017
|
PSD_FILE_ACCEPT,
|
|
12858
14018
|
PaintGesture,
|
|
12859
14019
|
PaintStrokeSession,
|
|
14020
|
+
PatternPlacement,
|
|
14021
|
+
PatternTile,
|
|
12860
14022
|
PsdError,
|
|
12861
14023
|
REFINE_EDGE_CONTROLS,
|
|
12862
14024
|
SELECTION_MODES,
|
|
@@ -12916,6 +14078,8 @@ export {
|
|
|
12916
14078
|
bradfordAdaptation,
|
|
12917
14079
|
buildLayerMaskPlanes,
|
|
12918
14080
|
buildToneLut,
|
|
14081
|
+
builtinPattern,
|
|
14082
|
+
builtinPatternIds,
|
|
12919
14083
|
bytesToBase64,
|
|
12920
14084
|
calculateCropDrag,
|
|
12921
14085
|
canvasCompositeOperation,
|
|
@@ -12923,6 +14087,7 @@ export {
|
|
|
12923
14087
|
changedTiles,
|
|
12924
14088
|
clampControlValue,
|
|
12925
14089
|
clampCropToImage,
|
|
14090
|
+
cleanMixer,
|
|
12926
14091
|
collectTiles,
|
|
12927
14092
|
colorBalanceChannel,
|
|
12928
14093
|
colorRange,
|
|
@@ -12963,6 +14128,8 @@ export {
|
|
|
12963
14128
|
decodePngDataUrl,
|
|
12964
14129
|
decodeSelectionRle,
|
|
12965
14130
|
defaultAdjustmentParams,
|
|
14131
|
+
defaultEffectGradient,
|
|
14132
|
+
defaultLayerEffect,
|
|
12966
14133
|
deflate,
|
|
12967
14134
|
deflateRaw,
|
|
12968
14135
|
depthMaskAlpha,
|
|
@@ -12973,6 +14140,7 @@ export {
|
|
|
12973
14140
|
deserializeEffects,
|
|
12974
14141
|
deserializeLayer,
|
|
12975
14142
|
deserializeLayers,
|
|
14143
|
+
deserializePatterns,
|
|
12976
14144
|
deserializeSelection,
|
|
12977
14145
|
developFilter,
|
|
12978
14146
|
developFromAdjustments,
|
|
@@ -12983,6 +14151,11 @@ export {
|
|
|
12983
14151
|
dropZone,
|
|
12984
14152
|
duplicateLayer,
|
|
12985
14153
|
edgeWidth,
|
|
14154
|
+
effectColorHex,
|
|
14155
|
+
effectReach,
|
|
14156
|
+
effectsFingerprint,
|
|
14157
|
+
effectsMargin,
|
|
14158
|
+
effectsScale,
|
|
12986
14159
|
ellipsePoints,
|
|
12987
14160
|
ellipseSelection,
|
|
12988
14161
|
emptyHistogram,
|
|
@@ -13016,8 +14189,10 @@ export {
|
|
|
13016
14189
|
guidePlanes,
|
|
13017
14190
|
guidedFilterColor,
|
|
13018
14191
|
handlePaintShortcut,
|
|
14192
|
+
hasActiveEffects,
|
|
13019
14193
|
hasResampledLegacyMask,
|
|
13020
14194
|
hashMaskContent,
|
|
14195
|
+
hashNoise,
|
|
13021
14196
|
hexToRgb,
|
|
13022
14197
|
histogramPath,
|
|
13023
14198
|
hitTestAnnotation,
|
|
@@ -13043,6 +14218,7 @@ export {
|
|
|
13043
14218
|
isAdjustmentKind,
|
|
13044
14219
|
isBlendMode,
|
|
13045
14220
|
isDefaultDepthMask,
|
|
14221
|
+
isLayerEffectKind,
|
|
13046
14222
|
isLayerLocked,
|
|
13047
14223
|
isLegacyShape,
|
|
13048
14224
|
isPaintTool,
|
|
@@ -13055,6 +14231,8 @@ export {
|
|
|
13055
14231
|
isSpatialFilter,
|
|
13056
14232
|
kelvinToTemperature,
|
|
13057
14233
|
layerBadge,
|
|
14234
|
+
layerEffectFields,
|
|
14235
|
+
layerEffectLabel,
|
|
13058
14236
|
layerEffectsOf,
|
|
13059
14237
|
layerLabel,
|
|
13060
14238
|
layerMaskCoverage,
|
|
@@ -13075,19 +14253,25 @@ export {
|
|
|
13075
14253
|
linearSrgbToXy,
|
|
13076
14254
|
linearToSrgb,
|
|
13077
14255
|
loadImageEditorDocument,
|
|
14256
|
+
loadMixer,
|
|
13078
14257
|
magicWand,
|
|
13079
14258
|
mapCropHandle,
|
|
13080
14259
|
mapLayer,
|
|
13081
14260
|
maskSummary,
|
|
14261
|
+
mergeLayerEffect,
|
|
13082
14262
|
migrateSerializedImageEditorState,
|
|
13083
14263
|
migrateToLayeredState,
|
|
14264
|
+
mixPigment8,
|
|
14265
|
+
mixPigmentRgb,
|
|
13084
14266
|
mixPigments,
|
|
14267
|
+
mixerLoadResult,
|
|
13085
14268
|
modifySelection,
|
|
13086
14269
|
moveCurvePoint,
|
|
13087
14270
|
moveLayer,
|
|
13088
14271
|
mulMatrix3,
|
|
13089
14272
|
needsLegacyBase,
|
|
13090
14273
|
neutralHueRange,
|
|
14274
|
+
newEffectId,
|
|
13091
14275
|
newLayerId,
|
|
13092
14276
|
normalizeAdjustmentLayer,
|
|
13093
14277
|
normalizeAdjustmentLayers,
|
|
@@ -13095,7 +14279,9 @@ export {
|
|
|
13095
14279
|
normalizeBlendMode,
|
|
13096
14280
|
normalizeBrush,
|
|
13097
14281
|
normalizeFilter,
|
|
14282
|
+
normalizeLayerEffects,
|
|
13098
14283
|
normalizeLayerMask,
|
|
14284
|
+
normalizeMixer,
|
|
13099
14285
|
normalizeProcessVersion,
|
|
13100
14286
|
normalizeRect,
|
|
13101
14287
|
normalizeRefineEdgeParams,
|
|
@@ -13107,6 +14293,10 @@ export {
|
|
|
13107
14293
|
parseBlend,
|
|
13108
14294
|
parseColor,
|
|
13109
14295
|
parseDataUrl,
|
|
14296
|
+
parseEffectColor,
|
|
14297
|
+
parseLfx2,
|
|
14298
|
+
parseLrfx,
|
|
14299
|
+
parsePatternBlock,
|
|
13110
14300
|
parseSelectionMode,
|
|
13111
14301
|
parseVersionedDescriptor,
|
|
13112
14302
|
photoFilterMatrix,
|
|
@@ -13124,6 +14314,7 @@ export {
|
|
|
13124
14314
|
projectQuarterTurn,
|
|
13125
14315
|
psdBytesFromBase64,
|
|
13126
14316
|
psdFileName,
|
|
14317
|
+
psdPatternToEffectPattern,
|
|
13127
14318
|
psdToLayers,
|
|
13128
14319
|
quickMaskPixels,
|
|
13129
14320
|
rangeLightness,
|
|
@@ -13143,6 +14334,7 @@ export {
|
|
|
13143
14334
|
removeLayer,
|
|
13144
14335
|
removeLayerEffect,
|
|
13145
14336
|
removeLayerFilter,
|
|
14337
|
+
renderLayerEffects,
|
|
13146
14338
|
renderLayeredBase,
|
|
13147
14339
|
requireLayer,
|
|
13148
14340
|
requireUnlocked,
|
|
@@ -13153,9 +14345,11 @@ export {
|
|
|
13153
14345
|
rgbToGray,
|
|
13154
14346
|
rgbToHsl,
|
|
13155
14347
|
rgbToReflectance,
|
|
14348
|
+
rgbUnitToHex,
|
|
13156
14349
|
runLayerMenuAction,
|
|
13157
14350
|
sampleLut,
|
|
13158
14351
|
sampleToneLut,
|
|
14352
|
+
scaleLayerEffect,
|
|
13159
14353
|
selectionArea,
|
|
13160
14354
|
selectionFromCoverage,
|
|
13161
14355
|
selectionFromPixels,
|
|
@@ -13168,6 +14362,7 @@ export {
|
|
|
13168
14362
|
serializeLayer,
|
|
13169
14363
|
serializeLayers,
|
|
13170
14364
|
serializeLayersExternal,
|
|
14365
|
+
serializePatterns,
|
|
13171
14366
|
serializeSelection,
|
|
13172
14367
|
setControlPath,
|
|
13173
14368
|
setLayerEffects,
|
|
@@ -13209,6 +14404,9 @@ export {
|
|
|
13209
14404
|
updateLayerFilter,
|
|
13210
14405
|
updateLayerMaskProps,
|
|
13211
14406
|
uvToXy,
|
|
14407
|
+
validateEffectGradient,
|
|
14408
|
+
validateLayerEffect,
|
|
14409
|
+
validateLayerEffects,
|
|
13212
14410
|
versionedDescriptorBytes,
|
|
13213
14411
|
vibranceLinear,
|
|
13214
14412
|
vibrancePixel,
|
|
@@ -13218,6 +14416,8 @@ export {
|
|
|
13218
14416
|
whiteBalanceMatrix,
|
|
13219
14417
|
withNewIds,
|
|
13220
14418
|
writeAdjustmentBlock,
|
|
14419
|
+
writeLfx2,
|
|
14420
|
+
writePatternBlock,
|
|
13221
14421
|
writePsd,
|
|
13222
14422
|
writeRegion,
|
|
13223
14423
|
xyToTempTint,
|