Web worker and HTML tweaks #1
23
dist/index.html
vendored
23
dist/index.html
vendored
@ -1,8 +1,8 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<script src="./wasm_exec.js"></script>
|
<script type="module" src="./wasm_exec.js"></script>
|
||||||
<script async src="./main.js"></script>
|
<script type="module" src="./main.js"></script>
|
||||||
<style>
|
<style>
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
body {
|
body {
|
||||||
@ -57,8 +57,19 @@
|
|||||||
algorithm.
|
algorithm.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Performance isn't great, as Go+Wasm only runs on a single thread. The
|
Running in the browser with Wasm causes a bit of a performance penalty.
|
||||||
Wasm call also blocks the rendering (that would be fixable, though).
|
Multithreading is not available (not that it matters much since
|
||||||
|
Floyd-Steinberg is single-threaded), and sending data back and forth
|
||||||
|
between JS and Wasm can take a little while.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
I've re-used code from
|
||||||
|
<a
|
||||||
|
href="https://www.sitepen.com/blog/using-webassembly-with-web-workers"
|
||||||
|
>this article</a
|
||||||
|
>
|
||||||
|
to make the Wasm code run in a web worker, with some adaptations for Go
|
||||||
|
oddities.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
If you're into that sort of thing, source code is available on
|
If you're into that sort of thing, source code is available on
|
||||||
@ -68,11 +79,13 @@
|
|||||||
</p>
|
</p>
|
||||||
<form>
|
<form>
|
||||||
<input type="file" id="source-image" accept="image/png, image/jpeg" />
|
<input type="file" id="source-image" accept="image/png, image/jpeg" />
|
||||||
<button id="go-btn" type="button">Go</button>
|
<button id="go-btn" type="button" disabled>Go</button>
|
||||||
</form>
|
</form>
|
||||||
|
<a id="output-wrapper" download="output.png">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<img id="output" />
|
<img id="output" />
|
||||||
</div>
|
</div>
|
||||||
|
</a>
|
||||||
</article>
|
</article>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
96
dist/main.js
vendored
96
dist/main.js
vendored
@ -1,38 +1,94 @@
|
|||||||
(async () => {
|
|
||||||
// Load element references
|
|
||||||
const fileInput = document.getElementById("source-image");
|
const fileInput = document.getElementById("source-image");
|
||||||
const btn = document.getElementById("go-btn");
|
const btn = document.getElementById("go-btn");
|
||||||
|
const output = document.getElementById("output");
|
||||||
|
const outputWrapper = document.getElementById("output-wrapper");
|
||||||
|
|
||||||
// Setup Wasm stuff
|
function wasmWorker(modulePath) {
|
||||||
const go = new Go();
|
// Create an object to later interact with
|
||||||
vm = await WebAssembly.instantiateStreaming(
|
const proxy = {};
|
||||||
fetch("./main.wasm"),
|
|
||||||
go.importObject
|
// Keep track of the messages being sent
|
||||||
);
|
// so we can resolve them correctly
|
||||||
go.run(vm.instance);
|
let id = 0;
|
||||||
|
let idPromises = {};
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const worker = new Worker("worker.js");
|
||||||
|
worker.postMessage({ eventType: "INITIALISE", eventData: modulePath });
|
||||||
|
worker.addEventListener("message", function (event) {
|
||||||
|
const { eventType, eventData, eventId } = event.data;
|
||||||
|
|
||||||
|
if (eventType === "INITIALISED") {
|
||||||
|
const methods = event.data.eventData;
|
||||||
|
methods.forEach((method) => {
|
||||||
|
proxy[method] = function () {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
worker.postMessage({
|
||||||
|
eventType: "CALL",
|
||||||
|
eventData: {
|
||||||
|
method: method,
|
||||||
|
arguments: Array.from(arguments), // arguments is not an array
|
||||||
|
},
|
||||||
|
eventId: id,
|
||||||
|
});
|
||||||
|
|
||||||
|
idPromises[id] = { resolve, reject };
|
||||||
|
id++;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
resolve(proxy);
|
||||||
|
return;
|
||||||
|
} else if (eventType === "RESULT") {
|
||||||
|
if (eventId !== undefined && idPromises[eventId]) {
|
||||||
|
idPromises[eventId].resolve(eventData);
|
||||||
|
delete idPromises[eventId];
|
||||||
|
}
|
||||||
|
} else if (eventType === "ERROR") {
|
||||||
|
if (eventId !== undefined && idPromises[eventId]) {
|
||||||
|
idPromises[eventId].reject(event.data.eventData);
|
||||||
|
delete idPromises[eventId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
worker.addEventListener("error", function (error) {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let workerResolve;
|
||||||
|
const wasmReady = new Promise((resolve) => {
|
||||||
|
workerResolve = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
let workerProxy;
|
||||||
|
wasmWorker("./main.wasm").then((w) => {
|
||||||
|
workerProxy = w;
|
||||||
|
workerResolve();
|
||||||
|
btn.removeAttribute("disabled");
|
||||||
|
});
|
||||||
|
|
||||||
// Setup event listener
|
|
||||||
btn.addEventListener("click", async () => {
|
btn.addEventListener("click", async () => {
|
||||||
// Clear image
|
// Clear image
|
||||||
outputElement = document.getElementById("output");
|
output.src = "";
|
||||||
outputElement.src = "";
|
|
||||||
|
|
||||||
// Check if a file was selected
|
// Check if a file was selected
|
||||||
if (fileInput.files.length === 0) {
|
if (fileInput.files.length === 0) {
|
||||||
alert("No file selected");
|
alert("No file selected");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.readAsArrayBuffer(fileInput.files[0]);
|
reader.readAsArrayBuffer(fileInput.files[0]);
|
||||||
reader.Read;
|
|
||||||
reader.onloadend = async (evt) => {
|
reader.onloadend = async (evt) => {
|
||||||
if (evt.target.readyState === FileReader.DONE) {
|
if (evt.target.readyState === FileReader.DONE) {
|
||||||
const array = new Uint8Array(evt.target.result);
|
const imageData = new Uint8Array(evt.target.result);
|
||||||
// Wasm magic happens here
|
const ditheredImage = await workerProxy.DitherNord(imageData);
|
||||||
ditheredImageData = await DitherNord(array);
|
|
||||||
document.getElementById("output").src =
|
const outputValue = `data:image/png;base64,${ditheredImage}`;
|
||||||
"data:image/png;base64," + ditheredImageData;
|
output.src = outputValue;
|
||||||
|
outputWrapper.href = outputValue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
})();
|
|
||||||
|
61
dist/worker.js
vendored
Normal file
61
dist/worker.js
vendored
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
importScripts("./wasm_exec.js");
|
||||||
|
|
||||||
|
if (!WebAssembly.instantiateStreaming) {
|
||||||
|
WebAssembly.instantiateStreaming = async (resp, importObject) => {
|
||||||
|
const source = await (await resp).arrayBuffer();
|
||||||
|
return await WebAssembly.instantiate(source, importObject);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create promise to handle Worker calls whilst
|
||||||
|
// module is still initialising
|
||||||
|
let wasmResolve;
|
||||||
|
const wasmReady = new Promise((resolve) => {
|
||||||
|
wasmResolve = resolve;
|
||||||
|
});
|
||||||
|
|
||||||
|
const go = new self.Go();
|
||||||
|
|
||||||
|
addEventListener(
|
||||||
|
"message",
|
||||||
|
async (e) => {
|
||||||
|
const { eventType, eventData, eventId } = e.data;
|
||||||
|
|
||||||
|
if (eventType === "INITIALISE") {
|
||||||
|
const instantiatedSource = await WebAssembly.instantiateStreaming(
|
||||||
|
fetch(eventData),
|
||||||
|
go.importObject
|
||||||
|
);
|
||||||
|
go.run(instantiatedSource.instance);
|
||||||
|
|
||||||
|
// Go does nor exposes the exports in the instantiated module :(((
|
||||||
|
const methods = ["DitherNord"];
|
||||||
|
wasmResolve(methods);
|
||||||
|
postMessage({
|
||||||
|
eventType: "INITIALISED",
|
||||||
|
eventData: methods,
|
||||||
|
});
|
||||||
|
} else if (eventType === "CALL") {
|
||||||
|
await wasmReady;
|
||||||
|
try {
|
||||||
|
const method = self[eventData.method];
|
||||||
|
const result = await method.apply(null, eventData.arguments);
|
||||||
|
self.postMessage({
|
||||||
|
eventType: "RESULT",
|
||||||
|
eventData: result,
|
||||||
|
eventId: eventId,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
self.postMessage({
|
||||||
|
eventType: "ERROR",
|
||||||
|
eventData:
|
||||||
|
"An error occured executing WASM instance function: " +
|
||||||
|
error.toString(),
|
||||||
|
eventId: eventId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
Loading…
Reference in New Issue
Block a user