@pixerate/editor 0.259.0 → 0.261.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 (80) hide show
  1. package/dist/agent/index.cjs +2 -2
  2. package/dist/agent/index.js +1 -1
  3. package/dist/animation-editor/index.cjs +61 -59
  4. package/dist/animation-editor/index.js +5 -3
  5. package/dist/brushes/index.cjs +52 -52
  6. package/dist/brushes/index.js +6 -6
  7. package/dist/{brushstrokeRender-JI4W6F2G.js → brushstrokeRender-25Q5BEY6.js} +17 -7
  8. package/dist/{brushstrokeRender-MQ6PSDM5.cjs → brushstrokeRender-N6UPZSQS.cjs} +31 -21
  9. package/dist/canvas/brushTypes.d.cts +5 -0
  10. package/dist/canvas/brushTypes.d.ts +5 -0
  11. package/dist/canvas/drawController.d.cts +15 -1
  12. package/dist/canvas/drawController.d.ts +15 -1
  13. package/dist/canvas/index.cjs +136 -102
  14. package/dist/canvas/index.js +35 -1
  15. package/dist/{chunk-3PPJRMIG.js → chunk-3O47TPGX.js} +197 -18
  16. package/dist/{chunk-5Q5MLXAD.cjs → chunk-3TQW5MPW.cjs} +23 -23
  17. package/dist/{chunk-JBBJE76O.cjs → chunk-3ZRNK3RO.cjs} +1138 -85
  18. package/dist/{chunk-YSGW7VV4.cjs → chunk-43NT242D.cjs} +1401 -228
  19. package/dist/{chunk-SMTUQWGY.js → chunk-5IW65VQ6.js} +1 -1
  20. package/dist/{chunk-YQ4PQTBB.js → chunk-6NZL24W4.js} +9 -1
  21. package/dist/{chunk-O3ZVZSNJ.cjs → chunk-7AUQ2UJ3.cjs} +137 -129
  22. package/dist/{chunk-4M4E3N6X.js → chunk-CQVTGBQI.js} +1 -1
  23. package/dist/{chunk-SSFMWJDK.js → chunk-D4TE5JNW.js} +1192 -19
  24. package/dist/{chunk-AORZ5M5C.js → chunk-EHALWBBD.js} +1 -1
  25. package/dist/{chunk-6WCQ35GI.cjs → chunk-NHA26O5X.cjs} +2 -2
  26. package/dist/{chunk-DPYAO5U6.cjs → chunk-PVJMTLPJ.cjs} +197 -18
  27. package/dist/{chunk-MQGQTKVW.cjs → chunk-PYTJBVW5.cjs} +29 -29
  28. package/dist/{chunk-IXQZHCOF.js → chunk-QAR2N7HZ.js} +1 -1
  29. package/dist/{chunk-5P4JEOIV.js → chunk-TMDDMJCR.js} +2 -2
  30. package/dist/{chunk-RSIKDWOP.cjs → chunk-TZOX7KWV.cjs} +15 -15
  31. package/dist/{chunk-IPUTIUGQ.cjs → chunk-UVZ7RQOE.cjs} +1 -1
  32. package/dist/{chunk-A7GJ6BXI.js → chunk-WJ4WQJCC.js} +1062 -9
  33. package/dist/{chunk-ICTPLVSR.js → chunk-X2EXYY4J.js} +1 -1
  34. package/dist/{chunk-WQCNC4DK.js → chunk-XC6DPRYI.js} +695 -4
  35. package/dist/{chunk-KBYB7OWB.cjs → chunk-YHDHFJUC.cjs} +776 -85
  36. package/dist/{chunk-P3PZ63M3.cjs → chunk-YLUIFXSL.cjs} +35 -35
  37. package/dist/commands/index.cjs +2 -2
  38. package/dist/commands/index.js +1 -1
  39. package/dist/image-editor/index.cjs +1385 -2484
  40. package/dist/image-editor/index.js +139 -1238
  41. package/dist/image-editor/selection-worker.cjs +3 -4
  42. package/dist/image-editor/selection-worker.js +1 -2
  43. package/dist/paint/index.cjs +3 -5
  44. package/dist/paint/index.js +40 -42
  45. package/dist/paint-editor/contract.d.cts +29 -0
  46. package/dist/paint-editor/contract.d.ts +29 -0
  47. package/dist/paint-editor/controller.d.cts +17 -0
  48. package/dist/paint-editor/controller.d.ts +17 -0
  49. package/dist/paint-editor/dab-stroke.d.cts +5 -0
  50. package/dist/paint-editor/dab-stroke.d.ts +5 -0
  51. package/dist/paint-editor/index.cjs +610 -327
  52. package/dist/paint-editor/index.d.cts +2 -0
  53. package/dist/paint-editor/index.d.ts +2 -0
  54. package/dist/paint-editor/index.js +337 -54
  55. package/dist/paint-editor/path-samples.d.cts +19 -0
  56. package/dist/paint-editor/path-samples.d.ts +19 -0
  57. package/dist/paint-editor/stamp-tip.d.cts +17 -0
  58. package/dist/paint-editor/stamp-tip.d.ts +17 -0
  59. package/dist/paint-editor/ui.d.cts +27 -1
  60. package/dist/paint-editor/ui.d.ts +27 -1
  61. package/dist/raster/index.cjs +2 -4
  62. package/dist/raster/index.js +6 -8
  63. package/dist/vector/brushstroke.d.cts +6 -0
  64. package/dist/vector/brushstroke.d.ts +6 -0
  65. package/dist/vector/controller.d.cts +11 -2
  66. package/dist/vector/controller.d.ts +11 -2
  67. package/dist/vector/index.cjs +990 -973
  68. package/dist/vector/index.js +28 -11
  69. package/package.json +1 -1
  70. package/skills/pixerate-brushes/SKILL.md +1 -1
  71. package/skills/pixerate-canvas/SKILL.md +3 -1
  72. package/skills/pixerate-paint-editor/SKILL.md +2 -0
  73. package/skills/pixerate-paint-sketch-to-finish/SKILL.md +5 -1
  74. package/skills/pixerate-vector/SKILL.md +2 -2
  75. package/dist/chunk-2VJAVXLI.js +0 -192
  76. package/dist/chunk-62Z5WLRA.js +0 -700
  77. package/dist/chunk-BOHDJJ2O.cjs +0 -192
  78. package/dist/chunk-NKGB64CW.cjs +0 -700
  79. package/dist/chunk-SS53NNZO.cjs +0 -1066
  80. package/dist/chunk-ZNA3IQZF.js +0 -1066
@@ -1,6 +1,25 @@
1
1
  import {
2
+ bestOffset,
3
+ contentAwareFill,
4
+ dilateMask,
5
+ maskBounds,
6
+ membraneFill,
7
+ seamlessClone
8
+ } from "./chunk-VWEWY4FY.js";
9
+ import {
10
+ DabWalker,
11
+ SmudgeTip,
12
+ TileMedium,
13
+ dabRect,
14
+ put255,
15
+ rasterizeDab
16
+ } from "./chunk-GCVFEPJG.js";
17
+ import {
18
+ blendPixel,
2
19
  createPigment,
3
- mixPigments
20
+ mixPigments,
21
+ srgbDecode,
22
+ srgbEncode
4
23
  } from "./chunk-IRSTHLQQ.js";
5
24
  import {
6
25
  TILE,
@@ -80,6 +99,648 @@ function createPigmentTools() {
80
99
  return [mixColors];
81
100
  }
82
101
 
102
+ // src/paint/retouch.ts
103
+ var TONE_RANGES = ["shadows", "midtones", "highlights"];
104
+ function luma(r, g, b) {
105
+ return 0.299 * r + 0.587 * g + 0.114 * b;
106
+ }
107
+ function dodgeBurnCurve(v, e, range, burn) {
108
+ const x = Math.max(0, v);
109
+ const k = Math.min(1, Math.max(0, e));
110
+ const c = Math.min(1, x);
111
+ if (range === "midtones") {
112
+ const w2 = Math.min(1, Math.max(0, 4 * x * (1 - x)));
113
+ return x + w2 * (Math.pow(c, burn ? 1 + k : 1 / (1 + k)) - x);
114
+ }
115
+ const w = range === "shadows" ? (1 - c) * (1 - c) : c * c;
116
+ return burn ? x - k * w * x : x + k * w * (1 - c);
117
+ }
118
+ function compressGamut(c, l) {
119
+ const L = Math.min(1, Math.max(0, l));
120
+ const lo = Math.min(c[0], c[1], c[2]);
121
+ const hi = Math.max(c[0], c[1], c[2]);
122
+ let t = 1;
123
+ if (hi > 1 && hi - L > 1e-9) t = Math.min(t, (1 - L) / (hi - L));
124
+ if (lo < 0 && L - lo > 1e-9) t = Math.min(t, L / (L - lo));
125
+ for (let i = 0; i < 3; i++) c[i] = L + (c[i] - L) * t;
126
+ }
127
+ function dodgeBurn(c, e, range, burn, protectTones) {
128
+ if (!(e > 0)) return c;
129
+ if (protectTones) {
130
+ const l = luma(c[0], c[1], c[2]);
131
+ const nl = dodgeBurnCurve(l, e, range, burn);
132
+ if (l > 1e-6) for (let i = 0; i < 3; i++) c[i] *= nl / l;
133
+ else c[0] = c[1] = c[2] = nl;
134
+ compressGamut(c, nl);
135
+ } else for (let i = 0; i < 3; i++) c[i] = dodgeBurnCurve(c[i], e, range, burn);
136
+ return c;
137
+ }
138
+ function saturationOf(r, g, b) {
139
+ const lo = Math.min(r, g, b);
140
+ const hi = Math.max(r, g, b);
141
+ return hi <= 1e-6 ? 0 : Math.min(1, Math.max(0, (hi - lo) / hi));
142
+ }
143
+ function sponge(c, amount, saturate, vibrance) {
144
+ let a = Math.min(1, Math.max(0, amount));
145
+ if (!(a > 0)) return c;
146
+ if (vibrance) {
147
+ const s = saturationOf(c[0], c[1], c[2]);
148
+ a *= saturate ? 1 - s : 0.25 + 0.75 * s;
149
+ }
150
+ const l = luma(c[0], c[1], c[2]);
151
+ const k = saturate ? 1 + a : 1 - a;
152
+ for (let i = 0; i < 3; i++) c[i] = l + (c[i] - l) * k;
153
+ compressGamut(c, l);
154
+ return c;
155
+ }
156
+ function perDabStrength(e, spacing) {
157
+ return 1 - Math.pow(1 - Math.min(1, Math.max(0, e)), Math.min(1, Math.max(0.01, spacing)));
158
+ }
159
+ var RETOUCH_SAMPLES = ["current", "current-below", "all"];
160
+ var SPOT_HEAL_TYPES = ["proximity-match", "content-aware", "smooth"];
161
+ var DEFAULT_RETOUCH = {
162
+ aligned: true,
163
+ sample: "current",
164
+ spotType: "proximity-match",
165
+ range: "midtones",
166
+ exposure: 0.5,
167
+ protectTones: true,
168
+ spongeMode: "desaturate",
169
+ vibrance: true,
170
+ strength: 0.5,
171
+ historySteps: 0
172
+ };
173
+ function normalizeRetouch(raw = {}, base = DEFAULT_RETOUCH) {
174
+ const unit2 = (v, d) => typeof v === "number" && Number.isFinite(v) ? Math.min(1, Math.max(0, v)) : d;
175
+ const bool = (v, d) => typeof v === "boolean" ? v : d;
176
+ const pick = (v, all, d) => all.includes(v) ? v : d;
177
+ return {
178
+ aligned: bool(raw.aligned, base.aligned),
179
+ sample: pick(raw.sample, RETOUCH_SAMPLES, base.sample),
180
+ spotType: pick(raw.spotType, SPOT_HEAL_TYPES, base.spotType),
181
+ range: pick(raw.range, TONE_RANGES, base.range),
182
+ exposure: unit2(raw.exposure, base.exposure),
183
+ protectTones: bool(raw.protectTones, base.protectTones),
184
+ spongeMode: pick(raw.spongeMode, ["desaturate", "saturate"], base.spongeMode),
185
+ vibrance: bool(raw.vibrance, base.vibrance),
186
+ strength: unit2(raw.strength, base.strength),
187
+ historySteps: typeof raw.historySteps === "number" && Number.isFinite(raw.historySteps) ? Math.min(1e3, Math.max(0, Math.round(raw.historySteps))) : base.historySteps
188
+ };
189
+ }
190
+ function boxBlurPixels(data, w, h, n, r) {
191
+ if (r < 1) return;
192
+ if (n === 4) for (let i = 0; i < data.length; i += 4) for (let c = 0; c < 3; c++) data[i + c] *= data[i + 3] / 255;
193
+ const tmp = new Float32Array(data.length);
194
+ const k = 1 / (2 * r + 1);
195
+ for (let y = 0; y < h; y++) {
196
+ for (let x = 0; x < w; x++) {
197
+ for (let c = 0; c < n; c++) {
198
+ let s = 0;
199
+ for (let d = -r; d <= r; d++) s += data[(y * w + Math.min(w - 1, Math.max(0, x + d))) * n + c];
200
+ tmp[(y * w + x) * n + c] = s * k;
201
+ }
202
+ }
203
+ }
204
+ for (let y = 0; y < h; y++) {
205
+ for (let x = 0; x < w; x++) {
206
+ for (let c = 0; c < n; c++) {
207
+ let s = 0;
208
+ for (let d = -r; d <= r; d++) s += tmp[(Math.min(h - 1, Math.max(0, y + d)) * w + x) * n + c];
209
+ data[(y * w + x) * n + c] = s * k;
210
+ }
211
+ }
212
+ }
213
+ if (n === 4) {
214
+ for (let i = 0; i < data.length; i += 4) {
215
+ const a = data[i + 3] / 255;
216
+ for (let c = 0; c < 3; c++) data[i + c] = a > 0 ? data[i + c] / a : 0;
217
+ }
218
+ }
219
+ }
220
+ function retouchFilter(data, w, h, n, kind, radius = 2, amount = 1) {
221
+ if (kind === "blur") {
222
+ boxBlurPixels(data, w, h, n, radius);
223
+ boxBlurPixels(data, w, h, n, radius);
224
+ return;
225
+ }
226
+ const soft = data.slice();
227
+ boxBlurPixels(soft, w, h, n, 1);
228
+ for (let i = 0; i < data.length; i++) {
229
+ if (n === 4 && (i & 3) === 3) continue;
230
+ const v = data[i] + amount * (data[i] - soft[i]);
231
+ data[i] = v > 0 ? v : 0;
232
+ }
233
+ }
234
+ var IDENTITY_CLONE_TRANSFORM = { scaleX: 1, scaleY: 1, rotate: 0 };
235
+ function isIdentityCloneTransform(t) {
236
+ return t.scaleX === 1 && t.scaleY === 1 && (t.rotate % 360 + 360) % 360 === 0;
237
+ }
238
+ function cloneSamplePoint(t, source, anchor, x, y) {
239
+ const a = -t.rotate * Math.PI / 180;
240
+ const c = Math.cos(a);
241
+ const s = Math.sin(a);
242
+ const dx = x - anchor.x;
243
+ const dy = y - anchor.y;
244
+ return [source.x + (c * dx - s * dy) / t.scaleX, source.y + (s * dx + c * dy) / t.scaleY];
245
+ }
246
+ var TransformedPixelSource = class {
247
+ constructor(inner, map) {
248
+ this.inner = inner;
249
+ this.map = map;
250
+ }
251
+ inner;
252
+ map;
253
+ px = [0, 0, 0, 0];
254
+ sample(x, y, out) {
255
+ const [u, v] = this.map(x, y);
256
+ const x0 = Math.floor(u);
257
+ const y0 = Math.floor(v);
258
+ const fx = u - x0;
259
+ const fy = v - y0;
260
+ let r = 0;
261
+ let g = 0;
262
+ let b = 0;
263
+ let a = 0;
264
+ for (let k = 0; k < 4; k++) {
265
+ const w = (k & 1 ? fx : 1 - fx) * (k & 2 ? fy : 1 - fy);
266
+ if (w <= 0) continue;
267
+ if (!this.inner.sample(x0 + (k & 1), y0 + (k >> 1), this.px)) continue;
268
+ const pa = this.px[3] * w;
269
+ r += this.px[0] * pa;
270
+ g += this.px[1] * pa;
271
+ b += this.px[2] * pa;
272
+ a += pa;
273
+ }
274
+ if (!(a > 0)) {
275
+ out[0] = out[1] = out[2] = out[3] = 0;
276
+ return false;
277
+ }
278
+ out[0] = r / a;
279
+ out[1] = g / a;
280
+ out[2] = b / a;
281
+ out[3] = a;
282
+ return true;
283
+ }
284
+ };
285
+ var TilePixelSource = class {
286
+ constructor(readTile, channels, offset = { x: 0, y: 0 }) {
287
+ this.readTile = readTile;
288
+ this.channels = channels;
289
+ this.offset = offset;
290
+ }
291
+ readTile;
292
+ channels;
293
+ offset;
294
+ cache = /* @__PURE__ */ new Map();
295
+ lastKey = NaN;
296
+ last = null;
297
+ sample(x, y, out) {
298
+ const lx = x - this.offset.x;
299
+ const ly = y - this.offset.y;
300
+ const tx = Math.floor(lx / TILE);
301
+ const ty = Math.floor(ly / TILE);
302
+ const key = tileKey(tx, ty);
303
+ if (key !== this.lastKey) {
304
+ let t2 = this.cache.get(key);
305
+ if (t2 === void 0) {
306
+ t2 = this.readTile(tx, ty);
307
+ this.cache.set(key, t2);
308
+ }
309
+ this.last = t2;
310
+ this.lastKey = key;
311
+ }
312
+ const t = this.last;
313
+ if (!t) {
314
+ out[0] = out[1] = out[2] = out[3] = 0;
315
+ return false;
316
+ }
317
+ const p = (ly - ty * TILE) * TILE + (lx - tx * TILE);
318
+ if (this.channels === 1) {
319
+ out[0] = out[1] = out[2] = t[p];
320
+ out[3] = 255;
321
+ return true;
322
+ }
323
+ const i = p * 4;
324
+ out[0] = t[i];
325
+ out[1] = t[i + 1];
326
+ out[2] = t[i + 2];
327
+ out[3] = t[i + 3];
328
+ return out[3] > 0;
329
+ }
330
+ };
331
+ var RegionPixelSource = class {
332
+ constructor(rect, data) {
333
+ this.rect = rect;
334
+ this.data = data;
335
+ }
336
+ rect;
337
+ data;
338
+ sample(x, y, out) {
339
+ const r = this.rect;
340
+ const lx = x - r.x;
341
+ const ly = y - r.y;
342
+ if (lx < 0 || ly < 0 || lx >= r.width || ly >= r.height) {
343
+ out[0] = out[1] = out[2] = out[3] = 0;
344
+ return false;
345
+ }
346
+ const i = (ly * r.width + lx) * 4;
347
+ out[0] = this.data[i];
348
+ out[1] = this.data[i + 1];
349
+ out[2] = this.data[i + 2];
350
+ out[3] = this.data[i + 3];
351
+ return out[3] > 0;
352
+ }
353
+ };
354
+ function readSourceRegion(source, rect, dx = 0, dy = 0) {
355
+ const out = new Float32Array(rect.width * rect.height * 4);
356
+ const px = [0, 0, 0, 0];
357
+ for (let y = 0; y < rect.height; y++) {
358
+ for (let x = 0; x < rect.width; x++) {
359
+ source.sample(rect.x + x + dx, rect.y + y + dy, px);
360
+ const i = (y * rect.width + x) * 4;
361
+ out[i] = px[0];
362
+ out[i + 1] = px[1];
363
+ out[i + 2] = px[2];
364
+ out[i + 3] = px[3];
365
+ }
366
+ }
367
+ return out;
368
+ }
369
+ function applyRetouchToTile(coverage, tileData, channels, tx, ty, paint, s, options = {}) {
370
+ const out = (options.into ?? tileData).slice();
371
+ const ox = tx * TILE + s.offset.x;
372
+ const oy = ty * TILE + s.offset.y;
373
+ let x0 = 0;
374
+ let y0 = 0;
375
+ let x1 = TILE;
376
+ let y1 = TILE;
377
+ if (options.region) {
378
+ const r = options.region;
379
+ x0 = Math.max(0, Math.floor(r.x) - ox);
380
+ y0 = Math.max(0, Math.floor(r.y) - oy);
381
+ x1 = Math.min(TILE, Math.ceil(r.x + r.width) - ox);
382
+ y1 = Math.min(TILE, Math.ceil(r.y + r.height) - oy);
383
+ if (x1 <= x0 || y1 <= y0) return out;
384
+ if (options.into) {
385
+ for (let y = y0; y < y1; y++) out.set(tileData.subarray((y * TILE + x0) * channels, (y * TILE + x1) * channels), (y * TILE + x0) * channels);
386
+ }
387
+ }
388
+ const sel = s.selection;
389
+ const blend = s.blend ?? "normal";
390
+ const src = [0, 0, 0, 0];
391
+ const rgb = [0, 0, 0];
392
+ const cb = [0, 0, 0, 0];
393
+ const cs = [0, 0, 0, 0];
394
+ let covKey = NaN;
395
+ let covTile;
396
+ for (let y = y0; y < y1; y++) {
397
+ const dy = oy + y;
398
+ const cty = Math.floor(dy / TILE);
399
+ for (let x = x0; x < x1; x++) {
400
+ const dx = ox + x;
401
+ const ctx = Math.floor(dx / TILE);
402
+ const key = tileKey(ctx, cty);
403
+ if (key !== covKey) {
404
+ covKey = key;
405
+ covTile = coverage.tiles.get(key);
406
+ }
407
+ if (!covTile) continue;
408
+ const c = covTile[(dy - cty * TILE) * TILE + (dx - ctx * TILE)];
409
+ if (c <= 0) continue;
410
+ let k = c * s.opacity;
411
+ if (sel) {
412
+ if (dx < 0 || dy < 0 || dx >= sel.width || dy >= sel.height) continue;
413
+ k *= sel.data[dy * sel.width + dx] / 255;
414
+ }
415
+ if (k > 1) k = 1;
416
+ if (k <= 0) continue;
417
+ const p = y * TILE + x;
418
+ if (channels === 1) {
419
+ const g = out[p];
420
+ let t;
421
+ if (paint.kind === "tone") {
422
+ rgb[0] = rgb[1] = rgb[2] = g / 255;
423
+ paint.tone(rgb);
424
+ t = luma(rgb[0], rgb[1], rgb[2]) * 255;
425
+ } else if (paint.kind === "color") t = paint.color[0];
426
+ else {
427
+ if (!paint.source.sample(dx + paint.dx, dy + paint.dy, src)) continue;
428
+ t = src[0];
429
+ k *= src[3] / 255;
430
+ }
431
+ out[p] = Math.round(g + (t - g) * k);
432
+ continue;
433
+ }
434
+ const i = p * 4;
435
+ const a = out[i + 3];
436
+ if (paint.kind === "tone") {
437
+ if (a === 0) continue;
438
+ rgb[0] = out[i] / 255;
439
+ rgb[1] = out[i + 1] / 255;
440
+ rgb[2] = out[i + 2] / 255;
441
+ const r0 = rgb[0];
442
+ const g0 = rgb[1];
443
+ const b0 = rgb[2];
444
+ paint.tone(rgb);
445
+ put255(out, i, r0 + (rgb[0] - r0) * k);
446
+ put255(out, i + 1, g0 + (rgb[1] - g0) * k);
447
+ put255(out, i + 2, b0 + (rgb[2] - b0) * k);
448
+ continue;
449
+ }
450
+ if (s.lockTransparency && a === 0) continue;
451
+ if (paint.kind === "color") {
452
+ src[0] = paint.color[0];
453
+ src[1] = paint.color[1];
454
+ src[2] = paint.color[2];
455
+ src[3] = paint.color[3];
456
+ } else if (!paint.source.sample(dx + paint.dx, dy + paint.dy, src)) continue;
457
+ for (let ch = 0; ch < 4; ch++) {
458
+ cb[ch] = out[i + ch] / 255;
459
+ cs[ch] = src[ch] / 255;
460
+ }
461
+ const o = blendPixel(blend, cb, cs, k);
462
+ put255(out, i, o[0]);
463
+ put255(out, i + 1, o[1]);
464
+ put255(out, i + 2, o[2]);
465
+ if (!s.lockTransparency) put255(out, i + 3, o[3]);
466
+ }
467
+ }
468
+ return out;
469
+ }
470
+ function applyToneToDeepTile(coverage, samples, tx, ty, tone, s, options = {}) {
471
+ const linear = samples instanceof Float32Array;
472
+ const out = (options.into ?? samples).slice();
473
+ const ox = tx * TILE + s.offset.x;
474
+ const oy = ty * TILE + s.offset.y;
475
+ let x0 = 0;
476
+ let y0 = 0;
477
+ let x1 = TILE;
478
+ let y1 = TILE;
479
+ if (options.region) {
480
+ const r = options.region;
481
+ x0 = Math.max(0, Math.floor(r.x) - ox);
482
+ y0 = Math.max(0, Math.floor(r.y) - oy);
483
+ x1 = Math.min(TILE, Math.ceil(r.x + r.width) - ox);
484
+ y1 = Math.min(TILE, Math.ceil(r.y + r.height) - oy);
485
+ if (x1 <= x0 || y1 <= y0) return out;
486
+ if (options.into) for (let y = y0; y < y1; y++) out.set(samples.subarray((y * TILE + x0) * 4, (y * TILE + x1) * 4), (y * TILE + x0) * 4);
487
+ }
488
+ const sel = s.selection;
489
+ const rgb = [0, 0, 0];
490
+ const before = [0, 0, 0];
491
+ const excess = [0, 0, 0];
492
+ let covKey = NaN;
493
+ let covTile;
494
+ for (let y = y0; y < y1; y++) {
495
+ const dy = oy + y;
496
+ const cty = Math.floor(dy / TILE);
497
+ for (let x = x0; x < x1; x++) {
498
+ const dx = ox + x;
499
+ const ctx = Math.floor(dx / TILE);
500
+ const key = tileKey(ctx, cty);
501
+ if (key !== covKey) {
502
+ covKey = key;
503
+ covTile = coverage.tiles.get(key);
504
+ }
505
+ if (!covTile) continue;
506
+ const c = covTile[(dy - cty * TILE) * TILE + (dx - ctx * TILE)];
507
+ if (c <= 0) continue;
508
+ let k = c * s.opacity;
509
+ if (sel) {
510
+ if (dx < 0 || dy < 0 || dx >= sel.width || dy >= sel.height) continue;
511
+ k *= sel.data[dy * sel.width + dx] / 255;
512
+ }
513
+ if (k > 1) k = 1;
514
+ if (k <= 0) continue;
515
+ const i = (y * TILE + x) * 4;
516
+ if (out[i + 3] === 0) continue;
517
+ for (let ch = 0; ch < 3; ch++) {
518
+ const v = out[i + ch];
519
+ if (linear) {
520
+ excess[ch] = v > 1 ? v - 1 : 0;
521
+ rgb[ch] = srgbEncode(v > 1 ? 1 : v < 0 ? 0 : v);
522
+ } else rgb[ch] = v / 65535;
523
+ before[ch] = rgb[ch];
524
+ }
525
+ tone(rgb);
526
+ for (let ch = 0; ch < 3; ch++) {
527
+ const e = before[ch] + (rgb[ch] - before[ch]) * k;
528
+ if (linear) out[i + ch] = srgbDecode(e) + excess[ch];
529
+ else out[i + ch] = e <= 0 ? 0 : e >= 1 ? 65535 : Math.round(e * 65535);
530
+ }
531
+ }
532
+ }
533
+ return out;
534
+ }
535
+ function toneOf(tool, settings) {
536
+ const burn = tool === "burn";
537
+ return (rgb) => {
538
+ dodgeBurn(rgb, settings.exposure, settings.range, burn, settings.protectTones);
539
+ };
540
+ }
541
+ function healPixels(w, h, src, dst, mask) {
542
+ const out = new Float32Array(dst);
543
+ const bb = maskBounds(w, h, mask);
544
+ if (!bb) return out;
545
+ const x0 = Math.max(0, bb[0] - 1);
546
+ const y0 = Math.max(0, bb[1] - 1);
547
+ const x1 = Math.min(w, bb[2] + 1);
548
+ const y1 = Math.min(h, bb[3] + 1);
549
+ const rw = x1 - x0;
550
+ const rh = y1 - y0;
551
+ const s = new Float32Array(rw * rh * 4);
552
+ const d = new Float32Array(rw * rh * 4);
553
+ const m = new Uint8Array(rw * rh);
554
+ for (let y = 0; y < rh; y++) {
555
+ for (let x = 0; x < rw; x++) {
556
+ const gi = (y + y0) * w + x + x0;
557
+ const li = y * rw + x;
558
+ m[li] = mask[gi] ? 1 : 0;
559
+ for (let c = 0; c < 4; c++) {
560
+ s[li * 4 + c] = src[gi * 4 + c];
561
+ d[li * 4 + c] = dst[gi * 4 + c];
562
+ }
563
+ }
564
+ }
565
+ const healed = seamlessClone(rw, rh, 4, s, d, m);
566
+ for (let y = 0; y < rh; y++) {
567
+ for (let x = 0; x < rw; x++) {
568
+ const gi = ((y + y0) * w + x + x0) * 4;
569
+ const li = (y * rw + x) * 4;
570
+ for (let c = 0; c < 4; c++) out[gi + c] = Math.min(255, Math.max(0, healed[li + c]));
571
+ }
572
+ }
573
+ return out;
574
+ }
575
+ function spotHealPixels(w, h, img, hole, size, type, onOffset) {
576
+ const domain = dilateMask(w, h, hole, 2);
577
+ const healMask = dilateMask(w, h, hole, 1);
578
+ let filled = null;
579
+ let bestFound = null;
580
+ if (type === "proximity-match") {
581
+ const ring = Math.min(16, Math.max(3, Math.floor(size / 8)));
582
+ const off2 = bestOffset(w, h, 4, img, domain, ring, Math.floor(Math.max(w, h) / 2));
583
+ if (off2) {
584
+ bestFound = off2;
585
+ filled = new Float32Array(img);
586
+ const [ddx, ddy] = off2;
587
+ for (let i = 0; i < w * h; i++) {
588
+ if (!domain[i]) continue;
589
+ const x = i % w + ddx;
590
+ const y = Math.floor(i / w) + ddy;
591
+ const j = (y * w + x) * 4;
592
+ for (let c = 0; c < 4; c++) filled[i * 4 + c] = img[j + c];
593
+ }
594
+ }
595
+ }
596
+ onOffset?.(filled ? bestFound : null);
597
+ if (!filled && type === "content-aware") filled = contentAwareFill(w, h, 4, img, domain);
598
+ if (!filled) filled = membraneFill(w, h, 4, img, domain);
599
+ return healPixels(w, h, filled, img, healMask);
600
+ }
601
+ function spotHealWindow(bounds, canvas) {
602
+ const g = Math.max(8, Math.ceil(Math.max(bounds.width, bounds.height)));
603
+ const x0 = Math.max(0, bounds.x - g);
604
+ const y0 = Math.max(0, bounds.y - g);
605
+ const x1 = Math.min(canvas.width, bounds.x + bounds.width + g);
606
+ const y1 = Math.min(canvas.height, bounds.y + bounds.height + g);
607
+ return { x: x0, y: y0, width: Math.max(0, x1 - x0), height: Math.max(0, y1 - y0) };
608
+ }
609
+ var RetouchDabStroke = class {
610
+ constructor(brush, target, effect) {
611
+ this.brush = brush;
612
+ this.effect = effect;
613
+ this.walker = new DabWalker(brush);
614
+ this.medium = new TileMedium(target);
615
+ }
616
+ brush;
617
+ effect;
618
+ dabCount = 0;
619
+ bounds = null;
620
+ walker;
621
+ medium;
622
+ smoothPos = null;
623
+ buf = new Float32Array(0);
624
+ push(points) {
625
+ const dabs = [];
626
+ for (const p of points) this.walker.push(this.smooth(p), dabs);
627
+ return this.paint(dabs);
628
+ }
629
+ finish(last) {
630
+ const dabs = [];
631
+ if (last && this.smoothPos && (this.smoothPos.x !== last.x || this.smoothPos.y !== last.y)) this.walker.push(last, dabs);
632
+ this.walker.finish(dabs);
633
+ return this.paint(dabs);
634
+ }
635
+ smooth(p) {
636
+ const a = 1 - Math.min(0.95, Math.max(0, this.brush.smoothing));
637
+ const cur = this.smoothPos;
638
+ const n = cur && a < 1 ? { ...p, x: cur.x + (p.x - cur.x) * a, y: cur.y + (p.y - cur.y) * a } : p;
639
+ this.smoothPos = n;
640
+ return n;
641
+ }
642
+ paint(dabs) {
643
+ const rects = [];
644
+ for (const d of dabs) {
645
+ const rect = dabRect(d, this.brush.hardness);
646
+ const n = rect.width * rect.height;
647
+ if (this.buf.length < n) this.buf = new Float32Array(n);
648
+ const cov = this.buf.subarray(0, n);
649
+ rasterizeDab({ ...d, alpha: 1 }, this.brush.hardness, rect, cov);
650
+ this.medium.reset();
651
+ this.effect(this.medium, d, rect, cov);
652
+ rects.push(rect);
653
+ this.bounds = unionRect(this.bounds, rect);
654
+ this.dabCount++;
655
+ }
656
+ return rects;
657
+ }
658
+ };
659
+ function smudgeEffect(brush, strength, mixing = "rgb") {
660
+ const s = Math.min(1, Math.max(0, strength));
661
+ const tip = new SmudgeTip({ strength: s, length: s, mixing }, brush.size);
662
+ return (medium, d, rect, cov) => tip.dab(medium, { x: d.x, y: d.y, radius: d.radius, opacity: d.opacity * d.alpha }, rect, cov);
663
+ }
664
+ function spongeEffect(brush, saturate, vibrance) {
665
+ const rgb = [0, 0, 0];
666
+ return (medium, d, rect, cov) => {
667
+ const w = rect.width;
668
+ for (let yy = 0; yy < rect.height; yy++) {
669
+ for (let xx = 0; xx < w; xx++) {
670
+ const cv = cov[yy * w + xx];
671
+ if (cv <= 0) continue;
672
+ const dx = rect.x + xx;
673
+ const dy = rect.y + yy;
674
+ const k = perDabStrength(cv * d.alpha * d.opacity * 0.5, brush.spacing) * medium.selectionAt(dx, dy);
675
+ if (k <= 0) continue;
676
+ const ri = medium.locate(dx, dy, false);
677
+ if (ri < 0 || medium.pixels[ri + 3] === 0) continue;
678
+ const i = medium.locate(dx, dy, true);
679
+ const px = medium.pixels;
680
+ rgb[0] = px[i] / 255;
681
+ rgb[1] = px[i + 1] / 255;
682
+ rgb[2] = px[i + 2] / 255;
683
+ sponge(rgb, k, saturate, vibrance);
684
+ put255(px, i, rgb[0]);
685
+ put255(px, i + 1, rgb[1]);
686
+ put255(px, i + 2, rgb[2]);
687
+ }
688
+ }
689
+ };
690
+ }
691
+ function rgbaViewOfGrey(target) {
692
+ const views = /* @__PURE__ */ new Map();
693
+ const dirty = /* @__PURE__ */ new Map();
694
+ const expand = (g) => {
695
+ const out = new Uint8ClampedArray(g.length * 4);
696
+ for (let i = 0; i < g.length; i++) {
697
+ out[i * 4] = out[i * 4 + 1] = out[i * 4 + 2] = g[i];
698
+ out[i * 4 + 3] = 255;
699
+ }
700
+ return out;
701
+ };
702
+ const view = {
703
+ offset: target.offset,
704
+ lockTransparency: true,
705
+ selection: target.selection,
706
+ readTile: (tx, ty) => {
707
+ const key = tileKey(tx, ty);
708
+ let v = views.get(key);
709
+ if (!v) {
710
+ v = expand(target.readTile(tx, ty) ?? new Uint8ClampedArray(TILE * TILE));
711
+ views.set(key, v);
712
+ }
713
+ return v;
714
+ },
715
+ writeTile: (tx, ty) => {
716
+ const key = tileKey(tx, ty);
717
+ let v = views.get(key);
718
+ if (!v) {
719
+ v = expand(target.readTile(tx, ty) ?? new Uint8ClampedArray(TILE * TILE));
720
+ views.set(key, v);
721
+ }
722
+ dirty.set(key, { tx, ty, data: v });
723
+ return v;
724
+ }
725
+ };
726
+ return {
727
+ target: view,
728
+ flush: () => {
729
+ for (const { tx, ty, data } of dirty.values()) {
730
+ const g = target.writeTile(tx, ty);
731
+ for (let i = 0; i < g.length; i++) g[i] = data[i * 4];
732
+ }
733
+ dirty.clear();
734
+ }
735
+ };
736
+ }
737
+ function unionRect(a, b) {
738
+ if (!a) return { ...b };
739
+ const x = Math.min(a.x, b.x);
740
+ const y = Math.min(a.y, b.y);
741
+ return { x, y, width: Math.max(a.x + a.width, b.x + b.width) - x, height: Math.max(a.y + a.height, b.y + b.height) - y };
742
+ }
743
+
83
744
  // src/paint/wet/model.ts
84
745
  var WET_TILE = 256;
85
746
  var SLOT_SIDE = 258;
@@ -1849,7 +2510,7 @@ var WetWorld = class {
1849
2510
  if (x1 <= x0 || y1 <= y0) continue;
1850
2511
  const whole = x0 === tr.x && y0 === tr.y && x1 >= Math.min(tr.x + WET_TILE, this.width) && y1 >= Math.min(tr.y + WET_TILE, this.height);
1851
2512
  if (whole) {
1852
- if (this.bakeKey(key, true)) bounds = unionRect(bounds, { x: tr.x, y: tr.y, width: Math.min(WET_TILE, this.width - tr.x), height: Math.min(WET_TILE, this.height - tr.y) });
2513
+ if (this.bakeKey(key, true)) bounds = unionRect2(bounds, { x: tr.x, y: tr.y, width: Math.min(WET_TILE, this.width - tr.x), height: Math.min(WET_TILE, this.height - tr.y) });
1853
2514
  this.free(key);
1854
2515
  continue;
1855
2516
  }
@@ -1869,7 +2530,7 @@ var WetWorld = class {
1869
2530
  f[c + P_SW * PLANE] = 0;
1870
2531
  }
1871
2532
  }
1872
- bounds = unionRect(bounds, { x: x0, y: y0, width: x1 - x0, height: y1 - y0 });
2533
+ bounds = unionRect2(bounds, { x: x0, y: y0, width: x1 - x0, height: y1 - y0 });
1873
2534
  this.markEdited(lane, x0, y0, x1, y1);
1874
2535
  }
1875
2536
  this.syncList();
@@ -2439,7 +3100,7 @@ function moveWetSnapshot(snap, from, to, dx, dy, lane = () => ({})) {
2439
3100
  });
2440
3101
  return { clock: snap.clock, tilt: { ...snap.tilt }, lanes, slots, bytes };
2441
3102
  }
2442
- function unionRect(a, b) {
3103
+ function unionRect2(a, b) {
2443
3104
  if (!a) return { ...b };
2444
3105
  const x = Math.min(a.x, b.x);
2445
3106
  const y = Math.min(a.y, b.y);
@@ -3326,6 +3987,36 @@ function pointerStrokeSamples(e, toPoint) {
3326
3987
 
3327
3988
  export {
3328
3989
  createPigmentTools,
3990
+ TONE_RANGES,
3991
+ luma,
3992
+ dodgeBurnCurve,
3993
+ dodgeBurn,
3994
+ saturationOf,
3995
+ sponge,
3996
+ perDabStrength,
3997
+ RETOUCH_SAMPLES,
3998
+ SPOT_HEAL_TYPES,
3999
+ DEFAULT_RETOUCH,
4000
+ normalizeRetouch,
4001
+ boxBlurPixels,
4002
+ retouchFilter,
4003
+ IDENTITY_CLONE_TRANSFORM,
4004
+ isIdentityCloneTransform,
4005
+ cloneSamplePoint,
4006
+ TransformedPixelSource,
4007
+ TilePixelSource,
4008
+ RegionPixelSource,
4009
+ readSourceRegion,
4010
+ applyRetouchToTile,
4011
+ applyToneToDeepTile,
4012
+ toneOf,
4013
+ healPixels,
4014
+ spotHealPixels,
4015
+ spotHealWindow,
4016
+ RetouchDabStroke,
4017
+ smudgeEffect,
4018
+ spongeEffect,
4019
+ rgbaViewOfGrey,
3329
4020
  WET_TILE,
3330
4021
  SLOT_SIDE,
3331
4022
  PLANE,