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
@@ -1,78 +1,64 @@
1
- import type { Color32, PaintAlphaMask, PaintBinaryMask, Rect } from '../_types'
1
+ import type { Color32 } from '../_types'
2
2
  import { forEachLinePoint } from '../Algorithm/forEachLinePoint'
3
+ import { sourceOverPerfect } from '../BlendModes/blend-modes-perfect'
4
+ import type { PixelAccumulator } from '../History/PixelAccumulator'
3
5
  import type { PixelEngineConfig } from '../History/PixelEngineConfig'
4
- import { _macro_paintRectCenterOffset } from '../Internal/helpers'
5
- import type { PixelTile } from '../PixelTile/PixelTile'
6
- import type { PixelTilePool } from '../PixelTile/PixelTilePool'
6
+ import { _macro_paintRectCenterOffset } from '../Internal/macros'
7
+ import { blendPixelData } from '../PixelData/blendPixelData'
8
+ import type { Rect } from '../Rect/_rect-types'
7
9
  import { trimRectBounds } from '../Rect/trimRectBounds'
10
+ import type { PixelTile } from '../Tile/_tile-types'
11
+ import type { TilePool } from '../Tile/TilePool'
12
+ import type { PaintAlphaMask, PaintBinaryMask } from './_paint-types'
13
+ import { eachTileInBounds } from './eachTileInBounds'
8
14
 
9
- export class PaintBuffer {
15
+ export class ColorPaintBuffer {
10
16
  readonly lookup: (PixelTile | undefined)[]
11
17
  private readonly scratchBounds: Rect = { x: 0, y: 0, w: 0, h: 0 }
12
18
 
13
19
  constructor(
14
20
  readonly config: PixelEngineConfig,
15
- readonly tilePool: PixelTilePool,
21
+ readonly tilePool: TilePool<PixelTile>,
22
+ private blendPixelDataFn = blendPixelData,
16
23
  ) {
17
24
  this.lookup = []
18
25
  }
19
26
 
20
- private eachTileInBounds(
21
- bounds: Rect,
22
- callback: (tile: PixelTile, bX: number, bY: number, bW: number, bH: number) => void,
23
- ): void {
24
- const { tileShift, targetColumns, targetRows, tileSize } = this.config
25
-
26
- const x1 = Math.max(0, bounds.x >> tileShift)
27
- const y1 = Math.max(0, bounds.y >> tileShift)
28
- const x2 = Math.min(targetColumns - 1, (bounds.x + bounds.w - 1) >> tileShift)
29
- const y2 = Math.min(targetRows - 1, (bounds.y + bounds.h - 1) >> tileShift)
30
-
31
- if (x1 > x2 || y1 > y2) return
32
-
33
- const lookup = this.lookup
34
- const tilePool = this.tilePool
35
-
36
- for (let ty = y1; ty <= y2; ty++) {
37
- const rowOffset = ty * targetColumns
38
- const tileTop = ty << tileShift
39
-
40
- for (let tx = x1; tx <= x2; tx++) {
41
- const id = rowOffset + tx
42
- const tile = lookup[id] ?? (lookup[id] = tilePool.getTile(id, tx, ty))
43
- const tileLeft = tx << tileShift
44
-
45
- const startX = bounds.x > tileLeft ? bounds.x : tileLeft
46
- const startY = bounds.y > tileTop ? bounds.y : tileTop
47
-
48
- const maskEndX = bounds.x + bounds.w
49
- const tileEndX = tileLeft + tileSize
50
- const endX = maskEndX < tileEndX ? maskEndX : tileEndX
51
-
52
- const maskEndY = bounds.y + bounds.h
53
- const tileEndY = tileTop + tileSize
54
- const endY = maskEndY < tileEndY ? maskEndY : tileEndY
55
-
56
- callback(tile, startX, startY, endX - startX, endY - startY)
57
- }
58
- }
59
- }
60
-
61
- writePaintAlphaMaskStroke(
27
+ paintAlphaMask(
28
+ color: Color32,
29
+ brush: PaintAlphaMask,
30
+ x: number,
31
+ y: number,
32
+ ): boolean
33
+ paintAlphaMask(
34
+ color: Color32,
35
+ brush: PaintAlphaMask,
36
+ startX: number,
37
+ startY: number,
38
+ endX: number,
39
+ endY: number,
40
+ ): boolean
41
+ paintAlphaMask(
62
42
  color: Color32,
63
43
  brush: PaintAlphaMask,
64
44
  x0: number,
65
45
  y0: number,
66
- x1: number,
67
- y1: number,
46
+ x1: number = x0,
47
+ y1: number = y0,
68
48
  ): boolean {
69
49
  const cA = color >>> 24
70
50
  if (cA === 0) return false
71
51
 
72
- const { tileShift, tileMask, target } = this.config
52
+ const scratch = this.scratchBounds
53
+ const lookup = this.lookup
54
+ const tilePool = this.tilePool
55
+ const config = this.config
56
+ const tileShift = config.tileShift
57
+ const tileMask = config.tileMask
58
+ const target = config.target
59
+
73
60
  const { w: bW, h: bH, data: bD, centerOffsetX, centerOffsetY } = brush
74
61
  const cRGB = color & 0x00ffffff
75
- const scratch = this.scratchBounds
76
62
 
77
63
  let changed = false
78
64
 
@@ -85,15 +71,15 @@ export class PaintBuffer {
85
71
  topLeftY,
86
72
  bW,
87
73
  bH,
88
- target.width,
89
- target.height,
74
+ target.w,
75
+ target.h,
90
76
  scratch,
91
77
  )
92
78
 
93
79
  if (scratch.w <= 0 || scratch.h <= 0) return
94
80
 
95
- this.eachTileInBounds(scratch, (tile, bX, bY, bW_t, bH_t) => {
96
- const d32 = tile.data32
81
+ eachTileInBounds(config, lookup, tilePool, scratch, (tile, bX, bY, bW_t, bH_t) => {
82
+ const d32 = tile.data
97
83
  let tileChanged = false
98
84
 
99
85
  for (let i = 0; i < bH_t; i++) {
@@ -127,20 +113,40 @@ export class PaintBuffer {
127
113
  return changed
128
114
  }
129
115
 
130
- writePaintBinaryMaskStroke(
116
+ paintBinaryMask(
117
+ color: Color32,
118
+ brush: PaintBinaryMask,
119
+ x: number,
120
+ y: number,
121
+ ): boolean
122
+ paintBinaryMask(
123
+ color: Color32,
124
+ brush: PaintBinaryMask,
125
+ startX: number,
126
+ startY: number,
127
+ endX: number,
128
+ endY: number,
129
+ ): boolean
130
+ paintBinaryMask(
131
131
  color: Color32,
132
132
  brush: PaintBinaryMask,
133
133
  x0: number,
134
134
  y0: number,
135
- x1: number,
136
- y1: number,
135
+ x1: number = x0,
136
+ y1: number = y0,
137
137
  ): boolean {
138
138
  const alphaIsZero = (color >>> 24) === 0
139
139
  if (alphaIsZero) return false
140
140
 
141
- const { tileShift, tileMask, target } = this.config
142
- const { w: bW, h: bH, data: bD, centerOffsetX, centerOffsetY } = brush
143
141
  const scratch = this.scratchBounds
142
+ const lookup = this.lookup
143
+ const tilePool = this.tilePool
144
+ const config = this.config
145
+ const tileShift = config.tileShift
146
+ const tileMask = config.tileMask
147
+ const target = config.target
148
+
149
+ const { w: bW, h: bH, data: bD, centerOffsetX, centerOffsetY } = brush
144
150
  let changed = false
145
151
 
146
152
  forEachLinePoint(x0, y0, x1, y1, (px, py) => {
@@ -152,15 +158,15 @@ export class PaintBuffer {
152
158
  topLeftY,
153
159
  bW,
154
160
  bH,
155
- target.width,
156
- target.height,
161
+ target.w,
162
+ target.h,
157
163
  scratch,
158
164
  )
159
165
 
160
166
  if (scratch.w <= 0 || scratch.h <= 0) return
161
167
 
162
- this.eachTileInBounds(scratch, (tile, bX, bY, bW_t, bH_t) => {
163
- const d32 = tile.data32
168
+ eachTileInBounds(config, lookup, tilePool, scratch, (tile, bX, bY, bW_t, bH_t) => {
169
+ const d32 = tile.data
164
170
  let tileChanged = false
165
171
 
166
172
  for (let i = 0; i < bH_t; i++) {
@@ -187,26 +193,44 @@ export class PaintBuffer {
187
193
  return changed
188
194
  }
189
195
 
190
- writeRectStroke(
196
+ paintRect(
197
+ color: Color32,
198
+ brushWidth: number,
199
+ brushHeight: number,
200
+ x: number,
201
+ y: number,
202
+ ): boolean
203
+ paintRect(
204
+ color: Color32,
205
+ brushWidth: number,
206
+ brushHeight: number,
207
+ startX: number,
208
+ startY: number,
209
+ endX: number,
210
+ endY: number,
211
+ ): boolean
212
+ paintRect(
191
213
  color: Color32,
192
214
  brushWidth: number,
193
215
  brushHeight: number,
194
216
  x0: number,
195
217
  y0: number,
196
- x1: number,
197
- y1: number,
218
+ x1: number = x0,
219
+ y1: number = y0,
198
220
  ): boolean {
199
221
  const alphaIsZero = (color >>> 24) === 0
200
222
  if (alphaIsZero) return false
201
223
 
224
+ const scratch = this.scratchBounds
225
+ const lookup = this.lookup
226
+ const tilePool = this.tilePool
202
227
  const config = this.config
203
228
  const tileShift = config.tileShift
204
229
  const tileMask = config.tileMask
205
230
  const target = config.target
206
- const scratch = this.scratchBounds
207
231
 
208
- const centerOffsetX = -_macro_paintRectCenterOffset(brushWidth)
209
- const centerOffsetY = -_macro_paintRectCenterOffset(brushHeight)
232
+ const centerOffsetX = _macro_paintRectCenterOffset(brushWidth)
233
+ const centerOffsetY = _macro_paintRectCenterOffset(brushHeight)
210
234
 
211
235
  let changed = false
212
236
 
@@ -224,17 +248,15 @@ export class PaintBuffer {
224
248
  topLeftY,
225
249
  brushWidth,
226
250
  brushHeight,
227
- target.width,
228
- target.height,
251
+ target.w,
252
+ target.h,
229
253
  scratch,
230
254
  )
231
255
 
232
256
  if (scratch.w <= 0 || scratch.h <= 0) return
233
257
 
234
- this.eachTileInBounds(
235
- scratch,
236
- (tile, bX, bY, bW_t, bH_t) => {
237
- const d32 = tile.data32
258
+ eachTileInBounds(config, lookup, tilePool, scratch, (tile, bX, bY, bW_t, bH_t) => {
259
+ const d32 = tile.data
238
260
  let tileChanged = false
239
261
 
240
262
  for (let i = 0; i < bH_t; i++) {
@@ -263,6 +285,55 @@ export class PaintBuffer {
263
285
  return changed
264
286
  }
265
287
 
288
+ private opts = {
289
+ alpha: 255,
290
+ blendFn: sourceOverPerfect,
291
+ x: 0,
292
+ y: 0,
293
+ w: 0,
294
+ h: 0,
295
+ }
296
+
297
+ commit(
298
+ accumulator: PixelAccumulator,
299
+ alpha = 255,
300
+ blendFn = sourceOverPerfect,
301
+ ) {
302
+ const tileShift = this.config.tileShift
303
+ const lookup = this.lookup
304
+ const opts = this.opts
305
+
306
+ const blendPixelDataFn = this.blendPixelDataFn
307
+ opts.alpha = alpha
308
+ opts.blendFn = blendFn
309
+
310
+ for (let i = 0; i < lookup.length; i++) {
311
+ const tile = lookup[i]
312
+
313
+ if (tile) {
314
+ const didChange = accumulator.storeTileBeforeState(tile.id, tile.tx, tile.ty)
315
+
316
+ const dx = tile.tx << tileShift
317
+ const dy = tile.ty << tileShift
318
+
319
+ opts.x = dx
320
+ opts.y = dy
321
+ opts.w = tile.w
322
+ opts.h = tile.h
323
+
324
+ didChange(
325
+ blendPixelDataFn(
326
+ this.config.target,
327
+ tile,
328
+ opts,
329
+ ),
330
+ )
331
+ }
332
+ }
333
+
334
+ this.clear()
335
+ }
336
+
266
337
  clear(): void {
267
338
  this.tilePool.releaseTiles(this.lookup)
268
339
  }
@@ -1,14 +1,15 @@
1
- import { CANVAS_CTX_FAILED } from '../../support/error-strings'
2
- import type { PaintBuffer } from './PaintBuffer'
1
+ import { CANVAS_CTX_FAILED } from '../Internal/_errors'
2
+ import type { ColorPaintBuffer } from './ColorPaintBuffer'
3
3
 
4
- export type PaintBufferCanvasRenderer = ReturnType<typeof makePaintBufferCanvasRenderer>
4
+ export type ColorPaintBufferCanvasRenderer = ReturnType<typeof makeColorPaintBufferCanvasRenderer>
5
5
 
6
6
  /**
7
7
  *
8
+ * @param paintBuffer
8
9
  * @param offscreenCanvasClass - @internal
9
10
  */
10
- export function makePaintBufferCanvasRenderer(
11
- paintBuffer: PaintBuffer,
11
+ export function makeColorPaintBufferCanvasRenderer(
12
+ paintBuffer: ColorPaintBuffer,
12
13
  offscreenCanvasClass = OffscreenCanvas,
13
14
  ) {
14
15
  const config = paintBuffer.config
@@ -0,0 +1,117 @@
1
+ import { type Color32 } from '../_types'
2
+ import type { CanvasContext, CanvasObjectFactory } from '../Canvas/_canvas-types'
3
+ import { packColor } from '../color'
4
+ import { CANVAS_CTX_FAILED } from '../Internal/_errors'
5
+ import { _macro_paintRectCenterOffset } from '../Internal/macros'
6
+ import { type BinaryMask, MaskType } from '../Mask/_mask-types'
7
+ import { makeBinaryMaskFromAlphaMask } from '../Mask/BinaryMask/makeBinaryMaskFromAlphaMask'
8
+ import { makeBinaryMaskOutline } from '../Mask/BinaryMask/makeBinaryMaskOutline'
9
+ import { makeCircleBinaryMaskOutline } from '../Mask/BinaryMask/makeCircleBinaryMaskOutline'
10
+ import { makeRectBinaryMaskOutline } from '../Mask/BinaryMask/makeRectBinaryMaskOutline'
11
+ import { fillPixelDataBinaryMask } from '../PixelData/fillPixelDataBinaryMask'
12
+ import { makeReusablePixelData } from '../PixelData/ReusablePixelData'
13
+ import type { Rect } from '../Rect/_rect-types'
14
+ import { type PaintMask, PaintMaskOutline } from './_paint-types'
15
+
16
+ export type PaintCursorRenderer = ReturnType<typeof makePaintCursorRenderer>
17
+
18
+ export function makePaintCursorRenderer<T extends HTMLCanvasElement | OffscreenCanvas>(
19
+ factory: CanvasObjectFactory<T> = (w, h) => new OffscreenCanvas(w, h) as T,
20
+ ) {
21
+ const canvas = factory(1, 1)
22
+ const ctx = canvas.getContext('2d')! as CanvasContext<T>
23
+ if (!ctx) throw new Error(CANVAS_CTX_FAILED)
24
+ ctx.imageSmoothingEnabled = false
25
+
26
+ const getPixelData = makeReusablePixelData()
27
+
28
+ let _color = packColor(0, 255, 255, 255)
29
+ let _scale = 1
30
+
31
+ let currentMask: PaintMask = {
32
+ type: MaskType.BINARY,
33
+ outlineType: PaintMaskOutline.RECT,
34
+ w: 1,
35
+ h: 1,
36
+ centerOffsetX: _macro_paintRectCenterOffset(10),
37
+ centerOffsetY: _macro_paintRectCenterOffset(10),
38
+ } as PaintMask
39
+
40
+ let outline: BinaryMask
41
+
42
+ function update(paintMask?: PaintMask, scale?: number, color?: Color32, alphaThreshold = 127) {
43
+ currentMask = paintMask ?? currentMask
44
+
45
+ _scale = scale ?? _scale
46
+ _color = color ?? _color
47
+
48
+ canvas.width = currentMask.w * _scale + 2 * _scale
49
+ canvas.height = currentMask.h * _scale + 2 * _scale
50
+
51
+ if (currentMask.type === MaskType.BINARY) {
52
+ if (currentMask.outlineType === PaintMaskOutline.CIRCLE) {
53
+ outline = makeCircleBinaryMaskOutline(currentMask.w, _scale)
54
+ } else if (currentMask.outlineType === PaintMaskOutline.RECT) {
55
+ outline = makeRectBinaryMaskOutline(currentMask.w, currentMask.h, _scale)
56
+ } else if (currentMask.outlineType === PaintMaskOutline.MASKED) {
57
+ outline = makeBinaryMaskOutline(currentMask, _scale)
58
+ }
59
+ } else if (currentMask.type === MaskType.ALPHA) {
60
+ const mask = makeBinaryMaskFromAlphaMask(currentMask, alphaThreshold)
61
+ outline = makeBinaryMaskOutline(mask, _scale)
62
+ }
63
+
64
+ const pixelData = getPixelData(outline.w, outline.h)
65
+ fillPixelDataBinaryMask(pixelData, _color, outline)
66
+ ctx.putImageData(pixelData.imageData, 0, 0)
67
+ }
68
+
69
+ const boundsScratch = { x: 0, y: 0, w: 0, h: 0 }
70
+
71
+ function getBounds(centerX: number, centerY: number): Rect {
72
+ boundsScratch.x = centerX + currentMask.centerOffsetX
73
+ boundsScratch.y = centerY + currentMask.centerOffsetY
74
+ boundsScratch.w = currentMask.w
75
+ boundsScratch.h = currentMask.h
76
+
77
+ return boundsScratch
78
+ }
79
+
80
+ const boundsScaledScratch = { x: 0, y: 0, w: 0, h: 0 }
81
+
82
+ function getOutlineBoundsScaled(centerX: number, centerY: number): Rect {
83
+ boundsScaledScratch.x = centerX * _scale + currentMask.centerOffsetX * _scale - 1
84
+ boundsScaledScratch.y = centerY * _scale + currentMask.centerOffsetY * _scale - 1
85
+ boundsScaledScratch.w = currentMask.w * _scale
86
+ boundsScaledScratch.h = currentMask.h * _scale
87
+
88
+ return boundsScaledScratch
89
+ }
90
+
91
+ function draw(
92
+ drawCtx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
93
+ centerX: number,
94
+ centerY: number,
95
+ ) {
96
+ const dx = centerX * _scale + currentMask.centerOffsetX * _scale - 1
97
+ const dy = centerY * _scale + currentMask.centerOffsetY * _scale - 1
98
+
99
+ drawCtx.drawImage(canvas, Math.floor(dx), Math.floor(dy))
100
+ }
101
+
102
+ function getSettings() {
103
+ return {
104
+ color: _color,
105
+ scale: _scale,
106
+ currentMask,
107
+ }
108
+ }
109
+
110
+ return {
111
+ update,
112
+ getBounds,
113
+ getBoundsScaled: getOutlineBoundsScaled,
114
+ draw,
115
+ getSettings,
116
+ }
117
+ }
@@ -0,0 +1,22 @@
1
+ import type { AlphaMask, BinaryMask } from '../Mask/_mask-types'
2
+
3
+ export enum PaintMaskOutline {
4
+ MASKED,
5
+ CIRCLE,
6
+ RECT,
7
+ }
8
+
9
+ interface BasePaintMask<T extends PaintMaskOutline = PaintMaskOutline> {
10
+ readonly outlineType: T
11
+ readonly centerOffsetX: number
12
+ readonly centerOffsetY: number
13
+ }
14
+
15
+ export interface PaintAlphaMask<T extends PaintMaskOutline = PaintMaskOutline> extends BasePaintMask<T>, AlphaMask {
16
+ }
17
+
18
+ export interface PaintBinaryMask<T extends PaintMaskOutline = PaintMaskOutline> extends BasePaintMask<T>, BinaryMask {
19
+ }
20
+
21
+ export type PaintMask = PaintAlphaMask<any> | PaintBinaryMask<any>
22
+
@@ -0,0 +1,45 @@
1
+ import type { PixelEngineConfig } from '../History/PixelEngineConfig'
2
+ import type { Rect } from '../Rect/_rect-types'
3
+ import type { Tile } from '../Tile/_tile-types'
4
+ import type { TilePool } from '../Tile/TilePool'
5
+
6
+ export function eachTileInBounds<T extends Tile>(
7
+ config: PixelEngineConfig,
8
+ lookup: (T | undefined)[],
9
+ tilePool: TilePool<T>,
10
+ bounds: Rect,
11
+ callback: (tile: T, bX: number, bY: number, bW: number, bH: number) => void,
12
+ ): void {
13
+ const { tileShift, targetColumns, targetRows, tileSize } = config
14
+
15
+ const x1 = Math.max(0, bounds.x >> tileShift)
16
+ const y1 = Math.max(0, bounds.y >> tileShift)
17
+ const x2 = Math.min(targetColumns - 1, (bounds.x + bounds.w - 1) >> tileShift)
18
+ const y2 = Math.min(targetRows - 1, (bounds.y + bounds.h - 1) >> tileShift)
19
+
20
+ if (x1 > x2 || y1 > y2) return
21
+
22
+ for (let ty = y1; ty <= y2; ty++) {
23
+ const rowOffset = ty * targetColumns
24
+ const tileTop = ty << tileShift
25
+
26
+ for (let tx = x1; tx <= x2; tx++) {
27
+ const id = rowOffset + tx
28
+ const tile = lookup[id] ?? (lookup[id] = tilePool.getTile(id, tx, ty))
29
+ const tileLeft = tx << tileShift
30
+
31
+ const startX = bounds.x > tileLeft ? bounds.x : tileLeft
32
+ const startY = bounds.y > tileTop ? bounds.y : tileTop
33
+
34
+ const maskEndX = bounds.x + bounds.w
35
+ const tileEndX = tileLeft + tileSize
36
+ const endX = maskEndX < tileEndX ? maskEndX : tileEndX
37
+
38
+ const maskEndY = bounds.y + bounds.h
39
+ const tileEndY = tileTop + tileSize
40
+ const endY = maskEndY < tileEndY ? maskEndY : tileEndY
41
+
42
+ callback(tile, startX, startY, endX - startX, endY - startY)
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,74 @@
1
+ import { _macro_paintCircleCenterOffset } from '../Internal/macros'
2
+ import { MaskType } from '../Mask/_mask-types'
3
+ import { type PaintAlphaMask, type PaintBinaryMask, PaintMaskOutline } from './_paint-types'
4
+
5
+ export function makeCirclePaintAlphaMask(size: number, fallOff: (d: number) => number = (d) => d): PaintAlphaMask {
6
+ const area = size * size
7
+ const data = new Uint8Array(area)
8
+ const radius = size / 2
9
+ const invR = 1 / radius
10
+
11
+ const centerOffset = _macro_paintCircleCenterOffset(radius)
12
+
13
+ for (let y = 0; y < size; y++) {
14
+ const rowOffset = y * size
15
+ const dy = y - radius + 0.5
16
+ const dy2 = dy * dy
17
+
18
+ for (let x = 0; x < size; x++) {
19
+ const dx = x - radius + 0.5
20
+ const distSqr = dx * dx + dy2
21
+
22
+ if (distSqr <= (radius * radius)) {
23
+ const dist = Math.sqrt(distSqr) * invR
24
+
25
+ // Pass 1.0 at center, 0.0 at edge
26
+ const strength = fallOff(1 - dist)
27
+ if (strength > 0) {
28
+ const intensity = (strength * 255) | 0
29
+ data[rowOffset + x] = Math.max(0, Math.min(255, intensity))
30
+ }
31
+ }
32
+ }
33
+ }
34
+
35
+ return {
36
+ type: MaskType.ALPHA,
37
+ outlineType: PaintMaskOutline.CIRCLE,
38
+ data,
39
+ w: size,
40
+ h: size,
41
+ centerOffsetX: centerOffset,
42
+ centerOffsetY: centerOffset,
43
+ }
44
+ }
45
+
46
+ export function makeCirclePaintBinaryMask(size: number): PaintBinaryMask {
47
+ const area = size * size
48
+ const data = new Uint8Array(area)
49
+ const radius = size / 2
50
+ const r2 = radius * radius
51
+
52
+ for (let y = 0; y < size; y++) {
53
+ for (let x = 0; x < size; x++) {
54
+ const dx = x - radius + 0.5
55
+ const dy = y - radius + 0.5
56
+ const distSqr = dx * dx + dy * dy
57
+ if (distSqr <= r2) {
58
+ data[y * size + x] = 1
59
+ }
60
+ }
61
+ }
62
+
63
+ const centerOffset = _macro_paintCircleCenterOffset(radius)
64
+
65
+ return {
66
+ type: MaskType.BINARY,
67
+ outlineType: PaintMaskOutline.CIRCLE,
68
+ w: size,
69
+ h: size,
70
+ data,
71
+ centerOffsetX: centerOffset,
72
+ centerOffsetY: centerOffset,
73
+ }
74
+ }
@@ -1,11 +1,13 @@
1
- import { type AlphaMask, type BinaryMask, MaskType, type PaintAlphaMask, type PaintBinaryMask } from '../_types'
2
- import { _macro_halfAndFloor } from '../Internal/helpers'
1
+ import { _macro_halfAndFloor } from '../Internal/macros'
2
+ import { type AlphaMask, type BinaryMask, MaskType } from '../Mask/_mask-types'
3
+ import { type PaintAlphaMask, type PaintBinaryMask, PaintMaskOutline } from './_paint-types'
3
4
 
4
5
  export function makePaintBinaryMask(
5
6
  mask: BinaryMask,
6
7
  ): PaintBinaryMask {
7
8
  return {
8
9
  type: MaskType.BINARY,
10
+ outlineType: PaintMaskOutline.MASKED,
9
11
  data: mask.data,
10
12
  w: mask.w,
11
13
  h: mask.h,
@@ -19,6 +21,7 @@ export function makePaintAlphaMask(
19
21
  ): PaintAlphaMask {
20
22
  return {
21
23
  type: MaskType.ALPHA,
24
+ outlineType: PaintMaskOutline.MASKED,
22
25
  data: mask.data,
23
26
  w: mask.w,
24
27
  h: mask.h,
@@ -1,5 +1,6 @@
1
- import { MaskType, type PaintAlphaMask } from '../_types'
2
- import { _macro_halfAndFloor } from '../Internal/helpers'
1
+ import { _macro_halfAndFloor } from '../Internal/macros'
2
+ import { MaskType } from '../Mask/_mask-types'
3
+ import { type PaintAlphaMask, PaintMaskOutline } from './_paint-types'
3
4
 
4
5
  export function makeRectFalloffPaintAlphaMask(
5
6
  width: number,
@@ -38,6 +39,7 @@ export function makeRectFalloffPaintAlphaMask(
38
39
 
39
40
  return {
40
41
  type: MaskType.ALPHA,
42
+ outlineType: PaintMaskOutline.RECT,
41
43
  data: data,
42
44
  w: width,
43
45
  h: height,