pixel-data-js 0.23.1 → 0.25.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 (85) hide show
  1. package/dist/index.dev.cjs +1816 -1802
  2. package/dist/index.dev.cjs.map +1 -1
  3. package/dist/index.dev.js +1799 -1786
  4. package/dist/index.dev.js.map +1 -1
  5. package/dist/index.prod.cjs +1816 -1802
  6. package/dist/index.prod.cjs.map +1 -1
  7. package/dist/index.prod.d.ts +349 -342
  8. package/dist/index.prod.js +1799 -1786
  9. package/dist/index.prod.js.map +1 -1
  10. package/package.json +3 -2
  11. package/src/Algorithm/floodFillSelection.ts +2 -2
  12. package/src/Canvas/CanvasFrameRenderer.ts +57 -0
  13. package/src/Canvas/ReusableCanvas.ts +60 -11
  14. package/src/Canvas/canvas-blend-modes.ts +28 -0
  15. package/src/History/HistoryAction.ts +38 -0
  16. package/src/History/HistoryManager.ts +4 -8
  17. package/src/History/PixelAccumulator.ts +137 -99
  18. package/src/History/PixelEngineConfig.ts +16 -6
  19. package/src/History/PixelMutator/mutatorApplyAlphaMask.ts +6 -6
  20. package/src/History/PixelMutator/mutatorApplyBinaryMask.ts +6 -6
  21. package/src/History/PixelMutator/mutatorBlendColor.ts +8 -5
  22. package/src/History/PixelMutator/mutatorBlendPixel.ts +22 -26
  23. package/src/History/PixelMutator/mutatorBlendPixelData.ts +7 -5
  24. package/src/History/PixelMutator/mutatorBlendPixelDataAlphaMask.ts +7 -5
  25. package/src/History/PixelMutator/mutatorBlendPixelDataBinaryMask.ts +7 -5
  26. package/src/History/PixelMutator/mutatorClear.ts +6 -5
  27. package/src/History/PixelMutator/mutatorFill.ts +34 -9
  28. package/src/History/PixelMutator/mutatorFillBinaryMask.ts +4 -2
  29. package/src/History/PixelMutator/mutatorInvert.ts +8 -4
  30. package/src/History/PixelMutator.ts +2 -21
  31. package/src/History/PixelPatchTiles.ts +4 -16
  32. package/src/History/PixelWriter.ts +150 -31
  33. package/src/ImageData/ReusableImageData.ts +3 -5
  34. package/src/Internal/helpers.ts +2 -0
  35. package/src/Paint/PaintBuffer.ts +269 -0
  36. package/src/Paint/PaintBufferCanvasRenderer.ts +48 -0
  37. package/src/Paint/makeCirclePaintAlphaMask.ts +41 -0
  38. package/src/{Mask/CircleBrushBinaryMask.ts → Paint/makeCirclePaintBinaryMask.ts} +5 -6
  39. package/src/Paint/makePaintMask.ts +28 -0
  40. package/src/Paint/makeRectFalloffPaintAlphaMask.ts +47 -0
  41. package/src/PixelData/PixelBuffer32.ts +2 -2
  42. package/src/PixelData/PixelData.ts +2 -28
  43. package/src/PixelData/applyAlphaMaskToPixelData.ts +20 -10
  44. package/src/PixelData/applyBinaryMaskToPixelData.ts +26 -19
  45. package/src/PixelData/blendColorPixelData.ts +33 -9
  46. package/src/PixelData/blendColorPixelDataAlphaMask.ts +19 -9
  47. package/src/PixelData/blendColorPixelDataBinaryMask.ts +19 -10
  48. package/src/PixelData/blendPixel.ts +47 -0
  49. package/src/PixelData/blendPixelData.ts +17 -7
  50. package/src/PixelData/blendPixelDataAlphaMask.ts +15 -7
  51. package/src/PixelData/blendPixelDataBinaryMask.ts +16 -7
  52. package/src/PixelData/blendPixelDataPaintBuffer.ts +37 -0
  53. package/src/PixelData/clearPixelData.ts +4 -4
  54. package/src/PixelData/extractPixelData.ts +4 -4
  55. package/src/PixelData/extractPixelDataBuffer.ts +4 -4
  56. package/src/PixelData/fillPixelData.ts +31 -21
  57. package/src/PixelData/fillPixelDataBinaryMask.ts +15 -7
  58. package/src/PixelData/fillPixelDataFast.ts +94 -0
  59. package/src/PixelData/invertPixelData.ts +6 -4
  60. package/src/PixelData/pixelDataToAlphaMask.ts +2 -2
  61. package/src/PixelData/reflectPixelData.ts +3 -3
  62. package/src/PixelData/resamplePixelData.ts +2 -2
  63. package/src/PixelData/writePaintBufferToPixelData.ts +26 -0
  64. package/src/PixelData/writePixelDataBuffer.ts +5 -5
  65. package/src/PixelTile/PixelTile.ts +21 -0
  66. package/src/PixelTile/PixelTilePool.ts +63 -0
  67. package/src/Rect/trimMaskRectBounds.ts +121 -0
  68. package/src/Rect/trimRectBounds.ts +25 -116
  69. package/src/_types.ts +17 -16
  70. package/src/index.ts +19 -24
  71. package/src/History/PixelMutator/mutatorApplyCircleBrush.ts +0 -78
  72. package/src/History/PixelMutator/mutatorApplyCircleBrushStroke.ts +0 -181
  73. package/src/History/PixelMutator/mutatorApplyCirclePencil.ts +0 -59
  74. package/src/History/PixelMutator/mutatorApplyCirclePencilStroke.ts +0 -171
  75. package/src/History/PixelMutator/mutatorApplyRectBrush.ts +0 -64
  76. package/src/History/PixelMutator/mutatorApplyRectBrushStroke.ts +0 -182
  77. package/src/History/PixelMutator/mutatorApplyRectPencil.ts +0 -65
  78. package/src/History/PixelMutator/mutatorApplyRectPencilStroke.ts +0 -164
  79. package/src/Mask/CircleBrushAlphaMask.ts +0 -32
  80. package/src/PixelData/applyCircleBrushToPixelData.ts +0 -91
  81. package/src/PixelData/applyRectBrushToPixelData.ts +0 -85
  82. package/src/Rect/getCircleBrushOrPencilBounds.ts +0 -43
  83. package/src/Rect/getCircleBrushOrPencilStrokeBounds.ts +0 -24
  84. package/src/Rect/getRectBrushOrPencilBounds.ts +0 -38
  85. package/src/Rect/getRectBrushOrPencilStrokeBounds.ts +0 -26
@@ -28,27 +28,29 @@ __export(src_exports, {
28
28
  IndexedImage: () => IndexedImage,
29
29
  MaskType: () => MaskType,
30
30
  OFFSCREEN_CANVAS_CTX_FAILED: () => OFFSCREEN_CANVAS_CTX_FAILED,
31
+ PaintBuffer: () => PaintBuffer,
31
32
  PixelAccumulator: () => PixelAccumulator,
32
33
  PixelBuffer32: () => PixelBuffer32,
33
34
  PixelData: () => PixelData,
34
35
  PixelEngineConfig: () => PixelEngineConfig,
35
36
  PixelTile: () => PixelTile,
37
+ PixelTilePool: () => PixelTilePool,
36
38
  PixelWriter: () => PixelWriter,
37
39
  UnsupportedFormatError: () => UnsupportedFormatError,
38
40
  applyAlphaMaskToPixelData: () => applyAlphaMaskToPixelData,
39
41
  applyBinaryMaskToAlphaMask: () => applyBinaryMaskToAlphaMask,
40
42
  applyBinaryMaskToPixelData: () => applyBinaryMaskToPixelData,
41
- applyCircleBrushToPixelData: () => applyCircleBrushToPixelData,
42
43
  applyPatchTiles: () => applyPatchTiles,
43
- applyRectBrushToPixelData: () => applyRectBrushToPixelData,
44
44
  base64DecodeArrayBuffer: () => base64DecodeArrayBuffer,
45
45
  base64EncodeArrayBuffer: () => base64EncodeArrayBuffer,
46
46
  blendColorPixelData: () => blendColorPixelData,
47
47
  blendColorPixelDataAlphaMask: () => blendColorPixelDataAlphaMask,
48
48
  blendColorPixelDataBinaryMask: () => blendColorPixelDataBinaryMask,
49
+ blendPixel: () => blendPixel,
49
50
  blendPixelData: () => blendPixelData,
50
51
  blendPixelDataAlphaMask: () => blendPixelDataAlphaMask,
51
52
  blendPixelDataBinaryMask: () => blendPixelDataBinaryMask,
53
+ blendPixelDataPaintBuffer: () => blendPixelDataPaintBuffer,
52
54
  clearPixelData: () => clearPixelData,
53
55
  color32ToCssRGBA: () => color32ToCssRGBA,
54
56
  color32ToHex: () => color32ToHex,
@@ -82,14 +84,11 @@ __export(src_exports, {
82
84
  fileToImageData: () => fileToImageData,
83
85
  fillPixelData: () => fillPixelData,
84
86
  fillPixelDataBinaryMask: () => fillPixelDataBinaryMask,
87
+ fillPixelDataFast: () => fillPixelDataFast,
85
88
  floodFillSelection: () => floodFillSelection,
86
89
  forEachLinePoint: () => forEachLinePoint,
87
- getCircleBrushOrPencilBounds: () => getCircleBrushOrPencilBounds,
88
- getCircleBrushOrPencilStrokeBounds: () => getCircleBrushOrPencilStrokeBounds,
89
90
  getImageDataFromClipboard: () => getImageDataFromClipboard,
90
91
  getIndexedImageColorCounts: () => getIndexedImageColorCounts,
91
- getRectBrushOrPencilBounds: () => getRectBrushOrPencilBounds,
92
- getRectBrushOrPencilStrokeBounds: () => getRectBrushOrPencilStrokeBounds,
93
92
  getRectsBounds: () => getRectsBounds,
94
93
  getSupportedPixelFormats: () => getSupportedPixelFormats,
95
94
  hardLightFast: () => hardLightFast,
@@ -122,15 +121,22 @@ __export(src_exports, {
122
121
  makeAlphaMask: () => makeAlphaMask,
123
122
  makeBinaryMask: () => makeBinaryMask,
124
123
  makeBlendModeRegistry: () => makeBlendModeRegistry,
125
- makeCircleBrushAlphaMask: () => makeCircleBrushAlphaMask,
126
- makeCircleBrushBinaryMask: () => makeCircleBrushBinaryMask,
124
+ makeCanvasFrameRenderer: () => makeCanvasFrameRenderer,
125
+ makeCirclePaintAlphaMask: () => makeCirclePaintAlphaMask,
126
+ makeCirclePaintBinaryMask: () => makeCirclePaintBinaryMask,
127
127
  makeFastBlendModeRegistry: () => makeFastBlendModeRegistry,
128
128
  makeFullPixelMutator: () => makeFullPixelMutator,
129
+ makeHistoryAction: () => makeHistoryAction,
129
130
  makeImageDataLike: () => makeImageDataLike,
131
+ makePaintAlphaMask: () => makePaintAlphaMask,
132
+ makePaintBinaryMask: () => makePaintBinaryMask,
133
+ makePaintBufferCanvasRenderer: () => makePaintBufferCanvasRenderer,
130
134
  makePerfectBlendModeRegistry: () => makePerfectBlendModeRegistry,
131
135
  makePixelCanvas: () => makePixelCanvas,
136
+ makeRectFalloffPaintAlphaMask: () => makeRectFalloffPaintAlphaMask,
132
137
  makeReusableCanvas: () => makeReusableCanvas,
133
138
  makeReusableImageData: () => makeReusableImageData,
139
+ makeReusableOffscreenCanvas: () => makeReusableOffscreenCanvas,
134
140
  merge2BinaryMaskRects: () => merge2BinaryMaskRects,
135
141
  mergeAlphaMasks: () => mergeAlphaMasks,
136
142
  mergeBinaryMaskRects: () => mergeBinaryMaskRects,
@@ -139,14 +145,6 @@ __export(src_exports, {
139
145
  multiplyPerfect: () => multiplyPerfect,
140
146
  mutatorApplyAlphaMask: () => mutatorApplyAlphaMask,
141
147
  mutatorApplyBinaryMask: () => mutatorApplyBinaryMask,
142
- mutatorApplyCircleBrush: () => mutatorApplyCircleBrush,
143
- mutatorApplyCircleBrushStroke: () => mutatorApplyCircleBrushStroke,
144
- mutatorApplyCirclePencil: () => mutatorApplyCirclePencil,
145
- mutatorApplyCirclePencilStroke: () => mutatorApplyCirclePencilStroke,
146
- mutatorApplyRectBrush: () => mutatorApplyRectBrush,
147
- mutatorApplyRectBrushStroke: () => mutatorApplyRectBrushStroke,
148
- mutatorApplyRectPencil: () => mutatorApplyRectPencil,
149
- mutatorApplyRectPencilStroke: () => mutatorApplyRectPencilStroke,
150
148
  mutatorBlendColor: () => mutatorBlendColor,
151
149
  mutatorBlendPixel: () => mutatorBlendPixel,
152
150
  mutatorBlendPixelData: () => mutatorBlendPixelData,
@@ -155,6 +153,7 @@ __export(src_exports, {
155
153
  mutatorClear: () => mutatorClear,
156
154
  mutatorFill: () => mutatorFill,
157
155
  mutatorFillBinaryMask: () => mutatorFillBinaryMask,
156
+ mutatorFillRect: () => mutatorFillRect,
158
157
  mutatorInvert: () => mutatorInvert,
159
158
  overlayFast: () => overlayFast,
160
159
  overlayPerfect: () => overlayPerfect,
@@ -186,6 +185,7 @@ __export(src_exports, {
186
185
  subtractFast: () => subtractFast,
187
186
  subtractPerfect: () => subtractPerfect,
188
187
  toBlendModeIndexAndName: () => toBlendModeIndexAndName,
188
+ trimMaskRectBounds: () => trimMaskRectBounds,
189
189
  trimRectBounds: () => trimRectBounds,
190
190
  uInt32ArrayToImageData: () => uInt32ArrayToImageData,
191
191
  uInt32ArrayToImageDataLike: () => uInt32ArrayToImageDataLike,
@@ -201,6 +201,7 @@ __export(src_exports, {
201
201
  writeImageDataBuffer: () => writeImageDataBuffer,
202
202
  writeImageDataToClipboard: () => writeImageDataToClipboard,
203
203
  writeImgBlobToClipboard: () => writeImgBlobToClipboard,
204
+ writePaintBufferToPixelData: () => writePaintBufferToPixelData,
204
205
  writePixelDataBuffer: () => writePixelDataBuffer
205
206
  });
206
207
  module.exports = __toCommonJS(src_exports);
@@ -439,8 +440,8 @@ function extractMaskBuffer(maskBuffer, maskWidth, xOrRect, y, w, h) {
439
440
  return out;
440
441
  }
441
442
 
442
- // src/Rect/trimRectBounds.ts
443
- function trimRectBounds(target, bounds) {
443
+ // src/Rect/trimMaskRectBounds.ts
444
+ function trimMaskRectBounds(target, bounds) {
444
445
  const originalX = target.x;
445
446
  const originalY = target.y;
446
447
  const originalW = target.w;
@@ -628,7 +629,7 @@ function floodFillSelection(img, startX, startY, {
628
629
  finalMask[my * sw + mx] = 1;
629
630
  }
630
631
  }
631
- trimRectBounds(selectionRect, {
632
+ trimMaskRectBounds(selectionRect, {
632
633
  x: 0,
633
634
  y: 0,
634
635
  w: width,
@@ -643,6 +644,26 @@ function floodFillSelection(img, startX, startY, {
643
644
  };
644
645
  }
645
646
 
647
+ // src/Algorithm/forEachLinePoint.ts
648
+ function forEachLinePoint(x0, y0, x1, y1, callback) {
649
+ const dx = x1 - x0;
650
+ const dy = y1 - y0;
651
+ const steps = Math.max(Math.abs(dx), Math.abs(dy));
652
+ if (steps === 0) {
653
+ callback(x0, y0);
654
+ return;
655
+ }
656
+ const xInc = dx / steps;
657
+ const yInc = dy / steps;
658
+ let curX = x0;
659
+ let curY = y0;
660
+ for (let i = 0; i <= steps; i++) {
661
+ callback(curX, curY);
662
+ curX += xInc;
663
+ curY += yInc;
664
+ }
665
+ }
666
+
646
667
  // src/BlendModes/blend-modes.ts
647
668
  var BaseBlendMode = {
648
669
  overwrite: 0,
@@ -1720,51 +1741,104 @@ var getKeyByValue = (obj, value) => {
1720
1741
  var OFFSCREEN_CANVAS_CTX_FAILED = "Failed to create OffscreenCanvas context";
1721
1742
  var CANVAS_CTX_FAILED = "Failed to create Canvas context";
1722
1743
 
1723
- // src/Canvas/PixelCanvas.ts
1724
- function makePixelCanvas(canvas) {
1725
- const ctx = canvas.getContext("2d");
1726
- if (!ctx) throw new Error(CANVAS_CTX_FAILED);
1727
- ctx.imageSmoothingEnabled = false;
1728
- return {
1729
- canvas,
1730
- ctx,
1731
- resize(w, h) {
1732
- canvas.width = w;
1733
- canvas.height = h;
1734
- ctx.imageSmoothingEnabled = false;
1735
- }
1736
- };
1737
- }
1738
-
1739
1744
  // src/Canvas/ReusableCanvas.ts
1740
1745
  function makeReusableCanvas() {
1746
+ return makeReusableCanvasMeta((w, h) => {
1747
+ const canvas = document.createElement("canvas");
1748
+ canvas.width = w;
1749
+ canvas.height = h;
1750
+ return canvas;
1751
+ });
1752
+ }
1753
+ function makeReusableOffscreenCanvas() {
1754
+ return makeReusableCanvasMeta((w, h) => new OffscreenCanvas(w, h));
1755
+ }
1756
+ function makeReusableCanvasMeta(factory) {
1741
1757
  let canvas = null;
1742
1758
  let ctx = null;
1759
+ const result = {
1760
+ canvas: null,
1761
+ ctx: null
1762
+ };
1743
1763
  function get2(width, height) {
1744
1764
  if (canvas === null) {
1745
- canvas = document.createElement("canvas");
1765
+ canvas = factory(width, height);
1746
1766
  ctx = canvas.getContext("2d");
1747
- if (!ctx) throw new Error(CANVAS_CTX_FAILED);
1767
+ if (!ctx) {
1768
+ throw new Error(CANVAS_CTX_FAILED);
1769
+ }
1770
+ ctx.imageSmoothingEnabled = false;
1771
+ result.canvas = canvas;
1772
+ result.ctx = ctx;
1773
+ return result;
1748
1774
  }
1749
1775
  if (canvas.width !== width || canvas.height !== height) {
1750
1776
  canvas.width = width;
1751
1777
  canvas.height = height;
1752
1778
  ctx.imageSmoothingEnabled = false;
1753
1779
  } else {
1780
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
1754
1781
  ctx.clearRect(0, 0, width, height);
1755
1782
  }
1756
- return {
1757
- canvas,
1758
- ctx
1759
- };
1783
+ return result;
1760
1784
  }
1761
1785
  get2.reset = () => {
1762
1786
  canvas = null;
1763
1787
  ctx = null;
1788
+ result.canvas = null;
1789
+ result.ctx = null;
1764
1790
  };
1765
1791
  return get2;
1766
1792
  }
1767
1793
 
1794
+ // src/Canvas/CanvasFrameRenderer.ts
1795
+ var defaults = {
1796
+ makeReusableCanvas
1797
+ };
1798
+ function makeCanvasFrameRenderer(deps = defaults) {
1799
+ const {
1800
+ makeReusableCanvas: makeReusableCanvas2 = defaults.makeReusableCanvas
1801
+ } = deps;
1802
+ const bufferCanvas = makeReusableCanvas2();
1803
+ return function renderCanvasFrame(pixelCanvas, scale, getImageData, drawPixelLayer, drawScreenLayer) {
1804
+ const {
1805
+ canvas,
1806
+ ctx
1807
+ } = pixelCanvas;
1808
+ const {
1809
+ ctx: pxCtx,
1810
+ canvas: pxCanvas
1811
+ } = bufferCanvas(canvas.width, canvas.height);
1812
+ const img = getImageData();
1813
+ if (img) {
1814
+ pxCtx.putImageData(img, 0, 0);
1815
+ }
1816
+ drawPixelLayer?.(pxCtx);
1817
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
1818
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1819
+ ctx.setTransform(scale, 0, 0, scale, 0, 0);
1820
+ ctx.drawImage(pxCanvas, 0, 0);
1821
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
1822
+ drawScreenLayer?.(ctx, scale);
1823
+ };
1824
+ }
1825
+
1826
+ // src/Canvas/PixelCanvas.ts
1827
+ function makePixelCanvas(canvas) {
1828
+ const ctx = canvas.getContext("2d");
1829
+ if (!ctx) throw new Error(CANVAS_CTX_FAILED);
1830
+ ctx.imageSmoothingEnabled = false;
1831
+ return {
1832
+ canvas,
1833
+ ctx,
1834
+ resize(w, h) {
1835
+ canvas.width = w;
1836
+ canvas.height = h;
1837
+ ctx.imageSmoothingEnabled = false;
1838
+ }
1839
+ };
1840
+ }
1841
+
1768
1842
  // src/ImageData/imgBlobToImageData.ts
1769
1843
  async function imgBlobToImageData(blob) {
1770
1844
  let bitmap = null;
@@ -1824,6 +1898,50 @@ async function writeImageDataToClipboard(imageData) {
1824
1898
  return writeImgBlobToClipboard(blob);
1825
1899
  }
1826
1900
 
1901
+ // src/History/PixelPatchTiles.ts
1902
+ function applyPatchTiles(target, tiles, tileSize) {
1903
+ for (let i = 0; i < tiles.length; i++) {
1904
+ const tile = tiles[i];
1905
+ if (!tile) continue;
1906
+ const dst = target.data32;
1907
+ const src = tile.data32;
1908
+ const dstWidth = target.width;
1909
+ const dstHeight = target.height;
1910
+ const startX = tile.tx * tileSize;
1911
+ const startY = tile.ty * tileSize;
1912
+ const copyWidth = Math.max(0, Math.min(tileSize, dstWidth - startX));
1913
+ if (copyWidth <= 0) continue;
1914
+ for (let ly = 0; ly < tileSize; ly++) {
1915
+ const globalY = startY + ly;
1916
+ if (globalY >= dstHeight) break;
1917
+ const dstIndex = globalY * dstWidth + startX;
1918
+ const srcIndex = ly * tileSize;
1919
+ const rowData = src.subarray(srcIndex, srcIndex + copyWidth);
1920
+ dst.set(rowData, dstIndex);
1921
+ }
1922
+ }
1923
+ }
1924
+
1925
+ // src/History/HistoryAction.ts
1926
+ function makeHistoryAction(writer, patch, after, afterUndo, afterRedo, applyPatchTilesFn = applyPatchTiles) {
1927
+ const target = writer.config.target;
1928
+ const tileSize = writer.config.tileSize;
1929
+ const accumulator = writer.accumulator;
1930
+ return {
1931
+ undo: () => {
1932
+ applyPatchTilesFn(target, patch.beforeTiles, tileSize);
1933
+ afterUndo?.();
1934
+ after?.();
1935
+ },
1936
+ redo: () => {
1937
+ applyPatchTilesFn(target, patch.afterTiles, tileSize);
1938
+ afterRedo?.();
1939
+ after?.();
1940
+ },
1941
+ dispose: () => accumulator.recyclePatch(patch)
1942
+ };
1943
+ }
1944
+
1827
1945
  // src/History/HistoryManager.ts
1828
1946
  var HistoryManager = class {
1829
1947
  constructor(maxSteps = 50) {
@@ -1882,134 +2000,123 @@ var HistoryManager = class {
1882
2000
  }
1883
2001
  };
1884
2002
 
1885
- // src/History/PixelPatchTiles.ts
1886
- var PixelTile = class {
1887
- constructor(id, tx, ty, tileArea) {
1888
- this.id = id;
1889
- this.tx = tx;
1890
- this.ty = ty;
1891
- this.data32 = new Uint32Array(tileArea);
1892
- }
1893
- data32;
1894
- };
1895
- function applyPatchTiles(target, tiles, tileSize = 256) {
1896
- for (let i = 0; i < tiles.length; i++) {
1897
- const tile = tiles[i];
1898
- if (!tile) continue;
1899
- const dst = target.data32;
1900
- const src = tile.data32;
1901
- const dstWidth = target.width;
1902
- const dstHeight = target.height;
1903
- const startX = tile.tx * tileSize;
1904
- const startY = tile.ty * tileSize;
1905
- const copyWidth = Math.max(0, Math.min(tileSize, dstWidth - startX));
1906
- if (copyWidth <= 0) return;
1907
- for (let ly = 0; ly < tileSize; ly++) {
1908
- const globalY = startY + ly;
1909
- if (globalY >= dstHeight) break;
1910
- const dstIndex = globalY * dstWidth + startX;
1911
- const srcIndex = ly * tileSize;
1912
- const rowData = src.subarray(srcIndex, srcIndex + copyWidth);
1913
- dst.set(rowData, dstIndex);
1914
- }
1915
- }
1916
- }
1917
-
1918
2003
  // src/History/PixelAccumulator.ts
1919
2004
  var PixelAccumulator = class {
1920
- constructor(target, config) {
1921
- this.target = target;
2005
+ constructor(config, tilePool) {
1922
2006
  this.config = config;
2007
+ this.tilePool = tilePool;
1923
2008
  this.lookup = [];
1924
2009
  this.beforeTiles = [];
1925
- this.pool = [];
1926
2010
  }
1927
2011
  lookup;
1928
2012
  beforeTiles;
1929
- pool;
1930
- getTile(id, tx, ty) {
1931
- let tile = this.pool.pop();
1932
- if (tile) {
1933
- tile.id = id;
1934
- tile.tx = tx;
1935
- tile.ty = ty;
1936
- return tile;
1937
- }
1938
- return new PixelTile(id, tx, ty, this.config.tileArea);
1939
- }
1940
2013
  recyclePatch(patch) {
1941
- const before = patch.beforeTiles;
1942
- for (let i = 0; i < before.length; i++) {
1943
- let tile = before[i];
1944
- if (tile) {
1945
- this.pool.push(tile);
1946
- }
1947
- }
1948
- const after = patch.afterTiles;
1949
- for (let i = 0; i < after.length; i++) {
1950
- let tile = after[i];
1951
- if (tile) {
1952
- this.pool.push(tile);
1953
- }
1954
- }
2014
+ this.tilePool.releaseTiles(patch.beforeTiles);
2015
+ this.tilePool.releaseTiles(patch.afterTiles);
1955
2016
  }
1956
2017
  /**
1957
2018
  * @param x pixel x coordinate
1958
2019
  * @param y pixel y coordinate
1959
2020
  */
1960
- storeTileBeforeState(x, y) {
1961
- let target = this.target;
1962
- let shift = this.config.tileShift;
1963
- let columns = target.width + this.config.tileMask >> shift;
1964
- let tx = x >> shift;
1965
- let ty = y >> shift;
1966
- let id = ty * columns + tx;
2021
+ storePixelBeforeState(x, y) {
2022
+ const shift = this.config.tileShift;
2023
+ const columns = this.config.targetColumns;
2024
+ const tx = x >> shift;
2025
+ const ty = y >> shift;
2026
+ const id = ty * columns + tx;
1967
2027
  let tile = this.lookup[id];
2028
+ let added = false;
1968
2029
  if (!tile) {
1969
- tile = this.getTile(id, tx, ty);
2030
+ tile = this.tilePool.getTile(id, tx, ty);
1970
2031
  this.extractState(tile);
1971
2032
  this.lookup[id] = tile;
1972
2033
  this.beforeTiles.push(tile);
2034
+ added = true;
1973
2035
  }
2036
+ return (didChange) => {
2037
+ if (!didChange && added) {
2038
+ this.beforeTiles.pop();
2039
+ this.lookup[id] = void 0;
2040
+ this.tilePool.releaseTile(tile);
2041
+ }
2042
+ return didChange;
2043
+ };
1974
2044
  }
1975
2045
  /**
1976
- *
1977
2046
  * @param x pixel x coordinate
1978
2047
  * @param y pixel y coordinate
1979
2048
  * @param w pixel width
1980
2049
  * @param h pixel height
1981
2050
  */
1982
2051
  storeRegionBeforeState(x, y, w, h) {
1983
- let target = this.target;
1984
- let shift = this.config.tileShift;
1985
- let columns = target.width + this.config.tileMask >> shift;
1986
- let startX = x >> shift;
1987
- let startY = y >> shift;
1988
- let endX = x + w - 1 >> shift;
1989
- let endY = y + h - 1 >> shift;
2052
+ const shift = this.config.tileShift;
2053
+ const columns = this.config.targetColumns;
2054
+ const startX = x >> shift;
2055
+ const startY = y >> shift;
2056
+ const endX = x + w - 1 >> shift;
2057
+ const endY = y + h - 1 >> shift;
2058
+ const startIndex = this.beforeTiles.length;
1990
2059
  for (let ty = startY; ty <= endY; ty++) {
1991
2060
  for (let tx = startX; tx <= endX; tx++) {
1992
- let id = ty * columns + tx;
2061
+ const id = ty * columns + tx;
1993
2062
  let tile = this.lookup[id];
1994
2063
  if (!tile) {
1995
- tile = this.getTile(id, tx, ty);
2064
+ tile = this.tilePool.getTile(id, tx, ty);
1996
2065
  this.extractState(tile);
1997
2066
  this.lookup[id] = tile;
1998
2067
  this.beforeTiles.push(tile);
1999
2068
  }
2000
2069
  }
2001
2070
  }
2071
+ return (didChange) => {
2072
+ if (!didChange) {
2073
+ const length = this.beforeTiles.length;
2074
+ for (let i = startIndex; i < length; i++) {
2075
+ let t = this.beforeTiles[i];
2076
+ if (t) {
2077
+ this.lookup[t.id] = void 0;
2078
+ this.tilePool.releaseTile(t);
2079
+ }
2080
+ }
2081
+ this.beforeTiles.length = startIndex;
2082
+ }
2083
+ return didChange;
2084
+ };
2085
+ }
2086
+ storeTileBeforeState(id, tx, ty) {
2087
+ let tile = this.lookup[id];
2088
+ let added = false;
2089
+ if (!tile) {
2090
+ tile = this.tilePool.getTile(id, tx, ty);
2091
+ this.extractState(tile);
2092
+ this.lookup[id] = tile;
2093
+ this.beforeTiles.push(tile);
2094
+ added = true;
2095
+ }
2096
+ return (didChange) => {
2097
+ if (!didChange && added) {
2098
+ this.beforeTiles.pop();
2099
+ this.lookup[id] = void 0;
2100
+ this.tilePool.releaseTile(tile);
2101
+ }
2102
+ return didChange;
2103
+ };
2002
2104
  }
2003
2105
  extractState(tile) {
2004
- let target = this.target;
2005
- let TILE_SIZE = this.config.tileSize;
2006
- let dst = tile.data32;
2007
- let src = target.data32;
2008
- let startX = tile.tx * TILE_SIZE;
2009
- let startY = tile.ty * TILE_SIZE;
2010
- let targetWidth = target.width;
2011
- let targetHeight = target.height;
2012
- let copyWidth = Math.max(0, Math.min(TILE_SIZE, targetWidth - startX));
2106
+ const target = this.config.target;
2107
+ const TILE_SIZE = this.config.tileSize;
2108
+ const dst = tile.data32;
2109
+ const src = target.data32;
2110
+ const startX = tile.tx * TILE_SIZE;
2111
+ const startY = tile.ty * TILE_SIZE;
2112
+ const targetWidth = target.width;
2113
+ const targetHeight = target.height;
2114
+ if (startX >= targetWidth || startX + TILE_SIZE <= 0 || startY >= targetHeight || startY + TILE_SIZE <= 0) {
2115
+ dst.fill(0);
2116
+ return;
2117
+ }
2118
+ let srcOffsetX = Math.max(0, -startX);
2119
+ let copyWidth = Math.max(0, Math.min(TILE_SIZE - srcOffsetX, targetWidth - Math.max(0, startX)));
2013
2120
  for (let ly = 0; ly < TILE_SIZE; ly++) {
2014
2121
  let globalY = startY + ly;
2015
2122
  let dstIndex = ly * TILE_SIZE;
@@ -2017,63 +2124,92 @@ var PixelAccumulator = class {
2017
2124
  dst.fill(0, dstIndex, dstIndex + TILE_SIZE);
2018
2125
  continue;
2019
2126
  }
2020
- let srcIndex = globalY * targetWidth + startX;
2127
+ let srcIndex = globalY * targetWidth + Math.max(0, startX);
2021
2128
  let rowData = src.subarray(srcIndex, srcIndex + copyWidth);
2022
- dst.set(rowData, dstIndex);
2023
- if (copyWidth < TILE_SIZE) {
2024
- dst.fill(0, dstIndex + copyWidth, dstIndex + TILE_SIZE);
2129
+ dst.set(rowData, dstIndex + srcOffsetX);
2130
+ if (srcOffsetX > 0) {
2131
+ dst.fill(0, dstIndex, dstIndex + srcOffsetX);
2132
+ }
2133
+ if (srcOffsetX + copyWidth < TILE_SIZE) {
2134
+ dst.fill(0, dstIndex + srcOffsetX + copyWidth, dstIndex + TILE_SIZE);
2025
2135
  }
2026
2136
  }
2027
2137
  }
2028
- extractAfterTiles() {
2029
- let afterTiles = [];
2030
- let length = this.beforeTiles.length;
2138
+ extractPatch() {
2139
+ const afterTiles = [];
2140
+ const length = this.beforeTiles.length;
2031
2141
  for (let i = 0; i < length; i++) {
2032
2142
  let beforeTile = this.beforeTiles[i];
2033
2143
  if (beforeTile) {
2034
- let afterTile = this.getTile(beforeTile.id, beforeTile.tx, beforeTile.ty);
2144
+ let afterTile = this.tilePool.getTile(beforeTile.id, beforeTile.tx, beforeTile.ty);
2035
2145
  this.extractState(afterTile);
2036
2146
  afterTiles.push(afterTile);
2037
2147
  }
2038
2148
  }
2039
- return afterTiles;
2040
- }
2041
- reset() {
2042
- this.lookup = [];
2149
+ const beforeTiles = this.beforeTiles;
2043
2150
  this.beforeTiles = [];
2151
+ this.lookup.length = 0;
2152
+ return {
2153
+ beforeTiles,
2154
+ afterTiles
2155
+ };
2156
+ }
2157
+ rollbackAfterError() {
2158
+ const target = this.config.target;
2159
+ const tileSize = this.config.tileSize;
2160
+ const length = this.beforeTiles.length;
2161
+ applyPatchTiles(target, this.beforeTiles, tileSize);
2162
+ for (let i = 0; i < length; i++) {
2163
+ let tile = this.beforeTiles[i];
2164
+ if (tile) {
2165
+ this.lookup[tile.id] = void 0;
2166
+ this.tilePool.releaseTile(tile);
2167
+ }
2168
+ }
2169
+ this.beforeTiles.length = 0;
2170
+ this.lookup.length = 0;
2044
2171
  }
2045
2172
  };
2046
2173
 
2047
2174
  // src/History/PixelEngineConfig.ts
2048
2175
  var PixelEngineConfig = class {
2049
2176
  tileSize;
2177
+ // pixelX = tileX << tileShift
2178
+ // pixelY = tileY << tileShift
2050
2179
  tileShift;
2051
2180
  tileMask;
2052
2181
  tileArea;
2053
- constructor(tileSize = 256) {
2182
+ target;
2183
+ targetColumns = 0;
2184
+ targetRows = 0;
2185
+ constructor(tileSize, target) {
2054
2186
  if ((tileSize & tileSize - 1) !== 0) {
2055
2187
  throw new Error("tileSize must be a power of 2");
2056
2188
  }
2057
2189
  this.tileSize = tileSize;
2058
- this.tileShift = Math.log2(tileSize);
2190
+ this.tileShift = 31 - Math.clz32(tileSize);
2059
2191
  this.tileMask = tileSize - 1;
2060
2192
  this.tileArea = tileSize * tileSize;
2193
+ this.target = target;
2194
+ this.targetColumns = target.width + this.tileMask >> this.tileShift;
2195
+ this.targetRows = target.height + this.tileMask >> this.tileShift;
2061
2196
  }
2062
2197
  };
2063
2198
 
2064
- // src/PixelData/applyAlphaMaskToPixelData.ts
2065
- function applyAlphaMaskToPixelData(dst, mask, opts = {}) {
2199
+ // src/PixelData/blendColorPixelData.ts
2200
+ function blendColorPixelData(dst, color, opts = {}) {
2066
2201
  const {
2067
2202
  x: targetX = 0,
2068
2203
  y: targetY = 0,
2069
2204
  w: width = dst.width,
2070
2205
  h: height = dst.height,
2071
2206
  alpha: globalAlpha = 255,
2072
- mx = 0,
2073
- my = 0,
2074
- invertMask = false
2207
+ blendFn = sourceOverPerfect
2075
2208
  } = opts;
2076
- if (globalAlpha === 0) return;
2209
+ if (globalAlpha === 0) return false;
2210
+ const baseSrcAlpha = color >>> 24;
2211
+ const isOverwrite = blendFn.isOverwrite || false;
2212
+ if (baseSrcAlpha === 0 && !isOverwrite) return false;
2077
2213
  let x = targetX;
2078
2214
  let y = targetY;
2079
2215
  let w = width;
@@ -2086,257 +2222,290 @@ function applyAlphaMaskToPixelData(dst, mask, opts = {}) {
2086
2222
  h += y;
2087
2223
  y = 0;
2088
2224
  }
2089
- w = Math.min(w, dst.width - x);
2090
- h = Math.min(h, dst.height - y);
2091
- if (w <= 0) return;
2092
- if (h <= 0) return;
2093
- const mPitch = mask.w;
2094
- if (mPitch <= 0) return;
2095
- const startX = mx + (x - targetX);
2096
- const startY = my + (y - targetY);
2097
- const sX0 = Math.max(0, startX);
2098
- const sY0 = Math.max(0, startY);
2099
- const sX1 = Math.min(mPitch, startX + w);
2100
- const sY1 = Math.min(mask.h, startY + h);
2101
- const finalW = sX1 - sX0;
2102
- const finalH = sY1 - sY0;
2103
- if (finalW <= 0) return;
2104
- if (finalH <= 0) return;
2105
- const xShift = sX0 - startX;
2106
- const yShift = sY0 - startY;
2225
+ const actualW = Math.min(w, dst.width - x);
2226
+ const actualH = Math.min(h, dst.height - y);
2227
+ if (actualW <= 0 || actualH <= 0) return false;
2228
+ let finalSrcColor = color;
2229
+ if (globalAlpha < 255) {
2230
+ const a = baseSrcAlpha * globalAlpha + 128 >> 8;
2231
+ if (a === 0 && !isOverwrite) return false;
2232
+ finalSrcColor = (color & 16777215 | a << 24) >>> 0;
2233
+ }
2107
2234
  const dst32 = dst.data32;
2108
2235
  const dw = dst.width;
2109
- const dStride = dw - finalW;
2110
- const mStride = mPitch - finalW;
2111
- const maskData = mask.data;
2112
- let dIdx = (y + yShift) * dw + (x + xShift);
2113
- let mIdx = sY0 * mPitch + sX0;
2114
- for (let iy = 0; iy < h; iy++) {
2115
- for (let ix = 0; ix < w; ix++) {
2116
- const mVal = maskData[mIdx];
2117
- const effectiveM = invertMask ? 255 - mVal : mVal;
2118
- let weight = 0;
2119
- if (effectiveM === 0) {
2120
- weight = 0;
2121
- } else if (effectiveM === 255) {
2122
- weight = globalAlpha;
2123
- } else if (globalAlpha === 255) {
2124
- weight = effectiveM;
2125
- } else {
2126
- weight = effectiveM * globalAlpha + 128 >> 8;
2127
- }
2128
- if (weight === 0) {
2129
- dst32[dIdx] = (dst32[dIdx] & 16777215) >>> 0;
2130
- } else if (weight !== 255) {
2131
- const d = dst32[dIdx];
2132
- const da = d >>> 24;
2133
- if (da !== 0) {
2134
- const finalAlpha = da === 255 ? weight : da * weight + 128 >> 8;
2135
- dst32[dIdx] = (d & 16777215 | finalAlpha << 24) >>> 0;
2136
- }
2236
+ let dIdx = y * dw + x | 0;
2237
+ const dStride = dw - actualW | 0;
2238
+ let didChange = false;
2239
+ for (let iy = 0; iy < actualH; iy++) {
2240
+ for (let ix = 0; ix < actualW; ix++) {
2241
+ const current = dst32[dIdx];
2242
+ const next = blendFn(finalSrcColor, current);
2243
+ if (current !== next) {
2244
+ dst32[dIdx] = next;
2245
+ didChange = true;
2137
2246
  }
2138
2247
  dIdx++;
2139
- mIdx++;
2140
2248
  }
2141
2249
  dIdx += dStride;
2142
- mIdx += mStride;
2143
2250
  }
2251
+ return didChange;
2144
2252
  }
2145
2253
 
2146
- // src/History/PixelMutator/mutatorApplyAlphaMask.ts
2147
- var defaults = {
2148
- applyAlphaMaskToPixelData
2149
- };
2150
- var mutatorApplyAlphaMask = ((writer, deps = defaults) => {
2254
+ // src/History/PixelMutator/mutatorBlendColor.ts
2255
+ var defaults2 = {
2256
+ blendColorPixelData
2257
+ };
2258
+ var mutatorBlendColor = ((writer, deps = defaults2) => {
2151
2259
  const {
2152
- applyAlphaMaskToPixelData: applyAlphaMaskToPixelData2 = defaults.applyAlphaMaskToPixelData
2260
+ blendColorPixelData: blendColorPixelData2 = defaults2.blendColorPixelData
2153
2261
  } = deps;
2154
2262
  return {
2155
- applyAlphaMask: (mask, opts = {}) => {
2156
- let target = writer.target;
2263
+ blendColor(color, opts = {}) {
2264
+ const target = writer.config.target;
2157
2265
  const {
2158
2266
  x = 0,
2159
2267
  y = 0,
2160
- w = writer.target.width,
2161
- h = writer.target.height
2268
+ w = target.width,
2269
+ h = target.height
2162
2270
  } = opts;
2163
- writer.accumulator.storeRegionBeforeState(x, y, w, h);
2164
- applyAlphaMaskToPixelData2(target, mask, opts);
2271
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2272
+ return didChange(blendColorPixelData2(target, color, opts));
2165
2273
  }
2166
2274
  };
2167
2275
  });
2168
2276
 
2169
- // src/PixelData/applyBinaryMaskToPixelData.ts
2170
- function applyBinaryMaskToPixelData(dst, mask, opts = {}) {
2277
+ // src/PixelData/blendPixel.ts
2278
+ function blendPixel(target, x, y, color, alpha = 255, blendFn = sourceOverPerfect) {
2279
+ if (alpha === 0) return false;
2280
+ let width = target.width;
2281
+ let height = target.height;
2282
+ if (x < 0 || x >= width || y < 0 || y >= height) return false;
2283
+ let srcAlpha = color >>> 24;
2284
+ let isOverwrite = blendFn.isOverwrite;
2285
+ if (srcAlpha === 0 && !isOverwrite) return false;
2286
+ let dst32 = target.data32;
2287
+ let index = y * width + x;
2288
+ let finalColor = color;
2289
+ if (alpha !== 255) {
2290
+ let finalAlpha = srcAlpha * alpha + 128 >> 8;
2291
+ if (finalAlpha === 0 && !isOverwrite) return false;
2292
+ finalColor = (color & 16777215 | finalAlpha << 24) >>> 0;
2293
+ }
2294
+ let current = dst32[index];
2295
+ let next = blendFn(finalColor, current);
2296
+ if (current !== next) {
2297
+ dst32[index] = next;
2298
+ return true;
2299
+ }
2300
+ return false;
2301
+ }
2302
+
2303
+ // src/History/PixelMutator/mutatorBlendPixel.ts
2304
+ var defaults3 = {
2305
+ blendPixel
2306
+ };
2307
+ var mutatorBlendPixel = ((writer, deps = defaults3) => {
2308
+ const {
2309
+ blendPixel: blendPixel2 = defaults3.blendPixel
2310
+ } = deps;
2311
+ return {
2312
+ blendPixel(x, y, color, alpha, blendFn) {
2313
+ const didChange = writer.accumulator.storePixelBeforeState(x, y);
2314
+ return didChange(blendPixel2(writer.config.target, x, y, color, alpha, blendFn));
2315
+ }
2316
+ };
2317
+ });
2318
+
2319
+ // src/PixelData/blendPixelData.ts
2320
+ function blendPixelData(dst, src, opts = {}) {
2171
2321
  const {
2172
2322
  x: targetX = 0,
2173
2323
  y: targetY = 0,
2174
- w: width = dst.width,
2175
- h: height = dst.height,
2324
+ sx: sourceX = 0,
2325
+ sy: sourceY = 0,
2326
+ w: width = src.width,
2327
+ h: height = src.height,
2176
2328
  alpha: globalAlpha = 255,
2177
- mx = 0,
2178
- my = 0,
2179
- invertMask = false
2329
+ blendFn = sourceOverPerfect
2180
2330
  } = opts;
2181
- if (globalAlpha === 0) return;
2331
+ if (globalAlpha === 0) return false;
2182
2332
  let x = targetX;
2183
2333
  let y = targetY;
2334
+ let sx = sourceX;
2335
+ let sy = sourceY;
2184
2336
  let w = width;
2185
2337
  let h = height;
2338
+ if (sx < 0) {
2339
+ x -= sx;
2340
+ w += sx;
2341
+ sx = 0;
2342
+ }
2343
+ if (sy < 0) {
2344
+ y -= sy;
2345
+ h += sy;
2346
+ sy = 0;
2347
+ }
2348
+ w = Math.min(w, src.width - sx);
2349
+ h = Math.min(h, src.height - sy);
2186
2350
  if (x < 0) {
2351
+ sx -= x;
2187
2352
  w += x;
2188
2353
  x = 0;
2189
2354
  }
2190
2355
  if (y < 0) {
2356
+ sy -= y;
2191
2357
  h += y;
2192
2358
  y = 0;
2193
2359
  }
2194
- w = Math.min(w, dst.width - x);
2195
- h = Math.min(h, dst.height - y);
2196
- if (w <= 0) return;
2197
- if (h <= 0) return;
2198
- const mPitch = mask.w;
2199
- if (mPitch <= 0) return;
2200
- const startX = mx + (x - targetX);
2201
- const startY = my + (y - targetY);
2202
- const sX0 = Math.max(0, startX);
2203
- const sY0 = Math.max(0, startY);
2204
- const sX1 = Math.min(mPitch, startX + w);
2205
- const sY1 = Math.min(mask.h, startY + h);
2206
- const finalW = sX1 - sX0;
2207
- const finalH = sY1 - sY0;
2208
- if (finalW <= 0) return;
2209
- if (finalH <= 0) return;
2210
- const xShift = sX0 - startX;
2211
- const yShift = sY0 - startY;
2360
+ const actualW = Math.min(w, dst.width - x);
2361
+ const actualH = Math.min(h, dst.height - y);
2362
+ if (actualW <= 0 || actualH <= 0) return false;
2212
2363
  const dst32 = dst.data32;
2364
+ const src32 = src.data32;
2213
2365
  const dw = dst.width;
2214
- const dStride = dw - finalW;
2215
- const mStride = mPitch - finalW;
2216
- const maskData = mask.data;
2217
- let dIdx = (y + yShift) * dw + (x + xShift);
2218
- let mIdx = sY0 * mPitch + sX0;
2219
- for (let iy = 0; iy < h; iy++) {
2220
- for (let ix = 0; ix < w; ix++) {
2221
- const mVal = maskData[mIdx];
2222
- const isMaskedOut = invertMask ? mVal !== 0 : mVal === 0;
2223
- if (isMaskedOut) {
2224
- dst32[dIdx] = (dst32[dIdx] & 16777215) >>> 0;
2225
- } else if (globalAlpha !== 255) {
2226
- const d = dst32[dIdx];
2227
- const da = d >>> 24;
2228
- if (da !== 0) {
2229
- const finalAlpha = da === 255 ? globalAlpha : da * globalAlpha + 128 >> 8;
2230
- dst32[dIdx] = (d & 16777215 | finalAlpha << 24) >>> 0;
2366
+ const sw = src.width;
2367
+ let dIdx = y * dw + x | 0;
2368
+ let sIdx = sy * sw + sx | 0;
2369
+ const dStride = dw - actualW | 0;
2370
+ const sStride = sw - actualW | 0;
2371
+ const isOpaque = globalAlpha === 255;
2372
+ const isOverwrite = blendFn.isOverwrite;
2373
+ let didChange = false;
2374
+ for (let iy = 0; iy < actualH; iy++) {
2375
+ for (let ix = 0; ix < actualW; ix++) {
2376
+ const srcCol = src32[sIdx];
2377
+ const srcAlpha = srcCol >>> 24;
2378
+ if (srcAlpha === 0 && !isOverwrite) {
2379
+ dIdx++;
2380
+ sIdx++;
2381
+ continue;
2382
+ }
2383
+ let finalCol = srcCol;
2384
+ if (!isOpaque) {
2385
+ const a = srcAlpha * globalAlpha + 128 >> 8;
2386
+ if (a === 0 && !isOverwrite) {
2387
+ dIdx++;
2388
+ sIdx++;
2389
+ continue;
2231
2390
  }
2391
+ finalCol = (srcCol & 16777215 | a << 24) >>> 0;
2392
+ }
2393
+ const current = dst32[dIdx];
2394
+ const next = blendFn(finalCol, dst32[dIdx]);
2395
+ if (current !== next) {
2396
+ dst32[dIdx] = next;
2397
+ didChange = true;
2232
2398
  }
2233
2399
  dIdx++;
2234
- mIdx++;
2400
+ sIdx++;
2235
2401
  }
2236
2402
  dIdx += dStride;
2237
- mIdx += mStride;
2403
+ sIdx += sStride;
2238
2404
  }
2405
+ return didChange;
2239
2406
  }
2240
2407
 
2241
- // src/History/PixelMutator/mutatorApplyBinaryMask.ts
2242
- var defaults2 = {
2243
- applyBinaryMaskToPixelData
2408
+ // src/History/PixelMutator/mutatorBlendPixelData.ts
2409
+ var defaults4 = {
2410
+ blendPixelData
2244
2411
  };
2245
- var mutatorApplyBinaryMask = ((writer, deps = defaults2) => {
2412
+ var mutatorBlendPixelData = ((writer, deps = defaults4) => {
2246
2413
  const {
2247
- applyBinaryMaskToPixelData: applyBinaryMaskToPixelData2 = defaults2.applyBinaryMaskToPixelData
2414
+ blendPixelData: blendPixelData2 = defaults4.blendPixelData
2248
2415
  } = deps;
2249
2416
  return {
2250
- applyBinaryMask: (mask, opts = {}) => {
2251
- let target = writer.target;
2417
+ blendPixelData(src, opts = {}) {
2252
2418
  const {
2253
2419
  x = 0,
2254
2420
  y = 0,
2255
- w = writer.target.width,
2256
- h = writer.target.height
2421
+ w = src.width,
2422
+ h = src.height
2257
2423
  } = opts;
2258
- writer.accumulator.storeRegionBeforeState(x, y, w, h);
2259
- applyBinaryMaskToPixelData2(target, mask, opts);
2424
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2425
+ return didChange(blendPixelData2(writer.config.target, src, opts));
2260
2426
  }
2261
2427
  };
2262
2428
  });
2263
2429
 
2264
- // src/Rect/getCircleBrushOrPencilBounds.ts
2265
- function getCircleBrushOrPencilBounds(centerX, centerY, brushSize, targetWidth, targetHeight, out) {
2266
- const r = brushSize / 2;
2267
- const minOffset = -Math.ceil(r - 0.5);
2268
- const maxOffset = Math.floor(r - 0.5);
2269
- const startX = Math.floor(centerX + minOffset);
2270
- const startY = Math.floor(centerY + minOffset);
2271
- const endX = Math.floor(centerX + maxOffset) + 1;
2272
- const endY = Math.floor(centerY + maxOffset) + 1;
2273
- const res = out ?? {
2274
- x: 0,
2275
- y: 0,
2276
- w: 0,
2277
- h: 0
2278
- };
2279
- const cStartX = Math.max(0, startX);
2280
- const cStartY = Math.max(0, startY);
2281
- const cEndX = Math.min(targetWidth, endX);
2282
- const cEndY = Math.min(targetHeight, endY);
2283
- const w = cEndX - cStartX;
2284
- const h = cEndY - cStartY;
2285
- res.x = cStartX;
2286
- res.y = cStartY;
2287
- res.w = w < 0 ? 0 : w;
2288
- res.h = h < 0 ? 0 : h;
2289
- return res;
2290
- }
2291
-
2292
- // src/PixelData/blendColorPixelDataAlphaMask.ts
2293
- function blendColorPixelDataAlphaMask(dst, color, mask, opts = {}) {
2294
- const targetX = opts.x ?? 0;
2295
- const targetY = opts.y ?? 0;
2296
- const w = opts.w ?? mask.w;
2297
- const h = opts.h ?? mask.h;
2298
- const globalAlpha = opts.alpha ?? 255;
2299
- const blendFn = opts.blendFn ?? sourceOverPerfect;
2300
- const mx = opts.mx ?? 0;
2301
- const my = opts.my ?? 0;
2302
- const invertMask = opts.invertMask ?? false;
2303
- if (globalAlpha === 0) return;
2304
- const baseSrcAlpha = color >>> 24;
2305
- const isOverwrite = blendFn.isOverwrite || false;
2306
- if (baseSrcAlpha === 0 && !isOverwrite) return;
2430
+ // src/PixelData/blendPixelDataAlphaMask.ts
2431
+ function blendPixelDataAlphaMask(dst, src, alphaMask, opts = {}) {
2432
+ const {
2433
+ x: targetX = 0,
2434
+ y: targetY = 0,
2435
+ sx: sourceX = 0,
2436
+ sy: sourceY = 0,
2437
+ w: width = src.width,
2438
+ h: height = src.height,
2439
+ alpha: globalAlpha = 255,
2440
+ blendFn = sourceOverPerfect,
2441
+ mx = 0,
2442
+ my = 0,
2443
+ invertMask = false
2444
+ } = opts;
2445
+ if (globalAlpha === 0) return false;
2307
2446
  let x = targetX;
2308
2447
  let y = targetY;
2309
- let actualW = w;
2310
- let actualH = h;
2448
+ let sx = sourceX;
2449
+ let sy = sourceY;
2450
+ let w = width;
2451
+ let h = height;
2452
+ if (sx < 0) {
2453
+ x -= sx;
2454
+ w += sx;
2455
+ sx = 0;
2456
+ }
2457
+ if (sy < 0) {
2458
+ y -= sy;
2459
+ h += sy;
2460
+ sy = 0;
2461
+ }
2462
+ w = Math.min(w, src.width - sx);
2463
+ h = Math.min(h, src.height - sy);
2311
2464
  if (x < 0) {
2312
- actualW += x;
2465
+ sx -= x;
2466
+ w += x;
2313
2467
  x = 0;
2314
2468
  }
2315
2469
  if (y < 0) {
2316
- actualH += y;
2470
+ sy -= y;
2471
+ h += y;
2317
2472
  y = 0;
2318
2473
  }
2319
- actualW = Math.min(actualW, dst.width - x);
2320
- actualH = Math.min(actualH, dst.height - y);
2321
- if (actualW <= 0 || actualH <= 0) return;
2474
+ const actualW = Math.min(w, dst.width - x);
2475
+ const actualH = Math.min(h, dst.height - y);
2476
+ if (actualW <= 0 || actualH <= 0) return false;
2477
+ const dw = dst.width;
2478
+ const sw = src.width;
2479
+ const mPitch = alphaMask.w;
2480
+ const maskData = alphaMask.data;
2322
2481
  const dx = x - targetX | 0;
2323
2482
  const dy = y - targetY | 0;
2324
2483
  const dst32 = dst.data32;
2325
- const dw = dst.width;
2326
- const mPitch = mask.w;
2327
- const maskData = mask.data;
2484
+ const src32 = src.data32;
2328
2485
  let dIdx = y * dw + x | 0;
2486
+ let sIdx = sy * sw + sx | 0;
2329
2487
  let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
2330
2488
  const dStride = dw - actualW | 0;
2331
- let mStride = mPitch - actualW | 0;
2489
+ const sStride = sw - actualW | 0;
2490
+ const mStride = mPitch - actualW | 0;
2332
2491
  const isOpaque = globalAlpha === 255;
2333
- const colorRGB = color & 16777215;
2492
+ const isOverwrite = blendFn.isOverwrite || false;
2493
+ let didChange = false;
2334
2494
  for (let iy = 0; iy < actualH; iy++) {
2335
2495
  for (let ix = 0; ix < actualW; ix++) {
2336
2496
  const mVal = maskData[mIdx];
2337
2497
  const effM = invertMask ? 255 - mVal : mVal;
2338
2498
  if (effM === 0) {
2339
2499
  dIdx++;
2500
+ sIdx++;
2501
+ mIdx++;
2502
+ continue;
2503
+ }
2504
+ const srcCol = src32[sIdx];
2505
+ const srcAlpha = srcCol >>> 24;
2506
+ if (srcAlpha === 0 && !isOverwrite) {
2507
+ dIdx++;
2508
+ sIdx++;
2340
2509
  mIdx++;
2341
2510
  continue;
2342
2511
  }
@@ -2348,779 +2517,915 @@ function blendColorPixelDataAlphaMask(dst, color, mask, opts = {}) {
2348
2517
  }
2349
2518
  if (weight === 0) {
2350
2519
  dIdx++;
2520
+ sIdx++;
2351
2521
  mIdx++;
2352
2522
  continue;
2353
2523
  }
2354
- let finalCol = color;
2524
+ let finalCol = srcCol;
2355
2525
  if (weight < 255) {
2356
- const a = baseSrcAlpha * weight + 128 >> 8;
2526
+ const a = srcAlpha * weight + 128 >> 8;
2357
2527
  if (a === 0 && !isOverwrite) {
2358
2528
  dIdx++;
2529
+ sIdx++;
2359
2530
  mIdx++;
2360
2531
  continue;
2361
2532
  }
2362
- finalCol = (colorRGB | a << 24) >>> 0;
2533
+ finalCol = (srcCol & 16777215 | a << 24) >>> 0;
2534
+ }
2535
+ const current = dst32[dIdx];
2536
+ const next = blendFn(finalCol, dst32[dIdx]);
2537
+ if (current !== next) {
2538
+ dst32[dIdx] = next;
2539
+ didChange = true;
2363
2540
  }
2364
- dst32[dIdx] = blendFn(finalCol, dst32[dIdx]);
2365
2541
  dIdx++;
2542
+ sIdx++;
2366
2543
  mIdx++;
2367
2544
  }
2368
2545
  dIdx += dStride;
2546
+ sIdx += sStride;
2369
2547
  mIdx += mStride;
2370
2548
  }
2549
+ return didChange;
2371
2550
  }
2372
2551
 
2373
- // src/PixelData/blendColorPixelDataBinaryMask.ts
2374
- function blendColorPixelDataBinaryMask(dst, color, mask, opts = {}) {
2375
- const targetX = opts.x ?? 0;
2376
- const targetY = opts.y ?? 0;
2377
- let w = opts.w ?? mask.w;
2378
- let h = opts.h ?? mask.h;
2379
- const globalAlpha = opts.alpha ?? 255;
2380
- const blendFn = opts.blendFn ?? sourceOverPerfect;
2381
- const mx = opts.mx ?? 0;
2382
- const my = opts.my ?? 0;
2383
- const invertMask = opts.invertMask ?? false;
2384
- if (globalAlpha === 0) return;
2385
- const baseSrcAlpha = color >>> 24;
2386
- const isOverwrite = blendFn.isOverwrite || false;
2387
- if (baseSrcAlpha === 0 && !isOverwrite) return;
2388
- let x = targetX;
2389
- let y = targetY;
2390
- if (x < 0) {
2391
- w += x;
2392
- x = 0;
2393
- }
2394
- if (y < 0) {
2395
- h += y;
2396
- y = 0;
2397
- }
2398
- const actualW = Math.min(w, dst.width - x);
2399
- const actualH = Math.min(h, dst.height - y);
2400
- if (actualW <= 0 || actualH <= 0) return;
2401
- let baseColorWithGlobalAlpha = color;
2402
- if (globalAlpha < 255) {
2403
- const a = baseSrcAlpha * globalAlpha + 128 >> 8;
2404
- if (a === 0 && !isOverwrite) return;
2405
- baseColorWithGlobalAlpha = (color & 16777215 | a << 24) >>> 0;
2406
- }
2407
- const dx = x - targetX | 0;
2408
- const dy = y - targetY | 0;
2552
+ // src/History/PixelMutator/mutatorBlendPixelDataAlphaMask.ts
2553
+ var defaults5 = {
2554
+ blendPixelDataAlphaMask
2555
+ };
2556
+ var mutatorBlendPixelDataAlphaMask = ((writer, deps = defaults5) => {
2557
+ const {
2558
+ blendPixelDataAlphaMask: blendPixelDataAlphaMask2 = defaults5.blendPixelDataAlphaMask
2559
+ } = deps;
2560
+ return {
2561
+ blendPixelDataAlphaMask(src, mask, opts = {}) {
2562
+ const x = opts.x ?? 0;
2563
+ const y = opts.y ?? 0;
2564
+ const w = opts.w ?? src.width;
2565
+ const h = opts.h ?? src.height;
2566
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2567
+ return didChange(blendPixelDataAlphaMask2(writer.config.target, src, mask, opts));
2568
+ }
2569
+ };
2570
+ });
2571
+
2572
+ // src/PixelData/blendPixelDataBinaryMask.ts
2573
+ function blendPixelDataBinaryMask(dst, src, binaryMask, opts = {}) {
2574
+ const {
2575
+ x: targetX = 0,
2576
+ y: targetY = 0,
2577
+ sx: sourceX = 0,
2578
+ sy: sourceY = 0,
2579
+ w: width = src.width,
2580
+ h: height = src.height,
2581
+ alpha: globalAlpha = 255,
2582
+ blendFn = sourceOverPerfect,
2583
+ mx = 0,
2584
+ my = 0,
2585
+ invertMask = false
2586
+ } = opts;
2587
+ if (globalAlpha === 0) return false;
2588
+ let x = targetX;
2589
+ let y = targetY;
2590
+ let sx = sourceX;
2591
+ let sy = sourceY;
2592
+ let w = width;
2593
+ let h = height;
2594
+ if (sx < 0) {
2595
+ x -= sx;
2596
+ w += sx;
2597
+ sx = 0;
2598
+ }
2599
+ if (sy < 0) {
2600
+ y -= sy;
2601
+ h += sy;
2602
+ sy = 0;
2603
+ }
2604
+ w = Math.min(w, src.width - sx);
2605
+ h = Math.min(h, src.height - sy);
2606
+ if (x < 0) {
2607
+ sx -= x;
2608
+ w += x;
2609
+ x = 0;
2610
+ }
2611
+ if (y < 0) {
2612
+ sy -= y;
2613
+ h += y;
2614
+ y = 0;
2615
+ }
2616
+ const actualW = Math.min(w, dst.width - x);
2617
+ const actualH = Math.min(h, dst.height - y);
2618
+ if (actualW <= 0 || actualH <= 0) return false;
2619
+ const dx = x - targetX | 0;
2620
+ const dy = y - targetY | 0;
2409
2621
  const dst32 = dst.data32;
2622
+ const src32 = src.data32;
2410
2623
  const dw = dst.width;
2411
- const mPitch = mask.w;
2412
- const maskData = mask.data;
2624
+ const sw = src.width;
2625
+ const mPitch = binaryMask.w;
2626
+ const maskData = binaryMask.data;
2413
2627
  let dIdx = y * dw + x | 0;
2628
+ let sIdx = sy * sw + sx | 0;
2414
2629
  let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
2415
2630
  const dStride = dw - actualW | 0;
2631
+ const sStride = sw - actualW | 0;
2416
2632
  const mStride = mPitch - actualW | 0;
2417
2633
  const skipVal = invertMask ? 1 : 0;
2634
+ const isOpaque = globalAlpha === 255;
2635
+ const isOverwrite = blendFn.isOverwrite || false;
2636
+ let didChange = false;
2418
2637
  for (let iy = 0; iy < actualH; iy++) {
2419
2638
  for (let ix = 0; ix < actualW; ix++) {
2420
2639
  if (maskData[mIdx] === skipVal) {
2421
2640
  dIdx++;
2641
+ sIdx++;
2642
+ mIdx++;
2643
+ continue;
2644
+ }
2645
+ const srcCol = src32[sIdx];
2646
+ const srcAlpha = srcCol >>> 24;
2647
+ if (srcAlpha === 0 && !isOverwrite) {
2648
+ dIdx++;
2649
+ sIdx++;
2422
2650
  mIdx++;
2423
2651
  continue;
2424
2652
  }
2425
- dst32[dIdx] = blendFn(baseColorWithGlobalAlpha, dst32[dIdx]);
2653
+ let finalCol = srcCol;
2654
+ if (!isOpaque) {
2655
+ const a = srcAlpha * globalAlpha + 128 >> 8;
2656
+ if (a === 0 && !isOverwrite) {
2657
+ dIdx++;
2658
+ sIdx++;
2659
+ mIdx++;
2660
+ continue;
2661
+ }
2662
+ finalCol = (srcCol & 16777215 | a << 24) >>> 0;
2663
+ }
2664
+ const current = dst32[dIdx];
2665
+ const next = blendFn(finalCol, dst32[dIdx]);
2666
+ if (current !== next) {
2667
+ dst32[dIdx] = next;
2668
+ didChange = true;
2669
+ }
2426
2670
  dIdx++;
2671
+ sIdx++;
2427
2672
  mIdx++;
2428
2673
  }
2429
2674
  dIdx += dStride;
2675
+ sIdx += sStride;
2430
2676
  mIdx += mStride;
2431
2677
  }
2678
+ return didChange;
2432
2679
  }
2433
2680
 
2434
- // src/PixelData/applyCircleBrushToPixelData.ts
2435
- function applyCircleBrushToPixelData(target, color, centerX, centerY, brush, alpha = 255, blendFn = sourceOverPerfect, scratchOptions = {}, bounds) {
2436
- const b = bounds ?? getCircleBrushOrPencilBounds(centerX, centerY, brush.size, target.width, target.height);
2437
- if (b.w <= 0 || b.h <= 0) return;
2438
- const unclippedStartX = Math.floor(centerX + brush.minOffset);
2439
- const unclippedStartY = Math.floor(centerY + brush.minOffset);
2440
- const ix = Math.max(unclippedStartX, b.x);
2441
- const iy = Math.max(unclippedStartY, b.y);
2442
- const ir = Math.min(unclippedStartX + brush.w, b.x + b.w);
2443
- const ib = Math.min(unclippedStartY + brush.h, b.y + b.h);
2444
- const iw = ir - ix;
2445
- const ih = ib - iy;
2446
- if (iw <= 0 || ih <= 0) return;
2447
- scratchOptions.x = ix;
2448
- scratchOptions.y = iy;
2449
- scratchOptions.w = iw;
2450
- scratchOptions.h = ih;
2451
- scratchOptions.mx = ix - unclippedStartX;
2452
- scratchOptions.my = iy - unclippedStartY;
2453
- scratchOptions.alpha = alpha;
2454
- scratchOptions.blendFn = blendFn;
2455
- if (brush.type === 0 /* ALPHA */) {
2456
- blendColorPixelDataAlphaMask(target, color, brush, scratchOptions);
2457
- }
2458
- if (brush.type === 1 /* BINARY */) {
2459
- blendColorPixelDataBinaryMask(target, color, brush, scratchOptions);
2460
- }
2461
- }
2462
-
2463
- // src/History/PixelMutator/mutatorApplyCircleBrush.ts
2464
- var defaults3 = {
2465
- applyCircleBrushToPixelData,
2466
- getCircleBrushOrPencilBounds
2681
+ // src/History/PixelMutator/mutatorBlendPixelDataBinaryMask.ts
2682
+ var defaults6 = {
2683
+ blendPixelDataBinaryMask
2467
2684
  };
2468
- var mutatorApplyCircleBrush = ((writer, deps = defaults3) => {
2685
+ var mutatorBlendPixelDataBinaryMask = ((writer, deps = defaults6) => {
2469
2686
  const {
2470
- applyCircleBrushToPixelData: applyCircleBrushToPixelData2 = defaults3.applyCircleBrushToPixelData,
2471
- getCircleBrushOrPencilBounds: getCircleBrushOrPencilBounds2 = defaults3.getCircleBrushOrPencilBounds
2687
+ blendPixelDataBinaryMask: blendPixelDataBinaryMask2 = defaults6.blendPixelDataBinaryMask
2472
2688
  } = deps;
2473
- const boundsOut = {
2474
- x: 0,
2475
- y: 0,
2476
- w: 0,
2477
- h: 0
2478
- };
2479
- const blendColorPixelOptions = {
2480
- alpha: 255,
2481
- blendFn: sourceOverPerfect,
2482
- x: 0,
2483
- y: 0,
2484
- w: 0,
2485
- h: 0
2486
- };
2487
2689
  return {
2488
- applyCircleBrush(color, centerX, centerY, brush, alpha = 255, blendFn) {
2489
- const bounds = getCircleBrushOrPencilBounds2(centerX, centerY, brush.size, writer.target.width, writer.target.height, boundsOut);
2490
- const {
2491
- x,
2492
- y,
2493
- w,
2494
- h
2495
- } = bounds;
2496
- writer.accumulator.storeRegionBeforeState(x, y, w, h);
2497
- applyCircleBrushToPixelData2(writer.target, color, centerX, centerY, brush, alpha, blendFn, blendColorPixelOptions, bounds);
2690
+ blendPixelDataBinaryMask(src, mask, opts = {}) {
2691
+ const x = opts.x ?? 0;
2692
+ const y = opts.y ?? 0;
2693
+ const w = opts.w ?? src.width;
2694
+ const h = opts.h ?? src.height;
2695
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2696
+ return didChange(blendPixelDataBinaryMask2(writer.config.target, src, mask, opts));
2498
2697
  }
2499
2698
  };
2500
2699
  });
2501
2700
 
2502
- // src/Algorithm/forEachLinePoint.ts
2503
- function forEachLinePoint(x0, y0, x1, y1, callback) {
2504
- const dx = x1 - x0;
2505
- const dy = y1 - y0;
2506
- const steps = Math.max(Math.abs(dx), Math.abs(dy));
2507
- if (steps === 0) {
2508
- callback(x0, y0);
2701
+ // src/PixelData/fillPixelDataFast.ts
2702
+ var SCRATCH_RECT = makeClippedRect();
2703
+ function fillPixelDataFast(dst, color, _x, _y, _w, _h) {
2704
+ let x;
2705
+ let y;
2706
+ let w;
2707
+ let h;
2708
+ if (typeof _x === "object") {
2709
+ x = _x.x ?? 0;
2710
+ y = _x.y ?? 0;
2711
+ w = _x.w ?? dst.width;
2712
+ h = _x.h ?? dst.height;
2713
+ } else if (typeof _x === "number") {
2714
+ x = _x;
2715
+ y = _y;
2716
+ w = _w;
2717
+ h = _h;
2718
+ } else {
2719
+ x = 0;
2720
+ y = 0;
2721
+ w = dst.width;
2722
+ h = dst.height;
2723
+ }
2724
+ const clip = resolveRectClipping(x, y, w, h, dst.width, dst.height, SCRATCH_RECT);
2725
+ if (!clip.inBounds) return;
2726
+ const {
2727
+ x: finalX,
2728
+ y: finalY,
2729
+ w: actualW,
2730
+ h: actualH
2731
+ } = clip;
2732
+ const dst32 = dst.data32;
2733
+ const dw = dst.width;
2734
+ if (actualW === dw && actualH === dst.height && finalX === 0 && finalY === 0) {
2735
+ dst32.fill(color);
2509
2736
  return;
2510
2737
  }
2511
- const xInc = dx / steps;
2512
- const yInc = dy / steps;
2513
- let curX = x0;
2514
- let curY = y0;
2515
- for (let i = 0; i <= steps; i++) {
2516
- callback(curX, curY);
2517
- curX += xInc;
2518
- curY += yInc;
2738
+ for (let iy = 0; iy < actualH; iy++) {
2739
+ const start = (finalY + iy) * dw + finalX;
2740
+ const end = start + actualW;
2741
+ dst32.fill(color, start, end);
2519
2742
  }
2520
2743
  }
2521
2744
 
2522
- // src/Rect/getCircleBrushOrPencilStrokeBounds.ts
2523
- function getCircleBrushOrPencilStrokeBounds(x0, y0, x1, y1, brushSize, result) {
2524
- const r = Math.ceil(brushSize / 2);
2525
- const minX = Math.min(x0, x1) - r;
2526
- const minY = Math.min(y0, y1) - r;
2527
- const maxX = Math.max(x0, x1) + r;
2528
- const maxY = Math.max(x0, y1) + r;
2529
- result.x = Math.floor(minX);
2530
- result.y = Math.floor(minY);
2531
- result.w = Math.ceil(maxX - minX);
2532
- result.h = Math.ceil(maxY - minY);
2533
- return result;
2534
- }
2535
-
2536
- // src/History/PixelMutator/mutatorApplyCircleBrushStroke.ts
2537
- var defaults4 = {
2538
- forEachLinePoint,
2539
- blendColorPixelDataAlphaMask,
2540
- getCircleBrushOrPencilBounds,
2541
- getCircleBrushOrPencilStrokeBounds
2745
+ // src/History/PixelMutator/mutatorClear.ts
2746
+ var defaults7 = {
2747
+ fillPixelData: fillPixelDataFast
2542
2748
  };
2543
- var mutatorApplyCircleBrushStroke = ((writer, deps = defaults4) => {
2749
+ var mutatorClear = ((writer, deps = defaults7) => {
2544
2750
  const {
2545
- forEachLinePoint: forEachLinePoint2 = defaults4.forEachLinePoint,
2546
- blendColorPixelDataAlphaMask: blendColorPixelDataAlphaMask2 = defaults4.blendColorPixelDataAlphaMask,
2547
- getCircleBrushOrPencilBounds: getCircleBrushOrPencilBounds2 = defaults4.getCircleBrushOrPencilBounds,
2548
- getCircleBrushOrPencilStrokeBounds: getCircleBrushOrPencilStrokeBounds2 = defaults4.getCircleBrushOrPencilStrokeBounds
2751
+ fillPixelData: fillPixelData2 = defaults7.fillPixelData
2549
2752
  } = deps;
2550
- const strokeBoundsOut = {
2551
- x: 0,
2552
- y: 0,
2553
- w: 0,
2554
- h: 0
2555
- };
2556
- const circleBrushBounds = {
2557
- x: 0,
2558
- y: 0,
2559
- w: 0,
2560
- h: 0
2561
- };
2562
- const blendColorPixelOptions = {
2563
- alpha: 255,
2564
- blendFn: sourceOverPerfect,
2565
- x: 0,
2566
- y: 0,
2567
- w: 0,
2568
- h: 0
2569
- };
2570
- const mask = {
2571
- type: 0 /* ALPHA */,
2572
- data: null,
2573
- w: 0,
2574
- h: 0
2575
- };
2576
2753
  return {
2577
- applyCircleBrushStroke(color, x0, y0, x1, y1, brush, alpha = 255, blendFn = sourceOverPerfect) {
2578
- const brushSize = brush.size;
2579
- const {
2580
- x: bx,
2581
- y: by,
2582
- w: bw,
2583
- h: bh
2584
- } = getCircleBrushOrPencilStrokeBounds2(x0, y0, x1, y1, brushSize, strokeBoundsOut);
2585
- if (bw <= 0 || bh <= 0) return;
2586
- mask.data = new Uint8Array(bw * bh);
2587
- mask.w = bw;
2588
- mask.h = bh;
2589
- const maskData = mask.data;
2590
- const brushData = brush.data;
2591
- const minOffset = brush.minOffset;
2592
- const targetWidth = writer.target.width;
2593
- const targetHeight = writer.target.height;
2594
- forEachLinePoint2(x0, y0, x1, y1, (px, py) => {
2595
- const {
2596
- x: cbx,
2597
- y: cby,
2598
- w: cbw,
2599
- h: cbh
2600
- } = getCircleBrushOrPencilBounds2(px, py, brushSize, targetWidth, targetHeight, circleBrushBounds);
2601
- writer.accumulator.storeRegionBeforeState(cbx, cby, cbw, cbh);
2602
- const startX = Math.max(bx, cbx);
2603
- const startY = Math.max(by, cby);
2604
- const endX = Math.min(bx + bw, cbx + cbw);
2605
- const endY = Math.min(by + bh, cby + cbh);
2606
- const unclippedStartX = Math.floor(px + minOffset);
2607
- const unclippedStartY = Math.floor(py + minOffset);
2608
- for (let my = startY; my < endY; my++) {
2609
- const strokeMaskY = my - by;
2610
- const strokeMaskRowOffset = strokeMaskY * bw;
2611
- const brushY = my - unclippedStartY;
2612
- const brushRowOffset = brushY * brushSize;
2613
- for (let mx = startX; mx < endX; mx++) {
2614
- const brushX = mx - unclippedStartX;
2615
- const brushVal = brushData[brushRowOffset + brushX];
2616
- if (brushVal > 0) {
2617
- const strokeMaskIdx = strokeMaskRowOffset + (mx - bx);
2618
- if (brushVal > maskData[strokeMaskIdx]) {
2619
- maskData[strokeMaskIdx] = brushVal;
2620
- }
2621
- }
2622
- }
2623
- }
2624
- });
2625
- blendColorPixelOptions.blendFn = blendFn;
2626
- blendColorPixelOptions.alpha = alpha;
2627
- blendColorPixelOptions.x = bx;
2628
- blendColorPixelOptions.y = by;
2629
- blendColorPixelOptions.w = bw;
2630
- blendColorPixelOptions.h = bh;
2631
- blendColorPixelDataAlphaMask2(writer.target, color, mask, blendColorPixelOptions);
2754
+ clear(rect = {}) {
2755
+ const target = writer.config.target;
2756
+ const x = rect.x ?? 0;
2757
+ const y = rect.y ?? 0;
2758
+ const w = rect.w ?? target.width;
2759
+ const h = rect.h ?? target.height;
2760
+ writer.accumulator.storeRegionBeforeState(x, y, w, h);
2761
+ fillPixelData2(target, 0, x, y, w, h);
2632
2762
  }
2633
2763
  };
2634
2764
  });
2635
2765
 
2636
- // src/History/PixelMutator/mutatorApplyCirclePencil.ts
2637
- var defaults5 = {
2638
- applyCircleBrushToPixelData,
2639
- getCircleBrushOrPencilBounds
2766
+ // src/PixelData/fillPixelData.ts
2767
+ var SCRATCH_RECT2 = makeClippedRect();
2768
+ function fillPixelData(dst, color, _x, _y, _w, _h) {
2769
+ let x;
2770
+ let y;
2771
+ let w;
2772
+ let h;
2773
+ if (typeof _x === "object") {
2774
+ x = _x.x ?? 0;
2775
+ y = _x.y ?? 0;
2776
+ w = _x.w ?? dst.width;
2777
+ h = _x.h ?? dst.height;
2778
+ } else if (typeof _x === "number") {
2779
+ x = _x;
2780
+ y = _y;
2781
+ w = _w;
2782
+ h = _h;
2783
+ } else {
2784
+ x = 0;
2785
+ y = 0;
2786
+ w = dst.width;
2787
+ h = dst.height;
2788
+ }
2789
+ const clip = resolveRectClipping(x, y, w, h, dst.width, dst.height, SCRATCH_RECT2);
2790
+ if (!clip.inBounds) return false;
2791
+ const {
2792
+ x: finalX,
2793
+ y: finalY,
2794
+ w: actualW,
2795
+ h: actualH
2796
+ } = clip;
2797
+ const dst32 = dst.data32;
2798
+ const dw = dst.width;
2799
+ let hasChanged = false;
2800
+ for (let iy = 0; iy < actualH; iy++) {
2801
+ const rowOffset = (finalY + iy) * dw;
2802
+ const start = rowOffset + finalX;
2803
+ const end = start + actualW;
2804
+ for (let i = start; i < end; i++) {
2805
+ if (dst32[i] !== color) {
2806
+ dst32[i] = color;
2807
+ hasChanged = true;
2808
+ }
2809
+ }
2810
+ }
2811
+ return hasChanged;
2812
+ }
2813
+
2814
+ // src/History/PixelMutator/mutatorFill.ts
2815
+ var defaults8 = {
2816
+ fillPixelData
2640
2817
  };
2641
- var mutatorApplyCirclePencil = ((writer, deps = defaults5) => {
2818
+ var mutatorFill = ((writer, deps = defaults8) => {
2642
2819
  const {
2643
- applyCircleBrushToPixelData: applyCircleBrushToPixelData2 = defaults5.applyCircleBrushToPixelData,
2644
- getCircleBrushOrPencilBounds: getCircleBrushOrPencilBounds2 = defaults5.getCircleBrushOrPencilBounds
2820
+ fillPixelData: fillPixelData2 = defaults8.fillPixelData
2645
2821
  } = deps;
2646
- const boundsOut = {
2647
- x: 0,
2648
- y: 0,
2649
- w: 0,
2650
- h: 0
2651
- };
2652
2822
  return {
2653
- applyCirclePencil(color, centerX, centerY, brush, alpha = 255, blendFn) {
2654
- const bounds = getCircleBrushOrPencilBounds2(centerX, centerY, brush.size, writer.target.width, writer.target.height, boundsOut);
2655
- const {
2656
- x,
2657
- y,
2658
- w,
2659
- h
2660
- } = bounds;
2661
- writer.accumulator.storeRegionBeforeState(x, y, w, h);
2662
- applyCircleBrushToPixelData2(writer.target, color, centerX, centerY, brush, alpha, blendFn, bounds);
2823
+ fill(color, x = 0, y = 0, w = writer.config.target.width, h = writer.config.target.height) {
2824
+ const target = writer.config.target;
2825
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2826
+ return didChange(fillPixelData2(target, color, x, y, w, h));
2663
2827
  }
2664
2828
  };
2665
2829
  });
2666
-
2667
- // src/History/PixelMutator/mutatorApplyCirclePencilStroke.ts
2668
- var defaults6 = {
2669
- forEachLinePoint,
2670
- blendColorPixelDataBinaryMask,
2671
- getCircleBrushOrPencilBounds,
2672
- getCircleBrushOrPencilStrokeBounds
2673
- };
2674
- var mutatorApplyCirclePencilStroke = ((writer, deps = defaults6) => {
2830
+ var mutatorFillRect = ((writer, deps = defaults8) => {
2675
2831
  const {
2676
- forEachLinePoint: forEachLinePoint2 = defaults6.forEachLinePoint,
2677
- blendColorPixelDataBinaryMask: blendColorPixelDataBinaryMask2 = defaults6.blendColorPixelDataBinaryMask,
2678
- getCircleBrushOrPencilStrokeBounds: getCircleBrushOrPencilStrokeBounds2 = defaults6.getCircleBrushOrPencilStrokeBounds,
2679
- getCircleBrushOrPencilBounds: getCircleBrushOrPencilBounds2 = defaults6.getCircleBrushOrPencilBounds
2832
+ fillPixelData: fillPixelData2 = defaults8.fillPixelData
2680
2833
  } = deps;
2681
- const strokeBoundsOut = {
2682
- x: 0,
2683
- y: 0,
2684
- w: 0,
2685
- h: 0
2686
- };
2687
- const circlePencilBounds = {
2688
- x: 0,
2689
- y: 0,
2690
- w: 0,
2691
- h: 0
2692
- };
2693
- const blendColorPixelOptions = {
2694
- alpha: 255,
2695
- blendFn: sourceOverPerfect,
2696
- x: 0,
2697
- y: 0,
2698
- w: 0,
2699
- h: 0
2700
- };
2701
- const mask = {
2702
- type: 1 /* BINARY */,
2703
- data: null,
2704
- w: 0,
2705
- h: 0
2706
- };
2707
2834
  return {
2708
- applyCirclePencilStroke(color, x0, y0, x1, y1, brush, alpha = 255, blendFn = sourceOverPerfect) {
2709
- const {
2710
- x: bx,
2711
- y: by,
2712
- w: bw,
2713
- h: bh
2714
- } = getCircleBrushOrPencilStrokeBounds2(x0, y0, x1, y1, brush.size, strokeBoundsOut);
2715
- if (bw <= 0 || bh <= 0) return;
2716
- mask.data = new Uint8Array(bw * bh);
2717
- mask.w = bw;
2718
- mask.h = bh;
2719
- const maskData = mask.data;
2720
- const targetWidth = writer.target.width;
2721
- const targetHeight = writer.target.height;
2722
- forEachLinePoint2(x0, y0, x1, y1, (px, py) => {
2723
- const {
2724
- x: cbx,
2725
- y: cby,
2726
- w: cbw,
2727
- h: cbh
2728
- } = getCircleBrushOrPencilBounds2(px, py, brush.size, targetWidth, targetHeight, circlePencilBounds);
2729
- writer.accumulator.storeRegionBeforeState(cbx, cby, cbw, cbh);
2730
- const unclippedStartX = Math.floor(px + brush.minOffset);
2731
- const unclippedStartY = Math.floor(py + brush.minOffset);
2732
- const startX = Math.max(bx, unclippedStartX);
2733
- const startY = Math.max(by, unclippedStartY);
2734
- const endX = Math.min(bx + bw, unclippedStartX + brush.w);
2735
- const endY = Math.min(by + bh, unclippedStartY + brush.h);
2736
- for (let my = startY; my < endY; my++) {
2737
- const brushY = my - unclippedStartY;
2738
- const maskRowOffset = (my - by) * bw;
2739
- const brushRowOffset = brushY * brush.w;
2740
- for (let mx = startX; mx < endX; mx++) {
2741
- const brushX = mx - unclippedStartX;
2742
- const brushAlpha = brush.data[brushRowOffset + brushX];
2743
- if (brushAlpha > 0) {
2744
- const maskIdx = maskRowOffset + (mx - bx);
2745
- maskData[maskIdx] = brushAlpha;
2746
- }
2747
- }
2748
- }
2749
- });
2750
- blendColorPixelOptions.blendFn = blendFn;
2751
- blendColorPixelOptions.alpha = alpha;
2752
- blendColorPixelOptions.x = bx;
2753
- blendColorPixelOptions.y = by;
2754
- blendColorPixelOptions.w = bw;
2755
- blendColorPixelOptions.h = bh;
2756
- blendColorPixelDataBinaryMask2(writer.target, color, mask, blendColorPixelOptions);
2835
+ fillRect(color, rect) {
2836
+ const target = writer.config.target;
2837
+ const didChange = writer.accumulator.storeRegionBeforeState(rect.x, rect.y, rect.w, rect.h);
2838
+ return didChange(fillPixelData2(target, color, rect.x, rect.y, rect.w, rect.h));
2757
2839
  }
2758
2840
  };
2759
2841
  });
2760
2842
 
2761
- // src/Rect/getRectBrushOrPencilBounds.ts
2762
- function getRectBrushOrPencilBounds(centerX, centerY, brushWidth, brushHeight, targetWidth, targetHeight, out) {
2763
- const startX = Math.floor(centerX - brushWidth / 2);
2764
- const startY = Math.floor(centerY - brushHeight / 2);
2765
- const endX = startX + brushWidth;
2766
- const endY = startY + brushHeight;
2767
- const res = out ?? {
2768
- x: 0,
2769
- y: 0,
2770
- w: 0,
2771
- h: 0
2772
- };
2773
- const cStartX = Math.max(0, startX);
2774
- const cStartY = Math.max(0, startY);
2775
- const cEndX = Math.min(targetWidth, endX);
2776
- const cEndY = Math.min(targetHeight, endY);
2777
- const w = cEndX - cStartX;
2778
- const h = cEndY - cStartY;
2779
- res.x = cStartX;
2780
- res.y = cStartY;
2781
- res.w = w < 0 ? 0 : w;
2782
- res.h = h < 0 ? 0 : h;
2783
- return res;
2784
- }
2785
-
2786
- // src/PixelData/applyRectBrushToPixelData.ts
2787
- function applyRectBrushToPixelData(target, color, centerX, centerY, brushWidth, brushHeight, alpha = 255, fallOff, blendFn = sourceOverPerfect, bounds) {
2788
- const targetWidth = target.width;
2789
- const targetHeight = target.height;
2790
- const b = bounds ?? getRectBrushOrPencilBounds(centerX, centerY, brushWidth, brushHeight, targetWidth, targetHeight);
2791
- if (b.w <= 0 || b.h <= 0) return;
2792
- const data32 = target.data32;
2793
- const baseColor = color & 16777215;
2794
- const baseSrcAlpha = color >>> 24;
2795
- const isOpaque = alpha === 255;
2796
- const invHalfW = 1 / (brushWidth / 2);
2797
- const invHalfH = 1 / (brushHeight / 2);
2798
- const centerOffsetX = brushWidth % 2 === 0 ? 0.5 : 0;
2799
- const centerOffsetY = brushHeight % 2 === 0 ? 0.5 : 0;
2800
- const fCenterX = Math.floor(centerX);
2801
- const fCenterY = Math.floor(centerY);
2802
- const endX = b.x + b.w;
2803
- const endY = b.y + b.h;
2804
- const isOverwrite = blendFn.isOverwrite;
2805
- for (let py = b.y; py < endY; py++) {
2806
- const rowOffset = py * targetWidth;
2807
- const dy = Math.abs(py - fCenterY + centerOffsetY) * invHalfH;
2808
- for (let px = b.x; px < endX; px++) {
2809
- const idx = rowOffset + px;
2810
- const dx = Math.abs(px - fCenterX + centerOffsetX) * invHalfW;
2811
- const dist = dx > dy ? dx : dy;
2812
- const strength = fallOff(dist);
2813
- const maskVal = strength * 255 | 0;
2814
- if (maskVal <= 0) continue;
2815
- let weight = alpha;
2816
- if (isOpaque) {
2817
- weight = maskVal;
2818
- } else if (maskVal !== 255) {
2819
- weight = maskVal * alpha + 128 >> 8;
2820
- }
2821
- let finalCol = color;
2822
- if (weight < 255) {
2823
- const a = baseSrcAlpha * weight + 128 >> 8;
2824
- if (a === 0 && !isOverwrite) continue;
2825
- finalCol = (a << 24 | baseColor) >>> 0;
2843
+ // src/PixelData/fillPixelDataBinaryMask.ts
2844
+ var SCRATCH_RECT3 = makeClippedRect();
2845
+ function fillPixelDataBinaryMask(dst, color, mask, alpha = 255, x = 0, y = 0) {
2846
+ if (alpha === 0) return false;
2847
+ const maskW = mask.w;
2848
+ const maskH = mask.h;
2849
+ const clip = resolveRectClipping(x, y, maskW, maskH, dst.width, dst.height, SCRATCH_RECT3);
2850
+ if (!clip.inBounds) return false;
2851
+ const {
2852
+ x: finalX,
2853
+ y: finalY,
2854
+ w: actualW,
2855
+ h: actualH
2856
+ } = clip;
2857
+ const maskData = mask.data;
2858
+ const dst32 = dst.data32;
2859
+ const dw = dst.width;
2860
+ let finalCol = color;
2861
+ if (alpha < 255) {
2862
+ const baseSrcAlpha = color >>> 24;
2863
+ const colorRGB = color & 16777215;
2864
+ const a = baseSrcAlpha * alpha + 128 >> 8;
2865
+ finalCol = (colorRGB | a << 24) >>> 0;
2866
+ }
2867
+ let hasChanged = false;
2868
+ for (let iy = 0; iy < actualH; iy++) {
2869
+ const currentY = finalY + iy;
2870
+ const maskY = currentY - y;
2871
+ const maskOffset = maskY * maskW;
2872
+ const dstRowOffset = currentY * dw;
2873
+ for (let ix = 0; ix < actualW; ix++) {
2874
+ const currentX = finalX + ix;
2875
+ const maskX = currentX - x;
2876
+ const maskIndex = maskOffset + maskX;
2877
+ if (maskData[maskIndex]) {
2878
+ const current = dst32[dstRowOffset + currentX];
2879
+ if (current !== finalCol) {
2880
+ dst32[dstRowOffset + currentX] = finalCol;
2881
+ hasChanged = true;
2882
+ }
2826
2883
  }
2827
- data32[idx] = blendFn(finalCol, data32[idx]);
2828
2884
  }
2829
2885
  }
2886
+ return hasChanged;
2830
2887
  }
2831
2888
 
2832
- // src/History/PixelMutator/mutatorApplyRectBrush.ts
2833
- var defaults7 = {
2834
- applyRectBrushToPixelData,
2835
- getRectBrushOrPencilBounds
2889
+ // src/History/PixelMutator/mutatorFillBinaryMask.ts
2890
+ var defaults9 = {
2891
+ fillPixelDataBinaryMask
2836
2892
  };
2837
- var mutatorApplyRectBrush = ((writer, deps = defaults7) => {
2893
+ var mutatorFillBinaryMask = ((writer, deps = defaults9) => {
2838
2894
  const {
2839
- applyRectBrushToPixelData: applyRectBrushToPixelData2 = defaults7.applyRectBrushToPixelData,
2840
- getRectBrushOrPencilBounds: getRectBrushOrPencilBounds2 = defaults7.getRectBrushOrPencilBounds
2895
+ fillPixelDataBinaryMask: fillPixelDataBinaryMask2 = defaults9.fillPixelDataBinaryMask
2841
2896
  } = deps;
2842
- const boundsOut = {
2843
- x: 0,
2844
- y: 0,
2845
- w: 0,
2846
- h: 0
2847
- };
2848
2897
  return {
2849
- applyRectBrush(color, centerX, centerY, brushWidth, brushHeight, alpha = 255, fallOff, blendFn) {
2850
- const bounds = getRectBrushOrPencilBounds2(centerX, centerY, brushWidth, brushHeight, writer.target.width, writer.target.height, boundsOut);
2851
- const {
2852
- x,
2853
- y,
2854
- w,
2855
- h
2856
- } = bounds;
2857
- writer.accumulator.storeRegionBeforeState(x, y, w, h);
2858
- applyRectBrushToPixelData2(writer.target, color, centerX, centerY, brushWidth, brushHeight, alpha, fallOff, blendFn, bounds);
2898
+ fillBinaryMask(color, mask, alpha = 255, x = 0, y = 0) {
2899
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, mask.w, mask.h);
2900
+ return didChange(fillPixelDataBinaryMask2(writer.config.target, color, mask, alpha, x, y));
2859
2901
  }
2860
2902
  };
2861
2903
  });
2862
2904
 
2863
- // src/Rect/getRectBrushOrPencilStrokeBounds.ts
2864
- function getRectBrushOrPencilStrokeBounds(x0, y0, x1, y1, brushWidth, brushHeight, result) {
2865
- const halfW = brushWidth / 2;
2866
- const halfH = brushHeight / 2;
2867
- const minX = Math.min(x0, x1) - halfW;
2868
- const minY = Math.min(y0, y1) - halfH;
2869
- const maxX = Math.max(x0, x1) + halfW;
2870
- const maxY = Math.max(y0, y1) + halfH;
2871
- result.x = Math.floor(minX);
2872
- result.y = Math.floor(minY);
2873
- result.w = Math.ceil(maxX - minX);
2874
- result.h = Math.ceil(maxY - minY);
2875
- return result;
2905
+ // src/PixelData/invertPixelData.ts
2906
+ var SCRATCH_RECT4 = makeClippedRect();
2907
+ function invertPixelData(pixelData, opts = {}) {
2908
+ const dst = pixelData;
2909
+ const {
2910
+ x: targetX = 0,
2911
+ y: targetY = 0,
2912
+ w: width = pixelData.width,
2913
+ h: height = pixelData.height,
2914
+ mask,
2915
+ mx = 0,
2916
+ my = 0,
2917
+ invertMask = false
2918
+ } = opts;
2919
+ const clip = resolveRectClipping(targetX, targetY, width, height, dst.width, dst.height, SCRATCH_RECT4);
2920
+ if (!clip.inBounds) return false;
2921
+ const {
2922
+ x,
2923
+ y,
2924
+ w: actualW,
2925
+ h: actualH
2926
+ } = clip;
2927
+ const dst32 = dst.data32;
2928
+ const dw = dst.width;
2929
+ const mPitch = mask?.w ?? width;
2930
+ const dx = x - targetX;
2931
+ const dy = y - targetY;
2932
+ let dIdx = y * dw + x;
2933
+ let mIdx = (my + dy) * mPitch + (mx + dx);
2934
+ const dStride = dw - actualW;
2935
+ const mStride = mPitch - actualW;
2936
+ if (mask) {
2937
+ const maskData = mask.data;
2938
+ for (let iy = 0; iy < actualH; iy++) {
2939
+ for (let ix = 0; ix < actualW; ix++) {
2940
+ const mVal = maskData[mIdx];
2941
+ const isHit = invertMask ? mVal === 0 : mVal === 1;
2942
+ if (isHit) {
2943
+ dst32[dIdx] = dst32[dIdx] ^ 16777215;
2944
+ }
2945
+ dIdx++;
2946
+ mIdx++;
2947
+ }
2948
+ dIdx += dStride;
2949
+ mIdx += mStride;
2950
+ }
2951
+ } else {
2952
+ for (let iy = 0; iy < actualH; iy++) {
2953
+ for (let ix = 0; ix < actualW; ix++) {
2954
+ dst32[dIdx] = dst32[dIdx] ^ 16777215;
2955
+ dIdx++;
2956
+ }
2957
+ dIdx += dStride;
2958
+ }
2959
+ }
2960
+ return true;
2876
2961
  }
2877
2962
 
2878
- // src/History/PixelMutator/mutatorApplyRectBrushStroke.ts
2879
- var defaults8 = {
2880
- forEachLinePoint,
2881
- blendColorPixelDataAlphaMask,
2882
- getRectBrushOrPencilBounds,
2883
- getRectBrushOrPencilStrokeBounds
2963
+ // src/History/PixelMutator/mutatorInvert.ts
2964
+ var defaults10 = {
2965
+ invertPixelData
2884
2966
  };
2885
- var mutatorApplyRectBrushStroke = ((writer, deps = defaults8) => {
2967
+ var mutatorInvert = ((writer, deps = defaults10) => {
2886
2968
  const {
2887
- forEachLinePoint: forEachLinePoint2 = defaults8.forEachLinePoint,
2888
- blendColorPixelDataAlphaMask: blendColorPixelDataAlphaMask2 = defaults8.blendColorPixelDataAlphaMask,
2889
- getRectBrushOrPencilBounds: getRectBrushOrPencilBounds2 = defaults8.getRectBrushOrPencilBounds,
2890
- getRectBrushOrPencilStrokeBounds: getRectBrushOrPencilStrokeBounds2 = defaults8.getRectBrushOrPencilStrokeBounds
2969
+ invertPixelData: invertPixelData2 = defaults10.invertPixelData
2891
2970
  } = deps;
2892
- const strokeBoundsOut = {
2893
- x: 0,
2894
- y: 0,
2895
- w: 0,
2896
- h: 0
2897
- };
2898
- const rectBrushBounds = {
2899
- x: 0,
2900
- y: 0,
2901
- w: 0,
2902
- h: 0
2903
- };
2904
- const blendColorPixelOptions = {
2905
- alpha: 255,
2906
- blendFn: sourceOverPerfect,
2907
- x: 0,
2908
- y: 0,
2909
- w: 0,
2910
- h: 0
2911
- };
2912
- const mask = {
2913
- type: 0 /* ALPHA */,
2914
- data: null,
2915
- w: 0,
2916
- h: 0
2917
- };
2918
2971
  return {
2919
- applyRectBrushStroke(color, x0, y0, x1, y1, brushWidth, brushHeight, alpha = 255, fallOff, blendFn = sourceOverPerfect) {
2972
+ invert(opts = {}) {
2973
+ const target = writer.config.target;
2920
2974
  const {
2921
- x: bx,
2922
- y: by,
2923
- w: bw,
2924
- h: bh
2925
- } = getRectBrushOrPencilStrokeBounds2(x0, y0, x1, y1, brushWidth, brushHeight, strokeBoundsOut);
2926
- if (bw <= 0 || bh <= 0) return;
2927
- mask.data = new Uint8Array(bw * bh);
2928
- mask.w = bw;
2929
- mask.h = bh;
2930
- const maskData = mask.data;
2931
- const halfW = brushWidth / 2;
2932
- const halfH = brushHeight / 2;
2933
- const invHalfW = 1 / halfW;
2934
- const invHalfH = 1 / halfH;
2935
- const centerOffsetX = brushWidth % 2 === 0 ? 0.5 : 0;
2936
- const centerOffsetY = brushHeight % 2 === 0 ? 0.5 : 0;
2937
- const targetWidth = writer.target.width;
2938
- const targetHeight = writer.target.height;
2939
- forEachLinePoint2(x0, y0, x1, y1, (px, py) => {
2940
- const {
2941
- x: rbx,
2942
- y: rby,
2943
- w: rbw,
2944
- h: rbh
2945
- } = getRectBrushOrPencilBounds2(px, py, brushWidth, brushHeight, targetWidth, targetHeight, rectBrushBounds);
2946
- writer.accumulator.storeRegionBeforeState(rbx, rby, rbw, rbh);
2947
- const startX = Math.max(bx, rbx);
2948
- const startY = Math.max(by, rby);
2949
- const endX = Math.min(bx + bw, rbx + rbw);
2950
- const endY = Math.min(by + bh, rby + rbh);
2951
- const fPx = Math.floor(px);
2952
- const fPy = Math.floor(py);
2953
- for (let my = startY; my < endY; my++) {
2954
- const dy = Math.abs(my - fPy + centerOffsetY) * invHalfH;
2955
- const maskRowOffset = (my - by) * bw;
2956
- for (let mx = startX; mx < endX; mx++) {
2957
- const dx = Math.abs(mx - fPx + centerOffsetX) * invHalfW;
2958
- const maskIdx = maskRowOffset + (mx - bx);
2959
- const dist = dx > dy ? dx : dy;
2960
- const strength = fallOff(dist);
2961
- if (strength > 0) {
2962
- const intensity = strength * 255 | 0;
2963
- if (intensity > maskData[maskIdx]) {
2964
- maskData[maskIdx] = intensity;
2965
- }
2966
- }
2967
- }
2968
- }
2969
- });
2970
- blendColorPixelOptions.blendFn = blendFn;
2971
- blendColorPixelOptions.alpha = alpha;
2972
- blendColorPixelOptions.x = bx;
2973
- blendColorPixelOptions.y = by;
2974
- blendColorPixelOptions.w = bw;
2975
- blendColorPixelOptions.h = bh;
2976
- blendColorPixelDataAlphaMask2(writer.target, color, mask, blendColorPixelOptions);
2975
+ x = 0,
2976
+ y = 0,
2977
+ w = target.width,
2978
+ h = target.height
2979
+ } = opts;
2980
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2981
+ return didChange(invertPixelData2(target, opts));
2977
2982
  }
2978
2983
  };
2979
2984
  });
2980
2985
 
2981
- // src/History/PixelMutator/mutatorApplyRectPencil.ts
2982
- var defaults9 = {
2983
- applyRectBrushToPixelData,
2984
- getRectBrushOrPencilBounds,
2985
- fallOff: () => 1
2986
- };
2987
- var mutatorApplyRectPencil = ((writer, deps = defaults9) => {
2988
- const {
2989
- applyRectBrushToPixelData: applyRectBrushToPixelData2 = defaults9.applyRectBrushToPixelData,
2990
- getRectBrushOrPencilBounds: getRectBrushOrPencilBounds2 = defaults9.getRectBrushOrPencilBounds,
2991
- fallOff = defaults9.fallOff
2992
- } = deps;
2993
- const boundsOut = {
2994
- x: 0,
2995
- y: 0,
2996
- w: 0,
2997
- h: 0
2998
- };
2986
+ // src/History/PixelMutator.ts
2987
+ function makeFullPixelMutator(writer) {
2999
2988
  return {
3000
- applyRectPencil(color, centerX, centerY, brushWidth, brushHeight, alpha = 255, blendFn) {
3001
- const bounds = getRectBrushOrPencilBounds2(centerX, centerY, brushWidth, brushHeight, writer.target.width, writer.target.height, boundsOut);
3002
- const {
3003
- x,
3004
- y,
3005
- w,
3006
- h
3007
- } = bounds;
3008
- writer.accumulator.storeRegionBeforeState(x, y, w, h);
3009
- applyRectBrushToPixelData2(writer.target, color, centerX, centerY, brushWidth, brushHeight, alpha, fallOff, blendFn, bounds);
3010
- }
2989
+ // @sort
2990
+ ...mutatorBlendColor(writer),
2991
+ ...mutatorBlendPixel(writer),
2992
+ ...mutatorBlendPixelData(writer),
2993
+ ...mutatorBlendPixelDataAlphaMask(writer),
2994
+ ...mutatorBlendPixelDataBinaryMask(writer),
2995
+ ...mutatorClear(writer),
2996
+ ...mutatorFill(writer),
2997
+ ...mutatorFillBinaryMask(writer),
2998
+ ...mutatorFillRect(writer),
2999
+ ...mutatorInvert(writer)
3011
3000
  };
3012
- });
3001
+ }
3013
3002
 
3014
- // src/History/PixelMutator/mutatorApplyRectPencilStroke.ts
3015
- var defaults10 = {
3016
- forEachLinePoint,
3017
- getRectBrushOrPencilBounds,
3018
- getRectBrushOrPencilStrokeBounds,
3019
- blendColorPixelDataBinaryMask
3020
- };
3021
- var mutatorApplyRectPencilStroke = ((writer, deps = defaults10) => {
3003
+ // src/ImageData/resizeImageData.ts
3004
+ function resizeImageData(target, newWidth, newHeight, offsetX = 0, offsetY = 0) {
3005
+ const result = new ImageData(newWidth, newHeight);
3022
3006
  const {
3023
- forEachLinePoint: forEachLinePoint2 = defaults10.forEachLinePoint,
3024
- blendColorPixelDataBinaryMask: blendColorPixelDataBinaryMask2 = defaults10.blendColorPixelDataBinaryMask,
3025
- getRectBrushOrPencilBounds: getRectBrushOrPencilBounds2 = defaults10.getRectBrushOrPencilBounds,
3026
- getRectBrushOrPencilStrokeBounds: getRectBrushOrPencilStrokeBounds2 = defaults10.getRectBrushOrPencilStrokeBounds
3027
- } = deps;
3028
- const strokeBoundsOut = {
3007
+ width: oldW,
3008
+ height: oldH,
3009
+ data: oldData
3010
+ } = target;
3011
+ const newData = result.data;
3012
+ const x0 = Math.max(0, offsetX);
3013
+ const y0 = Math.max(0, offsetY);
3014
+ const x1 = Math.min(newWidth, offsetX + oldW);
3015
+ const y1 = Math.min(newHeight, offsetY + oldH);
3016
+ if (x1 <= x0 || y1 <= y0) {
3017
+ return result;
3018
+ }
3019
+ const rowCount = y1 - y0;
3020
+ const rowLen = (x1 - x0) * 4;
3021
+ for (let row = 0; row < rowCount; row++) {
3022
+ const dstY = y0 + row;
3023
+ const srcY = dstY - offsetY;
3024
+ const srcX = x0 - offsetX;
3025
+ const dstStart = (dstY * newWidth + x0) * 4;
3026
+ const srcStart = (srcY * oldW + srcX) * 4;
3027
+ newData.set(oldData.subarray(srcStart, srcStart + rowLen), dstStart);
3028
+ }
3029
+ return result;
3030
+ }
3031
+
3032
+ // src/Internal/helpers.ts
3033
+ var macro_halfAndFloor = (value) => value >> 1;
3034
+
3035
+ // src/Rect/trimRectBounds.ts
3036
+ function trimRectBounds(x, y, w, h, targetWidth, targetHeight, out) {
3037
+ const res = out ?? {
3029
3038
  x: 0,
3030
3039
  y: 0,
3031
3040
  w: 0,
3032
3041
  h: 0
3033
3042
  };
3034
- const rectPencilBounds = {
3043
+ const left = Math.max(0, x);
3044
+ const top = Math.max(0, y);
3045
+ const right = Math.min(targetWidth, x + w);
3046
+ const bottom = Math.min(targetHeight, y + h);
3047
+ res.x = left;
3048
+ res.y = top;
3049
+ res.w = Math.max(0, right - left);
3050
+ res.h = Math.max(0, bottom - top);
3051
+ return res;
3052
+ }
3053
+
3054
+ // src/Paint/PaintBuffer.ts
3055
+ var PaintBuffer = class {
3056
+ constructor(config, tilePool) {
3057
+ this.config = config;
3058
+ this.tilePool = tilePool;
3059
+ this.lookup = [];
3060
+ }
3061
+ lookup;
3062
+ scratchBounds = {
3035
3063
  x: 0,
3036
3064
  y: 0,
3037
3065
  w: 0,
3038
3066
  h: 0
3039
3067
  };
3040
- const blendColorPixelOptions = {
3041
- alpha: 255,
3068
+ eachTileInBounds(bounds, callback) {
3069
+ const {
3070
+ tileShift,
3071
+ targetColumns,
3072
+ targetRows,
3073
+ tileSize
3074
+ } = this.config;
3075
+ const x1 = Math.max(0, bounds.x >> tileShift);
3076
+ const y1 = Math.max(0, bounds.y >> tileShift);
3077
+ const x2 = Math.min(targetColumns - 1, bounds.x + bounds.w - 1 >> tileShift);
3078
+ const y2 = Math.min(targetRows - 1, bounds.y + bounds.h - 1 >> tileShift);
3079
+ if (x1 > x2 || y1 > y2) return;
3080
+ const lookup = this.lookup;
3081
+ const tilePool = this.tilePool;
3082
+ for (let ty = y1; ty <= y2; ty++) {
3083
+ const rowOffset = ty * targetColumns;
3084
+ const tileTop = ty << tileShift;
3085
+ for (let tx = x1; tx <= x2; tx++) {
3086
+ const id = rowOffset + tx;
3087
+ const tile = lookup[id] ?? (lookup[id] = tilePool.getTile(id, tx, ty));
3088
+ const tileLeft = tx << tileShift;
3089
+ const startX = bounds.x > tileLeft ? bounds.x : tileLeft;
3090
+ const startY = bounds.y > tileTop ? bounds.y : tileTop;
3091
+ const maskEndX = bounds.x + bounds.w;
3092
+ const tileEndX = tileLeft + tileSize;
3093
+ const endX = maskEndX < tileEndX ? maskEndX : tileEndX;
3094
+ const maskEndY = bounds.y + bounds.h;
3095
+ const tileEndY = tileTop + tileSize;
3096
+ const endY = maskEndY < tileEndY ? maskEndY : tileEndY;
3097
+ callback(tile, startX, startY, endX - startX, endY - startY);
3098
+ }
3099
+ }
3100
+ }
3101
+ writePaintAlphaMaskStroke(color, brush, x0, y0, x1, y1) {
3102
+ const cA = color >>> 24;
3103
+ if (cA === 0) return false;
3104
+ const {
3105
+ tileShift,
3106
+ tileMask,
3107
+ target
3108
+ } = this.config;
3109
+ const {
3110
+ w: bW,
3111
+ h: bH,
3112
+ data: bD,
3113
+ centerOffsetX,
3114
+ centerOffsetY
3115
+ } = brush;
3116
+ const cRGB = color & 16777215;
3117
+ const scratch = this.scratchBounds;
3118
+ let changed = false;
3119
+ forEachLinePoint(x0, y0, x1, y1, (px, py) => {
3120
+ const topLeftX = Math.floor(px + centerOffsetX);
3121
+ const topLeftY = Math.floor(py + centerOffsetY);
3122
+ trimRectBounds(topLeftX, topLeftY, bW, bH, target.width, target.height, scratch);
3123
+ if (scratch.w <= 0 || scratch.h <= 0) return;
3124
+ this.eachTileInBounds(scratch, (tile, bX, bY, bW_t, bH_t) => {
3125
+ const d32 = tile.data32;
3126
+ let tileChanged = false;
3127
+ for (let i = 0; i < bH_t; i++) {
3128
+ const canvasY = bY + i;
3129
+ const bOff = (canvasY - topLeftY) * bW;
3130
+ const tOff = (canvasY & tileMask) << tileShift;
3131
+ const dS = tOff + (bX & tileMask);
3132
+ for (let j = 0; j < bW_t; j++) {
3133
+ const canvasX = bX + j;
3134
+ const brushA = bD[bOff + (canvasX - topLeftX)];
3135
+ if (brushA === 0) continue;
3136
+ const t = cA * brushA + 128;
3137
+ const blendedA = t + (t >> 8) >> 8;
3138
+ const idx = dS + j;
3139
+ const cur = d32[idx];
3140
+ if (brushA > cur >>> 24) {
3141
+ const next = (cRGB | blendedA << 24) >>> 0;
3142
+ if (cur !== next) {
3143
+ d32[idx] = next;
3144
+ tileChanged = true;
3145
+ }
3146
+ }
3147
+ }
3148
+ }
3149
+ if (tileChanged) changed = true;
3150
+ });
3151
+ });
3152
+ return changed;
3153
+ }
3154
+ writePaintBinaryMaskStroke(color, brush, x0, y0, x1, y1) {
3155
+ const alphaIsZero = color >>> 24 === 0;
3156
+ if (alphaIsZero) return false;
3157
+ const {
3158
+ tileShift,
3159
+ tileMask,
3160
+ target
3161
+ } = this.config;
3162
+ const {
3163
+ w: bW,
3164
+ h: bH,
3165
+ data: bD,
3166
+ centerOffsetX,
3167
+ centerOffsetY
3168
+ } = brush;
3169
+ const scratch = this.scratchBounds;
3170
+ let changed = false;
3171
+ forEachLinePoint(x0, y0, x1, y1, (px, py) => {
3172
+ const topLeftX = Math.floor(px + centerOffsetX);
3173
+ const topLeftY = Math.floor(py + centerOffsetY);
3174
+ trimRectBounds(topLeftX, topLeftY, bW, bH, target.width, target.height, scratch);
3175
+ if (scratch.w <= 0 || scratch.h <= 0) return;
3176
+ this.eachTileInBounds(scratch, (tile, bX, bY, bW_t, bH_t) => {
3177
+ const d32 = tile.data32;
3178
+ let tileChanged = false;
3179
+ for (let i = 0; i < bH_t; i++) {
3180
+ const canvasY = bY + i;
3181
+ const bOff = (canvasY - topLeftY) * bW;
3182
+ const tOff = (canvasY & tileMask) << tileShift;
3183
+ const dS = tOff + (bX & tileMask);
3184
+ for (let j = 0; j < bW_t; j++) {
3185
+ const canvasX = bX + j;
3186
+ if (bD[bOff + (canvasX - topLeftX)]) {
3187
+ const idx = dS + j;
3188
+ if (d32[idx] !== color) {
3189
+ d32[idx] = color;
3190
+ tileChanged = true;
3191
+ }
3192
+ }
3193
+ }
3194
+ }
3195
+ if (tileChanged) changed = true;
3196
+ });
3197
+ });
3198
+ return changed;
3199
+ }
3200
+ writeRectStroke(color, brushWidth, brushHeight, x0, y0, x1, y1) {
3201
+ const alphaIsZero = color >>> 24 === 0;
3202
+ if (alphaIsZero) return false;
3203
+ const config = this.config;
3204
+ const tileShift = config.tileShift;
3205
+ const tileMask = config.tileMask;
3206
+ const target = config.target;
3207
+ const scratch = this.scratchBounds;
3208
+ const centerOffsetX = macro_halfAndFloor(brushWidth - 1);
3209
+ const centerOffsetY = macro_halfAndFloor(brushHeight - 1);
3210
+ let changed = false;
3211
+ forEachLinePoint(x0, y0, x1, y1, (px, py) => {
3212
+ const topLeftX = Math.floor(px + centerOffsetX);
3213
+ const topLeftY = Math.floor(py + centerOffsetY);
3214
+ trimRectBounds(topLeftX, topLeftY, brushWidth, brushHeight, target.width, target.height, scratch);
3215
+ if (scratch.w <= 0 || scratch.h <= 0) return;
3216
+ this.eachTileInBounds(scratch, (tile, bX, bY, bW_t, bH_t) => {
3217
+ const d32 = tile.data32;
3218
+ let tileChanged = false;
3219
+ for (let i = 0; i < bH_t; i++) {
3220
+ const canvasY = bY + i;
3221
+ const tOff = (canvasY & tileMask) << tileShift;
3222
+ const dS = tOff + (bX & tileMask);
3223
+ for (let j = 0; j < bW_t; j++) {
3224
+ const idx = dS + j;
3225
+ if (d32[idx] !== color) {
3226
+ d32[idx] = color;
3227
+ tileChanged = true;
3228
+ }
3229
+ }
3230
+ }
3231
+ if (tileChanged) {
3232
+ changed = true;
3233
+ }
3234
+ });
3235
+ });
3236
+ return changed;
3237
+ }
3238
+ clear() {
3239
+ this.tilePool.releaseTiles(this.lookup);
3240
+ }
3241
+ };
3242
+
3243
+ // src/PixelTile/PixelTile.ts
3244
+ var PixelTile = class {
3245
+ constructor(id, tx, ty, tileSize, tileArea) {
3246
+ this.id = id;
3247
+ this.tx = tx;
3248
+ this.ty = ty;
3249
+ this.width = this.height = tileSize;
3250
+ this.data32 = new Uint32Array(tileArea);
3251
+ const data8 = new Uint8ClampedArray(this.data32.buffer);
3252
+ this.imageData = new ImageData(data8, tileSize, tileSize);
3253
+ }
3254
+ data32;
3255
+ width;
3256
+ height;
3257
+ imageData;
3258
+ };
3259
+
3260
+ // src/PixelTile/PixelTilePool.ts
3261
+ var PixelTilePool = class {
3262
+ pool;
3263
+ tileSize;
3264
+ tileArea;
3265
+ constructor(config) {
3266
+ this.pool = [];
3267
+ this.tileSize = config.tileSize;
3268
+ this.tileArea = config.tileArea;
3269
+ }
3270
+ getTile(id, tx, ty) {
3271
+ let tile = this.pool.pop();
3272
+ if (tile) {
3273
+ tile.id = id;
3274
+ tile.tx = tx;
3275
+ tile.ty = ty;
3276
+ tile.data32.fill(0);
3277
+ return tile;
3278
+ }
3279
+ return new PixelTile(id, tx, ty, this.tileSize, this.tileArea);
3280
+ }
3281
+ releaseTile(tile) {
3282
+ this.pool.push(tile);
3283
+ }
3284
+ releaseTiles(tiles) {
3285
+ let length = tiles.length;
3286
+ for (let i = 0; i < length; i++) {
3287
+ let tile = tiles[i];
3288
+ if (tile) {
3289
+ this.pool.push(tile);
3290
+ }
3291
+ }
3292
+ tiles.length = 0;
3293
+ }
3294
+ };
3295
+
3296
+ // src/History/PixelWriter.ts
3297
+ var PixelWriter = class {
3298
+ historyManager;
3299
+ accumulator;
3300
+ historyActionFactory;
3301
+ config;
3302
+ pixelTilePool;
3303
+ paintBuffer;
3304
+ mutator;
3305
+ blendPixelDataOpts = {
3306
+ alpha: 255,
3042
3307
  blendFn: sourceOverPerfect,
3043
3308
  x: 0,
3044
3309
  y: 0,
3045
3310
  w: 0,
3046
3311
  h: 0
3047
3312
  };
3048
- const mask = {
3049
- type: 1 /* BINARY */,
3050
- data: null,
3051
- w: 0,
3052
- h: 0
3053
- };
3054
- return {
3055
- applyRectPencilStroke(color, x0, y0, x1, y1, brushWidth, brushHeight, alpha = 255, blendFn = sourceOverPerfect) {
3056
- const {
3057
- x: bx,
3058
- y: by,
3059
- w: bw,
3060
- h: bh
3061
- } = getRectBrushOrPencilStrokeBounds2(x0, y0, x1, y1, brushWidth, brushHeight, strokeBoundsOut);
3062
- if (bw <= 0 || bh <= 0) return;
3063
- mask.data = new Uint8Array(bw * bh);
3064
- mask.w = bw;
3065
- mask.h = bh;
3066
- const maskData = mask.data;
3067
- const halfW = brushWidth / 2;
3068
- const halfH = brushHeight / 2;
3069
- const centerOffset = brushWidth % 2 === 0 ? 0.5 : 0;
3070
- const targetWidth = writer.target.width;
3071
- const targetHeight = writer.target.height;
3072
- forEachLinePoint2(x0, y0, x1, y1, (px, py) => {
3073
- const {
3074
- x: rbx,
3075
- y: rby,
3076
- w: rbw,
3077
- h: rbh
3078
- } = getRectBrushOrPencilBounds2(px, py, brushWidth, brushHeight, targetWidth, targetHeight, rectPencilBounds);
3079
- writer.accumulator.storeRegionBeforeState(rbx, rby, rbw, rbh);
3080
- const startX = Math.max(bx, rbx);
3081
- const startY = Math.max(by, rby);
3082
- const endX = Math.min(bx + bw, rbx + rbw);
3083
- const endY = Math.min(by + bh, rby + rbh);
3084
- const fPx = Math.floor(px);
3085
- const fPy = Math.floor(py);
3086
- for (let my = startY; my < endY; my++) {
3087
- const dy = Math.abs(my - fPy + centerOffset);
3088
- const maskRowOffset = (my - by) * bw;
3089
- for (let mx = startX; mx < endX; mx++) {
3090
- const dx = Math.abs(mx - fPx + centerOffset);
3091
- const maskIdx = maskRowOffset + (mx - bx);
3092
- if (dx <= halfW && dy <= halfH) {
3093
- maskData[maskIdx] = 1;
3094
- }
3095
- }
3096
- }
3097
- });
3098
- blendColorPixelOptions.blendFn = blendFn;
3099
- blendColorPixelOptions.alpha = alpha;
3100
- blendColorPixelOptions.x = bx;
3101
- blendColorPixelOptions.y = by;
3102
- blendColorPixelOptions.w = bw;
3103
- blendColorPixelOptions.h = bh;
3104
- blendColorPixelDataBinaryMask2(writer.target, color, mask, blendColorPixelOptions);
3313
+ _inProgress = false;
3314
+ constructor(target, mutatorFactory, {
3315
+ tileSize = 256,
3316
+ maxHistorySteps = 50,
3317
+ historyManager = new HistoryManager(maxHistorySteps),
3318
+ historyActionFactory = makeHistoryAction,
3319
+ pixelTilePool,
3320
+ accumulator
3321
+ } = {}) {
3322
+ this.config = new PixelEngineConfig(tileSize, target);
3323
+ this.historyManager = historyManager;
3324
+ this.pixelTilePool = pixelTilePool ?? new PixelTilePool(this.config);
3325
+ this.accumulator = accumulator ?? new PixelAccumulator(this.config, this.pixelTilePool);
3326
+ this.historyActionFactory = historyActionFactory;
3327
+ this.mutator = mutatorFactory(this);
3328
+ this.paintBuffer = new PaintBuffer(this.config, this.pixelTilePool);
3329
+ }
3330
+ /**
3331
+ * Executes `transaction` and commits the resulting pixel changes as a single
3332
+ * undoable history action.
3333
+ *
3334
+ * - If `transaction` throws, all accumulated changes are rolled back and the error
3335
+ * is re-thrown. No action is committed.
3336
+ * - If `transaction` completes without modifying any pixels, no action is committed.
3337
+ * - `withHistory` is not re-entrant. Calling it again from inside `transaction` will
3338
+ * throw immediately to prevent silent data loss from a nested extractPatch.
3339
+ *
3340
+ * @param transaction Callback to be executed inside the transaction.
3341
+ * @param after Called after both undo and redo — use for generic change notifications.
3342
+ * @param afterUndo Called after undo only — use for dimension or state changes specific to undo.
3343
+ * @param afterRedo Called after redo only.
3344
+ */
3345
+ withHistory(transaction, after, afterUndo, afterRedo) {
3346
+ if (this._inProgress) {
3347
+ throw new Error("withHistory is not re-entrant \u2014 commit or rollback the current operation first");
3105
3348
  }
3106
- };
3107
- });
3349
+ this._inProgress = true;
3350
+ try {
3351
+ transaction(this.mutator);
3352
+ } catch (e) {
3353
+ this.accumulator.rollbackAfterError();
3354
+ throw e;
3355
+ } finally {
3356
+ this._inProgress = false;
3357
+ }
3358
+ if (this.accumulator.beforeTiles.length === 0) return;
3359
+ const patch = this.accumulator.extractPatch();
3360
+ const action = this.historyActionFactory(this, patch, after, afterUndo, afterRedo);
3361
+ this.historyManager.commit(action);
3362
+ }
3363
+ resize(newWidth, newHeight, offsetX = 0, offsetY = 0, after, afterUndo, afterRedo, resizeImageDataFn = resizeImageData) {
3364
+ if (this._inProgress) {
3365
+ throw new Error("Cannot resize inside a withHistory callback");
3366
+ }
3367
+ if (this.accumulator.beforeTiles.length > 0) {
3368
+ throw new Error("Cannot resize with an open accumulator \u2014 commit or rollback first");
3369
+ }
3370
+ const config = this.config;
3371
+ const target = config.target;
3372
+ const beforeImageData = target.imageData;
3373
+ const afterImageData = resizeImageDataFn(beforeImageData, newWidth, newHeight, offsetX, offsetY);
3374
+ target.set(afterImageData);
3375
+ this.historyManager.commit({
3376
+ undo: () => {
3377
+ target.set(beforeImageData);
3378
+ afterUndo?.(beforeImageData);
3379
+ after?.(beforeImageData);
3380
+ },
3381
+ redo: () => {
3382
+ target.set(afterImageData);
3383
+ afterRedo?.(afterImageData);
3384
+ after?.(afterImageData);
3385
+ }
3386
+ });
3387
+ }
3388
+ commitPaintBuffer(alpha = 255, blendFn = sourceOverPerfect, blendPixelDataFn = blendPixelData) {
3389
+ const paintBuffer = this.paintBuffer;
3390
+ const tileShift = paintBuffer.config.tileShift;
3391
+ const lookup = paintBuffer.lookup;
3392
+ const opts = this.blendPixelDataOpts;
3393
+ opts.alpha = alpha;
3394
+ opts.blendFn = blendFn;
3395
+ for (let i = 0; i < lookup.length; i++) {
3396
+ const tile = lookup[i];
3397
+ if (tile) {
3398
+ const didChange = this.accumulator.storeTileBeforeState(tile.id, tile.tx, tile.ty);
3399
+ const dx = tile.tx << tileShift;
3400
+ const dy = tile.ty << tileShift;
3401
+ opts.x = dx;
3402
+ opts.y = dy;
3403
+ opts.w = tile.width;
3404
+ opts.h = tile.height;
3405
+ didChange(blendPixelDataFn(this.config.target, tile, opts));
3406
+ }
3407
+ }
3408
+ paintBuffer.clear();
3409
+ }
3410
+ };
3108
3411
 
3109
- // src/PixelData/blendColorPixelData.ts
3110
- function blendColorPixelData(dst, color, opts = {}) {
3412
+ // src/PixelData/applyAlphaMaskToPixelData.ts
3413
+ function applyAlphaMaskToPixelData(dst, mask, opts = {}) {
3111
3414
  const {
3112
3415
  x: targetX = 0,
3113
3416
  y: targetY = 0,
3114
3417
  w: width = dst.width,
3115
3418
  h: height = dst.height,
3116
3419
  alpha: globalAlpha = 255,
3117
- blendFn = sourceOverPerfect
3420
+ mx = 0,
3421
+ my = 0,
3422
+ invertMask = false
3118
3423
  } = opts;
3119
- if (globalAlpha === 0) return;
3120
- const baseSrcAlpha = color >>> 24;
3121
- const isOverwrite = blendFn.isOverwrite || false;
3122
- if (baseSrcAlpha === 0 && !isOverwrite) return;
3123
- let x = targetX, y = targetY, w = width, h = height;
3424
+ if (globalAlpha === 0) return false;
3425
+ let x = targetX;
3426
+ let y = targetY;
3427
+ let w = width;
3428
+ let h = height;
3124
3429
  if (x < 0) {
3125
3430
  w += x;
3126
3431
  x = 0;
@@ -3129,723 +3434,200 @@ function blendColorPixelData(dst, color, opts = {}) {
3129
3434
  h += y;
3130
3435
  y = 0;
3131
3436
  }
3132
- const actualW = Math.min(w, dst.width - x);
3133
- const actualH = Math.min(h, dst.height - y);
3134
- if (actualW <= 0 || actualH <= 0) return;
3135
- let finalSrcColor = color;
3136
- if (globalAlpha < 255) {
3137
- const a = baseSrcAlpha * globalAlpha + 128 >> 8;
3138
- if (a === 0 && !isOverwrite) return;
3139
- finalSrcColor = (color & 16777215 | a << 24) >>> 0;
3140
- }
3437
+ w = Math.min(w, dst.width - x);
3438
+ h = Math.min(h, dst.height - y);
3439
+ if (w <= 0) return false;
3440
+ if (h <= 0) return false;
3441
+ const mPitch = mask.w;
3442
+ if (mPitch <= 0) return false;
3443
+ const startX = mx + (x - targetX);
3444
+ const startY = my + (y - targetY);
3445
+ const sX0 = Math.max(0, startX);
3446
+ const sY0 = Math.max(0, startY);
3447
+ const sX1 = Math.min(mPitch, startX + w);
3448
+ const sY1 = Math.min(mask.h, startY + h);
3449
+ const finalW = sX1 - sX0;
3450
+ const finalH = sY1 - sY0;
3451
+ if (finalW <= 0) return false;
3452
+ if (finalH <= 0) return false;
3453
+ const xShift = sX0 - startX;
3454
+ const yShift = sY0 - startY;
3141
3455
  const dst32 = dst.data32;
3142
3456
  const dw = dst.width;
3143
- let dIdx = y * dw + x | 0;
3144
- const dStride = dw - actualW | 0;
3145
- for (let iy = 0; iy < actualH; iy++) {
3146
- for (let ix = 0; ix < actualW; ix++) {
3147
- dst32[dIdx] = blendFn(finalSrcColor, dst32[dIdx]);
3457
+ const dStride = dw - finalW;
3458
+ const mStride = mPitch - finalW;
3459
+ const maskData = mask.data;
3460
+ let dIdx = (y + yShift) * dw + (x + xShift);
3461
+ let mIdx = sY0 * mPitch + sX0;
3462
+ let didChange = false;
3463
+ for (let iy = 0; iy < h; iy++) {
3464
+ for (let ix = 0; ix < w; ix++) {
3465
+ const mVal = maskData[mIdx];
3466
+ const effectiveM = invertMask ? 255 - mVal : mVal;
3467
+ let weight = 0;
3468
+ if (effectiveM === 0) {
3469
+ weight = 0;
3470
+ } else if (effectiveM === 255) {
3471
+ weight = globalAlpha;
3472
+ } else if (globalAlpha === 255) {
3473
+ weight = effectiveM;
3474
+ } else {
3475
+ weight = effectiveM * globalAlpha + 128 >> 8;
3476
+ }
3477
+ if (weight === 0) {
3478
+ dst32[dIdx] = (dst32[dIdx] & 16777215) >>> 0;
3479
+ didChange = true;
3480
+ } else if (weight !== 255) {
3481
+ const d = dst32[dIdx];
3482
+ const da = d >>> 24;
3483
+ if (da !== 0) {
3484
+ const finalAlpha = da === 255 ? weight : da * weight + 128 >> 8;
3485
+ const current = dst32[dIdx];
3486
+ const next = (d & 16777215 | finalAlpha << 24) >>> 0;
3487
+ if (current !== next) {
3488
+ dst32[dIdx] = next;
3489
+ didChange = true;
3490
+ }
3491
+ }
3492
+ }
3148
3493
  dIdx++;
3494
+ mIdx++;
3149
3495
  }
3150
3496
  dIdx += dStride;
3497
+ mIdx += mStride;
3151
3498
  }
3499
+ return didChange;
3152
3500
  }
3153
3501
 
3154
- // src/History/PixelMutator/mutatorBlendColor.ts
3502
+ // src/History/PixelMutator/mutatorApplyAlphaMask.ts
3155
3503
  var defaults11 = {
3156
- blendColorPixelData
3504
+ applyAlphaMaskToPixelData
3157
3505
  };
3158
- var mutatorBlendColor = ((writer, deps = defaults11) => {
3506
+ var mutatorApplyAlphaMask = ((writer, deps = defaults11) => {
3159
3507
  const {
3160
- blendColorPixelData: blendColorPixelData2 = defaults11.blendColorPixelData
3508
+ applyAlphaMaskToPixelData: applyAlphaMaskToPixelData2 = defaults11.applyAlphaMaskToPixelData
3161
3509
  } = deps;
3162
3510
  return {
3163
- blendColor(color, opts = {}) {
3511
+ applyAlphaMask(mask, opts = {}) {
3512
+ let target = writer.config.target;
3164
3513
  const {
3165
3514
  x = 0,
3166
3515
  y = 0,
3167
- w = writer.target.width,
3168
- h = writer.target.height
3516
+ w = target.width,
3517
+ h = target.height
3169
3518
  } = opts;
3170
- writer.accumulator.storeRegionBeforeState(x, y, w, h);
3171
- blendColorPixelData2(writer.target, color, opts);
3519
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3520
+ return didChange(applyAlphaMaskToPixelData2(target, mask, opts));
3172
3521
  }
3173
3522
  };
3174
3523
  });
3175
3524
 
3176
- // src/History/PixelMutator/mutatorBlendPixel.ts
3177
- function mutatorBlendPixel(writer) {
3178
- return {
3179
- blendPixel(x, y, color, alpha = 255, blendFn = overwriteFast) {
3180
- let target = writer.target;
3181
- let width = target.width;
3182
- let height = target.height;
3183
- if (x < 0 || x >= width || y < 0 || y >= height) return;
3184
- writer.accumulator.storeTileBeforeState(x, y);
3185
- let index = y * width + x;
3186
- let bg = target.data32[index];
3187
- let finalColor = color;
3188
- if (alpha < 255) {
3189
- let baseSrcAlpha = color >>> 24;
3190
- let finalAlpha = baseSrcAlpha * alpha + 128 >> 8;
3191
- finalColor = (color & 16777215 | finalAlpha << 24) >>> 0;
3192
- }
3193
- target.data32[index] = blendFn(finalColor, bg);
3194
- }
3195
- };
3196
- }
3197
-
3198
- // src/PixelData/blendPixelData.ts
3199
- function blendPixelData(dst, src, opts = {}) {
3525
+ // src/PixelData/applyBinaryMaskToPixelData.ts
3526
+ function applyBinaryMaskToPixelData(dst, mask, opts = {}) {
3200
3527
  const {
3201
3528
  x: targetX = 0,
3202
3529
  y: targetY = 0,
3203
- sx: sourceX = 0,
3204
- sy: sourceY = 0,
3205
- w: width = src.width,
3206
- h: height = src.height,
3530
+ w: width = dst.width,
3531
+ h: height = dst.height,
3207
3532
  alpha: globalAlpha = 255,
3208
- blendFn = sourceOverPerfect
3533
+ mx = 0,
3534
+ my = 0,
3535
+ invertMask = false
3209
3536
  } = opts;
3210
- if (globalAlpha === 0) return;
3537
+ if (globalAlpha === 0) return false;
3211
3538
  let x = targetX;
3212
3539
  let y = targetY;
3213
- let sx = sourceX;
3214
- let sy = sourceY;
3215
3540
  let w = width;
3216
3541
  let h = height;
3217
- if (sx < 0) {
3218
- x -= sx;
3219
- w += sx;
3220
- sx = 0;
3221
- }
3222
- if (sy < 0) {
3223
- y -= sy;
3224
- h += sy;
3225
- sy = 0;
3226
- }
3227
- w = Math.min(w, src.width - sx);
3228
- h = Math.min(h, src.height - sy);
3229
3542
  if (x < 0) {
3230
- sx -= x;
3231
3543
  w += x;
3232
3544
  x = 0;
3233
3545
  }
3234
3546
  if (y < 0) {
3235
- sy -= y;
3236
3547
  h += y;
3237
3548
  y = 0;
3238
- }
3239
- const actualW = Math.min(w, dst.width - x);
3240
- const actualH = Math.min(h, dst.height - y);
3241
- if (actualW <= 0 || actualH <= 0) return;
3242
- const dst32 = dst.data32;
3243
- const src32 = src.data32;
3244
- const dw = dst.width;
3245
- const sw = src.width;
3246
- let dIdx = y * dw + x | 0;
3247
- let sIdx = sy * sw + sx | 0;
3248
- const dStride = dw - actualW | 0;
3249
- const sStride = sw - actualW | 0;
3250
- const isOpaque = globalAlpha === 255;
3251
- const isOverwrite = blendFn.isOverwrite;
3252
- for (let iy = 0; iy < actualH; iy++) {
3253
- for (let ix = 0; ix < actualW; ix++) {
3254
- const srcCol = src32[sIdx];
3255
- const srcAlpha = srcCol >>> 24;
3256
- if (srcAlpha === 0 && !isOverwrite) {
3257
- dIdx++;
3258
- sIdx++;
3259
- continue;
3260
- }
3261
- let finalCol = srcCol;
3262
- if (!isOpaque) {
3263
- const a = srcAlpha * globalAlpha + 128 >> 8;
3264
- if (a === 0 && !isOverwrite) {
3265
- dIdx++;
3266
- sIdx++;
3267
- continue;
3268
- }
3269
- finalCol = (srcCol & 16777215 | a << 24) >>> 0;
3270
- }
3271
- dst32[dIdx] = blendFn(finalCol, dst32[dIdx]);
3272
- dIdx++;
3273
- sIdx++;
3274
- }
3275
- dIdx += dStride;
3276
- sIdx += sStride;
3277
- }
3278
- }
3279
-
3280
- // src/History/PixelMutator/mutatorBlendPixelData.ts
3281
- var defaults12 = {
3282
- blendPixelData
3283
- };
3284
- var mutatorBlendPixelData = ((writer, deps = defaults12) => {
3285
- const {
3286
- blendPixelData: blendPixelData2 = defaults12.blendPixelData
3287
- } = deps;
3288
- return {
3289
- blendPixelData(src, opts = {}) {
3290
- const {
3291
- x = 0,
3292
- y = 0,
3293
- w = src.width,
3294
- h = src.height
3295
- } = opts;
3296
- writer.accumulator.storeRegionBeforeState(x, y, w, h);
3297
- blendPixelData2(writer.target, src, opts);
3298
- }
3299
- };
3300
- });
3301
-
3302
- // src/PixelData/blendPixelDataAlphaMask.ts
3303
- function blendPixelDataAlphaMask(dst, src, alphaMask, opts = {}) {
3304
- const {
3305
- x: targetX = 0,
3306
- y: targetY = 0,
3307
- sx: sourceX = 0,
3308
- sy: sourceY = 0,
3309
- w: width = src.width,
3310
- h: height = src.height,
3311
- alpha: globalAlpha = 255,
3312
- blendFn = sourceOverPerfect,
3313
- mx = 0,
3314
- my = 0,
3315
- invertMask = false
3316
- } = opts;
3317
- if (globalAlpha === 0) return;
3318
- let x = targetX;
3319
- let y = targetY;
3320
- let sx = sourceX;
3321
- let sy = sourceY;
3322
- let w = width;
3323
- let h = height;
3324
- if (sx < 0) {
3325
- x -= sx;
3326
- w += sx;
3327
- sx = 0;
3328
- }
3329
- if (sy < 0) {
3330
- y -= sy;
3331
- h += sy;
3332
- sy = 0;
3333
- }
3334
- w = Math.min(w, src.width - sx);
3335
- h = Math.min(h, src.height - sy);
3336
- if (x < 0) {
3337
- sx -= x;
3338
- w += x;
3339
- x = 0;
3340
- }
3341
- if (y < 0) {
3342
- sy -= y;
3343
- h += y;
3344
- y = 0;
3345
- }
3346
- const actualW = Math.min(w, dst.width - x);
3347
- const actualH = Math.min(h, dst.height - y);
3348
- if (actualW <= 0 || actualH <= 0) return;
3349
- const dw = dst.width;
3350
- const sw = src.width;
3351
- const mPitch = alphaMask.w;
3352
- const maskData = alphaMask.data;
3353
- const dx = x - targetX | 0;
3354
- const dy = y - targetY | 0;
3355
- const dst32 = dst.data32;
3356
- const src32 = src.data32;
3357
- let dIdx = y * dw + x | 0;
3358
- let sIdx = sy * sw + sx | 0;
3359
- let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
3360
- const dStride = dw - actualW | 0;
3361
- const sStride = sw - actualW | 0;
3362
- const mStride = mPitch - actualW | 0;
3363
- const isOpaque = globalAlpha === 255;
3364
- const isOverwrite = blendFn.isOverwrite || false;
3365
- for (let iy = 0; iy < actualH; iy++) {
3366
- for (let ix = 0; ix < actualW; ix++) {
3367
- const mVal = maskData[mIdx];
3368
- const effM = invertMask ? 255 - mVal : mVal;
3369
- if (effM === 0) {
3370
- dIdx++;
3371
- sIdx++;
3372
- mIdx++;
3373
- continue;
3374
- }
3375
- const srcCol = src32[sIdx];
3376
- const srcAlpha = srcCol >>> 24;
3377
- if (srcAlpha === 0 && !isOverwrite) {
3378
- dIdx++;
3379
- sIdx++;
3380
- mIdx++;
3381
- continue;
3382
- }
3383
- let weight = globalAlpha;
3384
- if (isOpaque) {
3385
- weight = effM;
3386
- } else if (effM !== 255) {
3387
- weight = effM * globalAlpha + 128 >> 8;
3388
- }
3389
- if (weight === 0) {
3390
- dIdx++;
3391
- sIdx++;
3392
- mIdx++;
3393
- continue;
3394
- }
3395
- let finalCol = srcCol;
3396
- if (weight < 255) {
3397
- const a = srcAlpha * weight + 128 >> 8;
3398
- if (a === 0 && !isOverwrite) {
3399
- dIdx++;
3400
- sIdx++;
3401
- mIdx++;
3402
- continue;
3403
- }
3404
- finalCol = (srcCol & 16777215 | a << 24) >>> 0;
3405
- }
3406
- dst32[dIdx] = blendFn(finalCol, dst32[dIdx]);
3407
- dIdx++;
3408
- sIdx++;
3409
- mIdx++;
3410
- }
3411
- dIdx += dStride;
3412
- sIdx += sStride;
3413
- mIdx += mStride;
3414
- }
3415
- }
3416
-
3417
- // src/History/PixelMutator/mutatorBlendPixelDataAlphaMask.ts
3418
- var defaults13 = {
3419
- blendPixelDataAlphaMask
3420
- };
3421
- var mutatorBlendPixelDataAlphaMask = ((writer, deps = defaults13) => {
3422
- const {
3423
- blendPixelDataAlphaMask: blendPixelDataAlphaMask2 = defaults13.blendPixelDataAlphaMask
3424
- } = deps;
3425
- return {
3426
- blendPixelDataAlphaMask(src, mask, opts = {}) {
3427
- const x = opts.x ?? 0;
3428
- const y = opts.y ?? 0;
3429
- const w = opts.w ?? src.width;
3430
- const h = opts.h ?? src.height;
3431
- writer.accumulator.storeRegionBeforeState(x, y, w, h);
3432
- blendPixelDataAlphaMask2(writer.target, src, mask, opts);
3433
- }
3434
- };
3435
- });
3436
-
3437
- // src/PixelData/blendPixelDataBinaryMask.ts
3438
- function blendPixelDataBinaryMask(dst, src, binaryMask, opts = {}) {
3439
- const {
3440
- x: targetX = 0,
3441
- y: targetY = 0,
3442
- sx: sourceX = 0,
3443
- sy: sourceY = 0,
3444
- w: width = src.width,
3445
- h: height = src.height,
3446
- alpha: globalAlpha = 255,
3447
- blendFn = sourceOverPerfect,
3448
- mx = 0,
3449
- my = 0,
3450
- invertMask = false
3451
- } = opts;
3452
- if (globalAlpha === 0) return;
3453
- let x = targetX;
3454
- let y = targetY;
3455
- let sx = sourceX;
3456
- let sy = sourceY;
3457
- let w = width;
3458
- let h = height;
3459
- if (sx < 0) {
3460
- x -= sx;
3461
- w += sx;
3462
- sx = 0;
3463
- }
3464
- if (sy < 0) {
3465
- y -= sy;
3466
- h += sy;
3467
- sy = 0;
3468
- }
3469
- w = Math.min(w, src.width - sx);
3470
- h = Math.min(h, src.height - sy);
3471
- if (x < 0) {
3472
- sx -= x;
3473
- w += x;
3474
- x = 0;
3475
- }
3476
- if (y < 0) {
3477
- sy -= y;
3478
- h += y;
3479
- y = 0;
3480
- }
3481
- const actualW = Math.min(w, dst.width - x);
3482
- const actualH = Math.min(h, dst.height - y);
3483
- if (actualW <= 0 || actualH <= 0) return;
3484
- const dx = x - targetX | 0;
3485
- const dy = y - targetY | 0;
3486
- const dst32 = dst.data32;
3487
- const src32 = src.data32;
3488
- const dw = dst.width;
3489
- const sw = src.width;
3490
- const mPitch = binaryMask.w;
3491
- const maskData = binaryMask.data;
3492
- let dIdx = y * dw + x | 0;
3493
- let sIdx = sy * sw + sx | 0;
3494
- let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
3495
- const dStride = dw - actualW | 0;
3496
- const sStride = sw - actualW | 0;
3497
- const mStride = mPitch - actualW | 0;
3498
- const skipVal = invertMask ? 1 : 0;
3499
- const isOpaque = globalAlpha === 255;
3500
- const isOverwrite = blendFn.isOverwrite || false;
3501
- for (let iy = 0; iy < actualH; iy++) {
3502
- for (let ix = 0; ix < actualW; ix++) {
3503
- if (maskData[mIdx] === skipVal) {
3504
- dIdx++;
3505
- sIdx++;
3506
- mIdx++;
3507
- continue;
3508
- }
3509
- const srcCol = src32[sIdx];
3510
- const srcAlpha = srcCol >>> 24;
3511
- if (srcAlpha === 0 && !isOverwrite) {
3512
- dIdx++;
3513
- sIdx++;
3514
- mIdx++;
3515
- continue;
3516
- }
3517
- let finalCol = srcCol;
3518
- if (!isOpaque) {
3519
- const a = srcAlpha * globalAlpha + 128 >> 8;
3520
- if (a === 0 && !isOverwrite) {
3521
- dIdx++;
3522
- sIdx++;
3523
- mIdx++;
3524
- continue;
3525
- }
3526
- finalCol = (srcCol & 16777215 | a << 24) >>> 0;
3527
- }
3528
- dst32[dIdx] = blendFn(finalCol, dst32[dIdx]);
3529
- dIdx++;
3530
- sIdx++;
3531
- mIdx++;
3532
- }
3533
- dIdx += dStride;
3534
- sIdx += sStride;
3535
- mIdx += mStride;
3536
- }
3537
- }
3538
-
3539
- // src/History/PixelMutator/mutatorBlendPixelDataBinaryMask.ts
3540
- var defaults14 = {
3541
- blendPixelDataBinaryMask
3542
- };
3543
- var mutatorBlendPixelDataBinaryMask = ((writer, deps = defaults14) => {
3544
- const {
3545
- blendPixelDataBinaryMask: blendPixelDataBinaryMask2 = defaults14.blendPixelDataBinaryMask
3546
- } = deps;
3547
- return {
3548
- blendPixelDataBinaryMask(src, mask, opts = {}) {
3549
- const x = opts.x ?? 0;
3550
- const y = opts.y ?? 0;
3551
- const w = opts.w ?? src.width;
3552
- const h = opts.h ?? src.height;
3553
- writer.accumulator.storeRegionBeforeState(x, y, w, h);
3554
- blendPixelDataBinaryMask2(writer.target, src, mask, opts);
3555
- }
3556
- };
3557
- });
3558
-
3559
- // src/PixelData/fillPixelData.ts
3560
- var SCRATCH_RECT = makeClippedRect();
3561
- function fillPixelData(dst, color, _x, _y, _w, _h) {
3562
- let x;
3563
- let y;
3564
- let w;
3565
- let h;
3566
- if (typeof _x === "object") {
3567
- x = _x.x ?? 0;
3568
- y = _x.y ?? 0;
3569
- w = _x.w ?? dst.width;
3570
- h = _x.h ?? dst.height;
3571
- } else if (typeof _x === "number") {
3572
- x = _x;
3573
- y = _y;
3574
- w = _w;
3575
- h = _h;
3576
- } else {
3577
- x = 0;
3578
- y = 0;
3579
- w = dst.width;
3580
- h = dst.height;
3581
- }
3582
- const clip = resolveRectClipping(x, y, w, h, dst.width, dst.height, SCRATCH_RECT);
3583
- if (!clip.inBounds) return;
3584
- const {
3585
- x: finalX,
3586
- y: finalY,
3587
- w: actualW,
3588
- h: actualH
3589
- } = clip;
3590
- const dst32 = dst.data32;
3591
- const dw = dst.width;
3592
- if (actualW === dw && actualH === dst.height && finalX === 0 && finalY === 0) {
3593
- dst32.fill(color);
3594
- return;
3595
- }
3596
- for (let iy = 0; iy < actualH; iy++) {
3597
- const start = (finalY + iy) * dw + finalX;
3598
- const end = start + actualW;
3599
- dst32.fill(color, start, end);
3600
- }
3601
- }
3602
-
3603
- // src/History/PixelMutator/mutatorClear.ts
3604
- var defaults15 = {
3605
- fillPixelData
3606
- };
3607
- var mutatorClear = ((writer, deps = defaults15) => {
3608
- const {
3609
- fillPixelData: fillPixelData2 = defaults15.fillPixelData
3610
- } = deps;
3611
- return {
3612
- clear(rect = {}) {
3613
- const x = rect.x ?? 0;
3614
- const y = rect.y ?? 0;
3615
- const w = rect.w ?? writer.target.width;
3616
- const h = rect.h ?? writer.target.height;
3617
- writer.accumulator.storeRegionBeforeState(x, y, w, h);
3618
- fillPixelData2(writer.target, 0, x, y, w, h);
3619
- }
3620
- };
3621
- });
3622
-
3623
- // src/History/PixelMutator/mutatorFill.ts
3624
- var defaults16 = {
3625
- fillPixelData
3626
- };
3627
- var mutatorFill = ((writer, deps = defaults16) => {
3628
- const {
3629
- fillPixelData: fillPixelData2 = defaults16.fillPixelData
3630
- } = deps;
3631
- return {
3632
- fill(color, rect = {}) {
3633
- const {
3634
- x = 0,
3635
- y = 0,
3636
- w = writer.target.width,
3637
- h = writer.target.height
3638
- } = rect;
3639
- writer.accumulator.storeRegionBeforeState(x, y, w, h);
3640
- fillPixelData2(writer.target, color, x, y, w, h);
3641
- }
3642
- };
3643
- });
3644
-
3645
- // src/PixelData/fillPixelDataBinaryMask.ts
3646
- var SCRATCH_RECT2 = makeClippedRect();
3647
- function fillPixelDataBinaryMask(dst, color, mask, alpha = 255, x = 0, y = 0) {
3648
- if (alpha === 0) return;
3649
- const maskW = mask.w;
3650
- const maskH = mask.h;
3651
- const clip = resolveRectClipping(x, y, maskW, maskH, dst.width, dst.height, SCRATCH_RECT2);
3652
- if (!clip.inBounds) return;
3653
- const {
3654
- x: finalX,
3655
- y: finalY,
3656
- w: actualW,
3657
- h: actualH
3658
- } = clip;
3659
- const maskData = mask.data;
3660
- const dst32 = dst.data32;
3661
- const dw = dst.width;
3662
- let finalCol = color;
3663
- if (alpha < 255) {
3664
- const baseSrcAlpha = color >>> 24;
3665
- const colorRGB = color & 16777215;
3666
- const a = baseSrcAlpha * alpha + 128 >> 8;
3667
- finalCol = (colorRGB | a << 24) >>> 0;
3668
- }
3669
- for (let iy = 0; iy < actualH; iy++) {
3670
- const currentY = finalY + iy;
3671
- const maskY = currentY - y;
3672
- const maskOffset = maskY * maskW;
3673
- const dstRowOffset = currentY * dw;
3674
- for (let ix = 0; ix < actualW; ix++) {
3675
- const currentX = finalX + ix;
3676
- const maskX = currentX - x;
3677
- const maskIndex = maskOffset + maskX;
3678
- if (maskData[maskIndex]) {
3679
- dst32[dstRowOffset + currentX] = finalCol;
3680
- }
3681
- }
3682
- }
3683
- }
3684
-
3685
- // src/History/PixelMutator/mutatorFillBinaryMask.ts
3686
- var defaults17 = {
3687
- fillPixelDataBinaryMask
3688
- };
3689
- var mutatorFillBinaryMask = ((writer, deps = defaults17) => {
3690
- const {
3691
- fillPixelDataBinaryMask: fillPixelDataBinaryMask2 = defaults17.fillPixelDataBinaryMask
3692
- } = deps;
3693
- return {
3694
- fillBinaryMask(color, mask, alpha = 255, x = 0, y = 0) {
3695
- writer.accumulator.storeRegionBeforeState(x, y, mask.w, mask.h);
3696
- fillPixelDataBinaryMask2(writer.target, color, mask, alpha, x, y);
3697
- }
3698
- };
3699
- });
3700
-
3701
- // src/PixelData/invertPixelData.ts
3702
- var SCRATCH_RECT3 = makeClippedRect();
3703
- function invertPixelData(pixelData, opts = {}) {
3704
- const dst = pixelData;
3705
- const {
3706
- x: targetX = 0,
3707
- y: targetY = 0,
3708
- w: width = pixelData.width,
3709
- h: height = pixelData.height,
3710
- mask,
3711
- mx = 0,
3712
- my = 0,
3713
- invertMask = false
3714
- } = opts;
3715
- const clip = resolveRectClipping(targetX, targetY, width, height, dst.width, dst.height, SCRATCH_RECT3);
3716
- if (!clip.inBounds) return;
3717
- const {
3718
- x,
3719
- y,
3720
- w: actualW,
3721
- h: actualH
3722
- } = clip;
3549
+ }
3550
+ w = Math.min(w, dst.width - x);
3551
+ h = Math.min(h, dst.height - y);
3552
+ if (w <= 0 || h <= 0) return false;
3553
+ const mPitch = mask.w;
3554
+ if (mPitch <= 0) return false;
3555
+ const startX = mx + (x - targetX);
3556
+ const startY = my + (y - targetY);
3557
+ const sX0 = Math.max(0, startX);
3558
+ const sY0 = Math.max(0, startY);
3559
+ const sX1 = Math.min(mPitch, startX + w);
3560
+ const sY1 = Math.min(mask.h, startY + h);
3561
+ const finalW = sX1 - sX0;
3562
+ const finalH = sY1 - sY0;
3563
+ if (finalW <= 0 || finalH <= 0) {
3564
+ return false;
3565
+ }
3566
+ const xShift = sX0 - startX;
3567
+ const yShift = sY0 - startY;
3723
3568
  const dst32 = dst.data32;
3724
3569
  const dw = dst.width;
3725
- const mPitch = mask?.w ?? width;
3726
- const dx = x - targetX;
3727
- const dy = y - targetY;
3728
- let dIdx = y * dw + x;
3729
- let mIdx = (my + dy) * mPitch + (mx + dx);
3730
- const dStride = dw - actualW;
3731
- const mStride = mPitch - actualW;
3732
- if (mask) {
3733
- const maskData = mask.data;
3734
- for (let iy = 0; iy < actualH; iy++) {
3735
- for (let ix = 0; ix < actualW; ix++) {
3736
- const mVal = maskData[mIdx];
3737
- const isHit = invertMask ? mVal === 0 : mVal === 1;
3738
- if (isHit) {
3739
- dst32[dIdx] = dst32[dIdx] ^ 16777215;
3570
+ const dStride = dw - finalW;
3571
+ const mStride = mPitch - finalW;
3572
+ const maskData = mask.data;
3573
+ let dIdx = (y + yShift) * dw + (x + xShift);
3574
+ let mIdx = sY0 * mPitch + sX0;
3575
+ let didChange = false;
3576
+ for (let iy = 0; iy < finalH; iy++) {
3577
+ for (let ix = 0; ix < finalW; ix++) {
3578
+ const mVal = maskData[mIdx];
3579
+ const isMaskedOut = invertMask ? mVal !== 0 : mVal === 0;
3580
+ if (isMaskedOut) {
3581
+ const current = dst32[dIdx];
3582
+ const next = (current & 16777215) >>> 0;
3583
+ if (current !== next) {
3584
+ dst32[dIdx] = next;
3585
+ didChange = true;
3586
+ }
3587
+ } else if (globalAlpha !== 255) {
3588
+ const d = dst32[dIdx];
3589
+ const da = d >>> 24;
3590
+ if (da !== 0) {
3591
+ const finalAlpha = da === 255 ? globalAlpha : da * globalAlpha + 128 >> 8;
3592
+ const next = (d & 16777215 | finalAlpha << 24) >>> 0;
3593
+ if (d !== next) {
3594
+ dst32[dIdx] = next;
3595
+ didChange = true;
3596
+ }
3740
3597
  }
3741
- dIdx++;
3742
- mIdx++;
3743
- }
3744
- dIdx += dStride;
3745
- mIdx += mStride;
3746
- }
3747
- } else {
3748
- for (let iy = 0; iy < actualH; iy++) {
3749
- for (let ix = 0; ix < actualW; ix++) {
3750
- dst32[dIdx] = dst32[dIdx] ^ 16777215;
3751
- dIdx++;
3752
3598
  }
3753
- dIdx += dStride;
3599
+ dIdx++;
3600
+ mIdx++;
3754
3601
  }
3602
+ dIdx += dStride;
3603
+ mIdx += mStride;
3755
3604
  }
3605
+ return didChange;
3756
3606
  }
3757
3607
 
3758
- // src/History/PixelMutator/mutatorInvert.ts
3759
- var defaults18 = {
3760
- invertPixelData
3608
+ // src/History/PixelMutator/mutatorApplyBinaryMask.ts
3609
+ var defaults12 = {
3610
+ applyBinaryMaskToPixelData
3761
3611
  };
3762
- var mutatorInvert = ((writer, deps = defaults18) => {
3612
+ var mutatorApplyBinaryMask = ((writer, deps = defaults12) => {
3763
3613
  const {
3764
- invertPixelData: invertPixelData2 = defaults18.invertPixelData
3614
+ applyBinaryMaskToPixelData: applyBinaryMaskToPixelData2 = defaults12.applyBinaryMaskToPixelData
3765
3615
  } = deps;
3766
3616
  return {
3767
- invert(opts = {}) {
3617
+ applyBinaryMask(mask, opts = {}) {
3618
+ let target = writer.config.target;
3768
3619
  const {
3769
3620
  x = 0,
3770
3621
  y = 0,
3771
- w = writer.target.width,
3772
- h = writer.target.height
3622
+ w = target.width,
3623
+ h = target.height
3773
3624
  } = opts;
3774
- writer.accumulator.storeRegionBeforeState(x, y, w, h);
3775
- invertPixelData2(writer.target, opts);
3625
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3626
+ return didChange(applyBinaryMaskToPixelData2(target, mask, opts));
3776
3627
  }
3777
3628
  };
3778
3629
  });
3779
3630
 
3780
- // src/History/PixelMutator.ts
3781
- function makeFullPixelMutator(writer) {
3782
- return {
3783
- // @sort
3784
- ...mutatorApplyAlphaMask(writer),
3785
- ...mutatorApplyBinaryMask(writer),
3786
- ...mutatorApplyCircleBrush(writer),
3787
- ...mutatorApplyCircleBrushStroke(writer),
3788
- ...mutatorApplyCirclePencil(writer),
3789
- ...mutatorApplyCirclePencilStroke(writer),
3790
- ...mutatorApplyRectBrush(writer),
3791
- ...mutatorApplyRectBrushStroke(writer),
3792
- ...mutatorApplyRectPencil(writer),
3793
- ...mutatorApplyRectPencilStroke(writer),
3794
- ...mutatorBlendColor(writer),
3795
- ...mutatorBlendPixel(writer),
3796
- ...mutatorBlendPixelData(writer),
3797
- ...mutatorBlendPixelDataAlphaMask(writer),
3798
- ...mutatorBlendPixelDataBinaryMask(writer),
3799
- ...mutatorClear(writer),
3800
- ...mutatorFill(writer),
3801
- ...mutatorFillBinaryMask(writer),
3802
- ...mutatorInvert(writer)
3803
- };
3804
- }
3805
-
3806
- // src/History/PixelWriter.ts
3807
- var PixelWriter = class {
3808
- target;
3809
- historyManager;
3810
- accumulator;
3811
- config;
3812
- mutator;
3813
- constructor(target, mutatorFactory, {
3814
- tileSize = 256,
3815
- maxHistorySteps = 50,
3816
- historyManager = new HistoryManager(maxHistorySteps)
3817
- } = {}) {
3818
- this.target = target;
3819
- this.config = new PixelEngineConfig(tileSize);
3820
- this.historyManager = historyManager;
3821
- this.accumulator = new PixelAccumulator(target, this.config);
3822
- this.mutator = mutatorFactory(this);
3823
- }
3824
- withHistory(cb) {
3825
- cb(this.mutator);
3826
- this.captureHistory();
3827
- }
3828
- captureHistory() {
3829
- const beforeTiles = this.accumulator.beforeTiles;
3830
- if (beforeTiles.length === 0) return;
3831
- const afterTiles = this.accumulator.extractAfterTiles();
3832
- const patch = {
3833
- beforeTiles,
3834
- afterTiles
3835
- };
3836
- const target = this.target;
3837
- const tileSize = this.config.tileSize;
3838
- const accumulator = this.accumulator;
3839
- const action = {
3840
- undo: () => applyPatchTiles(target, patch.beforeTiles, tileSize),
3841
- redo: () => applyPatchTiles(target, patch.afterTiles, tileSize),
3842
- dispose: () => accumulator.recyclePatch(patch)
3843
- };
3844
- this.historyManager.commit(action);
3845
- this.accumulator.reset();
3846
- }
3847
- };
3848
-
3849
3631
  // src/ImageData/copyImageData.ts
3850
3632
  function copyImageData({
3851
3633
  data,
@@ -3967,44 +3749,14 @@ function resampleImageData(source, factor) {
3967
3749
  return new ImageData(uint8ClampedArray, width, height);
3968
3750
  }
3969
3751
 
3970
- // src/ImageData/resizeImageData.ts
3971
- function resizeImageData(target, newWidth, newHeight, offsetX = 0, offsetY = 0) {
3972
- const result = new ImageData(newWidth, newHeight);
3973
- const {
3974
- width: oldW,
3975
- height: oldH,
3976
- data: oldData
3977
- } = target;
3978
- const newData = result.data;
3979
- const x0 = Math.max(0, offsetX);
3980
- const y0 = Math.max(0, offsetY);
3981
- const x1 = Math.min(newWidth, offsetX + oldW);
3982
- const y1 = Math.min(newHeight, offsetY + oldH);
3983
- if (x1 <= x0 || y1 <= y0) {
3984
- return result;
3985
- }
3986
- const rowCount = y1 - y0;
3987
- const rowLen = (x1 - x0) * 4;
3988
- for (let row = 0; row < rowCount; row++) {
3989
- const dstY = y0 + row;
3990
- const srcY = dstY - offsetY;
3991
- const srcX = x0 - offsetX;
3992
- const dstStart = (dstY * newWidth + x0) * 4;
3993
- const srcStart = (srcY * oldW + srcX) * 4;
3994
- newData.set(oldData.subarray(srcStart, srcStart + rowLen), dstStart);
3995
- }
3996
- return result;
3997
- }
3998
-
3999
3752
  // src/ImageData/ReusableImageData.ts
4000
3753
  function makeReusableImageData() {
4001
3754
  let imageData = null;
4002
3755
  return function getReusableImageData(width, height) {
4003
- const hasInstance = !!imageData;
4004
- const widthMatches = hasInstance && imageData.width === width;
4005
- const heightMatches = hasInstance && imageData.height === height;
4006
- if (!widthMatches || !heightMatches) {
3756
+ if (imageData === null || imageData.width !== width || imageData.height !== height) {
4007
3757
  imageData = new ImageData(width, height);
3758
+ } else {
3759
+ imageData.data.fill(0);
4008
3760
  }
4009
3761
  return imageData;
4010
3762
  };
@@ -4411,62 +4163,6 @@ function makeBinaryMask(w, h, data) {
4411
4163
  };
4412
4164
  }
4413
4165
 
4414
- // src/Mask/CircleBrushAlphaMask.ts
4415
- function makeCircleBrushAlphaMask(size, fallOff = () => 1) {
4416
- const area = size * size;
4417
- const data = new Uint8Array(area);
4418
- const radius = size / 2;
4419
- const invR = 1 / radius;
4420
- const minOffset = -Math.ceil(radius - 0.5);
4421
- for (let y = 0; y < size; y++) {
4422
- for (let x = 0; x < size; x++) {
4423
- const dx = x - radius + 0.5;
4424
- const dy = y - radius + 0.5;
4425
- const distSqr = dx * dx + dy * dy;
4426
- if (distSqr <= radius * radius) {
4427
- const dist = Math.sqrt(distSqr);
4428
- data[y * size + x] = fallOff(1 - dist * invR) * 255 | 0;
4429
- }
4430
- }
4431
- }
4432
- return {
4433
- type: 0 /* ALPHA */,
4434
- data,
4435
- w: size,
4436
- h: size,
4437
- radius,
4438
- size,
4439
- minOffset
4440
- };
4441
- }
4442
-
4443
- // src/Mask/CircleBrushBinaryMask.ts
4444
- function makeCircleBrushBinaryMask(size) {
4445
- const area = size * size;
4446
- const data = new Uint8Array(area);
4447
- const radius = size / 2;
4448
- const minOffset = -Math.ceil(radius - 0.5);
4449
- for (let y = 0; y < size; y++) {
4450
- for (let x = 0; x < size; x++) {
4451
- const dx = x - radius + 0.5;
4452
- const dy = y - radius + 0.5;
4453
- const distSqr = dx * dx + dy * dy;
4454
- if (distSqr <= radius * radius) {
4455
- data[y * size + x] = 1;
4456
- }
4457
- }
4458
- }
4459
- return {
4460
- type: 1 /* BINARY */,
4461
- data,
4462
- w: size,
4463
- h: size,
4464
- radius,
4465
- size,
4466
- minOffset
4467
- };
4468
- }
4469
-
4470
4166
  // src/Mask/applyBinaryMaskToAlphaMask.ts
4471
4167
  function applyBinaryMaskToAlphaMask(alphaMaskDst, binaryMaskSrc, opts = {}) {
4472
4168
  const {
@@ -4737,57 +4433,6 @@ function setMaskData(mask, width, height, data) {
4737
4433
  mask.data = data;
4738
4434
  }
4739
4435
 
4740
- // src/MaskRect/subtractBinaryMaskRects.ts
4741
- function subtractBinaryMaskRects(current, subtracting) {
4742
- let result = [...current];
4743
- for (const sub of subtracting) {
4744
- const next = [];
4745
- for (const r of result) {
4746
- const ix = Math.max(r.x, sub.x);
4747
- const iy = Math.max(r.y, sub.y);
4748
- const ix2 = Math.min(r.x + r.w, sub.x + sub.w);
4749
- const iy2 = Math.min(r.y + r.h, sub.y + sub.h);
4750
- if (ix >= ix2 || iy >= iy2) {
4751
- next.push(r);
4752
- continue;
4753
- }
4754
- if (r.y < iy) pushPiece(next, r, r.x, r.y, r.w, iy - r.y);
4755
- if (iy2 < r.y + r.h) pushPiece(next, r, r.x, iy2, r.w, r.y + r.h - iy2);
4756
- if (r.x < ix) pushPiece(next, r, r.x, iy, ix - r.x, iy2 - iy);
4757
- if (ix2 < r.x + r.w) pushPiece(next, r, ix2, iy, r.x + r.w - ix2, iy2 - iy);
4758
- }
4759
- result = next;
4760
- }
4761
- return result;
4762
- }
4763
- function pushPiece(dest, r, x, y, w, h) {
4764
- if (r.data === null || r.data === void 0) {
4765
- dest.push({
4766
- x,
4767
- y,
4768
- w,
4769
- h,
4770
- data: null,
4771
- type: null
4772
- });
4773
- return;
4774
- }
4775
- const lx = x - r.x;
4776
- const ly = y - r.y;
4777
- const data = new Uint8Array(w * h);
4778
- for (let row = 0; row < h; row++) {
4779
- data.set(r.data.subarray((ly + row) * r.w + lx, (ly + row) * r.w + lx + w), row * w);
4780
- }
4781
- dest.push({
4782
- x,
4783
- y,
4784
- w,
4785
- h,
4786
- data,
4787
- type: 1 /* BINARY */
4788
- });
4789
- }
4790
-
4791
4436
  // src/Rect/getRectsBounds.ts
4792
4437
  function getRectsBounds(rects) {
4793
4438
  if (rects.length === 1) return {
@@ -4895,13 +4540,64 @@ function mergeBinaryMaskRects(current, adding) {
4895
4540
  }
4896
4541
  if (!merged) next.push(r);
4897
4542
  }
4898
- rects.splice(0, rects.length, ...next);
4543
+ rects.splice(0, rects.length, ...next);
4544
+ }
4545
+ return rects;
4546
+ }
4547
+
4548
+ // src/MaskRect/subtractBinaryMaskRects.ts
4549
+ function subtractBinaryMaskRects(current, subtracting) {
4550
+ let result = [...current];
4551
+ for (const sub of subtracting) {
4552
+ const next = [];
4553
+ for (const r of result) {
4554
+ const ix = Math.max(r.x, sub.x);
4555
+ const iy = Math.max(r.y, sub.y);
4556
+ const ix2 = Math.min(r.x + r.w, sub.x + sub.w);
4557
+ const iy2 = Math.min(r.y + r.h, sub.y + sub.h);
4558
+ if (ix >= ix2 || iy >= iy2) {
4559
+ next.push(r);
4560
+ continue;
4561
+ }
4562
+ if (r.y < iy) pushPiece(next, r, r.x, r.y, r.w, iy - r.y);
4563
+ if (iy2 < r.y + r.h) pushPiece(next, r, r.x, iy2, r.w, r.y + r.h - iy2);
4564
+ if (r.x < ix) pushPiece(next, r, r.x, iy, ix - r.x, iy2 - iy);
4565
+ if (ix2 < r.x + r.w) pushPiece(next, r, ix2, iy, r.x + r.w - ix2, iy2 - iy);
4566
+ }
4567
+ result = next;
4899
4568
  }
4900
- return rects;
4569
+ return result;
4570
+ }
4571
+ function pushPiece(dest, r, x, y, w, h) {
4572
+ if (r.data === null || r.data === void 0) {
4573
+ dest.push({
4574
+ x,
4575
+ y,
4576
+ w,
4577
+ h,
4578
+ data: null,
4579
+ type: null
4580
+ });
4581
+ return;
4582
+ }
4583
+ const lx = x - r.x;
4584
+ const ly = y - r.y;
4585
+ const data = new Uint8Array(w * h);
4586
+ for (let row = 0; row < h; row++) {
4587
+ data.set(r.data.subarray((ly + row) * r.w + lx, (ly + row) * r.w + lx + w), row * w);
4588
+ }
4589
+ dest.push({
4590
+ x,
4591
+ y,
4592
+ w,
4593
+ h,
4594
+ data,
4595
+ type: 1 /* BINARY */
4596
+ });
4901
4597
  }
4902
4598
 
4903
4599
  // src/PixelData/PixelData.ts
4904
- var PixelData = class _PixelData {
4600
+ var PixelData = class {
4905
4601
  data32;
4906
4602
  imageData;
4907
4603
  width;
@@ -4919,30 +4615,191 @@ var PixelData = class _PixelData {
4919
4615
  this.width = imageData.width;
4920
4616
  this.height = imageData.height;
4921
4617
  }
4922
- // should only be used for debug and testing
4923
- copy() {
4924
- const data = this.imageData.data;
4925
- const buffer = new Uint8ClampedArray(data);
4926
- const Ctor = this.imageData.constructor;
4927
- const isCtorValid = typeof Ctor === "function";
4928
- let newImageData;
4929
- if (isCtorValid && Ctor !== Object) {
4930
- const ImageConstructor = Ctor;
4931
- newImageData = new ImageConstructor(buffer, this.width, this.height);
4932
- } else {
4933
- newImageData = {
4934
- width: this.width,
4935
- height: this.height,
4936
- data: buffer
4937
- };
4618
+ };
4619
+
4620
+ // src/PixelData/blendColorPixelDataAlphaMask.ts
4621
+ function blendColorPixelDataAlphaMask(dst, color, mask, opts = {}) {
4622
+ const targetX = opts.x ?? 0;
4623
+ const targetY = opts.y ?? 0;
4624
+ const w = opts.w ?? mask.w;
4625
+ const h = opts.h ?? mask.h;
4626
+ const globalAlpha = opts.alpha ?? 255;
4627
+ const blendFn = opts.blendFn ?? sourceOverPerfect;
4628
+ const mx = opts.mx ?? 0;
4629
+ const my = opts.my ?? 0;
4630
+ const invertMask = opts.invertMask ?? false;
4631
+ if (globalAlpha === 0) return false;
4632
+ const baseSrcAlpha = color >>> 24;
4633
+ const isOverwrite = blendFn.isOverwrite || false;
4634
+ if (baseSrcAlpha === 0 && !isOverwrite) return false;
4635
+ let x = targetX;
4636
+ let y = targetY;
4637
+ let actualW = w;
4638
+ let actualH = h;
4639
+ if (x < 0) {
4640
+ actualW += x;
4641
+ x = 0;
4642
+ }
4643
+ if (y < 0) {
4644
+ actualH += y;
4645
+ y = 0;
4646
+ }
4647
+ actualW = Math.min(actualW, dst.width - x);
4648
+ actualH = Math.min(actualH, dst.height - y);
4649
+ if (actualW <= 0 || actualH <= 0) return false;
4650
+ const dx = x - targetX | 0;
4651
+ const dy = y - targetY | 0;
4652
+ const dst32 = dst.data32;
4653
+ const dw = dst.width;
4654
+ const mPitch = mask.w;
4655
+ const maskData = mask.data;
4656
+ let dIdx = y * dw + x | 0;
4657
+ let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
4658
+ const dStride = dw - actualW | 0;
4659
+ const mStride = mPitch - actualW | 0;
4660
+ const isOpaque = globalAlpha === 255;
4661
+ const colorRGB = color & 16777215;
4662
+ let didChange = false;
4663
+ for (let iy = 0; iy < actualH; iy++) {
4664
+ for (let ix = 0; ix < actualW; ix++) {
4665
+ const mVal = maskData[mIdx];
4666
+ const effM = invertMask ? 255 - mVal : mVal;
4667
+ if (effM === 0) {
4668
+ dIdx++;
4669
+ mIdx++;
4670
+ continue;
4671
+ }
4672
+ let weight = globalAlpha;
4673
+ if (isOpaque) {
4674
+ weight = effM;
4675
+ } else if (effM !== 255) {
4676
+ weight = effM * globalAlpha + 128 >> 8;
4677
+ }
4678
+ if (weight === 0) {
4679
+ dIdx++;
4680
+ mIdx++;
4681
+ continue;
4682
+ }
4683
+ let finalCol = color;
4684
+ if (weight < 255) {
4685
+ const a = baseSrcAlpha * weight + 128 >> 8;
4686
+ if (a === 0 && !isOverwrite) {
4687
+ dIdx++;
4688
+ mIdx++;
4689
+ continue;
4690
+ }
4691
+ finalCol = (colorRGB | a << 24) >>> 0;
4692
+ }
4693
+ const current = dst32[dIdx];
4694
+ const next = blendFn(finalCol, current);
4695
+ if (current !== next) {
4696
+ dst32[dIdx] = next;
4697
+ didChange = true;
4698
+ }
4699
+ dIdx++;
4700
+ mIdx++;
4701
+ }
4702
+ dIdx += dStride;
4703
+ mIdx += mStride;
4704
+ }
4705
+ return didChange;
4706
+ }
4707
+
4708
+ // src/PixelData/blendColorPixelDataBinaryMask.ts
4709
+ function blendColorPixelDataBinaryMask(dst, color, mask, opts = {}) {
4710
+ const targetX = opts.x ?? 0;
4711
+ const targetY = opts.y ?? 0;
4712
+ let w = opts.w ?? mask.w;
4713
+ let h = opts.h ?? mask.h;
4714
+ const globalAlpha = opts.alpha ?? 255;
4715
+ const blendFn = opts.blendFn ?? sourceOverPerfect;
4716
+ const mx = opts.mx ?? 0;
4717
+ const my = opts.my ?? 0;
4718
+ const invertMask = opts.invertMask ?? false;
4719
+ if (globalAlpha === 0) return false;
4720
+ const baseSrcAlpha = color >>> 24;
4721
+ const isOverwrite = blendFn.isOverwrite || false;
4722
+ if (baseSrcAlpha === 0 && !isOverwrite) return false;
4723
+ let x = targetX;
4724
+ let y = targetY;
4725
+ if (x < 0) {
4726
+ w += x;
4727
+ x = 0;
4728
+ }
4729
+ if (y < 0) {
4730
+ h += y;
4731
+ y = 0;
4732
+ }
4733
+ const actualW = Math.min(w, dst.width - x);
4734
+ const actualH = Math.min(h, dst.height - y);
4735
+ if (actualW <= 0 || actualH <= 0) return false;
4736
+ let baseColorWithGlobalAlpha = color;
4737
+ if (globalAlpha < 255) {
4738
+ const a = baseSrcAlpha * globalAlpha + 128 >> 8;
4739
+ if (a === 0 && !isOverwrite) return false;
4740
+ baseColorWithGlobalAlpha = (color & 16777215 | a << 24) >>> 0;
4741
+ }
4742
+ const dx = x - targetX | 0;
4743
+ const dy = y - targetY | 0;
4744
+ const dst32 = dst.data32;
4745
+ const dw = dst.width;
4746
+ const mPitch = mask.w;
4747
+ const maskData = mask.data;
4748
+ let dIdx = y * dw + x | 0;
4749
+ let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
4750
+ const dStride = dw - actualW | 0;
4751
+ const mStride = mPitch - actualW | 0;
4752
+ const skipVal = invertMask ? 1 : 0;
4753
+ let didChange = false;
4754
+ for (let iy = 0; iy < actualH; iy++) {
4755
+ for (let ix = 0; ix < actualW; ix++) {
4756
+ if (maskData[mIdx] === skipVal) {
4757
+ dIdx++;
4758
+ mIdx++;
4759
+ continue;
4760
+ }
4761
+ const current = dst32[dIdx];
4762
+ const next = blendFn(baseColorWithGlobalAlpha, current);
4763
+ if (current !== next) {
4764
+ dst32[dIdx] = next;
4765
+ didChange = true;
4766
+ }
4767
+ dIdx++;
4768
+ mIdx++;
4938
4769
  }
4939
- return new _PixelData(newImageData);
4770
+ dIdx += dStride;
4771
+ mIdx += mStride;
4940
4772
  }
4773
+ return didChange;
4774
+ }
4775
+
4776
+ // src/PixelData/blendPixelDataPaintBuffer.ts
4777
+ var SCRATCH_OPTS = {
4778
+ x: 0,
4779
+ y: 0,
4780
+ alpha: 255,
4781
+ blendFn: void 0
4941
4782
  };
4783
+ function blendPixelDataPaintBuffer(paintBuffer, target, alpha = 255, blendFn, blendPixelDataFn = blendPixelData) {
4784
+ const tileShift = paintBuffer.config.tileShift;
4785
+ const lookup = paintBuffer.lookup;
4786
+ for (let i = 0; i < lookup.length; i++) {
4787
+ const tile = lookup[i];
4788
+ if (tile) {
4789
+ const x = tile.tx << tileShift;
4790
+ const y = tile.ty << tileShift;
4791
+ SCRATCH_OPTS.x = x;
4792
+ SCRATCH_OPTS.y = y;
4793
+ SCRATCH_OPTS.alpha = alpha;
4794
+ SCRATCH_OPTS.blendFn = blendFn;
4795
+ blendPixelDataFn(target, tile, SCRATCH_OPTS);
4796
+ }
4797
+ }
4798
+ }
4942
4799
 
4943
4800
  // src/PixelData/clearPixelData.ts
4944
4801
  function clearPixelData(dst, rect) {
4945
- fillPixelData(dst, 0, rect);
4802
+ fillPixelDataFast(dst, 0, rect);
4946
4803
  }
4947
4804
 
4948
4805
  // src/PixelData/extractPixelDataBuffer.ts
@@ -5161,6 +5018,162 @@ function writePixelDataBuffer(target, data, _x, _y, _w, _h) {
5161
5018
  dstData.set(data.subarray(srcStart, srcStart + copyW), dstStart);
5162
5019
  }
5163
5020
  }
5021
+
5022
+ // src/PixelData/writePaintBufferToPixelData.ts
5023
+ function writePaintBufferToPixelData(target, paintBuffer, writePixelDataBufferFn = writePixelDataBuffer) {
5024
+ const tileShift = paintBuffer.config.tileShift;
5025
+ const lookup = paintBuffer.lookup;
5026
+ for (let i = 0; i < lookup.length; i++) {
5027
+ const tile = lookup[i];
5028
+ if (tile) {
5029
+ const dx = tile.tx << tileShift;
5030
+ const dy = tile.ty << tileShift;
5031
+ writePixelDataBufferFn(target, tile.data32, dx, dy, tile.width, tile.height);
5032
+ }
5033
+ }
5034
+ }
5035
+
5036
+ // src/Paint/makeCirclePaintAlphaMask.ts
5037
+ function makeCirclePaintAlphaMask(size, fallOff = (d) => d) {
5038
+ const area = size * size;
5039
+ const data = new Uint8Array(area);
5040
+ const radius = size / 2;
5041
+ const invR = 1 / radius;
5042
+ const centerOffset = -Math.ceil(radius - 0.5);
5043
+ for (let y = 0; y < size; y++) {
5044
+ const rowOffset = y * size;
5045
+ const dy = y - radius + 0.5;
5046
+ const dy2 = dy * dy;
5047
+ for (let x = 0; x < size; x++) {
5048
+ const dx = x - radius + 0.5;
5049
+ const distSqr = dx * dx + dy2;
5050
+ if (distSqr <= radius * radius) {
5051
+ const dist = Math.sqrt(distSqr) * invR;
5052
+ const strength = fallOff(1 - dist);
5053
+ if (strength > 0) {
5054
+ const intensity = strength * 255 | 0;
5055
+ data[rowOffset + x] = Math.max(0, Math.min(255, intensity));
5056
+ }
5057
+ }
5058
+ }
5059
+ }
5060
+ return {
5061
+ type: 0 /* ALPHA */,
5062
+ data,
5063
+ w: size,
5064
+ h: size,
5065
+ centerOffsetX: centerOffset,
5066
+ centerOffsetY: centerOffset
5067
+ };
5068
+ }
5069
+
5070
+ // src/Paint/makeCirclePaintBinaryMask.ts
5071
+ function makeCirclePaintBinaryMask(size) {
5072
+ const area = size * size;
5073
+ const data = new Uint8Array(area);
5074
+ const radius = size / 2;
5075
+ const centerOffset = -Math.ceil(radius - 0.5);
5076
+ for (let y = 0; y < size; y++) {
5077
+ for (let x = 0; x < size; x++) {
5078
+ const dx = x - radius + 0.5;
5079
+ const dy = y - radius + 0.5;
5080
+ const distSqr = dx * dx + dy * dy;
5081
+ if (distSqr <= radius * radius) {
5082
+ data[y * size + x] = 1;
5083
+ }
5084
+ }
5085
+ }
5086
+ return {
5087
+ type: 1 /* BINARY */,
5088
+ data,
5089
+ w: size,
5090
+ h: size,
5091
+ centerOffsetX: centerOffset,
5092
+ centerOffsetY: centerOffset
5093
+ };
5094
+ }
5095
+
5096
+ // src/Paint/makePaintMask.ts
5097
+ function makePaintBinaryMask(mask) {
5098
+ return {
5099
+ type: 1 /* BINARY */,
5100
+ data: mask.data,
5101
+ w: mask.w,
5102
+ h: mask.h,
5103
+ centerOffsetX: -(mask.w >> 1),
5104
+ centerOffsetY: -(mask.h >> 1)
5105
+ };
5106
+ }
5107
+ function makePaintAlphaMask(mask) {
5108
+ return {
5109
+ type: 0 /* ALPHA */,
5110
+ data: mask.data,
5111
+ w: mask.w,
5112
+ h: mask.h,
5113
+ centerOffsetX: -(mask.w >> 1),
5114
+ centerOffsetY: -(mask.h >> 1)
5115
+ };
5116
+ }
5117
+
5118
+ // src/Paint/makeRectFalloffPaintAlphaMask.ts
5119
+ function makeRectFalloffPaintAlphaMask(width, height, fallOff = (d) => d) {
5120
+ const fPx = Math.floor(width / 2);
5121
+ const fPy = Math.floor(height / 2);
5122
+ const invHalfW = 2 / width;
5123
+ const invHalfH = 2 / height;
5124
+ const offX = width % 2 === 0 ? 0.5 : 0;
5125
+ const offY = height % 2 === 0 ? 0.5 : 0;
5126
+ const area = width * height;
5127
+ const data = new Uint8Array(area);
5128
+ for (let y = 0; y < height; y++) {
5129
+ const dy = Math.abs(y - fPy + offY) * invHalfH;
5130
+ const rowOffset = y * width;
5131
+ for (let x = 0; x < width; x++) {
5132
+ const dx = Math.abs(x - fPx + offX) * invHalfW;
5133
+ const dist = dx > dy ? dx : dy;
5134
+ const strength = fallOff(1 - dist);
5135
+ if (strength > 0) {
5136
+ const intensity = strength * 255 | 0;
5137
+ data[rowOffset + x] = Math.max(0, Math.min(255, intensity));
5138
+ }
5139
+ }
5140
+ }
5141
+ return {
5142
+ type: 0 /* ALPHA */,
5143
+ data,
5144
+ w: width,
5145
+ h: height,
5146
+ centerOffsetX: -macro_halfAndFloor(width),
5147
+ centerOffsetY: -macro_halfAndFloor(height)
5148
+ };
5149
+ }
5150
+
5151
+ // src/Paint/PaintBufferCanvasRenderer.ts
5152
+ function makePaintBufferCanvasRenderer(paintBuffer, offscreenCanvasClass = OffscreenCanvas) {
5153
+ const config = paintBuffer.config;
5154
+ const tileSize = config.tileSize;
5155
+ const tileShift = config.tileShift;
5156
+ const lookup = paintBuffer.lookup;
5157
+ const canvas = new offscreenCanvasClass(tileSize, tileSize);
5158
+ const ctx = canvas.getContext("2d");
5159
+ if (!ctx) throw new Error(CANVAS_CTX_FAILED);
5160
+ ctx.imageSmoothingEnabled = false;
5161
+ return function drawPaintBuffer(targetCtx, alpha = 255, compOperation = "source-over") {
5162
+ targetCtx.globalAlpha = alpha / 255;
5163
+ targetCtx.globalCompositeOperation = compOperation;
5164
+ for (let i = 0; i < lookup.length; i++) {
5165
+ const tile = lookup[i];
5166
+ if (tile) {
5167
+ const dx = tile.tx << tileShift;
5168
+ const dy = tile.ty << tileShift;
5169
+ ctx.putImageData(tile.imageData, 0, 0);
5170
+ targetCtx.drawImage(canvas, dx, dy);
5171
+ }
5172
+ }
5173
+ targetCtx.globalAlpha = 1;
5174
+ targetCtx.globalCompositeOperation = "source-over";
5175
+ };
5176
+ }
5164
5177
  // Annotate the CommonJS export names for ESM import in node:
5165
5178
  0 && (module.exports = {
5166
5179
  BASE_FAST_BLEND_MODE_FUNCTIONS,
@@ -5171,27 +5184,29 @@ function writePixelDataBuffer(target, data, _x, _y, _w, _h) {
5171
5184
  IndexedImage,
5172
5185
  MaskType,
5173
5186
  OFFSCREEN_CANVAS_CTX_FAILED,
5187
+ PaintBuffer,
5174
5188
  PixelAccumulator,
5175
5189
  PixelBuffer32,
5176
5190
  PixelData,
5177
5191
  PixelEngineConfig,
5178
5192
  PixelTile,
5193
+ PixelTilePool,
5179
5194
  PixelWriter,
5180
5195
  UnsupportedFormatError,
5181
5196
  applyAlphaMaskToPixelData,
5182
5197
  applyBinaryMaskToAlphaMask,
5183
5198
  applyBinaryMaskToPixelData,
5184
- applyCircleBrushToPixelData,
5185
5199
  applyPatchTiles,
5186
- applyRectBrushToPixelData,
5187
5200
  base64DecodeArrayBuffer,
5188
5201
  base64EncodeArrayBuffer,
5189
5202
  blendColorPixelData,
5190
5203
  blendColorPixelDataAlphaMask,
5191
5204
  blendColorPixelDataBinaryMask,
5205
+ blendPixel,
5192
5206
  blendPixelData,
5193
5207
  blendPixelDataAlphaMask,
5194
5208
  blendPixelDataBinaryMask,
5209
+ blendPixelDataPaintBuffer,
5195
5210
  clearPixelData,
5196
5211
  color32ToCssRGBA,
5197
5212
  color32ToHex,
@@ -5225,14 +5240,11 @@ function writePixelDataBuffer(target, data, _x, _y, _w, _h) {
5225
5240
  fileToImageData,
5226
5241
  fillPixelData,
5227
5242
  fillPixelDataBinaryMask,
5243
+ fillPixelDataFast,
5228
5244
  floodFillSelection,
5229
5245
  forEachLinePoint,
5230
- getCircleBrushOrPencilBounds,
5231
- getCircleBrushOrPencilStrokeBounds,
5232
5246
  getImageDataFromClipboard,
5233
5247
  getIndexedImageColorCounts,
5234
- getRectBrushOrPencilBounds,
5235
- getRectBrushOrPencilStrokeBounds,
5236
5248
  getRectsBounds,
5237
5249
  getSupportedPixelFormats,
5238
5250
  hardLightFast,
@@ -5265,15 +5277,22 @@ function writePixelDataBuffer(target, data, _x, _y, _w, _h) {
5265
5277
  makeAlphaMask,
5266
5278
  makeBinaryMask,
5267
5279
  makeBlendModeRegistry,
5268
- makeCircleBrushAlphaMask,
5269
- makeCircleBrushBinaryMask,
5280
+ makeCanvasFrameRenderer,
5281
+ makeCirclePaintAlphaMask,
5282
+ makeCirclePaintBinaryMask,
5270
5283
  makeFastBlendModeRegistry,
5271
5284
  makeFullPixelMutator,
5285
+ makeHistoryAction,
5272
5286
  makeImageDataLike,
5287
+ makePaintAlphaMask,
5288
+ makePaintBinaryMask,
5289
+ makePaintBufferCanvasRenderer,
5273
5290
  makePerfectBlendModeRegistry,
5274
5291
  makePixelCanvas,
5292
+ makeRectFalloffPaintAlphaMask,
5275
5293
  makeReusableCanvas,
5276
5294
  makeReusableImageData,
5295
+ makeReusableOffscreenCanvas,
5277
5296
  merge2BinaryMaskRects,
5278
5297
  mergeAlphaMasks,
5279
5298
  mergeBinaryMaskRects,
@@ -5282,14 +5301,6 @@ function writePixelDataBuffer(target, data, _x, _y, _w, _h) {
5282
5301
  multiplyPerfect,
5283
5302
  mutatorApplyAlphaMask,
5284
5303
  mutatorApplyBinaryMask,
5285
- mutatorApplyCircleBrush,
5286
- mutatorApplyCircleBrushStroke,
5287
- mutatorApplyCirclePencil,
5288
- mutatorApplyCirclePencilStroke,
5289
- mutatorApplyRectBrush,
5290
- mutatorApplyRectBrushStroke,
5291
- mutatorApplyRectPencil,
5292
- mutatorApplyRectPencilStroke,
5293
5304
  mutatorBlendColor,
5294
5305
  mutatorBlendPixel,
5295
5306
  mutatorBlendPixelData,
@@ -5298,6 +5309,7 @@ function writePixelDataBuffer(target, data, _x, _y, _w, _h) {
5298
5309
  mutatorClear,
5299
5310
  mutatorFill,
5300
5311
  mutatorFillBinaryMask,
5312
+ mutatorFillRect,
5301
5313
  mutatorInvert,
5302
5314
  overlayFast,
5303
5315
  overlayPerfect,
@@ -5329,6 +5341,7 @@ function writePixelDataBuffer(target, data, _x, _y, _w, _h) {
5329
5341
  subtractFast,
5330
5342
  subtractPerfect,
5331
5343
  toBlendModeIndexAndName,
5344
+ trimMaskRectBounds,
5332
5345
  trimRectBounds,
5333
5346
  uInt32ArrayToImageData,
5334
5347
  uInt32ArrayToImageDataLike,
@@ -5344,6 +5357,7 @@ function writePixelDataBuffer(target, data, _x, _y, _w, _h) {
5344
5357
  writeImageDataBuffer,
5345
5358
  writeImageDataToClipboard,
5346
5359
  writeImgBlobToClipboard,
5360
+ writePaintBufferToPixelData,
5347
5361
  writePixelDataBuffer
5348
5362
  });
5349
5363
  //# sourceMappingURL=index.dev.cjs.map