我正在尝试从PNG图像中获取像素数据以进行处理。目前的方法是使用canvas.drawImage
跟canvas.getImageData
(example here)。我正在寻找替代方案。
当前方法存在的问题是浏览器会修改受 alpha 影响的像素值,如here和here所述。
此问题已被问及before,但没有令人满意的答案。
答案 0 :(得分:7)
在不使用canvas和getImageData()
的情况下执行此操作的唯一方法是将PNG文件作为二进制类型数组加载,并在“手动”代码中解析文件。
<强>先决条件:强>
DataView
是最合适的视图)。典型的基于块的文件有一个四字节头,称为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的字节顺序(endianess)始终是big-endian(“网络”顺序)。
这使得解析仅支持一些(或所有)块的文件变得容易。对于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}
<pre id="out"></pre>
从这里您可以提取IHDR以查找图像大小和颜色类型,然后提取IDAT块以进行放气(PNG使用每个扫描线的滤波器,这会使事情变得复杂,以及隔行扫描模式,请参阅规格)和你几乎完成了;)