GDI+ 是 Windows XP 以降系統內建的現代 2D 圖形庫,原生支持 BMP、PNG、JPEG、GIF、TIFF、EMF、WMF、ICO 等主流格式讀寫,無需第三方庫 (如 libpng, libjpeg),部署零依賴,API 面向對象,適合內存流操作。
// StdAfx.h 或頭文件
#ifndef ULONG_PTR_DEFINED
#define ULONG_PTR unsigned long*
#endif
#include
using namespace Gdiplus;
#pragma comment(lib, "gdiplus.lib")
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);
};
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;
}
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 類型 | 副檔名 | 備註 |
|---|---|---|
| image/bmp | .bmp, .dib | 無壓縮 |
| image/png | .png | 無損壓縮,支持透明通道 |
| image/jpeg | .jpg, .jpeg | 有損壓縮,可調質量 |
| image/gif | .gif | 256 色,支持動畫 |
| image/tiff | .tif, .tiff | 多頁、多種壓縮 |
| image/x-emf | .emf | 增強元文件 (矢量) |
| image/x-wmf | .wmf | Windows 元文件 |
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);
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++ 圖片處理的首選方案。