11.4 与设备无关的位图(DIB) 的与设备无关性主要体现在以下两个方面:
DIB(Device-indepentent bitmap)
-
DIB
的颜色模式与设备无关。例如,一个256色的DIB即可以在真彩色显示模式下使用,也可以在16色模式下使用。 -
256
色以下(包括256色)的DIB拥有自己的颜色表,像素的颜色独立于系统调色板。
由于DIB不依赖于具体设备,因此可以用来永久性地保存图象。DIB一般是以*.BMP文件的形式保存在磁盘中的,有时也会保存在*.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;
结构用来描述颜色,其定义为typedef struct tagRGBQUAD {
BYTE rgbBlue; //
蓝色的强度BYTE rgbGreen; //
绿色的强度BYTE rgbRed; //
红色的强度BYTE rgbReserved; //
} RGBQUAD;
保留字节,为 0 注意, RGBQUAD 结构中的颜色顺序是 BGR ,而不是平常的 RGB 。 结构包含了 DIB 的各种信息,其定义为typedef struct tagBITMAPINFOHEADER{
DWORD biSize; //
该结构的大小LONG biWidth; //
LONG biHeight; //
WORD biPlanes; //
WORD biBitCount //
DWORD biCompression; //
DWORD biSizeImage; //
LONG biXPelsPerMeter; //
位图的宽度 ( 以像素为单位 ) 位图的高度 ( 以像素为单位 ) 必须为 1 每个像素的位数 (1 、 4 、 8 、 16 、 24 或 32) 压缩方式,一般为 0 或 BI_RGB ( 未压缩 ) 以字节为单位的图象大小 ( 仅用于压缩位图 ) 以目标设备每米的像素数来说明位图的水平分辨率LONG biYPelsPerMeter; //
DWORD biClrUsed; /*
DWORD biClrImportant; //
颜色表的颜色数,若为0则位图使用由biBitCount指定的最大颜色数*/重要颜色的数目,若该值为0则所有颜色都重要 以目标设备每米的像素数来说明位图的垂直分辨率} BITMAPINFOHEADER;
与 DDB 不同, DIB 的字节数组是从图象的最下面一行开始的逐行向上存储的,也即等于把图象倒过来然后在逐行扫描。另外,字节数组中每个扫描行的字节数必需是 4 的倍数,如果不足要用 0 补齐。 可以存储在 *.BMP 或 *.DIB 文件中。 DIB 文件是以 BITMAPFILEHEADER 结构开头的,该结构的定义为typedef struct tagBITMAPFILEHEADER {
WORD bfType; //
文件类型,必须为“ BM ”DWORD bfSize; //
文件的大小WORD bfReserved1; //
WORD bfReserved2; //
DWORD bfOffBits; //
为 0 为 0 存储的像素阵列相对于文件头的偏移量} BITMAPFILEHEADER;
紧随该结构的是一个 BITMAPINFOHEADER 结构,然后是 RGBQUAD 结构组成的颜色表 ( 如果有的话 ) ,文件最后存储的是 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 //
DIBWidth //
显示 DIB 返回 DIB 的宽度DIBHeight //
返回 DIB 的高度
DIB
RGBQUAD
BITMAPINFOHEADER
DIB
DIB
将
将
如果读者对这些函数的内部细节感兴趣,那么可以研究一下dibapi.cpp和myfile.cpp文件,但要做好吃苦的准备。即使利用上述API,编写使用DIB的程序仍然不是很轻松。为了满足读者的要求,笔者编写了一个名为CDib的较简单的DIB类,该类是基于上述API的,它的主要成员函数包括:
BOOL Load(LPCTSTR lpszFileName);
BOOL LoadFromResource(UINT nID);
CPalette* GetPalette()
BOOL Draw(CDC *pDC, int x, int y, int cx=0, int cy=0);
int Width(); //
以像素为单位返回DIB的宽度int Height(); //
以像素为单位返回DIB的高度
类的源代码在清单11.3和11.4列出,CDib类的定义位于CDib.h中,CDib类的成员函数代码位于CDib.cpp中。对于CDib类的代码这里就不作具体解释了,读者只要会用就行。 11.3 CDib.h 11.4 Cdib.cpp 从文件中载入DIB 清除旧位图 有可能没有调色板 从资源中载入DIB搜寻指定的资源载入位图资源 把hRes中的内容复制hDIB中 有可能没有调色板 显示DIB,该函数具有缩放功能参数x和y说明了目的矩形的左上角坐标,cx和cy说明了目的矩形的尺寸和cy若有一个为0则该函数按DIB的实际大小绘制,cx和cy的缺省值是0 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类的定义之前加入下面一行:当收到调色板消息时,主框架窗口会发送用户定义的WM_DOREALIZE消息通知视图。接下来,需要用ClassWizard为CMainFrame加入WM_QUERYNEWPALETTE和WM_PALETTECHANGED消息的处理函数,为CShowDibDoc类加入OnOpenDocument函数。最后,请读者按清单11.5、11.6和11.7修改程序。 11.5 CMainFrame类的部分代码 通知所有的子窗口 没有活动的MDI子框架窗口只通知活动视图返回TRUE表明实现了逻辑调色板 11.6 CShowDibDoc类的部分代码 载入DIB 11.7 CShowDibView类的部分代码 设置视图的滚动范围 刷新视图 参数决定了该视图是否实现前景调色板 输出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函数来完成。
CDib
清单
#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
清单
#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;
}
//
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;
}
//
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); //
::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;
}
//
//
//cx
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);
}
#define WM_DOREALIZE WM_USER+200
清单
// 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; //
CView* pView = pMDIChildWnd->GetActiveView();
pView->SendMessage(WM_DOREALIZE,0); //
return TRUE; //
}
清单
// 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); //
EndWaitCursor();
return bSuccess;
}
清单
// 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); //
}