GDI/GDI+(1): 将Bitmap导出为Byte[]

本文介绍三种从PNG图像导出Byte[]数组的方法,并将其保存为不同格式的图片。第一种方法利用GDI+的LockBits函数,第二种方法通过临时文件和IStream实现,第三种方法则是转换为BMP格式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近遇到一个问题,需要将Bitmap中的图像Buffer导出来。


有人说用GDI的GetDIBits方法,有人说用GDI+的LockBits方法。


找了很多资料,都没找到可直接运行的代码,只有老老实实的查MSDN。

最后终于搞定了,下面见代码:

// BitmapToByteArray.cpp : Defines the entry point for the console application.
//

#include <tchar.h>
#include <windows.h>

#include <gdiplus.h>
#pragma comment(lib, "gdiplus")
using namespace Gdiplus;

int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
	UINT  num = 0;          // number of image encoders
	UINT  size = 0;         // size of the image encoder array in bytes

	ImageCodecInfo* pImageCodecInfo = NULL;

	GetImageEncodersSize(&num, &size);
	if(size == 0)
		return -1;  // Failure

	pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
	if(pImageCodecInfo == NULL)
		return -1;  // Failure

	GetImageEncoders(num, size, pImageCodecInfo);

	for(UINT j = 0; j < num; ++j)
	{
		if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
		{
			*pClsid = pImageCodecInfo[j].Clsid;
			free(pImageCodecInfo);
			return j;  // Success
		}
	}

	free(pImageCodecInfo);
	return -1;  // Failure
}

void ExtractBitmapToByteArray_1()
{
	// Create a Bitmap object from a BMP file.
	Bitmap bitmap(_T("menu.png"));

	BitmapData bmpData;
	Rect rect(0, 0, bitmap.GetWidth(), bitmap.GetHeight());

	bitmap.LockBits(
		&rect,
		ImageLockModeWrite,
		PixelFormat32bppARGB,
		&bmpData);

	// Write to the temporary buffer provided by LockBits.
	UINT* pixels = (UINT*)bmpData.Scan0;

	int byteCount = bmpData.Stride * bmpData.Height;
	BYTE* pBuffer = new BYTE[byteCount];
	memcpy(pBuffer, pixels, byteCount);

	Bitmap bitmap22(
		bmpData.Width,
		bmpData.Height,
		bmpData.Stride,
		bmpData.PixelFormat,
		(BYTE*)bmpData.Scan0);

	CLSID clsid;
	GetEncoderClsid(_T("image/png"), &clsid);
	bitmap22.Save(_T("menu_1.png"), &clsid);
	delete []pBuffer;

	bitmap.UnlockBits(&bmpData);
}

// 通过临时文件将png图片保存到IStream中
BOOL ExtractBitmapToByteArray_2()
{
	Bitmap bitmap(_T("menu.png"));

	TCHAR pszPath[MAX_PATH] = {0};
	GetTempPath(MAX_PATH, pszPath);
	const TCHAR tmpName[] = _T("menu.png.tmp");
	lstrcat(pszPath, tmpName);

	IStorage* pstgFile = NULL;
	HRESULT hr = ::StgCreateDocfile(pszPath,
		STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
		0, &pstgFile);
	if( FAILED(hr) ) { return FALSE; }

	IStream * pStream = NULL;
	TCHAR pszName[10] = _T("Image");
	hr = pstgFile->OpenStream(pszName, NULL, STGM_READWRITE | STGM_SHARE_EXCLUSIVE, NULL, &pStream);
	if (FAILED(hr)) {
		hr = pstgFile->CreateStream(pszName, STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pStream);
		if (FAILED(hr)) { return FALSE; }
	}

	// 以png格式保存到IStream中
	CLSID ImageClsid;
	GetEncoderClsid(_T("image/png"), &ImageClsid);
	bitmap.Save(pStream, &ImageClsid);

	// Begin -- HH Debug
	STATSTG statstg;
	pStream->Stat(&statstg, STATFLAG_NONAME);
	int nDatalen = (ULONG)statstg.cbSize.QuadPart;
	// End -- HH Debug

	// read
	GetEncoderClsid(_T("image/png"), &ImageClsid);
	Gdiplus::Bitmap* pRead = Gdiplus::Bitmap::FromStream(pStream);
	pRead->Save(_T("menu2.png"), &ImageClsid);

	pStream->Release();
	pstgFile->Release();
	DeleteFile(pszPath);

	return TRUE;
}

//------------保存Bitmap到文件---------------
BOOL SaveBitmapToFile(HBITMAP hBitmap, LPCTSTR lpFileName ) 
{ 
	HDC hDC; //设备描述表 
	int iBits; //当前显示分辨率下每个像素所占字节数 
	WORD wBitCount; //位图中每个像素所占字节数 
	DWORD dwPaletteSize=0, //定义调色板大小, 位图中像素字节大小 ,位图文件大小 , 写入文件字节数 
		dwBmBitsSize, 
		dwDIBSize, dwWritten; 
	BITMAP Bitmap; //位图属性结构 
	BITMAPFILEHEADER bmfHdr; //位图文件头结构 
	BITMAPINFOHEADER bi; //位图信息头结构 
	LPBITMAPINFOHEADER lpbi; //指向位图信息头结构 

	HANDLE fh, hDib, hPal,hOldPal=NULL; //定义文件,分配内存句柄,调色板句柄 

	//计算位图文件每个像素所占字节数 
	HDC hWndDC = CreateDC(_T("DISPLAY"),NULL,NULL,NULL); 
	hDC = ::CreateCompatibleDC( hWndDC ) ; 
	iBits = GetDeviceCaps(hDC, BITSPIXEL) * GetDeviceCaps(hDC, PLANES); 
	DeleteDC(hDC); 

	if (iBits <= 1) 
		wBitCount = 1; 
	else if (iBits <= 4) 
		wBitCount = 4; 
	else if (iBits <= 8) 
		wBitCount = 8; 
	else if (iBits <= 24) 
		wBitCount = 24; 
	else 
		wBitCount = 24 ; 

	//计算调色板大小 
	if (wBitCount <= 8) 
		dwPaletteSize = (1 << wBitCount) * sizeof(RGBQUAD); 

	//设置位图信息头结构 
	GetObject(hBitmap, sizeof(BITMAP), (LPSTR)&Bitmap); 
	bi.biSize = sizeof(BITMAPINFOHEADER); 
	bi.biWidth = Bitmap.bmWidth; 
	bi.biHeight = Bitmap.bmHeight; 
	bi.biPlanes = 1; 
	bi.biBitCount = wBitCount; 
	bi.biCompression = BI_RGB; 
	bi.biSizeImage = 0; 
	bi.biXPelsPerMeter = 0; 
	bi.biYPelsPerMeter = 0; 
	bi.biClrUsed = 0; 
	bi.biClrImportant = 0; 

	dwBmBitsSize = ((Bitmap.bmWidth * wBitCount+31)/32) * 4 * Bitmap.bmHeight ; 

	//为位图内容分配内存 
	hDib = GlobalAlloc(GHND,dwBmBitsSize+dwPaletteSize+sizeof(BITMAPINFOHEADER)); 
	lpbi = (LPBITMAPINFOHEADER)GlobalLock(hDib); 
	*lpbi = bi; 

	// 处理调色板 
	hPal = GetStockObject(DEFAULT_PALETTE); 
	if (hPal) 
	{ 
		hDC = ::GetDC(NULL); 
		hOldPal = ::SelectPalette(hDC, (HPALETTE)hPal, FALSE); 
		RealizePalette(hDC); 
	} 

	// 获取该调色板下新的像素值 
	GetDIBits(hDC, hBitmap, 0, (UINT) Bitmap.bmHeight, 
		(LPSTR)lpbi + sizeof(BITMAPINFOHEADER) 
		+dwPaletteSize, 
		(LPBITMAPINFO ) 
		lpbi, DIB_RGB_COLORS); 

	//恢复调色板 
	if (hOldPal) 
	{ 
		SelectPalette(hDC, (HPALETTE)hOldPal, TRUE); 
		RealizePalette(hDC); 
		::ReleaseDC(NULL, hDC); 
	} 

	//创建位图文件 
	fh = CreateFile(lpFileName, GENERIC_WRITE, 
		0, NULL, CREATE_ALWAYS, 
		FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); 

	if (fh == INVALID_HANDLE_VALUE) 
		return FALSE; 

	// 设置位图文件头 
	bmfHdr.bfType = 0x4D42; // "BM" 
	dwDIBSize = sizeof(BITMAPFILEHEADER) 
		+ sizeof(BITMAPINFOHEADER) 
		+ dwPaletteSize + dwBmBitsSize; 
	bmfHdr.bfSize = dwDIBSize; 
	bmfHdr.bfReserved1 = 0; 
	bmfHdr.bfReserved2 = 0; 
	bmfHdr.bfOffBits = (DWORD)sizeof(BITMAPFILEHEADER) 
		+ (DWORD)sizeof(BITMAPINFOHEADER) 
		+ dwPaletteSize; 

	// 写入位图文件头 
	WriteFile(fh, (LPSTR)&bmfHdr, sizeof(BITMAPFILEHEADER), &dwWritten, NULL); 

	// 写入位图文件其余内容 
	WriteFile(fh, (LPSTR)lpbi, dwDIBSize, 
		&dwWritten, NULL); 

	//清除 
	GlobalUnlock(hDib); 
	GlobalFree(hDib); 
	CloseHandle(fh); 

	return TRUE; 
}

void ExtractBitmapToByteArray_3()
{
	Bitmap bmp(_T("menu.png"));
	HBITMAP hBitmap;
	bmp.GetHBITMAP(Color(0,255,255,255), &hBitmap);
	SaveBitmapToFile(hBitmap, _T("menu_3.bmp"));
}

int _tmain(int argc, _TCHAR* argv[])
{
	ULONG_PTR gdiplusToken;
	GdiplusStartupInput gdiplusStartupInput;
	GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

	ExtractBitmapToByteArray_1();

	GdiplusShutdown(gdiplusToken);

	return 0;
}

一共有2种方法将png保存到图片中。

第3种是将png保存到bmp中。


另外大家也可以在这里查看: 使用LockBits将Bitmap导出为Byte[]

### C# 中 PixelFormat 的用法 在 C# 和 GDI+ 库中,`PixelFormat` 枚举用于指定图像数据的像素格式。不同的 `PixelFormat` 值表示不同类型的位图编码方式以及颜色通道的数量和顺序。 对于 Direct3D 或 DirectX 使用的情况,设置渲染目标和回退缓冲区的像素格式为 `DXGI_FORMAT_B5G6R5_UNORM` 表明每个像素占用 16 位,其中红色分量占 5 位、绿色分量占 6 位而蓝色分量也占据 5 位[^1]。然而,在讨论 C# 及其图形处理时,通常会涉及到 System.Drawing.Common 或者其他第三方库如 OpenCvSharp 来操作图片。 当使用像 Emgu CV 这样的封装好的 OpenCV 绑定来创建应用程序时,可能会遇到需要转换图像格式的情形。例如,如果要加载一幅彩色 BMP 文件并将其显示在一个 Windows 窗体上,则可以这样做: ```csharp using System; using System.Drawing; // For Bitmap class using Emgu.CV; public void LoadAndDisplayImage() { string imagePath = "path_to_image.bmp"; using (Mat mat = CvInvoke.Imread(imagePath)) { Image<Bgr, byte> image = new Image<Bgr, byte>(mat); // 将图像转换成适合窗体重绘的形式 PictureBox pictureBox = new PictureBox(); pictureBox.Image = image.ToBitmap(); Form form = new Form(); form.Controls.Add(pictureBox); Application.Run(form); } } ``` 在这个例子中,通过读取一个标准 RGB 图片文件到内存中的矩阵对象 (`Mat`) 后再转存入特定于 BGR 颜色空间的托管结构体内(`Bgr`);最后一步则是把该结构体的内容导出给 .NET Framework 自带的 `System.Drawing.Bitmap` 类型以便进一步展示出来。 关于如何定义自定义 DLL 并从中调用函数的问题[^2],这超出了当前话题范围,但如果确实有这方面的需求,建议查阅有关 P/Invoke 技术的相关资料,它允许开发者声明外部方法签名从而实现跨平台互操作性功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值