@pixerate/editor 0.95.0 → 0.96.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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-E2ILRYTJ.js";
61
+ } from "../chunk-KFTQ2PKP.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 clamp3 = (v) => Math.max(-100, Math.min(100, v));
312
- return { temperature: clamp3(kelvinToTemperature(kelvin)), tint: clamp3(tint) };
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;
@@ -903,21 +919,21 @@ function toBase64(bytes, length) {
903
919
  parts.push(chunk);
904
920
  return parts.join("");
905
921
  }
906
- function fromBase64(text) {
907
- if (text.length % 4 !== 0) throw new Error("Invalid base64 length");
922
+ function fromBase64(text2) {
923
+ if (text2.length % 4 !== 0) throw new Error("Invalid base64 length");
908
924
  let pad = 0;
909
- if (text.endsWith("==")) pad = 2;
910
- else if (text.endsWith("=")) pad = 1;
911
- const out = new Uint8Array(text.length / 4 * 3 - pad);
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 = text.charCodeAt(i + k);
915
- if (ch === 61 && i + 4 === text.length && k >= 4 - pad) return 0;
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 < text.length; i += 4) {
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;
@@ -2428,10 +2444,10 @@ function depthMaskAlpha(depth, options) {
2428
2444
  }
2429
2445
  var filterSpecCache = /* @__PURE__ */ new WeakMap();
2430
2446
  function pixelFilterSpec(layer) {
2431
- const list = layer.filters;
2432
- if (!list?.length) return null;
2433
- if (filterSpecCache.has(list)) return filterSpecCache.get(list);
2434
- const filters = list.filter(isActiveFilter);
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(list, spec);
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 = (list) => {
2667
- for (const l of list) {
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 = (list) => {
2702
- for (const l of list) {
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);
@@ -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);
@@ -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 = (list) => list.map((l) => {
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(long2) {
5191
- return long2 ? this.u64() : this.u32();
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(long2) {
5331
+ beginLen(long3) {
5299
5332
  const at = this.length;
5300
- if (long2) this.u64(0);
5333
+ if (long3) this.u64(0);
5301
5334
  else this.u32(0);
5302
- return { at, long: long2 };
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 unit = r.ascii(4);
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 dbl = (v) => ({ type: "doub", value: clamp2(v, 0, 1) * 255 });
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 ", dbl(tint[0])], ["Grn ", dbl(tint[1])], ["Bl ", dbl(tint[2])]]) }],
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 long2 = psb && PSB_LONG_KEYS.has(b.key);
6409
- w.ascii(long2 ? "8B64" : "8BIM");
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 (long2) w.u64(b.data.length + pad);
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 list = this.groups.get(message) ?? [];
6710
- if (!list.includes(layer)) list.push(layer);
6711
- this.groups.set(message, list);
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 common = (layer, extra = []) => {
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 = common(layer, [{ key: "lsct", data: sectionBlock(layer.collapsed ? SECTION_CLOSED : SECTION_OPEN, blendKey) }]);
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
- ...common(layer, [{ key: block.key, data: block.data }]),
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 = { ...common(layer, extra), rect: EMPTY_RECT, channels: [], blendKey: BLEND_TO_PSD[layer.blend] ?? "norm" };
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
- return { bytes, psb, records: records.length };
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 { width, height, surfaceOf: (l) => this.surfaceOf(l), version: this.sourceVersion(), globalLight: this.state.globalLight };
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;
@@ -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 };
@@ -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
  };
@@ -10401,17 +11357,17 @@ function controlTrackBackground(spec) {
10401
11357
  return void 0;
10402
11358
  }
10403
11359
  }
10404
- function getControlPath(obj, path) {
10405
- let cur = obj;
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(obj, path, value) {
11368
+ function setControlPath(obj2, path, value) {
10413
11369
  const parts = path.split(".");
10414
- const root = Array.isArray(obj) ? [...obj] : { ...obj };
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 pct = sum / 255 / mask.data.length * 100;
10473
- const shown = pct >= 10 ? Math.round(pct) : Math.round(pct * 10) / 10;
11428
+ const pct2 = sum / 255 / mask.data.length * 100;
11429
+ const shown = pct2 >= 10 ? Math.round(pct2) : Math.round(pct2 * 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 num2 = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
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: num2(feather, 0) };
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: num2(x, NaN), y: num2(y, NaN), width: num2(width, NaN), height: num2(height, NaN) };
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 common = {
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 }], { ...common, fuzziness: num2(fuzziness, 40) });
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 }, { ...common, tolerance: num2(tolerance, 32), contiguous: contiguous !== false });
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, num2(radius, 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: num2(params.radius, 8),
11597
+ radius: num3(params.radius, 8),
10642
11598
  smartRadius: params.smartRadius === true,
10643
- smooth: num2(params.smooth, 0),
10644
- feather: num2(params.feather, 0),
10645
- contrast: num2(params.contrast, 0),
10646
- shiftEdge: num2(params.shiftEdge, 0)
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 pct = (n) => h.samples ? round(n / h.samples * 100) : 0;
11749
+ const pct2 = (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: pct(h.shadows[0]), green: pct(h.shadows[1]), blue: pct(h.shadows[2]) },
10800
- highlightsPercent: { red: pct(h.highlights[0]), green: pct(h.highlights[1]), blue: pct(h.highlights[2]) }
11755
+ shadowsPercent: { red: pct2(h.shadows[0]), green: pct2(h.shadows[1]), blue: pct2(h.shadows[2]) },
11756
+ highlightsPercent: { red: pct2(h.highlights[0]), green: pct2(h.highlights[1]), blue: pct2(h.highlights[2]) }
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: {
@@ -11743,11 +12699,13 @@ function settingsSchema(forUpdate) {
11743
12699
  additionalProperties: false
11744
12700
  };
11745
12701
  }
11746
- function patternList() {
11747
- return builtinPatternIds().map((id) => {
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 list = {
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 [list, add, update, remove, setEnabled, light];
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 locks 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).",
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 masks, 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.",
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 () => {
@@ -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 = (list, depth, parentId) => {
12826
- for (let i = list.length - 1; i >= 0; i--) {
12827
- const layer = list[i];
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,
@@ -13011,6 +13969,7 @@ export {
13011
13969
  DEFAULT_ADJUSTMENTS,
13012
13970
  DEFAULT_BRUSH,
13013
13971
  DEFAULT_DEPTH_MASK,
13972
+ DEFAULT_GLOBAL_LIGHT,
13014
13973
  DEFAULT_MIXER,
13015
13974
  DEFAULT_REFINE_EDGE_PARAMS,
13016
13975
  DEFAULT_SELECTION_TOOL_OPTIONS,
@@ -13031,11 +13990,14 @@ export {
13031
13990
  ImageEditorRenderer,
13032
13991
  LATEST_PROCESS_VERSION,
13033
13992
  LAYERED_SCHEMA_VERSION,
13993
+ LAYER_EFFECT_KINDS,
13034
13994
  LEGACY_PROCESS_VERSION,
13035
13995
  LUMA_709,
13036
13996
  LayerCompositor,
13997
+ LayerEffectError,
13037
13998
  LayerOperationError,
13038
13999
  MAX_BRUSH_SIZE,
14000
+ MAX_EFFECT_REACH,
13039
14001
  MAX_KELVIN,
13040
14002
  MIN_KELVIN,
13041
14003
  MIRED_PER_TEMPERATURE_UNIT,
@@ -13046,6 +14008,7 @@ export {
13046
14008
  MIXING_MODELS,
13047
14009
  MixerStroke,
13048
14010
  PAINT_TOOLS,
14011
+ PATTERN_BLOCK_KEYS,
13049
14012
  PIGMENT_BANDS,
13050
14013
  PIGMENT_LAMBDA_MIN,
13051
14014
  PIGMENT_LAMBDA_STEP,
@@ -13054,6 +14017,8 @@ export {
13054
14017
  PSD_FILE_ACCEPT,
13055
14018
  PaintGesture,
13056
14019
  PaintStrokeSession,
14020
+ PatternPlacement,
14021
+ PatternTile,
13057
14022
  PsdError,
13058
14023
  REFINE_EDGE_CONTROLS,
13059
14024
  SELECTION_MODES,
@@ -13113,6 +14078,8 @@ export {
13113
14078
  bradfordAdaptation,
13114
14079
  buildLayerMaskPlanes,
13115
14080
  buildToneLut,
14081
+ builtinPattern,
14082
+ builtinPatternIds,
13116
14083
  bytesToBase64,
13117
14084
  calculateCropDrag,
13118
14085
  canvasCompositeOperation,
@@ -13161,6 +14128,8 @@ export {
13161
14128
  decodePngDataUrl,
13162
14129
  decodeSelectionRle,
13163
14130
  defaultAdjustmentParams,
14131
+ defaultEffectGradient,
14132
+ defaultLayerEffect,
13164
14133
  deflate,
13165
14134
  deflateRaw,
13166
14135
  depthMaskAlpha,
@@ -13171,6 +14140,7 @@ export {
13171
14140
  deserializeEffects,
13172
14141
  deserializeLayer,
13173
14142
  deserializeLayers,
14143
+ deserializePatterns,
13174
14144
  deserializeSelection,
13175
14145
  developFilter,
13176
14146
  developFromAdjustments,
@@ -13181,6 +14151,11 @@ export {
13181
14151
  dropZone,
13182
14152
  duplicateLayer,
13183
14153
  edgeWidth,
14154
+ effectColorHex,
14155
+ effectReach,
14156
+ effectsFingerprint,
14157
+ effectsMargin,
14158
+ effectsScale,
13184
14159
  ellipsePoints,
13185
14160
  ellipseSelection,
13186
14161
  emptyHistogram,
@@ -13214,8 +14189,10 @@ export {
13214
14189
  guidePlanes,
13215
14190
  guidedFilterColor,
13216
14191
  handlePaintShortcut,
14192
+ hasActiveEffects,
13217
14193
  hasResampledLegacyMask,
13218
14194
  hashMaskContent,
14195
+ hashNoise,
13219
14196
  hexToRgb,
13220
14197
  histogramPath,
13221
14198
  hitTestAnnotation,
@@ -13241,6 +14218,7 @@ export {
13241
14218
  isAdjustmentKind,
13242
14219
  isBlendMode,
13243
14220
  isDefaultDepthMask,
14221
+ isLayerEffectKind,
13244
14222
  isLayerLocked,
13245
14223
  isLegacyShape,
13246
14224
  isPaintTool,
@@ -13253,6 +14231,8 @@ export {
13253
14231
  isSpatialFilter,
13254
14232
  kelvinToTemperature,
13255
14233
  layerBadge,
14234
+ layerEffectFields,
14235
+ layerEffectLabel,
13256
14236
  layerEffectsOf,
13257
14237
  layerLabel,
13258
14238
  layerMaskCoverage,
@@ -13278,6 +14258,7 @@ export {
13278
14258
  mapCropHandle,
13279
14259
  mapLayer,
13280
14260
  maskSummary,
14261
+ mergeLayerEffect,
13281
14262
  migrateSerializedImageEditorState,
13282
14263
  migrateToLayeredState,
13283
14264
  mixPigment8,
@@ -13290,6 +14271,7 @@ export {
13290
14271
  mulMatrix3,
13291
14272
  needsLegacyBase,
13292
14273
  neutralHueRange,
14274
+ newEffectId,
13293
14275
  newLayerId,
13294
14276
  normalizeAdjustmentLayer,
13295
14277
  normalizeAdjustmentLayers,
@@ -13297,6 +14279,7 @@ export {
13297
14279
  normalizeBlendMode,
13298
14280
  normalizeBrush,
13299
14281
  normalizeFilter,
14282
+ normalizeLayerEffects,
13300
14283
  normalizeLayerMask,
13301
14284
  normalizeMixer,
13302
14285
  normalizeProcessVersion,
@@ -13310,6 +14293,10 @@ export {
13310
14293
  parseBlend,
13311
14294
  parseColor,
13312
14295
  parseDataUrl,
14296
+ parseEffectColor,
14297
+ parseLfx2,
14298
+ parseLrfx,
14299
+ parsePatternBlock,
13313
14300
  parseSelectionMode,
13314
14301
  parseVersionedDescriptor,
13315
14302
  photoFilterMatrix,
@@ -13327,6 +14314,7 @@ export {
13327
14314
  projectQuarterTurn,
13328
14315
  psdBytesFromBase64,
13329
14316
  psdFileName,
14317
+ psdPatternToEffectPattern,
13330
14318
  psdToLayers,
13331
14319
  quickMaskPixels,
13332
14320
  rangeLightness,
@@ -13346,6 +14334,7 @@ export {
13346
14334
  removeLayer,
13347
14335
  removeLayerEffect,
13348
14336
  removeLayerFilter,
14337
+ renderLayerEffects,
13349
14338
  renderLayeredBase,
13350
14339
  requireLayer,
13351
14340
  requireUnlocked,
@@ -13360,6 +14349,7 @@ export {
13360
14349
  runLayerMenuAction,
13361
14350
  sampleLut,
13362
14351
  sampleToneLut,
14352
+ scaleLayerEffect,
13363
14353
  selectionArea,
13364
14354
  selectionFromCoverage,
13365
14355
  selectionFromPixels,
@@ -13372,6 +14362,7 @@ export {
13372
14362
  serializeLayer,
13373
14363
  serializeLayers,
13374
14364
  serializeLayersExternal,
14365
+ serializePatterns,
13375
14366
  serializeSelection,
13376
14367
  setControlPath,
13377
14368
  setLayerEffects,
@@ -13413,6 +14404,9 @@ export {
13413
14404
  updateLayerFilter,
13414
14405
  updateLayerMaskProps,
13415
14406
  uvToXy,
14407
+ validateEffectGradient,
14408
+ validateLayerEffect,
14409
+ validateLayerEffects,
13416
14410
  versionedDescriptorBytes,
13417
14411
  vibranceLinear,
13418
14412
  vibrancePixel,
@@ -13422,6 +14416,8 @@ export {
13422
14416
  whiteBalanceMatrix,
13423
14417
  withNewIds,
13424
14418
  writeAdjustmentBlock,
14419
+ writeLfx2,
14420
+ writePatternBlock,
13425
14421
  writePsd,
13426
14422
  writeRegion,
13427
14423
  xyToTempTint,