今天小编跟大家讲解下有关解码 PNG 图片 ,相信小伙伴们对这个话题应该有所关注吧,小编也收集到了有关解码 PNG 图片 的相关资料,希望小伙伴们看了有所帮助。
解码 PNG 图片就是把一张图片从二进制数据转成包含像素数据的ImageData。
图片的二进制数据可以从<canvas>,<img>,Object URLs,Image URLs,Blob对象上获取到。参见浏览器图像转换手册。
ImageData是一个包括了像素数据、图片宽高数据的对象。
示例图片:point_up_2: 这是一张我们接下去要解码的图片,但它太小了,放大了展示给大家看下。:point_down:
二进制数据我们先从浏览器的<input>标签上读取到Blob对象,然后拿到这张图片的二进制数据。
<input type="file"/><script> const input = document.querySelector('input'); input.addEventListener('change', async function(e) { const [file] = e.target.files; const arrayBuffer = await file.arrayBuffer(); console.log('arrayBuffer', arrayBuffer); // TODO: Let's decode arrayBuffer const imageData = decode(arrayBuffer); console.log('imageData', imageData); });</script>得到的arrayBuffer如下:
0 ~ 34 ~ 78 ~ 1112 ~ 150 ~ 15137, 80, 78, 7113, 10, 26, 100, 0, 0, 1373, 72, 68, 8216 ~ 310, 0, 0, 20, 0, 0, 22, 3, 0, 00, 15, 216, 22932 ~ 47183, 0, 0, 01, 115, 82, 7166, 1, 217, 20144, 127, 0, 048 ~ 630, 9, 112, 7289, 115, 0, 011, 19, 0, 011, 19, 1, 064 ~ 79154, 156, 24, 00, 0, 12, 8076, 84, 69, 2550, 0, 0, 25580 ~ 950, 0, 0, 255255, 255, 255, 2510, 96, 246, 00, 0, 4, 11696 ~ 11182, 78, 83, 255255, 255, 127, 128144, 197, 89, 00, 0, 12, 73112 ~ 12768, 65, 84, 120156, 99, 16, 96216, 0, 0, 0228, 0, 193, 39128 ~ 143168, 232, 87, 00, 0, 0, 7369, 78, 68, 17466, 96, 130每个表格的单元格内有 4 字节数据,每个字节由 8 位组成,1 位代表的是0或者1的一个数字。
PNG 文件签名一张 PNG 图片二进制数据的开头必须是这 8 字节:0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a。
0x代表这个数字是 16 进制表示的,0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a转换为 10 进制是137, 80, 78, 71, 13, 10, 26, 10。
0 ~ 34 ~ 7137, 80, 78, 7113, 10, 26, 100x89, 0x50, 0x4e, 0x470x0d, 0x0a, 0x1a, 0x0a这张图片的前 8 个字节满足签名的要求。
数据块数据块包含了图片所有的数据,一个数据块可以分为数据块的开始信息、数据块的数据信息和数据块的结束信息。
一个数据块的开始信息包含 2 个 32 位的数字,换算成字节的话,就是 8 个字节。前 4 个字节会被合并成一个 32 位的数字,表示数据信息的长度,后面 4 个字节可以被转换成文本,表示数据块的类型。
我们从第 8 个字节开始解析数据块的开始信息。
开始信息 18 ~ 1112 ~ 150, 0, 0, 1373, 72, 68, 82长度类型13IHDR这个数据块是IHDR类型,有13字节的数据信息。
数据信息 1IHDRIHDR里面的数据信息如下:
16 ~ 1920 ~ 2324 ~ 27280, 0, 0, 20, 0, 0, 22, 3, 0, 00widthheightdepth,colorType,compression,filterinterlace222, 3, 0, 00width(宽)和height(高)表示图片的宽高。depth(通道深度)代表每个色彩通道用几位数据表示。一张 PNG 图片是由像素组成的,每个像素由色彩通道组成,每个色彩通道又是由位来组成。colorType(色彩类型)PNG 图片一共有 5 种色彩类型,0代表灰度颜色,2代表用 RGB 表示颜色,即(R, G, B),3代表用色板表示颜色,4代表灰度和透明度来表示颜色,6代表用 RGB 和透明度表示颜色,即(R, G, B, A)。色板的色彩类型里,每个像素是由 1 个色彩通道表示的。compression代表了压缩算法。目前只支持0,表示 deflate/inflate。Deflate/inflate 是一种结合了 LZ77 和霍夫曼编码的无损压缩算法,被广泛运用于7-zip,zlib,gzip等场景。filter代表在压缩前应用的过滤函数类型,目前只支持0。过滤函数类型0里面包括了 5 种过滤函数。interlace代表图片数据是否经过交错,0代表没有交错,1代表交错。从上面的信息看出,这是一张 2 * 2 像素的图片,使用色板作为颜色类型,每个像素由 1 个色彩通道组成,每个色彩通道由 2 位组成。像素数据没有交错,经过0的过滤函数类型后,经过deflate压缩
结束信息 129 ~ 3215, 216, 229, 183结束信息包括了 4 字节的 CRC32 校验和。解码器应该根据数据块类型和数据块的数据信息计算 CRC32 校验和,并与结束信息中的校验和比对。如果相等,则认为图片数据被正确传输。
开始信息 233 ~ 3637 ~ 400, 0, 0, 1115, 82, 71, 66长度类型1sRGB这个数据块是sRGB信息,长度是 1 字节。
这个数据块类型是小写字母开头的,这表示这个数据块是辅助数据块,大写字母开头的数据块类型表示关键数据块。
数据信息 2sRGB411sRGB表示图片使用的色彩空间。
结束信息 242 ~ 45217, 201, 44, 127需要比对 CRC32。
开始信息 346 ~ 4950 ~ 530, 0, 0, 9112, 72, 89, 115长度类型9pHYs9 个字节的pHYs辅助数据信息。
数据信息 3pHYs54 ~ 5758 ~ 61620, 0, 11, 190, 0, 11, 191X 轴每个单位像素数Y 轴每个单位像素数单位28352835米pHYs数据块代表图片的物理世界大小,从上面的数据可以看出,这张图在现实世界中应该被渲染成每米 2835 像素,宽高一样。
结束信息 363 ~ 660, 154, 156, 24比对 CRC32。
开始信息 467 ~ 7071 ~ 740, 0, 0, 1280, 76, 84, 69长度类型12PLTE12 字节PLTE色板数据,是关键数据块。
数据信息 4PLTE75 ~ 7879 ~ 8283 ~ 86255, 0, 0, 0255, 0, 0, 0255, 255, 255, 255色板中包含的数据是 RGB 数据,以R, G, B的形式保存,这里一共 12 字节,表示了 4 个色块。得到的色板信息如下:
色板[[255, 0, 0], [0, 255, 0], [0, 0, 255], [255, 255, 255]]
结束信息 487 ~ 90251, 0, 96, 246比对 CRC32。
开始信息 591 ~ 9495 ~ 980, 0, 0, 4116, 82, 78, 83长度类型4tRNS4 字节tRNS透明度数据,是辅助数据块。
数据信息 5tRNS99 ~ 102255, 255, 255, 127这个数据块为色板提供透明信息,每个字节表示一个色块的透明信息。与色板组合后的色板如下:
色板[[255, 0, 0, 255], [0, 255, 0, 255], [0, 0, 255, 255], [255, 255, 255, 127]]结束信息 5103 ~ 106128, 144, 197, 89比对 CRC32。
开始信息 6107 ~ 110111 ~ 1140, 0, 0, 1273, 68, 65, 84长度类型12IDAT12 字节IDAT像素数据,是关键数据块。
数据信息 6IDAT115 ~ 118119 ~ 122123 ~ 126120, 156, 99, 1696, 216, 0, 00, 228, 0, 193在解析像素数据前,我们先要了解下像素数据是如何编码的。每行像素都会先经过过滤函数处理,每行像素的过滤函数可以不同。然后所有行的像素数据会经过 deflate 压缩算法压缩。所以,我们需要对这里的像素数据先解压,这里我们直接使用了zlib.inflate()函数。在浏览器上,可以使用 pako 工具包。
解压出来的像素数据是 Uint8Array:0, 16, 0, 176。
接下去我们需要仔细了解每行像素是如何编码,才能把上面的数据还原成像素点。
扫描线 Scanline一根扫描线包含图片一行像素的数据。我们知道这张图片的高度是 2,也就是像素数据中有 2 行扫描线。
一根扫描线由 1 字节的过滤函数标记和像素信息组成。像素信息一个接一个地排列,中间没有多余的空位。如果扫描线长度不足以填满字节的位数,最后几位会被补齐。一根扫描线的结构如下:
过滤函数像素…[补齐…]8 位每像素位数*每行像素数+补齐所以我们先要知道每个像素的位数才能解码扫描线。
色彩类型 - 色彩通道 - 通道深度 - 每像素位数色彩类型色彩每像素通道数通道深度每像素位数0灰度11, 2, 4, 8, 161, 2, 4, 8, 162真彩色(RGB)38, 1624, 483色板11, 2, 4, 81, 2, 4, 84灰度和透明度28, 1616, 326色彩色和透明度(RGBA)48, 1632, 64这张图片的色彩类型是3,所以每个像素包含1个色彩通道。又因为图片的通道深度是2,所以我们知道每个像素是用2位来表示的。
所以我们可以解码扫描线了。
解码扫描线行过滤函数像素…[补齐…]8 位每像素 2 位 * 2 像素+4 位补齐=8 位0000010000(16)1010110000(176)过滤函数在扫描线被压缩前,每根扫描线都会被单独的过滤函数处理,以使后面的压缩效果更好。
在过滤函数类型0中,有 5 种过滤函数:
过滤函数函数过滤方式0无保留原始数据1减减去 A2上减去 B3平均根据 A 和 B 取平均,并向下取证4Paeth使用最接近于 p = A + B − C 的 A、B 或 C 的数值过滤函数用 A、B、C 三点的数值来计算当前点 X。
这张图片里面的过滤函数0表示这张图数据未经过滤。所以我们只要保留原始数据就行了。
扫描线像素行第 1 列第 2 列补齐…000010000110110000这里每个像素中的数据表示了这个像素的颜色在色板中的索引。根据色板,我们可以还原出图片的像素信息:[[255, 0, 0, 255], [0, 255, 0, 255], [0, 0, 255, 255], [255, 255, 255, 127]]。
图片像素行\列010(255, 0, 0, 255)(0, 255, 0, 255)1(0, 0, 255, 255)(255, 255, 255, 127)结束信息 6127 ~ 13039, 168, 232, 87比对 CRC32。
开始信息 7131 ~ 134135 ~ 1380, 0, 0, 073, 69, 78, 680IEND0 字节IEND图片结束数据块,是关键数据块。
数据信息 7IEND无。
结束信息 7139 ~ 142174, 66, 96, 130比对 CRC32。
整张图片解码完成,最终的ImageData对象是:
ImageDataimageData = { width: 2, height: 2, data: [255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255, 255, 255, 127],};总结我们成功解码了一张简单的 PNG 图片,但其中,我简化了很多细节:
IDAT数据可以被分开放在多个数据块中。所以我们需要先收集到所有IDAT数据块,再对其解码。一共有 4 种关键数据块和 14 种辅助数据块。交错型的 PNG 图片可以让 PNG 展示地更快,但是会让IDAT数据变大。来源:爱蒂网