pixel-data-js 0.0.2 → 0.0.3

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/README.md CHANGED
@@ -1,24 +1,60 @@
1
1
  # Pixel Data JS
2
2
 
3
- A library of functions for interacting with pixel data and ImageData objects.
3
+ A **🔥Performance🔥** library of functions for interacting with pixel data and ImageData objects.
4
4
  This package is designed to be a tree-shake friendly list of functions.
5
5
 
6
+ [Documentation](https://unstoppablecarl.github.io/pixel-data-js/)
7
+
6
8
  ## Installation
7
9
 
8
10
  `$ npm i pixel-data-js`
9
11
 
10
12
 
11
- ## Example Usage
13
+ ## High-Performance Pixel Manipulation with `Uint32Array`
14
+
15
+ The `ImageData.data` object is a `Uint8ClampedArray`.
16
+ It is easy to use but, it is inefficient for heavy processing because every single pixel requires four separate write operations (Red, Green, Blue, and Alpha).
17
+ By using a `Uint32Array` view, we can treat all four color channels as a single 32-bit integer, allowing us to update an entire pixel in a single CPU operation.
18
+
19
+ ## The Concept: 32-bit Packing
20
+ A single pixel consists of four 8-bit channels (R, G, B, A). The entire color's data can fit into a single 32-bit unsigned integer `8 * 4 = 32` (`Color32`).
21
+
22
+ ## Example
23
+
24
+ ```ts
25
+ import { packColor } from 'pixel-data-js'
26
+
27
+ const ctx = canvas.getContext('2d')
28
+ const imageData = ctx.getImageData(0, 0, width, height)
29
+
30
+ // 1. Get the underlying buffer
31
+ const buffer = imageData.data.buffer
32
+
33
+ // 2. Create a 32-bit view of that same buffer
34
+ const data32 = new Uint32Array(buffer)
35
+
36
+ // 3. Write a single pixel (Red: 255, Green: 100, Blue: 0, Alpha: 255)
37
+ // This is 4x faster than writing to imageData.data[i...i+3]
38
+ data32[0] = packColor(255, 100, 0, 255)
39
+
40
+ // 4. Push back to canvas
41
+ ctx.putImageData(imageData, 0, 0)
42
+ ```
43
+ ### Color Integers
44
+
45
+ You can define colors using the `AABBGGRR` (Little-Endian) to make a `Color32` object.
12
46
 
13
47
 
14
48
  ## Building
15
49
 
16
50
  `$ pnpm install`
51
+
17
52
  `$ pnpm run build`
18
53
 
19
54
  ## Testing
20
55
 
21
56
  `$ pnpm run test`
57
+
22
58
  `$ pnpm run test:mutation`
23
59
 
24
60
  ## Releases Automation
@@ -20,34 +20,50 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
+ base64DecodeArrayBuffer: () => base64DecodeArrayBuffer,
24
+ base64EncodeArrayBuffer: () => base64EncodeArrayBuffer,
23
25
  deserializeImageData: () => deserializeImageData,
24
26
  deserializeNullableImageData: () => deserializeNullableImageData,
27
+ deserializeRawImageData: () => deserializeRawImageData,
25
28
  serializeImageData: () => serializeImageData,
26
29
  serializeNullableImageData: () => serializeNullableImageData
27
30
  });
28
31
  module.exports = __toCommonJS(src_exports);
29
32
 
30
33
  // src/ImageData/serialization.ts
34
+ function base64EncodeArrayBuffer(buffer) {
35
+ const binary = String.fromCharCode(...new Uint8Array(buffer));
36
+ return btoa(binary);
37
+ }
38
+ function base64DecodeArrayBuffer(encoded) {
39
+ const binary = atob(encoded);
40
+ const bytes = new Uint8ClampedArray(binary.length);
41
+ for (let i = 0; i < binary.length; i++) {
42
+ bytes[i] = binary.charCodeAt(i);
43
+ }
44
+ return bytes;
45
+ }
31
46
  function serializeImageData(imageData) {
32
- const binary = String.fromCharCode(...new Uint8Array(imageData.data.buffer));
33
- const base64 = btoa(binary);
34
47
  return {
35
48
  width: imageData.width,
36
49
  height: imageData.height,
37
- data: base64
50
+ data: base64EncodeArrayBuffer(imageData.data.buffer)
38
51
  };
39
52
  }
40
53
  function serializeNullableImageData(imageData) {
41
54
  if (!imageData) return null;
42
55
  return serializeImageData(imageData);
43
56
  }
57
+ function deserializeRawImageData(serialized) {
58
+ return {
59
+ width: serialized.width,
60
+ height: serialized.height,
61
+ data: base64DecodeArrayBuffer(serialized.data)
62
+ };
63
+ }
44
64
  function deserializeImageData(serialized) {
45
- const binary = atob(serialized.data);
46
- const bytes = new Uint8ClampedArray(binary.length);
47
- for (let i = 0; i < binary.length; i++) {
48
- bytes[i] = binary.charCodeAt(i);
49
- }
50
- return new ImageData(bytes, serialized.width, serialized.height);
65
+ const data = base64DecodeArrayBuffer(serialized.data);
66
+ return new ImageData(data, serialized.width, serialized.height);
51
67
  }
52
68
  function deserializeNullableImageData(serialized) {
53
69
  if (!serialized) return null;
@@ -55,8 +71,11 @@ function deserializeNullableImageData(serialized) {
55
71
  }
56
72
  // Annotate the CommonJS export names for ESM import in node:
57
73
  0 && (module.exports = {
74
+ base64DecodeArrayBuffer,
75
+ base64EncodeArrayBuffer,
58
76
  deserializeImageData,
59
77
  deserializeNullableImageData,
78
+ deserializeRawImageData,
60
79
  serializeImageData,
61
80
  serializeNullableImageData
62
81
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/ImageData/serialization.ts"],"sourcesContent":["export * from './ImageData/serialization'\n","export type SerializedImageData = {\n width: number,\n height: number,\n data: string,\n}\n\n/**\n * Serialize for use in JSON. Stored as base64 encoded string.\n */\nexport function serializeImageData<T extends ImageData>(imageData: T): SerializedImageData {\n const binary = String.fromCharCode(...new Uint8Array(imageData.data.buffer))\n const base64 = btoa(binary)\n\n return {\n width: imageData.width,\n height: imageData.height,\n data: base64,\n }\n}\n\nexport function serializeNullableImageData<T extends ImageData | null>(imageData: T): T extends null ? null : SerializedImageData {\n if (!imageData) return null as any\n\n return serializeImageData(imageData) as any\n}\n\nexport function deserializeImageData<T extends SerializedImageData>(serialized: T): ImageData {\n const binary = atob(serialized.data as string)\n const bytes = new Uint8ClampedArray(binary.length)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i)\n }\n\n return new ImageData(bytes, serialized.width, serialized.height) as any\n}\n\nexport function deserializeNullableImageData<T extends SerializedImageData | null>(serialized: T): T extends null ? null : ImageData {\n if (!serialized) return null as any\n return deserializeImageData(serialized) as any\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACSO,SAAS,mBAAwC,WAAmC;AACzF,QAAM,SAAS,OAAO,aAAa,GAAG,IAAI,WAAW,UAAU,KAAK,MAAM,CAAC;AAC3E,QAAM,SAAS,KAAK,MAAM;AAE1B,SAAO;AAAA,IACL,OAAO,UAAU;AAAA,IACjB,QAAQ,UAAU;AAAA,IAClB,MAAM;AAAA,EACR;AACF;AAEO,SAAS,2BAAuD,WAA2D;AAChI,MAAI,CAAC,UAAW,QAAO;AAEvB,SAAO,mBAAmB,SAAS;AACrC;AAEO,SAAS,qBAAoD,YAA0B;AAC5F,QAAM,SAAS,KAAK,WAAW,IAAc;AAC7C,QAAM,QAAQ,IAAI,kBAAkB,OAAO,MAAM;AACjD,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,CAAC,IAAI,OAAO,WAAW,CAAC;AAAA,EAChC;AAEA,SAAO,IAAI,UAAU,OAAO,WAAW,OAAO,WAAW,MAAM;AACjE;AAEO,SAAS,6BAAmE,YAAkD;AACnI,MAAI,CAAC,WAAY,QAAO;AACxB,SAAO,qBAAqB,UAAU;AACxC;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/ImageData/serialization.ts"],"sourcesContent":["export * from './ImageData/serialization'\n","import type { Base64EncodedUInt8Array, ImageDataLike, SerializedImageData } from '../_types'\n\nexport function base64EncodeArrayBuffer(buffer: ArrayBufferLike): Base64EncodedUInt8Array {\n const binary = String.fromCharCode(...new Uint8Array(buffer))\n return btoa(binary) as Base64EncodedUInt8Array\n}\n\nexport function base64DecodeArrayBuffer(encoded: Base64EncodedUInt8Array): Uint8ClampedArray {\n const binary = atob(encoded)\n const bytes = new Uint8ClampedArray(binary.length)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i)\n }\n return bytes\n}\n\n/**\n * Serialize for use in JSON. Pixel data is stored as base64 encoded string.\n */\nexport function serializeImageData<T extends ImageDataLike>(imageData: T): SerializedImageData {\n return {\n width: imageData.width,\n height: imageData.height,\n data: base64EncodeArrayBuffer(imageData.data.buffer),\n }\n}\n\nexport function serializeNullableImageData<T extends ImageDataLike | null>(imageData: T): T extends null ? null : SerializedImageData {\n if (!imageData) return null as any\n\n return serializeImageData(imageData) as any\n}\n\nexport function deserializeRawImageData<T extends SerializedImageData>(serialized: T): ImageDataLike {\n return {\n width: serialized.width,\n height: serialized.height,\n data: base64DecodeArrayBuffer(serialized.data as Base64EncodedUInt8Array),\n }\n}\n\nexport function deserializeImageData<T extends SerializedImageData>(serialized: T): ImageData {\n const data = base64DecodeArrayBuffer(serialized.data as Base64EncodedUInt8Array)\n\n return new ImageData(data as ImageDataArray, serialized.width, serialized.height) as any\n}\n\nexport function deserializeNullableImageData<T extends SerializedImageData | null>(serialized: T): T extends null ? null : ImageData {\n if (!serialized) return null as any\n return deserializeImageData(serialized) as any\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEO,SAAS,wBAAwB,QAAkD;AACxF,QAAM,SAAS,OAAO,aAAa,GAAG,IAAI,WAAW,MAAM,CAAC;AAC5D,SAAO,KAAK,MAAM;AACpB;AAEO,SAAS,wBAAwB,SAAqD;AAC3F,QAAM,SAAS,KAAK,OAAO;AAC3B,QAAM,QAAQ,IAAI,kBAAkB,OAAO,MAAM;AACjD,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,CAAC,IAAI,OAAO,WAAW,CAAC;AAAA,EAChC;AACA,SAAO;AACT;AAKO,SAAS,mBAA4C,WAAmC;AAC7F,SAAO;AAAA,IACL,OAAO,UAAU;AAAA,IACjB,QAAQ,UAAU;AAAA,IAClB,MAAM,wBAAwB,UAAU,KAAK,MAAM;AAAA,EACrD;AACF;AAEO,SAAS,2BAA2D,WAA2D;AACpI,MAAI,CAAC,UAAW,QAAO;AAEvB,SAAO,mBAAmB,SAAS;AACrC;AAEO,SAAS,wBAAuD,YAA8B;AACnG,SAAO;AAAA,IACL,OAAO,WAAW;AAAA,IAClB,QAAQ,WAAW;AAAA,IACnB,MAAM,wBAAwB,WAAW,IAA+B;AAAA,EAC1E;AACF;AAEO,SAAS,qBAAoD,YAA0B;AAC5F,QAAM,OAAO,wBAAwB,WAAW,IAA+B;AAE/E,SAAO,IAAI,UAAU,MAAwB,WAAW,OAAO,WAAW,MAAM;AAClF;AAEO,SAAS,6BAAmE,YAAkD;AACnI,MAAI,CAAC,WAAY,QAAO;AACxB,SAAO,qBAAqB,UAAU;AACxC;","names":[]}
package/dist/index.dev.js CHANGED
@@ -1,32 +1,48 @@
1
1
  // src/ImageData/serialization.ts
2
+ function base64EncodeArrayBuffer(buffer) {
3
+ const binary = String.fromCharCode(...new Uint8Array(buffer));
4
+ return btoa(binary);
5
+ }
6
+ function base64DecodeArrayBuffer(encoded) {
7
+ const binary = atob(encoded);
8
+ const bytes = new Uint8ClampedArray(binary.length);
9
+ for (let i = 0; i < binary.length; i++) {
10
+ bytes[i] = binary.charCodeAt(i);
11
+ }
12
+ return bytes;
13
+ }
2
14
  function serializeImageData(imageData) {
3
- const binary = String.fromCharCode(...new Uint8Array(imageData.data.buffer));
4
- const base64 = btoa(binary);
5
15
  return {
6
16
  width: imageData.width,
7
17
  height: imageData.height,
8
- data: base64
18
+ data: base64EncodeArrayBuffer(imageData.data.buffer)
9
19
  };
10
20
  }
11
21
  function serializeNullableImageData(imageData) {
12
22
  if (!imageData) return null;
13
23
  return serializeImageData(imageData);
14
24
  }
25
+ function deserializeRawImageData(serialized) {
26
+ return {
27
+ width: serialized.width,
28
+ height: serialized.height,
29
+ data: base64DecodeArrayBuffer(serialized.data)
30
+ };
31
+ }
15
32
  function deserializeImageData(serialized) {
16
- const binary = atob(serialized.data);
17
- const bytes = new Uint8ClampedArray(binary.length);
18
- for (let i = 0; i < binary.length; i++) {
19
- bytes[i] = binary.charCodeAt(i);
20
- }
21
- return new ImageData(bytes, serialized.width, serialized.height);
33
+ const data = base64DecodeArrayBuffer(serialized.data);
34
+ return new ImageData(data, serialized.width, serialized.height);
22
35
  }
23
36
  function deserializeNullableImageData(serialized) {
24
37
  if (!serialized) return null;
25
38
  return deserializeImageData(serialized);
26
39
  }
27
40
  export {
41
+ base64DecodeArrayBuffer,
42
+ base64EncodeArrayBuffer,
28
43
  deserializeImageData,
29
44
  deserializeNullableImageData,
45
+ deserializeRawImageData,
30
46
  serializeImageData,
31
47
  serializeNullableImageData
32
48
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ImageData/serialization.ts"],"sourcesContent":["export type SerializedImageData = {\n width: number,\n height: number,\n data: string,\n}\n\n/**\n * Serialize for use in JSON. Stored as base64 encoded string.\n */\nexport function serializeImageData<T extends ImageData>(imageData: T): SerializedImageData {\n const binary = String.fromCharCode(...new Uint8Array(imageData.data.buffer))\n const base64 = btoa(binary)\n\n return {\n width: imageData.width,\n height: imageData.height,\n data: base64,\n }\n}\n\nexport function serializeNullableImageData<T extends ImageData | null>(imageData: T): T extends null ? null : SerializedImageData {\n if (!imageData) return null as any\n\n return serializeImageData(imageData) as any\n}\n\nexport function deserializeImageData<T extends SerializedImageData>(serialized: T): ImageData {\n const binary = atob(serialized.data as string)\n const bytes = new Uint8ClampedArray(binary.length)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i)\n }\n\n return new ImageData(bytes, serialized.width, serialized.height) as any\n}\n\nexport function deserializeNullableImageData<T extends SerializedImageData | null>(serialized: T): T extends null ? null : ImageData {\n if (!serialized) return null as any\n return deserializeImageData(serialized) as any\n}\n"],"mappings":";AASO,SAAS,mBAAwC,WAAmC;AACzF,QAAM,SAAS,OAAO,aAAa,GAAG,IAAI,WAAW,UAAU,KAAK,MAAM,CAAC;AAC3E,QAAM,SAAS,KAAK,MAAM;AAE1B,SAAO;AAAA,IACL,OAAO,UAAU;AAAA,IACjB,QAAQ,UAAU;AAAA,IAClB,MAAM;AAAA,EACR;AACF;AAEO,SAAS,2BAAuD,WAA2D;AAChI,MAAI,CAAC,UAAW,QAAO;AAEvB,SAAO,mBAAmB,SAAS;AACrC;AAEO,SAAS,qBAAoD,YAA0B;AAC5F,QAAM,SAAS,KAAK,WAAW,IAAc;AAC7C,QAAM,QAAQ,IAAI,kBAAkB,OAAO,MAAM;AACjD,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,CAAC,IAAI,OAAO,WAAW,CAAC;AAAA,EAChC;AAEA,SAAO,IAAI,UAAU,OAAO,WAAW,OAAO,WAAW,MAAM;AACjE;AAEO,SAAS,6BAAmE,YAAkD;AACnI,MAAI,CAAC,WAAY,QAAO;AACxB,SAAO,qBAAqB,UAAU;AACxC;","names":[]}
1
+ {"version":3,"sources":["../src/ImageData/serialization.ts"],"sourcesContent":["import type { Base64EncodedUInt8Array, ImageDataLike, SerializedImageData } from '../_types'\n\nexport function base64EncodeArrayBuffer(buffer: ArrayBufferLike): Base64EncodedUInt8Array {\n const binary = String.fromCharCode(...new Uint8Array(buffer))\n return btoa(binary) as Base64EncodedUInt8Array\n}\n\nexport function base64DecodeArrayBuffer(encoded: Base64EncodedUInt8Array): Uint8ClampedArray {\n const binary = atob(encoded)\n const bytes = new Uint8ClampedArray(binary.length)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i)\n }\n return bytes\n}\n\n/**\n * Serialize for use in JSON. Pixel data is stored as base64 encoded string.\n */\nexport function serializeImageData<T extends ImageDataLike>(imageData: T): SerializedImageData {\n return {\n width: imageData.width,\n height: imageData.height,\n data: base64EncodeArrayBuffer(imageData.data.buffer),\n }\n}\n\nexport function serializeNullableImageData<T extends ImageDataLike | null>(imageData: T): T extends null ? null : SerializedImageData {\n if (!imageData) return null as any\n\n return serializeImageData(imageData) as any\n}\n\nexport function deserializeRawImageData<T extends SerializedImageData>(serialized: T): ImageDataLike {\n return {\n width: serialized.width,\n height: serialized.height,\n data: base64DecodeArrayBuffer(serialized.data as Base64EncodedUInt8Array),\n }\n}\n\nexport function deserializeImageData<T extends SerializedImageData>(serialized: T): ImageData {\n const data = base64DecodeArrayBuffer(serialized.data as Base64EncodedUInt8Array)\n\n return new ImageData(data as ImageDataArray, serialized.width, serialized.height) as any\n}\n\nexport function deserializeNullableImageData<T extends SerializedImageData | null>(serialized: T): T extends null ? null : ImageData {\n if (!serialized) return null as any\n return deserializeImageData(serialized) as any\n}\n"],"mappings":";AAEO,SAAS,wBAAwB,QAAkD;AACxF,QAAM,SAAS,OAAO,aAAa,GAAG,IAAI,WAAW,MAAM,CAAC;AAC5D,SAAO,KAAK,MAAM;AACpB;AAEO,SAAS,wBAAwB,SAAqD;AAC3F,QAAM,SAAS,KAAK,OAAO;AAC3B,QAAM,QAAQ,IAAI,kBAAkB,OAAO,MAAM;AACjD,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,CAAC,IAAI,OAAO,WAAW,CAAC;AAAA,EAChC;AACA,SAAO;AACT;AAKO,SAAS,mBAA4C,WAAmC;AAC7F,SAAO;AAAA,IACL,OAAO,UAAU;AAAA,IACjB,QAAQ,UAAU;AAAA,IAClB,MAAM,wBAAwB,UAAU,KAAK,MAAM;AAAA,EACrD;AACF;AAEO,SAAS,2BAA2D,WAA2D;AACpI,MAAI,CAAC,UAAW,QAAO;AAEvB,SAAO,mBAAmB,SAAS;AACrC;AAEO,SAAS,wBAAuD,YAA8B;AACnG,SAAO;AAAA,IACL,OAAO,WAAW;AAAA,IAClB,QAAQ,WAAW;AAAA,IACnB,MAAM,wBAAwB,WAAW,IAA+B;AAAA,EAC1E;AACF;AAEO,SAAS,qBAAoD,YAA0B;AAC5F,QAAM,OAAO,wBAAwB,WAAW,IAA+B;AAE/E,SAAO,IAAI,UAAU,MAAwB,WAAW,OAAO,WAAW,MAAM;AAClF;AAEO,SAAS,6BAAmE,YAAkD;AACnI,MAAI,CAAC,WAAY,QAAO;AACxB,SAAO,qBAAqB,UAAU;AACxC;","names":[]}
@@ -20,34 +20,50 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
+ base64DecodeArrayBuffer: () => base64DecodeArrayBuffer,
24
+ base64EncodeArrayBuffer: () => base64EncodeArrayBuffer,
23
25
  deserializeImageData: () => deserializeImageData,
24
26
  deserializeNullableImageData: () => deserializeNullableImageData,
27
+ deserializeRawImageData: () => deserializeRawImageData,
25
28
  serializeImageData: () => serializeImageData,
26
29
  serializeNullableImageData: () => serializeNullableImageData
27
30
  });
28
31
  module.exports = __toCommonJS(src_exports);
29
32
 
30
33
  // src/ImageData/serialization.ts
34
+ function base64EncodeArrayBuffer(buffer) {
35
+ const binary = String.fromCharCode(...new Uint8Array(buffer));
36
+ return btoa(binary);
37
+ }
38
+ function base64DecodeArrayBuffer(encoded) {
39
+ const binary = atob(encoded);
40
+ const bytes = new Uint8ClampedArray(binary.length);
41
+ for (let i = 0; i < binary.length; i++) {
42
+ bytes[i] = binary.charCodeAt(i);
43
+ }
44
+ return bytes;
45
+ }
31
46
  function serializeImageData(imageData) {
32
- const binary = String.fromCharCode(...new Uint8Array(imageData.data.buffer));
33
- const base64 = btoa(binary);
34
47
  return {
35
48
  width: imageData.width,
36
49
  height: imageData.height,
37
- data: base64
50
+ data: base64EncodeArrayBuffer(imageData.data.buffer)
38
51
  };
39
52
  }
40
53
  function serializeNullableImageData(imageData) {
41
54
  if (!imageData) return null;
42
55
  return serializeImageData(imageData);
43
56
  }
57
+ function deserializeRawImageData(serialized) {
58
+ return {
59
+ width: serialized.width,
60
+ height: serialized.height,
61
+ data: base64DecodeArrayBuffer(serialized.data)
62
+ };
63
+ }
44
64
  function deserializeImageData(serialized) {
45
- const binary = atob(serialized.data);
46
- const bytes = new Uint8ClampedArray(binary.length);
47
- for (let i = 0; i < binary.length; i++) {
48
- bytes[i] = binary.charCodeAt(i);
49
- }
50
- return new ImageData(bytes, serialized.width, serialized.height);
65
+ const data = base64DecodeArrayBuffer(serialized.data);
66
+ return new ImageData(data, serialized.width, serialized.height);
51
67
  }
52
68
  function deserializeNullableImageData(serialized) {
53
69
  if (!serialized) return null;
@@ -55,8 +71,11 @@ function deserializeNullableImageData(serialized) {
55
71
  }
56
72
  // Annotate the CommonJS export names for ESM import in node:
57
73
  0 && (module.exports = {
74
+ base64DecodeArrayBuffer,
75
+ base64EncodeArrayBuffer,
58
76
  deserializeImageData,
59
77
  deserializeNullableImageData,
78
+ deserializeRawImageData,
60
79
  serializeImageData,
61
80
  serializeNullableImageData
62
81
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/ImageData/serialization.ts"],"sourcesContent":["export * from './ImageData/serialization'\n","export type SerializedImageData = {\n width: number,\n height: number,\n data: string,\n}\n\n/**\n * Serialize for use in JSON. Stored as base64 encoded string.\n */\nexport function serializeImageData<T extends ImageData>(imageData: T): SerializedImageData {\n const binary = String.fromCharCode(...new Uint8Array(imageData.data.buffer))\n const base64 = btoa(binary)\n\n return {\n width: imageData.width,\n height: imageData.height,\n data: base64,\n }\n}\n\nexport function serializeNullableImageData<T extends ImageData | null>(imageData: T): T extends null ? null : SerializedImageData {\n if (!imageData) return null as any\n\n return serializeImageData(imageData) as any\n}\n\nexport function deserializeImageData<T extends SerializedImageData>(serialized: T): ImageData {\n const binary = atob(serialized.data as string)\n const bytes = new Uint8ClampedArray(binary.length)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i)\n }\n\n return new ImageData(bytes, serialized.width, serialized.height) as any\n}\n\nexport function deserializeNullableImageData<T extends SerializedImageData | null>(serialized: T): T extends null ? null : ImageData {\n if (!serialized) return null as any\n return deserializeImageData(serialized) as any\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACSO,SAAS,mBAAwC,WAAmC;AACzF,QAAM,SAAS,OAAO,aAAa,GAAG,IAAI,WAAW,UAAU,KAAK,MAAM,CAAC;AAC3E,QAAM,SAAS,KAAK,MAAM;AAE1B,SAAO;AAAA,IACL,OAAO,UAAU;AAAA,IACjB,QAAQ,UAAU;AAAA,IAClB,MAAM;AAAA,EACR;AACF;AAEO,SAAS,2BAAuD,WAA2D;AAChI,MAAI,CAAC,UAAW,QAAO;AAEvB,SAAO,mBAAmB,SAAS;AACrC;AAEO,SAAS,qBAAoD,YAA0B;AAC5F,QAAM,SAAS,KAAK,WAAW,IAAc;AAC7C,QAAM,QAAQ,IAAI,kBAAkB,OAAO,MAAM;AACjD,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,CAAC,IAAI,OAAO,WAAW,CAAC;AAAA,EAChC;AAEA,SAAO,IAAI,UAAU,OAAO,WAAW,OAAO,WAAW,MAAM;AACjE;AAEO,SAAS,6BAAmE,YAAkD;AACnI,MAAI,CAAC,WAAY,QAAO;AACxB,SAAO,qBAAqB,UAAU;AACxC;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/ImageData/serialization.ts"],"sourcesContent":["export * from './ImageData/serialization'\n","import type { Base64EncodedUInt8Array, ImageDataLike, SerializedImageData } from '../_types'\n\nexport function base64EncodeArrayBuffer(buffer: ArrayBufferLike): Base64EncodedUInt8Array {\n const binary = String.fromCharCode(...new Uint8Array(buffer))\n return btoa(binary) as Base64EncodedUInt8Array\n}\n\nexport function base64DecodeArrayBuffer(encoded: Base64EncodedUInt8Array): Uint8ClampedArray {\n const binary = atob(encoded)\n const bytes = new Uint8ClampedArray(binary.length)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i)\n }\n return bytes\n}\n\n/**\n * Serialize for use in JSON. Pixel data is stored as base64 encoded string.\n */\nexport function serializeImageData<T extends ImageDataLike>(imageData: T): SerializedImageData {\n return {\n width: imageData.width,\n height: imageData.height,\n data: base64EncodeArrayBuffer(imageData.data.buffer),\n }\n}\n\nexport function serializeNullableImageData<T extends ImageDataLike | null>(imageData: T): T extends null ? null : SerializedImageData {\n if (!imageData) return null as any\n\n return serializeImageData(imageData) as any\n}\n\nexport function deserializeRawImageData<T extends SerializedImageData>(serialized: T): ImageDataLike {\n return {\n width: serialized.width,\n height: serialized.height,\n data: base64DecodeArrayBuffer(serialized.data as Base64EncodedUInt8Array),\n }\n}\n\nexport function deserializeImageData<T extends SerializedImageData>(serialized: T): ImageData {\n const data = base64DecodeArrayBuffer(serialized.data as Base64EncodedUInt8Array)\n\n return new ImageData(data as ImageDataArray, serialized.width, serialized.height) as any\n}\n\nexport function deserializeNullableImageData<T extends SerializedImageData | null>(serialized: T): T extends null ? null : ImageData {\n if (!serialized) return null as any\n return deserializeImageData(serialized) as any\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEO,SAAS,wBAAwB,QAAkD;AACxF,QAAM,SAAS,OAAO,aAAa,GAAG,IAAI,WAAW,MAAM,CAAC;AAC5D,SAAO,KAAK,MAAM;AACpB;AAEO,SAAS,wBAAwB,SAAqD;AAC3F,QAAM,SAAS,KAAK,OAAO;AAC3B,QAAM,QAAQ,IAAI,kBAAkB,OAAO,MAAM;AACjD,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,CAAC,IAAI,OAAO,WAAW,CAAC;AAAA,EAChC;AACA,SAAO;AACT;AAKO,SAAS,mBAA4C,WAAmC;AAC7F,SAAO;AAAA,IACL,OAAO,UAAU;AAAA,IACjB,QAAQ,UAAU;AAAA,IAClB,MAAM,wBAAwB,UAAU,KAAK,MAAM;AAAA,EACrD;AACF;AAEO,SAAS,2BAA2D,WAA2D;AACpI,MAAI,CAAC,UAAW,QAAO;AAEvB,SAAO,mBAAmB,SAAS;AACrC;AAEO,SAAS,wBAAuD,YAA8B;AACnG,SAAO;AAAA,IACL,OAAO,WAAW;AAAA,IAClB,QAAQ,WAAW;AAAA,IACnB,MAAM,wBAAwB,WAAW,IAA+B;AAAA,EAC1E;AACF;AAEO,SAAS,qBAAoD,YAA0B;AAC5F,QAAM,OAAO,wBAAwB,WAAW,IAA+B;AAE/E,SAAO,IAAI,UAAU,MAAwB,WAAW,OAAO,WAAW,MAAM;AAClF;AAEO,SAAS,6BAAmE,YAAkD;AACnI,MAAI,CAAC,WAAY,QAAO;AACxB,SAAO,qBAAqB,UAAU;AACxC;","names":[]}
@@ -1,14 +1,26 @@
1
+ type ImageDataLike = {
2
+ width: number;
3
+ height: number;
4
+ data: Uint8ClampedArray;
5
+ };
1
6
  type SerializedImageData = {
2
7
  width: number;
3
8
  height: number;
4
9
  data: string;
5
10
  };
11
+ type Base64EncodedUInt8Array = string & {
12
+ readonly __brandBase64UInt8Array: unique symbol;
13
+ };
14
+
15
+ declare function base64EncodeArrayBuffer(buffer: ArrayBufferLike): Base64EncodedUInt8Array;
16
+ declare function base64DecodeArrayBuffer(encoded: Base64EncodedUInt8Array): Uint8ClampedArray;
6
17
  /**
7
- * Serialize for use in JSON. Stored as base64 encoded string.
18
+ * Serialize for use in JSON. Pixel data is stored as base64 encoded string.
8
19
  */
9
- declare function serializeImageData<T extends ImageData>(imageData: T): SerializedImageData;
10
- declare function serializeNullableImageData<T extends ImageData | null>(imageData: T): T extends null ? null : SerializedImageData;
20
+ declare function serializeImageData<T extends ImageDataLike>(imageData: T): SerializedImageData;
21
+ declare function serializeNullableImageData<T extends ImageDataLike | null>(imageData: T): T extends null ? null : SerializedImageData;
22
+ declare function deserializeRawImageData<T extends SerializedImageData>(serialized: T): ImageDataLike;
11
23
  declare function deserializeImageData<T extends SerializedImageData>(serialized: T): ImageData;
12
24
  declare function deserializeNullableImageData<T extends SerializedImageData | null>(serialized: T): T extends null ? null : ImageData;
13
25
 
14
- export { type SerializedImageData, deserializeImageData, deserializeNullableImageData, serializeImageData, serializeNullableImageData };
26
+ export { base64DecodeArrayBuffer, base64EncodeArrayBuffer, deserializeImageData, deserializeNullableImageData, deserializeRawImageData, serializeImageData, serializeNullableImageData };
@@ -1,32 +1,48 @@
1
1
  // src/ImageData/serialization.ts
2
+ function base64EncodeArrayBuffer(buffer) {
3
+ const binary = String.fromCharCode(...new Uint8Array(buffer));
4
+ return btoa(binary);
5
+ }
6
+ function base64DecodeArrayBuffer(encoded) {
7
+ const binary = atob(encoded);
8
+ const bytes = new Uint8ClampedArray(binary.length);
9
+ for (let i = 0; i < binary.length; i++) {
10
+ bytes[i] = binary.charCodeAt(i);
11
+ }
12
+ return bytes;
13
+ }
2
14
  function serializeImageData(imageData) {
3
- const binary = String.fromCharCode(...new Uint8Array(imageData.data.buffer));
4
- const base64 = btoa(binary);
5
15
  return {
6
16
  width: imageData.width,
7
17
  height: imageData.height,
8
- data: base64
18
+ data: base64EncodeArrayBuffer(imageData.data.buffer)
9
19
  };
10
20
  }
11
21
  function serializeNullableImageData(imageData) {
12
22
  if (!imageData) return null;
13
23
  return serializeImageData(imageData);
14
24
  }
25
+ function deserializeRawImageData(serialized) {
26
+ return {
27
+ width: serialized.width,
28
+ height: serialized.height,
29
+ data: base64DecodeArrayBuffer(serialized.data)
30
+ };
31
+ }
15
32
  function deserializeImageData(serialized) {
16
- const binary = atob(serialized.data);
17
- const bytes = new Uint8ClampedArray(binary.length);
18
- for (let i = 0; i < binary.length; i++) {
19
- bytes[i] = binary.charCodeAt(i);
20
- }
21
- return new ImageData(bytes, serialized.width, serialized.height);
33
+ const data = base64DecodeArrayBuffer(serialized.data);
34
+ return new ImageData(data, serialized.width, serialized.height);
22
35
  }
23
36
  function deserializeNullableImageData(serialized) {
24
37
  if (!serialized) return null;
25
38
  return deserializeImageData(serialized);
26
39
  }
27
40
  export {
41
+ base64DecodeArrayBuffer,
42
+ base64EncodeArrayBuffer,
28
43
  deserializeImageData,
29
44
  deserializeNullableImageData,
45
+ deserializeRawImageData,
30
46
  serializeImageData,
31
47
  serializeNullableImageData
32
48
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ImageData/serialization.ts"],"sourcesContent":["export type SerializedImageData = {\n width: number,\n height: number,\n data: string,\n}\n\n/**\n * Serialize for use in JSON. Stored as base64 encoded string.\n */\nexport function serializeImageData<T extends ImageData>(imageData: T): SerializedImageData {\n const binary = String.fromCharCode(...new Uint8Array(imageData.data.buffer))\n const base64 = btoa(binary)\n\n return {\n width: imageData.width,\n height: imageData.height,\n data: base64,\n }\n}\n\nexport function serializeNullableImageData<T extends ImageData | null>(imageData: T): T extends null ? null : SerializedImageData {\n if (!imageData) return null as any\n\n return serializeImageData(imageData) as any\n}\n\nexport function deserializeImageData<T extends SerializedImageData>(serialized: T): ImageData {\n const binary = atob(serialized.data as string)\n const bytes = new Uint8ClampedArray(binary.length)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i)\n }\n\n return new ImageData(bytes, serialized.width, serialized.height) as any\n}\n\nexport function deserializeNullableImageData<T extends SerializedImageData | null>(serialized: T): T extends null ? null : ImageData {\n if (!serialized) return null as any\n return deserializeImageData(serialized) as any\n}\n"],"mappings":";AASO,SAAS,mBAAwC,WAAmC;AACzF,QAAM,SAAS,OAAO,aAAa,GAAG,IAAI,WAAW,UAAU,KAAK,MAAM,CAAC;AAC3E,QAAM,SAAS,KAAK,MAAM;AAE1B,SAAO;AAAA,IACL,OAAO,UAAU;AAAA,IACjB,QAAQ,UAAU;AAAA,IAClB,MAAM;AAAA,EACR;AACF;AAEO,SAAS,2BAAuD,WAA2D;AAChI,MAAI,CAAC,UAAW,QAAO;AAEvB,SAAO,mBAAmB,SAAS;AACrC;AAEO,SAAS,qBAAoD,YAA0B;AAC5F,QAAM,SAAS,KAAK,WAAW,IAAc;AAC7C,QAAM,QAAQ,IAAI,kBAAkB,OAAO,MAAM;AACjD,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,CAAC,IAAI,OAAO,WAAW,CAAC;AAAA,EAChC;AAEA,SAAO,IAAI,UAAU,OAAO,WAAW,OAAO,WAAW,MAAM;AACjE;AAEO,SAAS,6BAAmE,YAAkD;AACnI,MAAI,CAAC,WAAY,QAAO;AACxB,SAAO,qBAAqB,UAAU;AACxC;","names":[]}
1
+ {"version":3,"sources":["../src/ImageData/serialization.ts"],"sourcesContent":["import type { Base64EncodedUInt8Array, ImageDataLike, SerializedImageData } from '../_types'\n\nexport function base64EncodeArrayBuffer(buffer: ArrayBufferLike): Base64EncodedUInt8Array {\n const binary = String.fromCharCode(...new Uint8Array(buffer))\n return btoa(binary) as Base64EncodedUInt8Array\n}\n\nexport function base64DecodeArrayBuffer(encoded: Base64EncodedUInt8Array): Uint8ClampedArray {\n const binary = atob(encoded)\n const bytes = new Uint8ClampedArray(binary.length)\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i)\n }\n return bytes\n}\n\n/**\n * Serialize for use in JSON. Pixel data is stored as base64 encoded string.\n */\nexport function serializeImageData<T extends ImageDataLike>(imageData: T): SerializedImageData {\n return {\n width: imageData.width,\n height: imageData.height,\n data: base64EncodeArrayBuffer(imageData.data.buffer),\n }\n}\n\nexport function serializeNullableImageData<T extends ImageDataLike | null>(imageData: T): T extends null ? null : SerializedImageData {\n if (!imageData) return null as any\n\n return serializeImageData(imageData) as any\n}\n\nexport function deserializeRawImageData<T extends SerializedImageData>(serialized: T): ImageDataLike {\n return {\n width: serialized.width,\n height: serialized.height,\n data: base64DecodeArrayBuffer(serialized.data as Base64EncodedUInt8Array),\n }\n}\n\nexport function deserializeImageData<T extends SerializedImageData>(serialized: T): ImageData {\n const data = base64DecodeArrayBuffer(serialized.data as Base64EncodedUInt8Array)\n\n return new ImageData(data as ImageDataArray, serialized.width, serialized.height) as any\n}\n\nexport function deserializeNullableImageData<T extends SerializedImageData | null>(serialized: T): T extends null ? null : ImageData {\n if (!serialized) return null as any\n return deserializeImageData(serialized) as any\n}\n"],"mappings":";AAEO,SAAS,wBAAwB,QAAkD;AACxF,QAAM,SAAS,OAAO,aAAa,GAAG,IAAI,WAAW,MAAM,CAAC;AAC5D,SAAO,KAAK,MAAM;AACpB;AAEO,SAAS,wBAAwB,SAAqD;AAC3F,QAAM,SAAS,KAAK,OAAO;AAC3B,QAAM,QAAQ,IAAI,kBAAkB,OAAO,MAAM;AACjD,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,UAAM,CAAC,IAAI,OAAO,WAAW,CAAC;AAAA,EAChC;AACA,SAAO;AACT;AAKO,SAAS,mBAA4C,WAAmC;AAC7F,SAAO;AAAA,IACL,OAAO,UAAU;AAAA,IACjB,QAAQ,UAAU;AAAA,IAClB,MAAM,wBAAwB,UAAU,KAAK,MAAM;AAAA,EACrD;AACF;AAEO,SAAS,2BAA2D,WAA2D;AACpI,MAAI,CAAC,UAAW,QAAO;AAEvB,SAAO,mBAAmB,SAAS;AACrC;AAEO,SAAS,wBAAuD,YAA8B;AACnG,SAAO;AAAA,IACL,OAAO,WAAW;AAAA,IAClB,QAAQ,WAAW;AAAA,IACnB,MAAM,wBAAwB,WAAW,IAA+B;AAAA,EAC1E;AACF;AAEO,SAAS,qBAAoD,YAA0B;AAC5F,QAAM,OAAO,wBAAwB,WAAW,IAA+B;AAE/E,SAAO,IAAI,UAAU,MAAwB,WAAW,OAAO,WAAW,MAAM;AAClF;AAEO,SAAS,6BAAmE,YAAkD;AACnI,MAAI,CAAC,WAAY,QAAO;AACxB,SAAO,qBAAqB,UAAU;AACxC;","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pixel-data-js",
3
3
  "type": "module",
4
- "version": "0.0.2",
4
+ "version": "0.0.3",
5
5
  "packageManager": "pnpm@10.30.0",
6
6
  "description": "JS Pixel and ImageData operations",
7
7
  "author": {
@@ -0,0 +1,36 @@
1
+ import type { Color32, ImageDataLike } from '../_types'
2
+
3
+ export function makeImageDataColor32Adapter(imageData: ImageDataLike) {
4
+ const data32 = new Uint32Array(imageData.data.buffer)
5
+
6
+ function inBounds(x: number, y: number) {
7
+ return x < 0 || x >= imageData.width || y < 0 || y >= imageData.height
8
+ }
9
+
10
+ function setPixel(
11
+ x: number,
12
+ y: number,
13
+ color: Color32,
14
+ ): void {
15
+ if (x < 0 || x >= imageData.width || y < 0 || y >= imageData.height) return
16
+ data32[y * imageData.width + x] = color
17
+ }
18
+
19
+ function getPixel(
20
+ x: number,
21
+ y: number,
22
+ ): Color32 | undefined {
23
+ if (x < 0 || x >= imageData.width || y < 0 || y >= imageData.height) return
24
+
25
+ return data32[y * imageData.width + x] as Color32
26
+ }
27
+
28
+ return {
29
+ inBounds,
30
+ imageData,
31
+ data32,
32
+ setPixel,
33
+ getPixel,
34
+ }
35
+ }
36
+
@@ -1,37 +1,48 @@
1
- export type SerializedImageData = {
2
- width: number,
3
- height: number,
4
- data: string,
1
+ import type { Base64EncodedUInt8Array, ImageDataLike, SerializedImageData } from '../_types'
2
+
3
+ export function base64EncodeArrayBuffer(buffer: ArrayBufferLike): Base64EncodedUInt8Array {
4
+ const binary = String.fromCharCode(...new Uint8Array(buffer))
5
+ return btoa(binary) as Base64EncodedUInt8Array
6
+ }
7
+
8
+ export function base64DecodeArrayBuffer(encoded: Base64EncodedUInt8Array): Uint8ClampedArray {
9
+ const binary = atob(encoded)
10
+ const bytes = new Uint8ClampedArray(binary.length)
11
+ for (let i = 0; i < binary.length; i++) {
12
+ bytes[i] = binary.charCodeAt(i)
13
+ }
14
+ return bytes
5
15
  }
6
16
 
7
17
  /**
8
- * Serialize for use in JSON. Stored as base64 encoded string.
18
+ * Serialize for use in JSON. Pixel data is stored as base64 encoded string.
9
19
  */
10
- export function serializeImageData<T extends ImageData>(imageData: T): SerializedImageData {
11
- const binary = String.fromCharCode(...new Uint8Array(imageData.data.buffer))
12
- const base64 = btoa(binary)
13
-
20
+ export function serializeImageData<T extends ImageDataLike>(imageData: T): SerializedImageData {
14
21
  return {
15
22
  width: imageData.width,
16
23
  height: imageData.height,
17
- data: base64,
24
+ data: base64EncodeArrayBuffer(imageData.data.buffer),
18
25
  }
19
26
  }
20
27
 
21
- export function serializeNullableImageData<T extends ImageData | null>(imageData: T): T extends null ? null : SerializedImageData {
28
+ export function serializeNullableImageData<T extends ImageDataLike | null>(imageData: T): T extends null ? null : SerializedImageData {
22
29
  if (!imageData) return null as any
23
30
 
24
31
  return serializeImageData(imageData) as any
25
32
  }
26
33
 
27
- export function deserializeImageData<T extends SerializedImageData>(serialized: T): ImageData {
28
- const binary = atob(serialized.data as string)
29
- const bytes = new Uint8ClampedArray(binary.length)
30
- for (let i = 0; i < binary.length; i++) {
31
- bytes[i] = binary.charCodeAt(i)
34
+ export function deserializeRawImageData<T extends SerializedImageData>(serialized: T): ImageDataLike {
35
+ return {
36
+ width: serialized.width,
37
+ height: serialized.height,
38
+ data: base64DecodeArrayBuffer(serialized.data as Base64EncodedUInt8Array),
32
39
  }
40
+ }
41
+
42
+ export function deserializeImageData<T extends SerializedImageData>(serialized: T): ImageData {
43
+ const data = base64DecodeArrayBuffer(serialized.data as Base64EncodedUInt8Array)
33
44
 
34
- return new ImageData(bytes, serialized.width, serialized.height) as any
45
+ return new ImageData(data as ImageDataArray, serialized.width, serialized.height) as any
35
46
  }
36
47
 
37
48
  export function deserializeNullableImageData<T extends SerializedImageData | null>(serialized: T): T extends null ? null : ImageData {
package/src/_types.ts ADDED
@@ -0,0 +1,22 @@
1
+ // ALL values are 0-255 (including alpha which in CSS is 0-1)
2
+ export type RGBA = { r: number, g: number, b: number, a: number }
3
+
4
+ // A 32-bit integer containing r,g,b,a data
5
+ export type Color32 = number & { readonly __brandColor32: unique symbol };
6
+
7
+ // ALL values are floats from 0-1
8
+ export type RGBAFloat = RGBA & { readonly __brandRGBAFloat: unique symbol }
9
+
10
+ export type ImageDataLike = {
11
+ width: number
12
+ height: number
13
+ data: Uint8ClampedArray
14
+ }
15
+
16
+ export type SerializedImageData = {
17
+ width: number,
18
+ height: number,
19
+ data: string,
20
+ }
21
+
22
+ export type Base64EncodedUInt8Array = string & { readonly __brandBase64UInt8Array: unique symbol }
package/src/color.ts ADDED
@@ -0,0 +1,79 @@
1
+ import type { Color32, RGBA } from './_types'
2
+
3
+ /**
4
+ * Packs RGBA into a 32-bit integer compatible with
5
+ * Little-Endian Uint32Array views on ImageData.
6
+ */
7
+ export function packColor(r: number, g: number, b: number, a: number): Color32 {
8
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
9
+ }
10
+
11
+ export function packRGBA({ r, g, b, a }: RGBA): Color32 {
12
+ return ((a << 24) | (b << 16) | (g << 8) | r) >>> 0 as Color32
13
+ }
14
+
15
+ export const unpackRed = (packed: Color32): number => (packed >>> 0) & 0xFF
16
+ export const unpackGreen = (packed: Color32): number => (packed >>> 8) & 0xFF
17
+ export const unpackBlue = (packed: Color32): number => (packed >>> 16) & 0xFF
18
+ export const unpackAlpha = (packed: Color32): number => (packed >>> 24) & 0xFF
19
+
20
+ export function unpackColor(packed: Color32): RGBA {
21
+ return {
22
+ r: (packed >>> 0) & 0xFF,
23
+ g: (packed >>> 8) & 0xFF,
24
+ b: (packed >>> 16) & 0xFF,
25
+ a: (packed >>> 24) & 0xFF,
26
+ }
27
+ }
28
+
29
+ const SCRATCH_RGBA: RGBA = { r: 0, g: 0, b: 0, a: 0 }
30
+
31
+ // uses a scratch arg for memory perf. Be careful about re-use.
32
+ export function unpackColorTo(packed: Color32, scratch = SCRATCH_RGBA): RGBA {
33
+ scratch.r = (packed >>> 0) & 0xFF
34
+ scratch.g = (packed >>> 8) & 0xFF
35
+ scratch.b = (packed >>> 16) & 0xFF
36
+ scratch.a = (packed >>> 24) & 0xFF
37
+ return scratch
38
+ }
39
+
40
+ export function colorDistance(a: Color32, b: Color32): number {
41
+ const dr = (a & 0xFF) - (b & 0xFF)
42
+ const dg = ((a >>> 8) & 0xFF) - ((b >>> 8) & 0xFF)
43
+ const db = ((a >>> 16) & 0xFF) - ((b >>> 16) & 0xFF)
44
+ const da = ((a >>> 24) & 0xFF) - ((b >>> 24) & 0xFF)
45
+ return dr * dr + dg * dg + db * db + da * da
46
+ }
47
+
48
+ export function lerpColor32(a: Color32, b: Color32, t: number): Color32 {
49
+ const r = (a & 0xFF) + t * ((b & 0xFF) - (a & 0xFF))
50
+ const g = ((a >>> 8) & 0xFF) + t * (((b >>> 8) & 0xFF) - ((a >>> 8) & 0xFF))
51
+ const b_ = ((a >>> 16) & 0xFF) + t * (((b >>> 16) & 0xFF) - ((a >>> 16) & 0xFF))
52
+ const a_ = ((a >>> 24) & 0xFF) + t * (((b >>> 24) & 0xFF) - ((a >>> 24) & 0xFF))
53
+
54
+ return packColor(r, g, b_, a_)
55
+ }
56
+
57
+ // Convert 0xAABBGGRR to #RRGGBBAA
58
+ export function color32ToHex(color: Color32): string {
59
+ const r = (color & 0xFF).toString(16).padStart(2, '0')
60
+ const g = ((color >>> 8) & 0xFF).toString(16).padStart(2, '0')
61
+ const b = ((color >>> 16) & 0xFF).toString(16).padStart(2, '0')
62
+ const a = ((color >>> 24) & 0xFF).toString(16).padStart(2, '0')
63
+ return `#${r}${g}${b}${a}`
64
+ }
65
+
66
+ /**
67
+ * Converts a 32-bit integer (0xAABBGGRR) to a CSS rgba() string.
68
+ * Example: 0xFF0000FF -> "rgba(255,0,0,1)"
69
+ */
70
+ export function color32ToCssRGBA(color: Color32): string {
71
+ const r = color & 0xFF
72
+ const g = (color >>> 8) & 0xFF
73
+ const b = (color >>> 16) & 0xFF
74
+ const a = (color >>> 24) & 0xFF
75
+
76
+ const alpha = Number((a / 255).toFixed(3))
77
+
78
+ return `rgba(${r},${g},${b},${alpha})`
79
+ }