现在的位置: 首页 > 综合 > 正文

[转贴] Windows编程和面向对象技术 chap11

2013年05月05日 ⁄ 综合 ⁄ 共 22320字 ⁄ 字号 评论关闭

第十一讲 多媒体编程

 

  随着多媒体技术的迅猛发展和PC性能的大幅度提高,在PC机上运行的应用程序越来越多地采用了多媒体技术.如果你编写的应用程序能够发出美妙的声音,播放有趣的动画,无疑将会给人留下深刻的映象.

  Windows 95提供了对多媒体编程的良好支持,本章将帮助读者迅速掌握一些实用的多媒体编程技术,主要的内容包括:

调色板

位图

依赖于设备的位图(DDB)

与设备无关的位图(DIB)

动画控件

媒体控制接口(MCI).

小结 

 

11.1调色板

 

11.1.1 调色板的原理

  PC机上显示的图象是由一个个像素组成的,每个像素都有自己的颜色属性。在PC的显示系统中,像素的颜色是基于RGB模型的,每一个像素的颜色由红(B)、绿(G)、蓝(B)三原色组合而成。每种原色用8位表示,这样一个的颜色就是24位的。以此推算,PC的SVGA适配器可以同时显示224约一千六百多万种颜色。24位的颜色通常被称作真彩色,用真彩色显示的图象可达到十分逼真的效果。

  但是,真彩色的显示需要大量的视频内存,一幅640×480的真彩色图象需要约1MB的视频内存。由于数据量大增,显示真彩色会使系统的整体性能迅速下降。为了解决这个问题,计算机使用调色板来限制颜色的数目。调色板实际上是一个有256个表项的RGB颜色表,颜色表的每项是一个24位的RGB颜色值。使用调色板时,在视频内存中存储的不是的24位颜色值,而是调色板的4位或8位的索引。这样一来,显示器可同时显示的颜色被限制在256色以内,对系统资源的耗费大大降低了。

  显示器可以被设置成16、256、64K、真彩色等显示模式,前两种模式需要调色板。在16或256色模式下,程序必须将想要显示的颜色正确地设置到调色板中,这样才能显示出预期的颜色。图11.1显示了调色板的工作原理。使用调色板的一个好处是不必改变视频内存中的值,只需改变调色板的颜色项就可快速地改变一幅图象的颜色或灰度。

  在DOS中,调色板的使用不会有什么问题。由于DOS是一个单任务操作系统,一次只能运行一个程序,因此程序可以独占调色板。在Windows环境下,情况就不那么简单了。Windows是一个多任务操作系统,可以同时运行多个程序。如果有几个程序都要设置调色板,就有可能产生冲突。为了避免这种冲突,Windows使用逻辑调色板来作为使用颜色的应用程序和系统调色板(物理调色板)之间的缓冲。

T11_1.tif (194188 bytes)

图11.1 调色板工作原理

 

  在Windows中,应用程序是通过一个或多个逻辑调色板来使用系统调色板(物理调色板)。在256色系统调色板中,Windows保留了20种颜色作为静态颜色,这些颜色用作显示Windows界面,应用程序一般不能改变。缺省的系统调色板只包含这20种静态颜色,调色板的其它项为空。应用程序要想使用新的颜色,必须将包含有所需颜色的逻辑调色板实现到系统调色板中。在实现过程中,Windows首先将逻辑调色板中的项与系统调色板中的项作完全匹配,对于逻辑调色板中不能完全匹配的项,Windows将其加入到系统调色板的空白项中,系统调色板总共有236个空白项可供使用,若系统调色板已满,则Windows将逻辑调色板的剩余项匹配到系统调色板中尽可能接近的颜色上。

  每个设备上下文都拥有一个逻辑调色板,缺省的逻辑调色板只有20种保留颜色,如果要使用新的颜色,则应该创建一个新的逻辑调色板并将其选入到设备上下文中。但光这样还不能使用新颜色,程序只有把设备上下文中的逻辑调色板实现到系统调色板中,新的颜色才能实现。在逻辑调色板被实现到系统调色板时,Windows会建立一个调色板映射表。当设备上下文用逻辑调色板中的颜色绘图时,GDI绘图函数会查询调色板映射表以把像素值从逻辑调色板的索引转换成系统调色板的索引,这样当像素被输出到视频内存中时就具有了正确的颜色值。图11.2说明了这种映射关系,从图中读者可以体会到逻辑调色板的缓冲作用。在该图中,GDI绘图函数使用逻辑调色板的索引1中的颜色来绘图,通过查询调色板映射表,得知系统调色板中的第23号索引与其完全匹配,这样实际输出到视频内存中的像素值是23。注意图中还演示了颜色的不完全匹配,即逻辑调色板中的索引15和系统调色板中的索引46。

  每个要使用额外颜色的窗口都会实现自己的逻辑调色板,逻辑调色板中的每种颜色在系统调色板中都有相同或相近的匹配。调色板的实现优先权越高,匹配的精度也就越高。Windows规定,活动窗口的逻辑调色板(如果有的话)具有最高的实现优先权。这是因为活动窗口是当前与用户交互的窗口,应该保证其有最佳的颜色显示。非活动窗口的优先权是按Z顺序自上到下确定的(Z顺序就是重叠窗口的重叠顺序)。活动窗口有权将其逻辑调色板作为前景调色板实现,非活动窗口则只能实现背景调色板。

提示:术语活动窗口(Active window)或前台窗口(Foreground window)是指当前与用户交互的窗口,活动窗口的顶端的标题条呈高亮显示,而非活动窗口的标题条则是灰色的。活动窗口肯定是一个顶层窗口(Top-level window),顶层窗口是指没有父窗口或父窗口是桌面窗口的窗口,这种窗口一般都有标题和边框,主要包括框架窗口和对话框。术语重叠窗口是指作为应用程序主窗口的窗口,我们可以把对话框看成是一种特殊的重叠式窗口。

 

T11_2.tif (192888 bytes)

图11.2 调色板的映射关系


 

11.1.2 调色板的创建和实现

MFC的CPalette类对逻辑调色板进行了封装。该类的成员函数CreatePalette负责创建逻辑调色板,该函数的声明为:

BOOL CreatePalette( LPLOGPALETTE lpLogPalette ); //成功则返回TRUE。

参数lpLogPalette是一个指向LPLOGPALETTE结构的指针,LPLOGPALETTE结构描述了逻辑调色板的内容,该结构的定义为:

typedef struct tagLOGPALETTE {

WORD palVersion; //Windows版本号,一般是0x300

WORD palNumEntries; //调色板中颜色表项的数目

PALETTEENTRY palPalEntry[1]; //每个表项的颜色和使用方法

} LOGPALETTE;

  结构中最重要的成员是PALETTEENTRY数组,数组项的数目由palNumEntries成员指定。PALETTEENTRY结构对调色板的某一个颜色表项进行了描述,该结构的定义为:

typedef struct tagPALETTEENTRY {

BYTE peRed; //红色的强度(0~255,下同)

BYTE peGreen; //绿色的强度

BYTE peBlue; //蓝色的强度

BYTE peFlags;

} PALETTEENTRY;

  成员peFlags说明了颜色表项的使用方法,在一般应用时为NULL,若读者对peFlags的详细说明感兴趣,可以查看Visual C++的联机帮助。

  可以看出,创建调色板的关键是在PALETTEENTRY数组中指定要使用的颜色。这些颜色可以是程序自己指定的特殊颜色,也可以从DIB位图中载入。逻辑调色板的大小可根据用户使用的颜色数来定,一般不能超过256个颜色表项。

  CreatePalette只是创建了逻辑调色板,此时调色板只是一张孤立的颜色表,还不能对系统产生影响。程序必需调用CDC::SelectPalette把逻辑调色板选入到要使用它的设备上下文中,然后调用CDC::RealizePalette把逻辑调色板实现到系统调色板中。函数的声明为:

CPalette* SelectPalette( CPalette* pPalette, BOOL bForceBackground );
该函数把指定的调色板选择到设备上下文中。参数pPalette指向一个CPalette对象。参数bForceBackground如果是TRUE,那么被选择的调色板总是作为背景调色板使用,如果bForceBackground是FALSE并且设备上下文是附属于某个窗口的,那么当窗口是活动窗口或活动窗口的子窗口时,被选择的调色板将作为前景调色板实现,否则作为背景调色板实现。如果使用调色板的是一个内存设备上下文,则该参数被忽略。函数返回设备上下文原来使用的调色板,若出错则返回NULL。

UINT RealizePalette( );
该函数把设备上下文中的逻辑调色板实现到系统调色板中。函数的返回值表明调色板映射表中有多少项被改变了。

 

  如果某一个窗口要显示特殊的颜色,那么一般应该在处理WM_PAINT消息时实现自己的逻辑调色板。也就是说,在OnPaint或OnDraw函数中重绘以前,要调用SelectPalette和RealizePalette。如果窗口显示的颜色比较重要,则在调用SelectPalette时应该指定bForceBackground参数为FALSE。

  前景调色板具有使用颜色的最高优先级,它有无条件占用系统调色板(20种保留颜色除外)的权力,也就是说,如果需要,前景调色板将覆盖系统调色板的236个表项,而不管这些表项是否正被别的窗口使用。背景调色板则无权破坏系统调色板中的已使用项。

  请读者注意,前景调色板应该是唯一。如果一个活动窗口同时要实现几个逻辑调色板,那么只能有一个调色板作为前景调色板实现,也即在调用CDC::SelectPalette时只能有一个bForceBackground被指定为FALSE,其它的bForceBackground必需为TRUE。通常是把具有输入焦点的窗口的调色板作为前景调色板实现,其它窗口只能使用背景调色板。如果活动窗口的子窗口全都使用前景调色板,则会导致程序的死循环。

提示:请读者注意区分活动窗口和有输入焦点的窗口。有输入焦点的窗口要么是活动窗口本身,要么是活动窗口的子窗口。也就是说,活动窗口不一定具有输入焦点,当活动窗口的子窗口获得输入焦点时,活动窗口就会失去输入焦点。

 

11.1.3 使用颜色的三种方法

  在调用GDI函数绘图时,可以用不同的方法来选择颜色。Windows用COLORREF数据类型来表示颜色,COLORREF型值的长度是4字节,其中最高位字节可以取三种不同的值,分别对应三种使用颜色的方法。表11.1列出了这些不同的取值及其含义。

 

表11.1 COLORREF型值的最高位字节的含义


取值


含义

0x00

指定RGB引用。此时三个低位字节含有红、绿、蓝色的强度,Windows将抖动20种保留的颜色来匹配指定的颜色,而不管程序是否实现了自己的调色板。

0x01

指定调色板索引引用。此时最低位字节含有逻辑调色板的索引,Windows根据该索引在逻辑调色板中找到所需的颜色。

0x02

指定调色板RGB引用。此时三个低位字节含有红、绿、蓝色的强度,Windows会在逻辑调色板中找到最匹配的颜色。

为了方便用户的使用,Windows提供了三个宏来构建三种不同的COLORREF数据,它们是:

COLORREF RGB(BYTE bRed,BYTE bGreen,BYTE bBlue); //RGB引用

COLORREF PALETTEINDEX(WORD wPaletteIndex); //调色板索引引用

COLORREF PALETTERGB(BYTE bRed,BYTE bGreen, //调色板RGB引用
BYTE bBlue);

例如,我们可以用上述三种方法来指定刷子的颜色。下面的代码用系统调色板中的红色建立一个刷子:

CBrush brush;

brush.CreateSolidBrush(RGB(255,0,0));

pDC->SelectObject(&brush);

下面的代码用逻辑调色板的索引2中的颜色来创建一个刷子:

pDC->SelectPalette(&m_Palette,FALSE);

pDC->RealizePalette( );

CBrush brush;

brush.CreateSolidBrush(PALETTEINDEX(2));

pDC->SelectObject(&brush);

下面的代码用逻辑调色板中最匹配的深灰色来创建一个刷子:

pDC->SelectPalette(&m_Palette,FALSE);

pDC->RealizePalette( );

CBrush brush;

brush.CreateSolidBrush(PALETTERGB(20,20,20));

pDC->SelectObject(&brush);

11.1.4 与系统调色板有关的消息

  为了协调各个窗口对系统调色板的使用,Windows在必要的时侯会向顶层窗口和重叠窗口发送消息WM_QUERYNEWPALETTE和WM_PALETTECHANGED。

  当某一顶层或重叠窗口(如主框架窗口)被激活时,会收到WM_QUERYNEWPALETTE消息,在窗口创建之初也会收到该消息,该消息先于WM_PAINT消息到达窗口。如果活动窗口要使用特殊的颜色,则在收到该消息时应该实现自己的逻辑调色板并重绘窗口。如果窗口实现了逻辑调色板,那么WM_QUERYNEWPALETTE消息的处理函数应返回TRUE。通常窗口在收到该消息后应该为有输入焦点的窗口(如视图)实现前景调色板,但如果程序觉得它显示的颜色并不重要,那么在收到该消息后可以把逻辑调色板作为背景调色板实现(指定CDC::SelectPalette函数的bForceBackground参数为TRUE),这样程序就失去了使用系统调色板的最高优先权。

  当活动窗口实现其前景调色板并改变了系统调色板时,Windows会向包括活动窗口在内的所有的顶层窗口和重叠窗口发送WM_PALETTECHANGED消息,在该消息的wParam参数中包含了改变系统调色板的窗口的句柄。其它窗口如果使用了自己的逻辑调色板,那么应该重新实现其逻辑调色板,并重绘窗口。这是因为系统调色板已经被改变了,必需重新建立调色板映射表并重绘,否则可能会显示错误的颜色。当然,非活动窗口只能使用背景调色板,所以显示的颜色肯定没有在前台的时侯好。要注意只有在活动窗口实现了前景调色板且改变了系统调色板时,才会产生WM_PALETTECHANGED消息。也就是说,如果窗口在调用CDC::SelectPalette时指定bForceBackground参数为TRUE,那么是不会产生WM_PALETTECHANGED消息。

  总之,WM_QUERYNEWPALETTE消息为活动窗口提供了实现前景调色板的机会,而WM_PALETTECHANGED消息为窗口提供了适应系统调色板变化的机会。

  需要指出的是,子窗口是收不到与调色板有关的消息的。因此,如果子窗口(如视图)要使用自己的逻辑调色板,那么顶层窗口或重叠窗口应该及时通知子窗口与调色板有关的消息。

11.1.5 具体实例

  现在让我们来看一个使用调色板的演示程序。该程序名为TestPal,如图11.3所示,该程序显示了两组红色方块,每组方块都是16×16共256个。左边的这组方块是用逻辑调色板画的,红色的强度从0到255递增,作为对比,在右边用RGB引用画出了256个递增的红色方块。读者可以对比这两组方块的颜色质量,以体会调色板索引引用和RGB引用的区别。该程序也着重向读者演示了处理调色板消息的方法。

T11_3.tif (237628 bytes)

图11.3 TestPal程序

 

  首先,请读者用AppWizard建立一个名为TestPal的MFC单文挡应用程序。然后,用ClassWizard为CMainFrame类加入WM_QUERYNEWPALETTE和WM_PALETTECHANGED消息的处理函数,使用缺省的函数名。接着,在TestPal.h文件中类CTestPalApp的定义前加入下面一行:


#define WM_DOREALIZE WM_USER+200

当收到调色板消息时,主框架窗口会发送用户定义的WM_DOREALIZE消息通知视图。

最后,请读者按清单11.1和11.2修改程序。

 


清单11.1 CMainFrame类的部分代码

BOOL CMainFrame::OnQueryNewPalette()

{

// TODO: Add your message handler code here and/or call default

 


GetActiveView()->SendMessage(WM_DOREALIZE);

return TRUE; //返回TRUE表明实现了逻辑调色板

}

 

void CMainFrame::OnPaletteChanged(CWnd* pFocusWnd)

{

CFrameWnd::OnPaletteChanged(pFocusWnd);

// TODO: Add your message handler code here

 


if(GetActiveView()!=pFocusWnd)

GetActiveView()->SendMessage(WM_DOREALIZE);

}

 


清单11.2 CTestPalView类的部分代码

// TestPalView.h : interface of the CTestPalView class

class CTestPalView : public CView

{

. . .

protected:

 


CPalette m_Palette;

. . .


afx_msg LRESULT OnDoRealize(WPARAM wParam, LPARAM lParam);

DECLARE_MESSAGE_MAP()

};

 

// TestPalView.cpp : implementation of the CTestPalView class

 

BEGIN_MESSAGE_MAP(CTestPalView, CView)

 


. . .

ON_MESSAGE(WM_DOREALIZE, OnDoRealize)

END_MESSAGE_MAP()

 

CTestPalView::CTestPalView()

{

// TODO: add construction code here

 


LPLOGPALETTE pLogPal;

pLogPal=(LPLOGPALETTE)malloc(sizeof(LOGPALETTE)+

sizeof(PALETTEENTRY)*256);

pLogPal->palVersion=0x300;

pLogPal->palNumEntries=256;

for(int i=0;i<256;i++)

{

pLogPal->palPalEntry[i].peRed=i; //初始化为红色

pLogPal->palPalEntry[i].peGreen=0;

pLogPal->palPalEntry[i].peBlue=0;

pLogPal->palPalEntry[i].peFlags=0;

}

if(!m_Palette.CreatePalette(pLogPal))

AfxMessageBox("Can't create palette!");

}

 

void CTestPalView::OnDraw(CDC* pDC)

{

CTestPalDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

 

// TODO: add draw code for native data here

 


CBrush brush,*pOldBrush;

int x,y,i;

 

pDC->SelectPalette(&m_Palette,FALSE);

pDC->RealizePalette();

pDC->SelectStockObject(BLACK_PEN);

for(i=0;i<256;i++)

{

x=(i%16)*16;

y=(i/16)*16;

brush.CreateSolidBrush(PALETTEINDEX(i)); //调色板索引引用

pOldBrush=pDC->SelectObject(&brush);

pDC->Rectangle(x,y,x+16,y+16);

pDC->SelectObject(pOldBrush);

brush.DeleteObject();

}

for(i=0;i<256;i++)

{

x=(i%16)*16+300;

y=(i/16)*16;

brush.CreateSolidBrush(RGB(i,0,0)); //RGB引用

pOldBrush=pDC->SelectObject(&brush);

pDC->Rectangle(x,y,x+16,y+16);

pDC->SelectObject(pOldBrush);

brush.DeleteObject();

}

}

 


 

LRESULT CTestPalView::OnDoRealize(WPARAM wParam, LPARAM)

{

CClientDC dc(this);

dc.SelectPalette(&m_Palette,FALSE);

if(dc.RealizePalette()) //若调色板映射被改变则刷新视图

GetDocument()->UpdateAllViews(NULL);

return 0L;

}

  在CTestPalView的构造函数中创建了一个含有256个递增红色的逻辑调色板。

  当变为活动窗口以及窗口创建时,TestPal程序的主框架窗口都会收到WM_QUERYNEWPALETTE消息,该消息的处理函数OnQueryNewPalette负责发送WM_DOREALIZE消息通知视图, 并返回TRUE以表明活动窗口实现了逻辑调色板。WM_DOREALIZE消息的处理函数CTestPalView::OnDoRealize为视图实现一个前景调色板,该函数中有一个判断语句可提高程序运行的效率:如果CDC::RealizePalette返回值大于零,则说明调色板映射表发生了变化,此时必须刷新视图,否则制图中的颜色将失真。如果RealizePalette返回零则说明调色板映射没有变化,这时就没有必要刷新视图。

  无论是TestPal还是别的应用程序在实现前景调色板并改变了系统调色板时,TestPal程序的主框架窗口都会收到WM_PALETTECHANGED消息。请注意该消息的处理函数CMainFrame::OnPaletteChanged有一个pFocusWnd参数,该参数表明是哪一个窗口改变了系统调色板。函数用pFocusWnd来判断,如果是别的应用程序实现了前景调色板,则通知视图调用OnDoRealize实现其逻辑调色板,注意虽然CDC::SelectPalette的bForceBackground参数是FALSE,但这时视图的逻辑调色板是作为背景调色板实现的。如果是TestPal自己的视图实现了前景调色板,则没有必要调用OnDoRealize。

  请读者将Windows当前的显示模式设置为256色,然后编译并运行TestPal,对比一下RGB引用与调色板索引引用的效果,读者不难发现左边用调色板索引引用输出的颜色比右边好的多。通过该程序我们可以看出,即使在系统调色板中已实现了丰富的红色的情况下,RGB引用得到的红色仍然是20种保留颜色的抖动色。

  读者可以打开Windows的画笔程序,并在该程序中打开一幅256色的位图(如Windows目录下的Forest.bmp)。在画笔和TestPal程序之间来回切换,读者可以看到,由于两个应用程序都正确的处理了调色板消息,在前台的应用程序总是具有最好的颜色显示,而后台程序的颜色虽然有些失真,但还比较令人满意。

  需要指出的是,TestPal程序只使用了一个逻辑调色板,所以它处理调色板消息的方法比较简单。如果程序要用到多个逻辑调色板,那么就需要采取一些新措施来保证只有一个逻辑调色板作为前景调色板使用。在11.4节读者可以看到使用多个逻辑调色板时的处理方法。

11.2 位图

  Windows用位图(Bitmap)来显示和保存图像,从单色的到24位真彩色图像都可以存储到位图中。

  位图实际上是一个像素值阵列,像素阵列存储在一个字节数组中,每一个像素的位数可以是1、4、8或24。单色位图的字节数组中的每一位代表一个像素,16色位图的字节数组中每一个字节存储两个像素,256色的位图每一个字节存储一个像素,而真彩色位图中每个像素用三个字节来表示。在256色以下的位图中存储的像素值实际上是调色板索引,在真彩色位图中存储的则是像素的RGB颜色值。

  位图分为依赖于设备的位图(DDB)和与设备无关的位图(DIB),二者有不同的用途。 

11.3 依赖于设备的位图(DDB)

DDB(Device-dependent bitmap)依赖于具体设备,这主要体现在以下两个方面:

  • DDB的颜色模式必需与输出设备相一致。例如,如果当前的显示设备是256色模式,那么DDB必然也是256色的,即一个像素用一个字节表示。

  • 在256色以下的位图中存储的像素值是系统调色板的索引,其颜色依赖于系统调色板。

 

由于DDB高度依赖输出设备,所以DDB只能存在于内存中,它要么在视频内存中,要么在系统内存中。


 

11.3.1 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;

 

11.3.2 DDB的用途

  DDB的主要用途是保存位图。要保存的位图可以来自资源位图,也可以是一个绘图的结果。

  前面说过,在256色以下的显示模式中,DDB中的像素值是系统调色板的索引。一般在系统调色板中除了保留的20种静态颜色外,其它表项都有可能被应用程序改变。如果DDB中有一些像素值是指向20种静态颜色以外的颜色,那么该位图的颜色将是不稳定的。因此,DDB不能用来长期存储色彩丰富的位图。如果位图使用的大部分颜色都是20种保留色,则该位图可以用CBitmap对象保存在内存中。例如,用CDC::LoadBitmap载入的资源位图一般都是颜色较简单的位图,对于那些颜色比较丰富的位图,只有使用下面将要介绍的DIB才能长期保存。

在窗口中显示DDB的方法有些特别,其过程分以下几步:

构建一个CDC对象,然后调用CDC::CreateCompatibleDC创建一个兼容的内存设备上下文。

调用CDC::SelectObject将DDB选入内存设备上下文中。

调用CDC::BitBlt或CDC::StretchBlt将DDB从内存设备上下文中输出到窗口的设备上下文中。

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

 

下面这段代码在视图中显示了一个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所示。

 


表11.2 常用的ROP代码


ROP码


含义

BLACKNESS

输出黑色

DSTINVERT

反转目的位图

MERGECOPY

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

MERGEPAINT

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

NOTSRCCOPY

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

NOTSRCERASE

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

PATCOPY

把图案拷贝到目的位图中

PATINVERT

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

PATPAINT

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

SRCAND

用与操作融合源位图和目的位图

SRCCOPY

把源位图拷贝到目的位图

SRCERASE

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

SRCINVERT

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

SRCPAINT

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

WHITENESS

输出白色

 

 

函数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脱离设备上下文,该位图就可以被其它对象使用。

11.4 与设备无关的位图(DIB)

 

DIB(Device-indepentent bitmap)的与设备无关性主要体现在以下两个方面:

  • DIB的颜色模式与设备无关。例如,一个256色的DIB即可以在真彩色显示模式下使用,也可以在16色模式下使用。

  • 256色以下(包括256色)的DIB拥有自己的颜色表,像素的颜色独立于系统调色板。

 

  由于DIB不依赖于具体设备,因此可以用来永久性地保存图象。DIB一般是以*.BMP文件的形式保存在磁盘中的,有时也会保存在*.DIB文件中。运行在不同输出设备下的应用程序可以通过DIB来交换图象。

DIB还可以用一种RLE算法来压缩图像数据,但一般来说DIB是不压缩的。

11.4.1 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像素的逻辑颜色索引转换成系统调色板索引。

 

11.4.2 编写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类的代码这里就不作具体解释了,读者只要会用就行。

清单11.3 CDib.h

#if !defined MYDIB

#define MYDIB

 

#include "dibapi.h"

 

class CDib

{

public:

CDib();

~CDib();

protected:

HDIB m_hDIB;

CPalette* m_palDIB;

public:

BOOL Load(LPCTSTR lpszFileName);

BOOL LoadFromResource(UINT nID);

CPalette* GetPalette() const

{ return m_palDIB; }

BOOL Draw(CDC *pDC, int x, int y, int cx=0, int cy=0);

int Width();

int Height();

void DeleteDIB();

};

 

#endif

 

 


清单11.4 Cdib.cpp

#include <stdafx.h>

#include "CDib.h"

 

#ifdef _DEBUG

#undef THIS_FILE

static char BASED_CODE THIS_FILE[] = __FILE__;

#endif

 

CDib::CDib()

{

m_palDIB=NULL;

m_hDIB=NULL;

}

 

CDib::~CDib()

{

DeleteDIB();

}

 

void CDib::DeleteDIB()

{

if (m_hDIB != NULL)

::GlobalFree((HGLOBAL) m_hDIB);

if (m_palDIB != NULL)

delete m_palDIB;

}

 

//从文件中载入DIB

BOOL CDib::Load(LPCTSTR lpszFileName)

{

HDIB hDIB;

CFile file;

CFileException fe;

if (!file.Open(lpszFileName, CFile::modeRead|CFile::shareDenyWrite, &fe))

{

AfxMessageBox(fe.m_cause);

return FALSE;

}

TRY

{

hDIB = ::ReadDIBFile(file);

}

CATCH (CFileException, eLoad)

{

file.Abort();

return FALSE;

}

END_CATCH

 

DeleteDIB(); //清除旧位图

m_hDIB=hDIB;

 

m_palDIB = new CPalette;

if (::CreateDIBPalette(m_hDIB, m_palDIB) == NULL)

{

// DIB有可能没有调色板

delete m_palDIB;

m_palDIB = NULL;

}

return TRUE;

}

 

//从资源中载入DIB

BOOL CDib::LoadFromResource(UINT nID)

{

HINSTANCE hResInst = AfxGetResourceHandle();

HRSRC hFindRes;

HDIB hDIB;

LPSTR pDIB;

LPSTR pRes;

HGLOBAL hRes;

//搜寻指定的资源

hFindRes = ::FindResource(hResInst, MAKEINTRESOURCE(nID), RT_BITMAP);

if (hFindRes == NULL) return FALSE;

hRes = ::LoadResource(hResInst, hFindRes); //载入位图资源

if (hRes == NULL) return FALSE;

DWORD dwSize=::SizeofResource(hResInst,hFindRes);

 

hDIB = (HDIB) ::GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, dwSize);

if (hDIB == NULL) return FALSE;

pDIB = (LPSTR)::GlobalLock((HGLOBAL)hDIB);

pRes = (LPSTR) ::LockResource(hRes);

memcpy(pDIB, pRes, dwSize); //把hRes中的内容复制hDIB中

::GlobalUnlock((HGLOBAL) hDIB);

 

DeleteDIB();

m_hDIB=hDIB;

m_palDIB = new CPalette;

if (::CreateDIBPalette(m_hDIB, m_palDIB) == NULL)

{

// DIB有可能没有调色板

delete m_palDIB;

m_palDIB = NULL;

}

return TRUE;

}

 

int CDib::Width()

{

if(m_hDIB==NULL) return 0;

LPSTR lpDIB = (LPSTR) ::GlobalLock((HGLOBAL) m_hDIB);

int cxDIB = (int) ::DIBWidth(lpDIB); // Size of DIB - x

::GlobalUnlock((HGLOBAL) m_hDIB);

return cxDIB;

}

 

int CDib::Height()

{

if(m_hDIB==NULL) return 0;

LPSTR lpDIB = (LPSTR) ::GlobalLock((HGLOBAL) m_hDIB);

int cyDIB = (int) ::DIBHeight(lpDIB); // Size of DIB - y

::GlobalUnlock((HGLOBAL) m_hDIB);

return cyDIB;

}

 

//显示DIB,该函数具有缩放功能

//参数x和y说明了目的矩形的左上角坐标,cx和cy说明了目的矩形的尺寸

//cx和cy若有一个为0则该函数按DIB的实际大小绘制,cx和cy的缺省值是0

BOOL CDib::Draw(CDC *pDC, int x, int y, int cx, int cy)

{

if(m_hDIB==NULL) return FALSE;

CRect rDIB,rDest;

rDest.left=x;

rDest.top=x;

if(cx==0||cy==0)

{

cx=Width();

cy=Height();

}

rDest.right=rDest.left+cx;

rDest.bottom=rDest.top+cy;

rDIB.left=rDIB.top=0;

rDIB.right=Width();

rDIB.bottom=Height();

return ::PaintDIB(pDC->GetSafeHdc(),&rDest,m_hDIB,&rDIB,m_palDIB);

}

 

11.4.3 使用CDib类的例子

  现在让我们来看一个使用CDib类的例子。如图11.4所示,程序名为ShowDib,是一个多文档应用程序,它的功能与VC的DibLook例程有些类似,可同时打开和显示多个位图。

T11_4.jpg (24220 bytes)

图11.4 用ShowDib来显示位图

 

  请读者用AppWizard建立一个名为ShowDib的MFC工程。程序应该用滚动视图来显示较大的位图,所以在MFC AppWizard的第6步应把CShowDibView的基类改为CScrollView。

  由于ShowDib程序要用到CDib类,所以应该把dibapi.cpp、myfile.cpp、dibapi.h、CDib.cpp和CDib.h文件拷贝到ShowDib目录下,并选择Project->Add to Project->Files命令把这些文件加到ShowDib工程中。

  在ShowDib.h文件中CShowDibApp类的定义之前加入下面一行:


#define WM_DOREALIZE WM_USER+200

  当收到调色板消息时,主框架窗口会发送用户定义的WM_DOREALIZE消息通知视图。

  接下来,需要用ClassWizard为CMainFrame加入WM_QUERYNEWPALETTE和WM_PALETTECHANGED消息的处理函数,为CShowDibDoc类加入OnOpenDocument函数。

  最后,请读者按清单11.5、11.6和11.7修改程序。


清单11.5 CMainFrame类的部分代码

// MainFrm.cpp : implementation of the CMainFrame class

void CMainFrame::OnPaletteChanged(CWnd* pFocusWnd)

{

CMDIFrameWnd::OnPaletteChanged(pFocusWnd);

// TODO: Add your message handler code here

 


SendMessageToDescendants(WM_DOREALIZE, 1); //通知所有的子窗口

}

 

BOOL CMainFrame::OnQueryNewPalette()

{

// TODO: Add your message handler code here and/or call default

 


CMDIChildWnd* pMDIChildWnd = MDIGetActive();

if (pMDIChildWnd == NULL)

return FALSE; // 没有活动的MDI子框架窗口

CView* pView = pMDIChildWnd->GetActiveView();

pView->SendMessage(WM_DOREALIZE,0); //只通知活动视图

return TRUE; //返回TRUE表明实现了逻辑调色板

}

 

 


清单11.6 CShowDibDoc类的部分代码

// ShowDibDoc.h : interface of the CShowDibDoc class

 


#include "CDib.h"

 

class CShowDibDoc : public CDocument

{

. . .

// Attributes

public:

 


CDib m_Dib;

. . .

};

 

// ShowDibDoc.cpp : implementation of the CShowDibDoc class

 

BOOL CShowDibDoc::OnOpenDocument(LPCTSTR lpszPathName)

{

if (!CDocument::OnOpenDocument(lpszPathName))

return FALSE;

// TODO: Add your specialized creation code here

 


BeginWaitCursor();

BOOL bSuccess=m_Dib.Load(lpszPathName); //载入DIB

EndWaitCursor();

return bSuccess;

}

 

 


 

清单11.7 CShowDibView类的部分代码

// ShowDibView.h : interface of the CShowDibView class

class CShowDibView : public CScrollView

{

. . .


afx_msg LRESULT OnDoRealize(WPARAM wParam, LPARAM lParam);

DECLARE_MESSAGE_MAP()

};

 

// ShowDibView.cpp : implementation of the CShowDibView class

 

BEGIN_MESSAGE_MAP(CShowDibView, CScrollView)

. . .


ON_MESSAGE(WM_DOREALIZE, OnDoRealize)

END_MESSAGE_MAP()

 

void CShowDibView::OnInitialUpdate()

{

CScrollView::OnInitialUpdate();

CSize sizeTotal;

// TODO: calculate the total size of this view

 


CShowDibDoc* pDoc = GetDocument();

sizeTotal.cx = pDoc->m_Dib.Width();

sizeTotal.cy = pDoc->m_Dib.Height();

SetScrollSizes(MM_TEXT, sizeTotal); //设置视图的滚动范围

}

 

void CShowDibView::OnActivateView(BOOL bActivate, CView* pActivateView, CView* pDeactiveView)

{

// TODO: Add your specialized code here and/or call the base class

 


if(bActivate)

OnDoRealize(0,0); //刷新视图

CScrollView::OnActivateView(bActivate, pActivateView, pDeactiveView);

}

 

 


LRESULT CShowDibView::OnDoRealize(WPARAM wParam, LPARAM)

{

CClientDC dc(this);

//wParam参数决定了该视图是否实现前景调色板

dc.SelectPalette(GetDocument()->m_Dib.GetPalette(),wParam);

if(dc.RealizePalette())

GetDocument()->UpdateAllViews(NULL);

return 0L;

}

 

void CShowDibView::OnDraw(CDC* pDC)

{

CShowDibDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// TODO: add draw cod

抱歉!评论已关闭.