pixel-data-js 0.26.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 +2227 -1050
  3. package/dist/index.prod.cjs.map +1 -1
  4. package/dist/index.prod.d.ts +549 -424
  5. package/dist/index.prod.js +2171 -1028
  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/{mutatorBlendPaintRect.ts → mutatorBlendColorPaintRect.ts} +5 -5
  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 +2 -2
  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 +47 -22
  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,104 @@
1
+ import { type BinaryMask, MaskType } from '../_mask-types'
2
+
3
+ export function makeCircleBinaryMaskOutline(size: number, scale: number): BinaryMask {
4
+ const outSize = size * scale + 2
5
+ const outArea = outSize * outSize
6
+ const data = new Uint8Array(outArea)
7
+
8
+ const radius = size / 2
9
+ const r2 = radius * radius
10
+
11
+ let prevMinX = -1
12
+ let prevMaxX = -1
13
+
14
+ let currMinX = -1
15
+ let currMaxX = -1
16
+
17
+ const initialDy = 0 - radius + 0.5
18
+ const initialDy2 = initialDy * initialDy
19
+
20
+ if (initialDy2 <= r2) {
21
+ const dx = Math.sqrt(r2 - initialDy2)
22
+ currMinX = Math.ceil(radius - 0.5 - dx)
23
+ currMaxX = Math.floor(radius - 0.5 + dx)
24
+ }
25
+
26
+ for (let iy = 0; iy < size; iy++) {
27
+ let nextMinX = -1
28
+ let nextMaxX = -1
29
+
30
+ if (iy + 1 < size) {
31
+ const ny = (iy + 1) - radius + 0.5
32
+ const ny2 = ny * ny
33
+
34
+ if (ny2 <= r2) {
35
+ const dx = Math.sqrt(r2 - ny2)
36
+ nextMinX = Math.ceil(radius - 0.5 - dx)
37
+ nextMaxX = Math.floor(radius - 0.5 + dx)
38
+ }
39
+ }
40
+
41
+ if (currMinX !== -1) {
42
+ for (let ix = currMinX; ix <= currMaxX; ix++) {
43
+ // Offset by 1 to leave room for the top/left outline edges
44
+ const sx = ix * scale + 1
45
+ const sy = iy * scale + 1
46
+
47
+ const isTop = prevMinX === -1 || ix < prevMinX || ix > prevMaxX
48
+ const isBottom = nextMinX === -1 || ix < nextMinX || ix > nextMaxX
49
+ const isLeft = ix === currMinX
50
+ const isRight = ix === currMaxX
51
+
52
+ if (isTop) {
53
+ const leftOut = prevMinX === -1 || (ix - 1) < prevMinX || (ix - 1) > prevMaxX
54
+ const rightOut = prevMinX === -1 || (ix + 1) < prevMinX || (ix + 1) > prevMaxX
55
+ const startX = leftOut ? sx - 1 : sx
56
+ const endX = rightOut ? sx + scale : sx + scale - 1
57
+
58
+ for (let x = startX; x <= endX; x++) {
59
+ const index = (sy - 1) * outSize + x
60
+ data[index] = 1
61
+ }
62
+ }
63
+
64
+ if (isBottom) {
65
+ const leftOut = nextMinX === -1 || (ix - 1) < nextMinX || (ix - 1) > nextMaxX
66
+ const rightOut = nextMinX === -1 || (ix + 1) < nextMinX || (ix + 1) > nextMaxX
67
+ const startX = leftOut ? sx - 1 : sx
68
+ const endX = rightOut ? sx + scale : sx + scale - 1
69
+
70
+ for (let x = startX; x <= endX; x++) {
71
+ const index = (sy + scale) * outSize + x
72
+ data[index] = 1
73
+ }
74
+ }
75
+
76
+ if (isLeft) {
77
+ for (let y = sy; y < sy + scale; y++) {
78
+ const index = y * outSize + (sx - 1)
79
+ data[index] = 1
80
+ }
81
+ }
82
+
83
+ if (isRight) {
84
+ for (let y = sy; y < sy + scale; y++) {
85
+ const index = y * outSize + (sx + scale)
86
+ data[index] = 1
87
+ }
88
+ }
89
+ }
90
+ }
91
+
92
+ prevMinX = currMinX
93
+ prevMaxX = currMaxX
94
+ currMinX = nextMinX
95
+ currMaxX = nextMaxX
96
+ }
97
+
98
+ return {
99
+ type: MaskType.BINARY,
100
+ w: outSize,
101
+ h: outSize,
102
+ data,
103
+ }
104
+ }
@@ -0,0 +1,34 @@
1
+ import { type BinaryMask, MaskType } from '../_mask-types'
2
+
3
+ export function makeRectBinaryMaskOutline(
4
+ w: number,
5
+ h: number,
6
+ scale = 1,
7
+ ): BinaryMask {
8
+ const rw = w * scale
9
+ const rh = h * scale
10
+
11
+ const outW = rw + 2
12
+ const outH = rh + 2
13
+ const outData = new Uint8Array(outW * outH)
14
+
15
+ // Top edge
16
+ outData.fill(1, 0, outW)
17
+
18
+ // Bottom edge
19
+ outData.fill(1, (outH - 1) * outW, outH * outW)
20
+
21
+ // Left and Right edges
22
+ for (let iy = 1; iy < outH - 1; iy++) {
23
+ const rowStart = iy * outW
24
+ outData[rowStart] = 1
25
+ outData[rowStart + outW - 1] = 1
26
+ }
27
+
28
+ return {
29
+ type: MaskType.BINARY,
30
+ w: outW,
31
+ h: outH,
32
+ data: outData,
33
+ }
34
+ }
@@ -1,4 +1,4 @@
1
- import { type BinaryMask, MaskType } from '../_types'
1
+ import { type BinaryMask, MaskType } from './_mask-types'
2
2
 
3
3
  /**
4
4
  * Creates a Binary Mask
@@ -0,0 +1,73 @@
1
+ import type { Rect } from '../Rect/_rect-types'
2
+
3
+ /**
4
+ * Defines how mask values should be interpreted during a draw operation.
5
+ */
6
+ export enum MaskType {
7
+ /**
8
+ * Values are treated as alpha weights.
9
+ * 0 is skipped, values > 0 are processed.
10
+ */
11
+ ALPHA,
12
+ /**
13
+ * Values are treated as on/off.
14
+ * 0 is fully transparent (skipped), any other value is fully opaque.
15
+ */
16
+ BINARY
17
+ }
18
+
19
+ export interface BaseMask {
20
+ readonly type: MaskType
21
+ readonly data: Uint8Array
22
+ readonly w: number
23
+ readonly h: number
24
+ }
25
+
26
+ export type Mask = BinaryMask | AlphaMask
27
+
28
+ /** Strictly 0 or 1 */
29
+ export interface BinaryMask extends BaseMask {
30
+ readonly type: MaskType.BINARY
31
+ }
32
+
33
+ /** Strictly 0-255 */
34
+ export interface AlphaMask extends BaseMask {
35
+ readonly type: MaskType.ALPHA
36
+ }
37
+
38
+ export interface MutableMask<T extends MaskType> {
39
+ readonly type: T
40
+ data: Uint8Array
41
+ w: number
42
+ h: number
43
+ }
44
+
45
+ export interface MutableAlphaMask extends MutableMask<MaskType.ALPHA> {
46
+ }
47
+
48
+ export interface MutableBinaryMask extends MutableMask<MaskType.BINARY> {
49
+ }
50
+
51
+ export type MaskRect<T extends MaskType> = Rect & {
52
+ type: T
53
+ data: Uint8Array
54
+ }
55
+
56
+ export type BinaryMaskRect = MaskRect<MaskType.BINARY>
57
+
58
+ export type AlphaMaskRect = MaskRect<MaskType.ALPHA>
59
+
60
+ export type NullableBinaryMaskRect = Rect & ({
61
+ type: MaskType.BINARY
62
+ data: Uint8Array
63
+ } | {
64
+ type?: null
65
+ data?: null
66
+ })
67
+ export type NullableMaskRect = Rect & ({
68
+ type: MaskType
69
+ data: Uint8Array
70
+ } | {
71
+ type?: null
72
+ data?: null
73
+ })
@@ -1,4 +1,5 @@
1
- import type { AlphaMask, ApplyMaskToPixelDataOptions, BinaryMask } from '../_types'
1
+ import type { ApplyMaskToPixelDataOptions } from '../_types'
2
+ import type { AlphaMask, BinaryMask } from './_mask-types'
2
3
 
3
4
  export function applyBinaryMaskToAlphaMask(
4
5
  alphaMaskDst: AlphaMask,
@@ -1,4 +1,4 @@
1
- import { type Mask } from '../_types'
1
+ import type { Mask } from './_mask-types'
2
2
 
3
3
  /**
4
4
  * Creates a new copy of a mask.
@@ -1,4 +1,5 @@
1
- import { type Mask, type Rect } from '../_types'
1
+ import type { Rect } from '../Rect/_rect-types'
2
+ import type { Mask } from './_mask-types'
2
3
 
3
4
  /**
4
5
  * Extracts a rectangular region from a 1D {@link Uint8Array} mask.
@@ -1,4 +1,4 @@
1
- import type { Rect } from '../_types'
1
+ import type { Rect } from '../Rect/_rect-types'
2
2
 
3
3
  /**
4
4
  * Extracts a rectangular region from a 1D {@link Uint8Array} mask.
@@ -1,4 +1,5 @@
1
- import { type AlphaMask, type MergeAlphaMasksOptions } from '../_types'
1
+ import type { MergeAlphaMasksOptions } from '../_types'
2
+ import type { AlphaMask } from './_mask-types'
2
3
 
3
4
  /**
4
5
  * Merges 2 alpha masks values are 0-255
@@ -58,7 +59,8 @@ export function mergeAlphaMasks(
58
59
  } else if (globalAlpha === 255) {
59
60
  weight = effectiveM
60
61
  } else {
61
- weight = (effectiveM * globalAlpha + 128) >> 8
62
+ const t = effectiveM * globalAlpha + 128
63
+ weight = (t + (t >> 8)) >> 8
62
64
  }
63
65
 
64
66
  if (weight !== 255) {
@@ -70,7 +72,8 @@ export function mergeAlphaMasks(
70
72
  if (da === 255) {
71
73
  dstData[dIdx] = weight
72
74
  } else if (da !== 0) {
73
- dstData[dIdx] = (da * weight + 128) >> 8
75
+ const t = da * weight + 128
76
+ dstData[dIdx] = (t + (t >> 8)) >> 8
74
77
  }
75
78
  }
76
79
  }
@@ -1,4 +1,5 @@
1
- import type { BinaryMask, MergeAlphaMasksOptions } from '../_types'
1
+ import type { MergeAlphaMasksOptions } from '../_types'
2
+ import type { BinaryMask } from './_mask-types'
2
3
 
3
4
  export function mergeBinaryMasks(
4
5
  dst: BinaryMask,
@@ -1,4 +1,4 @@
1
- import type { Mask } from '../_types'
1
+ import type { Mask } from './_mask-types'
2
2
 
3
3
  export function setMaskData(mask: Mask, width: number, height: number, data: Uint8Array): void {
4
4
  ;(mask as any).w = width
@@ -1,4 +1,4 @@
1
- import { MaskType, type NullableBinaryMaskRect } from '../_types'
1
+ import { MaskType, type NullableBinaryMaskRect } from '../Mask/_mask-types'
2
2
  import { getRectsBounds } from '../Rect/getRectsBounds'
3
3
 
4
4
  export function merge2BinaryMaskRects(
@@ -34,7 +34,7 @@ export function merge2BinaryMaskRects(
34
34
  }
35
35
  }
36
36
 
37
- const maskData = new Uint8Array(bounds.w * bounds.h)
37
+ const maskData = new Uint8Array(bounds.w * bounds.h)
38
38
 
39
39
  // --- Write A's contribution ---
40
40
  const aOffY = a.y - bounds.y
@@ -1,4 +1,4 @@
1
- import type { NullableBinaryMaskRect } from '../_types'
1
+ import type { NullableBinaryMaskRect } from '../Mask/_mask-types'
2
2
  import { merge2BinaryMaskRects } from './merge2BinaryMaskRects'
3
3
 
4
4
  export function mergeBinaryMaskRects(current: NullableBinaryMaskRect[], adding: NullableBinaryMaskRect[]): NullableBinaryMaskRect[] {
@@ -1,4 +1,4 @@
1
- import { MaskType, type NullableBinaryMaskRect } from '../_types'
1
+ import { MaskType, type NullableBinaryMaskRect } from '../Mask/_mask-types'
2
2
 
3
3
  export function subtractBinaryMaskRects(
4
4
  current: NullableBinaryMaskRect[],
@@ -0,0 +1,339 @@
1
+ import { type Color32 } from '../_types'
2
+ import { forEachLinePoint } from '../Algorithm/forEachLinePoint'
3
+ import { sourceOverPerfect } from '../BlendModes/blend-modes-perfect'
4
+ import type { PixelAccumulator } from '../History/PixelAccumulator'
5
+ import type { PixelEngineConfig } from '../History/PixelEngineConfig'
6
+ import { _macro_paintRectCenterOffset } from '../Internal/macros'
7
+ import { blendColorPixelDataAlphaMask } from '../PixelData/blendColorPixelDataAlphaMask'
8
+ import type { Rect } from '../Rect/_rect-types'
9
+ import { trimRectBounds } from '../Rect/trimRectBounds'
10
+ import type { AlphaMaskTile } from '../Tile/_tile-types'
11
+ import type { TilePool } from '../Tile/TilePool'
12
+ import type { PaintAlphaMask, PaintBinaryMask } from './_paint-types'
13
+ import { eachTileInBounds } from './eachTileInBounds'
14
+
15
+ export class AlphaMaskPaintBuffer {
16
+ readonly lookup: (AlphaMaskTile | undefined)[]
17
+ private readonly scratchBounds: Rect = { x: 0, y: 0, w: 0, h: 0 }
18
+
19
+ private blendColorPixelDataAlphaMaskFn = blendColorPixelDataAlphaMask
20
+ private forEachLinePointFn = forEachLinePoint
21
+ private trimRectBoundsFn = trimRectBounds
22
+ private eachTileInBoundsFn = eachTileInBounds
23
+
24
+ constructor(
25
+ readonly config: PixelEngineConfig,
26
+ readonly tilePool: TilePool<AlphaMaskTile>,
27
+ ) {
28
+ this.lookup = []
29
+ }
30
+
31
+ paintAlphaMask(
32
+ brush: PaintAlphaMask,
33
+ x: number,
34
+ y: number,
35
+ ): boolean
36
+ paintAlphaMask(
37
+ brush: PaintAlphaMask,
38
+ startX: number,
39
+ startY: number,
40
+ endX: number,
41
+ endY: number,
42
+ ): boolean
43
+ paintAlphaMask(
44
+ brush: PaintAlphaMask,
45
+ x0: number,
46
+ y0: number,
47
+ x1: number = x0,
48
+ y1: number = y0,
49
+ ): boolean {
50
+
51
+ const scratch = this.scratchBounds
52
+ const lookup = this.lookup
53
+ const tilePool = this.tilePool
54
+ const config = this.config
55
+ const tileShift = config.tileShift
56
+ const tileMask = config.tileMask
57
+ const target = config.target
58
+
59
+ const { w: bW, h: bH, data: bD, centerOffsetX, centerOffsetY } = brush
60
+
61
+ let changed = false
62
+
63
+ const eachTileInBoundsFn = this.eachTileInBoundsFn
64
+ const trimRectBoundsFn = this.trimRectBoundsFn
65
+
66
+ this.forEachLinePointFn(x0, y0, x1, y1, (px, py) => {
67
+
68
+ const topLeftX = Math.floor(px + centerOffsetX)
69
+ const topLeftY = Math.floor(py + centerOffsetY)
70
+ trimRectBoundsFn(
71
+ topLeftX,
72
+ topLeftY,
73
+ bW,
74
+ bH,
75
+ target.w,
76
+ target.h,
77
+ scratch,
78
+ )
79
+
80
+ if (scratch.w <= 0 || scratch.h <= 0) return
81
+
82
+ eachTileInBoundsFn(config, lookup, tilePool, scratch, (tile, bX, bY, bW_t, bH_t) => {
83
+ const data = tile.data
84
+ let tileChanged = false
85
+
86
+ for (let i = 0; i < bH_t; i++) {
87
+ const canvasY = bY + i
88
+ const bOff = (canvasY - topLeftY) * bW
89
+ const tOff = (canvasY & tileMask) << tileShift
90
+ const dS = tOff + (bX & tileMask)
91
+
92
+ for (let j = 0; j < bW_t; j++) {
93
+ const canvasX = bX + j
94
+ const brushA = bD[bOff + (canvasX - topLeftX)]
95
+ if (brushA === 0) continue
96
+ const idx = dS + j
97
+
98
+ // Only overwrite if the brush stroke is stronger than the existing mask pixel
99
+ if (brushA > data[idx]) {
100
+ data[idx] = brushA
101
+ tileChanged = true
102
+ }
103
+ }
104
+ }
105
+ if (tileChanged) changed = true
106
+ })
107
+ })
108
+ return changed
109
+ }
110
+
111
+ paintBinaryMask(
112
+ brush: PaintBinaryMask,
113
+ alpha: number,
114
+ x: number,
115
+ y: number,
116
+ ): boolean
117
+ paintBinaryMask(
118
+ brush: PaintBinaryMask,
119
+ alpha: number,
120
+ startX: number,
121
+ startY: number,
122
+ endX: number,
123
+ endY: number,
124
+ ): boolean
125
+ paintBinaryMask(
126
+ brush: PaintBinaryMask,
127
+ alpha: number,
128
+ x0: number,
129
+ y0: number,
130
+ x1: number = x0,
131
+ y1: number = y0,
132
+ ): boolean {
133
+ if (alpha === 0) return false
134
+
135
+ const scratch = this.scratchBounds
136
+ const lookup = this.lookup
137
+ const tilePool = this.tilePool
138
+ const config = this.config
139
+ const tileShift = config.tileShift
140
+ const tileMask = config.tileMask
141
+ const target = config.target
142
+
143
+ const { w: bW, h: bH, data: bD, centerOffsetX, centerOffsetY } = brush
144
+ let changed = false
145
+
146
+ const trimRectBoundsFn = this.trimRectBoundsFn
147
+ const eachTileInBoundsFn = this.eachTileInBoundsFn
148
+
149
+ this.forEachLinePointFn(x0, y0, x1, y1, (px, py) => {
150
+ const topLeftX = Math.floor(px + centerOffsetX)
151
+ const topLeftY = Math.floor(py + centerOffsetY)
152
+
153
+ trimRectBoundsFn(
154
+ topLeftX,
155
+ topLeftY,
156
+ bW,
157
+ bH,
158
+ target.w,
159
+ target.h,
160
+ scratch,
161
+ )
162
+
163
+ if (scratch.w <= 0 || scratch.h <= 0) return
164
+
165
+ eachTileInBoundsFn(config, lookup, tilePool, scratch, (tile, bX, bY, bW_t, bH_t) => {
166
+ const data = tile.data
167
+ let tileChanged = false
168
+
169
+ for (let i = 0; i < bH_t; i++) {
170
+ const canvasY = bY + i
171
+ const bOff = (canvasY - topLeftY) * bW
172
+ const tOff = (canvasY & tileMask) << tileShift
173
+ const dS = tOff + (bX & tileMask)
174
+
175
+ for (let j = 0; j < bW_t; j++) {
176
+ const canvasX = bX + j
177
+ if (bD[bOff + (canvasX - topLeftX)]) {
178
+ const idx = dS + j
179
+ if (data[idx] < alpha) {
180
+ data[idx] = alpha
181
+ tileChanged = true
182
+ }
183
+ }
184
+ }
185
+ }
186
+ if (tileChanged) changed = true
187
+ })
188
+ })
189
+
190
+ return changed
191
+ }
192
+
193
+ paintRect(
194
+ alpha: number,
195
+ brushWidth: number,
196
+ brushHeight: number,
197
+ x: number,
198
+ y: number,
199
+ ): boolean
200
+ paintRect(
201
+ alpha: number,
202
+ brushWidth: number,
203
+ brushHeight: number,
204
+ startX: number,
205
+ startY: number,
206
+ endX: number,
207
+ endY: number,
208
+ ): boolean
209
+ paintRect(
210
+ alpha: number,
211
+ brushWidth: number,
212
+ brushHeight: number,
213
+ x0: number,
214
+ y0: number,
215
+ x1: number = x0,
216
+ y1: number = y0,
217
+ ): boolean {
218
+ const scratch = this.scratchBounds
219
+ const lookup = this.lookup
220
+ const tilePool = this.tilePool
221
+ const config = this.config
222
+ const tileShift = config.tileShift
223
+ const tileMask = config.tileMask
224
+ const target = config.target
225
+
226
+ const centerOffsetX = _macro_paintRectCenterOffset(brushWidth)
227
+ const centerOffsetY = _macro_paintRectCenterOffset(brushHeight)
228
+
229
+ const trimRectBoundsFn = this.trimRectBoundsFn
230
+ const eachTileInBoundsFn = this.eachTileInBoundsFn
231
+
232
+ let changed = false
233
+ this.forEachLinePointFn(
234
+ x0,
235
+ y0,
236
+ x1,
237
+ y1,
238
+ (px, py) => {
239
+ const topLeftX = Math.floor(px + centerOffsetX)
240
+ const topLeftY = Math.floor(py + centerOffsetY)
241
+
242
+ trimRectBoundsFn(
243
+ topLeftX,
244
+ topLeftY,
245
+ brushWidth,
246
+ brushHeight,
247
+ target.w,
248
+ target.h,
249
+ scratch,
250
+ )
251
+
252
+ if (scratch.w <= 0 || scratch.h <= 0) return
253
+
254
+ eachTileInBoundsFn(config, lookup, tilePool, scratch, (tile, bX, bY, bW_t, bH_t) => {
255
+ const data = tile.data
256
+ let tileChanged = false
257
+
258
+ for (let i = 0; i < bH_t; i++) {
259
+ const canvasY = bY + i
260
+ const tOff = (canvasY & tileMask) << tileShift
261
+ const dS = tOff + (bX & tileMask)
262
+
263
+ for (let j = 0; j < bW_t; j++) {
264
+ const idx = dS + j
265
+
266
+ // If the new alpha is stronger than the current alpha, overwrite it
267
+ if (alpha > data[idx]) {
268
+ data[idx] = alpha
269
+ tileChanged = true
270
+ }
271
+ }
272
+ }
273
+
274
+ if (tileChanged) {
275
+ changed = true
276
+ }
277
+ },
278
+ )
279
+ },
280
+ )
281
+
282
+ return changed
283
+ }
284
+
285
+ private opts = {
286
+ alpha: 255,
287
+ blendFn: sourceOverPerfect,
288
+ x: 0,
289
+ y: 0,
290
+ w: 0,
291
+ h: 0,
292
+ }
293
+
294
+ commit(
295
+ accumulator: PixelAccumulator,
296
+ color: Color32,
297
+ alpha = 255,
298
+ blendFn = sourceOverPerfect,
299
+ ) {
300
+ const blendColorPixelDataAlphaMaskFn = this.blendColorPixelDataAlphaMaskFn
301
+ const tileShift = this.config.tileShift
302
+ const lookup = this.lookup
303
+ const opts = this.opts
304
+
305
+ opts.alpha = alpha
306
+ opts.blendFn = blendFn
307
+
308
+ for (let i = 0; i < lookup.length; i++) {
309
+ const tile = lookup[i]
310
+
311
+ if (tile) {
312
+ const didChange = accumulator.storeTileBeforeState(tile.id, tile.tx, tile.ty)
313
+
314
+ const dx = tile.tx << tileShift
315
+ const dy = tile.ty << tileShift
316
+
317
+ opts.x = dx
318
+ opts.y = dy
319
+ opts.w = tile.w
320
+ opts.h = tile.h
321
+
322
+ didChange(
323
+ blendColorPixelDataAlphaMaskFn(
324
+ this.config.target,
325
+ color,
326
+ tile,
327
+ opts,
328
+ ),
329
+ )
330
+ }
331
+ }
332
+
333
+ this.clear()
334
+ }
335
+
336
+ clear(): void {
337
+ this.tilePool.releaseTiles(this.lookup)
338
+ }
339
+ }