You don't have to keep it displayed on canvas given it isn't "animated". You can use temporary canvases for the initial render then convert it to a Blob (and from a Blob to an Object URL). Most browsers at that point use the GPU's transforms to quickly convert the image data to a basic compressed JPG or PNG and then the usual browser performance mechanics of an image apply (and as a URL you can use it in regular IMG tags or CSS things like background-image). Presuming you remember to revoke the Object URLs when you are done with the hashes to avoid Blob leaks, performance is improved over the "simpler" canvas techniques that just display the rendered canvases.
If code helps, here's how it looks as a React Hook in Typescript:
The cost is in the initial render to a canvas. Once on a canvas, there is no advantage to converting it to a blob for display in an IMG tag as far as performance goes.
That's not what my testing has shown in practice. Memory use drops dramatically in the conversion from canvas to blob when the browser compresses the render buffer to JPG/PNG under the hood. GPU usage seems to drop in the browsers I had under performance tools, but it subtle enough it could be just margin of error (as memory pressure was my bigger issue, that wasn't something I was keeping that good of an eye on).
But yes the biggest performance gains aren't in pure, static IMG tag usage scenarios, the biggest performance gains I saw were in combo with CSS animations, and that was something important to what I was studying. As Blurhases are useful loading states this seems a common use case to me of having a Blurhash shown/involved in things like navigation transitions, and it seems pretty clear browsers have a lot more tricks for optimizing static images involved in CSS Animations than they do for canvas surfaces.
I was looking for a way to better amortize or skip the initial render as well. It should be possible to take the TypedArray `decode` buffer and directly construct a Blob from it, but I couldn't find a MIME Type that matched the Bitmap format Blurhash produces (and Canvas setImageData reads) in the time I've had to poke at the project so far. As I mentioned, memory pressure was a performance concern in my testing, so I'd also be curious about the performance trade-off of paying for an initial canvas render and getting what seems to be a "nearly free" compression to JPG or PNG from the GPU in converting that to blob, versus using a larger bitmap blob but no canvas render step.
If code helps, here's how it looks as a React Hook in Typescript:
https://gist.github.com/WorldMaker/a3cbe0059acd827edee568198...
(I offered the code to the react-blurhash repository in its Issues.)