Visual C++中DDB与DIB位图编程全攻略(1)

1. 基本概念

  先来用通俗的语句讲解位图和调色板的概念。


  我们知道,自然界中的所有颜色都可以由红、绿、蓝(R,G,B)三基色组合而成。针对含有红、绿、蓝色成分的多少,可以对其分别分成0~255个等级,而红、绿、蓝的不同组合共有256×256×256种,因此约能表示1600万种颜色。对于人眼而言,这已经是"真彩色"了。

  对每个像素进行了(R,G,B)量化的图像就是位图,其在计算机中对应文件的扩展名一般为.bmp。既然用R,G,B的量化值就可以直接记录一张位图的所有像素,那我们需要调色板干什么呢?

  首先,我们可以计算完全利用(R,G,B)组合来存储一个800×600的位图所需要的空间为:

800×600×3 = 1440000(字节)= 1.37M(字节)

  惊人的大!因此,调色板横空出世了,它的功能在于缓解位图文件存储空间过大的问题。

  假设一个位图为16色,其像素总数为800×600。我们只需要用4个bit就可以存储这个位图的每个像素在16种颜色中所处的等级,然后调色板提供了这16种等级对应的(R,G,B)值,这样,存储这个16色位图只需要:

800×600×4/8 = 240000(字节)= 0.22 M(字节)

  额外的存储R,G,B表的开销(即调色板Palette,也称为颜色查找表LUT)仅仅为16×3=48字节。

  存储空间被大为减少!

  常见的位图有单色、16色、256色、16位及24位真彩色5种,对于前三者(即不大于256色)都可以调色板方式进行存储,而对16位及24位真彩色以调色板进行存储是不划算的,它们直接按照R,G,B分量进行存储。

  在此基础上我们来分析DDB位图(Device-dependent bitmap,与设备相关的位图)与DIB位图(Device-independent bitmap,与设备无关的位图)的概念以及二者的区别。

  DDB依赖于具体设备,它只能存在于内存中(视频内存或系统内存),其颜色模式必须与特定的输出设备相一致,使用系统调色板。一般只能载入色彩较简单的DDB位图,对于颜色较丰富的位图,需使用DIB才能长期保存。

  DIB不依赖于具体设备,可以用来永久性地保存图象。DIB一般是以*.BMP文件的形式保存在磁盘中的,有时也会保存在*.DIB文件中。 DIB位图的特点是将颜色信息储存在位图文件自身的颜色表中,应用程序要根据此颜色表为DIB创建逻辑调色板。因此,在输出一幅DIB位图之前,程序应该将其逻辑调色板选入到相关的设备上下文并实现到系统调色板中。

2. 例程简述

  本文后续的讲解都基于这样的一个例子工程,它是一个基于对话框的MFC应用程序,包括2个父菜单:

  (1) DDB位图

  DDB位图父菜单又包括两个子菜单:

  a. ID:IDM_LOADDDBPIC caption:加载

  单击事件:加载资源中的DDB位图并显示之

  b. ID:IDM_MARK_DDBPIC caption:标记

  单击事件:在DIB位图中透明地添加天极网logo

  (2) DIB位图

  DIB位图父菜单又包括两个子菜单:

  a. ID:IDM_OPENDIBPIC caption:打开

  单击事件:弹出文件对话框,打开.bmp位图文件,并显示

  b. ID:IDM_MARK_DIBPIC caption:标记

  单击事件:在DIB位图中透明地添加天极网logo

  工程中还包含下列位图资源:

  (1)IDB_LOADED_BITMAP:要加载的位图资源

  (2)IDB_YESKY_BITMAP:天极网logo

  后续篇章将集中在对4个子菜单单击事件消息处理函数的讲解,下面的代码是整个对话框类CBitMapExampleDlg的消息映射:

BEGIN_MESSAGE_MAP(CBitMapExampleDlg, CDialog)
//{{AFX_MSG_MAP(CBitMapExampleDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_COMMAND(IDM_LOADDDBPIC, OnLoadddbpic)
ON_COMMAND(IDM_MARK_DDBPIC, OnMarkDdbpic)
ON_COMMAND(IDM_OPENDIBPIC, OnOpendibpic)
ON_COMMAND(IDM_MARK_DIBPIC,OnMarkDibpic) //}}AFX_MSG_MAP
END_MESSAGE_MAP()

3. DDB位图编程

  先看DDB加载按钮的单击事件代码:

void CBitMapExampleDlg::OnLoadddbpic()
{
1: CBitmap bmpDraw;
2:  bmpDraw.LoadBitmap( IDB_LOADED_BITMAP );//装入要加载的DDB位图
3:  BITMAP bmpInfo;
4:  bmpDraw.GetBitmap( &bmpInfo ); //获取要加载DDB位图的尺寸
5:  CDC memDC;//定义一个兼容DC
6:  CClientDC dc( this );
7:  memDC.CreateCompatibleDC( &dc );//创建兼容DC
8:  CBitmap* pbmpOld = memDC.SelectObject( &bmpDraw );//保存原有DDB,并选入新DDB入DC

9:  dc.BitBlt( 0, 0, bmpInfo.bmWidth, bmpInfo.bmHeight, &memDC, 0, 0, SRCCOPY );

10: memDC.SelectObject( pbmpOld );//选入原DDB
}

  上述代码将产生如图1所示的效果,位图被安置在对话框(0,0)坐标开始的位置上。


图1 加载DDB位图资源

  我们来逐行解析上述代码是怎样产生图1的效果的。

  第1、2行定义了一个CBitmap对象,并调用其成员函数LoadBitmap加载工程中的位图资源IDB_LOADED_BITMAP。第3、4行定义了BITMAP结构体的实例并调用CBitmap的成员函数GetBitmap获得位图信息,BITMAP结构体定义在<wingdi.h>头文件中,其形式为:

/* Bitmap Header Definition */
typedef struct tagBITMAP
{
 LONG bmType; //必需为0
 LONG bmWidth; //位图的宽度(以像素为单位)
 LONG bmHeight; //位图的高度(以像素为单位)
 LONG bmWidthBytes; //每一扫描行所需的字节数,应是偶数
 WORD bmPlanes; //色平面数
 WORD bmBitsPixel; //色平面的颜色位数
 LPVOID bmBits; //指向存储像素阵列的数组
} BITMAP, *PBITMAP, NEAR *NPBITMAP, FAR *LPBITMAP;

  第5~8行的作用是:构建一个CDC对象,调用CDC::CreateCompatibleDC创建一个兼容的内存设备上下文,接着调用CDC::SelectObject将DDB选入内存设备上下文中。

  第9行调用函数CDC::BitBlt绘制位图,CDC::BitBlt的原型为:

CDC::BitBlt(int x, int y, int nWidth, int nHeight, CDC *pSrcDC, int xSrc, int ySrc, DWORD dwRop)

  CDC::BitBlt执行的操作为将源DC中位图复制到目的DC中。其中前四个参数为目的区域的坐标(x,y)及长度和宽度(Width, nHeight),第五个参数是源DC指针,接下来的参数是源DC中的起始坐标,最后一个参数为光栅操作的类型。

  第10行调用CDC::SelectObject把原来的DDB选入到内存设备上下文中并使新DDB脱离出来。

  与CDC::BitBlt对应的还有另一个函数CDC::StretchBlt,它具有缩放功能,其原型为:

BOOL CDC::StretchBlt(int x, int y, int nWidth, int nHeight, CDC *pSrcDC, int
xSrc, int ySrc, int nSrcWidth, int nSrcHeight, DWORD dwRop);

  该函数把位图从源矩形拷贝到目的矩形中,如果源和目的矩形尺寸不同,那么将缩放位图的功能以适应目的矩形的大小。函数的大部分参数与BitBlt的相同,但多了两个参数nSrcWidth和nSrcHeight用来指定源矩形的宽和高。

  如果我们将函数CBitMapExampleDlg::OnLoadddbpic() 中的第9行改为:

CRect clientRect;
GetClientRect(&clientRect); //获得对话框窗口的大小
dc.StretchBlt(0, 0, clientRect.right, clientRect.bottom, &memDC, 0, 0,
bmpInfo.bmWidth, bmpInfo.bmHeight, SRCCOPY);

  则单击加载按钮后的对话框如图2所示,位图被拉伸至整个对话框的范围。

Visual C++中DDB与DIB位图编程全攻略(1)_第1张图片
图2 拉伸位图

  CDC::BitBlt和dc.StretchBlt函数中的dwRop参数较为有用,它定义光栅操作的类型。请看"DDB位图"父菜单下"标记"子菜单单击事件的消息处理函数代码:

void CBitMapExampleDlg::OnMarkDdbpic()
{
 CBitmap bmpDraw;
 bmpDraw.LoadBitmap(IDB_YESKY_BITMAP); //装入天极网logo DDB位图资源
 BITMAP bmpInfo;
 bmpDraw.GetBitmap(&bmpInfo); //获取天极网logo位图的尺寸

 CDC memDC; //定义一个兼容DC
 CClientDC dc(this);
 memDC.CreateCompatibleDC(&dc); //创建DC

 CBitmap *pbmpOld = memDC.SelectObject(&bmpDraw);
 //保存原有DDB,并选入天极网logo位图入DC
 dc.BitBlt(0, 0, bmpInfo.bmWidth, bmpInfo.bmHeight, &memDC, 0, 0, SRCAND);
 memDC.SelectObject(pbmpOld); //选入原DDB
}

  单击该按钮后,将产生如图3的效果,天极网的logo被透明地添加到了位图中!

Visual C++中DDB与DIB位图编程全攻略(1)_第2张图片
图3 在DDB位图中加入天极网logo

  能产生这个效果的原因在于我们在代码行:

dc.BitBlt ( 0, 0, bmpInfo.bmWidth, bmpInfo.bmHeight, &memDC, 0, 0, SRCAND );

  中使用了参数SRCAND(不同于先前代码中SRCCOPY,它仅仅意味着复制源位图到目的位图),它的含义为源和目的间进行AND操作。我们不知道天极网的编辑同志是怎么为文章中的 图片加logo的,有可能他们就使用了具有自动AND功能的图像加logo批处理 软件。的确,我们可以利用例程中的原理写一个批处理软件,一次对一堆图片自动添加logo。

  参数dwRop除了可以为SRCAND和SRCCOPY外,还可以有如下取值:

  BLACKNESS:输出区域为黑色

  DSTINVERT:反转目的位图

  MERGECOPY:用与操作把图案(Pattern)与源位图融合起来

  MERGEPAINT:用或操作把反转的源位图与目的位图融合起来

  NOTSRCCOPY:把源位图反转然后拷贝到目的地

  NOTSRCERASE:用或操作融合源和目的位图,然后再反转

  PATCOPY:把图案拷贝到目的位图中

  PATINVERT:用异或操作把图案与目的位图相融合

  PATPAINT:用或操作融合图案和反转的源位图,然后用或操作把结果与目的位图融合

  SRCERASE:先反转目的位图,再用与操作将其与源位图融合

  SRCINVERT:用异或操作融合源位图和目的位图

  SRCPAINT:用或操作融合源位图和目的位图

  WHITENESS:输出区域为白色

  合理利用这些取值将帮助我们制作出特定要求的图像处理软件。

  从上述实例我们可以看出,在VC中使用CBitmap类,必须将位图放入工程的资源中,并使用类 CBitmap的成员函数LoadBitmap加载之,再通过CDC类的成员函数BitBlt进行DC拷贝等操作达到显示的目的。CBitmap有显示的不足:

  (1) 位图需要放入工程资源中,这将导致工程的可执行文件变大;

  (2) 因为位图需放入工程资源中,而资源中不能无穷无尽地包含位图,应用程序无法自适应地选取其它位图,能使用的位图十分有限的;

  (3) 类CBitmap只是DDB位图操作API的封装,不能独立于平台。

  DIB位图则可以解决上述问题,其特点是以.BMP位图文件格式存储独立于平台的图像数据,下面我们来详细分析。

你可能感兴趣的:(编程,C++,command,存储,mfc,图像处理)