/// /** * Runs a numbl filter script off the main thread so full-resolution images * with per-pixel scripts don't freeze the page. * * Contract: the script defines a function (default name `filterImage`) that * takes the image array `img` (H x W x 3 doubles, 0-255) and returns a new * image array. If the script has no function header it is run as-is with * `img` predefined and `out` read back. */ import { executeCode, RTV } from "numbl"; import type { RuntimeValue } from "numbl"; import { rgbaToTensorData, tensorToRaw, extractFunctionName, } from "./imageConvert.ts"; import type { FilterRequest, FilterResponse } from "./filterTypes.ts"; self.onmessage = (e: MessageEvent) => { const { id, script, width, height, rgba } = e.data; const logs: string[] = []; try { const { data, shape } = rgbaToTensorData(rgba, width, height); const img = RTV.tensor(data, shape); const fnName = extractFunctionName(script); const source = fnName ? `out = ${fnName}(img);` : script; const workspaceFiles = fnName ? [{ name: `${fnName}.m`, source: script }] : []; const t0 = performance.now(); const result = executeCode( source, { initialVariableValues: { img }, optimization: "1", // JS-JIT; browser-safe displayResults: false, onOutput: (text: string) => logs.push(text), }, workspaceFiles, "main.m" ); const elapsedMs = performance.now() - t0; const out: RuntimeValue | undefined = result.variableValues.out ?? result.returnValue; const image = tensorToRaw(out); const response: FilterResponse = { id, ok: true, width: image.width, height: image.height, rgba: image.rgba, logs, elapsedMs, }; self.postMessage(response, [image.rgba.buffer]); } catch (err) { const response: FilterResponse = { id, ok: false, error: err instanceof Error ? err.message : String(err), logs, }; self.postMessage(response); } };