pixel-data-js 0.10.0 → 0.11.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.10.0",
4
+ "version": "0.11.0",
5
5
  "packageManager": "pnpm@10.30.0",
6
6
  "description": "JS Pixel and ImageData operations",
7
7
  "author": {
@@ -2,34 +2,16 @@
2
2
  * Resamples ImageData by a specific factor.
3
3
  * Factor > 1 upscales, Factor < 1 downscales.
4
4
  */
5
- export function resampleImageData(
6
- source: ImageData,
7
- factor: number,
8
- ): ImageData {
9
- const srcW = source.width
10
- const srcH = source.height
11
- const dstW = Math.max(1, (srcW * factor) | 0)
12
- const dstH = Math.max(1, (srcH * factor) | 0)
13
- const srcData = source.data
14
- const dstData = new Uint8ClampedArray(dstW * dstH * 4)
5
+ import { resample32 } from '../Internal/resample32'
15
6
 
16
- for (let y = 0; y < dstH; y++) {
17
- const srcY = (y / factor) | 0
18
- const srcRowOffset = srcY * srcW * 4
19
- const dstRowOffset = y * dstW * 4
20
-
21
- for (let x = 0; x < dstW; x++) {
22
- const srcX = (x / factor) | 0
23
- const srcIdx = srcRowOffset + srcX * 4
24
- const dstIdx = dstRowOffset + x * 4
25
-
26
- // Copy RGBA channels
27
- dstData[dstIdx] = srcData[srcIdx]!
28
- dstData[dstIdx + 1] = srcData[srcIdx + 1]!
29
- dstData[dstIdx + 2] = srcData[srcIdx + 2]!
30
- dstData[dstIdx + 3] = srcData[srcIdx + 3]!
31
- }
32
- }
7
+ /**
8
+ * Resamples ImageData by a specific factor.
9
+ * Factor > 1 upscales, Factor < 1 downscales.
10
+ */
11
+ export function resampleImageData(source: ImageData, factor: number): ImageData {
12
+ const src32 = new Uint32Array(source.data.buffer)
13
+ const { data, width, height } = resample32(src32, source.width, source.height, factor)
33
14
 
34
- return new ImageData(dstData, dstW, dstH)
15
+ const uint8ClampedArray = new Uint8ClampedArray(data.buffer) as Uint8ClampedArray<ArrayBuffer>
16
+ return new ImageData(uint8ClampedArray, width, height)
35
17
  }
@@ -26,15 +26,24 @@ export type IndexedImage = {
26
26
  /**
27
27
  * Converts standard ImageData into an IndexedImage format.
28
28
  */
29
- /**
30
- * Converts standard ImageData into an IndexedImage format.
31
- */
32
- export function makeIndexedImage(imageData: ImageData): IndexedImage {
33
- const width = imageData.width
34
- const height = imageData.height
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
35
44
 
36
- // Use a 32-bit view to read pixels as packed integers (usually ABGR or RGBA)
37
- const rawData = new Uint32Array(imageData.data.buffer)
45
+ // Use a 32-bit view to read pixels as packed integers (unsigned)
46
+ const rawData = new Uint32Array(buffer)
38
47
  const indexedData = new Int32Array(rawData.length)
39
48
  const colorMap = new Map<number, number>()
40
49
 
@@ -47,15 +56,14 @@ export function makeIndexedImage(imageData: ImageData): IndexedImage {
47
56
  for (let i = 0; i < rawData.length; i++) {
48
57
  const pixel = rawData[i]!
49
58
 
50
- // Check if the pixel is fully transparent
59
+ // Check if the pixel is fully transparent (Alpha channel is highest byte)
51
60
  const alpha = (pixel >>> 24) & 0xFF
52
61
  const isTransparent = alpha === 0
53
- const colorKey = isTransparent ? transparentColor : pixel
62
+ const colorKey = isTransparent ? transparentColor : (pixel >>> 0)
54
63
 
55
64
  let id = colorMap.get(colorKey)
56
65
 
57
66
  if (id === undefined) {
58
- // Use the current length as the next ID to ensure sequence
59
67
  id = colorMap.size
60
68
  colorMap.set(colorKey, id)
61
69
  }
@@ -63,10 +71,11 @@ export function makeIndexedImage(imageData: ImageData): IndexedImage {
63
71
  indexedData[i] = id
64
72
  }
65
73
 
66
- const palette = new Uint32Array(colorMap.keys())
74
+ const palette = Uint32Array.from(colorMap.keys())
75
+
67
76
  return {
68
- width,
69
- height,
77
+ width: actualWidth,
78
+ height: actualHeight,
70
79
  data: indexedData,
71
80
  transparentPalletIndex,
72
81
  palette,
@@ -1,4 +1,9 @@
1
+ /**
2
+ * Resamples an IndexedImage by a specific factor using nearest neighbor
3
+ * Factor > 1 upscales, Factor < 1 downscales.
4
+ */
1
5
  import type { IndexedImage } from '../index'
6
+ import { resample32 } from '../Internal/resample32'
2
7
 
3
8
  /**
4
9
  * Resamples an IndexedImage by a specific factor using nearest neighbor
@@ -8,28 +13,18 @@ export function resampleIndexedImage(
8
13
  source: IndexedImage,
9
14
  factor: number,
10
15
  ): IndexedImage {
11
- const srcW = source.width
12
- const srcH = source.height
13
- const dstW = srcW * factor
14
- const dstH = srcH * factor
15
- const srcData = source.data
16
- const dstData = new Int32Array(dstW * dstH)
17
-
18
- for (let y = 0; y < dstH; y++) {
19
- const srcY = (y / factor) | 0
20
- const rowOffset = srcY * srcW
21
- const dstOffset = y * dstW
22
16
 
23
- for (let x = 0; x < dstW; x++) {
24
- const srcX = (x / factor) | 0
25
- dstData[dstOffset + x] = srcData[rowOffset + srcX]!
26
- }
27
- }
17
+ const { data, width, height } = resample32(
18
+ source.data,
19
+ source.width,
20
+ source.height,
21
+ factor,
22
+ )
28
23
 
29
24
  return {
30
- width: dstW,
31
- height: dstH,
32
- data: dstData,
25
+ width,
26
+ height,
27
+ data,
33
28
  palette: source.palette,
34
29
  transparentPalletIndex: source.transparentPalletIndex,
35
30
  }
@@ -0,0 +1,40 @@
1
+ const resample32Scratch = {
2
+ data: null as null | Int32Array,
3
+ width: 0,
4
+ height: 0,
5
+ }
6
+
7
+ type Resample32Result = { data: Int32Array; width: number; height: number }
8
+
9
+ export function resample32(
10
+ srcData32: Uint32Array | Int32Array,
11
+ srcW: number,
12
+ srcH: number,
13
+ factor: number,
14
+ ): Resample32Result {
15
+ const dstW = Math.max(1, (srcW * factor) | 0)
16
+ const dstH = Math.max(1, (srcH * factor) | 0)
17
+ const dstData = new Int32Array(dstW * dstH)
18
+
19
+ // Use the reciprocal to map back precisely
20
+ const scaleX = srcW / dstW
21
+ const scaleY = srcH / dstH
22
+
23
+ for (let y = 0; y < dstH; y++) {
24
+ const srcY = Math.min(srcH - 1, (y * scaleY) | 0)
25
+ const srcRowOffset = srcY * srcW
26
+ const dstRowOffset = y * dstW
27
+
28
+ for (let x = 0; x < dstW; x++) {
29
+ const srcX = Math.min(srcW - 1, (x * scaleX) | 0)
30
+
31
+ dstData[dstRowOffset + x] = srcData32[srcRowOffset + srcX]!
32
+ }
33
+ }
34
+
35
+ resample32Scratch.data = dstData
36
+ resample32Scratch.width = dstW
37
+ resample32Scratch.height = dstH
38
+
39
+ return resample32Scratch as Resample32Result
40
+ }
@@ -1,29 +1,23 @@
1
- import { PixelData } from '../index'
2
-
3
1
  /**
4
2
  * Resamples an PixelData by a specific factor using nearest neighbor
5
3
  * Factor > 1 upscales, Factor < 1 downscales.
6
4
  */
7
- export function resamplePixelData(pixelData: PixelData, factor: number): PixelData {
8
- const dstW = Math.max(1, (pixelData.width * factor) | 0)
9
- const dstH = Math.max(1, (pixelData.height * factor) | 0)
10
- const dstBuffer = new Uint8ClampedArray(dstW * dstH * 4)
11
- const dstData32 = new Uint32Array(dstBuffer.buffer)
12
-
13
- for (let y = 0; y < dstH; y++) {
14
- const srcY = (y / factor) | 0
15
- const srcRowOffset = srcY * pixelData.width
16
- const dstRowOffset = y * dstW
5
+ import { PixelData } from '../index'
6
+ import { resample32 } from '../Internal/resample32'
17
7
 
18
- for (let x = 0; x < dstW; x++) {
19
- const srcX = (x / factor) | 0
20
- dstData32[dstRowOffset + x] = pixelData.data32[srcRowOffset + srcX]!
21
- }
22
- }
8
+ /**
9
+ * Resamples PixelData by a specific factor using nearest neighbor.
10
+ * Factor > 1 upscales, Factor < 1 downscales.
11
+ */
12
+ export function resamplePixelData(
13
+ pixelData: PixelData,
14
+ factor: number,
15
+ ): PixelData {
16
+ const { data, width, height } = resample32(pixelData.data32, pixelData.width, pixelData.height, factor)
23
17
 
24
18
  return new PixelData({
25
- data: dstBuffer,
26
- width: dstW,
27
- height: dstH,
19
+ width,
20
+ height,
21
+ data: new Uint8ClampedArray(data.buffer),
28
22
  })
29
23
  }