Warning: WP_Syntax::substituteToken(): Argument #1 ($match) must be passed by reference, value given in /www/wwwroot/fawdlstty.com/wp-content/plugins/wp-syntax/wp-syntax.php on line 383
Warning: WP_Syntax::substituteToken(): Argument #1 ($match) must be passed by reference, value given in /www/wwwroot/fawdlstty.com/wp-content/plugins/wp-syntax/wp-syntax.php on line 383
Warning: WP_Syntax::substituteToken(): Argument #1 ($match) must be passed by reference, value given in /www/wwwroot/fawdlstty.com/wp-content/plugins/wp-syntax/wp-syntax.php on line 383
Warning: WP_Syntax::substituteToken(): Argument #1 ($match) must be passed by reference, value given in /www/wwwroot/fawdlstty.com/wp-content/plugins/wp-syntax/wp-syntax.php on line 383
Warning: WP_Syntax::substituteToken(): Argument #1 ($match) must be passed by reference, value given in /www/wwwroot/fawdlstty.com/wp-content/plugins/wp-syntax/wp-syntax.php on line 383
Warning: WP_Syntax::substituteToken(): Argument #1 ($match) must be passed by reference, value given in /www/wwwroot/fawdlstty.com/wp-content/plugins/wp-syntax/wp-syntax.php on line 383
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)