@pixerate/editor 0.102.0 → 0.104.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.
Files changed (59) hide show
  1. package/THIRD_PARTY_NOTICES.md +3 -0
  2. package/dist/agent/index.cjs +7 -7
  3. package/dist/agent/index.cjs.map +1 -1
  4. package/dist/agent/index.js +7 -7
  5. package/dist/canvas/index.cjs +3 -3
  6. package/dist/canvas/index.cjs.map +1 -1
  7. package/dist/canvas/index.js +4 -4
  8. package/dist/chunk-6BU5WU4R.cjs +1 -0
  9. package/dist/chunk-6BU5WU4R.cjs.map +1 -0
  10. package/dist/{chunk-IB53LVGV.js → chunk-DS7KDKPX.js} +1 -1
  11. package/dist/chunk-NQLQ2JOI.js +7176 -0
  12. package/dist/chunk-NQLQ2JOI.js.map +1 -0
  13. package/dist/{chunk-ZZOTHJ4K.cjs → chunk-PV42JITT.cjs} +1 -1
  14. package/dist/{chunk-ZZOTHJ4K.cjs.map → chunk-PV42JITT.cjs.map} +1 -1
  15. package/dist/chunk-VSMM7B3P.cjs +7176 -0
  16. package/dist/chunk-VSMM7B3P.cjs.map +1 -0
  17. package/dist/chunk-XYBJKUAU.js +1 -0
  18. package/dist/chunk-XYBJKUAU.js.map +1 -0
  19. package/dist/compositor-B1ZrKR89.d.ts +835 -0
  20. package/dist/compositor-CTMU-B1t.d.cts +835 -0
  21. package/dist/export-BLpMyCYg.d.ts +3139 -0
  22. package/dist/export-X9rY79p0.d.cts +3139 -0
  23. package/dist/homography-CZ-tFjay.d.cts +50 -0
  24. package/dist/homography-Ctfjoc1b.d.ts +50 -0
  25. package/dist/image-editor/index.cjs +2846 -9407
  26. package/dist/image-editor/index.cjs.map +1 -1
  27. package/dist/image-editor/index.d.cts +24 -3151
  28. package/dist/image-editor/index.d.ts +24 -3151
  29. package/dist/image-editor/index.js +2508 -9069
  30. package/dist/image-editor/index.js.map +1 -1
  31. package/dist/mixer-DG_9Yu2W.d.ts +282 -0
  32. package/dist/mixer-DGcH6HLu.d.cts +282 -0
  33. package/dist/paint/index.cjs +4 -3
  34. package/dist/paint/index.cjs.map +1 -1
  35. package/dist/paint/index.d.cts +3 -281
  36. package/dist/paint/index.d.ts +3 -281
  37. package/dist/paint/index.js +2 -1
  38. package/dist/paint-editor/index.cjs +4417 -0
  39. package/dist/paint-editor/index.cjs.map +1 -0
  40. package/dist/paint-editor/index.d.cts +1539 -0
  41. package/dist/paint-editor/index.d.ts +1539 -0
  42. package/dist/paint-editor/index.js +4417 -0
  43. package/dist/paint-editor/index.js.map +1 -0
  44. package/dist/raster/index.d.cts +6 -835
  45. package/dist/raster/index.d.ts +6 -835
  46. package/dist/{surface-DIA-XatS.d.cts → surface-AfHCH7BR.d.cts} +1 -1
  47. package/dist/{surface-DIA-XatS.d.ts → surface-AfHCH7BR.d.ts} +1 -1
  48. package/dist/tools/index.cjs +3 -3
  49. package/dist/tools/index.cjs.map +1 -1
  50. package/dist/tools/index.js +3 -3
  51. package/dist/vector/index.d.cts +2 -1
  52. package/dist/vector/index.d.ts +2 -1
  53. package/dist/{warp-B8rAguzs.d.ts → warp-CWHZzE3x.d.ts} +2 -49
  54. package/dist/{warp-nYs2DPrx.d.cts → warp-D5jz_tN7.d.cts} +2 -49
  55. package/package.json +6 -1
  56. package/skills/README.md +1 -0
  57. package/skills/pixerate-image-editor/SKILL.md +14 -5
  58. package/skills/pixerate-paint-editor/SKILL.md +203 -0
  59. /package/dist/{chunk-IB53LVGV.js.map → chunk-DS7KDKPX.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/chunk-VSMM7B3P.cjs","../src/image-editor/color.ts","../src/image-editor/geometry.ts","../src/image-editor/selection/mask.ts","../src/image-editor/selection/modify.ts","../src/image-editor/selection/layer-mask.ts","../src/image-editor/adjustment-layers.ts","../src/image-editor/filters.ts","../src/image-editor/layers/model.ts","../src/image-editor/layers/compositor.ts","../src/image-editor/transform.ts","../src/image-editor/layers/ops.ts","../src/image-editor/layers/smart.ts","../src/image-editor/layers/defaults.ts","../src/image-editor/layers/serialize.ts","../src/image-editor/psd/binary.ts","../src/image-editor/psd/descriptor.ts","../src/image-editor/psd/adjust.ts","../src/image-editor/psd/compression.ts","../src/image-editor/psd/file.ts","../src/image-editor/psd/pixels.ts","../src/image-editor/psd/patterns.ts","../src/image-editor/psd/import.ts","../src/image-editor/psd/effects.ts","../src/image-editor/psd/export.ts"],"names":["lu","lv","clamp","x","y","q","v","op","list","fail","lightness","props","effects","mask","layer","patternIds"],"mappings":"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UAAA;AACmB,UAAA;AACA,UAAA;AACpC,UAAA;AACqC,YAAA;AACD,YAAA;AACA,YAAA;AACA,YAAA;AACnD,UAAA;AACK,QAAA;AACuC,UAAA;AAC9C,QAAA;AACgB,QAAA;AACoC,UAAA;AACvB,UAAA;AACd,UAAA;AACf,QAAA;AACgB,QAAA;AAChB,QAAA;AACF,MAAA;AACK,MAAA;AAC+C,QAAA;AACtD,IAAA;AACF,EAAA;AAC6C,EAAA;AACE,EAAA;AAG7B,EAAA;AACkC,EAAA;AAES,EAAA;AAClB,EAAA;AACS,EAAA;AACL,EAAA;AACF,EAAA;AAEC,EAAA;AACrB,EAAA;AACG,EAAA;AACI,IAAA;AACtB,IAAA;AACA,IAAA;AACV,EAAA;AAC8B,EAAA;AACjB,EAAA;AACD,EAAA;AACqB,EAAA;AACA,EAAA;AACpB,EAAA;AACoB,EAAA;AACS,IAAA;AACK,IAAA;AAC/C,EAAA;AACuB,EAAA;AACM,IAAA;AACD,MAAA;AACL,MAAA;AACH,MAAA;AAClB,IAAA;AACqD,IAAA;AACvD,EAAA;AAEwC,EAAA;AACnB,EAAA;AACuC,IAAA;AAC1B,IAAA;AACH,IAAA;AACgB,MAAA;AACxB,MAAA;AACK,MAAA;AAC1B,IAAA;AACyD,IAAA;AAC3D,EAAA;AAEc,EAAA;AACZ,IAAA;AACyD,MAAA;AACxB,MAAA;AAC/B,MAAA;AACQ,MAAA;AACqC,MAAA;AAC7C,MAAA;AACkB,MAAA;AACoC,MAAA;AACxD,IAAA;AACA,IAAA;AACF,EAAA;AAC6D,EAAA;AACN,EAAA;AACzD;AxBy6MiE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/chunk-VSMM7B3P.cjs","sourcesContent":[null,"// Portions ported from lightcraft-main/crates/color/src/cct.rs, lightcraft-main/crates/color/src/lib.rs,\n// lightcraft-main/crates/pipeline/src/tone.rs, lightcraft-main/crates/pipeline/src/local.rs and\n// lightcraft-main/crates/develop/src/settings.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors).\n// See THIRD_PARTY_NOTICES.md.\n\n/**\n * Colour science for the image editor's process 2 rendering: sRGB transfer functions,\n * correlated colour temperature / tint (Kim et al. Planckian locus, tint as Duv), Bradford\n * white-balance adaptation relative to \"as shot\", and the display-referred tone curve.\n */\n\n/** A CIE 1931 chromaticity. */\nexport interface Chromaticity {\n x: number;\n y: number;\n}\n\n/** A row-major 3×3 matrix (`[m00, m01, m02, m10, …, m22]`). */\nexport type Matrix3 = [number, number, number, number, number, number, number, number, number];\n\n// ---------------------------------------------------------------------------\n// Process versions\n// ---------------------------------------------------------------------------\n\n/** The rendering of adjustments from before process versions existed. Frozen. */\nexport const LEGACY_PROCESS_VERSION = 1 as const;\n/** The process new edits and Reset get. */\nexport const LATEST_PROCESS_VERSION = 2 as const;\nexport type ImageProcessVersion = 1 | 2;\n\n/**\n * Reads a stored process number leniently: a missing or damaged value (not a whole number) is\n * the legacy process, so a saved edit from before process versions renders exactly as it did. A\n * whole number newer than this build knows renders with the latest process.\n */\nexport function normalizeProcessVersion(value: unknown): ImageProcessVersion {\n if (typeof value !== 'number' || !Number.isInteger(value)) return LEGACY_PROCESS_VERSION;\n return value >= LATEST_PROCESS_VERSION ? LATEST_PROCESS_VERSION : LEGACY_PROCESS_VERSION;\n}\n\n// ---------------------------------------------------------------------------\n// sRGB transfer\n// ---------------------------------------------------------------------------\n\nexport function srgbToLinear(v: number): number {\n return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);\n}\n\nexport function linearToSrgb(v: number): number {\n return v <= 0.0031308 ? v * 12.92 : 1.055 * Math.pow(v, 1 / 2.4) - 0.055;\n}\n\n/** Rec.709 / sRGB luminance weights (linear light). */\nexport const LUMA_709: readonly [number, number, number] = [0.2126, 0.7152, 0.0722];\n\n// ---------------------------------------------------------------------------\n// 3×3 matrices\n// ---------------------------------------------------------------------------\n\nexport const IDENTITY_MATRIX3: Matrix3 = [1, 0, 0, 0, 1, 0, 0, 0, 1];\n\nexport function mulMatrix3(a: Matrix3, b: Matrix3): Matrix3 {\n const out = new Array(9).fill(0) as Matrix3;\n for (let r = 0; r < 3; r++) {\n for (let c = 0; c < 3; c++) {\n out[r * 3 + c] = a[r * 3] * b[c] + a[r * 3 + 1] * b[3 + c] + a[r * 3 + 2] * b[6 + c];\n }\n }\n return out;\n}\n\nexport function applyMatrix3(m: Matrix3, v: readonly [number, number, number]): [number, number, number] {\n return [\n m[0] * v[0] + m[1] * v[1] + m[2] * v[2],\n m[3] * v[0] + m[4] * v[1] + m[5] * v[2],\n m[6] * v[0] + m[7] * v[1] + m[8] * v[2],\n ];\n}\n\nexport function invertMatrix3(m: Matrix3): Matrix3 | null {\n const [a, b, c, d, e, f, g, h, i] = m;\n const A = e * i - f * h;\n const B = -(d * i - f * g);\n const C = d * h - e * g;\n const det = a * A + b * B + c * C;\n if (!Number.isFinite(det) || Math.abs(det) < 1e-12) return null;\n const k = 1 / det;\n return [\n A * k, -(b * i - c * h) * k, (b * f - c * e) * k,\n B * k, (a * i - c * g) * k, -(a * f - c * d) * k,\n C * k, -(a * h - b * g) * k, (a * e - b * d) * k,\n ];\n}\n\nfunction diag3(x: number, y: number, z: number): Matrix3 {\n return [x, 0, 0, 0, y, 0, 0, 0, z];\n}\n\n// ---------------------------------------------------------------------------\n// Chromaticity, sRGB primaries, Bradford adaptation\n// ---------------------------------------------------------------------------\n\nexport const D65: Chromaticity = { x: 0.3127, y: 0.329 };\n\nfunction xyToXyz(p: Chromaticity): [number, number, number] {\n return [p.x / p.y, 1, (1 - p.x - p.y) / p.y];\n}\n\nfunction xyzToXy(v: readonly [number, number, number]): Chromaticity {\n const s = v[0] + v[1] + v[2];\n return s <= 0 ? { ...D65 } : { x: v[0] / s, y: v[1] / s };\n}\n\n/** Linear sRGB → XYZ (D65 white, Y = 1), derived from the primaries. */\nconst SRGB_TO_XYZ: Matrix3 = (() => {\n const p = [{ x: 0.64, y: 0.33 }, { x: 0.3, y: 0.6 }, { x: 0.15, y: 0.06 }].map(xyToXyz);\n const m: Matrix3 = [p[0][0], p[1][0], p[2][0], p[0][1], p[1][1], p[2][1], p[0][2], p[1][2], p[2][2]];\n const inv = invertMatrix3(m);\n const s = inv ? applyMatrix3(inv, xyToXyz(D65)) : [1, 1, 1];\n return mulMatrix3(m, diag3(s[0], s[1], s[2]));\n})();\nconst XYZ_TO_SRGB: Matrix3 = invertMatrix3(SRGB_TO_XYZ) ?? IDENTITY_MATRIX3;\n\n/** The Bradford cone-response matrix (XYZ → sharpened cones). */\nconst BRADFORD: Matrix3 = [0.8951, 0.2664, -0.1614, -0.7502, 1.7135, 0.0367, 0.0389, -0.0685, 1.0296];\nconst BRADFORD_INV: Matrix3 = invertMatrix3(BRADFORD) ?? IDENTITY_MATRIX3;\n\n/** Bradford chromatic adaptation (XYZ → XYZ) from white `src` to white `dst`. */\nexport function bradfordAdaptation(src: Chromaticity, dst: Chromaticity): Matrix3 {\n if (src.x === dst.x && src.y === dst.y) return [...IDENTITY_MATRIX3];\n const s = applyMatrix3(BRADFORD, xyToXyz(src));\n const d = applyMatrix3(BRADFORD, xyToXyz(dst));\n return mulMatrix3(BRADFORD_INV, mulMatrix3(diag3(d[0] / s[0], d[1] / s[1], d[2] / s[2]), BRADFORD));\n}\n\n/**\n * Matrix (linear sRGB → linear sRGB) that white-balances a scene lit by `src` so that it\n * renders as if lit by D65.\n */\nexport function wbMatrix(src: Chromaticity): Matrix3 {\n return mulMatrix3(XYZ_TO_SRGB, mulMatrix3(bradfordAdaptation(src, D65), SRGB_TO_XYZ));\n}\n\n/** The chromaticity of a linear sRGB colour. */\nexport function linearSrgbToXy(rgb: readonly [number, number, number]): Chromaticity {\n return xyzToXy(applyMatrix3(SRGB_TO_XYZ, rgb));\n}\n\n// ---------------------------------------------------------------------------\n// Correlated colour temperature and tint\n// ---------------------------------------------------------------------------\n\n/** Tint units per unit of Duv. */\nexport const TINT_SCALE = 3000;\nexport const MIN_KELVIN = 1667;\nexport const MAX_KELVIN = 25000;\n\nfunction kimXy(tIn: number): Chromaticity {\n const t = Math.min(MAX_KELVIN, Math.max(MIN_KELVIN, tIn));\n const t2 = t * t;\n const t3 = t2 * t;\n const x =\n t <= 4000\n ? -0.2661239e9 / t3 - 0.2343589e6 / t2 + 0.8776956e3 / t + 0.17991\n : -3.0258469e9 / t3 + 2.1070379e6 / t2 + 0.2226347e3 / t + 0.24039;\n const x2 = x * x;\n const x3 = x2 * x;\n const y =\n t <= 2222\n ? -1.1063814 * x3 - 1.3481102 * x2 + 2.18555832 * x - 0.20219683\n : t <= 4000\n ? -0.9549476 * x3 - 1.37418593 * x2 + 2.09137015 * x - 0.16748867\n : 3.081758 * x3 - 5.8733867 * x2 + 3.75112997 * x - 0.37001483;\n return { x, y };\n}\n\n/** CIE 1931 xy → CIE 1960 uv. */\nexport function xyToUv(p: Chromaticity): [number, number] {\n const d = -2 * p.x + 12 * p.y + 3;\n return [(4 * p.x) / d, (6 * p.y) / d];\n}\n\n/** CIE 1960 uv → CIE 1931 xy. */\nexport function uvToXy(u: number, v: number): Chromaticity {\n const d = 2 * u - 8 * v + 4;\n return { x: (3 * u) / d, y: (2 * v) / d };\n}\n\n/** Planckian locus in (u, v), extended linearly in mired space outside the approximation's range. */\nfunction locusUv(tIn: number): [number, number] {\n const t = Math.max(1000, tIn);\n if (t >= MIN_KELVIN && t <= MAX_KELVIN) return xyToUv(kimXy(t));\n const [a, b] = t < MIN_KELVIN ? [MIN_KELVIN, MIN_KELVIN + 50] : [MAX_KELVIN - 500, MAX_KELVIN];\n const [ua, va] = xyToUv(kimXy(a));\n const [ub, vb] = xyToUv(kimXy(b));\n const ma = 1e6 / a;\n const mb = 1e6 / b;\n const s = (1e6 / t - ma) / (mb - ma);\n return [ua + (ub - ua) * s, va + (vb - va) * s];\n}\n\n/** Unit normal of the locus at `t`, pointing towards green (increasing v). */\nfunction locusNormal(t: number): [number, number] {\n const m = 1e6 / t;\n const [u0, v0] = locusUv(1e6 / (m + 1));\n const [u1, v1] = locusUv(1e6 / Math.max(m - 1, 1));\n const du = u1 - u0;\n const dv = v1 - v0;\n const l = Math.max(Math.hypot(du, dv), 1e-12);\n const nx = -dv / l;\n const ny = du / l;\n return ny < 0 ? [-nx, -ny] : [nx, ny];\n}\n\n/**\n * Source illuminant chromaticity for temperature `kelvin` and correction `tint`.\n * Positive tint means a green source white, whose correction adds magenta to the image.\n */\nexport function tempTintToXy(kelvin: number, tint: number): Chromaticity {\n const [u, v] = locusUv(kelvin);\n const [nu, nv] = locusNormal(kelvin);\n const duv = tint / TINT_SCALE;\n return uvToXy(u + nu * duv, v + nv * duv);\n}\n\n/** Inverse of {@link tempTintToXy}: nearest locus temperature (searched in mired space) and tint. */\nexport function xyToTempTint(p: Chromaticity): { kelvin: number; tint: number } {\n const [u, v] = xyToUv(p);\n const dist = (m: number) => {\n const [lu, lv] = locusUv(1e6 / m);\n return (u - lu) ** 2 + (v - lv) ** 2;\n };\n // Coarse scan, then golden-section refine over mired 20..1000 (50000 K .. 1000 K).\n let best = 20;\n let bd = Infinity;\n for (let m = 20; m <= 1000; m += 5) {\n const d = dist(m);\n if (d < bd) {\n bd = d;\n best = m;\n }\n }\n let a = Math.max(best - 5, 20);\n let b = Math.min(best + 5, 1000);\n const g = 0.61803398875;\n for (let i = 0; i < 60; i++) {\n const c = b - g * (b - a);\n const d = a + g * (b - a);\n if (dist(c) < dist(d)) b = d;\n else a = c;\n }\n const kelvin = 1e6 / ((a + b) / 2);\n const [lu, lv] = locusUv(kelvin);\n const [nu, nv] = locusNormal(kelvin);\n const duv = (u - lu) * nu + (v - lv) * nv;\n return { kelvin, tint: duv * TINT_SCALE };\n}\n\n// ---------------------------------------------------------------------------\n// Relative white balance for rendered (JPEG / PNG / WebP) sources\n// ---------------------------------------------------------------------------\n\n/** Rendered images carry no as-shot white: \"as shot\" is 6500 K / tint 0 and maps to the identity. */\nexport const AS_SHOT_KELVIN = 6500;\n/** Mireds per unit of the `temperature` slider (−100…100). */\nexport const MIRED_PER_TEMPERATURE_UNIT = 1;\nconst AS_SHOT_MIRED = 1e6 / AS_SHOT_KELVIN;\n\n/**\n * Maps the `temperature` slider (−100 cool … +100 warm) to the Kelvin white-balance setting:\n * one mired per unit around 6500 K, so 0 → 6500 K, +100 → ~18 570 K (a strong warming\n * correction), −100 → ~3 940 K (a strong cooling correction). Mireds are perceptually even,\n * so equal slider steps look like equal changes.\n */\nexport function temperatureToKelvin(temperature: number): number {\n return 1e6 / (AS_SHOT_MIRED - temperature * MIRED_PER_TEMPERATURE_UNIT);\n}\n\n/** Inverse of {@link temperatureToKelvin}. */\nexport function kelvinToTemperature(kelvin: number): number {\n return (AS_SHOT_MIRED - 1e6 / kelvin) / MIRED_PER_TEMPERATURE_UNIT;\n}\n\nconst AS_SHOT_WB_INV: Matrix3 = invertMatrix3(wbMatrix(tempTintToXy(AS_SHOT_KELVIN, 0))) ?? IDENTITY_MATRIX3;\n\n/**\n * White-balance matrix (linear sRGB) for the `temperature` / `tint` sliders, relative to\n * \"as shot\": the identity at 0 / 0. Normalised so neutral luminance is preserved (white\n * balance doesn't change exposure).\n */\nexport function whiteBalanceMatrix(temperature: number, tint = 0): Matrix3 {\n if (!temperature && !tint) return [...IDENTITY_MATRIX3];\n const set = wbMatrix(tempTintToXy(temperatureToKelvin(temperature), tint));\n const m = mulMatrix3(set, AS_SHOT_WB_INV);\n const g = applyMatrix3(m, [1, 1, 1]);\n const y = g[0] * LUMA_709[0] + g[1] * LUMA_709[1] + g[2] * LUMA_709[2];\n return mulMatrix3(m, diag3(1 / y, 1 / y, 1 / y));\n}\n\n/**\n * Neutral-point eyedropper: the `temperature` / `tint` that render the sampled sRGB colour\n * (0–255 per channel, as it appears in the unadjusted image) neutral. Results are clamped to\n * the slider range −100…100.\n */\nexport function whiteBalanceFromSample(\n rgb: readonly [number, number, number] | { r: number; g: number; b: number }\n): { temperature: number; tint: number } {\n const [r, g, b] = Array.isArray(rgb) ? rgb : [(rgb as any).r, (rgb as any).g, (rgb as any).b];\n const lin: [number, number, number] = [r, g, b].map((c) => srgbToLinear(Math.min(255, Math.max(0, c)) / 255)) as [\n number,\n number,\n number,\n ];\n if (lin[0] + lin[1] + lin[2] <= 1e-6) return { temperature: 0, tint: 0 };\n // Under relative WB the matrix is wb(set) · wb(asShot)⁻¹, so the sample is neutral when the\n // chosen source white is the chromaticity of wb(asShot)⁻¹ · sample.\n const src = linearSrgbToXy(applyMatrix3(AS_SHOT_WB_INV, lin));\n const { kelvin, tint } = xyToTempTint(src);\n const clamp = (v: number) => Math.max(-100, Math.min(100, v));\n return { temperature: clamp(kelvinToTemperature(kelvin)), tint: clamp(tint) };\n}\n\n// ---------------------------------------------------------------------------\n// Tone curve (display-referred, identity at neutral)\n// ---------------------------------------------------------------------------\n\n/** Number of intervals in a tone LUT (the table has `TONE_LUT_SIZE + 1` entries). */\nexport const TONE_LUT_SIZE = 4096;\nconst GREY = 0.18;\nconst GREY_P = Math.pow(GREY, 1 / 2.2);\n/** Linear value where the highlight shoulder starts when there is headroom above 1. */\nconst SHOULDER_KNEE = 0.6;\n\n/**\n * The process 2 tone curve for one linear input value in `[0, headroom]`:\n *\n * 1. A highlight shoulder (extended Reinhard with white point `headroom`) above a knee of 0.6\n * rolls values above 1 (after exposure / white balance) back into range instead of clipping.\n * With no headroom (`headroom = 1`) it is exactly the identity.\n * 2. In a gamma-2.2 perceptual domain, contrast is an S-curve anchored at 0, middle grey and 1\n * (as `ToneMap::display`), and brightness a midtone lift anchored at 0 and 1.\n *\n * `contrast` and `brightness` are −100…100; both 0 with `headroom = 1` is the identity.\n */\nexport function toneCurve(y: number, contrast: number, brightness: number, headroom = 1): number {\n if (!(y > 0)) return 0;\n const h = Math.max(1, headroom);\n let o = Math.min(y, h);\n if (h > 1 && o > SHOULDER_KNEE) {\n const span = 1 - SHOULDER_KNEE;\n const x = (o - SHOULDER_KNEE) / span;\n const X = (h - SHOULDER_KNEE) / span;\n o = SHOULDER_KNEE + span * ((x * (1 + x / (X * X))) / (1 + x));\n }\n if (contrast === 0 && brightness === 0) return Math.min(1, o);\n let p = Math.pow(Math.min(1, o), 1 / 2.2);\n const c = contrast / 100;\n if (c !== 0) {\n p += c * 0.4 * (p - GREY_P) * (1 - (2 * p - 1) ** 2);\n }\n const b = brightness / 100;\n if (b !== 0) {\n // Smooth midtone lift that leaves black and white in place (slope stays positive).\n p += b * 0.8 * p * (1 - p);\n }\n return Math.pow(Math.min(1, Math.max(0, p)), 2.2);\n}\n\n/** Tabulates {@link toneCurve} over `[0, headroom]` in {@link TONE_LUT_SIZE} steps. */\nexport function buildToneLut(contrast: number, brightness: number, headroom = 1): Float32Array {\n const h = Math.max(1, headroom);\n const lut = new Float32Array(TONE_LUT_SIZE + 1);\n for (let i = 0; i <= TONE_LUT_SIZE; i++) {\n lut[i] = toneCurve((h * i) / TONE_LUT_SIZE, contrast, brightness, h);\n }\n return lut;\n}\n\n/** Linearly interpolated lookup into a LUT from {@link buildToneLut}. */\nexport function sampleToneLut(lut: Float32Array, headroom: number, y: number): number {\n if (!(y > 0)) return lut[0];\n const n = lut.length - 1;\n const f = (y * n) / Math.max(1, headroom);\n if (f >= n) return lut[n];\n const i = f | 0;\n return lut[i] + (lut[i + 1] - lut[i]) * (f - i);\n}\n","import type { Annotation, CropBox, ImageEditorState, ImagePoint, ImageTransform } from './types';\n\n/*\n * Coordinate spaces\n * -----------------\n * The crop box and every annotation are stored in ONE canonical space: natural image\n * pixels of the current source image (origin top-left of the un-rotated, un-flipped,\n * un-cropped image). Rotation, flips, crop and the viewport fit/zoom/pan are pure view\n * transforms applied on top of that space by a single forward mapping:\n *\n * target = origin + scale * R(angle) * F(flipH, flipV) * (image - regionCenter)\n *\n * where `regionCenter` is the centre of the visible region (the crop box, or the whole\n * image while the crop tool is active). `projectImagePoint` / `unprojectCanvasPoint`\n * implement the mapping and its exact inverse, and `applyImageProjectionToContext`\n * applies the very same transform to a 2D context, so rendering, hit-testing, viewport\n * metrics, export and mask generation cannot drift apart.\n */\n\nexport type CropHandle = 'nw' | 'ne' | 'se' | 'sw' | 'n' | 's' | 'e' | 'w' | 'inside' | null;\n\nexport interface ViewportMetrics {\n vpWidth: number;\n vpHeight: number;\n centerX: number;\n centerY: number;\n baseW: number;\n baseH: number;\n fitScale: number;\n renderW: number;\n renderH: number;\n totalScale: number;\n imageRect: { x: number; y: number; width: number; height: number };\n cropRect: { x: number; y: number; width: number; height: number } | null;\n}\n\n/**\n * A complete description of the natural-image -> target mapping (see module comment).\n */\nexport interface ImageProjection {\n /** Visible region in natural image pixels (crop box or full image). */\n region: CropBox;\n /** Rotation in degrees, normalised to [0, 360). */\n angle: number;\n flipH: boolean;\n flipV: boolean;\n /** Uniform scale from image pixels to target pixels. */\n scale: number;\n /** Target-space point that the centre of `region` maps to. */\n originX: number;\n originY: number;\n}\n\n/** Normalises any rotation in degrees to [0, 360). */\nexport function normalizeAngle(degrees: number): number {\n const a = ((degrees % 360) + 360) % 360;\n return a === 360 ? 0 : a;\n}\n\nfunction cosSin(angle: number): [number, number] {\n // Exact values for quarter turns so 90deg steps never accumulate float noise.\n switch (angle) {\n case 0:\n return [1, 0];\n case 90:\n return [0, 1];\n case 180:\n return [-1, 0];\n case 270:\n return [0, -1];\n default: {\n const rad = (angle * Math.PI) / 180;\n return [Math.cos(rad), Math.sin(rad)];\n }\n }\n}\n\n/** Whether the rotation swaps the displayed width and height. */\nexport function isQuarterTurnSwapped(transform: Pick<ImageTransform, 'rotate'>): boolean {\n const angle = normalizeAngle(transform.rotate);\n return angle === 90 || angle === 270;\n}\n\n/** Size of a region once rotated (bounding box for arbitrary angles). */\nexport function getRotatedSize(\n width: number,\n height: number,\n transform: Pick<ImageTransform, 'rotate'>\n): { width: number; height: number } {\n const [c, s] = cosSin(normalizeAngle(transform.rotate));\n return {\n width: Math.abs(width * c) + Math.abs(height * s),\n height: Math.abs(width * s) + Math.abs(height * c),\n };\n}\n\n/** Maps a natural-image point to target space. */\nexport function projectImagePoint(pt: ImagePoint, p: ImageProjection): ImagePoint {\n const [c, s] = cosSin(p.angle);\n let x = pt.x - (p.region.x + p.region.width / 2);\n let y = pt.y - (p.region.y + p.region.height / 2);\n if (p.flipH) x = -x;\n if (p.flipV) y = -y;\n const rx = x * c - y * s;\n const ry = x * s + y * c;\n return { x: p.originX + rx * p.scale, y: p.originY + ry * p.scale };\n}\n\n/** Exact inverse of {@link projectImagePoint}. */\nexport function unprojectCanvasPoint(pt: ImagePoint, p: ImageProjection): ImagePoint {\n const [c, s] = cosSin(p.angle);\n const rx = (pt.x - p.originX) / p.scale;\n const ry = (pt.y - p.originY) / p.scale;\n let x = rx * c + ry * s;\n let y = -rx * s + ry * c;\n if (p.flipH) x = -x;\n if (p.flipV) y = -y;\n return { x: x + p.region.x + p.region.width / 2, y: y + p.region.y + p.region.height / 2 };\n}\n\n/**\n * Applies the projection to a 2D context so that subsequent drawing in natural image\n * pixels lands exactly where {@link projectImagePoint} says it does.\n */\nexport function applyImageProjectionToContext(\n ctx: Pick<CanvasRenderingContext2D, 'translate' | 'scale' | 'rotate'>,\n p: ImageProjection\n): void {\n ctx.translate(p.originX, p.originY);\n ctx.scale(p.scale, p.scale);\n if (p.angle !== 0) ctx.rotate((p.angle * Math.PI) / 180);\n if (p.flipH || p.flipV) ctx.scale(p.flipH ? -1 : 1, p.flipV ? -1 : 1);\n ctx.translate(-(p.region.x + p.region.width / 2), -(p.region.y + p.region.height / 2));\n}\n\n/** Projects an image-space rectangle and returns its target-space bounding box. */\nexport function projectImageRect(\n rect: CropBox,\n p: ImageProjection\n): { x: number; y: number; width: number; height: number } {\n const corners = [\n projectImagePoint({ x: rect.x, y: rect.y }, p),\n projectImagePoint({ x: rect.x + rect.width, y: rect.y }, p),\n projectImagePoint({ x: rect.x, y: rect.y + rect.height }, p),\n projectImagePoint({ x: rect.x + rect.width, y: rect.y + rect.height }, p),\n ];\n const xs = corners.map((c) => c.x);\n const ys = corners.map((c) => c.y);\n const minX = Math.min(...xs);\n const minY = Math.min(...ys);\n return { x: minX, y: minY, width: Math.max(...xs) - minX, height: Math.max(...ys) - minY };\n}\n\n/** Clamps a crop box to the image bounds. */\nexport function clampCropToImage(crop: CropBox, width: number, height: number): CropBox {\n const x = Math.max(0, Math.min(width - 1, crop.x));\n const y = Math.max(0, Math.min(height - 1, crop.y));\n return {\n x,\n y,\n width: Math.max(1, Math.min(width - x, crop.width)),\n height: Math.max(1, Math.min(height - y, crop.height)),\n };\n}\n\nfunction resolveViewport(\n state: ImageEditorState,\n vpWidth: number,\n vpHeight: number,\n isActivelyCropping: boolean,\n cropOverride?: CropBox | null\n) {\n const { imageDimensions, crop: stateCrop, transform, zoom, pan, fit = 'margin' } = state;\n const naturalWidth = imageDimensions.width || 800;\n const naturalHeight = imageDimensions.height || 600;\n const full: CropBox = { x: 0, y: 0, width: naturalWidth, height: naturalHeight };\n const activeCrop =\n cropOverride !== undefined && cropOverride !== null\n ? cropOverride\n : (stateCrop ?? (isActivelyCropping ? full : null));\n\n const region =\n !isActivelyCropping && activeCrop ? clampCropToImage(activeCrop, naturalWidth, naturalHeight) : full;\n const { width: baseW, height: baseH } = getRotatedSize(region.width, region.height, transform);\n\n const fitScale =\n fit === 'contain'\n ? Math.min(vpWidth / baseW, vpHeight / baseH)\n : Math.min((vpWidth * 0.85) / baseW, (vpHeight * 0.85) / baseH, 1);\n const centerX = vpWidth / 2 + pan.x;\n const centerY = vpHeight / 2 + pan.y;\n\n const projection: ImageProjection = {\n region,\n angle: normalizeAngle(transform.rotate),\n flipH: transform.flipH,\n flipV: transform.flipV,\n scale: zoom * fitScale,\n originX: centerX,\n originY: centerY,\n };\n\n return { activeCrop, baseW, baseH, fitScale, centerX, centerY, projection };\n}\n\n/**\n * Returns the natural-image -> canvas projection used by the interactive viewport.\n */\nexport function getViewportProjection(\n state: ImageEditorState,\n vpWidth: number,\n vpHeight: number,\n isActivelyCropping = false,\n cropOverride?: CropBox | null\n): ImageProjection {\n return resolveViewport(state, vpWidth, vpHeight, isActivelyCropping, cropOverride).projection;\n}\n\n/**\n * Calculates current viewport projection metrics for canvas <-> image coordinate mapping.\n * `cropRect` is the on-screen (rotated/flipped) rectangle of the crop box.\n */\nexport function getViewportMetrics(\n state: ImageEditorState,\n vpWidth: number,\n vpHeight: number,\n isActivelyCropping = false,\n cropOverride?: CropBox | null\n): ViewportMetrics {\n const { zoom } = state;\n const { activeCrop, baseW, baseH, fitScale, centerX, centerY, projection } = resolveViewport(\n state,\n vpWidth,\n vpHeight,\n isActivelyCropping,\n cropOverride\n );\n\n const renderW = baseW * fitScale;\n const renderH = baseH * fitScale;\n\n const imageRect = {\n x: centerX - (renderW * zoom) / 2,\n y: centerY - (renderH * zoom) / 2,\n width: renderW * zoom,\n height: renderH * zoom,\n };\n\n const cropRect = activeCrop ? projectImageRect(activeCrop, projection) : null;\n\n return {\n vpWidth,\n vpHeight,\n centerX,\n centerY,\n baseW,\n baseH,\n fitScale,\n renderW,\n renderH,\n totalScale: zoom * fitScale,\n imageRect,\n cropRect,\n };\n}\n\n/**\n * Converts a point on the canvas (in internal canvas pixel space) to natural image pixel\n * space, accounting for zoom, pan, fit scale, crop offset, rotation and flips.\n */\nexport function canvasToImagePoint(\n canvasPt: ImagePoint,\n state: ImageEditorState,\n vpWidth: number,\n vpHeight: number,\n isActivelyCropping = false\n): ImagePoint {\n return unprojectCanvasPoint(canvasPt, getViewportProjection(state, vpWidth, vpHeight, isActivelyCropping));\n}\n\n/**\n * Converts a point in natural image pixel space to internal canvas pixel space.\n * Exact inverse of {@link canvasToImagePoint}.\n */\nexport function imageToCanvasPoint(\n imgPt: ImagePoint,\n state: ImageEditorState,\n vpWidth: number,\n vpHeight: number,\n isActivelyCropping = false\n): ImagePoint {\n return projectImagePoint(imgPt, getViewportProjection(state, vpWidth, vpHeight, isActivelyCropping));\n}\n\nconst HANDLE_VECTORS: Record<Exclude<CropHandle, 'inside' | null>, [number, number]> = {\n nw: [-1, -1],\n n: [0, -1],\n ne: [1, -1],\n e: [1, 0],\n se: [1, 1],\n s: [0, 1],\n sw: [-1, 1],\n w: [-1, 0],\n};\n\nfunction vectorToHandle(x: number, y: number): CropHandle {\n const vx = Math.abs(x) < 0.38 ? 0 : Math.sign(x);\n const vy = Math.abs(y) < 0.38 ? 0 : Math.sign(y);\n for (const [name, [hx, hy]] of Object.entries(HANDLE_VECTORS)) {\n if (hx === vx && hy === vy) return name as CropHandle;\n }\n return null;\n}\n\n/**\n * Converts a crop handle in screen orientation to the equivalent edge/corner of the crop\n * box in natural image space, or the reverse with `toScreen = true`.\n */\nexport function mapCropHandle(handle: CropHandle, transform: ImageTransform, toScreen = false): CropHandle {\n if (handle === null || handle === 'inside') return handle;\n const [hx, hy] = HANDLE_VECTORS[handle];\n const [c, s] = cosSin(normalizeAngle(transform.rotate));\n const fx = transform.flipH ? -1 : 1;\n const fy = transform.flipV ? -1 : 1;\n if (toScreen) {\n const x = hx * fx;\n const y = hy * fy;\n return vectorToHandle(x * c - y * s, x * s + y * c);\n }\n const x = hx * c + hy * s;\n const y = -hx * s + hy * c;\n return vectorToHandle(x * fx, y * fy);\n}\n\n/**\n * Hit-tests the crop box and its corner/edge handles given a canvas point.\n * The returned handle names the edge/corner of the crop box in natural image space\n * (what {@link calculateCropDrag} expects); use `getCropCursor(handle, state.transform)`\n * to obtain the matching on-screen cursor.\n */\nexport function hitTestCrop(\n canvasPt: ImagePoint,\n state: ImageEditorState,\n vpWidth: number,\n vpHeight: number,\n handleThreshold = 14,\n cropOverride?: CropBox | null\n): CropHandle {\n const metrics = getViewportMetrics(state, vpWidth, vpHeight, true, cropOverride);\n if (!metrics.cropRect) return null;\n\n const screenHandle = hitTestCropRect(canvasPt, metrics.cropRect, handleThreshold);\n return mapCropHandle(screenHandle, state.transform);\n}\n\nfunction hitTestCropRect(\n canvasPt: ImagePoint,\n rect: { x: number; y: number; width: number; height: number },\n handleThreshold: number\n): CropHandle {\n const { x, y, width: w, height: h } = rect;\n const px = canvasPt.x;\n const py = canvasPt.y;\n\n // 1. Corners (highest priority)\n const dist = (x1: number, y1: number, x2: number, y2: number) =>\n Math.hypot(x1 - x2, y1 - y2);\n\n if (dist(px, py, x, y) <= handleThreshold) return 'nw';\n if (dist(px, py, x + w, y) <= handleThreshold) return 'ne';\n if (dist(px, py, x + w, y + h) <= handleThreshold) return 'se';\n if (dist(px, py, x, y + h) <= handleThreshold) return 'sw';\n\n // 2. Edges\n const nearX = px >= x - handleThreshold && px <= x + w + handleThreshold;\n const nearY = py >= y - handleThreshold && py <= y + h + handleThreshold;\n\n if (nearX && Math.abs(py - y) <= handleThreshold) return 'n';\n if (nearX && Math.abs(py - (y + h)) <= handleThreshold) return 's';\n if (nearY && Math.abs(px - x) <= handleThreshold) return 'w';\n if (nearY && Math.abs(px - (x + w)) <= handleThreshold) return 'e';\n\n // 3. Inside crop region\n if (px >= x && px <= x + w && py >= y && py <= y + h) {\n return 'inside';\n }\n\n return null;\n}\n\n/**\n * Returns CSS cursor style corresponding to a crop handle. Pass the current transform when\n * the handle comes from {@link hitTestCrop} (image space) so the cursor matches the\n * on-screen orientation under rotation/flip.\n */\nexport function getCropCursor(handle: CropHandle, transform?: ImageTransform): string {\n const screenHandle = transform ? mapCropHandle(handle, transform, true) : handle;\n switch (screenHandle) {\n case 'nw':\n case 'se':\n return 'nwse-resize';\n case 'ne':\n case 'sw':\n return 'nesw-resize';\n case 'n':\n case 's':\n return 'ns-resize';\n case 'e':\n case 'w':\n return 'ew-resize';\n case 'inside':\n return 'move';\n default:\n return 'crosshair';\n }\n}\n\n/**\n * Calculates updated crop rectangle given a drag interaction.\n */\nexport function calculateCropDrag(\n startCrop: CropBox,\n handle: CropHandle,\n startImgPt: ImagePoint,\n currentImgPt: ImagePoint,\n imgWidth: number,\n imgHeight: number,\n aspectRatio?: number | null,\n transform?: ImageTransform\n): CropBox {\n // `aspectRatio` is the on-screen ratio; under a quarter turn it is inverted in image space.\n if (aspectRatio && transform && isQuarterTurnSwapped(transform)) {\n aspectRatio = 1 / aspectRatio;\n }\n const dx = currentImgPt.x - startImgPt.x;\n const dy = currentImgPt.y - startImgPt.y;\n\n // Whole crop box move\n if (handle === 'inside') {\n const maxX = Math.max(0, imgWidth - startCrop.width);\n const maxY = Math.max(0, imgHeight - startCrop.height);\n const newX = Math.max(0, Math.min(maxX, startCrop.x + dx));\n const newY = Math.max(0, Math.min(maxY, startCrop.y + dy));\n return {\n x: Math.round(newX),\n y: Math.round(newY),\n width: startCrop.width,\n height: startCrop.height,\n };\n }\n\n // Handle resizing\n let left = startCrop.x;\n let top = startCrop.y;\n let right = startCrop.x + startCrop.width;\n let bottom = startCrop.y + startCrop.height;\n\n const minSize = 20;\n\n if (handle === 'e' || handle === 'ne' || handle === 'se') {\n right = Math.min(imgWidth, Math.max(left + minSize, startCrop.x + startCrop.width + dx));\n }\n if (handle === 'w' || handle === 'nw' || handle === 'sw') {\n left = Math.max(0, Math.min(right - minSize, startCrop.x + dx));\n }\n if (handle === 's' || handle === 'se' || handle === 'sw') {\n bottom = Math.min(imgHeight, Math.max(top + minSize, startCrop.y + startCrop.height + dy));\n }\n if (handle === 'n' || handle === 'nw' || handle === 'ne') {\n top = Math.max(0, Math.min(bottom - minSize, startCrop.y + dy));\n }\n\n // Maintain aspect ratio if requested\n if (aspectRatio && aspectRatio > 0) {\n let currentW = right - left;\n let currentH = bottom - top;\n\n if (handle === 'e' || handle === 'w') {\n currentH = Math.min(imgHeight, currentW / aspectRatio);\n bottom = Math.min(imgHeight, top + currentH);\n } else if (handle === 'n' || handle === 's') {\n currentW = Math.min(imgWidth, currentH * aspectRatio);\n right = Math.min(imgWidth, left + currentW);\n } else {\n // Corner resizing\n const targetH = currentW / aspectRatio;\n if (top + targetH <= imgHeight) {\n bottom = top + targetH;\n } else {\n const targetW = currentH * aspectRatio;\n right = Math.min(imgWidth, left + targetW);\n }\n }\n }\n\n return {\n x: Math.round(left),\n y: Math.round(top),\n width: Math.round(Math.max(minSize, right - left)),\n height: Math.round(Math.max(minSize, bottom - top)),\n };\n}\n\n/**\n * Calculates bounding box of an annotation.\n */\nexport function getAnnotationBounds(ann: Annotation): { x: number; y: number; width: number; height: number } {\n switch (ann.type) {\n case 'rect':\n case 'image':\n return {\n x: Math.min(ann.x, ann.x + ann.width),\n y: Math.min(ann.y, ann.y + ann.height),\n width: Math.abs(ann.width),\n height: Math.abs(ann.height),\n };\n case 'circle':\n return {\n x: ann.x - Math.abs(ann.radiusX),\n y: ann.y - Math.abs(ann.radiusY),\n width: Math.abs(ann.radiusX) * 2,\n height: Math.abs(ann.radiusY) * 2,\n };\n case 'arrow':\n case 'line': {\n const minX = Math.min(ann.x, ann.endX);\n const minY = Math.min(ann.y, ann.endY);\n return {\n x: minX,\n y: minY,\n width: Math.max(Math.abs(ann.endX - ann.x), 10),\n height: Math.max(Math.abs(ann.endY - ann.y), 10),\n };\n }\n case 'pen': {\n if (!ann.points.length) return { x: ann.x, y: ann.y, width: 0, height: 0 };\n let minX = ann.points[0].x;\n let maxX = ann.points[0].x;\n let minY = ann.points[0].y;\n let maxY = ann.points[0].y;\n for (const pt of ann.points) {\n if (pt.x < minX) minX = pt.x;\n if (pt.x > maxX) maxX = pt.x;\n if (pt.y < minY) minY = pt.y;\n if (pt.y > maxY) maxY = pt.y;\n }\n return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };\n }\n case 'text':\n return {\n x: ann.x,\n y: ann.y,\n width: ann.text.length * ann.fontSize * 0.6,\n height: ann.fontSize * 1.2,\n };\n }\n}\n\n/**\n * Hit tests if a point in image space is within an annotation's bounds.\n */\nexport function hitTestAnnotation(imgPt: ImagePoint, ann: Annotation, padding = 10): boolean {\n const b = getAnnotationBounds(ann);\n return (\n imgPt.x >= b.x - padding &&\n imgPt.x <= b.x + b.width + padding &&\n imgPt.y >= b.y - padding &&\n imgPt.y <= b.y + b.height + padding\n );\n}\n\n","// Portions ported from photocraft/crates/algo/src/selection.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\nimport type { CropBox } from '../types';\n\n/**\n * A selection: one 8-bit coverage value per image pixel (0 = not selected, 255 = fully\n * selected), row-major over the whole source image in natural pixels — the same space as the\n * crop box and annotations. Soft values are partial selection (feathered or anti-aliased edges).\n *\n * Treat a mask as immutable: every operation returns a new one, so history entries can share\n * them without copying.\n */\nexport interface SelectionMask {\n readonly width: number;\n readonly height: number;\n readonly data: Uint8Array;\n /** Bounding box of the non-zero coverage, or `null` when nothing is selected. */\n readonly bounds: CropBox | null;\n}\n\n/** How a new selection combines with the current one. */\nexport type SelectionMode = 'replace' | 'add' | 'subtract' | 'intersect';\n\nexport const SELECTION_MODES: readonly SelectionMode[] = ['replace', 'add', 'subtract', 'intersect'];\n\nexport function isSelectionMode(value: unknown): value is SelectionMode {\n return typeof value === 'string' && (SELECTION_MODES as readonly string[]).includes(value);\n}\n\n/** Parses `replace|add|subtract|intersect`; anything else is `replace`. */\nexport function parseSelectionMode(value: unknown): SelectionMode {\n return isSelectionMode(value) ? value : 'replace';\n}\n\n/** RGBA pixels, 8 bits per channel, straight alpha (an `ImageData` qualifies). */\nexport interface PixelBuffer {\n width: number;\n height: number;\n data: ArrayLike<number>;\n}\n\n/** Serialized selection: a run-length encoding of the 8-bit coverage. */\nexport interface SerializedSelection {\n width: number;\n height: number;\n /** `r1:` + base64 of (value byte, LEB128 run length) pairs. */\n rle: string;\n}\n\nconst MAX_SIDE = 1 << 15;\n\nfunction checkSize(width: number, height: number): void {\n if (!Number.isInteger(width) || !Number.isInteger(height) || width < 1 || height < 1 || width > MAX_SIDE || height > MAX_SIDE) {\n throw new Error(`Invalid selection size ${width}×${height}`);\n }\n}\n\n/** Bounding box of non-zero bytes. */\nexport function computeSelectionBounds(width: number, height: number, data: ArrayLike<number>): CropBox | null {\n let x0 = width;\n let x1 = -1;\n let y0 = -1;\n let y1 = -1;\n for (let y = 0; y < height; y++) {\n const row = y * width;\n let first = -1;\n for (let x = 0; x < width; x++) {\n if (data[row + x] !== 0) {\n first = x;\n break;\n }\n }\n if (first < 0) continue;\n let last = first;\n for (let x = width - 1; x > first; x--) {\n if (data[row + x] !== 0) {\n last = x;\n break;\n }\n }\n if (y0 < 0) y0 = y;\n y1 = y;\n if (first < x0) x0 = first;\n if (last > x1) x1 = last;\n }\n if (y0 < 0) return null;\n return { x: x0, y: y0, width: x1 - x0 + 1, height: y1 - y0 + 1 };\n}\n\n/** Wraps coverage bytes (taken over, not copied) as a selection, computing its bounds. */\nexport function createSelectionMask(width: number, height: number, data?: Uint8Array): SelectionMask {\n checkSize(width, height);\n const bytes = data ?? new Uint8Array(width * height);\n if (bytes.length !== width * height) throw new Error('Selection data does not match its size');\n return { width, height, data: bytes, bounds: data ? computeSelectionBounds(width, height, bytes) : null };\n}\n\n/** Select › All. */\nexport function fullSelection(width: number, height: number): SelectionMask {\n checkSize(width, height);\n return { width, height, data: new Uint8Array(width * height).fill(255), bounds: { x: 0, y: 0, width, height } };\n}\n\nexport function isSelectionEmpty(mask: SelectionMask | null | undefined): boolean {\n return !mask || mask.bounds === null;\n}\n\n/** Coverage at a pixel, 0..1 (0 outside the image). */\nexport function selectionValueAt(mask: SelectionMask, x: number, y: number): number {\n const xi = Math.floor(x);\n const yi = Math.floor(y);\n if (xi < 0 || yi < 0 || xi >= mask.width || yi >= mask.height) return 0;\n return mask.data[yi * mask.width + xi] / 255;\n}\n\n/** 8-bit value of a coverage (clamped and rounded; NaN → 0). */\nexport function coverageToByte(v: number): number {\n if (!(v > 0)) return 0;\n if (v >= 1) return 255;\n return Math.floor(v * 255 + 0.5);\n}\n\n/** A selection from 0..1 coverage. */\nexport function selectionFromCoverage(width: number, height: number, coverage: ArrayLike<number>): SelectionMask {\n const n = width * height;\n const out = new Uint8Array(n);\n for (let i = 0; i < n; i++) out[i] = coverageToByte(coverage[i]);\n return createSelectionMask(width, height, out);\n}\n\n/** The selection's coverage as 0..1 floats. */\nexport function selectionToCoverage(mask: SelectionMask): Float32Array {\n const out = new Float32Array(mask.data.length);\n for (let i = 0; i < out.length; i++) out[i] = mask.data[i] / 255;\n return out;\n}\n\n/** Number of fully-or-partly selected pixels, weighted by coverage (Σ coverage). */\nexport function selectionArea(mask: SelectionMask | null | undefined): number {\n if (!mask || !mask.bounds) return 0;\n let sum = 0;\n const { x, y, width, height } = mask.bounds;\n for (let yy = y; yy < y + height; yy++) {\n const row = yy * mask.width;\n for (let xx = x; xx < x + width; xx++) sum += mask.data[row + xx];\n }\n return sum / 255;\n}\n\n/**\n * Combines `next` with `prev` by `mode` (8-bit, exact: combining a mask with itself is a no-op).\n * Add is the maximum, Subtract `max(prev − next, 0)`, Intersect the minimum. A missing `prev`\n * reads as nothing selected. Returns `null` when nothing remains selected.\n */\nexport function combineSelections(\n prev: SelectionMask | null | undefined,\n next: SelectionMask,\n mode: SelectionMode\n): SelectionMask | null {\n let result: SelectionMask;\n if (mode === 'replace' || (!prev && mode === 'add')) {\n result = next;\n } else if (!prev) {\n return null;\n } else {\n if (prev.width !== next.width || prev.height !== next.height) throw new Error('Selections differ in size');\n const a = prev.data;\n const b = next.data;\n const out = new Uint8Array(a.length);\n if (mode === 'add') for (let i = 0; i < a.length; i++) out[i] = a[i] > b[i] ? a[i] : b[i];\n else if (mode === 'subtract') for (let i = 0; i < a.length; i++) out[i] = a[i] > b[i] ? a[i] - b[i] : 0;\n else for (let i = 0; i < a.length; i++) out[i] = a[i] < b[i] ? a[i] : b[i];\n result = createSelectionMask(prev.width, prev.height, out);\n }\n return result.bounds ? result : null;\n}\n\n/** Select › Inverse. */\nexport function invertSelection(mask: SelectionMask): SelectionMask {\n const out = new Uint8Array(mask.data.length);\n for (let i = 0; i < out.length; i++) out[i] = 255 - mask.data[i];\n return createSelectionMask(mask.width, mask.height, out);\n}\n\n/** Bilinear resample to another size (e.g. an AI mask returned at a different resolution). */\nexport function resizeSelection(mask: SelectionMask, width: number, height: number): SelectionMask {\n if (mask.width === width && mask.height === height) return mask;\n checkSize(width, height);\n const out = new Uint8Array(width * height);\n const sx = mask.width / width;\n const sy = mask.height / height;\n const src = mask.data;\n const mw = mask.width;\n for (let y = 0; y < height; y++) {\n const fy = Math.min(mask.height - 1, Math.max(0, (y + 0.5) * sy - 0.5));\n const y0 = Math.floor(fy);\n const y1 = Math.min(mask.height - 1, y0 + 1);\n const ty = fy - y0;\n for (let x = 0; x < width; x++) {\n const fx = Math.min(mw - 1, Math.max(0, (x + 0.5) * sx - 0.5));\n const x0 = Math.floor(fx);\n const x1 = Math.min(mw - 1, x0 + 1);\n const tx = fx - x0;\n const top = src[y0 * mw + x0] * (1 - tx) + src[y0 * mw + x1] * tx;\n const bot = src[y1 * mw + x0] * (1 - tx) + src[y1 * mw + x1] * tx;\n out[y * width + x] = Math.round(top * (1 - ty) + bot * ty);\n }\n }\n return createSelectionMask(width, height, out);\n}\n\nexport type SelectionChannel = 'alpha' | 'luminance' | 'red' | 'green' | 'blue';\n\n/**\n * A selection from an image: by default its luminance (white = selected, the usual convention\n * of segmentation masks), or one channel. Partly transparent pixels count as black for\n * luminance, so a mask PNG with transparency works either way.\n */\nexport function selectionFromPixels(\n pixels: PixelBuffer,\n options: { channel?: SelectionChannel; invert?: boolean; threshold?: number } = {}\n): SelectionMask {\n const { width, height, data } = pixels;\n checkSize(width, height);\n if (data.length < width * height * 4) throw new Error('Pixel data is shorter than width × height × 4');\n const channel = options.channel ?? 'luminance';\n const out = new Uint8Array(width * height);\n const threshold = options.threshold;\n for (let i = 0, p = 0; i < out.length; i++, p += 4) {\n let v: number;\n switch (channel) {\n case 'alpha':\n v = data[p + 3];\n break;\n case 'red':\n v = data[p];\n break;\n case 'green':\n v = data[p + 1];\n break;\n case 'blue':\n v = data[p + 2];\n break;\n default:\n v = Math.round(((0.2126 * data[p] + 0.7152 * data[p + 1] + 0.0722 * data[p + 2]) * data[p + 3]) / 255);\n }\n if (options.invert) v = 255 - v;\n if (threshold !== undefined) v = v >= threshold ? 255 : 0;\n out[i] = v;\n }\n return createSelectionMask(width, height, out);\n}\n\n/** The selection as RGBA pixels: grey (white = selected, opaque) or white with coverage as alpha. */\nexport function selectionToPixels(mask: SelectionMask, as: 'gray' | 'alpha' = 'gray'): { width: number; height: number; data: Uint8ClampedArray } {\n const out = new Uint8ClampedArray(mask.data.length * 4);\n for (let i = 0, p = 0; i < mask.data.length; i++, p += 4) {\n const v = mask.data[i];\n if (as === 'gray') {\n out[p] = out[p + 1] = out[p + 2] = v;\n out[p + 3] = 255;\n } else {\n out[p] = out[p + 1] = out[p + 2] = 255;\n out[p + 3] = v;\n }\n }\n return { width: mask.width, height: mask.height, data: out };\n}\n\n// ---------------------------------------------------------------------------\n// Run-length encoding\n// ---------------------------------------------------------------------------\n\nconst B64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\nconst B64_INDEX = (() => {\n const t = new Int16Array(128).fill(-1);\n for (let i = 0; i < B64.length; i++) t[B64.charCodeAt(i)] = i;\n return t;\n})();\n\nfunction toBase64(bytes: Uint8Array, length: number): string {\n const parts: string[] = [];\n let chunk = '';\n for (let i = 0; i < length; i += 3) {\n const a = bytes[i];\n const b = i + 1 < length ? bytes[i + 1] : 0;\n const c = i + 2 < length ? bytes[i + 2] : 0;\n chunk += B64[a >> 2] + B64[((a & 3) << 4) | (b >> 4)];\n chunk += i + 1 < length ? B64[((b & 15) << 2) | (c >> 6)] : '=';\n chunk += i + 2 < length ? B64[c & 63] : '=';\n if (chunk.length >= 8192) {\n parts.push(chunk);\n chunk = '';\n }\n }\n parts.push(chunk);\n return parts.join('');\n}\n\nfunction fromBase64(text: string): Uint8Array {\n if (text.length % 4 !== 0) throw new Error('Invalid base64 length');\n let pad = 0;\n if (text.endsWith('==')) pad = 2;\n else if (text.endsWith('=')) pad = 1;\n const out = new Uint8Array((text.length / 4) * 3 - pad);\n let o = 0;\n const digit = (i: number, k: number): number => {\n const ch = text.charCodeAt(i + k);\n if (ch === 61 /* = */ && i + 4 === text.length && k >= 4 - pad) return 0;\n const idx = ch < 128 ? B64_INDEX[ch] : -1;\n if (idx < 0) throw new Error('Invalid base64 character');\n return idx;\n };\n for (let i = 0; i < text.length; i += 4) {\n const n = (digit(i, 0) << 18) | (digit(i, 1) << 12) | (digit(i, 2) << 6) | digit(i, 3);\n if (o < out.length) out[o++] = (n >> 16) & 255;\n if (o < out.length) out[o++] = (n >> 8) & 255;\n if (o < out.length) out[o++] = n & 255;\n }\n return out;\n}\n\n/** Run-length encodes coverage bytes: `r1:` + base64 of (value, LEB128 run length) pairs. */\nexport function encodeSelectionRle(data: Uint8Array): string {\n let buf = new Uint8Array(64);\n let len = 0;\n const push = (b: number) => {\n if (len === buf.length) {\n const next = new Uint8Array(buf.length * 2);\n next.set(buf);\n buf = next;\n }\n buf[len++] = b;\n };\n let i = 0;\n while (i < data.length) {\n const v = data[i];\n let j = i + 1;\n while (j < data.length && data[j] === v) j++;\n push(v);\n let run = j - i;\n while (run >= 128) {\n push((run & 127) | 128);\n run = Math.floor(run / 128);\n }\n push(run);\n i = j;\n }\n return 'r1:' + toBase64(buf, len);\n}\n\n/** Decodes {@link encodeSelectionRle}; throws unless the runs cover exactly `length` bytes. */\nexport function decodeSelectionRle(rle: string, length: number): Uint8Array {\n if (typeof rle !== 'string' || !rle.startsWith('r1:')) throw new Error('Unknown selection encoding');\n const bytes = fromBase64(rle.slice(3));\n const out = new Uint8Array(length);\n let o = 0;\n let i = 0;\n while (i < bytes.length) {\n const v = bytes[i++];\n let run = 0;\n let scale = 1;\n for (;;) {\n if (i >= bytes.length) throw new Error('Truncated selection run');\n const b = bytes[i++];\n run += (b & 127) * scale;\n if (b < 128) break;\n scale *= 128;\n if (scale > 2 ** 35) throw new Error('Selection run too long');\n }\n if (run === 0 || o + run > length) throw new Error('Selection runs do not match its size');\n if (v !== 0) out.fill(v, o, o + run);\n o += run;\n }\n if (o !== length) throw new Error('Selection runs do not match its size');\n return out;\n}\n\nexport function serializeSelection(mask: SelectionMask): SerializedSelection {\n return { width: mask.width, height: mask.height, rle: encodeSelectionRle(mask.data) };\n}\n\n/** Restores a {@link SerializedSelection}; throws on damaged data. */\nexport function deserializeSelection(value: unknown): SelectionMask {\n if (!value || typeof value !== 'object') throw new Error('selection must be an object');\n const v = value as Record<string, unknown>;\n const width = v.width as number;\n const height = v.height as number;\n checkSize(width, height);\n return createSelectionMask(width, height, decodeSelectionRle(v.rle as string, width * height));\n}\n\n/** A short, stable hash of a string or bytes (FNV-1a, 32-bit, hex) for cache keys. */\nexport function hashMaskContent(content: string | Uint8Array): string {\n let h = 0x811c9dc5;\n if (typeof content === 'string') {\n for (let i = 0; i < content.length; i++) {\n h ^= content.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n } else {\n for (let i = 0; i < content.length; i++) {\n h ^= content[i];\n h = Math.imul(h, 0x01000193);\n }\n }\n return `${(h >>> 0).toString(16)}:${content.length}`;\n}\n","// Portions ported from photocraft/crates/algo/src/selection.rs, photocraft/crates/algo/src/selection/distance.rs, photocraft/crates/algo/src/selection_blur.rs and photocraft/crates/algo/src/matting.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\nimport { createSelectionMask, coverageToByte, selectionToCoverage, type SelectionMask } from './mask';\nimport { gaussianBlur } from '../../raster/blur';\n\nexport { gaussianBlur, DIRECT_GAUSSIAN_MAX_SIGMA } from '../../raster/blur';\n\n/** Distance reported for every pixel when there is no seed at all. */\nexport const EMPTY_DISTANCE = 1e10;\n\n/**\n * Exact Euclidean distance from every pixel centre to the nearest seed pixel centre (row-major\n * `inside`, non-zero = seed): nearest seeds along columns, then the lower envelope of parabolas\n * along rows (Felzenszwalb & Huttenlocher). Linear in the pixel count.\n */\nexport function distanceTransform(inside: ArrayLike<number | boolean>, width: number, height: number): Float32Array {\n const n = width * height;\n const out = new Float32Array(n);\n let any = false;\n let all = true;\n for (let i = 0; i < n; i++) {\n if (inside[i]) any = true;\n else all = false;\n }\n if (!any) return out.fill(EMPTY_DISTANCE);\n if (all) return out;\n const NONE = 0xffffffff;\n // Vertical distance to the nearest seed in the same column.\n const vertical = new Uint32Array(n).fill(NONE);\n for (let x = 0; x < width; x++) {\n let nearest = -1;\n for (let y = 0; y < height; y++) {\n const i = y * width + x;\n if (inside[i]) nearest = y;\n if (nearest >= 0) vertical[i] = y - nearest;\n }\n nearest = -1;\n for (let y = height - 1; y >= 0; y--) {\n const i = y * width + x;\n if (vertical[i] === 0) nearest = y;\n if (nearest >= 0) vertical[i] = Math.min(vertical[i], nearest - y);\n }\n }\n const sites = new Int32Array(width);\n const starts = new Float64Array(width);\n for (let y = 0; y < height; y++) {\n const row = y * width;\n let count = 0;\n for (let q = 0; q < width; q++) {\n const dq = vertical[row + q];\n if (dq === NONE) continue;\n const fq = dq * dq;\n let start = -Infinity;\n while (count > 0) {\n const p = sites[count - 1];\n const dp = vertical[row + p];\n start = (fq - dp * dp) / (2 * (q - p)) + (q + p) * 0.5;\n if (start > starts[count - 1]) break;\n count--;\n }\n if (count === 0) start = -Infinity;\n sites[count] = q;\n starts[count] = start;\n count++;\n }\n if (count === 0) {\n out.fill(EMPTY_DISTANCE, row, row + width);\n continue;\n }\n let k = 0;\n for (let x = 0; x < width; x++) {\n while (k + 1 < count && starts[k + 1] < x) k++;\n const site = sites[k];\n const dx = x - site;\n const dv = vertical[row + site];\n out[row + x] = Math.sqrt(dx * dx + dv * dv);\n }\n }\n return out;\n}\n\nfunction thresholdInside(m: ArrayLike<number>, threshold = 0.5): Uint8Array {\n const out = new Uint8Array(m.length);\n for (let i = 0; i < m.length; i++) out[i] = m[i] >= threshold ? 1 : 0;\n return out;\n}\n\n/** Grows coverage (0..1) by `r` pixels: covered when the nearest selected centre is ≤ r away (fractional r → partial). */\nexport function expandCoverage(m: ArrayLike<number>, w: number, h: number, r: number): Float32Array {\n const d = distanceTransform(thresholdInside(m), w, h);\n const out = new Float32Array(m.length);\n for (let i = 0; i < out.length; i++) out[i] = Math.max(m[i], Math.min(1, Math.max(0, r + 1 - d[i])));\n return out;\n}\n\n/** Shrinks coverage by `r` pixels (expands the inverse). */\nexport function contractCoverage(m: ArrayLike<number>, w: number, h: number, r: number): Float32Array {\n const inv = new Float32Array(m.length);\n for (let i = 0; i < inv.length; i++) inv[i] = 1 - m[i];\n const e = expandCoverage(inv, w, h, r);\n for (let i = 0; i < e.length; i++) e[i] = 1 - e[i];\n return e;\n}\n\n/**\n * Select › Modify › Border: a soft band `2r` wide centred on the edge. A pixel `D` away (centre\n * to centre) from the nearest pixel on the other side of the edge is selected `1 − (D − 1) / r`.\n */\nexport function borderCoverage(m: ArrayLike<number>, w: number, h: number, r: number): Float32Array {\n const inside = thresholdInside(m);\n const outside = new Uint8Array(inside.length);\n for (let i = 0; i < inside.length; i++) outside[i] = inside[i] ? 0 : 1;\n const out = new Float32Array(m.length);\n if (!(r > 0)) return out;\n const toInside = distanceTransform(inside, w, h);\n const toOutside = distanceTransform(outside, w, h);\n for (let i = 0; i < out.length; i++) {\n const d = inside[i] ? toOutside[i] : toInside[i];\n out[i] = Math.min(1, Math.max(0, 1 - (d - 1) / r));\n }\n return out;\n}\n\n/** Box average of radius `r` along one line, edges replicated, via a running sum (O(1) per sample). */\nfunction boxLineReplicate(src: Float32Array, srcOff: number, stride: number, len: number, r: number, dst: Float32Array) {\n const at = (i: number) => src[srcOff + Math.min(len - 1, Math.max(0, i)) * stride];\n let sum = 0;\n for (let i = -r; i <= r; i++) sum += at(i);\n const k = 2 * r + 1;\n for (let i = 0; i < len; i++) {\n dst[i] = sum / k;\n sum += at(i + r + 1) - at(i - r);\n }\n}\n\n/**\n * Select › Modify › Smooth: box average of radius `round(r)` with replicated edges, then\n * re-thresholded at ½ — specks drop, corners round, straight edges stay hard.\n */\nexport function smoothCoverage(m: ArrayLike<number>, w: number, h: number, r: number): Float32Array {\n const rr = Number.isFinite(r) ? Math.max(0, Math.round(r)) : 0;\n const src = Float32Array.from(m);\n const tmp = new Float32Array(src.length);\n const line = new Float32Array(Math.max(w, h));\n if (rr > 0) {\n for (let y = 0; y < h; y++) {\n boxLineReplicate(src, y * w, 1, w, rr, line);\n tmp.set(line.subarray(0, w), y * w);\n }\n for (let x = 0; x < w; x++) {\n boxLineReplicate(tmp, x, w, h, rr, line);\n for (let y = 0; y < h; y++) src[y * w + x] = line[y];\n }\n }\n for (let i = 0; i < src.length; i++) src[i] = src[i] >= 0.5 ? 1 : 0;\n return src;\n}\n\n\n/** Select › Modify › Feather: Gaussian blur with σ = radius (as Photoshop), zero beyond the image. */\nexport function featherCoverage(m: ArrayLike<number>, w: number, h: number, radius: number): Float32Array {\n const out = gaussianBlur(m, w, h, Number.isFinite(radius) ? Math.max(0, radius) : 0, 'zero');\n for (let i = 0; i < out.length; i++) if (Math.abs(out[i]) < 1e-7) out[i] = 0;\n return out;\n}\n\n/**\n * Running max (or min) over a window of radius `r` along one line (edge-clipped), van Herk /\n * Gil–Werman: about three comparisons per sample whatever the radius.\n */\nfunction runningExtreme(src: Float32Array, off: number, stride: number, len: number, r: number, max: boolean, g: Float32Array, hb: Float32Array, dst: Float32Array) {\n const k = 2 * r + 1;\n const pick = max ? (a: number, b: number) => (a > b ? a : b) : (a: number, b: number) => (a < b ? a : b);\n const fill = max ? -Infinity : Infinity;\n // Padded line: r fills on each side.\n const plen = len + 2 * r;\n const at = (i: number) => (i < r || i >= r + len ? fill : src[off + (i - r) * stride]);\n for (let start = 0; start < plen; start += k) {\n const end = Math.min(plen, start + k);\n g[start] = at(start);\n for (let i = start + 1; i < end; i++) g[i] = pick(g[i - 1], at(i));\n hb[end - 1] = at(end - 1);\n for (let i = end - 2; i >= start; i--) hb[i] = pick(hb[i + 1], at(i));\n }\n for (let i = 0; i < len; i++) {\n // Window over padded indices i .. i + 2r.\n const a = hb[i];\n const b = g[i + 2 * r];\n dst[i] = pick(a, b);\n }\n}\n\n/** Grey dilation (`grow`) or erosion with a square of radius `r`, separable, O(1) per pixel. */\nexport function greyMorphology(src: ArrayLike<number>, w: number, h: number, r: number, grow: boolean): Float32Array {\n const out = Float32Array.from(src);\n if (r <= 0) return out;\n const n = Math.max(w, h);\n const g = new Float32Array(n + 2 * r);\n const hb = new Float32Array(n + 2 * r);\n const line = new Float32Array(n);\n for (let y = 0; y < h; y++) {\n runningExtreme(out, y * w, 1, w, r, grow, g, hb, line);\n out.set(line.subarray(0, w), y * w);\n }\n for (let x = 0; x < w; x++) {\n runningExtreme(out, x, w, h, r, grow, g, hb, line);\n for (let y = 0; y < h; y++) out[y * w + x] = line[y];\n }\n return out;\n}\n\n/**\n * Grows (positive) or shrinks (negative) soft coverage by a fractional distance: whole pixels\n * use one morphology, the remainder blends with the next pixel, so 0.3 px differs from 1 px.\n */\nexport function shiftCoverage(src: ArrayLike<number>, w: number, h: number, amount: number): Float32Array {\n const mag = Math.abs(amount);\n if (!Number.isFinite(mag) || mag < 1e-4) return Float32Array.from(src);\n const grow = amount > 0;\n const whole = Math.floor(mag);\n const frac = mag - whole;\n if (frac < 1e-4) return greyMorphology(src, w, h, whole, grow);\n const lo = whole === 0 ? Float32Array.from(src) : greyMorphology(src, w, h, whole, grow);\n const hi = greyMorphology(src, w, h, whole + 1, grow);\n for (let i = 0; i < lo.length; i++) lo[i] += (hi[i] - lo[i]) * frac;\n return lo;\n}\n\n// ---------------------------------------------------------------------------\n// Selection-level wrappers\n// ---------------------------------------------------------------------------\n\nexport type SelectionModifyOp = 'grow' | 'shrink' | 'feather' | 'border' | 'smooth';\n\nexport const SELECTION_MODIFY_OPS: readonly SelectionModifyOp[] = ['grow', 'shrink', 'feather', 'border', 'smooth'];\n\nfunction wrap(mask: SelectionMask, coverage: Float32Array): SelectionMask {\n const out = new Uint8Array(coverage.length);\n for (let i = 0; i < out.length; i++) out[i] = coverageToByte(coverage[i]);\n return createSelectionMask(mask.width, mask.height, out);\n}\n\n/** Select › Modify (grow = Expand, shrink = Contract, feather, border, smooth) by `radius` pixels. */\nexport function modifySelection(mask: SelectionMask, op: SelectionModifyOp, radius: number): SelectionMask {\n const r = Number.isFinite(radius) ? Math.max(0, radius) : 0;\n if (r === 0 && op !== 'border') return mask;\n const m = selectionToCoverage(mask);\n const { width: w, height: h } = mask;\n switch (op) {\n case 'grow':\n return wrap(mask, expandCoverage(m, w, h, r));\n case 'shrink':\n return wrap(mask, contractCoverage(m, w, h, r));\n case 'feather':\n return wrap(mask, featherCoverage(m, w, h, r));\n case 'border':\n return wrap(mask, borderCoverage(m, w, h, r));\n case 'smooth':\n return wrap(mask, smoothCoverage(m, w, h, r));\n default:\n throw new Error(`Unknown selection modification \"${op as string}\"`);\n }\n}\n","// Portions ported from photocraft/crates/engine/src/layer_mask_props_cmds.rs and photocraft/crates/compose/src/masks.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\nimport type { AdjustmentLayer, AdjustmentLayerMask } from '../types';\nimport { unprojectCanvasPoint, type ImageProjection } from '../geometry';\nimport {\n createSelectionMask,\n decodeSelectionRle,\n encodeSelectionRle,\n hashMaskContent,\n type SelectionMask,\n} from './mask';\nimport { gaussianBlur } from './modify';\n\n/** Properties of a layer mask that leave its pixels intact (Photoshop's Properties › Masks). */\nexport interface LayerMaskProps {\n /** 0..1 (default 1): at 0.5 the masked-out areas still get half of the adjustment. */\n density?: number;\n /** Gaussian feather σ in pixels, 0..1000 (default 0). */\n feather?: number;\n /** Disabled masks are kept but ignored (the layer applies everywhere). Default true. */\n enabled?: boolean;\n /**\n * Layer masks only: `false` unlinks the mask, so moving or transforming the layer's pixels leaves\n * it in place. Default true. Adjustment-layer masks in canvas space ignore it.\n */\n linked?: boolean;\n}\n\nconst MAX_SIDE = 1 << 15;\n\n/**\n * A clean layer mask from stored data, or `null` when it is unusable. Defaults are left out\n * (density 1, feather 0, enabled), so a plain mask serializes as `{width, height, rle}`. The\n * RLE is only decoded when rendering.\n */\nexport function normalizeLayerMask(raw: unknown): AdjustmentLayerMask | null {\n if (!raw || typeof raw !== 'object') return null;\n const r = raw as Record<string, unknown>;\n const { width, height, rle } = r;\n if (\n typeof width !== 'number' ||\n typeof height !== 'number' ||\n !Number.isInteger(width) ||\n !Number.isInteger(height) ||\n width < 1 ||\n height < 1 ||\n width > MAX_SIDE ||\n height > MAX_SIDE ||\n typeof rle !== 'string' ||\n !rle.startsWith('r1:')\n ) {\n return null;\n }\n const out: AdjustmentLayerMask = { width, height, rle };\n if (typeof r.density === 'number' && Number.isFinite(r.density)) {\n const d = Math.min(1, Math.max(0, r.density));\n if (d !== 1) out.density = d;\n }\n if (typeof r.feather === 'number' && Number.isFinite(r.feather)) {\n const f = Math.min(1000, Math.max(0, r.feather));\n if (f !== 0) out.feather = f;\n }\n if (r.enabled === false) out.enabled = false;\n return out;\n}\n\n/** A layer mask holding the selection's pixels (Add Layer Mask › Reveal Selection). */\nexport function layerMaskFromSelection(selection: SelectionMask, props: LayerMaskProps = {}): AdjustmentLayerMask {\n return normalizeLayerMask({ width: selection.width, height: selection.height, rle: encodeSelectionRle(selection.data), ...props })!;\n}\n\n/** Applies property changes; `undefined` fields keep their value. */\nexport function updateLayerMaskProps(mask: AdjustmentLayerMask, props: LayerMaskProps): AdjustmentLayerMask {\n return normalizeLayerMask({\n ...mask,\n ...(props.density !== undefined ? { density: props.density } : {}),\n ...(props.feather !== undefined ? { feather: props.feather } : {}),\n ...(props.enabled !== undefined ? { enabled: props.enabled } : {}),\n })!;\n}\n\n// Small LRU caches: masks are stored as RLE strings and layers are deep-copied often.\nconst DECODE_LIMIT = 8;\nconst decoded = new Map<string, SelectionMask | null>();\nconst coverageCache = new Map<string, Float32Array | null>();\n\nfunction lru<T>(cache: Map<string, T>, key: string, make: () => T): T {\n if (cache.has(key)) {\n const v = cache.get(key)!;\n cache.delete(key);\n cache.set(key, v);\n return v;\n }\n const v = make();\n cache.set(key, v);\n if (cache.size > DECODE_LIMIT) cache.delete(cache.keys().next().value as string);\n return v;\n}\n\n/** The mask's pixels as a selection (Load Selection from mask), or `null` when the data is damaged. */\nexport function decodeLayerMask(mask: AdjustmentLayerMask): SelectionMask | null {\n const key = `${mask.width}x${mask.height}:${mask.rle}`;\n return lru(decoded, key, () => {\n try {\n return createSelectionMask(mask.width, mask.height, decodeSelectionRle(mask.rle, mask.width * mask.height));\n } catch {\n return null;\n }\n });\n}\n\n/** Cache key of everything about a mask that changes pixels. */\nexport function layerMaskKey(mask: AdjustmentLayerMask | undefined): string {\n if (!mask) return '';\n return `${mask.width}x${mask.height}:${hashMaskContent(mask.rle)}:${mask.density ?? 1}:${mask.feather ?? 0}:${mask.enabled !== false}`;\n}\n\n/**\n * The mask's effective coverage (0..1, mask resolution): feathered (σ = feather, borders\n * replicated), then density applied as `1 − density·(1 − v)`. `null` for a disabled or damaged\n * mask, meaning \"no mask\".\n */\nexport function layerMaskCoverage(mask: AdjustmentLayerMask): Float32Array | null {\n if (mask.enabled === false) return null;\n return lru(coverageCache, layerMaskKey(mask), () => {\n const sel = decodeLayerMask(mask);\n if (!sel) return null;\n let v: Float32Array = new Float32Array(sel.data.length);\n for (let i = 0; i < v.length; i++) v[i] = sel.data[i] / 255;\n const feather = mask.feather ?? 0;\n if (feather > 0) v = gaussianBlur(v, mask.width, mask.height, feather, 'clamp');\n const density = mask.density ?? 1;\n if (density !== 1) for (let i = 0; i < v.length; i++) v[i] = 1 - density * (1 - v[i]);\n return v;\n });\n}\n\n/**\n * Resamples mask coverage (natural-image aligned, any resolution) into a target raster drawn\n * through `projection` (the base canvas of the renderer: crop, rotation, flips): each target\n * pixel centre is unprojected to natural pixels and sampled bilinearly.\n */\nexport function projectMaskCoverage(\n coverage: Float32Array,\n maskWidth: number,\n maskHeight: number,\n naturalWidth: number,\n naturalHeight: number,\n projection: ImageProjection,\n targetWidth: number,\n targetHeight: number\n): Float32Array {\n const out = new Float32Array(targetWidth * targetHeight);\n const o = unprojectCanvasPoint({ x: 0.5, y: 0.5 }, projection);\n const ox = unprojectCanvasPoint({ x: 1.5, y: 0.5 }, projection);\n const oy = unprojectCanvasPoint({ x: 0.5, y: 1.5 }, projection);\n const kx = maskWidth / naturalWidth;\n const ky = maskHeight / naturalHeight;\n // Affine map target → mask coordinates (pixel centres at +0.5).\n const ux = (ox.x - o.x) * kx;\n const vx = (ox.y - o.y) * ky;\n const uy = (oy.x - o.x) * kx;\n const vy = (oy.y - o.y) * ky;\n const u0 = o.x * kx - 0.5;\n const v0 = o.y * ky - 0.5;\n const mw1 = maskWidth - 1;\n const mh1 = maskHeight - 1;\n for (let y = 0; y < targetHeight; y++) {\n let u = u0 + y * uy;\n let v = v0 + y * vy;\n const row = y * targetWidth;\n for (let x = 0; x < targetWidth; x++, u += ux, v += vx) {\n const fu = u < 0 ? 0 : u > mw1 ? mw1 : u;\n const fv = v < 0 ? 0 : v > mh1 ? mh1 : v;\n const x0 = fu | 0;\n const y0 = fv | 0;\n const x1 = x0 < mw1 ? x0 + 1 : x0;\n const y1 = y0 < mh1 ? y0 + 1 : y0;\n const tx = fu - x0;\n const ty = fv - y0;\n const a = coverage[y0 * maskWidth + x0] * (1 - tx) + coverage[y0 * maskWidth + x1] * tx;\n const b = coverage[y1 * maskWidth + x0] * (1 - tx) + coverage[y1 * maskWidth + x1] * tx;\n out[row + x] = a * (1 - ty) + b * ty;\n }\n }\n return out;\n}\n\n/**\n * Per-layer mask coverage for a render target, keyed by layer id; `undefined` when no layer has\n * an enabled, valid mask (so mask-free stacks take the exact same path as before).\n */\nexport function buildLayerMaskPlanes(\n layers: readonly AdjustmentLayer[] | undefined,\n naturalWidth: number,\n naturalHeight: number,\n projection: ImageProjection,\n targetWidth: number,\n targetHeight: number\n): Map<string, Float32Array> | undefined {\n if (!layers?.some((l) => l.mask && l.mask.enabled !== false)) return undefined;\n const planes = new Map<string, Float32Array>();\n for (const layer of layers) {\n if (!layer.mask) continue;\n const coverage = layerMaskCoverage(layer.mask);\n if (!coverage) continue;\n planes.set(\n layer.id,\n projectMaskCoverage(coverage, layer.mask.width, layer.mask.height, naturalWidth, naturalHeight, projection, targetWidth, targetHeight)\n );\n }\n return planes.size ? planes : undefined;\n}\n","// Portions ported from photocraft-main/crates/compose/src/adjust.rs, photocraft-main/crates/doc/src/adjust.rs and photocraft-main/crates/color/src/convert.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\nimport type {\n AdjustmentKind,\n AdjustmentLayer,\n AdjustmentLayerMask,\n AdjustmentLayerOf,\n AdjustmentParamsMap,\n BlackWhiteParams,\n ColorBalanceParams,\n ColorBalanceTone,\n CurvePoint,\n HueRange,\n HueSaturationParams,\n LevelsChannel,\n PhotoFilterParams,\n VibranceParams,\n} from './types';\nimport { type BlendMode, blendRgb, mixPigmentRgb, normalizeBlendMode, setLum } from './blend';\nimport { linearToSrgb, srgbToLinear } from './color';\nimport { layerMaskKey, normalizeLayerMask } from './selection/layer-mask';\nimport { curveLut } from '../raster/curve';\n\nexport { curveLut };\n\n/**\n * Adjustment layers: Photoshop-matching evaluation on encoded sRGB colours in 0..1.\n *\n * Every layer is compiled once per parameter change into a per-pixel function (per-channel kinds\n * into three 4096-entry LUTs); {@link applyAdjustmentLayers} runs the stack on an ImageData.\n */\n\ntype Rgb = [number, number, number];\n\n/** Entries in a tone LUT (curves, levels, colour balance). */\nexport const ADJUSTMENT_LUT_SIZE = 4096;\n\nconst clamp01 = (v: number) => (v <= 0 ? 0 : v >= 1 ? 1 : v);\nconst finite = (v: unknown, fallback: number) => (typeof v === 'number' && Number.isFinite(v) ? v : fallback);\nconst clampTo = (v: unknown, lo: number, hi: number, fallback: number) => Math.min(hi, Math.max(lo, finite(v, fallback)));\nconst remEuclid = (a: number, b: number) => ((a % b) + b) % b;\n\n/** Linear interpolation into a 0..1-domain LUT. */\nexport function sampleLut(table: ArrayLike<number>, v: number): number {\n const n = table.length - 1;\n const x = clamp01(v) * n;\n const i = Math.floor(x);\n const j = Math.min(i + 1, n);\n const f = x - i;\n return table[i] * (1 - f) + table[j] * f;\n}\n\n// ---------------------------------------------------------------------------\n// Kinds, labels, defaults\n// ---------------------------------------------------------------------------\n\nexport interface AdjustmentKindInfo {\n kind: AdjustmentKind;\n label: string;\n}\n\nexport const ADJUSTMENT_KINDS: readonly AdjustmentKindInfo[] = [\n { kind: 'curves', label: 'Curves' },\n { kind: 'levels', label: 'Levels' },\n { kind: 'hueSaturation', label: 'Hue/Saturation' },\n { kind: 'vibrance', label: 'Vibrance' },\n { kind: 'colorBalance', label: 'Color Balance' },\n { kind: 'blackWhite', label: 'Black & White' },\n { kind: 'photoFilter', label: 'Photo Filter' },\n { kind: 'invert', label: 'Invert' },\n { kind: 'posterize', label: 'Posterize' },\n { kind: 'threshold', label: 'Threshold' },\n];\n\nconst KIND_LABELS = new Map(ADJUSTMENT_KINDS.map((k) => [k.kind, k.label]));\n\nexport function isAdjustmentKind(value: unknown): value is AdjustmentKind {\n return typeof value === 'string' && KIND_LABELS.has(value as AdjustmentKind);\n}\n\nexport function adjustmentKindLabel(kind: AdjustmentKind): string {\n return KIND_LABELS.get(kind) ?? kind;\n}\n\nexport const HUE_RANGE_LABELS = ['Reds', 'Yellows', 'Greens', 'Cyans', 'Blues', 'Magentas'] as const;\n\n/** The neutral range `i` (0 reds … 5 magentas): fully on for 30° around its centre, fading over 30° each side. */\nexport function neutralHueRange(i: number): HueRange {\n const c = 60 * i;\n return { hue: 0, saturation: 0, lightness: 0, bounds: [c - 45, c - 15, c + 15, c + 45] };\n}\n\nexport const identityCurve = (): CurvePoint[] => [\n { input: 0, output: 0 },\n { input: 255, output: 255 },\n];\n\nexport const identityLevels = (): LevelsChannel => ({ inBlack: 0, inWhite: 255, gamma: 1, outBlack: 0, outWhite: 255 });\n\n/** Neutral parameters of every kind (fresh objects on every call). */\nexport function defaultAdjustmentParams<K extends AdjustmentKind>(kind: K): AdjustmentParamsMap[K] {\n const defaults: { [P in AdjustmentKind]: () => AdjustmentParamsMap[P] } = {\n curves: () => ({ master: identityCurve(), red: identityCurve(), green: identityCurve(), blue: identityCurve() }),\n levels: () => ({ master: identityLevels(), red: identityLevels(), green: identityLevels(), blue: identityLevels() }),\n hueSaturation: () => ({\n hue: 0,\n saturation: 0,\n lightness: 0,\n colorize: false,\n ranges: [0, 1, 2, 3, 4, 5].map(neutralHueRange),\n }),\n vibrance: () => ({ vibrance: 0, saturation: 0 }),\n colorBalance: () => ({ shadows: [0, 0, 0], midtones: [0, 0, 0], highlights: [0, 0, 0], preserveLuminosity: true }),\n blackWhite: () => ({ reds: 40, yellows: 60, greens: 40, cyans: 60, blues: 20, magentas: 80, tint: null }),\n invert: () => ({}) as Record<string, never>,\n posterize: () => ({ levels: 4 }),\n threshold: () => ({ level: 128 }),\n photoFilter: () => ({ color: '#ec8a00', density: 25, preserveLuminosity: true }),\n };\n return defaults[kind]() as AdjustmentParamsMap[K];\n}\n\nlet layerCounter = 0;\n\n/** A new layer of `kind` with neutral (or the given) parameters, enabled, opacity 1, normal blend. */\nexport function createAdjustmentLayer<K extends AdjustmentKind>(\n kind: K,\n options: {\n id?: string;\n name?: string;\n params?: Partial<AdjustmentParamsMap[K]>;\n enabled?: boolean;\n opacity?: number;\n blend?: BlendMode;\n mask?: AdjustmentLayerMask;\n } = {}\n): AdjustmentLayerOf<K> {\n const raw = {\n id: options.id ?? `adj_${Date.now().toString(36)}_${(++layerCounter).toString(36)}`,\n kind,\n name: options.name,\n params: { ...defaultAdjustmentParams(kind), ...(options.params ?? {}) },\n enabled: options.enabled ?? true,\n opacity: options.opacity ?? 1,\n blend: options.blend ?? 'normal',\n mask: options.mask,\n };\n return normalizeAdjustmentLayer(raw) as AdjustmentLayerOf<K>;\n}\n\n// ---------------------------------------------------------------------------\n// Normalisation of stored layers\n// ---------------------------------------------------------------------------\n\nconst HEX = /^#[0-9a-f]{6}$/i;\n\nfunction normCurve(points: unknown): CurvePoint[] {\n if (!Array.isArray(points)) return identityCurve();\n const out = points\n .filter((p) => p && typeof p === 'object')\n .map((p) => ({ input: clampTo(p.input, 0, 255, 0), output: clampTo(p.output, 0, 255, 0) }))\n .sort((a, b) => a.input - b.input);\n return out.length >= 2 ? out : identityCurve();\n}\n\nfunction normLevels(ch: unknown): LevelsChannel {\n const c = (ch && typeof ch === 'object' ? ch : {}) as Partial<LevelsChannel>;\n return {\n inBlack: clampTo(c.inBlack, 0, 254, 0),\n inWhite: clampTo(c.inWhite, 1, 255, 255),\n gamma: clampTo(c.gamma, 0.1, 9.99, 1),\n outBlack: clampTo(c.outBlack, 0, 255, 0),\n outWhite: clampTo(c.outWhite, 0, 255, 255),\n };\n}\n\nfunction normTone(t: unknown): ColorBalanceTone {\n const a = Array.isArray(t) ? t : [];\n return [clampTo(a[0], -100, 100, 0), clampTo(a[1], -100, 100, 0), clampTo(a[2], -100, 100, 0)];\n}\n\nfunction normParams(kind: AdjustmentKind, raw: unknown): AdjustmentParamsMap[AdjustmentKind] {\n const p = (raw && typeof raw === 'object' ? raw : {}) as Record<string, any>;\n const d = defaultAdjustmentParams(kind) as Record<string, any>;\n switch (kind) {\n case 'curves':\n return { master: normCurve(p.master), red: normCurve(p.red), green: normCurve(p.green), blue: normCurve(p.blue) };\n case 'levels':\n return { master: normLevels(p.master), red: normLevels(p.red), green: normLevels(p.green), blue: normLevels(p.blue) };\n case 'hueSaturation': {\n const ranges: HueRange[] = [0, 1, 2, 3, 4, 5].map((i) => {\n const r = Array.isArray(p.ranges) && p.ranges[i] && typeof p.ranges[i] === 'object' ? p.ranges[i] : {};\n const n = neutralHueRange(i);\n const b = Array.isArray(r.bounds) && r.bounds.length === 4 && r.bounds.every((v: unknown) => Number.isFinite(v)) ? r.bounds : n.bounds;\n return {\n hue: clampTo(r.hue, -180, 180, 0),\n saturation: clampTo(r.saturation, -100, 100, 0),\n lightness: clampTo(r.lightness, -100, 100, 0),\n bounds: [b[0], b[1], b[2], b[3]] as [number, number, number, number],\n };\n });\n const colorize = p.colorize === true;\n return {\n hue: colorize ? clampTo(p.hue, 0, 360, 0) : clampTo(p.hue, -180, 180, 0),\n saturation: clampTo(p.saturation, -100, 100, 0),\n lightness: clampTo(p.lightness, -100, 100, 0),\n colorize,\n ranges,\n } satisfies HueSaturationParams;\n }\n case 'vibrance':\n return { vibrance: clampTo(p.vibrance, -100, 100, 0), saturation: clampTo(p.saturation, -100, 100, 0) } satisfies VibranceParams;\n case 'colorBalance':\n return {\n shadows: normTone(p.shadows),\n midtones: normTone(p.midtones),\n highlights: normTone(p.highlights),\n preserveLuminosity: p.preserveLuminosity !== false,\n } satisfies ColorBalanceParams;\n case 'blackWhite': {\n const out = {} as BlackWhiteParams;\n for (const k of ['reds', 'yellows', 'greens', 'cyans', 'blues', 'magentas'] as const) out[k] = clampTo(p[k], -200, 300, d[k]);\n out.tint = typeof p.tint === 'string' && HEX.test(p.tint) ? p.tint.toLowerCase() : null;\n return out;\n }\n case 'photoFilter':\n return {\n color: typeof p.color === 'string' && HEX.test(p.color) ? p.color.toLowerCase() : d.color,\n density: clampTo(p.density, 0, 100, d.density),\n preserveLuminosity: p.preserveLuminosity !== false,\n } satisfies PhotoFilterParams;\n case 'posterize':\n return { levels: Math.round(clampTo(p.levels, 2, 255, 4)) };\n case 'threshold':\n return { level: Math.round(clampTo(p.level, 1, 255, 128)) };\n case 'invert':\n return {} as Record<string, never>;\n }\n}\n\n/**\n * A clean, deep-copied layer from stored or hand-written data: missing fields get neutral\n * defaults, numbers are clamped to their ranges. `null` for an unknown kind.\n */\nexport function normalizeAdjustmentLayer(raw: unknown): AdjustmentLayer | null {\n if (!raw || typeof raw !== 'object') return null;\n const r = raw as Record<string, any>;\n if (!isAdjustmentKind(r.kind)) return null;\n const layer: Record<string, unknown> = {\n id: typeof r.id === 'string' && r.id ? r.id : `adj_${Date.now().toString(36)}_${(++layerCounter).toString(36)}`,\n kind: r.kind,\n enabled: r.enabled !== false,\n opacity: clampTo(r.opacity, 0, 1, 1),\n blend: normalizeBlendMode(r.blend),\n params: normParams(r.kind, r.params),\n };\n if (typeof r.name === 'string' && r.name) layer.name = r.name;\n // Only present when the layer has a usable mask, so mask-free layers serialize as before.\n const mask = normalizeLayerMask(r.mask);\n if (mask) layer.mask = mask;\n return layer as unknown as AdjustmentLayer;\n}\n\n/** Normalises a stored layer list (drops unknown kinds, de-duplicates ids). */\nexport function normalizeAdjustmentLayers(raw: unknown): AdjustmentLayer[] {\n if (!Array.isArray(raw)) return [];\n const seen = new Set<string>();\n const out: AdjustmentLayer[] = [];\n for (const item of raw) {\n const layer = normalizeAdjustmentLayer(item);\n if (!layer) continue;\n while (seen.has(layer.id)) layer.id = `${layer.id}_${(++layerCounter).toString(36)}`;\n seen.add(layer.id);\n out.push(layer);\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Curves\n// ---------------------------------------------------------------------------\n\n/** {@link curveLut} for Curves points in levels (0..255). */\nexport function curveLutFromLevels(points: readonly CurvePoint[]): Float32Array {\n return curveLut(points.map((p) => ({ input: p.input / 255, output: p.output / 255 })));\n}\n\n// ---------------------------------------------------------------------------\n// Levels\n// ---------------------------------------------------------------------------\n\n/** A Levels record with values in 0..1 (as `levelsValue` takes them). */\nexport interface UnitLevels {\n inBlack: number;\n inWhite: number;\n gamma: number;\n outBlack: number;\n outWhite: number;\n}\n\nconst toUnitLevels = (c: LevelsChannel): UnitLevels => ({\n inBlack: c.inBlack / 255,\n inWhite: c.inWhite / 255,\n gamma: c.gamma,\n outBlack: c.outBlack / 255,\n outWhite: c.outWhite / 255,\n});\n\n/**\n * One Levels record on `v` (0..1). With `quantum` (8-bit: 255) Photoshop works on whole levels:\n * the input is a level and the stretch is rounded (half up) before the gamma. A gamma above 1 has\n * Photoshop's soft shadow toe: a p-norm soft-min of the power curve and a line of slope ≈ 2^gamma.\n */\nexport function levelsValue(ch: UnitLevels, v: number, quantum?: number): number {\n const range = Math.max(1e-6, ch.inWhite - ch.inBlack);\n let u: number;\n if (quantum !== undefined && Number.isFinite(quantum) && quantum >= 1) {\n const q = quantum;\n const vq = Math.round(v * q) / q;\n u = Math.floor(clamp01((vq - ch.inBlack) / range) * q + 0.5 + 1e-3) / q;\n } else {\n u = clamp01((v - ch.inBlack) / range);\n }\n const g = Math.max(0.01, ch.gamma);\n let t: number;\n if (g > 1) {\n const power = Math.pow(u, 1 / g);\n const line = 0.93 * Math.pow(2, g) * u;\n if (power <= 1e-6 || line <= 1e-6) t = Math.min(power, line);\n else {\n const p = 10;\n t = Math.pow(Math.pow(power, -p) + Math.pow(line, -p), -1 / p);\n }\n } else {\n t = Math.pow(u, 1 / g);\n }\n return ch.outBlack + t * (ch.outWhite - ch.outBlack);\n}\n\n// ---------------------------------------------------------------------------\n// HSL and Hue/Saturation\n// ---------------------------------------------------------------------------\n\n/** RGB (0..1) → HSL with hue in turns (0..1). */\nexport function rgbToHsl(c: readonly number[]): [number, number, number] {\n const max = Math.max(c[0], c[1], c[2]);\n const min = Math.min(c[0], c[1], c[2]);\n const l = (max + min) / 2;\n if (Math.abs(max - min) < 1e-7) return [0, 0, l];\n const d = max - min;\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n let h: number;\n if (max === c[0]) h = remEuclid((c[1] - c[2]) / d, 6);\n else if (max === c[1]) h = (c[2] - c[0]) / d + 2;\n else h = (c[0] - c[1]) / d + 4;\n return [h / 6, s, l];\n}\n\nexport function hslToRgb(h: number, s: number, l: number): Rgb {\n if (s <= 0) return [l, l, l];\n const q = l < 0.5 ? l * (1 + s) : l + s - l * s;\n const p = 2 * l - q;\n const f = (t: number) => {\n t = remEuclid(t, 1);\n if (t < 1 / 6) return p + (q - p) * 6 * t;\n if (t < 0.5) return q;\n if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;\n return p;\n };\n return [f(h + 1 / 3), f(h), f(h - 1 / 3)];\n}\n\n/** How much a pixel of hue `deg` belongs to `range` (0..1): a trapezoid that may wrap past 0/360. */\nexport function hueRangeWeight(range: Pick<HueRange, 'bounds'>, deg: number): number {\n const [a, b0, c0, d0] = range.bounds;\n const rel = (x: number) => (Number.isFinite(x) && Number.isFinite(a) ? remEuclid(x - a, 360) : 0);\n const b = rel(b0);\n const c = Math.max(rel(c0), b);\n const d = Math.max(rel(d0), c);\n const h = rel(deg);\n if (h <= b) return b <= 0 ? 1 : h / b;\n if (h <= c) return 1;\n if (h <= d) return d <= c ? 1 : (d - h) / (d - c);\n return 0;\n}\n\nconst isNeutralRange = (r: HueRange) => r.hue === 0 && r.saturation === 0 && r.lightness === 0;\n\n/** Range edits as three hue-indexed tables (hue shift in degrees, saturation and lightness as fractions). */\nexport function hueRangeTables(ranges: readonly HueRange[]): [Float32Array, Float32Array, Float32Array] {\n const n = ADJUSTMENT_LUT_SIZE;\n const t: [Float32Array, Float32Array, Float32Array] = [new Float32Array(n), new Float32Array(n), new Float32Array(n)];\n const active = ranges.filter((r) => !isNeutralRange(r));\n for (let k = 0; k < n; k++) {\n const deg = (k / (n - 1)) * 360;\n for (const r of active) {\n const w = hueRangeWeight(r, deg);\n t[0][k] += w * r.hue;\n t[1][k] += (w * r.saturation) / 100;\n t[2][k] += (w * r.lightness) / 100;\n }\n }\n return t;\n}\n\n/**\n * One Hue/Saturation evaluation: hue shift in degrees, saturation and lightness in -1..1.\n * Lightness first, then hue and saturation (raising divides by 1 − s, lowering multiplies by\n * 1 + s). Colorize sets hue and saturation at the lightened lightness.\n */\nexport function hueSaturation(c: readonly number[], hue: number, s: number, l: number, colorize: boolean): Rgb {\n const lighten = (v: number) => (l > 0 ? v + (1 - v) * l : v * (1 + l));\n if (colorize) {\n const ll = rgbToHsl(c)[2];\n return hslToRgb(remEuclid(hue, 360) / 360, Math.min(1, Math.abs(s)), lighten(ll));\n }\n const [hh0, ss0, ll] = rgbToHsl([lighten(c[0]), lighten(c[1]), lighten(c[2])]);\n const hh = remEuclid(hh0 + hue / 360, 1);\n const ss = Math.min(1, Math.max(0, s > 0 ? ss0 / Math.max(1 / 512, 1 - s) : ss0 * (1 + s)));\n return hslToRgb(hh, ss, ll);\n}\n\n/** A colour range's Lightness: towards the brightest channel when raised, the darkest when lowered. */\nexport function rangeLightness(c: readonly number[], l: number): Rgb {\n const k = Math.min(1, Math.max(-1, l));\n const target = k > 0 ? Math.max(c[0], c[1], c[2]) : Math.min(c[0], c[1], c[2]);\n const a = Math.abs(k);\n return [c[0] + (target - c[0]) * a, c[1] + (target - c[1]) * a, c[2] + (target - c[2]) * a];\n}\n\n// ---------------------------------------------------------------------------\n// Vibrance\n// ---------------------------------------------------------------------------\n\n/** Grey Vibrance › Saturation desaturates to (linear light; blue has no weight). */\nexport const VIBRANCE_GREY: readonly [number, number] = [0.288, 0.712];\nconst VIBRANCE_BOOST = [0.5001, 0.0887, 0.4374, 0.7573, -0.8113, 0.5154, 2.8026] as const;\n\nfunction vibranceSkin(h: number): number {\n if (h <= 30) return 1;\n if (h < 45) return (45 - h) / 15;\n if (h < 300) return 0;\n return (h - 300) / 60;\n}\n\nfunction hsvHue(c: readonly number[], mx: number, mn: number): number {\n const d = mx - mn;\n let h: number;\n if (mx === c[0]) h = remEuclid((c[1] - c[2]) / d, 6);\n else if (mx === c[1]) h = (c[2] - c[0]) / d + 2;\n else h = (c[0] - c[1]) / d + 4;\n return h * 60;\n}\n\n/** The Vibrance slider (`v` in -1..1) on a linear-light pixel: an HSV change that keeps the hue. */\nexport function vibranceLinear(c: Rgb, v: number): Rgb {\n const mx = Math.max(c[0], c[1], c[2]);\n const mn = Math.min(c[0], c[1], c[2]);\n if (mx <= 0 || mx - mn <= 1e-9) return c;\n const t = 1 - mn / mx;\n const shape = t * (1 - t) * (2 - t + t * t) * mx * Math.max(0, 1 - mx);\n let t2: number, v2: number;\n if (v < 0) {\n const a = -v;\n t2 = t * (1 - a / 4) * (1 - a + (a * t * (1 + t)) / 2);\n v2 = mx - a * shape;\n } else {\n const [c0, c1, p, q, r0, ra, rt] = VIBRANCE_BOOST;\n const w = vibranceSkin(hsvHue(c, mx, mn));\n const a = v * (1 - w) + 0.64 * Math.pow(v, 1.6) * w;\n const boost =\n (c0 * a + c1 * a * a) * Math.pow(t, p) * Math.pow(1 - t, q) * Math.exp((r0 + ra * a + rt * (1 - t)) * Math.max(0, 1 - mx));\n t2 = Math.min(1, t * (1 + boost));\n v2 = mx + (v / 4) * shape;\n }\n const m2 = v2 * (1 - t2);\n const k = (v2 - m2) / (mx - mn);\n return [m2 + (c[0] - mn) * k, m2 + (c[1] - mn) * k, m2 + (c[2] - mn) * k];\n}\n\n/** Photoshop's Vibrance on one encoded sRGB pixel (`v`, `s`: the sliders / 100). */\nexport function vibrancePixel(c: readonly number[], v: number, s: number): Rgb {\n let l: Rgb = [srgbToLinear(Math.max(0, c[0])), srgbToLinear(Math.max(0, c[1])), srgbToLinear(Math.max(0, c[2]))];\n if (v !== 0) l = vibranceLinear(l, v);\n if (s !== 0) {\n const g = VIBRANCE_GREY[0] * l[0] + VIBRANCE_GREY[1] * l[1];\n l = l.map((x) => Math.min(1, Math.max(0, g + (1 + s) * (x - g)))) as Rgb;\n }\n return l.map((x) => linearToSrgb(clamp01(x))) as Rgb;\n}\n\n// ---------------------------------------------------------------------------\n// Color Balance\n// ---------------------------------------------------------------------------\n\n/** How far one Color Balance slider at ±100 moves the black or white point. */\nconst BALANCE_LEVELS = 0.39;\n\n/**\n * Color Balance on channel `ch` (0 red, 1 green, 2 blue) of `v` (0..1); `tones` are the\n * shadows, midtones and highlights slider triples (-100..100). Highlights, shadows, then midtones.\n */\nexport function colorBalanceChannel(\n tones: readonly [readonly number[], readonly number[], readonly number[]],\n preserveLuminosity: boolean,\n ch: number,\n v: number\n): number {\n const [shadows, midtones, highlights] = tones;\n const black = (x: number, b: number) => Math.max(0, (x - b) / (1 - b));\n const white = (x: number, w: number) => Math.min(1, x / w);\n let out = clamp01(v);\n for (const [tone, sliders] of [\n [2, highlights],\n [0, shadows],\n [1, midtones],\n ] as const) {\n const a = [0, 1, 2].map((i) => Math.min(1, Math.max(-1, sliders[i] / 100)));\n if (preserveLuminosity) {\n const own = a[ch] ?? 0;\n const lo = Math.min(a[0], a[1], a[2]);\n const hi = Math.max(a[0], a[1], a[2]);\n if (tone === 0) out = black(out, BALANCE_LEVELS * (hi - own));\n else if (tone === 1) out = Math.pow(out, Math.pow(2, (hi + lo) / 2 - own));\n else out = white(out, 1 - BALANCE_LEVELS * (own - lo));\n continue;\n }\n const s = a[ch];\n if (s === 0) continue;\n if (tone === 1) out = Math.pow(out, Math.pow(2, -s));\n else if (tone === 0 && s < 0) out = Math.pow(black(out, BALANCE_LEVELS * -s), Math.pow(2, -s / 2));\n else if (tone === 2 && s > 0) out = Math.pow(white(out, 1 - BALANCE_LEVELS * s), Math.pow(2, -s / 2));\n else out = Math.pow(out, Math.pow(2, -s / 2));\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Black & White, Photo Filter, Posterize, Threshold\n// ---------------------------------------------------------------------------\n\n/**\n * Black & White grey: the smallest channel, plus the secondary part (middle − smallest) and the\n * primary part (largest − middle) weighted by the sliders (percent: reds, yellows, greens, cyans,\n * blues, magentas).\n */\nexport function blackWhiteGray(c: readonly number[], weights: readonly number[]): number {\n const [r, g, b] = c;\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n const mid = r + g + b - max - min;\n const primary = r >= g && r >= b ? 0 : g >= b ? 2 : 4;\n const secondary = b <= r && b <= g ? 1 : r <= g ? 3 : 5;\n return clamp01(min + ((mid - min) * weights[secondary]) / 100 + ((max - mid) * weights[primary]) / 100);\n}\n\n/** D50 white (ICC PCS). */\nexport const D50 = [0.96422, 1.0, 0.82521] as const;\n/** Linear sRGB → D50 XYZ (Bradford-adapted, as in the ICC sRGB profile). */\nconst SRGB_TO_XYZ_D50 = [\n [0.436074, 0.385064, 0.14308],\n [0.222504, 0.716878, 0.060618],\n [0.013932, 0.097104, 0.714173],\n] as const;\n/** D50 XYZ → linear sRGB. */\nexport const XYZ_D50_TO_SRGB = [\n [3.133856, -1.616867, -0.490615],\n [-0.978768, 1.916142, 0.033454],\n [0.071945, -0.228991, 1.405243],\n] as const;\n\n/**\n * Photo Filter's linear-light RGB matrix (row-major 3×3): the pixel's D50 XYZ, relative to white,\n * multiplied by the filter colour's mixed in by `density` (0..1). `normalizeY` keeps luminance Y.\n */\nexport function photoFilterMatrix(color: readonly number[], density: number, normalizeY = false): number[][] {\n const d = Number.isFinite(density) ? clamp01(density) : 0;\n const lin = [0, 1, 2].map((i) => srgbToLinear(clamp01(color[i])));\n const xyz = SRGB_TO_XYZ_D50.map((r) => r[0] * lin[0] + r[1] * lin[1] + r[2] * lin[2]);\n let s = [0, 1, 2].map((i) => 1 - d + (d * xyz[i]) / D50[i]);\n if (normalizeY && s[1] > 1e-6) {\n const y = s[1];\n s = s.map((v) => v / y);\n }\n return [0, 1, 2].map((r) =>\n [0, 1, 2].map((c) => XYZ_D50_TO_SRGB[r][0] * s[0] * SRGB_TO_XYZ_D50[0][c] + XYZ_D50_TO_SRGB[r][1] * s[1] * SRGB_TO_XYZ_D50[1][c] + XYZ_D50_TO_SRGB[r][2] * s[2] * SRGB_TO_XYZ_D50[2][c])\n );\n}\n\n/** Photoshop posterize: `levels` equal input bins over 0..255, outputs `floor(k·255 / (levels − 1))`. */\nexport function posterizeValue(v: number, levels: number): number {\n const n = Math.min(255, Math.max(2, Math.round(levels)));\n const x = Math.round(clamp01(v) * 255);\n const bin = Math.min(n - 1, Math.floor((x * n) / 256));\n return Math.floor((bin * 255) / (n - 1)) / 255;\n}\n\n/** Rec.601 luma (Photoshop's grey for Threshold). */\nexport const rgbToGray = (c: readonly number[]) => 0.299 * c[0] + 0.587 * c[1] + 0.114 * c[2];\n\n/** `#rrggbb` → 0..1 RGB (black for anything else). */\nexport function hexToRgb(hex: string): Rgb {\n if (!HEX.test(hex)) return [0, 0, 0];\n const n = parseInt(hex.slice(1), 16);\n return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];\n}\n\n// ---------------------------------------------------------------------------\n// Compilation and evaluation\n// ---------------------------------------------------------------------------\n\n/** Mutates an encoded sRGB colour (0..1) in place. */\nexport type PixelOp = (c: Rgb) => void;\n\nfunction lutOp(luts: [Float32Array, Float32Array, Float32Array]): PixelOp {\n const [lr, lg, lb] = luts;\n return (c) => {\n c[0] = sampleLut(lr, c[0]);\n c[1] = sampleLut(lg, c[1]);\n c[2] = sampleLut(lb, c[2]);\n };\n}\n\nconst isIdentityCurve = (pts: readonly CurvePoint[]) =>\n pts.length === 2 && pts[0].input === 0 && pts[0].output === 0 && pts[1].input === 255 && pts[1].output === 255;\n\nconst isIdentityLevels = (c: LevelsChannel) =>\n c.inBlack === 0 && c.inWhite === 255 && c.gamma === 1 && c.outBlack === 0 && c.outWhite === 255;\n\n/** Quantum Levels works in (8-bit documents: whole levels). */\nconst LEVELS_QUANTUM = 255;\n\n/**\n * Per-channel LUTs of a Curves or Levels layer: each channel's record first, then the master.\n * `null` for any other kind.\n */\nexport function toneLuts(layer: AdjustmentLayer): [Float32Array, Float32Array, Float32Array] | null {\n const n = ADJUSTMENT_LUT_SIZE;\n if (layer.kind === 'curves') {\n const m = curveLutFromLevels(layer.params.master);\n return [layer.params.red, layer.params.green, layer.params.blue].map((pts) => {\n const ch = curveLutFromLevels(pts);\n for (let k = 0; k < n; k++) ch[k] = sampleLut(m, ch[k]);\n return ch;\n }) as [Float32Array, Float32Array, Float32Array];\n }\n if (layer.kind === 'levels') {\n const m = toUnitLevels(layer.params.master);\n return [layer.params.red, layer.params.green, layer.params.blue].map((rec) => {\n const c = toUnitLevels(rec);\n const out = new Float32Array(n);\n for (let k = 0; k < n; k++) out[k] = levelsValue(m, levelsValue(c, k / (n - 1), LEVELS_QUANTUM), LEVELS_QUANTUM);\n return out;\n }) as [Float32Array, Float32Array, Float32Array];\n }\n return null;\n}\n\n/**\n * Compiles a layer into a pixel function, or `null` when it is the identity (neutral parameters).\n * Pure: depends on `kind` and `params` only.\n */\nexport function compileAdjustmentLayer(layer: AdjustmentLayer): PixelOp | null {\n switch (layer.kind) {\n case 'curves': {\n const p = layer.params;\n if ([p.master, p.red, p.green, p.blue].every(isIdentityCurve)) return null;\n return lutOp(toneLuts(layer)!);\n }\n case 'levels': {\n const p = layer.params;\n if ([p.master, p.red, p.green, p.blue].every(isIdentityLevels)) return null;\n return lutOp(toneLuts(layer)!);\n }\n case 'hueSaturation': {\n const p = layer.params;\n const rangesActive = !p.colorize && p.ranges.some((r) => !isNeutralRange(r));\n if (!p.colorize && !rangesActive && p.hue === 0 && p.saturation === 0 && p.lightness === 0) return null;\n const tables = rangesActive ? hueRangeTables(p.ranges) : null;\n const sat = p.saturation / 100;\n const light = Math.min(1, Math.max(-1, p.lightness / 100));\n return (c) => {\n let dh = 0, ds = 0, dl = 0;\n if (tables) {\n const h0 = rgbToHsl(c)[0];\n dh = sampleLut(tables[0], h0);\n ds = sampleLut(tables[1], h0);\n dl = sampleLut(tables[2], h0);\n }\n const out = rangeLightness(hueSaturation(c, p.hue + dh, Math.min(1, Math.max(-1, sat + ds)), light, p.colorize), dl);\n c[0] = out[0];\n c[1] = out[1];\n c[2] = out[2];\n };\n }\n case 'vibrance': {\n const v = layer.params.vibrance / 100;\n const s = layer.params.saturation / 100;\n if (v === 0 && s === 0) return null;\n return (c) => {\n const out = vibrancePixel(c, v, s);\n c[0] = out[0];\n c[1] = out[1];\n c[2] = out[2];\n };\n }\n case 'colorBalance': {\n const p = layer.params;\n const tones = [p.shadows, p.midtones, p.highlights] as const;\n if (tones.every((t) => t[0] === 0 && t[1] === 0 && t[2] === 0)) return null;\n const n = ADJUSTMENT_LUT_SIZE;\n const luts = [0, 1, 2].map((ch) => {\n const out = new Float32Array(n);\n for (let k = 0; k < n; k++) out[k] = colorBalanceChannel(tones, p.preserveLuminosity, ch, k / (n - 1));\n return out;\n }) as [Float32Array, Float32Array, Float32Array];\n return lutOp(luts);\n }\n case 'blackWhite': {\n const p = layer.params;\n const weights = [p.reds, p.yellows, p.greens, p.cyans, p.blues, p.magentas];\n const tint = p.tint ? hexToRgb(p.tint) : null;\n return (c) => {\n const g = blackWhiteGray(c, weights);\n if (tint) {\n const t = setLum(tint, g);\n c[0] = clamp01(t[0]);\n c[1] = clamp01(t[1]);\n c[2] = clamp01(t[2]);\n } else {\n c[0] = c[1] = c[2] = g;\n }\n };\n }\n case 'photoFilter': {\n const p = layer.params;\n if (p.density <= 0) return null;\n const m = photoFilterMatrix(hexToRgb(p.color), p.density / 100, false);\n const preserve = p.preserveLuminosity;\n return (c) => {\n const l = [srgbToLinear(Math.max(0, c[0])), srgbToLinear(Math.max(0, c[1])), srgbToLinear(Math.max(0, c[2]))];\n let f: Rgb = [0, 1, 2].map((r) => linearToSrgb(Math.max(0, m[r][0] * l[0] + m[r][1] * l[1] + m[r][2] * l[2]))) as Rgb;\n if (preserve) {\n const target = 0.3 * c[0] + 0.59 * c[1] + 0.11 * c[2];\n f = setLum(f, target);\n }\n c[0] = clamp01(f[0]);\n c[1] = clamp01(f[1]);\n c[2] = clamp01(f[2]);\n };\n }\n case 'invert':\n return (c) => {\n c[0] = 1 - c[0];\n c[1] = 1 - c[1];\n c[2] = 1 - c[2];\n };\n case 'posterize': {\n const levels = layer.params.levels;\n return (c) => {\n c[0] = posterizeValue(c[0], levels);\n c[1] = posterizeValue(c[1], levels);\n c[2] = posterizeValue(c[2], levels);\n };\n }\n case 'threshold': {\n const t = Math.round(layer.params.level);\n return (c) => {\n const v = Math.round(rgbToGray(c) * 255) >= t ? 1 : 0;\n c[0] = c[1] = c[2] = v;\n };\n }\n }\n return null;\n}\n\n// Compiled layers keyed by kind + params, so a stack is compiled once per parameter change.\nconst COMPILE_CACHE_LIMIT = 64;\nconst compileCache = new Map<string, PixelOp | null>();\n\nfunction compiledFor(layer: AdjustmentLayer): PixelOp | null {\n const key = `${layer.kind}:${JSON.stringify(layer.params)}`;\n if (compileCache.has(key)) {\n const op = compileCache.get(key)!;\n // Refresh recency.\n compileCache.delete(key);\n compileCache.set(key, op);\n return op;\n }\n const op = compileAdjustmentLayer(layer);\n compileCache.set(key, op);\n if (compileCache.size > COMPILE_CACHE_LIMIT) compileCache.delete(compileCache.keys().next().value as string);\n return op;\n}\n\n/** Layers that change pixels: enabled, visible, not the identity. */\nexport function activeAdjustmentLayers(layers: readonly AdjustmentLayer[] | undefined): AdjustmentLayer[] {\n if (!layers?.length) return [];\n return layers.filter((l) => l.enabled !== false && (l.opacity ?? 1) > 0 && compiledFor(l) !== null);\n}\n\n/**\n * Evaluates one colour (encoded sRGB, 0..1) through a layer stack: each layer adjusts the result\n * of the layers below and is blended over it with its blend mode and opacity (an opaque\n * backdrop, so `(1 − α)·Cb + α·B(Cb, Cs)`).\n */\nexport function evaluateAdjustmentLayers(color: readonly number[], layers: readonly AdjustmentLayer[]): Rgb {\n const c: Rgb = [color[0], color[1], color[2]];\n for (const layer of layers) applyLayer(c, layer, compiledFor(layer));\n return c;\n}\n\nfunction applyLayer(c: Rgb, layer: AdjustmentLayer, op: PixelOp | null, coverage = 1): void {\n if (!op || layer.enabled === false) return;\n const alpha = Math.min(1, Math.max(0, layer.opacity ?? 1)) * coverage;\n if (alpha <= 0) return;\n const blend = layer.blend ?? 'normal';\n if (blend === 'normal' && alpha >= 1) {\n op(c);\n return;\n }\n const cb0 = c[0], cb1 = c[1], cb2 = c[2];\n op(c);\n if (blend === 'pigment') {\n // Mixes the adjusted colour into the original like paint, `alpha` of the way.\n mixPigmentRgb([clamp01(cb0), clamp01(cb1), clamp01(cb2)], [clamp01(c[0]), clamp01(c[1]), clamp01(c[2])], alpha, c);\n return;\n }\n const b = blend === 'normal' ? c : blendRgb(blend, [cb0, cb1, cb2], c);\n c[0] = (1 - alpha) * cb0 + alpha * b[0];\n c[1] = (1 - alpha) * cb1 + alpha * b[1];\n c[2] = (1 - alpha) * cb2 + alpha * b[2];\n}\n\n/**\n * Applies an adjustment-layer stack to 8-bit straight RGBA pixels in place. Each pixel is\n * decoded once, run through every layer in float, and encoded once. Transparent pixels are\n * left alone; alpha is never changed.\n *\n * `masks` (layer id → 0..1 coverage per pixel of `imageData`, see `buildLayerMaskPlanes`)\n * scales each masked layer's opacity per pixel: coverage 0 leaves the pixel as the layers below\n * made it, 1 applies the layer fully.\n */\nexport function applyAdjustmentLayers(\n imageData: { data: Uint8ClampedArray },\n layers: readonly AdjustmentLayer[] | undefined,\n masks?: ReadonlyMap<string, ArrayLike<number>>\n): void {\n const active = activeAdjustmentLayers(layers);\n if (!active.length) return;\n const ops = active.map((l) => compiledFor(l));\n const planes = active.map((l) => masks?.get(l.id));\n const data = imageData.data;\n const len = data.length;\n const c: Rgb = [0, 0, 0];\n for (let i = 0; i < len; i += 4) {\n if (data[i + 3] === 0) continue;\n c[0] = data[i] / 255;\n c[1] = data[i + 1] / 255;\n c[2] = data[i + 2] / 255;\n for (let k = 0; k < active.length; k++) {\n const plane = planes[k];\n if (plane) {\n const cov = plane[i >> 2];\n if (cov > 0) applyLayer(c, active[k], ops[k], cov >= 1 ? 1 : cov);\n } else {\n applyLayer(c, active[k], ops[k]);\n }\n }\n data[i] = Math.round(clamp01(c[0]) * 255);\n data[i + 1] = Math.round(clamp01(c[1]) * 255);\n data[i + 2] = Math.round(clamp01(c[2]) * 255);\n }\n}\n\n/** Stable cache key of a layer stack's pixel effect (for renderer caches). */\nexport function adjustmentLayersKey(layers: readonly AdjustmentLayer[] | undefined): string {\n if (!layers?.length) return '';\n return JSON.stringify(\n layers.map((l) => {\n const key: unknown[] = [l.kind, l.enabled !== false, l.opacity ?? 1, l.blend ?? 'normal', l.params];\n if (l.mask) key.push(layerMaskKey(l.mask));\n return key;\n })\n );\n}\n","import type { DepthMaskOptions, ImageAdjustments } from './types';\nimport {\n LUMA_709,\n LATEST_PROCESS_VERSION,\n TONE_LUT_SIZE,\n buildToneLut,\n linearToSrgb,\n normalizeProcessVersion,\n srgbToLinear,\n whiteBalanceMatrix,\n} from './color';\nimport { applyAdjustmentLayers } from './adjustment-layers';\n\nfunction clamp(value: number, min = 0, max = 255): number {\n return Math.max(min, Math.min(max, value));\n}\n\n/**\n * Applies color and tone adjustments (brightness, contrast, saturation, exposure, temperature, tint,\n * opacity, then any adjustment `layers`, then blur) directly to the pixel data of an ImageData buffer.\n *\n * `adjustments.process` selects the rendering: `2` is the linear-light pipeline\n * ({@link applyProcess2Adjustments}); anything else (including a missing value, as in edits saved\n * before process versions existed) is the frozen legacy rendering ({@link applyLegacyAdjustments}).\n */\nexport function applyAdjustments(\n imageData: ImageData,\n adjustments: ImageAdjustments,\n options: { layerMasks?: ReadonlyMap<string, ArrayLike<number>> } = {}\n): void {\n if (normalizeProcessVersion(adjustments.process) === LATEST_PROCESS_VERSION) {\n applyProcess2Adjustments(imageData, adjustments);\n } else {\n applyLegacyAdjustments(imageData, adjustments);\n }\n\n // Adjustment layers, bottom to top, on the encoded result (no-op without layers).\n // Layer masks (see `buildLayerMaskPlanes`) are aligned with `imageData`.\n applyAdjustmentLayers(imageData, adjustments.layers, options.layerMasks);\n\n // Apply blur if requested\n if (adjustments.blur > 0) {\n applyFastBoxBlur(imageData, Math.min(Math.round(adjustments.blur), 40));\n }\n}\n\n/**\n * Process 1: the original 8-bit gamma-space rendering, clamping after every step. Frozen so\n * that edits saved without a `process` look exactly as they always have. `tint` is ignored.\n */\nexport function applyLegacyAdjustments(imageData: { data: Uint8ClampedArray }, adjustments: ImageAdjustments): void {\n const { brightness, contrast, saturation, exposure, temperature, opacity } = adjustments;\n const data = imageData.data;\n const len = data.length;\n\n const hasBrightness = brightness !== 0;\n const hasContrast = contrast !== 0;\n const hasSaturation = saturation !== 0;\n const hasExposure = exposure !== 0;\n const hasTemperature = temperature !== 0;\n const hasOpacity = opacity < 1;\n\n // Pre-calculate contrast factor\n const contrastFactor = hasContrast\n ? (259 * (contrast + 255)) / (255 * (259 - contrast))\n : 1;\n\n // Pre-calculate exposure multiplier\n const exposureMultiplier = hasExposure ? Math.pow(2, exposure / 50) : 1;\n\n // Pre-calculate saturation multiplier\n const satMultiplier = hasSaturation ? 1 + saturation / 100 : 1;\n\n // Pre-calculate temperature offsets\n const tempOffset = hasTemperature ? temperature * 0.8 : 0;\n\n if (hasBrightness || hasContrast || hasSaturation || hasExposure || hasTemperature || hasOpacity) {\n for (let i = 0; i < len; i += 4) {\n let r = data[i];\n let g = data[i + 1];\n let b = data[i + 2];\n let a = data[i + 3];\n\n // Exposure\n if (hasExposure) {\n r = clamp(r * exposureMultiplier);\n g = clamp(g * exposureMultiplier);\n b = clamp(b * exposureMultiplier);\n }\n\n // Brightness\n if (hasBrightness) {\n const bOffset = brightness * 2.55;\n r = clamp(r + bOffset);\n g = clamp(g + bOffset);\n b = clamp(b + bOffset);\n }\n\n // Contrast\n if (hasContrast) {\n r = clamp(contrastFactor * (r - 128) + 128);\n g = clamp(contrastFactor * (g - 128) + 128);\n b = clamp(contrastFactor * (b - 128) + 128);\n }\n\n // Saturation\n if (hasSaturation) {\n const luminance = 0.2989 * r + 0.587 * g + 0.114 * b;\n r = clamp(luminance + (r - luminance) * satMultiplier);\n g = clamp(luminance + (g - luminance) * satMultiplier);\n b = clamp(luminance + (b - luminance) * satMultiplier);\n }\n\n // Temperature / Warmth\n if (hasTemperature) {\n r = clamp(r + tempOffset);\n b = clamp(b - tempOffset);\n }\n\n // Opacity\n if (hasOpacity) {\n a = clamp(a * opacity);\n }\n\n data[i] = r;\n data[i + 1] = g;\n data[i + 2] = b;\n data[i + 3] = a;\n }\n }\n}\n\n/** sRGB byte → linear light (exact; shared by every process 2 render). */\nconst DECODE_LUT: Float32Array = (() => {\n const lut = new Float32Array(256);\n for (let i = 0; i < 256; i++) lut[i] = srgbToLinear(i / 255);\n return lut;\n})();\n\n/** Intervals of the linear → sRGB byte encode table over [0, 1]. */\nconst ENCODE_LUT_SIZE = 65535;\nlet encodeLut: Uint8Array | null = null;\nfunction getEncodeLut(): Uint8Array {\n if (!encodeLut) {\n encodeLut = new Uint8Array(ENCODE_LUT_SIZE + 1);\n for (let i = 0; i <= ENCODE_LUT_SIZE; i++) {\n encodeLut[i] = Math.round(linearToSrgb(i / ENCODE_LUT_SIZE) * 255);\n }\n }\n return encodeLut;\n}\n\ninterface Process2Plan {\n key: string;\n /** Exposure gain folded into the white-balance matrix (row-major). */\n m: Float64Array;\n /** Per-channel tone LUTs over [0, headroom[c]]. */\n luts: [Float32Array, Float32Array, Float32Array];\n /** LUT index scale per channel: TONE_LUT_SIZE / headroom[c]. */\n scale: [number, number, number];\n saturation: number;\n}\n\nlet cachedPlan: Process2Plan | null = null;\n\n/**\n * Precomputes everything per parameter change: the 3×3 colour matrix (exposure gain × white\n * balance) and one tone LUT per channel whose domain is that channel's largest possible value,\n * so the highlight shoulder absorbs it instead of clipping.\n */\nfunction getProcess2Plan(adjustments: ImageAdjustments): Process2Plan {\n const exposure = adjustments.exposure || 0;\n const temperature = adjustments.temperature || 0;\n const tint = adjustments.tint || 0;\n const contrast = adjustments.contrast || 0;\n const brightness = adjustments.brightness || 0;\n const saturation = 1 + (adjustments.saturation || 0) / 100;\n const key = `${exposure},${temperature},${tint},${contrast},${brightness},${saturation}`;\n if (cachedPlan && cachedPlan.key === key) return cachedPlan;\n\n const gain = Math.pow(2, exposure / 50);\n const wb = whiteBalanceMatrix(temperature, tint);\n const m = new Float64Array(9);\n for (let i = 0; i < 9; i++) m[i] = wb[i] * gain;\n const luts = [] as unknown as [Float32Array, Float32Array, Float32Array];\n const scale = [0, 0, 0] as [number, number, number];\n for (let c = 0; c < 3; c++) {\n // Largest value this channel can reach from inputs in [0, 1].\n let reach = 0;\n for (let k = 0; k < 3; k++) reach += Math.max(0, m[c * 3 + k]);\n const headroom = Math.max(1, reach);\n luts[c] = buildToneLut(contrast, brightness, headroom);\n scale[c] = TONE_LUT_SIZE / headroom;\n }\n cachedPlan = { key, m, luts, scale, saturation };\n return cachedPlan;\n}\n\n/**\n * Process 2: decode sRGB to linear float once; apply exposure (gain) and white balance (a\n * Bradford matrix relative to as-shot) in linear light; a per-channel tone LUT for the\n * highlight shoulder, contrast and brightness; luminance-preserving saturation in linear light;\n * then encode back to sRGB through a LUT. Nothing is clamped between stages, only at the final\n * encode. Neutral settings are the identity.\n */\nexport function applyProcess2Adjustments(imageData: { data: Uint8ClampedArray }, adjustments: ImageAdjustments): void {\n const data = imageData.data;\n const len = data.length;\n const opacity = adjustments.opacity ?? 1;\n const hasOpacity = opacity < 1;\n const hasColor =\n !!adjustments.brightness ||\n !!adjustments.contrast ||\n !!adjustments.saturation ||\n !!adjustments.exposure ||\n !!adjustments.temperature ||\n !!adjustments.tint;\n\n if (!hasColor) {\n if (hasOpacity) {\n for (let i = 3; i < len; i += 4) data[i] = clamp(data[i] * opacity);\n }\n return;\n }\n\n const plan = getProcess2Plan(adjustments);\n const dec = DECODE_LUT;\n const enc = getEncodeLut();\n const { m, luts, scale, saturation } = plan;\n const m0 = m[0], m1 = m[1], m2 = m[2], m3 = m[3], m4 = m[4], m5 = m[5], m6 = m[6], m7 = m[7], m8 = m[8];\n const lutR = luts[0], lutG = luts[1], lutB = luts[2];\n const sR = scale[0], sG = scale[1], sB = scale[2];\n const N = TONE_LUT_SIZE;\n const hasSaturation = saturation !== 1;\n const wr = LUMA_709[0], wg = LUMA_709[1], wb = LUMA_709[2];\n\n for (let i = 0; i < len; i += 4) {\n const r0 = dec[data[i]];\n const g0 = dec[data[i + 1]];\n const b0 = dec[data[i + 2]];\n\n // Exposure × white balance (linear light, unclamped).\n let r = m0 * r0 + m1 * g0 + m2 * b0;\n let g = m3 * r0 + m4 * g0 + m5 * b0;\n let b = m6 * r0 + m7 * g0 + m8 * b0;\n\n // Tone LUTs (linear interpolation).\n let f: number;\n let k: number;\n if (r <= 0) r = lutR[0];\n else if ((f = r * sR) >= N) r = lutR[N];\n else { k = f | 0; r = lutR[k] + (lutR[k + 1] - lutR[k]) * (f - k); }\n if (g <= 0) g = lutG[0];\n else if ((f = g * sG) >= N) g = lutG[N];\n else { k = f | 0; g = lutG[k] + (lutG[k + 1] - lutG[k]) * (f - k); }\n if (b <= 0) b = lutB[0];\n else if ((f = b * sB) >= N) b = lutB[N];\n else { k = f | 0; b = lutB[k] + (lutB[k + 1] - lutB[k]) * (f - k); }\n\n // Luminance-preserving saturation in linear light.\n if (hasSaturation) {\n const y = wr * r + wg * g + wb * b;\n r = y + (r - y) * saturation;\n g = y + (g - y) * saturation;\n b = y + (b - y) * saturation;\n }\n\n // Encode (the only clamp).\n data[i] = enc[r <= 0 ? 0 : r >= 1 ? ENCODE_LUT_SIZE : (r * ENCODE_LUT_SIZE + 0.5) | 0];\n data[i + 1] = enc[g <= 0 ? 0 : g >= 1 ? ENCODE_LUT_SIZE : (g * ENCODE_LUT_SIZE + 0.5) | 0];\n data[i + 2] = enc[b <= 0 ? 0 : b >= 1 ? ENCODE_LUT_SIZE : (b * ENCODE_LUT_SIZE + 0.5) | 0];\n if (hasOpacity) data[i + 3] = clamp(data[i + 3] * opacity);\n }\n}\n\n/**\n * Fast separable box blur running horizontally and vertically (`adjustments.blur` and the `blur`\n * layer filter): `radius` pixels each side, edges clamped, rounded after each pass.\n */\nexport function applyBoxBlur(imageData: { width: number; height: number; data: Uint8ClampedArray }, radius: number): void {\n applyFastBoxBlur(imageData, radius);\n}\n\nfunction applyFastBoxBlur(imageData: { width: number; height: number; data: Uint8ClampedArray }, radius: number): void {\n if (radius < 1) return;\n const w = imageData.width;\n const h = imageData.height;\n const src = imageData.data;\n const temp = new Uint8ClampedArray(src.length);\n\n // Horizontal blur pass\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n let r = 0, g = 0, b = 0, a = 0;\n let count = 0;\n for (let k = -radius; k <= radius; k++) {\n const px = Math.min(w - 1, Math.max(0, x + k));\n const idx = (y * w + px) * 4;\n r += src[idx];\n g += src[idx + 1];\n b += src[idx + 2];\n a += src[idx + 3];\n count++;\n }\n const dstIdx = (y * w + x) * 4;\n temp[dstIdx] = Math.round(r / count);\n temp[dstIdx + 1] = Math.round(g / count);\n temp[dstIdx + 2] = Math.round(b / count);\n temp[dstIdx + 3] = Math.round(a / count);\n }\n }\n\n // Vertical blur pass back into src\n for (let x = 0; x < w; x++) {\n for (let y = 0; y < h; y++) {\n let r = 0, g = 0, b = 0, a = 0;\n let count = 0;\n for (let k = -radius; k <= radius; k++) {\n const py = Math.min(h - 1, Math.max(0, y + k));\n const idx = (py * w + x) * 4;\n r += temp[idx];\n g += temp[idx + 1];\n b += temp[idx + 2];\n a += temp[idx + 3];\n count++;\n }\n const dstIdx = (y * w + x) * 4;\n src[dstIdx] = Math.round(r / count);\n src[dstIdx + 1] = Math.round(g / count);\n src[dstIdx + 2] = Math.round(b / count);\n src[dstIdx + 3] = Math.round(a / count);\n }\n }\n}\n\n/**\n * Blends a depth/alpha mask into the main image data based on Gleamforge depth masking.\n * Supports threshold range with softness gradation, brightness mode, and optional inversion.\n */\nexport function applyDepthMask(\n mainImageData: ImageData,\n maskImageData: ImageData,\n options: DepthMaskOptions\n): void {\n const { mode, depthRange, softness, invert } = options;\n const mainData = mainImageData.data;\n const maskData = maskImageData.data;\n const len = Math.min(mainData.length, maskData.length);\n const effectiveSoftness = Math.max(1, softness || 10);\n const minDepth = depthRange[0];\n const maxDepth = depthRange[1];\n\n for (let i = 0; i < len; i += 4) {\n let maskValue = maskData[i]; // Use red channel of mask (or grayscale)\n if (invert) {\n maskValue = 255 - maskValue;\n }\n\n let alphaValue: number;\n\n if (mode === 'threshold') {\n if (maskValue >= maxDepth + effectiveSoftness) {\n alphaValue = 0;\n } else if (maskValue > maxDepth) {\n alphaValue = Math.round(((maxDepth + effectiveSoftness - maskValue) / effectiveSoftness) * 255);\n } else if (maskValue >= minDepth) {\n alphaValue = 255;\n } else if (maskValue < minDepth - effectiveSoftness) {\n alphaValue = 0;\n } else {\n alphaValue = Math.round(((maskValue - (minDepth - effectiveSoftness)) / effectiveSoftness) * 255);\n }\n } else {\n // Brightness mode\n alphaValue = clamp(maskValue + minDepth);\n }\n\n // Multiply existing alpha with mask alpha\n mainData[i + 3] = clamp((mainData[i + 3] * alphaValue) / 255);\n }\n}\n\n/**\n * Removes alpha channel by setting all alpha bytes to 255 (completely opaque).\n * From Gleamforge image-filters.ts.\n */\nexport function removeAlpha(imageData: ImageData): void {\n const data = imageData.data;\n const len = data.length;\n for (let i = 3; i < len; i += 4) {\n data[i] = 255;\n }\n}\n","/**\n * The image editor's layer stack: pixel, adjustment, group and annotation layers in one ordered\n * tree (bottom to top), all in document space (natural pixels of the document).\n *\n * Layer objects are immutable. Every change builds new objects along the path from the root to\n * the changed layer and shares everything else, so undo history, the compositor's caches and\n * serialization can compare layers by identity.\n */\n\nimport type {\n AdjustmentKind,\n AdjustmentParamsMap,\n Annotation,\n CropBox,\n DepthMaskOptions,\n} from '../types';\nimport type { PixelSurface } from '../../raster/surface';\nimport type { BlendMode } from '../../raster/blend';\nimport type { Interpolation } from '../../raster/transform';\nimport type { GlobalLight } from '../../raster/effects/model';\nimport type { EffectPattern } from '../../raster/effects/patterns';\nimport type { Quad, WarpMesh } from '../../geom/types';\nimport {\n findLayer,\n flattenLayers,\n insertLayer,\n isLayerLocked,\n mapLayer,\n type GroupBlendMode,\n type LayerLocation as RasterLayerLocation,\n type LayerLocks,\n type RasterLayerBase,\n type RasterMask as BaseRasterMask,\n type RasterPixelLayer,\n} from '../../raster/layers';\n\n/** Blend modes of a layer; groups also accept `pass-through`. */\nexport type LayerBlendMode = GroupBlendMode;\n\nexport type { LayerLocks };\n\n/**\n * A layer mask (see the raster module's `RasterMask`) whose parametric `source` is a depth map:\n * a depth mask is generated from it instead of painted, and its surface is unused.\n */\nexport type RasterMask = BaseRasterMask<DepthMaskOptions>;\n\nexport interface LayerBase extends RasterLayerBase {\n mask?: RasterMask;\n}\n\n/** The base sliders of the old document-level adjustments, as a filter on one layer. */\nexport interface DevelopSettings {\n brightness: number;\n contrast: number;\n saturation: number;\n exposure: number;\n temperature: number;\n tint?: number;\n /** Rendering process (see `ImageAdjustments.process`). */\n process?: 1 | 2;\n}\n\nexport type DevelopFilter = { id: string; kind: 'develop'; enabled: boolean } & DevelopSettings;\n/** Box blur of `radius` pixels (the old `adjustments.blur`), clamped at the canvas edges. */\nexport interface BlurFilter {\n id: string;\n kind: 'blur';\n enabled: boolean;\n radius: number;\n}\nexport type LayerFilter = DevelopFilter | BlurFilter;\nexport type LayerFilterKind = LayerFilter['kind'];\n\nexport interface PixelLayer extends LayerBase, Omit<RasterPixelLayer, keyof RasterLayerBase> {\n /** Live filters, applied bottom (first) to top before the mask and blend. */\n filters?: LayerFilter[];\n /**\n * `'document'`: the pixels are the document's source image, unmodified (the Background of an\n * opened image). Such a layer's `surface` is a placeholder; read its pixels through the\n * controller. Painting it drops the flag.\n */\n source?: 'document';\n}\n\nexport type AdjustmentLayerNode<K extends AdjustmentKind = AdjustmentKind> = {\n [P in K]: LayerBase & { type: 'adjustment'; kind: P; params: AdjustmentParamsMap[P] };\n}[K];\n\nexport interface GroupLayer extends Omit<LayerBase, 'blend'> {\n type: 'group';\n blend: LayerBlendMode;\n /** Bottom to top. */\n children: ImageLayer[];\n /** UI only. */\n collapsed?: boolean;\n}\n\n/** One annotation (pen, shape, arrow, text, image) as a layer, drawn by the annotation renderer. */\nexport interface AnnotationLayer extends LayerBase {\n type: 'annotation';\n annotation: Annotation;\n}\n\n// ---------------------------------------------------------------------------\n// Smart objects\n// ---------------------------------------------------------------------------\n\n/** A nested layer document: the contents of a smart object made from layers (or an embedded PSD). */\nexport interface SmartDocument {\n /** Bottom to top, on a canvas of the contents' `width × height`. */\n layers: ImageLayer[];\n globalLight?: GlobalLight;\n patterns?: EffectPattern[];\n}\n\n/**\n * What a smart object places: raster pixels or a nested layer document, at their own pixel size.\n * Immutable and shared: every instance placed from the same contents (duplicates, linked\n * instances) holds the same `id`, and replacing or editing the contents updates them all.\n */\nexport interface SmartContents {\n /** Shared by linked instances; independent copies get a fresh one. */\n id: string;\n /** File name shown in the UI and written to PSD (`Layer 1.psb`, `photo.png`). */\n name: string;\n /** Pixel size of the contents (the placement maps `0..width × 0..height` into the document). */\n width: number;\n height: number;\n /** `raster`: `raster` holds the pixels at (0, 0); `document`: `document` holds the layers. */\n kind: 'raster' | 'document';\n raster?: PixelSurface;\n document?: SmartDocument;\n /**\n * The file the contents came from (a PSD's embedded file), written back by Export Contents and\n * PSD export while the contents are unedited. Without `raster` / `document` the file could not\n * be decoded here (a JPEG outside a browser): the layer keeps its rendered pixels, and changes\n * that re-render it are refused until `prepareSmartObjects` decodes it.\n */\n file?: { bytes: Uint8Array; type: string };\n /** Linked to an external file (Photoshop `liFE`): only the embedded preview is held. */\n linked?: { path: string };\n}\n\n/**\n * Where a smart object's contents land, non-destructively: a warp in the contents' own pixels\n * (optional) and then the map of the contents' rectangle onto `corners`. Every change re-renders\n * from the contents, so repeated transforms never degrade.\n */\nexport interface SmartPlacement {\n /** Where the contents' corners land (top-left, top-right, bottom-right, bottom-left), document pixels. */\n corners: Quad;\n /** A Bézier warp over the contents' rectangle, control points in contents pixels (missing = none). */\n warp?: WarpMesh | null;\n /** Resampling (default bicubic). */\n interpolation?: Interpolation;\n}\n\n/**\n * A Photoshop smart filter we do not model (Gaussian Blur, Camera Raw, …), kept from a PSD so it\n * is listed, can be hidden, reordered or deleted, and is written back unchanged. It draws nothing.\n */\nexport interface PsdSmartFilter {\n id: string;\n kind: 'psd';\n enabled: boolean;\n /** Photoshop's menu name (`Gaussian Blur...`). */\n name: string;\n /** The filter's `filterFX` item descriptor (versioned descriptor bytes), as read. */\n data: Uint8Array;\n}\n\n/** One smart filter: a layer filter (or a kept PSD filter) with its own blending over the result below it. */\nexport type SmartFilter = (LayerFilter | PsdSmartFilter) & {\n /** How the filter's result blends with the result below it (Photoshop's Blending Options). */\n blend: BlendMode;\n /** 0..1 */\n opacity: number;\n};\nexport type SmartFilterKind = SmartFilter['kind'];\n\n/** The smart filter stack of a smart object. */\nexport interface SmartFilterStack {\n /** Layer › Smart Filter › Disable Smart Filters (each filter keeps its own `enabled`). */\n enabled: boolean;\n /** Bottom (applied first) to top. */\n filters: SmartFilter[];\n /**\n * The one filter mask (document space): where the filtered result shows over the unfiltered\n * contents. Missing reveals everywhere.\n */\n mask?: RasterMask;\n}\n\n/** PSD data of a smart object kept for round trips (see the PSD module). */\nexport interface SmartPsdData {\n /** The placed-layer block as read (`SoLd`, `SoLE` or `PlLd`) and its key. */\n key: string;\n data: Uint8Array;\n /** The instance id (`placed`), which keys the filter-effects cache. */\n placed?: string;\n}\n\n/**\n * A smart object: `contents` placed through `placement`, then the smart filters. `surface` is the\n * rendered result in document space (offset 0), rebuilt by every change to the contents,\n * placement or filters; the compositor draws it like a pixel layer's pixels, and masks, effects,\n * clipping and blend apply on top. Never paint into it: rasterize the layer first.\n */\nexport interface SmartObjectLayer extends LayerBase {\n type: 'smart';\n contents: SmartContents;\n placement: SmartPlacement;\n smartFilters?: SmartFilterStack;\n /** The rendered pixels (a cache of contents → placement → filters), document space. */\n surface: PixelSurface;\n /** Always (0, 0): a smart object moves through its placement. */\n offset: { x: number; y: number };\n /** 0..1, PSD \"Fill\" (missing = 1). */\n fillOpacity?: number;\n psd?: SmartPsdData;\n}\n\nexport type ImageLayer = PixelLayer | AdjustmentLayerNode | GroupLayer | AnnotationLayer | SmartObjectLayer;\nexport type ImageLayerType = ImageLayer['type'];\n\n/** Which part of the active layer painting targets (`filter-mask`: a smart object's filter mask). */\nexport type LayerTarget = 'pixels' | 'mask' | 'filter-mask';\n\n// ---------------------------------------------------------------------------\n// Ids\n// ---------------------------------------------------------------------------\n\nlet idCounter = 0;\n\n/** A fresh id with `prefix` (`layer`, `flt`, …). */\nexport function newLayerId(prefix = 'layer'): string {\n return `${prefix}_${Date.now().toString(36)}_${(++idCounter).toString(36)}`;\n}\n\n// ---------------------------------------------------------------------------\n// Tree helpers (pure)\n// ---------------------------------------------------------------------------\n\nexport type LayerLocation = RasterLayerLocation<ImageLayer>;\n\nexport { flattenLayers, findLayer, mapLayer, insertLayer, isLayerLocked };\n\n/** The top-level and nested pixel layers. */\nexport function pixelLayers(layers: readonly ImageLayer[]): PixelLayer[] {\n return flattenLayers(layers).filter((l): l is PixelLayer => l.type === 'pixel');\n}\n\n/** The develop filter of a pixel layer, if any. */\nexport function developFilter(layer: PixelLayer): DevelopFilter | undefined {\n return layer.filters?.find((f): f is DevelopFilter => f.kind === 'develop');\n}\n\nexport function blurFilter(layer: PixelLayer): BlurFilter | undefined {\n return layer.filters?.find((f): f is BlurFilter => f.kind === 'blur');\n}\n\n/** True for filters that change pixels (enabled, non-neutral). */\nexport function isActiveFilter(f: LayerFilter): boolean {\n if (!f.enabled) return false;\n if (f.kind === 'blur') return Math.min(Math.round(f.radius), 40) >= 1;\n return Boolean(f.brightness || f.contrast || f.saturation || f.exposure || f.temperature || f.tint);\n}\n\n/** True when a filter needs neighbouring pixels (it can't run tile by tile). */\nexport function isSpatialFilter(f: LayerFilter): boolean {\n return f.kind === 'blur';\n}\n\n/** Document-space bounds of a pixel layer's stored tiles, or `null`. */\nexport function pixelLayerBounds(layer: PixelLayer, surface: PixelSurface = layer.surface): CropBox | null {\n const b = surface.bounds;\n if (!b) return null;\n return { x: b.x + layer.offset.x, y: b.y + layer.offset.y, width: b.width, height: b.height };\n}\n\n/** Human label of a layer kind. */\nexport function layerTypeLabel(type: ImageLayerType): string {\n return type === 'pixel' ? 'Pixel layer' : type === 'adjustment' ? 'Adjustment layer' : type === 'group' ? 'Group' : type === 'smart' ? 'Smart object' : 'Annotation';\n}\n\n/** Layers whose pixels are a surface the compositor draws (pixel layers and smart objects). */\nexport function hasSurface(layer: ImageLayer): layer is PixelLayer | SmartObjectLayer {\n return layer.type === 'pixel' || layer.type === 'smart';\n}\n\n/** Every smart object in the stack (nested contents not included). */\nexport function smartObjectLayers(layers: readonly ImageLayer[]): SmartObjectLayer[] {\n return flattenLayers(layers).filter((l): l is SmartObjectLayer => l.type === 'smart');\n}\n","/**\n * The image editor's compositor: the raster engine (`@pixerate/editor/raster`) with the photo\n * layer kinds plugged in. Adjustment layers filter what is below them (a run of adjacent ones is\n * one float pass with one rounding at the end, exactly as `applyAdjustmentLayers` does, so a\n * document migrated from adjustment layers renders identically); annotation layers are drawn by\n * the host; pixel layers can carry develop and blur filters; masks can be depth masks.\n */\n\nimport { applyAdjustmentLayers } from '../adjustment-layers';\nimport { applyBoxBlur, applyLegacyAdjustments, applyProcess2Adjustments } from '../filters';\nimport { LATEST_PROCESS_VERSION, normalizeProcessVersion } from '../color';\nimport type { BlendMode } from '../blend';\nimport type { AdjustmentLayer, Annotation, CropBox, DepthMaskOptions, ImageAdjustments } from '../types';\nimport {\n RasterCompositor,\n type CompositeCallOptions,\n type CompositeStats,\n type CompositorOptions,\n type CompositorProfile,\n type PixelFilterSpec,\n type RasterSources,\n} from '../../raster/compositor';\nimport type { PixelSurface } from '../../raster/surface';\nimport { isActiveFilter, isSpatialFilter, type AdjustmentLayerNode, type ImageLayer, type LayerFilter, type PixelLayer, type SmartObjectLayer } from './model';\n\nexport { blendTile, changedTiles } from '../../raster/compositor';\nexport type { CompositeCallOptions, CompositeStats, CompositorOptions };\n\n/** What the compositor needs from its host besides the layers. */\nexport interface CompositorSources extends RasterSources {\n /** The pixels of a pixel layer or smart object (resolves layers whose pixels are the document source). */\n surfaceOf?(layer: PixelLayer | SmartObjectLayer): PixelSurface;\n /**\n * A parametric mask's depth map as RGBA at canvas size (the red channel is the depth), or\n * `null` while it is unavailable (the mask then reveals everything).\n */\n depthMap?(source: DepthMaskOptions): { width: number; height: number; data: ArrayLike<number> } | null;\n /** Draws one annotation into RGBA for `rect` (canvas hosts only); `null` when unavailable. */\n rasterizeAnnotation?(annotation: Annotation, rect: CropBox): Uint8ClampedArray | null;\n}\n\nconst legacyViewCache = new WeakMap<AdjustmentLayerNode, AdjustmentLayer>();\n\n/** The `AdjustmentLayer` shape `applyAdjustmentLayers` evaluates, for a node (memoised). */\nexport function adjustmentNodeAsLayer(node: AdjustmentLayerNode): AdjustmentLayer {\n let view = legacyViewCache.get(node);\n if (!view) {\n view = {\n id: node.id,\n kind: node.kind,\n enabled: node.visible,\n opacity: node.opacity,\n blend: node.blend,\n params: node.params,\n } as AdjustmentLayer;\n legacyViewCache.set(node, view);\n }\n return view;\n}\n\n/** Develop settings as `ImageAdjustments` for the process functions (opacity is document-level). */\nfunction developAdjustments(f: Extract<LayerFilter, { kind: 'develop' }>): ImageAdjustments {\n return {\n brightness: f.brightness,\n contrast: f.contrast,\n saturation: f.saturation,\n exposure: f.exposure,\n temperature: f.temperature,\n tint: f.tint ?? 0,\n blur: 0,\n opacity: 1,\n process: f.process,\n };\n}\n\n/** Runs per-pixel filters over RGBA `data` in place. */\nfunction applyPixelFilters(data: Uint8ClampedArray, filters: readonly LayerFilter[]): void {\n for (const f of filters) {\n if (!isActiveFilter(f) || f.kind !== 'develop') continue;\n const adj = developAdjustments(f);\n if (normalizeProcessVersion(adj.process) === LATEST_PROCESS_VERSION) applyProcess2Adjustments({ data }, adj);\n else applyLegacyAdjustments({ data }, adj);\n }\n}\n\n/** Evaluates a depth mask (see `applyDepthMask`) into one coverage byte per depth sample. */\nexport function depthMaskAlpha(depth: number, options: DepthMaskOptions): number {\n const { mode, depthRange, softness, invert } = options;\n let v = invert ? 255 - depth : depth;\n const soft = Math.max(1, softness || 10);\n const [lo, hi] = depthRange;\n if (mode === 'threshold') {\n if (v >= hi + soft) return 0;\n if (v > hi) return Math.round(((hi + soft - v) / soft) * 255);\n if (v >= lo) return 255;\n if (v < lo - soft) return 0;\n return Math.round(((v - (lo - soft)) / soft) * 255);\n }\n v += lo;\n return v < 0 ? 0 : v > 255 ? 255 : v;\n}\n\n/** Runs layer filters (develop, blur) over RGBA `data` (`width × height`) in place, bottom first. */\nexport function runLayerFilters(data: Uint8ClampedArray, width: number, height: number, filters: readonly LayerFilter[]): void {\n for (const f of filters) {\n if (!isActiveFilter(f)) continue;\n if (f.kind === 'develop') applyPixelFilters(data, [f]);\n else if (f.kind === 'blur') applyBoxBlur({ width, height, data }, Math.min(Math.round(f.radius), 40));\n }\n}\n\nconst filterSpecCache = new WeakMap<object, PixelFilterSpec | null>();\n\n/** A pixel layer's active filters for the engine (memoised per filter list). */\nfunction pixelFilterSpec(layer: PixelLayer): PixelFilterSpec | null {\n const list = layer.filters;\n if (!list?.length) return null;\n if (filterSpecCache.has(list)) return filterSpecCache.get(list)!;\n const filters = list.filter(isActiveFilter);\n let spec: PixelFilterSpec | null = null;\n if (filters.length) {\n const spatial = filters.some(isSpatialFilter);\n spec = {\n key: JSON.stringify(filters),\n spatial,\n apply: (data, width, height) => runLayerFilters(data, width, height, filters),\n };\n }\n filterSpecCache.set(list, spec);\n return spec;\n}\n\nconst PHOTO_PROFILE: CompositorProfile<ImageLayer, CompositorSources> = {\n isFilterLayer: (l) => l.type === 'adjustment',\n applyFilterRun: (layers, acc, _r, masks) => {\n const views = (layers as AdjustmentLayerNode[]).map(adjustmentNodeAsLayer);\n applyAdjustmentLayers({ data: acc }, views, masks);\n },\n contentTile: (l, r, sources) => (l.type === 'annotation' ? (sources.rasterizeAnnotation?.(l.annotation, r) ?? null) : null),\n /** Annotation layers draw with their annotation's blend mode unless the layer sets one. */\n contentBlend: (l) => (l.type === 'annotation' && l.blend === 'normal' && l.annotation.blend ? l.annotation.blend : (l.blend as BlendMode)),\n layerKey: (l) =>\n l.type === 'pixel' ? [l.filters ?? null, l.source ?? null] : l.type === 'adjustment' ? [l.kind, l.params] : l.type === 'annotation' ? l.annotation : undefined,\n pixelFilters: (l) => (l.type === 'pixel' ? pixelFilterSpec(l) : null),\n /** A smart object draws its rendered pixels (contents → placement → smart filters) like a pixel layer. */\n isPixelLike: (l) => l.type === 'smart',\n maskSourcePlane: (source, W, H, sources) => {\n const opts = source as DepthMaskOptions;\n const depth = sources.depthMap?.(opts) ?? null;\n if (!depth || depth.width !== W || depth.height !== H) return null;\n const v = new Float32Array(W * H);\n for (let i = 0; i < v.length; i++) v[i] = depthMaskAlpha(depth.data[i * 4], opts) / 255;\n return v;\n },\n};\n\n/** The per-tile compositor for image editor layer stacks (see the raster module for the caches). */\nexport class LayerCompositor extends RasterCompositor<ImageLayer, CompositorSources> {\n constructor(options: CompositorOptions = {}) {\n super(PHOTO_PROFILE, options);\n }\n}\n","/**\n * Free Transform geometry for the image editor, as pure functions both the UI bindings and the\n * agent tools use: a transform is the frame being transformed (`box`, document pixels), where its\n * four corners land (`corners`, TL, TR, BR, BL) and an optional warp mesh over the box. Scale,\n * rotate, skew and flips keep the corners a parallelogram (an affine map); Distort and Perspective\n * move them freely (a projective map); Warp bends the box through a Bézier mesh before the corner\n * map.\n *\n * The maths (homographies, meshes, presets) is shared with the vector editor in `src/geom`.\n */\n\nimport {\n applyMatrix,\n invertMatrix,\n matrixAbout,\n multiplyMatrices,\n rotateMatrix,\n translateMatrix,\n type AffineMatrix,\n} from '../geom/affine';\nimport {\n affineFromHomography,\n applyHomography,\n boxQuad,\n homographyFromAffine,\n homographyKeepsQuad,\n rectToQuad,\n type Homography,\n} from '../geom/homography';\nimport { evalWarpMesh, identityWarpMesh, isValidWarpMesh, WARP_STYLES, warpPresetMesh, type WarpPreset } from '../geom/warp';\nimport type { Quad, Vec2, WarpMesh, WarpStyle } from '../geom/types';\nimport type { CropBox } from './types';\n\nexport type { Quad, Vec2, WarpMesh, WarpStyle, WarpPreset, AffineMatrix, Homography };\nexport { WARP_STYLES };\n\n/** Free Transform modes (Edit › Transform). Scale, rotate and skew stay affine. */\nexport type FreeTransformMode = 'scale' | 'rotate' | 'skew' | 'distort' | 'perspective' | 'warp';\n\nexport const FREE_TRANSFORM_MODES: readonly FreeTransformMode[] = ['scale', 'rotate', 'skew', 'distort', 'perspective', 'warp'];\n\n/** The nine reference-point positions of the options bar's locator, over the transformed box. */\nexport type TransformAnchor = 'top-left' | 'top' | 'top-right' | 'left' | 'center' | 'right' | 'bottom-left' | 'bottom' | 'bottom-right';\n\nexport const TRANSFORM_ANCHORS: readonly TransformAnchor[] = ['top-left', 'top', 'top-right', 'left', 'center', 'right', 'bottom-left', 'bottom', 'bottom-right'];\n\nconst ANCHOR_FRACTION: Record<TransformAnchor, [number, number]> = {\n 'top-left': [0, 0],\n top: [0.5, 0],\n 'top-right': [1, 0],\n left: [0, 0.5],\n center: [0.5, 0.5],\n right: [1, 0.5],\n 'bottom-left': [0, 1],\n bottom: [0.5, 1],\n 'bottom-right': [1, 1],\n};\n\nexport function isTransformAnchor(value: unknown): value is TransformAnchor {\n return typeof value === 'string' && value in ANCHOR_FRACTION;\n}\n\n/** A transform in progress: the frame, where its corners go and an optional warp over the frame. */\nexport interface FreeTransformGeometry {\n /** The frame being transformed, in document pixels (the content bounds when it began). */\n box: CropBox;\n /** Where the frame's corners land: top-left, top-right, bottom-right, bottom-left (document pixels). */\n corners: Quad;\n /**\n * A warp applied in the frame's own space before the corner map: its control points are in\n * document pixels of the untransformed frame (`identityWarpMesh(box)` is no warp).\n */\n warp?: WarpMesh | null;\n}\n\n/** The untransformed geometry of a box. */\nexport function identityTransform(box: CropBox): FreeTransformGeometry {\n return { box: { ...box }, corners: boxQuad(box) };\n}\n\n/** Where `box`'s corners go under the affine `m`. */\nexport function cornersFromMatrix(box: CropBox, m: AffineMatrix): Quad {\n const q = boxQuad(box);\n return [applyMatrix(m, q[0]), applyMatrix(m, q[1]), applyMatrix(m, q[2]), applyMatrix(m, q[3])];\n}\n\n/** The affine matrix taking `box` onto `corners` when they form a parallelogram, else `null`. */\nexport function matrixFromCorners(box: CropBox, corners: Quad, eps = 1e-6): AffineMatrix | null {\n const [tl, tr, br, bl] = corners;\n const size = 1 + Math.max(box.width, box.height);\n if (Math.abs(tl.x + br.x - tr.x - bl.x) > eps * size || Math.abs(tl.y + br.y - tr.y - bl.y) > eps * size) return null;\n const h = rectToQuad(box, corners);\n return h ? affineFromHomography(h, 1e-9) : null;\n}\n\n/** The projective map of the corner move (box → corners), or `null` when the corners are degenerate. */\nexport function transformHomography(geometry: FreeTransformGeometry): Homography | null {\n const h = rectToQuad(geometry.box, geometry.corners);\n if (!h) return null;\n // A quad folded through the horizon would wrap content round infinity.\n return homographyKeepsQuad(h, boxQuad(geometry.box)) ? h : null;\n}\n\n/** Whether a warp mesh changes anything (not the identity over the box). */\nexport function hasWarp(geometry: FreeTransformGeometry): boolean {\n const w = geometry.warp;\n if (!w) return false;\n const id = identityWarpMesh(geometry.box, w.us.length - 1, w.vs.length - 1);\n if (id.points.length !== w.points.length || id.us.some((u, i) => Math.abs(u - w.us[i]) > 1e-12) || id.vs.some((v, i) => Math.abs(v - w.vs[i]) > 1e-12)) return true;\n return w.points.some((p, i) => Math.abs(p.x - id.points[i].x) > 1e-6 || Math.abs(p.y - id.points[i].y) > 1e-6);\n}\n\n/** Whether the geometry changes nothing. */\nexport function isIdentityTransform(geometry: FreeTransformGeometry): boolean {\n if (hasWarp(geometry)) return false;\n const q = boxQuad(geometry.box);\n return geometry.corners.every((c, i) => Math.abs(c.x - q[i].x) < 1e-6 && Math.abs(c.y - q[i].y) < 1e-6);\n}\n\n/**\n * The forward map of the whole transform (document pixels of the source → document pixels of the\n * result): the warp over the box, then the corner map. `null` where it is undefined.\n */\nexport function transformMap(geometry: FreeTransformGeometry): ((p: Vec2) => Vec2 | null) | null {\n const h = transformHomography(geometry);\n if (!h) return null;\n const { box } = geometry;\n const warp = hasWarp(geometry) ? geometry.warp! : null;\n if (!warp) return (p) => applyHomography(h, p);\n return (p) => applyHomography(h, evalWarpMesh(warp, (p.x - box.x) / box.width, (p.y - box.y) / box.height));\n}\n\n/** A point of the transformed box at box fractions (`fx`, `fy`), warp included. */\nexport function transformedPoint(geometry: FreeTransformGeometry, fx: number, fy: number): Vec2 | null {\n const f = transformMap(geometry);\n const { box } = geometry;\n return f ? f({ x: box.x + fx * box.width, y: box.y + fy * box.height }) : null;\n}\n\n/** Where an anchor of the box is after the corner map (the warp ignored). */\nexport function anchorPoint(geometry: FreeTransformGeometry, anchor: TransformAnchor): Vec2 {\n const [fx, fy] = ANCHOR_FRACTION[anchor];\n const h = rectToQuad(geometry.box, geometry.corners);\n const p = { x: geometry.box.x + fx * geometry.box.width, y: geometry.box.y + fy * geometry.box.height };\n return (h && applyHomography(h, p)) ?? p;\n}\n\n/** Bounds of the transformed result's outline (document pixels, not rounded). */\nexport function transformedBox(geometry: FreeTransformGeometry): CropBox | null {\n const f = transformMap(geometry);\n if (!f) return null;\n const { box } = geometry;\n const pts: Vec2[] = [];\n const n = hasWarp(geometry) ? 16 : 1;\n for (let i = 0; i <= n; i++) {\n const t = i / n;\n for (const [fx, fy] of [[t, 0], [1, t], [1 - t, 1], [0, 1 - t]]) {\n const p = f({ x: box.x + fx * box.width, y: box.y + fy * box.height });\n if (!p) return null;\n pts.push(p);\n }\n }\n const xs = pts.map((p) => p.x);\n const ys = pts.map((p) => p.y);\n const x = Math.min(...xs);\n const y = Math.min(...ys);\n return { x, y, width: Math.max(...xs) - x, height: Math.max(...ys) - y };\n}\n\n// ---------------------------------------------------------------------------\n// Numeric transforms (the options bar, and agents)\n// ---------------------------------------------------------------------------\n\n/** Free Transform as numbers, as Photoshop's options bar shows it. */\nexport interface TransformParams {\n /** Width in percent of the frame (default 100; negative flips). */\n scaleX?: number;\n /** Height in percent of the frame (default 100; negative flips). */\n scaleY?: number;\n /** Rotation in degrees, clockwise (default 0). */\n rotate?: number;\n /** Horizontal skew in degrees: vertical edges lean right for positive angles (default 0). */\n skewX?: number;\n /** Vertical skew in degrees: horizontal edges lean down for positive angles (default 0). */\n skewY?: number;\n /** Move after scaling and rotating, in document pixels (default 0). */\n dx?: number;\n dy?: number;\n /** The fixed point of scale, skew and rotation: an anchor of the frame or a document point (default the centre). */\n reference?: TransformAnchor | Vec2;\n}\n\n/** The reference point of params over a box (default its centre). */\nexport function referencePoint(box: CropBox, reference: TransformParams['reference']): Vec2 {\n if (reference && typeof reference === 'object') return { x: reference.x, y: reference.y };\n const [fx, fy] = ANCHOR_FRACTION[reference ?? 'center'];\n return { x: box.x + fx * box.width, y: box.y + fy * box.height };\n}\n\n/**\n * The affine matrix of `params` over `box`: about the reference point, skew, then scale, then\n * rotate; then the move.\n */\nexport function matrixFromParams(box: CropBox, params: TransformParams): AffineMatrix {\n const sx = (params.scaleX ?? 100) / 100;\n const sy = (params.scaleY ?? 100) / 100;\n const tx = Math.tan(((params.skewX ?? 0) * Math.PI) / 180);\n const ty = Math.tan(((params.skewY ?? 0) * Math.PI) / 180);\n const ref = referencePoint(box, params.reference);\n const linear = multiplyMatrices(multiplyMatrices([1, ty, tx, 1, 0, 0], [sx, 0, 0, sy, 0, 0]), rotateMatrix(params.rotate ?? 0));\n return multiplyMatrices(matrixAbout(linear, ref), translateMatrix(params.dx ?? 0, params.dy ?? 0));\n}\n\n/** What the options bar shows for a transform. */\nexport interface TransformReadout {\n /** False for Distort, Perspective and Warp: only `bounds` and `corners` then mean anything. */\n affine: boolean;\n /** Width and height in percent of the frame. */\n scaleX: number;\n scaleY: number;\n /** Rotation in degrees, clockwise, in (−180, 180]. */\n rotate: number;\n /** Horizontal skew in degrees (vertical skew is folded into rotation and scale). */\n skewX: number;\n /** Where the reference point is now (document pixels). */\n x: number;\n y: number;\n /** Bounds of the transformed outline. */\n bounds: CropBox | null;\n corners: Quad;\n}\n\n/**\n * Decomposes a transform for the options bar: the linear part as rotate · scale · skew (QR) with\n * the given reference point. Non-affine geometry reports its bounds and corners only.\n */\nexport function describeTransform(geometry: FreeTransformGeometry, reference: TransformParams['reference'] = 'center'): TransformReadout {\n const m = hasWarp(geometry) ? null : matrixFromCorners(geometry.box, geometry.corners);\n const ref = referencePoint(geometry.box, reference);\n const bounds = transformedBox(geometry);\n if (!m) {\n const h = rectToQuad(geometry.box, geometry.corners);\n const at = (h && applyHomography(h, ref)) ?? ref;\n return { affine: false, scaleX: 100, scaleY: 100, rotate: 0, skewX: 0, x: at.x, y: at.y, bounds, corners: geometry.corners };\n }\n const [a, b, c, d] = m;\n const sxAbs = Math.hypot(a, b);\n const rot = (Math.atan2(b, a) * 180) / Math.PI;\n const shear = sxAbs ? (a * c + b * d) / sxAbs : 0;\n const sy = sxAbs ? (a * d - b * c) / sxAbs : 0;\n // Skew then scale: [[sx, sx·tan(skew)], [0, sy]], so tan(skew) = shear / sx.\n const skew = sxAbs ? (Math.atan(shear / sxAbs) * 180) / Math.PI : 0;\n const at = applyMatrix(m, ref);\n const clean = (v: number) => (Math.abs(v) < 1e-9 ? 0 : v);\n return {\n affine: true,\n scaleX: clean(sxAbs * 100),\n scaleY: clean(sy * 100),\n rotate: clean(rot === -180 ? 180 : rot),\n skewX: clean(skew),\n x: at.x,\n y: at.y,\n bounds,\n corners: geometry.corners,\n };\n}\n\n/**\n * Corners for a transform given as numbers relative to the current corners: `params` are applied\n * after the current affine part (when the corners are a parallelogram) about the reference point\n * of the current geometry, so typing \"rotate 15\" rotates the box as it is now.\n */\nexport function applyParams(geometry: FreeTransformGeometry, params: TransformParams): Quad {\n const ref = params.reference && typeof params.reference === 'object' ? params.reference : anchorPoint(geometry, (params.reference as TransformAnchor | undefined) ?? 'center');\n const m = matrixFromParams({ x: ref.x, y: ref.y, width: 0, height: 0 }, { ...params, reference: ref });\n const c = geometry.corners;\n return [applyMatrix(m, c[0]), applyMatrix(m, c[1]), applyMatrix(m, c[2]), applyMatrix(m, c[3])];\n}\n\n/** Flip of the corners about the box's centre line (horizontal: left ↔ right). */\nexport function flipCorners(geometry: FreeTransformGeometry, axis: 'horizontal' | 'vertical'): Quad {\n const c = anchorPoint(geometry, 'center');\n const m: AffineMatrix = axis === 'horizontal' ? [-1, 0, 0, 1, 2 * c.x, 0] : [1, 0, 0, -1, 0, 2 * c.y];\n const q = geometry.corners;\n return [applyMatrix(m, q[0]), applyMatrix(m, q[1]), applyMatrix(m, q[2]), applyMatrix(m, q[3])];\n}\n\n/** Inverse of `matrixFromCorners` (box space → document) for mapping a document point back into the box. */\nexport function boxPointOf(geometry: FreeTransformGeometry, p: Vec2): Vec2 | null {\n const m = matrixFromCorners(geometry.box, geometry.corners);\n if (m) return applyMatrix(invertMatrix(m), p);\n return null;\n}\n\n/** A warp mesh from a preset over the box (null when it changes nothing). */\nexport function presetWarp(box: CropBox, preset: WarpPreset): WarpMesh | null {\n return warpPresetMesh(preset, box);\n}\n\n/** Validates a warp mesh for a box: well formed, finite, the right number of points. */\nexport function isValidWarp(mesh: unknown): mesh is WarpMesh {\n return isValidWarpMesh(mesh);\n}\n\n/** The geometry's affine map when it is one (no warp, a parallelogram), as a homography; else the projective map. */\nexport function geometryHomography(geometry: FreeTransformGeometry): Homography | null {\n const m = matrixFromCorners(geometry.box, geometry.corners);\n return m ? homographyFromAffine(m) : transformHomography(geometry);\n}\n","/**\n * Layer tree operations as pure functions: each validates its arguments against the stack and\n * throws a `LayerOperationError` that says what is wrong before changing anything, and returns a\n * new stack that shares every untouched layer. The controller wraps them with undo history.\n */\n\nimport type { BlendMode } from '../blend';\nimport { isBlendMode } from '../blend';\nimport type { CropBox } from '../types';\nimport { translateSurface } from '../../raster/surface';\nimport {\n defaultLayerEffect,\n isLayerEffectKind,\n LAYER_EFFECT_KINDS,\n LayerEffectError,\n mergeLayerEffect,\n newEffectId,\n validateLayerEffects,\n type LayerEffect,\n type LayerEffectKind,\n type LayerEffects,\n} from '../../raster/effects/model';\nimport {\n findLayer,\n flattenLayers,\n insertLayer,\n isLayerLocked,\n mapLayer,\n newLayerId,\n type GroupLayer,\n type ImageLayer,\n type LayerBlendMode,\n type LayerFilter,\n type LayerLocks,\n type PixelLayer,\n type RasterMask,\n} from './model';\n\n/** A refused layer operation; the message says why and how to fix it. */\nexport class LayerOperationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'LayerOperationError';\n }\n}\n\nconst fail = (message: string): never => {\n throw new LayerOperationError(message);\n};\n\n/** Where a layer goes: above or below a layer (in its group), or at a position in a group. */\nexport interface LayerPlacement {\n /** Directly above this layer, in the same group. */\n aboveId?: string;\n /** Directly below this layer, in the same group. */\n belowId?: string;\n /** Inside this group (at `index`, default the top). */\n parentId?: string;\n /** Index in the parent's children (or the top level), 0 = bottom. */\n index?: number;\n}\n\n/** The layer with `id`, or a `LayerOperationError` listing the ids that exist. */\nexport function requireLayer(layers: readonly ImageLayer[], id: string) {\n const loc = findLayer(layers, id);\n if (!loc) {\n const ids = flattenLayers(layers).map((l) => l.id);\n fail(`Unknown layer \"${id}\". ${ids.length ? `Layers: ${ids.join(', ')}.` : 'The document has no layers.'} Call list_layers for ids.`);\n }\n return loc!;\n}\n\n/** Refuses when the layer is locked for `what` (and always for `all`). */\nexport function requireUnlocked(layer: ImageLayer, what: keyof LayerLocks, action: string): void {\n if (isLayerLocked(layer, 'all')) fail(`Layer \"${layer.name || layer.id}\" is locked (all); unlock it before you ${action}.`);\n if (what !== 'all' && isLayerLocked(layer, what)) fail(`Layer \"${layer.name || layer.id}\" has its ${what} locked; unlock it before you ${action}.`);\n}\n\n/** Inserts `layer` at `placement` (default: the top level at `defaultIndex`, or the top). */\nexport function placeLayer(layers: readonly ImageLayer[], layer: ImageLayer, placement: LayerPlacement = {}, defaultIndex?: number): ImageLayer[] {\n const given = [placement.aboveId, placement.belowId, placement.parentId].filter((v) => v !== undefined).length;\n if (given > 1) fail('Give only one of aboveId, belowId and parentId.');\n if (placement.aboveId || placement.belowId) {\n const ref = requireLayer(layers, (placement.aboveId ?? placement.belowId)!);\n return insertLayer(layers, layer, ref.parent?.id ?? null, ref.index + (placement.aboveId ? 1 : 0));\n }\n if (placement.parentId) {\n const ref = requireLayer(layers, placement.parentId);\n if (ref.layer.type !== 'group') fail(`Layer \"${placement.parentId}\" is not a group; parentId must be a group id.`);\n return insertLayer(layers, layer, ref.layer.id, placement.index);\n }\n return insertLayer(layers, layer, null, placement.index ?? defaultIndex);\n}\n\n/** Removes a layer (refused when it is locked). */\nexport function removeLayer(layers: readonly ImageLayer[], id: string): ImageLayer[] {\n const loc = requireLayer(layers, id);\n requireUnlocked(loc.layer, 'all', 'delete it');\n return mapLayer(layers, id, () => null);\n}\n\n/** Moves a layer to `placement`. A group cannot go inside itself. Returns the new stack. */\nexport function moveLayer(layers: readonly ImageLayer[], id: string, placement: LayerPlacement): ImageLayer[] {\n const loc = requireLayer(layers, id);\n requireUnlocked(loc.layer, 'all', 'move it');\n const target = placement.aboveId ?? placement.belowId ?? placement.parentId;\n if (target === id) fail('A layer cannot be placed relative to itself.');\n if (target && loc.layer.type === 'group' && flattenLayers(loc.layer.children).some((l) => l.id === target)) {\n fail(`Group \"${id}\" cannot be moved into its own child \"${target}\".`);\n }\n if (target) requireLayer(layers, target);\n if (!target && placement.index === undefined) fail('Give aboveId, belowId, parentId or index.');\n const without = mapLayer(layers, id, () => null);\n return placeLayer(without, loc.layer, placement);\n}\n\n/** Every id in a subtree gets a fresh one (for duplicates). */\nexport function withNewIds<T extends ImageLayer>(layer: T): T {\n const next = { ...layer, id: newLayerId(layer.type === 'adjustment' ? 'adj' : layer.type) } as T;\n if (next.type === 'group') (next as GroupLayer).children = (next as GroupLayer).children.map(withNewIds);\n if (next.type === 'annotation') next.annotation = { ...next.annotation, id: next.id };\n if (next.type === 'smart' && next.psd?.placed) {\n // A copy is another instance of the same contents: Photoshop needs it to have its own instance id.\n next.psd = { ...next.psd };\n delete next.psd.placed;\n }\n return next;\n}\n\n/** Duplicates a layer (and its children) directly above it. Returns the stack and the copy's id. */\nexport function duplicateLayer(layers: readonly ImageLayer[], id: string, name?: string): { layers: ImageLayer[]; id: string } {\n const loc = requireLayer(layers, id);\n const copy = withNewIds(loc.layer);\n copy.name = name ?? `${loc.layer.name || 'Layer'} copy`;\n delete copy.locks;\n return { layers: insertLayer(layers, copy, loc.parent?.id ?? null, loc.index + 1), id: copy.id };\n}\n\n/** Properties `setLayerProperties` changes. */\nexport interface LayerProperties {\n name?: string;\n visible?: boolean;\n /** 0..1 */\n opacity?: number;\n /** 0..1, pixel layers only. */\n fillOpacity?: number;\n blend?: LayerBlendMode;\n clipped?: boolean;\n locks?: LayerLocks;\n /** Groups only (UI state). */\n collapsed?: boolean;\n}\n\n/** Validates and applies property changes to one layer. */\nexport function setLayerProperties(layers: readonly ImageLayer[], id: string, props: LayerProperties): ImageLayer[] {\n const loc = requireLayer(layers, id);\n const layer = loc.layer;\n const onlyUnlocking = Object.keys(props).every((k) => k === 'locks' || k === 'visible' || k === 'collapsed');\n if (!onlyUnlocking) requireUnlocked(layer, 'all', 'change its properties');\n const next = { ...layer } as ImageLayer;\n if (props.name !== undefined) {\n if (typeof props.name !== 'string' || !props.name.trim()) fail('name must be a non-empty string.');\n next.name = props.name.trim();\n }\n if (props.visible !== undefined) next.visible = Boolean(props.visible);\n if (props.opacity !== undefined) {\n if (!(typeof props.opacity === 'number' && props.opacity >= 0 && props.opacity <= 1)) fail('opacity must be a number from 0 to 1.');\n next.opacity = props.opacity;\n }\n if (props.fillOpacity !== undefined) {\n if (layer.type !== 'pixel' && layer.type !== 'smart') fail(`fillOpacity applies to pixel layers and smart objects; \"${id}\" is a ${layer.type} layer.`);\n if (!(typeof props.fillOpacity === 'number' && props.fillOpacity >= 0 && props.fillOpacity <= 1)) fail('fillOpacity must be a number from 0 to 1.');\n const p = next as { fillOpacity?: number };\n if (props.fillOpacity === 1) delete p.fillOpacity;\n else p.fillOpacity = props.fillOpacity;\n }\n if (props.blend !== undefined) {\n if (props.blend === 'pass-through') {\n if (layer.type !== 'group') fail('pass-through is only for groups.');\n } else if (!isBlendMode(props.blend)) fail(`Unknown blend mode \"${props.blend}\".`);\n (next as { blend: LayerBlendMode }).blend = props.blend;\n }\n if (props.clipped !== undefined) {\n if (props.clipped && loc.index === 0) fail('The bottom layer of a group or the stack has nothing below to clip to.');\n if (props.clipped) next.clipped = true;\n else delete next.clipped;\n }\n if (props.locks !== undefined) {\n const locks: LayerLocks = {};\n for (const k of ['pixels', 'transparency', 'position', 'all'] as const) if (props.locks[k]) locks[k] = true;\n if (Object.keys(locks).length) next.locks = locks;\n else delete next.locks;\n }\n if (props.collapsed !== undefined) {\n if (layer.type !== 'group') fail('collapsed applies to groups.');\n const g = next as GroupLayer;\n if (props.collapsed) g.collapsed = true;\n else delete g.collapsed;\n }\n return mapLayer(layers, id, () => next);\n}\n\n/**\n * Puts layers (siblings in one group or the top level) into a new group at the position of the\n * topmost one, keeping their order. Returns the stack and the group id.\n */\nexport function groupLayers(layers: readonly ImageLayer[], ids: string[], name = 'Group'): { layers: ImageLayer[]; id: string } {\n if (!ids.length) fail('Give at least one layer id to group.');\n const locs = ids.map((id) => requireLayer(layers, id));\n const parent = locs[0].parent?.id ?? null;\n if (locs.some((l) => (l.parent?.id ?? null) !== parent)) fail('Layers to group must share one parent; move them next to each other first.');\n for (const l of locs) requireUnlocked(l.layer, 'all', 'group it');\n const ordered = [...new Set(locs)].sort((a, b) => a.index - b.index);\n const top = ordered[ordered.length - 1].index;\n const below = ordered.filter((l) => l.index < top).length;\n const group: GroupLayer = {\n type: 'group',\n id: newLayerId('group'),\n name,\n visible: true,\n opacity: 1,\n blend: 'pass-through',\n children: ordered.map((l) => {\n const c = { ...l.layer } as ImageLayer;\n return c;\n }),\n };\n // The bottom child cannot be clipped to anything inside the new group.\n if (group.children[0]?.clipped) {\n const c = { ...group.children[0] };\n delete c.clipped;\n group.children[0] = c;\n }\n let out = layers as ImageLayer[];\n for (const l of ordered) out = mapLayer(out, l.layer.id, () => null);\n return { layers: insertLayer(out, group, parent, top - below), id: group.id };\n}\n\n/** Replaces a group by its children (the group's own opacity, blend and mask are dropped). */\nexport function ungroupLayer(layers: readonly ImageLayer[], id: string): { layers: ImageLayer[]; ids: string[] } {\n const loc = requireLayer(layers, id);\n if (loc.layer.type !== 'group') fail(`Layer \"${id}\" is not a group.`);\n requireUnlocked(loc.layer, 'all', 'ungroup it');\n const group = loc.layer as GroupLayer;\n let out = mapLayer(layers, id, () => null);\n group.children.forEach((c, i) => {\n out = insertLayer(out, c, loc.parent?.id ?? null, loc.index + i);\n });\n return { layers: out, ids: group.children.map((c) => c.id) };\n}\n\n/** Sets a pixel layer's offset (refused when its position is locked). An unlinked mask stays in place. */\nexport function offsetLayer(layers: readonly ImageLayer[], id: string, offset: { x: number; y: number }): ImageLayer[] {\n const loc = requireLayer(layers, id);\n if (loc.layer.type !== 'pixel') fail(`Only pixel layers move; \"${id}\" is a ${loc.layer.type} layer.`);\n requireUnlocked(loc.layer, 'position', 'move it');\n if (!Number.isFinite(offset.x) || !Number.isFinite(offset.y)) fail('x and y must be numbers.');\n const layer = loc.layer as PixelLayer;\n const next: PixelLayer = { ...layer, offset: { x: Math.round(offset.x), y: Math.round(offset.y) } };\n const mask = layer.mask;\n if (mask && mask.linked === false && !mask.source) {\n // An unlinked mask stays where it is in the document, so it moves the other way in layer space.\n next.mask = { ...mask, surface: translateSurface(mask.surface, layer.offset.x - next.offset.x, layer.offset.y - next.offset.y) };\n }\n return mapLayer(layers, id, () => next);\n}\n\n/** Sets or removes (`null`) a layer's mask. */\nexport function setLayerMask(layers: readonly ImageLayer[], id: string, mask: RasterMask | null): ImageLayer[] {\n const loc = requireLayer(layers, id);\n requireUnlocked(loc.layer, 'all', 'change its mask');\n return mapLayer(layers, id, (l) => {\n const next = { ...l } as ImageLayer;\n if (mask) next.mask = mask;\n else delete next.mask;\n return next;\n });\n}\n\n// ---------------------------------------------------------------------------\n// Filters\n// ---------------------------------------------------------------------------\n\nconst DEVELOP_FIELDS = ['brightness', 'contrast', 'saturation', 'exposure', 'temperature', 'tint'] as const;\n\n/** A clean filter from loose input (for tools and the UI). */\nexport function normalizeFilter(raw: Partial<LayerFilter> & { kind: LayerFilter['kind'] }, id = newLayerId('flt')): LayerFilter {\n const enabled = raw.enabled !== false;\n if (raw.kind === 'blur') {\n const r = (raw as { radius?: unknown }).radius;\n if (r !== undefined && !(typeof r === 'number' && r >= 0 && r <= 50)) fail('blur radius must be a number from 0 to 50 px.');\n return { id, kind: 'blur', enabled, radius: typeof r === 'number' ? r : 2 };\n }\n if (raw.kind !== 'develop') fail(`Unknown filter kind \"${(raw as { kind: string }).kind}\". Use develop or blur.`);\n const out = { id, kind: 'develop', enabled, brightness: 0, contrast: 0, saturation: 0, exposure: 0, temperature: 0, tint: 0, process: 2 } as LayerFilter & { kind: 'develop' };\n for (const k of DEVELOP_FIELDS) {\n const v = (raw as Record<string, unknown>)[k];\n if (v === undefined) continue;\n if (!(typeof v === 'number' && v >= -100 && v <= 100)) fail(`${k} must be a number from -100 to 100.`);\n out[k] = v as number;\n }\n const p = (raw as { process?: unknown }).process;\n if (p !== undefined) {\n if (p !== 1 && p !== 2) fail('process must be 1 or 2.');\n out.process = p as 1 | 2;\n }\n return out;\n}\n\nfunction requirePixelLayer(layers: readonly ImageLayer[], id: string): PixelLayer {\n const loc = requireLayer(layers, id);\n if (loc.layer.type !== 'pixel') fail(`Filters belong to pixel layers; \"${id}\" is a ${loc.layer.type} layer.`);\n requireUnlocked(loc.layer, 'all', 'change its filters');\n return loc.layer as PixelLayer;\n}\n\n/** Adds a filter on top of a pixel layer's filters. A layer has at most one filter of each kind. */\nexport function addLayerFilter(layers: readonly ImageLayer[], id: string, filter: LayerFilter): ImageLayer[] {\n const layer = requirePixelLayer(layers, id);\n if (layer.filters?.some((f) => f.kind === filter.kind)) fail(`Layer \"${id}\" already has a ${filter.kind} filter; change it with update_layer_filter.`);\n return mapLayer(layers, id, () => ({ ...layer, filters: [...(layer.filters ?? []), filter] }));\n}\n\n/** Changes one filter of a pixel layer (`changes` merged and validated). */\nexport function updateLayerFilter(layers: readonly ImageLayer[], id: string, filterId: string, changes: Record<string, unknown>): ImageLayer[] {\n const layer = requirePixelLayer(layers, id);\n const i = (layer.filters ?? []).findIndex((f) => f.id === filterId);\n if (i === -1) fail(`Layer \"${id}\" has no filter \"${filterId}\". Filters: ${(layer.filters ?? []).map((f) => f.id).join(', ') || 'none'}.`);\n const current = layer.filters![i];\n if ('kind' in changes && changes.kind !== current.kind) fail('A filter kind cannot change; remove it and add another.');\n const merged = normalizeFilter({ ...current, ...changes, kind: current.kind } as LayerFilter, current.id);\n const filters = [...layer.filters!];\n filters[i] = merged;\n return mapLayer(layers, id, () => ({ ...layer, filters }));\n}\n\nexport function removeLayerFilter(layers: readonly ImageLayer[], id: string, filterId: string): ImageLayer[] {\n const layer = requirePixelLayer(layers, id);\n if (!(layer.filters ?? []).some((f) => f.id === filterId)) fail(`Layer \"${id}\" has no filter \"${filterId}\".`);\n const filters = layer.filters!.filter((f) => f.id !== filterId);\n const next = { ...layer };\n if (filters.length) next.filters = filters;\n else delete next.filters;\n return mapLayer(layers, id, () => next);\n}\n\n// ---------------------------------------------------------------------------\n// Layer effects\n// ---------------------------------------------------------------------------\n\n/** A layer that can carry effects (pixel, annotation and group layers), checked for locks. */\nfunction requireEffectLayer(layers: readonly ImageLayer[], id: string, action: string): ImageLayer {\n const layer = requireLayer(layers, id).layer;\n if (layer.type === 'adjustment') fail(`Layer \"${layer.name || id}\" is an adjustment layer; effects go on pixel, annotation and group layers.`);\n requireUnlocked(layer, 'all', action);\n return layer;\n}\n\n/** A layer's effects, or an empty, enabled list when it has none. */\nexport function layerEffectsOf(layer: ImageLayer): LayerEffects {\n return layer.effects ?? { enabled: true, items: [] };\n}\n\n/** Wraps an effect validation error as a layer operation error (same message). */\nfunction guard<T>(fn: () => T): T {\n try {\n return fn();\n } catch (e) {\n if (e instanceof LayerEffectError) fail(e.message);\n throw e;\n }\n}\n\n/** Replaces a layer's effects (validated); `null` or an empty list without a PSD block removes them. */\nexport function setLayerEffects(layers: readonly ImageLayer[], id: string, effects: LayerEffects | null): ImageLayer[] {\n const layer = requireEffectLayer(layers, id, 'change its effects');\n const next = { ...layer };\n if (effects && (effects.items.length || effects.psd || effects.scale !== undefined || !effects.enabled)) {\n guard(() => validateLayerEffects(effects));\n next.effects = effects;\n } else delete next.effects;\n return mapLayer(layers, id, () => next as ImageLayer);\n}\n\n/**\n * Adds an effect of `kind` (Photoshop's defaults with `params` applied) to a layer's effects, at\n * `index` in the list (default: the end; the first listed of a kind is on top). Returns the stack\n * and the new effect.\n */\nexport function addLayerEffect(\n layers: readonly ImageLayer[],\n id: string,\n kind: LayerEffectKind,\n params: Record<string, unknown> = {},\n index?: number\n): { layers: ImageLayer[]; effect: LayerEffect } {\n if (!isLayerEffectKind(kind)) fail(`Unknown effect kind \"${String(kind)}\". Kinds: ${LAYER_EFFECT_KINDS.join(', ')}.`);\n const layer = requireEffectLayer(layers, id, 'add effects');\n const current = layerEffectsOf(layer);\n const ids = new Set(current.items.map((e) => e.id));\n let effectId = newEffectId();\n while (ids.has(effectId)) effectId = newEffectId();\n const { id: _ignored, kind: _kind, ...rest } = params;\n const effect = guard(() => mergeLayerEffect(defaultLayerEffect(kind, effectId), rest));\n const items = [...current.items];\n const at = index === undefined ? items.length : Math.max(0, Math.min(items.length, Math.floor(index)));\n items.splice(at, 0, effect);\n return { layers: setLayerEffects(layers, id, { ...current, items }), effect };\n}\n\n/** The index of effect `effectId` on a layer, or a `LayerOperationError` listing the effect ids. */\nfunction requireEffect(layer: ImageLayer, effectId: string): number {\n const items = layer.effects?.items ?? [];\n const i = items.findIndex((e) => e.id === effectId);\n if (i === -1) fail(`Layer \"${layer.name || layer.id}\" has no effect \"${effectId}\". Effects: ${items.map((e) => `${e.id} (${e.kind})`).join(', ') || 'none'}; call list_layer_effects.`);\n return i;\n}\n\n/** Changes one effect (only the given fields; nested objects replace whole, `null` removes an optional one). */\nexport function updateLayerEffect(layers: readonly ImageLayer[], id: string, effectId: string, changes: Record<string, unknown>): ImageLayer[] {\n const layer = requireEffectLayer(layers, id, 'change its effects');\n const i = requireEffect(layer, effectId);\n const current = layer.effects!;\n const items = [...current.items];\n items[i] = guard(() => mergeLayerEffect(items[i], changes));\n return setLayerEffects(layers, id, { ...current, items });\n}\n\nexport function removeLayerEffect(layers: readonly ImageLayer[], id: string, effectId: string): ImageLayer[] {\n const layer = requireEffectLayer(layers, id, 'change its effects');\n requireEffect(layer, effectId);\n const current = layer.effects!;\n return setLayerEffects(layers, id, { ...current, items: current.items.filter((e) => e.id !== effectId) });\n}\n\n/** Sets the master switch and, when given, Scale Effects (0.01..10, 1 = 100 %). */\nexport function setLayerEffectsEnabled(layers: readonly ImageLayer[], id: string, enabled: boolean, scale?: number): ImageLayer[] {\n const layer = requireEffectLayer(layers, id, 'change its effects');\n if (scale !== undefined && !(Number.isFinite(scale) && scale >= 0.01 && scale <= 10)) fail(`scale must be from 0.01 to 10 (1 = 100 %), got ${scale}.`);\n const current = layerEffectsOf(layer);\n const next: LayerEffects = { ...current, enabled };\n if (scale !== undefined) {\n if (scale === 1) delete next.scale;\n else next.scale = scale;\n }\n return setLayerEffects(layers, id, next);\n}\n\n/** A blend mode check for tools. */\nexport function parseBlend(value: unknown, allowPassThrough = false): BlendMode | 'pass-through' | undefined {\n if (value === undefined) return undefined;\n if (value === 'pass-through' && allowPassThrough) return value;\n if (!isBlendMode(value)) fail(`Unknown blend mode \"${String(value)}\".`);\n return value as BlendMode;\n}\n\n/** The union of two rectangles (either may be null). */\nexport function unionRect(a: CropBox | null, b: CropBox | null): CropBox | null {\n if (!a) return b;\n if (!b) return a;\n const x = Math.min(a.x, b.x);\n const y = Math.min(a.y, b.y);\n return { x, y, width: Math.max(a.x + a.width, b.x + b.width) - x, height: Math.max(a.y + a.height, b.y + b.height) - y };\n}\n","// Portions ported from photocraft/crates/doc/src/lib.rs (SmartObject, SmartSource, smart filters) (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\n/**\n * Smart objects as pure functions: their contents (raster pixels or a nested layer document,\n * shared by linked instances), the non-destructive placement (a warp in the contents' pixels and a\n * projective corner map, the Free Transform geometry), the smart filter stack with its one filter\n * mask, and the rendering that turns them into the layer's pixels. Every change re-renders from\n * the contents, so transforms never degrade.\n *\n * The operations validate against the stack and throw a `LayerOperationError` that says what is\n * wrong before changing anything, like the other layer operations.\n */\n\nimport { blendTile } from '../../raster/compositor';\nimport { gaussianBlur } from '../../raster/blur';\nimport { MAX_EFFECT_REACH, effectsMargin } from '../../raster/effects/model';\nimport {\n contentBounds,\n emptySurface,\n readRegion,\n surfaceFromPixels,\n translateSurface,\n type PixelSurface,\n type RasterRect,\n} from '../../raster/surface';\nimport { isInterpolation, reduceSurface, transformSurface, TransformTooLargeError, warpSurface, type Interpolation } from '../../raster/transform';\nimport { isBlendMode, type BlendMode } from '../../raster/blend';\nimport { boxQuad, invertHomography, multiplyHomographies, type Homography } from '../../geom/homography';\nimport { isValidWarpMesh, mapWarpMeshPoints } from '../../geom/warp';\nimport type { Quad, WarpMesh } from '../../geom/types';\nimport { hasWarp, transformHomography, transformMap, transformedBox, type FreeTransformGeometry } from '../transform';\nimport type { CropBox } from '../types';\nimport { LayerCompositor, runLayerFilters } from './compositor';\nimport {\n findLayer,\n flattenLayers,\n insertLayer,\n isActiveFilter,\n mapLayer,\n newLayerId,\n type ImageLayer,\n type LayerFilter,\n type PixelLayer,\n type RasterMask,\n type SmartContents,\n type SmartDocument,\n type SmartFilter,\n type SmartFilterStack,\n type SmartObjectLayer,\n type SmartPlacement,\n} from './model';\nimport { LayerOperationError, normalizeFilter, requireLayer, requireUnlocked } from './ops';\n\nconst fail = (message: string): never => {\n throw new LayerOperationError(message);\n};\n\n/**\n * How far beyond the canvas a smart object is rendered (document pixels): enough for any layer\n * effect to see the parts just outside the canvas. Further out a smart object is not drawn.\n */\nexport const SMART_RENDER_MARGIN = MAX_EFFECT_REACH;\n\n/** The document canvas a smart object renders for. */\nexport interface SmartCanvas {\n width: number;\n height: number;\n}\n\n/** A fresh contents id (independent contents). */\nexport function newContentsId(): string {\n return newLayerId('so');\n}\n\n/** The contents' rectangle in its own pixels. */\nexport function contentsBox(contents: SmartContents): CropBox {\n return { x: 0, y: 0, width: contents.width, height: contents.height };\n}\n\n/** A smart object's placement as Free Transform geometry (the frame is the contents' rectangle). */\nexport function placementGeometry(layer: Pick<SmartObjectLayer, 'contents' | 'placement'>): FreeTransformGeometry {\n return { box: contentsBox(layer.contents), corners: layer.placement.corners, warp: layer.placement.warp ?? null };\n}\n\n/** The placement that draws the contents 1:1 with their top-left corner at (`x`, `y`). */\nexport function placementAt(contents: Pick<SmartContents, 'width' | 'height'>, x: number, y: number): SmartPlacement {\n return { corners: boxQuad({ x, y, width: contents.width, height: contents.height }) };\n}\n\n/** Whether the contents can be rendered here (decoded pixels or a layer document). */\nexport function isContentsDecoded(contents: SmartContents): boolean {\n return contents.kind === 'raster' ? Boolean(contents.raster) : Boolean(contents.document);\n}\n\nfunction requireDecoded(layer: SmartObjectLayer, action: string): void {\n if (isContentsDecoded(layer.contents)) return;\n const what = layer.contents.file?.type ? `a ${layer.contents.file.type.toUpperCase()} file` : 'a file';\n fail(\n `The contents of smart object \"${layer.name || layer.id}\" are ${what} that could not be decoded here, so it cannot ${action}. ` +\n 'Run prepareSmartObjects() in a browser first, or rasterize it.'\n );\n}\n\n/** The layer with `id`, which must be a smart object. */\nexport function requireSmartObject(layers: readonly ImageLayer[], id: string): SmartObjectLayer {\n const layer = requireLayer(layers, id).layer;\n if (layer.type !== 'smart') fail(`Layer \"${layer.name || id}\" is a ${layer.type} layer, not a smart object; convert it with convert_to_smart_object.`);\n return layer as SmartObjectLayer;\n}\n\n// ---------------------------------------------------------------------------\n// Contents\n// ---------------------------------------------------------------------------\n\nconst contentsPixels = new WeakMap<SmartContents, PixelSurface>();\n\n/**\n * The contents as pixels at (0, 0) in their own space: the raster, or the nested document\n * composited at its size (annotation layers inside need a canvas and are left out). Memoised.\n */\nexport function contentsSurface(contents: SmartContents): PixelSurface {\n let s = contentsPixels.get(contents);\n if (s) return s;\n if (contents.kind === 'raster') {\n s = contents.raster ?? emptySurface(4);\n } else {\n const doc = contents.document;\n if (!doc) return emptySurface(4);\n const W = contents.width;\n const H = contents.height;\n const data = new LayerCompositor().composite(doc.layers, { x: 0, y: 0, width: W, height: H }, {\n width: W,\n height: H,\n globalLight: doc.globalLight,\n pattern: (id) => doc.patterns?.find((p) => p.id === id) ?? null,\n });\n s = surfaceFromPixels({ width: W, height: H, data }, 4, 0, 0);\n }\n contentsPixels.set(contents, s);\n return s;\n}\n\n/** Raster contents from RGBA pixels. */\nexport function rasterContents(pixels: { width: number; height: number; data: ArrayLike<number> }, name: string, id = newContentsId()): SmartContents {\n if (!(pixels.width > 0 && pixels.height > 0) || pixels.data.length < pixels.width * pixels.height * 4) fail('Contents pixels must be RGBA with width × height × 4 bytes.');\n return { id, name, width: pixels.width, height: pixels.height, kind: 'raster', raster: surfaceFromPixels(pixels, 4, 0, 0) };\n}\n\n/** Document contents from layers on a `width × height` canvas. */\nexport function documentContents(document: SmartDocument, width: number, height: number, name: string, id = newContentsId()): SmartContents {\n if (!(Number.isInteger(width) && Number.isInteger(height) && width > 0 && height > 0)) fail('Contents width and height must be positive whole numbers.');\n return { id, name, width, height, kind: 'document', document };\n}\n\n// ---------------------------------------------------------------------------\n// Rendering\n// ---------------------------------------------------------------------------\n\n/** How a render runs: full quality (default), or a fast preview. */\nexport interface SmartRenderOptions {\n /** Preview block size (a power of two): sample a reduced copy of the contents once per block. */\n step?: number;\n /** Only this part of the document (the visible area). */\n clip?: RasterRect;\n /** Leave the smart filters out (previews while transforming). */\n skipFilters?: boolean;\n /** Reduced copies of the contents by factor, kept by the caller between previews. */\n reduced?: Map<number, PixelSurface>;\n}\n\nconst placedCache = new WeakMap<SmartContents, Map<string, PixelSurface>>();\nconst filteredCache = new WeakMap<PixelSurface, { key: string; base: Uint8ClampedArray; filtered: Uint8ClampedArray }>();\n\nfunction placementKey(p: SmartPlacement, canvas: SmartCanvas): string {\n return JSON.stringify([p.corners, p.warp ?? null, p.interpolation ?? 'bicubic', canvas.width, canvas.height]);\n}\n\n/** The integer move of a placement that is a whole-pixel translation without a warp, or `null`. */\nfunction integerMove(layer: Pick<SmartObjectLayer, 'contents' | 'placement'>): { x: number; y: number } | null {\n const g = placementGeometry(layer);\n if (hasWarp(g)) return null;\n const q = boxQuad(g.box);\n const dx = g.corners[0].x - q[0].x;\n const dy = g.corners[0].y - q[0].y;\n if (Math.abs(dx - Math.round(dx)) > 1e-9 || Math.abs(dy - Math.round(dy)) > 1e-9) return null;\n const same = g.corners.every((c, i) => Math.abs(c.x - q[i].x - dx) < 1e-9 && Math.abs(c.y - q[i].y - dy) < 1e-9);\n return same ? { x: Math.round(dx), y: Math.round(dy) } : null;\n}\n\n/** The contents placed in the document (no filters), clipped to the canvas grown by the render margin. */\nexport function placeContents(layer: Pick<SmartObjectLayer, 'contents' | 'placement'>, canvas: SmartCanvas, options: SmartRenderOptions = {}): PixelSurface {\n const { contents, placement } = layer;\n const preview = Boolean(options.step && options.step > 1) || Boolean(options.clip);\n const key = placementKey(placement, canvas);\n if (!preview) {\n const hit = placedCache.get(contents)?.get(key);\n if (hit) return hit;\n }\n const src = contentsSurface(contents);\n const m = SMART_RENDER_MARGIN;\n const area = { x: -m, y: -m, width: canvas.width + 2 * m, height: canvas.height + 2 * m };\n let clip = area;\n if (options.clip) {\n const c = options.clip;\n const x0 = Math.max(area.x, Math.floor(c.x));\n const y0 = Math.max(area.y, Math.floor(c.y));\n const x1 = Math.min(area.x + area.width, Math.ceil(c.x + c.width));\n const y1 = Math.min(area.y + area.height, Math.ceil(c.y + c.height));\n clip = { x: x0, y: y0, width: Math.max(0, x1 - x0), height: Math.max(0, y1 - y0) };\n }\n let out: PixelSurface;\n const move = integerMove(layer);\n if (move && !options.clip) {\n // Exact and cheap: a whole-pixel move shares tiles (Convert to Smart Object renders this way).\n out = translateSurface(src, move.x, move.y);\n } else {\n const g = placementGeometry(layer);\n const interpolation: Interpolation = preview ? (placement.interpolation === 'nearest' ? 'nearest' : 'bilinear') : (placement.interpolation ?? 'bicubic');\n const step = options.step && options.step > 1 ? options.step : 1;\n let source = src;\n let k = 1;\n if (step > 1) {\n k = step;\n let r = options.reduced?.get(k);\n if (!r) {\n r = reduceSurface(src, k);\n options.reduced?.set(k, r);\n }\n source = r;\n }\n const box = { x: 0, y: 0, width: Math.ceil(contents.width / k), height: Math.ceil(contents.height / k) };\n try {\n if (hasWarp(g)) {\n const f = transformMap(g);\n if (!f) fail('The placement folds the contents over itself or through the horizon; keep the corners a convex quad.');\n out = warpSurface(source, (p) => f!({ x: p.x * k, y: p.y * k }), { interpolation, source: box, clip, step: step > 1 ? step : undefined });\n } else {\n const h = transformHomography(g);\n if (!h) fail('The placement folds the contents over itself or through the horizon; keep the corners a convex quad.');\n const hk = multiplyHomographies([k, 0, 0, 0, k, 0, 0, 0, 1], h!);\n out = transformSurface(source, hk, { interpolation, source: box, clip, step: step > 1 ? step : undefined });\n }\n } catch (e) {\n if (e instanceof TransformTooLargeError) fail(e.message);\n throw e;\n }\n }\n if (!preview) {\n let byKey = placedCache.get(contents);\n if (!byKey) {\n byKey = new Map();\n placedCache.set(contents, byKey);\n }\n byKey.set(key, out);\n while (byKey.size > 4) byKey.delete(byKey.keys().next().value as string);\n }\n return out;\n}\n\n/** The filters of a stack that change pixels (the stack enabled, each filter active, a modelled kind). */\nexport function activeSmartFilters(stack: SmartFilterStack | undefined): Array<SmartFilter & LayerFilter> {\n if (!stack?.enabled) return [];\n return stack.filters.filter((f): f is SmartFilter & LayerFilter => f.kind !== 'psd' && isActiveFilter(f as LayerFilter) && f.opacity > 0);\n}\n\n/** A filter mask's coverage over the canvas (0..1), after density and feather; `null` reveals all. */\nexport function filterMaskCoverage(mask: RasterMask | undefined, canvas: SmartCanvas): Float32Array | null {\n if (!mask || mask.enabled === false || mask.source) return null;\n const W = canvas.width;\n const H = canvas.height;\n const bytes = readRegion(mask.surface, { x: 0, y: 0, width: W, height: H });\n let v: Float32Array = new Float32Array(W * H);\n let all = true;\n for (let i = 0; i < v.length; i++) {\n v[i] = bytes[i] / 255;\n if (bytes[i] !== 255) all = false;\n }\n const feather = mask.feather ?? 0;\n if (all && !(feather > 0)) return null;\n if (feather > 0) v = gaussianBlur(v, W, H, feather, 'clamp') as Float32Array;\n const density = mask.density ?? 1;\n if (density !== 1) for (let i = 0; i < v.length; i++) v[i] = 1 - density * (1 - v[i]);\n return v;\n}\n\n/**\n * Runs a smart filter stack over the placed contents, over the canvas (spatial filters clamp at\n * its edges, as a pixel layer's filters do): each filter filters the result below it, and its own\n * blend mode and opacity mix its result with that input. The filter mask then mixes the filtered\n * result with the unfiltered contents. Returns `placed` itself when no filter is active.\n */\nexport function applySmartFilters(placed: PixelSurface, stack: SmartFilterStack | undefined, canvas: SmartCanvas): PixelSurface {\n const active = activeSmartFilters(stack);\n if (!active.length || !(canvas.width > 0 && canvas.height > 0)) return placed;\n const W = canvas.width;\n const H = canvas.height;\n const key = JSON.stringify([W, H, active.map((f) => ({ ...f, id: undefined }))]);\n let entry = filteredCache.get(placed);\n if (!entry || entry.key !== key) {\n const base = readRegion(placed, { x: 0, y: 0, width: W, height: H });\n let acc = new Uint8ClampedArray(base);\n for (const f of active) {\n const out = new Uint8ClampedArray(acc);\n runLayerFilters(out, W, H, [f]);\n if (f.blend === 'normal') {\n if (f.opacity >= 1) acc = out;\n else lerpInto(acc, out, f.opacity, null);\n } else {\n blendTile(acc, out, f.blend, f.opacity, null);\n }\n }\n entry = { key, base, filtered: acc };\n filteredCache.set(placed, entry);\n }\n const cov = filterMaskCoverage(stack!.mask, canvas);\n let result = entry.filtered;\n if (cov) {\n result = new Uint8ClampedArray(entry.base);\n lerpInto(result, entry.filtered, 1, cov);\n }\n return surfaceFromPixels({ width: W, height: H, data: result }, 4, 0, 0);\n}\n\n/** `dst = lerp(dst, src, k · coverage)` per channel. */\nfunction lerpInto(dst: Uint8ClampedArray, src: Uint8ClampedArray, k: number, coverage: Float32Array | null): void {\n for (let i = 0, p = 0; i < dst.length; i += 4, p++) {\n const t = coverage ? k * coverage[p] : k;\n if (t <= 0) continue;\n if (t >= 1) {\n dst[i] = src[i];\n dst[i + 1] = src[i + 1];\n dst[i + 2] = src[i + 2];\n dst[i + 3] = src[i + 3];\n continue;\n }\n for (let c = 0; c < 4; c++) dst[i + c] = Math.round(dst[i + c] + (src[i + c] - dst[i + c]) * t);\n }\n}\n\n/** A smart object's pixels: the contents through the placement, then the smart filters. */\nexport function renderSmartObject(layer: Pick<SmartObjectLayer, 'contents' | 'placement' | 'smartFilters'>, canvas: SmartCanvas, options: SmartRenderOptions = {}): PixelSurface {\n const placed = placeContents(layer, canvas, options);\n return options.skipFilters ? placed : applySmartFilters(placed, layer.smartFilters, canvas);\n}\n\n/** The layer with its pixels rendered again (refused when the contents are not decoded). */\nexport function rerender(layer: SmartObjectLayer, canvas: SmartCanvas, action = 'change'): SmartObjectLayer {\n requireDecoded(layer, action);\n return { ...layer, surface: renderSmartObject(layer, canvas) };\n}\n\n// ---------------------------------------------------------------------------\n// Placement\n// ---------------------------------------------------------------------------\n\n/** Validates a placement for contents of `width × height`; returns a clean copy. */\nexport function normalizePlacement(raw: SmartPlacement, contents: Pick<SmartContents, 'width' | 'height'>): SmartPlacement {\n const c = raw?.corners as unknown;\n if (!Array.isArray(c) || c.length !== 4 || !c.every((p) => p && Number.isFinite(p.x) && Number.isFinite(p.y))) {\n fail('corners must be four finite points [top-left, top-right, bottom-right, bottom-left] in document pixels.');\n }\n const out: SmartPlacement = { corners: (c as Quad).map((p) => ({ x: p.x, y: p.y })) as unknown as Quad };\n if (raw.warp) {\n if (!isValidWarpMesh(raw.warp)) fail('warp must be a mesh with us and vs (knots from 0 to 1, increasing) and (3·columns + 1) × (3·rows + 1) finite points in contents pixels.');\n out.warp = { us: [...raw.warp.us], vs: [...raw.warp.vs], points: raw.warp.points.map((p) => ({ x: p.x, y: p.y })) };\n }\n if (raw.interpolation !== undefined) {\n if (!isInterpolation(raw.interpolation)) fail(`Unknown interpolation \"${String(raw.interpolation)}\"; use nearest, bilinear or bicubic.`);\n if (raw.interpolation !== 'bicubic') out.interpolation = raw.interpolation;\n }\n const g: FreeTransformGeometry = { box: { x: 0, y: 0, width: contents.width, height: contents.height }, corners: out.corners, warp: out.warp ?? null };\n if (!transformHomography(g)) fail('These corners fold the contents over themselves or through the horizon; keep the quad convex.');\n if (out.warp && !hasWarp(g)) delete out.warp;\n return out;\n}\n\n/** The projective map of a warp-free placement (contents → document), or `null`. */\nfunction placementHomography(layer: Pick<SmartObjectLayer, 'contents' | 'placement'>): Homography | null {\n const g = placementGeometry(layer);\n return hasWarp(g) ? null : transformHomography(g);\n}\n\n/**\n * Moves a linked mask with a placement change when both placements are warp-free (by the change's\n * projective map); otherwise, or for an unlinked or parametric mask, it stays.\n */\nfunction followMask(mask: RasterMask | undefined, before: SmartObjectLayer, after: SmartObjectLayer): RasterMask | undefined {\n if (!mask || mask.linked === false || mask.source) return mask;\n const h0 = placementHomography(before);\n const h1 = placementHomography(after);\n if (!h0 || !h1) return mask;\n const inv = invertHomography(h0);\n if (!inv) return mask;\n const delta = multiplyHomographies(inv, h1);\n const surface = transformSurface(mask.surface, delta, { interpolation: 'bilinear' });\n return { ...mask, surface };\n}\n\n/**\n * Sets a smart object's placement (corners, warp, interpolation) and renders it again from its\n * contents. A linked layer mask and filter mask follow a warp-free change. Returns the stack and\n * whether the masks moved.\n */\nexport function setSmartPlacement(\n layers: readonly ImageLayer[],\n id: string,\n placement: SmartPlacement,\n canvas: SmartCanvas\n): { layers: ImageLayer[]; layer: SmartObjectLayer; masksMoved: boolean } {\n const layer = requireSmartObject(layers, id);\n requireUnlocked(layer, 'position', 'transform it');\n requireDecoded(layer, 'be transformed');\n const clean = normalizePlacement(placement, layer.contents);\n let next: SmartObjectLayer = { ...layer, placement: clean };\n let masksMoved = false;\n if (layer.mask) {\n const m = followMask(layer.mask, layer, next);\n masksMoved ||= m !== layer.mask;\n next.mask = m;\n }\n if (next.smartFilters?.mask) {\n const m = followMask(next.smartFilters.mask, layer, next);\n masksMoved ||= m !== next.smartFilters.mask;\n next.smartFilters = { ...next.smartFilters, mask: m };\n }\n next = rerender(next, canvas, 'be transformed');\n return { layers: mapLayer(layers, id, () => next), layer: next, masksMoved };\n}\n\n/**\n * Moves a smart object by whole pixels (the move tool): its corners, a linked mask and filter mask\n * move with it. Without spatial smart filters the rendered pixels are shifted, not resampled.\n */\nexport function offsetSmartObject(layers: readonly ImageLayer[], id: string, dx: number, dy: number, canvas: SmartCanvas): ImageLayer[] {\n const layer = requireSmartObject(layers, id);\n requireUnlocked(layer, 'position', 'move it');\n const mx = Math.round(dx);\n const my = Math.round(dy);\n if (!mx && !my) return layers as ImageLayer[];\n const corners = layer.placement.corners.map((p) => ({ x: p.x + mx, y: p.y + my })) as unknown as Quad;\n let next: SmartObjectLayer = { ...layer, placement: { ...layer.placement, corners } };\n const moveMask = (m: RasterMask | undefined) => (m && m.linked !== false && !m.source ? { ...m, surface: translateSurface(m.surface, mx, my) } : m);\n if (layer.mask) next.mask = moveMask(layer.mask);\n if (layer.smartFilters?.mask) next.smartFilters = { ...layer.smartFilters, mask: moveMask(layer.smartFilters.mask) };\n // Shifting is exact unless a filter reads across the canvas edge, or the render was cut at its margin.\n const spatial = activeSmartFilters(layer.smartFilters).some((f) => f.kind === 'blur');\n const b = contentBounds(layer.surface);\n const m = SMART_RENDER_MARGIN;\n const cut = b && (b.x <= -m || b.y <= -m || b.x + b.width >= canvas.width + m || b.y + b.height >= canvas.height + m);\n if (!spatial && !cut && !(next.smartFilters?.mask && activeSmartFilters(next.smartFilters).length && next.smartFilters.mask.linked === false)) {\n next = { ...next, surface: translateSurface(layer.surface, mx, my) };\n } else {\n next = rerender(next, canvas, 'move');\n }\n return mapLayer(layers, id, () => next);\n}\n\n// ---------------------------------------------------------------------------\n// Convert, rasterize, copy\n// ---------------------------------------------------------------------------\n\n/** Moves a layer (and everything in it) by whole pixels, for nesting it in contents. */\nfunction translateLayer(layer: ImageLayer, dx: number, dy: number): ImageLayer {\n if (!dx && !dy) return layer;\n const moveMask = (m: RasterMask | undefined): RasterMask | undefined => {\n if (!m) return m;\n if (m.source) fail(`Layer \"${layer.name || layer.id}\" has a depth mask, which cannot move into a smart object; bake or remove it first.`);\n return { ...m, surface: translateSurface(m.surface, dx, dy) };\n };\n switch (layer.type) {\n case 'pixel':\n // Pixels and the mask are in layer space; moving the offset moves both.\n return { ...layer, offset: { x: layer.offset.x + dx, y: layer.offset.y + dy } };\n case 'smart': {\n const next: SmartObjectLayer = {\n ...layer,\n placement: { ...layer.placement, corners: layer.placement.corners.map((p) => ({ x: p.x + dx, y: p.y + dy })) as unknown as Quad },\n surface: translateSurface(layer.surface, dx, dy),\n mask: moveMask(layer.mask),\n };\n if (!next.mask) delete next.mask;\n if (layer.smartFilters?.mask) next.smartFilters = { ...layer.smartFilters, mask: moveMask(layer.smartFilters.mask) };\n return next;\n }\n case 'group': {\n const next = { ...layer, children: layer.children.map((c) => translateLayer(c, dx, dy)), mask: moveMask(layer.mask) };\n if (!next.mask) delete next.mask;\n return next;\n }\n case 'adjustment': {\n const next = { ...layer, mask: moveMask(layer.mask) } as ImageLayer;\n if (!next.mask) delete next.mask;\n return next;\n }\n case 'annotation':\n return fail(`Annotation layer \"${layer.name || layer.id}\" cannot go into a smart object; rasterize it first.`);\n }\n}\n\n/** Integer document bounds a layer draws into (effects included), or `null` for none; the canvas for layers without bounds. */\nfunction layerExtent(layer: ImageLayer, surfaceOf: (l: PixelLayer) => PixelSurface, canvas: SmartCanvas): CropBox | null {\n const full = { x: 0, y: 0, width: canvas.width, height: canvas.height };\n let b: CropBox | null = null;\n if (layer.type === 'pixel' || layer.type === 'smart') {\n const s = layer.type === 'pixel' ? surfaceOf(layer) : layer.surface;\n if (s.defaultValue !== 0) b = full;\n else {\n const c = contentBounds(s);\n b = c ? { x: c.x + layer.offset.x, y: c.y + layer.offset.y, width: c.width, height: c.height } : null;\n }\n } else if (layer.type === 'group') {\n for (const c of layer.children) b = union(b, layerExtent(c, surfaceOf, canvas));\n } else {\n b = full;\n }\n const m = effectsMargin(layer.effects);\n if (b && m > 0) b = { x: b.x - Math.ceil(m), y: b.y - Math.ceil(m), width: b.width + 2 * Math.ceil(m), height: b.height + 2 * Math.ceil(m) };\n return b;\n}\n\nfunction union(a: CropBox | null, b: CropBox | null): CropBox | null {\n if (!a) return b;\n if (!b) return a;\n const x = Math.min(a.x, b.x);\n const y = Math.min(a.y, b.y);\n return { x, y, width: Math.max(a.x + a.width, b.x + b.width) - x, height: Math.max(a.y + a.height, b.y + b.height) - y };\n}\n\n/** Plain smart filters from a pixel layer's filters (normal, 100 %). */\nfunction asSmartFilters(filters: readonly LayerFilter[] | undefined): SmartFilterStack | undefined {\n if (!filters?.length) return undefined;\n return { enabled: true, filters: filters.map((f) => ({ ...f, blend: 'normal' as BlendMode, opacity: 1 })) };\n}\n\nexport interface ConvertOptions {\n /** The pixels of a pixel layer (resolves the Background's document source). */\n surfaceOf(layer: PixelLayer): PixelSurface;\n /** Name of the new layer (default: the top layer's name). */\n name?: string;\n}\n\n/**\n * Layer › Smart Objects › Convert to Smart Object: sibling layers become one smart object at the\n * position of the topmost one.\n *\n * - One pixel layer becomes raster contents (its pixels, cropped to their bounds) and keeps its\n * id, name, visibility, opacity, fill, blend, clipping, mask and effects; its filters become\n * smart filters. It renders exactly as before.\n * - Anything else (several layers, a group, an adjustment, a smart object) becomes a nested\n * document holding the layers as they were, sized to their bounds (effects included), and the\n * new layer is a plain Normal layer named after the top one, rendering the isolated composite.\n *\n * Returns the stack, the new layer and its contents.\n */\nexport function convertToSmartObject(\n layers: readonly ImageLayer[],\n ids: readonly string[],\n canvas: SmartCanvas,\n options: ConvertOptions\n): { layers: ImageLayer[]; layer: SmartObjectLayer } {\n if (!ids.length) fail('Give at least one layer id to convert.');\n const locs = [...new Set(ids)].map((id) => requireLayer(layers, id));\n const parent = locs[0].parent?.id ?? null;\n if (locs.some((l) => (l.parent?.id ?? null) !== parent)) fail('Layers to convert must share one parent; move them next to each other first.');\n for (const l of locs) requireUnlocked(l.layer, 'all', 'convert it');\n const ordered = locs.sort((a, b) => a.index - b.index);\n const top = ordered[ordered.length - 1];\n\n let smart: SmartObjectLayer;\n const single = ordered.length === 1 ? ordered[0].layer : null;\n if (single && single.type === 'pixel') {\n const surface = options.surfaceOf(single);\n if (surface.defaultValue !== 0) fail(`Layer \"${single.name || single.id}\" has no transparent area to crop to; it cannot be converted.`);\n const b = contentBounds(surface);\n if (!b) fail(`Layer \"${single.name || single.id}\" has no pixels to convert; paint something first.`);\n const contents: SmartContents = {\n id: newContentsId(),\n name: `${single.name || 'Layer'}.png`,\n width: b!.width,\n height: b!.height,\n kind: 'raster',\n raster: translateSurface(surface, -b!.x, -b!.y),\n };\n smart = {\n type: 'smart',\n id: single.id,\n name: single.name,\n visible: single.visible,\n opacity: single.opacity,\n blend: single.blend,\n contents,\n placement: placementAt(contents, b!.x + single.offset.x, b!.y + single.offset.y),\n surface: emptySurface(4),\n offset: { x: 0, y: 0 },\n };\n if (single.clipped) smart.clipped = true;\n if (single.fillOpacity !== undefined) smart.fillOpacity = single.fillOpacity;\n if (single.effects) smart.effects = single.effects;\n if (single.mask) {\n // Masks of pixel layers are in layer space; a smart object's are in document space.\n smart.mask = single.mask.source ? single.mask : { ...single.mask, surface: translateSurface(single.mask.surface, single.offset.x, single.offset.y) };\n }\n const filters = asSmartFilters(single.filters);\n if (filters) smart.smartFilters = filters;\n } else {\n let b: CropBox | null = null;\n for (const l of ordered) b = union(b, layerExtent(l.layer, options.surfaceOf, canvas));\n if (!b || !(b.width > 0 && b.height > 0)) fail('The layers have no pixels to convert; paint something first.');\n const nested = ordered.map((l) => {\n let layer = l.layer;\n if (layer.type === 'pixel' && layer.source === 'document') {\n const resolved = { ...layer, surface: options.surfaceOf(layer) };\n delete resolved.source;\n layer = resolved;\n }\n if (flattenLayers([layer]).some((x) => x.type === 'pixel' && x.source === 'document')) fail(`Layer \"${layer.name || layer.id}\" holds the document image inside a group; convert the Background on its own.`);\n return translateLayer(layer, -b!.x, -b!.y);\n });\n // The bottom layer has nothing below it to clip to inside the contents.\n if (nested[0].clipped) {\n const first = { ...nested[0] };\n delete first.clipped;\n nested[0] = first;\n }\n const name = options.name?.trim() || top.layer.name || 'Smart Object';\n const contents = documentContents({ layers: nested }, b!.width, b!.height, `${name}.psb`);\n smart = {\n type: 'smart',\n id: ordered.length === 1 ? ordered[0].layer.id : newLayerId('smart'),\n name,\n visible: true,\n opacity: 1,\n blend: 'normal',\n contents,\n placement: placementAt(contents, b!.x, b!.y),\n surface: emptySurface(4),\n offset: { x: 0, y: 0 },\n };\n }\n if (options.name?.trim()) smart.name = options.name.trim();\n smart = { ...smart, surface: renderSmartObject(smart, canvas) };\n const below = ordered.filter((l) => l.index < top.index).length;\n let out = layers as ImageLayer[];\n for (const l of ordered) out = mapLayer(out, l.layer.id, () => null);\n return { layers: insertLayer(out, smart, parent, top.index - below), layer: smart };\n}\n\n/**\n * Layer › Smart Objects › Rasterize: the smart object becomes a pixel layer holding what it\n * renders (filters baked), with its mask, effects, blend, opacity and fill. Same id.\n */\nexport function rasterizeSmartObject(layers: readonly ImageLayer[], id: string): { layers: ImageLayer[]; layer: PixelLayer } {\n const layer = requireSmartObject(layers, id);\n requireUnlocked(layer, 'all', 'rasterize it');\n const out: PixelLayer = {\n type: 'pixel',\n id: layer.id,\n name: layer.name,\n visible: layer.visible,\n opacity: layer.opacity,\n blend: layer.blend,\n surface: layer.surface,\n offset: { x: 0, y: 0 },\n };\n if (layer.clipped) out.clipped = true;\n if (layer.mask) out.mask = layer.mask;\n if (layer.locks) out.locks = layer.locks;\n if (layer.effects) out.effects = layer.effects;\n if (layer.fillOpacity !== undefined) out.fillOpacity = layer.fillOpacity;\n return { layers: mapLayer(layers, id, () => out), layer: out };\n}\n\n/**\n * Layer › Smart Objects › New Smart Object via Copy (`linked: false`, fresh contents that edit\n * independently) or a linked duplicate (`linked: true`, shared contents), directly above.\n */\nexport function copySmartObject(layers: readonly ImageLayer[], id: string, linked: boolean, name?: string): { layers: ImageLayer[]; layer: SmartObjectLayer } {\n const loc = requireLayer(layers, id);\n const layer = requireSmartObject(layers, id);\n const copy: SmartObjectLayer = { ...layer, id: newLayerId('smart'), name: name?.trim() || `${layer.name || 'Smart Object'} copy` };\n delete copy.locks;\n delete copy.psd;\n if (!linked) copy.contents = { ...layer.contents, id: newContentsId() };\n if (copy.smartFilters) copy.smartFilters = { ...copy.smartFilters, filters: copy.smartFilters.filters.map((f) => ({ ...f, id: newLayerId('flt') })) };\n return { layers: insertLayer(layers, copy, loc.parent?.id ?? null, loc.index + 1), layer: copy };\n}\n\n/** Ids of the smart objects in the stack placed from contents `contentsId` (linked instances), bottom first. */\nexport function instancesOf(layers: readonly ImageLayer[], contentsId: string): string[] {\n return flattenLayers(layers)\n .filter((l): l is SmartObjectLayer => l.type === 'smart' && l.contents.id === contentsId)\n .map((l) => l.id);\n}\n\n/**\n * Puts new contents into every instance of `contentsId` and renders them again. With\n * `keepPlacement` false (Replace Contents of a different size), each instance keeps its scale and\n * rotation about the contents' centre, and a warp is stretched to the new size.\n */\nexport function replaceContents(\n layers: readonly ImageLayer[],\n contentsId: string,\n contents: SmartContents,\n canvas: SmartCanvas\n): { layers: ImageLayer[]; ids: string[] } {\n const ids = instancesOf(layers, contentsId);\n if (!ids.length) fail(`No smart object uses contents \"${contentsId}\".`);\n for (const id of ids) requireUnlocked(findLayer(layers, id)!.layer, 'all', 'replace its contents');\n const next = { ...contents, id: contentsId };\n let out = layers as ImageLayer[];\n for (const id of ids) {\n out = mapLayer(out, id, (l) => {\n const layer = l as SmartObjectLayer;\n return rerender({ ...layer, contents: next, placement: refitPlacement(layer, next) }, canvas, 'take the new contents');\n });\n }\n return { layers: out, ids };\n}\n\n/** A placement for new contents of another size: the same map about the contents' centre (warp stretched). */\nfunction refitPlacement(layer: SmartObjectLayer, contents: Pick<SmartContents, 'width' | 'height'>): SmartPlacement {\n const W0 = layer.contents.width;\n const H0 = layer.contents.height;\n const W1 = contents.width;\n const H1 = contents.height;\n if (W0 === W1 && H0 === H1) return layer.placement;\n const g = placementGeometry(layer);\n const h = transformHomography(g);\n if (!h) return placementAt(contents, 0, 0);\n // The new rectangle centred on the old one, through the old corner map.\n const shift: Homography = [1, 0, (W0 - W1) / 2, 0, 1, (H0 - H1) / 2, 0, 0, 1];\n const hn = multiplyHomographies(shift, h);\n const pts = boxQuad({ x: 0, y: 0, width: W1, height: H1 }).map((p) => {\n const w = hn[6] * p.x + hn[7] * p.y + hn[8];\n return { x: (hn[0] * p.x + hn[1] * p.y + hn[2]) / w, y: (hn[3] * p.x + hn[4] * p.y + hn[5]) / w };\n });\n const out: SmartPlacement = { corners: pts as unknown as Quad };\n if (layer.placement.interpolation) out.interpolation = layer.placement.interpolation;\n if (layer.placement.warp) out.warp = mapWarpMeshPoints(layer.placement.warp, (p) => ({ x: (p.x * W1) / W0, y: (p.y * H1) / H0 }));\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Smart filters\n// ---------------------------------------------------------------------------\n\n/** A clean smart filter from loose input: a `develop` or `blur` filter plus blend and opacity. */\nexport function normalizeSmartFilter(raw: Record<string, unknown> & { kind: string }, id = newLayerId('flt')): SmartFilter {\n if (raw.kind === 'psd') fail('Photoshop filters can only come from a PSD; add develop or blur.');\n const base = normalizeFilter(raw as Partial<LayerFilter> & { kind: LayerFilter['kind'] }, id);\n const blend = raw.blend ?? 'normal';\n if (!isBlendMode(blend) || blend === 'pigment') fail(`Unknown blend mode \"${String(blend)}\" for a smart filter.`);\n const opacity = raw.opacity ?? 1;\n if (!(typeof opacity === 'number' && opacity >= 0 && opacity <= 1)) fail('opacity must be a number from 0 to 1.');\n return { ...base, blend: blend as BlendMode, opacity: opacity as number };\n}\n\nfunction withStack(layer: SmartObjectLayer, stack: SmartFilterStack | undefined, canvas: SmartCanvas): SmartObjectLayer {\n const next = { ...layer };\n if (stack && (stack.filters.length || stack.mask || !stack.enabled)) next.smartFilters = stack;\n else delete next.smartFilters;\n return rerender(next, canvas, 'change its smart filters');\n}\n\nfunction stackOf(layer: SmartObjectLayer): SmartFilterStack {\n return layer.smartFilters ?? { enabled: true, filters: [] };\n}\n\nfunction requireFilter(layer: SmartObjectLayer, filterId: string): number {\n const list = layer.smartFilters?.filters ?? [];\n const i = list.findIndex((f) => f.id === filterId);\n if (i === -1) fail(`Smart object \"${layer.name || layer.id}\" has no smart filter \"${filterId}\". Filters: ${list.map((f) => `${f.id} (${f.kind})`).join(', ') || 'none'}; call get_smart_object.`);\n return i;\n}\n\nfunction editableSmart(layers: readonly ImageLayer[], id: string): SmartObjectLayer {\n const layer = requireSmartObject(layers, id);\n requireUnlocked(layer, 'all', 'change its smart filters');\n return layer;\n}\n\n/** Adds a smart filter at `index` (default the top). Returns the stack and the filter. */\nexport function addSmartFilter(\n layers: readonly ImageLayer[],\n id: string,\n filter: SmartFilter,\n canvas: SmartCanvas,\n index?: number\n): { layers: ImageLayer[]; layer: SmartObjectLayer; filter: SmartFilter } {\n const layer = editableSmart(layers, id);\n const stack = stackOf(layer);\n const filters = [...stack.filters];\n const at = index === undefined ? filters.length : Math.max(0, Math.min(filters.length, Math.floor(index)));\n filters.splice(at, 0, filter);\n const next = withStack(layer, { ...stack, filters }, canvas);\n return { layers: mapLayer(layers, id, () => next), layer: next, filter };\n}\n\n/** Changes one smart filter (settings, enabled, blend, opacity; validated). */\nexport function updateSmartFilter(\n layers: readonly ImageLayer[],\n id: string,\n filterId: string,\n changes: Record<string, unknown>,\n canvas: SmartCanvas\n): { layers: ImageLayer[]; layer: SmartObjectLayer; filter: SmartFilter } {\n const layer = editableSmart(layers, id);\n const i = requireFilter(layer, filterId);\n const current = layer.smartFilters!.filters[i];\n if ('kind' in changes && changes.kind !== current.kind) fail('A smart filter kind cannot change; remove it and add another.');\n let merged: SmartFilter;\n if (current.kind === 'psd') {\n const extra = Object.keys(changes).filter((k) => !['enabled', 'blend', 'opacity', 'kind'].includes(k));\n if (extra.length) fail(`\"${current.name}\" is a Photoshop filter we keep but do not model; only enabled, blend and opacity change. Unknown: ${extra.join(', ')}.`);\n const blend = changes.blend ?? current.blend;\n if (!isBlendMode(blend) || blend === 'pigment') fail(`Unknown blend mode \"${String(blend)}\" for a smart filter.`);\n const opacity = changes.opacity ?? current.opacity;\n if (!(typeof opacity === 'number' && opacity >= 0 && opacity <= 1)) fail('opacity must be a number from 0 to 1.');\n merged = { ...current, enabled: changes.enabled === undefined ? current.enabled : Boolean(changes.enabled), blend: blend as BlendMode, opacity: opacity as number };\n } else {\n merged = normalizeSmartFilter({ ...current, ...changes, kind: current.kind } as Record<string, unknown> & { kind: string }, current.id);\n }\n const filters = [...layer.smartFilters!.filters];\n filters[i] = merged;\n const next = withStack(layer, { ...layer.smartFilters!, filters }, canvas);\n return { layers: mapLayer(layers, id, () => next), layer: next, filter: merged };\n}\n\n/** Removes one smart filter. */\nexport function removeSmartFilter(layers: readonly ImageLayer[], id: string, filterId: string, canvas: SmartCanvas): { layers: ImageLayer[]; layer: SmartObjectLayer } {\n const layer = editableSmart(layers, id);\n requireFilter(layer, filterId);\n const stack = layer.smartFilters!;\n const filters = stack.filters.filter((f) => f.id !== filterId);\n // The filter mask goes with the last filter, as in Photoshop.\n const next = withStack(layer, filters.length ? { ...stack, filters } : stack.enabled ? undefined : { enabled: false, filters }, canvas);\n return { layers: mapLayer(layers, id, () => next), layer: next };\n}\n\n/** Moves one smart filter to `index` (0 = bottom, applied first). */\nexport function moveSmartFilter(layers: readonly ImageLayer[], id: string, filterId: string, index: number, canvas: SmartCanvas): { layers: ImageLayer[]; layer: SmartObjectLayer } {\n const layer = editableSmart(layers, id);\n const i = requireFilter(layer, filterId);\n const list = layer.smartFilters!.filters;\n if (!Number.isInteger(index) || index < 0 || index >= list.length) fail(`index must be a whole number from 0 to ${list.length - 1}.`);\n const filters = [...list];\n const [f] = filters.splice(i, 1);\n filters.splice(index, 0, f);\n const next = withStack(layer, { ...layer.smartFilters!, filters }, canvas);\n return { layers: mapLayer(layers, id, () => next), layer: next };\n}\n\n/** Turns the whole smart filter stack on or off (each filter keeps its own switch). */\nexport function setSmartFiltersEnabled(layers: readonly ImageLayer[], id: string, enabled: boolean, canvas: SmartCanvas): { layers: ImageLayer[]; layer: SmartObjectLayer } {\n const layer = editableSmart(layers, id);\n const next = withStack(layer, { ...stackOf(layer), enabled: Boolean(enabled) }, canvas);\n return { layers: mapLayer(layers, id, () => next), layer: next };\n}\n\n/** Sets or removes (`null`) the smart filter mask. */\nexport function setSmartFilterMask(layers: readonly ImageLayer[], id: string, mask: RasterMask | null, canvas: SmartCanvas): { layers: ImageLayer[]; layer: SmartObjectLayer } {\n const layer = editableSmart(layers, id);\n if (mask?.source) fail('A smart filter mask is painted, not generated; use a raster mask.');\n const stack = { ...stackOf(layer) };\n if (mask) stack.mask = mask;\n else delete stack.mask;\n const next = withStack(layer, stack, canvas);\n return { layers: mapLayer(layers, id, () => next), layer: next };\n}\n\n/** A smart object's filter mask surface replaced (painting it), rendered again. */\nexport function withFilterMaskSurface(layer: SmartObjectLayer, surface: PixelSurface, canvas: SmartCanvas): SmartObjectLayer {\n const stack = stackOf(layer);\n if (!stack.mask) fail(`Smart object \"${layer.name || layer.id}\" has no filter mask; add one with set_smart_filter_mask.`);\n return withStack(layer, { ...stack, mask: { ...stack.mask!, surface } }, canvas);\n}\n\n/** The transformed outline of a smart object's contents (document bounds, warp included). */\nexport function placementBounds(layer: Pick<SmartObjectLayer, 'contents' | 'placement'>): CropBox | null {\n return transformedBox(placementGeometry(layer));\n}\n","import { LATEST_PROCESS_VERSION } from '../color';\nimport type { DepthMaskOptions, ImageAdjustments, ImageTransform } from '../types';\n\n/** Neutral base adjustments, latest process (re-exported as `DEFAULT_ADJUSTMENTS`). */\nexport const DEFAULT_ADJUSTMENTS_VALUES: ImageAdjustments = {\n brightness: 0,\n contrast: 0,\n saturation: 0,\n exposure: 0,\n temperature: 0,\n tint: 0,\n blur: 0,\n opacity: 1,\n process: LATEST_PROCESS_VERSION,\n};\n\nexport const DEFAULT_TRANSFORM_VALUES: ImageTransform = {\n rotate: 0,\n flipH: false,\n flipV: false,\n};\n\nexport const DEFAULT_DEPTH_MASK_VALUES: DepthMaskOptions = {\n enabled: false,\n maskSource: null,\n mode: 'threshold',\n depthRange: [0, 255],\n softness: 10,\n invert: false,\n};\n","/**\n * Schema version 3 of the image editor document: the layer tree, with pixel layers and painted\n * masks stored as PNG assets (cropped to their content and placed by position), plus the\n * document-level crop, transform, output opacity, migrated blur and selection.\n *\n * Encoding is synchronous and exact (see `png.ts`). Each surface's encoding is memoised, so\n * saving an unchanged layer again costs nothing.\n */\n\nimport { normalizeAdjustmentLayer } from '../adjustment-layers';\nimport { isBlendMode, normalizeBlendMode } from '../blend';\nimport { normalizeProcessVersion } from '../color';\nimport type { Annotation, CropBox, DepthMaskOptions, ImageTransform } from '../types';\nimport type { SerializedSelection } from '../selection/mask';\nimport { DEFAULT_DEPTH_MASK_VALUES } from './defaults';\nimport {\n newLayerId,\n type PsdSmartFilter,\n type SmartContents,\n type SmartFilter,\n type SmartFilterStack,\n type SmartObjectLayer,\n type SmartPlacement,\n type AdjustmentLayerNode,\n type GroupLayer,\n type ImageLayer,\n type LayerBase,\n type LayerFilter,\n type LayerLocks,\n type PixelLayer,\n type RasterMask,\n} from './model';\nimport { base64ToBytes, bytesToBase64, decodePng, parseDataUrl, pngDataUrl } from '../../raster/png';\nimport { normalizeLayerEffects, type GlobalLight, type LayerEffect, type LayerEffects } from '../../raster/effects/model';\nimport type { EffectPattern } from '../../raster/effects/patterns';\nimport { contentBounds, emptySurface, readRegion, surfaceFromPixels, type TiledSurface } from '../../raster/surface';\nimport { isInterpolation } from '../../raster/transform';\nimport { isValidWarpMesh } from '../../geom/warp';\nimport type { Quad, WarpMesh } from '../../geom/types';\nimport { normalizePlacement, renderSmartObject } from './smart';\n\nexport const LAYERED_SCHEMA_VERSION = 3 as const;\n\n/** A stored raster mask. Pixels: `asset` (grey PNG) placed at `x`, `y`; elsewhere `defaultValue`. */\nexport interface SerializedRasterMask {\n defaultValue: 0 | 255;\n asset?: string;\n x?: number;\n y?: number;\n density?: number;\n feather?: number;\n enabled?: boolean;\n /** `false`: unlinked from the layer's pixels. */\n linked?: boolean;\n source?: DepthMaskOptions;\n}\n\ninterface SerializedLayerBase {\n id: string;\n name: string;\n visible: boolean;\n opacity: number;\n blend: string;\n clipped?: boolean;\n mask?: SerializedRasterMask;\n locks?: LayerLocks;\n effects?: SerializedLayerEffects;\n}\n\n/** A layer's effects: the effect list as JSON plus the PSD block they came from (base64). */\nexport interface SerializedLayerEffects {\n enabled: boolean;\n scale?: number;\n items: LayerEffect[];\n psd?: { key: string; data: string };\n}\n\nexport interface SerializedPixelLayer extends SerializedLayerBase {\n type: 'pixel';\n /** Asset id of the pixels (RGBA PNG) placed at `x`, `y` in layer space; missing when empty. */\n asset?: string;\n x?: number;\n y?: number;\n offset: { x: number; y: number };\n fillOpacity?: number;\n filters?: LayerFilter[];\n /** `'document'`: the pixels are the document's source image (no asset is stored). */\n source?: 'document';\n}\n\nexport interface SerializedAdjustmentLayer extends SerializedLayerBase {\n type: 'adjustment';\n kind: string;\n params: unknown;\n}\n\nexport interface SerializedGroupLayer extends SerializedLayerBase {\n type: 'group';\n children: SerializedLayer[];\n collapsed?: boolean;\n}\n\nexport interface SerializedAnnotationLayer extends SerializedLayerBase {\n type: 'annotation';\n annotation: Annotation;\n}\n\n/** A smart filter as stored (a Photoshop filter keeps its descriptor as base64). */\nexport type SerializedSmartFilter = (LayerFilter | (Omit<PsdSmartFilter, 'data'> & { data: string })) & { blend: string; opacity: number };\n\nexport interface SerializedSmartObjectLayer extends SerializedLayerBase {\n type: 'smart';\n /** Id of its entry in the document's `smartContents` (shared by linked instances). */\n contents: string;\n placement: { corners: Array<{ x: number; y: number }>; warp?: WarpMesh; interpolation?: string };\n smartFilters?: { enabled: boolean; filters: SerializedSmartFilter[]; mask?: SerializedRasterMask };\n /** The rendered pixels (RGBA PNG) at `x`, `y` in document space, so a load does not re-render. */\n asset?: string;\n x?: number;\n y?: number;\n fillOpacity?: number;\n /** The PSD placed-layer block kept for round trips (base64). */\n psd?: { key: string; data: string; placed?: string };\n}\n\n/** Smart object contents as stored once per document, referenced by id from every instance. */\nexport interface SerializedSmartContents {\n id: string;\n name: string;\n width: number;\n height: number;\n kind: 'raster' | 'document';\n /** Raster contents: RGBA PNG asset at (0, 0). */\n asset?: string;\n /** Document contents: the nested layers (their assets are in the document's `assets`). */\n layers?: SerializedLayer[];\n globalLight?: GlobalLight;\n patterns?: SerializedPattern[];\n /** The file the contents came from (base64), written back while unedited. */\n file?: { type: string; data: string };\n linked?: { path: string };\n}\n\nexport type SerializedLayer = SerializedPixelLayer | SerializedAdjustmentLayer | SerializedGroupLayer | SerializedAnnotationLayer | SerializedSmartObjectLayer;\n\n/** A version 3 document (layers). */\nexport interface SerializedLayeredImageEditorState {\n version: 3;\n /** Canvas size in pixels. */\n width: number;\n height: number;\n /** Bottom to top. */\n layers: SerializedLayer[];\n /** Asset id → PNG data URL (or an external reference, see `ImageEditorAssetStore`). */\n assets: Record<string, string>;\n activeLayerId: string | null;\n crop: CropBox | null;\n transform: ImageTransform;\n /** Export alpha, 0..1. */\n opacity: number;\n /** Blur migrated from a v1 / v2 document with adjustment layers (runs after the layers). */\n legacyOutputBlur?: number;\n selection?: SerializedSelection;\n /** Global light for layer effects (missing = 120°, 30°). */\n globalLight?: GlobalLight;\n /** The document's patterns for pattern effects, each an RGBA PNG data URL. */\n patterns?: SerializedPattern[];\n /** Contents of the smart objects (nested ones included), each stored once. */\n smartContents?: SerializedSmartContents[];\n}\n\nexport interface SerializedPattern {\n id: string;\n name: string;\n png: string;\n}\n\nexport function serializePatterns(patterns: readonly EffectPattern[]): SerializedPattern[] {\n return patterns.map((p) => ({ id: p.id, name: p.name, png: pngDataUrl(p.width, p.height, p.data) }));\n}\n\n/** Reads stored patterns, skipping (and reporting in `problems`) any that cannot be decoded. */\nexport function deserializePatterns(raw: unknown, problems: string[] = []): EffectPattern[] {\n if (!Array.isArray(raw)) return [];\n const out: EffectPattern[] = [];\n for (const p of raw) {\n if (!p || typeof p !== 'object' || typeof p.id !== 'string' || typeof p.png !== 'string') continue;\n try {\n const png = decodePngDataUrl(p.png, 4);\n out.push({ id: p.id, name: typeof p.name === 'string' ? p.name : p.id, width: png.width, height: png.height, data: png.data });\n } catch (e) {\n problems.push(`Pattern \"${p.name ?? p.id}\" could not be decoded (${e instanceof Error ? e.message : String(e)}).`);\n }\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Writing\n// ---------------------------------------------------------------------------\n\ninterface EncodedSurface {\n url: string;\n x: number;\n y: number;\n}\n\nconst encodedCache = new WeakMap<TiledSurface, EncodedSurface | null>();\n\n/** The surface's content (non-default pixels) as a PNG data URL and its position, or `null`. */\nexport function encodeSurface(surface: TiledSurface): EncodedSurface | null {\n if (encodedCache.has(surface)) return encodedCache.get(surface)!;\n const b = contentBounds(surface);\n let out: EncodedSurface | null = null;\n if (b) {\n const data = readRegion(surface, b);\n out = { url: pngDataUrl(b.width, b.height, data, surface.channels), x: b.x, y: b.y };\n }\n encodedCache.set(surface, out);\n return out;\n}\n\nexport interface SerializeContext {\n /** Adds an asset and returns its id. */\n addAsset(url: string, hint: string): string;\n /** Stores a smart object's contents (once per id). Without it, smart objects cannot be written. */\n addContents?(contents: SmartContents): void;\n}\n\nfunction baseFields(layer: ImageLayer, ctx: SerializeContext): SerializedLayerBase {\n const out: SerializedLayerBase = {\n id: layer.id,\n name: layer.name,\n visible: layer.visible,\n opacity: layer.opacity,\n blend: layer.blend,\n };\n if (layer.clipped) out.clipped = true;\n if (layer.mask) out.mask = serializeMask(layer.mask, `${layer.id}-mask`, ctx);\n if (layer.locks && Object.values(layer.locks).some(Boolean)) out.locks = { ...layer.locks };\n if (layer.effects) out.effects = serializeEffects(layer.effects);\n return out;\n}\n\nexport function serializeEffects(effects: LayerEffects): SerializedLayerEffects {\n const out: SerializedLayerEffects = { enabled: effects.enabled, items: JSON.parse(JSON.stringify(effects.items)) };\n if (effects.scale !== undefined && effects.scale !== 1) out.scale = effects.scale;\n if (effects.psd) out.psd = { key: effects.psd.key, data: bytesToBase64(effects.psd.data) };\n return out;\n}\n\n/** Reads stored effects (leniently: broken effects are dropped), or `undefined`. */\nexport function deserializeEffects(raw: unknown): LayerEffects | undefined {\n if (!raw || typeof raw !== 'object') return undefined;\n const r = raw as Record<string, unknown>;\n let psd: { key: string; data: Uint8Array } | undefined;\n const p = r.psd as Record<string, unknown> | undefined;\n if (p && typeof p.key === 'string' && typeof p.data === 'string') {\n try {\n psd = { key: p.key, data: base64ToBytes(p.data) };\n } catch {\n psd = undefined;\n }\n }\n return normalizeLayerEffects({ ...r, psd });\n}\n\nfunction serializeMask(mask: RasterMask, hint: string, ctx: SerializeContext): SerializedRasterMask {\n const out: SerializedRasterMask = { defaultValue: mask.defaultValue };\n if (!mask.source) {\n const enc = encodeSurface(mask.surface);\n if (enc) {\n out.asset = ctx.addAsset(enc.url, hint);\n out.x = enc.x;\n out.y = enc.y;\n }\n } else {\n out.source = { ...mask.source, depthRange: [...mask.source.depthRange] as [number, number] };\n }\n if (mask.density !== undefined && mask.density !== 1) out.density = mask.density;\n if (mask.feather) out.feather = mask.feather;\n if (mask.enabled === false) out.enabled = false;\n if (mask.linked === false) out.linked = false;\n return out;\n}\n\nexport function serializeLayer(layer: ImageLayer, ctx: SerializeContext): SerializedLayer {\n const base = baseFields(layer, ctx);\n switch (layer.type) {\n case 'pixel': {\n const out: SerializedPixelLayer = { ...base, type: 'pixel', offset: { ...layer.offset } };\n if (layer.source === 'document') out.source = 'document';\n else {\n const enc = encodeSurface(layer.surface);\n if (enc) {\n out.asset = ctx.addAsset(enc.url, layer.id);\n out.x = enc.x;\n out.y = enc.y;\n }\n }\n if (layer.fillOpacity !== undefined && layer.fillOpacity !== 1) out.fillOpacity = layer.fillOpacity;\n if (layer.filters?.length) out.filters = JSON.parse(JSON.stringify(layer.filters));\n return out;\n }\n case 'adjustment':\n return { ...base, type: 'adjustment', kind: layer.kind, params: JSON.parse(JSON.stringify(layer.params)) };\n case 'group': {\n const out: SerializedGroupLayer = { ...base, type: 'group', children: layer.children.map((c) => serializeLayer(c, ctx)) };\n if (layer.collapsed) out.collapsed = true;\n return out;\n }\n case 'annotation':\n return { ...base, type: 'annotation', annotation: JSON.parse(JSON.stringify(layer.annotation)) };\n case 'smart':\n return serializeSmartObject(layer, base, ctx);\n }\n}\n\nfunction serializeSmartFilters(stack: SmartFilterStack, layerId: string, ctx: SerializeContext): NonNullable<SerializedSmartObjectLayer['smartFilters']> {\n const out: NonNullable<SerializedSmartObjectLayer['smartFilters']> = {\n enabled: stack.enabled,\n filters: stack.filters.map((f) => (f.kind === 'psd' ? { ...f, data: bytesToBase64(f.data) } : JSON.parse(JSON.stringify(f)))),\n };\n if (stack.mask) out.mask = serializeMask(stack.mask, `${layerId}-filter-mask`, ctx);\n return out;\n}\n\nfunction serializeSmartObject(layer: SmartObjectLayer, base: SerializedLayerBase, ctx: SerializeContext): SerializedSmartObjectLayer {\n if (!ctx.addContents) throw new Error('Smart objects need a contents table to be written.');\n ctx.addContents(layer.contents);\n const p = layer.placement;\n const out: SerializedSmartObjectLayer = {\n ...base,\n type: 'smart',\n contents: layer.contents.id,\n placement: { corners: p.corners.map((c) => ({ x: c.x, y: c.y })) },\n };\n if (p.warp) out.placement.warp = JSON.parse(JSON.stringify(p.warp));\n if (p.interpolation && p.interpolation !== 'bicubic') out.placement.interpolation = p.interpolation;\n if (layer.smartFilters) out.smartFilters = serializeSmartFilters(layer.smartFilters, layer.id, ctx);\n const enc = encodeSurface(layer.surface);\n if (enc) {\n out.asset = ctx.addAsset(enc.url, layer.id);\n out.x = enc.x;\n out.y = enc.y;\n }\n if (layer.fillOpacity !== undefined && layer.fillOpacity !== 1) out.fillOpacity = layer.fillOpacity;\n if (layer.psd) {\n out.psd = { key: layer.psd.key, data: bytesToBase64(layer.psd.data) };\n if (layer.psd.placed) out.psd.placed = layer.psd.placed;\n }\n return out;\n}\n\n/** Writes one contents entry (nested layers add their own assets and contents to `ctx`). */\nexport function serializeContents(c: SmartContents, ctx: SerializeContext): SerializedSmartContents {\n const out: SerializedSmartContents = { id: c.id, name: c.name, width: c.width, height: c.height, kind: c.kind };\n if (c.kind === 'raster' && c.raster) {\n const enc = encodeSurface(c.raster);\n if (enc) {\n if (enc.x !== 0 || enc.y !== 0) {\n // Contents always start at (0, 0): store the full rectangle.\n const data = readRegion(c.raster, { x: 0, y: 0, width: c.width, height: c.height });\n out.asset = ctx.addAsset(pngDataUrl(c.width, c.height, data, 4), `contents-${c.id}`);\n } else out.asset = ctx.addAsset(enc.url, `contents-${c.id}`);\n }\n }\n if (c.kind === 'document' && c.document) {\n out.layers = c.document.layers.map((l) => serializeLayer(l, ctx));\n if (c.document.globalLight) out.globalLight = { ...c.document.globalLight };\n if (c.document.patterns?.length) out.patterns = serializePatterns(c.document.patterns);\n }\n if (c.file) out.file = { type: c.file.type, data: bytesToBase64(c.file.bytes) };\n if (c.linked) out.linked = { path: c.linked.path };\n return out;\n}\n\n/**\n * Serializes a layer tree with inline assets (asset ids are derived from layer ids). Smart object\n * contents go to `smartContents`, once each (present only when there are smart objects).\n */\nexport function serializeLayers(layers: readonly ImageLayer[]): { layers: SerializedLayer[]; assets: Record<string, string>; smartContents?: SerializedSmartContents[] } {\n const assets: Record<string, string> = {};\n const contents = new Map<string, SerializedSmartContents | null>();\n const ctx: SerializeContext = {\n addAsset(url, hint) {\n let id = hint;\n for (let n = 2; id in assets && assets[id] !== url; n++) id = `${hint}-${n}`;\n assets[id] = url;\n return id;\n },\n addContents(c) {\n if (contents.has(c.id)) return;\n // Reserve the id first: contents can hold instances of themselves only through a cycle, which is cut.\n contents.set(c.id, null);\n contents.set(c.id, serializeContents(c, ctx));\n },\n };\n const out = layers.map((l) => serializeLayer(l, ctx));\n const list = [...contents.values()].filter(Boolean) as SerializedSmartContents[];\n return list.length ? { layers: out, assets, smartContents: list } : { layers: out, assets };\n}\n\n// ---------------------------------------------------------------------------\n// External assets\n// ---------------------------------------------------------------------------\n\n/**\n * Where a host keeps layer pixels outside the JSON (`toJSON({ assets: 'external', store })`):\n * `put` stores a PNG and returns a reference (a URL, a key) that `get` resolves later.\n */\nexport interface ImageEditorAssetStore {\n put(blob: Blob, info: { id: string; width: number; height: number }): Promise<string>;\n get(ref: string): Promise<Blob>;\n}\n\nconst externalRefs = new WeakMap<ImageEditorAssetStore, Map<string, string>>();\n\n/**\n * Serializes a layer tree with its assets in `store`: each PNG is uploaded once per store (by\n * content), and `assets` maps asset ids to the store's references.\n */\nexport async function serializeLayersExternal(\n layers: readonly ImageLayer[],\n store: ImageEditorAssetStore\n): Promise<{ layers: SerializedLayer[]; assets: Record<string, string>; smartContents?: SerializedSmartContents[] }> {\n const inline = serializeLayers(layers);\n let refs = externalRefs.get(store);\n if (!refs) {\n refs = new Map();\n externalRefs.set(store, refs);\n }\n const assets: Record<string, string> = {};\n for (const [id, url] of Object.entries(inline.assets)) {\n let ref = refs.get(url);\n if (!ref) {\n const parsed = parseDataUrl(url)!;\n const png = decodePngHeader(parsed.bytes);\n ref = await store.put(new Blob([parsed.bytes as BlobPart], { type: 'image/png' }), { id, width: png.width, height: png.height });\n if (typeof ref !== 'string' || !ref) throw new Error(`The asset store returned no reference for \"${id}\".`);\n refs.set(url, ref);\n }\n assets[id] = ref;\n }\n return inline.smartContents ? { layers: inline.layers, assets, smartContents: inline.smartContents } : { layers: inline.layers, assets };\n}\n\n/** Width and height from a PNG's header. */\nfunction decodePngHeader(bytes: Uint8Array): { width: number; height: number } {\n const u32 = (o: number) => ((bytes[o] << 24) | (bytes[o + 1] << 16) | (bytes[o + 2] << 8) | bytes[o + 3]) >>> 0;\n return { width: u32(16), height: u32(20) };\n}\n\n/**\n * Turns external asset references back into data URLs through `store.get` (data URLs pass through),\n * so the document loads like an inline one. References that fail are reported and left out.\n */\nexport async function resolveExternalAssets(\n assets: Record<string, string>,\n store: ImageEditorAssetStore | undefined,\n problems: string[]\n): Promise<Record<string, string>> {\n const out: Record<string, string> = {};\n for (const [id, ref] of Object.entries(assets)) {\n if (ref.startsWith('data:')) {\n out[id] = ref;\n continue;\n }\n if (!store) {\n problems.push(`Asset \"${id}\" is external (${ref}) and no asset store was given.`);\n continue;\n }\n try {\n const blob = await store.get(ref);\n const bytes = new Uint8Array(await blob.arrayBuffer());\n out[id] = `data:image/png;base64,${bytesToBase64(bytes)}`;\n } catch (err) {\n problems.push(`Asset \"${id}\" (${ref}) could not be fetched: ${(err as Error).message}`);\n }\n }\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Reading\n// ---------------------------------------------------------------------------\n\nexport interface DeserializeContext {\n assets: Record<string, string>;\n /** The stored `smartContents` table (smart objects reference it by id). */\n smartContents?: unknown[];\n /** Canvas size, for re-rendering a smart object stored without its pixels (default 1 × 1). */\n canvas?: { width: number; height: number };\n /** @internal Contents read so far by id (`null` while being read). */\n contentsMemo?: Map<string, SmartContents | null>;\n /** Decodes an asset (PNG data URL by default). Throw for an unusable asset. */\n decodeAsset?(ref: string, channels: 1 | 4): { width: number; height: number; data: ArrayLike<number> };\n /** Problems found while reading (unknown kinds, broken assets); the rest still loads. */\n problems: string[];\n}\n\n/** Decodes a PNG data URL into pixels of `channels` bytes each. */\nexport function decodePngDataUrl(url: string, channels: 1 | 4): { width: number; height: number; data: Uint8ClampedArray } {\n const parsed = parseDataUrl(url);\n if (!parsed) throw new Error('Asset is not a data URL.');\n const png = decodePng(parsed.bytes, { keepGray: channels === 1 });\n if (png.channels === channels) return png;\n if (channels === 1) {\n // An RGBA PNG used as a mask: take its luminance (alpha-weighted).\n const out = new Uint8ClampedArray(png.width * png.height);\n for (let i = 0, p = 0; p < out.length; i += 4, p++) {\n out[p] = Math.round(((0.299 * png.data[i] + 0.587 * png.data[i + 1] + 0.114 * png.data[i + 2]) * png.data[i + 3]) / 255);\n }\n return { width: png.width, height: png.height, data: out };\n }\n const out = new Uint8ClampedArray(png.width * png.height * 4);\n for (let p = 0; p < png.width * png.height; p++) {\n out[p * 4] = out[p * 4 + 1] = out[p * 4 + 2] = png.data[p];\n out[p * 4 + 3] = 255;\n }\n return { width: png.width, height: png.height, data: out };\n}\n\nconst num = (v: unknown, lo: number, hi: number, fallback: number) =>\n typeof v === 'number' && Number.isFinite(v) ? Math.min(hi, Math.max(lo, v)) : fallback;\n\nfunction readSurface(ref: unknown, x: unknown, y: unknown, channels: 1 | 4, defaultValue: number, ctx: DeserializeContext, what: string): TiledSurface {\n if (typeof ref !== 'string' || !ref) return emptySurface(channels, defaultValue);\n const url = ctx.assets[ref];\n if (typeof url !== 'string') {\n ctx.problems.push(`${what}: asset \"${ref}\" is missing`);\n return emptySurface(channels, defaultValue);\n }\n try {\n const px = (ctx.decodeAsset ?? decodePngDataUrl)(url, channels);\n return surfaceFromPixels(px, channels, Math.round(num(x, -1e7, 1e7, 0)), Math.round(num(y, -1e7, 1e7, 0)), defaultValue);\n } catch (err) {\n ctx.problems.push(`${what}: asset \"${ref}\" could not be decoded (${(err as Error).message})`);\n return emptySurface(channels, defaultValue);\n }\n}\n\nfunction readMask(raw: unknown, ctx: DeserializeContext, what: string): RasterMask | undefined {\n if (!raw || typeof raw !== 'object') return undefined;\n const r = raw as Record<string, unknown>;\n const defaultValue: 0 | 255 = r.defaultValue === 255 ? 255 : 0;\n const mask: RasterMask = {\n surface: r.source ? emptySurface(1, 255) : readSurface(r.asset, r.x, r.y, 1, defaultValue, ctx, `${what} mask`),\n defaultValue: r.source ? 255 : defaultValue,\n };\n if (typeof r.density === 'number' && r.density !== 1) mask.density = num(r.density, 0, 1, 1);\n if (typeof r.feather === 'number' && r.feather > 0) mask.feather = num(r.feather, 0, 1000, 0);\n if (r.enabled === false) mask.enabled = false;\n if (r.linked === false) mask.linked = false;\n if (r.source && typeof r.source === 'object') {\n const s = { ...DEFAULT_DEPTH_MASK_VALUES, ...(r.source as Partial<DepthMaskOptions>) };\n mask.source = { ...s, depthRange: [...s.depthRange] as [number, number] };\n }\n return mask;\n}\n\nfunction readFilters(raw: unknown): LayerFilter[] | undefined {\n if (!Array.isArray(raw)) return undefined;\n const out: LayerFilter[] = [];\n for (const f of raw) {\n if (!f || typeof f !== 'object') continue;\n const r = f as Record<string, unknown>;\n const id = typeof r.id === 'string' && r.id ? r.id : newLayerId('flt');\n const enabled = r.enabled !== false;\n if (r.kind === 'blur') out.push({ id, kind: 'blur', enabled, radius: num(r.radius, 0, 50, 0) });\n else if (r.kind === 'develop') {\n out.push({\n id,\n kind: 'develop',\n enabled,\n brightness: num(r.brightness, -100, 100, 0),\n contrast: num(r.contrast, -100, 100, 0),\n saturation: num(r.saturation, -100, 100, 0),\n exposure: num(r.exposure, -100, 100, 0),\n temperature: num(r.temperature, -100, 100, 0),\n tint: num(r.tint, -100, 100, 0),\n process: normalizeProcessVersion(r.process),\n });\n }\n }\n return out.length ? out : undefined;\n}\n\nexport function deserializeLayer(raw: unknown, ctx: DeserializeContext): ImageLayer | null {\n if (!raw || typeof raw !== 'object') return null;\n const r = raw as Record<string, any>;\n const id = typeof r.id === 'string' && r.id ? r.id : newLayerId();\n const base: LayerBase = {\n id,\n name: typeof r.name === 'string' ? r.name : '',\n visible: r.visible !== false,\n opacity: num(r.opacity, 0, 1, 1),\n blend: normalizeBlendMode(r.blend),\n };\n if (r.clipped === true) base.clipped = true;\n const mask = readMask(r.mask, ctx, `Layer \"${base.name || id}\"`);\n if (mask) base.mask = mask;\n if (r.locks && typeof r.locks === 'object') {\n const locks: LayerLocks = {};\n for (const k of ['pixels', 'transparency', 'position', 'all'] as const) if (r.locks[k] === true) locks[k] = true;\n if (Object.keys(locks).length) base.locks = locks;\n }\n const effects = deserializeEffects(r.effects);\n if (effects && r.type !== 'adjustment') base.effects = effects;\n switch (r.type) {\n case 'pixel': {\n const layer: PixelLayer = {\n ...base,\n type: 'pixel',\n surface: r.source === 'document' ? emptySurface(4) : readSurface(r.asset, r.x, r.y, 4, 0, ctx, `Layer \"${base.name || id}\"`),\n offset: { x: Math.round(num(r.offset?.x, -1e7, 1e7, 0)), y: Math.round(num(r.offset?.y, -1e7, 1e7, 0)) },\n };\n if (r.source === 'document') layer.source = 'document';\n if (typeof r.fillOpacity === 'number' && r.fillOpacity !== 1) layer.fillOpacity = num(r.fillOpacity, 0, 1, 1);\n const filters = readFilters(r.filters);\n if (filters) layer.filters = filters;\n return layer;\n }\n case 'adjustment': {\n const norm = normalizeAdjustmentLayer({ id, kind: r.kind, params: r.params });\n if (!norm) {\n ctx.problems.push(`Layer \"${base.name || id}\": unknown adjustment kind \"${r.kind}\"`);\n return null;\n }\n return { ...base, type: 'adjustment', kind: norm.kind, params: norm.params } as AdjustmentLayerNode;\n }\n case 'group': {\n const children = Array.isArray(r.children) ? r.children.map((c: unknown) => deserializeLayer(c, ctx)).filter(Boolean) : [];\n const blend = r.blend === 'pass-through' ? 'pass-through' : isBlendMode(r.blend) ? r.blend : 'pass-through';\n const group: GroupLayer = { ...base, type: 'group', blend, children: children as ImageLayer[] };\n if (r.collapsed === true) group.collapsed = true;\n return group;\n }\n case 'annotation': {\n const a = r.annotation;\n if (!a || typeof a !== 'object' || typeof a.type !== 'string') {\n ctx.problems.push(`Layer \"${base.name || id}\": annotation is missing`);\n return null;\n }\n return { ...base, type: 'annotation', annotation: JSON.parse(JSON.stringify({ ...a, id: typeof a.id === 'string' ? a.id : id })) };\n }\n case 'smart':\n return readSmartObject(r, base, ctx);\n default:\n ctx.problems.push(`Layer \"${base.name || id}\": unknown layer type \"${r.type}\"`);\n return null;\n }\n}\n\n/** Reads contents `id` from the table (memoised; a contents that contains itself is refused). */\nfunction readContents(id: string, ctx: DeserializeContext): SmartContents | null {\n const memo = (ctx.contentsMemo ??= new Map());\n if (memo.has(id)) {\n const c = memo.get(id);\n if (c === null) ctx.problems.push(`Smart object contents \"${id}\" contain themselves; the inner instance was dropped`);\n return c ?? null;\n }\n const raw = (ctx.smartContents ?? []).find((c) => c && typeof c === 'object' && (c as { id?: unknown }).id === id) as Record<string, any> | undefined;\n if (!raw) {\n ctx.problems.push(`Smart object contents \"${id}\" are missing`);\n return null;\n }\n const width = Math.round(num(raw.width, 0, 300000, 0));\n const height = Math.round(num(raw.height, 0, 300000, 0));\n if (!(width > 0 && height > 0)) {\n ctx.problems.push(`Smart object contents \"${id}\" have no size`);\n return null;\n }\n memo.set(id, null);\n const c: SmartContents = { id, name: typeof raw.name === 'string' ? raw.name : 'Contents', width, height, kind: raw.kind === 'document' ? 'document' : 'raster' };\n if (raw.file && typeof raw.file.type === 'string' && typeof raw.file.data === 'string') {\n try {\n c.file = { type: raw.file.type, bytes: base64ToBytes(raw.file.data) };\n } catch {\n ctx.problems.push(`Smart object contents \"${c.name}\": the embedded file could not be read`);\n }\n }\n if (raw.linked && typeof raw.linked.path === 'string') c.linked = { path: raw.linked.path };\n if (c.kind === 'raster') {\n if (typeof raw.asset === 'string') c.raster = readSurface(raw.asset, 0, 0, 4, 0, ctx, `Smart object contents \"${c.name}\"`);\n else if (!c.file) c.raster = emptySurface(4);\n } else {\n const outer = ctx.canvas;\n ctx.canvas = { width, height };\n const layers = deserializeLayers(raw.layers, ctx);\n ctx.canvas = outer;\n c.document = { layers };\n if (raw.globalLight && typeof raw.globalLight === 'object') c.document.globalLight = { angle: num(raw.globalLight.angle, -360, 360, 120), altitude: num(raw.globalLight.altitude, 0, 90, 30) };\n const patterns = deserializePatterns(raw.patterns, ctx.problems);\n if (patterns.length) c.document.patterns = patterns;\n }\n memo.set(id, c);\n return c;\n}\n\nfunction readSmartFilters(raw: unknown, ctx: DeserializeContext, what: string): SmartFilterStack | undefined {\n if (!raw || typeof raw !== 'object') return undefined;\n const r = raw as Record<string, any>;\n const filters: SmartFilter[] = [];\n const list = Array.isArray(r.filters) ? r.filters : [];\n const plain = readFilters(list.filter((f: any) => f && f.kind !== 'psd')) ?? [];\n let k = 0;\n for (const f of list) {\n if (!f || typeof f !== 'object') continue;\n const blend = isBlendMode(f.blend) && f.blend !== 'pigment' ? f.blend : 'normal';\n const opacity = num(f.opacity, 0, 1, 1);\n if (f.kind === 'psd') {\n try {\n filters.push({ id: typeof f.id === 'string' ? f.id : newLayerId('flt'), kind: 'psd', enabled: f.enabled !== false, name: typeof f.name === 'string' ? f.name : 'Filter', data: base64ToBytes(String(f.data ?? '')), blend, opacity });\n } catch {\n ctx.problems.push(`${what}: a Photoshop smart filter could not be read`);\n }\n } else if (f.kind === 'develop' || f.kind === 'blur') {\n const base = plain[k++];\n if (base) filters.push({ ...base, blend, opacity });\n }\n }\n const stack: SmartFilterStack = { enabled: r.enabled !== false, filters };\n const mask = readMask(r.mask, ctx, `${what} filter`);\n if (mask) stack.mask = mask;\n return stack;\n}\n\nfunction readSmartObject(r: Record<string, any>, base: LayerBase, ctx: DeserializeContext): SmartObjectLayer | null {\n const what = `Layer \"${base.name || base.id}\"`;\n const contents = typeof r.contents === 'string' ? readContents(r.contents, ctx) : null;\n if (!contents) {\n ctx.problems.push(`${what}: its smart object contents are missing; the layer was dropped`);\n return null;\n }\n const p = r.placement ?? {};\n let placement: SmartPlacement;\n try {\n const raw: SmartPlacement = { corners: p.corners as Quad };\n if (p.warp && isValidWarpMesh(p.warp)) raw.warp = p.warp;\n if (isInterpolation(p.interpolation)) raw.interpolation = p.interpolation;\n placement = normalizePlacement(raw, contents);\n } catch (e) {\n ctx.problems.push(`${what}: its placement is invalid (${e instanceof Error ? e.message : String(e)}); it was placed at the top left`);\n placement = { corners: [{ x: 0, y: 0 }, { x: contents.width, y: 0 }, { x: contents.width, y: contents.height }, { x: 0, y: contents.height }] };\n }\n const layer: SmartObjectLayer = { ...base, type: 'smart', contents, placement, surface: emptySurface(4), offset: { x: 0, y: 0 } };\n const filters = readSmartFilters(r.smartFilters, ctx, what);\n if (filters) layer.smartFilters = filters;\n if (typeof r.fillOpacity === 'number' && r.fillOpacity !== 1) layer.fillOpacity = num(r.fillOpacity, 0, 1, 1);\n if (r.psd && typeof r.psd.key === 'string' && typeof r.psd.data === 'string') {\n try {\n layer.psd = { key: r.psd.key, data: base64ToBytes(r.psd.data) };\n if (typeof r.psd.placed === 'string') layer.psd.placed = r.psd.placed;\n } catch {\n // A damaged PSD block is regenerated on export.\n }\n }\n if (typeof r.asset === 'string') layer.surface = readSurface(r.asset, r.x, r.y, 4, 0, ctx, what);\n else if (contents.raster || contents.document) layer.surface = renderSmartObject(layer, ctx.canvas ?? { width: 1, height: 1 });\n return layer;\n}\n\n/** Reads a stored layer tree (layers that cannot be read are dropped and reported). */\nexport function deserializeLayers(raw: unknown, ctx: DeserializeContext): ImageLayer[] {\n if (!Array.isArray(raw)) return [];\n const seen = new Set<string>();\n const dedupe = (list: ImageLayer[]): ImageLayer[] =>\n list.map((l) => {\n let next = l;\n if (seen.has(l.id)) next = { ...l, id: newLayerId() } as ImageLayer;\n seen.add(next.id);\n if (next.type === 'group') next = { ...next, children: dedupe(next.children) };\n return next;\n });\n return dedupe(raw.map((l) => deserializeLayer(l, ctx)).filter(Boolean) as ImageLayer[]);\n}\n","// Portions ported from photocraft/crates/psd/src/io.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\n/** A malformed or unsupported PSD; the message says what was wrong and where. */\nexport class PsdError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'PsdError';\n }\n}\n\n/** Big-endian reader over a byte range that never reads past its end (it throws `PsdError`). */\nexport class PsdReader {\n pos: number;\n\n constructor(\n readonly data: Uint8Array,\n private readonly start = 0,\n readonly end = data.length\n ) {\n this.pos = start;\n }\n\n get remaining(): number {\n return this.end - this.pos;\n }\n\n get offset(): number {\n return this.pos - this.start;\n }\n\n private need(n: number, what: string): void {\n if (n < 0 || this.pos + n > this.end) throw new PsdError(`Unexpected end of data reading ${what} at byte ${this.pos}.`);\n }\n\n u8(): number {\n this.need(1, 'a byte');\n return this.data[this.pos++];\n }\n\n i8(): number {\n const v = this.u8();\n return v > 127 ? v - 256 : v;\n }\n\n u16(): number {\n this.need(2, 'a 16-bit value');\n const v = (this.data[this.pos] << 8) | this.data[this.pos + 1];\n this.pos += 2;\n return v;\n }\n\n i16(): number {\n const v = this.u16();\n return v > 32767 ? v - 65536 : v;\n }\n\n u32(): number {\n this.need(4, 'a 32-bit value');\n const d = this.data;\n const p = this.pos;\n this.pos += 4;\n return ((d[p] << 24) | (d[p + 1] << 16) | (d[p + 2] << 8) | d[p + 3]) >>> 0;\n }\n\n i32(): number {\n return this.u32() | 0;\n }\n\n u64(): number {\n const hi = this.u32();\n const lo = this.u32();\n const v = hi * 2 ** 32 + lo;\n if (!Number.isSafeInteger(v)) throw new PsdError('A 64-bit length is too large.');\n return v;\n }\n\n i64(): number {\n const hi = this.i32();\n const lo = this.u32();\n return hi * 2 ** 32 + lo;\n }\n\n f32(): number {\n this.need(4, 'a float');\n const v = new DataView(this.data.buffer, this.data.byteOffset + this.pos, 4).getFloat32(0, false);\n this.pos += 4;\n return v;\n }\n\n f64(): number {\n this.need(8, 'a double');\n const v = new DataView(this.data.buffer, this.data.byteOffset + this.pos, 8).getFloat64(0, false);\n this.pos += 8;\n return v;\n }\n\n /** A length field: 8 bytes in PSB where the format says so, else 4. */\n len(long: boolean): number {\n return long ? this.u64() : this.u32();\n }\n\n bytes(n: number): Uint8Array {\n this.need(n, `${n} bytes`);\n const out = this.data.subarray(this.pos, this.pos + n);\n this.pos += n;\n return out;\n }\n\n ascii(n: number): string {\n return String.fromCharCode(...this.bytes(n));\n }\n\n skip(n: number): void {\n this.need(n, `${n} bytes`);\n this.pos += n;\n }\n\n /** A reader over the next `n` bytes (and skips them here). */\n sub(n: number): PsdReader {\n this.need(n, `a ${n}-byte section`);\n const r = new PsdReader(this.data, this.pos, this.pos + n);\n this.pos += n;\n return r;\n }\n\n peek(n: number): Uint8Array {\n return this.data.subarray(this.pos, Math.min(this.end, this.pos + n));\n }\n\n rest(): Uint8Array {\n const out = this.data.subarray(this.pos, this.end);\n this.pos = this.end;\n return out;\n }\n\n /** Refuses counts that could not fit in what is left (each item at least `size` bytes). */\n checkCount(n: number, size: number, what: string): void {\n if (n * size > this.remaining) throw new PsdError(`${what}: count ${n} does not fit in the remaining ${this.remaining} bytes.`);\n }\n\n /** Pascal string padded to a multiple of `align` (length byte included). */\n pascal(align: number): Uint8Array {\n const n = this.u8();\n const s = this.bytes(n);\n const pad = (align - ((1 + n) % align)) % align;\n this.skip(Math.min(pad, this.remaining));\n return s;\n }\n\n /** Photoshop Unicode string: u32 code-unit count, UTF-16BE units (trailing NULs dropped). */\n unicode(): string {\n const n = this.u32();\n this.checkCount(n, 2, 'Unicode string');\n let s = '';\n for (let i = 0; i < n; i++) s += String.fromCharCode(this.u16());\n return s.replace(/\\u0000+$/, '');\n }\n}\n\n/** A growable big-endian writer. */\nexport class PsdWriter {\n private buf = new Uint8Array(1 << 16);\n length = 0;\n\n private ensure(n: number): void {\n if (this.length + n <= this.buf.length) return;\n let size = this.buf.length * 2;\n while (size < this.length + n) size *= 2;\n const next = new Uint8Array(size);\n next.set(this.buf.subarray(0, this.length));\n this.buf = next;\n }\n\n u8(v: number): void {\n this.ensure(1);\n this.buf[this.length++] = v & 255;\n }\n\n u16(v: number): void {\n this.ensure(2);\n this.buf[this.length++] = (v >>> 8) & 255;\n this.buf[this.length++] = v & 255;\n }\n\n i16(v: number): void {\n this.u16(v < 0 ? v + 65536 : v);\n }\n\n u32(v: number): void {\n this.ensure(4);\n this.buf[this.length++] = (v >>> 24) & 255;\n this.buf[this.length++] = (v >>> 16) & 255;\n this.buf[this.length++] = (v >>> 8) & 255;\n this.buf[this.length++] = v & 255;\n }\n\n i32(v: number): void {\n this.u32(v >>> 0);\n }\n\n u64(v: number): void {\n this.u32(Math.floor(v / 2 ** 32));\n this.u32(v >>> 0);\n }\n\n f64(v: number): void {\n this.ensure(8);\n new DataView(this.buf.buffer, this.length, 8).setFloat64(0, v, false);\n this.length += 8;\n }\n\n bytes(b: ArrayLike<number>): void {\n this.ensure(b.length);\n this.buf.set(b as Uint8Array, this.length);\n this.length += b.length;\n }\n\n ascii(s: string): void {\n for (let i = 0; i < s.length; i++) this.u8(s.charCodeAt(i));\n }\n\n zeros(n: number): void {\n this.ensure(n);\n this.buf.fill(0, this.length, this.length + n);\n this.length += n;\n }\n\n /** Reserves a length field; `endLen` fills it with the bytes written since. */\n beginLen(long: boolean): { at: number; long: boolean } {\n const at = this.length;\n if (long) this.u64(0);\n else this.u32(0);\n return { at, long };\n }\n\n endLen(mark: { at: number; long: boolean }): number {\n const n = this.length - mark.at - (mark.long ? 8 : 4);\n const save = this.length;\n this.length = mark.at;\n if (mark.long) this.u64(n);\n else this.u32(n);\n this.length = save;\n return n;\n }\n\n /** Pascal string padded to a multiple of `align`. */\n pascal(s: Uint8Array, align: number): void {\n const n = Math.min(255, s.length);\n this.u8(n);\n this.bytes(s.subarray(0, n));\n this.zeros((align - ((1 + n) % align)) % align);\n }\n\n unicode(s: string, nul = false): void {\n const n = s.length + (nul ? 1 : 0);\n this.u32(n);\n for (let i = 0; i < s.length; i++) this.u16(s.charCodeAt(i));\n if (nul) this.u16(0);\n }\n\n result(): Uint8Array {\n return this.buf.slice(0, this.length);\n }\n}\n\n/** A legacy (Pascal) name: UTF-8 when valid, else Latin-1. */\nexport function decodeLegacyName(b: Uint8Array): string {\n try {\n return new TextDecoder('utf-8', { fatal: true }).decode(b);\n } catch {\n return String.fromCharCode(...b);\n }\n}\n\n/** Encodes a name for a legacy Pascal field: non-ASCII characters become `?`. */\nexport function encodeLegacyName(s: string): Uint8Array {\n const out: number[] = [];\n for (const ch of s) {\n const c = ch.codePointAt(0)!;\n out.push(c >= 32 && c < 127 ? c : 63);\n if (out.length >= 255) break;\n }\n return Uint8Array.from(out);\n}\n","// Portions ported from photocraft/crates/psd/src/descriptor.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\n/**\n * Photoshop ActionDescriptors (the structured values inside `vibA`, `blwh` and other blocks):\n * read and write. Keys and class ids are 4-character codes, or longer strings.\n */\n\nimport { PsdError, PsdReader, PsdWriter } from './binary';\n\nconst MAX_DEPTH = 64;\n/** Ids that are 4 characters long but stored as strings, never codes. */\nconst STRING_IDS = new Set(['warp', 'list', 'time']);\n\nexport interface DescClass {\n name: string;\n classId: string;\n}\n\nexport type ReferenceItem =\n | { type: 'prop'; class: DescClass; key: string }\n | { type: 'Clss'; class: DescClass }\n | { type: 'Enmr'; class: DescClass; typeId: string; value: string }\n | { type: 'rele'; class: DescClass; offset: number }\n | { type: 'Idnt'; class: DescClass; id: number }\n | { type: 'indx'; class: DescClass; index: number }\n | { type: 'name'; class: DescClass; name: string };\n\nexport type DescValue =\n | { type: 'obj '; items: ReferenceItem[] }\n | { type: 'Objc' | 'GlbO'; value: Descriptor }\n | { type: 'VlLs'; items: DescValue[] }\n | { type: 'doub'; value: number }\n | { type: 'UntF'; unit: string; value: number }\n | { type: 'UnFl'; unit: string; values: number[] }\n | { type: 'TEXT'; value: string }\n | { type: 'enum'; typeId: string; value: string }\n | { type: 'long'; value: number }\n | { type: 'comp'; value: number }\n | { type: 'bool'; value: boolean }\n | { type: 'type' | 'GlbC'; class: DescClass }\n | { type: 'alis' | 'Pth ' | 'tdta'; data: Uint8Array }\n | { type: 'ObAr'; prefix: number; body: Descriptor };\n\nexport interface Descriptor {\n name: string;\n classId: string;\n items: Array<[string, DescValue]>;\n}\n\nexport function descriptor(classId: string, items: Array<[string, DescValue]> = []): Descriptor {\n return { name: '', classId, items };\n}\n\nexport function descGet(d: Descriptor, key: string): DescValue | undefined {\n return d.items.find(([k]) => k === key)?.[1];\n}\n\n/** A number value (long, double or unit float), or `undefined`. */\nexport function descNumber(d: Descriptor, key: string): number | undefined {\n const v = descGet(d, key);\n if (!v) return undefined;\n if (v.type === 'long' || v.type === 'doub' || v.type === 'UntF' || v.type === 'comp') return v.value;\n return undefined;\n}\n\nexport function descBool(d: Descriptor, key: string): boolean | undefined {\n const v = descGet(d, key);\n return v?.type === 'bool' ? v.value : undefined;\n}\n\nexport function descObject(d: Descriptor, key: string): Descriptor | undefined {\n const v = descGet(d, key);\n return v && (v.type === 'Objc' || v.type === 'GlbO') ? v.value : undefined;\n}\n\nfunction readId(r: PsdReader): string {\n const n = r.u32();\n return n === 0 ? r.ascii(4) : r.ascii(n);\n}\n\nfunction writeId(w: PsdWriter, id: string): void {\n if (id.length === 4 && !STRING_IDS.has(id)) {\n w.u32(0);\n w.ascii(id);\n } else {\n w.u32(id.length);\n w.ascii(id);\n }\n}\n\nfunction readClass(r: PsdReader): DescClass {\n return { name: r.unicode(), classId: readId(r) };\n}\n\nfunction writeClass(w: PsdWriter, c: DescClass): void {\n w.unicode(c.name);\n writeId(w, c.classId);\n}\n\nfunction readReferenceItem(r: PsdReader): ReferenceItem {\n const type = r.ascii(4);\n switch (type) {\n case 'prop':\n return { type, class: readClass(r), key: readId(r) };\n case 'Clss':\n return { type, class: readClass(r) };\n case 'Enmr':\n return { type, class: readClass(r), typeId: readId(r), value: readId(r) };\n case 'rele':\n return { type, class: readClass(r), offset: r.i32() };\n case 'Idnt':\n return { type, class: readClass(r), id: r.u32() };\n case 'indx':\n return { type, class: readClass(r), index: r.u32() };\n case 'name':\n return { type, class: readClass(r), name: r.unicode() };\n default:\n throw new PsdError(`Unsupported descriptor reference item \"${type}\".`);\n }\n}\n\nfunction writeReferenceItem(w: PsdWriter, it: ReferenceItem): void {\n w.ascii(it.type);\n writeClass(w, it.class);\n switch (it.type) {\n case 'prop':\n writeId(w, it.key);\n break;\n case 'Enmr':\n writeId(w, it.typeId);\n writeId(w, it.value);\n break;\n case 'rele':\n w.i32(it.offset);\n break;\n case 'Idnt':\n w.u32(it.id);\n break;\n case 'indx':\n w.u32(it.index);\n break;\n case 'name':\n w.unicode(it.name);\n break;\n }\n}\n\nfunction readValue(r: PsdReader, depth: number): DescValue {\n if (depth > MAX_DEPTH) throw new PsdError('Descriptor nesting is too deep.');\n const type = r.ascii(4);\n switch (type) {\n case 'obj ': {\n const n = r.u32();\n r.checkCount(n, 8, 'Descriptor reference');\n const items: ReferenceItem[] = [];\n for (let i = 0; i < n; i++) items.push(readReferenceItem(r));\n return { type, items };\n }\n case 'Objc':\n case 'GlbO':\n return { type, value: readDescriptorBody(r, depth + 1) };\n case 'VlLs': {\n const n = r.u32();\n r.checkCount(n, 4, 'Descriptor list');\n const items: DescValue[] = [];\n for (let i = 0; i < n; i++) items.push(readValue(r, depth + 1));\n return { type, items };\n }\n case 'doub':\n return { type, value: r.f64() };\n case 'UntF':\n return { type, unit: r.ascii(4), value: r.f64() };\n case 'UnFl': {\n const unit = r.ascii(4);\n const n = r.u32();\n r.checkCount(n, 8, 'Unit float list');\n const values: number[] = [];\n for (let i = 0; i < n; i++) values.push(r.f64());\n return { type, unit, values };\n }\n case 'TEXT':\n return { type, value: r.unicode() };\n case 'enum':\n return { type, typeId: readId(r), value: readId(r) };\n case 'long':\n return { type, value: r.i32() };\n case 'comp':\n return { type, value: r.i64() };\n case 'bool':\n return { type, value: r.u8() !== 0 };\n case 'type':\n case 'GlbC':\n return { type, class: readClass(r) };\n case 'alis':\n case 'Pth ':\n case 'tdta':\n return { type, data: r.bytes(r.u32()) };\n case 'ObAr':\n return { type, prefix: r.u32(), body: readDescriptorBody(r, depth + 1) };\n default:\n throw new PsdError(`Unsupported descriptor value type \"${type}\".`);\n }\n}\n\nfunction writeValue(w: PsdWriter, v: DescValue): void {\n w.ascii(v.type);\n switch (v.type) {\n case 'obj ':\n w.u32(v.items.length);\n for (const it of v.items) writeReferenceItem(w, it);\n break;\n case 'Objc':\n case 'GlbO':\n writeDescriptorBody(w, v.value);\n break;\n case 'VlLs':\n w.u32(v.items.length);\n for (const it of v.items) writeValue(w, it);\n break;\n case 'doub':\n w.f64(v.value);\n break;\n case 'UntF':\n w.ascii(v.unit);\n w.f64(v.value);\n break;\n case 'UnFl':\n w.ascii(v.unit);\n w.u32(v.values.length);\n for (const x of v.values) w.f64(x);\n break;\n case 'TEXT':\n w.unicode(v.value, true);\n break;\n case 'enum':\n writeId(w, v.typeId);\n writeId(w, v.value);\n break;\n case 'long':\n w.i32(v.value);\n break;\n case 'comp':\n w.u32(Math.floor(v.value / 2 ** 32) >>> 0);\n w.u32(v.value >>> 0);\n break;\n case 'bool':\n w.u8(v.value ? 1 : 0);\n break;\n case 'type':\n case 'GlbC':\n writeClass(w, v.class);\n break;\n case 'alis':\n case 'Pth ':\n case 'tdta':\n w.u32(v.data.length);\n w.bytes(v.data);\n break;\n case 'ObAr':\n w.u32(v.prefix);\n writeDescriptorBody(w, v.body);\n break;\n }\n}\n\nfunction readDescriptorBody(r: PsdReader, depth: number): Descriptor {\n if (depth > MAX_DEPTH) throw new PsdError('Descriptor nesting is too deep.');\n const name = r.unicode();\n const classId = readId(r);\n const n = r.u32();\n r.checkCount(n, 10, 'Descriptor items');\n const items: Array<[string, DescValue]> = [];\n for (let i = 0; i < n; i++) {\n const key = readId(r);\n items.push([key, readValue(r, depth + 1)]);\n }\n return { name, classId, items };\n}\n\nfunction writeDescriptorBody(w: PsdWriter, d: Descriptor): void {\n w.unicode(d.name, true);\n writeId(w, d.classId);\n w.u32(d.items.length);\n for (const [k, v] of d.items) {\n writeId(w, k);\n writeValue(w, v);\n }\n}\n\n/** A versioned descriptor (u32 version 16, then the descriptor) at the reader's position. */\nexport function readVersionedDescriptor(r: PsdReader): Descriptor {\n const version = r.u32();\n if (version !== 16) throw new PsdError(`Descriptor version ${version}, expected 16.`);\n return readDescriptorBody(r, 0);\n}\n\nexport function writeVersionedDescriptor(w: PsdWriter, d: Descriptor): void {\n w.u32(16);\n writeDescriptorBody(w, d);\n}\n\n/** A versioned descriptor from the start of `bytes` (trailing bytes are ignored). */\nexport function parseVersionedDescriptor(bytes: Uint8Array): Descriptor {\n return readVersionedDescriptor(new PsdReader(bytes));\n}\n\nexport function versionedDescriptorBytes(d: Descriptor): Uint8Array {\n const w = new PsdWriter();\n writeVersionedDescriptor(w, d);\n return w.result();\n}\n","// Portions ported from photocraft/crates/io/src/adjust_map.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\n/**\n * PSD adjustment-layer blocks ↔ our ten adjustment kinds. Parameters use our units (levels\n * 0..255, degrees, percent), which are also Photoshop's, so most fields copy straight across.\n */\n\nimport { D50, XYZ_D50_TO_SRGB, defaultAdjustmentParams, hexToRgb, neutralHueRange } from '../adjustment-layers';\nimport { linearToSrgb } from '../color';\nimport type {\n AdjustmentKind,\n AdjustmentParamsMap,\n BlackWhiteParams,\n CurvePoint,\n CurvesParams,\n HueRange,\n LevelsChannel,\n} from '../types';\nimport { PsdReader, PsdWriter } from './binary';\nimport {\n type Descriptor,\n type DescValue,\n descBool,\n descNumber,\n descObject,\n descriptor,\n parseVersionedDescriptor,\n versionedDescriptorBytes,\n} from './descriptor';\n\n/** Every adjustment block key Photoshop writes. */\nexport const ADJUSTMENT_KEYS = ['levl', 'curv', 'hue2', 'brit', 'nvrt', 'thrs', 'post', 'expA', 'vibA', 'blnc', 'mixr', 'grdm', 'phfl', 'selc', 'blwh', 'clrL'];\n\n/** Keys we read and write. */\nexport const SUPPORTED_ADJUSTMENT_KEYS: Record<string, AdjustmentKind> = {\n levl: 'levels',\n curv: 'curves',\n hue2: 'hueSaturation',\n vibA: 'vibrance',\n blnc: 'colorBalance',\n blwh: 'blackWhite',\n nvrt: 'invert',\n post: 'posterize',\n thrs: 'threshold',\n phfl: 'photoFilter',\n};\n\nconst LABELS: Record<string, string> = {\n levl: 'Levels',\n curv: 'Curves',\n hue2: 'Hue/Saturation',\n brit: 'Brightness/Contrast',\n nvrt: 'Invert',\n thrs: 'Threshold',\n post: 'Posterize',\n expA: 'Exposure',\n vibA: 'Vibrance',\n blnc: 'Color Balance',\n mixr: 'Channel Mixer',\n grdm: 'Gradient Map',\n phfl: 'Photo Filter',\n selc: 'Selective Color',\n blwh: 'Black & White',\n clrL: 'Color Lookup',\n};\n\nexport function adjustmentLabel(key: string): string {\n return LABELS[key] ?? 'adjustment';\n}\n\nexport type ParsedAdjustment = {\n [K in AdjustmentKind]: { kind: K; params: AdjustmentParamsMap[K]; approximated?: string };\n}[AdjustmentKind];\n\nconst clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v));\nconst toHex = (rgb: readonly number[]) =>\n '#' + rgb.map((v) => Math.round(clamp(v, 0, 1) * 255).toString(16).padStart(2, '0')).join('');\n\n/** The canonical form of four range sliders: increasing, within one turn, centre in -30..330. */\nexport function canonicalBounds(b: readonly number[]): [number, number, number, number] {\n if (b.some((v) => !Number.isFinite(v))) return [-45, -15, 15, 45];\n const rem = (v: number) => ((v % 360) + 360) % 360;\n const out = [b[0], b[0], b[0], b[0]];\n for (let k = 1; k < 4; k++) out[k] = Math.min(out[k - 1] + rem(b[k] - out[k - 1]), out[0] + 359);\n const centre = (out[1] + out[2]) / 2;\n const shift = rem(centre + 30) - 30 - centre;\n return out.map((v) => v + shift) as [number, number, number, number];\n}\n\nfunction levelsRecord(r: PsdReader): LevelsChannel {\n const inBlack = r.u16();\n const inWhite = r.u16();\n const outBlack = r.u16();\n const outWhite = r.u16();\n const gamma = r.u16() / 100;\n return {\n inBlack: clamp(inBlack, 0, 255),\n inWhite: clamp(inWhite, 0, 255),\n gamma: clamp(gamma || 1, 0.1, 9.99),\n outBlack: clamp(outBlack, 0, 255),\n outWhite: clamp(outWhite, 0, 255),\n };\n}\n\n/** A pencil (256-entry map) curve as points every 15 levels. */\nfunction curveFromMap(map: Uint8Array): CurvePoint[] {\n const pts: CurvePoint[] = [];\n for (let i = 0; i <= 255; i += 15) pts.push({ input: i, output: map[i] });\n return pts;\n}\n\nfunction parseCurves(d: Uint8Array): ParsedAdjustment | null {\n const r = new PsdReader(d);\n const pencil = r.u8() !== 0;\n const version = r.u16();\n if (version !== 1 && version !== 4) return null;\n const bits = r.u32();\n const params = defaultAdjustmentParams('curves');\n const order: Array<keyof CurvesParams> = ['master', 'red', 'green', 'blue'];\n for (let bit = 0; bit < 32; bit++) {\n if (!(bits & (1 << bit))) continue;\n let pts: CurvePoint[];\n if (pencil) {\n pts = curveFromMap(r.bytes(256));\n } else {\n const n = r.u16();\n r.checkCount(n, 4, 'Curve points');\n pts = [];\n for (let i = 0; i < n; i++) {\n const output = r.u16();\n const input = r.u16();\n pts.push({ input: clamp(input, 0, 255), output: clamp(output, 0, 255) });\n }\n pts.sort((a, b) => a.input - b.input);\n }\n if (bit < 4 && pts.length >= 2) params[order[bit]] = pts;\n }\n return { kind: 'curves', params, approximated: pencil ? 'its pencil curve is approximated by points' : undefined };\n}\n\nfunction parseHueSaturation(d: Uint8Array): ParsedAdjustment {\n const r = new PsdReader(d);\n r.u16();\n const colorize = r.u8() !== 0;\n r.u8();\n const triple = () => [r.i16(), r.i16(), r.i16()];\n const col = triple();\n const master = triple();\n const ranges: HueRange[] = [];\n for (let i = 0; i < 6; i++) {\n if (r.remaining < 14) {\n ranges.push(neutralHueRange(i));\n continue;\n }\n const b = [r.u16(), r.u16(), r.u16(), r.u16()];\n const [hue, saturation, lightness] = triple();\n ranges.push({ hue, saturation, lightness, bounds: canonicalBounds(b) });\n }\n const [hue, saturation, lightness] = colorize ? col : master;\n return { kind: 'hueSaturation', params: { hue, saturation, lightness, colorize, ranges } };\n}\n\nconst BW_KEYS: Array<[keyof Omit<BlackWhiteParams, 'tint'>, string]> = [\n ['reds', 'Rd '],\n ['yellows', 'Yllw'],\n ['greens', 'Grn '],\n ['cyans', 'Cyn '],\n ['blues', 'Bl '],\n ['magentas', 'Mgnt'],\n];\nconst BW_TINT = [225 / 255, 211 / 255, 179 / 255];\n\nfunction descRgb(d: Descriptor | undefined): number[] | null {\n if (!d) return null;\n if (descNumber(d, 'redFloat') !== undefined) {\n return [descNumber(d, 'redFloat')!, descNumber(d, 'greenFloat') ?? 0, descNumber(d, 'blueFloat') ?? 0].map((v) => clamp(v, 0, 1));\n }\n const r = descNumber(d, 'Rd ');\n const g = descNumber(d, 'Grn ');\n const b = descNumber(d, 'Bl ');\n if (r === undefined || g === undefined || b === undefined) return null;\n return [r, g, b].map((v) => clamp(v / 255, 0, 1));\n}\n\nfunction parseBlackWhite(d: Uint8Array): ParsedAdjustment {\n const desc = parseVersionedDescriptor(d);\n const params = defaultAdjustmentParams('blackWhite');\n for (const [field, key] of BW_KEYS) {\n const v = descNumber(desc, key);\n if (v !== undefined) params[field] = clamp(v, -200, 300);\n }\n params.tint = descBool(desc, 'useTint') ? toHex(descRgb(descObject(desc, 'tintColor')) ?? BW_TINT) : null;\n return { kind: 'blackWhite', params };\n}\n\nfunction labToRgb(l: number, a: number, b: number): number[] {\n const fy = (l + 16) / 116;\n const fx = fy + a / 500;\n const fz = fy - b / 200;\n const f = (t: number) => (t > 6 / 29 ? t ** 3 : 3 * (6 / 29) ** 2 * (t - 4 / 29));\n const xyz = [f(fx) * D50[0], f(fy) * D50[1], f(fz) * D50[2]];\n return XYZ_D50_TO_SRGB.map((row) => linearToSrgb(clamp(row[0] * xyz[0] + row[1] * xyz[1] + row[2] * xyz[2], 0, 1)));\n}\n\n/** A PSD colour structure (space id + four u16) as 0..1 RGB, or `null` for spaces we cannot read. */\nexport function decodePsdColor(space: number, c: readonly number[]): number[] | null {\n const n = (v: number) => v / 65535;\n switch (space) {\n case 0:\n return [n(c[0]), n(c[1]), n(c[2])];\n case 1: {\n const h = n(c[0]) * 360;\n const s = n(c[1]);\n const v = n(c[2]);\n const k = (m: number) => (m + h / 60) % 6;\n const f = (m: number) => v - v * s * Math.max(0, Math.min(k(m), 4 - k(m), 1));\n return [f(5), f(3), f(1)];\n }\n case 2: {\n // Stored inverted: 65535 is no ink. A naive conversion; CMYK profiles are not read.\n const [cy, m, y, k] = c.map(n);\n return [cy * k, m * k, y * k];\n }\n case 7: {\n const signed = (v: number) => (v << 16) >> 16;\n return labToRgb(Math.min(c[0], 10000) / 100, signed(c[1]) / 100, signed(c[2]) / 100);\n }\n case 8: {\n const v = 1 - Math.min(c[0], 10000) / 10000;\n return [v, v, v];\n }\n default:\n return null;\n }\n}\n\nfunction parsePhotoFilter(d: Uint8Array): ParsedAdjustment | null {\n const r = new PsdReader(d);\n const version = r.u16();\n let rgb: number[] | null;\n if (version === 2) {\n const space = r.u16();\n const c = [r.u16(), r.u16(), r.u16(), r.u16()];\n rgb = [0, 2, 7, 8].includes(space) ? decodePsdColor(space, c) : null;\n } else if (version === 3) {\n const rel = [r.u32(), r.u32(), r.u32()].map((v) => v / 32768);\n if (rel.some((v) => v > 2)) return null;\n const xyz = [rel[0] * D50[0], rel[1], rel[2] * D50[2]];\n rgb = XYZ_D50_TO_SRGB.map((row) => linearToSrgb(clamp(row[0] * xyz[0] + row[1] * xyz[1] + row[2] * xyz[2], 0, 1)));\n } else {\n return null;\n }\n if (!rgb) return null;\n const density = clamp(r.u32(), 0, 100);\n const preserveLuminosity = r.u8() !== 0;\n return { kind: 'photoFilter', params: { color: toHex(rgb), density, preserveLuminosity } };\n}\n\n/**\n * Our parameters for an adjustment block, or `null` when the key is not one of our kinds or the\n * block cannot be read.\n */\nexport function parseAdjustmentBlock(key: string, data: Uint8Array): ParsedAdjustment | null {\n try {\n const r = new PsdReader(data);\n switch (key) {\n case 'nvrt':\n return { kind: 'invert', params: {} as Record<string, never> };\n case 'thrs':\n return { kind: 'threshold', params: { level: clamp(r.u16(), 1, 255) } };\n case 'post':\n return { kind: 'posterize', params: { levels: clamp(r.u16(), 2, 255) } };\n case 'levl': {\n r.u16();\n const master = levelsRecord(r);\n const red = levelsRecord(r);\n const green = levelsRecord(r);\n const blue = levelsRecord(r);\n return { kind: 'levels', params: { master, red, green, blue } };\n }\n case 'curv':\n return parseCurves(data);\n case 'hue2':\n return parseHueSaturation(data);\n case 'vibA': {\n const desc = parseVersionedDescriptor(data);\n const get = (k: string) => clamp(descNumber(desc, k) ?? 0, -100, 100);\n return { kind: 'vibrance', params: { vibrance: get('vibrance'), saturation: get('Strt') } };\n }\n case 'blnc': {\n const tone = () => [r.i16(), r.i16(), r.i16()].map((v) => clamp(v, -100, 100)) as [number, number, number];\n const shadows = tone();\n const midtones = tone();\n const highlights = tone();\n return { kind: 'colorBalance', params: { shadows, midtones, highlights, preserveLuminosity: r.u8() !== 0 } };\n }\n case 'blwh':\n return parseBlackWhite(data);\n case 'phfl':\n return parsePhotoFilter(data);\n default:\n return null;\n }\n } catch {\n return null;\n }\n}\n\nconst q = (v: number) => Math.round(clamp(v, 0, 255));\nconst i16 = (v: number) => Math.round(clamp(v, -32768, 32767));\n\nfunction writeLevels(w: PsdWriter, c: LevelsChannel): void {\n w.u16(q(c.inBlack));\n w.u16(q(c.inWhite));\n w.u16(q(c.outBlack));\n w.u16(q(c.outWhite));\n w.u16(Math.round(clamp(c.gamma * 100, 1, 999)));\n}\n\nconst long = (value: number): DescValue => ({ type: 'long', value: i16(value) });\n\n/** The PSD block (key and data) for one of our adjustment layers. */\nexport function writeAdjustmentBlock<K extends AdjustmentKind>(kind: K, params: AdjustmentParamsMap[K]): { key: string; data: Uint8Array } {\n const w = new PsdWriter();\n switch (kind) {\n case 'invert':\n return { key: 'nvrt', data: new Uint8Array(0) };\n case 'threshold': {\n const p = params as AdjustmentParamsMap['threshold'];\n w.u16(Math.max(1, q(p.level)));\n w.u16(0);\n return { key: 'thrs', data: w.result() };\n }\n case 'posterize': {\n const p = params as AdjustmentParamsMap['posterize'];\n w.u16(Math.round(clamp(p.levels, 2, 255)));\n w.u16(0);\n return { key: 'post', data: w.result() };\n }\n case 'levels': {\n const p = params as AdjustmentParamsMap['levels'];\n w.u16(2);\n for (const c of [p.master, p.red, p.green, p.blue]) writeLevels(w, c);\n const ident: LevelsChannel = { inBlack: 0, inWhite: 255, gamma: 1, outBlack: 0, outWhite: 255 };\n for (let i = 4; i < 29; i++) writeLevels(w, ident);\n return { key: 'levl', data: w.result() };\n }\n case 'curves': {\n const p = params as AdjustmentParamsMap['curves'];\n w.u8(0);\n w.u16(1);\n w.u32(0b1111);\n for (const c of [p.master, p.red, p.green, p.blue]) {\n const pts = c.slice(0, 19);\n w.u16(pts.length);\n for (const pt of pts) {\n w.u16(q(pt.output));\n w.u16(q(pt.input));\n }\n }\n return { key: 'curv', data: w.result() };\n }\n case 'hueSaturation': {\n const p = params as AdjustmentParamsMap['hueSaturation'];\n w.u16(2);\n w.u8(p.colorize ? 1 : 0);\n w.u8(0);\n for (let k = 0; k < 2; k++) for (const x of [p.hue, p.saturation, p.lightness]) w.i16(i16(x));\n for (let i = 0; i < 6; i++) {\n const r = p.ranges[i] ?? neutralHueRange(i);\n for (const b of r.bounds) w.u16(Math.round(((b % 360) + 360) % 360) % 360);\n for (const x of [r.hue, r.saturation, r.lightness]) w.i16(i16(x));\n }\n return { key: 'hue2', data: w.result() };\n }\n case 'vibrance': {\n const p = params as AdjustmentParamsMap['vibrance'];\n const d = descriptor('null', [\n ['vibrance', long(clamp(p.vibrance, -100, 100))],\n ['Strt', long(clamp(p.saturation, -100, 100))],\n ]);\n return { key: 'vibA', data: versionedDescriptorBytes(d) };\n }\n case 'colorBalance': {\n const p = params as AdjustmentParamsMap['colorBalance'];\n for (const x of [...p.shadows, ...p.midtones, ...p.highlights]) w.i16(i16(clamp(x, -100, 100)));\n w.u8(p.preserveLuminosity ? 1 : 0);\n w.u8(0);\n return { key: 'blnc', data: w.result() };\n }\n case 'blackWhite': {\n const p = params as AdjustmentParamsMap['blackWhite'];\n const tint = p.tint ? hexToRgb(p.tint) : BW_TINT;\n const dbl = (v: number): DescValue => ({ type: 'doub', value: clamp(v, 0, 1) * 255 });\n const items: Array<[string, DescValue]> = BW_KEYS.map(([field, key]) => [key, long(clamp(p[field], -200, 300))]);\n items.push(\n ['useTint', { type: 'bool', value: p.tint !== null }],\n ['tintColor', { type: 'Objc', value: descriptor('RGBC', [['Rd ', dbl(tint[0])], ['Grn ', dbl(tint[1])], ['Bl ', dbl(tint[2])]]) }],\n ['bwPresetKind', { type: 'long', value: 1 }],\n ['blackAndWhitePresetFileName', { type: 'TEXT', value: '' }]\n );\n return { key: 'blwh', data: versionedDescriptorBytes(descriptor('null', items)) };\n }\n case 'photoFilter': {\n const p = params as AdjustmentParamsMap['photoFilter'];\n const rgb = hexToRgb(p.color);\n w.u16(2);\n w.u16(0);\n for (const x of rgb) w.u16(Math.round(clamp(x, 0, 1) * 65535));\n w.u16(0);\n w.u32(Math.round(clamp(p.density, 0, 100)));\n w.u8(p.preserveLuminosity ? 1 : 0);\n w.zeros(3);\n return { key: 'phfl', data: w.result() };\n }\n }\n throw new Error(`Unknown adjustment kind \"${kind}\".`);\n}\n","// Portions ported from photocraft/crates/psd/src/compression.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\n/** Channel and image data compression: raw, RLE (PackBits), ZIP and ZIP with prediction. */\n\nimport { ZlibError, deflate, inflate } from '../../raster/zlib';\nimport { PsdError, PsdReader } from './binary';\n\nexport type PsdCompression = 0 | 1 | 2 | 3;\nexport const COMPRESSION_NAMES = ['raw', 'rle', 'zip', 'zip-prediction'] as const;\n\n/** Largest decoded channel set accepted from one file (bytes). */\nexport const MAX_DECODED_BYTES = 2 ** 31;\n\nexport function rowBytes(width: number, depth: number): number {\n return depth === 1 ? Math.ceil(width / 8) : width * Math.max(1, depth >> 3);\n}\n\nexport interface PlaneLayout {\n planes: number;\n width: number;\n height: number;\n depth: number;\n /** PSB: RLE row counts are 4 bytes. */\n psb: boolean;\n}\n\nfunction decodedLength(l: PlaneLayout): number {\n const total = l.planes * l.height * rowBytes(l.width, l.depth);\n if (total > MAX_DECODED_BYTES) throw new PsdError(`Decoded channel data of ${total} bytes is larger than the ${MAX_DECODED_BYTES}-byte limit.`);\n return total;\n}\n\n/** Worst-case PackBits size of `n` bytes. */\nexport function packbitsBound(n: number): number {\n return n + Math.ceil(n / 128);\n}\n\n/** PackBits: writes `src` encoded into `out` at `at` (room for `packbitsBound`); returns the new offset. */\nexport function packbitsEncode(src: Uint8Array, out: Uint8Array, at: number): number {\n const n = src.length;\n let i = 0;\n while (i < n) {\n let run = 1;\n while (i + run < n && run < 128 && src[i + run] === src[i]) run++;\n if (run >= 2) {\n out[at++] = (257 - run) & 255;\n out[at++] = src[i];\n i += run;\n continue;\n }\n const start = i;\n let len = 0;\n while (i < n && len < 128) {\n if (i + 2 < n && src[i] === src[i + 1] && src[i] === src[i + 2]) break;\n i++;\n len++;\n }\n out[at++] = len - 1;\n out.set(src.subarray(start, start + len), at);\n at += len;\n }\n return at;\n}\n\n/** PackBits: decodes exactly `expected` bytes from `src` into `out` at `at`; returns the new offset. */\nexport function packbitsDecodeInto(src: Uint8Array, expected: number, out: Uint8Array, at: number): number {\n const target = at + expected;\n let i = 0;\n while (at < target) {\n if (i >= src.length) throw new PsdError('PackBits row ended early.');\n const h = src[i++];\n if (h < 128) {\n const len = h + 1;\n if (i + len > src.length) throw new PsdError('PackBits literal is truncated.');\n if (at + len > target) throw new PsdError('PackBits literal overflows its row.');\n out.set(src.subarray(i, i + len), at);\n at += len;\n i += len;\n } else if (h !== 128) {\n const len = 257 - h;\n if (i >= src.length) throw new PsdError('PackBits run is truncated.');\n const b = src[i++];\n if (at + len > target) throw new PsdError('PackBits run overflows its row.');\n out.fill(b, at, at + len);\n at += len;\n }\n }\n return at;\n}\n\nfunction inflateChecked(data: Uint8Array, expected: number): Uint8Array {\n let out: Uint8Array;\n try {\n out = inflate(data, { maxOutput: expected + (1 << 20), sizeHint: expected, verify: false });\n } catch (e) {\n throw new PsdError(`ZIP channel data: ${e instanceof ZlibError || e instanceof Error ? e.message : String(e)}`);\n }\n if (out.length < expected) throw new PsdError(`ZIP stream produced ${out.length} of ${expected} bytes.`);\n return out.length === expected ? out : out.subarray(0, expected);\n}\n\nfunction unpredict(buf: Uint8Array, l: PlaneLayout): void {\n const rb = rowBytes(l.width, l.depth);\n if (rb === 0) return;\n const w = l.width;\n const scratch = l.depth === 32 ? new Uint8Array(rb) : null;\n for (let o = 0; o + rb <= buf.length; o += rb) {\n if (l.depth === 1 || l.depth === 8) {\n for (let i = 1; i < rb; i++) buf[o + i] = (buf[o + i] + buf[o + i - 1]) & 255;\n } else if (l.depth === 16) {\n for (let i = 1; i < w; i++) {\n const a = (buf[o + 2 * i] << 8) | buf[o + 2 * i + 1];\n const b = (buf[o + 2 * i - 2] << 8) | buf[o + 2 * i - 1];\n const v = (a + b) & 0xffff;\n buf[o + 2 * i] = v >> 8;\n buf[o + 2 * i + 1] = v & 255;\n }\n } else if (l.depth === 32) {\n for (let i = 1; i < rb; i++) buf[o + i] = (buf[o + i] + buf[o + i - 1]) & 255;\n scratch!.set(buf.subarray(o, o + rb));\n for (let i = 0; i < w; i++) for (let k = 0; k < 4; k++) buf[o + 4 * i + k] = scratch![k * w + i];\n } else {\n throw new PsdError(`ZIP prediction is not defined for depth ${l.depth}.`);\n }\n }\n}\n\nfunction decodeRle(r: PsdReader, l: PlaneLayout, total: number): Uint8Array {\n const rows = l.planes * l.height;\n const rb = rowBytes(l.width, l.depth);\n r.checkCount(rows, l.psb ? 4 : 2, 'RLE row counts');\n const counts = new Array<number>(rows);\n for (let i = 0; i < rows; i++) counts[i] = l.psb ? r.u32() : r.u16();\n if (total > r.remaining * 130) throw new PsdError('RLE data is too short for its declared size.');\n const out = new Uint8Array(total);\n let at = 0;\n for (const c of counts) at = packbitsDecodeInto(r.bytes(c), rb, out, at);\n return out;\n}\n\n/** Decodes planar channel data (`planes` planes of `height` rows each). */\nexport function decodePlanes(compression: number, data: Uint8Array, l: PlaneLayout): Uint8Array {\n const total = decodedLength(l);\n switch (compression) {\n case 0:\n if (data.length < total) throw new PsdError(`Raw channel data has ${data.length} of ${total} bytes.`);\n return data.slice(0, total);\n case 1:\n return decodeRle(new PsdReader(data), l, total);\n case 2:\n return inflateChecked(data, total).slice();\n case 3: {\n const v = inflateChecked(data, total).slice();\n unpredict(v, l);\n return v;\n }\n default:\n throw new PsdError(`Unsupported compression method ${compression}.`);\n }\n}\n\n/** RLE-encodes planar data: row counts (u16, or u32 in PSB), then the packed rows. */\nexport function encodeRle(decoded: Uint8Array, l: PlaneLayout): Uint8Array {\n const rows = l.planes * l.height;\n const rb = rowBytes(l.width, l.depth);\n const cs = l.psb ? 4 : 2;\n const packed = new Uint8Array(rows * packbitsBound(rb));\n let at = 0;\n const counts = new Uint8Array(rows * cs);\n for (let row = 0; row < rows; row++) {\n const start = at;\n at = packbitsEncode(decoded.subarray(row * rb, (row + 1) * rb), packed, at);\n const n = at - start;\n if (cs === 2) {\n if (n > 0xffff) throw new PsdError('An RLE row is longer than 65535 bytes; write a PSB instead.');\n counts[row * 2] = n >> 8;\n counts[row * 2 + 1] = n & 255;\n } else {\n counts[row * 4] = n >>> 24;\n counts[row * 4 + 1] = (n >>> 16) & 255;\n counts[row * 4 + 2] = (n >>> 8) & 255;\n counts[row * 4 + 3] = n & 255;\n }\n }\n const out = new Uint8Array(counts.length + at);\n out.set(counts);\n out.set(packed.subarray(0, at), counts.length);\n return out;\n}\n\n/** ZIP without prediction (used by tests to build fixtures). */\nexport function encodeZip(decoded: Uint8Array): Uint8Array {\n return deflate(decoded);\n}\n","// Portions ported from photocraft/crates/psd/src/{header,file,layer,tagged,tree,resources,image_data}.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\n/**\n * The PSD / PSB container: header, colour mode data, image resources, layer and mask info\n * (records, channel data, tagged blocks) and the merged image. Channel data stays encoded\n * until a caller decodes it, so inspecting a file is cheap.\n */\n\nimport { PsdError, PsdReader, PsdWriter, decodeLegacyName } from './binary';\nimport { decodePlanes, rowBytes } from './compression';\n\nexport const PSD_MAX_DIMENSION = 30_000;\nexport const PSB_MAX_DIMENSION = 300_000;\nconst MAX_CHANNELS = 56;\nconst MAX_LAYER_CHANNELS = 64;\n\nexport const COLOR_MODES: Record<number, string> = {\n 0: 'bitmap',\n 1: 'grayscale',\n 2: 'indexed',\n 3: 'rgb',\n 4: 'cmyk',\n 7: 'multichannel',\n 8: 'duotone',\n 9: 'lab',\n};\n\n/** Tagged block keys whose length is 8 bytes in a PSB. */\nconst PSB_LONG_KEYS = new Set([\n 'LMsk', 'Lr16', 'Lr32', 'Layr', 'Mt16', 'Mt32', 'Mtrn', 'Alph', 'FMsk', 'lnk2', 'FEid', 'FXid', 'PxSD',\n 'lnk3', 'lnkE', 'pths', 'extd', 'extn', 'FELS', 'cinf', 'artd',\n]);\n\nexport const CHANNEL_ALPHA = -1;\nexport const CHANNEL_USER_MASK = -2;\nexport const CHANNEL_REAL_USER_MASK = -3;\n\nexport const LAYER_FLAG_TRANSPARENCY_PROTECTED = 1;\nexport const LAYER_FLAG_HIDDEN = 2;\n/** Mask flag bit 0, \"position relative to layer\": Photoshop sets it when the mask is unlinked (photocraft reads it so). */\nexport const MASK_FLAG_UNLINKED = 1;\nexport const MASK_FLAG_DISABLED = 2;\nexport const MASK_FLAG_PARAMETERS = 16;\n\n/** Section divider (`lsct`) kinds. */\nexport const SECTION_OTHER = 0;\nexport const SECTION_OPEN = 1;\nexport const SECTION_CLOSED = 2;\nexport const SECTION_DIVIDER = 3;\n\nexport interface PsdHeader {\n psb: boolean;\n channels: number;\n height: number;\n width: number;\n depth: number;\n colorMode: number;\n}\n\nexport interface PsdRect {\n top: number;\n left: number;\n bottom: number;\n right: number;\n}\n\nexport interface PsdChannel {\n id: number;\n /** `null` for a zero-length channel. */\n compression: number | null;\n /** Encoded bytes, without the compression field. */\n data: Uint8Array;\n}\n\nexport interface PsdLayerMask {\n rect: PsdRect;\n defaultColor: number;\n flags: number;\n userDensity?: number;\n userFeather?: number;\n vectorDensity?: number;\n vectorFeather?: number;\n /** The real (vector-combined) user mask, when present. */\n real?: { flags: number; background: number; rect: PsdRect };\n}\n\nexport interface PsdTaggedBlock {\n key: string;\n data: Uint8Array;\n}\n\nexport interface PsdLayerRecord {\n rect: PsdRect;\n channels: PsdChannel[];\n blendKey: string;\n opacity: number;\n clipping: number;\n flags: number;\n mask?: PsdLayerMask;\n /** The legacy (Pascal) name; `layerName` prefers `luni`. */\n legacyName: string;\n blocks: PsdTaggedBlock[];\n}\n\nexport interface PsdResource {\n id: number;\n name: string;\n data: Uint8Array;\n}\n\nexport interface PsdFile {\n header: PsdHeader;\n colorModeData: Uint8Array;\n resources: PsdResource[];\n /** Bottom-most first, as stored. */\n layers: PsdLayerRecord[];\n /** The layer count was negative: the merged image's first extra channel is its alpha. */\n mergedAlpha: boolean;\n globalBlocks: PsdTaggedBlock[];\n imageCompression: number;\n imageData: Uint8Array;\n}\n\n// ---------------------------------------------------------------------------\n// Reading\n// ---------------------------------------------------------------------------\n\nfunction readRect(r: PsdReader): PsdRect {\n return { top: r.i32(), left: r.i32(), bottom: r.i32(), right: r.i32() };\n}\n\nexport function rectSize(rect: PsdRect): { width: number; height: number } {\n const width = rect.right - rect.left;\n const height = rect.bottom - rect.top;\n if (width < 0 || height < 0) throw new PsdError('A layer rectangle has a negative size.');\n if (width > PSB_MAX_DIMENSION || height > PSB_MAX_DIMENSION) throw new PsdError('A layer is larger than 300000 pixels.');\n return { width, height };\n}\n\nfunction readHeader(r: PsdReader): PsdHeader {\n if (r.ascii(4) !== '8BPS') throw new PsdError('Not a PSD file: the signature is not \"8BPS\".');\n const version = r.u16();\n if (version !== 1 && version !== 2) throw new PsdError(`Unsupported PSD version ${version} (1 is PSD, 2 is PSB).`);\n r.skip(6);\n const h: PsdHeader = { psb: version === 2, channels: r.u16(), height: r.u32(), width: r.u32(), depth: r.u16(), colorMode: r.u16() };\n if (h.channels < 1 || h.channels > MAX_CHANNELS) throw new PsdError(`Channel count ${h.channels} is outside 1 to ${MAX_CHANNELS}.`);\n if (h.width < 1 || h.height < 1) throw new PsdError(`Image size ${h.width} × ${h.height} must be at least 1 × 1.`);\n const max = h.psb ? PSB_MAX_DIMENSION : PSD_MAX_DIMENSION;\n if (h.width > max || h.height > max) throw new PsdError(`Image size ${h.width} × ${h.height} exceeds ${max} pixels.`);\n if (![1, 8, 16, 32].includes(h.depth)) throw new PsdError(`Unsupported bit depth ${h.depth}.`);\n return h;\n}\n\nfunction readResources(r: PsdReader): PsdResource[] {\n const s = r.sub(r.u32());\n const out: PsdResource[] = [];\n while (s.remaining >= 12) {\n s.ascii(4); // 8BIM, or another vendor signature\n const id = s.u16();\n const name = decodeLegacyName(s.pascal(2));\n const len = s.u32();\n const data = s.bytes(len);\n if (len % 2 && s.remaining) s.skip(1);\n out.push({ id, name, data });\n }\n return out;\n}\n\nconst isSig = (r: PsdReader, at = 0) => {\n const b = r.data.subarray(r.pos + at, r.pos + at + 4);\n if (b.length < 4) return false;\n const s = String.fromCharCode(b[0], b[1], b[2], b[3]);\n return s === '8BIM' || s === '8B64';\n};\n\n/** Tagged blocks until the end of `r` (or something that is not a block). */\nfunction readBlocks(r: PsdReader, psb: boolean, alignment: number): PsdTaggedBlock[] {\n const out: PsdTaggedBlock[] = [];\n while (r.remaining >= 12 && isSig(r)) {\n r.skip(4);\n const key = r.ascii(4);\n const len = r.len(psb && PSB_LONG_KEYS.has(key));\n const data = r.bytes(len);\n // Writers disagree on padding: take the gap up to the next signature, else the default.\n let pad = -1;\n for (let k = 0; k <= 3; k++) {\n if (k === r.remaining || (k < r.remaining && isSig(r, k))) {\n pad = k;\n break;\n }\n }\n if (pad < 0) {\n const def = (alignment - (len % alignment)) % alignment;\n pad = r.peek(def).every((b) => b === 0) && r.remaining >= def ? def : 0;\n }\n r.skip(pad);\n out.push({ key, data });\n }\n return out;\n}\n\nfunction maskParameterBytes(pf: number): number {\n return (pf & 1 ? 1 : 0) + (pf & 2 ? 8 : 0) + (pf & 4 ? 1 : 0) + (pf & 8 ? 8 : 0);\n}\n\n/** Photoshop writes the real mask before the parameters; the spec says after. Guess which. */\nfunction realBeforeParameters(d: Uint8Array): boolean {\n const fits = (at: number) => {\n if (at >= d.length) return false;\n const end = at + 1 + maskParameterBytes(d[at]);\n return d.length >= end && d.length <= end + 3;\n };\n if (fits(18)) return false;\n if (!fits(36)) return false;\n const known = (pf: number) => (pf & 0xf0) === 0;\n const bgOk = (at: number) => at + 1 < d.length && (d[at + 1] === 0 || d[at + 1] === 255);\n const photoshop = bgOk(18) && known(d[36]);\n const realAt = 19 + maskParameterBytes(d[18]);\n const spec = known(d[18]) && d.length >= realAt + 18 && d.length <= realAt + 21 && bgOk(realAt);\n return photoshop || !spec;\n}\n\nfunction parseMask(d: Uint8Array): PsdLayerMask | undefined {\n try {\n const r = new PsdReader(d);\n const m: PsdLayerMask = { rect: readRect(r), defaultColor: r.u8(), flags: r.u8() };\n const readReal = () => (r.remaining >= 18 ? { flags: r.u8(), background: r.u8(), rect: readRect(r) } : undefined);\n const params = (m.flags & MASK_FLAG_PARAMETERS) !== 0;\n const realFirst = params && realBeforeParameters(d);\n if (realFirst) m.real = readReal();\n if (params) {\n const pf = r.u8();\n if (pf & 1) m.userDensity = r.u8();\n if (pf & 2) m.userFeather = r.f64();\n if (pf & 4) m.vectorDensity = r.u8();\n if (pf & 8) m.vectorFeather = r.f64();\n }\n if (!realFirst) m.real = readReal();\n return m;\n } catch {\n return undefined;\n }\n}\n\nfunction readLayerRecord(r: PsdReader, psb: boolean): { record: PsdLayerRecord; lengths: number[] } {\n const rect = readRect(r);\n const n = r.u16();\n if (n > MAX_LAYER_CHANNELS) throw new PsdError(`A layer has ${n} channels (at most ${MAX_LAYER_CHANNELS}).`);\n const channels: PsdChannel[] = [];\n const lengths: number[] = [];\n for (let i = 0; i < n; i++) {\n channels.push({ id: r.i16(), compression: null, data: new Uint8Array(0) });\n lengths.push(r.len(psb));\n }\n if (r.ascii(4) !== '8BIM') throw new PsdError('A layer record has no \"8BIM\" blend-mode signature.');\n const blendKey = r.ascii(4);\n const opacity = r.u8();\n const clipping = r.u8();\n const flags = r.u8();\n r.u8();\n const x = r.sub(r.u32());\n const maskBytes = x.bytes(x.u32());\n const mask = maskBytes.length ? parseMask(maskBytes) : undefined;\n x.skip(x.u32()); // blending ranges\n const legacyName = decodeLegacyName(x.pascal(4));\n const blocks = readBlocks(x, psb, 2);\n return { record: { rect, channels, blendKey, opacity, clipping, flags, mask, legacyName, blocks }, lengths };\n}\n\nfunction readLayerInfo(data: Uint8Array, psb: boolean): { layers: PsdLayerRecord[]; mergedAlpha: boolean } {\n const r = new PsdReader(data);\n const count = r.i16();\n const n = Math.abs(count);\n r.checkCount(n, 34, 'Layer records');\n const read = [];\n for (let i = 0; i < n; i++) read.push(readLayerRecord(r, psb));\n for (const { record, lengths } of read) {\n record.channels.forEach((ch, i) => {\n const len = lengths[i];\n if (len === 0) return;\n if (len === 1) throw new PsdError('A channel has a data length of 1.');\n ch.compression = r.u16();\n ch.data = r.bytes(len - 2);\n });\n }\n return { layers: read.map((x) => x.record), mergedAlpha: count < 0 };\n}\n\n/** Parses a PSD or PSB. Throws `PsdError` with what was wrong. */\nexport function readPsd(bytes: Uint8Array): PsdFile {\n const r = new PsdReader(bytes);\n const header = readHeader(r);\n const { psb } = header;\n const colorModeData = r.bytes(r.u32());\n const resources = readResources(r);\n const s = r.sub(r.len(psb));\n let layers: PsdLayerRecord[] = [];\n let mergedAlpha = false;\n let globalBlocks: PsdTaggedBlock[] = [];\n if (s.remaining) {\n const liLen = s.len(psb);\n if (liLen > 0) ({ layers, mergedAlpha } = readLayerInfo(s.bytes(liLen), psb));\n if (s.remaining >= 4) {\n s.skip(s.u32()); // global layer mask\n globalBlocks = readBlocks(s, psb, 4);\n // 16- and 32-bit files keep their layers in an Lr16 / Lr32 block.\n const key = header.depth === 16 ? 'Lr16' : header.depth === 32 ? 'Lr32' : null;\n const block = globalBlocks.find((b) => (key ? b.key === key : !layers.length && ['Layr', 'Lr16', 'Lr32'].includes(b.key)));\n if (block) {\n ({ layers, mergedAlpha } = readLayerInfo(block.data, psb));\n globalBlocks = globalBlocks.filter((b) => b !== block);\n }\n }\n }\n const imageCompression = r.remaining >= 2 ? r.u16() : 0;\n return { header, colorModeData, resources, layers, mergedAlpha, globalBlocks, imageCompression, imageData: r.rest() };\n}\n\n// ---------------------------------------------------------------------------\n// Queries\n// ---------------------------------------------------------------------------\n\nexport function layerBlock(rec: PsdLayerRecord, key: string): Uint8Array | undefined {\n return rec.blocks.find((b) => b.key === key)?.data;\n}\n\nexport function layerName(rec: PsdLayerRecord): string {\n const luni = layerBlock(rec, 'luni');\n if (luni) {\n try {\n return new PsdReader(luni).unicode();\n } catch {\n // fall back to the legacy name\n }\n }\n return rec.legacyName;\n}\n\nexport interface SectionDivider {\n kind: number;\n blendKey?: string;\n}\n\nexport function sectionDivider(rec: PsdLayerRecord): SectionDivider | undefined {\n const d = layerBlock(rec, 'lsct') ?? layerBlock(rec, 'lsdk');\n if (!d || d.length < 4) return undefined;\n const r = new PsdReader(d);\n const kind = r.u32();\n if (r.remaining >= 8 && r.ascii(4) === '8BIM') return { kind, blendKey: r.ascii(4) };\n return { kind };\n}\n\nexport function u32Block(rec: PsdLayerRecord, key: string): number | undefined {\n const d = layerBlock(rec, key);\n return d && d.length >= 4 ? new PsdReader(d).u32() : undefined;\n}\n\nexport function u8Block(rec: PsdLayerRecord, key: string): number | undefined {\n const d = layerBlock(rec, key);\n return d && d.length >= 1 ? d[0] : undefined;\n}\n\nexport interface PsdTreeNode {\n /** Index into `PsdFile.layers` (the folder record for a group). */\n index: number;\n children?: PsdTreeNode[];\n}\n\n/** The layer tree, bottom-most first (the stored order, as in our model), from the `lsct` dividers. */\nexport function layerTree(file: PsdFile): PsdTreeNode[] {\n const stack: PsdTreeNode[][] = [[]];\n file.layers.forEach((rec, index) => {\n const kind = sectionDivider(rec)?.kind ?? SECTION_OTHER;\n if (kind === SECTION_DIVIDER) {\n stack.push([]);\n } else if (kind === SECTION_OPEN || kind === SECTION_CLOSED) {\n const children = stack.length > 1 ? stack.pop()! : [];\n stack[stack.length - 1].push({ index, children });\n } else {\n stack[stack.length - 1].push({ index });\n }\n });\n while (stack.length > 1) {\n const children = stack.pop()!;\n stack[stack.length - 1].push(...children);\n }\n return stack[0];\n}\n\nexport function channelRect(rec: PsdLayerRecord, id: number): PsdRect {\n if (id === CHANNEL_USER_MASK && rec.mask) return rec.mask.rect;\n if (id === CHANNEL_REAL_USER_MASK && rec.mask) return rec.mask.real?.rect ?? rec.mask.rect;\n return rec.rect;\n}\n\n/** One layer channel decoded to `depth`-bit planar bytes, or `undefined` if the layer has none. */\nexport function decodeLayerChannel(file: PsdFile, rec: PsdLayerRecord, id: number): Uint8Array | undefined {\n const ch = rec.channels.find((c) => c.id === id);\n if (!ch) return undefined;\n const { width, height } = rectSize(channelRect(rec, id));\n if (ch.compression === null) {\n if (width * height === 0) return new Uint8Array(0);\n throw new PsdError(`Channel ${id} of layer \"${layerName(rec)}\" has no data.`);\n }\n return decodePlanes(ch.compression, ch.data, { planes: 1, width, height, depth: file.header.depth, psb: file.header.psb });\n}\n\n/** The merged image's channels decoded, planar. */\nexport function decodeMergedImage(file: PsdFile): Uint8Array {\n const { channels, width, height, depth, psb } = file.header;\n return decodePlanes(file.imageCompression, file.imageData, { planes: channels, width, height, depth, psb });\n}\n\nexport function resource(file: PsdFile, id: number): Uint8Array | undefined {\n return file.resources.find((r) => r.id === id)?.data;\n}\n\n/** Resolution (1005) in pixels per inch, or `undefined`. */\nexport function resolution(file: PsdFile): number | undefined {\n const d = resource(file, 1005);\n if (!d || d.length < 4) return undefined;\n return new PsdReader(d).u32() / 65536;\n}\n\nexport { rowBytes };\n\n// ---------------------------------------------------------------------------\n// Writing\n// ---------------------------------------------------------------------------\n\nfunction writeRect(w: PsdWriter, r: PsdRect): void {\n w.i32(r.top);\n w.i32(r.left);\n w.i32(r.bottom);\n w.i32(r.right);\n}\n\nfunction writeBlocks(w: PsdWriter, blocks: PsdTaggedBlock[], psb: boolean, alignment: number): void {\n for (const b of blocks) {\n const long = psb && PSB_LONG_KEYS.has(b.key);\n w.ascii(long ? '8B64' : '8BIM');\n w.ascii(b.key);\n // Pad inside the declared length, as Photoshop does, so readers that skip by length stay aligned.\n const pad = (alignment - (b.data.length % alignment)) % alignment;\n if (long) w.u64(b.data.length + pad);\n else w.u32(b.data.length + pad);\n w.bytes(b.data);\n w.zeros(pad);\n }\n}\n\nfunction writeMask(w: PsdWriter, m: PsdLayerMask): void {\n writeRect(w, m.rect);\n w.u8(m.defaultColor);\n const pf = (m.userDensity !== undefined ? 1 : 0) | (m.userFeather !== undefined ? 2 : 0);\n w.u8(pf ? m.flags | MASK_FLAG_PARAMETERS : m.flags & ~MASK_FLAG_PARAMETERS);\n if (pf) {\n w.u8(pf);\n if (m.userDensity !== undefined) w.u8(m.userDensity);\n if (m.userFeather !== undefined) w.f64(m.userFeather);\n }\n // Pad to the 20-byte minimum the spec asks for.\n const len = 18 + (pf ? 1 + maskParameterBytes(pf) : 0);\n w.zeros(Math.max(0, 20 - len) + ((4 - (Math.max(20, len) % 4)) % 4));\n}\n\nfunction writeLayerRecord(w: PsdWriter, rec: PsdLayerRecord, psb: boolean, nameBytes: Uint8Array): void {\n writeRect(w, rec.rect);\n w.u16(rec.channels.length);\n for (const c of rec.channels) {\n w.i16(c.id);\n const len = c.compression === null ? 0 : 2 + c.data.length;\n if (psb) w.u64(len);\n else w.u32(len);\n }\n if (rec.blendKey.length !== 4) throw new PsdError(`Blend key \"${rec.blendKey}\" must be 4 characters.`);\n w.ascii('8BIM');\n w.ascii(rec.blendKey);\n w.u8(rec.opacity);\n w.u8(rec.clipping);\n w.u8(rec.flags);\n w.u8(0);\n const extra = w.beginLen(false);\n const mask = w.beginLen(false);\n if (rec.mask) writeMask(w, rec.mask);\n w.endLen(mask);\n // Blending ranges: composite gray plus one per colour channel, all full.\n w.u32(40);\n for (let i = 0; i < 5; i++) w.bytes([0, 0, 255, 255, 0, 0, 255, 255]);\n w.pascal(nameBytes, 4);\n writeBlocks(w, rec.blocks, psb, 2);\n w.endLen(extra);\n}\n\n/** Serializes a file. Channel data must already be encoded; `encodeName` writes legacy (Pascal) names. */\nexport function writePsd(file: PsdFile, encodeName: (s: string) => Uint8Array): Uint8Array {\n const { header } = file;\n const { psb } = header;\n const w = new PsdWriter();\n w.ascii('8BPS');\n w.u16(psb ? 2 : 1);\n w.zeros(6);\n w.u16(header.channels);\n w.u32(header.height);\n w.u32(header.width);\n w.u16(header.depth);\n w.u16(header.colorMode);\n w.u32(file.colorModeData.length);\n w.bytes(file.colorModeData);\n const res = w.beginLen(false);\n for (const r of file.resources) {\n w.ascii('8BIM');\n w.u16(r.id);\n w.pascal(encodeName(r.name), 2);\n w.u32(r.data.length);\n w.bytes(r.data);\n if (r.data.length % 2) w.u8(0);\n }\n w.endLen(res);\n const lm = w.beginLen(psb);\n if (file.layers.length || file.globalBlocks.length) {\n const li = w.beginLen(psb);\n if (file.layers.length) {\n const start = w.length;\n w.i16(file.mergedAlpha ? -file.layers.length : file.layers.length);\n for (const rec of file.layers) writeLayerRecord(w, rec, psb, encodeName(rec.legacyName));\n for (const rec of file.layers) {\n for (const c of rec.channels) {\n if (c.compression === null) continue;\n w.u16(c.compression);\n w.bytes(c.data);\n }\n }\n if ((w.length - start) % 2) w.u8(0);\n }\n w.endLen(li);\n w.u32(0); // no global layer mask\n writeBlocks(w, file.globalBlocks, psb, 4);\n }\n w.endLen(lm);\n w.u16(file.imageCompression);\n w.bytes(file.imageData);\n return w.result();\n}\n","// Portions ported from photocraft/crates/io/src/pixels.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\n/** Planar PSD channels (any colour mode, 1 / 8 / 16 / 32 bits) → 8-bit sRGB RGBA, and back. */\n\nimport { D50, XYZ_D50_TO_SRGB } from '../adjustment-layers';\nimport { linearToSrgb } from '../color';\nimport { PsdError } from './binary';\n\nexport const MODE_BITMAP = 0;\nexport const MODE_GRAYSCALE = 1;\nexport const MODE_INDEXED = 2;\nexport const MODE_RGB = 3;\nexport const MODE_CMYK = 4;\nexport const MODE_MULTICHANNEL = 7;\nexport const MODE_DUOTONE = 8;\nexport const MODE_LAB = 9;\n\n/** Colour channels of a mode (alpha and spot channels come after them). */\nexport function colorChannelCount(mode: number, headerChannels: number): number {\n switch (mode) {\n case MODE_RGB:\n case MODE_LAB:\n return 3;\n case MODE_CMYK:\n return 4;\n case MODE_MULTICHANNEL:\n return Math.min(3, headerChannels);\n default:\n return 1;\n }\n}\n\nconst clamp01 = (v: number) => (v < 0 ? 0 : v > 1 ? 1 : v);\n\n/** A plane's sample at `i` as 0..1 (`gamma`: 32-bit samples are linear light, so encode them). */\nfunction sampler(plane: Uint8Array | undefined, depth: number, width: number, encode: boolean, fill: number): (i: number) => number {\n if (!plane) return () => fill;\n switch (depth) {\n case 8:\n return (i) => plane[i] / 255;\n case 16:\n return (i) => ((plane[2 * i] << 8) | plane[2 * i + 1]) / 65535;\n case 32: {\n const view = new DataView(plane.buffer, plane.byteOffset, plane.byteLength);\n return (i) => {\n const v = clamp01(view.getFloat32(4 * i, false) || 0);\n return encode ? linearToSrgb(v) : v;\n };\n }\n case 1: {\n const rb = Math.ceil(width / 8);\n // Bitmap: a set bit is black.\n return (i) => {\n const x = i % width;\n const y = (i - x) / width;\n return (plane[y * rb + (x >> 3)] >> (7 - (x & 7))) & 1 ? 0 : 1;\n };\n }\n default:\n throw new PsdError(`Unsupported bit depth ${depth}.`);\n }\n}\n\nfunction labToSrgb(l: number, a: number, b: number): [number, number, number] {\n const fy = (l + 16) / 116;\n const f = (t: number) => (t > 6 / 29 ? t ** 3 : 3 * (6 / 29) ** 2 * (t - 4 / 29));\n const x = f(fy + a / 500) * D50[0];\n const y = f(fy) * D50[1];\n const z = f(fy - b / 200) * D50[2];\n const m = XYZ_D50_TO_SRGB;\n return [\n linearToSrgb(clamp01(m[0][0] * x + m[0][1] * y + m[0][2] * z)),\n linearToSrgb(clamp01(m[1][0] * x + m[1][1] * y + m[1][2] * z)),\n linearToSrgb(clamp01(m[2][0] * x + m[2][1] * y + m[2][2] * z)),\n ];\n}\n\nexport interface ChannelSet {\n width: number;\n height: number;\n depth: number;\n mode: number;\n /** Colour planes in mode order (missing planes read as 0, or no ink for CMYK). */\n color: Array<Uint8Array | undefined>;\n /** Missing: opaque. */\n alpha?: Uint8Array;\n /** Indexed: 768-byte palette (all reds, all greens, all blues). */\n palette?: Uint8Array;\n}\n\n/** Interleaved 8-bit sRGB RGBA. */\nexport function toRgba8(set: ChannelSet): Uint8ClampedArray {\n const { width, height, depth, mode } = set;\n const n = width * height;\n const out = new Uint8ClampedArray(n * 4);\n const linear = depth === 32;\n const isLab = mode === MODE_LAB;\n const cmyk = mode === MODE_CMYK;\n // CMYK planes store 1 for no ink; a missing plane is no ink too.\n const planes = set.color.map((p) => sampler(p, depth, width, linear && !isLab && !cmyk, cmyk ? 1 : 0));\n const alpha = sampler(set.alpha, depth, width, false, 1);\n const fast8 = depth === 8 && mode === MODE_RGB && set.color.every(Boolean);\n if (fast8) {\n const [r, g, b] = set.color as Uint8Array[];\n const a = set.alpha;\n for (let i = 0, o = 0; i < n; i++, o += 4) {\n out[o] = r[i];\n out[o + 1] = g[i];\n out[o + 2] = b[i];\n out[o + 3] = a ? a[i] : 255;\n }\n return out;\n }\n const palette = set.palette;\n for (let i = 0, o = 0; i < n; i++, o += 4) {\n let r: number;\n let g: number;\n let b: number;\n switch (mode) {\n case MODE_RGB:\n case MODE_MULTICHANNEL:\n r = planes[0](i);\n g = planes[1] ? planes[1](i) : r;\n b = planes[2] ? planes[2](i) : r;\n break;\n case MODE_CMYK: {\n const k = planes[3] ? planes[3](i) : 1;\n r = planes[0](i) * k;\n g = planes[1](i) * k;\n b = planes[2](i) * k;\n break;\n }\n case MODE_LAB:\n [r, g, b] = labToSrgb(planes[0](i) * 100, planes[1](i) * 255 - 128, planes[2](i) * 255 - 128);\n break;\n case MODE_INDEXED: {\n const idx = Math.round(planes[0](i) * 255);\n if (palette && palette.length >= 768) {\n out[o] = palette[idx];\n out[o + 1] = palette[256 + idx];\n out[o + 2] = palette[512 + idx];\n out[o + 3] = Math.round(alpha(i) * 255);\n continue;\n }\n r = g = b = idx / 255;\n break;\n }\n default:\n r = g = b = planes[0](i);\n }\n out[o] = Math.round(r * 255);\n out[o + 1] = Math.round(g * 255);\n out[o + 2] = Math.round(b * 255);\n out[o + 3] = Math.round(alpha(i) * 255);\n }\n return out;\n}\n\n/** An 8-bit coverage plane (masks) from any depth. */\nexport function toMask8(plane: Uint8Array, width: number, height: number, depth: number): Uint8ClampedArray {\n if (depth === 8) return new Uint8ClampedArray(plane.buffer, plane.byteOffset, width * height);\n const s = sampler(plane, depth, width, false, 0);\n const out = new Uint8ClampedArray(width * height);\n for (let i = 0; i < out.length; i++) out[i] = Math.round(s(i) * 255);\n return out;\n}\n\n/** Splits interleaved RGBA into planes `[r, g, b, a]` (8-bit). */\nexport function rgbaToPlanes(data: ArrayLike<number>, n: number): [Uint8Array, Uint8Array, Uint8Array, Uint8Array] {\n const planes: [Uint8Array, Uint8Array, Uint8Array, Uint8Array] = [new Uint8Array(n), new Uint8Array(n), new Uint8Array(n), new Uint8Array(n)];\n for (let i = 0, o = 0; i < n; i++, o += 4) {\n planes[0][i] = data[o];\n planes[1][i] = data[o + 1];\n planes[2][i] = data[o + 2];\n planes[3][i] = data[o + 3];\n }\n return planes;\n}\n\n/**\n * Photoshop stores a transparent merged image matted over white (colour × alpha + white × (1 −\n * alpha)). `unmatteWhite` recovers the colour in place; `matteWhite` applies the matte.\n */\nexport function unmatteWhite(data: Uint8ClampedArray): void {\n for (let i = 0; i < data.length; i += 4) {\n const a = data[i + 3];\n if (a === 255 || a === 0) continue;\n const k = 255 - a;\n for (let c = 0; c < 3; c++) data[i + c] = Math.round(((data[i + c] - k) * 255) / a);\n }\n}\n\nexport function matteWhite(data: ArrayLike<number>): Uint8ClampedArray {\n const out = Uint8ClampedArray.from(data);\n for (let i = 0; i < out.length; i += 4) {\n const a = out[i + 3];\n if (a === 255) continue;\n for (let c = 0; c < 3; c++) out[i + c] = Math.round((out[i + c] * a + 255 * (255 - a)) / 255);\n }\n return out;\n}\n","// Portions ported from photocraft-main/crates/psd/src/patterns.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\n/**\n * Patterns: the global `Patt` / `Pat2` / `Pat3` tagged blocks (8 / 16 / 32-bit documents), which\n * pattern overlays, pattern strokes and bevel textures refer to by id.\n *\n * ```text\n * pattern := version u32 (1), image mode u32, height u16, width u16,\n * name (Unicode string), unique id (Pascal string, unpadded),\n * [indexed: 768-byte palette], virtual memory array list\n * VMA list := version u32 (3), length u32, rect (top, left, bottom, right i32),\n * channel count u32, then count + 2 arrays (user mask, sheet mask)\n * array := written u32 (0 = absent), length u32 (0 = absent), depth u32, rect,\n * depth u16, compression u8 (0 raw, 1 PackBits), data\n * ```\n *\n * Each pattern in a block is prefixed by its u32 length and padded to 4 bytes. Photoshop writes\n * the channel count as 24 and marks only the real channels as written; the first written arrays\n * are the colour channels and the next one is transparency.\n */\n\nimport type { EffectPattern } from '../../raster/effects/patterns';\nimport { PsdError, PsdReader, PsdWriter } from './binary';\nimport { decodePlanes, encodeRle } from './compression';\nimport { MODE_CMYK, MODE_INDEXED, MODE_LAB, MODE_RGB, toRgba8 } from './pixels';\n\nconst MAX_EDGE = 30000;\n/** Pattern block keys, by document depth. */\nexport const PATTERN_BLOCK_KEYS = ['Patt', 'Pat2', 'Pat3'];\n\n/** One pattern tile as stored: planar samples. */\nexport interface PsdPattern {\n mode: number;\n width: number;\n height: number;\n name: string;\n id: string;\n palette?: Uint8Array;\n depth: number;\n channels: Uint8Array[];\n alpha?: Uint8Array;\n}\n\n/** Colour channels of a PSD colour mode. */\nfunction modeChannels(mode: number): number {\n return mode === MODE_RGB || mode === MODE_LAB ? 3 : mode === MODE_CMYK ? 4 : 1;\n}\n\nconst rowBytes = (width: number, depth: number) => (depth === 1 ? Math.ceil(width / 8) : width * (depth / 8));\n\nfunction readPattern(r: PsdReader): PsdPattern {\n const version = r.u32();\n if (version !== 1) throw new PsdError(`Pattern version ${version}, expected 1.`);\n const mode = r.u32();\n r.u16();\n r.u16();\n const name = r.unicode();\n const idLength = r.u8();\n const id = new TextDecoder().decode(r.bytes(idLength));\n let palette: Uint8Array | undefined;\n if (mode === MODE_INDEXED) {\n palette = r.bytes(768).slice();\n // Some writers follow the palette with 4 more bytes.\n const next = r.peek(4);\n if (next.length === 4 && !(next[0] === 0 && next[1] === 0 && next[2] === 0 && next[3] === 3)) r.skip(4);\n }\n const vmaVersion = r.u32();\n if (vmaVersion !== 3) throw new PsdError(`Pattern virtual memory array list version ${vmaVersion}, expected 3.`);\n const length = r.u32();\n const body = new PsdReader(r.bytes(length));\n const [top, left, bottom, right] = [body.i32(), body.i32(), body.i32(), body.i32()];\n const width = right - left;\n const height = bottom - top;\n if (!(width > 0 && height > 0 && width <= MAX_EDGE && height <= MAX_EDGE)) throw new PsdError(`Pattern \"${name}\" has an invalid size ${width} × ${height}.`);\n const count = body.u32();\n if (count > 64) throw new PsdError(`Pattern \"${name}\" has ${count} channels.`);\n const planes: Uint8Array[] = [];\n let depth = 8;\n for (let i = 0; i < count + 2 && body.remaining > 0; i++) {\n if (body.u32() === 0) continue;\n const arrayLength = body.u32();\n if (arrayLength === 0) continue;\n if (arrayLength < 23) throw new PsdError(`Pattern \"${name}\" has a short channel.`);\n const d32 = body.u32();\n const [ct, cl, cb, cr] = [body.i32(), body.i32(), body.i32(), body.i32()];\n const d16 = body.u16();\n const compression = body.u8();\n const data = body.bytes(arrayLength - 23);\n const d = [1, 8, 16, 32].includes(d16) ? d16 : d32;\n if (![1, 8, 16, 32].includes(d)) throw new PsdError(`Pattern \"${name}\" has depth ${d}.`);\n if (ct < top || cl < left || cb > bottom || cr > right || cb < ct || cr < cl) throw new PsdError(`Pattern \"${name}\" has a channel outside its rectangle.`);\n if (compression > 1) throw new PsdError(`Pattern \"${name}\" uses compression ${compression}.`);\n depth = d;\n const cw = cr - cl;\n const ch = cb - ct;\n const plane = decodePlanes(compression, data, { planes: 1, width: cw, height: ch, depth: d, psb: false });\n if (cw === width && ch === height) {\n planes.push(plane);\n continue;\n }\n // A smaller channel rectangle is padding: place it in a full plane.\n if (d === 1) throw new PsdError(`Pattern \"${name}\" has a cropped bitmap channel.`);\n const bps = d / 8;\n const out = new Uint8Array(rowBytes(width, d) * height);\n for (let y = 0; y < ch; y++) {\n out.set(plane.subarray(y * cw * bps, (y + 1) * cw * bps), ((ct - top + y) * width + (cl - left)) * bps);\n }\n planes.push(out);\n }\n const nc = modeChannels(mode);\n if (planes.length < nc) throw new PsdError(`Pattern \"${name}\" has fewer channels than its colour mode.`);\n return { mode, width, height, name, id, palette, depth, channels: planes.slice(0, nc), alpha: planes[nc] };\n}\n\n/** The patterns of a `Patt` / `Pat2` / `Pat3` block. Throws `PsdError` on malformed data. */\nexport function parsePatternBlock(data: Uint8Array): PsdPattern[] {\n const r = new PsdReader(data);\n const out: PsdPattern[] = [];\n while (r.remaining >= 4) {\n const length = r.u32();\n if (length === 0) break;\n out.push(readPattern(new PsdReader(r.bytes(length))));\n r.skip(Math.min((4 - (length % 4)) % 4, r.remaining));\n }\n return out;\n}\n\n/** A stored pattern as straight 8-bit sRGB RGBA, for rendering. */\nexport function psdPatternToEffectPattern(p: PsdPattern): EffectPattern {\n const data = toRgba8({ width: p.width, height: p.height, depth: p.depth, mode: p.mode, color: p.channels, alpha: p.alpha, palette: p.palette });\n return { id: p.id, name: p.name, width: p.width, height: p.height, data };\n}\n\n/** Writes 8-bit RGB(A) patterns as `Patt` block data (PackBits channels, transparency in the user mask slot). */\nexport function writePatternBlock(patterns: readonly EffectPattern[]): Uint8Array {\n const out = new PsdWriter();\n for (const p of patterns) {\n if (!(p.width > 0 && p.height > 0) || p.width > 32767 || p.height > 32767) throw new PsdError(`Pattern \"${p.name}\" is ${p.width} × ${p.height}; patterns are 1 to 32767 px.`);\n const one = new PsdWriter();\n one.u32(1);\n one.u32(MODE_RGB);\n one.u16(p.height);\n one.u16(p.width);\n one.unicode(p.name, true);\n const id = new TextEncoder().encode(p.id).subarray(0, 255);\n one.u8(id.length);\n one.bytes(id);\n const n = p.width * p.height;\n const plane = (c: number) => {\n const v = new Uint8Array(n);\n for (let i = 0; i < n; i++) v[i] = p.data[i * 4 + c];\n return v;\n };\n const alpha = plane(3);\n const opaque = alpha.every((v) => v === 255);\n const body = new PsdWriter();\n for (const v of [0, 0, p.height, p.width]) body.i32(v);\n body.u32(24);\n const array = (v: Uint8Array) => {\n const data = encodeRle(v, { planes: 1, width: p.width, height: p.height, depth: 8, psb: false });\n body.u32(1);\n body.u32(23 + data.length);\n body.u32(8);\n for (const x of [0, 0, p.height, p.width]) body.i32(x);\n body.u16(8);\n body.u8(1);\n body.bytes(data);\n };\n for (let c = 0; c < 3; c++) array(plane(c));\n for (let c = 3; c < 24; c++) body.u32(0);\n if (opaque) body.u32(0);\n else array(alpha);\n body.u32(0);\n const b = body.result();\n one.u32(3);\n one.u32(b.length);\n one.bytes(b);\n const bytes = one.result();\n out.u32(bytes.length);\n out.bytes(bytes);\n for (let k = 0; k < (4 - (bytes.length % 4)) % 4; k++) out.u8(0);\n }\n return out.result();\n}\n","// Portions ported from photocraft/crates/io/src/psd_import.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\n/**\n * PSD / PSB → our layer tree. Everything the model holds maps directly; everything else comes in\n * as close as it can (type, smart-object, fill and shape layers as their stored pixels) with a\n * warning naming the layers.\n */\n\nimport type { BlendMode } from '../blend';\nimport { emptySurface, surfaceFromPixels } from '../../raster/surface';\nimport type { GroupLayer, ImageLayer, LayerBlendMode, LayerLocks, PixelLayer, RasterMask } from '../layers/model';\nimport { ADJUSTMENT_KEYS, adjustmentLabel, parseAdjustmentBlock } from './adjust';\nimport { PsdError } from './binary';\nimport {\n CHANNEL_ALPHA,\n CHANNEL_REAL_USER_MASK,\n CHANNEL_USER_MASK,\n COLOR_MODES,\n LAYER_FLAG_HIDDEN,\n MASK_FLAG_DISABLED,\n MASK_FLAG_UNLINKED,\n SECTION_CLOSED,\n type PsdFile,\n type PsdLayerRecord,\n type PsdRect,\n type PsdTreeNode,\n decodeLayerChannel,\n decodeMergedImage,\n layerBlock,\n layerName,\n layerTree,\n readPsd,\n rectSize,\n resolution,\n resource,\n rowBytes,\n sectionDivider,\n u32Block,\n u8Block,\n} from './file';\nimport { MODE_INDEXED, colorChannelCount, toMask8, toRgba8, unmatteWhite } from './pixels';\nimport { parseLfx2, parseLrfx } from './effects';\nimport { PATTERN_BLOCK_KEYS, parsePatternBlock, psdPatternToEffectPattern } from './patterns';\nimport { builtinPattern, type EffectPattern } from '../../raster/effects/patterns';\nimport type { GlobalLight, LayerEffect, LayerEffects } from '../../raster/effects/model';\n\n/** PSD blend keys → our blend modes (`diss` is the one we lack). */\nexport const PSD_BLEND_KEYS: Record<string, LayerBlendMode> = {\n pass: 'pass-through',\n norm: 'normal',\n dark: 'darken',\n 'mul ': 'multiply',\n idiv: 'color-burn',\n lbrn: 'linear-burn',\n dkCl: 'darker-color',\n lite: 'lighten',\n scrn: 'screen',\n 'div ': 'color-dodge',\n lddg: 'linear-dodge',\n lgCl: 'lighter-color',\n over: 'overlay',\n sLit: 'soft-light',\n hLit: 'hard-light',\n vLit: 'vivid-light',\n lLit: 'linear-light',\n pLit: 'pin-light',\n hMix: 'hard-mix',\n diff: 'difference',\n smud: 'exclusion',\n fsub: 'subtract',\n fdiv: 'divide',\n 'hue ': 'hue',\n 'sat ': 'saturation',\n colr: 'color',\n 'lum ': 'luminosity',\n};\n\n/** Our blend modes → PSD keys. `pigment` has none (export writes `norm`, see `toPsd`). */\nexport const BLEND_TO_PSD: Partial<Record<LayerBlendMode, string>> = Object.fromEntries(\n Object.entries(PSD_BLEND_KEYS).map(([k, v]) => [v, k])\n) as Partial<Record<LayerBlendMode, string>>;\n\nconst FILL_KEYS = ['SoCo', 'GdFl', 'PtFl'];\nconst TYPE_KEYS = ['TySh', 'tySh'];\nconst SMART_KEYS = ['SoLd', 'SoLE', 'PlLd'];\nconst VECTOR_KEYS = ['vmsk', 'vsms'];\n\n/** What happened to one PSD layer on import. */\nexport interface PsdLayerSummary {\n name: string;\n /** Our layer type, or `null` when the layer was dropped. */\n type: 'pixel' | 'adjustment' | 'group' | null;\n /** `mapped`: held exactly; `approximated`: imported with losses; `dropped`: left out. */\n status: 'mapped' | 'approximated' | 'dropped';\n /** What Photoshop calls it (`pixel`, `type`, `smart-object`, `fill`, `shape`, `Levels` …). */\n psdKind: string;\n /** Why it is not `mapped`. */\n notes: string[];\n /** Our layer id (when imported). */\n id?: string;\n depth: number;\n}\n\nexport interface PsdImportResult {\n width: number;\n height: number;\n /** Bottom to top, as in our model. */\n layers: ImageLayer[];\n /** One sentence per problem, naming the affected layers. */\n warnings: string[];\n /** Every PSD layer, bottom first, a group before its children. */\n summary: PsdLayerSummary[];\n /** Pixels per inch, when the file says. */\n resolution?: number;\n /** The patterns embedded in the file (`Patt` / `Pat2` / `Pat3`), which pattern effects use. */\n patterns: EffectPattern[];\n /** The global light (image resources 1037 and 1049), when the file has one. */\n globalLight?: GlobalLight;\n info: { psb: boolean; depth: number; colorMode: string; channels: number };\n}\n\nexport interface PsdImportOptions {\n /** Makes layer ids (defaults to `psd-<n>`). */\n newId?: (hint: string) => string;\n /** Skip decoding pixels (inspection only). */\n skipPixels?: boolean;\n}\n\n/** A PSD byte (0..255) as the whole percent Photoshop shows, 0..1. */\nconst percent = (v: number) => Math.round((v / 255) * 100) / 100;\n\nclass Warnings {\n private groups = new Map<string, string[]>();\n readonly extra: string[] = [];\n\n add(message: string, layer: string): void {\n const list = this.groups.get(message) ?? [];\n if (!list.includes(layer)) list.push(layer);\n this.groups.set(message, list);\n }\n\n list(): string[] {\n const out = [...this.extra];\n for (const [message, layers] of this.groups) {\n const names = layers.map((n) => `\"${n}\"`);\n const shown = names.length > 6 ? `${names.slice(0, 6).join(', ')} and ${names.length - 6} more` : names.join(', ');\n out.push(`${message} (${layers.length === 1 ? 'layer' : 'layers'} ${shown}).`);\n }\n return out;\n }\n}\n\nfunction psdKind(rec: PsdLayerRecord): string {\n if (rec.blocks.some((b) => TYPE_KEYS.includes(b.key))) return 'type';\n if (rec.blocks.some((b) => SMART_KEYS.includes(b.key))) return 'smart-object';\n const fill = rec.blocks.some((b) => FILL_KEYS.includes(b.key));\n const vector = rec.blocks.some((b) => VECTOR_KEYS.includes(b.key) || b.key === 'vstk');\n if (fill && vector) return 'shape';\n if (fill) return 'fill';\n const adj = rec.blocks.find((b) => ADJUSTMENT_KEYS.includes(b.key));\n if (adj) return adjustmentLabel(adj.key);\n return 'pixel';\n}\n\nfunction locksOf(rec: PsdLayerRecord): LayerLocks | undefined {\n const v = u32Block(rec, 'lspf');\n if (!v) return undefined;\n const locks: LayerLocks = {};\n if (v & 0x80000000) locks.all = true;\n if (v & 1) locks.transparency = true;\n if (v & 2) locks.pixels = true;\n if (v & 4) locks.position = true;\n return Object.keys(locks).length ? locks : undefined;\n}\n\ninterface Ctx {\n file: PsdFile;\n warn: Warnings;\n newId: (hint: string) => string;\n skipPixels: boolean;\n /** Every PSD layer, bottom first, a group before its children. */\n summary: PsdLayerSummary[];\n colorCount: number;\n palette?: Uint8Array;\n patterns: Map<string, EffectPattern>;\n}\n\nfunction decodeOr(ctx: Ctx, rec: PsdLayerRecord, id: number, name: string): Uint8Array | undefined {\n try {\n return decodeLayerChannel(ctx.file, rec, id);\n } catch (e) {\n ctx.warn.add(`A channel could not be decoded (${e instanceof Error ? e.message : String(e)}) and the layer is treated as empty`, name);\n return undefined;\n }\n}\n\n/** A rectangle's size, or 0 × 0 (with a warning) for a malformed one. */\nfunction safeSize(ctx: Ctx, rect: PsdRect, name: string): { width: number; height: number } {\n try {\n return rectSize(rect);\n } catch (e) {\n ctx.warn.add(`A malformed rectangle (${e instanceof Error ? e.message : String(e)}) left the layer or its mask empty`, name);\n return { width: 0, height: 0 };\n }\n}\n\nfunction recordPixels(ctx: Ctx, rec: PsdLayerRecord, name: string): PixelLayer['surface'] {\n const { width, height } = safeSize(ctx, rec.rect, name);\n if (!width || !height || ctx.skipPixels) return emptySurface(4);\n const ids = [...Array.from({ length: ctx.colorCount }, (_, i) => i), CHANNEL_ALPHA];\n const planes = ids.map((id) => (rec.channels.some((c) => c.id === id) ? decodeOr(ctx, rec, id, name) ?? null : undefined));\n // A channel that is in the file but unreadable leaves the layer empty (as the warning says).\n if (planes.some((p) => p === null)) return emptySurface(4);\n if (planes.every((p) => p === undefined)) return emptySurface(4);\n const data = toRgba8({\n width,\n height,\n depth: ctx.file.header.depth,\n mode: ctx.file.header.colorMode,\n color: planes.slice(0, ctx.colorCount) as Array<Uint8Array | undefined>,\n alpha: planes[ctx.colorCount] as Uint8Array | undefined,\n palette: ctx.palette,\n });\n return surfaceFromPixels({ width, height, data }, 4, rec.rect.left, rec.rect.top);\n}\n\nfunction recordMask(ctx: Ctx, rec: PsdLayerRecord, name: string): RasterMask | undefined {\n const m = rec.mask;\n if (!m) return undefined;\n const useReal = rec.channels.some((c) => c.id === CHANNEL_REAL_USER_MASK) && m.real;\n const id = useReal ? CHANNEL_REAL_USER_MASK : CHANNEL_USER_MASK;\n if (!rec.channels.some((c) => c.id === id)) return undefined;\n const rect = useReal ? m.real!.rect : m.rect;\n const def: 0 | 255 = (useReal ? m.real!.background : m.defaultColor) >= 128 ? 255 : 0;\n const flags = useReal ? m.real!.flags : m.flags;\n let surface = emptySurface(1, def);\n const { width, height } = safeSize(ctx, rect, name);\n if (width && height && !ctx.skipPixels) {\n const plane = decodeOr(ctx, rec, id, name);\n if (plane) surface = surfaceFromPixels({ width, height, data: toMask8(plane, width, height, ctx.file.header.depth) }, 1, rect.left, rect.top, def);\n }\n const mask: RasterMask = { surface, defaultValue: def };\n if (m.userDensity !== undefined && m.userDensity < 255) mask.density = percent(m.userDensity);\n if (m.userFeather !== undefined && m.userFeather > 0) mask.feather = Math.min(1000, m.userFeather);\n if (flags & MASK_FLAG_DISABLED) mask.enabled = false;\n if (flags & MASK_FLAG_UNLINKED) mask.linked = false;\n return mask;\n}\n\nfunction baseProps(ctx: Ctx, rec: PsdLayerRecord, name: string, notes: string[]) {\n let blend = PSD_BLEND_KEYS[rec.blendKey];\n if (rec.blendKey === 'diss') {\n ctx.warn.add('Dissolve is not supported and became Normal', name);\n notes.push('Dissolve became Normal');\n } else if (!blend) {\n ctx.warn.add(`An unknown blend mode became Normal`, name);\n notes.push(`unknown blend mode \"${rec.blendKey}\"`);\n }\n if (!blend || blend === 'pass-through') blend = 'normal';\n const props = {\n name,\n visible: (rec.flags & LAYER_FLAG_HIDDEN) === 0,\n opacity: percent(rec.opacity),\n blend: blend as BlendMode,\n } as { name: string; visible: boolean; opacity: number; blend: BlendMode; clipped?: boolean; locks?: LayerLocks };\n if (rec.clipping) props.clipped = true;\n const locks = locksOf(rec);\n if (locks) props.locks = locks;\n if (rec.blocks.some((b) => b.key === 'artb' || b.key === 'artd' || b.key === 'abdd')) {\n ctx.warn.add('Artboards are not supported; they came in as plain groups, without their background colour and clipping', name);\n notes.push('artboard imported as a group');\n }\n if (rec.blocks.some((b) => VECTOR_KEYS.includes(b.key)) && psdKind(rec) === 'pixel') {\n ctx.warn.add('Vector masks are not supported and were ignored', name);\n notes.push('vector mask ignored');\n }\n return props;\n}\n\n/** Pattern ids an effect paints with. */\nfunction patternIds(e: LayerEffect): string[] {\n if (e.kind === 'pattern-overlay') return [e.patternId];\n if (e.kind === 'stroke' && e.fill.type === 'pattern') return [e.fill.patternId];\n if (e.kind === 'bevel-emboss' && e.texture) return [e.texture.patternId];\n return [];\n}\n\n/**\n * A layer's effects: `lmfx` (several instances per kind) over `lfx2` (`lfxs` on groups), else\n * the legacy `lrFX`. The block read is kept so export writes it back while the effects are\n * unchanged; warnings name only what is not drawn exactly.\n */\nfunction recordEffects(ctx: Ctx, rec: PsdLayerRecord, name: string, notes: string[], group: boolean): LayerEffects | undefined {\n const key = ['lmfx', 'lfx2', 'lfxs'].find((k) => layerBlock(rec, k));\n if (key) {\n const data = layerBlock(rec, key)!;\n const parsed = parseLfx2(data);\n if (!parsed) {\n ctx.warn.add('Layer effects could not be read; they are kept for export but not drawn', name);\n notes.push('layer effects could not be read');\n return { enabled: true, items: [], psd: { key, data: data.slice() } };\n }\n for (const n of parsed.notes) {\n ctx.warn.add(`Layer effects were approximated: ${n}`, name);\n notes.push(`effects: ${n}`);\n }\n for (const e of parsed.effects.items) {\n for (const id of patternIds(e)) {\n if (ctx.patterns.has(id) || builtinPattern(id)) continue;\n ctx.warn.add('A pattern effect uses a pattern the file does not contain, and paints nothing', name);\n notes.push(`pattern \"${id}\" is not in the file`);\n }\n }\n return { ...parsed.effects, psd: { key, data: data.slice() } };\n }\n const legacy = layerBlock(rec, 'lrFX');\n if (legacy && !group) {\n // Photoshop ignores legacy effects on groups.\n const parsed = parseLrfx(legacy);\n if (!parsed) return undefined;\n for (const n of parsed.notes) {\n ctx.warn.add(`Layer effects were approximated: ${n}`, name);\n notes.push(`effects: ${n}`);\n }\n return parsed.effects.items.length ? parsed.effects : undefined;\n }\n return undefined;\n}\n\nfunction importNode(ctx: Ctx, node: PsdTreeNode, depth: number): ImageLayer | null {\n const rec = ctx.file.layers[node.index];\n const name = layerName(rec);\n const notes: string[] = [];\n const kind = psdKind(rec);\n const entry: PsdLayerSummary = { name, type: null, status: 'mapped', psdKind: node.children ? 'group' : kind, notes, depth };\n ctx.summary.push(entry);\n\n if (node.children) {\n const props = baseProps(ctx, rec, name, notes);\n const divider = sectionDivider(rec);\n const key = divider?.blendKey ?? rec.blendKey;\n const groupBlend: LayerBlendMode = key === 'pass' ? 'pass-through' : (PSD_BLEND_KEYS[key] ?? 'normal');\n const children: ImageLayer[] = [];\n for (const child of node.children) {\n const layer = importNode(ctx, child, depth + 1);\n if (layer) children.push(layer);\n }\n const group: GroupLayer = { type: 'group', id: ctx.newId(name), ...props, blend: groupBlend, children };\n const effects = recordEffects(ctx, rec, name, notes, true);\n if (effects) group.effects = effects;\n if (divider?.kind === SECTION_CLOSED) group.collapsed = true;\n const mask = recordMask(ctx, rec, name);\n if (mask) group.mask = mask;\n entry.type = 'group';\n entry.id = group.id;\n entry.status = notes.length ? 'approximated' : 'mapped';\n return group;\n }\n\n const adjBlock = rec.blocks.find((b) => ADJUSTMENT_KEYS.includes(b.key));\n if (adjBlock) {\n const parsed = parseAdjustmentBlock(adjBlock.key, adjBlock.data);\n if (!parsed) {\n const label = adjustmentLabel(adjBlock.key);\n ctx.warn.add(`${label} adjustment layers are not supported and were left out`, name);\n notes.push(`${label} is not supported`);\n entry.status = 'dropped';\n return null;\n }\n const props = baseProps(ctx, rec, name, notes);\n if (parsed.approximated) {\n ctx.warn.add(`A ${adjustmentLabel(adjBlock.key)} layer was approximated: ${parsed.approximated}`, name);\n notes.push(parsed.approximated);\n }\n const layer = { type: 'adjustment', id: ctx.newId(name), ...props, kind: parsed.kind, params: parsed.params } as ImageLayer;\n const mask = recordMask(ctx, rec, name);\n if (mask) layer.mask = mask;\n entry.type = 'adjustment';\n entry.id = layer.id;\n entry.status = notes.length ? 'approximated' : 'mapped';\n return layer;\n }\n\n const props = baseProps(ctx, rec, name, notes);\n if (kind !== 'pixel') {\n const what = kind === 'smart-object' ? 'Smart objects' : kind === 'type' ? 'Type layers' : kind === 'shape' ? 'Shape layers' : 'Fill layers';\n ctx.warn.add(`${what} came in as pixel layers (their stored pixels)`, name);\n notes.push(`${kind} layer imported as pixels`);\n }\n const layer: PixelLayer = { type: 'pixel', id: ctx.newId(name), ...props, surface: recordPixels(ctx, rec, name), offset: { x: 0, y: 0 } };\n const fill = u8Block(rec, 'iOpa');\n if (fill !== undefined && fill < 255) layer.fillOpacity = percent(fill);\n const effects = recordEffects(ctx, rec, name, notes, false);\n if (effects) layer.effects = effects;\n const mask = recordMask(ctx, rec, name);\n if (mask) layer.mask = mask;\n entry.type = 'pixel';\n entry.id = layer.id;\n entry.status = notes.length ? 'approximated' : 'mapped';\n return layer;\n}\n\nfunction mergedLayer(ctx: Ctx): PixelLayer | null {\n const { header } = ctx.file;\n const { width, height } = header;\n let planes: Uint8Array;\n try {\n planes = decodeMergedImage(ctx.file);\n } catch (e) {\n ctx.warn.extra.push(`The merged image could not be decoded (${e instanceof Error ? e.message : String(e)}).`);\n return null;\n }\n const size = rowBytes(width, header.depth) * height;\n const plane = (i: number) => (i < header.channels ? planes.subarray(i * size, (i + 1) * size) : undefined);\n const cc = ctx.colorCount;\n const hasAlpha = header.channels > cc && (ctx.file.layers.length === 0 || ctx.file.mergedAlpha);\n const data = toRgba8({\n width,\n height,\n depth: header.depth,\n mode: header.colorMode,\n color: Array.from({ length: cc }, (_, i) => plane(i)),\n alpha: hasAlpha ? plane(cc) : undefined,\n palette: ctx.palette,\n });\n if (hasAlpha) unmatteWhite(data);\n return {\n type: 'pixel',\n id: ctx.newId('Background'),\n name: 'Background',\n visible: true,\n opacity: 1,\n blend: 'normal',\n surface: surfaceFromPixels({ width, height, data }, 4),\n offset: { x: 0, y: 0 },\n };\n}\n\n/** Maps a parsed PSD to our layers. */\nexport function psdToLayers(file: PsdFile, options: PsdImportOptions = {}): PsdImportResult {\n const { header } = file;\n const warn = new Warnings();\n let n = 0;\n const ctx: Ctx = {\n file,\n warn,\n newId: options.newId ?? (() => `psd-${++n}`),\n skipPixels: options.skipPixels ?? false,\n summary: [],\n colorCount: colorChannelCount(header.colorMode, header.channels),\n palette: header.colorMode === MODE_INDEXED ? file.colorModeData : undefined,\n patterns: new Map(),\n };\n for (const block of file.globalBlocks) {\n if (!PATTERN_BLOCK_KEYS.includes(block.key)) continue;\n try {\n for (const p of parsePatternBlock(block.data)) ctx.patterns.set(p.id, psdPatternToEffectPattern(p));\n } catch (e) {\n warn.extra.push(`The file's patterns could not be read (${e instanceof Error ? e.message : String(e)}); pattern effects paint nothing.`);\n }\n }\n const mode = COLOR_MODES[header.colorMode] ?? `mode ${header.colorMode}`;\n if (mode !== 'rgb' || header.depth !== 8) {\n warn.extra.push(`The file is ${header.depth}-bit ${mode}; it was converted to 8-bit RGB.`);\n }\n if (resource(file, 1039)) warn.extra.push('The embedded ICC profile is ignored; colours are read as sRGB.');\n\n let layers: ImageLayer[] = [];\n if (file.layers.length) {\n const tree = layerTree(file);\n const built: ImageLayer[] = [];\n for (const node of tree) {\n const layer = importNode(ctx, node, 0);\n if (layer) built.push(layer);\n }\n layers = built;\n if (!layers.length && !ctx.skipPixels) {\n // Every layer was left out: the merged image is still the best picture of the file.\n const bg = mergedLayer(ctx);\n if (bg) {\n layers = [bg];\n warn.extra.push('No layer could be imported, so the merged image was opened as the Background.');\n }\n }\n } else if (!ctx.skipPixels) {\n const bg = mergedLayer(ctx);\n if (bg) layers = [bg];\n }\n const i32 = (b: Uint8Array | undefined) => (b && b.length >= 4 ? new DataView(b.buffer, b.byteOffset, 4).getInt32(0) : undefined);\n const angle = i32(resource(file, 1037));\n const altitude = i32(resource(file, 1049));\n return {\n width: header.width,\n height: header.height,\n layers,\n warnings: warn.list(),\n summary: ctx.summary,\n resolution: resolution(file),\n patterns: [...ctx.patterns.values()],\n ...(angle !== undefined || altitude !== undefined\n ? { globalLight: { angle: Math.max(-360, Math.min(360, angle ?? 120)), altitude: Math.max(0, Math.min(90, altitude ?? 30)) } }\n : {}),\n info: { psb: header.psb, depth: header.depth, colorMode: mode, channels: header.channels },\n };\n}\n\n/** Parses and maps PSD / PSB bytes. Throws `PsdError` for files that cannot be read at all. */\nexport function importPsd(bytes: Uint8Array, options: PsdImportOptions = {}): PsdImportResult {\n if (bytes.length < 26) throw new PsdError('Not a PSD file: it is shorter than a PSD header.');\n return psdToLayers(readPsd(bytes), options);\n}\n","// Portions ported from photocraft-main/crates/io/src/effects_map.rs and blocks.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\n/**\n * Layer effects ↔ PSD: the object-based effects descriptor (`lfx2`, `lfxs` on groups, `lmfx` with\n * several instances per kind) both ways, and the legacy `lrFX` block on read. Key names follow the\n * descriptors Photoshop writes (documented by reverse-engineering in psd-tools and ag-psd, MIT).\n *\n * Reading reports what it could not represent exactly (`notes`), so import warns only for that.\n */\n\nimport type { BlendMode } from '../blend';\nimport {\n defaultLayerEffect,\n effectColorHex,\n effectsScale,\n scaleLayerEffect,\n newEffectId,\n parseEffectColor,\n type ContourPoint,\n type EffectContour,\n type EffectFill,\n type EffectGradient,\n type GradientStyle,\n type LayerEffect,\n type LayerEffectKind,\n type LayerEffects,\n} from '../../raster/effects/model';\nimport { PsdReader, PsdWriter } from './binary';\nimport { descBool, descGet, descNumber, descObject, descriptor, parseVersionedDescriptor, versionedDescriptorBytes, type DescValue, type Descriptor } from './descriptor';\nimport { decodePsdColor } from './adjust';\nimport { PSD_BLEND_KEYS } from './import';\n\n/** Descriptor blend ids (four-character codes) → our modes. */\nconst BLEND_CODES: Array<[BlendMode, string]> = [\n ['normal', 'Nrml'],\n ['darken', 'Drkn'],\n ['multiply', 'Mltp'],\n ['color-burn', 'CBrn'],\n ['linear-burn', 'linearBurn'],\n ['darker-color', 'darkerColor'],\n ['lighten', 'Lghn'],\n ['screen', 'Scrn'],\n ['color-dodge', 'CDdg'],\n ['linear-dodge', 'linearDodge'],\n ['lighter-color', 'lighterColor'],\n ['overlay', 'Ovrl'],\n ['soft-light', 'SftL'],\n ['hard-light', 'HrdL'],\n ['vivid-light', 'vividLight'],\n ['linear-light', 'linearLight'],\n ['pin-light', 'pinLight'],\n ['hard-mix', 'hardMix'],\n ['difference', 'Dfrn'],\n ['exclusion', 'Xclu'],\n ['subtract', 'blendSubtraction'],\n ['divide', 'blendDivide'],\n ['hue', 'H '],\n ['saturation', 'Strt'],\n ['color', 'Clr '],\n ['luminosity', 'Lmns'],\n];\n\n/** Long string ids some writers use instead of the four-character codes. */\nconst BLEND_LONG: Array<[BlendMode, string]> = [\n ['normal', 'normal'],\n ['darken', 'darken'],\n ['multiply', 'multiply'],\n ['color-burn', 'colorBurn'],\n ['lighten', 'lighten'],\n ['screen', 'screen'],\n ['color-dodge', 'colorDodge'],\n ['overlay', 'overlay'],\n ['soft-light', 'softLight'],\n ['hard-light', 'hardLight'],\n ['difference', 'difference'],\n ['exclusion', 'exclusion'],\n ['hue', 'hue'],\n ['saturation', 'saturation'],\n ['color', 'color'],\n ['luminosity', 'luminosity'],\n];\n\nconst BLEND_OF = new Map<string, BlendMode>([...BLEND_CODES, ...BLEND_LONG].map(([m, id]) => [id, m]));\nconst BLEND_ID = new Map<BlendMode, string>(BLEND_CODES);\n\n/** What reading met that our model cannot hold exactly (one short phrase each). */\ntype Notes = Set<string>;\n\n// ---------------------------------------------------------------------------\n// Descriptor helpers\n// ---------------------------------------------------------------------------\n\nconst num = (d: Descriptor, key: string, fallback: number) => descNumber(d, key) ?? fallback;\nconst pct = (d: Descriptor, key: string, fallback: number) => num(d, key, fallback * 100) / 100;\nconst bool = (d: Descriptor, key: string) => descBool(d, key) === true;\nconst enumOf = (d: Descriptor, key: string): string | undefined => {\n const v = descGet(d, key);\n return v?.type === 'enum' ? v.value : undefined;\n};\nconst text = (d: Descriptor | undefined, key: string): string => {\n const v = d ? descGet(d, key) : undefined;\n return v?.type === 'TEXT' ? v.value : '';\n};\nconst list = (d: Descriptor, key: string): Descriptor[] => {\n const v = descGet(d, key);\n if (v?.type !== 'VlLs') return [];\n return v.items.flatMap((it) => (it.type === 'Objc' || it.type === 'GlbO' ? [it.value] : []));\n};\nconst clamp = (v: number, lo: number, hi: number) => (Number.isFinite(v) ? Math.min(hi, Math.max(lo, v)) : lo);\n\nconst unit = (u: string, value: number): DescValue => ({ type: 'UntF', unit: u, value });\nconst dbl = (value: number): DescValue => ({ type: 'doub', value });\nconst long = (value: number): DescValue => ({ type: 'long', value: Math.round(value) });\nconst boolV = (value: boolean): DescValue => ({ type: 'bool', value });\nconst enumV = (typeId: string, value: string): DescValue => ({ type: 'enum', typeId, value });\nconst textV = (value: string): DescValue => ({ type: 'TEXT', value });\nconst obj = (d: Descriptor): DescValue => ({ type: 'Objc', value: d });\n\nfunction blendOf(d: Descriptor, key: string, fallback: BlendMode, notes: Notes): BlendMode {\n const id = enumOf(d, key);\n if (id === undefined) return fallback;\n const m = BLEND_OF.get(id);\n if (m) return m;\n notes.add(id === 'Dslv' || id === 'dissolve' ? 'Dissolve became Normal' : `an unknown blend mode \"${id}\" became Normal`);\n return 'normal';\n}\n\n/** A descriptor colour (RGB, HSB, CMYK, Lab, grayscale, or 0..1 floats) as `#rrggbb`. */\nfunction colorOf(d: Descriptor | undefined, fallback = '#000000'): string {\n if (!d) return fallback;\n const g = (k: string) => descNumber(d, k);\n if (g('redFloat') !== undefined) return effectColorHex([g('redFloat')!, g('greenFloat') ?? 0, g('blueFloat') ?? 0]);\n switch (d.classId) {\n case 'HSBC': {\n const rgb = decodePsdColor(1, [((g('H ') ?? 0) / 360) * 65535, ((g('Strt') ?? 0) / 100) * 65535, ((g('Brgh') ?? 0) / 100) * 65535]);\n return rgb ? effectColorHex(rgb) : fallback;\n }\n case 'CMYC': {\n const inv = (k: string) => (1 - (g(k) ?? 0) / 100) * 65535;\n const rgb = decodePsdColor(2, [inv('Cyn '), inv('Mgnt'), inv('Ylw '), inv('Blck')]);\n return rgb ? effectColorHex(rgb) : fallback;\n }\n case 'LbCl': {\n const rgb = decodePsdColor(7, [(g('Lmnc') ?? 0) * 100, Math.round((g('A ') ?? 0) * 100) & 0xffff, Math.round((g('B ') ?? 0) * 100) & 0xffff]);\n return rgb ? effectColorHex(rgb) : fallback;\n }\n case 'Grsc': {\n const v = 1 - (g('Gry ') ?? 0) / 100;\n return effectColorHex([v, v, v]);\n }\n }\n const r = g('Rd ');\n const gr = g('Grn ');\n const b = g('Bl ');\n if (r === undefined || gr === undefined || b === undefined) return fallback;\n return effectColorHex([r / 255, gr / 255, b / 255]);\n}\n\nfunction colorDesc(hex: string): Descriptor {\n const [r, g, b] = parseEffectColor(hex) ?? [0, 0, 0];\n return descriptor('RGBC', [\n ['Rd ', dbl(r * 255)],\n ['Grn ', dbl(g * 255)],\n ['Bl ', dbl(b * 255)],\n ]);\n}\n\nfunction contourOf(d: Descriptor, key: string): EffectContour | undefined {\n const c = descObject(d, key);\n if (!c) return undefined;\n const name = text(c, 'Nm ');\n const points: ContourPoint[] = list(c, 'Crv ').map((p) => {\n const out: ContourPoint = { input: clamp(num(p, 'Hrzn', 0) / 255, 0, 1), output: clamp(num(p, 'Vrtc', 0) / 255, 0, 1) };\n // `Cnty` (continuity) false marks a corner; absent means smooth.\n if (descBool(p, 'Cnty') === false) out.corner = true;\n return out;\n });\n 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);\n if (points.length < 2 || (linear && (!name || name === 'Linear'))) return undefined;\n return { name, points };\n}\n\nfunction contourDesc(c: EffectContour | undefined): DescValue {\n const name = c?.name ?? 'Linear';\n const points = c?.points ?? [\n { input: 0, output: 0 },\n { input: 1, output: 1 },\n ];\n const pts: DescValue = {\n type: 'VlLs',\n items: points.map((p) => {\n const items: Array<[string, DescValue]> = [\n ['Hrzn', dbl(p.input * 255)],\n ['Vrtc', dbl(p.output * 255)],\n ];\n if (p.corner) items.push(['Cnty', boolV(false)]);\n return obj(descriptor('CrPt', items));\n }),\n };\n return obj(\n descriptor('ShpC', [\n ['Nm ', textV(name)],\n ['Crv ', pts],\n ])\n );\n}\n\nconst GRADIENT_STYLES: Array<[GradientStyle, string]> = [\n ['linear', 'Lnr '],\n ['radial', 'Rdl '],\n ['angle', 'Angl'],\n ['reflected', 'Rflc'],\n ['diamond', 'Dmnd'],\n];\n\n/** A `Grad` descriptor's stops: midpoints become an extra stop each; smoothness is drawn linearly. */\nfunction gradientOf(d: Descriptor, notes: Notes): EffectGradient {\n const g: EffectGradient = {\n stops: [\n { position: 0, color: '#000000' },\n { position: 1, color: '#ffffff' },\n ],\n style: (GRADIENT_STYLES.find(([, id]) => id === enumOf(d, 'Type'))?.[0] ?? 'linear') as GradientStyle,\n angle: clamp(num(d, 'Angl', 90), -360, 360),\n scale: clamp(pct(d, 'Scl ', 1), 0.1, 1.5),\n reverse: bool(d, 'Rvrs'),\n align: descBool(d, 'Algn') !== false,\n offset: { x: 0, y: 0 },\n };\n const off = descObject(d, 'Ofst');\n if (off) g.offset = { x: clamp(pct(off, 'Hrzn', 0), -1, 1), y: clamp(pct(off, 'Vrtc', 0), -1, 1) };\n const grad = descObject(d, 'Grad');\n if (!grad) return g;\n if (enumOf(grad, 'GrdF') === 'ClNs') {\n notes.add('noise gradients became black to white');\n return g;\n }\n const raw = list(grad, 'Clrs')\n .map((s) => {\n const type = enumOf(s, 'Type');\n if (type === 'FrgC' || type === 'BckC') notes.add('foreground / background gradient stops became black / white');\n const color = type === 'BckC' ? '#ffffff' : colorOf(descObject(s, 'Clr '));\n return { position: clamp(num(s, 'Lctn', 0) / 4096, 0, 1), color, mid: clamp(num(s, 'Mdpn', 50) / 100, 0.05, 0.95) };\n })\n .sort((a, b) => a.position - b.position);\n if (raw.length) {\n const stops: EffectGradient['stops'] = [];\n raw.forEach((s, i) => {\n // A midpoint (stored on the stop that ends its segment) becomes an even mix at that point.\n if (i > 0 && Math.abs(s.mid - 0.5) > 1e-3) {\n const a = raw[i - 1];\n const ca = parseEffectColor(a.color)!;\n const cb = parseEffectColor(s.color)!;\n stops.push({ position: a.position + (s.position - a.position) * s.mid, color: effectColorHex(ca.map((v, k) => (v + cb[k]) / 2)) });\n notes.add('gradient midpoints are drawn as straight segments');\n }\n stops.push({ position: s.position, color: s.color });\n });\n g.stops = stops;\n }\n const opacity = list(grad, 'Trns')\n .map((s) => ({ position: clamp(num(s, 'Lctn', 0) / 4096, 0, 1), opacity: clamp(num(s, 'Opct', 100) / 100, 0, 1) }))\n .sort((a, b) => a.position - b.position);\n if (opacity.some((o) => o.opacity < 1)) g.opacityStops = opacity;\n if (num(grad, 'Intr', 0) > 0) notes.add('gradient smoothness is drawn as straight segments');\n return g;\n}\n\nfunction gradientItems(g: EffectGradient): Array<[string, DescValue]> {\n const clrs: DescValue = {\n type: 'VlLs',\n items: g.stops.map((s) =>\n obj(\n descriptor('Clrt', [\n ['Clr ', obj(colorDesc(s.color))],\n ['Type', enumV('Clry', 'UsrS')],\n ['Lctn', long(s.position * 4096)],\n ['Mdpn', long(50)],\n ])\n )\n ),\n };\n const op = g.opacityStops?.length ? g.opacityStops : g.stops.map((s) => ({ position: s.position, opacity: 1 }));\n const trns: DescValue = {\n type: 'VlLs',\n items: op.map((s) =>\n obj(\n descriptor('TrnS', [\n ['Opct', unit('#Prc', s.opacity * 100)],\n ['Lctn', long(s.position * 4096)],\n ['Mdpn', long(50)],\n ])\n )\n ),\n };\n const grad = descriptor('Grdn', [\n ['Nm ', textV('Custom')],\n ['GrdF', enumV('GrdF', 'CstS')],\n ['Intr', dbl(0)],\n ['Clrs', clrs],\n ['Trns', trns],\n ]);\n return [\n ['Grad', obj(grad)],\n ['Angl', unit('#Ang', g.angle)],\n ['Type', enumV('GrdT', GRADIENT_STYLES.find(([s]) => s === g.style)![1])],\n ['Rvrs', boolV(g.reverse)],\n ['Algn', boolV(g.align)],\n ['Scl ', unit('#Prc', g.scale * 100)],\n [\n 'Ofst',\n obj(\n descriptor('Pnt ', [\n ['Hrzn', unit('#Prc', g.offset.x * 100)],\n ['Vrtc', unit('#Prc', g.offset.y * 100)],\n ])\n ),\n ],\n ];\n}\n\nfunction patternRefOf(d: Descriptor): { patternId: string; name: string } {\n const p = descObject(d, 'Ptrn');\n return { patternId: text(p, 'Idnt') || text(p, 'Nm '), name: text(p, 'Nm ') };\n}\n\nfunction patternRefDesc(patternId: string, name: string | undefined): DescValue {\n return obj(\n descriptor('Ptrn', [\n ['Nm ', textV(name ?? patternId)],\n ['Idnt', textV(patternId)],\n ])\n );\n}\n\nfunction placementOf(d: Descriptor): { angle: number; link: boolean; phase: { x: number; y: number } } {\n const phase = descObject(d, 'phase');\n return {\n angle: clamp(num(d, 'Angl', 0), -360, 360),\n link: descBool(d, 'Algn') !== false,\n phase: phase ? { x: num(phase, 'Hrzn', 0), y: num(phase, 'Vrtc', 0) } : { x: 0, y: 0 },\n };\n}\n\nfunction placementItems(angle: number, link: boolean, phase: { x: number; y: number }): Array<[string, DescValue]> {\n const out: Array<[string, DescValue]> = [];\n if (angle) out.push(['Angl', unit('#Ang', angle)]);\n out.push(['Algn', boolV(link)]);\n out.push([\n 'phase',\n obj(\n descriptor('Pnt ', [\n ['Hrzn', dbl(phase.x)],\n ['Vrtc', dbl(phase.y)],\n ])\n ),\n ]);\n return out;\n}\n\n// ---------------------------------------------------------------------------\n// Reading\n// ---------------------------------------------------------------------------\n\n/** Single-instance key and multi-instance list key per kind, in Photoshop's order. */\nconst KINDS: Array<[string, string, LayerEffectKind]> = [\n ['DrSh', 'dropShadowMulti', 'drop-shadow'],\n ['IrSh', 'innerShadowMulti', 'inner-shadow'],\n ['OrGl', '', 'outer-glow'],\n ['IrGl', '', 'inner-glow'],\n ['ebbl', '', 'bevel-emboss'],\n ['ChFX', '', 'satin'],\n ['SoFi', 'solidFillMulti', 'color-overlay'],\n ['GrFl', 'gradientFillMulti', 'gradient-overlay'],\n ['patternFill', '', 'pattern-overlay'],\n ['FrFX', 'frameFXMulti', 'stroke'],\n];\n\nfunction common(d: Descriptor, kind: LayerEffectKind, notes: Notes) {\n const def = defaultLayerEffect(kind, 'x') as { blend: BlendMode; opacity: number };\n return { id: newEffectId(), enabled: descBool(d, 'enab') !== false, blend: blendOf(d, 'Md ', def.blend, notes), opacity: clamp(pct(d, 'Opct', def.opacity), 0, 1) };\n}\n\nfunction readOne(kind: LayerEffectKind, d: Descriptor, notes: Notes): LayerEffect {\n const size = (key: string, fallback: number) => clamp(num(d, key, fallback), 0, 250);\n switch (kind) {\n case 'drop-shadow':\n case 'inner-shadow': {\n const base = {\n ...common(d, kind, notes),\n color: colorOf(descObject(d, 'Clr ')),\n angle: clamp(num(d, 'lagl', 120), -360, 360),\n useGlobalLight: descBool(d, 'uglg') !== false,\n distance: clamp(num(d, 'Dstn', 5), 0, 30000),\n size: size('blur', 5),\n contour: contourOf(d, 'TrnS'),\n antiAlias: bool(d, 'AntA'),\n noise: clamp(pct(d, 'Nose', 0), 0, 1),\n };\n const spread = clamp(pct(d, 'Ckmt', 0), 0, 1);\n return kind === 'drop-shadow'\n ? { ...base, kind, spread, knocksOut: descBool(d, 'layerConceals') !== false }\n : { ...base, kind, choke: spread };\n }\n case 'outer-glow':\n case 'inner-glow': {\n const fill: EffectFill = descGet(d, 'Grad') ? { type: 'gradient', gradient: gradientOf(d, notes) } : { type: 'color', color: colorOf(descObject(d, 'Clr '), '#ffffbe') };\n const jitter = clamp(pct(d, 'ShdN', 0), 0, 1);\n if (jitter > 0) notes.add('glow jitter is not drawn');\n const base = {\n ...common(d, kind, notes),\n fill: fill as Extract<EffectFill, { type: 'color' | 'gradient' }>,\n technique: enumOf(d, 'GlwT') === 'PrBL' ? ('precise' as const) : ('softer' as const),\n size: size('blur', 5),\n contour: contourOf(d, 'TrnS'),\n antiAlias: bool(d, 'AntA'),\n range: clamp(pct(d, 'Inpr', 0.5), 0.01, 1),\n jitter,\n noise: clamp(pct(d, 'Nose', 0), 0, 1),\n };\n const spread = clamp(pct(d, 'Ckmt', 0), 0, 1);\n return kind === 'outer-glow' ? { ...base, kind, spread } : { ...base, kind, choke: spread, source: enumOf(d, 'glwS') === 'SrcC' ? 'center' : 'edge' };\n }\n case 'bevel-emboss': {\n const styles: Record<string, 'inner-bevel' | 'outer-bevel' | 'emboss' | 'pillow-emboss' | 'stroke-emboss'> = { OtrB: 'outer-bevel', InrB: 'inner-bevel', Embs: 'emboss', PlEb: 'pillow-emboss', strokeEmboss: 'stroke-emboss' };\n const out: LayerEffect = {\n id: newEffectId(),\n enabled: descBool(d, 'enab') !== false,\n kind,\n style: styles[enumOf(d, 'bvlS') ?? ''] ?? 'inner-bevel',\n technique: enumOf(d, 'bvlT') === 'PrBL' ? 'chisel-hard' : enumOf(d, 'bvlT') === 'Slmt' ? 'chisel-soft' : 'smooth',\n depth: clamp(pct(d, 'srgR', 1), 0, 10),\n direction: enumOf(d, 'bvlD') === 'Out ' ? 'down' : 'up',\n size: size('blur', 5),\n soften: clamp(num(d, 'Sftn', 0), 0, 16),\n angle: clamp(num(d, 'lagl', 120), -360, 360),\n altitude: clamp(num(d, 'Lald', 30), 0, 90),\n useGlobalLight: descBool(d, 'uglg') !== false,\n glossContour: contourOf(d, 'TrnS'),\n highlightColor: colorOf(descObject(d, 'hglC'), '#ffffff'),\n highlightBlend: blendOf(d, 'hglM', 'screen', notes),\n highlightOpacity: clamp(pct(d, 'hglO', 0.75), 0, 1),\n shadowColor: colorOf(descObject(d, 'sdwC')),\n shadowBlend: blendOf(d, 'sdwM', 'multiply', notes),\n shadowOpacity: clamp(pct(d, 'sdwO', 0.75), 0, 1),\n };\n if (bool(d, 'useShape')) out.contourElement = { contour: contourOf(d, 'MpgS') ?? { name: 'Linear', points: [{ input: 0, output: 0 }, { input: 1, output: 1 }] }, range: clamp(pct(d, 'Inpr', 0.5), 0, 1), antiAlias: bool(d, 'AntA') };\n if (bool(d, 'useTexture')) {\n const ref = patternRefOf(d);\n const place = placementOf(d);\n out.texture = { patternId: ref.patternId, name: ref.name, scale: clamp(pct(d, 'Scl ', 1), 0.01, 10), depth: clamp(pct(d, 'textureDepth', 1), -10, 10), invert: bool(d, 'InvT'), link: place.link, phase: place.phase };\n }\n return out;\n }\n case 'satin':\n return {\n ...common(d, kind, notes),\n kind,\n color: colorOf(descObject(d, 'Clr ')),\n angle: clamp(num(d, 'lagl', 19), -360, 360),\n distance: size('Dstn', 11),\n size: size('blur', 14),\n contour: contourOf(d, 'MpgS'),\n antiAlias: bool(d, 'AntA'),\n invert: bool(d, 'Invr'),\n };\n case 'color-overlay':\n return { ...common(d, kind, notes), kind, color: colorOf(descObject(d, 'Clr ')) };\n case 'gradient-overlay':\n return { ...common(d, kind, notes), kind, gradient: gradientOf(d, notes), dither: bool(d, 'Dthr') };\n case 'pattern-overlay': {\n const ref = patternRefOf(d);\n const place = placementOf(d);\n return { ...common(d, kind, notes), kind, patternId: ref.patternId, name: ref.name, scale: clamp(pct(d, 'Scl ', 1), 0.01, 10), angle: place.angle, link: place.link, phase: place.phase };\n }\n case 'stroke': {\n const paint = enumOf(d, 'PntT');\n let fill: EffectFill;\n if (paint === 'GrFl') fill = { type: 'gradient', gradient: gradientOf(d, notes) };\n else if (paint === 'Ptrn') {\n const ref = patternRefOf(d);\n fill = { type: 'pattern', patternId: ref.patternId, name: ref.name, scale: clamp(pct(d, 'Scl ', 1), 0.01, 10) };\n } else fill = { type: 'color', color: colorOf(descObject(d, 'Clr ')) };\n const pos = enumOf(d, 'Styl');\n return { ...common(d, kind, notes), kind, size: size('Sz ', 3), position: pos === 'InsF' ? 'inside' : pos === 'CtrF' ? 'center' : 'outside', fill };\n }\n }\n}\n\nexport interface ParsedLayerEffects {\n effects: LayerEffects;\n /** What could not be held exactly (empty when the mapping is exact). */\n notes: string[];\n}\n\n/**\n * Parses `lfx2` / `lfxs` / `lmfx` block data (u32 version 0, then a versioned descriptor).\n * Effects marked `present: false` are left out. Returns `null` when the data is not a descriptor.\n */\nexport function parseLfx2(data: Uint8Array): ParsedLayerEffects | null {\n let d: Descriptor;\n try {\n d = parseVersionedDescriptor(data.subarray(4));\n } catch {\n return null;\n }\n const notes: Notes = new Set();\n const items: LayerEffect[] = [];\n for (const [single, multi, kind] of KINDS) {\n const many = multi ? list(d, multi) : [];\n const one = descObject(d, single);\n for (const x of many.length ? many : one ? [one] : []) {\n if (descBool(x, 'present') === false) continue;\n items.push(readOne(kind, x, notes));\n }\n }\n // `Scl ` is not a live multiplier: Photoshop stores the sizes in pixels already, and the scale\n // only records the document's resolution relative to 72 ppi (208.33 % at 150 ppi).\n const effects: LayerEffects = { enabled: descBool(d, 'masterFXSwitch') !== false, items };\n return { effects, notes: [...notes] };\n}\n\n// ---------------------------------------------------------------------------\n// Writing\n// ---------------------------------------------------------------------------\n\nfunction commonItems(e: { enabled: boolean; blend: BlendMode; opacity: number }): Array<[string, DescValue]> {\n return [\n ['enab', boolV(e.enabled)],\n ['present', boolV(true)],\n ['showInDialog', boolV(true)],\n ['Md ', enumV('BlnM', BLEND_ID.get(e.blend) ?? 'Nrml')],\n ['Opct', unit('#Prc', e.opacity * 100)],\n ];\n}\n\nfunction writeOne(e: LayerEffect): Descriptor {\n switch (e.kind) {\n case 'drop-shadow':\n case 'inner-shadow': {\n const inner = e.kind === 'inner-shadow';\n const items: Array<[string, DescValue]> = [\n ...commonItems(e),\n ['Clr ', obj(colorDesc(e.color))],\n ['uglg', boolV(e.useGlobalLight)],\n ['lagl', unit('#Ang', e.angle)],\n ['Dstn', unit('#Pxl', e.distance)],\n ['Ckmt', unit('#Pxl', (inner ? e.choke : e.spread) * 100)],\n ['blur', unit('#Pxl', e.size)],\n ['Nose', unit('#Prc', e.noise * 100)],\n ['AntA', boolV(e.antiAlias)],\n ['TrnS', contourDesc(e.contour)],\n ];\n if (e.kind === 'drop-shadow') items.push(['layerConceals', boolV(e.knocksOut)]);\n return descriptor(inner ? 'IrSh' : 'DrSh', items);\n }\n case 'outer-glow':\n case 'inner-glow': {\n const inner = e.kind === 'inner-glow';\n const items: Array<[string, DescValue]> = [...commonItems(e)];\n if (e.fill.type === 'gradient') items.push(...gradientItems(e.fill.gradient));\n else items.push(['Clr ', obj(colorDesc(e.fill.color))]);\n items.push(\n ['GlwT', enumV('BETE', e.technique === 'precise' ? 'PrBL' : 'SfBL')],\n ['Ckmt', unit('#Pxl', (inner ? e.choke : e.spread) * 100)],\n ['blur', unit('#Pxl', e.size)],\n ['Nose', unit('#Prc', e.noise * 100)],\n ['ShdN', unit('#Prc', e.jitter * 100)],\n ['AntA', boolV(e.antiAlias)],\n ['TrnS', contourDesc(e.contour)],\n ['Inpr', unit('#Prc', e.range * 100)]\n );\n if (e.kind === 'inner-glow') items.push(['glwS', enumV('IGSr', e.source === 'center' ? 'SrcC' : 'SrcE')]);\n return descriptor(inner ? 'IrGl' : 'OrGl', items);\n }\n case 'bevel-emboss': {\n const style = { 'outer-bevel': 'OtrB', 'inner-bevel': 'InrB', emboss: 'Embs', 'pillow-emboss': 'PlEb', 'stroke-emboss': 'strokeEmboss' }[e.style];\n const tech = { smooth: 'SfBL', 'chisel-hard': 'PrBL', 'chisel-soft': 'Slmt' }[e.technique];\n const items: Array<[string, DescValue]> = [\n ['enab', boolV(e.enabled)],\n ['present', boolV(true)],\n ['showInDialog', boolV(true)],\n ['hglM', enumV('BlnM', BLEND_ID.get(e.highlightBlend) ?? 'Scrn')],\n ['hglC', obj(colorDesc(e.highlightColor))],\n ['hglO', unit('#Prc', e.highlightOpacity * 100)],\n ['sdwM', enumV('BlnM', BLEND_ID.get(e.shadowBlend) ?? 'Mltp')],\n ['sdwC', obj(colorDesc(e.shadowColor))],\n ['sdwO', unit('#Prc', e.shadowOpacity * 100)],\n ['bvlT', enumV('bvlT', tech)],\n ['bvlS', enumV('BESl', style)],\n ['uglg', boolV(e.useGlobalLight)],\n ['lagl', unit('#Ang', e.angle)],\n ['Lald', unit('#Ang', e.altitude)],\n ['srgR', unit('#Prc', e.depth * 100)],\n ['blur', unit('#Pxl', e.size)],\n ['bvlD', enumV('BESs', e.direction === 'up' ? 'In ' : 'Out ')],\n ['TrnS', contourDesc(e.glossContour)],\n ['Sftn', unit('#Pxl', e.soften)],\n ];\n if (e.contourElement) {\n items.push(['useShape', boolV(true)], ['MpgS', contourDesc(e.contourElement.contour)], ['AntA', boolV(e.contourElement.antiAlias)], ['Inpr', unit('#Prc', e.contourElement.range * 100)]);\n } else items.push(['useShape', boolV(false)]);\n if (e.texture) {\n const t = e.texture;\n items.push(\n ['useTexture', boolV(true)],\n ['InvT', boolV(t.invert)],\n ['Scl ', unit('#Prc', t.scale * 100)],\n ['textureDepth', unit('#Prc', t.depth * 100)],\n ['Ptrn', patternRefDesc(t.patternId, t.name)],\n ...placementItems(0, t.link ?? true, t.phase ?? { x: 0, y: 0 })\n );\n } else items.push(['useTexture', boolV(false)]);\n return descriptor('ebbl', items);\n }\n case 'satin':\n return descriptor('ChFX', [\n ...commonItems(e),\n ['Clr ', obj(colorDesc(e.color))],\n ['AntA', boolV(e.antiAlias)],\n ['Invr', boolV(e.invert)],\n ['lagl', unit('#Ang', e.angle)],\n ['Dstn', unit('#Pxl', e.distance)],\n ['blur', unit('#Pxl', e.size)],\n ['MpgS', contourDesc(e.contour)],\n ]);\n case 'color-overlay':\n return descriptor('SoFi', [...commonItems(e), ['Clr ', obj(colorDesc(e.color))]]);\n case 'gradient-overlay':\n return descriptor('GrFl', [...commonItems(e), ...gradientItems(e.gradient), ['Dthr', boolV(e.dither)]]);\n case 'pattern-overlay':\n return descriptor('patternFill', [\n ...commonItems(e),\n ['Ptrn', patternRefDesc(e.patternId, e.name)],\n ['Scl ', unit('#Prc', e.scale * 100)],\n ...placementItems(e.angle ?? 0, e.link ?? true, e.phase ?? { x: 0, y: 0 }),\n ]);\n case 'stroke': {\n const items: Array<[string, DescValue]> = [\n ...commonItems(e),\n ['Styl', enumV('FStl', e.position === 'inside' ? 'InsF' : e.position === 'center' ? 'CtrF' : 'OutF')],\n ['Sz ', unit('#Pxl', e.size)],\n ];\n const f = e.fill;\n if (f.type === 'color') items.push(['PntT', enumV('FrFl', 'SClr')], ['Clr ', obj(colorDesc(f.color))]);\n else if (f.type === 'gradient') items.push(['PntT', enumV('FrFl', 'GrFl')], ...gradientItems(f.gradient));\n else items.push(['PntT', enumV('FrFl', 'Ptrn')], ['Ptrn', patternRefDesc(f.patternId, f.name)], ['Scl ', unit('#Prc', f.scale * 100)]);\n return descriptor('FrFX', items);\n }\n }\n}\n\n/**\n * Writes effects as `lfx2` block data, Scale Effects baked into the sizes (Photoshop's `Scl ` is\n * written as 100 %). Several instances of one kind go into its multi key\n * (`dropShadowMulti`, `frameFXMulti` …); kinds without one keep only the first instance.\n * Returns the data and how many instances had to be left out.\n */\nexport function writeLfx2(effects: LayerEffects): { data: Uint8Array; dropped: number } {\n const items: Array<[string, DescValue]> = [\n ['Scl ', unit('#Prc', 100)],\n ['masterFXSwitch', boolV(effects.enabled)],\n ];\n // Scale Effects is baked into the sizes: Photoshop reads them as pixels.\n const scale = effectsScale(effects);\n let dropped = 0;\n for (const [single, multi, kind] of KINDS) {\n const of = effects.items.filter((e) => e.kind === kind).map((e) => writeOne(scaleLayerEffect(e, scale)));\n if (!of.length) continue;\n if (of.length === 1) items.push([single, obj(of[0])]);\n else if (multi) items.push([multi, { type: 'VlLs', items: of.map(obj) }]);\n else {\n items.push([single, obj(of[0])]);\n dropped += of.length - 1;\n }\n }\n const body = versionedDescriptorBytes(descriptor('null', items));\n const w = new PsdWriter();\n w.u32(0);\n w.bytes(body);\n // Layer blocks keep their pad byte inside the length, as Photoshop writes them.\n if (w.length % 2) w.u8(0);\n return { data: w.result(), dropped };\n}\n\n// ---------------------------------------------------------------------------\n// Legacy effects (`lrFX`, Photoshop 5)\n// ---------------------------------------------------------------------------\n\n/** Parses legacy `lrFX` effects: drop / inner shadows, outer / inner glows and solid fill. Bevels are skipped. */\nexport function parseLrfx(data: Uint8Array): ParsedLayerEffects | null {\n try {\n const r = new PsdReader(data);\n r.u16();\n const count = r.u16();\n const items: LayerEffect[] = [];\n const notes: Notes = new Set();\n const fixed = (v: number) => (v | 0) / 65536;\n for (let i = 0; i < count; i++) {\n if (new TextDecoder().decode(r.bytes(4)) !== '8BIM') return null;\n const key = new TextDecoder().decode(r.bytes(4));\n const size = r.u32();\n const b = new PsdReader(r.bytes(size));\n const colorAt = (rd: PsdReader) => {\n rd.u16();\n const c = [rd.u16(), rd.u16(), rd.u16(), rd.u16()];\n return effectColorHex([c[0] >> 8, c[1] >> 8, c[2] >> 8].map((v) => v / 255));\n };\n const blendAt = (rd: PsdReader) => {\n rd.skip(4);\n const k = new TextDecoder().decode(rd.bytes(4));\n const m = PSD_BLEND_KEYS[k];\n return (m && m !== 'pass-through' ? m : 'normal') as BlendMode;\n };\n if (key === 'dsdw' || key === 'isdw') {\n b.u32();\n const blur = fixed(b.u32());\n b.u32();\n const angle = fixed(b.u32());\n const distance = fixed(b.u32());\n const color = colorAt(b);\n const blend = blendAt(b);\n const enabled = b.u8() !== 0;\n const useGlobalLight = b.u8() !== 0;\n const opacity = b.u8() / 255;\n const base = { id: newEffectId(), enabled, blend, opacity, color, angle, useGlobalLight, distance: clamp(distance, 0, 30000), size: clamp(blur, 0, 250), antiAlias: false, noise: 0 };\n items.push(key === 'dsdw' ? { ...base, kind: 'drop-shadow', spread: 0, knocksOut: true } : { ...base, kind: 'inner-shadow', choke: 0 });\n } else if (key === 'oglw' || key === 'iglw') {\n b.u32();\n const blur = fixed(b.u32());\n b.u32();\n const color = colorAt(b);\n const blend = blendAt(b);\n const enabled = b.u8() !== 0;\n const opacity = b.u8() / 255;\n const base = { id: newEffectId(), enabled, blend, opacity, fill: { type: 'color' as const, color }, technique: 'softer' as const, size: clamp(blur, 0, 250), antiAlias: false, range: 0.5, jitter: 0, noise: 0 };\n items.push(key === 'oglw' ? { ...base, kind: 'outer-glow', spread: 0 } : { ...base, kind: 'inner-glow', choke: 0, source: 'edge' });\n } else if (key === 'sofi') {\n b.u32();\n const blend = blendAt(b);\n const color = colorAt(b);\n const opacity = b.u8() / 255;\n const enabled = b.u8() !== 0;\n items.push({ id: newEffectId(), kind: 'color-overlay', enabled, blend, opacity, color });\n } else if (key === 'bevl') notes.add('legacy bevels are not read');\n }\n return { effects: { enabled: true, items }, notes: [...notes] };\n } catch {\n return null;\n }\n}\n","// Portions ported from photocraft/crates/io/src/psd_export.rs and photocraft/crates/psd/src/builder.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\n/**\n * Our layer tree → an 8-bit RGB PSD (PSB above 30 000 px) with real layers, groups, adjustment\n * layers and masks, RLE-compressed, plus the merged image the caller rendered.\n *\n * The stack must be *prepared*: annotation layers rasterized, layer filters and parametric masks\n * baked, and no layer reading the document source (see `ImageEditorController.toPsd`).\n */\n\nimport { adjustmentKindLabel } from '../adjustment-layers';\nimport { contentBounds, readRegion } from '../../raster/surface';\nimport { isActiveFilter, type ImageLayer, type RasterMask } from '../layers/model';\nimport type { CropBox } from '../types';\nimport { PsdError, PsdWriter, encodeLegacyName } from './binary';\nimport { writeAdjustmentBlock } from './adjust';\nimport { encodeRle } from './compression';\nimport {\n CHANNEL_ALPHA,\n CHANNEL_USER_MASK,\n LAYER_FLAG_HIDDEN,\n LAYER_FLAG_TRANSPARENCY_PROTECTED,\n MASK_FLAG_DISABLED,\n MASK_FLAG_UNLINKED,\n PSD_MAX_DIMENSION,\n SECTION_CLOSED,\n SECTION_DIVIDER,\n SECTION_OPEN,\n type PsdChannel,\n type PsdLayerMask,\n type PsdLayerRecord,\n type PsdRect,\n type PsdResource,\n type PsdTaggedBlock,\n writePsd,\n} from './file';\nimport { BLEND_TO_PSD } from './import';\nimport { matteWhite, rgbaToPlanes } from './pixels';\nimport { parseLfx2, writeLfx2 } from './effects';\nimport { writePatternBlock } from './patterns';\nimport { effectsFingerprint, type GlobalLight, type LayerEffect } from '../../raster/effects/model';\nimport { builtinPattern, type EffectPattern } from '../../raster/effects/patterns';\n\nexport interface PsdExportInput {\n width: number;\n height: number;\n /** Prepared stack, bottom to top. */\n layers: readonly ImageLayer[];\n /** The composite, `width × height` RGBA, written as the merged image. */\n merged: ArrayLike<number>;\n /** Pixels per inch (default 72). */\n resolution?: number;\n /** The document's patterns; those the effects use are embedded (built-in ones too). */\n patterns?: readonly EffectPattern[];\n /** Written as image resources 1037 and 1049. */\n globalLight?: GlobalLight;\n}\n\nexport interface PsdExportResult {\n bytes: Uint8Array;\n psb: boolean;\n /** Layer records written (group folders and dividers included). */\n records: number;\n /** One sentence per loss, naming the layers. */\n warnings: string[];\n}\n\n/** Pattern ids an effect paints with. */\nfunction usedPatterns(e: LayerEffect): string[] {\n if (e.kind === 'pattern-overlay') return [e.patternId];\n if (e.kind === 'stroke' && e.fill.type === 'pattern') return [e.fill.patternId];\n if (e.kind === 'bevel-emboss' && e.texture) return [e.texture.patternId];\n return [];\n}\n\nconst padEven = (d: Uint8Array) => {\n if (d.length % 2 === 0) return d;\n const out = new Uint8Array(d.length + 1);\n out.set(d);\n return out;\n};\n\n/** Layer flag bits 3 and 4: \"bit 4 is useful\" and \"pixel data irrelevant\" (group records). */\nconst FLAG_PIXELS_IRRELEVANT = 8 | 16;\nconst EMPTY_RECT: PsdRect = { top: 0, left: 0, bottom: 0, right: 0 };\n\nfunction unicodeBlock(s: string): Uint8Array {\n const w = new PsdWriter();\n w.unicode(s);\n return w.result();\n}\n\nfunction u32Bytes(v: number): Uint8Array {\n const w = new PsdWriter();\n w.u32(v);\n return w.result();\n}\n\nfunction sectionBlock(kind: number, blendKey?: string): Uint8Array {\n const w = new PsdWriter();\n w.u32(kind);\n if (blendKey) {\n w.ascii('8BIM');\n w.ascii(blendKey);\n }\n return w.result();\n}\n\nconst toRect = (r: CropBox): PsdRect => ({ top: r.y, left: r.x, bottom: r.y + r.height, right: r.x + r.width });\n\nfunction rleChannel(id: number, plane: Uint8Array, width: number, height: number, psb: boolean): PsdChannel {\n return { id, compression: 1, data: encodeRle(plane, { planes: 1, width, height, depth: 8, psb }) };\n}\n\nconst emptyChannel = (id: number): PsdChannel => ({ id, compression: 0, data: new Uint8Array(0) });\n\nfunction maskChannel(mask: RasterMask, ox: number, oy: number, psb: boolean): { channel: PsdChannel; mask: PsdLayerMask } {\n if (mask.source) throw new PsdError('A parametric mask must be baked before export.');\n const bounds = contentBounds(mask.surface);\n const def = mask.defaultValue;\n const flags = (mask.enabled === false ? MASK_FLAG_DISABLED : 0) | (mask.linked === false ? MASK_FLAG_UNLINKED : 0);\n const m: PsdLayerMask = { rect: EMPTY_RECT, defaultColor: def, flags };\n if ((mask.density ?? 1) < 1) m.userDensity = Math.round(Math.max(0, mask.density ?? 1) * 255);\n if ((mask.feather ?? 0) > 0) m.userFeather = mask.feather;\n if (!bounds) return { channel: emptyChannel(CHANNEL_USER_MASK), mask: m };\n const plane = readRegion(mask.surface, bounds);\n m.rect = toRect({ ...bounds, x: bounds.x + ox, y: bounds.y + oy });\n return { channel: rleChannel(CHANNEL_USER_MASK, new Uint8Array(plane.buffer, plane.byteOffset, plane.length), bounds.width, bounds.height, psb), mask: m };\n}\n\nfunction lockBits(layer: ImageLayer): number {\n const l = layer.locks;\n if (!l) return 0;\n return (l.all ? 0x80000000 : 0) | (l.transparency ? 1 : 0) | (l.pixels ? 2 : 0) | (l.position ? 4 : 0);\n}\n\n/** Writes the prepared stack as PSD (or PSB) bytes. */\nexport function exportPsd(input: PsdExportInput): PsdExportResult {\n const { width, height } = input;\n if (width < 1 || height < 1) throw new PsdError('The document is empty.');\n const psb = width > PSD_MAX_DIMENSION || height > PSD_MAX_DIMENSION;\n const records: PsdLayerRecord[] = [];\n let nextId = 0;\n const notes = new Map<string, string[]>();\n const note = (msg: string, name: string) => notes.set(msg, [...(notes.get(msg) ?? []), name]);\n const patternIds = new Set<string>();\n\n /**\n * A layer's effects block: the block it was read from while the effects still map to the same\n * values (or could not be read and were not changed), else regenerated.\n */\n const effectsBlock = (layer: ImageLayer): PsdTaggedBlock | null => {\n const fx = layer.effects;\n if (!fx) return null;\n for (const e of fx.items) for (const id of usedPatterns(e)) patternIds.add(id);\n const key = layer.type === 'group' ? 'lfxs' : 'lfx2';\n if (fx.psd) {\n const original = parseLfx2(fx.psd.data);\n if (original ? effectsFingerprint(original.effects) === effectsFingerprint(fx) : !fx.items.length) return { key, data: padEven(fx.psd.data) };\n }\n if (!fx.items.length && fx.enabled && (fx.scale ?? 1) === 1) return null;\n const { data, dropped } = writeLfx2(fx);\n 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);\n return { key, data };\n };\n\n const common = (layer: ImageLayer, extra: PsdTaggedBlock[] = []): Pick<PsdLayerRecord, 'opacity' | 'clipping' | 'flags' | 'legacyName' | 'blocks'> => {\n const name = layer.name || (layer.type === 'adjustment' ? adjustmentKindLabel(layer.kind) : '');\n const blocks: PsdTaggedBlock[] = [{ key: 'luni', data: unicodeBlock(name) }, { key: 'lyid', data: u32Bytes(++nextId) }, ...extra];\n const fx = effectsBlock(layer);\n if (fx) blocks.push(fx);\n const locks = lockBits(layer);\n if (locks) blocks.push({ key: 'lspf', data: u32Bytes(locks >>> 0) });\n return {\n opacity: Math.round(Math.min(1, Math.max(0, layer.opacity)) * 255),\n clipping: layer.clipped ? 1 : 0,\n flags: (layer.visible ? 0 : LAYER_FLAG_HIDDEN) | (layer.locks?.transparency ? LAYER_FLAG_TRANSPARENCY_PROTECTED : 0),\n legacyName: name,\n blocks,\n };\n };\n\n const write = (layer: ImageLayer): void => {\n switch (layer.type) {\n case 'group': {\n records.push({\n rect: EMPTY_RECT,\n channels: [CHANNEL_ALPHA, 0, 1, 2].map(emptyChannel),\n blendKey: 'norm',\n opacity: 255,\n clipping: 0,\n flags: FLAG_PIXELS_IRRELEVANT | (layer.visible ? 0 : LAYER_FLAG_HIDDEN),\n legacyName: '</Layer group>',\n blocks: [\n { key: 'luni', data: unicodeBlock('</Layer group>') },\n { key: 'lsct', data: sectionBlock(SECTION_DIVIDER) },\n ],\n });\n for (const child of layer.children) write(child);\n const blendKey = BLEND_TO_PSD[layer.blend] ?? 'norm';\n const base = common(layer, [{ key: 'lsct', data: sectionBlock(layer.collapsed ? SECTION_CLOSED : SECTION_OPEN, blendKey) }]);\n const rec: PsdLayerRecord = {\n ...base,\n rect: EMPTY_RECT,\n channels: [CHANNEL_ALPHA, 0, 1, 2].map(emptyChannel),\n blendKey,\n flags: base.flags | FLAG_PIXELS_IRRELEVANT,\n };\n if (layer.mask) {\n const m = maskChannel(layer.mask, 0, 0, psb);\n rec.channels.push(m.channel);\n rec.mask = m.mask;\n }\n records.push(rec);\n return;\n }\n case 'adjustment': {\n const block = writeAdjustmentBlock(layer.kind, layer.params);\n const rec: PsdLayerRecord = {\n ...common(layer, [{ key: block.key, data: block.data }]),\n rect: EMPTY_RECT,\n channels: [CHANNEL_ALPHA, 0, 1, 2].map(emptyChannel),\n blendKey: BLEND_TO_PSD[layer.blend] ?? 'norm',\n };\n if (layer.mask) {\n const m = maskChannel(layer.mask, 0, 0, psb);\n rec.channels.push(m.channel);\n rec.mask = m.mask;\n }\n records.push(rec);\n return;\n }\n case 'pixel': {\n if (layer.source === 'document') throw new PsdError(`Layer \"${layer.name}\" reads the document image; resolve its pixels before export.`);\n if (layer.filters?.some(isActiveFilter)) throw new PsdError(`Layer \"${layer.name}\" has live filters; bake them before export.`);\n const extra: PsdTaggedBlock[] = [];\n if (layer.fillOpacity !== undefined && layer.fillOpacity < 1) {\n extra.push({ key: 'iOpa', data: Uint8Array.from([Math.round(Math.max(0, layer.fillOpacity) * 255), 0, 0, 0]) });\n }\n const rec: PsdLayerRecord = { ...common(layer, extra), rect: EMPTY_RECT, channels: [], blendKey: BLEND_TO_PSD[layer.blend] ?? 'norm' };\n const bounds = contentBounds(layer.surface);\n if (bounds) {\n const n = bounds.width * bounds.height;\n const [r, g, b, a] = rgbaToPlanes(readRegion(layer.surface, bounds), n);\n rec.rect = toRect({ ...bounds, x: bounds.x + layer.offset.x, y: bounds.y + layer.offset.y });\n rec.channels = [\n rleChannel(CHANNEL_ALPHA, a, bounds.width, bounds.height, psb),\n rleChannel(0, r, bounds.width, bounds.height, psb),\n rleChannel(1, g, bounds.width, bounds.height, psb),\n rleChannel(2, b, bounds.width, bounds.height, psb),\n ];\n } else {\n rec.channels = [CHANNEL_ALPHA, 0, 1, 2].map(emptyChannel);\n }\n if (layer.mask) {\n const m = maskChannel(layer.mask, layer.offset.x, layer.offset.y, psb);\n rec.channels.push(m.channel);\n rec.mask = m.mask;\n }\n records.push(rec);\n return;\n }\n case 'annotation':\n throw new PsdError(`Annotation layer \"${layer.name}\" must be rasterized before export.`);\n }\n };\n for (const layer of input.layers) write(layer);\n if (records.length > 32767) throw new PsdError('A PSD holds at most 32767 layer records.');\n\n // Merged image: RGB, plus alpha when anything is transparent.\n const n = width * height;\n if (input.merged.length < n * 4) throw new PsdError(`The merged image has ${input.merged.length} bytes, expected ${n * 4}.`);\n // Photoshop's merged image is matted over white where it is transparent.\n const [r, g, b, a] = rgbaToPlanes(matteWhite(input.merged), n);\n const transparent = a.some((v) => v !== 255);\n const planes = transparent ? [r, g, b, a] : [r, g, b];\n const merged = new Uint8Array(n * planes.length);\n planes.forEach((p, i) => merged.set(p, i * n));\n\n const res = Math.max(1, input.resolution ?? 72);\n const rw = new PsdWriter();\n for (let k = 0; k < 2; k++) {\n rw.u32(Math.round(res * 65536));\n rw.u16(1); // pixels per inch\n rw.u16(1); // inches\n }\n const version = new PsdWriter();\n version.u32(1);\n version.u8(1); // the merged image is real\n version.unicode('Pixerate Editor');\n version.unicode('Pixerate Editor');\n version.u32(1);\n const resources: PsdResource[] = [\n { id: 1005, name: '', data: rw.result() },\n { id: 1057, name: '', data: version.result() },\n ];\n if (input.globalLight) {\n const i32 = (v: number) => {\n const w = new PsdWriter();\n w.i32(Math.round(v));\n return w.result();\n };\n resources.push({ id: 1037, name: '', data: i32(input.globalLight.angle) }, { id: 1049, name: '', data: i32(input.globalLight.altitude) });\n }\n // The patterns the effects use, so Photoshop can draw them.\n const globalBlocks: PsdTaggedBlock[] = [];\n if (patternIds.size) {\n const byId = new Map((input.patterns ?? []).map((p) => [p.id, p]));\n const embed: EffectPattern[] = [];\n for (const id of patternIds) {\n const p = byId.get(id) ?? builtinPattern(id);\n if (p) embed.push(p);\n else note(`Pattern \"${id}\" is not in the document and was not embedded`, id);\n }\n if (embed.length) globalBlocks.push({ key: 'Patt', data: writePatternBlock(embed) });\n }\n\n const bytes = writePsd(\n {\n header: { psb, channels: planes.length, width, height, depth: 8, colorMode: 3 },\n colorModeData: new Uint8Array(0),\n resources,\n layers: records,\n mergedAlpha: transparent && records.length > 0,\n globalBlocks,\n imageCompression: 1,\n imageData: encodeRle(merged, { planes: planes.length, width, height, depth: 8, psb }),\n },\n encodeLegacyName\n );\n const warnings = [...notes].map(([msg, names]) => `${msg} (${names.map((n) => `\"${n}\"`).join(', ')}).`);\n return { bytes, psb, records: records.length, warnings };\n}\n"]}