1. 程式人生 > >使用GDI +載入JPG和PNG資源的CGdiPlusBitmap類

使用GDI +載入JPG和PNG資源的CGdiPlusBitmap類

介紹

最近我需要顯示一些JPG和PNG檔案。我有一箇舊版的LeadTools和這兩種格式的開源庫,但希望我的可執行檔案儘可能的小。所以我決定給GDI +一個嘗試。我很快發現GDI +設計不好,非常古怪,但它對我的目的很好,直到我發現我的恐懼,GDI +無法載入儲存為資源的JPG或PNG影象!

像我相信,其他開發人員面臨這個問題,我不相信文件,試圖Bitmap::FromResource無濟於事。在瀏覽可用的Bitmap方法的同時,我碰到過Bitmap::FromStream

經過一些測試和幾個錯誤,主要是由於可怕的GDI +文件,我想出了工作程式碼。休息一晚後,我決定將程式碼封裝在一個簡單的類中,以確保記憶體被釋放。

其結果是兩類:CGdiPlusBitmapCGdiPlusBitmapResource

Gotcha

在討論程式碼本身之前,有一個需要解決的GDI +的警告。使用JPG,某些TIFF和其他格式,原始影象資訊必須始終可用。換句話說,如果您開啟點陣圖Bitmap::FromFile,則在影象開啟時您不能刪除或以其他方式更改該檔案。同樣的限制適用於CGdiPlusBitmapResource(我的測試發現,PNG和BMP檔案似乎沒有遵循這一概括,雖然我不知道這是標準行為還是與我的檔案只是一個僥倖。)

GDI +初始化

在進行任何GDI +呼叫之前,需要初始化GDI +。我建議將以下資料成員新增到派生自

CWinApp

ULONG_PTR m_gdiplusToken;

InitInstance(),新增以下呼叫:

Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);

ExitInstance(),新增以下內容:

Gdiplus::GdiplusShutdown(m_gdiplusToken);

我建立了兩個類,基類是非常簡單的封裝 Bitmap和封裝全域性記憶體的派生類。我想,如果我有耐心和願望,我可以延長這個類,但我沒有必要這樣做。

(如果你好奇為什麼我不是簡單地從ATL類派生出來的CImage,那是因為程式碼被用在一個沒有使用MFC或ATL的程式中,但程式碼很簡單,很容易被修改為使用CImage作為基類。)

我不會干擾CGdiPlusBitmap 類,除非說它有一個公共的資料成員Bitmap* m_pBitmap(在類中,我使用Gdiplus名稱空間來開發GDI +物件,以防開發人員不想宣告using namespace Gdiplus;。)

CGdiPlusBitmapResource類有幾個建構函式和幾個過載Load功能。過載功能簡單地允許像我一樣的惰性程式設計師不需要鍵入MAKEINTRESOURCELoad函式將資源名稱和型別作為字串,並且是類的關鍵。本程式碼全部如下:

inline
bool CGdiPlusBitmapResource::Load(LPCTSTR pName, LPCTSTR pType, 
                                  HMODULE hInst)
{
    Empty();

    HRSRC hResource = ::FindResource(hInst, pName, pType);
    if (!hResource)
        return false;
    
    DWORD imageSize = ::SizeofResource(hInst, hResource);
    if (!imageSize)
        return false;

    const void* pResourceData = ::LockResource(::LoadResource(hInst, 
                                              hResource));
    if (!pResourceData)
        return false;

    m_hBuffer  = ::GlobalAlloc(GMEM_MOVEABLE, imageSize);
    if (m_hBuffer)
    {
        void* pBuffer = ::GlobalLock(m_hBuffer);
        if (pBuffer)
        {
            CopyMemory(pBuffer, pResourceData, imageSize);

            IStream* pStream = NULL;
            if (::CreateStreamOnHGlobal(m_hBuffer, FALSE, &pStream) == S_OK)
            {
                m_pBitmap = Gdiplus::Bitmap::FromStream(pStream);
                pStream->Release();
        if (m_pBitmap)
        { 
          if (m_pBitmap->GetLastStatus() == Gdiplus::Ok)
            return true;

          delete m_pBitmap;
          m_pBitmap = NULL;
        }
            }
            m_pBitmap = NULL;
            ::GlobalUnlock(m_hBuffer);
        }
        ::GlobalFree(m_hBuffer);
        m_hBuffer = NULL;
    }
    return false;
}

我發現程式碼自作聰明,雖然知道返回值的::LoadResource是一個HGLOBAL可能會發現明顯的雙重複制使用CopyMemory混亂。簡而言之,CreateStreamOnHGlobal 需要使用標誌HGLOBAL分配控制代碼。通過GlobalAlloc使用GMEM_MOVEABLE標誌