如何使用 javascript 从 PNG 中提取像素信息(getImageData 替代方案)

2024-01-14

我正在尝试从 PNG 图像中获取像素数据进行处理。目前的方法是使用canvas.drawImage已关注canvas.getImageData (例子在这里 https://stackoverflow.com/questions/3528299/get-pixel-color-of-base64-png-using-javascript)。我正在寻找替代方案。

当前方法的问题是浏览器会修改受以下因素影响的像素值alpha,正如所讨论的here https://stackoverflow.com/questions/11601302/html5-canvas-to-png-zeroes-all-channels-when-alpha-transparent and here http://www.dreamincode.net/forums/topic/352957-canvas-how-to-prevent-zero-alpha-from-zeroing-rgb-channels/.

这个问题已被问到before https://stackoverflow.com/questions/17228724/how-to-convert-a-png-base64-string-to-pixel-array-without-using-canvas-getimaged,但没有得到令人满意的答案。


不使用画布来做到这一点的唯一方法getImageData()是将 PNG 文件作为二进制类型数组加载,并“手动”在代码中解析该文件。

先决条件:

  • 为此,您需要 PNG 规范,您可以找到该规范here http://www.libpng.org/pub/png/spec/iso/index-object.html.
  • 您需要知道如何使用类型化数组(为此DataView是最合适的视图)。
  • PNG 文件是chunk基于,你需要知道如何解析块

典型的基于块的文件具有称为 FourCC 标识符的四字节标头,后面是大小和杂项。数据取决于文件格式定义。

Then chunks紧随其后,通常包含 FOURCC(或四字符代码),然后是不带块头的块的大小。原则:


    MAGIC FOURCC
    SIZE/MISC    - depending on definition
    ...

    CHK1         - Chunk FourCC
    SIZE         - unsigned long
    .... data

    CHK2
    SIZE
    .... data
  

这种格式原理最初源自 80 年代中期的 Commodore Amiga 平台和 EA/IFF(交错文件格式)。

但现在一些供应商已经扩展或改变了块格式,因此对于 PNG 块来说,它实际上看起来像这样:

标头(始终为 8 字节且字节值相同):


‰PNG       (first byte is 0x89, see specs for reason)
CR + LF    0x0C0A
EOC + LF   0x1A0A
  

Chunks:


SIZE      (4 bytes, may be 0 (f.ex. IEND). Excl. chunk header and crc)
FOURCC    (4 bytes, ie. "IHDR", "IDAT")
[...data] (length: SIZE x bytes)
CRC32     (4 bytes representing the CRC-32 checksum of the data)
  

(有关详细信息,请参阅上面引用的规范链接)。

PPE 的字节顺序(字节顺序)始终是大字节顺序(“网络”顺序)。

这使得解析仅支持部分(或全部)块的文件变得容易。对于 PNG,您至少需要支持 (source https://en.wikipedia.org/wiki/Portable_Network_Graphics#.22Chunks.22_within_the_file):

  • IHDR必须是第一个块;它包含(按此顺序)图像的宽度、高度、位深度和颜色类型。
  • IDAT包含图像,该图像可能被分割为多个 IDAT 块。这种分割会稍微增加文件大小,但更容易传输 PNG。 IDAT块包含实际的图像数据,它是压缩算法的输出流。
  • IEND标记文件结束。

如果您打算支持调色板(颜色索引)文件,您还需要支持PLTE块。当您解析 IHDR 块时,您将能够看到使用的颜色格式(对于 RGB 数据,输入 2;对于 RGBA,输入 6 等等)。

解析本身很容易,因此您最大的挑战将是支持 ICC 配置文件(当存在于iCCPchunk)来调整图像颜色数据。典型的块是伽马块(gAMA)其中包含单个伽玛值,您可以应用该值将数据转换为线性格式,以便在应用显示伽玛时正确显示(还有与颜色相关的其他特殊块)。

第二大挑战是减压它使用 INFLATE。您可以使用诸如PAKO zlib 端口 https://github.com/nodeca/pako为您完成这项工作,并且该端口的性能接近本机 zlib。除此之外,如果要对数据进行错误检查(推荐),还应该支持 CRC-32 检查。

出于安全原因,您应该始终检查字段是否包含它们应该包含的数据,以及保留空间是否使用 0 或定义的数据进行初始化。

希望这可以帮助!

块解析器示例:(注意:不能在 IE 中运行)。

function pngParser(buffer) {

  var view = new DataView(buffer),
      len = buffer.byteLength,
      magic1, magic2,
      chunks = [],
      size, fourCC, crc, offset,
      pos = 0;  // current offset in buffer ("file")

  // check header
  magic1 = view.getUint32(pos); pos += 4;
  magic2 = view.getUint32(pos); pos += 4;

  if (magic1 === 0x89504E47 && magic2 === 0x0D0A1A0A) {

    // parse chunks
    while (pos < len) {

      // chunk header
      size = view.getUint32(pos);
      fourCC = getFourCC(view.getUint32(pos + 4));

      // data offset
      offset = pos + 8;
      pos = offset + size;

      // crc
      crc = view.getUint32(pos);
      pos += 4;

      // store chunk
      chunks.push({
        fourCC: fourCC,
        size: size,
        offset: offset,
        crc: crc
      })
    }

    return {chunks: chunks}
  } 
  else {
      return {error: "Not a PNG file."}
  }

  function getFourCC(int) {
    var c = String.fromCharCode;
    return c(int >>> 24) + c(int >>> 16 & 0xff) + c(int >>> 8 & 0xff) + c(int & 0xff);
  }
}

// USAGE: ------------------------------------------------

fetch("//i.imgur.com/GP6Q3v8.png")
  .then(function(resp) {return resp.arrayBuffer()}).then(function(buffer) {

  var info = pngParser(buffer);

  // parse each chunk here...
  for (var i = 0, chunks = info.chunks, chunk; chunk = chunks[i++];) {
    out("CHUNK : " + chunk.fourCC);
    out("SIZE  : " + chunk.size + " bytes");
    out("OFFSET: " + chunk.offset + " bytes");
    out("CRC   : 0x" + (chunk.crc>>>0).toString(16).toUpperCase());
    out("-------------------------------");
  }

  function out(txt) {document.getElementById("out").innerHTML += txt + "<br>"}
});
body {font: 14px monospace}
<pre id="out"></pre>

从这里你可以提取 IHDR 来查找图像大小和颜色类型,然后使用 IDAT 块进行压缩(PNG 在每个扫描线使用过滤器,这会让事情变得有点复杂,以及隔行扫描模式,请参阅规格),你就快完成了;)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用 javascript 从 PNG 中提取像素信息(getImageData 替代方案) 的相关文章

随机推荐