GDI+ 提供了強大的 Image 類,支持在內存中無縫轉換主流圖片格式,無需落地臨時文件。本文詳細講解 CMemFile ↔ IStream ↔ GDI+ Image 的完整轉換鏈路,並封裝為通用函數 MBmpToMImage。
#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);
}
// ... 轉換函數 ...
};
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;
}
遍歷系統安裝的編碼器,匹配 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 類型 | 典型擴展名 | 備註 |
|---|---|---|---|
| bmp | image/bmp | .bmp, .dib | 無壓縮,體積大 |
| png | image/png | .png | 無損壓縮,支持透明通道,推薦 |
| jpeg / jpg | image/jpeg | .jpg, .jpeg | 有損壓縮,適合照片,可通過 EncoderParameters 調質量 |
| gif | image/gif | .gif | 256 色,支持動畫,專利已過期 |
| tiff | image/tiff | .tif, .tiff | 支持多頁、多種壓縮,印刷/掃描常用 |
| emf | image/emf | .emf | 增強圖元文件,矢量圖形,可無限縮放 |
// 在 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);
HGLOBAL/IStream*/Image* 封裝為智能指針類,確保異常安全。Image 操作後調用 pImage->GetLastStatus(),非 Ok 即報錯。GMEM_FIXED + FALSE (不釋放內存);輸出流用 GMEM_MOVEABLE + TRUE (流負責釋放),配合 GlobalLock/Unlock 訪問數據。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 專案中構建專業級圖像處理模塊。