Documentation/Source example
Example source
samples/wasm/WebAssembly Image Filters.worker.js
Referenced by:module:WebAssembly
// JSplitter WebAssembly example - Worker.
// Compiles C-generated WASM once, retains image bytes in linear memory and
// recomputes filters on requests without sending pixels back into the Worker.
// No browser fetch(), WASI, TextEncoder, or external JavaScript dependencies.
let wasm = null;
let srcOffset = 0;
let dstOffset = 0;
let width = 0;
let height = 0;
let imageRevision = 0;
function ensureMemory(requiredBytes) {
const memory = wasm.exports.memory;
const availableBytes = memory.buffer.byteLength;
if (requiredBytes > availableBytes) {
memory.grow(Math.ceil((requiredBytes - availableBytes) / 65536));
}
}
function render(threshold, mode, requestId) {
if (!width || !height) return;
const start = performance.now();
const edgeCount = wasm.exports.filter(
srcOffset, dstOffset, width, height, threshold, mode
);
if (edgeCount < 0) throw new Error('Invalid Sobel filter parameters');
const byteLength = width * height * 4;
// Copy the WASM memory region to an independent transferable ArrayBuffer.
// This is necessary: WASM linear memory must remain owned by this Worker.
const pixels = new Uint8Array(byteLength);
pixels.set(new Uint8Array(wasm.exports.memory.buffer, dstOffset, byteLength));
const elapsedMs = performance.now() - start;
postMessage({
type: 'result', revision: imageRevision, requestId,
width, height, pixels, edgeCount, elapsedMs
}, [pixels.buffer]);
}
onmessage = function (e) {
const m = e.data;
try {
if (m.type === 'load') {
if (!m.pixels || !m.width || !m.height ||
m.pixels.length !== m.width * m.height * 4 ||
m.width > 2048 || m.height > 2048 || m.width < 3 || m.height < 3) {
throw new Error('Invalid image dimensions or RGBA data');
}
width = m.width;
height = m.height;
imageRevision = m.revision;
const byteLength = width * height * 4;
// The toolchain exports __heap_base; it indicates the beginning of
// unused WASM linear memory after compiled data/stack.
const base = wasm.exports.__heap_base.value;
srcOffset = (base + 15) & ~15;
dstOffset = (srcOffset + byteLength + 15) & ~15;
ensureMemory(dstOffset + byteLength);
new Uint8Array(wasm.exports.memory.buffer, srcOffset, byteLength)
.set(m.pixels);
render(m.threshold, m.mode, m.requestId);
} else if (m.type === 'filter') {
if (m.revision === imageRevision) {
render(m.threshold, m.mode, m.requestId);
}
}
} catch (err) {
postMessage({type: 'error', message: String(err)});
}
};
try {
// For a file-backed Worker the relative path is resolved relative to
// this Worker file by JSplitter's file path resolver.
const bytes = utils.ReadBinaryFile('album_art_filters.wasm');
if (!bytes) throw new Error('Cannot read album_art_filters.wasm');
const module = new WebAssembly.Module(bytes);
wasm = new WebAssembly.Instance(module);
postMessage({type: 'ready'});
} catch (err) {
postMessage({type: 'error', message: 'WASM initialization: ' + err});
}