学习VC++编制的BMP图像文件的处理程序.。
一、 主要内容:
1. BMP图像文件的结构分析;
2. BMP图像文件的读写;
3. BMP图像文件的置固定值处理、反色处理、平滑处理;
二、 设计实现:
1. BMP图像文件的结构分析;
A. BMP文件结构
BMP 文件是 Windows 操作系统所推荐和支持的图像文件格式,是一种将内存或显示器的图像数据不经过压缩而直接按位存盘的文件格式,所以称为位图(bitmap)文件,因其文件扩展名为BMP,故称为 BMP 文件格式,简称 BMP文件。
BMP 图像文件被分成 4 个部分:
1) 位图文件头(Bitmap File Header)、
2) 位图信息头(Bitmap Info Header)、
3) 颜色表(Color Map)
4) 位图数据(即图像数据,Data Bits或 Data Body)。
图 1 BMP 文件结构示意图
第 1 部分为位图文件头 BITMAPFILEHEADER,是一个结构体类型,该结构的长度是固定的,为 14 个字节。其定义如下:
typedef structtagBITMAPFILEHEADER
{
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER, FAR *LPBITMAPFILEHEADER,*PBITMAPFILEHEADER;
BITMAPFILEHEADER 结构的各个域详细说明如下:
— bfType:位图文件类型,必须是 0x424D,即字符串“BM”,也就是说,所有的“*.bmp”文件的头
两个字节都是“BM”。
— bfSize:位图文件大小,包括这 14 个字节。
— bfReserved1, bfReserved2:Windows 保留字,暂不用。
— bfOffBits:从文件头到实际的位图数据的偏移字节数,图 1 中前 3 个部分的长度之和。
第 2 部分为位图信息头 BITMAPINFOHEADER,也是一个结构体类型的数据结构,该结构的长度也是固定的,为 40 个字节(WORD 为无符号 16 位整数, DWORD 为无符号 32 位整数,LONG 为 32 位整数)。
其定义如下:
typedef structtagBITMAPINFOHEADER
{
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;
BITMAPINFOHEADER 结构的各个域的详细说明如下:
— biSize:本结构的长度,为 40 个字节。
— biWidth:位图的宽度,以像素为单位。
— biHeight:位图的高度,以像素为单位。
— biPlanes:目标设备的级别,必须是 1。
— biBitCount:每个像素所占的位数(bit),其值必须为 1(黑白图像)、4(16 色图)、8(256色)、24(真彩色图),新的BMP 格式支持 32 位色。
— biCompresssion:位图压缩类型,有效的值为 BI_RGB(未经压缩)、BI_RLE8、BI_RLE4、BI_BITFILEDS(均为 Windows 定义常量)。这里只讨论未经压缩的情况,即 biCompression=BI_RGB。
— biSizeImage:实际的位图数据占用的字节数,该值的大小在第 4 部分位图数据中有具体解释。
— biXPelsPerMeter:指定目标设备的水平分辨率,单位是像素/米。
— biYPelsPerMeter:指定目标设备的垂直分辨率,单位是像素/米。
— biClrUsed:位图实际用到的颜色数,如果该值为零,则用到的颜色数为 2 的biBitCount 次幂。
— biClrImportant:位图显示过程中重要的颜色数,如果该值为零,则认为所有的颜色都是重要的。
第 3 部分为颜色表。颜色表实际上是一个 RGBQUAD 结构的数组,数组的长度由biClrUsed
指定(如果该值为零,则由 biBitCount 指定,即 2 的 biBitCount 次幂个元素)。RGBQUAD结构是一个结构体类型,占 4 个字节,其定义如下:
typedef struct tagRGBQUAD
{
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
}RGBQUAD;
RGBQUAD 结构的各个域的详细说明如下:
l rgbBlue:该颜色的蓝色分量;
l rgbGreen:该颜色的绿色分量;
l rgbRed:该颜色的红色分量;
l rgbReserved:保留字节,暂不用。
有些位图需要颜色表;有些位图(如真彩色图)则不需要颜色表,颜色表的长度由 BITMAPINFOHEADER 结构中 biBitCount 分量决定。对于biBitCount 值为 1 的二值图像,每像素占1bit,图像中只有两种(如黑白)颜色,颜色表也就有 2^1=2 个表项,整个颜色表的大小为2*4=8个字节;对于 biBitCount 值为 8 的灰度图像,每像素占 8bit,图像中有 2^8=256 种颜色,颜色表也就有256 个表项,且每个表项的 R、G、B 分量相等,整个颜色表的大小为 256*4=1024 个字节;而对于biBitCount=24 的真彩色图像,由于每像素 3 个字节中分别代表了 R、G、B 三分量的值,此时不需要颜色表,因此真彩色图的 BITMAPINFOHEADER 结构后面直接就是位图数据。
第4 部分是位图数据,即图像数据,其紧跟在位图文件头、位图信息头和颜色表(如果有颜色表的话)之后,记录了图像的每一个像素值。对于有颜色表的位图,位图数据就是该像素颜色在调色板中的索引值;对于真彩色图,位图数据就是实际的 R、G、B 值(三个分量的存储顺序是 B、G、R)。下面分别就 2 色、16 色、256 色和真彩色位图的位图数据进行说明:
l 对于 2 色位图,用 1 位就可以表示该像素的颜色,所以 1 个字节能存储 8 个像素的颜色值。
l 对于 16 色位图,用 4 位可以表示一个像素的颜色。所以一个字节可以存储 2 个像素的颜色值。
l 对于 256 色位图,1 个字节刚好存储 1 个像素的颜色值。
l 对于真彩色位图,3 个字节才能表示 1 个像素的颜色值。
需要注意两点:
第一, Windows 规定一个扫描行所占的字节数必须是 4 的倍数,不足 4 的倍数则要对其进行扩充。假设图像的宽为 biWidth 个像素、每像素 biBitCount 个比特,其一个扫描行所占的真实字节数的计算公式如下:
DataSizePerLine = (biWidth * biBitCount /8+ 3) / 4*4
那么,不压缩情况下位图数据的大小(BITMAPINFOHEADER 结构中的 biSizeImage 成员)计算如下:
biSizeImage = DataSizePerLine * biHeight
第二,一般来说, BMP 文件的数据是从图像的左下角开始逐行扫描图像的,即从下到上、从左到右,将图像的像素值一一记录下来,因此图像坐标零点在图像左下角。
B. GDI 对象及 GDI 位图
GDI 是图形设备接口(Graphics Device Interface)的缩写,Windows 的 GDI 对象类型是通过 Microsoft 基础类库(MFC)中的类来表示的,而 CGdiObject 正是所有 GDI 对象类的抽象基类,即 Windows 的 GDI 对象是通过 CGdiObject 派生类的 C++对象来表示的。
下面我们给出了GDI 派生类的列表。
— CBitmap——位图是一种位矩阵,每一个显示像素都对应于其中的一个或多个位。
— CBrush——刷子定义了一种位图形式的像素,利用它可以对区域内部填充颜色。
— CFont——字体是一种具有某种风格和尺寸的所有字符的完整集合,它常被当做资源存于磁盘中,其中有一些还依赖于某种设备。
— CPallete——调色板是一种颜色映射接口,它允许应用程序在不干扰其他应用程序的前提下,可以充分利用输出设备的颜色描绘能力。
— CPen——笔是一种用来画线及绘制有形边框的工具,可以指定它的颜色及厚度。
— CRgn——区域是由多边形、椭圆或者二者组合形成的一种范围,可利用它进行填充、裁剪以及鼠标点中测试。
CBitmap 类封装了 Windows GDI 位图,同时提供了一些操作位图的成员函数。像笔和字体一样,CBitmap 对象是 GDI 对象的一种,在使用 CBitmap 对象时必须创建一个CBitmap 对象,然后把它选进设备环境中,再调用该类中的成员对位图进行操作,当我们对它使用完后,还必须将它从设备环境中选出来并删除掉。
C. 设备无关位图(DIB)
DIB 格式(Device-Independent Bitmanp)是与设备无关的 BMP 文件格式调入内存中的 DIB 位图是脱离文件而存在的,其结构可以分为三部分。内存中的 DIB 实际上是 BMP 文件结构去掉位图文件头结构后剩下的三部分,而这三部分结构包含了位图显示和处理所需要的所有信息。
1.调色板
2.DIB 访问函数
— SetDIBitsToDevice——该函数直接在显示器或打印机上显示 DIB。显示时不进行缩放,位图的每一位对应一个显示像素或一个打印点。不能进行缩放限制了它的使用。该函数不能像 BitBlt()函数那样使用,因为BitBlt()使用的是逻辑坐标。
— StretchDIBits——该函数按照与 StretchBlt()函数类似的方式将DIB 直接显示在显示器或打印机上。
— GetDIBits——该函数利用申请到的内存,由 GDI 位图来构造 DIB。我们可以对 DIB 的格式进行控制,因为我们可以指定每个像素的颜色位数,并且可以指定是否对它进行压缩。如果使用了压缩格式,就必须对 GetDIBits 进行两次调用,一次用于计算所需要的内存,另一次用来产生 DIB 数据。
— CreateDIBitmap——该函数从 DIB 出发来创建 GDI 位图。与所有这些 DIB 函数一样,我们必须提供一个设备环境指针作为参数。这里我们需要一个显示器设备环境,不需要内存设备环境。
— CreateDIBSection——该函数是一个新的 Win32 函数,它创建一个特殊的DIB,称为 DIB 项(DIBSection),然后返回一个 GDI 位图句柄。该函数为我们提供了DIB 和 GDI 位图最好的特性。
我们可以直接访问 DIB 的内存,而且利用位图句柄和内存设备环境,还可以在 DIB 中调用 GDI 函数画图。
2. BMP图像文件的读写;
在基于 MFC 的文档-视图结构应用程序中,有关文件的操作(如读、写)一般在文档类中完成,而有关显示部分一般在视图类中完成。
1) 按照 MFC 工程向导,创建一个单文档应用程序。
2) 在文档类头文件 ex002Doc.h 中加入公有成员变量 m_pDib,用来存放打开 BMP文件的 DIB 指针。
public:
unsigned char *m_pDib;//存放打开文件的 DIB
并在构造函数和析构函数中对m_pDib 进行初始化和资源释放。
3) DIB 位图的读入。这需要重载文档类 OnOpenDocument()函数。
并加入代码如下:
BOOLCChap1_4Doc::OnOpenDocument(LPCTSTR lpszPathName)
{
//释放缓冲区
if(m_pDib!=NULL){
delete []m_pDib;
m_pDib=NULL;
}
//打开指定文件
CFile file;
if (!file.Open(lpszPathName,CFile::modeRead | CFile::shareDenyWrite))
return FALSE;
//跳过位图文件头结构
file.Seek(sizeof(BITMAPFILEHEADER),0);
//申请 DIB 所需要的内存空间,将除 BITMAPFILEHEADER 结构以外的
//位图数据读入内存,存放在 m_pDib 所指向的缓冲区中
m_pDib=newBYTE[file.GetLength() -sizeof(BITMAPFILEHEADER)];
file.Read(m_pDib,file.GetLength() -sizeof(BITMAPFILEHEADER));
return TRUE;
}
当运行应用程序并打开一个 BMP 文件的时候,系统执行OnOpenDocument()函数,一次性分配 DIB 结构所需要的所有内存缓冲区,由 m_pDib 指向,并将打开的图像数据(BITMAPINFOHEADER、颜色表和位图数据)读入该缓冲区中。其缓冲区数据内容的分布如图:
图2 m_pDib 所指向的内存缓冲区数据分布示意图
从图中可以看出,m_pDib 所指向的缓冲区的位置也就是位图 BITMAPINFOHEADER 结构的起始位置;而颜色表则是在 m_pDib+sizeof(BITMAPINFOHEADER)处;最后,位图数据的起始位置为 m_pDib+sizeof(BITMAPINFOHEADER)+颜色表大小(颜色表大小以字节为单位,可以由 BITMAPINFOHEADER 中的 biBitCount 成员求出)。因此,程序不需要再单独为 BITMAPINFOHEADER、颜色表或位图数据分配缓冲区了,也就是说,给了我们一个 DIB 结构,我们就可以根据需要对DIB 做任何的编程操作。
4) DIB 位图的显示。图像显示的操作由 CChap1_4View 类的 OnDraw()函数实现。
获取文档类句柄;
根据 DIB 的颜色表创建自己的调色板并选进设备环境中。
显示 DIB 到显示器。使用StretchDIBits函数。
5) DIB 的存储。重载文档类 OnSaveDocument()函数。
6) 双击 ResourceView 窗口的 String Table,
修改IDR_MAINFRAME的标题为:
ex002\n\nex002\n(*.bmp)\n.bmp\nex002.Document\nex002.Document,它保证了打开文件对话框的文件类型为“.bmp”。
3. BMP图像文件的置固定值处理、反色处理、平滑处理;
1) 置固定值处理
对DIB 位图数据的访问,分为灰度图像或彩色图像分别处理。
置固定值就是对某一点进行处理,赋给一个固定的颜色值。如黑色即赋给0值。
for(i=0;i
for(j=0;j
for(k=0;k<3;k++)//彩色图像,每像素三个分量都置 0
*(pImgData+i*lineByte+j*3+k)=0;
}
2) 反色处理
用255 – 该点的颜色值,如是彩色,三个分量都减。
3) 平滑处理
采用的某点的九宫格平均值赋给该点的算法。