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.
- package/dist/index.dev.cjs +1816 -1802
- package/dist/index.dev.cjs.map +1 -1
- package/dist/index.dev.js +1799 -1786
- package/dist/index.dev.js.map +1 -1
- package/dist/index.prod.cjs +1816 -1802
- package/dist/index.prod.cjs.map +1 -1
- package/dist/index.prod.d.ts +349 -342
- package/dist/index.prod.js +1799 -1786
- package/dist/index.prod.js.map +1 -1
- package/package.json +3 -2
- package/src/Algorithm/floodFillSelection.ts +2 -2
- package/src/Canvas/CanvasFrameRenderer.ts +57 -0
- package/src/Canvas/ReusableCanvas.ts +60 -11
- package/src/Canvas/canvas-blend-modes.ts +28 -0
- package/src/History/HistoryAction.ts +38 -0
- package/src/History/HistoryManager.ts +4 -8
- package/src/History/PixelAccumulator.ts +137 -99
- package/src/History/PixelEngineConfig.ts +16 -6
- package/src/History/PixelMutator/mutatorApplyAlphaMask.ts +6 -6
- package/src/History/PixelMutator/mutatorApplyBinaryMask.ts +6 -6
- package/src/History/PixelMutator/mutatorBlendColor.ts +8 -5
- package/src/History/PixelMutator/mutatorBlendPixel.ts +22 -26
- package/src/History/PixelMutator/mutatorBlendPixelData.ts +7 -5
- package/src/History/PixelMutator/mutatorBlendPixelDataAlphaMask.ts +7 -5
- package/src/History/PixelMutator/mutatorBlendPixelDataBinaryMask.ts +7 -5
- package/src/History/PixelMutator/mutatorClear.ts +6 -5
- package/src/History/PixelMutator/mutatorFill.ts +34 -9
- package/src/History/PixelMutator/mutatorFillBinaryMask.ts +4 -2
- package/src/History/PixelMutator/mutatorInvert.ts +8 -4
- package/src/History/PixelMutator.ts +2 -21
- package/src/History/PixelPatchTiles.ts +4 -16
- package/src/History/PixelWriter.ts +150 -31
- package/src/ImageData/ReusableImageData.ts +3 -5
- package/src/Internal/helpers.ts +2 -0
- package/src/Paint/PaintBuffer.ts +269 -0
- package/src/Paint/PaintBufferCanvasRenderer.ts +48 -0
- package/src/Paint/makeCirclePaintAlphaMask.ts +41 -0
- package/src/{Mask/CircleBrushBinaryMask.ts → Paint/makeCirclePaintBinaryMask.ts} +5 -6
- package/src/Paint/makePaintMask.ts +28 -0
- package/src/Paint/makeRectFalloffPaintAlphaMask.ts +47 -0
- package/src/PixelData/PixelBuffer32.ts +2 -2
- package/src/PixelData/PixelData.ts +2 -28
- package/src/PixelData/applyAlphaMaskToPixelData.ts +20 -10
- package/src/PixelData/applyBinaryMaskToPixelData.ts +26 -19
- package/src/PixelData/blendColorPixelData.ts +33 -9
- package/src/PixelData/blendColorPixelDataAlphaMask.ts +19 -9
- package/src/PixelData/blendColorPixelDataBinaryMask.ts +19 -10
- package/src/PixelData/blendPixel.ts +47 -0
- package/src/PixelData/blendPixelData.ts +17 -7
- package/src/PixelData/blendPixelDataAlphaMask.ts +15 -7
- package/src/PixelData/blendPixelDataBinaryMask.ts +16 -7
- package/src/PixelData/blendPixelDataPaintBuffer.ts +37 -0
- package/src/PixelData/clearPixelData.ts +4 -4
- package/src/PixelData/extractPixelData.ts +4 -4
- package/src/PixelData/extractPixelDataBuffer.ts +4 -4
- package/src/PixelData/fillPixelData.ts +31 -21
- package/src/PixelData/fillPixelDataBinaryMask.ts +15 -7
- package/src/PixelData/fillPixelDataFast.ts +94 -0
- package/src/PixelData/invertPixelData.ts +6 -4
- package/src/PixelData/pixelDataToAlphaMask.ts +2 -2
- package/src/PixelData/reflectPixelData.ts +3 -3
- package/src/PixelData/resamplePixelData.ts +2 -2
- package/src/PixelData/writePaintBufferToPixelData.ts +26 -0
- package/src/PixelData/writePixelDataBuffer.ts +5 -5
- package/src/PixelTile/PixelTile.ts +21 -0
- package/src/PixelTile/PixelTilePool.ts +63 -0
- package/src/Rect/trimMaskRectBounds.ts +121 -0
- package/src/Rect/trimRectBounds.ts +25 -116
- package/src/_types.ts +17 -16
- package/src/index.ts +19 -24
- package/src/History/PixelMutator/mutatorApplyCircleBrush.ts +0 -78
- package/src/History/PixelMutator/mutatorApplyCircleBrushStroke.ts +0 -181
- package/src/History/PixelMutator/mutatorApplyCirclePencil.ts +0 -59
- package/src/History/PixelMutator/mutatorApplyCirclePencilStroke.ts +0 -171
- package/src/History/PixelMutator/mutatorApplyRectBrush.ts +0 -64
- package/src/History/PixelMutator/mutatorApplyRectBrushStroke.ts +0 -182
- package/src/History/PixelMutator/mutatorApplyRectPencil.ts +0 -65
- package/src/History/PixelMutator/mutatorApplyRectPencilStroke.ts +0 -164
- package/src/Mask/CircleBrushAlphaMask.ts +0 -32
- package/src/PixelData/applyCircleBrushToPixelData.ts +0 -91
- package/src/PixelData/applyRectBrushToPixelData.ts +0 -85
- package/src/Rect/getCircleBrushOrPencilBounds.ts +0 -43
- package/src/Rect/getCircleBrushOrPencilStrokeBounds.ts +0 -24
- package/src/Rect/getRectBrushOrPencilBounds.ts +0 -38
- package/src/Rect/getRectBrushOrPencilStrokeBounds.ts +0 -26
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
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:
|
|
11
|
-
export function extractPixelDataBuffer(source:
|
|
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:
|
|
13
|
+
source: IPixelData32,
|
|
14
14
|
_x: Rect | number,
|
|
15
15
|
_y?: number,
|
|
16
16
|
_w?: number,
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import type { Color32,
|
|
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
|
|
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
|
-
*
|
|
12
|
+
* @returns true if any pixels were actually modified.
|
|
13
13
|
*/
|
|
14
14
|
export function fillPixelData(
|
|
15
|
-
dst:
|
|
15
|
+
dst: IPixelData32,
|
|
16
16
|
color: Color32,
|
|
17
17
|
rect?: Partial<Rect>,
|
|
18
|
-
):
|
|
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:
|
|
28
|
+
dst: IPixelData32,
|
|
29
29
|
color: Color32,
|
|
30
30
|
x: number,
|
|
31
31
|
y: number,
|
|
32
32
|
w: number,
|
|
33
33
|
h: number,
|
|
34
|
-
):
|
|
34
|
+
): boolean
|
|
35
35
|
export function fillPixelData(
|
|
36
|
-
dst:
|
|
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
|
-
):
|
|
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(
|
|
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
|
|
89
|
+
const rowOffset = (finalY + iy) * dw
|
|
90
|
+
const start = rowOffset + finalX
|
|
89
91
|
const end = start + actualW
|
|
90
|
-
|
|
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,
|
|
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
|
|
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:
|
|
16
|
+
dst: IPixelData32,
|
|
17
17
|
color: Color32,
|
|
18
18
|
mask: BinaryMask,
|
|
19
19
|
alpha = 255,
|
|
20
20
|
x = 0,
|
|
21
21
|
y = 0,
|
|
22
|
-
):
|
|
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]
|
|
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
|
|
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:
|
|
7
|
+
pixelData: IPixelData32,
|
|
8
8
|
opts: PixelMutateOptions = {},
|
|
9
|
-
):
|
|
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,
|
|
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:
|
|
9
|
+
pixelData: IPixelData32,
|
|
10
10
|
): AlphaMask {
|
|
11
11
|
const {
|
|
12
12
|
data32,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { IPixelData32 } from '../_types'
|
|
2
2
|
|
|
3
|
-
export function reflectPixelDataHorizontal(pixelData:
|
|
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:
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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:
|
|
13
|
+
target: IPixelData32,
|
|
14
14
|
data: Uint32Array,
|
|
15
15
|
rect: Rect,
|
|
16
16
|
): void
|
|
17
17
|
export function writePixelDataBuffer(
|
|
18
|
-
target:
|
|
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:
|
|
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
|
+
}
|