pixel-data-js 0.25.0 → 0.26.0

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