pixel-data-js 0.27.0 → 0.28.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 (139) hide show
  1. package/README.md +12 -2
  2. package/dist/index.prod.cjs +2222 -1045
  3. package/dist/index.prod.cjs.map +1 -1
  4. package/dist/index.prod.d.ts +542 -417
  5. package/dist/index.prod.js +2167 -1024
  6. package/dist/index.prod.js.map +1 -1
  7. package/package.json +11 -11
  8. package/src/Algorithm/floodFillSelection.ts +8 -6
  9. package/src/Algorithm/forEachLinePoint.ts +6 -6
  10. package/src/{Internal/resample32.ts → Algorithm/resampleUint32Array.ts} +11 -21
  11. package/src/BlendModes/blend-modes-fast.ts +169 -0
  12. package/src/BlendModes/blend-modes-perfect.ts +207 -0
  13. package/src/BlendModes/blend-modes.ts +9 -0
  14. package/src/Canvas/CanvasFrameRenderer.ts +20 -28
  15. package/src/Canvas/CanvasPixelDataRenderer.ts +23 -0
  16. package/src/Canvas/PixelCanvas.ts +2 -7
  17. package/src/Canvas/ReusableCanvas.ts +4 -12
  18. package/src/Canvas/_canvas-types.ts +26 -0
  19. package/src/History/PixelAccumulator.ts +17 -17
  20. package/src/History/PixelEngineConfig.ts +3 -3
  21. package/src/History/PixelMutator/mutatorApplyAlphaMask.ts +4 -3
  22. package/src/History/PixelMutator/mutatorApplyBinaryMask.ts +4 -3
  23. package/src/History/PixelMutator/mutatorApplyMask.ts +4 -3
  24. package/src/History/PixelMutator/mutatorBlendAlphaMask.ts +6 -4
  25. package/src/History/PixelMutator/mutatorBlendBinaryMask.ts +6 -4
  26. package/src/History/PixelMutator/mutatorBlendColor.ts +2 -2
  27. package/src/History/PixelMutator/mutatorBlendColorPaintAlphaMask.ts +2 -1
  28. package/src/History/PixelMutator/mutatorBlendColorPaintBinaryMask.ts +2 -1
  29. package/src/History/PixelMutator/mutatorBlendColorPaintMask.ts +3 -1
  30. package/src/History/PixelMutator/mutatorBlendColorPaintRect.ts +3 -3
  31. package/src/History/PixelMutator/mutatorBlendMask.ts +6 -4
  32. package/src/History/PixelMutator/mutatorBlendPixelData.ts +5 -4
  33. package/src/History/PixelMutator/mutatorClear.ts +4 -3
  34. package/src/History/PixelMutator/mutatorFill.ts +5 -4
  35. package/src/History/PixelMutator/mutatorFillBinaryMask.ts +2 -1
  36. package/src/History/PixelMutator/mutatorInvert.ts +2 -2
  37. package/src/History/PixelMutator.ts +1 -1
  38. package/src/History/PixelPatchTiles.ts +7 -7
  39. package/src/History/PixelWriter.ts +12 -63
  40. package/src/ImageData/ImageDataLike.ts +1 -1
  41. package/src/ImageData/_ImageData-types.ts +13 -0
  42. package/src/ImageData/copyImageData.ts +1 -1
  43. package/src/ImageData/extractImageDataBuffer.ts +3 -2
  44. package/src/ImageData/imageDataToUint32Array.ts +18 -0
  45. package/src/ImageData/resampleImageData.ts +3 -3
  46. package/src/ImageData/resizeImageData.ts +1 -1
  47. package/src/ImageData/serialization.ts +1 -1
  48. package/src/ImageData/uInt32ArrayToImageData.ts +1 -1
  49. package/src/ImageData/writeImageData.ts +2 -2
  50. package/src/ImageData/writeImageDataBuffer.ts +2 -2
  51. package/src/IndexedImage/IndexedImage.ts +56 -98
  52. package/src/IndexedImage/_indexedImage-types.ts +18 -0
  53. package/src/IndexedImage/getIndexedImageColorCounts.ts +3 -3
  54. package/src/IndexedImage/indexedImageToAverageColor.ts +1 -1
  55. package/src/IndexedImage/indexedImageToImageData.ts +4 -6
  56. package/src/IndexedImage/resampleIndexedImage.ts +7 -15
  57. package/src/Input/fileToImageData.ts +1 -1
  58. package/src/Internal/_errors.ts +2 -0
  59. package/src/Internal/macros.ts +14 -0
  60. package/src/Mask/AlphaMask.ts +1 -1
  61. package/src/Mask/BinaryMask/makeBinaryMaskFromAlphaMask.ts +23 -0
  62. package/src/Mask/BinaryMask/makeBinaryMaskOutline.ts +88 -0
  63. package/src/Mask/BinaryMask/makeCircleBinaryMaskOutline.ts +104 -0
  64. package/src/Mask/BinaryMask/makeRectBinaryMaskOutline.ts +34 -0
  65. package/src/Mask/BinaryMask.ts +1 -1
  66. package/src/Mask/_mask-types.ts +73 -0
  67. package/src/Mask/applyBinaryMaskToAlphaMask.ts +2 -1
  68. package/src/Mask/copyMask.ts +1 -1
  69. package/src/Mask/extractMask.ts +2 -1
  70. package/src/Mask/extractMaskBuffer.ts +1 -1
  71. package/src/Mask/mergeAlphaMasks.ts +6 -3
  72. package/src/Mask/mergeBinaryMasks.ts +2 -1
  73. package/src/Mask/setMaskData.ts +1 -1
  74. package/src/MaskRect/merge2BinaryMaskRects.ts +2 -2
  75. package/src/MaskRect/mergeBinaryMaskRects.ts +1 -1
  76. package/src/MaskRect/subtractBinaryMaskRects.ts +1 -1
  77. package/src/Paint/AlphaMaskPaintBuffer.ts +339 -0
  78. package/src/Paint/AlphaMaskPaintBufferCanvasRenderer.ts +78 -0
  79. package/src/Paint/BinaryMaskPaintBuffer.ts +254 -0
  80. package/src/Paint/BinaryMaskPaintBufferCanvasRenderer.ts +67 -0
  81. package/src/Paint/{PaintBuffer.ts → ColorPaintBuffer.ts} +148 -77
  82. package/src/Paint/{PaintBufferCanvasRenderer.ts → ColorPaintBufferCanvasRenderer.ts} +6 -5
  83. package/src/Paint/PaintCursorRenderer.ts +117 -0
  84. package/src/Paint/_paint-types.ts +22 -0
  85. package/src/Paint/eachTileInBounds.ts +45 -0
  86. package/src/Paint/makeCirclePaintMask.ts +74 -0
  87. package/src/Paint/makePaintMask.ts +5 -2
  88. package/src/Paint/makeRectFalloffPaintAlphaMask.ts +4 -2
  89. package/src/PixelData/PixelData.ts +15 -19
  90. package/src/PixelData/ReusablePixelData.ts +36 -0
  91. package/src/PixelData/_pixelData-types.ts +17 -0
  92. package/src/PixelData/applyAlphaMaskToPixelData.ts +80 -43
  93. package/src/PixelData/applyBinaryMaskToPixelData.ts +10 -8
  94. package/src/PixelData/applyMaskToPixelData.ts +4 -9
  95. package/src/PixelData/blendColorPixelData.ts +9 -8
  96. package/src/PixelData/blendColorPixelDataAlphaMask.ts +9 -7
  97. package/src/PixelData/blendColorPixelDataBinaryMask.ts +9 -7
  98. package/src/PixelData/blendColorPixelDataMask.ts +4 -2
  99. package/src/PixelData/blendColorPixelDataPaintAlphaMask.ts +4 -2
  100. package/src/PixelData/blendColorPixelDataPaintBinaryMask.ts +4 -2
  101. package/src/PixelData/blendColorPixelDataPaintMask.ts +5 -2
  102. package/src/PixelData/blendPixel.ts +6 -5
  103. package/src/PixelData/blendPixelData.ts +14 -13
  104. package/src/PixelData/blendPixelDataAlphaMask.ts +15 -13
  105. package/src/PixelData/blendPixelDataBinaryMask.ts +15 -13
  106. package/src/PixelData/blendPixelDataMask.ts +5 -3
  107. package/src/PixelData/blendPixelDataPaintBuffer.ts +5 -4
  108. package/src/PixelData/clearPixelDataFast.ts +4 -2
  109. package/src/PixelData/copyPixelData.ts +14 -0
  110. package/src/PixelData/extractPixelData.ts +8 -7
  111. package/src/PixelData/extractPixelDataBuffer.ts +9 -8
  112. package/src/PixelData/fillPixelData.ts +16 -14
  113. package/src/PixelData/fillPixelDataBinaryMask.ts +10 -8
  114. package/src/PixelData/fillPixelDataFast.ts +16 -14
  115. package/src/PixelData/invertPixelData.ts +9 -8
  116. package/src/PixelData/pixelDataToAlphaMask.ts +9 -8
  117. package/src/PixelData/reflectPixelData.ts +9 -9
  118. package/src/PixelData/resamplePixelData.ts +20 -9
  119. package/src/PixelData/rotatePixelData.ts +8 -7
  120. package/src/PixelData/uInt32ArrayToPixelData.ts +15 -0
  121. package/src/PixelData/writePaintBufferToPixelData.ts +5 -5
  122. package/src/PixelData/writePixelDataBuffer.ts +10 -9
  123. package/src/Rect/_rect-types.ts +7 -0
  124. package/src/Rect/getRectsBounds.ts +1 -1
  125. package/src/Rect/trimMaskRectBounds.ts +2 -1
  126. package/src/Rect/trimRectBounds.ts +1 -1
  127. package/src/Tile/MaskTile.ts +40 -0
  128. package/src/Tile/PixelTile.ts +23 -0
  129. package/src/{PixelTile/PixelTilePool.ts → Tile/TilePool.ts} +9 -9
  130. package/src/Tile/_tile-types.ts +33 -0
  131. package/src/_errors.ts +1 -0
  132. package/src/_types.ts +2 -118
  133. package/src/index.ts +46 -21
  134. package/src/ImageData/imageDataToUInt32Array.ts +0 -13
  135. package/src/Internal/helpers.ts +0 -5
  136. package/src/Paint/makeCirclePaintAlphaMask.ts +0 -41
  137. package/src/Paint/makeCirclePaintBinaryMask.ts +0 -29
  138. package/src/PixelTile/PixelTile.ts +0 -21
  139. /package/src/{Internal → Rect}/resolveClipping.ts +0 -0
@@ -0,0 +1,117 @@
1
+ import { type Color32 } from '../_types'
2
+ import type { CanvasContext, CanvasObjectFactory } from '../Canvas/_canvas-types'
3
+ import { packColor } from '../color'
4
+ import { CANVAS_CTX_FAILED } from '../Internal/_errors'
5
+ import { _macro_paintRectCenterOffset } from '../Internal/macros'
6
+ import { type BinaryMask, MaskType } from '../Mask/_mask-types'
7
+ import { makeBinaryMaskFromAlphaMask } from '../Mask/BinaryMask/makeBinaryMaskFromAlphaMask'
8
+ import { makeBinaryMaskOutline } from '../Mask/BinaryMask/makeBinaryMaskOutline'
9
+ import { makeCircleBinaryMaskOutline } from '../Mask/BinaryMask/makeCircleBinaryMaskOutline'
10
+ import { makeRectBinaryMaskOutline } from '../Mask/BinaryMask/makeRectBinaryMaskOutline'
11
+ import { fillPixelDataBinaryMask } from '../PixelData/fillPixelDataBinaryMask'
12
+ import { makeReusablePixelData } from '../PixelData/ReusablePixelData'
13
+ import type { Rect } from '../Rect/_rect-types'
14
+ import { type PaintMask, PaintMaskOutline } from './_paint-types'
15
+
16
+ export type PaintCursorRenderer = ReturnType<typeof makePaintCursorRenderer>
17
+
18
+ export function makePaintCursorRenderer<T extends HTMLCanvasElement | OffscreenCanvas>(
19
+ factory: CanvasObjectFactory<T> = (w, h) => new OffscreenCanvas(w, h) as T,
20
+ ) {
21
+ const canvas = factory(1, 1)
22
+ const ctx = canvas.getContext('2d')! as CanvasContext<T>
23
+ if (!ctx) throw new Error(CANVAS_CTX_FAILED)
24
+ ctx.imageSmoothingEnabled = false
25
+
26
+ const getPixelData = makeReusablePixelData()
27
+
28
+ let _color = packColor(0, 255, 255, 255)
29
+ let _scale = 1
30
+
31
+ let currentMask: PaintMask = {
32
+ type: MaskType.BINARY,
33
+ outlineType: PaintMaskOutline.RECT,
34
+ w: 1,
35
+ h: 1,
36
+ centerOffsetX: _macro_paintRectCenterOffset(10),
37
+ centerOffsetY: _macro_paintRectCenterOffset(10),
38
+ } as PaintMask
39
+
40
+ let outline: BinaryMask
41
+
42
+ function update(paintMask?: PaintMask, scale?: number, color?: Color32, alphaThreshold = 127) {
43
+ currentMask = paintMask ?? currentMask
44
+
45
+ _scale = scale ?? _scale
46
+ _color = color ?? _color
47
+
48
+ canvas.width = currentMask.w * _scale + 2 * _scale
49
+ canvas.height = currentMask.h * _scale + 2 * _scale
50
+
51
+ if (currentMask.type === MaskType.BINARY) {
52
+ if (currentMask.outlineType === PaintMaskOutline.CIRCLE) {
53
+ outline = makeCircleBinaryMaskOutline(currentMask.w, _scale)
54
+ } else if (currentMask.outlineType === PaintMaskOutline.RECT) {
55
+ outline = makeRectBinaryMaskOutline(currentMask.w, currentMask.h, _scale)
56
+ } else if (currentMask.outlineType === PaintMaskOutline.MASKED) {
57
+ outline = makeBinaryMaskOutline(currentMask, _scale)
58
+ }
59
+ } else if (currentMask.type === MaskType.ALPHA) {
60
+ const mask = makeBinaryMaskFromAlphaMask(currentMask, alphaThreshold)
61
+ outline = makeBinaryMaskOutline(mask, _scale)
62
+ }
63
+
64
+ const pixelData = getPixelData(outline.w, outline.h)
65
+ fillPixelDataBinaryMask(pixelData, _color, outline)
66
+ ctx.putImageData(pixelData.imageData, 0, 0)
67
+ }
68
+
69
+ const boundsScratch = { x: 0, y: 0, w: 0, h: 0 }
70
+
71
+ function getBounds(centerX: number, centerY: number): Rect {
72
+ boundsScratch.x = centerX + currentMask.centerOffsetX
73
+ boundsScratch.y = centerY + currentMask.centerOffsetY
74
+ boundsScratch.w = currentMask.w
75
+ boundsScratch.h = currentMask.h
76
+
77
+ return boundsScratch
78
+ }
79
+
80
+ const boundsScaledScratch = { x: 0, y: 0, w: 0, h: 0 }
81
+
82
+ function getOutlineBoundsScaled(centerX: number, centerY: number): Rect {
83
+ boundsScaledScratch.x = centerX * _scale + currentMask.centerOffsetX * _scale - 1
84
+ boundsScaledScratch.y = centerY * _scale + currentMask.centerOffsetY * _scale - 1
85
+ boundsScaledScratch.w = currentMask.w * _scale
86
+ boundsScaledScratch.h = currentMask.h * _scale
87
+
88
+ return boundsScaledScratch
89
+ }
90
+
91
+ function draw(
92
+ drawCtx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
93
+ centerX: number,
94
+ centerY: number,
95
+ ) {
96
+ const dx = centerX * _scale + currentMask.centerOffsetX * _scale - 1
97
+ const dy = centerY * _scale + currentMask.centerOffsetY * _scale - 1
98
+
99
+ drawCtx.drawImage(canvas, Math.floor(dx), Math.floor(dy))
100
+ }
101
+
102
+ function getSettings() {
103
+ return {
104
+ color: _color,
105
+ scale: _scale,
106
+ currentMask,
107
+ }
108
+ }
109
+
110
+ return {
111
+ update,
112
+ getBounds,
113
+ getBoundsScaled: getOutlineBoundsScaled,
114
+ draw,
115
+ getSettings,
116
+ }
117
+ }
@@ -0,0 +1,22 @@
1
+ import type { AlphaMask, BinaryMask } from '../Mask/_mask-types'
2
+
3
+ export enum PaintMaskOutline {
4
+ MASKED,
5
+ CIRCLE,
6
+ RECT,
7
+ }
8
+
9
+ interface BasePaintMask<T extends PaintMaskOutline = PaintMaskOutline> {
10
+ readonly outlineType: T
11
+ readonly centerOffsetX: number
12
+ readonly centerOffsetY: number
13
+ }
14
+
15
+ export interface PaintAlphaMask<T extends PaintMaskOutline = PaintMaskOutline> extends BasePaintMask<T>, AlphaMask {
16
+ }
17
+
18
+ export interface PaintBinaryMask<T extends PaintMaskOutline = PaintMaskOutline> extends BasePaintMask<T>, BinaryMask {
19
+ }
20
+
21
+ export type PaintMask = PaintAlphaMask<any> | PaintBinaryMask<any>
22
+
@@ -0,0 +1,45 @@
1
+ import type { PixelEngineConfig } from '../History/PixelEngineConfig'
2
+ import type { Rect } from '../Rect/_rect-types'
3
+ import type { Tile } from '../Tile/_tile-types'
4
+ import type { TilePool } from '../Tile/TilePool'
5
+
6
+ export function eachTileInBounds<T extends Tile>(
7
+ config: PixelEngineConfig,
8
+ lookup: (T | undefined)[],
9
+ tilePool: TilePool<T>,
10
+ bounds: Rect,
11
+ callback: (tile: T, bX: number, bY: number, bW: number, bH: number) => void,
12
+ ): void {
13
+ const { tileShift, targetColumns, targetRows, tileSize } = config
14
+
15
+ const x1 = Math.max(0, bounds.x >> tileShift)
16
+ const y1 = Math.max(0, bounds.y >> tileShift)
17
+ const x2 = Math.min(targetColumns - 1, (bounds.x + bounds.w - 1) >> tileShift)
18
+ const y2 = Math.min(targetRows - 1, (bounds.y + bounds.h - 1) >> tileShift)
19
+
20
+ if (x1 > x2 || y1 > y2) return
21
+
22
+ for (let ty = y1; ty <= y2; ty++) {
23
+ const rowOffset = ty * targetColumns
24
+ const tileTop = ty << tileShift
25
+
26
+ for (let tx = x1; tx <= x2; tx++) {
27
+ const id = rowOffset + tx
28
+ const tile = lookup[id] ?? (lookup[id] = tilePool.getTile(id, tx, ty))
29
+ const tileLeft = tx << tileShift
30
+
31
+ const startX = bounds.x > tileLeft ? bounds.x : tileLeft
32
+ const startY = bounds.y > tileTop ? bounds.y : tileTop
33
+
34
+ const maskEndX = bounds.x + bounds.w
35
+ const tileEndX = tileLeft + tileSize
36
+ const endX = maskEndX < tileEndX ? maskEndX : tileEndX
37
+
38
+ const maskEndY = bounds.y + bounds.h
39
+ const tileEndY = tileTop + tileSize
40
+ const endY = maskEndY < tileEndY ? maskEndY : tileEndY
41
+
42
+ callback(tile, startX, startY, endX - startX, endY - startY)
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,74 @@
1
+ import { _macro_paintCircleCenterOffset } from '../Internal/macros'
2
+ import { MaskType } from '../Mask/_mask-types'
3
+ import { type PaintAlphaMask, type PaintBinaryMask, PaintMaskOutline } from './_paint-types'
4
+
5
+ export function makeCirclePaintAlphaMask(size: number, fallOff: (d: number) => number = (d) => d): PaintAlphaMask {
6
+ const area = size * size
7
+ const data = new Uint8Array(area)
8
+ const radius = size / 2
9
+ const invR = 1 / radius
10
+
11
+ const centerOffset = _macro_paintCircleCenterOffset(radius)
12
+
13
+ for (let y = 0; y < size; y++) {
14
+ const rowOffset = y * size
15
+ const dy = y - radius + 0.5
16
+ const dy2 = dy * dy
17
+
18
+ for (let x = 0; x < size; x++) {
19
+ const dx = x - radius + 0.5
20
+ const distSqr = dx * dx + dy2
21
+
22
+ if (distSqr <= (radius * radius)) {
23
+ const dist = Math.sqrt(distSqr) * invR
24
+
25
+ // Pass 1.0 at center, 0.0 at edge
26
+ const strength = fallOff(1 - dist)
27
+ if (strength > 0) {
28
+ const intensity = (strength * 255) | 0
29
+ data[rowOffset + x] = Math.max(0, Math.min(255, intensity))
30
+ }
31
+ }
32
+ }
33
+ }
34
+
35
+ return {
36
+ type: MaskType.ALPHA,
37
+ outlineType: PaintMaskOutline.CIRCLE,
38
+ data,
39
+ w: size,
40
+ h: size,
41
+ centerOffsetX: centerOffset,
42
+ centerOffsetY: centerOffset,
43
+ }
44
+ }
45
+
46
+ export function makeCirclePaintBinaryMask(size: number): PaintBinaryMask {
47
+ const area = size * size
48
+ const data = new Uint8Array(area)
49
+ const radius = size / 2
50
+ const r2 = radius * radius
51
+
52
+ for (let y = 0; y < size; y++) {
53
+ for (let x = 0; x < size; x++) {
54
+ const dx = x - radius + 0.5
55
+ const dy = y - radius + 0.5
56
+ const distSqr = dx * dx + dy * dy
57
+ if (distSqr <= r2) {
58
+ data[y * size + x] = 1
59
+ }
60
+ }
61
+ }
62
+
63
+ const centerOffset = _macro_paintCircleCenterOffset(radius)
64
+
65
+ return {
66
+ type: MaskType.BINARY,
67
+ outlineType: PaintMaskOutline.CIRCLE,
68
+ w: size,
69
+ h: size,
70
+ data,
71
+ centerOffsetX: centerOffset,
72
+ centerOffsetY: centerOffset,
73
+ }
74
+ }
@@ -1,11 +1,13 @@
1
- import { type AlphaMask, type BinaryMask, MaskType, type PaintAlphaMask, type PaintBinaryMask } from '../_types'
2
- import { _macro_halfAndFloor } from '../Internal/helpers'
1
+ import { _macro_halfAndFloor } from '../Internal/macros'
2
+ import { type AlphaMask, type BinaryMask, MaskType } from '../Mask/_mask-types'
3
+ import { type PaintAlphaMask, type PaintBinaryMask, PaintMaskOutline } from './_paint-types'
3
4
 
4
5
  export function makePaintBinaryMask(
5
6
  mask: BinaryMask,
6
7
  ): PaintBinaryMask {
7
8
  return {
8
9
  type: MaskType.BINARY,
10
+ outlineType: PaintMaskOutline.MASKED,
9
11
  data: mask.data,
10
12
  w: mask.w,
11
13
  h: mask.h,
@@ -19,6 +21,7 @@ export function makePaintAlphaMask(
19
21
  ): PaintAlphaMask {
20
22
  return {
21
23
  type: MaskType.ALPHA,
24
+ outlineType: PaintMaskOutline.MASKED,
22
25
  data: mask.data,
23
26
  w: mask.w,
24
27
  h: mask.h,
@@ -1,5 +1,6 @@
1
- import { MaskType, type PaintAlphaMask } from '../_types'
2
- import { _macro_halfAndFloor } from '../Internal/helpers'
1
+ import { _macro_halfAndFloor } from '../Internal/macros'
2
+ import { MaskType } from '../Mask/_mask-types'
3
+ import { type PaintAlphaMask, PaintMaskOutline } from './_paint-types'
3
4
 
4
5
  export function makeRectFalloffPaintAlphaMask(
5
6
  width: number,
@@ -38,6 +39,7 @@ export function makeRectFalloffPaintAlphaMask(
38
39
 
39
40
  return {
40
41
  type: MaskType.ALPHA,
42
+ outlineType: PaintMaskOutline.RECT,
41
43
  data: data,
42
44
  w: width,
43
45
  h: height,
@@ -1,23 +1,19 @@
1
- import type { ImageDataLike, IPixelData } from '../_types'
2
- import { imageDataToUInt32Array } from '../ImageData/imageDataToUInt32Array'
1
+ import type { ImageDataLike } from '../ImageData/_ImageData-types'
2
+ import { _macro_imageDataToUint32Array } from '../ImageData/imageDataToUint32Array'
3
+ import type { PixelData } from './_pixelData-types'
3
4
 
4
- export class PixelData<T extends ImageDataLike = ImageData> implements IPixelData<T> {
5
- readonly data32: Uint32Array
6
- readonly imageData: T
7
- readonly width: number
8
- readonly height: number
9
-
10
- constructor(imageData: T) {
11
- this.data32 = imageDataToUInt32Array(imageData)
12
- this.imageData = imageData
13
- this.width = imageData.width
14
- this.height = imageData.height
5
+ export function makePixelData<T extends ImageDataLike = ImageData>(imageData: T): PixelData<T> {
6
+ return {
7
+ data: _macro_imageDataToUint32Array(imageData),
8
+ imageData,
9
+ w: imageData.width,
10
+ h: imageData.height,
15
11
  }
12
+ }
16
13
 
17
- set(imageData: T): void {
18
- ;(this as any).imageData = imageData
19
- ;(this as any).data32 = imageDataToUInt32Array(imageData)
20
- ;(this as any).width = imageData.width
21
- ;(this as any).height = imageData.height
22
- }
14
+ export function setPixelData(target: PixelData, imageData: ImageData) {
15
+ ;(target as any).data = _macro_imageDataToUint32Array(imageData)
16
+ ;(target as any).imageData = imageData
17
+ ;(target as any).w = imageData.width
18
+ ;(target as any).h = imageData.height
23
19
  }
@@ -0,0 +1,36 @@
1
+ import type { PixelData } from './_pixelData-types'
2
+ import { setPixelData } from './PixelData'
3
+
4
+ export type ReusablePixelData = ReturnType<typeof makeReusablePixelData>
5
+
6
+ /**
7
+ * Creates a factory function that manages a single, reusable PixelData instance.
8
+ * This is used to minimize garbage collection overhead by recycling the
9
+ * underlying pixel buffer across multiple operations.
10
+ * @returns A function that takes width and height and returns a pooled PixelData instance.
11
+ */
12
+ export function makeReusablePixelData() {
13
+ const pixelData = {
14
+ w: 0,
15
+ h: 0,
16
+ data: null as unknown as Uint32Array,
17
+ imageData: null as unknown as ImageData,
18
+ }
19
+
20
+ /**
21
+ * Retrieves a PixelData instance of the requested dimensions.
22
+ * If the requested dimensions differ from the cached instance, a new one is allocated.
23
+ * @param width - The desired width in pixels.
24
+ * @param height - The desired height in pixels.
25
+ * @returns The cached PixelData object.
26
+ */
27
+ return function getReusablePixelData(width: number, height: number): PixelData {
28
+ if (pixelData.w !== width || pixelData.h !== height) {
29
+ setPixelData(pixelData, new ImageData(width, height))
30
+ } else {
31
+ pixelData.data.fill(0)
32
+ }
33
+
34
+ return pixelData
35
+ }
36
+ }
@@ -0,0 +1,17 @@
1
+ import type { ImageDataLike } from '../ImageData/_ImageData-types'
2
+
3
+ export interface PixelData32 {
4
+ readonly data: Uint32Array
5
+ readonly w: number
6
+ readonly h: number
7
+ }
8
+
9
+ export interface MutablePixelData32 {
10
+ data: Uint32Array
11
+ w: number
12
+ h: number
13
+ }
14
+
15
+ export interface PixelData<T extends ImageDataLike = ImageData> extends PixelData32 {
16
+ readonly imageData: T
17
+ }
@@ -1,4 +1,6 @@
1
- import { type AlphaMask, type ApplyMaskToPixelDataOptions, type Color32, type IPixelData32 } from '../_types'
1
+ import { type ApplyMaskToPixelDataOptions } from '../_types'
2
+ import type { AlphaMask } from '../Mask/_mask-types'
3
+ import type { PixelData32 } from './_pixelData-types'
2
4
 
3
5
  /**
4
6
  * Directly applies a mask to a region of PixelData,
@@ -6,14 +8,14 @@ import { type AlphaMask, type ApplyMaskToPixelDataOptions, type Color32, type IP
6
8
  * @returns true if any pixels were actually modified.
7
9
  */
8
10
  export function applyAlphaMaskToPixelData(
9
- target: IPixelData32,
11
+ target: PixelData32,
10
12
  mask: AlphaMask,
11
13
  opts?: ApplyMaskToPixelDataOptions,
12
14
  ): boolean {
13
15
  const targetX = opts?.x ?? 0
14
16
  const targetY = opts?.y ?? 0
15
- const width = opts?.w ?? target.width
16
- const height = opts?.h ?? target.height
17
+ const width = opts?.w ?? target.w
18
+ const height = opts?.h ?? target.h
17
19
  const globalAlpha = opts?.alpha ?? 255
18
20
  const mx = opts?.mx ?? 0
19
21
  const my = opts?.my ?? 0
@@ -37,8 +39,8 @@ export function applyAlphaMaskToPixelData(
37
39
  y = 0
38
40
  }
39
41
 
40
- w = Math.min(w, target.width - x)
41
- h = Math.min(h, target.height - y)
42
+ w = Math.min(w, target.w - x)
43
+ h = Math.min(h, target.h - y)
42
44
 
43
45
  if (w <= 0) return false
44
46
  if (h <= 0) return false
@@ -70,8 +72,8 @@ export function applyAlphaMaskToPixelData(
70
72
  const xShift = sX0 - startX
71
73
  const yShift = sY0 - startY
72
74
 
73
- const dst32 = target.data32
74
- const dw = target.width
75
+ const dst32 = target.data
76
+ const dw = target.w
75
77
  const dStride = dw - finalW
76
78
  const mStride = mPitch - finalW
77
79
  const maskData = mask.data
@@ -80,51 +82,86 @@ export function applyAlphaMaskToPixelData(
80
82
  let mIdx = sY0 * mPitch + sX0
81
83
 
82
84
  let didChange = false
83
- for (let iy = 0; iy < h; iy++) {
84
- for (let ix = 0; ix < w; ix++) {
85
- const mVal = maskData[mIdx]
86
- // Unified logic branch inside the hot path
87
- const effectiveM = invertMask ? 255 - mVal : mVal
88
-
89
- let weight = 0
90
-
91
- if (effectiveM === 0) {
92
- weight = 0
93
- } else if (effectiveM === 255) {
94
- weight = globalAlpha
95
- } else if (globalAlpha === 255) {
96
- weight = effectiveM
97
- } else {
98
- weight = (effectiveM * globalAlpha + 128) >> 8
99
- }
100
-
101
- if (weight === 0) {
102
- // Clear alpha channel
103
- dst32[dIdx] = (dst32[dIdx] & 0x00ffffff) >>> 0
104
- didChange = true
105
- } else if (weight !== 255) {
106
- // Merge alpha channel
107
- const d = dst32[dIdx]
108
- const da = d >>> 24
85
+ if (invertMask) {
86
+ for (let iy = 0; iy < finalH; iy++) {
87
+ for (let ix = 0; ix < finalW; ix++) {
88
+ const effectiveM = 255 - maskData[mIdx]
109
89
 
110
- if (da !== 0) {
111
- const finalAlpha = da === 255 ? weight : (da * weight + 128) >> 8
90
+ if (effectiveM === 0) {
91
+ const current = dst32[dIdx]
92
+ const next = (current & 0x00ffffff) >>> 0
112
93
 
113
- const current = dst32[dIdx] as Color32
114
- const next = ((d & 0x00ffffff) | (finalAlpha << 24)) >>> 0
115
94
  if (current !== next) {
116
95
  dst32[dIdx] = next
117
96
  didChange = true
118
97
  }
98
+ } else {
99
+ const t1 = effectiveM * globalAlpha + 128
100
+ const weight = (t1 + (t1 >> 8)) >> 8
101
+
102
+ if (weight < 255) {
103
+ const current = dst32[dIdx]
104
+ const da = current >>> 24
105
+
106
+ if (da !== 0) {
107
+ const t2 = da * weight + 128
108
+ const finalAlpha = (t2 + (t2 >> 8)) >> 8
109
+ const next = ((current & 0x00ffffff) | (finalAlpha << 24)) >>> 0
110
+
111
+ if (current !== next) {
112
+ dst32[dIdx] = next
113
+ didChange = true
114
+ }
115
+ }
116
+ }
119
117
  }
120
- }
121
118
 
122
- dIdx++
123
- mIdx++
119
+ dIdx++
120
+ mIdx++
121
+ }
122
+ dIdx += dStride
123
+ mIdx += mStride
124
124
  }
125
+ } else {
126
+ for (let iy = 0; iy < finalH; iy++) {
127
+ for (let ix = 0; ix < finalW; ix++) {
128
+ const effectiveM = maskData[mIdx]
129
+
130
+ if (effectiveM === 0) {
131
+ const current = dst32[dIdx]
132
+ const next = (current & 0x00ffffff) >>> 0
125
133
 
126
- dIdx += dStride
127
- mIdx += mStride
134
+ if (current !== next) {
135
+ dst32[dIdx] = next
136
+ didChange = true
137
+ }
138
+ } else {
139
+ const t1 = effectiveM * globalAlpha + 128
140
+ const weight = (t1 + (t1 >> 8)) >> 8
141
+
142
+ if (weight < 255) {
143
+ const current = dst32[dIdx]
144
+ const da = current >>> 24
145
+
146
+ if (da !== 0) {
147
+ const t2 = da * weight + 128
148
+ const finalAlpha = (t2 + (t2 >> 8)) >> 8
149
+ const next = ((current & 0x00ffffff) | (finalAlpha << 24)) >>> 0
150
+
151
+ if (current !== next) {
152
+ dst32[dIdx] = next
153
+ didChange = true
154
+ }
155
+ }
156
+ }
157
+ }
158
+
159
+ dIdx++
160
+ mIdx++
161
+ }
162
+ dIdx += dStride
163
+ mIdx += mStride
164
+ }
128
165
  }
129
166
  return didChange
130
167
  }
@@ -1,4 +1,6 @@
1
- import { type ApplyMaskToPixelDataOptions, type BinaryMask, type IPixelData32 } from '../_types'
1
+ import { type ApplyMaskToPixelDataOptions } from '../_types'
2
+ import type { BinaryMask } from '../Mask/_mask-types'
3
+ import type { PixelData32 } from './_pixelData-types'
2
4
 
3
5
  /**
4
6
  * Directly applies a mask to a region of PixelData,
@@ -6,14 +8,14 @@ import { type ApplyMaskToPixelDataOptions, type BinaryMask, type IPixelData32 }
6
8
  * @returns true if any pixels were actually modified.
7
9
  */
8
10
  export function applyBinaryMaskToPixelData(
9
- target: IPixelData32,
11
+ target: PixelData32,
10
12
  mask: BinaryMask,
11
13
  opts?: ApplyMaskToPixelDataOptions,
12
14
  ): boolean {
13
15
  const targetX = opts?.x ?? 0
14
16
  const targetY = opts?.y ?? 0
15
- const width = opts?.w ?? target.width
16
- const height = opts?.h ?? target.height
17
+ const width = opts?.w ?? target.w
18
+ const height = opts?.h ?? target.h
17
19
  const globalAlpha = opts?.alpha ?? 255
18
20
  const mx = opts?.mx ?? 0
19
21
  const my = opts?.my ?? 0
@@ -36,8 +38,8 @@ export function applyBinaryMaskToPixelData(
36
38
  y = 0
37
39
  }
38
40
 
39
- w = Math.min(w, target.width - x)
40
- h = Math.min(h, target.height - y)
41
+ w = Math.min(w, target.w - x)
42
+ h = Math.min(h, target.h - y)
41
43
 
42
44
  if (w <= 0 || h <= 0) return false
43
45
 
@@ -67,8 +69,8 @@ export function applyBinaryMaskToPixelData(
67
69
  const xShift = sX0 - startX
68
70
  const yShift = sY0 - startY
69
71
 
70
- const dst32 = target.data32
71
- const dw = target.width
72
+ const dst32 = target.data
73
+ const dw = target.w
72
74
  const dStride = dw - finalW
73
75
  const mStride = mPitch - finalW
74
76
  const maskData = mask.data
@@ -1,16 +1,11 @@
1
- import {
2
- type AlphaMask,
3
- type ApplyMaskToPixelDataOptions,
4
- type BinaryMask,
5
- type IPixelData32,
6
- type Mask,
7
- MaskType,
8
- } from '../_types'
1
+ import { type ApplyMaskToPixelDataOptions } from '../_types'
2
+ import { type AlphaMask, type BinaryMask, type Mask, MaskType } from '../Mask/_mask-types'
3
+ import type { PixelData32 } from './_pixelData-types'
9
4
  import { applyAlphaMaskToPixelData } from './applyAlphaMaskToPixelData'
10
5
  import { applyBinaryMaskToPixelData } from './applyBinaryMaskToPixelData'
11
6
 
12
7
  export function applyMaskToPixelData(
13
- dst: IPixelData32,
8
+ dst: PixelData32,
14
9
  mask: Mask,
15
10
  opts?: ApplyMaskToPixelDataOptions,
16
11
  ): boolean {
@@ -1,19 +1,20 @@
1
- import { type Color32, type ColorBlendOptions, type IPixelData32 } from '../_types'
1
+ import { type Color32, type ColorBlendOptions } from '../_types'
2
2
  import { sourceOverPerfect } from '../BlendModes/blend-modes-perfect'
3
+ import type { PixelData32 } from './_pixelData-types'
3
4
 
4
5
  /**
5
6
  * Blends a solid color into a target pixel buffer.
6
7
  * @returns true if any pixels were actually modified.
7
8
  */
8
9
  export function blendColorPixelData(
9
- target: IPixelData32,
10
+ target: PixelData32,
10
11
  color: Color32,
11
12
  opts?: ColorBlendOptions,
12
13
  ): boolean {
13
14
  const targetX = opts?.x ?? 0
14
15
  const targetY = opts?.y ?? 0
15
- const width = opts?.w ?? target.width
16
- const height = opts?.h ?? target.height
16
+ const width = opts?.w ?? target.w
17
+ const height = opts?.h ?? target.h
17
18
  const globalAlpha = opts?.alpha ?? 255
18
19
  const blendFn = opts?.blendFn ?? sourceOverPerfect
19
20
  if (globalAlpha === 0) return false
@@ -39,8 +40,8 @@ export function blendColorPixelData(
39
40
  y = 0
40
41
  }
41
42
 
42
- const actualW = Math.min(w, target.width - x)
43
- const actualH = Math.min(h, target.height - y)
43
+ const actualW = Math.min(w, target.w - x)
44
+ const actualH = Math.min(h, target.h - y)
44
45
 
45
46
  if (actualW <= 0 || actualH <= 0) return false
46
47
 
@@ -53,8 +54,8 @@ export function blendColorPixelData(
53
54
  finalSrcColor = ((color & 0x00ffffff) | (a << 24)) >>> 0 as Color32
54
55
  }
55
56
 
56
- const dst32 = target.data32
57
- const dw = target.width
57
+ const dst32 = target.data
58
+ const dw = target.w
58
59
  let dIdx = (y * dw + x) | 0
59
60
  const dStride = (dw - actualW) | 0
60
61
  let didChange = false