14

I have a Uint8Array named frameBytes. I have created RGBA values from this byte array with this code:

for (var i = 0; i < frameBytes.length; i++) {
    imgData.data[4 * i] = frameBytes[i];// red
    imgData.data[4 * i + 1] = frameBytes[i]; // green
    imgData.data[4 * i + 2] = frameBytes[i];// blue
    imgData.data[4 * i + 3] = 255; // alpha
}

Then I have shown this RGBA values to canvas using the following code:

var ctx = fingerFrame.getContext('2d');
var imgData = ctx.createImageData(fingerFrame.width, fingerFrame.height);
ctx.putImageData(imgData, 0, 0, 0, 0, fingerFrame.width, fingerFrame.height);

After that, from the canvas, I used this to put the image inside an image tag:

const img = document.getElementById('i');
img.src = fingerFrame.toDataURL();

But I do not want to use canvas. I want to show images in image tags from Uint8Array directly. How can I do that?

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
Christopher Marlowe
  • 2,098
  • 6
  • 38
  • 68
  • And why don't you want to use a canvas? That's the most straightforward way. Other ways would all involve you use an external script able to encode in an image format from your RGBA values i.e it would have to build entirely the image file. – Kaiido May 31 '18 at 23:17
  • Can you give me some example of that ? – Christopher Marlowe Jun 03 '18 at 03:30
  • 1
    Of what "that"? Of building your own PNG encoder? Hum nope. [Specs for png are here](https://www.w3.org/TR/2003/REC-PNG-20031110/), [pako.js](https://github.com/nodeca/pako/) will probably help you too, but that's about all I can do for you. If you meant an example of doing it with canvas, then https://jsfiddle.net/9ggpqzL0/ – Kaiido Jun 03 '18 at 08:05
  • I realise this is an old post, but for anyone it might help: look at this fine [utility](https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727). It is a simple code block you can copy. You can use it this way: `var base64image = "data:image/png;base64,"+ bytesToBase64(data)` making sure that the mimetype `image/png` is correct (_you will have to figure that out_). – akeem Jan 31 '20 at 03:51

3 Answers3

21

I want to show image in image tag from Uint8Array directly

This is very simple using a Blob:

// Small red dot image
const content = new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 5, 0, 0, 0, 5, 8, 6, 0, 0, 0, 141, 111, 38, 229, 0, 0, 0, 28, 73, 68, 65, 84, 8, 215, 99, 248, 255, 255, 63, 195, 127, 6, 32, 5, 195, 32, 18, 132, 208, 49, 241, 130, 88, 205, 4, 0, 14, 245, 53, 203, 209, 142, 14, 31, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130]);

document.getElementById('my-img').src = URL.createObjectURL(
  new Blob([content.buffer], { type: 'image/png' } /* (1) */)
);
Should display a small red dot: <img id="my-img">

(1) It also works without specifying the Blob MIME type.

tanguy_k
  • 11,307
  • 6
  • 54
  • 58
6

I think you can make an ImageData directly, from the example on that page:

const arr = new Uint8ClampedArray(40000);

// Iterate through every pixel
for (let i = 0; i < arr.length; i += 4) {
  arr[i + 0] = 0;    // R value
  arr[i + 1] = 190;  // G value
  arr[i + 2] = 0;    // B value
  arr[i + 3] = 255;  // A value
}

// Initialize a new ImageData object
let imageData = new ImageData(arr, 200);

Unfortunately there does not seem to be any way to display an ImageData in an <img> element, only in a <canvas>. <img> requires an actual image file.

Fortunately the BMP format is widely supported and supports raw RGBA data. You just need to prepend an appropriate BMP header. Once that is done you can pass your data to the <img> using the technique outlined by Ben Fortune. I would NOT use a data: URL even though you find people all over the web using it. It is needlessly inefficient.

Here's some example code. It appends the pixel data to the bitmap header in a single buffer since that will be more efficient. If you already have the data you could create a separate Uint8Array just for the header and concatenate them in the Blob constructor, i.e. new Blob([header, pixels]). I haven't tried that.

const header_size = 70;

const width = 255;
const height = 255;
const image_size = width * height * 4;

const arr = new Uint8Array(header_size + image_size);
const view = new DataView(arr.buffer);

// File Header

// BM magic number.
view.setUint16(0, 0x424D, false);
// File size.
view.setUint32(2, arr.length, true);
// Offset to image data.
view.setUint32(10, header_size, true);

// BITMAPINFOHEADER

// Size of BITMAPINFOHEADER
view.setUint32(14, 40, true);
// Width
view.setInt32(18, width, true);
// Height (signed because negative values flip
// the image vertically).
view.setInt32(22, height, true);
// Number of colour planes (colours stored as
// separate images; must be 1).
view.setUint16(26, 1, true);
// Bits per pixel.
view.setUint16(28, 32, true);
// Compression method, 6 = BI_ALPHABITFIELDS
view.setUint32(30, 6, true);
// Image size in bytes.
view.setUint32(34, image_size, true);
// Horizontal resolution, pixels per metre.
// This will be unused in this situation.
view.setInt32(38, 10000, true);
// Vertical resolution, pixels per metre.
view.setInt32(42, 10000, true);
// Number of colours. 0 = all
view.setUint32(46, 0, true);
// Number of important colours. 0 = all
view.setUint32(50, 0, true);

// Colour table. Because we used BI_ALPHABITFIELDS
// this specifies the R, G, B and A bitmasks.

// Red
view.setUint32(54, 0x000000FF, true);
// Green
view.setUint32(58, 0x0000FF00, true);
// Blue
view.setUint32(62, 0x00FF0000, true);
// Alpha
view.setUint32(66, 0xFF000000, true);

// Pixel data.
for (let w = 0; w < width; ++w) {
  for (let h = 0; h < height; ++h) {
    const offset = header_size + (h * width + w) * 4;
    arr[offset + 0] = w;     // R value
    arr[offset + 1] = h;     // G value
    arr[offset + 2] = 255-w; // B value
    arr[offset + 3] = 255-h; // A value
  }
}

const blob = new Blob([arr], { type: "image/bmp" });
const url = window.URL.createObjectURL(blob);

const img = document.getElementById('i');
img.src = url;
<img id="i">

A big caveat is that this RGBA variant of BMP is not widely supported at all. Chrome seems to support it. Firefox doesn't, nor does Apple's finder. If you're writing an Electron app it should be fine but I wouldn't use it on the web.

However, since you have set alpha to 255 I'm guessing you don't even need the alpha channel. In that case you can use BI_RGB instead:

    const header_size = 54;

    const width = 255;
    const height = 255;
       
    const image_size = width * height * 4;

    const arr = new Uint8Array(header_size + image_size);
    const view = new DataView(arr.buffer);

    // File Header

    // BM magic number.
    view.setUint16(0, 0x424D, false);
    // File size.
    view.setUint32(2, arr.length, true);
    // Offset to image data.
    view.setUint32(10, header_size, true);

    // BITMAPINFOHEADER

    // Size of BITMAPINFOHEADER
    view.setUint32(14, 40, true);
    // Width
    view.setInt32(18, width, true);
    // Height (signed because negative values flip
    // the image vertically).
    view.setInt32(22, height, true);
    // Number of colour planes (colours stored as
    // separate images; must be 1).
    view.setUint16(26, 1, true);
    // Bits per pixel.
    view.setUint16(28, 32, true);
    // Compression method, 0 = BI_RGB
    view.setUint32(30, 0, true);
    // Image size in bytes.
    view.setUint32(34, image_size, true);
    // Horizontal resolution, pixels per metre.
    // This will be unused in this situation.
    view.setInt32(38, 10000, true);
    // Vertical resolution, pixels per metre.
    view.setInt32(42, 10000, true);
    // Number of colours. 0 = all
    view.setUint32(46, 0, true);
    // Number of important colours. 0 = all
    view.setUint32(50, 0, true);

    // Pixel data.
    for (let w = 0; w < width; ++w) {
      for (let h = 0; h < height; ++h) {
        const offset = header_size + (h * width + w) * 4;
        arr[offset + 0] = w;     // R value
        arr[offset + 1] = h;     // G value
        arr[offset + 2] = 255-w; // B value
        // arr[offset + 3] is ignored but must still be present because we specified 32 BPP
      }
    }

    const blob = new Blob([arr], { type: "image/bmp" });
    const url = window.URL.createObjectURL(blob);

    const img = document.getElementById('i');
    img.src = url;
<img id="i">

In the above example I still use 32 BPP, but because I set the compression to BI_RGB the alpha channel is ignored. This is a bit wasteful of memory. You can set it to 24 BPP instead and then only use 3 bytes per pixel, but the caveat is each row has to be padded to up to a multiple of 4 bytes, which I couldn't be bothered to do here.

Timmmm
  • 88,195
  • 71
  • 364
  • 509
-2

I belive you can use the btoa function, which would create a base64 ASCII representation of the image data..

 const img = document.getElementById('i');  

 //ImageDataArrayBuffer is your uint8 Array Buffer
 img.src =  "data:image/png;base64,"+ btoa(String.fromCharCode.apply(null, ImageDataArrayBuffer));