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.
@@ -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
- }
@@ -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
- }