pixel-data-js 0.0.1 → 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,144 +20,63 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
- createPiniaSimplePersist: () => createPiniaSimplePersist,
24
- makeSimplePersistMapper: () => makeSimplePersistMapper
23
+ base64DecodeArrayBuffer: () => base64DecodeArrayBuffer,
24
+ base64EncodeArrayBuffer: () => base64EncodeArrayBuffer,
25
+ deserializeImageData: () => deserializeImageData,
26
+ deserializeNullableImageData: () => deserializeNullableImageData,
27
+ deserializeRawImageData: () => deserializeRawImageData,
28
+ serializeImageData: () => serializeImageData,
29
+ serializeNullableImageData: () => serializeNullableImageData
25
30
  });
26
31
  module.exports = __toCommonJS(src_exports);
27
32
 
28
- // src/plugin.ts
29
- var import_pinia = require("pinia");
30
- function createPiniaSimplePersist(globalOptions = {}) {
31
- const makeKey = globalOptions.makeKey ?? ((id) => `pinia-${id}`);
32
- return (context) => {
33
- const { store, options } = context;
34
- ensureStoreHasRestoreState(store);
35
- ensureStoreHasSerializeState(store);
36
- const persist = options.persist;
37
- if (!persist) return;
38
- const {
39
- key = makeKey(store.$id),
40
- storage = localStorage,
41
- serializer = {
42
- serialize: JSON.stringify,
43
- deserialize: JSON.parse
44
- },
45
- debounce = 0,
46
- beforeRestore,
47
- afterRestore,
48
- onRestoreError
49
- } = { ...globalOptions, ...persist };
50
- const restoreState = () => {
51
- beforeRestore?.(context);
52
- const stored = storage.getItem(key);
53
- if (!stored) return;
54
- try {
55
- const data = serializer.deserialize(stored);
56
- store.$restoreState(data);
57
- afterRestore?.(context);
58
- } catch (error) {
59
- if (onRestoreError) {
60
- onRestoreError(error instanceof Error ? error : new Error(String(error)));
61
- } else {
62
- throw error;
63
- }
64
- }
65
- };
66
- const saveState = () => {
67
- const state = store.$serializeState();
68
- const serialized = serializer.serialize(state);
69
- storage.setItem(key, serialized);
70
- };
71
- let finalSave = saveState;
72
- let cleanupDebounce;
73
- if (debounce) {
74
- const { debouncedFn, cleanup } = makeDebounce(saveState, debounce);
75
- finalSave = debouncedFn;
76
- cleanupDebounce = cleanup;
77
- }
78
- const unsubscribe = store.$subscribe(() => {
79
- finalSave();
80
- }, { detached: true });
81
- const originalDispose = store.$dispose;
82
- store.$dispose = function() {
83
- unsubscribe();
84
- cleanupDebounce?.();
85
- originalDispose.call(this);
86
- };
87
- restoreState();
88
- };
33
+ // src/ImageData/serialization.ts
34
+ function base64EncodeArrayBuffer(buffer) {
35
+ const binary = String.fromCharCode(...new Uint8Array(buffer));
36
+ return btoa(binary);
89
37
  }
90
- var makeDebounce = (callback, waitFor) => {
91
- let timeout;
92
- const debouncedFn = () => {
93
- if (timeout) clearTimeout(timeout);
94
- timeout = setTimeout(() => {
95
- callback();
96
- }, waitFor);
97
- };
98
- const cleanup = () => {
99
- if (timeout) {
100
- clearTimeout(timeout);
101
- timeout = void 0;
102
- }
103
- };
104
- return { debouncedFn, cleanup };
105
- };
106
- function ensureStoreHasRestoreState(store) {
107
- if (typeof store.$restoreState !== "function") {
108
- throw new Error("A store using pinia-simple-persist must have a $restoreState() method");
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);
109
43
  }
44
+ return bytes;
110
45
  }
111
- function ensureStoreHasSerializeState(store) {
112
- if (typeof store.$serializeState !== "function") {
113
- throw new Error("A store using pinia-simple-persist must have a $serializeState() method");
114
- }
46
+ function serializeImageData(imageData) {
47
+ return {
48
+ width: imageData.width,
49
+ height: imageData.height,
50
+ data: base64EncodeArrayBuffer(imageData.data.buffer)
51
+ };
115
52
  }
116
-
117
- // src/mapper.ts
118
- var import_vue = require("vue");
119
- function makeSimplePersistMapper(state, defaults) {
53
+ function serializeNullableImageData(imageData) {
54
+ if (!imageData) return null;
55
+ return serializeImageData(imageData);
56
+ }
57
+ function deserializeRawImageData(serialized) {
120
58
  return {
121
- $serializeState() {
122
- const out = {};
123
- for (const key in state) {
124
- const item = state[key];
125
- let value;
126
- if ((0, import_vue.isRef)(item)) {
127
- value = item.value;
128
- } else {
129
- value = item;
130
- }
131
- out[key] = (0, import_vue.isReactive)(value) ? (0, import_vue.toRaw)(value) : value;
132
- }
133
- return out;
134
- },
135
- $restoreState(data) {
136
- for (const key in state) {
137
- if (!(key in data)) continue;
138
- const item = state[key];
139
- if ((0, import_vue.isRef)(item)) {
140
- item.value = data[key];
141
- } else if ((0, import_vue.isReactive)(item)) {
142
- Object.assign(item, data[key]);
143
- }
144
- }
145
- },
146
- $reset() {
147
- for (const key in state) {
148
- const item = state[key];
149
- if ((0, import_vue.isRef)(item)) {
150
- item.value = defaults[key];
151
- } else if ((0, import_vue.isReactive)(item)) {
152
- Object.assign(item, defaults[key]);
153
- }
154
- }
155
- }
59
+ width: serialized.width,
60
+ height: serialized.height,
61
+ data: base64DecodeArrayBuffer(serialized.data)
156
62
  };
157
63
  }
64
+ function deserializeImageData(serialized) {
65
+ const data = base64DecodeArrayBuffer(serialized.data);
66
+ return new ImageData(data, serialized.width, serialized.height);
67
+ }
68
+ function deserializeNullableImageData(serialized) {
69
+ if (!serialized) return null;
70
+ return deserializeImageData(serialized);
71
+ }
158
72
  // Annotate the CommonJS export names for ESM import in node:
159
73
  0 && (module.exports = {
160
- createPiniaSimplePersist,
161
- makeSimplePersistMapper
74
+ base64DecodeArrayBuffer,
75
+ base64EncodeArrayBuffer,
76
+ deserializeImageData,
77
+ deserializeNullableImageData,
78
+ deserializeRawImageData,
79
+ serializeImageData,
80
+ serializeNullableImageData
162
81
  });
163
82
  //# sourceMappingURL=index.dev.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/plugin.ts","../src/mapper.ts"],"sourcesContent":["export * from './plugin'\nexport { makeSimplePersistMapper } from './mapper'\n","import type { PiniaPluginContext } from 'pinia'\nimport 'pinia'\nimport type { Serializer, StorageLike } from './types'\n\nexport type BaseSimplePersistOptions<Serialized> = {\n storage?: StorageLike\n serializer?: Serializer<Serialized>,\n debounce?: number\n beforeRestore?: (context: PiniaPluginContext) => void\n afterRestore?: (context: PiniaPluginContext) => void,\n onRestoreError?: (err: Error) => void,\n}\n\nexport type GlobalSimplePersistOptions = BaseSimplePersistOptions<any> & {\n makeKey?: (storeId: string) => string,\n}\n\nexport type SimplePersistOptions<Serialized> = BaseSimplePersistOptions<Serialized> & {\n key?: string,\n}\n\nexport function createPiniaSimplePersist(globalOptions: GlobalSimplePersistOptions = {}) {\n const makeKey = globalOptions.makeKey ?? ((id: string) => `pinia-${id}`)\n\n return (context: PiniaPluginContext) => {\n const { store, options } = context\n\n ensureStoreHasRestoreState(store)\n ensureStoreHasSerializeState(store)\n\n const persist = options.persist as SimplePersistOptions<any> | undefined\n if (!persist) return\n\n const {\n key = makeKey(store.$id),\n storage = localStorage,\n serializer = {\n serialize: JSON.stringify,\n deserialize: JSON.parse,\n },\n debounce = 0,\n beforeRestore,\n afterRestore,\n onRestoreError,\n } = { ...globalOptions, ...persist }\n\n const restoreState = () => {\n beforeRestore?.(context)\n\n const stored = storage.getItem(key)\n if (!stored) return\n\n try {\n const data = serializer.deserialize(stored)\n store.$restoreState(data)\n afterRestore?.(context)\n } catch (error) {\n if (onRestoreError) {\n onRestoreError(error instanceof Error ? error : new Error(String(error)))\n } else {\n throw error\n }\n }\n }\n\n const saveState = () => {\n const state = store.$serializeState()\n const serialized = serializer.serialize(state)\n storage.setItem(key, serialized)\n }\n\n let finalSave = saveState\n let cleanupDebounce: (() => void) | undefined\n\n if (debounce) {\n const { debouncedFn, cleanup } = makeDebounce(saveState, debounce)\n finalSave = debouncedFn\n cleanupDebounce = cleanup\n }\n\n // Watch for changes and persist\n const unsubscribe = store.$subscribe(() => {\n finalSave()\n }, { detached: true })\n\n // Cleanup on store disposal\n const originalDispose = store.$dispose\n store.$dispose = function() {\n unsubscribe()\n cleanupDebounce?.()\n originalDispose.call(this)\n }\n\n // Restore state on initialization\n restoreState()\n }\n}\n\nconst makeDebounce = <T extends () => void>(\n callback: T,\n waitFor: number,\n) => {\n let timeout: ReturnType<typeof setTimeout> | undefined\n\n const debouncedFn = (): void => {\n if (timeout) clearTimeout(timeout)\n timeout = setTimeout(() => {\n callback()\n }, waitFor)\n }\n\n const cleanup = (): void => {\n if (timeout) {\n clearTimeout(timeout)\n timeout = undefined\n }\n }\n\n return { debouncedFn, cleanup }\n}\n\nfunction ensureStoreHasRestoreState<T>(store: T): asserts store is T & { $restoreState: (data: any) => void } {\n if (typeof (store as any).$restoreState !== 'function') {\n throw new Error('A store using pinia-simple-persist must have a $restoreState() method')\n }\n}\n\nfunction ensureStoreHasSerializeState<T>(store: T): asserts store is T & { $serializeState: () => any } {\n if (typeof (store as any).$serializeState !== 'function') {\n throw new Error('A store using pinia-simple-persist must have a $serializeState() method')\n }\n}\n","import type { Ref } from 'vue'\nimport { toRaw, isRef, isReactive } from 'vue'\n\ntype RefOrReactive<T> = Ref<T> | T\n\nexport function makeSimplePersistMapper<T extends Record<string, any>>(\n state: { [K in keyof T]: RefOrReactive<T[K]> },\n defaults: T,\n) {\n return {\n $serializeState(): T {\n const out = {} as T\n for (const key in state) {\n const item = state[key]\n let value: any\n\n if (isRef(item)) {\n value = item.value\n } else {\n value = item\n }\n\n out[key] = isReactive(value) ? toRaw(value) : value\n }\n return out\n },\n\n $restoreState(data: T) {\n for (const key in state) {\n if (!(key in data)) continue\n\n const item = state[key]\n\n if (isRef(item)) {\n item.value = data[key]\n } else if (isReactive(item)) {\n Object.assign(item, data[key])\n }\n }\n },\n\n $reset() {\n for (const key in state) {\n const item = state[key]\n\n if (isRef(item)) {\n item.value = defaults[key]\n } else if (isReactive(item)) {\n Object.assign(item, defaults[key])\n }\n }\n },\n }\n}"],"mappings":"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","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,135 +1,49 @@
1
- // src/plugin.ts
2
- import "pinia";
3
- function createPiniaSimplePersist(globalOptions = {}) {
4
- const makeKey = globalOptions.makeKey ?? ((id) => `pinia-${id}`);
5
- return (context) => {
6
- const { store, options } = context;
7
- ensureStoreHasRestoreState(store);
8
- ensureStoreHasSerializeState(store);
9
- const persist = options.persist;
10
- if (!persist) return;
11
- const {
12
- key = makeKey(store.$id),
13
- storage = localStorage,
14
- serializer = {
15
- serialize: JSON.stringify,
16
- deserialize: JSON.parse
17
- },
18
- debounce = 0,
19
- beforeRestore,
20
- afterRestore,
21
- onRestoreError
22
- } = { ...globalOptions, ...persist };
23
- const restoreState = () => {
24
- beforeRestore?.(context);
25
- const stored = storage.getItem(key);
26
- if (!stored) return;
27
- try {
28
- const data = serializer.deserialize(stored);
29
- store.$restoreState(data);
30
- afterRestore?.(context);
31
- } catch (error) {
32
- if (onRestoreError) {
33
- onRestoreError(error instanceof Error ? error : new Error(String(error)));
34
- } else {
35
- throw error;
36
- }
37
- }
38
- };
39
- const saveState = () => {
40
- const state = store.$serializeState();
41
- const serialized = serializer.serialize(state);
42
- storage.setItem(key, serialized);
43
- };
44
- let finalSave = saveState;
45
- let cleanupDebounce;
46
- if (debounce) {
47
- const { debouncedFn, cleanup } = makeDebounce(saveState, debounce);
48
- finalSave = debouncedFn;
49
- cleanupDebounce = cleanup;
50
- }
51
- const unsubscribe = store.$subscribe(() => {
52
- finalSave();
53
- }, { detached: true });
54
- const originalDispose = store.$dispose;
55
- store.$dispose = function() {
56
- unsubscribe();
57
- cleanupDebounce?.();
58
- originalDispose.call(this);
59
- };
60
- restoreState();
61
- };
1
+ // src/ImageData/serialization.ts
2
+ function base64EncodeArrayBuffer(buffer) {
3
+ const binary = String.fromCharCode(...new Uint8Array(buffer));
4
+ return btoa(binary);
62
5
  }
63
- var makeDebounce = (callback, waitFor) => {
64
- let timeout;
65
- const debouncedFn = () => {
66
- if (timeout) clearTimeout(timeout);
67
- timeout = setTimeout(() => {
68
- callback();
69
- }, waitFor);
70
- };
71
- const cleanup = () => {
72
- if (timeout) {
73
- clearTimeout(timeout);
74
- timeout = void 0;
75
- }
76
- };
77
- return { debouncedFn, cleanup };
78
- };
79
- function ensureStoreHasRestoreState(store) {
80
- if (typeof store.$restoreState !== "function") {
81
- throw new Error("A store using pinia-simple-persist must have a $restoreState() method");
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);
82
11
  }
12
+ return bytes;
83
13
  }
84
- function ensureStoreHasSerializeState(store) {
85
- if (typeof store.$serializeState !== "function") {
86
- throw new Error("A store using pinia-simple-persist must have a $serializeState() method");
87
- }
14
+ function serializeImageData(imageData) {
15
+ return {
16
+ width: imageData.width,
17
+ height: imageData.height,
18
+ data: base64EncodeArrayBuffer(imageData.data.buffer)
19
+ };
20
+ }
21
+ function serializeNullableImageData(imageData) {
22
+ if (!imageData) return null;
23
+ return serializeImageData(imageData);
88
24
  }
89
-
90
- // src/mapper.ts
91
- import { toRaw, isRef, isReactive } from "vue";
92
- function makeSimplePersistMapper(state, defaults) {
25
+ function deserializeRawImageData(serialized) {
93
26
  return {
94
- $serializeState() {
95
- const out = {};
96
- for (const key in state) {
97
- const item = state[key];
98
- let value;
99
- if (isRef(item)) {
100
- value = item.value;
101
- } else {
102
- value = item;
103
- }
104
- out[key] = isReactive(value) ? toRaw(value) : value;
105
- }
106
- return out;
107
- },
108
- $restoreState(data) {
109
- for (const key in state) {
110
- if (!(key in data)) continue;
111
- const item = state[key];
112
- if (isRef(item)) {
113
- item.value = data[key];
114
- } else if (isReactive(item)) {
115
- Object.assign(item, data[key]);
116
- }
117
- }
118
- },
119
- $reset() {
120
- for (const key in state) {
121
- const item = state[key];
122
- if (isRef(item)) {
123
- item.value = defaults[key];
124
- } else if (isReactive(item)) {
125
- Object.assign(item, defaults[key]);
126
- }
127
- }
128
- }
27
+ width: serialized.width,
28
+ height: serialized.height,
29
+ data: base64DecodeArrayBuffer(serialized.data)
129
30
  };
130
31
  }
32
+ function deserializeImageData(serialized) {
33
+ const data = base64DecodeArrayBuffer(serialized.data);
34
+ return new ImageData(data, serialized.width, serialized.height);
35
+ }
36
+ function deserializeNullableImageData(serialized) {
37
+ if (!serialized) return null;
38
+ return deserializeImageData(serialized);
39
+ }
131
40
  export {
132
- createPiniaSimplePersist,
133
- makeSimplePersistMapper
41
+ base64DecodeArrayBuffer,
42
+ base64EncodeArrayBuffer,
43
+ deserializeImageData,
44
+ deserializeNullableImageData,
45
+ deserializeRawImageData,
46
+ serializeImageData,
47
+ serializeNullableImageData
134
48
  };
135
49
  //# sourceMappingURL=index.dev.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/plugin.ts","../src/mapper.ts"],"sourcesContent":["import type { PiniaPluginContext } from 'pinia'\nimport 'pinia'\nimport type { Serializer, StorageLike } from './types'\n\nexport type BaseSimplePersistOptions<Serialized> = {\n storage?: StorageLike\n serializer?: Serializer<Serialized>,\n debounce?: number\n beforeRestore?: (context: PiniaPluginContext) => void\n afterRestore?: (context: PiniaPluginContext) => void,\n onRestoreError?: (err: Error) => void,\n}\n\nexport type GlobalSimplePersistOptions = BaseSimplePersistOptions<any> & {\n makeKey?: (storeId: string) => string,\n}\n\nexport type SimplePersistOptions<Serialized> = BaseSimplePersistOptions<Serialized> & {\n key?: string,\n}\n\nexport function createPiniaSimplePersist(globalOptions: GlobalSimplePersistOptions = {}) {\n const makeKey = globalOptions.makeKey ?? ((id: string) => `pinia-${id}`)\n\n return (context: PiniaPluginContext) => {\n const { store, options } = context\n\n ensureStoreHasRestoreState(store)\n ensureStoreHasSerializeState(store)\n\n const persist = options.persist as SimplePersistOptions<any> | undefined\n if (!persist) return\n\n const {\n key = makeKey(store.$id),\n storage = localStorage,\n serializer = {\n serialize: JSON.stringify,\n deserialize: JSON.parse,\n },\n debounce = 0,\n beforeRestore,\n afterRestore,\n onRestoreError,\n } = { ...globalOptions, ...persist }\n\n const restoreState = () => {\n beforeRestore?.(context)\n\n const stored = storage.getItem(key)\n if (!stored) return\n\n try {\n const data = serializer.deserialize(stored)\n store.$restoreState(data)\n afterRestore?.(context)\n } catch (error) {\n if (onRestoreError) {\n onRestoreError(error instanceof Error ? error : new Error(String(error)))\n } else {\n throw error\n }\n }\n }\n\n const saveState = () => {\n const state = store.$serializeState()\n const serialized = serializer.serialize(state)\n storage.setItem(key, serialized)\n }\n\n let finalSave = saveState\n let cleanupDebounce: (() => void) | undefined\n\n if (debounce) {\n const { debouncedFn, cleanup } = makeDebounce(saveState, debounce)\n finalSave = debouncedFn\n cleanupDebounce = cleanup\n }\n\n // Watch for changes and persist\n const unsubscribe = store.$subscribe(() => {\n finalSave()\n }, { detached: true })\n\n // Cleanup on store disposal\n const originalDispose = store.$dispose\n store.$dispose = function() {\n unsubscribe()\n cleanupDebounce?.()\n originalDispose.call(this)\n }\n\n // Restore state on initialization\n restoreState()\n }\n}\n\nconst makeDebounce = <T extends () => void>(\n callback: T,\n waitFor: number,\n) => {\n let timeout: ReturnType<typeof setTimeout> | undefined\n\n const debouncedFn = (): void => {\n if (timeout) clearTimeout(timeout)\n timeout = setTimeout(() => {\n callback()\n }, waitFor)\n }\n\n const cleanup = (): void => {\n if (timeout) {\n clearTimeout(timeout)\n timeout = undefined\n }\n }\n\n return { debouncedFn, cleanup }\n}\n\nfunction ensureStoreHasRestoreState<T>(store: T): asserts store is T & { $restoreState: (data: any) => void } {\n if (typeof (store as any).$restoreState !== 'function') {\n throw new Error('A store using pinia-simple-persist must have a $restoreState() method')\n }\n}\n\nfunction ensureStoreHasSerializeState<T>(store: T): asserts store is T & { $serializeState: () => any } {\n if (typeof (store as any).$serializeState !== 'function') {\n throw new Error('A store using pinia-simple-persist must have a $serializeState() method')\n }\n}\n","import type { Ref } from 'vue'\nimport { toRaw, isRef, isReactive } from 'vue'\n\ntype RefOrReactive<T> = Ref<T> | T\n\nexport function makeSimplePersistMapper<T extends Record<string, any>>(\n state: { [K in keyof T]: RefOrReactive<T[K]> },\n defaults: T,\n) {\n return {\n $serializeState(): T {\n const out = {} as T\n for (const key in state) {\n const item = state[key]\n let value: any\n\n if (isRef(item)) {\n value = item.value\n } else {\n value = item\n }\n\n out[key] = isReactive(value) ? toRaw(value) : value\n }\n return out\n },\n\n $restoreState(data: T) {\n for (const key in state) {\n if (!(key in data)) continue\n\n const item = state[key]\n\n if (isRef(item)) {\n item.value = data[key]\n } else if (isReactive(item)) {\n Object.assign(item, data[key])\n }\n }\n },\n\n $reset() {\n for (const key in state) {\n const item = state[key]\n\n if (isRef(item)) {\n item.value = defaults[key]\n } else if (isReactive(item)) {\n Object.assign(item, defaults[key])\n }\n }\n },\n }\n}"],"mappings":"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","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,144 +20,63 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var src_exports = {};
22
22
  __export(src_exports, {
23
- createPiniaSimplePersist: () => createPiniaSimplePersist,
24
- makeSimplePersistMapper: () => makeSimplePersistMapper
23
+ base64DecodeArrayBuffer: () => base64DecodeArrayBuffer,
24
+ base64EncodeArrayBuffer: () => base64EncodeArrayBuffer,
25
+ deserializeImageData: () => deserializeImageData,
26
+ deserializeNullableImageData: () => deserializeNullableImageData,
27
+ deserializeRawImageData: () => deserializeRawImageData,
28
+ serializeImageData: () => serializeImageData,
29
+ serializeNullableImageData: () => serializeNullableImageData
25
30
  });
26
31
  module.exports = __toCommonJS(src_exports);
27
32
 
28
- // src/plugin.ts
29
- var import_pinia = require("pinia");
30
- function createPiniaSimplePersist(globalOptions = {}) {
31
- const makeKey = globalOptions.makeKey ?? ((id) => `pinia-${id}`);
32
- return (context) => {
33
- const { store, options } = context;
34
- ensureStoreHasRestoreState(store);
35
- ensureStoreHasSerializeState(store);
36
- const persist = options.persist;
37
- if (!persist) return;
38
- const {
39
- key = makeKey(store.$id),
40
- storage = localStorage,
41
- serializer = {
42
- serialize: JSON.stringify,
43
- deserialize: JSON.parse
44
- },
45
- debounce = 0,
46
- beforeRestore,
47
- afterRestore,
48
- onRestoreError
49
- } = { ...globalOptions, ...persist };
50
- const restoreState = () => {
51
- beforeRestore?.(context);
52
- const stored = storage.getItem(key);
53
- if (!stored) return;
54
- try {
55
- const data = serializer.deserialize(stored);
56
- store.$restoreState(data);
57
- afterRestore?.(context);
58
- } catch (error) {
59
- if (onRestoreError) {
60
- onRestoreError(error instanceof Error ? error : new Error(String(error)));
61
- } else {
62
- throw error;
63
- }
64
- }
65
- };
66
- const saveState = () => {
67
- const state = store.$serializeState();
68
- const serialized = serializer.serialize(state);
69
- storage.setItem(key, serialized);
70
- };
71
- let finalSave = saveState;
72
- let cleanupDebounce;
73
- if (debounce) {
74
- const { debouncedFn, cleanup } = makeDebounce(saveState, debounce);
75
- finalSave = debouncedFn;
76
- cleanupDebounce = cleanup;
77
- }
78
- const unsubscribe = store.$subscribe(() => {
79
- finalSave();
80
- }, { detached: true });
81
- const originalDispose = store.$dispose;
82
- store.$dispose = function() {
83
- unsubscribe();
84
- cleanupDebounce?.();
85
- originalDispose.call(this);
86
- };
87
- restoreState();
88
- };
33
+ // src/ImageData/serialization.ts
34
+ function base64EncodeArrayBuffer(buffer) {
35
+ const binary = String.fromCharCode(...new Uint8Array(buffer));
36
+ return btoa(binary);
89
37
  }
90
- var makeDebounce = (callback, waitFor) => {
91
- let timeout;
92
- const debouncedFn = () => {
93
- if (timeout) clearTimeout(timeout);
94
- timeout = setTimeout(() => {
95
- callback();
96
- }, waitFor);
97
- };
98
- const cleanup = () => {
99
- if (timeout) {
100
- clearTimeout(timeout);
101
- timeout = void 0;
102
- }
103
- };
104
- return { debouncedFn, cleanup };
105
- };
106
- function ensureStoreHasRestoreState(store) {
107
- if (typeof store.$restoreState !== "function") {
108
- throw new Error("A store using pinia-simple-persist must have a $restoreState() method");
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);
109
43
  }
44
+ return bytes;
110
45
  }
111
- function ensureStoreHasSerializeState(store) {
112
- if (typeof store.$serializeState !== "function") {
113
- throw new Error("A store using pinia-simple-persist must have a $serializeState() method");
114
- }
46
+ function serializeImageData(imageData) {
47
+ return {
48
+ width: imageData.width,
49
+ height: imageData.height,
50
+ data: base64EncodeArrayBuffer(imageData.data.buffer)
51
+ };
115
52
  }
116
-
117
- // src/mapper.ts
118
- var import_vue = require("vue");
119
- function makeSimplePersistMapper(state, defaults) {
53
+ function serializeNullableImageData(imageData) {
54
+ if (!imageData) return null;
55
+ return serializeImageData(imageData);
56
+ }
57
+ function deserializeRawImageData(serialized) {
120
58
  return {
121
- $serializeState() {
122
- const out = {};
123
- for (const key in state) {
124
- const item = state[key];
125
- let value;
126
- if ((0, import_vue.isRef)(item)) {
127
- value = item.value;
128
- } else {
129
- value = item;
130
- }
131
- out[key] = (0, import_vue.isReactive)(value) ? (0, import_vue.toRaw)(value) : value;
132
- }
133
- return out;
134
- },
135
- $restoreState(data) {
136
- for (const key in state) {
137
- if (!(key in data)) continue;
138
- const item = state[key];
139
- if ((0, import_vue.isRef)(item)) {
140
- item.value = data[key];
141
- } else if ((0, import_vue.isReactive)(item)) {
142
- Object.assign(item, data[key]);
143
- }
144
- }
145
- },
146
- $reset() {
147
- for (const key in state) {
148
- const item = state[key];
149
- if ((0, import_vue.isRef)(item)) {
150
- item.value = defaults[key];
151
- } else if ((0, import_vue.isReactive)(item)) {
152
- Object.assign(item, defaults[key]);
153
- }
154
- }
155
- }
59
+ width: serialized.width,
60
+ height: serialized.height,
61
+ data: base64DecodeArrayBuffer(serialized.data)
156
62
  };
157
63
  }
64
+ function deserializeImageData(serialized) {
65
+ const data = base64DecodeArrayBuffer(serialized.data);
66
+ return new ImageData(data, serialized.width, serialized.height);
67
+ }
68
+ function deserializeNullableImageData(serialized) {
69
+ if (!serialized) return null;
70
+ return deserializeImageData(serialized);
71
+ }
158
72
  // Annotate the CommonJS export names for ESM import in node:
159
73
  0 && (module.exports = {
160
- createPiniaSimplePersist,
161
- makeSimplePersistMapper
74
+ base64DecodeArrayBuffer,
75
+ base64EncodeArrayBuffer,
76
+ deserializeImageData,
77
+ deserializeNullableImageData,
78
+ deserializeRawImageData,
79
+ serializeImageData,
80
+ serializeNullableImageData
162
81
  });
163
82
  //# sourceMappingURL=index.prod.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/plugin.ts","../src/mapper.ts"],"sourcesContent":["export * from './plugin'\nexport { makeSimplePersistMapper } from './mapper'\n","import type { PiniaPluginContext } from 'pinia'\nimport 'pinia'\nimport type { Serializer, StorageLike } from './types'\n\nexport type BaseSimplePersistOptions<Serialized> = {\n storage?: StorageLike\n serializer?: Serializer<Serialized>,\n debounce?: number\n beforeRestore?: (context: PiniaPluginContext) => void\n afterRestore?: (context: PiniaPluginContext) => void,\n onRestoreError?: (err: Error) => void,\n}\n\nexport type GlobalSimplePersistOptions = BaseSimplePersistOptions<any> & {\n makeKey?: (storeId: string) => string,\n}\n\nexport type SimplePersistOptions<Serialized> = BaseSimplePersistOptions<Serialized> & {\n key?: string,\n}\n\nexport function createPiniaSimplePersist(globalOptions: GlobalSimplePersistOptions = {}) {\n const makeKey = globalOptions.makeKey ?? ((id: string) => `pinia-${id}`)\n\n return (context: PiniaPluginContext) => {\n const { store, options } = context\n\n ensureStoreHasRestoreState(store)\n ensureStoreHasSerializeState(store)\n\n const persist = options.persist as SimplePersistOptions<any> | undefined\n if (!persist) return\n\n const {\n key = makeKey(store.$id),\n storage = localStorage,\n serializer = {\n serialize: JSON.stringify,\n deserialize: JSON.parse,\n },\n debounce = 0,\n beforeRestore,\n afterRestore,\n onRestoreError,\n } = { ...globalOptions, ...persist }\n\n const restoreState = () => {\n beforeRestore?.(context)\n\n const stored = storage.getItem(key)\n if (!stored) return\n\n try {\n const data = serializer.deserialize(stored)\n store.$restoreState(data)\n afterRestore?.(context)\n } catch (error) {\n if (onRestoreError) {\n onRestoreError(error instanceof Error ? error : new Error(String(error)))\n } else {\n throw error\n }\n }\n }\n\n const saveState = () => {\n const state = store.$serializeState()\n const serialized = serializer.serialize(state)\n storage.setItem(key, serialized)\n }\n\n let finalSave = saveState\n let cleanupDebounce: (() => void) | undefined\n\n if (debounce) {\n const { debouncedFn, cleanup } = makeDebounce(saveState, debounce)\n finalSave = debouncedFn\n cleanupDebounce = cleanup\n }\n\n // Watch for changes and persist\n const unsubscribe = store.$subscribe(() => {\n finalSave()\n }, { detached: true })\n\n // Cleanup on store disposal\n const originalDispose = store.$dispose\n store.$dispose = function() {\n unsubscribe()\n cleanupDebounce?.()\n originalDispose.call(this)\n }\n\n // Restore state on initialization\n restoreState()\n }\n}\n\nconst makeDebounce = <T extends () => void>(\n callback: T,\n waitFor: number,\n) => {\n let timeout: ReturnType<typeof setTimeout> | undefined\n\n const debouncedFn = (): void => {\n if (timeout) clearTimeout(timeout)\n timeout = setTimeout(() => {\n callback()\n }, waitFor)\n }\n\n const cleanup = (): void => {\n if (timeout) {\n clearTimeout(timeout)\n timeout = undefined\n }\n }\n\n return { debouncedFn, cleanup }\n}\n\nfunction ensureStoreHasRestoreState<T>(store: T): asserts store is T & { $restoreState: (data: any) => void } {\n if (typeof (store as any).$restoreState !== 'function') {\n throw new Error('A store using pinia-simple-persist must have a $restoreState() method')\n }\n}\n\nfunction ensureStoreHasSerializeState<T>(store: T): asserts store is T & { $serializeState: () => any } {\n if (typeof (store as any).$serializeState !== 'function') {\n throw new Error('A store using pinia-simple-persist must have a $serializeState() method')\n }\n}\n","import type { Ref } from 'vue'\nimport { toRaw, isRef, isReactive } from 'vue'\n\ntype RefOrReactive<T> = Ref<T> | T\n\nexport function makeSimplePersistMapper<T extends Record<string, any>>(\n state: { [K in keyof T]: RefOrReactive<T[K]> },\n defaults: T,\n) {\n return {\n $serializeState(): T {\n const out = {} as T\n for (const key in state) {\n const item = state[key]\n let value: any\n\n if (isRef(item)) {\n value = item.value\n } else {\n value = item\n }\n\n out[key] = isReactive(value) ? toRaw(value) : value\n }\n return out\n },\n\n $restoreState(data: T) {\n for (const key in state) {\n if (!(key in data)) continue\n\n const item = state[key]\n\n if (isRef(item)) {\n item.value = data[key]\n } else if (isReactive(item)) {\n Object.assign(item, data[key])\n }\n }\n },\n\n $reset() {\n for (const key in state) {\n const item = state[key]\n\n if (isRef(item)) {\n item.value = defaults[key]\n } else if (isReactive(item)) {\n Object.assign(item, defaults[key])\n }\n }\n },\n }\n}"],"mappings":"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","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,68 +1,26 @@
1
- import { PiniaPluginContext } from 'pinia';
2
- import { Ref } from 'vue';
3
-
4
- /**
5
- * Synchronous storage based on Web Storage API.
6
- * @see https://developer.mozilla.org/en-US/docs/Web/API/Storage
7
- */
8
- interface StorageLike {
9
- /**
10
- * Get a key's value if it exists.
11
- */
12
- getItem: (key: string) => string | null;
13
- /**
14
- * Set a key with a value, or update it if it exists.
15
- */
16
- setItem: (key: string, value: string) => void;
17
- }
18
- interface Serializer<T> {
19
- /**
20
- * Serialize state into string before storing.
21
- * @default JSON.stringify
22
- * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
23
- */
24
- serialize: (data: T) => string;
25
- /**
26
- * Deserializes string into state before hydrating.
27
- * @default JSON.parse
28
- * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
29
- */
30
- deserialize: (data: string) => T;
31
- }
32
- declare module 'pinia' {
33
- interface DefineStoreOptionsBase<S, Store> {
34
- persist?: SimplePersistOptions<any> | boolean;
35
- }
36
- interface PiniaCustomProperties {
37
- $persist: () => void;
38
- $restoreState?: (data: any) => void;
39
- $serializeState?: () => any;
40
- }
41
- }
42
-
43
- type BaseSimplePersistOptions<Serialized> = {
44
- storage?: StorageLike;
45
- serializer?: Serializer<Serialized>;
46
- debounce?: number;
47
- beforeRestore?: (context: PiniaPluginContext) => void;
48
- afterRestore?: (context: PiniaPluginContext) => void;
49
- onRestoreError?: (err: Error) => void;
1
+ type ImageDataLike = {
2
+ width: number;
3
+ height: number;
4
+ data: Uint8ClampedArray;
50
5
  };
51
- type GlobalSimplePersistOptions = BaseSimplePersistOptions<any> & {
52
- makeKey?: (storeId: string) => string;
6
+ type SerializedImageData = {
7
+ width: number;
8
+ height: number;
9
+ data: string;
53
10
  };
54
- type SimplePersistOptions<Serialized> = BaseSimplePersistOptions<Serialized> & {
55
- key?: string;
11
+ type Base64EncodedUInt8Array = string & {
12
+ readonly __brandBase64UInt8Array: unique symbol;
56
13
  };
57
- declare function createPiniaSimplePersist(globalOptions?: GlobalSimplePersistOptions): (context: PiniaPluginContext) => void;
58
14
 
59
- type RefOrReactive<T> = Ref<T> | T;
60
- declare function makeSimplePersistMapper<T extends Record<string, any>>(state: {
61
- [K in keyof T]: RefOrReactive<T[K]>;
62
- }, defaults: T): {
63
- $serializeState(): T;
64
- $restoreState(data: T): void;
65
- $reset(): void;
66
- };
15
+ declare function base64EncodeArrayBuffer(buffer: ArrayBufferLike): Base64EncodedUInt8Array;
16
+ declare function base64DecodeArrayBuffer(encoded: Base64EncodedUInt8Array): Uint8ClampedArray;
17
+ /**
18
+ * Serialize for use in JSON. Pixel data is stored as base64 encoded string.
19
+ */
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;
23
+ declare function deserializeImageData<T extends SerializedImageData>(serialized: T): ImageData;
24
+ declare function deserializeNullableImageData<T extends SerializedImageData | null>(serialized: T): T extends null ? null : ImageData;
67
25
 
68
- export { type BaseSimplePersistOptions, type GlobalSimplePersistOptions, type SimplePersistOptions, createPiniaSimplePersist, makeSimplePersistMapper };
26
+ export { base64DecodeArrayBuffer, base64EncodeArrayBuffer, deserializeImageData, deserializeNullableImageData, deserializeRawImageData, serializeImageData, serializeNullableImageData };
@@ -1,135 +1,49 @@
1
- // src/plugin.ts
2
- import "pinia";
3
- function createPiniaSimplePersist(globalOptions = {}) {
4
- const makeKey = globalOptions.makeKey ?? ((id) => `pinia-${id}`);
5
- return (context) => {
6
- const { store, options } = context;
7
- ensureStoreHasRestoreState(store);
8
- ensureStoreHasSerializeState(store);
9
- const persist = options.persist;
10
- if (!persist) return;
11
- const {
12
- key = makeKey(store.$id),
13
- storage = localStorage,
14
- serializer = {
15
- serialize: JSON.stringify,
16
- deserialize: JSON.parse
17
- },
18
- debounce = 0,
19
- beforeRestore,
20
- afterRestore,
21
- onRestoreError
22
- } = { ...globalOptions, ...persist };
23
- const restoreState = () => {
24
- beforeRestore?.(context);
25
- const stored = storage.getItem(key);
26
- if (!stored) return;
27
- try {
28
- const data = serializer.deserialize(stored);
29
- store.$restoreState(data);
30
- afterRestore?.(context);
31
- } catch (error) {
32
- if (onRestoreError) {
33
- onRestoreError(error instanceof Error ? error : new Error(String(error)));
34
- } else {
35
- throw error;
36
- }
37
- }
38
- };
39
- const saveState = () => {
40
- const state = store.$serializeState();
41
- const serialized = serializer.serialize(state);
42
- storage.setItem(key, serialized);
43
- };
44
- let finalSave = saveState;
45
- let cleanupDebounce;
46
- if (debounce) {
47
- const { debouncedFn, cleanup } = makeDebounce(saveState, debounce);
48
- finalSave = debouncedFn;
49
- cleanupDebounce = cleanup;
50
- }
51
- const unsubscribe = store.$subscribe(() => {
52
- finalSave();
53
- }, { detached: true });
54
- const originalDispose = store.$dispose;
55
- store.$dispose = function() {
56
- unsubscribe();
57
- cleanupDebounce?.();
58
- originalDispose.call(this);
59
- };
60
- restoreState();
61
- };
1
+ // src/ImageData/serialization.ts
2
+ function base64EncodeArrayBuffer(buffer) {
3
+ const binary = String.fromCharCode(...new Uint8Array(buffer));
4
+ return btoa(binary);
62
5
  }
63
- var makeDebounce = (callback, waitFor) => {
64
- let timeout;
65
- const debouncedFn = () => {
66
- if (timeout) clearTimeout(timeout);
67
- timeout = setTimeout(() => {
68
- callback();
69
- }, waitFor);
70
- };
71
- const cleanup = () => {
72
- if (timeout) {
73
- clearTimeout(timeout);
74
- timeout = void 0;
75
- }
76
- };
77
- return { debouncedFn, cleanup };
78
- };
79
- function ensureStoreHasRestoreState(store) {
80
- if (typeof store.$restoreState !== "function") {
81
- throw new Error("A store using pinia-simple-persist must have a $restoreState() method");
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);
82
11
  }
12
+ return bytes;
83
13
  }
84
- function ensureStoreHasSerializeState(store) {
85
- if (typeof store.$serializeState !== "function") {
86
- throw new Error("A store using pinia-simple-persist must have a $serializeState() method");
87
- }
14
+ function serializeImageData(imageData) {
15
+ return {
16
+ width: imageData.width,
17
+ height: imageData.height,
18
+ data: base64EncodeArrayBuffer(imageData.data.buffer)
19
+ };
20
+ }
21
+ function serializeNullableImageData(imageData) {
22
+ if (!imageData) return null;
23
+ return serializeImageData(imageData);
88
24
  }
89
-
90
- // src/mapper.ts
91
- import { toRaw, isRef, isReactive } from "vue";
92
- function makeSimplePersistMapper(state, defaults) {
25
+ function deserializeRawImageData(serialized) {
93
26
  return {
94
- $serializeState() {
95
- const out = {};
96
- for (const key in state) {
97
- const item = state[key];
98
- let value;
99
- if (isRef(item)) {
100
- value = item.value;
101
- } else {
102
- value = item;
103
- }
104
- out[key] = isReactive(value) ? toRaw(value) : value;
105
- }
106
- return out;
107
- },
108
- $restoreState(data) {
109
- for (const key in state) {
110
- if (!(key in data)) continue;
111
- const item = state[key];
112
- if (isRef(item)) {
113
- item.value = data[key];
114
- } else if (isReactive(item)) {
115
- Object.assign(item, data[key]);
116
- }
117
- }
118
- },
119
- $reset() {
120
- for (const key in state) {
121
- const item = state[key];
122
- if (isRef(item)) {
123
- item.value = defaults[key];
124
- } else if (isReactive(item)) {
125
- Object.assign(item, defaults[key]);
126
- }
127
- }
128
- }
27
+ width: serialized.width,
28
+ height: serialized.height,
29
+ data: base64DecodeArrayBuffer(serialized.data)
129
30
  };
130
31
  }
32
+ function deserializeImageData(serialized) {
33
+ const data = base64DecodeArrayBuffer(serialized.data);
34
+ return new ImageData(data, serialized.width, serialized.height);
35
+ }
36
+ function deserializeNullableImageData(serialized) {
37
+ if (!serialized) return null;
38
+ return deserializeImageData(serialized);
39
+ }
131
40
  export {
132
- createPiniaSimplePersist,
133
- makeSimplePersistMapper
41
+ base64DecodeArrayBuffer,
42
+ base64EncodeArrayBuffer,
43
+ deserializeImageData,
44
+ deserializeNullableImageData,
45
+ deserializeRawImageData,
46
+ serializeImageData,
47
+ serializeNullableImageData
134
48
  };
135
49
  //# sourceMappingURL=index.prod.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/plugin.ts","../src/mapper.ts"],"sourcesContent":["import type { PiniaPluginContext } from 'pinia'\nimport 'pinia'\nimport type { Serializer, StorageLike } from './types'\n\nexport type BaseSimplePersistOptions<Serialized> = {\n storage?: StorageLike\n serializer?: Serializer<Serialized>,\n debounce?: number\n beforeRestore?: (context: PiniaPluginContext) => void\n afterRestore?: (context: PiniaPluginContext) => void,\n onRestoreError?: (err: Error) => void,\n}\n\nexport type GlobalSimplePersistOptions = BaseSimplePersistOptions<any> & {\n makeKey?: (storeId: string) => string,\n}\n\nexport type SimplePersistOptions<Serialized> = BaseSimplePersistOptions<Serialized> & {\n key?: string,\n}\n\nexport function createPiniaSimplePersist(globalOptions: GlobalSimplePersistOptions = {}) {\n const makeKey = globalOptions.makeKey ?? ((id: string) => `pinia-${id}`)\n\n return (context: PiniaPluginContext) => {\n const { store, options } = context\n\n ensureStoreHasRestoreState(store)\n ensureStoreHasSerializeState(store)\n\n const persist = options.persist as SimplePersistOptions<any> | undefined\n if (!persist) return\n\n const {\n key = makeKey(store.$id),\n storage = localStorage,\n serializer = {\n serialize: JSON.stringify,\n deserialize: JSON.parse,\n },\n debounce = 0,\n beforeRestore,\n afterRestore,\n onRestoreError,\n } = { ...globalOptions, ...persist }\n\n const restoreState = () => {\n beforeRestore?.(context)\n\n const stored = storage.getItem(key)\n if (!stored) return\n\n try {\n const data = serializer.deserialize(stored)\n store.$restoreState(data)\n afterRestore?.(context)\n } catch (error) {\n if (onRestoreError) {\n onRestoreError(error instanceof Error ? error : new Error(String(error)))\n } else {\n throw error\n }\n }\n }\n\n const saveState = () => {\n const state = store.$serializeState()\n const serialized = serializer.serialize(state)\n storage.setItem(key, serialized)\n }\n\n let finalSave = saveState\n let cleanupDebounce: (() => void) | undefined\n\n if (debounce) {\n const { debouncedFn, cleanup } = makeDebounce(saveState, debounce)\n finalSave = debouncedFn\n cleanupDebounce = cleanup\n }\n\n // Watch for changes and persist\n const unsubscribe = store.$subscribe(() => {\n finalSave()\n }, { detached: true })\n\n // Cleanup on store disposal\n const originalDispose = store.$dispose\n store.$dispose = function() {\n unsubscribe()\n cleanupDebounce?.()\n originalDispose.call(this)\n }\n\n // Restore state on initialization\n restoreState()\n }\n}\n\nconst makeDebounce = <T extends () => void>(\n callback: T,\n waitFor: number,\n) => {\n let timeout: ReturnType<typeof setTimeout> | undefined\n\n const debouncedFn = (): void => {\n if (timeout) clearTimeout(timeout)\n timeout = setTimeout(() => {\n callback()\n }, waitFor)\n }\n\n const cleanup = (): void => {\n if (timeout) {\n clearTimeout(timeout)\n timeout = undefined\n }\n }\n\n return { debouncedFn, cleanup }\n}\n\nfunction ensureStoreHasRestoreState<T>(store: T): asserts store is T & { $restoreState: (data: any) => void } {\n if (typeof (store as any).$restoreState !== 'function') {\n throw new Error('A store using pinia-simple-persist must have a $restoreState() method')\n }\n}\n\nfunction ensureStoreHasSerializeState<T>(store: T): asserts store is T & { $serializeState: () => any } {\n if (typeof (store as any).$serializeState !== 'function') {\n throw new Error('A store using pinia-simple-persist must have a $serializeState() method')\n }\n}\n","import type { Ref } from 'vue'\nimport { toRaw, isRef, isReactive } from 'vue'\n\ntype RefOrReactive<T> = Ref<T> | T\n\nexport function makeSimplePersistMapper<T extends Record<string, any>>(\n state: { [K in keyof T]: RefOrReactive<T[K]> },\n defaults: T,\n) {\n return {\n $serializeState(): T {\n const out = {} as T\n for (const key in state) {\n const item = state[key]\n let value: any\n\n if (isRef(item)) {\n value = item.value\n } else {\n value = item\n }\n\n out[key] = isReactive(value) ? toRaw(value) : value\n }\n return out\n },\n\n $restoreState(data: T) {\n for (const key in state) {\n if (!(key in data)) continue\n\n const item = state[key]\n\n if (isRef(item)) {\n item.value = data[key]\n } else if (isReactive(item)) {\n Object.assign(item, data[key])\n }\n }\n },\n\n $reset() {\n for (const key in state) {\n const item = state[key]\n\n if (isRef(item)) {\n item.value = defaults[key]\n } else if (isReactive(item)) {\n Object.assign(item, defaults[key])\n }\n }\n },\n }\n}"],"mappings":"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","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.1",
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
+ }