调色板原理
調色板原理 & 編程
? 邏輯調色板結構LOGPALETTE,該結構定義如下:
typedef struct tagLOGPALETTE
{
WORD palVersion; //調色板的板本號,應該指定該值為0x300;
WORD palNumEntries;//調色板中的表項數,對于灰度圖像該值為256;
PALETEENTRY palPalEntry[1];//調色板中的顏色表項,由于該表項的數目不一定,所以這里數組長度定義為1,灰度圖像對應的該數組的長度為 ???????????????????????????????????????????????????? 256;
} LOGPALETTE;
顏色表項結構PALETTEENTRY定義了調色板中的每一個顏色表項的顏色和使用方式,定義如下:
typedef struct tagPALETTEENTRY
{
BYTE peRed; //R分量值;
BYTE peGreen; //G分量值;
BYTE peBlue; //B分量值;
BYTE peFlags; // 該顏色被使用的方式,一般情況下設為"0";
}PALETTEENTRY;
Windows系統使用調色板管理器來管理與調色板有關的操作,通常活動窗口的調色板即是當前系統調色板,所有的非活動窗口都必須按照此系統調色板來顯示自己的顏色,此時調色板管理器將自動的用系統調色板中的最近似顏色來映射相應的顯示顏色。如果窗口或應用程序按自己的調色板顯示顏色,就必須將自己的調色板載入到系統調色板中, 這種操作叫作實現調色板,實現調色板包括兩個步驟: 1.? 首先將調色板選擇到設備上下文中,可以通過CDC::SelectPalette() 選入設備上下文 2.? 然后在設備上下文中實現調色板,????可以通過 CDC::RealizePalette()實現設備調色板。 ? 在實現調色板的過程中,通過在框架類中處理Windows定義的消息WM_QUERYNEWPALETTE 、WM_PALETTECHANGED及視圖類中處理自定義消息WM_DOREALIZE(該消息在主框架窗口定義如下:#define WM_REALIZEPAL (WM_USER+101))來實現調色板的操作。當系統需要處理調色板的變化時,將向程序的主窗口發送WM_QUERYNEWPALETTE 、WM_PALETTECHANGED,例如當某一窗口即將激活時,主框架窗口將收到WM_QUERYNEWPALETTE消息,通知該窗口將要收到輸入焦點,給它一次機會實現其自身的邏輯調色板;當系統調色板改變后,主框架窗口將收到WM_PALETTECHANGED消息,通知其它窗口系統調色板已經改變,此時每一窗口都應該實現其邏輯調色板,重畫客戶區。
由于上述的調色板變更消息是發往主框架窗口的,所以我們只能在主窗口中響應這兩個消息,然后由主框架窗口通知各個視窗,使得程序激活時能自動裝載自己的調色板。我們定義的用戶消息WM_REALIZEPAL用于主框架窗口通知視窗它已經收到調色板變更消息,視窗應該協調其調色板。下面我們給出了各個消息的響應處理函數的具體實現代碼和注釋:
//
void CMainFrame::OnPaletteChanged(CWnd* pFocusWnd)
{ //總實現活動視的調色板
CMDIFrameWnd::OnPaletteChanged(pFocusWnd);
CMDIChildWnd* pMDIChildWnd = MDIGetActive();//得到活動的子窗口指針;
if (pMDIChildWnd == NULL)
return
CView* pView = pMDIChildWnd->GetActiveView();//得到視圖的指針;
ASSERT(pView != NULL);
SendMessageToDescendants(WM_DOREALIZE, (WPARAM)pView->m_hWnd); //通知所有子窗口系統調色板已改變
}
BOOL CMainFrame::OnQueryNewPalette()//提供實現系統調色板的機會
{
// 實現活動視的調色板
CMDIChildWnd* pMDIChildWnd = MDIGetActive();//得到活動的子窗口指針;
if (pMDIChildWnd == NULL)
return FALSE;//no active MDI child frame (no new palette)
CView* pView = pMDIChildWnd->GetActiveView();//得到活動子窗口的視圖指針;
ASSERT(pView != NULL);
pView->SendMessage(WM_DOREALIZE, (WPARAM)pView->m_hWnd);//通知活動視圖實現系統調色板
return TRUE;
}
/
BOOL CDibView::OnDoRealize(WPARAM wParam, LPARAM)//實現系統調色板
{
ASSERT(wParam != NULL);
CDibDoc* pDoc = GetDocument();
if (pDoc->m_hDIB == NULL)
return FALSE; // must be a new document
CPalette* pPal = pDoc->m_palDIB;
//調色板的顏色表數據在InitDIBData()函數中實現
if (pPal != NULL)
{
CMainFrame* pAppFrame = (CMainFrame*) AfxGetApp()->m_pMainWnd;//得到程序的主框架指針;
ASSERT_KINDOF(CMainFrame, pAppFrame);
CClientDC appDC(pAppFrame);//獲取主框架的設備上下文;
CPalette* oldPalette = appDC.SelectPalette(pPal, ((HWND)wParam) != m_hWnd);
if (oldPalette != NULL)
{
UINT nColorsChanged = appDC.RealizePalette();//實現系統調色板
if (nColorsChanged > 0)
pDoc->UpdateAllViews(NULL);//更新視圖
appDC.SelectPalette(oldPalette, TRUE);?//將原系統調色板置為背景調色板
}
else
{
TRACE0("\\tSelectPalette failed in");
}
return TRUE;
}
注:在調用API函數顯示位圖時,不要忘記設置邏輯調色板,即"背景"調色板,否則位圖將無法正確顯示,讀者可以從后面的顯示部分的實現看出我們在顯示時實現了邏輯調色板。上述的處理相對來說比較繁瑣復雜,可能對于初學者來說也比較難于理解,所以如果我們的程序僅僅限于處理灰度圖象,可以采用另外一種相對簡單的辦法,即在文檔類的初始化階段定義一個灰度調色板,然后在設備上下文中實現它,這樣作的好處是在度取灰度位圖時可以不再考慮文件中的顏色表信息,提高了文件讀取速度,筆者在開發一個基于機器視覺的項目時采用的就是這種方法,取的了比較滿意的效果。首先定義一個指向邏輯顏色表結構 LOGPALETTE的指針pPal,填充該指針,然后將該指針與調色板指針聯系起來,該方法的具體實現如下:
/
CDibDoc::CDibDoc()
{
LOGPALETTE *Pal;
Pal=new LOGPALETTE;
m_palDIB=new Cpalette;
pPal->palVersion=0x300;
pPal->palNumEntries=256;
for(int i=0;i<256;i++)
{//每個顏色表項的R、G、B值相等,并且各個值從"0"到"255"序列展開;
Pal->palPalentry[i].peRed=i;
pPal->palPalentry[i].peGreen=i;
pPal->palPalentry[i].peBlue=i;
pPal->palPalentry[i].peFlags=0;
}
m_palDIB->CreatePalette(pPal);
} 2.??調色板的原理 PC機上顯示的圖象是由一個個像素組成的,每個像素都有自己的顏色屬性。在PC的顯示系統中,像素的顏色是基于RGB模型的,每一個像素的顏色由紅(B)、綠(G)、藍(B)三原色組合而成。每種原色用8位表示,這樣一個的顏色就是24位的。以此推算,PC的SVGA適配器可以同時顯示224約一千六百多萬種顏色。24位的顏色通常被稱作真彩色,用真彩色顯示的圖象可達到十分逼真的效果。但是,真彩色的顯示需要大量的視頻內存,一幅640×480的真彩色圖象需要約1MB的視頻內存。由于數據量大增,顯示真彩色會使系統的整體性能迅速下降。
3.?調色板的創建和實現 ? 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。 通常是把具有輸入焦點的窗口的調色板作為前景調色板實現,其它窗口只能使用背景調色板。 如果活動窗口的子窗口全都使用前景調色板,則會導致程序的死循環。
4.?使用顏色的三種方法 ? 在調用GDI函數繪圖時,可以用不同的方法來選擇顏色。Windows用COLORREF數據類型來表示顏色,COLORREF型值的長度是4字節,其中最高位字節可以取三種不同的值,分別對應三種使用顏色的方法。表11.1列出了這些不同的取值及其含義。 COLORREF型值的最高位字節的含義????
? 為了方便用戶的使用,Windows提供了三個宏來構建三種不同的COLORREF數據,它們是: COLORREF RGB(BYTE bRed,BYTE bGreen,BYTE bBlue); //RGB引用 COLORREF PALETTEINDEX(WORD wPaletteIndex);??? //調色板索引引用 COLORREF PALETTERGB(BYTE bRed,BYTE bGreen,?BYTE bBlue);??? //調色板RGB引用
? 例如,我們可以用上述三種方法來指定刷子的顏色: (1). 調用系統調色板中的紅色建立一個刷子: CBrush brush; brush.CreateSolidBrush(RGB(255,0,0)); pDC->SelectObject(&brush); (2). 調用邏輯調色板的索引2中的顏色來創建一個刷子: pDC->SelectPalette(&m_Palette,FALSE); pDC->RealizePalette( ); CBrush brush; brush.CreateSolidBrush(PALETTEINDEX(2)); pDC->SelectObject(&brush); (3).調用邏輯調色板中最匹配的深灰色來創建一個刷子: pDC->SelectPalette(&m_Palette,FALSE); pDC->RealizePalette( ); CBrush brush; brush.CreateSolidBrush(PALETTERGB(20,20,20)); pDC->SelectObject(&brush); ? 5.?與系統調色板有關的消息 ? ?為了協調各個窗口對系統調色板的使用,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消息為窗口提供了適應系統調色板變化的機會。 需要指出的是,子窗口是收不到與調色板有關的消息的。因此,如果子窗口(如視圖)要使用自己的邏輯調色板,那么頂層窗口或重疊窗口應該及時通知子窗口與調色板有關的消息。 ? 6.?具體實例 現在讓我們來看一個使用調色板的演示程序。該程序名為TestPal,如圖11.3所示,該程序顯示了兩組紅色方塊,每組方塊都是16×16共256個。左邊的這組方塊是用邏輯調色板畫的,紅色的強度從0到255遞增,作為對比,在右邊用RGB引用畫出了256個遞增的紅色方塊。讀者可以對比這兩組方塊的顏色質量,以體會調色板索引引用和RGB引用的區別。該程序也著重向讀者演示了處理調色板消息的方法。 ? 首先,請讀者用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節讀者可以看到使用多個邏輯調色板時的處理方法。
typedef struct tagLOGPALETTE
{
WORD palVersion; //調色板的板本號,應該指定該值為0x300;
WORD palNumEntries;//調色板中的表項數,對于灰度圖像該值為256;
PALETEENTRY palPalEntry[1];//調色板中的顏色表項,由于該表項的數目不一定,所以這里數組長度定義為1,灰度圖像對應的該數組的長度為 ???????????????????????????????????????????????????? 256;
} LOGPALETTE;
顏色表項結構PALETTEENTRY定義了調色板中的每一個顏色表項的顏色和使用方式,定義如下:
typedef struct tagPALETTEENTRY
{
BYTE peRed; //R分量值;
BYTE peGreen; //G分量值;
BYTE peBlue; //B分量值;
BYTE peFlags; // 該顏色被使用的方式,一般情況下設為"0";
}PALETTEENTRY;
Windows系統使用調色板管理器來管理與調色板有關的操作,通常活動窗口的調色板即是當前系統調色板,所有的非活動窗口都必須按照此系統調色板來顯示自己的顏色,此時調色板管理器將自動的用系統調色板中的最近似顏色來映射相應的顯示顏色。如果窗口或應用程序按自己的調色板顯示顏色,就必須將自己的調色板載入到系統調色板中, 這種操作叫作實現調色板,實現調色板包括兩個步驟: 1.? 首先將調色板選擇到設備上下文中,可以通過CDC::SelectPalette() 選入設備上下文 2.? 然后在設備上下文中實現調色板,????可以通過 CDC::RealizePalette()實現設備調色板。 ? 在實現調色板的過程中,通過在框架類中處理Windows定義的消息WM_QUERYNEWPALETTE 、WM_PALETTECHANGED及視圖類中處理自定義消息WM_DOREALIZE(該消息在主框架窗口定義如下:#define WM_REALIZEPAL (WM_USER+101))來實現調色板的操作。當系統需要處理調色板的變化時,將向程序的主窗口發送WM_QUERYNEWPALETTE 、WM_PALETTECHANGED,例如當某一窗口即將激活時,主框架窗口將收到WM_QUERYNEWPALETTE消息,通知該窗口將要收到輸入焦點,給它一次機會實現其自身的邏輯調色板;當系統調色板改變后,主框架窗口將收到WM_PALETTECHANGED消息,通知其它窗口系統調色板已經改變,此時每一窗口都應該實現其邏輯調色板,重畫客戶區。
由于上述的調色板變更消息是發往主框架窗口的,所以我們只能在主窗口中響應這兩個消息,然后由主框架窗口通知各個視窗,使得程序激活時能自動裝載自己的調色板。我們定義的用戶消息WM_REALIZEPAL用于主框架窗口通知視窗它已經收到調色板變更消息,視窗應該協調其調色板。下面我們給出了各個消息的響應處理函數的具體實現代碼和注釋:
//
void CMainFrame::OnPaletteChanged(CWnd* pFocusWnd)
{ //總實現活動視的調色板
CMDIFrameWnd::OnPaletteChanged(pFocusWnd);
CMDIChildWnd* pMDIChildWnd = MDIGetActive();//得到活動的子窗口指針;
if (pMDIChildWnd == NULL)
return
CView* pView = pMDIChildWnd->GetActiveView();//得到視圖的指針;
ASSERT(pView != NULL);
SendMessageToDescendants(WM_DOREALIZE, (WPARAM)pView->m_hWnd); //通知所有子窗口系統調色板已改變
}
BOOL CMainFrame::OnQueryNewPalette()//提供實現系統調色板的機會
{
// 實現活動視的調色板
CMDIChildWnd* pMDIChildWnd = MDIGetActive();//得到活動的子窗口指針;
if (pMDIChildWnd == NULL)
return FALSE;//no active MDI child frame (no new palette)
CView* pView = pMDIChildWnd->GetActiveView();//得到活動子窗口的視圖指針;
ASSERT(pView != NULL);
pView->SendMessage(WM_DOREALIZE, (WPARAM)pView->m_hWnd);//通知活動視圖實現系統調色板
return TRUE;
}
/
BOOL CDibView::OnDoRealize(WPARAM wParam, LPARAM)//實現系統調色板
{
ASSERT(wParam != NULL);
CDibDoc* pDoc = GetDocument();
if (pDoc->m_hDIB == NULL)
return FALSE; // must be a new document
CPalette* pPal = pDoc->m_palDIB;
//調色板的顏色表數據在InitDIBData()函數中實現
if (pPal != NULL)
{
CMainFrame* pAppFrame = (CMainFrame*) AfxGetApp()->m_pMainWnd;//得到程序的主框架指針;
ASSERT_KINDOF(CMainFrame, pAppFrame);
CClientDC appDC(pAppFrame);//獲取主框架的設備上下文;
CPalette* oldPalette = appDC.SelectPalette(pPal, ((HWND)wParam) != m_hWnd);
if (oldPalette != NULL)
{
UINT nColorsChanged = appDC.RealizePalette();//實現系統調色板
if (nColorsChanged > 0)
pDoc->UpdateAllViews(NULL);//更新視圖
appDC.SelectPalette(oldPalette, TRUE);?//將原系統調色板置為背景調色板
}
else
{
TRACE0("\\tSelectPalette failed in");
}
return TRUE;
}
注:在調用API函數顯示位圖時,不要忘記設置邏輯調色板,即"背景"調色板,否則位圖將無法正確顯示,讀者可以從后面的顯示部分的實現看出我們在顯示時實現了邏輯調色板。上述的處理相對來說比較繁瑣復雜,可能對于初學者來說也比較難于理解,所以如果我們的程序僅僅限于處理灰度圖象,可以采用另外一種相對簡單的辦法,即在文檔類的初始化階段定義一個灰度調色板,然后在設備上下文中實現它,這樣作的好處是在度取灰度位圖時可以不再考慮文件中的顏色表信息,提高了文件讀取速度,筆者在開發一個基于機器視覺的項目時采用的就是這種方法,取的了比較滿意的效果。首先定義一個指向邏輯顏色表結構 LOGPALETTE的指針pPal,填充該指針,然后將該指針與調色板指針聯系起來,該方法的具體實現如下:
/
CDibDoc::CDibDoc()
{
LOGPALETTE *Pal;
Pal=new LOGPALETTE;
m_palDIB=new Cpalette;
pPal->palVersion=0x300;
pPal->palNumEntries=256;
for(int i=0;i<256;i++)
{//每個顏色表項的R、G、B值相等,并且各個值從"0"到"255"序列展開;
Pal->palPalentry[i].peRed=i;
pPal->palPalentry[i].peGreen=i;
pPal->palPalentry[i].peBlue=i;
pPal->palPalentry[i].peFlags=0;
}
m_palDIB->CreatePalette(pPal);
} 2.??調色板的原理 PC機上顯示的圖象是由一個個像素組成的,每個像素都有自己的顏色屬性。在PC的顯示系統中,像素的顏色是基于RGB模型的,每一個像素的顏色由紅(B)、綠(G)、藍(B)三原色組合而成。每種原色用8位表示,這樣一個的顏色就是24位的。以此推算,PC的SVGA適配器可以同時顯示224約一千六百多萬種顏色。24位的顏色通常被稱作真彩色,用真彩色顯示的圖象可達到十分逼真的效果。但是,真彩色的顯示需要大量的視頻內存,一幅640×480的真彩色圖象需要約1MB的視頻內存。由于數據量大增,顯示真彩色會使系統的整體性能迅速下降。
?
?如圖為一個256色顯示模式中的調色板的工作原理 ? ?圖11.1 調色板工作原理?
為了解決這個問題,計算機使用調色板來限制顏色的數目。調色板實際上是一個有256個表項的RGB顏色表,顏色表的每項是一個24位的RGB顏色值。使用調色板時,在視頻內存中存儲的不是的24位顏色值,而是調色板的4位或8位的索引。這樣一來,顯示器可同時顯示的顏色被限制在256色以內,對系統資源的耗費大大降低了(不同時刻可以采用不同的調色板,因此可以擴展總共可以顯示的顏色)。 顯示器可以被設置成16、256、64K、真彩色等顯示模式,前兩種模式需要調色板。在16或256色模式下,程序必須將想要顯示的顏色正確地設置到調色板中,這樣才能顯示出預期的顏色。圖11.1顯示了調色板的工作原理。使用調色板的一個好處是不必改變視頻內存中的值,只需改變調色板的顏色項就可快速地改變一幅圖象的顏色或灰度。 在DOS中,調色板的使用不會有什么問題。由于DOS是一個單任務操作系統,一次只能運行一個程序,因此程序可以獨占調色板。在Windows環境下,情況就不那么簡單了。Windows是一個多任務操作系統,可以同時運行多個程序。如果有幾個程序都要設置調色板,就有可能產生沖突。為了避免這種沖突,Windows使用邏輯調色板來作為使用顏色的應用程序和系統調色板(物理調色板)之間的緩沖。? ? ? ? 圖11.2 調色板的映射關系 ? 在Windows中,應用程序是通過一個或多個邏輯調色板來使用系統調色板(物理調色板)。 在256色系統調色板中,Windows保留了20種顏色作為靜態顏色,這些顏色用作顯示Windows界面,應用程序一般不能改變。 缺省的系統調色板只包含這20種靜態顏色,調色板的其它項為空。應用程序要想使用新的顏色,必須將包含有所需顏色的邏輯調色板實現到系統調色板中。在實現過程中 1. Windows首先將邏輯調色板中的項與系統調色板中的項作完全匹配,對于邏輯調色板中不能完全匹配的項,Windows將其加入到系統調色板的空白項中,系統調色板總共有236個空白項可供使用, 1. 若系統調色板已滿,則Windows將邏輯調色板的剩余項匹配到系統調色板中盡可能接近的顏色上。 每個設備上下文都擁有一個邏輯調色板,缺省的邏輯調色板只有20種保留顏色,如果要使用新的顏色,則應該創建一個新的邏輯調色板并將其選入到設備上下文中。但光這樣還不能使用新顏色,程序只有把設備上下文中的邏輯調色板實現到系統調色板中,新的顏色才能實現。在邏輯調色板被實現到系統調色板時,Windows會建立一個調色板映射表。當設備上下文用邏輯調色板中的顏色繪圖時,GDI繪圖函數會查詢調色板映射表以把像素值從邏輯調色板的索引轉換成系統調色板的索引,這樣當像素被輸出到視頻內存中時就具有了正確的顏色值。圖11.2說明了這種映射關系,從圖中讀者可以體會到邏輯調色板的緩沖作用。在該圖中,GDI繪圖函數使用邏輯調色板的索引1中的顏色來繪圖,通過查詢調色板映射表,得知系統調色板中的第23號索引與其完全匹配,這樣實際輸出到視頻內存中的像素值是23。注意圖中還演示了顏色的不完全匹配,即邏輯調色板中的索引15和系統調色板中的索引46。 每個要使用額外顏色的窗口都會實現自己的邏輯調色板,邏輯調色板中的每種顏色在系統調色板中都有相同或相近的匹配。調色板的實現優先權越高,匹配的精度也就越高。Windows規定,活動窗口的邏輯調色板(如果有的話)具有最高的實現優先權。這是因為活動窗口是當前與用戶交互的窗口,應該保證其有最佳的顏色顯示。非活動窗口的優先權是按Z順序自上到下確定的(Z順序就是重疊窗口的重疊順序)。活動窗口有權將其邏輯調色板作為前景調色板實現,非活動窗口則只能實現背景調色板。| 提示:術語活動窗口(Active window)或前臺窗口(Foreground window)是指當前與用戶交互的窗口,活動窗口的頂端的標題條呈高亮顯示,而非活動窗口的標題條則是灰色的。活動窗口肯定是一個頂層窗口(Top-level window),頂層窗口是指沒有父窗口或父窗口是桌面窗口的窗口,這種窗口一般都有標題和邊框,主要包括框架窗口和對話框。術語重疊窗口是指作為應用程序主窗口的窗口,我們可以把對話框看成是一種特殊的重疊式窗口。 |
該函數把指定的調色板選擇到設備上下文中。參數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。 通常是把具有輸入焦點的窗口的調色板作為前景調色板實現,其它窗口只能使用背景調色板。 如果活動窗口的子窗口全都使用前景調色板,則會導致程序的死循環。
| 提示:請讀者注意區分活動窗口和有輸入焦點的窗口。有輸入焦點的窗口要么是活動窗口本身,要么是活動窗口的子窗口。也就是說,活動窗口不一定具有輸入焦點,當活動窗口的子窗口獲得輸入焦點時,活動窗口就會失去輸入焦點。 |
| 取值 | 含義 |
| 0x00 | 指定RGB引用。此時三個低位字節含有紅、綠、藍色的強度,Windows將抖動20種保留的顏色來匹配指定的顏色,而不管程序是否實現了自己的調色板。 |
| 0x01 | 指定調色板索引引用。此時最低位字節含有邏輯調色板的索引,Windows根據該索引在邏輯調色板中找到所需的顏色。 |
| 0x02 | 指定調色板RGB引用。此時三個低位字節含有紅、綠、藍色的強度,Windows會在邏輯調色板中找到最匹配的顏色。 |
? 例如,我們可以用上述三種方法來指定刷子的顏色: (1). 調用系統調色板中的紅色建立一個刷子: CBrush brush; brush.CreateSolidBrush(RGB(255,0,0)); pDC->SelectObject(&brush); (2). 調用邏輯調色板的索引2中的顏色來創建一個刷子: pDC->SelectPalette(&m_Palette,FALSE); pDC->RealizePalette( ); CBrush brush; brush.CreateSolidBrush(PALETTEINDEX(2)); pDC->SelectObject(&brush); (3).調用邏輯調色板中最匹配的深灰色來創建一個刷子: pDC->SelectPalette(&m_Palette,FALSE); pDC->RealizePalette( ); CBrush brush; brush.CreateSolidBrush(PALETTERGB(20,20,20)); pDC->SelectObject(&brush); ? 5.?與系統調色板有關的消息 ? ?為了協調各個窗口對系統調色板的使用,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消息為窗口提供了適應系統調色板變化的機會。 需要指出的是,子窗口是收不到與調色板有關的消息的。因此,如果子窗口(如視圖)要使用自己的邏輯調色板,那么頂層窗口或重疊窗口應該及時通知子窗口與調色板有關的消息。 ? 6.?具體實例 現在讓我們來看一個使用調色板的演示程序。該程序名為TestPal,如圖11.3所示,該程序顯示了兩組紅色方塊,每組方塊都是16×16共256個。左邊的這組方塊是用邏輯調色板畫的,紅色的強度從0到255遞增,作為對比,在右邊用RGB引用畫出了256個遞增的紅色方塊。讀者可以對比這兩組方塊的顏色質量,以體會調色板索引引用和RGB引用的區別。該程序也著重向讀者演示了處理調色板消息的方法。 ? 首先,請讀者用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節讀者可以看到使用多個邏輯調色板時的處理方法。
總結
- 上一篇: sizeof小览
- 下一篇: 这才是程序员该有的桌面壁纸!