2

TL/DR: How to convert Uint8ClampedArray with RGBA values to an image without using DOM (canvas element) or OffscreenCanvas?

I'm looking for a way to convert a Uint8ClampedArray object, which holds RGBA values, to a valid Blob object or a Blob URL using URL.createObjectURL for data which was created by the blurhashor fast-blurhash packages, with the restriction of not being able to use the DOM (canvas, etc.) or OffscreenCanvas.

The blurhash library converts a "blurhash" to a Uint8ClampedArray object, holding RGBA values, which then can be used to render a placeholder image; most often by using a canvas element.

In my case I'd like to create a viewable in-browser image from the Uint8ClampedArray object without using DOM-related functions or OffscreenCanvas.

Is there any way to convert the given Uint8ClampedArray data to a viewable image using Blob, a Blob URL or any other browser-native functionality without using canvas?

Thanks a lot!

thasmo
  • 9,159
  • 6
  • 27
  • 32

1 Answers1

2

The best would have been to have a way to go from an ImageBitmap to a Blob directly, but there isn't, yet (though a bitmaprenderer canvas is very close to this).

But you could write yourself a png (or any other format) encoder, or use one already available on the Internets (here I'll use https://github.com/wheany/js-png-encoder because it was already in my browser's history, for some reasons).

// We use a 32bit Array because it's faster to set pixels on it
const data = new Uint32Array(300*150);
// make some noise
for(let i = 0; i<data.length; i++) {
  data[i] = Math.random() * 0xFFFFFF + 0xFF000000;
}
const uint8 = new Uint8ClampedArray(data.buffer);
const as_str = [...uint8].map(byte => String.fromCharCode(byte)).join("");
const png_str = generatePng(300, 150, as_str);
const png_arr = new Uint8Array(png_str.length);
for (let i = 0; i < png_str.length; i++) {
  png_arr[i] = png_str.charCodeAt(i);
}
const blob = new Blob([png_arr], {type: "image/png"});
const img = new Image();
img.src = URL.createObjectURL(blob);
document.body.append(img)
<script src="https://cdn.jsdelivr.net/gh/wheany/js-png-encoder@master/generatepng.js"></script>

However this may not be such a good idea. Just in this snippet, we did copy the image's data 5 times in memory (2 times as raw pixel data, 3 times as png), all the path we used to do all the conversions were extremely slow, compared to what native libpng can do. I don't know why you think you can't use a canvas, but note that even node has a 2D canvas implementation.

Kaiido
  • 123,334
  • 13
  • 219
  • 285