1. 程式人生 > >VC6使用GDI+進行影象的特效處理和MFC學習筆記-1

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