一、实现方法
用Visual Basic写图像显示程序之所以如此轻松,完全是利用了琳琅满目的图像处理控件,它们处理了显示图像文件的所有底层工作,而C++程序员为了实现相同的功能必须忙乎半天。其实,C/C++程序员也能使用那些Visual Basic程序员所用的(或者说几乎一样的)图像控件。Visual Basic用的图像控件实际上都是基于一个系统级的COM类--IPicture。下面是有关Ipicture类的方法描述:
get_Handle():返回图像对象的 Windows GDI句柄;
get_Hpal():返回图像对象当前使用的调色板拷贝;
get_Type():返回当前图像对象的的图像类型;
get_Width():返回当前图像对象的图像宽度;
get_Height():返回当前图像对象的图像高度;
Render():在指定的位置、指定的设备上下文上绘制指定的图像部分;
set_Hpal():设置当前图像的调色板;
get_CurDC():返回当前选中这个图像的设备上下文;
SelectPicture():将一个位图图像选入给定的设备上下文,返回选中图像的设备上下文和图像的GDI句柄 ;
get_KeepOriginalForma():返回图像对象KeepOriginalFormat 属性的当前值;
put_KeepOriginalFormat():设置图像对象的KeepOriginalFormat 属性;
PictureChanged():通知图像对象它的图像资源改变了;
SaveAsFile():将图像数据存储到流中,格式与存成文件格式相同;
get_Attributes():返回图像位属性当前的设置;
从上面的方法可以看出,Ipicture类操纵着图像对象及其属性。图像对象提供对位图的抽象,而Windows负责BMP、JPG和GIF等格式的标准实现。程序员要做的只是实例化Ipicture类对象,然后调用其Render()函数。与通常使用接口的方式不同,这里实例的创建我们不用CoCreateInstance()函数,而是用一个专门的函数OleLoadPicture(),具体实现代码如下:
IStream* pstm = // 需要一个流(stream) IPicture* pIPicture; hr = OleLoadPicture(pstm, 0, FALSE, IID_IPicture, (void**)&pIPicture); |
OleLoadPicture()函数从数据流中加载图像并创建一个可用来显示图像的新IPicture对象。 rc = // 显示图像的矩形
IPicture 负责处理所有琐事,以便确定图形之格式,如 Windows 位图、JPEG或者GIF文件--甚至是图标和元文件(metafiles)。当然啦,所有这些的实现细节是需要技巧的,为此本实例写了一个Demo程序Myimgapp来示范这些IPicture的使用方法。Myimgapp是个典型的MFC文档/视图程序,在编写这个程序之前,首先对 IPicture COM接口进行封装,之所以要这么做,主要是考虑到并不是每一个程序员都能熟练运用COM接口进行编程,另外将IPicture的主要功能封装在C++类中可以使我们的问题更容易解决,封装的这个C++类名字叫做CPicture。它的定义和实现细节请参考本文提供的源代码。
本实例在这个类中将复杂而陌生的COM风格的参数映射成MFC程序员更为熟悉的类型。例如,CPicture可以让你直接从文件名加载一幅图像,CFile或者CArchive,而不用去处理流,CPicture::Render()替你完成了IPicture中所有令人讨厌的但又是必须的HIMETRIC平滑转换工作。CPicture甚至具备了一个Load()函数,它可以从资源数据中加载图像,所以你只要用下面的代码就可以显示资源中的图像:
CPicture pic(ID_MYPIC); // 加载图像 CRect rc(0,0,0,0); // 使用缺省的rc pic.Render(pDC, rc); // 显示图像 |
CPicture::Render提供一个显示图片的矩形。IPicture 对图像进行延伸处理。如果传递一个空矩形,则CPicture用图像本身的大小--不进行延伸处理。对于图像本身而言,CPicture查找"IMAGE"类型的资源,所以在资源文件中必须要在程序的资源文件中加入如下语句来添加图像资源:IDR_MYPIC IMAGE MOVEABLE PURE "res//MyPic.jpg"。
CPicture是个很棒的傻瓜类,它具备一个 ATL 智能指针CComQIPtr指向IPicture接口,通过调用OleLoadPicture来初始化不同的Load函数。CPicture提供了常用的打包函数来调用底层的IPicture。CPicture只封装了那些在Demo例子程序中要用到的方法。如果读者朋友需要调用IPicture::get_Handle或其它一些很少用到的IPicture方法,可以自己尝试编写相应的打包代码。
总之,IPicture/CPicture简化了图像的显示,它甚至可以实现调色板的识别这样复杂的处理,读者朋友完全可以抛开老式DIB 图像绘制方法,如加载调色板、BitBlts()、StretchBlts()等操作--这一切IPicture全都可以搞掂。
程序中有一个消息处理器值得一提:它就是视图类的OnEraseBkgnd()函数,当要显示的图像比客户区小的时候,这个函数必须绘制空白区域,OnEraseBkgnd()函数创建一个与图像大小相等的切边(clip)矩形,然后将客户区填成黑色,之所以要创建切边矩形,主要是避免当改变窗口大小时出现的抖动--FillRect()不绘制切边矩形内的区域,此乃Windows图形处理的常识。
二、编程步骤
1、 启动Visual C++6.0,生成一个单文档视图结构的应用程序,视图类的基类为CscrollView,同时将该程序命名为"Myimgapp";
2、 在应用程序的项目代码中添加"CPicture"类;工具栏上添加图像显示比例的按钮,具体参加代码部分;
3、 使用资源编辑器向程序中添加Jepg格式的图像资源;
4、 添加代码,编译运行程序。
三、程序代码
/// Picture object--encapsulates IPicture #pragma once #include <atlbase.h> class CPicture { public: CPicture(); ~CPicture(); // Load frm various sosurces BOOL Load(UINT nIDRes); BOOL Load(LPCTSTR pszPathName); BOOL Load(CFile& file); BOOL Load(CArchive& ar); BOOL Load(IStream* pstm); // render to device context BOOL Render(CDC* pDC, CRect rc=CRect(0,0,0,0), LPCRECT prcMFBounds=NULL) const; CSize GetImageSize(CDC* pDC=NULL) const; operator IPicture*() { return m_spIPicture; } void GetHIMETRICSize(OLE_XSIZE_HIMETRIC& cx, OLE_YSIZE_HIMETRIC& cy) const { cx = cy = 0; const_cast<CPicture*>(this)->m_hr = m_spIPicture->get_Width(&cx); ASSERT(SUCCEEDED(m_hr)); const_cast<CPicture*>(this)->m_hr = m_spIPicture->get_Height(&cy); ASSERT(SUCCEEDED(m_hr)); } void Free() { if (m_spIPicture) { m_spIPicture.Release(); } } protected: CComQIPtr<IPicture>m_spIPicture; // ATL smart pointer to IPicture HRESULT m_hr; // last error code }; /// CPicture implementation #include "StdAfx.h" #include "Picture.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif CPicture::CPicture() {} CPicture::~CPicture() {} BOOL CPicture::Load(UINT nIDRes) // Load from resource. Looks for "IMAGE" type. { // find resource in resource file HINSTANCE hInst = AfxGetResourceHandle(); HRSRC hRsrc = ::FindResource(hInst,MAKEINTRESOURCE(nIDRes),"IMAGE"); // type if (!hRsrc) return FALSE; // load resource into memory DWORD len = SizeofResource(hInst, hRsrc); BYTE* lpRsrc = (BYTE*)LoadResource(hInst, hRsrc); if (!lpRsrc) return FALSE; // create memory file and load it CMemFile file(lpRsrc, len); BOOL bRet = Load(file); FreeResource(hRsrc); GlobalFree(lpRsrc); return bRet; } BOOL CPicture::Load(LPCTSTR pszPathName) // Load from path name. { CFile file; if (!file.Open(pszPathName, CFile::modeRead|CFile::shareDenyWrite)) return FALSE; BOOL bRet = Load(file); file.Close(); return bRet; } BOOL CPicture::Load(CFile& file) // Load from CFile { CArchive ar(&file, CArchive::load | CArchive::bNoFlushOnDelete); return Load(ar); } // Load from archive--create stream and load from stream. BOOL CPicture::Load(CArchive& ar) { CArchiveStream arcstream(&ar); return Load((IStream*)&arcstream); } // // Load from stream (IStream). This is the one that really does it: call // OleLoadPicture to do the work. BOOL CPicture::Load(IStream* pstm) { Free(); HRESULT hr = OleLoadPicture(pstm, 0, FALSE,IID_IPicture, (void**)&m_spIPicture); ASSERT(SUCCEEDED(hr) && m_spIPicture); return TRUE; } // // Render to device context. Covert to HIMETRIC for IPicture. BOOL CPicture::Render(CDC* pDC, CRect rc, LPCRECT prcMFBounds) const { ASSERT(pDC); if (rc.IsRectNull()) { CSize sz = GetImageSize(pDC); rc.right = sz.cx; rc.bottom = sz.cy; } long hmWidth,hmHeight; // HIMETRIC units GetHIMETRICSize(hmWidth, hmHeight); m_spIPicture->Render(*pDC, rc.left, rc.top, rc.Width(), rc.Height(),0, hmHeight, hmWidth, -hmHeight, prcMFBounds); return TRUE; } // // Get image size in pixels. Converts from HIMETRIC to device coords. CSize CPicture::GetImageSize(CDC* pDC) const { if (!m_spIPicture) return CSize(0,0); LONG hmWidth, hmHeight; // HIMETRIC units m_spIPicture->get_Width(&hmWidth); m_spIPicture->get_Height(&hmHeight); CSize sz(hmWidth,hmHeight); if (pDC==NULL) { CWindowDC dc(NULL); dc.HIMETRICtoDP(&sz); // convert to pixels } else { pDC->HIMETRICtoDP(&sz); } return sz; } / Picture view is a typical scroll view. #include "Doc.h" class CPictureView : public CScrollView { public: virtual ~CPictureView(); CPictureDoc* GetDocument() { return (CPictureDoc*)m_pDocument; } protected: BOOL m_rcImage; // rect to display image in UINT m_iHowScale; // how to scale image CPictureView(); void GetImageRect(CRect& rc); void SetScrollSizes(); virtual void OnDraw(CDC* pDC); // overridden to draw this view virtual void OnInitialUpdate(); // called first time after construct // command/message handlers afx_msg void OnViewScale(UINT nID); afx_msg void OnUpdateViewScale(CCmdUI* pCmdUI); afx_msg BOOL OnEraseBkgnd(CDC* pDC); afx_msg void OnSize(UINT nType, int cx, int cy); DECLARE_DYNCREATE(CPictureView) DECLARE_MESSAGE_MAP() }; // CPictureView #include "StdAfx.h" #include "View.h" #include "resource.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif IMPLEMENT_DYNCREATE(CPictureView, CScrollView) BEGIN_MESSAGE_MAP(CPictureView, CScrollView) ON_WM_ERASEBKGND() ON_WM_SIZE() ON_COMMAND_RANGE(ID_VIEW_TOFIT, ID_VIEW100, OnViewScale) ON_UPDATE_COMMAND_UI_RANGE(ID_VIEW_TOFIT, ID_VIEW100, OnUpdateViewScale) END_MESSAGE_MAP() CPictureView::CPictureView() { m_iHowScale = ID_VIEW_TOFIT; } CPictureView::~CPictureView() {} void CPictureView::OnInitialUpdate() { SetScrollSizes(); } Set scroll sizes based on picture. Page size = client hieight/width; // line size = 1/10 of this. void CPictureView::SetScrollSizes() { CRect rcClient; GetClientRect(&rcClient); CRect rcImage; GetImageRect(rcImage); CSize szTotal = rcImage.Size(); CSize szPage = rcClient.Size(); CSize szLine = szPage; szLine.cx /= 10; szLine.cy /= 10; CScrollView::SetScrollSizes(MM_TEXT, szTotal, szPage, szLine); Invalidate(); } View was sized: readjust scroll sizes if I'm in "zoom to fit" mode void CPictureView::OnSize(UINT nType, int cx, int cy) { CScrollView::OnSize(nType, cx, cy); if (m_iHowScale==ID_VIEW_TOFIT) { SetScrollSizes(); } } // // Erase the background. This is required in case the image is smaller than // the client area, to paint the extra background. Use clipping to avoid flicker. BOOL CPictureView::OnEraseBkgnd(CDC* pDC) { CPictureDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // get client rectangle CRect rcClient; GetClientRect(&rcClient); CRect rc = rcClient; // get image rectangle CRect rcImage; GetImageRect(rcImage); rc = rcImage; CPoint pt = pDC->GetViewportOrg(); CSize sz = GetTotalSize(); // create clipping region CRgn clipRgn; clipRgn.CreateRectRgnIndirect(&rcClient); pDC->SelectClipRgn(&clipRgn); pDC->ExcludeClipRect(&rcImage); CBrush brush(RGB(0,0,0)); // black pDC->FillRect(&rcClient, &brush); pDC->SelectClipRgn(NULL); return TRUE; } // // Draw the picture -- call CPicture to do it. void CPictureView::OnDraw(CDC* pDC) { CPictureDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); CPicture* ppic = pDoc->GetPicture(); ASSERT(ppic); if (*ppic) { CRect rc; GetImageRect(rc); ppic->Render(pDC,rc); } } // // Get image rectangle, scaled for current zoom factor. void CPictureView::GetImageRect(CRect& rc) { CPictureDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); CPicture* ppic = pDoc->GetPicture(); ASSERT(ppic); if (!ppic || !*ppic) { rc.SetRect(0,0,0,0); } else if (m_iHowScale==ID_VIEW_TOFIT) { GetClientRect(&rc); } else { CSize sz = ppic->GetImageSize(); switch (m_iHowScale) { case ID_VIEW25: sz.cx >>= 2; sz.cy >>= 2; break; case ID_VIEW33: sz.cx /= 3; sz.cy /= 3; break; case ID_VIEW50: sz.cx >>= 1; sz.cy >>= 1; break; case ID_VIEW75: sz.cx = (sz.cx * 3)/4; sz.cy = (sz.cy * 3)/4; break; } rc.SetRect(0,0,sz.cx,sz.cy); } } // // Handle zoom command. void CPictureView::OnViewScale(UINT nID) { if (m_iHowScale != nID) { m_iHowScale = nID; ScrollToPosition(CPoint(0,0)); OnInitialUpdate(); } } Update zoom menu -- check the whichever zoom factor I'm at now. void CPictureView::OnUpdateViewScale(CCmdUI* pCmdUI) { pCmdUI->SetCheck(pCmdUI->m_nID == m_iHowScale); } |
四、小结
本实例通过Cpicutre类现了JEPG图像的显示,读者可以将该类直接用在多媒体应用程序的开发中。另外,MFC提供了一个现成的类--CPictureHolder,这个类的功能几乎与CPicture完全一样,读者朋友可以在afxctl.h文件中找到它的定义。