pixel-data-js 0.23.1 → 0.24.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 (66) hide show
  1. package/dist/index.dev.cjs +1024 -596
  2. package/dist/index.dev.cjs.map +1 -1
  3. package/dist/index.dev.js +1010 -592
  4. package/dist/index.dev.js.map +1 -1
  5. package/dist/index.prod.cjs +1024 -596
  6. package/dist/index.prod.cjs.map +1 -1
  7. package/dist/index.prod.d.ts +280 -165
  8. package/dist/index.prod.js +1010 -592
  9. package/dist/index.prod.js.map +1 -1
  10. package/package.json +3 -2
  11. package/src/Canvas/CanvasFrameRenderer.ts +57 -0
  12. package/src/Canvas/ReusableCanvas.ts +60 -11
  13. package/src/History/HistoryAction.ts +38 -0
  14. package/src/History/HistoryManager.ts +4 -8
  15. package/src/History/PixelAccumulator.ts +95 -80
  16. package/src/History/PixelEngineConfig.ts +18 -6
  17. package/src/History/PixelMutator/mutatorApplyAlphaMask.ts +6 -6
  18. package/src/History/PixelMutator/mutatorApplyBinaryMask.ts +6 -6
  19. package/src/History/PixelMutator/mutatorApplyCircleBrushStroke.ts +6 -5
  20. package/src/History/PixelMutator/mutatorApplyCirclePencil.ts +22 -22
  21. package/src/History/PixelMutator/mutatorApplyCirclePencilStroke.ts +6 -5
  22. package/src/History/PixelMutator/mutatorApplyRectBrush.ts +19 -19
  23. package/src/History/PixelMutator/mutatorApplyRectBrushStroke.ts +6 -4
  24. package/src/History/PixelMutator/mutatorApplyRectPencil.ts +20 -20
  25. package/src/History/PixelMutator/mutatorApplyRectPencilStroke.ts +6 -4
  26. package/src/History/PixelMutator/mutatorBlendColor.ts +8 -5
  27. package/src/History/PixelMutator/mutatorBlendColorCircleMask.ts +71 -0
  28. package/src/History/PixelMutator/mutatorBlendPixel.ts +22 -26
  29. package/src/History/PixelMutator/mutatorBlendPixelData.ts +5 -3
  30. package/src/History/PixelMutator/mutatorBlendPixelDataAlphaMask.ts +5 -3
  31. package/src/History/PixelMutator/mutatorBlendPixelDataBinaryMask.ts +5 -3
  32. package/src/History/PixelMutator/mutatorClear.ts +6 -5
  33. package/src/History/PixelMutator/mutatorFill.ts +34 -9
  34. package/src/History/PixelMutator/mutatorFillBinaryMask.ts +4 -2
  35. package/src/History/PixelMutator/mutatorInvert.ts +8 -4
  36. package/src/History/PixelMutator.ts +4 -3
  37. package/src/History/PixelPatchTiles.ts +3 -15
  38. package/src/History/PixelWriter.ts +29 -33
  39. package/src/ImageData/ReusableImageData.ts +3 -5
  40. package/src/Mask/{CircleBrushAlphaMask.ts → CircleAlphaMask.ts} +2 -2
  41. package/src/Mask/{CircleBrushBinaryMask.ts → CircleBinaryMask.ts} +2 -2
  42. package/src/PixelData/PixelData.ts +1 -27
  43. package/src/PixelData/applyAlphaMaskToPixelData.ts +19 -9
  44. package/src/PixelData/applyBinaryMaskToPixelData.ts +24 -17
  45. package/src/PixelData/applyRectBrushToPixelData.ts +18 -5
  46. package/src/PixelData/blendColorPixelData.ts +31 -7
  47. package/src/PixelData/blendColorPixelDataAlphaMask.ts +16 -6
  48. package/src/PixelData/blendColorPixelDataBinaryMask.ts +16 -7
  49. package/src/PixelData/{applyCircleBrushToPixelData.ts → blendColorPixelDataCircleMask.ts} +11 -10
  50. package/src/PixelData/blendPixel.ts +47 -0
  51. package/src/PixelData/blendPixelData.ts +14 -4
  52. package/src/PixelData/blendPixelDataAlphaMask.ts +12 -4
  53. package/src/PixelData/blendPixelDataBinaryMask.ts +13 -4
  54. package/src/PixelData/blendPixelDataPaintBuffer.ts +37 -0
  55. package/src/PixelData/clearPixelData.ts +2 -2
  56. package/src/PixelData/fillPixelData.ts +26 -16
  57. package/src/PixelData/fillPixelDataBinaryMask.ts +12 -4
  58. package/src/PixelData/fillPixelDataFast.ts +94 -0
  59. package/src/PixelData/invertPixelData.ts +4 -2
  60. package/src/PixelTile/PaintBuffer.ts +122 -0
  61. package/src/PixelTile/PaintBufferRenderer.ts +40 -0
  62. package/src/PixelTile/PixelTile.ts +21 -0
  63. package/src/PixelTile/PixelTilePool.ts +63 -0
  64. package/src/_types.ts +9 -9
  65. package/src/index.ts +16 -6
  66. package/src/History/PixelMutator/mutatorApplyCircleBrush.ts +0 -78
@@ -28,17 +28,18 @@ __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
44
  applyRectBrushToPixelData: () => applyRectBrushToPixelData,
44
45
  base64DecodeArrayBuffer: () => base64DecodeArrayBuffer,
@@ -46,9 +47,12 @@ __export(src_exports, {
46
47
  blendColorPixelData: () => blendColorPixelData,
47
48
  blendColorPixelDataAlphaMask: () => blendColorPixelDataAlphaMask,
48
49
  blendColorPixelDataBinaryMask: () => blendColorPixelDataBinaryMask,
50
+ blendColorPixelDataCircleMask: () => blendColorPixelDataCircleMask,
51
+ blendPixel: () => blendPixel,
49
52
  blendPixelData: () => blendPixelData,
50
53
  blendPixelDataAlphaMask: () => blendPixelDataAlphaMask,
51
54
  blendPixelDataBinaryMask: () => blendPixelDataBinaryMask,
55
+ blendPixelDataPaintBuffer: () => blendPixelDataPaintBuffer,
52
56
  clearPixelData: () => clearPixelData,
53
57
  color32ToCssRGBA: () => color32ToCssRGBA,
54
58
  color32ToHex: () => color32ToHex,
@@ -82,6 +86,7 @@ __export(src_exports, {
82
86
  fileToImageData: () => fileToImageData,
83
87
  fillPixelData: () => fillPixelData,
84
88
  fillPixelDataBinaryMask: () => fillPixelDataBinaryMask,
89
+ fillPixelDataFast: () => fillPixelDataFast,
85
90
  floodFillSelection: () => floodFillSelection,
86
91
  forEachLinePoint: () => forEachLinePoint,
87
92
  getCircleBrushOrPencilBounds: () => getCircleBrushOrPencilBounds,
@@ -122,15 +127,19 @@ __export(src_exports, {
122
127
  makeAlphaMask: () => makeAlphaMask,
123
128
  makeBinaryMask: () => makeBinaryMask,
124
129
  makeBlendModeRegistry: () => makeBlendModeRegistry,
125
- makeCircleBrushAlphaMask: () => makeCircleBrushAlphaMask,
126
- makeCircleBrushBinaryMask: () => makeCircleBrushBinaryMask,
130
+ makeCanvasFrameRenderer: () => makeCanvasFrameRenderer,
131
+ makeCircleAlphaMask: () => makeCircleAlphaMask,
132
+ makeCircleBinaryMask: () => makeCircleBinaryMask,
127
133
  makeFastBlendModeRegistry: () => makeFastBlendModeRegistry,
128
134
  makeFullPixelMutator: () => makeFullPixelMutator,
135
+ makeHistoryAction: () => makeHistoryAction,
129
136
  makeImageDataLike: () => makeImageDataLike,
137
+ makePaintBufferRenderer: () => makePaintBufferRenderer,
130
138
  makePerfectBlendModeRegistry: () => makePerfectBlendModeRegistry,
131
139
  makePixelCanvas: () => makePixelCanvas,
132
140
  makeReusableCanvas: () => makeReusableCanvas,
133
141
  makeReusableImageData: () => makeReusableImageData,
142
+ makeReusableOffscreenCanvas: () => makeReusableOffscreenCanvas,
134
143
  merge2BinaryMaskRects: () => merge2BinaryMaskRects,
135
144
  mergeAlphaMasks: () => mergeAlphaMasks,
136
145
  mergeBinaryMaskRects: () => mergeBinaryMaskRects,
@@ -139,7 +148,6 @@ __export(src_exports, {
139
148
  multiplyPerfect: () => multiplyPerfect,
140
149
  mutatorApplyAlphaMask: () => mutatorApplyAlphaMask,
141
150
  mutatorApplyBinaryMask: () => mutatorApplyBinaryMask,
142
- mutatorApplyCircleBrush: () => mutatorApplyCircleBrush,
143
151
  mutatorApplyCircleBrushStroke: () => mutatorApplyCircleBrushStroke,
144
152
  mutatorApplyCirclePencil: () => mutatorApplyCirclePencil,
145
153
  mutatorApplyCirclePencilStroke: () => mutatorApplyCirclePencilStroke,
@@ -148,6 +156,7 @@ __export(src_exports, {
148
156
  mutatorApplyRectPencil: () => mutatorApplyRectPencil,
149
157
  mutatorApplyRectPencilStroke: () => mutatorApplyRectPencilStroke,
150
158
  mutatorBlendColor: () => mutatorBlendColor,
159
+ mutatorBlendColorCircleMask: () => mutatorBlendColorCircleMask,
151
160
  mutatorBlendPixel: () => mutatorBlendPixel,
152
161
  mutatorBlendPixelData: () => mutatorBlendPixelData,
153
162
  mutatorBlendPixelDataAlphaMask: () => mutatorBlendPixelDataAlphaMask,
@@ -155,6 +164,7 @@ __export(src_exports, {
155
164
  mutatorClear: () => mutatorClear,
156
165
  mutatorFill: () => mutatorFill,
157
166
  mutatorFillBinaryMask: () => mutatorFillBinaryMask,
167
+ mutatorFillRect: () => mutatorFillRect,
158
168
  mutatorInvert: () => mutatorInvert,
159
169
  overlayFast: () => overlayFast,
160
170
  overlayPerfect: () => overlayPerfect,
@@ -643,6 +653,26 @@ function floodFillSelection(img, startX, startY, {
643
653
  };
644
654
  }
645
655
 
656
+ // src/Algorithm/forEachLinePoint.ts
657
+ function forEachLinePoint(x0, y0, x1, y1, callback) {
658
+ const dx = x1 - x0;
659
+ const dy = y1 - y0;
660
+ const steps = Math.max(Math.abs(dx), Math.abs(dy));
661
+ if (steps === 0) {
662
+ callback(x0, y0);
663
+ return;
664
+ }
665
+ const xInc = dx / steps;
666
+ const yInc = dy / steps;
667
+ let curX = x0;
668
+ let curY = y0;
669
+ for (let i = 0; i <= steps; i++) {
670
+ callback(curX, curY);
671
+ curX += xInc;
672
+ curY += yInc;
673
+ }
674
+ }
675
+
646
676
  // src/BlendModes/blend-modes.ts
647
677
  var BaseBlendMode = {
648
678
  overwrite: 0,
@@ -1720,51 +1750,104 @@ var getKeyByValue = (obj, value) => {
1720
1750
  var OFFSCREEN_CANVAS_CTX_FAILED = "Failed to create OffscreenCanvas context";
1721
1751
  var CANVAS_CTX_FAILED = "Failed to create Canvas context";
1722
1752
 
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
1753
  // src/Canvas/ReusableCanvas.ts
1740
1754
  function makeReusableCanvas() {
1755
+ return makeReusableCanvasMeta((w, h) => {
1756
+ const canvas = document.createElement("canvas");
1757
+ canvas.width = w;
1758
+ canvas.height = h;
1759
+ return canvas;
1760
+ });
1761
+ }
1762
+ function makeReusableOffscreenCanvas() {
1763
+ return makeReusableCanvasMeta((w, h) => new OffscreenCanvas(w, h));
1764
+ }
1765
+ function makeReusableCanvasMeta(factory) {
1741
1766
  let canvas = null;
1742
1767
  let ctx = null;
1768
+ const result = {
1769
+ canvas: null,
1770
+ ctx: null
1771
+ };
1743
1772
  function get2(width, height) {
1744
1773
  if (canvas === null) {
1745
- canvas = document.createElement("canvas");
1774
+ canvas = factory(width, height);
1746
1775
  ctx = canvas.getContext("2d");
1747
- if (!ctx) throw new Error(CANVAS_CTX_FAILED);
1776
+ if (!ctx) {
1777
+ throw new Error(CANVAS_CTX_FAILED);
1778
+ }
1779
+ ctx.imageSmoothingEnabled = false;
1780
+ result.canvas = canvas;
1781
+ result.ctx = ctx;
1782
+ return result;
1748
1783
  }
1749
1784
  if (canvas.width !== width || canvas.height !== height) {
1750
1785
  canvas.width = width;
1751
1786
  canvas.height = height;
1752
1787
  ctx.imageSmoothingEnabled = false;
1753
1788
  } else {
1789
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
1754
1790
  ctx.clearRect(0, 0, width, height);
1755
1791
  }
1756
- return {
1757
- canvas,
1758
- ctx
1759
- };
1792
+ return result;
1760
1793
  }
1761
1794
  get2.reset = () => {
1762
1795
  canvas = null;
1763
1796
  ctx = null;
1797
+ result.canvas = null;
1798
+ result.ctx = null;
1764
1799
  };
1765
1800
  return get2;
1766
1801
  }
1767
1802
 
1803
+ // src/Canvas/CanvasFrameRenderer.ts
1804
+ var defaults = {
1805
+ makeReusableCanvas
1806
+ };
1807
+ function makeCanvasFrameRenderer(deps = defaults) {
1808
+ const {
1809
+ makeReusableCanvas: makeReusableCanvas2 = defaults.makeReusableCanvas
1810
+ } = deps;
1811
+ const bufferCanvas = makeReusableCanvas2();
1812
+ return function renderCanvasFrame(pixelCanvas, scale, getImageData, drawPixelLayer, drawScreenLayer) {
1813
+ const {
1814
+ canvas,
1815
+ ctx
1816
+ } = pixelCanvas;
1817
+ const {
1818
+ ctx: pxCtx,
1819
+ canvas: pxCanvas
1820
+ } = bufferCanvas(canvas.width, canvas.height);
1821
+ const img = getImageData();
1822
+ if (img) {
1823
+ pxCtx.putImageData(img, 0, 0);
1824
+ }
1825
+ drawPixelLayer?.(pxCtx);
1826
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
1827
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1828
+ ctx.setTransform(scale, 0, 0, scale, 0, 0);
1829
+ ctx.drawImage(pxCanvas, 0, 0);
1830
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
1831
+ drawScreenLayer?.(ctx, scale);
1832
+ };
1833
+ }
1834
+
1835
+ // src/Canvas/PixelCanvas.ts
1836
+ function makePixelCanvas(canvas) {
1837
+ const ctx = canvas.getContext("2d");
1838
+ if (!ctx) throw new Error(CANVAS_CTX_FAILED);
1839
+ ctx.imageSmoothingEnabled = false;
1840
+ return {
1841
+ canvas,
1842
+ ctx,
1843
+ resize(w, h) {
1844
+ canvas.width = w;
1845
+ canvas.height = h;
1846
+ ctx.imageSmoothingEnabled = false;
1847
+ }
1848
+ };
1849
+ }
1850
+
1768
1851
  // src/ImageData/imgBlobToImageData.ts
1769
1852
  async function imgBlobToImageData(blob) {
1770
1853
  let bitmap = null;
@@ -1824,6 +1907,50 @@ async function writeImageDataToClipboard(imageData) {
1824
1907
  return writeImgBlobToClipboard(blob);
1825
1908
  }
1826
1909
 
1910
+ // src/History/PixelPatchTiles.ts
1911
+ function applyPatchTiles(target, tiles, tileSize) {
1912
+ for (let i = 0; i < tiles.length; i++) {
1913
+ const tile = tiles[i];
1914
+ if (!tile) continue;
1915
+ const dst = target.data32;
1916
+ const src = tile.data32;
1917
+ const dstWidth = target.width;
1918
+ const dstHeight = target.height;
1919
+ const startX = tile.tx * tileSize;
1920
+ const startY = tile.ty * tileSize;
1921
+ const copyWidth = Math.max(0, Math.min(tileSize, dstWidth - startX));
1922
+ if (copyWidth <= 0) continue;
1923
+ for (let ly = 0; ly < tileSize; ly++) {
1924
+ const globalY = startY + ly;
1925
+ if (globalY >= dstHeight) break;
1926
+ const dstIndex = globalY * dstWidth + startX;
1927
+ const srcIndex = ly * tileSize;
1928
+ const rowData = src.subarray(srcIndex, srcIndex + copyWidth);
1929
+ dst.set(rowData, dstIndex);
1930
+ }
1931
+ }
1932
+ }
1933
+
1934
+ // src/History/HistoryAction.ts
1935
+ function makeHistoryAction(writer, patch, after, afterUndo, afterRedo, applyPatchTilesFn = applyPatchTiles) {
1936
+ const target = writer.config.target;
1937
+ const tileSize = writer.config.tileSize;
1938
+ const accumulator = writer.accumulator;
1939
+ return {
1940
+ undo: () => {
1941
+ applyPatchTilesFn(target, patch.beforeTiles, tileSize);
1942
+ afterUndo?.();
1943
+ after?.();
1944
+ },
1945
+ redo: () => {
1946
+ applyPatchTilesFn(target, patch.afterTiles, tileSize);
1947
+ afterRedo?.();
1948
+ after?.();
1949
+ },
1950
+ dispose: () => accumulator.recyclePatch(patch)
1951
+ };
1952
+ }
1953
+
1827
1954
  // src/History/HistoryManager.ts
1828
1955
  var HistoryManager = class {
1829
1956
  constructor(maxSteps = 50) {
@@ -1882,126 +2009,91 @@ var HistoryManager = class {
1882
2009
  }
1883
2010
  };
1884
2011
 
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
2012
  // src/History/PixelAccumulator.ts
1919
2013
  var PixelAccumulator = class {
1920
- constructor(target, config) {
1921
- this.target = target;
2014
+ constructor(config, tilePool) {
1922
2015
  this.config = config;
2016
+ this.tilePool = tilePool;
1923
2017
  this.lookup = [];
1924
2018
  this.beforeTiles = [];
1925
- this.pool = [];
1926
2019
  }
1927
2020
  lookup;
1928
2021
  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
2022
  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
- }
2023
+ this.tilePool.releaseTiles(patch.beforeTiles);
2024
+ this.tilePool.releaseTiles(patch.afterTiles);
1955
2025
  }
1956
2026
  /**
1957
2027
  * @param x pixel x coordinate
1958
2028
  * @param y pixel y coordinate
1959
2029
  */
1960
- storeTileBeforeState(x, y) {
1961
- let target = this.target;
2030
+ storePixelBeforeState(x, y) {
1962
2031
  let shift = this.config.tileShift;
1963
- let columns = target.width + this.config.tileMask >> shift;
2032
+ let columns = this.config.targetColumns;
1964
2033
  let tx = x >> shift;
1965
2034
  let ty = y >> shift;
1966
2035
  let id = ty * columns + tx;
1967
2036
  let tile = this.lookup[id];
2037
+ let added = false;
1968
2038
  if (!tile) {
1969
- tile = this.getTile(id, tx, ty);
2039
+ tile = this.tilePool.getTile(id, tx, ty);
1970
2040
  this.extractState(tile);
1971
2041
  this.lookup[id] = tile;
1972
2042
  this.beforeTiles.push(tile);
2043
+ added = true;
1973
2044
  }
2045
+ return (didChange) => {
2046
+ if (!didChange && added) {
2047
+ this.beforeTiles.pop();
2048
+ this.lookup[id] = void 0;
2049
+ this.tilePool.releaseTile(tile);
2050
+ }
2051
+ return didChange;
2052
+ };
1974
2053
  }
1975
2054
  /**
1976
- *
1977
2055
  * @param x pixel x coordinate
1978
2056
  * @param y pixel y coordinate
1979
2057
  * @param w pixel width
1980
2058
  * @param h pixel height
1981
2059
  */
1982
2060
  storeRegionBeforeState(x, y, w, h) {
1983
- let target = this.target;
1984
2061
  let shift = this.config.tileShift;
1985
- let columns = target.width + this.config.tileMask >> shift;
2062
+ let columns = this.config.targetColumns;
1986
2063
  let startX = x >> shift;
1987
2064
  let startY = y >> shift;
1988
2065
  let endX = x + w - 1 >> shift;
1989
2066
  let endY = y + h - 1 >> shift;
2067
+ let startIndex = this.beforeTiles.length;
1990
2068
  for (let ty = startY; ty <= endY; ty++) {
1991
2069
  for (let tx = startX; tx <= endX; tx++) {
1992
2070
  let id = ty * columns + tx;
1993
2071
  let tile = this.lookup[id];
1994
2072
  if (!tile) {
1995
- tile = this.getTile(id, tx, ty);
2073
+ tile = this.tilePool.getTile(id, tx, ty);
1996
2074
  this.extractState(tile);
1997
2075
  this.lookup[id] = tile;
1998
2076
  this.beforeTiles.push(tile);
1999
2077
  }
2000
2078
  }
2001
2079
  }
2080
+ return (didChange) => {
2081
+ if (!didChange) {
2082
+ let length = this.beforeTiles.length;
2083
+ for (let i = startIndex; i < length; i++) {
2084
+ let t = this.beforeTiles[i];
2085
+ if (t) {
2086
+ this.lookup[t.id] = void 0;
2087
+ this.tilePool.releaseTile(t);
2088
+ }
2089
+ }
2090
+ this.beforeTiles.length = startIndex;
2091
+ }
2092
+ return didChange;
2093
+ };
2002
2094
  }
2003
2095
  extractState(tile) {
2004
- let target = this.target;
2096
+ let target = this.config.target;
2005
2097
  let TILE_SIZE = this.config.tileSize;
2006
2098
  let dst = tile.data32;
2007
2099
  let src = target.data32;
@@ -2009,7 +2101,12 @@ var PixelAccumulator = class {
2009
2101
  let startY = tile.ty * TILE_SIZE;
2010
2102
  let targetWidth = target.width;
2011
2103
  let targetHeight = target.height;
2012
- let copyWidth = Math.max(0, Math.min(TILE_SIZE, targetWidth - startX));
2104
+ if (startX >= targetWidth || startX + TILE_SIZE <= 0 || startY >= targetHeight || startY + TILE_SIZE <= 0) {
2105
+ dst.fill(0);
2106
+ return;
2107
+ }
2108
+ let srcOffsetX = Math.max(0, -startX);
2109
+ let copyWidth = Math.max(0, Math.min(TILE_SIZE - srcOffsetX, targetWidth - Math.max(0, startX)));
2013
2110
  for (let ly = 0; ly < TILE_SIZE; ly++) {
2014
2111
  let globalY = startY + ly;
2015
2112
  let dstIndex = ly * TILE_SIZE;
@@ -2017,47 +2114,77 @@ var PixelAccumulator = class {
2017
2114
  dst.fill(0, dstIndex, dstIndex + TILE_SIZE);
2018
2115
  continue;
2019
2116
  }
2020
- let srcIndex = globalY * targetWidth + startX;
2117
+ let srcIndex = globalY * targetWidth + Math.max(0, startX);
2021
2118
  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);
2119
+ dst.set(rowData, dstIndex + srcOffsetX);
2120
+ if (srcOffsetX > 0) {
2121
+ dst.fill(0, dstIndex, dstIndex + srcOffsetX);
2122
+ }
2123
+ if (srcOffsetX + copyWidth < TILE_SIZE) {
2124
+ dst.fill(0, dstIndex + srcOffsetX + copyWidth, dstIndex + TILE_SIZE);
2025
2125
  }
2026
2126
  }
2027
2127
  }
2028
- extractAfterTiles() {
2128
+ extractPatch() {
2029
2129
  let afterTiles = [];
2030
2130
  let length = this.beforeTiles.length;
2031
2131
  for (let i = 0; i < length; i++) {
2032
2132
  let beforeTile = this.beforeTiles[i];
2033
2133
  if (beforeTile) {
2034
- let afterTile = this.getTile(beforeTile.id, beforeTile.tx, beforeTile.ty);
2134
+ let afterTile = this.tilePool.getTile(beforeTile.id, beforeTile.tx, beforeTile.ty);
2035
2135
  this.extractState(afterTile);
2036
2136
  afterTiles.push(afterTile);
2037
2137
  }
2038
2138
  }
2039
- return afterTiles;
2040
- }
2041
- reset() {
2042
- this.lookup = [];
2139
+ let beforeTiles = this.beforeTiles;
2043
2140
  this.beforeTiles = [];
2141
+ this.lookup.length = 0;
2142
+ return {
2143
+ beforeTiles,
2144
+ afterTiles
2145
+ };
2146
+ }
2147
+ rollback() {
2148
+ let target = this.config.target;
2149
+ let tileSize = this.config.tileSize;
2150
+ let length = this.beforeTiles.length;
2151
+ applyPatchTiles(target, this.beforeTiles, tileSize);
2152
+ for (let i = 0; i < length; i++) {
2153
+ let tile = this.beforeTiles[i];
2154
+ if (tile) {
2155
+ this.lookup[tile.id] = void 0;
2156
+ this.tilePool.releaseTile(tile);
2157
+ }
2158
+ }
2159
+ this.beforeTiles.length = 0;
2160
+ this.lookup.length = 0;
2044
2161
  }
2045
2162
  };
2046
2163
 
2047
2164
  // src/History/PixelEngineConfig.ts
2048
2165
  var PixelEngineConfig = class {
2049
2166
  tileSize;
2167
+ // pixelX = tileX << tileShift
2168
+ // pixelY = tileY << tileShift
2050
2169
  tileShift;
2051
2170
  tileMask;
2052
2171
  tileArea;
2053
- constructor(tileSize = 256) {
2172
+ target;
2173
+ targetColumns = 0;
2174
+ constructor(tileSize, target) {
2054
2175
  if ((tileSize & tileSize - 1) !== 0) {
2055
2176
  throw new Error("tileSize must be a power of 2");
2056
2177
  }
2057
2178
  this.tileSize = tileSize;
2058
- this.tileShift = Math.log2(tileSize);
2179
+ this.tileShift = 31 - Math.clz32(tileSize);
2059
2180
  this.tileMask = tileSize - 1;
2060
2181
  this.tileArea = tileSize * tileSize;
2182
+ this.setTarget(target);
2183
+ }
2184
+ setTarget(target) {
2185
+ ;
2186
+ this.target = target;
2187
+ this.targetColumns = target.width + this.tileMask >> this.tileShift;
2061
2188
  }
2062
2189
  };
2063
2190
 
@@ -2073,7 +2200,7 @@ function applyAlphaMaskToPixelData(dst, mask, opts = {}) {
2073
2200
  my = 0,
2074
2201
  invertMask = false
2075
2202
  } = opts;
2076
- if (globalAlpha === 0) return;
2203
+ if (globalAlpha === 0) return false;
2077
2204
  let x = targetX;
2078
2205
  let y = targetY;
2079
2206
  let w = width;
@@ -2088,10 +2215,10 @@ function applyAlphaMaskToPixelData(dst, mask, opts = {}) {
2088
2215
  }
2089
2216
  w = Math.min(w, dst.width - x);
2090
2217
  h = Math.min(h, dst.height - y);
2091
- if (w <= 0) return;
2092
- if (h <= 0) return;
2218
+ if (w <= 0) return false;
2219
+ if (h <= 0) return false;
2093
2220
  const mPitch = mask.w;
2094
- if (mPitch <= 0) return;
2221
+ if (mPitch <= 0) return false;
2095
2222
  const startX = mx + (x - targetX);
2096
2223
  const startY = my + (y - targetY);
2097
2224
  const sX0 = Math.max(0, startX);
@@ -2100,8 +2227,8 @@ function applyAlphaMaskToPixelData(dst, mask, opts = {}) {
2100
2227
  const sY1 = Math.min(mask.h, startY + h);
2101
2228
  const finalW = sX1 - sX0;
2102
2229
  const finalH = sY1 - sY0;
2103
- if (finalW <= 0) return;
2104
- if (finalH <= 0) return;
2230
+ if (finalW <= 0) return false;
2231
+ if (finalH <= 0) return false;
2105
2232
  const xShift = sX0 - startX;
2106
2233
  const yShift = sY0 - startY;
2107
2234
  const dst32 = dst.data32;
@@ -2111,6 +2238,7 @@ function applyAlphaMaskToPixelData(dst, mask, opts = {}) {
2111
2238
  const maskData = mask.data;
2112
2239
  let dIdx = (y + yShift) * dw + (x + xShift);
2113
2240
  let mIdx = sY0 * mPitch + sX0;
2241
+ let didChange = false;
2114
2242
  for (let iy = 0; iy < h; iy++) {
2115
2243
  for (let ix = 0; ix < w; ix++) {
2116
2244
  const mVal = maskData[mIdx];
@@ -2127,12 +2255,18 @@ function applyAlphaMaskToPixelData(dst, mask, opts = {}) {
2127
2255
  }
2128
2256
  if (weight === 0) {
2129
2257
  dst32[dIdx] = (dst32[dIdx] & 16777215) >>> 0;
2258
+ didChange = true;
2130
2259
  } else if (weight !== 255) {
2131
2260
  const d = dst32[dIdx];
2132
2261
  const da = d >>> 24;
2133
2262
  if (da !== 0) {
2134
2263
  const finalAlpha = da === 255 ? weight : da * weight + 128 >> 8;
2135
- dst32[dIdx] = (d & 16777215 | finalAlpha << 24) >>> 0;
2264
+ const current = dst32[dIdx];
2265
+ const next = (d & 16777215 | finalAlpha << 24) >>> 0;
2266
+ if (current !== next) {
2267
+ dst32[dIdx] = next;
2268
+ didChange = true;
2269
+ }
2136
2270
  }
2137
2271
  }
2138
2272
  dIdx++;
@@ -2141,27 +2275,28 @@ function applyAlphaMaskToPixelData(dst, mask, opts = {}) {
2141
2275
  dIdx += dStride;
2142
2276
  mIdx += mStride;
2143
2277
  }
2278
+ return didChange;
2144
2279
  }
2145
2280
 
2146
2281
  // src/History/PixelMutator/mutatorApplyAlphaMask.ts
2147
- var defaults = {
2282
+ var defaults2 = {
2148
2283
  applyAlphaMaskToPixelData
2149
2284
  };
2150
- var mutatorApplyAlphaMask = ((writer, deps = defaults) => {
2285
+ var mutatorApplyAlphaMask = ((writer, deps = defaults2) => {
2151
2286
  const {
2152
- applyAlphaMaskToPixelData: applyAlphaMaskToPixelData2 = defaults.applyAlphaMaskToPixelData
2287
+ applyAlphaMaskToPixelData: applyAlphaMaskToPixelData2 = defaults2.applyAlphaMaskToPixelData
2153
2288
  } = deps;
2154
2289
  return {
2155
- applyAlphaMask: (mask, opts = {}) => {
2156
- let target = writer.target;
2290
+ applyAlphaMask(mask, opts = {}) {
2291
+ let target = writer.config.target;
2157
2292
  const {
2158
2293
  x = 0,
2159
2294
  y = 0,
2160
- w = writer.target.width,
2161
- h = writer.target.height
2295
+ w = target.width,
2296
+ h = target.height
2162
2297
  } = opts;
2163
- writer.accumulator.storeRegionBeforeState(x, y, w, h);
2164
- applyAlphaMaskToPixelData2(target, mask, opts);
2298
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2299
+ return didChange(applyAlphaMaskToPixelData2(target, mask, opts));
2165
2300
  }
2166
2301
  };
2167
2302
  });
@@ -2178,7 +2313,7 @@ function applyBinaryMaskToPixelData(dst, mask, opts = {}) {
2178
2313
  my = 0,
2179
2314
  invertMask = false
2180
2315
  } = opts;
2181
- if (globalAlpha === 0) return;
2316
+ if (globalAlpha === 0) return false;
2182
2317
  let x = targetX;
2183
2318
  let y = targetY;
2184
2319
  let w = width;
@@ -2193,10 +2328,9 @@ function applyBinaryMaskToPixelData(dst, mask, opts = {}) {
2193
2328
  }
2194
2329
  w = Math.min(w, dst.width - x);
2195
2330
  h = Math.min(h, dst.height - y);
2196
- if (w <= 0) return;
2197
- if (h <= 0) return;
2331
+ if (w <= 0 || h <= 0) return false;
2198
2332
  const mPitch = mask.w;
2199
- if (mPitch <= 0) return;
2333
+ if (mPitch <= 0) return false;
2200
2334
  const startX = mx + (x - targetX);
2201
2335
  const startY = my + (y - targetY);
2202
2336
  const sX0 = Math.max(0, startX);
@@ -2205,8 +2339,9 @@ function applyBinaryMaskToPixelData(dst, mask, opts = {}) {
2205
2339
  const sY1 = Math.min(mask.h, startY + h);
2206
2340
  const finalW = sX1 - sX0;
2207
2341
  const finalH = sY1 - sY0;
2208
- if (finalW <= 0) return;
2209
- if (finalH <= 0) return;
2342
+ if (finalW <= 0 || finalH <= 0) {
2343
+ return false;
2344
+ }
2210
2345
  const xShift = sX0 - startX;
2211
2346
  const yShift = sY0 - startY;
2212
2347
  const dst32 = dst.data32;
@@ -2216,18 +2351,28 @@ function applyBinaryMaskToPixelData(dst, mask, opts = {}) {
2216
2351
  const maskData = mask.data;
2217
2352
  let dIdx = (y + yShift) * dw + (x + xShift);
2218
2353
  let mIdx = sY0 * mPitch + sX0;
2219
- for (let iy = 0; iy < h; iy++) {
2220
- for (let ix = 0; ix < w; ix++) {
2354
+ let didChange = false;
2355
+ for (let iy = 0; iy < finalH; iy++) {
2356
+ for (let ix = 0; ix < finalW; ix++) {
2221
2357
  const mVal = maskData[mIdx];
2222
2358
  const isMaskedOut = invertMask ? mVal !== 0 : mVal === 0;
2223
2359
  if (isMaskedOut) {
2224
- dst32[dIdx] = (dst32[dIdx] & 16777215) >>> 0;
2360
+ const current = dst32[dIdx];
2361
+ const next = (current & 16777215) >>> 0;
2362
+ if (current !== next) {
2363
+ dst32[dIdx] = next;
2364
+ didChange = true;
2365
+ }
2225
2366
  } else if (globalAlpha !== 255) {
2226
2367
  const d = dst32[dIdx];
2227
2368
  const da = d >>> 24;
2228
2369
  if (da !== 0) {
2229
2370
  const finalAlpha = da === 255 ? globalAlpha : da * globalAlpha + 128 >> 8;
2230
- dst32[dIdx] = (d & 16777215 | finalAlpha << 24) >>> 0;
2371
+ const next = (d & 16777215 | finalAlpha << 24) >>> 0;
2372
+ if (d !== next) {
2373
+ dst32[dIdx] = next;
2374
+ didChange = true;
2375
+ }
2231
2376
  }
2232
2377
  }
2233
2378
  dIdx++;
@@ -2236,59 +2381,32 @@ function applyBinaryMaskToPixelData(dst, mask, opts = {}) {
2236
2381
  dIdx += dStride;
2237
2382
  mIdx += mStride;
2238
2383
  }
2384
+ return didChange;
2239
2385
  }
2240
2386
 
2241
2387
  // src/History/PixelMutator/mutatorApplyBinaryMask.ts
2242
- var defaults2 = {
2388
+ var defaults3 = {
2243
2389
  applyBinaryMaskToPixelData
2244
2390
  };
2245
- var mutatorApplyBinaryMask = ((writer, deps = defaults2) => {
2391
+ var mutatorApplyBinaryMask = ((writer, deps = defaults3) => {
2246
2392
  const {
2247
- applyBinaryMaskToPixelData: applyBinaryMaskToPixelData2 = defaults2.applyBinaryMaskToPixelData
2393
+ applyBinaryMaskToPixelData: applyBinaryMaskToPixelData2 = defaults3.applyBinaryMaskToPixelData
2248
2394
  } = deps;
2249
2395
  return {
2250
- applyBinaryMask: (mask, opts = {}) => {
2251
- let target = writer.target;
2396
+ applyBinaryMask(mask, opts = {}) {
2397
+ let target = writer.config.target;
2252
2398
  const {
2253
2399
  x = 0,
2254
2400
  y = 0,
2255
- w = writer.target.width,
2256
- h = writer.target.height
2401
+ w = target.width,
2402
+ h = target.height
2257
2403
  } = opts;
2258
- writer.accumulator.storeRegionBeforeState(x, y, w, h);
2259
- applyBinaryMaskToPixelData2(target, mask, opts);
2404
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2405
+ return didChange(applyBinaryMaskToPixelData2(target, mask, opts));
2260
2406
  }
2261
2407
  };
2262
2408
  });
2263
2409
 
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
2410
  // src/PixelData/blendColorPixelDataAlphaMask.ts
2293
2411
  function blendColorPixelDataAlphaMask(dst, color, mask, opts = {}) {
2294
2412
  const targetX = opts.x ?? 0;
@@ -2300,10 +2418,10 @@ function blendColorPixelDataAlphaMask(dst, color, mask, opts = {}) {
2300
2418
  const mx = opts.mx ?? 0;
2301
2419
  const my = opts.my ?? 0;
2302
2420
  const invertMask = opts.invertMask ?? false;
2303
- if (globalAlpha === 0) return;
2421
+ if (globalAlpha === 0) return false;
2304
2422
  const baseSrcAlpha = color >>> 24;
2305
2423
  const isOverwrite = blendFn.isOverwrite || false;
2306
- if (baseSrcAlpha === 0 && !isOverwrite) return;
2424
+ if (baseSrcAlpha === 0 && !isOverwrite) return false;
2307
2425
  let x = targetX;
2308
2426
  let y = targetY;
2309
2427
  let actualW = w;
@@ -2318,7 +2436,7 @@ function blendColorPixelDataAlphaMask(dst, color, mask, opts = {}) {
2318
2436
  }
2319
2437
  actualW = Math.min(actualW, dst.width - x);
2320
2438
  actualH = Math.min(actualH, dst.height - y);
2321
- if (actualW <= 0 || actualH <= 0) return;
2439
+ if (actualW <= 0 || actualH <= 0) return false;
2322
2440
  const dx = x - targetX | 0;
2323
2441
  const dy = y - targetY | 0;
2324
2442
  const dst32 = dst.data32;
@@ -2328,9 +2446,10 @@ function blendColorPixelDataAlphaMask(dst, color, mask, opts = {}) {
2328
2446
  let dIdx = y * dw + x | 0;
2329
2447
  let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
2330
2448
  const dStride = dw - actualW | 0;
2331
- let mStride = mPitch - actualW | 0;
2449
+ const mStride = mPitch - actualW | 0;
2332
2450
  const isOpaque = globalAlpha === 255;
2333
2451
  const colorRGB = color & 16777215;
2452
+ let didChange = false;
2334
2453
  for (let iy = 0; iy < actualH; iy++) {
2335
2454
  for (let ix = 0; ix < actualW; ix++) {
2336
2455
  const mVal = maskData[mIdx];
@@ -2361,162 +2480,47 @@ function blendColorPixelDataAlphaMask(dst, color, mask, opts = {}) {
2361
2480
  }
2362
2481
  finalCol = (colorRGB | a << 24) >>> 0;
2363
2482
  }
2364
- dst32[dIdx] = blendFn(finalCol, dst32[dIdx]);
2483
+ const current = dst32[dIdx];
2484
+ const next = blendFn(finalCol, current);
2485
+ if (current !== next) {
2486
+ dst32[dIdx] = next;
2487
+ didChange = true;
2488
+ }
2365
2489
  dIdx++;
2366
2490
  mIdx++;
2367
2491
  }
2368
2492
  dIdx += dStride;
2369
2493
  mIdx += mStride;
2370
2494
  }
2495
+ return didChange;
2371
2496
  }
2372
2497
 
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;
2409
- const dst32 = dst.data32;
2410
- const dw = dst.width;
2411
- const mPitch = mask.w;
2412
- const maskData = mask.data;
2413
- let dIdx = y * dw + x | 0;
2414
- let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
2415
- const dStride = dw - actualW | 0;
2416
- const mStride = mPitch - actualW | 0;
2417
- const skipVal = invertMask ? 1 : 0;
2418
- for (let iy = 0; iy < actualH; iy++) {
2419
- for (let ix = 0; ix < actualW; ix++) {
2420
- if (maskData[mIdx] === skipVal) {
2421
- dIdx++;
2422
- mIdx++;
2423
- continue;
2424
- }
2425
- dst32[dIdx] = blendFn(baseColorWithGlobalAlpha, dst32[dIdx]);
2426
- dIdx++;
2427
- mIdx++;
2428
- }
2429
- dIdx += dStride;
2430
- mIdx += mStride;
2431
- }
2432
- }
2433
-
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
2467
- };
2468
- var mutatorApplyCircleBrush = ((writer, deps = defaults3) => {
2469
- const {
2470
- applyCircleBrushToPixelData: applyCircleBrushToPixelData2 = defaults3.applyCircleBrushToPixelData,
2471
- getCircleBrushOrPencilBounds: getCircleBrushOrPencilBounds2 = defaults3.getCircleBrushOrPencilBounds
2472
- } = 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,
2498
+ // src/Rect/getCircleBrushOrPencilBounds.ts
2499
+ function getCircleBrushOrPencilBounds(centerX, centerY, brushSize, targetWidth, targetHeight, out) {
2500
+ const r = brushSize / 2;
2501
+ const minOffset = -Math.ceil(r - 0.5);
2502
+ const maxOffset = Math.floor(r - 0.5);
2503
+ const startX = Math.floor(centerX + minOffset);
2504
+ const startY = Math.floor(centerY + minOffset);
2505
+ const endX = Math.floor(centerX + maxOffset) + 1;
2506
+ const endY = Math.floor(centerY + maxOffset) + 1;
2507
+ const res = out ?? {
2482
2508
  x: 0,
2483
2509
  y: 0,
2484
2510
  w: 0,
2485
2511
  h: 0
2486
2512
  };
2487
- 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);
2498
- }
2499
- };
2500
- });
2501
-
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);
2509
- return;
2510
- }
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;
2519
- }
2513
+ const cStartX = Math.max(0, startX);
2514
+ const cStartY = Math.max(0, startY);
2515
+ const cEndX = Math.min(targetWidth, endX);
2516
+ const cEndY = Math.min(targetHeight, endY);
2517
+ const w = cEndX - cStartX;
2518
+ const h = cEndY - cStartY;
2519
+ res.x = cStartX;
2520
+ res.y = cStartY;
2521
+ res.w = w < 0 ? 0 : w;
2522
+ res.h = h < 0 ? 0 : h;
2523
+ return res;
2520
2524
  }
2521
2525
 
2522
2526
  // src/Rect/getCircleBrushOrPencilStrokeBounds.ts
@@ -2589,8 +2593,9 @@ var mutatorApplyCircleBrushStroke = ((writer, deps = defaults4) => {
2589
2593
  const maskData = mask.data;
2590
2594
  const brushData = brush.data;
2591
2595
  const minOffset = brush.minOffset;
2592
- const targetWidth = writer.target.width;
2593
- const targetHeight = writer.target.height;
2596
+ const target = writer.config.target;
2597
+ const targetWidth = target.width;
2598
+ const targetHeight = target.height;
2594
2599
  forEachLinePoint2(x0, y0, x1, y1, (px, py) => {
2595
2600
  const {
2596
2601
  x: cbx,
@@ -2628,19 +2633,117 @@ var mutatorApplyCircleBrushStroke = ((writer, deps = defaults4) => {
2628
2633
  blendColorPixelOptions.y = by;
2629
2634
  blendColorPixelOptions.w = bw;
2630
2635
  blendColorPixelOptions.h = bh;
2631
- blendColorPixelDataAlphaMask2(writer.target, color, mask, blendColorPixelOptions);
2636
+ blendColorPixelDataAlphaMask2(target, color, mask, blendColorPixelOptions);
2632
2637
  }
2633
2638
  };
2634
2639
  });
2635
2640
 
2636
- // src/History/PixelMutator/mutatorApplyCirclePencil.ts
2641
+ // src/PixelData/blendColorPixelDataBinaryMask.ts
2642
+ function blendColorPixelDataBinaryMask(dst, color, mask, opts = {}) {
2643
+ const targetX = opts.x ?? 0;
2644
+ const targetY = opts.y ?? 0;
2645
+ let w = opts.w ?? mask.w;
2646
+ let h = opts.h ?? mask.h;
2647
+ const globalAlpha = opts.alpha ?? 255;
2648
+ const blendFn = opts.blendFn ?? sourceOverPerfect;
2649
+ const mx = opts.mx ?? 0;
2650
+ const my = opts.my ?? 0;
2651
+ const invertMask = opts.invertMask ?? false;
2652
+ if (globalAlpha === 0) return false;
2653
+ const baseSrcAlpha = color >>> 24;
2654
+ const isOverwrite = blendFn.isOverwrite || false;
2655
+ if (baseSrcAlpha === 0 && !isOverwrite) return false;
2656
+ let x = targetX;
2657
+ let y = targetY;
2658
+ if (x < 0) {
2659
+ w += x;
2660
+ x = 0;
2661
+ }
2662
+ if (y < 0) {
2663
+ h += y;
2664
+ y = 0;
2665
+ }
2666
+ const actualW = Math.min(w, dst.width - x);
2667
+ const actualH = Math.min(h, dst.height - y);
2668
+ if (actualW <= 0 || actualH <= 0) return false;
2669
+ let baseColorWithGlobalAlpha = color;
2670
+ if (globalAlpha < 255) {
2671
+ const a = baseSrcAlpha * globalAlpha + 128 >> 8;
2672
+ if (a === 0 && !isOverwrite) return false;
2673
+ baseColorWithGlobalAlpha = (color & 16777215 | a << 24) >>> 0;
2674
+ }
2675
+ const dx = x - targetX | 0;
2676
+ const dy = y - targetY | 0;
2677
+ const dst32 = dst.data32;
2678
+ const dw = dst.width;
2679
+ const mPitch = mask.w;
2680
+ const maskData = mask.data;
2681
+ let dIdx = y * dw + x | 0;
2682
+ let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
2683
+ const dStride = dw - actualW | 0;
2684
+ const mStride = mPitch - actualW | 0;
2685
+ const skipVal = invertMask ? 1 : 0;
2686
+ let didChange = false;
2687
+ for (let iy = 0; iy < actualH; iy++) {
2688
+ for (let ix = 0; ix < actualW; ix++) {
2689
+ if (maskData[mIdx] === skipVal) {
2690
+ dIdx++;
2691
+ mIdx++;
2692
+ continue;
2693
+ }
2694
+ const current = dst32[dIdx];
2695
+ const next = blendFn(baseColorWithGlobalAlpha, current);
2696
+ if (current !== next) {
2697
+ dst32[dIdx] = next;
2698
+ didChange = true;
2699
+ }
2700
+ dIdx++;
2701
+ mIdx++;
2702
+ }
2703
+ dIdx += dStride;
2704
+ mIdx += mStride;
2705
+ }
2706
+ return didChange;
2707
+ }
2708
+
2709
+ // src/PixelData/blendColorPixelDataCircleMask.ts
2710
+ function blendColorPixelDataCircleMask(target, color, centerX, centerY, brush, alpha = 255, blendFn = sourceOverPerfect, scratchOptions = {}, bounds) {
2711
+ const b = bounds ?? getCircleBrushOrPencilBounds(centerX, centerY, brush.size, target.width, target.height);
2712
+ if (b.w <= 0 || b.h <= 0) return false;
2713
+ const unclippedStartX = Math.floor(centerX + brush.minOffset);
2714
+ const unclippedStartY = Math.floor(centerY + brush.minOffset);
2715
+ const ix = Math.max(unclippedStartX, b.x);
2716
+ const iy = Math.max(unclippedStartY, b.y);
2717
+ const ir = Math.min(unclippedStartX + brush.w, b.x + b.w);
2718
+ const ib = Math.min(unclippedStartY + brush.h, b.y + b.h);
2719
+ const iw = ir - ix;
2720
+ const ih = ib - iy;
2721
+ if (iw <= 0 || ih <= 0) return false;
2722
+ scratchOptions.x = ix;
2723
+ scratchOptions.y = iy;
2724
+ scratchOptions.w = iw;
2725
+ scratchOptions.h = ih;
2726
+ scratchOptions.mx = ix - unclippedStartX;
2727
+ scratchOptions.my = iy - unclippedStartY;
2728
+ scratchOptions.alpha = alpha;
2729
+ scratchOptions.blendFn = blendFn;
2730
+ if (brush.type === 0 /* ALPHA */) {
2731
+ return blendColorPixelDataAlphaMask(target, color, brush, scratchOptions);
2732
+ }
2733
+ if (brush.type === 1 /* BINARY */) {
2734
+ return blendColorPixelDataBinaryMask(target, color, brush, scratchOptions);
2735
+ }
2736
+ return false;
2737
+ }
2738
+
2739
+ // src/History/PixelMutator/mutatorBlendColorCircleMask.ts
2637
2740
  var defaults5 = {
2638
- applyCircleBrushToPixelData,
2741
+ blendColorPixelDataCircleMask,
2639
2742
  getCircleBrushOrPencilBounds
2640
2743
  };
2641
- var mutatorApplyCirclePencil = ((writer, deps = defaults5) => {
2744
+ var mutatorBlendColorCircleMask = ((writer, deps = defaults5) => {
2642
2745
  const {
2643
- applyCircleBrushToPixelData: applyCircleBrushToPixelData2 = defaults5.applyCircleBrushToPixelData,
2746
+ blendColorPixelDataCircleMask: blendColorPixelDataCircleMask2 = defaults5.blendColorPixelDataCircleMask,
2644
2747
  getCircleBrushOrPencilBounds: getCircleBrushOrPencilBounds2 = defaults5.getCircleBrushOrPencilBounds
2645
2748
  } = deps;
2646
2749
  const boundsOut = {
@@ -2649,34 +2752,63 @@ var mutatorApplyCirclePencil = ((writer, deps = defaults5) => {
2649
2752
  w: 0,
2650
2753
  h: 0
2651
2754
  };
2755
+ const blendColorPixelOptions = {
2756
+ alpha: 255,
2757
+ blendFn: sourceOverPerfect,
2758
+ x: 0,
2759
+ y: 0,
2760
+ w: 0,
2761
+ h: 0
2762
+ };
2763
+ return {
2764
+ applyCircleMask(color, centerX, centerY, brush, alpha = 255, blendFn) {
2765
+ const target = writer.config.target;
2766
+ const b = getCircleBrushOrPencilBounds2(centerX, centerY, brush.size, target.width, target.height, boundsOut);
2767
+ const didChange = writer.accumulator.storeRegionBeforeState(b.x, b.y, b.w, b.h);
2768
+ return didChange(blendColorPixelDataCircleMask2(target, color, centerX, centerY, brush, alpha, blendFn, blendColorPixelOptions, b));
2769
+ }
2770
+ };
2771
+ });
2772
+
2773
+ // src/History/PixelMutator/mutatorApplyCirclePencil.ts
2774
+ var defaults6 = {
2775
+ applyCircleMaskToPixelData: blendColorPixelDataCircleMask,
2776
+ getCircleBrushOrPencilBounds
2777
+ };
2778
+ var mutatorApplyCirclePencil = ((writer, deps = defaults6) => {
2779
+ const {
2780
+ applyCircleMaskToPixelData = defaults6.applyCircleMaskToPixelData,
2781
+ getCircleBrushOrPencilBounds: getCircleBrushOrPencilBounds2 = defaults6.getCircleBrushOrPencilBounds
2782
+ } = deps;
2783
+ const boundsOut = {
2784
+ x: 0,
2785
+ y: 0,
2786
+ w: 0,
2787
+ h: 0
2788
+ };
2652
2789
  return {
2653
2790
  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);
2791
+ const target = writer.config.target;
2792
+ const b = getCircleBrushOrPencilBounds2(centerX, centerY, brush.size, target.width, target.height, boundsOut);
2793
+ const didChange = writer.accumulator.storeRegionBeforeState(b.x, b.y, b.w, b.h);
2794
+ return didChange(applyCircleMaskToPixelData(target, color, centerX, centerY, brush, alpha, blendFn, b));
2663
2795
  }
2664
2796
  };
2665
2797
  });
2666
2798
 
2667
2799
  // src/History/PixelMutator/mutatorApplyCirclePencilStroke.ts
2668
- var defaults6 = {
2800
+ var defaults7 = {
2669
2801
  forEachLinePoint,
2670
2802
  blendColorPixelDataBinaryMask,
2671
2803
  getCircleBrushOrPencilBounds,
2672
2804
  getCircleBrushOrPencilStrokeBounds
2673
2805
  };
2674
- var mutatorApplyCirclePencilStroke = ((writer, deps = defaults6) => {
2806
+ var mutatorApplyCirclePencilStroke = ((writer, deps = defaults7) => {
2675
2807
  const {
2676
- forEachLinePoint: forEachLinePoint2 = defaults6.forEachLinePoint,
2677
- blendColorPixelDataBinaryMask: blendColorPixelDataBinaryMask2 = defaults6.blendColorPixelDataBinaryMask,
2678
- getCircleBrushOrPencilStrokeBounds: getCircleBrushOrPencilStrokeBounds2 = defaults6.getCircleBrushOrPencilStrokeBounds,
2679
- getCircleBrushOrPencilBounds: getCircleBrushOrPencilBounds2 = defaults6.getCircleBrushOrPencilBounds
2808
+ forEachLinePoint: forEachLinePoint2 = defaults7.forEachLinePoint,
2809
+ blendColorPixelDataBinaryMask: blendColorPixelDataBinaryMask2 = defaults7.blendColorPixelDataBinaryMask,
2810
+ getCircleBrushOrPencilStrokeBounds: getCircleBrushOrPencilStrokeBounds2 = defaults7.getCircleBrushOrPencilStrokeBounds,
2811
+ getCircleBrushOrPencilBounds: getCircleBrushOrPencilBounds2 = defaults7.getCircleBrushOrPencilBounds
2680
2812
  } = deps;
2681
2813
  const strokeBoundsOut = {
2682
2814
  x: 0,
@@ -2717,8 +2849,9 @@ var mutatorApplyCirclePencilStroke = ((writer, deps = defaults6) => {
2717
2849
  mask.w = bw;
2718
2850
  mask.h = bh;
2719
2851
  const maskData = mask.data;
2720
- const targetWidth = writer.target.width;
2721
- const targetHeight = writer.target.height;
2852
+ const target = writer.config.target;
2853
+ const targetWidth = target.width;
2854
+ const targetHeight = target.height;
2722
2855
  forEachLinePoint2(x0, y0, x1, y1, (px, py) => {
2723
2856
  const {
2724
2857
  x: cbx,
@@ -2753,7 +2886,7 @@ var mutatorApplyCirclePencilStroke = ((writer, deps = defaults6) => {
2753
2886
  blendColorPixelOptions.y = by;
2754
2887
  blendColorPixelOptions.w = bw;
2755
2888
  blendColorPixelOptions.h = bh;
2756
- blendColorPixelDataBinaryMask2(writer.target, color, mask, blendColorPixelOptions);
2889
+ blendColorPixelDataBinaryMask2(target, color, mask, blendColorPixelOptions);
2757
2890
  }
2758
2891
  };
2759
2892
  });
@@ -2788,7 +2921,9 @@ function applyRectBrushToPixelData(target, color, centerX, centerY, brushWidth,
2788
2921
  const targetWidth = target.width;
2789
2922
  const targetHeight = target.height;
2790
2923
  const b = bounds ?? getRectBrushOrPencilBounds(centerX, centerY, brushWidth, brushHeight, targetWidth, targetHeight);
2791
- if (b.w <= 0 || b.h <= 0) return;
2924
+ if (b.w <= 0 || b.h <= 0) {
2925
+ return false;
2926
+ }
2792
2927
  const data32 = target.data32;
2793
2928
  const baseColor = color & 16777215;
2794
2929
  const baseSrcAlpha = color >>> 24;
@@ -2802,6 +2937,7 @@ function applyRectBrushToPixelData(target, color, centerX, centerY, brushWidth,
2802
2937
  const endX = b.x + b.w;
2803
2938
  const endY = b.y + b.h;
2804
2939
  const isOverwrite = blendFn.isOverwrite;
2940
+ let didChange = false;
2805
2941
  for (let py = b.y; py < endY; py++) {
2806
2942
  const rowOffset = py * targetWidth;
2807
2943
  const dy = Math.abs(py - fCenterY + centerOffsetY) * invHalfH;
@@ -2824,20 +2960,26 @@ function applyRectBrushToPixelData(target, color, centerX, centerY, brushWidth,
2824
2960
  if (a === 0 && !isOverwrite) continue;
2825
2961
  finalCol = (a << 24 | baseColor) >>> 0;
2826
2962
  }
2827
- data32[idx] = blendFn(finalCol, data32[idx]);
2963
+ const current = data32[idx];
2964
+ const next = blendFn(finalCol, current);
2965
+ if (current !== next) {
2966
+ data32[idx] = next;
2967
+ didChange = true;
2968
+ }
2828
2969
  }
2829
2970
  }
2971
+ return didChange;
2830
2972
  }
2831
2973
 
2832
2974
  // src/History/PixelMutator/mutatorApplyRectBrush.ts
2833
- var defaults7 = {
2975
+ var defaults8 = {
2834
2976
  applyRectBrushToPixelData,
2835
2977
  getRectBrushOrPencilBounds
2836
2978
  };
2837
- var mutatorApplyRectBrush = ((writer, deps = defaults7) => {
2979
+ var mutatorApplyRectBrush = ((writer, deps = defaults8) => {
2838
2980
  const {
2839
- applyRectBrushToPixelData: applyRectBrushToPixelData2 = defaults7.applyRectBrushToPixelData,
2840
- getRectBrushOrPencilBounds: getRectBrushOrPencilBounds2 = defaults7.getRectBrushOrPencilBounds
2981
+ applyRectBrushToPixelData: applyRectBrushToPixelData2 = defaults8.applyRectBrushToPixelData,
2982
+ getRectBrushOrPencilBounds: getRectBrushOrPencilBounds2 = defaults8.getRectBrushOrPencilBounds
2841
2983
  } = deps;
2842
2984
  const boundsOut = {
2843
2985
  x: 0,
@@ -2847,15 +2989,10 @@ var mutatorApplyRectBrush = ((writer, deps = defaults7) => {
2847
2989
  };
2848
2990
  return {
2849
2991
  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);
2992
+ const target = writer.config.target;
2993
+ const b = getRectBrushOrPencilBounds2(centerX, centerY, brushWidth, brushHeight, target.width, target.height, boundsOut);
2994
+ const didChange = writer.accumulator.storeRegionBeforeState(b.x, b.y, b.w, b.h);
2995
+ return didChange(applyRectBrushToPixelData2(target, color, centerX, centerY, brushWidth, brushHeight, alpha, fallOff, blendFn, b));
2859
2996
  }
2860
2997
  };
2861
2998
  });
@@ -2876,18 +3013,18 @@ function getRectBrushOrPencilStrokeBounds(x0, y0, x1, y1, brushWidth, brushHeigh
2876
3013
  }
2877
3014
 
2878
3015
  // src/History/PixelMutator/mutatorApplyRectBrushStroke.ts
2879
- var defaults8 = {
3016
+ var defaults9 = {
2880
3017
  forEachLinePoint,
2881
3018
  blendColorPixelDataAlphaMask,
2882
3019
  getRectBrushOrPencilBounds,
2883
3020
  getRectBrushOrPencilStrokeBounds
2884
3021
  };
2885
- var mutatorApplyRectBrushStroke = ((writer, deps = defaults8) => {
3022
+ var mutatorApplyRectBrushStroke = ((writer, deps = defaults9) => {
2886
3023
  const {
2887
- forEachLinePoint: forEachLinePoint2 = defaults8.forEachLinePoint,
2888
- blendColorPixelDataAlphaMask: blendColorPixelDataAlphaMask2 = defaults8.blendColorPixelDataAlphaMask,
2889
- getRectBrushOrPencilBounds: getRectBrushOrPencilBounds2 = defaults8.getRectBrushOrPencilBounds,
2890
- getRectBrushOrPencilStrokeBounds: getRectBrushOrPencilStrokeBounds2 = defaults8.getRectBrushOrPencilStrokeBounds
3024
+ forEachLinePoint: forEachLinePoint2 = defaults9.forEachLinePoint,
3025
+ blendColorPixelDataAlphaMask: blendColorPixelDataAlphaMask2 = defaults9.blendColorPixelDataAlphaMask,
3026
+ getRectBrushOrPencilBounds: getRectBrushOrPencilBounds2 = defaults9.getRectBrushOrPencilBounds,
3027
+ getRectBrushOrPencilStrokeBounds: getRectBrushOrPencilStrokeBounds2 = defaults9.getRectBrushOrPencilStrokeBounds
2891
3028
  } = deps;
2892
3029
  const strokeBoundsOut = {
2893
3030
  x: 0,
@@ -2934,8 +3071,9 @@ var mutatorApplyRectBrushStroke = ((writer, deps = defaults8) => {
2934
3071
  const invHalfH = 1 / halfH;
2935
3072
  const centerOffsetX = brushWidth % 2 === 0 ? 0.5 : 0;
2936
3073
  const centerOffsetY = brushHeight % 2 === 0 ? 0.5 : 0;
2937
- const targetWidth = writer.target.width;
2938
- const targetHeight = writer.target.height;
3074
+ const target = writer.config.target;
3075
+ const targetWidth = target.width;
3076
+ const targetHeight = target.height;
2939
3077
  forEachLinePoint2(x0, y0, x1, y1, (px, py) => {
2940
3078
  const {
2941
3079
  x: rbx,
@@ -2973,22 +3111,22 @@ var mutatorApplyRectBrushStroke = ((writer, deps = defaults8) => {
2973
3111
  blendColorPixelOptions.y = by;
2974
3112
  blendColorPixelOptions.w = bw;
2975
3113
  blendColorPixelOptions.h = bh;
2976
- blendColorPixelDataAlphaMask2(writer.target, color, mask, blendColorPixelOptions);
3114
+ blendColorPixelDataAlphaMask2(target, color, mask, blendColorPixelOptions);
2977
3115
  }
2978
3116
  };
2979
3117
  });
2980
3118
 
2981
3119
  // src/History/PixelMutator/mutatorApplyRectPencil.ts
2982
- var defaults9 = {
3120
+ var defaults10 = {
2983
3121
  applyRectBrushToPixelData,
2984
3122
  getRectBrushOrPencilBounds,
2985
3123
  fallOff: () => 1
2986
3124
  };
2987
- var mutatorApplyRectPencil = ((writer, deps = defaults9) => {
3125
+ var mutatorApplyRectPencil = ((writer, deps = defaults10) => {
2988
3126
  const {
2989
- applyRectBrushToPixelData: applyRectBrushToPixelData2 = defaults9.applyRectBrushToPixelData,
2990
- getRectBrushOrPencilBounds: getRectBrushOrPencilBounds2 = defaults9.getRectBrushOrPencilBounds,
2991
- fallOff = defaults9.fallOff
3127
+ applyRectBrushToPixelData: applyRectBrushToPixelData2 = defaults10.applyRectBrushToPixelData,
3128
+ getRectBrushOrPencilBounds: getRectBrushOrPencilBounds2 = defaults10.getRectBrushOrPencilBounds,
3129
+ fallOff = defaults10.fallOff
2992
3130
  } = deps;
2993
3131
  const boundsOut = {
2994
3132
  x: 0,
@@ -2998,32 +3136,27 @@ var mutatorApplyRectPencil = ((writer, deps = defaults9) => {
2998
3136
  };
2999
3137
  return {
3000
3138
  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);
3139
+ const target = writer.config.target;
3140
+ const b = getRectBrushOrPencilBounds2(centerX, centerY, brushWidth, brushHeight, target.width, target.height, boundsOut);
3141
+ const didChange = writer.accumulator.storeRegionBeforeState(b.x, b.y, b.w, b.h);
3142
+ return didChange(applyRectBrushToPixelData2(target, color, centerX, centerY, brushWidth, brushHeight, alpha, fallOff, blendFn, b));
3010
3143
  }
3011
3144
  };
3012
3145
  });
3013
3146
 
3014
3147
  // src/History/PixelMutator/mutatorApplyRectPencilStroke.ts
3015
- var defaults10 = {
3148
+ var defaults11 = {
3016
3149
  forEachLinePoint,
3017
3150
  getRectBrushOrPencilBounds,
3018
3151
  getRectBrushOrPencilStrokeBounds,
3019
3152
  blendColorPixelDataBinaryMask
3020
3153
  };
3021
- var mutatorApplyRectPencilStroke = ((writer, deps = defaults10) => {
3154
+ var mutatorApplyRectPencilStroke = ((writer, deps = defaults11) => {
3022
3155
  const {
3023
- forEachLinePoint: forEachLinePoint2 = defaults10.forEachLinePoint,
3024
- blendColorPixelDataBinaryMask: blendColorPixelDataBinaryMask2 = defaults10.blendColorPixelDataBinaryMask,
3025
- getRectBrushOrPencilBounds: getRectBrushOrPencilBounds2 = defaults10.getRectBrushOrPencilBounds,
3026
- getRectBrushOrPencilStrokeBounds: getRectBrushOrPencilStrokeBounds2 = defaults10.getRectBrushOrPencilStrokeBounds
3156
+ forEachLinePoint: forEachLinePoint2 = defaults11.forEachLinePoint,
3157
+ blendColorPixelDataBinaryMask: blendColorPixelDataBinaryMask2 = defaults11.blendColorPixelDataBinaryMask,
3158
+ getRectBrushOrPencilBounds: getRectBrushOrPencilBounds2 = defaults11.getRectBrushOrPencilBounds,
3159
+ getRectBrushOrPencilStrokeBounds: getRectBrushOrPencilStrokeBounds2 = defaults11.getRectBrushOrPencilStrokeBounds
3027
3160
  } = deps;
3028
3161
  const strokeBoundsOut = {
3029
3162
  x: 0,
@@ -3067,8 +3200,9 @@ var mutatorApplyRectPencilStroke = ((writer, deps = defaults10) => {
3067
3200
  const halfW = brushWidth / 2;
3068
3201
  const halfH = brushHeight / 2;
3069
3202
  const centerOffset = brushWidth % 2 === 0 ? 0.5 : 0;
3070
- const targetWidth = writer.target.width;
3071
- const targetHeight = writer.target.height;
3203
+ const target = writer.config.target;
3204
+ const targetWidth = target.width;
3205
+ const targetHeight = target.height;
3072
3206
  forEachLinePoint2(x0, y0, x1, y1, (px, py) => {
3073
3207
  const {
3074
3208
  x: rbx,
@@ -3101,7 +3235,7 @@ var mutatorApplyRectPencilStroke = ((writer, deps = defaults10) => {
3101
3235
  blendColorPixelOptions.y = by;
3102
3236
  blendColorPixelOptions.w = bw;
3103
3237
  blendColorPixelOptions.h = bh;
3104
- blendColorPixelDataBinaryMask2(writer.target, color, mask, blendColorPixelOptions);
3238
+ blendColorPixelDataBinaryMask2(target, color, mask, blendColorPixelOptions);
3105
3239
  }
3106
3240
  };
3107
3241
  });
@@ -3116,11 +3250,14 @@ function blendColorPixelData(dst, color, opts = {}) {
3116
3250
  alpha: globalAlpha = 255,
3117
3251
  blendFn = sourceOverPerfect
3118
3252
  } = opts;
3119
- if (globalAlpha === 0) return;
3253
+ if (globalAlpha === 0) return false;
3120
3254
  const baseSrcAlpha = color >>> 24;
3121
3255
  const isOverwrite = blendFn.isOverwrite || false;
3122
- if (baseSrcAlpha === 0 && !isOverwrite) return;
3123
- let x = targetX, y = targetY, w = width, h = height;
3256
+ if (baseSrcAlpha === 0 && !isOverwrite) return false;
3257
+ let x = targetX;
3258
+ let y = targetY;
3259
+ let w = width;
3260
+ let h = height;
3124
3261
  if (x < 0) {
3125
3262
  w += x;
3126
3263
  x = 0;
@@ -3131,69 +3268,97 @@ function blendColorPixelData(dst, color, opts = {}) {
3131
3268
  }
3132
3269
  const actualW = Math.min(w, dst.width - x);
3133
3270
  const actualH = Math.min(h, dst.height - y);
3134
- if (actualW <= 0 || actualH <= 0) return;
3271
+ if (actualW <= 0 || actualH <= 0) return false;
3135
3272
  let finalSrcColor = color;
3136
3273
  if (globalAlpha < 255) {
3137
3274
  const a = baseSrcAlpha * globalAlpha + 128 >> 8;
3138
- if (a === 0 && !isOverwrite) return;
3275
+ if (a === 0 && !isOverwrite) return false;
3139
3276
  finalSrcColor = (color & 16777215 | a << 24) >>> 0;
3140
3277
  }
3141
3278
  const dst32 = dst.data32;
3142
3279
  const dw = dst.width;
3143
3280
  let dIdx = y * dw + x | 0;
3144
3281
  const dStride = dw - actualW | 0;
3282
+ let didChange = false;
3145
3283
  for (let iy = 0; iy < actualH; iy++) {
3146
3284
  for (let ix = 0; ix < actualW; ix++) {
3147
- dst32[dIdx] = blendFn(finalSrcColor, dst32[dIdx]);
3285
+ const current = dst32[dIdx];
3286
+ const next = blendFn(finalSrcColor, current);
3287
+ if (current !== next) {
3288
+ dst32[dIdx] = next;
3289
+ didChange = true;
3290
+ }
3148
3291
  dIdx++;
3149
3292
  }
3150
3293
  dIdx += dStride;
3151
3294
  }
3295
+ return didChange;
3152
3296
  }
3153
3297
 
3154
3298
  // src/History/PixelMutator/mutatorBlendColor.ts
3155
- var defaults11 = {
3299
+ var defaults12 = {
3156
3300
  blendColorPixelData
3157
3301
  };
3158
- var mutatorBlendColor = ((writer, deps = defaults11) => {
3302
+ var mutatorBlendColor = ((writer, deps = defaults12) => {
3159
3303
  const {
3160
- blendColorPixelData: blendColorPixelData2 = defaults11.blendColorPixelData
3304
+ blendColorPixelData: blendColorPixelData2 = defaults12.blendColorPixelData
3161
3305
  } = deps;
3162
3306
  return {
3163
3307
  blendColor(color, opts = {}) {
3308
+ const target = writer.config.target;
3164
3309
  const {
3165
3310
  x = 0,
3166
3311
  y = 0,
3167
- w = writer.target.width,
3168
- h = writer.target.height
3312
+ w = target.width,
3313
+ h = target.height
3169
3314
  } = opts;
3170
- writer.accumulator.storeRegionBeforeState(x, y, w, h);
3171
- blendColorPixelData2(writer.target, color, opts);
3315
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3316
+ return didChange(blendColorPixelData2(target, color, opts));
3172
3317
  }
3173
3318
  };
3174
3319
  });
3175
3320
 
3321
+ // src/PixelData/blendPixel.ts
3322
+ function blendPixel(target, x, y, color, alpha = 255, blendFn = sourceOverPerfect) {
3323
+ if (alpha === 0) return false;
3324
+ let width = target.width;
3325
+ let height = target.height;
3326
+ if (x < 0 || x >= width || y < 0 || y >= height) return false;
3327
+ let srcAlpha = color >>> 24;
3328
+ let isOverwrite = blendFn.isOverwrite;
3329
+ if (srcAlpha === 0 && !isOverwrite) return false;
3330
+ let dst32 = target.data32;
3331
+ let index = y * width + x;
3332
+ let finalColor = color;
3333
+ if (alpha !== 255) {
3334
+ let finalAlpha = srcAlpha * alpha + 128 >> 8;
3335
+ if (finalAlpha === 0 && !isOverwrite) return false;
3336
+ finalColor = (color & 16777215 | finalAlpha << 24) >>> 0;
3337
+ }
3338
+ let current = dst32[index];
3339
+ let next = blendFn(finalColor, current);
3340
+ if (current !== next) {
3341
+ dst32[index] = next;
3342
+ return true;
3343
+ }
3344
+ return false;
3345
+ }
3346
+
3176
3347
  // src/History/PixelMutator/mutatorBlendPixel.ts
3177
- function mutatorBlendPixel(writer) {
3348
+ var defaults13 = {
3349
+ blendPixel
3350
+ };
3351
+ var mutatorBlendPixel = ((writer, deps = defaults13) => {
3352
+ const {
3353
+ blendPixel: blendPixel2 = defaults13.blendPixel
3354
+ } = deps;
3178
3355
  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);
3356
+ blendPixel(x, y, color, alpha, blendFn) {
3357
+ const didChange = writer.accumulator.storePixelBeforeState(x, y);
3358
+ return didChange(blendPixel2(writer.config.target, x, y, color, alpha, blendFn));
3194
3359
  }
3195
3360
  };
3196
- }
3361
+ });
3197
3362
 
3198
3363
  // src/PixelData/blendPixelData.ts
3199
3364
  function blendPixelData(dst, src, opts = {}) {
@@ -3207,7 +3372,7 @@ function blendPixelData(dst, src, opts = {}) {
3207
3372
  alpha: globalAlpha = 255,
3208
3373
  blendFn = sourceOverPerfect
3209
3374
  } = opts;
3210
- if (globalAlpha === 0) return;
3375
+ if (globalAlpha === 0) return false;
3211
3376
  let x = targetX;
3212
3377
  let y = targetY;
3213
3378
  let sx = sourceX;
@@ -3238,7 +3403,7 @@ function blendPixelData(dst, src, opts = {}) {
3238
3403
  }
3239
3404
  const actualW = Math.min(w, dst.width - x);
3240
3405
  const actualH = Math.min(h, dst.height - y);
3241
- if (actualW <= 0 || actualH <= 0) return;
3406
+ if (actualW <= 0 || actualH <= 0) return false;
3242
3407
  const dst32 = dst.data32;
3243
3408
  const src32 = src.data32;
3244
3409
  const dw = dst.width;
@@ -3249,6 +3414,7 @@ function blendPixelData(dst, src, opts = {}) {
3249
3414
  const sStride = sw - actualW | 0;
3250
3415
  const isOpaque = globalAlpha === 255;
3251
3416
  const isOverwrite = blendFn.isOverwrite;
3417
+ let didChange = false;
3252
3418
  for (let iy = 0; iy < actualH; iy++) {
3253
3419
  for (let ix = 0; ix < actualW; ix++) {
3254
3420
  const srcCol = src32[sIdx];
@@ -3268,22 +3434,28 @@ function blendPixelData(dst, src, opts = {}) {
3268
3434
  }
3269
3435
  finalCol = (srcCol & 16777215 | a << 24) >>> 0;
3270
3436
  }
3271
- dst32[dIdx] = blendFn(finalCol, dst32[dIdx]);
3437
+ const current = dst32[dIdx];
3438
+ const next = blendFn(finalCol, dst32[dIdx]);
3439
+ if (current !== next) {
3440
+ dst32[dIdx] = next;
3441
+ didChange = true;
3442
+ }
3272
3443
  dIdx++;
3273
3444
  sIdx++;
3274
3445
  }
3275
3446
  dIdx += dStride;
3276
3447
  sIdx += sStride;
3277
3448
  }
3449
+ return didChange;
3278
3450
  }
3279
3451
 
3280
3452
  // src/History/PixelMutator/mutatorBlendPixelData.ts
3281
- var defaults12 = {
3453
+ var defaults14 = {
3282
3454
  blendPixelData
3283
3455
  };
3284
- var mutatorBlendPixelData = ((writer, deps = defaults12) => {
3456
+ var mutatorBlendPixelData = ((writer, deps = defaults14) => {
3285
3457
  const {
3286
- blendPixelData: blendPixelData2 = defaults12.blendPixelData
3458
+ blendPixelData: blendPixelData2 = defaults14.blendPixelData
3287
3459
  } = deps;
3288
3460
  return {
3289
3461
  blendPixelData(src, opts = {}) {
@@ -3293,8 +3465,8 @@ var mutatorBlendPixelData = ((writer, deps = defaults12) => {
3293
3465
  w = src.width,
3294
3466
  h = src.height
3295
3467
  } = opts;
3296
- writer.accumulator.storeRegionBeforeState(x, y, w, h);
3297
- blendPixelData2(writer.target, src, opts);
3468
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3469
+ return didChange(blendPixelData2(writer.config.target, src, opts));
3298
3470
  }
3299
3471
  };
3300
3472
  });
@@ -3314,7 +3486,7 @@ function blendPixelDataAlphaMask(dst, src, alphaMask, opts = {}) {
3314
3486
  my = 0,
3315
3487
  invertMask = false
3316
3488
  } = opts;
3317
- if (globalAlpha === 0) return;
3489
+ if (globalAlpha === 0) return false;
3318
3490
  let x = targetX;
3319
3491
  let y = targetY;
3320
3492
  let sx = sourceX;
@@ -3345,7 +3517,7 @@ function blendPixelDataAlphaMask(dst, src, alphaMask, opts = {}) {
3345
3517
  }
3346
3518
  const actualW = Math.min(w, dst.width - x);
3347
3519
  const actualH = Math.min(h, dst.height - y);
3348
- if (actualW <= 0 || actualH <= 0) return;
3520
+ if (actualW <= 0 || actualH <= 0) return false;
3349
3521
  const dw = dst.width;
3350
3522
  const sw = src.width;
3351
3523
  const mPitch = alphaMask.w;
@@ -3362,6 +3534,7 @@ function blendPixelDataAlphaMask(dst, src, alphaMask, opts = {}) {
3362
3534
  const mStride = mPitch - actualW | 0;
3363
3535
  const isOpaque = globalAlpha === 255;
3364
3536
  const isOverwrite = blendFn.isOverwrite || false;
3537
+ let didChange = false;
3365
3538
  for (let iy = 0; iy < actualH; iy++) {
3366
3539
  for (let ix = 0; ix < actualW; ix++) {
3367
3540
  const mVal = maskData[mIdx];
@@ -3403,7 +3576,12 @@ function blendPixelDataAlphaMask(dst, src, alphaMask, opts = {}) {
3403
3576
  }
3404
3577
  finalCol = (srcCol & 16777215 | a << 24) >>> 0;
3405
3578
  }
3406
- dst32[dIdx] = blendFn(finalCol, dst32[dIdx]);
3579
+ const current = dst32[dIdx];
3580
+ const next = blendFn(finalCol, dst32[dIdx]);
3581
+ if (current !== next) {
3582
+ dst32[dIdx] = next;
3583
+ didChange = true;
3584
+ }
3407
3585
  dIdx++;
3408
3586
  sIdx++;
3409
3587
  mIdx++;
@@ -3412,15 +3590,16 @@ function blendPixelDataAlphaMask(dst, src, alphaMask, opts = {}) {
3412
3590
  sIdx += sStride;
3413
3591
  mIdx += mStride;
3414
3592
  }
3593
+ return didChange;
3415
3594
  }
3416
3595
 
3417
3596
  // src/History/PixelMutator/mutatorBlendPixelDataAlphaMask.ts
3418
- var defaults13 = {
3597
+ var defaults15 = {
3419
3598
  blendPixelDataAlphaMask
3420
3599
  };
3421
- var mutatorBlendPixelDataAlphaMask = ((writer, deps = defaults13) => {
3600
+ var mutatorBlendPixelDataAlphaMask = ((writer, deps = defaults15) => {
3422
3601
  const {
3423
- blendPixelDataAlphaMask: blendPixelDataAlphaMask2 = defaults13.blendPixelDataAlphaMask
3602
+ blendPixelDataAlphaMask: blendPixelDataAlphaMask2 = defaults15.blendPixelDataAlphaMask
3424
3603
  } = deps;
3425
3604
  return {
3426
3605
  blendPixelDataAlphaMask(src, mask, opts = {}) {
@@ -3428,8 +3607,8 @@ var mutatorBlendPixelDataAlphaMask = ((writer, deps = defaults13) => {
3428
3607
  const y = opts.y ?? 0;
3429
3608
  const w = opts.w ?? src.width;
3430
3609
  const h = opts.h ?? src.height;
3431
- writer.accumulator.storeRegionBeforeState(x, y, w, h);
3432
- blendPixelDataAlphaMask2(writer.target, src, mask, opts);
3610
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3611
+ return didChange(blendPixelDataAlphaMask2(writer.config.target, src, mask, opts));
3433
3612
  }
3434
3613
  };
3435
3614
  });
@@ -3449,7 +3628,7 @@ function blendPixelDataBinaryMask(dst, src, binaryMask, opts = {}) {
3449
3628
  my = 0,
3450
3629
  invertMask = false
3451
3630
  } = opts;
3452
- if (globalAlpha === 0) return;
3631
+ if (globalAlpha === 0) return false;
3453
3632
  let x = targetX;
3454
3633
  let y = targetY;
3455
3634
  let sx = sourceX;
@@ -3480,7 +3659,7 @@ function blendPixelDataBinaryMask(dst, src, binaryMask, opts = {}) {
3480
3659
  }
3481
3660
  const actualW = Math.min(w, dst.width - x);
3482
3661
  const actualH = Math.min(h, dst.height - y);
3483
- if (actualW <= 0 || actualH <= 0) return;
3662
+ if (actualW <= 0 || actualH <= 0) return false;
3484
3663
  const dx = x - targetX | 0;
3485
3664
  const dy = y - targetY | 0;
3486
3665
  const dst32 = dst.data32;
@@ -3498,6 +3677,7 @@ function blendPixelDataBinaryMask(dst, src, binaryMask, opts = {}) {
3498
3677
  const skipVal = invertMask ? 1 : 0;
3499
3678
  const isOpaque = globalAlpha === 255;
3500
3679
  const isOverwrite = blendFn.isOverwrite || false;
3680
+ let didChange = false;
3501
3681
  for (let iy = 0; iy < actualH; iy++) {
3502
3682
  for (let ix = 0; ix < actualW; ix++) {
3503
3683
  if (maskData[mIdx] === skipVal) {
@@ -3525,7 +3705,12 @@ function blendPixelDataBinaryMask(dst, src, binaryMask, opts = {}) {
3525
3705
  }
3526
3706
  finalCol = (srcCol & 16777215 | a << 24) >>> 0;
3527
3707
  }
3528
- dst32[dIdx] = blendFn(finalCol, dst32[dIdx]);
3708
+ const current = dst32[dIdx];
3709
+ const next = blendFn(finalCol, dst32[dIdx]);
3710
+ if (current !== next) {
3711
+ dst32[dIdx] = next;
3712
+ didChange = true;
3713
+ }
3529
3714
  dIdx++;
3530
3715
  sIdx++;
3531
3716
  mIdx++;
@@ -3534,15 +3719,16 @@ function blendPixelDataBinaryMask(dst, src, binaryMask, opts = {}) {
3534
3719
  sIdx += sStride;
3535
3720
  mIdx += mStride;
3536
3721
  }
3722
+ return didChange;
3537
3723
  }
3538
3724
 
3539
3725
  // src/History/PixelMutator/mutatorBlendPixelDataBinaryMask.ts
3540
- var defaults14 = {
3726
+ var defaults16 = {
3541
3727
  blendPixelDataBinaryMask
3542
3728
  };
3543
- var mutatorBlendPixelDataBinaryMask = ((writer, deps = defaults14) => {
3729
+ var mutatorBlendPixelDataBinaryMask = ((writer, deps = defaults16) => {
3544
3730
  const {
3545
- blendPixelDataBinaryMask: blendPixelDataBinaryMask2 = defaults14.blendPixelDataBinaryMask
3731
+ blendPixelDataBinaryMask: blendPixelDataBinaryMask2 = defaults16.blendPixelDataBinaryMask
3546
3732
  } = deps;
3547
3733
  return {
3548
3734
  blendPixelDataBinaryMask(src, mask, opts = {}) {
@@ -3550,15 +3736,15 @@ var mutatorBlendPixelDataBinaryMask = ((writer, deps = defaults14) => {
3550
3736
  const y = opts.y ?? 0;
3551
3737
  const w = opts.w ?? src.width;
3552
3738
  const h = opts.h ?? src.height;
3553
- writer.accumulator.storeRegionBeforeState(x, y, w, h);
3554
- blendPixelDataBinaryMask2(writer.target, src, mask, opts);
3739
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3740
+ return didChange(blendPixelDataBinaryMask2(writer.config.target, src, mask, opts));
3555
3741
  }
3556
3742
  };
3557
3743
  });
3558
3744
 
3559
- // src/PixelData/fillPixelData.ts
3745
+ // src/PixelData/fillPixelDataFast.ts
3560
3746
  var SCRATCH_RECT = makeClippedRect();
3561
- function fillPixelData(dst, color, _x, _y, _w, _h) {
3747
+ function fillPixelDataFast(dst, color, _x, _y, _w, _h) {
3562
3748
  let x;
3563
3749
  let y;
3564
3750
  let w;
@@ -3601,55 +3787,111 @@ function fillPixelData(dst, color, _x, _y, _w, _h) {
3601
3787
  }
3602
3788
 
3603
3789
  // src/History/PixelMutator/mutatorClear.ts
3604
- var defaults15 = {
3605
- fillPixelData
3790
+ var defaults17 = {
3791
+ fillPixelData: fillPixelDataFast
3606
3792
  };
3607
- var mutatorClear = ((writer, deps = defaults15) => {
3793
+ var mutatorClear = ((writer, deps = defaults17) => {
3608
3794
  const {
3609
- fillPixelData: fillPixelData2 = defaults15.fillPixelData
3795
+ fillPixelData: fillPixelData2 = defaults17.fillPixelData
3610
3796
  } = deps;
3611
3797
  return {
3612
3798
  clear(rect = {}) {
3799
+ const target = writer.config.target;
3613
3800
  const x = rect.x ?? 0;
3614
3801
  const y = rect.y ?? 0;
3615
- const w = rect.w ?? writer.target.width;
3616
- const h = rect.h ?? writer.target.height;
3802
+ const w = rect.w ?? target.width;
3803
+ const h = rect.h ?? target.height;
3617
3804
  writer.accumulator.storeRegionBeforeState(x, y, w, h);
3618
- fillPixelData2(writer.target, 0, x, y, w, h);
3805
+ fillPixelData2(target, 0, x, y, w, h);
3619
3806
  }
3620
3807
  };
3621
3808
  });
3622
3809
 
3810
+ // src/PixelData/fillPixelData.ts
3811
+ var SCRATCH_RECT2 = makeClippedRect();
3812
+ function fillPixelData(dst, color, _x, _y, _w, _h) {
3813
+ let x;
3814
+ let y;
3815
+ let w;
3816
+ let h;
3817
+ if (typeof _x === "object") {
3818
+ x = _x.x ?? 0;
3819
+ y = _x.y ?? 0;
3820
+ w = _x.w ?? dst.width;
3821
+ h = _x.h ?? dst.height;
3822
+ } else if (typeof _x === "number") {
3823
+ x = _x;
3824
+ y = _y;
3825
+ w = _w;
3826
+ h = _h;
3827
+ } else {
3828
+ x = 0;
3829
+ y = 0;
3830
+ w = dst.width;
3831
+ h = dst.height;
3832
+ }
3833
+ const clip = resolveRectClipping(x, y, w, h, dst.width, dst.height, SCRATCH_RECT2);
3834
+ if (!clip.inBounds) return false;
3835
+ const {
3836
+ x: finalX,
3837
+ y: finalY,
3838
+ w: actualW,
3839
+ h: actualH
3840
+ } = clip;
3841
+ const dst32 = dst.data32;
3842
+ const dw = dst.width;
3843
+ let hasChanged = false;
3844
+ for (let iy = 0; iy < actualH; iy++) {
3845
+ const rowOffset = (finalY + iy) * dw;
3846
+ const start = rowOffset + finalX;
3847
+ const end = start + actualW;
3848
+ for (let i = start; i < end; i++) {
3849
+ if (dst32[i] !== color) {
3850
+ dst32[i] = color;
3851
+ hasChanged = true;
3852
+ }
3853
+ }
3854
+ }
3855
+ return hasChanged;
3856
+ }
3857
+
3623
3858
  // src/History/PixelMutator/mutatorFill.ts
3624
- var defaults16 = {
3859
+ var defaults18 = {
3625
3860
  fillPixelData
3626
3861
  };
3627
- var mutatorFill = ((writer, deps = defaults16) => {
3862
+ var mutatorFill = ((writer, deps = defaults18) => {
3628
3863
  const {
3629
- fillPixelData: fillPixelData2 = defaults16.fillPixelData
3864
+ fillPixelData: fillPixelData2 = defaults18.fillPixelData
3630
3865
  } = deps;
3631
3866
  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);
3867
+ fill(color, x = 0, y = 0, w = writer.config.target.width, h = writer.config.target.height) {
3868
+ const target = writer.config.target;
3869
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3870
+ return didChange(fillPixelData2(target, color, x, y, w, h));
3871
+ }
3872
+ };
3873
+ });
3874
+ var mutatorFillRect = ((writer, deps = defaults18) => {
3875
+ const {
3876
+ fillPixelData: fillPixelData2 = defaults18.fillPixelData
3877
+ } = deps;
3878
+ return {
3879
+ fillRect(color, rect) {
3880
+ const target = writer.config.target;
3881
+ const didChange = writer.accumulator.storeRegionBeforeState(rect.x, rect.y, rect.w, rect.h);
3882
+ return didChange(fillPixelData2(target, color, rect.x, rect.y, rect.w, rect.h));
3641
3883
  }
3642
3884
  };
3643
3885
  });
3644
3886
 
3645
3887
  // src/PixelData/fillPixelDataBinaryMask.ts
3646
- var SCRATCH_RECT2 = makeClippedRect();
3888
+ var SCRATCH_RECT3 = makeClippedRect();
3647
3889
  function fillPixelDataBinaryMask(dst, color, mask, alpha = 255, x = 0, y = 0) {
3648
- if (alpha === 0) return;
3890
+ if (alpha === 0) return false;
3649
3891
  const maskW = mask.w;
3650
3892
  const maskH = mask.h;
3651
- const clip = resolveRectClipping(x, y, maskW, maskH, dst.width, dst.height, SCRATCH_RECT2);
3652
- if (!clip.inBounds) return;
3893
+ const clip = resolveRectClipping(x, y, maskW, maskH, dst.width, dst.height, SCRATCH_RECT3);
3894
+ if (!clip.inBounds) return false;
3653
3895
  const {
3654
3896
  x: finalX,
3655
3897
  y: finalY,
@@ -3666,6 +3908,7 @@ function fillPixelDataBinaryMask(dst, color, mask, alpha = 255, x = 0, y = 0) {
3666
3908
  const a = baseSrcAlpha * alpha + 128 >> 8;
3667
3909
  finalCol = (colorRGB | a << 24) >>> 0;
3668
3910
  }
3911
+ let hasChanged = false;
3669
3912
  for (let iy = 0; iy < actualH; iy++) {
3670
3913
  const currentY = finalY + iy;
3671
3914
  const maskY = currentY - y;
@@ -3676,30 +3919,35 @@ function fillPixelDataBinaryMask(dst, color, mask, alpha = 255, x = 0, y = 0) {
3676
3919
  const maskX = currentX - x;
3677
3920
  const maskIndex = maskOffset + maskX;
3678
3921
  if (maskData[maskIndex]) {
3679
- dst32[dstRowOffset + currentX] = finalCol;
3922
+ const current = dst32[dstRowOffset + currentX];
3923
+ if (current !== finalCol) {
3924
+ dst32[dstRowOffset + currentX] = finalCol;
3925
+ hasChanged = true;
3926
+ }
3680
3927
  }
3681
3928
  }
3682
3929
  }
3930
+ return hasChanged;
3683
3931
  }
3684
3932
 
3685
3933
  // src/History/PixelMutator/mutatorFillBinaryMask.ts
3686
- var defaults17 = {
3934
+ var defaults19 = {
3687
3935
  fillPixelDataBinaryMask
3688
3936
  };
3689
- var mutatorFillBinaryMask = ((writer, deps = defaults17) => {
3937
+ var mutatorFillBinaryMask = ((writer, deps = defaults19) => {
3690
3938
  const {
3691
- fillPixelDataBinaryMask: fillPixelDataBinaryMask2 = defaults17.fillPixelDataBinaryMask
3939
+ fillPixelDataBinaryMask: fillPixelDataBinaryMask2 = defaults19.fillPixelDataBinaryMask
3692
3940
  } = deps;
3693
3941
  return {
3694
3942
  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);
3943
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, mask.w, mask.h);
3944
+ return didChange(fillPixelDataBinaryMask2(writer.config.target, color, mask, alpha, x, y));
3697
3945
  }
3698
3946
  };
3699
3947
  });
3700
3948
 
3701
3949
  // src/PixelData/invertPixelData.ts
3702
- var SCRATCH_RECT3 = makeClippedRect();
3950
+ var SCRATCH_RECT4 = makeClippedRect();
3703
3951
  function invertPixelData(pixelData, opts = {}) {
3704
3952
  const dst = pixelData;
3705
3953
  const {
@@ -3712,8 +3960,8 @@ function invertPixelData(pixelData, opts = {}) {
3712
3960
  my = 0,
3713
3961
  invertMask = false
3714
3962
  } = opts;
3715
- const clip = resolveRectClipping(targetX, targetY, width, height, dst.width, dst.height, SCRATCH_RECT3);
3716
- if (!clip.inBounds) return;
3963
+ const clip = resolveRectClipping(targetX, targetY, width, height, dst.width, dst.height, SCRATCH_RECT4);
3964
+ if (!clip.inBounds) return false;
3717
3965
  const {
3718
3966
  x,
3719
3967
  y,
@@ -3753,26 +4001,28 @@ function invertPixelData(pixelData, opts = {}) {
3753
4001
  dIdx += dStride;
3754
4002
  }
3755
4003
  }
4004
+ return true;
3756
4005
  }
3757
4006
 
3758
4007
  // src/History/PixelMutator/mutatorInvert.ts
3759
- var defaults18 = {
4008
+ var defaults20 = {
3760
4009
  invertPixelData
3761
4010
  };
3762
- var mutatorInvert = ((writer, deps = defaults18) => {
4011
+ var mutatorInvert = ((writer, deps = defaults20) => {
3763
4012
  const {
3764
- invertPixelData: invertPixelData2 = defaults18.invertPixelData
4013
+ invertPixelData: invertPixelData2 = defaults20.invertPixelData
3765
4014
  } = deps;
3766
4015
  return {
3767
4016
  invert(opts = {}) {
4017
+ const target = writer.config.target;
3768
4018
  const {
3769
4019
  x = 0,
3770
4020
  y = 0,
3771
- w = writer.target.width,
3772
- h = writer.target.height
4021
+ w = target.width,
4022
+ h = target.height
3773
4023
  } = opts;
3774
- writer.accumulator.storeRegionBeforeState(x, y, w, h);
3775
- invertPixelData2(writer.target, opts);
4024
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
4025
+ return didChange(invertPixelData2(target, opts));
3776
4026
  }
3777
4027
  };
3778
4028
  });
@@ -3783,7 +4033,6 @@ function makeFullPixelMutator(writer) {
3783
4033
  // @sort
3784
4034
  ...mutatorApplyAlphaMask(writer),
3785
4035
  ...mutatorApplyBinaryMask(writer),
3786
- ...mutatorApplyCircleBrush(writer),
3787
4036
  ...mutatorApplyCircleBrushStroke(writer),
3788
4037
  ...mutatorApplyCirclePencil(writer),
3789
4038
  ...mutatorApplyCirclePencilStroke(writer),
@@ -3792,6 +4041,7 @@ function makeFullPixelMutator(writer) {
3792
4041
  ...mutatorApplyRectPencil(writer),
3793
4042
  ...mutatorApplyRectPencilStroke(writer),
3794
4043
  ...mutatorBlendColor(writer),
4044
+ ...mutatorBlendColorCircleMask(writer),
3795
4045
  ...mutatorBlendPixel(writer),
3796
4046
  ...mutatorBlendPixelData(writer),
3797
4047
  ...mutatorBlendPixelDataAlphaMask(writer),
@@ -3799,50 +4049,96 @@ function makeFullPixelMutator(writer) {
3799
4049
  ...mutatorClear(writer),
3800
4050
  ...mutatorFill(writer),
3801
4051
  ...mutatorFillBinaryMask(writer),
4052
+ ...mutatorFillRect(writer),
3802
4053
  ...mutatorInvert(writer)
3803
4054
  };
3804
4055
  }
3805
4056
 
4057
+ // src/PixelTile/PixelTile.ts
4058
+ var PixelTile = class {
4059
+ constructor(id, tx, ty, tileSize, tileArea) {
4060
+ this.id = id;
4061
+ this.tx = tx;
4062
+ this.ty = ty;
4063
+ this.width = this.height = tileSize;
4064
+ this.data32 = new Uint32Array(tileArea);
4065
+ const data8 = new Uint8ClampedArray(this.data32.buffer);
4066
+ this.imageData = new ImageData(data8, tileSize, tileSize);
4067
+ }
4068
+ data32;
4069
+ width;
4070
+ height;
4071
+ imageData;
4072
+ };
4073
+
4074
+ // src/PixelTile/PixelTilePool.ts
4075
+ var PixelTilePool = class {
4076
+ pool;
4077
+ tileSize;
4078
+ tileArea;
4079
+ constructor(config) {
4080
+ this.pool = [];
4081
+ this.tileSize = config.tileSize;
4082
+ this.tileArea = config.tileArea;
4083
+ }
4084
+ getTile(id, tx, ty) {
4085
+ let tile = this.pool.pop();
4086
+ if (tile) {
4087
+ tile.id = id;
4088
+ tile.tx = tx;
4089
+ tile.ty = ty;
4090
+ tile.data32.fill(0);
4091
+ return tile;
4092
+ }
4093
+ return new PixelTile(id, tx, ty, this.tileSize, this.tileArea);
4094
+ }
4095
+ releaseTile(tile) {
4096
+ this.pool.push(tile);
4097
+ }
4098
+ releaseTiles(tiles) {
4099
+ let length = tiles.length;
4100
+ for (let i = 0; i < length; i++) {
4101
+ let tile = tiles[i];
4102
+ if (tile) {
4103
+ this.pool.push(tile);
4104
+ }
4105
+ }
4106
+ tiles.length = 0;
4107
+ }
4108
+ };
4109
+
3806
4110
  // src/History/PixelWriter.ts
3807
4111
  var PixelWriter = class {
3808
- target;
3809
4112
  historyManager;
3810
4113
  accumulator;
4114
+ historyActionFactory;
3811
4115
  config;
3812
4116
  mutator;
3813
4117
  constructor(target, mutatorFactory, {
3814
4118
  tileSize = 256,
3815
4119
  maxHistorySteps = 50,
3816
- historyManager = new HistoryManager(maxHistorySteps)
4120
+ historyManager = new HistoryManager(maxHistorySteps),
4121
+ historyActionFactory = makeHistoryAction,
4122
+ pixelTilePool
3817
4123
  } = {}) {
3818
- this.target = target;
3819
- this.config = new PixelEngineConfig(tileSize);
4124
+ this.config = new PixelEngineConfig(tileSize, target);
3820
4125
  this.historyManager = historyManager;
3821
- this.accumulator = new PixelAccumulator(target, this.config);
4126
+ pixelTilePool ??= new PixelTilePool(this.config);
4127
+ this.accumulator = new PixelAccumulator(this.config, pixelTilePool);
4128
+ this.historyActionFactory = historyActionFactory;
3822
4129
  this.mutator = mutatorFactory(this);
3823
4130
  }
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
- };
4131
+ withHistory(cb, after, afterUndo, afterRedo) {
4132
+ try {
4133
+ cb(this.mutator);
4134
+ } catch (e) {
4135
+ this.accumulator.rollback();
4136
+ throw e;
4137
+ }
4138
+ if (this.accumulator.beforeTiles.length === 0) return;
4139
+ const patch = this.accumulator.extractPatch();
4140
+ const action = this.historyActionFactory(this, patch, after, afterUndo, afterRedo);
3844
4141
  this.historyManager.commit(action);
3845
- this.accumulator.reset();
3846
4142
  }
3847
4143
  };
3848
4144
 
@@ -4000,11 +4296,10 @@ function resizeImageData(target, newWidth, newHeight, offsetX = 0, offsetY = 0)
4000
4296
  function makeReusableImageData() {
4001
4297
  let imageData = null;
4002
4298
  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) {
4299
+ if (imageData === null || imageData.width !== width || imageData.height !== height) {
4007
4300
  imageData = new ImageData(width, height);
4301
+ } else {
4302
+ imageData.data.fill(0);
4008
4303
  }
4009
4304
  return imageData;
4010
4305
  };
@@ -4411,8 +4706,8 @@ function makeBinaryMask(w, h, data) {
4411
4706
  };
4412
4707
  }
4413
4708
 
4414
- // src/Mask/CircleBrushAlphaMask.ts
4415
- function makeCircleBrushAlphaMask(size, fallOff = () => 1) {
4709
+ // src/Mask/CircleAlphaMask.ts
4710
+ function makeCircleAlphaMask(size, fallOff = () => 1) {
4416
4711
  const area = size * size;
4417
4712
  const data = new Uint8Array(area);
4418
4713
  const radius = size / 2;
@@ -4440,8 +4735,8 @@ function makeCircleBrushAlphaMask(size, fallOff = () => 1) {
4440
4735
  };
4441
4736
  }
4442
4737
 
4443
- // src/Mask/CircleBrushBinaryMask.ts
4444
- function makeCircleBrushBinaryMask(size) {
4738
+ // src/Mask/CircleBinaryMask.ts
4739
+ function makeCircleBinaryMask(size) {
4445
4740
  const area = size * size;
4446
4741
  const data = new Uint8Array(area);
4447
4742
  const radius = size / 2;
@@ -4737,57 +5032,6 @@ function setMaskData(mask, width, height, data) {
4737
5032
  mask.data = data;
4738
5033
  }
4739
5034
 
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
5035
  // src/Rect/getRectsBounds.ts
4792
5036
  function getRectsBounds(rects) {
4793
5037
  if (rects.length === 1) return {
@@ -4900,8 +5144,59 @@ function mergeBinaryMaskRects(current, adding) {
4900
5144
  return rects;
4901
5145
  }
4902
5146
 
5147
+ // src/MaskRect/subtractBinaryMaskRects.ts
5148
+ function subtractBinaryMaskRects(current, subtracting) {
5149
+ let result = [...current];
5150
+ for (const sub of subtracting) {
5151
+ const next = [];
5152
+ for (const r of result) {
5153
+ const ix = Math.max(r.x, sub.x);
5154
+ const iy = Math.max(r.y, sub.y);
5155
+ const ix2 = Math.min(r.x + r.w, sub.x + sub.w);
5156
+ const iy2 = Math.min(r.y + r.h, sub.y + sub.h);
5157
+ if (ix >= ix2 || iy >= iy2) {
5158
+ next.push(r);
5159
+ continue;
5160
+ }
5161
+ if (r.y < iy) pushPiece(next, r, r.x, r.y, r.w, iy - r.y);
5162
+ if (iy2 < r.y + r.h) pushPiece(next, r, r.x, iy2, r.w, r.y + r.h - iy2);
5163
+ if (r.x < ix) pushPiece(next, r, r.x, iy, ix - r.x, iy2 - iy);
5164
+ if (ix2 < r.x + r.w) pushPiece(next, r, ix2, iy, r.x + r.w - ix2, iy2 - iy);
5165
+ }
5166
+ result = next;
5167
+ }
5168
+ return result;
5169
+ }
5170
+ function pushPiece(dest, r, x, y, w, h) {
5171
+ if (r.data === null || r.data === void 0) {
5172
+ dest.push({
5173
+ x,
5174
+ y,
5175
+ w,
5176
+ h,
5177
+ data: null,
5178
+ type: null
5179
+ });
5180
+ return;
5181
+ }
5182
+ const lx = x - r.x;
5183
+ const ly = y - r.y;
5184
+ const data = new Uint8Array(w * h);
5185
+ for (let row = 0; row < h; row++) {
5186
+ data.set(r.data.subarray((ly + row) * r.w + lx, (ly + row) * r.w + lx + w), row * w);
5187
+ }
5188
+ dest.push({
5189
+ x,
5190
+ y,
5191
+ w,
5192
+ h,
5193
+ data,
5194
+ type: 1 /* BINARY */
5195
+ });
5196
+ }
5197
+
4903
5198
  // src/PixelData/PixelData.ts
4904
- var PixelData = class _PixelData {
5199
+ var PixelData = class {
4905
5200
  data32;
4906
5201
  imageData;
4907
5202
  width;
@@ -4919,30 +5214,35 @@ var PixelData = class _PixelData {
4919
5214
  this.width = imageData.width;
4920
5215
  this.height = imageData.height;
4921
5216
  }
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
- };
5217
+ };
5218
+
5219
+ // src/PixelData/blendPixelDataPaintBuffer.ts
5220
+ var SCRATCH_OPTS = {
5221
+ x: 0,
5222
+ y: 0,
5223
+ alpha: 255,
5224
+ blendFn: void 0
5225
+ };
5226
+ function blendPixelDataPaintBuffer(paintBuffer, target, alpha = 255, blendFn, blendPixelDataFn = blendPixelData) {
5227
+ const tileShift = paintBuffer.config.tileShift;
5228
+ const lookup = paintBuffer.lookup;
5229
+ for (let i = 0; i < lookup.length; i++) {
5230
+ const tile = lookup[i];
5231
+ if (tile) {
5232
+ const x = tile.tx << tileShift;
5233
+ const y = tile.ty << tileShift;
5234
+ SCRATCH_OPTS.x = x;
5235
+ SCRATCH_OPTS.y = y;
5236
+ SCRATCH_OPTS.alpha = alpha;
5237
+ SCRATCH_OPTS.blendFn = blendFn;
5238
+ blendPixelDataFn(target, tile, SCRATCH_OPTS);
4938
5239
  }
4939
- return new _PixelData(newImageData);
4940
5240
  }
4941
- };
5241
+ }
4942
5242
 
4943
5243
  // src/PixelData/clearPixelData.ts
4944
5244
  function clearPixelData(dst, rect) {
4945
- fillPixelData(dst, 0, rect);
5245
+ fillPixelDataFast(dst, 0, rect);
4946
5246
  }
4947
5247
 
4948
5248
  // src/PixelData/extractPixelDataBuffer.ts
@@ -5161,6 +5461,124 @@ function writePixelDataBuffer(target, data, _x, _y, _w, _h) {
5161
5461
  dstData.set(data.subarray(srcStart, srcStart + copyW), dstStart);
5162
5462
  }
5163
5463
  }
5464
+
5465
+ // src/PixelTile/PaintBuffer.ts
5466
+ var PaintBuffer = class {
5467
+ constructor(config, tilePool) {
5468
+ this.config = config;
5469
+ this.tilePool = tilePool;
5470
+ this.lookup = [];
5471
+ }
5472
+ lookup;
5473
+ processMaskTiles(mask, callback) {
5474
+ const {
5475
+ tileShift,
5476
+ targetColumns
5477
+ } = this.config;
5478
+ const x1 = mask.x >> tileShift;
5479
+ const y1 = mask.y >> tileShift;
5480
+ const x2 = mask.x + mask.w - 1 >> tileShift;
5481
+ const y2 = mask.y + mask.h - 1 >> tileShift;
5482
+ for (let ty = y1; ty <= y2; ty++) {
5483
+ const tileRowIndex = ty * targetColumns;
5484
+ const tileTop = ty << tileShift;
5485
+ for (let tx = x1; tx <= x2; tx++) {
5486
+ const id = tileRowIndex + tx;
5487
+ let tile = this.lookup[id];
5488
+ if (!tile) {
5489
+ tile = this.tilePool.getTile(id, tx, ty);
5490
+ this.lookup[id] = tile;
5491
+ }
5492
+ const tileLeft = tx << tileShift;
5493
+ const startX = Math.max(mask.x, tileLeft);
5494
+ const endX = Math.min(mask.x + mask.w, tileLeft + this.config.tileSize);
5495
+ const startY = Math.max(mask.y, tileTop);
5496
+ const endY = Math.min(mask.y + mask.h, tileTop + this.config.tileSize);
5497
+ callback(tile, startX, startY, endX - startX, endY - startY, startX - mask.x, startY - mask.y);
5498
+ }
5499
+ }
5500
+ }
5501
+ writeColorBinaryMaskRect(color, mask) {
5502
+ const {
5503
+ tileShift,
5504
+ tileMask
5505
+ } = this.config;
5506
+ const maskData = mask.data;
5507
+ const maskW = mask.w;
5508
+ this.processMaskTiles(mask, (tile, bX, bY, bW, bH, mX, mY) => {
5509
+ const data32 = tile.data32;
5510
+ const startTileX = bX & tileMask;
5511
+ for (let i = 0; i < bH; i++) {
5512
+ const tileY = bY + i & tileMask;
5513
+ const maskY = mY + i;
5514
+ const tileRowOffset = tileY << tileShift;
5515
+ const maskRowOffset = maskY * maskW;
5516
+ const destStart = tileRowOffset + startTileX;
5517
+ const maskStart = maskRowOffset + mX;
5518
+ for (let j = 0; j < bW; j++) {
5519
+ if (maskData[maskStart + j]) {
5520
+ data32[destStart + j] = color;
5521
+ }
5522
+ }
5523
+ }
5524
+ });
5525
+ }
5526
+ writeColorAlphaMaskRect(color, mask) {
5527
+ const {
5528
+ tileShift,
5529
+ tileMask
5530
+ } = this.config;
5531
+ const maskData = mask.data;
5532
+ const maskW = mask.w;
5533
+ const colorRGB = color & 16777215;
5534
+ const colorA = color >>> 24;
5535
+ this.processMaskTiles(mask, (tile, bX, bY, bW, bH, mX, mY) => {
5536
+ const data32 = tile.data32;
5537
+ const startTileX = bX & tileMask;
5538
+ for (let i = 0; i < bH; i++) {
5539
+ const tileY = bY + i & tileMask;
5540
+ const maskY = mY + i;
5541
+ const tileRowOffset = tileY << tileShift;
5542
+ const maskRowOffset = maskY * maskW;
5543
+ const destStart = tileRowOffset + startTileX;
5544
+ const maskStart = maskRowOffset + mX;
5545
+ for (let j = 0; j < bW; j++) {
5546
+ const maskA = maskData[maskStart + j];
5547
+ if (maskA > 0) {
5548
+ const finalA = colorA * maskA + 128 >> 8;
5549
+ data32[destStart + j] = (colorRGB | finalA << 24) >>> 0;
5550
+ }
5551
+ }
5552
+ }
5553
+ });
5554
+ }
5555
+ clear() {
5556
+ this.tilePool.releaseTiles(this.lookup);
5557
+ }
5558
+ };
5559
+
5560
+ // src/PixelTile/PaintBufferRenderer.ts
5561
+ function makePaintBufferRenderer(paintBuffer, offscreenCanvasClass = OffscreenCanvas) {
5562
+ const config = paintBuffer.config;
5563
+ const tileSize = config.tileSize;
5564
+ const tileShift = config.tileShift;
5565
+ const lookup = paintBuffer.lookup;
5566
+ const canvas = new offscreenCanvasClass(tileSize, tileSize);
5567
+ const ctx = canvas.getContext("2d");
5568
+ if (!ctx) throw new Error(CANVAS_CTX_FAILED);
5569
+ ctx.imageSmoothingEnabled = false;
5570
+ return function drawPaintBuffer(target) {
5571
+ for (let i = 0; i < lookup.length; i++) {
5572
+ const tile = lookup[i];
5573
+ if (tile) {
5574
+ const dx = tile.tx << tileShift;
5575
+ const dy = tile.ty << tileShift;
5576
+ ctx.putImageData(tile.imageData, 0, 0);
5577
+ target.drawImage(canvas, dx, dy);
5578
+ }
5579
+ }
5580
+ };
5581
+ }
5164
5582
  // Annotate the CommonJS export names for ESM import in node:
5165
5583
  0 && (module.exports = {
5166
5584
  BASE_FAST_BLEND_MODE_FUNCTIONS,
@@ -5171,17 +5589,18 @@ function writePixelDataBuffer(target, data, _x, _y, _w, _h) {
5171
5589
  IndexedImage,
5172
5590
  MaskType,
5173
5591
  OFFSCREEN_CANVAS_CTX_FAILED,
5592
+ PaintBuffer,
5174
5593
  PixelAccumulator,
5175
5594
  PixelBuffer32,
5176
5595
  PixelData,
5177
5596
  PixelEngineConfig,
5178
5597
  PixelTile,
5598
+ PixelTilePool,
5179
5599
  PixelWriter,
5180
5600
  UnsupportedFormatError,
5181
5601
  applyAlphaMaskToPixelData,
5182
5602
  applyBinaryMaskToAlphaMask,
5183
5603
  applyBinaryMaskToPixelData,
5184
- applyCircleBrushToPixelData,
5185
5604
  applyPatchTiles,
5186
5605
  applyRectBrushToPixelData,
5187
5606
  base64DecodeArrayBuffer,
@@ -5189,9 +5608,12 @@ function writePixelDataBuffer(target, data, _x, _y, _w, _h) {
5189
5608
  blendColorPixelData,
5190
5609
  blendColorPixelDataAlphaMask,
5191
5610
  blendColorPixelDataBinaryMask,
5611
+ blendColorPixelDataCircleMask,
5612
+ blendPixel,
5192
5613
  blendPixelData,
5193
5614
  blendPixelDataAlphaMask,
5194
5615
  blendPixelDataBinaryMask,
5616
+ blendPixelDataPaintBuffer,
5195
5617
  clearPixelData,
5196
5618
  color32ToCssRGBA,
5197
5619
  color32ToHex,
@@ -5225,6 +5647,7 @@ function writePixelDataBuffer(target, data, _x, _y, _w, _h) {
5225
5647
  fileToImageData,
5226
5648
  fillPixelData,
5227
5649
  fillPixelDataBinaryMask,
5650
+ fillPixelDataFast,
5228
5651
  floodFillSelection,
5229
5652
  forEachLinePoint,
5230
5653
  getCircleBrushOrPencilBounds,
@@ -5265,15 +5688,19 @@ function writePixelDataBuffer(target, data, _x, _y, _w, _h) {
5265
5688
  makeAlphaMask,
5266
5689
  makeBinaryMask,
5267
5690
  makeBlendModeRegistry,
5268
- makeCircleBrushAlphaMask,
5269
- makeCircleBrushBinaryMask,
5691
+ makeCanvasFrameRenderer,
5692
+ makeCircleAlphaMask,
5693
+ makeCircleBinaryMask,
5270
5694
  makeFastBlendModeRegistry,
5271
5695
  makeFullPixelMutator,
5696
+ makeHistoryAction,
5272
5697
  makeImageDataLike,
5698
+ makePaintBufferRenderer,
5273
5699
  makePerfectBlendModeRegistry,
5274
5700
  makePixelCanvas,
5275
5701
  makeReusableCanvas,
5276
5702
  makeReusableImageData,
5703
+ makeReusableOffscreenCanvas,
5277
5704
  merge2BinaryMaskRects,
5278
5705
  mergeAlphaMasks,
5279
5706
  mergeBinaryMaskRects,
@@ -5282,7 +5709,6 @@ function writePixelDataBuffer(target, data, _x, _y, _w, _h) {
5282
5709
  multiplyPerfect,
5283
5710
  mutatorApplyAlphaMask,
5284
5711
  mutatorApplyBinaryMask,
5285
- mutatorApplyCircleBrush,
5286
5712
  mutatorApplyCircleBrushStroke,
5287
5713
  mutatorApplyCirclePencil,
5288
5714
  mutatorApplyCirclePencilStroke,
@@ -5291,6 +5717,7 @@ function writePixelDataBuffer(target, data, _x, _y, _w, _h) {
5291
5717
  mutatorApplyRectPencil,
5292
5718
  mutatorApplyRectPencilStroke,
5293
5719
  mutatorBlendColor,
5720
+ mutatorBlendColorCircleMask,
5294
5721
  mutatorBlendPixel,
5295
5722
  mutatorBlendPixelData,
5296
5723
  mutatorBlendPixelDataAlphaMask,
@@ -5298,6 +5725,7 @@ function writePixelDataBuffer(target, data, _x, _y, _w, _h) {
5298
5725
  mutatorClear,
5299
5726
  mutatorFill,
5300
5727
  mutatorFillBinaryMask,
5728
+ mutatorFillRect,
5301
5729
  mutatorInvert,
5302
5730
  overlayFast,
5303
5731
  overlayPerfect,