经过几天的奋战,按照网上的主要步骤,搞出来了一个用VC打开位图的程序,很是兴奋,现在把主要代码发在这里,供对VC和图象处理有兴趣的朋友们研究交流.
一.首先简单介绍下:与设备无关的位图(DIB)
DIB(Device-indepentent bitmap)的与设备无关性主要体现在以下两个方面:
DIB的颜色模式与设备无关。例如,一个256色的DIB即可以在真彩色显示模式下使用,也可以在16色模式下使用。
256色以下(包括256色)的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中创建一个逻辑调色板
本程序中编写了一个名为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的高度
PaintDIB //显示DIB
DIBWidth //返回DIB的宽度
DIBHeight //返回DIB的高度
CDib类的源代码在清单11.3和11.4列出,CDib类的定义位于CDib.h中,CDib类的成员函数代码位于CDib.cpp中。
二.主要程序.(黑体部分为需要自己添加的代码或函数)
(1)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);
}
(2)建工程.此程序名为ShowDib,是一个多文档应用程序,它的功能与VC的DibLook例程有些类似,可同时打开和显示多个位图。
首先用AppWizard建立一个名为ShowDib的MFC工程。程序应该用滚动视图来显示较大的位图,所以在MFC AppWizard的第6步应把CShowDibView的基类改为CScrollView。
由于ShowDib程序要用到CDib类,所以应该把dibapi.cpp、myfile.cpp、dibapi.h、CDib.cpp和CDib.h文件(注意,这些文件都在MSDN自带的一个diblook例子里)拷贝到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 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函数来完成。