上一节我们说了GDI+的矩阵,关于矩阵也只是单独的说了一下,如果以后有时间看看能否推出GDI+的应用课程。
这一节呢,我们借助GDI+打造一个简单的windows功能图片查看器,包括图片的旋转,缩放和Alpha透明处理,保存,当然还有一项新功能,显示拖拽图像文件
#pragma once typedef struct tagSize { float cx; float cy; } _SIZE, *_PSIZE, *_LPSIZE; class CGdiPlusImage { //变量定义 protected: Image * m_pImage; //图片对象 tagSize m_szZoom; //缩放变量 int m_nRotateAngle; //旋转角度 BYTE m_cbAlphaDepth; //Alpha数值 //函数定义 public: //构造函数 CGdiPlusImage(); //析构函数 virtual ~CGdiPlusImage(); //状态函数 public: //是否加载 bool IsNull(); //获取宽度 INT GetWidth(); //获取高度 INT GetHeight(); //管理函数 public: //销毁图片 bool DestroyImage(); //加载图片 bool LoadImage(LPCTSTR pszFileName); //功能函数 public: //设置旋转 void SetRotation(int nAngle); //设置缩放 void SetScale(float cx,float cy); //设置Alpha void SetAlpha(BYTE cbAlphaDepth); //保存图像 bool Save(LPCTSTR pszFileName,LPCTSTR format=TEXT("image/png")); //获取解码器 int GetEncoderClsid(LPCTSTR format, CLSID* pClsid); //绘画函数 public: //绘画图像 bool DrawImage(CDC * pDC, INT nXPos, INT nYPos); };
#include "StdAfx.h" #include "GdiPlusImage.h" //构造函数 CGdiPlusImage::CGdiPlusImage(void) { //设置变量 m_pImage=NULL; m_szZoom.cx = m_szZoom.cy = 1.0f; m_nRotateAngle = 0; m_cbAlphaDepth = 255; } //析构函数 CGdiPlusImage::~CGdiPlusImage(void) { //销毁图片 DestroyImage(); } //是否加载 bool CGdiPlusImage::IsNull() { //加载判断 if (m_pImage==NULL) return true; if (m_pImage->GetLastStatus()!=Ok) return true; return false; } //获取宽度 INT CGdiPlusImage::GetWidth() { //加载判断 ASSERT(IsNull()==false); if (IsNull()==true) return 0; //获取宽度 return m_pImage->GetWidth(); } //获取高度 INT CGdiPlusImage::GetHeight() { //加载判断 ASSERT(IsNull()==false); if (IsNull()==true) return 0; //获取高度 return m_pImage->GetHeight(); } //销毁图片 bool CGdiPlusImage::DestroyImage() { //删除对象 if (m_pImage!=NULL) SafeDelete(m_pImage); return true; } //加载图片 bool CGdiPlusImage::LoadImage(LPCTSTR pszFileName) { //加载判断 ASSERT(m_pImage==NULL); if (m_pImage!=NULL) return false; //加载文件 CT2CW strFileName(pszFileName); m_pImage=Image::FromFile((LPCWSTR)strFileName); //错误判断 if ((m_pImage==NULL)||(m_pImage->GetLastStatus()!=Ok)) { DestroyImage(); return false; } return true; } //绘画图像 bool CGdiPlusImage::DrawImage(CDC * pDC, INT nXPos, INT nYPos) { //加载判断 ASSERT(m_pImage!=NULL); if (m_pImage==NULL) return false; //创建屏幕 ASSERT(pDC!=NULL); Graphics graphics(pDC->GetSafeHdc()); GraphicsContainer Containter = graphics.BeginContainer(); graphics.SetSmoothingMode(SmoothingModeHighQuality); //获取属性 INT nImageWidth=m_pImage->GetWidth(); INT nImageHeight=m_pImage->GetHeight(); //构造位置 RectF rcDrawRect; rcDrawRect.X=0;//(REAL)nXPos; rcDrawRect.Y=0;//(REAL)nYPos; rcDrawRect.Width=(REAL)nImageWidth; rcDrawRect.Height=(REAL)nImageHeight; Matrix matrix; CPoint pt((nXPos+nImageWidth/2), (nYPos+nImageHeight/2)); PointF ptBase((float)pt.x,(float)pt.y); //矩阵变换要注意顺序,先平移在缩放后旋转 matrix.Translate((REAL)nXPos,(REAL)nYPos,MatrixOrderAppend); //缩放 if ( m_szZoom.cx != 1.0f || m_szZoom.cy != 1.0f ) { ptBase.X += m_szZoom.cx; ptBase.Y += m_szZoom.cy; matrix.Translate(-ptBase.X,-ptBase.Y,MatrixOrderAppend); matrix.Scale(m_szZoom.cx,m_szZoom.cy,MatrixOrderAppend); matrix.Translate(ptBase.X,ptBase.Y,MatrixOrderAppend); } //旋转 if (m_nRotateAngle) { matrix.RotateAt((REAL)m_nRotateAngle,ptBase,MatrixOrderAppend); } graphics.SetTransform(&matrix); //透明矩阵 ColorMatrix Matrix= { 1.0f,0.0f,0.0f,0.0f,0.0f, 0.0f,1.0f,0.0f,0.0f,0.0f, 0.0f,0.0f,1.0f,0.0f,0.0f, 0.0f,0.0f,0.0f,m_cbAlphaDepth/255.0f,0.0f, 0.0f,0.0f,0.0f,0.0f,1.0f }; //设置属性 ImageAttributes Attributes; Attributes.SetColorMatrix(&Matrix,ColorMatrixFlagsDefault,ColorAdjustTypeBitmap); //绘画图像 graphics.DrawImage(m_pImage,rcDrawRect,0,0,(REAL)nImageWidth,(REAL)nImageHeight,UnitPixel,&Attributes); graphics.ResetTransform(); graphics.EndContainer(Containter); return true; } void CGdiPlusImage::SetRotation( int nAngle) { m_nRotateAngle = nAngle; } void CGdiPlusImage::SetScale(float cx,float cy) { m_szZoom.cx = cx; m_szZoom.cy = cy; } void CGdiPlusImage::SetAlpha( BYTE cbAlphaDepth ) { if ( cbAlphaDepth < 0 ) m_cbAlphaDepth = 0; if ( cbAlphaDepth > 255 ) m_cbAlphaDepth = 255; m_cbAlphaDepth = cbAlphaDepth; } int CGdiPlusImage::GetEncoderClsid(LPCTSTR format, CLSID* pClsid) { UINT num = 0; UINT size = 0; ImageCodecInfo* pImageCodecInfo = NULL; GetImageEncodersSize(&num, &size); if(size == 0) return -1; pImageCodecInfo = (ImageCodecInfo*)(malloc(size)); if(pImageCodecInfo == NULL) return -1; GetImageEncoders(num, size, pImageCodecInfo); for(UINT j = 0; j < num; ++j) { if( StrCmp(pImageCodecInfo[j].MimeType, format) == 0 ) { *pClsid = pImageCodecInfo[j].Clsid; free(pImageCodecInfo); return j; } } free(pImageCodecInfo); return -1; } bool CGdiPlusImage::Save(LPCTSTR pszFileName,LPCTSTR format) { //加载文件 CT2CW strFileName(pszFileName); //图像保存 CLSID Clsid; GetEncoderClsid(format, &Clsid); if( m_pImage->Save((LPCWSTR)strFileName, &Clsid) == Ok ) return true; return false; }
这个类虽然短小,但是五脏俱全,在DrawImage方法中,我们在内部实现了矩阵Matrix,在这里我们需要注意一点,在做矩阵变换的时候,切记要注意顺序,先平移在缩放后旋转,否则会引发意外的效果。其次还需要注意,图片的旋转是依附于旋转中心点进行旋转的,在这里我们指定的是图片的中心点,再次,关于缩放,默认情况下,缩放会相对与左上角的位置进行平移,即,图像放大时,图像会与左上角的坐标会越来越远,反之亦然,所以,为了这种情况的发生,我们缩放后平移的位置减去,使图像一直保持窗口的中央。
关于颜色矩阵,大家可以先看一下这些文章,帮助理解
《GDI+ ColorMatrix的完全揭秘》
《ColorMatrix的含义》
《颜色调整矩阵ColorMatrix详解》
关于保存,GDI+并没有给我们提供GetEncoderClsid函数,所以,在这里我们自行封装GDI+的解码器功能,当然还有简单的方法,使用CImage的Save方法同样可以达到保存的目的,不过,本篇主要是讲解GDI+的Image使用过程,CImage的使用就不说了。
关于从桌面等文件夹拖拽显示图像文件,主要是响应WM_DROPFILES消息,不过在添加WM_DROPFILES消息之前,先把对话框的Accept Files属性设为true,否则一切都是徒劳。
而拖拽,我们住需要2个函数就够了,DragQueryFile的返回值,返回我们往窗口中拖进多少张资源,参数用来获取拖进窗口的文件的完整路径,有了路径,剩下加载图片也就简单的不能再简单了
void CImageHandleDlg::OnDropFiles(HDROP hDropInfo) { UINT nCount; TCHAR szFilePath[MAX_PATH]; nCount = DragQueryFile(hDropInfo, 0xFFFFFFFF, NULL, 0); if(nCount) { DragQueryFile(hDropInfo, nCount-1, szFilePath, sizeof(szFilePath)); } DragFinish(hDropInfo); if ( m_Image.IsNull() == false ) { m_Image.DestroyImage(); } if( !m_Image.LoadImage(szFilePath) ) { AfxMessageBox(TEXT("加载资源失败!")); return; } CRect rcClient; GetClientRect(&rcClient); //初始化变量 m_ptPos.SetPoint((rcClient.Width()-m_Image.GetWidth())/2,(rcClient.Height()-m_Image.GetHeight())/2); m_fScale = 1.0f; m_nAngle = 0; //恢复数据 m_Image.SetRotation(m_nAngle); m_Image.SetScale(m_fScale,m_fScale); Invalidate(FALSE); CBaseDialog::OnDropFiles(hDropInfo); }
如上示例代码中,通过DragQueryFile我们返回出用户拖进多少个文件,根据返回的数量,我们可以挨个遍历这些文件,依次通过DragQueryFile继续把文件的路径信息全部查询出来,在这里我也只是将最后的一个文件的地址返回了出来,如果你的项目中需要处理多个文件,那么就for遍历挨个取路径吧
最后,关于保存文件框CFileDialog,CFileDialog默认情况下是不会给文件追加后缀名的,除非我们手工输入后缀名,不过,我们可以通过获取CFileDialog下方的类型来追加后缀名,(ps:这里很多朋友之前都遇到过这种问题,这里大家也留心一下)
CString strFileName; CString strExtension; strFileName = dlg.m_ofn.lpstrFile; switch (dlg.m_ofn.nFilterIndex) { case 1: strExtension = TEXT("bmp"); break; case 2: strExtension = TEXT("jpeg"); break; case 3: strExtension = TEXT("gif"); break; case 4: strExtension = TEXT("png"); break; default: break; } strFileName = strFileName + TEXT('.') + strExtension;
好了,总的情况就这些了,更多的图片处理和特效,大家可以继续扩展
源码下载地址:
http://pan.baidu.com/s/11QpOi