有人問我如何對圖像進行旋轉處理,也就是讓用戶歪著脖子看圖像,用戶的脖子擰斷了怎麼辦?反正不會來找我......
其實這個問題的一種解決方法是利用二維(x,y坐標中)矩陣轉換實現圖像旋轉。使用高中時所學的三角知識或者大學中的線性代數知識就可以解決。其原理是已知一個點的坐標,那麼這個點的旋轉坐標可以通過(x*cos(A) + y*sin(A),- x*sin(A) + y*cos(A))
求得,這裡A是以弧度為單位的角度(2P弧度=360度)。因此,只要將圖像加載到內存中,然後將它選入設備上下文,接著調用GetPixel和SetPixel,象上面所說的那樣映射所有的像素,便可以實現圖像的旋轉效果。對於90、180、-90度的旋轉,這是一個不錯的方法,因為正弦、余弦的值無外乎+/-1或者0......話還沒等我說完,一塊磚頭就朝我頭上飛過來了,啊唷,我當然不會叫你用這種方法做事情啦!有更好的方法呢。下面就是本文的正題:使用GDI+實現圖像旋轉處理。我想了解GDI+的人不是很多,因為它是Windows中的新東西。要想了解它的細節,請參考MSDN的有關文章。
GDI+是GDI圖形庫的一個增強版本,C++可以使用這個庫。它內建於Windows XP 和Microsoft .NET,而對於Windows 98、Windows NT和Windows 2000,則有一個可重新發布的版本。GDI+是一個C++ API。它用C++類和C++方法。GDI+所包含的內容非常多,遠遠不止我在此所描述的這些。為了使用GDI+,你必須包含(#include)<gdiplus.h>文件,並將工程鏈接到gdiplus.lib庫,這兩個文件包含在最新的Windows SDK中。我對“在MFC程序中顯示JPG/GIF圖像”一文中的例子代碼Myimgapp進行了重寫,並改名為Myimgapp2,其代碼對CPicture類進行了重大改動,因為原來的CPicture主要針對IPicture進行封裝,而這一次主要是封裝GDI+。下面是Myimgapp2運行畫面,如圖一所示。這個程序示范了如何用GDI+來旋轉圖像。
圖一 圖像旋轉90度
前面說過,原來程序中的那個C++類CPicture是基於IPicture接口的,它處理圖像的COM接口。在本文的例子程序Myimgapp2中,我重寫了CPicture的代碼,使用了GDI+裡的圖像類(Image)。對所有的細節都進行了封裝。新的CPicture類不再使用 IPicture 這個COM接口,而是用Image取而代之,所有其它的類如CPictureView 和 CPictureCtrl都能和從前一樣工作。但有兩個障礙要解決:第一個是你必須對GDI庫進行初始化以及最後的終止釋放操作,與其說它是個障礙,還不如說它是GDI+本身的需要,在哪裡進行這兩個操作呢?最佳的地方莫過於在程序的 InitInstance 和 ExitInstance函數中: //初始化 GDI
class CMyApp : public CWinApp {
protected:
GdiplusStartupInput m_gdiplusStartupInput;
ULONG_PTR m_gdiplusToken;
…….
};
//釋放GDI
BOOL CMyApp::InitInstance()
{
VERIFY(GdiplusStartup(&m_gdiplusToken,
&m_gdiplusStartupInput, NULL)==Ok);
…….
}
int CMyApp::ExitInstance()
{
GdiplusShutdown(m_gdiplusToken);
return CWinApp::ExitInstance();
}
CMyApp::m_gdiplusToken 是一個很神奇的東東,它來自GdiplusStartup 並被傳遞到GdiplusShutdown。m_gdiplusStartupInput 是一個結構,它包含某些GDI+的啟動參數。缺省構造函數建立一個智能的缺省值,它又一次證明了C++比C更好。一旦你啟動GDI+,就可以使用它了。原來的CPicture類有一個指向IPicture的指針,而新版的CPicture類有一個指向Image的指針。同樣,它也有可重載的Load函數來從不同的地方加載圖象。例如,下面便是新版的CPicture如何從某個路徑名中加載圖像文件。 BOOL CPicture::Load(LPCTSTR pszPathName)
{
Free();
USES_CONVERSION;
m_pImage = Image::FromFile(A2W(pszPathName),
m_bUseEmbeddedColorManagement);
return m_pImage->GetLastStatus()==Ok;
}
在此代碼段中,重點是GDI+要用寬字符串,所以你要用USES_CONVERSION 和 A2W.。原來的CPicture用Load函數從某個CFile、CArchive、資源ID或流中加載圖像。所有Load函數最終都走到從流中加載圖像的例程:CPicture::Load(IStream*)。當我開始用Image代替IPicture,並用GDI+函數從流中加載數據時,它不工作。情況真是很糟,令人沮喪。問題出在MFC的CArchiveStream類,這個類在CArchive類之上實現流化。可能是CArchiveStream沒有正確實現所有的IStream方法,Image::FromStream無法正確處理基於CArchiveStream的流操作,更糟的是它返回一切OK,但實際上當你試圖顯示或者刪除Image時會失敗。 為了解決這個問題,我重寫了CPicture::Load(UINT nID),其中用到了CreateStreamOnHGlobal函數。這個API函數很好用,它在一個全局內存塊中創建一個流:
// 分配全局內存並在其中創建流
HGLOBAL m_hMem = GlobalAlloc(GMEM_FIXED, len);
BYTE* pmem = (BYTE*)GlobalLock(m_hMem);
memcpy(pmem,lpRsrc,len);
IStream* pstm;
CreateStreamOnHGlobal(m_hMem,FALSE,&pstm);
這裡lpRsrc已經指向內存中的圖形資源。所以基本思路是加載圖像資源,將它拷貝到全局內存中,在內存上創建一個流,然後用Image::FromStream創建一個圖象,有關細節請參見例子源代碼。當對象被銷毀或某人加載另外的圖像時,CPicture自動釋放全局內存。對於從CFile或者CArchive加載的情況——其實,根本不需要考慮這種情況。 以上是加載的方法,下面看看如何顯示圖像,你必須使用GDI+類Graphics——與原來GDI中的設備上下文(HDC或CDC)類似,它也有一個方法DrawImage:
BOOL CPicture::Render(CDC* pDC, CRect rc) const
有關細節請參考源代碼。用Image代替IPicture來表現圖像要簡單一些。Image::GetWidth 和Image::GetHeight分別獲得象素的寬度和高度,這正是你所需要代替IPicture中HIMETRIC單位的東東。一般來說,用GDI+很容易編程,例如下面示范了如何旋轉:
{
…….
Graphics graphics(pDC->m_hDC);
graphics.DrawImage(m_pImage,
rc.left, rc.top, rc.Width(), rc.Height());
} void CPicture::Rotate(RotateFlipType rft)
{
if (m_pImage) {
m_pImage->RotateFlip(rft);
}
}
這個代碼將圖像順時針旋轉90度,下面列出了RotateFlipType所有可能的情況: RotateFlipType 選項 // 用於Image::RotateFlip的枚舉類型
enum RotateFlipType
{
RotateNoneFlipNone = 0,
Rotate90FlipNone = 1,
Rotate180FlipNone = 2,
Rotate270FlipNone = 3,
RotateNoneFlipX = 4,
Rotate90FlipX = 5,
Rotate180FlipX = 6,
Rotate270FlipX = 7,
RotateNoneFlipY = Rotate180FlipX,
Rotate90FlipY = Rotate270FlipX,
Rotate180FlipY = RotateNoneFlipX,
Rotate270FlipY = Rotate90FlipX,
RotateNoneFlipXY = Rotate180FlipNone,
Rotate90FlipXY = Rotate270FlipNone,
Rotate180FlipXY = RotateNoneFlipNone,
Rotate270FlipXY = Rotate90FlipNone
};
GDI+ 的其它函數用於展開和修剪圖像,它甚至還有一個函數Image::GetThumbnailImage用來解決縮略圖問題,有興趣的話不妨自己試一下。 我鼓勵大家去研究一下GDI+。有些程序員因為它速度慢而不喜歡它,但是有很多文檔中提供了一些技巧來改善它的性能。例如,有一個類叫CachedBitmap,這個類以優化設備的格式保存位圖。對於象視頻或高端圖像編輯等圖形敏感的應用程序你可能不會用GDI+來編程(一般都用DirectX),但對於日常的一般圖形應用來說,GDI+不失為一種比GDI更好的選擇。