于比较常用的图片格式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 = null;
//如果是资源文件处理
StreamResourceInfo info = Application.GetResourceStream(new Uri(path, UriKind.Relative));
if (info != null)
{
stream = info.Stream;
}
//如果是独立存储文件处理
using (IsolatedStorageFile myIsolatedStorage = IsolatedStorageFile.GetUserStoreForApplication())
{
//打开文件
stream = myIsolatedStorage.OpenFile(path, FileMode.Open, FileAccess.Read);
}
从图片文件流stream中读取8个字节,然后再根据不同的图片格式做文件头匹配比较具能判断出文件的格式,代码如下。

/// <summary>
/// 定义图片格式
/// </summary>
public enum ImageType
{
Null,
Png,
Jpg,
Gif,
Bmp
}
/// <summary>
/// 获取图片格式
/// </summary>
private ImageType getImageType(Stream stream)
{
//图片格式
ImageType type = ImageType.Null;
//读取图片文件头8个字节,并根据若干个字节来确定图片格式
byte[] header = new byte[8];
stream.Read(header, 0, 8);
//确定图片格式
if (header[0] == 0x89 &&
header[1] == 0x50 && // P
header[2] == 0x4E && // N
header[3] == 0x47 && // G
header[4] == 0x0D &&
header[5] == 0x0A &&
header[6] == 0x1A &&
header[7] == 0x0A)
{
//Png图片 8字节:89 50 4E 47 0D 0A 1A 0A
type = ImageType.Png;
}
else if (header[0] == 0xFF &&
header[1] == 0xD8)
{
//Jpg图片 2字节:FF D8
type = ImageType.Jpg;
}
else if (header[0] == 0x47 && // G
header[1] == 0x49 && // I
header[2] == 0x46 && // F
header[3] == 0x38 && // 8
(header[4] == 0x39 || // 9
header[4] == 0x37) && // 7
header[5] == 0x61) // a
{
//Gif图片 6字节:47 49 46 38 39|37 61
type = ImageType.Gif;
}
else if (header[0] == 0x42 && //B
header[1] == 0x4D) //M
{
//Bmp图片 2字节:42 4D
type = ImageType.Bmp;
}
//关闭字节流
stream.Close();
return 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格式,如果不是,则退出。

//读取图片文件头8个字节,并根据这8个字节来判断是否为PNG图片
byte[] header = new byte[8];
stream.Read(header, 0, 8);
//Png图片 8字节:89 50 4E 47 0D 0A 1A 0A
if (!(header[0] == 0x89 &&
header[1] == 0x50 && // P
header[2] == 0x4E && // N
header[3] == 0x47 && // G
header[4] == 0x0D &&
header[5] == 0x0A &&
header[6] == 0x1A &&
header[7] == 0x0A))
{
//不是PNG图片
return;
}
2.然后跳过8个字节,即文件头数据块的长度值 00 00 00 0D,以及文件头数据块标识 49 48 44 52(IHDR)。

//数据域长度 4 指定数据域的长度,固定为00 00 00 0D
//数据块符号 4 49 48 44 52,是“IHDR”的 Ascii 码
stream.Seek(8, SeekOrigin.Current);
3.接下来要读取就是图片的宽度和高度值,读取8个字节,由于是按照高低位调换存储,所以需要做高低位转换,转换后通过BitConverter类直接将字节类型转换为整数型即为图片的尺寸。

//读取宽度,高度 各4字节
byte[] buffer = new byte[8];
stream.Read(buffer, 0, buffer.Length);
Array.Reverse(buffer, 0, 4);
Array.Reverse(buffer, 4, 4);
width_ = BitConverter.ToInt32(buffer, 0);
height_ = BitConverter.ToInt32(buffer, 4);
GIF:
GIF图片的文件格式相对比较简单,其中宽度和高度信息存放在逻辑视屏描述块的前4个字节,而逻辑视屏描述块是GIF图片的第二块区域,第一个区域为6个字节的头部,头部包括标识符和版本。下表列出到高度信息为止的各个字节的描述。
名称 | 字节 | 说明 |
头部 | ||
标识符 | 3 | GIF 47 49 46 |
版本 | 3 | 87a(89a) 38 39|37 61 |
逻辑视屏描述块 | ||
宽度 | 2 | |
高度 | 2 |
根据上面的格式很容易获取图片的高度和宽度信息,具体代码如下。

//gif图片信息域(47 49 46 38 39|37 61) GIF89(7)a,共6字节
//根据6字节判断是否为gif图片
byte[] header = new byte[6];
stream.Read(header, 0, 6);
if (!(header[0] == 0x47 && // G
header[1] == 0x49 && // I
header[2] == 0x46 && // F
header[3] == 0x38 && // 8
(header[4] == 0x39 || // 9
header[4] == 0x37) && // 7
header[5] == 0x61)) // a
{
//不是GIF图片,退出
return;
}
//读取宽度,高度 各2字节
byte[] buffer = new byte[4];
stream.Read(buffer, 0, buffer.Length);
width_ = BitConverter.ToInt16(buffer, 0);
height_ = BitConverter.ToInt16(buffer, 2);
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图片的尺寸大小。

//Bmp 图片前2字节:0x42 4D
byte[] header = new byte[2];
stream.Read(header, 0, 2);
if ( !(header[0] == 0x42 && header[1] == 0x4D))
{
//不是BMP图片
return;
}
//跳过16个字节
stream.Seek(16, SeekOrigin.Current);
//bmp图片的宽度信息保存在第 18-21位 4字节
//bmp图片的高度度信息保存在第 22-25位 4字节
//读取宽度,高度 各4字节
byte[] buffer = new byte[8];
stream.Read(buffer, 0, buffer.Length);
width_ = BitConverter.ToInt32(buffer, 0);
height_ = BitConverter.ToInt32(buffer, 4);
JPG:
由于jpg图片的格式相对于png要复杂很多,所以首先我们要先清楚的了解jpg图片的数据格式,jpg图片包括SOI和数据两个部分。
SOI,Start of Image,图像开始,标记代码 2字节 固定值0xFFD8。
数据部分分成很多数据段,数据段的一般结构如下。
名称 | 字节 | 说明 |
段标识 | >= 1 | 多于一个的0xFF |
段类型 | 1 | 类型编码(称作“标记码”) |
段长度 | 2 | 包括段内容和段长度本身,不包括段标识和段类型 |
短内容 | <= 65533 |
段类型有30种,但只有10种是必须被所有程序识别的,其它的类型都可以忽略。在这么多的段中,其中JPG图片的尺寸相关信息存储在SOF0(图像基本信息)段中。所以需要详细了解一下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图片,如果不是,则退出解析过程。

//读取2个字节的SOI,即0xFFD8
byte[] header = new byte[2];
stream.Read(header, 0, 2);
//判断是否为JPG,不是退出解析
if (!(header[0] == 0xFF &&
header[1] == 0xD8))
{
//不是JPG图片
return;
}
2.接下来就需要解析图片的数据部分,由于数据部分是有很多不同的数据段构成,数据段拥有一些共同的特性,所以这里我们需要做一个循环来逐个遍历查找到SOFO数据段。

//段类型
int type = -1;
int ff = -1;
//记录当前读取的位置
long ps = 0;
//逐个遍历所以段,查找SOFO段
do
{
do
{
//每个新段的开始标识为oxff,查找下一个新段
ff = stream.ReadByte();
if (ff < 0) //文件结束
{
return;
}
} while (ff != 0xff);
do
{
//段与段之间有一个或多个oxff间隔,跳过这些oxff之后的字节为段标识
type = stream.ReadByte();
} while (type == 0xff);
//记录当前位置
ps = stream.Position;
switch (type)
{
case 0x00:
case 0x01:
case 0xD0:
case 0xD1:
case 0xD2:
case 0xD3:
case 0xD4:
case 0xD5:
case 0xD6:
case 0xD7:
break;
case 0xc0: //SOF0段(图像基本信息)
case 0xc2: //JFIF格式的 SOF0段
{
//找到SOFO段,解析宽度和高度信息
getJpgSize(stream);
return;
}
default: //别的段都跳过
//获取段长度,直接跳过
ps = stream.ReadByte() * 256;
ps = stream.Position + ps + stream.ReadByte() - 2;
break;
}
if (ps + 1 >= stream.Length) //文件结束
{
return;
}
stream.Position = ps; //移动指针
} while (type != 0xda); // 扫描行开始
3.找到SOFO数据段后,就可以解析图片的宽度和高度信息。

/// <summary>
/// 解析JPG图片的尺寸
/// </summary>
/// <param name="stream"></param>
private void getJpgSize(Stream stream)
{
//跳过2个自己长度信息和1个字节的精度信息
stream.Seek(3, SeekOrigin.Current);
//高度 占2字节 低位高位互换
height_ = stream.ReadByte() * 256;
height_ += stream.ReadByte();
//宽度 占2字节 低位高位互换
width_ = stream.ReadByte() * 256;
width_ += stream.ReadByte();
}
出处::
作者:宇之乐
出处:http://www.cnblogs.com/huizhang212/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
</div>
</div>