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+。下面开始详细教程:
需要使用GDI+首先需要以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <Gdiplus.h>
#pragma comment(lib, "gdiplus.lib")
// 下面这句看个人爱好是否使用
using namespace Gdiplus;
 
 
// 在第一次使用GDI+对象前,调用以下代码:
ULONG_PTR gdiplusToken; // 这个变量需要保存下来
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
 
// 在最后一次使用GDI+对象之后,调用以下代码:
GdiplusShutdown(gdiplusToken);

首先是在窗口上绘制一个图像,有两种实现思路。一种是拿到窗口绘图句柄就直接绘制,这种方式绘制的图像在窗口移动到屏幕边缘再移回来,那么移出屏幕部分就会消失,或者把窗口最小化再还原,那么绘制的部分也会被消失。实现代码类似如下:

1
2
3
4
5
// 根据是否需要裁剪非客户区或者如何裁剪,选择合适的函数获取HDC
HDC hdc = GetDC (hWnd); // GetDCEx、GetWindowDC
// 在此处编写绘图代码
// 对于获取的窗口DC,使用ReleaseDC释放;对于自己生成的DC,使用DeleteDC释放
ReleaseDC (hWnd, hdc);

另一种是在WM_PAINT事件里面绘制,这种绘制方式不会有以上那样的问题,也是个人比较推荐的方式。实现代码类似如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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与GDI+窗口绘制的示例代码分别如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//
// 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);
// C艹对象在函数结束时自动释放,此处就不需要编写释放代码咯

从代码形式上看,GDI+简洁太多了。

说说GDI+里面最坑的东西。对于像素点需要一个一个去计算的实现代码,用GetPixel、SetPixel简直慢的一比。网上对此也有很多实现思路,比如用GDI来代替实现什么的。但我还是推荐使用物理内存访问来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 图片对象
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 对象的代码的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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;
}

由于GDI函数不好查,下面就附一则不太全的 GDI 函数索引表,摘自百度百科:
设备上下文函数(如GetDC、CreateDC、DeleteDC)
画线函数(如LineTo、Polyline、Arc)
填充画图函数(如Ellipse、FillRect、Pie)
画图属性函数(如SetBkColor、SetBkMode、SetTextColor)
文本、字体函数(如TextOut、GetFontData)
位图函数(如SetPixel、BitBlt、StretchBlt)
坐标函数(如DPtoLP、LPtoDP、ScreenToClient、ClientToScreen)
映射函数(如SetMapMode、SetWindowExtEx、SetViewportExtEx)
元文件函数(如PlayMetaFile、SetWinMetaFileBits)
区域函数(如FillRgn、FrameRgn、InvertRgn)
路径函数(如BeginPath、EndPath、StrokeAndFillPath)
裁剪函数(如SelectClipRgn、SelectClipPath)

发布者

fawdlstty

又一只萌萌哒程序猿~~

发表评论

电子邮件地址不会被公开。 必填项已用*标注