1 調色板的原理
PC機上顯示的圖象是由一個(gè)個(gè)像素組成的,每個(gè)像素都有自己的顏色屬性。在PC的顯示系統中,像素的顏色是基于RGB模型的,每一個(gè)像素的顏色由紅(B)、綠(G)、藍(B)三原色組合而成。每種原色用8位表示,這樣一個(gè)的顏色就是24位的。以此推算,PC的SVGA適配器可以同時(shí)顯示224約一千六百多萬(wàn)種顏色。24位的顏色通常被稱(chēng)作真彩色,用真彩色顯示的圖象可達到十分逼真的效果。
但是,真彩色的顯示需要大量的視頻內存,一幅640×480的真彩色圖象需要約1MB的視頻內存。由于數據量大增,顯示真彩色會(huì )使系統的整體性能迅速下降。為了解決這個(gè)問(wèn)題,計算機使用調色板來(lái)限制顏色的數目。調色板實(shí)際上是一個(gè)有256個(gè)表項的RGB顏色表,顏色表的每項是一個(gè)24位的RGB顏色值。使用調色板時(shí),在視頻內存中存儲的不是的24位顏色值,而是調色板的4位或8位的索引。這樣一來(lái),顯示器可同時(shí)顯示的顏色被限制在256色以?xún)?,對系統資源的耗費大大降低了。
顯示器可以被設置成16、256、64K、真彩色等顯示模式,前兩種模式需要調色板。在16或256色模式下,程序必須將想要顯示的顏色正確地設置到調色板中,這樣才能顯示出預期的顏色。圖11.1顯示了調色板的工作原理。使用調色板的一個(gè)好處是不必改變視頻內存中的值,只需改變調色板的顏色項就可快速地改變一幅圖象的顏色或灰度。
在DOS中,調色板的使用不會(huì )有什么問(wèn)題。由于DOS是一個(gè)單任務(wù)操作系統,一次只能運行一個(gè)程序,因此程序可以獨占調色板。在Windows環(huán)境下,情況就不那么簡(jiǎn)單了。Windows是一個(gè)多任務(wù)操作系統,可以同時(shí)運行多個(gè)程序。如果有幾個(gè)程序都要設置調色板,就有可能產(chǎn)生沖突。為了避免這種沖突,Windows使用邏輯調色板來(lái)作為使用顏色的應用程序和系統調色板(物理調色板)之間的緩沖。
圖1 調色板工作原理為下1所示:
在Windows中,應用程序是通過(guò)一個(gè)或多個(gè)邏輯調色板來(lái)使用系統調色板(物理調色板)。在256色系統調色板中,Windows保留了20種顏色作為靜態(tài)顏色,這些顏色用作顯示W(wǎng)indows界面,應用程序一般不能改變。缺省的系統調色板只包含這20種靜態(tài)顏色,調色板的其它項為空。應用程序要想使用新的顏色,必須將包含有所需顏色的邏輯調色板實(shí)現到系統調色板中。在實(shí)現過(guò)程中,Windows首先將邏輯調色板中的項與系統調色板中的項作完全匹配,對于邏輯調色板中不能完全匹配的項,Windows將其加入到系統調色板的空白項中,系統調色板總共有236個(gè)空白項可供使用,若系統調色板已滿(mǎn),則Windows將邏輯調色板的剩余項匹配到系統調色板中盡可能接近的顏色上。
每個(gè)設備上下文都擁有一個(gè)邏輯調色板,缺省的邏輯調色板只有20種保留顏色,如果要使用新的顏色,則應該創(chuàng )建一個(gè)新的邏輯調色板并將其選入到設備上下文中。但光這樣還不能使用新顏色,程序只有把設備上下文中的邏輯調色板實(shí)現到系統調色板中,新的顏色才能實(shí)現。在邏輯調色板被實(shí)現到系統調色板時(shí),Windows會(huì )建立一個(gè)調色板映射表。當設備上下文用邏輯調色板中的顏色繪圖時(shí),GDI繪圖函數會(huì )查詢(xún)調色板映射表以把像素值從邏輯調色板的索引轉換成系統調色板的索引,這樣當像素被輸出到視頻內存中時(shí)就具有了正確的顏色值。圖11.2說(shuō)明了這種映射關(guān)系,從圖中讀者可以體會(huì )到邏輯調色板的緩沖作用。在該圖中,GDI繪圖函數使用邏輯調色板的索引1中的顏色來(lái)繪圖,通過(guò)查詢(xún)調色板映射表,得知系統調色板中的第23號索引與其完全匹配,這樣實(shí)際輸出到視頻內存中的像素值是23。注意圖中還演示了顏色的不完全匹配,即邏輯調色板中的索引15和系統調色板中的索引46。
每個(gè)要使用額外顏色的窗口都會(huì )實(shí)現自己的邏輯調色板,邏輯調色板中的每種顏色在系統調色板中都有相同或相近的匹配。調色板的實(shí)現優(yōu)先權越高,匹配的精度也就越高。Windows規定,活動(dòng)窗口的邏輯調色板(如果有的話(huà))具有最高的實(shí)現優(yōu)先權。這是因為活動(dòng)窗口是當前與用戶(hù)交互的窗口,應該保證其有最佳的顏色顯示。非活動(dòng)窗口的優(yōu)先權是按Z順序自上到下確定的(Z順序就是重疊窗口的重疊順序)?;顒?dòng)窗口有權將其邏輯調色板作為前景調色板實(shí)現,非活動(dòng)窗口則只能實(shí)現背景調色板。
2 調色板的創(chuàng )建和實(shí)現
MFC的CPalette類(lèi)對邏輯調色板進(jìn)行了封裝。該類(lèi)的成員函數CreatePalette負責創(chuàng )建邏輯調色板,該函數的聲明為:
BOOL CreatePalette( LPLOGPALETTE lpLogPalette ); //成功則返回TRUE。
參數lpLogPalette是一個(gè)指向LPLOGPALETTE結構的指針,LPLOGPALETTE結構描述了邏輯調色板的內容,該結構的定義為:
typedef struct tagLOGPALETTE {
WORD palVersion; //Windows版本號,一般是0x300
WORD palNumEntries; //調色板中顏色表項的數目
PALETTEENTRY palPalEntry[1]; //每個(gè)表項的顏色和使用方法
} LOGPALETTE;
結構中最重要的成員是PALETTEENTRY數組,數組項的數目由palNumEntries成員指定。PALETTEENTRY結構對調色板的某一個(gè)顏色表項進(jìn)行了描述,該結構的定義為:
typedef struct tagPALETTEENTRY {
BYTE peRed; //紅色的強度(0~255,下同)
BYTE peGreen; //綠色的強度
BYTE peBlue; //藍色的強度
BYTE peFlags;
} PALETTEENTRY;
成員peFlags說(shuō)明了顏色表項的使用方法,在一般應用時(shí)為NULL,若讀者對peFlags的詳細說(shuō)明感興趣,可以查看Visual C++的聯(lián)機幫助。
可以看出,創(chuàng )建調色板的關(guān)鍵是在PALETTEENTRY數組中指定要使用的顏色。這些顏色可以是程序自己指定的特殊顏色,也可以從DIB位圖中載入。邏輯調色板的大小可根據用戶(hù)使用的顏色數來(lái)定,一般不能超過(guò)256個(gè)顏色表項。
CreatePalette只是創(chuàng )建了邏輯調色板,此時(shí)調色板只是一張孤立的顏色表,還不能對系統產(chǎn)生影響。程序必需調用CDC::SelectPalette把邏輯調色板選入到要使用它的設備上下文中,然后調用CDC::RealizePalette把邏輯調色板實(shí)現到系統調色板中。函數的聲明為:
CPalette* SelectPalette( CPalette* pPalette, BOOL bForceBackground );
該函數把指定的調色板選擇到設備上下文中。參數pPalette指向一個(gè)CPalette對象。參數bForceBackground如果是TRUE,那么被選擇的調色板總是作為背景調色板使用,如果bForceBackground是FALSE并且設備上下文是附屬于某個(gè)窗口的,那么當窗口是活動(dòng)窗口或活動(dòng)窗口的子窗口時(shí),被選擇的調色板將作為前景調色板實(shí)現,否則作為背景調色板實(shí)現。如果使用調色板的是一個(gè)內存設備上下文,則該參數被忽略。函數返回設備上下文原來(lái)使用的調色板,若出錯則返回NULL。
UINT RealizePalette( );
該函數把設備上下文中的邏輯調色板實(shí)現到系統調色板中。函數的返回值表明調色板映射表中有多少項被改變了。
如果某一個(gè)窗口要顯示特殊的顏色,那么一般應該在處理WM_PAINT消息時(shí)實(shí)現自己的邏輯調色板。也就是說(shuō),在OnPaint或OnDraw函數中重繪以前,要調用SelectPalette和RealizePalette。如果窗口顯示的顏色比較重要,則在調用SelectPalette時(shí)應該指定bForceBackground參數為FALSE。
前景調色板具有使用顏色的最高優(yōu)先級,它有無(wú)條件占用系統調色板(20種保留顏色除外)的權力,也就是說(shuō),如果需要,前景調色板將覆蓋系統調色板的236個(gè)表項,而不管這些表項是否正被別的窗口使用。背景調色板則無(wú)權破壞系統調色板中的已使用項。
請讀者注意,前景調色板應該是唯一。如果一個(gè)活動(dòng)窗口同時(shí)要實(shí)現幾個(gè)邏輯調色板,那么只能有一個(gè)調色板作為前景調色板實(shí)現,也即在調用CDC::SelectPalette時(shí)只能有一個(gè)bForceBackground被指定為FALSE,其它的bForceBackground必需為T(mén)RUE。通常是把具有輸入焦點(diǎn)的窗口的調色板作為前景調色板實(shí)現,其它窗口只能使用背景調色板。如果活動(dòng)窗口的子窗口全都使用前景調色板,則會(huì )導致程序的死循環(huán)。
提示:請讀者注意區分活動(dòng)窗口和有輸入焦點(diǎn)的窗口。有輸入焦點(diǎn)的窗口要么是活動(dòng)窗口本身,要么是活動(dòng)窗口的子窗口。也就是說(shuō),活動(dòng)窗口不一定具有輸入焦點(diǎn),當活動(dòng)窗口的子窗口獲得輸入焦點(diǎn)時(shí),活動(dòng)窗口就會(huì )失去輸入焦點(diǎn)。
3 使用顏色的三種方法
在調用GDI函數繪圖時(shí),可以用不同的方法來(lái)選擇顏色。Windows用COLORREF數據類(lèi)型來(lái)表示顏色,COLORREF型值的長(cháng)度是4字節,其中最高位字節可以取三種不同的值,分別對應三種使用顏色的方法。表11.1列出了這些不同的取值及其含義。
表1 COLORREF型值的最高位字節的含義
取值 含義
0x00 指定RGB引用。此時(shí)三個(gè)低位字節含有紅、綠、藍色的強度,Windows將抖動(dòng)20種保留的顏色來(lái)匹配指定的顏色,而不管程序是否實(shí)現了自己的調色板。
0x01 指定調色板索引引用。此時(shí)最低位字節含有邏輯調色板的索引,Windows根據該索引在邏輯調色板中找到所需的顏色。
0x02 指定調色板RGB引用。此時(shí)三個(gè)低位字節含有紅、綠、藍色的強度,Windows會(huì )在邏輯調色板中找到最匹配的顏色。
為了方便用戶(hù)的使用,Windows提供了三個(gè)宏來(lái)構建三種不同的COLORREF數據,它們是:
COLORREF RGB(BYTE bRed,BYTE bGreen,BYTE bBlue); //RGB引用
COLORREF PALETTEINDEX(WORD wPaletteIndex); //調色板索引引用
COLORREF PALETTERGB(BYTE bRed,BYTE bGreen, //調色板RGB引用
BYTE bBlue);
例如,我們可以用上述三種方法來(lái)指定刷子的顏色。下面的代碼用系統調色板中的紅色建立一個(gè)刷子:
CBrush brush;
brush.CreateSolidBrush(RGB(255,0,0));
pDC->SelectObject(&brush);
下面的代碼用邏輯調色板的索引2中的顏色來(lái)創(chuàng )建一個(gè)刷子:
pDC->SelectPalette(&m_Palette,FALSE);
pDC->RealizePalette( );
CBrush brush;
brush.CreateSolidBrush(PALETTEINDEX(2));
pDC->SelectObject(&brush);
下面的代碼用邏輯調色板中最匹配的深灰色來(lái)創(chuàng )建一個(gè)刷子:
pDC->SelectPalette(&m_Palette,FALSE);
pDC->RealizePalette( );
CBrush brush;
brush.CreateSolidBrush(PALETTERGB(20,20,20));
pDC->SelectObject(&brush);
4 與系統調色板有關(guān)的消息
為了協(xié)調各個(gè)窗口對系統調色板的使用,Windows在必要的時(shí)侯會(huì )向頂層窗口和重疊窗口發(fā)送消息WM_QUERYNEWPALETTE和WM_PALETTECHANGED。
當某一頂層或重疊窗口(如主框架窗口)被激活時(shí),會(huì )收到WM_QUERYNEWPALETTE消息,在窗口創(chuàng )建之初也會(huì )收到該消息,該消息先于WM_PAINT消息到達窗口。如果活動(dòng)窗口要使用特殊的顏色,則在收到該消息時(shí)應該實(shí)現自己的邏輯調色板并重繪窗口。如果窗口實(shí)現了邏輯調色板,那么WM_QUERYNEWPALETTE消息的處理函數應返回TRUE。通常窗口在收到該消息后應該為有輸入焦點(diǎn)的窗口(如視圖)實(shí)現前景調色板,但如果程序覺(jué)得它顯示的顏色并不重要,那么在收到該消息后可以把邏輯調色板作為背景調色板實(shí)現(指定CDC::SelectPalette函數的bForceBackground參數為T(mén)RUE),這樣程序就失去了使用系統調色板的最高優(yōu)先權。
當活動(dòng)窗口實(shí)現其前景調色板并改變了系統調色板時(shí),Windows會(huì )向包括活動(dòng)窗口在內的所有的頂層窗口和重疊窗口發(fā)送WM_PALETTECHANGED消息,在該消息的wParam參數中包含了改變系統調色板的窗口的句柄。其它窗口如果使用了自己的邏輯調色板,那么應該重新實(shí)現其邏輯調色板,并重繪窗口。這是因為系統調色板已經(jīng)被改變了,必需重新建立調色板映射表并重繪,否則可能會(huì )顯示錯誤的顏色。當然,非活動(dòng)窗口只能使用背景調色板,所以顯示的顏色肯定沒(méi)有在前臺的時(shí)侯好。要注意只有在活動(dòng)窗口實(shí)現了前景調色板且改變了系統調色板時(shí),才會(huì )產(chǎn)生WM_PALETTECHANGED消息。也就是說(shuō),如果窗口在調用CDC::SelectPalette時(shí)指定bForceBackground參數為T(mén)RUE,那么是不會(huì )產(chǎn)生WM_PALETTECHANGED消息。
總之,WM_QUERYNEWPALETTE消息為活動(dòng)窗口提供了實(shí)現前景調色板的機會(huì ),而WM_PALETTECHANGED消息為窗口提供了適應系統調色板變化的機會(huì )。
需要指出的是,子窗口是收不到與調色板有關(guān)的消息的。因此,如果子窗口(如視圖)要使用自己的邏輯調色板,那么頂層窗口或重疊窗口應該及時(shí)通知子窗口與調色板有關(guān)的消息。
5 具體實(shí)例
現在讓我們來(lái)看一個(gè)使用調色板的演示程序。該程序名為T(mén)estPal,如圖11.3所示,該程序顯示了兩組紅色方塊,每組方塊都是16×16共256個(gè)。左邊的這組方塊是用邏輯調色板畫(huà)的,紅色的強度從0到255遞增,作為對比,在右邊用RGB引用畫(huà)出了256個(gè)遞增的紅色方塊。讀者可以對比這兩組方塊的顏色質(zhì)量,以體會(huì )調色板索引引用和RGB引用的區別。該程序也著(zhù)重向讀者演示了處理調色板消息的方法。
圖3 TestPal程序
首先,請讀者用AppWizard建立一個(gè)名為T(mén)estPal的MFC單文擋應用程序。然后,用ClassWizard為CMainFrame類(lèi)加入WM_QUERYNEWPALETTE和WM_PALETTECHANGED消息的處理函數,使用缺省的函數名。接著(zhù),在TestPal.h文件中類(lèi)CTestPalApp的定義前加入下面一行:
#define WM_DOREALIZE WM_USER+200
當收到調色板消息時(shí),主框架窗口會(huì )發(fā)送用戶(hù)定義的WM_DOREALIZE消息通知視圖。
最后,請讀者按清單11.1和11.2修改程序。
清單11.1 CMainFrame類(lèi)的部分代碼
BOOL CMainFrame::OnQueryNewPalette()
{
// TODO: Add your message handler code here and/or call default
GetActiveView()->SendMessage(WM_DOREALIZE);
return TRUE; //返回TRUE表明實(shí)現了邏輯調色板
}
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類(lèi)的部分代碼
// 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
GetActiveView()->SendMessage(WM_DOREALIZE);
return TRUE; //返回TRUE表明實(shí)現了邏輯調色板
}
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類(lèi)的部分代碼
// 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的構造函數中創(chuàng )建了一個(gè)含有256個(gè)遞增紅色的邏輯調色板。
當變?yōu)榛顒?dòng)窗口以及窗口創(chuàng )建時(shí),TestPal程序的主框架窗口都會(huì )收到WM_QUERYNEWPALETTE消息,該消息的處理函數OnQueryNewPalette負責發(fā)送WM_DOREALIZE消息通知視圖, 并返回TRUE以表明活動(dòng)窗口實(shí)現了邏輯調色板。WM_DOREALIZE消息的處理函數CTestPalView::OnDoRealize為視圖實(shí)現一個(gè)前景調色板,該函數中有一個(gè)判斷語(yǔ)句可提高程序運行的效率:如果CDC::RealizePalette返回值大于零,則說(shuō)明調色板映射表發(fā)生了變化,此時(shí)必須刷新視圖,否則制圖中的顏色將失真。如果RealizePalette返回零則說(shuō)明調色板映射沒(méi)有變化,這時(shí)就沒(méi)有必要刷新視圖。
無(wú)論是TestPal還是別的應用程序在實(shí)現前景調色板并改變了系統調色板時(shí),TestPal程序的主框架窗口都會(huì )收到WM_PALETTECHANGED消息。請注意該消息的處理函數CMainFrame::OnPaletteChanged有一個(gè)pFocusWnd參數,該參數表明是哪一個(gè)窗口改變了系統調色板。函數用pFocusWnd來(lái)判斷,如果是別的應用程序實(shí)現了前景調色板,則通知視圖調用OnDoRealize實(shí)現其邏輯調色板,注意雖然CDC::SelectPalette的bForceBackground參數是FALSE,但這時(shí)視圖的邏輯調色板是作為背景調色板實(shí)現的。如果是TestPal自己的視圖實(shí)現了前景調色板,則沒(méi)有必要調用OnDoRealize。
請讀者將Windows當前的顯示模式設置為256色,然后編譯并運行TestPal,對比一下RGB引用與調色板索引引用的效果,讀者不難發(fā)現左邊用調色板索引引用輸出的顏色比右邊好的多。通過(guò)該程序我們可以看出,即使在系統調色板中已實(shí)現了豐富的紅色的情況下,RGB引用得到的紅色仍然是20種保留顏色的抖動(dòng)色。
讀者可以打開(kāi)Windows的畫(huà)筆程序,并在該程序中打開(kāi)一幅256色的位圖(如Windows目錄下的Forest.bmp)。在畫(huà)筆和TestPal程序之間來(lái)回切換,讀者可以看到,由于兩個(gè)應用程序都正確的處理了調色板消息,在前臺的應用程序總是具有最好的顏色顯示,而后臺程序的顏色雖然有些失真,但還比較令人滿(mǎn)意。
需要指出的是,TestPal程序只使用了一個(gè)邏輯調色板,所以它處理調色板消息的方法比較簡(jiǎn)單。如果程序要用到多個(gè)邏輯調色板,那么就需要采取一些新措施來(lái)保證只有一個(gè)邏輯調色板作為前景調色板使用。在11.4節讀者可以看到使用多個(gè)邏輯調色板時(shí)的處理方法。
聯(lián)系客服