转载自:https://www.fawdlstty.com/archives/396.html,如有侵权,请联系我进行删除。
1 GDI与GDI+的区别
GDI是Graphics Device Interface的缩写,含义是图形设备接口,它的主要任务是负责系统与绘图程序之间的信息交换,处理所有Windows程序的图形输出。
GDI+是在GDI基础上提供的一层更高级的图像绘制抽象接口,语义更明确调用更方便。
它们都支持向图片对象或者窗口上输出图形。在窗口上绘图时它们都使用窗口提供的HDC句柄实现绘制;在图片对象绘制图像时,GDI+支持直接传入图片对象实现对图片的绘制,GDI需要先创建一个与图片兼容的HDC,再将HDC与被绘制图片进行绑定,然后才能在图片上进行绘制。
它们在用法上相似,区别主要有以下几个方面:
- GDI不支持透明图片处理(AlphaBlend只能混合颜色,透明得由第三方库支持)
- GDI不支持反锯齿(对于图片绘制线条、图像或拉伸等处理时,可能出现白色锯齿形状图像,影响美观)
- GDI对于图片颜色处理具有很大优势,GDI+相比之下处理速度很慢
- GDI是以C的接口形式提供接口,GDI+是以C艹和托管类的方式提供接口
- 使用GDI+的程序在初始化后、程序关闭前需调用GDI+初始化、释放的代码
- 从层次结构上来说,GDI+更好用
没有绝对的好与坏,可以根据需求来决定使用GDI或GDI+。
2 GDI+的使用
使用GDI+需要首先包含头文件
#include <Gdiplus.h>
#pragma comment(lib, "gdiplus.lib")
using namespace Gdiplus;
在第一次使用GDI+对象前,需使用以下代码进行初始化
ULONG_PTR gdiplusToken; // 这个变量需要保存下来
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
在程序退出之前,需调用以下代码进行对象释放
GdiplusShutdown(gdiplusToken);
首先是在窗口上绘制一个图像,有两种实现思路。
一种是拿到窗口绘图句柄就直接绘制,这种方式绘制的图像在窗口移动到屏幕边缘再移回来,那么移出屏幕部分就会消失,或者把窗口最小化再还原,那么绘制的部分也会被消失。实现代码类似如下:
// 根据是否需要裁剪非客户区或者如何裁剪,选择合适的函数获取HDC
HDC hdc = GetDC (hWnd);
// 在此处编写绘图代码
// 对于获取的窗口DC,使用ReleaseDC释放;对于自己生成的DC,使用DeleteDC释放
ReleaseDC (hWnd, hdc);
另一种是在WM_PAINT事件里面绘制,这种绘制方式不会有以上那样的问题,也是个人比较推荐的方式。实现代码类似如下:
LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_PAINT:
PAINTSTRUCT ps = {0};
HDC hdc = BeginPaint (hWnd, &ps);
// 在此处编写绘图代码
EndPaint (hWnd, &ps);
break;
default:
return DefWindowProc (hWnd, uMsg, wParam, lParam);
break;
}
return 0;
}
// 需要刷新窗口时调用以下代码
RECT rect;
GetWindowRect (hWnd, &rect);
InvalidateRect (hWnd, &rect, TRUE);
对于直接绘制到界面上的代码,很可能出现闪屏的问题,另外多次的直接向界面绘制图像,效率也不高,推荐做法是使用双缓冲。
具体实现思路是创建一个窗口大小的图片,首先向图片进行绘制,绘制完成后再将图片绘制到屏幕HDC上,可以提高效率,且避免闪屏的问题。
GDI双缓冲绘制示例代码如下:
// 创建内存兼容 DC
HDC hTmpDc = CreateCompatibleDC (hdc);
// 创建内存兼容位图
HBITMAP hTmpBmp = CreateCompatibleBitmap (hdc, width, height);
// 选定绘图对象
SelectObject (hTmpDc, hTmpBmp);
// 在此处编写绘图代码,绘制到 hTmpDc 设备
// 将图片绘制到屏幕上
BitBlt (hdc, 0, 0, width, height, hTmpDc, 0, 0, SRCCOPY);
// 释放内存兼容位图及内存兼容DC
DeleteObject (hTmpBmp);
DeleteDC (hTmpDc);
GDI+双缓冲绘制示例代码如下:
// 创建临时位图
Gdiplus::Bitmap tmpBmp (width, height, PixelFormat32bppARGB);
// 创建绘制临时位图所需 Graphics 对象
Gdiplus::Graphics tmpG (&tmpBmp);
// 在此处编写绘图代码,绘制到 tmpG 对象
// 创建窗口DC所需 Graphics 对象
Gdiplus::Graphics g (hdc);
// 将图片绘制到屏幕上
g.DrawImage (&tmpBmp, Gdiplus::Rect (0, 0, width, height),
0, 0, width, height, Gdiplus::UnitPixel);
从代码形式上看,GDI+简洁太多了。
3 GDI+的问题
GDI+里面最坑的东西。对于像素点需要一个一个去计算的实现代码,用GetPixel、SetPixel简直慢的一比。网上对此也有很多实现思路,比如用GDI来代替实现什么的。
但我还是推荐使用物理内存访问来实现:
// 图片对象
Gdiplus::Bitmap bmp (width, height, PixelFormat32bppARGB);
// 图片数据对象
Gdiplus::BitmapData bmpData;
// 锁定内存区域
bmp.LockBits (&Gdiplus::Rect (0, 0, width, height),
Gdiplus::ImageLockModeRead, PixelFormat32bppARGB, &bmpData);
// 此时可以使用指针来读取图片内存区域。bmpData.Scan0 指向的就是图片数据区
// 图片像素大小受像素格式影响,此处一个像素就占4个字节
// 在这个图片里面读取数据,然后在另一个图片里面写入数据
// 解锁内存区域
bmp.UnlockBits (&bmpData);
接下来附一则常用代码:从资源加载 Image 对象的代码的实现
Gdiplus::Image *load_image_from_resource (LPCTSTR lpResName, LPCTSTR lpResType) {
HMODULE hModule = GetModuleHandle (NULL);
//搜索资源
HRSRC hRsrc = FindResource (hModule, lpResName, lpResType);
if (NULL == hRsrc)
return nullptr;
//获取资源大小
DWORD dwSize = SizeofResource (hModule, hRsrc);
//加载资源
HGLOBAL hGlobal = LoadResource (hModule, hRsrc);
if (NULL == hGlobal) {
FreeResource (hGlobal);
return nullptr;
}
//锁定资源
LockResource (hGlobal);
// 创建资源流
LPSTREAM pStream;
if (S_OK != CreateStreamOnHGlobal (hGlobal, true, &pStream)) {
GlobalUnlock (hGlobal);
FreeResource (hGlobal);
return nullptr;
}
// 从资源流创建 Image 对象
Gdiplus::Image *img = Gdiplus::Image::FromStream (pStream);
GlobalUnlock (hGlobal);
FreeResource (hGlobal);
return img;
}
本文作者:StubbornHuang
版权声明:本文为站长原创文章,如果转载请注明原文链接!
原文标题:Windows – GDI/GDI+区别、用法总结
原文链接:https://www.stubbornhuang.com/2612/
发布于:2023年05月15日 16:41:13
修改于:2023年05月15日 16:41:13
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
评论
52