VC6使用GDI+进行图像的特效处理和MFC学习笔记-1

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;
}


















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Visual C++6.0使用GDI+的一般方法 1. 载解压GDI+开发包; 2. 正确设置include & lib 目录; 3. stdafx.h 添加: #ifndef ULONG_PTR #define ULONG_PTR unsigned long* #endif #include 4. 程序中添加GDI+的包含文件gdiplus.h以及附加的类库gdiplus.lib。 通常gdiplus.h包含文件添加在应用程序的stdafx.h文件中,而gdiplus.lib可用两种进行添加: 第一种是直接在stdafx.h文件中添加下列语句: #pragma comment( lib, "gdiplus.lib" ) 另一种方法是: 在VC.net中添加库文件在:项目菜单->属性->链接器->输入 举个例子: (1)在应用程序项目的应用类中,添加一个成员变量,如下列代码: ULONG_PTR m_gdiplusToken; 其中,ULONG_PTR是一个DWORD数据类型,该成员变量用来保存GDI+被初始化后在应用程序中的GDI+标识,以便能在应用程序退出后,引用该标识来调用Gdiplus:: GdiplusShutdown来关闭GDI+。 (2)在应用类中添加ExitInstance的重载,并添加下列代码用来关闭GDI+: int CGDITestApp::ExitInstance() { Gdiplus::GdiplusShutdown(m_gdiplusToken); return CWinApp::ExitInstance(); } (3)在应用类的InitInstance函数中添加GDI+的初始化代码: 注意:下面这些GDI+的初始化代码必须放在m_pMainWnd->UpdateWindow();之前。 CWinApp::InitInstance(); Gdiplus::GdiplusStartupInput gdiplusStartupInput; Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL); (4)在需要绘图的窗口或视图类中添加GDI+的绘制代码。 下面分别就单文档和基于对话框应用程序为例,说明使用GDI+的一般过程和方法。 1. 在单文档应用程序中使用GDI+ 在上面的过程中,我们就是以一个单文档应用程序Ex_GDIPlus作为示例的。下面列出第4步所涉及的代码: void CGDITestView::OnDraw(CDC* pDC) { CGDITestDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here usingnamespace Gdiplus; Graphics graphics(pDC->m_hDC); Pen newPen(Color(255,0,0),3); HatchBrush newBrush(HatchStyleCross,Color(255,0,255,0),Color(255,0,0,255));//创建一个填充画刷,前景色为绿色,背景色为蓝色 graphics.DrawRectangle(&newPen,50,50,100,60);// 在(50,50)处绘制一个长为100,高为60的矩形 graphics.FillRectangle(&newBrush,50,50,100,60); // 在(50,50)处填充一个长为100,高为60的矩形区域 } 编译并运行,结果如图:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值