【问题标题】:How to extract pixel information from PNG using javascript (getImageData alternatives)如何使用 javascript 从 PNG 中提取像素信息(getImageData 替代品)
【发布时间】:2015-04-20 01:16:39
【问题描述】:

我正在尝试从 PNG 图像中获取像素数据进行处理。当前的方法是使用canvas.drawImage 后跟canvas.getImageData (example here)。我正在寻找替代品。

当前方法的问题是浏览器会修改受 alpha 影响的像素值,正如 herehere 所讨论的那样。

已向before 提出此问题,但没有令人满意的答案。

【问题讨论】:

  • 您可以将 alpha 设置为不透明,以便按照您的意愿工作吗?
  • 原始PNG实际上是灰度图像。 RGBA 值由浏览器在调用drawImage 时设置。
  • 循环像素并将A通道子像素设置为255,然后再次运行getImageData()。应该可以正常工作
  • 是的。或者只是编写一些代码来解析 PNG 文件并将其作为二进制数据读取......不,将 alpha 设置为 255 听起来是个好主意。
  • 要修改A频道,我必须这样做buffer = ctx.getImageData(0,0,img.width,img.height.data);。到那时,buffer 已经全为零了。

标签: javascript html canvas png


【解决方案1】:

不使用 canvas 和 getImageData() 的唯一方法是将 PNG 文件加载为二进制类型的数组并“手动”在代码中解析文件。

先决条件:

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

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

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

 MAGIC FOURCC
    尺寸/杂项 - 取决于定义
    ...

    CHK1 - 块 FourCC
    大小 - 无符号长
    .... 数据

    CHK2
    尺寸
    .... 数据

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

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

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

‰PNG(第一个字节是 0x89,原因见规范)
CR + 低频 0x0C0A
EOC + LF 0x1A0A

块:

SIZE (4 bytes, 可能为 0 (f.ex. IEND). Excl. chunk header and crc)
FOURCC(4 个字节,即“IHDR”、“IDAT”)
[...数据](长度:大小 x 字节)
CRC32(4个字节代表数据的CRC-32校验和)

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

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

这使得解析仅支持部分(或全部)块的文件变得容易。对于 PNG,您至少需要支持 (source):

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

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

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

第二大挑战是使用 INFLATE 的解压。您可以使用诸如 PAKO zlib port 之类的项目来为您完成这项工作,并且此端口的性能接近原生 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}
&lt;pre id="out"&gt;&lt;/pre&gt;

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

【讨论】:

  • 哇!!这是一个详尽的答案,正如您所说,它本身就是一个项目。也许是时候重新考虑我的策略了。
  • @ahmohamed 它确实可以是一个大型项目,但取决于您想要支持多少。我添加了一个可用于提取所需数据的工作示例 PNG 块遍历器(请注意,我没有添加太多的错误检查,这当然是生产所必需的)
  • 非常感谢您。当我问这个问题时,我所希望的远不止于此。我会试着绕过它并决定如何去。
  • pngparse 库仅适用于 node.js。如何在客户端使用它?
  • @ahmohamed 如果你不害怕 alpha 版本,我现在制作了pngtoy,它可以读取和解析 PNG 文件++(注意:它目前在隔行扫描和一些特殊情况下失败)。看看 - 在客户端工作。有关详细信息,请参阅测试文件夹。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多