调色板只有图片的颜色小于等于256色的时候才有,16位高彩和24位32位真彩是没有调色板的.
调色板的存在的意义只是在当初486以前为了节省空间的一种采用索引的压缩算法,现在没有人这种东西。
调色板是为了节约空简所用的,相当于一个索引。只有16位以下的才用调色板,真彩色不用调色板。
让我们来看看下面的例子。
有一个长宽各为200个象素,颜色数为16色的彩色图,每一个象素都用R、G、B三个分量表示。因为每个分量有256个级别,要用8位(bit),即一个字节(byte)来表示,所以每个象素需要用3个字节。整个图象要用200×200×3,约120k字节,可不是一个小数目呀!如果我们用下面的方法,就能省的多。
因为是一个16色图,也就是说这幅图中最多只有16种颜色,我们可以用一个表:表中的每一行记录一种颜色的R、G、B值。这样当我们表示一个象素的颜色时,只需要指出该颜色是在第几行,即该颜色在表中的索引值。举个例子,如果表的第0行为255,0,0(红色),那么当某个象素为红色时,只需要标明0即可。
让我们再来计算一下:16种状态可以用4位(bit)表示,所以一个象素要用半个字节。整个图象要用200×200×0.5,约20k字节,再加上表占用的字节为3×16=48字节.整个占用的字节数约为前面的1/6,省很多吧?
这张R、G、B的表,就是我们常说的调色板(Palette),另一种叫法是颜色查找表LUT(Look Up Table),似乎更确切一些。Windows位图中便用到了调色板技术。其实不光是Windows位图,许多图象文件格式如pcx、tif、gif等都用到了。所以很好地掌握调色板的概念是十分有用的。
有一种图,它的颜色数高达256×256×256种,也就是说包含我们上述提到的R、G、B颜色表示方法中所有的颜色,这种图叫做真彩色图(true color)。真彩色图并不是说一幅图包含了所有的颜色,而是说它具有显示所有颜色的能力,即最多可以包含所有的颜色。表示真彩色图时,每个象素直接用R、G、B三个分量字节表示,而不采用调色板技术。原因很明显:如果用调色板,表示一个象素也要用24位,这是因为每种颜色的索引要用24位(因为总共有224种颜色,即调色板有224行),和直接用R,G,B三个分量表示用的字节数一样,不但没有任何便宜,还要加上一个256×256×256×3个字节的大调色板。所以真彩色图直接用R、G、B三个分量表示,它又叫做24位色图。
调色板一般是为了显示256色图象时使用的。图象(BMP图象)按颜色种类分类可以分为:
1、黑白图象。使用2个颜色的调色板;
2、256色图象(包括256级灰度图象),使用调色板。调色板中记录的是图象中使用的256种颜色,图象数据中记录的是颜色索引,通过这个索引值就可以找到对应的颜色。
3、24bit真彩色图象,不使用调色板。图象数据中保留RGB三种颜色组合,可以直接显示。
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使用逻辑调色板来作为使用颜色的应用程序和系统调色板(物理调色板)之间的缓冲。
图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),顶层窗口是指没有父窗口或父窗口是桌面窗口的窗口,这种窗口一般都有标题和边框,主要包括框架窗口和对话框。术语重叠窗口是指作为应用程序主窗口的窗口,我们可以把对话框看成是一种特殊的重叠式窗口。 |
图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引用的区别。该程序也着重向读者演示了处理调色板消息的方法。
图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例程有些类似,可同时打开和显示多个位图。
图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 code for native data here
pDoc->m_Dib.Draw(pDC,0,0); //输出DIB
}
在程序中使用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函数来完成。
=========================================================================
在计算机显示系统中,只有高档的显示系统才可以在屏幕每一个位置上同时显示各种可能的颜色,大多数显示卡只能显示有限数量的颜色。标准VGA能同屏显示16色,每像素占4位存储空间。内存足够的SVGA适配器最多每像素占8位,可同屏幕显示262144种不同颜色中的256色。普通使用的SVGA显示适配器通过将像素值解释为一个表格的索引,根据索引值将每个像素的内容转变为一种颜色。这个表格就是硬件调色板,其条目就是RGB值。
Windows 系统在支持硬件调色板的时候,遇到了些困难。如果 Windows 系统允许任何一个程序改变视频硬件调色板中RGB 颜色的设置,那么在系统中运行的每一个应用程序都将受影响。例如硬件调色板中的黑色被改为蓝色,那么每一个窗口中的所有黑色像素立即就会被改为蓝色。这就违反了 Windows 应用程序作为独立窗口运行,彼此互不干扰的基本原则。另一个问题是 Windows 程序可以在任何系统运行,而大多数系统中显示的颜色和 Super VGA 系统中的颜色数量不同。为了解决上述问题,从3.0版开始Windows提供了一个调色板管理器,用作用户开发的应用程序和输出设备。调色板管理器由系统调色板和逻辑调色板构成,它们用于管理实际设备的“硬件调色板”。系统调色板是相应于显示器的硬件调色板,它是一个有二十种保留颜色的数组,它的项数与显示器实际能显示的颜色数相等,这些保留颜色被用来绘制系统的菜单、按钮控制、屏幕桌面颜色和抖动的画刷等。一般情况下,这二十种保留颜色不能被改变。逻辑调色板是模拟显示卡中硬件调色板的一块内存区域,逻辑调色板中每一项包含一个TGB值,这个RGB值供应用程序创建彩色的画笔、字体、画刷和位图。当逻辑调色板包括的项比硬件设备实际支持的要多时,“扩充的”逻辑调色板就匹配硬件调色板的相近颜色,如果逻辑调色板所包括的项少于硬件调色板,硬件调色板中的一些颜色将不使用。为了更加深入的了解Windows系统中的调色板,这里我们再来看一下调色板管理器的工作原理:
如果Windows显示器处理的是24位彩色,调色板管理器没有太大意义,因为可以显示的颜色(约有一千六百万种)能表示DIB中所有颜色。即使显示器处理的只是3位或4位颜色,调色板管理器也没有什么用处,这时系统本身储存有20种系统颜色,已超过了设备能显示的颜色数组。只有对八位(256种颜色)彩色显示器,调色板管理器才真正起作用。为了使调色板管理正常工作,当Windows应用程序使用逻辑调色板时,首先必须检查显示驱动器是否支持逻辑调色板,然后根据需要创建一个逻辑调色板,在使用逻辑调色板之前还必须将其先安装到系统调色板上。具体过程如下:
1) 检查显示驱动程序是否支持逻辑调色板
if(GetDeviceCaps(hDC,DRIVERSION)==0x300)
{kk1}
}
else{kk1}
//不支持逻辑调色板
}
2)逻辑调色板的创建
首先根据Windows规定的逻辑调色板的格式,在内存中分配一块相应大小的区域。内存块的大小根据需要显示的颜色数所定。
typedefstructtagLOGPALETTE{kk1}
WORDpalVersion;
/*windows的版本号*/
WORDpalNumEntries;
/*调色板的项数*/
PALETTEENTRYpalPalEntry[1];
}LOGPALETTE;
typedef struct tagPALETTEENTRY{kk1}
BYTEpeRed;
BYTEpeGreen;
BYTEpeBlue;
BYTEpeFlags;
}PALETTEENTRY;
公式如下:
Sizeof(LOGPALETTE)*numcolor*Sizeof(PALETTEENTRY)
numcolor-----------显示的颜色数;
然后将Windows系统的版本号,及调色板要显示的颜色数填入LOGPALETTE结构中;再将所需要显示的颜色的RGB值依次填入PALETTEENTRY结构中。最后,利用Windows的API中的CreatePalette()函数创建一个调色板,并利用函数SelectPalette()把创建的调色板选入设备描述表。这样一个逻辑调色板就创建了。
(3)逻辑调色板映射到系统调色板
API中RealizePalette()函数可以使Windows的调色板管理器工作。RealizePalette()函数通知Windows调色板管理器真正地改变系统调色板,使其与设备描述表中的逻辑调色板
相匹配并按要求映射颜色。这个映射过程不是简单的将逻辑调色板前面的颜色放入系统调色板二十中保留颜色之后,逻辑调色板中剩余的颜色就抛弃不管。它的映射规则如下:
如果逻辑调色板中某种颜色在系统调色板中已存在,该颜色将映射到系统调色板中相应颜色上。
如果逻辑调色板中某种颜色在系统调色板中无相应颜色,只要系统调色板有空间,该颜色就加到系统调色板上。
当系统调色板已满时,逻辑调色板中颜色映射到系统调色板中最相近的颜色上。根据以上规则映射就形成了Windows用来正确显示图象的调色板。通常情况下,Windows系保留二十种供所有窗口使用的系统颜色不能被改变,但是系统还是提供了函数SetSystemPaletteUse()允许应用程序去改变系统调色板,或者恢复正常状态。在使用一个16到64色的设备时,改变系统调色板是扩展颜色选择项的一种方法。对于64色以上的设备来说,具有足够的颜色。因此,保留系统的颜色与增加需要的颜色并不矛盾。
(4)在退出程序前,释放逻辑调色板分配的内存,恢复原来的调色板,删除当前调色板。
LocalUnlock(hLocPal);
LocalFree(hLocPal);
SeletPalette(hDC,hOldPal,FALSE);
DeleteObject(hNewPal);
三、逻辑调色板冲突的解决方法
当几个程序都使用逻辑调色板的时候,Windows系统把当前优先级最高窗口的逻辑调色板送给系统调色板。非活动窗口的颜色取决于还有哪些颜色未被采用,这些非活动窗口首先使用系统调色板中剩余不用的项,然后才为那些无法得到原色的颜色使用调色板上相近的颜色。当要在同一窗口显示具有不同逻辑调色板的设备无关位图时,Windows系统就无法支持。我们解决这个问题的办法是:合并所有的逻辑调色板。当逻辑调色板的总项数不超过256时,合并操作只需将所有的逻辑调色板连接在一起;当总项数超过256时,可采用以下方法来合并逻辑调色板:从不同逻辑调色板中选出最常用的颜色,组成一个256项逻辑调色板。反复地合并不同逻辑调色板中相邻颜色并用其平均值来取它们。这样就把所有逻辑调色板简化成一个相似的256色逻辑调色板。