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/dist/index.dev.cjs +157 -36
- package/dist/index.dev.cjs.map +1 -1
- package/dist/index.dev.js +154 -35
- package/dist/index.dev.js.map +1 -1
- package/dist/index.prod.cjs +157 -36
- package/dist/index.prod.cjs.map +1 -1
- package/dist/index.prod.d.ts +60 -20
- package/dist/index.prod.js +154 -35
- package/dist/index.prod.js.map +1 -1
- package/package.json +1 -1
- package/src/ImageData/writeImageData.ts +89 -0
- package/src/IndexedImage/IndexedImage.ts +91 -61
- package/src/IndexedImage/indexedImageToImageData.ts +19 -0
- package/src/IndexedImage/resampleIndexedImage.ts +5 -5
- package/src/Internal/resample32.ts +6 -0
- package/src/index.ts +2 -0
package/package.json
CHANGED
|
@@ -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
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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
|
|
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
|
-
*
|
|
13
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
47
|
+
static fromImageData(imageData: ImageData): IndexedImage {
|
|
48
|
+
return IndexedImage.fromRaw(imageData.data, imageData.width, imageData.height)
|
|
49
|
+
}
|
|
50
|
+
|
|
20
51
|
/**
|
|
21
|
-
*
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
54
|
-
|
|
71
|
+
// Initialize palette with normalized transparent color
|
|
72
|
+
colorMap.set(transparentColor, transparentPalletIndex)
|
|
55
73
|
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
82
|
+
if (id === undefined) {
|
|
83
|
+
id = colorMap.size
|
|
84
|
+
colorMap.set(colorKey, id)
|
|
85
|
+
}
|
|
65
86
|
|
|
66
|
-
|
|
67
|
-
id = colorMap.size
|
|
68
|
-
colorMap.set(colorKey, id)
|
|
87
|
+
indexedData[i] = id
|
|
69
88
|
}
|
|
70
89
|
|
|
71
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
29
|
-
|
|
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'
|