依赖于设备的位图(DDB)
DDB(Device-dependent bitmap)依赖于具体设备,这主要体现在以下两个方面:
DDB的颜色模式必需与输出设备相一致。例如,如果当前的显示设备是256色模式,那么DDB必然也是256色的,即一个像素用一个字节表示。
在256色以下的位图中存储的像素值是系统调色板的索引,其颜色依赖于系统调色板
由于DDB高度依赖输出设备,所以DDB只能存在于内存中,它要么在视频内存中,要么在系统内存中。
DDB的创建
MFC的CBitmap类封装了DDB。该类提供了几个函数用来创建DDB:
BOOL LoadBitmap( LPCTSTR lpszResourceName );
BOOL LoadBitmap( UINT nIDResource );
该函数从资源中载入一幅位图,若载入成功则返回TRUE。资源位图实际上是一个DIB,该函数在载入时把它转换成了DDB。BOOL CreateBitmap( int nWidth, int nHeight, UINT nPlanes, UINT nBitcount, const void* lpBits );
该函数用来创建一幅空白的DDB。参数nWidth和nHeight以像素为单位说明了位图的宽度和高度。nPlanes是DDB的色平面数,nBitcount是每个色平面的颜色位数。一般来说,nPlanes为1,而nBitcount代表DDB中每个像素值所占的位数,但在创建16色DDB时,nPlanes为4,而nBitcount为1。参数lpBits指向存储像素阵列的数组,该数组应该逐行存储位图的每个像素值。注意,数组中每行像素的数目必需是偶数个字节,如果是奇数,则应该用0补足。若创建成功函数返回TRUE。BOOL CreateCompatibleBitmap( CDC* pDC, int nWidth, int nHeight );
该函数创建一个与指定设备上下文兼容的DDB。参数pDC指向一个设备上下文,nWidth和nHeight是DDB的尺寸。若创建成功函数返回TRUE
可以调用CBitmap的成员函数GetBitmap来查询DDB的各种属性(如尺寸):
int GetBitmap( BITMAP* pBitMap );
该函数用来获得与DDB有关的信息,参数pBitMap指向一个BITMAP结构。BITMAP结构的定义为:typedef struct tagBITMAP {
LONG bmType; //必需为0
LONG bmWidth; //位图的宽度(以像素为单位)
LONG bmHeight; //位图的高度(以像素为单位)
LONG bmWidthBytes; //每一扫描行所需的字节数,应是偶数
WORD bmPlanes; //色平面数
WORD bmBitsPixel; //色平面的颜色位数
LPVOID bmBits; //指向存储像素阵列的数组
} BITMAP;
DDB的用途
DDB的主要用途是保存位图。要保存的位图可以来自资源位图,也可以是一个绘图的结果。
前面说过,在256色以下的显示模式中,DDB中的像素值是系统调色板的索引。一般在系统调色板中除了保留的20种静态颜色外,其它表项都有可能被应用程序改变。如果DDB中有一些像素值是指向20种静态颜色以外的颜色,那么该位图的颜色将是不稳定的。因此,DDB不能用来长期存储色彩丰富的位图。如果位图使用的大部分颜色都是20种保留色,则该位图可以用CBitmap对象保存在内存中。例如,用CDC::LoadBitmap载入的资源位图一般都是颜色较简单的位图,对于那些颜色比较丰富的位图,只有使用下面将要介绍的DIB才能长期保存。
一个位图句柄不可同时选入到两个内存DC中。内存DC对于选入的位图具有排他性,如果你m_MenDC.SelectObject(m_hbmpBK);而m_hbmpBK已经被其他的内存DC选用了话,那刚才这句m_MenDC.SelectObject(m_hbmpBK);是毫无意义的。
在窗口中显示DDB的方法有些特别,其过程分以下几步:
构建一个CDC对象,然后调用CDC::CreateCompatibleDC创建一个兼容的内存设备上下文。
调用CDC::SelectObject将DDB选入内存设备上下文中。
调用CDC::BitBlt或CDC::StretchBlt将DDB从内存设备上下文中输出到窗口的设备上下文中。
调用CDC::SelectObject把原来的DDB选入到内存设备上下文中并使新DDB脱离出来。
有一段时间一直在想为什么位图就需要到兼容DC,就像画家绘画一样,设备环境好比是画家的画布,图形对象好比是画家的画笔。用画笔在画布上绘画,不同的画笔将画出不同的画来。选择合适的图形对象和绘图对象,才能按照要求完成绘图任务。
我们可以把‘屏幕’当做一副位图,也就是画布了,普通得拿起画笔就是在屏幕上画了,当然如果有已经画好的位图了,我们就没必要再拿起画笔来画了,如果要再画就不是原来的位图了,(windows位图实际上是映射到显示像素的位阵列,程序可以获得位图数据得副本,但其中得位排列取决于显示硬件,我们要保持位图得信息还是粘贴符合实际点),只要把它拿过来粘到我的画布上就OK了,通过BITBLT,如果只selectobject 就相当于只拿起来了图片而没有沾上(BITBLT),还是不能显示.当然BITBLT得作用就是书上是这么解释的:显示器的“位图”实际上是显示器表面得映像,打印机设备得“位图”是打印页面本身。因此,你不能将位图选入显示设备环境或打印机设备环境(也就是已经有画布了,要想显示除非粘贴位图,也就是复制这些位)。你必须使用CDC:CreateCompatibleDC函数为位图创建一个特殊得内存设备环境,然后,你必须使用CDC成员函数StretchBit或BITBLT来从内存设备环境中象“真正”得设备环境中复制这些位。这些“位复制”函数一般在查看类的OnDraw函数中调用。(真讨厌“必须”这个词,还得让人去揣摩,这就是《VC++技术内幕(第五版)》如此之差,翻译过来得书真是难看)。
我们不可以这样想:如果把原来得显示画布删了,也就是显示器“位图”删了,再选入自己得位图。这样就大错特错了,因为这个GDI对象is CGdiObject public data member that stores.由于它指向的对象是最初选到设备环境中,所以你不能删除这个对象,删除得后果是崩溃得一发不可收拾(我没试过,可以试试O(∩_∩)O)
下面这段代码在视图中显示了一个DDB:
void CMyView::OnDraw( CDC* pDC)
{
. . .
CDC MemDC;
CBitmap *oldBmp;
BITMAP bmpInfo;
int bmWidth,bmHeight;
MemDC.CreateCompatibleDC(pDC);
oldBmp=MemDC.SelectObject(&m_Bitmap); //m_Bitmap是一个CBitmap对象
m_Bitmap.GetBitmap(&bmpInfo); //获取位图的尺寸
bmWidth=bmpInfo.bmWidth;
bmHeight=bmpInfo.bmHeight;
pDC->BitBlt(0,0,bmWidth,bmHeight,&MemDC,0,0,SRCCOPY);
MemDC.SelectObject(oldBmp); //使位图m_Bitmap脱离设备上下文
. . .
}
函数CDC::BitBlt的声明为:
BOOL BitBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop );
该函数把源设备上下文中的位图复制到本身的设备上下文中,两个设备上下文可以是内存设备上下文,也可以是同一个设备上下文。参数x和y是目的矩形的逻辑坐标,参数nWidth和nHeight说明了目的矩形及源位图的宽和高。pSrcDC指向源设备上下文,xSrc和ySrc说明了源矩形相对于源位图左上角的偏移。参数dwRop指定了光栅操作(ROP)代码,一些常用的ROP代码如表11.2所示。
函数CDC::StretchBlt的声明为:
BOOL StretchBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, int nSrcWidth, int nSrcHeight, DWORD dwRop );
该函数把位图从源矩形拷贝到目的矩形中,如果源和目的矩形尺寸不同,那么将缩放位图的功能以适应目的矩形的大小。函数的大部分参数与BitBlt的相同,但多了两个参数nSrcWidth和nSrcHeight用来指定源矩形的宽和高。
DDB的一个重要用途是用作设备上下文的显示表面。每一个设备上下文都包含有一个DDB,该位图实际上是在显示设备的缓冲区中(如视频内存),我们可以把它看做设备上下文的显示表面,设备上下文用GDI函数绘图实际上就是修改它所包含的DDB(显示表面)的过程。
普通的设备上下文都是在屏幕上绘图的,而使用内存设备上下文则可以在系统内存中绘制图形。内存设备上下文是一种特殊的设备上下文,它将系统内存用作显示表面。程序可以使用内存设备上下文预先在系统内存中绘制复杂的图形,然后再快速地将其复制到实际的设备上下文的显示表面上,而绘制图形的结果仍保存在内存设备上下文的DDB中。
提示:有人可能会想到用BitBlt函数把绘图结果从显示设备拷贝到内存设备上下文中,这种方法可以工作,但有时会出错。当源矩形被别的窗口遮住时,BitBlt会把别的窗口中的像素拷贝下来。 内存设备上下文缺省的DDB是一个1×1的单色位图,如此小的显示表面显然是没有用的,因此程序一般要为内存设备对象选择一个合适大小的彩色DDB。
下面这段代码创建了一个内存设备上下文,并在其包含的DDB中画了一个灰色实心矩形,然后再把DDB输出到屏幕上。
void CMyView::OnDraw(CDC* pDC)
{
. . .
CDC MemDC;
CBitmap bm,*oldBmp;
MemDC.CreateCompatibleDC(pDC); //创建一个兼容的内存设备上下文
bm.CreateCompatibleBitmap(pDC,100,50); //创建一个兼容的DDB
oldBmp=MemDC.SelectObject(&bm);
MemDC.SelectStockObject(BLACK_PEN);
MemDC.SelectStockObject(GRAY_BRUSH);
MemDC.Rectangle(0,0,50,50); //在DDB中画一个矩形
pDC->BitBlt(0,0,100,50,&MemDC,0,0,SRCCOPY);
MemDC.SelectObject(oldBmp); //使位图bm对象脱离设备上下文
. . .
}
在上面的代码中,绘图的结果保存在位图bm中,一旦调用MemDC.SelectObject(oldBmp)使位图bm脱离设备上下文,该位图就可以被其它对象使用。
设备无关的位图(DIB)
DIB(Device-indepentent bitmap)的与设备无关性主要体现在以下两个方面:
DIB的颜色模式与设备无关。例如,一个256色的DIB即可以在真彩色显示模式下使用,也可以在16色模式下使用。
256色以下(包括256色)的DIB拥有自己的颜色表,像素的颜色独立于系统调色板。
由于DIB不依赖于具体设备,因此可以用来永久性地保存图象。DIB一般是以*.BMP文件的形式保存在磁盘中的,有时也会保存在*.DIB文件中。运行在不同输出设备下的应用程序可以通过DIB来交换图象。
DIB还可以用一种RLE算法来压缩图像数据,但一般来说DIB是不压缩的。
DIB的结构
与Borland C++下的框架类库OWL不同,MFC未提供现成的类来封装DIB。尽管Microsoft列出了一些理由,但没有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的颜色格式转换成与输出设备相同的颜色格式。例如,在真彩色的显示模式下要显示一个256色的DIB,则应该将其转换成24位的颜色格式。
将DIB像素的逻辑颜色索引转换成系统调色板索引。
编写DIB类
由于MFC未提供DIB类,用户在使用DIB时将面临繁重的Windows API编程任务。幸运的是,Visual C++提供了一个较高层次的API,简化了DIB的使用。这些API函数实际上是由MFC的DibLook例程提供的,它们位于DibLook目录下的dibapi.cpp、myfile.cpp和dibapi.h文件中,主要包括:
ReadDIBFile //把DIB文件读入内存
SaveDIB //把DIB保存到文件中
CreateDIBPalette //从DIB中创建一个逻辑调色板
PaintDIB //显示DIB
DIBWidth //返回DIB的宽度
DIBHeight //返回DIB的高度
如果读者对这些函数的内部细节感兴趣,那么可以研究一下dibapi.cpp和myfile.cpp文件,但要做好吃苦的准备。
即使利用上述API,编写使用DIB的程序仍然不是很轻松。为了满足读者的要求,笔者编写了一个名为CDib的较简单的DIB类,该类是基于上述API的,它的主要成员函数包括:
BOOL Load(LPCTSTR lpszFileName);
该函数从文件中载入DIB,参数lpszFileName说明了文件名。若成功载入则函数返回TRUE,否则返回FALSE。BOOL LoadFromResource(UINT nID);
该函数从资源中载入位图,参数nID是资源位图的ID。若成功载入则函数返回TRUE,否则返回FALSE。CPalette* GetPalette()
返回DIB的逻辑调色板。BOOL Draw(CDC *pDC, int x, int y, int cx=0, int cy=0);
该函数在指定的矩形区域内显示DIB,它具有缩放位图的功能。参数pDC指向用于绘图的设备上下文,参数x和y说明了目的矩形的左上角坐标,cx和cy说明了目的矩形的尺寸,cx和cy若有一个为0则该函数按DIB的实际大小绘制位图,cx和cy的缺省值是0。若成功则函数返回TRUE,否则返回FALSE。int Width(); //以像素为单位返回DIB的宽度
int Height(); //以像素为单位返回DIB的高度
CDib类的源代码在清单11.3和11.4列出,CDib类的定义位于CDib.h中,CDib类的成员函数代码位于CDib.cpp中。对于CDib类的代码这里就不作具体解释了,读者只要会用就行。
在程序中使用CDib对象的代码很简单。当用户在ShowDib程序中选择File->Open命令并从打开文件对话框中选择了一个BMP文件后,CShowDibDoc::OnOpenDocument函数被调用,该函数调用CDib::Load载入位图。在CShowDibView::OnDraw中,调用CDib::Draw输出位图。在CShowDibView::OnInitialUpdate中,根据DIB的尺寸来确定视图的滚动范围。
需要重点研究的是ShowDib如何处理调色板问题的。ShowDib是一个多文档应用程序,可以同时显示多幅位图。由于每个位图一般都有不同的调色板,这样就产生了共享系统调色板的问题。程序必须采取措施来保证只有一个视图的逻辑调色板作为前景调色板使用。
当主框架窗口收到WM_QUERYNEWPALETTE消息时,主框架窗口向具有输入焦点的视图发送wParam参数为0的WM_DOREALIZE消息,该视图的消息处理函数CShowDibView::OnDoRealize为视图实现前景调色板并在必要时重绘视图,这样活动视图中的位图就具有最佳颜色显示。
如果活动视图在实现其前景调色板时改变了系统调色板,或是别的应用程序的前景调色板改变了系统调色板,那么Windows会向所有顶层窗口和重叠窗口发送WM_PALETTECHANGED消息,DibLook的主框架窗口也会收到该消息。主框架窗口对该消息的处理是向所有的视图发送wParam参数为1的WM_DOREALIZE消息,通知它们实现各自的背景调色板并在必要时重绘,这样所有的位图都能显示令人满意的颜色。
当某一视图被激活时,需要调用OnDoRealize来实现其前景调色板,这一任务由CShowDibView:: OnActivateView函数来完成。
该函数对指定的源设备环境区域中的像素进行位块(bit_block)转换,以传送到目标设备环境。windows就给我们提供了这些函数来操作了,所以还是按照它得思路走吧,因为里面也涉及到一些硬件得事情。比如标准得VGA卡具有四个连续得彩色平面,每个平面取一个对应位,他们组合起来就代表一个像素,当创建位图得时候,设置四个颜色值。位图颜色被限制在标准得16色,windows不在位图中使用抖动颜色。