pixel-data-js 0.24.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/dist/index.dev.cjs +1431 -1845
  2. package/dist/index.dev.cjs.map +1 -1
  3. package/dist/index.dev.js +1297 -1702
  4. package/dist/index.dev.js.map +1 -1
  5. package/dist/index.prod.cjs +1305 -1719
  6. package/dist/index.prod.cjs.map +1 -1
  7. package/dist/index.prod.d.ts +220 -328
  8. package/dist/index.prod.js +1423 -1828
  9. package/dist/index.prod.js.map +1 -1
  10. package/package.json +1 -1
  11. package/src/Algorithm/floodFillSelection.ts +2 -2
  12. package/src/Canvas/canvas-blend-modes.ts +28 -0
  13. package/src/History/PixelAccumulator.ts +52 -29
  14. package/src/History/PixelEngineConfig.ts +7 -9
  15. package/src/History/PixelMutator/mutatorBlendPixelData.ts +2 -2
  16. package/src/History/PixelMutator/mutatorBlendPixelDataAlphaMask.ts +2 -2
  17. package/src/History/PixelMutator/mutatorBlendPixelDataBinaryMask.ts +2 -2
  18. package/src/History/PixelMutator.ts +0 -20
  19. package/src/History/PixelPatchTiles.ts +2 -2
  20. package/src/History/PixelWriter.ts +132 -9
  21. package/src/Internal/helpers.ts +2 -0
  22. package/src/Paint/PaintBuffer.ts +269 -0
  23. package/src/{PixelTile/PaintBufferRenderer.ts → Paint/PaintBufferCanvasRenderer.ts} +13 -5
  24. package/src/Paint/makeCirclePaintAlphaMask.ts +41 -0
  25. package/src/{Mask/CircleBinaryMask.ts → Paint/makeCirclePaintBinaryMask.ts} +5 -6
  26. package/src/Paint/makePaintMask.ts +28 -0
  27. package/src/Paint/makeRectFalloffPaintAlphaMask.ts +47 -0
  28. package/src/PixelData/PixelBuffer32.ts +2 -2
  29. package/src/PixelData/PixelData.ts +1 -1
  30. package/src/PixelData/applyAlphaMaskToPixelData.ts +2 -2
  31. package/src/PixelData/applyBinaryMaskToPixelData.ts +2 -2
  32. package/src/PixelData/blendColorPixelData.ts +2 -2
  33. package/src/PixelData/blendColorPixelDataAlphaMask.ts +3 -3
  34. package/src/PixelData/blendColorPixelDataBinaryMask.ts +3 -3
  35. package/src/PixelData/blendPixel.ts +2 -2
  36. package/src/PixelData/blendPixelData.ts +3 -3
  37. package/src/PixelData/blendPixelDataAlphaMask.ts +3 -3
  38. package/src/PixelData/blendPixelDataBinaryMask.ts +3 -3
  39. package/src/PixelData/blendPixelDataPaintBuffer.ts +3 -3
  40. package/src/PixelData/clearPixelData.ts +2 -2
  41. package/src/PixelData/extractPixelData.ts +4 -4
  42. package/src/PixelData/extractPixelDataBuffer.ts +4 -4
  43. package/src/PixelData/fillPixelData.ts +5 -5
  44. package/src/PixelData/fillPixelDataBinaryMask.ts +3 -3
  45. package/src/PixelData/fillPixelDataFast.ts +5 -5
  46. package/src/PixelData/invertPixelData.ts +2 -2
  47. package/src/PixelData/pixelDataToAlphaMask.ts +2 -2
  48. package/src/PixelData/reflectPixelData.ts +3 -3
  49. package/src/PixelData/resamplePixelData.ts +2 -2
  50. package/src/PixelData/writePaintBufferToPixelData.ts +26 -0
  51. package/src/PixelData/writePixelDataBuffer.ts +5 -5
  52. package/src/Rect/trimMaskRectBounds.ts +121 -0
  53. package/src/Rect/trimRectBounds.ts +25 -116
  54. package/src/_types.ts +16 -15
  55. package/src/index.ts +9 -24
  56. package/src/History/PixelMutator/mutatorApplyCircleBrushStroke.ts +0 -182
  57. package/src/History/PixelMutator/mutatorApplyCirclePencil.ts +0 -59
  58. package/src/History/PixelMutator/mutatorApplyCirclePencilStroke.ts +0 -172
  59. package/src/History/PixelMutator/mutatorApplyRectBrush.ts +0 -64
  60. package/src/History/PixelMutator/mutatorApplyRectBrushStroke.ts +0 -184
  61. package/src/History/PixelMutator/mutatorApplyRectPencil.ts +0 -65
  62. package/src/History/PixelMutator/mutatorApplyRectPencilStroke.ts +0 -166
  63. package/src/History/PixelMutator/mutatorBlendColorCircleMask.ts +0 -71
  64. package/src/Mask/CircleAlphaMask.ts +0 -32
  65. package/src/PixelData/applyRectBrushToPixelData.ts +0 -98
  66. package/src/PixelData/blendColorPixelDataCircleMask.ts +0 -92
  67. package/src/PixelTile/PaintBuffer.ts +0 -122
  68. package/src/Rect/getCircleBrushOrPencilBounds.ts +0 -43
  69. package/src/Rect/getCircleBrushOrPencilStrokeBounds.ts +0 -24
  70. package/src/Rect/getRectBrushOrPencilBounds.ts +0 -38
  71. package/src/Rect/getRectBrushOrPencilStrokeBounds.ts +0 -26
@@ -232,8 +232,8 @@ function extractMaskBuffer(maskBuffer, maskWidth, xOrRect, y, w, h) {
232
232
  return out;
233
233
  }
234
234
 
235
- // src/Rect/trimRectBounds.ts
236
- function trimRectBounds(target, bounds) {
235
+ // src/Rect/trimMaskRectBounds.ts
236
+ function trimMaskRectBounds(target, bounds) {
237
237
  const originalX = target.x;
238
238
  const originalY = target.y;
239
239
  const originalW = target.w;
@@ -421,7 +421,7 @@ function floodFillSelection(img, startX, startY, {
421
421
  finalMask[my * sw + mx] = 1;
422
422
  }
423
423
  }
424
- trimRectBounds(selectionRect, {
424
+ trimMaskRectBounds(selectionRect, {
425
425
  x: 0,
426
426
  y: 0,
427
427
  w: width,
@@ -1811,11 +1811,11 @@ var PixelAccumulator = class {
1811
1811
  * @param y pixel y coordinate
1812
1812
  */
1813
1813
  storePixelBeforeState(x, y) {
1814
- let shift = this.config.tileShift;
1815
- let columns = this.config.targetColumns;
1816
- let tx = x >> shift;
1817
- let ty = y >> shift;
1818
- let id = ty * columns + tx;
1814
+ const shift = this.config.tileShift;
1815
+ const columns = this.config.targetColumns;
1816
+ const tx = x >> shift;
1817
+ const ty = y >> shift;
1818
+ const id = ty * columns + tx;
1819
1819
  let tile = this.lookup[id];
1820
1820
  let added = false;
1821
1821
  if (!tile) {
@@ -1841,16 +1841,16 @@ var PixelAccumulator = class {
1841
1841
  * @param h pixel height
1842
1842
  */
1843
1843
  storeRegionBeforeState(x, y, w, h) {
1844
- let shift = this.config.tileShift;
1845
- let columns = this.config.targetColumns;
1846
- let startX = x >> shift;
1847
- let startY = y >> shift;
1848
- let endX = x + w - 1 >> shift;
1849
- let endY = y + h - 1 >> shift;
1850
- let startIndex = this.beforeTiles.length;
1844
+ const shift = this.config.tileShift;
1845
+ const columns = this.config.targetColumns;
1846
+ const startX = x >> shift;
1847
+ const startY = y >> shift;
1848
+ const endX = x + w - 1 >> shift;
1849
+ const endY = y + h - 1 >> shift;
1850
+ const startIndex = this.beforeTiles.length;
1851
1851
  for (let ty = startY; ty <= endY; ty++) {
1852
1852
  for (let tx = startX; tx <= endX; tx++) {
1853
- let id = ty * columns + tx;
1853
+ const id = ty * columns + tx;
1854
1854
  let tile = this.lookup[id];
1855
1855
  if (!tile) {
1856
1856
  tile = this.tilePool.getTile(id, tx, ty);
@@ -1862,7 +1862,7 @@ var PixelAccumulator = class {
1862
1862
  }
1863
1863
  return (didChange) => {
1864
1864
  if (!didChange) {
1865
- let length = this.beforeTiles.length;
1865
+ const length = this.beforeTiles.length;
1866
1866
  for (let i = startIndex; i < length; i++) {
1867
1867
  let t = this.beforeTiles[i];
1868
1868
  if (t) {
@@ -1875,15 +1875,34 @@ var PixelAccumulator = class {
1875
1875
  return didChange;
1876
1876
  };
1877
1877
  }
1878
+ storeTileBeforeState(id, tx, ty) {
1879
+ let tile = this.lookup[id];
1880
+ let added = false;
1881
+ if (!tile) {
1882
+ tile = this.tilePool.getTile(id, tx, ty);
1883
+ this.extractState(tile);
1884
+ this.lookup[id] = tile;
1885
+ this.beforeTiles.push(tile);
1886
+ added = true;
1887
+ }
1888
+ return (didChange) => {
1889
+ if (!didChange && added) {
1890
+ this.beforeTiles.pop();
1891
+ this.lookup[id] = void 0;
1892
+ this.tilePool.releaseTile(tile);
1893
+ }
1894
+ return didChange;
1895
+ };
1896
+ }
1878
1897
  extractState(tile) {
1879
- let target = this.config.target;
1880
- let TILE_SIZE = this.config.tileSize;
1881
- let dst = tile.data32;
1882
- let src = target.data32;
1883
- let startX = tile.tx * TILE_SIZE;
1884
- let startY = tile.ty * TILE_SIZE;
1885
- let targetWidth = target.width;
1886
- let targetHeight = target.height;
1898
+ const target = this.config.target;
1899
+ const TILE_SIZE = this.config.tileSize;
1900
+ const dst = tile.data32;
1901
+ const src = target.data32;
1902
+ const startX = tile.tx * TILE_SIZE;
1903
+ const startY = tile.ty * TILE_SIZE;
1904
+ const targetWidth = target.width;
1905
+ const targetHeight = target.height;
1887
1906
  if (startX >= targetWidth || startX + TILE_SIZE <= 0 || startY >= targetHeight || startY + TILE_SIZE <= 0) {
1888
1907
  dst.fill(0);
1889
1908
  return;
@@ -1909,8 +1928,8 @@ var PixelAccumulator = class {
1909
1928
  }
1910
1929
  }
1911
1930
  extractPatch() {
1912
- let afterTiles = [];
1913
- let length = this.beforeTiles.length;
1931
+ const afterTiles = [];
1932
+ const length = this.beforeTiles.length;
1914
1933
  for (let i = 0; i < length; i++) {
1915
1934
  let beforeTile = this.beforeTiles[i];
1916
1935
  if (beforeTile) {
@@ -1919,7 +1938,7 @@ var PixelAccumulator = class {
1919
1938
  afterTiles.push(afterTile);
1920
1939
  }
1921
1940
  }
1922
- let beforeTiles = this.beforeTiles;
1941
+ const beforeTiles = this.beforeTiles;
1923
1942
  this.beforeTiles = [];
1924
1943
  this.lookup.length = 0;
1925
1944
  return {
@@ -1927,10 +1946,10 @@ var PixelAccumulator = class {
1927
1946
  afterTiles
1928
1947
  };
1929
1948
  }
1930
- rollback() {
1931
- let target = this.config.target;
1932
- let tileSize = this.config.tileSize;
1933
- let length = this.beforeTiles.length;
1949
+ rollbackAfterError() {
1950
+ const target = this.config.target;
1951
+ const tileSize = this.config.tileSize;
1952
+ const length = this.beforeTiles.length;
1934
1953
  applyPatchTiles(target, this.beforeTiles, tileSize);
1935
1954
  for (let i = 0; i < length; i++) {
1936
1955
  let tile = this.beforeTiles[i];
@@ -1954,6 +1973,7 @@ var PixelEngineConfig = class {
1954
1973
  tileArea;
1955
1974
  target;
1956
1975
  targetColumns = 0;
1976
+ targetRows = 0;
1957
1977
  constructor(tileSize, target) {
1958
1978
  if ((tileSize & tileSize - 1) !== 0) {
1959
1979
  throw new Error("tileSize must be a power of 2");
@@ -1962,28 +1982,26 @@ var PixelEngineConfig = class {
1962
1982
  this.tileShift = 31 - Math.clz32(tileSize);
1963
1983
  this.tileMask = tileSize - 1;
1964
1984
  this.tileArea = tileSize * tileSize;
1965
- this.setTarget(target);
1966
- }
1967
- setTarget(target) {
1968
- ;
1969
1985
  this.target = target;
1970
1986
  this.targetColumns = target.width + this.tileMask >> this.tileShift;
1987
+ this.targetRows = target.height + this.tileMask >> this.tileShift;
1971
1988
  }
1972
1989
  };
1973
1990
 
1974
- // src/PixelData/applyAlphaMaskToPixelData.ts
1975
- function applyAlphaMaskToPixelData(dst, mask, opts = {}) {
1991
+ // src/PixelData/blendColorPixelData.ts
1992
+ function blendColorPixelData(dst, color, opts = {}) {
1976
1993
  const {
1977
1994
  x: targetX = 0,
1978
1995
  y: targetY = 0,
1979
1996
  w: width = dst.width,
1980
1997
  h: height = dst.height,
1981
1998
  alpha: globalAlpha = 255,
1982
- mx = 0,
1983
- my = 0,
1984
- invertMask = false
1999
+ blendFn = sourceOverPerfect
1985
2000
  } = opts;
1986
2001
  if (globalAlpha === 0) return false;
2002
+ const baseSrcAlpha = color >>> 24;
2003
+ const isOverwrite = blendFn.isOverwrite || false;
2004
+ if (baseSrcAlpha === 0 && !isOverwrite) return false;
1987
2005
  let x = targetX;
1988
2006
  let y = targetY;
1989
2007
  let w = width;
@@ -1996,82 +2014,46 @@ function applyAlphaMaskToPixelData(dst, mask, opts = {}) {
1996
2014
  h += y;
1997
2015
  y = 0;
1998
2016
  }
1999
- w = Math.min(w, dst.width - x);
2000
- h = Math.min(h, dst.height - y);
2001
- if (w <= 0) return false;
2002
- if (h <= 0) return false;
2003
- const mPitch = mask.w;
2004
- if (mPitch <= 0) return false;
2005
- const startX = mx + (x - targetX);
2006
- const startY = my + (y - targetY);
2007
- const sX0 = Math.max(0, startX);
2008
- const sY0 = Math.max(0, startY);
2009
- const sX1 = Math.min(mPitch, startX + w);
2010
- const sY1 = Math.min(mask.h, startY + h);
2011
- const finalW = sX1 - sX0;
2012
- const finalH = sY1 - sY0;
2013
- if (finalW <= 0) return false;
2014
- if (finalH <= 0) return false;
2015
- const xShift = sX0 - startX;
2016
- const yShift = sY0 - startY;
2017
+ const actualW = Math.min(w, dst.width - x);
2018
+ const actualH = Math.min(h, dst.height - y);
2019
+ if (actualW <= 0 || actualH <= 0) return false;
2020
+ let finalSrcColor = color;
2021
+ if (globalAlpha < 255) {
2022
+ const a = baseSrcAlpha * globalAlpha + 128 >> 8;
2023
+ if (a === 0 && !isOverwrite) return false;
2024
+ finalSrcColor = (color & 16777215 | a << 24) >>> 0;
2025
+ }
2017
2026
  const dst32 = dst.data32;
2018
2027
  const dw = dst.width;
2019
- const dStride = dw - finalW;
2020
- const mStride = mPitch - finalW;
2021
- const maskData = mask.data;
2022
- let dIdx = (y + yShift) * dw + (x + xShift);
2023
- let mIdx = sY0 * mPitch + sX0;
2028
+ let dIdx = y * dw + x | 0;
2029
+ const dStride = dw - actualW | 0;
2024
2030
  let didChange = false;
2025
- for (let iy = 0; iy < h; iy++) {
2026
- for (let ix = 0; ix < w; ix++) {
2027
- const mVal = maskData[mIdx];
2028
- const effectiveM = invertMask ? 255 - mVal : mVal;
2029
- let weight = 0;
2030
- if (effectiveM === 0) {
2031
- weight = 0;
2032
- } else if (effectiveM === 255) {
2033
- weight = globalAlpha;
2034
- } else if (globalAlpha === 255) {
2035
- weight = effectiveM;
2036
- } else {
2037
- weight = effectiveM * globalAlpha + 128 >> 8;
2038
- }
2039
- if (weight === 0) {
2040
- dst32[dIdx] = (dst32[dIdx] & 16777215) >>> 0;
2031
+ for (let iy = 0; iy < actualH; iy++) {
2032
+ for (let ix = 0; ix < actualW; ix++) {
2033
+ const current = dst32[dIdx];
2034
+ const next = blendFn(finalSrcColor, current);
2035
+ if (current !== next) {
2036
+ dst32[dIdx] = next;
2041
2037
  didChange = true;
2042
- } else if (weight !== 255) {
2043
- const d = dst32[dIdx];
2044
- const da = d >>> 24;
2045
- if (da !== 0) {
2046
- const finalAlpha = da === 255 ? weight : da * weight + 128 >> 8;
2047
- const current = dst32[dIdx];
2048
- const next = (d & 16777215 | finalAlpha << 24) >>> 0;
2049
- if (current !== next) {
2050
- dst32[dIdx] = next;
2051
- didChange = true;
2052
- }
2053
- }
2054
2038
  }
2055
2039
  dIdx++;
2056
- mIdx++;
2057
2040
  }
2058
2041
  dIdx += dStride;
2059
- mIdx += mStride;
2060
2042
  }
2061
2043
  return didChange;
2062
2044
  }
2063
2045
 
2064
- // src/History/PixelMutator/mutatorApplyAlphaMask.ts
2046
+ // src/History/PixelMutator/mutatorBlendColor.ts
2065
2047
  var defaults2 = {
2066
- applyAlphaMaskToPixelData
2048
+ blendColorPixelData
2067
2049
  };
2068
- var mutatorApplyAlphaMask = ((writer, deps = defaults2) => {
2050
+ var mutatorBlendColor = ((writer, deps = defaults2) => {
2069
2051
  const {
2070
- applyAlphaMaskToPixelData: applyAlphaMaskToPixelData2 = defaults2.applyAlphaMaskToPixelData
2052
+ blendColorPixelData: blendColorPixelData2 = defaults2.blendColorPixelData
2071
2053
  } = deps;
2072
2054
  return {
2073
- applyAlphaMask(mask, opts = {}) {
2074
- let target = writer.config.target;
2055
+ blendColor(color, opts = {}) {
2056
+ const target = writer.config.target;
2075
2057
  const {
2076
2058
  x = 0,
2077
2059
  y = 0,
@@ -2079,159 +2061,227 @@ var mutatorApplyAlphaMask = ((writer, deps = defaults2) => {
2079
2061
  h = target.height
2080
2062
  } = opts;
2081
2063
  const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2082
- return didChange(applyAlphaMaskToPixelData2(target, mask, opts));
2064
+ return didChange(blendColorPixelData2(target, color, opts));
2083
2065
  }
2084
2066
  };
2085
2067
  });
2086
2068
 
2087
- // src/PixelData/applyBinaryMaskToPixelData.ts
2088
- function applyBinaryMaskToPixelData(dst, mask, opts = {}) {
2069
+ // src/PixelData/blendPixel.ts
2070
+ function blendPixel(target, x, y, color, alpha = 255, blendFn = sourceOverPerfect) {
2071
+ if (alpha === 0) return false;
2072
+ let width = target.width;
2073
+ let height = target.height;
2074
+ if (x < 0 || x >= width || y < 0 || y >= height) return false;
2075
+ let srcAlpha = color >>> 24;
2076
+ let isOverwrite = blendFn.isOverwrite;
2077
+ if (srcAlpha === 0 && !isOverwrite) return false;
2078
+ let dst32 = target.data32;
2079
+ let index = y * width + x;
2080
+ let finalColor = color;
2081
+ if (alpha !== 255) {
2082
+ let finalAlpha = srcAlpha * alpha + 128 >> 8;
2083
+ if (finalAlpha === 0 && !isOverwrite) return false;
2084
+ finalColor = (color & 16777215 | finalAlpha << 24) >>> 0;
2085
+ }
2086
+ let current = dst32[index];
2087
+ let next = blendFn(finalColor, current);
2088
+ if (current !== next) {
2089
+ dst32[index] = next;
2090
+ return true;
2091
+ }
2092
+ return false;
2093
+ }
2094
+
2095
+ // src/History/PixelMutator/mutatorBlendPixel.ts
2096
+ var defaults3 = {
2097
+ blendPixel
2098
+ };
2099
+ var mutatorBlendPixel = ((writer, deps = defaults3) => {
2100
+ const {
2101
+ blendPixel: blendPixel2 = defaults3.blendPixel
2102
+ } = deps;
2103
+ return {
2104
+ blendPixel(x, y, color, alpha, blendFn) {
2105
+ const didChange = writer.accumulator.storePixelBeforeState(x, y);
2106
+ return didChange(blendPixel2(writer.config.target, x, y, color, alpha, blendFn));
2107
+ }
2108
+ };
2109
+ });
2110
+
2111
+ // src/PixelData/blendPixelData.ts
2112
+ function blendPixelData(dst, src, opts = {}) {
2089
2113
  const {
2090
2114
  x: targetX = 0,
2091
2115
  y: targetY = 0,
2092
- w: width = dst.width,
2093
- h: height = dst.height,
2116
+ sx: sourceX = 0,
2117
+ sy: sourceY = 0,
2118
+ w: width = src.width,
2119
+ h: height = src.height,
2094
2120
  alpha: globalAlpha = 255,
2095
- mx = 0,
2096
- my = 0,
2097
- invertMask = false
2121
+ blendFn = sourceOverPerfect
2098
2122
  } = opts;
2099
2123
  if (globalAlpha === 0) return false;
2100
2124
  let x = targetX;
2101
2125
  let y = targetY;
2126
+ let sx = sourceX;
2127
+ let sy = sourceY;
2102
2128
  let w = width;
2103
2129
  let h = height;
2130
+ if (sx < 0) {
2131
+ x -= sx;
2132
+ w += sx;
2133
+ sx = 0;
2134
+ }
2135
+ if (sy < 0) {
2136
+ y -= sy;
2137
+ h += sy;
2138
+ sy = 0;
2139
+ }
2140
+ w = Math.min(w, src.width - sx);
2141
+ h = Math.min(h, src.height - sy);
2104
2142
  if (x < 0) {
2143
+ sx -= x;
2105
2144
  w += x;
2106
2145
  x = 0;
2107
2146
  }
2108
2147
  if (y < 0) {
2148
+ sy -= y;
2109
2149
  h += y;
2110
2150
  y = 0;
2111
2151
  }
2112
- w = Math.min(w, dst.width - x);
2113
- h = Math.min(h, dst.height - y);
2114
- if (w <= 0 || h <= 0) return false;
2115
- const mPitch = mask.w;
2116
- if (mPitch <= 0) return false;
2117
- const startX = mx + (x - targetX);
2118
- const startY = my + (y - targetY);
2119
- const sX0 = Math.max(0, startX);
2120
- const sY0 = Math.max(0, startY);
2121
- const sX1 = Math.min(mPitch, startX + w);
2122
- const sY1 = Math.min(mask.h, startY + h);
2123
- const finalW = sX1 - sX0;
2124
- const finalH = sY1 - sY0;
2125
- if (finalW <= 0 || finalH <= 0) {
2126
- return false;
2127
- }
2128
- const xShift = sX0 - startX;
2129
- const yShift = sY0 - startY;
2152
+ const actualW = Math.min(w, dst.width - x);
2153
+ const actualH = Math.min(h, dst.height - y);
2154
+ if (actualW <= 0 || actualH <= 0) return false;
2130
2155
  const dst32 = dst.data32;
2156
+ const src32 = src.data32;
2131
2157
  const dw = dst.width;
2132
- const dStride = dw - finalW;
2133
- const mStride = mPitch - finalW;
2134
- const maskData = mask.data;
2135
- let dIdx = (y + yShift) * dw + (x + xShift);
2136
- let mIdx = sY0 * mPitch + sX0;
2158
+ const sw = src.width;
2159
+ let dIdx = y * dw + x | 0;
2160
+ let sIdx = sy * sw + sx | 0;
2161
+ const dStride = dw - actualW | 0;
2162
+ const sStride = sw - actualW | 0;
2163
+ const isOpaque = globalAlpha === 255;
2164
+ const isOverwrite = blendFn.isOverwrite;
2137
2165
  let didChange = false;
2138
- for (let iy = 0; iy < finalH; iy++) {
2139
- for (let ix = 0; ix < finalW; ix++) {
2140
- const mVal = maskData[mIdx];
2141
- const isMaskedOut = invertMask ? mVal !== 0 : mVal === 0;
2142
- if (isMaskedOut) {
2143
- const current = dst32[dIdx];
2144
- const next = (current & 16777215) >>> 0;
2145
- if (current !== next) {
2146
- dst32[dIdx] = next;
2147
- didChange = true;
2148
- }
2149
- } else if (globalAlpha !== 255) {
2150
- const d = dst32[dIdx];
2151
- const da = d >>> 24;
2152
- if (da !== 0) {
2153
- const finalAlpha = da === 255 ? globalAlpha : da * globalAlpha + 128 >> 8;
2154
- const next = (d & 16777215 | finalAlpha << 24) >>> 0;
2155
- if (d !== next) {
2156
- dst32[dIdx] = next;
2157
- didChange = true;
2158
- }
2166
+ for (let iy = 0; iy < actualH; iy++) {
2167
+ for (let ix = 0; ix < actualW; ix++) {
2168
+ const srcCol = src32[sIdx];
2169
+ const srcAlpha = srcCol >>> 24;
2170
+ if (srcAlpha === 0 && !isOverwrite) {
2171
+ dIdx++;
2172
+ sIdx++;
2173
+ continue;
2174
+ }
2175
+ let finalCol = srcCol;
2176
+ if (!isOpaque) {
2177
+ const a = srcAlpha * globalAlpha + 128 >> 8;
2178
+ if (a === 0 && !isOverwrite) {
2179
+ dIdx++;
2180
+ sIdx++;
2181
+ continue;
2159
2182
  }
2183
+ finalCol = (srcCol & 16777215 | a << 24) >>> 0;
2160
2184
  }
2161
- dIdx++;
2162
- mIdx++;
2185
+ const current = dst32[dIdx];
2186
+ const next = blendFn(finalCol, dst32[dIdx]);
2187
+ if (current !== next) {
2188
+ dst32[dIdx] = next;
2189
+ didChange = true;
2190
+ }
2191
+ dIdx++;
2192
+ sIdx++;
2163
2193
  }
2164
2194
  dIdx += dStride;
2165
- mIdx += mStride;
2195
+ sIdx += sStride;
2166
2196
  }
2167
2197
  return didChange;
2168
2198
  }
2169
2199
 
2170
- // src/History/PixelMutator/mutatorApplyBinaryMask.ts
2171
- var defaults3 = {
2172
- applyBinaryMaskToPixelData
2200
+ // src/History/PixelMutator/mutatorBlendPixelData.ts
2201
+ var defaults4 = {
2202
+ blendPixelData
2173
2203
  };
2174
- var mutatorApplyBinaryMask = ((writer, deps = defaults3) => {
2204
+ var mutatorBlendPixelData = ((writer, deps = defaults4) => {
2175
2205
  const {
2176
- applyBinaryMaskToPixelData: applyBinaryMaskToPixelData2 = defaults3.applyBinaryMaskToPixelData
2206
+ blendPixelData: blendPixelData2 = defaults4.blendPixelData
2177
2207
  } = deps;
2178
2208
  return {
2179
- applyBinaryMask(mask, opts = {}) {
2180
- let target = writer.config.target;
2209
+ blendPixelData(src, opts = {}) {
2181
2210
  const {
2182
2211
  x = 0,
2183
2212
  y = 0,
2184
- w = target.width,
2185
- h = target.height
2213
+ w = src.width,
2214
+ h = src.height
2186
2215
  } = opts;
2187
2216
  const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2188
- return didChange(applyBinaryMaskToPixelData2(target, mask, opts));
2217
+ return didChange(blendPixelData2(writer.config.target, src, opts));
2189
2218
  }
2190
2219
  };
2191
2220
  });
2192
2221
 
2193
- // src/PixelData/blendColorPixelDataAlphaMask.ts
2194
- function blendColorPixelDataAlphaMask(dst, color, mask, opts = {}) {
2195
- const targetX = opts.x ?? 0;
2196
- const targetY = opts.y ?? 0;
2197
- const w = opts.w ?? mask.w;
2198
- const h = opts.h ?? mask.h;
2199
- const globalAlpha = opts.alpha ?? 255;
2200
- const blendFn = opts.blendFn ?? sourceOverPerfect;
2201
- const mx = opts.mx ?? 0;
2202
- const my = opts.my ?? 0;
2203
- const invertMask = opts.invertMask ?? false;
2222
+ // src/PixelData/blendPixelDataAlphaMask.ts
2223
+ function blendPixelDataAlphaMask(dst, src, alphaMask, opts = {}) {
2224
+ const {
2225
+ x: targetX = 0,
2226
+ y: targetY = 0,
2227
+ sx: sourceX = 0,
2228
+ sy: sourceY = 0,
2229
+ w: width = src.width,
2230
+ h: height = src.height,
2231
+ alpha: globalAlpha = 255,
2232
+ blendFn = sourceOverPerfect,
2233
+ mx = 0,
2234
+ my = 0,
2235
+ invertMask = false
2236
+ } = opts;
2204
2237
  if (globalAlpha === 0) return false;
2205
- const baseSrcAlpha = color >>> 24;
2206
- const isOverwrite = blendFn.isOverwrite || false;
2207
- if (baseSrcAlpha === 0 && !isOverwrite) return false;
2208
2238
  let x = targetX;
2209
2239
  let y = targetY;
2210
- let actualW = w;
2211
- let actualH = h;
2240
+ let sx = sourceX;
2241
+ let sy = sourceY;
2242
+ let w = width;
2243
+ let h = height;
2244
+ if (sx < 0) {
2245
+ x -= sx;
2246
+ w += sx;
2247
+ sx = 0;
2248
+ }
2249
+ if (sy < 0) {
2250
+ y -= sy;
2251
+ h += sy;
2252
+ sy = 0;
2253
+ }
2254
+ w = Math.min(w, src.width - sx);
2255
+ h = Math.min(h, src.height - sy);
2212
2256
  if (x < 0) {
2213
- actualW += x;
2257
+ sx -= x;
2258
+ w += x;
2214
2259
  x = 0;
2215
2260
  }
2216
2261
  if (y < 0) {
2217
- actualH += y;
2262
+ sy -= y;
2263
+ h += y;
2218
2264
  y = 0;
2219
2265
  }
2220
- actualW = Math.min(actualW, dst.width - x);
2221
- actualH = Math.min(actualH, dst.height - y);
2266
+ const actualW = Math.min(w, dst.width - x);
2267
+ const actualH = Math.min(h, dst.height - y);
2222
2268
  if (actualW <= 0 || actualH <= 0) return false;
2269
+ const dw = dst.width;
2270
+ const sw = src.width;
2271
+ const mPitch = alphaMask.w;
2272
+ const maskData = alphaMask.data;
2223
2273
  const dx = x - targetX | 0;
2224
2274
  const dy = y - targetY | 0;
2225
2275
  const dst32 = dst.data32;
2226
- const dw = dst.width;
2227
- const mPitch = mask.w;
2228
- const maskData = mask.data;
2276
+ const src32 = src.data32;
2229
2277
  let dIdx = y * dw + x | 0;
2278
+ let sIdx = sy * sw + sx | 0;
2230
2279
  let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
2231
2280
  const dStride = dw - actualW | 0;
2281
+ const sStride = sw - actualW | 0;
2232
2282
  const mStride = mPitch - actualW | 0;
2233
2283
  const isOpaque = globalAlpha === 255;
2234
- const colorRGB = color & 16777215;
2284
+ const isOverwrite = blendFn.isOverwrite || false;
2235
2285
  let didChange = false;
2236
2286
  for (let iy = 0; iy < actualH; iy++) {
2237
2287
  for (let ix = 0; ix < actualW; ix++) {
@@ -2239,6 +2289,15 @@ function blendColorPixelDataAlphaMask(dst, color, mask, opts = {}) {
2239
2289
  const effM = invertMask ? 255 - mVal : mVal;
2240
2290
  if (effM === 0) {
2241
2291
  dIdx++;
2292
+ sIdx++;
2293
+ mIdx++;
2294
+ continue;
2295
+ }
2296
+ const srcCol = src32[sIdx];
2297
+ const srcAlpha = srcCol >>> 24;
2298
+ if (srcAlpha === 0 && !isOverwrite) {
2299
+ dIdx++;
2300
+ sIdx++;
2242
2301
  mIdx++;
2243
2302
  continue;
2244
2303
  }
@@ -2250,844 +2309,459 @@ function blendColorPixelDataAlphaMask(dst, color, mask, opts = {}) {
2250
2309
  }
2251
2310
  if (weight === 0) {
2252
2311
  dIdx++;
2312
+ sIdx++;
2253
2313
  mIdx++;
2254
2314
  continue;
2255
2315
  }
2256
- let finalCol = color;
2316
+ let finalCol = srcCol;
2257
2317
  if (weight < 255) {
2258
- const a = baseSrcAlpha * weight + 128 >> 8;
2318
+ const a = srcAlpha * weight + 128 >> 8;
2259
2319
  if (a === 0 && !isOverwrite) {
2260
2320
  dIdx++;
2321
+ sIdx++;
2261
2322
  mIdx++;
2262
2323
  continue;
2263
2324
  }
2264
- finalCol = (colorRGB | a << 24) >>> 0;
2325
+ finalCol = (srcCol & 16777215 | a << 24) >>> 0;
2265
2326
  }
2266
2327
  const current = dst32[dIdx];
2267
- const next = blendFn(finalCol, current);
2328
+ const next = blendFn(finalCol, dst32[dIdx]);
2268
2329
  if (current !== next) {
2269
2330
  dst32[dIdx] = next;
2270
2331
  didChange = true;
2271
2332
  }
2272
2333
  dIdx++;
2334
+ sIdx++;
2273
2335
  mIdx++;
2274
2336
  }
2275
2337
  dIdx += dStride;
2338
+ sIdx += sStride;
2276
2339
  mIdx += mStride;
2277
2340
  }
2278
2341
  return didChange;
2279
2342
  }
2280
2343
 
2281
- // src/Rect/getCircleBrushOrPencilBounds.ts
2282
- function getCircleBrushOrPencilBounds(centerX, centerY, brushSize, targetWidth, targetHeight, out) {
2283
- const r = brushSize / 2;
2284
- const minOffset = -Math.ceil(r - 0.5);
2285
- const maxOffset = Math.floor(r - 0.5);
2286
- const startX = Math.floor(centerX + minOffset);
2287
- const startY = Math.floor(centerY + minOffset);
2288
- const endX = Math.floor(centerX + maxOffset) + 1;
2289
- const endY = Math.floor(centerY + maxOffset) + 1;
2290
- const res = out ?? {
2291
- x: 0,
2292
- y: 0,
2293
- w: 0,
2294
- h: 0
2295
- };
2296
- const cStartX = Math.max(0, startX);
2297
- const cStartY = Math.max(0, startY);
2298
- const cEndX = Math.min(targetWidth, endX);
2299
- const cEndY = Math.min(targetHeight, endY);
2300
- const w = cEndX - cStartX;
2301
- const h = cEndY - cStartY;
2302
- res.x = cStartX;
2303
- res.y = cStartY;
2304
- res.w = w < 0 ? 0 : w;
2305
- res.h = h < 0 ? 0 : h;
2306
- return res;
2307
- }
2308
-
2309
- // src/Rect/getCircleBrushOrPencilStrokeBounds.ts
2310
- function getCircleBrushOrPencilStrokeBounds(x0, y0, x1, y1, brushSize, result) {
2311
- const r = Math.ceil(brushSize / 2);
2312
- const minX = Math.min(x0, x1) - r;
2313
- const minY = Math.min(y0, y1) - r;
2314
- const maxX = Math.max(x0, x1) + r;
2315
- const maxY = Math.max(x0, y1) + r;
2316
- result.x = Math.floor(minX);
2317
- result.y = Math.floor(minY);
2318
- result.w = Math.ceil(maxX - minX);
2319
- result.h = Math.ceil(maxY - minY);
2320
- return result;
2321
- }
2322
-
2323
- // src/History/PixelMutator/mutatorApplyCircleBrushStroke.ts
2324
- var defaults4 = {
2325
- forEachLinePoint,
2326
- blendColorPixelDataAlphaMask,
2327
- getCircleBrushOrPencilBounds,
2328
- getCircleBrushOrPencilStrokeBounds
2344
+ // src/History/PixelMutator/mutatorBlendPixelDataAlphaMask.ts
2345
+ var defaults5 = {
2346
+ blendPixelDataAlphaMask
2329
2347
  };
2330
- var mutatorApplyCircleBrushStroke = ((writer, deps = defaults4) => {
2348
+ var mutatorBlendPixelDataAlphaMask = ((writer, deps = defaults5) => {
2331
2349
  const {
2332
- forEachLinePoint: forEachLinePoint2 = defaults4.forEachLinePoint,
2333
- blendColorPixelDataAlphaMask: blendColorPixelDataAlphaMask2 = defaults4.blendColorPixelDataAlphaMask,
2334
- getCircleBrushOrPencilBounds: getCircleBrushOrPencilBounds2 = defaults4.getCircleBrushOrPencilBounds,
2335
- getCircleBrushOrPencilStrokeBounds: getCircleBrushOrPencilStrokeBounds2 = defaults4.getCircleBrushOrPencilStrokeBounds
2350
+ blendPixelDataAlphaMask: blendPixelDataAlphaMask2 = defaults5.blendPixelDataAlphaMask
2336
2351
  } = deps;
2337
- const strokeBoundsOut = {
2338
- x: 0,
2339
- y: 0,
2340
- w: 0,
2341
- h: 0
2342
- };
2343
- const circleBrushBounds = {
2344
- x: 0,
2345
- y: 0,
2346
- w: 0,
2347
- h: 0
2348
- };
2349
- const blendColorPixelOptions = {
2350
- alpha: 255,
2351
- blendFn: sourceOverPerfect,
2352
- x: 0,
2353
- y: 0,
2354
- w: 0,
2355
- h: 0
2356
- };
2357
- const mask = {
2358
- type: 0 /* ALPHA */,
2359
- data: null,
2360
- w: 0,
2361
- h: 0
2362
- };
2363
2352
  return {
2364
- applyCircleBrushStroke(color, x0, y0, x1, y1, brush, alpha = 255, blendFn = sourceOverPerfect) {
2365
- const brushSize = brush.size;
2366
- const {
2367
- x: bx,
2368
- y: by,
2369
- w: bw,
2370
- h: bh
2371
- } = getCircleBrushOrPencilStrokeBounds2(x0, y0, x1, y1, brushSize, strokeBoundsOut);
2372
- if (bw <= 0 || bh <= 0) return;
2373
- mask.data = new Uint8Array(bw * bh);
2374
- mask.w = bw;
2375
- mask.h = bh;
2376
- const maskData = mask.data;
2377
- const brushData = brush.data;
2378
- const minOffset = brush.minOffset;
2379
- const target = writer.config.target;
2380
- const targetWidth = target.width;
2381
- const targetHeight = target.height;
2382
- forEachLinePoint2(x0, y0, x1, y1, (px, py) => {
2383
- const {
2384
- x: cbx,
2385
- y: cby,
2386
- w: cbw,
2387
- h: cbh
2388
- } = getCircleBrushOrPencilBounds2(px, py, brushSize, targetWidth, targetHeight, circleBrushBounds);
2389
- writer.accumulator.storeRegionBeforeState(cbx, cby, cbw, cbh);
2390
- const startX = Math.max(bx, cbx);
2391
- const startY = Math.max(by, cby);
2392
- const endX = Math.min(bx + bw, cbx + cbw);
2393
- const endY = Math.min(by + bh, cby + cbh);
2394
- const unclippedStartX = Math.floor(px + minOffset);
2395
- const unclippedStartY = Math.floor(py + minOffset);
2396
- for (let my = startY; my < endY; my++) {
2397
- const strokeMaskY = my - by;
2398
- const strokeMaskRowOffset = strokeMaskY * bw;
2399
- const brushY = my - unclippedStartY;
2400
- const brushRowOffset = brushY * brushSize;
2401
- for (let mx = startX; mx < endX; mx++) {
2402
- const brushX = mx - unclippedStartX;
2403
- const brushVal = brushData[brushRowOffset + brushX];
2404
- if (brushVal > 0) {
2405
- const strokeMaskIdx = strokeMaskRowOffset + (mx - bx);
2406
- if (brushVal > maskData[strokeMaskIdx]) {
2407
- maskData[strokeMaskIdx] = brushVal;
2408
- }
2409
- }
2410
- }
2411
- }
2412
- });
2413
- blendColorPixelOptions.blendFn = blendFn;
2414
- blendColorPixelOptions.alpha = alpha;
2415
- blendColorPixelOptions.x = bx;
2416
- blendColorPixelOptions.y = by;
2417
- blendColorPixelOptions.w = bw;
2418
- blendColorPixelOptions.h = bh;
2419
- blendColorPixelDataAlphaMask2(target, color, mask, blendColorPixelOptions);
2353
+ blendPixelDataAlphaMask(src, mask, opts = {}) {
2354
+ const x = opts.x ?? 0;
2355
+ const y = opts.y ?? 0;
2356
+ const w = opts.w ?? src.width;
2357
+ const h = opts.h ?? src.height;
2358
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2359
+ return didChange(blendPixelDataAlphaMask2(writer.config.target, src, mask, opts));
2420
2360
  }
2421
2361
  };
2422
2362
  });
2423
2363
 
2424
- // src/PixelData/blendColorPixelDataBinaryMask.ts
2425
- function blendColorPixelDataBinaryMask(dst, color, mask, opts = {}) {
2426
- const targetX = opts.x ?? 0;
2427
- const targetY = opts.y ?? 0;
2428
- let w = opts.w ?? mask.w;
2429
- let h = opts.h ?? mask.h;
2430
- const globalAlpha = opts.alpha ?? 255;
2431
- const blendFn = opts.blendFn ?? sourceOverPerfect;
2432
- const mx = opts.mx ?? 0;
2433
- const my = opts.my ?? 0;
2434
- const invertMask = opts.invertMask ?? false;
2364
+ // src/PixelData/blendPixelDataBinaryMask.ts
2365
+ function blendPixelDataBinaryMask(dst, src, binaryMask, opts = {}) {
2366
+ const {
2367
+ x: targetX = 0,
2368
+ y: targetY = 0,
2369
+ sx: sourceX = 0,
2370
+ sy: sourceY = 0,
2371
+ w: width = src.width,
2372
+ h: height = src.height,
2373
+ alpha: globalAlpha = 255,
2374
+ blendFn = sourceOverPerfect,
2375
+ mx = 0,
2376
+ my = 0,
2377
+ invertMask = false
2378
+ } = opts;
2435
2379
  if (globalAlpha === 0) return false;
2436
- const baseSrcAlpha = color >>> 24;
2437
- const isOverwrite = blendFn.isOverwrite || false;
2438
- if (baseSrcAlpha === 0 && !isOverwrite) return false;
2439
2380
  let x = targetX;
2440
2381
  let y = targetY;
2382
+ let sx = sourceX;
2383
+ let sy = sourceY;
2384
+ let w = width;
2385
+ let h = height;
2386
+ if (sx < 0) {
2387
+ x -= sx;
2388
+ w += sx;
2389
+ sx = 0;
2390
+ }
2391
+ if (sy < 0) {
2392
+ y -= sy;
2393
+ h += sy;
2394
+ sy = 0;
2395
+ }
2396
+ w = Math.min(w, src.width - sx);
2397
+ h = Math.min(h, src.height - sy);
2441
2398
  if (x < 0) {
2399
+ sx -= x;
2442
2400
  w += x;
2443
2401
  x = 0;
2444
2402
  }
2445
2403
  if (y < 0) {
2404
+ sy -= y;
2446
2405
  h += y;
2447
2406
  y = 0;
2448
2407
  }
2449
2408
  const actualW = Math.min(w, dst.width - x);
2450
2409
  const actualH = Math.min(h, dst.height - y);
2451
2410
  if (actualW <= 0 || actualH <= 0) return false;
2452
- let baseColorWithGlobalAlpha = color;
2453
- if (globalAlpha < 255) {
2454
- const a = baseSrcAlpha * globalAlpha + 128 >> 8;
2455
- if (a === 0 && !isOverwrite) return false;
2456
- baseColorWithGlobalAlpha = (color & 16777215 | a << 24) >>> 0;
2457
- }
2458
2411
  const dx = x - targetX | 0;
2459
2412
  const dy = y - targetY | 0;
2460
2413
  const dst32 = dst.data32;
2414
+ const src32 = src.data32;
2461
2415
  const dw = dst.width;
2462
- const mPitch = mask.w;
2463
- const maskData = mask.data;
2416
+ const sw = src.width;
2417
+ const mPitch = binaryMask.w;
2418
+ const maskData = binaryMask.data;
2464
2419
  let dIdx = y * dw + x | 0;
2420
+ let sIdx = sy * sw + sx | 0;
2465
2421
  let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
2466
2422
  const dStride = dw - actualW | 0;
2423
+ const sStride = sw - actualW | 0;
2467
2424
  const mStride = mPitch - actualW | 0;
2468
2425
  const skipVal = invertMask ? 1 : 0;
2426
+ const isOpaque = globalAlpha === 255;
2427
+ const isOverwrite = blendFn.isOverwrite || false;
2469
2428
  let didChange = false;
2470
2429
  for (let iy = 0; iy < actualH; iy++) {
2471
2430
  for (let ix = 0; ix < actualW; ix++) {
2472
2431
  if (maskData[mIdx] === skipVal) {
2473
2432
  dIdx++;
2433
+ sIdx++;
2434
+ mIdx++;
2435
+ continue;
2436
+ }
2437
+ const srcCol = src32[sIdx];
2438
+ const srcAlpha = srcCol >>> 24;
2439
+ if (srcAlpha === 0 && !isOverwrite) {
2440
+ dIdx++;
2441
+ sIdx++;
2474
2442
  mIdx++;
2475
2443
  continue;
2476
2444
  }
2445
+ let finalCol = srcCol;
2446
+ if (!isOpaque) {
2447
+ const a = srcAlpha * globalAlpha + 128 >> 8;
2448
+ if (a === 0 && !isOverwrite) {
2449
+ dIdx++;
2450
+ sIdx++;
2451
+ mIdx++;
2452
+ continue;
2453
+ }
2454
+ finalCol = (srcCol & 16777215 | a << 24) >>> 0;
2455
+ }
2477
2456
  const current = dst32[dIdx];
2478
- const next = blendFn(baseColorWithGlobalAlpha, current);
2457
+ const next = blendFn(finalCol, dst32[dIdx]);
2479
2458
  if (current !== next) {
2480
2459
  dst32[dIdx] = next;
2481
2460
  didChange = true;
2482
2461
  }
2483
2462
  dIdx++;
2463
+ sIdx++;
2484
2464
  mIdx++;
2485
2465
  }
2486
2466
  dIdx += dStride;
2467
+ sIdx += sStride;
2487
2468
  mIdx += mStride;
2488
2469
  }
2489
2470
  return didChange;
2490
2471
  }
2491
2472
 
2492
- // src/PixelData/blendColorPixelDataCircleMask.ts
2493
- function blendColorPixelDataCircleMask(target, color, centerX, centerY, brush, alpha = 255, blendFn = sourceOverPerfect, scratchOptions = {}, bounds) {
2494
- const b = bounds ?? getCircleBrushOrPencilBounds(centerX, centerY, brush.size, target.width, target.height);
2495
- if (b.w <= 0 || b.h <= 0) return false;
2496
- const unclippedStartX = Math.floor(centerX + brush.minOffset);
2497
- const unclippedStartY = Math.floor(centerY + brush.minOffset);
2498
- const ix = Math.max(unclippedStartX, b.x);
2499
- const iy = Math.max(unclippedStartY, b.y);
2500
- const ir = Math.min(unclippedStartX + brush.w, b.x + b.w);
2501
- const ib = Math.min(unclippedStartY + brush.h, b.y + b.h);
2502
- const iw = ir - ix;
2503
- const ih = ib - iy;
2504
- if (iw <= 0 || ih <= 0) return false;
2505
- scratchOptions.x = ix;
2506
- scratchOptions.y = iy;
2507
- scratchOptions.w = iw;
2508
- scratchOptions.h = ih;
2509
- scratchOptions.mx = ix - unclippedStartX;
2510
- scratchOptions.my = iy - unclippedStartY;
2511
- scratchOptions.alpha = alpha;
2512
- scratchOptions.blendFn = blendFn;
2513
- if (brush.type === 0 /* ALPHA */) {
2514
- return blendColorPixelDataAlphaMask(target, color, brush, scratchOptions);
2515
- }
2516
- if (brush.type === 1 /* BINARY */) {
2517
- return blendColorPixelDataBinaryMask(target, color, brush, scratchOptions);
2518
- }
2519
- return false;
2520
- }
2521
-
2522
- // src/History/PixelMutator/mutatorBlendColorCircleMask.ts
2523
- var defaults5 = {
2524
- blendColorPixelDataCircleMask,
2525
- getCircleBrushOrPencilBounds
2473
+ // src/History/PixelMutator/mutatorBlendPixelDataBinaryMask.ts
2474
+ var defaults6 = {
2475
+ blendPixelDataBinaryMask
2526
2476
  };
2527
- var mutatorBlendColorCircleMask = ((writer, deps = defaults5) => {
2477
+ var mutatorBlendPixelDataBinaryMask = ((writer, deps = defaults6) => {
2528
2478
  const {
2529
- blendColorPixelDataCircleMask: blendColorPixelDataCircleMask2 = defaults5.blendColorPixelDataCircleMask,
2530
- getCircleBrushOrPencilBounds: getCircleBrushOrPencilBounds2 = defaults5.getCircleBrushOrPencilBounds
2479
+ blendPixelDataBinaryMask: blendPixelDataBinaryMask2 = defaults6.blendPixelDataBinaryMask
2531
2480
  } = deps;
2532
- const boundsOut = {
2533
- x: 0,
2534
- y: 0,
2535
- w: 0,
2536
- h: 0
2537
- };
2538
- const blendColorPixelOptions = {
2539
- alpha: 255,
2540
- blendFn: sourceOverPerfect,
2541
- x: 0,
2542
- y: 0,
2543
- w: 0,
2544
- h: 0
2545
- };
2546
2481
  return {
2547
- applyCircleMask(color, centerX, centerY, brush, alpha = 255, blendFn) {
2548
- const target = writer.config.target;
2549
- const b = getCircleBrushOrPencilBounds2(centerX, centerY, brush.size, target.width, target.height, boundsOut);
2550
- const didChange = writer.accumulator.storeRegionBeforeState(b.x, b.y, b.w, b.h);
2551
- return didChange(blendColorPixelDataCircleMask2(target, color, centerX, centerY, brush, alpha, blendFn, blendColorPixelOptions, b));
2482
+ blendPixelDataBinaryMask(src, mask, opts = {}) {
2483
+ const x = opts.x ?? 0;
2484
+ const y = opts.y ?? 0;
2485
+ const w = opts.w ?? src.width;
2486
+ const h = opts.h ?? src.height;
2487
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2488
+ return didChange(blendPixelDataBinaryMask2(writer.config.target, src, mask, opts));
2552
2489
  }
2553
2490
  };
2554
2491
  });
2555
2492
 
2556
- // src/History/PixelMutator/mutatorApplyCirclePencil.ts
2557
- var defaults6 = {
2558
- applyCircleMaskToPixelData: blendColorPixelDataCircleMask,
2559
- getCircleBrushOrPencilBounds
2560
- };
2561
- var mutatorApplyCirclePencil = ((writer, deps = defaults6) => {
2493
+ // src/PixelData/fillPixelDataFast.ts
2494
+ var SCRATCH_RECT = makeClippedRect();
2495
+ function fillPixelDataFast(dst, color, _x, _y, _w, _h) {
2496
+ let x;
2497
+ let y;
2498
+ let w;
2499
+ let h;
2500
+ if (typeof _x === "object") {
2501
+ x = _x.x ?? 0;
2502
+ y = _x.y ?? 0;
2503
+ w = _x.w ?? dst.width;
2504
+ h = _x.h ?? dst.height;
2505
+ } else if (typeof _x === "number") {
2506
+ x = _x;
2507
+ y = _y;
2508
+ w = _w;
2509
+ h = _h;
2510
+ } else {
2511
+ x = 0;
2512
+ y = 0;
2513
+ w = dst.width;
2514
+ h = dst.height;
2515
+ }
2516
+ const clip = resolveRectClipping(x, y, w, h, dst.width, dst.height, SCRATCH_RECT);
2517
+ if (!clip.inBounds) return;
2562
2518
  const {
2563
- applyCircleMaskToPixelData = defaults6.applyCircleMaskToPixelData,
2564
- getCircleBrushOrPencilBounds: getCircleBrushOrPencilBounds2 = defaults6.getCircleBrushOrPencilBounds
2565
- } = deps;
2566
- const boundsOut = {
2567
- x: 0,
2568
- y: 0,
2569
- w: 0,
2570
- h: 0
2571
- };
2572
- return {
2573
- applyCirclePencil(color, centerX, centerY, brush, alpha = 255, blendFn) {
2574
- const target = writer.config.target;
2575
- const b = getCircleBrushOrPencilBounds2(centerX, centerY, brush.size, target.width, target.height, boundsOut);
2576
- const didChange = writer.accumulator.storeRegionBeforeState(b.x, b.y, b.w, b.h);
2577
- return didChange(applyCircleMaskToPixelData(target, color, centerX, centerY, brush, alpha, blendFn, b));
2578
- }
2579
- };
2580
- });
2519
+ x: finalX,
2520
+ y: finalY,
2521
+ w: actualW,
2522
+ h: actualH
2523
+ } = clip;
2524
+ const dst32 = dst.data32;
2525
+ const dw = dst.width;
2526
+ if (actualW === dw && actualH === dst.height && finalX === 0 && finalY === 0) {
2527
+ dst32.fill(color);
2528
+ return;
2529
+ }
2530
+ for (let iy = 0; iy < actualH; iy++) {
2531
+ const start = (finalY + iy) * dw + finalX;
2532
+ const end = start + actualW;
2533
+ dst32.fill(color, start, end);
2534
+ }
2535
+ }
2581
2536
 
2582
- // src/History/PixelMutator/mutatorApplyCirclePencilStroke.ts
2537
+ // src/History/PixelMutator/mutatorClear.ts
2583
2538
  var defaults7 = {
2584
- forEachLinePoint,
2585
- blendColorPixelDataBinaryMask,
2586
- getCircleBrushOrPencilBounds,
2587
- getCircleBrushOrPencilStrokeBounds
2539
+ fillPixelData: fillPixelDataFast
2588
2540
  };
2589
- var mutatorApplyCirclePencilStroke = ((writer, deps = defaults7) => {
2541
+ var mutatorClear = ((writer, deps = defaults7) => {
2590
2542
  const {
2591
- forEachLinePoint: forEachLinePoint2 = defaults7.forEachLinePoint,
2592
- blendColorPixelDataBinaryMask: blendColorPixelDataBinaryMask2 = defaults7.blendColorPixelDataBinaryMask,
2593
- getCircleBrushOrPencilStrokeBounds: getCircleBrushOrPencilStrokeBounds2 = defaults7.getCircleBrushOrPencilStrokeBounds,
2594
- getCircleBrushOrPencilBounds: getCircleBrushOrPencilBounds2 = defaults7.getCircleBrushOrPencilBounds
2543
+ fillPixelData: fillPixelData2 = defaults7.fillPixelData
2595
2544
  } = deps;
2596
- const strokeBoundsOut = {
2597
- x: 0,
2598
- y: 0,
2599
- w: 0,
2600
- h: 0
2601
- };
2602
- const circlePencilBounds = {
2603
- x: 0,
2604
- y: 0,
2605
- w: 0,
2606
- h: 0
2607
- };
2608
- const blendColorPixelOptions = {
2609
- alpha: 255,
2610
- blendFn: sourceOverPerfect,
2611
- x: 0,
2612
- y: 0,
2613
- w: 0,
2614
- h: 0
2615
- };
2616
- const mask = {
2617
- type: 1 /* BINARY */,
2618
- data: null,
2619
- w: 0,
2620
- h: 0
2621
- };
2622
2545
  return {
2623
- applyCirclePencilStroke(color, x0, y0, x1, y1, brush, alpha = 255, blendFn = sourceOverPerfect) {
2624
- const {
2625
- x: bx,
2626
- y: by,
2627
- w: bw,
2628
- h: bh
2629
- } = getCircleBrushOrPencilStrokeBounds2(x0, y0, x1, y1, brush.size, strokeBoundsOut);
2630
- if (bw <= 0 || bh <= 0) return;
2631
- mask.data = new Uint8Array(bw * bh);
2632
- mask.w = bw;
2633
- mask.h = bh;
2634
- const maskData = mask.data;
2546
+ clear(rect = {}) {
2635
2547
  const target = writer.config.target;
2636
- const targetWidth = target.width;
2637
- const targetHeight = target.height;
2638
- forEachLinePoint2(x0, y0, x1, y1, (px, py) => {
2639
- const {
2640
- x: cbx,
2641
- y: cby,
2642
- w: cbw,
2643
- h: cbh
2644
- } = getCircleBrushOrPencilBounds2(px, py, brush.size, targetWidth, targetHeight, circlePencilBounds);
2645
- writer.accumulator.storeRegionBeforeState(cbx, cby, cbw, cbh);
2646
- const unclippedStartX = Math.floor(px + brush.minOffset);
2647
- const unclippedStartY = Math.floor(py + brush.minOffset);
2648
- const startX = Math.max(bx, unclippedStartX);
2649
- const startY = Math.max(by, unclippedStartY);
2650
- const endX = Math.min(bx + bw, unclippedStartX + brush.w);
2651
- const endY = Math.min(by + bh, unclippedStartY + brush.h);
2652
- for (let my = startY; my < endY; my++) {
2653
- const brushY = my - unclippedStartY;
2654
- const maskRowOffset = (my - by) * bw;
2655
- const brushRowOffset = brushY * brush.w;
2656
- for (let mx = startX; mx < endX; mx++) {
2657
- const brushX = mx - unclippedStartX;
2658
- const brushAlpha = brush.data[brushRowOffset + brushX];
2659
- if (brushAlpha > 0) {
2660
- const maskIdx = maskRowOffset + (mx - bx);
2661
- maskData[maskIdx] = brushAlpha;
2662
- }
2663
- }
2664
- }
2665
- });
2666
- blendColorPixelOptions.blendFn = blendFn;
2667
- blendColorPixelOptions.alpha = alpha;
2668
- blendColorPixelOptions.x = bx;
2669
- blendColorPixelOptions.y = by;
2670
- blendColorPixelOptions.w = bw;
2671
- blendColorPixelOptions.h = bh;
2672
- blendColorPixelDataBinaryMask2(target, color, mask, blendColorPixelOptions);
2548
+ const x = rect.x ?? 0;
2549
+ const y = rect.y ?? 0;
2550
+ const w = rect.w ?? target.width;
2551
+ const h = rect.h ?? target.height;
2552
+ writer.accumulator.storeRegionBeforeState(x, y, w, h);
2553
+ fillPixelData2(target, 0, x, y, w, h);
2673
2554
  }
2674
2555
  };
2675
2556
  });
2676
2557
 
2677
- // src/Rect/getRectBrushOrPencilBounds.ts
2678
- function getRectBrushOrPencilBounds(centerX, centerY, brushWidth, brushHeight, targetWidth, targetHeight, out) {
2679
- const startX = Math.floor(centerX - brushWidth / 2);
2680
- const startY = Math.floor(centerY - brushHeight / 2);
2681
- const endX = startX + brushWidth;
2682
- const endY = startY + brushHeight;
2683
- const res = out ?? {
2684
- x: 0,
2685
- y: 0,
2686
- w: 0,
2687
- h: 0
2688
- };
2689
- const cStartX = Math.max(0, startX);
2690
- const cStartY = Math.max(0, startY);
2691
- const cEndX = Math.min(targetWidth, endX);
2692
- const cEndY = Math.min(targetHeight, endY);
2693
- const w = cEndX - cStartX;
2694
- const h = cEndY - cStartY;
2695
- res.x = cStartX;
2696
- res.y = cStartY;
2697
- res.w = w < 0 ? 0 : w;
2698
- res.h = h < 0 ? 0 : h;
2699
- return res;
2700
- }
2701
-
2702
- // src/PixelData/applyRectBrushToPixelData.ts
2703
- function applyRectBrushToPixelData(target, color, centerX, centerY, brushWidth, brushHeight, alpha = 255, fallOff, blendFn = sourceOverPerfect, bounds) {
2704
- const targetWidth = target.width;
2705
- const targetHeight = target.height;
2706
- const b = bounds ?? getRectBrushOrPencilBounds(centerX, centerY, brushWidth, brushHeight, targetWidth, targetHeight);
2707
- if (b.w <= 0 || b.h <= 0) {
2708
- return false;
2558
+ // src/PixelData/fillPixelData.ts
2559
+ var SCRATCH_RECT2 = makeClippedRect();
2560
+ function fillPixelData(dst, color, _x, _y, _w, _h) {
2561
+ let x;
2562
+ let y;
2563
+ let w;
2564
+ let h;
2565
+ if (typeof _x === "object") {
2566
+ x = _x.x ?? 0;
2567
+ y = _x.y ?? 0;
2568
+ w = _x.w ?? dst.width;
2569
+ h = _x.h ?? dst.height;
2570
+ } else if (typeof _x === "number") {
2571
+ x = _x;
2572
+ y = _y;
2573
+ w = _w;
2574
+ h = _h;
2575
+ } else {
2576
+ x = 0;
2577
+ y = 0;
2578
+ w = dst.width;
2579
+ h = dst.height;
2709
2580
  }
2710
- const data32 = target.data32;
2711
- const baseColor = color & 16777215;
2712
- const baseSrcAlpha = color >>> 24;
2713
- const isOpaque = alpha === 255;
2714
- const invHalfW = 1 / (brushWidth / 2);
2715
- const invHalfH = 1 / (brushHeight / 2);
2716
- const centerOffsetX = brushWidth % 2 === 0 ? 0.5 : 0;
2717
- const centerOffsetY = brushHeight % 2 === 0 ? 0.5 : 0;
2718
- const fCenterX = Math.floor(centerX);
2719
- const fCenterY = Math.floor(centerY);
2720
- const endX = b.x + b.w;
2721
- const endY = b.y + b.h;
2722
- const isOverwrite = blendFn.isOverwrite;
2723
- let didChange = false;
2724
- for (let py = b.y; py < endY; py++) {
2725
- const rowOffset = py * targetWidth;
2726
- const dy = Math.abs(py - fCenterY + centerOffsetY) * invHalfH;
2727
- for (let px = b.x; px < endX; px++) {
2728
- const idx = rowOffset + px;
2729
- const dx = Math.abs(px - fCenterX + centerOffsetX) * invHalfW;
2730
- const dist = dx > dy ? dx : dy;
2731
- const strength = fallOff(dist);
2732
- const maskVal = strength * 255 | 0;
2733
- if (maskVal <= 0) continue;
2734
- let weight = alpha;
2735
- if (isOpaque) {
2736
- weight = maskVal;
2737
- } else if (maskVal !== 255) {
2738
- weight = maskVal * alpha + 128 >> 8;
2739
- }
2740
- let finalCol = color;
2741
- if (weight < 255) {
2742
- const a = baseSrcAlpha * weight + 128 >> 8;
2743
- if (a === 0 && !isOverwrite) continue;
2744
- finalCol = (a << 24 | baseColor) >>> 0;
2745
- }
2746
- const current = data32[idx];
2747
- const next = blendFn(finalCol, current);
2748
- if (current !== next) {
2749
- data32[idx] = next;
2750
- didChange = true;
2581
+ const clip = resolveRectClipping(x, y, w, h, dst.width, dst.height, SCRATCH_RECT2);
2582
+ if (!clip.inBounds) return false;
2583
+ const {
2584
+ x: finalX,
2585
+ y: finalY,
2586
+ w: actualW,
2587
+ h: actualH
2588
+ } = clip;
2589
+ const dst32 = dst.data32;
2590
+ const dw = dst.width;
2591
+ let hasChanged = false;
2592
+ for (let iy = 0; iy < actualH; iy++) {
2593
+ const rowOffset = (finalY + iy) * dw;
2594
+ const start = rowOffset + finalX;
2595
+ const end = start + actualW;
2596
+ for (let i = start; i < end; i++) {
2597
+ if (dst32[i] !== color) {
2598
+ dst32[i] = color;
2599
+ hasChanged = true;
2751
2600
  }
2752
2601
  }
2753
2602
  }
2754
- return didChange;
2603
+ return hasChanged;
2755
2604
  }
2756
2605
 
2757
- // src/History/PixelMutator/mutatorApplyRectBrush.ts
2606
+ // src/History/PixelMutator/mutatorFill.ts
2758
2607
  var defaults8 = {
2759
- applyRectBrushToPixelData,
2760
- getRectBrushOrPencilBounds
2608
+ fillPixelData
2761
2609
  };
2762
- var mutatorApplyRectBrush = ((writer, deps = defaults8) => {
2610
+ var mutatorFill = ((writer, deps = defaults8) => {
2763
2611
  const {
2764
- applyRectBrushToPixelData: applyRectBrushToPixelData2 = defaults8.applyRectBrushToPixelData,
2765
- getRectBrushOrPencilBounds: getRectBrushOrPencilBounds2 = defaults8.getRectBrushOrPencilBounds
2612
+ fillPixelData: fillPixelData2 = defaults8.fillPixelData
2766
2613
  } = deps;
2767
- const boundsOut = {
2768
- x: 0,
2769
- y: 0,
2770
- w: 0,
2771
- h: 0
2772
- };
2773
2614
  return {
2774
- applyRectBrush(color, centerX, centerY, brushWidth, brushHeight, alpha = 255, fallOff, blendFn) {
2615
+ fill(color, x = 0, y = 0, w = writer.config.target.width, h = writer.config.target.height) {
2775
2616
  const target = writer.config.target;
2776
- const b = getRectBrushOrPencilBounds2(centerX, centerY, brushWidth, brushHeight, target.width, target.height, boundsOut);
2777
- const didChange = writer.accumulator.storeRegionBeforeState(b.x, b.y, b.w, b.h);
2778
- return didChange(applyRectBrushToPixelData2(target, color, centerX, centerY, brushWidth, brushHeight, alpha, fallOff, blendFn, b));
2617
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2618
+ return didChange(fillPixelData2(target, color, x, y, w, h));
2779
2619
  }
2780
2620
  };
2781
2621
  });
2782
-
2783
- // src/Rect/getRectBrushOrPencilStrokeBounds.ts
2784
- function getRectBrushOrPencilStrokeBounds(x0, y0, x1, y1, brushWidth, brushHeight, result) {
2785
- const halfW = brushWidth / 2;
2786
- const halfH = brushHeight / 2;
2787
- const minX = Math.min(x0, x1) - halfW;
2788
- const minY = Math.min(y0, y1) - halfH;
2789
- const maxX = Math.max(x0, x1) + halfW;
2790
- const maxY = Math.max(y0, y1) + halfH;
2791
- result.x = Math.floor(minX);
2792
- result.y = Math.floor(minY);
2793
- result.w = Math.ceil(maxX - minX);
2794
- result.h = Math.ceil(maxY - minY);
2795
- return result;
2796
- }
2797
-
2798
- // src/History/PixelMutator/mutatorApplyRectBrushStroke.ts
2799
- var defaults9 = {
2800
- forEachLinePoint,
2801
- blendColorPixelDataAlphaMask,
2802
- getRectBrushOrPencilBounds,
2803
- getRectBrushOrPencilStrokeBounds
2804
- };
2805
- var mutatorApplyRectBrushStroke = ((writer, deps = defaults9) => {
2622
+ var mutatorFillRect = ((writer, deps = defaults8) => {
2806
2623
  const {
2807
- forEachLinePoint: forEachLinePoint2 = defaults9.forEachLinePoint,
2808
- blendColorPixelDataAlphaMask: blendColorPixelDataAlphaMask2 = defaults9.blendColorPixelDataAlphaMask,
2809
- getRectBrushOrPencilBounds: getRectBrushOrPencilBounds2 = defaults9.getRectBrushOrPencilBounds,
2810
- getRectBrushOrPencilStrokeBounds: getRectBrushOrPencilStrokeBounds2 = defaults9.getRectBrushOrPencilStrokeBounds
2624
+ fillPixelData: fillPixelData2 = defaults8.fillPixelData
2811
2625
  } = deps;
2812
- const strokeBoundsOut = {
2813
- x: 0,
2814
- y: 0,
2815
- w: 0,
2816
- h: 0
2817
- };
2818
- const rectBrushBounds = {
2819
- x: 0,
2820
- y: 0,
2821
- w: 0,
2822
- h: 0
2823
- };
2824
- const blendColorPixelOptions = {
2825
- alpha: 255,
2826
- blendFn: sourceOverPerfect,
2827
- x: 0,
2828
- y: 0,
2829
- w: 0,
2830
- h: 0
2831
- };
2832
- const mask = {
2833
- type: 0 /* ALPHA */,
2834
- data: null,
2835
- w: 0,
2836
- h: 0
2837
- };
2838
2626
  return {
2839
- applyRectBrushStroke(color, x0, y0, x1, y1, brushWidth, brushHeight, alpha = 255, fallOff, blendFn = sourceOverPerfect) {
2840
- const {
2841
- x: bx,
2842
- y: by,
2843
- w: bw,
2844
- h: bh
2845
- } = getRectBrushOrPencilStrokeBounds2(x0, y0, x1, y1, brushWidth, brushHeight, strokeBoundsOut);
2846
- if (bw <= 0 || bh <= 0) return;
2847
- mask.data = new Uint8Array(bw * bh);
2848
- mask.w = bw;
2849
- mask.h = bh;
2850
- const maskData = mask.data;
2851
- const halfW = brushWidth / 2;
2852
- const halfH = brushHeight / 2;
2853
- const invHalfW = 1 / halfW;
2854
- const invHalfH = 1 / halfH;
2855
- const centerOffsetX = brushWidth % 2 === 0 ? 0.5 : 0;
2856
- const centerOffsetY = brushHeight % 2 === 0 ? 0.5 : 0;
2627
+ fillRect(color, rect) {
2857
2628
  const target = writer.config.target;
2858
- const targetWidth = target.width;
2859
- const targetHeight = target.height;
2860
- forEachLinePoint2(x0, y0, x1, y1, (px, py) => {
2861
- const {
2862
- x: rbx,
2863
- y: rby,
2864
- w: rbw,
2865
- h: rbh
2866
- } = getRectBrushOrPencilBounds2(px, py, brushWidth, brushHeight, targetWidth, targetHeight, rectBrushBounds);
2867
- writer.accumulator.storeRegionBeforeState(rbx, rby, rbw, rbh);
2868
- const startX = Math.max(bx, rbx);
2869
- const startY = Math.max(by, rby);
2870
- const endX = Math.min(bx + bw, rbx + rbw);
2871
- const endY = Math.min(by + bh, rby + rbh);
2872
- const fPx = Math.floor(px);
2873
- const fPy = Math.floor(py);
2874
- for (let my = startY; my < endY; my++) {
2875
- const dy = Math.abs(my - fPy + centerOffsetY) * invHalfH;
2876
- const maskRowOffset = (my - by) * bw;
2877
- for (let mx = startX; mx < endX; mx++) {
2878
- const dx = Math.abs(mx - fPx + centerOffsetX) * invHalfW;
2879
- const maskIdx = maskRowOffset + (mx - bx);
2880
- const dist = dx > dy ? dx : dy;
2881
- const strength = fallOff(dist);
2882
- if (strength > 0) {
2883
- const intensity = strength * 255 | 0;
2884
- if (intensity > maskData[maskIdx]) {
2885
- maskData[maskIdx] = intensity;
2886
- }
2887
- }
2888
- }
2889
- }
2890
- });
2891
- blendColorPixelOptions.blendFn = blendFn;
2892
- blendColorPixelOptions.alpha = alpha;
2893
- blendColorPixelOptions.x = bx;
2894
- blendColorPixelOptions.y = by;
2895
- blendColorPixelOptions.w = bw;
2896
- blendColorPixelOptions.h = bh;
2897
- blendColorPixelDataAlphaMask2(target, color, mask, blendColorPixelOptions);
2629
+ const didChange = writer.accumulator.storeRegionBeforeState(rect.x, rect.y, rect.w, rect.h);
2630
+ return didChange(fillPixelData2(target, color, rect.x, rect.y, rect.w, rect.h));
2898
2631
  }
2899
2632
  };
2900
2633
  });
2901
2634
 
2902
- // src/History/PixelMutator/mutatorApplyRectPencil.ts
2903
- var defaults10 = {
2904
- applyRectBrushToPixelData,
2905
- getRectBrushOrPencilBounds,
2906
- fallOff: () => 1
2907
- };
2908
- var mutatorApplyRectPencil = ((writer, deps = defaults10) => {
2635
+ // src/PixelData/fillPixelDataBinaryMask.ts
2636
+ var SCRATCH_RECT3 = makeClippedRect();
2637
+ function fillPixelDataBinaryMask(dst, color, mask, alpha = 255, x = 0, y = 0) {
2638
+ if (alpha === 0) return false;
2639
+ const maskW = mask.w;
2640
+ const maskH = mask.h;
2641
+ const clip = resolveRectClipping(x, y, maskW, maskH, dst.width, dst.height, SCRATCH_RECT3);
2642
+ if (!clip.inBounds) return false;
2909
2643
  const {
2910
- applyRectBrushToPixelData: applyRectBrushToPixelData2 = defaults10.applyRectBrushToPixelData,
2911
- getRectBrushOrPencilBounds: getRectBrushOrPencilBounds2 = defaults10.getRectBrushOrPencilBounds,
2912
- fallOff = defaults10.fallOff
2913
- } = deps;
2914
- const boundsOut = {
2915
- x: 0,
2916
- y: 0,
2917
- w: 0,
2918
- h: 0
2919
- };
2920
- return {
2921
- applyRectPencil(color, centerX, centerY, brushWidth, brushHeight, alpha = 255, blendFn) {
2922
- const target = writer.config.target;
2923
- const b = getRectBrushOrPencilBounds2(centerX, centerY, brushWidth, brushHeight, target.width, target.height, boundsOut);
2924
- const didChange = writer.accumulator.storeRegionBeforeState(b.x, b.y, b.w, b.h);
2925
- return didChange(applyRectBrushToPixelData2(target, color, centerX, centerY, brushWidth, brushHeight, alpha, fallOff, blendFn, b));
2926
- }
2927
- };
2928
- });
2929
-
2930
- // src/History/PixelMutator/mutatorApplyRectPencilStroke.ts
2931
- var defaults11 = {
2932
- forEachLinePoint,
2933
- getRectBrushOrPencilBounds,
2934
- getRectBrushOrPencilStrokeBounds,
2935
- blendColorPixelDataBinaryMask
2644
+ x: finalX,
2645
+ y: finalY,
2646
+ w: actualW,
2647
+ h: actualH
2648
+ } = clip;
2649
+ const maskData = mask.data;
2650
+ const dst32 = dst.data32;
2651
+ const dw = dst.width;
2652
+ let finalCol = color;
2653
+ if (alpha < 255) {
2654
+ const baseSrcAlpha = color >>> 24;
2655
+ const colorRGB = color & 16777215;
2656
+ const a = baseSrcAlpha * alpha + 128 >> 8;
2657
+ finalCol = (colorRGB | a << 24) >>> 0;
2658
+ }
2659
+ let hasChanged = false;
2660
+ for (let iy = 0; iy < actualH; iy++) {
2661
+ const currentY = finalY + iy;
2662
+ const maskY = currentY - y;
2663
+ const maskOffset = maskY * maskW;
2664
+ const dstRowOffset = currentY * dw;
2665
+ for (let ix = 0; ix < actualW; ix++) {
2666
+ const currentX = finalX + ix;
2667
+ const maskX = currentX - x;
2668
+ const maskIndex = maskOffset + maskX;
2669
+ if (maskData[maskIndex]) {
2670
+ const current = dst32[dstRowOffset + currentX];
2671
+ if (current !== finalCol) {
2672
+ dst32[dstRowOffset + currentX] = finalCol;
2673
+ hasChanged = true;
2674
+ }
2675
+ }
2676
+ }
2677
+ }
2678
+ return hasChanged;
2679
+ }
2680
+
2681
+ // src/History/PixelMutator/mutatorFillBinaryMask.ts
2682
+ var defaults9 = {
2683
+ fillPixelDataBinaryMask
2936
2684
  };
2937
- var mutatorApplyRectPencilStroke = ((writer, deps = defaults11) => {
2685
+ var mutatorFillBinaryMask = ((writer, deps = defaults9) => {
2938
2686
  const {
2939
- forEachLinePoint: forEachLinePoint2 = defaults11.forEachLinePoint,
2940
- blendColorPixelDataBinaryMask: blendColorPixelDataBinaryMask2 = defaults11.blendColorPixelDataBinaryMask,
2941
- getRectBrushOrPencilBounds: getRectBrushOrPencilBounds2 = defaults11.getRectBrushOrPencilBounds,
2942
- getRectBrushOrPencilStrokeBounds: getRectBrushOrPencilStrokeBounds2 = defaults11.getRectBrushOrPencilStrokeBounds
2687
+ fillPixelDataBinaryMask: fillPixelDataBinaryMask2 = defaults9.fillPixelDataBinaryMask
2943
2688
  } = deps;
2944
- const strokeBoundsOut = {
2945
- x: 0,
2946
- y: 0,
2947
- w: 0,
2948
- h: 0
2949
- };
2950
- const rectPencilBounds = {
2951
- x: 0,
2952
- y: 0,
2953
- w: 0,
2954
- h: 0
2955
- };
2956
- const blendColorPixelOptions = {
2957
- alpha: 255,
2958
- blendFn: sourceOverPerfect,
2959
- x: 0,
2960
- y: 0,
2961
- w: 0,
2962
- h: 0
2963
- };
2964
- const mask = {
2965
- type: 1 /* BINARY */,
2966
- data: null,
2967
- w: 0,
2968
- h: 0
2969
- };
2970
2689
  return {
2971
- applyRectPencilStroke(color, x0, y0, x1, y1, brushWidth, brushHeight, alpha = 255, blendFn = sourceOverPerfect) {
2972
- const {
2973
- x: bx,
2974
- y: by,
2975
- w: bw,
2976
- h: bh
2977
- } = getRectBrushOrPencilStrokeBounds2(x0, y0, x1, y1, brushWidth, brushHeight, strokeBoundsOut);
2978
- if (bw <= 0 || bh <= 0) return;
2979
- mask.data = new Uint8Array(bw * bh);
2980
- mask.w = bw;
2981
- mask.h = bh;
2982
- const maskData = mask.data;
2983
- const halfW = brushWidth / 2;
2984
- const halfH = brushHeight / 2;
2985
- const centerOffset = brushWidth % 2 === 0 ? 0.5 : 0;
2986
- const target = writer.config.target;
2987
- const targetWidth = target.width;
2988
- const targetHeight = target.height;
2989
- forEachLinePoint2(x0, y0, x1, y1, (px, py) => {
2990
- const {
2991
- x: rbx,
2992
- y: rby,
2993
- w: rbw,
2994
- h: rbh
2995
- } = getRectBrushOrPencilBounds2(px, py, brushWidth, brushHeight, targetWidth, targetHeight, rectPencilBounds);
2996
- writer.accumulator.storeRegionBeforeState(rbx, rby, rbw, rbh);
2997
- const startX = Math.max(bx, rbx);
2998
- const startY = Math.max(by, rby);
2999
- const endX = Math.min(bx + bw, rbx + rbw);
3000
- const endY = Math.min(by + bh, rby + rbh);
3001
- const fPx = Math.floor(px);
3002
- const fPy = Math.floor(py);
3003
- for (let my = startY; my < endY; my++) {
3004
- const dy = Math.abs(my - fPy + centerOffset);
3005
- const maskRowOffset = (my - by) * bw;
3006
- for (let mx = startX; mx < endX; mx++) {
3007
- const dx = Math.abs(mx - fPx + centerOffset);
3008
- const maskIdx = maskRowOffset + (mx - bx);
3009
- if (dx <= halfW && dy <= halfH) {
3010
- maskData[maskIdx] = 1;
3011
- }
3012
- }
3013
- }
3014
- });
3015
- blendColorPixelOptions.blendFn = blendFn;
3016
- blendColorPixelOptions.alpha = alpha;
3017
- blendColorPixelOptions.x = bx;
3018
- blendColorPixelOptions.y = by;
3019
- blendColorPixelOptions.w = bw;
3020
- blendColorPixelOptions.h = bh;
3021
- blendColorPixelDataBinaryMask2(target, color, mask, blendColorPixelOptions);
2690
+ fillBinaryMask(color, mask, alpha = 255, x = 0, y = 0) {
2691
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, mask.w, mask.h);
2692
+ return didChange(fillPixelDataBinaryMask2(writer.config.target, color, mask, alpha, x, y));
3022
2693
  }
3023
2694
  };
3024
2695
  });
3025
2696
 
3026
- // src/PixelData/blendColorPixelData.ts
3027
- function blendColorPixelData(dst, color, opts = {}) {
2697
+ // src/PixelData/invertPixelData.ts
2698
+ var SCRATCH_RECT4 = makeClippedRect();
2699
+ function invertPixelData(pixelData, opts = {}) {
2700
+ const dst = pixelData;
3028
2701
  const {
3029
2702
  x: targetX = 0,
3030
2703
  y: targetY = 0,
3031
- w: width = dst.width,
3032
- h: height = dst.height,
3033
- alpha: globalAlpha = 255,
3034
- blendFn = sourceOverPerfect
2704
+ w: width = pixelData.width,
2705
+ h: height = pixelData.height,
2706
+ mask,
2707
+ mx = 0,
2708
+ my = 0,
2709
+ invertMask = false
3035
2710
  } = opts;
3036
- if (globalAlpha === 0) return false;
3037
- const baseSrcAlpha = color >>> 24;
3038
- const isOverwrite = blendFn.isOverwrite || false;
3039
- if (baseSrcAlpha === 0 && !isOverwrite) return false;
3040
- let x = targetX;
3041
- let y = targetY;
3042
- let w = width;
3043
- let h = height;
3044
- if (x < 0) {
3045
- w += x;
3046
- x = 0;
3047
- }
3048
- if (y < 0) {
3049
- h += y;
3050
- y = 0;
3051
- }
3052
- const actualW = Math.min(w, dst.width - x);
3053
- const actualH = Math.min(h, dst.height - y);
3054
- if (actualW <= 0 || actualH <= 0) return false;
3055
- let finalSrcColor = color;
3056
- if (globalAlpha < 255) {
3057
- const a = baseSrcAlpha * globalAlpha + 128 >> 8;
3058
- if (a === 0 && !isOverwrite) return false;
3059
- finalSrcColor = (color & 16777215 | a << 24) >>> 0;
3060
- }
2711
+ const clip = resolveRectClipping(targetX, targetY, width, height, dst.width, dst.height, SCRATCH_RECT4);
2712
+ if (!clip.inBounds) return false;
2713
+ const {
2714
+ x,
2715
+ y,
2716
+ w: actualW,
2717
+ h: actualH
2718
+ } = clip;
3061
2719
  const dst32 = dst.data32;
3062
2720
  const dw = dst.width;
3063
- let dIdx = y * dw + x | 0;
3064
- const dStride = dw - actualW | 0;
3065
- let didChange = false;
3066
- for (let iy = 0; iy < actualH; iy++) {
3067
- for (let ix = 0; ix < actualW; ix++) {
3068
- const current = dst32[dIdx];
3069
- const next = blendFn(finalSrcColor, current);
3070
- if (current !== next) {
3071
- dst32[dIdx] = next;
3072
- didChange = true;
2721
+ const mPitch = mask?.w ?? width;
2722
+ const dx = x - targetX;
2723
+ const dy = y - targetY;
2724
+ let dIdx = y * dw + x;
2725
+ let mIdx = (my + dy) * mPitch + (mx + dx);
2726
+ const dStride = dw - actualW;
2727
+ const mStride = mPitch - actualW;
2728
+ if (mask) {
2729
+ const maskData = mask.data;
2730
+ for (let iy = 0; iy < actualH; iy++) {
2731
+ for (let ix = 0; ix < actualW; ix++) {
2732
+ const mVal = maskData[mIdx];
2733
+ const isHit = invertMask ? mVal === 0 : mVal === 1;
2734
+ if (isHit) {
2735
+ dst32[dIdx] = dst32[dIdx] ^ 16777215;
2736
+ }
2737
+ dIdx++;
2738
+ mIdx++;
3073
2739
  }
3074
- dIdx++;
2740
+ dIdx += dStride;
2741
+ mIdx += mStride;
2742
+ }
2743
+ } else {
2744
+ for (let iy = 0; iy < actualH; iy++) {
2745
+ for (let ix = 0; ix < actualW; ix++) {
2746
+ dst32[dIdx] = dst32[dIdx] ^ 16777215;
2747
+ dIdx++;
2748
+ }
2749
+ dIdx += dStride;
3075
2750
  }
3076
- dIdx += dStride;
3077
2751
  }
3078
- return didChange;
2752
+ return true;
3079
2753
  }
3080
2754
 
3081
- // src/History/PixelMutator/mutatorBlendColor.ts
3082
- var defaults12 = {
3083
- blendColorPixelData
2755
+ // src/History/PixelMutator/mutatorInvert.ts
2756
+ var defaults10 = {
2757
+ invertPixelData
3084
2758
  };
3085
- var mutatorBlendColor = ((writer, deps = defaults12) => {
2759
+ var mutatorInvert = ((writer, deps = defaults10) => {
3086
2760
  const {
3087
- blendColorPixelData: blendColorPixelData2 = defaults12.blendColorPixelData
2761
+ invertPixelData: invertPixelData2 = defaults10.invertPixelData
3088
2762
  } = deps;
3089
2763
  return {
3090
- blendColor(color, opts = {}) {
2764
+ invert(opts = {}) {
3091
2765
  const target = writer.config.target;
3092
2766
  const {
3093
2767
  x = 0,
@@ -3096,708 +2770,644 @@ var mutatorBlendColor = ((writer, deps = defaults12) => {
3096
2770
  h = target.height
3097
2771
  } = opts;
3098
2772
  const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3099
- return didChange(blendColorPixelData2(target, color, opts));
2773
+ return didChange(invertPixelData2(target, opts));
3100
2774
  }
3101
2775
  };
3102
2776
  });
3103
2777
 
3104
- // src/PixelData/blendPixel.ts
3105
- function blendPixel(target, x, y, color, alpha = 255, blendFn = sourceOverPerfect) {
3106
- if (alpha === 0) return false;
3107
- let width = target.width;
3108
- let height = target.height;
3109
- if (x < 0 || x >= width || y < 0 || y >= height) return false;
3110
- let srcAlpha = color >>> 24;
3111
- let isOverwrite = blendFn.isOverwrite;
3112
- if (srcAlpha === 0 && !isOverwrite) return false;
3113
- let dst32 = target.data32;
3114
- let index = y * width + x;
3115
- let finalColor = color;
3116
- if (alpha !== 255) {
3117
- let finalAlpha = srcAlpha * alpha + 128 >> 8;
3118
- if (finalAlpha === 0 && !isOverwrite) return false;
3119
- finalColor = (color & 16777215 | finalAlpha << 24) >>> 0;
2778
+ // src/History/PixelMutator.ts
2779
+ function makeFullPixelMutator(writer) {
2780
+ return {
2781
+ // @sort
2782
+ ...mutatorBlendColor(writer),
2783
+ ...mutatorBlendPixel(writer),
2784
+ ...mutatorBlendPixelData(writer),
2785
+ ...mutatorBlendPixelDataAlphaMask(writer),
2786
+ ...mutatorBlendPixelDataBinaryMask(writer),
2787
+ ...mutatorClear(writer),
2788
+ ...mutatorFill(writer),
2789
+ ...mutatorFillBinaryMask(writer),
2790
+ ...mutatorFillRect(writer),
2791
+ ...mutatorInvert(writer)
2792
+ };
2793
+ }
2794
+
2795
+ // src/ImageData/resizeImageData.ts
2796
+ function resizeImageData(target, newWidth, newHeight, offsetX = 0, offsetY = 0) {
2797
+ const result = new ImageData(newWidth, newHeight);
2798
+ const {
2799
+ width: oldW,
2800
+ height: oldH,
2801
+ data: oldData
2802
+ } = target;
2803
+ const newData = result.data;
2804
+ const x0 = Math.max(0, offsetX);
2805
+ const y0 = Math.max(0, offsetY);
2806
+ const x1 = Math.min(newWidth, offsetX + oldW);
2807
+ const y1 = Math.min(newHeight, offsetY + oldH);
2808
+ if (x1 <= x0 || y1 <= y0) {
2809
+ return result;
3120
2810
  }
3121
- let current = dst32[index];
3122
- let next = blendFn(finalColor, current);
3123
- if (current !== next) {
3124
- dst32[index] = next;
3125
- return true;
2811
+ const rowCount = y1 - y0;
2812
+ const rowLen = (x1 - x0) * 4;
2813
+ for (let row = 0; row < rowCount; row++) {
2814
+ const dstY = y0 + row;
2815
+ const srcY = dstY - offsetY;
2816
+ const srcX = x0 - offsetX;
2817
+ const dstStart = (dstY * newWidth + x0) * 4;
2818
+ const srcStart = (srcY * oldW + srcX) * 4;
2819
+ newData.set(oldData.subarray(srcStart, srcStart + rowLen), dstStart);
3126
2820
  }
3127
- return false;
2821
+ return result;
3128
2822
  }
3129
2823
 
3130
- // src/History/PixelMutator/mutatorBlendPixel.ts
3131
- var defaults13 = {
3132
- blendPixel
3133
- };
3134
- var mutatorBlendPixel = ((writer, deps = defaults13) => {
3135
- const {
3136
- blendPixel: blendPixel2 = defaults13.blendPixel
3137
- } = deps;
3138
- return {
3139
- blendPixel(x, y, color, alpha, blendFn) {
3140
- const didChange = writer.accumulator.storePixelBeforeState(x, y);
3141
- return didChange(blendPixel2(writer.config.target, x, y, color, alpha, blendFn));
3142
- }
2824
+ // src/Internal/helpers.ts
2825
+ var macro_halfAndFloor = (value) => value >> 1;
2826
+
2827
+ // src/Rect/trimRectBounds.ts
2828
+ function trimRectBounds(x, y, w, h, targetWidth, targetHeight, out) {
2829
+ const res = out ?? {
2830
+ x: 0,
2831
+ y: 0,
2832
+ w: 0,
2833
+ h: 0
3143
2834
  };
3144
- });
2835
+ const left = Math.max(0, x);
2836
+ const top = Math.max(0, y);
2837
+ const right = Math.min(targetWidth, x + w);
2838
+ const bottom = Math.min(targetHeight, y + h);
2839
+ res.x = left;
2840
+ res.y = top;
2841
+ res.w = Math.max(0, right - left);
2842
+ res.h = Math.max(0, bottom - top);
2843
+ return res;
2844
+ }
3145
2845
 
3146
- // src/PixelData/blendPixelData.ts
3147
- function blendPixelData(dst, src, opts = {}) {
3148
- const {
3149
- x: targetX = 0,
3150
- y: targetY = 0,
3151
- sx: sourceX = 0,
3152
- sy: sourceY = 0,
3153
- w: width = src.width,
3154
- h: height = src.height,
3155
- alpha: globalAlpha = 255,
3156
- blendFn = sourceOverPerfect
3157
- } = opts;
3158
- if (globalAlpha === 0) return false;
3159
- let x = targetX;
3160
- let y = targetY;
3161
- let sx = sourceX;
3162
- let sy = sourceY;
3163
- let w = width;
3164
- let h = height;
3165
- if (sx < 0) {
3166
- x -= sx;
3167
- w += sx;
3168
- sx = 0;
3169
- }
3170
- if (sy < 0) {
3171
- y -= sy;
3172
- h += sy;
3173
- sy = 0;
3174
- }
3175
- w = Math.min(w, src.width - sx);
3176
- h = Math.min(h, src.height - sy);
3177
- if (x < 0) {
3178
- sx -= x;
3179
- w += x;
3180
- x = 0;
3181
- }
3182
- if (y < 0) {
3183
- sy -= y;
3184
- h += y;
3185
- y = 0;
3186
- }
3187
- const actualW = Math.min(w, dst.width - x);
3188
- const actualH = Math.min(h, dst.height - y);
3189
- if (actualW <= 0 || actualH <= 0) return false;
3190
- const dst32 = dst.data32;
3191
- const src32 = src.data32;
3192
- const dw = dst.width;
3193
- const sw = src.width;
3194
- let dIdx = y * dw + x | 0;
3195
- let sIdx = sy * sw + sx | 0;
3196
- const dStride = dw - actualW | 0;
3197
- const sStride = sw - actualW | 0;
3198
- const isOpaque = globalAlpha === 255;
3199
- const isOverwrite = blendFn.isOverwrite;
3200
- let didChange = false;
3201
- for (let iy = 0; iy < actualH; iy++) {
3202
- for (let ix = 0; ix < actualW; ix++) {
3203
- const srcCol = src32[sIdx];
3204
- const srcAlpha = srcCol >>> 24;
3205
- if (srcAlpha === 0 && !isOverwrite) {
3206
- dIdx++;
3207
- sIdx++;
3208
- continue;
3209
- }
3210
- let finalCol = srcCol;
3211
- if (!isOpaque) {
3212
- const a = srcAlpha * globalAlpha + 128 >> 8;
3213
- if (a === 0 && !isOverwrite) {
3214
- dIdx++;
3215
- sIdx++;
3216
- continue;
3217
- }
3218
- finalCol = (srcCol & 16777215 | a << 24) >>> 0;
3219
- }
3220
- const current = dst32[dIdx];
3221
- const next = blendFn(finalCol, dst32[dIdx]);
3222
- if (current !== next) {
3223
- dst32[dIdx] = next;
3224
- didChange = true;
3225
- }
3226
- dIdx++;
3227
- sIdx++;
3228
- }
3229
- dIdx += dStride;
3230
- sIdx += sStride;
3231
- }
3232
- return didChange;
3233
- }
3234
-
3235
- // src/History/PixelMutator/mutatorBlendPixelData.ts
3236
- var defaults14 = {
3237
- blendPixelData
3238
- };
3239
- var mutatorBlendPixelData = ((writer, deps = defaults14) => {
3240
- const {
3241
- blendPixelData: blendPixelData2 = defaults14.blendPixelData
3242
- } = deps;
3243
- return {
3244
- blendPixelData(src, opts = {}) {
3245
- const {
3246
- x = 0,
3247
- y = 0,
3248
- w = src.width,
3249
- h = src.height
3250
- } = opts;
3251
- const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3252
- return didChange(blendPixelData2(writer.config.target, src, opts));
3253
- }
3254
- };
3255
- });
3256
-
3257
- // src/PixelData/blendPixelDataAlphaMask.ts
3258
- function blendPixelDataAlphaMask(dst, src, alphaMask, opts = {}) {
3259
- const {
3260
- x: targetX = 0,
3261
- y: targetY = 0,
3262
- sx: sourceX = 0,
3263
- sy: sourceY = 0,
3264
- w: width = src.width,
3265
- h: height = src.height,
3266
- alpha: globalAlpha = 255,
3267
- blendFn = sourceOverPerfect,
3268
- mx = 0,
3269
- my = 0,
3270
- invertMask = false
3271
- } = opts;
3272
- if (globalAlpha === 0) return false;
3273
- let x = targetX;
3274
- let y = targetY;
3275
- let sx = sourceX;
3276
- let sy = sourceY;
3277
- let w = width;
3278
- let h = height;
3279
- if (sx < 0) {
3280
- x -= sx;
3281
- w += sx;
3282
- sx = 0;
3283
- }
3284
- if (sy < 0) {
3285
- y -= sy;
3286
- h += sy;
3287
- sy = 0;
3288
- }
3289
- w = Math.min(w, src.width - sx);
3290
- h = Math.min(h, src.height - sy);
3291
- if (x < 0) {
3292
- sx -= x;
3293
- w += x;
3294
- x = 0;
3295
- }
3296
- if (y < 0) {
3297
- sy -= y;
3298
- h += y;
3299
- y = 0;
3300
- }
3301
- const actualW = Math.min(w, dst.width - x);
3302
- const actualH = Math.min(h, dst.height - y);
3303
- if (actualW <= 0 || actualH <= 0) return false;
3304
- const dw = dst.width;
3305
- const sw = src.width;
3306
- const mPitch = alphaMask.w;
3307
- const maskData = alphaMask.data;
3308
- const dx = x - targetX | 0;
3309
- const dy = y - targetY | 0;
3310
- const dst32 = dst.data32;
3311
- const src32 = src.data32;
3312
- let dIdx = y * dw + x | 0;
3313
- let sIdx = sy * sw + sx | 0;
3314
- let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
3315
- const dStride = dw - actualW | 0;
3316
- const sStride = sw - actualW | 0;
3317
- const mStride = mPitch - actualW | 0;
3318
- const isOpaque = globalAlpha === 255;
3319
- const isOverwrite = blendFn.isOverwrite || false;
3320
- let didChange = false;
3321
- for (let iy = 0; iy < actualH; iy++) {
3322
- for (let ix = 0; ix < actualW; ix++) {
3323
- const mVal = maskData[mIdx];
3324
- const effM = invertMask ? 255 - mVal : mVal;
3325
- if (effM === 0) {
3326
- dIdx++;
3327
- sIdx++;
3328
- mIdx++;
3329
- continue;
3330
- }
3331
- const srcCol = src32[sIdx];
3332
- const srcAlpha = srcCol >>> 24;
3333
- if (srcAlpha === 0 && !isOverwrite) {
3334
- dIdx++;
3335
- sIdx++;
3336
- mIdx++;
3337
- continue;
3338
- }
3339
- let weight = globalAlpha;
3340
- if (isOpaque) {
3341
- weight = effM;
3342
- } else if (effM !== 255) {
3343
- weight = effM * globalAlpha + 128 >> 8;
3344
- }
3345
- if (weight === 0) {
3346
- dIdx++;
3347
- sIdx++;
3348
- mIdx++;
3349
- continue;
3350
- }
3351
- let finalCol = srcCol;
3352
- if (weight < 255) {
3353
- const a = srcAlpha * weight + 128 >> 8;
3354
- if (a === 0 && !isOverwrite) {
3355
- dIdx++;
3356
- sIdx++;
3357
- mIdx++;
3358
- continue;
3359
- }
3360
- finalCol = (srcCol & 16777215 | a << 24) >>> 0;
3361
- }
3362
- const current = dst32[dIdx];
3363
- const next = blendFn(finalCol, dst32[dIdx]);
3364
- if (current !== next) {
3365
- dst32[dIdx] = next;
3366
- didChange = true;
3367
- }
3368
- dIdx++;
3369
- sIdx++;
3370
- mIdx++;
3371
- }
3372
- dIdx += dStride;
3373
- sIdx += sStride;
3374
- mIdx += mStride;
3375
- }
3376
- return didChange;
3377
- }
3378
-
3379
- // src/History/PixelMutator/mutatorBlendPixelDataAlphaMask.ts
3380
- var defaults15 = {
3381
- blendPixelDataAlphaMask
3382
- };
3383
- var mutatorBlendPixelDataAlphaMask = ((writer, deps = defaults15) => {
3384
- const {
3385
- blendPixelDataAlphaMask: blendPixelDataAlphaMask2 = defaults15.blendPixelDataAlphaMask
3386
- } = deps;
3387
- return {
3388
- blendPixelDataAlphaMask(src, mask, opts = {}) {
3389
- const x = opts.x ?? 0;
3390
- const y = opts.y ?? 0;
3391
- const w = opts.w ?? src.width;
3392
- const h = opts.h ?? src.height;
3393
- const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3394
- return didChange(blendPixelDataAlphaMask2(writer.config.target, src, mask, opts));
3395
- }
3396
- };
3397
- });
3398
-
3399
- // src/PixelData/blendPixelDataBinaryMask.ts
3400
- function blendPixelDataBinaryMask(dst, src, binaryMask, opts = {}) {
3401
- const {
3402
- x: targetX = 0,
3403
- y: targetY = 0,
3404
- sx: sourceX = 0,
3405
- sy: sourceY = 0,
3406
- w: width = src.width,
3407
- h: height = src.height,
3408
- alpha: globalAlpha = 255,
3409
- blendFn = sourceOverPerfect,
3410
- mx = 0,
3411
- my = 0,
3412
- invertMask = false
3413
- } = opts;
3414
- if (globalAlpha === 0) return false;
3415
- let x = targetX;
3416
- let y = targetY;
3417
- let sx = sourceX;
3418
- let sy = sourceY;
3419
- let w = width;
3420
- let h = height;
3421
- if (sx < 0) {
3422
- x -= sx;
3423
- w += sx;
3424
- sx = 0;
3425
- }
3426
- if (sy < 0) {
3427
- y -= sy;
3428
- h += sy;
3429
- sy = 0;
3430
- }
3431
- w = Math.min(w, src.width - sx);
3432
- h = Math.min(h, src.height - sy);
3433
- if (x < 0) {
3434
- sx -= x;
3435
- w += x;
3436
- x = 0;
3437
- }
3438
- if (y < 0) {
3439
- sy -= y;
3440
- h += y;
3441
- y = 0;
2846
+ // src/Paint/PaintBuffer.ts
2847
+ var PaintBuffer = class {
2848
+ constructor(config, tilePool) {
2849
+ this.config = config;
2850
+ this.tilePool = tilePool;
2851
+ this.lookup = [];
3442
2852
  }
3443
- const actualW = Math.min(w, dst.width - x);
3444
- const actualH = Math.min(h, dst.height - y);
3445
- if (actualW <= 0 || actualH <= 0) return false;
3446
- const dx = x - targetX | 0;
3447
- const dy = y - targetY | 0;
3448
- const dst32 = dst.data32;
3449
- const src32 = src.data32;
3450
- const dw = dst.width;
3451
- const sw = src.width;
3452
- const mPitch = binaryMask.w;
3453
- const maskData = binaryMask.data;
3454
- let dIdx = y * dw + x | 0;
3455
- let sIdx = sy * sw + sx | 0;
3456
- let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
3457
- const dStride = dw - actualW | 0;
3458
- const sStride = sw - actualW | 0;
3459
- const mStride = mPitch - actualW | 0;
3460
- const skipVal = invertMask ? 1 : 0;
3461
- const isOpaque = globalAlpha === 255;
3462
- const isOverwrite = blendFn.isOverwrite || false;
3463
- let didChange = false;
3464
- for (let iy = 0; iy < actualH; iy++) {
3465
- for (let ix = 0; ix < actualW; ix++) {
3466
- if (maskData[mIdx] === skipVal) {
3467
- dIdx++;
3468
- sIdx++;
3469
- mIdx++;
3470
- continue;
3471
- }
3472
- const srcCol = src32[sIdx];
3473
- const srcAlpha = srcCol >>> 24;
3474
- if (srcAlpha === 0 && !isOverwrite) {
3475
- dIdx++;
3476
- sIdx++;
3477
- mIdx++;
3478
- continue;
3479
- }
3480
- let finalCol = srcCol;
3481
- if (!isOpaque) {
3482
- const a = srcAlpha * globalAlpha + 128 >> 8;
3483
- if (a === 0 && !isOverwrite) {
3484
- dIdx++;
3485
- sIdx++;
3486
- mIdx++;
3487
- continue;
3488
- }
3489
- finalCol = (srcCol & 16777215 | a << 24) >>> 0;
3490
- }
3491
- const current = dst32[dIdx];
3492
- const next = blendFn(finalCol, dst32[dIdx]);
3493
- if (current !== next) {
3494
- dst32[dIdx] = next;
3495
- didChange = true;
2853
+ lookup;
2854
+ scratchBounds = {
2855
+ x: 0,
2856
+ y: 0,
2857
+ w: 0,
2858
+ h: 0
2859
+ };
2860
+ eachTileInBounds(bounds, callback) {
2861
+ const {
2862
+ tileShift,
2863
+ targetColumns,
2864
+ targetRows,
2865
+ tileSize
2866
+ } = this.config;
2867
+ const x1 = Math.max(0, bounds.x >> tileShift);
2868
+ const y1 = Math.max(0, bounds.y >> tileShift);
2869
+ const x2 = Math.min(targetColumns - 1, bounds.x + bounds.w - 1 >> tileShift);
2870
+ const y2 = Math.min(targetRows - 1, bounds.y + bounds.h - 1 >> tileShift);
2871
+ if (x1 > x2 || y1 > y2) return;
2872
+ const lookup = this.lookup;
2873
+ const tilePool = this.tilePool;
2874
+ for (let ty = y1; ty <= y2; ty++) {
2875
+ const rowOffset = ty * targetColumns;
2876
+ const tileTop = ty << tileShift;
2877
+ for (let tx = x1; tx <= x2; tx++) {
2878
+ const id = rowOffset + tx;
2879
+ const tile = lookup[id] ?? (lookup[id] = tilePool.getTile(id, tx, ty));
2880
+ const tileLeft = tx << tileShift;
2881
+ const startX = bounds.x > tileLeft ? bounds.x : tileLeft;
2882
+ const startY = bounds.y > tileTop ? bounds.y : tileTop;
2883
+ const maskEndX = bounds.x + bounds.w;
2884
+ const tileEndX = tileLeft + tileSize;
2885
+ const endX = maskEndX < tileEndX ? maskEndX : tileEndX;
2886
+ const maskEndY = bounds.y + bounds.h;
2887
+ const tileEndY = tileTop + tileSize;
2888
+ const endY = maskEndY < tileEndY ? maskEndY : tileEndY;
2889
+ callback(tile, startX, startY, endX - startX, endY - startY);
3496
2890
  }
3497
- dIdx++;
3498
- sIdx++;
3499
- mIdx++;
3500
2891
  }
3501
- dIdx += dStride;
3502
- sIdx += sStride;
3503
- mIdx += mStride;
3504
2892
  }
3505
- return didChange;
3506
- }
3507
-
3508
- // src/History/PixelMutator/mutatorBlendPixelDataBinaryMask.ts
3509
- var defaults16 = {
3510
- blendPixelDataBinaryMask
3511
- };
3512
- var mutatorBlendPixelDataBinaryMask = ((writer, deps = defaults16) => {
3513
- const {
3514
- blendPixelDataBinaryMask: blendPixelDataBinaryMask2 = defaults16.blendPixelDataBinaryMask
3515
- } = deps;
3516
- return {
3517
- blendPixelDataBinaryMask(src, mask, opts = {}) {
3518
- const x = opts.x ?? 0;
3519
- const y = opts.y ?? 0;
3520
- const w = opts.w ?? src.width;
3521
- const h = opts.h ?? src.height;
3522
- const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3523
- return didChange(blendPixelDataBinaryMask2(writer.config.target, src, mask, opts));
3524
- }
3525
- };
3526
- });
3527
-
3528
- // src/PixelData/fillPixelDataFast.ts
3529
- var SCRATCH_RECT = makeClippedRect();
3530
- function fillPixelDataFast(dst, color, _x, _y, _w, _h) {
3531
- let x;
3532
- let y;
3533
- let w;
3534
- let h;
3535
- if (typeof _x === "object") {
3536
- x = _x.x ?? 0;
3537
- y = _x.y ?? 0;
3538
- w = _x.w ?? dst.width;
3539
- h = _x.h ?? dst.height;
3540
- } else if (typeof _x === "number") {
3541
- x = _x;
3542
- y = _y;
3543
- w = _w;
3544
- h = _h;
3545
- } else {
3546
- x = 0;
3547
- y = 0;
3548
- w = dst.width;
3549
- h = dst.height;
2893
+ writePaintAlphaMaskStroke(color, brush, x0, y0, x1, y1) {
2894
+ const cA = color >>> 24;
2895
+ if (cA === 0) return false;
2896
+ const {
2897
+ tileShift,
2898
+ tileMask,
2899
+ target
2900
+ } = this.config;
2901
+ const {
2902
+ w: bW,
2903
+ h: bH,
2904
+ data: bD,
2905
+ centerOffsetX,
2906
+ centerOffsetY
2907
+ } = brush;
2908
+ const cRGB = color & 16777215;
2909
+ const scratch = this.scratchBounds;
2910
+ let changed = false;
2911
+ forEachLinePoint(x0, y0, x1, y1, (px, py) => {
2912
+ const topLeftX = Math.floor(px + centerOffsetX);
2913
+ const topLeftY = Math.floor(py + centerOffsetY);
2914
+ trimRectBounds(topLeftX, topLeftY, bW, bH, target.width, target.height, scratch);
2915
+ if (scratch.w <= 0 || scratch.h <= 0) return;
2916
+ this.eachTileInBounds(scratch, (tile, bX, bY, bW_t, bH_t) => {
2917
+ const d32 = tile.data32;
2918
+ let tileChanged = false;
2919
+ for (let i = 0; i < bH_t; i++) {
2920
+ const canvasY = bY + i;
2921
+ const bOff = (canvasY - topLeftY) * bW;
2922
+ const tOff = (canvasY & tileMask) << tileShift;
2923
+ const dS = tOff + (bX & tileMask);
2924
+ for (let j = 0; j < bW_t; j++) {
2925
+ const canvasX = bX + j;
2926
+ const brushA = bD[bOff + (canvasX - topLeftX)];
2927
+ if (brushA === 0) continue;
2928
+ const t = cA * brushA + 128;
2929
+ const blendedA = t + (t >> 8) >> 8;
2930
+ const idx = dS + j;
2931
+ const cur = d32[idx];
2932
+ if (brushA > cur >>> 24) {
2933
+ const next = (cRGB | blendedA << 24) >>> 0;
2934
+ if (cur !== next) {
2935
+ d32[idx] = next;
2936
+ tileChanged = true;
2937
+ }
2938
+ }
2939
+ }
2940
+ }
2941
+ if (tileChanged) changed = true;
2942
+ });
2943
+ });
2944
+ return changed;
3550
2945
  }
3551
- const clip = resolveRectClipping(x, y, w, h, dst.width, dst.height, SCRATCH_RECT);
3552
- if (!clip.inBounds) return;
3553
- const {
3554
- x: finalX,
3555
- y: finalY,
3556
- w: actualW,
3557
- h: actualH
3558
- } = clip;
3559
- const dst32 = dst.data32;
3560
- const dw = dst.width;
3561
- if (actualW === dw && actualH === dst.height && finalX === 0 && finalY === 0) {
3562
- dst32.fill(color);
3563
- return;
2946
+ writePaintBinaryMaskStroke(color, brush, x0, y0, x1, y1) {
2947
+ const alphaIsZero = color >>> 24 === 0;
2948
+ if (alphaIsZero) return false;
2949
+ const {
2950
+ tileShift,
2951
+ tileMask,
2952
+ target
2953
+ } = this.config;
2954
+ const {
2955
+ w: bW,
2956
+ h: bH,
2957
+ data: bD,
2958
+ centerOffsetX,
2959
+ centerOffsetY
2960
+ } = brush;
2961
+ const scratch = this.scratchBounds;
2962
+ let changed = false;
2963
+ forEachLinePoint(x0, y0, x1, y1, (px, py) => {
2964
+ const topLeftX = Math.floor(px + centerOffsetX);
2965
+ const topLeftY = Math.floor(py + centerOffsetY);
2966
+ trimRectBounds(topLeftX, topLeftY, bW, bH, target.width, target.height, scratch);
2967
+ if (scratch.w <= 0 || scratch.h <= 0) return;
2968
+ this.eachTileInBounds(scratch, (tile, bX, bY, bW_t, bH_t) => {
2969
+ const d32 = tile.data32;
2970
+ let tileChanged = false;
2971
+ for (let i = 0; i < bH_t; i++) {
2972
+ const canvasY = bY + i;
2973
+ const bOff = (canvasY - topLeftY) * bW;
2974
+ const tOff = (canvasY & tileMask) << tileShift;
2975
+ const dS = tOff + (bX & tileMask);
2976
+ for (let j = 0; j < bW_t; j++) {
2977
+ const canvasX = bX + j;
2978
+ if (bD[bOff + (canvasX - topLeftX)]) {
2979
+ const idx = dS + j;
2980
+ if (d32[idx] !== color) {
2981
+ d32[idx] = color;
2982
+ tileChanged = true;
2983
+ }
2984
+ }
2985
+ }
2986
+ }
2987
+ if (tileChanged) changed = true;
2988
+ });
2989
+ });
2990
+ return changed;
2991
+ }
2992
+ writeRectStroke(color, brushWidth, brushHeight, x0, y0, x1, y1) {
2993
+ const alphaIsZero = color >>> 24 === 0;
2994
+ if (alphaIsZero) return false;
2995
+ const config = this.config;
2996
+ const tileShift = config.tileShift;
2997
+ const tileMask = config.tileMask;
2998
+ const target = config.target;
2999
+ const scratch = this.scratchBounds;
3000
+ const centerOffsetX = macro_halfAndFloor(brushWidth - 1);
3001
+ const centerOffsetY = macro_halfAndFloor(brushHeight - 1);
3002
+ let changed = false;
3003
+ forEachLinePoint(x0, y0, x1, y1, (px, py) => {
3004
+ const topLeftX = Math.floor(px + centerOffsetX);
3005
+ const topLeftY = Math.floor(py + centerOffsetY);
3006
+ trimRectBounds(topLeftX, topLeftY, brushWidth, brushHeight, target.width, target.height, scratch);
3007
+ if (scratch.w <= 0 || scratch.h <= 0) return;
3008
+ this.eachTileInBounds(scratch, (tile, bX, bY, bW_t, bH_t) => {
3009
+ const d32 = tile.data32;
3010
+ let tileChanged = false;
3011
+ for (let i = 0; i < bH_t; i++) {
3012
+ const canvasY = bY + i;
3013
+ const tOff = (canvasY & tileMask) << tileShift;
3014
+ const dS = tOff + (bX & tileMask);
3015
+ for (let j = 0; j < bW_t; j++) {
3016
+ const idx = dS + j;
3017
+ if (d32[idx] !== color) {
3018
+ d32[idx] = color;
3019
+ tileChanged = true;
3020
+ }
3021
+ }
3022
+ }
3023
+ if (tileChanged) {
3024
+ changed = true;
3025
+ }
3026
+ });
3027
+ });
3028
+ return changed;
3564
3029
  }
3565
- for (let iy = 0; iy < actualH; iy++) {
3566
- const start = (finalY + iy) * dw + finalX;
3567
- const end = start + actualW;
3568
- dst32.fill(color, start, end);
3030
+ clear() {
3031
+ this.tilePool.releaseTiles(this.lookup);
3569
3032
  }
3570
- }
3033
+ };
3571
3034
 
3572
- // src/History/PixelMutator/mutatorClear.ts
3573
- var defaults17 = {
3574
- fillPixelData: fillPixelDataFast
3035
+ // src/PixelTile/PixelTile.ts
3036
+ var PixelTile = class {
3037
+ constructor(id, tx, ty, tileSize, tileArea) {
3038
+ this.id = id;
3039
+ this.tx = tx;
3040
+ this.ty = ty;
3041
+ this.width = this.height = tileSize;
3042
+ this.data32 = new Uint32Array(tileArea);
3043
+ const data8 = new Uint8ClampedArray(this.data32.buffer);
3044
+ this.imageData = new ImageData(data8, tileSize, tileSize);
3045
+ }
3046
+ data32;
3047
+ width;
3048
+ height;
3049
+ imageData;
3575
3050
  };
3576
- var mutatorClear = ((writer, deps = defaults17) => {
3577
- const {
3578
- fillPixelData: fillPixelData2 = defaults17.fillPixelData
3579
- } = deps;
3580
- return {
3581
- clear(rect = {}) {
3582
- const target = writer.config.target;
3583
- const x = rect.x ?? 0;
3584
- const y = rect.y ?? 0;
3585
- const w = rect.w ?? target.width;
3586
- const h = rect.h ?? target.height;
3587
- writer.accumulator.storeRegionBeforeState(x, y, w, h);
3588
- fillPixelData2(target, 0, x, y, w, h);
3589
- }
3590
- };
3591
- });
3592
3051
 
3593
- // src/PixelData/fillPixelData.ts
3594
- var SCRATCH_RECT2 = makeClippedRect();
3595
- function fillPixelData(dst, color, _x, _y, _w, _h) {
3596
- let x;
3597
- let y;
3598
- let w;
3599
- let h;
3600
- if (typeof _x === "object") {
3601
- x = _x.x ?? 0;
3602
- y = _x.y ?? 0;
3603
- w = _x.w ?? dst.width;
3604
- h = _x.h ?? dst.height;
3605
- } else if (typeof _x === "number") {
3606
- x = _x;
3607
- y = _y;
3608
- w = _w;
3609
- h = _h;
3610
- } else {
3611
- x = 0;
3612
- y = 0;
3613
- w = dst.width;
3614
- h = dst.height;
3052
+ // src/PixelTile/PixelTilePool.ts
3053
+ var PixelTilePool = class {
3054
+ pool;
3055
+ tileSize;
3056
+ tileArea;
3057
+ constructor(config) {
3058
+ this.pool = [];
3059
+ this.tileSize = config.tileSize;
3060
+ this.tileArea = config.tileArea;
3615
3061
  }
3616
- const clip = resolveRectClipping(x, y, w, h, dst.width, dst.height, SCRATCH_RECT2);
3617
- if (!clip.inBounds) return false;
3618
- const {
3619
- x: finalX,
3620
- y: finalY,
3621
- w: actualW,
3622
- h: actualH
3623
- } = clip;
3624
- const dst32 = dst.data32;
3625
- const dw = dst.width;
3626
- let hasChanged = false;
3627
- for (let iy = 0; iy < actualH; iy++) {
3628
- const rowOffset = (finalY + iy) * dw;
3629
- const start = rowOffset + finalX;
3630
- const end = start + actualW;
3631
- for (let i = start; i < end; i++) {
3632
- if (dst32[i] !== color) {
3633
- dst32[i] = color;
3634
- hasChanged = true;
3062
+ getTile(id, tx, ty) {
3063
+ let tile = this.pool.pop();
3064
+ if (tile) {
3065
+ tile.id = id;
3066
+ tile.tx = tx;
3067
+ tile.ty = ty;
3068
+ tile.data32.fill(0);
3069
+ return tile;
3070
+ }
3071
+ return new PixelTile(id, tx, ty, this.tileSize, this.tileArea);
3072
+ }
3073
+ releaseTile(tile) {
3074
+ this.pool.push(tile);
3075
+ }
3076
+ releaseTiles(tiles) {
3077
+ let length = tiles.length;
3078
+ for (let i = 0; i < length; i++) {
3079
+ let tile = tiles[i];
3080
+ if (tile) {
3081
+ this.pool.push(tile);
3635
3082
  }
3636
3083
  }
3084
+ tiles.length = 0;
3637
3085
  }
3638
- return hasChanged;
3639
- }
3640
-
3641
- // src/History/PixelMutator/mutatorFill.ts
3642
- var defaults18 = {
3643
- fillPixelData
3644
3086
  };
3645
- var mutatorFill = ((writer, deps = defaults18) => {
3646
- const {
3647
- fillPixelData: fillPixelData2 = defaults18.fillPixelData
3648
- } = deps;
3649
- return {
3650
- fill(color, x = 0, y = 0, w = writer.config.target.width, h = writer.config.target.height) {
3651
- const target = writer.config.target;
3652
- const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3653
- return didChange(fillPixelData2(target, color, x, y, w, h));
3654
- }
3087
+
3088
+ // src/History/PixelWriter.ts
3089
+ var PixelWriter = class {
3090
+ historyManager;
3091
+ accumulator;
3092
+ historyActionFactory;
3093
+ config;
3094
+ pixelTilePool;
3095
+ paintBuffer;
3096
+ mutator;
3097
+ blendPixelDataOpts = {
3098
+ alpha: 255,
3099
+ blendFn: sourceOverPerfect,
3100
+ x: 0,
3101
+ y: 0,
3102
+ w: 0,
3103
+ h: 0
3655
3104
  };
3656
- });
3657
- var mutatorFillRect = ((writer, deps = defaults18) => {
3658
- const {
3659
- fillPixelData: fillPixelData2 = defaults18.fillPixelData
3660
- } = deps;
3661
- return {
3662
- fillRect(color, rect) {
3663
- const target = writer.config.target;
3664
- const didChange = writer.accumulator.storeRegionBeforeState(rect.x, rect.y, rect.w, rect.h);
3665
- return didChange(fillPixelData2(target, color, rect.x, rect.y, rect.w, rect.h));
3105
+ _inProgress = false;
3106
+ constructor(target, mutatorFactory, {
3107
+ tileSize = 256,
3108
+ maxHistorySteps = 50,
3109
+ historyManager = new HistoryManager(maxHistorySteps),
3110
+ historyActionFactory = makeHistoryAction,
3111
+ pixelTilePool,
3112
+ accumulator
3113
+ } = {}) {
3114
+ this.config = new PixelEngineConfig(tileSize, target);
3115
+ this.historyManager = historyManager;
3116
+ this.pixelTilePool = pixelTilePool ?? new PixelTilePool(this.config);
3117
+ this.accumulator = accumulator ?? new PixelAccumulator(this.config, this.pixelTilePool);
3118
+ this.historyActionFactory = historyActionFactory;
3119
+ this.mutator = mutatorFactory(this);
3120
+ this.paintBuffer = new PaintBuffer(this.config, this.pixelTilePool);
3121
+ }
3122
+ /**
3123
+ * Executes `transaction` and commits the resulting pixel changes as a single
3124
+ * undoable history action.
3125
+ *
3126
+ * - If `transaction` throws, all accumulated changes are rolled back and the error
3127
+ * is re-thrown. No action is committed.
3128
+ * - If `transaction` completes without modifying any pixels, no action is committed.
3129
+ * - `withHistory` is not re-entrant. Calling it again from inside `transaction` will
3130
+ * throw immediately to prevent silent data loss from a nested extractPatch.
3131
+ *
3132
+ * @param transaction Callback to be executed inside the transaction.
3133
+ * @param after Called after both undo and redo — use for generic change notifications.
3134
+ * @param afterUndo Called after undo only — use for dimension or state changes specific to undo.
3135
+ * @param afterRedo Called after redo only.
3136
+ */
3137
+ withHistory(transaction, after, afterUndo, afterRedo) {
3138
+ if (this._inProgress) {
3139
+ throw new Error("withHistory is not re-entrant \u2014 commit or rollback the current operation first");
3666
3140
  }
3667
- };
3668
- });
3141
+ this._inProgress = true;
3142
+ try {
3143
+ transaction(this.mutator);
3144
+ } catch (e) {
3145
+ this.accumulator.rollbackAfterError();
3146
+ throw e;
3147
+ } finally {
3148
+ this._inProgress = false;
3149
+ }
3150
+ if (this.accumulator.beforeTiles.length === 0) return;
3151
+ const patch = this.accumulator.extractPatch();
3152
+ const action = this.historyActionFactory(this, patch, after, afterUndo, afterRedo);
3153
+ this.historyManager.commit(action);
3154
+ }
3155
+ resize(newWidth, newHeight, offsetX = 0, offsetY = 0, after, afterUndo, afterRedo, resizeImageDataFn = resizeImageData) {
3156
+ if (this._inProgress) {
3157
+ throw new Error("Cannot resize inside a withHistory callback");
3158
+ }
3159
+ if (this.accumulator.beforeTiles.length > 0) {
3160
+ throw new Error("Cannot resize with an open accumulator \u2014 commit or rollback first");
3161
+ }
3162
+ const config = this.config;
3163
+ const target = config.target;
3164
+ const beforeImageData = target.imageData;
3165
+ const afterImageData = resizeImageDataFn(beforeImageData, newWidth, newHeight, offsetX, offsetY);
3166
+ target.set(afterImageData);
3167
+ this.historyManager.commit({
3168
+ undo: () => {
3169
+ target.set(beforeImageData);
3170
+ afterUndo?.(beforeImageData);
3171
+ after?.(beforeImageData);
3172
+ },
3173
+ redo: () => {
3174
+ target.set(afterImageData);
3175
+ afterRedo?.(afterImageData);
3176
+ after?.(afterImageData);
3177
+ }
3178
+ });
3179
+ }
3180
+ commitPaintBuffer(alpha = 255, blendFn = sourceOverPerfect, blendPixelDataFn = blendPixelData) {
3181
+ const paintBuffer = this.paintBuffer;
3182
+ const tileShift = paintBuffer.config.tileShift;
3183
+ const lookup = paintBuffer.lookup;
3184
+ const opts = this.blendPixelDataOpts;
3185
+ opts.alpha = alpha;
3186
+ opts.blendFn = blendFn;
3187
+ for (let i = 0; i < lookup.length; i++) {
3188
+ const tile = lookup[i];
3189
+ if (tile) {
3190
+ const didChange = this.accumulator.storeTileBeforeState(tile.id, tile.tx, tile.ty);
3191
+ const dx = tile.tx << tileShift;
3192
+ const dy = tile.ty << tileShift;
3193
+ opts.x = dx;
3194
+ opts.y = dy;
3195
+ opts.w = tile.width;
3196
+ opts.h = tile.height;
3197
+ didChange(blendPixelDataFn(this.config.target, tile, opts));
3198
+ }
3199
+ }
3200
+ paintBuffer.clear();
3201
+ }
3202
+ };
3669
3203
 
3670
- // src/PixelData/fillPixelDataBinaryMask.ts
3671
- var SCRATCH_RECT3 = makeClippedRect();
3672
- function fillPixelDataBinaryMask(dst, color, mask, alpha = 255, x = 0, y = 0) {
3673
- if (alpha === 0) return false;
3674
- const maskW = mask.w;
3675
- const maskH = mask.h;
3676
- const clip = resolveRectClipping(x, y, maskW, maskH, dst.width, dst.height, SCRATCH_RECT3);
3677
- if (!clip.inBounds) return false;
3204
+ // src/PixelData/applyAlphaMaskToPixelData.ts
3205
+ function applyAlphaMaskToPixelData(dst, mask, opts = {}) {
3678
3206
  const {
3679
- x: finalX,
3680
- y: finalY,
3681
- w: actualW,
3682
- h: actualH
3683
- } = clip;
3684
- const maskData = mask.data;
3207
+ x: targetX = 0,
3208
+ y: targetY = 0,
3209
+ w: width = dst.width,
3210
+ h: height = dst.height,
3211
+ alpha: globalAlpha = 255,
3212
+ mx = 0,
3213
+ my = 0,
3214
+ invertMask = false
3215
+ } = opts;
3216
+ if (globalAlpha === 0) return false;
3217
+ let x = targetX;
3218
+ let y = targetY;
3219
+ let w = width;
3220
+ let h = height;
3221
+ if (x < 0) {
3222
+ w += x;
3223
+ x = 0;
3224
+ }
3225
+ if (y < 0) {
3226
+ h += y;
3227
+ y = 0;
3228
+ }
3229
+ w = Math.min(w, dst.width - x);
3230
+ h = Math.min(h, dst.height - y);
3231
+ if (w <= 0) return false;
3232
+ if (h <= 0) return false;
3233
+ const mPitch = mask.w;
3234
+ if (mPitch <= 0) return false;
3235
+ const startX = mx + (x - targetX);
3236
+ const startY = my + (y - targetY);
3237
+ const sX0 = Math.max(0, startX);
3238
+ const sY0 = Math.max(0, startY);
3239
+ const sX1 = Math.min(mPitch, startX + w);
3240
+ const sY1 = Math.min(mask.h, startY + h);
3241
+ const finalW = sX1 - sX0;
3242
+ const finalH = sY1 - sY0;
3243
+ if (finalW <= 0) return false;
3244
+ if (finalH <= 0) return false;
3245
+ const xShift = sX0 - startX;
3246
+ const yShift = sY0 - startY;
3685
3247
  const dst32 = dst.data32;
3686
3248
  const dw = dst.width;
3687
- let finalCol = color;
3688
- if (alpha < 255) {
3689
- const baseSrcAlpha = color >>> 24;
3690
- const colorRGB = color & 16777215;
3691
- const a = baseSrcAlpha * alpha + 128 >> 8;
3692
- finalCol = (colorRGB | a << 24) >>> 0;
3693
- }
3694
- let hasChanged = false;
3695
- for (let iy = 0; iy < actualH; iy++) {
3696
- const currentY = finalY + iy;
3697
- const maskY = currentY - y;
3698
- const maskOffset = maskY * maskW;
3699
- const dstRowOffset = currentY * dw;
3700
- for (let ix = 0; ix < actualW; ix++) {
3701
- const currentX = finalX + ix;
3702
- const maskX = currentX - x;
3703
- const maskIndex = maskOffset + maskX;
3704
- if (maskData[maskIndex]) {
3705
- const current = dst32[dstRowOffset + currentX];
3706
- if (current !== finalCol) {
3707
- dst32[dstRowOffset + currentX] = finalCol;
3708
- hasChanged = true;
3249
+ const dStride = dw - finalW;
3250
+ const mStride = mPitch - finalW;
3251
+ const maskData = mask.data;
3252
+ let dIdx = (y + yShift) * dw + (x + xShift);
3253
+ let mIdx = sY0 * mPitch + sX0;
3254
+ let didChange = false;
3255
+ for (let iy = 0; iy < h; iy++) {
3256
+ for (let ix = 0; ix < w; ix++) {
3257
+ const mVal = maskData[mIdx];
3258
+ const effectiveM = invertMask ? 255 - mVal : mVal;
3259
+ let weight = 0;
3260
+ if (effectiveM === 0) {
3261
+ weight = 0;
3262
+ } else if (effectiveM === 255) {
3263
+ weight = globalAlpha;
3264
+ } else if (globalAlpha === 255) {
3265
+ weight = effectiveM;
3266
+ } else {
3267
+ weight = effectiveM * globalAlpha + 128 >> 8;
3268
+ }
3269
+ if (weight === 0) {
3270
+ dst32[dIdx] = (dst32[dIdx] & 16777215) >>> 0;
3271
+ didChange = true;
3272
+ } else if (weight !== 255) {
3273
+ const d = dst32[dIdx];
3274
+ const da = d >>> 24;
3275
+ if (da !== 0) {
3276
+ const finalAlpha = da === 255 ? weight : da * weight + 128 >> 8;
3277
+ const current = dst32[dIdx];
3278
+ const next = (d & 16777215 | finalAlpha << 24) >>> 0;
3279
+ if (current !== next) {
3280
+ dst32[dIdx] = next;
3281
+ didChange = true;
3282
+ }
3709
3283
  }
3710
3284
  }
3285
+ dIdx++;
3286
+ mIdx++;
3711
3287
  }
3288
+ dIdx += dStride;
3289
+ mIdx += mStride;
3712
3290
  }
3713
- return hasChanged;
3291
+ return didChange;
3714
3292
  }
3715
3293
 
3716
- // src/History/PixelMutator/mutatorFillBinaryMask.ts
3717
- var defaults19 = {
3718
- fillPixelDataBinaryMask
3294
+ // src/History/PixelMutator/mutatorApplyAlphaMask.ts
3295
+ var defaults11 = {
3296
+ applyAlphaMaskToPixelData
3719
3297
  };
3720
- var mutatorFillBinaryMask = ((writer, deps = defaults19) => {
3298
+ var mutatorApplyAlphaMask = ((writer, deps = defaults11) => {
3721
3299
  const {
3722
- fillPixelDataBinaryMask: fillPixelDataBinaryMask2 = defaults19.fillPixelDataBinaryMask
3300
+ applyAlphaMaskToPixelData: applyAlphaMaskToPixelData2 = defaults11.applyAlphaMaskToPixelData
3723
3301
  } = deps;
3724
3302
  return {
3725
- fillBinaryMask(color, mask, alpha = 255, x = 0, y = 0) {
3726
- const didChange = writer.accumulator.storeRegionBeforeState(x, y, mask.w, mask.h);
3727
- return didChange(fillPixelDataBinaryMask2(writer.config.target, color, mask, alpha, x, y));
3303
+ applyAlphaMask(mask, opts = {}) {
3304
+ let target = writer.config.target;
3305
+ const {
3306
+ x = 0,
3307
+ y = 0,
3308
+ w = target.width,
3309
+ h = target.height
3310
+ } = opts;
3311
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3312
+ return didChange(applyAlphaMaskToPixelData2(target, mask, opts));
3728
3313
  }
3729
3314
  };
3730
3315
  });
3731
3316
 
3732
- // src/PixelData/invertPixelData.ts
3733
- var SCRATCH_RECT4 = makeClippedRect();
3734
- function invertPixelData(pixelData, opts = {}) {
3735
- const dst = pixelData;
3317
+ // src/PixelData/applyBinaryMaskToPixelData.ts
3318
+ function applyBinaryMaskToPixelData(dst, mask, opts = {}) {
3736
3319
  const {
3737
3320
  x: targetX = 0,
3738
3321
  y: targetY = 0,
3739
- w: width = pixelData.width,
3740
- h: height = pixelData.height,
3741
- mask,
3322
+ w: width = dst.width,
3323
+ h: height = dst.height,
3324
+ alpha: globalAlpha = 255,
3742
3325
  mx = 0,
3743
3326
  my = 0,
3744
3327
  invertMask = false
3745
3328
  } = opts;
3746
- const clip = resolveRectClipping(targetX, targetY, width, height, dst.width, dst.height, SCRATCH_RECT4);
3747
- if (!clip.inBounds) return false;
3748
- const {
3749
- x,
3750
- y,
3751
- w: actualW,
3752
- h: actualH
3753
- } = clip;
3329
+ if (globalAlpha === 0) return false;
3330
+ let x = targetX;
3331
+ let y = targetY;
3332
+ let w = width;
3333
+ let h = height;
3334
+ if (x < 0) {
3335
+ w += x;
3336
+ x = 0;
3337
+ }
3338
+ if (y < 0) {
3339
+ h += y;
3340
+ y = 0;
3341
+ }
3342
+ w = Math.min(w, dst.width - x);
3343
+ h = Math.min(h, dst.height - y);
3344
+ if (w <= 0 || h <= 0) return false;
3345
+ const mPitch = mask.w;
3346
+ if (mPitch <= 0) return false;
3347
+ const startX = mx + (x - targetX);
3348
+ const startY = my + (y - targetY);
3349
+ const sX0 = Math.max(0, startX);
3350
+ const sY0 = Math.max(0, startY);
3351
+ const sX1 = Math.min(mPitch, startX + w);
3352
+ const sY1 = Math.min(mask.h, startY + h);
3353
+ const finalW = sX1 - sX0;
3354
+ const finalH = sY1 - sY0;
3355
+ if (finalW <= 0 || finalH <= 0) {
3356
+ return false;
3357
+ }
3358
+ const xShift = sX0 - startX;
3359
+ const yShift = sY0 - startY;
3754
3360
  const dst32 = dst.data32;
3755
3361
  const dw = dst.width;
3756
- const mPitch = mask?.w ?? width;
3757
- const dx = x - targetX;
3758
- const dy = y - targetY;
3759
- let dIdx = y * dw + x;
3760
- let mIdx = (my + dy) * mPitch + (mx + dx);
3761
- const dStride = dw - actualW;
3762
- const mStride = mPitch - actualW;
3763
- if (mask) {
3764
- const maskData = mask.data;
3765
- for (let iy = 0; iy < actualH; iy++) {
3766
- for (let ix = 0; ix < actualW; ix++) {
3767
- const mVal = maskData[mIdx];
3768
- const isHit = invertMask ? mVal === 0 : mVal === 1;
3769
- if (isHit) {
3770
- dst32[dIdx] = dst32[dIdx] ^ 16777215;
3362
+ const dStride = dw - finalW;
3363
+ const mStride = mPitch - finalW;
3364
+ const maskData = mask.data;
3365
+ let dIdx = (y + yShift) * dw + (x + xShift);
3366
+ let mIdx = sY0 * mPitch + sX0;
3367
+ let didChange = false;
3368
+ for (let iy = 0; iy < finalH; iy++) {
3369
+ for (let ix = 0; ix < finalW; ix++) {
3370
+ const mVal = maskData[mIdx];
3371
+ const isMaskedOut = invertMask ? mVal !== 0 : mVal === 0;
3372
+ if (isMaskedOut) {
3373
+ const current = dst32[dIdx];
3374
+ const next = (current & 16777215) >>> 0;
3375
+ if (current !== next) {
3376
+ dst32[dIdx] = next;
3377
+ didChange = true;
3378
+ }
3379
+ } else if (globalAlpha !== 255) {
3380
+ const d = dst32[dIdx];
3381
+ const da = d >>> 24;
3382
+ if (da !== 0) {
3383
+ const finalAlpha = da === 255 ? globalAlpha : da * globalAlpha + 128 >> 8;
3384
+ const next = (d & 16777215 | finalAlpha << 24) >>> 0;
3385
+ if (d !== next) {
3386
+ dst32[dIdx] = next;
3387
+ didChange = true;
3388
+ }
3771
3389
  }
3772
- dIdx++;
3773
- mIdx++;
3774
- }
3775
- dIdx += dStride;
3776
- mIdx += mStride;
3777
- }
3778
- } else {
3779
- for (let iy = 0; iy < actualH; iy++) {
3780
- for (let ix = 0; ix < actualW; ix++) {
3781
- dst32[dIdx] = dst32[dIdx] ^ 16777215;
3782
- dIdx++;
3783
3390
  }
3784
- dIdx += dStride;
3391
+ dIdx++;
3392
+ mIdx++;
3785
3393
  }
3394
+ dIdx += dStride;
3395
+ mIdx += mStride;
3786
3396
  }
3787
- return true;
3397
+ return didChange;
3788
3398
  }
3789
3399
 
3790
- // src/History/PixelMutator/mutatorInvert.ts
3791
- var defaults20 = {
3792
- invertPixelData
3400
+ // src/History/PixelMutator/mutatorApplyBinaryMask.ts
3401
+ var defaults12 = {
3402
+ applyBinaryMaskToPixelData
3793
3403
  };
3794
- var mutatorInvert = ((writer, deps = defaults20) => {
3404
+ var mutatorApplyBinaryMask = ((writer, deps = defaults12) => {
3795
3405
  const {
3796
- invertPixelData: invertPixelData2 = defaults20.invertPixelData
3406
+ applyBinaryMaskToPixelData: applyBinaryMaskToPixelData2 = defaults12.applyBinaryMaskToPixelData
3797
3407
  } = deps;
3798
3408
  return {
3799
- invert(opts = {}) {
3800
- const target = writer.config.target;
3409
+ applyBinaryMask(mask, opts = {}) {
3410
+ let target = writer.config.target;
3801
3411
  const {
3802
3412
  x = 0,
3803
3413
  y = 0,
@@ -3805,126 +3415,11 @@ var mutatorInvert = ((writer, deps = defaults20) => {
3805
3415
  h = target.height
3806
3416
  } = opts;
3807
3417
  const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3808
- return didChange(invertPixelData2(target, opts));
3418
+ return didChange(applyBinaryMaskToPixelData2(target, mask, opts));
3809
3419
  }
3810
3420
  };
3811
3421
  });
3812
3422
 
3813
- // src/History/PixelMutator.ts
3814
- function makeFullPixelMutator(writer) {
3815
- return {
3816
- // @sort
3817
- ...mutatorApplyAlphaMask(writer),
3818
- ...mutatorApplyBinaryMask(writer),
3819
- ...mutatorApplyCircleBrushStroke(writer),
3820
- ...mutatorApplyCirclePencil(writer),
3821
- ...mutatorApplyCirclePencilStroke(writer),
3822
- ...mutatorApplyRectBrush(writer),
3823
- ...mutatorApplyRectBrushStroke(writer),
3824
- ...mutatorApplyRectPencil(writer),
3825
- ...mutatorApplyRectPencilStroke(writer),
3826
- ...mutatorBlendColor(writer),
3827
- ...mutatorBlendColorCircleMask(writer),
3828
- ...mutatorBlendPixel(writer),
3829
- ...mutatorBlendPixelData(writer),
3830
- ...mutatorBlendPixelDataAlphaMask(writer),
3831
- ...mutatorBlendPixelDataBinaryMask(writer),
3832
- ...mutatorClear(writer),
3833
- ...mutatorFill(writer),
3834
- ...mutatorFillBinaryMask(writer),
3835
- ...mutatorFillRect(writer),
3836
- ...mutatorInvert(writer)
3837
- };
3838
- }
3839
-
3840
- // src/PixelTile/PixelTile.ts
3841
- var PixelTile = class {
3842
- constructor(id, tx, ty, tileSize, tileArea) {
3843
- this.id = id;
3844
- this.tx = tx;
3845
- this.ty = ty;
3846
- this.width = this.height = tileSize;
3847
- this.data32 = new Uint32Array(tileArea);
3848
- const data8 = new Uint8ClampedArray(this.data32.buffer);
3849
- this.imageData = new ImageData(data8, tileSize, tileSize);
3850
- }
3851
- data32;
3852
- width;
3853
- height;
3854
- imageData;
3855
- };
3856
-
3857
- // src/PixelTile/PixelTilePool.ts
3858
- var PixelTilePool = class {
3859
- pool;
3860
- tileSize;
3861
- tileArea;
3862
- constructor(config) {
3863
- this.pool = [];
3864
- this.tileSize = config.tileSize;
3865
- this.tileArea = config.tileArea;
3866
- }
3867
- getTile(id, tx, ty) {
3868
- let tile = this.pool.pop();
3869
- if (tile) {
3870
- tile.id = id;
3871
- tile.tx = tx;
3872
- tile.ty = ty;
3873
- tile.data32.fill(0);
3874
- return tile;
3875
- }
3876
- return new PixelTile(id, tx, ty, this.tileSize, this.tileArea);
3877
- }
3878
- releaseTile(tile) {
3879
- this.pool.push(tile);
3880
- }
3881
- releaseTiles(tiles) {
3882
- let length = tiles.length;
3883
- for (let i = 0; i < length; i++) {
3884
- let tile = tiles[i];
3885
- if (tile) {
3886
- this.pool.push(tile);
3887
- }
3888
- }
3889
- tiles.length = 0;
3890
- }
3891
- };
3892
-
3893
- // src/History/PixelWriter.ts
3894
- var PixelWriter = class {
3895
- historyManager;
3896
- accumulator;
3897
- historyActionFactory;
3898
- config;
3899
- mutator;
3900
- constructor(target, mutatorFactory, {
3901
- tileSize = 256,
3902
- maxHistorySteps = 50,
3903
- historyManager = new HistoryManager(maxHistorySteps),
3904
- historyActionFactory = makeHistoryAction,
3905
- pixelTilePool
3906
- } = {}) {
3907
- this.config = new PixelEngineConfig(tileSize, target);
3908
- this.historyManager = historyManager;
3909
- pixelTilePool ??= new PixelTilePool(this.config);
3910
- this.accumulator = new PixelAccumulator(this.config, pixelTilePool);
3911
- this.historyActionFactory = historyActionFactory;
3912
- this.mutator = mutatorFactory(this);
3913
- }
3914
- withHistory(cb, after, afterUndo, afterRedo) {
3915
- try {
3916
- cb(this.mutator);
3917
- } catch (e) {
3918
- this.accumulator.rollback();
3919
- throw e;
3920
- }
3921
- if (this.accumulator.beforeTiles.length === 0) return;
3922
- const patch = this.accumulator.extractPatch();
3923
- const action = this.historyActionFactory(this, patch, after, afterUndo, afterRedo);
3924
- this.historyManager.commit(action);
3925
- }
3926
- };
3927
-
3928
3423
  // src/ImageData/copyImageData.ts
3929
3424
  function copyImageData({
3930
3425
  data,
@@ -4046,35 +3541,6 @@ function resampleImageData(source, factor) {
4046
3541
  return new ImageData(uint8ClampedArray, width, height);
4047
3542
  }
4048
3543
 
4049
- // src/ImageData/resizeImageData.ts
4050
- function resizeImageData(target, newWidth, newHeight, offsetX = 0, offsetY = 0) {
4051
- const result = new ImageData(newWidth, newHeight);
4052
- const {
4053
- width: oldW,
4054
- height: oldH,
4055
- data: oldData
4056
- } = target;
4057
- const newData = result.data;
4058
- const x0 = Math.max(0, offsetX);
4059
- const y0 = Math.max(0, offsetY);
4060
- const x1 = Math.min(newWidth, offsetX + oldW);
4061
- const y1 = Math.min(newHeight, offsetY + oldH);
4062
- if (x1 <= x0 || y1 <= y0) {
4063
- return result;
4064
- }
4065
- const rowCount = y1 - y0;
4066
- const rowLen = (x1 - x0) * 4;
4067
- for (let row = 0; row < rowCount; row++) {
4068
- const dstY = y0 + row;
4069
- const srcY = dstY - offsetY;
4070
- const srcX = x0 - offsetX;
4071
- const dstStart = (dstY * newWidth + x0) * 4;
4072
- const srcStart = (srcY * oldW + srcX) * 4;
4073
- newData.set(oldData.subarray(srcStart, srcStart + rowLen), dstStart);
4074
- }
4075
- return result;
4076
- }
4077
-
4078
3544
  // src/ImageData/ReusableImageData.ts
4079
3545
  function makeReusableImageData() {
4080
3546
  let imageData = null;
@@ -4489,62 +3955,6 @@ function makeBinaryMask(w, h, data) {
4489
3955
  };
4490
3956
  }
4491
3957
 
4492
- // src/Mask/CircleAlphaMask.ts
4493
- function makeCircleAlphaMask(size, fallOff = () => 1) {
4494
- const area = size * size;
4495
- const data = new Uint8Array(area);
4496
- const radius = size / 2;
4497
- const invR = 1 / radius;
4498
- const minOffset = -Math.ceil(radius - 0.5);
4499
- for (let y = 0; y < size; y++) {
4500
- for (let x = 0; x < size; x++) {
4501
- const dx = x - radius + 0.5;
4502
- const dy = y - radius + 0.5;
4503
- const distSqr = dx * dx + dy * dy;
4504
- if (distSqr <= radius * radius) {
4505
- const dist = Math.sqrt(distSqr);
4506
- data[y * size + x] = fallOff(1 - dist * invR) * 255 | 0;
4507
- }
4508
- }
4509
- }
4510
- return {
4511
- type: 0 /* ALPHA */,
4512
- data,
4513
- w: size,
4514
- h: size,
4515
- radius,
4516
- size,
4517
- minOffset
4518
- };
4519
- }
4520
-
4521
- // src/Mask/CircleBinaryMask.ts
4522
- function makeCircleBinaryMask(size) {
4523
- const area = size * size;
4524
- const data = new Uint8Array(area);
4525
- const radius = size / 2;
4526
- const minOffset = -Math.ceil(radius - 0.5);
4527
- for (let y = 0; y < size; y++) {
4528
- for (let x = 0; x < size; x++) {
4529
- const dx = x - radius + 0.5;
4530
- const dy = y - radius + 0.5;
4531
- const distSqr = dx * dx + dy * dy;
4532
- if (distSqr <= radius * radius) {
4533
- data[y * size + x] = 1;
4534
- }
4535
- }
4536
- }
4537
- return {
4538
- type: 1 /* BINARY */,
4539
- data,
4540
- w: size,
4541
- h: size,
4542
- radius,
4543
- size,
4544
- minOffset
4545
- };
4546
- }
4547
-
4548
3958
  // src/Mask/applyBinaryMaskToAlphaMask.ts
4549
3959
  function applyBinaryMaskToAlphaMask(alphaMaskDst, binaryMaskSrc, opts = {}) {
4550
3960
  const {
@@ -4999,6 +4409,162 @@ var PixelData = class {
4999
4409
  }
5000
4410
  };
5001
4411
 
4412
+ // src/PixelData/blendColorPixelDataAlphaMask.ts
4413
+ function blendColorPixelDataAlphaMask(dst, color, mask, opts = {}) {
4414
+ const targetX = opts.x ?? 0;
4415
+ const targetY = opts.y ?? 0;
4416
+ const w = opts.w ?? mask.w;
4417
+ const h = opts.h ?? mask.h;
4418
+ const globalAlpha = opts.alpha ?? 255;
4419
+ const blendFn = opts.blendFn ?? sourceOverPerfect;
4420
+ const mx = opts.mx ?? 0;
4421
+ const my = opts.my ?? 0;
4422
+ const invertMask = opts.invertMask ?? false;
4423
+ if (globalAlpha === 0) return false;
4424
+ const baseSrcAlpha = color >>> 24;
4425
+ const isOverwrite = blendFn.isOverwrite || false;
4426
+ if (baseSrcAlpha === 0 && !isOverwrite) return false;
4427
+ let x = targetX;
4428
+ let y = targetY;
4429
+ let actualW = w;
4430
+ let actualH = h;
4431
+ if (x < 0) {
4432
+ actualW += x;
4433
+ x = 0;
4434
+ }
4435
+ if (y < 0) {
4436
+ actualH += y;
4437
+ y = 0;
4438
+ }
4439
+ actualW = Math.min(actualW, dst.width - x);
4440
+ actualH = Math.min(actualH, dst.height - y);
4441
+ if (actualW <= 0 || actualH <= 0) return false;
4442
+ const dx = x - targetX | 0;
4443
+ const dy = y - targetY | 0;
4444
+ const dst32 = dst.data32;
4445
+ const dw = dst.width;
4446
+ const mPitch = mask.w;
4447
+ const maskData = mask.data;
4448
+ let dIdx = y * dw + x | 0;
4449
+ let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
4450
+ const dStride = dw - actualW | 0;
4451
+ const mStride = mPitch - actualW | 0;
4452
+ const isOpaque = globalAlpha === 255;
4453
+ const colorRGB = color & 16777215;
4454
+ let didChange = false;
4455
+ for (let iy = 0; iy < actualH; iy++) {
4456
+ for (let ix = 0; ix < actualW; ix++) {
4457
+ const mVal = maskData[mIdx];
4458
+ const effM = invertMask ? 255 - mVal : mVal;
4459
+ if (effM === 0) {
4460
+ dIdx++;
4461
+ mIdx++;
4462
+ continue;
4463
+ }
4464
+ let weight = globalAlpha;
4465
+ if (isOpaque) {
4466
+ weight = effM;
4467
+ } else if (effM !== 255) {
4468
+ weight = effM * globalAlpha + 128 >> 8;
4469
+ }
4470
+ if (weight === 0) {
4471
+ dIdx++;
4472
+ mIdx++;
4473
+ continue;
4474
+ }
4475
+ let finalCol = color;
4476
+ if (weight < 255) {
4477
+ const a = baseSrcAlpha * weight + 128 >> 8;
4478
+ if (a === 0 && !isOverwrite) {
4479
+ dIdx++;
4480
+ mIdx++;
4481
+ continue;
4482
+ }
4483
+ finalCol = (colorRGB | a << 24) >>> 0;
4484
+ }
4485
+ const current = dst32[dIdx];
4486
+ const next = blendFn(finalCol, current);
4487
+ if (current !== next) {
4488
+ dst32[dIdx] = next;
4489
+ didChange = true;
4490
+ }
4491
+ dIdx++;
4492
+ mIdx++;
4493
+ }
4494
+ dIdx += dStride;
4495
+ mIdx += mStride;
4496
+ }
4497
+ return didChange;
4498
+ }
4499
+
4500
+ // src/PixelData/blendColorPixelDataBinaryMask.ts
4501
+ function blendColorPixelDataBinaryMask(dst, color, mask, opts = {}) {
4502
+ const targetX = opts.x ?? 0;
4503
+ const targetY = opts.y ?? 0;
4504
+ let w = opts.w ?? mask.w;
4505
+ let h = opts.h ?? mask.h;
4506
+ const globalAlpha = opts.alpha ?? 255;
4507
+ const blendFn = opts.blendFn ?? sourceOverPerfect;
4508
+ const mx = opts.mx ?? 0;
4509
+ const my = opts.my ?? 0;
4510
+ const invertMask = opts.invertMask ?? false;
4511
+ if (globalAlpha === 0) return false;
4512
+ const baseSrcAlpha = color >>> 24;
4513
+ const isOverwrite = blendFn.isOverwrite || false;
4514
+ if (baseSrcAlpha === 0 && !isOverwrite) return false;
4515
+ let x = targetX;
4516
+ let y = targetY;
4517
+ if (x < 0) {
4518
+ w += x;
4519
+ x = 0;
4520
+ }
4521
+ if (y < 0) {
4522
+ h += y;
4523
+ y = 0;
4524
+ }
4525
+ const actualW = Math.min(w, dst.width - x);
4526
+ const actualH = Math.min(h, dst.height - y);
4527
+ if (actualW <= 0 || actualH <= 0) return false;
4528
+ let baseColorWithGlobalAlpha = color;
4529
+ if (globalAlpha < 255) {
4530
+ const a = baseSrcAlpha * globalAlpha + 128 >> 8;
4531
+ if (a === 0 && !isOverwrite) return false;
4532
+ baseColorWithGlobalAlpha = (color & 16777215 | a << 24) >>> 0;
4533
+ }
4534
+ const dx = x - targetX | 0;
4535
+ const dy = y - targetY | 0;
4536
+ const dst32 = dst.data32;
4537
+ const dw = dst.width;
4538
+ const mPitch = mask.w;
4539
+ const maskData = mask.data;
4540
+ let dIdx = y * dw + x | 0;
4541
+ let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
4542
+ const dStride = dw - actualW | 0;
4543
+ const mStride = mPitch - actualW | 0;
4544
+ const skipVal = invertMask ? 1 : 0;
4545
+ let didChange = false;
4546
+ for (let iy = 0; iy < actualH; iy++) {
4547
+ for (let ix = 0; ix < actualW; ix++) {
4548
+ if (maskData[mIdx] === skipVal) {
4549
+ dIdx++;
4550
+ mIdx++;
4551
+ continue;
4552
+ }
4553
+ const current = dst32[dIdx];
4554
+ const next = blendFn(baseColorWithGlobalAlpha, current);
4555
+ if (current !== next) {
4556
+ dst32[dIdx] = next;
4557
+ didChange = true;
4558
+ }
4559
+ dIdx++;
4560
+ mIdx++;
4561
+ }
4562
+ dIdx += dStride;
4563
+ mIdx += mStride;
4564
+ }
4565
+ return didChange;
4566
+ }
4567
+
5002
4568
  // src/PixelData/blendPixelDataPaintBuffer.ts
5003
4569
  var SCRATCH_OPTS = {
5004
4570
  x: 0,
@@ -5245,103 +4811,137 @@ function writePixelDataBuffer(target, data, _x, _y, _w, _h) {
5245
4811
  }
5246
4812
  }
5247
4813
 
5248
- // src/PixelTile/PaintBuffer.ts
5249
- var PaintBuffer = class {
5250
- constructor(config, tilePool) {
5251
- this.config = config;
5252
- this.tilePool = tilePool;
5253
- this.lookup = [];
4814
+ // src/PixelData/writePaintBufferToPixelData.ts
4815
+ function writePaintBufferToPixelData(target, paintBuffer, writePixelDataBufferFn = writePixelDataBuffer) {
4816
+ const tileShift = paintBuffer.config.tileShift;
4817
+ const lookup = paintBuffer.lookup;
4818
+ for (let i = 0; i < lookup.length; i++) {
4819
+ const tile = lookup[i];
4820
+ if (tile) {
4821
+ const dx = tile.tx << tileShift;
4822
+ const dy = tile.ty << tileShift;
4823
+ writePixelDataBufferFn(target, tile.data32, dx, dy, tile.width, tile.height);
4824
+ }
5254
4825
  }
5255
- lookup;
5256
- processMaskTiles(mask, callback) {
5257
- const {
5258
- tileShift,
5259
- targetColumns
5260
- } = this.config;
5261
- const x1 = mask.x >> tileShift;
5262
- const y1 = mask.y >> tileShift;
5263
- const x2 = mask.x + mask.w - 1 >> tileShift;
5264
- const y2 = mask.y + mask.h - 1 >> tileShift;
5265
- for (let ty = y1; ty <= y2; ty++) {
5266
- const tileRowIndex = ty * targetColumns;
5267
- const tileTop = ty << tileShift;
5268
- for (let tx = x1; tx <= x2; tx++) {
5269
- const id = tileRowIndex + tx;
5270
- let tile = this.lookup[id];
5271
- if (!tile) {
5272
- tile = this.tilePool.getTile(id, tx, ty);
5273
- this.lookup[id] = tile;
4826
+ }
4827
+
4828
+ // src/Paint/makeCirclePaintAlphaMask.ts
4829
+ function makeCirclePaintAlphaMask(size, fallOff = (d) => d) {
4830
+ const area = size * size;
4831
+ const data = new Uint8Array(area);
4832
+ const radius = size / 2;
4833
+ const invR = 1 / radius;
4834
+ const centerOffset = -Math.ceil(radius - 0.5);
4835
+ for (let y = 0; y < size; y++) {
4836
+ const rowOffset = y * size;
4837
+ const dy = y - radius + 0.5;
4838
+ const dy2 = dy * dy;
4839
+ for (let x = 0; x < size; x++) {
4840
+ const dx = x - radius + 0.5;
4841
+ const distSqr = dx * dx + dy2;
4842
+ if (distSqr <= radius * radius) {
4843
+ const dist = Math.sqrt(distSqr) * invR;
4844
+ const strength = fallOff(1 - dist);
4845
+ if (strength > 0) {
4846
+ const intensity = strength * 255 | 0;
4847
+ data[rowOffset + x] = Math.max(0, Math.min(255, intensity));
5274
4848
  }
5275
- const tileLeft = tx << tileShift;
5276
- const startX = Math.max(mask.x, tileLeft);
5277
- const endX = Math.min(mask.x + mask.w, tileLeft + this.config.tileSize);
5278
- const startY = Math.max(mask.y, tileTop);
5279
- const endY = Math.min(mask.y + mask.h, tileTop + this.config.tileSize);
5280
- callback(tile, startX, startY, endX - startX, endY - startY, startX - mask.x, startY - mask.y);
5281
4849
  }
5282
4850
  }
5283
4851
  }
5284
- writeColorBinaryMaskRect(color, mask) {
5285
- const {
5286
- tileShift,
5287
- tileMask
5288
- } = this.config;
5289
- const maskData = mask.data;
5290
- const maskW = mask.w;
5291
- this.processMaskTiles(mask, (tile, bX, bY, bW, bH, mX, mY) => {
5292
- const data32 = tile.data32;
5293
- const startTileX = bX & tileMask;
5294
- for (let i = 0; i < bH; i++) {
5295
- const tileY = bY + i & tileMask;
5296
- const maskY = mY + i;
5297
- const tileRowOffset = tileY << tileShift;
5298
- const maskRowOffset = maskY * maskW;
5299
- const destStart = tileRowOffset + startTileX;
5300
- const maskStart = maskRowOffset + mX;
5301
- for (let j = 0; j < bW; j++) {
5302
- if (maskData[maskStart + j]) {
5303
- data32[destStart + j] = color;
5304
- }
5305
- }
4852
+ return {
4853
+ type: 0 /* ALPHA */,
4854
+ data,
4855
+ w: size,
4856
+ h: size,
4857
+ centerOffsetX: centerOffset,
4858
+ centerOffsetY: centerOffset
4859
+ };
4860
+ }
4861
+
4862
+ // src/Paint/makeCirclePaintBinaryMask.ts
4863
+ function makeCirclePaintBinaryMask(size) {
4864
+ const area = size * size;
4865
+ const data = new Uint8Array(area);
4866
+ const radius = size / 2;
4867
+ const centerOffset = -Math.ceil(radius - 0.5);
4868
+ for (let y = 0; y < size; y++) {
4869
+ for (let x = 0; x < size; x++) {
4870
+ const dx = x - radius + 0.5;
4871
+ const dy = y - radius + 0.5;
4872
+ const distSqr = dx * dx + dy * dy;
4873
+ if (distSqr <= radius * radius) {
4874
+ data[y * size + x] = 1;
5306
4875
  }
5307
- });
4876
+ }
5308
4877
  }
5309
- writeColorAlphaMaskRect(color, mask) {
5310
- const {
5311
- tileShift,
5312
- tileMask
5313
- } = this.config;
5314
- const maskData = mask.data;
5315
- const maskW = mask.w;
5316
- const colorRGB = color & 16777215;
5317
- const colorA = color >>> 24;
5318
- this.processMaskTiles(mask, (tile, bX, bY, bW, bH, mX, mY) => {
5319
- const data32 = tile.data32;
5320
- const startTileX = bX & tileMask;
5321
- for (let i = 0; i < bH; i++) {
5322
- const tileY = bY + i & tileMask;
5323
- const maskY = mY + i;
5324
- const tileRowOffset = tileY << tileShift;
5325
- const maskRowOffset = maskY * maskW;
5326
- const destStart = tileRowOffset + startTileX;
5327
- const maskStart = maskRowOffset + mX;
5328
- for (let j = 0; j < bW; j++) {
5329
- const maskA = maskData[maskStart + j];
5330
- if (maskA > 0) {
5331
- const finalA = colorA * maskA + 128 >> 8;
5332
- data32[destStart + j] = (colorRGB | finalA << 24) >>> 0;
5333
- }
5334
- }
4878
+ return {
4879
+ type: 1 /* BINARY */,
4880
+ data,
4881
+ w: size,
4882
+ h: size,
4883
+ centerOffsetX: centerOffset,
4884
+ centerOffsetY: centerOffset
4885
+ };
4886
+ }
4887
+
4888
+ // src/Paint/makePaintMask.ts
4889
+ function makePaintBinaryMask(mask) {
4890
+ return {
4891
+ type: 1 /* BINARY */,
4892
+ data: mask.data,
4893
+ w: mask.w,
4894
+ h: mask.h,
4895
+ centerOffsetX: -macro_halfAndFloor(mask.w),
4896
+ centerOffsetY: -macro_halfAndFloor(mask.h)
4897
+ };
4898
+ }
4899
+ function makePaintAlphaMask(mask) {
4900
+ return {
4901
+ type: 0 /* ALPHA */,
4902
+ data: mask.data,
4903
+ w: mask.w,
4904
+ h: mask.h,
4905
+ centerOffsetX: -macro_halfAndFloor(mask.w),
4906
+ centerOffsetY: -macro_halfAndFloor(mask.h)
4907
+ };
4908
+ }
4909
+
4910
+ // src/Paint/makeRectFalloffPaintAlphaMask.ts
4911
+ function makeRectFalloffPaintAlphaMask(width, height, fallOff = (d) => d) {
4912
+ const fPx = Math.floor(width / 2);
4913
+ const fPy = Math.floor(height / 2);
4914
+ const invHalfW = 2 / width;
4915
+ const invHalfH = 2 / height;
4916
+ const offX = width % 2 === 0 ? 0.5 : 0;
4917
+ const offY = height % 2 === 0 ? 0.5 : 0;
4918
+ const area = width * height;
4919
+ const data = new Uint8Array(area);
4920
+ for (let y = 0; y < height; y++) {
4921
+ const dy = Math.abs(y - fPy + offY) * invHalfH;
4922
+ const rowOffset = y * width;
4923
+ for (let x = 0; x < width; x++) {
4924
+ const dx = Math.abs(x - fPx + offX) * invHalfW;
4925
+ const dist = dx > dy ? dx : dy;
4926
+ const strength = fallOff(1 - dist);
4927
+ if (strength > 0) {
4928
+ const intensity = strength * 255 | 0;
4929
+ data[rowOffset + x] = Math.max(0, Math.min(255, intensity));
5335
4930
  }
5336
- });
5337
- }
5338
- clear() {
5339
- this.tilePool.releaseTiles(this.lookup);
4931
+ }
5340
4932
  }
5341
- };
4933
+ return {
4934
+ type: 0 /* ALPHA */,
4935
+ data,
4936
+ w: width,
4937
+ h: height,
4938
+ centerOffsetX: -(width >> 1),
4939
+ centerOffsetY: -(height >> 1)
4940
+ };
4941
+ }
5342
4942
 
5343
- // src/PixelTile/PaintBufferRenderer.ts
5344
- function makePaintBufferRenderer(paintBuffer, offscreenCanvasClass = OffscreenCanvas) {
4943
+ // src/Paint/PaintBufferCanvasRenderer.ts
4944
+ function makePaintBufferCanvasRenderer(paintBuffer, offscreenCanvasClass = OffscreenCanvas) {
5345
4945
  const config = paintBuffer.config;
5346
4946
  const tileSize = config.tileSize;
5347
4947
  const tileShift = config.tileShift;
@@ -5350,16 +4950,20 @@ function makePaintBufferRenderer(paintBuffer, offscreenCanvasClass = OffscreenCa
5350
4950
  const ctx = canvas.getContext("2d");
5351
4951
  if (!ctx) throw new Error(CANVAS_CTX_FAILED);
5352
4952
  ctx.imageSmoothingEnabled = false;
5353
- return function drawPaintBuffer(target) {
4953
+ return function drawPaintBuffer(targetCtx, alpha = 255, compOperation = "source-over") {
4954
+ targetCtx.globalAlpha = alpha / 255;
4955
+ targetCtx.globalCompositeOperation = compOperation;
5354
4956
  for (let i = 0; i < lookup.length; i++) {
5355
4957
  const tile = lookup[i];
5356
4958
  if (tile) {
5357
4959
  const dx = tile.tx << tileShift;
5358
4960
  const dy = tile.ty << tileShift;
5359
4961
  ctx.putImageData(tile.imageData, 0, 0);
5360
- target.drawImage(canvas, dx, dy);
4962
+ targetCtx.drawImage(canvas, dx, dy);
5361
4963
  }
5362
4964
  }
4965
+ targetCtx.globalAlpha = 1;
4966
+ targetCtx.globalCompositeOperation = "source-over";
5363
4967
  };
5364
4968
  }
5365
4969
  export {
@@ -5384,13 +4988,11 @@ export {
5384
4988
  applyBinaryMaskToAlphaMask,
5385
4989
  applyBinaryMaskToPixelData,
5386
4990
  applyPatchTiles,
5387
- applyRectBrushToPixelData,
5388
4991
  base64DecodeArrayBuffer,
5389
4992
  base64EncodeArrayBuffer,
5390
4993
  blendColorPixelData,
5391
4994
  blendColorPixelDataAlphaMask,
5392
4995
  blendColorPixelDataBinaryMask,
5393
- blendColorPixelDataCircleMask,
5394
4996
  blendPixel,
5395
4997
  blendPixelData,
5396
4998
  blendPixelDataAlphaMask,
@@ -5432,12 +5034,8 @@ export {
5432
5034
  fillPixelDataFast,
5433
5035
  floodFillSelection,
5434
5036
  forEachLinePoint,
5435
- getCircleBrushOrPencilBounds,
5436
- getCircleBrushOrPencilStrokeBounds,
5437
5037
  getImageDataFromClipboard,
5438
5038
  getIndexedImageColorCounts,
5439
- getRectBrushOrPencilBounds,
5440
- getRectBrushOrPencilStrokeBounds,
5441
5039
  getRectsBounds,
5442
5040
  getSupportedPixelFormats,
5443
5041
  hardLightFast,
@@ -5471,15 +5069,18 @@ export {
5471
5069
  makeBinaryMask,
5472
5070
  makeBlendModeRegistry,
5473
5071
  makeCanvasFrameRenderer,
5474
- makeCircleAlphaMask,
5475
- makeCircleBinaryMask,
5072
+ makeCirclePaintAlphaMask,
5073
+ makeCirclePaintBinaryMask,
5476
5074
  makeFastBlendModeRegistry,
5477
5075
  makeFullPixelMutator,
5478
5076
  makeHistoryAction,
5479
5077
  makeImageDataLike,
5480
- makePaintBufferRenderer,
5078
+ makePaintAlphaMask,
5079
+ makePaintBinaryMask,
5080
+ makePaintBufferCanvasRenderer,
5481
5081
  makePerfectBlendModeRegistry,
5482
5082
  makePixelCanvas,
5083
+ makeRectFalloffPaintAlphaMask,
5483
5084
  makeReusableCanvas,
5484
5085
  makeReusableImageData,
5485
5086
  makeReusableOffscreenCanvas,
@@ -5491,15 +5092,7 @@ export {
5491
5092
  multiplyPerfect,
5492
5093
  mutatorApplyAlphaMask,
5493
5094
  mutatorApplyBinaryMask,
5494
- mutatorApplyCircleBrushStroke,
5495
- mutatorApplyCirclePencil,
5496
- mutatorApplyCirclePencilStroke,
5497
- mutatorApplyRectBrush,
5498
- mutatorApplyRectBrushStroke,
5499
- mutatorApplyRectPencil,
5500
- mutatorApplyRectPencilStroke,
5501
5095
  mutatorBlendColor,
5502
- mutatorBlendColorCircleMask,
5503
5096
  mutatorBlendPixel,
5504
5097
  mutatorBlendPixelData,
5505
5098
  mutatorBlendPixelDataAlphaMask,
@@ -5539,6 +5132,7 @@ export {
5539
5132
  subtractFast,
5540
5133
  subtractPerfect,
5541
5134
  toBlendModeIndexAndName,
5135
+ trimMaskRectBounds,
5542
5136
  trimRectBounds,
5543
5137
  uInt32ArrayToImageData,
5544
5138
  uInt32ArrayToImageDataLike,
@@ -5554,6 +5148,7 @@ export {
5554
5148
  writeImageDataBuffer,
5555
5149
  writeImageDataToClipboard,
5556
5150
  writeImgBlobToClipboard,
5151
+ writePaintBufferToPixelData,
5557
5152
  writePixelDataBuffer
5558
5153
  };
5559
5154
  //# sourceMappingURL=index.prod.js.map