pixel-data-js 0.0.3 → 0.2.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.
@@ -0,0 +1,228 @@
1
+ import type { BlendColor32, Color32 } from '../_types'
2
+
3
+ export const sourceOverColor32: BlendColor32 = (src, dst) => {
4
+ const a = (src >>> 24) & 0xFF
5
+ if (a === 255) return src
6
+ if (a === 0) return dst
7
+
8
+ // Pattern: (src * a + dst * (255 - a)) >> 8
9
+ // We process RB and G separately so they don't overflow into each other
10
+ const rbMask = 0xFF00FF
11
+ const gMask = 0x00FF00
12
+
13
+ const sRB = src & rbMask
14
+ const sG = src & gMask
15
+ const dRB = dst & rbMask
16
+ const dG = dst & gMask
17
+
18
+ const invA = 255 - a
19
+
20
+ const outRB = ((sRB * a + dRB * invA) >> 8) & rbMask
21
+ const outG = ((sG * a + dG * invA) >> 8) & gMask
22
+
23
+ // Re-pack with opaque alpha (or calculate combined alpha if needed)
24
+ const outA = (a + (((dst >>> 24) & 0xFF) * invA >> 8))
25
+ return ((outA << 24) | outRB | outG) >>> 0 as Color32
26
+ }
27
+
28
+ /**
29
+ * Screen: Lightens the destination (inverse of Multiply).
30
+ * Result = 1 - ((1 - Src) * (1 - Dst))
31
+ */
32
+ export const screenColor32: BlendColor32 = (src, dst) => {
33
+ const sa = (src >>> 24) & 0xFF
34
+ if (sa === 0) return dst
35
+
36
+ const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
37
+
38
+ // 1. Core Math
39
+ const br = 255 - (((255 - (src & 0xFF)) * (255 - dr)) >> 8)
40
+ const bg = 255 - (((255 - ((src >> 8) & 0xFF)) * (255 - dg)) >> 8)
41
+ const bb = 255 - (((255 - ((src >> 16) & 0xFF)) * (255 - db)) >> 8)
42
+
43
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
44
+
45
+ // 2. Alpha Lerp inlined
46
+ const invA = 255 - sa
47
+ const r = (br * sa + dr * invA) >> 8
48
+ const g = (bg * sa + dg * invA) >> 8
49
+ const b = (bb * sa + db * invA) >> 8
50
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) >> 8
51
+
52
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
53
+ }
54
+
55
+ /**
56
+ * Linear Dodge (Additive): Simply adds the source to the destination.
57
+ * Clamps at 255.
58
+ */
59
+ export const linearDodgeColor32: BlendColor32 = (src, dst) => {
60
+ const sa = (src >>> 24) & 0xFF
61
+ if (sa === 0) return dst
62
+
63
+ const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
64
+
65
+ // 1. Core Math (Additive with clamping)
66
+ const br = Math.min(255, (src & 0xFF) + dr)
67
+ const bg = Math.min(255, ((src >> 8) & 0xFF) + dg)
68
+ const bb = Math.min(255, ((src >> 16) & 0xFF) + db)
69
+
70
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
71
+
72
+ // 2. Alpha Lerp inlined
73
+ const invA = 255 - sa
74
+ const r = (br * sa + dr * invA) >> 8
75
+ const g = (bg * sa + dg * invA) >> 8
76
+ const b = (bb * sa + db * invA) >> 8
77
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) >> 8
78
+
79
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
80
+ }
81
+
82
+ /**
83
+ * Multiply: Darkens the destination based on the source color.
84
+ * Result = (Src * Dst) / 255
85
+ */
86
+ export const multiplyColor32: BlendColor32 = (src, dst) => {
87
+ const sa = (src >>> 24) & 0xFF
88
+ if (sa === 0) return dst
89
+
90
+ const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
91
+
92
+ // 1. Core Math
93
+ const br = ((src & 0xFF) * dr) >> 8
94
+ const bg = (((src >> 8) & 0xFF) * dg) >> 8
95
+ const bb = (((src >> 16) & 0xFF) * db) >> 8
96
+
97
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
98
+
99
+ // 2. Alpha Lerp inlined
100
+ const invA = 255 - sa
101
+ const r = (br * sa + dr * invA) >> 8
102
+ const g = (bg * sa + dg * invA) >> 8
103
+ const b = (bb * sa + db * invA) >> 8
104
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) >> 8
105
+
106
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
107
+ }
108
+
109
+ /**
110
+ * Difference: Subtracts the darker color from the lighter color.
111
+ * Result = |Src - Dst|
112
+ */
113
+ export const differenceColor32: BlendColor32 = (src, dst) => {
114
+ const sa = (src >>> 24) & 0xFF
115
+ if (sa === 0) return dst
116
+
117
+ const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
118
+
119
+ // 1. Core Math
120
+ const br = Math.abs((src & 0xFF) - dr)
121
+ const bg = Math.abs(((src >> 8) & 0xFF) - dg)
122
+ const bb = Math.abs(((src >> 16) & 0xFF) - db)
123
+
124
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
125
+
126
+ // 2. Alpha Lerp inlined
127
+ const invA = 255 - sa
128
+ const r = (br * sa + dr * invA) >> 8
129
+ const g = (bg * sa + dg * invA) >> 8
130
+ const b = (bb * sa + db * invA) >> 8
131
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) >> 8
132
+
133
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
134
+ }
135
+
136
+ /**
137
+ * Hard Light: Decides Multiply vs Screen based on SOURCE brightness.
138
+ * Acts like a harsh spotlight.
139
+ */
140
+ export const hardLightColor32: BlendColor32 = (src, dst) => {
141
+ const sa = (src >>> 24) & 0xFF
142
+ if (sa === 0) return dst
143
+
144
+ const sr = src & 0xFF, sg = (src >> 8) & 0xFF, sb = (src >> 16) & 0xFF
145
+ const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
146
+
147
+ // 1. Core Math
148
+ const br = sr < 128 ? (2 * sr * dr) >> 8 : 255 - (2 * (255 - sr) * (255 - dr) >> 8)
149
+ const bg = sg < 128 ? (2 * sg * dg) >> 8 : 255 - (2 * (255 - sg) * (255 - dg) >> 8)
150
+ const bb = sb < 128 ? (2 * sb * db) >> 8 : 255 - (2 * (255 - sb) * (255 - db) >> 8)
151
+
152
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
153
+
154
+ // 2. Alpha Lerp inlined
155
+ const invA = 255 - sa
156
+ const r = (br * sa + dr * invA) >> 8
157
+ const g = (bg * sa + dg * invA) >> 8
158
+ const b = (bb * sa + db * invA) >> 8
159
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) >> 8
160
+
161
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
162
+ }
163
+
164
+ /**
165
+ * Color Burn: Darkens the destination to reflect the source color.
166
+ * Intense saturation in the darks.
167
+ */
168
+ export const colorBurnColor32: BlendColor32 = (src, dst) => {
169
+ const sa = (src >>> 24) & 0xFF
170
+ if (sa === 0) return dst
171
+
172
+ const sr = src & 0xFF, sg = (src >> 8) & 0xFF, sb = (src >> 16) & 0xFF
173
+ const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
174
+
175
+ // 1. Core Math (Avoid division by zero)
176
+ const br = dr === 255 ? 255 : Math.max(0, 255 - ((255 - dr) << 8) / (sr || 1))
177
+ const bg = dg === 255 ? 255 : Math.max(0, 255 - ((255 - dg) << 8) / (sg || 1))
178
+ const bb = db === 255 ? 255 : Math.max(0, 255 - ((255 - db) << 8) / (sb || 1))
179
+
180
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
181
+
182
+ // 2. Alpha Lerp inlined
183
+ const invA = 255 - sa
184
+ const r = (br * sa + dr * invA) >> 8
185
+ const g = (bg * sa + dg * invA) >> 8
186
+ const b = (bb * sa + db * invA) >> 8
187
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) >> 8
188
+
189
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
190
+ }
191
+ /**
192
+ * Overlay: The classic "Contrast" mode.
193
+ * Decides Multiply vs Screen based on DESTINATION brightness.
194
+ */
195
+ export const overlayColor32: BlendColor32 = (src, dst) => {
196
+ const sa = (src >>> 24) & 0xFF
197
+ if (sa === 0) return dst
198
+
199
+ const sr = src & 0xFF, sg = (src >> 8) & 0xFF, sb = (src >> 16) & 0xFF
200
+ const dr = dst & 0xFF, dg = (dst >> 8) & 0xFF, db = (dst >> 16) & 0xFF
201
+
202
+ // 1. Core Math
203
+ const br = dr < 128 ? (2 * sr * dr) >> 8 : 255 - (2 * (255 - sr) * (255 - dr) >> 8)
204
+ const bg = dg < 128 ? (2 * sg * dg) >> 8 : 255 - (2 * (255 - sg) * (255 - dg) >> 8)
205
+ const bb = db < 128 ? (2 * sb * db) >> 8 : 255 - (2 * (255 - sb) * (255 - db) >> 8)
206
+
207
+ if (sa === 255) return (0xFF000000 | (bb << 16) | (bg << 8) | br) >>> 0 as Color32
208
+
209
+ // 2. Alpha Lerp inlined
210
+ const invA = 255 - sa
211
+ const r = (br * sa + dr * invA) >> 8
212
+ const g = (bg * sa + dg * invA) >> 8
213
+ const b = (bb * sa + db * invA) >> 8
214
+ const a = (255 * sa + ((dst >>> 24) & 0xFF) * invA) >> 8
215
+
216
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
217
+ }
218
+
219
+ export const COLOR_32_BLEND_MODES = {
220
+ sourceOver: sourceOverColor32,
221
+ screen: screenColor32,
222
+ linearDodge: linearDodgeColor32,
223
+ multiply: multiplyColor32,
224
+ difference: differenceColor32,
225
+ overlay: overlayColor32,
226
+ hardLight: hardLightColor32,
227
+ colorBurn: colorBurnColor32,
228
+ }
@@ -0,0 +1,177 @@
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
+ }
@@ -0,0 +1,150 @@
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
+ }
@@ -1,4 +1,4 @@
1
- import type { Color32, ImageDataLike } from '../_types'
1
+ import type { Color32, ImageDataLike, Rect } from '../_types'
2
2
 
3
3
  export function makeImageDataColor32Adapter(imageData: ImageDataLike) {
4
4
  const data32 = new Uint32Array(imageData.data.buffer)
@@ -34,3 +34,68 @@ export function makeImageDataColor32Adapter(imageData: ImageDataLike) {
34
34
  }
35
35
  }
36
36
 
37
+ export function extractPixelData(
38
+ imageData: ImageDataLike,
39
+ rect: Rect,
40
+ ): Uint8ClampedArray
41
+
42
+ export function extractPixelData(
43
+ imageData: ImageDataLike,
44
+ x: number,
45
+ y: number,
46
+ w: number,
47
+ h: number,
48
+ ): Uint8ClampedArray
49
+ export function extractPixelData(
50
+ imageData: ImageDataLike,
51
+ _x: Rect | number,
52
+ _y?: number,
53
+ _w?: number,
54
+ _h?: number,
55
+ ): Uint8ClampedArray {
56
+ const { x, y, w, h } = typeof _x === 'object'
57
+ ? _x
58
+ : { x: _x, y: _y!, w: _w!, h: _h! }
59
+
60
+ const { width: srcW, height: srcH, data: src } = imageData
61
+ // Safety check for invalid dimensions
62
+ if (w <= 0 || h <= 0) return new Uint8ClampedArray(0)
63
+ const out = new Uint8ClampedArray(w * h * 4)
64
+
65
+ const x0 = Math.max(0, x)
66
+ const y0 = Math.max(0, y)
67
+ const x1 = Math.min(srcW, x + w)
68
+ const y1 = Math.min(srcH, y + h)
69
+
70
+ // If no intersection, return the empty
71
+ if (x1 <= x0 || y1 <= y0) return out
72
+
73
+ for (let row = 0; row < (y1 - y0); row++) {
74
+ // Where to read from the source canvas
75
+ const srcRow = y0 + row
76
+ const srcStart = (srcRow * srcW + x0) * 4
77
+ const rowLen = (x1 - x0) * 4
78
+
79
+ // Where to write into the 'out' patch
80
+ const dstRow = (y0 - y) + row
81
+ const dstCol = (x0 - x)
82
+ const dstStart = (dstRow * w + dstCol) * 4
83
+
84
+ // Perform the high-speed bulk copy
85
+ out.set(src.subarray(srcStart, srcStart + rowLen), dstStart)
86
+ }
87
+
88
+ return out
89
+ }
90
+
91
+ export function copyImageData({ data, width, height }: ImageDataLike): ImageData {
92
+ return new ImageData(data.slice(), width, height)
93
+ }
94
+
95
+ export function copyImageDataLike({ data, width, height }: ImageDataLike): ImageDataLike {
96
+ return {
97
+ data: data.slice(),
98
+ width,
99
+ height,
100
+ }
101
+ }
@@ -5,7 +5,7 @@ export function base64EncodeArrayBuffer(buffer: ArrayBufferLike): Base64EncodedU
5
5
  return btoa(binary) as Base64EncodedUInt8Array
6
6
  }
7
7
 
8
- export function base64DecodeArrayBuffer(encoded: Base64EncodedUInt8Array): Uint8ClampedArray {
8
+ export function base64DecodeArrayBuffer(encoded: Base64EncodedUInt8Array): Uint8ClampedArray<ArrayBuffer> {
9
9
  const binary = atob(encoded)
10
10
  const bytes = new Uint8ClampedArray(binary.length)
11
11
  for (let i = 0; i < binary.length; i++) {
package/src/_types.ts CHANGED
@@ -2,21 +2,43 @@
2
2
  export type RGBA = { r: number, g: number, b: number, a: number }
3
3
 
4
4
  // A 32-bit integer containing r,g,b,a data
5
- export type Color32 = number & { readonly __brandColor32: unique symbol };
5
+ export type Color32 = number & { readonly __brandColor32: unique symbol }
6
6
 
7
- // ALL values are floats from 0-1
8
- export type RGBAFloat = RGBA & { readonly __brandRGBAFloat: unique symbol }
7
+ export type BlendColor32 = (src: Color32, dst: Color32) => Color32
9
8
 
10
9
  export type ImageDataLike = {
11
10
  width: number
12
11
  height: number
13
- data: Uint8ClampedArray
12
+ data: Uint8ClampedArray<ArrayBuffer>
14
13
  }
15
14
 
16
15
  export type SerializedImageData = {
17
- width: number,
18
- height: number,
19
- data: string,
16
+ width: number
17
+ height: number
18
+ data: string
20
19
  }
21
20
 
22
21
  export type Base64EncodedUInt8Array = string & { readonly __brandBase64UInt8Array: unique symbol }
22
+
23
+ export type Rect = { x: number; y: number; w: number; h: number }
24
+
25
+ export enum MaskType {
26
+ ALPHA,
27
+ BINARY
28
+ }
29
+
30
+ interface BaseMaskData {
31
+ readonly width: number
32
+ readonly height: number
33
+ readonly data: Uint8Array
34
+ }
35
+
36
+ export interface AlphaMask extends BaseMaskData {
37
+ readonly type: MaskType.ALPHA
38
+ }
39
+
40
+ export interface BinaryMask extends BaseMaskData {
41
+ readonly type: MaskType.BINARY
42
+ }
43
+
44
+ export type AnyMask = AlphaMask | BinaryMask