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,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
+ }
@@ -0,0 +1,78 @@
1
+ import type { Color32 } from '../_types'
2
+ import { CANVAS_CTX_FAILED } from '../Internal/_errors'
3
+ import { makePixelData } from '../PixelData/PixelData'
4
+ import type { AlphaMaskPaintBuffer } from './AlphaMaskPaintBuffer'
5
+
6
+ export type AlphaMaskPaintBufferCanvasRenderer = ReturnType<typeof makeAlphaMaskPaintBufferCanvasRenderer>
7
+
8
+ export function makeAlphaMaskPaintBufferCanvasRenderer(
9
+ paintBuffer: AlphaMaskPaintBuffer,
10
+ offscreenCanvasClass = OffscreenCanvas,
11
+ ) {
12
+ const config = paintBuffer.config
13
+ const tileSize = config.tileSize
14
+ const tileShift = config.tileShift
15
+ const tileArea = config.tileArea
16
+ const lookup = paintBuffer.lookup
17
+
18
+ const canvas = new offscreenCanvasClass(tileSize, tileSize)
19
+ const ctx = canvas.getContext('2d')
20
+
21
+ if (!ctx) throw new Error(CANVAS_CTX_FAILED)
22
+
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,
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
+ const colorRGB = color & 0x00ffffff
38
+
39
+ if (baseSrcAlpha === 0) return
40
+
41
+ targetCtx.globalAlpha = alpha / 255
42
+ targetCtx.globalCompositeOperation = compOperation
43
+
44
+ for (let i = 0; i < lookup.length; i++) {
45
+ const tile = lookup[i]
46
+
47
+ if (tile) {
48
+ const data8 = tile.data
49
+ view32.fill(0)
50
+
51
+ for (let p = 0; p < tileArea; p++) {
52
+ const maskA = data8[p]
53
+ if (maskA === 0) continue
54
+
55
+ // If mask is solid, the final pixel is just the unmodified color
56
+ if (maskA === 255) {
57
+ view32[p] = color
58
+ } else {
59
+ // Otherwise, blend the color's inherent alpha with the mask's alpha
60
+ const t = baseSrcAlpha * maskA + 128
61
+ const finalA = (t + (t >> 8)) >> 8
62
+
63
+ view32[p] = ((colorRGB | (finalA << 24)) >>> 0) as Color32
64
+ }
65
+ }
66
+
67
+ const dx = tile.tx << tileShift
68
+ const dy = tile.ty << tileShift
69
+
70
+ ctx.putImageData(bridge.imageData, 0, 0)
71
+ targetCtx.drawImage(canvas, dx, dy)
72
+ }
73
+ }
74
+
75
+ targetCtx.globalAlpha = 1
76
+ targetCtx.globalCompositeOperation = 'source-over'
77
+ }
78
+ }