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,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,283 @@
1
+ import { forEachLinePoint } from '../Algorithm/forEachLinePoint'
2
+ import type { PixelEngineConfig } from '../History/PixelEngineConfig'
3
+ import { _macro_paintRectCenterOffset } from '../Internal/macros'
4
+ import type { Rect } from '../Rect/_rect-types'
5
+ import { trimRectBounds } from '../Rect/trimRectBounds'
6
+ import type { AlphaMaskTile } from '../Tile/_tile-types'
7
+ import type { TilePool } from '../Tile/TilePool'
8
+ import type { PaintAlphaMask, PaintBinaryMask } from './_paint-types'
9
+ import { eachTileInBounds } from './eachTileInBounds'
10
+
11
+ export class AlphaMaskPaintBuffer {
12
+ readonly lookup: (AlphaMaskTile | undefined)[]
13
+ private readonly scratchBounds: Rect = { x: 0, y: 0, w: 0, h: 0 }
14
+
15
+ private forEachLinePointFn = forEachLinePoint
16
+ private trimRectBoundsFn = trimRectBounds
17
+ private eachTileInBoundsFn = eachTileInBounds
18
+
19
+ constructor(
20
+ readonly config: PixelEngineConfig,
21
+ readonly tilePool: TilePool<AlphaMaskTile>,
22
+ ) {
23
+ this.lookup = []
24
+ }
25
+
26
+ paintAlphaMask(
27
+ brush: PaintAlphaMask,
28
+ x: number,
29
+ y: number,
30
+ ): boolean
31
+ paintAlphaMask(
32
+ brush: PaintAlphaMask,
33
+ startX: number,
34
+ startY: number,
35
+ endX: number,
36
+ endY: number,
37
+ ): boolean
38
+ paintAlphaMask(
39
+ brush: PaintAlphaMask,
40
+ x0: number,
41
+ y0: number,
42
+ x1: number = x0,
43
+ y1: number = y0,
44
+ ): boolean {
45
+
46
+ const scratch = this.scratchBounds
47
+ const lookup = this.lookup
48
+ const tilePool = this.tilePool
49
+ const config = this.config
50
+ const tileShift = config.tileShift
51
+ const tileMask = config.tileMask
52
+ const target = config.target
53
+
54
+ const { w: bW, h: bH, data: bD, centerOffsetX, centerOffsetY } = brush
55
+
56
+ let changed = false
57
+
58
+ const eachTileInBoundsFn = this.eachTileInBoundsFn
59
+ const trimRectBoundsFn = this.trimRectBoundsFn
60
+
61
+ this.forEachLinePointFn(x0, y0, x1, y1, (px, py) => {
62
+
63
+ const topLeftX = Math.floor(px + centerOffsetX)
64
+ const topLeftY = Math.floor(py + centerOffsetY)
65
+ trimRectBoundsFn(
66
+ topLeftX,
67
+ topLeftY,
68
+ bW,
69
+ bH,
70
+ target.w,
71
+ target.h,
72
+ scratch,
73
+ )
74
+
75
+ if (scratch.w <= 0 || scratch.h <= 0) return
76
+
77
+ eachTileInBoundsFn(config, lookup, tilePool, scratch, (tile, bX, bY, bW_t, bH_t) => {
78
+ const data = tile.data
79
+ let tileChanged = false
80
+
81
+ for (let i = 0; i < bH_t; i++) {
82
+ const canvasY = bY + i
83
+ const bOff = (canvasY - topLeftY) * bW
84
+ const tOff = (canvasY & tileMask) << tileShift
85
+ const dS = tOff + (bX & tileMask)
86
+
87
+ for (let j = 0; j < bW_t; j++) {
88
+ const canvasX = bX + j
89
+ const brushA = bD[bOff + (canvasX - topLeftX)]
90
+ if (brushA === 0) continue
91
+ const idx = dS + j
92
+
93
+ // Only overwrite if the brush stroke is stronger than the existing mask pixel
94
+ if (brushA > data[idx]) {
95
+ data[idx] = brushA
96
+ tileChanged = true
97
+ }
98
+ }
99
+ }
100
+ if (tileChanged) changed = true
101
+ })
102
+ })
103
+ return changed
104
+ }
105
+
106
+ paintBinaryMask(
107
+ brush: PaintBinaryMask,
108
+ alpha: number,
109
+ x: number,
110
+ y: number,
111
+ ): boolean
112
+ paintBinaryMask(
113
+ brush: PaintBinaryMask,
114
+ alpha: number,
115
+ startX: number,
116
+ startY: number,
117
+ endX: number,
118
+ endY: number,
119
+ ): boolean
120
+ paintBinaryMask(
121
+ brush: PaintBinaryMask,
122
+ alpha: number,
123
+ x0: number,
124
+ y0: number,
125
+ x1: number = x0,
126
+ y1: number = y0,
127
+ ): boolean {
128
+ if (alpha === 0) return false
129
+
130
+ const scratch = this.scratchBounds
131
+ const lookup = this.lookup
132
+ const tilePool = this.tilePool
133
+ const config = this.config
134
+ const tileShift = config.tileShift
135
+ const tileMask = config.tileMask
136
+ const target = config.target
137
+
138
+ const { w: bW, h: bH, data: bD, centerOffsetX, centerOffsetY } = brush
139
+ let changed = false
140
+
141
+ const trimRectBoundsFn = this.trimRectBoundsFn
142
+ const eachTileInBoundsFn = this.eachTileInBoundsFn
143
+
144
+ this.forEachLinePointFn(x0, y0, x1, y1, (px, py) => {
145
+ const topLeftX = Math.floor(px + centerOffsetX)
146
+ const topLeftY = Math.floor(py + centerOffsetY)
147
+
148
+ trimRectBoundsFn(
149
+ topLeftX,
150
+ topLeftY,
151
+ bW,
152
+ bH,
153
+ target.w,
154
+ target.h,
155
+ scratch,
156
+ )
157
+
158
+ if (scratch.w <= 0 || scratch.h <= 0) return
159
+
160
+ eachTileInBoundsFn(config, lookup, tilePool, scratch, (tile, bX, bY, bW_t, bH_t) => {
161
+ const data = tile.data
162
+ let tileChanged = false
163
+
164
+ for (let i = 0; i < bH_t; i++) {
165
+ const canvasY = bY + i
166
+ const bOff = (canvasY - topLeftY) * bW
167
+ const tOff = (canvasY & tileMask) << tileShift
168
+ const dS = tOff + (bX & tileMask)
169
+
170
+ for (let j = 0; j < bW_t; j++) {
171
+ const canvasX = bX + j
172
+ if (bD[bOff + (canvasX - topLeftX)]) {
173
+ const idx = dS + j
174
+ if (data[idx] < alpha) {
175
+ data[idx] = alpha
176
+ tileChanged = true
177
+ }
178
+ }
179
+ }
180
+ }
181
+ if (tileChanged) changed = true
182
+ })
183
+ })
184
+
185
+ return changed
186
+ }
187
+
188
+ paintRect(
189
+ alpha: number,
190
+ brushWidth: number,
191
+ brushHeight: number,
192
+ x: number,
193
+ y: number,
194
+ ): boolean
195
+ paintRect(
196
+ alpha: number,
197
+ brushWidth: number,
198
+ brushHeight: number,
199
+ startX: number,
200
+ startY: number,
201
+ endX: number,
202
+ endY: number,
203
+ ): boolean
204
+ paintRect(
205
+ alpha: number,
206
+ brushWidth: number,
207
+ brushHeight: number,
208
+ x0: number,
209
+ y0: number,
210
+ x1: number = x0,
211
+ y1: number = y0,
212
+ ): boolean {
213
+ const scratch = this.scratchBounds
214
+ const lookup = this.lookup
215
+ const tilePool = this.tilePool
216
+ const config = this.config
217
+ const tileShift = config.tileShift
218
+ const tileMask = config.tileMask
219
+ const target = config.target
220
+
221
+ const centerOffsetX = _macro_paintRectCenterOffset(brushWidth)
222
+ const centerOffsetY = _macro_paintRectCenterOffset(brushHeight)
223
+
224
+ const trimRectBoundsFn = this.trimRectBoundsFn
225
+ const eachTileInBoundsFn = this.eachTileInBoundsFn
226
+
227
+ let changed = false
228
+ this.forEachLinePointFn(
229
+ x0,
230
+ y0,
231
+ x1,
232
+ y1,
233
+ (px, py) => {
234
+ const topLeftX = Math.floor(px + centerOffsetX)
235
+ const topLeftY = Math.floor(py + centerOffsetY)
236
+
237
+ trimRectBoundsFn(
238
+ topLeftX,
239
+ topLeftY,
240
+ brushWidth,
241
+ brushHeight,
242
+ target.w,
243
+ target.h,
244
+ scratch,
245
+ )
246
+
247
+ if (scratch.w <= 0 || scratch.h <= 0) return
248
+
249
+ eachTileInBoundsFn(config, lookup, tilePool, scratch, (tile, bX, bY, bW_t, bH_t) => {
250
+ const data = tile.data
251
+ let tileChanged = false
252
+
253
+ for (let i = 0; i < bH_t; i++) {
254
+ const canvasY = bY + i
255
+ const tOff = (canvasY & tileMask) << tileShift
256
+ const dS = tOff + (bX & tileMask)
257
+
258
+ for (let j = 0; j < bW_t; j++) {
259
+ const idx = dS + j
260
+
261
+ // If the new alpha is stronger than the current alpha, overwrite it
262
+ if (alpha > data[idx]) {
263
+ data[idx] = alpha
264
+ tileChanged = true
265
+ }
266
+ }
267
+ }
268
+
269
+ if (tileChanged) {
270
+ changed = true
271
+ }
272
+ },
273
+ )
274
+ },
275
+ )
276
+
277
+ return changed
278
+ }
279
+
280
+ clear(): void {
281
+ this.tilePool.releaseTiles(this.lookup)
282
+ }
283
+ }
@@ -0,0 +1,198 @@
1
+ import { forEachLinePoint } from '../Algorithm/forEachLinePoint'
2
+ import type { PixelEngineConfig } from '../History/PixelEngineConfig'
3
+ import { _macro_paintRectCenterOffset } from '../Internal/macros'
4
+ import type { Rect } from '../Rect/_rect-types'
5
+ import { trimRectBounds } from '../Rect/trimRectBounds'
6
+ import type { BinaryMaskTile } from '../Tile/_tile-types'
7
+ import type { TilePool } from '../Tile/TilePool'
8
+ import type { PaintBinaryMask } from './_paint-types'
9
+ import { eachTileInBounds } from './eachTileInBounds'
10
+
11
+ export class BinaryMaskPaintBuffer {
12
+ readonly lookup: (BinaryMaskTile | undefined)[]
13
+ private readonly scratchBounds: Rect = { x: 0, y: 0, w: 0, h: 0 }
14
+
15
+ private forEachLinePointFn = forEachLinePoint
16
+ private trimRectBoundsFn = trimRectBounds
17
+ private eachTileInBoundsFn = eachTileInBounds
18
+
19
+ constructor(
20
+ readonly config: PixelEngineConfig,
21
+ readonly tilePool: TilePool<BinaryMaskTile>,
22
+ ) {
23
+ this.lookup = []
24
+ }
25
+
26
+ paintBinaryMask(
27
+ brush: PaintBinaryMask,
28
+ x: number,
29
+ y: number,
30
+ ): boolean
31
+ paintBinaryMask(
32
+ brush: PaintBinaryMask,
33
+ startX: number,
34
+ startY: number,
35
+ endX: number,
36
+ endY: number,
37
+ ): boolean
38
+ paintBinaryMask(
39
+ brush: PaintBinaryMask,
40
+ x0: number,
41
+ y0: number,
42
+ x1: number = x0,
43
+ y1: number = y0,
44
+ ): boolean {
45
+ const scratch = this.scratchBounds
46
+ const lookup = this.lookup
47
+ const tilePool = this.tilePool
48
+ const config = this.config
49
+ const tileShift = config.tileShift
50
+ const tileMask = config.tileMask
51
+ const target = config.target
52
+
53
+ const { w: bW, h: bH, data: bD, centerOffsetX, centerOffsetY } = brush
54
+ let changed = false
55
+
56
+ const trimRectBoundsFn = this.trimRectBoundsFn
57
+ const eachTileInBoundsFn = this.eachTileInBoundsFn
58
+
59
+ this.forEachLinePointFn(x0, y0, x1, y1, (px, py) => {
60
+ const topLeftX = Math.floor(px + centerOffsetX)
61
+ const topLeftY = Math.floor(py + centerOffsetY)
62
+
63
+ trimRectBoundsFn(
64
+ topLeftX,
65
+ topLeftY,
66
+ bW,
67
+ bH,
68
+ target.w,
69
+ target.h,
70
+ scratch,
71
+ )
72
+
73
+ if (scratch.w <= 0 || scratch.h <= 0) return
74
+
75
+ eachTileInBoundsFn(config, lookup, tilePool, scratch, (tile, bX, bY, bW_t, bH_t) => {
76
+ const data = tile.data
77
+ let tileChanged = false
78
+
79
+ for (let i = 0; i < bH_t; i++) {
80
+ const canvasY = bY + i
81
+ const bOff = (canvasY - topLeftY) * bW
82
+ const tOff = (canvasY & tileMask) << tileShift
83
+ const dS = tOff + (bX & tileMask)
84
+
85
+ for (let j = 0; j < bW_t; j++) {
86
+ const canvasX = bX + j
87
+
88
+ if (bD[bOff + (canvasX - topLeftX)]) {
89
+ const idx = dS + j
90
+
91
+ // Only write if it's not already "on"
92
+ if (data[idx] === 0) {
93
+ data[idx] = 1
94
+ tileChanged = true
95
+ }
96
+ }
97
+ }
98
+ }
99
+ if (tileChanged) changed = true
100
+ })
101
+ })
102
+
103
+ return changed
104
+ }
105
+
106
+ paintRect(
107
+ brushWidth: number,
108
+ brushHeight: number,
109
+ x: number,
110
+ y: number,
111
+ ): boolean
112
+ paintRect(
113
+ brushWidth: number,
114
+ brushHeight: number,
115
+ startX: number,
116
+ startY: number,
117
+ endX: number,
118
+ endY: number,
119
+ ): boolean
120
+ paintRect(
121
+ brushWidth: number,
122
+ brushHeight: number,
123
+ x0: number,
124
+ y0: number,
125
+ x1: number = x0,
126
+ y1: number = y0,
127
+ ): boolean {
128
+ const scratch = this.scratchBounds
129
+ const lookup = this.lookup
130
+ const tilePool = this.tilePool
131
+ const config = this.config
132
+ const tileShift = config.tileShift
133
+ const tileMask = config.tileMask
134
+ const target = config.target
135
+
136
+ const centerOffsetX = _macro_paintRectCenterOffset(brushWidth)
137
+ const centerOffsetY = _macro_paintRectCenterOffset(brushHeight)
138
+
139
+ const trimRectBoundsFn = this.trimRectBoundsFn
140
+ const eachTileInBoundsFn = this.eachTileInBoundsFn
141
+
142
+ let changed = false
143
+ this.forEachLinePointFn(
144
+ x0,
145
+ y0,
146
+ x1,
147
+ y1,
148
+ (px, py) => {
149
+ const topLeftX = Math.floor(px + centerOffsetX)
150
+ const topLeftY = Math.floor(py + centerOffsetY)
151
+
152
+ trimRectBoundsFn(
153
+ topLeftX,
154
+ topLeftY,
155
+ brushWidth,
156
+ brushHeight,
157
+ target.w,
158
+ target.h,
159
+ scratch,
160
+ )
161
+
162
+ if (scratch.w <= 0 || scratch.h <= 0) return
163
+
164
+ eachTileInBoundsFn(config, lookup, tilePool, scratch, (tile, bX, bY, bW_t, bH_t) => {
165
+ const data = tile.data
166
+ let tileChanged = false
167
+
168
+ for (let i = 0; i < bH_t; i++) {
169
+ const canvasY = bY + i
170
+ const tOff = (canvasY & tileMask) << tileShift
171
+ const dS = tOff + (bX & tileMask)
172
+
173
+ for (let j = 0; j < bW_t; j++) {
174
+ const idx = dS + j
175
+
176
+ // Only write if it's not already "on"
177
+ if (data[idx] === 0) {
178
+ data[idx] = 1
179
+ tileChanged = true
180
+ }
181
+ }
182
+ }
183
+
184
+ if (tileChanged) {
185
+ changed = true
186
+ }
187
+ },
188
+ )
189
+ },
190
+ )
191
+
192
+ return changed
193
+ }
194
+
195
+ clear(): void {
196
+ this.tilePool.releaseTiles(this.lookup)
197
+ }
198
+ }