pixel-data-js 0.2.0 → 0.3.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 +216 -245
- package/dist/index.dev.cjs.map +1 -1
- package/dist/index.dev.js +214 -240
- package/dist/index.dev.js.map +1 -1
- package/dist/index.prod.cjs +216 -245
- package/dist/index.prod.cjs.map +1 -1
- package/dist/index.prod.d.ts +145 -147
- package/dist/index.prod.js +214 -240
- 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 +5 -5
- package/src/ImageData/blit.ts +0 -177
- package/src/ImageData/mask.ts +0 -150
package/src/ImageData/blit.ts
DELETED
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import { type AnyMask, type BlendColor32, type Color32, type ImageDataLike, MaskType } from '../_types'
|
|
2
|
-
import { sourceOverColor32 } from './blend-modes'
|
|
3
|
-
|
|
4
|
-
export type BlendImageDataOptions = {
|
|
5
|
-
/**
|
|
6
|
-
* The x-coordinate in the destination image where the blend begins.
|
|
7
|
-
* @default 0
|
|
8
|
-
*/
|
|
9
|
-
dx?: number
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* The y-coordinate in the destination image where the blend begins.
|
|
13
|
-
* @default 0
|
|
14
|
-
*/
|
|
15
|
-
dy?: number
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* The x-coordinate of the top-left corner of the sub-rectangle
|
|
19
|
-
* of the source image to extract.
|
|
20
|
-
* @default 0
|
|
21
|
-
*/
|
|
22
|
-
sx?: number
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* The y-coordinate of the top-left corner of the sub-rectangle
|
|
26
|
-
* of the source image to extract.
|
|
27
|
-
* @default 0
|
|
28
|
-
*/
|
|
29
|
-
sy?: number
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* The width of the sub-rectangle of the source image to extract.
|
|
33
|
-
* Defaults to the full remaining width of the source.
|
|
34
|
-
*/
|
|
35
|
-
sw?: number
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* The height of the sub-rectangle of the source image to extract.
|
|
39
|
-
* Defaults to the full remaining height of the source.
|
|
40
|
-
*/
|
|
41
|
-
sh?: number
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Overall layer opacity, typically ranging from 0.0 (transparent) to 1.0 (opaque).
|
|
45
|
-
* @default 1.0
|
|
46
|
-
*/
|
|
47
|
-
opacity?: number
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Same as opacity but is 0-255 and faster when processing. If Present opacity is ignored.
|
|
51
|
-
* @default undefined
|
|
52
|
-
*/
|
|
53
|
-
alpha?: number
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* An optional alpha mask buffer.
|
|
57
|
-
* The values in this array (0-255) determine the intensity of the blend
|
|
58
|
-
* at each corresponding pixel.
|
|
59
|
-
*/
|
|
60
|
-
mask?: AnyMask | null
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* The specific blending function/algorithm to use for pixel math
|
|
64
|
-
* (e.g., Multiply, Screen, Overlay).
|
|
65
|
-
*/
|
|
66
|
-
blendFn?: BlendColor32
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Blits source ImageData into a destination ImageData using 32-bit integer bitwise blending.
|
|
71
|
-
* This function bypasses standard Canvas API limitations by operating directly on
|
|
72
|
-
* Uint32Array views. It supports various blend modes, binary/alpha masking, and
|
|
73
|
-
* automatic clipping of both source and destination bounds.
|
|
74
|
-
* @example
|
|
75
|
-
* blendImageData32(ctx.getImageData(0,0,100,100), sprite, {
|
|
76
|
-
* blendFn: COLOR_32_BLEND_MODES.multiply,
|
|
77
|
-
* mask: brushMask,
|
|
78
|
-
* maskMode: MaskMode.ALPHA
|
|
79
|
-
* });
|
|
80
|
-
*/
|
|
81
|
-
export function blendImageData(
|
|
82
|
-
dst: ImageDataLike,
|
|
83
|
-
src: ImageDataLike,
|
|
84
|
-
opts: BlendImageDataOptions,
|
|
85
|
-
) {
|
|
86
|
-
let {
|
|
87
|
-
dx = 0,
|
|
88
|
-
dy = 0,
|
|
89
|
-
sx = 0,
|
|
90
|
-
sy = 0,
|
|
91
|
-
sw = src.width,
|
|
92
|
-
sh = src.height,
|
|
93
|
-
opacity = 1,
|
|
94
|
-
alpha,
|
|
95
|
-
blendFn = sourceOverColor32,
|
|
96
|
-
mask,
|
|
97
|
-
} = opts
|
|
98
|
-
|
|
99
|
-
// 1. Clip Source Area
|
|
100
|
-
if (sx < 0) {
|
|
101
|
-
dx -= sx
|
|
102
|
-
sw += sx
|
|
103
|
-
sx = 0
|
|
104
|
-
}
|
|
105
|
-
if (sy < 0) {
|
|
106
|
-
dy -= sy
|
|
107
|
-
sh += sy
|
|
108
|
-
sy = 0
|
|
109
|
-
}
|
|
110
|
-
sw = Math.min(sw, src.width - sx)
|
|
111
|
-
sh = Math.min(sh, src.height - sy)
|
|
112
|
-
|
|
113
|
-
// 2. Clip Destination Area
|
|
114
|
-
if (dx < 0) {
|
|
115
|
-
sx -= dx
|
|
116
|
-
sw += dx
|
|
117
|
-
dx = 0
|
|
118
|
-
}
|
|
119
|
-
if (dy < 0) {
|
|
120
|
-
sy -= dy
|
|
121
|
-
sh += dy
|
|
122
|
-
dy = 0
|
|
123
|
-
}
|
|
124
|
-
const actualW = Math.min(sw, dst.width - dx)
|
|
125
|
-
const actualH = Math.min(sh, dst.height - dy)
|
|
126
|
-
|
|
127
|
-
if (actualW <= 0 || actualH <= 0) return
|
|
128
|
-
|
|
129
|
-
// 32-bit views of the same memory
|
|
130
|
-
const dst32 = new Uint32Array(dst.data.buffer)
|
|
131
|
-
const src32 = new Uint32Array(src.data.buffer)
|
|
132
|
-
|
|
133
|
-
const dw = dst.width
|
|
134
|
-
const sw_orig = src.width
|
|
135
|
-
|
|
136
|
-
const gAlpha = alpha !== undefined
|
|
137
|
-
? (alpha | 0)
|
|
138
|
-
: Math.round(opacity * 255)
|
|
139
|
-
|
|
140
|
-
const maskIsAlpha = mask?.type === MaskType.ALPHA
|
|
141
|
-
|
|
142
|
-
for (let iy = 0; iy < actualH; iy++) {
|
|
143
|
-
const dRow = (iy + dy) * dw
|
|
144
|
-
const sRow = (iy + sy) * sw_orig
|
|
145
|
-
|
|
146
|
-
for (let ix = 0; ix < actualW; ix++) {
|
|
147
|
-
const di = dRow + (ix + dx)
|
|
148
|
-
const si = sRow + (ix + sx)
|
|
149
|
-
|
|
150
|
-
let s = src32[si] as Color32
|
|
151
|
-
let sa = (s >>> 24) & 0xFF
|
|
152
|
-
|
|
153
|
-
// skip fully transparent pixel
|
|
154
|
-
if (sa === 0) continue
|
|
155
|
-
|
|
156
|
-
let activeWeight = gAlpha
|
|
157
|
-
|
|
158
|
-
if (mask) {
|
|
159
|
-
const m = mask.data[si]
|
|
160
|
-
if (m === 0) continue
|
|
161
|
-
activeWeight = maskIsAlpha ? (m * activeWeight + 128) >> 8 : activeWeight
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (activeWeight < 255) {
|
|
165
|
-
sa = (sa * activeWeight + 128) >> 8
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// If combined alpha is 0 after masking/opacity, skip the blend math
|
|
169
|
-
if (sa === 0) continue
|
|
170
|
-
|
|
171
|
-
// Re-pack source with final calculated alpha
|
|
172
|
-
s = ((s & 0x00FFFFFF) | (sa << 24)) >>> 0 as Color32
|
|
173
|
-
|
|
174
|
-
dst32[di] = blendFn(s, dst32[di] as Color32)
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
package/src/ImageData/mask.ts
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import type { AlphaMask, BinaryMask, ImageDataLike } from '../_types'
|
|
2
|
-
|
|
3
|
-
export type ApplyMaskOptions = {
|
|
4
|
-
/**
|
|
5
|
-
* The x-coordinate in the destination image where the mask begins.
|
|
6
|
-
* @default 0
|
|
7
|
-
*/
|
|
8
|
-
dx?: number
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* The y-coordinate in the destination image where the mask begins.
|
|
12
|
-
* @default 0
|
|
13
|
-
*/
|
|
14
|
-
dy?: number
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* The x-coordinate of the top-left corner of the sub-rectangle
|
|
18
|
-
* of the source image to extract.
|
|
19
|
-
* @default 0
|
|
20
|
-
*/
|
|
21
|
-
sx?: number
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* The y-coordinate of the top-left corner of the sub-rectangle
|
|
25
|
-
* of the source image to extract.
|
|
26
|
-
* @default 0
|
|
27
|
-
*/
|
|
28
|
-
sy?: number
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* The width of the sub-rectangle of the source image to extract.
|
|
32
|
-
* Defaults to the full remaining width of the source.
|
|
33
|
-
*/
|
|
34
|
-
sw?: number
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* The height of the sub-rectangle of the source image to extract.
|
|
38
|
-
* Defaults to the full remaining height of the source.
|
|
39
|
-
*/
|
|
40
|
-
sh?: number;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Applies a binary (on/off) mask to an RGBA buffer.
|
|
45
|
-
* If mask value is 0, pixel becomes transparent.
|
|
46
|
-
*/
|
|
47
|
-
export function applyBinaryMask(
|
|
48
|
-
dst: ImageDataLike,
|
|
49
|
-
mask: BinaryMask,
|
|
50
|
-
opts: ApplyMaskOptions = {},
|
|
51
|
-
) {
|
|
52
|
-
const { width: maskWidth, height: maskHeight } = mask
|
|
53
|
-
|
|
54
|
-
const { dx = 0, dy = 0, sx = 0, sy = 0, sw = maskWidth, sh = maskHeight } = opts
|
|
55
|
-
|
|
56
|
-
// 1. Calculate intersection boundaries
|
|
57
|
-
const x0 = Math.max(0, dx, dx + (0 - sx))
|
|
58
|
-
const y0 = Math.max(0, dy, dy + (0 - sy))
|
|
59
|
-
const x1 = Math.min(dst.width, dx + sw, dx + (maskWidth - sx))
|
|
60
|
-
const y1 = Math.min(dst.height, dy + sh, dy + (maskHeight - sy))
|
|
61
|
-
|
|
62
|
-
if (x1 <= x0 || y1 <= y0) return
|
|
63
|
-
|
|
64
|
-
const { data: dstData, width: dstW } = dst
|
|
65
|
-
|
|
66
|
-
for (let y = y0; y < y1; y++) {
|
|
67
|
-
const maskY = y - dy + sy
|
|
68
|
-
const dstRowOffset = y * dstW * 4
|
|
69
|
-
const maskRowOffset = maskY * maskWidth
|
|
70
|
-
|
|
71
|
-
for (let x = x0; x < x1; x++) {
|
|
72
|
-
const maskX = x - dx + sx
|
|
73
|
-
const mIdx = maskRowOffset + maskX
|
|
74
|
-
|
|
75
|
-
// Binary check: If mask is 0, kill the alpha
|
|
76
|
-
if (mask.data[mIdx] === 0) {
|
|
77
|
-
const aIdx = dstRowOffset + (x * 4) + 3
|
|
78
|
-
dstData[aIdx] = 0
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return dst
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Applies a smooth alpha mask to an RGBA buffer.
|
|
88
|
-
* Multiplies existing Alpha by (maskValue / 255).
|
|
89
|
-
*/
|
|
90
|
-
export function applyAlphaMask(
|
|
91
|
-
dst: ImageData,
|
|
92
|
-
mask: AlphaMask,
|
|
93
|
-
opts: ApplyMaskOptions = {},
|
|
94
|
-
): void {
|
|
95
|
-
let { dx = 0, dy = 0, sx = 0, sy = 0, sw = mask.width, sh = mask.height } = opts
|
|
96
|
-
|
|
97
|
-
// 1. Clipping Logic
|
|
98
|
-
if (dx < 0) {
|
|
99
|
-
sx -= dx
|
|
100
|
-
sw += dx
|
|
101
|
-
dx = 0
|
|
102
|
-
}
|
|
103
|
-
if (dy < 0) {
|
|
104
|
-
sy -= dy
|
|
105
|
-
sh += dy
|
|
106
|
-
dy = 0
|
|
107
|
-
}
|
|
108
|
-
if (sx < 0) {
|
|
109
|
-
dx -= sx
|
|
110
|
-
sw += sx
|
|
111
|
-
sx = 0
|
|
112
|
-
}
|
|
113
|
-
if (sy < 0) {
|
|
114
|
-
dy -= sy
|
|
115
|
-
sh += sy
|
|
116
|
-
sy = 0
|
|
117
|
-
}
|
|
118
|
-
const actualW = Math.min(sw, dst.width - dx, mask.width - sx)
|
|
119
|
-
const actualH = Math.min(sh, dst.height - dy, mask.height - sy)
|
|
120
|
-
|
|
121
|
-
if (actualW <= 0 || actualH <= 0) return
|
|
122
|
-
|
|
123
|
-
const dData = dst.data
|
|
124
|
-
const mData = mask.data
|
|
125
|
-
const dW = dst.width
|
|
126
|
-
const mW = mask.width
|
|
127
|
-
|
|
128
|
-
for (let y = 0; y < actualH; y++) {
|
|
129
|
-
const dOffset = ((dy + y) * dW + dx) << 2
|
|
130
|
-
const mOffset = (sy + y) * mW + sx
|
|
131
|
-
|
|
132
|
-
for (let x = 0; x < actualW; x++) {
|
|
133
|
-
const mVal = mData[mOffset + x]
|
|
134
|
-
|
|
135
|
-
if (mVal === 255) continue
|
|
136
|
-
|
|
137
|
-
const aIdx = dOffset + (x << 2) + 3
|
|
138
|
-
|
|
139
|
-
// --- BRANCH: Zero Alpha ---
|
|
140
|
-
if (mVal === 0) {
|
|
141
|
-
dData[aIdx] = 0
|
|
142
|
-
continue
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// To get 101 from 200 * 128, we use the bias (a * m + 257) >> 8
|
|
146
|
-
// 25600 + 257 = 25857. 25857 >> 8 = 101.
|
|
147
|
-
dData[aIdx] = (dData[aIdx] * mVal + 257) >> 8
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|