@pixerate/editor 0.23.0 → 0.24.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 (155) hide show
  1. package/README.md +96 -1
  2. package/THIRD_PARTY_NOTICES.md +51 -0
  3. package/dist/agent/index.cjs +9 -8
  4. package/dist/agent/index.cjs.map +1 -1
  5. package/dist/agent/index.d.cts +4 -3
  6. package/dist/agent/index.d.ts +4 -3
  7. package/dist/agent/index.js +3 -2
  8. package/dist/agent/index.js.map +1 -1
  9. package/dist/canvas/index.cjs +1210 -17
  10. package/dist/canvas/index.cjs.map +1 -1
  11. package/dist/canvas/index.d.cts +318 -1
  12. package/dist/canvas/index.d.ts +318 -1
  13. package/dist/canvas/index.js +1199 -6
  14. package/dist/canvas/index.js.map +1 -1
  15. package/dist/{chunk-QBJ22YZR.js → chunk-3CN2WD3X.js} +1354 -206
  16. package/dist/chunk-3CN2WD3X.js.map +1 -0
  17. package/dist/{chunk-JJDLPEWL.js → chunk-3LV6RRPH.js} +2 -2
  18. package/dist/{chunk-MJZBNWG7.js → chunk-5S3Z4PPF.js} +78 -557
  19. package/dist/chunk-5S3Z4PPF.js.map +1 -0
  20. package/dist/chunk-664LQYFJ.js +115 -0
  21. package/dist/chunk-664LQYFJ.js.map +1 -0
  22. package/dist/{chunk-AA4ZEVCC.cjs → chunk-D27YFC46.cjs} +101 -580
  23. package/dist/chunk-D27YFC46.cjs.map +1 -0
  24. package/dist/chunk-D6FH2YAX.cjs +131 -0
  25. package/dist/chunk-D6FH2YAX.cjs.map +1 -0
  26. package/dist/{chunk-4SUNJZWB.cjs → chunk-FD4LPFAE.cjs} +48 -200
  27. package/dist/chunk-FD4LPFAE.cjs.map +1 -0
  28. package/dist/chunk-FODNLX5N.js +749 -0
  29. package/dist/chunk-FODNLX5N.js.map +1 -0
  30. package/dist/{chunk-OYXSKQFR.js → chunk-FPENXAEJ.js} +35 -187
  31. package/dist/chunk-FPENXAEJ.js.map +1 -0
  32. package/dist/chunk-HLIMTC6N.js +1 -0
  33. package/dist/chunk-HLIMTC6N.js.map +1 -0
  34. package/dist/{chunk-QGHGJ56O.js → chunk-ITBSKI26.js} +1483 -19
  35. package/dist/chunk-ITBSKI26.js.map +1 -0
  36. package/dist/{chunk-XDMXXC4Z.cjs → chunk-MA2F5KHF.cjs} +350 -227
  37. package/dist/chunk-MA2F5KHF.cjs.map +1 -0
  38. package/dist/{chunk-RIKBJLS2.cjs → chunk-MCSJFWAO.cjs} +1356 -208
  39. package/dist/chunk-MCSJFWAO.cjs.map +1 -0
  40. package/dist/chunk-MKA3AHLV.cjs +256 -0
  41. package/dist/chunk-MKA3AHLV.cjs.map +1 -0
  42. package/dist/chunk-OLZJXL4R.cjs +749 -0
  43. package/dist/chunk-OLZJXL4R.cjs.map +1 -0
  44. package/dist/chunk-OXSV64JX.js +689 -0
  45. package/dist/chunk-OXSV64JX.js.map +1 -0
  46. package/dist/chunk-P3RQ5UJE.cjs +689 -0
  47. package/dist/chunk-P3RQ5UJE.cjs.map +1 -0
  48. package/dist/chunk-P6ODPZ4W.js +256 -0
  49. package/dist/chunk-P6ODPZ4W.js.map +1 -0
  50. package/dist/chunk-PGNZKFQS.js +685 -0
  51. package/dist/chunk-PGNZKFQS.js.map +1 -0
  52. package/dist/chunk-PL4JB3BF.cjs +1 -0
  53. package/dist/chunk-PL4JB3BF.cjs.map +1 -0
  54. package/dist/{chunk-GVSMRPHB.cjs → chunk-SBCZ6RN5.cjs} +1568 -104
  55. package/dist/chunk-SBCZ6RN5.cjs.map +1 -0
  56. package/dist/{chunk-CL37JGFW.cjs → chunk-SML64VJG.cjs} +4 -4
  57. package/dist/{chunk-CL37JGFW.cjs.map → chunk-SML64VJG.cjs.map} +1 -1
  58. package/dist/chunk-TNNJXF46.cjs +115 -0
  59. package/dist/chunk-TNNJXF46.cjs.map +1 -0
  60. package/dist/chunk-U5HZSFCS.js +131 -0
  61. package/dist/chunk-U5HZSFCS.js.map +1 -0
  62. package/dist/{chunk-K3R5NG2D.js → chunk-U5KWZU4X.js} +251 -128
  63. package/dist/chunk-U5KWZU4X.js.map +1 -0
  64. package/dist/chunk-XNORY7M6.cjs +685 -0
  65. package/dist/chunk-XNORY7M6.cjs.map +1 -0
  66. package/dist/commands/index.d.cts +5 -353
  67. package/dist/commands/index.d.ts +5 -353
  68. package/dist/comment-store-BEMgVlAA.d.cts +135 -0
  69. package/dist/comment-store-BEMgVlAA.d.ts +135 -0
  70. package/dist/compare/index.cjs +15 -0
  71. package/dist/compare/index.cjs.map +1 -0
  72. package/dist/compare/index.d.cts +58 -0
  73. package/dist/compare/index.d.ts +58 -0
  74. package/dist/compare/index.js +15 -0
  75. package/dist/compare/index.js.map +1 -0
  76. package/dist/{controller-pePCJLT1.d.cts → controller-BhLLAoi8.d.cts} +45 -4
  77. package/dist/{controller-pePCJLT1.d.ts → controller-BhLLAoi8.d.ts} +45 -4
  78. package/dist/documents-BQjzJNe2.d.ts +76 -0
  79. package/dist/documents-KRTPhvst.d.cts +76 -0
  80. package/dist/extensions/index.cjs +53 -5
  81. package/dist/extensions/index.cjs.map +1 -1
  82. package/dist/extensions/index.d.cts +7 -3
  83. package/dist/extensions/index.d.ts +7 -3
  84. package/dist/extensions/index.js +52 -4
  85. package/dist/generation/index.cjs +1373 -0
  86. package/dist/generation/index.cjs.map +1 -0
  87. package/dist/generation/index.d.cts +554 -0
  88. package/dist/generation/index.d.ts +554 -0
  89. package/dist/generation/index.js +1373 -0
  90. package/dist/generation/index.js.map +1 -0
  91. package/dist/grammar/index.cjs +48 -3
  92. package/dist/grammar/index.cjs.map +1 -1
  93. package/dist/grammar/index.d.cts +322 -100
  94. package/dist/grammar/index.d.ts +322 -100
  95. package/dist/grammar/index.js +54 -9
  96. package/dist/image-editor/index.cjs +1831 -30
  97. package/dist/image-editor/index.cjs.map +1 -1
  98. package/dist/image-editor/index.d.cts +590 -3
  99. package/dist/image-editor/index.d.ts +590 -3
  100. package/dist/image-editor/index.js +1807 -6
  101. package/dist/image-editor/index.js.map +1 -1
  102. package/dist/{index-CCr_z9kF.d.cts → index-BhJBSm2d.d.cts} +370 -4
  103. package/dist/{index-Ck6ajwXh.d.ts → index-fJrlkJCd.d.ts} +370 -4
  104. package/dist/index.cjs +162 -25
  105. package/dist/index.cjs.map +1 -1
  106. package/dist/index.d.cts +14 -8
  107. package/dist/index.d.ts +14 -8
  108. package/dist/index.js +174 -37
  109. package/dist/index.js.map +1 -1
  110. package/dist/migrate-iTlm3a8h.d.cts +112 -0
  111. package/dist/migrate-iTlm3a8h.d.ts +112 -0
  112. package/dist/persistence/index.cjs +676 -0
  113. package/dist/persistence/index.cjs.map +1 -0
  114. package/dist/persistence/index.d.cts +347 -0
  115. package/dist/persistence/index.d.ts +347 -0
  116. package/dist/persistence/index.js +676 -0
  117. package/dist/persistence/index.js.map +1 -0
  118. package/dist/{registry-D5-R90p0.d.cts → registry-BDe-vlv3.d.ts} +2 -40
  119. package/dist/registry-D32LpIc4.d.cts +354 -0
  120. package/dist/{registry-D5-R90p0.d.ts → registry-DnzpgY27.d.cts} +2 -40
  121. package/dist/registry-evKTP4ls.d.ts +354 -0
  122. package/dist/schema-DYDGdYL9.d.cts +41 -0
  123. package/dist/schema-DYDGdYL9.d.ts +41 -0
  124. package/dist/serializers/index.cjs +4 -3
  125. package/dist/serializers/index.cjs.map +1 -1
  126. package/dist/serializers/index.d.cts +2 -1
  127. package/dist/serializers/index.d.ts +2 -1
  128. package/dist/serializers/index.js +3 -2
  129. package/dist/sourcemap-C8AH3MBQ.d.cts +91 -0
  130. package/dist/sourcemap-DA1Evhrv.d.ts +91 -0
  131. package/dist/spreadsheet/index.cjs +37 -3
  132. package/dist/spreadsheet/index.cjs.map +1 -1
  133. package/dist/spreadsheet/index.d.cts +188 -17
  134. package/dist/spreadsheet/index.d.ts +188 -17
  135. package/dist/spreadsheet/index.js +43 -9
  136. package/dist/{tokens-CyLZmIei.d.cts → tokens-D7yXvUU6.d.cts} +15 -2
  137. package/dist/{tokens-CyLZmIei.d.ts → tokens-D7yXvUU6.d.ts} +15 -2
  138. package/dist/{track-changes-BSF58RK0.d.ts → track-changes-BK5sVf4_.d.cts} +18 -136
  139. package/dist/{track-changes-BSF58RK0.d.cts → track-changes-CDjCrwKe.d.ts} +18 -136
  140. package/package.json +16 -1
  141. package/dist/chunk-4SUNJZWB.cjs.map +0 -1
  142. package/dist/chunk-AA4ZEVCC.cjs.map +0 -1
  143. package/dist/chunk-AXV2ZW34.cjs +0 -118
  144. package/dist/chunk-AXV2ZW34.cjs.map +0 -1
  145. package/dist/chunk-B2ZRLN4S.js +0 -118
  146. package/dist/chunk-B2ZRLN4S.js.map +0 -1
  147. package/dist/chunk-GVSMRPHB.cjs.map +0 -1
  148. package/dist/chunk-K3R5NG2D.js.map +0 -1
  149. package/dist/chunk-MJZBNWG7.js.map +0 -1
  150. package/dist/chunk-OYXSKQFR.js.map +0 -1
  151. package/dist/chunk-QBJ22YZR.js.map +0 -1
  152. package/dist/chunk-QGHGJ56O.js.map +0 -1
  153. package/dist/chunk-RIKBJLS2.cjs.map +0 -1
  154. package/dist/chunk-XDMXXC4Z.cjs.map +0 -1
  155. /package/dist/{chunk-JJDLPEWL.js.map → chunk-3LV6RRPH.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/image-editor/index.cjs","../../src/image-editor/color.ts","../../src/image-editor/filters.ts","../../src/image-editor/geometry.ts","../../src/image-editor/renderer.ts","../../src/image-editor/controller.ts"],"names":["lu","lv","clamp"],"mappings":"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K,IAAA;AACL,MAAA;AACK,QAAA;AACD,UAAA;AACC,UAAA;AACJ,QAAA;AACE,MAAA;AAED,QAAA;AACF,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACK,YAAA;AACL,UAAA;AACA,UAAA;AACF,QAAA;AACE,UAAA;AACF,QAAA;AACF,MAAA;AACD,IAAA;AACH,EAAA;AAAA;AAIa,EAAA;AACJ,IAAA;AACT,EAAA;AAEa,EAAA;AACL,IAAA;AACC,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOa,EAAA;AACH,IAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACD,IAAA;AACC,IAAA;AACF,IAAA;AACG,IAAA;AACT,EAAA;AAEa,EAAA;AACL,IAAA;AACC,IAAA;AACT,EAAA;AAAA;AAAA;AAAA;AAAA;AAMa,EAAA;AACA,IAAA;AACb,EAAA;AAAA;AAAA;AAAA;AAAA;AAMa,EAAA;AACA,IAAA;AACb,EAAA;AAEc,EAAA;AACN,IAAA;AACD,IAAA;AAEC,IAAA;AACK,IAAA;AACA,IAAA;AAEN,IAAA;AACM,IAAA;AACA,IAAA;AAEA,IAAA;AACA,MAAA;AACF,QAAA;AACH,UAAA;AACK,YAAA;AACA,YAAA;AACH,YAAA;AACA,YAAA;AACF,UAAA;AACK,UAAA;AACA,UAAA;AACP,QAAA;AACF,MAAA;AACK,MAAA;AACP,IAAA;AAEI,IAAA;AACG,MAAA;AACA,MAAA;AACP,IAAA;AACK,IAAA;AACP,EAAA;AAAA;AAI4C,EAAA;AACnC,IAAA;AACT,EAAA;AAEgB,EAAA;AACT,IAAA;AACA,IAAA;AACA,IAAA;AACP,EAAA;AAEQ,EAAA;AACC,IAAA;AACI,MAAA;AACH,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACK,QAAA;AACH,QAAA;AACF,MAAA;AACA,MAAA;AACF,IAAA;AACF,EAAA;AAEQ,EAAA;AACK,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,MAAA;AACR,MAAA;AACF,IAAA;AACW,IAAA;AACb,EAAA;AACF;AAMS;AACuB,EAAA;AAC1B,EAAA;AACS,IAAA;AACF,IAAA;AACX,EAAA;AACO,EAAA;AACT;AAGS;AACG,EAAA;AACG,EAAA;AACA,EAAA;AACN,IAAA;AACM,MAAA;AACN,IAAA;AACM,MAAA;AACN,IAAA;AACM,MAAA;AACN,IAAA;AACM,MAAA;AACN,IAAA;AACA,IAAA;AACM,MAAA;AACN,IAAA;AACM,MAAA;AACb,EAAA;AACF;ALquCe;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/image-editor/index.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 { 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';\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, 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(imageData: ImageData, adjustments: ImageAdjustments): void {\n if (normalizeProcessVersion(adjustments.process) === LATEST_PROCESS_VERSION) {\n applyProcess2Adjustments(imageData, adjustments);\n } else {\n applyLegacyAdjustments(imageData, adjustments);\n }\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: ImageData, 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: ImageData, 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.\n */\nfunction applyFastBoxBlur(imageData: ImageData, 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","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 } = 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 = 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","import type {\n Annotation,\n CropBox,\n ExportOptions,\n ImageEditorState,\n ImageTransform,\n PenAnnotation,\n RectAnnotation,\n CircleAnnotation,\n ArrowAnnotation,\n LineAnnotation,\n TextAnnotation,\n ImageAnnotation,\n} from './types';\nimport { applyAdjustments, applyDepthMask } from './filters';\nimport { normalizeProcessVersion } from './color';\nimport {\n applyImageProjectionToContext,\n clampCropToImage,\n getAnnotationBounds,\n getRotatedSize,\n getViewportMetrics,\n getViewportProjection,\n normalizeAngle,\n type ImageProjection,\n type ViewportMetrics,\n} from './geometry';\n\nexport interface RenderOptions {\n viewportWidth?: number;\n viewportHeight?: number;\n showCropOverlay?: boolean;\n showSelectionOverlay?: boolean;\n interactive?: boolean;\n draftAnnotation?: Annotation | null;\n draftCrop?: CropBox | null;\n}\n\n/**\n * Image Cache to avoid refetching HTMLImageElement objects repeatedly during re-renders.\n */\nclass ImageCache {\n private cache = new Map<string, HTMLImageElement>();\n\n public async get(src: string): Promise<HTMLImageElement> {\n const existing = this.cache.get(src);\n if (existing && existing.complete) {\n return existing;\n }\n\n return new Promise((resolve, reject) => {\n if (typeof window === 'undefined' || typeof Image === 'undefined') {\n reject(new Error('Image loading is only supported in browser/DOM environments'));\n return;\n }\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.onload = () => {\n this.cache.set(src, img);\n resolve(img);\n };\n img.onerror = (e) => reject(new Error(`Failed to load image from: ${src}`));\n img.src = src;\n\n // In JSDOM or test environments where Image resource loading is inactive, resolve promptly\n if (typeof navigator !== 'undefined' && /jsdom/i.test(navigator.userAgent)) {\n setTimeout(() => {\n try {\n Object.defineProperty(img, 'naturalWidth', { value: 800, configurable: true });\n Object.defineProperty(img, 'naturalHeight', { value: 600, configurable: true });\n Object.defineProperty(img, 'complete', { value: true, configurable: true });\n } catch {\n // Ignore definition errors in restrictive environments\n }\n this.cache.set(src, img);\n resolve(img);\n }, 10);\n }\n });\n }\n\n public set(src: string, img: HTMLImageElement): void {\n this.cache.set(src, img);\n }\n\n public clear(): void {\n this.cache.clear();\n }\n}\n\nexport const globalImageCache = new ImageCache();\n\n/**\n * Renders an image with all its transformations, filters, depth masks, and annotations\n * to a target HTMLCanvasElement.\n */\nexport class ImageEditorRenderer {\n public imageCache = globalImageCache;\n private cachedBaseCanvas: HTMLCanvasElement | null = null;\n private cachedBaseKey = '';\n private sourceIds = new WeakMap<object, number>();\n private nextSourceId = 0;\n\n /**\n * Helper to create a canvas element safely in browser/JSDOM.\n */\n public createCanvas(width: number, height: number): HTMLCanvasElement {\n if (typeof document !== 'undefined' && document.createElement) {\n const c = document.createElement('canvas');\n c.width = Math.max(1, Math.round(width));\n c.height = Math.max(1, Math.round(height));\n return c;\n }\n // Fallback for non-DOM / headless\n return {\n width: Math.max(1, Math.round(width)),\n height: Math.max(1, Math.round(height)),\n getContext: () => null,\n toDataURL: () => '',\n toBlob: () => {},\n } as unknown as HTMLCanvasElement;\n }\n\n /**\n * Renders the editor state onto a canvas (viewport or preview).\n */\n public async renderToCanvas(\n targetCanvas: HTMLCanvasElement,\n imageSource: CanvasImageSource | string,\n state: ImageEditorState,\n options: RenderOptions = {}\n ): Promise<void> {\n const ctx = targetCanvas.getContext('2d');\n if (!ctx) return;\n\n let img: CanvasImageSource;\n if (typeof imageSource === 'string') {\n img = await this.imageCache.get(imageSource);\n } else {\n img = imageSource;\n }\n\n const { width: naturalWidth, height: naturalHeight } = getSourceSize(img);\n // Geometry helpers read `imageDimensions`; make sure they see the real source size so the\n // viewport drawn here and the hit-testing done by the UI share one projection.\n const geoState: ImageEditorState =\n state.imageDimensions.width === naturalWidth && state.imageDimensions.height === naturalHeight\n ? state\n : { ...state, imageDimensions: { width: naturalWidth, height: naturalHeight } };\n\n const { crop, transform, adjustments, depthMask, annotations, selectedAnnotationId, zoom, pan, isOriginalCompared } = state;\n\n // Viewport dimensions\n const vpWidth = options.viewportWidth || targetCanvas.width;\n const vpHeight = options.viewportHeight || targetCanvas.height;\n\n targetCanvas.width = vpWidth;\n targetCanvas.height = vpHeight;\n\n ctx.clearRect(0, 0, vpWidth, vpHeight);\n\n // If in compare mode, render original image directly and exit\n if (isOriginalCompared) {\n this.drawFittedImage(ctx, img, naturalWidth, naturalHeight, vpWidth, vpHeight, zoom, pan);\n return;\n }\n\n // When actively in crop tool or overlay requested, show full image so user can adjust the crop box\n const isActivelyCropping = Boolean(options.showCropOverlay || state.activeTool === 'crop');\n const projection = getViewportProjection(geoState, vpWidth, vpHeight, isActivelyCropping);\n const { region } = projection;\n const rotated = getRotatedSize(region.width, region.height, transform);\n\n // 1. Offscreen canvas for Base Image + Transform (Flip/Rotate) + Crop + Adjustments + Depth mask\n const baseKey = [\n this.getSourceId(img),\n state.sourceUrl,\n `${naturalWidth}x${naturalHeight}`,\n `${region.x},${region.y},${region.width},${region.height}`,\n `${projection.angle},${transform.flipH},${transform.flipV}`,\n `${adjustments.brightness},${adjustments.contrast},${adjustments.saturation},${adjustments.exposure},${adjustments.temperature},${adjustments.tint ?? 0},${adjustments.blur},${adjustments.opacity},p${normalizeProcessVersion(adjustments.process)}`,\n `${depthMask.enabled},${depthMask.maskSource},${depthMask.depthRange?.[0]}-${depthMask.depthRange?.[1]},${depthMask.softness},${depthMask.invert},${depthMask.mode}`,\n ].join('|');\n\n let baseCanvas = this.cachedBaseCanvas;\n if (!baseCanvas || this.cachedBaseKey !== baseKey) {\n baseCanvas = await this.renderBaseImage(img, naturalWidth, naturalHeight, region, transform, state);\n this.cachedBaseCanvas = baseCanvas;\n this.cachedBaseKey = baseKey;\n }\n\n // 2. Draw the base canvas into the viewport (centred, fitted, zoomed, panned)\n const drawW = rotated.width * projection.scale;\n const drawH = rotated.height * projection.scale;\n ctx.drawImage(baseCanvas, projection.originX - drawW / 2, projection.originY - drawH / 2, drawW, drawH);\n\n // 3. Annotations live in natural image space: draw them through the same projection,\n // clipped to the visible (cropped) region.\n if (annotations.length > 0 || options.draftAnnotation) {\n ctx.save();\n applyImageProjectionToContext(ctx, projection);\n ctx.beginPath();\n ctx.rect(region.x, region.y, region.width, region.height);\n ctx.clip();\n\n for (const ann of annotations) {\n await this.drawAnnotation(ctx, ann);\n if (options.showSelectionOverlay && ann.id === selectedAnnotationId) {\n this.drawAnnotationSelection(ctx, ann);\n }\n }\n\n // Render active live draft annotation\n if (options.draftAnnotation) {\n await this.drawAnnotation(ctx, options.draftAnnotation);\n }\n\n ctx.restore();\n }\n\n // 4. Draw Crop Grid Overlay if crop overlay is requested\n if (isActivelyCropping) {\n const activeCrop =\n options.draftCrop !== undefined && options.draftCrop !== null\n ? options.draftCrop\n : (crop ?? { x: 0, y: 0, width: naturalWidth, height: naturalHeight });\n this.drawCropOverlay(ctx, getViewportMetrics(geoState, vpWidth, vpHeight, true, activeCrop));\n }\n }\n\n /**\n * Draws `region` of the source image with rotation/flip applied, then pixel adjustments\n * and the depth mask (which receives the identical transform so it stays aligned).\n */\n private async renderBaseImage(\n img: CanvasImageSource,\n naturalWidth: number,\n naturalHeight: number,\n region: CropBox,\n transform: ImageTransform,\n state: Pick<ImageEditorState, 'adjustments' | 'depthMask'>\n ): Promise<HTMLCanvasElement> {\n const { adjustments, depthMask } = state;\n const rotated = getRotatedSize(region.width, region.height, transform);\n const baseW = Math.max(1, Math.round(rotated.width));\n const baseH = Math.max(1, Math.round(rotated.height));\n const baseCanvas = this.createCanvas(baseW, baseH);\n const baseCtx = baseCanvas.getContext('2d');\n if (!baseCtx) return baseCanvas;\n\n const projection = getBaseProjection(region, transform, baseW, baseH);\n\n baseCtx.save();\n applyImageProjectionToContext(baseCtx, projection);\n baseCtx.drawImage(\n img,\n region.x, region.y, region.width, region.height,\n region.x, region.y, region.width, region.height\n );\n baseCtx.restore();\n\n const imageData = baseCtx.getImageData(0, 0, baseW, baseH);\n if (imageData) {\n applyAdjustments(imageData, adjustments);\n\n if (depthMask.enabled && depthMask.maskSource) {\n try {\n const maskImg = await this.imageCache.get(depthMask.maskSource);\n const maskSize = getSourceSize(maskImg, naturalWidth, naturalHeight);\n const sx = maskSize.width / naturalWidth;\n const sy = maskSize.height / naturalHeight;\n const maskCanvas = this.createCanvas(baseW, baseH);\n const maskCtx = maskCanvas.getContext('2d');\n if (maskCtx) {\n // The depth map covers the whole source image; scale it to natural pixels and\n // apply the exact same rotate/flip/crop projection as the image itself.\n maskCtx.save();\n applyImageProjectionToContext(maskCtx, projection);\n maskCtx.drawImage(\n maskImg,\n region.x * sx, region.y * sy, region.width * sx, region.height * sy,\n region.x, region.y, region.width, region.height\n );\n maskCtx.restore();\n const maskData = maskCtx.getImageData(0, 0, baseW, baseH);\n if (maskData) {\n applyDepthMask(imageData, maskData, depthMask);\n }\n }\n } catch {\n // Mask failed to load; continue without crashing\n }\n }\n\n baseCtx.putImageData(imageData, 0, 0);\n }\n\n return baseCanvas;\n }\n\n private getSourceId(img: CanvasImageSource): number {\n const key = img as unknown as object;\n let id = this.sourceIds.get(key);\n if (id === undefined) {\n id = ++this.nextSourceId;\n this.sourceIds.set(key, id);\n }\n return id;\n }\n\n /**\n * Draws an individual annotation onto the canvas.\n */\n public async drawAnnotation(ctx: CanvasRenderingContext2D, ann: Annotation): Promise<void> {\n ctx.save();\n if (ann.opacity !== undefined) {\n ctx.globalAlpha = ann.opacity;\n }\n\n if (ann.rotation) {\n ctx.translate(ann.x, ann.y);\n ctx.rotate((ann.rotation * Math.PI) / 180);\n ctx.translate(-ann.x, -ann.y);\n }\n\n switch (ann.type) {\n case 'pen': {\n const pen = ann as PenAnnotation;\n if (pen.points.length > 0) {\n ctx.beginPath();\n ctx.strokeStyle = pen.strokeColor;\n ctx.lineWidth = pen.strokeWidth;\n ctx.lineCap = 'round';\n ctx.lineJoin = 'round';\n ctx.moveTo(pen.points[0].x, pen.points[0].y);\n for (let i = 1; i < pen.points.length; i++) {\n ctx.lineTo(pen.points[i].x, pen.points[i].y);\n }\n ctx.stroke();\n }\n break;\n }\n\n case 'rect': {\n const rect = ann as RectAnnotation;\n ctx.strokeStyle = rect.strokeColor;\n ctx.lineWidth = rect.strokeWidth;\n if (rect.fillColor) {\n ctx.fillStyle = rect.fillColor;\n ctx.fillRect(rect.x, rect.y, rect.width, rect.height);\n }\n if (rect.strokeWidth > 0) {\n ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);\n }\n break;\n }\n\n case 'circle': {\n const circle = ann as CircleAnnotation;\n ctx.beginPath();\n ctx.strokeStyle = circle.strokeColor;\n ctx.lineWidth = circle.strokeWidth;\n ctx.ellipse(circle.x, circle.y, Math.abs(circle.radiusX), Math.abs(circle.radiusY), 0, 0, 2 * Math.PI);\n if (circle.fillColor) {\n ctx.fillStyle = circle.fillColor;\n ctx.fill();\n }\n if (circle.strokeWidth > 0) {\n ctx.stroke();\n }\n break;\n }\n\n case 'arrow': {\n const arrow = ann as ArrowAnnotation;\n ctx.strokeStyle = arrow.strokeColor;\n ctx.fillStyle = arrow.strokeColor;\n ctx.lineWidth = arrow.strokeWidth;\n\n // Line body\n ctx.beginPath();\n ctx.moveTo(arrow.x, arrow.y);\n ctx.lineTo(arrow.endX, arrow.endY);\n ctx.stroke();\n\n // Arrow head\n const angle = Math.atan2(arrow.endY - arrow.y, arrow.endX - arrow.x);\n const headLen = Math.max(12, arrow.strokeWidth * 3);\n ctx.beginPath();\n ctx.moveTo(arrow.endX, arrow.endY);\n ctx.lineTo(\n arrow.endX - headLen * Math.cos(angle - Math.PI / 6),\n arrow.endY - headLen * Math.sin(angle - Math.PI / 6)\n );\n ctx.lineTo(\n arrow.endX - headLen * Math.cos(angle + Math.PI / 6),\n arrow.endY - headLen * Math.sin(angle + Math.PI / 6)\n );\n ctx.closePath();\n ctx.fill();\n break;\n }\n\n case 'line': {\n const line = ann as LineAnnotation;\n ctx.beginPath();\n ctx.strokeStyle = line.strokeColor;\n ctx.lineWidth = line.strokeWidth;\n ctx.moveTo(line.x, line.y);\n ctx.lineTo(line.endX, line.endY);\n ctx.stroke();\n break;\n }\n\n case 'text': {\n const textAnn = ann as TextAnnotation;\n ctx.font = `${textAnn.fontWeight || 'normal'} ${textAnn.fontSize}px ${textAnn.fontFamily || 'sans-serif'}`;\n ctx.fillStyle = textAnn.color;\n ctx.textBaseline = 'top';\n ctx.fillText(textAnn.text, textAnn.x, textAnn.y);\n break;\n }\n\n case 'image': {\n const imgAnn = ann as ImageAnnotation;\n try {\n const loadedImg = await this.imageCache.get(imgAnn.src);\n ctx.drawImage(loadedImg, imgAnn.x, imgAnn.y, imgAnn.width, imgAnn.height);\n } catch {\n // Skip if watermark image failed to load\n }\n break;\n }\n }\n\n ctx.restore();\n }\n\n /**\n * Draws selection box around active annotation.\n */\n private drawAnnotationSelection(ctx: CanvasRenderingContext2D, ann: Annotation): void {\n ctx.save();\n ctx.strokeStyle = '#3b82f6'; // Tailwind blue-500\n ctx.lineWidth = 1.5;\n ctx.setLineDash([4, 4]);\n\n const bounds = getAnnotationBounds(ann);\n const pad = 4;\n ctx.strokeRect(bounds.x - pad, bounds.y - pad, bounds.width + pad * 2, bounds.height + pad * 2);\n\n // Draw handles\n ctx.fillStyle = '#ffffff';\n ctx.strokeStyle = '#3b82f6';\n ctx.setLineDash([]);\n const handleSize = 6;\n const corners = [\n { x: bounds.x - pad, y: bounds.y - pad },\n { x: bounds.x + bounds.width + pad, y: bounds.y - pad },\n { x: bounds.x - pad, y: bounds.y + bounds.height + pad },\n { x: bounds.x + bounds.width + pad, y: bounds.y + bounds.height + pad },\n ];\n for (const c of corners) {\n ctx.fillRect(c.x - handleSize / 2, c.y - handleSize / 2, handleSize, handleSize);\n ctx.strokeRect(c.x - handleSize / 2, c.y - handleSize / 2, handleSize, handleSize);\n }\n\n ctx.restore();\n }\n\n /** @deprecated Use the standalone `getAnnotationBounds` from the geometry module. */\n public getAnnotationBounds(ann: Annotation): { x: number; y: number; width: number; height: number } {\n return getAnnotationBounds(ann);\n }\n\n /**\n * Draws a rule-of-thirds crop grid overlay. All rectangles come from\n * `getViewportMetrics`, i.e. the crop box projected through rotation/flip.\n */\n private drawCropOverlay(ctx: CanvasRenderingContext2D, metrics: ViewportMetrics): void {\n const { imageRect, cropRect } = metrics;\n if (!cropRect) return;\n\n const imgL = imageRect.x;\n const imgT = imageRect.y;\n const imgR = imageRect.x + imageRect.width;\n const imgB = imageRect.y + imageRect.height;\n const startX = cropRect.x;\n const startY = cropRect.y;\n const cropBoxW = cropRect.width;\n const cropBoxH = cropRect.height;\n\n ctx.save();\n\n // Dark semi-transparent scrim outside crop box (within the image)\n ctx.fillStyle = 'rgba(0, 0, 0, 0.45)';\n // Top\n ctx.fillRect(imgL, imgT, imageRect.width, Math.max(0, startY - imgT));\n // Bottom\n ctx.fillRect(imgL, startY + cropBoxH, imageRect.width, Math.max(0, imgB - (startY + cropBoxH)));\n // Left\n ctx.fillRect(imgL, startY, Math.max(0, startX - imgL), cropBoxH);\n // Right\n ctx.fillRect(startX + cropBoxW, startY, Math.max(0, imgR - (startX + cropBoxW)), cropBoxH);\n\n // Crop border\n ctx.strokeStyle = '#ffffff';\n ctx.lineWidth = 2;\n ctx.strokeRect(startX, startY, cropBoxW, cropBoxH);\n\n // Rule of thirds lines\n ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)';\n ctx.lineWidth = 1;\n ctx.beginPath();\n // Vertical lines\n ctx.moveTo(startX + cropBoxW / 3, startY);\n ctx.lineTo(startX + cropBoxW / 3, startY + cropBoxH);\n ctx.moveTo(startX + (cropBoxW * 2) / 3, startY);\n ctx.lineTo(startX + (cropBoxW * 2) / 3, startY + cropBoxH);\n // Horizontal lines\n ctx.moveTo(startX, startY + cropBoxH / 3);\n ctx.lineTo(startX + cropBoxW, startY + cropBoxH / 3);\n ctx.moveTo(startX, startY + (cropBoxH * 2) / 3);\n ctx.lineTo(startX + cropBoxW, startY + (cropBoxH * 2) / 3);\n ctx.stroke();\n\n // Corner and edge handles\n ctx.fillStyle = '#ffffff';\n ctx.strokeStyle = '#0284c7';\n ctx.lineWidth = 1.5;\n const handleSize = 10;\n const handles = [\n // Corners\n { x: startX, y: startY },\n { x: startX + cropBoxW, y: startY },\n { x: startX, y: startY + cropBoxH },\n { x: startX + cropBoxW, y: startY + cropBoxH },\n // Edges\n { x: startX + cropBoxW / 2, y: startY },\n { x: startX + cropBoxW / 2, y: startY + cropBoxH },\n { x: startX, y: startY + cropBoxH / 2 },\n { x: startX + cropBoxW, y: startY + cropBoxH / 2 },\n ];\n for (const h of handles) {\n ctx.fillRect(h.x - handleSize / 2, h.y - handleSize / 2, handleSize, handleSize);\n ctx.strokeRect(h.x - handleSize / 2, h.y - handleSize / 2, handleSize, handleSize);\n }\n\n ctx.restore();\n }\n\n private drawFittedImage(\n ctx: CanvasRenderingContext2D,\n img: CanvasImageSource,\n imgW: number,\n imgH: number,\n vpW: number,\n vpH: number,\n zoom: number,\n pan: { x: number; y: number }\n ): void {\n ctx.save();\n ctx.translate(vpW / 2 + pan.x, vpH / 2 + pan.y);\n ctx.scale(zoom, zoom);\n const fitScale = Math.min((vpW * 0.85) / imgW, (vpH * 0.85) / imgH, 1);\n const renderW = imgW * fitScale;\n const renderH = imgH * fitScale;\n ctx.drawImage(img, -renderW / 2, -renderH / 2, renderW, renderH);\n ctx.restore();\n }\n\n /**\n * Performs an export render at exact pixel dimensions without zoom/pan viewport offsets.\n */\n public async renderExport(\n imageSource: CanvasImageSource | string,\n state: ImageEditorState,\n options: ExportOptions = {}\n ): Promise<HTMLCanvasElement> {\n let img: CanvasImageSource;\n if (typeof imageSource === 'string') {\n img = await this.imageCache.get(imageSource);\n } else {\n img = imageSource;\n }\n\n const { width: naturalWidth, height: naturalHeight } = getSourceSize(img);\n const { crop, transform, annotations } = state;\n\n const region = crop\n ? clampCropToImage(crop, naturalWidth, naturalHeight)\n : { x: 0, y: 0, width: naturalWidth, height: naturalHeight };\n const rotated = getRotatedSize(region.width, region.height, transform);\n const baseW = Math.max(1, Math.round(rotated.width));\n const baseH = Math.max(1, Math.round(rotated.height));\n\n const exportW = options.width || baseW;\n const exportH = options.height || baseH;\n\n const exportCanvas = this.createCanvas(exportW, exportH);\n const ctx = exportCanvas.getContext('2d');\n if (!ctx) return exportCanvas;\n\n const baseCanvas = await this.renderBaseImage(img, naturalWidth, naturalHeight, region, transform, state);\n\n // Scale to target export dimensions\n ctx.drawImage(baseCanvas, 0, 0, baseW, baseH, 0, 0, exportW, exportH);\n\n // Draw annotations (natural image space) through the same rotate/flip/crop projection\n if (options.includeAnnotations !== false && annotations.length > 0) {\n ctx.save();\n ctx.scale(exportW / baseW, exportH / baseH);\n applyImageProjectionToContext(ctx, getBaseProjection(region, transform, baseW, baseH));\n for (const ann of annotations) {\n await this.drawAnnotation(ctx, ann);\n }\n ctx.restore();\n }\n\n return exportCanvas;\n }\n\n /**\n * Generates a strictly binary mask canvas (white = area to regenerate, black = keep) in\n * natural image pixel space of the current source image — the same space the crop box\n * and annotations are stored in. Pair it with `toSourceDataURL()` for AI inpainting.\n */\n public async renderMask(\n state: ImageEditorState,\n options?: { useAnnotations?: boolean; useCrop?: boolean }\n ): Promise<HTMLCanvasElement> {\n const naturalWidth = Math.max(1, Math.round(state.imageDimensions.width));\n const naturalHeight = Math.max(1, Math.round(state.imageDimensions.height));\n const maskCanvas = this.createCanvas(naturalWidth, naturalHeight);\n const ctx = maskCanvas.getContext('2d');\n if (!ctx) return maskCanvas;\n\n // Background: unmasked (black)\n ctx.fillStyle = MASK_BLACK;\n ctx.fillRect(0, 0, naturalWidth, naturalHeight);\n\n const useCrop = options?.useCrop ?? true;\n const useAnnotations = options?.useAnnotations ?? true;\n\n // Crop box mask (if active)\n if (useCrop && state.crop) {\n ctx.fillStyle = MASK_WHITE;\n ctx.fillRect(state.crop.x, state.crop.y, state.crop.width, state.crop.height);\n }\n\n // Annotation masks (drawn in solid white, never in annotation colours)\n if (useAnnotations && state.annotations.length > 0) {\n for (const ann of state.annotations) {\n this.drawAnnotationMask(ctx, ann);\n }\n }\n\n // Anti-aliased edges produce grey pixels: threshold to pure black/white.\n const imageData = ctx.getImageData?.(0, 0, naturalWidth, naturalHeight);\n if (imageData) {\n const d = imageData.data;\n for (let i = 0; i < d.length; i += 4) {\n const v = d[i] >= 128 ? 255 : 0;\n d[i] = v;\n d[i + 1] = v;\n d[i + 2] = v;\n d[i + 3] = 255;\n }\n ctx.putImageData(imageData, 0, 0);\n }\n\n return maskCanvas;\n }\n\n private drawAnnotationMask(ctx: CanvasRenderingContext2D, ann: Annotation): void {\n ctx.save();\n ctx.globalAlpha = 1;\n ctx.fillStyle = MASK_WHITE;\n ctx.strokeStyle = MASK_WHITE;\n ctx.lineCap = 'round';\n ctx.lineJoin = 'round';\n if (ann.rotation) {\n ctx.translate(ann.x, ann.y);\n ctx.rotate((ann.rotation * Math.PI) / 180);\n ctx.translate(-ann.x, -ann.y);\n }\n\n switch (ann.type) {\n case 'pen': {\n if (ann.points.length > 0) {\n ctx.beginPath();\n ctx.lineWidth = Math.max(ann.strokeWidth || 0, 8);\n ctx.moveTo(ann.points[0].x, ann.points[0].y);\n for (let i = 1; i < ann.points.length; i++) {\n ctx.lineTo(ann.points[i].x, ann.points[i].y);\n }\n ctx.stroke();\n }\n break;\n }\n case 'circle': {\n ctx.beginPath();\n ctx.ellipse(ann.x, ann.y, Math.abs(ann.radiusX), Math.abs(ann.radiusY), 0, 0, Math.PI * 2);\n ctx.fill();\n break;\n }\n case 'arrow':\n case 'line': {\n ctx.beginPath();\n ctx.lineWidth = Math.max(ann.strokeWidth || 0, 8);\n ctx.moveTo(ann.x, ann.y);\n ctx.lineTo(ann.endX, ann.endY);\n ctx.stroke();\n break;\n }\n case 'rect':\n case 'image':\n case 'text': {\n const b = getAnnotationBounds(ann);\n ctx.fillRect(b.x, b.y, b.width, b.height);\n break;\n }\n }\n ctx.restore();\n }\n}\n\nconst MASK_WHITE = '#ffffff';\nconst MASK_BLACK = '#000000';\n\nfunction getSourceSize(img: CanvasImageSource, fallbackW = 800, fallbackH = 600): { width: number; height: number } {\n const anyImg = img as any;\n return {\n width: anyImg.naturalWidth || anyImg.videoWidth || anyImg.width || fallbackW,\n height: anyImg.naturalHeight || anyImg.videoHeight || anyImg.height || fallbackH,\n };\n}\n\nfunction getBaseProjection(region: CropBox, transform: ImageTransform, baseW: number, baseH: number): ImageProjection {\n return {\n region,\n angle: normalizeAngle(transform.rotate),\n flipH: transform.flipH,\n flipV: transform.flipV,\n scale: 1,\n originX: baseW / 2,\n originY: baseH / 2,\n };\n}\n","import {\n Annotation,\n CreateAnnotationPayload,\n AspectRatio,\n CropBox,\n DepthMaskOptions,\n ExportOptions,\n ImageAdjustments,\n ImageDimensions,\n ImageEditorOptions,\n ImageEditorState,\n ImageEditorTool,\n ImageTransform,\n ImagePoint,\n SerializedImageEditorState,\n} from './types';\nimport { ImageEditorRenderer, RenderOptions } from './renderer';\nimport { clampCropToImage, isQuarterTurnSwapped } from './geometry';\nimport { LATEST_PROCESS_VERSION, LEGACY_PROCESS_VERSION, normalizeProcessVersion } from './color';\n\n/** The image a history entry was recorded against. */\ninterface SourceSnapshot {\n sourceUrl: string | null;\n element: CanvasImageSource | null;\n dimensions: ImageDimensions;\n}\n\n/**\n * Undo/redo entry: the serializable edit state plus the source image it applies to, so\n * AI edits that replace the image are restorable.\n */\ninterface HistoryEntry {\n state: SerializedImageEditorState;\n source: SourceSnapshot;\n}\n\nexport const DEFAULT_ADJUSTMENTS: 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\n/**\n * Fills in defaults for stored adjustments. A stored object without `process` was saved before\n * process versions existed and keeps the legacy rendering.\n */\nexport function normalizeStoredAdjustments(stored: Partial<ImageAdjustments> | undefined | null): ImageAdjustments {\n if (!stored) return { ...DEFAULT_ADJUSTMENTS };\n return {\n ...DEFAULT_ADJUSTMENTS,\n ...stored,\n process: normalizeProcessVersion(stored.process),\n };\n}\n\nexport const DEFAULT_TRANSFORM: ImageTransform = {\n rotate: 0,\n flipH: false,\n flipV: false,\n};\n\nexport const DEFAULT_DEPTH_MASK: DepthMaskOptions = {\n enabled: false,\n maskSource: null,\n mode: 'threshold',\n depthRange: [0, 255],\n softness: 10,\n invert: false,\n};\n\n/**\n * ImageEditorController manages the complete lifecycle, canvas rendering pipeline,\n * tool interaction states, undo/redo history, serialization, and image export.\n */\nexport class ImageEditorController {\n private state: ImageEditorState;\n private renderer: ImageEditorRenderer;\n private imageElement: CanvasImageSource | null = null;\n private listeners = new Set<(state: ImageEditorState) => void>();\n private onStateChange?: (state: ImageEditorState) => void;\n\n // History stacks\n private undoStack: HistoryEntry[] = [];\n private redoStack: HistoryEntry[] = [];\n private maxHistoryDepth: number;\n private isApplyingHistory = false;\n private initialSnapshot: HistoryEntry;\n\n constructor(options: ImageEditorOptions = {}) {\n this.maxHistoryDepth = options.maxHistoryDepth ?? 50;\n this.onStateChange = options.onStateChange;\n this.renderer = new ImageEditorRenderer();\n\n const initialAdjustments = normalizeStoredAdjustments(options.initialState?.adjustments);\n\n const initialTransform: ImageTransform = {\n ...DEFAULT_TRANSFORM,\n ...options.initialState?.transform,\n };\n\n const initialDepthMask: DepthMaskOptions = {\n ...DEFAULT_DEPTH_MASK,\n ...options.initialState?.depthMask,\n };\n\n this.state = {\n sourceUrl: typeof options.image === 'string' ? options.image : null,\n imageDimensions: { width: 800, height: 600 },\n crop: options.initialState?.crop ?? null,\n transform: initialTransform,\n adjustments: initialAdjustments,\n depthMask: initialDepthMask,\n annotations: options.initialState?.annotations ?? [],\n selectedAnnotationId: null,\n activeTool: 'select',\n zoom: 1,\n pan: { x: 0, y: 0 },\n isOriginalCompared: false,\n };\n\n if (options.image instanceof Object && 'width' in options.image) {\n this.imageElement = options.image as CanvasImageSource;\n this.state.sourceUrl = (options.image as any).src || null;\n this.state.imageDimensions = {\n width: (options.image as any).naturalWidth || (options.image as any).width || 800,\n height: (options.image as any).naturalHeight || (options.image as any).height || 600,\n };\n }\n\n this.initialSnapshot = this.captureHistoryEntry();\n\n if (typeof options.image === 'string') {\n this.loadImage(options.image);\n }\n }\n\n // --- Image Loading ---\n\n /**\n * Loads a new source image and makes it the baseline that `reset()` returns to.\n * Does not record history; use `applyInpaintedImage` / `applyBackgroundRemovedImage`\n * for undoable source replacement.\n */\n public async loadImage(src: string | HTMLImageElement): Promise<void> {\n // Keep the URL visible synchronously (UI bindings compare against it while loading).\n if (typeof src === 'string') this.state.sourceUrl = src;\n const source = await this.resolveSource(src);\n this.imageElement = source.element;\n this.state.sourceUrl = source.sourceUrl;\n this.state.imageDimensions = source.dimensions;\n\n this.initialSnapshot = this.captureHistoryEntry();\n this.notify();\n }\n\n private async resolveSource(src: string | HTMLImageElement): Promise<SourceSnapshot> {\n if (typeof src === 'string') {\n try {\n const img = await this.renderer.imageCache.get(src);\n return {\n sourceUrl: src,\n element: img,\n dimensions: {\n width: img.naturalWidth || img.width || 800,\n height: img.naturalHeight || img.height || 600,\n },\n };\n } catch {\n // In SSR / non-DOM environments, retain current dimensions\n return { sourceUrl: src, element: null, dimensions: { ...this.state.imageDimensions } };\n }\n }\n return {\n sourceUrl: (src as any).src || null,\n element: src,\n dimensions: {\n width: src.naturalWidth || src.width || 800,\n height: src.naturalHeight || src.height || 600,\n },\n };\n }\n\n public getImageElement(): CanvasImageSource | null {\n return this.imageElement;\n }\n\n // --- State & Subscription ---\n\n public getState(): ImageEditorState {\n return this.state;\n }\n\n public subscribe(listener: (state: ImageEditorState) => void): () => void {\n this.listeners.add(listener);\n listener(this.state);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private notify(): void {\n const currentState = { ...this.state };\n this.onStateChange?.(currentState);\n this.listeners.forEach((l) => l(currentState));\n }\n\n // --- History (Undo / Redo / Reset) ---\n\n private captureHistoryEntry(): HistoryEntry {\n return {\n state: this.serializeState(),\n source: {\n sourceUrl: this.state.sourceUrl,\n element: this.imageElement,\n dimensions: { ...this.state.imageDimensions },\n },\n };\n }\n\n private restoreHistoryEntry(entry: HistoryEntry): void {\n this.applySerializedState(entry.state);\n this.imageElement = entry.source.element;\n this.state.sourceUrl = entry.source.sourceUrl;\n this.state.imageDimensions = { ...entry.source.dimensions };\n if (\n this.state.selectedAnnotationId &&\n !this.state.annotations.some((a) => a.id === this.state.selectedAnnotationId)\n ) {\n this.state.selectedAnnotationId = null;\n }\n }\n\n private recordHistory(): void {\n if (this.isApplyingHistory) return;\n this.undoStack.push(this.captureHistoryEntry());\n if (this.undoStack.length > this.maxHistoryDepth) {\n this.undoStack.shift();\n }\n this.redoStack = [];\n }\n\n public get canUndo(): boolean {\n return this.undoStack.length > 0;\n }\n\n public get canRedo(): boolean {\n return this.redoStack.length > 0;\n }\n\n public undo(): boolean {\n if (!this.canUndo) return false;\n this.isApplyingHistory = true;\n this.redoStack.push(this.captureHistoryEntry());\n\n const prev = this.undoStack.pop()!;\n this.restoreHistoryEntry(prev);\n this.isApplyingHistory = false;\n this.notify();\n return true;\n }\n\n public redo(): boolean {\n if (!this.canRedo) return false;\n this.isApplyingHistory = true;\n this.undoStack.push(this.captureHistoryEntry());\n\n const next = this.redoStack.pop()!;\n this.restoreHistoryEntry(next);\n this.isApplyingHistory = false;\n this.notify();\n return true;\n }\n\n public reset(): void {\n this.recordHistory();\n this.restoreHistoryEntry(this.initialSnapshot);\n this.state.zoom = 1;\n this.state.pan = { x: 0, y: 0 };\n this.state.activeTool = 'select';\n this.state.selectedAnnotationId = null;\n this.notify();\n }\n\n // --- Active Tool ---\n\n public setTool(tool: ImageEditorTool): void {\n if (this.state.activeTool === tool) return;\n this.state.activeTool = tool;\n if (tool !== 'select') {\n this.state.selectedAnnotationId = null;\n }\n // If switching to crop tool and no crop is defined, initialize crop to full image\n if (tool === 'crop' && !this.state.crop) {\n this.state.crop = {\n x: 0,\n y: 0,\n width: this.state.imageDimensions.width,\n height: this.state.imageDimensions.height,\n };\n }\n this.notify();\n }\n\n // --- Crop ---\n\n public setCrop(crop: CropBox | null, addToHistory = true): void {\n if (addToHistory) {\n this.recordHistory();\n }\n this.state.crop = crop;\n this.notify();\n }\n\n public applyCropPreset(ratio: AspectRatio): void {\n this.recordHistory();\n const { width, height } = this.state.imageDimensions;\n // `ratio` is the on-screen aspect; the crop box lives in natural image space.\n if (ratio && isQuarterTurnSwapped(this.state.transform)) {\n ratio = 1 / ratio;\n }\n if (!ratio) {\n // Freeform: full image\n this.state.crop = { x: 0, y: 0, width, height };\n } else {\n let targetW = width;\n let targetH = targetW / ratio;\n\n if (targetH > height) {\n targetH = height;\n targetW = targetH * ratio;\n }\n\n const x = Math.max(0, Math.round((width - targetW) / 2));\n const y = Math.max(0, Math.round((height - targetH) / 2));\n this.state.crop = {\n x,\n y,\n width: Math.round(targetW),\n height: Math.round(targetH),\n };\n }\n this.notify();\n }\n\n public resetCrop(): void {\n this.recordHistory();\n this.state.crop = null;\n this.notify();\n }\n\n // --- Transformations (Rotate / Flip) ---\n\n public rotate(degrees: number): void {\n this.recordHistory();\n const current = this.state.transform.rotate;\n this.state.transform = { ...this.state.transform, rotate: (current + degrees) % 360 };\n this.notify();\n }\n\n public setRotation(degrees: number): void {\n this.recordHistory();\n this.state.transform = { ...this.state.transform, rotate: degrees % 360 };\n this.notify();\n }\n\n public flipHorizontal(): void {\n this.recordHistory();\n this.state.transform = { ...this.state.transform, flipH: !this.state.transform.flipH };\n this.notify();\n }\n\n public flipVertical(): void {\n this.recordHistory();\n this.state.transform = { ...this.state.transform, flipV: !this.state.transform.flipV };\n this.notify();\n }\n\n // --- Adjustments ---\n\n public setAdjustments(partial: Partial<ImageAdjustments>, commitToHistory = true): void {\n if (commitToHistory) {\n this.recordHistory();\n }\n this.state.adjustments = {\n ...this.state.adjustments,\n ...partial,\n };\n this.notify();\n }\n\n /** True when the adjustments use an older rendering process than {@link LATEST_PROCESS_VERSION}. */\n public get isProcessOutdated(): boolean {\n return normalizeProcessVersion(this.state.adjustments.process) < LATEST_PROCESS_VERSION;\n }\n\n /**\n * Update to Current Process: re-interprets the current adjustment values with the latest\n * rendering process (one undo step). Returns false when already current.\n */\n public upgradeProcess(): boolean {\n if (!this.isProcessOutdated) return false;\n this.setAdjustments({ process: LATEST_PROCESS_VERSION });\n return true;\n }\n\n public resetAdjustments(): void {\n this.recordHistory();\n this.state.adjustments = { ...DEFAULT_ADJUSTMENTS };\n this.notify();\n }\n\n // --- Depth Masking (Gleamforge) ---\n\n public setDepthMask(partial: Partial<DepthMaskOptions>): void {\n this.recordHistory();\n this.state.depthMask = {\n ...this.state.depthMask,\n ...partial,\n };\n this.notify();\n }\n\n public resetDepthMask(): void {\n this.recordHistory();\n this.state.depthMask = { ...DEFAULT_DEPTH_MASK };\n this.notify();\n }\n\n // --- Annotations & Layers ---\n\n public addAnnotation(annotation: CreateAnnotationPayload): string {\n this.recordHistory();\n const id = `ann_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;\n const fullAnnotation = { ...annotation, id } as Annotation;\n this.state.annotations = [...this.state.annotations, fullAnnotation];\n this.state.selectedAnnotationId = id;\n this.notify();\n return id;\n }\n\n public updateAnnotation(id: string, partial: Partial<Annotation>, commitToHistory = true): void {\n if (commitToHistory) {\n this.recordHistory();\n }\n this.state.annotations = this.state.annotations.map((ann) => {\n if (ann.id === id) {\n return { ...ann, ...partial } as Annotation;\n }\n return ann;\n });\n this.notify();\n }\n\n public removeAnnotation(id: string): void {\n this.recordHistory();\n this.state.annotations = this.state.annotations.filter((ann) => ann.id !== id);\n if (this.state.selectedAnnotationId === id) {\n this.state.selectedAnnotationId = null;\n }\n this.notify();\n }\n\n public clearAnnotations(): void {\n this.recordHistory();\n this.state.annotations = [];\n this.state.selectedAnnotationId = null;\n this.notify();\n }\n\n public selectAnnotation(id: string | null): void {\n this.state.selectedAnnotationId = id;\n this.notify();\n }\n\n // --- Zoom & Pan ---\n\n public setZoom(zoom: number): void {\n this.state.zoom = Math.max(0.1, Math.min(10, zoom));\n this.notify();\n }\n\n public zoomIn(factor = 1.2): void {\n this.setZoom(this.state.zoom * factor);\n }\n\n public zoomOut(factor = 1.2): void {\n this.setZoom(this.state.zoom / factor);\n }\n\n public resetZoom(): void {\n this.state.zoom = 1;\n this.state.pan = { x: 0, y: 0 };\n this.notify();\n }\n\n public setPan(pan: ImagePoint): void {\n this.state.pan = pan;\n this.notify();\n }\n\n public setComparing(comparing: boolean): void {\n if (this.state.isOriginalCompared === comparing) return;\n this.state.isOriginalCompared = comparing;\n this.notify();\n }\n\n // --- Rendering & Export ---\n\n public async render(targetCanvas: HTMLCanvasElement, options?: RenderOptions): Promise<void> {\n const source = this.imageElement || this.state.sourceUrl;\n if (!source) return;\n try {\n await this.renderer.renderToCanvas(targetCanvas, source, this.state, options);\n } catch {\n // In non-DOM or test mock environments without image loading, safely ignore\n }\n }\n\n public async renderExport(options?: ExportOptions): Promise<HTMLCanvasElement> {\n const source = this.imageElement || this.state.sourceUrl;\n if (!source) {\n return this.renderer.createCanvas(1, 1);\n }\n return this.renderer.renderExport(source, this.state, options);\n }\n\n public async toDataURL(options: ExportOptions = {}): Promise<string> {\n const canvas = await this.renderExport(options);\n const format = options.format || 'image/png';\n const quality = options.quality ?? 0.92;\n return canvas.toDataURL(format, quality);\n }\n\n public async toBlob(options: ExportOptions = {}): Promise<Blob> {\n const canvas = await this.renderExport(options);\n const format = options.format || 'image/png';\n const quality = options.quality ?? 0.92;\n\n return new Promise((resolve, reject) => {\n if (canvas.toBlob) {\n canvas.toBlob((blob) => {\n if (blob) resolve(blob);\n else reject(new Error('Failed to create Blob from canvas'));\n }, format, quality);\n } else {\n // Fallback for environments lacking canvas.toBlob\n try {\n const dataUrl = canvas.toDataURL(format, quality);\n const parts = dataUrl.split(',');\n const byteStr = atob(parts[1]);\n const ab = new ArrayBuffer(byteStr.length);\n const ia = new Uint8Array(ab);\n for (let i = 0; i < byteStr.length; i++) {\n ia[i] = byteStr.charCodeAt(i);\n }\n resolve(new Blob([ab], { type: format }));\n } catch (err) {\n reject(err);\n }\n }\n });\n }\n\n // --- AI Masking & Transformation Helpers ---\n\n public async renderMask(options?: { useAnnotations?: boolean; useCrop?: boolean }): Promise<HTMLCanvasElement> {\n return this.renderer.renderMask(this.state, options);\n }\n\n public async toMaskDataURL(options?: { useAnnotations?: boolean; useCrop?: boolean }): Promise<string> {\n const canvas = await this.renderMask(options);\n return canvas.toDataURL('image/png');\n }\n\n /**\n * Renders the current source image at natural size with no crop, rotation, flip,\n * adjustments or annotations. This is the image that `toMaskDataURL()` is aligned with\n * and the space that `applyInpaintedImage` / `applyBackgroundRemovedImage` expect back.\n */\n public async renderSource(): Promise<HTMLCanvasElement> {\n const { width, height } = this.state.imageDimensions;\n const canvas = this.renderer.createCanvas(width, height);\n const source = this.imageElement || this.state.sourceUrl;\n const ctx = canvas.getContext('2d');\n if (!ctx || !source) return canvas;\n const img = typeof source === 'string' ? await this.renderer.imageCache.get(source) : source;\n ctx.drawImage(img, 0, 0, width, height);\n return canvas;\n }\n\n public async toSourceDataURL(options: Pick<ExportOptions, 'format' | 'quality'> = {}): Promise<string> {\n const canvas = await this.renderSource();\n return canvas.toDataURL(options.format || 'image/png', options.quality ?? 0.92);\n }\n\n /**\n * Replaces the source image with an inpainted result (natural image space, see\n * `toSourceDataURL`) and clears the mask annotations, as ONE undoable step.\n */\n public async applyInpaintedImage(newDataUrl: string): Promise<void> {\n await this.replaceSource(newDataUrl, true);\n }\n\n /**\n * Replaces the source image with a background-removed result as ONE undoable step.\n * Crop and annotations are kept (rescaled if the new image has different dimensions).\n */\n public async applyBackgroundRemovedImage(newDataUrl: string): Promise<void> {\n await this.replaceSource(newDataUrl, false);\n }\n\n private async replaceSource(src: string, clearAnnotations: boolean): Promise<void> {\n const next = await this.resolveSource(src);\n this.recordHistory();\n\n const prev = this.state.imageDimensions;\n const sx = next.dimensions.width / (prev.width || next.dimensions.width);\n const sy = next.dimensions.height / (prev.height || next.dimensions.height);\n\n this.imageElement = next.element;\n this.state.sourceUrl = next.sourceUrl;\n this.state.imageDimensions = next.dimensions;\n\n if (sx !== 1 || sy !== 1) {\n if (this.state.crop) {\n this.state.crop = clampCropToImage(\n {\n x: Math.round(this.state.crop.x * sx),\n y: Math.round(this.state.crop.y * sy),\n width: Math.round(this.state.crop.width * sx),\n height: Math.round(this.state.crop.height * sy),\n },\n next.dimensions.width,\n next.dimensions.height\n );\n }\n this.state.annotations = this.state.annotations.map((ann) => scaleAnnotation(ann, sx, sy));\n }\n\n if (clearAnnotations) {\n this.state.annotations = [];\n this.state.selectedAnnotationId = null;\n }\n this.notify();\n }\n\n // --- Serialization ---\n\n public toJSON(): SerializedImageEditorState {\n return this.serializeState();\n }\n\n public loadJSON(data: SerializedImageEditorState): void {\n this.recordHistory();\n this.applySerializedState(data);\n this.notify();\n }\n\n private serializeState(): SerializedImageEditorState {\n return {\n version: 1,\n crop: this.state.crop ? { ...this.state.crop } : null,\n transform: { ...this.state.transform },\n adjustments: serializeAdjustments(this.state.adjustments),\n depthMask: {\n ...this.state.depthMask,\n depthRange: [...this.state.depthMask.depthRange] as [number, number],\n },\n annotations: JSON.parse(JSON.stringify(this.state.annotations)),\n };\n }\n\n private applySerializedState(data: SerializedImageEditorState): void {\n this.state.crop = data.crop ? { ...data.crop } : null;\n this.state.transform = { ...data.transform };\n this.state.adjustments = normalizeStoredAdjustments(data.adjustments);\n this.state.depthMask = {\n ...data.depthMask,\n depthRange: [...data.depthMask.depthRange] as [number, number],\n };\n this.state.annotations = JSON.parse(JSON.stringify(data.annotations || []));\n }\n}\n\n/**\n * Legacy-process adjustments are saved without `process` (and without an unused zero `tint`), so\n * they serialize exactly as they did before process versions existed.\n */\nfunction serializeAdjustments(adjustments: ImageAdjustments): ImageAdjustments {\n const out: ImageAdjustments = { ...adjustments };\n if (normalizeProcessVersion(out.process) === LEGACY_PROCESS_VERSION) {\n delete out.process;\n if (!out.tint) delete out.tint;\n }\n return out;\n}\n\n/** Scales an annotation's geometry from one image size to another. */\nfunction scaleAnnotation(ann: Annotation, sx: number, sy: number): Annotation {\n const k = Math.sqrt(Math.abs(sx * sy));\n const base = { ...ann, x: ann.x * sx, y: ann.y * sy } as Annotation;\n switch (base.type) {\n case 'pen':\n return { ...base, points: base.points.map((p) => ({ x: p.x * sx, y: p.y * sy })), strokeWidth: base.strokeWidth * k };\n case 'rect':\n return { ...base, width: base.width * sx, height: base.height * sy, strokeWidth: base.strokeWidth * k };\n case 'image':\n return { ...base, width: base.width * sx, height: base.height * sy };\n case 'circle':\n return { ...base, radiusX: base.radiusX * sx, radiusY: base.radiusY * sy, strokeWidth: base.strokeWidth * k };\n case 'arrow':\n case 'line':\n return { ...base, endX: base.endX * sx, endY: base.endY * sy, strokeWidth: base.strokeWidth * k };\n case 'text':\n return { ...base, fontSize: base.fontSize * k };\n }\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/image-editor/index.cjs","../../src/image-editor/color.ts","../../src/image-editor/blend.ts","../../src/image-editor/adjustment-layers.ts","../../src/image-editor/filters.ts","../../src/image-editor/histogram.ts","../../src/image-editor/geometry.ts","../../src/image-editor/renderer.ts","../../src/image-editor/controller.ts","../../src/image-editor/document.ts","../../src/image-editor/controls.ts","../../src/image-editor/tools.ts","../../src/image-editor/curve-editing.ts"],"names":["lu","lv","clamp","op","v"],"mappings":"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AA;AAEO,EAAA;AACa,IAAA;AACC,IAAA;AAC0D,IAAA;AAC/E,EAAA;AACF;AX0zGsF;AACA;AYvjHZ;AAG8B;AAC3B,EAAA;AAC7E;AAGiG;AACX,EAAA;AACtF;AAGiH;AAC1E,EAAA;AACd,EAAA;AACf,EAAA;AAC2B,EAAA;AACnB,IAAA;AACiB,IAAA;AACgD,IAAA;AACjF,EAAA;AACO,EAAA;AACT;AAUU;AACG,EAAA;AACU,EAAA;AACI,EAAA;AACmB,IAAA;AACF,IAAA;AACxB,IAAA;AACP,MAAA;AACC,MAAA;AACV,IAAA;AACD,EAAA;AACM,EAAA;AACT;AAGwH;AAC7C,EAAA;AACM,EAAA;AACzB,EAAA;AACxB,EAAA;AACJ,EAAA;AACE,EAAA;AAC9B;AAG0G;AAChE,EAAA;AACK,EAAA;AACK,EAAA;AACa,EAAA;AACgB,EAAA;AACxE,EAAA;AACT;AAG6F;AACT,EAAA;AACjB,EAAA;AACnE;AAEoD;AZmiHkC;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/image-editor/index.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","// Portions ported from photocraft-main/crates/color/src/blend.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\n/**\n * Photoshop blend modes on straight (non-premultiplied) 0..1 colours.\n *\n * Formulas follow the W3C Compositing / ISO 32000 definitions, with Photoshop's variants where\n * they differ (Soft Light, Vivid / Linear / Pin Light, Hard Mix, Linear Burn / Dodge, Subtract,\n * Divide, Darker / Lighter Color), and the hardened `ClipColor` for the non-separable modes.\n */\n\nexport type BlendMode =\n | 'normal'\n | 'darken'\n | 'multiply'\n | 'color-burn'\n | 'linear-burn'\n | 'darker-color'\n | 'lighten'\n | 'screen'\n | 'color-dodge'\n | 'linear-dodge'\n | 'lighter-color'\n | 'overlay'\n | 'soft-light'\n | 'hard-light'\n | 'vivid-light'\n | 'linear-light'\n | 'pin-light'\n | 'hard-mix'\n | 'difference'\n | 'exclusion'\n | 'subtract'\n | 'divide'\n | 'hue'\n | 'saturation'\n | 'color'\n | 'luminosity';\n\nexport interface BlendModeInfo {\n id: BlendMode;\n label: string;\n /** Canvas `globalCompositeOperation` with the same maths, when there is one. */\n canvas: GlobalCompositeOperation | null;\n}\n\n/** Every blend mode, in Photoshop's menu order. */\nexport const BLEND_MODES: readonly BlendModeInfo[] = [\n { id: 'normal', label: 'Normal', canvas: 'source-over' },\n { id: 'darken', label: 'Darken', canvas: 'darken' },\n { id: 'multiply', label: 'Multiply', canvas: 'multiply' },\n { id: 'color-burn', label: 'Color Burn', canvas: 'color-burn' },\n { id: 'linear-burn', label: 'Linear Burn', canvas: null },\n { id: 'darker-color', label: 'Darker Color', canvas: null },\n { id: 'lighten', label: 'Lighten', canvas: 'lighten' },\n { id: 'screen', label: 'Screen', canvas: 'screen' },\n { id: 'color-dodge', label: 'Color Dodge', canvas: 'color-dodge' },\n { id: 'linear-dodge', label: 'Linear Dodge (Add)', canvas: null },\n { id: 'lighter-color', label: 'Lighter Color', canvas: null },\n { id: 'overlay', label: 'Overlay', canvas: 'overlay' },\n // Canvas soft-light is the W3C formula, not Photoshop's: use the scalar path.\n { id: 'soft-light', label: 'Soft Light', canvas: null },\n { id: 'hard-light', label: 'Hard Light', canvas: 'hard-light' },\n { id: 'vivid-light', label: 'Vivid Light', canvas: null },\n { id: 'linear-light', label: 'Linear Light', canvas: null },\n { id: 'pin-light', label: 'Pin Light', canvas: null },\n { id: 'hard-mix', label: 'Hard Mix', canvas: null },\n { id: 'difference', label: 'Difference', canvas: 'difference' },\n { id: 'exclusion', label: 'Exclusion', canvas: 'exclusion' },\n { id: 'subtract', label: 'Subtract', canvas: null },\n { id: 'divide', label: 'Divide', canvas: null },\n { id: 'hue', label: 'Hue', canvas: 'hue' },\n { id: 'saturation', label: 'Saturation', canvas: 'saturation' },\n { id: 'color', label: 'Color', canvas: 'color' },\n { id: 'luminosity', label: 'Luminosity', canvas: 'luminosity' },\n];\n\nconst BLEND_INFO = new Map<string, BlendModeInfo>(BLEND_MODES.map((m) => [m.id, m]));\n\nexport function isBlendMode(value: unknown): value is BlendMode {\n return typeof value === 'string' && BLEND_INFO.has(value);\n}\n\n/** A stored blend mode, or `normal` for a missing / unknown value. */\nexport function normalizeBlendMode(value: unknown): BlendMode {\n return isBlendMode(value) ? value : 'normal';\n}\n\n/**\n * The canvas composite operation that implements `mode` exactly, or `null` when the canvas has\n * none (draw through {@link blendImageData} instead).\n */\nexport function canvasCompositeOperation(mode: BlendMode | undefined): GlobalCompositeOperation | null {\n return BLEND_INFO.get(mode ?? 'normal')?.canvas ?? null;\n}\n\n/** Whether a mode works on whole colours (hue, saturation, colour, luminosity, darker / lighter colour). */\nexport function isSeparableBlendMode(mode: BlendMode): boolean {\n return !(\n mode === 'hue' ||\n mode === 'saturation' ||\n mode === 'color' ||\n mode === 'luminosity' ||\n mode === 'darker-color' ||\n mode === 'lighter-color'\n );\n}\n\nfunction colorBurn(cb: number, cs: number): number {\n if (cb >= 1) return 1;\n if (cs <= 0) return 0;\n return 1 - Math.min(1, (1 - cb) / cs);\n}\n\nfunction colorDodge(cb: number, cs: number): number {\n if (cb <= 0) return 0;\n if (cs >= 1) return 1;\n return Math.min(1, cb / (1 - cs));\n}\n\nfunction hardLight(cb: number, cs: number): number {\n if (cs <= 0.5) return cb * 2 * cs;\n const s = 2 * cs - 1;\n return cb + s - cb * s;\n}\n\n/** Photoshop's Soft Light (differs from W3C in the upper half). */\nfunction softLight(cb: number, cs: number): number {\n return cs <= 0.5\n ? 2 * cb * cs + cb * cb * (1 - 2 * cs)\n : 2 * cb * (1 - cs) + Math.sqrt(Math.max(0, cb)) * (2 * cs - 1);\n}\n\nfunction vividLightHardMix(cb: number, cs: number): number {\n const EDGE = 1e-4;\n if (cs <= 0.5) {\n if (cb >= 1 - EDGE) return 1;\n if (cs <= 0) return 0;\n return 1 - Math.min(1, (1 - cb) / (2 * cs));\n }\n if (cb <= EDGE) return 0;\n if (cs >= 1) return 1;\n return Math.min(1, cb / (2 * (1 - cs)));\n}\n\n/** Separable blend function `B(cb, cs)` of one channel. Non-separable modes return `cs`. */\nexport function blendChannel(mode: BlendMode, cb: number, cs: number): number {\n switch (mode) {\n case 'normal':\n return cs;\n case 'darken':\n return Math.min(cb, cs);\n case 'multiply':\n return cb * cs;\n case 'color-burn':\n return colorBurn(cb, cs);\n case 'linear-burn':\n return Math.max(0, cb + cs - 1);\n case 'lighten':\n return Math.max(cb, cs);\n case 'screen':\n return cb + cs - cb * cs;\n case 'color-dodge':\n return colorDodge(cb, cs);\n case 'linear-dodge':\n return Math.min(1, cb + cs);\n case 'overlay':\n return hardLight(cs, cb);\n case 'soft-light':\n return softLight(cb, cs);\n case 'hard-light':\n return hardLight(cb, cs);\n case 'vivid-light':\n if (cs <= 0) return 0;\n if (cs >= 1) return 1;\n return cs <= 0.5 ? colorBurn(cb, 2 * cs) : colorDodge(cb, 2 * (cs - 0.5));\n case 'linear-light':\n return Math.min(1, Math.max(0, cb + 2 * cs - 1));\n case 'pin-light':\n return cs <= 0.5 ? Math.min(cb, 2 * cs) : Math.max(cb, 2 * cs - 1);\n case 'hard-mix':\n return vividLightHardMix(cb, cs) >= 0.5 - 1e-6 ? 1 : 0;\n case 'difference':\n return Math.abs(cb - cs);\n case 'exclusion':\n return cb + cs - 2 * cb * cs;\n case 'subtract':\n return Math.max(0, cb - cs);\n case 'divide':\n if (cs <= 0) return cb <= 0 ? 0 : 1;\n return Math.min(1, cb / cs);\n default:\n return cs;\n }\n}\n\nexport type Rgb = [number, number, number];\nexport type Rgba = [number, number, number, number];\n\n/** Luminosity of the non-separable modes (W3C / ISO 32000 weights). */\nexport function blendLum(c: readonly number[]): number {\n return 0.3 * c[0] + 0.59 * c[1] + 0.11 * c[2];\n}\n\n/**\n * ClipColor, hardened: when the luminosity itself is out of range no colour of that luminosity\n * is in gamut, so the result is its grey (the spec formula turned such colours inside out and,\n * for greys, divided by rounding noise).\n */\nfunction clipColor(c: Rgb): Rgb {\n const l = blendLum(c);\n const n = Math.min(c[0], c[1], c[2]);\n const x = Math.max(c[0], c[1], c[2]);\n let out: Rgb = [c[0], c[1], c[2]];\n if (n < 0) {\n const d = l - n;\n out = out.map((v) => (l > 0 && d > 0 ? l + ((v - l) * l) / d : l)) as Rgb;\n }\n if (x > 1) {\n const d = x - l;\n out = out.map((v) => (l < 1 && d > 0 ? l + ((v - l) * (1 - l)) / d : l)) as Rgb;\n }\n return out;\n}\n\n/** `c` with its luminosity moved to `l`, clipped back into gamut towards grey (SetLum + ClipColor). */\nexport function setLum(c: readonly number[], l: number): Rgb {\n const d = l - blendLum(c);\n return clipColor([c[0] + d, c[1] + d, c[2] + d]);\n}\n\nfunction sat(c: readonly number[]): number {\n return Math.max(c[0], c[1], c[2]) - Math.min(c[0], c[1], c[2]);\n}\n\nfunction setSat(c: readonly number[], s: number): Rgb {\n const idx = [0, 1, 2].sort((a, b) => c[a] - c[b]);\n const [imin, imid, imax] = idx;\n const out: Rgb = [0, 0, 0];\n if (c[imax] > c[imin]) {\n out[imid] = ((c[imid] - c[imin]) * s) / (c[imax] - c[imin]);\n out[imax] = s;\n }\n out[imin] = 0;\n return out;\n}\n\n/** `B(Cb, Cs)` of a whole colour, including the non-separable modes. */\nexport function blendRgb(mode: BlendMode, cb: readonly number[], cs: readonly number[]): Rgb {\n switch (mode) {\n case 'hue':\n return setLum(setSat(cs, sat(cb)), blendLum(cb));\n case 'saturation':\n return setLum(setSat(cb, sat(cs)), blendLum(cb));\n case 'color':\n return setLum(cs, blendLum(cb));\n case 'luminosity':\n return setLum(cb, blendLum(cs));\n case 'darker-color':\n return blendLum(cs) < blendLum(cb) ? [cs[0], cs[1], cs[2]] : [cb[0], cb[1], cb[2]];\n case 'lighter-color':\n return blendLum(cs) > blendLum(cb) ? [cs[0], cs[1], cs[2]] : [cb[0], cb[1], cb[2]];\n default:\n return [blendChannel(mode, cb[0], cs[0]), blendChannel(mode, cb[1], cs[1]), blendChannel(mode, cb[2], cs[2])];\n }\n}\n\n/**\n * Composites a straight-alpha `source` over a straight-alpha `backdrop` (both RGBA in 0..1) with a\n * blend mode and an extra `alpha` (opacity), using the W3C general formula:\n * `αo = αs + αb(1 − αs)`, `αo·Co = (1 − αs)·αb·Cb + (1 − αb)·αs·Cs + αs·αb·B(Cb, Cs)`.\n */\nexport function blendPixel(mode: BlendMode, backdrop: readonly number[], source: readonly number[], alpha = 1): Rgba {\n const ab = backdrop[3] ?? 1;\n const as = (source[3] ?? 1) * alpha;\n if (!(as > 0)) return [backdrop[0], backdrop[1], backdrop[2], ab];\n const b = blendRgb(mode, backdrop, source);\n const ao = as + ab * (1 - as);\n if (ao <= 0) return [0, 0, 0, 0];\n const out: Rgba = [0, 0, 0, ao];\n for (let i = 0; i < 3; i++) {\n out[i] = ((1 - as) * ab * backdrop[i] + (1 - ab) * as * source[i] + as * ab * b[i]) / ao;\n }\n return out;\n}\n\n/**\n * Blends `source` into `backdrop` in place (8-bit straight RGBA of the same size) with\n * {@link blendPixel}. The scalar fallback for modes the canvas cannot composite.\n */\nexport function blendImageData(backdrop: ImageData, source: ImageData, mode: BlendMode, alpha = 1): void {\n const d = backdrop.data;\n const s = source.data;\n const len = Math.min(d.length, s.length);\n const cb = [0, 0, 0, 0];\n const cs = [0, 0, 0, 0];\n for (let i = 0; i < len; i += 4) {\n if (s[i + 3] === 0) continue;\n for (let k = 0; k < 4; k++) {\n cb[k] = d[i + k] / 255;\n cs[k] = s[i + k] / 255;\n }\n const o = blendPixel(mode, cb, cs, alpha);\n d[i] = Math.round(o[0] * 255);\n d[i + 1] = Math.round(o[1] * 255);\n d[i + 2] = Math.round(o[2] * 255);\n d[i + 3] = Math.round(o[3] * 255);\n }\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 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, normalizeBlendMode, setLum } from './blend';\nimport { linearToSrgb, srgbToLinear } from './color';\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 } = {}\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 };\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 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/**\n * Photoshop curve: a natural cubic spline through the points (0..1 in and out; it may overshoot\n * between points, as Photoshop's does), constant outside the first / last point, clamped to\n * 0..1, sampled into a LUT. Fewer than two distinct points: the identity.\n */\nexport function curveLut(points: ReadonlyArray<{ input: number; output: number }>, size = ADJUSTMENT_LUT_SIZE): Float32Array {\n const pts = points\n .filter((p) => Number.isFinite(p.input) && Number.isFinite(p.output))\n .map((p) => [p.input, p.output] as [number, number])\n .sort((a, b) => a[0] - b[0]);\n const dedup: [number, number][] = [];\n for (const p of pts) {\n if (dedup.length && Math.abs(dedup[dedup.length - 1][0] - p[0]) < 1e-6) continue;\n dedup.push(p);\n }\n const out = new Float32Array(size);\n const n = dedup.length;\n if (n < 2) {\n for (let k = 0; k < size; k++) out[k] = k / (size - 1);\n return out;\n }\n const x = dedup.map((p) => p[0]);\n const y = dedup.map((p) => p[1]);\n // Second derivatives of the natural spline (tridiagonal solve).\n const m2 = new Float64Array(n);\n if (n > 2) {\n const c = new Float64Array(n);\n const d = new Float64Array(n);\n for (let i = 1; i < n - 1; i++) {\n const h0 = x[i] - x[i - 1];\n const h1 = x[i + 1] - x[i];\n const a = h0 / 6;\n const b = (h0 + h1) / 3;\n const cc = h1 / 6;\n const r = (y[i + 1] - y[i]) / h1 - (y[i] - y[i - 1]) / h0;\n const denom = b - a * c[i - 1];\n c[i] = cc / denom;\n d[i] = (r - a * d[i - 1]) / denom;\n }\n for (let i = n - 2; i >= 1; i--) m2[i] = d[i] - c[i] * m2[i + 1];\n }\n let seg = 0;\n for (let k = 0; k < size; k++) {\n const xv = k / (size - 1);\n if (xv <= x[0]) {\n out[k] = clamp01(y[0]);\n continue;\n }\n if (xv >= x[n - 1]) {\n out[k] = clamp01(y[n - 1]);\n continue;\n }\n while (seg < n - 2 && xv > x[seg + 1]) seg++;\n const x0 = x[seg], x1 = x[seg + 1];\n const h = x1 - x0;\n const a = (x1 - xv) / h;\n const b = (xv - x0) / h;\n const v = a * y[seg] + b * y[seg + 1] + ((a * a * a - a) * m2[seg] + (b * b * b - b) * m2[seg + 1]) * (h * h) / 6;\n out[k] = clamp01(v);\n }\n return out;\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). */\nconst 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. */\nconst 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): void {\n if (!op || layer.enabled === false) return;\n const alpha = Math.min(1, Math.max(0, layer.opacity ?? 1));\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 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 */\nexport function applyAdjustmentLayers(imageData: ImageData, layers: readonly AdjustmentLayer[] | undefined): void {\n const active = activeAdjustmentLayers(layers);\n if (!active.length) return;\n const ops = active.map((l) => compiledFor(l));\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++) applyLayer(c, active[k], ops[k]);\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) => [l.kind, l.enabled !== false, l.opacity ?? 1, l.blend ?? 'normal', l.params])\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(imageData: ImageData, adjustments: ImageAdjustments): 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 applyAdjustmentLayers(imageData, adjustments.layers);\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: ImageData, 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: ImageData, 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.\n */\nfunction applyFastBoxBlur(imageData: ImageData, 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","// Portions ported from photocraft-main/crates/algo/src/histogram.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\n/** Display bins of a histogram. */\nexport const HISTOGRAM_BINS = 256;\n\nexport interface ImageHistogram {\n /** 256-bin counts per channel. */\n red: Uint32Array;\n green: Uint32Array;\n blue: Uint32Array;\n /** 256-bin counts of Rec.709 luma `round(0.2126 R + 0.7152 G + 0.0722 B)` of the encoded values. */\n luminance: Uint32Array;\n /** Visible pixels counted (alpha > 0). Each counts once, partially transparent ones included. */\n samples: number;\n /** Fully transparent pixels, excluded from every bin. */\n transparent: number;\n /** Per channel (R, G, B): visible pixels at exactly 0. */\n shadows: [number, number, number];\n /** Per channel (R, G, B): visible pixels at exactly 255. */\n highlights: [number, number, number];\n /** Largest bin count over R, G, B and luminance (for scaling a plot). */\n max: number;\n}\n\ninterface PixelSource {\n data: ArrayLike<number>;\n width?: number;\n height?: number;\n}\n\n/**\n * Per-channel and luminance histograms of straight 8-bit RGBA pixels (an `ImageData` or anything\n * with `data`), with clipping counts at the exact endpoints. Transparent pixels cannot add\n * phantom peaks: they are counted separately and excluded.\n */\nexport function computeHistogram(imageData: PixelSource): ImageHistogram {\n const red = new Uint32Array(HISTOGRAM_BINS);\n const green = new Uint32Array(HISTOGRAM_BINS);\n const blue = new Uint32Array(HISTOGRAM_BINS);\n const luminance = new Uint32Array(HISTOGRAM_BINS);\n const shadows: [number, number, number] = [0, 0, 0];\n const highlights: [number, number, number] = [0, 0, 0];\n let samples = 0;\n let transparent = 0;\n const data = imageData.data;\n const len = data.length - (data.length % 4);\n for (let i = 0; i < len; i += 4) {\n if (!(data[i + 3] > 0)) {\n transparent++;\n continue;\n }\n samples++;\n const r = clampByte(data[i]);\n const g = clampByte(data[i + 1]);\n const b = clampByte(data[i + 2]);\n red[r]++;\n green[g]++;\n blue[b]++;\n luminance[Math.round(0.2126 * r + 0.7152 * g + 0.0722 * b)]++;\n if (r === 0) shadows[0]++;\n if (g === 0) shadows[1]++;\n if (b === 0) shadows[2]++;\n if (r === 255) highlights[0]++;\n if (g === 255) highlights[1]++;\n if (b === 255) highlights[2]++;\n }\n let max = 0;\n for (const bins of [red, green, blue, luminance]) {\n for (let k = 0; k < HISTOGRAM_BINS; k++) if (bins[k] > max) max = bins[k];\n }\n return { red, green, blue, luminance, samples, transparent, shadows, highlights, max };\n}\n\nfunction clampByte(v: number): number {\n return v <= 0 ? 0 : v >= 255 ? 255 : Math.round(v);\n}\n\nexport type HistogramChannel = 'red' | 'green' | 'blue' | 'luminance';\n\n/**\n * An SVG path (`M … Z`) of one channel scaled into a `width` × `height` box, bins left to right,\n * counts up from the bottom. `scale: 'sqrt'` (default) keeps small populations visible.\n */\nexport function histogramPath(\n histogram: ImageHistogram,\n channel: HistogramChannel,\n width: number,\n height: number,\n options: { scale?: 'linear' | 'sqrt'; max?: number } = {}\n): string {\n const bins = histogram[channel];\n const sqrt = (options.scale ?? 'sqrt') === 'sqrt';\n const f = (v: number) => (sqrt ? Math.sqrt(v) : v);\n const top = f(options.max ?? histogram.max) || 1;\n const dx = width / (HISTOGRAM_BINS - 1);\n let d = `M0 ${height}`;\n for (let k = 0; k < HISTOGRAM_BINS; k++) {\n const y = height - (f(bins[k]) / top) * height;\n d += ` L${round2(k * dx)} ${round2(y)}`;\n }\n return `${d} L${round2(width)} ${height} Z`;\n}\n\nconst round2 = (v: number) => Math.round(v * 100) / 100;\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 } = 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 = 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","import type {\n Annotation,\n CropBox,\n ExportOptions,\n ImageEditorState,\n ImageTransform,\n PenAnnotation,\n RectAnnotation,\n CircleAnnotation,\n ArrowAnnotation,\n LineAnnotation,\n TextAnnotation,\n ImageAnnotation,\n} from './types';\nimport { applyAdjustments, applyDepthMask } from './filters';\nimport { adjustmentLayersKey } from './adjustment-layers';\nimport { canvasCompositeOperation, blendImageData, normalizeBlendMode } from './blend';\nimport { computeHistogram, type ImageHistogram } from './histogram';\nimport { normalizeProcessVersion } from './color';\nimport {\n applyImageProjectionToContext,\n clampCropToImage,\n getAnnotationBounds,\n getRotatedSize,\n getViewportMetrics,\n getViewportProjection,\n normalizeAngle,\n type ImageProjection,\n type ViewportMetrics,\n} from './geometry';\n\nexport interface RenderOptions {\n viewportWidth?: number;\n viewportHeight?: number;\n showCropOverlay?: boolean;\n showSelectionOverlay?: boolean;\n interactive?: boolean;\n draftAnnotation?: Annotation | null;\n draftCrop?: CropBox | null;\n}\n\n/**\n * Image Cache to avoid refetching HTMLImageElement objects repeatedly during re-renders.\n */\nclass ImageCache {\n private cache = new Map<string, HTMLImageElement>();\n\n public async get(src: string): Promise<HTMLImageElement> {\n const existing = this.cache.get(src);\n if (existing && existing.complete) {\n return existing;\n }\n\n return new Promise((resolve, reject) => {\n if (typeof window === 'undefined' || typeof Image === 'undefined') {\n reject(new Error('Image loading is only supported in browser/DOM environments'));\n return;\n }\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.onload = () => {\n this.cache.set(src, img);\n resolve(img);\n };\n img.onerror = (e) => reject(new Error(`Failed to load image from: ${src}`));\n img.src = src;\n\n // In JSDOM or test environments where Image resource loading is inactive, resolve promptly\n if (typeof navigator !== 'undefined' && /jsdom/i.test(navigator.userAgent)) {\n setTimeout(() => {\n try {\n Object.defineProperty(img, 'naturalWidth', { value: 800, configurable: true });\n Object.defineProperty(img, 'naturalHeight', { value: 600, configurable: true });\n Object.defineProperty(img, 'complete', { value: true, configurable: true });\n } catch {\n // Ignore definition errors in restrictive environments\n }\n this.cache.set(src, img);\n resolve(img);\n }, 10);\n }\n });\n }\n\n public set(src: string, img: HTMLImageElement): void {\n this.cache.set(src, img);\n }\n\n public clear(): void {\n this.cache.clear();\n }\n}\n\nexport const globalImageCache = new ImageCache();\n\n/**\n * Renders an image with all its transformations, filters, depth masks, and annotations\n * to a target HTMLCanvasElement.\n */\nexport class ImageEditorRenderer {\n public imageCache = globalImageCache;\n private cachedBaseCanvas: HTMLCanvasElement | null = null;\n private cachedBaseKey = '';\n /**\n * Histogram of the last viewport base render (after adjustments and layers, before\n * annotations); `null` until a render with pixel access has happened.\n */\n public previewHistogram: ImageHistogram | null = null;\n /** Called whenever the viewport base image is re-rendered with the new histogram. */\n public onPreviewHistogram?: (histogram: ImageHistogram | null) => void;\n private sourceIds = new WeakMap<object, number>();\n private nextSourceId = 0;\n\n /**\n * Helper to create a canvas element safely in browser/JSDOM.\n */\n public createCanvas(width: number, height: number): HTMLCanvasElement {\n if (typeof document !== 'undefined' && document.createElement) {\n const c = document.createElement('canvas');\n c.width = Math.max(1, Math.round(width));\n c.height = Math.max(1, Math.round(height));\n return c;\n }\n // Fallback for non-DOM / headless\n return {\n width: Math.max(1, Math.round(width)),\n height: Math.max(1, Math.round(height)),\n getContext: () => null,\n toDataURL: () => '',\n toBlob: () => {},\n } as unknown as HTMLCanvasElement;\n }\n\n /**\n * Renders the editor state onto a canvas (viewport or preview).\n */\n public async renderToCanvas(\n targetCanvas: HTMLCanvasElement,\n imageSource: CanvasImageSource | string,\n state: ImageEditorState,\n options: RenderOptions = {}\n ): Promise<void> {\n const ctx = targetCanvas.getContext('2d');\n if (!ctx) return;\n\n let img: CanvasImageSource;\n if (typeof imageSource === 'string') {\n img = await this.imageCache.get(imageSource);\n } else {\n img = imageSource;\n }\n\n const { width: naturalWidth, height: naturalHeight } = getSourceSize(img);\n // Geometry helpers read `imageDimensions`; make sure they see the real source size so the\n // viewport drawn here and the hit-testing done by the UI share one projection.\n const geoState: ImageEditorState =\n state.imageDimensions.width === naturalWidth && state.imageDimensions.height === naturalHeight\n ? state\n : { ...state, imageDimensions: { width: naturalWidth, height: naturalHeight } };\n\n const { crop, transform, adjustments, depthMask, annotations, selectedAnnotationId, zoom, pan, isOriginalCompared } = state;\n\n // Viewport dimensions\n const vpWidth = options.viewportWidth || targetCanvas.width;\n const vpHeight = options.viewportHeight || targetCanvas.height;\n\n targetCanvas.width = vpWidth;\n targetCanvas.height = vpHeight;\n\n ctx.clearRect(0, 0, vpWidth, vpHeight);\n\n // If in compare mode, render original image directly and exit\n if (isOriginalCompared) {\n this.drawFittedImage(ctx, img, naturalWidth, naturalHeight, vpWidth, vpHeight, zoom, pan);\n return;\n }\n\n // When actively in crop tool or overlay requested, show full image so user can adjust the crop box\n const isActivelyCropping = Boolean(options.showCropOverlay || state.activeTool === 'crop');\n const projection = getViewportProjection(geoState, vpWidth, vpHeight, isActivelyCropping);\n const { region } = projection;\n const rotated = getRotatedSize(region.width, region.height, transform);\n\n // 1. Offscreen canvas for Base Image + Transform (Flip/Rotate) + Crop + Adjustments + Depth mask\n const baseKey = [\n this.getSourceId(img),\n state.sourceUrl,\n `${naturalWidth}x${naturalHeight}`,\n `${region.x},${region.y},${region.width},${region.height}`,\n `${projection.angle},${transform.flipH},${transform.flipV}`,\n `${adjustments.brightness},${adjustments.contrast},${adjustments.saturation},${adjustments.exposure},${adjustments.temperature},${adjustments.tint ?? 0},${adjustments.blur},${adjustments.opacity},p${normalizeProcessVersion(adjustments.process)}`,\n adjustmentLayersKey(adjustments.layers),\n `${depthMask.enabled},${depthMask.maskSource},${depthMask.depthRange?.[0]}-${depthMask.depthRange?.[1]},${depthMask.softness},${depthMask.invert},${depthMask.mode}`,\n ].join('|');\n\n let baseCanvas = this.cachedBaseCanvas;\n if (!baseCanvas || this.cachedBaseKey !== baseKey) {\n let histogram: ImageHistogram | null = null;\n baseCanvas = await this.renderBaseImage(img, naturalWidth, naturalHeight, region, transform, state, (pixels) => {\n histogram = computeHistogram(pixels);\n });\n this.cachedBaseCanvas = baseCanvas;\n this.cachedBaseKey = baseKey;\n this.previewHistogram = histogram;\n this.onPreviewHistogram?.(histogram);\n }\n\n // 2. Draw the base canvas into the viewport (centred, fitted, zoomed, panned)\n const drawW = rotated.width * projection.scale;\n const drawH = rotated.height * projection.scale;\n ctx.drawImage(baseCanvas, projection.originX - drawW / 2, projection.originY - drawH / 2, drawW, drawH);\n\n // 3. Annotations live in natural image space: draw them through the same projection,\n // clipped to the visible (cropped) region.\n if (annotations.length > 0 || options.draftAnnotation) {\n ctx.save();\n applyImageProjectionToContext(ctx, projection);\n ctx.beginPath();\n ctx.rect(region.x, region.y, region.width, region.height);\n ctx.clip();\n\n for (const ann of annotations) {\n await this.drawAnnotation(ctx, ann);\n if (options.showSelectionOverlay && ann.id === selectedAnnotationId) {\n this.drawAnnotationSelection(ctx, ann);\n }\n }\n\n // Render active live draft annotation\n if (options.draftAnnotation) {\n await this.drawAnnotation(ctx, options.draftAnnotation);\n }\n\n ctx.restore();\n }\n\n // 4. Draw Crop Grid Overlay if crop overlay is requested\n if (isActivelyCropping) {\n const activeCrop =\n options.draftCrop !== undefined && options.draftCrop !== null\n ? options.draftCrop\n : (crop ?? { x: 0, y: 0, width: naturalWidth, height: naturalHeight });\n this.drawCropOverlay(ctx, getViewportMetrics(geoState, vpWidth, vpHeight, true, activeCrop));\n }\n }\n\n /**\n * Draws `region` of the source image with rotation/flip applied, then pixel adjustments\n * and the depth mask (which receives the identical transform so it stays aligned).\n */\n private async renderBaseImage(\n img: CanvasImageSource,\n naturalWidth: number,\n naturalHeight: number,\n region: CropBox,\n transform: ImageTransform,\n state: Pick<ImageEditorState, 'adjustments' | 'depthMask'>,\n onPixels?: (pixels: ImageData) => void\n ): Promise<HTMLCanvasElement> {\n const { adjustments, depthMask } = state;\n const rotated = getRotatedSize(region.width, region.height, transform);\n const baseW = Math.max(1, Math.round(rotated.width));\n const baseH = Math.max(1, Math.round(rotated.height));\n const baseCanvas = this.createCanvas(baseW, baseH);\n const baseCtx = baseCanvas.getContext('2d');\n if (!baseCtx) return baseCanvas;\n\n const projection = getBaseProjection(region, transform, baseW, baseH);\n\n baseCtx.save();\n applyImageProjectionToContext(baseCtx, projection);\n baseCtx.drawImage(\n img,\n region.x, region.y, region.width, region.height,\n region.x, region.y, region.width, region.height\n );\n baseCtx.restore();\n\n const imageData = baseCtx.getImageData(0, 0, baseW, baseH);\n if (imageData) {\n applyAdjustments(imageData, adjustments);\n\n if (depthMask.enabled && depthMask.maskSource) {\n try {\n const maskImg = await this.imageCache.get(depthMask.maskSource);\n const maskSize = getSourceSize(maskImg, naturalWidth, naturalHeight);\n const sx = maskSize.width / naturalWidth;\n const sy = maskSize.height / naturalHeight;\n const maskCanvas = this.createCanvas(baseW, baseH);\n const maskCtx = maskCanvas.getContext('2d');\n if (maskCtx) {\n // The depth map covers the whole source image; scale it to natural pixels and\n // apply the exact same rotate/flip/crop projection as the image itself.\n maskCtx.save();\n applyImageProjectionToContext(maskCtx, projection);\n maskCtx.drawImage(\n maskImg,\n region.x * sx, region.y * sy, region.width * sx, region.height * sy,\n region.x, region.y, region.width, region.height\n );\n maskCtx.restore();\n const maskData = maskCtx.getImageData(0, 0, baseW, baseH);\n if (maskData) {\n applyDepthMask(imageData, maskData, depthMask);\n }\n }\n } catch {\n // Mask failed to load; continue without crashing\n }\n }\n\n onPixels?.(imageData);\n baseCtx.putImageData(imageData, 0, 0);\n }\n\n return baseCanvas;\n }\n\n private getSourceId(img: CanvasImageSource): number {\n const key = img as unknown as object;\n let id = this.sourceIds.get(key);\n if (id === undefined) {\n id = ++this.nextSourceId;\n this.sourceIds.set(key, id);\n }\n return id;\n }\n\n /**\n * Draws an individual annotation onto the canvas.\n */\n public async drawAnnotation(ctx: CanvasRenderingContext2D, ann: Annotation): Promise<void> {\n const blend = normalizeBlendMode(ann.blend);\n if (blend === 'normal') {\n await this.drawAnnotationContent(ctx, ann);\n return;\n }\n const op = canvasCompositeOperation(blend);\n if (op) {\n ctx.save();\n ctx.globalCompositeOperation = op;\n await this.drawAnnotationContent(ctx, ann);\n ctx.restore();\n return;\n }\n if (!(await this.drawAnnotationBlended(ctx, ann))) {\n // No pixel access (e.g. a tainted or headless canvas): draw it normally.\n await this.drawAnnotationContent(ctx, ann);\n }\n }\n\n /**\n * Scalar fallback for blend modes the canvas cannot composite (Photoshop soft light, linear /\n * vivid / pin light, hard mix, subtract, divide, darker / lighter colour, linear burn / dodge):\n * draws the annotation on a same-size layer with the same transform, blends it into a copy of\n * the target's pixels with `blendImageData`, and copies the result back inside the current clip.\n */\n private async drawAnnotationBlended(ctx: CanvasRenderingContext2D, ann: Annotation): Promise<boolean> {\n const canvas = ctx.canvas as HTMLCanvasElement | undefined;\n if (!canvas || typeof ctx.getTransform !== 'function' || typeof ctx.getImageData !== 'function') return false;\n const w = canvas.width;\n const h = canvas.height;\n const layer = this.createCanvas(w, h);\n const lctx = layer.getContext('2d');\n if (!lctx) return false;\n try {\n lctx.setTransform(ctx.getTransform());\n lctx.globalAlpha = ctx.globalAlpha;\n await this.drawAnnotationContent(lctx, ann);\n const backdrop = ctx.getImageData(0, 0, w, h);\n const source = lctx.getImageData(0, 0, w, h);\n if (!backdrop || !source) return false;\n blendImageData(backdrop, source, normalizeBlendMode(ann.blend));\n lctx.setTransform(1, 0, 0, 1, 0, 0);\n lctx.putImageData(backdrop, 0, 0);\n } catch {\n return false;\n }\n ctx.save();\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.globalAlpha = 1;\n ctx.globalCompositeOperation = 'copy';\n ctx.drawImage(layer, 0, 0);\n ctx.restore();\n return true;\n }\n\n private async drawAnnotationContent(ctx: CanvasRenderingContext2D, ann: Annotation): Promise<void> {\n ctx.save();\n if (ann.opacity !== undefined) {\n ctx.globalAlpha = ann.opacity;\n }\n\n if (ann.rotation) {\n ctx.translate(ann.x, ann.y);\n ctx.rotate((ann.rotation * Math.PI) / 180);\n ctx.translate(-ann.x, -ann.y);\n }\n\n switch (ann.type) {\n case 'pen': {\n const pen = ann as PenAnnotation;\n if (pen.points.length > 0) {\n ctx.beginPath();\n ctx.strokeStyle = pen.strokeColor;\n ctx.lineWidth = pen.strokeWidth;\n ctx.lineCap = 'round';\n ctx.lineJoin = 'round';\n ctx.moveTo(pen.points[0].x, pen.points[0].y);\n for (let i = 1; i < pen.points.length; i++) {\n ctx.lineTo(pen.points[i].x, pen.points[i].y);\n }\n ctx.stroke();\n }\n break;\n }\n\n case 'rect': {\n const rect = ann as RectAnnotation;\n ctx.strokeStyle = rect.strokeColor;\n ctx.lineWidth = rect.strokeWidth;\n if (rect.fillColor) {\n ctx.fillStyle = rect.fillColor;\n ctx.fillRect(rect.x, rect.y, rect.width, rect.height);\n }\n if (rect.strokeWidth > 0) {\n ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);\n }\n break;\n }\n\n case 'circle': {\n const circle = ann as CircleAnnotation;\n ctx.beginPath();\n ctx.strokeStyle = circle.strokeColor;\n ctx.lineWidth = circle.strokeWidth;\n ctx.ellipse(circle.x, circle.y, Math.abs(circle.radiusX), Math.abs(circle.radiusY), 0, 0, 2 * Math.PI);\n if (circle.fillColor) {\n ctx.fillStyle = circle.fillColor;\n ctx.fill();\n }\n if (circle.strokeWidth > 0) {\n ctx.stroke();\n }\n break;\n }\n\n case 'arrow': {\n const arrow = ann as ArrowAnnotation;\n ctx.strokeStyle = arrow.strokeColor;\n ctx.fillStyle = arrow.strokeColor;\n ctx.lineWidth = arrow.strokeWidth;\n\n // Line body\n ctx.beginPath();\n ctx.moveTo(arrow.x, arrow.y);\n ctx.lineTo(arrow.endX, arrow.endY);\n ctx.stroke();\n\n // Arrow head\n const angle = Math.atan2(arrow.endY - arrow.y, arrow.endX - arrow.x);\n const headLen = Math.max(12, arrow.strokeWidth * 3);\n ctx.beginPath();\n ctx.moveTo(arrow.endX, arrow.endY);\n ctx.lineTo(\n arrow.endX - headLen * Math.cos(angle - Math.PI / 6),\n arrow.endY - headLen * Math.sin(angle - Math.PI / 6)\n );\n ctx.lineTo(\n arrow.endX - headLen * Math.cos(angle + Math.PI / 6),\n arrow.endY - headLen * Math.sin(angle + Math.PI / 6)\n );\n ctx.closePath();\n ctx.fill();\n break;\n }\n\n case 'line': {\n const line = ann as LineAnnotation;\n ctx.beginPath();\n ctx.strokeStyle = line.strokeColor;\n ctx.lineWidth = line.strokeWidth;\n ctx.moveTo(line.x, line.y);\n ctx.lineTo(line.endX, line.endY);\n ctx.stroke();\n break;\n }\n\n case 'text': {\n const textAnn = ann as TextAnnotation;\n ctx.font = `${textAnn.fontWeight || 'normal'} ${textAnn.fontSize}px ${textAnn.fontFamily || 'sans-serif'}`;\n ctx.fillStyle = textAnn.color;\n ctx.textBaseline = 'top';\n ctx.fillText(textAnn.text, textAnn.x, textAnn.y);\n break;\n }\n\n case 'image': {\n const imgAnn = ann as ImageAnnotation;\n try {\n const loadedImg = await this.imageCache.get(imgAnn.src);\n ctx.drawImage(loadedImg, imgAnn.x, imgAnn.y, imgAnn.width, imgAnn.height);\n } catch {\n // Skip if watermark image failed to load\n }\n break;\n }\n }\n\n ctx.restore();\n }\n\n /**\n * Draws selection box around active annotation.\n */\n private drawAnnotationSelection(ctx: CanvasRenderingContext2D, ann: Annotation): void {\n ctx.save();\n ctx.strokeStyle = '#3b82f6'; // Tailwind blue-500\n ctx.lineWidth = 1.5;\n ctx.setLineDash([4, 4]);\n\n const bounds = getAnnotationBounds(ann);\n const pad = 4;\n ctx.strokeRect(bounds.x - pad, bounds.y - pad, bounds.width + pad * 2, bounds.height + pad * 2);\n\n // Draw handles\n ctx.fillStyle = '#ffffff';\n ctx.strokeStyle = '#3b82f6';\n ctx.setLineDash([]);\n const handleSize = 6;\n const corners = [\n { x: bounds.x - pad, y: bounds.y - pad },\n { x: bounds.x + bounds.width + pad, y: bounds.y - pad },\n { x: bounds.x - pad, y: bounds.y + bounds.height + pad },\n { x: bounds.x + bounds.width + pad, y: bounds.y + bounds.height + pad },\n ];\n for (const c of corners) {\n ctx.fillRect(c.x - handleSize / 2, c.y - handleSize / 2, handleSize, handleSize);\n ctx.strokeRect(c.x - handleSize / 2, c.y - handleSize / 2, handleSize, handleSize);\n }\n\n ctx.restore();\n }\n\n /** @deprecated Use the standalone `getAnnotationBounds` from the geometry module. */\n public getAnnotationBounds(ann: Annotation): { x: number; y: number; width: number; height: number } {\n return getAnnotationBounds(ann);\n }\n\n /**\n * Draws a rule-of-thirds crop grid overlay. All rectangles come from\n * `getViewportMetrics`, i.e. the crop box projected through rotation/flip.\n */\n private drawCropOverlay(ctx: CanvasRenderingContext2D, metrics: ViewportMetrics): void {\n const { imageRect, cropRect } = metrics;\n if (!cropRect) return;\n\n const imgL = imageRect.x;\n const imgT = imageRect.y;\n const imgR = imageRect.x + imageRect.width;\n const imgB = imageRect.y + imageRect.height;\n const startX = cropRect.x;\n const startY = cropRect.y;\n const cropBoxW = cropRect.width;\n const cropBoxH = cropRect.height;\n\n ctx.save();\n\n // Dark semi-transparent scrim outside crop box (within the image)\n ctx.fillStyle = 'rgba(0, 0, 0, 0.45)';\n // Top\n ctx.fillRect(imgL, imgT, imageRect.width, Math.max(0, startY - imgT));\n // Bottom\n ctx.fillRect(imgL, startY + cropBoxH, imageRect.width, Math.max(0, imgB - (startY + cropBoxH)));\n // Left\n ctx.fillRect(imgL, startY, Math.max(0, startX - imgL), cropBoxH);\n // Right\n ctx.fillRect(startX + cropBoxW, startY, Math.max(0, imgR - (startX + cropBoxW)), cropBoxH);\n\n // Crop border\n ctx.strokeStyle = '#ffffff';\n ctx.lineWidth = 2;\n ctx.strokeRect(startX, startY, cropBoxW, cropBoxH);\n\n // Rule of thirds lines\n ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)';\n ctx.lineWidth = 1;\n ctx.beginPath();\n // Vertical lines\n ctx.moveTo(startX + cropBoxW / 3, startY);\n ctx.lineTo(startX + cropBoxW / 3, startY + cropBoxH);\n ctx.moveTo(startX + (cropBoxW * 2) / 3, startY);\n ctx.lineTo(startX + (cropBoxW * 2) / 3, startY + cropBoxH);\n // Horizontal lines\n ctx.moveTo(startX, startY + cropBoxH / 3);\n ctx.lineTo(startX + cropBoxW, startY + cropBoxH / 3);\n ctx.moveTo(startX, startY + (cropBoxH * 2) / 3);\n ctx.lineTo(startX + cropBoxW, startY + (cropBoxH * 2) / 3);\n ctx.stroke();\n\n // Corner and edge handles\n ctx.fillStyle = '#ffffff';\n ctx.strokeStyle = '#0284c7';\n ctx.lineWidth = 1.5;\n const handleSize = 10;\n const handles = [\n // Corners\n { x: startX, y: startY },\n { x: startX + cropBoxW, y: startY },\n { x: startX, y: startY + cropBoxH },\n { x: startX + cropBoxW, y: startY + cropBoxH },\n // Edges\n { x: startX + cropBoxW / 2, y: startY },\n { x: startX + cropBoxW / 2, y: startY + cropBoxH },\n { x: startX, y: startY + cropBoxH / 2 },\n { x: startX + cropBoxW, y: startY + cropBoxH / 2 },\n ];\n for (const h of handles) {\n ctx.fillRect(h.x - handleSize / 2, h.y - handleSize / 2, handleSize, handleSize);\n ctx.strokeRect(h.x - handleSize / 2, h.y - handleSize / 2, handleSize, handleSize);\n }\n\n ctx.restore();\n }\n\n private drawFittedImage(\n ctx: CanvasRenderingContext2D,\n img: CanvasImageSource,\n imgW: number,\n imgH: number,\n vpW: number,\n vpH: number,\n zoom: number,\n pan: { x: number; y: number }\n ): void {\n ctx.save();\n ctx.translate(vpW / 2 + pan.x, vpH / 2 + pan.y);\n ctx.scale(zoom, zoom);\n const fitScale = Math.min((vpW * 0.85) / imgW, (vpH * 0.85) / imgH, 1);\n const renderW = imgW * fitScale;\n const renderH = imgH * fitScale;\n ctx.drawImage(img, -renderW / 2, -renderH / 2, renderW, renderH);\n ctx.restore();\n }\n\n /**\n * Performs an export render at exact pixel dimensions without zoom/pan viewport offsets.\n */\n public async renderExport(\n imageSource: CanvasImageSource | string,\n state: ImageEditorState,\n options: ExportOptions = {}\n ): Promise<HTMLCanvasElement> {\n let img: CanvasImageSource;\n if (typeof imageSource === 'string') {\n img = await this.imageCache.get(imageSource);\n } else {\n img = imageSource;\n }\n\n const { width: naturalWidth, height: naturalHeight } = getSourceSize(img);\n const { crop, transform, annotations } = state;\n\n const region = crop\n ? clampCropToImage(crop, naturalWidth, naturalHeight)\n : { x: 0, y: 0, width: naturalWidth, height: naturalHeight };\n const rotated = getRotatedSize(region.width, region.height, transform);\n const baseW = Math.max(1, Math.round(rotated.width));\n const baseH = Math.max(1, Math.round(rotated.height));\n\n const exportW = options.width || baseW;\n const exportH = options.height || baseH;\n\n const exportCanvas = this.createCanvas(exportW, exportH);\n const ctx = exportCanvas.getContext('2d');\n if (!ctx) return exportCanvas;\n\n const baseCanvas = await this.renderBaseImage(img, naturalWidth, naturalHeight, region, transform, state);\n\n // Scale to target export dimensions\n ctx.drawImage(baseCanvas, 0, 0, baseW, baseH, 0, 0, exportW, exportH);\n\n // Draw annotations (natural image space) through the same rotate/flip/crop projection\n if (options.includeAnnotations !== false && annotations.length > 0) {\n ctx.save();\n ctx.scale(exportW / baseW, exportH / baseH);\n applyImageProjectionToContext(ctx, getBaseProjection(region, transform, baseW, baseH));\n for (const ann of annotations) {\n await this.drawAnnotation(ctx, ann);\n }\n ctx.restore();\n }\n\n return exportCanvas;\n }\n\n /**\n * Generates a strictly binary mask canvas (white = area to regenerate, black = keep) in\n * natural image pixel space of the current source image — the same space the crop box\n * and annotations are stored in. Pair it with `toSourceDataURL()` for AI inpainting.\n */\n public async renderMask(\n state: ImageEditorState,\n options?: { useAnnotations?: boolean; useCrop?: boolean }\n ): Promise<HTMLCanvasElement> {\n const naturalWidth = Math.max(1, Math.round(state.imageDimensions.width));\n const naturalHeight = Math.max(1, Math.round(state.imageDimensions.height));\n const maskCanvas = this.createCanvas(naturalWidth, naturalHeight);\n const ctx = maskCanvas.getContext('2d');\n if (!ctx) return maskCanvas;\n\n // Background: unmasked (black)\n ctx.fillStyle = MASK_BLACK;\n ctx.fillRect(0, 0, naturalWidth, naturalHeight);\n\n const useCrop = options?.useCrop ?? true;\n const useAnnotations = options?.useAnnotations ?? true;\n\n // Crop box mask (if active)\n if (useCrop && state.crop) {\n ctx.fillStyle = MASK_WHITE;\n ctx.fillRect(state.crop.x, state.crop.y, state.crop.width, state.crop.height);\n }\n\n // Annotation masks (drawn in solid white, never in annotation colours)\n if (useAnnotations && state.annotations.length > 0) {\n for (const ann of state.annotations) {\n this.drawAnnotationMask(ctx, ann);\n }\n }\n\n // Anti-aliased edges produce grey pixels: threshold to pure black/white.\n const imageData = ctx.getImageData?.(0, 0, naturalWidth, naturalHeight);\n if (imageData) {\n const d = imageData.data;\n for (let i = 0; i < d.length; i += 4) {\n const v = d[i] >= 128 ? 255 : 0;\n d[i] = v;\n d[i + 1] = v;\n d[i + 2] = v;\n d[i + 3] = 255;\n }\n ctx.putImageData(imageData, 0, 0);\n }\n\n return maskCanvas;\n }\n\n private drawAnnotationMask(ctx: CanvasRenderingContext2D, ann: Annotation): void {\n ctx.save();\n ctx.globalAlpha = 1;\n ctx.fillStyle = MASK_WHITE;\n ctx.strokeStyle = MASK_WHITE;\n ctx.lineCap = 'round';\n ctx.lineJoin = 'round';\n if (ann.rotation) {\n ctx.translate(ann.x, ann.y);\n ctx.rotate((ann.rotation * Math.PI) / 180);\n ctx.translate(-ann.x, -ann.y);\n }\n\n switch (ann.type) {\n case 'pen': {\n if (ann.points.length > 0) {\n ctx.beginPath();\n ctx.lineWidth = Math.max(ann.strokeWidth || 0, 8);\n ctx.moveTo(ann.points[0].x, ann.points[0].y);\n for (let i = 1; i < ann.points.length; i++) {\n ctx.lineTo(ann.points[i].x, ann.points[i].y);\n }\n ctx.stroke();\n }\n break;\n }\n case 'circle': {\n ctx.beginPath();\n ctx.ellipse(ann.x, ann.y, Math.abs(ann.radiusX), Math.abs(ann.radiusY), 0, 0, Math.PI * 2);\n ctx.fill();\n break;\n }\n case 'arrow':\n case 'line': {\n ctx.beginPath();\n ctx.lineWidth = Math.max(ann.strokeWidth || 0, 8);\n ctx.moveTo(ann.x, ann.y);\n ctx.lineTo(ann.endX, ann.endY);\n ctx.stroke();\n break;\n }\n case 'rect':\n case 'image':\n case 'text': {\n const b = getAnnotationBounds(ann);\n ctx.fillRect(b.x, b.y, b.width, b.height);\n break;\n }\n }\n ctx.restore();\n }\n}\n\nconst MASK_WHITE = '#ffffff';\nconst MASK_BLACK = '#000000';\n\nfunction getSourceSize(img: CanvasImageSource, fallbackW = 800, fallbackH = 600): { width: number; height: number } {\n const anyImg = img as any;\n return {\n width: anyImg.naturalWidth || anyImg.videoWidth || anyImg.width || fallbackW,\n height: anyImg.naturalHeight || anyImg.videoHeight || anyImg.height || fallbackH,\n };\n}\n\nfunction getBaseProjection(region: CropBox, transform: ImageTransform, baseW: number, baseH: number): ImageProjection {\n return {\n region,\n angle: normalizeAngle(transform.rotate),\n flipH: transform.flipH,\n flipV: transform.flipV,\n scale: 1,\n originX: baseW / 2,\n originY: baseH / 2,\n };\n}\n","import {\n AdjustmentKind,\n AdjustmentLayer,\n AdjustmentParamsMap,\n Annotation,\n CreateAnnotationPayload,\n AspectRatio,\n CropBox,\n DepthMaskOptions,\n ExportOptions,\n ImageAdjustments,\n ImageDimensions,\n ImageEditorOptions,\n ImageEditorState,\n ImageEditorTool,\n ImageTransform,\n ImagePoint,\n SerializedImageEditorState,\n} from './types';\nimport { ImageEditorRenderer, RenderOptions } from './renderer';\nimport { clampCropToImage, isQuarterTurnSwapped } from './geometry';\nimport { LATEST_PROCESS_VERSION, LEGACY_PROCESS_VERSION, normalizeProcessVersion } from './color';\nimport { createAdjustmentLayer, normalizeAdjustmentLayer, normalizeAdjustmentLayers } from './adjustment-layers';\nimport type { BlendMode } from './blend';\nimport type { ImageHistogram } from './histogram';\n\n/** Latest serialized schema version (`2`: adjustment layers). */\nexport const IMAGE_EDITOR_SCHEMA_VERSION = 2 as const;\n\n/** Changes to an adjustment layer; `params` is merged into the current parameters. */\nexport interface AdjustmentLayerUpdate<K extends AdjustmentKind = AdjustmentKind> {\n params?: Partial<AdjustmentParamsMap[K]>;\n enabled?: boolean;\n opacity?: number;\n blend?: BlendMode;\n name?: string;\n}\n\n/** A new adjustment layer: its kind plus optional initial values. */\nexport interface AdjustmentLayerInit<K extends AdjustmentKind = AdjustmentKind> extends AdjustmentLayerUpdate<K> {\n kind: K;\n id?: string;\n}\n\n/** The image a history entry was recorded against. */\ninterface SourceSnapshot {\n sourceUrl: string | null;\n element: CanvasImageSource | null;\n dimensions: ImageDimensions;\n}\n\n/**\n * Undo/redo entry: the serializable edit state plus the source image it applies to, so\n * AI edits that replace the image are restorable.\n */\ninterface HistoryEntry {\n state: SerializedImageEditorState;\n source: SourceSnapshot;\n}\n\nexport const DEFAULT_ADJUSTMENTS: 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\n/**\n * Fills in defaults for stored adjustments. A stored object without `process` was saved before\n * process versions existed and keeps the legacy rendering.\n */\nexport function normalizeStoredAdjustments(stored: Partial<ImageAdjustments> | undefined | null): ImageAdjustments {\n if (!stored) return { ...DEFAULT_ADJUSTMENTS };\n const out: ImageAdjustments = {\n ...DEFAULT_ADJUSTMENTS,\n ...stored,\n process: normalizeProcessVersion(stored.process),\n };\n // Layers are deep-copied and cleaned; an empty or missing list is left out entirely.\n const layers = normalizeAdjustmentLayers(stored.layers);\n if (layers.length) out.layers = layers;\n else delete out.layers;\n return out;\n}\n\n/**\n * Brings a stored editor state of any schema version to the current shape. Version 1 (and a\n * missing version) has no adjustment layers; everything else is unchanged, so old states load\n * and render exactly as before.\n */\nexport function migrateSerializedImageEditorState(\n data: Partial<SerializedImageEditorState> & { version?: number }\n): SerializedImageEditorState {\n const adjustments = normalizeStoredAdjustments(data.adjustments);\n return {\n version: adjustments.layers?.length ? IMAGE_EDITOR_SCHEMA_VERSION : 1,\n crop: data.crop ? { ...data.crop } : null,\n transform: { ...DEFAULT_TRANSFORM, ...data.transform },\n adjustments: serializeAdjustments(adjustments),\n depthMask: {\n ...DEFAULT_DEPTH_MASK,\n ...data.depthMask,\n depthRange: [...(data.depthMask?.depthRange ?? DEFAULT_DEPTH_MASK.depthRange)] as [number, number],\n },\n annotations: JSON.parse(JSON.stringify(data.annotations ?? [])),\n };\n}\n\nexport const DEFAULT_TRANSFORM: ImageTransform = {\n rotate: 0,\n flipH: false,\n flipV: false,\n};\n\nexport const DEFAULT_DEPTH_MASK: DepthMaskOptions = {\n enabled: false,\n maskSource: null,\n mode: 'threshold',\n depthRange: [0, 255],\n softness: 10,\n invert: false,\n};\n\n/**\n * ImageEditorController manages the complete lifecycle, canvas rendering pipeline,\n * tool interaction states, undo/redo history, serialization, and image export.\n */\nexport class ImageEditorController {\n private state: ImageEditorState;\n private renderer: ImageEditorRenderer;\n private imageElement: CanvasImageSource | null = null;\n private listeners = new Set<(state: ImageEditorState) => void>();\n private histogramListeners = new Set<(histogram: ImageHistogram | null) => void>();\n private onStateChange?: (state: ImageEditorState) => void;\n\n // History stacks\n private undoStack: HistoryEntry[] = [];\n private redoStack: HistoryEntry[] = [];\n private maxHistoryDepth: number;\n private isApplyingHistory = false;\n private initialSnapshot: HistoryEntry;\n\n constructor(options: ImageEditorOptions = {}) {\n this.maxHistoryDepth = options.maxHistoryDepth ?? 50;\n this.onStateChange = options.onStateChange;\n this.renderer = new ImageEditorRenderer();\n this.renderer.onPreviewHistogram = (histogram) => {\n this.histogramListeners.forEach((l) => l(histogram));\n };\n\n const initialAdjustments = normalizeStoredAdjustments(options.initialState?.adjustments);\n\n const initialTransform: ImageTransform = {\n ...DEFAULT_TRANSFORM,\n ...options.initialState?.transform,\n };\n\n const initialDepthMask: DepthMaskOptions = {\n ...DEFAULT_DEPTH_MASK,\n ...options.initialState?.depthMask,\n };\n\n this.state = {\n sourceUrl: typeof options.image === 'string' ? options.image : null,\n imageDimensions: { width: 800, height: 600 },\n crop: options.initialState?.crop ?? null,\n transform: initialTransform,\n adjustments: initialAdjustments,\n depthMask: initialDepthMask,\n annotations: options.initialState?.annotations ?? [],\n selectedAnnotationId: null,\n activeTool: 'select',\n zoom: 1,\n pan: { x: 0, y: 0 },\n isOriginalCompared: false,\n };\n\n if (options.image instanceof Object && 'width' in options.image) {\n this.imageElement = options.image as CanvasImageSource;\n this.state.sourceUrl = (options.image as any).src || null;\n this.state.imageDimensions = {\n width: (options.image as any).naturalWidth || (options.image as any).width || 800,\n height: (options.image as any).naturalHeight || (options.image as any).height || 600,\n };\n }\n\n this.initialSnapshot = this.captureHistoryEntry();\n\n if (typeof options.image === 'string') {\n this.loadImage(options.image);\n }\n }\n\n // --- Image Loading ---\n\n /**\n * Loads a new source image and makes it the baseline that `reset()` returns to.\n * Does not record history; use `applyInpaintedImage` / `applyBackgroundRemovedImage`\n * for undoable source replacement.\n */\n public async loadImage(src: string | HTMLImageElement): Promise<void> {\n // Keep the URL visible synchronously (UI bindings compare against it while loading).\n if (typeof src === 'string') this.state.sourceUrl = src;\n const source = await this.resolveSource(src);\n this.imageElement = source.element;\n this.state.sourceUrl = source.sourceUrl;\n this.state.imageDimensions = source.dimensions;\n\n this.initialSnapshot = this.captureHistoryEntry();\n this.notify();\n }\n\n private async resolveSource(src: string | HTMLImageElement): Promise<SourceSnapshot> {\n if (typeof src === 'string') {\n try {\n const img = await this.renderer.imageCache.get(src);\n return {\n sourceUrl: src,\n element: img,\n dimensions: {\n width: img.naturalWidth || img.width || 800,\n height: img.naturalHeight || img.height || 600,\n },\n };\n } catch {\n // In SSR / non-DOM environments, retain current dimensions\n return { sourceUrl: src, element: null, dimensions: { ...this.state.imageDimensions } };\n }\n }\n return {\n sourceUrl: (src as any).src || null,\n element: src,\n dimensions: {\n width: src.naturalWidth || src.width || 800,\n height: src.naturalHeight || src.height || 600,\n },\n };\n }\n\n public getImageElement(): CanvasImageSource | null {\n return this.imageElement;\n }\n\n // --- State & Subscription ---\n\n public getState(): ImageEditorState {\n return this.state;\n }\n\n public subscribe(listener: (state: ImageEditorState) => void): () => void {\n this.listeners.add(listener);\n listener(this.state);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private notify(): void {\n const currentState = { ...this.state };\n this.onStateChange?.(currentState);\n this.listeners.forEach((l) => l(currentState));\n }\n\n // --- History (Undo / Redo / Reset) ---\n\n private captureHistoryEntry(): HistoryEntry {\n return {\n state: this.serializeState(),\n source: {\n sourceUrl: this.state.sourceUrl,\n element: this.imageElement,\n dimensions: { ...this.state.imageDimensions },\n },\n };\n }\n\n private restoreHistoryEntry(entry: HistoryEntry): void {\n this.applySerializedState(entry.state);\n this.imageElement = entry.source.element;\n this.state.sourceUrl = entry.source.sourceUrl;\n this.state.imageDimensions = { ...entry.source.dimensions };\n if (\n this.state.selectedAnnotationId &&\n !this.state.annotations.some((a) => a.id === this.state.selectedAnnotationId)\n ) {\n this.state.selectedAnnotationId = null;\n }\n }\n\n private recordHistory(): void {\n if (this.isApplyingHistory) return;\n this.undoStack.push(this.captureHistoryEntry());\n if (this.undoStack.length > this.maxHistoryDepth) {\n this.undoStack.shift();\n }\n this.redoStack = [];\n }\n\n public get canUndo(): boolean {\n return this.undoStack.length > 0;\n }\n\n public get canRedo(): boolean {\n return this.redoStack.length > 0;\n }\n\n public undo(): boolean {\n if (!this.canUndo) return false;\n this.isApplyingHistory = true;\n this.redoStack.push(this.captureHistoryEntry());\n\n const prev = this.undoStack.pop()!;\n this.restoreHistoryEntry(prev);\n this.isApplyingHistory = false;\n this.notify();\n return true;\n }\n\n public redo(): boolean {\n if (!this.canRedo) return false;\n this.isApplyingHistory = true;\n this.undoStack.push(this.captureHistoryEntry());\n\n const next = this.redoStack.pop()!;\n this.restoreHistoryEntry(next);\n this.isApplyingHistory = false;\n this.notify();\n return true;\n }\n\n public reset(): void {\n this.recordHistory();\n this.restoreHistoryEntry(this.initialSnapshot);\n this.state.zoom = 1;\n this.state.pan = { x: 0, y: 0 };\n this.state.activeTool = 'select';\n this.state.selectedAnnotationId = null;\n this.notify();\n }\n\n // --- Active Tool ---\n\n public setTool(tool: ImageEditorTool): void {\n if (this.state.activeTool === tool) return;\n this.state.activeTool = tool;\n if (tool !== 'select') {\n this.state.selectedAnnotationId = null;\n }\n // If switching to crop tool and no crop is defined, initialize crop to full image\n if (tool === 'crop' && !this.state.crop) {\n this.state.crop = {\n x: 0,\n y: 0,\n width: this.state.imageDimensions.width,\n height: this.state.imageDimensions.height,\n };\n }\n this.notify();\n }\n\n // --- Crop ---\n\n public setCrop(crop: CropBox | null, addToHistory = true): void {\n if (addToHistory) {\n this.recordHistory();\n }\n this.state.crop = crop;\n this.notify();\n }\n\n public applyCropPreset(ratio: AspectRatio): void {\n this.recordHistory();\n const { width, height } = this.state.imageDimensions;\n // `ratio` is the on-screen aspect; the crop box lives in natural image space.\n if (ratio && isQuarterTurnSwapped(this.state.transform)) {\n ratio = 1 / ratio;\n }\n if (!ratio) {\n // Freeform: full image\n this.state.crop = { x: 0, y: 0, width, height };\n } else {\n let targetW = width;\n let targetH = targetW / ratio;\n\n if (targetH > height) {\n targetH = height;\n targetW = targetH * ratio;\n }\n\n const x = Math.max(0, Math.round((width - targetW) / 2));\n const y = Math.max(0, Math.round((height - targetH) / 2));\n this.state.crop = {\n x,\n y,\n width: Math.round(targetW),\n height: Math.round(targetH),\n };\n }\n this.notify();\n }\n\n public resetCrop(): void {\n this.recordHistory();\n this.state.crop = null;\n this.notify();\n }\n\n // --- Transformations (Rotate / Flip) ---\n\n public rotate(degrees: number): void {\n this.recordHistory();\n const current = this.state.transform.rotate;\n this.state.transform = { ...this.state.transform, rotate: (current + degrees) % 360 };\n this.notify();\n }\n\n public setRotation(degrees: number): void {\n this.recordHistory();\n this.state.transform = { ...this.state.transform, rotate: degrees % 360 };\n this.notify();\n }\n\n public flipHorizontal(): void {\n this.recordHistory();\n this.state.transform = { ...this.state.transform, flipH: !this.state.transform.flipH };\n this.notify();\n }\n\n public flipVertical(): void {\n this.recordHistory();\n this.state.transform = { ...this.state.transform, flipV: !this.state.transform.flipV };\n this.notify();\n }\n\n // --- Adjustments ---\n\n public setAdjustments(partial: Partial<ImageAdjustments>, commitToHistory = true): void {\n if (commitToHistory) {\n this.recordHistory();\n }\n const next: ImageAdjustments = {\n ...this.state.adjustments,\n ...partial,\n };\n if ('layers' in partial) {\n const layers = normalizeAdjustmentLayers(partial.layers);\n if (layers.length) next.layers = layers;\n else delete next.layers;\n }\n this.state.adjustments = next;\n this.notify();\n }\n\n /** True when the adjustments use an older rendering process than {@link LATEST_PROCESS_VERSION}. */\n public get isProcessOutdated(): boolean {\n return normalizeProcessVersion(this.state.adjustments.process) < LATEST_PROCESS_VERSION;\n }\n\n /**\n * Update to Current Process: re-interprets the current adjustment values with the latest\n * rendering process (one undo step). Returns false when already current.\n */\n public upgradeProcess(): boolean {\n if (!this.isProcessOutdated) return false;\n this.setAdjustments({ process: LATEST_PROCESS_VERSION });\n return true;\n }\n\n /** Resets the base sliders (latest process). Adjustment layers are kept; see `clearAdjustmentLayers`. */\n public resetAdjustments(): void {\n this.recordHistory();\n const layers = this.state.adjustments.layers;\n this.state.adjustments = { ...DEFAULT_ADJUSTMENTS };\n if (layers?.length) this.state.adjustments.layers = layers;\n this.notify();\n }\n\n // --- Adjustment layers ---\n\n /** The adjustment layers, bottom (first) to top (last). */\n public getAdjustmentLayers(): AdjustmentLayer[] {\n return this.state.adjustments.layers ?? [];\n }\n\n public getAdjustmentLayer(id: string): AdjustmentLayer | undefined {\n return this.getAdjustmentLayers().find((l) => l.id === id);\n }\n\n private setLayers(layers: AdjustmentLayer[]): void {\n const next: ImageAdjustments = { ...this.state.adjustments };\n if (layers.length) next.layers = layers;\n else delete next.layers;\n this.state.adjustments = next;\n }\n\n /**\n * Adds an adjustment layer on top (or at `index`, 0 = bottom) as one undo step and returns\n * its id. `kind` alone gives neutral parameters.\n */\n public addAdjustmentLayer<K extends AdjustmentKind>(init: K | AdjustmentLayerInit<K>, index?: number): string {\n const spec: AdjustmentLayerInit<K> = typeof init === 'string' ? { kind: init } : init;\n const layers = [...this.getAdjustmentLayers()];\n const layer = createAdjustmentLayer(spec.kind, {\n id: spec.id && !layers.some((l) => l.id === spec.id) ? spec.id : undefined,\n name: spec.name,\n params: spec.params,\n enabled: spec.enabled,\n opacity: spec.opacity,\n blend: spec.blend,\n });\n this.recordHistory();\n const at = index === undefined ? layers.length : Math.max(0, Math.min(layers.length, Math.round(index)));\n layers.splice(at, 0, layer);\n this.setLayers(layers);\n this.notify();\n return layer.id;\n }\n\n /**\n * Updates a layer (`params` merged into the current ones, then normalised). Pass\n * `commitToHistory = false` for the moves of a drag after recording its start once.\n * Returns false for an unknown id.\n */\n public updateAdjustmentLayer(id: string, update: AdjustmentLayerUpdate, commitToHistory = true): boolean {\n const layers = this.getAdjustmentLayers();\n const index = layers.findIndex((l) => l.id === id);\n if (index === -1) return false;\n const current = layers[index];\n const merged = normalizeAdjustmentLayer({\n ...current,\n ...(update.enabled !== undefined ? { enabled: update.enabled } : {}),\n ...(update.opacity !== undefined ? { opacity: update.opacity } : {}),\n ...(update.blend !== undefined ? { blend: update.blend } : {}),\n ...(update.name !== undefined ? { name: update.name } : {}),\n params: { ...current.params, ...(update.params ?? {}) },\n });\n if (!merged) return false;\n if (commitToHistory) this.recordHistory();\n const next = [...layers];\n next[index] = merged;\n this.setLayers(next);\n this.notify();\n return true;\n }\n\n /** Removes a layer as one undo step. Returns false for an unknown id. */\n public removeAdjustmentLayer(id: string): boolean {\n const layers = this.getAdjustmentLayers();\n if (!layers.some((l) => l.id === id)) return false;\n this.recordHistory();\n this.setLayers(layers.filter((l) => l.id !== id));\n this.notify();\n return true;\n }\n\n /** Moves a layer to `toIndex` (0 = bottom) as one undo step. Returns false if nothing moved. */\n public moveAdjustmentLayer(id: string, toIndex: number): boolean {\n const layers = [...this.getAdjustmentLayers()];\n const from = layers.findIndex((l) => l.id === id);\n if (from === -1) return false;\n const to = Math.max(0, Math.min(layers.length - 1, Math.round(toIndex)));\n if (to === from) return false;\n this.recordHistory();\n const [layer] = layers.splice(from, 1);\n layers.splice(to, 0, layer);\n this.setLayers(layers);\n this.notify();\n return true;\n }\n\n /** Shows or hides a layer (flips it without `enabled`) as one undo step. */\n public toggleAdjustmentLayer(id: string, enabled?: boolean): boolean {\n const layer = this.getAdjustmentLayer(id);\n if (!layer) return false;\n const next = enabled ?? !layer.enabled;\n if (next === layer.enabled) return false;\n return this.updateAdjustmentLayer(id, { enabled: next });\n }\n\n /** Removes every adjustment layer as one undo step. */\n public clearAdjustmentLayers(): void {\n if (!this.getAdjustmentLayers().length) return;\n this.recordHistory();\n this.setLayers([]);\n this.notify();\n }\n\n // --- Histogram ---\n\n /**\n * Histogram of the last rendered preview (after adjustments and layers, before annotations),\n * or `null` before the first render or without canvas pixel access.\n */\n public getHistogram(): ImageHistogram | null {\n return this.renderer.previewHistogram;\n }\n\n /** Calls `listener` now and whenever a render produces a new preview histogram. */\n public subscribeHistogram(listener: (histogram: ImageHistogram | null) => void): () => void {\n this.histogramListeners.add(listener);\n listener(this.renderer.previewHistogram);\n return () => {\n this.histogramListeners.delete(listener);\n };\n }\n\n // --- Depth Masking (Gleamforge) ---\n\n public setDepthMask(partial: Partial<DepthMaskOptions>): void {\n this.recordHistory();\n this.state.depthMask = {\n ...this.state.depthMask,\n ...partial,\n };\n this.notify();\n }\n\n public resetDepthMask(): void {\n this.recordHistory();\n this.state.depthMask = { ...DEFAULT_DEPTH_MASK };\n this.notify();\n }\n\n // --- Annotations & Layers ---\n\n public addAnnotation(annotation: CreateAnnotationPayload): string {\n this.recordHistory();\n const id = `ann_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;\n const fullAnnotation = { ...annotation, id } as Annotation;\n this.state.annotations = [...this.state.annotations, fullAnnotation];\n this.state.selectedAnnotationId = id;\n this.notify();\n return id;\n }\n\n public updateAnnotation(id: string, partial: Partial<Annotation>, commitToHistory = true): void {\n if (commitToHistory) {\n this.recordHistory();\n }\n this.state.annotations = this.state.annotations.map((ann) => {\n if (ann.id === id) {\n return { ...ann, ...partial } as Annotation;\n }\n return ann;\n });\n this.notify();\n }\n\n public removeAnnotation(id: string): void {\n this.recordHistory();\n this.state.annotations = this.state.annotations.filter((ann) => ann.id !== id);\n if (this.state.selectedAnnotationId === id) {\n this.state.selectedAnnotationId = null;\n }\n this.notify();\n }\n\n public clearAnnotations(): void {\n this.recordHistory();\n this.state.annotations = [];\n this.state.selectedAnnotationId = null;\n this.notify();\n }\n\n public selectAnnotation(id: string | null): void {\n this.state.selectedAnnotationId = id;\n this.notify();\n }\n\n // --- Zoom & Pan ---\n\n public setZoom(zoom: number): void {\n this.state.zoom = Math.max(0.1, Math.min(10, zoom));\n this.notify();\n }\n\n public zoomIn(factor = 1.2): void {\n this.setZoom(this.state.zoom * factor);\n }\n\n public zoomOut(factor = 1.2): void {\n this.setZoom(this.state.zoom / factor);\n }\n\n public resetZoom(): void {\n this.state.zoom = 1;\n this.state.pan = { x: 0, y: 0 };\n this.notify();\n }\n\n public setPan(pan: ImagePoint): void {\n this.state.pan = pan;\n this.notify();\n }\n\n public setComparing(comparing: boolean): void {\n if (this.state.isOriginalCompared === comparing) return;\n this.state.isOriginalCompared = comparing;\n this.notify();\n }\n\n // --- Rendering & Export ---\n\n public async render(targetCanvas: HTMLCanvasElement, options?: RenderOptions): Promise<void> {\n const source = this.imageElement || this.state.sourceUrl;\n if (!source) return;\n try {\n await this.renderer.renderToCanvas(targetCanvas, source, this.state, options);\n } catch {\n // In non-DOM or test mock environments without image loading, safely ignore\n }\n }\n\n public async renderExport(options?: ExportOptions): Promise<HTMLCanvasElement> {\n const source = this.imageElement || this.state.sourceUrl;\n if (!source) {\n return this.renderer.createCanvas(1, 1);\n }\n return this.renderer.renderExport(source, this.state, options);\n }\n\n public async toDataURL(options: ExportOptions = {}): Promise<string> {\n const canvas = await this.renderExport(options);\n const format = options.format || 'image/png';\n const quality = options.quality ?? 0.92;\n return canvas.toDataURL(format, quality);\n }\n\n public async toBlob(options: ExportOptions = {}): Promise<Blob> {\n const canvas = await this.renderExport(options);\n const format = options.format || 'image/png';\n const quality = options.quality ?? 0.92;\n\n return new Promise((resolve, reject) => {\n if (canvas.toBlob) {\n canvas.toBlob((blob) => {\n if (blob) resolve(blob);\n else reject(new Error('Failed to create Blob from canvas'));\n }, format, quality);\n } else {\n // Fallback for environments lacking canvas.toBlob\n try {\n const dataUrl = canvas.toDataURL(format, quality);\n const parts = dataUrl.split(',');\n const byteStr = atob(parts[1]);\n const ab = new ArrayBuffer(byteStr.length);\n const ia = new Uint8Array(ab);\n for (let i = 0; i < byteStr.length; i++) {\n ia[i] = byteStr.charCodeAt(i);\n }\n resolve(new Blob([ab], { type: format }));\n } catch (err) {\n reject(err);\n }\n }\n });\n }\n\n // --- AI Masking & Transformation Helpers ---\n\n public async renderMask(options?: { useAnnotations?: boolean; useCrop?: boolean }): Promise<HTMLCanvasElement> {\n return this.renderer.renderMask(this.state, options);\n }\n\n public async toMaskDataURL(options?: { useAnnotations?: boolean; useCrop?: boolean }): Promise<string> {\n const canvas = await this.renderMask(options);\n return canvas.toDataURL('image/png');\n }\n\n /**\n * Renders the current source image at natural size with no crop, rotation, flip,\n * adjustments or annotations. This is the image that `toMaskDataURL()` is aligned with\n * and the space that `applyInpaintedImage` / `applyBackgroundRemovedImage` expect back.\n */\n public async renderSource(): Promise<HTMLCanvasElement> {\n const { width, height } = this.state.imageDimensions;\n const canvas = this.renderer.createCanvas(width, height);\n const source = this.imageElement || this.state.sourceUrl;\n const ctx = canvas.getContext('2d');\n if (!ctx || !source) return canvas;\n const img = typeof source === 'string' ? await this.renderer.imageCache.get(source) : source;\n ctx.drawImage(img, 0, 0, width, height);\n return canvas;\n }\n\n public async toSourceDataURL(options: Pick<ExportOptions, 'format' | 'quality'> = {}): Promise<string> {\n const canvas = await this.renderSource();\n return canvas.toDataURL(options.format || 'image/png', options.quality ?? 0.92);\n }\n\n /**\n * Replaces the source image with an inpainted result (natural image space, see\n * `toSourceDataURL`) and clears the mask annotations, as ONE undoable step.\n */\n public async applyInpaintedImage(newDataUrl: string): Promise<void> {\n await this.replaceSource(newDataUrl, true);\n }\n\n /**\n * Replaces the source image with a background-removed result as ONE undoable step.\n * Crop and annotations are kept (rescaled if the new image has different dimensions).\n */\n public async applyBackgroundRemovedImage(newDataUrl: string): Promise<void> {\n await this.replaceSource(newDataUrl, false);\n }\n\n private async replaceSource(src: string, clearAnnotations: boolean): Promise<void> {\n const next = await this.resolveSource(src);\n this.recordHistory();\n\n const prev = this.state.imageDimensions;\n const sx = next.dimensions.width / (prev.width || next.dimensions.width);\n const sy = next.dimensions.height / (prev.height || next.dimensions.height);\n\n this.imageElement = next.element;\n this.state.sourceUrl = next.sourceUrl;\n this.state.imageDimensions = next.dimensions;\n\n if (sx !== 1 || sy !== 1) {\n if (this.state.crop) {\n this.state.crop = clampCropToImage(\n {\n x: Math.round(this.state.crop.x * sx),\n y: Math.round(this.state.crop.y * sy),\n width: Math.round(this.state.crop.width * sx),\n height: Math.round(this.state.crop.height * sy),\n },\n next.dimensions.width,\n next.dimensions.height\n );\n }\n this.state.annotations = this.state.annotations.map((ann) => scaleAnnotation(ann, sx, sy));\n }\n\n if (clearAnnotations) {\n this.state.annotations = [];\n this.state.selectedAnnotationId = null;\n }\n this.notify();\n }\n\n // --- Serialization ---\n\n public toJSON(): SerializedImageEditorState {\n return this.serializeState();\n }\n\n public loadJSON(data: SerializedImageEditorState): void {\n this.recordHistory();\n this.applySerializedState(migrateSerializedImageEditorState(data));\n this.notify();\n }\n\n private serializeState(): SerializedImageEditorState {\n return {\n // States without adjustment layers keep schema 1, so they round-trip unchanged.\n version: this.state.adjustments.layers?.length ? IMAGE_EDITOR_SCHEMA_VERSION : 1,\n crop: this.state.crop ? { ...this.state.crop } : null,\n transform: { ...this.state.transform },\n adjustments: serializeAdjustments(this.state.adjustments),\n depthMask: {\n ...this.state.depthMask,\n depthRange: [...this.state.depthMask.depthRange] as [number, number],\n },\n annotations: JSON.parse(JSON.stringify(this.state.annotations)),\n };\n }\n\n private applySerializedState(data: SerializedImageEditorState): void {\n this.state.crop = data.crop ? { ...data.crop } : null;\n this.state.transform = { ...data.transform };\n this.state.adjustments = normalizeStoredAdjustments(data.adjustments);\n this.state.depthMask = {\n ...data.depthMask,\n depthRange: [...data.depthMask.depthRange] as [number, number],\n };\n this.state.annotations = JSON.parse(JSON.stringify(data.annotations || []));\n }\n}\n\n/**\n * Legacy-process adjustments are saved without `process` (and without an unused zero `tint`), so\n * they serialize exactly as they did before process versions existed.\n */\nfunction serializeAdjustments(adjustments: ImageAdjustments): ImageAdjustments {\n const out: ImageAdjustments = { ...adjustments };\n if (out.layers?.length) out.layers = JSON.parse(JSON.stringify(out.layers));\n else delete out.layers;\n if (normalizeProcessVersion(out.process) === LEGACY_PROCESS_VERSION) {\n delete out.process;\n if (!out.tint) delete out.tint;\n }\n return out;\n}\n\n/** Scales an annotation's geometry from one image size to another. */\nfunction scaleAnnotation(ann: Annotation, sx: number, sy: number): Annotation {\n const k = Math.sqrt(Math.abs(sx * sy));\n const base = { ...ann, x: ann.x * sx, y: ann.y * sy } as Annotation;\n switch (base.type) {\n case 'pen':\n return { ...base, points: base.points.map((p) => ({ x: p.x * sx, y: p.y * sy })), strokeWidth: base.strokeWidth * k };\n case 'rect':\n return { ...base, width: base.width * sx, height: base.height * sy, strokeWidth: base.strokeWidth * k };\n case 'image':\n return { ...base, width: base.width * sx, height: base.height * sy };\n case 'circle':\n return { ...base, radiusX: base.radiusX * sx, radiusY: base.radiusY * sy, strokeWidth: base.strokeWidth * k };\n case 'arrow':\n case 'line':\n return { ...base, endX: base.endX * sx, endY: base.endY * sy, strokeWidth: base.strokeWidth * k };\n case 'text':\n return { ...base, fontSize: base.fontSize * k };\n }\n}\n","import {\n loadDocument,\n type JsonObject,\n type LoadReport,\n type MigrationStep,\n} from '../persistence/migrate';\nimport { DEFAULT_DEPTH_MASK, DEFAULT_TRANSFORM, normalizeStoredAdjustments } from './controller';\nimport type {\n Annotation,\n CropBox,\n DepthMaskOptions,\n ImageAdjustments,\n ImageTransform,\n SerializedImageEditorState,\n} from './types';\n\n/** The `version` that `ImageEditorController.toJSON()` writes. */\nexport const IMAGE_EDITOR_STATE_VERSION = 1;\n\n/**\n * Migration steps for `SerializedImageEditorState`, keyed by the version they\n * start from. Empty while only version 1 exists.\n */\nexport const imageEditorStateMigrations: Record<number, MigrationStep> = {};\n\nexport interface ImageEditorDocumentLoad {\n /** State to pass to `loadJSON` / `initialState`. */\n state: SerializedImageEditorState;\n report: LoadReport;\n /** Top-level keys this build does not know; spread them into the saved JSON to keep them. */\n unknown: JsonObject;\n}\n\nconst isObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\nconst isNumber = (value: unknown): value is number =>\n typeof value === 'number' && Number.isFinite(value);\n\nfunction expectObject(value: unknown, what: string): Record<string, unknown> {\n if (!isObject(value)) throw new Error(`${what} must be an object`);\n return value;\n}\n\nfunction checkFields(\n value: Record<string, unknown>,\n types: Record<string, (v: unknown) => boolean>,\n what: string,\n) {\n for (const [field, ok] of Object.entries(types)) {\n if (field in value && value[field] !== undefined && !ok(value[field])) {\n throw new Error(`${what}.${field} has an invalid value`);\n }\n }\n}\n\nconst isBoolean = (v: unknown) => typeof v === 'boolean';\n\nfunction parseCrop(value: unknown): CropBox | null {\n if (value === null) return null;\n const crop = expectObject(value, 'crop');\n for (const field of ['x', 'y', 'width', 'height'] as const) {\n if (!isNumber(crop[field])) throw new Error(`crop.${field} must be a number`);\n }\n return { ...(crop as unknown as CropBox) };\n}\n\nfunction parseTransform(value: unknown): ImageTransform {\n const transform = expectObject(value, 'transform');\n checkFields(transform, { rotate: isNumber, flipH: isBoolean, flipV: isBoolean }, 'transform');\n return { ...DEFAULT_TRANSFORM, ...(transform as Partial<ImageTransform>) };\n}\n\nconst ADJUSTMENT_NUMBERS = [\n 'brightness',\n 'contrast',\n 'saturation',\n 'exposure',\n 'temperature',\n 'tint',\n 'blur',\n 'opacity',\n] as const;\n\nfunction parseAdjustments(value: unknown): ImageAdjustments {\n const adjustments = expectObject(value, 'adjustments');\n checkFields(\n adjustments,\n {\n ...Object.fromEntries(ADJUSTMENT_NUMBERS.map((k) => [k, isNumber])),\n process: (v) => v === 1 || v === 2,\n },\n 'adjustments',\n );\n // Kept as stored: `loadJSON` fills defaults and keeps a missing `process` legacy.\n return { ...(adjustments as unknown as ImageAdjustments) };\n}\n\nfunction parseDepthMask(value: unknown): DepthMaskOptions {\n const mask = expectObject(value, 'depthMask');\n checkFields(\n mask,\n {\n enabled: isBoolean,\n maskSource: (v) => v === null || typeof v === 'string',\n mode: (v) => v === 'threshold' || v === 'brightness',\n depthRange: (v) => Array.isArray(v) && v.length === 2 && v.every(isNumber),\n softness: isNumber,\n invert: isBoolean,\n },\n 'depthMask',\n );\n const merged = { ...DEFAULT_DEPTH_MASK, ...(mask as Partial<DepthMaskOptions>) };\n return { ...merged, depthRange: [...merged.depthRange] as [number, number] };\n}\n\nconst ANNOTATION_TYPES = new Set(['pen', 'rect', 'circle', 'arrow', 'line', 'text', 'image']);\n\nconst isAnnotation = (value: unknown): value is Annotation =>\n isObject(value) &&\n typeof value.id === 'string' &&\n typeof value.type === 'string' &&\n ANNOTATION_TYPES.has(value.type) &&\n isNumber(value.x) &&\n isNumber(value.y);\n\n/**\n * Loads stored image editor state (a JSON string or object) with a `LoadReport`:\n *\n * - Runs `imageEditorStateMigrations` up to `IMAGE_EDITOR_STATE_VERSION`; a missing\n * `version` is taken as current.\n * - A newer `version` is still loaded but reported `readOnly`: do not save over it.\n * - Each section (`crop`, `transform`, `adjustments`, `depthMask`, `annotations`)\n * falls back to its default on its own; invalid annotations are dropped one by one.\n * - Unknown top-level keys are returned in `unknown`.\n *\n * Valid state from `toJSON()` comes back unchanged, so `loadJSON(state)` behaves\n * exactly as with the raw object.\n */\nexport function loadImageEditorDocument(input: unknown): ImageEditorDocumentLoad {\n let dropped = 0;\n const result = loadDocument<Omit<SerializedImageEditorState, 'version'>>(\n input,\n {\n crop: { parse: parseCrop, defaults: null },\n transform: { parse: parseTransform, defaults: () => ({ ...DEFAULT_TRANSFORM }) },\n adjustments: {\n parse: parseAdjustments,\n // Same as what `loadJSON` derives from missing adjustments.\n defaults: () => normalizeStoredAdjustments(undefined),\n },\n depthMask: {\n parse: parseDepthMask,\n defaults: () => ({ ...DEFAULT_DEPTH_MASK, depthRange: [...DEFAULT_DEPTH_MASK.depthRange] as [number, number] }),\n },\n annotations: {\n parse: (value) => {\n if (!Array.isArray(value)) throw new Error('annotations must be an array');\n const kept = value.filter(isAnnotation);\n dropped = value.length - kept.length;\n return JSON.parse(JSON.stringify(kept)) as Annotation[];\n },\n defaults: () => [],\n },\n },\n {\n currentVersion: IMAGE_EDITOR_STATE_VERSION,\n steps: imageEditorStateMigrations,\n },\n );\n if (dropped > 0) {\n result.report.problems.push({\n section: 'annotations',\n message: `${dropped} invalid annotation${dropped === 1 ? '' : 's'} dropped`,\n });\n }\n const state = { version: 1, ...result.value } as SerializedImageEditorState;\n return { state, report: result.report, unknown: result.unknown };\n}\n","// Portions ported from lightcraft-main/crates/develop/src/controls.rs (MIT OR Apache-2.0, © ArtCraft Team and contributors). See THIRD_PARTY_NOTICES.md.\n\nimport type { AdjustmentKind, ImageAdjustments } from './types';\n\n/**\n * Control specs: one table describes every numeric adjustment slider (id, label, section, range,\n * default, step, display precision and track style). The React and Svelte panels render from it,\n * the agent tools expose it as a schema, and values are addressed by id.\n */\n\nexport type ControlSection = 'light' | 'color' | 'effects' | 'layer';\n\n/** How a slider track is drawn. */\nexport type ControlTrack =\n /** Plain track, fill from the minimum. */\n | { kind: 'plain' }\n /** Zero in the middle. */\n | { kind: 'centered' }\n /** Blue → yellow (white-balance temperature). */\n | { kind: 'temp' }\n /** Green → magenta (tint). */\n | { kind: 'tint' }\n /** A two-colour gradient. */\n | { kind: 'gradient'; from: string; to: string }\n /** Full hue rainbow. */\n | { kind: 'rainbow' };\n\nexport interface ControlSpec {\n /** Dotted path of the value (`exposure`; inside a layer's params: `midtones.0`, `ranges.4.hue`). */\n id: string;\n label: string;\n section: ControlSection;\n min: number;\n max: number;\n step: number;\n default: number;\n /** Decimal places shown (after `displayScale`). */\n decimals: number;\n track: ControlTrack;\n /** Multiplier for display (opacity 0..1 shows as 0..100 %). */\n displayScale?: number;\n /** Unit suffix for display (`px`, `%`, `°`). */\n unit?: string;\n /** The rendering process the control needs (tint: process 2). */\n requiresProcess?: 1 | 2;\n}\n\nconst plain: ControlTrack = { kind: 'plain' };\nconst centered: ControlTrack = { kind: 'centered' };\n\n/** Every numeric base adjustment, in panel order. */\nexport const IMAGE_ADJUSTMENT_CONTROLS: readonly ControlSpec[] = [\n { id: 'brightness', label: 'Brightness', section: 'light', min: -100, max: 100, step: 1, default: 0, decimals: 0, track: centered },\n { id: 'contrast', label: 'Contrast', section: 'light', min: -100, max: 100, step: 1, default: 0, decimals: 0, track: centered },\n {\n id: 'saturation', label: 'Saturation', section: 'color', min: -100, max: 100, step: 1, default: 0, decimals: 0,\n track: { kind: 'gradient', from: '#7a7a7a', to: '#e04a3a' },\n },\n { id: 'exposure', label: 'Exposure', section: 'light', min: -100, max: 100, step: 1, default: 0, decimals: 0, track: centered },\n { id: 'temperature', label: 'Warmth / Temperature', section: 'color', min: -100, max: 100, step: 1, default: 0, decimals: 0, track: { kind: 'temp' } },\n { id: 'tint', label: 'Tint', section: 'color', min: -100, max: 100, step: 1, default: 0, decimals: 0, track: { kind: 'tint' }, requiresProcess: 2 },\n { id: 'blur', label: 'Blur', section: 'effects', min: 0, max: 30, step: 1, default: 0, decimals: 0, track: plain, unit: 'px' },\n { id: 'opacity', label: 'Opacity', section: 'effects', min: 0, max: 1, step: 0.05, default: 1, decimals: 0, track: plain, displayScale: 100, unit: '%' },\n];\n\nconst tone = (prefix: string, label: string): ControlSpec[] =>\n [\n ['0', 'Cyan – Red', '#00b7c7', '#e0343a'],\n ['1', 'Magenta – Green', '#d23ad2', '#2fb34a'],\n ['2', 'Yellow – Blue', '#e0c63a', '#3a5be0'],\n ].map(([i, l, from, to]) => ({\n id: `${prefix}.${i}`, label: `${label} ${l}`, section: 'layer', min: -100, max: 100, step: 1, default: 0, decimals: 0,\n track: { kind: 'gradient', from, to },\n }));\n\nconst levelsRecord = (ch: string, label: string): ControlSpec[] => [\n { id: `${ch}.inBlack`, label: `${label} input black`, section: 'layer', min: 0, max: 253, step: 1, default: 0, decimals: 0, track: plain },\n { id: `${ch}.gamma`, label: `${label} gamma`, section: 'layer', min: 0.1, max: 9.99, step: 0.01, default: 1, decimals: 2, track: plain },\n { id: `${ch}.inWhite`, label: `${label} input white`, section: 'layer', min: 2, max: 255, step: 1, default: 255, decimals: 0, track: plain },\n { id: `${ch}.outBlack`, label: `${label} output black`, section: 'layer', min: 0, max: 255, step: 1, default: 0, decimals: 0, track: plain },\n { id: `${ch}.outWhite`, label: `${label} output white`, section: 'layer', min: 0, max: 255, step: 1, default: 255, decimals: 0, track: plain },\n];\n\nconst RANGE_NAMES = ['Reds', 'Yellows', 'Greens', 'Cyans', 'Blues', 'Magentas'];\n\n/**\n * Numeric parameters of each adjustment-layer kind, addressed inside `layer.params`. Curves points\n * and booleans / colours (colorize, preserve luminosity, tint, filter colour) are edited by the\n * dedicated controls, not sliders.\n */\nexport const ADJUSTMENT_LAYER_CONTROLS: Readonly<Record<AdjustmentKind, readonly ControlSpec[]>> = {\n curves: [],\n levels: [\n ...levelsRecord('master', 'RGB'),\n ...levelsRecord('red', 'Red'),\n ...levelsRecord('green', 'Green'),\n ...levelsRecord('blue', 'Blue'),\n ],\n hueSaturation: [\n { id: 'hue', label: 'Hue', section: 'layer', min: -180, max: 180, step: 1, default: 0, decimals: 0, track: { kind: 'rainbow' }, unit: '°' },\n { id: 'saturation', label: 'Saturation', section: 'layer', min: -100, max: 100, step: 1, default: 0, decimals: 0, track: { kind: 'gradient', from: '#7a7a7a', to: '#e04a3a' } },\n { id: 'lightness', label: 'Lightness', section: 'layer', min: -100, max: 100, step: 1, default: 0, decimals: 0, track: { kind: 'gradient', from: '#000000', to: '#ffffff' } },\n ...RANGE_NAMES.flatMap((name, i): ControlSpec[] => [\n { id: `ranges.${i}.hue`, label: `${name} hue`, section: 'layer', min: -180, max: 180, step: 1, default: 0, decimals: 0, track: { kind: 'rainbow' }, unit: '°' },\n { id: `ranges.${i}.saturation`, label: `${name} saturation`, section: 'layer', min: -100, max: 100, step: 1, default: 0, decimals: 0, track: centered },\n { id: `ranges.${i}.lightness`, label: `${name} lightness`, section: 'layer', min: -100, max: 100, step: 1, default: 0, decimals: 0, track: centered },\n ]),\n ],\n vibrance: [\n { id: 'vibrance', label: 'Vibrance', section: 'layer', min: -100, max: 100, step: 1, default: 0, decimals: 0, track: { kind: 'gradient', from: '#7a7a7a', to: '#d8406a' } },\n { id: 'saturation', label: 'Saturation', section: 'layer', min: -100, max: 100, step: 1, default: 0, decimals: 0, track: { kind: 'gradient', from: '#7a7a7a', to: '#e04a3a' } },\n ],\n colorBalance: [...tone('shadows', 'Shadows'), ...tone('midtones', 'Midtones'), ...tone('highlights', 'Highlights')],\n blackWhite: [\n ['reds', 'Reds', 40, '#e0343a'],\n ['yellows', 'Yellows', 60, '#e0c63a'],\n ['greens', 'Greens', 40, '#2fb34a'],\n ['cyans', 'Cyans', 60, '#00b7c7'],\n ['blues', 'Blues', 20, '#3a5be0'],\n ['magentas', 'Magentas', 80, '#d23ad2'],\n ].map(([id, label, def, to]) => ({\n id: id as string, label: label as string, section: 'layer' as const, min: -200, max: 300, step: 1, default: def as number, decimals: 0,\n track: { kind: 'gradient' as const, from: '#000000', to: to as string }, unit: '%',\n })),\n invert: [],\n posterize: [{ id: 'levels', label: 'Levels', section: 'layer', min: 2, max: 255, step: 1, default: 4, decimals: 0, track: plain }],\n threshold: [{ id: 'level', label: 'Threshold level', section: 'layer', min: 1, max: 255, step: 1, default: 128, decimals: 0, track: { kind: 'gradient', from: '#000000', to: '#ffffff' } }],\n photoFilter: [{ id: 'density', label: 'Density', section: 'layer', min: 0, max: 100, step: 1, default: 25, decimals: 0, track: plain, unit: '%' }],\n};\n\n/** Opacity of any adjustment layer (0..1, shown as %). */\nexport const ADJUSTMENT_LAYER_OPACITY_CONTROL: ControlSpec = {\n id: 'opacity', label: 'Opacity', section: 'layer', min: 0, max: 1, step: 0.01, default: 1, decimals: 0, track: plain, displayScale: 100, unit: '%',\n};\n\nconst BASE_BY_ID = new Map(IMAGE_ADJUSTMENT_CONTROLS.map((c) => [c.id, c]));\n\n/** The base-adjustment control with this id. */\nexport function findAdjustmentControl(id: string): ControlSpec | undefined {\n return BASE_BY_ID.get(id);\n}\n\n/** The control of a layer parameter (`opacity` included). */\nexport function findLayerControl(kind: AdjustmentKind, id: string): ControlSpec | undefined {\n if (id === 'opacity') return ADJUSTMENT_LAYER_OPACITY_CONTROL;\n return ADJUSTMENT_LAYER_CONTROLS[kind]?.find((c) => c.id === id);\n}\n\n/** `v` clamped to the control's range and snapped to its step; non-finite values give the default. */\nexport function clampControlValue(spec: ControlSpec, v: unknown): number {\n if (typeof v !== 'number' || !Number.isFinite(v)) return spec.default;\n const clamped = Math.min(spec.max, Math.max(spec.min, v));\n const steps = Math.round((clamped - spec.min) / spec.step);\n const snapped = spec.min + steps * spec.step;\n // Trim float noise from the step arithmetic.\n return Math.min(spec.max, Number(snapped.toFixed(6)));\n}\n\n/** A value as the slider shows it (`+12`, `35%`, `4px`). */\nexport function formatControlValue(spec: ControlSpec, v: number): string {\n const shown = (Number.isFinite(v) ? v : spec.default) * (spec.displayScale ?? 1);\n const s = shown.toFixed(spec.decimals);\n return `${s}${spec.unit ?? ''}`;\n}\n\n/** Where `v` sits on the track, 0 (left) to 1 (right). */\nexport function controlPosition(spec: ControlSpec, v: number): number {\n const p = (clampControlValue(spec, v) - spec.min) / (spec.max - spec.min || 1);\n return Math.min(1, Math.max(0, p));\n}\n\n/** CSS background for a control's track. */\nexport function controlTrackBackground(spec: ControlSpec): string | undefined {\n const t = spec.track;\n switch (t.kind) {\n case 'temp':\n return 'linear-gradient(to right, #3a7bd5, #d8d8d8, #f2c94c)';\n case 'tint':\n return 'linear-gradient(to right, #3fbf4f, #d8d8d8, #d23ad2)';\n case 'gradient':\n return `linear-gradient(to right, ${t.from}, ${t.to})`;\n case 'rainbow':\n return 'linear-gradient(to right, #00ffff, #0000ff, #ff00ff, #ff0000, #ffff00, #00ff00, #00ffff)';\n default:\n return undefined;\n }\n}\n\n/** Reads a dotted path (`ranges.2.hue`) from an object. */\nexport function getControlPath(obj: unknown, path: string): unknown {\n let cur: any = obj;\n for (const part of path.split('.')) {\n if (cur === null || cur === undefined) return undefined;\n cur = cur[part];\n }\n return cur;\n}\n\n/** A copy of `obj` with the dotted path set (arrays and objects along the path are cloned). */\nexport function setControlPath<T>(obj: T, path: string, value: unknown): T {\n const parts = path.split('.');\n const root: any = Array.isArray(obj) ? [...(obj as unknown[])] : { ...(obj as object) };\n let cur = root;\n for (let i = 0; i < parts.length - 1; i++) {\n const next = cur[parts[i]];\n cur[parts[i]] = Array.isArray(next) ? [...next] : next && typeof next === 'object' ? { ...next } : {};\n cur = cur[parts[i]];\n }\n cur[parts[parts.length - 1]] = value;\n return root as T;\n}\n\n/** The base adjustment values the control table describes. */\nexport function readAdjustmentControls(adjustments: ImageAdjustments): Record<string, number> {\n const out: Record<string, number> = {};\n for (const c of IMAGE_ADJUSTMENT_CONTROLS) {\n const v = (adjustments as unknown as Record<string, unknown>)[c.id];\n out[c.id] = typeof v === 'number' && Number.isFinite(v) ? v : c.default;\n }\n return out;\n}\n","import type { AgentTool, AgentToolProvider } from '../agent/registry';\nimport type { ImageEditorController } from './controller';\nimport type { AdjustmentKind, ImageAdjustments } from './types';\nimport { normalizeProcessVersion } from './color';\nimport {\n ADJUSTMENT_KINDS,\n isAdjustmentKind,\n} from './adjustment-layers';\nimport { BLEND_MODES, isBlendMode } from './blend';\nimport {\n ADJUSTMENT_LAYER_CONTROLS,\n ADJUSTMENT_LAYER_OPACITY_CONTROL,\n IMAGE_ADJUSTMENT_CONTROLS,\n clampControlValue,\n findAdjustmentControl,\n findLayerControl,\n readAdjustmentControls,\n setControlPath,\n} from './controls';\n\nexport interface ImageEditorToolProviderOptions {\n /** Provider id (unique per registry). Default `image-editor`. */\n id?: string;\n /** Always prefix tool names with `<namespace>__`. */\n namespace?: string;\n}\n\nconst LAYER_PATH = /^layers\\.([^.]+)\\.(.+)$/;\n\nfunction adjustmentsSummary(adjustments: ImageAdjustments) {\n return {\n process: normalizeProcessVersion(adjustments.process),\n values: readAdjustmentControls(adjustments),\n layers: (adjustments.layers ?? []).map((l) => JSON.parse(JSON.stringify(l))),\n };\n}\n\n/**\n * Agent tools for an image editor, for a `ToolRegistry`:\n *\n * - `get_adjustments`: base slider values (by control id), process version and adjustment layers.\n * - `list_controls`: the control table (ids, ranges, defaults, steps), layer kinds and blend modes.\n * - `set_adjustments {values, label}`: sets base controls (`exposure`) and layer parameters\n * (`layers.<id>.vibrance`, `layers.<id>.opacity`) in one undo step; values are clamped to the\n * control ranges and the applied values are echoed.\n * - `add_adjustment_layer {kind, params?, opacity?, blend?, name?}`.\n * - `render_preview {maxSide}`: the rendered image as a PNG, longest side at most `maxSide`.\n */\nexport function createImageEditorToolProvider(\n controller: ImageEditorController,\n options: ImageEditorToolProviderOptions = {}\n): AgentToolProvider {\n const getAdjustments: AgentTool = {\n name: 'get_adjustments',\n title: 'Read image adjustments',\n description:\n 'Returns the image editor adjustment values by control id, the rendering process version and the adjustment layers (bottom to top).',\n inputSchema: { type: 'object', properties: {}, additionalProperties: false },\n annotations: {\n title: 'Read image adjustments',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n handler: () => adjustmentsSummary(controller.getState().adjustments),\n };\n\n const listControls: AgentTool = {\n name: 'list_controls',\n title: 'List adjustment controls',\n description:\n 'Lists every adjustment control (id, label, section, min, max, step, default), the parameters of each adjustment-layer kind, and the blend modes.',\n inputSchema: { type: 'object', properties: {}, additionalProperties: false },\n annotations: {\n title: 'List adjustment controls',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n handler: () => ({\n controls: IMAGE_ADJUSTMENT_CONTROLS.map(({ id, label, section, min, max, step, default: def, requiresProcess }) => ({\n id, label, section, min, max, step, default: def, ...(requiresProcess ? { requiresProcess } : {}),\n })),\n layerKinds: ADJUSTMENT_KINDS.map(({ kind, label }) => ({\n kind,\n label,\n controls: [...ADJUSTMENT_LAYER_CONTROLS[kind], ADJUSTMENT_LAYER_OPACITY_CONTROL].map(({ id, label: l, min, max, step, default: def }) => ({\n id, label: l, min, max, step, default: def,\n })),\n })),\n blendModes: BLEND_MODES.map((m) => m.id),\n layerPathFormat: 'layers.<layerId>.<controlId>',\n }),\n };\n\n const setAdjustments: AgentTool = {\n name: 'set_adjustments',\n title: 'Set image adjustments',\n description:\n 'Sets adjustment values by control id in one undoable step. Base controls use their id (e.g. \"exposure\"); layer parameters use \"layers.<layerId>.<controlId>\" (e.g. \"layers.adj_1.vibrance\"). Values are clamped to each control range; the applied values are returned.',\n inputSchema: {\n type: 'object',\n properties: {\n values: {\n type: 'object',\n description: 'Map of control id to number. See list_controls for ids and ranges.',\n additionalProperties: { type: 'number' },\n },\n label: { type: 'string', description: 'Short description of the change (echoed back).' },\n },\n required: ['values'],\n additionalProperties: false,\n },\n annotations: {\n title: 'Set image adjustments',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n handler: ({ values, label }) => {\n const entries = Object.entries((values ?? {}) as Record<string, unknown>);\n if (!entries.length) throw new Error('No values given. Pass {\"values\": {\"<control id>\": number}}.');\n const state = controller.getState();\n const base: Partial<ImageAdjustments> = {};\n const layers = (state.adjustments.layers ?? []).map((l) => ({ ...l }));\n const applied: Record<string, number> = {};\n const clamped: string[] = [];\n const unknown: string[] = [];\n for (const [id, raw] of entries) {\n const m = LAYER_PATH.exec(id);\n if (m) {\n const layer = layers.find((l) => l.id === m[1]);\n const spec = layer ? findLayerControl(layer.kind, m[2]) : undefined;\n if (!layer || !spec) {\n unknown.push(id);\n continue;\n }\n const v = clampControlValue(spec, raw);\n if (v !== raw) clamped.push(id);\n if (spec.id === 'opacity') layer.opacity = v;\n else layer.params = setControlPath(layer.params, spec.id, v) as typeof layer.params;\n applied[id] = v;\n continue;\n }\n const spec = findAdjustmentControl(id);\n if (!spec) {\n unknown.push(id);\n continue;\n }\n const v = clampControlValue(spec, raw);\n if (v !== raw) clamped.push(id);\n (base as Record<string, number>)[id] = v;\n applied[id] = v;\n }\n if (unknown.length) {\n const accepted = IMAGE_ADJUSTMENT_CONTROLS.map((c) => c.id).join(', ');\n throw new Error(\n `Unknown control id(s): ${unknown.join(', ')}. Accepted: ${accepted}, or layers.<layerId>.<controlId> for an existing layer.`\n );\n }\n const changedLayers = Object.keys(applied).some((id) => id.startsWith('layers.'));\n controller.setAdjustments(changedLayers ? { ...base, layers } : base);\n const result: Record<string, unknown> = { applied, clamped };\n if (typeof label === 'string' && label) result.label = label;\n const notes: string[] = [];\n if ('tint' in applied && normalizeProcessVersion(controller.getState().adjustments.process) < 2) {\n notes.push('Tint has no effect on process 1 edits.');\n }\n if (notes.length) result.notes = notes;\n return result;\n },\n };\n\n const addLayer: AgentTool = {\n name: 'add_adjustment_layer',\n title: 'Add an adjustment layer',\n description:\n 'Adds a non-destructive adjustment layer on top of the stack (one undoable step) and returns it. Kinds: ' +\n ADJUSTMENT_KINDS.map((k) => k.kind).join(', ') +\n '. Omitted params are neutral.',\n inputSchema: {\n type: 'object',\n properties: {\n kind: { type: 'string', enum: ADJUSTMENT_KINDS.map((k) => k.kind) },\n params: { type: 'object', description: 'Initial parameters (see list_controls); merged with neutral defaults.' },\n opacity: { type: 'number', minimum: 0, maximum: 1 },\n blend: { type: 'string', enum: BLEND_MODES.map((m) => m.id) },\n name: { type: 'string' },\n },\n required: ['kind'],\n additionalProperties: false,\n },\n annotations: {\n title: 'Add an adjustment layer',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n handler: ({ kind, params, opacity, blend, name }) => {\n if (!isAdjustmentKind(kind)) throw new Error(`Unknown adjustment kind \"${kind}\".`);\n if (blend !== undefined && !isBlendMode(blend)) throw new Error(`Unknown blend mode \"${blend}\".`);\n const id = controller.addAdjustmentLayer({ kind: kind as AdjustmentKind, params, opacity, blend, name });\n const layer = controller.getAdjustmentLayer(id)!;\n return { layer: JSON.parse(JSON.stringify(layer)), index: controller.getAdjustmentLayers().length - 1 };\n },\n };\n\n const renderPreview: AgentTool = {\n name: 'render_preview',\n title: 'Render a preview',\n description: 'Renders the edited image (crop, adjustments, layers, annotations) as a PNG whose longest side is at most maxSide pixels.',\n inputSchema: {\n type: 'object',\n properties: {\n maxSide: { type: 'integer', minimum: 16, maximum: 2048, description: 'Longest side in pixels (default 512).' },\n },\n additionalProperties: false,\n },\n annotations: {\n title: 'Render a preview',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n handler: async ({ maxSide }) => {\n const side = Math.max(16, Math.min(2048, Math.round(typeof maxSide === 'number' ? maxSide : 512)));\n const state = controller.getState();\n const crop = state.crop ?? { x: 0, y: 0, ...state.imageDimensions };\n const quarter = Math.abs(Math.round((state.transform.rotate % 180) / 90)) % 2 === 1;\n const w = quarter ? crop.height : crop.width;\n const h = quarter ? crop.width : crop.height;\n const k = Math.min(1, side / Math.max(w, h, 1));\n const dataUrl = await controller.toDataURL({\n format: 'image/png',\n width: Math.max(1, Math.round(w * k)),\n height: Math.max(1, Math.round(h * k)),\n });\n const comma = dataUrl.indexOf(',');\n if (!dataUrl.startsWith('data:image/png') || comma < 0 || comma === dataUrl.length - 1) {\n throw new Error('Rendering is not available in this environment.');\n }\n return {\n content: [\n { type: 'image', data: dataUrl.slice(comma + 1), mimeType: 'image/png' },\n { type: 'text', text: `Preview ${Math.round(w * k)}×${Math.round(h * k)}` },\n ],\n };\n },\n };\n\n return {\n id: options.id ?? 'image-editor',\n namespace: options.namespace,\n tools: [getAdjustments, listControls, setAdjustments, addLayer, renderPreview],\n };\n}\n\n","import type { CurvePoint } from './types';\nimport { curveLutFromLevels } from './adjustment-layers';\n\n/**\n * Framework-neutral helpers for a Curves editor drawn in a `width` × `height` box (levels 0..255\n * on both axes, output up). The React and Svelte curve editors share them.\n */\n\nconst clampLevel = (v: number) => Math.min(255, Math.max(0, Math.round(v)));\n\n/** Box coordinates of a curve point. */\nexport function curvePointToBox(p: CurvePoint, width: number, height: number): { x: number; y: number } {\n return { x: (p.input / 255) * width, y: height - (p.output / 255) * height };\n}\n\n/** The curve point (whole levels, clamped) under box coordinates. */\nexport function boxToCurvePoint(x: number, y: number, width: number, height: number): CurvePoint {\n return { input: clampLevel((x / (width || 1)) * 255), output: clampLevel(((height - y) / (height || 1)) * 255) };\n}\n\n/** An SVG polyline path of the evaluated curve (spline, clamped, constant beyond the end points). */\nexport function curveSvgPath(points: readonly CurvePoint[], width: number, height: number, samples = 64): string {\n const lut = curveLutFromLevels(points);\n const n = lut.length - 1;\n let d = '';\n for (let i = 0; i <= samples; i++) {\n const t = i / samples;\n const y = lut[Math.round(t * n)];\n d += `${i === 0 ? 'M' : ' L'}${round2(t * width)} ${round2(height - y * height)}`;\n }\n return d;\n}\n\n/** Index of the point within `radius` box units of (x, y), nearest first; -1 for none. */\nexport function hitTestCurvePoint(\n points: readonly CurvePoint[],\n x: number,\n y: number,\n width: number,\n height: number,\n radius = 8\n): number {\n let best = -1;\n let bestD = radius * radius;\n points.forEach((p, i) => {\n const b = curvePointToBox(p, width, height);\n const d = (b.x - x) ** 2 + (b.y - y) ** 2;\n if (d <= bestD) {\n best = i;\n bestD = d;\n }\n });\n return best;\n}\n\n/** Inserts a point in input order (replacing one at the same input). Returns the new list and its index. */\nexport function insertCurvePoint(points: readonly CurvePoint[], p: CurvePoint): { points: CurvePoint[]; index: number } {\n const point = { input: clampLevel(p.input), output: clampLevel(p.output) };\n const out = points.filter((q) => q.input !== point.input).map((q) => ({ ...q }));\n let index = out.findIndex((q) => q.input > point.input);\n if (index === -1) index = out.length;\n out.splice(index, 0, point);\n return { points: out, index };\n}\n\n/** Moves point `index`, keeping its input strictly between its neighbours'. */\nexport function moveCurvePoint(points: readonly CurvePoint[], index: number, p: CurvePoint): CurvePoint[] {\n const out = points.map((q) => ({ ...q }));\n if (index < 0 || index >= out.length) return out;\n const lo = index > 0 ? out[index - 1].input + 1 : 0;\n const hi = index < out.length - 1 ? out[index + 1].input - 1 : 255;\n out[index] = { input: Math.min(hi, Math.max(lo, clampLevel(p.input))), output: clampLevel(p.output) };\n return out;\n}\n\n/** Removes point `index`; a curve keeps at least two points. */\nexport function removeCurvePoint(points: readonly CurvePoint[], index: number): CurvePoint[] {\n if (points.length <= 2 || index < 0 || index >= points.length) return points.map((q) => ({ ...q }));\n return points.filter((_, i) => i !== index).map((q) => ({ ...q }));\n}\n\nconst round2 = (v: number) => Math.round(v * 100) / 100;\n"]}