@pixerate/editor 0.95.0 → 0.97.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 +1 -0
- package/dist/{chunk-T6H3JDGA.cjs → chunk-4AB5BHYK.cjs} +254 -63
- package/dist/chunk-4AB5BHYK.cjs.map +1 -0
- package/dist/{chunk-E2ILRYTJ.js → chunk-OEVSVA6V.js} +231 -40
- package/dist/chunk-OEVSVA6V.js.map +1 -0
- package/dist/image-editor/index.cjs +1559 -385
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +172 -5
- package/dist/image-editor/index.d.ts +172 -5
- package/dist/image-editor/index.js +1329 -155
- package/dist/image-editor/index.js.map +1 -1
- package/dist/paint/index.d.cts +1 -1
- package/dist/paint/index.d.ts +1 -1
- package/dist/raster/index.cjs +4 -2
- package/dist/raster/index.cjs.map +1 -1
- package/dist/raster/index.d.cts +839 -5
- package/dist/raster/index.d.ts +839 -5
- package/dist/raster/index.js +3 -1
- package/dist/{surface-CgAiXVI3.d.cts → surface-DIA-XatS.d.cts} +1 -1
- package/dist/{surface-CgAiXVI3.d.ts → surface-DIA-XatS.d.ts} +1 -1
- package/package.json +1 -1
- package/skills/pixerate-image-editor/SKILL.md +13 -4
- package/dist/chunk-E2ILRYTJ.js.map +0 -1
- package/dist/chunk-T6H3JDGA.cjs.map +0 -1
- package/dist/zlib-B3LIoxF-.d.cts +0 -796
- package/dist/zlib-eiTEEj1z.d.ts +0 -796
|
@@ -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,29 +23,42 @@ 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-OEVSVA6V.js";
|
|
46
62
|
import {
|
|
47
63
|
CoverageMap,
|
|
48
64
|
DEFAULT_BRUSH,
|
|
@@ -308,8 +324,8 @@ function whiteBalanceFromSample(rgb) {
|
|
|
308
324
|
if (lin[0] + lin[1] + lin[2] <= 1e-6) return { temperature: 0, tint: 0 };
|
|
309
325
|
const src = linearSrgbToXy(applyMatrix3(AS_SHOT_WB_INV, lin));
|
|
310
326
|
const { kelvin, tint } = xyToTempTint(src);
|
|
311
|
-
const
|
|
312
|
-
return { temperature:
|
|
327
|
+
const clamp4 = (v) => Math.max(-100, Math.min(100, v));
|
|
328
|
+
return { temperature: clamp4(kelvinToTemperature(kelvin)), tint: clamp4(tint) };
|
|
313
329
|
}
|
|
314
330
|
var TONE_LUT_SIZE = 4096;
|
|
315
331
|
var GREY = 0.18;
|
|
@@ -541,20 +557,20 @@ function hitTestCrop(canvasPt, state, vpWidth, vpHeight, handleThreshold = 14, c
|
|
|
541
557
|
}
|
|
542
558
|
function hitTestCropRect(canvasPt, rect, handleThreshold) {
|
|
543
559
|
const { x, y, width: w, height: h } = rect;
|
|
544
|
-
const
|
|
560
|
+
const px2 = canvasPt.x;
|
|
545
561
|
const py = canvasPt.y;
|
|
546
562
|
const dist = (x1, y1, x2, y2) => Math.hypot(x1 - x2, y1 - y2);
|
|
547
|
-
if (dist(
|
|
548
|
-
if (dist(
|
|
549
|
-
if (dist(
|
|
550
|
-
if (dist(
|
|
551
|
-
const nearX =
|
|
563
|
+
if (dist(px2, py, x, y) <= handleThreshold) return "nw";
|
|
564
|
+
if (dist(px2, py, x + w, y) <= handleThreshold) return "ne";
|
|
565
|
+
if (dist(px2, py, x + w, y + h) <= handleThreshold) return "se";
|
|
566
|
+
if (dist(px2, py, x, y + h) <= handleThreshold) return "sw";
|
|
567
|
+
const nearX = px2 >= x - handleThreshold && px2 <= x + w + handleThreshold;
|
|
552
568
|
const nearY = py >= y - handleThreshold && py <= y + h + handleThreshold;
|
|
553
569
|
if (nearX && Math.abs(py - y) <= handleThreshold) return "n";
|
|
554
570
|
if (nearX && Math.abs(py - (y + h)) <= handleThreshold) return "s";
|
|
555
|
-
if (nearY && Math.abs(
|
|
556
|
-
if (nearY && Math.abs(
|
|
557
|
-
if (
|
|
571
|
+
if (nearY && Math.abs(px2 - x) <= handleThreshold) return "w";
|
|
572
|
+
if (nearY && Math.abs(px2 - (x + w)) <= handleThreshold) return "e";
|
|
573
|
+
if (px2 >= x && px2 <= x + w && py >= y && py <= y + h) {
|
|
558
574
|
return "inside";
|
|
559
575
|
}
|
|
560
576
|
return null;
|
|
@@ -903,21 +919,21 @@ function toBase64(bytes, length) {
|
|
|
903
919
|
parts.push(chunk);
|
|
904
920
|
return parts.join("");
|
|
905
921
|
}
|
|
906
|
-
function fromBase64(
|
|
907
|
-
if (
|
|
922
|
+
function fromBase64(text2) {
|
|
923
|
+
if (text2.length % 4 !== 0) throw new Error("Invalid base64 length");
|
|
908
924
|
let pad = 0;
|
|
909
|
-
if (
|
|
910
|
-
else if (
|
|
911
|
-
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);
|
|
912
928
|
let o = 0;
|
|
913
929
|
const digit = (i, k) => {
|
|
914
|
-
const ch =
|
|
915
|
-
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;
|
|
916
932
|
const idx = ch < 128 ? B64_INDEX[ch] : -1;
|
|
917
933
|
if (idx < 0) throw new Error("Invalid base64 character");
|
|
918
934
|
return idx;
|
|
919
935
|
};
|
|
920
|
-
for (let i = 0; i <
|
|
936
|
+
for (let i = 0; i < text2.length; i += 4) {
|
|
921
937
|
const n = digit(i, 0) << 18 | digit(i, 1) << 12 | digit(i, 2) << 6 | digit(i, 3);
|
|
922
938
|
if (o < out.length) out[o++] = n >> 16 & 255;
|
|
923
939
|
if (o < out.length) out[o++] = n >> 8 & 255;
|
|
@@ -1582,13 +1598,13 @@ function hslToRgb(h, s, l) {
|
|
|
1582
1598
|
};
|
|
1583
1599
|
return [f(h + 1 / 3), f(h), f(h - 1 / 3)];
|
|
1584
1600
|
}
|
|
1585
|
-
function hueRangeWeight(range,
|
|
1601
|
+
function hueRangeWeight(range, deg2) {
|
|
1586
1602
|
const [a, b0, c0, d0] = range.bounds;
|
|
1587
1603
|
const rel = (x) => Number.isFinite(x) && Number.isFinite(a) ? remEuclid(x - a, 360) : 0;
|
|
1588
1604
|
const b = rel(b0);
|
|
1589
1605
|
const c = Math.max(rel(c0), b);
|
|
1590
1606
|
const d = Math.max(rel(d0), c);
|
|
1591
|
-
const h = rel(
|
|
1607
|
+
const h = rel(deg2);
|
|
1592
1608
|
if (h <= b) return b <= 0 ? 1 : h / b;
|
|
1593
1609
|
if (h <= c) return 1;
|
|
1594
1610
|
if (h <= d) return d <= c ? 1 : (d - h) / (d - c);
|
|
@@ -1600,9 +1616,9 @@ function hueRangeTables(ranges) {
|
|
|
1600
1616
|
const t = [new Float32Array(n), new Float32Array(n), new Float32Array(n)];
|
|
1601
1617
|
const active = ranges.filter((r) => !isNeutralRange(r));
|
|
1602
1618
|
for (let k = 0; k < n; k++) {
|
|
1603
|
-
const
|
|
1619
|
+
const deg2 = k / (n - 1) * 360;
|
|
1604
1620
|
for (const r of active) {
|
|
1605
|
-
const w = hueRangeWeight(r,
|
|
1621
|
+
const w = hueRangeWeight(r, deg2);
|
|
1606
1622
|
t[0][k] += w * r.hue;
|
|
1607
1623
|
t[1][k] += w * r.saturation / 100;
|
|
1608
1624
|
t[2][k] += w * r.lightness / 100;
|
|
@@ -2165,8 +2181,8 @@ function applyFastBoxBlur(imageData, radius) {
|
|
|
2165
2181
|
let r = 0, g = 0, b = 0, a = 0;
|
|
2166
2182
|
let count = 0;
|
|
2167
2183
|
for (let k = -radius; k <= radius; k++) {
|
|
2168
|
-
const
|
|
2169
|
-
const idx = (y * w +
|
|
2184
|
+
const px2 = Math.min(w - 1, Math.max(0, x + k));
|
|
2185
|
+
const idx = (y * w + px2) * 4;
|
|
2170
2186
|
r += src[idx];
|
|
2171
2187
|
g += src[idx + 1];
|
|
2172
2188
|
b += src[idx + 2];
|
|
@@ -2428,10 +2444,10 @@ function depthMaskAlpha(depth, options) {
|
|
|
2428
2444
|
}
|
|
2429
2445
|
var filterSpecCache = /* @__PURE__ */ new WeakMap();
|
|
2430
2446
|
function pixelFilterSpec(layer) {
|
|
2431
|
-
const
|
|
2432
|
-
if (!
|
|
2433
|
-
if (filterSpecCache.has(
|
|
2434
|
-
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);
|
|
2435
2451
|
let spec = null;
|
|
2436
2452
|
if (filters.length) {
|
|
2437
2453
|
const spatial = filters.some(isSpatialFilter);
|
|
@@ -2446,7 +2462,7 @@ function pixelFilterSpec(layer) {
|
|
|
2446
2462
|
}
|
|
2447
2463
|
};
|
|
2448
2464
|
}
|
|
2449
|
-
filterSpecCache.set(
|
|
2465
|
+
filterSpecCache.set(list2, spec);
|
|
2450
2466
|
return spec;
|
|
2451
2467
|
}
|
|
2452
2468
|
var PHOTO_PROFILE = {
|
|
@@ -2663,8 +2679,8 @@ function legacyAdjustmentLayer(node, width, height) {
|
|
|
2663
2679
|
}
|
|
2664
2680
|
function developTarget(layers, activeLayerId) {
|
|
2665
2681
|
const pixels = [];
|
|
2666
|
-
const walk = (
|
|
2667
|
-
for (const l of
|
|
2682
|
+
const walk = (list2) => {
|
|
2683
|
+
for (const l of list2) {
|
|
2668
2684
|
if (l.type === "pixel") pixels.push(l);
|
|
2669
2685
|
else if (l.type === "group") walk(l.children);
|
|
2670
2686
|
}
|
|
@@ -2698,8 +2714,8 @@ function legacyView(stack, width, height, activeLayerId = null) {
|
|
|
2698
2714
|
};
|
|
2699
2715
|
const adjNodes = [];
|
|
2700
2716
|
const annotations = [];
|
|
2701
|
-
const walk = (
|
|
2702
|
-
for (const l of
|
|
2717
|
+
const walk = (list2) => {
|
|
2718
|
+
for (const l of list2) {
|
|
2703
2719
|
if (l.type === "adjustment") adjNodes.push(l);
|
|
2704
2720
|
else if (l.type === "annotation") annotations.push(l.annotation);
|
|
2705
2721
|
else if (l.type === "group") walk(l.children);
|
|
@@ -3121,13 +3137,13 @@ var ImageEditorRenderer = class {
|
|
|
3121
3137
|
ctx.save();
|
|
3122
3138
|
try {
|
|
3123
3139
|
applyImageProjectionToContext(ctx, projection);
|
|
3124
|
-
const
|
|
3140
|
+
const px2 = 1 / (projection.scale || 1);
|
|
3125
3141
|
ctx.beginPath();
|
|
3126
|
-
ctx.arc(b.x, b.y, Math.max(
|
|
3127
|
-
ctx.lineWidth = 2 *
|
|
3142
|
+
ctx.arc(b.x, b.y, Math.max(px2, b.diameter / 2), 0, Math.PI * 2);
|
|
3143
|
+
ctx.lineWidth = 2 * px2;
|
|
3128
3144
|
ctx.strokeStyle = "rgba(0,0,0,0.6)";
|
|
3129
3145
|
ctx.stroke();
|
|
3130
|
-
ctx.lineWidth =
|
|
3146
|
+
ctx.lineWidth = px2;
|
|
3131
3147
|
ctx.strokeStyle = "#ffffff";
|
|
3132
3148
|
ctx.stroke();
|
|
3133
3149
|
} catch {
|
|
@@ -3410,32 +3426,32 @@ var ImageEditorRenderer = class {
|
|
|
3410
3426
|
this.outlineCache.set(selection, segs);
|
|
3411
3427
|
}
|
|
3412
3428
|
if (!segs.length) return;
|
|
3413
|
-
const
|
|
3429
|
+
const px2 = 1 / (scale || 1);
|
|
3414
3430
|
ctx.beginPath();
|
|
3415
3431
|
for (let i = 0; i < segs.length; i += 4) {
|
|
3416
3432
|
ctx.moveTo(segs[i], segs[i + 1]);
|
|
3417
3433
|
ctx.lineTo(segs[i + 2], segs[i + 3]);
|
|
3418
3434
|
}
|
|
3419
|
-
ctx.lineWidth =
|
|
3435
|
+
ctx.lineWidth = px2;
|
|
3420
3436
|
ctx.strokeStyle = "#ffffff";
|
|
3421
3437
|
ctx.stroke();
|
|
3422
3438
|
if (typeof ctx.setLineDash === "function") {
|
|
3423
|
-
ctx.setLineDash([4 *
|
|
3424
|
-
ctx.lineDashOffset = -offset *
|
|
3439
|
+
ctx.setLineDash([4 * px2, 4 * px2]);
|
|
3440
|
+
ctx.lineDashOffset = -offset * px2;
|
|
3425
3441
|
}
|
|
3426
3442
|
ctx.strokeStyle = "#000000";
|
|
3427
3443
|
ctx.stroke();
|
|
3428
3444
|
if (typeof ctx.setLineDash === "function") ctx.setLineDash([]);
|
|
3429
3445
|
}
|
|
3430
3446
|
drawDraftPath(ctx, points, scale) {
|
|
3431
|
-
const
|
|
3447
|
+
const px2 = 1 / (scale || 1);
|
|
3432
3448
|
ctx.beginPath();
|
|
3433
3449
|
points.forEach((p, i) => i === 0 ? ctx.moveTo(p.x, p.y) : ctx.lineTo(p.x, p.y));
|
|
3434
3450
|
ctx.closePath();
|
|
3435
|
-
ctx.lineWidth =
|
|
3451
|
+
ctx.lineWidth = px2;
|
|
3436
3452
|
ctx.strokeStyle = "#ffffff";
|
|
3437
3453
|
ctx.stroke();
|
|
3438
|
-
if (typeof ctx.setLineDash === "function") ctx.setLineDash([4 *
|
|
3454
|
+
if (typeof ctx.setLineDash === "function") ctx.setLineDash([4 * px2, 4 * px2]);
|
|
3439
3455
|
ctx.strokeStyle = "#000000";
|
|
3440
3456
|
ctx.stroke();
|
|
3441
3457
|
if (typeof ctx.setLineDash === "function") ctx.setLineDash([]);
|
|
@@ -4281,8 +4297,8 @@ var gfRadius = (p) => Math.min(64, Math.max(1, Math.ceil(p.radius)));
|
|
|
4281
4297
|
var smoothSigma = (p) => p.smooth / 100 * 4;
|
|
4282
4298
|
var featherSigma = (p) => p.feather / 2;
|
|
4283
4299
|
function shiftAmount(p) {
|
|
4284
|
-
const
|
|
4285
|
-
return Number.isFinite(
|
|
4300
|
+
const px2 = p.shiftEdge / 100 * Math.max(p.radius, 4);
|
|
4301
|
+
return Number.isFinite(px2) ? Math.min(64, Math.max(-64, px2)) : 0;
|
|
4286
4302
|
}
|
|
4287
4303
|
function refineEdgeHalo(params = {}) {
|
|
4288
4304
|
const p = normalizeRefineEdgeParams(params);
|
|
@@ -4490,6 +4506,23 @@ function refineSelection(pixels, mask, params = {}) {
|
|
|
4490
4506
|
|
|
4491
4507
|
// src/image-editor/layers/serialize.ts
|
|
4492
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
|
+
}
|
|
4493
4526
|
var encodedCache = /* @__PURE__ */ new WeakMap();
|
|
4494
4527
|
function encodeSurface(surface) {
|
|
4495
4528
|
if (encodedCache.has(surface)) return encodedCache.get(surface);
|
|
@@ -4669,8 +4702,8 @@ function readSurface(ref, x, y, channels, defaultValue, ctx, what) {
|
|
|
4669
4702
|
return emptySurface(channels, defaultValue);
|
|
4670
4703
|
}
|
|
4671
4704
|
try {
|
|
4672
|
-
const
|
|
4673
|
-
return surfaceFromPixels(
|
|
4705
|
+
const px2 = (ctx.decodeAsset ?? decodePngDataUrl)(url, channels);
|
|
4706
|
+
return surfaceFromPixels(px2, channels, Math.round(num(x, -1e7, 1e7, 0)), Math.round(num(y, -1e7, 1e7, 0)), defaultValue);
|
|
4674
4707
|
} catch (err) {
|
|
4675
4708
|
ctx.problems.push(`${what}: asset "${ref}" could not be decoded (${err.message})`);
|
|
4676
4709
|
return emptySurface(channels, defaultValue);
|
|
@@ -4785,7 +4818,7 @@ function deserializeLayer(raw, ctx) {
|
|
|
4785
4818
|
function deserializeLayers(raw, ctx) {
|
|
4786
4819
|
if (!Array.isArray(raw)) return [];
|
|
4787
4820
|
const seen = /* @__PURE__ */ new Set();
|
|
4788
|
-
const dedupe = (
|
|
4821
|
+
const dedupe = (list2) => list2.map((l) => {
|
|
4789
4822
|
let next = l;
|
|
4790
4823
|
if (seen.has(l.id)) next = { ...l, id: newLayerId() };
|
|
4791
4824
|
seen.add(next.id);
|
|
@@ -5187,8 +5220,8 @@ var PsdReader = class _PsdReader {
|
|
|
5187
5220
|
return v;
|
|
5188
5221
|
}
|
|
5189
5222
|
/** A length field: 8 bytes in PSB where the format says so, else 4. */
|
|
5190
|
-
len(
|
|
5191
|
-
return
|
|
5223
|
+
len(long3) {
|
|
5224
|
+
return long3 ? this.u64() : this.u32();
|
|
5192
5225
|
}
|
|
5193
5226
|
bytes(n) {
|
|
5194
5227
|
this.need(n, `${n} bytes`);
|
|
@@ -5295,11 +5328,11 @@ var PsdWriter = class {
|
|
|
5295
5328
|
this.length += n;
|
|
5296
5329
|
}
|
|
5297
5330
|
/** Reserves a length field; `endLen` fills it with the bytes written since. */
|
|
5298
|
-
beginLen(
|
|
5331
|
+
beginLen(long3) {
|
|
5299
5332
|
const at = this.length;
|
|
5300
|
-
if (
|
|
5333
|
+
if (long3) this.u64(0);
|
|
5301
5334
|
else this.u32(0);
|
|
5302
|
-
return { at, long:
|
|
5335
|
+
return { at, long: long3 };
|
|
5303
5336
|
}
|
|
5304
5337
|
endLen(mark) {
|
|
5305
5338
|
const n = this.length - mark.at - (mark.long ? 8 : 4);
|
|
@@ -5459,12 +5492,12 @@ function readValue(r, depth) {
|
|
|
5459
5492
|
case "UntF":
|
|
5460
5493
|
return { type, unit: r.ascii(4), value: r.f64() };
|
|
5461
5494
|
case "UnFl": {
|
|
5462
|
-
const
|
|
5495
|
+
const unit2 = r.ascii(4);
|
|
5463
5496
|
const n = r.u32();
|
|
5464
5497
|
r.checkCount(n, 8, "Unit float list");
|
|
5465
5498
|
const values = [];
|
|
5466
5499
|
for (let i = 0; i < n; i++) values.push(r.f64());
|
|
5467
|
-
return { type, unit, values };
|
|
5500
|
+
return { type, unit: unit2, values };
|
|
5468
5501
|
}
|
|
5469
5502
|
case "TEXT":
|
|
5470
5503
|
return { type, value: r.unicode() };
|
|
@@ -5917,11 +5950,11 @@ function writeAdjustmentBlock(kind, params) {
|
|
|
5917
5950
|
case "blackWhite": {
|
|
5918
5951
|
const p = params;
|
|
5919
5952
|
const tint = p.tint ? hexToRgb(p.tint) : BW_TINT;
|
|
5920
|
-
const
|
|
5953
|
+
const dbl2 = (v) => ({ type: "doub", value: clamp2(v, 0, 1) * 255 });
|
|
5921
5954
|
const items = BW_KEYS.map(([field, key]) => [key, long(clamp2(p[field], -200, 300))]);
|
|
5922
5955
|
items.push(
|
|
5923
5956
|
["useTint", { type: "bool", value: p.tint !== null }],
|
|
5924
|
-
["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])]]) }],
|
|
5925
5958
|
["bwPresetKind", { type: "long", value: 1 }],
|
|
5926
5959
|
["blackAndWhitePresetFileName", { type: "TEXT", value: "" }]
|
|
5927
5960
|
);
|
|
@@ -6405,11 +6438,11 @@ function writeRect(w, r) {
|
|
|
6405
6438
|
}
|
|
6406
6439
|
function writeBlocks(w, blocks, psb, alignment) {
|
|
6407
6440
|
for (const b of blocks) {
|
|
6408
|
-
const
|
|
6409
|
-
w.ascii(
|
|
6441
|
+
const long3 = psb && PSB_LONG_KEYS.has(b.key);
|
|
6442
|
+
w.ascii(long3 ? "8B64" : "8BIM");
|
|
6410
6443
|
w.ascii(b.key);
|
|
6411
6444
|
const pad = (alignment - b.data.length % alignment) % alignment;
|
|
6412
|
-
if (
|
|
6445
|
+
if (long3) w.u64(b.data.length + pad);
|
|
6413
6446
|
else w.u32(b.data.length + pad);
|
|
6414
6447
|
w.bytes(b.data);
|
|
6415
6448
|
w.zeros(pad);
|
|
@@ -6663,6 +6696,784 @@ function matteWhite(data) {
|
|
|
6663
6696
|
return out;
|
|
6664
6697
|
}
|
|
6665
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
|
+
|
|
6666
7477
|
// src/image-editor/psd/import.ts
|
|
6667
7478
|
var PSD_BLEND_KEYS = {
|
|
6668
7479
|
pass: "pass-through",
|
|
@@ -6700,15 +7511,14 @@ var FILL_KEYS = ["SoCo", "GdFl", "PtFl"];
|
|
|
6700
7511
|
var TYPE_KEYS = ["TySh", "tySh"];
|
|
6701
7512
|
var SMART_KEYS = ["SoLd", "SoLE", "PlLd"];
|
|
6702
7513
|
var VECTOR_KEYS = ["vmsk", "vsms"];
|
|
6703
|
-
var EFFECT_KEYS = ["lfx2", "lfxs", "lmfx"];
|
|
6704
7514
|
var percent = (v) => Math.round(v / 255 * 100) / 100;
|
|
6705
7515
|
var Warnings = class {
|
|
6706
7516
|
groups = /* @__PURE__ */ new Map();
|
|
6707
7517
|
extra = [];
|
|
6708
7518
|
add(message, layer) {
|
|
6709
|
-
const
|
|
6710
|
-
if (!
|
|
6711
|
-
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);
|
|
6712
7522
|
}
|
|
6713
7523
|
list() {
|
|
6714
7524
|
const out = [...this.extra];
|
|
@@ -6815,10 +7625,6 @@ function baseProps(ctx, rec, name, notes) {
|
|
|
6815
7625
|
if (rec.clipping) props.clipped = true;
|
|
6816
7626
|
const locks = locksOf(rec);
|
|
6817
7627
|
if (locks) props.locks = locks;
|
|
6818
|
-
if (rec.blocks.some((b) => EFFECT_KEYS.includes(b.key))) {
|
|
6819
|
-
ctx.warn.add("Layer effects (lfx2) are not rendered", name);
|
|
6820
|
-
notes.push("layer effects are not rendered");
|
|
6821
|
-
}
|
|
6822
7628
|
if (rec.blocks.some((b) => b.key === "artb" || b.key === "artd" || b.key === "abdd")) {
|
|
6823
7629
|
ctx.warn.add("Artboards are not supported; they came in as plain groups, without their background colour and clipping", name);
|
|
6824
7630
|
notes.push("artboard imported as a group");
|
|
@@ -6829,6 +7635,47 @@ function baseProps(ctx, rec, name, notes) {
|
|
|
6829
7635
|
}
|
|
6830
7636
|
return props;
|
|
6831
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
|
+
}
|
|
6832
7679
|
function importNode(ctx, node, depth) {
|
|
6833
7680
|
const rec = ctx.file.layers[node.index];
|
|
6834
7681
|
const name = layerName(rec);
|
|
@@ -6847,6 +7694,8 @@ function importNode(ctx, node, depth) {
|
|
|
6847
7694
|
if (layer2) children.push(layer2);
|
|
6848
7695
|
}
|
|
6849
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;
|
|
6850
7699
|
if (divider?.kind === SECTION_CLOSED) group.collapsed = true;
|
|
6851
7700
|
const mask2 = recordMask(ctx, rec, name);
|
|
6852
7701
|
if (mask2) group.mask = mask2;
|
|
@@ -6887,6 +7736,8 @@ function importNode(ctx, node, depth) {
|
|
|
6887
7736
|
const layer = { type: "pixel", id: ctx.newId(name), ...props, surface: recordPixels(ctx, rec, name), offset: { x: 0, y: 0 } };
|
|
6888
7737
|
const fill = u8Block(rec, "iOpa");
|
|
6889
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;
|
|
6890
7741
|
const mask = recordMask(ctx, rec, name);
|
|
6891
7742
|
if (mask) layer.mask = mask;
|
|
6892
7743
|
entry.type = "pixel";
|
|
@@ -6940,8 +7791,17 @@ function psdToLayers(file, options = {}) {
|
|
|
6940
7791
|
skipPixels: options.skipPixels ?? false,
|
|
6941
7792
|
summary: [],
|
|
6942
7793
|
colorCount: colorChannelCount(header.colorMode, header.channels),
|
|
6943
|
-
palette: header.colorMode === MODE_INDEXED ? file.colorModeData : void 0
|
|
7794
|
+
palette: header.colorMode === MODE_INDEXED ? file.colorModeData : void 0,
|
|
7795
|
+
patterns: /* @__PURE__ */ new Map()
|
|
6944
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
|
+
}
|
|
6945
7805
|
const mode = COLOR_MODES[header.colorMode] ?? `mode ${header.colorMode}`;
|
|
6946
7806
|
if (mode !== "rgb" || header.depth !== 8) {
|
|
6947
7807
|
warn.extra.push(`The file is ${header.depth}-bit ${mode}; it was converted to 8-bit RGB.`);
|
|
@@ -6967,6 +7827,9 @@ function psdToLayers(file, options = {}) {
|
|
|
6967
7827
|
const bg = mergedLayer(ctx);
|
|
6968
7828
|
if (bg) layers = [bg];
|
|
6969
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));
|
|
6970
7833
|
return {
|
|
6971
7834
|
width: header.width,
|
|
6972
7835
|
height: header.height,
|
|
@@ -6974,6 +7837,8 @@ function psdToLayers(file, options = {}) {
|
|
|
6974
7837
|
warnings: warn.list(),
|
|
6975
7838
|
summary: ctx.summary,
|
|
6976
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)) } } : {},
|
|
6977
7842
|
info: { psb: header.psb, depth: header.depth, colorMode: mode, channels: header.channels }
|
|
6978
7843
|
};
|
|
6979
7844
|
}
|
|
@@ -6983,6 +7848,18 @@ function importPsd(bytes, options = {}) {
|
|
|
6983
7848
|
}
|
|
6984
7849
|
|
|
6985
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
|
+
};
|
|
6986
7863
|
var FLAG_PIXELS_IRRELEVANT = 8 | 16;
|
|
6987
7864
|
var EMPTY_RECT = { top: 0, left: 0, bottom: 0, right: 0 };
|
|
6988
7865
|
function unicodeBlock(s) {
|
|
@@ -7032,9 +7909,28 @@ function exportPsd(input) {
|
|
|
7032
7909
|
const psb = width > PSD_MAX_DIMENSION || height > PSD_MAX_DIMENSION;
|
|
7033
7910
|
const records = [];
|
|
7034
7911
|
let nextId = 0;
|
|
7035
|
-
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 = []) => {
|
|
7036
7930
|
const name = layer.name || (layer.type === "adjustment" ? adjustmentKindLabel(layer.kind) : "");
|
|
7037
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);
|
|
7038
7934
|
const locks = lockBits(layer);
|
|
7039
7935
|
if (locks) blocks.push({ key: "lspf", data: u32Bytes(locks >>> 0) });
|
|
7040
7936
|
return {
|
|
@@ -7063,7 +7959,7 @@ function exportPsd(input) {
|
|
|
7063
7959
|
});
|
|
7064
7960
|
for (const child of layer.children) write(child);
|
|
7065
7961
|
const blendKey = BLEND_TO_PSD[layer.blend] ?? "norm";
|
|
7066
|
-
const base =
|
|
7962
|
+
const base = common2(layer, [{ key: "lsct", data: sectionBlock(layer.collapsed ? SECTION_CLOSED : SECTION_OPEN, blendKey) }]);
|
|
7067
7963
|
const rec = {
|
|
7068
7964
|
...base,
|
|
7069
7965
|
rect: EMPTY_RECT,
|
|
@@ -7082,7 +7978,7 @@ function exportPsd(input) {
|
|
|
7082
7978
|
case "adjustment": {
|
|
7083
7979
|
const block = writeAdjustmentBlock(layer.kind, layer.params);
|
|
7084
7980
|
const rec = {
|
|
7085
|
-
...
|
|
7981
|
+
...common2(layer, [{ key: block.key, data: block.data }]),
|
|
7086
7982
|
rect: EMPTY_RECT,
|
|
7087
7983
|
channels: [CHANNEL_ALPHA, 0, 1, 2].map(emptyChannel),
|
|
7088
7984
|
blendKey: BLEND_TO_PSD[layer.blend] ?? "norm"
|
|
@@ -7102,7 +7998,7 @@ function exportPsd(input) {
|
|
|
7102
7998
|
if (layer.fillOpacity !== void 0 && layer.fillOpacity < 1) {
|
|
7103
7999
|
extra.push({ key: "iOpa", data: Uint8Array.from([Math.round(Math.max(0, layer.fillOpacity) * 255), 0, 0, 0]) });
|
|
7104
8000
|
}
|
|
7105
|
-
const rec = { ...
|
|
8001
|
+
const rec = { ...common2(layer, extra), rect: EMPTY_RECT, channels: [], blendKey: BLEND_TO_PSD[layer.blend] ?? "norm" };
|
|
7106
8002
|
const bounds = contentBounds(layer.surface);
|
|
7107
8003
|
if (bounds) {
|
|
7108
8004
|
const n2 = bounds.width * bounds.height;
|
|
@@ -7155,6 +8051,25 @@ function exportPsd(input) {
|
|
|
7155
8051
|
{ id: 1005, name: "", data: rw.result() },
|
|
7156
8052
|
{ id: 1057, name: "", data: version.result() }
|
|
7157
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
|
+
}
|
|
7158
8073
|
const bytes = writePsd(
|
|
7159
8074
|
{
|
|
7160
8075
|
header: { psb, channels: planes.length, width, height, depth: 8, colorMode: 3 },
|
|
@@ -7162,13 +8077,14 @@ function exportPsd(input) {
|
|
|
7162
8077
|
resources,
|
|
7163
8078
|
layers: records,
|
|
7164
8079
|
mergedAlpha: transparent && records.length > 0,
|
|
7165
|
-
globalBlocks
|
|
8080
|
+
globalBlocks,
|
|
7166
8081
|
imageCompression: 1,
|
|
7167
8082
|
imageData: encodeRle(merged, { planes: planes.length, width, height, depth: 8, psb })
|
|
7168
8083
|
},
|
|
7169
8084
|
encodeLegacyName
|
|
7170
8085
|
);
|
|
7171
|
-
|
|
8086
|
+
const warnings = [...notes].map(([msg, names]) => `${msg} (${names.map((n2) => `"${n2}"`).join(", ")}).`);
|
|
8087
|
+
return { bytes, psb, records: records.length, warnings };
|
|
7172
8088
|
}
|
|
7173
8089
|
|
|
7174
8090
|
// src/image-editor/controller.ts
|
|
@@ -7419,8 +8335,9 @@ var ImageEditorController = class {
|
|
|
7419
8335
|
this.renderer.layerSources = {
|
|
7420
8336
|
surfaceOf: (layer) => this.surfaceOf(layer),
|
|
7421
8337
|
sourceIsOpaque: () => this.sourceIsOpaque(),
|
|
7422
|
-
version: () => this.sourceVersion()
|
|
7423
|
-
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
|
|
7424
8341
|
};
|
|
7425
8342
|
this.state = {
|
|
7426
8343
|
sourceUrl: typeof options.image === "string" ? options.image : null,
|
|
@@ -7575,6 +8492,7 @@ var ImageEditorController = class {
|
|
|
7575
8492
|
};
|
|
7576
8493
|
if (s.legacyOutputBlur !== void 0) entry.legacyOutputBlur = s.legacyOutputBlur;
|
|
7577
8494
|
if (s.globalLight) entry.globalLight = { ...s.globalLight };
|
|
8495
|
+
if (s.patterns) entry.patterns = s.patterns;
|
|
7578
8496
|
return entry;
|
|
7579
8497
|
}
|
|
7580
8498
|
restoreHistoryEntry(entry) {
|
|
@@ -7589,6 +8507,8 @@ var ImageEditorController = class {
|
|
|
7589
8507
|
else delete s.legacyOutputBlur;
|
|
7590
8508
|
if (entry.globalLight) s.globalLight = { ...entry.globalLight };
|
|
7591
8509
|
else delete s.globalLight;
|
|
8510
|
+
if (entry.patterns) s.patterns = entry.patterns;
|
|
8511
|
+
else delete s.patterns;
|
|
7592
8512
|
s.selection = entry.selection;
|
|
7593
8513
|
if (this.imageElement !== entry.source.element && !this.samplePixelsOverride) this.samplePixels = null;
|
|
7594
8514
|
this.imageElement = entry.source.element;
|
|
@@ -7810,7 +8730,14 @@ var ImageEditorController = class {
|
|
|
7810
8730
|
}
|
|
7811
8731
|
compositorSources() {
|
|
7812
8732
|
const { width, height } = this.naturalSize;
|
|
7813
|
-
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
|
+
};
|
|
7814
8741
|
}
|
|
7815
8742
|
sourceIds = /* @__PURE__ */ new WeakMap();
|
|
7816
8743
|
nextSourceId = 1;
|
|
@@ -7836,17 +8763,17 @@ var ImageEditorController = class {
|
|
|
7836
8763
|
*/
|
|
7837
8764
|
sourceSurface() {
|
|
7838
8765
|
const { width, height } = this.naturalSize;
|
|
7839
|
-
const fromPixels = (
|
|
7840
|
-
let s = this.sourceSurfaces.get(
|
|
8766
|
+
const fromPixels = (px3) => {
|
|
8767
|
+
let s = this.sourceSurfaces.get(px3);
|
|
7841
8768
|
if (!s) {
|
|
7842
|
-
s = surfaceFromPixels(
|
|
7843
|
-
this.sourceSurfaces.set(
|
|
8769
|
+
s = surfaceFromPixels(px3, 4);
|
|
8770
|
+
this.sourceSurfaces.set(px3, s);
|
|
7844
8771
|
}
|
|
7845
8772
|
return s;
|
|
7846
8773
|
};
|
|
7847
|
-
const
|
|
7848
|
-
const fits = Boolean(
|
|
7849
|
-
if (this.samplePixelsOverride && fits) return fromPixels(
|
|
8774
|
+
const px2 = this.samplePixels;
|
|
8775
|
+
const fits = Boolean(px2 && px2.width === width && px2.height === height);
|
|
8776
|
+
if (this.samplePixelsOverride && fits) return fromPixels(px2);
|
|
7850
8777
|
const el = this.imageElement;
|
|
7851
8778
|
if (el) {
|
|
7852
8779
|
const cached = this.sourceSurfaces.get(el);
|
|
@@ -7860,7 +8787,7 @@ var ImageEditorController = class {
|
|
|
7860
8787
|
return s;
|
|
7861
8788
|
}
|
|
7862
8789
|
}
|
|
7863
|
-
return fits ? fromPixels(
|
|
8790
|
+
return fits ? fromPixels(px2) : null;
|
|
7864
8791
|
}
|
|
7865
8792
|
opaqueCache = /* @__PURE__ */ new WeakMap();
|
|
7866
8793
|
/** Whether every source pixel is opaque (some legacy renders depend on it). */
|
|
@@ -7919,11 +8846,11 @@ var ImageEditorController = class {
|
|
|
7919
8846
|
const { width, height } = this.naturalSize;
|
|
7920
8847
|
let surface = emptySurface(4);
|
|
7921
8848
|
if (init.pixels) {
|
|
7922
|
-
const
|
|
7923
|
-
if (!(
|
|
8849
|
+
const px2 = init.pixels;
|
|
8850
|
+
if (!(px2.width > 0 && px2.height > 0) || px2.data.length < px2.width * px2.height * 4) {
|
|
7924
8851
|
throw new LayerOperationError("pixels must be RGBA with width \xD7 height \xD7 4 bytes.");
|
|
7925
8852
|
}
|
|
7926
|
-
surface = surfaceFromPixels(
|
|
8853
|
+
surface = surfaceFromPixels(px2, 4);
|
|
7927
8854
|
} else if (init.color !== void 0) {
|
|
7928
8855
|
const c = parseColor(init.color);
|
|
7929
8856
|
surface = solidSurface(4, { x: 0, y: 0, width, height }, packRgba(c[0], c[1], c[2], c[3]));
|
|
@@ -8171,14 +9098,14 @@ var ImageEditorController = class {
|
|
|
8171
9098
|
const surface = this.surfaceOf(layer);
|
|
8172
9099
|
const sb = sel.bounds;
|
|
8173
9100
|
const src = { x: sb.x - layer.offset.x, y: sb.y - layer.offset.y, width: sb.width, height: sb.height };
|
|
8174
|
-
const
|
|
8175
|
-
const kept = options.cut ? new Uint8ClampedArray(
|
|
9101
|
+
const px2 = readRegion(surface, src);
|
|
9102
|
+
const kept = options.cut ? new Uint8ClampedArray(px2) : null;
|
|
8176
9103
|
for (let y = 0; y < sb.height; y++) {
|
|
8177
9104
|
for (let x = 0; x < sb.width; x++) {
|
|
8178
9105
|
const c = sel.data[(sb.y + y) * sel.width + sb.x + x];
|
|
8179
9106
|
const i = (y * sb.width + x) * 4 + 3;
|
|
8180
9107
|
if (kept) kept[i] = Math.round(kept[i] * (1 - c / 255));
|
|
8181
|
-
|
|
9108
|
+
px2[i] = Math.round(px2[i] * (c / 255));
|
|
8182
9109
|
}
|
|
8183
9110
|
}
|
|
8184
9111
|
const copy = {
|
|
@@ -8188,7 +9115,7 @@ var ImageEditorController = class {
|
|
|
8188
9115
|
visible: true,
|
|
8189
9116
|
opacity: 1,
|
|
8190
9117
|
blend: "normal",
|
|
8191
|
-
surface: surfaceFromPixels({ width: src.width, height: src.height, data:
|
|
9118
|
+
surface: surfaceFromPixels({ width: src.width, height: src.height, data: px2 }, 4, src.x, src.y),
|
|
8192
9119
|
offset: { ...layer.offset }
|
|
8193
9120
|
};
|
|
8194
9121
|
let next = insertLayer(this.state.layers, copy, loc.parent?.id ?? null, loc.index + 1);
|
|
@@ -8256,6 +9183,22 @@ var ImageEditorController = class {
|
|
|
8256
9183
|
this.commitLayers(setLayerEffects(this.state.layers, id, effects), void 0, commitToHistory);
|
|
8257
9184
|
return this.getLayerEffects(id);
|
|
8258
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
|
+
}
|
|
8259
9202
|
/** The light effects with "use global light" follow. */
|
|
8260
9203
|
getGlobalLight() {
|
|
8261
9204
|
return { ...this.state.globalLight ?? DEFAULT_GLOBAL_LIGHT };
|
|
@@ -9434,9 +10377,9 @@ var ImageEditorController = class {
|
|
|
9434
10377
|
return;
|
|
9435
10378
|
}
|
|
9436
10379
|
const { width, height } = this.naturalSize;
|
|
9437
|
-
let
|
|
9438
|
-
if (
|
|
9439
|
-
const data = new Uint8ClampedArray(
|
|
10380
|
+
let px2 = await this.decodeImage(newDataUrl);
|
|
10381
|
+
if (px2.width !== width || px2.height !== height) px2 = resizePixels(px2, width, height);
|
|
10382
|
+
const data = new Uint8ClampedArray(px2.data);
|
|
9440
10383
|
const sel = this.state.selection?.bounds ? this.state.selection : null;
|
|
9441
10384
|
if (sel) for (let p = 0, i = 3; p < width * height; p++, i += 4) data[i] = Math.round(data[i] * sel.data[p] / 255);
|
|
9442
10385
|
return this.addPixelLayer({ name: "Inpaint", pixels: { width, height, data } });
|
|
@@ -9457,10 +10400,10 @@ var ImageEditorController = class {
|
|
|
9457
10400
|
if (!target) throw new LayerOperationError("There is no pixel layer to mask.");
|
|
9458
10401
|
requireUnlocked(target, "all", "mask it");
|
|
9459
10402
|
const { width, height } = this.naturalSize;
|
|
9460
|
-
let
|
|
9461
|
-
if (
|
|
10403
|
+
let px2 = await this.decodeImage(newDataUrl);
|
|
10404
|
+
if (px2.width !== width || px2.height !== height) px2 = resizePixels(px2, width, height);
|
|
9462
10405
|
const alpha = new Uint8ClampedArray(width * height);
|
|
9463
|
-
for (let p = 0; p < alpha.length; p++) alpha[p] =
|
|
10406
|
+
for (let p = 0; p < alpha.length; p++) alpha[p] = px2.data[p * 4 + 3];
|
|
9464
10407
|
let surface = surfaceFromPixels({ width, height, data: alpha }, 1, 0, 0, 0);
|
|
9465
10408
|
if (target.offset.x || target.offset.y) surface = translateSurface(surface, -target.offset.x, -target.offset.y);
|
|
9466
10409
|
this.commitLayers(setLayerMask(this.state.layers, target.id, { surface, defaultValue: 0 }));
|
|
@@ -9538,6 +10481,10 @@ var ImageEditorController = class {
|
|
|
9538
10481
|
s.activeLayerId = pixels.length ? pixels[pixels.length - 1].id : null;
|
|
9539
10482
|
s.activeTarget = "pixels";
|
|
9540
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;
|
|
9541
10488
|
this.lastLoadProblems = result.warnings;
|
|
9542
10489
|
this.notify();
|
|
9543
10490
|
return {
|
|
@@ -9606,6 +10553,7 @@ var ImageEditorController = class {
|
|
|
9606
10553
|
const content = { ...layer, visible: true, opacity: 1, blend: "normal", annotation: { ...layer.annotation, blend: void 0 } };
|
|
9607
10554
|
delete content.mask;
|
|
9608
10555
|
delete content.clipped;
|
|
10556
|
+
delete content.effects;
|
|
9609
10557
|
const data = compositor.composite([content], canvas, sources);
|
|
9610
10558
|
const out2 = {
|
|
9611
10559
|
type: "pixel",
|
|
@@ -9619,6 +10567,7 @@ var ImageEditorController = class {
|
|
|
9619
10567
|
};
|
|
9620
10568
|
if (layer.clipped) out2.clipped = true;
|
|
9621
10569
|
if (layer.locks) out2.locks = layer.locks;
|
|
10570
|
+
if (layer.effects) out2.effects = layer.effects;
|
|
9622
10571
|
const mask2 = bakeMask(layer);
|
|
9623
10572
|
if (mask2) out2.mask = mask2;
|
|
9624
10573
|
return out2;
|
|
@@ -9635,6 +10584,7 @@ var ImageEditorController = class {
|
|
|
9635
10584
|
delete content.mask;
|
|
9636
10585
|
delete content.clipped;
|
|
9637
10586
|
delete content.fillOpacity;
|
|
10587
|
+
delete content.effects;
|
|
9638
10588
|
const data = compositor.composite([content], canvas, sources);
|
|
9639
10589
|
out = { ...out, surface: surfaceFromPixels({ width, height, data }, 4, -layer.offset.x, -layer.offset.y) };
|
|
9640
10590
|
}
|
|
@@ -9657,7 +10607,8 @@ var ImageEditorController = class {
|
|
|
9657
10607
|
if (s.legacyOutputBlur) warnings.push("The document-level blur of an old document is not written.");
|
|
9658
10608
|
for (const [msg, names] of note) warnings.push(`${msg} (${names.length === 1 ? "layer" : "layers"} ${names.map((n) => `"${n}"`).join(", ")}).`);
|
|
9659
10609
|
const merged = compositor.composite(prepared, canvas, { ...sources, version: `psd:${Date.now()}` });
|
|
9660
|
-
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);
|
|
9661
10612
|
return { bytes: result.bytes, warnings, psb: result.psb, width, height };
|
|
9662
10613
|
}
|
|
9663
10614
|
async replaceSource(src, clearAnnotations) {
|
|
@@ -9761,6 +10712,9 @@ var ImageEditorController = class {
|
|
|
9761
10712
|
const light = data.globalLight;
|
|
9762
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)) };
|
|
9763
10714
|
else delete s.globalLight;
|
|
10715
|
+
const patterns = deserializePatterns(data.patterns, problems);
|
|
10716
|
+
if (patterns.length) s.patterns = patterns;
|
|
10717
|
+
else delete s.patterns;
|
|
9764
10718
|
if (!this.imageElement && typeof data.width === "number" && typeof data.height === "number" && data.width > 0 && data.height > 0) {
|
|
9765
10719
|
s.imageDimensions = { width: data.width, height: data.height };
|
|
9766
10720
|
}
|
|
@@ -9775,6 +10729,7 @@ var ImageEditorController = class {
|
|
|
9775
10729
|
s.transform = { ...data.transform };
|
|
9776
10730
|
s.activeLayerId = null;
|
|
9777
10731
|
delete s.globalLight;
|
|
10732
|
+
delete s.patterns;
|
|
9778
10733
|
}
|
|
9779
10734
|
s.activeTarget = "pixels";
|
|
9780
10735
|
this.viewKey = [];
|
|
@@ -9784,7 +10739,7 @@ var ImageEditorController = class {
|
|
|
9784
10739
|
serializeState() {
|
|
9785
10740
|
const { width, height } = this.naturalSize;
|
|
9786
10741
|
const view = legacyView(this.stack(), width, height, null);
|
|
9787
|
-
if (view.exact && !this.state.globalLight) {
|
|
10742
|
+
if (view.exact && !this.state.globalLight && !this.state.patterns?.length) {
|
|
9788
10743
|
return {
|
|
9789
10744
|
// States without adjustment layers keep schema 1, so they round-trip unchanged.
|
|
9790
10745
|
version: view.adjustments.layers?.length ? 2 : 1,
|
|
@@ -9812,6 +10767,7 @@ var ImageEditorController = class {
|
|
|
9812
10767
|
};
|
|
9813
10768
|
if (this.state.legacyOutputBlur !== void 0) out.legacyOutputBlur = this.state.legacyOutputBlur;
|
|
9814
10769
|
if (this.state.globalLight) out.globalLight = { ...this.state.globalLight };
|
|
10770
|
+
if (this.state.patterns?.length) out.patterns = serializePatterns(this.state.patterns);
|
|
9815
10771
|
return out;
|
|
9816
10772
|
}
|
|
9817
10773
|
};
|
|
@@ -9957,24 +10913,24 @@ function pixelsOnly(layers) {
|
|
|
9957
10913
|
}
|
|
9958
10914
|
return out;
|
|
9959
10915
|
}
|
|
9960
|
-
function resizePixels(
|
|
9961
|
-
const src =
|
|
10916
|
+
function resizePixels(px2, width, height) {
|
|
10917
|
+
const src = px2.data;
|
|
9962
10918
|
const out = new Uint8ClampedArray(width * height * 4);
|
|
9963
|
-
const kx =
|
|
9964
|
-
const ky =
|
|
10919
|
+
const kx = px2.width / width;
|
|
10920
|
+
const ky = px2.height / height;
|
|
9965
10921
|
for (let y = 0; y < height; y++) {
|
|
9966
|
-
const fy = Math.min(
|
|
10922
|
+
const fy = Math.min(px2.height - 1, Math.max(0, (y + 0.5) * ky - 0.5));
|
|
9967
10923
|
const y0 = Math.floor(fy);
|
|
9968
|
-
const y1 = Math.min(
|
|
10924
|
+
const y1 = Math.min(px2.height - 1, y0 + 1);
|
|
9969
10925
|
const ty = fy - y0;
|
|
9970
10926
|
for (let x = 0; x < width; x++) {
|
|
9971
|
-
const fx = Math.min(
|
|
10927
|
+
const fx = Math.min(px2.width - 1, Math.max(0, (x + 0.5) * kx - 0.5));
|
|
9972
10928
|
const x0 = Math.floor(fx);
|
|
9973
|
-
const x1 = Math.min(
|
|
10929
|
+
const x1 = Math.min(px2.width - 1, x0 + 1);
|
|
9974
10930
|
const tx = fx - x0;
|
|
9975
10931
|
for (let c = 0; c < 4; c++) {
|
|
9976
|
-
const a = src[(y0 *
|
|
9977
|
-
const b = src[(y1 *
|
|
10932
|
+
const a = src[(y0 * px2.width + x0) * 4 + c] * (1 - tx) + src[(y0 * px2.width + x1) * 4 + c] * tx;
|
|
10933
|
+
const b = src[(y1 * px2.width + x0) * 4 + c] * (1 - tx) + src[(y1 * px2.width + x1) * 4 + c] * tx;
|
|
9978
10934
|
out[(y * width + x) * 4 + c] = a * (1 - ty) + b * ty;
|
|
9979
10935
|
}
|
|
9980
10936
|
}
|
|
@@ -10401,17 +11357,17 @@ function controlTrackBackground(spec) {
|
|
|
10401
11357
|
return void 0;
|
|
10402
11358
|
}
|
|
10403
11359
|
}
|
|
10404
|
-
function getControlPath(
|
|
10405
|
-
let cur =
|
|
11360
|
+
function getControlPath(obj2, path) {
|
|
11361
|
+
let cur = obj2;
|
|
10406
11362
|
for (const part of path.split(".")) {
|
|
10407
11363
|
if (cur === null || cur === void 0) return void 0;
|
|
10408
11364
|
cur = cur[part];
|
|
10409
11365
|
}
|
|
10410
11366
|
return cur;
|
|
10411
11367
|
}
|
|
10412
|
-
function setControlPath(
|
|
11368
|
+
function setControlPath(obj2, path, value) {
|
|
10413
11369
|
const parts = path.split(".");
|
|
10414
|
-
const root = Array.isArray(
|
|
11370
|
+
const root = Array.isArray(obj2) ? [...obj2] : { ...obj2 };
|
|
10415
11371
|
let cur = root;
|
|
10416
11372
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
10417
11373
|
const next = cur[parts[i]];
|
|
@@ -10469,8 +11425,8 @@ function describeSelection(mask) {
|
|
|
10469
11425
|
if (!mask || !mask.bounds) return "No selection";
|
|
10470
11426
|
let sum = 0;
|
|
10471
11427
|
for (let i = 0; i < mask.data.length; i++) sum += mask.data[i];
|
|
10472
|
-
const
|
|
10473
|
-
const shown =
|
|
11428
|
+
const pct3 = sum / 255 / mask.data.length * 100;
|
|
11429
|
+
const shown = pct3 >= 10 ? Math.round(pct3) : Math.round(pct3 * 10) / 10;
|
|
10474
11430
|
return `${mask.bounds.width} \xD7 ${mask.bounds.height} px selected (${shown}% of the image)`;
|
|
10475
11431
|
}
|
|
10476
11432
|
|
|
@@ -10480,7 +11436,7 @@ var modeSchema = {
|
|
|
10480
11436
|
enum: [...SELECTION_MODES],
|
|
10481
11437
|
description: "How the result combines with the current selection (default replace)."
|
|
10482
11438
|
};
|
|
10483
|
-
var
|
|
11439
|
+
var num3 = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
|
|
10484
11440
|
function summary(controller) {
|
|
10485
11441
|
const sel = controller.getSelection();
|
|
10486
11442
|
const { width, height } = controller.getState().imageDimensions;
|
|
@@ -10530,7 +11486,7 @@ function createImageSelectionTools(controller) {
|
|
|
10530
11486
|
},
|
|
10531
11487
|
annotations: writeAnnotations("Select a region", true),
|
|
10532
11488
|
handler: ({ shape, x, y, width, height, mode, feather }) => {
|
|
10533
|
-
const opts = { mode: parseSelectionMode(mode), feather:
|
|
11489
|
+
const opts = { mode: parseSelectionMode(mode), feather: num3(feather, 0) };
|
|
10534
11490
|
switch (shape) {
|
|
10535
11491
|
case "all":
|
|
10536
11492
|
controller.selectAll();
|
|
@@ -10543,7 +11499,7 @@ function createImageSelectionTools(controller) {
|
|
|
10543
11499
|
break;
|
|
10544
11500
|
case "rect":
|
|
10545
11501
|
case "ellipse": {
|
|
10546
|
-
const rect = { x:
|
|
11502
|
+
const rect = { x: num3(x, NaN), y: num3(y, NaN), width: num3(width, NaN), height: num3(height, NaN) };
|
|
10547
11503
|
if (![rect.x, rect.y, rect.width, rect.height].every(Number.isFinite)) {
|
|
10548
11504
|
throw new Error("rect and ellipse need numeric x, y, width and height.");
|
|
10549
11505
|
}
|
|
@@ -10582,15 +11538,15 @@ function createImageSelectionTools(controller) {
|
|
|
10582
11538
|
},
|
|
10583
11539
|
annotations: writeAnnotations("Select by colour", true),
|
|
10584
11540
|
handler: async ({ x, y, color, tolerance, contiguous, fuzziness, sampleMerged, sample, mode }) => {
|
|
10585
|
-
const
|
|
11541
|
+
const common2 = {
|
|
10586
11542
|
mode: parseSelectionMode(mode),
|
|
10587
11543
|
sampleMerged: sampleMerged === true,
|
|
10588
11544
|
sample: sample ?? "composite"
|
|
10589
11545
|
};
|
|
10590
11546
|
if (typeof color === "string") {
|
|
10591
|
-
await controller.selectColorRange([{ color }], { ...
|
|
11547
|
+
await controller.selectColorRange([{ color }], { ...common2, fuzziness: num3(fuzziness, 40) });
|
|
10592
11548
|
} else if (typeof x === "number" && typeof y === "number") {
|
|
10593
|
-
await controller.magicWand({ x, y }, { ...
|
|
11549
|
+
await controller.magicWand({ x, y }, { ...common2, tolerance: num3(tolerance, 32), contiguous: contiguous !== false });
|
|
10594
11550
|
} else {
|
|
10595
11551
|
throw new Error("Pass x and y (magic wand) or color (#rrggbb).");
|
|
10596
11552
|
}
|
|
@@ -10614,7 +11570,7 @@ function createImageSelectionTools(controller) {
|
|
|
10614
11570
|
handler: ({ op, radius }) => {
|
|
10615
11571
|
if (!SELECTION_MODIFY_OPS.includes(op)) throw new Error(`Unknown op "${String(op)}".`);
|
|
10616
11572
|
if (!controller.getSelection()) throw new Error("Nothing is selected.");
|
|
10617
|
-
controller.modifySelection(op, Math.min(500, Math.max(0,
|
|
11573
|
+
controller.modifySelection(op, Math.min(500, Math.max(0, num3(radius, 0))));
|
|
10618
11574
|
return summary(controller);
|
|
10619
11575
|
}
|
|
10620
11576
|
};
|
|
@@ -10638,12 +11594,12 @@ function createImageSelectionTools(controller) {
|
|
|
10638
11594
|
handler: async (params) => {
|
|
10639
11595
|
if (!controller.getSelection()) throw new Error("Nothing is selected.");
|
|
10640
11596
|
await controller.refineSelection({
|
|
10641
|
-
radius:
|
|
11597
|
+
radius: num3(params.radius, 8),
|
|
10642
11598
|
smartRadius: params.smartRadius === true,
|
|
10643
|
-
smooth:
|
|
10644
|
-
feather:
|
|
10645
|
-
contrast:
|
|
10646
|
-
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)
|
|
10647
11603
|
});
|
|
10648
11604
|
return summary(controller);
|
|
10649
11605
|
}
|
|
@@ -10790,14 +11746,14 @@ function rebin(bins, count) {
|
|
|
10790
11746
|
return out;
|
|
10791
11747
|
}
|
|
10792
11748
|
function histogramSummary(h, bins) {
|
|
10793
|
-
const
|
|
11749
|
+
const pct3 = (n) => h.samples ? round(n / h.samples * 100) : 0;
|
|
10794
11750
|
const channels = { red: h.red, green: h.green, blue: h.blue, luminance: h.luminance };
|
|
10795
11751
|
return {
|
|
10796
11752
|
samples: h.samples,
|
|
10797
11753
|
transparent: h.transparent,
|
|
10798
11754
|
clipping: {
|
|
10799
|
-
shadowsPercent: { red:
|
|
10800
|
-
highlightsPercent: { red:
|
|
11755
|
+
shadowsPercent: { red: pct3(h.shadows[0]), green: pct3(h.shadows[1]), blue: pct3(h.shadows[2]) },
|
|
11756
|
+
highlightsPercent: { red: pct3(h.highlights[0]), green: pct3(h.highlights[1]), blue: pct3(h.highlights[2]) }
|
|
10801
11757
|
},
|
|
10802
11758
|
stats: Object.fromEntries(Object.entries(channels).map(([k, v]) => [k, binStats(v, h.samples)])),
|
|
10803
11759
|
...bins ? { bins: Object.fromEntries(Object.entries(channels).map(([k, v]) => [k, rebin(v, bins)])) } : {}
|
|
@@ -11552,7 +12508,7 @@ var WRITE_IDEMPOTENT3 = { readOnlyHint: false, destructiveHint: false, idempoten
|
|
|
11552
12508
|
var DESTRUCTIVE2 = { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false };
|
|
11553
12509
|
var LAYER_ID3 = { type: "string", description: "Layer id from list_layers (a pixel, annotation or group layer)." };
|
|
11554
12510
|
var EFFECT_ID = { type: "string", description: "Effect id from list_layer_effects or add_layer_effect." };
|
|
11555
|
-
var BLEND_IDS = BLEND_MODES.map((m) => m.id);
|
|
12511
|
+
var BLEND_IDS = BLEND_MODES.map((m) => m.id).filter((id) => id !== "pigment");
|
|
11556
12512
|
var DEFS = {
|
|
11557
12513
|
color: { type: "string", pattern: "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$", description: "Colour as #rrggbb." },
|
|
11558
12514
|
contour: {
|
|
@@ -11664,7 +12620,7 @@ var DEFS = {
|
|
|
11664
12620
|
};
|
|
11665
12621
|
var nullable = (schema, description) => ({ anyOf: [schema, { type: "null" }], description });
|
|
11666
12622
|
function settingsSchema(forUpdate) {
|
|
11667
|
-
const
|
|
12623
|
+
const opt2 = (schema, description) => forUpdate ? nullable(schema, `${description} null removes it.`) : { ...schema, description };
|
|
11668
12624
|
return {
|
|
11669
12625
|
type: "object",
|
|
11670
12626
|
description: forUpdate ? "Settings to change (only these; nested objects are replaced whole). Which settings a kind takes: see add_layer_effect." : "Settings to change from Photoshop's defaults (all optional). Unknown settings for the kind are refused with the list of its settings.",
|
|
@@ -11679,7 +12635,7 @@ function settingsSchema(forUpdate) {
|
|
|
11679
12635
|
spread: { type: "number", minimum: 0, maximum: 1, description: "drop-shadow, outer-glow: 0..1 of the size made solid (default 0)." },
|
|
11680
12636
|
choke: { type: "number", minimum: 0, maximum: 1, description: "inner-shadow, inner-glow: 0..1 of the size made solid (default 0)." },
|
|
11681
12637
|
size: { type: "number", minimum: 0, maximum: 250, description: "Shadows, glows, satin, bevel-emboss: blur / size in px (default 5; satin 14); stroke: width in px (default 3)." },
|
|
11682
|
-
contour:
|
|
12638
|
+
contour: opt2({ $ref: "#/$defs/contour" }, "Shadows, glows, satin: coverage contour (default linear)."),
|
|
11683
12639
|
antiAlias: { type: "boolean", description: "Shadows, glows, satin: smooth the contour (default false; satin true)." },
|
|
11684
12640
|
noise: { type: "number", minimum: 0, maximum: 1, description: "Shadows, glows: speckle 0..1 (default 0)." },
|
|
11685
12641
|
knocksOut: { type: "boolean", description: "drop-shadow: the layer hides the shadow beneath its see-through fill (default true)." },
|
|
@@ -11693,14 +12649,14 @@ function settingsSchema(forUpdate) {
|
|
|
11693
12649
|
direction: { type: "string", enum: ["up", "down"], description: "bevel-emboss: raised (up, default) or sunk (down)." },
|
|
11694
12650
|
soften: { type: "number", minimum: 0, maximum: 16, description: "bevel-emboss: blur of the height in px (default 0)." },
|
|
11695
12651
|
altitude: { type: "number", minimum: 0, maximum: 90, description: "bevel-emboss: light altitude in degrees (default 30)." },
|
|
11696
|
-
glossContour:
|
|
12652
|
+
glossContour: opt2({ $ref: "#/$defs/contour" }, "bevel-emboss: gloss contour over the shading (default linear)."),
|
|
11697
12653
|
highlightColor: { $ref: "#/$defs/color", description: "bevel-emboss: highlight colour (default #ffffff)." },
|
|
11698
12654
|
highlightBlend: { type: "string", enum: BLEND_IDS, description: "bevel-emboss: highlight mode (default screen)." },
|
|
11699
12655
|
highlightOpacity: { type: "number", minimum: 0, maximum: 1, description: "bevel-emboss: highlight opacity (default 0.75)." },
|
|
11700
12656
|
shadowColor: { $ref: "#/$defs/color", description: "bevel-emboss: shadow colour (default #000000)." },
|
|
11701
12657
|
shadowBlend: { type: "string", enum: BLEND_IDS, description: "bevel-emboss: shadow mode (default multiply)." },
|
|
11702
12658
|
shadowOpacity: { type: "number", minimum: 0, maximum: 1, description: "bevel-emboss: shadow opacity (default 0.75)." },
|
|
11703
|
-
contourElement:
|
|
12659
|
+
contourElement: opt2(
|
|
11704
12660
|
{
|
|
11705
12661
|
type: "object",
|
|
11706
12662
|
properties: {
|
|
@@ -11713,7 +12669,7 @@ function settingsSchema(forUpdate) {
|
|
|
11713
12669
|
},
|
|
11714
12670
|
"bevel-emboss: the Contour element, shaping the bevel's height profile (default off)."
|
|
11715
12671
|
),
|
|
11716
|
-
texture:
|
|
12672
|
+
texture: opt2(
|
|
11717
12673
|
{
|
|
11718
12674
|
type: "object",
|
|
11719
12675
|
properties: {
|
|
@@ -11743,11 +12699,13 @@ function settingsSchema(forUpdate) {
|
|
|
11743
12699
|
additionalProperties: false
|
|
11744
12700
|
};
|
|
11745
12701
|
}
|
|
11746
|
-
function patternList() {
|
|
11747
|
-
|
|
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) => {
|
|
11748
12705
|
const p = builtinPattern(id);
|
|
11749
12706
|
return { id, name: p.name, width: p.width, height: p.height, builtin: true };
|
|
11750
12707
|
});
|
|
12708
|
+
return [...doc, ...builtins];
|
|
11751
12709
|
}
|
|
11752
12710
|
function describeLayerEffects(effects) {
|
|
11753
12711
|
return {
|
|
@@ -11765,13 +12723,13 @@ function createImageEffectTools(controller) {
|
|
|
11765
12723
|
return id;
|
|
11766
12724
|
};
|
|
11767
12725
|
const result = (layerId, extra = {}) => ({ layerId, ...extra, ...describeLayerEffects(controller.getLayerEffects(layerId)) });
|
|
11768
|
-
const
|
|
12726
|
+
const list2 = {
|
|
11769
12727
|
name: "list_layer_effects",
|
|
11770
12728
|
title: "List layer effects",
|
|
11771
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.",
|
|
11772
12730
|
inputSchema: { type: "object", properties: { layerId: LAYER_ID3 }, required: ["layerId"], additionalProperties: false },
|
|
11773
12731
|
annotations: { title: "List layer effects", ...READ3 },
|
|
11774
|
-
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] })
|
|
11775
12733
|
};
|
|
11776
12734
|
const add = {
|
|
11777
12735
|
name: "add_layer_effect",
|
|
@@ -11871,7 +12829,7 @@ function createImageEffectTools(controller) {
|
|
|
11871
12829
|
return { globalLight: controller.setGlobalLight(next) };
|
|
11872
12830
|
}
|
|
11873
12831
|
};
|
|
11874
|
-
return [
|
|
12832
|
+
return [list2, add, update, remove, setEnabled, light];
|
|
11875
12833
|
}
|
|
11876
12834
|
|
|
11877
12835
|
// src/image-editor/layers/paint-ui.ts
|
|
@@ -12242,7 +13200,7 @@ function createImagePsdTools(controller) {
|
|
|
12242
13200
|
const open = {
|
|
12243
13201
|
name: "import_psd",
|
|
12244
13202
|
title: "Open a PSD as the document",
|
|
12245
|
-
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).",
|
|
12246
13204
|
inputSchema: { type: "object", properties: { data: DATA }, required: ["data"], additionalProperties: false },
|
|
12247
13205
|
annotations: { title: "Open a PSD as the document", ...DESTRUCTIVE4 },
|
|
12248
13206
|
handler: ({ data }) => {
|
|
@@ -12261,7 +13219,7 @@ function createImagePsdTools(controller) {
|
|
|
12261
13219
|
const save = {
|
|
12262
13220
|
name: "export_psd",
|
|
12263
13221
|
title: "Export the document as a PSD",
|
|
12264
|
-
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.",
|
|
12265
13223
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
12266
13224
|
annotations: { title: "Export the document as a PSD", ...READ5 },
|
|
12267
13225
|
handler: async () => {
|
|
@@ -12728,8 +13686,8 @@ function applyRegionMosaic(imageData, regions, blockSize = 12) {
|
|
|
12728
13686
|
const count = bw * bh;
|
|
12729
13687
|
for (let py = by; py < by + bh; py++) {
|
|
12730
13688
|
const rowOffset = py * width;
|
|
12731
|
-
for (let
|
|
12732
|
-
const idx = (rowOffset +
|
|
13689
|
+
for (let px2 = bx; px2 < bx + bw; px2++) {
|
|
13690
|
+
const idx = (rowOffset + px2) * 4;
|
|
12733
13691
|
rSum += data[idx];
|
|
12734
13692
|
gSum += data[idx + 1];
|
|
12735
13693
|
bSum += data[idx + 2];
|
|
@@ -12742,8 +13700,8 @@ function applyRegionMosaic(imageData, regions, blockSize = 12) {
|
|
|
12742
13700
|
const aAvg = Math.round(aSum / count);
|
|
12743
13701
|
for (let py = by; py < by + bh; py++) {
|
|
12744
13702
|
const rowOffset = py * width;
|
|
12745
|
-
for (let
|
|
12746
|
-
const idx = (rowOffset +
|
|
13703
|
+
for (let px2 = bx; px2 < bx + bw; px2++) {
|
|
13704
|
+
const idx = (rowOffset + px2) * 4;
|
|
12747
13705
|
data[idx] = rAvg;
|
|
12748
13706
|
data[idx + 1] = gAvg;
|
|
12749
13707
|
data[idx + 2] = bAvg;
|
|
@@ -12822,9 +13780,9 @@ function applyRegionBlur(imageData, regions, radius = 10) {
|
|
|
12822
13780
|
// src/image-editor/layers/panel.ts
|
|
12823
13781
|
function layerPanelRows(layers, activeId) {
|
|
12824
13782
|
const rows = [];
|
|
12825
|
-
const walk = (
|
|
12826
|
-
for (let i =
|
|
12827
|
-
const layer =
|
|
13783
|
+
const walk = (list2, depth, parentId) => {
|
|
13784
|
+
for (let i = list2.length - 1; i >= 0; i--) {
|
|
13785
|
+
const layer = list2[i];
|
|
12828
13786
|
rows.push({
|
|
12829
13787
|
layer,
|
|
12830
13788
|
depth,
|
|
@@ -12961,6 +13919,179 @@ async function runLayerMenuAction(controller, action) {
|
|
|
12961
13919
|
}
|
|
12962
13920
|
}
|
|
12963
13921
|
|
|
13922
|
+
// src/image-editor/layers/effects-ui.ts
|
|
13923
|
+
function layerEffectsPanel(layer) {
|
|
13924
|
+
const empty = { readOnly: true, enabled: true, scale: 1, rows: [] };
|
|
13925
|
+
if (!layer) return { ...empty, available: false, reason: "Select a layer to add effects." };
|
|
13926
|
+
if (layer.type === "adjustment") return { ...empty, available: false, reason: "Adjustment layers cannot have effects." };
|
|
13927
|
+
const fx = layer.effects;
|
|
13928
|
+
const readOnly = Boolean(layer.locks?.all);
|
|
13929
|
+
return {
|
|
13930
|
+
available: true,
|
|
13931
|
+
readOnly,
|
|
13932
|
+
reason: readOnly ? "The layer is locked; unlock it to change its effects." : void 0,
|
|
13933
|
+
enabled: fx?.enabled ?? true,
|
|
13934
|
+
scale: fx?.scale ?? 1,
|
|
13935
|
+
rows: (fx?.items ?? []).map((e) => ({ id: e.id, kind: e.kind, label: layerEffectLabel(e.kind), enabled: e.enabled }))
|
|
13936
|
+
};
|
|
13937
|
+
}
|
|
13938
|
+
function layerEffectsBadge(layer) {
|
|
13939
|
+
if (!layer.effects?.items.length) return null;
|
|
13940
|
+
return hasActiveEffects(layer.effects) ? "on" : "off";
|
|
13941
|
+
}
|
|
13942
|
+
var LAYER_EFFECT_MENU = LAYER_EFFECT_KINDS.map((kind) => ({ kind, label: layerEffectLabel(kind) }));
|
|
13943
|
+
var BLENDS = BLEND_MODES.filter((m) => m.id !== "pigment").map((m) => ({ value: m.id, label: m.label }));
|
|
13944
|
+
var opt = (...pairs) => pairs.map(([value, label]) => ({ value, label }));
|
|
13945
|
+
function patternOptions(patterns) {
|
|
13946
|
+
return [...patterns.map((p) => ({ value: p.id, label: p.name || p.id })), ...builtinPatternIds().map((id) => ({ value: id, label: builtinPattern(id).name }))];
|
|
13947
|
+
}
|
|
13948
|
+
function getPath(o, path) {
|
|
13949
|
+
let v = o;
|
|
13950
|
+
for (const p of path.split(".")) {
|
|
13951
|
+
if (v === null || v === void 0) return void 0;
|
|
13952
|
+
if (Array.isArray(v)) v = p === "last" ? v[v.length - 1] : v[Number(p)];
|
|
13953
|
+
else v = v[p];
|
|
13954
|
+
}
|
|
13955
|
+
return v;
|
|
13956
|
+
}
|
|
13957
|
+
function setPath(o, parts, value) {
|
|
13958
|
+
if (!parts.length) return value;
|
|
13959
|
+
const [p, ...rest] = parts;
|
|
13960
|
+
if (Array.isArray(o)) {
|
|
13961
|
+
const i = p === "last" ? o.length - 1 : Number(p);
|
|
13962
|
+
const out = [...o];
|
|
13963
|
+
out[i] = setPath(o[i], rest, value);
|
|
13964
|
+
return out;
|
|
13965
|
+
}
|
|
13966
|
+
const r = o ?? {};
|
|
13967
|
+
return { ...r, [p]: setPath(r[p], rest, value) };
|
|
13968
|
+
}
|
|
13969
|
+
function fillControls(f, allowPattern, patterns) {
|
|
13970
|
+
const types = allowPattern ? opt(["color", "Color"], ["gradient", "Gradient"], ["pattern", "Pattern"]) : opt(["color", "Color"], ["gradient", "Gradient"]);
|
|
13971
|
+
const out = [{ key: "fill.type", label: "Fill", type: "select", value: f.type, options: types }];
|
|
13972
|
+
if (f.type === "color") out.push({ key: "fill.color", label: "Color", type: "color", value: f.color });
|
|
13973
|
+
else if (f.type === "gradient") out.push(...gradientControls("fill.gradient", f.gradient));
|
|
13974
|
+
else {
|
|
13975
|
+
out.push(
|
|
13976
|
+
{ key: "fill.patternId", label: "Pattern", type: "select", value: f.patternId, options: patternOptions(patterns) },
|
|
13977
|
+
{ key: "fill.scale", label: "Scale", type: "range", min: 0.01, max: 10, step: 0.01, value: f.scale, percent: true, unit: "%" }
|
|
13978
|
+
);
|
|
13979
|
+
}
|
|
13980
|
+
return out;
|
|
13981
|
+
}
|
|
13982
|
+
function gradientControls(prefix, g) {
|
|
13983
|
+
return [
|
|
13984
|
+
{ key: `${prefix}.stops.0.color`, label: "Start colour", type: "color", value: g.stops[0]?.color ?? "#000000" },
|
|
13985
|
+
{ key: `${prefix}.stops.last.color`, label: "End colour", type: "color", value: g.stops[g.stops.length - 1]?.color ?? "#ffffff" },
|
|
13986
|
+
{ key: `${prefix}.style`, label: "Style", type: "select", value: g.style, options: opt(["linear", "Linear"], ["radial", "Radial"], ["angle", "Angle"], ["reflected", "Reflected"], ["diamond", "Diamond"]) },
|
|
13987
|
+
{ key: `${prefix}.angle`, label: "Angle", type: "range", min: -180, max: 180, step: 1, value: g.angle, unit: "\xB0" },
|
|
13988
|
+
{ key: `${prefix}.scale`, label: "Scale", type: "range", min: 0.1, max: 1.5, step: 0.01, value: g.scale, percent: true, unit: "%" },
|
|
13989
|
+
{ key: `${prefix}.reverse`, label: "Reverse", type: "checkbox", value: g.reverse },
|
|
13990
|
+
{ key: `${prefix}.align`, label: "Align with layer", type: "checkbox", value: g.align }
|
|
13991
|
+
];
|
|
13992
|
+
}
|
|
13993
|
+
var px = (key, label, value, max = 250) => ({ key, label, type: "range", min: 0, max, step: 1, value, unit: "px" });
|
|
13994
|
+
var pct2 = (key, label, value, min = 0, max = 1) => ({ key, label, type: "range", min, max, step: 0.01, value, percent: true, unit: "%" });
|
|
13995
|
+
var deg = (key, label, value, min = -180, max = 180) => ({ key, label, type: "range", min, max, step: 1, value, unit: "\xB0" });
|
|
13996
|
+
function layerEffectControls(e, patterns = []) {
|
|
13997
|
+
const blended = e.kind === "bevel-emboss" ? [] : [
|
|
13998
|
+
{ key: "blend", label: "Blend mode", type: "select", value: e.blend, options: BLENDS },
|
|
13999
|
+
pct2("opacity", "Opacity", e.opacity)
|
|
14000
|
+
];
|
|
14001
|
+
switch (e.kind) {
|
|
14002
|
+
case "drop-shadow":
|
|
14003
|
+
case "inner-shadow":
|
|
14004
|
+
return [
|
|
14005
|
+
...blended,
|
|
14006
|
+
{ key: "color", label: "Color", type: "color", value: e.color },
|
|
14007
|
+
{ key: "useGlobalLight", label: "Use global light", type: "checkbox", value: e.useGlobalLight },
|
|
14008
|
+
deg("angle", "Angle", e.angle),
|
|
14009
|
+
px("distance", "Distance", e.distance, 500),
|
|
14010
|
+
e.kind === "drop-shadow" ? pct2("spread", "Spread", e.spread) : pct2("choke", "Choke", e.choke),
|
|
14011
|
+
px("size", "Size", e.size),
|
|
14012
|
+
pct2("noise", "Noise", e.noise),
|
|
14013
|
+
...e.kind === "drop-shadow" ? [{ key: "knocksOut", label: "Layer knocks out shadow", type: "checkbox", value: e.knocksOut }] : []
|
|
14014
|
+
];
|
|
14015
|
+
case "outer-glow":
|
|
14016
|
+
case "inner-glow":
|
|
14017
|
+
return [
|
|
14018
|
+
...blended,
|
|
14019
|
+
...fillControls(e.fill, false, patterns),
|
|
14020
|
+
{ key: "technique", label: "Technique", type: "select", value: e.technique, options: opt(["softer", "Softer"], ["precise", "Precise"]) },
|
|
14021
|
+
...e.kind === "inner-glow" ? [{ key: "source", label: "Source", type: "select", value: e.source, options: opt(["edge", "Edge"], ["center", "Center"]) }] : [],
|
|
14022
|
+
e.kind === "outer-glow" ? pct2("spread", "Spread", e.spread) : pct2("choke", "Choke", e.choke),
|
|
14023
|
+
px("size", "Size", e.size),
|
|
14024
|
+
pct2("range", "Range", e.range, 0.01, 1),
|
|
14025
|
+
pct2("noise", "Noise", e.noise)
|
|
14026
|
+
];
|
|
14027
|
+
case "bevel-emboss":
|
|
14028
|
+
return [
|
|
14029
|
+
{ key: "style", label: "Style", type: "select", value: e.style, options: opt(["inner-bevel", "Inner Bevel"], ["outer-bevel", "Outer Bevel"], ["emboss", "Emboss"], ["pillow-emboss", "Pillow Emboss"], ["stroke-emboss", "Stroke Emboss"]) },
|
|
14030
|
+
{ key: "technique", label: "Technique", type: "select", value: e.technique, options: opt(["smooth", "Smooth"], ["chisel-hard", "Chisel Hard"], ["chisel-soft", "Chisel Soft"]) },
|
|
14031
|
+
pct2("depth", "Depth", e.depth, 0, 10),
|
|
14032
|
+
{ key: "direction", label: "Direction", type: "select", value: e.direction, options: opt(["up", "Up"], ["down", "Down"]) },
|
|
14033
|
+
px("size", "Size", e.size),
|
|
14034
|
+
px("soften", "Soften", e.soften, 16),
|
|
14035
|
+
{ key: "useGlobalLight", label: "Use global light", type: "checkbox", value: e.useGlobalLight },
|
|
14036
|
+
deg("angle", "Angle", e.angle),
|
|
14037
|
+
deg("altitude", "Altitude", e.altitude, 0, 90),
|
|
14038
|
+
{ key: "highlightColor", label: "Highlight", type: "color", value: e.highlightColor },
|
|
14039
|
+
{ key: "highlightBlend", label: "Highlight mode", type: "select", value: e.highlightBlend, options: BLENDS },
|
|
14040
|
+
pct2("highlightOpacity", "Highlight opacity", e.highlightOpacity),
|
|
14041
|
+
{ key: "shadowColor", label: "Shadow", type: "color", value: e.shadowColor },
|
|
14042
|
+
{ key: "shadowBlend", label: "Shadow mode", type: "select", value: e.shadowBlend, options: BLENDS },
|
|
14043
|
+
pct2("shadowOpacity", "Shadow opacity", e.shadowOpacity)
|
|
14044
|
+
];
|
|
14045
|
+
case "satin":
|
|
14046
|
+
return [
|
|
14047
|
+
...blended,
|
|
14048
|
+
{ key: "color", label: "Color", type: "color", value: e.color },
|
|
14049
|
+
deg("angle", "Angle", e.angle),
|
|
14050
|
+
px("distance", "Distance", e.distance),
|
|
14051
|
+
px("size", "Size", e.size),
|
|
14052
|
+
{ key: "invert", label: "Invert", type: "checkbox", value: e.invert }
|
|
14053
|
+
];
|
|
14054
|
+
case "color-overlay":
|
|
14055
|
+
return [...blended, { key: "color", label: "Color", type: "color", value: e.color }];
|
|
14056
|
+
case "gradient-overlay":
|
|
14057
|
+
return [...blended, ...gradientControls("gradient", e.gradient)];
|
|
14058
|
+
case "pattern-overlay":
|
|
14059
|
+
return [
|
|
14060
|
+
...blended,
|
|
14061
|
+
{ key: "patternId", label: "Pattern", type: "select", value: e.patternId, options: patternOptions(patterns) },
|
|
14062
|
+
pct2("scale", "Scale", e.scale, 0.01, 10),
|
|
14063
|
+
deg("angle", "Angle", e.angle ?? 0),
|
|
14064
|
+
{ key: "link", label: "Link with layer", type: "checkbox", value: e.link ?? true }
|
|
14065
|
+
];
|
|
14066
|
+
case "stroke":
|
|
14067
|
+
return [
|
|
14068
|
+
...blended,
|
|
14069
|
+
px("size", "Size", e.size),
|
|
14070
|
+
{ key: "position", label: "Position", type: "select", value: e.position, options: opt(["outside", "Outside"], ["inside", "Inside"], ["center", "Center"]) },
|
|
14071
|
+
...fillControls(e.fill, true, patterns)
|
|
14072
|
+
];
|
|
14073
|
+
}
|
|
14074
|
+
}
|
|
14075
|
+
function fillOfType(current, type, patterns) {
|
|
14076
|
+
if (type === "gradient") return { type: "gradient", gradient: defaultEffectGradient() };
|
|
14077
|
+
if (type === "pattern") return { type: "pattern", patternId: patterns[0]?.id ?? "checker", scale: 1 };
|
|
14078
|
+
return { type: "color", color: current.type === "color" ? current.color : "#000000" };
|
|
14079
|
+
}
|
|
14080
|
+
function effectControlChange(effect, key, value, patterns = []) {
|
|
14081
|
+
const [top, ...rest] = key.split(".");
|
|
14082
|
+
if (top === "fill" && rest[0] === "type") return { fill: fillOfType(effect.fill, String(value), patterns) };
|
|
14083
|
+
if (top === "patternId" && !rest.length) {
|
|
14084
|
+
const name = patternOptions(patterns).find((o) => o.value === value)?.label;
|
|
14085
|
+
return name ? { patternId: value, name } : { patternId: value };
|
|
14086
|
+
}
|
|
14087
|
+
if (!rest.length) return { [top]: value };
|
|
14088
|
+
if (top === "fill" && rest[0] === "patternId") {
|
|
14089
|
+
const name = patternOptions(patterns).find((o) => o.value === value)?.label;
|
|
14090
|
+
return { fill: { ...effect.fill, patternId: value, ...name ? { name } : {} } };
|
|
14091
|
+
}
|
|
14092
|
+
return { [top]: setPath(getPath(effect, top), rest, value) };
|
|
14093
|
+
}
|
|
14094
|
+
|
|
12964
14095
|
// src/image-editor/psd/ui.ts
|
|
12965
14096
|
var PSD_FILE_ACCEPT = ".psd,.psb,image/vnd.adobe.photoshop,application/x-photoshop";
|
|
12966
14097
|
function blobBytes(file) {
|
|
@@ -13011,6 +14142,7 @@ export {
|
|
|
13011
14142
|
DEFAULT_ADJUSTMENTS,
|
|
13012
14143
|
DEFAULT_BRUSH,
|
|
13013
14144
|
DEFAULT_DEPTH_MASK,
|
|
14145
|
+
DEFAULT_GLOBAL_LIGHT,
|
|
13014
14146
|
DEFAULT_MIXER,
|
|
13015
14147
|
DEFAULT_REFINE_EDGE_PARAMS,
|
|
13016
14148
|
DEFAULT_SELECTION_TOOL_OPTIONS,
|
|
@@ -13031,11 +14163,15 @@ export {
|
|
|
13031
14163
|
ImageEditorRenderer,
|
|
13032
14164
|
LATEST_PROCESS_VERSION,
|
|
13033
14165
|
LAYERED_SCHEMA_VERSION,
|
|
14166
|
+
LAYER_EFFECT_KINDS,
|
|
14167
|
+
LAYER_EFFECT_MENU,
|
|
13034
14168
|
LEGACY_PROCESS_VERSION,
|
|
13035
14169
|
LUMA_709,
|
|
13036
14170
|
LayerCompositor,
|
|
14171
|
+
LayerEffectError,
|
|
13037
14172
|
LayerOperationError,
|
|
13038
14173
|
MAX_BRUSH_SIZE,
|
|
14174
|
+
MAX_EFFECT_REACH,
|
|
13039
14175
|
MAX_KELVIN,
|
|
13040
14176
|
MIN_KELVIN,
|
|
13041
14177
|
MIRED_PER_TEMPERATURE_UNIT,
|
|
@@ -13046,6 +14182,7 @@ export {
|
|
|
13046
14182
|
MIXING_MODELS,
|
|
13047
14183
|
MixerStroke,
|
|
13048
14184
|
PAINT_TOOLS,
|
|
14185
|
+
PATTERN_BLOCK_KEYS,
|
|
13049
14186
|
PIGMENT_BANDS,
|
|
13050
14187
|
PIGMENT_LAMBDA_MIN,
|
|
13051
14188
|
PIGMENT_LAMBDA_STEP,
|
|
@@ -13054,6 +14191,8 @@ export {
|
|
|
13054
14191
|
PSD_FILE_ACCEPT,
|
|
13055
14192
|
PaintGesture,
|
|
13056
14193
|
PaintStrokeSession,
|
|
14194
|
+
PatternPlacement,
|
|
14195
|
+
PatternTile,
|
|
13057
14196
|
PsdError,
|
|
13058
14197
|
REFINE_EDGE_CONTROLS,
|
|
13059
14198
|
SELECTION_MODES,
|
|
@@ -13113,6 +14252,8 @@ export {
|
|
|
13113
14252
|
bradfordAdaptation,
|
|
13114
14253
|
buildLayerMaskPlanes,
|
|
13115
14254
|
buildToneLut,
|
|
14255
|
+
builtinPattern,
|
|
14256
|
+
builtinPatternIds,
|
|
13116
14257
|
bytesToBase64,
|
|
13117
14258
|
calculateCropDrag,
|
|
13118
14259
|
canvasCompositeOperation,
|
|
@@ -13161,6 +14302,8 @@ export {
|
|
|
13161
14302
|
decodePngDataUrl,
|
|
13162
14303
|
decodeSelectionRle,
|
|
13163
14304
|
defaultAdjustmentParams,
|
|
14305
|
+
defaultEffectGradient,
|
|
14306
|
+
defaultLayerEffect,
|
|
13164
14307
|
deflate,
|
|
13165
14308
|
deflateRaw,
|
|
13166
14309
|
depthMaskAlpha,
|
|
@@ -13171,6 +14314,7 @@ export {
|
|
|
13171
14314
|
deserializeEffects,
|
|
13172
14315
|
deserializeLayer,
|
|
13173
14316
|
deserializeLayers,
|
|
14317
|
+
deserializePatterns,
|
|
13174
14318
|
deserializeSelection,
|
|
13175
14319
|
developFilter,
|
|
13176
14320
|
developFromAdjustments,
|
|
@@ -13181,6 +14325,12 @@ export {
|
|
|
13181
14325
|
dropZone,
|
|
13182
14326
|
duplicateLayer,
|
|
13183
14327
|
edgeWidth,
|
|
14328
|
+
effectColorHex,
|
|
14329
|
+
effectControlChange,
|
|
14330
|
+
effectReach,
|
|
14331
|
+
effectsFingerprint,
|
|
14332
|
+
effectsMargin,
|
|
14333
|
+
effectsScale,
|
|
13184
14334
|
ellipsePoints,
|
|
13185
14335
|
ellipseSelection,
|
|
13186
14336
|
emptyHistogram,
|
|
@@ -13214,8 +14364,10 @@ export {
|
|
|
13214
14364
|
guidePlanes,
|
|
13215
14365
|
guidedFilterColor,
|
|
13216
14366
|
handlePaintShortcut,
|
|
14367
|
+
hasActiveEffects,
|
|
13217
14368
|
hasResampledLegacyMask,
|
|
13218
14369
|
hashMaskContent,
|
|
14370
|
+
hashNoise,
|
|
13219
14371
|
hexToRgb,
|
|
13220
14372
|
histogramPath,
|
|
13221
14373
|
hitTestAnnotation,
|
|
@@ -13241,6 +14393,7 @@ export {
|
|
|
13241
14393
|
isAdjustmentKind,
|
|
13242
14394
|
isBlendMode,
|
|
13243
14395
|
isDefaultDepthMask,
|
|
14396
|
+
isLayerEffectKind,
|
|
13244
14397
|
isLayerLocked,
|
|
13245
14398
|
isLegacyShape,
|
|
13246
14399
|
isPaintTool,
|
|
@@ -13253,7 +14406,12 @@ export {
|
|
|
13253
14406
|
isSpatialFilter,
|
|
13254
14407
|
kelvinToTemperature,
|
|
13255
14408
|
layerBadge,
|
|
14409
|
+
layerEffectControls,
|
|
14410
|
+
layerEffectFields,
|
|
14411
|
+
layerEffectLabel,
|
|
14412
|
+
layerEffectsBadge,
|
|
13256
14413
|
layerEffectsOf,
|
|
14414
|
+
layerEffectsPanel,
|
|
13257
14415
|
layerLabel,
|
|
13258
14416
|
layerMaskCoverage,
|
|
13259
14417
|
layerMaskFromSelection,
|
|
@@ -13278,6 +14436,7 @@ export {
|
|
|
13278
14436
|
mapCropHandle,
|
|
13279
14437
|
mapLayer,
|
|
13280
14438
|
maskSummary,
|
|
14439
|
+
mergeLayerEffect,
|
|
13281
14440
|
migrateSerializedImageEditorState,
|
|
13282
14441
|
migrateToLayeredState,
|
|
13283
14442
|
mixPigment8,
|
|
@@ -13290,6 +14449,7 @@ export {
|
|
|
13290
14449
|
mulMatrix3,
|
|
13291
14450
|
needsLegacyBase,
|
|
13292
14451
|
neutralHueRange,
|
|
14452
|
+
newEffectId,
|
|
13293
14453
|
newLayerId,
|
|
13294
14454
|
normalizeAdjustmentLayer,
|
|
13295
14455
|
normalizeAdjustmentLayers,
|
|
@@ -13297,6 +14457,7 @@ export {
|
|
|
13297
14457
|
normalizeBlendMode,
|
|
13298
14458
|
normalizeBrush,
|
|
13299
14459
|
normalizeFilter,
|
|
14460
|
+
normalizeLayerEffects,
|
|
13300
14461
|
normalizeLayerMask,
|
|
13301
14462
|
normalizeMixer,
|
|
13302
14463
|
normalizeProcessVersion,
|
|
@@ -13310,6 +14471,10 @@ export {
|
|
|
13310
14471
|
parseBlend,
|
|
13311
14472
|
parseColor,
|
|
13312
14473
|
parseDataUrl,
|
|
14474
|
+
parseEffectColor,
|
|
14475
|
+
parseLfx2,
|
|
14476
|
+
parseLrfx,
|
|
14477
|
+
parsePatternBlock,
|
|
13313
14478
|
parseSelectionMode,
|
|
13314
14479
|
parseVersionedDescriptor,
|
|
13315
14480
|
photoFilterMatrix,
|
|
@@ -13327,6 +14492,7 @@ export {
|
|
|
13327
14492
|
projectQuarterTurn,
|
|
13328
14493
|
psdBytesFromBase64,
|
|
13329
14494
|
psdFileName,
|
|
14495
|
+
psdPatternToEffectPattern,
|
|
13330
14496
|
psdToLayers,
|
|
13331
14497
|
quickMaskPixels,
|
|
13332
14498
|
rangeLightness,
|
|
@@ -13346,6 +14512,7 @@ export {
|
|
|
13346
14512
|
removeLayer,
|
|
13347
14513
|
removeLayerEffect,
|
|
13348
14514
|
removeLayerFilter,
|
|
14515
|
+
renderLayerEffects,
|
|
13349
14516
|
renderLayeredBase,
|
|
13350
14517
|
requireLayer,
|
|
13351
14518
|
requireUnlocked,
|
|
@@ -13360,6 +14527,7 @@ export {
|
|
|
13360
14527
|
runLayerMenuAction,
|
|
13361
14528
|
sampleLut,
|
|
13362
14529
|
sampleToneLut,
|
|
14530
|
+
scaleLayerEffect,
|
|
13363
14531
|
selectionArea,
|
|
13364
14532
|
selectionFromCoverage,
|
|
13365
14533
|
selectionFromPixels,
|
|
@@ -13372,6 +14540,7 @@ export {
|
|
|
13372
14540
|
serializeLayer,
|
|
13373
14541
|
serializeLayers,
|
|
13374
14542
|
serializeLayersExternal,
|
|
14543
|
+
serializePatterns,
|
|
13375
14544
|
serializeSelection,
|
|
13376
14545
|
setControlPath,
|
|
13377
14546
|
setLayerEffects,
|
|
@@ -13413,6 +14582,9 @@ export {
|
|
|
13413
14582
|
updateLayerFilter,
|
|
13414
14583
|
updateLayerMaskProps,
|
|
13415
14584
|
uvToXy,
|
|
14585
|
+
validateEffectGradient,
|
|
14586
|
+
validateLayerEffect,
|
|
14587
|
+
validateLayerEffects,
|
|
13416
14588
|
versionedDescriptorBytes,
|
|
13417
14589
|
vibranceLinear,
|
|
13418
14590
|
vibrancePixel,
|
|
@@ -13422,6 +14594,8 @@ export {
|
|
|
13422
14594
|
whiteBalanceMatrix,
|
|
13423
14595
|
withNewIds,
|
|
13424
14596
|
writeAdjustmentBlock,
|
|
14597
|
+
writeLfx2,
|
|
14598
|
+
writePatternBlock,
|
|
13425
14599
|
writePsd,
|
|
13426
14600
|
writeRegion,
|
|
13427
14601
|
xyToTempTint,
|