img2num 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/dist/browser/__vite-browser-external-CSF28Lpk.js +1 -0
- package/dist/browser/img2num.js +179 -0
- package/dist/browser/wasmWorker-BVnzGxvB.js +2 -0
- package/{wasmWorker.js → dist/browser/wasmWorker.js} +31 -5
- package/dist/node/img2num.js +514 -0
- package/dist/node/index.js +2 -0
- package/dist/node/index.wasm +0 -0
- package/dist/node/wasmWorker-CpNpexYK.js +4471 -0
- package/dist/node/wasmWorker.js +198 -0
- package/dist/node/webgpu-BjVEVfI9.js +23 -0
- package/package.json +23 -13
- package/{safeWasmWrappers.js → src/safeWasmWrappers.js} +9 -7
- package/src/target/browser/worker.js +10 -0
- package/src/target/node/webgpu.js +35 -0
- package/src/target/node/worker.js +20 -0
- package/{wasmClient.js → src/wasmClient.js} +30 -15
- package/src/workers/wasmWorker.js +243 -0
- package/tsconfig.typedoc.json +23 -0
- package/vite.config.js +74 -0
- package/build-wasm/index.js +0 -14
- package/build-wasm/index.wasm +0 -0
- /package/{imageToUint8ClampedArray.js → src/imageToUint8ClampedArray.js} +0 -0
- /package/{index.js → src/index.js} +0 -0
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
* });
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
|
-
import createImg2NumModule from "
|
|
41
|
+
import createImg2NumModule from "@wasm/index.js";
|
|
42
42
|
|
|
43
43
|
let wasmModule;
|
|
44
44
|
|
|
@@ -154,7 +154,8 @@ async function callWasm(funcName, argsMap, returnType) {
|
|
|
154
154
|
* - returnType: expected return type of the WASM export
|
|
155
155
|
* @param {MessageEvent} event
|
|
156
156
|
*/
|
|
157
|
-
|
|
157
|
+
|
|
158
|
+
async function handleMessage(data) {
|
|
158
159
|
await readyPromise;
|
|
159
160
|
|
|
160
161
|
const { id, funcName, args, bufferKeys, returnType } = data;
|
|
@@ -205,13 +206,38 @@ self.onmessage = async ({ data }) => {
|
|
|
205
206
|
wasmModule._free(result);
|
|
206
207
|
}
|
|
207
208
|
|
|
208
|
-
|
|
209
|
+
globalThis.postMessage({ id, output, returnValue });
|
|
209
210
|
} catch (error) {
|
|
210
|
-
|
|
211
|
+
globalThis.postMessage({ id, error: error.message });
|
|
211
212
|
} finally {
|
|
212
213
|
// -------- Cleanup --------
|
|
213
214
|
for (const { ptr } of pointers.values()) {
|
|
214
215
|
wasmModule._free(ptr);
|
|
215
216
|
}
|
|
216
217
|
}
|
|
217
|
-
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (__TARGET__ === "node") {
|
|
221
|
+
const { parentPort } = await import("node:worker_threads");
|
|
222
|
+
const { initWebGPU, destroyWebGPU } = await import("../target/node/webgpu.js");
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
await initWebGPU();
|
|
226
|
+
} catch (err) {
|
|
227
|
+
console.error(`[Img2Num node/worker.js] Error: ${err}`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// 2. FIX THE TYPO: Polyfill globalThis.postMessage so handleMessage can call it natively!
|
|
231
|
+
globalThis.postMessage = (data) => parentPort.postMessage(data);
|
|
232
|
+
|
|
233
|
+
// 3. Listen for incoming messages from the console app main thread
|
|
234
|
+
// (Node passes the raw payload directly, no nested event wrapper needed)
|
|
235
|
+
parentPort.on("message", async (data) => {
|
|
236
|
+
await handleMessage(data);
|
|
237
|
+
await destroyWebGPU();
|
|
238
|
+
parentPort.close();
|
|
239
|
+
});
|
|
240
|
+
} else {
|
|
241
|
+
// Browser Worker setup: Standard event-unwrapping listener
|
|
242
|
+
globalThis.onmessage = ({ data }) => handleMessage(data);
|
|
243
|
+
}
|
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
import { Worker } from "node:worker_threads";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
//#region src/imageToUint8ClampedArray.js
|
|
5
|
+
/**
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
* Convenience image conversion utility to ensure type compatibility with the library.
|
|
8
|
+
*
|
|
9
|
+
* @file Convenience utility function.
|
|
10
|
+
*
|
|
11
|
+
* @module image-utils
|
|
12
|
+
* @license MIT
|
|
13
|
+
* @copyright Ryan Millard 2026
|
|
14
|
+
* @author Ryan Millard
|
|
15
|
+
* @since 0.0.0
|
|
16
|
+
*
|
|
17
|
+
* @exports imageToUint8ClampedArray
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* @summary Convert an image file into a `Uint8ClampedArray` of pixel data (RGBA).
|
|
21
|
+
*
|
|
22
|
+
* @function imageToUint8ClampedArray
|
|
23
|
+
* @async
|
|
24
|
+
* @description
|
|
25
|
+
* Reads an image file (PNG, JPEG, etc.) and returns its pixel data as a `Uint8ClampedArray`.
|
|
26
|
+
* Each pixel consists of four consecutive values: red, green, blue, and alpha (RGBA).
|
|
27
|
+
* Also returns the image's original width and height. Useful for canvas operations,
|
|
28
|
+
* image processing, WebGL textures, or computer vision tasks.
|
|
29
|
+
*
|
|
30
|
+
*
|
|
31
|
+
* @param {File} file - The image file to process. Must be a valid `File` object, e.g., from an `<input type="file">` element.
|
|
32
|
+
*
|
|
33
|
+
* @returns {Promise<{pixels: Uint8ClampedArray, width: number, height: number}>}
|
|
34
|
+
* A Promise resolving to an object containing:
|
|
35
|
+
* - `pixels`: A `Uint8ClampedArray` of RGBA pixel values.
|
|
36
|
+
* - `width`: Width of the image in pixels.
|
|
37
|
+
* - `height`: Height of the image in pixels.
|
|
38
|
+
*
|
|
39
|
+
* @throws {Error} Will not throw in current implementation, but could reject if the image fails to load.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* const fileInput = document.querySelector("#fileInput");
|
|
43
|
+
* fileInput.addEventListener("change", async (event) => {
|
|
44
|
+
* const file = event.target.files[0];
|
|
45
|
+
* const { pixels, width, height } = await imageToUint8ClampedArray(file);
|
|
46
|
+
* console.log("Width:", width, "Height:", height);
|
|
47
|
+
* console.log("Pixels:", pixels);
|
|
48
|
+
* });
|
|
49
|
+
*
|
|
50
|
+
* @todo Add error handling for invalid or corrupt image files.
|
|
51
|
+
* @variation Standard image file input
|
|
52
|
+
*/
|
|
53
|
+
function imageToUint8ClampedArray(file) {
|
|
54
|
+
return new Promise((resolve) => {
|
|
55
|
+
const img = new Image();
|
|
56
|
+
img.onload = () => {
|
|
57
|
+
const canvas = document.createElement("canvas");
|
|
58
|
+
canvas.width = img.width;
|
|
59
|
+
canvas.height = img.height;
|
|
60
|
+
const ctx = canvas.getContext("2d");
|
|
61
|
+
ctx.drawImage(img, 0, 0);
|
|
62
|
+
const { data } = ctx.getImageData(0, 0, img.width, img.height);
|
|
63
|
+
resolve({
|
|
64
|
+
pixels: data,
|
|
65
|
+
width: img.width,
|
|
66
|
+
height: img.height
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
img.src = URL.createObjectURL(file);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/target/node/worker.js
|
|
74
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
75
|
+
async function createWorker() {
|
|
76
|
+
const worker = new Worker(resolve(__dirname, "./wasmWorker.js"));
|
|
77
|
+
return {
|
|
78
|
+
postMessage: (msg) => worker.postMessage(msg),
|
|
79
|
+
onMessage: (fn) => worker.on("message", fn),
|
|
80
|
+
onError: (fn) => worker.on("error", fn),
|
|
81
|
+
terminate: () => worker.terminate()
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/wasmClient.js
|
|
86
|
+
/**
|
|
87
|
+
* @packageDocumentation
|
|
88
|
+
* Advanced low-level interface for communicating with the WASM worker.
|
|
89
|
+
* Provides granular control over calling WASM functions asynchronously,
|
|
90
|
+
* handling memory transfers, and managing worker lifecycle.
|
|
91
|
+
*
|
|
92
|
+
* @file Manages function call requests to the WASM worker.
|
|
93
|
+
* @internal
|
|
94
|
+
*
|
|
95
|
+
* @module wasm-client
|
|
96
|
+
* @license MIT
|
|
97
|
+
* @copyright Ryan Millard 2026
|
|
98
|
+
* @author Ryan Millard
|
|
99
|
+
* @since 0.0.0
|
|
100
|
+
* @description This module provides low-level image processing functions using WASM.
|
|
101
|
+
* You must specify exact C++ function signatures and manage the input data carefully.
|
|
102
|
+
* This allows more granular control.
|
|
103
|
+
*/
|
|
104
|
+
/**
|
|
105
|
+
* Worker instance that handles communication with the WASM module.
|
|
106
|
+
* @type {Worker | null}
|
|
107
|
+
* @private
|
|
108
|
+
*/
|
|
109
|
+
var worker;
|
|
110
|
+
/**
|
|
111
|
+
* Incremental ID counter for correlating requests and responses.
|
|
112
|
+
* @type {number}
|
|
113
|
+
* @private
|
|
114
|
+
*/
|
|
115
|
+
var idCounter = 0;
|
|
116
|
+
/**
|
|
117
|
+
* Maps request IDs to their corresponding promise callbacks.
|
|
118
|
+
* @type {Map<number, {resolve: Function, reject: Function}>}
|
|
119
|
+
* @private
|
|
120
|
+
*/
|
|
121
|
+
var callbacks = /* @__PURE__ */ new Map();
|
|
122
|
+
/**
|
|
123
|
+
* Flag to ensure the WASM worker is initialized only once.
|
|
124
|
+
* @type {boolean}
|
|
125
|
+
* @private
|
|
126
|
+
*/
|
|
127
|
+
var initialized = false;
|
|
128
|
+
/**
|
|
129
|
+
* Handle response messages from the WASM worker.
|
|
130
|
+
* @param {{ id: number, output?: any, returnValue?: any, error?: string }} data
|
|
131
|
+
*/
|
|
132
|
+
function handleMessage(data) {
|
|
133
|
+
const cb = callbacks.get(data.id);
|
|
134
|
+
if (!cb) return;
|
|
135
|
+
callbacks.delete(data.id);
|
|
136
|
+
if (data.error) cb.reject(new Error(data.error));
|
|
137
|
+
else cb.resolve({
|
|
138
|
+
output: data.output,
|
|
139
|
+
returnValue: data.returnValue
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* @summary Initialize the WASM worker.
|
|
144
|
+
*
|
|
145
|
+
* @description
|
|
146
|
+
* Sets up message and error handlers. Safe to call multiple times;
|
|
147
|
+
* subsequent calls are no-ops. After initialization, functions can be called
|
|
148
|
+
* via {@link callWasm}.
|
|
149
|
+
*
|
|
150
|
+
* @function initWasmWorker
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* import { initWasmWorker } from "./wasmClient.js";
|
|
154
|
+
*
|
|
155
|
+
* initWasmWorker();
|
|
156
|
+
*
|
|
157
|
+
* @since 0.0.0
|
|
158
|
+
*/
|
|
159
|
+
async function initWasmWorker() {
|
|
160
|
+
if (initialized) return;
|
|
161
|
+
worker = await createWorker();
|
|
162
|
+
worker.onMessage(handleMessage);
|
|
163
|
+
worker.onError((event) => {
|
|
164
|
+
const output = event.message || "WASM worker error";
|
|
165
|
+
const err = /* @__PURE__ */ new Error(`[Img2Num wasmClient] Error: ${output}`);
|
|
166
|
+
for (const [_id, cb] of callbacks) cb.reject(err);
|
|
167
|
+
callbacks.clear();
|
|
168
|
+
});
|
|
169
|
+
initialized = true;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* @summary Call a function in the WASM worker.
|
|
173
|
+
*
|
|
174
|
+
* @description
|
|
175
|
+
* Directly send a request to the WASM worker to call the specified function,
|
|
176
|
+
* passing specific buffers and and arguments.
|
|
177
|
+
*
|
|
178
|
+
* @async
|
|
179
|
+
* @function callWasm
|
|
180
|
+
* @param {Object} __named_parameters - Options for the WASM call.
|
|
181
|
+
* @property {string} __named_parameters.funcName - The name of the WASM function to invoke.
|
|
182
|
+
* @property {Object} [__named_parameters.args={}] - Named arguments to pass to the WASM function.
|
|
183
|
+
* @property {string[]} [__named_parameters.bufferKeys=[]] - Keys of arguments that should be transferred as ArrayBuffers.
|
|
184
|
+
* @property {string} [__named_parameters.returnType="void"] - Expected return type.
|
|
185
|
+
*
|
|
186
|
+
* @returns {Promise<{output: any, returnValue: any}>} Resolves with the result of the WASM function call.
|
|
187
|
+
*
|
|
188
|
+
* @throws {Error} If the worker has not been initialized.
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* import { callWasm, initWasmWorker } from "./wasmClient.js";
|
|
192
|
+
*
|
|
193
|
+
* initWasmWorker();
|
|
194
|
+
*
|
|
195
|
+
* const result = await callWasm({
|
|
196
|
+
* funcName: "gaussian_blur_fft",
|
|
197
|
+
* args: { pixels, width, height, sigma_pixels: 5 },
|
|
198
|
+
* bufferKeys: ["pixels"],
|
|
199
|
+
* returnType: "Uint8ClampedArray"
|
|
200
|
+
* });
|
|
201
|
+
* console.log(result.output);
|
|
202
|
+
*
|
|
203
|
+
* @since 0.0.0
|
|
204
|
+
*/
|
|
205
|
+
async function callWasm({ funcName, args = {}, bufferKeys = [], returnType = "void" }) {
|
|
206
|
+
if (!initialized) throw new Error("WASM worker not initialized. Call initWasmWorker() first.");
|
|
207
|
+
const id = idCounter++;
|
|
208
|
+
return new Promise((resolve, reject) => {
|
|
209
|
+
callbacks.set(id, {
|
|
210
|
+
resolve,
|
|
211
|
+
reject
|
|
212
|
+
});
|
|
213
|
+
worker.postMessage({
|
|
214
|
+
id,
|
|
215
|
+
funcName,
|
|
216
|
+
args,
|
|
217
|
+
bufferKeys,
|
|
218
|
+
returnType
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
//#endregion
|
|
223
|
+
//#region src/safeWasmWrappers.js
|
|
224
|
+
/**
|
|
225
|
+
* @packageDocumentation
|
|
226
|
+
* High-level image operations exposed via WASM.
|
|
227
|
+
*
|
|
228
|
+
* The exports defined here abstract away the manual memory management required
|
|
229
|
+
* when importing raw WASM functions, making them more JavaScript-friendly.
|
|
230
|
+
*
|
|
231
|
+
* @file Safely wraps unsafe WASM (C++) function calls.
|
|
232
|
+
*
|
|
233
|
+
* @module image-wasm
|
|
234
|
+
* @license MIT
|
|
235
|
+
* @copyright Ryan Millard 2026
|
|
236
|
+
* @author Ryan Millard
|
|
237
|
+
* @since 0.0.0
|
|
238
|
+
* @description This module provides high-level image processing functions using WASM.
|
|
239
|
+
* Each function handles memory management and exposes a JavaScript-friendly API.
|
|
240
|
+
*/
|
|
241
|
+
await initWasmWorker();
|
|
242
|
+
/**
|
|
243
|
+
* @summary Apply a Gaussian blur to an image using FFT in WASM.
|
|
244
|
+
*
|
|
245
|
+
* @description
|
|
246
|
+
* Takes a Uint8ClampedArray and its dimensions and applies a Gaussian blur on the Uint8ClampedArray image.
|
|
247
|
+
* The `sigma_pixels` parameter determines the blur radius and has a dynamic default value equal to 5% of the image's width.
|
|
248
|
+
* Useful for denoising images by applying a low-pass filter. Sped up by a 2-D FFT.
|
|
249
|
+
*
|
|
250
|
+
* @async
|
|
251
|
+
* @function gaussianBlur
|
|
252
|
+
* @param {Object} options - The input options.
|
|
253
|
+
* @param {Uint8ClampedArray} options.pixels - The image pixel data (flat RGBA array).
|
|
254
|
+
* @param {number} options.width - The width of the image.
|
|
255
|
+
* @param {number} options.height - The height of the image.
|
|
256
|
+
* @param {number} [options.sigma_pixels=width*0.005] - Standard deviation of the Gaussian blur (default=width*0.005; 5% of width).
|
|
257
|
+
* @returns {Promise<Uint8ClampedArray>} The blurred image pixels.
|
|
258
|
+
* @throws {Error} If the WASM function fails or memory allocation fails.
|
|
259
|
+
* @example
|
|
260
|
+
* const blurred = await gaussianBlur({ pixels, width, height });
|
|
261
|
+
* @todo Fix FFT zero-padding bug around edges of the image.
|
|
262
|
+
* @variation Standard Gaussian blur using FFT
|
|
263
|
+
* @since 0.0.0
|
|
264
|
+
*/
|
|
265
|
+
var gaussianBlur = async ({ pixels, width, height, sigma_pixels = width * .005 }) => {
|
|
266
|
+
return (await callWasm({
|
|
267
|
+
funcName: "gaussian_blur_fft",
|
|
268
|
+
args: {
|
|
269
|
+
pixels,
|
|
270
|
+
width,
|
|
271
|
+
height,
|
|
272
|
+
sigma_pixels
|
|
273
|
+
},
|
|
274
|
+
bufferKeys: [{
|
|
275
|
+
key: "pixels",
|
|
276
|
+
type: "Uint8ClampedArray"
|
|
277
|
+
}]
|
|
278
|
+
})).output.pixels;
|
|
279
|
+
};
|
|
280
|
+
/**
|
|
281
|
+
* @summary Apply a bilateral filter to an image using WASM.
|
|
282
|
+
*
|
|
283
|
+
* @description
|
|
284
|
+
* Takes a Uint8ClampedArray and its dimensions and applies a bilateral filter on the Uint8ClampedArray image.
|
|
285
|
+
* The `sigma_spatial` and `sigma_range` set weights to the respective Gaussian kernels applied to spatial (x, y) and range (color) data -
|
|
286
|
+
* they both have recommended default values applied.
|
|
287
|
+
* The default `color_space` is 0, which is CIE LAB, but sRGB can be chosen by setting `color_space` = 1. CIE LAB is more
|
|
288
|
+
* accurate, but sRGB is slightly faster.
|
|
289
|
+
*
|
|
290
|
+
* @async
|
|
291
|
+
* @function bilateralFilter
|
|
292
|
+
* @param {Object} options - The input options.
|
|
293
|
+
* @param {Uint8ClampedArray} options.pixels - The image pixel data (flat RGBA array).
|
|
294
|
+
* @param {number} options.width - The width of the image.
|
|
295
|
+
* @param {number} options.height - The height of the image.
|
|
296
|
+
* @param {number} [options.sigma_spatial=3] - Spatial standard deviation.
|
|
297
|
+
* @param {number} [options.sigma_range=50] - Range (color) standard deviation.
|
|
298
|
+
* @param {number} [options.color_space=0] - Color space mode (0: CIE LAB; 1: sRGB).
|
|
299
|
+
* @returns {Promise<Uint8ClampedArray>} The filtered image pixels.
|
|
300
|
+
* @throws {Error} If the WASM function fails.
|
|
301
|
+
* @example
|
|
302
|
+
* const filtered = await bilateralFilter({ pixels, width, height });
|
|
303
|
+
* @variation Standard bilateral filter with default parameters
|
|
304
|
+
* @since 0.0.0
|
|
305
|
+
*/
|
|
306
|
+
var bilateralFilter = async ({ pixels, width, height, sigma_spatial = 3, sigma_range = 50, color_space = 0 }) => {
|
|
307
|
+
return (await callWasm({
|
|
308
|
+
funcName: "bilateral_filter",
|
|
309
|
+
args: {
|
|
310
|
+
pixels,
|
|
311
|
+
width,
|
|
312
|
+
height,
|
|
313
|
+
sigma_spatial,
|
|
314
|
+
sigma_range,
|
|
315
|
+
color_space
|
|
316
|
+
},
|
|
317
|
+
bufferKeys: [{
|
|
318
|
+
key: "pixels",
|
|
319
|
+
type: "Uint8ClampedArray"
|
|
320
|
+
}]
|
|
321
|
+
})).output.pixels;
|
|
322
|
+
};
|
|
323
|
+
/**
|
|
324
|
+
* @summary Apply a black-biased threshold filter to reduce colors in an image.
|
|
325
|
+
*
|
|
326
|
+
* @description
|
|
327
|
+
* Apply a simple sRGB bin-based threshold on the Uint8ClampedArray image.
|
|
328
|
+
* The bins in this function are determined by the `num_colors` parameter.
|
|
329
|
+
*
|
|
330
|
+
* @async
|
|
331
|
+
* @function blackThreshold
|
|
332
|
+
* @param {Object} options - The input options.
|
|
333
|
+
* @param {Uint8ClampedArray} options.pixels - The image pixel data (flat RGBA array).
|
|
334
|
+
* @param {number} options.width - The width of the image.
|
|
335
|
+
* @param {number} options.height - The height of the image.
|
|
336
|
+
* @param {number} options.num_colors - Number of colors to reduce the image to.
|
|
337
|
+
* @returns {Promise<Uint8ClampedArray>} The thresholded image pixels.
|
|
338
|
+
* @throws {Error} If the WASM function fails.
|
|
339
|
+
* @example
|
|
340
|
+
* const thresholded = await blackThreshold({ pixels, width, height, num_colors: 16 });
|
|
341
|
+
* @see {@link https://en.wikipedia.org/wiki/Color_quantization|Color Quantization Wiki}
|
|
342
|
+
* @todo Support different bias levels for black/white thresholds.
|
|
343
|
+
* @variation Black-biased threshold with customizable number of colors
|
|
344
|
+
* @since 0.0.0
|
|
345
|
+
*/
|
|
346
|
+
var blackThreshold = async ({ pixels, width, height, num_colors }) => {
|
|
347
|
+
return (await callWasm({
|
|
348
|
+
funcName: "black_threshold_image",
|
|
349
|
+
args: {
|
|
350
|
+
pixels,
|
|
351
|
+
width,
|
|
352
|
+
height,
|
|
353
|
+
num_colors
|
|
354
|
+
},
|
|
355
|
+
bufferKeys: [{
|
|
356
|
+
key: "pixels",
|
|
357
|
+
type: "Uint8ClampedArray"
|
|
358
|
+
}]
|
|
359
|
+
})).output.pixels;
|
|
360
|
+
};
|
|
361
|
+
/**
|
|
362
|
+
* @summary Cluster pixels using the K-Means algorithm in WASM.
|
|
363
|
+
*
|
|
364
|
+
* @description
|
|
365
|
+
* Apply a standard K-Means clustering algorithm to the input image in the specified `color_space`
|
|
366
|
+
* (default is 0: CIE LAB, but 1: sRGB can be use) using pre-specified maximum color and iteration counts.
|
|
367
|
+
* You can provide the `out_pixels` and `out_labels` arrays,
|
|
368
|
+
* however this is atypical in JavaScript (since it is modified in-place and you will need to allocate a sufficiently large array),
|
|
369
|
+
* so it is recommended to use the default arguments and returns.
|
|
370
|
+
*
|
|
371
|
+
* @async
|
|
372
|
+
* @function kmeans
|
|
373
|
+
* @param {Object} options - The input options.
|
|
374
|
+
* @param {Uint8ClampedArray} options.pixels - Original image pixels.
|
|
375
|
+
* @param {Uint8ClampedArray} [options.out_pixels=new Uint8ClampedArray(pixels.length)] - Output pixels array.
|
|
376
|
+
* @param {Int32Array} [options.out_labels=new Int32Array(pixels.length/4)] - Output labels array.
|
|
377
|
+
* @param {number} options.width - Image width.
|
|
378
|
+
* @param {number} options.height - Image height.
|
|
379
|
+
* @param {number} options.num_colors - Number of color clusters.
|
|
380
|
+
* @param {number} [options.max_iter=100] - Maximum number of iterations.
|
|
381
|
+
* @param {number} [options.color_space=0] - Color space mode.
|
|
382
|
+
* @returns {Promise<{pixels: Uint8ClampedArray, labels: Int32Array}>} Clustered pixels and labels.
|
|
383
|
+
* @throws {Error} If the WASM function fails or iterations do not converge.
|
|
384
|
+
* @example
|
|
385
|
+
* const { pixels: clusteredPixels, labels } = await kmeans({ pixels, width, height, num_colors: 8 });
|
|
386
|
+
* @variation K-means clustering with default color space
|
|
387
|
+
* @since 0.0.0
|
|
388
|
+
*/
|
|
389
|
+
var kmeans = async ({ pixels, out_pixels = new Uint8ClampedArray(pixels.length), out_labels = new Int32Array(pixels.length / 4), width, height, num_colors, max_iter = 100, color_space = 0 }) => {
|
|
390
|
+
const result = await callWasm({
|
|
391
|
+
funcName: "kmeans",
|
|
392
|
+
args: {
|
|
393
|
+
pixels,
|
|
394
|
+
out_pixels,
|
|
395
|
+
out_labels,
|
|
396
|
+
width,
|
|
397
|
+
height,
|
|
398
|
+
num_colors,
|
|
399
|
+
max_iter,
|
|
400
|
+
color_space
|
|
401
|
+
},
|
|
402
|
+
bufferKeys: [
|
|
403
|
+
{
|
|
404
|
+
key: "pixels",
|
|
405
|
+
type: "Uint8ClampedArray"
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
key: "out_pixels",
|
|
409
|
+
type: "Uint8ClampedArray"
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
key: "out_labels",
|
|
413
|
+
type: "Int32Array"
|
|
414
|
+
}
|
|
415
|
+
]
|
|
416
|
+
});
|
|
417
|
+
return {
|
|
418
|
+
pixels: result.output.out_pixels,
|
|
419
|
+
labels: result.output.out_labels
|
|
420
|
+
};
|
|
421
|
+
};
|
|
422
|
+
/**
|
|
423
|
+
* @summary Convert labeled regions to SVG contours.
|
|
424
|
+
*
|
|
425
|
+
* @description
|
|
426
|
+
* Convert an input image and its labeled regions into an SVG.
|
|
427
|
+
*
|
|
428
|
+
* @async
|
|
429
|
+
* @function findContours
|
|
430
|
+
* @param {Object} options - The input options.
|
|
431
|
+
* @param {Uint8ClampedArray} options.pixels - Original image pixels.
|
|
432
|
+
* @param {Int32Array} options.labels - Label array from clustering (e.g., K-Means) or segmentation.
|
|
433
|
+
* @param {number} options.width - Image width.
|
|
434
|
+
* @param {number} options.height - Image height.
|
|
435
|
+
* @param {number} [options.min_area=100] - Minimum area of a region to be considered a contour.
|
|
436
|
+
* @param {number} [options.min_thickness=10] - Minimum thickness of a region to be considered a contour.
|
|
437
|
+
* @returns {Promise<{svg: string}>} Generated SVG.
|
|
438
|
+
* @throws {Error} If the WASM function fails or input labels are invalid.
|
|
439
|
+
* @example
|
|
440
|
+
* const { svg } = await findContours({ pixels, labels, width, height });
|
|
441
|
+
* @variation Converts labeled (from a clustering algorithm, e.g. K-Means) image into an SVG.
|
|
442
|
+
* @since 0.0.0
|
|
443
|
+
*/
|
|
444
|
+
var findContours = async ({ pixels, labels, width, height, min_area = 100, min_thickness = 10 }) => {
|
|
445
|
+
return { svg: (await callWasm({
|
|
446
|
+
funcName: "labels_to_svg",
|
|
447
|
+
args: {
|
|
448
|
+
pixels,
|
|
449
|
+
labels,
|
|
450
|
+
width,
|
|
451
|
+
height,
|
|
452
|
+
min_area,
|
|
453
|
+
min_thickness
|
|
454
|
+
},
|
|
455
|
+
bufferKeys: [{
|
|
456
|
+
key: "pixels",
|
|
457
|
+
type: "Uint8ClampedArray"
|
|
458
|
+
}, {
|
|
459
|
+
key: "labels",
|
|
460
|
+
type: "Int32Array"
|
|
461
|
+
}],
|
|
462
|
+
returnType: "string"
|
|
463
|
+
})).returnValue };
|
|
464
|
+
};
|
|
465
|
+
/**
|
|
466
|
+
* @summary Convert raster images (e.g., JPEG, PNG) to SVGs.
|
|
467
|
+
*
|
|
468
|
+
* @description
|
|
469
|
+
* Convert an input raster image into an SVG. A unification of `bilateralFilter`, `kmeans`, and `findContours`.
|
|
470
|
+
*
|
|
471
|
+
* @async
|
|
472
|
+
* @function imageToSvg
|
|
473
|
+
* @param {Object} options - The input options.
|
|
474
|
+
* @param {Uint8ClampedArray} options.pixels - Original image pixels.
|
|
475
|
+
* @param {number} options.width - Image width.
|
|
476
|
+
* @param {number} options.height - Image height.
|
|
477
|
+
* @param {number} [options.sigma_spatial=3] - Spatial standard deviation.
|
|
478
|
+
* @param {number} [options.sigma_range=50] - Range (color) standard deviation.
|
|
479
|
+
* @param {number} [options.num_colors=16] - Number of color clusters.
|
|
480
|
+
* @param {number} [options.max_iter=100] - Maximum number of iterations.
|
|
481
|
+
* @param {number} [options.min_area=100] - Minimum area of a region to be considered a contour.
|
|
482
|
+
* @param {number} [options.min_thickness=10] - Minimum thickness of a region to be considered a contour.
|
|
483
|
+
* @param {number} [options.color_space=0] - Color space mode.
|
|
484
|
+
* @returns {Promise<{svg: string}>} Generated SVG.
|
|
485
|
+
* @throws {Error} If the WASM function fails or input labels are invalid.
|
|
486
|
+
* @example
|
|
487
|
+
* const { svg } = await findContours({ pixels, labels, width, height });
|
|
488
|
+
* @variation Convert a raster image (e.g., PNG, JPG) into an SVG.
|
|
489
|
+
* @since 0.0.0
|
|
490
|
+
*/
|
|
491
|
+
var imageToSvg = async ({ pixels, width, height, sigma_spatial = 3, sigma_range = 50, num_colors = 16, max_iter = 100, min_area = 100, min_thickness = 10, color_space = 0 }) => {
|
|
492
|
+
return { svg: (await callWasm({
|
|
493
|
+
funcName: "image_to_svg",
|
|
494
|
+
args: {
|
|
495
|
+
pixels,
|
|
496
|
+
width,
|
|
497
|
+
height,
|
|
498
|
+
sigma_spatial,
|
|
499
|
+
sigma_range,
|
|
500
|
+
num_colors,
|
|
501
|
+
max_iter,
|
|
502
|
+
min_area,
|
|
503
|
+
min_thickness,
|
|
504
|
+
color_space
|
|
505
|
+
},
|
|
506
|
+
bufferKeys: [{
|
|
507
|
+
key: "pixels",
|
|
508
|
+
type: "Uint8ClampedArray"
|
|
509
|
+
}],
|
|
510
|
+
returnType: "string"
|
|
511
|
+
})).returnValue };
|
|
512
|
+
};
|
|
513
|
+
//#endregion
|
|
514
|
+
export { bilateralFilter, blackThreshold, findContours, gaussianBlur, imageToSvg, imageToUint8ClampedArray, kmeans };
|