www.129028.com金沙png的故事:获取图片信息和像素内容

日期:2019-10-22编辑作者:Web前端

png的故事:获取图片信息和像素内容

2017/03/25 · JavaScript · 1 评论 · PNG

原文出处: AlloyTeam   

对于一个PNG文件来说,其文件头总是由位固定的字节来描述的,HEX: 89 50 4E 47 0D 0A 1A 0A

前言

现在时富媒体时代,图片的重要性对于数十亿互联网用户来说不言而喻,图片本身就是像素点阵的合集,但是为了如何更快更好的存储图片而诞生了各种各样的图片格式:jpeg、png、gif、webp等,而这次我们要拿来开刀的,就是png。

使用ultra打开一个png图片,结果如下:

简介

首先,png是什么鬼?我们来看看wiki上的一句话简介:

Portable Network Graphics (PNG) is a raster graphics file format that supports lossless data compression.

也就是说,png是一种使用无损压缩的图片格式,而大家熟知的另外一种图片格式——jpeg则是采用有损压缩的方式。用通俗易懂的方式来讲,当原图片数据被编码成png格式后,是可以完全还原成原本的图片数据的,而编码成jpeg则会损耗一部分图片数据,这是因为两者的编码方式和定位不同。jpeg着重于人眼的观感,保留更多的亮度信息,去掉一些不影响观感的色度信息,因此是有损耗的压缩。png则保留原始所有的颜色信息,并且支持透明/alpha通道,然后采用无损压缩进行编码。因此对于jpeg来说,通常适合颜色更丰富、可以在人眼识别不了的情况下尽可能去掉冗余颜色数据的图片,比如照片之类的图片;而png适合需要保留原始图片信息、需要支持透明度的图片。

以下,我们来尝试获取png编码的图片数据:

 www.129028.com金沙 1

结构

图片是属于2进制文件,因此在拿到png图片并想对其进行解析的话,就得以二进制的方式进行读取操作。png图片包含两部分:文件头和数据块。

        其中第一个字节0x89超出了ASCII字符的范围,这是为了避免某些软件将PNG文件当做文本文件来处理。文件中剩余的部分由3个以上的PNG的数据块(Chunk)按照特定的顺序组成,因此,一个标准的PNG文件结构应该如下:

文件头

png的文件头就是png图片的前8个字节,其值为[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A],人们常常把这个头称之为“魔数”。玩过linux的同学估计知道,可以使用file命令类判断一个文件是属于格式类型,就算我们把这个文件类型的后缀改得乱七八糟也可以识别出来,用的就是判断“魔数”这个方法。有兴趣的同学还可以使用String.fromCharCode将这个“魔数”转成字符串看看,就知道为什么png会取这个值作为文件头了。

用代码来判断也很简单:

JavaScript

// 读取指定长度字节 function readBytes(buffer, begin, length) {     return Array.prototype.slice.call(buffer, begin, begin + length); }   let header = readBytes(pngBuffer, 0, 8); // [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]

1
2
3
4
5
6
// 读取指定长度字节
function readBytes(buffer, begin, length) {
    return Array.prototype.slice.call(buffer, begin, begin + length);
}
 
let header = readBytes(pngBuffer, 0, 8); // [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]

PNG文件标志

PNG数据块

……

PNG数据块

数据块

去掉了png图片等前8个字节,剩下的就是存放png数据的数据块,我们通常称之为chunk

顾名思义,数据块就是一段数据,我们按照一定规则对png图片(这里指的是去掉了头的png图片数据,下同)进行切分,其中一段数据就是一个数据块。每个数据块的长度是不定的,我们需要通过一定的方法去提取出来,不过我们要先知道有哪些类型的数据块才好判断。

PNG数据块(Chunk)

        PNG定义了两种类型的数据块,一种是称为关键数据块(critical chunk),这是标准的数据块,另一种叫做辅助数据块(ancillary chunks),这是可选的数据块。关键数据块定义了4个标准数据块,每个PNG文件都必须包含它们,PNG读写软件也都必须要支持这些数据块。你可以从“可选否”一栏查看是否必须支持的数据块。虽然PNG文件规范没有要求PNG编译码器对可选数据块进行编码和译码,但规范提倡支持可选数据块。

下表就是PNG中数据块的类别,其中,关键数据块部分我们使用深色背景加以区分。

PNG文件格式中的数据块

数据块符号

数据块名称 

多数据块 

可选否 

位置限制 

IHDR 

文件头数据块 

否 

否 

第一块 

cHRM 

基色和白色点数据块 

否 

在PLTE和IDAT之前

gAMA 

图像γ数据块 

否 

在PLTE和IDAT之前 

sBIT 

样本有效位数据块 

否 

在PLTE和IDAT之前 

PLTE 

调色板数据块 

否 

在IDAT之前 

bKGD 

背景颜色数据块 

否 

在PLTE之后IDAT之前 

hIST 

图像直方图数据块 

否 

在PLTE之后IDAT之前 

tRNS 

图像透明数据块 

否 

在PLTE之后IDAT之前 

oFFs 

(专用公共数据块) 

否 

在IDAT之前 

pHYs 

物理像素尺寸数据块 

否 

在IDAT之前 

sCAL 

(专用公共数据块) 

否 

在IDAT之前 

IDAT 

图像数据块 

否 

与其他IDAT连续

tIME 

图像最后修改时间数据块 

否 

无限制 

tEXt 

文本信息数据块 

无限制 

zTXt 

压缩文本数据块 

无限制 

fRAc 

(专用公共数据块) 

无限制 

gIFg 

(专用公共数据块) 

无限制 

gIFt 

(专用公共数据块) 

无限制 

gIFx 

(专用公共数据块) 

无限制 

IEND 

图像结束数据 

否 

否 

最后一个数据块 

这里要补充一个iCCP

数据块类型

数据块类型有很多种,但是其中大部分我们都不需要用到,因为里面没有存储我们需要用到的数据。我们需要关注的数据块只有以下四种:

  • IHDR:存放图片信息。
  • www.129028.com金沙,PLTE:存放索引颜色。
  • IDAT:存放图片数据。
  • IEND:图片数据结束标志。

只要解析这四种数据块就可以获取图片本身的所有数据,因此我们也称这四种数据块为“关键数据块”

数据块结构

PNG文件中,每个数据块(比如IHDR,cHRM,IDAT等)由4个部分组成,如下:

名称 

字节数 

说明 

Length (长度) 

4字节 

指定数据块中数据域的长度,其长度不超过(231-1)字节 

Chunk Type Code (数据块类型码) 

4字节 

数据块类型码由ASCII字母(A-Z和a-z)组成 

Chunk Data (数据块数据) 

可变长度 

存储按照Chunk Type Code指定的数据 

CRC (循环冗余检测) 

4字节 

存储用来检测是否有错误的循环冗余码 

CRC(cyclic redundancy check)域中的值是对Chunk Type Code域和Chunk Data域中的数据进行计算得到的。CRC具体算法定义在ISO 3309和ITU-T V.42中.

注意:Length值的是除:length本身,Chunk Type Code,CRC外的长度,也就是Chunk Data的长度。

下面,我们依次来了解一下各个【关键数据块】的结构

数据块格式

数据块格式如下:

描述 长度
数据块内容长度 4字节
数据块类型 4字节
数据块内容 不定字节
crc冗余校验码 4字节

这样我们就可以轻易的指导当前数据块的长度了,即数据块内容长度 + 12字节,用代码实现如下:

JavaScript

// 读取32位无符号整型数 function readInt32(buffer, offset) {     offset = offset || 0;     return (buffer[offset] << 24) + (buffer[offset + 1] << 16) + (buffer[offset + 2] << 8) + (buffer[offset + 3] << 0); }   let length = readInt32(readBytes(4)); // 数据块内容长度 let type = readBytes(4); // 数据块类型 let chunkData = readBytes(length); // 数据块内容 let crc = readBytes(4); // crc冗余校验码

1
2
3
4
5
6
7
8
9
10
// 读取32位无符号整型数
function readInt32(buffer, offset) {
    offset = offset || 0;
    return (buffer[offset] << 24) + (buffer[offset + 1] << 16) + (buffer[offset + 2] << 8) + (buffer[offset + 3] << 0);
}
 
let length = readInt32(readBytes(4)); // 数据块内容长度
let type = readBytes(4); // 数据块类型
let chunkData = readBytes(length); // 数据块内容
let crc = readBytes(4); // crc冗余校验码

这里的crc冗余校验码在我们解码过程中用不到,所以这里不做详解。除此之外,数据块内容长度和数据块内容好解释,不过数据块类型有何作用呢,这里我们先将这个type转成字符串类型:

JavaScript

// 将buffer数组转为字符串 function bufferToString(buffer) {     let str = '';     for(let i=0, len=buffer.length; i<len; i++){         str += String.fromCharCode(buffer[i]);     }     return str; }   type = bufferToString(type);

1
2
3
4
5
6
7
8
9
10
// 将buffer数组转为字符串
function bufferToString(buffer) {
    let str = '';
    for(let i=0, len=buffer.length; i<len; i++){
        str += String.fromCharCode(buffer[i]);
    }
    return str;
}
 
type = bufferToString(type);

然后会发现type的值是四个大写英文字母,没错,这就是上面提到的数据块类型。上面还提到了我们只需要解析关键数据块,因此遇到type不等于IHDR、PLTE、IDAT、IEND中任意一个的数据块就直接舍弃好了。当我们拿到一个关键数据块,就直接解析其数据块内容就可以了,即上面代码中的chunkData字段。

IHDR

        文件头数据块IHDR(header chunk):它包含有PNG文件中存储的图像数据的基本信息,并要作为第一个数据块出现在PNG数据流中,而且一个PNG数据流(文件)中只能有一个文件头数据块。
文件头数据块由13字节组成,它的格式如下表所示:

域的名称 

字节数 

说明 

Width 

4 bytes 

图像宽度,以像素为单位 

Height 

4 bytes 

图像高度,以像素为单位 

Bit depth 

1 byte 

图像深度: 
索引彩色图像:1,2,4或8 
灰度图像:1,2,4,8或16 
真彩色图像:8或16 

ColorType 

1 byte 

颜色类型:
0:灰度图像, 1,2,4,8或16 
2:真彩色图像,8或16 
3:索引彩色图像,1,2,4或8 
4:带α通道数据的灰度图像,8或16 
6:带α通道数据的真彩色图像,8或16 

Compression method 

1 byte 

压缩方法(LZ77派生算法) 

Filter method 

1 byte 

滤波器方法 

Interlace method 

1 byte 

隔行扫描方法:
0:非隔行扫描 
1: Adam7(由Adam M. Costello开发的7遍隔行扫描方法) 

 

由于我们研究的是手机上的PNG,因此,首先我们看看MIDP1.0对所使用PNG图片的要求吧:

● 在MIDP1.0中,我们只可以使用1.0版本的PNG图片。并且,所以的PNG关键数据块都有特别要求:
IHDR
● 文件大小:MIDP支持任意大小的PNG图片,然而,实际上,如果一个图片过大,会由于内存耗尽而无法读取。
● 颜色类型:所有颜色类型都有被支持,虽然这些颜色的显示依赖于实际设备的显示能力。同时,MIDP也能支持alpha通道,但是,所有的alpha通道信息都会被忽略并且当作不透明的颜色对待。
● 色深:所有的色深都能被支持。
● 压缩方法:仅支持压缩方式0(deflate压缩方式),这和jar文件的压缩方式完全相同,所以,PNG图片数据的解压和jar文件的解压可以使用相同的代码。(其实这也就是为什么J2ME能很好的支持PNG图像的原因:))
● 滤波器方法:尽管在PNG的白皮书中仅定义了方法0,然而所有的5种方法都被支持!
● 隔行扫描:虽然MIDP支持0、1两种方式,然而,当使用隔行扫描时,MIDP却不会真正的使用隔行扫描方式来显示。
● PLTE chunk:支持
● IDAT chunk:图像信息必须使用5种过滤方式中的方式0 (None, Sub, Up, Average, Paeth)
● IEND chunk:当IEND数据块被找到时,这个PNG图像才认为是合法的PNG图像。
● 可选数据块:MIDP可以支持下列辅助数据块,然而,这却不是必须的。
bKGD cHRM gAMA hIST iCCP iTXt pHYs
sBIT sPLT sRGB tEXt tIME tRNS zTXt

关于更多的信息,可以参考www.w3.org

本文由www.129028.com金沙发布于Web前端,转载请注明出处:www.129028.com金沙png的故事:获取图片信息和像素内容

关键词:

www.129028.com金沙前端实现 SVG 转 PNG

一般方式 创建 imageimage,src = xxx.svg; 创建 canvas,dragImage 将图片贴到 canvas 上; 利用 toDataUrl 函数,将 canvas 的表示为...

详细>>

H5移动端知识点总结

H5移动端知识点总结 2016/02/05 · CSS , HTML5 · 2评论 · 移动端 原文出处: 涂根华     移动开发基本知识点 一. 使用re...

详细>>

圣杯布局小结

圣杯布局小结 2016/01/30 · HTML5 · 1评论 · 圣杯布局 原文出处: 流云诸葛     圣杯布局,很久之前就听过,但是一直...

详细>>

腾讯微云黑色遮罩引导蒙版更好的CSS实现方式

基于clip-path的任意元素的碎片拼接动效 2016/06/08 · CSS · clip-path 原文出处: 张鑫旭(@张鑫旭)     腾讯微云黑色遮...

详细>>