- BeginPaint 与 GetDC
- CreatCompatibleDC
- 加载BMP位图实例
- 为什么要用 HBitMap 去 LoadImage,而不是直接用 DC 去 Load?
- 为什么要用 LoadImage 到 HBitMap,然后让 mdc 去 select,然后再让 mdc 复制到 hdc?
- 为什么不直接让 hdc 直接去 select 图片的 HBitMap?
- 修改图片之后再显现的实例
为了能够理清楚一些常见的 DC ,这里请让博主在介绍一下几种 DC 的获得方式:
- BeginPaint 通过窗口 (hwnd) 获得
- GetDC 通过窗口 (hwnd) 获得
- CreatCompatibleDC 通过 hdc 获得
关于这三种情况获得的 DC,需要注意的是:从窗口上获得到的DC在释放资源的时候需要调用 ReleaseDC (释放引用,这样其他地方还可以接着用),而通过 CreatCompatibleDC 创建的 DC 我们通常称之为 Memory DC (博主管这叫“缓存DC”),当你不需要它的时候,需要通过 DeleteDC 来释放资源(因为这个DC是你单独申请的,如果你不用了,其他也不会有地方用它,只能直接 Delete)。
BeginPaint 与 GetDC
同样是通过窗口获得其客户端区域(Client area)的可绘制DC,BeginPaint 函数是先使窗口有效再重画,而 GetDC 没有这样,使用 GetDC 时由于窗口始终处于无效状态,不断会产生WM_PAINT消息。
BeginPaint 的表现是,收到 WM_PAINT 时绘制。所以正常情况下,如果没有手动触发 WM_PAINT 那么打开窗口则只绘制一次。
GetDC 的表现是,不断收到 WM_PAINT 消息,绘图区域(DC)不停地刷新。如果绘制过程比较慢的
CreatCompatibleDC
CreatCompatibleDC 函数的书面描述是,用来创建一个与指定设备兼容的内存设备上下文环境(DC)。
这个书面描述感觉有点不好理解,这里博主说一下自己的看法:
关于这个 CreatCompatibleDC 函数首先要了解这其中的单词:Creat、Compatible、DC。其中 Create 是“创建”、Compatible 是兼容的、DC 是博主说的“可画图的内存对象”。那么创建和 DC 都很好理解。这里我们重点要理解这个“兼容的”这个单词。
3个意思:
- 该DC的类型与兼容目标相同。虽然博主一再强调关于DC只需要把它当做“可以用来画图的内存就好了”,但是“橘生淮南则为橘,生于淮北则为枳”,从不同的设备中创建出来的DC,互相之间也是有差别的,由此产生的DC的类型包括:显示器、打印机、存储器和数据的索引。
- 该DC不需要指定区域、大小。感觉这一条有点凑数的感觉(博主:别打我!)
- 该DC与兼容的对象可以方便交流数据。该功能在GDI内部实现。比如使用 BitBlt 函数可以将兼容 DC 上的位图写入到目标 DC中。
加载BMP位图实例
BMP 是 Bitmap 的简写,中文常常称之为 “位图”。
#define WINDOW_WIDTH 400 #define WINDOW_HEIGHT 300 void Paint(HWND hwnd) { PAINTSTRUCT ps; HDC hdc; HDC mdc; HBITMAP hbmp; // 位图绘制对象句柄 hdc = BeginPaint(hwnd, &ps); // 创建缓存DC (当前窗口DC的兼容DC) mdc = CreateCompatibleDC(hdc); // 加载 "E:\bg.bmp" 到位图绘制对象 hbmp 中 hbmp = (HBITMAP)LoadImage( NULL, // 模块实例句柄(要加载的图片在其他DLL中时) "E:\bg.bmp", // 位图路径 IMAGE_BITMAP, // 位图类型 WINDOW_WIDTH, // 指定图片宽 WINDOW_HEIGHT, // 指定图片高 LR_LOADFROMFILE // 从路径处加载图片 ); // 缓存DC选择位图绘制对象(可以理解为将图片存到mdc中) SelectObject(mdc, hbmp); // 将缓存DC中的位图复制到窗口DC上 BitBlt( hdc, // 目的DC 0,0, // 目的DC的 x,y 坐标 WINDOW_WIDTH, // 要粘贴的图片宽 WINDOW_HEIGHT, // 要粘贴的图片高 mdc, // 缓存DC 0,0, // 缓存DC的 x,y 坐标 SRCCOPY // 粘贴方式 ); DeleteObject(hbmp); DeleteDC(mdc); EndPaint(hwnd, &ps); }
如何理解上述代码
对于初学者很容易对上述代码迷惑。常见的几个问题列举:
为什么要用 HBitMap 去 LoadImage,而不是直接用 DC 去 Load?
按照博主的思路来描述一下这个问题吧,画图可以简单的分成三个部分,分别是:
载体(如白纸、油画画板)、工具(如2B铅笔、马克笔)、颜料。
对应到GDI程序中,使用画笔可以看成:载体(DC)、工具(画笔 HPEN)、颜料(CreatePen 函数中传入)。
那么对应加载位图可以这样看:载体(DC)、工具(?)、颜料(LoadImage 中指定图片)
这样很明显可以看到缺少一个工具加载位图的工具。也就是我们的 HBITMAP 对象。
这里拓展一下GDI中可以在DC用来绘制的工具:
对象 | 通过什么函数创建 |
---|---|
Bitmap | CreateBitmap, CreateBitmapIndirect, CreateCompatibleBitmap, CreateDIBitmap, CreateDIBSection。 Bitmap对象只能被缓存DC(通过 CreatCompatibleDC创建)选中,并且单个bitmap不能同时被多个DC选中。 |
Brush | CreateBrushIndirect, CreateDIBPatternBrush, CreateDIBPatternBrushPt, CreateHatchBrush, CreatePatternBrush, CreateSolidBrush |
Font | CreateFont, CreateFontIndirect |
Pen | CreatePen, CreatePenIndirect |
Region | CombineRgn, CreateEllipticRgn, CreateEllipticRgnIndirect, CreatePolygonRgn, CreateRectRgn, CreateRectRgnIndirect |
此列表出处见 SelectObject function on MSDN
为什么要用 LoadImage 到 HBitMap,然后让 mdc 去 select,然后再让 mdc 复制到 hdc?
如果C语言基础学的好的话,那么这个问题也可以用一个很简单的角度去看待:
1.申请缓存空间
2.加载数据到缓存
3.输出数据(将缓存中的数据写入到目标内存)
以基础的C语言代码为例,假设是“输出用户输入的一个整数”,那么C语言代码如下:
// 1.(申请缓存空间) 声明一个4字节的int型变量,用于缓存用户输入 int num; // 2.(加载数据到缓存) 将用户输入的数字加载到num中 scanf("%d", &num); // 3.(将缓存中的数据写入到目标内存) 输出 num 到屏幕(将num的值写入到显存) printf("你输入的是:%d n", num);
对应到加载图片的程序就可以这样理解了。
void Paint(HWND hwnd) { PAINTSTRUCT ps; HDC hdc; HDC mdc; HBITMAP hbmp; hdc = BeginPaint(hwnd, &ps); // 1.(申请缓存空间) 创建缓存DC mdc = CreateCompatibleDC(hdc); // 2.(加载数据到缓存) 通过 Hbitmap 加载数据到 缓存DC hbmp = (HBITMAP)LoadImage( ... ); SelectObject(mdc, hbmp); // 3.(将缓存中的数据写入到目标内存) 将缓存DC中的位图复制到窗口DC上 BitBlt( ... ); DeleteObject(hbmp); DeleteDC(mdc); EndPaint(hwnd, &ps); }
这代码也就是看起来长了一点,没什么大不了的,本质上还是基础的程序逻辑。区别仅仅在于:
1.缓存的空间从 int 换成了 HDC。
2.加载数据的方法从使用 scanf 换成了 LoadImage 和 SelectObject。
3.输出数据的对象从 int类型的num 写入到显存,换成了 HDC类型的mdc 写入到 hdc;输出数据的方法从 printf 换成了 BitBlt。
PS:引用博主一句 装逼 的话来总结一下上面的对比:“程序做并且只在做两件事,一、读数据,二、写数据”
为什么不直接让 hdc 直接去 select 图片的 HBitMap?
1.首先上文有提到过 Bitmap 对象只能被缓存DC (Memory DC,通过 CreateCompatibleDC 创建的)选中。
2.其次,就算情况1不存在,bitmap 可以被 hdc 选中,这在程序的逻辑上也是一件很别扭的事情。依旧是套用 C语言基础的程序来看这个问题:
// 1.不申请缓存空间 // 2.3.直接获取数据并输出 printf("你输入的是:%d n", scanf( ? ) );
或者这样:
// 1.不申请缓存空间 // 2.3.直接获取数据并输出 printf("你输入的是:%s n", gets( ? ) );
基础好的可以马上发现上面两个示意的代码都是没法实现的。当然,如果你依旧觉得这个合理也是可以的,毕竟在其他的语言(比如python)里面这种情况是可以实现的。但是这样写的话,会有如下两种情况:
- 不自由。无法修改该数据,只能死板的输出。
- 效率低。没有缓存,要输出的时候必须要当时加载,下一次再次用到的时候又要重新加载。
综上。缓存DC是有存在的必要。
修改图片之后再显现的实例
为了照应上文,这里附上一个修改缓存DC中图片然后再显示到窗口的例子。
void Paint(HWND hwnd) { PAINTSTRUCT ps; HDC hdc; HDC mdc; HBITMAP hbmp; // 位图绘制对象句柄 hdc = BeginPaint(hwnd, &ps); // 加载图片到缓存 DC mdc = CreateCompatibleDC(hdc); hbmp = (HBITMAP)LoadImage(NULL, "E:\bg.bmp", IMAGE_BITMAP, WINDOW_WIDTH, WINDOW_HEIGHT, LR_LOADFROMFILE); SelectObject(mdc, hbmp); // 修改缓存DC (在缓存的图片上写字) TextOut(mdc, 0,0, TEXT("Hi, 缓存DC你好。"), strlen(TEXT("Hi, 缓存DC你好。"))); // 将缓存DC中的位图复制到窗口DC上 BitBlt(hdc, 0,0, WINDOW_WIDTH, WINDOW_HEIGHT, mdc, 0,0, SRCCOPY); DeleteObject(hbmp); DeleteDC(mdc); EndPaint(hwnd, &ps); }