diff --git a/.github/workflows/build-react-app.yml b/.github/workflows/build-react-app.yml index c6c8fc1ea..3a355c58b 100644 --- a/.github/workflows/build-react-app.yml +++ b/.github/workflows/build-react-app.yml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - name: Install Dependendencies - run: pnpm --filter react-example install + run: pnpm install - name: Download WASM artifacts if: ${{ inputs.wasm-artifacts-path }} @@ -34,6 +34,10 @@ jobs: name: wasm-build path: ${{ inputs.wasm-artifacts-path }} + - name: Build img2num module + # Only build for browser since that is all we need here + run: pnpm -F img2num build:browser + - name: Build React app run: pnpm -F react-example run build diff --git a/Justfile b/Justfile index ab5e6048e..dc3214db1 100644 --- a/Justfile +++ b/Justfile @@ -10,10 +10,13 @@ help: \t cpp: build c++ core and c bindings \n \ \t js: build js/wasm bindings \n \ \t py: build python bindings and python package \n \ + \t packages-js: build browser and node packages using js/wasm bindings \n \ \t all: build all of above \n \ clean : \n \ \t cpp: delete c++ and c build folder (build-c-cpp) \n \ \t js: delete js build folder (build-wasm) \n \ + \t packages-js: delete packages/js/dist folder \n \ + \t packages-py: delete packages/py/build-py folder \n \ docs : \n \ \t build: build docusaurus server \n \ \t start: start docusaurus server on port 3000 \n \ @@ -23,11 +26,13 @@ help: console-cpp : run example C++ app on input image \n \ console-c : run example C app on input image \n \ console-py : run example python app on input image \n \ + console-js : run example node app on input image \n \ " init: @echo "Pulling submodules" git submodule update --init + pnpm install just build all format: @@ -49,12 +54,18 @@ build-py: uv sync --reinstall uv build --wheel +build-packages-js: + @echo "Build js packages" + just build-wasm + pnpm -F img2num build + build target: case "{{ target }}" in \ cpp) just build-c-cpp ;; \ js) just build-wasm ;; \ py) just build-py ;; \ - all) just build-c-cpp build-wasm build-py react-js build docs build ;; \ + packages-js) just build-packages-js ;; \ + all) just build-c-cpp build-wasm build-py build-packages-js react-js build docs build ;; \ esac clean target: @@ -62,6 +73,8 @@ clean target: case "{{ target }}" in \ cpp) rm -rf build-c-cpp/ ;; \ js) rm -rf build-wasm/ ;; \ + packages-js) rm -rf packages/js/dist/ ;; \ + packages-py) rm -rf packages/py/build-py/ ;; \ esac docs action: @@ -71,7 +84,7 @@ docs action: start) cd docs/ && pnpm run serve ;; \ esac -react-js action: build-wasm +react-js action: build-packages-js @echo "Run react sample app" case "{{ action }}" in \ build) pnpm -F react-example run build ;; \ @@ -90,3 +103,8 @@ console-cpp input: console-c input: @echo "./build-c-cpp/example-apps/console-c/console_c_app {{ input }}" ./build-c-cpp/example-apps/console-c/CImg2NumExample_console_c "{{ input }}" + +console-js input: + @echo "node example-apps/console-js/index.js {{ input }}" + node example-apps/console-js/index.js "{{ input }}" + diff --git a/bindings/js/CMakeLists.txt b/bindings/js/CMakeLists.txt index 23e4bc7b8..51217372c 100644 --- a/bindings/js/CMakeLists.txt +++ b/bindings/js/CMakeLists.txt @@ -22,7 +22,7 @@ target_include_directories(img2num_wasm PRIVATE set(COMMON_FLAGS "SHELL:-sMODULARIZE=1" "SHELL:-sEXPORT_ES6=1" - "SHELL:-sENVIRONMENT=web,worker" + "SHELL:-sENVIRONMENT=web,worker,node" "SHELL:-sEXPORTED_FUNCTIONS=['_malloc','_free']" "SHELL:-sEXPORTED_RUNTIME_METHODS=['ccall','cwrap','getValue','setValue','HEAPU8','HEAP32','UTF8ToString','stringToUTF8','lengthBytesUTF8']" "SHELL:-sINITIAL_MEMORY=2GB" diff --git a/core/include/internal/gpu.h b/core/include/internal/gpu.h index 41384e6f7..43dae3c44 100644 --- a/core/include/internal/gpu.h +++ b/core/include/internal/gpu.h @@ -128,8 +128,7 @@ class GPU { ); }; - void init_gpu() { // wgpu::Instance& instance, wgpu::Adapter& adapter, wgpu::Device& device, - // wgpu::Queue& queue) { + void init_gpu() { if (gpu_initialized) return; diff --git a/docs/plugins/docusaurusPluginTypeDocConfig.js b/docs/plugins/docusaurusPluginTypeDocConfig.js index e1d5661e5..084aebe9c 100644 --- a/docs/plugins/docusaurusPluginTypeDocConfig.js +++ b/docs/plugins/docusaurusPluginTypeDocConfig.js @@ -2,13 +2,13 @@ const config = [ "docusaurus-plugin-typedoc", { name: "API", - entryPoints: ["../packages/js/index.js"], + entryPoints: ["../packages/js/src/index.js"], entryPointStrategy: "expand", out: "docs/js/api", json: "static/js/api.json", readme: "none", tsconfig: "../packages/js/tsconfig.typedoc.json", - exclude: ["node_modules/", "build-wasm/", ".gitignore", "package.json", "*.test.*"], + exclude: ["node_modules/", "build-wasm/", "dist/", ".gitignore", "package.json", "*.test.*"], blockTags: ["@param", "@returns", "@throws", "@example", "@async", "@summary", "@property", "@todo", "@variation", "@description", "@module", "@author", "@license", "@exports", "@see", "@since"], gitRevision: "main", sourceLinkTemplate: "https://github.com/Ryan-Millard/Img2Num/blob/{gitRevision}/{path}#L{line}", diff --git a/eslint.config.js b/eslint.config.js index 784f71d07..8c3614b84 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -15,6 +15,7 @@ export default defineConfig([ "scripts/img2num-dev-scripts/node_modules", + "packages/js/dist", "packages/js/build-wasm", "packages/js/node_modules", diff --git a/example-apps/console-js/index.js b/example-apps/console-js/index.js new file mode 100644 index 000000000..818ff22fa --- /dev/null +++ b/example-apps/console-js/index.js @@ -0,0 +1,26 @@ +import { writeFileSync } from "fs"; +import { imageToSvg } from "img2num"; +import sharp from "sharp"; + +const imagePath = process.argv[2]; + +if (!imagePath) { + console.error("Usage: node index.js "); + process.exit(1); +} + +console.log(`Processing image: ${imagePath}`); + +const { data, info } = await sharp(imagePath).ensureAlpha().raw().toBuffer({ resolveWithObject: true }); + +const pixels = new Uint8ClampedArray(data.buffer); +const { width, height } = info; + +console.log(`Image size: ${width}x${height}`); +console.log("Running img2num in Node.js..."); + +const { svg } = await imageToSvg({ pixels, width, height }); + +writeFileSync("output.svg", svg); +console.log("Done! SVG saved to output.svg"); +process.exit(); diff --git a/example-apps/console-js/package.json b/example-apps/console-js/package.json new file mode 100644 index 000000000..20681fc29 --- /dev/null +++ b/example-apps/console-js/package.json @@ -0,0 +1,12 @@ +{ + "name": "console-js", + "version": "1.0.0", + "type": "module", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "img2num": "workspace:*", + "sharp": "^0.33.0" + } +} diff --git a/example-apps/react-js/vite.config.js b/example-apps/react-js/vite.config.js index 2f7385ba8..fe0aac77b 100644 --- a/example-apps/react-js/vite.config.js +++ b/example-apps/react-js/vite.config.js @@ -34,4 +34,14 @@ export default defineConfig({ generateContributorCreditsPlugin(), svgr(), ], + worker: { + format: "es", // Keeps your top-level awaits working + }, + build: { + target: "esnext", + rollupOptions: { + // Tells the bundler to ignore these when building for the web browser + external: ["webgpu", "worker_threads", "url", "path", "fs"], + }, + }, }); diff --git a/output.svg b/output.svg new file mode 100644 index 000000000..bd28fc9f6 --- /dev/null +++ b/output.svg @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/js/imageToUint8ClampedArray.test.js b/packages/js/imageToUint8ClampedArray.test.js deleted file mode 100644 index 2f1006045..000000000 --- a/packages/js/imageToUint8ClampedArray.test.js +++ /dev/null @@ -1,272 +0,0 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { loadImageToUint8Array, uint8ClampedArrayToSVG } from "./image-utils"; - -// Mock imagetracerjs -vi.mock("imagetracerjs", () => ({ - default: { - imagedataToSVG: vi.fn(() => ''), - }, -})); - -import ImageTracer from "imagetracerjs"; - -describe("image-utils", () => { - describe("loadImageToUint8Array", () => { - let mockCanvas; - let mockContext; - let originalCreateElement; - - beforeEach(() => { - vi.clearAllMocks(); - - // Mock canvas context - mockContext = { - drawImage: vi.fn(), - getImageData: vi.fn(() => ({ - data: new Uint8ClampedArray([255, 0, 0, 255, 0, 255, 0, 255]), // 2 pixels: red, green - })), - putImageData: vi.fn(), - }; - - // Mock canvas - mockCanvas = { - width: 0, - height: 0, - getContext: vi.fn(() => mockContext), - }; - - // Store original and mock document.createElement - originalCreateElement = document.createElement.bind(document); - document.createElement = vi.fn((tag) => { - if (tag === "canvas") { - return mockCanvas; - } - return originalCreateElement(tag); - }); - }); - - afterEach(() => { - document.createElement = originalCreateElement; - }); - - it("should load an image file and return pixel data with dimensions", async () => { - const mockFile = new Blob(["fake-image-data"], { type: "image/png" }); - - // Mock Image class - class MockImage { - constructor() { - this.width = 2; - this.height = 1; - this.onload = null; - this.src = ""; - - // Trigger onload after src is set - setTimeout(() => { - if (this.onload) this.onload(); - }, 0); - } - } - - vi.stubGlobal("Image", MockImage); - - const result = await loadImageToUint8Array(mockFile); - - expect(result).toHaveProperty("pixels"); - expect(result).toHaveProperty("width", 2); - expect(result).toHaveProperty("height", 1); - expect(result.pixels).toBeInstanceOf(Uint8ClampedArray); - expect(result.pixels.length).toBe(8); // 2 pixels * 4 channels (RGBA) - }); - - it("should create a canvas with the image dimensions", async () => { - const mockFile = new Blob(["fake-image-data"], { type: "image/png" }); - - class MockImage { - constructor() { - this.width = 100; - this.height = 50; - this.onload = null; - setTimeout(() => { - if (this.onload) this.onload(); - }, 0); - } - } - - vi.stubGlobal("Image", MockImage); - - await loadImageToUint8Array(mockFile); - - expect(mockCanvas.width).toBe(100); - expect(mockCanvas.height).toBe(50); - }); - - it("should draw the image onto the canvas", async () => { - const mockFile = new Blob(["fake-image-data"], { type: "image/png" }); - - let imageInstance; - class MockImage { - constructor() { - this.width = 10; - this.height = 10; - this.onload = null; - imageInstance = this; - setTimeout(() => { - if (this.onload) this.onload(); - }, 0); - } - } - - vi.stubGlobal("Image", MockImage); - - await loadImageToUint8Array(mockFile); - - expect(mockContext.drawImage).toHaveBeenCalledWith(imageInstance, 0, 0); - }); - - it("should call URL.createObjectURL with the file", async () => { - const mockFile = new Blob(["fake-image-data"], { type: "image/png" }); - - class MockImage { - constructor() { - this.width = 1; - this.height = 1; - this.onload = null; - setTimeout(() => { - if (this.onload) this.onload(); - }, 0); - } - } - - vi.stubGlobal("Image", MockImage); - - await loadImageToUint8Array(mockFile); - - expect(URL.createObjectURL).toHaveBeenCalledWith(mockFile); - }); - }); - - describe("uint8ClampedArrayToSVG", () => { - let mockCanvas; - let mockContext; - let originalCreateElement; - - beforeEach(() => { - vi.clearAllMocks(); - - mockContext = { - drawImage: vi.fn(), - getImageData: vi.fn(() => ({ - data: new Uint8ClampedArray([255, 0, 0, 255]), - })), - putImageData: vi.fn(), - }; - - mockCanvas = { - width: 0, - height: 0, - getContext: vi.fn(() => mockContext), - }; - - originalCreateElement = document.createElement.bind(document); - document.createElement = vi.fn((tag) => { - if (tag === "canvas") { - return mockCanvas; - } - return originalCreateElement(tag); - }); - }); - - afterEach(() => { - document.createElement = originalCreateElement; - }); - - it("should convert pixel data to SVG string", async () => { - const pixels = new Uint8ClampedArray([255, 0, 0, 128, 0, 255, 0, 128]); // 2 pixels - const width = 2; - const height = 1; - - const result = await uint8ClampedArrayToSVG({ pixels, width, height }); - - expect(result).toContain(" { - const pixels = new Uint8ClampedArray([255, 0, 0, 0, 0, 255, 0, 0]); // 2 pixels with 0 alpha - const width = 2; - const height = 1; - - await uint8ClampedArrayToSVG({ pixels, width, height }); - - // After processing, alpha values at indices 3 and 7 should be 255 - expect(pixels[3]).toBe(255); - expect(pixels[7]).toBe(255); - }); - - it("should create a canvas with correct dimensions", async () => { - const pixels = new Uint8ClampedArray(400); // 10x10 image * 4 channels - const width = 10; - const height = 10; - - await uint8ClampedArrayToSVG({ pixels, width, height }); - - expect(mockCanvas.width).toBe(10); - expect(mockCanvas.height).toBe(10); - }); - - it("should call ImageTracer.imagedataToSVG", async () => { - const pixels = new Uint8ClampedArray([255, 0, 0, 255]); - const width = 1; - const height = 1; - - await uint8ClampedArrayToSVG({ pixels, width, height }); - - expect(ImageTracer.imagedataToSVG).toHaveBeenCalledWith( - expect.any(Object), - expect.objectContaining({ - ltres: 0, - rightangleenhance: false, - }), - ); - }); - - it("should return null and log error when conversion fails", async () => { - const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {}); - - // Create a scenario that causes an error by making createElement throw - document.createElement = vi.fn(() => { - throw new Error("Canvas creation failed"); - }); - - const pixels = new Uint8ClampedArray([255, 0, 0, 255]); - const result = await uint8ClampedArrayToSVG({ pixels, width: 1, height: 1 }); - - expect(result).toBeNull(); - expect(consoleSpy).toHaveBeenCalledWith("Error in SVG conversion:", expect.any(Error)); - - consoleSpy.mockRestore(); - }); - - it("should inject viewBox attribute into SVG", async () => { - const pixels = new Uint8ClampedArray(160); // 5x8 image * 4 channels - const width = 5; - const height = 8; - - const result = await uint8ClampedArrayToSVG({ pixels, width, height }); - - expect(result).toContain('viewBox="0 0 5 8"'); - }); - - it("should put ImageData onto the canvas", async () => { - const pixels = new Uint8ClampedArray([255, 0, 0, 255]); - const width = 1; - const height = 1; - - await uint8ClampedArrayToSVG({ pixels, width, height }); - - expect(mockContext.putImageData).toHaveBeenCalledWith(expect.any(Object), 0, 0); - }); - }); -}); diff --git a/packages/js/package.json b/packages/js/package.json index d75782f57..ae38bc730 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -23,21 +23,28 @@ "license": "MIT", "author": "Ryan-Millard", "type": "module", - "main": "./index.js", + "main": "./dist/browser/img2num.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "build:browser": "cross-env TARGET=browser vite build", + "build:node": "cross-env TARGET=node vite build", + "build": "pnpm build:browser && pnpm build:node" }, "engines": { "node": ">=14" }, - "files": [ - "build-wasm/index.js", - "build-wasm/index.wasm", - "index.js", - "safeWasmWrappers.js", - "wasmClient.js", - "wasmWorker.js", - "imageToUint8ClampedArray.js" - ], - "dependencies": {} + "exports": { + ".": { + "node": "./dist/node/img2num.js", + "browser": "./dist/browser/img2num.js", + "import": "./dist/browser/img2num.js", + "default": "./dist/browser/img2num.js" + } + }, + "optionalDependencies": { + "webgpu": "^0.4.0" + }, + "devDependencies": { + "cross-env": "^10.1.0", + "vite": "^8.0.14" + } } diff --git a/packages/js/imageToUint8ClampedArray.js b/packages/js/src/imageToUint8ClampedArray.js similarity index 100% rename from packages/js/imageToUint8ClampedArray.js rename to packages/js/src/imageToUint8ClampedArray.js diff --git a/packages/js/index.js b/packages/js/src/index.js similarity index 100% rename from packages/js/index.js rename to packages/js/src/index.js diff --git a/packages/js/safeWasmWrappers.js b/packages/js/src/safeWasmWrappers.js similarity index 98% rename from packages/js/safeWasmWrappers.js rename to packages/js/src/safeWasmWrappers.js index 801ea8c14..5c8ae5c5c 100644 --- a/packages/js/safeWasmWrappers.js +++ b/packages/js/src/safeWasmWrappers.js @@ -16,10 +16,10 @@ * Each function handles memory management and exposes a JavaScript-friendly API. */ -import { initWasmWorker, callWasm } from "./wasmClient.js"; +import { callWasm, initWasmWorker } from "./wasmClient.js"; // Ensure worker is ready as soon as this module is imported -initWasmWorker(); +await initWasmWorker(); //it's an async function as of #433 /** * @summary Apply a Gaussian blur to an image using FFT in WASM. diff --git a/packages/js/src/target/browser/worker.js b/packages/js/src/target/browser/worker.js new file mode 100644 index 000000000..1ef456501 --- /dev/null +++ b/packages/js/src/target/browser/worker.js @@ -0,0 +1,10 @@ +export async function createWorker() { + const worker = new Worker(new URL("@workers/wasmWorker.js", import.meta.url), { type: "module" }); + + return { + postMessage: (msg) => worker.postMessage(msg), + onMessage: (fn) => (worker.onmessage = (e) => fn(e.data)), + onError: (fn) => (worker.onerror = fn), + terminate: () => worker.terminate(), + }; +} diff --git a/packages/js/src/target/node/webgpu.js b/packages/js/src/target/node/webgpu.js new file mode 100644 index 000000000..819f51d20 --- /dev/null +++ b/packages/js/src/target/node/webgpu.js @@ -0,0 +1,35 @@ +import { create } from "webgpu"; + +let gpuInitPromise; + +export async function initWebGPU() { + if (globalThis.navigator?.gpu) return globalThis.navigator.gpu; + if (!gpuInitPromise) { + gpuInitPromise = Promise.resolve().then(() => { + const nativeGpu = create(["backend=vulkan"]); + globalThis.navigator ??= {}; + globalThis.navigator.gpu = nativeGpu; + return nativeGpu; + }); + } + return gpuInitPromise; +} + +// Drop-in companion deallocation handler +export async function destroyWebGPU() { + // 2. Sever the native reference links so Dawn can drop its ref counts + if (globalThis.navigator?.gpu) { + delete globalThis.navigator.gpu; + if (Object.keys(globalThis.navigator).length === 0) { + delete globalThis.navigator; + } + } + + // 3. Reset our internal module-level promise cache + gpuInitPromise = null; + + // 4. CRITICAL: Yield to the event loop. This gives Dawn's native engine + // a small time window to notice the reference count hit 0, dismantle its + // background threads, and flush outstanding callbacks BEFORE the process terminates. + await new Promise((resolve) => setTimeout(resolve, 50)); +} diff --git a/packages/js/src/target/node/worker.js b/packages/js/src/target/node/worker.js new file mode 100644 index 000000000..447dc488e --- /dev/null +++ b/packages/js/src/target/node/worker.js @@ -0,0 +1,20 @@ +import { Worker } from "node:worker_threads"; +import { fileURLToPath } from "node:url"; +import { dirname, resolve } from "node:path"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +export async function createWorker() { + // Reference the worker by path relative to this file at runtime, + // not via import.meta.url which Vite will try to bundle/inline. + const workerPath = resolve(__dirname, "./wasmWorker.js"); + + const worker = new Worker(workerPath); + + return { + postMessage: (msg) => worker.postMessage(msg), + onMessage: (fn) => worker.on("message", fn), + onError: (fn) => worker.on("error", fn), + terminate: () => worker.terminate(), + }; +} diff --git a/packages/js/wasmClient.js b/packages/js/src/wasmClient.js similarity index 84% rename from packages/js/wasmClient.js rename to packages/js/src/wasmClient.js index dcfe7ce50..33264c4e5 100644 --- a/packages/js/wasmClient.js +++ b/packages/js/src/wasmClient.js @@ -17,6 +17,8 @@ * This allows more granular control. */ +import { createWorker } from "@__TARGET__/worker.js"; + /** * Worker instance that handles communication with the WASM module. * @type {Worker | null} @@ -42,6 +44,22 @@ const callbacks = new Map(); */ let initialized = false; +/** + * Handle response messages from the WASM worker. + * @param {{ id: number, output?: any, returnValue?: any, error?: string }} data + */ +function handleMessage(data) { + const cb = callbacks.get(data.id); + if (!cb) return; + callbacks.delete(data.id); + + if (data.error) { + cb.reject(new Error(data.error)); + } else { + cb.resolve({ output: data.output, returnValue: data.returnValue }); + } +} + /** * @summary Initialize the WASM worker. * @@ -59,27 +77,21 @@ let initialized = false; * * @since 0.0.0 */ -export function initWasmWorker() { - if (initialized) return; - - worker = new Worker(new URL("./wasmWorker.js", import.meta.url), { type: "module" }); - worker.onmessage = ({ data }) => { - const { id, error, output, returnValue } = data; - const cb = callbacks.get(id); - if (!cb) return; +export async function initWasmWorker() { + if (initialized) return; - error ? cb.reject(new Error(error)) : cb.resolve({ output, returnValue }); - callbacks.delete(id); - }; + worker = await createWorker(); - worker.onerror = (event) => { - const err = new Error(event.message || "WASM worker error"); + worker.onMessage(handleMessage); + worker.onError((event) => { + const output = event.message || "WASM worker error"; + const err = new Error(`[Img2Num wasmClient] Error: ${output}`); for (const [_id, cb] of callbacks) { cb.reject(err); } callbacks.clear(); - }; + }); initialized = true; } @@ -120,10 +132,13 @@ export function initWasmWorker() { */ export async function callWasm({ funcName, args = {}, bufferKeys = [], returnType = "void" }) { if (!initialized) throw new Error("WASM worker not initialized. Call initWasmWorker() first."); + const id = idCounter++; + return new Promise((resolve, reject) => { callbacks.set(id, { resolve, reject }); - worker.postMessage({ id, funcName, args, bufferKeys, returnType }); // <-- send it + + worker.postMessage({ id, funcName, args, bufferKeys, returnType }); }); } diff --git a/packages/js/wasmWorker.js b/packages/js/src/workers/wasmWorker.js similarity index 85% rename from packages/js/wasmWorker.js rename to packages/js/src/workers/wasmWorker.js index 124be04e4..1729021a5 100644 --- a/packages/js/wasmWorker.js +++ b/packages/js/src/workers/wasmWorker.js @@ -38,7 +38,7 @@ * }); */ -import createImg2NumModule from "./build-wasm/index.js"; +import createImg2NumModule from "@wasm/index.js"; let wasmModule; @@ -154,7 +154,8 @@ async function callWasm(funcName, argsMap, returnType) { * - returnType: expected return type of the WASM export * @param {MessageEvent} event */ -self.onmessage = async ({ data }) => { + +async function handleMessage(data) { await readyPromise; const { id, funcName, args, bufferKeys, returnType } = data; @@ -205,13 +206,38 @@ self.onmessage = async ({ data }) => { wasmModule._free(result); } - self.postMessage({ id, output, returnValue }); + globalThis.postMessage({ id, output, returnValue }); } catch (error) { - self.postMessage({ id, error: error.message }); + globalThis.postMessage({ id, error: error.message }); } finally { // -------- Cleanup -------- for (const { ptr } of pointers.values()) { wasmModule._free(ptr); } } -}; +} + +if (__TARGET__ === "node") { + const { parentPort } = await import("node:worker_threads"); + const { initWebGPU, destroyWebGPU } = await import("../target/node/webgpu.js"); + + try { + await initWebGPU(); + } catch (err) { + console.error(`[Img2Num node/worker.js] Error: ${err}`); + } + + // 2. FIX THE TYPO: Polyfill globalThis.postMessage so handleMessage can call it natively! + globalThis.postMessage = (data) => parentPort.postMessage(data); + + // 3. Listen for incoming messages from the console app main thread + // (Node passes the raw payload directly, no nested event wrapper needed) + parentPort.on("message", async (data) => { + await handleMessage(data); + await destroyWebGPU(); + parentPort.close(); + }); +} else { + // Browser Worker setup: Standard event-unwrapping listener + globalThis.onmessage = ({ data }) => handleMessage(data); +} diff --git a/packages/js/tsconfig.typedoc.json b/packages/js/tsconfig.typedoc.json index d18a514e4..51a1d0e0f 100644 --- a/packages/js/tsconfig.typedoc.json +++ b/packages/js/tsconfig.typedoc.json @@ -7,7 +7,17 @@ "outDir": "dist", "rootDir": ".", "moduleResolution": "bundler", - "esModuleInterop": true + "esModuleInterop": true, + // 1. CRITICAL: Tells TypeScript to stop checking external dependency declarations. + // This immediately squashes type collisions originating inside node_modules. + "skipLibCheck": true, + + // 2. ISOLATION: Explicitly restrict libraries to standard Core ECMAScript features. + // By omitting "DOM" here, you prevent TypeScript from loading its built-in WebGPU types. + "lib": ["ESNext"] }, - "include": ["*.js"] + "include": ["*.js", "src/*.js", "src/target/**/*.js", "src/workers/*.js"], + "typedocOptions": { + "exclude": ["node_modules/", "dist/"] + } } diff --git a/packages/js/vite.config.js b/packages/js/vite.config.js new file mode 100644 index 000000000..ed1808d34 --- /dev/null +++ b/packages/js/vite.config.js @@ -0,0 +1,74 @@ +import { defineConfig } from "vite"; +import path from "path"; + +export default defineConfig(() => { + const __TARGET__ = process.env.TARGET ?? "browser"; + const isNode = __TARGET__ === "node"; + + return { + base: "./", + plugins: [], + + // NATIVE COPIER: Copies index.wasm into dist/node/ automatically + publicDir: isNode ? "build-wasm" : "src/workers", + + build: { + base: "./", + outDir: `dist/${__TARGET__}`, + assetsDir: ".", + emptyOutDir: true, + + // 1. CRITICAL: This tells the production bundler to preserve + // native Node subsystems (like 'module' and 'fs') instead of shimming them for the web. + ssr: isNode, + ...(isNode && { target: "node18" }), + + minify: !isNode, + + lib: isNode + ? { + entry: { + img2num: "src/index.js", + wasmWorker: "src/workers/wasmWorker.js", + }, + formats: ["es"], + fileName: (format, entryName) => `${entryName}.js`, + } + : { + entry: "src/index.js", + formats: ["es"], + }, + + rollupOptions: { + external: isNode ? ["node:worker_threads", "worker_threads", "node:path", "path", "node:url", "url", "node:webgpu", "webgpu", "node:module", "module"] : [], + + output: isNode + ? { + manualChunks(id) { + if (id.includes("build-wasm")) { + return "wasmWorker"; + } + }, + assetFileNames: "[name][extname]", + } + : {}, + }, + }, + + define: { + __TARGET__: JSON.stringify(__TARGET__), + }, + + resolve: { + alias: { + "@__TARGET__": path.resolve(__dirname, `./src/target/${__TARGET__}`), + "@wasm": path.resolve(__dirname, "./build-wasm"), + "@workers": path.resolve(__dirname, "./src/workers"), + }, + }, + + worker: { + format: "es", + }, + }; +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d986fa72c..e5109513a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -122,6 +122,15 @@ importers: specifier: ^6.0.3 version: 6.0.3 + example-apps/console-js: + dependencies: + img2num: + specifier: workspace:* + version: link:../../packages/js + sharp: + specifier: ^0.33.0 + version: 0.33.5 + example-apps/react-js: dependencies: '@tanstack/react-query': @@ -231,7 +240,18 @@ importers: specifier: ^4.1.6 version: 4.1.7(@types/node@25.6.0)(@vitest/coverage-v8@4.1.7)(jsdom@29.1.1)(vite@8.0.14(@types/node@25.6.0)(jiti@1.21.7)(terser@5.46.2)(yaml@2.8.3)) - packages/js: {} + packages/js: + devDependencies: + cross-env: + specifier: ^10.1.0 + version: 10.1.0 + vite: + specifier: ^8.0.14 + version: 8.0.14(@types/node@25.6.0)(jiti@1.21.7)(terser@5.46.2)(yaml@2.8.3) + optionalDependencies: + webgpu: + specifier: ^0.4.0 + version: 0.4.0 scripts/img2num-dev-scripts: dependencies: @@ -1522,6 +1542,9 @@ packages: '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@epic-web/invariant@1.0.0': + resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} + '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1626,34 +1649,68 @@ packages: resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} engines: {node: '>=18'} + '@img/sharp-darwin-arm64@0.33.5': + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + '@img/sharp-darwin-arm64@0.34.5': resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [darwin] + '@img/sharp-darwin-x64@0.33.5': + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + '@img/sharp-darwin-x64@0.34.5': resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [darwin] + '@img/sharp-libvips-darwin-arm64@1.0.4': + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + cpu: [arm64] + os: [darwin] + '@img/sharp-libvips-darwin-arm64@1.2.4': resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} cpu: [arm64] os: [darwin] + '@img/sharp-libvips-darwin-x64@1.0.4': + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + cpu: [x64] + os: [darwin] + '@img/sharp-libvips-darwin-x64@1.2.4': resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} cpu: [x64] os: [darwin] + '@img/sharp-libvips-linux-arm64@1.0.4': + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@img/sharp-libvips-linux-arm64@1.2.4': resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] libc: [glibc] + '@img/sharp-libvips-linux-arm@1.0.5': + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + cpu: [arm] + os: [linux] + libc: [glibc] + '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] @@ -1672,30 +1729,61 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-libvips-linux-s390x@1.0.4': + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] libc: [glibc] + '@img/sharp-libvips-linux-x64@1.0.4': + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + cpu: [x64] + os: [linux] + libc: [glibc] + '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] libc: [glibc] + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + cpu: [arm64] + os: [linux] + libc: [musl] + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] libc: [musl] + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + cpu: [x64] + os: [linux] + libc: [musl] + '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] libc: [musl] + '@img/sharp-linux-arm64@0.33.5': + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1703,6 +1791,13 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-linux-arm@0.33.5': + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1724,6 +1819,13 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-linux-s390x@0.33.5': + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1731,6 +1833,13 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-linux-x64@0.33.5': + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1738,6 +1847,13 @@ packages: os: [linux] libc: [glibc] + '@img/sharp-linuxmusl-arm64@0.33.5': + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1745,6 +1861,13 @@ packages: os: [linux] libc: [musl] + '@img/sharp-linuxmusl-x64@0.33.5': + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1752,6 +1875,11 @@ packages: os: [linux] libc: [musl] + '@img/sharp-wasm32@0.33.5': + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1763,12 +1891,24 @@ packages: cpu: [arm64] os: [win32] + '@img/sharp-win32-ia32@0.33.5': + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + '@img/sharp-win32-ia32@0.34.5': resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ia32] os: [win32] + '@img/sharp-win32-x64@0.33.5': + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@img/sharp-win32-x64@0.34.5': resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -2922,6 +3062,9 @@ packages: '@webassemblyjs/wast-printer@1.14.1': resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + '@webgpu/types@0.1.70': + resolution: {integrity: sha512-LFiNHHKMvmAEvwVew3JLJmTdShhbdwRFSImUshGhE2mGE8ybQzIo63l5uRp+YKnNx+8Qno8Kf6gN+DKMreIJCA==} + '@xtuc/ieee754@1.2.0': resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} @@ -3368,6 +3511,13 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + colord@2.9.3: resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} @@ -3569,6 +3719,11 @@ packages: typescript: optional: true + cross-env@10.1.0: + resolution: {integrity: sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==} + engines: {node: '>=20'} + hasBin: true + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -4895,6 +5050,9 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-arrayish@0.3.4: + resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -6921,6 +7079,10 @@ packages: shallowequal@1.1.0: resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} + sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + sharp@0.34.5: resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -6959,6 +7121,9 @@ packages: signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + simple-swizzle@0.2.4: + resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} + sirv@2.0.4: resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} engines: {node: '>= 10'} @@ -7644,6 +7809,9 @@ packages: web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + webgpu@0.4.0: + resolution: {integrity: sha512-F5pimn3Aoi0zWjuRdiVs5TnrUwSzD2lESBohsIUsqyitWkGRQlXU2fhV6ycXlQTa1bvAf3sjqiUpBEpmSQ5ptA==} + webidl-conversions@8.0.1: resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} engines: {node: '>=20'} @@ -9962,6 +10130,8 @@ snapshots: tslib: 2.8.1 optional: true + '@epic-web/invariant@1.0.0': {} + '@eslint-community/eslint-utils@4.9.1(eslint@10.2.1(jiti@1.21.7))': dependencies: eslint: 10.2.1(jiti@1.21.7) @@ -10060,25 +10230,47 @@ snapshots: '@img/colour@1.1.0': {} + '@img/sharp-darwin-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.4 + optional: true + '@img/sharp-darwin-arm64@0.34.5': optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.2.4 optional: true + '@img/sharp-darwin-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.4 + optional: true + '@img/sharp-darwin-x64@0.34.5': optionalDependencies: '@img/sharp-libvips-darwin-x64': 1.2.4 optional: true + '@img/sharp-libvips-darwin-arm64@1.0.4': + optional: true + '@img/sharp-libvips-darwin-arm64@1.2.4': optional: true + '@img/sharp-libvips-darwin-x64@1.0.4': + optional: true + '@img/sharp-libvips-darwin-x64@1.2.4': optional: true + '@img/sharp-libvips-linux-arm64@1.0.4': + optional: true + '@img/sharp-libvips-linux-arm64@1.2.4': optional: true + '@img/sharp-libvips-linux-arm@1.0.5': + optional: true + '@img/sharp-libvips-linux-arm@1.2.4': optional: true @@ -10088,23 +10280,45 @@ snapshots: '@img/sharp-libvips-linux-riscv64@1.2.4': optional: true + '@img/sharp-libvips-linux-s390x@1.0.4': + optional: true + '@img/sharp-libvips-linux-s390x@1.2.4': optional: true + '@img/sharp-libvips-linux-x64@1.0.4': + optional: true + '@img/sharp-libvips-linux-x64@1.2.4': optional: true + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + optional: true + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': optional: true + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + optional: true + '@img/sharp-libvips-linuxmusl-x64@1.2.4': optional: true + '@img/sharp-linux-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.4 + optional: true + '@img/sharp-linux-arm64@0.34.5': optionalDependencies: '@img/sharp-libvips-linux-arm64': 1.2.4 optional: true + '@img/sharp-linux-arm@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.5 + optional: true + '@img/sharp-linux-arm@0.34.5': optionalDependencies: '@img/sharp-libvips-linux-arm': 1.2.4 @@ -10120,26 +10334,51 @@ snapshots: '@img/sharp-libvips-linux-riscv64': 1.2.4 optional: true + '@img/sharp-linux-s390x@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.0.4 + optional: true + '@img/sharp-linux-s390x@0.34.5': optionalDependencies: '@img/sharp-libvips-linux-s390x': 1.2.4 optional: true + '@img/sharp-linux-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.4 + optional: true + '@img/sharp-linux-x64@0.34.5': optionalDependencies: '@img/sharp-libvips-linux-x64': 1.2.4 optional: true + '@img/sharp-linuxmusl-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + optional: true + '@img/sharp-linuxmusl-arm64@0.34.5': optionalDependencies: '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 optional: true + '@img/sharp-linuxmusl-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + optional: true + '@img/sharp-linuxmusl-x64@0.34.5': optionalDependencies: '@img/sharp-libvips-linuxmusl-x64': 1.2.4 optional: true + '@img/sharp-wasm32@0.33.5': + dependencies: + '@emnapi/runtime': 1.10.0 + optional: true + '@img/sharp-wasm32@0.34.5': dependencies: '@emnapi/runtime': 1.10.0 @@ -10148,9 +10387,15 @@ snapshots: '@img/sharp-win32-arm64@0.34.5': optional: true + '@img/sharp-win32-ia32@0.33.5': + optional: true + '@img/sharp-win32-ia32@0.34.5': optional: true + '@img/sharp-win32-x64@0.33.5': + optional: true + '@img/sharp-win32-x64@0.34.5': optional: true @@ -11403,6 +11648,9 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 + '@webgpu/types@0.1.70': + optional: true + '@xtuc/ieee754@1.2.0': {} '@xtuc/long@4.2.2': {} @@ -11902,6 +12150,16 @@ snapshots: color-name@1.1.4: {} + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.4 + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + colord@2.9.3: {} colorette@2.0.20: {} @@ -12136,6 +12394,11 @@ snapshots: optionalDependencies: typescript: 6.0.3 + cross-env@10.1.0: + dependencies: + '@epic-web/invariant': 1.0.0 + cross-spawn: 7.0.6 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -13688,6 +13951,8 @@ snapshots: is-arrayish@0.2.1: {} + is-arrayish@0.3.4: {} + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -16164,6 +16429,32 @@ snapshots: shallowequal@1.1.0: {} + sharp@0.33.5: + dependencies: + color: 4.2.3 + detect-libc: 2.1.2 + semver: 7.7.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 + sharp@0.34.5: dependencies: '@img/colour': 1.1.0 @@ -16235,6 +16526,10 @@ snapshots: signal-exit@3.0.7: {} + simple-swizzle@0.2.4: + dependencies: + is-arrayish: 0.3.4 + sirv@2.0.4: dependencies: '@polka/url': 1.0.0-next.29 @@ -16868,6 +17163,14 @@ snapshots: web-namespaces@2.0.1: {} + webgpu@0.4.0: + dependencies: + '@webgpu/types': 0.1.70 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + optional: true + webidl-conversions@8.0.1: {} webpack-bundle-analyzer@4.10.2: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ce54c4603..9b2e92428 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,7 @@ packages: - packages/js - example-apps/react-js + - example-apps/console-js - docs - scripts/img2num-dev-scripts @@ -10,3 +11,4 @@ onlyBuiltDependencies: - core-js-pure - esbuild - sharp + - webgpu diff --git a/test.jpg b/test.jpg new file mode 100644 index 000000000..bd6f8ecc6 Binary files /dev/null and b/test.jpg differ