與裝置無關的點陣圖(DIB)
阿新 • • 發佈:2019-01-24
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;
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函式來完成。