@pixerate/editor 0.22.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 +175 -2
- package/THIRD_PARTY_NOTICES.md +139 -0
- package/dist/agent/index.cjs +87 -111
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +70 -211
- package/dist/agent/index.d.ts +70 -211
- package/dist/agent/index.js +78 -102
- package/dist/agent/index.js.map +1 -1
- package/dist/arrangeShortcuts-B8O3f8-P.d.cts +184 -0
- package/dist/arrangeShortcuts-sLlD2rSX.d.ts +184 -0
- package/dist/canvas/index.cjs +2075 -86
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +608 -186
- package/dist/canvas/index.d.ts +608 -186
- package/dist/canvas/index.js +2060 -71
- package/dist/canvas/index.js.map +1 -1
- package/dist/chunk-26BVFKHU.cjs +453 -0
- package/dist/chunk-26BVFKHU.cjs.map +1 -0
- package/dist/chunk-2H7ZHPEJ.js +762 -0
- package/dist/chunk-2H7ZHPEJ.js.map +1 -0
- package/dist/{chunk-DENSCKFC.js → chunk-3CN2WD3X.js} +1730 -189
- 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-7DWMJCAT.js +453 -0
- package/dist/chunk-7DWMJCAT.js.map +1 -0
- package/dist/chunk-ARRTP33I.js +101 -0
- package/dist/chunk-ARRTP33I.js.map +1 -0
- package/dist/chunk-CPOKFNBG.cjs +101 -0
- package/dist/chunk-CPOKFNBG.cjs.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-G2I4QJOT.cjs +762 -0
- package/dist/chunk-G2I4QJOT.cjs.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-6M3BCTBD.cjs → chunk-MA2F5KHF.cjs} +470 -242
- package/dist/chunk-MA2F5KHF.cjs.map +1 -0
- package/dist/{chunk-SAZRENEZ.cjs → chunk-MCSJFWAO.cjs} +1733 -192
- 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-RU52I7QO.js → chunk-U5KWZU4X.js} +388 -160
- 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.cjs +860 -0
- package/dist/commands/index.cjs.map +1 -0
- package/dist/commands/index.d.cts +244 -0
- package/dist/commands/index.d.ts +244 -0
- package/dist/commands/index.js +860 -0
- package/dist/commands/index.js.map +1 -0
- 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-B7GIybc4.d.cts → controller-BhLLAoi8.d.cts} +119 -5
- package/dist/{controller-B7GIybc4.d.ts → controller-BhLLAoi8.d.ts} +119 -5
- 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/history/index.cjs +12 -2
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.d.cts +211 -7
- package/dist/history/index.d.ts +211 -7
- package/dist/history/index.js +13 -3
- package/dist/image-editor/index.cjs +2244 -38
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +742 -4
- package/dist/image-editor/index.d.ts +742 -4
- package/dist/image-editor/index.js +2222 -16
- 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 +193 -98
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -48
- package/dist/index.d.ts +17 -48
- package/dist/index.js +198 -103
- 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-BDe-vlv3.d.ts +171 -0
- package/dist/registry-D32LpIc4.d.cts +354 -0
- package/dist/registry-DnzpgY27.d.cts +171 -0
- 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 +53 -3
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.d.cts +247 -18
- package/dist/spreadsheet/index.d.ts +247 -18
- package/dist/spreadsheet/index.js +58 -8
- 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.cts → track-changes-BK5sVf4_.d.cts} +18 -136
- package/dist/{track-changes-BSF58RK0.d.ts → track-changes-CDjCrwKe.d.ts} +18 -136
- package/dist/{types-CJ4U-HHI.d.ts → types-DX4Mh4SL.d.cts} +1 -1
- package/dist/{types-CJ4U-HHI.d.cts → types-DX4Mh4SL.d.ts} +1 -1
- package/dist/types-GT5a5YAX.d.cts +86 -0
- package/dist/types-GT5a5YAX.d.ts +86 -0
- package/package.json +23 -2
- package/dist/chunk-2TWGLC5K.cjs +0 -262
- package/dist/chunk-2TWGLC5K.cjs.map +0 -1
- package/dist/chunk-4SUNJZWB.cjs.map +0 -1
- package/dist/chunk-6M3BCTBD.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-DENSCKFC.js.map +0 -1
- package/dist/chunk-GVSMRPHB.cjs.map +0 -1
- package/dist/chunk-KAADNAPF.js +0 -262
- package/dist/chunk-KAADNAPF.js.map +0 -1
- package/dist/chunk-MJZBNWG7.js.map +0 -1
- package/dist/chunk-OYXSKQFR.js.map +0 -1
- package/dist/chunk-QGHGJ56O.js.map +0 -1
- package/dist/chunk-RU52I7QO.js.map +0 -1
- package/dist/chunk-SAZRENEZ.cjs.map +0 -1
- package/dist/types-BZgcDsC5.d.cts +0 -42
- package/dist/types-BZgcDsC5.d.ts +0 -42
- /package/dist/{chunk-JJDLPEWL.js.map → chunk-3LV6RRPH.js.map} +0 -0
|
@@ -1,9 +1,1140 @@
|
|
|
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
|
|
6
|
+
var LEGACY_PROCESS_VERSION = 1;
|
|
7
|
+
var LATEST_PROCESS_VERSION = 2;
|
|
8
|
+
function normalizeProcessVersion(value) {
|
|
9
|
+
if (typeof value !== "number" || !Number.isInteger(value)) return LEGACY_PROCESS_VERSION;
|
|
10
|
+
return value >= LATEST_PROCESS_VERSION ? LATEST_PROCESS_VERSION : LEGACY_PROCESS_VERSION;
|
|
11
|
+
}
|
|
12
|
+
function srgbToLinear(v) {
|
|
13
|
+
return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
14
|
+
}
|
|
15
|
+
function linearToSrgb(v) {
|
|
16
|
+
return v <= 31308e-7 ? v * 12.92 : 1.055 * Math.pow(v, 1 / 2.4) - 0.055;
|
|
17
|
+
}
|
|
18
|
+
var LUMA_709 = [0.2126, 0.7152, 0.0722];
|
|
19
|
+
var IDENTITY_MATRIX3 = [1, 0, 0, 0, 1, 0, 0, 0, 1];
|
|
20
|
+
function mulMatrix3(a, b) {
|
|
21
|
+
const out = new Array(9).fill(0);
|
|
22
|
+
for (let r = 0; r < 3; r++) {
|
|
23
|
+
for (let c = 0; c < 3; c++) {
|
|
24
|
+
out[r * 3 + c] = a[r * 3] * b[c] + a[r * 3 + 1] * b[3 + c] + a[r * 3 + 2] * b[6 + c];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
function applyMatrix3(m, v) {
|
|
30
|
+
return [
|
|
31
|
+
m[0] * v[0] + m[1] * v[1] + m[2] * v[2],
|
|
32
|
+
m[3] * v[0] + m[4] * v[1] + m[5] * v[2],
|
|
33
|
+
m[6] * v[0] + m[7] * v[1] + m[8] * v[2]
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
function invertMatrix3(m) {
|
|
37
|
+
const [a, b, c, d, e, f, g, h, i] = m;
|
|
38
|
+
const A = e * i - f * h;
|
|
39
|
+
const B = -(d * i - f * g);
|
|
40
|
+
const C = d * h - e * g;
|
|
41
|
+
const det = a * A + b * B + c * C;
|
|
42
|
+
if (!Number.isFinite(det) || Math.abs(det) < 1e-12) return null;
|
|
43
|
+
const k = 1 / det;
|
|
44
|
+
return [
|
|
45
|
+
A * k,
|
|
46
|
+
-(b * i - c * h) * k,
|
|
47
|
+
(b * f - c * e) * k,
|
|
48
|
+
B * k,
|
|
49
|
+
(a * i - c * g) * k,
|
|
50
|
+
-(a * f - c * d) * k,
|
|
51
|
+
C * k,
|
|
52
|
+
-(a * h - b * g) * k,
|
|
53
|
+
(a * e - b * d) * k
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
function diag3(x, y, z) {
|
|
57
|
+
return [x, 0, 0, 0, y, 0, 0, 0, z];
|
|
58
|
+
}
|
|
59
|
+
var D65 = { x: 0.3127, y: 0.329 };
|
|
60
|
+
function xyToXyz(p) {
|
|
61
|
+
return [p.x / p.y, 1, (1 - p.x - p.y) / p.y];
|
|
62
|
+
}
|
|
63
|
+
function xyzToXy(v) {
|
|
64
|
+
const s = v[0] + v[1] + v[2];
|
|
65
|
+
return s <= 0 ? { ...D65 } : { x: v[0] / s, y: v[1] / s };
|
|
66
|
+
}
|
|
67
|
+
var SRGB_TO_XYZ = (() => {
|
|
68
|
+
const p = [{ x: 0.64, y: 0.33 }, { x: 0.3, y: 0.6 }, { x: 0.15, y: 0.06 }].map(xyToXyz);
|
|
69
|
+
const m = [p[0][0], p[1][0], p[2][0], p[0][1], p[1][1], p[2][1], p[0][2], p[1][2], p[2][2]];
|
|
70
|
+
const inv = invertMatrix3(m);
|
|
71
|
+
const s = inv ? applyMatrix3(inv, xyToXyz(D65)) : [1, 1, 1];
|
|
72
|
+
return mulMatrix3(m, diag3(s[0], s[1], s[2]));
|
|
73
|
+
})();
|
|
74
|
+
var XYZ_TO_SRGB = _nullishCoalesce(invertMatrix3(SRGB_TO_XYZ), () => ( IDENTITY_MATRIX3));
|
|
75
|
+
var BRADFORD = [0.8951, 0.2664, -0.1614, -0.7502, 1.7135, 0.0367, 0.0389, -0.0685, 1.0296];
|
|
76
|
+
var BRADFORD_INV = _nullishCoalesce(invertMatrix3(BRADFORD), () => ( IDENTITY_MATRIX3));
|
|
77
|
+
function bradfordAdaptation(src, dst) {
|
|
78
|
+
if (src.x === dst.x && src.y === dst.y) return [...IDENTITY_MATRIX3];
|
|
79
|
+
const s = applyMatrix3(BRADFORD, xyToXyz(src));
|
|
80
|
+
const d = applyMatrix3(BRADFORD, xyToXyz(dst));
|
|
81
|
+
return mulMatrix3(BRADFORD_INV, mulMatrix3(diag3(d[0] / s[0], d[1] / s[1], d[2] / s[2]), BRADFORD));
|
|
82
|
+
}
|
|
83
|
+
function wbMatrix(src) {
|
|
84
|
+
return mulMatrix3(XYZ_TO_SRGB, mulMatrix3(bradfordAdaptation(src, D65), SRGB_TO_XYZ));
|
|
85
|
+
}
|
|
86
|
+
function linearSrgbToXy(rgb) {
|
|
87
|
+
return xyzToXy(applyMatrix3(SRGB_TO_XYZ, rgb));
|
|
88
|
+
}
|
|
89
|
+
var TINT_SCALE = 3e3;
|
|
90
|
+
var MIN_KELVIN = 1667;
|
|
91
|
+
var MAX_KELVIN = 25e3;
|
|
92
|
+
function kimXy(tIn) {
|
|
93
|
+
const t = Math.min(MAX_KELVIN, Math.max(MIN_KELVIN, tIn));
|
|
94
|
+
const t2 = t * t;
|
|
95
|
+
const t3 = t2 * t;
|
|
96
|
+
const x = t <= 4e3 ? -266123900 / t3 - 234358.9 / t2 + 877.6956 / t + 0.17991 : -3025846900 / t3 + 21070379e-1 / t2 + 222.6347 / t + 0.24039;
|
|
97
|
+
const x2 = x * x;
|
|
98
|
+
const x3 = x2 * x;
|
|
99
|
+
const y = t <= 2222 ? -1.1063814 * x3 - 1.3481102 * x2 + 2.18555832 * x - 0.20219683 : t <= 4e3 ? -0.9549476 * x3 - 1.37418593 * x2 + 2.09137015 * x - 0.16748867 : 3.081758 * x3 - 5.8733867 * x2 + 3.75112997 * x - 0.37001483;
|
|
100
|
+
return { x, y };
|
|
101
|
+
}
|
|
102
|
+
function xyToUv(p) {
|
|
103
|
+
const d = -2 * p.x + 12 * p.y + 3;
|
|
104
|
+
return [4 * p.x / d, 6 * p.y / d];
|
|
105
|
+
}
|
|
106
|
+
function uvToXy(u, v) {
|
|
107
|
+
const d = 2 * u - 8 * v + 4;
|
|
108
|
+
return { x: 3 * u / d, y: 2 * v / d };
|
|
109
|
+
}
|
|
110
|
+
function locusUv(tIn) {
|
|
111
|
+
const t = Math.max(1e3, tIn);
|
|
112
|
+
if (t >= MIN_KELVIN && t <= MAX_KELVIN) return xyToUv(kimXy(t));
|
|
113
|
+
const [a, b] = t < MIN_KELVIN ? [MIN_KELVIN, MIN_KELVIN + 50] : [MAX_KELVIN - 500, MAX_KELVIN];
|
|
114
|
+
const [ua, va] = xyToUv(kimXy(a));
|
|
115
|
+
const [ub, vb] = xyToUv(kimXy(b));
|
|
116
|
+
const ma = 1e6 / a;
|
|
117
|
+
const mb = 1e6 / b;
|
|
118
|
+
const s = (1e6 / t - ma) / (mb - ma);
|
|
119
|
+
return [ua + (ub - ua) * s, va + (vb - va) * s];
|
|
120
|
+
}
|
|
121
|
+
function locusNormal(t) {
|
|
122
|
+
const m = 1e6 / t;
|
|
123
|
+
const [u0, v0] = locusUv(1e6 / (m + 1));
|
|
124
|
+
const [u1, v1] = locusUv(1e6 / Math.max(m - 1, 1));
|
|
125
|
+
const du = u1 - u0;
|
|
126
|
+
const dv = v1 - v0;
|
|
127
|
+
const l = Math.max(Math.hypot(du, dv), 1e-12);
|
|
128
|
+
const nx = -dv / l;
|
|
129
|
+
const ny = du / l;
|
|
130
|
+
return ny < 0 ? [-nx, -ny] : [nx, ny];
|
|
131
|
+
}
|
|
132
|
+
function tempTintToXy(kelvin, tint) {
|
|
133
|
+
const [u, v] = locusUv(kelvin);
|
|
134
|
+
const [nu, nv] = locusNormal(kelvin);
|
|
135
|
+
const duv = tint / TINT_SCALE;
|
|
136
|
+
return uvToXy(u + nu * duv, v + nv * duv);
|
|
137
|
+
}
|
|
138
|
+
function xyToTempTint(p) {
|
|
139
|
+
const [u, v] = xyToUv(p);
|
|
140
|
+
const dist = (m) => {
|
|
141
|
+
const [lu2, lv2] = locusUv(1e6 / m);
|
|
142
|
+
return (u - lu2) ** 2 + (v - lv2) ** 2;
|
|
143
|
+
};
|
|
144
|
+
let best = 20;
|
|
145
|
+
let bd = Infinity;
|
|
146
|
+
for (let m = 20; m <= 1e3; m += 5) {
|
|
147
|
+
const d = dist(m);
|
|
148
|
+
if (d < bd) {
|
|
149
|
+
bd = d;
|
|
150
|
+
best = m;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
let a = Math.max(best - 5, 20);
|
|
154
|
+
let b = Math.min(best + 5, 1e3);
|
|
155
|
+
const g = 0.61803398875;
|
|
156
|
+
for (let i = 0; i < 60; i++) {
|
|
157
|
+
const c = b - g * (b - a);
|
|
158
|
+
const d = a + g * (b - a);
|
|
159
|
+
if (dist(c) < dist(d)) b = d;
|
|
160
|
+
else a = c;
|
|
161
|
+
}
|
|
162
|
+
const kelvin = 1e6 / ((a + b) / 2);
|
|
163
|
+
const [lu, lv] = locusUv(kelvin);
|
|
164
|
+
const [nu, nv] = locusNormal(kelvin);
|
|
165
|
+
const duv = (u - lu) * nu + (v - lv) * nv;
|
|
166
|
+
return { kelvin, tint: duv * TINT_SCALE };
|
|
167
|
+
}
|
|
168
|
+
var AS_SHOT_KELVIN = 6500;
|
|
169
|
+
var MIRED_PER_TEMPERATURE_UNIT = 1;
|
|
170
|
+
var AS_SHOT_MIRED = 1e6 / AS_SHOT_KELVIN;
|
|
171
|
+
function temperatureToKelvin(temperature) {
|
|
172
|
+
return 1e6 / (AS_SHOT_MIRED - temperature * MIRED_PER_TEMPERATURE_UNIT);
|
|
173
|
+
}
|
|
174
|
+
function kelvinToTemperature(kelvin) {
|
|
175
|
+
return (AS_SHOT_MIRED - 1e6 / kelvin) / MIRED_PER_TEMPERATURE_UNIT;
|
|
176
|
+
}
|
|
177
|
+
var AS_SHOT_WB_INV = _nullishCoalesce(invertMatrix3(wbMatrix(tempTintToXy(AS_SHOT_KELVIN, 0))), () => ( IDENTITY_MATRIX3));
|
|
178
|
+
function whiteBalanceMatrix(temperature, tint = 0) {
|
|
179
|
+
if (!temperature && !tint) return [...IDENTITY_MATRIX3];
|
|
180
|
+
const set = wbMatrix(tempTintToXy(temperatureToKelvin(temperature), tint));
|
|
181
|
+
const m = mulMatrix3(set, AS_SHOT_WB_INV);
|
|
182
|
+
const g = applyMatrix3(m, [1, 1, 1]);
|
|
183
|
+
const y = g[0] * LUMA_709[0] + g[1] * LUMA_709[1] + g[2] * LUMA_709[2];
|
|
184
|
+
return mulMatrix3(m, diag3(1 / y, 1 / y, 1 / y));
|
|
185
|
+
}
|
|
186
|
+
function whiteBalanceFromSample(rgb) {
|
|
187
|
+
const [r, g, b] = Array.isArray(rgb) ? rgb : [rgb.r, rgb.g, rgb.b];
|
|
188
|
+
const lin = [r, g, b].map((c) => srgbToLinear(Math.min(255, Math.max(0, c)) / 255));
|
|
189
|
+
if (lin[0] + lin[1] + lin[2] <= 1e-6) return { temperature: 0, tint: 0 };
|
|
190
|
+
const src = linearSrgbToXy(applyMatrix3(AS_SHOT_WB_INV, lin));
|
|
191
|
+
const { kelvin, tint } = xyToTempTint(src);
|
|
192
|
+
const clamp2 = (v) => Math.max(-100, Math.min(100, v));
|
|
193
|
+
return { temperature: clamp2(kelvinToTemperature(kelvin)), tint: clamp2(tint) };
|
|
194
|
+
}
|
|
195
|
+
var TONE_LUT_SIZE = 4096;
|
|
196
|
+
var GREY = 0.18;
|
|
197
|
+
var GREY_P = Math.pow(GREY, 1 / 2.2);
|
|
198
|
+
var SHOULDER_KNEE = 0.6;
|
|
199
|
+
function toneCurve(y, contrast, brightness, headroom = 1) {
|
|
200
|
+
if (!(y > 0)) return 0;
|
|
201
|
+
const h = Math.max(1, headroom);
|
|
202
|
+
let o = Math.min(y, h);
|
|
203
|
+
if (h > 1 && o > SHOULDER_KNEE) {
|
|
204
|
+
const span = 1 - SHOULDER_KNEE;
|
|
205
|
+
const x = (o - SHOULDER_KNEE) / span;
|
|
206
|
+
const X = (h - SHOULDER_KNEE) / span;
|
|
207
|
+
o = SHOULDER_KNEE + span * (x * (1 + x / (X * X)) / (1 + x));
|
|
208
|
+
}
|
|
209
|
+
if (contrast === 0 && brightness === 0) return Math.min(1, o);
|
|
210
|
+
let p = Math.pow(Math.min(1, o), 1 / 2.2);
|
|
211
|
+
const c = contrast / 100;
|
|
212
|
+
if (c !== 0) {
|
|
213
|
+
p += c * 0.4 * (p - GREY_P) * (1 - (2 * p - 1) ** 2);
|
|
214
|
+
}
|
|
215
|
+
const b = brightness / 100;
|
|
216
|
+
if (b !== 0) {
|
|
217
|
+
p += b * 0.8 * p * (1 - p);
|
|
218
|
+
}
|
|
219
|
+
return Math.pow(Math.min(1, Math.max(0, p)), 2.2);
|
|
220
|
+
}
|
|
221
|
+
function buildToneLut(contrast, brightness, headroom = 1) {
|
|
222
|
+
const h = Math.max(1, headroom);
|
|
223
|
+
const lut = new Float32Array(TONE_LUT_SIZE + 1);
|
|
224
|
+
for (let i = 0; i <= TONE_LUT_SIZE; i++) {
|
|
225
|
+
lut[i] = toneCurve(h * i / TONE_LUT_SIZE, contrast, brightness, h);
|
|
226
|
+
}
|
|
227
|
+
return lut;
|
|
228
|
+
}
|
|
229
|
+
function sampleToneLut(lut, headroom, y) {
|
|
230
|
+
if (!(y > 0)) return lut[0];
|
|
231
|
+
const n = lut.length - 1;
|
|
232
|
+
const f = y * n / Math.max(1, headroom);
|
|
233
|
+
if (f >= n) return lut[n];
|
|
234
|
+
const i = f | 0;
|
|
235
|
+
return lut[i] + (lut[i + 1] - lut[i]) * (f - i);
|
|
236
|
+
}
|
|
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
|
+
|
|
1121
|
+
// src/image-editor/filters.ts
|
|
2
1122
|
function clamp(value, min = 0, max = 255) {
|
|
3
1123
|
return Math.max(min, Math.min(max, value));
|
|
4
1124
|
}
|
|
5
1125
|
function applyAdjustments(imageData, adjustments) {
|
|
6
|
-
|
|
1126
|
+
if (normalizeProcessVersion(adjustments.process) === LATEST_PROCESS_VERSION) {
|
|
1127
|
+
applyProcess2Adjustments(imageData, adjustments);
|
|
1128
|
+
} else {
|
|
1129
|
+
applyLegacyAdjustments(imageData, adjustments);
|
|
1130
|
+
}
|
|
1131
|
+
applyAdjustmentLayers(imageData, adjustments.layers);
|
|
1132
|
+
if (adjustments.blur > 0) {
|
|
1133
|
+
applyFastBoxBlur(imageData, Math.min(Math.round(adjustments.blur), 40));
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
function applyLegacyAdjustments(imageData, adjustments) {
|
|
1137
|
+
const { brightness, contrast, saturation, exposure, temperature, opacity } = adjustments;
|
|
7
1138
|
const data = imageData.data;
|
|
8
1139
|
const len = data.length;
|
|
9
1140
|
const hasBrightness = brightness !== 0;
|
|
@@ -57,8 +1188,108 @@ function applyAdjustments(imageData, adjustments) {
|
|
|
57
1188
|
data[i + 3] = a;
|
|
58
1189
|
}
|
|
59
1190
|
}
|
|
60
|
-
|
|
61
|
-
|
|
1191
|
+
}
|
|
1192
|
+
var DECODE_LUT = (() => {
|
|
1193
|
+
const lut = new Float32Array(256);
|
|
1194
|
+
for (let i = 0; i < 256; i++) lut[i] = srgbToLinear(i / 255);
|
|
1195
|
+
return lut;
|
|
1196
|
+
})();
|
|
1197
|
+
var ENCODE_LUT_SIZE = 65535;
|
|
1198
|
+
var encodeLut = null;
|
|
1199
|
+
function getEncodeLut() {
|
|
1200
|
+
if (!encodeLut) {
|
|
1201
|
+
encodeLut = new Uint8Array(ENCODE_LUT_SIZE + 1);
|
|
1202
|
+
for (let i = 0; i <= ENCODE_LUT_SIZE; i++) {
|
|
1203
|
+
encodeLut[i] = Math.round(linearToSrgb(i / ENCODE_LUT_SIZE) * 255);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
return encodeLut;
|
|
1207
|
+
}
|
|
1208
|
+
var cachedPlan = null;
|
|
1209
|
+
function getProcess2Plan(adjustments) {
|
|
1210
|
+
const exposure = adjustments.exposure || 0;
|
|
1211
|
+
const temperature = adjustments.temperature || 0;
|
|
1212
|
+
const tint = adjustments.tint || 0;
|
|
1213
|
+
const contrast = adjustments.contrast || 0;
|
|
1214
|
+
const brightness = adjustments.brightness || 0;
|
|
1215
|
+
const saturation = 1 + (adjustments.saturation || 0) / 100;
|
|
1216
|
+
const key = `${exposure},${temperature},${tint},${contrast},${brightness},${saturation}`;
|
|
1217
|
+
if (cachedPlan && cachedPlan.key === key) return cachedPlan;
|
|
1218
|
+
const gain = Math.pow(2, exposure / 50);
|
|
1219
|
+
const wb = whiteBalanceMatrix(temperature, tint);
|
|
1220
|
+
const m = new Float64Array(9);
|
|
1221
|
+
for (let i = 0; i < 9; i++) m[i] = wb[i] * gain;
|
|
1222
|
+
const luts = [];
|
|
1223
|
+
const scale = [0, 0, 0];
|
|
1224
|
+
for (let c = 0; c < 3; c++) {
|
|
1225
|
+
let reach = 0;
|
|
1226
|
+
for (let k = 0; k < 3; k++) reach += Math.max(0, m[c * 3 + k]);
|
|
1227
|
+
const headroom = Math.max(1, reach);
|
|
1228
|
+
luts[c] = buildToneLut(contrast, brightness, headroom);
|
|
1229
|
+
scale[c] = TONE_LUT_SIZE / headroom;
|
|
1230
|
+
}
|
|
1231
|
+
cachedPlan = { key, m, luts, scale, saturation };
|
|
1232
|
+
return cachedPlan;
|
|
1233
|
+
}
|
|
1234
|
+
function applyProcess2Adjustments(imageData, adjustments) {
|
|
1235
|
+
const data = imageData.data;
|
|
1236
|
+
const len = data.length;
|
|
1237
|
+
const opacity = _nullishCoalesce(adjustments.opacity, () => ( 1));
|
|
1238
|
+
const hasOpacity = opacity < 1;
|
|
1239
|
+
const hasColor = !!adjustments.brightness || !!adjustments.contrast || !!adjustments.saturation || !!adjustments.exposure || !!adjustments.temperature || !!adjustments.tint;
|
|
1240
|
+
if (!hasColor) {
|
|
1241
|
+
if (hasOpacity) {
|
|
1242
|
+
for (let i = 3; i < len; i += 4) data[i] = clamp(data[i] * opacity);
|
|
1243
|
+
}
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
const plan = getProcess2Plan(adjustments);
|
|
1247
|
+
const dec = DECODE_LUT;
|
|
1248
|
+
const enc = getEncodeLut();
|
|
1249
|
+
const { m, luts, scale, saturation } = plan;
|
|
1250
|
+
const m0 = m[0], m1 = m[1], m2 = m[2], m3 = m[3], m4 = m[4], m5 = m[5], m6 = m[6], m7 = m[7], m8 = m[8];
|
|
1251
|
+
const lutR = luts[0], lutG = luts[1], lutB = luts[2];
|
|
1252
|
+
const sR = scale[0], sG = scale[1], sB = scale[2];
|
|
1253
|
+
const N = TONE_LUT_SIZE;
|
|
1254
|
+
const hasSaturation = saturation !== 1;
|
|
1255
|
+
const wr = LUMA_709[0], wg = LUMA_709[1], wb = LUMA_709[2];
|
|
1256
|
+
for (let i = 0; i < len; i += 4) {
|
|
1257
|
+
const r0 = dec[data[i]];
|
|
1258
|
+
const g0 = dec[data[i + 1]];
|
|
1259
|
+
const b0 = dec[data[i + 2]];
|
|
1260
|
+
let r = m0 * r0 + m1 * g0 + m2 * b0;
|
|
1261
|
+
let g = m3 * r0 + m4 * g0 + m5 * b0;
|
|
1262
|
+
let b = m6 * r0 + m7 * g0 + m8 * b0;
|
|
1263
|
+
let f;
|
|
1264
|
+
let k;
|
|
1265
|
+
if (r <= 0) r = lutR[0];
|
|
1266
|
+
else if ((f = r * sR) >= N) r = lutR[N];
|
|
1267
|
+
else {
|
|
1268
|
+
k = f | 0;
|
|
1269
|
+
r = lutR[k] + (lutR[k + 1] - lutR[k]) * (f - k);
|
|
1270
|
+
}
|
|
1271
|
+
if (g <= 0) g = lutG[0];
|
|
1272
|
+
else if ((f = g * sG) >= N) g = lutG[N];
|
|
1273
|
+
else {
|
|
1274
|
+
k = f | 0;
|
|
1275
|
+
g = lutG[k] + (lutG[k + 1] - lutG[k]) * (f - k);
|
|
1276
|
+
}
|
|
1277
|
+
if (b <= 0) b = lutB[0];
|
|
1278
|
+
else if ((f = b * sB) >= N) b = lutB[N];
|
|
1279
|
+
else {
|
|
1280
|
+
k = f | 0;
|
|
1281
|
+
b = lutB[k] + (lutB[k + 1] - lutB[k]) * (f - k);
|
|
1282
|
+
}
|
|
1283
|
+
if (hasSaturation) {
|
|
1284
|
+
const y = wr * r + wg * g + wb * b;
|
|
1285
|
+
r = y + (r - y) * saturation;
|
|
1286
|
+
g = y + (g - y) * saturation;
|
|
1287
|
+
b = y + (b - y) * saturation;
|
|
1288
|
+
}
|
|
1289
|
+
data[i] = enc[r <= 0 ? 0 : r >= 1 ? ENCODE_LUT_SIZE : r * ENCODE_LUT_SIZE + 0.5 | 0];
|
|
1290
|
+
data[i + 1] = enc[g <= 0 ? 0 : g >= 1 ? ENCODE_LUT_SIZE : g * ENCODE_LUT_SIZE + 0.5 | 0];
|
|
1291
|
+
data[i + 2] = enc[b <= 0 ? 0 : b >= 1 ? ENCODE_LUT_SIZE : b * ENCODE_LUT_SIZE + 0.5 | 0];
|
|
1292
|
+
if (hasOpacity) data[i + 3] = clamp(data[i + 3] * opacity);
|
|
62
1293
|
}
|
|
63
1294
|
}
|
|
64
1295
|
function applyFastBoxBlur(imageData, radius) {
|
|
@@ -148,6 +1379,63 @@ function removeAlpha(imageData) {
|
|
|
148
1379
|
}
|
|
149
1380
|
}
|
|
150
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
|
+
|
|
151
1439
|
// src/image-editor/geometry.ts
|
|
152
1440
|
function normalizeAngle(degrees) {
|
|
153
1441
|
const a = (degrees % 360 + 360) % 360;
|
|
@@ -534,12 +1822,19 @@ var ImageCache = (_class = class {constructor() { _class.prototype.__init.call(t
|
|
|
534
1822
|
}
|
|
535
1823
|
}, _class);
|
|
536
1824
|
var globalImageCache = new ImageCache();
|
|
537
|
-
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); }
|
|
538
1826
|
__init2() {this.imageCache = globalImageCache}
|
|
539
1827
|
__init3() {this.cachedBaseCanvas = null}
|
|
540
1828
|
__init4() {this.cachedBaseKey = ""}
|
|
541
|
-
|
|
542
|
-
|
|
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}
|
|
543
1838
|
/**
|
|
544
1839
|
* Helper to create a canvas element safely in browser/JSDOM.
|
|
545
1840
|
*/
|
|
@@ -593,14 +1888,20 @@ var ImageEditorRenderer = (_class2 = class {constructor() { _class2.prototype.__
|
|
|
593
1888
|
`${naturalWidth}x${naturalHeight}`,
|
|
594
1889
|
`${region.x},${region.y},${region.width},${region.height}`,
|
|
595
1890
|
`${projection.angle},${transform.flipH},${transform.flipV}`,
|
|
596
|
-
`${adjustments.brightness},${adjustments.contrast},${adjustments.saturation},${adjustments.exposure},${adjustments.temperature},${adjustments.blur},${adjustments.opacity}`,
|
|
597
|
-
|
|
1891
|
+
`${adjustments.brightness},${adjustments.contrast},${adjustments.saturation},${adjustments.exposure},${adjustments.temperature},${_nullishCoalesce(adjustments.tint, () => ( 0))},${adjustments.blur},${adjustments.opacity},p${normalizeProcessVersion(adjustments.process)}`,
|
|
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}`
|
|
598
1894
|
].join("|");
|
|
599
1895
|
let baseCanvas = this.cachedBaseCanvas;
|
|
600
1896
|
if (!baseCanvas || this.cachedBaseKey !== baseKey) {
|
|
601
|
-
|
|
1897
|
+
let histogram = null;
|
|
1898
|
+
baseCanvas = await this.renderBaseImage(img, naturalWidth, naturalHeight, region, transform, state, (pixels) => {
|
|
1899
|
+
histogram = computeHistogram(pixels);
|
|
1900
|
+
});
|
|
602
1901
|
this.cachedBaseCanvas = baseCanvas;
|
|
603
1902
|
this.cachedBaseKey = baseKey;
|
|
1903
|
+
this.previewHistogram = histogram;
|
|
1904
|
+
_optionalChain([this, 'access', _11 => _11.onPreviewHistogram, 'optionalCall', _12 => _12(histogram)]);
|
|
604
1905
|
}
|
|
605
1906
|
const drawW = rotated.width * projection.scale;
|
|
606
1907
|
const drawH = rotated.height * projection.scale;
|
|
@@ -631,7 +1932,7 @@ var ImageEditorRenderer = (_class2 = class {constructor() { _class2.prototype.__
|
|
|
631
1932
|
* Draws `region` of the source image with rotation/flip applied, then pixel adjustments
|
|
632
1933
|
* and the depth mask (which receives the identical transform so it stays aligned).
|
|
633
1934
|
*/
|
|
634
|
-
async renderBaseImage(img, naturalWidth, naturalHeight, region, transform, state) {
|
|
1935
|
+
async renderBaseImage(img, naturalWidth, naturalHeight, region, transform, state, onPixels) {
|
|
635
1936
|
const { adjustments, depthMask } = state;
|
|
636
1937
|
const rotated = getRotatedSize(region.width, region.height, transform);
|
|
637
1938
|
const baseW = Math.max(1, Math.round(rotated.width));
|
|
@@ -688,6 +1989,7 @@ var ImageEditorRenderer = (_class2 = class {constructor() { _class2.prototype.__
|
|
|
688
1989
|
} catch (e3) {
|
|
689
1990
|
}
|
|
690
1991
|
}
|
|
1992
|
+
_optionalChain([onPixels, 'optionalCall', _13 => _13(imageData)]);
|
|
691
1993
|
baseCtx.putImageData(imageData, 0, 0);
|
|
692
1994
|
}
|
|
693
1995
|
return baseCanvas;
|
|
@@ -705,6 +2007,59 @@ var ImageEditorRenderer = (_class2 = class {constructor() { _class2.prototype.__
|
|
|
705
2007
|
* Draws an individual annotation onto the canvas.
|
|
706
2008
|
*/
|
|
707
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) {
|
|
708
2063
|
ctx.save();
|
|
709
2064
|
if (ann.opacity !== void 0) {
|
|
710
2065
|
ctx.globalAlpha = ann.opacity;
|
|
@@ -807,7 +2162,7 @@ var ImageEditorRenderer = (_class2 = class {constructor() { _class2.prototype.__
|
|
|
807
2162
|
try {
|
|
808
2163
|
const loadedImg = await this.imageCache.get(imgAnn.src);
|
|
809
2164
|
ctx.drawImage(loadedImg, imgAnn.x, imgAnn.y, imgAnn.width, imgAnn.height);
|
|
810
|
-
} catch (
|
|
2165
|
+
} catch (e5) {
|
|
811
2166
|
}
|
|
812
2167
|
break;
|
|
813
2168
|
}
|
|
@@ -960,8 +2315,8 @@ var ImageEditorRenderer = (_class2 = class {constructor() { _class2.prototype.__
|
|
|
960
2315
|
if (!ctx) return maskCanvas;
|
|
961
2316
|
ctx.fillStyle = MASK_BLACK;
|
|
962
2317
|
ctx.fillRect(0, 0, naturalWidth, naturalHeight);
|
|
963
|
-
const useCrop = _nullishCoalesce(_optionalChain([options, 'optionalAccess',
|
|
964
|
-
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));
|
|
965
2320
|
if (useCrop && state.crop) {
|
|
966
2321
|
ctx.fillStyle = MASK_WHITE;
|
|
967
2322
|
ctx.fillRect(state.crop.x, state.crop.y, state.crop.width, state.crop.height);
|
|
@@ -971,7 +2326,7 @@ var ImageEditorRenderer = (_class2 = class {constructor() { _class2.prototype.__
|
|
|
971
2326
|
this.drawAnnotationMask(ctx, ann);
|
|
972
2327
|
}
|
|
973
2328
|
}
|
|
974
|
-
const imageData = _optionalChain([ctx, 'access',
|
|
2329
|
+
const imageData = _optionalChain([ctx, 'access', _16 => _16.getImageData, 'optionalCall', _17 => _17(0, 0, naturalWidth, naturalHeight)]);
|
|
975
2330
|
if (imageData) {
|
|
976
2331
|
const d = imageData.data;
|
|
977
2332
|
for (let i = 0; i < d.length; i += 4) {
|
|
@@ -1058,15 +2413,45 @@ function getBaseProjection(region, transform, baseW, baseH) {
|
|
|
1058
2413
|
}
|
|
1059
2414
|
|
|
1060
2415
|
// src/image-editor/controller.ts
|
|
2416
|
+
var IMAGE_EDITOR_SCHEMA_VERSION = 2;
|
|
1061
2417
|
var DEFAULT_ADJUSTMENTS = {
|
|
1062
2418
|
brightness: 0,
|
|
1063
2419
|
contrast: 0,
|
|
1064
2420
|
saturation: 0,
|
|
1065
2421
|
exposure: 0,
|
|
1066
2422
|
temperature: 0,
|
|
2423
|
+
tint: 0,
|
|
1067
2424
|
blur: 0,
|
|
1068
|
-
opacity: 1
|
|
2425
|
+
opacity: 1,
|
|
2426
|
+
process: LATEST_PROCESS_VERSION
|
|
1069
2427
|
};
|
|
2428
|
+
function normalizeStoredAdjustments(stored) {
|
|
2429
|
+
if (!stored) return { ...DEFAULT_ADJUSTMENTS };
|
|
2430
|
+
const out = {
|
|
2431
|
+
...DEFAULT_ADJUSTMENTS,
|
|
2432
|
+
...stored,
|
|
2433
|
+
process: normalizeProcessVersion(stored.process)
|
|
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
|
+
};
|
|
2454
|
+
}
|
|
1070
2455
|
var DEFAULT_TRANSFORM = {
|
|
1071
2456
|
rotate: 0,
|
|
1072
2457
|
flipH: false,
|
|
@@ -1083,39 +2468,40 @@ var DEFAULT_DEPTH_MASK = {
|
|
|
1083
2468
|
var ImageEditorController = (_class3 = class {
|
|
1084
2469
|
|
|
1085
2470
|
|
|
1086
|
-
|
|
1087
|
-
|
|
2471
|
+
__init8() {this.imageElement = null}
|
|
2472
|
+
__init9() {this.listeners = /* @__PURE__ */ new Set()}
|
|
2473
|
+
__init10() {this.histogramListeners = /* @__PURE__ */ new Set()}
|
|
1088
2474
|
|
|
1089
2475
|
// History stacks
|
|
1090
|
-
|
|
1091
|
-
|
|
2476
|
+
__init11() {this.undoStack = []}
|
|
2477
|
+
__init12() {this.redoStack = []}
|
|
1092
2478
|
|
|
1093
|
-
|
|
2479
|
+
__init13() {this.isApplyingHistory = false}
|
|
1094
2480
|
|
|
1095
|
-
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);
|
|
1096
2482
|
this.maxHistoryDepth = _nullishCoalesce(options.maxHistoryDepth, () => ( 50));
|
|
1097
2483
|
this.onStateChange = options.onStateChange;
|
|
1098
2484
|
this.renderer = new ImageEditorRenderer();
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
..._optionalChain([options, 'access', _9 => _9.initialState, 'optionalAccess', _10 => _10.adjustments])
|
|
2485
|
+
this.renderer.onPreviewHistogram = (histogram) => {
|
|
2486
|
+
this.histogramListeners.forEach((l) => l(histogram));
|
|
1102
2487
|
};
|
|
2488
|
+
const initialAdjustments = normalizeStoredAdjustments(_optionalChain([options, 'access', _22 => _22.initialState, 'optionalAccess', _23 => _23.adjustments]));
|
|
1103
2489
|
const initialTransform = {
|
|
1104
2490
|
...DEFAULT_TRANSFORM,
|
|
1105
|
-
..._optionalChain([options, 'access',
|
|
2491
|
+
..._optionalChain([options, 'access', _24 => _24.initialState, 'optionalAccess', _25 => _25.transform])
|
|
1106
2492
|
};
|
|
1107
2493
|
const initialDepthMask = {
|
|
1108
2494
|
...DEFAULT_DEPTH_MASK,
|
|
1109
|
-
..._optionalChain([options, 'access',
|
|
2495
|
+
..._optionalChain([options, 'access', _26 => _26.initialState, 'optionalAccess', _27 => _27.depthMask])
|
|
1110
2496
|
};
|
|
1111
2497
|
this.state = {
|
|
1112
2498
|
sourceUrl: typeof options.image === "string" ? options.image : null,
|
|
1113
2499
|
imageDimensions: { width: 800, height: 600 },
|
|
1114
|
-
crop: _nullishCoalesce(_optionalChain([options, 'access',
|
|
2500
|
+
crop: _nullishCoalesce(_optionalChain([options, 'access', _28 => _28.initialState, 'optionalAccess', _29 => _29.crop]), () => ( null)),
|
|
1115
2501
|
transform: initialTransform,
|
|
1116
2502
|
adjustments: initialAdjustments,
|
|
1117
2503
|
depthMask: initialDepthMask,
|
|
1118
|
-
annotations: _nullishCoalesce(_optionalChain([options, 'access',
|
|
2504
|
+
annotations: _nullishCoalesce(_optionalChain([options, 'access', _30 => _30.initialState, 'optionalAccess', _31 => _31.annotations]), () => ( [])),
|
|
1119
2505
|
selectedAnnotationId: null,
|
|
1120
2506
|
activeTool: "select",
|
|
1121
2507
|
zoom: 1,
|
|
@@ -1162,7 +2548,7 @@ var ImageEditorController = (_class3 = class {
|
|
|
1162
2548
|
height: img.naturalHeight || img.height || 600
|
|
1163
2549
|
}
|
|
1164
2550
|
};
|
|
1165
|
-
} catch (
|
|
2551
|
+
} catch (e6) {
|
|
1166
2552
|
return { sourceUrl: src, element: null, dimensions: { ...this.state.imageDimensions } };
|
|
1167
2553
|
}
|
|
1168
2554
|
}
|
|
@@ -1191,7 +2577,7 @@ var ImageEditorController = (_class3 = class {
|
|
|
1191
2577
|
}
|
|
1192
2578
|
notify() {
|
|
1193
2579
|
const currentState = { ...this.state };
|
|
1194
|
-
_optionalChain([this, 'access',
|
|
2580
|
+
_optionalChain([this, 'access', _32 => _32.onStateChange, 'optionalCall', _33 => _33(currentState)]);
|
|
1195
2581
|
this.listeners.forEach((l) => l(currentState));
|
|
1196
2582
|
}
|
|
1197
2583
|
// --- History (Undo / Redo / Reset) ---
|
|
@@ -1340,16 +2726,154 @@ var ImageEditorController = (_class3 = class {
|
|
|
1340
2726
|
if (commitToHistory) {
|
|
1341
2727
|
this.recordHistory();
|
|
1342
2728
|
}
|
|
1343
|
-
|
|
2729
|
+
const next = {
|
|
1344
2730
|
...this.state.adjustments,
|
|
1345
2731
|
...partial
|
|
1346
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;
|
|
1347
2739
|
this.notify();
|
|
1348
2740
|
}
|
|
2741
|
+
/** True when the adjustments use an older rendering process than {@link LATEST_PROCESS_VERSION}. */
|
|
2742
|
+
get isProcessOutdated() {
|
|
2743
|
+
return normalizeProcessVersion(this.state.adjustments.process) < LATEST_PROCESS_VERSION;
|
|
2744
|
+
}
|
|
2745
|
+
/**
|
|
2746
|
+
* Update to Current Process: re-interprets the current adjustment values with the latest
|
|
2747
|
+
* rendering process (one undo step). Returns false when already current.
|
|
2748
|
+
*/
|
|
2749
|
+
upgradeProcess() {
|
|
2750
|
+
if (!this.isProcessOutdated) return false;
|
|
2751
|
+
this.setAdjustments({ process: LATEST_PROCESS_VERSION });
|
|
2752
|
+
return true;
|
|
2753
|
+
}
|
|
2754
|
+
/** Resets the base sliders (latest process). Adjustment layers are kept; see `clearAdjustmentLayers`. */
|
|
1349
2755
|
resetAdjustments() {
|
|
1350
2756
|
this.recordHistory();
|
|
2757
|
+
const layers = this.state.adjustments.layers;
|
|
1351
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);
|
|
1352
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([]);
|
|
2860
|
+
this.notify();
|
|
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
|
+
};
|
|
1353
2877
|
}
|
|
1354
2878
|
// --- Depth Masking (Gleamforge) ---
|
|
1355
2879
|
setDepthMask(partial) {
|
|
@@ -1436,7 +2960,7 @@ var ImageEditorController = (_class3 = class {
|
|
|
1436
2960
|
if (!source) return;
|
|
1437
2961
|
try {
|
|
1438
2962
|
await this.renderer.renderToCanvas(targetCanvas, source, this.state, options);
|
|
1439
|
-
} catch (
|
|
2963
|
+
} catch (e7) {
|
|
1440
2964
|
}
|
|
1441
2965
|
}
|
|
1442
2966
|
async renderExport(options) {
|
|
@@ -1556,15 +3080,16 @@ var ImageEditorController = (_class3 = class {
|
|
|
1556
3080
|
}
|
|
1557
3081
|
loadJSON(data) {
|
|
1558
3082
|
this.recordHistory();
|
|
1559
|
-
this.applySerializedState(data);
|
|
3083
|
+
this.applySerializedState(migrateSerializedImageEditorState(data));
|
|
1560
3084
|
this.notify();
|
|
1561
3085
|
}
|
|
1562
3086
|
serializeState() {
|
|
1563
3087
|
return {
|
|
1564
|
-
|
|
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,
|
|
1565
3090
|
crop: this.state.crop ? { ...this.state.crop } : null,
|
|
1566
3091
|
transform: { ...this.state.transform },
|
|
1567
|
-
adjustments:
|
|
3092
|
+
adjustments: serializeAdjustments(this.state.adjustments),
|
|
1568
3093
|
depthMask: {
|
|
1569
3094
|
...this.state.depthMask,
|
|
1570
3095
|
depthRange: [...this.state.depthMask.depthRange]
|
|
@@ -1575,7 +3100,7 @@ var ImageEditorController = (_class3 = class {
|
|
|
1575
3100
|
applySerializedState(data) {
|
|
1576
3101
|
this.state.crop = data.crop ? { ...data.crop } : null;
|
|
1577
3102
|
this.state.transform = { ...data.transform };
|
|
1578
|
-
this.state.adjustments =
|
|
3103
|
+
this.state.adjustments = normalizeStoredAdjustments(data.adjustments);
|
|
1579
3104
|
this.state.depthMask = {
|
|
1580
3105
|
...data.depthMask,
|
|
1581
3106
|
depthRange: [...data.depthMask.depthRange]
|
|
@@ -1583,6 +3108,16 @@ var ImageEditorController = (_class3 = class {
|
|
|
1583
3108
|
this.state.annotations = JSON.parse(JSON.stringify(data.annotations || []));
|
|
1584
3109
|
}
|
|
1585
3110
|
}, _class3);
|
|
3111
|
+
function serializeAdjustments(adjustments) {
|
|
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;
|
|
3115
|
+
if (normalizeProcessVersion(out.process) === LEGACY_PROCESS_VERSION) {
|
|
3116
|
+
delete out.process;
|
|
3117
|
+
if (!out.tint) delete out.tint;
|
|
3118
|
+
}
|
|
3119
|
+
return out;
|
|
3120
|
+
}
|
|
1586
3121
|
function scaleAnnotation(ann, sx, sy) {
|
|
1587
3122
|
const k = Math.sqrt(Math.abs(sx * sy));
|
|
1588
3123
|
const base = { ...ann, x: ann.x * sx, y: ann.y * sy };
|
|
@@ -1603,6 +3138,677 @@ function scaleAnnotation(ann, sx, sy) {
|
|
|
1603
3138
|
}
|
|
1604
3139
|
}
|
|
1605
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
|
+
|
|
3778
|
+
|
|
3779
|
+
|
|
3780
|
+
|
|
3781
|
+
|
|
3782
|
+
|
|
3783
|
+
|
|
3784
|
+
|
|
3785
|
+
|
|
3786
|
+
|
|
3787
|
+
|
|
3788
|
+
|
|
3789
|
+
|
|
3790
|
+
|
|
3791
|
+
|
|
3792
|
+
|
|
3793
|
+
|
|
3794
|
+
|
|
3795
|
+
|
|
3796
|
+
|
|
3797
|
+
|
|
3798
|
+
|
|
3799
|
+
|
|
3800
|
+
|
|
3801
|
+
|
|
3802
|
+
|
|
3803
|
+
|
|
3804
|
+
|
|
3805
|
+
|
|
3806
|
+
|
|
3807
|
+
|
|
3808
|
+
|
|
3809
|
+
|
|
3810
|
+
|
|
3811
|
+
|
|
1606
3812
|
|
|
1607
3813
|
|
|
1608
3814
|
|
|
@@ -1630,5 +3836,5 @@ function scaleAnnotation(ann, sx, sy) {
|
|
|
1630
3836
|
|
|
1631
3837
|
|
|
1632
3838
|
|
|
1633
|
-
exports.DEFAULT_ADJUSTMENTS = DEFAULT_ADJUSTMENTS; exports.DEFAULT_DEPTH_MASK = DEFAULT_DEPTH_MASK; exports.DEFAULT_TRANSFORM = DEFAULT_TRANSFORM; exports.ImageEditorController = ImageEditorController; exports.ImageEditorRenderer = ImageEditorRenderer; exports.applyAdjustments = applyAdjustments; exports.applyDepthMask = applyDepthMask; exports.applyImageProjectionToContext = applyImageProjectionToContext; 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.isQuarterTurnSwapped = isQuarterTurnSwapped; exports.mapCropHandle = mapCropHandle; exports.normalizeAngle = normalizeAngle; exports.projectImagePoint = projectImagePoint; exports.projectImageRect = projectImageRect; exports.removeAlpha = removeAlpha; exports.unprojectCanvasPoint = unprojectCanvasPoint;
|
|
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;
|
|
1634
3840
|
//# sourceMappingURL=index.cjs.map
|