pixel-data-js 0.30.0 → 0.32.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.prod.cjs +473 -312
- package/dist/index.prod.cjs.map +1 -1
- package/dist/index.prod.d.ts +130 -55
- package/dist/index.prod.js +469 -308
- package/dist/index.prod.js.map +1 -1
- package/package.json +1 -1
- package/src/Control/BatchedQueue.ts +76 -0
- package/src/Control/RenderQueue.ts +49 -0
- package/src/History/PixelWriter.ts +5 -9
- package/src/ImageData/extractImageData.ts +55 -0
- package/src/ImageData/extractImageDataBuffer.ts +57 -28
- package/src/ImageData/writeImageData.ts +45 -66
- package/src/ImageData/writeImageDataBuffer.ts +79 -41
- package/src/PixelData/extractPixelDataBuffer.ts +53 -40
- package/src/PixelData/fillPixelData.ts +46 -28
- package/src/PixelData/fillPixelDataBinaryMask.ts +43 -38
- package/src/PixelData/fillPixelDataFast.ts +26 -16
- package/src/PixelData/invertPixelData.ts +38 -21
- package/src/PixelData/resizePixelData.ts +75 -0
- package/src/PixelData/writePixelDataBuffer.ts +53 -38
- package/src/index.ts +5 -1
- package/src/Rect/resolveClipping.ts +0 -140
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import type { Rect } from '../Rect/_rect-types'
|
|
2
|
-
import { makeClippedBlit, resolveBlitClipping } from '../Rect/resolveClipping'
|
|
3
2
|
import type { PixelData32 } from './_pixelData-types'
|
|
4
3
|
|
|
5
|
-
const SCRATCH_BLIT = makeClippedBlit()
|
|
6
|
-
|
|
7
4
|
/**
|
|
8
5
|
* Extracts a rectangular region of pixels from PixelData.
|
|
9
6
|
* Returns a new Uint32Array containing the extracted pixels.
|
|
10
7
|
*/
|
|
11
|
-
export function extractPixelDataBuffer(
|
|
12
|
-
|
|
8
|
+
export function extractPixelDataBuffer(
|
|
9
|
+
source: PixelData32,
|
|
10
|
+
rect: Rect,
|
|
11
|
+
): Uint32Array
|
|
12
|
+
export function extractPixelDataBuffer(
|
|
13
|
+
source: PixelData32,
|
|
14
|
+
x: number,
|
|
15
|
+
y: number,
|
|
16
|
+
w: number,
|
|
17
|
+
h: number,
|
|
18
|
+
): Uint32Array
|
|
13
19
|
export function extractPixelDataBuffer(
|
|
14
20
|
source: PixelData32,
|
|
15
21
|
_x: Rect | number,
|
|
@@ -17,56 +23,63 @@ export function extractPixelDataBuffer(
|
|
|
17
23
|
_w?: number,
|
|
18
24
|
_h?: number,
|
|
19
25
|
): Uint32Array {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
let x: number
|
|
27
|
+
let y: number
|
|
28
|
+
let w: number
|
|
29
|
+
let h: number
|
|
30
|
+
|
|
31
|
+
if (typeof _x === 'object') {
|
|
32
|
+
x = _x.x
|
|
33
|
+
y = _x.y
|
|
34
|
+
w = _x.w
|
|
35
|
+
h = _x.h
|
|
36
|
+
} else {
|
|
37
|
+
x = _x
|
|
38
|
+
y = _y!
|
|
39
|
+
w = _w!
|
|
40
|
+
h = _h!
|
|
41
|
+
}
|
|
23
42
|
|
|
24
43
|
const srcW = source.w
|
|
25
44
|
const srcH = source.h
|
|
26
45
|
const srcData = source.data
|
|
27
46
|
|
|
28
|
-
|
|
29
|
-
if (
|
|
30
|
-
return new Uint32Array(0)
|
|
31
|
-
}
|
|
47
|
+
if (w <= 0) return new Uint32Array(0)
|
|
48
|
+
if (h <= 0) return new Uint32Array(0)
|
|
32
49
|
|
|
33
50
|
const dstData = new Uint32Array(w * h)
|
|
34
51
|
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
srcW,
|
|
49
|
-
srcH,
|
|
50
|
-
SCRATCH_BLIT,
|
|
51
|
-
)
|
|
52
|
+
// Inline clipping logic to avoid object allocations
|
|
53
|
+
let srcX = x
|
|
54
|
+
let srcY = y
|
|
55
|
+
let dstX = 0
|
|
56
|
+
let dstY = 0
|
|
57
|
+
let copyW = w
|
|
58
|
+
let copyH = h
|
|
59
|
+
|
|
60
|
+
if (srcX < 0) {
|
|
61
|
+
dstX = -srcX
|
|
62
|
+
copyW += srcX
|
|
63
|
+
srcX = 0
|
|
64
|
+
}
|
|
52
65
|
|
|
53
|
-
if (
|
|
66
|
+
if (srcY < 0) {
|
|
67
|
+
dstY = -srcY
|
|
68
|
+
copyH += srcY
|
|
69
|
+
srcY = 0
|
|
70
|
+
}
|
|
54
71
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
w: copyW,
|
|
61
|
-
h: copyH,
|
|
62
|
-
} = clip
|
|
72
|
+
copyW = Math.min(copyW, srcW - srcX)
|
|
73
|
+
copyH = Math.min(copyH, srcH - srcY)
|
|
74
|
+
|
|
75
|
+
if (copyW <= 0) return dstData
|
|
76
|
+
if (copyH <= 0) return dstData
|
|
63
77
|
|
|
64
78
|
for (let row = 0; row < copyH; row++) {
|
|
65
79
|
const srcStart = (srcY + row) * srcW + srcX
|
|
66
80
|
const dstStart = (dstY + row) * w + dstX
|
|
67
|
-
|
|
68
|
-
// Perform the high-speed 32-bit bulk copy
|
|
69
81
|
const chunk = srcData.subarray(srcStart, srcStart + copyW)
|
|
82
|
+
|
|
70
83
|
dstData.set(chunk, dstStart)
|
|
71
84
|
}
|
|
72
85
|
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import type { Color32 } from '../_types'
|
|
2
2
|
import type { Rect } from '../Rect/_rect-types'
|
|
3
|
-
import { makeClippedRect, resolveRectClipping } from '../Rect/resolveClipping'
|
|
4
3
|
import type { PixelData32 } from './_pixelData-types'
|
|
5
4
|
|
|
6
|
-
const SCRATCH_RECT = makeClippedRect()
|
|
7
|
-
|
|
8
5
|
/**
|
|
9
6
|
* Fills a region or the {@link PixelData32} buffer with a solid color.
|
|
10
7
|
*
|
|
@@ -42,6 +39,9 @@ export function fillPixelData(
|
|
|
42
39
|
_w?: number,
|
|
43
40
|
_h?: number,
|
|
44
41
|
): boolean {
|
|
42
|
+
const dstW = dst.w
|
|
43
|
+
const dstH = dst.h
|
|
44
|
+
|
|
45
45
|
let x: number
|
|
46
46
|
let y: number
|
|
47
47
|
let w: number
|
|
@@ -50,8 +50,8 @@ export function fillPixelData(
|
|
|
50
50
|
if (typeof _x === 'object') {
|
|
51
51
|
x = _x.x ?? 0
|
|
52
52
|
y = _x.y ?? 0
|
|
53
|
-
w = _x.w ??
|
|
54
|
-
h = _x.h ??
|
|
53
|
+
w = _x.w ?? dstW
|
|
54
|
+
h = _x.h ?? dstH
|
|
55
55
|
} else if (typeof _x === 'number') {
|
|
56
56
|
x = _x
|
|
57
57
|
y = _y!
|
|
@@ -60,37 +60,55 @@ export function fillPixelData(
|
|
|
60
60
|
} else {
|
|
61
61
|
x = 0
|
|
62
62
|
y = 0
|
|
63
|
-
w =
|
|
64
|
-
h =
|
|
63
|
+
w = dstW
|
|
64
|
+
h = dstH
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Inline bounds clipping
|
|
68
|
+
let dstX = x
|
|
69
|
+
let dstY = y
|
|
70
|
+
let fillW = w
|
|
71
|
+
let fillH = h
|
|
72
|
+
|
|
73
|
+
if (dstX < 0) {
|
|
74
|
+
fillW += dstX
|
|
75
|
+
dstX = 0
|
|
65
76
|
}
|
|
66
77
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
h,
|
|
72
|
-
dst.w,
|
|
73
|
-
dst.h,
|
|
74
|
-
SCRATCH_RECT,
|
|
75
|
-
)
|
|
78
|
+
if (dstY < 0) {
|
|
79
|
+
fillH += dstY
|
|
80
|
+
dstY = 0
|
|
81
|
+
}
|
|
76
82
|
|
|
77
|
-
|
|
83
|
+
fillW = Math.min(fillW, dstW - dstX)
|
|
84
|
+
fillH = Math.min(fillH, dstH - dstY)
|
|
78
85
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
y: finalY,
|
|
82
|
-
w: actualW,
|
|
83
|
-
h: actualH,
|
|
84
|
-
} = clip
|
|
86
|
+
if (fillW <= 0) return false
|
|
87
|
+
if (fillH <= 0) return false
|
|
85
88
|
|
|
86
89
|
const dst32 = dst.data
|
|
87
|
-
const dw = dst.w
|
|
88
90
|
let hasChanged = false
|
|
89
91
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const start =
|
|
93
|
-
const end = start +
|
|
92
|
+
// Fast-path: If the area spans the full width, we can treat it as a contiguous 1D array
|
|
93
|
+
if (dstX === 0 && fillW === dstW) {
|
|
94
|
+
const start = dstY * dstW
|
|
95
|
+
const end = start + fillW * fillH
|
|
96
|
+
|
|
97
|
+
for (let i = start; i < end; i++) {
|
|
98
|
+
if (dst32[i] !== color) {
|
|
99
|
+
dst32[i] = color
|
|
100
|
+
hasChanged = true
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return hasChanged
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Standard path: row-by-row
|
|
108
|
+
for (let iy = 0; iy < fillH; iy++) {
|
|
109
|
+
const rowOffset = (dstY + iy) * dstW
|
|
110
|
+
const start = rowOffset + dstX
|
|
111
|
+
const end = start + fillW
|
|
94
112
|
|
|
95
113
|
for (let i = start; i < end; i++) {
|
|
96
114
|
if (dst32[i] !== color) {
|
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import type { Color32 } from '../_types'
|
|
2
2
|
import type { BinaryMask } from '../Mask/_mask-types'
|
|
3
|
-
import { makeClippedRect, resolveRectClipping } from '../Rect/resolveClipping'
|
|
4
3
|
import type { PixelData32 } from './_pixelData-types'
|
|
5
4
|
|
|
6
|
-
const SCRATCH_RECT = makeClippedRect()
|
|
7
|
-
|
|
8
5
|
/**
|
|
9
|
-
* Fills
|
|
6
|
+
* Fills the target PixelData with a color based on a binary mask.
|
|
7
|
+
*
|
|
10
8
|
* @param target - The target to modify.
|
|
11
9
|
* @param color - The color to apply.
|
|
12
|
-
* @param mask - The mask
|
|
13
|
-
* @param x -
|
|
14
|
-
* @param y -
|
|
10
|
+
* @param mask - The binary mask determining where to fill.
|
|
11
|
+
* @param x - Horizontal offset to place the mask.
|
|
12
|
+
* @param y - Vertical offset to place the mask.
|
|
13
|
+
* @returns true if any pixels were actually modified.
|
|
15
14
|
*/
|
|
16
15
|
export function fillPixelDataBinaryMask(
|
|
17
16
|
target: PixelData32,
|
|
@@ -20,55 +19,61 @@ export function fillPixelDataBinaryMask(
|
|
|
20
19
|
x = 0,
|
|
21
20
|
y = 0,
|
|
22
21
|
): boolean {
|
|
23
|
-
|
|
22
|
+
const targetW = target.w
|
|
23
|
+
const targetH = target.h
|
|
24
24
|
const maskW = mask.w
|
|
25
25
|
const maskH = mask.h
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
// Inline clipping logic
|
|
28
|
+
let dstX = x
|
|
29
|
+
let dstY = y
|
|
30
|
+
let actualW = maskW
|
|
31
|
+
let actualH = maskH
|
|
32
|
+
|
|
33
|
+
if (dstX < 0) {
|
|
34
|
+
actualW += dstX
|
|
35
|
+
dstX = 0
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (dstY < 0) {
|
|
39
|
+
actualH += dstY
|
|
40
|
+
dstY = 0
|
|
41
|
+
}
|
|
36
42
|
|
|
37
|
-
|
|
43
|
+
actualW = Math.min(actualW, targetW - dstX)
|
|
44
|
+
actualH = Math.min(actualH, targetH - dstY)
|
|
38
45
|
|
|
39
|
-
|
|
40
|
-
x: finalX,
|
|
41
|
-
y: finalY,
|
|
42
|
-
w: actualW,
|
|
43
|
-
h: actualH,
|
|
44
|
-
} = clip
|
|
46
|
+
if (actualW <= 0 || actualH <= 0) return false
|
|
45
47
|
|
|
46
48
|
const maskData = mask.data
|
|
47
49
|
const dst32 = target.data
|
|
48
|
-
|
|
50
|
+
|
|
51
|
+
// Calculate offsets for the mask based on clipping
|
|
52
|
+
const mx = dstX - x
|
|
53
|
+
const my = dstY - y
|
|
49
54
|
|
|
50
55
|
let hasChanged = false
|
|
51
56
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const maskOffset = maskY * maskW
|
|
57
|
+
// Stride-based loop for performance
|
|
58
|
+
let dIdx = dstY * targetW + dstX
|
|
59
|
+
let mIdx = my * maskW + mx
|
|
56
60
|
|
|
57
|
-
|
|
61
|
+
const dStride = targetW - actualW
|
|
62
|
+
const mStride = maskW - actualW
|
|
58
63
|
|
|
64
|
+
for (let iy = 0; iy < actualH; iy++) {
|
|
59
65
|
for (let ix = 0; ix < actualW; ix++) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (maskData[maskIndex]) {
|
|
65
|
-
const current = dst32[dstRowOffset + currentX]
|
|
66
|
-
if (current !== color) {
|
|
67
|
-
dst32[dstRowOffset + currentX] = color
|
|
66
|
+
if (maskData[mIdx]) {
|
|
67
|
+
if (dst32[dIdx] !== color) {
|
|
68
|
+
dst32[dIdx] = color
|
|
68
69
|
hasChanged = true
|
|
69
70
|
}
|
|
70
71
|
}
|
|
72
|
+
dIdx++
|
|
73
|
+
mIdx++
|
|
71
74
|
}
|
|
75
|
+
dIdx += dStride
|
|
76
|
+
mIdx += mStride
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
return hasChanged
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import type { Color32 } from '../_types'
|
|
2
2
|
import type { Rect } from '../Rect/_rect-types'
|
|
3
|
-
import { makeClippedRect, resolveRectClipping } from '../Rect/resolveClipping'
|
|
4
3
|
import type { PixelData32 } from './_pixelData-types'
|
|
5
4
|
|
|
6
|
-
const SCRATCH_RECT = makeClippedRect()
|
|
7
|
-
|
|
8
5
|
/**
|
|
9
6
|
* Fills a region or the {@link PixelData32} buffer with a solid color.
|
|
10
7
|
* This function is faster than {@link fillPixelData} but does not
|
|
@@ -44,6 +41,9 @@ export function fillPixelDataFast(
|
|
|
44
41
|
_w?: number,
|
|
45
42
|
_h?: number,
|
|
46
43
|
): void {
|
|
44
|
+
const dstW = dst.w
|
|
45
|
+
const dstH = dst.h
|
|
46
|
+
|
|
47
47
|
let x: number
|
|
48
48
|
let y: number
|
|
49
49
|
let w: number
|
|
@@ -66,31 +66,41 @@ export function fillPixelDataFast(
|
|
|
66
66
|
h = dst.h
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
// Inline bounds clipping
|
|
70
|
+
let dstX = x
|
|
71
|
+
let dstY = y
|
|
72
|
+
let fillW = w
|
|
73
|
+
let fillH = h
|
|
74
|
+
|
|
75
|
+
if (dstX < 0) {
|
|
76
|
+
fillW += dstX
|
|
77
|
+
dstX = 0
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (dstY < 0) {
|
|
81
|
+
fillH += dstY
|
|
82
|
+
dstY = 0
|
|
83
|
+
}
|
|
70
84
|
|
|
71
|
-
|
|
85
|
+
fillW = Math.min(fillW, dstW - dstX)
|
|
86
|
+
fillH = Math.min(fillH, dstH - dstY)
|
|
72
87
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
x: finalX,
|
|
76
|
-
y: finalY,
|
|
77
|
-
w: actualW,
|
|
78
|
-
h: actualH,
|
|
79
|
-
} = clip
|
|
88
|
+
if (fillW <= 0) return
|
|
89
|
+
if (fillH <= 0) return
|
|
80
90
|
|
|
81
91
|
const dst32 = dst.data
|
|
82
92
|
const dw = dst.w
|
|
83
93
|
|
|
84
94
|
// Optimization: If filling the entire buffer, use the native .fill()
|
|
85
|
-
if (
|
|
95
|
+
if (fillW === dw && fillH === dst.h && dstX === 0 && dstY === 0) {
|
|
86
96
|
dst32.fill(color)
|
|
87
97
|
return
|
|
88
98
|
}
|
|
89
99
|
|
|
90
100
|
// Row-by-row fill for partial rectangles
|
|
91
|
-
for (let iy = 0; iy <
|
|
92
|
-
const start = (
|
|
93
|
-
const end = start +
|
|
101
|
+
for (let iy = 0; iy < fillH; iy++) {
|
|
102
|
+
const start = (dstY + iy) * dw + dstX
|
|
103
|
+
const end = start + fillW
|
|
94
104
|
dst32.fill(color, start, end)
|
|
95
105
|
}
|
|
96
106
|
}
|
|
@@ -1,49 +1,66 @@
|
|
|
1
1
|
import { type PixelMutateOptions } from '../_types'
|
|
2
|
-
import { makeClippedRect, resolveRectClipping } from '../Rect/resolveClipping'
|
|
3
2
|
import type { PixelData32 } from './_pixelData-types'
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Inverts the RGB color data of the target PixelData, optionally controlled by a mask.
|
|
6
|
+
* @param target - The target to modify.
|
|
7
|
+
* @param opts - Options defining the area, mask, and offsets.
|
|
8
|
+
* @returns true if the operation was performed within bounds.
|
|
9
|
+
*/
|
|
7
10
|
export function invertPixelData(
|
|
8
11
|
target: PixelData32,
|
|
9
12
|
opts?: PixelMutateOptions,
|
|
10
13
|
): boolean {
|
|
14
|
+
const targetW = target.w
|
|
15
|
+
const targetH = target.h
|
|
16
|
+
|
|
11
17
|
const mask = opts?.mask
|
|
18
|
+
const invertMask = opts?.invertMask ?? false
|
|
19
|
+
|
|
12
20
|
const targetX = opts?.x ?? 0
|
|
13
21
|
const targetY = opts?.y ?? 0
|
|
14
22
|
const mx = opts?.mx ?? 0
|
|
15
23
|
const my = opts?.my ?? 0
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const invertMask = opts?.invertMask ?? false
|
|
24
|
+
const w = opts?.w ?? targetW
|
|
25
|
+
const h = opts?.h ?? targetH
|
|
19
26
|
|
|
20
|
-
|
|
27
|
+
// Inline clipping logic
|
|
28
|
+
let x = targetX
|
|
29
|
+
let y = targetY
|
|
30
|
+
let actualW = w
|
|
31
|
+
let actualH = h
|
|
21
32
|
|
|
22
|
-
if (
|
|
33
|
+
if (x < 0) {
|
|
34
|
+
actualW += x
|
|
35
|
+
x = 0
|
|
36
|
+
}
|
|
23
37
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
y
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
if (y < 0) {
|
|
39
|
+
actualH += y
|
|
40
|
+
y = 0
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
actualW = Math.min(actualW, targetW - x)
|
|
44
|
+
actualH = Math.min(actualH, targetH - y)
|
|
45
|
+
|
|
46
|
+
if (actualW <= 0 || actualH <= 0) return false
|
|
30
47
|
|
|
31
48
|
const dst32 = target.data
|
|
32
|
-
const dw =
|
|
33
|
-
const mPitch = mask?.w ?? width
|
|
49
|
+
const dw = targetW
|
|
34
50
|
|
|
51
|
+
// Calculate relative movement for the mask coordinate
|
|
35
52
|
const dx = x - targetX
|
|
36
53
|
const dy = y - targetY
|
|
37
54
|
|
|
38
55
|
let dIdx = y * dw + x
|
|
39
|
-
let mIdx = (my + dy) * mPitch + (mx + dx)
|
|
40
|
-
|
|
41
56
|
const dStride = dw - actualW
|
|
42
|
-
const mStride = mPitch - actualW
|
|
43
57
|
|
|
44
|
-
// Optimization: Split loops to avoid checking `if (mask)` for every pixel.
|
|
45
58
|
if (mask) {
|
|
46
59
|
const maskData = mask.data
|
|
60
|
+
const mPitch = mask.w
|
|
61
|
+
let mIdx = (my + dy) * mPitch + (mx + dx)
|
|
62
|
+
const mStride = mPitch - actualW
|
|
63
|
+
|
|
47
64
|
for (let iy = 0; iy < actualH; iy++) {
|
|
48
65
|
for (let ix = 0; ix < actualW; ix++) {
|
|
49
66
|
const mVal = maskData[mIdx]
|
|
@@ -52,7 +69,7 @@ export function invertPixelData(
|
|
|
52
69
|
: mVal === 1
|
|
53
70
|
|
|
54
71
|
if (isHit) {
|
|
55
|
-
// XOR with 0x00FFFFFF flips RGB bits and ignores Alpha
|
|
72
|
+
// XOR with 0x00FFFFFF flips RGB bits and ignores Alpha
|
|
56
73
|
dst32[dIdx] = dst32[dIdx] ^ 0x00FFFFFF
|
|
57
74
|
}
|
|
58
75
|
dIdx++
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { MutablePixelData32, PixelData32 } from './_pixelData-types'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Non-destructively resizes the {@link PixelData32} buffer to new dimensions, optionally
|
|
5
|
+
* offsetting the original content.
|
|
6
|
+
* This operation creates a new buffer. It does not scale or stretch pixels;
|
|
7
|
+
* instead, it crops or pads the image based on the new dimensions.
|
|
8
|
+
*
|
|
9
|
+
* @param target - The source pixel data to resize.
|
|
10
|
+
* @param newWidth - The target width in pixels.
|
|
11
|
+
* @param newHeight - The target height in pixels.
|
|
12
|
+
* @param offsetX - The horizontal offset for placing the original image.
|
|
13
|
+
* @param offsetY - The vertical offset for placing the original image.
|
|
14
|
+
* @param out - output object
|
|
15
|
+
* @returns A new {@link PixelData32} object with the specified dimensions.
|
|
16
|
+
*/
|
|
17
|
+
export function resizePixelData(
|
|
18
|
+
target: PixelData32,
|
|
19
|
+
newWidth: number,
|
|
20
|
+
newHeight: number,
|
|
21
|
+
offsetX = 0,
|
|
22
|
+
offsetY = 0,
|
|
23
|
+
out?: MutablePixelData32,
|
|
24
|
+
): PixelData32 {
|
|
25
|
+
const newData = new Uint32Array(newWidth * newHeight)
|
|
26
|
+
const {
|
|
27
|
+
w: oldW,
|
|
28
|
+
h: oldH,
|
|
29
|
+
data: oldData,
|
|
30
|
+
} = target
|
|
31
|
+
|
|
32
|
+
const result = out ?? {} as MutablePixelData32
|
|
33
|
+
result.w = newWidth
|
|
34
|
+
result.h = newHeight
|
|
35
|
+
result.data = newData
|
|
36
|
+
|
|
37
|
+
// Determine intersection of the old image (at offset) and new canvas bounds
|
|
38
|
+
const x0 = Math.max(0, offsetX)
|
|
39
|
+
const y0 = Math.max(0, offsetY)
|
|
40
|
+
const x1 = Math.min(newWidth, offsetX + oldW)
|
|
41
|
+
const y1 = Math.min(newHeight, offsetY + oldH)
|
|
42
|
+
|
|
43
|
+
if (x1 <= x0 || y1 <= y0) {
|
|
44
|
+
return result
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const copyW = x1 - x0
|
|
48
|
+
const copyH = y1 - y0
|
|
49
|
+
|
|
50
|
+
// Optimization: If we are copying the full width of both buffers,
|
|
51
|
+
// we can perform a single bulk 1D copy.
|
|
52
|
+
if (copyW === oldW && copyW === newWidth && offsetX === 0) {
|
|
53
|
+
const srcStart = (y0 - offsetY) * oldW
|
|
54
|
+
const dstStart = y0 * newWidth
|
|
55
|
+
const len = copyW * copyH
|
|
56
|
+
|
|
57
|
+
newData.set(oldData.subarray(srcStart, srcStart + len), dstStart)
|
|
58
|
+
return result
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Standard row-by-row copy
|
|
62
|
+
for (let row = 0; row < copyH; row++) {
|
|
63
|
+
const dstY = y0 + row
|
|
64
|
+
const srcY = dstY - offsetY
|
|
65
|
+
const srcX = x0 - offsetX
|
|
66
|
+
|
|
67
|
+
const dstStart = dstY * newWidth + x0
|
|
68
|
+
const srcStart = srcY * oldW + srcX
|
|
69
|
+
const chunk = oldData.subarray(srcStart, srcStart + copyW)
|
|
70
|
+
|
|
71
|
+
newData.set(chunk, dstStart)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return result
|
|
75
|
+
}
|