Windows Programming/GDI与绘图
设备上下文
编辑设备上下文DC是要绘图的环境设备的数据结构。每一个窗口都对应一个DC。可以把窗口的DC描述为对应于一块显存。
系统不允许直接向窗口DC加载一份位图。必须用函数CreateCompatibleBitmap在内存中创建窗口的一个兼容DC(此时是单色的1*1像素大小),用函数CreateCompatibleBitmap或者LoadImage加载一个位图,用函数SelectObject把位图加载到兼容DC中(此时就有了绘图的点阵),再用函数BitBlt把兼容DC的一部分拷贝到窗口DC。可以通过调用GetDeviceCaps函数来确定一个设备是否支持光栅操作。当不再需要内存设备上下文环境时,可调用DeleteDc函数删除它。
GDI 坐标变换
编辑Win32 API使用四种坐标空间:[1]
- 世界坐标系空间:允许平移、旋转、尺度、剪切、翻转等变化。使用SetWorldTransform函数通知DC将使用世界坐标系。此后的逻辑坐标系空间是世界坐标系空间。
- 页面坐标空间,是通常的逻辑坐标系。
- 设备坐标空间:只允许到物理设备坐标空间的平移变换。总是使用像素作为坐标值,x、y轴的零点总是在左上角。
- 物理设备坐标空间:通常指程序窗口的客户区,但也可以是整个桌面、应用程序的整个窗口(包括框架、标题栏和菜单栏)、打印机的一页、绘图仪的一页纸。
- 客户区域坐标:左上角为(0,0)。通过GetDC()函数获取。
- 屏幕坐标,包括整个屏幕,屏幕的左上角为(0,0)屏幕坐标。通过GetDC()函数获取。对于非子窗口,WM_MOVE消息、CreateWindow函数和MoveWindow函数。以及GetMessage、GetCursorPos、GetWindowRect、WindowFromPoint和SetBrushOrg等函数。ClientToScreen和ScreenToClient函数可在客户区域坐标与屏幕区域坐标之间转换。
- (应用程序)框架窗口坐标,包括应用程序的标题条、菜单、滚动条和窗口框,窗口的左上角为(0,0)。使用GetWindowDC()函数得到的窗口设备环境,可以将逻辑单位转换成窗口坐标。
设备空间用于提供一个设备无关的坐标系。设备空间到物理空间的转换只限于平移,并由Windows的窗口管理部分控制,这种转换的唯一用途是确保设备空间的原点被映射到物理设备上的适当点上。没有函数能设置这种转换,也没有函数可以获取有关数据。
除了少数特例,GDI绘图API函数使用逻辑坐标。windows对所有的消息(如WM_SIZE,WM_MOUSEMOVE,WM_LBUTTONDOWN, WM_LBUTTONUP),所有的非GDI函数和少数GDI函数(如GetDeviceCaps函数)永远使用设备坐标。鼠标的光标位置用设备坐标表示。可以认为MFC的CDC的所有成员函数都以逻辑坐标作为其参数;CWnd的成员函数都以设备坐标作为其参数;所有选中测试操作(如CRect::PtInRect)都应考虑设备坐标。逻辑坐标和设备坐标的转换由映射模式决定。映射模式被储存在设备环境(DC)中。GetMapMode函数用于从设备环境得到当前的映射模式,SetMapMode函数用于设置设备环境的映射模式。有8种映射方式。缺省映射模式MM_TEXT的得名,是因为轴的方向与读文本的方向一致。
逻辑坐标系与设备坐标系变换时,需要注意“原点”(origin point)并不是通常所指,而是一个坐标系中一点,用于同另一个坐标系的“原点”重合。因此,坐标系的x、y坐标都为0的点,本文称作“零点”以示区别。逻辑坐标所在的坐标系称为"窗口"(Windowport),将设备坐标所在的坐标系称为"视口"(Viewport)。逻辑坐标系原点(xWinOrg,yWinOrg)总被映射为设备坐标系原点(xViewOrg,yViewOrg)。API函数SetViewportOrg和SetWindowOrg 用来设置视口和窗口的原点。
API函数SetWindowExt和SetViewportExt 用于改变窗口和视口的范围。如果参数为负值表示相应的坐标轴反向。范围本身的值并不重要,但两个范围的比值用于坐标变换。
API函数DPtoLP和LPtoDP在设备坐标及逻辑坐标之间互相转换。
如果未做任何坐标系变换,逻辑坐标和设备坐标在缺省模式(MM_TEXT)下,如果移动了客户区的垂直或水平滚动条,这两种坐标就不简单对应了。
示例
编辑void CMFCApplication1View::OnDraw(CDC* pDC)
{
pDC->SetMapMode(MM_LOMETRIC); // 此后逻辑坐标系的y轴为垂直向上
pDC->SetViewportOrg(100,100); // 设备坐标的原点设在设备空间的(100,100), 该原点总是与逻辑坐标系原点重合
pDC->Rectangle(0, 0, 40, 40); // 在逻辑坐标的第一象限画出一个空心正方形
pDC->SelectStockObject(GRAY_BRUSH);
pDC->Rectangle(-10, -10, 10, 10); // 围绕逻辑坐标零点画出一个小型灰色正方形
pDC->TextOutW(50,0,CString("Zero Point 1")); // 目前,重合的原点在此,同时也是逻辑坐标的零点
pDC->SetWindowOrg(200, 200); // 逻辑坐标系的原点设在逻辑坐标(200,200),然后该原点与此时的设备坐标的原点重合
pDC->SelectStockObject(BLACK_BRUSH);
pDC->Rectangle(-10, -10, 10, 10); // 围绕此时的逻辑坐标零点画出一个小型黑色正方形
pDC->Rectangle(0, 0, 40, 40); // 在此时的逻辑坐标的第一象限画出一个黑色正方形
pDC->TextOutW(50, 0, CString("Zero Point 2")); //此时的逻辑坐标零点
}
- GetWindowRect() 得到的是在屏幕坐标系下的RECT(即以屏幕左上角为原点)
- GetClientRect() 得到的是在客户区坐标系下的窗口客户区的RECT。因此返回值的左上角永远为(0,0),只具有描述客户区大小的意义
- ScreenToClient() 就是把屏幕坐标系下的RECT坐标转换为指定窗口的客户区坐标系下的RECT坐标。
绘图时机
编辑- Invalidate把整个客户区标为无效,屏幕不一定马上更新。
- InvalidateRect()的功能与Invalidate基本一样,指定某个矩形区域无效
- UpdateWindow绕过了消息队列,直接按照WM_PAINT消息调用窗口过程,其无效区范围就是消息队列中WM_PAINT消息已有的无效区。
- RedrawWindow比UpdateWindow功能更为丰富。