一个Windows的位图实际上是一些和显示像素相对应的位阵列,它有两种类型:一种称之为GDI(Graphic Device Interface)位图,另一种是DIB位图(Device-Independent Bitmap)。GDI位图包含了一种和Windows的GDI模块有关的Windows数据结构,该数据结构是与设备有关的,故此位图又称为DDB位图(Device-Dependent Bitmap)。当用户的程序取得位图数据信息时,其位图显示方式视显示卡而定。由于GDI位图的这种设备依赖性,当位图通过网络传送到另一台PC,很可能就会出现问题。DIB比GDI位图有很多编程优势,例如它自带颜色信息,从而使调色板管理更加容易。且任何运行Windows的机器都可以处理DIB,并通常以后缀为.BMP的文件形式被保存在磁盘中或作为资源存在于程序的EXE或DLL文件中。
CBitmap类继承自CGdiObject,是封装了图形设备接口(GDI)的位图,提供成员函数来操纵位图。要使用一个CBitmap对象,构造该对象,用初始化成员函数之一把一个位图句柄连接到该对象,然后调用该对象的成员函数。CBitmap类主要用于处理DDB位图,封装了与DDB位图操作函数相关的数据结构和操作函数。结构体BITMAP定义了DDB位图的类型、宽度、高度、颜色和像素值,其定义如下:
typedef struct _tagBITMAP { LONG bmType ; // set to 0 LONG bmWidth ; // width in pixels LONG bmHeight ; // height in pixels LONG bmWidthBytes ; // width of row in bytes WORD bmPlanes ; // number of color planes WORD bmBitsPixel ; // number of bits per pixel LPVOID bmBits ; // pointer to pixel bits } BITMAP, * PBITMAP ;
而CBitmap的LoadBitmap、CreateCompatibleBitmap、SetBitmapBits、GetBitmap等成员函数则定义了对DDB位图的装载、创建、设定位值和属性查询等操作。创建或装入内存的位图必须用CDC::SelectObject函数来将其选入设备上下文中,然后用CDC的BitBlt或StretchBlt函数显示出来,这两个函数的原型如下:
BOOL BitBlt(int x, int y, int nWith, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop);该函数把源设备上下文中的位图复制到本身的设备上下文中,两个设备下下文可以是内存设备下下文,也可以是同一个设备上下文。
在VC++.NET中,GDI+的Image类封装了对BMP、GIF、JPEG、PNG、TIFF、WMF(Windows元文件)和EMF(增强WMF)图像文件的调入、显示、格式转换以及简单处理(如缩放、旋转、拉伸等)的功能。而Bitmap类(注意不是结构BITMAP)是从Image类继承的一个图像(另一个从Image继承的类是Metafile类),它封装了Windows位图操作的常用功能。例如,Bitmap::SetPixel和Bitmap::GetPixel分别用来对位图进行读写像素操作,从而可以为图像的柔化和锐化处理提供一种可能。这些功能和MFC的新类CImage功能基本一样,如果仅用于图像的读取与显示,用Bitmap类或Image类是不错的选择,如果是做图像处理,则CImage可能更符合MFC程序员的编程习惯。
CImage类是VC++.NET中MFC和ATL共享的新类,它能从外部磁盘中调入一个JPEG、GIF、BMP和PNG格式的图像文件加以显示,而且这些文件格式可以相互转换。CImage既能处理DIB位图也能处理非DIB位图,但你可以仅用DIB位图来Create或CImage::Load。你也可以使用Attach把一个非DIB位图连接给CImage对象,但你不能使用下列方法,这些方法仅支持DIB位图:GetBits、GetColorTable、GetMaxColorTable、Entries、GetPitch、GetPixelAddress、IsIndexed、SetColorTable。要确定一个连接的位图是否是一个DIB位图,调用IsDibSection。
由于CImage在不同的Windows操作系统中其某些性能是不一样的,因此在使用时要特别注意。例如,CImage::PlgBlt和CImage::MaskBlt只能在 Windows NT 4.0 或更高版本中使用,但不能运行在Windows 95/98 应用程序中。CImage::AlphaBlend和CImage::TransparentBlt也只能在 Windows 2000/98或其更高版本中使用。即使在Windows 2000运行程序还必须将stdafx.h文件中的WINVER和_WIN32_WINNT的预定义修改成0x0500才能正常使用。 CImage可以在MFC或ATL中使用。当使用CImage创建一个项目时,必须包含atlimage.h文件。
CImage封装了DIB(设备无关位图)的功能,因而可以让我们能够处理每个位图像素。CImage提供了HBITMAP操作符,因此HBITMAP为参数的地方,都可以用CImage来替代。
由于DIB图不依赖于具体设备,因此可以用来永久性地保存图象。DIB一般是以*.BMP文件的形式保存在磁盘中的,有时也会保存在*.DIB文件中。运行在不同输出设备下的应用程序可以通过DIB来交换图象。因而在数字图象处理中会经常用到DIB位图。DIB还可以用一种RLE算法来压缩图像数据,但一般来说DIB是不压缩的。在VC++.net之前MFC未提供现成的类来封装DIB,这给MFC用户带来很多不便。因为用户要想使用DIB,首先应该了解DIB的结构。在内存中,一个完整的DIB由两部分组成:一个BITMAPINFO结构和一个存储像素阵列的数组。BITMAPINFO描述了位图的大小,颜色模式和调色板等各种属性,其定义为
typedef struct tagBITMAPINFO { BITMAPINFOHEADER bmiHeader; RGBQUAD bmiColors[1]; //颜色表 } BITMAPINFO;
RGBQUAD结构用来描述颜色,其定义为
typedef struct tagRGBQUAD { BYTE rgbBlue; //蓝色的强度 BYTE rgbGreen; //绿色的强度 BYTE rgbRed; //红色的强度 BYTE rgbReserved; //保留字节,为0 } RGBQUAD;注意,RGBQUAD结构中的颜色顺序是BGR,而不是平常的RGB。
BITMAPINFOHEADER结构包含了DIB的各种信息,其定义为
typedef struct tagBITMAPINFOHEADER{ DWORD biSize; //该结构的大小 LONG biWidth; //位图的宽度(以像素为单位) LONG biHeight; //位图的高度(以像素为单位) WORD biPlanes; //必须为1 WORD biBitCount //每个像素的位数(1、4、8、16、24或32) DWORD biCompression; //压缩方式,一般为0或BI_RGB (未压缩) DWORD biSizeImage; //以字节为单位的图象大小(仅用于压缩位图) LONG biXPelsPerMeter; //以目标设备每米的像素数来说明位图的水平分辨率 LONG biYPelsPerMeter; //以目标设备每米的像素数来说明位图的垂直分辨率 DWORD biClrUsed; /*颜色表的颜色数,若为0则位图使用由biBitCount指定的最大颜色数*/ DWORD biClrImportant; //重要颜色的数目,若该值为0则所有颜色都重要 } BITMAPINFOHEADER;
与DDB不同,DIB的字节数组是从图象的最下面一行开始的逐行向上存储的,也即等于把图象倒过来然后在逐行扫描。另外,字节数组中每个扫描行的字节数必需是4的倍数,如果不足要用0补齐。
DIB可以存储在*.BMP或*.DIB文件中。DIB文件是以BITMAPFILEHEADER结构开头的,该结构的定义为
typedef struct tagBITMAPFILEHEADER { WORD bfType; //文件类型,必须为“BM” DWORD bfSize; //文件的大小 WORD bfReserved1; //为0 WORD bfReserved2; //为0 DWORD bfOffBits; //存储的像素阵列相对于文件头的偏移量 } BITMAPFILEHEADER;紧随该结构的是一个BITMAPINFOHEADER结构,然后是RGBQUAD结构组成的颜色表(如果有的话),文件最后存储的是DIB的像素阵列。
DIB的颜色信息储存在自己的颜色表中,程序一般要根据颜色表为DIB创建逻辑调色板。在输出一幅DIB之前,程序应该将其逻辑调色板选入到相关的设备上下文中并实现到系统调色板中,然后再调用相关的GDI函数(如::SetDIBitsToDevice或::StretchDIBits)输出DIB。在输出过程中,GDI函数会把DIB转换成DDB,这项工作主要包括以下两步:将DIB的颜色格式转换成与输出设备相同的颜色格式;将DIB像素的逻辑颜色索引转换成系统调色板索引。
看到这么多结构,你是不是已经眼花缭乱了,然而更不幸的还在后面。以上说的DIB是Windows中的DIB,还有一种DIB是OS/2 DIB,它和Windows DIB的主要区别是位图信息结构(信息头和颜色表结构)。它们的图像数据的存储方式是完全一样的。在OS/2 DIB中,与WIndows DIB的BITMAPFILEHEADER、BITMAPINFOHEADER、RGBQUAD相对应的结构分别是BITMAPCOREHEADER、BITMAPCOREINFO、和RGBTRIPLE。它们的定义分别如下:
typedef struct tagBITMAPCOREHEADER // bmch { DWORD bcSize ; // size of the structure = 12 WORD bcWidth ; // width of image in pixels WORD bcHeight ; // height of image in pixels WORD bcPlanes ; // = 1 WORD bcBitCount ; // bits per pixel (1, 4, 8, or 24) } BITMAPCOREHEADER, * PBITMAPCOREHEADER ; typedef struct tagBITMAPCOREINFO // bmci { BITMAPCOREHEADER bmciHeader ; // core-header structure RGBTRIPLE bmciColors[1] ; // color table array } BITMAPCOREINFO, * PBITMAPCOREINFO ; typedef struct tagRGBTRIPLE // rgbt { BYTE rgbtBlue ; // blue level BYTE rgbtGreen ; // green level BYTE rgbtRed ; // red level } RGBTRIPLE;因此再操作之前你应该先 根据BITMAPINFOHEADER的大小来判断是哪一种DIB,然后再进行操作。
由于MFC未提供一个封装好的易用的DIB类,用户在使用DIB时将面临繁重的Windows API编程任务。不信你看看Microsoft提供的MFC的DibLook例程提就知道了。所以传统的图像处理方法一般都会把这些Win32 SDK中的操作DIB位图的APIs做一个封装,作为一个通用的类来使用,以减少后续算法编写中的编程负担。VC++.NET提供了多种常用图像文件格式(如BMP、TIF、GIF、JPEG与PNG等)的输入/输出模块,ATL中的CImage类极大地简化了图像数据的操作,因此VC++.NET中的图像处理应以CImage为基础。
现在市面上有一些冠名教用VC++.NET做图像处理的书,但是编程方法还是老的一套,没有跳出设备相关位图(DDB)与设备无关位图(DIB)概念的条框,编程方法比较复杂、繁琐、低效。用VC++.NET环境做图像处理,应该以CImage类为基础,完全跳出DDB、DIB概念的条框,才能使得处理方法返朴归真,大为简化。
关于Image Engineering & Computer Vision的更多讨论与交流,敬请关注本博和新浪微博songzi_tea.