|
11.4 与设备无关的位图(DIB)
DIB(Device-indepentent bitmap)的与设备无关性主要体现在以下两个方面:
由于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描述了位图的大小,颜色模式和调色板等各种属性,其定义为
RGBQUAD结构用来描述颜色,其定义为
注意,RGBQUAD结构中的颜色顺序是BGR,而不是平常的RGB。 BITMAPINFOHEADER结构包含了DIB的各种信息,其定义为
与DDB不同,DIB的字节数组是从图象的最下面一行开始的逐行向上存储的,也即等于把图象倒过来然后在逐行扫描。另外,字节数组中每个扫描行的字节数必需是4的倍数,如果不足要用0补齐。 DIB可以存储在*.BMP或*.DIB文件中。DIB文件是以BITMAPFILEHEADER结构开头的,该结构的定义为
紧随该结构的是一个BITMAPINFOHEADER结构,然后是RGBQUAD结构组成的颜色表(如果有的话),文件最后存储的是DIB的像素阵列。 DIB的颜色信息储存在自己的颜色表中,程序一般要根据颜色表为DIB创建逻辑调色板。在输出一幅DIB之前,程序应该将其逻辑调色板选入到相关的设备上下文中并实现到系统调色板中,然后再调用相关的GDI函数(如::SetDIBitsToDevice或::StretchDIBits)输出DIB。在输出过程中,GDI函数会把DIB转换成DDB,这项工作主要包括以下两步:
11.4.2 编写DIB类 由于MFC未提供DIB类,用户在使用DIB时将面临繁重的Windows API编程任务。幸运的是,Visual C++提供了一个较高层次的API,简化了DIB的使用。这些API函数实际上是由MFC的DibLook例程提供的,它们位于DibLook目录下的dibapi.cpp、myfile.cpp和dibapi.h文件中,主要包括:
如果读者对这些函数的内部细节感兴趣,那么可以研究一下dibapi.cpp和myfile.cpp文件,但要做好吃苦的准备。 即使利用上述API,编写使用DIB的程序仍然不是很轻松。为了满足读者的要求,笔者编写了一个名为CDib的较简单的DIB类,该类是基于上述API的,它的主要成员函数包括:
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函数来完成。
本教程幼髡? 作者: 不详, 来源: Visual C++王朝 |