天天看点

完整的位图文件解析

云彩挂上的二叉树 - http://blog.csdn.net/markl22222/archive/2011/04/06/6304318.aspx

与上次简单的位图加载不同,这次是完整的位图格式解析.暂时没有考虑压缩格式的位图.

下面的内容难免有错误,各位如发现纰漏请及时指出.

  • 1.位图结构介绍

位图其实比较复杂,主要是由于各种原因导致的标准拓展和复杂化所致.

从磁盘上加载的BMP图片属于设备无关位图(DIB).通常来讲,DIB位图的基本结构如下:

完整的位图文件解析

其中,最上面的BITMAPFILEHEADER可以由后面的BITMAPINFO还原出来,没有BITMAPFILEHEADER的DIB被成为"压缩DIB".这里的压缩,指的是DIB的其余部分在连续的内存空间里紧挨着存放.

很多用于加载DIB的API,如LoadResource,LoadImage之类,它们返回的DIB句柄就是一个压缩的DIB.

  • BITMAPFILEHEADER 位图文件头

BITMAPFILEHEADER 结构定义:

typedef struct tagBITMAPFILEHEADER {

WORD bfType;

DWORD bfSize;

WORD bfReserved1;

WORD bfReserved2;

DWORD bfOffBits;

} BITMAPFILEHEADER, FAR *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;

bfType是BMP文件的签名.对于目前的BMP文件,此字段只可能是两个ASCII字符'B'和'M'.其他的字段内容表示的文件仅被用在OS/2系统中.在校验BMP文件时可以直接通过检查文件最开头的16位字符是否为0x4D42来判断此文件是否为BMP文件.

bfSize存储了整个BMP文件的大小.

bfOffBits是Pixel数据段相对文件最开始的偏移量.

  • BITMAPINFO 位图信息

BITMAPINFO 结构定义:

typedef struct tagBITMAPINFO {

BITMAPINFOHEADER bmiHeader;

RGBQUAD bmiColors[1];

} BITMAPINFO, FAR *LPBITMAPINFO, *PBITMAPINFO;

typedef struct tagBITMAPINFOHEADER{

DWORD biSize;

LONG biWidth;

LONG biHeight;

WORD biPlanes;

WORD biBitCount;

DWORD biCompression;

DWORD biSizeImage;

LONG biXPelsPerMeter;

LONG biYPelsPerMeter;

DWORD biClrUsed;

DWORD biClrImportant;

} BITMAPINFOHEADER, FAR *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;

typedef struct tagRGBQUAD {

BYTE rgbBlue;

BYTE rgbGreen;

BYTE rgbRed;

BYTE rgbReserved;

} RGBQUAD;

typedef RGBQUAD FAR* LPRGBQUAD;

BITMAPINFOHEADER为位图信息头.

位图信息头的版本相当多,最初的版本是BITMAPCOREHEADER,仅用于OS/2,从Win3.1开始是BITMAPINFOHEADER,被称为位图信息结构版本3;在后面还有配合Win95与NT中新特征而形成的版本4:BITMAPV4HEADER;以及配合Win98和Win2K中新特征而形成的版本5:BITMAPV5HEADER.

一般最经常看到的是BITMAPINFOHEADER,我们这里只简要介绍BITMAPINFOHEADER.

biSize在这里必须等于sizeof(BITMAPINFOHEADER),这是用来判断信息头版本的标记.其实更高版本的信息头只是在低版本上做了一些拓展,依然可以用低版本的方式解析.

biWidth与biHeight是以像素为单位的图像宽度和高度(BITMAPCOREHEADER中它们的类型是WORD,后面的版本都是LONG).biHeight可能取负值.当biHeight<0时表示像素阵列的扫描线由上至下进行.

biPlanes总是等于1.在MSDN中有这样的描述:"Specifies the number of planes for the target device. This value must be set to 1".DIB只支持单个位平面的图像.

biBitCount,表示一个像素所需要的二进制位数.可能有以下取值:1;2;4;8;16;24;32.

biCompression,压缩格式.BI_RGB与BI_BITFIELDS表示未压缩的位图.16位与32位位图的此字段可以是BI_BITFIELDS,此时在位图信息头之后将加入3个DWORD表示如何从Pixel中提取RGB的屏蔽位.

biSizeImage是像素阵列的大小.BI_RGB的位图此字段可以是0.由于像素字节数组中每个扫描行的字节数必需是4的倍数,不足将会用0补齐,因此真正的像素阵列行大小计算起来有点绕.有个通用的公式可以解决这个问题:pitch = 4 * ( (biWidth * biBitCount + 31) / 32 );当然,还有很多其他的计算方法,这里就不一一列举了.最后,biSizeImage = pitch * biHeight;

biClrUsed表示颜色表的项数.

biClrImportant表示显示位图实际需要的颜色表项数.

  • Pixel[][] 像素阵列

不同位数的位图,其像素阵列的存取都有所不同.

1,4,8位位图的像素阵列中存放的不是像素,而是此像素在颜色表中的索引.通过索引查找颜色表就可以得到这个像素点的颜色值.

16位位图是最为麻烦的.不仅位图数据分555与565(指RGB三个分量在每个16位像素中的排列方法)两种格式,位图类型也分BI_RGB与BI_BITFIELDS两种类型.并且最后得到的RGB数据还需要处理成合适的大小,因为16位中每个通道只有32或64级,但常规颜色是256级,因此需要按照色深进行转换.

24位位图比较简单,每个24位像素中RGB按888排列,即每个通道一个BYTE.

32位位图可以比24位位图多一个Alpha通道(但是不一定),因此可以显示半透明效果(RGBA).其他读取同24位位图一致.

  • 2.位图像素读取

下面给出各种不同位数位图像素阵列的读取方法.

首先是通用的数据预读取,这里定义一个宏来完成所有工作:

#pragma push_macro("PreDecode")

#undef PreDecode

#define PreDecode() /

int img_w = bmiInfo.bmiHeader.biWidth; /

int img_h = bmiInfo.bmiHeader.biHeight; /

int bit_c = bmiInfo.bmiHeader.biBitCount; /

int bit_s = bmhHead.bfOffBits; /

int pit_b = PitchBytes(img_w, bit_c); /

int pit_w = PitchWidth(pit_b); /

CGC gc; /

BYTE* temp = ExMem::Alloc<BYTE>(&gc, pit_b)

//#define PreDecode 

下面是具体的像素读取算法.

1位位图(单色位图):

PreDecode();

// 获取调色板

RGBQUAD colors[1 << 1] = {0};

pFile->Seek(bit_s - sizeof(colors), IFileObject::begin);

pFile->Read(colors, sizeof(colors), 1);

// 解析图像

for(int y = 0; y < img_h; ++y)

{

int pos = img_w * y;

int inx = pit_w * y;

pFile->Seek(bit_s + inx, IFileObject::begin);

pFile->Read(temp, pit_b, 1);

inx = 0;

for(int x = 0; x < img_w; ++inx)

{

for(int n = 7; n >= 0 && x < img_w; --n, ++x, ++pos)

{

BYTE c = (temp[inx] >> (1 * n)) & 0x01;

bmBuff[pos] = ExRGBA

(

colors[c].rgbBlue,

colors[c].rgbGreen,

colors[c].rgbRed,

(BYTE)~0

);

}

}

}

4位位图(16色位图):

PreDecode();

// 获取调色板

RGBQUAD colors[1 << 4] = {0};

pFile->Seek(bit_s - sizeof(colors), IFileObject::begin);

pFile->Read(colors, sizeof(colors), 1);

// 解析图像

for(int y = 0; y < img_h; ++y)

{

int pos = img_w * y;

int inx = pit_w * y;

pFile->Seek(bit_s + inx, IFileObject::begin);

pFile->Read(temp, pit_b, 1);

inx = 0;

for(int x = 0; x < img_w; ++inx)

{

for(int n = 1; n >= 0 && x < img_w; --n, ++x, ++pos)

{

BYTE c = (temp[inx] >> (4 * n)) & 0x0F;

bmBuff[pos] = ExRGBA

(

colors[c].rgbBlue,

colors[c].rgbGreen,

colors[c].rgbRed,

(BYTE)~0

);

}

}

8位位图(256色位图):

PreDecode();

// 获取调色板

RGBQUAD colors[1 << 8] = {0};

pFile->Seek(bit_s - sizeof(colors), IFileObject::begin);

pFile->Read(colors, sizeof(colors), 1);

// 解析图像

for(int y = 0; y < img_h; ++y)

{

int pos = img_w * y;

int inx = pit_w * y;

pFile->Seek(bit_s + inx, IFileObject::begin);

pFile->Read(temp, pit_b, 1);

for(int x = 0; x < img_w; ++x, ++pos)

{

bmBuff[pos] = ExRGBA

(

colors[temp[x]].rgbBlue,

colors[temp[x]].rgbGreen,

colors[temp[x]].rgbRed,

(BYTE)~0

);

}

}

16位位图(高彩位图):

PreDecode();

// 获取掩码

uint32_t mask[3] = {0};

if (bmiInfo.bmiHeader.biCompression == BI_RGB)

{

mask[0] = 0x7C00;

mask[1] = 0x03E0;

mask[2] = 0x001F;

} else // BI_BITFIELDS

{

pFile->Seek(sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER), IFileObject::begin);

pFile->Read(mask, sizeof(mask), 1);

}

BYTE cnt_m[3] =

{

BitCount(mask[0]),

BitCount(mask[1]),

BitCount(mask[2])

};

// 解析图像

for(int y = 0; y < img_h; ++y)

{

int pos = img_w * y;

int inx = pit_w * y;

pFile->Seek(bit_s + inx, IFileObject::begin);

pFile->Read(temp, pit_b, 1);

inx = 0;

for(int x = 0; x < img_w; ++x, ++pos, inx += 2)

{

uint16_t pixel = *(uint16_t*)(temp + inx);

bmBuff[pos] = ExRGBA

(

(pixel & mask[2]) << (8 - cnt_m[2]),

((pixel & mask[1]) >> cnt_m[2]) << (8 - cnt_m[1]),

((pixel & mask[0]) >> (cnt_m[1] + cnt_m[2])) << (8 - cnt_m[0]),

(BYTE)~0

);

}

}

24位位图(真彩位图):

PreDecode();

// 解析图像

for(int y = 0; y < img_h; ++y)

{

int pos = img_w * y;

int inx = pit_w * y;

pFile->Seek(bit_s + inx, IFileObject::begin);

pFile->Read(temp, pit_b, 1);

inx = 0;

for(int x = 0; x < img_w; ++x, ++pos, inx += 3)

{

bmBuff[pos] = ExRGBA

(

temp[inx],

temp[inx + 1],

temp[inx + 2],

(BYTE)~0

);

}

}

32位位图(增强真彩位图):

PreDecode();

// 解析图像

for(int y = 0; y < img_h; ++y)

{

int pos = img_w * y;

int inx = pit_w * y;

pFile->Seek(bit_s + inx, IFileObject::begin);

pFile->Read(temp, pit_b, 1);

inx = 0;

for(int x = 0; x < img_w; ++x, ++pos, inx += 4)

{

bmBuff[pos] = ExRGBA

(

temp[inx],

temp[inx + 1],

temp[inx + 2],

(BYTE)~0/*temp[inx + 3]*/

);

}

}

  • 3.完整示例代码

这里给出的代码是我个人程序中的代码,自己单独使用需要按照自己的需求对其中的一些预定义及底层调用做适当的修改.

全部的图像解析及底层库代码请访问:http://code.google.com/p/win32-standard-expansion/

  • CoderObject.h

//

// CoderObject - 编/解码器基类

//

// Author: 木头云

// Blog: blog.csdn.net/markl22222

// E-Mail: [email protected]

// Date: 2011-04-05

// Version: 1.0.0001.2350

//

// History:

// - 1.0.0001.2350(2011-04-05) = 将具体的image_t内存块申请工作统一放在ICoderObject中处理

// = ICoderObject::DeleteImage()不再断言Image参数

//

#ifndef __CoderObject_h__

#define __CoderObject_h__

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

EXP_BEG

//

interface ICoderObject

{

protected:

IFileObject* m_pFile;

template <DWORD SizeT>

EXP_INLINE static bool CheckFile(IFileObject* pFile, const BYTE (&chkHead)[SizeT])

{

if(!pFile) return false;

CFileSeeker seeker(pFile);

BYTE tmp_buff[SizeT] = {0};

// 判断头部

if(!pFile->Seek(0, IFileObject::begin))

return false;

if (pFile->Read(tmp_buff, _countof(tmp_buff), sizeof(BYTE)) != _countof(chkHead))

return false;

if (memcmp(tmp_buff, chkHead, sizeof(chkHead)) != 0)

return false;

return true;

}

EXP_INLINE static image_t GetImageBuff(LONG nWidth, LONG nHeight, BYTE*& pBuff)

{

if (nWidth <= 0 || nHeight <= 0) return NULL;

pBuff = NULL;

BITMAPINFO bmi = {0};

bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);

bmi.bmiHeader.biBitCount = 32;

bmi.bmiHeader.biCompression = BI_RGB;

bmi.bmiHeader.biPlanes = 1;

bmi.bmiHeader.biWidth = nWidth;

bmi.bmiHeader.biHeight = nHeight;

image_t image = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, (void**)&pBuff, NULL, 0);

return pBuff ? image : NULL;

}

public:

ICoderObject()

: m_pFile(NULL)

{}

ICoderObject(IFileObject* pFile)

: m_pFile(NULL)

{ SetFile(pFile); }

virtual ~ICoderObject()

{}

public:

virtual void SetFile(IFileObject* pFile)

{ m_pFile = pFile; }

virtual IFileObject* GetFile()

{ return m_pFile; }

virtual bool Encode(image_t Image) = 0;

virtual image_t Decode() = 0;

EXP_INLINE static bool DeleteImage(image_t Image)

{ return Image ? ::DeleteObject(Image) : true; }

};

//

EXP_END

#endif/*__CoderObject_h__*/

  • BmpCoder.h

//

// BmpCoder - BMP文件编/解码器

//

// Author: 木头云

// Blog: blog.csdn.net/markl22222

// E-Mail: [email protected]

// Date: 2011-04-06

// Version: 1.0.0003.1544

//

// History:

// - 1.0.0001.2350(2011-04-05) ^ 优化BmpCoder的结构

// + 添加对16位位图的解析处理

// - 1.0.0002.1326(2011-04-06) = 32位位图不再解析Alpha通道

// - 1.0.0003.1544(2011-04-06) ^ 支持任何格式的16位位图解码

//

#ifndef __BmpCoder_h__

#define __BmpCoder_h__

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

#include "ImgCoder/CoderObject.h"

EXP_BEG

//

class CBmpCoder : public ICoderObject

{

protected:

// 拿到对齐后的字节宽度

EXP_INLINE static int PitchBytes(int nWidth, int nBitCount)

{ return (nWidth * nBitCount + 7) >> 3; }

EXP_INLINE static int PitchWidth(int nPitByts)

{ return ((nPitByts + 3) >> 2) << 2; } // 4 * ( (biWidth * biBitCount + 31) / 32 );

EXP_INLINE static int PitchWidth(int nWidth, int nBitCount)

{ return PitchWidth(PitchBytes(nWidth, nBitCount)); }

// 计算二进制中1的个数

EXP_INLINE static BYTE BitCount(DWORD nNum)

{

BYTE cnt = 0;

while(nNum)

{

++cnt;

nNum &= (nNum - 1);

}

return cnt;

}

public:

EXP_INLINE static bool CheckFile(IFileObject* pFile)

{

if(!pFile) return false;

BYTE chk_head[2] = { 0x42, 0x4D };

return ICoderObject::CheckFile(pFile, chk_head);

}

public:

CBmpCoder()

: ICoderObject()

{}

CBmpCoder(IFileObject* pFile)

: ICoderObject(pFile)

{}

protected:

#pragma push_macro("PreDecode")

#undef PreDecode

#define PreDecode() /

int img_w = bmiInfo.bmiHeader.biWidth; /

int img_h = bmiInfo.bmiHeader.biHeight; /

int bit_c = bmiInfo.bmiHeader.biBitCount; /

int bit_s = bmhHead.bfOffBits; /

int pit_b = PitchBytes(img_w, bit_c); /

int pit_w = PitchWidth(pit_b); /

CGC gc; /

BYTE* temp = ExMem::Alloc<BYTE>(&gc, pit_b)

//#define PreDecode

EXP_INLINE static void Decode32(IFileObject* pFile, BITMAPFILEHEADER& bmhHead, BITMAPINFO& bmiInfo, COLORREF* bmBuff)

{

if (!pFile || !bmBuff) return;

PreDecode();

// 解析图像

for(int y = 0; y < img_h; ++y)

{

int pos = img_w * y;

int inx = pit_w * y;

pFile->Seek(bit_s + inx, IFileObject::begin);

pFile->Read(temp, pit_b, 1);

inx = 0;

for(int x = 0; x < img_w; ++x, ++pos, inx += 4)

{

bmBuff[pos] = ExRGBA

(

temp[inx],

temp[inx + 1],

temp[inx + 2],

(BYTE)~0

);

}

}

}

EXP_INLINE static void Decode24(IFileObject* pFile, BITMAPFILEHEADER& bmhHead, BITMAPINFO& bmiInfo, COLORREF* bmBuff)

{

if (!pFile || !bmBuff) return;

PreDecode();

// 解析图像

for(int y = 0; y < img_h; ++y)

{

int pos = img_w * y;

int inx = pit_w * y;

pFile->Seek(bit_s + inx, IFileObject::begin);

pFile->Read(temp, pit_b, 1);

inx = 0;

for(int x = 0; x < img_w; ++x, ++pos, inx += 3)

{

bmBuff[pos] = ExRGBA

(

temp[inx],

temp[inx + 1],

temp[inx + 2],

(BYTE)~0

);

}

}

}

EXP_INLINE static void Decode16(IFileObject* pFile, BITMAPFILEHEADER& bmhHead, BITMAPINFO& bmiInfo, COLORREF* bmBuff)

{

if (!pFile || !bmBuff) return;

PreDecode();

// 获取掩码

uint32_t mask[3] = {0};

if (bmiInfo.bmiHeader.biCompression == BI_RGB)

{

mask[0] = 0x7C00;

mask[1] = 0x03E0;

mask[2] = 0x001F;

} else // BI_BITFIELDS

{

pFile->Seek(sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER), IFileObject::begin);

pFile->Read(mask, sizeof(mask), 1);

}

BYTE cnt_m[3] =

{

BitCount(mask[0]),

BitCount(mask[1]),

BitCount(mask[2])

};

// 解析图像

for(int y = 0; y < img_h; ++y)

{

int pos = img_w * y;

int inx = pit_w * y;

pFile->Seek(bit_s + inx, IFileObject::begin);

pFile->Read(temp, pit_b, 1);

inx = 0;

for(int x = 0; x < img_w; ++x, ++pos, inx += 2)

{

uint16_t pixel = *(uint16_t*)(temp + inx);

bmBuff[pos] = ExRGBA

(

(pixel & mask[2]) << (8 - cnt_m[2]),

((pixel & mask[1]) >> cnt_m[2]) << (8 - cnt_m[1]),

((pixel & mask[0]) >> (cnt_m[1] + cnt_m[2])) << (8 - cnt_m[0]),

(BYTE)~0

);

}

}

}

EXP_INLINE static void Decode8(IFileObject* pFile, BITMAPFILEHEADER& bmhHead, BITMAPINFO& bmiInfo, COLORREF* bmBuff)

{

if (!pFile || !bmBuff) return;

PreDecode();

// 获取调色板

RGBQUAD colors[1 << 8] = {0};

pFile->Seek(bit_s - sizeof(colors), IFileObject::begin);

pFile->Read(colors, sizeof(colors), 1);

// 解析图像

for(int y = 0; y < img_h; ++y)

{

int pos = img_w * y;

int inx = pit_w * y;

pFile->Seek(bit_s + inx, IFileObject::begin);

pFile->Read(temp, pit_b, 1);

for(int x = 0; x < img_w; ++x, ++pos)

{

bmBuff[pos] = ExRGBA

(

colors[temp[x]].rgbBlue,

colors[temp[x]].rgbGreen,

colors[temp[x]].rgbRed,

(BYTE)~0

);

}

}

}

EXP_INLINE static void Decode4(IFileObject* pFile, BITMAPFILEHEADER& bmhHead, BITMAPINFO& bmiInfo, COLORREF* bmBuff)

{

if (!pFile || !bmBuff) return;

PreDecode();

// 获取调色板

RGBQUAD colors[1 << 4] = {0};

pFile->Seek(bit_s - sizeof(colors), IFileObject::begin);

pFile->Read(colors, sizeof(colors), 1);

// 解析图像

for(int y = 0; y < img_h; ++y)

{

int pos = img_w * y;

int inx = pit_w * y;

pFile->Seek(bit_s + inx, IFileObject::begin);

pFile->Read(temp, pit_b, 1);

inx = 0;

for(int x = 0; x < img_w; ++inx)

{

for(int n = 1; n >= 0 && x < img_w; --n, ++x, ++pos)

{

BYTE c = (temp[inx] >> (4 * n)) & 0x0F;

bmBuff[pos] = ExRGBA

(

colors[c].rgbBlue,

colors[c].rgbGreen,

colors[c].rgbRed,

(BYTE)~0

);

}

}

}

}

EXP_INLINE static void Decode1(IFileObject* pFile, BITMAPFILEHEADER& bmhHead, BITMAPINFO& bmiInfo, COLORREF* bmBuff)

{

if (!pFile || !bmBuff) return;

PreDecode();

// 获取调色板

RGBQUAD colors[1 << 1] = {0};

pFile->Seek(bit_s - sizeof(colors), IFileObject::begin);

pFile->Read(colors, sizeof(colors), 1);

// 解析图像

for(int y = 0; y < img_h; ++y)

{

int pos = img_w * y;

int inx = pit_w * y;

pFile->Seek(bit_s + inx, IFileObject::begin);

pFile->Read(temp, pit_b, 1);

inx = 0;

for(int x = 0; x < img_w; ++inx)

{

for(int n = 7; n >= 0 && x < img_w; --n, ++x, ++pos)

{

BYTE c = (temp[inx] >> (1 * n)) & 0x01;

bmBuff[pos] = ExRGBA

(

colors[c].rgbBlue,

colors[c].rgbGreen,

colors[c].rgbRed,

(BYTE)~0

);

}

}

}

}

#pragma pop_macro("PreDecode")

public:

bool Encode(image_t Image)

{

return false;

}

image_t Decode()

{

IFileObject* file = GetFile();

if(!CheckFile(file)) return NULL;

CFileSeeker seeker(file);

// 获取图像信息

BITMAPFILEHEADER file_head = {0};

file->Read(&file_head, sizeof(file_head), 1);

BITMAPINFO file_info = {0};

file->Read(&file_info, sizeof(file_info), 1);

if (file_info.bmiHeader.biCompression != BI_RGB &&

file_info.bmiHeader.biCompression != BI_BITFIELDS)

return NULL;

// 根据图像信息申请一个图像缓冲区

COLORREF* bmbf = NULL;

image_t image =

GetImageBuff(file_info.bmiHeader.biWidth, file_info.bmiHeader.biHeight, (BYTE*&)bmbf);

if(!image) return NULL;

// 解析图像信息

switch (file_info.bmiHeader.biBitCount)

{

case 32:

Decode32(file, file_head, file_info, bmbf);

break;

case 24:

Decode24(file, file_head, file_info, bmbf);

break;

case 16:

Decode16(file, file_head, file_info, bmbf);

break;

case 8:

Decode8(file, file_head, file_info, bmbf);

break;

case 4:

Decode4(file, file_head, file_info, bmbf);

break;

case 1:

Decode1(file, file_head, file_info, bmbf);

break;

}

// 返回image_t

return image;

}

};

//

EXP_END

#endif/*__BmpCoder_h__*/