常见图片宽高解析
于比较常用的图片格式Png、Jpg、Gif、Bmp,我们需要针对不同的图片格式使用不同的控件来显示,这里就有一个来解析图片格式的问题。我们不能单纯的用文件后缀名.png、.jpg、.jpeg、.gif、.bmp来区分图片格式,因为实际上我们可以直接修改图片后缀名,修改后缀名并不能修改图片的格式,图片还是保持它原来的格式。
图片文件的格式结果中,在头部信息(一般都会在图片文件最开始的几个字节)中都会包含图片的格式信息。下面就列车常用的这几种格式图片的头部信息标识(十六进制)。
1.Png图片文件包括8字节:89 50 4E 47 0D 0A 1A 0A。即为 .PNG....。
2.Jpg图片文件包括2字节:FF D8。
3.Gif图片文件包括6字节:47 49 46 38 39|37 61 。即为 GIF89(7)a。
4.Bmp图片文件包括2字节:42 4D。即为 BM。
根据图片问题头标识信息我们可以能很方便的判断出文件的格式,首先我们需要获取图片文件的字节流信息,代码如下。
Stream stream = ; StreamResourceInfo info = Application.GetResourceStream( Uri(path, UriKind.Relative)); (info != ) { stream = info.Stream; } (IsolatedStorageFile myIsolatedStorage = IsolatedStorageFile.GetUserStoreForApplication()) { stream = myIsolatedStorage.OpenFile(path, FileMode.Open, FileAccess.Read); }
从图片文件流stream中读取8个字节,然后再根据不同的图片格式做文件头匹配比较具能判断出文件的格式,代码如下。
ImageType { Null, Png, Jpg, Gif, Bmp } ImageType getImageType(Stream stream) { ImageType type = ImageType.Null; [] header = []; stream.Read(header, , ); (header[] == && header[] == && header[] == && header[] == && header[] == && header[] == && header[] == && header[] == ) { type = ImageType.Png; } (header[] == && header[] == ) { type = ImageType.Jpg; } (header[] == && header[] == && header[] == && header[] == && (header[] == || header[] == ) && header[] == ) { type = ImageType.Gif; } (header[] == && header[] == ) { type = ImageType.Bmp; } stream.Close(); type; }
解析到图片格式后,我们就可以根据图片格式选择对应的控件来显示图片了。
PNG:
要解析Png图片的宽度和高度信息,首先需要了解Png图片的数据块结构,Png图片的尺寸信息存放在文件头数据块中,所以我们需要了解文件头的数据块结构。
文件头数据块IHDR(header chunk):它包含有PNG文件中存储的图像数据的基本信息,并要作为第一个数据块出现在PNG数据流中,而且一个PNG数据流中只能有一个文件头数据块。 文件头数据块由13字节组成,其中前8个字节即为图片的宽度和高度信息,各占4个字节。
文件头数据块是第一个数据块,但是在数据块前,也就是PNG图片文件的最开始位置首先存储的是PNG文件署名域,占8个字节,即:89 50 4e 47 0d 0a 1a 0a ( .PNG....),通过这个可以判断图片是否为PNG格式,接下来是文件头数据块的长度和标识,各占4个字节,文件头数据块的长度为13,所以文件头数据块的长度固定为 00 00 00 0D,而文件头数据块的标识为49 48 44 52,即“IHDR”。
通过上面的解析,我们可以分一下几步来解析PNG图片的宽度和高度。
1.首先读取起始位的8个字节,即PNG文件署名域,判断图片是否为PNG格式,如果不是,则退出。
[] header = []; stream.Read(header, , ); (!(header[] == && header[] == && header[] == && header[] == && header[] == && header[] == && header[] == && header[] == )) { ; }
2.然后跳过8个字节,即文件头数据块的长度值 00 00 00 0D,以及文件头数据块标识 49 48 44 52(IHDR)。
stream.Seek(, SeekOrigin.Current);
3.接下来要读取就是图片的宽度和高度值,读取8个字节,由于是按照高低位调换存储,所以需要做高低位转换,转换后通过BitConverter类直接将字节类型转换为整数型即为图片的尺寸。
[] buffer = []; stream.Read(buffer, , buffer.Length); Array.Reverse(buffer, , ); Array.Reverse(buffer, , ); width_ = BitConverter.ToInt32(buffer, ); height_ = BitConverter.ToInt32(buffer, );
GIF:
GIF图片的文件格式相对比较简单,其中宽度和高度信息存放在逻辑视屏描述块的前4个字节,而逻辑视屏描述块是GIF图片的第二块区域,第一个区域为6个字节的头部,头部包括标识符和版本。下表列出到高度信息为止的各个字节的描述。
名称
字节
说明
头部
标识符
3
GIF 47 49 46
版本
3
87a(89a) 38 39|37 61
逻辑视屏描述块
宽度
2
高度
2
根据上面的格式很容易获取图片的高度和宽度信息,具体代码如下。
[] header = []; stream.Read(header, , ); (!(header[] == && header[] == && header[] == && header[] == && (header[] == || header[] == ) && header[] == )) { ; } [] buffer = []; stream.Read(buffer, , buffer.Length); width_ = BitConverter.ToInt16(buffer, ); height_ = BitConverter.ToInt16(buffer, );
BMP:
典型的位图文件格式通常包含下面几个数据块:
位图头:保存位图文件的总体信息。
位图信息:保存位图图像的详细信息。
调色板:保存所用颜色的定义。
位图数据:保存一个又一个像素的实际图像。
通过解析位图头我们可以判断图片是否为bmp格式,而我们所需要的图片尺寸信息存储在位图信息数据块里。所以我们需要详细了解位图头和位图信息两个数据块的存储结构。
位图头,这部分是识别信息,典型的应用程序会首先普通读取这部分数据以确保的确是位图文件并且没有损坏。
字节 #0-1 保存位图文件的标识符,这两个字节的典型数据是BM。
字节 #2-5 使用一个dword保存位图文件大小。
字节 #6-9 是保留部分,留做以后的扩展使用,对实际的解码格式没有影响。
字节 #10-13 保存位图数据位置的地址偏移,也就是起始地址。
位图信息,这部分告诉应用程序图像的详细信息,在屏幕上显示图像将会使用这些信息,它从文件的第15个字节开始。
字节 #14-17 定义以下用来描述影像的区块(BitmapInfoHeader)的大小。它的值是:40 - Windows 3.2、95、NT、12 - OS/2 1.x、240 - OS/2 2.x
字节 #18-21 保存位图宽度(以像素个数表示)。
字节 #22-25 保存位图高度(以像素个数表示)。
…………
以上关于bmp文件结构的内容参考维基百科http://zh.wikipedia.org/wiki/BMP。
通过上面对BMP图片格式的了解,可以用下面的代码来解析bmp图片的尺寸大小。
[] header = []; stream.Read(header, , ); ( !(header[] == && header[] == )) { ; } stream.Seek(, SeekOrigin.Current); [] buffer = []; stream.Read(buffer, , buffer.Length); width_ = BitConverter.ToInt32(buffer, ); height_ = BitConverter.ToInt32(buffer, );
JPG:
由于jpg图片的格式相对于png要复杂很多,所以首先我们要先清楚的了解jpg图片的数据格式,jpg图片包括SOI和数据两个部分。
SOI,Start of Image,图像开始,标记代码 2字节 固定值0xFFD8。
数据部分分成很多数据段,数据段的一般结构如下。
段数据结构
名称
字节
说明
段标识
>= 1
多于一个的0xFF
段类型
1
类型编码(称作“标记码”)
段长度
2
包括段内容和段长度本身,不包括段标识和段类型
短内容
<= 65533
段类型有30种,但只有10种是必须被所有程序识别的,其它的类型都可以忽略。在这么多的段中,其中JPG图片的尺寸相关信息存储在SOF0(图像基本信息)段中。所以需要详细了解一下SOFO段的数据结构。
SOFO段结构
名称
字节
说明
段标识
1
0XFF
段类型
1
0XCO JFIF格式的为0XC2
段长度
2
其值=8+组件数量×3
样本精度
1
8 每个样本位数(大多数软件不支持12和16)
图片高的
2
采用Motorola格式,即:高位在前,低位在后
图片宽度
2
采用Motorola格式,即:高位在前,低位在后
由于我们是为了解析JPG图片的宽度和高度信息,所以上表SOFO段结构只列出了到宽度为止结构信息,接下来还有其他一些图片的相关信息,这里就不再列出。
根据上面对JPG图片格式的解析,我们可以分一下几步来解析JPG图片的尺寸信息。
1.读取2个字节的SOI,即0xFFD8,根据这两个字节判断图片是否为JPG图片,如果不是,则退出解析过程。
[] header = []; stream.Read(header, , ); (!(header[] == && header[] == )) { ; }
2.接下来就需要解析图片的数据部分,由于数据部分是有很多不同的数据段构成,数据段拥有一些共同的特性,所以这里我们需要做一个循环来逐个遍历查找到SOFO数据段。
type = -; ff = -; ps = ; { { ff = stream.ReadByte(); (ff < ) { ; } } (ff != ); { type = stream.ReadByte(); } (type == ); ps = stream.Position; (type) { : : : : : : : : : : ; : : { getJpgSize(stream); ; } : ps = stream.ReadByte() * ; ps = stream.Position + ps + stream.ReadByte() - ; ; } (ps + >= stream.Length) { ; } stream.Position = ps; } (type != );
3.找到SOFO数据段后,就可以解析图片的宽度和高度信息。
getJpgSize(Stream stream) { stream.Seek(, SeekOrigin.Current); height_ = stream.ReadByte() * ; height_ += stream.ReadByte(); width_ = stream.ReadByte() * ; width_ += stream.ReadByte(); }