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

GDI+ 內存圖片格式轉換完全指南 (BMP/PNG/JPEG/GIF/TIFF/EMF 互轉)

     編輯:VC 教學

前言

GDI+ 提供了強大的 Image 類,支持在內存中無縫轉換主流圖片格式,無需落地臨時文件。本文詳細講解 CMemFile ↔ IStream ↔ GDI+ Image 的完整轉換鏈路,並封裝為通用函數 MBmpToMImage。

一、環境初始化

StdAfx.h 預編譯頭配置

#ifndef ULONG_PTR
#define ULONG_PTR unsigned long*
#endif
#include 
using namespace Gdiplus;
#pragma comment(lib, "gdiplus.lib")

類成員與生命週期管理

class CMyImageProcessor {
    GdiplusStartupInput m_gdiplusStartupInput;
    ULONG_PTR m_gdiplusToken;

public:
    CMyImageProcessor() {
        // 應用啟動時調用 (如 CWinApp::InitInstance 或視圖 OnCreate)
        GdiplusStartup(&m_gdiplusToken, &m_gdiplusStartupInput, NULL);
    }
    ~CMyImageProcessor() {
        // 應用退出時調用 (如 CWinApp::ExitInstance 或視圖 OnDestroy)
        GdiplusShutdown(m_gdiplusToken);
    }
    // ... 轉換函數 ...
};

二、核心轉換函數:MBmpToMImage

BOOL MBmpToMImage(CMemFile& cbfBmp, CMemFile& cbfImage, const CString& strType) {
    // 1. 參數檢查
    int iBmpSize = cbfBmp.GetLength();
    if (iBmpSize <= 0) return FALSE;

    // 2. 分配全局內存並創建輸入 IStream
    HGLOBAL hMemBmp = GlobalAlloc(GMEM_FIXED, iBmpSize);
    if (!hMemBmp) return FALSE;

    IStream* pStmBmp = NULL;
    if (FAILED(CreateStreamOnHGlobal(hMemBmp, FALSE, &pStmBmp)) || !pStmBmp) {
        GlobalFree(hMemBmp);
        return FALSE;
    }

    // 3. 複製數據到流
    BYTE* pbyBmp = (BYTE*)GlobalLock(hMemBmp);
    cbfBmp.SeekToBegin();
    cbfBmp.Read(pbyBmp, iBmpSize);
    GlobalUnlock(hMemBmp); // 鎖定期間不可移動內存

    // 4. 從流創建 GDI+ Image 對象
    Image* pImage = Image::FromStream(pStmBmp, FALSE); // FALSE: 不保留流所有權
    pStmBmp->Release(); // Image 內部已複製像素數據,流可釋放
    if (!pImage || pImage->GetLastStatus() != Ok) {
        if (pImage) delete pImage;
        GlobalFree(hMemBmp);
        return FALSE;
    }

    // 5. 獲取目標格式編碼器 CLSID
    CLSID clsidEncoder;
    CString mimeType = _T("image/") + strType; // "image/jpeg", "image/png"...
    USES_CONVERSION;
    if (FAILED(GetEncoderClsid(T2W(mimeType), &clsidEncoder))) {
        delete pImage;
        GlobalFree(hMemBmp);
        return FALSE;
    }

    // 6. 創建輸出流 (可移動內存,便於後續 GetSize)
    HGLOBAL hMemImage = GlobalAlloc(GMEM_MOVEABLE, 0);
    if (!hMemImage) {
        delete pImage;
        GlobalFree(hMemBmp);
        return FALSE;
    }

    IStream* pStmImage = NULL;
    if (FAILED(CreateStreamOnHGlobal(hMemImage, TRUE, &pStmImage)) || !pStmImage) {
        delete pImage;
        GlobalFree(hMemBmp);
        GlobalFree(hMemImage);
        return FALSE;
    }

    // 7. 保存圖像到輸出流
    Status st = pImage->Save(pStmImage, &clsidEncoder, NULL); // 無額外編碼參數
    delete pImage; // Image 對象任務結束
    if (st != Ok) {
        pStmImage->Release();
        GlobalFree(hMemBmp);
        GlobalFree(hMemImage);
        return FALSE;
    }

    // 8. 將輸出流數據讀回 CMemFile
    LARGE_INTEGER liZero = {0};
    pStmImage->Seek(liZero, STREAM_SEEK_SET, NULL); // 重置流指針

    STATSTG stat;
    pStmImage->Stat(&stat, STATFLAG_NONAME);
    ULONG outSize = stat.cbSize.LowPart; // 假設 < 4GB

    BYTE* pbyImage = (BYTE*)GlobalLock(hMemImage);
    cbfImage.SeekToBegin();
    cbfImage.Write(pbyImage, outSize);
    GlobalUnlock(hMemImage);

    // 9. 清理
    pStmImage->Release();
    GlobalFree(hMemBmp);
    GlobalFree(hMemImage);

    return TRUE;
}

三、關鍵輔助函數:GetEncoderClsid

遍歷系統安裝的編碼器,匹配 MIME 類型:

HRESULT GetEncoderClsid(const WCHAR* mimeType, CLSID* pClsid) {
    UINT numEncoders = 0, size = 0;
    GetImageEncodersSize(&numEncoders, &size);
    if (size == 0) return E_FAIL;

    std::unique_ptr buffer(new BYTE[size]);
    ImageCodecInfo* pCodecs = reinterpret_cast(buffer.get());
    GetImageEncoders(numEncoders, size, pCodecs);

    for (UINT i = 0; i < numEncoders; ++i) {
        if (wcscmp(pCodecs[i].MimeType, mimeType) == 0) {
            *pClsid = pCodecs[i].Clsid;
            return S_OK;
        }
    }
    return E_FAIL; // 未找到編碼器
}

四、支持格式對照表

strType 參數MIME 類型典型擴展名備註
bmpimage/bmp.bmp, .dib無壓縮,體積大
pngimage/png.png無損壓縮,支持透明通道,推薦
jpeg / jpgimage/jpeg.jpg, .jpeg有損壓縮,適合照片,可通過 EncoderParameters 調質量
gifimage/gif.gif256 色,支持動畫,專利已過期
tiffimage/tiff.tif, .tiff支持多頁、多種壓縮,印刷/掃描常用
emfimage/emf.emf增強圖元文件,矢量圖形,可無限縮放

五、進階:JPEG 質量控制

// 在 Save 前設置 EncoderParameters
EncoderParameters eps;
eps.Count = 1;
eps.Parameter[0].Guid = EncoderQuality;
eps.Parameter[0].Type = EncoderParameterValueTypeLong;
eps.Parameter[0].NumberOfValues = 1;
ULONG quality = 85; // 0-100
eps.Parameter[0].Value = &quality;

pImage->Save(pStmImage, &clsidEncoder, &eps);

六、錯誤處理與資源管理最佳實踐

  1. RAII 封裝:將 HGLOBAL/IStream*/Image* 封裝為智能指針類,確保異常安全。
  2. 檢查 GDI+ 狀態:每次 Image 操作後調用 pImage->GetLastStatus(),非 Ok 即報錯。
  3. 內存流標誌:輸入流用 GMEM_FIXED + FALSE (不釋放內存);輸出流用 GMEM_MOVEABLE + TRUE (流負責釋放),配合 GlobalLock/Unlock 訪問數據。
  4. 線程安全:GDI+ 非線程安全,多線程調用需加鎖或每線程獨立 GdiplusStartup。

七、調用示例

void CMyView::OnConvertImage() {
    // 1. 讀取文件到 CMemFile
    CMemFile memIn;
    CFile file(_T("input.bmp"), CFile::modeRead);
    BYTE* buf = new BYTE[file.GetLength()];
    file.Read(buf, file.GetLength());
    memIn.Write(buf, file.GetLength());
    delete[] buf;

    // 2. 轉換為 PNG
    CMemFile memOut;
    CMyImageProcessor processor; // 構造/析構自動管理 GDI+ 生命週期
    if (processor.MBmpToMImage(memIn, memOut, _T("png"))) {
        // 3. 保存結果
        CFile fileOut(_T("output.png"), CFile::modeCreate | CFile::modeWrite);
        memOut.SeekToBegin();
        BYTE* outBuf = new BYTE[memOut.GetLength()];
        memOut.Read(outBuf, memOut.GetLength());
        fileOut.Write(outBuf, memOut.GetLength());
        delete[] outBuf;
        AfxMessageBox(_T("轉換成功!"));
    } else {
        AfxMessageBox(_T("轉換失敗:格式不支持或數據損壞"));
    }
}

結語

GDI+ Image::FromStream + Image::Save 構成了「內存流 → 解碼像素 → 編碼流」的通用管線,配合 IStream/HGLOBAL/CMemFile 互轉,即可實現零臨時文件的高性能格式轉換。掌握編碼器 CLSID 查找、EncoderParameters 參數調優、資源 RAII 管理,即可在 MFC/Win32 專案中構建專業級圖像處理模塊。

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