GDI+是微软提供的做图形处理方面的一套类库,这里记录下学习过程,还有MFC的学习过程。想完成一个能打开,显示图像并进行特效处理,转存的小程序。
一.环境搭建和小测试
由于VC6.0不带GDI+的类库,需要自己下载相关文件,貌似高版本的VS会自带GDI+。
创建一个多文档的工程,名称为"UsingGDIPlus"。
将下载好的Includes文件路径添加到VC的Directories->Include files目录下,而且顺序必须是第一个,否则可能会出错。将"GdiPlus.lib"文件拷贝到工程目录下,与CPP文件同级,将"gdiplus.dll"拷贝到运行目录下,与"exe"同级。
在StdAfx.h文件中添加如下代码:
#define ULONG_PTR ULONG
#pragma comment( lib, "gdiplus.lib" )
#include <gdiplus.h>
using namespace Gdiplus;
主要要加#define ULONG_PTR ULONG,否则会报错。
在APP的类中添加一个全局变量,如:
ULONG_PTR gdiplusToken;
然后在InitInstance()中添加如下代码:
// Initialize GDI+.
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
// The main window has been initialized, so show and update it.
pMainFrame->ShowWindow(m_nCmdShow);
pMainFrame->UpdateWindow();
注意:初始化GDI的部分一定要放在ShowWindow的前面,否则图片会无法显示。
下面就能显示图片了,在View类的OnDraw函数中,添加如下代码:
Gdiplus::Graphics graphics(pDC->GetSafeHdc());
Gdiplus::Image image(L"E:\\WorkSpace_VC\\UsingGDIPlus\\Debug\\123.jpg");
graphics.DrawImage(&image, 0, 0);
这个地方使用的绝对路径,不知道为什么,使用相对路径就会出现OutOfMemoy的错误。
Gdiplus::Graphics graphics(pDC->GetSafeHdc());
初始化一个Graphics对象,主要使用GdipCreateFromHDC方面创建一个绘图区域,GdipCreateFromHDC一个参数就是传入的hdc,应外一个返回参数就是GpGraphics的一个指针对象,再把这个指针对象再赋值给Graphics类的成员变量nativeGraphics。这就是构造函数做的事情。
Gdiplus::Image image(L"E:\\WorkSpace_VC\\UsingGDIPlus\\Debug\\123.jpg");
其实就是使用GdipLoadImageFromFile(filename, &nativeImage);方法加载图片。并且会对Image的成员变量nativeImage赋值。不明白这个nativeImage是干什么用的。
graphics.DrawImage(&image, 0, 0);
调用GdipDrawImageI(nativeGraphics, image ? image->nativeImage: NULL, x, y)在View中显示图片,x,y代表从窗口中声明位置开始绘图,从窗口的左上角开始算起。
运行程序,就应该能够看到图片了。
二.双缓冲和图像刷新问题。
显示图片后,不停地拖动窗口以改变其大小,这时候就会发现窗口在闪烁,效果不是很好。因为每一次的窗口改变都要檫除背景再重新绘图,而且在OnDraw中要加载图片,这样效率低闪烁的也就比较明显了。要预防这种情况一般采用双缓冲的方法,即:先在内存中创建一块区域来,把图像数据加载进来,然后在使用Bitblt方法从内存把数据显示出来,这样图像数据一直在内存里,不用每次都加载图像文件。另外还需要对View类的OnEraseBkgnd消息进行处理,让它直接返回,不要擦除背景。这样几乎就看不到闪烁了。
大致代码如下:
void CUsingGDIPlusView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo)
{
// TODO: Add your specialized code here and/or call the base class
if (!m_bIsHasContent)
{
CBitmap bmp;
m_memoryDC.CreateCompatibleDC(pDC);
bmp.CreateCompatibleBitmap(pDC, 800, 600);
m_memoryDC.SelectObject(&bmp);
Gdiplus::Graphics graphics(m_memoryDC.m_hDC);
Gdiplus::Image image(L"E:\\WorkSpace_VC\\UsingGDIPlus\\Debug\\123.jpg");
graphics.DrawImage(&image, 0, 0, 800, 600);
m_bIsHasContent = TRUE;
}
CView::OnPrepareDC(pDC, pInfo);
}
m_memoryDC是CDC的一个对象,使用CreateCompatibleDC方法创建一个与指定pDC兼容的内存设备上下文(memory device context)。device context一般称为设备上下文,可以理解为应用程序和硬件设备之间的一个桥梁,通过它程序就不必关心底层硬件是什么,一切交由系统处理和硬件打交道的过程。然后使用CBitmap的CreateCompatibleBitmap初始化与指定pDC兼容的bitmap对象。再将这个bitmap对象加载到创建的内存设备上下文中。这样再使用GDI+的方法将image数据加载到内存中去。
BOOL CUsingGDIPlusView::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
return TRUE;
return CView::OnEraseBkgnd(pDC);
}
让OnEraseBkgnd直接返回TRUE,这样就不会不停地檫除背景了。
void CUsingGDIPlusView::OnDraw(CDC* pDC)
{
CUsingGDIPlusDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
// Gdiplus::Graphics graphics(pDC->GetSafeHdc());
// Gdiplus::Image image(L"E:\\WorkSpace_VC\\UsingGDIPlus\\Debug\\123.jpg");
// graphics.DrawImage(&image, 0, 0);
pDC->BitBlt(0,0,800,600,&m_memoryDC,0,0,SRCCOPY);
}
使用BitBlt方法将内存中的Bitmap对象拷贝到当前的设备上下文中,这样图像就能显示,改变窗口大小也就不会闪烁了。
但是这样会有另外一个问题,由于不檫除背景,当窗口尺寸大于图像尺寸后,大于的这个部分BitBlt无法绘制,所以就会有重影,这时候就需要程序处理了。下面就开始MFC的练习了,一步一步,做个简单的图像特效处理程序。大概思路是使用GDI+打开,加载图片,然后自己再处理内存中的RGB数据。这中间还会有MFC学习过程。
开始之前的思考:
因为基于多文档的程序,所以可以同时打开多个文件,在User没有关闭这个图片或者程序之前需要将这些图片信息保存下来,这些图片信息使用自定义的一个结构体(_IMAGEFILEINFO)来保存,定义一个CMyImage类提供对_IMAGEFILEINFO的一些操作。还有就是再把GDI+封装一下。使用std::list存储自定义类的指针。这样应该就没什么问题了,走一步再看吧。
图片信息的结构体:
enum FILE_TYPE{JPG_FILE, BMP_FILE, GIF_FILE, TIFF_FILE, PNG_FILE, ICON_FILE, ERROR_FILE};
typedef struct _IMAGEFILEINFO
{
wchar_t* pFileName;
wchar_t* pFilePath;
FILE_TYPE nFileType;
//Overload == operator
bool operator==(_IMAGEFILEINFO& rhs) const
{
if (wcscmp(pFilePath, rhs.pFilePath) == 0)
return true;
else
return false;
}
}IMAGEFILEINFO, *PIMAGEFILEINFO;
三.修改打开对话框:
首先修改程序每次启动时都会默认打开一个文档,修改方法如下:在App的InitInstance函数中添加如下代码:
// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
cmdInfo.m_nShellCommand = CCommandLineInfo::FileNothing; //Added:For don't open any doc at luanch.
查看CCommandLineInfo()的构造函数,其中将m_nShellCommand = FileNew;所以才每次新建一个doc,m_nShellCommand的值是一个枚举变量:
enum { FileNew, FileOpen, FilePrint, FilePrintTo, FileDDE,
AppUnregister, FileNothing = -1 } m_nShellCommand;
然后查看Resource的Menu资源,发现有两Menu,测试发现它们分别在不打开任何文档下显示IDR_MAINFRAME,在打开文档后显示IDR_USINGGTYPE,但其总打开的菜单项的资源ID都是ID_FILE_OPEN,说明只需要处理一个消息响应函数就行了。
在App类中添加打开菜单项的响应函数,void CUsingGDIPlusApp::OnFileOpen();注意不要在View,Doc或者MainFrame中添加,因为存在不打开任何文档的情况,所以View,Doc无法响应这种情况,而打开文档之后,MainFrame又无法响应,所以在App类添加响应函数,在任何情况下都能响应打开文件的消息。
因为GDI+好像只支持九中图像格式,所以在打开对话框那里最好进行文件类型的过滤,只显示支持的文件。打开对话框代码:
CString strFileTypeFilter = "JPG File(*.jpg *.jpeg)|*.jpg|\
BMP File (*.bmp)|*.bmp|\
GIF File(*.gif)|*.gif|\
TIFF File(*.tif)|*.tif|\
PNG File(*.png)|*.png|\
ICON FIle(*.ico)|*.ico|\
All Files(*.*)|*.*||";
CFileDialog openFileDlg(TRUE, //Set to TRUE to construct a File Open dialog box or FALSE to construct a File Save As dialog box.
NULL, //The default filename extension.
NULL, //The initial filename that appears in the filename edit box.
OFN_HIDEREADONLY /*Hides the Read Only check box*/ | OFN_OVERWRITEPROMPT, //Causes the Save As dialog box to generate a message box if the selected file already exists. The user must confirm whether to overwrite the file.
strFileTypeFilter, //A series of string pairs that specify filters you can apply to the file.
NULL);//A pointer to the file dialog-box object’s parent or owner window.
四.添加打开的文件到List中
if (openFileDlg.DoModal() == IDOK)
{
PIMAGEFILEINFO pifi = new _IMAGEFILEINFO[sizeof(struct _IMAGEFILEINFO)];
ASSERT(NULL != pifi);
pifi->nFileType = ERROR_FILE;
pifi->pFileName = NULL;
pifi->pFilePath = NULL;
CMyImage mi;
BOOL bIsError = FALSE;
int nRes = mi.SetImageGFileInfo(openFileDlg.GetPathName(), openFileDlg.GetFileName(), openFileDlg.GetFileExt(), pifi);
if (nRes == ERROR_FILE)
{
::AfxMessageBox(_T("The select file type error"));
bIsError = TRUE;
}
if (mi.CheckImageHasBeenOpen(pifi, m_openImageList))
{
::AfxMessageBox(_T("The select file has been open"));
bIsError = TRUE;
}
if (bIsError)
{
mi.DeleteOneImageInfo(pifi); //Should delete new memory, otherwise may memory leak.
return;
}
else //Add to the list.
{
m_openImageList.push_back(pifi);
this->OpenDocumentFile(openFileDlg.GetPathName()); //Call doc class OnOpenDocument(LPCTSTR lpszPathName) to let MFC deal default open operation.
}
}
释放内存:
<span style="font-family: Arial, Helvetica, sans-serif;">/**********************************************************************************</span>
* DeleteOneImageInfo(PIMAGEFILEINFO pifi)
* Delete new memory in open file.
**********************************************************************************/
void CMyImage::DeleteOneImageInfo(PIMAGEFILEINFO pifi)
{
ASSERT(NULL != pifi);
if (NULL != pifi->pFileName)
{
delete[] pifi->pFileName;
pifi->pFileName = NULL;
}
if (NULL != pifi->pFilePath)
{
delete[] pifi->pFilePath;
pifi->pFilePath = NULL;
}
delete pifi;
pifi = NULL;
}
/**********************************************************************************
* DeleteImageInfo()
* Delete all memory space in open image list. For destructor use.
**********************************************************************************/
void CMyImage::DeleteAllImageInfo(std::list<PIMAGEFILEINFO>& openList)
{
for (std::list<PIMAGEFILEINFO>::iterator it = openList.begin(); it != openList.end();)
{
PIMAGEFILEINFO pifi = *it;
DeleteOneImageInfo(pifi); //Delete the item, but it still in list.
pifi = NULL;
openList.erase(it); //Erase from list.
if (openList.size() > 0)
it = openList.begin(); //When erase item in list, list size has been changed, so need restart iterator.
else
return;
}
}
SetImageGFileInfo函数:
/************************************************************************
* CImageEditor::SetImageGFileInfo(const wchar_t* pPath, const wchar_t* pFileName, const wchar_t* pFileExt)
* Set file full path, file name, file type info.
* const wchar_t* pPath: (IN)The open image file path.
* const wchar_t* pFileName: (IN)Only file name.
* const wchar_t* pFileExt: (IN)File exension.
* Return 0 if all OK.
************************************************************************/
int CMyImage::SetImageGFileInfo(const wchar_t* pPath, const wchar_t* pFileName, const wchar_t* pFileExt, PIMAGEFILEINFO pifi)
{
ASSERT(NULL != pPath);
ASSERT(NULL != pFileName);
ASSERT(NULL != pFileExt);
ASSERT(NULL != pifi);
//Get file full path.
SIZE_T nStrLen = wcslen(pPath);
pifi->pFilePath = new wchar_t[nStrLen + 1];
ASSERT(NULL != pifi->pFilePath);
wmemset(pifi->pFilePath, 0, nStrLen + 1);
wcscpy(pifi->pFilePath, pPath);
//Get file name with extension.
nStrLen = wcslen(pFileName);
pifi->pFileName = new wchar_t[nStrLen + 1];
ASSERT(NULL != pifi->pFileName);
wmemset(pifi->pFileName, 0, nStrLen + 1);
wcscpy(pifi->pFileName, pFileName);
//Get file type.
FILE_TYPE nType = GetImageType(pFileExt);
if (nType == ERROR_FILE)
return ERROR_FILE;
else
pifi->nFileType = nType;
return 0;
}