pixel-data-js 0.24.0 → 0.25.2

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 (72) hide show
  1. package/dist/index.dev.cjs +1476 -1834
  2. package/dist/index.dev.cjs.map +1 -1
  3. package/dist/index.dev.js +1465 -1816
  4. package/dist/index.dev.js.map +1 -1
  5. package/dist/index.prod.cjs +1475 -1833
  6. package/dist/index.prod.cjs.map +1 -1
  7. package/dist/index.prod.d.ts +233 -310
  8. package/dist/index.prod.js +1465 -1816
  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/mutatorBlendPaintMask.ts +60 -0
  16. package/src/History/PixelMutator/mutatorBlendPixelData.ts +2 -2
  17. package/src/History/PixelMutator/mutatorBlendPixelDataAlphaMask.ts +2 -2
  18. package/src/History/PixelMutator/mutatorBlendPixelDataBinaryMask.ts +2 -2
  19. package/src/History/PixelMutator.ts +0 -20
  20. package/src/History/PixelPatchTiles.ts +2 -2
  21. package/src/History/PixelWriter.ts +132 -9
  22. package/src/Internal/helpers.ts +2 -0
  23. package/src/Paint/PaintBuffer.ts +269 -0
  24. package/src/{PixelTile/PaintBufferRenderer.ts → Paint/PaintBufferCanvasRenderer.ts} +13 -5
  25. package/src/Paint/makeCirclePaintAlphaMask.ts +41 -0
  26. package/src/{Mask/CircleBinaryMask.ts → Paint/makeCirclePaintBinaryMask.ts} +5 -6
  27. package/src/Paint/makePaintMask.ts +28 -0
  28. package/src/Paint/makeRectFalloffPaintAlphaMask.ts +47 -0
  29. package/src/PixelData/PixelBuffer32.ts +2 -2
  30. package/src/PixelData/PixelData.ts +1 -1
  31. package/src/PixelData/applyAlphaMaskToPixelData.ts +2 -2
  32. package/src/PixelData/applyBinaryMaskToPixelData.ts +2 -2
  33. package/src/PixelData/blendColorPixelData.ts +2 -2
  34. package/src/PixelData/blendColorPixelDataAlphaMask.ts +3 -3
  35. package/src/PixelData/blendColorPixelDataBinaryMask.ts +3 -3
  36. package/src/PixelData/blendPixel.ts +2 -2
  37. package/src/PixelData/blendPixelData.ts +3 -3
  38. package/src/PixelData/blendPixelDataAlphaMask.ts +3 -3
  39. package/src/PixelData/blendPixelDataBinaryMask.ts +3 -3
  40. package/src/PixelData/blendPixelDataPaintBuffer.ts +3 -3
  41. package/src/PixelData/clearPixelData.ts +2 -2
  42. package/src/PixelData/extractPixelData.ts +4 -4
  43. package/src/PixelData/extractPixelDataBuffer.ts +4 -4
  44. package/src/PixelData/fillPixelData.ts +5 -5
  45. package/src/PixelData/fillPixelDataBinaryMask.ts +3 -3
  46. package/src/PixelData/fillPixelDataFast.ts +5 -5
  47. package/src/PixelData/invertPixelData.ts +2 -2
  48. package/src/PixelData/pixelDataToAlphaMask.ts +2 -2
  49. package/src/PixelData/reflectPixelData.ts +3 -3
  50. package/src/PixelData/resamplePixelData.ts +2 -2
  51. package/src/PixelData/writePaintBufferToPixelData.ts +26 -0
  52. package/src/PixelData/writePixelDataBuffer.ts +5 -5
  53. package/src/Rect/trimMaskRectBounds.ts +121 -0
  54. package/src/Rect/trimRectBounds.ts +25 -116
  55. package/src/_types.ts +16 -15
  56. package/src/index.ts +11 -24
  57. package/src/History/PixelMutator/mutatorApplyCircleBrushStroke.ts +0 -182
  58. package/src/History/PixelMutator/mutatorApplyCirclePencil.ts +0 -59
  59. package/src/History/PixelMutator/mutatorApplyCirclePencilStroke.ts +0 -172
  60. package/src/History/PixelMutator/mutatorApplyRectBrush.ts +0 -64
  61. package/src/History/PixelMutator/mutatorApplyRectBrushStroke.ts +0 -184
  62. package/src/History/PixelMutator/mutatorApplyRectPencil.ts +0 -65
  63. package/src/History/PixelMutator/mutatorApplyRectPencilStroke.ts +0 -166
  64. package/src/History/PixelMutator/mutatorBlendColorCircleMask.ts +0 -71
  65. package/src/Mask/CircleAlphaMask.ts +0 -32
  66. package/src/PixelData/applyRectBrushToPixelData.ts +0 -98
  67. package/src/PixelData/blendColorPixelDataCircleMask.ts +0 -92
  68. package/src/PixelTile/PaintBuffer.ts +0 -122
  69. package/src/Rect/getCircleBrushOrPencilBounds.ts +0 -43
  70. package/src/Rect/getCircleBrushOrPencilStrokeBounds.ts +0 -24
  71. package/src/Rect/getRectBrushOrPencilBounds.ts +0 -38
  72. package/src/Rect/getRectBrushOrPencilStrokeBounds.ts +0 -26
@@ -23,6 +23,7 @@ __export(src_exports, {
23
23
  BASE_FAST_BLEND_MODE_FUNCTIONS: () => BASE_FAST_BLEND_MODE_FUNCTIONS,
24
24
  BASE_PERFECT_BLEND_MODE_FUNCTIONS: () => BASE_PERFECT_BLEND_MODE_FUNCTIONS,
25
25
  BaseBlendMode: () => BaseBlendMode,
26
+ CANVAS_COMPOSITE_MAP: () => CANVAS_COMPOSITE_MAP,
26
27
  CANVAS_CTX_FAILED: () => CANVAS_CTX_FAILED,
27
28
  HistoryManager: () => HistoryManager,
28
29
  IndexedImage: () => IndexedImage,
@@ -41,13 +42,11 @@ __export(src_exports, {
41
42
  applyBinaryMaskToAlphaMask: () => applyBinaryMaskToAlphaMask,
42
43
  applyBinaryMaskToPixelData: () => applyBinaryMaskToPixelData,
43
44
  applyPatchTiles: () => applyPatchTiles,
44
- applyRectBrushToPixelData: () => applyRectBrushToPixelData,
45
45
  base64DecodeArrayBuffer: () => base64DecodeArrayBuffer,
46
46
  base64EncodeArrayBuffer: () => base64EncodeArrayBuffer,
47
47
  blendColorPixelData: () => blendColorPixelData,
48
48
  blendColorPixelDataAlphaMask: () => blendColorPixelDataAlphaMask,
49
49
  blendColorPixelDataBinaryMask: () => blendColorPixelDataBinaryMask,
50
- blendColorPixelDataCircleMask: () => blendColorPixelDataCircleMask,
51
50
  blendPixel: () => blendPixel,
52
51
  blendPixelData: () => blendPixelData,
53
52
  blendPixelDataAlphaMask: () => blendPixelDataAlphaMask,
@@ -89,12 +88,8 @@ __export(src_exports, {
89
88
  fillPixelDataFast: () => fillPixelDataFast,
90
89
  floodFillSelection: () => floodFillSelection,
91
90
  forEachLinePoint: () => forEachLinePoint,
92
- getCircleBrushOrPencilBounds: () => getCircleBrushOrPencilBounds,
93
- getCircleBrushOrPencilStrokeBounds: () => getCircleBrushOrPencilStrokeBounds,
94
91
  getImageDataFromClipboard: () => getImageDataFromClipboard,
95
92
  getIndexedImageColorCounts: () => getIndexedImageColorCounts,
96
- getRectBrushOrPencilBounds: () => getRectBrushOrPencilBounds,
97
- getRectBrushOrPencilStrokeBounds: () => getRectBrushOrPencilStrokeBounds,
98
93
  getRectsBounds: () => getRectsBounds,
99
94
  getSupportedPixelFormats: () => getSupportedPixelFormats,
100
95
  hardLightFast: () => hardLightFast,
@@ -128,15 +123,18 @@ __export(src_exports, {
128
123
  makeBinaryMask: () => makeBinaryMask,
129
124
  makeBlendModeRegistry: () => makeBlendModeRegistry,
130
125
  makeCanvasFrameRenderer: () => makeCanvasFrameRenderer,
131
- makeCircleAlphaMask: () => makeCircleAlphaMask,
132
- makeCircleBinaryMask: () => makeCircleBinaryMask,
126
+ makeCirclePaintAlphaMask: () => makeCirclePaintAlphaMask,
127
+ makeCirclePaintBinaryMask: () => makeCirclePaintBinaryMask,
133
128
  makeFastBlendModeRegistry: () => makeFastBlendModeRegistry,
134
129
  makeFullPixelMutator: () => makeFullPixelMutator,
135
130
  makeHistoryAction: () => makeHistoryAction,
136
131
  makeImageDataLike: () => makeImageDataLike,
137
- makePaintBufferRenderer: () => makePaintBufferRenderer,
132
+ makePaintAlphaMask: () => makePaintAlphaMask,
133
+ makePaintBinaryMask: () => makePaintBinaryMask,
134
+ makePaintBufferCanvasRenderer: () => makePaintBufferCanvasRenderer,
138
135
  makePerfectBlendModeRegistry: () => makePerfectBlendModeRegistry,
139
136
  makePixelCanvas: () => makePixelCanvas,
137
+ makeRectFalloffPaintAlphaMask: () => makeRectFalloffPaintAlphaMask,
140
138
  makeReusableCanvas: () => makeReusableCanvas,
141
139
  makeReusableImageData: () => makeReusableImageData,
142
140
  makeReusableOffscreenCanvas: () => makeReusableOffscreenCanvas,
@@ -148,15 +146,8 @@ __export(src_exports, {
148
146
  multiplyPerfect: () => multiplyPerfect,
149
147
  mutatorApplyAlphaMask: () => mutatorApplyAlphaMask,
150
148
  mutatorApplyBinaryMask: () => mutatorApplyBinaryMask,
151
- mutatorApplyCircleBrushStroke: () => mutatorApplyCircleBrushStroke,
152
- mutatorApplyCirclePencil: () => mutatorApplyCirclePencil,
153
- mutatorApplyCirclePencilStroke: () => mutatorApplyCirclePencilStroke,
154
- mutatorApplyRectBrush: () => mutatorApplyRectBrush,
155
- mutatorApplyRectBrushStroke: () => mutatorApplyRectBrushStroke,
156
- mutatorApplyRectPencil: () => mutatorApplyRectPencil,
157
- mutatorApplyRectPencilStroke: () => mutatorApplyRectPencilStroke,
158
149
  mutatorBlendColor: () => mutatorBlendColor,
159
- mutatorBlendColorCircleMask: () => mutatorBlendColorCircleMask,
150
+ mutatorBlendPaintMask: () => mutatorBlendPaintMask,
160
151
  mutatorBlendPixel: () => mutatorBlendPixel,
161
152
  mutatorBlendPixelData: () => mutatorBlendPixelData,
162
153
  mutatorBlendPixelDataAlphaMask: () => mutatorBlendPixelDataAlphaMask,
@@ -196,6 +187,7 @@ __export(src_exports, {
196
187
  subtractFast: () => subtractFast,
197
188
  subtractPerfect: () => subtractPerfect,
198
189
  toBlendModeIndexAndName: () => toBlendModeIndexAndName,
190
+ trimMaskRectBounds: () => trimMaskRectBounds,
199
191
  trimRectBounds: () => trimRectBounds,
200
192
  uInt32ArrayToImageData: () => uInt32ArrayToImageData,
201
193
  uInt32ArrayToImageDataLike: () => uInt32ArrayToImageDataLike,
@@ -211,6 +203,7 @@ __export(src_exports, {
211
203
  writeImageDataBuffer: () => writeImageDataBuffer,
212
204
  writeImageDataToClipboard: () => writeImageDataToClipboard,
213
205
  writeImgBlobToClipboard: () => writeImgBlobToClipboard,
206
+ writePaintBufferToPixelData: () => writePaintBufferToPixelData,
214
207
  writePixelDataBuffer: () => writePixelDataBuffer
215
208
  });
216
209
  module.exports = __toCommonJS(src_exports);
@@ -449,8 +442,8 @@ function extractMaskBuffer(maskBuffer, maskWidth, xOrRect, y, w, h) {
449
442
  return out;
450
443
  }
451
444
 
452
- // src/Rect/trimRectBounds.ts
453
- function trimRectBounds(target, bounds) {
445
+ // src/Rect/trimMaskRectBounds.ts
446
+ function trimMaskRectBounds(target, bounds) {
454
447
  const originalX = target.x;
455
448
  const originalY = target.y;
456
449
  const originalW = target.w;
@@ -638,7 +631,7 @@ function floodFillSelection(img, startX, startY, {
638
631
  finalMask[my * sw + mx] = 1;
639
632
  }
640
633
  }
641
- trimRectBounds(selectionRect, {
634
+ trimMaskRectBounds(selectionRect, {
642
635
  x: 0,
643
636
  y: 0,
644
637
  w: width,
@@ -1750,6 +1743,24 @@ var getKeyByValue = (obj, value) => {
1750
1743
  var OFFSCREEN_CANVAS_CTX_FAILED = "Failed to create OffscreenCanvas context";
1751
1744
  var CANVAS_CTX_FAILED = "Failed to create Canvas context";
1752
1745
 
1746
+ // src/Canvas/canvas-blend-modes.ts
1747
+ var CANVAS_COMPOSITE_MAP = {
1748
+ [BaseBlendMode.overwrite]: "copy",
1749
+ [BaseBlendMode.sourceOver]: "source-over",
1750
+ [BaseBlendMode.darken]: "darken",
1751
+ [BaseBlendMode.multiply]: "multiply",
1752
+ [BaseBlendMode.colorBurn]: "color-burn",
1753
+ [BaseBlendMode.lighten]: "lighten",
1754
+ [BaseBlendMode.screen]: "screen",
1755
+ [BaseBlendMode.colorDodge]: "color-dodge",
1756
+ [BaseBlendMode.linearDodge]: "lighter",
1757
+ [BaseBlendMode.overlay]: "overlay",
1758
+ [BaseBlendMode.softLight]: "soft-light",
1759
+ [BaseBlendMode.hardLight]: "hard-light",
1760
+ [BaseBlendMode.difference]: "difference",
1761
+ [BaseBlendMode.exclusion]: "exclusion"
1762
+ };
1763
+
1753
1764
  // src/Canvas/ReusableCanvas.ts
1754
1765
  function makeReusableCanvas() {
1755
1766
  return makeReusableCanvasMeta((w, h) => {
@@ -2028,11 +2039,11 @@ var PixelAccumulator = class {
2028
2039
  * @param y pixel y coordinate
2029
2040
  */
2030
2041
  storePixelBeforeState(x, y) {
2031
- let shift = this.config.tileShift;
2032
- let columns = this.config.targetColumns;
2033
- let tx = x >> shift;
2034
- let ty = y >> shift;
2035
- let id = ty * columns + tx;
2042
+ const shift = this.config.tileShift;
2043
+ const columns = this.config.targetColumns;
2044
+ const tx = x >> shift;
2045
+ const ty = y >> shift;
2046
+ const id = ty * columns + tx;
2036
2047
  let tile = this.lookup[id];
2037
2048
  let added = false;
2038
2049
  if (!tile) {
@@ -2058,16 +2069,16 @@ var PixelAccumulator = class {
2058
2069
  * @param h pixel height
2059
2070
  */
2060
2071
  storeRegionBeforeState(x, y, w, h) {
2061
- let shift = this.config.tileShift;
2062
- let columns = this.config.targetColumns;
2063
- let startX = x >> shift;
2064
- let startY = y >> shift;
2065
- let endX = x + w - 1 >> shift;
2066
- let endY = y + h - 1 >> shift;
2067
- let startIndex = this.beforeTiles.length;
2072
+ const shift = this.config.tileShift;
2073
+ const columns = this.config.targetColumns;
2074
+ const startX = x >> shift;
2075
+ const startY = y >> shift;
2076
+ const endX = x + w - 1 >> shift;
2077
+ const endY = y + h - 1 >> shift;
2078
+ const startIndex = this.beforeTiles.length;
2068
2079
  for (let ty = startY; ty <= endY; ty++) {
2069
2080
  for (let tx = startX; tx <= endX; tx++) {
2070
- let id = ty * columns + tx;
2081
+ const id = ty * columns + tx;
2071
2082
  let tile = this.lookup[id];
2072
2083
  if (!tile) {
2073
2084
  tile = this.tilePool.getTile(id, tx, ty);
@@ -2079,7 +2090,7 @@ var PixelAccumulator = class {
2079
2090
  }
2080
2091
  return (didChange) => {
2081
2092
  if (!didChange) {
2082
- let length = this.beforeTiles.length;
2093
+ const length = this.beforeTiles.length;
2083
2094
  for (let i = startIndex; i < length; i++) {
2084
2095
  let t = this.beforeTiles[i];
2085
2096
  if (t) {
@@ -2092,15 +2103,34 @@ var PixelAccumulator = class {
2092
2103
  return didChange;
2093
2104
  };
2094
2105
  }
2106
+ storeTileBeforeState(id, tx, ty) {
2107
+ let tile = this.lookup[id];
2108
+ let added = false;
2109
+ if (!tile) {
2110
+ tile = this.tilePool.getTile(id, tx, ty);
2111
+ this.extractState(tile);
2112
+ this.lookup[id] = tile;
2113
+ this.beforeTiles.push(tile);
2114
+ added = true;
2115
+ }
2116
+ return (didChange) => {
2117
+ if (!didChange && added) {
2118
+ this.beforeTiles.pop();
2119
+ this.lookup[id] = void 0;
2120
+ this.tilePool.releaseTile(tile);
2121
+ }
2122
+ return didChange;
2123
+ };
2124
+ }
2095
2125
  extractState(tile) {
2096
- let target = this.config.target;
2097
- let TILE_SIZE = this.config.tileSize;
2098
- let dst = tile.data32;
2099
- let src = target.data32;
2100
- let startX = tile.tx * TILE_SIZE;
2101
- let startY = tile.ty * TILE_SIZE;
2102
- let targetWidth = target.width;
2103
- let targetHeight = target.height;
2126
+ const target = this.config.target;
2127
+ const TILE_SIZE = this.config.tileSize;
2128
+ const dst = tile.data32;
2129
+ const src = target.data32;
2130
+ const startX = tile.tx * TILE_SIZE;
2131
+ const startY = tile.ty * TILE_SIZE;
2132
+ const targetWidth = target.width;
2133
+ const targetHeight = target.height;
2104
2134
  if (startX >= targetWidth || startX + TILE_SIZE <= 0 || startY >= targetHeight || startY + TILE_SIZE <= 0) {
2105
2135
  dst.fill(0);
2106
2136
  return;
@@ -2126,8 +2156,8 @@ var PixelAccumulator = class {
2126
2156
  }
2127
2157
  }
2128
2158
  extractPatch() {
2129
- let afterTiles = [];
2130
- let length = this.beforeTiles.length;
2159
+ const afterTiles = [];
2160
+ const length = this.beforeTiles.length;
2131
2161
  for (let i = 0; i < length; i++) {
2132
2162
  let beforeTile = this.beforeTiles[i];
2133
2163
  if (beforeTile) {
@@ -2136,7 +2166,7 @@ var PixelAccumulator = class {
2136
2166
  afterTiles.push(afterTile);
2137
2167
  }
2138
2168
  }
2139
- let beforeTiles = this.beforeTiles;
2169
+ const beforeTiles = this.beforeTiles;
2140
2170
  this.beforeTiles = [];
2141
2171
  this.lookup.length = 0;
2142
2172
  return {
@@ -2144,10 +2174,10 @@ var PixelAccumulator = class {
2144
2174
  afterTiles
2145
2175
  };
2146
2176
  }
2147
- rollback() {
2148
- let target = this.config.target;
2149
- let tileSize = this.config.tileSize;
2150
- let length = this.beforeTiles.length;
2177
+ rollbackAfterError() {
2178
+ const target = this.config.target;
2179
+ const tileSize = this.config.tileSize;
2180
+ const length = this.beforeTiles.length;
2151
2181
  applyPatchTiles(target, this.beforeTiles, tileSize);
2152
2182
  for (let i = 0; i < length; i++) {
2153
2183
  let tile = this.beforeTiles[i];
@@ -2171,6 +2201,7 @@ var PixelEngineConfig = class {
2171
2201
  tileArea;
2172
2202
  target;
2173
2203
  targetColumns = 0;
2204
+ targetRows = 0;
2174
2205
  constructor(tileSize, target) {
2175
2206
  if ((tileSize & tileSize - 1) !== 0) {
2176
2207
  throw new Error("tileSize must be a power of 2");
@@ -2179,28 +2210,26 @@ var PixelEngineConfig = class {
2179
2210
  this.tileShift = 31 - Math.clz32(tileSize);
2180
2211
  this.tileMask = tileSize - 1;
2181
2212
  this.tileArea = tileSize * tileSize;
2182
- this.setTarget(target);
2183
- }
2184
- setTarget(target) {
2185
- ;
2186
2213
  this.target = target;
2187
2214
  this.targetColumns = target.width + this.tileMask >> this.tileShift;
2215
+ this.targetRows = target.height + this.tileMask >> this.tileShift;
2188
2216
  }
2189
2217
  };
2190
2218
 
2191
- // src/PixelData/applyAlphaMaskToPixelData.ts
2192
- function applyAlphaMaskToPixelData(dst, mask, opts = {}) {
2219
+ // src/PixelData/blendColorPixelData.ts
2220
+ function blendColorPixelData(dst, color, opts = {}) {
2193
2221
  const {
2194
2222
  x: targetX = 0,
2195
2223
  y: targetY = 0,
2196
2224
  w: width = dst.width,
2197
2225
  h: height = dst.height,
2198
2226
  alpha: globalAlpha = 255,
2199
- mx = 0,
2200
- my = 0,
2201
- invertMask = false
2227
+ blendFn = sourceOverPerfect
2202
2228
  } = opts;
2203
2229
  if (globalAlpha === 0) return false;
2230
+ const baseSrcAlpha = color >>> 24;
2231
+ const isOverwrite = blendFn.isOverwrite || false;
2232
+ if (baseSrcAlpha === 0 && !isOverwrite) return false;
2204
2233
  let x = targetX;
2205
2234
  let y = targetY;
2206
2235
  let w = width;
@@ -2213,82 +2242,46 @@ function applyAlphaMaskToPixelData(dst, mask, opts = {}) {
2213
2242
  h += y;
2214
2243
  y = 0;
2215
2244
  }
2216
- w = Math.min(w, dst.width - x);
2217
- h = Math.min(h, dst.height - y);
2218
- if (w <= 0) return false;
2219
- if (h <= 0) return false;
2220
- const mPitch = mask.w;
2221
- if (mPitch <= 0) return false;
2222
- const startX = mx + (x - targetX);
2223
- const startY = my + (y - targetY);
2224
- const sX0 = Math.max(0, startX);
2225
- const sY0 = Math.max(0, startY);
2226
- const sX1 = Math.min(mPitch, startX + w);
2227
- const sY1 = Math.min(mask.h, startY + h);
2228
- const finalW = sX1 - sX0;
2229
- const finalH = sY1 - sY0;
2230
- if (finalW <= 0) return false;
2231
- if (finalH <= 0) return false;
2232
- const xShift = sX0 - startX;
2233
- const yShift = sY0 - startY;
2245
+ const actualW = Math.min(w, dst.width - x);
2246
+ const actualH = Math.min(h, dst.height - y);
2247
+ if (actualW <= 0 || actualH <= 0) return false;
2248
+ let finalSrcColor = color;
2249
+ if (globalAlpha < 255) {
2250
+ const a = baseSrcAlpha * globalAlpha + 128 >> 8;
2251
+ if (a === 0 && !isOverwrite) return false;
2252
+ finalSrcColor = (color & 16777215 | a << 24) >>> 0;
2253
+ }
2234
2254
  const dst32 = dst.data32;
2235
2255
  const dw = dst.width;
2236
- const dStride = dw - finalW;
2237
- const mStride = mPitch - finalW;
2238
- const maskData = mask.data;
2239
- let dIdx = (y + yShift) * dw + (x + xShift);
2240
- let mIdx = sY0 * mPitch + sX0;
2256
+ let dIdx = y * dw + x | 0;
2257
+ const dStride = dw - actualW | 0;
2241
2258
  let didChange = false;
2242
- for (let iy = 0; iy < h; iy++) {
2243
- for (let ix = 0; ix < w; ix++) {
2244
- const mVal = maskData[mIdx];
2245
- const effectiveM = invertMask ? 255 - mVal : mVal;
2246
- let weight = 0;
2247
- if (effectiveM === 0) {
2248
- weight = 0;
2249
- } else if (effectiveM === 255) {
2250
- weight = globalAlpha;
2251
- } else if (globalAlpha === 255) {
2252
- weight = effectiveM;
2253
- } else {
2254
- weight = effectiveM * globalAlpha + 128 >> 8;
2255
- }
2256
- if (weight === 0) {
2257
- dst32[dIdx] = (dst32[dIdx] & 16777215) >>> 0;
2259
+ for (let iy = 0; iy < actualH; iy++) {
2260
+ for (let ix = 0; ix < actualW; ix++) {
2261
+ const current = dst32[dIdx];
2262
+ const next = blendFn(finalSrcColor, current);
2263
+ if (current !== next) {
2264
+ dst32[dIdx] = next;
2258
2265
  didChange = true;
2259
- } else if (weight !== 255) {
2260
- const d = dst32[dIdx];
2261
- const da = d >>> 24;
2262
- if (da !== 0) {
2263
- const finalAlpha = da === 255 ? weight : da * weight + 128 >> 8;
2264
- const current = dst32[dIdx];
2265
- const next = (d & 16777215 | finalAlpha << 24) >>> 0;
2266
- if (current !== next) {
2267
- dst32[dIdx] = next;
2268
- didChange = true;
2269
- }
2270
- }
2271
2266
  }
2272
2267
  dIdx++;
2273
- mIdx++;
2274
2268
  }
2275
2269
  dIdx += dStride;
2276
- mIdx += mStride;
2277
2270
  }
2278
2271
  return didChange;
2279
2272
  }
2280
2273
 
2281
- // src/History/PixelMutator/mutatorApplyAlphaMask.ts
2274
+ // src/History/PixelMutator/mutatorBlendColor.ts
2282
2275
  var defaults2 = {
2283
- applyAlphaMaskToPixelData
2276
+ blendColorPixelData
2284
2277
  };
2285
- var mutatorApplyAlphaMask = ((writer, deps = defaults2) => {
2278
+ var mutatorBlendColor = ((writer, deps = defaults2) => {
2286
2279
  const {
2287
- applyAlphaMaskToPixelData: applyAlphaMaskToPixelData2 = defaults2.applyAlphaMaskToPixelData
2280
+ blendColorPixelData: blendColorPixelData2 = defaults2.blendColorPixelData
2288
2281
  } = deps;
2289
2282
  return {
2290
- applyAlphaMask(mask, opts = {}) {
2291
- let target = writer.config.target;
2283
+ blendColor(color, opts = {}) {
2284
+ const target = writer.config.target;
2292
2285
  const {
2293
2286
  x = 0,
2294
2287
  y = 0,
@@ -2296,159 +2289,227 @@ var mutatorApplyAlphaMask = ((writer, deps = defaults2) => {
2296
2289
  h = target.height
2297
2290
  } = opts;
2298
2291
  const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2299
- return didChange(applyAlphaMaskToPixelData2(target, mask, opts));
2292
+ return didChange(blendColorPixelData2(target, color, opts));
2300
2293
  }
2301
2294
  };
2302
2295
  });
2303
2296
 
2304
- // src/PixelData/applyBinaryMaskToPixelData.ts
2305
- function applyBinaryMaskToPixelData(dst, mask, opts = {}) {
2297
+ // src/PixelData/blendPixel.ts
2298
+ function blendPixel(target, x, y, color, alpha = 255, blendFn = sourceOverPerfect) {
2299
+ if (alpha === 0) return false;
2300
+ let width = target.width;
2301
+ let height = target.height;
2302
+ if (x < 0 || x >= width || y < 0 || y >= height) return false;
2303
+ let srcAlpha = color >>> 24;
2304
+ let isOverwrite = blendFn.isOverwrite;
2305
+ if (srcAlpha === 0 && !isOverwrite) return false;
2306
+ let dst32 = target.data32;
2307
+ let index = y * width + x;
2308
+ let finalColor = color;
2309
+ if (alpha !== 255) {
2310
+ let finalAlpha = srcAlpha * alpha + 128 >> 8;
2311
+ if (finalAlpha === 0 && !isOverwrite) return false;
2312
+ finalColor = (color & 16777215 | finalAlpha << 24) >>> 0;
2313
+ }
2314
+ let current = dst32[index];
2315
+ let next = blendFn(finalColor, current);
2316
+ if (current !== next) {
2317
+ dst32[index] = next;
2318
+ return true;
2319
+ }
2320
+ return false;
2321
+ }
2322
+
2323
+ // src/History/PixelMutator/mutatorBlendPixel.ts
2324
+ var defaults3 = {
2325
+ blendPixel
2326
+ };
2327
+ var mutatorBlendPixel = ((writer, deps = defaults3) => {
2328
+ const {
2329
+ blendPixel: blendPixel2 = defaults3.blendPixel
2330
+ } = deps;
2331
+ return {
2332
+ blendPixel(x, y, color, alpha, blendFn) {
2333
+ const didChange = writer.accumulator.storePixelBeforeState(x, y);
2334
+ return didChange(blendPixel2(writer.config.target, x, y, color, alpha, blendFn));
2335
+ }
2336
+ };
2337
+ });
2338
+
2339
+ // src/PixelData/blendPixelData.ts
2340
+ function blendPixelData(dst, src, opts = {}) {
2306
2341
  const {
2307
2342
  x: targetX = 0,
2308
2343
  y: targetY = 0,
2309
- w: width = dst.width,
2310
- h: height = dst.height,
2344
+ sx: sourceX = 0,
2345
+ sy: sourceY = 0,
2346
+ w: width = src.width,
2347
+ h: height = src.height,
2311
2348
  alpha: globalAlpha = 255,
2312
- mx = 0,
2313
- my = 0,
2314
- invertMask = false
2349
+ blendFn = sourceOverPerfect
2315
2350
  } = opts;
2316
2351
  if (globalAlpha === 0) return false;
2317
2352
  let x = targetX;
2318
2353
  let y = targetY;
2354
+ let sx = sourceX;
2355
+ let sy = sourceY;
2319
2356
  let w = width;
2320
2357
  let h = height;
2358
+ if (sx < 0) {
2359
+ x -= sx;
2360
+ w += sx;
2361
+ sx = 0;
2362
+ }
2363
+ if (sy < 0) {
2364
+ y -= sy;
2365
+ h += sy;
2366
+ sy = 0;
2367
+ }
2368
+ w = Math.min(w, src.width - sx);
2369
+ h = Math.min(h, src.height - sy);
2321
2370
  if (x < 0) {
2371
+ sx -= x;
2322
2372
  w += x;
2323
2373
  x = 0;
2324
2374
  }
2325
2375
  if (y < 0) {
2376
+ sy -= y;
2326
2377
  h += y;
2327
2378
  y = 0;
2328
2379
  }
2329
- w = Math.min(w, dst.width - x);
2330
- h = Math.min(h, dst.height - y);
2331
- if (w <= 0 || h <= 0) return false;
2332
- const mPitch = mask.w;
2333
- if (mPitch <= 0) return false;
2334
- const startX = mx + (x - targetX);
2335
- const startY = my + (y - targetY);
2336
- const sX0 = Math.max(0, startX);
2337
- const sY0 = Math.max(0, startY);
2338
- const sX1 = Math.min(mPitch, startX + w);
2339
- const sY1 = Math.min(mask.h, startY + h);
2340
- const finalW = sX1 - sX0;
2341
- const finalH = sY1 - sY0;
2342
- if (finalW <= 0 || finalH <= 0) {
2343
- return false;
2344
- }
2345
- const xShift = sX0 - startX;
2346
- const yShift = sY0 - startY;
2380
+ const actualW = Math.min(w, dst.width - x);
2381
+ const actualH = Math.min(h, dst.height - y);
2382
+ if (actualW <= 0 || actualH <= 0) return false;
2347
2383
  const dst32 = dst.data32;
2384
+ const src32 = src.data32;
2348
2385
  const dw = dst.width;
2349
- const dStride = dw - finalW;
2350
- const mStride = mPitch - finalW;
2351
- const maskData = mask.data;
2352
- let dIdx = (y + yShift) * dw + (x + xShift);
2353
- let mIdx = sY0 * mPitch + sX0;
2386
+ const sw = src.width;
2387
+ let dIdx = y * dw + x | 0;
2388
+ let sIdx = sy * sw + sx | 0;
2389
+ const dStride = dw - actualW | 0;
2390
+ const sStride = sw - actualW | 0;
2391
+ const isOpaque = globalAlpha === 255;
2392
+ const isOverwrite = blendFn.isOverwrite;
2354
2393
  let didChange = false;
2355
- for (let iy = 0; iy < finalH; iy++) {
2356
- for (let ix = 0; ix < finalW; ix++) {
2357
- const mVal = maskData[mIdx];
2358
- const isMaskedOut = invertMask ? mVal !== 0 : mVal === 0;
2359
- if (isMaskedOut) {
2360
- const current = dst32[dIdx];
2361
- const next = (current & 16777215) >>> 0;
2362
- if (current !== next) {
2363
- dst32[dIdx] = next;
2364
- didChange = true;
2365
- }
2366
- } else if (globalAlpha !== 255) {
2367
- const d = dst32[dIdx];
2368
- const da = d >>> 24;
2369
- if (da !== 0) {
2370
- const finalAlpha = da === 255 ? globalAlpha : da * globalAlpha + 128 >> 8;
2371
- const next = (d & 16777215 | finalAlpha << 24) >>> 0;
2372
- if (d !== next) {
2373
- dst32[dIdx] = next;
2374
- didChange = true;
2375
- }
2394
+ for (let iy = 0; iy < actualH; iy++) {
2395
+ for (let ix = 0; ix < actualW; ix++) {
2396
+ const srcCol = src32[sIdx];
2397
+ const srcAlpha = srcCol >>> 24;
2398
+ if (srcAlpha === 0 && !isOverwrite) {
2399
+ dIdx++;
2400
+ sIdx++;
2401
+ continue;
2402
+ }
2403
+ let finalCol = srcCol;
2404
+ if (!isOpaque) {
2405
+ const a = srcAlpha * globalAlpha + 128 >> 8;
2406
+ if (a === 0 && !isOverwrite) {
2407
+ dIdx++;
2408
+ sIdx++;
2409
+ continue;
2376
2410
  }
2411
+ finalCol = (srcCol & 16777215 | a << 24) >>> 0;
2412
+ }
2413
+ const current = dst32[dIdx];
2414
+ const next = blendFn(finalCol, dst32[dIdx]);
2415
+ if (current !== next) {
2416
+ dst32[dIdx] = next;
2417
+ didChange = true;
2377
2418
  }
2378
2419
  dIdx++;
2379
- mIdx++;
2420
+ sIdx++;
2380
2421
  }
2381
2422
  dIdx += dStride;
2382
- mIdx += mStride;
2423
+ sIdx += sStride;
2383
2424
  }
2384
2425
  return didChange;
2385
2426
  }
2386
2427
 
2387
- // src/History/PixelMutator/mutatorApplyBinaryMask.ts
2388
- var defaults3 = {
2389
- applyBinaryMaskToPixelData
2428
+ // src/History/PixelMutator/mutatorBlendPixelData.ts
2429
+ var defaults4 = {
2430
+ blendPixelData
2390
2431
  };
2391
- var mutatorApplyBinaryMask = ((writer, deps = defaults3) => {
2432
+ var mutatorBlendPixelData = ((writer, deps = defaults4) => {
2392
2433
  const {
2393
- applyBinaryMaskToPixelData: applyBinaryMaskToPixelData2 = defaults3.applyBinaryMaskToPixelData
2434
+ blendPixelData: blendPixelData2 = defaults4.blendPixelData
2394
2435
  } = deps;
2395
2436
  return {
2396
- applyBinaryMask(mask, opts = {}) {
2397
- let target = writer.config.target;
2437
+ blendPixelData(src, opts = {}) {
2398
2438
  const {
2399
2439
  x = 0,
2400
2440
  y = 0,
2401
- w = target.width,
2402
- h = target.height
2441
+ w = src.width,
2442
+ h = src.height
2403
2443
  } = opts;
2404
2444
  const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2405
- return didChange(applyBinaryMaskToPixelData2(target, mask, opts));
2445
+ return didChange(blendPixelData2(writer.config.target, src, opts));
2406
2446
  }
2407
2447
  };
2408
2448
  });
2409
2449
 
2410
- // src/PixelData/blendColorPixelDataAlphaMask.ts
2411
- function blendColorPixelDataAlphaMask(dst, color, mask, opts = {}) {
2412
- const targetX = opts.x ?? 0;
2413
- const targetY = opts.y ?? 0;
2414
- const w = opts.w ?? mask.w;
2415
- const h = opts.h ?? mask.h;
2416
- const globalAlpha = opts.alpha ?? 255;
2417
- const blendFn = opts.blendFn ?? sourceOverPerfect;
2418
- const mx = opts.mx ?? 0;
2419
- const my = opts.my ?? 0;
2420
- const invertMask = opts.invertMask ?? false;
2450
+ // src/PixelData/blendPixelDataAlphaMask.ts
2451
+ function blendPixelDataAlphaMask(dst, src, alphaMask, opts = {}) {
2452
+ const {
2453
+ x: targetX = 0,
2454
+ y: targetY = 0,
2455
+ sx: sourceX = 0,
2456
+ sy: sourceY = 0,
2457
+ w: width = src.width,
2458
+ h: height = src.height,
2459
+ alpha: globalAlpha = 255,
2460
+ blendFn = sourceOverPerfect,
2461
+ mx = 0,
2462
+ my = 0,
2463
+ invertMask = false
2464
+ } = opts;
2421
2465
  if (globalAlpha === 0) return false;
2422
- const baseSrcAlpha = color >>> 24;
2423
- const isOverwrite = blendFn.isOverwrite || false;
2424
- if (baseSrcAlpha === 0 && !isOverwrite) return false;
2425
2466
  let x = targetX;
2426
2467
  let y = targetY;
2427
- let actualW = w;
2428
- let actualH = h;
2468
+ let sx = sourceX;
2469
+ let sy = sourceY;
2470
+ let w = width;
2471
+ let h = height;
2472
+ if (sx < 0) {
2473
+ x -= sx;
2474
+ w += sx;
2475
+ sx = 0;
2476
+ }
2477
+ if (sy < 0) {
2478
+ y -= sy;
2479
+ h += sy;
2480
+ sy = 0;
2481
+ }
2482
+ w = Math.min(w, src.width - sx);
2483
+ h = Math.min(h, src.height - sy);
2429
2484
  if (x < 0) {
2430
- actualW += x;
2485
+ sx -= x;
2486
+ w += x;
2431
2487
  x = 0;
2432
2488
  }
2433
2489
  if (y < 0) {
2434
- actualH += y;
2490
+ sy -= y;
2491
+ h += y;
2435
2492
  y = 0;
2436
2493
  }
2437
- actualW = Math.min(actualW, dst.width - x);
2438
- actualH = Math.min(actualH, dst.height - y);
2494
+ const actualW = Math.min(w, dst.width - x);
2495
+ const actualH = Math.min(h, dst.height - y);
2439
2496
  if (actualW <= 0 || actualH <= 0) return false;
2497
+ const dw = dst.width;
2498
+ const sw = src.width;
2499
+ const mPitch = alphaMask.w;
2500
+ const maskData = alphaMask.data;
2440
2501
  const dx = x - targetX | 0;
2441
2502
  const dy = y - targetY | 0;
2442
2503
  const dst32 = dst.data32;
2443
- const dw = dst.width;
2444
- const mPitch = mask.w;
2445
- const maskData = mask.data;
2504
+ const src32 = src.data32;
2446
2505
  let dIdx = y * dw + x | 0;
2506
+ let sIdx = sy * sw + sx | 0;
2447
2507
  let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
2448
2508
  const dStride = dw - actualW | 0;
2509
+ const sStride = sw - actualW | 0;
2449
2510
  const mStride = mPitch - actualW | 0;
2450
2511
  const isOpaque = globalAlpha === 255;
2451
- const colorRGB = color & 16777215;
2512
+ const isOverwrite = blendFn.isOverwrite || false;
2452
2513
  let didChange = false;
2453
2514
  for (let iy = 0; iy < actualH; iy++) {
2454
2515
  for (let ix = 0; ix < actualW; ix++) {
@@ -2456,6 +2517,15 @@ function blendColorPixelDataAlphaMask(dst, color, mask, opts = {}) {
2456
2517
  const effM = invertMask ? 255 - mVal : mVal;
2457
2518
  if (effM === 0) {
2458
2519
  dIdx++;
2520
+ sIdx++;
2521
+ mIdx++;
2522
+ continue;
2523
+ }
2524
+ const srcCol = src32[sIdx];
2525
+ const srcAlpha = srcCol >>> 24;
2526
+ if (srcAlpha === 0 && !isOverwrite) {
2527
+ dIdx++;
2528
+ sIdx++;
2459
2529
  mIdx++;
2460
2530
  continue;
2461
2531
  }
@@ -2467,844 +2537,459 @@ function blendColorPixelDataAlphaMask(dst, color, mask, opts = {}) {
2467
2537
  }
2468
2538
  if (weight === 0) {
2469
2539
  dIdx++;
2540
+ sIdx++;
2470
2541
  mIdx++;
2471
2542
  continue;
2472
2543
  }
2473
- let finalCol = color;
2544
+ let finalCol = srcCol;
2474
2545
  if (weight < 255) {
2475
- const a = baseSrcAlpha * weight + 128 >> 8;
2546
+ const a = srcAlpha * weight + 128 >> 8;
2476
2547
  if (a === 0 && !isOverwrite) {
2477
2548
  dIdx++;
2549
+ sIdx++;
2478
2550
  mIdx++;
2479
2551
  continue;
2480
2552
  }
2481
- finalCol = (colorRGB | a << 24) >>> 0;
2553
+ finalCol = (srcCol & 16777215 | a << 24) >>> 0;
2482
2554
  }
2483
2555
  const current = dst32[dIdx];
2484
- const next = blendFn(finalCol, current);
2556
+ const next = blendFn(finalCol, dst32[dIdx]);
2485
2557
  if (current !== next) {
2486
2558
  dst32[dIdx] = next;
2487
2559
  didChange = true;
2488
2560
  }
2489
2561
  dIdx++;
2562
+ sIdx++;
2490
2563
  mIdx++;
2491
2564
  }
2492
2565
  dIdx += dStride;
2566
+ sIdx += sStride;
2493
2567
  mIdx += mStride;
2494
2568
  }
2495
2569
  return didChange;
2496
2570
  }
2497
2571
 
2498
- // src/Rect/getCircleBrushOrPencilBounds.ts
2499
- function getCircleBrushOrPencilBounds(centerX, centerY, brushSize, targetWidth, targetHeight, out) {
2500
- const r = brushSize / 2;
2501
- const minOffset = -Math.ceil(r - 0.5);
2502
- const maxOffset = Math.floor(r - 0.5);
2503
- const startX = Math.floor(centerX + minOffset);
2504
- const startY = Math.floor(centerY + minOffset);
2505
- const endX = Math.floor(centerX + maxOffset) + 1;
2506
- const endY = Math.floor(centerY + maxOffset) + 1;
2507
- const res = out ?? {
2508
- x: 0,
2509
- y: 0,
2510
- w: 0,
2511
- h: 0
2512
- };
2513
- const cStartX = Math.max(0, startX);
2514
- const cStartY = Math.max(0, startY);
2515
- const cEndX = Math.min(targetWidth, endX);
2516
- const cEndY = Math.min(targetHeight, endY);
2517
- const w = cEndX - cStartX;
2518
- const h = cEndY - cStartY;
2519
- res.x = cStartX;
2520
- res.y = cStartY;
2521
- res.w = w < 0 ? 0 : w;
2522
- res.h = h < 0 ? 0 : h;
2523
- return res;
2524
- }
2525
-
2526
- // src/Rect/getCircleBrushOrPencilStrokeBounds.ts
2527
- function getCircleBrushOrPencilStrokeBounds(x0, y0, x1, y1, brushSize, result) {
2528
- const r = Math.ceil(brushSize / 2);
2529
- const minX = Math.min(x0, x1) - r;
2530
- const minY = Math.min(y0, y1) - r;
2531
- const maxX = Math.max(x0, x1) + r;
2532
- const maxY = Math.max(x0, y1) + r;
2533
- result.x = Math.floor(minX);
2534
- result.y = Math.floor(minY);
2535
- result.w = Math.ceil(maxX - minX);
2536
- result.h = Math.ceil(maxY - minY);
2537
- return result;
2538
- }
2539
-
2540
- // src/History/PixelMutator/mutatorApplyCircleBrushStroke.ts
2541
- var defaults4 = {
2542
- forEachLinePoint,
2543
- blendColorPixelDataAlphaMask,
2544
- getCircleBrushOrPencilBounds,
2545
- getCircleBrushOrPencilStrokeBounds
2572
+ // src/History/PixelMutator/mutatorBlendPixelDataAlphaMask.ts
2573
+ var defaults5 = {
2574
+ blendPixelDataAlphaMask
2546
2575
  };
2547
- var mutatorApplyCircleBrushStroke = ((writer, deps = defaults4) => {
2576
+ var mutatorBlendPixelDataAlphaMask = ((writer, deps = defaults5) => {
2548
2577
  const {
2549
- forEachLinePoint: forEachLinePoint2 = defaults4.forEachLinePoint,
2550
- blendColorPixelDataAlphaMask: blendColorPixelDataAlphaMask2 = defaults4.blendColorPixelDataAlphaMask,
2551
- getCircleBrushOrPencilBounds: getCircleBrushOrPencilBounds2 = defaults4.getCircleBrushOrPencilBounds,
2552
- getCircleBrushOrPencilStrokeBounds: getCircleBrushOrPencilStrokeBounds2 = defaults4.getCircleBrushOrPencilStrokeBounds
2578
+ blendPixelDataAlphaMask: blendPixelDataAlphaMask2 = defaults5.blendPixelDataAlphaMask
2553
2579
  } = deps;
2554
- const strokeBoundsOut = {
2555
- x: 0,
2556
- y: 0,
2557
- w: 0,
2558
- h: 0
2559
- };
2560
- const circleBrushBounds = {
2561
- x: 0,
2562
- y: 0,
2563
- w: 0,
2564
- h: 0
2565
- };
2566
- const blendColorPixelOptions = {
2567
- alpha: 255,
2568
- blendFn: sourceOverPerfect,
2569
- x: 0,
2570
- y: 0,
2571
- w: 0,
2572
- h: 0
2573
- };
2574
- const mask = {
2575
- type: 0 /* ALPHA */,
2576
- data: null,
2577
- w: 0,
2578
- h: 0
2579
- };
2580
2580
  return {
2581
- applyCircleBrushStroke(color, x0, y0, x1, y1, brush, alpha = 255, blendFn = sourceOverPerfect) {
2582
- const brushSize = brush.size;
2583
- const {
2584
- x: bx,
2585
- y: by,
2586
- w: bw,
2587
- h: bh
2588
- } = getCircleBrushOrPencilStrokeBounds2(x0, y0, x1, y1, brushSize, strokeBoundsOut);
2589
- if (bw <= 0 || bh <= 0) return;
2590
- mask.data = new Uint8Array(bw * bh);
2591
- mask.w = bw;
2592
- mask.h = bh;
2593
- const maskData = mask.data;
2594
- const brushData = brush.data;
2595
- const minOffset = brush.minOffset;
2596
- const target = writer.config.target;
2597
- const targetWidth = target.width;
2598
- const targetHeight = target.height;
2599
- forEachLinePoint2(x0, y0, x1, y1, (px, py) => {
2600
- const {
2601
- x: cbx,
2602
- y: cby,
2603
- w: cbw,
2604
- h: cbh
2605
- } = getCircleBrushOrPencilBounds2(px, py, brushSize, targetWidth, targetHeight, circleBrushBounds);
2606
- writer.accumulator.storeRegionBeforeState(cbx, cby, cbw, cbh);
2607
- const startX = Math.max(bx, cbx);
2608
- const startY = Math.max(by, cby);
2609
- const endX = Math.min(bx + bw, cbx + cbw);
2610
- const endY = Math.min(by + bh, cby + cbh);
2611
- const unclippedStartX = Math.floor(px + minOffset);
2612
- const unclippedStartY = Math.floor(py + minOffset);
2613
- for (let my = startY; my < endY; my++) {
2614
- const strokeMaskY = my - by;
2615
- const strokeMaskRowOffset = strokeMaskY * bw;
2616
- const brushY = my - unclippedStartY;
2617
- const brushRowOffset = brushY * brushSize;
2618
- for (let mx = startX; mx < endX; mx++) {
2619
- const brushX = mx - unclippedStartX;
2620
- const brushVal = brushData[brushRowOffset + brushX];
2621
- if (brushVal > 0) {
2622
- const strokeMaskIdx = strokeMaskRowOffset + (mx - bx);
2623
- if (brushVal > maskData[strokeMaskIdx]) {
2624
- maskData[strokeMaskIdx] = brushVal;
2625
- }
2626
- }
2627
- }
2628
- }
2629
- });
2630
- blendColorPixelOptions.blendFn = blendFn;
2631
- blendColorPixelOptions.alpha = alpha;
2632
- blendColorPixelOptions.x = bx;
2633
- blendColorPixelOptions.y = by;
2634
- blendColorPixelOptions.w = bw;
2635
- blendColorPixelOptions.h = bh;
2636
- blendColorPixelDataAlphaMask2(target, color, mask, blendColorPixelOptions);
2581
+ blendPixelDataAlphaMask(src, mask, opts = {}) {
2582
+ const x = opts.x ?? 0;
2583
+ const y = opts.y ?? 0;
2584
+ const w = opts.w ?? src.width;
2585
+ const h = opts.h ?? src.height;
2586
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2587
+ return didChange(blendPixelDataAlphaMask2(writer.config.target, src, mask, opts));
2637
2588
  }
2638
2589
  };
2639
2590
  });
2640
2591
 
2641
- // src/PixelData/blendColorPixelDataBinaryMask.ts
2642
- function blendColorPixelDataBinaryMask(dst, color, mask, opts = {}) {
2643
- const targetX = opts.x ?? 0;
2644
- const targetY = opts.y ?? 0;
2645
- let w = opts.w ?? mask.w;
2646
- let h = opts.h ?? mask.h;
2647
- const globalAlpha = opts.alpha ?? 255;
2648
- const blendFn = opts.blendFn ?? sourceOverPerfect;
2649
- const mx = opts.mx ?? 0;
2650
- const my = opts.my ?? 0;
2651
- const invertMask = opts.invertMask ?? false;
2592
+ // src/PixelData/blendPixelDataBinaryMask.ts
2593
+ function blendPixelDataBinaryMask(dst, src, binaryMask, opts = {}) {
2594
+ const {
2595
+ x: targetX = 0,
2596
+ y: targetY = 0,
2597
+ sx: sourceX = 0,
2598
+ sy: sourceY = 0,
2599
+ w: width = src.width,
2600
+ h: height = src.height,
2601
+ alpha: globalAlpha = 255,
2602
+ blendFn = sourceOverPerfect,
2603
+ mx = 0,
2604
+ my = 0,
2605
+ invertMask = false
2606
+ } = opts;
2652
2607
  if (globalAlpha === 0) return false;
2653
- const baseSrcAlpha = color >>> 24;
2654
- const isOverwrite = blendFn.isOverwrite || false;
2655
- if (baseSrcAlpha === 0 && !isOverwrite) return false;
2656
2608
  let x = targetX;
2657
2609
  let y = targetY;
2610
+ let sx = sourceX;
2611
+ let sy = sourceY;
2612
+ let w = width;
2613
+ let h = height;
2614
+ if (sx < 0) {
2615
+ x -= sx;
2616
+ w += sx;
2617
+ sx = 0;
2618
+ }
2619
+ if (sy < 0) {
2620
+ y -= sy;
2621
+ h += sy;
2622
+ sy = 0;
2623
+ }
2624
+ w = Math.min(w, src.width - sx);
2625
+ h = Math.min(h, src.height - sy);
2658
2626
  if (x < 0) {
2627
+ sx -= x;
2659
2628
  w += x;
2660
2629
  x = 0;
2661
2630
  }
2662
2631
  if (y < 0) {
2632
+ sy -= y;
2663
2633
  h += y;
2664
2634
  y = 0;
2665
2635
  }
2666
2636
  const actualW = Math.min(w, dst.width - x);
2667
2637
  const actualH = Math.min(h, dst.height - y);
2668
2638
  if (actualW <= 0 || actualH <= 0) return false;
2669
- let baseColorWithGlobalAlpha = color;
2670
- if (globalAlpha < 255) {
2671
- const a = baseSrcAlpha * globalAlpha + 128 >> 8;
2672
- if (a === 0 && !isOverwrite) return false;
2673
- baseColorWithGlobalAlpha = (color & 16777215 | a << 24) >>> 0;
2674
- }
2675
2639
  const dx = x - targetX | 0;
2676
2640
  const dy = y - targetY | 0;
2677
2641
  const dst32 = dst.data32;
2642
+ const src32 = src.data32;
2678
2643
  const dw = dst.width;
2679
- const mPitch = mask.w;
2680
- const maskData = mask.data;
2644
+ const sw = src.width;
2645
+ const mPitch = binaryMask.w;
2646
+ const maskData = binaryMask.data;
2681
2647
  let dIdx = y * dw + x | 0;
2648
+ let sIdx = sy * sw + sx | 0;
2682
2649
  let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
2683
2650
  const dStride = dw - actualW | 0;
2651
+ const sStride = sw - actualW | 0;
2684
2652
  const mStride = mPitch - actualW | 0;
2685
2653
  const skipVal = invertMask ? 1 : 0;
2654
+ const isOpaque = globalAlpha === 255;
2655
+ const isOverwrite = blendFn.isOverwrite || false;
2686
2656
  let didChange = false;
2687
2657
  for (let iy = 0; iy < actualH; iy++) {
2688
2658
  for (let ix = 0; ix < actualW; ix++) {
2689
2659
  if (maskData[mIdx] === skipVal) {
2690
2660
  dIdx++;
2661
+ sIdx++;
2662
+ mIdx++;
2663
+ continue;
2664
+ }
2665
+ const srcCol = src32[sIdx];
2666
+ const srcAlpha = srcCol >>> 24;
2667
+ if (srcAlpha === 0 && !isOverwrite) {
2668
+ dIdx++;
2669
+ sIdx++;
2691
2670
  mIdx++;
2692
2671
  continue;
2693
2672
  }
2673
+ let finalCol = srcCol;
2674
+ if (!isOpaque) {
2675
+ const a = srcAlpha * globalAlpha + 128 >> 8;
2676
+ if (a === 0 && !isOverwrite) {
2677
+ dIdx++;
2678
+ sIdx++;
2679
+ mIdx++;
2680
+ continue;
2681
+ }
2682
+ finalCol = (srcCol & 16777215 | a << 24) >>> 0;
2683
+ }
2694
2684
  const current = dst32[dIdx];
2695
- const next = blendFn(baseColorWithGlobalAlpha, current);
2685
+ const next = blendFn(finalCol, dst32[dIdx]);
2696
2686
  if (current !== next) {
2697
2687
  dst32[dIdx] = next;
2698
2688
  didChange = true;
2699
2689
  }
2700
2690
  dIdx++;
2691
+ sIdx++;
2701
2692
  mIdx++;
2702
2693
  }
2703
2694
  dIdx += dStride;
2695
+ sIdx += sStride;
2704
2696
  mIdx += mStride;
2705
2697
  }
2706
2698
  return didChange;
2707
2699
  }
2708
2700
 
2709
- // src/PixelData/blendColorPixelDataCircleMask.ts
2710
- function blendColorPixelDataCircleMask(target, color, centerX, centerY, brush, alpha = 255, blendFn = sourceOverPerfect, scratchOptions = {}, bounds) {
2711
- const b = bounds ?? getCircleBrushOrPencilBounds(centerX, centerY, brush.size, target.width, target.height);
2712
- if (b.w <= 0 || b.h <= 0) return false;
2713
- const unclippedStartX = Math.floor(centerX + brush.minOffset);
2714
- const unclippedStartY = Math.floor(centerY + brush.minOffset);
2715
- const ix = Math.max(unclippedStartX, b.x);
2716
- const iy = Math.max(unclippedStartY, b.y);
2717
- const ir = Math.min(unclippedStartX + brush.w, b.x + b.w);
2718
- const ib = Math.min(unclippedStartY + brush.h, b.y + b.h);
2719
- const iw = ir - ix;
2720
- const ih = ib - iy;
2721
- if (iw <= 0 || ih <= 0) return false;
2722
- scratchOptions.x = ix;
2723
- scratchOptions.y = iy;
2724
- scratchOptions.w = iw;
2725
- scratchOptions.h = ih;
2726
- scratchOptions.mx = ix - unclippedStartX;
2727
- scratchOptions.my = iy - unclippedStartY;
2728
- scratchOptions.alpha = alpha;
2729
- scratchOptions.blendFn = blendFn;
2730
- if (brush.type === 0 /* ALPHA */) {
2731
- return blendColorPixelDataAlphaMask(target, color, brush, scratchOptions);
2732
- }
2733
- if (brush.type === 1 /* BINARY */) {
2734
- return blendColorPixelDataBinaryMask(target, color, brush, scratchOptions);
2735
- }
2736
- return false;
2737
- }
2738
-
2739
- // src/History/PixelMutator/mutatorBlendColorCircleMask.ts
2740
- var defaults5 = {
2741
- blendColorPixelDataCircleMask,
2742
- getCircleBrushOrPencilBounds
2701
+ // src/History/PixelMutator/mutatorBlendPixelDataBinaryMask.ts
2702
+ var defaults6 = {
2703
+ blendPixelDataBinaryMask
2743
2704
  };
2744
- var mutatorBlendColorCircleMask = ((writer, deps = defaults5) => {
2705
+ var mutatorBlendPixelDataBinaryMask = ((writer, deps = defaults6) => {
2745
2706
  const {
2746
- blendColorPixelDataCircleMask: blendColorPixelDataCircleMask2 = defaults5.blendColorPixelDataCircleMask,
2747
- getCircleBrushOrPencilBounds: getCircleBrushOrPencilBounds2 = defaults5.getCircleBrushOrPencilBounds
2707
+ blendPixelDataBinaryMask: blendPixelDataBinaryMask2 = defaults6.blendPixelDataBinaryMask
2748
2708
  } = deps;
2749
- const boundsOut = {
2750
- x: 0,
2751
- y: 0,
2752
- w: 0,
2753
- h: 0
2754
- };
2755
- const blendColorPixelOptions = {
2756
- alpha: 255,
2757
- blendFn: sourceOverPerfect,
2758
- x: 0,
2759
- y: 0,
2760
- w: 0,
2761
- h: 0
2762
- };
2763
2709
  return {
2764
- applyCircleMask(color, centerX, centerY, brush, alpha = 255, blendFn) {
2765
- const target = writer.config.target;
2766
- const b = getCircleBrushOrPencilBounds2(centerX, centerY, brush.size, target.width, target.height, boundsOut);
2767
- const didChange = writer.accumulator.storeRegionBeforeState(b.x, b.y, b.w, b.h);
2768
- return didChange(blendColorPixelDataCircleMask2(target, color, centerX, centerY, brush, alpha, blendFn, blendColorPixelOptions, b));
2710
+ blendPixelDataBinaryMask(src, mask, opts = {}) {
2711
+ const x = opts.x ?? 0;
2712
+ const y = opts.y ?? 0;
2713
+ const w = opts.w ?? src.width;
2714
+ const h = opts.h ?? src.height;
2715
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2716
+ return didChange(blendPixelDataBinaryMask2(writer.config.target, src, mask, opts));
2769
2717
  }
2770
2718
  };
2771
2719
  });
2772
2720
 
2773
- // src/History/PixelMutator/mutatorApplyCirclePencil.ts
2774
- var defaults6 = {
2775
- applyCircleMaskToPixelData: blendColorPixelDataCircleMask,
2776
- getCircleBrushOrPencilBounds
2777
- };
2778
- var mutatorApplyCirclePencil = ((writer, deps = defaults6) => {
2721
+ // src/PixelData/fillPixelDataFast.ts
2722
+ var SCRATCH_RECT = makeClippedRect();
2723
+ function fillPixelDataFast(dst, color, _x, _y, _w, _h) {
2724
+ let x;
2725
+ let y;
2726
+ let w;
2727
+ let h;
2728
+ if (typeof _x === "object") {
2729
+ x = _x.x ?? 0;
2730
+ y = _x.y ?? 0;
2731
+ w = _x.w ?? dst.width;
2732
+ h = _x.h ?? dst.height;
2733
+ } else if (typeof _x === "number") {
2734
+ x = _x;
2735
+ y = _y;
2736
+ w = _w;
2737
+ h = _h;
2738
+ } else {
2739
+ x = 0;
2740
+ y = 0;
2741
+ w = dst.width;
2742
+ h = dst.height;
2743
+ }
2744
+ const clip = resolveRectClipping(x, y, w, h, dst.width, dst.height, SCRATCH_RECT);
2745
+ if (!clip.inBounds) return;
2779
2746
  const {
2780
- applyCircleMaskToPixelData = defaults6.applyCircleMaskToPixelData,
2781
- getCircleBrushOrPencilBounds: getCircleBrushOrPencilBounds2 = defaults6.getCircleBrushOrPencilBounds
2782
- } = deps;
2783
- const boundsOut = {
2784
- x: 0,
2785
- y: 0,
2786
- w: 0,
2787
- h: 0
2788
- };
2789
- return {
2790
- applyCirclePencil(color, centerX, centerY, brush, alpha = 255, blendFn) {
2791
- const target = writer.config.target;
2792
- const b = getCircleBrushOrPencilBounds2(centerX, centerY, brush.size, target.width, target.height, boundsOut);
2793
- const didChange = writer.accumulator.storeRegionBeforeState(b.x, b.y, b.w, b.h);
2794
- return didChange(applyCircleMaskToPixelData(target, color, centerX, centerY, brush, alpha, blendFn, b));
2795
- }
2796
- };
2797
- });
2747
+ x: finalX,
2748
+ y: finalY,
2749
+ w: actualW,
2750
+ h: actualH
2751
+ } = clip;
2752
+ const dst32 = dst.data32;
2753
+ const dw = dst.width;
2754
+ if (actualW === dw && actualH === dst.height && finalX === 0 && finalY === 0) {
2755
+ dst32.fill(color);
2756
+ return;
2757
+ }
2758
+ for (let iy = 0; iy < actualH; iy++) {
2759
+ const start = (finalY + iy) * dw + finalX;
2760
+ const end = start + actualW;
2761
+ dst32.fill(color, start, end);
2762
+ }
2763
+ }
2798
2764
 
2799
- // src/History/PixelMutator/mutatorApplyCirclePencilStroke.ts
2765
+ // src/History/PixelMutator/mutatorClear.ts
2800
2766
  var defaults7 = {
2801
- forEachLinePoint,
2802
- blendColorPixelDataBinaryMask,
2803
- getCircleBrushOrPencilBounds,
2804
- getCircleBrushOrPencilStrokeBounds
2767
+ fillPixelData: fillPixelDataFast
2805
2768
  };
2806
- var mutatorApplyCirclePencilStroke = ((writer, deps = defaults7) => {
2769
+ var mutatorClear = ((writer, deps = defaults7) => {
2807
2770
  const {
2808
- forEachLinePoint: forEachLinePoint2 = defaults7.forEachLinePoint,
2809
- blendColorPixelDataBinaryMask: blendColorPixelDataBinaryMask2 = defaults7.blendColorPixelDataBinaryMask,
2810
- getCircleBrushOrPencilStrokeBounds: getCircleBrushOrPencilStrokeBounds2 = defaults7.getCircleBrushOrPencilStrokeBounds,
2811
- getCircleBrushOrPencilBounds: getCircleBrushOrPencilBounds2 = defaults7.getCircleBrushOrPencilBounds
2771
+ fillPixelData: fillPixelData2 = defaults7.fillPixelData
2812
2772
  } = deps;
2813
- const strokeBoundsOut = {
2814
- x: 0,
2815
- y: 0,
2816
- w: 0,
2817
- h: 0
2818
- };
2819
- const circlePencilBounds = {
2820
- x: 0,
2821
- y: 0,
2822
- w: 0,
2823
- h: 0
2824
- };
2825
- const blendColorPixelOptions = {
2826
- alpha: 255,
2827
- blendFn: sourceOverPerfect,
2828
- x: 0,
2829
- y: 0,
2830
- w: 0,
2831
- h: 0
2832
- };
2833
- const mask = {
2834
- type: 1 /* BINARY */,
2835
- data: null,
2836
- w: 0,
2837
- h: 0
2838
- };
2839
2773
  return {
2840
- applyCirclePencilStroke(color, x0, y0, x1, y1, brush, alpha = 255, blendFn = sourceOverPerfect) {
2841
- const {
2842
- x: bx,
2843
- y: by,
2844
- w: bw,
2845
- h: bh
2846
- } = getCircleBrushOrPencilStrokeBounds2(x0, y0, x1, y1, brush.size, strokeBoundsOut);
2847
- if (bw <= 0 || bh <= 0) return;
2848
- mask.data = new Uint8Array(bw * bh);
2849
- mask.w = bw;
2850
- mask.h = bh;
2851
- const maskData = mask.data;
2774
+ clear(rect = {}) {
2852
2775
  const target = writer.config.target;
2853
- const targetWidth = target.width;
2854
- const targetHeight = target.height;
2855
- forEachLinePoint2(x0, y0, x1, y1, (px, py) => {
2856
- const {
2857
- x: cbx,
2858
- y: cby,
2859
- w: cbw,
2860
- h: cbh
2861
- } = getCircleBrushOrPencilBounds2(px, py, brush.size, targetWidth, targetHeight, circlePencilBounds);
2862
- writer.accumulator.storeRegionBeforeState(cbx, cby, cbw, cbh);
2863
- const unclippedStartX = Math.floor(px + brush.minOffset);
2864
- const unclippedStartY = Math.floor(py + brush.minOffset);
2865
- const startX = Math.max(bx, unclippedStartX);
2866
- const startY = Math.max(by, unclippedStartY);
2867
- const endX = Math.min(bx + bw, unclippedStartX + brush.w);
2868
- const endY = Math.min(by + bh, unclippedStartY + brush.h);
2869
- for (let my = startY; my < endY; my++) {
2870
- const brushY = my - unclippedStartY;
2871
- const maskRowOffset = (my - by) * bw;
2872
- const brushRowOffset = brushY * brush.w;
2873
- for (let mx = startX; mx < endX; mx++) {
2874
- const brushX = mx - unclippedStartX;
2875
- const brushAlpha = brush.data[brushRowOffset + brushX];
2876
- if (brushAlpha > 0) {
2877
- const maskIdx = maskRowOffset + (mx - bx);
2878
- maskData[maskIdx] = brushAlpha;
2879
- }
2880
- }
2881
- }
2882
- });
2883
- blendColorPixelOptions.blendFn = blendFn;
2884
- blendColorPixelOptions.alpha = alpha;
2885
- blendColorPixelOptions.x = bx;
2886
- blendColorPixelOptions.y = by;
2887
- blendColorPixelOptions.w = bw;
2888
- blendColorPixelOptions.h = bh;
2889
- blendColorPixelDataBinaryMask2(target, color, mask, blendColorPixelOptions);
2776
+ const x = rect.x ?? 0;
2777
+ const y = rect.y ?? 0;
2778
+ const w = rect.w ?? target.width;
2779
+ const h = rect.h ?? target.height;
2780
+ writer.accumulator.storeRegionBeforeState(x, y, w, h);
2781
+ fillPixelData2(target, 0, x, y, w, h);
2890
2782
  }
2891
2783
  };
2892
2784
  });
2893
2785
 
2894
- // src/Rect/getRectBrushOrPencilBounds.ts
2895
- function getRectBrushOrPencilBounds(centerX, centerY, brushWidth, brushHeight, targetWidth, targetHeight, out) {
2896
- const startX = Math.floor(centerX - brushWidth / 2);
2897
- const startY = Math.floor(centerY - brushHeight / 2);
2898
- const endX = startX + brushWidth;
2899
- const endY = startY + brushHeight;
2900
- const res = out ?? {
2901
- x: 0,
2902
- y: 0,
2903
- w: 0,
2904
- h: 0
2905
- };
2906
- const cStartX = Math.max(0, startX);
2907
- const cStartY = Math.max(0, startY);
2908
- const cEndX = Math.min(targetWidth, endX);
2909
- const cEndY = Math.min(targetHeight, endY);
2910
- const w = cEndX - cStartX;
2911
- const h = cEndY - cStartY;
2912
- res.x = cStartX;
2913
- res.y = cStartY;
2914
- res.w = w < 0 ? 0 : w;
2915
- res.h = h < 0 ? 0 : h;
2916
- return res;
2917
- }
2918
-
2919
- // src/PixelData/applyRectBrushToPixelData.ts
2920
- function applyRectBrushToPixelData(target, color, centerX, centerY, brushWidth, brushHeight, alpha = 255, fallOff, blendFn = sourceOverPerfect, bounds) {
2921
- const targetWidth = target.width;
2922
- const targetHeight = target.height;
2923
- const b = bounds ?? getRectBrushOrPencilBounds(centerX, centerY, brushWidth, brushHeight, targetWidth, targetHeight);
2924
- if (b.w <= 0 || b.h <= 0) {
2925
- return false;
2786
+ // src/PixelData/fillPixelData.ts
2787
+ var SCRATCH_RECT2 = makeClippedRect();
2788
+ function fillPixelData(dst, color, _x, _y, _w, _h) {
2789
+ let x;
2790
+ let y;
2791
+ let w;
2792
+ let h;
2793
+ if (typeof _x === "object") {
2794
+ x = _x.x ?? 0;
2795
+ y = _x.y ?? 0;
2796
+ w = _x.w ?? dst.width;
2797
+ h = _x.h ?? dst.height;
2798
+ } else if (typeof _x === "number") {
2799
+ x = _x;
2800
+ y = _y;
2801
+ w = _w;
2802
+ h = _h;
2803
+ } else {
2804
+ x = 0;
2805
+ y = 0;
2806
+ w = dst.width;
2807
+ h = dst.height;
2926
2808
  }
2927
- const data32 = target.data32;
2928
- const baseColor = color & 16777215;
2929
- const baseSrcAlpha = color >>> 24;
2930
- const isOpaque = alpha === 255;
2931
- const invHalfW = 1 / (brushWidth / 2);
2932
- const invHalfH = 1 / (brushHeight / 2);
2933
- const centerOffsetX = brushWidth % 2 === 0 ? 0.5 : 0;
2934
- const centerOffsetY = brushHeight % 2 === 0 ? 0.5 : 0;
2935
- const fCenterX = Math.floor(centerX);
2936
- const fCenterY = Math.floor(centerY);
2937
- const endX = b.x + b.w;
2938
- const endY = b.y + b.h;
2939
- const isOverwrite = blendFn.isOverwrite;
2940
- let didChange = false;
2941
- for (let py = b.y; py < endY; py++) {
2942
- const rowOffset = py * targetWidth;
2943
- const dy = Math.abs(py - fCenterY + centerOffsetY) * invHalfH;
2944
- for (let px = b.x; px < endX; px++) {
2945
- const idx = rowOffset + px;
2946
- const dx = Math.abs(px - fCenterX + centerOffsetX) * invHalfW;
2947
- const dist = dx > dy ? dx : dy;
2948
- const strength = fallOff(dist);
2949
- const maskVal = strength * 255 | 0;
2950
- if (maskVal <= 0) continue;
2951
- let weight = alpha;
2952
- if (isOpaque) {
2953
- weight = maskVal;
2954
- } else if (maskVal !== 255) {
2955
- weight = maskVal * alpha + 128 >> 8;
2956
- }
2957
- let finalCol = color;
2958
- if (weight < 255) {
2959
- const a = baseSrcAlpha * weight + 128 >> 8;
2960
- if (a === 0 && !isOverwrite) continue;
2961
- finalCol = (a << 24 | baseColor) >>> 0;
2962
- }
2963
- const current = data32[idx];
2964
- const next = blendFn(finalCol, current);
2965
- if (current !== next) {
2966
- data32[idx] = next;
2967
- didChange = true;
2809
+ const clip = resolveRectClipping(x, y, w, h, dst.width, dst.height, SCRATCH_RECT2);
2810
+ if (!clip.inBounds) return false;
2811
+ const {
2812
+ x: finalX,
2813
+ y: finalY,
2814
+ w: actualW,
2815
+ h: actualH
2816
+ } = clip;
2817
+ const dst32 = dst.data32;
2818
+ const dw = dst.width;
2819
+ let hasChanged = false;
2820
+ for (let iy = 0; iy < actualH; iy++) {
2821
+ const rowOffset = (finalY + iy) * dw;
2822
+ const start = rowOffset + finalX;
2823
+ const end = start + actualW;
2824
+ for (let i = start; i < end; i++) {
2825
+ if (dst32[i] !== color) {
2826
+ dst32[i] = color;
2827
+ hasChanged = true;
2968
2828
  }
2969
2829
  }
2970
2830
  }
2971
- return didChange;
2831
+ return hasChanged;
2972
2832
  }
2973
2833
 
2974
- // src/History/PixelMutator/mutatorApplyRectBrush.ts
2834
+ // src/History/PixelMutator/mutatorFill.ts
2975
2835
  var defaults8 = {
2976
- applyRectBrushToPixelData,
2977
- getRectBrushOrPencilBounds
2836
+ fillPixelData
2978
2837
  };
2979
- var mutatorApplyRectBrush = ((writer, deps = defaults8) => {
2838
+ var mutatorFill = ((writer, deps = defaults8) => {
2980
2839
  const {
2981
- applyRectBrushToPixelData: applyRectBrushToPixelData2 = defaults8.applyRectBrushToPixelData,
2982
- getRectBrushOrPencilBounds: getRectBrushOrPencilBounds2 = defaults8.getRectBrushOrPencilBounds
2840
+ fillPixelData: fillPixelData2 = defaults8.fillPixelData
2983
2841
  } = deps;
2984
- const boundsOut = {
2985
- x: 0,
2986
- y: 0,
2987
- w: 0,
2988
- h: 0
2989
- };
2990
2842
  return {
2991
- applyRectBrush(color, centerX, centerY, brushWidth, brushHeight, alpha = 255, fallOff, blendFn) {
2843
+ fill(color, x = 0, y = 0, w = writer.config.target.width, h = writer.config.target.height) {
2992
2844
  const target = writer.config.target;
2993
- const b = getRectBrushOrPencilBounds2(centerX, centerY, brushWidth, brushHeight, target.width, target.height, boundsOut);
2994
- const didChange = writer.accumulator.storeRegionBeforeState(b.x, b.y, b.w, b.h);
2995
- return didChange(applyRectBrushToPixelData2(target, color, centerX, centerY, brushWidth, brushHeight, alpha, fallOff, blendFn, b));
2845
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
2846
+ return didChange(fillPixelData2(target, color, x, y, w, h));
2996
2847
  }
2997
2848
  };
2998
2849
  });
2999
-
3000
- // src/Rect/getRectBrushOrPencilStrokeBounds.ts
3001
- function getRectBrushOrPencilStrokeBounds(x0, y0, x1, y1, brushWidth, brushHeight, result) {
3002
- const halfW = brushWidth / 2;
3003
- const halfH = brushHeight / 2;
3004
- const minX = Math.min(x0, x1) - halfW;
3005
- const minY = Math.min(y0, y1) - halfH;
3006
- const maxX = Math.max(x0, x1) + halfW;
3007
- const maxY = Math.max(y0, y1) + halfH;
3008
- result.x = Math.floor(minX);
3009
- result.y = Math.floor(minY);
3010
- result.w = Math.ceil(maxX - minX);
3011
- result.h = Math.ceil(maxY - minY);
3012
- return result;
3013
- }
3014
-
3015
- // src/History/PixelMutator/mutatorApplyRectBrushStroke.ts
3016
- var defaults9 = {
3017
- forEachLinePoint,
3018
- blendColorPixelDataAlphaMask,
3019
- getRectBrushOrPencilBounds,
3020
- getRectBrushOrPencilStrokeBounds
3021
- };
3022
- var mutatorApplyRectBrushStroke = ((writer, deps = defaults9) => {
2850
+ var mutatorFillRect = ((writer, deps = defaults8) => {
3023
2851
  const {
3024
- forEachLinePoint: forEachLinePoint2 = defaults9.forEachLinePoint,
3025
- blendColorPixelDataAlphaMask: blendColorPixelDataAlphaMask2 = defaults9.blendColorPixelDataAlphaMask,
3026
- getRectBrushOrPencilBounds: getRectBrushOrPencilBounds2 = defaults9.getRectBrushOrPencilBounds,
3027
- getRectBrushOrPencilStrokeBounds: getRectBrushOrPencilStrokeBounds2 = defaults9.getRectBrushOrPencilStrokeBounds
2852
+ fillPixelData: fillPixelData2 = defaults8.fillPixelData
3028
2853
  } = deps;
3029
- const strokeBoundsOut = {
3030
- x: 0,
3031
- y: 0,
3032
- w: 0,
3033
- h: 0
3034
- };
3035
- const rectBrushBounds = {
3036
- x: 0,
3037
- y: 0,
3038
- w: 0,
3039
- h: 0
3040
- };
3041
- const blendColorPixelOptions = {
3042
- alpha: 255,
3043
- blendFn: sourceOverPerfect,
3044
- x: 0,
3045
- y: 0,
3046
- w: 0,
3047
- h: 0
3048
- };
3049
- const mask = {
3050
- type: 0 /* ALPHA */,
3051
- data: null,
3052
- w: 0,
3053
- h: 0
3054
- };
3055
2854
  return {
3056
- applyRectBrushStroke(color, x0, y0, x1, y1, brushWidth, brushHeight, alpha = 255, fallOff, blendFn = sourceOverPerfect) {
3057
- const {
3058
- x: bx,
3059
- y: by,
3060
- w: bw,
3061
- h: bh
3062
- } = getRectBrushOrPencilStrokeBounds2(x0, y0, x1, y1, brushWidth, brushHeight, strokeBoundsOut);
3063
- if (bw <= 0 || bh <= 0) return;
3064
- mask.data = new Uint8Array(bw * bh);
3065
- mask.w = bw;
3066
- mask.h = bh;
3067
- const maskData = mask.data;
3068
- const halfW = brushWidth / 2;
3069
- const halfH = brushHeight / 2;
3070
- const invHalfW = 1 / halfW;
3071
- const invHalfH = 1 / halfH;
3072
- const centerOffsetX = brushWidth % 2 === 0 ? 0.5 : 0;
3073
- const centerOffsetY = brushHeight % 2 === 0 ? 0.5 : 0;
2855
+ fillRect(color, rect) {
3074
2856
  const target = writer.config.target;
3075
- const targetWidth = target.width;
3076
- const targetHeight = target.height;
3077
- forEachLinePoint2(x0, y0, x1, y1, (px, py) => {
3078
- const {
3079
- x: rbx,
3080
- y: rby,
3081
- w: rbw,
3082
- h: rbh
3083
- } = getRectBrushOrPencilBounds2(px, py, brushWidth, brushHeight, targetWidth, targetHeight, rectBrushBounds);
3084
- writer.accumulator.storeRegionBeforeState(rbx, rby, rbw, rbh);
3085
- const startX = Math.max(bx, rbx);
3086
- const startY = Math.max(by, rby);
3087
- const endX = Math.min(bx + bw, rbx + rbw);
3088
- const endY = Math.min(by + bh, rby + rbh);
3089
- const fPx = Math.floor(px);
3090
- const fPy = Math.floor(py);
3091
- for (let my = startY; my < endY; my++) {
3092
- const dy = Math.abs(my - fPy + centerOffsetY) * invHalfH;
3093
- const maskRowOffset = (my - by) * bw;
3094
- for (let mx = startX; mx < endX; mx++) {
3095
- const dx = Math.abs(mx - fPx + centerOffsetX) * invHalfW;
3096
- const maskIdx = maskRowOffset + (mx - bx);
3097
- const dist = dx > dy ? dx : dy;
3098
- const strength = fallOff(dist);
3099
- if (strength > 0) {
3100
- const intensity = strength * 255 | 0;
3101
- if (intensity > maskData[maskIdx]) {
3102
- maskData[maskIdx] = intensity;
3103
- }
3104
- }
3105
- }
3106
- }
3107
- });
3108
- blendColorPixelOptions.blendFn = blendFn;
3109
- blendColorPixelOptions.alpha = alpha;
3110
- blendColorPixelOptions.x = bx;
3111
- blendColorPixelOptions.y = by;
3112
- blendColorPixelOptions.w = bw;
3113
- blendColorPixelOptions.h = bh;
3114
- blendColorPixelDataAlphaMask2(target, color, mask, blendColorPixelOptions);
2857
+ const didChange = writer.accumulator.storeRegionBeforeState(rect.x, rect.y, rect.w, rect.h);
2858
+ return didChange(fillPixelData2(target, color, rect.x, rect.y, rect.w, rect.h));
3115
2859
  }
3116
2860
  };
3117
2861
  });
3118
2862
 
3119
- // src/History/PixelMutator/mutatorApplyRectPencil.ts
3120
- var defaults10 = {
3121
- applyRectBrushToPixelData,
3122
- getRectBrushOrPencilBounds,
3123
- fallOff: () => 1
3124
- };
3125
- var mutatorApplyRectPencil = ((writer, deps = defaults10) => {
2863
+ // src/PixelData/fillPixelDataBinaryMask.ts
2864
+ var SCRATCH_RECT3 = makeClippedRect();
2865
+ function fillPixelDataBinaryMask(dst, color, mask, alpha = 255, x = 0, y = 0) {
2866
+ if (alpha === 0) return false;
2867
+ const maskW = mask.w;
2868
+ const maskH = mask.h;
2869
+ const clip = resolveRectClipping(x, y, maskW, maskH, dst.width, dst.height, SCRATCH_RECT3);
2870
+ if (!clip.inBounds) return false;
3126
2871
  const {
3127
- applyRectBrushToPixelData: applyRectBrushToPixelData2 = defaults10.applyRectBrushToPixelData,
3128
- getRectBrushOrPencilBounds: getRectBrushOrPencilBounds2 = defaults10.getRectBrushOrPencilBounds,
3129
- fallOff = defaults10.fallOff
3130
- } = deps;
3131
- const boundsOut = {
3132
- x: 0,
3133
- y: 0,
3134
- w: 0,
3135
- h: 0
3136
- };
3137
- return {
3138
- applyRectPencil(color, centerX, centerY, brushWidth, brushHeight, alpha = 255, blendFn) {
3139
- const target = writer.config.target;
3140
- const b = getRectBrushOrPencilBounds2(centerX, centerY, brushWidth, brushHeight, target.width, target.height, boundsOut);
3141
- const didChange = writer.accumulator.storeRegionBeforeState(b.x, b.y, b.w, b.h);
3142
- return didChange(applyRectBrushToPixelData2(target, color, centerX, centerY, brushWidth, brushHeight, alpha, fallOff, blendFn, b));
3143
- }
3144
- };
3145
- });
3146
-
3147
- // src/History/PixelMutator/mutatorApplyRectPencilStroke.ts
3148
- var defaults11 = {
3149
- forEachLinePoint,
3150
- getRectBrushOrPencilBounds,
3151
- getRectBrushOrPencilStrokeBounds,
3152
- blendColorPixelDataBinaryMask
2872
+ x: finalX,
2873
+ y: finalY,
2874
+ w: actualW,
2875
+ h: actualH
2876
+ } = clip;
2877
+ const maskData = mask.data;
2878
+ const dst32 = dst.data32;
2879
+ const dw = dst.width;
2880
+ let finalCol = color;
2881
+ if (alpha < 255) {
2882
+ const baseSrcAlpha = color >>> 24;
2883
+ const colorRGB = color & 16777215;
2884
+ const a = baseSrcAlpha * alpha + 128 >> 8;
2885
+ finalCol = (colorRGB | a << 24) >>> 0;
2886
+ }
2887
+ let hasChanged = false;
2888
+ for (let iy = 0; iy < actualH; iy++) {
2889
+ const currentY = finalY + iy;
2890
+ const maskY = currentY - y;
2891
+ const maskOffset = maskY * maskW;
2892
+ const dstRowOffset = currentY * dw;
2893
+ for (let ix = 0; ix < actualW; ix++) {
2894
+ const currentX = finalX + ix;
2895
+ const maskX = currentX - x;
2896
+ const maskIndex = maskOffset + maskX;
2897
+ if (maskData[maskIndex]) {
2898
+ const current = dst32[dstRowOffset + currentX];
2899
+ if (current !== finalCol) {
2900
+ dst32[dstRowOffset + currentX] = finalCol;
2901
+ hasChanged = true;
2902
+ }
2903
+ }
2904
+ }
2905
+ }
2906
+ return hasChanged;
2907
+ }
2908
+
2909
+ // src/History/PixelMutator/mutatorFillBinaryMask.ts
2910
+ var defaults9 = {
2911
+ fillPixelDataBinaryMask
3153
2912
  };
3154
- var mutatorApplyRectPencilStroke = ((writer, deps = defaults11) => {
2913
+ var mutatorFillBinaryMask = ((writer, deps = defaults9) => {
3155
2914
  const {
3156
- forEachLinePoint: forEachLinePoint2 = defaults11.forEachLinePoint,
3157
- blendColorPixelDataBinaryMask: blendColorPixelDataBinaryMask2 = defaults11.blendColorPixelDataBinaryMask,
3158
- getRectBrushOrPencilBounds: getRectBrushOrPencilBounds2 = defaults11.getRectBrushOrPencilBounds,
3159
- getRectBrushOrPencilStrokeBounds: getRectBrushOrPencilStrokeBounds2 = defaults11.getRectBrushOrPencilStrokeBounds
2915
+ fillPixelDataBinaryMask: fillPixelDataBinaryMask2 = defaults9.fillPixelDataBinaryMask
3160
2916
  } = deps;
3161
- const strokeBoundsOut = {
3162
- x: 0,
3163
- y: 0,
3164
- w: 0,
3165
- h: 0
3166
- };
3167
- const rectPencilBounds = {
3168
- x: 0,
3169
- y: 0,
3170
- w: 0,
3171
- h: 0
3172
- };
3173
- const blendColorPixelOptions = {
3174
- alpha: 255,
3175
- blendFn: sourceOverPerfect,
3176
- x: 0,
3177
- y: 0,
3178
- w: 0,
3179
- h: 0
3180
- };
3181
- const mask = {
3182
- type: 1 /* BINARY */,
3183
- data: null,
3184
- w: 0,
3185
- h: 0
3186
- };
3187
2917
  return {
3188
- applyRectPencilStroke(color, x0, y0, x1, y1, brushWidth, brushHeight, alpha = 255, blendFn = sourceOverPerfect) {
3189
- const {
3190
- x: bx,
3191
- y: by,
3192
- w: bw,
3193
- h: bh
3194
- } = getRectBrushOrPencilStrokeBounds2(x0, y0, x1, y1, brushWidth, brushHeight, strokeBoundsOut);
3195
- if (bw <= 0 || bh <= 0) return;
3196
- mask.data = new Uint8Array(bw * bh);
3197
- mask.w = bw;
3198
- mask.h = bh;
3199
- const maskData = mask.data;
3200
- const halfW = brushWidth / 2;
3201
- const halfH = brushHeight / 2;
3202
- const centerOffset = brushWidth % 2 === 0 ? 0.5 : 0;
3203
- const target = writer.config.target;
3204
- const targetWidth = target.width;
3205
- const targetHeight = target.height;
3206
- forEachLinePoint2(x0, y0, x1, y1, (px, py) => {
3207
- const {
3208
- x: rbx,
3209
- y: rby,
3210
- w: rbw,
3211
- h: rbh
3212
- } = getRectBrushOrPencilBounds2(px, py, brushWidth, brushHeight, targetWidth, targetHeight, rectPencilBounds);
3213
- writer.accumulator.storeRegionBeforeState(rbx, rby, rbw, rbh);
3214
- const startX = Math.max(bx, rbx);
3215
- const startY = Math.max(by, rby);
3216
- const endX = Math.min(bx + bw, rbx + rbw);
3217
- const endY = Math.min(by + bh, rby + rbh);
3218
- const fPx = Math.floor(px);
3219
- const fPy = Math.floor(py);
3220
- for (let my = startY; my < endY; my++) {
3221
- const dy = Math.abs(my - fPy + centerOffset);
3222
- const maskRowOffset = (my - by) * bw;
3223
- for (let mx = startX; mx < endX; mx++) {
3224
- const dx = Math.abs(mx - fPx + centerOffset);
3225
- const maskIdx = maskRowOffset + (mx - bx);
3226
- if (dx <= halfW && dy <= halfH) {
3227
- maskData[maskIdx] = 1;
3228
- }
3229
- }
3230
- }
3231
- });
3232
- blendColorPixelOptions.blendFn = blendFn;
3233
- blendColorPixelOptions.alpha = alpha;
3234
- blendColorPixelOptions.x = bx;
3235
- blendColorPixelOptions.y = by;
3236
- blendColorPixelOptions.w = bw;
3237
- blendColorPixelOptions.h = bh;
3238
- blendColorPixelDataBinaryMask2(target, color, mask, blendColorPixelOptions);
2918
+ fillBinaryMask(color, mask, alpha = 255, x = 0, y = 0) {
2919
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, mask.w, mask.h);
2920
+ return didChange(fillPixelDataBinaryMask2(writer.config.target, color, mask, alpha, x, y));
3239
2921
  }
3240
2922
  };
3241
2923
  });
3242
2924
 
3243
- // src/PixelData/blendColorPixelData.ts
3244
- function blendColorPixelData(dst, color, opts = {}) {
2925
+ // src/PixelData/invertPixelData.ts
2926
+ var SCRATCH_RECT4 = makeClippedRect();
2927
+ function invertPixelData(pixelData, opts = {}) {
2928
+ const dst = pixelData;
3245
2929
  const {
3246
2930
  x: targetX = 0,
3247
2931
  y: targetY = 0,
3248
- w: width = dst.width,
3249
- h: height = dst.height,
3250
- alpha: globalAlpha = 255,
3251
- blendFn = sourceOverPerfect
2932
+ w: width = pixelData.width,
2933
+ h: height = pixelData.height,
2934
+ mask,
2935
+ mx = 0,
2936
+ my = 0,
2937
+ invertMask = false
3252
2938
  } = opts;
3253
- if (globalAlpha === 0) return false;
3254
- const baseSrcAlpha = color >>> 24;
3255
- const isOverwrite = blendFn.isOverwrite || false;
3256
- if (baseSrcAlpha === 0 && !isOverwrite) return false;
3257
- let x = targetX;
3258
- let y = targetY;
3259
- let w = width;
3260
- let h = height;
3261
- if (x < 0) {
3262
- w += x;
3263
- x = 0;
3264
- }
3265
- if (y < 0) {
3266
- h += y;
3267
- y = 0;
3268
- }
3269
- const actualW = Math.min(w, dst.width - x);
3270
- const actualH = Math.min(h, dst.height - y);
3271
- if (actualW <= 0 || actualH <= 0) return false;
3272
- let finalSrcColor = color;
3273
- if (globalAlpha < 255) {
3274
- const a = baseSrcAlpha * globalAlpha + 128 >> 8;
3275
- if (a === 0 && !isOverwrite) return false;
3276
- finalSrcColor = (color & 16777215 | a << 24) >>> 0;
3277
- }
2939
+ const clip = resolveRectClipping(targetX, targetY, width, height, dst.width, dst.height, SCRATCH_RECT4);
2940
+ if (!clip.inBounds) return false;
2941
+ const {
2942
+ x,
2943
+ y,
2944
+ w: actualW,
2945
+ h: actualH
2946
+ } = clip;
3278
2947
  const dst32 = dst.data32;
3279
2948
  const dw = dst.width;
3280
- let dIdx = y * dw + x | 0;
3281
- const dStride = dw - actualW | 0;
3282
- let didChange = false;
3283
- for (let iy = 0; iy < actualH; iy++) {
3284
- for (let ix = 0; ix < actualW; ix++) {
3285
- const current = dst32[dIdx];
3286
- const next = blendFn(finalSrcColor, current);
3287
- if (current !== next) {
3288
- dst32[dIdx] = next;
3289
- didChange = true;
2949
+ const mPitch = mask?.w ?? width;
2950
+ const dx = x - targetX;
2951
+ const dy = y - targetY;
2952
+ let dIdx = y * dw + x;
2953
+ let mIdx = (my + dy) * mPitch + (mx + dx);
2954
+ const dStride = dw - actualW;
2955
+ const mStride = mPitch - actualW;
2956
+ if (mask) {
2957
+ const maskData = mask.data;
2958
+ for (let iy = 0; iy < actualH; iy++) {
2959
+ for (let ix = 0; ix < actualW; ix++) {
2960
+ const mVal = maskData[mIdx];
2961
+ const isHit = invertMask ? mVal === 0 : mVal === 1;
2962
+ if (isHit) {
2963
+ dst32[dIdx] = dst32[dIdx] ^ 16777215;
2964
+ }
2965
+ dIdx++;
2966
+ mIdx++;
3290
2967
  }
3291
- dIdx++;
2968
+ dIdx += dStride;
2969
+ mIdx += mStride;
2970
+ }
2971
+ } else {
2972
+ for (let iy = 0; iy < actualH; iy++) {
2973
+ for (let ix = 0; ix < actualW; ix++) {
2974
+ dst32[dIdx] = dst32[dIdx] ^ 16777215;
2975
+ dIdx++;
2976
+ }
2977
+ dIdx += dStride;
3292
2978
  }
3293
- dIdx += dStride;
3294
2979
  }
3295
- return didChange;
2980
+ return true;
3296
2981
  }
3297
2982
 
3298
- // src/History/PixelMutator/mutatorBlendColor.ts
3299
- var defaults12 = {
3300
- blendColorPixelData
2983
+ // src/History/PixelMutator/mutatorInvert.ts
2984
+ var defaults10 = {
2985
+ invertPixelData
3301
2986
  };
3302
- var mutatorBlendColor = ((writer, deps = defaults12) => {
2987
+ var mutatorInvert = ((writer, deps = defaults10) => {
3303
2988
  const {
3304
- blendColorPixelData: blendColorPixelData2 = defaults12.blendColorPixelData
2989
+ invertPixelData: invertPixelData2 = defaults10.invertPixelData
3305
2990
  } = deps;
3306
2991
  return {
3307
- blendColor(color, opts = {}) {
2992
+ invert(opts = {}) {
3308
2993
  const target = writer.config.target;
3309
2994
  const {
3310
2995
  x = 0,
@@ -3313,708 +2998,641 @@ var mutatorBlendColor = ((writer, deps = defaults12) => {
3313
2998
  h = target.height
3314
2999
  } = opts;
3315
3000
  const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3316
- return didChange(blendColorPixelData2(target, color, opts));
3001
+ return didChange(invertPixelData2(target, opts));
3317
3002
  }
3318
3003
  };
3319
3004
  });
3320
3005
 
3321
- // src/PixelData/blendPixel.ts
3322
- function blendPixel(target, x, y, color, alpha = 255, blendFn = sourceOverPerfect) {
3323
- if (alpha === 0) return false;
3324
- let width = target.width;
3325
- let height = target.height;
3326
- if (x < 0 || x >= width || y < 0 || y >= height) return false;
3327
- let srcAlpha = color >>> 24;
3328
- let isOverwrite = blendFn.isOverwrite;
3329
- if (srcAlpha === 0 && !isOverwrite) return false;
3330
- let dst32 = target.data32;
3331
- let index = y * width + x;
3332
- let finalColor = color;
3333
- if (alpha !== 255) {
3334
- let finalAlpha = srcAlpha * alpha + 128 >> 8;
3335
- if (finalAlpha === 0 && !isOverwrite) return false;
3336
- finalColor = (color & 16777215 | finalAlpha << 24) >>> 0;
3006
+ // src/History/PixelMutator.ts
3007
+ function makeFullPixelMutator(writer) {
3008
+ return {
3009
+ // @sort
3010
+ ...mutatorBlendColor(writer),
3011
+ ...mutatorBlendPixel(writer),
3012
+ ...mutatorBlendPixelData(writer),
3013
+ ...mutatorBlendPixelDataAlphaMask(writer),
3014
+ ...mutatorBlendPixelDataBinaryMask(writer),
3015
+ ...mutatorClear(writer),
3016
+ ...mutatorFill(writer),
3017
+ ...mutatorFillBinaryMask(writer),
3018
+ ...mutatorFillRect(writer),
3019
+ ...mutatorInvert(writer)
3020
+ };
3021
+ }
3022
+
3023
+ // src/ImageData/resizeImageData.ts
3024
+ function resizeImageData(target, newWidth, newHeight, offsetX = 0, offsetY = 0) {
3025
+ const result = new ImageData(newWidth, newHeight);
3026
+ const {
3027
+ width: oldW,
3028
+ height: oldH,
3029
+ data: oldData
3030
+ } = target;
3031
+ const newData = result.data;
3032
+ const x0 = Math.max(0, offsetX);
3033
+ const y0 = Math.max(0, offsetY);
3034
+ const x1 = Math.min(newWidth, offsetX + oldW);
3035
+ const y1 = Math.min(newHeight, offsetY + oldH);
3036
+ if (x1 <= x0 || y1 <= y0) {
3037
+ return result;
3337
3038
  }
3338
- let current = dst32[index];
3339
- let next = blendFn(finalColor, current);
3340
- if (current !== next) {
3341
- dst32[index] = next;
3342
- return true;
3039
+ const rowCount = y1 - y0;
3040
+ const rowLen = (x1 - x0) * 4;
3041
+ for (let row = 0; row < rowCount; row++) {
3042
+ const dstY = y0 + row;
3043
+ const srcY = dstY - offsetY;
3044
+ const srcX = x0 - offsetX;
3045
+ const dstStart = (dstY * newWidth + x0) * 4;
3046
+ const srcStart = (srcY * oldW + srcX) * 4;
3047
+ newData.set(oldData.subarray(srcStart, srcStart + rowLen), dstStart);
3343
3048
  }
3344
- return false;
3049
+ return result;
3345
3050
  }
3346
3051
 
3347
- // src/History/PixelMutator/mutatorBlendPixel.ts
3348
- var defaults13 = {
3349
- blendPixel
3350
- };
3351
- var mutatorBlendPixel = ((writer, deps = defaults13) => {
3352
- const {
3353
- blendPixel: blendPixel2 = defaults13.blendPixel
3354
- } = deps;
3355
- return {
3356
- blendPixel(x, y, color, alpha, blendFn) {
3357
- const didChange = writer.accumulator.storePixelBeforeState(x, y);
3358
- return didChange(blendPixel2(writer.config.target, x, y, color, alpha, blendFn));
3359
- }
3052
+ // src/Rect/trimRectBounds.ts
3053
+ function trimRectBounds(x, y, w, h, targetWidth, targetHeight, out) {
3054
+ const res = out ?? {
3055
+ x: 0,
3056
+ y: 0,
3057
+ w: 0,
3058
+ h: 0
3360
3059
  };
3361
- });
3362
-
3363
- // src/PixelData/blendPixelData.ts
3364
- function blendPixelData(dst, src, opts = {}) {
3365
- const {
3366
- x: targetX = 0,
3367
- y: targetY = 0,
3368
- sx: sourceX = 0,
3369
- sy: sourceY = 0,
3370
- w: width = src.width,
3371
- h: height = src.height,
3372
- alpha: globalAlpha = 255,
3373
- blendFn = sourceOverPerfect
3374
- } = opts;
3375
- if (globalAlpha === 0) return false;
3376
- let x = targetX;
3377
- let y = targetY;
3378
- let sx = sourceX;
3379
- let sy = sourceY;
3380
- let w = width;
3381
- let h = height;
3382
- if (sx < 0) {
3383
- x -= sx;
3384
- w += sx;
3385
- sx = 0;
3386
- }
3387
- if (sy < 0) {
3388
- y -= sy;
3389
- h += sy;
3390
- sy = 0;
3391
- }
3392
- w = Math.min(w, src.width - sx);
3393
- h = Math.min(h, src.height - sy);
3394
- if (x < 0) {
3395
- sx -= x;
3396
- w += x;
3397
- x = 0;
3398
- }
3399
- if (y < 0) {
3400
- sy -= y;
3401
- h += y;
3402
- y = 0;
3403
- }
3404
- const actualW = Math.min(w, dst.width - x);
3405
- const actualH = Math.min(h, dst.height - y);
3406
- if (actualW <= 0 || actualH <= 0) return false;
3407
- const dst32 = dst.data32;
3408
- const src32 = src.data32;
3409
- const dw = dst.width;
3410
- const sw = src.width;
3411
- let dIdx = y * dw + x | 0;
3412
- let sIdx = sy * sw + sx | 0;
3413
- const dStride = dw - actualW | 0;
3414
- const sStride = sw - actualW | 0;
3415
- const isOpaque = globalAlpha === 255;
3416
- const isOverwrite = blendFn.isOverwrite;
3417
- let didChange = false;
3418
- for (let iy = 0; iy < actualH; iy++) {
3419
- for (let ix = 0; ix < actualW; ix++) {
3420
- const srcCol = src32[sIdx];
3421
- const srcAlpha = srcCol >>> 24;
3422
- if (srcAlpha === 0 && !isOverwrite) {
3423
- dIdx++;
3424
- sIdx++;
3425
- continue;
3426
- }
3427
- let finalCol = srcCol;
3428
- if (!isOpaque) {
3429
- const a = srcAlpha * globalAlpha + 128 >> 8;
3430
- if (a === 0 && !isOverwrite) {
3431
- dIdx++;
3432
- sIdx++;
3433
- continue;
3434
- }
3435
- finalCol = (srcCol & 16777215 | a << 24) >>> 0;
3436
- }
3437
- const current = dst32[dIdx];
3438
- const next = blendFn(finalCol, dst32[dIdx]);
3439
- if (current !== next) {
3440
- dst32[dIdx] = next;
3441
- didChange = true;
3442
- }
3443
- dIdx++;
3444
- sIdx++;
3445
- }
3446
- dIdx += dStride;
3447
- sIdx += sStride;
3448
- }
3449
- return didChange;
3450
- }
3451
-
3452
- // src/History/PixelMutator/mutatorBlendPixelData.ts
3453
- var defaults14 = {
3454
- blendPixelData
3455
- };
3456
- var mutatorBlendPixelData = ((writer, deps = defaults14) => {
3457
- const {
3458
- blendPixelData: blendPixelData2 = defaults14.blendPixelData
3459
- } = deps;
3460
- return {
3461
- blendPixelData(src, opts = {}) {
3462
- const {
3463
- x = 0,
3464
- y = 0,
3465
- w = src.width,
3466
- h = src.height
3467
- } = opts;
3468
- const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3469
- return didChange(blendPixelData2(writer.config.target, src, opts));
3470
- }
3471
- };
3472
- });
3473
-
3474
- // src/PixelData/blendPixelDataAlphaMask.ts
3475
- function blendPixelDataAlphaMask(dst, src, alphaMask, opts = {}) {
3476
- const {
3477
- x: targetX = 0,
3478
- y: targetY = 0,
3479
- sx: sourceX = 0,
3480
- sy: sourceY = 0,
3481
- w: width = src.width,
3482
- h: height = src.height,
3483
- alpha: globalAlpha = 255,
3484
- blendFn = sourceOverPerfect,
3485
- mx = 0,
3486
- my = 0,
3487
- invertMask = false
3488
- } = opts;
3489
- if (globalAlpha === 0) return false;
3490
- let x = targetX;
3491
- let y = targetY;
3492
- let sx = sourceX;
3493
- let sy = sourceY;
3494
- let w = width;
3495
- let h = height;
3496
- if (sx < 0) {
3497
- x -= sx;
3498
- w += sx;
3499
- sx = 0;
3500
- }
3501
- if (sy < 0) {
3502
- y -= sy;
3503
- h += sy;
3504
- sy = 0;
3505
- }
3506
- w = Math.min(w, src.width - sx);
3507
- h = Math.min(h, src.height - sy);
3508
- if (x < 0) {
3509
- sx -= x;
3510
- w += x;
3511
- x = 0;
3512
- }
3513
- if (y < 0) {
3514
- sy -= y;
3515
- h += y;
3516
- y = 0;
3517
- }
3518
- const actualW = Math.min(w, dst.width - x);
3519
- const actualH = Math.min(h, dst.height - y);
3520
- if (actualW <= 0 || actualH <= 0) return false;
3521
- const dw = dst.width;
3522
- const sw = src.width;
3523
- const mPitch = alphaMask.w;
3524
- const maskData = alphaMask.data;
3525
- const dx = x - targetX | 0;
3526
- const dy = y - targetY | 0;
3527
- const dst32 = dst.data32;
3528
- const src32 = src.data32;
3529
- let dIdx = y * dw + x | 0;
3530
- let sIdx = sy * sw + sx | 0;
3531
- let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
3532
- const dStride = dw - actualW | 0;
3533
- const sStride = sw - actualW | 0;
3534
- const mStride = mPitch - actualW | 0;
3535
- const isOpaque = globalAlpha === 255;
3536
- const isOverwrite = blendFn.isOverwrite || false;
3537
- let didChange = false;
3538
- for (let iy = 0; iy < actualH; iy++) {
3539
- for (let ix = 0; ix < actualW; ix++) {
3540
- const mVal = maskData[mIdx];
3541
- const effM = invertMask ? 255 - mVal : mVal;
3542
- if (effM === 0) {
3543
- dIdx++;
3544
- sIdx++;
3545
- mIdx++;
3546
- continue;
3547
- }
3548
- const srcCol = src32[sIdx];
3549
- const srcAlpha = srcCol >>> 24;
3550
- if (srcAlpha === 0 && !isOverwrite) {
3551
- dIdx++;
3552
- sIdx++;
3553
- mIdx++;
3554
- continue;
3555
- }
3556
- let weight = globalAlpha;
3557
- if (isOpaque) {
3558
- weight = effM;
3559
- } else if (effM !== 255) {
3560
- weight = effM * globalAlpha + 128 >> 8;
3561
- }
3562
- if (weight === 0) {
3563
- dIdx++;
3564
- sIdx++;
3565
- mIdx++;
3566
- continue;
3567
- }
3568
- let finalCol = srcCol;
3569
- if (weight < 255) {
3570
- const a = srcAlpha * weight + 128 >> 8;
3571
- if (a === 0 && !isOverwrite) {
3572
- dIdx++;
3573
- sIdx++;
3574
- mIdx++;
3575
- continue;
3576
- }
3577
- finalCol = (srcCol & 16777215 | a << 24) >>> 0;
3578
- }
3579
- const current = dst32[dIdx];
3580
- const next = blendFn(finalCol, dst32[dIdx]);
3581
- if (current !== next) {
3582
- dst32[dIdx] = next;
3583
- didChange = true;
3584
- }
3585
- dIdx++;
3586
- sIdx++;
3587
- mIdx++;
3588
- }
3589
- dIdx += dStride;
3590
- sIdx += sStride;
3591
- mIdx += mStride;
3592
- }
3593
- return didChange;
3594
- }
3595
-
3596
- // src/History/PixelMutator/mutatorBlendPixelDataAlphaMask.ts
3597
- var defaults15 = {
3598
- blendPixelDataAlphaMask
3599
- };
3600
- var mutatorBlendPixelDataAlphaMask = ((writer, deps = defaults15) => {
3601
- const {
3602
- blendPixelDataAlphaMask: blendPixelDataAlphaMask2 = defaults15.blendPixelDataAlphaMask
3603
- } = deps;
3604
- return {
3605
- blendPixelDataAlphaMask(src, mask, opts = {}) {
3606
- const x = opts.x ?? 0;
3607
- const y = opts.y ?? 0;
3608
- const w = opts.w ?? src.width;
3609
- const h = opts.h ?? src.height;
3610
- const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3611
- return didChange(blendPixelDataAlphaMask2(writer.config.target, src, mask, opts));
3612
- }
3613
- };
3614
- });
3615
-
3616
- // src/PixelData/blendPixelDataBinaryMask.ts
3617
- function blendPixelDataBinaryMask(dst, src, binaryMask, opts = {}) {
3618
- const {
3619
- x: targetX = 0,
3620
- y: targetY = 0,
3621
- sx: sourceX = 0,
3622
- sy: sourceY = 0,
3623
- w: width = src.width,
3624
- h: height = src.height,
3625
- alpha: globalAlpha = 255,
3626
- blendFn = sourceOverPerfect,
3627
- mx = 0,
3628
- my = 0,
3629
- invertMask = false
3630
- } = opts;
3631
- if (globalAlpha === 0) return false;
3632
- let x = targetX;
3633
- let y = targetY;
3634
- let sx = sourceX;
3635
- let sy = sourceY;
3636
- let w = width;
3637
- let h = height;
3638
- if (sx < 0) {
3639
- x -= sx;
3640
- w += sx;
3641
- sx = 0;
3642
- }
3643
- if (sy < 0) {
3644
- y -= sy;
3645
- h += sy;
3646
- sy = 0;
3647
- }
3648
- w = Math.min(w, src.width - sx);
3649
- h = Math.min(h, src.height - sy);
3650
- if (x < 0) {
3651
- sx -= x;
3652
- w += x;
3653
- x = 0;
3654
- }
3655
- if (y < 0) {
3656
- sy -= y;
3657
- h += y;
3658
- y = 0;
3060
+ const left = Math.max(0, x);
3061
+ const top = Math.max(0, y);
3062
+ const right = Math.min(targetWidth, x + w);
3063
+ const bottom = Math.min(targetHeight, y + h);
3064
+ res.x = left;
3065
+ res.y = top;
3066
+ res.w = Math.max(0, right - left);
3067
+ res.h = Math.max(0, bottom - top);
3068
+ return res;
3069
+ }
3070
+
3071
+ // src/Paint/PaintBuffer.ts
3072
+ var PaintBuffer = class {
3073
+ constructor(config, tilePool) {
3074
+ this.config = config;
3075
+ this.tilePool = tilePool;
3076
+ this.lookup = [];
3659
3077
  }
3660
- const actualW = Math.min(w, dst.width - x);
3661
- const actualH = Math.min(h, dst.height - y);
3662
- if (actualW <= 0 || actualH <= 0) return false;
3663
- const dx = x - targetX | 0;
3664
- const dy = y - targetY | 0;
3665
- const dst32 = dst.data32;
3666
- const src32 = src.data32;
3667
- const dw = dst.width;
3668
- const sw = src.width;
3669
- const mPitch = binaryMask.w;
3670
- const maskData = binaryMask.data;
3671
- let dIdx = y * dw + x | 0;
3672
- let sIdx = sy * sw + sx | 0;
3673
- let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
3674
- const dStride = dw - actualW | 0;
3675
- const sStride = sw - actualW | 0;
3676
- const mStride = mPitch - actualW | 0;
3677
- const skipVal = invertMask ? 1 : 0;
3678
- const isOpaque = globalAlpha === 255;
3679
- const isOverwrite = blendFn.isOverwrite || false;
3680
- let didChange = false;
3681
- for (let iy = 0; iy < actualH; iy++) {
3682
- for (let ix = 0; ix < actualW; ix++) {
3683
- if (maskData[mIdx] === skipVal) {
3684
- dIdx++;
3685
- sIdx++;
3686
- mIdx++;
3687
- continue;
3688
- }
3689
- const srcCol = src32[sIdx];
3690
- const srcAlpha = srcCol >>> 24;
3691
- if (srcAlpha === 0 && !isOverwrite) {
3692
- dIdx++;
3693
- sIdx++;
3694
- mIdx++;
3695
- continue;
3696
- }
3697
- let finalCol = srcCol;
3698
- if (!isOpaque) {
3699
- const a = srcAlpha * globalAlpha + 128 >> 8;
3700
- if (a === 0 && !isOverwrite) {
3701
- dIdx++;
3702
- sIdx++;
3703
- mIdx++;
3704
- continue;
3705
- }
3706
- finalCol = (srcCol & 16777215 | a << 24) >>> 0;
3707
- }
3708
- const current = dst32[dIdx];
3709
- const next = blendFn(finalCol, dst32[dIdx]);
3710
- if (current !== next) {
3711
- dst32[dIdx] = next;
3712
- didChange = true;
3078
+ lookup;
3079
+ scratchBounds = {
3080
+ x: 0,
3081
+ y: 0,
3082
+ w: 0,
3083
+ h: 0
3084
+ };
3085
+ eachTileInBounds(bounds, callback) {
3086
+ const {
3087
+ tileShift,
3088
+ targetColumns,
3089
+ targetRows,
3090
+ tileSize
3091
+ } = this.config;
3092
+ const x1 = Math.max(0, bounds.x >> tileShift);
3093
+ const y1 = Math.max(0, bounds.y >> tileShift);
3094
+ const x2 = Math.min(targetColumns - 1, bounds.x + bounds.w - 1 >> tileShift);
3095
+ const y2 = Math.min(targetRows - 1, bounds.y + bounds.h - 1 >> tileShift);
3096
+ if (x1 > x2 || y1 > y2) return;
3097
+ const lookup = this.lookup;
3098
+ const tilePool = this.tilePool;
3099
+ for (let ty = y1; ty <= y2; ty++) {
3100
+ const rowOffset = ty * targetColumns;
3101
+ const tileTop = ty << tileShift;
3102
+ for (let tx = x1; tx <= x2; tx++) {
3103
+ const id = rowOffset + tx;
3104
+ const tile = lookup[id] ?? (lookup[id] = tilePool.getTile(id, tx, ty));
3105
+ const tileLeft = tx << tileShift;
3106
+ const startX = bounds.x > tileLeft ? bounds.x : tileLeft;
3107
+ const startY = bounds.y > tileTop ? bounds.y : tileTop;
3108
+ const maskEndX = bounds.x + bounds.w;
3109
+ const tileEndX = tileLeft + tileSize;
3110
+ const endX = maskEndX < tileEndX ? maskEndX : tileEndX;
3111
+ const maskEndY = bounds.y + bounds.h;
3112
+ const tileEndY = tileTop + tileSize;
3113
+ const endY = maskEndY < tileEndY ? maskEndY : tileEndY;
3114
+ callback(tile, startX, startY, endX - startX, endY - startY);
3713
3115
  }
3714
- dIdx++;
3715
- sIdx++;
3716
- mIdx++;
3717
3116
  }
3718
- dIdx += dStride;
3719
- sIdx += sStride;
3720
- mIdx += mStride;
3721
3117
  }
3722
- return didChange;
3723
- }
3724
-
3725
- // src/History/PixelMutator/mutatorBlendPixelDataBinaryMask.ts
3726
- var defaults16 = {
3727
- blendPixelDataBinaryMask
3728
- };
3729
- var mutatorBlendPixelDataBinaryMask = ((writer, deps = defaults16) => {
3730
- const {
3731
- blendPixelDataBinaryMask: blendPixelDataBinaryMask2 = defaults16.blendPixelDataBinaryMask
3732
- } = deps;
3733
- return {
3734
- blendPixelDataBinaryMask(src, mask, opts = {}) {
3735
- const x = opts.x ?? 0;
3736
- const y = opts.y ?? 0;
3737
- const w = opts.w ?? src.width;
3738
- const h = opts.h ?? src.height;
3739
- const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3740
- return didChange(blendPixelDataBinaryMask2(writer.config.target, src, mask, opts));
3741
- }
3742
- };
3743
- });
3744
-
3745
- // src/PixelData/fillPixelDataFast.ts
3746
- var SCRATCH_RECT = makeClippedRect();
3747
- function fillPixelDataFast(dst, color, _x, _y, _w, _h) {
3748
- let x;
3749
- let y;
3750
- let w;
3751
- let h;
3752
- if (typeof _x === "object") {
3753
- x = _x.x ?? 0;
3754
- y = _x.y ?? 0;
3755
- w = _x.w ?? dst.width;
3756
- h = _x.h ?? dst.height;
3757
- } else if (typeof _x === "number") {
3758
- x = _x;
3759
- y = _y;
3760
- w = _w;
3761
- h = _h;
3762
- } else {
3763
- x = 0;
3764
- y = 0;
3765
- w = dst.width;
3766
- h = dst.height;
3118
+ writePaintAlphaMaskStroke(color, brush, x0, y0, x1, y1) {
3119
+ const cA = color >>> 24;
3120
+ if (cA === 0) return false;
3121
+ const {
3122
+ tileShift,
3123
+ tileMask,
3124
+ target
3125
+ } = this.config;
3126
+ const {
3127
+ w: bW,
3128
+ h: bH,
3129
+ data: bD,
3130
+ centerOffsetX,
3131
+ centerOffsetY
3132
+ } = brush;
3133
+ const cRGB = color & 16777215;
3134
+ const scratch = this.scratchBounds;
3135
+ let changed = false;
3136
+ forEachLinePoint(x0, y0, x1, y1, (px, py) => {
3137
+ const topLeftX = Math.floor(px + centerOffsetX);
3138
+ const topLeftY = Math.floor(py + centerOffsetY);
3139
+ trimRectBounds(topLeftX, topLeftY, bW, bH, target.width, target.height, scratch);
3140
+ if (scratch.w <= 0 || scratch.h <= 0) return;
3141
+ this.eachTileInBounds(scratch, (tile, bX, bY, bW_t, bH_t) => {
3142
+ const d32 = tile.data32;
3143
+ let tileChanged = false;
3144
+ for (let i = 0; i < bH_t; i++) {
3145
+ const canvasY = bY + i;
3146
+ const bOff = (canvasY - topLeftY) * bW;
3147
+ const tOff = (canvasY & tileMask) << tileShift;
3148
+ const dS = tOff + (bX & tileMask);
3149
+ for (let j = 0; j < bW_t; j++) {
3150
+ const canvasX = bX + j;
3151
+ const brushA = bD[bOff + (canvasX - topLeftX)];
3152
+ if (brushA === 0) continue;
3153
+ const t = cA * brushA + 128;
3154
+ const blendedA = t + (t >> 8) >> 8;
3155
+ const idx = dS + j;
3156
+ const cur = d32[idx];
3157
+ if (brushA > cur >>> 24) {
3158
+ const next = (cRGB | blendedA << 24) >>> 0;
3159
+ if (cur !== next) {
3160
+ d32[idx] = next;
3161
+ tileChanged = true;
3162
+ }
3163
+ }
3164
+ }
3165
+ }
3166
+ if (tileChanged) changed = true;
3167
+ });
3168
+ });
3169
+ return changed;
3767
3170
  }
3768
- const clip = resolveRectClipping(x, y, w, h, dst.width, dst.height, SCRATCH_RECT);
3769
- if (!clip.inBounds) return;
3770
- const {
3771
- x: finalX,
3772
- y: finalY,
3773
- w: actualW,
3774
- h: actualH
3775
- } = clip;
3776
- const dst32 = dst.data32;
3777
- const dw = dst.width;
3778
- if (actualW === dw && actualH === dst.height && finalX === 0 && finalY === 0) {
3779
- dst32.fill(color);
3780
- return;
3171
+ writePaintBinaryMaskStroke(color, brush, x0, y0, x1, y1) {
3172
+ const alphaIsZero = color >>> 24 === 0;
3173
+ if (alphaIsZero) return false;
3174
+ const {
3175
+ tileShift,
3176
+ tileMask,
3177
+ target
3178
+ } = this.config;
3179
+ const {
3180
+ w: bW,
3181
+ h: bH,
3182
+ data: bD,
3183
+ centerOffsetX,
3184
+ centerOffsetY
3185
+ } = brush;
3186
+ const scratch = this.scratchBounds;
3187
+ let changed = false;
3188
+ forEachLinePoint(x0, y0, x1, y1, (px, py) => {
3189
+ const topLeftX = Math.floor(px + centerOffsetX);
3190
+ const topLeftY = Math.floor(py + centerOffsetY);
3191
+ trimRectBounds(topLeftX, topLeftY, bW, bH, target.width, target.height, scratch);
3192
+ if (scratch.w <= 0 || scratch.h <= 0) return;
3193
+ this.eachTileInBounds(scratch, (tile, bX, bY, bW_t, bH_t) => {
3194
+ const d32 = tile.data32;
3195
+ let tileChanged = false;
3196
+ for (let i = 0; i < bH_t; i++) {
3197
+ const canvasY = bY + i;
3198
+ const bOff = (canvasY - topLeftY) * bW;
3199
+ const tOff = (canvasY & tileMask) << tileShift;
3200
+ const dS = tOff + (bX & tileMask);
3201
+ for (let j = 0; j < bW_t; j++) {
3202
+ const canvasX = bX + j;
3203
+ if (bD[bOff + (canvasX - topLeftX)]) {
3204
+ const idx = dS + j;
3205
+ if (d32[idx] !== color) {
3206
+ d32[idx] = color;
3207
+ tileChanged = true;
3208
+ }
3209
+ }
3210
+ }
3211
+ }
3212
+ if (tileChanged) changed = true;
3213
+ });
3214
+ });
3215
+ return changed;
3216
+ }
3217
+ writeRectStroke(color, brushWidth, brushHeight, x0, y0, x1, y1) {
3218
+ const alphaIsZero = color >>> 24 === 0;
3219
+ if (alphaIsZero) return false;
3220
+ const config = this.config;
3221
+ const tileShift = config.tileShift;
3222
+ const tileMask = config.tileMask;
3223
+ const target = config.target;
3224
+ const scratch = this.scratchBounds;
3225
+ const centerOffsetX = brushWidth - 1 >> 1;
3226
+ const centerOffsetY = brushHeight - 1 >> 1;
3227
+ let changed = false;
3228
+ forEachLinePoint(x0, y0, x1, y1, (px, py) => {
3229
+ const topLeftX = Math.floor(px + centerOffsetX);
3230
+ const topLeftY = Math.floor(py + centerOffsetY);
3231
+ trimRectBounds(topLeftX, topLeftY, brushWidth, brushHeight, target.width, target.height, scratch);
3232
+ if (scratch.w <= 0 || scratch.h <= 0) return;
3233
+ this.eachTileInBounds(scratch, (tile, bX, bY, bW_t, bH_t) => {
3234
+ const d32 = tile.data32;
3235
+ let tileChanged = false;
3236
+ for (let i = 0; i < bH_t; i++) {
3237
+ const canvasY = bY + i;
3238
+ const tOff = (canvasY & tileMask) << tileShift;
3239
+ const dS = tOff + (bX & tileMask);
3240
+ for (let j = 0; j < bW_t; j++) {
3241
+ const idx = dS + j;
3242
+ if (d32[idx] !== color) {
3243
+ d32[idx] = color;
3244
+ tileChanged = true;
3245
+ }
3246
+ }
3247
+ }
3248
+ if (tileChanged) {
3249
+ changed = true;
3250
+ }
3251
+ });
3252
+ });
3253
+ return changed;
3781
3254
  }
3782
- for (let iy = 0; iy < actualH; iy++) {
3783
- const start = (finalY + iy) * dw + finalX;
3784
- const end = start + actualW;
3785
- dst32.fill(color, start, end);
3255
+ clear() {
3256
+ this.tilePool.releaseTiles(this.lookup);
3786
3257
  }
3787
- }
3258
+ };
3788
3259
 
3789
- // src/History/PixelMutator/mutatorClear.ts
3790
- var defaults17 = {
3791
- fillPixelData: fillPixelDataFast
3260
+ // src/PixelTile/PixelTile.ts
3261
+ var PixelTile = class {
3262
+ constructor(id, tx, ty, tileSize, tileArea) {
3263
+ this.id = id;
3264
+ this.tx = tx;
3265
+ this.ty = ty;
3266
+ this.width = this.height = tileSize;
3267
+ this.data32 = new Uint32Array(tileArea);
3268
+ const data8 = new Uint8ClampedArray(this.data32.buffer);
3269
+ this.imageData = new ImageData(data8, tileSize, tileSize);
3270
+ }
3271
+ data32;
3272
+ width;
3273
+ height;
3274
+ imageData;
3792
3275
  };
3793
- var mutatorClear = ((writer, deps = defaults17) => {
3794
- const {
3795
- fillPixelData: fillPixelData2 = defaults17.fillPixelData
3796
- } = deps;
3797
- return {
3798
- clear(rect = {}) {
3799
- const target = writer.config.target;
3800
- const x = rect.x ?? 0;
3801
- const y = rect.y ?? 0;
3802
- const w = rect.w ?? target.width;
3803
- const h = rect.h ?? target.height;
3804
- writer.accumulator.storeRegionBeforeState(x, y, w, h);
3805
- fillPixelData2(target, 0, x, y, w, h);
3806
- }
3807
- };
3808
- });
3809
3276
 
3810
- // src/PixelData/fillPixelData.ts
3811
- var SCRATCH_RECT2 = makeClippedRect();
3812
- function fillPixelData(dst, color, _x, _y, _w, _h) {
3813
- let x;
3814
- let y;
3815
- let w;
3816
- let h;
3817
- if (typeof _x === "object") {
3818
- x = _x.x ?? 0;
3819
- y = _x.y ?? 0;
3820
- w = _x.w ?? dst.width;
3821
- h = _x.h ?? dst.height;
3822
- } else if (typeof _x === "number") {
3823
- x = _x;
3824
- y = _y;
3825
- w = _w;
3826
- h = _h;
3827
- } else {
3828
- x = 0;
3829
- y = 0;
3830
- w = dst.width;
3831
- h = dst.height;
3277
+ // src/PixelTile/PixelTilePool.ts
3278
+ var PixelTilePool = class {
3279
+ pool;
3280
+ tileSize;
3281
+ tileArea;
3282
+ constructor(config) {
3283
+ this.pool = [];
3284
+ this.tileSize = config.tileSize;
3285
+ this.tileArea = config.tileArea;
3832
3286
  }
3833
- const clip = resolveRectClipping(x, y, w, h, dst.width, dst.height, SCRATCH_RECT2);
3834
- if (!clip.inBounds) return false;
3835
- const {
3836
- x: finalX,
3837
- y: finalY,
3838
- w: actualW,
3839
- h: actualH
3840
- } = clip;
3841
- const dst32 = dst.data32;
3842
- const dw = dst.width;
3843
- let hasChanged = false;
3844
- for (let iy = 0; iy < actualH; iy++) {
3845
- const rowOffset = (finalY + iy) * dw;
3846
- const start = rowOffset + finalX;
3847
- const end = start + actualW;
3848
- for (let i = start; i < end; i++) {
3849
- if (dst32[i] !== color) {
3850
- dst32[i] = color;
3851
- hasChanged = true;
3287
+ getTile(id, tx, ty) {
3288
+ let tile = this.pool.pop();
3289
+ if (tile) {
3290
+ tile.id = id;
3291
+ tile.tx = tx;
3292
+ tile.ty = ty;
3293
+ tile.data32.fill(0);
3294
+ return tile;
3295
+ }
3296
+ return new PixelTile(id, tx, ty, this.tileSize, this.tileArea);
3297
+ }
3298
+ releaseTile(tile) {
3299
+ this.pool.push(tile);
3300
+ }
3301
+ releaseTiles(tiles) {
3302
+ let length = tiles.length;
3303
+ for (let i = 0; i < length; i++) {
3304
+ let tile = tiles[i];
3305
+ if (tile) {
3306
+ this.pool.push(tile);
3852
3307
  }
3853
3308
  }
3309
+ tiles.length = 0;
3854
3310
  }
3855
- return hasChanged;
3856
- }
3857
-
3858
- // src/History/PixelMutator/mutatorFill.ts
3859
- var defaults18 = {
3860
- fillPixelData
3861
3311
  };
3862
- var mutatorFill = ((writer, deps = defaults18) => {
3863
- const {
3864
- fillPixelData: fillPixelData2 = defaults18.fillPixelData
3865
- } = deps;
3866
- return {
3867
- fill(color, x = 0, y = 0, w = writer.config.target.width, h = writer.config.target.height) {
3868
- const target = writer.config.target;
3869
- const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3870
- return didChange(fillPixelData2(target, color, x, y, w, h));
3871
- }
3312
+
3313
+ // src/History/PixelWriter.ts
3314
+ var PixelWriter = class {
3315
+ historyManager;
3316
+ accumulator;
3317
+ historyActionFactory;
3318
+ config;
3319
+ pixelTilePool;
3320
+ paintBuffer;
3321
+ mutator;
3322
+ blendPixelDataOpts = {
3323
+ alpha: 255,
3324
+ blendFn: sourceOverPerfect,
3325
+ x: 0,
3326
+ y: 0,
3327
+ w: 0,
3328
+ h: 0
3872
3329
  };
3873
- });
3874
- var mutatorFillRect = ((writer, deps = defaults18) => {
3875
- const {
3876
- fillPixelData: fillPixelData2 = defaults18.fillPixelData
3877
- } = deps;
3878
- return {
3879
- fillRect(color, rect) {
3880
- const target = writer.config.target;
3881
- const didChange = writer.accumulator.storeRegionBeforeState(rect.x, rect.y, rect.w, rect.h);
3882
- return didChange(fillPixelData2(target, color, rect.x, rect.y, rect.w, rect.h));
3330
+ _inProgress = false;
3331
+ constructor(target, mutatorFactory, {
3332
+ tileSize = 256,
3333
+ maxHistorySteps = 50,
3334
+ historyManager = new HistoryManager(maxHistorySteps),
3335
+ historyActionFactory = makeHistoryAction,
3336
+ pixelTilePool,
3337
+ accumulator
3338
+ } = {}) {
3339
+ this.config = new PixelEngineConfig(tileSize, target);
3340
+ this.historyManager = historyManager;
3341
+ this.pixelTilePool = pixelTilePool ?? new PixelTilePool(this.config);
3342
+ this.accumulator = accumulator ?? new PixelAccumulator(this.config, this.pixelTilePool);
3343
+ this.historyActionFactory = historyActionFactory;
3344
+ this.mutator = mutatorFactory(this);
3345
+ this.paintBuffer = new PaintBuffer(this.config, this.pixelTilePool);
3346
+ }
3347
+ /**
3348
+ * Executes `transaction` and commits the resulting pixel changes as a single
3349
+ * undoable history action.
3350
+ *
3351
+ * - If `transaction` throws, all accumulated changes are rolled back and the error
3352
+ * is re-thrown. No action is committed.
3353
+ * - If `transaction` completes without modifying any pixels, no action is committed.
3354
+ * - `withHistory` is not re-entrant. Calling it again from inside `transaction` will
3355
+ * throw immediately to prevent silent data loss from a nested extractPatch.
3356
+ *
3357
+ * @param transaction Callback to be executed inside the transaction.
3358
+ * @param after Called after both undo and redo — use for generic change notifications.
3359
+ * @param afterUndo Called after undo only — use for dimension or state changes specific to undo.
3360
+ * @param afterRedo Called after redo only.
3361
+ */
3362
+ withHistory(transaction, after, afterUndo, afterRedo) {
3363
+ if (this._inProgress) {
3364
+ throw new Error("withHistory is not re-entrant \u2014 commit or rollback the current operation first");
3883
3365
  }
3884
- };
3885
- });
3366
+ this._inProgress = true;
3367
+ try {
3368
+ transaction(this.mutator);
3369
+ } catch (e) {
3370
+ this.accumulator.rollbackAfterError();
3371
+ throw e;
3372
+ } finally {
3373
+ this._inProgress = false;
3374
+ }
3375
+ if (this.accumulator.beforeTiles.length === 0) return;
3376
+ const patch = this.accumulator.extractPatch();
3377
+ const action = this.historyActionFactory(this, patch, after, afterUndo, afterRedo);
3378
+ this.historyManager.commit(action);
3379
+ }
3380
+ resize(newWidth, newHeight, offsetX = 0, offsetY = 0, after, afterUndo, afterRedo, resizeImageDataFn = resizeImageData) {
3381
+ if (this._inProgress) {
3382
+ throw new Error("Cannot resize inside a withHistory callback");
3383
+ }
3384
+ if (this.accumulator.beforeTiles.length > 0) {
3385
+ throw new Error("Cannot resize with an open accumulator \u2014 commit or rollback first");
3386
+ }
3387
+ const config = this.config;
3388
+ const target = config.target;
3389
+ const beforeImageData = target.imageData;
3390
+ const afterImageData = resizeImageDataFn(beforeImageData, newWidth, newHeight, offsetX, offsetY);
3391
+ target.set(afterImageData);
3392
+ this.historyManager.commit({
3393
+ undo: () => {
3394
+ target.set(beforeImageData);
3395
+ afterUndo?.(beforeImageData);
3396
+ after?.(beforeImageData);
3397
+ },
3398
+ redo: () => {
3399
+ target.set(afterImageData);
3400
+ afterRedo?.(afterImageData);
3401
+ after?.(afterImageData);
3402
+ }
3403
+ });
3404
+ }
3405
+ commitPaintBuffer(alpha = 255, blendFn = sourceOverPerfect, blendPixelDataFn = blendPixelData) {
3406
+ const paintBuffer = this.paintBuffer;
3407
+ const tileShift = paintBuffer.config.tileShift;
3408
+ const lookup = paintBuffer.lookup;
3409
+ const opts = this.blendPixelDataOpts;
3410
+ opts.alpha = alpha;
3411
+ opts.blendFn = blendFn;
3412
+ for (let i = 0; i < lookup.length; i++) {
3413
+ const tile = lookup[i];
3414
+ if (tile) {
3415
+ const didChange = this.accumulator.storeTileBeforeState(tile.id, tile.tx, tile.ty);
3416
+ const dx = tile.tx << tileShift;
3417
+ const dy = tile.ty << tileShift;
3418
+ opts.x = dx;
3419
+ opts.y = dy;
3420
+ opts.w = tile.width;
3421
+ opts.h = tile.height;
3422
+ didChange(blendPixelDataFn(this.config.target, tile, opts));
3423
+ }
3424
+ }
3425
+ paintBuffer.clear();
3426
+ }
3427
+ };
3886
3428
 
3887
- // src/PixelData/fillPixelDataBinaryMask.ts
3888
- var SCRATCH_RECT3 = makeClippedRect();
3889
- function fillPixelDataBinaryMask(dst, color, mask, alpha = 255, x = 0, y = 0) {
3890
- if (alpha === 0) return false;
3891
- const maskW = mask.w;
3892
- const maskH = mask.h;
3893
- const clip = resolveRectClipping(x, y, maskW, maskH, dst.width, dst.height, SCRATCH_RECT3);
3894
- if (!clip.inBounds) return false;
3429
+ // src/PixelData/applyAlphaMaskToPixelData.ts
3430
+ function applyAlphaMaskToPixelData(dst, mask, opts = {}) {
3895
3431
  const {
3896
- x: finalX,
3897
- y: finalY,
3898
- w: actualW,
3899
- h: actualH
3900
- } = clip;
3901
- const maskData = mask.data;
3432
+ x: targetX = 0,
3433
+ y: targetY = 0,
3434
+ w: width = dst.width,
3435
+ h: height = dst.height,
3436
+ alpha: globalAlpha = 255,
3437
+ mx = 0,
3438
+ my = 0,
3439
+ invertMask = false
3440
+ } = opts;
3441
+ if (globalAlpha === 0) return false;
3442
+ let x = targetX;
3443
+ let y = targetY;
3444
+ let w = width;
3445
+ let h = height;
3446
+ if (x < 0) {
3447
+ w += x;
3448
+ x = 0;
3449
+ }
3450
+ if (y < 0) {
3451
+ h += y;
3452
+ y = 0;
3453
+ }
3454
+ w = Math.min(w, dst.width - x);
3455
+ h = Math.min(h, dst.height - y);
3456
+ if (w <= 0) return false;
3457
+ if (h <= 0) return false;
3458
+ const mPitch = mask.w;
3459
+ if (mPitch <= 0) return false;
3460
+ const startX = mx + (x - targetX);
3461
+ const startY = my + (y - targetY);
3462
+ const sX0 = Math.max(0, startX);
3463
+ const sY0 = Math.max(0, startY);
3464
+ const sX1 = Math.min(mPitch, startX + w);
3465
+ const sY1 = Math.min(mask.h, startY + h);
3466
+ const finalW = sX1 - sX0;
3467
+ const finalH = sY1 - sY0;
3468
+ if (finalW <= 0) return false;
3469
+ if (finalH <= 0) return false;
3470
+ const xShift = sX0 - startX;
3471
+ const yShift = sY0 - startY;
3902
3472
  const dst32 = dst.data32;
3903
3473
  const dw = dst.width;
3904
- let finalCol = color;
3905
- if (alpha < 255) {
3906
- const baseSrcAlpha = color >>> 24;
3907
- const colorRGB = color & 16777215;
3908
- const a = baseSrcAlpha * alpha + 128 >> 8;
3909
- finalCol = (colorRGB | a << 24) >>> 0;
3910
- }
3911
- let hasChanged = false;
3912
- for (let iy = 0; iy < actualH; iy++) {
3913
- const currentY = finalY + iy;
3914
- const maskY = currentY - y;
3915
- const maskOffset = maskY * maskW;
3916
- const dstRowOffset = currentY * dw;
3917
- for (let ix = 0; ix < actualW; ix++) {
3918
- const currentX = finalX + ix;
3919
- const maskX = currentX - x;
3920
- const maskIndex = maskOffset + maskX;
3921
- if (maskData[maskIndex]) {
3922
- const current = dst32[dstRowOffset + currentX];
3923
- if (current !== finalCol) {
3924
- dst32[dstRowOffset + currentX] = finalCol;
3925
- hasChanged = true;
3474
+ const dStride = dw - finalW;
3475
+ const mStride = mPitch - finalW;
3476
+ const maskData = mask.data;
3477
+ let dIdx = (y + yShift) * dw + (x + xShift);
3478
+ let mIdx = sY0 * mPitch + sX0;
3479
+ let didChange = false;
3480
+ for (let iy = 0; iy < h; iy++) {
3481
+ for (let ix = 0; ix < w; ix++) {
3482
+ const mVal = maskData[mIdx];
3483
+ const effectiveM = invertMask ? 255 - mVal : mVal;
3484
+ let weight = 0;
3485
+ if (effectiveM === 0) {
3486
+ weight = 0;
3487
+ } else if (effectiveM === 255) {
3488
+ weight = globalAlpha;
3489
+ } else if (globalAlpha === 255) {
3490
+ weight = effectiveM;
3491
+ } else {
3492
+ weight = effectiveM * globalAlpha + 128 >> 8;
3493
+ }
3494
+ if (weight === 0) {
3495
+ dst32[dIdx] = (dst32[dIdx] & 16777215) >>> 0;
3496
+ didChange = true;
3497
+ } else if (weight !== 255) {
3498
+ const d = dst32[dIdx];
3499
+ const da = d >>> 24;
3500
+ if (da !== 0) {
3501
+ const finalAlpha = da === 255 ? weight : da * weight + 128 >> 8;
3502
+ const current = dst32[dIdx];
3503
+ const next = (d & 16777215 | finalAlpha << 24) >>> 0;
3504
+ if (current !== next) {
3505
+ dst32[dIdx] = next;
3506
+ didChange = true;
3507
+ }
3926
3508
  }
3927
3509
  }
3510
+ dIdx++;
3511
+ mIdx++;
3928
3512
  }
3513
+ dIdx += dStride;
3514
+ mIdx += mStride;
3929
3515
  }
3930
- return hasChanged;
3516
+ return didChange;
3931
3517
  }
3932
3518
 
3933
- // src/History/PixelMutator/mutatorFillBinaryMask.ts
3934
- var defaults19 = {
3935
- fillPixelDataBinaryMask
3519
+ // src/History/PixelMutator/mutatorApplyAlphaMask.ts
3520
+ var defaults11 = {
3521
+ applyAlphaMaskToPixelData
3936
3522
  };
3937
- var mutatorFillBinaryMask = ((writer, deps = defaults19) => {
3523
+ var mutatorApplyAlphaMask = ((writer, deps = defaults11) => {
3938
3524
  const {
3939
- fillPixelDataBinaryMask: fillPixelDataBinaryMask2 = defaults19.fillPixelDataBinaryMask
3525
+ applyAlphaMaskToPixelData: applyAlphaMaskToPixelData2 = defaults11.applyAlphaMaskToPixelData
3940
3526
  } = deps;
3941
3527
  return {
3942
- fillBinaryMask(color, mask, alpha = 255, x = 0, y = 0) {
3943
- const didChange = writer.accumulator.storeRegionBeforeState(x, y, mask.w, mask.h);
3944
- return didChange(fillPixelDataBinaryMask2(writer.config.target, color, mask, alpha, x, y));
3528
+ applyAlphaMask(mask, opts = {}) {
3529
+ let target = writer.config.target;
3530
+ const {
3531
+ x = 0,
3532
+ y = 0,
3533
+ w = target.width,
3534
+ h = target.height
3535
+ } = opts;
3536
+ const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
3537
+ return didChange(applyAlphaMaskToPixelData2(target, mask, opts));
3945
3538
  }
3946
3539
  };
3947
3540
  });
3948
3541
 
3949
- // src/PixelData/invertPixelData.ts
3950
- var SCRATCH_RECT4 = makeClippedRect();
3951
- function invertPixelData(pixelData, opts = {}) {
3952
- const dst = pixelData;
3542
+ // src/PixelData/applyBinaryMaskToPixelData.ts
3543
+ function applyBinaryMaskToPixelData(dst, mask, opts = {}) {
3953
3544
  const {
3954
3545
  x: targetX = 0,
3955
3546
  y: targetY = 0,
3956
- w: width = pixelData.width,
3957
- h: height = pixelData.height,
3958
- mask,
3547
+ w: width = dst.width,
3548
+ h: height = dst.height,
3549
+ alpha: globalAlpha = 255,
3959
3550
  mx = 0,
3960
3551
  my = 0,
3961
3552
  invertMask = false
3962
3553
  } = opts;
3963
- const clip = resolveRectClipping(targetX, targetY, width, height, dst.width, dst.height, SCRATCH_RECT4);
3964
- if (!clip.inBounds) return false;
3965
- const {
3966
- x,
3967
- y,
3968
- w: actualW,
3969
- h: actualH
3970
- } = clip;
3554
+ if (globalAlpha === 0) return false;
3555
+ let x = targetX;
3556
+ let y = targetY;
3557
+ let w = width;
3558
+ let h = height;
3559
+ if (x < 0) {
3560
+ w += x;
3561
+ x = 0;
3562
+ }
3563
+ if (y < 0) {
3564
+ h += y;
3565
+ y = 0;
3566
+ }
3567
+ w = Math.min(w, dst.width - x);
3568
+ h = Math.min(h, dst.height - y);
3569
+ if (w <= 0 || h <= 0) return false;
3570
+ const mPitch = mask.w;
3571
+ if (mPitch <= 0) return false;
3572
+ const startX = mx + (x - targetX);
3573
+ const startY = my + (y - targetY);
3574
+ const sX0 = Math.max(0, startX);
3575
+ const sY0 = Math.max(0, startY);
3576
+ const sX1 = Math.min(mPitch, startX + w);
3577
+ const sY1 = Math.min(mask.h, startY + h);
3578
+ const finalW = sX1 - sX0;
3579
+ const finalH = sY1 - sY0;
3580
+ if (finalW <= 0 || finalH <= 0) {
3581
+ return false;
3582
+ }
3583
+ const xShift = sX0 - startX;
3584
+ const yShift = sY0 - startY;
3971
3585
  const dst32 = dst.data32;
3972
3586
  const dw = dst.width;
3973
- const mPitch = mask?.w ?? width;
3974
- const dx = x - targetX;
3975
- const dy = y - targetY;
3976
- let dIdx = y * dw + x;
3977
- let mIdx = (my + dy) * mPitch + (mx + dx);
3978
- const dStride = dw - actualW;
3979
- const mStride = mPitch - actualW;
3980
- if (mask) {
3981
- const maskData = mask.data;
3982
- for (let iy = 0; iy < actualH; iy++) {
3983
- for (let ix = 0; ix < actualW; ix++) {
3984
- const mVal = maskData[mIdx];
3985
- const isHit = invertMask ? mVal === 0 : mVal === 1;
3986
- if (isHit) {
3987
- dst32[dIdx] = dst32[dIdx] ^ 16777215;
3587
+ const dStride = dw - finalW;
3588
+ const mStride = mPitch - finalW;
3589
+ const maskData = mask.data;
3590
+ let dIdx = (y + yShift) * dw + (x + xShift);
3591
+ let mIdx = sY0 * mPitch + sX0;
3592
+ let didChange = false;
3593
+ for (let iy = 0; iy < finalH; iy++) {
3594
+ for (let ix = 0; ix < finalW; ix++) {
3595
+ const mVal = maskData[mIdx];
3596
+ const isMaskedOut = invertMask ? mVal !== 0 : mVal === 0;
3597
+ if (isMaskedOut) {
3598
+ const current = dst32[dIdx];
3599
+ const next = (current & 16777215) >>> 0;
3600
+ if (current !== next) {
3601
+ dst32[dIdx] = next;
3602
+ didChange = true;
3603
+ }
3604
+ } else if (globalAlpha !== 255) {
3605
+ const d = dst32[dIdx];
3606
+ const da = d >>> 24;
3607
+ if (da !== 0) {
3608
+ const finalAlpha = da === 255 ? globalAlpha : da * globalAlpha + 128 >> 8;
3609
+ const next = (d & 16777215 | finalAlpha << 24) >>> 0;
3610
+ if (d !== next) {
3611
+ dst32[dIdx] = next;
3612
+ didChange = true;
3613
+ }
3988
3614
  }
3989
- dIdx++;
3990
- mIdx++;
3991
- }
3992
- dIdx += dStride;
3993
- mIdx += mStride;
3994
- }
3995
- } else {
3996
- for (let iy = 0; iy < actualH; iy++) {
3997
- for (let ix = 0; ix < actualW; ix++) {
3998
- dst32[dIdx] = dst32[dIdx] ^ 16777215;
3999
- dIdx++;
4000
3615
  }
4001
- dIdx += dStride;
3616
+ dIdx++;
3617
+ mIdx++;
4002
3618
  }
3619
+ dIdx += dStride;
3620
+ mIdx += mStride;
4003
3621
  }
4004
- return true;
3622
+ return didChange;
4005
3623
  }
4006
3624
 
4007
- // src/History/PixelMutator/mutatorInvert.ts
4008
- var defaults20 = {
4009
- invertPixelData
3625
+ // src/History/PixelMutator/mutatorApplyBinaryMask.ts
3626
+ var defaults12 = {
3627
+ applyBinaryMaskToPixelData
4010
3628
  };
4011
- var mutatorInvert = ((writer, deps = defaults20) => {
3629
+ var mutatorApplyBinaryMask = ((writer, deps = defaults12) => {
4012
3630
  const {
4013
- invertPixelData: invertPixelData2 = defaults20.invertPixelData
3631
+ applyBinaryMaskToPixelData: applyBinaryMaskToPixelData2 = defaults12.applyBinaryMaskToPixelData
4014
3632
  } = deps;
4015
3633
  return {
4016
- invert(opts = {}) {
4017
- const target = writer.config.target;
3634
+ applyBinaryMask(mask, opts = {}) {
3635
+ let target = writer.config.target;
4018
3636
  const {
4019
3637
  x = 0,
4020
3638
  y = 0,
@@ -4022,125 +3640,200 @@ var mutatorInvert = ((writer, deps = defaults20) => {
4022
3640
  h = target.height
4023
3641
  } = opts;
4024
3642
  const didChange = writer.accumulator.storeRegionBeforeState(x, y, w, h);
4025
- return didChange(invertPixelData2(target, opts));
3643
+ return didChange(applyBinaryMaskToPixelData2(target, mask, opts));
4026
3644
  }
4027
3645
  };
4028
3646
  });
4029
3647
 
4030
- // src/History/PixelMutator.ts
4031
- function makeFullPixelMutator(writer) {
4032
- return {
4033
- // @sort
4034
- ...mutatorApplyAlphaMask(writer),
4035
- ...mutatorApplyBinaryMask(writer),
4036
- ...mutatorApplyCircleBrushStroke(writer),
4037
- ...mutatorApplyCirclePencil(writer),
4038
- ...mutatorApplyCirclePencilStroke(writer),
4039
- ...mutatorApplyRectBrush(writer),
4040
- ...mutatorApplyRectBrushStroke(writer),
4041
- ...mutatorApplyRectPencil(writer),
4042
- ...mutatorApplyRectPencilStroke(writer),
4043
- ...mutatorBlendColor(writer),
4044
- ...mutatorBlendColorCircleMask(writer),
4045
- ...mutatorBlendPixel(writer),
4046
- ...mutatorBlendPixelData(writer),
4047
- ...mutatorBlendPixelDataAlphaMask(writer),
4048
- ...mutatorBlendPixelDataBinaryMask(writer),
4049
- ...mutatorClear(writer),
4050
- ...mutatorFill(writer),
4051
- ...mutatorFillBinaryMask(writer),
4052
- ...mutatorFillRect(writer),
4053
- ...mutatorInvert(writer)
4054
- };
4055
- }
4056
-
4057
- // src/PixelTile/PixelTile.ts
4058
- var PixelTile = class {
4059
- constructor(id, tx, ty, tileSize, tileArea) {
4060
- this.id = id;
4061
- this.tx = tx;
4062
- this.ty = ty;
4063
- this.width = this.height = tileSize;
4064
- this.data32 = new Uint32Array(tileArea);
4065
- const data8 = new Uint8ClampedArray(this.data32.buffer);
4066
- this.imageData = new ImageData(data8, tileSize, tileSize);
3648
+ // src/PixelData/blendColorPixelDataAlphaMask.ts
3649
+ function blendColorPixelDataAlphaMask(dst, color, mask, opts = {}) {
3650
+ const targetX = opts.x ?? 0;
3651
+ const targetY = opts.y ?? 0;
3652
+ const w = opts.w ?? mask.w;
3653
+ const h = opts.h ?? mask.h;
3654
+ const globalAlpha = opts.alpha ?? 255;
3655
+ const blendFn = opts.blendFn ?? sourceOverPerfect;
3656
+ const mx = opts.mx ?? 0;
3657
+ const my = opts.my ?? 0;
3658
+ const invertMask = opts.invertMask ?? false;
3659
+ if (globalAlpha === 0) return false;
3660
+ const baseSrcAlpha = color >>> 24;
3661
+ const isOverwrite = blendFn.isOverwrite || false;
3662
+ if (baseSrcAlpha === 0 && !isOverwrite) return false;
3663
+ let x = targetX;
3664
+ let y = targetY;
3665
+ let actualW = w;
3666
+ let actualH = h;
3667
+ if (x < 0) {
3668
+ actualW += x;
3669
+ x = 0;
4067
3670
  }
4068
- data32;
4069
- width;
4070
- height;
4071
- imageData;
4072
- };
4073
-
4074
- // src/PixelTile/PixelTilePool.ts
4075
- var PixelTilePool = class {
4076
- pool;
4077
- tileSize;
4078
- tileArea;
4079
- constructor(config) {
4080
- this.pool = [];
4081
- this.tileSize = config.tileSize;
4082
- this.tileArea = config.tileArea;
3671
+ if (y < 0) {
3672
+ actualH += y;
3673
+ y = 0;
4083
3674
  }
4084
- getTile(id, tx, ty) {
4085
- let tile = this.pool.pop();
4086
- if (tile) {
4087
- tile.id = id;
4088
- tile.tx = tx;
4089
- tile.ty = ty;
4090
- tile.data32.fill(0);
4091
- return tile;
3675
+ actualW = Math.min(actualW, dst.width - x);
3676
+ actualH = Math.min(actualH, dst.height - y);
3677
+ if (actualW <= 0 || actualH <= 0) return false;
3678
+ const dx = x - targetX | 0;
3679
+ const dy = y - targetY | 0;
3680
+ const dst32 = dst.data32;
3681
+ const dw = dst.width;
3682
+ const mPitch = mask.w;
3683
+ const maskData = mask.data;
3684
+ let dIdx = y * dw + x | 0;
3685
+ let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
3686
+ const dStride = dw - actualW | 0;
3687
+ const mStride = mPitch - actualW | 0;
3688
+ const isOpaque = globalAlpha === 255;
3689
+ const colorRGB = color & 16777215;
3690
+ let didChange = false;
3691
+ for (let iy = 0; iy < actualH; iy++) {
3692
+ for (let ix = 0; ix < actualW; ix++) {
3693
+ const mVal = maskData[mIdx];
3694
+ const effM = invertMask ? 255 - mVal : mVal;
3695
+ if (effM === 0) {
3696
+ dIdx++;
3697
+ mIdx++;
3698
+ continue;
3699
+ }
3700
+ let weight = globalAlpha;
3701
+ if (isOpaque) {
3702
+ weight = effM;
3703
+ } else if (effM !== 255) {
3704
+ weight = effM * globalAlpha + 128 >> 8;
3705
+ }
3706
+ if (weight === 0) {
3707
+ dIdx++;
3708
+ mIdx++;
3709
+ continue;
3710
+ }
3711
+ let finalCol = color;
3712
+ if (weight < 255) {
3713
+ const a = baseSrcAlpha * weight + 128 >> 8;
3714
+ if (a === 0 && !isOverwrite) {
3715
+ dIdx++;
3716
+ mIdx++;
3717
+ continue;
3718
+ }
3719
+ finalCol = (colorRGB | a << 24) >>> 0;
3720
+ }
3721
+ const current = dst32[dIdx];
3722
+ const next = blendFn(finalCol, current);
3723
+ if (current !== next) {
3724
+ dst32[dIdx] = next;
3725
+ didChange = true;
3726
+ }
3727
+ dIdx++;
3728
+ mIdx++;
4092
3729
  }
4093
- return new PixelTile(id, tx, ty, this.tileSize, this.tileArea);
3730
+ dIdx += dStride;
3731
+ mIdx += mStride;
4094
3732
  }
4095
- releaseTile(tile) {
4096
- this.pool.push(tile);
3733
+ return didChange;
3734
+ }
3735
+
3736
+ // src/PixelData/blendColorPixelDataBinaryMask.ts
3737
+ function blendColorPixelDataBinaryMask(dst, color, mask, opts = {}) {
3738
+ const targetX = opts.x ?? 0;
3739
+ const targetY = opts.y ?? 0;
3740
+ let w = opts.w ?? mask.w;
3741
+ let h = opts.h ?? mask.h;
3742
+ const globalAlpha = opts.alpha ?? 255;
3743
+ const blendFn = opts.blendFn ?? sourceOverPerfect;
3744
+ const mx = opts.mx ?? 0;
3745
+ const my = opts.my ?? 0;
3746
+ const invertMask = opts.invertMask ?? false;
3747
+ if (globalAlpha === 0) return false;
3748
+ const baseSrcAlpha = color >>> 24;
3749
+ const isOverwrite = blendFn.isOverwrite || false;
3750
+ if (baseSrcAlpha === 0 && !isOverwrite) return false;
3751
+ let x = targetX;
3752
+ let y = targetY;
3753
+ if (x < 0) {
3754
+ w += x;
3755
+ x = 0;
3756
+ }
3757
+ if (y < 0) {
3758
+ h += y;
3759
+ y = 0;
3760
+ }
3761
+ const actualW = Math.min(w, dst.width - x);
3762
+ const actualH = Math.min(h, dst.height - y);
3763
+ if (actualW <= 0 || actualH <= 0) return false;
3764
+ let baseColorWithGlobalAlpha = color;
3765
+ if (globalAlpha < 255) {
3766
+ const a = baseSrcAlpha * globalAlpha + 128 >> 8;
3767
+ if (a === 0 && !isOverwrite) return false;
3768
+ baseColorWithGlobalAlpha = (color & 16777215 | a << 24) >>> 0;
4097
3769
  }
4098
- releaseTiles(tiles) {
4099
- let length = tiles.length;
4100
- for (let i = 0; i < length; i++) {
4101
- let tile = tiles[i];
4102
- if (tile) {
4103
- this.pool.push(tile);
3770
+ const dx = x - targetX | 0;
3771
+ const dy = y - targetY | 0;
3772
+ const dst32 = dst.data32;
3773
+ const dw = dst.width;
3774
+ const mPitch = mask.w;
3775
+ const maskData = mask.data;
3776
+ let dIdx = y * dw + x | 0;
3777
+ let mIdx = (my + dy) * mPitch + (mx + dx) | 0;
3778
+ const dStride = dw - actualW | 0;
3779
+ const mStride = mPitch - actualW | 0;
3780
+ const skipVal = invertMask ? 1 : 0;
3781
+ let didChange = false;
3782
+ for (let iy = 0; iy < actualH; iy++) {
3783
+ for (let ix = 0; ix < actualW; ix++) {
3784
+ if (maskData[mIdx] === skipVal) {
3785
+ dIdx++;
3786
+ mIdx++;
3787
+ continue;
3788
+ }
3789
+ const current = dst32[dIdx];
3790
+ const next = blendFn(baseColorWithGlobalAlpha, current);
3791
+ if (current !== next) {
3792
+ dst32[dIdx] = next;
3793
+ didChange = true;
4104
3794
  }
3795
+ dIdx++;
3796
+ mIdx++;
4105
3797
  }
4106
- tiles.length = 0;
3798
+ dIdx += dStride;
3799
+ mIdx += mStride;
4107
3800
  }
4108
- };
3801
+ return didChange;
3802
+ }
4109
3803
 
4110
- // src/History/PixelWriter.ts
4111
- var PixelWriter = class {
4112
- historyManager;
4113
- accumulator;
4114
- historyActionFactory;
4115
- config;
4116
- mutator;
4117
- constructor(target, mutatorFactory, {
4118
- tileSize = 256,
4119
- maxHistorySteps = 50,
4120
- historyManager = new HistoryManager(maxHistorySteps),
4121
- historyActionFactory = makeHistoryAction,
4122
- pixelTilePool
4123
- } = {}) {
4124
- this.config = new PixelEngineConfig(tileSize, target);
4125
- this.historyManager = historyManager;
4126
- pixelTilePool ??= new PixelTilePool(this.config);
4127
- this.accumulator = new PixelAccumulator(this.config, pixelTilePool);
4128
- this.historyActionFactory = historyActionFactory;
4129
- this.mutator = mutatorFactory(this);
4130
- }
4131
- withHistory(cb, after, afterUndo, afterRedo) {
4132
- try {
4133
- cb(this.mutator);
4134
- } catch (e) {
4135
- this.accumulator.rollback();
4136
- throw e;
4137
- }
4138
- if (this.accumulator.beforeTiles.length === 0) return;
4139
- const patch = this.accumulator.extractPatch();
4140
- const action = this.historyActionFactory(this, patch, after, afterUndo, afterRedo);
4141
- this.historyManager.commit(action);
4142
- }
3804
+ // src/History/PixelMutator/mutatorBlendPaintMask.ts
3805
+ var defaults13 = {
3806
+ blendColorPixelDataAlphaMask,
3807
+ blendColorPixelDataBinaryMask
4143
3808
  };
3809
+ var mutatorBlendPaintMask = ((writer, deps = defaults13) => {
3810
+ const {
3811
+ blendColorPixelDataBinaryMask: blendColorPixelDataBinaryMask2 = defaults13.blendColorPixelDataBinaryMask,
3812
+ blendColorPixelDataAlphaMask: blendColorPixelDataAlphaMask2 = defaults13.blendColorPixelDataAlphaMask
3813
+ } = deps;
3814
+ const OPTS = {
3815
+ x: 0,
3816
+ y: 0,
3817
+ blendFn: sourceOverPerfect,
3818
+ alpha: 255
3819
+ };
3820
+ return {
3821
+ blendColorPaintMask(color, mask, x, y, alpha = 255, blendFn = sourceOverPerfect) {
3822
+ const tx = x + mask.centerOffsetX;
3823
+ const ty = y + mask.centerOffsetY;
3824
+ const didChange = writer.accumulator.storeRegionBeforeState(tx, ty, mask.w, mask.h);
3825
+ OPTS.x = tx;
3826
+ OPTS.y = ty;
3827
+ OPTS.alpha = alpha;
3828
+ OPTS.blendFn = blendFn;
3829
+ if (mask.type === 1 /* BINARY */) {
3830
+ return didChange(blendColorPixelDataBinaryMask2(writer.config.target, color, mask, OPTS));
3831
+ } else {
3832
+ return didChange(blendColorPixelDataAlphaMask2(writer.config.target, color, mask, OPTS));
3833
+ }
3834
+ }
3835
+ };
3836
+ });
4144
3837
 
4145
3838
  // src/ImageData/copyImageData.ts
4146
3839
  function copyImageData({
@@ -4263,35 +3956,6 @@ function resampleImageData(source, factor) {
4263
3956
  return new ImageData(uint8ClampedArray, width, height);
4264
3957
  }
4265
3958
 
4266
- // src/ImageData/resizeImageData.ts
4267
- function resizeImageData(target, newWidth, newHeight, offsetX = 0, offsetY = 0) {
4268
- const result = new ImageData(newWidth, newHeight);
4269
- const {
4270
- width: oldW,
4271
- height: oldH,
4272
- data: oldData
4273
- } = target;
4274
- const newData = result.data;
4275
- const x0 = Math.max(0, offsetX);
4276
- const y0 = Math.max(0, offsetY);
4277
- const x1 = Math.min(newWidth, offsetX + oldW);
4278
- const y1 = Math.min(newHeight, offsetY + oldH);
4279
- if (x1 <= x0 || y1 <= y0) {
4280
- return result;
4281
- }
4282
- const rowCount = y1 - y0;
4283
- const rowLen = (x1 - x0) * 4;
4284
- for (let row = 0; row < rowCount; row++) {
4285
- const dstY = y0 + row;
4286
- const srcY = dstY - offsetY;
4287
- const srcX = x0 - offsetX;
4288
- const dstStart = (dstY * newWidth + x0) * 4;
4289
- const srcStart = (srcY * oldW + srcX) * 4;
4290
- newData.set(oldData.subarray(srcStart, srcStart + rowLen), dstStart);
4291
- }
4292
- return result;
4293
- }
4294
-
4295
3959
  // src/ImageData/ReusableImageData.ts
4296
3960
  function makeReusableImageData() {
4297
3961
  let imageData = null;
@@ -4706,62 +4370,6 @@ function makeBinaryMask(w, h, data) {
4706
4370
  };
4707
4371
  }
4708
4372
 
4709
- // src/Mask/CircleAlphaMask.ts
4710
- function makeCircleAlphaMask(size, fallOff = () => 1) {
4711
- const area = size * size;
4712
- const data = new Uint8Array(area);
4713
- const radius = size / 2;
4714
- const invR = 1 / radius;
4715
- const minOffset = -Math.ceil(radius - 0.5);
4716
- for (let y = 0; y < size; y++) {
4717
- for (let x = 0; x < size; x++) {
4718
- const dx = x - radius + 0.5;
4719
- const dy = y - radius + 0.5;
4720
- const distSqr = dx * dx + dy * dy;
4721
- if (distSqr <= radius * radius) {
4722
- const dist = Math.sqrt(distSqr);
4723
- data[y * size + x] = fallOff(1 - dist * invR) * 255 | 0;
4724
- }
4725
- }
4726
- }
4727
- return {
4728
- type: 0 /* ALPHA */,
4729
- data,
4730
- w: size,
4731
- h: size,
4732
- radius,
4733
- size,
4734
- minOffset
4735
- };
4736
- }
4737
-
4738
- // src/Mask/CircleBinaryMask.ts
4739
- function makeCircleBinaryMask(size) {
4740
- const area = size * size;
4741
- const data = new Uint8Array(area);
4742
- const radius = size / 2;
4743
- const minOffset = -Math.ceil(radius - 0.5);
4744
- for (let y = 0; y < size; y++) {
4745
- for (let x = 0; x < size; x++) {
4746
- const dx = x - radius + 0.5;
4747
- const dy = y - radius + 0.5;
4748
- const distSqr = dx * dx + dy * dy;
4749
- if (distSqr <= radius * radius) {
4750
- data[y * size + x] = 1;
4751
- }
4752
- }
4753
- }
4754
- return {
4755
- type: 1 /* BINARY */,
4756
- data,
4757
- w: size,
4758
- h: size,
4759
- radius,
4760
- size,
4761
- minOffset
4762
- };
4763
- }
4764
-
4765
4373
  // src/Mask/applyBinaryMaskToAlphaMask.ts
4766
4374
  function applyBinaryMaskToAlphaMask(alphaMaskDst, binaryMaskSrc, opts = {}) {
4767
4375
  const {
@@ -5462,103 +5070,140 @@ function writePixelDataBuffer(target, data, _x, _y, _w, _h) {
5462
5070
  }
5463
5071
  }
5464
5072
 
5465
- // src/PixelTile/PaintBuffer.ts
5466
- var PaintBuffer = class {
5467
- constructor(config, tilePool) {
5468
- this.config = config;
5469
- this.tilePool = tilePool;
5470
- this.lookup = [];
5073
+ // src/PixelData/writePaintBufferToPixelData.ts
5074
+ function writePaintBufferToPixelData(target, paintBuffer, writePixelDataBufferFn = writePixelDataBuffer) {
5075
+ const tileShift = paintBuffer.config.tileShift;
5076
+ const lookup = paintBuffer.lookup;
5077
+ for (let i = 0; i < lookup.length; i++) {
5078
+ const tile = lookup[i];
5079
+ if (tile) {
5080
+ const dx = tile.tx << tileShift;
5081
+ const dy = tile.ty << tileShift;
5082
+ writePixelDataBufferFn(target, tile.data32, dx, dy, tile.width, tile.height);
5083
+ }
5471
5084
  }
5472
- lookup;
5473
- processMaskTiles(mask, callback) {
5474
- const {
5475
- tileShift,
5476
- targetColumns
5477
- } = this.config;
5478
- const x1 = mask.x >> tileShift;
5479
- const y1 = mask.y >> tileShift;
5480
- const x2 = mask.x + mask.w - 1 >> tileShift;
5481
- const y2 = mask.y + mask.h - 1 >> tileShift;
5482
- for (let ty = y1; ty <= y2; ty++) {
5483
- const tileRowIndex = ty * targetColumns;
5484
- const tileTop = ty << tileShift;
5485
- for (let tx = x1; tx <= x2; tx++) {
5486
- const id = tileRowIndex + tx;
5487
- let tile = this.lookup[id];
5488
- if (!tile) {
5489
- tile = this.tilePool.getTile(id, tx, ty);
5490
- this.lookup[id] = tile;
5085
+ }
5086
+
5087
+ // src/Paint/makeCirclePaintAlphaMask.ts
5088
+ function makeCirclePaintAlphaMask(size, fallOff = (d) => d) {
5089
+ const area = size * size;
5090
+ const data = new Uint8Array(area);
5091
+ const radius = size / 2;
5092
+ const invR = 1 / radius;
5093
+ const centerOffset = -Math.ceil(radius - 0.5);
5094
+ for (let y = 0; y < size; y++) {
5095
+ const rowOffset = y * size;
5096
+ const dy = y - radius + 0.5;
5097
+ const dy2 = dy * dy;
5098
+ for (let x = 0; x < size; x++) {
5099
+ const dx = x - radius + 0.5;
5100
+ const distSqr = dx * dx + dy2;
5101
+ if (distSqr <= radius * radius) {
5102
+ const dist = Math.sqrt(distSqr) * invR;
5103
+ const strength = fallOff(1 - dist);
5104
+ if (strength > 0) {
5105
+ const intensity = strength * 255 | 0;
5106
+ data[rowOffset + x] = Math.max(0, Math.min(255, intensity));
5491
5107
  }
5492
- const tileLeft = tx << tileShift;
5493
- const startX = Math.max(mask.x, tileLeft);
5494
- const endX = Math.min(mask.x + mask.w, tileLeft + this.config.tileSize);
5495
- const startY = Math.max(mask.y, tileTop);
5496
- const endY = Math.min(mask.y + mask.h, tileTop + this.config.tileSize);
5497
- callback(tile, startX, startY, endX - startX, endY - startY, startX - mask.x, startY - mask.y);
5498
5108
  }
5499
5109
  }
5500
5110
  }
5501
- writeColorBinaryMaskRect(color, mask) {
5502
- const {
5503
- tileShift,
5504
- tileMask
5505
- } = this.config;
5506
- const maskData = mask.data;
5507
- const maskW = mask.w;
5508
- this.processMaskTiles(mask, (tile, bX, bY, bW, bH, mX, mY) => {
5509
- const data32 = tile.data32;
5510
- const startTileX = bX & tileMask;
5511
- for (let i = 0; i < bH; i++) {
5512
- const tileY = bY + i & tileMask;
5513
- const maskY = mY + i;
5514
- const tileRowOffset = tileY << tileShift;
5515
- const maskRowOffset = maskY * maskW;
5516
- const destStart = tileRowOffset + startTileX;
5517
- const maskStart = maskRowOffset + mX;
5518
- for (let j = 0; j < bW; j++) {
5519
- if (maskData[maskStart + j]) {
5520
- data32[destStart + j] = color;
5521
- }
5522
- }
5111
+ return {
5112
+ type: 0 /* ALPHA */,
5113
+ data,
5114
+ w: size,
5115
+ h: size,
5116
+ centerOffsetX: centerOffset,
5117
+ centerOffsetY: centerOffset
5118
+ };
5119
+ }
5120
+
5121
+ // src/Paint/makeCirclePaintBinaryMask.ts
5122
+ function makeCirclePaintBinaryMask(size) {
5123
+ const area = size * size;
5124
+ const data = new Uint8Array(area);
5125
+ const radius = size / 2;
5126
+ const centerOffset = -Math.ceil(radius - 0.5);
5127
+ for (let y = 0; y < size; y++) {
5128
+ for (let x = 0; x < size; x++) {
5129
+ const dx = x - radius + 0.5;
5130
+ const dy = y - radius + 0.5;
5131
+ const distSqr = dx * dx + dy * dy;
5132
+ if (distSqr <= radius * radius) {
5133
+ data[y * size + x] = 1;
5523
5134
  }
5524
- });
5135
+ }
5525
5136
  }
5526
- writeColorAlphaMaskRect(color, mask) {
5527
- const {
5528
- tileShift,
5529
- tileMask
5530
- } = this.config;
5531
- const maskData = mask.data;
5532
- const maskW = mask.w;
5533
- const colorRGB = color & 16777215;
5534
- const colorA = color >>> 24;
5535
- this.processMaskTiles(mask, (tile, bX, bY, bW, bH, mX, mY) => {
5536
- const data32 = tile.data32;
5537
- const startTileX = bX & tileMask;
5538
- for (let i = 0; i < bH; i++) {
5539
- const tileY = bY + i & tileMask;
5540
- const maskY = mY + i;
5541
- const tileRowOffset = tileY << tileShift;
5542
- const maskRowOffset = maskY * maskW;
5543
- const destStart = tileRowOffset + startTileX;
5544
- const maskStart = maskRowOffset + mX;
5545
- for (let j = 0; j < bW; j++) {
5546
- const maskA = maskData[maskStart + j];
5547
- if (maskA > 0) {
5548
- const finalA = colorA * maskA + 128 >> 8;
5549
- data32[destStart + j] = (colorRGB | finalA << 24) >>> 0;
5550
- }
5551
- }
5137
+ return {
5138
+ type: 1 /* BINARY */,
5139
+ data,
5140
+ w: size,
5141
+ h: size,
5142
+ centerOffsetX: centerOffset,
5143
+ centerOffsetY: centerOffset
5144
+ };
5145
+ }
5146
+
5147
+ // src/Internal/helpers.ts
5148
+ var macro_halfAndFloor = (value) => value >> 1;
5149
+
5150
+ // src/Paint/makePaintMask.ts
5151
+ function makePaintBinaryMask(mask) {
5152
+ return {
5153
+ type: 1 /* BINARY */,
5154
+ data: mask.data,
5155
+ w: mask.w,
5156
+ h: mask.h,
5157
+ centerOffsetX: -macro_halfAndFloor(mask.w),
5158
+ centerOffsetY: -macro_halfAndFloor(mask.h)
5159
+ };
5160
+ }
5161
+ function makePaintAlphaMask(mask) {
5162
+ return {
5163
+ type: 0 /* ALPHA */,
5164
+ data: mask.data,
5165
+ w: mask.w,
5166
+ h: mask.h,
5167
+ centerOffsetX: -macro_halfAndFloor(mask.w),
5168
+ centerOffsetY: -macro_halfAndFloor(mask.h)
5169
+ };
5170
+ }
5171
+
5172
+ // src/Paint/makeRectFalloffPaintAlphaMask.ts
5173
+ function makeRectFalloffPaintAlphaMask(width, height, fallOff = (d) => d) {
5174
+ const fPx = Math.floor(width / 2);
5175
+ const fPy = Math.floor(height / 2);
5176
+ const invHalfW = 2 / width;
5177
+ const invHalfH = 2 / height;
5178
+ const offX = width % 2 === 0 ? 0.5 : 0;
5179
+ const offY = height % 2 === 0 ? 0.5 : 0;
5180
+ const area = width * height;
5181
+ const data = new Uint8Array(area);
5182
+ for (let y = 0; y < height; y++) {
5183
+ const dy = Math.abs(y - fPy + offY) * invHalfH;
5184
+ const rowOffset = y * width;
5185
+ for (let x = 0; x < width; x++) {
5186
+ const dx = Math.abs(x - fPx + offX) * invHalfW;
5187
+ const dist = dx > dy ? dx : dy;
5188
+ const strength = fallOff(1 - dist);
5189
+ if (strength > 0) {
5190
+ const intensity = strength * 255 | 0;
5191
+ data[rowOffset + x] = Math.max(0, Math.min(255, intensity));
5552
5192
  }
5553
- });
5554
- }
5555
- clear() {
5556
- this.tilePool.releaseTiles(this.lookup);
5193
+ }
5557
5194
  }
5558
- };
5195
+ return {
5196
+ type: 0 /* ALPHA */,
5197
+ data,
5198
+ w: width,
5199
+ h: height,
5200
+ centerOffsetX: -macro_halfAndFloor(width),
5201
+ centerOffsetY: -macro_halfAndFloor(height)
5202
+ };
5203
+ }
5559
5204
 
5560
- // src/PixelTile/PaintBufferRenderer.ts
5561
- function makePaintBufferRenderer(paintBuffer, offscreenCanvasClass = OffscreenCanvas) {
5205
+ // src/Paint/PaintBufferCanvasRenderer.ts
5206
+ function makePaintBufferCanvasRenderer(paintBuffer, offscreenCanvasClass = OffscreenCanvas) {
5562
5207
  const config = paintBuffer.config;
5563
5208
  const tileSize = config.tileSize;
5564
5209
  const tileShift = config.tileShift;
@@ -5567,16 +5212,20 @@ function makePaintBufferRenderer(paintBuffer, offscreenCanvasClass = OffscreenCa
5567
5212
  const ctx = canvas.getContext("2d");
5568
5213
  if (!ctx) throw new Error(CANVAS_CTX_FAILED);
5569
5214
  ctx.imageSmoothingEnabled = false;
5570
- return function drawPaintBuffer(target) {
5215
+ return function drawPaintBuffer(targetCtx, alpha = 255, compOperation = "source-over") {
5216
+ targetCtx.globalAlpha = alpha / 255;
5217
+ targetCtx.globalCompositeOperation = compOperation;
5571
5218
  for (let i = 0; i < lookup.length; i++) {
5572
5219
  const tile = lookup[i];
5573
5220
  if (tile) {
5574
5221
  const dx = tile.tx << tileShift;
5575
5222
  const dy = tile.ty << tileShift;
5576
5223
  ctx.putImageData(tile.imageData, 0, 0);
5577
- target.drawImage(canvas, dx, dy);
5224
+ targetCtx.drawImage(canvas, dx, dy);
5578
5225
  }
5579
5226
  }
5227
+ targetCtx.globalAlpha = 1;
5228
+ targetCtx.globalCompositeOperation = "source-over";
5580
5229
  };
5581
5230
  }
5582
5231
  // Annotate the CommonJS export names for ESM import in node:
@@ -5584,6 +5233,7 @@ function makePaintBufferRenderer(paintBuffer, offscreenCanvasClass = OffscreenCa
5584
5233
  BASE_FAST_BLEND_MODE_FUNCTIONS,
5585
5234
  BASE_PERFECT_BLEND_MODE_FUNCTIONS,
5586
5235
  BaseBlendMode,
5236
+ CANVAS_COMPOSITE_MAP,
5587
5237
  CANVAS_CTX_FAILED,
5588
5238
  HistoryManager,
5589
5239
  IndexedImage,
@@ -5602,13 +5252,11 @@ function makePaintBufferRenderer(paintBuffer, offscreenCanvasClass = OffscreenCa
5602
5252
  applyBinaryMaskToAlphaMask,
5603
5253
  applyBinaryMaskToPixelData,
5604
5254
  applyPatchTiles,
5605
- applyRectBrushToPixelData,
5606
5255
  base64DecodeArrayBuffer,
5607
5256
  base64EncodeArrayBuffer,
5608
5257
  blendColorPixelData,
5609
5258
  blendColorPixelDataAlphaMask,
5610
5259
  blendColorPixelDataBinaryMask,
5611
- blendColorPixelDataCircleMask,
5612
5260
  blendPixel,
5613
5261
  blendPixelData,
5614
5262
  blendPixelDataAlphaMask,
@@ -5650,12 +5298,8 @@ function makePaintBufferRenderer(paintBuffer, offscreenCanvasClass = OffscreenCa
5650
5298
  fillPixelDataFast,
5651
5299
  floodFillSelection,
5652
5300
  forEachLinePoint,
5653
- getCircleBrushOrPencilBounds,
5654
- getCircleBrushOrPencilStrokeBounds,
5655
5301
  getImageDataFromClipboard,
5656
5302
  getIndexedImageColorCounts,
5657
- getRectBrushOrPencilBounds,
5658
- getRectBrushOrPencilStrokeBounds,
5659
5303
  getRectsBounds,
5660
5304
  getSupportedPixelFormats,
5661
5305
  hardLightFast,
@@ -5689,15 +5333,18 @@ function makePaintBufferRenderer(paintBuffer, offscreenCanvasClass = OffscreenCa
5689
5333
  makeBinaryMask,
5690
5334
  makeBlendModeRegistry,
5691
5335
  makeCanvasFrameRenderer,
5692
- makeCircleAlphaMask,
5693
- makeCircleBinaryMask,
5336
+ makeCirclePaintAlphaMask,
5337
+ makeCirclePaintBinaryMask,
5694
5338
  makeFastBlendModeRegistry,
5695
5339
  makeFullPixelMutator,
5696
5340
  makeHistoryAction,
5697
5341
  makeImageDataLike,
5698
- makePaintBufferRenderer,
5342
+ makePaintAlphaMask,
5343
+ makePaintBinaryMask,
5344
+ makePaintBufferCanvasRenderer,
5699
5345
  makePerfectBlendModeRegistry,
5700
5346
  makePixelCanvas,
5347
+ makeRectFalloffPaintAlphaMask,
5701
5348
  makeReusableCanvas,
5702
5349
  makeReusableImageData,
5703
5350
  makeReusableOffscreenCanvas,
@@ -5709,15 +5356,8 @@ function makePaintBufferRenderer(paintBuffer, offscreenCanvasClass = OffscreenCa
5709
5356
  multiplyPerfect,
5710
5357
  mutatorApplyAlphaMask,
5711
5358
  mutatorApplyBinaryMask,
5712
- mutatorApplyCircleBrushStroke,
5713
- mutatorApplyCirclePencil,
5714
- mutatorApplyCirclePencilStroke,
5715
- mutatorApplyRectBrush,
5716
- mutatorApplyRectBrushStroke,
5717
- mutatorApplyRectPencil,
5718
- mutatorApplyRectPencilStroke,
5719
5359
  mutatorBlendColor,
5720
- mutatorBlendColorCircleMask,
5360
+ mutatorBlendPaintMask,
5721
5361
  mutatorBlendPixel,
5722
5362
  mutatorBlendPixelData,
5723
5363
  mutatorBlendPixelDataAlphaMask,
@@ -5757,6 +5397,7 @@ function makePaintBufferRenderer(paintBuffer, offscreenCanvasClass = OffscreenCa
5757
5397
  subtractFast,
5758
5398
  subtractPerfect,
5759
5399
  toBlendModeIndexAndName,
5400
+ trimMaskRectBounds,
5760
5401
  trimRectBounds,
5761
5402
  uInt32ArrayToImageData,
5762
5403
  uInt32ArrayToImageDataLike,
@@ -5772,6 +5413,7 @@ function makePaintBufferRenderer(paintBuffer, offscreenCanvasClass = OffscreenCa
5772
5413
  writeImageDataBuffer,
5773
5414
  writeImageDataToClipboard,
5774
5415
  writeImgBlobToClipboard,
5416
+ writePaintBufferToPixelData,
5775
5417
  writePixelDataBuffer
5776
5418
  });
5777
5419
  //# sourceMappingURL=index.dev.cjs.map