0

To transform binary image data (e.g. from a file input) into a canvas image source, createImageBitmap or Image.decode can be used.

Is there a cancellable alternative to these methods?

I want to render images from a multiple file input to a canvas element, one at a time. My current approach causes severe delays, if additional decode operations are started while the previous ones still run (I am only interested in the result of the most recent one).

I found out that Image.decode can be cancelled in some browsers, by reassinging the image src. However, this does not work universally (e.g. not in Firefox).

I am open to use third party libraries, as long as they are performant (WebAssembly) and free and if they at least support the most basic image formats like JPEG and PNG.

Edit

To clarify things, I created a Codepen: https://codepen.io/Tims777/pen/bGxvgBQ

Just select a bunch (100+) of large (~20MP) JPEGs and hold down the up button for a while to torture your CPU.

Tims
  • 1
  • 4
  • 1
    _"My current approach causes severe delays"_ - please post your current actual code, and please explain exactly what you mean by "severe delays" - do you mean the browser's UI thread is blocked - or just that it takes a long time (but is otherwise non-blocking)? – Dai Mar 14 '23 at 03:19
  • The code is part of a larger application so I did not include an exemple yet. I will try and put together a minimal example. – Tims Mar 14 '23 at 03:24
  • By `Image.decode`, do you mean `HTMLImageElement.decode`? If so, then because that's a `Promise`-returning function it means that you can always _ignore_ the Promise's result when cancelled or after a timeout, even if the `Promise`-returning function doesn't support cancelling. – Dai Mar 14 '23 at 03:30
  • I added an example now. As you can see, ignoring the promise result is not enough, because it will leave the decode operation running in the background. – Tims Mar 14 '23 at 05:15
  • As a workaround, you could prevent the user from having more than N-many concurrent `decode` operations, say, 3 or 4, that way having 20 images wouldn't make the computer melt - though I agree it's not exactly ideal. – Dai Mar 14 '23 at 05:40
  • BTW, people report that Firefox _does_ support cancelling an image-load operation, but the catch is you have to reset `.src` to a valid image URI, not an empty-string (or has it changed in recent Firefox builds?). [Another suggestion](https://stackoverflow.com/a/48639311/159145) is to load the image in a WebWorker, and to kill the worker on-abort - though I don't know how well that would work in-practice. – Dai Mar 14 '23 at 05:42

1 Answers1

0

Thanks to Dai I can now answer my own question:

Using a WebWorker makes for a pretty straightforward solution, since the worker can be cancelled right in the middle of execution whenever the need arises.

The worker.js itself can be as simple as this:

onmessage = function(event) {
    createImageBitmap(event.data).then(image => postMessage(image));
}

And here is my reworked update function:

function update() {
    abortController?.abort();
    abortController = new AbortController();

    if (indexInput.valueAsNumber > fileInput.files.length) return;
    const file = fileInput.files[indexInput.valueAsNumber];
    const signal = abortController.signal;

    const worker = new Worker("worker.js");
    worker.onmessage = (message) => ctx1.drawImage(message.data, 0, 0, canvas1.width, canvas1.height);
    worker.postMessage(file);
    signal.onabort = () => worker.terminate();
}
Tims
  • 1
  • 4
  • Btw your code doesnt handle the case when `fileInput.files.length === 0` (which is when the user clicks Browse and removes the file selection) – Dai Mar 14 '23 at 11:06
  • Also I’m curious if you can cache the worker instance - and I’m unsure if it’s always safe to call `postMessage` before wiring-up the `message` event-handler. – Dai Mar 14 '23 at 11:08
  • I don't think that it is possible to restart a worker instance, once it has been terminated - so there is probably no way around recreating the worker every time. Regarding your other two points, I tried updating my answer accordingly to address those. – Tims Mar 14 '23 at 11:30