pixel-data-js 0.2.0 → 0.4.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 +571 -239
- package/dist/index.dev.cjs.map +1 -1
- package/dist/index.dev.js +559 -233
- package/dist/index.dev.js.map +1 -1
- package/dist/index.prod.cjs +571 -239
- package/dist/index.prod.cjs.map +1 -1
- package/dist/index.prod.d.ts +191 -147
- package/dist/index.prod.js +559 -233
- package/dist/index.prod.js.map +1 -1
- package/package.json +3 -4
- package/src/ImageData/copyImageData.ts +13 -0
- package/src/ImageData/{read-write-pixels.ts → extractImageData.ts} +4 -51
- package/src/ImageData/writeImageData.ts +54 -0
- package/src/Mask/copyMask.ts +10 -0
- package/src/Mask/invertMask.ts +25 -0
- package/src/Mask/mergeMasks.ts +105 -0
- package/src/PixelData/applyMaskToPixelData.ts +129 -0
- package/src/PixelData/blendColorPixelData.ts +157 -0
- package/src/PixelData/blendPixelData.ts +196 -0
- package/src/PixelData/clearPixelData.ts +14 -0
- package/src/PixelData/fillPixelData.ts +56 -0
- package/src/PixelData.ts +20 -0
- package/src/_types.ts +120 -12
- package/src/{ImageData/blend-modes.ts → blend-modes.ts} +1 -1
- package/src/index.ts +16 -5
- package/src/ImageData/blit.ts +0 -177
- package/src/ImageData/mask.ts +0 -150
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { type AnyMask, type ApplyMaskOptions, MaskType } from '../_types'
|
|
2
|
+
import type { PixelData } from '../PixelData'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Directly applies a mask to a region of PixelData,
|
|
6
|
+
* modifying the destination's alpha channel in-place.
|
|
7
|
+
*/
|
|
8
|
+
export function applyMaskToPixelData(
|
|
9
|
+
dst: PixelData,
|
|
10
|
+
mask: AnyMask,
|
|
11
|
+
opts: ApplyMaskOptions,
|
|
12
|
+
): void {
|
|
13
|
+
const {
|
|
14
|
+
x: targetX = 0,
|
|
15
|
+
y: targetY = 0,
|
|
16
|
+
w: width = dst.width,
|
|
17
|
+
h: height = dst.height,
|
|
18
|
+
alpha: globalAlpha = 255,
|
|
19
|
+
maskType = MaskType.ALPHA,
|
|
20
|
+
mw,
|
|
21
|
+
mx = 0,
|
|
22
|
+
my = 0,
|
|
23
|
+
invertMask = false,
|
|
24
|
+
} = opts
|
|
25
|
+
|
|
26
|
+
let x = targetX
|
|
27
|
+
let y = targetY
|
|
28
|
+
let w = width
|
|
29
|
+
let h = height
|
|
30
|
+
|
|
31
|
+
// Clipping Logic
|
|
32
|
+
if (x < 0) {
|
|
33
|
+
w += x
|
|
34
|
+
x = 0
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (y < 0) {
|
|
38
|
+
h += y
|
|
39
|
+
y = 0
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const actualW = Math.min(w, dst.width - x)
|
|
43
|
+
const actualH = Math.min(h, dst.height - y)
|
|
44
|
+
|
|
45
|
+
if (actualW <= 0 || actualH <= 0 || globalAlpha === 0) {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const dst32 = dst.data32
|
|
50
|
+
const dw = dst.width
|
|
51
|
+
const mPitch = mw ?? width
|
|
52
|
+
const isAlpha = maskType === MaskType.ALPHA
|
|
53
|
+
const dx = x - targetX
|
|
54
|
+
const dy = y - targetY
|
|
55
|
+
|
|
56
|
+
let dIdx = y * dw + x
|
|
57
|
+
let mIdx = (my + dy) * mPitch + (mx + dx)
|
|
58
|
+
|
|
59
|
+
const dStride = dw - actualW
|
|
60
|
+
const mStride = mPitch - actualW
|
|
61
|
+
|
|
62
|
+
for (let iy = 0; iy < actualH; iy++) {
|
|
63
|
+
for (let ix = 0; ix < actualW; ix++) {
|
|
64
|
+
const mVal = mask[mIdx]
|
|
65
|
+
let weight = globalAlpha
|
|
66
|
+
|
|
67
|
+
if (isAlpha) {
|
|
68
|
+
const effectiveM = invertMask
|
|
69
|
+
? 255 - mVal
|
|
70
|
+
: mVal
|
|
71
|
+
|
|
72
|
+
// Short-circuit: if source is 0, destination alpha becomes 0
|
|
73
|
+
if (effectiveM === 0) {
|
|
74
|
+
dst32[dIdx] = (dst32[dIdx] & 0x00ffffff) >>> 0
|
|
75
|
+
dIdx++
|
|
76
|
+
mIdx++
|
|
77
|
+
continue
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
weight = globalAlpha === 255
|
|
81
|
+
? effectiveM
|
|
82
|
+
: (effectiveM * globalAlpha + 128) >> 8
|
|
83
|
+
} else {
|
|
84
|
+
// Strict Binary 1/0 Logic
|
|
85
|
+
const isHit = invertMask
|
|
86
|
+
? mVal === 0
|
|
87
|
+
: mVal === 1
|
|
88
|
+
|
|
89
|
+
if (!isHit) {
|
|
90
|
+
dst32[dIdx] = (dst32[dIdx] & 0x00ffffff) >>> 0
|
|
91
|
+
dIdx++
|
|
92
|
+
mIdx++
|
|
93
|
+
continue
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
weight = globalAlpha
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// If calculated weight is 0, clear alpha
|
|
100
|
+
if (weight === 0) {
|
|
101
|
+
dst32[dIdx] = (dst32[dIdx] & 0x00ffffff) >>> 0
|
|
102
|
+
} else {
|
|
103
|
+
const d = dst32[dIdx]
|
|
104
|
+
const da = (d >>> 24)
|
|
105
|
+
|
|
106
|
+
let finalAlpha = da
|
|
107
|
+
|
|
108
|
+
if (da === 0) {
|
|
109
|
+
// Already transparent
|
|
110
|
+
} else if (weight === 255) {
|
|
111
|
+
// Identity: keep original da
|
|
112
|
+
} else if (da === 255) {
|
|
113
|
+
// Identity: result is just the weight
|
|
114
|
+
finalAlpha = weight
|
|
115
|
+
} else {
|
|
116
|
+
finalAlpha = (da * weight + 128) >> 8
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
dst32[dIdx] = ((d & 0x00ffffff) | (finalAlpha << 24)) >>> 0
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
dIdx++
|
|
123
|
+
mIdx++
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
dIdx += dStride
|
|
127
|
+
mIdx += mStride
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { type Color32, type ColorBlendOptions, MaskType } from '../_types'
|
|
2
|
+
import { sourceOverColor32 } from '../blend-modes'
|
|
3
|
+
import type { PixelData } from '../PixelData'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Fills a rectangle in the destination PixelData with a single color,
|
|
7
|
+
* supporting blend modes, global alpha, and masking.
|
|
8
|
+
*/
|
|
9
|
+
export function blendColorPixelData(
|
|
10
|
+
dst: PixelData,
|
|
11
|
+
color: Color32,
|
|
12
|
+
opts: ColorBlendOptions,
|
|
13
|
+
): void {
|
|
14
|
+
const {
|
|
15
|
+
x: targetX = 0,
|
|
16
|
+
y: targetY = 0,
|
|
17
|
+
w: width = dst.width,
|
|
18
|
+
h: height = dst.height,
|
|
19
|
+
alpha: globalAlpha = 255,
|
|
20
|
+
blendFn = sourceOverColor32,
|
|
21
|
+
mask,
|
|
22
|
+
maskType = MaskType.ALPHA,
|
|
23
|
+
mw,
|
|
24
|
+
mx = 0,
|
|
25
|
+
my = 0,
|
|
26
|
+
invertMask = false,
|
|
27
|
+
} = opts
|
|
28
|
+
|
|
29
|
+
if (globalAlpha === 0) return
|
|
30
|
+
|
|
31
|
+
let x = targetX
|
|
32
|
+
let y = targetY
|
|
33
|
+
let w = width
|
|
34
|
+
let h = height
|
|
35
|
+
|
|
36
|
+
// 1. Destination Clipping
|
|
37
|
+
if (x < 0) {
|
|
38
|
+
w += x
|
|
39
|
+
x = 0
|
|
40
|
+
}
|
|
41
|
+
if (y < 0) {
|
|
42
|
+
h += y
|
|
43
|
+
y = 0
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const actualW = Math.min(w, dst.width - x)
|
|
47
|
+
const actualH = Math.min(h, dst.height - y)
|
|
48
|
+
|
|
49
|
+
if (actualW <= 0 || actualH <= 0) return
|
|
50
|
+
|
|
51
|
+
const dst32 = dst.data32
|
|
52
|
+
const dw = dst.width
|
|
53
|
+
const mPitch = mw ?? width
|
|
54
|
+
const isAlphaMask = maskType === MaskType.ALPHA
|
|
55
|
+
|
|
56
|
+
const dx = x - targetX
|
|
57
|
+
const dy = y - targetY
|
|
58
|
+
|
|
59
|
+
let dIdx = y * dw + x
|
|
60
|
+
let mIdx = (my + dy) * mPitch + (mx + dx)
|
|
61
|
+
|
|
62
|
+
const dStride = dw - actualW
|
|
63
|
+
const mStride = mPitch - actualW
|
|
64
|
+
|
|
65
|
+
// Pre-calculate the source color with global alpha
|
|
66
|
+
const baseSrcColor = color
|
|
67
|
+
const baseSrcAlpha = (baseSrcColor >>> 24)
|
|
68
|
+
|
|
69
|
+
for (let iy = 0; iy < actualH; iy++) {
|
|
70
|
+
for (let ix = 0; ix < actualW; ix++) {
|
|
71
|
+
|
|
72
|
+
// Early exit if source pixel is already transparent
|
|
73
|
+
if (baseSrcAlpha === 0) {
|
|
74
|
+
dIdx++
|
|
75
|
+
mIdx++
|
|
76
|
+
continue
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let weight = globalAlpha
|
|
80
|
+
|
|
81
|
+
if (mask) {
|
|
82
|
+
const mVal = mask[mIdx]
|
|
83
|
+
|
|
84
|
+
if (isAlphaMask) {
|
|
85
|
+
const effectiveM = invertMask
|
|
86
|
+
? 255 - mVal
|
|
87
|
+
: mVal
|
|
88
|
+
|
|
89
|
+
// If mask is transparent, skip
|
|
90
|
+
if (effectiveM === 0) {
|
|
91
|
+
dIdx++
|
|
92
|
+
mIdx++
|
|
93
|
+
continue
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// globalAlpha is not a factor
|
|
97
|
+
if (globalAlpha === 255) {
|
|
98
|
+
weight = effectiveM
|
|
99
|
+
// mask is not a factor
|
|
100
|
+
} else if (effectiveM === 255) {
|
|
101
|
+
weight = globalAlpha
|
|
102
|
+
} else {
|
|
103
|
+
// use rounding-corrected multiplication
|
|
104
|
+
weight = (effectiveM * globalAlpha + 128) >> 8
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
const isHit = invertMask
|
|
108
|
+
? mVal === 0
|
|
109
|
+
: mVal === 1
|
|
110
|
+
|
|
111
|
+
if (!isHit) {
|
|
112
|
+
dIdx++
|
|
113
|
+
mIdx++
|
|
114
|
+
continue
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
weight = globalAlpha
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Final safety check for weight (can be 0 if globalAlpha or alphaMask rounds down)
|
|
121
|
+
if (weight === 0) {
|
|
122
|
+
dIdx++
|
|
123
|
+
mIdx++
|
|
124
|
+
continue
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Apply Weight to Source Alpha
|
|
129
|
+
let currentSrcAlpha = baseSrcAlpha
|
|
130
|
+
let currentSrcColor = baseSrcColor
|
|
131
|
+
|
|
132
|
+
if (weight < 255) {
|
|
133
|
+
if (baseSrcAlpha === 255) {
|
|
134
|
+
currentSrcAlpha = weight
|
|
135
|
+
} else {
|
|
136
|
+
currentSrcAlpha = (baseSrcAlpha * weight + 128) >> 8
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (currentSrcAlpha === 0) {
|
|
140
|
+
dIdx++
|
|
141
|
+
mIdx++
|
|
142
|
+
continue
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
currentSrcColor = ((baseSrcColor & 0x00ffffff) | (currentSrcAlpha << 24)) >>> 0 as Color32
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
dst32[dIdx] = blendFn(currentSrcColor, dst32[dIdx] as Color32)
|
|
149
|
+
|
|
150
|
+
dIdx++
|
|
151
|
+
mIdx++
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
dIdx += dStride
|
|
155
|
+
mIdx += mStride
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { type Color32, MaskType, type PixelBlendOptions } from '../_types'
|
|
2
|
+
import { sourceOverColor32 } from '../blend-modes'
|
|
3
|
+
import type { PixelData } from '../PixelData'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Blits source PixelData into a destination PixelData using 32-bit integer bitwise blending.
|
|
7
|
+
* This function bypasses standard ImageData limitations by operating directly on
|
|
8
|
+
* Uint32Array views. It supports various blend modes, binary/alpha masking, and
|
|
9
|
+
* automatic clipping of both source and destination bounds.
|
|
10
|
+
* @example
|
|
11
|
+
*
|
|
12
|
+
* const dst = new PixelData(ctx.getImageData(0,0,100,100))
|
|
13
|
+
* blendImageData32(dst, sprite, {
|
|
14
|
+
* blendFn: COLOR_32_BLEND_MODES.multiply,
|
|
15
|
+
* mask: brushMask,
|
|
16
|
+
* maskType: MaskType.ALPHA
|
|
17
|
+
* });
|
|
18
|
+
*/
|
|
19
|
+
export function blendPixelData(
|
|
20
|
+
dst: PixelData,
|
|
21
|
+
src: PixelData,
|
|
22
|
+
opts: PixelBlendOptions,
|
|
23
|
+
) {
|
|
24
|
+
const {
|
|
25
|
+
x: targetX = 0,
|
|
26
|
+
y: targetY = 0,
|
|
27
|
+
sx: sourceX = 0,
|
|
28
|
+
sy: sourceY = 0,
|
|
29
|
+
w: width = src.width,
|
|
30
|
+
h: height = src.height,
|
|
31
|
+
alpha: globalAlpha = 255,
|
|
32
|
+
blendFn = sourceOverColor32,
|
|
33
|
+
mask,
|
|
34
|
+
maskType = MaskType.ALPHA,
|
|
35
|
+
mw,
|
|
36
|
+
mx = 0,
|
|
37
|
+
my = 0,
|
|
38
|
+
invertMask = false,
|
|
39
|
+
} = opts
|
|
40
|
+
|
|
41
|
+
if (globalAlpha === 0) return
|
|
42
|
+
|
|
43
|
+
let x = targetX
|
|
44
|
+
let y = targetY
|
|
45
|
+
let sx = sourceX
|
|
46
|
+
let sy = sourceY
|
|
47
|
+
let w = width
|
|
48
|
+
let h = height
|
|
49
|
+
|
|
50
|
+
// 1. Source Clipping
|
|
51
|
+
if (sx < 0) {
|
|
52
|
+
x -= sx
|
|
53
|
+
w += sx
|
|
54
|
+
sx = 0
|
|
55
|
+
}
|
|
56
|
+
if (sy < 0) {
|
|
57
|
+
y -= sy
|
|
58
|
+
h += sy
|
|
59
|
+
sy = 0
|
|
60
|
+
}
|
|
61
|
+
w = Math.min(w, src.width - sx)
|
|
62
|
+
h = Math.min(h, src.height - sy)
|
|
63
|
+
|
|
64
|
+
// 2. Destination Clipping
|
|
65
|
+
if (x < 0) {
|
|
66
|
+
sx -= x
|
|
67
|
+
w += x
|
|
68
|
+
x = 0
|
|
69
|
+
}
|
|
70
|
+
if (y < 0) {
|
|
71
|
+
sy -= y
|
|
72
|
+
h += y
|
|
73
|
+
y = 0
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const actualW = Math.min(w, dst.width - x)
|
|
77
|
+
const actualH = Math.min(h, dst.height - y)
|
|
78
|
+
|
|
79
|
+
if (actualW <= 0 || actualH <= 0) return
|
|
80
|
+
|
|
81
|
+
const dst32 = dst.data32
|
|
82
|
+
const src32 = src.data32
|
|
83
|
+
const dw = dst.width
|
|
84
|
+
const sw = src.width
|
|
85
|
+
const mPitch = mw ?? width
|
|
86
|
+
const isAlphaMask = maskType === MaskType.ALPHA
|
|
87
|
+
|
|
88
|
+
const dx = x - targetX
|
|
89
|
+
const dy = y - targetY
|
|
90
|
+
|
|
91
|
+
let dIdx = y * dw + x
|
|
92
|
+
let sIdx = sy * sw + sx
|
|
93
|
+
let mIdx = (my + dy) * mPitch + (mx + dx)
|
|
94
|
+
|
|
95
|
+
const dStride = dw - actualW
|
|
96
|
+
const sStride = sw - actualW
|
|
97
|
+
const mStride = mPitch - actualW
|
|
98
|
+
|
|
99
|
+
for (let iy = 0; iy < actualH; iy++) {
|
|
100
|
+
for (let ix = 0; ix < actualW; ix++) {
|
|
101
|
+
const baseSrcColor = src32[sIdx] as Color32
|
|
102
|
+
const baseSrcAlpha = (baseSrcColor >>> 24)
|
|
103
|
+
|
|
104
|
+
// Early exit if source pixel is already transparent
|
|
105
|
+
if (baseSrcAlpha === 0) {
|
|
106
|
+
dIdx++
|
|
107
|
+
sIdx++
|
|
108
|
+
mIdx++
|
|
109
|
+
continue
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let weight = globalAlpha
|
|
113
|
+
|
|
114
|
+
if (mask) {
|
|
115
|
+
const mVal = mask[mIdx]
|
|
116
|
+
|
|
117
|
+
if (isAlphaMask) {
|
|
118
|
+
const effectiveM = invertMask
|
|
119
|
+
? 255 - mVal
|
|
120
|
+
: mVal
|
|
121
|
+
|
|
122
|
+
// If mask is transparent, skip
|
|
123
|
+
if (effectiveM === 0) {
|
|
124
|
+
dIdx++
|
|
125
|
+
sIdx++
|
|
126
|
+
mIdx++
|
|
127
|
+
continue
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// globalAlpha is not a factor
|
|
131
|
+
if (globalAlpha === 255) {
|
|
132
|
+
weight = effectiveM
|
|
133
|
+
// mask is not a factor
|
|
134
|
+
} else if (effectiveM === 255) {
|
|
135
|
+
weight = globalAlpha
|
|
136
|
+
} else {
|
|
137
|
+
// use rounding-corrected multiplication
|
|
138
|
+
weight = (effectiveM * globalAlpha + 128) >> 8
|
|
139
|
+
}
|
|
140
|
+
} else {
|
|
141
|
+
const isHit = invertMask
|
|
142
|
+
? mVal === 0
|
|
143
|
+
: mVal === 1
|
|
144
|
+
|
|
145
|
+
if (!isHit) {
|
|
146
|
+
dIdx++
|
|
147
|
+
sIdx++
|
|
148
|
+
mIdx++
|
|
149
|
+
continue
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
weight = globalAlpha
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Final safety check for weight (can be 0 if globalAlpha or alphaMask rounds down)
|
|
156
|
+
if (weight === 0) {
|
|
157
|
+
dIdx++
|
|
158
|
+
sIdx++
|
|
159
|
+
mIdx++
|
|
160
|
+
continue
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Apply Weight to Source Alpha
|
|
165
|
+
let currentSrcAlpha = baseSrcAlpha
|
|
166
|
+
let currentSrcColor = baseSrcColor
|
|
167
|
+
|
|
168
|
+
if (weight < 255) {
|
|
169
|
+
if (baseSrcAlpha === 255) {
|
|
170
|
+
currentSrcAlpha = weight
|
|
171
|
+
} else {
|
|
172
|
+
currentSrcAlpha = (baseSrcAlpha * weight + 128) >> 8
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (currentSrcAlpha === 0) {
|
|
176
|
+
dIdx++
|
|
177
|
+
sIdx++
|
|
178
|
+
mIdx++
|
|
179
|
+
continue
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
currentSrcColor = ((baseSrcColor & 0x00ffffff) | (currentSrcAlpha << 24)) >>> 0 as Color32
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
dst32[dIdx] = blendFn(currentSrcColor, dst32[dIdx] as Color32)
|
|
186
|
+
|
|
187
|
+
dIdx++
|
|
188
|
+
sIdx++
|
|
189
|
+
mIdx++
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
dIdx += dStride
|
|
193
|
+
sIdx += sStride
|
|
194
|
+
mIdx += mStride
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Color32, Rect } from '../_types'
|
|
2
|
+
import type { PixelData } from '../PixelData'
|
|
3
|
+
import { fillPixelData } from './fillPixelData'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Clears a region of the PixelData to transparent (0x00000000).
|
|
7
|
+
* Internally uses the optimized fillPixelData.
|
|
8
|
+
*/
|
|
9
|
+
export function clearPixelData(
|
|
10
|
+
dst: PixelData,
|
|
11
|
+
rect?: Partial<Rect>,
|
|
12
|
+
): void {
|
|
13
|
+
fillPixelData(dst, 0 as Color32, rect)
|
|
14
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Color32, Rect } from '../_types'
|
|
2
|
+
import type { PixelData } from '../PixelData'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A high-performance solid fill for PixelData.
|
|
6
|
+
*/
|
|
7
|
+
export function fillPixelData(
|
|
8
|
+
dst: PixelData,
|
|
9
|
+
color: Color32,
|
|
10
|
+
rect?: Partial<Rect>,
|
|
11
|
+
): void {
|
|
12
|
+
const {
|
|
13
|
+
x: targetX = 0,
|
|
14
|
+
y: targetY = 0,
|
|
15
|
+
w: width = dst.width,
|
|
16
|
+
h: height = dst.height,
|
|
17
|
+
} = rect || {}
|
|
18
|
+
|
|
19
|
+
let x = targetX
|
|
20
|
+
let y = targetY
|
|
21
|
+
let w = width
|
|
22
|
+
let h = height
|
|
23
|
+
|
|
24
|
+
// Destination Clipping
|
|
25
|
+
if (x < 0) {
|
|
26
|
+
w += x
|
|
27
|
+
x = 0
|
|
28
|
+
}
|
|
29
|
+
if (y < 0) {
|
|
30
|
+
h += y
|
|
31
|
+
y = 0
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const actualW = Math.min(w, dst.width - x)
|
|
35
|
+
const actualH = Math.min(h, dst.height - y)
|
|
36
|
+
|
|
37
|
+
if (actualW <= 0 || actualH <= 0) {
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const dst32 = dst.data32
|
|
42
|
+
const dw = dst.width
|
|
43
|
+
|
|
44
|
+
// Optimization: If filling the entire buffer, use the native .fill()
|
|
45
|
+
if (actualW === dw && actualH === dst.height && x === 0 && y === 0) {
|
|
46
|
+
dst32.fill(color)
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Row-by-row fill for partial rectangles
|
|
51
|
+
for (let iy = 0; iy < actualH; iy++) {
|
|
52
|
+
const start = (y + iy) * dw + x
|
|
53
|
+
const end = start + actualW
|
|
54
|
+
dst32.fill(color, start, end)
|
|
55
|
+
}
|
|
56
|
+
}
|
package/src/PixelData.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ImageDataLike } from './_types'
|
|
2
|
+
|
|
3
|
+
export class PixelData {
|
|
4
|
+
public readonly data32: Uint32Array
|
|
5
|
+
public readonly width: number
|
|
6
|
+
public readonly height: number
|
|
7
|
+
|
|
8
|
+
constructor(public readonly imageData: ImageDataLike) {
|
|
9
|
+
this.width = imageData.width
|
|
10
|
+
this.height = imageData.height
|
|
11
|
+
|
|
12
|
+
// Create the view once.
|
|
13
|
+
// Shift right by 2 is a fast bitwise division by 4.
|
|
14
|
+
this.data32 = new Uint32Array(
|
|
15
|
+
imageData.data.buffer,
|
|
16
|
+
imageData.data.byteOffset,
|
|
17
|
+
imageData.data.byteLength >> 2,
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
}
|