Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Uint8Array to image in JavaScript

I have 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 GRBA 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 canvas I used to image in image tag using the following code:

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

But I do not want to use canvas. I want to show image in image tag from Uint8Array directly. How can I do that? Any help will be highly appreciated.

like image 321
Christopher Marlowe Avatar asked May 31 '18 09:05

Christopher Marlowe


2 Answers

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.

like image 171
tanguy_k Avatar answered Sep 21 '22 20:09

tanguy_k


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.

like image 21
Timmmm Avatar answered Sep 17 '22 20:09

Timmmm