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/src/_types.ts CHANGED
@@ -1,9 +1,15 @@
1
- // ALL values are 0-255 (including alpha which in CSS is 0-1)
1
+ /** ALL values are 0-255 (including alpha which in CSS is 0-1) */
2
2
  export type RGBA = { r: number, g: number, b: number, a: number }
3
3
 
4
- // A 32-bit integer containing r,g,b,a data
4
+ /** Represents a 32-bit color in 0xAABBGGRR (Little endian) */
5
5
  export type Color32 = number & { readonly __brandColor32: unique symbol }
6
6
 
7
+ /**
8
+ * A function that defines how to combine a source color with a destination color.
9
+ * @param src - The incoming color (source).
10
+ * @param dst - The existing color in the buffer (destination).
11
+ * @returns The resulting 32-bit color to be written to the buffer.
12
+ */
7
13
  export type BlendColor32 = (src: Color32, dst: Color32) => Color32
8
14
 
9
15
  export type ImageDataLike = {
@@ -20,25 +26,127 @@ export type SerializedImageData = {
20
26
 
21
27
  export type Base64EncodedUInt8Array = string & { readonly __brandBase64UInt8Array: unique symbol }
22
28
 
23
- export type Rect = { x: number; y: number; w: number; h: number }
29
+ /** Rectangle definition */
30
+ export type Rect = {
31
+ x: number
32
+ y: number
33
+ w: number
34
+ h: number
35
+ }
24
36
 
37
+ /**
38
+ * Defines how mask values should be interpreted during a draw operation.
39
+ */
25
40
  export enum MaskType {
41
+ /**
42
+ * Values are treated as alpha weights.
43
+ * 0 is skipped, values > 0 are processed.
44
+ */
26
45
  ALPHA,
46
+ /**
47
+ * Values are treated as on/off.
48
+ * 0 is fully transparent (skipped), any other value is fully opaque.
49
+ */
27
50
  BINARY
28
51
  }
29
52
 
30
- interface BaseMaskData {
31
- readonly width: number
32
- readonly height: number
33
- readonly data: Uint8Array
53
+ /** Strictly 0 or 1 */
54
+ export type BinaryMask = Uint8Array & { readonly __brand: 'Binary' }
55
+ /** Strictly 0-255 */
56
+ export type AlphaMask = Uint8Array & { readonly __brand: 'Alpha' }
57
+
58
+ export type AnyMask = BinaryMask | AlphaMask
59
+
60
+ /**
61
+ * Configuration for pixel manipulation operations.
62
+ * Designed to be used by spreading a Rect object ({x, y, w, h}) directly.
63
+ */
64
+ export interface PixelOptions {
65
+ /**
66
+ * The starting X coordinate in the destination buffer.
67
+ * @Defaults 0.
68
+ * */
69
+ x?: number
70
+ /**
71
+ * The starting Y coordinate in the destination buffer.
72
+ * @Default 0.
73
+ * */
74
+ y?: number
75
+ /**
76
+ * The width of the region to process.
77
+ * @Default Source width.
78
+ * */
79
+ w?: number
80
+ /**
81
+ * The height of the region to process.
82
+ * @Default Source height.
83
+ * */
84
+ h?: number
85
+
86
+ /**
87
+ * Overall layer opacity 0-255.
88
+ * @default 255
89
+ */
90
+ alpha?: number
91
+
92
+ /**
93
+ * Mask width.
94
+ * @default w
95
+ * */
96
+ mw?: number
97
+
98
+ /**
99
+ * X offset into the mask buffer.
100
+ * @default 0
101
+ * */
102
+ mx?: number
103
+
104
+ /**
105
+ * Y offset into the mask buffer.
106
+ * @default 0
107
+ * */
108
+ my?: number
109
+
110
+ /** An optional mask to restrict where pixels are written. */
111
+ mask?: AnyMask | null
112
+
113
+ /** The interpretation logic for the provided mask. Defaults to MaskType.Binary. */
114
+ maskType?: MaskType
115
+
116
+ /** If true the inverse of the mask will be applied */
117
+ invertMask?: boolean
34
118
  }
35
119
 
36
- export interface AlphaMask extends BaseMaskData {
37
- readonly type: MaskType.ALPHA
120
+ /**
121
+ * Configuration for blitting (copying/blending) one image into another.
122
+ */
123
+ export interface PixelBlendOptions extends PixelOptions {
124
+ /**
125
+ * The source rectangle x-coordinate
126
+ * @default 0
127
+ */
128
+ sx?: number
129
+
130
+ /**
131
+ * The source rectangle y-coordinate
132
+ * @default 0
133
+ */
134
+ sy?: number
135
+
136
+ /** The specific blending function/algorithm to use for pixel math. */
137
+ blendFn?: BlendColor32
38
138
  }
39
139
 
40
- export interface BinaryMask extends BaseMaskData {
41
- readonly type: MaskType.BINARY
140
+ /**
141
+ * Configuration for operations that require color blending.
142
+ */
143
+ export interface ColorBlendOptions extends PixelOptions {
144
+ /** The blending logic used to combine source and destination pixels. */
145
+ blendFn?: BlendColor32
42
146
  }
43
147
 
44
- export type AnyMask = AlphaMask | BinaryMask
148
+ export type ApplyMaskOptions = Omit<PixelOptions, 'mask'>
149
+
150
+ // export function invertBinaryMask(dst: BinaryMask): void
151
+ // export function invertAlphaMask(dst: AlphaMask): void
152
+
@@ -1,4 +1,4 @@
1
- import type { BlendColor32, Color32 } from '../_types'
1
+ import type { BlendColor32, Color32 } from './_types'
2
2
 
3
3
  export const sourceOverColor32: BlendColor32 = (src, dst) => {
4
4
  const a = (src >>> 24) & 0xFF
package/src/index.ts CHANGED
@@ -1,7 +1,18 @@
1
- export * from './ImageData/blend-modes'
2
- export * from './ImageData/blit'
3
- export * from './ImageData/mask'
4
- export * from './ImageData/read-write-pixels'
5
- export * from './ImageData/serialization'
6
1
  export * from './_types'
2
+ export * from './blend-modes'
7
3
  export * from './color'
4
+
5
+ export * from './ImageData/copyImageData'
6
+ export * from './ImageData/extractImageData'
7
+ export * from './ImageData/serialization'
8
+ export * from './ImageData/writeImageData'
9
+
10
+ export * from './Mask/copyMask'
11
+ export * from './Mask/invertMask'
12
+ export * from './Mask/mergeMasks'
13
+
14
+ export * from './PixelData/applyMaskToPixelData'
15
+ export * from './PixelData/blendColorPixelData'
16
+ export * from './PixelData/blendPixelData'
17
+ export * from './PixelData/clearPixelData'
18
+ export * from './PixelData/fillPixelData'
@@ -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
- }