如何使用javascript从PNG中提取像素信息(getImageData alternative)

ahm*_*med 2 javascript html5 png canvas

我试图从PNG图像中获取像素数据进行处理.目前的方法是使用canvas.drawImagefollow canvas.getImageData(这里的例子).我正在寻找替代品.

当前方法的问题在于浏览器修改受alpha影响的像素值,如此此处所讨论的.

之前已经提出这个问题,但没有令人满意的答案.

小智 8

在不使用canvas的情况下执行此操作的唯一方法getImageData()是将PNG文件作为二进制类型数组加载,并在代码中"手动"解析文件.

先决条件:

  • 为此,您需要PNG规范,您可以在此处找到.
  • 您需要知道如何使用类型化数组(对于此a DataView是最合适的视图).
  • PNG文件是基于的,您需要知道如何解析块

典型的基于块的文件具有称为FourCC标识符的四字节头,后跟size和misc.数据取决于文件格式定义.

然后在此之后放置,通常包含FOURCC(或四个字符代码),然后是没有块头的块的大小.原则上:

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

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

    CHK2
    SIZE
    .... data

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

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

标头(总是8个字节和相同的字节值):

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

大块:

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)

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

而且PNG的字节顺序(字节顺序)总是大端("网络"顺序).

这使得解析仅支持一些(或所有)块的文件变得容易.对于PNG,您至少需要支持(来源):

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

如果您打算支持调色板(颜色索引)文件,您还需要支持PLTE块.解析IHDR块时,您将能够看到使用的是哪种颜色格式(RGB数据类型为2,RGBA类型为6,依此类推).

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

第二大挑战是使用INFLATE 的减压.您可以使用PAKO zlib端口等项目为您完成此任务,并且此端口的性能接近本机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>"}
});
Run Code Online (Sandbox Code Playgroud)
body {font: 14px monospace}
Run Code Online (Sandbox Code Playgroud)
<pre id="out"></pre>
Run Code Online (Sandbox Code Playgroud)

从这里您可以提取IHDR以查找图像大小和颜色类型,然后IDAT块进行放气(PNG使用每个扫描线的滤波器,这会使事情变得复杂,以及隔行扫描模式,请参阅规格)并且您差不多完成了;)