记录GDI+保存位图
记录使用GDI+保存位图
我做的一个项目上有一个需求:以图片的形式保存当前窗口的内容。刚开始我使用GDI中的函数,手动填充位图的内容,并写入到文件中,不知道由于什么原因,无法保存。遂使用GDI+来保存位图。其中也有许多坑,但最终也实现了该功能。现记录下来,希望对有类似需求的读者有帮助。
基本思路
通过GDI+的文档我们可以得知,Gdiplus::Bitmap类可以通过一个位图句柄构造,且该类继承了Gdiplus::Image类,Gdiplus::Image类中有一个Save方法,可以将对象中的图片内容保存到文件中。总结来说就是两步:
- 构造正确的
Gdiplus::Bitmap对象 - 调用
Save方法保存位图
构造Gdiplus::Bitmap对象
要构造正确的Gdiplus::Bitmap对象,需要先初始化一个位图句柄。该句柄指向的位图就是我们需要保存的窗口截屏。接下来就说说如何得到窗口内容对应的位图。
将窗口客户区的内容保存到位图中
如何通过一个窗口句柄获得存有其客户区的位图呢?参见以下代码:
void MyBitmap::GetBitmap()
{RECT rect;::GetClientRect(this->hwnd, &rect); //获取窗口客户区的矩形int width = rect.right - rect.left; //位图的宽度int height = rect.bottom - rect.top; //位图的高度HDC hdc = GetDC(this->hwnd); //获取窗口的设备上下文HDC hdcMem = CreateCompatibleDC(hdc); //创建兼容的内存dc,这个dc用作缓冲区,接收hdc的内容,相当于画板HBITMAP hBitmap, hOldBitmap;hBitmap = CreateCompatibleBitmap(hdc, width, height); //创建兼容位图,相当于画布hOldBitmap = static_cast<HBITMAP>(SelectObject(hdcMem, hBitmap)); //此时hBitmap将用于存储hdc的位图BitBlt(hdcMem, 0, 0, width, height, hdc, 0, 0, SRCCOPY); //复制hdc的内容到hdcMem中,相当于绘制画布hBitmap = static_cast<HBITMAP>(SelectObject(hdcMem, hOldBitmap)); //得到获取的位图this->hBmp = hBitmap; //设置该成员变量以便后续使用//释放资源ReleaseDC(this->hwnd, hdc);DeleteDC(hdcMem);DeleteObject(hOldBitmap);
}
初始化Gdiplus
Gdiplus::Bitmap中的save方法中第二个参数是一个编码器的id,我们需要获取位图编码器的id。
UINT num, size;
Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL;
if (Gdiplus::GetImageEncodersSize(&num, &size) != Gdiplus::Status::Ok) this->errhandler(TEXT("GetImageEncodercInfo error"));
if (size == 0) this->errhandler(TEXT("没有可用的编码器"));pImageCodecInfo = reinterpret_cast<Gdiplus::ImageCodecInfo*>(malloc(size));
if (pImageCodecInfo == NULL) this->errhandler(TEXT("mallco error"));
if ((res = Gdiplus::GetImageEncoders(num, size, pImageCodecInfo)) != Gdiplus::Status::Ok)
{this->errhandler(err_msg);
}CLSID clsId;
for (UINT i = 0; i < num; i++)
{if (wcscmp(pImageCodecInfo[i].MimeType, L"image/bmp")) //遍历编码器,匹配bmp编码器{clsId = pImageCodecInfo[i].Clsid;free(pImageCodecInfo);break;}
}
初始化Gdiplus::Bitmap对象
有了有效的位图句柄和Clsid,我们就可以初始化一个Gdiplus::Bitmap对象了:
Gdiplus::Bitmap* bitmap = new Gdiplus::Bitmap(this->hBmp, NULL);
调用Save方法保存位图并清理资源
if ((res = bitmap->Save(this->filename, &clsId, NULL)) != Gdiplus::Status::Ok)
{this->errhandler(err_msg);
}delete bitmap; //调用Gdiplus::GdiplusShutdown之前必须清理GDI+对象,否则会引起地址访问错误
Gdiplus::GdiplusShutdown(gdiplusToken);
如此就可以保存某一个窗口的客户区内容了。
