首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC++

GDI+ 內存圖片格式轉換完全實現 (BMP/PNG/JPG/GIF/TIFF 互轉)

     編輯:VC++

為什麼選擇 GDI+?

GDI+ 是 Windows XP 以降系統內建的現代 2D 圖形庫,原生支持 BMP、PNG、JPEG、GIF、TIFF、EMF、WMF、ICO 等主流格式讀寫,無需第三方庫 (如 libpng, libjpeg),部署零依賴,API 面向對象,適合內存流操作。

核心流程:內存流 → Image → 內存流

  1. 源數據 (`CMemFile`/`CFile`) → `GlobalAlloc` → `CreateStreamOnHGlobal` → `IStream*`
  2. `Image::FromStream(pStream)` 創建 `Image` 對象 (自動識別格式)
  3. 目標格式 → `GetEncoderClsid(L"image/png")` 獲取 `CLSID`
  4. `Image->Save(pOutStream, &clsid, ¶ms)` 編碼保存
  5. `IStream` → `GlobalLock` → 寫入目標 `CMemFile`/`CFile`

完整封裝類實現

1. 頭文件設置

// StdAfx.h 或頭文件
#ifndef ULONG_PTR_DEFINED
#define ULONG_PTR unsigned long*
#endif
#include 
using namespace Gdiplus;
#pragma comment(lib, "gdiplus.lib")

2. 類成員與初始化

class CImageConverter {
    GdiplusStartupInput m_gdiplusSI;
    ULONG_PTR m_gdiplusToken;

public:
    CImageConverter() {
        GdiplusStartup(&m_gdiplusToken, &m_gdiplusSI, NULL);
    }
    ~CImageConverter() {
        GdiplusShutdown(m_gdiplusToken);
    }

    // 核心轉換函數
    bool Convert(CMemFile& src, CMemFile& dst, const CString& strTargetMimeType);
    // 輔助:MIME -> CLSID
    static bool GetEncoderClsid(const WCHAR* mimeType, CLSID* pClsid);
};

3. 核心轉換邏輯

bool CImageConverter::Convert(CMemFile& src, CMemFile& dst, const CString& strTargetMimeType) {
    // 1. 源數據 -> IStream
    DWORD dwSrcLen = src.GetLength();
    HGLOBAL hSrcMem = GlobalAlloc(GMEM_MOVEABLE, dwSrcLen);
    if (!hSrcMem) return false;
    LPBYTE pSrcData = (LPBYTE)GlobalLock(hSrcMem);
    src.SeekToBegin();
    src.Read(pSrcData, dwSrcLen);
    GlobalUnlock(hSrcMem);

    IStream* pSrcStream = NULL;
    HRESULT hr = CreateStreamOnHGlobal(hSrcMem, TRUE, &pSrcStream); // TRUE: 釋放流時自動釋放 HGLOBAL
    if (FAILED(hr) || !pSrcStream) { GlobalFree(hSrcMem); return false; }

    // 2. 創建 Image 對象
    Image* pImage = Image::FromStream(pSrcStream, FALSE); // FALSE: 不驗證完整性,加快速度
    pSrcStream->Release(); // Image 內部複製了數據,流可釋放
    if (!pImage || pImage->GetLastStatus() != Ok) {
        if (pImage) delete pImage;
        return false;
    }

    // 3. 獲取目標編碼器 CLSID
    CLSID clsidEncoder;
    USES_CONVERSION;
    if (!GetEncoderClsid(A2W(strTargetMimeType), &clsidEncoder)) {
        delete pImage;
        return false;
    }

    // 4. 目標 IStream (內存可增長)
    IStream* pDstStream = NULL;
    HGLOBAL hDstMem = GlobalAlloc(GMEM_MOVEABLE, 0); // 0 初始大小,自動增長
    if (!hDstMem) { delete pImage; return false; }
    hr = CreateStreamOnHGlobal(hDstMem, TRUE, &pDstStream);
    if (FAILED(hr) || !pDstStream) { GlobalFree(hDstMem); delete pImage; return false; }

    // 5. 編碼保存 (可選 EncoderParameters 設置質量等)
    EncoderParameters ep = {0};
    // 示例:JPEG 質量 90
    // if (strTargetMimeType.CompareNoCase(_T("image/jpeg")) == 0) { ... 設置 ep ... }
    hr = pImage->Save(pDstStream, &clsidEncoder, &ep);
    delete pImage;
    if (FAILED(hr)) { pDstStream->Release(); return false; }

    // 6. IStream -> CMemFile
    LARGE_INTEGER liZero = {0};
    pDstStream->Seek(liZero, STREAM_SEEK_SET, NULL); // 重置指針
    STATSTG stat; pDstStream->Stat(&stat, STATFLAG_NONAME);
    ULONG dstSize = (ULONG)stat.cbSize.QuadPart;

    LPBYTE pDstData = (LPBYTE)GlobalLock(hDstMem);
    dst.SeekToBegin();
    dst.Write(pDstData, dstSize);
    GlobalUnlock(hDstMem);

    pDstStream->Release(); // 自動釋放 hDstMem
    return true;
}

4. 關鍵輔助函數:MIME 類型查找編碼器 CLSID

bool CImageConverter::GetEncoderClsid(const WCHAR* mimeType, CLSID* pClsid) {
    UINT numEncoders = 0, size = 0;
    GetImageEncodersSize(&numEncoders, &size);
    if (size == 0) return false;

    ImageCodecInfo* pCodecs = (ImageCodecInfo*)malloc(size);
    if (!pCodecs) return false;
    GetImageEncoders(numEncoders, size, pCodecs);

    bool found = false;
    for (UINT i = 0; i < numEncoders; i++) {
        if (wcscmp(pCodecs[i].MimeType, mimeType) == 0) {
            *pClsid = pCodecs[i].Clsid;
            found = true;
            break;
        }
    }
    free(pCodecs);
    return found;
}

支持格式對照表 (常用 MIME → 副檔名)

MIME 類型副檔名備註
image/bmp.bmp, .dib無壓縮
image/png.png無損壓縮,支持透明通道
image/jpeg.jpg, .jpeg有損壓縮,可調質量
image/gif.gif256 色,支持動畫
image/tiff.tif, .tiff多頁、多種壓縮
image/x-emf.emf增強元文件 (矢量)
image/x-wmf.wmfWindows 元文件

進階:設置 JPEG 壓縮質量

EncoderParameters ep;
ep.Count = 1;
ep.Parameter[0].Guid = EncoderQuality;
ep.Parameter[0].Type = EncoderParameterValueTypeLong;
ep.Parameter[0].NumberOfValues = 1;
ULONG quality = 90; // 0-100
ep.Parameter[0].Value = &quality;

pImage->Save(pDstStream, &clsidJpeg, &ep);

錯誤處理與資源管理要點

  • GDI+ 初始化/關閉: 進程級單例,不可重複調用。建議封裝為單例類或在 `CWinApp::InitInstance`/`ExitInstance` 中管理。
  • COM 參考計數: `IStream*`、`Image*` 必須 `Release()`/`delete`,配合 `CComPtr`/`std::unique_ptr` 自動化。
  • 全局內存鎖定: `GlobalLock`/`GlobalUnlock` 嚴格配對,`CreateStreamOnHGlobal(hMem, TRUE)` 設置 `fDeleteOnRelease=TRUE` 簡化釋放邏輯。
  • 異常安全: 關鍵資源分配後立即檢查失敗路徑釋放,或使用 RAII 封裝。

調用示例

void OnConvertBmpToPng() {
    CFileDialog dlg(TRUE, _T("bmp"), NULL, OFN_FILEMUSTEXIST, _T("BMP Files (*.bmp)|*.bmp||"));
    if (dlg.DoModal() != IDOK) return;

    CMemFile src, dst;
    // 讀入源文件
    CFile file(dlg.GetPathName(), CFile::modeRead | CFile::shareDenyWrite);
    src.SetLength(file.GetLength());
    file.Read(src.GetBuffer(), (UINT)file.GetLength());
    file.Close();

    // 轉換
    CImageConverter conv;
    if (conv.Convert(src, dst, _T("image/png"))) {
        // 保存目標
        CString outPath = dlg.GetPathName(); outPath.Replace(_T(".bmp"), _T(".png"));
        CFile outFile(outPath, CFile::modeCreate | CFile::modeWrite);
        dst.SeekToBegin();
        BYTE* buf = new BYTE[dst.GetLength()];
        dst.Read(buf, (UINT)dst.GetLength());
        outFile.Write(buf, (UINT)dst.GetLength());
        delete[] buf;
        AfxMessageBox(_T("轉換成功!"));
    } else {
        AfxMessageBox(_T("轉換失敗:格式不支持或數據損壞"));
    }
}

總結

GDI+ 提供了極其簡潔的內存圖片格式轉換能力:`IStream` 橋接內存與 `Image` 對象,`GetEncoderClsid` 解析目標格式,`Image::Save` 一鍵編碼。封裝為 `CImageConverter` 類後,業務代碼僅需指定源/目標內存流與 MIME 類型。零外部依賴、支持主流格式、可調編碼參數,是 Windows 平台 C++ 圖片處理的首選方案。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved