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()中新增如下程式碼:
注意:初始化GDI的部分一定要放在ShowWindow的前面,否則圖片會無法顯示。// 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();
下面就能顯示圖片了,在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;
}