pixel-data-js 0.25.2 → 0.27.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 (63) hide show
  1. package/dist/index.prod.cjs +1696 -1526
  2. package/dist/index.prod.cjs.map +1 -1
  3. package/dist/index.prod.d.ts +414 -311
  4. package/dist/index.prod.js +1676 -1519
  5. package/dist/index.prod.js.map +1 -1
  6. package/package.json +8 -9
  7. package/src/Algorithm/floodFillSelection.ts +49 -80
  8. package/src/Canvas/PixelCanvas.ts +1 -1
  9. package/src/Canvas/ReusableCanvas.ts +1 -1
  10. package/src/History/HistoryAction.ts +6 -5
  11. package/src/History/PixelMutator/mutatorApplyAlphaMask.ts +8 -10
  12. package/src/History/PixelMutator/mutatorApplyBinaryMask.ts +8 -10
  13. package/src/History/PixelMutator/mutatorApplyMask.ts +39 -0
  14. package/src/History/PixelMutator/{mutatorBlendPixelDataAlphaMask.ts → mutatorBlendAlphaMask.ts} +9 -9
  15. package/src/History/PixelMutator/{mutatorBlendPixelDataBinaryMask.ts → mutatorBlendBinaryMask.ts} +9 -9
  16. package/src/History/PixelMutator/mutatorBlendColor.ts +8 -9
  17. package/src/History/PixelMutator/mutatorBlendColorPaintAlphaMask.ts +51 -0
  18. package/src/History/PixelMutator/mutatorBlendColorPaintBinaryMask.ts +51 -0
  19. package/src/History/PixelMutator/{mutatorBlendPaintMask.ts → mutatorBlendColorPaintMask.ts} +3 -3
  20. package/src/History/PixelMutator/mutatorBlendColorPaintRect.ts +55 -0
  21. package/src/History/PixelMutator/mutatorBlendMask.ts +43 -0
  22. package/src/History/PixelMutator/mutatorBlendPixel.ts +2 -2
  23. package/src/History/PixelMutator/mutatorBlendPixelData.ts +8 -9
  24. package/src/History/PixelMutator/mutatorClear.ts +13 -11
  25. package/src/History/PixelMutator/mutatorFill.ts +2 -2
  26. package/src/History/PixelMutator/mutatorFillBinaryMask.ts +3 -4
  27. package/src/History/PixelMutator/mutatorInvert.ts +8 -9
  28. package/src/History/PixelMutator.ts +20 -4
  29. package/src/History/PixelWriter.ts +11 -13
  30. package/src/Input/fileToImageData.ts +1 -1
  31. package/src/Internal/helpers.ts +4 -1
  32. package/src/Mask/applyBinaryMaskToAlphaMask.ts +8 -10
  33. package/src/Paint/PaintBuffer.ts +3 -3
  34. package/src/Paint/PaintBufferCanvasRenderer.ts +1 -1
  35. package/src/Paint/makePaintMask.ts +5 -5
  36. package/src/Paint/makeRectFalloffPaintAlphaMask.ts +3 -3
  37. package/src/PixelData/applyAlphaMaskToPixelData.ts +14 -16
  38. package/src/PixelData/applyBinaryMaskToPixelData.ts +14 -16
  39. package/src/PixelData/applyMaskToPixelData.ts +22 -0
  40. package/src/PixelData/blendColorPixelData.ts +12 -15
  41. package/src/PixelData/blendColorPixelDataAlphaMask.ts +16 -16
  42. package/src/PixelData/blendColorPixelDataBinaryMask.ts +16 -16
  43. package/src/PixelData/blendColorPixelDataMask.ts +16 -0
  44. package/src/PixelData/blendColorPixelDataPaintAlphaMask.ts +30 -0
  45. package/src/PixelData/blendColorPixelDataPaintBinaryMask.ts +30 -0
  46. package/src/PixelData/blendColorPixelDataPaintMask.ts +35 -0
  47. package/src/PixelData/blendPixelData.ts +14 -16
  48. package/src/PixelData/blendPixelDataAlphaMask.ts +17 -19
  49. package/src/PixelData/blendPixelDataBinaryMask.ts +17 -19
  50. package/src/PixelData/blendPixelDataMask.ts +16 -0
  51. package/src/PixelData/blendPixelDataPaintBuffer.ts +1 -1
  52. package/src/PixelData/{clearPixelData.ts → clearPixelDataFast.ts} +2 -2
  53. package/src/PixelData/fillPixelDataBinaryMask.ts +8 -22
  54. package/src/PixelData/fillPixelDataFast.ts +2 -2
  55. package/src/PixelData/invertPixelData.ts +13 -16
  56. package/src/_types.ts +8 -7
  57. package/src/index.ts +41 -31
  58. package/dist/index.dev.cjs +0 -5419
  59. package/dist/index.dev.cjs.map +0 -1
  60. package/dist/index.dev.js +0 -5208
  61. package/dist/index.dev.js.map +0 -1
  62. package/src/Canvas/_constants.ts +0 -2
  63. package/src/PixelData/PixelBuffer32.ts +0 -28
@@ -297,37 +297,22 @@ function trimMaskRectBounds(target, bounds) {
297
297
  }
298
298
 
299
299
  // src/Algorithm/floodFillSelection.ts
300
- function floodFillSelection(img, startX, startY, {
301
- contiguous = true,
302
- tolerance = 0,
303
- bounds
304
- } = {}) {
305
- let imageData;
306
- let data32;
307
- if ("data32" in img) {
308
- data32 = img.data32;
309
- imageData = img.imageData;
310
- } else {
311
- data32 = new Uint32Array(img.data.buffer, img.data.byteOffset, img.data.byteLength >> 2);
312
- imageData = img;
313
- }
314
- const {
315
- width,
316
- height
317
- } = img;
318
- const limit = bounds || {
319
- x: 0,
320
- y: 0,
321
- w: width,
322
- h: height
323
- };
324
- const xMin = Math.max(0, limit.x);
325
- const xMax = Math.min(width - 1, limit.x + limit.w - 1);
326
- const yMin = Math.max(0, limit.y);
327
- const yMax = Math.min(height - 1, limit.y + limit.h - 1);
300
+ function floodFillSelection(target, startX, startY, contiguous = true, tolerance = 0, bounds, out) {
301
+ const data32 = target.data32;
302
+ const width = target.width;
303
+ const height = target.height;
304
+ const lx = bounds?.x ?? 0;
305
+ const ly = bounds?.y ?? 0;
306
+ const lw = bounds?.w ?? width;
307
+ const lh = bounds?.h ?? height;
308
+ const xMin = Math.max(0, lx);
309
+ const xMax = Math.min(width - 1, lx + lw - 1);
310
+ const yMin = Math.max(0, ly);
311
+ const yMax = Math.min(height - 1, ly + lh - 1);
328
312
  if (startX < xMin || startX > xMax || startY < yMin || startY > yMax) {
329
313
  return null;
330
314
  }
315
+ out = out ?? {};
331
316
  const baseColor = data32[startY * width + startX];
332
317
  let matchCount = 0;
333
318
  const matchX = new Uint16Array(width * height);
@@ -398,42 +383,33 @@ function floodFillSelection(img, startX, startY, {
398
383
  }
399
384
  }
400
385
  }
401
- if (matchCount === 0) {
402
- return null;
403
- }
386
+ if (matchCount === 0) return null;
404
387
  const w = maxX - minX + 1;
405
388
  const h = maxY - minY + 1;
406
- const selectionRect = {
407
- x: minX,
408
- y: minY,
409
- w,
410
- h,
411
- data: new Uint8Array(w * h),
412
- type: 1 /* BINARY */
413
- };
414
- const sw = selectionRect.w;
415
- const sh = selectionRect.h;
416
- const finalMask = selectionRect.data;
389
+ out.startX = startX;
390
+ out.startY = startY;
391
+ out.x = minX;
392
+ out.y = minY;
393
+ out.w = w;
394
+ out.h = h;
395
+ out.data = new Uint8Array(w * h);
396
+ out.type = 1 /* BINARY */;
397
+ const finalMask = out.data;
417
398
  for (let i = 0; i < matchCount; i++) {
418
- const mx = matchX[i] - selectionRect.x;
419
- const my = matchY[i] - selectionRect.y;
420
- if (mx >= 0 && mx < sw && my >= 0 && my < sh) {
421
- finalMask[my * sw + mx] = 1;
399
+ const mx = matchX[i] - minX;
400
+ const my = matchY[i] - minY;
401
+ if (mx >= 0 && mx < w && my >= 0 && my < h) {
402
+ finalMask[my * w + mx] = 1;
422
403
  }
423
404
  }
424
- trimMaskRectBounds(selectionRect, {
405
+ trimMaskRectBounds(out, {
425
406
  x: 0,
426
407
  y: 0,
427
408
  w: width,
428
409
  h: height
429
410
  });
430
- const extracted = extractImageDataBuffer(imageData, selectionRect.x, selectionRect.y, selectionRect.w, selectionRect.h);
431
- return {
432
- startX,
433
- startY,
434
- selectionRect,
435
- pixels: extracted
436
- };
411
+ out.pixels = extractImageDataBuffer(target.imageData, out.x, out.y, out.w, out.h);
412
+ return out;
437
413
  }
438
414
 
439
415
  // src/Algorithm/forEachLinePoint.ts
@@ -456,35 +432,6 @@ function forEachLinePoint(x0, y0, x1, y1, callback) {
456
432
  }
457
433
  }
458
434
 
459
- // src/BlendModes/blend-modes.ts
460
- var BaseBlendMode = {
461
- overwrite: 0,
462
- sourceOver: 1,
463
- darken: 2,
464
- multiply: 3,
465
- colorBurn: 4,
466
- linearBurn: 5,
467
- darkerColor: 6,
468
- lighten: 7,
469
- screen: 8,
470
- colorDodge: 9,
471
- linearDodge: 10,
472
- lighterColor: 11,
473
- overlay: 12,
474
- softLight: 13,
475
- hardLight: 14,
476
- vividLight: 15,
477
- linearLight: 16,
478
- pinLight: 17,
479
- hardMix: 18,
480
- difference: 19,
481
- exclusion: 20,
482
- subtract: 21,
483
- divide: 22
484
- };
485
- var overwriteBase = (src, _dst) => src;
486
- overwriteBase.isOverwrite = true;
487
-
488
435
  // src/BlendModes/BlendModeRegistry.ts
489
436
  function makeBlendModeRegistry(blendModes, initialEntries, registryName = "anonymous") {
490
437
  const blendToName = /* @__PURE__ */ new Map();
@@ -524,6 +471,35 @@ function makeBlendModeRegistry(blendModes, initialEntries, registryName = "anony
524
471
  };
525
472
  }
526
473
 
474
+ // src/BlendModes/blend-modes.ts
475
+ var BaseBlendMode = {
476
+ overwrite: 0,
477
+ sourceOver: 1,
478
+ darken: 2,
479
+ multiply: 3,
480
+ colorBurn: 4,
481
+ linearBurn: 5,
482
+ darkerColor: 6,
483
+ lighten: 7,
484
+ screen: 8,
485
+ colorDodge: 9,
486
+ linearDodge: 10,
487
+ lighterColor: 11,
488
+ overlay: 12,
489
+ softLight: 13,
490
+ hardLight: 14,
491
+ vividLight: 15,
492
+ linearLight: 16,
493
+ pinLight: 17,
494
+ hardMix: 18,
495
+ difference: 19,
496
+ exclusion: 20,
497
+ subtract: 21,
498
+ divide: 22
499
+ };
500
+ var overwriteBase = (src, _dst) => src;
501
+ overwriteBase.isOverwrite = true;
502
+
527
503
  // src/BlendModes/blend-modes-fast.ts
528
504
  var overwriteFast = overwriteBase;
529
505
  var sourceOverFast = (src, dst) => {
@@ -1529,28 +1505,10 @@ var getKeyByValue = (obj, value) => {
1529
1505
  }
1530
1506
  };
1531
1507
 
1532
- // src/Canvas/_constants.ts
1508
+ // support/error-strings.ts
1533
1509
  var OFFSCREEN_CANVAS_CTX_FAILED = "Failed to create OffscreenCanvas context";
1534
1510
  var CANVAS_CTX_FAILED = "Failed to create Canvas context";
1535
1511
 
1536
- // src/Canvas/canvas-blend-modes.ts
1537
- var CANVAS_COMPOSITE_MAP = {
1538
- [BaseBlendMode.overwrite]: "copy",
1539
- [BaseBlendMode.sourceOver]: "source-over",
1540
- [BaseBlendMode.darken]: "darken",
1541
- [BaseBlendMode.multiply]: "multiply",
1542
- [BaseBlendMode.colorBurn]: "color-burn",
1543
- [BaseBlendMode.lighten]: "lighten",
1544
- [BaseBlendMode.screen]: "screen",
1545
- [BaseBlendMode.colorDodge]: "color-dodge",
1546
- [BaseBlendMode.linearDodge]: "lighter",
1547
- [BaseBlendMode.overlay]: "overlay",
1548
- [BaseBlendMode.softLight]: "soft-light",
1549
- [BaseBlendMode.hardLight]: "hard-light",
1550
- [BaseBlendMode.difference]: "difference",
1551
- [BaseBlendMode.exclusion]: "exclusion"
1552
- };
1553
-
1554
1512
  // src/Canvas/ReusableCanvas.ts
1555
1513
  function makeReusableCanvas() {
1556
1514
  return makeReusableCanvasMeta((w, h) => {
@@ -1649,6 +1607,24 @@ function makePixelCanvas(canvas) {
1649
1607
  };
1650
1608
  }
1651
1609
 
1610
+ // src/Canvas/canvas-blend-modes.ts
1611
+ var CANVAS_COMPOSITE_MAP = {
1612
+ [BaseBlendMode.overwrite]: "copy",
1613
+ [BaseBlendMode.sourceOver]: "source-over",
1614
+ [BaseBlendMode.darken]: "darken",
1615
+ [BaseBlendMode.multiply]: "multiply",
1616
+ [BaseBlendMode.colorBurn]: "color-burn",
1617
+ [BaseBlendMode.lighten]: "lighten",
1618
+ [BaseBlendMode.screen]: "screen",
1619
+ [BaseBlendMode.colorDodge]: "color-dodge",
1620
+ [BaseBlendMode.linearDodge]: "lighter",
1621
+ [BaseBlendMode.overlay]: "overlay",
1622
+ [BaseBlendMode.softLight]: "soft-light",
1623
+ [BaseBlendMode.hardLight]: "hard-light",
1624
+ [BaseBlendMode.difference]: "difference",
1625
+ [BaseBlendMode.exclusion]: "exclusion"
1626
+ };
1627
+
1652
1628
  // src/ImageData/imgBlobToImageData.ts
1653
1629
  async function imgBlobToImageData(blob) {
1654
1630
  let bitmap = null;
@@ -1733,10 +1709,9 @@ function applyPatchTiles(target, tiles, tileSize) {
1733
1709
  }
1734
1710
 
1735
1711
  // src/History/HistoryAction.ts
1736
- function makeHistoryAction(writer, patch, after, afterUndo, afterRedo, applyPatchTilesFn = applyPatchTiles) {
1737
- const target = writer.config.target;
1738
- const tileSize = writer.config.tileSize;
1739
- const accumulator = writer.accumulator;
1712
+ function makeHistoryAction(config, accumulator, patch, after, afterUndo, afterRedo, applyPatchTilesFn = applyPatchTiles) {
1713
+ const target = config.target;
1714
+ const tileSize = config.tileSize;
1740
1715
  return {
1741
1716
  undo: () => {
1742
1717
  applyPatchTilesFn(target, patch.beforeTiles, tileSize);
@@ -2006,20 +1981,17 @@ var PixelEngineConfig = class {
2006
1981
  }
2007
1982
  };
2008
1983
 
2009
- // src/PixelData/blendColorPixelData.ts
2010
- function blendColorPixelData(dst, color, opts = {}) {
2011
- const {
2012
- x: targetX = 0,
2013
- y: targetY = 0,
2014
- w: width = dst.width,
2015
- h: height = dst.height,
2016
- alpha: globalAlpha = 255,
2017
- blendFn = sourceOverPerfect
2018
- } = opts;
1984
+ // src/PixelData/applyAlphaMaskToPixelData.ts
1985
+ function applyAlphaMaskToPixelData(target, mask, opts) {
1986
+ const targetX = opts?.x ?? 0;
1987
+ const targetY = opts?.y ?? 0;
1988
+ const width = opts?.w ?? target.width;
1989
+ const height = opts?.h ?? target.height;
1990
+ const globalAlpha = opts?.alpha ?? 255;
1991
+ const mx = opts?.mx ?? 0;
1992
+ const my = opts?.my ?? 0;
1993
+ const invertMask = opts?.invertMask ?? false;
2019
1994
  if (globalAlpha === 0) return false;
2020
- const baseSrcAlpha = color >>> 24;
2021
- const isOverwrite = blendFn.isOverwrite || false;
2022
- if (baseSrcAlpha === 0 && !isOverwrite) return false;
2023
1995
  let x = targetX;
2024
1996
  let y = targetY;
2025
1997
  let w = width;
@@ -2032,112 +2004,318 @@ function blendColorPixelData(dst, color, opts = {}) {
2032
2004
  h += y;
2033
2005
  y = 0;
2034
2006
  }
2035
- const actualW = Math.min(w, dst.width - x);
2036
- const actualH = Math.min(h, dst.height - y);
2037
- if (actualW <= 0 || actualH <= 0) return false;
2038
- let finalSrcColor = color;
2039
- if (globalAlpha < 255) {
2040
- const a = baseSrcAlpha * globalAlpha + 128 >> 8;
2041
- if (a === 0 && !isOverwrite) return false;
2042
- finalSrcColor = (color & 16777215 | a << 24) >>> 0;
2043
- }
2044
- const dst32 = dst.data32;
2045
- const dw = dst.width;
2046
- let dIdx = y * dw + x | 0;
2047
- const dStride = dw - actualW | 0;
2007
+ w = Math.min(w, target.width - x);
2008
+ h = Math.min(h, target.height - y);
2009
+ if (w <= 0) return false;
2010
+ if (h <= 0) return false;
2011
+ const mPitch = mask.w;
2012
+ if (mPitch <= 0) return false;
2013
+ const startX = mx + (x - targetX);
2014
+ const startY = my + (y - targetY);
2015
+ const sX0 = Math.max(0, startX);
2016
+ const sY0 = Math.max(0, startY);
2017
+ const sX1 = Math.min(mPitch, startX + w);
2018
+ const sY1 = Math.min(mask.h, startY + h);
2019
+ const finalW = sX1 - sX0;
2020
+ const finalH = sY1 - sY0;
2021
+ if (finalW <= 0) return false;
2022
+ if (finalH <= 0) return false;
2023
+ const xShift = sX0 - startX;
2024
+ const yShift = sY0 - startY;
2025
+ const dst32 = target.data32;
2026
+ const dw = target.width;
2027
+ const dStride = dw - finalW;
2028
+ const mStride = mPitch - finalW;
2029
+ const maskData = mask.data;
2030
+ let dIdx = (y + yShift) * dw + (x + xShift);
2031
+ let mIdx = sY0 * mPitch + sX0;
2048
2032
  let didChange = false;
2049
- for (let iy = 0; iy < actualH; iy++) {
2050
- for (let ix = 0; ix < actualW; ix++) {
2051
- const current = dst32[dIdx];
2052
- const next = blendFn(finalSrcColor, current);
2053
- if (current !== next) {
2054
- dst32[dIdx] = next;
2033
+ for (let iy = 0; iy < h; iy++) {
2034
+ for (let ix = 0; ix < w; ix++) {
2035
+ const mVal = maskData[mIdx];
2036
+ const effectiveM = invertMask ? 255 - mVal : mVal;
2037
+ let weight = 0;
2038
+ if (effectiveM === 0) {
2039
+ weight = 0;
2040
+ } else if (effectiveM === 255) {
2041
+ weight = globalAlpha;
2042
+ } else if (globalAlpha === 255) {
2043
+ weight = effectiveM;
2044
+ } else {
2045
+ weight = effectiveM * globalAlpha + 128 >> 8;
2046
+ }
2047
+ if (weight === 0) {
2048
+ dst32[dIdx] = (dst32[dIdx] & 16777215) >>> 0;
2055
2049
  didChange = true;
2050
+ } else if (weight !== 255) {
2051
+ const d = dst32[dIdx];
2052
+ const da = d >>> 24;
2053
+ if (da !== 0) {
2054
+ const finalAlpha = da === 255 ? weight : da * weight + 128 >> 8;
2055
+ const current = dst32[dIdx];
2056
+ const next = (d & 16777215 | finalAlpha << 24) >>> 0;
2057
+ if (current !== next) {
2058
+ dst32[dIdx] = next;
2059
+ didChange = true;
2060
+ }
2061
+ }
2056
2062
  }
2057
2063
  dIdx++;
2064
+ mIdx++;
2058
2065
  }
2059
2066
  dIdx += dStride;
2067
+ mIdx += mStride;
2060
2068
  }
2061
2069
  return didChange;
2062
2070
  }
2063
2071
 
2064
- // src/History/PixelMutator/mutatorBlendColor.ts
2065
- var defaults2 = {
2066
- blendColorPixelData
2067
- };
2068
- var mutatorBlendColor = ((writer, deps = defaults2) => {
2072
+ // src/ImageData/resizeImageData.ts
2073
+ function resizeImageData(target, newWidth, newHeight, offsetX = 0, offsetY = 0) {
2074
+ const result = new ImageData(newWidth, newHeight);
2069
2075
  const {
2070
- blendColorPixelData: blendColorPixelData2 = defaults2.blendColorPixelData
2071
- } = deps;
2072
- return {
2073
- blendColor(color, opts = {}) {
2074
- const target = writer.config.target;
2075
- const {
2076
- x = 0,
2077
- y = 0,
2078
- w = target.width,
2079
- h = target.height
2080
- } = opts;
2081
- const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2082
- return didChange(blendColorPixelData2(target, color, opts));
2083
- }
2084
- };
2085
- });
2086
-
2087
- // src/PixelData/blendPixel.ts
2088
- function blendPixel(target, x, y, color, alpha = 255, blendFn = sourceOverPerfect) {
2089
- if (alpha === 0) return false;
2090
- let width = target.width;
2091
- let height = target.height;
2092
- if (x < 0 || x >= width || y < 0 || y >= height) return false;
2093
- let srcAlpha = color >>> 24;
2094
- let isOverwrite = blendFn.isOverwrite;
2095
- if (srcAlpha === 0 && !isOverwrite) return false;
2096
- let dst32 = target.data32;
2097
- let index = y * width + x;
2098
- let finalColor = color;
2099
- if (alpha !== 255) {
2100
- let finalAlpha = srcAlpha * alpha + 128 >> 8;
2101
- if (finalAlpha === 0 && !isOverwrite) return false;
2102
- finalColor = (color & 16777215 | finalAlpha << 24) >>> 0;
2076
+ width: oldW,
2077
+ height: oldH,
2078
+ data: oldData
2079
+ } = target;
2080
+ const newData = result.data;
2081
+ const x0 = Math.max(0, offsetX);
2082
+ const y0 = Math.max(0, offsetY);
2083
+ const x1 = Math.min(newWidth, offsetX + oldW);
2084
+ const y1 = Math.min(newHeight, offsetY + oldH);
2085
+ if (x1 <= x0 || y1 <= y0) {
2086
+ return result;
2103
2087
  }
2104
- let current = dst32[index];
2105
- let next = blendFn(finalColor, current);
2106
- if (current !== next) {
2107
- dst32[index] = next;
2108
- return true;
2088
+ const rowCount = y1 - y0;
2089
+ const rowLen = (x1 - x0) * 4;
2090
+ for (let row = 0; row < rowCount; row++) {
2091
+ const dstY = y0 + row;
2092
+ const srcY = dstY - offsetY;
2093
+ const srcX = x0 - offsetX;
2094
+ const dstStart = (dstY * newWidth + x0) * 4;
2095
+ const srcStart = (srcY * oldW + srcX) * 4;
2096
+ newData.set(oldData.subarray(srcStart, srcStart + rowLen), dstStart);
2109
2097
  }
2110
- return false;
2098
+ return result;
2111
2099
  }
2112
2100
 
2113
- // src/History/PixelMutator/mutatorBlendPixel.ts
2114
- var defaults3 = {
2115
- blendPixel
2116
- };
2117
- var mutatorBlendPixel = ((writer, deps = defaults3) => {
2118
- const {
2119
- blendPixel: blendPixel2 = defaults3.blendPixel
2120
- } = deps;
2121
- return {
2122
- blendPixel(x, y, color, alpha, blendFn) {
2123
- const didChange = writer.accumulator.storePixelBeforeState(x, y);
2124
- return didChange(blendPixel2(writer.config.target, x, y, color, alpha, blendFn));
2125
- }
2101
+ // src/Rect/trimRectBounds.ts
2102
+ function trimRectBounds(x, y, w, h, targetWidth, targetHeight, out) {
2103
+ const res = out ?? {
2104
+ x: 0,
2105
+ y: 0,
2106
+ w: 0,
2107
+ h: 0
2126
2108
  };
2127
- });
2109
+ const left = Math.max(0, x);
2110
+ const top = Math.max(0, y);
2111
+ const right = Math.min(targetWidth, x + w);
2112
+ const bottom = Math.min(targetHeight, y + h);
2113
+ res.x = left;
2114
+ res.y = top;
2115
+ res.w = Math.max(0, right - left);
2116
+ res.h = Math.max(0, bottom - top);
2117
+ return res;
2118
+ }
2119
+
2120
+ // src/Paint/PaintBuffer.ts
2121
+ var PaintBuffer = class {
2122
+ constructor(config, tilePool) {
2123
+ this.config = config;
2124
+ this.tilePool = tilePool;
2125
+ this.lookup = [];
2126
+ }
2127
+ lookup;
2128
+ scratchBounds = {
2129
+ x: 0,
2130
+ y: 0,
2131
+ w: 0,
2132
+ h: 0
2133
+ };
2134
+ eachTileInBounds(bounds, callback) {
2135
+ const {
2136
+ tileShift,
2137
+ targetColumns,
2138
+ targetRows,
2139
+ tileSize
2140
+ } = this.config;
2141
+ const x1 = Math.max(0, bounds.x >> tileShift);
2142
+ const y1 = Math.max(0, bounds.y >> tileShift);
2143
+ const x2 = Math.min(targetColumns - 1, bounds.x + bounds.w - 1 >> tileShift);
2144
+ const y2 = Math.min(targetRows - 1, bounds.y + bounds.h - 1 >> tileShift);
2145
+ if (x1 > x2 || y1 > y2) return;
2146
+ const lookup = this.lookup;
2147
+ const tilePool = this.tilePool;
2148
+ for (let ty = y1; ty <= y2; ty++) {
2149
+ const rowOffset = ty * targetColumns;
2150
+ const tileTop = ty << tileShift;
2151
+ for (let tx = x1; tx <= x2; tx++) {
2152
+ const id = rowOffset + tx;
2153
+ const tile = lookup[id] ?? (lookup[id] = tilePool.getTile(id, tx, ty));
2154
+ const tileLeft = tx << tileShift;
2155
+ const startX = bounds.x > tileLeft ? bounds.x : tileLeft;
2156
+ const startY = bounds.y > tileTop ? bounds.y : tileTop;
2157
+ const maskEndX = bounds.x + bounds.w;
2158
+ const tileEndX = tileLeft + tileSize;
2159
+ const endX = maskEndX < tileEndX ? maskEndX : tileEndX;
2160
+ const maskEndY = bounds.y + bounds.h;
2161
+ const tileEndY = tileTop + tileSize;
2162
+ const endY = maskEndY < tileEndY ? maskEndY : tileEndY;
2163
+ callback(tile, startX, startY, endX - startX, endY - startY);
2164
+ }
2165
+ }
2166
+ }
2167
+ writePaintAlphaMaskStroke(color, brush, x0, y0, x1, y1) {
2168
+ const cA = color >>> 24;
2169
+ if (cA === 0) return false;
2170
+ const {
2171
+ tileShift,
2172
+ tileMask,
2173
+ target
2174
+ } = this.config;
2175
+ const {
2176
+ w: bW,
2177
+ h: bH,
2178
+ data: bD,
2179
+ centerOffsetX,
2180
+ centerOffsetY
2181
+ } = brush;
2182
+ const cRGB = color & 16777215;
2183
+ const scratch = this.scratchBounds;
2184
+ let changed = false;
2185
+ forEachLinePoint(x0, y0, x1, y1, (px, py) => {
2186
+ const topLeftX = Math.floor(px + centerOffsetX);
2187
+ const topLeftY = Math.floor(py + centerOffsetY);
2188
+ trimRectBounds(topLeftX, topLeftY, bW, bH, target.width, target.height, scratch);
2189
+ if (scratch.w <= 0 || scratch.h <= 0) return;
2190
+ this.eachTileInBounds(scratch, (tile, bX, bY, bW_t, bH_t) => {
2191
+ const d32 = tile.data32;
2192
+ let tileChanged = false;
2193
+ for (let i = 0; i < bH_t; i++) {
2194
+ const canvasY = bY + i;
2195
+ const bOff = (canvasY - topLeftY) * bW;
2196
+ const tOff = (canvasY & tileMask) << tileShift;
2197
+ const dS = tOff + (bX & tileMask);
2198
+ for (let j = 0; j < bW_t; j++) {
2199
+ const canvasX = bX + j;
2200
+ const brushA = bD[bOff + (canvasX - topLeftX)];
2201
+ if (brushA === 0) continue;
2202
+ const t = cA * brushA + 128;
2203
+ const blendedA = t + (t >> 8) >> 8;
2204
+ const idx = dS + j;
2205
+ const cur = d32[idx];
2206
+ if (brushA > cur >>> 24) {
2207
+ const next = (cRGB | blendedA << 24) >>> 0;
2208
+ if (cur !== next) {
2209
+ d32[idx] = next;
2210
+ tileChanged = true;
2211
+ }
2212
+ }
2213
+ }
2214
+ }
2215
+ if (tileChanged) changed = true;
2216
+ });
2217
+ });
2218
+ return changed;
2219
+ }
2220
+ writePaintBinaryMaskStroke(color, brush, x0, y0, x1, y1) {
2221
+ const alphaIsZero = color >>> 24 === 0;
2222
+ if (alphaIsZero) return false;
2223
+ const {
2224
+ tileShift,
2225
+ tileMask,
2226
+ target
2227
+ } = this.config;
2228
+ const {
2229
+ w: bW,
2230
+ h: bH,
2231
+ data: bD,
2232
+ centerOffsetX,
2233
+ centerOffsetY
2234
+ } = brush;
2235
+ const scratch = this.scratchBounds;
2236
+ let changed = false;
2237
+ forEachLinePoint(x0, y0, x1, y1, (px, py) => {
2238
+ const topLeftX = Math.floor(px + centerOffsetX);
2239
+ const topLeftY = Math.floor(py + centerOffsetY);
2240
+ trimRectBounds(topLeftX, topLeftY, bW, bH, target.width, target.height, scratch);
2241
+ if (scratch.w <= 0 || scratch.h <= 0) return;
2242
+ this.eachTileInBounds(scratch, (tile, bX, bY, bW_t, bH_t) => {
2243
+ const d32 = tile.data32;
2244
+ let tileChanged = false;
2245
+ for (let i = 0; i < bH_t; i++) {
2246
+ const canvasY = bY + i;
2247
+ const bOff = (canvasY - topLeftY) * bW;
2248
+ const tOff = (canvasY & tileMask) << tileShift;
2249
+ const dS = tOff + (bX & tileMask);
2250
+ for (let j = 0; j < bW_t; j++) {
2251
+ const canvasX = bX + j;
2252
+ if (bD[bOff + (canvasX - topLeftX)]) {
2253
+ const idx = dS + j;
2254
+ if (d32[idx] !== color) {
2255
+ d32[idx] = color;
2256
+ tileChanged = true;
2257
+ }
2258
+ }
2259
+ }
2260
+ }
2261
+ if (tileChanged) changed = true;
2262
+ });
2263
+ });
2264
+ return changed;
2265
+ }
2266
+ writeRectStroke(color, brushWidth, brushHeight, x0, y0, x1, y1) {
2267
+ const alphaIsZero = color >>> 24 === 0;
2268
+ if (alphaIsZero) return false;
2269
+ const config = this.config;
2270
+ const tileShift = config.tileShift;
2271
+ const tileMask = config.tileMask;
2272
+ const target = config.target;
2273
+ const scratch = this.scratchBounds;
2274
+ const centerOffsetX = -(brushWidth - 1 >> 1);
2275
+ const centerOffsetY = -(brushHeight - 1 >> 1);
2276
+ let changed = false;
2277
+ forEachLinePoint(x0, y0, x1, y1, (px, py) => {
2278
+ const topLeftX = Math.floor(px + centerOffsetX);
2279
+ const topLeftY = Math.floor(py + centerOffsetY);
2280
+ trimRectBounds(topLeftX, topLeftY, brushWidth, brushHeight, target.width, target.height, scratch);
2281
+ if (scratch.w <= 0 || scratch.h <= 0) return;
2282
+ this.eachTileInBounds(scratch, (tile, bX, bY, bW_t, bH_t) => {
2283
+ const d32 = tile.data32;
2284
+ let tileChanged = false;
2285
+ for (let i = 0; i < bH_t; i++) {
2286
+ const canvasY = bY + i;
2287
+ const tOff = (canvasY & tileMask) << tileShift;
2288
+ const dS = tOff + (bX & tileMask);
2289
+ for (let j = 0; j < bW_t; j++) {
2290
+ const idx = dS + j;
2291
+ if (d32[idx] !== color) {
2292
+ d32[idx] = color;
2293
+ tileChanged = true;
2294
+ }
2295
+ }
2296
+ }
2297
+ if (tileChanged) {
2298
+ changed = true;
2299
+ }
2300
+ });
2301
+ });
2302
+ return changed;
2303
+ }
2304
+ clear() {
2305
+ this.tilePool.releaseTiles(this.lookup);
2306
+ }
2307
+ };
2128
2308
 
2129
2309
  // src/PixelData/blendPixelData.ts
2130
- function blendPixelData(dst, src, opts = {}) {
2131
- const {
2132
- x: targetX = 0,
2133
- y: targetY = 0,
2134
- sx: sourceX = 0,
2135
- sy: sourceY = 0,
2136
- w: width = src.width,
2137
- h: height = src.height,
2138
- alpha: globalAlpha = 255,
2139
- blendFn = sourceOverPerfect
2140
- } = opts;
2310
+ function blendPixelData(target, src, opts) {
2311
+ const targetX = opts?.x ?? 0;
2312
+ const targetY = opts?.y ?? 0;
2313
+ const sourceX = opts?.sx ?? 0;
2314
+ const sourceY = opts?.sy ?? 0;
2315
+ const width = opts?.w ?? src.width;
2316
+ const height = opts?.h ?? src.height;
2317
+ const globalAlpha = opts?.alpha ?? 255;
2318
+ const blendFn = opts?.blendFn ?? sourceOverPerfect;
2141
2319
  if (globalAlpha === 0) return false;
2142
2320
  let x = targetX;
2143
2321
  let y = targetY;
@@ -2167,12 +2345,12 @@ function blendPixelData(dst, src, opts = {}) {
2167
2345
  h += y;
2168
2346
  y = 0;
2169
2347
  }
2170
- const actualW = Math.min(w, dst.width - x);
2171
- const actualH = Math.min(h, dst.height - y);
2348
+ const actualW = Math.min(w, target.width - x);
2349
+ const actualH = Math.min(h, target.height - y);
2172
2350
  if (actualW <= 0 || actualH <= 0) return false;
2173
- const dst32 = dst.data32;
2351
+ const dst32 = target.data32;
2174
2352
  const src32 = src.data32;
2175
- const dw = dst.width;
2353
+ const dw = target.width;
2176
2354
  const sw = src.width;
2177
2355
  let dIdx = y * dw + x | 0;
2178
2356
  let sIdx = sy * sw + sx | 0;
@@ -2215,44 +2393,334 @@ function blendPixelData(dst, src, opts = {}) {
2215
2393
  return didChange;
2216
2394
  }
2217
2395
 
2218
- // src/History/PixelMutator/mutatorBlendPixelData.ts
2219
- var defaults4 = {
2220
- blendPixelData
2396
+ // src/PixelTile/PixelTile.ts
2397
+ var PixelTile = class {
2398
+ constructor(id, tx, ty, tileSize, tileArea) {
2399
+ this.id = id;
2400
+ this.tx = tx;
2401
+ this.ty = ty;
2402
+ this.width = this.height = tileSize;
2403
+ this.data32 = new Uint32Array(tileArea);
2404
+ const data8 = new Uint8ClampedArray(this.data32.buffer);
2405
+ this.imageData = new ImageData(data8, tileSize, tileSize);
2406
+ }
2407
+ data32;
2408
+ width;
2409
+ height;
2410
+ imageData;
2221
2411
  };
2222
- var mutatorBlendPixelData = ((writer, deps = defaults4) => {
2223
- const {
2224
- blendPixelData: blendPixelData2 = defaults4.blendPixelData
2225
- } = deps;
2226
- return {
2227
- blendPixelData(src, opts = {}) {
2228
- const {
2229
- x = 0,
2230
- y = 0,
2231
- w = src.width,
2232
- h = src.height
2233
- } = opts;
2234
- const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2235
- return didChange(blendPixelData2(writer.config.target, src, opts));
2236
- }
2237
- };
2238
- });
2239
2412
 
2240
- // src/PixelData/blendPixelDataAlphaMask.ts
2241
- function blendPixelDataAlphaMask(dst, src, alphaMask, opts = {}) {
2242
- const {
2243
- x: targetX = 0,
2244
- y: targetY = 0,
2245
- sx: sourceX = 0,
2246
- sy: sourceY = 0,
2247
- w: width = src.width,
2248
- h: height = src.height,
2249
- alpha: globalAlpha = 255,
2250
- blendFn = sourceOverPerfect,
2251
- mx = 0,
2252
- my = 0,
2253
- invertMask = false
2254
- } = opts;
2255
- if (globalAlpha === 0) return false;
2413
+ // src/PixelTile/PixelTilePool.ts
2414
+ var PixelTilePool = class {
2415
+ pool;
2416
+ tileSize;
2417
+ tileArea;
2418
+ constructor(config) {
2419
+ this.pool = [];
2420
+ this.tileSize = config.tileSize;
2421
+ this.tileArea = config.tileArea;
2422
+ }
2423
+ getTile(id, tx, ty) {
2424
+ let tile = this.pool.pop();
2425
+ if (tile) {
2426
+ tile.id = id;
2427
+ tile.tx = tx;
2428
+ tile.ty = ty;
2429
+ tile.data32.fill(0);
2430
+ return tile;
2431
+ }
2432
+ return new PixelTile(id, tx, ty, this.tileSize, this.tileArea);
2433
+ }
2434
+ releaseTile(tile) {
2435
+ this.pool.push(tile);
2436
+ }
2437
+ releaseTiles(tiles) {
2438
+ let length = tiles.length;
2439
+ for (let i = 0; i < length; i++) {
2440
+ let tile = tiles[i];
2441
+ if (tile) {
2442
+ this.pool.push(tile);
2443
+ }
2444
+ }
2445
+ tiles.length = 0;
2446
+ }
2447
+ };
2448
+
2449
+ // src/History/PixelWriter.ts
2450
+ var PixelWriter = class {
2451
+ historyManager;
2452
+ accumulator;
2453
+ historyActionFactory;
2454
+ config;
2455
+ pixelTilePool;
2456
+ paintBuffer;
2457
+ mutator;
2458
+ blendPixelDataOpts = {
2459
+ alpha: 255,
2460
+ blendFn: sourceOverPerfect,
2461
+ x: 0,
2462
+ y: 0,
2463
+ w: 0,
2464
+ h: 0
2465
+ };
2466
+ _inProgress = false;
2467
+ constructor(target, mutatorFactory, options) {
2468
+ const tileSize = options?.tileSize ?? 256;
2469
+ const maxHistorySteps = options?.maxHistorySteps ?? 50;
2470
+ this.config = new PixelEngineConfig(tileSize, target);
2471
+ this.historyManager = options?.historyManager ?? new HistoryManager(maxHistorySteps);
2472
+ this.historyActionFactory = options?.historyActionFactory ?? makeHistoryAction;
2473
+ this.pixelTilePool = options?.pixelTilePool ?? new PixelTilePool(this.config);
2474
+ this.accumulator = options?.accumulator ?? new PixelAccumulator(this.config, this.pixelTilePool);
2475
+ this.mutator = mutatorFactory(this);
2476
+ this.paintBuffer = new PaintBuffer(this.config, this.pixelTilePool);
2477
+ }
2478
+ /**
2479
+ * Executes `transaction` and commits the resulting pixel changes as a single
2480
+ * undoable history action.
2481
+ *
2482
+ * - If `transaction` throws, all accumulated changes are rolled back and the error
2483
+ * is re-thrown. No action is committed.
2484
+ * - If `transaction` completes without modifying any pixels, no action is committed.
2485
+ * - `withHistory` is not re-entrant. Calling it again from inside `transaction` will
2486
+ * throw immediately to prevent silent data loss from a nested extractPatch.
2487
+ *
2488
+ * @param transaction Callback to be executed inside the transaction.
2489
+ * @param after Called after both undo and redo — use for generic change notifications.
2490
+ * @param afterUndo Called after undo only — use for dimension or state changes specific to undo.
2491
+ * @param afterRedo Called after redo only.
2492
+ */
2493
+ withHistory(transaction, after, afterUndo, afterRedo) {
2494
+ if (this._inProgress) {
2495
+ throw new Error("withHistory is not re-entrant \u2014 commit or rollback the current operation first");
2496
+ }
2497
+ this._inProgress = true;
2498
+ try {
2499
+ transaction(this.mutator);
2500
+ } catch (e) {
2501
+ this.accumulator.rollbackAfterError();
2502
+ throw e;
2503
+ } finally {
2504
+ this._inProgress = false;
2505
+ }
2506
+ if (this.accumulator.beforeTiles.length === 0) return;
2507
+ const patch = this.accumulator.extractPatch();
2508
+ const action = this.historyActionFactory(this.config, this.accumulator, patch, after, afterUndo, afterRedo);
2509
+ this.historyManager.commit(action);
2510
+ }
2511
+ resize(newWidth, newHeight, offsetX = 0, offsetY = 0, after, afterUndo, afterRedo, resizeImageDataFn = resizeImageData) {
2512
+ if (this._inProgress) {
2513
+ throw new Error("Cannot resize inside a withHistory callback");
2514
+ }
2515
+ if (this.accumulator.beforeTiles.length > 0) {
2516
+ throw new Error("Cannot resize with an open accumulator \u2014 commit or rollback first");
2517
+ }
2518
+ const config = this.config;
2519
+ const target = config.target;
2520
+ const beforeImageData = target.imageData;
2521
+ const afterImageData = resizeImageDataFn(beforeImageData, newWidth, newHeight, offsetX, offsetY);
2522
+ target.set(afterImageData);
2523
+ this.historyManager.commit({
2524
+ undo: () => {
2525
+ target.set(beforeImageData);
2526
+ afterUndo?.(beforeImageData);
2527
+ after?.(beforeImageData);
2528
+ },
2529
+ redo: () => {
2530
+ target.set(afterImageData);
2531
+ afterRedo?.(afterImageData);
2532
+ after?.(afterImageData);
2533
+ }
2534
+ });
2535
+ }
2536
+ commitPaintBuffer(alpha = 255, blendFn = sourceOverPerfect, blendPixelDataFn = blendPixelData) {
2537
+ const paintBuffer = this.paintBuffer;
2538
+ const tileShift = paintBuffer.config.tileShift;
2539
+ const lookup = paintBuffer.lookup;
2540
+ const opts = this.blendPixelDataOpts;
2541
+ opts.alpha = alpha;
2542
+ opts.blendFn = blendFn;
2543
+ for (let i = 0; i < lookup.length; i++) {
2544
+ const tile = lookup[i];
2545
+ if (tile) {
2546
+ const didChange = this.accumulator.storeTileBeforeState(tile.id, tile.tx, tile.ty);
2547
+ const dx = tile.tx << tileShift;
2548
+ const dy = tile.ty << tileShift;
2549
+ opts.x = dx;
2550
+ opts.y = dy;
2551
+ opts.w = tile.width;
2552
+ opts.h = tile.height;
2553
+ didChange(blendPixelDataFn(this.config.target, tile, opts));
2554
+ }
2555
+ }
2556
+ paintBuffer.clear();
2557
+ }
2558
+ };
2559
+
2560
+ // src/History/PixelMutator/mutatorApplyAlphaMask.ts
2561
+ var defaults2 = {
2562
+ applyAlphaMaskToPixelData
2563
+ };
2564
+ var mutatorApplyAlphaMask = ((writer, deps = defaults2) => {
2565
+ const {
2566
+ applyAlphaMaskToPixelData: applyAlphaMaskToPixelData2 = defaults2.applyAlphaMaskToPixelData
2567
+ } = deps;
2568
+ return {
2569
+ applyAlphaMask(mask, opts) {
2570
+ const target = writer.config.target;
2571
+ const x = opts?.x ?? 0;
2572
+ const y = opts?.y ?? 0;
2573
+ const w = opts?.w ?? target.width;
2574
+ const h = opts?.h ?? target.height;
2575
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2576
+ return didChange(applyAlphaMaskToPixelData2(target, mask, opts));
2577
+ }
2578
+ };
2579
+ });
2580
+
2581
+ // src/PixelData/applyBinaryMaskToPixelData.ts
2582
+ function applyBinaryMaskToPixelData(target, mask, opts) {
2583
+ const targetX = opts?.x ?? 0;
2584
+ const targetY = opts?.y ?? 0;
2585
+ const width = opts?.w ?? target.width;
2586
+ const height = opts?.h ?? target.height;
2587
+ const globalAlpha = opts?.alpha ?? 255;
2588
+ const mx = opts?.mx ?? 0;
2589
+ const my = opts?.my ?? 0;
2590
+ const invertMask = opts?.invertMask ?? false;
2591
+ if (globalAlpha === 0) return false;
2592
+ let x = targetX;
2593
+ let y = targetY;
2594
+ let w = width;
2595
+ let h = height;
2596
+ if (x < 0) {
2597
+ w += x;
2598
+ x = 0;
2599
+ }
2600
+ if (y < 0) {
2601
+ h += y;
2602
+ y = 0;
2603
+ }
2604
+ w = Math.min(w, target.width - x);
2605
+ h = Math.min(h, target.height - y);
2606
+ if (w <= 0 || h <= 0) return false;
2607
+ const mPitch = mask.w;
2608
+ if (mPitch <= 0) return false;
2609
+ const startX = mx + (x - targetX);
2610
+ const startY = my + (y - targetY);
2611
+ const sX0 = Math.max(0, startX);
2612
+ const sY0 = Math.max(0, startY);
2613
+ const sX1 = Math.min(mPitch, startX + w);
2614
+ const sY1 = Math.min(mask.h, startY + h);
2615
+ const finalW = sX1 - sX0;
2616
+ const finalH = sY1 - sY0;
2617
+ if (finalW <= 0 || finalH <= 0) {
2618
+ return false;
2619
+ }
2620
+ const xShift = sX0 - startX;
2621
+ const yShift = sY0 - startY;
2622
+ const dst32 = target.data32;
2623
+ const dw = target.width;
2624
+ const dStride = dw - finalW;
2625
+ const mStride = mPitch - finalW;
2626
+ const maskData = mask.data;
2627
+ let dIdx = (y + yShift) * dw + (x + xShift);
2628
+ let mIdx = sY0 * mPitch + sX0;
2629
+ let didChange = false;
2630
+ for (let iy = 0; iy < finalH; iy++) {
2631
+ for (let ix = 0; ix < finalW; ix++) {
2632
+ const mVal = maskData[mIdx];
2633
+ const isMaskedOut = invertMask ? mVal !== 0 : mVal === 0;
2634
+ if (isMaskedOut) {
2635
+ const current = dst32[dIdx];
2636
+ const next = (current & 16777215) >>> 0;
2637
+ if (current !== next) {
2638
+ dst32[dIdx] = next;
2639
+ didChange = true;
2640
+ }
2641
+ } else if (globalAlpha !== 255) {
2642
+ const d = dst32[dIdx];
2643
+ const da = d >>> 24;
2644
+ if (da !== 0) {
2645
+ const finalAlpha = da === 255 ? globalAlpha : da * globalAlpha + 128 >> 8;
2646
+ const next = (d & 16777215 | finalAlpha << 24) >>> 0;
2647
+ if (d !== next) {
2648
+ dst32[dIdx] = next;
2649
+ didChange = true;
2650
+ }
2651
+ }
2652
+ }
2653
+ dIdx++;
2654
+ mIdx++;
2655
+ }
2656
+ dIdx += dStride;
2657
+ mIdx += mStride;
2658
+ }
2659
+ return didChange;
2660
+ }
2661
+
2662
+ // src/History/PixelMutator/mutatorApplyBinaryMask.ts
2663
+ var defaults3 = {
2664
+ applyBinaryMaskToPixelData
2665
+ };
2666
+ var mutatorApplyBinaryMask = ((writer, deps = defaults3) => {
2667
+ const {
2668
+ applyBinaryMaskToPixelData: applyBinaryMaskToPixelData2 = defaults3.applyBinaryMaskToPixelData
2669
+ } = deps;
2670
+ return {
2671
+ applyBinaryMask(mask, opts) {
2672
+ const target = writer.config.target;
2673
+ const x = opts?.x ?? 0;
2674
+ const y = opts?.y ?? 0;
2675
+ const w = opts?.w ?? target.width;
2676
+ const h = opts?.h ?? target.height;
2677
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2678
+ return didChange(applyBinaryMaskToPixelData2(target, mask, opts));
2679
+ }
2680
+ };
2681
+ });
2682
+
2683
+ // src/History/PixelMutator/mutatorApplyMask.ts
2684
+ var defaults4 = {
2685
+ applyBinaryMaskToPixelData,
2686
+ applyAlphaMaskToPixelData
2687
+ };
2688
+ var mutatorApplyMask = ((writer, deps = defaults4) => {
2689
+ const {
2690
+ applyBinaryMaskToPixelData: applyBinaryMaskToPixelData2 = defaults4.applyBinaryMaskToPixelData,
2691
+ applyAlphaMaskToPixelData: applyAlphaMaskToPixelData2 = defaults4.applyAlphaMaskToPixelData
2692
+ } = deps;
2693
+ return {
2694
+ applyMask(mask, opts) {
2695
+ const target = writer.config.target;
2696
+ const x = opts?.x ?? 0;
2697
+ const y = opts?.y ?? 0;
2698
+ const w = opts?.w ?? target.width;
2699
+ const h = opts?.h ?? target.height;
2700
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2701
+ if (mask.type === 1 /* BINARY */) {
2702
+ return didChange(applyBinaryMaskToPixelData2(target, mask, opts));
2703
+ } else {
2704
+ return didChange(applyAlphaMaskToPixelData2(target, mask, opts));
2705
+ }
2706
+ }
2707
+ };
2708
+ });
2709
+
2710
+ // src/PixelData/blendPixelDataAlphaMask.ts
2711
+ function blendPixelDataAlphaMask(target, src, alphaMask, opts) {
2712
+ const targetX = opts?.x ?? 0;
2713
+ const targetY = opts?.y ?? 0;
2714
+ const sourceX = opts?.sx ?? 0;
2715
+ const sourceY = opts?.sy ?? 0;
2716
+ const width = opts?.w ?? src.width;
2717
+ const height = opts?.h ?? src.height;
2718
+ const globalAlpha = opts?.alpha ?? 255;
2719
+ const blendFn = opts?.blendFn ?? sourceOverPerfect;
2720
+ const mx = opts?.mx ?? 0;
2721
+ const my = opts?.my ?? 0;
2722
+ const invertMask = opts?.invertMask ?? false;
2723
+ if (globalAlpha === 0) return false;
2256
2724
  let x = targetX;
2257
2725
  let y = targetY;
2258
2726
  let sx = sourceX;
@@ -2281,16 +2749,16 @@ function blendPixelDataAlphaMask(dst, src, alphaMask, opts = {}) {
2281
2749
  h += y;
2282
2750
  y = 0;
2283
2751
  }
2284
- const actualW = Math.min(w, dst.width - x);
2285
- const actualH = Math.min(h, dst.height - y);
2752
+ const actualW = Math.min(w, target.width - x);
2753
+ const actualH = Math.min(h, target.height - y);
2286
2754
  if (actualW <= 0 || actualH <= 0) return false;
2287
- const dw = dst.width;
2755
+ const dw = target.width;
2288
2756
  const sw = src.width;
2289
2757
  const mPitch = alphaMask.w;
2290
2758
  const maskData = alphaMask.data;
2291
2759
  const dx = x - targetX | 0;
2292
2760
  const dy = y - targetY | 0;
2293
- const dst32 = dst.data32;
2761
+ const dst32 = target.data32;
2294
2762
  const src32 = src.data32;
2295
2763
  let dIdx = y * dw + x | 0;
2296
2764
  let sIdx = sy * sw + sx | 0;
@@ -2359,20 +2827,20 @@ function blendPixelDataAlphaMask(dst, src, alphaMask, opts = {}) {
2359
2827
  return didChange;
2360
2828
  }
2361
2829
 
2362
- // src/History/PixelMutator/mutatorBlendPixelDataAlphaMask.ts
2830
+ // src/History/PixelMutator/mutatorBlendAlphaMask.ts
2363
2831
  var defaults5 = {
2364
2832
  blendPixelDataAlphaMask
2365
2833
  };
2366
- var mutatorBlendPixelDataAlphaMask = ((writer, deps = defaults5) => {
2834
+ var mutatorBlendAlphaMask = ((writer, deps = defaults5) => {
2367
2835
  const {
2368
2836
  blendPixelDataAlphaMask: blendPixelDataAlphaMask2 = defaults5.blendPixelDataAlphaMask
2369
2837
  } = deps;
2370
2838
  return {
2371
- blendPixelDataAlphaMask(src, mask, opts = {}) {
2372
- const x = opts.x ?? 0;
2373
- const y = opts.y ?? 0;
2374
- const w = opts.w ?? src.width;
2375
- const h = opts.h ?? src.height;
2839
+ blendAlphaMask(src, mask, opts) {
2840
+ const x = opts?.x ?? 0;
2841
+ const y = opts?.y ?? 0;
2842
+ const w = opts?.w ?? src.width;
2843
+ const h = opts?.h ?? src.height;
2376
2844
  const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2377
2845
  return didChange(blendPixelDataAlphaMask2(writer.config.target, src, mask, opts));
2378
2846
  }
@@ -2380,20 +2848,18 @@ var mutatorBlendPixelDataAlphaMask = ((writer, deps = defaults5) => {
2380
2848
  });
2381
2849
 
2382
2850
  // src/PixelData/blendPixelDataBinaryMask.ts
2383
- function blendPixelDataBinaryMask(dst, src, binaryMask, opts = {}) {
2384
- const {
2385
- x: targetX = 0,
2386
- y: targetY = 0,
2387
- sx: sourceX = 0,
2388
- sy: sourceY = 0,
2389
- w: width = src.width,
2390
- h: height = src.height,
2391
- alpha: globalAlpha = 255,
2392
- blendFn = sourceOverPerfect,
2393
- mx = 0,
2394
- my = 0,
2395
- invertMask = false
2396
- } = opts;
2851
+ function blendPixelDataBinaryMask(target, src, binaryMask, opts) {
2852
+ const targetX = opts?.x ?? 0;
2853
+ const targetY = opts?.y ?? 0;
2854
+ const sourceX = opts?.sx ?? 0;
2855
+ const sourceY = opts?.sy ?? 0;
2856
+ const width = opts?.w ?? src.width;
2857
+ const height = opts?.h ?? src.height;
2858
+ const globalAlpha = opts?.alpha ?? 255;
2859
+ const blendFn = opts?.blendFn ?? sourceOverPerfect;
2860
+ const mx = opts?.mx ?? 0;
2861
+ const my = opts?.my ?? 0;
2862
+ const invertMask = opts?.invertMask ?? false;
2397
2863
  if (globalAlpha === 0) return false;
2398
2864
  let x = targetX;
2399
2865
  let y = targetY;
@@ -2423,14 +2889,14 @@ function blendPixelDataBinaryMask(dst, src, binaryMask, opts = {}) {
2423
2889
  h += y;
2424
2890
  y = 0;
2425
2891
  }
2426
- const actualW = Math.min(w, dst.width - x);
2427
- const actualH = Math.min(h, dst.height - y);
2892
+ const actualW = Math.min(w, target.width - x);
2893
+ const actualH = Math.min(h, target.height - y);
2428
2894
  if (actualW <= 0 || actualH <= 0) return false;
2429
2895
  const dx = x - targetX | 0;
2430
2896
  const dy = y - targetY | 0;
2431
- const dst32 = dst.data32;
2897
+ const dst32 = target.data32;
2432
2898
  const src32 = src.data32;
2433
- const dw = dst.width;
2899
+ const dw = target.width;
2434
2900
  const sw = src.width;
2435
2901
  const mPitch = binaryMask.w;
2436
2902
  const maskData = binaryMask.data;
@@ -2488,1146 +2954,744 @@ function blendPixelDataBinaryMask(dst, src, binaryMask, opts = {}) {
2488
2954
  return didChange;
2489
2955
  }
2490
2956
 
2491
- // src/History/PixelMutator/mutatorBlendPixelDataBinaryMask.ts
2957
+ // src/History/PixelMutator/mutatorBlendBinaryMask.ts
2492
2958
  var defaults6 = {
2493
2959
  blendPixelDataBinaryMask
2494
2960
  };
2495
- var mutatorBlendPixelDataBinaryMask = ((writer, deps = defaults6) => {
2961
+ var mutatorBlendBinaryMask = ((writer, deps = defaults6) => {
2496
2962
  const {
2497
2963
  blendPixelDataBinaryMask: blendPixelDataBinaryMask2 = defaults6.blendPixelDataBinaryMask
2498
2964
  } = deps;
2499
2965
  return {
2500
- blendPixelDataBinaryMask(src, mask, opts = {}) {
2501
- const x = opts.x ?? 0;
2502
- const y = opts.y ?? 0;
2503
- const w = opts.w ?? src.width;
2504
- const h = opts.h ?? src.height;
2966
+ blendBinaryMask(src, mask, opts) {
2967
+ const x = opts?.x ?? 0;
2968
+ const y = opts?.y ?? 0;
2969
+ const w = opts?.w ?? src.width;
2970
+ const h = opts?.h ?? src.height;
2505
2971
  const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2506
2972
  return didChange(blendPixelDataBinaryMask2(writer.config.target, src, mask, opts));
2507
2973
  }
2508
2974
  };
2509
2975
  });
2510
2976
 
2511
- // src/PixelData/fillPixelDataFast.ts
2512
- var SCRATCH_RECT = makeClippedRect();
2513
- function fillPixelDataFast(dst, color, _x, _y, _w, _h) {
2514
- let x;
2515
- let y;
2516
- let w;
2517
- let h;
2518
- if (typeof _x === "object") {
2519
- x = _x.x ?? 0;
2520
- y = _x.y ?? 0;
2521
- w = _x.w ?? dst.width;
2522
- h = _x.h ?? dst.height;
2523
- } else if (typeof _x === "number") {
2524
- x = _x;
2525
- y = _y;
2526
- w = _w;
2527
- h = _h;
2528
- } else {
2977
+ // src/PixelData/blendColorPixelData.ts
2978
+ function blendColorPixelData(target, color, opts) {
2979
+ const targetX = opts?.x ?? 0;
2980
+ const targetY = opts?.y ?? 0;
2981
+ const width = opts?.w ?? target.width;
2982
+ const height = opts?.h ?? target.height;
2983
+ const globalAlpha = opts?.alpha ?? 255;
2984
+ const blendFn = opts?.blendFn ?? sourceOverPerfect;
2985
+ if (globalAlpha === 0) return false;
2986
+ const baseSrcAlpha = color >>> 24;
2987
+ const isOverwrite = blendFn.isOverwrite || false;
2988
+ if (baseSrcAlpha === 0 && !isOverwrite) return false;
2989
+ let x = targetX;
2990
+ let y = targetY;
2991
+ let w = width;
2992
+ let h = height;
2993
+ if (x < 0) {
2994
+ w += x;
2529
2995
  x = 0;
2996
+ }
2997
+ if (y < 0) {
2998
+ h += y;
2530
2999
  y = 0;
2531
- w = dst.width;
2532
- h = dst.height;
2533
3000
  }
2534
- const clip = resolveRectClipping(x, y, w, h, dst.width, dst.height, SCRATCH_RECT);
2535
- if (!clip.inBounds) return;
2536
- const {
2537
- x: finalX,
2538
- y: finalY,
2539
- w: actualW,
2540
- h: actualH
2541
- } = clip;
2542
- const dst32 = dst.data32;
2543
- const dw = dst.width;
2544
- if (actualW === dw && actualH === dst.height && finalX === 0 && finalY === 0) {
2545
- dst32.fill(color);
2546
- return;
3001
+ const actualW = Math.min(w, target.width - x);
3002
+ const actualH = Math.min(h, target.height - y);
3003
+ if (actualW <= 0 || actualH <= 0) return false;
3004
+ let finalSrcColor = color;
3005
+ if (globalAlpha < 255) {
3006
+ const a = baseSrcAlpha * globalAlpha + 128 >> 8;
3007
+ if (a === 0 && !isOverwrite) return false;
3008
+ finalSrcColor = (color & 16777215 | a << 24) >>> 0;
2547
3009
  }
3010
+ const dst32 = target.data32;
3011
+ const dw = target.width;
3012
+ let dIdx = y * dw + x | 0;
3013
+ const dStride = dw - actualW | 0;
3014
+ let didChange = false;
2548
3015
  for (let iy = 0; iy < actualH; iy++) {
2549
- const start = (finalY + iy) * dw + finalX;
2550
- const end = start + actualW;
2551
- dst32.fill(color, start, end);
3016
+ for (let ix = 0; ix < actualW; ix++) {
3017
+ const current = dst32[dIdx];
3018
+ const next = blendFn(finalSrcColor, current);
3019
+ if (current !== next) {
3020
+ dst32[dIdx] = next;
3021
+ didChange = true;
3022
+ }
3023
+ dIdx++;
3024
+ }
3025
+ dIdx += dStride;
2552
3026
  }
3027
+ return didChange;
2553
3028
  }
2554
3029
 
2555
- // src/History/PixelMutator/mutatorClear.ts
3030
+ // src/History/PixelMutator/mutatorBlendColor.ts
2556
3031
  var defaults7 = {
2557
- fillPixelData: fillPixelDataFast
3032
+ blendColorPixelData
2558
3033
  };
2559
- var mutatorClear = ((writer, deps = defaults7) => {
3034
+ var mutatorBlendColor = ((writer, deps = defaults7) => {
2560
3035
  const {
2561
- fillPixelData: fillPixelData2 = defaults7.fillPixelData
3036
+ blendColorPixelData: blendColorPixelData2 = defaults7.blendColorPixelData
2562
3037
  } = deps;
2563
3038
  return {
2564
- clear(rect = {}) {
3039
+ blendColor(color, opts) {
2565
3040
  const target = writer.config.target;
2566
- const x = rect.x ?? 0;
2567
- const y = rect.y ?? 0;
2568
- const w = rect.w ?? target.width;
2569
- const h = rect.h ?? target.height;
2570
- writer.accumulator.storeRegionBeforeState(x, y, w, h);
2571
- fillPixelData2(target, 0, x, y, w, h);
3041
+ const x = opts?.x ?? 0;
3042
+ const y = opts?.y ?? 0;
3043
+ const w = opts?.w ?? target.width;
3044
+ const h = opts?.h ?? target.height;
3045
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3046
+ return didChange(blendColorPixelData2(target, color, opts));
2572
3047
  }
2573
3048
  };
2574
3049
  });
2575
3050
 
2576
- // src/PixelData/fillPixelData.ts
2577
- var SCRATCH_RECT2 = makeClippedRect();
2578
- function fillPixelData(dst, color, _x, _y, _w, _h) {
2579
- let x;
2580
- let y;
2581
- let w;
2582
- let h;
2583
- if (typeof _x === "object") {
2584
- x = _x.x ?? 0;
2585
- y = _x.y ?? 0;
2586
- w = _x.w ?? dst.width;
2587
- h = _x.h ?? dst.height;
2588
- } else if (typeof _x === "number") {
2589
- x = _x;
2590
- y = _y;
2591
- w = _w;
2592
- h = _h;
2593
- } else {
3051
+ // src/PixelData/blendColorPixelDataAlphaMask.ts
3052
+ function blendColorPixelDataAlphaMask(target, color, mask, opts) {
3053
+ const targetX = opts?.x ?? 0;
3054
+ const targetY = opts?.y ?? 0;
3055
+ const w = opts?.w ?? mask.w;
3056
+ const h = opts?.h ?? mask.h;
3057
+ const globalAlpha = opts?.alpha ?? 255;
3058
+ const blendFn = opts?.blendFn ?? sourceOverPerfect;
3059
+ const mx = opts?.mx ?? 0;
3060
+ const my = opts?.my ?? 0;
3061
+ const invertMask = opts?.invertMask ?? false;
3062
+ if (globalAlpha === 0) return false;
3063
+ const baseSrcAlpha = color >>> 24;
3064
+ const isOverwrite = blendFn.isOverwrite || false;
3065
+ if (baseSrcAlpha === 0 && !isOverwrite) return false;
3066
+ let x = targetX;
3067
+ let y = targetY;
3068
+ let actualW = w;
3069
+ let actualH = h;
3070
+ if (x < 0) {
3071
+ actualW += x;
2594
3072
  x = 0;
3073
+ }
3074
+ if (y < 0) {
3075
+ actualH += y;
2595
3076
  y = 0;
2596
- w = dst.width;
2597
- h = dst.height;
2598
3077
  }
2599
- const clip = resolveRectClipping(x, y, w, h, dst.width, dst.height, SCRATCH_RECT2);
2600
- if (!clip.inBounds) return false;
2601
- const {
2602
- x: finalX,
2603
- y: finalY,
2604
- w: actualW,
2605
- h: actualH
2606
- } = clip;
2607
- const dst32 = dst.data32;
2608
- const dw = dst.width;
2609
- let hasChanged = false;
3078
+ actualW = Math.min(actualW, target.width - x);
3079
+ actualH = Math.min(actualH, target.height - y);
3080
+ if (actualW <= 0 || actualH <= 0) return false;
3081
+ const dx = x - targetX | 0;
3082
+ const dy = y - targetY | 0;
3083
+ const dst32 = target.data32;
3084
+ const dw = target.width;
3085
+ const mPitch = mask.w;
3086
+ const maskData = mask.data;
3087
+ let dIdx = y * dw + x | 0;
3088
+ let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
3089
+ const dStride = dw - actualW | 0;
3090
+ const mStride = mPitch - actualW | 0;
3091
+ const isOpaque = globalAlpha === 255;
3092
+ const colorRGB = color & 16777215;
3093
+ let didChange = false;
2610
3094
  for (let iy = 0; iy < actualH; iy++) {
2611
- const rowOffset = (finalY + iy) * dw;
2612
- const start = rowOffset + finalX;
2613
- const end = start + actualW;
2614
- for (let i = start; i < end; i++) {
2615
- if (dst32[i] !== color) {
2616
- dst32[i] = color;
2617
- hasChanged = true;
3095
+ for (let ix = 0; ix < actualW; ix++) {
3096
+ const mVal = maskData[mIdx];
3097
+ const effM = invertMask ? 255 - mVal : mVal;
3098
+ if (effM === 0) {
3099
+ dIdx++;
3100
+ mIdx++;
3101
+ continue;
3102
+ }
3103
+ let weight = globalAlpha;
3104
+ if (isOpaque) {
3105
+ weight = effM;
3106
+ } else if (effM !== 255) {
3107
+ weight = effM * globalAlpha + 128 >> 8;
2618
3108
  }
3109
+ if (weight === 0) {
3110
+ dIdx++;
3111
+ mIdx++;
3112
+ continue;
3113
+ }
3114
+ let finalCol = color;
3115
+ if (weight < 255) {
3116
+ const a = baseSrcAlpha * weight + 128 >> 8;
3117
+ if (a === 0 && !isOverwrite) {
3118
+ dIdx++;
3119
+ mIdx++;
3120
+ continue;
3121
+ }
3122
+ finalCol = (colorRGB | a << 24) >>> 0;
3123
+ }
3124
+ const current = dst32[dIdx];
3125
+ const next = blendFn(finalCol, current);
3126
+ if (current !== next) {
3127
+ dst32[dIdx] = next;
3128
+ didChange = true;
3129
+ }
3130
+ dIdx++;
3131
+ mIdx++;
2619
3132
  }
3133
+ dIdx += dStride;
3134
+ mIdx += mStride;
2620
3135
  }
2621
- return hasChanged;
3136
+ return didChange;
2622
3137
  }
2623
3138
 
2624
- // src/History/PixelMutator/mutatorFill.ts
3139
+ // src/History/PixelMutator/mutatorBlendColorPaintAlphaMask.ts
2625
3140
  var defaults8 = {
2626
- fillPixelData
3141
+ blendColorPixelDataAlphaMask
2627
3142
  };
2628
- var mutatorFill = ((writer, deps = defaults8) => {
3143
+ var mutatorBlendColorPaintAlphaMask = ((writer, deps = defaults8) => {
2629
3144
  const {
2630
- fillPixelData: fillPixelData2 = defaults8.fillPixelData
3145
+ blendColorPixelDataAlphaMask: blendColorPixelDataAlphaMask2 = defaults8.blendColorPixelDataAlphaMask
2631
3146
  } = deps;
2632
- return {
2633
- fill(color, x = 0, y = 0, w = writer.config.target.width, h = writer.config.target.height) {
2634
- const target = writer.config.target;
2635
- const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2636
- return didChange(fillPixelData2(target, color, x, y, w, h));
2637
- }
3147
+ const OPTS = {
3148
+ x: 0,
3149
+ y: 0,
3150
+ blendFn: sourceOverPerfect,
3151
+ alpha: 255
2638
3152
  };
2639
- });
2640
- var mutatorFillRect = ((writer, deps = defaults8) => {
2641
- const {
2642
- fillPixelData: fillPixelData2 = defaults8.fillPixelData
2643
- } = deps;
2644
3153
  return {
2645
- fillRect(color, rect) {
2646
- const target = writer.config.target;
2647
- const didChange = writer.accumulator.storeRegionBeforeState(rect.x, rect.y, rect.w, rect.h);
2648
- return didChange(fillPixelData2(target, color, rect.x, rect.y, rect.w, rect.h));
3154
+ blendColorPaintAlphaMask(color, mask, x, y, alpha = 255, blendFn = sourceOverPerfect) {
3155
+ const tx = x + mask.centerOffsetX;
3156
+ const ty = y + mask.centerOffsetY;
3157
+ const didChange = writer.accumulator.storeRegionBeforeState(tx, ty, mask.w, mask.h);
3158
+ OPTS.x = tx;
3159
+ OPTS.y = ty;
3160
+ OPTS.alpha = alpha;
3161
+ OPTS.blendFn = blendFn;
3162
+ return didChange(blendColorPixelDataAlphaMask2(writer.config.target, color, mask, OPTS));
2649
3163
  }
2650
3164
  };
2651
3165
  });
2652
3166
 
2653
- // src/PixelData/fillPixelDataBinaryMask.ts
2654
- var SCRATCH_RECT3 = makeClippedRect();
2655
- function fillPixelDataBinaryMask(dst, color, mask, alpha = 255, x = 0, y = 0) {
2656
- if (alpha === 0) return false;
2657
- const maskW = mask.w;
2658
- const maskH = mask.h;
2659
- const clip = resolveRectClipping(x, y, maskW, maskH, dst.width, dst.height, SCRATCH_RECT3);
2660
- if (!clip.inBounds) return false;
2661
- const {
2662
- x: finalX,
2663
- y: finalY,
2664
- w: actualW,
2665
- h: actualH
2666
- } = clip;
2667
- const maskData = mask.data;
2668
- const dst32 = dst.data32;
2669
- const dw = dst.width;
2670
- let finalCol = color;
2671
- if (alpha < 255) {
2672
- const baseSrcAlpha = color >>> 24;
2673
- const colorRGB = color & 16777215;
2674
- const a = baseSrcAlpha * alpha + 128 >> 8;
2675
- finalCol = (colorRGB | a << 24) >>> 0;
2676
- }
2677
- let hasChanged = false;
2678
- for (let iy = 0; iy < actualH; iy++) {
2679
- const currentY = finalY + iy;
2680
- const maskY = currentY - y;
2681
- const maskOffset = maskY * maskW;
2682
- const dstRowOffset = currentY * dw;
2683
- for (let ix = 0; ix < actualW; ix++) {
2684
- const currentX = finalX + ix;
2685
- const maskX = currentX - x;
2686
- const maskIndex = maskOffset + maskX;
2687
- if (maskData[maskIndex]) {
2688
- const current = dst32[dstRowOffset + currentX];
2689
- if (current !== finalCol) {
2690
- dst32[dstRowOffset + currentX] = finalCol;
2691
- hasChanged = true;
2692
- }
2693
- }
2694
- }
3167
+ // src/PixelData/blendColorPixelDataBinaryMask.ts
3168
+ function blendColorPixelDataBinaryMask(target, color, mask, opts) {
3169
+ const targetX = opts?.x ?? 0;
3170
+ const targetY = opts?.y ?? 0;
3171
+ let w = opts?.w ?? mask.w;
3172
+ let h = opts?.h ?? mask.h;
3173
+ const globalAlpha = opts?.alpha ?? 255;
3174
+ const blendFn = opts?.blendFn ?? sourceOverPerfect;
3175
+ const mx = opts?.mx ?? 0;
3176
+ const my = opts?.my ?? 0;
3177
+ const invertMask = opts?.invertMask ?? false;
3178
+ if (globalAlpha === 0) return false;
3179
+ const baseSrcAlpha = color >>> 24;
3180
+ const isOverwrite = blendFn.isOverwrite || false;
3181
+ if (baseSrcAlpha === 0 && !isOverwrite) return false;
3182
+ let x = targetX;
3183
+ let y = targetY;
3184
+ if (x < 0) {
3185
+ w += x;
3186
+ x = 0;
2695
3187
  }
2696
- return hasChanged;
3188
+ if (y < 0) {
3189
+ h += y;
3190
+ y = 0;
3191
+ }
3192
+ const actualW = Math.min(w, target.width - x);
3193
+ const actualH = Math.min(h, target.height - y);
3194
+ if (actualW <= 0 || actualH <= 0) return false;
3195
+ let baseColorWithGlobalAlpha = color;
3196
+ if (globalAlpha < 255) {
3197
+ const a = baseSrcAlpha * globalAlpha + 128 >> 8;
3198
+ if (a === 0 && !isOverwrite) return false;
3199
+ baseColorWithGlobalAlpha = (color & 16777215 | a << 24) >>> 0;
3200
+ }
3201
+ const dx = x - targetX | 0;
3202
+ const dy = y - targetY | 0;
3203
+ const dst32 = target.data32;
3204
+ const dw = target.width;
3205
+ const mPitch = mask.w;
3206
+ const maskData = mask.data;
3207
+ let dIdx = y * dw + x | 0;
3208
+ let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
3209
+ const dStride = dw - actualW | 0;
3210
+ const mStride = mPitch - actualW | 0;
3211
+ const skipVal = invertMask ? 1 : 0;
3212
+ let didChange = false;
3213
+ for (let iy = 0; iy < actualH; iy++) {
3214
+ for (let ix = 0; ix < actualW; ix++) {
3215
+ if (maskData[mIdx] === skipVal) {
3216
+ dIdx++;
3217
+ mIdx++;
3218
+ continue;
3219
+ }
3220
+ const current = dst32[dIdx];
3221
+ const next = blendFn(baseColorWithGlobalAlpha, current);
3222
+ if (current !== next) {
3223
+ dst32[dIdx] = next;
3224
+ didChange = true;
3225
+ }
3226
+ dIdx++;
3227
+ mIdx++;
3228
+ }
3229
+ dIdx += dStride;
3230
+ mIdx += mStride;
3231
+ }
3232
+ return didChange;
2697
3233
  }
2698
3234
 
2699
- // src/History/PixelMutator/mutatorFillBinaryMask.ts
3235
+ // src/History/PixelMutator/mutatorBlendColorPaintBinaryMask.ts
2700
3236
  var defaults9 = {
2701
- fillPixelDataBinaryMask
3237
+ blendColorPixelDataBinaryMask
2702
3238
  };
2703
- var mutatorFillBinaryMask = ((writer, deps = defaults9) => {
3239
+ var mutatorBlendColorPaintBinaryMask = ((writer, deps = defaults9) => {
2704
3240
  const {
2705
- fillPixelDataBinaryMask: fillPixelDataBinaryMask2 = defaults9.fillPixelDataBinaryMask
3241
+ blendColorPixelDataBinaryMask: blendColorPixelDataBinaryMask2 = defaults9.blendColorPixelDataBinaryMask
2706
3242
  } = deps;
3243
+ const OPTS = {
3244
+ x: 0,
3245
+ y: 0,
3246
+ blendFn: sourceOverPerfect,
3247
+ alpha: 255
3248
+ };
2707
3249
  return {
2708
- fillBinaryMask(color, mask, alpha = 255, x = 0, y = 0) {
2709
- const didChange = writer.accumulator.storeRegionBeforeState(x, y, mask.w, mask.h);
2710
- return didChange(fillPixelDataBinaryMask2(writer.config.target, color, mask, alpha, x, y));
3250
+ blendColorPaintBinaryMask(color, mask, x, y, alpha = 255, blendFn = sourceOverPerfect) {
3251
+ const tx = x + mask.centerOffsetX;
3252
+ const ty = y + mask.centerOffsetY;
3253
+ const didChange = writer.accumulator.storeRegionBeforeState(tx, ty, mask.w, mask.h);
3254
+ OPTS.x = tx;
3255
+ OPTS.y = ty;
3256
+ OPTS.alpha = alpha;
3257
+ OPTS.blendFn = blendFn;
3258
+ return didChange(blendColorPixelDataBinaryMask2(writer.config.target, color, mask, OPTS));
2711
3259
  }
2712
3260
  };
2713
3261
  });
2714
3262
 
2715
- // src/PixelData/invertPixelData.ts
2716
- var SCRATCH_RECT4 = makeClippedRect();
2717
- function invertPixelData(pixelData, opts = {}) {
2718
- const dst = pixelData;
3263
+ // src/History/PixelMutator/mutatorBlendColorPaintMask.ts
3264
+ var defaults10 = {
3265
+ blendColorPixelDataAlphaMask,
3266
+ blendColorPixelDataBinaryMask
3267
+ };
3268
+ var mutatorBlendColorPaintMask = ((writer, deps = defaults10) => {
2719
3269
  const {
2720
- x: targetX = 0,
2721
- y: targetY = 0,
2722
- w: width = pixelData.width,
2723
- h: height = pixelData.height,
2724
- mask,
2725
- mx = 0,
2726
- my = 0,
2727
- invertMask = false
2728
- } = opts;
2729
- const clip = resolveRectClipping(targetX, targetY, width, height, dst.width, dst.height, SCRATCH_RECT4);
3270
+ blendColorPixelDataBinaryMask: blendColorPixelDataBinaryMask2 = defaults10.blendColorPixelDataBinaryMask,
3271
+ blendColorPixelDataAlphaMask: blendColorPixelDataAlphaMask2 = defaults10.blendColorPixelDataAlphaMask
3272
+ } = deps;
3273
+ const OPTS = {
3274
+ x: 0,
3275
+ y: 0,
3276
+ blendFn: sourceOverPerfect,
3277
+ alpha: 255
3278
+ };
3279
+ return {
3280
+ blendColorPaintMask(color, mask, x, y, alpha = 255, blendFn = sourceOverPerfect) {
3281
+ const tx = x + mask.centerOffsetX;
3282
+ const ty = y + mask.centerOffsetY;
3283
+ const didChange = writer.accumulator.storeRegionBeforeState(tx, ty, mask.w, mask.h);
3284
+ OPTS.x = tx;
3285
+ OPTS.y = ty;
3286
+ OPTS.alpha = alpha;
3287
+ OPTS.blendFn = blendFn;
3288
+ if (mask.type === 1 /* BINARY */) {
3289
+ return didChange(blendColorPixelDataBinaryMask2(writer.config.target, color, mask, OPTS));
3290
+ } else {
3291
+ return didChange(blendColorPixelDataAlphaMask2(writer.config.target, color, mask, OPTS));
3292
+ }
3293
+ }
3294
+ };
3295
+ });
3296
+
3297
+ // src/History/PixelMutator/mutatorBlendMask.ts
3298
+ var defaults11 = {
3299
+ blendPixelDataAlphaMask,
3300
+ blendPixelDataBinaryMask
3301
+ };
3302
+ var mutatorBlendMask = ((writer, deps = defaults11) => {
3303
+ const {
3304
+ blendPixelDataAlphaMask: blendPixelDataAlphaMask2 = defaults11.blendPixelDataAlphaMask,
3305
+ blendPixelDataBinaryMask: blendPixelDataBinaryMask2 = defaults11.blendPixelDataBinaryMask
3306
+ } = deps;
3307
+ return {
3308
+ blendMask(src, mask, opts) {
3309
+ const x = opts?.x ?? 0;
3310
+ const y = opts?.y ?? 0;
3311
+ const w = opts?.w ?? src.width;
3312
+ const h = opts?.h ?? src.height;
3313
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3314
+ if (mask.type === 1 /* BINARY */) {
3315
+ return didChange(blendPixelDataBinaryMask2(writer.config.target, src, mask, opts));
3316
+ } else {
3317
+ return didChange(blendPixelDataAlphaMask2(writer.config.target, src, mask, opts));
3318
+ }
3319
+ }
3320
+ };
3321
+ });
3322
+
3323
+ // src/History/PixelMutator/mutatorBlendColorPaintRect.ts
3324
+ var defaults12 = {
3325
+ blendColorPixelData
3326
+ };
3327
+ var mutatorBlendColorPaintRect = ((writer, deps = defaults12) => {
3328
+ const {
3329
+ blendColorPixelData: blendColorPixelData2 = defaults12.blendColorPixelData
3330
+ } = deps;
3331
+ const OPTS = {
3332
+ x: 0,
3333
+ y: 0,
3334
+ w: 0,
3335
+ h: 0,
3336
+ blendFn: sourceOverPerfect,
3337
+ alpha: 255
3338
+ };
3339
+ return {
3340
+ blendColorPaintRect(color, centerX, centerY, brushWidth, brushHeight, alpha = 255, blendFn = sourceOverPerfect) {
3341
+ const target = writer.config.target;
3342
+ const topLeftX = centerX + -(brushWidth - 1 >> 1);
3343
+ const topLeftY = centerY + -(brushHeight - 1 >> 1);
3344
+ OPTS.x = topLeftX;
3345
+ OPTS.y = topLeftY;
3346
+ OPTS.w = brushWidth;
3347
+ OPTS.h = brushHeight;
3348
+ OPTS.blendFn = blendFn;
3349
+ OPTS.alpha = alpha;
3350
+ const didChange = writer.accumulator.storeRegionBeforeState(topLeftX, topLeftY, brushWidth, brushHeight);
3351
+ return didChange(blendColorPixelData2(target, color, OPTS));
3352
+ }
3353
+ };
3354
+ });
3355
+
3356
+ // src/PixelData/blendPixel.ts
3357
+ function blendPixel(target, x, y, color, alpha = 255, blendFn = sourceOverPerfect) {
3358
+ if (alpha === 0) return false;
3359
+ let width = target.width;
3360
+ let height = target.height;
3361
+ if (x < 0 || x >= width || y < 0 || y >= height) return false;
3362
+ let srcAlpha = color >>> 24;
3363
+ let isOverwrite = blendFn.isOverwrite;
3364
+ if (srcAlpha === 0 && !isOverwrite) return false;
3365
+ let dst32 = target.data32;
3366
+ let index = y * width + x;
3367
+ let finalColor = color;
3368
+ if (alpha !== 255) {
3369
+ let finalAlpha = srcAlpha * alpha + 128 >> 8;
3370
+ if (finalAlpha === 0 && !isOverwrite) return false;
3371
+ finalColor = (color & 16777215 | finalAlpha << 24) >>> 0;
3372
+ }
3373
+ let current = dst32[index];
3374
+ let next = blendFn(finalColor, current);
3375
+ if (current !== next) {
3376
+ dst32[index] = next;
3377
+ return true;
3378
+ }
3379
+ return false;
3380
+ }
3381
+
3382
+ // src/History/PixelMutator/mutatorBlendPixel.ts
3383
+ var defaults13 = {
3384
+ blendPixel
3385
+ };
3386
+ var mutatorBlendPixel = ((writer, deps = defaults13) => {
3387
+ const {
3388
+ blendPixel: blendPixel2 = defaults13.blendPixel
3389
+ } = deps;
3390
+ return {
3391
+ blendPixel(x, y, color, alpha, blendFn) {
3392
+ const didChange = writer.accumulator.storePixelBeforeState(x, y);
3393
+ return didChange(blendPixel2(writer.config.target, x, y, color, alpha, blendFn));
3394
+ }
3395
+ };
3396
+ });
3397
+
3398
+ // src/History/PixelMutator/mutatorBlendPixelData.ts
3399
+ var defaults14 = {
3400
+ blendPixelData
3401
+ };
3402
+ var mutatorBlendPixelData = ((writer, deps = defaults14) => {
3403
+ const {
3404
+ blendPixelData: blendPixelData2 = defaults14.blendPixelData
3405
+ } = deps;
3406
+ return {
3407
+ blendPixelData(src, opts) {
3408
+ const x = opts?.x ?? 0;
3409
+ const y = opts?.y ?? 0;
3410
+ const w = opts?.w ?? src.width;
3411
+ const h = opts?.h ?? src.height;
3412
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3413
+ return didChange(blendPixelData2(writer.config.target, src, opts));
3414
+ }
3415
+ };
3416
+ });
3417
+
3418
+ // src/PixelData/fillPixelData.ts
3419
+ var SCRATCH_RECT = makeClippedRect();
3420
+ function fillPixelData(dst, color, _x, _y, _w, _h) {
3421
+ let x;
3422
+ let y;
3423
+ let w;
3424
+ let h;
3425
+ if (typeof _x === "object") {
3426
+ x = _x.x ?? 0;
3427
+ y = _x.y ?? 0;
3428
+ w = _x.w ?? dst.width;
3429
+ h = _x.h ?? dst.height;
3430
+ } else if (typeof _x === "number") {
3431
+ x = _x;
3432
+ y = _y;
3433
+ w = _w;
3434
+ h = _h;
3435
+ } else {
3436
+ x = 0;
3437
+ y = 0;
3438
+ w = dst.width;
3439
+ h = dst.height;
3440
+ }
3441
+ const clip = resolveRectClipping(x, y, w, h, dst.width, dst.height, SCRATCH_RECT);
2730
3442
  if (!clip.inBounds) return false;
2731
3443
  const {
2732
- x,
2733
- y,
3444
+ x: finalX,
3445
+ y: finalY,
2734
3446
  w: actualW,
2735
3447
  h: actualH
2736
3448
  } = clip;
2737
3449
  const dst32 = dst.data32;
2738
3450
  const dw = dst.width;
2739
- const mPitch = mask?.w ?? width;
2740
- const dx = x - targetX;
2741
- const dy = y - targetY;
2742
- let dIdx = y * dw + x;
2743
- let mIdx = (my + dy) * mPitch + (mx + dx);
2744
- const dStride = dw - actualW;
2745
- const mStride = mPitch - actualW;
2746
- if (mask) {
2747
- const maskData = mask.data;
2748
- for (let iy = 0; iy < actualH; iy++) {
2749
- for (let ix = 0; ix < actualW; ix++) {
2750
- const mVal = maskData[mIdx];
2751
- const isHit = invertMask ? mVal === 0 : mVal === 1;
2752
- if (isHit) {
2753
- dst32[dIdx] = dst32[dIdx] ^ 16777215;
2754
- }
2755
- dIdx++;
2756
- mIdx++;
2757
- }
2758
- dIdx += dStride;
2759
- mIdx += mStride;
2760
- }
2761
- } else {
2762
- for (let iy = 0; iy < actualH; iy++) {
2763
- for (let ix = 0; ix < actualW; ix++) {
2764
- dst32[dIdx] = dst32[dIdx] ^ 16777215;
2765
- dIdx++;
3451
+ let hasChanged = false;
3452
+ for (let iy = 0; iy < actualH; iy++) {
3453
+ const rowOffset = (finalY + iy) * dw;
3454
+ const start = rowOffset + finalX;
3455
+ const end = start + actualW;
3456
+ for (let i = start; i < end; i++) {
3457
+ if (dst32[i] !== color) {
3458
+ dst32[i] = color;
3459
+ hasChanged = true;
2766
3460
  }
2767
- dIdx += dStride;
2768
3461
  }
2769
3462
  }
2770
- return true;
3463
+ return hasChanged;
2771
3464
  }
2772
3465
 
2773
- // src/History/PixelMutator/mutatorInvert.ts
2774
- var defaults10 = {
2775
- invertPixelData
3466
+ // src/History/PixelMutator/mutatorClear.ts
3467
+ var defaults15 = {
3468
+ fillPixelData
2776
3469
  };
2777
- var mutatorInvert = ((writer, deps = defaults10) => {
3470
+ var mutatorClear = ((writer, deps = defaults15) => {
2778
3471
  const {
2779
- invertPixelData: invertPixelData2 = defaults10.invertPixelData
3472
+ fillPixelData: fillPixelData2 = defaults15.fillPixelData
2780
3473
  } = deps;
2781
3474
  return {
2782
- invert(opts = {}) {
3475
+ clear(rect) {
2783
3476
  const target = writer.config.target;
2784
- const {
2785
- x = 0,
2786
- y = 0,
2787
- w = target.width,
2788
- h = target.height
2789
- } = opts;
3477
+ const x = rect?.x ?? 0;
3478
+ const y = rect?.y ?? 0;
3479
+ const w = rect?.w ?? target.width;
3480
+ const h = rect?.h ?? target.height;
2790
3481
  const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2791
- return didChange(invertPixelData2(target, opts));
3482
+ return didChange(fillPixelData2(target, 0, x, y, w, h));
2792
3483
  }
2793
3484
  };
2794
3485
  });
2795
3486
 
2796
- // src/History/PixelMutator.ts
2797
- function makeFullPixelMutator(writer) {
2798
- return {
2799
- // @sort
2800
- ...mutatorBlendColor(writer),
2801
- ...mutatorBlendPixel(writer),
2802
- ...mutatorBlendPixelData(writer),
2803
- ...mutatorBlendPixelDataAlphaMask(writer),
2804
- ...mutatorBlendPixelDataBinaryMask(writer),
2805
- ...mutatorClear(writer),
2806
- ...mutatorFill(writer),
2807
- ...mutatorFillBinaryMask(writer),
2808
- ...mutatorFillRect(writer),
2809
- ...mutatorInvert(writer)
2810
- };
2811
- }
2812
-
2813
- // src/ImageData/resizeImageData.ts
2814
- function resizeImageData(target, newWidth, newHeight, offsetX = 0, offsetY = 0) {
2815
- const result = new ImageData(newWidth, newHeight);
2816
- const {
2817
- width: oldW,
2818
- height: oldH,
2819
- data: oldData
2820
- } = target;
2821
- const newData = result.data;
2822
- const x0 = Math.max(0, offsetX);
2823
- const y0 = Math.max(0, offsetY);
2824
- const x1 = Math.min(newWidth, offsetX + oldW);
2825
- const y1 = Math.min(newHeight, offsetY + oldH);
2826
- if (x1 <= x0 || y1 <= y0) {
2827
- return result;
2828
- }
2829
- const rowCount = y1 - y0;
2830
- const rowLen = (x1 - x0) * 4;
2831
- for (let row = 0; row < rowCount; row++) {
2832
- const dstY = y0 + row;
2833
- const srcY = dstY - offsetY;
2834
- const srcX = x0 - offsetX;
2835
- const dstStart = (dstY * newWidth + x0) * 4;
2836
- const srcStart = (srcY * oldW + srcX) * 4;
2837
- newData.set(oldData.subarray(srcStart, srcStart + rowLen), dstStart);
2838
- }
2839
- return result;
2840
- }
2841
-
2842
- // src/Internal/helpers.ts
2843
- var macro_halfAndFloor = (value) => value >> 1;
2844
-
2845
- // src/Rect/trimRectBounds.ts
2846
- function trimRectBounds(x, y, w, h, targetWidth, targetHeight, out) {
2847
- const res = out ?? {
2848
- x: 0,
2849
- y: 0,
2850
- w: 0,
2851
- h: 0
2852
- };
2853
- const left = Math.max(0, x);
2854
- const top = Math.max(0, y);
2855
- const right = Math.min(targetWidth, x + w);
2856
- const bottom = Math.min(targetHeight, y + h);
2857
- res.x = left;
2858
- res.y = top;
2859
- res.w = Math.max(0, right - left);
2860
- res.h = Math.max(0, bottom - top);
2861
- return res;
2862
- }
2863
-
2864
- // src/Paint/PaintBuffer.ts
2865
- var PaintBuffer = class {
2866
- constructor(config, tilePool) {
2867
- this.config = config;
2868
- this.tilePool = tilePool;
2869
- this.lookup = [];
2870
- }
2871
- lookup;
2872
- scratchBounds = {
2873
- x: 0,
2874
- y: 0,
2875
- w: 0,
2876
- h: 0
2877
- };
2878
- eachTileInBounds(bounds, callback) {
2879
- const {
2880
- tileShift,
2881
- targetColumns,
2882
- targetRows,
2883
- tileSize
2884
- } = this.config;
2885
- const x1 = Math.max(0, bounds.x >> tileShift);
2886
- const y1 = Math.max(0, bounds.y >> tileShift);
2887
- const x2 = Math.min(targetColumns - 1, bounds.x + bounds.w - 1 >> tileShift);
2888
- const y2 = Math.min(targetRows - 1, bounds.y + bounds.h - 1 >> tileShift);
2889
- if (x1 > x2 || y1 > y2) return;
2890
- const lookup = this.lookup;
2891
- const tilePool = this.tilePool;
2892
- for (let ty = y1; ty <= y2; ty++) {
2893
- const rowOffset = ty * targetColumns;
2894
- const tileTop = ty << tileShift;
2895
- for (let tx = x1; tx <= x2; tx++) {
2896
- const id = rowOffset + tx;
2897
- const tile = lookup[id] ?? (lookup[id] = tilePool.getTile(id, tx, ty));
2898
- const tileLeft = tx << tileShift;
2899
- const startX = bounds.x > tileLeft ? bounds.x : tileLeft;
2900
- const startY = bounds.y > tileTop ? bounds.y : tileTop;
2901
- const maskEndX = bounds.x + bounds.w;
2902
- const tileEndX = tileLeft + tileSize;
2903
- const endX = maskEndX < tileEndX ? maskEndX : tileEndX;
2904
- const maskEndY = bounds.y + bounds.h;
2905
- const tileEndY = tileTop + tileSize;
2906
- const endY = maskEndY < tileEndY ? maskEndY : tileEndY;
2907
- callback(tile, startX, startY, endX - startX, endY - startY);
2908
- }
2909
- }
2910
- }
2911
- writePaintAlphaMaskStroke(color, brush, x0, y0, x1, y1) {
2912
- const cA = color >>> 24;
2913
- if (cA === 0) return false;
2914
- const {
2915
- tileShift,
2916
- tileMask,
2917
- target
2918
- } = this.config;
2919
- const {
2920
- w: bW,
2921
- h: bH,
2922
- data: bD,
2923
- centerOffsetX,
2924
- centerOffsetY
2925
- } = brush;
2926
- const cRGB = color & 16777215;
2927
- const scratch = this.scratchBounds;
2928
- let changed = false;
2929
- forEachLinePoint(x0, y0, x1, y1, (px, py) => {
2930
- const topLeftX = Math.floor(px + centerOffsetX);
2931
- const topLeftY = Math.floor(py + centerOffsetY);
2932
- trimRectBounds(topLeftX, topLeftY, bW, bH, target.width, target.height, scratch);
2933
- if (scratch.w <= 0 || scratch.h <= 0) return;
2934
- this.eachTileInBounds(scratch, (tile, bX, bY, bW_t, bH_t) => {
2935
- const d32 = tile.data32;
2936
- let tileChanged = false;
2937
- for (let i = 0; i < bH_t; i++) {
2938
- const canvasY = bY + i;
2939
- const bOff = (canvasY - topLeftY) * bW;
2940
- const tOff = (canvasY & tileMask) << tileShift;
2941
- const dS = tOff + (bX & tileMask);
2942
- for (let j = 0; j < bW_t; j++) {
2943
- const canvasX = bX + j;
2944
- const brushA = bD[bOff + (canvasX - topLeftX)];
2945
- if (brushA === 0) continue;
2946
- const t = cA * brushA + 128;
2947
- const blendedA = t + (t >> 8) >> 8;
2948
- const idx = dS + j;
2949
- const cur = d32[idx];
2950
- if (brushA > cur >>> 24) {
2951
- const next = (cRGB | blendedA << 24) >>> 0;
2952
- if (cur !== next) {
2953
- d32[idx] = next;
2954
- tileChanged = true;
2955
- }
2956
- }
2957
- }
2958
- }
2959
- if (tileChanged) changed = true;
2960
- });
2961
- });
2962
- return changed;
2963
- }
2964
- writePaintBinaryMaskStroke(color, brush, x0, y0, x1, y1) {
2965
- const alphaIsZero = color >>> 24 === 0;
2966
- if (alphaIsZero) return false;
2967
- const {
2968
- tileShift,
2969
- tileMask,
2970
- target
2971
- } = this.config;
2972
- const {
2973
- w: bW,
2974
- h: bH,
2975
- data: bD,
2976
- centerOffsetX,
2977
- centerOffsetY
2978
- } = brush;
2979
- const scratch = this.scratchBounds;
2980
- let changed = false;
2981
- forEachLinePoint(x0, y0, x1, y1, (px, py) => {
2982
- const topLeftX = Math.floor(px + centerOffsetX);
2983
- const topLeftY = Math.floor(py + centerOffsetY);
2984
- trimRectBounds(topLeftX, topLeftY, bW, bH, target.width, target.height, scratch);
2985
- if (scratch.w <= 0 || scratch.h <= 0) return;
2986
- this.eachTileInBounds(scratch, (tile, bX, bY, bW_t, bH_t) => {
2987
- const d32 = tile.data32;
2988
- let tileChanged = false;
2989
- for (let i = 0; i < bH_t; i++) {
2990
- const canvasY = bY + i;
2991
- const bOff = (canvasY - topLeftY) * bW;
2992
- const tOff = (canvasY & tileMask) << tileShift;
2993
- const dS = tOff + (bX & tileMask);
2994
- for (let j = 0; j < bW_t; j++) {
2995
- const canvasX = bX + j;
2996
- if (bD[bOff + (canvasX - topLeftX)]) {
2997
- const idx = dS + j;
2998
- if (d32[idx] !== color) {
2999
- d32[idx] = color;
3000
- tileChanged = true;
3001
- }
3002
- }
3003
- }
3004
- }
3005
- if (tileChanged) changed = true;
3006
- });
3007
- });
3008
- return changed;
3009
- }
3010
- writeRectStroke(color, brushWidth, brushHeight, x0, y0, x1, y1) {
3011
- const alphaIsZero = color >>> 24 === 0;
3012
- if (alphaIsZero) return false;
3013
- const config = this.config;
3014
- const tileShift = config.tileShift;
3015
- const tileMask = config.tileMask;
3016
- const target = config.target;
3017
- const scratch = this.scratchBounds;
3018
- const centerOffsetX = macro_halfAndFloor(brushWidth - 1);
3019
- const centerOffsetY = macro_halfAndFloor(brushHeight - 1);
3020
- let changed = false;
3021
- forEachLinePoint(x0, y0, x1, y1, (px, py) => {
3022
- const topLeftX = Math.floor(px + centerOffsetX);
3023
- const topLeftY = Math.floor(py + centerOffsetY);
3024
- trimRectBounds(topLeftX, topLeftY, brushWidth, brushHeight, target.width, target.height, scratch);
3025
- if (scratch.w <= 0 || scratch.h <= 0) return;
3026
- this.eachTileInBounds(scratch, (tile, bX, bY, bW_t, bH_t) => {
3027
- const d32 = tile.data32;
3028
- let tileChanged = false;
3029
- for (let i = 0; i < bH_t; i++) {
3030
- const canvasY = bY + i;
3031
- const tOff = (canvasY & tileMask) << tileShift;
3032
- const dS = tOff + (bX & tileMask);
3033
- for (let j = 0; j < bW_t; j++) {
3034
- const idx = dS + j;
3035
- if (d32[idx] !== color) {
3036
- d32[idx] = color;
3037
- tileChanged = true;
3038
- }
3039
- }
3040
- }
3041
- if (tileChanged) {
3042
- changed = true;
3043
- }
3044
- });
3045
- });
3046
- return changed;
3047
- }
3048
- clear() {
3049
- this.tilePool.releaseTiles(this.lookup);
3050
- }
3051
- };
3052
-
3053
- // src/PixelTile/PixelTile.ts
3054
- var PixelTile = class {
3055
- constructor(id, tx, ty, tileSize, tileArea) {
3056
- this.id = id;
3057
- this.tx = tx;
3058
- this.ty = ty;
3059
- this.width = this.height = tileSize;
3060
- this.data32 = new Uint32Array(tileArea);
3061
- const data8 = new Uint8ClampedArray(this.data32.buffer);
3062
- this.imageData = new ImageData(data8, tileSize, tileSize);
3063
- }
3064
- data32;
3065
- width;
3066
- height;
3067
- imageData;
3068
- };
3069
-
3070
- // src/PixelTile/PixelTilePool.ts
3071
- var PixelTilePool = class {
3072
- pool;
3073
- tileSize;
3074
- tileArea;
3075
- constructor(config) {
3076
- this.pool = [];
3077
- this.tileSize = config.tileSize;
3078
- this.tileArea = config.tileArea;
3079
- }
3080
- getTile(id, tx, ty) {
3081
- let tile = this.pool.pop();
3082
- if (tile) {
3083
- tile.id = id;
3084
- tile.tx = tx;
3085
- tile.ty = ty;
3086
- tile.data32.fill(0);
3087
- return tile;
3088
- }
3089
- return new PixelTile(id, tx, ty, this.tileSize, this.tileArea);
3090
- }
3091
- releaseTile(tile) {
3092
- this.pool.push(tile);
3093
- }
3094
- releaseTiles(tiles) {
3095
- let length = tiles.length;
3096
- for (let i = 0; i < length; i++) {
3097
- let tile = tiles[i];
3098
- if (tile) {
3099
- this.pool.push(tile);
3100
- }
3101
- }
3102
- tiles.length = 0;
3103
- }
3104
- };
3105
-
3106
- // src/History/PixelWriter.ts
3107
- var PixelWriter = class {
3108
- historyManager;
3109
- accumulator;
3110
- historyActionFactory;
3111
- config;
3112
- pixelTilePool;
3113
- paintBuffer;
3114
- mutator;
3115
- blendPixelDataOpts = {
3116
- alpha: 255,
3117
- blendFn: sourceOverPerfect,
3118
- x: 0,
3119
- y: 0,
3120
- w: 0,
3121
- h: 0
3122
- };
3123
- _inProgress = false;
3124
- constructor(target, mutatorFactory, {
3125
- tileSize = 256,
3126
- maxHistorySteps = 50,
3127
- historyManager = new HistoryManager(maxHistorySteps),
3128
- historyActionFactory = makeHistoryAction,
3129
- pixelTilePool,
3130
- accumulator
3131
- } = {}) {
3132
- this.config = new PixelEngineConfig(tileSize, target);
3133
- this.historyManager = historyManager;
3134
- this.pixelTilePool = pixelTilePool ?? new PixelTilePool(this.config);
3135
- this.accumulator = accumulator ?? new PixelAccumulator(this.config, this.pixelTilePool);
3136
- this.historyActionFactory = historyActionFactory;
3137
- this.mutator = mutatorFactory(this);
3138
- this.paintBuffer = new PaintBuffer(this.config, this.pixelTilePool);
3139
- }
3140
- /**
3141
- * Executes `transaction` and commits the resulting pixel changes as a single
3142
- * undoable history action.
3143
- *
3144
- * - If `transaction` throws, all accumulated changes are rolled back and the error
3145
- * is re-thrown. No action is committed.
3146
- * - If `transaction` completes without modifying any pixels, no action is committed.
3147
- * - `withHistory` is not re-entrant. Calling it again from inside `transaction` will
3148
- * throw immediately to prevent silent data loss from a nested extractPatch.
3149
- *
3150
- * @param transaction Callback to be executed inside the transaction.
3151
- * @param after Called after both undo and redo — use for generic change notifications.
3152
- * @param afterUndo Called after undo only — use for dimension or state changes specific to undo.
3153
- * @param afterRedo Called after redo only.
3154
- */
3155
- withHistory(transaction, after, afterUndo, afterRedo) {
3156
- if (this._inProgress) {
3157
- throw new Error("withHistory is not re-entrant \u2014 commit or rollback the current operation first");
3158
- }
3159
- this._inProgress = true;
3160
- try {
3161
- transaction(this.mutator);
3162
- } catch (e) {
3163
- this.accumulator.rollbackAfterError();
3164
- throw e;
3165
- } finally {
3166
- this._inProgress = false;
3167
- }
3168
- if (this.accumulator.beforeTiles.length === 0) return;
3169
- const patch = this.accumulator.extractPatch();
3170
- const action = this.historyActionFactory(this, patch, after, afterUndo, afterRedo);
3171
- this.historyManager.commit(action);
3172
- }
3173
- resize(newWidth, newHeight, offsetX = 0, offsetY = 0, after, afterUndo, afterRedo, resizeImageDataFn = resizeImageData) {
3174
- if (this._inProgress) {
3175
- throw new Error("Cannot resize inside a withHistory callback");
3176
- }
3177
- if (this.accumulator.beforeTiles.length > 0) {
3178
- throw new Error("Cannot resize with an open accumulator \u2014 commit or rollback first");
3179
- }
3180
- const config = this.config;
3181
- const target = config.target;
3182
- const beforeImageData = target.imageData;
3183
- const afterImageData = resizeImageDataFn(beforeImageData, newWidth, newHeight, offsetX, offsetY);
3184
- target.set(afterImageData);
3185
- this.historyManager.commit({
3186
- undo: () => {
3187
- target.set(beforeImageData);
3188
- afterUndo?.(beforeImageData);
3189
- after?.(beforeImageData);
3190
- },
3191
- redo: () => {
3192
- target.set(afterImageData);
3193
- afterRedo?.(afterImageData);
3194
- after?.(afterImageData);
3195
- }
3196
- });
3197
- }
3198
- commitPaintBuffer(alpha = 255, blendFn = sourceOverPerfect, blendPixelDataFn = blendPixelData) {
3199
- const paintBuffer = this.paintBuffer;
3200
- const tileShift = paintBuffer.config.tileShift;
3201
- const lookup = paintBuffer.lookup;
3202
- const opts = this.blendPixelDataOpts;
3203
- opts.alpha = alpha;
3204
- opts.blendFn = blendFn;
3205
- for (let i = 0; i < lookup.length; i++) {
3206
- const tile = lookup[i];
3207
- if (tile) {
3208
- const didChange = this.accumulator.storeTileBeforeState(tile.id, tile.tx, tile.ty);
3209
- const dx = tile.tx << tileShift;
3210
- const dy = tile.ty << tileShift;
3211
- opts.x = dx;
3212
- opts.y = dy;
3213
- opts.w = tile.width;
3214
- opts.h = tile.height;
3215
- didChange(blendPixelDataFn(this.config.target, tile, opts));
3216
- }
3217
- }
3218
- paintBuffer.clear();
3219
- }
3487
+ // src/History/PixelMutator/mutatorFill.ts
3488
+ var defaults16 = {
3489
+ fillPixelData
3220
3490
  };
3221
-
3222
- // src/PixelData/applyAlphaMaskToPixelData.ts
3223
- function applyAlphaMaskToPixelData(dst, mask, opts = {}) {
3491
+ var mutatorFill = ((writer, deps = defaults16) => {
3224
3492
  const {
3225
- x: targetX = 0,
3226
- y: targetY = 0,
3227
- w: width = dst.width,
3228
- h: height = dst.height,
3229
- alpha: globalAlpha = 255,
3230
- mx = 0,
3231
- my = 0,
3232
- invertMask = false
3233
- } = opts;
3234
- if (globalAlpha === 0) return false;
3235
- let x = targetX;
3236
- let y = targetY;
3237
- let w = width;
3238
- let h = height;
3239
- if (x < 0) {
3240
- w += x;
3241
- x = 0;
3242
- }
3243
- if (y < 0) {
3244
- h += y;
3245
- y = 0;
3246
- }
3247
- w = Math.min(w, dst.width - x);
3248
- h = Math.min(h, dst.height - y);
3249
- if (w <= 0) return false;
3250
- if (h <= 0) return false;
3251
- const mPitch = mask.w;
3252
- if (mPitch <= 0) return false;
3253
- const startX = mx + (x - targetX);
3254
- const startY = my + (y - targetY);
3255
- const sX0 = Math.max(0, startX);
3256
- const sY0 = Math.max(0, startY);
3257
- const sX1 = Math.min(mPitch, startX + w);
3258
- const sY1 = Math.min(mask.h, startY + h);
3259
- const finalW = sX1 - sX0;
3260
- const finalH = sY1 - sY0;
3261
- if (finalW <= 0) return false;
3262
- if (finalH <= 0) return false;
3263
- const xShift = sX0 - startX;
3264
- const yShift = sY0 - startY;
3265
- const dst32 = dst.data32;
3266
- const dw = dst.width;
3267
- const dStride = dw - finalW;
3268
- const mStride = mPitch - finalW;
3269
- const maskData = mask.data;
3270
- let dIdx = (y + yShift) * dw + (x + xShift);
3271
- let mIdx = sY0 * mPitch + sX0;
3272
- let didChange = false;
3273
- for (let iy = 0; iy < h; iy++) {
3274
- for (let ix = 0; ix < w; ix++) {
3275
- const mVal = maskData[mIdx];
3276
- const effectiveM = invertMask ? 255 - mVal : mVal;
3277
- let weight = 0;
3278
- if (effectiveM === 0) {
3279
- weight = 0;
3280
- } else if (effectiveM === 255) {
3281
- weight = globalAlpha;
3282
- } else if (globalAlpha === 255) {
3283
- weight = effectiveM;
3284
- } else {
3285
- weight = effectiveM * globalAlpha + 128 >> 8;
3286
- }
3287
- if (weight === 0) {
3288
- dst32[dIdx] = (dst32[dIdx] & 16777215) >>> 0;
3289
- didChange = true;
3290
- } else if (weight !== 255) {
3291
- const d = dst32[dIdx];
3292
- const da = d >>> 24;
3293
- if (da !== 0) {
3294
- const finalAlpha = da === 255 ? weight : da * weight + 128 >> 8;
3295
- const current = dst32[dIdx];
3296
- const next = (d & 16777215 | finalAlpha << 24) >>> 0;
3297
- if (current !== next) {
3298
- dst32[dIdx] = next;
3299
- didChange = true;
3300
- }
3301
- }
3302
- }
3303
- dIdx++;
3304
- mIdx++;
3493
+ fillPixelData: fillPixelData2 = defaults16.fillPixelData
3494
+ } = deps;
3495
+ return {
3496
+ fill(color, x = 0, y = 0, w = writer.config.target.width, h = writer.config.target.height) {
3497
+ const target = writer.config.target;
3498
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3499
+ return didChange(fillPixelData2(target, color, x, y, w, h));
3305
3500
  }
3306
- dIdx += dStride;
3307
- mIdx += mStride;
3308
- }
3309
- return didChange;
3310
- }
3311
-
3312
- // src/History/PixelMutator/mutatorApplyAlphaMask.ts
3313
- var defaults11 = {
3314
- applyAlphaMaskToPixelData
3315
- };
3316
- var mutatorApplyAlphaMask = ((writer, deps = defaults11) => {
3501
+ };
3502
+ });
3503
+ var mutatorFillRect = ((writer, deps = defaults16) => {
3317
3504
  const {
3318
- applyAlphaMaskToPixelData: applyAlphaMaskToPixelData2 = defaults11.applyAlphaMaskToPixelData
3505
+ fillPixelData: fillPixelData2 = defaults16.fillPixelData
3319
3506
  } = deps;
3320
3507
  return {
3321
- applyAlphaMask(mask, opts = {}) {
3322
- let target = writer.config.target;
3323
- const {
3324
- x = 0,
3325
- y = 0,
3326
- w = target.width,
3327
- h = target.height
3328
- } = opts;
3329
- const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3330
- return didChange(applyAlphaMaskToPixelData2(target, mask, opts));
3508
+ fillRect(color, rect) {
3509
+ const target = writer.config.target;
3510
+ const didChange = writer.accumulator.storeRegionBeforeState(rect.x, rect.y, rect.w, rect.h);
3511
+ return didChange(fillPixelData2(target, color, rect.x, rect.y, rect.w, rect.h));
3331
3512
  }
3332
3513
  };
3333
3514
  });
3334
3515
 
3335
- // src/PixelData/applyBinaryMaskToPixelData.ts
3336
- function applyBinaryMaskToPixelData(dst, mask, opts = {}) {
3516
+ // src/PixelData/fillPixelDataBinaryMask.ts
3517
+ var SCRATCH_RECT2 = makeClippedRect();
3518
+ function fillPixelDataBinaryMask(target, color, mask, x = 0, y = 0) {
3519
+ const maskW = mask.w;
3520
+ const maskH = mask.h;
3521
+ const clip = resolveRectClipping(x, y, maskW, maskH, target.width, target.height, SCRATCH_RECT2);
3522
+ if (!clip.inBounds) return false;
3337
3523
  const {
3338
- x: targetX = 0,
3339
- y: targetY = 0,
3340
- w: width = dst.width,
3341
- h: height = dst.height,
3342
- alpha: globalAlpha = 255,
3343
- mx = 0,
3344
- my = 0,
3345
- invertMask = false
3346
- } = opts;
3347
- if (globalAlpha === 0) return false;
3348
- let x = targetX;
3349
- let y = targetY;
3350
- let w = width;
3351
- let h = height;
3352
- if (x < 0) {
3353
- w += x;
3354
- x = 0;
3355
- }
3356
- if (y < 0) {
3357
- h += y;
3358
- y = 0;
3359
- }
3360
- w = Math.min(w, dst.width - x);
3361
- h = Math.min(h, dst.height - y);
3362
- if (w <= 0 || h <= 0) return false;
3363
- const mPitch = mask.w;
3364
- if (mPitch <= 0) return false;
3365
- const startX = mx + (x - targetX);
3366
- const startY = my + (y - targetY);
3367
- const sX0 = Math.max(0, startX);
3368
- const sY0 = Math.max(0, startY);
3369
- const sX1 = Math.min(mPitch, startX + w);
3370
- const sY1 = Math.min(mask.h, startY + h);
3371
- const finalW = sX1 - sX0;
3372
- const finalH = sY1 - sY0;
3373
- if (finalW <= 0 || finalH <= 0) {
3374
- return false;
3375
- }
3376
- const xShift = sX0 - startX;
3377
- const yShift = sY0 - startY;
3378
- const dst32 = dst.data32;
3379
- const dw = dst.width;
3380
- const dStride = dw - finalW;
3381
- const mStride = mPitch - finalW;
3524
+ x: finalX,
3525
+ y: finalY,
3526
+ w: actualW,
3527
+ h: actualH
3528
+ } = clip;
3382
3529
  const maskData = mask.data;
3383
- let dIdx = (y + yShift) * dw + (x + xShift);
3384
- let mIdx = sY0 * mPitch + sX0;
3385
- let didChange = false;
3386
- for (let iy = 0; iy < finalH; iy++) {
3387
- for (let ix = 0; ix < finalW; ix++) {
3388
- const mVal = maskData[mIdx];
3389
- const isMaskedOut = invertMask ? mVal !== 0 : mVal === 0;
3390
- if (isMaskedOut) {
3391
- const current = dst32[dIdx];
3392
- const next = (current & 16777215) >>> 0;
3393
- if (current !== next) {
3394
- dst32[dIdx] = next;
3395
- didChange = true;
3396
- }
3397
- } else if (globalAlpha !== 255) {
3398
- const d = dst32[dIdx];
3399
- const da = d >>> 24;
3400
- if (da !== 0) {
3401
- const finalAlpha = da === 255 ? globalAlpha : da * globalAlpha + 128 >> 8;
3402
- const next = (d & 16777215 | finalAlpha << 24) >>> 0;
3403
- if (d !== next) {
3404
- dst32[dIdx] = next;
3405
- didChange = true;
3406
- }
3530
+ const dst32 = target.data32;
3531
+ const dw = target.width;
3532
+ let hasChanged = false;
3533
+ for (let iy = 0; iy < actualH; iy++) {
3534
+ const currentY = finalY + iy;
3535
+ const maskY = currentY - y;
3536
+ const maskOffset = maskY * maskW;
3537
+ const dstRowOffset = currentY * dw;
3538
+ for (let ix = 0; ix < actualW; ix++) {
3539
+ const currentX = finalX + ix;
3540
+ const maskX = currentX - x;
3541
+ const maskIndex = maskOffset + maskX;
3542
+ if (maskData[maskIndex]) {
3543
+ const current = dst32[dstRowOffset + currentX];
3544
+ if (current !== color) {
3545
+ dst32[dstRowOffset + currentX] = color;
3546
+ hasChanged = true;
3407
3547
  }
3408
3548
  }
3409
- dIdx++;
3410
- mIdx++;
3411
3549
  }
3412
- dIdx += dStride;
3413
- mIdx += mStride;
3414
3550
  }
3415
- return didChange;
3551
+ return hasChanged;
3416
3552
  }
3417
3553
 
3418
- // src/History/PixelMutator/mutatorApplyBinaryMask.ts
3419
- var defaults12 = {
3420
- applyBinaryMaskToPixelData
3554
+ // src/History/PixelMutator/mutatorFillBinaryMask.ts
3555
+ var defaults17 = {
3556
+ fillPixelDataBinaryMask
3421
3557
  };
3422
- var mutatorApplyBinaryMask = ((writer, deps = defaults12) => {
3558
+ var mutatorFillBinaryMask = ((writer, deps = defaults17) => {
3423
3559
  const {
3424
- applyBinaryMaskToPixelData: applyBinaryMaskToPixelData2 = defaults12.applyBinaryMaskToPixelData
3560
+ fillPixelDataBinaryMask: fillPixelDataBinaryMask2 = defaults17.fillPixelDataBinaryMask
3425
3561
  } = deps;
3426
3562
  return {
3427
- applyBinaryMask(mask, opts = {}) {
3428
- let target = writer.config.target;
3429
- const {
3430
- x = 0,
3431
- y = 0,
3432
- w = target.width,
3433
- h = target.height
3434
- } = opts;
3435
- const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3436
- return didChange(applyBinaryMaskToPixelData2(target, mask, opts));
3563
+ fillBinaryMask(color, mask, x = 0, y = 0) {
3564
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, mask.w, mask.h);
3565
+ return didChange(fillPixelDataBinaryMask2(writer.config.target, color, mask, x, y));
3437
3566
  }
3438
3567
  };
3439
3568
  });
3440
3569
 
3441
- // src/PixelData/blendColorPixelDataAlphaMask.ts
3442
- function blendColorPixelDataAlphaMask(dst, color, mask, opts = {}) {
3443
- const targetX = opts.x ?? 0;
3444
- const targetY = opts.y ?? 0;
3445
- const w = opts.w ?? mask.w;
3446
- const h = opts.h ?? mask.h;
3447
- const globalAlpha = opts.alpha ?? 255;
3448
- const blendFn = opts.blendFn ?? sourceOverPerfect;
3449
- const mx = opts.mx ?? 0;
3450
- const my = opts.my ?? 0;
3451
- const invertMask = opts.invertMask ?? false;
3452
- if (globalAlpha === 0) return false;
3453
- const baseSrcAlpha = color >>> 24;
3454
- const isOverwrite = blendFn.isOverwrite || false;
3455
- if (baseSrcAlpha === 0 && !isOverwrite) return false;
3456
- let x = targetX;
3457
- let y = targetY;
3458
- let actualW = w;
3459
- let actualH = h;
3460
- if (x < 0) {
3461
- actualW += x;
3462
- x = 0;
3463
- }
3464
- if (y < 0) {
3465
- actualH += y;
3466
- y = 0;
3467
- }
3468
- actualW = Math.min(actualW, dst.width - x);
3469
- actualH = Math.min(actualH, dst.height - y);
3470
- if (actualW <= 0 || actualH <= 0) return false;
3471
- const dx = x - targetX | 0;
3472
- const dy = y - targetY | 0;
3473
- const dst32 = dst.data32;
3474
- const dw = dst.width;
3475
- const mPitch = mask.w;
3476
- const maskData = mask.data;
3477
- let dIdx = y * dw + x | 0;
3478
- let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
3479
- const dStride = dw - actualW | 0;
3480
- const mStride = mPitch - actualW | 0;
3481
- const isOpaque = globalAlpha === 255;
3482
- const colorRGB = color & 16777215;
3483
- let didChange = false;
3484
- for (let iy = 0; iy < actualH; iy++) {
3485
- for (let ix = 0; ix < actualW; ix++) {
3486
- const mVal = maskData[mIdx];
3487
- const effM = invertMask ? 255 - mVal : mVal;
3488
- if (effM === 0) {
3489
- dIdx++;
3490
- mIdx++;
3491
- continue;
3492
- }
3493
- let weight = globalAlpha;
3494
- if (isOpaque) {
3495
- weight = effM;
3496
- } else if (effM !== 255) {
3497
- weight = effM * globalAlpha + 128 >> 8;
3498
- }
3499
- if (weight === 0) {
3500
- dIdx++;
3501
- mIdx++;
3502
- continue;
3503
- }
3504
- let finalCol = color;
3505
- if (weight < 255) {
3506
- const a = baseSrcAlpha * weight + 128 >> 8;
3507
- if (a === 0 && !isOverwrite) {
3508
- dIdx++;
3509
- mIdx++;
3510
- continue;
3570
+ // src/PixelData/invertPixelData.ts
3571
+ var SCRATCH_RECT3 = makeClippedRect();
3572
+ function invertPixelData(target, opts) {
3573
+ const mask = opts?.mask;
3574
+ const targetX = opts?.x ?? 0;
3575
+ const targetY = opts?.y ?? 0;
3576
+ const mx = opts?.mx ?? 0;
3577
+ const my = opts?.my ?? 0;
3578
+ const width = opts?.w ?? target.width;
3579
+ const height = opts?.h ?? target.height;
3580
+ const invertMask = opts?.invertMask ?? false;
3581
+ const clip = resolveRectClipping(targetX, targetY, width, height, target.width, target.height, SCRATCH_RECT3);
3582
+ if (!clip.inBounds) return false;
3583
+ const {
3584
+ x,
3585
+ y,
3586
+ w: actualW,
3587
+ h: actualH
3588
+ } = clip;
3589
+ const dst32 = target.data32;
3590
+ const dw = target.width;
3591
+ const mPitch = mask?.w ?? width;
3592
+ const dx = x - targetX;
3593
+ const dy = y - targetY;
3594
+ let dIdx = y * dw + x;
3595
+ let mIdx = (my + dy) * mPitch + (mx + dx);
3596
+ const dStride = dw - actualW;
3597
+ const mStride = mPitch - actualW;
3598
+ if (mask) {
3599
+ const maskData = mask.data;
3600
+ for (let iy = 0; iy < actualH; iy++) {
3601
+ for (let ix = 0; ix < actualW; ix++) {
3602
+ const mVal = maskData[mIdx];
3603
+ const isHit = invertMask ? mVal === 0 : mVal === 1;
3604
+ if (isHit) {
3605
+ dst32[dIdx] = dst32[dIdx] ^ 16777215;
3511
3606
  }
3512
- finalCol = (colorRGB | a << 24) >>> 0;
3513
- }
3514
- const current = dst32[dIdx];
3515
- const next = blendFn(finalCol, current);
3516
- if (current !== next) {
3517
- dst32[dIdx] = next;
3518
- didChange = true;
3519
- }
3520
- dIdx++;
3521
- mIdx++;
3522
- }
3523
- dIdx += dStride;
3524
- mIdx += mStride;
3525
- }
3526
- return didChange;
3527
- }
3528
-
3529
- // src/PixelData/blendColorPixelDataBinaryMask.ts
3530
- function blendColorPixelDataBinaryMask(dst, color, mask, opts = {}) {
3531
- const targetX = opts.x ?? 0;
3532
- const targetY = opts.y ?? 0;
3533
- let w = opts.w ?? mask.w;
3534
- let h = opts.h ?? mask.h;
3535
- const globalAlpha = opts.alpha ?? 255;
3536
- const blendFn = opts.blendFn ?? sourceOverPerfect;
3537
- const mx = opts.mx ?? 0;
3538
- const my = opts.my ?? 0;
3539
- const invertMask = opts.invertMask ?? false;
3540
- if (globalAlpha === 0) return false;
3541
- const baseSrcAlpha = color >>> 24;
3542
- const isOverwrite = blendFn.isOverwrite || false;
3543
- if (baseSrcAlpha === 0 && !isOverwrite) return false;
3544
- let x = targetX;
3545
- let y = targetY;
3546
- if (x < 0) {
3547
- w += x;
3548
- x = 0;
3549
- }
3550
- if (y < 0) {
3551
- h += y;
3552
- y = 0;
3553
- }
3554
- const actualW = Math.min(w, dst.width - x);
3555
- const actualH = Math.min(h, dst.height - y);
3556
- if (actualW <= 0 || actualH <= 0) return false;
3557
- let baseColorWithGlobalAlpha = color;
3558
- if (globalAlpha < 255) {
3559
- const a = baseSrcAlpha * globalAlpha + 128 >> 8;
3560
- if (a === 0 && !isOverwrite) return false;
3561
- baseColorWithGlobalAlpha = (color & 16777215 | a << 24) >>> 0;
3562
- }
3563
- const dx = x - targetX | 0;
3564
- const dy = y - targetY | 0;
3565
- const dst32 = dst.data32;
3566
- const dw = dst.width;
3567
- const mPitch = mask.w;
3568
- const maskData = mask.data;
3569
- let dIdx = y * dw + x | 0;
3570
- let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
3571
- const dStride = dw - actualW | 0;
3572
- const mStride = mPitch - actualW | 0;
3573
- const skipVal = invertMask ? 1 : 0;
3574
- let didChange = false;
3575
- for (let iy = 0; iy < actualH; iy++) {
3576
- for (let ix = 0; ix < actualW; ix++) {
3577
- if (maskData[mIdx] === skipVal) {
3578
3607
  dIdx++;
3579
3608
  mIdx++;
3580
- continue;
3581
3609
  }
3582
- const current = dst32[dIdx];
3583
- const next = blendFn(baseColorWithGlobalAlpha, current);
3584
- if (current !== next) {
3585
- dst32[dIdx] = next;
3586
- didChange = true;
3610
+ dIdx += dStride;
3611
+ mIdx += mStride;
3612
+ }
3613
+ } else {
3614
+ for (let iy = 0; iy < actualH; iy++) {
3615
+ for (let ix = 0; ix < actualW; ix++) {
3616
+ dst32[dIdx] = dst32[dIdx] ^ 16777215;
3617
+ dIdx++;
3587
3618
  }
3588
- dIdx++;
3589
- mIdx++;
3619
+ dIdx += dStride;
3590
3620
  }
3591
- dIdx += dStride;
3592
- mIdx += mStride;
3593
3621
  }
3594
- return didChange;
3622
+ return true;
3595
3623
  }
3596
3624
 
3597
- // src/History/PixelMutator/mutatorBlendPaintMask.ts
3598
- var defaults13 = {
3599
- blendColorPixelDataAlphaMask,
3600
- blendColorPixelDataBinaryMask
3625
+ // src/History/PixelMutator/mutatorInvert.ts
3626
+ var defaults18 = {
3627
+ invertPixelData
3601
3628
  };
3602
- var mutatorBlendPaintMask = ((writer, deps = defaults13) => {
3629
+ var mutatorInvert = ((writer, deps = defaults18) => {
3603
3630
  const {
3604
- blendColorPixelDataBinaryMask: blendColorPixelDataBinaryMask2 = defaults13.blendColorPixelDataBinaryMask,
3605
- blendColorPixelDataAlphaMask: blendColorPixelDataAlphaMask2 = defaults13.blendColorPixelDataAlphaMask
3631
+ invertPixelData: invertPixelData2 = defaults18.invertPixelData
3606
3632
  } = deps;
3607
- const OPTS = {
3608
- x: 0,
3609
- y: 0,
3610
- blendFn: sourceOverPerfect,
3611
- alpha: 255
3612
- };
3613
3633
  return {
3614
- blendColorPaintMask(color, mask, x, y, alpha = 255, blendFn = sourceOverPerfect) {
3615
- const tx = x + mask.centerOffsetX;
3616
- const ty = y + mask.centerOffsetY;
3617
- const didChange = writer.accumulator.storeRegionBeforeState(tx, ty, mask.w, mask.h);
3618
- OPTS.x = tx;
3619
- OPTS.y = ty;
3620
- OPTS.alpha = alpha;
3621
- OPTS.blendFn = blendFn;
3622
- if (mask.type === 1 /* BINARY */) {
3623
- return didChange(blendColorPixelDataBinaryMask2(writer.config.target, color, mask, OPTS));
3624
- } else {
3625
- return didChange(blendColorPixelDataAlphaMask2(writer.config.target, color, mask, OPTS));
3626
- }
3634
+ invert(opts) {
3635
+ const target = writer.config.target;
3636
+ const x = opts?.x ?? 0;
3637
+ const y = opts?.y ?? 0;
3638
+ const w = opts?.w ?? target.width;
3639
+ const h = opts?.h ?? target.height;
3640
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3641
+ return didChange(invertPixelData2(target, opts));
3627
3642
  }
3628
3643
  };
3629
3644
  });
3630
3645
 
3646
+ // src/History/PixelMutator.ts
3647
+ function makeFullPixelMutator(writer) {
3648
+ return {
3649
+ // @sort
3650
+ ...mutatorApplyAlphaMask(writer),
3651
+ ...mutatorApplyBinaryMask(writer),
3652
+ ...mutatorApplyMask(writer),
3653
+ ...mutatorBlendAlphaMask(writer),
3654
+ ...mutatorBlendBinaryMask(writer),
3655
+ ...mutatorBlendColor(writer),
3656
+ ...mutatorBlendColorPaintAlphaMask(writer),
3657
+ ...mutatorBlendColorPaintBinaryMask(writer),
3658
+ ...mutatorBlendColorPaintMask(writer),
3659
+ ...mutatorBlendColorPaintRect(writer),
3660
+ ...mutatorBlendMask(writer),
3661
+ ...mutatorBlendPixel(writer),
3662
+ ...mutatorBlendPixelData(writer),
3663
+ ...mutatorClear(writer),
3664
+ ...mutatorFill(writer),
3665
+ ...mutatorFillBinaryMask(writer),
3666
+ ...mutatorFillRect(writer),
3667
+ ...mutatorInvert(writer)
3668
+ };
3669
+ }
3670
+
3671
+ // src/ImageData/ImageDataLike.ts
3672
+ function makeImageDataLike(width, height, data) {
3673
+ const size = width * height * 4;
3674
+ const buffer = data ? new Uint8ClampedArray(data.buffer, data.byteOffset, size) : new Uint8ClampedArray(size);
3675
+ return {
3676
+ width,
3677
+ height,
3678
+ data: buffer
3679
+ };
3680
+ }
3681
+
3682
+ // src/ImageData/ReusableImageData.ts
3683
+ function makeReusableImageData() {
3684
+ let imageData = null;
3685
+ return function getReusableImageData(width, height) {
3686
+ if (imageData === null || imageData.width !== width || imageData.height !== height) {
3687
+ imageData = new ImageData(width, height);
3688
+ } else {
3689
+ imageData.data.fill(0);
3690
+ }
3691
+ return imageData;
3692
+ };
3693
+ }
3694
+
3631
3695
  // src/ImageData/copyImageData.ts
3632
3696
  function copyImageData({
3633
3697
  data,
@@ -3648,17 +3712,6 @@ function copyImageDataLike({
3648
3712
  };
3649
3713
  }
3650
3714
 
3651
- // src/ImageData/ImageDataLike.ts
3652
- function makeImageDataLike(width, height, data) {
3653
- const size = width * height * 4;
3654
- const buffer = data ? new Uint8ClampedArray(data.buffer, data.byteOffset, size) : new Uint8ClampedArray(size);
3655
- return {
3656
- width,
3657
- height,
3658
- data: buffer
3659
- };
3660
- }
3661
-
3662
3715
  // src/ImageData/imageDataToAlphaMaskBuffer.ts
3663
3716
  function imageDataToAlphaMaskBuffer(imageData) {
3664
3717
  const {
@@ -3749,19 +3802,6 @@ function resampleImageData(source, factor) {
3749
3802
  return new ImageData(uint8ClampedArray, width, height);
3750
3803
  }
3751
3804
 
3752
- // src/ImageData/ReusableImageData.ts
3753
- function makeReusableImageData() {
3754
- let imageData = null;
3755
- return function getReusableImageData(width, height) {
3756
- if (imageData === null || imageData.width !== width || imageData.height !== height) {
3757
- imageData = new ImageData(width, height);
3758
- } else {
3759
- imageData.data.fill(0);
3760
- }
3761
- return imageData;
3762
- };
3763
- }
3764
-
3765
3805
  // src/ImageData/serialization.ts
3766
3806
  function base64EncodeArrayBuffer(buffer) {
3767
3807
  const uint8 = new Uint8Array(buffer);
@@ -3916,18 +3956,6 @@ function writeImageDataBuffer(target, data, _x, _y, _w, _h) {
3916
3956
  }
3917
3957
  }
3918
3958
 
3919
- // src/IndexedImage/getIndexedImageColorCounts.ts
3920
- function getIndexedImageColorCounts(indexedImage) {
3921
- const data = indexedImage.data;
3922
- const palette = indexedImage.palette;
3923
- const frequencies = new Int32Array(palette.length);
3924
- for (let i = 0; i < data.length; i++) {
3925
- const colorIndex = data[i];
3926
- frequencies[colorIndex]++;
3927
- }
3928
- return frequencies;
3929
- }
3930
-
3931
3959
  // src/IndexedImage/IndexedImage.ts
3932
3960
  var IndexedImage = class _IndexedImage {
3933
3961
  /** The width of the image in pixels. */
@@ -4006,6 +4034,18 @@ var IndexedImage = class _IndexedImage {
4006
4034
  }
4007
4035
  };
4008
4036
 
4037
+ // src/IndexedImage/getIndexedImageColorCounts.ts
4038
+ function getIndexedImageColorCounts(indexedImage) {
4039
+ const data = indexedImage.data;
4040
+ const palette = indexedImage.palette;
4041
+ const frequencies = new Int32Array(palette.length);
4042
+ for (let i = 0; i < data.length; i++) {
4043
+ const colorIndex = data[i];
4044
+ frequencies[colorIndex]++;
4045
+ }
4046
+ return frequencies;
4047
+ }
4048
+
4009
4049
  // src/IndexedImage/indexedImageToAverageColor.ts
4010
4050
  function indexedImageToAverageColor(indexedImage, includeTransparent = false) {
4011
4051
  const {
@@ -4164,16 +4204,14 @@ function makeBinaryMask(w, h, data) {
4164
4204
  }
4165
4205
 
4166
4206
  // src/Mask/applyBinaryMaskToAlphaMask.ts
4167
- function applyBinaryMaskToAlphaMask(alphaMaskDst, binaryMaskSrc, opts = {}) {
4168
- const {
4169
- x: targetX = 0,
4170
- y: targetY = 0,
4171
- w: reqWidth = 0,
4172
- h: reqHeight = 0,
4173
- mx = 0,
4174
- my = 0,
4175
- invertMask = false
4176
- } = opts;
4207
+ function applyBinaryMaskToAlphaMask(alphaMaskDst, binaryMaskSrc, opts) {
4208
+ const targetX = opts?.x ?? 0;
4209
+ const targetY = opts?.y ?? 0;
4210
+ const reqWidth = opts?.w ?? 0;
4211
+ const reqHeight = opts?.h ?? 0;
4212
+ const mx = opts?.mx ?? 0;
4213
+ const my = opts?.my ?? 0;
4214
+ const invertMask = opts?.invertMask ?? false;
4177
4215
  const dstWidth = alphaMaskDst.w;
4178
4216
  if (dstWidth <= 0) return;
4179
4217
  if (binaryMaskSrc.data.length === 0) return;
@@ -4586,14 +4624,156 @@ function pushPiece(dest, r, x, y, w, h) {
4586
4624
  for (let row = 0; row < h; row++) {
4587
4625
  data.set(r.data.subarray((ly + row) * r.w + lx, (ly + row) * r.w + lx + w), row * w);
4588
4626
  }
4589
- dest.push({
4590
- x,
4591
- y,
4592
- w,
4593
- h,
4627
+ dest.push({
4628
+ x,
4629
+ y,
4630
+ w,
4631
+ h,
4632
+ data,
4633
+ type: 1 /* BINARY */
4634
+ });
4635
+ }
4636
+
4637
+ // src/Paint/PaintBufferCanvasRenderer.ts
4638
+ function makePaintBufferCanvasRenderer(paintBuffer, offscreenCanvasClass = OffscreenCanvas) {
4639
+ const config = paintBuffer.config;
4640
+ const tileSize = config.tileSize;
4641
+ const tileShift = config.tileShift;
4642
+ const lookup = paintBuffer.lookup;
4643
+ const canvas = new offscreenCanvasClass(tileSize, tileSize);
4644
+ const ctx = canvas.getContext("2d");
4645
+ if (!ctx) throw new Error(CANVAS_CTX_FAILED);
4646
+ ctx.imageSmoothingEnabled = false;
4647
+ return function drawPaintBuffer(targetCtx, alpha = 255, compOperation = "source-over") {
4648
+ targetCtx.globalAlpha = alpha / 255;
4649
+ targetCtx.globalCompositeOperation = compOperation;
4650
+ for (let i = 0; i < lookup.length; i++) {
4651
+ const tile = lookup[i];
4652
+ if (tile) {
4653
+ const dx = tile.tx << tileShift;
4654
+ const dy = tile.ty << tileShift;
4655
+ ctx.putImageData(tile.imageData, 0, 0);
4656
+ targetCtx.drawImage(canvas, dx, dy);
4657
+ }
4658
+ }
4659
+ targetCtx.globalAlpha = 1;
4660
+ targetCtx.globalCompositeOperation = "source-over";
4661
+ };
4662
+ }
4663
+
4664
+ // src/Paint/makeCirclePaintAlphaMask.ts
4665
+ function makeCirclePaintAlphaMask(size, fallOff = (d) => d) {
4666
+ const area = size * size;
4667
+ const data = new Uint8Array(area);
4668
+ const radius = size / 2;
4669
+ const invR = 1 / radius;
4670
+ const centerOffset = -Math.ceil(radius - 0.5);
4671
+ for (let y = 0; y < size; y++) {
4672
+ const rowOffset = y * size;
4673
+ const dy = y - radius + 0.5;
4674
+ const dy2 = dy * dy;
4675
+ for (let x = 0; x < size; x++) {
4676
+ const dx = x - radius + 0.5;
4677
+ const distSqr = dx * dx + dy2;
4678
+ if (distSqr <= radius * radius) {
4679
+ const dist = Math.sqrt(distSqr) * invR;
4680
+ const strength = fallOff(1 - dist);
4681
+ if (strength > 0) {
4682
+ const intensity = strength * 255 | 0;
4683
+ data[rowOffset + x] = Math.max(0, Math.min(255, intensity));
4684
+ }
4685
+ }
4686
+ }
4687
+ }
4688
+ return {
4689
+ type: 0 /* ALPHA */,
4690
+ data,
4691
+ w: size,
4692
+ h: size,
4693
+ centerOffsetX: centerOffset,
4694
+ centerOffsetY: centerOffset
4695
+ };
4696
+ }
4697
+
4698
+ // src/Paint/makeCirclePaintBinaryMask.ts
4699
+ function makeCirclePaintBinaryMask(size) {
4700
+ const area = size * size;
4701
+ const data = new Uint8Array(area);
4702
+ const radius = size / 2;
4703
+ const centerOffset = -Math.ceil(radius - 0.5);
4704
+ for (let y = 0; y < size; y++) {
4705
+ for (let x = 0; x < size; x++) {
4706
+ const dx = x - radius + 0.5;
4707
+ const dy = y - radius + 0.5;
4708
+ const distSqr = dx * dx + dy * dy;
4709
+ if (distSqr <= radius * radius) {
4710
+ data[y * size + x] = 1;
4711
+ }
4712
+ }
4713
+ }
4714
+ return {
4715
+ type: 1 /* BINARY */,
4716
+ data,
4717
+ w: size,
4718
+ h: size,
4719
+ centerOffsetX: centerOffset,
4720
+ centerOffsetY: centerOffset
4721
+ };
4722
+ }
4723
+
4724
+ // src/Paint/makePaintMask.ts
4725
+ function makePaintBinaryMask(mask) {
4726
+ return {
4727
+ type: 1 /* BINARY */,
4728
+ data: mask.data,
4729
+ w: mask.w,
4730
+ h: mask.h,
4731
+ centerOffsetX: -(mask.w >> 1),
4732
+ centerOffsetY: -(mask.h >> 1)
4733
+ };
4734
+ }
4735
+ function makePaintAlphaMask(mask) {
4736
+ return {
4737
+ type: 0 /* ALPHA */,
4738
+ data: mask.data,
4739
+ w: mask.w,
4740
+ h: mask.h,
4741
+ centerOffsetX: -(mask.w >> 1),
4742
+ centerOffsetY: -(mask.h >> 1)
4743
+ };
4744
+ }
4745
+
4746
+ // src/Paint/makeRectFalloffPaintAlphaMask.ts
4747
+ function makeRectFalloffPaintAlphaMask(width, height, fallOff = (d) => d) {
4748
+ const fPx = Math.floor(width / 2);
4749
+ const fPy = Math.floor(height / 2);
4750
+ const invHalfW = 2 / width;
4751
+ const invHalfH = 2 / height;
4752
+ const offX = width % 2 === 0 ? 0.5 : 0;
4753
+ const offY = height % 2 === 0 ? 0.5 : 0;
4754
+ const area = width * height;
4755
+ const data = new Uint8Array(area);
4756
+ for (let y = 0; y < height; y++) {
4757
+ const dy = Math.abs(y - fPy + offY) * invHalfH;
4758
+ const rowOffset = y * width;
4759
+ for (let x = 0; x < width; x++) {
4760
+ const dx = Math.abs(x - fPx + offX) * invHalfW;
4761
+ const dist = dx > dy ? dx : dy;
4762
+ const strength = fallOff(1 - dist);
4763
+ if (strength > 0) {
4764
+ const intensity = strength * 255 | 0;
4765
+ data[rowOffset + x] = Math.max(0, Math.min(255, intensity));
4766
+ }
4767
+ }
4768
+ }
4769
+ return {
4770
+ type: 0 /* ALPHA */,
4594
4771
  data,
4595
- type: 1 /* BINARY */
4596
- });
4772
+ w: width,
4773
+ h: height,
4774
+ centerOffsetX: -(width >> 1),
4775
+ centerOffsetY: -(height >> 1)
4776
+ };
4597
4777
  }
4598
4778
 
4599
4779
  // src/PixelData/PixelData.ts
@@ -4617,14 +4797,96 @@ var PixelData = class {
4617
4797
  }
4618
4798
  };
4619
4799
 
4620
- // src/PixelData/blendPixelDataPaintBuffer.ts
4800
+ // src/PixelData/applyMaskToPixelData.ts
4801
+ function applyMaskToPixelData(dst, mask, opts) {
4802
+ if (mask.type === 1 /* BINARY */) {
4803
+ return applyBinaryMaskToPixelData(dst, mask, opts);
4804
+ } else {
4805
+ return applyAlphaMaskToPixelData(dst, mask, opts);
4806
+ }
4807
+ }
4808
+
4809
+ // src/PixelData/blendColorPixelDataMask.ts
4810
+ function blendColorPixelDataMask(dst, color, mask, opts) {
4811
+ if (mask.type === 1 /* BINARY */) {
4812
+ return blendColorPixelDataBinaryMask(dst, color, mask, opts);
4813
+ } else {
4814
+ return blendColorPixelDataAlphaMask(dst, color, mask, opts);
4815
+ }
4816
+ }
4817
+
4818
+ // src/PixelData/blendColorPixelDataPaintAlphaMask.ts
4621
4819
  var SCRATCH_OPTS = {
4820
+ x: 0,
4821
+ y: 0,
4822
+ alpha: 255,
4823
+ blendFn: sourceOverPerfect
4824
+ };
4825
+ function blendColorPixelDataPaintAlphaMask(dst, color, mask, x, y, alpha = 255, blendFn = sourceOverPerfect) {
4826
+ const tx = x + mask.centerOffsetX;
4827
+ const ty = y + mask.centerOffsetY;
4828
+ SCRATCH_OPTS.x = tx;
4829
+ SCRATCH_OPTS.y = ty;
4830
+ SCRATCH_OPTS.alpha = alpha;
4831
+ SCRATCH_OPTS.blendFn = blendFn;
4832
+ return blendColorPixelDataAlphaMask(dst, color, mask, SCRATCH_OPTS);
4833
+ }
4834
+
4835
+ // src/PixelData/blendColorPixelDataPaintBinaryMask.ts
4836
+ var SCRATCH_OPTS2 = {
4837
+ x: 0,
4838
+ y: 0,
4839
+ alpha: 255,
4840
+ blendFn: sourceOverPerfect
4841
+ };
4842
+ function blendColorPixelDataPaintBinaryMask(dst, color, mask, x, y, alpha = 255, blendFn = sourceOverPerfect) {
4843
+ const tx = x + mask.centerOffsetX;
4844
+ const ty = y + mask.centerOffsetY;
4845
+ SCRATCH_OPTS2.x = tx;
4846
+ SCRATCH_OPTS2.y = ty;
4847
+ SCRATCH_OPTS2.alpha = alpha;
4848
+ SCRATCH_OPTS2.blendFn = blendFn;
4849
+ return blendColorPixelDataBinaryMask(dst, color, mask, SCRATCH_OPTS2);
4850
+ }
4851
+
4852
+ // src/PixelData/blendColorPixelDataPaintMask.ts
4853
+ var SCRATCH_OPTS3 = {
4854
+ x: 0,
4855
+ y: 0,
4856
+ alpha: 255,
4857
+ blendFn: sourceOverPerfect
4858
+ };
4859
+ function blendColorPixelDataPaintMask(dst, color, mask, x, y, alpha = 255, blendFn = sourceOverPerfect) {
4860
+ const tx = x + mask.centerOffsetX;
4861
+ const ty = y + mask.centerOffsetY;
4862
+ SCRATCH_OPTS3.x = tx;
4863
+ SCRATCH_OPTS3.y = ty;
4864
+ SCRATCH_OPTS3.alpha = alpha;
4865
+ SCRATCH_OPTS3.blendFn = blendFn;
4866
+ if (mask.type === 1 /* BINARY */) {
4867
+ return blendColorPixelDataBinaryMask(dst, color, mask, SCRATCH_OPTS3);
4868
+ } else {
4869
+ return blendColorPixelDataAlphaMask(dst, color, mask, SCRATCH_OPTS3);
4870
+ }
4871
+ }
4872
+
4873
+ // src/PixelData/blendPixelDataMask.ts
4874
+ function blendPixelDataMask(target, src, mask, opts) {
4875
+ if (mask.type === 1 /* BINARY */) {
4876
+ return blendPixelDataBinaryMask(target, src, mask, opts);
4877
+ } else {
4878
+ return blendPixelDataAlphaMask(target, src, mask, opts);
4879
+ }
4880
+ }
4881
+
4882
+ // src/PixelData/blendPixelDataPaintBuffer.ts
4883
+ var SCRATCH_OPTS4 = {
4622
4884
  x: 0,
4623
4885
  y: 0,
4624
4886
  alpha: 255,
4625
4887
  blendFn: void 0
4626
4888
  };
4627
- function blendPixelDataPaintBuffer(paintBuffer, target, alpha = 255, blendFn, blendPixelDataFn = blendPixelData) {
4889
+ function blendPixelDataPaintBuffer(target, paintBuffer, alpha = 255, blendFn, blendPixelDataFn = blendPixelData) {
4628
4890
  const tileShift = paintBuffer.config.tileShift;
4629
4891
  const lookup = paintBuffer.lookup;
4630
4892
  for (let i = 0; i < lookup.length; i++) {
@@ -4632,17 +4894,61 @@ function blendPixelDataPaintBuffer(paintBuffer, target, alpha = 255, blendFn, bl
4632
4894
  if (tile) {
4633
4895
  const x = tile.tx << tileShift;
4634
4896
  const y = tile.ty << tileShift;
4635
- SCRATCH_OPTS.x = x;
4636
- SCRATCH_OPTS.y = y;
4637
- SCRATCH_OPTS.alpha = alpha;
4638
- SCRATCH_OPTS.blendFn = blendFn;
4639
- blendPixelDataFn(target, tile, SCRATCH_OPTS);
4897
+ SCRATCH_OPTS4.x = x;
4898
+ SCRATCH_OPTS4.y = y;
4899
+ SCRATCH_OPTS4.alpha = alpha;
4900
+ SCRATCH_OPTS4.blendFn = blendFn;
4901
+ blendPixelDataFn(target, tile, SCRATCH_OPTS4);
4640
4902
  }
4641
4903
  }
4642
4904
  }
4643
4905
 
4644
- // src/PixelData/clearPixelData.ts
4645
- function clearPixelData(dst, rect) {
4906
+ // src/PixelData/fillPixelDataFast.ts
4907
+ var SCRATCH_RECT4 = makeClippedRect();
4908
+ function fillPixelDataFast(dst, color, _x, _y, _w, _h) {
4909
+ let x;
4910
+ let y;
4911
+ let w;
4912
+ let h;
4913
+ if (typeof _x === "object") {
4914
+ x = _x.x ?? 0;
4915
+ y = _x.y ?? 0;
4916
+ w = _x.w ?? dst.width;
4917
+ h = _x.h ?? dst.height;
4918
+ } else if (typeof _x === "number") {
4919
+ x = _x;
4920
+ y = _y;
4921
+ w = _w;
4922
+ h = _h;
4923
+ } else {
4924
+ x = 0;
4925
+ y = 0;
4926
+ w = dst.width;
4927
+ h = dst.height;
4928
+ }
4929
+ const clip = resolveRectClipping(x, y, w, h, dst.width, dst.height, SCRATCH_RECT4);
4930
+ if (!clip.inBounds) return;
4931
+ const {
4932
+ x: finalX,
4933
+ y: finalY,
4934
+ w: actualW,
4935
+ h: actualH
4936
+ } = clip;
4937
+ const dst32 = dst.data32;
4938
+ const dw = dst.width;
4939
+ if (actualW === dw && actualH === dst.height && finalX === 0 && finalY === 0) {
4940
+ dst32.fill(color);
4941
+ return;
4942
+ }
4943
+ for (let iy = 0; iy < actualH; iy++) {
4944
+ const start = (finalY + iy) * dw + finalX;
4945
+ const end = start + actualW;
4946
+ dst32.fill(color, start, end);
4947
+ }
4948
+ }
4949
+
4950
+ // src/PixelData/clearPixelDataFast.ts
4951
+ function clearPixelDataFast(dst, rect) {
4646
4952
  fillPixelDataFast(dst, 0, rect);
4647
4953
  }
4648
4954
 
@@ -4705,26 +5011,6 @@ function extractPixelData(source, _x, _y, _w, _h) {
4705
5011
  return result;
4706
5012
  }
4707
5013
 
4708
- // src/PixelData/PixelBuffer32.ts
4709
- var PixelBuffer32 = class _PixelBuffer32 {
4710
- constructor(width, height, data32) {
4711
- this.width = width;
4712
- this.height = height;
4713
- this.data32 = data32 ?? new Uint32Array(width * height);
4714
- }
4715
- data32;
4716
- set(width, height, data32) {
4717
- ;
4718
- this.data32 = data32 ?? new Uint32Array(width * height);
4719
- this.width = width;
4720
- this.height = height;
4721
- }
4722
- copy() {
4723
- const newData32 = new Uint32Array(this.data32);
4724
- return new _PixelBuffer32(this.width, this.height, newData32);
4725
- }
4726
- };
4727
-
4728
5014
  // src/PixelData/pixelDataToAlphaMask.ts
4729
5015
  function pixelDataToAlphaMask(pixelData) {
4730
5016
  const {
@@ -4876,161 +5162,16 @@ function writePaintBufferToPixelData(target, paintBuffer, writePixelDataBufferFn
4876
5162
  }
4877
5163
  }
4878
5164
  }
4879
-
4880
- // src/Paint/makeCirclePaintAlphaMask.ts
4881
- function makeCirclePaintAlphaMask(size, fallOff = (d) => d) {
4882
- const area = size * size;
4883
- const data = new Uint8Array(area);
4884
- const radius = size / 2;
4885
- const invR = 1 / radius;
4886
- const centerOffset = -Math.ceil(radius - 0.5);
4887
- for (let y = 0; y < size; y++) {
4888
- const rowOffset = y * size;
4889
- const dy = y - radius + 0.5;
4890
- const dy2 = dy * dy;
4891
- for (let x = 0; x < size; x++) {
4892
- const dx = x - radius + 0.5;
4893
- const distSqr = dx * dx + dy2;
4894
- if (distSqr <= radius * radius) {
4895
- const dist = Math.sqrt(distSqr) * invR;
4896
- const strength = fallOff(1 - dist);
4897
- if (strength > 0) {
4898
- const intensity = strength * 255 | 0;
4899
- data[rowOffset + x] = Math.max(0, Math.min(255, intensity));
4900
- }
4901
- }
4902
- }
4903
- }
4904
- return {
4905
- type: 0 /* ALPHA */,
4906
- data,
4907
- w: size,
4908
- h: size,
4909
- centerOffsetX: centerOffset,
4910
- centerOffsetY: centerOffset
4911
- };
4912
- }
4913
-
4914
- // src/Paint/makeCirclePaintBinaryMask.ts
4915
- function makeCirclePaintBinaryMask(size) {
4916
- const area = size * size;
4917
- const data = new Uint8Array(area);
4918
- const radius = size / 2;
4919
- const centerOffset = -Math.ceil(radius - 0.5);
4920
- for (let y = 0; y < size; y++) {
4921
- for (let x = 0; x < size; x++) {
4922
- const dx = x - radius + 0.5;
4923
- const dy = y - radius + 0.5;
4924
- const distSqr = dx * dx + dy * dy;
4925
- if (distSqr <= radius * radius) {
4926
- data[y * size + x] = 1;
4927
- }
4928
- }
4929
- }
4930
- return {
4931
- type: 1 /* BINARY */,
4932
- data,
4933
- w: size,
4934
- h: size,
4935
- centerOffsetX: centerOffset,
4936
- centerOffsetY: centerOffset
4937
- };
4938
- }
4939
-
4940
- // src/Paint/makePaintMask.ts
4941
- function makePaintBinaryMask(mask) {
4942
- return {
4943
- type: 1 /* BINARY */,
4944
- data: mask.data,
4945
- w: mask.w,
4946
- h: mask.h,
4947
- centerOffsetX: -(mask.w >> 1),
4948
- centerOffsetY: -(mask.h >> 1)
4949
- };
4950
- }
4951
- function makePaintAlphaMask(mask) {
4952
- return {
4953
- type: 0 /* ALPHA */,
4954
- data: mask.data,
4955
- w: mask.w,
4956
- h: mask.h,
4957
- centerOffsetX: -(mask.w >> 1),
4958
- centerOffsetY: -(mask.h >> 1)
4959
- };
4960
- }
4961
-
4962
- // src/Paint/makeRectFalloffPaintAlphaMask.ts
4963
- function makeRectFalloffPaintAlphaMask(width, height, fallOff = (d) => d) {
4964
- const fPx = Math.floor(width / 2);
4965
- const fPy = Math.floor(height / 2);
4966
- const invHalfW = 2 / width;
4967
- const invHalfH = 2 / height;
4968
- const offX = width % 2 === 0 ? 0.5 : 0;
4969
- const offY = height % 2 === 0 ? 0.5 : 0;
4970
- const area = width * height;
4971
- const data = new Uint8Array(area);
4972
- for (let y = 0; y < height; y++) {
4973
- const dy = Math.abs(y - fPy + offY) * invHalfH;
4974
- const rowOffset = y * width;
4975
- for (let x = 0; x < width; x++) {
4976
- const dx = Math.abs(x - fPx + offX) * invHalfW;
4977
- const dist = dx > dy ? dx : dy;
4978
- const strength = fallOff(1 - dist);
4979
- if (strength > 0) {
4980
- const intensity = strength * 255 | 0;
4981
- data[rowOffset + x] = Math.max(0, Math.min(255, intensity));
4982
- }
4983
- }
4984
- }
4985
- return {
4986
- type: 0 /* ALPHA */,
4987
- data,
4988
- w: width,
4989
- h: height,
4990
- centerOffsetX: -macro_halfAndFloor(width),
4991
- centerOffsetY: -macro_halfAndFloor(height)
4992
- };
4993
- }
4994
-
4995
- // src/Paint/PaintBufferCanvasRenderer.ts
4996
- function makePaintBufferCanvasRenderer(paintBuffer, offscreenCanvasClass = OffscreenCanvas) {
4997
- const config = paintBuffer.config;
4998
- const tileSize = config.tileSize;
4999
- const tileShift = config.tileShift;
5000
- const lookup = paintBuffer.lookup;
5001
- const canvas = new offscreenCanvasClass(tileSize, tileSize);
5002
- const ctx = canvas.getContext("2d");
5003
- if (!ctx) throw new Error(CANVAS_CTX_FAILED);
5004
- ctx.imageSmoothingEnabled = false;
5005
- return function drawPaintBuffer(targetCtx, alpha = 255, compOperation = "source-over") {
5006
- targetCtx.globalAlpha = alpha / 255;
5007
- targetCtx.globalCompositeOperation = compOperation;
5008
- for (let i = 0; i < lookup.length; i++) {
5009
- const tile = lookup[i];
5010
- if (tile) {
5011
- const dx = tile.tx << tileShift;
5012
- const dy = tile.ty << tileShift;
5013
- ctx.putImageData(tile.imageData, 0, 0);
5014
- targetCtx.drawImage(canvas, dx, dy);
5015
- }
5016
- }
5017
- targetCtx.globalAlpha = 1;
5018
- targetCtx.globalCompositeOperation = "source-over";
5019
- };
5020
- }
5021
5165
  export {
5022
5166
  BASE_FAST_BLEND_MODE_FUNCTIONS,
5023
5167
  BASE_PERFECT_BLEND_MODE_FUNCTIONS,
5024
5168
  BaseBlendMode,
5025
5169
  CANVAS_COMPOSITE_MAP,
5026
- CANVAS_CTX_FAILED,
5027
5170
  HistoryManager,
5028
5171
  IndexedImage,
5029
5172
  MaskType,
5030
- OFFSCREEN_CANVAS_CTX_FAILED,
5031
5173
  PaintBuffer,
5032
5174
  PixelAccumulator,
5033
- PixelBuffer32,
5034
5175
  PixelData,
5035
5176
  PixelEngineConfig,
5036
5177
  PixelTile,
@@ -5040,18 +5181,24 @@ export {
5040
5181
  applyAlphaMaskToPixelData,
5041
5182
  applyBinaryMaskToAlphaMask,
5042
5183
  applyBinaryMaskToPixelData,
5184
+ applyMaskToPixelData,
5043
5185
  applyPatchTiles,
5044
5186
  base64DecodeArrayBuffer,
5045
5187
  base64EncodeArrayBuffer,
5046
5188
  blendColorPixelData,
5047
5189
  blendColorPixelDataAlphaMask,
5048
5190
  blendColorPixelDataBinaryMask,
5191
+ blendColorPixelDataMask,
5192
+ blendColorPixelDataPaintAlphaMask,
5193
+ blendColorPixelDataPaintBinaryMask,
5194
+ blendColorPixelDataPaintMask,
5049
5195
  blendPixel,
5050
5196
  blendPixelData,
5051
5197
  blendPixelDataAlphaMask,
5052
5198
  blendPixelDataBinaryMask,
5199
+ blendPixelDataMask,
5053
5200
  blendPixelDataPaintBuffer,
5054
- clearPixelData,
5201
+ clearPixelDataFast,
5055
5202
  color32ToCssRGBA,
5056
5203
  color32ToHex,
5057
5204
  colorBurnFast,
@@ -5124,6 +5271,8 @@ export {
5124
5271
  makeCanvasFrameRenderer,
5125
5272
  makeCirclePaintAlphaMask,
5126
5273
  makeCirclePaintBinaryMask,
5274
+ makeClippedBlit,
5275
+ makeClippedRect,
5127
5276
  makeFastBlendModeRegistry,
5128
5277
  makeFullPixelMutator,
5129
5278
  makeHistoryAction,
@@ -5145,12 +5294,17 @@ export {
5145
5294
  multiplyPerfect,
5146
5295
  mutatorApplyAlphaMask,
5147
5296
  mutatorApplyBinaryMask,
5297
+ mutatorApplyMask,
5298
+ mutatorBlendAlphaMask,
5299
+ mutatorBlendBinaryMask,
5148
5300
  mutatorBlendColor,
5149
- mutatorBlendPaintMask,
5301
+ mutatorBlendColorPaintAlphaMask,
5302
+ mutatorBlendColorPaintBinaryMask,
5303
+ mutatorBlendColorPaintMask,
5304
+ mutatorBlendColorPaintRect,
5305
+ mutatorBlendMask,
5150
5306
  mutatorBlendPixel,
5151
5307
  mutatorBlendPixelData,
5152
- mutatorBlendPixelDataAlphaMask,
5153
- mutatorBlendPixelDataBinaryMask,
5154
5308
  mutatorClear,
5155
5309
  mutatorFill,
5156
5310
  mutatorFillBinaryMask,
@@ -5168,10 +5322,13 @@ export {
5168
5322
  pixelDataToAlphaMask,
5169
5323
  reflectPixelDataHorizontal,
5170
5324
  reflectPixelDataVertical,
5325
+ resample32,
5171
5326
  resampleImageData,
5172
5327
  resampleIndexedImage,
5173
5328
  resamplePixelData,
5174
5329
  resizeImageData,
5330
+ resolveBlitClipping,
5331
+ resolveRectClipping,
5175
5332
  rotatePixelData,
5176
5333
  screenFast,
5177
5334
  screenPerfect,