pixel-data-js 0.11.1 → 0.12.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pixel-data-js",
3
3
  "type": "module",
4
- "version": "0.11.1",
4
+ "version": "0.12.0",
5
5
  "packageManager": "pnpm@10.30.0",
6
6
  "description": "JS Pixel and ImageData operations",
7
7
  "author": {
@@ -0,0 +1,89 @@
1
+ import { MaskType } from '../_types'
2
+
3
+ /**
4
+ * Writes image data from a source to a target with support for clipping and alpha masking.
5
+ *
6
+ * @param target - The destination ImageData to write to.
7
+ * @param source - The source ImageData to read from.
8
+ * @param x - The x-coordinate in the target where drawing starts.
9
+ * @param y - The y-coordinate in the target where drawing starts.
10
+ * @param sx - The x-coordinate in the source to start copying from.
11
+ * @param sy - The y-coordinate in the source to start copying from.
12
+ * @param sw - The width of the rectangle to copy.
13
+ * @param sh - The height of the rectangle to copy.
14
+ * @param mask - An optional Uint8Array mask (0-255). 0 is transparent, 255 is opaque.
15
+ * @param maskType - type of mask
16
+ */
17
+ export function writeImageData(
18
+ target: ImageData,
19
+ source: ImageData,
20
+ x: number,
21
+ y: number,
22
+ sx: number = 0,
23
+ sy: number = 0,
24
+ sw: number = source.width,
25
+ sh: number = source.height,
26
+ mask: Uint8Array | null = null,
27
+ maskType: MaskType = MaskType.BINARY,
28
+ ): void {
29
+ const dstW = target.width
30
+ const dstH = target.height
31
+ const dstData = target.data
32
+ const srcW = source.width
33
+ const srcData = source.data
34
+
35
+ const x0 = Math.max(0, x)
36
+ const y0 = Math.max(0, y)
37
+ const x1 = Math.min(dstW, x + sw)
38
+ const y1 = Math.min(dstH, y + sh)
39
+
40
+ if (x1 <= x0 || y1 <= y0) {
41
+ return
42
+ }
43
+
44
+ const useMask = !!mask
45
+ const rowCount = y1 - y0
46
+ const rowLenPixels = x1 - x0
47
+
48
+ for (let row = 0; row < rowCount; row++) {
49
+ const dstY = y0 + row
50
+ const srcY = sy + (dstY - y)
51
+ const srcXBase = sx + (x0 - x)
52
+
53
+ const dstStart = (dstY * dstW + x0) * 4
54
+ const srcStart = (srcY * srcW + srcXBase) * 4
55
+
56
+ if (useMask && mask) {
57
+ for (let ix = 0; ix < rowLenPixels; ix++) {
58
+ const mi = srcY * srcW + (srcXBase + ix)
59
+ const alpha = mask[mi]
60
+
61
+ if (alpha === 0) {
62
+ continue
63
+ }
64
+
65
+ const di = dstStart + (ix * 4)
66
+ const si = srcStart + (ix * 4)
67
+
68
+ if (maskType === MaskType.BINARY || alpha === 255) {
69
+ dstData[di] = srcData[si]
70
+ dstData[di + 1] = srcData[si + 1]
71
+ dstData[di + 2] = srcData[si + 2]
72
+ dstData[di + 3] = srcData[si + 3]
73
+ } else {
74
+ const a = alpha / 255
75
+ const invA = 1 - a
76
+
77
+ dstData[di] = srcData[si] * a + dstData[di] * invA
78
+ dstData[di + 1] = srcData[si + 1] * a + dstData[di + 1] * invA
79
+ dstData[di + 2] = srcData[si + 2] * a + dstData[di + 2] * invA
80
+ dstData[di + 3] = srcData[si + 3] * a + dstData[di + 3] * invA
81
+ }
82
+ }
83
+ } else {
84
+ const byteLen = rowLenPixels * 4
85
+ const sub = srcData.subarray(srcStart, srcStart + byteLen)
86
+ dstData.set(sub, dstStart)
87
+ }
88
+ }
89
+ }
@@ -1,83 +1,113 @@
1
+ import type { Color32 } from '../_types'
2
+
1
3
  /**
2
- * Compressed data format for image processing optimization.
3
- * Representing an image as a grid of palette indices rather than raw RGBA values
4
- * significantly reduces memory overhead and optimizes pattern matching logic.
4
+ * Represents an image using a palette-based indexing system.
5
+ * Instead of storing 4 bytes (RGBA) per pixel, this class stores a single index
6
+ * into a color palette. This format is optimized for memory efficiency and
7
+ * high-speed pattern matching or recoloring operations.
5
8
  */
6
- export type IndexedImage = {
9
+ export class IndexedImage {
7
10
  /** The width of the image in pixels. */
8
- width: number;
11
+ public readonly width: number
9
12
  /** The height of the image in pixels. */
10
- height: number;
13
+ public readonly height: number
14
+ /** Flat array of palette indices. Index = x + (y * width). */
15
+ public readonly data: Int32Array
16
+ /** The palette of unique 32-bit colors (ABGR/RGBA packed) found in the image. */
17
+ public readonly palette: Uint32Array
18
+ /** The specific index in the palette reserved for fully transparent pixels. */
19
+ public readonly transparentPalletIndex: number
20
+
11
21
  /**
12
- * A flat array of indices where each value points to a color in the palette.
13
- * Accessible via the formula: `index = x + (y * width)`.
22
+ * @param width - Image width.
23
+ * @param height - Image height.
24
+ * @param data - The indexed pixel data.
25
+ * @param palette - The array of packed colors.
26
+ * @param transparentPalletIndex - The index representing alpha 0.
14
27
  */
15
- data: Int32Array;
28
+ constructor(
29
+ width: number,
30
+ height: number,
31
+ data: Int32Array,
32
+ palette: Uint32Array,
33
+ transparentPalletIndex: number,
34
+ ) {
35
+ this.width = width
36
+ this.height = height
37
+ this.data = data
38
+ this.palette = palette
39
+ this.transparentPalletIndex = transparentPalletIndex
40
+ }
41
+
16
42
  /**
17
- * A palette of packed 32-bit colors (ABGR).
43
+ * Creates an IndexedImage from standard browser ImageData.
44
+ * @param imageData - The source ImageData to convert.
45
+ * @returns A new IndexedImage instance.
18
46
  */
19
- palette: Uint32Array;
47
+ static fromImageData(imageData: ImageData): IndexedImage {
48
+ return IndexedImage.fromRaw(imageData.data, imageData.width, imageData.height)
49
+ }
50
+
20
51
  /**
21
- * The specific index in the palette that represents a fully transparent pixel.
52
+ * Creates an IndexedImage from a raw byte buffer and dimensions.
53
+ * Any pixel with an alpha channel of 0 is normalized to the transparent palette index.
54
+ * @param data - Raw RGBA byte data.
55
+ * @param width - Image width.
56
+ * @param height - Image height.
57
+ * @returns A new IndexedImage instance.
22
58
  */
23
- transparentPalletIndex: number;
24
- };
25
-
26
- /**
27
- * Converts standard ImageData into an IndexedImage format.
28
- */
29
- export function makeIndexedImage(imageData: ImageData): IndexedImage;
30
- export function makeIndexedImage(
31
- data: Uint8ClampedArray,
32
- width: number,
33
- height: number,
34
- ): IndexedImage;
35
- export function makeIndexedImage(
36
- imageOrData: ImageData | Uint8ClampedArray,
37
- width?: number,
38
- height?: number,
39
- ): IndexedImage {
40
- const isImageData = 'width' in imageOrData
41
- const actualWidth = isImageData ? imageOrData.width : (width as number)
42
- const actualHeight = isImageData ? imageOrData.height : (height as number)
43
- const buffer = isImageData ? imageOrData.data.buffer : imageOrData.buffer
44
-
45
- // Use a 32-bit view to read pixels as packed integers (unsigned)
46
- const rawData = new Uint32Array(buffer)
47
- const indexedData = new Int32Array(rawData.length)
48
- const colorMap = new Map<number, number>()
49
-
50
- const transparentColor = 0
51
- const transparentPalletIndex = 0
59
+ static fromRaw(
60
+ data: Uint8ClampedArray,
61
+ width: number,
62
+ height: number,
63
+ ): IndexedImage {
64
+ const buffer = data.buffer
65
+ const rawData = new Uint32Array(buffer)
66
+ const indexedData = new Int32Array(rawData.length)
67
+ const colorMap = new Map<number, number>()
68
+ const transparentColor = 0
69
+ const transparentPalletIndex = 0
52
70
 
53
- // Initialize palette with normalized transparent color
54
- colorMap.set(transparentColor, transparentPalletIndex)
71
+ // Initialize palette with normalized transparent color
72
+ colorMap.set(transparentColor, transparentPalletIndex)
55
73
 
56
- for (let i = 0; i < rawData.length; i++) {
57
- const pixel = rawData[i]!
74
+ for (let i = 0; i < rawData.length; i++) {
75
+ const pixel = rawData[i] as number
76
+ const alpha = (pixel >>> 24) & 0xFF
77
+ const isTransparent = alpha === 0
78
+ const colorKey = isTransparent ? transparentColor : (pixel >>> 0)
58
79
 
59
- // Check if the pixel is fully transparent (Alpha channel is highest byte)
60
- const alpha = (pixel >>> 24) & 0xFF
61
- const isTransparent = alpha === 0
62
- const colorKey = isTransparent ? transparentColor : (pixel >>> 0)
80
+ let id = colorMap.get(colorKey)
63
81
 
64
- let id = colorMap.get(colorKey)
82
+ if (id === undefined) {
83
+ id = colorMap.size
84
+ colorMap.set(colorKey, id)
85
+ }
65
86
 
66
- if (id === undefined) {
67
- id = colorMap.size
68
- colorMap.set(colorKey, id)
87
+ indexedData[i] = id
69
88
  }
70
89
 
71
- indexedData[i] = id
90
+ const palette = Uint32Array.from(colorMap.keys())
91
+
92
+ return new IndexedImage(
93
+ width,
94
+ height,
95
+ indexedData,
96
+ palette,
97
+ transparentPalletIndex,
98
+ )
72
99
  }
73
100
 
74
- const palette = Uint32Array.from(colorMap.keys())
101
+ /**
102
+ * Retrieves the 32-bit packed color value at the given coordinates.
103
+ * @param x - X coordinate.
104
+ * @param y - Y coordinate.
105
+ * @returns The packed color from the palette.
106
+ */
107
+ public getColorAt(x: number, y: number): Color32 {
108
+ const index = x + y * this.width
109
+ const paletteIndex = this.data[index]
75
110
 
76
- return {
77
- width: actualWidth,
78
- height: actualHeight,
79
- data: indexedData,
80
- transparentPalletIndex,
81
- palette,
111
+ return this.palette[paletteIndex] as Color32
82
112
  }
83
113
  }
@@ -0,0 +1,19 @@
1
+ import type { IndexedImage } from './IndexedImage'
2
+
3
+ /**
4
+ * Converts an IndexedImage back into standard ImageData.
5
+ */
6
+ export function indexedImageToImageData(indexedImage: IndexedImage): ImageData {
7
+ const { width, height, data, palette } = indexedImage
8
+ const result = new ImageData(width, height)
9
+ const data32 = new Uint32Array(result.data.buffer)
10
+
11
+ for (let i = 0; i < data.length; i++) {
12
+ const paletteIndex = data[i]
13
+ const color = palette[paletteIndex]
14
+
15
+ data32[i] = color
16
+ }
17
+
18
+ return result
19
+ }
@@ -2,7 +2,7 @@
2
2
  * Resamples an IndexedImage by a specific factor using nearest neighbor
3
3
  * Factor > 1 upscales, Factor < 1 downscales.
4
4
  */
5
- import type { IndexedImage } from '../index'
5
+ import { IndexedImage } from '../index'
6
6
  import { resample32 } from '../Internal/resample32'
7
7
 
8
8
  /**
@@ -21,11 +21,11 @@ export function resampleIndexedImage(
21
21
  factor,
22
22
  )
23
23
 
24
- return {
24
+ return new IndexedImage(
25
25
  width,
26
26
  height,
27
27
  data,
28
- palette: source.palette,
29
- transparentPalletIndex: source.transparentPalletIndex,
30
- }
28
+ source.palette,
29
+ source.transparentPalletIndex,
30
+ )
31
31
  }
@@ -4,8 +4,14 @@ const resample32Scratch = {
4
4
  height: 0,
5
5
  }
6
6
 
7
+ /**
8
+ * @internal
9
+ */
7
10
  type Resample32Result = { data: Int32Array; width: number; height: number }
8
11
 
12
+ /**
13
+ * @internal
14
+ */
9
15
  export function resample32(
10
16
  srcData32: Uint32Array | Int32Array,
11
17
  srcW: number,
package/src/index.ts CHANGED
@@ -33,11 +33,13 @@ export * from './ImageData/resampleImageData'
33
33
  export * from './ImageData/resizeImageData'
34
34
  export * from './ImageData/serialization'
35
35
  export * from './ImageData/writeImageDataPixels'
36
+ export * from './ImageData/writeImageData'
36
37
 
37
38
  export * from './IndexedImage/IndexedImage'
38
39
  export * from './IndexedImage/getIndexedImageColorCounts'
39
40
  export * from './IndexedImage/indexedImageToAverageColor'
40
41
  export * from './IndexedImage/resampleIndexedImage'
42
+ export * from './IndexedImage/indexedImageToImageData'
41
43
 
42
44
  export * from './Input/fileInputChangeToImageData'
43
45
  export * from './Input/fileToImageData'