@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.
- package/README.md +96 -1
- package/THIRD_PARTY_NOTICES.md +51 -0
- package/dist/agent/index.cjs +9 -8
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +4 -3
- package/dist/agent/index.d.ts +4 -3
- package/dist/agent/index.js +3 -2
- package/dist/agent/index.js.map +1 -1
- package/dist/canvas/index.cjs +1210 -17
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +318 -1
- package/dist/canvas/index.d.ts +318 -1
- package/dist/canvas/index.js +1199 -6
- package/dist/canvas/index.js.map +1 -1
- package/dist/{chunk-QBJ22YZR.js → chunk-3CN2WD3X.js} +1354 -206
- package/dist/chunk-3CN2WD3X.js.map +1 -0
- package/dist/{chunk-JJDLPEWL.js → chunk-3LV6RRPH.js} +2 -2
- package/dist/{chunk-MJZBNWG7.js → chunk-5S3Z4PPF.js} +78 -557
- package/dist/chunk-5S3Z4PPF.js.map +1 -0
- package/dist/chunk-664LQYFJ.js +115 -0
- package/dist/chunk-664LQYFJ.js.map +1 -0
- package/dist/{chunk-AA4ZEVCC.cjs → chunk-D27YFC46.cjs} +101 -580
- package/dist/chunk-D27YFC46.cjs.map +1 -0
- package/dist/chunk-D6FH2YAX.cjs +131 -0
- package/dist/chunk-D6FH2YAX.cjs.map +1 -0
- package/dist/{chunk-4SUNJZWB.cjs → chunk-FD4LPFAE.cjs} +48 -200
- package/dist/chunk-FD4LPFAE.cjs.map +1 -0
- package/dist/chunk-FODNLX5N.js +749 -0
- package/dist/chunk-FODNLX5N.js.map +1 -0
- package/dist/{chunk-OYXSKQFR.js → chunk-FPENXAEJ.js} +35 -187
- package/dist/chunk-FPENXAEJ.js.map +1 -0
- package/dist/chunk-HLIMTC6N.js +1 -0
- package/dist/chunk-HLIMTC6N.js.map +1 -0
- package/dist/{chunk-QGHGJ56O.js → chunk-ITBSKI26.js} +1483 -19
- package/dist/chunk-ITBSKI26.js.map +1 -0
- package/dist/{chunk-XDMXXC4Z.cjs → chunk-MA2F5KHF.cjs} +350 -227
- package/dist/chunk-MA2F5KHF.cjs.map +1 -0
- package/dist/{chunk-RIKBJLS2.cjs → chunk-MCSJFWAO.cjs} +1356 -208
- package/dist/chunk-MCSJFWAO.cjs.map +1 -0
- package/dist/chunk-MKA3AHLV.cjs +256 -0
- package/dist/chunk-MKA3AHLV.cjs.map +1 -0
- package/dist/chunk-OLZJXL4R.cjs +749 -0
- package/dist/chunk-OLZJXL4R.cjs.map +1 -0
- package/dist/chunk-OXSV64JX.js +689 -0
- package/dist/chunk-OXSV64JX.js.map +1 -0
- package/dist/chunk-P3RQ5UJE.cjs +689 -0
- package/dist/chunk-P3RQ5UJE.cjs.map +1 -0
- package/dist/chunk-P6ODPZ4W.js +256 -0
- package/dist/chunk-P6ODPZ4W.js.map +1 -0
- package/dist/chunk-PGNZKFQS.js +685 -0
- package/dist/chunk-PGNZKFQS.js.map +1 -0
- package/dist/chunk-PL4JB3BF.cjs +1 -0
- package/dist/chunk-PL4JB3BF.cjs.map +1 -0
- package/dist/{chunk-GVSMRPHB.cjs → chunk-SBCZ6RN5.cjs} +1568 -104
- package/dist/chunk-SBCZ6RN5.cjs.map +1 -0
- package/dist/{chunk-CL37JGFW.cjs → chunk-SML64VJG.cjs} +4 -4
- package/dist/{chunk-CL37JGFW.cjs.map → chunk-SML64VJG.cjs.map} +1 -1
- package/dist/chunk-TNNJXF46.cjs +115 -0
- package/dist/chunk-TNNJXF46.cjs.map +1 -0
- package/dist/chunk-U5HZSFCS.js +131 -0
- package/dist/chunk-U5HZSFCS.js.map +1 -0
- package/dist/{chunk-K3R5NG2D.js → chunk-U5KWZU4X.js} +251 -128
- package/dist/chunk-U5KWZU4X.js.map +1 -0
- package/dist/chunk-XNORY7M6.cjs +685 -0
- package/dist/chunk-XNORY7M6.cjs.map +1 -0
- package/dist/commands/index.d.cts +5 -353
- package/dist/commands/index.d.ts +5 -353
- package/dist/comment-store-BEMgVlAA.d.cts +135 -0
- package/dist/comment-store-BEMgVlAA.d.ts +135 -0
- package/dist/compare/index.cjs +15 -0
- package/dist/compare/index.cjs.map +1 -0
- package/dist/compare/index.d.cts +58 -0
- package/dist/compare/index.d.ts +58 -0
- package/dist/compare/index.js +15 -0
- package/dist/compare/index.js.map +1 -0
- package/dist/{controller-pePCJLT1.d.cts → controller-BhLLAoi8.d.cts} +45 -4
- package/dist/{controller-pePCJLT1.d.ts → controller-BhLLAoi8.d.ts} +45 -4
- package/dist/documents-BQjzJNe2.d.ts +76 -0
- package/dist/documents-KRTPhvst.d.cts +76 -0
- package/dist/extensions/index.cjs +53 -5
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.d.cts +7 -3
- package/dist/extensions/index.d.ts +7 -3
- package/dist/extensions/index.js +52 -4
- package/dist/generation/index.cjs +1373 -0
- package/dist/generation/index.cjs.map +1 -0
- package/dist/generation/index.d.cts +554 -0
- package/dist/generation/index.d.ts +554 -0
- package/dist/generation/index.js +1373 -0
- package/dist/generation/index.js.map +1 -0
- package/dist/grammar/index.cjs +48 -3
- package/dist/grammar/index.cjs.map +1 -1
- package/dist/grammar/index.d.cts +322 -100
- package/dist/grammar/index.d.ts +322 -100
- package/dist/grammar/index.js +54 -9
- package/dist/image-editor/index.cjs +1831 -30
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +590 -3
- package/dist/image-editor/index.d.ts +590 -3
- package/dist/image-editor/index.js +1807 -6
- package/dist/image-editor/index.js.map +1 -1
- package/dist/{index-CCr_z9kF.d.cts → index-BhJBSm2d.d.cts} +370 -4
- package/dist/{index-Ck6ajwXh.d.ts → index-fJrlkJCd.d.ts} +370 -4
- package/dist/index.cjs +162 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -8
- package/dist/index.d.ts +14 -8
- package/dist/index.js +174 -37
- package/dist/index.js.map +1 -1
- package/dist/migrate-iTlm3a8h.d.cts +112 -0
- package/dist/migrate-iTlm3a8h.d.ts +112 -0
- package/dist/persistence/index.cjs +676 -0
- package/dist/persistence/index.cjs.map +1 -0
- package/dist/persistence/index.d.cts +347 -0
- package/dist/persistence/index.d.ts +347 -0
- package/dist/persistence/index.js +676 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/{registry-D5-R90p0.d.cts → registry-BDe-vlv3.d.ts} +2 -40
- package/dist/registry-D32LpIc4.d.cts +354 -0
- package/dist/{registry-D5-R90p0.d.ts → registry-DnzpgY27.d.cts} +2 -40
- package/dist/registry-evKTP4ls.d.ts +354 -0
- package/dist/schema-DYDGdYL9.d.cts +41 -0
- package/dist/schema-DYDGdYL9.d.ts +41 -0
- package/dist/serializers/index.cjs +4 -3
- package/dist/serializers/index.cjs.map +1 -1
- package/dist/serializers/index.d.cts +2 -1
- package/dist/serializers/index.d.ts +2 -1
- package/dist/serializers/index.js +3 -2
- package/dist/sourcemap-C8AH3MBQ.d.cts +91 -0
- package/dist/sourcemap-DA1Evhrv.d.ts +91 -0
- package/dist/spreadsheet/index.cjs +37 -3
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.d.cts +188 -17
- package/dist/spreadsheet/index.d.ts +188 -17
- package/dist/spreadsheet/index.js +43 -9
- package/dist/{tokens-CyLZmIei.d.cts → tokens-D7yXvUU6.d.cts} +15 -2
- package/dist/{tokens-CyLZmIei.d.ts → tokens-D7yXvUU6.d.ts} +15 -2
- package/dist/{track-changes-BSF58RK0.d.ts → track-changes-BK5sVf4_.d.cts} +18 -136
- package/dist/{track-changes-BSF58RK0.d.cts → track-changes-CDjCrwKe.d.ts} +18 -136
- package/package.json +16 -1
- package/dist/chunk-4SUNJZWB.cjs.map +0 -1
- package/dist/chunk-AA4ZEVCC.cjs.map +0 -1
- package/dist/chunk-AXV2ZW34.cjs +0 -118
- package/dist/chunk-AXV2ZW34.cjs.map +0 -1
- package/dist/chunk-B2ZRLN4S.js +0 -118
- package/dist/chunk-B2ZRLN4S.js.map +0 -1
- package/dist/chunk-GVSMRPHB.cjs.map +0 -1
- package/dist/chunk-K3R5NG2D.js.map +0 -1
- package/dist/chunk-MJZBNWG7.js.map +0 -1
- package/dist/chunk-OYXSKQFR.js.map +0 -1
- package/dist/chunk-QBJ22YZR.js.map +0 -1
- package/dist/chunk-QGHGJ56O.js.map +0 -1
- package/dist/chunk-RIKBJLS2.cjs.map +0 -1
- package/dist/chunk-XDMXXC4Z.cjs.map +0 -1
- /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
|
|
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
|
-
|
|
885
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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',
|
|
1307
|
-
const useAnnotations = _nullishCoalesce(_optionalChain([options, 'optionalAccess',
|
|
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',
|
|
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
|
-
|
|
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
|
-
|
|
1440
|
-
|
|
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
|
-
|
|
1444
|
-
|
|
2476
|
+
__init11() {this.undoStack = []}
|
|
2477
|
+
__init12() {this.redoStack = []}
|
|
1445
2478
|
|
|
1446
|
-
|
|
2479
|
+
__init13() {this.isApplyingHistory = false}
|
|
1447
2480
|
|
|
1448
|
-
constructor(options = {}) {;_class3.prototype.
|
|
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
|
-
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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 (
|
|
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',
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|