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
@@ -1,99 +1,91 @@
1
- import type { BlendColor32, Color32, IPixelData, Rect } from '../_types'
1
+ import {
2
+ type BlendColor32,
3
+ type CircleBrushMask,
4
+ type Color32,
5
+ type ColorBlendMaskOptions,
6
+ type IPixelData,
7
+ MaskType,
8
+ type Rect,
9
+ } from '../_types'
2
10
  import { sourceOverPerfect } from '../BlendModes/blend-modes-perfect'
3
11
  import { getCircleBrushOrPencilBounds } from '../Rect/getCircleBrushOrPencilBounds'
12
+ import { blendColorPixelDataAlphaMask } from './blendColorPixelDataAlphaMask'
13
+ import { blendColorPixelDataBinaryMask } from './blendColorPixelDataBinaryMask'
4
14
 
5
15
  /**
6
- * Applies a circular brush to pixel data, blending a color with optional falloff.
16
+ * Applies a circular brush to pixel data using a pre-calculated alpha mask.
7
17
  *
8
18
  * @param target The PixelData to modify.
9
19
  * @param color The brush color.
10
20
  * @param centerX The center x-coordinate of the brush.
11
21
  * @param centerY The center y-coordinate of the brush.
12
- * @param brushSize The diameter of the brush.
22
+ * @param brush The pre-calculated CircleBrushAlphaMask.
13
23
  * @param alpha The overall opacity of the brush (0-255).
14
- * @default 255
15
- * @param fallOff A function that returns an alpha multiplier (0-1) based on the normalized distance (0-1) from the circle's center.
16
24
  * @param blendFn
25
+ * @param scratchOptions
17
26
  * @param bounds precalculated result from {@link getCircleBrushOrPencilBounds}
18
- * @default sourceOverPerfect
19
27
  */
20
28
  export function applyCircleBrushToPixelData(
21
29
  target: IPixelData,
22
30
  color: Color32,
23
31
  centerX: number,
24
32
  centerY: number,
25
- brushSize: number,
33
+ brush: CircleBrushMask,
26
34
  alpha = 255,
27
- fallOff: (dist: number) => number,
28
35
  blendFn: BlendColor32 = sourceOverPerfect,
36
+ scratchOptions: ColorBlendMaskOptions = {},
29
37
  bounds?: Rect,
30
38
  ): void {
31
- const targetWidth = target.width
32
- const targetHeight = target.height
33
-
34
- // Use provided bounds OR calculate them once
35
39
  const b = bounds ?? getCircleBrushOrPencilBounds(
36
40
  centerX,
37
41
  centerY,
38
- brushSize,
39
- targetWidth,
40
- targetHeight,
42
+ brush.size,
43
+ target.width,
44
+ target.height,
41
45
  )
42
46
 
43
47
  if (b.w <= 0 || b.h <= 0) return
44
48
 
45
- const data32 = target.data32
46
- const r = brushSize / 2
47
- const rSqr = r * r
48
- const invR = 1 / r
49
-
50
- const centerOffset = (brushSize % 2 === 0) ? 0.5 : 0
51
-
52
- const endX = b.x + b.w
53
- const endY = b.y + b.h
49
+ const unclippedStartX = Math.floor(centerX + brush.minOffset)
50
+ const unclippedStartY = Math.floor(centerY + brush.minOffset)
54
51
 
55
- // Anchor the math to the floor of the center for exact pixel art parity
56
- const fCenterX = Math.floor(centerX)
57
- const fCenterY = Math.floor(centerY)
58
- const baseSrcAlpha = (color >>> 24)
59
- const colorRGB = color & 0x00ffffff
60
- const isOpaque = alpha === 255
61
- const isOverwrite = (blendFn as any).isOverwrite
52
+ // Calculate the intersection between the unclipped mask rect and the allowed bounds
53
+ const ix = Math.max(unclippedStartX, b.x)
54
+ const iy = Math.max(unclippedStartY, b.y)
55
+ const ir = Math.min(unclippedStartX + brush.w, b.x + b.w)
56
+ const ib = Math.min(unclippedStartY + brush.h, b.y + b.h)
62
57
 
63
- for (let cy = b.y; cy < endY; cy++) {
64
- const relY = (cy - fCenterY) + centerOffset
65
- const dySqr = relY * relY
66
- const rowOffset = cy * targetWidth
58
+ const iw = ir - ix
59
+ const ih = ib - iy
67
60
 
68
- for (let cx = b.x; cx < endX; cx++) {
69
- const relX = (cx - fCenterX) + centerOffset
70
- const dSqr = relX * relX + dySqr
61
+ // If the mask falls entirely outside the bounds, exit
62
+ if (iw <= 0 || ih <= 0) return
71
63
 
72
- if (dSqr <= rSqr) {
73
- const idx = rowOffset + cx
74
- let weight = alpha
64
+ // Apply the intersected coordinates and internal mask offsets
65
+ scratchOptions.x = ix
66
+ scratchOptions.y = iy
67
+ scratchOptions.w = iw
68
+ scratchOptions.h = ih
69
+ scratchOptions.mx = ix - unclippedStartX
70
+ scratchOptions.my = iy - unclippedStartY
71
+ scratchOptions.alpha = alpha
72
+ scratchOptions.blendFn = blendFn
75
73
 
76
- const strength = fallOff(1 - (Math.sqrt(dSqr) * invR))
77
- const maskVal = (strength * 255) | 0
78
- if (maskVal === 0) continue
79
-
80
- // Match Blitter's weight calculation exactly
81
- if (isOpaque) {
82
- weight = maskVal
83
- } else if (maskVal !== 255) {
84
- weight = (maskVal * alpha + 128) >> 8
85
- }
86
-
87
- // Match Blitter's final color calculation exactly
88
- let finalCol = color
89
- if (weight < 255) {
90
- const a = (baseSrcAlpha * weight + 128) >> 8
91
- if (a === 0 && !isOverwrite) continue
92
- finalCol = (colorRGB | (a << 24)) >>> 0 as Color32
93
- }
74
+ if (brush.type === MaskType.ALPHA) {
75
+ blendColorPixelDataAlphaMask(
76
+ target,
77
+ color,
78
+ brush,
79
+ scratchOptions,
80
+ )
81
+ }
94
82
 
95
- data32[idx] = blendFn(finalCol, data32[idx] as Color32)
96
- }
97
- }
83
+ if (brush.type === MaskType.BINARY) {
84
+ blendColorPixelDataBinaryMask(
85
+ target,
86
+ color,
87
+ brush,
88
+ scratchOptions,
89
+ )
98
90
  }
99
91
  }
@@ -1,26 +1,35 @@
1
1
  import type { AlphaMask, Color32, ColorBlendMaskOptions, IPixelData } from '../_types'
2
2
  import { sourceOverPerfect } from '../BlendModes/blend-modes-perfect'
3
3
 
4
+ /**
5
+ * Blends a solid color into a target pixel buffer using an alpha mask.
6
+ *
7
+ * @remarks
8
+ * If the width (`w`) or height (`h`) are omitted from the options, they will safely
9
+ * default to the dimensions of the provided mask to prevent out-of-bounds memory access.
10
+ *
11
+ * @param dst - The destination {@link IPixelData} buffer to modify.
12
+ * @param color - The solid color to apply.
13
+ * @param mask - The mask defining the per-pixel opacity of the target area.
14
+ * @param opts - Configuration options including placement coordinates, bounds, global alpha, and mask offsets.
15
+ */
4
16
  export function blendColorPixelDataAlphaMask(
5
17
  dst: IPixelData,
6
18
  color: Color32,
7
19
  mask: AlphaMask,
8
- opts: ColorBlendMaskOptions,
9
- ) {
10
- const {
11
- x: targetX = 0,
12
- y: targetY = 0,
13
- w: width = dst.width,
14
- h: height = dst.height,
15
- alpha: globalAlpha = 255,
16
- blendFn = sourceOverPerfect,
17
- mw = width,
18
- mx = 0,
19
- my = 0,
20
- invertMask = false,
21
- } = opts
22
-
23
- if (globalAlpha === 0 || !mask) return
20
+ opts: ColorBlendMaskOptions = {},
21
+ ): void {
22
+ const targetX = opts.x ?? 0
23
+ const targetY = opts.y ?? 0
24
+ const w = opts.w ?? mask.w
25
+ const h = opts.h ?? mask.h
26
+ const globalAlpha = opts.alpha ?? 255
27
+ const blendFn = opts.blendFn ?? sourceOverPerfect
28
+ const mx = opts.mx ?? 0
29
+ const my = opts.my ?? 0
30
+ const invertMask = opts.invertMask ?? false
31
+
32
+ if (globalAlpha === 0) return
24
33
 
25
34
  const baseSrcAlpha = (color >>> 24)
26
35
  const isOverwrite = (blendFn as any).isOverwrite || false
@@ -29,21 +38,21 @@ export function blendColorPixelDataAlphaMask(
29
38
 
30
39
  let x = targetX
31
40
  let y = targetY
32
- let w = width
33
- let h = height
41
+ let actualW = w
42
+ let actualH = h
34
43
 
35
44
  if (x < 0) {
36
- w += x
45
+ actualW += x
37
46
  x = 0
38
47
  }
39
48
 
40
49
  if (y < 0) {
41
- h += y
50
+ actualH += y
42
51
  y = 0
43
52
  }
44
53
 
45
- const actualW = Math.min(w, dst.width - x)
46
- const actualH = Math.min(h, dst.height - y)
54
+ actualW = Math.min(actualW, dst.width - x)
55
+ actualH = Math.min(actualH, dst.height - y)
47
56
 
48
57
  if (actualW <= 0 || actualH <= 0) return
49
58
 
@@ -52,7 +61,8 @@ export function blendColorPixelDataAlphaMask(
52
61
 
53
62
  const dst32 = dst.data32
54
63
  const dw = dst.width
55
- const mPitch = mw
64
+ const mPitch = mask.w
65
+ const maskData = mask.data
56
66
 
57
67
  let dIdx = (y * dw + x) | 0
58
68
  let mIdx = ((my + dy) * mPitch + (mx + dx)) | 0
@@ -64,7 +74,7 @@ export function blendColorPixelDataAlphaMask(
64
74
 
65
75
  for (let iy = 0; iy < actualH; iy++) {
66
76
  for (let ix = 0; ix < actualW; ix++) {
67
- const mVal = mask[mIdx]
77
+ const mVal = maskData[mIdx]
68
78
  const effM = invertMask ? 255 - mVal : mVal
69
79
 
70
80
  if (effM === 0) {
@@ -96,7 +106,7 @@ export function blendColorPixelDataAlphaMask(
96
106
  mIdx++
97
107
  continue
98
108
  }
99
- finalCol = (colorRGB | (a << 24)) >>> 0 as Color32
109
+ finalCol = ((colorRGB | (a << 24)) >>> 0) as Color32
100
110
  }
101
111
 
102
112
  dst32[dIdx] = blendFn(finalCol, dst32[dIdx] as Color32)
@@ -1,26 +1,35 @@
1
1
  import type { BinaryMask, Color32, ColorBlendMaskOptions, IPixelData } from '../_types'
2
2
  import { sourceOverPerfect } from '../BlendModes/blend-modes-perfect'
3
3
 
4
+ /**
5
+ * Blends a solid color into a target pixel buffer using a binary mask.
6
+ *
7
+ * @remarks
8
+ * If the width (`w`) or height (`h`) are omitted from the options, they will safely
9
+ * default to the dimensions of the provided mask to prevent out-of-bounds memory access.
10
+ *
11
+ * @param dst - The destination {@link IPixelData} buffer to modify.
12
+ * @param color - The solid color to apply.
13
+ * @param mask - The mask defining the per-pixel opacity of the target area.
14
+ * @param opts - Configuration options including placement coordinates, bounds, global alpha, and mask offsets.
15
+ */
4
16
  export function blendColorPixelDataBinaryMask(
5
17
  dst: IPixelData,
6
18
  color: Color32,
7
19
  mask: BinaryMask,
8
20
  opts: ColorBlendMaskOptions = {},
9
21
  ) {
10
- const {
11
- x: targetX = 0,
12
- y: targetY = 0,
13
- w: width = dst.width,
14
- h: height = dst.height,
15
- alpha: globalAlpha = 255,
16
- blendFn = sourceOverPerfect,
17
- mw = width,
18
- mx = 0,
19
- my = 0,
20
- invertMask = false,
21
- } = opts
22
-
23
- if (globalAlpha === 0 || !mask) return
22
+ const targetX = opts.x ?? 0
23
+ const targetY = opts.y ?? 0
24
+ let w = opts.w ?? mask.w
25
+ let h = opts.h ?? mask.h
26
+ const globalAlpha = opts.alpha ?? 255
27
+ const blendFn = opts.blendFn ?? sourceOverPerfect
28
+ const mx = opts.mx ?? 0
29
+ const my = opts.my ?? 0
30
+ const invertMask = opts.invertMask ?? false
31
+
32
+ if (globalAlpha === 0) return
24
33
 
25
34
  const baseSrcAlpha = (color >>> 24)
26
35
  const isOverwrite = (blendFn as any).isOverwrite || false
@@ -29,8 +38,6 @@ export function blendColorPixelDataBinaryMask(
29
38
 
30
39
  let x = targetX
31
40
  let y = targetY
32
- let w = width
33
- let h = height
34
41
 
35
42
  if (x < 0) {
36
43
  w += x
@@ -60,8 +67,8 @@ export function blendColorPixelDataBinaryMask(
60
67
 
61
68
  const dst32 = dst.data32
62
69
  const dw = dst.width
63
- const mPitch = mw
64
-
70
+ const mPitch = mask.w
71
+ const maskData = mask.data
65
72
  let dIdx = (y * dw + x) | 0
66
73
  let mIdx = ((my + dy) * mPitch + (mx + dx)) | 0
67
74
 
@@ -71,7 +78,7 @@ export function blendColorPixelDataBinaryMask(
71
78
 
72
79
  for (let iy = 0; iy < actualH; iy++) {
73
80
  for (let ix = 0; ix < actualW; ix++) {
74
- if (mask[mIdx] === skipVal) {
81
+ if (maskData[mIdx] === skipVal) {
75
82
  dIdx++
76
83
  mIdx++
77
84
  continue
@@ -16,7 +16,6 @@ export function blendPixelDataAlphaMask(
16
16
  h: height = src.height,
17
17
  alpha: globalAlpha = 255,
18
18
  blendFn = sourceOverPerfect,
19
- mw = src.width,
20
19
  mx = 0,
21
20
  my = 0,
22
21
  invertMask = false,
@@ -62,7 +61,8 @@ export function blendPixelDataAlphaMask(
62
61
  // 2. Index Setup
63
62
  const dw = dst.width
64
63
  const sw = src.width
65
- const mPitch = mw
64
+ const mPitch = alphaMask.w
65
+ const maskData = alphaMask.data
66
66
 
67
67
  // dx/dy is the displacement from requested start to clipped start.
68
68
  // This keeps the mask locked to the source content during cross-clipping.
@@ -85,7 +85,7 @@ export function blendPixelDataAlphaMask(
85
85
 
86
86
  for (let iy = 0; iy < actualH; iy++) {
87
87
  for (let ix = 0; ix < actualW; ix++) {
88
- const mVal = alphaMask[mIdx]
88
+ const mVal = maskData[mIdx]
89
89
  const effM = invertMask ? 255 - mVal : mVal
90
90
 
91
91
  // Early exit if mask is fully transparent
@@ -16,7 +16,6 @@ export function blendPixelDataBinaryMask(
16
16
  h: height = src.height,
17
17
  alpha: globalAlpha = 255,
18
18
  blendFn = sourceOverPerfect,
19
- mw = src.width,
20
19
  mx = 0,
21
20
  my = 0,
22
21
  invertMask = false,
@@ -72,7 +71,8 @@ export function blendPixelDataBinaryMask(
72
71
  const src32 = src.data32
73
72
  const dw = dst.width
74
73
  const sw = src.width
75
- const mPitch = mw
74
+ const mPitch = binaryMask.w
75
+ const maskData = binaryMask.data
76
76
 
77
77
  let dIdx = (y * dw + x) | 0
78
78
  let sIdx = (sy * sw + sx) | 0
@@ -89,7 +89,7 @@ export function blendPixelDataBinaryMask(
89
89
  for (let iy = 0; iy < actualH; iy++) {
90
90
  for (let ix = 0; ix < actualW; ix++) {
91
91
  // Binary Mask Check (Earliest exit)
92
- if (binaryMask[mIdx] === skipVal) {
92
+ if (maskData[mIdx] === skipVal) {
93
93
  dIdx++
94
94
  sIdx++
95
95
  mIdx++
@@ -1,4 +1,4 @@
1
- import type { Color32, IPixelData, Rect } from '../_types'
1
+ import type { BinaryMaskRect, Color32, IPixelData } from '../_types'
2
2
  import { fillPixelData } from './fillPixelData'
3
3
 
4
4
  /**
@@ -7,7 +7,7 @@ import { fillPixelData } from './fillPixelData'
7
7
  */
8
8
  export function clearPixelData(
9
9
  dst: IPixelData,
10
- rect?: Partial<Rect>,
10
+ rect?: Partial<BinaryMaskRect>,
11
11
  ): void {
12
12
  fillPixelData(dst, 0 as Color32, rect)
13
13
  }
@@ -1,29 +1,28 @@
1
- import type { BinaryMask, BinaryMaskRect, Color32, IPixelData } from '../_types'
1
+ import type { Color32, IPixelData, Rect } from '../_types'
2
2
  import { makeClippedRect, resolveRectClipping } from '../Internal/resolveClipping'
3
- import type { PixelData } from './PixelData'
4
3
 
5
4
  const SCRATCH_RECT = makeClippedRect()
6
5
 
7
6
  /**
8
- * Fills a region or the {@link PixelData} buffer with a solid color.
7
+ * Fills a region or the {@link IPixelData} buffer with a solid color.
9
8
  *
10
- * @param dst - The target {@link PixelData} to modify.
11
- * @param color - The {@link Color32} value to apply.
12
- * @param rect - A {@link BinaryMaskRect} defining the area to fill.
9
+ * @param dst - The target to modify.
10
+ * @param color - The color to apply.
11
+ * @param rect - Defines the area to fill. If omitted, the entire
12
+ * buffer is filled.
13
13
  */
14
14
  export function fillPixelData(
15
15
  dst: IPixelData,
16
16
  color: Color32,
17
- rect?: Partial<BinaryMaskRect>,
17
+ rect?: Partial<Rect>,
18
18
  ): void
19
19
  /**
20
- * @param dst - The target {@link PixelData} to modify.
21
- * @param color - The {@link Color32} value to apply.
20
+ * @param dst - The target to modify.
21
+ * @param color - The color to apply.
22
22
  * @param x - Starting horizontal coordinate.
23
23
  * @param y - Starting vertical coordinate.
24
24
  * @param w - Width of the fill area.
25
25
  * @param h - Height of the fill area.
26
- * @param mask - A {@link BinaryMaskRect} defining the area to fill
27
26
  */
28
27
  export function fillPixelData(
29
28
  dst: IPixelData,
@@ -32,36 +31,30 @@ export function fillPixelData(
32
31
  y: number,
33
32
  w: number,
34
33
  h: number,
35
- mask?: BinaryMask,
36
34
  ): void
37
35
  export function fillPixelData(
38
36
  dst: IPixelData,
39
37
  color: Color32,
40
- _x?: Partial<BinaryMaskRect> | number,
38
+ _x?: Partial<Rect> | number,
41
39
  _y?: number,
42
40
  _w?: number,
43
41
  _h?: number,
44
- _mask?: BinaryMask,
45
42
  ): void {
46
43
  let x: number
47
44
  let y: number
48
45
  let w: number
49
46
  let h: number
50
- let mask: BinaryMask | undefined
51
47
 
52
48
  if (typeof _x === 'object') {
53
49
  x = _x.x ?? 0
54
50
  y = _x.y ?? 0
55
51
  w = _x.w ?? dst.width
56
52
  h = _x.h ?? dst.height
57
- mask = _x.mask
58
-
59
53
  } else if (typeof _x === 'number') {
60
54
  x = _x
61
55
  y = _y!
62
56
  w = _w!
63
57
  h = _h!
64
- mask = _mask
65
58
  } else {
66
59
  x = 0
67
60
  y = 0
@@ -90,30 +83,10 @@ export function fillPixelData(
90
83
  return
91
84
  }
92
85
 
93
- if (mask) {
94
- for (let iy = 0; iy < actualH; iy++) {
95
- const currentY = finalY + iy
96
- const maskY = currentY - y
97
- const maskOffset = maskY * w
98
-
99
- for (let ix = 0; ix < actualW; ix++) {
100
- const currentX = finalX + ix
101
- const maskX = currentX - x
102
- const maskIndex = maskOffset + maskX
103
- const isMasked = mask[maskIndex]
104
-
105
- if (isMasked) {
106
- const dstIndex = currentY * dw + currentX
107
- dst32[dstIndex] = color
108
- }
109
- }
110
- }
111
- } else {
112
- // Row-by-row fill for partial rectangles
113
- for (let iy = 0; iy < actualH; iy++) {
114
- const start = (finalY + iy) * dw + finalX
115
- const end = start + actualW
116
- dst32.fill(color, start, end)
117
- }
86
+ // Row-by-row fill for partial rectangles
87
+ for (let iy = 0; iy < actualH; iy++) {
88
+ const start = (finalY + iy) * dw + finalX
89
+ const end = start + actualW
90
+ dst32.fill(color, start, end)
118
91
  }
119
92
  }
@@ -0,0 +1,79 @@
1
+ import type { BinaryMask, Color32, IPixelData } from '../_types'
2
+ import { makeClippedRect, resolveRectClipping } from '../Internal/resolveClipping'
3
+
4
+ const SCRATCH_RECT = makeClippedRect()
5
+
6
+ /**
7
+ * Fills a region of the {@link IPixelData} buffer with a solid color using a mask.
8
+ * @param dst - The target to modify.
9
+ * @param color - The color to apply.
10
+ * @param mask - The mask defining the area to fill.
11
+ * @param alpha - The overall opacity of the fill (0-255).
12
+ * @param x - Starting horizontal coordinate for the mask placement.
13
+ * @param y - Starting vertical coordinate for the mask placement.
14
+ */
15
+ export function fillPixelDataBinaryMask(
16
+ dst: IPixelData,
17
+ color: Color32,
18
+ mask: BinaryMask,
19
+ alpha = 255,
20
+ x = 0,
21
+ y = 0,
22
+ ): void {
23
+ if (alpha === 0) return
24
+
25
+ const maskW = mask.w
26
+ const maskH = mask.h
27
+
28
+ const clip = resolveRectClipping(
29
+ x,
30
+ y,
31
+ maskW,
32
+ maskH,
33
+ dst.width,
34
+ dst.height,
35
+ SCRATCH_RECT,
36
+ )
37
+
38
+ if (!clip.inBounds) return
39
+
40
+ const {
41
+ x: finalX,
42
+ y: finalY,
43
+ w: actualW,
44
+ h: actualH,
45
+ } = clip
46
+
47
+ const maskData = mask.data
48
+ const dst32 = dst.data32
49
+ const dw = dst.width
50
+
51
+ // Pre-calculate the alpha-adjusted color once outside the loop
52
+ let finalCol = color
53
+
54
+ if (alpha < 255) {
55
+ const baseSrcAlpha = color >>> 24
56
+ const colorRGB = color & 0x00ffffff
57
+ const a = (baseSrcAlpha * alpha + 128) >> 8
58
+
59
+ finalCol = ((colorRGB | (a << 24)) >>> 0) as Color32
60
+ }
61
+
62
+ for (let iy = 0; iy < actualH; iy++) {
63
+ const currentY = finalY + iy
64
+ const maskY = currentY - y
65
+ const maskOffset = maskY * maskW
66
+
67
+ const dstRowOffset = currentY * dw
68
+
69
+ for (let ix = 0; ix < actualW; ix++) {
70
+ const currentX = finalX + ix
71
+ const maskX = currentX - x
72
+ const maskIndex = maskOffset + maskX
73
+
74
+ if (maskData[maskIndex]) {
75
+ dst32[dstRowOffset + currentX] = finalCol
76
+ }
77
+ }
78
+ }
79
+ }
@@ -14,7 +14,6 @@ export function invertPixelData(
14
14
  w: width = pixelData.width,
15
15
  h: height = pixelData.height,
16
16
  mask,
17
- mw,
18
17
  mx = 0,
19
18
  my = 0,
20
19
  invertMask = false,
@@ -33,7 +32,7 @@ export function invertPixelData(
33
32
 
34
33
  const dst32 = dst.data32
35
34
  const dw = dst.width
36
- const mPitch = mw ?? width
35
+ const mPitch = mask?.w ?? width
37
36
 
38
37
  const dx = x - targetX
39
38
  const dy = y - targetY
@@ -46,9 +45,10 @@ export function invertPixelData(
46
45
 
47
46
  // Optimization: Split loops to avoid checking `if (mask)` for every pixel.
48
47
  if (mask) {
48
+ const maskData = mask.data
49
49
  for (let iy = 0; iy < actualH; iy++) {
50
50
  for (let ix = 0; ix < actualW; ix++) {
51
- const mVal = mask[mIdx]
51
+ const mVal = maskData[mIdx]
52
52
  const isHit = invertMask
53
53
  ? mVal === 0
54
54
  : mVal === 1
@@ -1,4 +1,5 @@
1
1
  import type { AlphaMask, IPixelData } from '../_types'
2
+ import { makeAlphaMask } from '../Mask/AlphaMask'
2
3
 
3
4
  /**
4
5
  * Extracts the alpha channel from PixelData into a single-channel mask.
@@ -13,14 +14,15 @@ export function pixelDataToAlphaMask(
13
14
  height,
14
15
  } = pixelData
15
16
  const len = data32.length
16
- const mask = new Uint8Array(width * height) as AlphaMask
17
+ const mask = makeAlphaMask(width, height)
18
+ const maskData = mask.data
17
19
 
18
20
  for (let i = 0; i < len; i++) {
19
21
  const val = data32[i]
20
22
 
21
23
  // Extract the Alpha byte (top 8 bits in ABGR / Little-Endian)
22
24
  // Shift right by 24 moves the 4th byte to the 1st position
23
- mask[i] = (val >>> 24) & 0xff
25
+ maskData[i] = (val >>> 24) & 0xff
24
26
  }
25
27
 
26
28
  return mask
@@ -1,14 +1,13 @@
1
1
  import { type IPixelData, type Rect } from '../_types'
2
2
  import { makeClippedBlit, resolveBlitClipping } from '../Internal/resolveClipping'
3
- import type { PixelData } from './PixelData'
4
3
 
5
4
  const SCRATCH_BLIT = makeClippedBlit()
6
5
 
7
6
  /**
8
- * Copies a pixel buffer into a specific region of a {@link PixelData} object.
7
+ * Copies a pixel buffer into a specific region of a {@link IPixelData} object.
9
8
  *
10
9
  * This function performs a direct memory copy from a {@link Uint32Array}
11
- * into the target {@link PixelData} buffer.
10
+ * into the target buffer.
12
11
  */
13
12
  export function writePixelDataBuffer(
14
13
  target: IPixelData,