pixel-data-js 0.20.0 → 0.22.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/index.dev.cjs +722 -357
  2. package/dist/index.dev.cjs.map +1 -1
  3. package/dist/index.dev.js +709 -356
  4. package/dist/index.dev.js.map +1 -1
  5. package/dist/index.prod.cjs +722 -357
  6. package/dist/index.prod.cjs.map +1 -1
  7. package/dist/index.prod.d.ts +283 -128
  8. package/dist/index.prod.js +709 -356
  9. package/dist/index.prod.js.map +1 -1
  10. package/package.json +1 -1
  11. package/src/Algorithm/floodFillSelection.ts +12 -14
  12. package/src/BlendModes/toBlendModeIndexAndName.ts +0 -7
  13. package/src/Clipboard/writeImgBlobToClipboard.ts +1 -1
  14. package/src/History/PixelMutator/mutatorApplyAlphaMask.ts +3 -0
  15. package/src/History/PixelMutator/mutatorApplyBinaryMask.ts +3 -0
  16. package/src/History/PixelMutator/mutatorApplyCircleBrush.ts +25 -6
  17. package/src/History/PixelMutator/mutatorApplyCircleBrushStroke.ts +89 -46
  18. package/src/History/PixelMutator/mutatorApplyCirclePencil.ts +7 -7
  19. package/src/History/PixelMutator/mutatorApplyCirclePencilStroke.ts +81 -41
  20. package/src/History/PixelMutator/mutatorApplyRectBrush.ts +3 -0
  21. package/src/History/PixelMutator/mutatorApplyRectBrushStroke.ts +18 -5
  22. package/src/History/PixelMutator/mutatorApplyRectPencil.ts +3 -0
  23. package/src/History/PixelMutator/mutatorApplyRectPencilStroke.ts +19 -4
  24. package/src/History/PixelMutator/mutatorBlendColor.ts +4 -0
  25. package/src/History/PixelMutator/mutatorBlendPixelData.ts +4 -0
  26. package/src/History/PixelMutator/mutatorClear.ts +11 -8
  27. package/src/History/PixelMutator/mutatorFill.ts +4 -0
  28. package/src/History/PixelMutator/mutatorFillBinaryMask.ts +28 -0
  29. package/src/History/PixelMutator/mutatorInvert.ts +3 -0
  30. package/src/ImageData/extractImageDataBuffer.ts +3 -3
  31. package/src/ImageData/{imageDataToAlphaMask.ts → imageDataToAlphaMaskBuffer.ts} +3 -4
  32. package/src/ImageData/resizeImageData.ts +3 -5
  33. package/src/ImageData/writeImageDataBuffer.ts +7 -7
  34. package/src/Mask/AlphaMask.ts +16 -0
  35. package/src/Mask/BinaryMask.ts +16 -0
  36. package/src/Mask/CircleBrushAlphaMask.ts +32 -0
  37. package/src/Mask/CircleBrushBinaryMask.ts +30 -0
  38. package/src/Mask/applyBinaryMaskToAlphaMask.ts +12 -9
  39. package/src/Mask/copyMask.ts +9 -3
  40. package/src/Mask/extractMask.ts +33 -31
  41. package/src/Mask/extractMaskBuffer.ts +87 -0
  42. package/src/Mask/invertMask.ts +6 -4
  43. package/src/Mask/mergeAlphaMasks.ts +11 -10
  44. package/src/Mask/mergeBinaryMasks.ts +10 -9
  45. package/src/Mask/setMaskData.ts +7 -0
  46. package/src/MaskRect/merge2BinaryMaskRects.ts +81 -0
  47. package/src/MaskRect/mergeBinaryMaskRects.ts +39 -0
  48. package/src/MaskRect/subtractBinaryMaskRects.ts +80 -0
  49. package/src/PixelData/applyAlphaMaskToPixelData.ts +8 -5
  50. package/src/PixelData/applyBinaryMaskToPixelData.ts +8 -9
  51. package/src/PixelData/applyCircleBrushToPixelData.ts +54 -62
  52. package/src/PixelData/blendColorPixelDataAlphaMask.ts +35 -25
  53. package/src/PixelData/blendColorPixelDataBinaryMask.ts +26 -19
  54. package/src/PixelData/blendPixelDataAlphaMask.ts +3 -3
  55. package/src/PixelData/blendPixelDataBinaryMask.ts +3 -3
  56. package/src/PixelData/clearPixelData.ts +2 -2
  57. package/src/PixelData/fillPixelData.ts +15 -42
  58. package/src/PixelData/fillPixelDataBinaryMask.ts +79 -0
  59. package/src/PixelData/invertPixelData.ts +3 -3
  60. package/src/PixelData/pixelDataToAlphaMask.ts +4 -2
  61. package/src/PixelData/writePixelDataBuffer.ts +2 -3
  62. package/src/Rect/getRectsBounds.ts +22 -0
  63. package/src/Rect/trimRectBounds.ts +20 -17
  64. package/src/_types.ts +55 -29
  65. package/src/index.ts +16 -1
@@ -0,0 +1,87 @@
1
+ import type { Rect } from '../_types'
2
+
3
+ /**
4
+ * Extracts a rectangular region from a 1D {@link Uint8Array} mask.
5
+ * This utility calculates the necessary offsets based on the `maskWidth` to
6
+ * slice out a specific area.
7
+ *
8
+ * @param maskBuffer - The source 1D array representing the full 2D mask.
9
+ * @param maskWidth - The width of the original source mask (stride).
10
+ * @param rect - A {@link Rect} object defining the region to extract.
11
+ * @returns A new {@link Uint8Array} containing the extracted region.
12
+ */
13
+ export function extractMaskBuffer(
14
+ maskBuffer: Uint8Array,
15
+ maskWidth: number,
16
+ rect: Rect,
17
+ ): Uint8Array
18
+
19
+ /**
20
+ * @param maskBuffer - The source 1D array representing the full 2D mask.
21
+ * @param maskWidth - The width of the original source mask (stride).
22
+ * @param x - The starting horizontal coordinate.
23
+ * @param y - The starting vertical coordinate.
24
+ * @param w - The width of the region to extract.
25
+ * @param h - The height of the region to extract.
26
+ * @returns A new {@link Uint8Array} containing the extracted region.
27
+ */
28
+ export function extractMaskBuffer(
29
+ maskBuffer: Uint8Array,
30
+ maskWidth: number,
31
+ x: number,
32
+ y: number,
33
+ w: number,
34
+ h: number,
35
+ ): Uint8Array
36
+ export function extractMaskBuffer(
37
+ maskBuffer: Uint8Array,
38
+ maskWidth: number,
39
+ xOrRect: number | Rect,
40
+ y?: number,
41
+ w?: number,
42
+ h?: number,
43
+ ): Uint8Array {
44
+ let finalX: number
45
+ let finalY: number
46
+ let finalW: number
47
+ let finalH: number
48
+
49
+ if (typeof xOrRect === 'object') {
50
+ finalX = xOrRect.x
51
+ finalY = xOrRect.y
52
+ finalW = xOrRect.w
53
+ finalH = xOrRect.h
54
+ } else {
55
+ finalX = xOrRect
56
+ finalY = y!
57
+ finalW = w!
58
+ finalH = h!
59
+ }
60
+
61
+ const out = new Uint8Array(finalW * finalH)
62
+ const srcH = maskBuffer.length / maskWidth
63
+
64
+ for (let row = 0; row < finalH; row++) {
65
+ const currentSrcY = finalY + row
66
+
67
+ if (currentSrcY < 0 || currentSrcY >= srcH) {
68
+ continue
69
+ }
70
+
71
+ const start = Math.max(0, finalX)
72
+ const end = Math.min(maskWidth, finalX + finalW)
73
+
74
+ if (start < end) {
75
+ const srcOffset = currentSrcY * maskWidth + start
76
+ const dstOffset = (row * finalW) + (start - finalX)
77
+ const count = end - start
78
+
79
+ out.set(
80
+ maskBuffer.subarray(srcOffset, srcOffset + count),
81
+ dstOffset,
82
+ )
83
+ }
84
+ }
85
+
86
+ return out
87
+ }
@@ -4,10 +4,11 @@ import type { AlphaMask, BinaryMask } from '../index'
4
4
  * Inverts a BinaryMask in-place.
5
5
  */
6
6
  export function invertBinaryMask(dst: BinaryMask): void {
7
- const len = dst.length
7
+ const data = dst.data
8
+ const len = data.length
8
9
 
9
10
  for (let i = 0; i < len; i++) {
10
- dst[i] = dst[i] === 0
11
+ data[i] = data[i] === 0
11
12
  ? 1
12
13
  : 0
13
14
  }
@@ -17,9 +18,10 @@ export function invertBinaryMask(dst: BinaryMask): void {
17
18
  * Inverts an AlphaMask in-place.
18
19
  */
19
20
  export function invertAlphaMask(dst: AlphaMask): void {
20
- const len = dst.length
21
+ const data = dst.data
22
+ const len = data.length
21
23
 
22
24
  for (let i = 0; i < len; i++) {
23
- dst[i] = 255 - dst[i]
25
+ data[i] = 255 - data[i]
24
26
  }
25
27
  }
@@ -5,9 +5,7 @@ import { type AlphaMask, type MergeAlphaMasksOptions } from '../_types'
5
5
  */
6
6
  export function mergeAlphaMasks(
7
7
  dst: AlphaMask,
8
- dstWidth: number,
9
8
  src: AlphaMask,
10
- srcWidth: number,
11
9
  opts: MergeAlphaMasksOptions,
12
10
  ): void {
13
11
  const {
@@ -20,18 +18,21 @@ export function mergeAlphaMasks(
20
18
  my = 0,
21
19
  invertMask = false,
22
20
  } = opts
23
- const dstHeight = (dst.length / dstWidth) | 0
24
- const srcHeight = (src.length / srcWidth) | 0
25
21
 
26
22
  if (width <= 0) return
27
23
  if (height <= 0) return
28
24
  if (globalAlpha === 0) return
29
25
 
26
+ const dstData = dst.data
27
+ const srcData = src.data
28
+ const srcWidth = src.w
29
+ const dstWidth = dst.w
30
+
30
31
  const startX = Math.max(0, -targetX, -mx)
31
32
  const startY = Math.max(0, -targetY, -my)
32
33
 
33
34
  const endX = Math.min(width, dstWidth - targetX, srcWidth - mx)
34
- const endY = Math.min(height, dstHeight - targetY, srcHeight - my)
35
+ const endY = Math.min(height, dst.h - targetY, src.h - my)
35
36
 
36
37
  if (startX >= endX) return
37
38
  if (startY >= endY) return
@@ -44,7 +45,7 @@ export function mergeAlphaMasks(
44
45
  let sIdx = sy * srcWidth + mx + startX
45
46
 
46
47
  for (let ix = startX; ix < endX; ix++) {
47
- const rawM = src[sIdx]
48
+ const rawM = srcData[sIdx]
48
49
  // Unified logic branch inside the hot path
49
50
  const effectiveM = invertMask ? 255 - rawM : rawM
50
51
 
@@ -62,14 +63,14 @@ export function mergeAlphaMasks(
62
63
 
63
64
  if (weight !== 255) {
64
65
  if (weight === 0) {
65
- dst[dIdx] = 0
66
+ dstData[dIdx] = 0
66
67
  } else {
67
- const da = dst[dIdx]
68
+ const da = dstData[dIdx]
68
69
 
69
70
  if (da === 255) {
70
- dst[dIdx] = weight
71
+ dstData[dIdx] = weight
71
72
  } else if (da !== 0) {
72
- dst[dIdx] = (da * weight + 128) >> 8
73
+ dstData[dIdx] = (da * weight + 128) >> 8
73
74
  }
74
75
  }
75
76
  }
@@ -2,9 +2,7 @@ import type { BinaryMask, MergeAlphaMasksOptions } from '../_types'
2
2
 
3
3
  export function mergeBinaryMasks(
4
4
  dst: BinaryMask,
5
- dstWidth: number,
6
5
  src: BinaryMask,
7
- srcWidth: number,
8
6
  opts: MergeAlphaMasksOptions,
9
7
  ): void {
10
8
  const {
@@ -16,12 +14,15 @@ export function mergeBinaryMasks(
16
14
  my = 0,
17
15
  invertMask = false,
18
16
  } = opts
17
+
18
+ const dstData = dst.data
19
+ const srcData = src.data
20
+ const srcWidth = src.w
21
+ const dstWidth = dst.w
22
+
19
23
  if (dstWidth <= 0) return
20
24
  if (srcWidth <= 0) return
21
25
 
22
- const dstHeight = (dst.length / dstWidth) | 0
23
- const srcHeight = (src.length / srcWidth) | 0
24
-
25
26
  // 1. Destination Clipping
26
27
  let x = targetX
27
28
  let y = targetY
@@ -39,7 +40,7 @@ export function mergeBinaryMasks(
39
40
  }
40
41
 
41
42
  w = Math.min(w, dstWidth - x)
42
- h = Math.min(h, dstHeight - y)
43
+ h = Math.min(h, dst.h - y)
43
44
 
44
45
  if (w <= 0) return
45
46
  if (h <= 0) return
@@ -51,7 +52,7 @@ export function mergeBinaryMasks(
51
52
  const sX0 = Math.max(0, startX)
52
53
  const sY0 = Math.max(0, startY)
53
54
  const sX1 = Math.min(srcWidth, startX + w)
54
- const sY1 = Math.min(srcHeight, startY + h)
55
+ const sY1 = Math.min(src.h, startY + h)
55
56
 
56
57
  const finalW = sX1 - sX0
57
58
  const finalH = sY1 - sY0
@@ -71,12 +72,12 @@ export function mergeBinaryMasks(
71
72
 
72
73
  for (let iy = 0; iy < finalH; iy++) {
73
74
  for (let ix = 0; ix < finalW; ix++) {
74
- const mVal = src[sIdx]
75
+ const mVal = srcData[sIdx]
75
76
  // Determine if the source pixel effectively "clears" the destination
76
77
  const isMaskedOut = invertMask ? mVal !== 0 : mVal === 0
77
78
 
78
79
  if (isMaskedOut) {
79
- dst[dIdx] = 0
80
+ dstData[dIdx] = 0
80
81
  }
81
82
 
82
83
  dIdx++
@@ -0,0 +1,7 @@
1
+ import type { Mask } from '../_types'
2
+
3
+ export function setMaskData(mask: Mask, width: number, height: number, data: Uint8Array): void {
4
+ ;(mask as any).w = width
5
+ ;(mask as any).h = height
6
+ ;(mask as any).data = data
7
+ }
@@ -0,0 +1,81 @@
1
+ import { MaskType, type NullableBinaryMaskRect } from '../_types'
2
+ import { getRectsBounds } from '../Rect/getRectsBounds'
3
+
4
+ export function merge2BinaryMaskRects(
5
+ a: NullableBinaryMaskRect,
6
+ b: NullableBinaryMaskRect,
7
+ ): NullableBinaryMaskRect {
8
+ const bounds = getRectsBounds([a, b])
9
+
10
+ // If both are fully selected, check if they form a perfect, gapless rectangle
11
+ if (
12
+ (a.data === null || a.data === undefined)
13
+ && (b.data === null || b.data === undefined)
14
+ ) {
15
+ const ix = Math.max(a.x, b.x)
16
+ const iy = Math.max(a.y, b.y)
17
+ const ir = Math.min(a.x + a.w, b.x + b.w)
18
+ const ib = Math.min(a.y + a.h, b.y + b.h)
19
+
20
+ const iw = Math.max(0, ir - ix)
21
+ const ih = Math.max(0, ib - iy)
22
+
23
+ const intersectionArea = iw * ih
24
+ const areaA = a.w * a.h
25
+ const areaB = b.w * b.h
26
+ const boundsArea = bounds.w * bounds.h
27
+
28
+ if (boundsArea === areaA + areaB - intersectionArea) {
29
+ return {
30
+ ...bounds,
31
+ data: null,
32
+ type: null,
33
+ }
34
+ }
35
+ }
36
+
37
+ const maskData = new Uint8Array(bounds.w * bounds.h)
38
+
39
+ // --- Write A's contribution ---
40
+ const aOffY = a.y - bounds.y
41
+ const aOffX = a.x - bounds.x
42
+
43
+ if (a.data === undefined || a.data === null) {
44
+ for (let ay = 0; ay < a.h; ay++) {
45
+ const destRow = (aOffY + ay) * bounds.w + aOffX
46
+ maskData.fill(1, destRow, destRow + a.w)
47
+ }
48
+ } else {
49
+ for (let ay = 0; ay < a.h; ay++) {
50
+ const srcRow = ay * a.w
51
+ const destRow = (aOffY + ay) * bounds.w + aOffX
52
+ maskData.set(a.data.subarray(srcRow, srcRow + a.w), destRow)
53
+ }
54
+ }
55
+
56
+ // --- OR B's contribution ---
57
+ const bOffY = b.y - bounds.y
58
+ const bOffX = b.x - bounds.x
59
+
60
+ if (b.data === undefined || b.data === null) {
61
+ for (let by = 0; by < b.h; by++) {
62
+ const destRow = (bOffY + by) * bounds.w + bOffX
63
+ maskData.fill(1, destRow, destRow + b.w)
64
+ }
65
+ } else {
66
+ for (let by = 0; by < b.h; by++) {
67
+ const srcRow = by * b.w
68
+ const destRow = (bOffY + by) * bounds.w + bOffX
69
+
70
+ for (let bx = 0; bx < b.w; bx++) {
71
+ maskData[destRow + bx] |= b.data[srcRow + bx]
72
+ }
73
+ }
74
+ }
75
+
76
+ return {
77
+ ...bounds,
78
+ data: maskData,
79
+ type: MaskType.BINARY,
80
+ }
81
+ }
@@ -0,0 +1,39 @@
1
+ import type { NullableBinaryMaskRect } from '../_types'
2
+ import { merge2BinaryMaskRects } from './merge2BinaryMaskRects'
3
+
4
+ export function mergeBinaryMaskRects(current: NullableBinaryMaskRect[], adding: NullableBinaryMaskRect[]): NullableBinaryMaskRect[] {
5
+ const rects = [...current, ...adding]
6
+
7
+ let changed = true
8
+ while (changed) {
9
+ changed = false
10
+ const next: NullableBinaryMaskRect[] = []
11
+
12
+ for (const r of rects) {
13
+ let merged = false
14
+
15
+ for (let i = 0; i < next.length; i++) {
16
+ const n = next[i]
17
+
18
+ const overlap =
19
+ r.x <= n.x + n.w &&
20
+ r.x + r.w >= n.x &&
21
+ r.y <= n.y + n.h &&
22
+ r.y + r.h >= n.y
23
+
24
+ if (overlap) {
25
+ next[i] = merge2BinaryMaskRects(n, r)
26
+ merged = true
27
+ changed = true
28
+ break
29
+ }
30
+ }
31
+
32
+ if (!merged) next.push(r)
33
+ }
34
+
35
+ rects.splice(0, rects.length, ...next)
36
+ }
37
+
38
+ return rects
39
+ }
@@ -0,0 +1,80 @@
1
+ import { MaskType, type NullableBinaryMaskRect } from '../_types'
2
+
3
+ export function subtractBinaryMaskRects(
4
+ current: NullableBinaryMaskRect[],
5
+ subtracting: NullableBinaryMaskRect[],
6
+ ): NullableBinaryMaskRect[] {
7
+ let result = [...current]
8
+
9
+ for (const sub of subtracting) {
10
+ const next: NullableBinaryMaskRect[] = []
11
+
12
+ for (const r of result) {
13
+ const ix = Math.max(r.x, sub.x)
14
+ const iy = Math.max(r.y, sub.y)
15
+ const ix2 = Math.min(r.x + r.w, sub.x + sub.w)
16
+ const iy2 = Math.min(r.y + r.h, sub.y + sub.h)
17
+
18
+ if (ix >= ix2 || iy >= iy2) {
19
+ next.push(r)
20
+ continue
21
+ }
22
+
23
+ // Split r into up to 4 pieces around the intersection.
24
+ // Extract directly from r.mask — no intermediate copy, no mutation.
25
+ //
26
+ // ┌──────────────┐
27
+ // │ TOP │ r.y .. iy (full width)
28
+ // ├────┬─────┬───┤
29
+ // │LEFT│ sub │RGT│ iy .. iy2 (side strips)
30
+ // ├────┴─────┴───┤
31
+ // │ BOTTOM │ iy2 .. r.y+r.h (full width)
32
+ // └──────────────┘
33
+
34
+ if (r.y < iy) pushPiece(next, r, r.x, r.y, r.w, iy - r.y)
35
+ if (iy2 < r.y + r.h) pushPiece(next, r, r.x, iy2, r.w, r.y + r.h - iy2)
36
+ if (r.x < ix) pushPiece(next, r, r.x, iy, ix - r.x, iy2 - iy)
37
+ if (ix2 < r.x + r.w) pushPiece(next, r, ix2, iy, r.x + r.w - ix2, iy2 - iy)
38
+ }
39
+
40
+ result = next
41
+ }
42
+
43
+ return result
44
+ }
45
+
46
+ /**
47
+ * Extract sub-region (x, y, w, h) in global coords from r's mask and push
48
+ * onto dest. If r.mask is null (fully selected) the piece is also null —
49
+ * zero allocations on the happy path.
50
+ */
51
+ function pushPiece(
52
+ dest: NullableBinaryMaskRect[],
53
+ r: NullableBinaryMaskRect,
54
+ x: number,
55
+ y: number,
56
+ w: number,
57
+ h: number,
58
+ ): void {
59
+ if (r.data === null || r.data === undefined) {
60
+ dest.push({ x, y, w, h, data: null, type: null })
61
+ return
62
+ }
63
+
64
+ // Coords local to r.mask
65
+ const lx = x - r.x
66
+ const ly = y - r.y
67
+
68
+ const data = new Uint8Array(w * h)
69
+ for (let row = 0; row < h; row++) {
70
+ data.set(
71
+ r.data.subarray(
72
+ (ly + row) * r.w + lx,
73
+ (ly + row) * r.w + lx + w,
74
+ ),
75
+ row * w,
76
+ )
77
+ }
78
+
79
+ dest.push({ x, y, w, h, data, type: MaskType.BINARY })
80
+ }
@@ -1,5 +1,9 @@
1
1
  import { type AlphaMask, type ApplyMaskToPixelDataOptions, type IPixelData } from '../_types'
2
2
 
3
+ /**
4
+ * Directly applies a mask to a region of PixelData,
5
+ * modifying the destination's alpha channel in-place.
6
+ */
3
7
  export function applyAlphaMaskToPixelData(
4
8
  dst: IPixelData,
5
9
  mask: AlphaMask,
@@ -11,7 +15,6 @@ export function applyAlphaMaskToPixelData(
11
15
  w: width = dst.width,
12
16
  h: height = dst.height,
13
17
  alpha: globalAlpha = 255,
14
- mw,
15
18
  mx = 0,
16
19
  my = 0,
17
20
  invertMask = false,
@@ -42,9 +45,8 @@ export function applyAlphaMaskToPixelData(
42
45
  if (h <= 0) return
43
46
 
44
47
  // 2. Determine Source Dimensions
45
- const mPitch = mw ?? width
48
+ const mPitch = mask.w
46
49
  if (mPitch <= 0) return
47
- const maskHeight = (mask.length / mPitch) | 0
48
50
 
49
51
  // 3. Source Bounds Clipping
50
52
  // Calculate where we would start reading in the mask
@@ -55,7 +57,7 @@ export function applyAlphaMaskToPixelData(
55
57
  const sX0 = Math.max(0, startX)
56
58
  const sY0 = Math.max(0, startY)
57
59
  const sX1 = Math.min(mPitch, startX + w)
58
- const sY1 = Math.min(maskHeight, startY + h)
60
+ const sY1 = Math.min(mask.h, startY + h)
59
61
 
60
62
  const finalW = sX1 - sX0
61
63
  const finalH = sY1 - sY0
@@ -73,13 +75,14 @@ export function applyAlphaMaskToPixelData(
73
75
  const dw = dst.width
74
76
  const dStride = dw - finalW
75
77
  const mStride = mPitch - finalW
78
+ const maskData = mask.data
76
79
 
77
80
  let dIdx = (y + yShift) * dw + (x + xShift)
78
81
  let mIdx = sY0 * mPitch + sX0
79
82
 
80
83
  for (let iy = 0; iy < h; iy++) {
81
84
  for (let ix = 0; ix < w; ix++) {
82
- const mVal = mask[mIdx]
85
+ const mVal = maskData[mIdx]
83
86
  // Unified logic branch inside the hot path
84
87
  const effectiveM = invertMask ? 255 - mVal : mVal
85
88
 
@@ -14,14 +14,13 @@ export function applyBinaryMaskToPixelData(
14
14
  y: targetY = 0,
15
15
  w: width = dst.width,
16
16
  h: height = dst.height,
17
- alpha = 255,
18
- mw,
17
+ alpha: globalAlpha = 255,
19
18
  mx = 0,
20
19
  my = 0,
21
20
  invertMask = false,
22
21
  } = opts
23
22
 
24
- if (alpha === 0) return
23
+ if (globalAlpha === 0) return
25
24
 
26
25
  // 1. Initial Destination Clipping
27
26
  let x = targetX
@@ -46,9 +45,8 @@ export function applyBinaryMaskToPixelData(
46
45
  if (h <= 0) return
47
46
 
48
47
  // 2. Determine Source Dimensions
49
- const mPitch = mw ?? width
48
+ const mPitch = mask.w
50
49
  if (mPitch <= 0) return
51
- const maskHeight = (mask.length / mPitch) | 0
52
50
 
53
51
  // 3. Source Bounds Clipping
54
52
  // Calculate where we would start reading in the mask
@@ -59,7 +57,7 @@ export function applyBinaryMaskToPixelData(
59
57
  const sX0 = Math.max(0, startX)
60
58
  const sY0 = Math.max(0, startY)
61
59
  const sX1 = Math.min(mPitch, startX + w)
62
- const sY1 = Math.min(maskHeight, startY + h)
60
+ const sY1 = Math.min(mask.h, startY + h)
63
61
 
64
62
  const finalW = sX1 - sX0
65
63
  const finalH = sY1 - sY0
@@ -77,26 +75,27 @@ export function applyBinaryMaskToPixelData(
77
75
  const dw = dst.width
78
76
  const dStride = dw - finalW
79
77
  const mStride = mPitch - finalW
78
+ const maskData = mask.data
80
79
 
81
80
  let dIdx = (y + yShift) * dw + (x + xShift)
82
81
  let mIdx = sY0 * mPitch + sX0
83
82
 
84
83
  for (let iy = 0; iy < h; iy++) {
85
84
  for (let ix = 0; ix < w; ix++) {
86
- const mVal = mask[mIdx]
85
+ const mVal = maskData[mIdx]
87
86
  // Consistently determines if this pixel should be "masked out" (cleared)
88
87
  const isMaskedOut = invertMask ? mVal !== 0 : mVal === 0
89
88
 
90
89
  if (isMaskedOut) {
91
90
  // Clear alpha channel only (keep RGB)
92
91
  dst32[dIdx] = (dst32[dIdx] & 0x00ffffff) >>> 0
93
- } else if (alpha !== 255) {
92
+ } else if (globalAlpha !== 255) {
94
93
  const d = dst32[dIdx]
95
94
  const da = d >>> 24
96
95
 
97
96
  // If pixel isn't already fully transparent, apply global alpha
98
97
  if (da !== 0) {
99
- const finalAlpha = da === 255 ? alpha : (da * alpha + 128) >> 8
98
+ const finalAlpha = da === 255 ? globalAlpha : (da * globalAlpha + 128) >> 8
100
99
  dst32[dIdx] = ((d & 0x00ffffff) | (finalAlpha << 24)) >>> 0
101
100
  }
102
101
  }