@pixerate/editor 0.207.0 → 0.209.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/animation-editor/index.cjs +60 -59
  2. package/dist/animation-editor/index.js +6 -5
  3. package/dist/{brushstrokeRender-7LXCU7PD.js → brushstrokeRender-C3UU6GVC.js} +5 -4
  4. package/dist/{brushstrokeRender-VO4ZTWVA.cjs → brushstrokeRender-EAJNE6WP.cjs} +12 -11
  5. package/dist/canvas/index.cjs +8 -8
  6. package/dist/canvas/index.js +1 -1
  7. package/dist/{chunk-2N2QPO3H.js → chunk-2PBZBRP5.js} +9 -7
  8. package/dist/{chunk-7NW46N7I.cjs → chunk-3EHPLDFQ.cjs} +54 -52
  9. package/dist/{chunk-H367YTXI.js → chunk-42TWUPNJ.js} +13 -387
  10. package/dist/chunk-474IQG74.cjs +384 -0
  11. package/dist/{chunk-MLC7HUHO.js → chunk-BGOCAUXH.js} +1 -1
  12. package/dist/chunk-DWYQPL2A.js +2010 -0
  13. package/dist/{chunk-YXKLVD56.js → chunk-F7YA3MMC.js} +2 -2
  14. package/dist/{chunk-ZZGPYNS2.js → chunk-G4E6RUPC.js} +1 -1
  15. package/dist/chunk-HVVR5PZW.js +384 -0
  16. package/dist/{chunk-FR2Q2LVF.cjs → chunk-I7G3RSUJ.cjs} +35 -35
  17. package/dist/chunk-K6PXS6DO.cjs +102 -0
  18. package/dist/{chunk-ZIDUPSP4.js → chunk-KFIOEOCQ.js} +24 -761
  19. package/dist/{chunk-V7GWZIZK.cjs → chunk-KWNMBMKG.cjs} +23 -21
  20. package/dist/chunk-L3G23N5Y.js +763 -0
  21. package/dist/{chunk-GHCXLQ47.js → chunk-NDMXB4PR.js} +2 -2
  22. package/dist/{chunk-CWM3CUUX.cjs → chunk-OLNIPTB3.cjs} +39 -413
  23. package/dist/{chunk-EBDCRTZI.cjs → chunk-PQ3H2AAN.cjs} +92 -90
  24. package/dist/{chunk-BRKVNXDZ.cjs → chunk-QAUIZPYI.cjs} +7 -7
  25. package/dist/{chunk-UYXL6ZWQ.cjs → chunk-QE7D7TZR.cjs} +494 -1231
  26. package/dist/chunk-QYLWG22N.cjs +763 -0
  27. package/dist/{chunk-YQ5C5BVK.cjs → chunk-ROVK6AXX.cjs} +60 -60
  28. package/dist/{chunk-HGTDMKNE.cjs → chunk-UOGKSVRQ.cjs} +126 -221
  29. package/dist/{chunk-EL5PAHNP.js → chunk-USQCKRZG.js} +6 -4
  30. package/dist/{chunk-36OKQDCF.js → chunk-WJ2RBZMM.js} +5 -3
  31. package/dist/chunk-X4LUGTQX.cjs +2010 -0
  32. package/dist/{chunk-NQMP3CWF.js → chunk-YBJXCQTR.js} +15 -110
  33. package/dist/chunk-ZIF4UJJ6.js +102 -0
  34. package/dist/{chunk-WTW7QRTS.cjs → chunk-ZMHPQ6HP.cjs} +1 -1
  35. package/dist/image-editor/controller.d.cts +53 -5
  36. package/dist/image-editor/controller.d.ts +53 -5
  37. package/dist/image-editor/index.cjs +1806 -3356
  38. package/dist/image-editor/index.js +462 -2012
  39. package/dist/image-editor/selection/index.d.cts +1 -0
  40. package/dist/image-editor/selection/index.d.ts +1 -0
  41. package/dist/image-editor/selection/segment/focus.d.cts +2 -0
  42. package/dist/image-editor/selection/segment/focus.d.ts +2 -0
  43. package/dist/image-editor/selection/segment/grabcut.d.cts +4 -2
  44. package/dist/image-editor/selection/segment/grabcut.d.ts +4 -2
  45. package/dist/image-editor/selection/segment/sky.d.cts +2 -0
  46. package/dist/image-editor/selection/segment/sky.d.ts +2 -0
  47. package/dist/image-editor/selection/segment/subject.d.cts +2 -1
  48. package/dist/image-editor/selection/segment/subject.d.ts +2 -1
  49. package/dist/image-editor/selection/segment/work.d.cts +5 -0
  50. package/dist/image-editor/selection/segment/work.d.ts +5 -0
  51. package/dist/image-editor/selection/select-and-mask.d.cts +12 -2
  52. package/dist/image-editor/selection/select-and-mask.d.ts +12 -2
  53. package/dist/image-editor/selection/tool-options.d.cts +3 -1
  54. package/dist/image-editor/selection/tool-options.d.ts +3 -1
  55. package/dist/image-editor/selection/worker.d.cts +126 -0
  56. package/dist/image-editor/selection/worker.d.ts +126 -0
  57. package/dist/image-editor/selection-worker.cjs +14 -0
  58. package/dist/image-editor/selection-worker.d.cts +12 -0
  59. package/dist/image-editor/selection-worker.d.ts +12 -0
  60. package/dist/image-editor/selection-worker.js +14 -0
  61. package/dist/image-editor/types.d.cts +22 -0
  62. package/dist/image-editor/types.d.ts +22 -0
  63. package/dist/paint/index.cjs +6 -5
  64. package/dist/paint/index.js +5 -4
  65. package/dist/paint-editor/index.cjs +394 -390
  66. package/dist/paint-editor/index.js +18 -14
  67. package/dist/raster/index.cjs +22 -18
  68. package/dist/raster/index.js +29 -25
  69. package/dist/vector/index.cjs +986 -985
  70. package/dist/vector/index.js +6 -5
  71. package/package.json +11 -1
  72. package/skills/pixerate-image-editor/SKILL.md +1 -1
@@ -0,0 +1,763 @@
1
+ import {
2
+ gaussianBlur
3
+ } from "./chunk-ZIF4UJJ6.js";
4
+ import {
5
+ TILE,
6
+ TiledSurface,
7
+ contentBounds,
8
+ finishTile,
9
+ readPixel,
10
+ readRegion,
11
+ tileCoord,
12
+ tileKey,
13
+ uniformTile,
14
+ writeRegion
15
+ } from "./chunk-HVVR5PZW.js";
16
+
17
+ // src/image-editor/selection/mask.ts
18
+ var SELECTION_MODES = ["replace", "add", "subtract", "intersect"];
19
+ function isSelectionMode(value) {
20
+ return typeof value === "string" && SELECTION_MODES.includes(value);
21
+ }
22
+ function parseSelectionMode(value) {
23
+ return isSelectionMode(value) ? value : "replace";
24
+ }
25
+ var MAX_SIDE = 1 << 15;
26
+ function checkSize(width, height) {
27
+ if (!Number.isInteger(width) || !Number.isInteger(height) || width < 1 || height < 1 || width > MAX_SIDE || height > MAX_SIDE) {
28
+ throw new Error(`Invalid selection size ${width}\xD7${height}`);
29
+ }
30
+ }
31
+ function computeSelectionBounds(width, height, data) {
32
+ let x0 = width;
33
+ let x1 = -1;
34
+ let y0 = -1;
35
+ let y1 = -1;
36
+ for (let y = 0; y < height; y++) {
37
+ const row = y * width;
38
+ let first = -1;
39
+ for (let x = 0; x < width; x++) {
40
+ if (data[row + x] !== 0) {
41
+ first = x;
42
+ break;
43
+ }
44
+ }
45
+ if (first < 0) continue;
46
+ let last = first;
47
+ for (let x = width - 1; x > first; x--) {
48
+ if (data[row + x] !== 0) {
49
+ last = x;
50
+ break;
51
+ }
52
+ }
53
+ if (y0 < 0) y0 = y;
54
+ y1 = y;
55
+ if (first < x0) x0 = first;
56
+ if (last > x1) x1 = last;
57
+ }
58
+ if (y0 < 0) return null;
59
+ return { x: x0, y: y0, width: x1 - x0 + 1, height: y1 - y0 + 1 };
60
+ }
61
+ var TILE_PIXELS = TILE * TILE;
62
+ var MATERIALIZED_LIMIT = 4;
63
+ var materialized = /* @__PURE__ */ new Map();
64
+ var lastRead = null;
65
+ var lastData = null;
66
+ var TiledSelectionMask = class {
67
+ constructor(width, height, surface, bounds, data) {
68
+ this.width = width;
69
+ this.height = height;
70
+ this.surface = surface;
71
+ this.cachedBounds = bounds;
72
+ if (data) this.remember(data);
73
+ }
74
+ width;
75
+ height;
76
+ surface;
77
+ cachedBounds;
78
+ weak = null;
79
+ get bounds() {
80
+ if (this.cachedBounds === void 0) this.cachedBounds = contentBounds(this.surface);
81
+ return this.cachedBounds;
82
+ }
83
+ get data() {
84
+ if (lastRead === this) return lastData;
85
+ let d = materialized.get(this);
86
+ if (d) {
87
+ materialized.delete(this);
88
+ materialized.set(this, d);
89
+ lastRead = this;
90
+ lastData = d;
91
+ return d;
92
+ }
93
+ d = this.weak?.deref();
94
+ if (!d) {
95
+ const all = readRegion(this.surface, { x: 0, y: 0, width: this.width, height: this.height });
96
+ d = new Uint8Array(all.buffer, all.byteOffset, all.length);
97
+ }
98
+ this.remember(d);
99
+ return d;
100
+ }
101
+ remember(d) {
102
+ lastRead = this;
103
+ lastData = d;
104
+ this.weak = typeof WeakRef === "function" ? new WeakRef(d) : null;
105
+ materialized.set(this, d);
106
+ while (materialized.size > MATERIALIZED_LIMIT) materialized.delete(materialized.keys().next().value);
107
+ }
108
+ };
109
+ function tileGrid(width, height) {
110
+ return { cols: Math.ceil(width / TILE), rows: Math.ceil(height / TILE) };
111
+ }
112
+ function insideTile(tx, ty, width, height) {
113
+ return (tx + 1) * TILE <= width && (ty + 1) * TILE <= height;
114
+ }
115
+ function surfaceFromCoverage(width, height, data) {
116
+ const tiles = /* @__PURE__ */ new Map();
117
+ const { cols, rows } = tileGrid(width, height);
118
+ for (let ty = 0; ty < rows; ty++) {
119
+ for (let tx = 0; tx < cols; tx++) {
120
+ const ox = tx * TILE;
121
+ const oy = ty * TILE;
122
+ const w = Math.min(TILE, width - ox);
123
+ const h = Math.min(TILE, height - oy);
124
+ let lo = 255;
125
+ let hi = 0;
126
+ for (let y = 0; y < h && !(hi > 0 && lo < hi); y++) {
127
+ const row = (oy + y) * width + ox;
128
+ for (let x = 0; x < w; x++) {
129
+ const v = data[row + x];
130
+ if (v < lo) lo = v;
131
+ if (v > hi) hi = v;
132
+ }
133
+ }
134
+ if (hi === 0) continue;
135
+ if (lo === hi && w === TILE && h === TILE) {
136
+ tiles.set(tileKey(tx, ty), uniformTile(1, hi));
137
+ continue;
138
+ }
139
+ const t = new Uint8ClampedArray(TILE_PIXELS);
140
+ for (let y = 0; y < h; y++) {
141
+ const row = (oy + y) * width + ox;
142
+ for (let x = 0; x < w; x++) t[y * TILE + x] = data[row + x];
143
+ }
144
+ tiles.set(tileKey(tx, ty), { data: t, uniform: null });
145
+ }
146
+ }
147
+ return new TiledSurface(1, tiles, 0);
148
+ }
149
+ function clipTile(tile, w, h) {
150
+ const d = tile.data;
151
+ let outside = false;
152
+ for (let y = 0; y < TILE && !outside; y++) {
153
+ for (let x = y < h ? w : 0; x < TILE; x++) {
154
+ if (d[y * TILE + x] !== 0) {
155
+ outside = true;
156
+ break;
157
+ }
158
+ }
159
+ }
160
+ if (!outside) return tile;
161
+ const t = new Uint8ClampedArray(TILE_PIXELS);
162
+ for (let y = 0; y < h; y++) t.set(d.subarray(y * TILE, y * TILE + w), y * TILE);
163
+ return finishTile(t, 1, 0);
164
+ }
165
+ function selectionFromTiles(width, height, surface) {
166
+ checkSize(width, height);
167
+ if (surface.channels !== 1) throw new Error("A selection needs a 1-channel surface");
168
+ if (surface.defaultValue !== 0) {
169
+ const all = readRegion(surface, { x: 0, y: 0, width, height });
170
+ return createSelectionMask(width, height, new Uint8Array(all.buffer, all.byteOffset, all.length));
171
+ }
172
+ const { cols, rows } = tileGrid(width, height);
173
+ let tiles = null;
174
+ for (const [key, tile] of surface.tiles) {
175
+ const { tx, ty } = tileCoord(key);
176
+ let next = tile;
177
+ if (tx < 0 || ty < 0 || tx >= cols || ty >= rows) next = null;
178
+ else if (!insideTile(tx, ty, width, height)) next = clipTile(tile, Math.min(TILE, width - tx * TILE), Math.min(TILE, height - ty * TILE));
179
+ if (next === tile) continue;
180
+ tiles ??= new Map(surface.tiles);
181
+ if (next) tiles.set(key, next);
182
+ else tiles.delete(key);
183
+ }
184
+ return new TiledSelectionMask(width, height, tiles ? new TiledSurface(1, tiles, 0) : surface);
185
+ }
186
+ function createSelectionMask(width, height, data) {
187
+ checkSize(width, height);
188
+ if (!data) return new TiledSelectionMask(width, height, new TiledSurface(1, /* @__PURE__ */ new Map(), 0), null);
189
+ if (data.length !== width * height) throw new Error("Selection data does not match its size");
190
+ return new TiledSelectionMask(width, height, surfaceFromCoverage(width, height, data), void 0, data);
191
+ }
192
+ function selectionFromRegion(width, height, rect, data) {
193
+ checkSize(width, height);
194
+ 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");
195
+ return new TiledSelectionMask(width, height, writeRegion(new TiledSurface(1, /* @__PURE__ */ new Map(), 0), rect, data));
196
+ }
197
+ function fullSelection(width, height) {
198
+ checkSize(width, height);
199
+ const tiles = /* @__PURE__ */ new Map();
200
+ const { cols, rows } = tileGrid(width, height);
201
+ for (let ty = 0; ty < rows; ty++) {
202
+ for (let tx = 0; tx < cols; tx++) {
203
+ if (insideTile(tx, ty, width, height)) {
204
+ tiles.set(tileKey(tx, ty), uniformTile(1, 255));
205
+ continue;
206
+ }
207
+ const t = new Uint8ClampedArray(TILE_PIXELS);
208
+ const w = Math.min(TILE, width - tx * TILE);
209
+ for (let y = 0; y < Math.min(TILE, height - ty * TILE); y++) t.fill(255, y * TILE, y * TILE + w);
210
+ tiles.set(tileKey(tx, ty), { data: t, uniform: null });
211
+ }
212
+ }
213
+ return new TiledSelectionMask(width, height, new TiledSurface(1, tiles, 0), { x: 0, y: 0, width, height });
214
+ }
215
+ function isSelectionEmpty(mask) {
216
+ return !mask || mask.bounds === null;
217
+ }
218
+ function selectionValueAt(mask, x, y) {
219
+ const xi = Math.floor(x);
220
+ const yi = Math.floor(y);
221
+ if (xi < 0 || yi < 0 || xi >= mask.width || yi >= mask.height) return 0;
222
+ return readPixel(mask.surface, xi, yi)[0] / 255;
223
+ }
224
+ function coverageToByte(v) {
225
+ if (!(v > 0)) return 0;
226
+ if (v >= 1) return 255;
227
+ return Math.floor(v * 255 + 0.5);
228
+ }
229
+ function selectionFromCoverage(width, height, coverage) {
230
+ const n = width * height;
231
+ const out = new Uint8Array(n);
232
+ for (let i = 0; i < n; i++) out[i] = coverageToByte(coverage[i]);
233
+ return createSelectionMask(width, height, out);
234
+ }
235
+ function selectionToCoverage(mask) {
236
+ const out = new Float32Array(mask.data.length);
237
+ for (let i = 0; i < out.length; i++) out[i] = mask.data[i] / 255;
238
+ return out;
239
+ }
240
+ function selectionArea(mask) {
241
+ if (!mask || !mask.bounds) return 0;
242
+ let sum = 0;
243
+ for (const tile of mask.surface.tiles.values()) {
244
+ if (tile.uniform !== null) {
245
+ sum += tile.uniform * TILE_PIXELS;
246
+ continue;
247
+ }
248
+ const d = tile.data;
249
+ for (let i = 0; i < d.length; i++) sum += d[i];
250
+ }
251
+ return sum / 255;
252
+ }
253
+ function combineSelections(prev, next, mode) {
254
+ let result;
255
+ if (mode === "replace" || !prev && mode === "add") {
256
+ result = next;
257
+ } else if (!prev) {
258
+ return null;
259
+ } else {
260
+ if (prev.width !== next.width || prev.height !== next.height) throw new Error("Selections differ in size");
261
+ result = new TiledSelectionMask(prev.width, prev.height, combineSurfaces(prev.surface, next.surface, mode));
262
+ }
263
+ return result.bounds ? result : null;
264
+ }
265
+ var FULL = 255;
266
+ function combineTile(a, b, mode) {
267
+ if (mode === "add") {
268
+ if (!b || a?.uniform === FULL) return a;
269
+ if (!a || b.uniform === FULL) return b;
270
+ } else if (mode === "subtract") {
271
+ if (!a || !b) return a;
272
+ if (b.uniform === FULL) return void 0;
273
+ } else {
274
+ if (!a || !b) return void 0;
275
+ if (a.uniform === FULL) return b;
276
+ if (b.uniform === FULL) return a;
277
+ }
278
+ const x = a.data;
279
+ const y = b.data;
280
+ const out = new Uint8ClampedArray(TILE_PIXELS);
281
+ if (mode === "add") for (let i = 0; i < out.length; i++) out[i] = x[i] > y[i] ? x[i] : y[i];
282
+ else if (mode === "subtract") for (let i = 0; i < out.length; i++) out[i] = x[i] > y[i] ? x[i] - y[i] : 0;
283
+ else for (let i = 0; i < out.length; i++) out[i] = x[i] < y[i] ? x[i] : y[i];
284
+ return finishTile(out, 1, 0) ?? void 0;
285
+ }
286
+ function combineSurfaces(a, b, mode) {
287
+ const tiles = /* @__PURE__ */ new Map();
288
+ for (const key of /* @__PURE__ */ new Set([...a.tiles.keys(), ...b.tiles.keys()])) {
289
+ const tile = combineTile(a.tiles.get(key), b.tiles.get(key), mode);
290
+ if (tile) tiles.set(key, tile);
291
+ }
292
+ return new TiledSurface(1, tiles, 0);
293
+ }
294
+ function invertSelection(mask) {
295
+ const { width, height } = mask;
296
+ const tiles = /* @__PURE__ */ new Map();
297
+ const { cols, rows } = tileGrid(width, height);
298
+ for (let ty = 0; ty < rows; ty++) {
299
+ for (let tx = 0; tx < cols; tx++) {
300
+ const src = mask.surface.getTile(tx, ty);
301
+ const key = tileKey(tx, ty);
302
+ if (insideTile(tx, ty, width, height)) {
303
+ if (!src) tiles.set(key, uniformTile(1, FULL));
304
+ else if (src.uniform !== null) {
305
+ if (src.uniform !== FULL) tiles.set(key, uniformTile(1, FULL - src.uniform));
306
+ } else {
307
+ const out2 = new Uint8ClampedArray(TILE_PIXELS);
308
+ for (let i = 0; i < out2.length; i++) out2[i] = FULL - src.data[i];
309
+ const t2 = finishTile(out2, 1, 0);
310
+ if (t2) tiles.set(key, t2);
311
+ }
312
+ continue;
313
+ }
314
+ const w = Math.min(TILE, width - tx * TILE);
315
+ const h = Math.min(TILE, height - ty * TILE);
316
+ const out = new Uint8ClampedArray(TILE_PIXELS);
317
+ for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) out[y * TILE + x] = FULL - (src ? src.data[y * TILE + x] : 0);
318
+ const t = finishTile(out, 1, 0);
319
+ if (t) tiles.set(key, t);
320
+ }
321
+ }
322
+ return new TiledSelectionMask(width, height, new TiledSurface(1, tiles, 0));
323
+ }
324
+ function resizeSelection(mask, width, height) {
325
+ if (mask.width === width && mask.height === height) return mask;
326
+ checkSize(width, height);
327
+ const out = new Uint8Array(width * height);
328
+ const sx = mask.width / width;
329
+ const sy = mask.height / height;
330
+ const src = mask.data;
331
+ const mw = mask.width;
332
+ for (let y = 0; y < height; y++) {
333
+ const fy = Math.min(mask.height - 1, Math.max(0, (y + 0.5) * sy - 0.5));
334
+ const y0 = Math.floor(fy);
335
+ const y1 = Math.min(mask.height - 1, y0 + 1);
336
+ const ty = fy - y0;
337
+ for (let x = 0; x < width; x++) {
338
+ const fx = Math.min(mw - 1, Math.max(0, (x + 0.5) * sx - 0.5));
339
+ const x0 = Math.floor(fx);
340
+ const x1 = Math.min(mw - 1, x0 + 1);
341
+ const tx = fx - x0;
342
+ const top = src[y0 * mw + x0] * (1 - tx) + src[y0 * mw + x1] * tx;
343
+ const bot = src[y1 * mw + x0] * (1 - tx) + src[y1 * mw + x1] * tx;
344
+ out[y * width + x] = Math.round(top * (1 - ty) + bot * ty);
345
+ }
346
+ }
347
+ return createSelectionMask(width, height, out);
348
+ }
349
+ function selectionFromPixels(pixels, options = {}) {
350
+ const { width, height, data } = pixels;
351
+ checkSize(width, height);
352
+ if (data.length < width * height * 4) throw new Error("Pixel data is shorter than width \xD7 height \xD7 4");
353
+ const channel = options.channel ?? "luminance";
354
+ const out = new Uint8Array(width * height);
355
+ const threshold = options.threshold;
356
+ for (let i = 0, p = 0; i < out.length; i++, p += 4) {
357
+ let v;
358
+ switch (channel) {
359
+ case "alpha":
360
+ v = data[p + 3];
361
+ break;
362
+ case "red":
363
+ v = data[p];
364
+ break;
365
+ case "green":
366
+ v = data[p + 1];
367
+ break;
368
+ case "blue":
369
+ v = data[p + 2];
370
+ break;
371
+ default:
372
+ v = Math.round((0.2126 * data[p] + 0.7152 * data[p + 1] + 0.0722 * data[p + 2]) * data[p + 3] / 255);
373
+ }
374
+ if (options.invert) v = 255 - v;
375
+ if (threshold !== void 0) v = v >= threshold ? 255 : 0;
376
+ out[i] = v;
377
+ }
378
+ return createSelectionMask(width, height, out);
379
+ }
380
+ function selectionToPixels(mask, as = "gray") {
381
+ const out = new Uint8ClampedArray(mask.data.length * 4);
382
+ for (let i = 0, p = 0; i < mask.data.length; i++, p += 4) {
383
+ const v = mask.data[i];
384
+ if (as === "gray") {
385
+ out[p] = out[p + 1] = out[p + 2] = v;
386
+ out[p + 3] = 255;
387
+ } else {
388
+ out[p] = out[p + 1] = out[p + 2] = 255;
389
+ out[p + 3] = v;
390
+ }
391
+ }
392
+ return { width: mask.width, height: mask.height, data: out };
393
+ }
394
+ var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
395
+ var B64_INDEX = (() => {
396
+ const t = new Int16Array(128).fill(-1);
397
+ for (let i = 0; i < B64.length; i++) t[B64.charCodeAt(i)] = i;
398
+ return t;
399
+ })();
400
+ function toBase64(bytes, length) {
401
+ const parts = [];
402
+ let chunk = "";
403
+ for (let i = 0; i < length; i += 3) {
404
+ const a = bytes[i];
405
+ const b = i + 1 < length ? bytes[i + 1] : 0;
406
+ const c = i + 2 < length ? bytes[i + 2] : 0;
407
+ chunk += B64[a >> 2] + B64[(a & 3) << 4 | b >> 4];
408
+ chunk += i + 1 < length ? B64[(b & 15) << 2 | c >> 6] : "=";
409
+ chunk += i + 2 < length ? B64[c & 63] : "=";
410
+ if (chunk.length >= 8192) {
411
+ parts.push(chunk);
412
+ chunk = "";
413
+ }
414
+ }
415
+ parts.push(chunk);
416
+ return parts.join("");
417
+ }
418
+ function fromBase64(text) {
419
+ if (text.length % 4 !== 0) throw new Error("Invalid base64 length");
420
+ let pad = 0;
421
+ if (text.endsWith("==")) pad = 2;
422
+ else if (text.endsWith("=")) pad = 1;
423
+ const out = new Uint8Array(text.length / 4 * 3 - pad);
424
+ let o = 0;
425
+ const digit = (i, k) => {
426
+ const ch = text.charCodeAt(i + k);
427
+ if (ch === 61 && i + 4 === text.length && k >= 4 - pad) return 0;
428
+ const idx = ch < 128 ? B64_INDEX[ch] : -1;
429
+ if (idx < 0) throw new Error("Invalid base64 character");
430
+ return idx;
431
+ };
432
+ for (let i = 0; i < text.length; i += 4) {
433
+ const n = digit(i, 0) << 18 | digit(i, 1) << 12 | digit(i, 2) << 6 | digit(i, 3);
434
+ if (o < out.length) out[o++] = n >> 16 & 255;
435
+ if (o < out.length) out[o++] = n >> 8 & 255;
436
+ if (o < out.length) out[o++] = n & 255;
437
+ }
438
+ return out;
439
+ }
440
+ function encodeSelectionRle(data) {
441
+ let buf = new Uint8Array(64);
442
+ let len = 0;
443
+ const push = (b) => {
444
+ if (len === buf.length) {
445
+ const next = new Uint8Array(buf.length * 2);
446
+ next.set(buf);
447
+ buf = next;
448
+ }
449
+ buf[len++] = b;
450
+ };
451
+ let i = 0;
452
+ while (i < data.length) {
453
+ const v = data[i];
454
+ let j = i + 1;
455
+ while (j < data.length && data[j] === v) j++;
456
+ push(v);
457
+ let run = j - i;
458
+ while (run >= 128) {
459
+ push(run & 127 | 128);
460
+ run = Math.floor(run / 128);
461
+ }
462
+ push(run);
463
+ i = j;
464
+ }
465
+ return "r1:" + toBase64(buf, len);
466
+ }
467
+ function decodeSelectionRle(rle, length) {
468
+ if (typeof rle !== "string" || !rle.startsWith("r1:")) throw new Error("Unknown selection encoding");
469
+ const bytes = fromBase64(rle.slice(3));
470
+ const out = new Uint8Array(length);
471
+ let o = 0;
472
+ let i = 0;
473
+ while (i < bytes.length) {
474
+ const v = bytes[i++];
475
+ let run = 0;
476
+ let scale = 1;
477
+ for (; ; ) {
478
+ if (i >= bytes.length) throw new Error("Truncated selection run");
479
+ const b = bytes[i++];
480
+ run += (b & 127) * scale;
481
+ if (b < 128) break;
482
+ scale *= 128;
483
+ if (scale > 2 ** 35) throw new Error("Selection run too long");
484
+ }
485
+ if (run === 0 || o + run > length) throw new Error("Selection runs do not match its size");
486
+ if (v !== 0) out.fill(v, o, o + run);
487
+ o += run;
488
+ }
489
+ if (o !== length) throw new Error("Selection runs do not match its size");
490
+ return out;
491
+ }
492
+ function serializeSelection(mask) {
493
+ return { width: mask.width, height: mask.height, rle: encodeSelectionRle(mask.data) };
494
+ }
495
+ function deserializeSelection(value) {
496
+ if (!value || typeof value !== "object") throw new Error("selection must be an object");
497
+ const v = value;
498
+ const width = v.width;
499
+ const height = v.height;
500
+ checkSize(width, height);
501
+ return createSelectionMask(width, height, decodeSelectionRle(v.rle, width * height));
502
+ }
503
+ function hashMaskContent(content) {
504
+ let h = 2166136261;
505
+ if (typeof content === "string") {
506
+ for (let i = 0; i < content.length; i++) {
507
+ h ^= content.charCodeAt(i);
508
+ h = Math.imul(h, 16777619);
509
+ }
510
+ } else {
511
+ for (let i = 0; i < content.length; i++) {
512
+ h ^= content[i];
513
+ h = Math.imul(h, 16777619);
514
+ }
515
+ }
516
+ return `${(h >>> 0).toString(16)}:${content.length}`;
517
+ }
518
+
519
+ // src/image-editor/selection/modify.ts
520
+ var EMPTY_DISTANCE = 1e10;
521
+ function distanceTransform(inside, width, height) {
522
+ const n = width * height;
523
+ const out = new Float32Array(n);
524
+ let any = false;
525
+ let all = true;
526
+ for (let i = 0; i < n; i++) {
527
+ if (inside[i]) any = true;
528
+ else all = false;
529
+ }
530
+ if (!any) return out.fill(EMPTY_DISTANCE);
531
+ if (all) return out;
532
+ const NONE = 4294967295;
533
+ const vertical = new Uint32Array(n).fill(NONE);
534
+ for (let x = 0; x < width; x++) {
535
+ let nearest = -1;
536
+ for (let y = 0; y < height; y++) {
537
+ const i = y * width + x;
538
+ if (inside[i]) nearest = y;
539
+ if (nearest >= 0) vertical[i] = y - nearest;
540
+ }
541
+ nearest = -1;
542
+ for (let y = height - 1; y >= 0; y--) {
543
+ const i = y * width + x;
544
+ if (vertical[i] === 0) nearest = y;
545
+ if (nearest >= 0) vertical[i] = Math.min(vertical[i], nearest - y);
546
+ }
547
+ }
548
+ const sites = new Int32Array(width);
549
+ const starts = new Float64Array(width);
550
+ for (let y = 0; y < height; y++) {
551
+ const row = y * width;
552
+ let count = 0;
553
+ for (let q = 0; q < width; q++) {
554
+ const dq = vertical[row + q];
555
+ if (dq === NONE) continue;
556
+ const fq = dq * dq;
557
+ let start = -Infinity;
558
+ while (count > 0) {
559
+ const p = sites[count - 1];
560
+ const dp = vertical[row + p];
561
+ start = (fq - dp * dp) / (2 * (q - p)) + (q + p) * 0.5;
562
+ if (start > starts[count - 1]) break;
563
+ count--;
564
+ }
565
+ if (count === 0) start = -Infinity;
566
+ sites[count] = q;
567
+ starts[count] = start;
568
+ count++;
569
+ }
570
+ if (count === 0) {
571
+ out.fill(EMPTY_DISTANCE, row, row + width);
572
+ continue;
573
+ }
574
+ let k = 0;
575
+ for (let x = 0; x < width; x++) {
576
+ while (k + 1 < count && starts[k + 1] < x) k++;
577
+ const site = sites[k];
578
+ const dx = x - site;
579
+ const dv = vertical[row + site];
580
+ out[row + x] = Math.sqrt(dx * dx + dv * dv);
581
+ }
582
+ }
583
+ return out;
584
+ }
585
+ function thresholdInside(m, threshold = 0.5) {
586
+ const out = new Uint8Array(m.length);
587
+ for (let i = 0; i < m.length; i++) out[i] = m[i] >= threshold ? 1 : 0;
588
+ return out;
589
+ }
590
+ function expandCoverage(m, w, h, r) {
591
+ const d = distanceTransform(thresholdInside(m), w, h);
592
+ const out = new Float32Array(m.length);
593
+ for (let i = 0; i < out.length; i++) out[i] = Math.max(m[i], Math.min(1, Math.max(0, r + 1 - d[i])));
594
+ return out;
595
+ }
596
+ function contractCoverage(m, w, h, r) {
597
+ const inv = new Float32Array(m.length);
598
+ for (let i = 0; i < inv.length; i++) inv[i] = 1 - m[i];
599
+ const e = expandCoverage(inv, w, h, r);
600
+ for (let i = 0; i < e.length; i++) e[i] = 1 - e[i];
601
+ return e;
602
+ }
603
+ function borderCoverage(m, w, h, r) {
604
+ const inside = thresholdInside(m);
605
+ const outside = new Uint8Array(inside.length);
606
+ for (let i = 0; i < inside.length; i++) outside[i] = inside[i] ? 0 : 1;
607
+ const out = new Float32Array(m.length);
608
+ if (!(r > 0)) return out;
609
+ const toInside = distanceTransform(inside, w, h);
610
+ const toOutside = distanceTransform(outside, w, h);
611
+ for (let i = 0; i < out.length; i++) {
612
+ const d = inside[i] ? toOutside[i] : toInside[i];
613
+ out[i] = Math.min(1, Math.max(0, 1 - (d - 1) / r));
614
+ }
615
+ return out;
616
+ }
617
+ function boxLineReplicate(src, srcOff, stride, len, r, dst) {
618
+ const at = (i) => src[srcOff + Math.min(len - 1, Math.max(0, i)) * stride];
619
+ let sum = 0;
620
+ for (let i = -r; i <= r; i++) sum += at(i);
621
+ const k = 2 * r + 1;
622
+ for (let i = 0; i < len; i++) {
623
+ dst[i] = sum / k;
624
+ sum += at(i + r + 1) - at(i - r);
625
+ }
626
+ }
627
+ function smoothCoverage(m, w, h, r) {
628
+ const rr = Number.isFinite(r) ? Math.max(0, Math.round(r)) : 0;
629
+ const src = Float32Array.from(m);
630
+ const tmp = new Float32Array(src.length);
631
+ const line = new Float32Array(Math.max(w, h));
632
+ if (rr > 0) {
633
+ for (let y = 0; y < h; y++) {
634
+ boxLineReplicate(src, y * w, 1, w, rr, line);
635
+ tmp.set(line.subarray(0, w), y * w);
636
+ }
637
+ for (let x = 0; x < w; x++) {
638
+ boxLineReplicate(tmp, x, w, h, rr, line);
639
+ for (let y = 0; y < h; y++) src[y * w + x] = line[y];
640
+ }
641
+ }
642
+ for (let i = 0; i < src.length; i++) src[i] = src[i] >= 0.5 ? 1 : 0;
643
+ return src;
644
+ }
645
+ function featherCoverage(m, w, h, radius) {
646
+ const out = gaussianBlur(m, w, h, Number.isFinite(radius) ? Math.max(0, radius) : 0, "zero");
647
+ for (let i = 0; i < out.length; i++) if (Math.abs(out[i]) < 1e-7) out[i] = 0;
648
+ return out;
649
+ }
650
+ function runningExtreme(src, off, stride, len, r, max, g, hb, dst) {
651
+ const k = 2 * r + 1;
652
+ const pick = max ? (a, b) => a > b ? a : b : (a, b) => a < b ? a : b;
653
+ const fill = max ? -Infinity : Infinity;
654
+ const plen = len + 2 * r;
655
+ const at = (i) => i < r || i >= r + len ? fill : src[off + (i - r) * stride];
656
+ for (let start = 0; start < plen; start += k) {
657
+ const end = Math.min(plen, start + k);
658
+ g[start] = at(start);
659
+ for (let i = start + 1; i < end; i++) g[i] = pick(g[i - 1], at(i));
660
+ hb[end - 1] = at(end - 1);
661
+ for (let i = end - 2; i >= start; i--) hb[i] = pick(hb[i + 1], at(i));
662
+ }
663
+ for (let i = 0; i < len; i++) {
664
+ const a = hb[i];
665
+ const b = g[i + 2 * r];
666
+ dst[i] = pick(a, b);
667
+ }
668
+ }
669
+ function greyMorphology(src, w, h, r, grow) {
670
+ const out = Float32Array.from(src);
671
+ if (r <= 0) return out;
672
+ const n = Math.max(w, h);
673
+ const g = new Float32Array(n + 2 * r);
674
+ const hb = new Float32Array(n + 2 * r);
675
+ const line = new Float32Array(n);
676
+ for (let y = 0; y < h; y++) {
677
+ runningExtreme(out, y * w, 1, w, r, grow, g, hb, line);
678
+ out.set(line.subarray(0, w), y * w);
679
+ }
680
+ for (let x = 0; x < w; x++) {
681
+ runningExtreme(out, x, w, h, r, grow, g, hb, line);
682
+ for (let y = 0; y < h; y++) out[y * w + x] = line[y];
683
+ }
684
+ return out;
685
+ }
686
+ function shiftCoverage(src, w, h, amount) {
687
+ const mag = Math.abs(amount);
688
+ if (!Number.isFinite(mag) || mag < 1e-4) return Float32Array.from(src);
689
+ const grow = amount > 0;
690
+ const whole = Math.floor(mag);
691
+ const frac = mag - whole;
692
+ if (frac < 1e-4) return greyMorphology(src, w, h, whole, grow);
693
+ const lo = whole === 0 ? Float32Array.from(src) : greyMorphology(src, w, h, whole, grow);
694
+ const hi = greyMorphology(src, w, h, whole + 1, grow);
695
+ for (let i = 0; i < lo.length; i++) lo[i] += (hi[i] - lo[i]) * frac;
696
+ return lo;
697
+ }
698
+ var SELECTION_MODIFY_OPS = ["expand", "contract", "feather", "border", "smooth", "grow", "shrink"];
699
+ function wrap(mask, coverage) {
700
+ const out = new Uint8Array(coverage.length);
701
+ for (let i = 0; i < out.length; i++) out[i] = coverageToByte(coverage[i]);
702
+ return createSelectionMask(mask.width, mask.height, out);
703
+ }
704
+ function modifySelection(mask, op, radius) {
705
+ const r = Number.isFinite(radius) ? Math.max(0, radius) : 0;
706
+ if (r === 0 && op !== "border") return mask;
707
+ const m = selectionToCoverage(mask);
708
+ const { width: w, height: h } = mask;
709
+ switch (op) {
710
+ case "expand":
711
+ case "grow":
712
+ return wrap(mask, expandCoverage(m, w, h, r));
713
+ case "contract":
714
+ case "shrink":
715
+ return wrap(mask, contractCoverage(m, w, h, r));
716
+ case "feather":
717
+ return wrap(mask, featherCoverage(m, w, h, r));
718
+ case "border":
719
+ return wrap(mask, borderCoverage(m, w, h, r));
720
+ case "smooth":
721
+ return wrap(mask, smoothCoverage(m, w, h, r));
722
+ default:
723
+ throw new Error(`Unknown selection modification "${op}"`);
724
+ }
725
+ }
726
+
727
+ export {
728
+ SELECTION_MODES,
729
+ isSelectionMode,
730
+ parseSelectionMode,
731
+ computeSelectionBounds,
732
+ selectionFromTiles,
733
+ createSelectionMask,
734
+ selectionFromRegion,
735
+ fullSelection,
736
+ isSelectionEmpty,
737
+ selectionValueAt,
738
+ coverageToByte,
739
+ selectionFromCoverage,
740
+ selectionToCoverage,
741
+ selectionArea,
742
+ combineSelections,
743
+ invertSelection,
744
+ resizeSelection,
745
+ selectionFromPixels,
746
+ selectionToPixels,
747
+ encodeSelectionRle,
748
+ decodeSelectionRle,
749
+ serializeSelection,
750
+ deserializeSelection,
751
+ hashMaskContent,
752
+ EMPTY_DISTANCE,
753
+ distanceTransform,
754
+ expandCoverage,
755
+ contractCoverage,
756
+ borderCoverage,
757
+ smoothCoverage,
758
+ featherCoverage,
759
+ greyMorphology,
760
+ shiftCoverage,
761
+ SELECTION_MODIFY_OPS,
762
+ modifySelection
763
+ };