我有两张图片(合并时会有1:1宽度:高度比)。如果我在unix上使用convert a.png b.png -append c.png
组合它们,它就可以完美地运行。我试图在javascript中实现这一点。我正在将arraybuffers(包含img数据)添加到一起,因为在画布中绘制它们似乎都不会产生相同的图像。如果我只是附加每个数组缓冲区,则图像比例为2:1;有谁知道如何正确附加数组缓冲区,类似于convert
做什么?
编辑:要详细说明,只需在画布上堆叠就不会工作(我已尝试过)。这可能是由于低级别的画布代码,我怀疑这是由于画布如何连接两个图像之间的边界处的像素。它需要是arraybuffers。
答案 0 :(得分:7)
如果由于某种原因不想通过Image
加载图像,那么唯一的选择是手动解析和解压缩文件。由于ICC / gamma支持,浏览器可以改变图像,这是事实。但是,在画布步骤中不会发生这种情况,但是在图像加载和转换为RGBA数据期间。
话虽如此,getImageDate()
/ putImageData()
期间的过程也会因(取消)预乘和舍入误差而改变像素值。
使用画布将两个PNG图像合并为一个图像的示例:
var ctx = c.getContext("2d"), // canvas 2d context
img1 = new Image, // create two image
img2 = new Image, // elements
count = 2; // Track for loader
// load images
img1.onload = img2.onload = function() { // make sure images are
if (!--count) append(); // loaded first
};
img1.crossOrigin = img2.crossOrigin = ""; // need this for this demo
img1.src = "http://i.imgur.com/hlHEhUhb.jpg"; // random images...
img2.src = "http://i.imgur.com/ynzkv40b.jpg";
// process images
function append() {
// use width to sum the images
c.width = img1.width + img2.width; // set total height
c.height = Math.max(img1.height,img2.height); // set max height
ctx.drawImage(img1, 0, 0); // draw in image 1
ctx.drawImage(img2, img1.width, 0) ; // draw in image 2
console.log(c.toDataURL()); // extract, send to server
}

<canvas id=c></canvas>
&#13;
您不能简单地将PNG数据相互合并而不先解码它们。这是因为图像数据块被压缩(放气),并且PNG文件中的每个扫描线都使用描述正在使用的线路滤波器的初始字节。
如果只是垂直,简单地合并它们可能会使收缩的数据无效,而由于将引入额外的滤波器字节,它可能会使行数据与长度无效。每行的过滤器也可能不同。
因此无法解析,解压缩和解码源PNG文件。但是,为了解析PNG文件,您必须知道如何构建文件格式。
PNG文件中的主要文件结构是:
-Signature- 8 bytes
IHDR chunk required (width, height, depth, mode etc.)
[PLTE chunk] required for indexed color mode
[Misc chunks] optional ancillary and private chunks
IDAT chunk required, can be multiple
IEND chunk required, last chunk (data-less)
在这种情况下可以忽略任何其他块,除非您使用索引调色板,在这种情况下您还需要考虑PLTE块。
如果当前块未知或不需要,Chunks允许您跳到下一个块。块使用8个字节构建,然后是数据,然后是4个字节的CRC-32校验和(不需要数据,就像IEND块一样):
0x00 SIZE (4 bytes)
0x04 FOURCC (4 bytes)
0x08 DATA (variable, can be 0)
0x?? CRC-32 (4 bytes)
大小仅代表数据。名称将是块名称的ASCII表示,总是四个字节(&#34; IDAT&#34;,&#34; IEND&#34;,...)。
如果您不想验证数据,可以忽略CRC-32校验和,但是当您生成新的PNG文件时不能忽略它,因为大多数PNG查看器/解析器使用此值并且它包含块名称
所有值都以big-endian字节顺序无符号。
读取分块数据文件(如PNG)的典型方法是初始化第一个块的起始偏移量。然后迭代读取并同时移动文件光标,检查块名称。
例如:
var pos = 8; // first chunk position
var dv = new DataView(arraybuffer); // use a DataView
制作一些辅助函数来读取和移动位置:
function getUint32() { // and for Uint16 etc.
var data = dv.getUint32(pos); // use big-endian byte-order
pos += 4;
return data
}
// decode chunk name to string (from pngtoy)
function getFourCC() {
var v = getUint32(),
c = String.fromCharCode;
return c((v & 0xff000000)>>>24) + c((v & 0xff0000)>>>16) +
c((v & 0xff00)>>>8) + c((v & 0xff)>>>0)
}
现在允许我们按预期使用文件缓冲区:
// repeated actions:
var size = getUint32();
var name = getFourCC();
var data, crc;
if (name === "IHDR") { // check chunk type
data = new Uint8Array(dv.buffer, pos, size); // get data section from chunk
pos += size; // next chunk or the end
crc = getUint32(); // read CRC-32 checksum
// validate CRC-32 here
}
else pos += size + 4; // skip data and crc
提示:即使跳过了块,也可能是根据CRC校验和验证数据的一点,以找到文件损坏的早期指示。
IDAT块总是包含泄露的数据,因为这是格式规范中唯一有效的存储形式,必须先膨胀。对于这个过程,我建议(一如既往)Pako implementation of the zlib library。
然后每个输入图像的读取过程变为(需要使用DataView
):
0x89504E47 0x0D0A1A0A
(big-endian)。我们仍然无法合并文件,因为我们需要使用过滤字节解码每条扫描线。 PNG中有五种不同的线路滤波器,其中0表示不需要滤波,直到更复杂的4 Paeth滤波器。
此外,图像可以交织(Adam-7),由于渐进性,需要采用不同的方法。
当您解码每条扫描线(并在需要时进行反交错)时,您的原始位图不受浏览器的ICC / gamma影响。
需要采取额外的步骤来检查两个图像是否属于同一类型(例如RGB,RGBA等)。如果不是,则必须另外通过&#34;转换为另一种格式。升级&#34;信息/质量较差的人。如果你应该采用相同的格式和深度。
如果尺寸不同会在最终结果中留下某种空隙,可能需要填充填充没有覆盖的空像素,具体取决于格式,如果不想要这样的话作为透明度等。
现在您可以水平或垂直合并两个位图。
您提到想要水平合并位图 -
为新缓冲区设置主循环,然后在每条扫描线的图像1和2之间交替,以便将两条第一条扫描线作为单个扫描线复制到新缓冲区中。
然后再次保存图像的相反过程是:
我的策略是使用普通数组来部分构建文件,以保存每个类型化的数组部分(签名)和数据块+数据。然后将数组传递给Blob,Blob将部分连接到单个二进制缓冲区。
例如:
var arr = [];
arr.push(taSig); // ta* = typed array
arr.push(taIHDR);
arr.push(taIDAT);
arr.push(taIEND);
然后将数组传递给Blob:
var blob = new Blob(arr, {type: "image/png"});
可以找到完整的PNG文件格式规范 here 。
我建议您查看我的 pngtoy (PNG解析器和解码器,MIT lic。)了解详情。它执行与上述类似的步骤以获得原始解码位图。