@pixerate/editor 0.259.0 → 0.260.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/dist/agent/index.cjs +2 -2
- package/dist/agent/index.js +1 -1
- package/dist/animation-editor/index.cjs +61 -59
- package/dist/animation-editor/index.js +5 -3
- package/dist/brushes/index.cjs +52 -52
- package/dist/brushes/index.js +6 -6
- package/dist/{brushstrokeRender-JI4W6F2G.js → brushstrokeRender-25Q5BEY6.js} +17 -7
- package/dist/{brushstrokeRender-MQ6PSDM5.cjs → brushstrokeRender-N6UPZSQS.cjs} +31 -21
- package/dist/canvas/brushTypes.d.cts +5 -0
- package/dist/canvas/brushTypes.d.ts +5 -0
- package/dist/canvas/drawController.d.cts +15 -1
- package/dist/canvas/drawController.d.ts +15 -1
- package/dist/canvas/index.cjs +136 -102
- package/dist/canvas/index.js +35 -1
- package/dist/{chunk-3PPJRMIG.js → chunk-3O47TPGX.js} +197 -18
- package/dist/{chunk-5Q5MLXAD.cjs → chunk-3TQW5MPW.cjs} +23 -23
- package/dist/{chunk-JBBJE76O.cjs → chunk-3ZRNK3RO.cjs} +1138 -85
- package/dist/{chunk-YSGW7VV4.cjs → chunk-43NT242D.cjs} +1401 -228
- package/dist/{chunk-SMTUQWGY.js → chunk-5IW65VQ6.js} +1 -1
- package/dist/{chunk-YQ4PQTBB.js → chunk-6NZL24W4.js} +9 -1
- package/dist/{chunk-O3ZVZSNJ.cjs → chunk-7AUQ2UJ3.cjs} +137 -129
- package/dist/{chunk-4M4E3N6X.js → chunk-CQVTGBQI.js} +1 -1
- package/dist/{chunk-SSFMWJDK.js → chunk-D4TE5JNW.js} +1192 -19
- package/dist/{chunk-AORZ5M5C.js → chunk-EHALWBBD.js} +1 -1
- package/dist/{chunk-6WCQ35GI.cjs → chunk-NHA26O5X.cjs} +2 -2
- package/dist/{chunk-DPYAO5U6.cjs → chunk-PVJMTLPJ.cjs} +197 -18
- package/dist/{chunk-MQGQTKVW.cjs → chunk-PYTJBVW5.cjs} +29 -29
- package/dist/{chunk-IXQZHCOF.js → chunk-QAR2N7HZ.js} +1 -1
- package/dist/{chunk-5P4JEOIV.js → chunk-TMDDMJCR.js} +2 -2
- package/dist/{chunk-RSIKDWOP.cjs → chunk-TZOX7KWV.cjs} +15 -15
- package/dist/{chunk-IPUTIUGQ.cjs → chunk-UVZ7RQOE.cjs} +1 -1
- package/dist/{chunk-A7GJ6BXI.js → chunk-WJ4WQJCC.js} +1062 -9
- package/dist/{chunk-ICTPLVSR.js → chunk-X2EXYY4J.js} +1 -1
- package/dist/{chunk-WQCNC4DK.js → chunk-XC6DPRYI.js} +695 -4
- package/dist/{chunk-KBYB7OWB.cjs → chunk-YHDHFJUC.cjs} +776 -85
- package/dist/{chunk-P3PZ63M3.cjs → chunk-YLUIFXSL.cjs} +35 -35
- package/dist/commands/index.cjs +2 -2
- package/dist/commands/index.js +1 -1
- package/dist/image-editor/index.cjs +1385 -2484
- package/dist/image-editor/index.js +139 -1238
- package/dist/image-editor/selection-worker.cjs +3 -4
- package/dist/image-editor/selection-worker.js +1 -2
- package/dist/paint/index.cjs +3 -5
- package/dist/paint/index.js +40 -42
- package/dist/paint-editor/contract.d.cts +29 -0
- package/dist/paint-editor/contract.d.ts +29 -0
- package/dist/paint-editor/dab-stroke.d.cts +5 -0
- package/dist/paint-editor/dab-stroke.d.ts +5 -0
- package/dist/paint-editor/index.cjs +402 -258
- package/dist/paint-editor/index.d.cts +1 -0
- package/dist/paint-editor/index.d.ts +1 -0
- package/dist/paint-editor/index.js +197 -53
- package/dist/paint-editor/stamp-tip.d.cts +17 -0
- package/dist/paint-editor/stamp-tip.d.ts +17 -0
- package/dist/paint-editor/ui.d.cts +21 -1
- package/dist/paint-editor/ui.d.ts +21 -1
- package/dist/raster/index.cjs +2 -4
- package/dist/raster/index.js +6 -8
- package/dist/vector/brushstroke.d.cts +6 -0
- package/dist/vector/brushstroke.d.ts +6 -0
- package/dist/vector/controller.d.cts +11 -2
- package/dist/vector/controller.d.ts +11 -2
- package/dist/vector/index.cjs +990 -973
- package/dist/vector/index.js +28 -11
- package/package.json +1 -1
- package/skills/pixerate-brushes/SKILL.md +1 -1
- package/skills/pixerate-canvas/SKILL.md +3 -1
- package/skills/pixerate-vector/SKILL.md +2 -2
- package/dist/chunk-2VJAVXLI.js +0 -192
- package/dist/chunk-62Z5WLRA.js +0 -700
- package/dist/chunk-BOHDJJ2O.cjs +0 -192
- package/dist/chunk-NKGB64CW.cjs +0 -700
- package/dist/chunk-SS53NNZO.cjs +0 -1066
- package/dist/chunk-ZNA3IQZF.js +0 -1066
|
@@ -1,4 +1,9 @@
|
|
|
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;
|
|
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 _chunkK6PXS6DOcjs = require('./chunk-K6PXS6DO.cjs');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
var _chunkBB6TR422cjs = require('./chunk-BB6TR422.cjs');
|
|
2
7
|
|
|
3
8
|
|
|
4
9
|
|
|
@@ -7,10 +12,1012 @@
|
|
|
7
12
|
|
|
8
13
|
|
|
9
14
|
|
|
10
|
-
var _chunkSS53NNZOcjs = require('./chunk-SS53NNZO.cjs');
|
|
11
15
|
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
|
|
18
|
+
var _chunk474IQG74cjs = require('./chunk-474IQG74.cjs');
|
|
19
|
+
|
|
20
|
+
// src/image-editor/selection/mask.ts
|
|
21
|
+
var SELECTION_MODES = ["replace", "add", "subtract", "intersect"];
|
|
22
|
+
function isSelectionMode(value) {
|
|
23
|
+
return typeof value === "string" && SELECTION_MODES.includes(value);
|
|
24
|
+
}
|
|
25
|
+
function parseSelectionMode(value) {
|
|
26
|
+
return isSelectionMode(value) ? value : "replace";
|
|
27
|
+
}
|
|
28
|
+
var MAX_SIDE = 1 << 15;
|
|
29
|
+
function checkSize(width, height) {
|
|
30
|
+
if (!Number.isInteger(width) || !Number.isInteger(height) || width < 1 || height < 1 || width > MAX_SIDE || height > MAX_SIDE) {
|
|
31
|
+
throw new Error(`Invalid selection size ${width}\xD7${height}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function computeSelectionBounds(width, height, data) {
|
|
35
|
+
let x0 = width;
|
|
36
|
+
let x1 = -1;
|
|
37
|
+
let y0 = -1;
|
|
38
|
+
let y1 = -1;
|
|
39
|
+
for (let y = 0; y < height; y++) {
|
|
40
|
+
const row = y * width;
|
|
41
|
+
let first = -1;
|
|
42
|
+
for (let x = 0; x < width; x++) {
|
|
43
|
+
if (data[row + x] !== 0) {
|
|
44
|
+
first = x;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (first < 0) continue;
|
|
49
|
+
let last = first;
|
|
50
|
+
for (let x = width - 1; x > first; x--) {
|
|
51
|
+
if (data[row + x] !== 0) {
|
|
52
|
+
last = x;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (y0 < 0) y0 = y;
|
|
57
|
+
y1 = y;
|
|
58
|
+
if (first < x0) x0 = first;
|
|
59
|
+
if (last > x1) x1 = last;
|
|
60
|
+
}
|
|
61
|
+
if (y0 < 0) return null;
|
|
62
|
+
return { x: x0, y: y0, width: x1 - x0 + 1, height: y1 - y0 + 1 };
|
|
63
|
+
}
|
|
64
|
+
var TILE_PIXELS = _chunk474IQG74cjs.TILE * _chunk474IQG74cjs.TILE;
|
|
65
|
+
var MATERIALIZED_LIMIT = 4;
|
|
66
|
+
var materialized = /* @__PURE__ */ new Map();
|
|
67
|
+
var lastRead = null;
|
|
68
|
+
var lastData = null;
|
|
69
|
+
var TiledSelectionMask = (_class = class {
|
|
70
|
+
constructor(width, height, surface, bounds, data) {;_class.prototype.__init.call(this);
|
|
71
|
+
this.width = width;
|
|
72
|
+
this.height = height;
|
|
73
|
+
this.surface = surface;
|
|
74
|
+
this.cachedBounds = bounds;
|
|
75
|
+
if (data) this.remember(data);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
__init() {this.weak = null}
|
|
82
|
+
get bounds() {
|
|
83
|
+
if (this.cachedBounds === void 0) this.cachedBounds = _chunk474IQG74cjs.contentBounds.call(void 0, this.surface);
|
|
84
|
+
return this.cachedBounds;
|
|
85
|
+
}
|
|
86
|
+
get data() {
|
|
87
|
+
if (lastRead === this) return lastData;
|
|
88
|
+
let d = materialized.get(this);
|
|
89
|
+
if (d) {
|
|
90
|
+
materialized.delete(this);
|
|
91
|
+
materialized.set(this, d);
|
|
92
|
+
lastRead = this;
|
|
93
|
+
lastData = d;
|
|
94
|
+
return d;
|
|
95
|
+
}
|
|
96
|
+
d = _optionalChain([this, 'access', _ => _.weak, 'optionalAccess', _2 => _2.deref, 'call', _3 => _3()]);
|
|
97
|
+
if (!d) {
|
|
98
|
+
const all = _chunk474IQG74cjs.readRegion.call(void 0, this.surface, { x: 0, y: 0, width: this.width, height: this.height });
|
|
99
|
+
d = new Uint8Array(all.buffer, all.byteOffset, all.length);
|
|
100
|
+
}
|
|
101
|
+
this.remember(d);
|
|
102
|
+
return d;
|
|
103
|
+
}
|
|
104
|
+
remember(d) {
|
|
105
|
+
lastRead = this;
|
|
106
|
+
lastData = d;
|
|
107
|
+
this.weak = typeof WeakRef === "function" ? new WeakRef(d) : null;
|
|
108
|
+
materialized.set(this, d);
|
|
109
|
+
while (materialized.size > MATERIALIZED_LIMIT) materialized.delete(materialized.keys().next().value);
|
|
110
|
+
}
|
|
111
|
+
}, _class);
|
|
112
|
+
function tileGrid(width, height) {
|
|
113
|
+
return { cols: Math.ceil(width / _chunk474IQG74cjs.TILE), rows: Math.ceil(height / _chunk474IQG74cjs.TILE) };
|
|
114
|
+
}
|
|
115
|
+
function insideTile(tx, ty, width, height) {
|
|
116
|
+
return (tx + 1) * _chunk474IQG74cjs.TILE <= width && (ty + 1) * _chunk474IQG74cjs.TILE <= height;
|
|
117
|
+
}
|
|
118
|
+
function surfaceFromCoverage(width, height, data) {
|
|
119
|
+
const tiles = /* @__PURE__ */ new Map();
|
|
120
|
+
const { cols, rows } = tileGrid(width, height);
|
|
121
|
+
for (let ty = 0; ty < rows; ty++) {
|
|
122
|
+
for (let tx = 0; tx < cols; tx++) {
|
|
123
|
+
const ox = tx * _chunk474IQG74cjs.TILE;
|
|
124
|
+
const oy = ty * _chunk474IQG74cjs.TILE;
|
|
125
|
+
const w = Math.min(_chunk474IQG74cjs.TILE, width - ox);
|
|
126
|
+
const h = Math.min(_chunk474IQG74cjs.TILE, height - oy);
|
|
127
|
+
let lo = 255;
|
|
128
|
+
let hi = 0;
|
|
129
|
+
for (let y = 0; y < h && !(hi > 0 && lo < hi); y++) {
|
|
130
|
+
const row = (oy + y) * width + ox;
|
|
131
|
+
for (let x = 0; x < w; x++) {
|
|
132
|
+
const v = data[row + x];
|
|
133
|
+
if (v < lo) lo = v;
|
|
134
|
+
if (v > hi) hi = v;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (hi === 0) continue;
|
|
138
|
+
if (lo === hi && w === _chunk474IQG74cjs.TILE && h === _chunk474IQG74cjs.TILE) {
|
|
139
|
+
tiles.set(_chunk474IQG74cjs.tileKey.call(void 0, tx, ty), _chunk474IQG74cjs.uniformTile.call(void 0, 1, hi));
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const t = new Uint8ClampedArray(TILE_PIXELS);
|
|
143
|
+
for (let y = 0; y < h; y++) {
|
|
144
|
+
const row = (oy + y) * width + ox;
|
|
145
|
+
for (let x = 0; x < w; x++) t[y * _chunk474IQG74cjs.TILE + x] = data[row + x];
|
|
146
|
+
}
|
|
147
|
+
tiles.set(_chunk474IQG74cjs.tileKey.call(void 0, tx, ty), { data: t, uniform: null });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return new (0, _chunk474IQG74cjs.TiledSurface)(1, tiles, 0);
|
|
151
|
+
}
|
|
152
|
+
function clipTile(tile, w, h) {
|
|
153
|
+
const d = tile.data;
|
|
154
|
+
let outside = false;
|
|
155
|
+
for (let y = 0; y < _chunk474IQG74cjs.TILE && !outside; y++) {
|
|
156
|
+
for (let x = y < h ? w : 0; x < _chunk474IQG74cjs.TILE; x++) {
|
|
157
|
+
if (d[y * _chunk474IQG74cjs.TILE + x] !== 0) {
|
|
158
|
+
outside = true;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (!outside) return tile;
|
|
164
|
+
const t = new Uint8ClampedArray(TILE_PIXELS);
|
|
165
|
+
for (let y = 0; y < h; y++) t.set(d.subarray(y * _chunk474IQG74cjs.TILE, y * _chunk474IQG74cjs.TILE + w), y * _chunk474IQG74cjs.TILE);
|
|
166
|
+
return _chunk474IQG74cjs.finishTile.call(void 0, t, 1, 0);
|
|
167
|
+
}
|
|
168
|
+
function selectionFromTiles(width, height, surface) {
|
|
169
|
+
checkSize(width, height);
|
|
170
|
+
if (surface.channels !== 1) throw new Error("A selection needs a 1-channel surface");
|
|
171
|
+
if (surface.defaultValue !== 0) {
|
|
172
|
+
const all = _chunk474IQG74cjs.readRegion.call(void 0, surface, { x: 0, y: 0, width, height });
|
|
173
|
+
return createSelectionMask(width, height, new Uint8Array(all.buffer, all.byteOffset, all.length));
|
|
174
|
+
}
|
|
175
|
+
const { cols, rows } = tileGrid(width, height);
|
|
176
|
+
let tiles = null;
|
|
177
|
+
for (const [key, tile] of surface.tiles) {
|
|
178
|
+
const { tx, ty } = _chunk474IQG74cjs.tileCoord.call(void 0, key);
|
|
179
|
+
let next = tile;
|
|
180
|
+
if (tx < 0 || ty < 0 || tx >= cols || ty >= rows) next = null;
|
|
181
|
+
else if (!insideTile(tx, ty, width, height)) next = clipTile(tile, Math.min(_chunk474IQG74cjs.TILE, width - tx * _chunk474IQG74cjs.TILE), Math.min(_chunk474IQG74cjs.TILE, height - ty * _chunk474IQG74cjs.TILE));
|
|
182
|
+
if (next === tile) continue;
|
|
183
|
+
tiles ??= new Map(surface.tiles);
|
|
184
|
+
if (next) tiles.set(key, next);
|
|
185
|
+
else tiles.delete(key);
|
|
186
|
+
}
|
|
187
|
+
return new TiledSelectionMask(width, height, tiles ? new (0, _chunk474IQG74cjs.TiledSurface)(1, tiles, 0) : surface);
|
|
188
|
+
}
|
|
189
|
+
function createSelectionMask(width, height, data) {
|
|
190
|
+
checkSize(width, height);
|
|
191
|
+
if (!data) return new TiledSelectionMask(width, height, new (0, _chunk474IQG74cjs.TiledSurface)(1, /* @__PURE__ */ new Map(), 0), null);
|
|
192
|
+
if (data.length !== width * height) throw new Error("Selection data does not match its size");
|
|
193
|
+
return new TiledSelectionMask(width, height, surfaceFromCoverage(width, height, data), void 0, data);
|
|
194
|
+
}
|
|
195
|
+
function selectionFromRegion(width, height, rect, data) {
|
|
196
|
+
checkSize(width, height);
|
|
197
|
+
if (rect.x < 0 || rect.y < 0 || rect.x + rect.width > width || rect.y + rect.height > height) throw new Error("The region lies outside the selection");
|
|
198
|
+
return new TiledSelectionMask(width, height, _chunk474IQG74cjs.writeRegion.call(void 0, new (0, _chunk474IQG74cjs.TiledSurface)(1, /* @__PURE__ */ new Map(), 0), rect, data));
|
|
199
|
+
}
|
|
200
|
+
function fullSelection(width, height) {
|
|
201
|
+
checkSize(width, height);
|
|
202
|
+
const tiles = /* @__PURE__ */ new Map();
|
|
203
|
+
const { cols, rows } = tileGrid(width, height);
|
|
204
|
+
for (let ty = 0; ty < rows; ty++) {
|
|
205
|
+
for (let tx = 0; tx < cols; tx++) {
|
|
206
|
+
if (insideTile(tx, ty, width, height)) {
|
|
207
|
+
tiles.set(_chunk474IQG74cjs.tileKey.call(void 0, tx, ty), _chunk474IQG74cjs.uniformTile.call(void 0, 1, 255));
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
const t = new Uint8ClampedArray(TILE_PIXELS);
|
|
211
|
+
const w = Math.min(_chunk474IQG74cjs.TILE, width - tx * _chunk474IQG74cjs.TILE);
|
|
212
|
+
for (let y = 0; y < Math.min(_chunk474IQG74cjs.TILE, height - ty * _chunk474IQG74cjs.TILE); y++) t.fill(255, y * _chunk474IQG74cjs.TILE, y * _chunk474IQG74cjs.TILE + w);
|
|
213
|
+
tiles.set(_chunk474IQG74cjs.tileKey.call(void 0, tx, ty), { data: t, uniform: null });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return new TiledSelectionMask(width, height, new (0, _chunk474IQG74cjs.TiledSurface)(1, tiles, 0), { x: 0, y: 0, width, height });
|
|
217
|
+
}
|
|
218
|
+
function isSelectionEmpty(mask) {
|
|
219
|
+
return !mask || mask.bounds === null;
|
|
220
|
+
}
|
|
221
|
+
function selectionValueAt(mask, x, y) {
|
|
222
|
+
const xi = Math.floor(x);
|
|
223
|
+
const yi = Math.floor(y);
|
|
224
|
+
if (xi < 0 || yi < 0 || xi >= mask.width || yi >= mask.height) return 0;
|
|
225
|
+
return _chunk474IQG74cjs.readPixel.call(void 0, mask.surface, xi, yi)[0] / 255;
|
|
226
|
+
}
|
|
227
|
+
function coverageToByte(v) {
|
|
228
|
+
if (!(v > 0)) return 0;
|
|
229
|
+
if (v >= 1) return 255;
|
|
230
|
+
return Math.floor(v * 255 + 0.5);
|
|
231
|
+
}
|
|
232
|
+
function selectionFromCoverage(width, height, coverage) {
|
|
233
|
+
const n = width * height;
|
|
234
|
+
const out = new Uint8Array(n);
|
|
235
|
+
for (let i = 0; i < n; i++) out[i] = coverageToByte(coverage[i]);
|
|
236
|
+
return createSelectionMask(width, height, out);
|
|
237
|
+
}
|
|
238
|
+
function selectionToCoverage(mask) {
|
|
239
|
+
const out = new Float32Array(mask.data.length);
|
|
240
|
+
for (let i = 0; i < out.length; i++) out[i] = mask.data[i] / 255;
|
|
241
|
+
return out;
|
|
242
|
+
}
|
|
243
|
+
function selectionArea(mask) {
|
|
244
|
+
if (!mask || !mask.bounds) return 0;
|
|
245
|
+
let sum = 0;
|
|
246
|
+
for (const tile of mask.surface.tiles.values()) {
|
|
247
|
+
if (tile.uniform !== null) {
|
|
248
|
+
sum += tile.uniform * TILE_PIXELS;
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
const d = tile.data;
|
|
252
|
+
for (let i = 0; i < d.length; i++) sum += d[i];
|
|
253
|
+
}
|
|
254
|
+
return sum / 255;
|
|
255
|
+
}
|
|
256
|
+
function combineSelections(prev, next, mode) {
|
|
257
|
+
let result;
|
|
258
|
+
if (mode === "replace" || !prev && mode === "add") {
|
|
259
|
+
result = next;
|
|
260
|
+
} else if (!prev) {
|
|
261
|
+
return null;
|
|
262
|
+
} else {
|
|
263
|
+
if (prev.width !== next.width || prev.height !== next.height) throw new Error("Selections differ in size");
|
|
264
|
+
result = new TiledSelectionMask(prev.width, prev.height, combineSurfaces(prev.surface, next.surface, mode));
|
|
265
|
+
}
|
|
266
|
+
return result.bounds ? result : null;
|
|
267
|
+
}
|
|
268
|
+
var FULL = 255;
|
|
269
|
+
function combineTile(a, b, mode) {
|
|
270
|
+
if (mode === "add") {
|
|
271
|
+
if (!b || _optionalChain([a, 'optionalAccess', _4 => _4.uniform]) === FULL) return a;
|
|
272
|
+
if (!a || b.uniform === FULL) return b;
|
|
273
|
+
} else if (mode === "subtract") {
|
|
274
|
+
if (!a || !b) return a;
|
|
275
|
+
if (b.uniform === FULL) return void 0;
|
|
276
|
+
} else {
|
|
277
|
+
if (!a || !b) return void 0;
|
|
278
|
+
if (a.uniform === FULL) return b;
|
|
279
|
+
if (b.uniform === FULL) return a;
|
|
280
|
+
}
|
|
281
|
+
const x = a.data;
|
|
282
|
+
const y = b.data;
|
|
283
|
+
const out = new Uint8ClampedArray(TILE_PIXELS);
|
|
284
|
+
if (mode === "add") for (let i = 0; i < out.length; i++) out[i] = x[i] > y[i] ? x[i] : y[i];
|
|
285
|
+
else if (mode === "subtract") for (let i = 0; i < out.length; i++) out[i] = x[i] > y[i] ? x[i] - y[i] : 0;
|
|
286
|
+
else for (let i = 0; i < out.length; i++) out[i] = x[i] < y[i] ? x[i] : y[i];
|
|
287
|
+
return _nullishCoalesce(_chunk474IQG74cjs.finishTile.call(void 0, out, 1, 0), () => ( void 0));
|
|
288
|
+
}
|
|
289
|
+
function combineSurfaces(a, b, mode) {
|
|
290
|
+
const tiles = /* @__PURE__ */ new Map();
|
|
291
|
+
for (const key of /* @__PURE__ */ new Set([...a.tiles.keys(), ...b.tiles.keys()])) {
|
|
292
|
+
const tile = combineTile(a.tiles.get(key), b.tiles.get(key), mode);
|
|
293
|
+
if (tile) tiles.set(key, tile);
|
|
294
|
+
}
|
|
295
|
+
return new (0, _chunk474IQG74cjs.TiledSurface)(1, tiles, 0);
|
|
296
|
+
}
|
|
297
|
+
function invertSelection(mask) {
|
|
298
|
+
const { width, height } = mask;
|
|
299
|
+
const tiles = /* @__PURE__ */ new Map();
|
|
300
|
+
const { cols, rows } = tileGrid(width, height);
|
|
301
|
+
for (let ty = 0; ty < rows; ty++) {
|
|
302
|
+
for (let tx = 0; tx < cols; tx++) {
|
|
303
|
+
const src = mask.surface.getTile(tx, ty);
|
|
304
|
+
const key = _chunk474IQG74cjs.tileKey.call(void 0, tx, ty);
|
|
305
|
+
if (insideTile(tx, ty, width, height)) {
|
|
306
|
+
if (!src) tiles.set(key, _chunk474IQG74cjs.uniformTile.call(void 0, 1, FULL));
|
|
307
|
+
else if (src.uniform !== null) {
|
|
308
|
+
if (src.uniform !== FULL) tiles.set(key, _chunk474IQG74cjs.uniformTile.call(void 0, 1, FULL - src.uniform));
|
|
309
|
+
} else {
|
|
310
|
+
const out2 = new Uint8ClampedArray(TILE_PIXELS);
|
|
311
|
+
for (let i = 0; i < out2.length; i++) out2[i] = FULL - src.data[i];
|
|
312
|
+
const t2 = _chunk474IQG74cjs.finishTile.call(void 0, out2, 1, 0);
|
|
313
|
+
if (t2) tiles.set(key, t2);
|
|
314
|
+
}
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
const w = Math.min(_chunk474IQG74cjs.TILE, width - tx * _chunk474IQG74cjs.TILE);
|
|
318
|
+
const h = Math.min(_chunk474IQG74cjs.TILE, height - ty * _chunk474IQG74cjs.TILE);
|
|
319
|
+
const out = new Uint8ClampedArray(TILE_PIXELS);
|
|
320
|
+
for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) out[y * _chunk474IQG74cjs.TILE + x] = FULL - (src ? src.data[y * _chunk474IQG74cjs.TILE + x] : 0);
|
|
321
|
+
const t = _chunk474IQG74cjs.finishTile.call(void 0, out, 1, 0);
|
|
322
|
+
if (t) tiles.set(key, t);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return new TiledSelectionMask(width, height, new (0, _chunk474IQG74cjs.TiledSurface)(1, tiles, 0));
|
|
326
|
+
}
|
|
327
|
+
function resizeSelection(mask, width, height) {
|
|
328
|
+
if (mask.width === width && mask.height === height) return mask;
|
|
329
|
+
checkSize(width, height);
|
|
330
|
+
const out = new Uint8Array(width * height);
|
|
331
|
+
const sx = mask.width / width;
|
|
332
|
+
const sy = mask.height / height;
|
|
333
|
+
const src = mask.data;
|
|
334
|
+
const mw = mask.width;
|
|
335
|
+
for (let y = 0; y < height; y++) {
|
|
336
|
+
const fy = Math.min(mask.height - 1, Math.max(0, (y + 0.5) * sy - 0.5));
|
|
337
|
+
const y0 = Math.floor(fy);
|
|
338
|
+
const y1 = Math.min(mask.height - 1, y0 + 1);
|
|
339
|
+
const ty = fy - y0;
|
|
340
|
+
for (let x = 0; x < width; x++) {
|
|
341
|
+
const fx = Math.min(mw - 1, Math.max(0, (x + 0.5) * sx - 0.5));
|
|
342
|
+
const x0 = Math.floor(fx);
|
|
343
|
+
const x1 = Math.min(mw - 1, x0 + 1);
|
|
344
|
+
const tx = fx - x0;
|
|
345
|
+
const top = src[y0 * mw + x0] * (1 - tx) + src[y0 * mw + x1] * tx;
|
|
346
|
+
const bot = src[y1 * mw + x0] * (1 - tx) + src[y1 * mw + x1] * tx;
|
|
347
|
+
out[y * width + x] = Math.round(top * (1 - ty) + bot * ty);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return createSelectionMask(width, height, out);
|
|
351
|
+
}
|
|
352
|
+
function selectionFromPixels(pixels, options = {}) {
|
|
353
|
+
const { width, height, data } = pixels;
|
|
354
|
+
checkSize(width, height);
|
|
355
|
+
if (data.length < width * height * 4) throw new Error("Pixel data is shorter than width \xD7 height \xD7 4");
|
|
356
|
+
const channel = _nullishCoalesce(options.channel, () => ( "luminance"));
|
|
357
|
+
const out = new Uint8Array(width * height);
|
|
358
|
+
const threshold = options.threshold;
|
|
359
|
+
for (let i = 0, p = 0; i < out.length; i++, p += 4) {
|
|
360
|
+
let v;
|
|
361
|
+
switch (channel) {
|
|
362
|
+
case "alpha":
|
|
363
|
+
v = data[p + 3];
|
|
364
|
+
break;
|
|
365
|
+
case "red":
|
|
366
|
+
v = data[p];
|
|
367
|
+
break;
|
|
368
|
+
case "green":
|
|
369
|
+
v = data[p + 1];
|
|
370
|
+
break;
|
|
371
|
+
case "blue":
|
|
372
|
+
v = data[p + 2];
|
|
373
|
+
break;
|
|
374
|
+
default:
|
|
375
|
+
v = Math.round((0.2126 * data[p] + 0.7152 * data[p + 1] + 0.0722 * data[p + 2]) * data[p + 3] / 255);
|
|
376
|
+
}
|
|
377
|
+
if (options.invert) v = 255 - v;
|
|
378
|
+
if (threshold !== void 0) v = v >= threshold ? 255 : 0;
|
|
379
|
+
out[i] = v;
|
|
380
|
+
}
|
|
381
|
+
return createSelectionMask(width, height, out);
|
|
382
|
+
}
|
|
383
|
+
function selectionToPixels(mask, as = "gray") {
|
|
384
|
+
const out = new Uint8ClampedArray(mask.data.length * 4);
|
|
385
|
+
for (let i = 0, p = 0; i < mask.data.length; i++, p += 4) {
|
|
386
|
+
const v = mask.data[i];
|
|
387
|
+
if (as === "gray") {
|
|
388
|
+
out[p] = out[p + 1] = out[p + 2] = v;
|
|
389
|
+
out[p + 3] = 255;
|
|
390
|
+
} else {
|
|
391
|
+
out[p] = out[p + 1] = out[p + 2] = 255;
|
|
392
|
+
out[p + 3] = v;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return { width: mask.width, height: mask.height, data: out };
|
|
396
|
+
}
|
|
397
|
+
var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
398
|
+
var B64_INDEX = (() => {
|
|
399
|
+
const t = new Int16Array(128).fill(-1);
|
|
400
|
+
for (let i = 0; i < B64.length; i++) t[B64.charCodeAt(i)] = i;
|
|
401
|
+
return t;
|
|
402
|
+
})();
|
|
403
|
+
function toBase64(bytes, length) {
|
|
404
|
+
const parts = [];
|
|
405
|
+
let chunk = "";
|
|
406
|
+
for (let i = 0; i < length; i += 3) {
|
|
407
|
+
const a = bytes[i];
|
|
408
|
+
const b = i + 1 < length ? bytes[i + 1] : 0;
|
|
409
|
+
const c = i + 2 < length ? bytes[i + 2] : 0;
|
|
410
|
+
chunk += B64[a >> 2] + B64[(a & 3) << 4 | b >> 4];
|
|
411
|
+
chunk += i + 1 < length ? B64[(b & 15) << 2 | c >> 6] : "=";
|
|
412
|
+
chunk += i + 2 < length ? B64[c & 63] : "=";
|
|
413
|
+
if (chunk.length >= 8192) {
|
|
414
|
+
parts.push(chunk);
|
|
415
|
+
chunk = "";
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
parts.push(chunk);
|
|
419
|
+
return parts.join("");
|
|
420
|
+
}
|
|
421
|
+
function fromBase64(text) {
|
|
422
|
+
if (text.length % 4 !== 0) throw new Error("Invalid base64 length");
|
|
423
|
+
let pad = 0;
|
|
424
|
+
if (text.endsWith("==")) pad = 2;
|
|
425
|
+
else if (text.endsWith("=")) pad = 1;
|
|
426
|
+
const out = new Uint8Array(text.length / 4 * 3 - pad);
|
|
427
|
+
let o = 0;
|
|
428
|
+
const digit = (i, k) => {
|
|
429
|
+
const ch = text.charCodeAt(i + k);
|
|
430
|
+
if (ch === 61 && i + 4 === text.length && k >= 4 - pad) return 0;
|
|
431
|
+
const idx = ch < 128 ? B64_INDEX[ch] : -1;
|
|
432
|
+
if (idx < 0) throw new Error("Invalid base64 character");
|
|
433
|
+
return idx;
|
|
434
|
+
};
|
|
435
|
+
for (let i = 0; i < text.length; i += 4) {
|
|
436
|
+
const n = digit(i, 0) << 18 | digit(i, 1) << 12 | digit(i, 2) << 6 | digit(i, 3);
|
|
437
|
+
if (o < out.length) out[o++] = n >> 16 & 255;
|
|
438
|
+
if (o < out.length) out[o++] = n >> 8 & 255;
|
|
439
|
+
if (o < out.length) out[o++] = n & 255;
|
|
440
|
+
}
|
|
441
|
+
return out;
|
|
442
|
+
}
|
|
443
|
+
function encodeSelectionRle(data) {
|
|
444
|
+
let buf = new Uint8Array(64);
|
|
445
|
+
let len = 0;
|
|
446
|
+
const push = (b) => {
|
|
447
|
+
if (len === buf.length) {
|
|
448
|
+
const next = new Uint8Array(buf.length * 2);
|
|
449
|
+
next.set(buf);
|
|
450
|
+
buf = next;
|
|
451
|
+
}
|
|
452
|
+
buf[len++] = b;
|
|
453
|
+
};
|
|
454
|
+
let i = 0;
|
|
455
|
+
while (i < data.length) {
|
|
456
|
+
const v = data[i];
|
|
457
|
+
let j = i + 1;
|
|
458
|
+
while (j < data.length && data[j] === v) j++;
|
|
459
|
+
push(v);
|
|
460
|
+
let run = j - i;
|
|
461
|
+
while (run >= 128) {
|
|
462
|
+
push(run & 127 | 128);
|
|
463
|
+
run = Math.floor(run / 128);
|
|
464
|
+
}
|
|
465
|
+
push(run);
|
|
466
|
+
i = j;
|
|
467
|
+
}
|
|
468
|
+
return "r1:" + toBase64(buf, len);
|
|
469
|
+
}
|
|
470
|
+
function decodeSelectionRle(rle, length) {
|
|
471
|
+
if (typeof rle !== "string" || !rle.startsWith("r1:")) throw new Error("Unknown selection encoding");
|
|
472
|
+
const bytes = fromBase64(rle.slice(3));
|
|
473
|
+
const out = new Uint8Array(length);
|
|
474
|
+
let o = 0;
|
|
475
|
+
let i = 0;
|
|
476
|
+
while (i < bytes.length) {
|
|
477
|
+
const v = bytes[i++];
|
|
478
|
+
let run = 0;
|
|
479
|
+
let scale = 1;
|
|
480
|
+
for (; ; ) {
|
|
481
|
+
if (i >= bytes.length) throw new Error("Truncated selection run");
|
|
482
|
+
const b = bytes[i++];
|
|
483
|
+
run += (b & 127) * scale;
|
|
484
|
+
if (b < 128) break;
|
|
485
|
+
scale *= 128;
|
|
486
|
+
if (scale > 2 ** 35) throw new Error("Selection run too long");
|
|
487
|
+
}
|
|
488
|
+
if (run === 0 || o + run > length) throw new Error("Selection runs do not match its size");
|
|
489
|
+
if (v !== 0) out.fill(v, o, o + run);
|
|
490
|
+
o += run;
|
|
491
|
+
}
|
|
492
|
+
if (o !== length) throw new Error("Selection runs do not match its size");
|
|
493
|
+
return out;
|
|
494
|
+
}
|
|
495
|
+
function serializeSelection(mask) {
|
|
496
|
+
return { width: mask.width, height: mask.height, rle: encodeSelectionRle(mask.data) };
|
|
497
|
+
}
|
|
498
|
+
function deserializeSelection(value) {
|
|
499
|
+
if (!value || typeof value !== "object") throw new Error("selection must be an object");
|
|
500
|
+
const v = value;
|
|
501
|
+
const width = v.width;
|
|
502
|
+
const height = v.height;
|
|
503
|
+
checkSize(width, height);
|
|
504
|
+
return createSelectionMask(width, height, decodeSelectionRle(v.rle, width * height));
|
|
505
|
+
}
|
|
506
|
+
function hashMaskContent(content) {
|
|
507
|
+
let h = 2166136261;
|
|
508
|
+
if (typeof content === "string") {
|
|
509
|
+
for (let i = 0; i < content.length; i++) {
|
|
510
|
+
h ^= content.charCodeAt(i);
|
|
511
|
+
h = Math.imul(h, 16777619);
|
|
512
|
+
}
|
|
513
|
+
} else {
|
|
514
|
+
for (let i = 0; i < content.length; i++) {
|
|
515
|
+
h ^= content[i];
|
|
516
|
+
h = Math.imul(h, 16777619);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return `${(h >>> 0).toString(16)}:${content.length}`;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// src/image-editor/selection/modify.ts
|
|
523
|
+
var EMPTY_DISTANCE = 1e10;
|
|
524
|
+
function distanceTransform(inside, width, height) {
|
|
525
|
+
const n = width * height;
|
|
526
|
+
const out = new Float32Array(n);
|
|
527
|
+
let any = false;
|
|
528
|
+
let all = true;
|
|
529
|
+
for (let i = 0; i < n; i++) {
|
|
530
|
+
if (inside[i]) any = true;
|
|
531
|
+
else all = false;
|
|
532
|
+
}
|
|
533
|
+
if (!any) return out.fill(EMPTY_DISTANCE);
|
|
534
|
+
if (all) return out;
|
|
535
|
+
const NONE2 = 4294967295;
|
|
536
|
+
const vertical = new Uint32Array(n).fill(NONE2);
|
|
537
|
+
for (let x = 0; x < width; x++) {
|
|
538
|
+
let nearest = -1;
|
|
539
|
+
for (let y = 0; y < height; y++) {
|
|
540
|
+
const i = y * width + x;
|
|
541
|
+
if (inside[i]) nearest = y;
|
|
542
|
+
if (nearest >= 0) vertical[i] = y - nearest;
|
|
543
|
+
}
|
|
544
|
+
nearest = -1;
|
|
545
|
+
for (let y = height - 1; y >= 0; y--) {
|
|
546
|
+
const i = y * width + x;
|
|
547
|
+
if (vertical[i] === 0) nearest = y;
|
|
548
|
+
if (nearest >= 0) vertical[i] = Math.min(vertical[i], nearest - y);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const sites = new Int32Array(width);
|
|
552
|
+
const starts = new Float64Array(width);
|
|
553
|
+
for (let y = 0; y < height; y++) {
|
|
554
|
+
const row = y * width;
|
|
555
|
+
let count = 0;
|
|
556
|
+
for (let q = 0; q < width; q++) {
|
|
557
|
+
const dq = vertical[row + q];
|
|
558
|
+
if (dq === NONE2) continue;
|
|
559
|
+
const fq = dq * dq;
|
|
560
|
+
let start = -Infinity;
|
|
561
|
+
while (count > 0) {
|
|
562
|
+
const p = sites[count - 1];
|
|
563
|
+
const dp = vertical[row + p];
|
|
564
|
+
start = (fq - dp * dp) / (2 * (q - p)) + (q + p) * 0.5;
|
|
565
|
+
if (start > starts[count - 1]) break;
|
|
566
|
+
count--;
|
|
567
|
+
}
|
|
568
|
+
if (count === 0) start = -Infinity;
|
|
569
|
+
sites[count] = q;
|
|
570
|
+
starts[count] = start;
|
|
571
|
+
count++;
|
|
572
|
+
}
|
|
573
|
+
if (count === 0) {
|
|
574
|
+
out.fill(EMPTY_DISTANCE, row, row + width);
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
let k = 0;
|
|
578
|
+
for (let x = 0; x < width; x++) {
|
|
579
|
+
while (k + 1 < count && starts[k + 1] < x) k++;
|
|
580
|
+
const site = sites[k];
|
|
581
|
+
const dx = x - site;
|
|
582
|
+
const dv = vertical[row + site];
|
|
583
|
+
out[row + x] = Math.sqrt(dx * dx + dv * dv);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return out;
|
|
587
|
+
}
|
|
588
|
+
function thresholdInside(m, threshold = 0.5) {
|
|
589
|
+
const out = new Uint8Array(m.length);
|
|
590
|
+
for (let i = 0; i < m.length; i++) out[i] = m[i] >= threshold ? 1 : 0;
|
|
591
|
+
return out;
|
|
592
|
+
}
|
|
593
|
+
function expandCoverage(m, w, h, r) {
|
|
594
|
+
const d = distanceTransform(thresholdInside(m), w, h);
|
|
595
|
+
const out = new Float32Array(m.length);
|
|
596
|
+
for (let i = 0; i < out.length; i++) out[i] = Math.max(m[i], Math.min(1, Math.max(0, r + 1 - d[i])));
|
|
597
|
+
return out;
|
|
598
|
+
}
|
|
599
|
+
function contractCoverage(m, w, h, r) {
|
|
600
|
+
const inv = new Float32Array(m.length);
|
|
601
|
+
for (let i = 0; i < inv.length; i++) inv[i] = 1 - m[i];
|
|
602
|
+
const e = expandCoverage(inv, w, h, r);
|
|
603
|
+
for (let i = 0; i < e.length; i++) e[i] = 1 - e[i];
|
|
604
|
+
return e;
|
|
605
|
+
}
|
|
606
|
+
function borderCoverage(m, w, h, r) {
|
|
607
|
+
const inside = thresholdInside(m);
|
|
608
|
+
const outside = new Uint8Array(inside.length);
|
|
609
|
+
for (let i = 0; i < inside.length; i++) outside[i] = inside[i] ? 0 : 1;
|
|
610
|
+
const out = new Float32Array(m.length);
|
|
611
|
+
if (!(r > 0)) return out;
|
|
612
|
+
const toInside = distanceTransform(inside, w, h);
|
|
613
|
+
const toOutside = distanceTransform(outside, w, h);
|
|
614
|
+
for (let i = 0; i < out.length; i++) {
|
|
615
|
+
const d = inside[i] ? toOutside[i] : toInside[i];
|
|
616
|
+
out[i] = Math.min(1, Math.max(0, 1 - (d - 1) / r));
|
|
617
|
+
}
|
|
618
|
+
return out;
|
|
619
|
+
}
|
|
620
|
+
function boxLineReplicate(src, srcOff, stride, len, r, dst) {
|
|
621
|
+
const at = (i) => src[srcOff + Math.min(len - 1, Math.max(0, i)) * stride];
|
|
622
|
+
let sum = 0;
|
|
623
|
+
for (let i = -r; i <= r; i++) sum += at(i);
|
|
624
|
+
const k = 2 * r + 1;
|
|
625
|
+
for (let i = 0; i < len; i++) {
|
|
626
|
+
dst[i] = sum / k;
|
|
627
|
+
sum += at(i + r + 1) - at(i - r);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
function smoothCoverage(m, w, h, r) {
|
|
631
|
+
const rr = Number.isFinite(r) ? Math.max(0, Math.round(r)) : 0;
|
|
632
|
+
const src = Float32Array.from(m);
|
|
633
|
+
const tmp = new Float32Array(src.length);
|
|
634
|
+
const line = new Float32Array(Math.max(w, h));
|
|
635
|
+
if (rr > 0) {
|
|
636
|
+
for (let y = 0; y < h; y++) {
|
|
637
|
+
boxLineReplicate(src, y * w, 1, w, rr, line);
|
|
638
|
+
tmp.set(line.subarray(0, w), y * w);
|
|
639
|
+
}
|
|
640
|
+
for (let x = 0; x < w; x++) {
|
|
641
|
+
boxLineReplicate(tmp, x, w, h, rr, line);
|
|
642
|
+
for (let y = 0; y < h; y++) src[y * w + x] = line[y];
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
for (let i = 0; i < src.length; i++) src[i] = src[i] >= 0.5 ? 1 : 0;
|
|
646
|
+
return src;
|
|
647
|
+
}
|
|
648
|
+
function featherCoverage(m, w, h, radius) {
|
|
649
|
+
const out = _chunkK6PXS6DOcjs.gaussianBlur.call(void 0, m, w, h, Number.isFinite(radius) ? Math.max(0, radius) : 0, "zero");
|
|
650
|
+
for (let i = 0; i < out.length; i++) if (Math.abs(out[i]) < 1e-7) out[i] = 0;
|
|
651
|
+
return out;
|
|
652
|
+
}
|
|
653
|
+
function runningExtreme(src, off, stride, len, r, max, g, hb, dst) {
|
|
654
|
+
const k = 2 * r + 1;
|
|
655
|
+
const pick = max ? (a, b) => a > b ? a : b : (a, b) => a < b ? a : b;
|
|
656
|
+
const fill = max ? -Infinity : Infinity;
|
|
657
|
+
const plen = len + 2 * r;
|
|
658
|
+
const at = (i) => i < r || i >= r + len ? fill : src[off + (i - r) * stride];
|
|
659
|
+
for (let start = 0; start < plen; start += k) {
|
|
660
|
+
const end = Math.min(plen, start + k);
|
|
661
|
+
g[start] = at(start);
|
|
662
|
+
for (let i = start + 1; i < end; i++) g[i] = pick(g[i - 1], at(i));
|
|
663
|
+
hb[end - 1] = at(end - 1);
|
|
664
|
+
for (let i = end - 2; i >= start; i--) hb[i] = pick(hb[i + 1], at(i));
|
|
665
|
+
}
|
|
666
|
+
for (let i = 0; i < len; i++) {
|
|
667
|
+
const a = hb[i];
|
|
668
|
+
const b = g[i + 2 * r];
|
|
669
|
+
dst[i] = pick(a, b);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
function greyMorphology(src, w, h, r, grow) {
|
|
673
|
+
const out = Float32Array.from(src);
|
|
674
|
+
if (r <= 0) return out;
|
|
675
|
+
const n = Math.max(w, h);
|
|
676
|
+
const g = new Float32Array(n + 2 * r);
|
|
677
|
+
const hb = new Float32Array(n + 2 * r);
|
|
678
|
+
const line = new Float32Array(n);
|
|
679
|
+
for (let y = 0; y < h; y++) {
|
|
680
|
+
runningExtreme(out, y * w, 1, w, r, grow, g, hb, line);
|
|
681
|
+
out.set(line.subarray(0, w), y * w);
|
|
682
|
+
}
|
|
683
|
+
for (let x = 0; x < w; x++) {
|
|
684
|
+
runningExtreme(out, x, w, h, r, grow, g, hb, line);
|
|
685
|
+
for (let y = 0; y < h; y++) out[y * w + x] = line[y];
|
|
686
|
+
}
|
|
687
|
+
return out;
|
|
688
|
+
}
|
|
689
|
+
function shiftCoverage(src, w, h, amount) {
|
|
690
|
+
const mag = Math.abs(amount);
|
|
691
|
+
if (!Number.isFinite(mag) || mag < 1e-4) return Float32Array.from(src);
|
|
692
|
+
const grow = amount > 0;
|
|
693
|
+
const whole = Math.floor(mag);
|
|
694
|
+
const frac = mag - whole;
|
|
695
|
+
if (frac < 1e-4) return greyMorphology(src, w, h, whole, grow);
|
|
696
|
+
const lo = whole === 0 ? Float32Array.from(src) : greyMorphology(src, w, h, whole, grow);
|
|
697
|
+
const hi = greyMorphology(src, w, h, whole + 1, grow);
|
|
698
|
+
for (let i = 0; i < lo.length; i++) lo[i] += (hi[i] - lo[i]) * frac;
|
|
699
|
+
return lo;
|
|
700
|
+
}
|
|
701
|
+
var SELECTION_MODIFY_OPS = ["expand", "contract", "feather", "border", "smooth", "grow", "shrink"];
|
|
702
|
+
function wrap(mask, coverage) {
|
|
703
|
+
const out = new Uint8Array(coverage.length);
|
|
704
|
+
for (let i = 0; i < out.length; i++) out[i] = coverageToByte(coverage[i]);
|
|
705
|
+
return createSelectionMask(mask.width, mask.height, out);
|
|
706
|
+
}
|
|
707
|
+
function modifySelection(mask, op, radius) {
|
|
708
|
+
const r = Number.isFinite(radius) ? Math.max(0, radius) : 0;
|
|
709
|
+
if (r === 0 && op !== "border") return mask;
|
|
710
|
+
const m = selectionToCoverage(mask);
|
|
711
|
+
const { width: w, height: h } = mask;
|
|
712
|
+
switch (op) {
|
|
713
|
+
case "expand":
|
|
714
|
+
case "grow":
|
|
715
|
+
return wrap(mask, expandCoverage(m, w, h, r));
|
|
716
|
+
case "contract":
|
|
717
|
+
case "shrink":
|
|
718
|
+
return wrap(mask, contractCoverage(m, w, h, r));
|
|
719
|
+
case "feather":
|
|
720
|
+
return wrap(mask, featherCoverage(m, w, h, r));
|
|
721
|
+
case "border":
|
|
722
|
+
return wrap(mask, borderCoverage(m, w, h, r));
|
|
723
|
+
case "smooth":
|
|
724
|
+
return wrap(mask, smoothCoverage(m, w, h, r));
|
|
725
|
+
default:
|
|
726
|
+
throw new Error(`Unknown selection modification "${op}"`);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// src/image-editor/selection/refine.ts
|
|
731
|
+
var DEFAULT_REFINE_EDGE_PARAMS = {
|
|
732
|
+
radius: 0,
|
|
733
|
+
smartRadius: false,
|
|
734
|
+
smooth: 0,
|
|
735
|
+
feather: 0,
|
|
736
|
+
contrast: 0,
|
|
737
|
+
shiftEdge: 0
|
|
738
|
+
};
|
|
739
|
+
var GUIDED_FILTER_EPS = 1e-4;
|
|
740
|
+
var fin = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
|
|
741
|
+
function normalizeRefineEdgeParams(p = {}) {
|
|
742
|
+
return {
|
|
743
|
+
radius: Math.min(250, Math.max(0, fin(p.radius, 0))),
|
|
744
|
+
smartRadius: p.smartRadius === true,
|
|
745
|
+
smooth: Math.min(100, Math.max(0, fin(p.smooth, 0))),
|
|
746
|
+
feather: Math.min(1e3, Math.max(0, fin(p.feather, 0))),
|
|
747
|
+
contrast: Math.min(100, Math.max(0, fin(p.contrast, 0))),
|
|
748
|
+
shiftEdge: Math.min(100, Math.max(-100, fin(p.shiftEdge, 0)))
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
var gfRadius = (p) => Math.min(64, Math.max(1, Math.ceil(p.radius)));
|
|
752
|
+
var smoothSigma = (p) => p.smooth / 100 * 4;
|
|
753
|
+
var featherSigma = (p) => p.feather / 2;
|
|
754
|
+
function shiftAmount(p) {
|
|
755
|
+
const px = p.shiftEdge / 100 * Math.max(p.radius, 4);
|
|
756
|
+
return Number.isFinite(px) ? Math.min(64, Math.max(-64, px)) : 0;
|
|
757
|
+
}
|
|
758
|
+
function refineEdgeHalo(params = {}) {
|
|
759
|
+
const p = normalizeRefineEdgeParams(params);
|
|
760
|
+
const g = gfRadius(p);
|
|
761
|
+
const edge = p.radius > 0 ? Math.max(Math.ceil(p.radius), 2 * g + (p.smartRadius ? g + 1 : 0)) : 0;
|
|
762
|
+
return edge + Math.ceil(3 * smoothSigma(p)) + Math.ceil(3 * featherSigma(p)) + Math.ceil(Math.abs(shiftAmount(p))) + 2;
|
|
763
|
+
}
|
|
764
|
+
function boxMean(src, w, h, r) {
|
|
765
|
+
const tmp = new Float32Array(w * h);
|
|
766
|
+
const pre = new Float64Array(Math.max(w, h) + 1);
|
|
767
|
+
for (let y = 0; y < h; y++) {
|
|
768
|
+
const row = y * w;
|
|
769
|
+
for (let x = 0; x < w; x++) pre[x + 1] = pre[x] + src[row + x];
|
|
770
|
+
for (let x = 0; x < w; x++) {
|
|
771
|
+
const lo = Math.max(0, x - r);
|
|
772
|
+
const hi = Math.min(w, x + r + 1);
|
|
773
|
+
tmp[row + x] = (pre[hi] - pre[lo]) / (hi - lo);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
const out = new Float32Array(w * h);
|
|
777
|
+
for (let x = 0; x < w; x++) {
|
|
778
|
+
for (let y = 0; y < h; y++) pre[y + 1] = pre[y] + tmp[y * w + x];
|
|
779
|
+
for (let y = 0; y < h; y++) {
|
|
780
|
+
const lo = Math.max(0, y - r);
|
|
781
|
+
const hi = Math.min(h, y + r + 1);
|
|
782
|
+
out[y * w + x] = (pre[hi] - pre[lo]) / (hi - lo);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
return out;
|
|
786
|
+
}
|
|
787
|
+
function guidedFilterColor(guide, p, w, h, r, eps = GUIDED_FILTER_EPS) {
|
|
788
|
+
const n = w * h;
|
|
789
|
+
const [c0, c1, c2] = guide;
|
|
790
|
+
const prod = (a, b2) => {
|
|
791
|
+
const o = new Float32Array(n);
|
|
792
|
+
for (let i = 0; i < n; i++) o[i] = a[i] * b2[i];
|
|
793
|
+
return o;
|
|
794
|
+
};
|
|
795
|
+
const m0 = boxMean(c0, w, h, r);
|
|
796
|
+
const m1 = boxMean(c1, w, h, r);
|
|
797
|
+
const m2 = boxMean(c2, w, h, r);
|
|
798
|
+
const mp = boxMean(p, w, h, r);
|
|
799
|
+
const mip0 = boxMean(prod(c0, p), w, h, r);
|
|
800
|
+
const mip1 = boxMean(prod(c1, p), w, h, r);
|
|
801
|
+
const mip2 = boxMean(prod(c2, p), w, h, r);
|
|
802
|
+
const s00 = boxMean(prod(c0, c0), w, h, r);
|
|
803
|
+
const s01 = boxMean(prod(c0, c1), w, h, r);
|
|
804
|
+
const s02 = boxMean(prod(c0, c2), w, h, r);
|
|
805
|
+
const s11 = boxMean(prod(c1, c1), w, h, r);
|
|
806
|
+
const s12 = boxMean(prod(c1, c2), w, h, r);
|
|
807
|
+
const s22 = boxMean(prod(c2, c2), w, h, r);
|
|
808
|
+
const a0 = new Float32Array(n);
|
|
809
|
+
const a1 = new Float32Array(n);
|
|
810
|
+
const a2 = new Float32Array(n);
|
|
811
|
+
const b = new Float32Array(n);
|
|
812
|
+
for (let i = 0; i < n; i++) {
|
|
813
|
+
const u0 = m0[i], u1 = m1[i], u2 = m2[i], up = mp[i];
|
|
814
|
+
const rr = s00[i] - u0 * u0 + eps;
|
|
815
|
+
const rg = s01[i] - u0 * u1;
|
|
816
|
+
const rb = s02[i] - u0 * u2;
|
|
817
|
+
const gg = s11[i] - u1 * u1 + eps;
|
|
818
|
+
const gb = s12[i] - u1 * u2;
|
|
819
|
+
const bb = s22[i] - u2 * u2 + eps;
|
|
820
|
+
const v0 = mip0[i] - u0 * up;
|
|
821
|
+
const v1 = mip1[i] - u1 * up;
|
|
822
|
+
const v2 = mip2[i] - u2 * up;
|
|
823
|
+
const i00 = gg * bb - gb * gb;
|
|
824
|
+
const i01 = gb * rb - rg * bb;
|
|
825
|
+
const i02 = rg * gb - gg * rb;
|
|
826
|
+
const i11 = rr * bb - rb * rb;
|
|
827
|
+
const i12 = rb * rg - rr * gb;
|
|
828
|
+
const i22 = rr * gg - rg * rg;
|
|
829
|
+
const det = rr * i00 + rg * i01 + rb * i02;
|
|
830
|
+
let x0 = 0, x1 = 0, x2 = 0;
|
|
831
|
+
if (Math.abs(det) >= 1e-30) {
|
|
832
|
+
x0 = (i00 * v0 + i01 * v1 + i02 * v2) / det;
|
|
833
|
+
x1 = (i01 * v0 + i11 * v1 + i12 * v2) / det;
|
|
834
|
+
x2 = (i02 * v0 + i12 * v1 + i22 * v2) / det;
|
|
835
|
+
}
|
|
836
|
+
a0[i] = x0;
|
|
837
|
+
a1[i] = x1;
|
|
838
|
+
a2[i] = x2;
|
|
839
|
+
b[i] = up - x0 * u0 - x1 * u1 - x2 * u2;
|
|
840
|
+
}
|
|
841
|
+
const ma0 = boxMean(a0, w, h, r);
|
|
842
|
+
const ma1 = boxMean(a1, w, h, r);
|
|
843
|
+
const ma2 = boxMean(a2, w, h, r);
|
|
844
|
+
const mb = boxMean(b, w, h, r);
|
|
845
|
+
const out = new Float32Array(n);
|
|
846
|
+
for (let i = 0; i < n; i++) out[i] = ma0[i] * c0[i] + ma1[i] * c1[i] + ma2[i] * c2[i] + mb[i];
|
|
847
|
+
return out;
|
|
848
|
+
}
|
|
849
|
+
function edgeWidth(guide, w, h, r) {
|
|
850
|
+
const n = w * h;
|
|
851
|
+
const y = new Float32Array(n);
|
|
852
|
+
for (let i = 0; i < n; i++) y[i] = 0.299 * guide[0][i] + 0.587 * guide[1][i] + 0.114 * guide[2][i];
|
|
853
|
+
const g = new Float32Array(n);
|
|
854
|
+
for (let yy = 0; yy < h; yy++) {
|
|
855
|
+
for (let xx = 0; xx < w; xx++) {
|
|
856
|
+
const gx = (y[yy * w + Math.min(w - 1, xx + 1)] - y[yy * w + Math.max(0, xx - 1)]) / 2;
|
|
857
|
+
const gy = (y[Math.min(h - 1, yy + 1) * w + xx] - y[Math.max(0, yy - 1) * w + xx]) / 2;
|
|
858
|
+
g[yy * w + xx] = Math.sqrt(gx * gx + gy * gy);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
const gm = greyMorphology(g, w, h, r, true);
|
|
862
|
+
const hi = greyMorphology(y, w, h, r, true);
|
|
863
|
+
const lo = greyMorphology(y, w, h, r, false);
|
|
864
|
+
const out = new Float32Array(n);
|
|
865
|
+
for (let i = 0; i < n; i++) out[i] = (hi[i] - lo[i]) / Math.max(gm[i], 1e-4);
|
|
866
|
+
return out;
|
|
867
|
+
}
|
|
868
|
+
function refineCoverage(guide, m, w, h, params = {}, band = null) {
|
|
869
|
+
const p = normalizeRefineEdgeParams(params);
|
|
870
|
+
let a = Float32Array.from(m);
|
|
871
|
+
if ((p.radius > 0 || band) && guide) {
|
|
872
|
+
const n = w * h;
|
|
873
|
+
const boundary = new Uint8Array(n);
|
|
874
|
+
let any = false;
|
|
875
|
+
for (let y = 0; y < h; y++) {
|
|
876
|
+
for (let x = 0; x < w; x++) {
|
|
877
|
+
const i = y * w + x;
|
|
878
|
+
const v = m[i] >= 0.5;
|
|
879
|
+
const diff = x > 0 && m[i - 1] >= 0.5 !== v || x + 1 < w && m[i + 1] >= 0.5 !== v || y > 0 && m[i - w] >= 0.5 !== v || y + 1 < h && m[i + w] >= 0.5 !== v;
|
|
880
|
+
if (diff) {
|
|
881
|
+
boundary[i] = 1;
|
|
882
|
+
any = true;
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
if (any || band) {
|
|
887
|
+
const dist = distanceTransform(boundary, w, h);
|
|
888
|
+
const r = band ? Math.max(4, gfRadius(p)) : gfRadius(p);
|
|
889
|
+
const q = guidedFilterColor(guide, m, w, h, r);
|
|
890
|
+
const widths = p.smartRadius ? edgeWidth(guide, w, h, r) : null;
|
|
891
|
+
for (let i = 0; i < n; i++) {
|
|
892
|
+
const reff = widths ? Math.min(Math.max(p.radius, 1.5), Math.max(1.5, widths[i])) : p.radius;
|
|
893
|
+
const edge = p.radius > 0 ? Math.min(1, Math.max(0, reff + 1 - dist[i])) : 0;
|
|
894
|
+
const wgt = band ? Math.max(edge, Math.min(1, Math.max(0, band[i]))) : edge;
|
|
895
|
+
if (wgt > 0) {
|
|
896
|
+
const qi = Math.min(1, Math.max(0, q[i]));
|
|
897
|
+
a[i] = m[i] + (qi - m[i]) * wgt;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
const ss = smoothSigma(p);
|
|
903
|
+
if (ss >= 0.3) {
|
|
904
|
+
a = _chunkK6PXS6DOcjs.gaussianBlur.call(void 0, a, w, h, ss, "clamp");
|
|
905
|
+
const gain = 1 + ss;
|
|
906
|
+
for (let i = 0; i < a.length; i++) a[i] = Math.min(1, Math.max(0, (a[i] - 0.5) * gain + 0.5));
|
|
907
|
+
}
|
|
908
|
+
a = _chunkK6PXS6DOcjs.gaussianBlur.call(void 0, a, w, h, featherSigma(p), "clamp");
|
|
909
|
+
if (p.contrast > 0) {
|
|
910
|
+
const gain = 1 / (1 - 0.99 * p.contrast / 100);
|
|
911
|
+
for (let i = 0; i < a.length; i++) a[i] = Math.min(1, Math.max(0, (a[i] - 0.5) * gain + 0.5));
|
|
912
|
+
}
|
|
913
|
+
const amount = shiftAmount(p);
|
|
914
|
+
if (Math.abs(amount) >= 1e-4) a = shiftCoverage(a, w, h, amount);
|
|
915
|
+
return a;
|
|
916
|
+
}
|
|
917
|
+
function guidePlanes(pixels, win) {
|
|
918
|
+
const box = _nullishCoalesce(win, () => ( { x: 0, y: 0, width: pixels.width, height: pixels.height }));
|
|
919
|
+
const n = box.width * box.height;
|
|
920
|
+
const r = new Float32Array(n);
|
|
921
|
+
const g = new Float32Array(n);
|
|
922
|
+
const b = new Float32Array(n);
|
|
923
|
+
const d = pixels.data;
|
|
924
|
+
for (let y = 0; y < box.height; y++) {
|
|
925
|
+
for (let x = 0; x < box.width; x++) {
|
|
926
|
+
const i = y * box.width + x;
|
|
927
|
+
const p = ((box.y + y) * pixels.width + box.x + x) * 4;
|
|
928
|
+
r[i] = d[p] / 255;
|
|
929
|
+
g[i] = d[p + 1] / 255;
|
|
930
|
+
b[i] = d[p + 2] / 255;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
return [r, g, b];
|
|
934
|
+
}
|
|
935
|
+
function refineSelection(pixels, mask, params = {}, refineBrush = null) {
|
|
936
|
+
const p = normalizeRefineEdgeParams(params);
|
|
937
|
+
if (pixels && (pixels.width !== mask.width || pixels.height !== mask.height)) {
|
|
938
|
+
throw new Error("Refine Edge needs pixels the size of the selection");
|
|
939
|
+
}
|
|
940
|
+
if (!mask.bounds) return mask;
|
|
941
|
+
const brush = _optionalChain([refineBrush, 'optionalAccess', _5 => _5.bounds]) ? refineBrush : null;
|
|
942
|
+
const halo = refineEdgeHalo(p) + (brush ? 2 * Math.max(4, gfRadius(p)) : 0);
|
|
943
|
+
const b = brush ? {
|
|
944
|
+
x: Math.min(mask.bounds.x, brush.bounds.x),
|
|
945
|
+
y: Math.min(mask.bounds.y, brush.bounds.y),
|
|
946
|
+
r: Math.max(mask.bounds.x + mask.bounds.width, brush.bounds.x + brush.bounds.width),
|
|
947
|
+
btm: Math.max(mask.bounds.y + mask.bounds.height, brush.bounds.y + brush.bounds.height)
|
|
948
|
+
} : { x: mask.bounds.x, y: mask.bounds.y, r: mask.bounds.x + mask.bounds.width, btm: mask.bounds.y + mask.bounds.height };
|
|
949
|
+
const x0 = Math.max(0, b.x - halo);
|
|
950
|
+
const y0 = Math.max(0, b.y - halo);
|
|
951
|
+
const x1 = Math.min(mask.width, b.r + halo);
|
|
952
|
+
const y1 = Math.min(mask.height, b.btm + halo);
|
|
953
|
+
const ww = x1 - x0;
|
|
954
|
+
const wh = y1 - y0;
|
|
955
|
+
const m = new Float32Array(ww * wh);
|
|
956
|
+
for (let y = 0; y < wh; y++) {
|
|
957
|
+
const src = (y0 + y) * mask.width + x0;
|
|
958
|
+
for (let x = 0; x < ww; x++) m[y * ww + x] = mask.data[src + x] / 255;
|
|
959
|
+
}
|
|
960
|
+
const guide = pixels && (p.radius > 0 || brush) ? guidePlanes(pixels, { x: x0, y: y0, width: ww, height: wh }) : null;
|
|
961
|
+
let band = null;
|
|
962
|
+
if (brush) {
|
|
963
|
+
band = new Float32Array(ww * wh);
|
|
964
|
+
const bd = brush.data;
|
|
965
|
+
for (let y = 0; y < wh; y++) {
|
|
966
|
+
const src = (y0 + y) * mask.width + x0;
|
|
967
|
+
for (let x = 0; x < ww; x++) band[y * ww + x] = bd[src + x] / 255;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
const refined = refineCoverage(guide, m, ww, wh, p, band);
|
|
971
|
+
const out = new Uint8Array(mask.width * mask.height);
|
|
972
|
+
for (let y = 0; y < wh; y++) {
|
|
973
|
+
const dst = (y0 + y) * mask.width + x0;
|
|
974
|
+
for (let x = 0; x < ww; x++) out[dst + x] = coverageToByte(refined[y * ww + x]);
|
|
975
|
+
}
|
|
976
|
+
return createSelectionMask(mask.width, mask.height, out);
|
|
977
|
+
}
|
|
978
|
+
function decontaminateColors(rgba, coverage, w, h, amount, radius = 8) {
|
|
979
|
+
const out = Uint8ClampedArray.from(rgba);
|
|
980
|
+
const n = w * h;
|
|
981
|
+
const k = Math.min(1, Math.max(0, amount));
|
|
982
|
+
if (k === 0) return out;
|
|
983
|
+
const r = Math.max(2, Math.round(radius));
|
|
984
|
+
const fgW = new Float32Array(n);
|
|
985
|
+
const bgW = new Float32Array(n);
|
|
986
|
+
for (let i = 0; i < n; i++) {
|
|
987
|
+
fgW[i] = coverage[i] >= 250 ? 1 : 0;
|
|
988
|
+
bgW[i] = coverage[i] <= 5 ? 1 : 0;
|
|
989
|
+
}
|
|
990
|
+
const meanOf = (weights, radiusPx) => {
|
|
991
|
+
const wm = boxMean(weights, w, h, radiusPx);
|
|
992
|
+
const planes = [0, 1, 2].map((c) => {
|
|
993
|
+
const v = new Float32Array(n);
|
|
994
|
+
for (let i = 0; i < n; i++) v[i] = rgba[i * 4 + c] * weights[i];
|
|
995
|
+
const m = boxMean(v, w, h, radiusPx);
|
|
996
|
+
for (let i = 0; i < n; i++) m[i] = wm[i] > 1e-6 ? m[i] / wm[i] : NaN;
|
|
997
|
+
return m;
|
|
998
|
+
});
|
|
999
|
+
return { weight: wm, planes };
|
|
1000
|
+
};
|
|
1001
|
+
const fg = meanOf(fgW, r);
|
|
1002
|
+
const fgWide = meanOf(fgW, r * 3);
|
|
1003
|
+
const bg = meanOf(bgW, r);
|
|
1004
|
+
for (let i = 0; i < n; i++) {
|
|
1005
|
+
const c = coverage[i];
|
|
1006
|
+
if (c <= 5 || c >= 250) continue;
|
|
1007
|
+
const a = c / 255;
|
|
1008
|
+
for (let ch = 0; ch < 3; ch++) {
|
|
1009
|
+
const C = rgba[i * 4 + ch];
|
|
1010
|
+
const B = bg.planes[ch][i];
|
|
1011
|
+
const Fm = Number.isNaN(fg.planes[ch][i]) ? fgWide.planes[ch][i] : fg.planes[ch][i];
|
|
1012
|
+
let F;
|
|
1013
|
+
if (a >= 0.5 && !Number.isNaN(B)) F = (C - (1 - a) * B) / a;
|
|
1014
|
+
else if (!Number.isNaN(Fm)) F = Fm;
|
|
1015
|
+
else F = C;
|
|
1016
|
+
out[i * 4 + ch] = Math.round(C + k * (Math.min(255, Math.max(0, F)) - C));
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
return out;
|
|
1020
|
+
}
|
|
14
1021
|
|
|
15
1022
|
// src/image-editor/selection/shapes.ts
|
|
16
1023
|
function polygonCoverage(points, width, height, antiAlias = true) {
|
|
@@ -89,10 +1096,10 @@ function polygonCoverageBox(points, width, height, antiAlias = true) {
|
|
|
89
1096
|
}
|
|
90
1097
|
function polygonSelection(width, height, points, options = {}) {
|
|
91
1098
|
const { box, coverage } = polygonCoverageBox(points, width, height, _nullishCoalesce(options.antiAlias, () => ( true)));
|
|
92
|
-
if (!box) return
|
|
1099
|
+
if (!box) return createSelectionMask(width, height);
|
|
93
1100
|
const out = new Uint8Array(coverage.length);
|
|
94
|
-
for (let i = 0; i < out.length; i++) out[i] =
|
|
95
|
-
return
|
|
1101
|
+
for (let i = 0; i < out.length; i++) out[i] = coverageToByte(coverage[i]);
|
|
1102
|
+
return selectionFromRegion(width, height, box, out);
|
|
96
1103
|
}
|
|
97
1104
|
function normalizeRect(rect) {
|
|
98
1105
|
const x = rect.width < 0 ? rect.x + rect.width : rect.x;
|
|
@@ -376,7 +1383,7 @@ var WORK_SIZE = 512;
|
|
|
376
1383
|
var workCache = /* @__PURE__ */ new WeakMap();
|
|
377
1384
|
function workImage(pixels, maxSide = WORK_SIZE) {
|
|
378
1385
|
const sizes = workCache.get(pixels.data);
|
|
379
|
-
const cached = _optionalChain([sizes, 'optionalAccess',
|
|
1386
|
+
const cached = _optionalChain([sizes, 'optionalAccess', _6 => _6.get, 'call', _7 => _7(maxSide)]);
|
|
380
1387
|
if (cached && cached.sourceWidth === pixels.width && cached.sourceHeight === pixels.height) return cached;
|
|
381
1388
|
const { width: W, height: H, data } = pixels;
|
|
382
1389
|
const scale = Math.min(1, maxSide / Math.max(W, H));
|
|
@@ -555,18 +1562,18 @@ function upscaleRegion(work, pixels, roi, label, enhance) {
|
|
|
555
1562
|
out[Y * RW + X] = Math.round(alpha * 255);
|
|
556
1563
|
}
|
|
557
1564
|
}
|
|
558
|
-
return
|
|
1565
|
+
return selectionFromRegion(W, H, { x: X0, y: Y0, width: RW, height: RH }, out);
|
|
559
1566
|
}
|
|
560
1567
|
|
|
561
1568
|
// src/image-editor/selection/segment/grabcut.ts
|
|
562
1569
|
var K = 5;
|
|
563
|
-
var ColorGmm = (
|
|
564
|
-
|
|
565
|
-
|
|
1570
|
+
var ColorGmm = (_class2 = class {constructor() { _class2.prototype.__init2.call(this);_class2.prototype.__init3.call(this);_class2.prototype.__init4.call(this);_class2.prototype.__init5.call(this); }
|
|
1571
|
+
__init2() {this.logWeight = new Float64Array(K)}
|
|
1572
|
+
__init3() {this.mean = new Float64Array(K * 3)}
|
|
566
1573
|
/** Inverse covariances, row-major 3 × 3. */
|
|
567
|
-
|
|
1574
|
+
__init4() {this.inv = new Float64Array(K * 9)}
|
|
568
1575
|
/** ln(weight) − ½ ln((2π)³ |Σ|) per component; −∞ for an empty one. */
|
|
569
|
-
|
|
1576
|
+
__init5() {this.logNorm = new Float64Array(K).fill(-Infinity)}
|
|
570
1577
|
/** Fits the components to the pixels `ids` of `rgb`, each in component `comp[id]`. */
|
|
571
1578
|
fit(rgb, ids, comp) {
|
|
572
1579
|
const n = new Float64Array(K);
|
|
@@ -651,7 +1658,7 @@ var ColorGmm = (_class = class {constructor() { _class.prototype.__init.call(thi
|
|
|
651
1658
|
}
|
|
652
1659
|
return best;
|
|
653
1660
|
}
|
|
654
|
-
},
|
|
1661
|
+
}, _class2);
|
|
655
1662
|
function initialComponents(rgb, ids, comp) {
|
|
656
1663
|
if (!ids.length) return;
|
|
657
1664
|
const step = Math.max(1, Math.floor(ids.length / 6e3));
|
|
@@ -717,7 +1724,7 @@ function grabCutLabels(work, setup, iterations, onRound) {
|
|
|
717
1724
|
const graph = new GridGraph(rw, rh);
|
|
718
1725
|
for (let p = 0; p < n; p++) {
|
|
719
1726
|
if (hardBg[p]) graph.addTerminal(p, 0, HARD);
|
|
720
|
-
else if (_optionalChain([hardFg, 'optionalAccess',
|
|
1727
|
+
else if (_optionalChain([hardFg, 'optionalAccess', _8 => _8[p]])) graph.addTerminal(p, HARD, 0);
|
|
721
1728
|
else {
|
|
722
1729
|
const i = global[p] * 3;
|
|
723
1730
|
graph.addTerminal(p, Math.round(bg.cost(rgb[i], rgb[i + 1], rgb[i + 2]) * UNITS), Math.round(fg.cost(rgb[i], rgb[i + 1], rgb[i + 2]) * UNITS));
|
|
@@ -732,7 +1739,7 @@ function grabCutLabels(work, setup, iterations, onRound) {
|
|
|
732
1739
|
if (v !== label[p]) changed++;
|
|
733
1740
|
label[p] = v;
|
|
734
1741
|
}
|
|
735
|
-
_optionalChain([onRound, 'optionalCall',
|
|
1742
|
+
_optionalChain([onRound, 'optionalCall', _9 => _9((iter + 1) / iterations)]);
|
|
736
1743
|
if (changed <= n * 1e-3) break;
|
|
737
1744
|
}
|
|
738
1745
|
return label;
|
|
@@ -768,10 +1775,10 @@ function keepMainPieces(label, rw, rh, seed) {
|
|
|
768
1775
|
}
|
|
769
1776
|
function selectObjectIn(pixels, target, options = {}) {
|
|
770
1777
|
const report = options.onProgress;
|
|
771
|
-
_optionalChain([report, 'optionalCall',
|
|
1778
|
+
_optionalChain([report, 'optionalCall', _10 => _10(0, "Reducing the image")]);
|
|
772
1779
|
const work = workImage(pixels);
|
|
773
|
-
_optionalChain([report, 'optionalCall',
|
|
774
|
-
const round = (fraction) => _optionalChain([report, 'optionalCall',
|
|
1780
|
+
_optionalChain([report, 'optionalCall', _11 => _11(0.1, "Finding the object")]);
|
|
1781
|
+
const round = (fraction) => _optionalChain([report, 'optionalCall', _12 => _12(0.1 + 0.8 * fraction, "Finding the object")]);
|
|
775
1782
|
const { width: w, height: h, scale } = work;
|
|
776
1783
|
const iterations = Math.max(1, Math.round(_nullishCoalesce(options.iterations, () => ( 5))));
|
|
777
1784
|
const enhance = _nullishCoalesce(options.enhanceEdge, () => ( false));
|
|
@@ -781,7 +1788,7 @@ function selectObjectIn(pixels, target, options = {}) {
|
|
|
781
1788
|
const by0 = Math.max(0, Math.floor(r.y * scale));
|
|
782
1789
|
const bx1 = Math.min(w, Math.ceil((r.x + r.width) * scale));
|
|
783
1790
|
const by1 = Math.min(h, Math.ceil((r.y + r.height) * scale));
|
|
784
|
-
if (bx1 - bx0 < 2 || by1 - by0 < 2) return
|
|
1791
|
+
if (bx1 - bx0 < 2 || by1 - by0 < 2) return createSelectionMask(pixels.width, pixels.height);
|
|
785
1792
|
const m = Math.max(8, Math.round(0.25 * Math.max(bx1 - bx0, by1 - by0)));
|
|
786
1793
|
const rx = Math.max(0, bx0 - m);
|
|
787
1794
|
const ry = Math.max(0, by0 - m);
|
|
@@ -800,9 +1807,9 @@ function selectObjectIn(pixels, target, options = {}) {
|
|
|
800
1807
|
}
|
|
801
1808
|
const label = grabCutLabels(work, { roi, initial, hardBg, hardFg: null }, iterations, round);
|
|
802
1809
|
keepMainPieces(label, roi.width, roi.height, null);
|
|
803
|
-
_optionalChain([report, 'optionalCall',
|
|
1810
|
+
_optionalChain([report, 'optionalCall', _13 => _13(0.9, "Scaling up")]);
|
|
804
1811
|
const out = upscaleRegion(work, pixels, roi, label, enhance);
|
|
805
|
-
_optionalChain([report, 'optionalCall',
|
|
1812
|
+
_optionalChain([report, 'optionalCall', _14 => _14(1, "Done")]);
|
|
806
1813
|
return out;
|
|
807
1814
|
}
|
|
808
1815
|
const px = Math.min(w - 1, Math.max(0, Math.floor(target.point.x * scale)));
|
|
@@ -864,9 +1871,9 @@ function selectObjectIn(pixels, target, options = {}) {
|
|
|
864
1871
|
small = !s.touches && s.found > 1.5 * s.seeded ? { roi, label: s.label } : null;
|
|
865
1872
|
}
|
|
866
1873
|
if (!touches || whole) {
|
|
867
|
-
_optionalChain([report, 'optionalCall',
|
|
1874
|
+
_optionalChain([report, 'optionalCall', _15 => _15(0.9, "Scaling up")]);
|
|
868
1875
|
const out = small && edgesReached(label, roi, w, h) >= 3 ? upscaleRegion(work, pixels, small.roi, small.label, enhance) : upscaleRegion(work, pixels, roi, label, enhance);
|
|
869
|
-
_optionalChain([report, 'optionalCall',
|
|
1876
|
+
_optionalChain([report, 'optionalCall', _16 => _16(1, "Done")]);
|
|
870
1877
|
return out;
|
|
871
1878
|
}
|
|
872
1879
|
half = Math.round(half * 1.6);
|
|
@@ -953,7 +1960,7 @@ function segmentStroke(work, stroke, grown, start) {
|
|
|
953
1960
|
const bin = binOf(rgb, g);
|
|
954
1961
|
bgHist[bin]++;
|
|
955
1962
|
bgN++;
|
|
956
|
-
if (stroke.mask[g] || _optionalChain([grown, 'optionalAccess',
|
|
1963
|
+
if (stroke.mask[g] || _optionalChain([grown, 'optionalAccess', _17 => _17[g]])) {
|
|
957
1964
|
fgHist[bin]++;
|
|
958
1965
|
fgN++;
|
|
959
1966
|
}
|
|
@@ -1035,12 +2042,12 @@ function quickSelectStroke(pixels, stroke, options = {}) {
|
|
|
1035
2042
|
const roi = { x: rx, y: ry, width: Math.min(w, Math.ceil(x1 + margin)) - rx, height: Math.min(h, Math.ceil(y1 + margin)) - ry };
|
|
1036
2043
|
let grown = null;
|
|
1037
2044
|
const sel = options.grow ? options.selection : null;
|
|
1038
|
-
if (_optionalChain([sel, 'optionalAccess',
|
|
2045
|
+
if (_optionalChain([sel, 'optionalAccess', _18 => _18.bounds])) {
|
|
1039
2046
|
grown = new Uint8Array(w * h);
|
|
1040
2047
|
const b = sel.bounds;
|
|
1041
2048
|
for (let y = Math.max(0, Math.floor(b.y * scale)); y < Math.min(h, Math.ceil((b.y + b.height) * scale)); y++) {
|
|
1042
2049
|
for (let x = Math.max(0, Math.floor(b.x * scale)); x < Math.min(w, Math.ceil((b.x + b.width) * scale)); x++) {
|
|
1043
|
-
if (
|
|
2050
|
+
if (selectionValueAt(sel, Math.min(sel.width - 1, Math.floor((x + 0.5) / scale)), Math.min(sel.height - 1, Math.floor((y + 0.5) / scale))) >= 0.5) grown[y * w + x] = 1;
|
|
1044
2051
|
}
|
|
1045
2052
|
}
|
|
1046
2053
|
}
|
|
@@ -1064,7 +2071,7 @@ function brushStrokeMask(width, height, points, size, hardness = 1) {
|
|
|
1064
2071
|
const by = Math.max(0, Math.floor(y0 - r - 1));
|
|
1065
2072
|
const bw = Math.min(width, Math.ceil(x1 + r + 1)) - bx;
|
|
1066
2073
|
const bh = Math.min(height, Math.ceil(y1 + r + 1)) - by;
|
|
1067
|
-
if (!(bw > 0 && bh > 0)) return
|
|
2074
|
+
if (!(bw > 0 && bh > 0)) return createSelectionMask(width, height);
|
|
1068
2075
|
const out = new Uint8Array(bw * bh);
|
|
1069
2076
|
const segs = points.length === 1 ? [[points[0], points[0]]] : points.slice(1).map((p, i) => [points[i], p]);
|
|
1070
2077
|
for (let y = 0; y < bh; y++) {
|
|
@@ -1084,27 +2091,27 @@ function brushStrokeMask(width, height, points, size, hardness = 1) {
|
|
|
1084
2091
|
out[y * bw + x] = Math.round(Math.max(0, v) * 255);
|
|
1085
2092
|
}
|
|
1086
2093
|
}
|
|
1087
|
-
return
|
|
2094
|
+
return selectionFromRegion(width, height, { x: bx, y: by, width: bw, height: bh }, out);
|
|
1088
2095
|
}
|
|
1089
2096
|
function applyRefineEdits(pixels, selection, edits, width, height) {
|
|
1090
|
-
let sel = _optionalChain([selection, 'optionalAccess',
|
|
2097
|
+
let sel = _optionalChain([selection, 'optionalAccess', _19 => _19.bounds]) ? selection : null;
|
|
1091
2098
|
let band = null;
|
|
1092
2099
|
for (const e of edits) {
|
|
1093
2100
|
if (e.kind === "object") {
|
|
1094
2101
|
if (!pixels) throw new Error("Object Selection needs the image pixels.");
|
|
1095
2102
|
if (!e.rect === !e.point) throw new Error("An object edit takes rect (a box around the object) or point (on it), not both.");
|
|
1096
2103
|
const region2 = selectObjectIn(pixels, e.rect ? { rect: e.rect } : { point: e.point });
|
|
1097
|
-
sel =
|
|
2104
|
+
sel = combineSelections(sel, region2, _nullishCoalesce(e.mode, () => ( "add")));
|
|
1098
2105
|
continue;
|
|
1099
2106
|
}
|
|
1100
2107
|
if (!e.points.length) throw new Error(`A ${e.kind} stroke needs at least one point.`);
|
|
1101
2108
|
if (e.kind === "lasso") {
|
|
1102
2109
|
if (e.points.length < 3) throw new Error("A lasso edit needs at least 3 points.");
|
|
1103
|
-
sel =
|
|
2110
|
+
sel = combineSelections(sel, polygonSelection(width, height, e.points, {}), _nullishCoalesce(e.mode, () => ( "add")));
|
|
1104
2111
|
continue;
|
|
1105
2112
|
}
|
|
1106
2113
|
if (e.kind === "refine-brush") {
|
|
1107
|
-
band =
|
|
2114
|
+
band = combineSelections(band, brushStrokeMask(width, height, e.points, _nullishCoalesce(e.size, () => ( 40))), "add");
|
|
1108
2115
|
continue;
|
|
1109
2116
|
}
|
|
1110
2117
|
const mode = _nullishCoalesce(e.mode, () => ( "add"));
|
|
@@ -1115,7 +2122,7 @@ function applyRefineEdits(pixels, selection, edits, width, height) {
|
|
|
1115
2122
|
} else {
|
|
1116
2123
|
region = brushStrokeMask(width, height, e.points, _nullishCoalesce(e.size, () => ( 20)), _nullishCoalesce(e.hardness, () => ( 1)));
|
|
1117
2124
|
}
|
|
1118
|
-
sel =
|
|
2125
|
+
sel = combineSelections(sel, region, mode);
|
|
1119
2126
|
}
|
|
1120
2127
|
return { selection: sel, refineBrush: band };
|
|
1121
2128
|
}
|
|
@@ -1162,7 +2169,7 @@ function softMaskToSelection(work, mask) {
|
|
|
1162
2169
|
out[Y * W + X] = Math.round(Math.min(1, Math.max(0, m)) * 255);
|
|
1163
2170
|
}
|
|
1164
2171
|
}
|
|
1165
|
-
return
|
|
2172
|
+
return selectionFromRegion(W, H, { x: 0, y: 0, width: W, height: H }, out);
|
|
1166
2173
|
}
|
|
1167
2174
|
|
|
1168
2175
|
// src/image-editor/selection/segment/subject.ts
|
|
@@ -1334,14 +2341,14 @@ function otsuThreshold(values, weights) {
|
|
|
1334
2341
|
}
|
|
1335
2342
|
function findSubject(pixels, options = {}) {
|
|
1336
2343
|
const report = options.onProgress;
|
|
1337
|
-
_optionalChain([report, 'optionalCall',
|
|
2344
|
+
_optionalChain([report, 'optionalCall', _20 => _20(0, "Reducing the image")]);
|
|
1338
2345
|
const work = workImage(pixels);
|
|
1339
2346
|
const { width: w, height: h } = work;
|
|
1340
|
-
_optionalChain([report, 'optionalCall',
|
|
2347
|
+
_optionalChain([report, 'optionalCall', _21 => _21(0.1, "Grouping into superpixels")]);
|
|
1341
2348
|
const sp = slicSuperpixels(work);
|
|
1342
|
-
_optionalChain([report, 'optionalCall',
|
|
2349
|
+
_optionalChain([report, 'optionalCall', _22 => _22(0.3, "Measuring what stands out")]);
|
|
1343
2350
|
const sal = superpixelSaliency(sp, w, h);
|
|
1344
|
-
_optionalChain([report, 'optionalCall',
|
|
2351
|
+
_optionalChain([report, 'optionalCall', _23 => _23(0.4, "Finding the subject")]);
|
|
1345
2352
|
const t = otsuThreshold(sal, sp.size);
|
|
1346
2353
|
const n = w * h;
|
|
1347
2354
|
const initial = new Uint8Array(n);
|
|
@@ -1359,20 +2366,20 @@ function findSubject(pixels, options = {}) {
|
|
|
1359
2366
|
for (let y = 0; y < h; y++) {
|
|
1360
2367
|
for (const x of [0, w - 1]) if (sal[sp.label[y * w + x]] < t * 0.5) hardBg[y * w + x] = 1;
|
|
1361
2368
|
}
|
|
1362
|
-
const empty = (reason) => ({ selection:
|
|
2369
|
+
const empty = (reason) => ({ selection: createSelectionMask(pixels.width, pixels.height), reason });
|
|
1363
2370
|
let guessed = 0;
|
|
1364
2371
|
for (let i = 0; i < n; i++) guessed += initial[i];
|
|
1365
2372
|
if (!guessed || guessed > n * 0.95) return empty("Nothing stands out from the background.");
|
|
1366
2373
|
const roi = { x: 0, y: 0, width: w, height: h };
|
|
1367
|
-
const label = grabCutLabels(work, { roi, initial, hardBg, hardFg }, 5, (f) => _optionalChain([report, 'optionalCall',
|
|
2374
|
+
const label = grabCutLabels(work, { roi, initial, hardBg, hardFg }, 5, (f) => _optionalChain([report, 'optionalCall', _24 => _24(0.4 + 0.5 * f, "Finding the subject")]));
|
|
1368
2375
|
keepMainPieces(label, w, h, null);
|
|
1369
2376
|
let kept = 0;
|
|
1370
2377
|
for (let i = 0; i < n; i++) kept += label[i];
|
|
1371
2378
|
if (kept < n * 2e-3) return empty("Nothing stands out from the background.");
|
|
1372
2379
|
if (kept > n * 0.97) return empty("The whole image looks like one subject; select it with select_all instead.");
|
|
1373
|
-
_optionalChain([report, 'optionalCall',
|
|
2380
|
+
_optionalChain([report, 'optionalCall', _25 => _25(0.9, "Scaling up")]);
|
|
1374
2381
|
const selection = upscaleRegion(work, pixels, roi, label, _nullishCoalesce(options.enhanceEdge, () => ( false)));
|
|
1375
|
-
_optionalChain([report, 'optionalCall',
|
|
2382
|
+
_optionalChain([report, 'optionalCall', _26 => _26(1, "Done")]);
|
|
1376
2383
|
return { selection };
|
|
1377
2384
|
}
|
|
1378
2385
|
|
|
@@ -1392,9 +2399,9 @@ function estimateNoise(luma, w, h) {
|
|
|
1392
2399
|
}
|
|
1393
2400
|
function focusArea(pixels, options = {}) {
|
|
1394
2401
|
const report = options.onProgress;
|
|
1395
|
-
_optionalChain([report, 'optionalCall',
|
|
2402
|
+
_optionalChain([report, 'optionalCall', _27 => _27(0, "Reducing the image")]);
|
|
1396
2403
|
const work = workImage(pixels, FOCUS_SIZE);
|
|
1397
|
-
_optionalChain([report, 'optionalCall',
|
|
2404
|
+
_optionalChain([report, 'optionalCall', _28 => _28(0.2, "Measuring sharpness")]);
|
|
1398
2405
|
const { width: w, height: h, scale } = work;
|
|
1399
2406
|
const n = w * h;
|
|
1400
2407
|
const luma = workLuma(work);
|
|
@@ -1411,10 +2418,10 @@ function focusArea(pixels, options = {}) {
|
|
|
1411
2418
|
}
|
|
1412
2419
|
}
|
|
1413
2420
|
const r = Math.max(2, Math.round(0.012 * Math.max(w, h)));
|
|
1414
|
-
const energy =
|
|
2421
|
+
const energy = boxMean(detail, w, h, r);
|
|
1415
2422
|
const sharp = new Float32Array(n);
|
|
1416
2423
|
for (let i = 0; i < n; i++) sharp[i] = Math.log1p(Math.sqrt(energy[i]) * 400);
|
|
1417
|
-
_optionalChain([report, 'optionalCall',
|
|
2424
|
+
_optionalChain([report, 'optionalCall', _29 => _29(0.5, "Separating what is in focus")]);
|
|
1418
2425
|
const sorted = Float32Array.from(sharp).sort();
|
|
1419
2426
|
const top = sorted[Math.floor(n * 0.995)] || 1;
|
|
1420
2427
|
for (let i = 0; i < n; i++) sharp[i] = Math.min(1, sharp[i] / top);
|
|
@@ -1425,20 +2432,20 @@ function focusArea(pixels, options = {}) {
|
|
|
1425
2432
|
let mask = new Float32Array(n);
|
|
1426
2433
|
for (let i = 0; i < n; i++) mask[i] = sharp[i] >= t ? 1 : 0;
|
|
1427
2434
|
const c = 2 * r;
|
|
1428
|
-
const grown =
|
|
2435
|
+
const grown = boxMean(mask, w, h, c);
|
|
1429
2436
|
for (let i = 0; i < n; i++) grown[i] = grown[i] > 1e-3 ? 1 : 0;
|
|
1430
|
-
const shrunk =
|
|
2437
|
+
const shrunk = boxMean(grown, w, h, c);
|
|
1431
2438
|
for (let i = 0; i < n; i++) mask[i] = shrunk[i] >= 0.999 ? 1 : 0;
|
|
1432
|
-
const pulled =
|
|
2439
|
+
const pulled = boxMean(mask, w, h, Math.max(1, Math.round(r / 2)));
|
|
1433
2440
|
for (let i = 0; i < n; i++) mask[i] = pulled[i] >= 0.999 ? 1 : 0;
|
|
1434
|
-
_optionalChain([report, 'optionalCall',
|
|
2441
|
+
_optionalChain([report, 'optionalCall', _30 => _30(0.7, "Fitting the edge")]);
|
|
1435
2442
|
const softness = Math.max(0, _nullishCoalesce(options.softness, () => ( 4))) * scale;
|
|
1436
2443
|
if (softness > 0) {
|
|
1437
|
-
const fitted =
|
|
2444
|
+
const fitted = guidedFilterColor(workPlanes(work), mask, w, h, Math.max(1, Math.round(softness * 2)), 1e-3);
|
|
1438
2445
|
for (let i = 0; i < n; i++) mask[i] = Math.min(1, Math.max(0, fitted[i]));
|
|
1439
2446
|
}
|
|
1440
2447
|
const selection = softMaskToSelection(work, mask);
|
|
1441
|
-
_optionalChain([report, 'optionalCall',
|
|
2448
|
+
_optionalChain([report, 'optionalCall', _31 => _31(1, "Done")]);
|
|
1442
2449
|
return { selection, noiseLevel: Math.round(sigma * 255 * 10) / 10 };
|
|
1443
2450
|
}
|
|
1444
2451
|
|
|
@@ -1461,9 +2468,9 @@ function lab(r, g, b) {
|
|
|
1461
2468
|
var sigmoid = (v) => 1 / (1 + Math.exp(-v));
|
|
1462
2469
|
function findSky(pixels, options = {}) {
|
|
1463
2470
|
const report = options.onProgress;
|
|
1464
|
-
_optionalChain([report, 'optionalCall',
|
|
2471
|
+
_optionalChain([report, 'optionalCall', _32 => _32(0, "Reducing the image")]);
|
|
1465
2472
|
const work = workImage(pixels, SKY_SIZE);
|
|
1466
|
-
_optionalChain([report, 'optionalCall',
|
|
2473
|
+
_optionalChain([report, 'optionalCall', _33 => _33(0.2, "Scoring the sky")]);
|
|
1467
2474
|
const { width: w, height: h, rgb, scale } = work;
|
|
1468
2475
|
const n = w * h;
|
|
1469
2476
|
const L = new Float32Array(n);
|
|
@@ -1475,7 +2482,7 @@ function findSky(pixels, options = {}) {
|
|
|
1475
2482
|
A[i] = a;
|
|
1476
2483
|
B[i] = b;
|
|
1477
2484
|
}
|
|
1478
|
-
const luma =
|
|
2485
|
+
const luma = boxMean(workLuma(work), w, h, 1);
|
|
1479
2486
|
const grad = new Float32Array(n);
|
|
1480
2487
|
for (let y = 0; y < h; y++) {
|
|
1481
2488
|
for (let x = 0; x < w; x++) {
|
|
@@ -1486,7 +2493,7 @@ function findSky(pixels, options = {}) {
|
|
|
1486
2493
|
}
|
|
1487
2494
|
}
|
|
1488
2495
|
const roughR = Math.max(1, Math.round(6e-3 * Math.max(w, h)));
|
|
1489
|
-
const rough =
|
|
2496
|
+
const rough = boxMean(grad, w, h, roughR);
|
|
1490
2497
|
const score = new Float32Array(n);
|
|
1491
2498
|
for (let i = 0; i < n; i++) {
|
|
1492
2499
|
const chroma = Math.hypot(A[i], B[i]);
|
|
@@ -1499,7 +2506,7 @@ function findSky(pixels, options = {}) {
|
|
|
1499
2506
|
}
|
|
1500
2507
|
const threshold = Math.min(100, Math.max(0, _nullishCoalesce(options.threshold, () => ( 50))));
|
|
1501
2508
|
const t = 0.55 - (threshold - 50) * 6e-3;
|
|
1502
|
-
_optionalChain([report, 'optionalCall',
|
|
2509
|
+
_optionalChain([report, 'optionalCall', _34 => _34(0.5, "Growing the sky")]);
|
|
1503
2510
|
const sky = new Uint8Array(n);
|
|
1504
2511
|
const stack = [];
|
|
1505
2512
|
const seedRows = Math.max(1, Math.round(h * 0.04));
|
|
@@ -1512,7 +2519,7 @@ function findSky(pixels, options = {}) {
|
|
|
1512
2519
|
}
|
|
1513
2520
|
}
|
|
1514
2521
|
}
|
|
1515
|
-
if (!stack.length) return { selection:
|
|
2522
|
+
if (!stack.length) return { selection: createSelectionMask(pixels.width, pixels.height), reason: "No sky was found along the top of the image." };
|
|
1516
2523
|
const low = t * 0.7;
|
|
1517
2524
|
while (stack.length) {
|
|
1518
2525
|
const p = stack.pop();
|
|
@@ -1556,17 +2563,17 @@ function findSky(pixels, options = {}) {
|
|
|
1556
2563
|
}
|
|
1557
2564
|
front = next;
|
|
1558
2565
|
}
|
|
1559
|
-
if (count < n * 5e-3) return { selection:
|
|
1560
|
-
_optionalChain([report, 'optionalCall',
|
|
2566
|
+
if (count < n * 5e-3) return { selection: createSelectionMask(pixels.width, pixels.height), reason: "No sky was found along the top of the image." };
|
|
2567
|
+
_optionalChain([report, 'optionalCall', _35 => _35(0.7, "Fitting the edge")]);
|
|
1561
2568
|
let mask = Float32Array.from(sky);
|
|
1562
2569
|
const softness = Math.max(0, _nullishCoalesce(options.softness, () => ( 4))) * scale;
|
|
1563
2570
|
if (softness > 0) {
|
|
1564
|
-
const fitted =
|
|
2571
|
+
const fitted = guidedFilterColor(workPlanes(work), mask, w, h, Math.max(1, Math.round(softness * 2)), 1e-3);
|
|
1565
2572
|
mask = new Float32Array(n);
|
|
1566
2573
|
for (let i = 0; i < n; i++) mask[i] = Math.min(1, Math.max(0, fitted[i]));
|
|
1567
2574
|
}
|
|
1568
2575
|
const selection = softMaskToSelection(work, mask);
|
|
1569
|
-
_optionalChain([report, 'optionalCall',
|
|
2576
|
+
_optionalChain([report, 'optionalCall', _36 => _36(1, "Done")]);
|
|
1570
2577
|
return { selection };
|
|
1571
2578
|
}
|
|
1572
2579
|
|
|
@@ -1580,7 +2587,7 @@ function serializeMask(mask) {
|
|
|
1580
2587
|
return { width: mask.width, height: mask.height, bounds: { ...b }, bytes };
|
|
1581
2588
|
}
|
|
1582
2589
|
function deserializeMask(s) {
|
|
1583
|
-
return s.bounds ?
|
|
2590
|
+
return s.bounds ? selectionFromRegion(s.width, s.height, s.bounds, s.bytes) : createSelectionMask(s.width, s.height);
|
|
1584
2591
|
}
|
|
1585
2592
|
function runSelectionJob(job, pixels, onProgress) {
|
|
1586
2593
|
switch (job.op) {
|
|
@@ -1608,21 +2615,21 @@ function runSelectionJob(job, pixels, onProgress) {
|
|
|
1608
2615
|
})
|
|
1609
2616
|
};
|
|
1610
2617
|
case "fill": {
|
|
1611
|
-
_optionalChain([onProgress, 'optionalCall',
|
|
2618
|
+
_optionalChain([onProgress, 'optionalCall', _37 => _37(0, "Filling")]);
|
|
1612
2619
|
const filled = _chunkBB6TR422cjs.contentAwareFill.call(void 0, pixels.width, pixels.height, 4, pixels.data, job.hole, {
|
|
1613
2620
|
seed: job.seed,
|
|
1614
2621
|
exclude: job.exclude,
|
|
1615
|
-
onProgress: (f) => _optionalChain([onProgress, 'optionalCall',
|
|
2622
|
+
onProgress: (f) => _optionalChain([onProgress, 'optionalCall', _38 => _38(f * 0.99, "Filling")])
|
|
1616
2623
|
});
|
|
1617
|
-
_optionalChain([onProgress, 'optionalCall',
|
|
1618
|
-
return { mask:
|
|
2624
|
+
_optionalChain([onProgress, 'optionalCall', _39 => _39(1, "Done")]);
|
|
2625
|
+
return { mask: createSelectionMask(1, 1), pixels: Uint8ClampedArray.from(filled) };
|
|
1619
2626
|
}
|
|
1620
2627
|
case "refine-preview": {
|
|
1621
|
-
_optionalChain([onProgress, 'optionalCall',
|
|
2628
|
+
_optionalChain([onProgress, 'optionalCall', _40 => _40(0, "Refining")]);
|
|
1622
2629
|
const base = job.base ? deserializeMask(job.base) : null;
|
|
1623
2630
|
const { selection, refineBrush } = applyRefineEdits(pixels, base, job.edits, pixels.width, pixels.height);
|
|
1624
|
-
const mask = _optionalChain([selection, 'optionalAccess',
|
|
1625
|
-
_optionalChain([onProgress, 'optionalCall',
|
|
2631
|
+
const mask = _optionalChain([selection, 'optionalAccess', _41 => _41.bounds]) ? refineSelection(pixels, selection, job.params, refineBrush) : createSelectionMask(pixels.width, pixels.height);
|
|
2632
|
+
_optionalChain([onProgress, 'optionalCall', _42 => _42(1, "Done")]);
|
|
1626
2633
|
return { mask };
|
|
1627
2634
|
}
|
|
1628
2635
|
}
|
|
@@ -1650,20 +2657,20 @@ function startSelectionWorker(scope) {
|
|
|
1650
2657
|
};
|
|
1651
2658
|
}
|
|
1652
2659
|
var abortError = () => typeof DOMException === "function" ? new DOMException("The selection was cancelled.", "AbortError") : Object.assign(new Error("The selection was cancelled."), { name: "AbortError" });
|
|
1653
|
-
var SelectionWorkerClient = (
|
|
1654
|
-
constructor(factory) {;
|
|
2660
|
+
var SelectionWorkerClient = (_class3 = class {
|
|
2661
|
+
constructor(factory) {;_class3.prototype.__init6.call(this);_class3.prototype.__init7.call(this);_class3.prototype.__init8.call(this);_class3.prototype.__init9.call(this);_class3.prototype.__init10.call(this);
|
|
1655
2662
|
this.factory = factory;
|
|
1656
2663
|
}
|
|
1657
2664
|
|
|
1658
|
-
|
|
2665
|
+
__init6() {this.worker = null}
|
|
1659
2666
|
/** Pixel buffers this worker already has, by their data array. */
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
2667
|
+
__init7() {this.sent = /* @__PURE__ */ new WeakMap()}
|
|
2668
|
+
__init8() {this.nextKey = 1}
|
|
2669
|
+
__init9() {this.nextId = 1}
|
|
2670
|
+
__init10() {this.pending = /* @__PURE__ */ new Map()}
|
|
1664
2671
|
/** Runs `job` on `pixels` in the worker. Aborting `signal` terminates the worker and rejects with an `AbortError`. */
|
|
1665
2672
|
run(job, pixels, options = {}) {
|
|
1666
|
-
if (_optionalChain([options, 'access',
|
|
2673
|
+
if (_optionalChain([options, 'access', _43 => _43.signal, 'optionalAccess', _44 => _44.aborted])) return Promise.reject(abortError());
|
|
1667
2674
|
const worker = this.ensure();
|
|
1668
2675
|
let key = this.sent.get(pixels.data);
|
|
1669
2676
|
if (key === void 0) {
|
|
@@ -1674,7 +2681,7 @@ var SelectionWorkerClient = (_class2 = class {
|
|
|
1674
2681
|
const id = this.nextId++;
|
|
1675
2682
|
return new Promise((resolve, reject) => {
|
|
1676
2683
|
this.pending.set(id, { resolve, reject, onProgress: options.onProgress });
|
|
1677
|
-
_optionalChain([options, 'access',
|
|
2684
|
+
_optionalChain([options, 'access', _45 => _45.signal, 'optionalAccess', _46 => _46.addEventListener, 'call', _47 => _47(
|
|
1678
2685
|
"abort",
|
|
1679
2686
|
() => {
|
|
1680
2687
|
if (this.pending.has(id)) this.terminate(abortError());
|
|
@@ -1686,7 +2693,7 @@ var SelectionWorkerClient = (_class2 = class {
|
|
|
1686
2693
|
}
|
|
1687
2694
|
/** Stops the worker; jobs still running reject with `reason` (default an `AbortError`). */
|
|
1688
2695
|
terminate(reason = abortError()) {
|
|
1689
|
-
_optionalChain([this, 'access',
|
|
2696
|
+
_optionalChain([this, 'access', _48 => _48.worker, 'optionalAccess', _49 => _49.terminate, 'optionalCall', _50 => _50()]);
|
|
1690
2697
|
this.worker = null;
|
|
1691
2698
|
this.sent = /* @__PURE__ */ new WeakMap();
|
|
1692
2699
|
const pending = [...this.pending.values()];
|
|
@@ -1700,18 +2707,64 @@ var SelectionWorkerClient = (_class2 = class {
|
|
|
1700
2707
|
const msg = event.data;
|
|
1701
2708
|
const p = this.pending.get(msg.id);
|
|
1702
2709
|
if (!p) return;
|
|
1703
|
-
if (msg.type === "progress") _optionalChain([p, 'access',
|
|
2710
|
+
if (msg.type === "progress") _optionalChain([p, 'access', _51 => _51.onProgress, 'optionalCall', _52 => _52(msg.fraction, msg.stage)]);
|
|
1704
2711
|
else {
|
|
1705
2712
|
this.pending.delete(msg.id);
|
|
1706
2713
|
if (msg.type === "error") p.reject(new Error(msg.message));
|
|
1707
2714
|
else p.resolve({ mask: deserializeMask(msg.mask), reason: msg.reason, noiseLevel: msg.noiseLevel, pixels: msg.pixels });
|
|
1708
2715
|
}
|
|
1709
2716
|
};
|
|
1710
|
-
worker.onerror = (event) => this.terminate(new Error(_optionalChain([event, 'optionalAccess',
|
|
2717
|
+
worker.onerror = (event) => this.terminate(new Error(_optionalChain([event, 'optionalAccess', _53 => _53.message]) || "The selection worker failed."));
|
|
1711
2718
|
this.worker = worker;
|
|
1712
2719
|
return worker;
|
|
1713
2720
|
}
|
|
1714
|
-
},
|
|
2721
|
+
}, _class3);
|
|
2722
|
+
|
|
2723
|
+
|
|
2724
|
+
|
|
2725
|
+
|
|
2726
|
+
|
|
2727
|
+
|
|
2728
|
+
|
|
2729
|
+
|
|
2730
|
+
|
|
2731
|
+
|
|
2732
|
+
|
|
2733
|
+
|
|
2734
|
+
|
|
2735
|
+
|
|
2736
|
+
|
|
2737
|
+
|
|
2738
|
+
|
|
2739
|
+
|
|
2740
|
+
|
|
2741
|
+
|
|
2742
|
+
|
|
2743
|
+
|
|
2744
|
+
|
|
2745
|
+
|
|
2746
|
+
|
|
2747
|
+
|
|
2748
|
+
|
|
2749
|
+
|
|
2750
|
+
|
|
2751
|
+
|
|
2752
|
+
|
|
2753
|
+
|
|
2754
|
+
|
|
2755
|
+
|
|
2756
|
+
|
|
2757
|
+
|
|
2758
|
+
|
|
2759
|
+
|
|
2760
|
+
|
|
2761
|
+
|
|
2762
|
+
|
|
2763
|
+
|
|
2764
|
+
|
|
2765
|
+
|
|
2766
|
+
|
|
2767
|
+
|
|
1715
2768
|
|
|
1716
2769
|
|
|
1717
2770
|
|
|
@@ -1751,4 +2804,4 @@ var SelectionWorkerClient = (_class2 = class {
|
|
|
1751
2804
|
|
|
1752
2805
|
|
|
1753
2806
|
|
|
1754
|
-
exports.polygonCoverage = polygonCoverage; exports.polygonCoverageBox = polygonCoverageBox; exports.polygonSelection = polygonSelection; exports.normalizeRect = normalizeRect; exports.rectSelection = rectSelection; exports.ellipsePoints = ellipsePoints; exports.ellipseSelection = ellipseSelection; exports.GridGraph = GridGraph; exports.WORK_SIZE = WORK_SIZE; exports.workImage = workImage; exports.GAMMA = GAMMA; exports.UNITS = UNITS; exports.smoothnessWeights = smoothnessWeights; exports.boxBlur = boxBlur; exports.upscaleRegion = upscaleRegion; exports.ColorGmm = ColorGmm; exports.grabCutLabels = grabCutLabels; exports.keepMainPieces = keepMainPieces; exports.selectObjectIn = selectObjectIn; exports.quickSelectStroke = quickSelectStroke; exports.REFINE_EDIT_KINDS = REFINE_EDIT_KINDS; exports.brushStrokeMask = brushStrokeMask; exports.applyRefineEdits = applyRefineEdits; exports.workPlanes = workPlanes; exports.workLuma = workLuma; exports.softMaskToSelection = softMaskToSelection; exports.slicSuperpixels = slicSuperpixels; exports.superpixelSaliency = superpixelSaliency; exports.otsuThreshold = otsuThreshold; exports.findSubject = findSubject; exports.focusArea = focusArea; exports.findSky = findSky; exports.serializeMask = serializeMask; exports.deserializeMask = deserializeMask; exports.runSelectionJob = runSelectionJob; exports.startSelectionWorker = startSelectionWorker; exports.SelectionWorkerClient = SelectionWorkerClient;
|
|
2807
|
+
exports.SELECTION_MODES = SELECTION_MODES; exports.isSelectionMode = isSelectionMode; exports.parseSelectionMode = parseSelectionMode; exports.computeSelectionBounds = computeSelectionBounds; exports.selectionFromTiles = selectionFromTiles; exports.createSelectionMask = createSelectionMask; exports.selectionFromRegion = selectionFromRegion; exports.fullSelection = fullSelection; exports.isSelectionEmpty = isSelectionEmpty; exports.selectionValueAt = selectionValueAt; exports.coverageToByte = coverageToByte; exports.selectionFromCoverage = selectionFromCoverage; exports.selectionToCoverage = selectionToCoverage; exports.selectionArea = selectionArea; exports.combineSelections = combineSelections; exports.invertSelection = invertSelection; exports.resizeSelection = resizeSelection; exports.selectionFromPixels = selectionFromPixels; exports.selectionToPixels = selectionToPixels; exports.encodeSelectionRle = encodeSelectionRle; exports.decodeSelectionRle = decodeSelectionRle; exports.serializeSelection = serializeSelection; exports.deserializeSelection = deserializeSelection; exports.hashMaskContent = hashMaskContent; exports.EMPTY_DISTANCE = EMPTY_DISTANCE; exports.distanceTransform = distanceTransform; exports.expandCoverage = expandCoverage; exports.contractCoverage = contractCoverage; exports.borderCoverage = borderCoverage; exports.smoothCoverage = smoothCoverage; exports.featherCoverage = featherCoverage; exports.greyMorphology = greyMorphology; exports.shiftCoverage = shiftCoverage; exports.SELECTION_MODIFY_OPS = SELECTION_MODIFY_OPS; exports.modifySelection = modifySelection; exports.DEFAULT_REFINE_EDGE_PARAMS = DEFAULT_REFINE_EDGE_PARAMS; exports.GUIDED_FILTER_EPS = GUIDED_FILTER_EPS; exports.normalizeRefineEdgeParams = normalizeRefineEdgeParams; exports.refineEdgeHalo = refineEdgeHalo; exports.boxMean = boxMean; exports.guidedFilterColor = guidedFilterColor; exports.edgeWidth = edgeWidth; exports.refineCoverage = refineCoverage; exports.guidePlanes = guidePlanes; exports.refineSelection = refineSelection; exports.decontaminateColors = decontaminateColors; exports.polygonCoverage = polygonCoverage; exports.polygonCoverageBox = polygonCoverageBox; exports.polygonSelection = polygonSelection; exports.normalizeRect = normalizeRect; exports.rectSelection = rectSelection; exports.ellipsePoints = ellipsePoints; exports.ellipseSelection = ellipseSelection; exports.GridGraph = GridGraph; exports.WORK_SIZE = WORK_SIZE; exports.workImage = workImage; exports.GAMMA = GAMMA; exports.UNITS = UNITS; exports.smoothnessWeights = smoothnessWeights; exports.boxBlur = boxBlur; exports.upscaleRegion = upscaleRegion; exports.ColorGmm = ColorGmm; exports.grabCutLabels = grabCutLabels; exports.keepMainPieces = keepMainPieces; exports.selectObjectIn = selectObjectIn; exports.quickSelectStroke = quickSelectStroke; exports.REFINE_EDIT_KINDS = REFINE_EDIT_KINDS; exports.brushStrokeMask = brushStrokeMask; exports.applyRefineEdits = applyRefineEdits; exports.workPlanes = workPlanes; exports.workLuma = workLuma; exports.softMaskToSelection = softMaskToSelection; exports.slicSuperpixels = slicSuperpixels; exports.superpixelSaliency = superpixelSaliency; exports.otsuThreshold = otsuThreshold; exports.findSubject = findSubject; exports.focusArea = focusArea; exports.findSky = findSky; exports.serializeMask = serializeMask; exports.deserializeMask = deserializeMask; exports.runSelectionJob = runSelectionJob; exports.startSelectionWorker = startSelectionWorker; exports.SelectionWorkerClient = SelectionWorkerClient;
|