JavaScript中的Uint8Array转换为图像

14

我有一个名为frameBytes的Uint8Array。我使用以下代码从这个字节数组创建RGBA值:

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
}

接下来,我将使用以下代码将这些RGBA值显示在画布上:

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

之后,我使用以下代码将画布中的图像放入图像标签中:

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

但我不想使用画布。我希望能直接在图像标签中显示来自Uint8Array的图像。我该怎么做?


为什么不想使用画布呢?这是最直接的方法。其他方法都需要使用外部脚本,能够从RGBA值编码成图像格式,也就是说,它必须完全构建图像文件。 - Kaiido
你能给我一些例子吗? - Christopher Marlowe
1
那个“什么”?建立自己的PNG编码器吗?嗯,不是。PNG规格在这里pako.js可能也会对你有所帮助,但这就是我能为你做的全部了。如果你是指使用画布的示例,那么可以参考https://jsfiddle.net/9ggpqzL0/。 - Kaiido
我知道这是一篇旧帖子,但对于任何需要帮助的人:请查看这个很好的实用工具。这是一个简单的代码块,您可以复制它。您可以这样使用它:var base64image = "data:image/png;base64,"+ bytesToBase64(data) 确保 mimetype image/png 是正确的(_您将不得不自己解决_)。 - akeem
3个回答

22
我想直接从Uint8Array中将图像显示在图像标记中。
这很简单,使用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) 不指定 Blob MIME 类型也可以正常工作。


1
这只是针对uint8array是PNG格式的情况,那么原始数据怎么办? - Yeets

6

我认为你可以直接根据该页面上的示例,创建一个ImageData

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);

遗憾的是,似乎没有办法在 <img> 元素中显示 ImageData,只能在 <canvas> 中显示。因为 <img> 需要一个真正的图像文件。

幸运的是,BMP 格式被广泛支持,并支持原始的 RGBA 数据。您只需要添加适当的 BMP 头文件即可。完成后,您可以使用 Ben Fortune 提出的技术将数据传递给 <img>。尽管在全网上您可能会发现人们使用它,但我不建议使用 data: URL,因为这样效率低下。

以下是一些示例代码。它将像素数据附加到位图头文件中的单个缓冲区中,因为这样更有效率。如果您已经拥有数据,则可以创建一个单独的 Uint8Array 用于头部,并在 Blob 构造函数中连接它们,即 new Blob([header, pixels])。我没有尝试过这种方法。

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">

需要注意的是,这种 RGBA 变体的 BMP 格式在广泛支持方面并不如其他格式。Chrome 可以支持它,但 Firefox 和 Apple 的 finder 不支持。如果你正在编写 Electron 应用程序,则应该可以使用它,但是我不建议在Web上使用。

然而,既然您将 alpha 设置为 255,我猜您甚至不需要 alpha 通道。在这种情况下,您可以使用 BI_RGB 代替:

    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">

在上面的例子中,我仍然使用32 BPP,但是因为我将压缩设置为BI_RGB,所以alpha通道被忽略了。这浪费了一些内存。您可以将其设置为24 BPP,然后每个像素只使用3个字节,但是注意每行必须填充到4个字节的倍数,这里我没去纠结。

-2

我相信你可以使用btoa函数,它可以创建图像数据的base64 ASCII表示。

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

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

你需要在数据URL前面添加 data:image/png;base64, - Ben Fortune
3
uint8Array OP仅包含RGBA值,它不是一个PNG文件(没有标头,未压缩数据等)。你的解决方案不会起作用。 - Kaiido

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接