pixel-data-js 0.27.0 → 0.29.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 (148) hide show
  1. package/README.md +12 -2
  2. package/dist/index.prod.cjs +2355 -1124
  3. package/dist/index.prod.cjs.map +1 -1
  4. package/dist/index.prod.d.ts +558 -424
  5. package/dist/index.prod.js +2304 -1115
  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/_constants.ts +3 -0
  59. package/src/Internal/_errors.ts +2 -0
  60. package/src/Internal/macros.ts +14 -0
  61. package/src/Mask/AlphaMask.ts +1 -1
  62. package/src/Mask/BinaryMask/makeBinaryMaskFromAlphaMask.ts +23 -0
  63. package/src/Mask/BinaryMask/makeBinaryMaskOutline.ts +88 -0
  64. package/src/Mask/BinaryMask/makeCircleBinaryMaskOutline.ts +104 -0
  65. package/src/Mask/BinaryMask/makeRectBinaryMaskOutline.ts +34 -0
  66. package/src/Mask/BinaryMask.ts +1 -1
  67. package/src/Mask/_mask-types.ts +73 -0
  68. package/src/Mask/applyBinaryMaskToAlphaMask.ts +2 -1
  69. package/src/Mask/copyMask.ts +1 -1
  70. package/src/Mask/extractMask.ts +2 -1
  71. package/src/Mask/extractMaskBuffer.ts +1 -1
  72. package/src/Mask/mergeAlphaMasks.ts +6 -3
  73. package/src/Mask/mergeBinaryMasks.ts +2 -1
  74. package/src/Mask/setMaskData.ts +1 -1
  75. package/src/MaskRect/merge2BinaryMaskRects.ts +2 -2
  76. package/src/MaskRect/mergeBinaryMaskRects.ts +1 -1
  77. package/src/MaskRect/subtractBinaryMaskRects.ts +1 -1
  78. package/src/Paint/AlphaMaskPaintBuffer.ts +283 -0
  79. package/src/Paint/BinaryMaskPaintBuffer.ts +198 -0
  80. package/src/Paint/{PaintBuffer.ts → ColorPaintBuffer.ts} +95 -77
  81. package/src/Paint/Commit/AlphaMaskPaintBufferCommitter.ts +26 -0
  82. package/src/Paint/Commit/AlphaMaskPaintBufferManager.ts +34 -0
  83. package/src/Paint/Commit/BinaryMaskPaintBufferCommitter.ts +26 -0
  84. package/src/Paint/Commit/BinaryMaskPaintBufferManager.ts +31 -0
  85. package/src/Paint/Commit/ColorPaintBufferCommitter.ts +23 -0
  86. package/src/Paint/Commit/ColorPaintBufferManager.ts +34 -0
  87. package/src/Paint/Commit/commitColorPaintBuffer.ts +55 -0
  88. package/src/Paint/Commit/commitMaskPaintBuffer.ts +78 -0
  89. package/src/Paint/Render/AlphaMaskPaintBufferCanvasRenderer.ts +78 -0
  90. package/src/Paint/Render/BinaryMaskPaintBufferCanvasRenderer.ts +67 -0
  91. package/src/Paint/{PaintBufferCanvasRenderer.ts → Render/ColorPaintBufferCanvasRenderer.ts} +13 -14
  92. package/src/Paint/Render/PaintCursorRenderer.ts +118 -0
  93. package/src/Paint/_paint-types.ts +22 -0
  94. package/src/Paint/eachTileInBounds.ts +45 -0
  95. package/src/Paint/makeCirclePaintMask.ts +74 -0
  96. package/src/Paint/makePaintMask.ts +5 -2
  97. package/src/Paint/makeRectFalloffPaintAlphaMask.ts +4 -2
  98. package/src/PixelData/PixelData.ts +15 -19
  99. package/src/PixelData/ReusablePixelData.ts +36 -0
  100. package/src/PixelData/_pixelData-types.ts +17 -0
  101. package/src/PixelData/applyAlphaMaskToPixelData.ts +80 -43
  102. package/src/PixelData/applyBinaryMaskToPixelData.ts +10 -8
  103. package/src/PixelData/applyMaskToPixelData.ts +4 -9
  104. package/src/PixelData/blendColorPixelData.ts +9 -8
  105. package/src/PixelData/blendColorPixelDataAlphaMask.ts +9 -7
  106. package/src/PixelData/blendColorPixelDataBinaryMask.ts +9 -7
  107. package/src/PixelData/blendColorPixelDataMask.ts +4 -2
  108. package/src/PixelData/blendColorPixelDataPaintAlphaMask.ts +4 -2
  109. package/src/PixelData/blendColorPixelDataPaintBinaryMask.ts +4 -2
  110. package/src/PixelData/blendColorPixelDataPaintMask.ts +5 -2
  111. package/src/PixelData/blendPixel.ts +6 -5
  112. package/src/PixelData/blendPixelData.ts +14 -13
  113. package/src/PixelData/blendPixelDataAlphaMask.ts +15 -13
  114. package/src/PixelData/blendPixelDataBinaryMask.ts +15 -13
  115. package/src/PixelData/blendPixelDataMask.ts +5 -3
  116. package/src/PixelData/blendPixelDataPaintBuffer.ts +5 -4
  117. package/src/PixelData/clearPixelDataFast.ts +4 -2
  118. package/src/PixelData/copyPixelData.ts +14 -0
  119. package/src/PixelData/extractPixelData.ts +8 -7
  120. package/src/PixelData/extractPixelDataBuffer.ts +9 -8
  121. package/src/PixelData/fillPixelData.ts +16 -14
  122. package/src/PixelData/fillPixelDataBinaryMask.ts +10 -8
  123. package/src/PixelData/fillPixelDataFast.ts +16 -14
  124. package/src/PixelData/invertPixelData.ts +9 -8
  125. package/src/PixelData/pixelDataToAlphaMask.ts +9 -8
  126. package/src/PixelData/reflectPixelData.ts +9 -9
  127. package/src/PixelData/resamplePixelData.ts +20 -9
  128. package/src/PixelData/rotatePixelData.ts +8 -7
  129. package/src/PixelData/uInt32ArrayToPixelData.ts +15 -0
  130. package/src/PixelData/writePaintBufferToPixelData.ts +5 -5
  131. package/src/PixelData/writePixelDataBuffer.ts +10 -9
  132. package/src/Rect/_rect-types.ts +7 -0
  133. package/src/Rect/getRectsBounds.ts +1 -1
  134. package/src/Rect/trimMaskRectBounds.ts +2 -1
  135. package/src/Rect/trimRectBounds.ts +1 -1
  136. package/src/Tile/MaskTile.ts +40 -0
  137. package/src/Tile/PixelTile.ts +23 -0
  138. package/src/{PixelTile/PixelTilePool.ts → Tile/TilePool.ts} +9 -9
  139. package/src/Tile/_tile-types.ts +33 -0
  140. package/src/_errors.ts +1 -0
  141. package/src/_types.ts +2 -118
  142. package/src/index.ts +57 -21
  143. package/src/ImageData/imageDataToUInt32Array.ts +0 -13
  144. package/src/Internal/helpers.ts +0 -5
  145. package/src/Paint/makeCirclePaintAlphaMask.ts +0 -41
  146. package/src/Paint/makeCirclePaintBinaryMask.ts +0 -29
  147. package/src/PixelTile/PixelTile.ts +0 -21
  148. /package/src/{Internal → Rect}/resolveClipping.ts +0 -0
@@ -0,0 +1,67 @@
1
+ import type { Color32 } from '../../_types'
2
+ import type { CanvasObjectFactory } from '../../Canvas/_canvas-types'
3
+ import { DEFAULT_CANVAS_FACTORY } from '../../Internal/_constants'
4
+ import { CANVAS_CTX_FAILED } from '../../Internal/_errors'
5
+ import { makePixelData } from '../../PixelData/PixelData'
6
+ import type { BinaryMaskPaintBuffer } from '../BinaryMaskPaintBuffer'
7
+
8
+ export type BinaryMaskPaintBufferCanvasRenderer = ReturnType<typeof makeBinaryMaskPaintBufferCanvasRenderer>
9
+
10
+ export function makeBinaryMaskPaintBufferCanvasRenderer(
11
+ paintBuffer: BinaryMaskPaintBuffer,
12
+ canvasFactory: CanvasObjectFactory<any> = DEFAULT_CANVAS_FACTORY,
13
+ ) {
14
+ const config = paintBuffer.config
15
+ const tileSize = config.tileSize
16
+ const tileShift = config.tileShift
17
+ const tileArea = config.tileArea
18
+ const lookup = paintBuffer.lookup
19
+
20
+ const canvas = canvasFactory(tileSize, tileSize)
21
+ const ctx = canvas.getContext('2d')
22
+ if (!ctx) throw new Error(CANVAS_CTX_FAILED)
23
+ ctx.imageSmoothingEnabled = false
24
+
25
+ const bridge = makePixelData(new ImageData(tileSize, tileSize))
26
+ const view32 = bridge.data
27
+
28
+ return function drawPaintBuffer(
29
+ targetCtx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
30
+ color: Color32,
31
+ alpha = 255,
32
+ compOperation: GlobalCompositeOperation = 'source-over',
33
+ ): void {
34
+ if (alpha === 0) return
35
+
36
+ const baseSrcAlpha = (color >>> 24)
37
+ if (baseSrcAlpha === 0) return
38
+
39
+ targetCtx.globalAlpha = alpha / 255
40
+ targetCtx.globalCompositeOperation = compOperation
41
+
42
+ for (let i = 0; i < lookup.length; i++) {
43
+ const tile = lookup[i]
44
+
45
+ if (tile) {
46
+ const data8 = tile.data
47
+ view32.fill(0)
48
+
49
+ for (let p = 0; p < tileArea; p++) {
50
+ // If mask is solid, the final pixel is just the unmodified color
51
+ if (data8[p] === 1) {
52
+ view32[p] = color
53
+ }
54
+ }
55
+
56
+ const dx = tile.tx << tileShift
57
+ const dy = tile.ty << tileShift
58
+
59
+ ctx.putImageData(bridge.imageData, 0, 0)
60
+ targetCtx.drawImage(canvas, dx, dy)
61
+ }
62
+ }
63
+
64
+ targetCtx.globalAlpha = 1
65
+ targetCtx.globalCompositeOperation = 'source-over'
66
+ }
67
+ }
@@ -1,27 +1,26 @@
1
- import { CANVAS_CTX_FAILED } from '../../support/error-strings'
2
- import type { PaintBuffer } from './PaintBuffer'
3
-
4
- export type PaintBufferCanvasRenderer = ReturnType<typeof makePaintBufferCanvasRenderer>
5
-
6
- /**
7
- *
8
- * @param offscreenCanvasClass - @internal
9
- */
10
- export function makePaintBufferCanvasRenderer(
11
- paintBuffer: PaintBuffer,
12
- offscreenCanvasClass = OffscreenCanvas,
1
+ import type { CanvasObjectFactory } from '../../Canvas/_canvas-types'
2
+ import { DEFAULT_CANVAS_FACTORY } from '../../Internal/_constants'
3
+ import { CANVAS_CTX_FAILED } from '../../Internal/_errors'
4
+ import type { ColorPaintBuffer } from '../ColorPaintBuffer'
5
+
6
+ export type ColorPaintBufferCanvasRenderer = ReturnType<typeof makeColorPaintBufferCanvasRenderer>
7
+
8
+ export function makeColorPaintBufferCanvasRenderer(
9
+ paintBuffer: ColorPaintBuffer,
10
+ canvasFactory: CanvasObjectFactory<any> = DEFAULT_CANVAS_FACTORY,
13
11
  ) {
14
12
  const config = paintBuffer.config
15
13
  const tileSize = config.tileSize
16
14
  const tileShift = config.tileShift
17
15
  const lookup = paintBuffer.lookup
18
- const canvas = new offscreenCanvasClass(tileSize, tileSize)
16
+
17
+ const canvas = canvasFactory(tileSize, tileSize)
19
18
  const ctx = canvas.getContext('2d')
20
19
  if (!ctx) throw new Error(CANVAS_CTX_FAILED)
21
20
  ctx.imageSmoothingEnabled = false
22
21
 
23
22
  return function drawPaintBuffer(
24
- targetCtx: CanvasRenderingContext2D,
23
+ targetCtx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
25
24
  alpha = 255,
26
25
  compOperation: GlobalCompositeOperation = 'source-over',
27
26
  ): void {
@@ -0,0 +1,118 @@
1
+ import { type Color32 } from '../../_types'
2
+ import type { ReusableCanvasFactory } from '../../Canvas/_canvas-types'
3
+ import { makeReusableOffscreenCanvas } from '../../Canvas/ReusableCanvas'
4
+ import { packColor } from '../../color'
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 = OffscreenCanvas>(
19
+ reusableCanvasFactory?: () => ReusableCanvasFactory<T>,
20
+ ) {
21
+ const factory = (reusableCanvasFactory ?? makeReusableOffscreenCanvas) as unknown as () => ReusableCanvasFactory<T>
22
+ const updateBuffer = factory()
23
+ const { canvas, ctx } = updateBuffer(1, 1)
24
+
25
+ const getPixelData = makeReusablePixelData()
26
+
27
+ let _color = packColor(0, 255, 255, 255)
28
+ let _scale = 1
29
+
30
+ let currentMask: PaintMask = {
31
+ type: MaskType.BINARY,
32
+ outlineType: PaintMaskOutline.RECT,
33
+ w: 1,
34
+ h: 1,
35
+ centerOffsetX: _macro_paintRectCenterOffset(10),
36
+ centerOffsetY: _macro_paintRectCenterOffset(10),
37
+ } as PaintMask
38
+
39
+ let outline: BinaryMask
40
+
41
+ function update(paintMask?: PaintMask, scale?: number, color?: Color32, alphaThreshold = 127) {
42
+ currentMask = paintMask ?? currentMask
43
+
44
+ _scale = scale ?? _scale
45
+ _color = color ?? _color
46
+
47
+ updateBuffer(
48
+ currentMask.w * _scale + 2 * _scale,
49
+ currentMask.h * _scale + 2 * _scale,
50
+ )
51
+
52
+ if (currentMask.type === MaskType.BINARY) {
53
+ if (currentMask.outlineType === PaintMaskOutline.CIRCLE) {
54
+ outline = makeCircleBinaryMaskOutline(currentMask.w, _scale)
55
+ } else if (currentMask.outlineType === PaintMaskOutline.RECT) {
56
+ outline = makeRectBinaryMaskOutline(currentMask.w, currentMask.h, _scale)
57
+ } else if (currentMask.outlineType === PaintMaskOutline.MASKED) {
58
+ outline = makeBinaryMaskOutline(currentMask, _scale)
59
+ }
60
+ } else if (currentMask.type === MaskType.ALPHA) {
61
+ const mask = makeBinaryMaskFromAlphaMask(currentMask, alphaThreshold)
62
+ outline = makeBinaryMaskOutline(mask, _scale)
63
+ }
64
+
65
+ const pixelData = getPixelData(outline.w, outline.h)
66
+ fillPixelDataBinaryMask(pixelData, _color, outline)
67
+ ctx.putImageData(pixelData.imageData, 0, 0)
68
+ }
69
+
70
+ const boundsScratch = { x: 0, y: 0, w: 0, h: 0 }
71
+
72
+ function getBounds(centerX: number, centerY: number): Rect {
73
+ boundsScratch.x = centerX + currentMask.centerOffsetX
74
+ boundsScratch.y = centerY + currentMask.centerOffsetY
75
+ boundsScratch.w = currentMask.w
76
+ boundsScratch.h = currentMask.h
77
+
78
+ return boundsScratch
79
+ }
80
+
81
+ const boundsScaledScratch = { x: 0, y: 0, w: 0, h: 0 }
82
+
83
+ function getOutlineBoundsScaled(centerX: number, centerY: number): Rect {
84
+ boundsScaledScratch.x = centerX * _scale + currentMask.centerOffsetX * _scale - 1
85
+ boundsScaledScratch.y = centerY * _scale + currentMask.centerOffsetY * _scale - 1
86
+ boundsScaledScratch.w = currentMask.w * _scale
87
+ boundsScaledScratch.h = currentMask.h * _scale
88
+
89
+ return boundsScaledScratch
90
+ }
91
+
92
+ function draw(
93
+ drawCtx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
94
+ centerX: number,
95
+ centerY: number,
96
+ ) {
97
+ const dx = centerX * _scale + currentMask.centerOffsetX * _scale - 1
98
+ const dy = centerY * _scale + currentMask.centerOffsetY * _scale - 1
99
+
100
+ drawCtx.drawImage(canvas, Math.floor(dx), Math.floor(dy))
101
+ }
102
+
103
+ function getSettings() {
104
+ return {
105
+ color: _color,
106
+ scale: _scale,
107
+ currentMask,
108
+ }
109
+ }
110
+
111
+ return {
112
+ update,
113
+ getBounds,
114
+ getBoundsScaled: getOutlineBoundsScaled,
115
+ draw,
116
+ getSettings,
117
+ }
118
+ }
@@ -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
  }