在MFC程序中显示JPG/GIF图像

在MFC程序中显示JPG/GIF图像 编译:Northtibet

下载源代码

    如果你是一个使用VB编程的程序员,要在程序中显示JPG或者GIF图像简直易如反掌,将图像控件拖到Form中,分分钟即可搞掂。但是C++程序员要显示同样的图形却没有那么轻松,那么是不是要自己编写JPG解压缩代码呢?当然不用那么复杂啦!本文将针对这个问题讨论如何在MFC中显示JPG或者GIF图像。     用VB写图像显示程序之所以如此轻松,完全是利用了琳琅满目的图像处理控件,把你想要做的事情都一一搞掂。而C++程序员为了实现相同的功能必须忙乎半天。其实,C/C++程序员也能使用那些VB程序员所用的(或者说几乎一样的)图像控件。VB用的图像控件实际上都基于一个系统级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 = // 显示图像的矩形
// 将rc 转换为 HIMETRIC
spIPicture->Render(pDC, rc);      
    IPicture 负责处理所有琐事,以便确定图形之格式,如 Windows 位图、JPEG或者GIF文件——甚至是图标和元文件(metafiles)。当然啦,所有这些的实现细节是需要技巧的,为此我写了一个Demo程序Myimgapp(如图二)来示范这些IPicture的使用方法。 图一 Myimgapp的运行画面     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方法,你可以自己尝试编写相应的打包代码。 另外,在编写完CPicture之后,我发现了一个现成的MFC类——CPictureHolder,这个类的功能几乎与CPicture完全一样,你可以在afxctl.h文件中找到它的定义。 前面说过,Demo例子是个典型的MFC文档/视图应用程序,因此它肯定少不了与文档和视图类相对应的CPictureDoc 和CPictureView: CPictureDoc类没有什么特别的处理代码,它用CPicture对象存储图像:
class CPictureDoc : public CDocument {
protected:
  CPicture m_pict; // the picture
};      
并且CPictureDoc::Serialize 调用CPicture::Load 从MFC存档的数据中读取图像。
void CPictureDoc::Serialize(CArchive& ar)
{
  if (ar.IsLoading()) {
    m_pict.Load(ar);
  }
}      
为了使Myimgapp程序更实用,CPictureDoc::OnNewDocument从程序资源数据加载了一幅图像。为了显示这幅图像,CPictureView::OnDraw要调用CPicture::Render。这样程序一启动便会显示一幅默认的图像。
void CPictureView::OnDraw(CDC* pDC)
{
  CPictureDoc* pDoc = GetDocument();
  CPicture* ppic = pDoc->GetPicture();
  CRect rc;
  GetImageRect(rc);
  ppic->Render(pDC,rc);
}
    GetImageRect是CPictureView类的一个成员函数,作用是根据当前Myimgapp的缩放比率(可用25%、33%、50%、75%、100%或自适应方式)获取图像矩形。GetImageRect调用CPicture::GetImageSize来获得真正的图像大小,然后根据比率显示。 CPictureView其余的部分完全和CScrollView的做法差不多,初始化视图并设置滚动大小,处理命令等等。唯一让人操心的是IPicture::Render中HIMETRIC的处理问题,因为标准的MFC应用程序都使用MM_TEXT映射模型。不用担心,CPicture::Render和CPicture::GetImageSize会将这一切转换过来,所以你不必为这些事情伤神。 CPictureView有一个消息处理器值得一提:它就是OnEraseBkgnd,当要显示的图像比客户区小的时候,这个函数必须绘制空白区域,如图二,OnEraseBkgnd创建一个与图像大小相等的切边(clip)矩形,然后将客户区填成黑色。之所以要创建切边矩形,主要是避免当改变窗口大小时出现的抖动——FillRect不绘制切边矩形内的区域,此乃Windows图形处理的常识。 图二 OnEraseBkgnd 填充修剪的图像     IPicture/CPicture简化了图像的显示。它甚至可以实现调色板的识别这样复杂的处理。你完全可以抛开老式DIB 图像绘制方法,如加载调色板、BitBlts、StretchBlts等等——这一切IPicture全都可以搞掂。如果你未曾用IPicture显示过图像,那么现在试试吧。 CPictureView完成图像浏览的任务看来不是什么难事了。但是如果要把一幅图像添加到一个对话框或者其它的什么窗口中怎么办呢?为此我创建了另外一个类——CPictureCtrl。 CPictureCtrl 使你可以在任何对话框或窗口中把图像作为子窗口显示。例如:
class CAboutDialog : public CDialog {
protected:
  CPictureCtrl m_wndPict;
  virtual BOOL OnInitDialog();
};
BOOL CAboutDialog::OnInitDialog()
{
  m_wndPict.SubclassDlgItem(IDC_MYIMAGE,this);
  return CDialog::OnInitDialog();
}      
    假设你的对话框中有一个静态控制,它的ID=IDC_IMAGE,并且有一幅IMAGE资源的ID与之相同。则从CStaticLink派生出的CPictureCtrl还可以指定一个URL超链接(或者创建一个ID与此控制或图像的ID相同的串资源)。如果你指定了一个URL,则在图像上单击鼠标将启动默认浏览器访问URL。真是酷呆了。CPicture控制着CPicture对象并改写WM_PAINT消息处理例程,调用CPicture::Render代替通常的静态控制处理例程。处理细节请参见代码。打开Myimgapp程序的“关于”对话框就知道了。 使用GDI+进行图像处理 编译/NorthTibet下载源代码 前段时间VCKBASE发布了一篇有关图像处理的文章“在MFC程序中显示JPG/GIF图像”,之后不断有人问我如何对图像进行旋转处理,也就是让用户歪着脖子看图像,用户的脖子拧断了怎么办?反正不会来找我...... 其实这个问题的一种解决方法是利用二维(x,y坐标中)矩阵转换实现图像旋转。使用高中时所学的三角知识或者大学中的线性代数知识就可以解决。其原理是已知一个点的坐标,那么这个点的旋转坐标可以通过 (x*cos(A) + y*sin(A),- x*sin(A) + y*cos(A)) 求得,这里A是以弧度为单位的角度(2P弧度=360度)。因此,只要将图像加载到内存中,然后将它选入设备上下文,接着调用GetPixel和SetPixel,象上面所说的那样映射所有的像素,便可以实现图像的旋转效果。对于90、180、-90度的旋转,这是一个不错的方法,因为正弦、余弦的值无外乎+/-1或者0......话还没等我说完,一块砖头就朝我头上飞过来了,啊唷,我当然不会叫你用这种方法做事情啦!有更好的方法呢。下面就是本文的正题:使用GDI+实现图像旋转处理。我想了解GDI+的人不是很多,因为它是Windows中的新东西。要想了解它的细节,请参考MSDN的有关文章。 GDI+是GDI图形库的一个增强版本,C++可以使用这个库。它内建于 Windows XPMicrosoft .NET,而对于 Windows 98Windows NTWindows 2000,则有一个可重新发布的版本。GDI+是一个C++ API。它用C++类和C++方法。GDI+所包含的内容非常多,远远不止我在此所描述的这些。为了使用GDI+,你必须包含(#include) 文件,并将工程链接到gdiplus.lib库,这两个文件包含在最新的Windows SDK中。我对“在MFC程序中显示JPG/GIF图像”一文中的例子代码Myimgapp进行了重写,并改名为Myimgapp2,其代码对CPicture类进行了重大改动,因为原来的CPicture主要针对IPicture进行封装,而这一次主要是封装GDI+。下面是Myimgapp2运行画面,如图一所示。这个程序示范了如何用GDI+来旋转图像。
在MFC程序中显示JPG/GIF图像(图四) 图像旋转90度
前面说过,原来程序中的那个C++类CPicture是基于IPicture接口的,它处理图像的COM接口。在本文的例子程序Myimgapp2中,我重写了CPicture的代码,使用了GDI+里的图像类(Image)。对所有的细节都进行了封装。新的CPicture类不再使用 IPicture 这个COM接口,而是用Image取而代之,所有其它的类如CPictureView 和 CPictureCtrl都能和从前一样工作。但有两个障碍要解决:第一个是你必须对GDI库进行初始化以及最后的终止释放操作,与其说它是个障碍,还不如说它是GDI+本身的需要,在哪里进行这两个操作呢?最佳的地方莫过于在程序的 InitInstance 和 ExitInstance函数中: //初始化 GDI class CMyApp : public CWinApp { protected: GdiplusStartupInput m_gdiplusStartupInput; ULONG_PTR m_gdiplusToken; ……. }; //释放GDI BOOL CMyApp::InitInstance() { VERIFY(GdiplusStartup(&m_gdiplusToken, &m_gdiplusStartupInput, NULL)==Ok); ……. } int CMyApp::ExitInstance() { GdiplusShutdown(m_gdiplusToken); return CWinApp::ExitInstance(); } CMyApp::m_gdiplusToken 是一个很神奇的东东,它来自GdiplusStartup 并被传递到GdiplusShutdown。m_gdiplusStartupInput 是一个结构,它包含某些GDI+的启动参数。缺省构造函数建立一个智能的缺省值,它又一次证明了C++比C更好。一旦你启动GDI+,就可以使用它了。原来的CPicture类有一个指向IPicture的指针,而新版的CPicture类有一个指向Image的指针。同样,它也有可重载的Load函数来从不同的地方加载图象。例如,下面便是新版的CPicture如何从某个路径名中加载图像文件。 BOOL CPicture::Load(LPCTSTR pszPathName) { Free(); USES_CONVERSION; m_pImage = Image::FromFile(A2W(pszPathName), m_bUseEmbeddedColorManagement); return m_pImage->GetLastStatus()==Ok; } 在此代码段中,重点是GDI+要用宽字符串,所以你要用USES_CONVERSION 和 A2W.。原来的CPicture用Load函数从某个CFile、CArchive、资源ID或流中加载图像。所有Load函数最终都走到从流中加载图像的例程:CPicture::Load(IStream*)。当我开始用Image代替IPicture,并用GDI+函数从流中加载数据时,它不工作。情况真是很糟,令人沮丧。问题出在MFC的CArchiveStream类,这个类在CArchive类之上实现流化。可能是CArchiveStream没有正确实现所有的IStream方法,Image::FromStream无法正确处理基于CArchiveStream的流操作,更糟的是它返回一切OK,但实际上当你试图显示或者删除Image时会失败。 为了解决这个问题,我重写了CPicture::Load(UINT nID),其中用到了CreateStreamOnHGlobal函数。这个API函数很好用,它在一个全局内存块中创建一个流: // 分配全局内存并在其中创建流 HGLOBAL m_hMem = GlobalAlloc(GMEM_FIXED, len); BYTE* pmem = (BYTE*)GlobalLock(m_hMem); memcpy(pmem,lpRsrc,len); IStream* pstm; CreateStreamOnHGlobal(m_hMem,FALSE,&pstm); 这里lpRsrc已经指向内存中的图形资源。所以基本思路是加载图像资源,将它拷贝到全局内存中,在内存上创建一个流,然后用Image::FromStream创建一个图象,有关细节请参见例子源代码。当对象被销毁或某人加载另外的图像时,CPicture自动释放全局内存。对于从CFile或者CArchive加载的情况——其实,根本不需要考虑这种情况。 以上是加载的方法,下面看看如何显示图像,你必须使用GDI+类Graphics——与原来GDI中的设备上下文(HDC或CDC)类似,它也有一个方法DrawImage: BOOL CPicture::Render(CDC* pDC, CRect rc) const { ……. Graphics graphics(pDC->m_hDC); graphics.DrawImage(m_pImage, rc.left, rc.top, rc.Width(), rc.Height()); } 有关细节请参考源代码。用Image代替IPicture来表现图像要简单一些。Image::GetWidth 和Image::GetHeight分别获得象素的宽度和高度,这正是你所需要代替IPicture中HIMETRIC单位的东东。一般来说,用GDI+很容易编程,例如下面示范了如何旋转: void CPicture::Rotate(RotateFlipType rft) { if (m_pImage) { m_pImage->RotateFlip(rft); } } 这个代码将图像顺时针旋转90度,下面列出了RotateFlipType所有可能的情况: RotateFlipType 选项 // 用于Image::RotateFlip的枚举类型 enum RotateFlipType { RotateNoneFlipNone = 0, Rotate90FlipNone = 1, Rotate180FlipNone = 2, Rotate270FlipNone = 3, RotateNoneFlipX = 4, Rotate90FlipX = 5, Rotate180FlipX = 6, Rotate270FlipX = 7, RotateNoneFlipY = Rotate180FlipX, Rotate90FlipY = Rotate270FlipX, Rotate180FlipY = RotateNoneFlipX, Rotate270FlipY = Rotate90FlipX, RotateNoneFlipXY = Rotate180FlipNone, Rotate90FlipXY = Rotate270FlipNone, Rotate180FlipXY = RotateNoneFlipNone, Rotate270FlipXY = Rotate90FlipNone }; GDI+ 的其它函数用于展开和修剪图像,它甚至还有一个函数Image::GetThumbnailImage用来解决缩略图问题,有兴趣的话不妨自己试一下。 我鼓励大家去研究一下GDI+。有些程序员因为它速度慢而不喜欢它,但是有很多文档中提供了一些技巧来改善它的性能。例如,有一个类叫CachedBitmap,这个类以优化设备的格式保存位图。对于象视频或高端图像编辑等图形敏感的应用程序你可能不会用GDI+来编程(一般都用DirectX),但对于日常的一般图形应用来说,GDI+不失为一种比GDI更好的选择。 总结一下 VC6 下如何使用GDI+ :) 1.下载解压GDI+开发包: http://www.codeguru.com/gdi/GDIPlus.zip 2.正确设置include & lib 目录 3.在 stdafx.h 添加: #ifndef ULONG_PTR #define ULONG_PTR unsigned long* #endif
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值