1、调色板的创建和实现
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。通常是把具有输入焦点的窗口的调色板作为前景调色板实现,其它窗口只能使用背景调色板。如果活动窗口的子窗口全都使用前景调色板,则会导致程序的死循环。
提示:请读者注意区分活动窗口和有输入焦点的窗口。有输入焦点的窗口要么是活动窗口本身,要么是活动窗口的子窗口。也就是说,活动窗口不一定具有输入焦点,当活动窗口的子窗口获得输入焦点时,活动窗口就会失去输入焦点。 |
2、使用颜色的三种方法
在调用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, BYTE bBlue); //调色板RGB引用
例如,我们可以用上述三种方法来指定刷子的颜色。下面的代码用系统调色板中的红色建立一个刷子:
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);
3、与系统调色板有关的消息
为了协调各个窗口对系统调色板的使用,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消息为窗口提供了适应系统调色板变化的机会。
需要指出的是,子窗口是收不到与调色板有关的消息的。因此,如果子窗口(如视图)要使用自己的逻辑调色板,那么顶层窗口或重叠窗口应该及时通知子窗口与调色板有关的消息。
4、 具体实例
现在让我们来看一个使用调色板的演示程序。该程序名为TestPal,如图11.3所示,该程序显示了两组红色方块,每组方块都是16×16共256个。左边的这组方块是用逻辑调色板画的,红色的强度从0到255递增,作为对比,在右边用RGB引用画出了256个递增的红色方块。读者可以对比这两组方块的颜色质量,以体会调色板索引引用和RGB引用的区别。该程序也着重向读者演示了处理调色板消息的方法。
图 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修改程序。
清单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); }
清单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程序只使用了一个逻辑调色板,所以它处理调色板消息的方法比较简单。如果程序要用到多个逻辑调色板,那么就需要采取一些新措施来保证只有一个逻辑调色板作为前景调色板使用。