使用GDI+保存图像为8bpp的灰度图像

本文介绍了如何使用GDI+将图像保存为8位灰度图像,包括构建位图文件头、创建调色板、灰度化处理、保存图像等关键步骤。

使用GDI+保存图像为8bpp的灰度图像,GDI+真的有些特殊。。。。。哭


//   Greyscale conversion
#define GREY(r, g, b) (BYTE)(((WORD)r * 77 + (WORD)g * 150 + (WORD)b * 29) >> 8)	// .299R + .587G + .114B
//#define GREY(r, g, b) (BYTE)(((WORD)r * 169 + (WORD)g * 256 + (WORD)b * 87) >> 9)	// .33R + 0.5G + .17B


// BOOL GDIPlusImage::SaveToFileWith8pp(TCHAR *pszPath)
// 功能:保存图片为8位的灰度图像
// 参数: pszPath要保存的文件名
BOOL GDIPlusImage::SaveToFileWith8pp(TCHAR *pszPath)
{
	Bitmap *ima = this->m_pBitmap;		// GDIPlusImage的Bitmap对象

	if (!ima || !pszPath || !*pszPath)
	{
		return FALSE;
	}

	int width = m_pBitmap->GetWidth();
	int height = m_pBitmap->GetHeight();
	int bitcount = 8;			//1, 4, 8, 24, 32

	//////////////////////////////////////////////////////////////////////////
	//Build bitmap header
	BITMAPFILEHEADER bitmapFileHeader; 
	BITMAPINFOHEADER bitmapInfoHeader; 
	BYTE			 rgbquad[4];			// RGBQUAD
	int				 index = 0;

	DWORD stride = ((bitcount*width + 31)/32)*4;		//每行都是4的倍数,或者说是DWORD大小的倍数

	switch(bitcount) { 
	case 1: 
		index = 2; 
		bitmapFileHeader.bfOffBits = (DWORD)(sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 2*4); 
		break; 
	case 4: 
		index = 16; 
		bitmapFileHeader.bfOffBits = (DWORD)(sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 16*4); 
		break; 
	case 8: 
		index = 256; 
		bitmapFileHeader.bfOffBits = (DWORD)(sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + 256*sizeof(RGBQUAD)); 
		break; 
	case 24: 
	case 32: 
		index = 0; 
		bitmapFileHeader.bfOffBits = (DWORD)(sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)); 
		break; 
	default:
		break;
	} 

	//构造Bitmap文件头BITMAPFILEHEADER 
	bitmapFileHeader.bfType = 0x4d42;    // 很重要的标志位  BM 标识
	bitmapFileHeader.bfSize = (DWORD)(bitmapFileHeader.bfOffBits + height * stride);		//bmp文件长度  
	bitmapFileHeader.bfReserved1 = 0; 
	bitmapFileHeader.bfReserved2 = 0; 

	//构造Bitmap文件信息头BITMAPINFOHEADER 
	bitmapInfoHeader.biSize = sizeof(BITMAPINFOHEADER); 
	bitmapInfoHeader.biWidth = width; 
	bitmapInfoHeader.biHeight = height; 
	bitmapInfoHeader.biPlanes = 1; 
	bitmapInfoHeader.biBitCount = bitcount;
	bitmapInfoHeader.biCompression = BI_RGB;				// 未压缩
	bitmapInfoHeader.biSizeImage = height * stride; 
	bitmapInfoHeader.biXPelsPerMeter = 3780; 
	bitmapInfoHeader.biYPelsPerMeter = 3780; 
	bitmapInfoHeader.biClrUsed = 0; 
	bitmapInfoHeader.biClrImportant = 0; 

	//创建BMP内存映像,写入位图头部
	BYTE *pMyBmp = new BYTE[bitmapFileHeader.bfSize];		// 我的位图pMyBmp
	BYTE *curr = pMyBmp;									// curr指针指示pMyBmp的位置
	memset(curr, 0, bitmapFileHeader.bfSize); 

	//写入头信息 
	memcpy(curr, &bitmapFileHeader,sizeof(BITMAPFILEHEADER));
	curr = pMyBmp + sizeof(BITMAPFILEHEADER); 
	memcpy(curr, &bitmapInfoHeader,sizeof(BITMAPINFOHEADER)); 
	curr += sizeof(BITMAPINFOHEADER);

	//构造调色板 
	if(8 == bitcount) 
	{
		rgbquad[3] = 0;										//rgbReserved
		for(int i = 0; i < 256; i++) 
		{ 
			rgbquad[0] = rgbquad[1] = rgbquad[2] = i; 
			memcpy(curr, rgbquad, sizeof(RGBQUAD)); 
			curr += sizeof(RGBQUAD); 
		} 
	}else if (4 == bitcount)
	{
		rgbquad[3] = 0;
		for (int i = 0; i < 16; i++)
		{
			rgbquad[0] = rgbquad[1] = rgbquad[2] = i; 
			memcpy(curr, rgbquad, sizeof(RGBQUAD)); 
			curr += sizeof(RGBQUAD); 
		}
	}
	else if(1 == bitcount) 
	{ 
		rgbquad[3] = 0;										//rgbReserved
		for(int i = 0; i < 2; i++) 
		{ 
			rgbquad[0] = rgbquad[1] = rgbquad[2] = (256 - i)%256; 
			memcpy(curr, rgbquad, sizeof(RGBQUAD)); 
			curr += sizeof(RGBQUAD); 
		} 
	}

	//用GDI+加载数据源,也可以是其他的,写入文件数据
	Rect rect(0,0,width,height);		// Gdiplus+
	BitmapData bmData;
	Status iSucess = ima->LockBits(
		&rect,
		ImageLockModeRead,
		ima->GetPixelFormat() ,
		&bmData);

	BYTE *_pixels = (BYTE*)bmData.Scan0;	//原图rect区域内存位置的起始指针,以BYTE作为单元类型
	BYTE *_pRow;
	int _strideoff8 = stride - width;			//前面计算的索引图像的stride
	BYTE _grey;

	// build pixles, GDI+ (windows)的图像数据的原点在左下角,参考一下OpenCV的IplImage结构体
	switch(ima->GetPixelFormat())
	{
	case PixelFormat24bppRGB:
		{
			int _strideoff24 = stride - 3*width;			//前面计算的索引图像的stride

			//  灰度化
			for (int i=height-1; i >= 0; i--)
			{
				_pRow = _pixels + i*bmData.Stride;		// 当前的行

				for (int j=width-1; j >= 0; j--)
				{	
					BYTE* _pixels_b;		//b
					BYTE* _pixels_g;		//g
					BYTE* _pixels_r;		//r
					
					_pixels_b = _pRow++;  //blue
					_pixels_g = _pRow++;  //green
					_pixels_r = _pRow++;  //red

					_grey = GREY(*_pixels_r, *_pixels_g, *_pixels_b); 

					*curr = _grey;    //根据红绿蓝求出此像素的灰度值
					curr++;
				}
				curr += _strideoff8;
			}
		}
		break;

	case PixelFormat32bppARGB:
		{
			//  灰度化
			for (int i=height-1;i>=0;i--)
			{
				_pRow = _pixels + i*bmData.Stride;
				
				for (int j=width-1;j>=0;j--)
				{			
					BYTE* _pixels_b;
					BYTE* _pixels_g;		
					BYTE* _pixels_r;		

					_pixels_b = _pRow++;  //blue
					_pixels_g = _pRow++;  //green
					_pixels_r = _pRow++;  //red
					_pRow++;

					_grey = GREY(*_pixels_r, *_pixels_g, *_pixels_b); 

					*curr = _grey;
					curr++;
				}
				curr += _strideoff8;
			}
		}
		break;
	case PixelFormat8bppIndexed:  // Gdi+只能保存成24位真彩色的图像
		{
			// 不能直接用memcpy复制,需要水平镜像,memcpy(curr, _pixels, height * stride);
			for (int i=height-1; i >= 0; i--)
			{
				_pRow = _pixels + i*bmData.Stride;
// 				for (int j=width-1;j >= 0;j--)
// 				{	
// 					_grey = *_pRow++; 
// 					*curr = _grey; 
// 					curr++;
// 				}
				memcpy(curr, _pRow, width);
				curr += stride;
			}
		}
		break;
	default:
		break;
	}

	// 保存图像 pMyBmp 到文件
	try
	{
		CFile f(pszPath, CFile::modeCreate | CFile::modeWrite );
		f.Write(pMyBmp, bitmapFileHeader.bfSize);
		f.Close();
	}
	catch( CFileException* e )
	{
		TCHAR szCause[255];
		e->GetErrorMessage(szCause, 255);

		CString msg;
		msg.Format(_T("file error: %s, m_cause:%d\n"),szCause, e->m_cause);

		TRACE1("%s",msg);
		AfxMessageBox(msg);
		e->Delete();
	}

	//clean:
	ima->UnlockBits(&bmData);
	delete[] pMyBmp;

	return TRUE;
}


System::Drawing::Bitmap^ _img = nullptr; property System::Drawing::Bitmap^ DisplayImage { System::Drawing::Bitmap^ get() { if (NULL == _imgHeader || nullptr == _imgData || _imgHeader->width * _imgHeader->height * _imgHeader->bpp * _imgHeader->channels != _imgData->Length) { return nullptr; } System::Threading::Monitor::Enter(_lock);//线程锁 if (nullptr == _img) { if (SINGLE_CHANNEL == _imgHeader->channels)//单通道 { //创建图像新实例 _img = gcnew System::Drawing::Bitmap(_imgHeader->width, _imgHeader->height, System::Drawing::Imaging::PixelFormat::Format8bppIndexed); System::Drawing::Imaging::ColorPalette^ colorPallette = _img->Palette;//颜色盘 for (int i = 0; i < 256; i++) { System::Drawing::Color c = System::Drawing::Color::FromArgb(i, i, i); colorPallette->Entries[i] = c; } _img->Palette = colorPallette; pin_ptr<byte> pin_img = &_imgData[0];//赋值源图像数据的首地址 //获取图像数据 System::Drawing::Imaging::BitmapData^ data0 = _img->LockBits(System::Drawing::Rectangle(0, 0, _imgHeader->width, _imgHeader->height), System::Drawing::Imaging::ImageLockMode::ReadOnly, _img->PixelFormat); void* ptr = data0->Scan0.ToPointer(); //将此实例的值转换为指向未指定类型的指针。 //获取实际显示的图像内容 根据图像数据及像素格式转化为实际显示的图像 lxCamData::GetDisplayImgData(pin_img, *_imgHeader, (byte*)ptr, data0->Stride * data0->Height); //printf("\n stride: %d H: %d\n", data0->Stride, data0->Height); _img->UnlockBits(data0); } if (TRIPLE_CHANNEL == _imgHeader->channels)//3通道 { _img = gcnew System::Drawing::Bitmap(_imgHeader->width, _imgHeader->height, System::Drawing::Imaging::PixelFormat::Format24bppRgb); pin_ptr<byte> pin_img = &_imgData[0]; System::Drawing::Imaging::BitmapData^ data0 = _img->LockBits(System::Drawing::Rectangle(0, 0, _imgHeader->width, _imgHeader->height), System::Drawing::Imaging::ImageLockMode::ReadOnly, _img->PixelFormat); void* ptr = data0->Scan0.ToPointer(); lxCamData::GetDisplayImgData(pin_img, *_imgHeader, (byte*)ptr, data0->Stride * data0->Height); _img->UnlockBits(data0); } } System::Threading::Monitor::Exit(_lock); return _img;//返回图像 } }将这段代码转换为纯C++语言格式显示,使用OpenCV的cv::Mat替代GDI+实现
03-20
在 C++使用 GDI+ 获取图像缓冲区通常涉及到创建和管理 GDI+ 对象、加载或绘制图像,然后访问该图像的数据。下面是详细介绍如何实现这一过程: ### 步骤 1: 初始化 GDI+ 首先需要初始化 GDI+ 库,在程序启动时调用 `GdiplusStartup` 函数完成必要的准备工作。 ```cpp #include <windows.h> #include <gdiplus.h> using namespace Gdiplus; // ... ULONG_PTR gdiplusToken; GdiplusStartupInput gdiplusStartupInput; Status startup_status = GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); if (startup_status != Ok) { // Handle error... } ``` ### 步骤 2: 创建并填充位图 接下来,你可以从文件加载一张图片到内存中的 `Bitmap*` 或者直接构造一个新的空白位图用于绘图。 #### 加载现有图像: ```cpp CLSID pngClsid; GetEncoderClsid(L"image/png", &pngClsid); Bitmap *bitmap = new Bitmap(L"path_to_image.png"); ``` #### 新建一个空的位图(例如大小为800x600像素): ```cpp INT width = 800; INT height = 600; Color bkcolor = Color::White; Bitmap *bitmap = new Bitmap(width, height, PixelFormat32bppRGB); Graphics graphics(bitmap); graphics.Clear(bkcolor); ``` ### 步骤 3: 访问位图数据 一旦拥有了有效的 `Bitmap*`, 可以利用其成员函数如 `LockBits()` 来锁定一部分图像区域并将它映射成应用程序可以读写的原始字节数组形式。 ```cpp Rect rect(0, 0, bitmap->GetWidth(), bitmap->GetHeight()); BitmapData bmpData; PixelFormat format = PixelFormat32bppARGB; Status lock_status = bitmap->LockBits( &rect, ImageLockModeRead | ImageLockModeWrite, format, &bmpData ); if(lock_status == Ok){ BYTE* pScan0 = static_cast<BYTE*>(bmpData.Scan0); // Now you can work with the image data pointed to by pScan0. } else { // handle failure case here } // Don't forget to unlock when done working with it! bitmap->UnlockBits(&bmpData); ``` 此时,`pScan0` 就是指向了图像最左下角的第一个扫描线起始位置的一个指针,每条扫描线上包含有连续排列的颜色分量值(取决于所选像素格式),对于 32 bpp ARGB 格式来说就是 alpha-red-green-blue 四个字节依次存储的信息。 请注意,在修改完这些数据之后记得再次调用 `UnlockBits()` 解锁这块区域,并最终释放掉不再使用的资源比如删除 `Bitmap*` 实例等动作。 最后别忘了关闭对 GDI+ 的引用计数: ```cpp GdiplusShutdown(gdiplusToken); ``` 这是一般流程;具体的细节可能会因项目需求而有所不同,请参考 MSDN 文档获得更深入的帮助和支持信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值