@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,4 +1,8 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3;// src/image-editor/color.ts
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3;
2
+
3
+ var _chunkD6FH2YAXcjs = require('../chunk-D6FH2YAX.cjs');
4
+
5
+ // src/image-editor/color.ts
2
6
  var LEGACY_PROCESS_VERSION = 1;
3
7
  var LATEST_PROCESS_VERSION = 2;
4
8
  function normalizeProcessVersion(value) {
@@ -231,6 +235,889 @@ function sampleToneLut(lut, headroom, y) {
231
235
  return lut[i] + (lut[i + 1] - lut[i]) * (f - i);
232
236
  }
233
237
 
238
+ // src/image-editor/blend.ts
239
+ var BLEND_MODES = [
240
+ { id: "normal", label: "Normal", canvas: "source-over" },
241
+ { id: "darken", label: "Darken", canvas: "darken" },
242
+ { id: "multiply", label: "Multiply", canvas: "multiply" },
243
+ { id: "color-burn", label: "Color Burn", canvas: "color-burn" },
244
+ { id: "linear-burn", label: "Linear Burn", canvas: null },
245
+ { id: "darker-color", label: "Darker Color", canvas: null },
246
+ { id: "lighten", label: "Lighten", canvas: "lighten" },
247
+ { id: "screen", label: "Screen", canvas: "screen" },
248
+ { id: "color-dodge", label: "Color Dodge", canvas: "color-dodge" },
249
+ { id: "linear-dodge", label: "Linear Dodge (Add)", canvas: null },
250
+ { id: "lighter-color", label: "Lighter Color", canvas: null },
251
+ { id: "overlay", label: "Overlay", canvas: "overlay" },
252
+ // Canvas soft-light is the W3C formula, not Photoshop's: use the scalar path.
253
+ { id: "soft-light", label: "Soft Light", canvas: null },
254
+ { id: "hard-light", label: "Hard Light", canvas: "hard-light" },
255
+ { id: "vivid-light", label: "Vivid Light", canvas: null },
256
+ { id: "linear-light", label: "Linear Light", canvas: null },
257
+ { id: "pin-light", label: "Pin Light", canvas: null },
258
+ { id: "hard-mix", label: "Hard Mix", canvas: null },
259
+ { id: "difference", label: "Difference", canvas: "difference" },
260
+ { id: "exclusion", label: "Exclusion", canvas: "exclusion" },
261
+ { id: "subtract", label: "Subtract", canvas: null },
262
+ { id: "divide", label: "Divide", canvas: null },
263
+ { id: "hue", label: "Hue", canvas: "hue" },
264
+ { id: "saturation", label: "Saturation", canvas: "saturation" },
265
+ { id: "color", label: "Color", canvas: "color" },
266
+ { id: "luminosity", label: "Luminosity", canvas: "luminosity" }
267
+ ];
268
+ var BLEND_INFO = new Map(BLEND_MODES.map((m) => [m.id, m]));
269
+ function isBlendMode(value) {
270
+ return typeof value === "string" && BLEND_INFO.has(value);
271
+ }
272
+ function normalizeBlendMode(value) {
273
+ return isBlendMode(value) ? value : "normal";
274
+ }
275
+ function canvasCompositeOperation(mode) {
276
+ return _nullishCoalesce(_optionalChain([BLEND_INFO, 'access', _2 => _2.get, 'call', _3 => _3(_nullishCoalesce(mode, () => ( "normal"))), 'optionalAccess', _4 => _4.canvas]), () => ( null));
277
+ }
278
+ function isSeparableBlendMode(mode) {
279
+ return !(mode === "hue" || mode === "saturation" || mode === "color" || mode === "luminosity" || mode === "darker-color" || mode === "lighter-color");
280
+ }
281
+ function colorBurn(cb, cs) {
282
+ if (cb >= 1) return 1;
283
+ if (cs <= 0) return 0;
284
+ return 1 - Math.min(1, (1 - cb) / cs);
285
+ }
286
+ function colorDodge(cb, cs) {
287
+ if (cb <= 0) return 0;
288
+ if (cs >= 1) return 1;
289
+ return Math.min(1, cb / (1 - cs));
290
+ }
291
+ function hardLight(cb, cs) {
292
+ if (cs <= 0.5) return cb * 2 * cs;
293
+ const s = 2 * cs - 1;
294
+ return cb + s - cb * s;
295
+ }
296
+ function softLight(cb, cs) {
297
+ return cs <= 0.5 ? 2 * cb * cs + cb * cb * (1 - 2 * cs) : 2 * cb * (1 - cs) + Math.sqrt(Math.max(0, cb)) * (2 * cs - 1);
298
+ }
299
+ function vividLightHardMix(cb, cs) {
300
+ const EDGE = 1e-4;
301
+ if (cs <= 0.5) {
302
+ if (cb >= 1 - EDGE) return 1;
303
+ if (cs <= 0) return 0;
304
+ return 1 - Math.min(1, (1 - cb) / (2 * cs));
305
+ }
306
+ if (cb <= EDGE) return 0;
307
+ if (cs >= 1) return 1;
308
+ return Math.min(1, cb / (2 * (1 - cs)));
309
+ }
310
+ function blendChannel(mode, cb, cs) {
311
+ switch (mode) {
312
+ case "normal":
313
+ return cs;
314
+ case "darken":
315
+ return Math.min(cb, cs);
316
+ case "multiply":
317
+ return cb * cs;
318
+ case "color-burn":
319
+ return colorBurn(cb, cs);
320
+ case "linear-burn":
321
+ return Math.max(0, cb + cs - 1);
322
+ case "lighten":
323
+ return Math.max(cb, cs);
324
+ case "screen":
325
+ return cb + cs - cb * cs;
326
+ case "color-dodge":
327
+ return colorDodge(cb, cs);
328
+ case "linear-dodge":
329
+ return Math.min(1, cb + cs);
330
+ case "overlay":
331
+ return hardLight(cs, cb);
332
+ case "soft-light":
333
+ return softLight(cb, cs);
334
+ case "hard-light":
335
+ return hardLight(cb, cs);
336
+ case "vivid-light":
337
+ if (cs <= 0) return 0;
338
+ if (cs >= 1) return 1;
339
+ return cs <= 0.5 ? colorBurn(cb, 2 * cs) : colorDodge(cb, 2 * (cs - 0.5));
340
+ case "linear-light":
341
+ return Math.min(1, Math.max(0, cb + 2 * cs - 1));
342
+ case "pin-light":
343
+ return cs <= 0.5 ? Math.min(cb, 2 * cs) : Math.max(cb, 2 * cs - 1);
344
+ case "hard-mix":
345
+ return vividLightHardMix(cb, cs) >= 0.5 - 1e-6 ? 1 : 0;
346
+ case "difference":
347
+ return Math.abs(cb - cs);
348
+ case "exclusion":
349
+ return cb + cs - 2 * cb * cs;
350
+ case "subtract":
351
+ return Math.max(0, cb - cs);
352
+ case "divide":
353
+ if (cs <= 0) return cb <= 0 ? 0 : 1;
354
+ return Math.min(1, cb / cs);
355
+ default:
356
+ return cs;
357
+ }
358
+ }
359
+ function blendLum(c) {
360
+ return 0.3 * c[0] + 0.59 * c[1] + 0.11 * c[2];
361
+ }
362
+ function clipColor(c) {
363
+ const l = blendLum(c);
364
+ const n = Math.min(c[0], c[1], c[2]);
365
+ const x = Math.max(c[0], c[1], c[2]);
366
+ let out = [c[0], c[1], c[2]];
367
+ if (n < 0) {
368
+ const d = l - n;
369
+ out = out.map((v) => l > 0 && d > 0 ? l + (v - l) * l / d : l);
370
+ }
371
+ if (x > 1) {
372
+ const d = x - l;
373
+ out = out.map((v) => l < 1 && d > 0 ? l + (v - l) * (1 - l) / d : l);
374
+ }
375
+ return out;
376
+ }
377
+ function setLum(c, l) {
378
+ const d = l - blendLum(c);
379
+ return clipColor([c[0] + d, c[1] + d, c[2] + d]);
380
+ }
381
+ function sat(c) {
382
+ return Math.max(c[0], c[1], c[2]) - Math.min(c[0], c[1], c[2]);
383
+ }
384
+ function setSat(c, s) {
385
+ const idx = [0, 1, 2].sort((a, b) => c[a] - c[b]);
386
+ const [imin, imid, imax] = idx;
387
+ const out = [0, 0, 0];
388
+ if (c[imax] > c[imin]) {
389
+ out[imid] = (c[imid] - c[imin]) * s / (c[imax] - c[imin]);
390
+ out[imax] = s;
391
+ }
392
+ out[imin] = 0;
393
+ return out;
394
+ }
395
+ function blendRgb(mode, cb, cs) {
396
+ switch (mode) {
397
+ case "hue":
398
+ return setLum(setSat(cs, sat(cb)), blendLum(cb));
399
+ case "saturation":
400
+ return setLum(setSat(cb, sat(cs)), blendLum(cb));
401
+ case "color":
402
+ return setLum(cs, blendLum(cb));
403
+ case "luminosity":
404
+ return setLum(cb, blendLum(cs));
405
+ case "darker-color":
406
+ return blendLum(cs) < blendLum(cb) ? [cs[0], cs[1], cs[2]] : [cb[0], cb[1], cb[2]];
407
+ case "lighter-color":
408
+ return blendLum(cs) > blendLum(cb) ? [cs[0], cs[1], cs[2]] : [cb[0], cb[1], cb[2]];
409
+ default:
410
+ return [blendChannel(mode, cb[0], cs[0]), blendChannel(mode, cb[1], cs[1]), blendChannel(mode, cb[2], cs[2])];
411
+ }
412
+ }
413
+ function blendPixel(mode, backdrop, source, alpha = 1) {
414
+ const ab = _nullishCoalesce(backdrop[3], () => ( 1));
415
+ const as = (_nullishCoalesce(source[3], () => ( 1))) * alpha;
416
+ if (!(as > 0)) return [backdrop[0], backdrop[1], backdrop[2], ab];
417
+ const b = blendRgb(mode, backdrop, source);
418
+ const ao = as + ab * (1 - as);
419
+ if (ao <= 0) return [0, 0, 0, 0];
420
+ const out = [0, 0, 0, ao];
421
+ for (let i = 0; i < 3; i++) {
422
+ out[i] = ((1 - as) * ab * backdrop[i] + (1 - ab) * as * source[i] + as * ab * b[i]) / ao;
423
+ }
424
+ return out;
425
+ }
426
+ function blendImageData(backdrop, source, mode, alpha = 1) {
427
+ const d = backdrop.data;
428
+ const s = source.data;
429
+ const len = Math.min(d.length, s.length);
430
+ const cb = [0, 0, 0, 0];
431
+ const cs = [0, 0, 0, 0];
432
+ for (let i = 0; i < len; i += 4) {
433
+ if (s[i + 3] === 0) continue;
434
+ for (let k = 0; k < 4; k++) {
435
+ cb[k] = d[i + k] / 255;
436
+ cs[k] = s[i + k] / 255;
437
+ }
438
+ const o = blendPixel(mode, cb, cs, alpha);
439
+ d[i] = Math.round(o[0] * 255);
440
+ d[i + 1] = Math.round(o[1] * 255);
441
+ d[i + 2] = Math.round(o[2] * 255);
442
+ d[i + 3] = Math.round(o[3] * 255);
443
+ }
444
+ }
445
+
446
+ // src/image-editor/adjustment-layers.ts
447
+ var ADJUSTMENT_LUT_SIZE = 4096;
448
+ var clamp01 = (v) => v <= 0 ? 0 : v >= 1 ? 1 : v;
449
+ var finite = (v, fallback) => typeof v === "number" && Number.isFinite(v) ? v : fallback;
450
+ var clampTo = (v, lo, hi, fallback) => Math.min(hi, Math.max(lo, finite(v, fallback)));
451
+ var remEuclid = (a, b) => (a % b + b) % b;
452
+ function sampleLut(table, v) {
453
+ const n = table.length - 1;
454
+ const x = clamp01(v) * n;
455
+ const i = Math.floor(x);
456
+ const j = Math.min(i + 1, n);
457
+ const f = x - i;
458
+ return table[i] * (1 - f) + table[j] * f;
459
+ }
460
+ var ADJUSTMENT_KINDS = [
461
+ { kind: "curves", label: "Curves" },
462
+ { kind: "levels", label: "Levels" },
463
+ { kind: "hueSaturation", label: "Hue/Saturation" },
464
+ { kind: "vibrance", label: "Vibrance" },
465
+ { kind: "colorBalance", label: "Color Balance" },
466
+ { kind: "blackWhite", label: "Black & White" },
467
+ { kind: "photoFilter", label: "Photo Filter" },
468
+ { kind: "invert", label: "Invert" },
469
+ { kind: "posterize", label: "Posterize" },
470
+ { kind: "threshold", label: "Threshold" }
471
+ ];
472
+ var KIND_LABELS = new Map(ADJUSTMENT_KINDS.map((k) => [k.kind, k.label]));
473
+ function isAdjustmentKind(value) {
474
+ return typeof value === "string" && KIND_LABELS.has(value);
475
+ }
476
+ function adjustmentKindLabel(kind) {
477
+ return _nullishCoalesce(KIND_LABELS.get(kind), () => ( kind));
478
+ }
479
+ var HUE_RANGE_LABELS = ["Reds", "Yellows", "Greens", "Cyans", "Blues", "Magentas"];
480
+ function neutralHueRange(i) {
481
+ const c = 60 * i;
482
+ return { hue: 0, saturation: 0, lightness: 0, bounds: [c - 45, c - 15, c + 15, c + 45] };
483
+ }
484
+ var identityCurve = () => [
485
+ { input: 0, output: 0 },
486
+ { input: 255, output: 255 }
487
+ ];
488
+ var identityLevels = () => ({ inBlack: 0, inWhite: 255, gamma: 1, outBlack: 0, outWhite: 255 });
489
+ function defaultAdjustmentParams(kind) {
490
+ const defaults = {
491
+ curves: () => ({ master: identityCurve(), red: identityCurve(), green: identityCurve(), blue: identityCurve() }),
492
+ levels: () => ({ master: identityLevels(), red: identityLevels(), green: identityLevels(), blue: identityLevels() }),
493
+ hueSaturation: () => ({
494
+ hue: 0,
495
+ saturation: 0,
496
+ lightness: 0,
497
+ colorize: false,
498
+ ranges: [0, 1, 2, 3, 4, 5].map(neutralHueRange)
499
+ }),
500
+ vibrance: () => ({ vibrance: 0, saturation: 0 }),
501
+ colorBalance: () => ({ shadows: [0, 0, 0], midtones: [0, 0, 0], highlights: [0, 0, 0], preserveLuminosity: true }),
502
+ blackWhite: () => ({ reds: 40, yellows: 60, greens: 40, cyans: 60, blues: 20, magentas: 80, tint: null }),
503
+ invert: () => ({}),
504
+ posterize: () => ({ levels: 4 }),
505
+ threshold: () => ({ level: 128 }),
506
+ photoFilter: () => ({ color: "#ec8a00", density: 25, preserveLuminosity: true })
507
+ };
508
+ return defaults[kind]();
509
+ }
510
+ var layerCounter = 0;
511
+ function createAdjustmentLayer(kind, options = {}) {
512
+ const raw = {
513
+ id: _nullishCoalesce(options.id, () => ( `adj_${Date.now().toString(36)}_${(++layerCounter).toString(36)}`)),
514
+ kind,
515
+ name: options.name,
516
+ params: { ...defaultAdjustmentParams(kind), ..._nullishCoalesce(options.params, () => ( {})) },
517
+ enabled: _nullishCoalesce(options.enabled, () => ( true)),
518
+ opacity: _nullishCoalesce(options.opacity, () => ( 1)),
519
+ blend: _nullishCoalesce(options.blend, () => ( "normal"))
520
+ };
521
+ return normalizeAdjustmentLayer(raw);
522
+ }
523
+ var HEX = /^#[0-9a-f]{6}$/i;
524
+ function normCurve(points) {
525
+ if (!Array.isArray(points)) return identityCurve();
526
+ const out = points.filter((p) => p && typeof p === "object").map((p) => ({ input: clampTo(p.input, 0, 255, 0), output: clampTo(p.output, 0, 255, 0) })).sort((a, b) => a.input - b.input);
527
+ return out.length >= 2 ? out : identityCurve();
528
+ }
529
+ function normLevels(ch) {
530
+ const c = ch && typeof ch === "object" ? ch : {};
531
+ return {
532
+ inBlack: clampTo(c.inBlack, 0, 254, 0),
533
+ inWhite: clampTo(c.inWhite, 1, 255, 255),
534
+ gamma: clampTo(c.gamma, 0.1, 9.99, 1),
535
+ outBlack: clampTo(c.outBlack, 0, 255, 0),
536
+ outWhite: clampTo(c.outWhite, 0, 255, 255)
537
+ };
538
+ }
539
+ function normTone(t) {
540
+ const a = Array.isArray(t) ? t : [];
541
+ return [clampTo(a[0], -100, 100, 0), clampTo(a[1], -100, 100, 0), clampTo(a[2], -100, 100, 0)];
542
+ }
543
+ function normParams(kind, raw) {
544
+ const p = raw && typeof raw === "object" ? raw : {};
545
+ const d = defaultAdjustmentParams(kind);
546
+ switch (kind) {
547
+ case "curves":
548
+ return { master: normCurve(p.master), red: normCurve(p.red), green: normCurve(p.green), blue: normCurve(p.blue) };
549
+ case "levels":
550
+ return { master: normLevels(p.master), red: normLevels(p.red), green: normLevels(p.green), blue: normLevels(p.blue) };
551
+ case "hueSaturation": {
552
+ const ranges = [0, 1, 2, 3, 4, 5].map((i) => {
553
+ const r = Array.isArray(p.ranges) && p.ranges[i] && typeof p.ranges[i] === "object" ? p.ranges[i] : {};
554
+ const n = neutralHueRange(i);
555
+ const b = Array.isArray(r.bounds) && r.bounds.length === 4 && r.bounds.every((v) => Number.isFinite(v)) ? r.bounds : n.bounds;
556
+ return {
557
+ hue: clampTo(r.hue, -180, 180, 0),
558
+ saturation: clampTo(r.saturation, -100, 100, 0),
559
+ lightness: clampTo(r.lightness, -100, 100, 0),
560
+ bounds: [b[0], b[1], b[2], b[3]]
561
+ };
562
+ });
563
+ const colorize = p.colorize === true;
564
+ return {
565
+ hue: colorize ? clampTo(p.hue, 0, 360, 0) : clampTo(p.hue, -180, 180, 0),
566
+ saturation: clampTo(p.saturation, -100, 100, 0),
567
+ lightness: clampTo(p.lightness, -100, 100, 0),
568
+ colorize,
569
+ ranges
570
+ };
571
+ }
572
+ case "vibrance":
573
+ return { vibrance: clampTo(p.vibrance, -100, 100, 0), saturation: clampTo(p.saturation, -100, 100, 0) };
574
+ case "colorBalance":
575
+ return {
576
+ shadows: normTone(p.shadows),
577
+ midtones: normTone(p.midtones),
578
+ highlights: normTone(p.highlights),
579
+ preserveLuminosity: p.preserveLuminosity !== false
580
+ };
581
+ case "blackWhite": {
582
+ const out = {};
583
+ for (const k of ["reds", "yellows", "greens", "cyans", "blues", "magentas"]) out[k] = clampTo(p[k], -200, 300, d[k]);
584
+ out.tint = typeof p.tint === "string" && HEX.test(p.tint) ? p.tint.toLowerCase() : null;
585
+ return out;
586
+ }
587
+ case "photoFilter":
588
+ return {
589
+ color: typeof p.color === "string" && HEX.test(p.color) ? p.color.toLowerCase() : d.color,
590
+ density: clampTo(p.density, 0, 100, d.density),
591
+ preserveLuminosity: p.preserveLuminosity !== false
592
+ };
593
+ case "posterize":
594
+ return { levels: Math.round(clampTo(p.levels, 2, 255, 4)) };
595
+ case "threshold":
596
+ return { level: Math.round(clampTo(p.level, 1, 255, 128)) };
597
+ case "invert":
598
+ return {};
599
+ }
600
+ }
601
+ function normalizeAdjustmentLayer(raw) {
602
+ if (!raw || typeof raw !== "object") return null;
603
+ const r = raw;
604
+ if (!isAdjustmentKind(r.kind)) return null;
605
+ const layer = {
606
+ id: typeof r.id === "string" && r.id ? r.id : `adj_${Date.now().toString(36)}_${(++layerCounter).toString(36)}`,
607
+ kind: r.kind,
608
+ enabled: r.enabled !== false,
609
+ opacity: clampTo(r.opacity, 0, 1, 1),
610
+ blend: normalizeBlendMode(r.blend),
611
+ params: normParams(r.kind, r.params)
612
+ };
613
+ if (typeof r.name === "string" && r.name) layer.name = r.name;
614
+ return layer;
615
+ }
616
+ function normalizeAdjustmentLayers(raw) {
617
+ if (!Array.isArray(raw)) return [];
618
+ const seen = /* @__PURE__ */ new Set();
619
+ const out = [];
620
+ for (const item of raw) {
621
+ const layer = normalizeAdjustmentLayer(item);
622
+ if (!layer) continue;
623
+ while (seen.has(layer.id)) layer.id = `${layer.id}_${(++layerCounter).toString(36)}`;
624
+ seen.add(layer.id);
625
+ out.push(layer);
626
+ }
627
+ return out;
628
+ }
629
+ function curveLut(points, size = ADJUSTMENT_LUT_SIZE) {
630
+ const pts = points.filter((p) => Number.isFinite(p.input) && Number.isFinite(p.output)).map((p) => [p.input, p.output]).sort((a, b) => a[0] - b[0]);
631
+ const dedup = [];
632
+ for (const p of pts) {
633
+ if (dedup.length && Math.abs(dedup[dedup.length - 1][0] - p[0]) < 1e-6) continue;
634
+ dedup.push(p);
635
+ }
636
+ const out = new Float32Array(size);
637
+ const n = dedup.length;
638
+ if (n < 2) {
639
+ for (let k = 0; k < size; k++) out[k] = k / (size - 1);
640
+ return out;
641
+ }
642
+ const x = dedup.map((p) => p[0]);
643
+ const y = dedup.map((p) => p[1]);
644
+ const m2 = new Float64Array(n);
645
+ if (n > 2) {
646
+ const c = new Float64Array(n);
647
+ const d = new Float64Array(n);
648
+ for (let i = 1; i < n - 1; i++) {
649
+ const h0 = x[i] - x[i - 1];
650
+ const h1 = x[i + 1] - x[i];
651
+ const a = h0 / 6;
652
+ const b = (h0 + h1) / 3;
653
+ const cc = h1 / 6;
654
+ const r = (y[i + 1] - y[i]) / h1 - (y[i] - y[i - 1]) / h0;
655
+ const denom = b - a * c[i - 1];
656
+ c[i] = cc / denom;
657
+ d[i] = (r - a * d[i - 1]) / denom;
658
+ }
659
+ for (let i = n - 2; i >= 1; i--) m2[i] = d[i] - c[i] * m2[i + 1];
660
+ }
661
+ let seg = 0;
662
+ for (let k = 0; k < size; k++) {
663
+ const xv = k / (size - 1);
664
+ if (xv <= x[0]) {
665
+ out[k] = clamp01(y[0]);
666
+ continue;
667
+ }
668
+ if (xv >= x[n - 1]) {
669
+ out[k] = clamp01(y[n - 1]);
670
+ continue;
671
+ }
672
+ while (seg < n - 2 && xv > x[seg + 1]) seg++;
673
+ const x0 = x[seg], x1 = x[seg + 1];
674
+ const h = x1 - x0;
675
+ const a = (x1 - xv) / h;
676
+ const b = (xv - x0) / h;
677
+ 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;
678
+ out[k] = clamp01(v);
679
+ }
680
+ return out;
681
+ }
682
+ function curveLutFromLevels(points) {
683
+ return curveLut(points.map((p) => ({ input: p.input / 255, output: p.output / 255 })));
684
+ }
685
+ var toUnitLevels = (c) => ({
686
+ inBlack: c.inBlack / 255,
687
+ inWhite: c.inWhite / 255,
688
+ gamma: c.gamma,
689
+ outBlack: c.outBlack / 255,
690
+ outWhite: c.outWhite / 255
691
+ });
692
+ function levelsValue(ch, v, quantum) {
693
+ const range = Math.max(1e-6, ch.inWhite - ch.inBlack);
694
+ let u;
695
+ if (quantum !== void 0 && Number.isFinite(quantum) && quantum >= 1) {
696
+ const q = quantum;
697
+ const vq = Math.round(v * q) / q;
698
+ u = Math.floor(clamp01((vq - ch.inBlack) / range) * q + 0.5 + 1e-3) / q;
699
+ } else {
700
+ u = clamp01((v - ch.inBlack) / range);
701
+ }
702
+ const g = Math.max(0.01, ch.gamma);
703
+ let t;
704
+ if (g > 1) {
705
+ const power = Math.pow(u, 1 / g);
706
+ const line = 0.93 * Math.pow(2, g) * u;
707
+ if (power <= 1e-6 || line <= 1e-6) t = Math.min(power, line);
708
+ else {
709
+ const p = 10;
710
+ t = Math.pow(Math.pow(power, -p) + Math.pow(line, -p), -1 / p);
711
+ }
712
+ } else {
713
+ t = Math.pow(u, 1 / g);
714
+ }
715
+ return ch.outBlack + t * (ch.outWhite - ch.outBlack);
716
+ }
717
+ function rgbToHsl(c) {
718
+ const max = Math.max(c[0], c[1], c[2]);
719
+ const min = Math.min(c[0], c[1], c[2]);
720
+ const l = (max + min) / 2;
721
+ if (Math.abs(max - min) < 1e-7) return [0, 0, l];
722
+ const d = max - min;
723
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
724
+ let h;
725
+ if (max === c[0]) h = remEuclid((c[1] - c[2]) / d, 6);
726
+ else if (max === c[1]) h = (c[2] - c[0]) / d + 2;
727
+ else h = (c[0] - c[1]) / d + 4;
728
+ return [h / 6, s, l];
729
+ }
730
+ function hslToRgb(h, s, l) {
731
+ if (s <= 0) return [l, l, l];
732
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
733
+ const p = 2 * l - q;
734
+ const f = (t) => {
735
+ t = remEuclid(t, 1);
736
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
737
+ if (t < 0.5) return q;
738
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
739
+ return p;
740
+ };
741
+ return [f(h + 1 / 3), f(h), f(h - 1 / 3)];
742
+ }
743
+ function hueRangeWeight(range, deg) {
744
+ const [a, b0, c0, d0] = range.bounds;
745
+ const rel = (x) => Number.isFinite(x) && Number.isFinite(a) ? remEuclid(x - a, 360) : 0;
746
+ const b = rel(b0);
747
+ const c = Math.max(rel(c0), b);
748
+ const d = Math.max(rel(d0), c);
749
+ const h = rel(deg);
750
+ if (h <= b) return b <= 0 ? 1 : h / b;
751
+ if (h <= c) return 1;
752
+ if (h <= d) return d <= c ? 1 : (d - h) / (d - c);
753
+ return 0;
754
+ }
755
+ var isNeutralRange = (r) => r.hue === 0 && r.saturation === 0 && r.lightness === 0;
756
+ function hueRangeTables(ranges) {
757
+ const n = ADJUSTMENT_LUT_SIZE;
758
+ const t = [new Float32Array(n), new Float32Array(n), new Float32Array(n)];
759
+ const active = ranges.filter((r) => !isNeutralRange(r));
760
+ for (let k = 0; k < n; k++) {
761
+ const deg = k / (n - 1) * 360;
762
+ for (const r of active) {
763
+ const w = hueRangeWeight(r, deg);
764
+ t[0][k] += w * r.hue;
765
+ t[1][k] += w * r.saturation / 100;
766
+ t[2][k] += w * r.lightness / 100;
767
+ }
768
+ }
769
+ return t;
770
+ }
771
+ function hueSaturation(c, hue, s, l, colorize) {
772
+ const lighten = (v) => l > 0 ? v + (1 - v) * l : v * (1 + l);
773
+ if (colorize) {
774
+ const ll2 = rgbToHsl(c)[2];
775
+ return hslToRgb(remEuclid(hue, 360) / 360, Math.min(1, Math.abs(s)), lighten(ll2));
776
+ }
777
+ const [hh0, ss0, ll] = rgbToHsl([lighten(c[0]), lighten(c[1]), lighten(c[2])]);
778
+ const hh = remEuclid(hh0 + hue / 360, 1);
779
+ const ss = Math.min(1, Math.max(0, s > 0 ? ss0 / Math.max(1 / 512, 1 - s) : ss0 * (1 + s)));
780
+ return hslToRgb(hh, ss, ll);
781
+ }
782
+ function rangeLightness(c, l) {
783
+ const k = Math.min(1, Math.max(-1, l));
784
+ const target = k > 0 ? Math.max(c[0], c[1], c[2]) : Math.min(c[0], c[1], c[2]);
785
+ const a = Math.abs(k);
786
+ return [c[0] + (target - c[0]) * a, c[1] + (target - c[1]) * a, c[2] + (target - c[2]) * a];
787
+ }
788
+ var VIBRANCE_GREY = [0.288, 0.712];
789
+ var VIBRANCE_BOOST = [0.5001, 0.0887, 0.4374, 0.7573, -0.8113, 0.5154, 2.8026];
790
+ function vibranceSkin(h) {
791
+ if (h <= 30) return 1;
792
+ if (h < 45) return (45 - h) / 15;
793
+ if (h < 300) return 0;
794
+ return (h - 300) / 60;
795
+ }
796
+ function hsvHue(c, mx, mn) {
797
+ const d = mx - mn;
798
+ let h;
799
+ if (mx === c[0]) h = remEuclid((c[1] - c[2]) / d, 6);
800
+ else if (mx === c[1]) h = (c[2] - c[0]) / d + 2;
801
+ else h = (c[0] - c[1]) / d + 4;
802
+ return h * 60;
803
+ }
804
+ function vibranceLinear(c, v) {
805
+ const mx = Math.max(c[0], c[1], c[2]);
806
+ const mn = Math.min(c[0], c[1], c[2]);
807
+ if (mx <= 0 || mx - mn <= 1e-9) return c;
808
+ const t = 1 - mn / mx;
809
+ const shape = t * (1 - t) * (2 - t + t * t) * mx * Math.max(0, 1 - mx);
810
+ let t2, v2;
811
+ if (v < 0) {
812
+ const a = -v;
813
+ t2 = t * (1 - a / 4) * (1 - a + a * t * (1 + t) / 2);
814
+ v2 = mx - a * shape;
815
+ } else {
816
+ const [c0, c1, p, q, r0, ra, rt] = VIBRANCE_BOOST;
817
+ const w = vibranceSkin(hsvHue(c, mx, mn));
818
+ const a = v * (1 - w) + 0.64 * Math.pow(v, 1.6) * w;
819
+ const boost = (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));
820
+ t2 = Math.min(1, t * (1 + boost));
821
+ v2 = mx + v / 4 * shape;
822
+ }
823
+ const m2 = v2 * (1 - t2);
824
+ const k = (v2 - m2) / (mx - mn);
825
+ return [m2 + (c[0] - mn) * k, m2 + (c[1] - mn) * k, m2 + (c[2] - mn) * k];
826
+ }
827
+ function vibrancePixel(c, v, s) {
828
+ let l = [srgbToLinear(Math.max(0, c[0])), srgbToLinear(Math.max(0, c[1])), srgbToLinear(Math.max(0, c[2]))];
829
+ if (v !== 0) l = vibranceLinear(l, v);
830
+ if (s !== 0) {
831
+ const g = VIBRANCE_GREY[0] * l[0] + VIBRANCE_GREY[1] * l[1];
832
+ l = l.map((x) => Math.min(1, Math.max(0, g + (1 + s) * (x - g))));
833
+ }
834
+ return l.map((x) => linearToSrgb(clamp01(x)));
835
+ }
836
+ var BALANCE_LEVELS = 0.39;
837
+ function colorBalanceChannel(tones, preserveLuminosity, ch, v) {
838
+ const [shadows, midtones, highlights] = tones;
839
+ const black = (x, b) => Math.max(0, (x - b) / (1 - b));
840
+ const white = (x, w) => Math.min(1, x / w);
841
+ let out = clamp01(v);
842
+ for (const [tone2, sliders] of [
843
+ [2, highlights],
844
+ [0, shadows],
845
+ [1, midtones]
846
+ ]) {
847
+ const a = [0, 1, 2].map((i) => Math.min(1, Math.max(-1, sliders[i] / 100)));
848
+ if (preserveLuminosity) {
849
+ const own = _nullishCoalesce(a[ch], () => ( 0));
850
+ const lo = Math.min(a[0], a[1], a[2]);
851
+ const hi = Math.max(a[0], a[1], a[2]);
852
+ if (tone2 === 0) out = black(out, BALANCE_LEVELS * (hi - own));
853
+ else if (tone2 === 1) out = Math.pow(out, Math.pow(2, (hi + lo) / 2 - own));
854
+ else out = white(out, 1 - BALANCE_LEVELS * (own - lo));
855
+ continue;
856
+ }
857
+ const s = a[ch];
858
+ if (s === 0) continue;
859
+ if (tone2 === 1) out = Math.pow(out, Math.pow(2, -s));
860
+ else if (tone2 === 0 && s < 0) out = Math.pow(black(out, BALANCE_LEVELS * -s), Math.pow(2, -s / 2));
861
+ else if (tone2 === 2 && s > 0) out = Math.pow(white(out, 1 - BALANCE_LEVELS * s), Math.pow(2, -s / 2));
862
+ else out = Math.pow(out, Math.pow(2, -s / 2));
863
+ }
864
+ return out;
865
+ }
866
+ function blackWhiteGray(c, weights) {
867
+ const [r, g, b] = c;
868
+ const max = Math.max(r, g, b);
869
+ const min = Math.min(r, g, b);
870
+ const mid = r + g + b - max - min;
871
+ const primary = r >= g && r >= b ? 0 : g >= b ? 2 : 4;
872
+ const secondary = b <= r && b <= g ? 1 : r <= g ? 3 : 5;
873
+ return clamp01(min + (mid - min) * weights[secondary] / 100 + (max - mid) * weights[primary] / 100);
874
+ }
875
+ var D50 = [0.96422, 1, 0.82521];
876
+ var SRGB_TO_XYZ_D50 = [
877
+ [0.436074, 0.385064, 0.14308],
878
+ [0.222504, 0.716878, 0.060618],
879
+ [0.013932, 0.097104, 0.714173]
880
+ ];
881
+ var XYZ_D50_TO_SRGB = [
882
+ [3.133856, -1.616867, -0.490615],
883
+ [-0.978768, 1.916142, 0.033454],
884
+ [0.071945, -0.228991, 1.405243]
885
+ ];
886
+ function photoFilterMatrix(color, density, normalizeY = false) {
887
+ const d = Number.isFinite(density) ? clamp01(density) : 0;
888
+ const lin = [0, 1, 2].map((i) => srgbToLinear(clamp01(color[i])));
889
+ const xyz = SRGB_TO_XYZ_D50.map((r) => r[0] * lin[0] + r[1] * lin[1] + r[2] * lin[2]);
890
+ let s = [0, 1, 2].map((i) => 1 - d + d * xyz[i] / D50[i]);
891
+ if (normalizeY && s[1] > 1e-6) {
892
+ const y = s[1];
893
+ s = s.map((v) => v / y);
894
+ }
895
+ return [0, 1, 2].map(
896
+ (r) => [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])
897
+ );
898
+ }
899
+ function posterizeValue(v, levels) {
900
+ const n = Math.min(255, Math.max(2, Math.round(levels)));
901
+ const x = Math.round(clamp01(v) * 255);
902
+ const bin = Math.min(n - 1, Math.floor(x * n / 256));
903
+ return Math.floor(bin * 255 / (n - 1)) / 255;
904
+ }
905
+ var rgbToGray = (c) => 0.299 * c[0] + 0.587 * c[1] + 0.114 * c[2];
906
+ function hexToRgb(hex) {
907
+ if (!HEX.test(hex)) return [0, 0, 0];
908
+ const n = parseInt(hex.slice(1), 16);
909
+ return [(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255];
910
+ }
911
+ function lutOp(luts) {
912
+ const [lr, lg, lb] = luts;
913
+ return (c) => {
914
+ c[0] = sampleLut(lr, c[0]);
915
+ c[1] = sampleLut(lg, c[1]);
916
+ c[2] = sampleLut(lb, c[2]);
917
+ };
918
+ }
919
+ var isIdentityCurve = (pts) => pts.length === 2 && pts[0].input === 0 && pts[0].output === 0 && pts[1].input === 255 && pts[1].output === 255;
920
+ var isIdentityLevels = (c) => c.inBlack === 0 && c.inWhite === 255 && c.gamma === 1 && c.outBlack === 0 && c.outWhite === 255;
921
+ var LEVELS_QUANTUM = 255;
922
+ function toneLuts(layer) {
923
+ const n = ADJUSTMENT_LUT_SIZE;
924
+ if (layer.kind === "curves") {
925
+ const m = curveLutFromLevels(layer.params.master);
926
+ return [layer.params.red, layer.params.green, layer.params.blue].map((pts) => {
927
+ const ch = curveLutFromLevels(pts);
928
+ for (let k = 0; k < n; k++) ch[k] = sampleLut(m, ch[k]);
929
+ return ch;
930
+ });
931
+ }
932
+ if (layer.kind === "levels") {
933
+ const m = toUnitLevels(layer.params.master);
934
+ return [layer.params.red, layer.params.green, layer.params.blue].map((rec) => {
935
+ const c = toUnitLevels(rec);
936
+ const out = new Float32Array(n);
937
+ for (let k = 0; k < n; k++) out[k] = levelsValue(m, levelsValue(c, k / (n - 1), LEVELS_QUANTUM), LEVELS_QUANTUM);
938
+ return out;
939
+ });
940
+ }
941
+ return null;
942
+ }
943
+ function compileAdjustmentLayer(layer) {
944
+ switch (layer.kind) {
945
+ case "curves": {
946
+ const p = layer.params;
947
+ if ([p.master, p.red, p.green, p.blue].every(isIdentityCurve)) return null;
948
+ return lutOp(toneLuts(layer));
949
+ }
950
+ case "levels": {
951
+ const p = layer.params;
952
+ if ([p.master, p.red, p.green, p.blue].every(isIdentityLevels)) return null;
953
+ return lutOp(toneLuts(layer));
954
+ }
955
+ case "hueSaturation": {
956
+ const p = layer.params;
957
+ const rangesActive = !p.colorize && p.ranges.some((r) => !isNeutralRange(r));
958
+ if (!p.colorize && !rangesActive && p.hue === 0 && p.saturation === 0 && p.lightness === 0) return null;
959
+ const tables = rangesActive ? hueRangeTables(p.ranges) : null;
960
+ const sat2 = p.saturation / 100;
961
+ const light = Math.min(1, Math.max(-1, p.lightness / 100));
962
+ return (c) => {
963
+ let dh = 0, ds = 0, dl = 0;
964
+ if (tables) {
965
+ const h0 = rgbToHsl(c)[0];
966
+ dh = sampleLut(tables[0], h0);
967
+ ds = sampleLut(tables[1], h0);
968
+ dl = sampleLut(tables[2], h0);
969
+ }
970
+ const out = rangeLightness(hueSaturation(c, p.hue + dh, Math.min(1, Math.max(-1, sat2 + ds)), light, p.colorize), dl);
971
+ c[0] = out[0];
972
+ c[1] = out[1];
973
+ c[2] = out[2];
974
+ };
975
+ }
976
+ case "vibrance": {
977
+ const v = layer.params.vibrance / 100;
978
+ const s = layer.params.saturation / 100;
979
+ if (v === 0 && s === 0) return null;
980
+ return (c) => {
981
+ const out = vibrancePixel(c, v, s);
982
+ c[0] = out[0];
983
+ c[1] = out[1];
984
+ c[2] = out[2];
985
+ };
986
+ }
987
+ case "colorBalance": {
988
+ const p = layer.params;
989
+ const tones = [p.shadows, p.midtones, p.highlights];
990
+ if (tones.every((t) => t[0] === 0 && t[1] === 0 && t[2] === 0)) return null;
991
+ const n = ADJUSTMENT_LUT_SIZE;
992
+ const luts = [0, 1, 2].map((ch) => {
993
+ const out = new Float32Array(n);
994
+ for (let k = 0; k < n; k++) out[k] = colorBalanceChannel(tones, p.preserveLuminosity, ch, k / (n - 1));
995
+ return out;
996
+ });
997
+ return lutOp(luts);
998
+ }
999
+ case "blackWhite": {
1000
+ const p = layer.params;
1001
+ const weights = [p.reds, p.yellows, p.greens, p.cyans, p.blues, p.magentas];
1002
+ const tint = p.tint ? hexToRgb(p.tint) : null;
1003
+ return (c) => {
1004
+ const g = blackWhiteGray(c, weights);
1005
+ if (tint) {
1006
+ const t = setLum(tint, g);
1007
+ c[0] = clamp01(t[0]);
1008
+ c[1] = clamp01(t[1]);
1009
+ c[2] = clamp01(t[2]);
1010
+ } else {
1011
+ c[0] = c[1] = c[2] = g;
1012
+ }
1013
+ };
1014
+ }
1015
+ case "photoFilter": {
1016
+ const p = layer.params;
1017
+ if (p.density <= 0) return null;
1018
+ const m = photoFilterMatrix(hexToRgb(p.color), p.density / 100, false);
1019
+ const preserve = p.preserveLuminosity;
1020
+ return (c) => {
1021
+ const l = [srgbToLinear(Math.max(0, c[0])), srgbToLinear(Math.max(0, c[1])), srgbToLinear(Math.max(0, c[2]))];
1022
+ let f = [0, 1, 2].map((r) => linearToSrgb(Math.max(0, m[r][0] * l[0] + m[r][1] * l[1] + m[r][2] * l[2])));
1023
+ if (preserve) {
1024
+ const target = 0.3 * c[0] + 0.59 * c[1] + 0.11 * c[2];
1025
+ f = setLum(f, target);
1026
+ }
1027
+ c[0] = clamp01(f[0]);
1028
+ c[1] = clamp01(f[1]);
1029
+ c[2] = clamp01(f[2]);
1030
+ };
1031
+ }
1032
+ case "invert":
1033
+ return (c) => {
1034
+ c[0] = 1 - c[0];
1035
+ c[1] = 1 - c[1];
1036
+ c[2] = 1 - c[2];
1037
+ };
1038
+ case "posterize": {
1039
+ const levels = layer.params.levels;
1040
+ return (c) => {
1041
+ c[0] = posterizeValue(c[0], levels);
1042
+ c[1] = posterizeValue(c[1], levels);
1043
+ c[2] = posterizeValue(c[2], levels);
1044
+ };
1045
+ }
1046
+ case "threshold": {
1047
+ const t = Math.round(layer.params.level);
1048
+ return (c) => {
1049
+ const v = Math.round(rgbToGray(c) * 255) >= t ? 1 : 0;
1050
+ c[0] = c[1] = c[2] = v;
1051
+ };
1052
+ }
1053
+ }
1054
+ return null;
1055
+ }
1056
+ var COMPILE_CACHE_LIMIT = 64;
1057
+ var compileCache = /* @__PURE__ */ new Map();
1058
+ function compiledFor(layer) {
1059
+ const key = `${layer.kind}:${JSON.stringify(layer.params)}`;
1060
+ if (compileCache.has(key)) {
1061
+ const op2 = compileCache.get(key);
1062
+ compileCache.delete(key);
1063
+ compileCache.set(key, op2);
1064
+ return op2;
1065
+ }
1066
+ const op = compileAdjustmentLayer(layer);
1067
+ compileCache.set(key, op);
1068
+ if (compileCache.size > COMPILE_CACHE_LIMIT) compileCache.delete(compileCache.keys().next().value);
1069
+ return op;
1070
+ }
1071
+ function activeAdjustmentLayers(layers) {
1072
+ if (!_optionalChain([layers, 'optionalAccess', _5 => _5.length])) return [];
1073
+ return layers.filter((l) => l.enabled !== false && (_nullishCoalesce(l.opacity, () => ( 1))) > 0 && compiledFor(l) !== null);
1074
+ }
1075
+ function evaluateAdjustmentLayers(color, layers) {
1076
+ const c = [color[0], color[1], color[2]];
1077
+ for (const layer of layers) applyLayer(c, layer, compiledFor(layer));
1078
+ return c;
1079
+ }
1080
+ function applyLayer(c, layer, op) {
1081
+ if (!op || layer.enabled === false) return;
1082
+ const alpha = Math.min(1, Math.max(0, _nullishCoalesce(layer.opacity, () => ( 1))));
1083
+ if (alpha <= 0) return;
1084
+ const blend = _nullishCoalesce(layer.blend, () => ( "normal"));
1085
+ if (blend === "normal" && alpha >= 1) {
1086
+ op(c);
1087
+ return;
1088
+ }
1089
+ const cb0 = c[0], cb1 = c[1], cb2 = c[2];
1090
+ op(c);
1091
+ const b = blend === "normal" ? c : blendRgb(blend, [cb0, cb1, cb2], c);
1092
+ c[0] = (1 - alpha) * cb0 + alpha * b[0];
1093
+ c[1] = (1 - alpha) * cb1 + alpha * b[1];
1094
+ c[2] = (1 - alpha) * cb2 + alpha * b[2];
1095
+ }
1096
+ function applyAdjustmentLayers(imageData, layers) {
1097
+ const active = activeAdjustmentLayers(layers);
1098
+ if (!active.length) return;
1099
+ const ops = active.map((l) => compiledFor(l));
1100
+ const data = imageData.data;
1101
+ const len = data.length;
1102
+ const c = [0, 0, 0];
1103
+ for (let i = 0; i < len; i += 4) {
1104
+ if (data[i + 3] === 0) continue;
1105
+ c[0] = data[i] / 255;
1106
+ c[1] = data[i + 1] / 255;
1107
+ c[2] = data[i + 2] / 255;
1108
+ for (let k = 0; k < active.length; k++) applyLayer(c, active[k], ops[k]);
1109
+ data[i] = Math.round(clamp01(c[0]) * 255);
1110
+ data[i + 1] = Math.round(clamp01(c[1]) * 255);
1111
+ data[i + 2] = Math.round(clamp01(c[2]) * 255);
1112
+ }
1113
+ }
1114
+ function adjustmentLayersKey(layers) {
1115
+ if (!_optionalChain([layers, 'optionalAccess', _6 => _6.length])) return "";
1116
+ return JSON.stringify(
1117
+ layers.map((l) => [l.kind, l.enabled !== false, _nullishCoalesce(l.opacity, () => ( 1)), _nullishCoalesce(l.blend, () => ( "normal")), l.params])
1118
+ );
1119
+ }
1120
+
234
1121
  // src/image-editor/filters.ts
235
1122
  function clamp(value, min = 0, max = 255) {
236
1123
  return Math.max(min, Math.min(max, value));
@@ -241,6 +1128,7 @@ function applyAdjustments(imageData, adjustments) {
241
1128
  } else {
242
1129
  applyLegacyAdjustments(imageData, adjustments);
243
1130
  }
1131
+ applyAdjustmentLayers(imageData, adjustments.layers);
244
1132
  if (adjustments.blur > 0) {
245
1133
  applyFastBoxBlur(imageData, Math.min(Math.round(adjustments.blur), 40));
246
1134
  }
@@ -491,6 +1379,63 @@ function removeAlpha(imageData) {
491
1379
  }
492
1380
  }
493
1381
 
1382
+ // src/image-editor/histogram.ts
1383
+ var HISTOGRAM_BINS = 256;
1384
+ function computeHistogram(imageData) {
1385
+ const red = new Uint32Array(HISTOGRAM_BINS);
1386
+ const green = new Uint32Array(HISTOGRAM_BINS);
1387
+ const blue = new Uint32Array(HISTOGRAM_BINS);
1388
+ const luminance = new Uint32Array(HISTOGRAM_BINS);
1389
+ const shadows = [0, 0, 0];
1390
+ const highlights = [0, 0, 0];
1391
+ let samples = 0;
1392
+ let transparent = 0;
1393
+ const data = imageData.data;
1394
+ const len = data.length - data.length % 4;
1395
+ for (let i = 0; i < len; i += 4) {
1396
+ if (!(data[i + 3] > 0)) {
1397
+ transparent++;
1398
+ continue;
1399
+ }
1400
+ samples++;
1401
+ const r = clampByte(data[i]);
1402
+ const g = clampByte(data[i + 1]);
1403
+ const b = clampByte(data[i + 2]);
1404
+ red[r]++;
1405
+ green[g]++;
1406
+ blue[b]++;
1407
+ luminance[Math.round(0.2126 * r + 0.7152 * g + 0.0722 * b)]++;
1408
+ if (r === 0) shadows[0]++;
1409
+ if (g === 0) shadows[1]++;
1410
+ if (b === 0) shadows[2]++;
1411
+ if (r === 255) highlights[0]++;
1412
+ if (g === 255) highlights[1]++;
1413
+ if (b === 255) highlights[2]++;
1414
+ }
1415
+ let max = 0;
1416
+ for (const bins of [red, green, blue, luminance]) {
1417
+ for (let k = 0; k < HISTOGRAM_BINS; k++) if (bins[k] > max) max = bins[k];
1418
+ }
1419
+ return { red, green, blue, luminance, samples, transparent, shadows, highlights, max };
1420
+ }
1421
+ function clampByte(v) {
1422
+ return v <= 0 ? 0 : v >= 255 ? 255 : Math.round(v);
1423
+ }
1424
+ function histogramPath(histogram, channel, width, height, options = {}) {
1425
+ const bins = histogram[channel];
1426
+ const sqrt = (_nullishCoalesce(options.scale, () => ( "sqrt"))) === "sqrt";
1427
+ const f = (v) => sqrt ? Math.sqrt(v) : v;
1428
+ const top = f(_nullishCoalesce(options.max, () => ( histogram.max))) || 1;
1429
+ const dx = width / (HISTOGRAM_BINS - 1);
1430
+ let d = `M0 ${height}`;
1431
+ for (let k = 0; k < HISTOGRAM_BINS; k++) {
1432
+ const y = height - f(bins[k]) / top * height;
1433
+ d += ` L${round2(k * dx)} ${round2(y)}`;
1434
+ }
1435
+ return `${d} L${round2(width)} ${height} Z`;
1436
+ }
1437
+ var round2 = (v) => Math.round(v * 100) / 100;
1438
+
494
1439
  // src/image-editor/geometry.ts
495
1440
  function normalizeAngle(degrees) {
496
1441
  const a = (degrees % 360 + 360) % 360;
@@ -877,12 +1822,19 @@ var ImageCache = (_class = class {constructor() { _class.prototype.__init.call(t
877
1822
  }
878
1823
  }, _class);
879
1824
  var globalImageCache = new ImageCache();
880
- var ImageEditorRenderer = (_class2 = class {constructor() { _class2.prototype.__init2.call(this);_class2.prototype.__init3.call(this);_class2.prototype.__init4.call(this);_class2.prototype.__init5.call(this);_class2.prototype.__init6.call(this); }
1825
+ var ImageEditorRenderer = (_class2 = class {constructor() { _class2.prototype.__init2.call(this);_class2.prototype.__init3.call(this);_class2.prototype.__init4.call(this);_class2.prototype.__init5.call(this);_class2.prototype.__init6.call(this);_class2.prototype.__init7.call(this); }
881
1826
  __init2() {this.imageCache = globalImageCache}
882
1827
  __init3() {this.cachedBaseCanvas = null}
883
1828
  __init4() {this.cachedBaseKey = ""}
884
- __init5() {this.sourceIds = /* @__PURE__ */ new WeakMap()}
885
- __init6() {this.nextSourceId = 0}
1829
+ /**
1830
+ * Histogram of the last viewport base render (after adjustments and layers, before
1831
+ * annotations); `null` until a render with pixel access has happened.
1832
+ */
1833
+ __init5() {this.previewHistogram = null}
1834
+ /** Called whenever the viewport base image is re-rendered with the new histogram. */
1835
+
1836
+ __init6() {this.sourceIds = /* @__PURE__ */ new WeakMap()}
1837
+ __init7() {this.nextSourceId = 0}
886
1838
  /**
887
1839
  * Helper to create a canvas element safely in browser/JSDOM.
888
1840
  */
@@ -937,13 +1889,19 @@ var ImageEditorRenderer = (_class2 = class {constructor() { _class2.prototype.__
937
1889
  `${region.x},${region.y},${region.width},${region.height}`,
938
1890
  `${projection.angle},${transform.flipH},${transform.flipV}`,
939
1891
  `${adjustments.brightness},${adjustments.contrast},${adjustments.saturation},${adjustments.exposure},${adjustments.temperature},${_nullishCoalesce(adjustments.tint, () => ( 0))},${adjustments.blur},${adjustments.opacity},p${normalizeProcessVersion(adjustments.process)}`,
940
- `${depthMask.enabled},${depthMask.maskSource},${_optionalChain([depthMask, 'access', _ => _.depthRange, 'optionalAccess', _2 => _2[0]])}-${_optionalChain([depthMask, 'access', _3 => _3.depthRange, 'optionalAccess', _4 => _4[1]])},${depthMask.softness},${depthMask.invert},${depthMask.mode}`
1892
+ adjustmentLayersKey(adjustments.layers),
1893
+ `${depthMask.enabled},${depthMask.maskSource},${_optionalChain([depthMask, 'access', _7 => _7.depthRange, 'optionalAccess', _8 => _8[0]])}-${_optionalChain([depthMask, 'access', _9 => _9.depthRange, 'optionalAccess', _10 => _10[1]])},${depthMask.softness},${depthMask.invert},${depthMask.mode}`
941
1894
  ].join("|");
942
1895
  let baseCanvas = this.cachedBaseCanvas;
943
1896
  if (!baseCanvas || this.cachedBaseKey !== baseKey) {
944
- baseCanvas = await this.renderBaseImage(img, naturalWidth, naturalHeight, region, transform, state);
1897
+ let histogram = null;
1898
+ baseCanvas = await this.renderBaseImage(img, naturalWidth, naturalHeight, region, transform, state, (pixels) => {
1899
+ histogram = computeHistogram(pixels);
1900
+ });
945
1901
  this.cachedBaseCanvas = baseCanvas;
946
1902
  this.cachedBaseKey = baseKey;
1903
+ this.previewHistogram = histogram;
1904
+ _optionalChain([this, 'access', _11 => _11.onPreviewHistogram, 'optionalCall', _12 => _12(histogram)]);
947
1905
  }
948
1906
  const drawW = rotated.width * projection.scale;
949
1907
  const drawH = rotated.height * projection.scale;
@@ -974,7 +1932,7 @@ var ImageEditorRenderer = (_class2 = class {constructor() { _class2.prototype.__
974
1932
  * Draws `region` of the source image with rotation/flip applied, then pixel adjustments
975
1933
  * and the depth mask (which receives the identical transform so it stays aligned).
976
1934
  */
977
- async renderBaseImage(img, naturalWidth, naturalHeight, region, transform, state) {
1935
+ async renderBaseImage(img, naturalWidth, naturalHeight, region, transform, state, onPixels) {
978
1936
  const { adjustments, depthMask } = state;
979
1937
  const rotated = getRotatedSize(region.width, region.height, transform);
980
1938
  const baseW = Math.max(1, Math.round(rotated.width));
@@ -1031,6 +1989,7 @@ var ImageEditorRenderer = (_class2 = class {constructor() { _class2.prototype.__
1031
1989
  } catch (e3) {
1032
1990
  }
1033
1991
  }
1992
+ _optionalChain([onPixels, 'optionalCall', _13 => _13(imageData)]);
1034
1993
  baseCtx.putImageData(imageData, 0, 0);
1035
1994
  }
1036
1995
  return baseCanvas;
@@ -1048,6 +2007,59 @@ var ImageEditorRenderer = (_class2 = class {constructor() { _class2.prototype.__
1048
2007
  * Draws an individual annotation onto the canvas.
1049
2008
  */
1050
2009
  async drawAnnotation(ctx, ann) {
2010
+ const blend = normalizeBlendMode(ann.blend);
2011
+ if (blend === "normal") {
2012
+ await this.drawAnnotationContent(ctx, ann);
2013
+ return;
2014
+ }
2015
+ const op = canvasCompositeOperation(blend);
2016
+ if (op) {
2017
+ ctx.save();
2018
+ ctx.globalCompositeOperation = op;
2019
+ await this.drawAnnotationContent(ctx, ann);
2020
+ ctx.restore();
2021
+ return;
2022
+ }
2023
+ if (!await this.drawAnnotationBlended(ctx, ann)) {
2024
+ await this.drawAnnotationContent(ctx, ann);
2025
+ }
2026
+ }
2027
+ /**
2028
+ * Scalar fallback for blend modes the canvas cannot composite (Photoshop soft light, linear /
2029
+ * vivid / pin light, hard mix, subtract, divide, darker / lighter colour, linear burn / dodge):
2030
+ * draws the annotation on a same-size layer with the same transform, blends it into a copy of
2031
+ * the target's pixels with `blendImageData`, and copies the result back inside the current clip.
2032
+ */
2033
+ async drawAnnotationBlended(ctx, ann) {
2034
+ const canvas = ctx.canvas;
2035
+ if (!canvas || typeof ctx.getTransform !== "function" || typeof ctx.getImageData !== "function") return false;
2036
+ const w = canvas.width;
2037
+ const h = canvas.height;
2038
+ const layer = this.createCanvas(w, h);
2039
+ const lctx = layer.getContext("2d");
2040
+ if (!lctx) return false;
2041
+ try {
2042
+ lctx.setTransform(ctx.getTransform());
2043
+ lctx.globalAlpha = ctx.globalAlpha;
2044
+ await this.drawAnnotationContent(lctx, ann);
2045
+ const backdrop = ctx.getImageData(0, 0, w, h);
2046
+ const source = lctx.getImageData(0, 0, w, h);
2047
+ if (!backdrop || !source) return false;
2048
+ blendImageData(backdrop, source, normalizeBlendMode(ann.blend));
2049
+ lctx.setTransform(1, 0, 0, 1, 0, 0);
2050
+ lctx.putImageData(backdrop, 0, 0);
2051
+ } catch (e4) {
2052
+ return false;
2053
+ }
2054
+ ctx.save();
2055
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
2056
+ ctx.globalAlpha = 1;
2057
+ ctx.globalCompositeOperation = "copy";
2058
+ ctx.drawImage(layer, 0, 0);
2059
+ ctx.restore();
2060
+ return true;
2061
+ }
2062
+ async drawAnnotationContent(ctx, ann) {
1051
2063
  ctx.save();
1052
2064
  if (ann.opacity !== void 0) {
1053
2065
  ctx.globalAlpha = ann.opacity;
@@ -1150,7 +2162,7 @@ var ImageEditorRenderer = (_class2 = class {constructor() { _class2.prototype.__
1150
2162
  try {
1151
2163
  const loadedImg = await this.imageCache.get(imgAnn.src);
1152
2164
  ctx.drawImage(loadedImg, imgAnn.x, imgAnn.y, imgAnn.width, imgAnn.height);
1153
- } catch (e4) {
2165
+ } catch (e5) {
1154
2166
  }
1155
2167
  break;
1156
2168
  }
@@ -1303,8 +2315,8 @@ var ImageEditorRenderer = (_class2 = class {constructor() { _class2.prototype.__
1303
2315
  if (!ctx) return maskCanvas;
1304
2316
  ctx.fillStyle = MASK_BLACK;
1305
2317
  ctx.fillRect(0, 0, naturalWidth, naturalHeight);
1306
- const useCrop = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _5 => _5.useCrop]), () => ( true));
1307
- const useAnnotations = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _6 => _6.useAnnotations]), () => ( true));
2318
+ const useCrop = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _14 => _14.useCrop]), () => ( true));
2319
+ const useAnnotations = _nullishCoalesce(_optionalChain([options, 'optionalAccess', _15 => _15.useAnnotations]), () => ( true));
1308
2320
  if (useCrop && state.crop) {
1309
2321
  ctx.fillStyle = MASK_WHITE;
1310
2322
  ctx.fillRect(state.crop.x, state.crop.y, state.crop.width, state.crop.height);
@@ -1314,7 +2326,7 @@ var ImageEditorRenderer = (_class2 = class {constructor() { _class2.prototype.__
1314
2326
  this.drawAnnotationMask(ctx, ann);
1315
2327
  }
1316
2328
  }
1317
- const imageData = _optionalChain([ctx, 'access', _7 => _7.getImageData, 'optionalCall', _8 => _8(0, 0, naturalWidth, naturalHeight)]);
2329
+ const imageData = _optionalChain([ctx, 'access', _16 => _16.getImageData, 'optionalCall', _17 => _17(0, 0, naturalWidth, naturalHeight)]);
1318
2330
  if (imageData) {
1319
2331
  const d = imageData.data;
1320
2332
  for (let i = 0; i < d.length; i += 4) {
@@ -1401,6 +2413,7 @@ function getBaseProjection(region, transform, baseW, baseH) {
1401
2413
  }
1402
2414
 
1403
2415
  // src/image-editor/controller.ts
2416
+ var IMAGE_EDITOR_SCHEMA_VERSION = 2;
1404
2417
  var DEFAULT_ADJUSTMENTS = {
1405
2418
  brightness: 0,
1406
2419
  contrast: 0,
@@ -1414,11 +2427,30 @@ var DEFAULT_ADJUSTMENTS = {
1414
2427
  };
1415
2428
  function normalizeStoredAdjustments(stored) {
1416
2429
  if (!stored) return { ...DEFAULT_ADJUSTMENTS };
1417
- return {
2430
+ const out = {
1418
2431
  ...DEFAULT_ADJUSTMENTS,
1419
2432
  ...stored,
1420
2433
  process: normalizeProcessVersion(stored.process)
1421
2434
  };
2435
+ const layers = normalizeAdjustmentLayers(stored.layers);
2436
+ if (layers.length) out.layers = layers;
2437
+ else delete out.layers;
2438
+ return out;
2439
+ }
2440
+ function migrateSerializedImageEditorState(data) {
2441
+ const adjustments = normalizeStoredAdjustments(data.adjustments);
2442
+ return {
2443
+ version: _optionalChain([adjustments, 'access', _18 => _18.layers, 'optionalAccess', _19 => _19.length]) ? IMAGE_EDITOR_SCHEMA_VERSION : 1,
2444
+ crop: data.crop ? { ...data.crop } : null,
2445
+ transform: { ...DEFAULT_TRANSFORM, ...data.transform },
2446
+ adjustments: serializeAdjustments(adjustments),
2447
+ depthMask: {
2448
+ ...DEFAULT_DEPTH_MASK,
2449
+ ...data.depthMask,
2450
+ depthRange: [..._nullishCoalesce(_optionalChain([data, 'access', _20 => _20.depthMask, 'optionalAccess', _21 => _21.depthRange]), () => ( DEFAULT_DEPTH_MASK.depthRange))]
2451
+ },
2452
+ annotations: JSON.parse(JSON.stringify(_nullishCoalesce(data.annotations, () => ( []))))
2453
+ };
1422
2454
  }
1423
2455
  var DEFAULT_TRANSFORM = {
1424
2456
  rotate: 0,
@@ -1436,36 +2468,40 @@ var DEFAULT_DEPTH_MASK = {
1436
2468
  var ImageEditorController = (_class3 = class {
1437
2469
 
1438
2470
 
1439
- __init7() {this.imageElement = null}
1440
- __init8() {this.listeners = /* @__PURE__ */ new Set()}
2471
+ __init8() {this.imageElement = null}
2472
+ __init9() {this.listeners = /* @__PURE__ */ new Set()}
2473
+ __init10() {this.histogramListeners = /* @__PURE__ */ new Set()}
1441
2474
 
1442
2475
  // History stacks
1443
- __init9() {this.undoStack = []}
1444
- __init10() {this.redoStack = []}
2476
+ __init11() {this.undoStack = []}
2477
+ __init12() {this.redoStack = []}
1445
2478
 
1446
- __init11() {this.isApplyingHistory = false}
2479
+ __init13() {this.isApplyingHistory = false}
1447
2480
 
1448
- constructor(options = {}) {;_class3.prototype.__init7.call(this);_class3.prototype.__init8.call(this);_class3.prototype.__init9.call(this);_class3.prototype.__init10.call(this);_class3.prototype.__init11.call(this);
2481
+ constructor(options = {}) {;_class3.prototype.__init8.call(this);_class3.prototype.__init9.call(this);_class3.prototype.__init10.call(this);_class3.prototype.__init11.call(this);_class3.prototype.__init12.call(this);_class3.prototype.__init13.call(this);
1449
2482
  this.maxHistoryDepth = _nullishCoalesce(options.maxHistoryDepth, () => ( 50));
1450
2483
  this.onStateChange = options.onStateChange;
1451
2484
  this.renderer = new ImageEditorRenderer();
1452
- const initialAdjustments = normalizeStoredAdjustments(_optionalChain([options, 'access', _9 => _9.initialState, 'optionalAccess', _10 => _10.adjustments]));
2485
+ this.renderer.onPreviewHistogram = (histogram) => {
2486
+ this.histogramListeners.forEach((l) => l(histogram));
2487
+ };
2488
+ const initialAdjustments = normalizeStoredAdjustments(_optionalChain([options, 'access', _22 => _22.initialState, 'optionalAccess', _23 => _23.adjustments]));
1453
2489
  const initialTransform = {
1454
2490
  ...DEFAULT_TRANSFORM,
1455
- ..._optionalChain([options, 'access', _11 => _11.initialState, 'optionalAccess', _12 => _12.transform])
2491
+ ..._optionalChain([options, 'access', _24 => _24.initialState, 'optionalAccess', _25 => _25.transform])
1456
2492
  };
1457
2493
  const initialDepthMask = {
1458
2494
  ...DEFAULT_DEPTH_MASK,
1459
- ..._optionalChain([options, 'access', _13 => _13.initialState, 'optionalAccess', _14 => _14.depthMask])
2495
+ ..._optionalChain([options, 'access', _26 => _26.initialState, 'optionalAccess', _27 => _27.depthMask])
1460
2496
  };
1461
2497
  this.state = {
1462
2498
  sourceUrl: typeof options.image === "string" ? options.image : null,
1463
2499
  imageDimensions: { width: 800, height: 600 },
1464
- crop: _nullishCoalesce(_optionalChain([options, 'access', _15 => _15.initialState, 'optionalAccess', _16 => _16.crop]), () => ( null)),
2500
+ crop: _nullishCoalesce(_optionalChain([options, 'access', _28 => _28.initialState, 'optionalAccess', _29 => _29.crop]), () => ( null)),
1465
2501
  transform: initialTransform,
1466
2502
  adjustments: initialAdjustments,
1467
2503
  depthMask: initialDepthMask,
1468
- annotations: _nullishCoalesce(_optionalChain([options, 'access', _17 => _17.initialState, 'optionalAccess', _18 => _18.annotations]), () => ( [])),
2504
+ annotations: _nullishCoalesce(_optionalChain([options, 'access', _30 => _30.initialState, 'optionalAccess', _31 => _31.annotations]), () => ( [])),
1469
2505
  selectedAnnotationId: null,
1470
2506
  activeTool: "select",
1471
2507
  zoom: 1,
@@ -1512,7 +2548,7 @@ var ImageEditorController = (_class3 = class {
1512
2548
  height: img.naturalHeight || img.height || 600
1513
2549
  }
1514
2550
  };
1515
- } catch (e5) {
2551
+ } catch (e6) {
1516
2552
  return { sourceUrl: src, element: null, dimensions: { ...this.state.imageDimensions } };
1517
2553
  }
1518
2554
  }
@@ -1541,7 +2577,7 @@ var ImageEditorController = (_class3 = class {
1541
2577
  }
1542
2578
  notify() {
1543
2579
  const currentState = { ...this.state };
1544
- _optionalChain([this, 'access', _19 => _19.onStateChange, 'optionalCall', _20 => _20(currentState)]);
2580
+ _optionalChain([this, 'access', _32 => _32.onStateChange, 'optionalCall', _33 => _33(currentState)]);
1545
2581
  this.listeners.forEach((l) => l(currentState));
1546
2582
  }
1547
2583
  // --- History (Undo / Redo / Reset) ---
@@ -1690,10 +2726,16 @@ var ImageEditorController = (_class3 = class {
1690
2726
  if (commitToHistory) {
1691
2727
  this.recordHistory();
1692
2728
  }
1693
- this.state.adjustments = {
2729
+ const next = {
1694
2730
  ...this.state.adjustments,
1695
2731
  ...partial
1696
2732
  };
2733
+ if ("layers" in partial) {
2734
+ const layers = normalizeAdjustmentLayers(partial.layers);
2735
+ if (layers.length) next.layers = layers;
2736
+ else delete next.layers;
2737
+ }
2738
+ this.state.adjustments = next;
1697
2739
  this.notify();
1698
2740
  }
1699
2741
  /** True when the adjustments use an older rendering process than {@link LATEST_PROCESS_VERSION}. */
@@ -1709,11 +2751,130 @@ var ImageEditorController = (_class3 = class {
1709
2751
  this.setAdjustments({ process: LATEST_PROCESS_VERSION });
1710
2752
  return true;
1711
2753
  }
2754
+ /** Resets the base sliders (latest process). Adjustment layers are kept; see `clearAdjustmentLayers`. */
1712
2755
  resetAdjustments() {
1713
2756
  this.recordHistory();
2757
+ const layers = this.state.adjustments.layers;
1714
2758
  this.state.adjustments = { ...DEFAULT_ADJUSTMENTS };
2759
+ if (_optionalChain([layers, 'optionalAccess', _34 => _34.length])) this.state.adjustments.layers = layers;
2760
+ this.notify();
2761
+ }
2762
+ // --- Adjustment layers ---
2763
+ /** The adjustment layers, bottom (first) to top (last). */
2764
+ getAdjustmentLayers() {
2765
+ return _nullishCoalesce(this.state.adjustments.layers, () => ( []));
2766
+ }
2767
+ getAdjustmentLayer(id) {
2768
+ return this.getAdjustmentLayers().find((l) => l.id === id);
2769
+ }
2770
+ setLayers(layers) {
2771
+ const next = { ...this.state.adjustments };
2772
+ if (layers.length) next.layers = layers;
2773
+ else delete next.layers;
2774
+ this.state.adjustments = next;
2775
+ }
2776
+ /**
2777
+ * Adds an adjustment layer on top (or at `index`, 0 = bottom) as one undo step and returns
2778
+ * its id. `kind` alone gives neutral parameters.
2779
+ */
2780
+ addAdjustmentLayer(init, index) {
2781
+ const spec = typeof init === "string" ? { kind: init } : init;
2782
+ const layers = [...this.getAdjustmentLayers()];
2783
+ const layer = createAdjustmentLayer(spec.kind, {
2784
+ id: spec.id && !layers.some((l) => l.id === spec.id) ? spec.id : void 0,
2785
+ name: spec.name,
2786
+ params: spec.params,
2787
+ enabled: spec.enabled,
2788
+ opacity: spec.opacity,
2789
+ blend: spec.blend
2790
+ });
2791
+ this.recordHistory();
2792
+ const at = index === void 0 ? layers.length : Math.max(0, Math.min(layers.length, Math.round(index)));
2793
+ layers.splice(at, 0, layer);
2794
+ this.setLayers(layers);
2795
+ this.notify();
2796
+ return layer.id;
2797
+ }
2798
+ /**
2799
+ * Updates a layer (`params` merged into the current ones, then normalised). Pass
2800
+ * `commitToHistory = false` for the moves of a drag after recording its start once.
2801
+ * Returns false for an unknown id.
2802
+ */
2803
+ updateAdjustmentLayer(id, update, commitToHistory = true) {
2804
+ const layers = this.getAdjustmentLayers();
2805
+ const index = layers.findIndex((l) => l.id === id);
2806
+ if (index === -1) return false;
2807
+ const current = layers[index];
2808
+ const merged = normalizeAdjustmentLayer({
2809
+ ...current,
2810
+ ...update.enabled !== void 0 ? { enabled: update.enabled } : {},
2811
+ ...update.opacity !== void 0 ? { opacity: update.opacity } : {},
2812
+ ...update.blend !== void 0 ? { blend: update.blend } : {},
2813
+ ...update.name !== void 0 ? { name: update.name } : {},
2814
+ params: { ...current.params, ..._nullishCoalesce(update.params, () => ( {})) }
2815
+ });
2816
+ if (!merged) return false;
2817
+ if (commitToHistory) this.recordHistory();
2818
+ const next = [...layers];
2819
+ next[index] = merged;
2820
+ this.setLayers(next);
2821
+ this.notify();
2822
+ return true;
2823
+ }
2824
+ /** Removes a layer as one undo step. Returns false for an unknown id. */
2825
+ removeAdjustmentLayer(id) {
2826
+ const layers = this.getAdjustmentLayers();
2827
+ if (!layers.some((l) => l.id === id)) return false;
2828
+ this.recordHistory();
2829
+ this.setLayers(layers.filter((l) => l.id !== id));
2830
+ this.notify();
2831
+ return true;
2832
+ }
2833
+ /** Moves a layer to `toIndex` (0 = bottom) as one undo step. Returns false if nothing moved. */
2834
+ moveAdjustmentLayer(id, toIndex) {
2835
+ const layers = [...this.getAdjustmentLayers()];
2836
+ const from = layers.findIndex((l) => l.id === id);
2837
+ if (from === -1) return false;
2838
+ const to = Math.max(0, Math.min(layers.length - 1, Math.round(toIndex)));
2839
+ if (to === from) return false;
2840
+ this.recordHistory();
2841
+ const [layer] = layers.splice(from, 1);
2842
+ layers.splice(to, 0, layer);
2843
+ this.setLayers(layers);
2844
+ this.notify();
2845
+ return true;
2846
+ }
2847
+ /** Shows or hides a layer (flips it without `enabled`) as one undo step. */
2848
+ toggleAdjustmentLayer(id, enabled) {
2849
+ const layer = this.getAdjustmentLayer(id);
2850
+ if (!layer) return false;
2851
+ const next = _nullishCoalesce(enabled, () => ( !layer.enabled));
2852
+ if (next === layer.enabled) return false;
2853
+ return this.updateAdjustmentLayer(id, { enabled: next });
2854
+ }
2855
+ /** Removes every adjustment layer as one undo step. */
2856
+ clearAdjustmentLayers() {
2857
+ if (!this.getAdjustmentLayers().length) return;
2858
+ this.recordHistory();
2859
+ this.setLayers([]);
1715
2860
  this.notify();
1716
2861
  }
2862
+ // --- Histogram ---
2863
+ /**
2864
+ * Histogram of the last rendered preview (after adjustments and layers, before annotations),
2865
+ * or `null` before the first render or without canvas pixel access.
2866
+ */
2867
+ getHistogram() {
2868
+ return this.renderer.previewHistogram;
2869
+ }
2870
+ /** Calls `listener` now and whenever a render produces a new preview histogram. */
2871
+ subscribeHistogram(listener) {
2872
+ this.histogramListeners.add(listener);
2873
+ listener(this.renderer.previewHistogram);
2874
+ return () => {
2875
+ this.histogramListeners.delete(listener);
2876
+ };
2877
+ }
1717
2878
  // --- Depth Masking (Gleamforge) ---
1718
2879
  setDepthMask(partial) {
1719
2880
  this.recordHistory();
@@ -1799,7 +2960,7 @@ var ImageEditorController = (_class3 = class {
1799
2960
  if (!source) return;
1800
2961
  try {
1801
2962
  await this.renderer.renderToCanvas(targetCanvas, source, this.state, options);
1802
- } catch (e6) {
2963
+ } catch (e7) {
1803
2964
  }
1804
2965
  }
1805
2966
  async renderExport(options) {
@@ -1919,12 +3080,13 @@ var ImageEditorController = (_class3 = class {
1919
3080
  }
1920
3081
  loadJSON(data) {
1921
3082
  this.recordHistory();
1922
- this.applySerializedState(data);
3083
+ this.applySerializedState(migrateSerializedImageEditorState(data));
1923
3084
  this.notify();
1924
3085
  }
1925
3086
  serializeState() {
1926
3087
  return {
1927
- version: 1,
3088
+ // States without adjustment layers keep schema 1, so they round-trip unchanged.
3089
+ version: _optionalChain([this, 'access', _35 => _35.state, 'access', _36 => _36.adjustments, 'access', _37 => _37.layers, 'optionalAccess', _38 => _38.length]) ? IMAGE_EDITOR_SCHEMA_VERSION : 1,
1928
3090
  crop: this.state.crop ? { ...this.state.crop } : null,
1929
3091
  transform: { ...this.state.transform },
1930
3092
  adjustments: serializeAdjustments(this.state.adjustments),
@@ -1948,6 +3110,8 @@ var ImageEditorController = (_class3 = class {
1948
3110
  }, _class3);
1949
3111
  function serializeAdjustments(adjustments) {
1950
3112
  const out = { ...adjustments };
3113
+ if (_optionalChain([out, 'access', _39 => _39.layers, 'optionalAccess', _40 => _40.length])) out.layers = JSON.parse(JSON.stringify(out.layers));
3114
+ else delete out.layers;
1951
3115
  if (normalizeProcessVersion(out.process) === LEGACY_PROCESS_VERSION) {
1952
3116
  delete out.process;
1953
3117
  if (!out.tint) delete out.tint;
@@ -1974,6 +3138,643 @@ function scaleAnnotation(ann, sx, sy) {
1974
3138
  }
1975
3139
  }
1976
3140
 
3141
+ // src/image-editor/document.ts
3142
+ var IMAGE_EDITOR_STATE_VERSION = 1;
3143
+ var imageEditorStateMigrations = {};
3144
+ var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3145
+ var isNumber = (value) => typeof value === "number" && Number.isFinite(value);
3146
+ function expectObject(value, what) {
3147
+ if (!isObject(value)) throw new Error(`${what} must be an object`);
3148
+ return value;
3149
+ }
3150
+ function checkFields(value, types, what) {
3151
+ for (const [field, ok] of Object.entries(types)) {
3152
+ if (field in value && value[field] !== void 0 && !ok(value[field])) {
3153
+ throw new Error(`${what}.${field} has an invalid value`);
3154
+ }
3155
+ }
3156
+ }
3157
+ var isBoolean = (v) => typeof v === "boolean";
3158
+ function parseCrop(value) {
3159
+ if (value === null) return null;
3160
+ const crop = expectObject(value, "crop");
3161
+ for (const field of ["x", "y", "width", "height"]) {
3162
+ if (!isNumber(crop[field])) throw new Error(`crop.${field} must be a number`);
3163
+ }
3164
+ return { ...crop };
3165
+ }
3166
+ function parseTransform(value) {
3167
+ const transform = expectObject(value, "transform");
3168
+ checkFields(transform, { rotate: isNumber, flipH: isBoolean, flipV: isBoolean }, "transform");
3169
+ return { ...DEFAULT_TRANSFORM, ...transform };
3170
+ }
3171
+ var ADJUSTMENT_NUMBERS = [
3172
+ "brightness",
3173
+ "contrast",
3174
+ "saturation",
3175
+ "exposure",
3176
+ "temperature",
3177
+ "tint",
3178
+ "blur",
3179
+ "opacity"
3180
+ ];
3181
+ function parseAdjustments(value) {
3182
+ const adjustments = expectObject(value, "adjustments");
3183
+ checkFields(
3184
+ adjustments,
3185
+ {
3186
+ ...Object.fromEntries(ADJUSTMENT_NUMBERS.map((k) => [k, isNumber])),
3187
+ process: (v) => v === 1 || v === 2
3188
+ },
3189
+ "adjustments"
3190
+ );
3191
+ return { ...adjustments };
3192
+ }
3193
+ function parseDepthMask(value) {
3194
+ const mask = expectObject(value, "depthMask");
3195
+ checkFields(
3196
+ mask,
3197
+ {
3198
+ enabled: isBoolean,
3199
+ maskSource: (v) => v === null || typeof v === "string",
3200
+ mode: (v) => v === "threshold" || v === "brightness",
3201
+ depthRange: (v) => Array.isArray(v) && v.length === 2 && v.every(isNumber),
3202
+ softness: isNumber,
3203
+ invert: isBoolean
3204
+ },
3205
+ "depthMask"
3206
+ );
3207
+ const merged = { ...DEFAULT_DEPTH_MASK, ...mask };
3208
+ return { ...merged, depthRange: [...merged.depthRange] };
3209
+ }
3210
+ var ANNOTATION_TYPES = /* @__PURE__ */ new Set(["pen", "rect", "circle", "arrow", "line", "text", "image"]);
3211
+ var isAnnotation = (value) => isObject(value) && typeof value.id === "string" && typeof value.type === "string" && ANNOTATION_TYPES.has(value.type) && isNumber(value.x) && isNumber(value.y);
3212
+ function loadImageEditorDocument(input) {
3213
+ let dropped = 0;
3214
+ const result = _chunkD6FH2YAXcjs.loadDocument.call(void 0,
3215
+ input,
3216
+ {
3217
+ crop: { parse: parseCrop, defaults: null },
3218
+ transform: { parse: parseTransform, defaults: () => ({ ...DEFAULT_TRANSFORM }) },
3219
+ adjustments: {
3220
+ parse: parseAdjustments,
3221
+ // Same as what `loadJSON` derives from missing adjustments.
3222
+ defaults: () => normalizeStoredAdjustments(void 0)
3223
+ },
3224
+ depthMask: {
3225
+ parse: parseDepthMask,
3226
+ defaults: () => ({ ...DEFAULT_DEPTH_MASK, depthRange: [...DEFAULT_DEPTH_MASK.depthRange] })
3227
+ },
3228
+ annotations: {
3229
+ parse: (value) => {
3230
+ if (!Array.isArray(value)) throw new Error("annotations must be an array");
3231
+ const kept = value.filter(isAnnotation);
3232
+ dropped = value.length - kept.length;
3233
+ return JSON.parse(JSON.stringify(kept));
3234
+ },
3235
+ defaults: () => []
3236
+ }
3237
+ },
3238
+ {
3239
+ currentVersion: IMAGE_EDITOR_STATE_VERSION,
3240
+ steps: imageEditorStateMigrations
3241
+ }
3242
+ );
3243
+ if (dropped > 0) {
3244
+ result.report.problems.push({
3245
+ section: "annotations",
3246
+ message: `${dropped} invalid annotation${dropped === 1 ? "" : "s"} dropped`
3247
+ });
3248
+ }
3249
+ const state = { version: 1, ...result.value };
3250
+ return { state, report: result.report, unknown: result.unknown };
3251
+ }
3252
+
3253
+ // src/image-editor/controls.ts
3254
+ var plain = { kind: "plain" };
3255
+ var centered = { kind: "centered" };
3256
+ var IMAGE_ADJUSTMENT_CONTROLS = [
3257
+ { id: "brightness", label: "Brightness", section: "light", min: -100, max: 100, step: 1, default: 0, decimals: 0, track: centered },
3258
+ { id: "contrast", label: "Contrast", section: "light", min: -100, max: 100, step: 1, default: 0, decimals: 0, track: centered },
3259
+ {
3260
+ id: "saturation",
3261
+ label: "Saturation",
3262
+ section: "color",
3263
+ min: -100,
3264
+ max: 100,
3265
+ step: 1,
3266
+ default: 0,
3267
+ decimals: 0,
3268
+ track: { kind: "gradient", from: "#7a7a7a", to: "#e04a3a" }
3269
+ },
3270
+ { id: "exposure", label: "Exposure", section: "light", min: -100, max: 100, step: 1, default: 0, decimals: 0, track: centered },
3271
+ { id: "temperature", label: "Warmth / Temperature", section: "color", min: -100, max: 100, step: 1, default: 0, decimals: 0, track: { kind: "temp" } },
3272
+ { id: "tint", label: "Tint", section: "color", min: -100, max: 100, step: 1, default: 0, decimals: 0, track: { kind: "tint" }, requiresProcess: 2 },
3273
+ { id: "blur", label: "Blur", section: "effects", min: 0, max: 30, step: 1, default: 0, decimals: 0, track: plain, unit: "px" },
3274
+ { id: "opacity", label: "Opacity", section: "effects", min: 0, max: 1, step: 0.05, default: 1, decimals: 0, track: plain, displayScale: 100, unit: "%" }
3275
+ ];
3276
+ var tone = (prefix, label) => [
3277
+ ["0", "Cyan \u2013 Red", "#00b7c7", "#e0343a"],
3278
+ ["1", "Magenta \u2013 Green", "#d23ad2", "#2fb34a"],
3279
+ ["2", "Yellow \u2013 Blue", "#e0c63a", "#3a5be0"]
3280
+ ].map(([i, l, from, to]) => ({
3281
+ id: `${prefix}.${i}`,
3282
+ label: `${label} ${l}`,
3283
+ section: "layer",
3284
+ min: -100,
3285
+ max: 100,
3286
+ step: 1,
3287
+ default: 0,
3288
+ decimals: 0,
3289
+ track: { kind: "gradient", from, to }
3290
+ }));
3291
+ var levelsRecord = (ch, label) => [
3292
+ { id: `${ch}.inBlack`, label: `${label} input black`, section: "layer", min: 0, max: 253, step: 1, default: 0, decimals: 0, track: plain },
3293
+ { id: `${ch}.gamma`, label: `${label} gamma`, section: "layer", min: 0.1, max: 9.99, step: 0.01, default: 1, decimals: 2, track: plain },
3294
+ { id: `${ch}.inWhite`, label: `${label} input white`, section: "layer", min: 2, max: 255, step: 1, default: 255, decimals: 0, track: plain },
3295
+ { id: `${ch}.outBlack`, label: `${label} output black`, section: "layer", min: 0, max: 255, step: 1, default: 0, decimals: 0, track: plain },
3296
+ { id: `${ch}.outWhite`, label: `${label} output white`, section: "layer", min: 0, max: 255, step: 1, default: 255, decimals: 0, track: plain }
3297
+ ];
3298
+ var RANGE_NAMES = ["Reds", "Yellows", "Greens", "Cyans", "Blues", "Magentas"];
3299
+ var ADJUSTMENT_LAYER_CONTROLS = {
3300
+ curves: [],
3301
+ levels: [
3302
+ ...levelsRecord("master", "RGB"),
3303
+ ...levelsRecord("red", "Red"),
3304
+ ...levelsRecord("green", "Green"),
3305
+ ...levelsRecord("blue", "Blue")
3306
+ ],
3307
+ hueSaturation: [
3308
+ { id: "hue", label: "Hue", section: "layer", min: -180, max: 180, step: 1, default: 0, decimals: 0, track: { kind: "rainbow" }, unit: "\xB0" },
3309
+ { id: "saturation", label: "Saturation", section: "layer", min: -100, max: 100, step: 1, default: 0, decimals: 0, track: { kind: "gradient", from: "#7a7a7a", to: "#e04a3a" } },
3310
+ { id: "lightness", label: "Lightness", section: "layer", min: -100, max: 100, step: 1, default: 0, decimals: 0, track: { kind: "gradient", from: "#000000", to: "#ffffff" } },
3311
+ ...RANGE_NAMES.flatMap((name, i) => [
3312
+ { id: `ranges.${i}.hue`, label: `${name} hue`, section: "layer", min: -180, max: 180, step: 1, default: 0, decimals: 0, track: { kind: "rainbow" }, unit: "\xB0" },
3313
+ { id: `ranges.${i}.saturation`, label: `${name} saturation`, section: "layer", min: -100, max: 100, step: 1, default: 0, decimals: 0, track: centered },
3314
+ { id: `ranges.${i}.lightness`, label: `${name} lightness`, section: "layer", min: -100, max: 100, step: 1, default: 0, decimals: 0, track: centered }
3315
+ ])
3316
+ ],
3317
+ vibrance: [
3318
+ { id: "vibrance", label: "Vibrance", section: "layer", min: -100, max: 100, step: 1, default: 0, decimals: 0, track: { kind: "gradient", from: "#7a7a7a", to: "#d8406a" } },
3319
+ { id: "saturation", label: "Saturation", section: "layer", min: -100, max: 100, step: 1, default: 0, decimals: 0, track: { kind: "gradient", from: "#7a7a7a", to: "#e04a3a" } }
3320
+ ],
3321
+ colorBalance: [...tone("shadows", "Shadows"), ...tone("midtones", "Midtones"), ...tone("highlights", "Highlights")],
3322
+ blackWhite: [
3323
+ ["reds", "Reds", 40, "#e0343a"],
3324
+ ["yellows", "Yellows", 60, "#e0c63a"],
3325
+ ["greens", "Greens", 40, "#2fb34a"],
3326
+ ["cyans", "Cyans", 60, "#00b7c7"],
3327
+ ["blues", "Blues", 20, "#3a5be0"],
3328
+ ["magentas", "Magentas", 80, "#d23ad2"]
3329
+ ].map(([id, label, def, to]) => ({
3330
+ id,
3331
+ label,
3332
+ section: "layer",
3333
+ min: -200,
3334
+ max: 300,
3335
+ step: 1,
3336
+ default: def,
3337
+ decimals: 0,
3338
+ track: { kind: "gradient", from: "#000000", to },
3339
+ unit: "%"
3340
+ })),
3341
+ invert: [],
3342
+ posterize: [{ id: "levels", label: "Levels", section: "layer", min: 2, max: 255, step: 1, default: 4, decimals: 0, track: plain }],
3343
+ 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" } }],
3344
+ photoFilter: [{ id: "density", label: "Density", section: "layer", min: 0, max: 100, step: 1, default: 25, decimals: 0, track: plain, unit: "%" }]
3345
+ };
3346
+ var ADJUSTMENT_LAYER_OPACITY_CONTROL = {
3347
+ id: "opacity",
3348
+ label: "Opacity",
3349
+ section: "layer",
3350
+ min: 0,
3351
+ max: 1,
3352
+ step: 0.01,
3353
+ default: 1,
3354
+ decimals: 0,
3355
+ track: plain,
3356
+ displayScale: 100,
3357
+ unit: "%"
3358
+ };
3359
+ var BASE_BY_ID = new Map(IMAGE_ADJUSTMENT_CONTROLS.map((c) => [c.id, c]));
3360
+ function findAdjustmentControl(id) {
3361
+ return BASE_BY_ID.get(id);
3362
+ }
3363
+ function findLayerControl(kind, id) {
3364
+ if (id === "opacity") return ADJUSTMENT_LAYER_OPACITY_CONTROL;
3365
+ return _optionalChain([ADJUSTMENT_LAYER_CONTROLS, 'access', _41 => _41[kind], 'optionalAccess', _42 => _42.find, 'call', _43 => _43((c) => c.id === id)]);
3366
+ }
3367
+ function clampControlValue(spec, v) {
3368
+ if (typeof v !== "number" || !Number.isFinite(v)) return spec.default;
3369
+ const clamped = Math.min(spec.max, Math.max(spec.min, v));
3370
+ const steps = Math.round((clamped - spec.min) / spec.step);
3371
+ const snapped = spec.min + steps * spec.step;
3372
+ return Math.min(spec.max, Number(snapped.toFixed(6)));
3373
+ }
3374
+ function formatControlValue(spec, v) {
3375
+ const shown = (Number.isFinite(v) ? v : spec.default) * (_nullishCoalesce(spec.displayScale, () => ( 1)));
3376
+ const s = shown.toFixed(spec.decimals);
3377
+ return `${s}${_nullishCoalesce(spec.unit, () => ( ""))}`;
3378
+ }
3379
+ function controlPosition(spec, v) {
3380
+ const p = (clampControlValue(spec, v) - spec.min) / (spec.max - spec.min || 1);
3381
+ return Math.min(1, Math.max(0, p));
3382
+ }
3383
+ function controlTrackBackground(spec) {
3384
+ const t = spec.track;
3385
+ switch (t.kind) {
3386
+ case "temp":
3387
+ return "linear-gradient(to right, #3a7bd5, #d8d8d8, #f2c94c)";
3388
+ case "tint":
3389
+ return "linear-gradient(to right, #3fbf4f, #d8d8d8, #d23ad2)";
3390
+ case "gradient":
3391
+ return `linear-gradient(to right, ${t.from}, ${t.to})`;
3392
+ case "rainbow":
3393
+ return "linear-gradient(to right, #00ffff, #0000ff, #ff00ff, #ff0000, #ffff00, #00ff00, #00ffff)";
3394
+ default:
3395
+ return void 0;
3396
+ }
3397
+ }
3398
+ function getControlPath(obj, path) {
3399
+ let cur = obj;
3400
+ for (const part of path.split(".")) {
3401
+ if (cur === null || cur === void 0) return void 0;
3402
+ cur = cur[part];
3403
+ }
3404
+ return cur;
3405
+ }
3406
+ function setControlPath(obj, path, value) {
3407
+ const parts = path.split(".");
3408
+ const root = Array.isArray(obj) ? [...obj] : { ...obj };
3409
+ let cur = root;
3410
+ for (let i = 0; i < parts.length - 1; i++) {
3411
+ const next = cur[parts[i]];
3412
+ cur[parts[i]] = Array.isArray(next) ? [...next] : next && typeof next === "object" ? { ...next } : {};
3413
+ cur = cur[parts[i]];
3414
+ }
3415
+ cur[parts[parts.length - 1]] = value;
3416
+ return root;
3417
+ }
3418
+ function readAdjustmentControls(adjustments) {
3419
+ const out = {};
3420
+ for (const c of IMAGE_ADJUSTMENT_CONTROLS) {
3421
+ const v = adjustments[c.id];
3422
+ out[c.id] = typeof v === "number" && Number.isFinite(v) ? v : c.default;
3423
+ }
3424
+ return out;
3425
+ }
3426
+
3427
+ // src/image-editor/tools.ts
3428
+ var LAYER_PATH = /^layers\.([^.]+)\.(.+)$/;
3429
+ function adjustmentsSummary(adjustments) {
3430
+ return {
3431
+ process: normalizeProcessVersion(adjustments.process),
3432
+ values: readAdjustmentControls(adjustments),
3433
+ layers: (_nullishCoalesce(adjustments.layers, () => ( []))).map((l) => JSON.parse(JSON.stringify(l)))
3434
+ };
3435
+ }
3436
+ function createImageEditorToolProvider(controller, options = {}) {
3437
+ const getAdjustments = {
3438
+ name: "get_adjustments",
3439
+ title: "Read image adjustments",
3440
+ description: "Returns the image editor adjustment values by control id, the rendering process version and the adjustment layers (bottom to top).",
3441
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
3442
+ annotations: {
3443
+ title: "Read image adjustments",
3444
+ readOnlyHint: true,
3445
+ destructiveHint: false,
3446
+ idempotentHint: true,
3447
+ openWorldHint: false
3448
+ },
3449
+ handler: () => adjustmentsSummary(controller.getState().adjustments)
3450
+ };
3451
+ const listControls = {
3452
+ name: "list_controls",
3453
+ title: "List adjustment controls",
3454
+ description: "Lists every adjustment control (id, label, section, min, max, step, default), the parameters of each adjustment-layer kind, and the blend modes.",
3455
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
3456
+ annotations: {
3457
+ title: "List adjustment controls",
3458
+ readOnlyHint: true,
3459
+ destructiveHint: false,
3460
+ idempotentHint: true,
3461
+ openWorldHint: false
3462
+ },
3463
+ handler: () => ({
3464
+ controls: IMAGE_ADJUSTMENT_CONTROLS.map(({ id, label, section, min, max, step, default: def, requiresProcess }) => ({
3465
+ id,
3466
+ label,
3467
+ section,
3468
+ min,
3469
+ max,
3470
+ step,
3471
+ default: def,
3472
+ ...requiresProcess ? { requiresProcess } : {}
3473
+ })),
3474
+ layerKinds: ADJUSTMENT_KINDS.map(({ kind, label }) => ({
3475
+ kind,
3476
+ label,
3477
+ controls: [...ADJUSTMENT_LAYER_CONTROLS[kind], ADJUSTMENT_LAYER_OPACITY_CONTROL].map(({ id, label: l, min, max, step, default: def }) => ({
3478
+ id,
3479
+ label: l,
3480
+ min,
3481
+ max,
3482
+ step,
3483
+ default: def
3484
+ }))
3485
+ })),
3486
+ blendModes: BLEND_MODES.map((m) => m.id),
3487
+ layerPathFormat: "layers.<layerId>.<controlId>"
3488
+ })
3489
+ };
3490
+ const setAdjustments = {
3491
+ name: "set_adjustments",
3492
+ title: "Set image adjustments",
3493
+ description: '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.',
3494
+ inputSchema: {
3495
+ type: "object",
3496
+ properties: {
3497
+ values: {
3498
+ type: "object",
3499
+ description: "Map of control id to number. See list_controls for ids and ranges.",
3500
+ additionalProperties: { type: "number" }
3501
+ },
3502
+ label: { type: "string", description: "Short description of the change (echoed back)." }
3503
+ },
3504
+ required: ["values"],
3505
+ additionalProperties: false
3506
+ },
3507
+ annotations: {
3508
+ title: "Set image adjustments",
3509
+ readOnlyHint: false,
3510
+ destructiveHint: false,
3511
+ idempotentHint: true,
3512
+ openWorldHint: false
3513
+ },
3514
+ handler: ({ values, label }) => {
3515
+ const entries = Object.entries(_nullishCoalesce(values, () => ( {})));
3516
+ if (!entries.length) throw new Error('No values given. Pass {"values": {"<control id>": number}}.');
3517
+ const state = controller.getState();
3518
+ const base = {};
3519
+ const layers = (_nullishCoalesce(state.adjustments.layers, () => ( []))).map((l) => ({ ...l }));
3520
+ const applied = {};
3521
+ const clamped = [];
3522
+ const unknown = [];
3523
+ for (const [id, raw] of entries) {
3524
+ const m = LAYER_PATH.exec(id);
3525
+ if (m) {
3526
+ const layer = layers.find((l) => l.id === m[1]);
3527
+ const spec2 = layer ? findLayerControl(layer.kind, m[2]) : void 0;
3528
+ if (!layer || !spec2) {
3529
+ unknown.push(id);
3530
+ continue;
3531
+ }
3532
+ const v2 = clampControlValue(spec2, raw);
3533
+ if (v2 !== raw) clamped.push(id);
3534
+ if (spec2.id === "opacity") layer.opacity = v2;
3535
+ else layer.params = setControlPath(layer.params, spec2.id, v2);
3536
+ applied[id] = v2;
3537
+ continue;
3538
+ }
3539
+ const spec = findAdjustmentControl(id);
3540
+ if (!spec) {
3541
+ unknown.push(id);
3542
+ continue;
3543
+ }
3544
+ const v = clampControlValue(spec, raw);
3545
+ if (v !== raw) clamped.push(id);
3546
+ base[id] = v;
3547
+ applied[id] = v;
3548
+ }
3549
+ if (unknown.length) {
3550
+ const accepted = IMAGE_ADJUSTMENT_CONTROLS.map((c) => c.id).join(", ");
3551
+ throw new Error(
3552
+ `Unknown control id(s): ${unknown.join(", ")}. Accepted: ${accepted}, or layers.<layerId>.<controlId> for an existing layer.`
3553
+ );
3554
+ }
3555
+ const changedLayers = Object.keys(applied).some((id) => id.startsWith("layers."));
3556
+ controller.setAdjustments(changedLayers ? { ...base, layers } : base);
3557
+ const result = { applied, clamped };
3558
+ if (typeof label === "string" && label) result.label = label;
3559
+ const notes = [];
3560
+ if ("tint" in applied && normalizeProcessVersion(controller.getState().adjustments.process) < 2) {
3561
+ notes.push("Tint has no effect on process 1 edits.");
3562
+ }
3563
+ if (notes.length) result.notes = notes;
3564
+ return result;
3565
+ }
3566
+ };
3567
+ const addLayer = {
3568
+ name: "add_adjustment_layer",
3569
+ title: "Add an adjustment layer",
3570
+ description: "Adds a non-destructive adjustment layer on top of the stack (one undoable step) and returns it. Kinds: " + ADJUSTMENT_KINDS.map((k) => k.kind).join(", ") + ". Omitted params are neutral.",
3571
+ inputSchema: {
3572
+ type: "object",
3573
+ properties: {
3574
+ kind: { type: "string", enum: ADJUSTMENT_KINDS.map((k) => k.kind) },
3575
+ params: { type: "object", description: "Initial parameters (see list_controls); merged with neutral defaults." },
3576
+ opacity: { type: "number", minimum: 0, maximum: 1 },
3577
+ blend: { type: "string", enum: BLEND_MODES.map((m) => m.id) },
3578
+ name: { type: "string" }
3579
+ },
3580
+ required: ["kind"],
3581
+ additionalProperties: false
3582
+ },
3583
+ annotations: {
3584
+ title: "Add an adjustment layer",
3585
+ readOnlyHint: false,
3586
+ destructiveHint: false,
3587
+ idempotentHint: false,
3588
+ openWorldHint: false
3589
+ },
3590
+ handler: ({ kind, params, opacity, blend, name }) => {
3591
+ if (!isAdjustmentKind(kind)) throw new Error(`Unknown adjustment kind "${kind}".`);
3592
+ if (blend !== void 0 && !isBlendMode(blend)) throw new Error(`Unknown blend mode "${blend}".`);
3593
+ const id = controller.addAdjustmentLayer({ kind, params, opacity, blend, name });
3594
+ const layer = controller.getAdjustmentLayer(id);
3595
+ return { layer: JSON.parse(JSON.stringify(layer)), index: controller.getAdjustmentLayers().length - 1 };
3596
+ }
3597
+ };
3598
+ const renderPreview = {
3599
+ name: "render_preview",
3600
+ title: "Render a preview",
3601
+ description: "Renders the edited image (crop, adjustments, layers, annotations) as a PNG whose longest side is at most maxSide pixels.",
3602
+ inputSchema: {
3603
+ type: "object",
3604
+ properties: {
3605
+ maxSide: { type: "integer", minimum: 16, maximum: 2048, description: "Longest side in pixels (default 512)." }
3606
+ },
3607
+ additionalProperties: false
3608
+ },
3609
+ annotations: {
3610
+ title: "Render a preview",
3611
+ readOnlyHint: true,
3612
+ destructiveHint: false,
3613
+ idempotentHint: true,
3614
+ openWorldHint: false
3615
+ },
3616
+ handler: async ({ maxSide }) => {
3617
+ const side = Math.max(16, Math.min(2048, Math.round(typeof maxSide === "number" ? maxSide : 512)));
3618
+ const state = controller.getState();
3619
+ const crop = _nullishCoalesce(state.crop, () => ( { x: 0, y: 0, ...state.imageDimensions }));
3620
+ const quarter = Math.abs(Math.round(state.transform.rotate % 180 / 90)) % 2 === 1;
3621
+ const w = quarter ? crop.height : crop.width;
3622
+ const h = quarter ? crop.width : crop.height;
3623
+ const k = Math.min(1, side / Math.max(w, h, 1));
3624
+ const dataUrl = await controller.toDataURL({
3625
+ format: "image/png",
3626
+ width: Math.max(1, Math.round(w * k)),
3627
+ height: Math.max(1, Math.round(h * k))
3628
+ });
3629
+ const comma = dataUrl.indexOf(",");
3630
+ if (!dataUrl.startsWith("data:image/png") || comma < 0 || comma === dataUrl.length - 1) {
3631
+ throw new Error("Rendering is not available in this environment.");
3632
+ }
3633
+ return {
3634
+ content: [
3635
+ { type: "image", data: dataUrl.slice(comma + 1), mimeType: "image/png" },
3636
+ { type: "text", text: `Preview ${Math.round(w * k)}\xD7${Math.round(h * k)}` }
3637
+ ]
3638
+ };
3639
+ }
3640
+ };
3641
+ return {
3642
+ id: _nullishCoalesce(options.id, () => ( "image-editor")),
3643
+ namespace: options.namespace,
3644
+ tools: [getAdjustments, listControls, setAdjustments, addLayer, renderPreview]
3645
+ };
3646
+ }
3647
+
3648
+ // src/image-editor/curve-editing.ts
3649
+ var clampLevel = (v) => Math.min(255, Math.max(0, Math.round(v)));
3650
+ function curvePointToBox(p, width, height) {
3651
+ return { x: p.input / 255 * width, y: height - p.output / 255 * height };
3652
+ }
3653
+ function boxToCurvePoint(x, y, width, height) {
3654
+ return { input: clampLevel(x / (width || 1) * 255), output: clampLevel((height - y) / (height || 1) * 255) };
3655
+ }
3656
+ function curveSvgPath(points, width, height, samples = 64) {
3657
+ const lut = curveLutFromLevels(points);
3658
+ const n = lut.length - 1;
3659
+ let d = "";
3660
+ for (let i = 0; i <= samples; i++) {
3661
+ const t = i / samples;
3662
+ const y = lut[Math.round(t * n)];
3663
+ d += `${i === 0 ? "M" : " L"}${round22(t * width)} ${round22(height - y * height)}`;
3664
+ }
3665
+ return d;
3666
+ }
3667
+ function hitTestCurvePoint(points, x, y, width, height, radius = 8) {
3668
+ let best = -1;
3669
+ let bestD = radius * radius;
3670
+ points.forEach((p, i) => {
3671
+ const b = curvePointToBox(p, width, height);
3672
+ const d = (b.x - x) ** 2 + (b.y - y) ** 2;
3673
+ if (d <= bestD) {
3674
+ best = i;
3675
+ bestD = d;
3676
+ }
3677
+ });
3678
+ return best;
3679
+ }
3680
+ function insertCurvePoint(points, p) {
3681
+ const point = { input: clampLevel(p.input), output: clampLevel(p.output) };
3682
+ const out = points.filter((q) => q.input !== point.input).map((q) => ({ ...q }));
3683
+ let index = out.findIndex((q) => q.input > point.input);
3684
+ if (index === -1) index = out.length;
3685
+ out.splice(index, 0, point);
3686
+ return { points: out, index };
3687
+ }
3688
+ function moveCurvePoint(points, index, p) {
3689
+ const out = points.map((q) => ({ ...q }));
3690
+ if (index < 0 || index >= out.length) return out;
3691
+ const lo = index > 0 ? out[index - 1].input + 1 : 0;
3692
+ const hi = index < out.length - 1 ? out[index + 1].input - 1 : 255;
3693
+ out[index] = { input: Math.min(hi, Math.max(lo, clampLevel(p.input))), output: clampLevel(p.output) };
3694
+ return out;
3695
+ }
3696
+ function removeCurvePoint(points, index) {
3697
+ if (points.length <= 2 || index < 0 || index >= points.length) return points.map((q) => ({ ...q }));
3698
+ return points.filter((_, i) => i !== index).map((q) => ({ ...q }));
3699
+ }
3700
+ var round22 = (v) => Math.round(v * 100) / 100;
3701
+
3702
+
3703
+
3704
+
3705
+
3706
+
3707
+
3708
+
3709
+
3710
+
3711
+
3712
+
3713
+
3714
+
3715
+
3716
+
3717
+
3718
+
3719
+
3720
+
3721
+
3722
+
3723
+
3724
+
3725
+
3726
+
3727
+
3728
+
3729
+
3730
+
3731
+
3732
+
3733
+
3734
+
3735
+
3736
+
3737
+
3738
+
3739
+
3740
+
3741
+
3742
+
3743
+
3744
+
3745
+
3746
+
3747
+
3748
+
3749
+
3750
+
3751
+
3752
+
3753
+
3754
+
3755
+
3756
+
3757
+
3758
+
3759
+
3760
+
3761
+
3762
+
3763
+
3764
+
3765
+
3766
+
3767
+
3768
+
3769
+
3770
+
3771
+
3772
+
3773
+
3774
+
3775
+
3776
+
3777
+
1977
3778
 
1978
3779
 
1979
3780
 
@@ -2035,5 +3836,5 @@ function scaleAnnotation(ann, sx, sy) {
2035
3836
 
2036
3837
 
2037
3838
 
2038
- exports.AS_SHOT_KELVIN = AS_SHOT_KELVIN; exports.D65 = D65; exports.DEFAULT_ADJUSTMENTS = DEFAULT_ADJUSTMENTS; exports.DEFAULT_DEPTH_MASK = DEFAULT_DEPTH_MASK; exports.DEFAULT_TRANSFORM = DEFAULT_TRANSFORM; exports.IDENTITY_MATRIX3 = IDENTITY_MATRIX3; exports.ImageEditorController = ImageEditorController; exports.ImageEditorRenderer = ImageEditorRenderer; exports.LATEST_PROCESS_VERSION = LATEST_PROCESS_VERSION; exports.LEGACY_PROCESS_VERSION = LEGACY_PROCESS_VERSION; exports.LUMA_709 = LUMA_709; exports.MAX_KELVIN = MAX_KELVIN; exports.MIN_KELVIN = MIN_KELVIN; exports.MIRED_PER_TEMPERATURE_UNIT = MIRED_PER_TEMPERATURE_UNIT; exports.TINT_SCALE = TINT_SCALE; exports.TONE_LUT_SIZE = TONE_LUT_SIZE; exports.applyAdjustments = applyAdjustments; exports.applyDepthMask = applyDepthMask; exports.applyImageProjectionToContext = applyImageProjectionToContext; exports.applyLegacyAdjustments = applyLegacyAdjustments; exports.applyMatrix3 = applyMatrix3; exports.applyProcess2Adjustments = applyProcess2Adjustments; exports.bradfordAdaptation = bradfordAdaptation; exports.buildToneLut = buildToneLut; exports.calculateCropDrag = calculateCropDrag; exports.canvasToImagePoint = canvasToImagePoint; exports.clampCropToImage = clampCropToImage; exports.getAnnotationBounds = getAnnotationBounds; exports.getCropCursor = getCropCursor; exports.getRotatedSize = getRotatedSize; exports.getViewportMetrics = getViewportMetrics; exports.getViewportProjection = getViewportProjection; exports.globalImageCache = globalImageCache; exports.hitTestAnnotation = hitTestAnnotation; exports.hitTestCrop = hitTestCrop; exports.imageToCanvasPoint = imageToCanvasPoint; exports.invertMatrix3 = invertMatrix3; exports.isQuarterTurnSwapped = isQuarterTurnSwapped; exports.kelvinToTemperature = kelvinToTemperature; exports.linearSrgbToXy = linearSrgbToXy; exports.linearToSrgb = linearToSrgb; exports.mapCropHandle = mapCropHandle; exports.mulMatrix3 = mulMatrix3; exports.normalizeAngle = normalizeAngle; exports.normalizeProcessVersion = normalizeProcessVersion; exports.normalizeStoredAdjustments = normalizeStoredAdjustments; exports.projectImagePoint = projectImagePoint; exports.projectImageRect = projectImageRect; exports.removeAlpha = removeAlpha; exports.sampleToneLut = sampleToneLut; exports.srgbToLinear = srgbToLinear; exports.tempTintToXy = tempTintToXy; exports.temperatureToKelvin = temperatureToKelvin; exports.toneCurve = toneCurve; exports.unprojectCanvasPoint = unprojectCanvasPoint; exports.uvToXy = uvToXy; exports.wbMatrix = wbMatrix; exports.whiteBalanceFromSample = whiteBalanceFromSample; exports.whiteBalanceMatrix = whiteBalanceMatrix; exports.xyToTempTint = xyToTempTint; exports.xyToUv = xyToUv;
3839
+ exports.ADJUSTMENT_KINDS = ADJUSTMENT_KINDS; exports.ADJUSTMENT_LAYER_CONTROLS = ADJUSTMENT_LAYER_CONTROLS; exports.ADJUSTMENT_LAYER_OPACITY_CONTROL = ADJUSTMENT_LAYER_OPACITY_CONTROL; exports.ADJUSTMENT_LUT_SIZE = ADJUSTMENT_LUT_SIZE; exports.AS_SHOT_KELVIN = AS_SHOT_KELVIN; exports.BLEND_MODES = BLEND_MODES; exports.D65 = D65; exports.DEFAULT_ADJUSTMENTS = DEFAULT_ADJUSTMENTS; exports.DEFAULT_DEPTH_MASK = DEFAULT_DEPTH_MASK; exports.DEFAULT_TRANSFORM = DEFAULT_TRANSFORM; exports.HISTOGRAM_BINS = HISTOGRAM_BINS; exports.HUE_RANGE_LABELS = HUE_RANGE_LABELS; exports.IDENTITY_MATRIX3 = IDENTITY_MATRIX3; exports.IMAGE_ADJUSTMENT_CONTROLS = IMAGE_ADJUSTMENT_CONTROLS; exports.IMAGE_EDITOR_SCHEMA_VERSION = IMAGE_EDITOR_SCHEMA_VERSION; exports.IMAGE_EDITOR_STATE_VERSION = IMAGE_EDITOR_STATE_VERSION; exports.ImageEditorController = ImageEditorController; exports.ImageEditorRenderer = ImageEditorRenderer; exports.LATEST_PROCESS_VERSION = LATEST_PROCESS_VERSION; exports.LEGACY_PROCESS_VERSION = LEGACY_PROCESS_VERSION; exports.LUMA_709 = LUMA_709; exports.MAX_KELVIN = MAX_KELVIN; exports.MIN_KELVIN = MIN_KELVIN; exports.MIRED_PER_TEMPERATURE_UNIT = MIRED_PER_TEMPERATURE_UNIT; exports.TINT_SCALE = TINT_SCALE; exports.TONE_LUT_SIZE = TONE_LUT_SIZE; exports.VIBRANCE_GREY = VIBRANCE_GREY; exports.activeAdjustmentLayers = activeAdjustmentLayers; exports.adjustmentKindLabel = adjustmentKindLabel; exports.adjustmentLayersKey = adjustmentLayersKey; exports.applyAdjustmentLayers = applyAdjustmentLayers; exports.applyAdjustments = applyAdjustments; exports.applyDepthMask = applyDepthMask; exports.applyImageProjectionToContext = applyImageProjectionToContext; exports.applyLegacyAdjustments = applyLegacyAdjustments; exports.applyMatrix3 = applyMatrix3; exports.applyProcess2Adjustments = applyProcess2Adjustments; exports.blackWhiteGray = blackWhiteGray; exports.blendChannel = blendChannel; exports.blendImageData = blendImageData; exports.blendLum = blendLum; exports.blendPixel = blendPixel; exports.blendRgb = blendRgb; exports.boxToCurvePoint = boxToCurvePoint; exports.bradfordAdaptation = bradfordAdaptation; exports.buildToneLut = buildToneLut; exports.calculateCropDrag = calculateCropDrag; exports.canvasCompositeOperation = canvasCompositeOperation; exports.canvasToImagePoint = canvasToImagePoint; exports.clampControlValue = clampControlValue; exports.clampCropToImage = clampCropToImage; exports.colorBalanceChannel = colorBalanceChannel; exports.compileAdjustmentLayer = compileAdjustmentLayer; exports.computeHistogram = computeHistogram; exports.controlPosition = controlPosition; exports.controlTrackBackground = controlTrackBackground; exports.createAdjustmentLayer = createAdjustmentLayer; exports.createImageEditorToolProvider = createImageEditorToolProvider; exports.curveLut = curveLut; exports.curveLutFromLevels = curveLutFromLevels; exports.curvePointToBox = curvePointToBox; exports.curveSvgPath = curveSvgPath; exports.defaultAdjustmentParams = defaultAdjustmentParams; exports.evaluateAdjustmentLayers = evaluateAdjustmentLayers; exports.findAdjustmentControl = findAdjustmentControl; exports.findLayerControl = findLayerControl; exports.formatControlValue = formatControlValue; exports.getAnnotationBounds = getAnnotationBounds; exports.getControlPath = getControlPath; exports.getCropCursor = getCropCursor; exports.getRotatedSize = getRotatedSize; exports.getViewportMetrics = getViewportMetrics; exports.getViewportProjection = getViewportProjection; exports.globalImageCache = globalImageCache; exports.hexToRgb = hexToRgb; exports.histogramPath = histogramPath; exports.hitTestAnnotation = hitTestAnnotation; exports.hitTestCrop = hitTestCrop; exports.hitTestCurvePoint = hitTestCurvePoint; exports.hslToRgb = hslToRgb; exports.hueRangeTables = hueRangeTables; exports.hueRangeWeight = hueRangeWeight; exports.hueSaturation = hueSaturation; exports.identityCurve = identityCurve; exports.identityLevels = identityLevels; exports.imageEditorStateMigrations = imageEditorStateMigrations; exports.imageToCanvasPoint = imageToCanvasPoint; exports.insertCurvePoint = insertCurvePoint; exports.invertMatrix3 = invertMatrix3; exports.isAdjustmentKind = isAdjustmentKind; exports.isBlendMode = isBlendMode; exports.isQuarterTurnSwapped = isQuarterTurnSwapped; exports.isSeparableBlendMode = isSeparableBlendMode; exports.kelvinToTemperature = kelvinToTemperature; exports.levelsValue = levelsValue; exports.linearSrgbToXy = linearSrgbToXy; exports.linearToSrgb = linearToSrgb; exports.loadImageEditorDocument = loadImageEditorDocument; exports.mapCropHandle = mapCropHandle; exports.migrateSerializedImageEditorState = migrateSerializedImageEditorState; exports.moveCurvePoint = moveCurvePoint; exports.mulMatrix3 = mulMatrix3; exports.neutralHueRange = neutralHueRange; exports.normalizeAdjustmentLayer = normalizeAdjustmentLayer; exports.normalizeAdjustmentLayers = normalizeAdjustmentLayers; exports.normalizeAngle = normalizeAngle; exports.normalizeBlendMode = normalizeBlendMode; exports.normalizeProcessVersion = normalizeProcessVersion; exports.normalizeStoredAdjustments = normalizeStoredAdjustments; exports.photoFilterMatrix = photoFilterMatrix; exports.posterizeValue = posterizeValue; exports.projectImagePoint = projectImagePoint; exports.projectImageRect = projectImageRect; exports.rangeLightness = rangeLightness; exports.readAdjustmentControls = readAdjustmentControls; exports.removeAlpha = removeAlpha; exports.removeCurvePoint = removeCurvePoint; exports.rgbToGray = rgbToGray; exports.rgbToHsl = rgbToHsl; exports.sampleLut = sampleLut; exports.sampleToneLut = sampleToneLut; exports.setControlPath = setControlPath; exports.setLum = setLum; exports.srgbToLinear = srgbToLinear; exports.tempTintToXy = tempTintToXy; exports.temperatureToKelvin = temperatureToKelvin; exports.toneCurve = toneCurve; exports.toneLuts = toneLuts; exports.unprojectCanvasPoint = unprojectCanvasPoint; exports.uvToXy = uvToXy; exports.vibranceLinear = vibranceLinear; exports.vibrancePixel = vibrancePixel; exports.wbMatrix = wbMatrix; exports.whiteBalanceFromSample = whiteBalanceFromSample; exports.whiteBalanceMatrix = whiteBalanceMatrix; exports.xyToTempTint = xyToTempTint; exports.xyToUv = xyToUv;
2039
3840
  //# sourceMappingURL=index.cjs.map