pixel-data-js 0.23.1 → 0.25.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 (85) hide show
  1. package/dist/index.dev.cjs +1816 -1802
  2. package/dist/index.dev.cjs.map +1 -1
  3. package/dist/index.dev.js +1799 -1786
  4. package/dist/index.dev.js.map +1 -1
  5. package/dist/index.prod.cjs +1816 -1802
  6. package/dist/index.prod.cjs.map +1 -1
  7. package/dist/index.prod.d.ts +349 -342
  8. package/dist/index.prod.js +1799 -1786
  9. package/dist/index.prod.js.map +1 -1
  10. package/package.json +3 -2
  11. package/src/Algorithm/floodFillSelection.ts +2 -2
  12. package/src/Canvas/CanvasFrameRenderer.ts +57 -0
  13. package/src/Canvas/ReusableCanvas.ts +60 -11
  14. package/src/Canvas/canvas-blend-modes.ts +28 -0
  15. package/src/History/HistoryAction.ts +38 -0
  16. package/src/History/HistoryManager.ts +4 -8
  17. package/src/History/PixelAccumulator.ts +137 -99
  18. package/src/History/PixelEngineConfig.ts +16 -6
  19. package/src/History/PixelMutator/mutatorApplyAlphaMask.ts +6 -6
  20. package/src/History/PixelMutator/mutatorApplyBinaryMask.ts +6 -6
  21. package/src/History/PixelMutator/mutatorBlendColor.ts +8 -5
  22. package/src/History/PixelMutator/mutatorBlendPixel.ts +22 -26
  23. package/src/History/PixelMutator/mutatorBlendPixelData.ts +7 -5
  24. package/src/History/PixelMutator/mutatorBlendPixelDataAlphaMask.ts +7 -5
  25. package/src/History/PixelMutator/mutatorBlendPixelDataBinaryMask.ts +7 -5
  26. package/src/History/PixelMutator/mutatorClear.ts +6 -5
  27. package/src/History/PixelMutator/mutatorFill.ts +34 -9
  28. package/src/History/PixelMutator/mutatorFillBinaryMask.ts +4 -2
  29. package/src/History/PixelMutator/mutatorInvert.ts +8 -4
  30. package/src/History/PixelMutator.ts +2 -21
  31. package/src/History/PixelPatchTiles.ts +4 -16
  32. package/src/History/PixelWriter.ts +150 -31
  33. package/src/ImageData/ReusableImageData.ts +3 -5
  34. package/src/Internal/helpers.ts +2 -0
  35. package/src/Paint/PaintBuffer.ts +269 -0
  36. package/src/Paint/PaintBufferCanvasRenderer.ts +48 -0
  37. package/src/Paint/makeCirclePaintAlphaMask.ts +41 -0
  38. package/src/{Mask/CircleBrushBinaryMask.ts → Paint/makeCirclePaintBinaryMask.ts} +5 -6
  39. package/src/Paint/makePaintMask.ts +28 -0
  40. package/src/Paint/makeRectFalloffPaintAlphaMask.ts +47 -0
  41. package/src/PixelData/PixelBuffer32.ts +2 -2
  42. package/src/PixelData/PixelData.ts +2 -28
  43. package/src/PixelData/applyAlphaMaskToPixelData.ts +20 -10
  44. package/src/PixelData/applyBinaryMaskToPixelData.ts +26 -19
  45. package/src/PixelData/blendColorPixelData.ts +33 -9
  46. package/src/PixelData/blendColorPixelDataAlphaMask.ts +19 -9
  47. package/src/PixelData/blendColorPixelDataBinaryMask.ts +19 -10
  48. package/src/PixelData/blendPixel.ts +47 -0
  49. package/src/PixelData/blendPixelData.ts +17 -7
  50. package/src/PixelData/blendPixelDataAlphaMask.ts +15 -7
  51. package/src/PixelData/blendPixelDataBinaryMask.ts +16 -7
  52. package/src/PixelData/blendPixelDataPaintBuffer.ts +37 -0
  53. package/src/PixelData/clearPixelData.ts +4 -4
  54. package/src/PixelData/extractPixelData.ts +4 -4
  55. package/src/PixelData/extractPixelDataBuffer.ts +4 -4
  56. package/src/PixelData/fillPixelData.ts +31 -21
  57. package/src/PixelData/fillPixelDataBinaryMask.ts +15 -7
  58. package/src/PixelData/fillPixelDataFast.ts +94 -0
  59. package/src/PixelData/invertPixelData.ts +6 -4
  60. package/src/PixelData/pixelDataToAlphaMask.ts +2 -2
  61. package/src/PixelData/reflectPixelData.ts +3 -3
  62. package/src/PixelData/resamplePixelData.ts +2 -2
  63. package/src/PixelData/writePaintBufferToPixelData.ts +26 -0
  64. package/src/PixelData/writePixelDataBuffer.ts +5 -5
  65. package/src/PixelTile/PixelTile.ts +21 -0
  66. package/src/PixelTile/PixelTilePool.ts +63 -0
  67. package/src/Rect/trimMaskRectBounds.ts +121 -0
  68. package/src/Rect/trimRectBounds.ts +25 -116
  69. package/src/_types.ts +17 -16
  70. package/src/index.ts +19 -24
  71. package/src/History/PixelMutator/mutatorApplyCircleBrush.ts +0 -78
  72. package/src/History/PixelMutator/mutatorApplyCircleBrushStroke.ts +0 -181
  73. package/src/History/PixelMutator/mutatorApplyCirclePencil.ts +0 -59
  74. package/src/History/PixelMutator/mutatorApplyCirclePencilStroke.ts +0 -171
  75. package/src/History/PixelMutator/mutatorApplyRectBrush.ts +0 -64
  76. package/src/History/PixelMutator/mutatorApplyRectBrushStroke.ts +0 -182
  77. package/src/History/PixelMutator/mutatorApplyRectPencil.ts +0 -65
  78. package/src/History/PixelMutator/mutatorApplyRectPencilStroke.ts +0 -164
  79. package/src/Mask/CircleBrushAlphaMask.ts +0 -32
  80. package/src/PixelData/applyCircleBrushToPixelData.ts +0 -91
  81. package/src/PixelData/applyRectBrushToPixelData.ts +0 -85
  82. package/src/Rect/getCircleBrushOrPencilBounds.ts +0 -43
  83. package/src/Rect/getCircleBrushOrPencilStrokeBounds.ts +0 -24
  84. package/src/Rect/getRectBrushOrPencilBounds.ts +0 -38
  85. package/src/Rect/getRectBrushOrPencilStrokeBounds.ts +0 -26
@@ -1,4 +1,4 @@
1
- import type { IPixelData, Rect } from '../_types'
1
+ import type { IPixelData32, Rect } from '../_types'
2
2
  import { makeClippedBlit, resolveBlitClipping } from '../Internal/resolveClipping'
3
3
 
4
4
  const SCRATCH_BLIT = makeClippedBlit()
@@ -7,10 +7,10 @@ const SCRATCH_BLIT = makeClippedBlit()
7
7
  * Extracts a rectangular region of pixels from PixelData.
8
8
  * Returns a new Uint32Array containing the extracted pixels.
9
9
  */
10
- export function extractPixelDataBuffer(source: IPixelData, rect: Rect): Uint32Array
11
- export function extractPixelDataBuffer(source: IPixelData, x: number, y: number, w: number, h: number): Uint32Array
10
+ export function extractPixelDataBuffer(source: IPixelData32, rect: Rect): Uint32Array
11
+ export function extractPixelDataBuffer(source: IPixelData32, x: number, y: number, w: number, h: number): Uint32Array
12
12
  export function extractPixelDataBuffer(
13
- source: IPixelData,
13
+ source: IPixelData32,
14
14
  _x: Rect | number,
15
15
  _y?: number,
16
16
  _w?: number,
@@ -1,21 +1,21 @@
1
- import type { Color32, IPixelData, Rect } from '../_types'
1
+ import type { Color32, IPixelData32, Rect } from '../_types'
2
2
  import { makeClippedRect, resolveRectClipping } from '../Internal/resolveClipping'
3
3
 
4
4
  const SCRATCH_RECT = makeClippedRect()
5
5
 
6
6
  /**
7
- * Fills a region or the {@link IPixelData} buffer with a solid color.
7
+ * Fills a region or the {@link IPixelData32} buffer with a solid color.
8
8
  *
9
9
  * @param dst - The target to modify.
10
10
  * @param color - The color to apply.
11
11
  * @param rect - Defines the area to fill. If omitted, the entire
12
- * buffer is filled.
12
+ * @returns true if any pixels were actually modified.
13
13
  */
14
14
  export function fillPixelData(
15
- dst: IPixelData,
15
+ dst: IPixelData32,
16
16
  color: Color32,
17
17
  rect?: Partial<Rect>,
18
- ): void
18
+ ): boolean
19
19
  /**
20
20
  * @param dst - The target to modify.
21
21
  * @param color - The color to apply.
@@ -25,21 +25,21 @@ export function fillPixelData(
25
25
  * @param h - Height of the fill area.
26
26
  */
27
27
  export function fillPixelData(
28
- dst: IPixelData,
28
+ dst: IPixelData32,
29
29
  color: Color32,
30
30
  x: number,
31
31
  y: number,
32
32
  w: number,
33
33
  h: number,
34
- ): void
34
+ ): boolean
35
35
  export function fillPixelData(
36
- dst: IPixelData,
36
+ dst: IPixelData32,
37
37
  color: Color32,
38
38
  _x?: Partial<Rect> | number,
39
39
  _y?: number,
40
40
  _w?: number,
41
41
  _h?: number,
42
- ): void {
42
+ ): boolean {
43
43
  let x: number
44
44
  let y: number
45
45
  let w: number
@@ -62,11 +62,18 @@ export function fillPixelData(
62
62
  h = dst.height
63
63
  }
64
64
 
65
- const clip = resolveRectClipping(x, y, w, h, dst.width, dst.height, SCRATCH_RECT)
65
+ const clip = resolveRectClipping(
66
+ x,
67
+ y,
68
+ w,
69
+ h,
70
+ dst.width,
71
+ dst.height,
72
+ SCRATCH_RECT,
73
+ )
66
74
 
67
- if (!clip.inBounds) return
75
+ if (!clip.inBounds) return false
68
76
 
69
- // Use the clipped values
70
77
  const {
71
78
  x: finalX,
72
79
  y: finalY,
@@ -76,17 +83,20 @@ export function fillPixelData(
76
83
 
77
84
  const dst32 = dst.data32
78
85
  const dw = dst.width
86
+ let hasChanged = false
79
87
 
80
- // Optimization: If filling the entire buffer, use the native .fill()
81
- if (actualW === dw && actualH === dst.height && finalX === 0 && finalY === 0) {
82
- dst32.fill(color)
83
- return
84
- }
85
-
86
- // Row-by-row fill for partial rectangles
87
88
  for (let iy = 0; iy < actualH; iy++) {
88
- const start = (finalY + iy) * dw + finalX
89
+ const rowOffset = (finalY + iy) * dw
90
+ const start = rowOffset + finalX
89
91
  const end = start + actualW
90
- dst32.fill(color, start, end)
92
+
93
+ for (let i = start; i < end; i++) {
94
+ if (dst32[i] !== color) {
95
+ dst32[i] = color
96
+ hasChanged = true
97
+ }
98
+ }
91
99
  }
100
+
101
+ return hasChanged
92
102
  }
@@ -1,10 +1,10 @@
1
- import type { BinaryMask, Color32, IPixelData } from '../_types'
1
+ import type { BinaryMask, Color32, IPixelData32 } from '../_types'
2
2
  import { makeClippedRect, resolveRectClipping } from '../Internal/resolveClipping'
3
3
 
4
4
  const SCRATCH_RECT = makeClippedRect()
5
5
 
6
6
  /**
7
- * Fills a region of the {@link IPixelData} buffer with a solid color using a mask.
7
+ * Fills a region of the {@link IPixelData32} buffer with a solid color using a mask.
8
8
  * @param dst - The target to modify.
9
9
  * @param color - The color to apply.
10
10
  * @param mask - The mask defining the area to fill.
@@ -13,14 +13,14 @@ const SCRATCH_RECT = makeClippedRect()
13
13
  * @param y - Starting vertical coordinate for the mask placement.
14
14
  */
15
15
  export function fillPixelDataBinaryMask(
16
- dst: IPixelData,
16
+ dst: IPixelData32,
17
17
  color: Color32,
18
18
  mask: BinaryMask,
19
19
  alpha = 255,
20
20
  x = 0,
21
21
  y = 0,
22
- ): void {
23
- if (alpha === 0) return
22
+ ): boolean {
23
+ if (alpha === 0) return false
24
24
 
25
25
  const maskW = mask.w
26
26
  const maskH = mask.h
@@ -35,7 +35,7 @@ export function fillPixelDataBinaryMask(
35
35
  SCRATCH_RECT,
36
36
  )
37
37
 
38
- if (!clip.inBounds) return
38
+ if (!clip.inBounds) return false
39
39
 
40
40
  const {
41
41
  x: finalX,
@@ -59,6 +59,8 @@ export function fillPixelDataBinaryMask(
59
59
  finalCol = ((colorRGB | (a << 24)) >>> 0) as Color32
60
60
  }
61
61
 
62
+ let hasChanged = false
63
+
62
64
  for (let iy = 0; iy < actualH; iy++) {
63
65
  const currentY = finalY + iy
64
66
  const maskY = currentY - y
@@ -72,8 +74,14 @@ export function fillPixelDataBinaryMask(
72
74
  const maskIndex = maskOffset + maskX
73
75
 
74
76
  if (maskData[maskIndex]) {
75
- dst32[dstRowOffset + currentX] = finalCol
77
+ const current = dst32[dstRowOffset + currentX]
78
+ if (current !== finalCol) {
79
+ dst32[dstRowOffset + currentX] = finalCol
80
+ hasChanged = true
81
+ }
76
82
  }
77
83
  }
78
84
  }
85
+
86
+ return hasChanged
79
87
  }
@@ -0,0 +1,94 @@
1
+ import type { Color32, IPixelData32, Rect } from '../_types'
2
+ import { makeClippedRect, resolveRectClipping } from '../Internal/resolveClipping'
3
+
4
+ const SCRATCH_RECT = makeClippedRect()
5
+
6
+ /**
7
+ * Fills a region or the {@link IPixelData32} buffer with a solid color.
8
+ * This function is faster than {@link fillPixelData} but does not
9
+ * return a boolean value indicating changes were made.
10
+ *
11
+ * @param dst - The target to modify.
12
+ * @param color - The color to apply.
13
+ * @param rect - Defines the area to fill. If omitted, the entire
14
+ * buffer is filled.
15
+ */
16
+ export function fillPixelDataFast(
17
+ dst: IPixelData32,
18
+ color: Color32,
19
+ rect?: Partial<Rect>,
20
+ ): void
21
+ /**
22
+ * @param dst - The target to modify.
23
+ * @param color - The color to apply.
24
+ * @param x - Starting horizontal coordinate.
25
+ * @param y - Starting vertical coordinate.
26
+ * @param w - Width of the fill area.
27
+ * @param h - Height of the fill area.
28
+ */
29
+ export function fillPixelDataFast(
30
+ dst: IPixelData32,
31
+ color: Color32,
32
+ x: number,
33
+ y: number,
34
+ w: number,
35
+ h: number,
36
+ ): void
37
+ export function fillPixelDataFast(
38
+ dst: IPixelData32,
39
+ color: Color32,
40
+ _x?: Partial<Rect> | number,
41
+ _y?: number,
42
+ _w?: number,
43
+ _h?: number,
44
+ ): void {
45
+ let x: number
46
+ let y: number
47
+ let w: number
48
+ let h: number
49
+
50
+ if (typeof _x === 'object') {
51
+ x = _x.x ?? 0
52
+ y = _x.y ?? 0
53
+ w = _x.w ?? dst.width
54
+ h = _x.h ?? dst.height
55
+ } else if (typeof _x === 'number') {
56
+ x = _x
57
+ y = _y!
58
+ w = _w!
59
+ h = _h!
60
+ } else {
61
+ x = 0
62
+ y = 0
63
+ w = dst.width
64
+ h = dst.height
65
+ }
66
+
67
+ const clip = resolveRectClipping(x, y, w, h, dst.width, dst.height, SCRATCH_RECT)
68
+
69
+ if (!clip.inBounds) return
70
+
71
+ // Use the clipped values
72
+ const {
73
+ x: finalX,
74
+ y: finalY,
75
+ w: actualW,
76
+ h: actualH,
77
+ } = clip
78
+
79
+ const dst32 = dst.data32
80
+ const dw = dst.width
81
+
82
+ // Optimization: If filling the entire buffer, use the native .fill()
83
+ if (actualW === dw && actualH === dst.height && finalX === 0 && finalY === 0) {
84
+ dst32.fill(color)
85
+ return
86
+ }
87
+
88
+ // Row-by-row fill for partial rectangles
89
+ for (let iy = 0; iy < actualH; iy++) {
90
+ const start = (finalY + iy) * dw + finalX
91
+ const end = start + actualW
92
+ dst32.fill(color, start, end)
93
+ }
94
+ }
@@ -1,12 +1,12 @@
1
- import { type IPixelData, type PixelMutateOptions } from '../_types'
1
+ import { type IPixelData32, type PixelMutateOptions } from '../_types'
2
2
  import { makeClippedRect, resolveRectClipping } from '../Internal/resolveClipping'
3
3
 
4
4
  const SCRATCH_RECT = makeClippedRect()
5
5
 
6
6
  export function invertPixelData(
7
- pixelData: IPixelData,
7
+ pixelData: IPixelData32,
8
8
  opts: PixelMutateOptions = {},
9
- ): void {
9
+ ): boolean {
10
10
  const dst = pixelData
11
11
  const {
12
12
  x: targetX = 0,
@@ -21,7 +21,7 @@ export function invertPixelData(
21
21
 
22
22
  const clip = resolveRectClipping(targetX, targetY, width, height, dst.width, dst.height, SCRATCH_RECT)
23
23
 
24
- if (!clip.inBounds) return
24
+ if (!clip.inBounds) return false
25
25
 
26
26
  const {
27
27
  x,
@@ -72,4 +72,6 @@ export function invertPixelData(
72
72
  dIdx += dStride
73
73
  }
74
74
  }
75
+
76
+ return true
75
77
  }
@@ -1,4 +1,4 @@
1
- import type { AlphaMask, IPixelData } from '../_types'
1
+ import type { AlphaMask, IPixelData32 } from '../_types'
2
2
  import { makeAlphaMask } from '../Mask/AlphaMask'
3
3
 
4
4
  /**
@@ -6,7 +6,7 @@ import { makeAlphaMask } from '../Mask/AlphaMask'
6
6
  * Returns a Uint8Array branded as AlphaMask.
7
7
  */
8
8
  export function pixelDataToAlphaMask(
9
- pixelData: IPixelData,
9
+ pixelData: IPixelData32,
10
10
  ): AlphaMask {
11
11
  const {
12
12
  data32,
@@ -1,6 +1,6 @@
1
- import type { IPixelData } from '../_types'
1
+ import type { IPixelData32 } from '../_types'
2
2
 
3
- export function reflectPixelDataHorizontal(pixelData: IPixelData): void {
3
+ export function reflectPixelDataHorizontal(pixelData: IPixelData32): void {
4
4
  const width = pixelData.width
5
5
  const height = pixelData.height
6
6
  const data = pixelData.data32
@@ -20,7 +20,7 @@ export function reflectPixelDataHorizontal(pixelData: IPixelData): void {
20
20
  }
21
21
  }
22
22
 
23
- export function reflectPixelDataVertical(pixelData: IPixelData): void {
23
+ export function reflectPixelDataVertical(pixelData: IPixelData32): void {
24
24
  const width = pixelData.width
25
25
  const height = pixelData.height
26
26
  const data = pixelData.data32
@@ -1,4 +1,4 @@
1
- import { type IPixelData, PixelData } from '../index'
1
+ import { type IPixelData32, PixelData } from '../index'
2
2
  import { resample32 } from '../Internal/resample32'
3
3
 
4
4
  /**
@@ -6,7 +6,7 @@ import { resample32 } from '../Internal/resample32'
6
6
  * Factor > 1 upscales, Factor < 1 downscales.
7
7
  */
8
8
  export function resamplePixelData(
9
- pixelData: IPixelData,
9
+ pixelData: IPixelData32,
10
10
  factor: number,
11
11
  ): PixelData {
12
12
  const { data, width, height } = resample32(pixelData.data32, pixelData.width, pixelData.height, factor)
@@ -0,0 +1,26 @@
1
+ import type { IPixelData } from '../_types'
2
+ import type { PaintBuffer } from '../Paint/PaintBuffer'
3
+ import { writePixelDataBuffer } from './writePixelDataBuffer'
4
+
5
+ /**
6
+ * @param writePixelDataBufferFn - @hidden
7
+ */
8
+ export function writePaintBufferToPixelData(
9
+ target: IPixelData,
10
+ paintBuffer: PaintBuffer,
11
+ writePixelDataBufferFn = writePixelDataBuffer,
12
+ ) {
13
+ const tileShift = paintBuffer.config.tileShift
14
+ const lookup = paintBuffer.lookup
15
+
16
+ for (let i = 0; i < lookup.length; i++) {
17
+ const tile = lookup[i]
18
+
19
+ if (tile) {
20
+ const dx = tile.tx << tileShift
21
+ const dy = tile.ty << tileShift
22
+
23
+ writePixelDataBufferFn(target, tile.data32, dx, dy, tile.width, tile.height)
24
+ }
25
+ }
26
+ }
@@ -1,21 +1,21 @@
1
- import { type IPixelData, type Rect } from '../_types'
1
+ import { type IPixelData32, type Rect } from '../_types'
2
2
  import { makeClippedBlit, resolveBlitClipping } from '../Internal/resolveClipping'
3
3
 
4
4
  const SCRATCH_BLIT = makeClippedBlit()
5
5
 
6
6
  /**
7
- * Copies a pixel buffer into a specific region of a {@link IPixelData} object.
7
+ * Copies a pixel buffer into a specific region of a {@link IPixelData32} object.
8
8
  *
9
9
  * This function performs a direct memory copy from a {@link Uint32Array}
10
10
  * into the target buffer.
11
11
  */
12
12
  export function writePixelDataBuffer(
13
- target: IPixelData,
13
+ target: IPixelData32,
14
14
  data: Uint32Array,
15
15
  rect: Rect,
16
16
  ): void
17
17
  export function writePixelDataBuffer(
18
- target: IPixelData,
18
+ target: IPixelData32,
19
19
  data: Uint32Array,
20
20
  x: number,
21
21
  y: number,
@@ -23,7 +23,7 @@ export function writePixelDataBuffer(
23
23
  h: number,
24
24
  ): void
25
25
  export function writePixelDataBuffer(
26
- target: IPixelData,
26
+ target: IPixelData32,
27
27
  data: Uint32Array,
28
28
  _x: Rect | number,
29
29
  _y?: number,
@@ -0,0 +1,21 @@
1
+ import type { IPixelData } from '../_types'
2
+
3
+ export class PixelTile implements IPixelData {
4
+ readonly data32: Uint32Array
5
+ readonly width: number
6
+ readonly height: number
7
+ readonly imageData: ImageData
8
+
9
+ constructor(
10
+ public id: number,
11
+ public tx: number,
12
+ public ty: number,
13
+ tileSize: number,
14
+ tileArea: number,
15
+ ) {
16
+ this.width = this.height = tileSize
17
+ this.data32 = new Uint32Array(tileArea)
18
+ const data8 = new Uint8ClampedArray(this.data32.buffer) as Uint8ClampedArray<ArrayBuffer>
19
+ this.imageData = new ImageData(data8, tileSize, tileSize)
20
+ }
21
+ }
@@ -0,0 +1,63 @@
1
+ import type { PixelEngineConfig } from '../History/PixelEngineConfig'
2
+
3
+ import { PixelTile } from './PixelTile'
4
+
5
+ export class PixelTilePool {
6
+ public pool: PixelTile[]
7
+
8
+ private tileSize: number
9
+ private tileArea: number
10
+
11
+ constructor(
12
+ config: PixelEngineConfig,
13
+ ) {
14
+ this.pool = []
15
+ this.tileSize = config.tileSize
16
+ this.tileArea = config.tileArea
17
+ }
18
+
19
+ getTile(
20
+ id: number,
21
+ tx: number,
22
+ ty: number,
23
+ ): PixelTile {
24
+ let tile = this.pool.pop()
25
+
26
+ if (tile) {
27
+ tile.id = id
28
+ tile.tx = tx
29
+ tile.ty = ty
30
+
31
+ // Wipe dirty memory from previous uses before handing it out
32
+ tile.data32.fill(0)
33
+
34
+ return tile
35
+ }
36
+
37
+ return new PixelTile(
38
+ id,
39
+ tx,
40
+ ty,
41
+ this.tileSize,
42
+ this.tileArea,
43
+ )
44
+ }
45
+
46
+ releaseTile(tile: PixelTile): void {
47
+ this.pool.push(tile)
48
+ }
49
+
50
+ releaseTiles(tiles: (PixelTile | undefined)[]): void {
51
+ let length = tiles.length
52
+
53
+ for (let i = 0; i < length; i++) {
54
+ let tile = tiles[i]
55
+
56
+ if (tile) {
57
+ this.pool.push(tile)
58
+ }
59
+ }
60
+
61
+ tiles.length = 0
62
+ }
63
+ }
@@ -0,0 +1,121 @@
1
+ import type { NullableMaskRect, Rect } from '../_types'
2
+ import { extractMaskBuffer } from '../Mask/extractMaskBuffer'
3
+
4
+ /**
5
+ * Intersects a target rectangle with a boundary, trimming dimensions and masks in-place.
6
+ * This utility calculates the axis-aligned intersection between the `target` and `bounds`.
7
+ * If the `target` includes a `mask` (as in a {@link NullableMaskRect}), the mask is physically
8
+ * cropped and re-aligned using `extractMaskBuffer` to match the new dimensions.
9
+ * @param target - The rectangle or selection object to be trimmed. **Note:** This object is mutated in-place.
10
+ * @param bounds - The boundary rectangle defining the maximum allowable area (e.g., canvas dimensions).
11
+ * @example
12
+ * const selection = { x: -10, y: -10, w: 50, h: 50, mask: new Uint8Array(2500) };
13
+ * const canvas = { x: 0, y: 0, w: 100, h: 100 };
14
+ * // Selection will be moved to (0,0) and resized to 40x40.
15
+ * // The mask is cropped by 10 px on the top and left.
16
+ * trimRectBounds(selection, canvas);
17
+ */
18
+ export function trimMaskRectBounds<T extends NullableMaskRect>(
19
+ target: T,
20
+ bounds: Rect,
21
+ ): void {
22
+ const originalX = target.x
23
+ const originalY = target.y
24
+ const originalW = target.w
25
+
26
+ const intersectedX = Math.max(target.x, bounds.x)
27
+ const intersectedY = Math.max(target.y, bounds.y)
28
+
29
+ const intersectedMaxX = Math.min(
30
+ target.x + target.w,
31
+ bounds.x + bounds.w,
32
+ )
33
+ const intersectedMaxY = Math.min(
34
+ target.y + target.h,
35
+ bounds.y + bounds.h,
36
+ )
37
+
38
+ // Intersection check
39
+ if (intersectedMaxX <= intersectedX || intersectedMaxY <= intersectedY) {
40
+ target.w = 0
41
+ target.h = 0
42
+
43
+ if ('data' in target && target.data) {
44
+ target.data = new Uint8Array(0)
45
+ }
46
+
47
+ return
48
+ }
49
+
50
+ const intersectedW = intersectedMaxX - intersectedX
51
+ const intersectedH = intersectedMaxY - intersectedY
52
+ const offsetX = intersectedX - originalX
53
+ const offsetY = intersectedY - originalY
54
+
55
+ target.x = intersectedX
56
+ target.y = intersectedY
57
+ target.w = intersectedW
58
+ target.h = intersectedH
59
+
60
+ if ('data' in target && target.data) {
61
+ const currentMaskBuffer = extractMaskBuffer(
62
+ target.data,
63
+ originalW,
64
+ offsetX,
65
+ offsetY,
66
+ intersectedW,
67
+ intersectedH,
68
+ )
69
+
70
+ let minX = intersectedW
71
+ let maxX = -1
72
+ let minY = intersectedH
73
+ let maxY = -1
74
+
75
+ // Scan for content
76
+ for (let y = 0; y < intersectedH; y++) {
77
+ for (let x = 0; x < intersectedW; x++) {
78
+ if (currentMaskBuffer[y * intersectedW + x] !== 0) {
79
+ if (x < minX) minX = x
80
+ if (x > maxX) maxX = x
81
+ if (y < minY) minY = y
82
+ if (y > maxY) maxY = y
83
+ }
84
+ }
85
+ }
86
+
87
+ // If no content is found (all zeros)
88
+ if (maxX === -1) {
89
+ target.w = 0
90
+ target.h = 0
91
+ target.data = new Uint8Array(0)
92
+
93
+ return
94
+ }
95
+
96
+ const finalW = maxX - minX + 1
97
+ const finalH = maxY - minY + 1
98
+
99
+ // Only shift and crop if the content is smaller than the intersection
100
+ if (finalW !== intersectedW || finalH !== intersectedH) {
101
+ const newMaskBuffer = extractMaskBuffer(
102
+ currentMaskBuffer,
103
+ intersectedW,
104
+ minX,
105
+ minY,
106
+ finalW,
107
+ finalH,
108
+ )
109
+
110
+ target.x += minX
111
+ target.y += minY
112
+ target.w = finalW
113
+ target.h = finalH
114
+ target.data = newMaskBuffer
115
+ } else {
116
+ target.w = finalW
117
+ target.h = finalH
118
+ target.data = currentMaskBuffer
119
+ }
120
+ }
121
+ }