使用GDI+進行圖片處理時要注意的問題

與GDI相比,GDI+要強大很多。對於Windows應用程序來說,用GDI是比較多的,也是比較熟練的,GDI+相對用的較少一點,但是現在GDI+的使用已經很普遍了。GDI+支持各種類型圖片的處理,比如常見的bmp、jpg、gif、png等類型,特別是GDI+處理png圖片時有很大的優勢。有時我們需要將圖片文件加載到內存中,然後進行UI的繪製,由於要支持多種類型的圖片的載入,所以首先想到的是使用GDI+中的圖片處理類Image或Bitmap。有時我們也需要將內存中的位圖數據,保存成各種類型的圖片文件,我們也要用到圖片處理類Image或Bitmap。GDI+功能強大,但相對GDI而言,要難用很多,在使用的過程中也有很多需要注意的地方。下面結合本人在實際開發過程中遇到的問題,進行一些總結,以供參考。

        1、GDI+庫的加載與卸載

        在程序初始化時,添加加載GDI+的代碼:

  1. ULONG_PTR m_gdiplusToken;  
  2.   
  3. // 初始化GDI+  
  4. Gdiplus::GdiplusStartupInput gdiplusStartupInput;  
  5. Gdiplus::GdiplusStartup( &m_gdiplusToken, &gdiplusStartupInput, NULL );  

         在程序退出時,添加卸載GDI+的代碼:

  1. // 釋放GDI+資源  
  2. Gdiplus::GdiplusShutdown( m_gdiplusToken );  
          在使用GDI+中相關函數和結構時,儘量加上Gdiplus命名空間名,以防止與其他模塊的代碼因爲字段的名稱相同出現衝突。比如,GDI+庫中定義GDI+函數執行結果的每句類型Status,定義如下所示。如果我們需要判斷函數是否正確執行,應該將返回值和Gdiplus::Ok,而不是直接和Ok比較,注意這個加上Gdiplus命名空間名的好習慣。

  1. enum Status  
  2. {  
  3.     Ok = 0,  
  4.     GenericError = 1,  
  5.     InvalidParameter = 2,  
  6.     OutOfMemory = 3,  
  7.     ObjectBusy = 4,  
  8.     InsufficientBuffer = 5,  
  9.     NotImplemented = 6,  
  10.     Win32Error = 7,  
  11.     WrongState = 8,  
  12.     Aborted = 9,  
  13.     FileNotFound = 10,  
  14.     ValueOverflow = 11,  
  15.     AccessDenied = 12,  
  16.     UnknownImageFormat = 13,  
  17.     FontFamilyNotFound = 14,  
  18.     FontStyleNotFound = 15,  
  19.     NotTrueTypeFont = 16,  
  20.     UnsupportedGdiplusVersion = 17,  
  21.     GdiplusNotInitialized = 18,  
  22.     PropertyNotFound = 19,  
  23.     PropertyNotSupported = 20,  
  24. #if (GDIPVER >= 0x0110)  
  25.     ProfileNotFound = 21,  
  26. #endif //(GDIPVER >= 0x0110)  
  27. };  
  1.   

      2、靜態函數FromFile、FromHBitmap和FromStream的使用

FromFile主要是將圖片文件加載到GDI+對象中,FromHBitmap和FromStream函數則是將內存中的圖片數據加載到GDI+對象中。我們平常處理圖片加載與格式轉換時主要用到兩個類:Bitmap類和Image類。Bitmap類繼承於Image類,這三個函數它都有。Image類則只有FromFile和FromStream函數。在使用這三個函數時,要注意一下幾點。

         (1)  對於FromFile、FromHBitmap和FromStream這三個函數,都是靜態函數,MSDN對於返回值的說明:This method returns a pointer to the new Bitmap/Image object(在VS中GO到函數的定義出也是能看出來的,函數返回是new出來的對象)。這意味着什麼呢?因爲返回的是新創建的類的對象,是需要我們使用者來負責銷燬的,即對象使用完了後需要我們手動將之delete掉。如果不delete掉,不僅會導致內存泄漏,也會導致GDI句柄泄漏。這點在我們的項目開發中是深有體會的,特別是GDI句柄泄漏使用了專門的工具進行檢測的。

        (2) 在使用Image::FromFile時,要注意將指定的文件加載到Image對象中後,會將磁盤上對應的文件“鎖住”,其他地方如果要同時加載該文件則可能會出問題,這也是我們在開發過程中遇到的問題。我們的處理辦法是,不使用Image::FromFile函數,使用Image::FromStream。對於Image::FromStream,我們先將文件讀到內存中,然後再將內存中數據倒到流中,然後調用Image::FromStream從流中將圖片數據加載到Image對象中。使用Image::FromStream的流程較複雜,使用時要注意,也有一些陷阱,下面我們會談到。

         (3) 對於GDI+提供的函數,對於需要傳入字符串的參數,一般均是WCHAR*寬字節類型,所以在調用之前要確保傳入字符串是寬字節的。這點和COM接口類似,一般都要傳入寬字節的字符串。

       3、Image::FromStream的使用

           此處主要講如何將圖片文件加載到Image對象中的,使用Image::FromStream加載的流程大概爲:先將圖片文件讀到HGLOBAL內存中,然後調用CreateStreamOnHGlobal函數在HGLOBAL內存數據基礎上創建流,最後調用Image::FromStream將圖片數據加載到new出來的Image對象中。相關的代碼如下所示:

  1. Image* m_pImg; // 定義成CXXXXXXXXX類的成員變量  
  2.   
  3. BOOL CXXXXXXXXX::Load( LPCTSTR pszFileName )  
  4. {  
  5.     ASSERT( pszFileName != NULL );  
  6.   
  7.     CFile file;  
  8.     DWORD dwSize;  
  9.   
  10.         // 打開文件  
  11.     if ( !file.Open( szFileName,  
  12.         CFile::modeRead |   
  13.         CFile::shareDenyWrite ) )  
  14.     {  
  15.         TRACE( _T( "Load (file): Error opening file %s\n" ), szFileName );  
  16.         return FALSE;  
  17.     };  
  18.   
  19.         // 根據文件大小分配HGLOBAL內存  
  20.     dwSize = (DWORD)file.GetLength();  
  21.     HGLOBAL hGlobal = GlobalAlloc( GMEM_MOVEABLE | GMEM_NODISCARD, dwSize );  
  22.     if ( !hGlobal )  
  23.     {  
  24.         TRACE( _T( "Load (file): Error allocating memory\n" ) );  
  25.         return FALSE;  
  26.     };  
  27.   
  28.     char *pData = reinterpret_cast<char*>(GlobalLock(hGlobal));  
  29.     if ( !pData )  
  30.     {  
  31.         TRACE( _T( "Load (file): Error locking memory\n" ) );  
  32.         GlobalFree( hGlobal );  
  33.         return FALSE;  
  34.     };  
  35.   
  36.         // 將文件內容讀到HGLOBAL內存中  
  37.     TRY  
  38.     {  
  39.         file.Read( pData, dwSize );  
  40.     }  
  41.     CATCH( CFileException, e );                                            
  42.     {  
  43.         TRACE( _T( "Load (file): An exception occured while reading the file %s\n"),  
  44.             szFileName );  
  45.         GlobalFree( hGlobal );  
  46.         e->Delete();  
  47.         file.Close();  
  48.         return FALSE;  
  49.     }  
  50.     END_CATCH  
  51.   
  52.     GlobalUnlock( hGlobal );  
  53.     file.Close();  
  54.   
  55.         // 利用hGlobal內存中的數據創建stream  
  56.     IStream *pStream = NULL;  
  57.     if ( CreateStreamOnHGlobal( hGlobal, TRUE, &pStream ) != S_OK )  
  58.     {  
  59.         return FALSE;  
  60.     }  
  61.   
  62.     m_pImg = Image::FromStream( pStream );  
  63.         ASSERT( m_pImg != NULL )  
  64.   
  65.     // 要加上這一句,否則由GlobalAlloc得來的hGlobal內存沒有被釋放,導致內存泄露,由於  
  66.     // CreateStreamOnHGlobal第二個參數被設置爲TRUE,所以調用pStream->Release()會自動  
  67.     // 將hGlobal內存(參見msdn對CreateStreamOnHGlobal的說明)  
  68.     pStream->Release();  
  69.    
  70.         .......// 後續代碼此處省略  
  71. }  

如上面的代碼,必須要加上pStream->Release();這句,否則會導致內存泄漏,因爲上面GlobalAlloc來的內存沒有釋放。但是代碼中使用完後並沒有調用GlobalFree來釋放內存,那自動釋放內存是如何做到的呢?那我們就來看看MSDN中,對CreateStreamOnHGlobal函數的說明:

  1. WINOLEAPI CreateStreamOnHGlobal(  
  2.   __in          HGLOBAL hGlobal,  
  3.   __in          BOOL fDeleteOnRelease,   // 主要看這個參數的說明  
  4.   __out         LPSTREAM* ppstm  
  5. );  
參數fDeleteOnRelease的說明:A value that indicates whether the underlying handle for this stream object should be automatically freed when the stream object is released.If set to FALSE, the caller must free the hGlobal after the final release. If set to TRUE, the final release will automatically free the hGlobal parameter.

也就是說,當將fDeleteOnRelease參數設置爲FALSE時,調用pStream->Release();時就不會自動釋放GlobalAlloc來的內存,此時必須手動調用GlobalFree來釋放;當將fDeleteOnRelease參數設置爲TRUE時,在調用pStream->Release();是會自動將GlobalAlloc來的內存釋放掉。

       4、GDI+的繪圖渲染能力

         當我們在用GDI繪製斜線線條(非水平線條、非豎直線條)時,會有明顯的鋸齒,看起來效果不太好。用GDI+繪製則要好很多,因爲GDI+的渲染效果要比GDI好很多,平滑很多。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章