1、视口、窗口 |
---|
GDI
绘图中涉及两个坐标系:逻辑坐标系和设备坐标系。逻辑坐标系对应窗口,设备坐标系对应视口。默认情况下,整个客户区就是视口。设备坐标系的点(0,0)
在客户区左上角,x
轴方向向右增加,y
轴方向向下增加,单位是像素。窗口相当于画布,GDI
绘图函数中坐标参数和尺寸参数都是基于窗口的逻辑坐标系。windows负责根据映射模式、窗口原点、窗口范围、视口原点、视口范围,把逻辑坐标系中的图像映射到设备坐标系中显示。
2、映射模式 |
---|
windows提供了8种映射模式:
映射模式 | 逻辑单位 | x轴方向 | y轴方向 |
---|---|---|---|
MM_TEXT | 像素 | 右 | 下 |
MM_LOMETRIC | 0.1mm | 右 | 上 |
MM_HIMETRIC | 0.01mm | 右 | 上 |
MM_LOENGLISH | 0.01in | 右 | 上 |
MM_HIENGLISH | 0.001in | 右 | 上 |
MM_TWIPS | 1/1440in | 右 | 上 |
MM_ISOTROPIC | x = y | 可选 | 可选 |
MM_ANISOTROPIC | x != y | 可选 | 可选 |
-
MM_TEXT
窗口原点:默认(0, 0),可以改变 视口原点:默认(0, 0),可以改变 窗口范围:(1, 1),不可改变 视口范围:(1, 1),不可改变
-
MM_LOMETRIC、MM_HIMETRIC、MM_LOENGLISH、MM_HIENGLISH、MM_TWIPS
窗口原点:默认(0, 0),可以改变 视口原点:默认(0, 0),可以改变 窗口范围:默认值依赖系统,不可改变 视口范围:默认值依赖系统,不可改变
视口范围和窗口范围的数值并没什么特殊意义,重要的是视口范围和窗口范围的比值,它表示了单位长度的像素数。
-
MM_ISOTROPIC
窗口原点:默认(0, 0),可以改变 视口原点:默认(0, 0),可以改变 窗口范围:默认值依赖系统,可以改变 视口范围:默认值依赖系统,可以改变
isotropic
是各向同性的意思,在MM_ISOTROPIC
中表现为x
轴和y
轴方向上,每个逻辑单位对应相同数量的设备单位,也就是说dpiX
等于dpiY
。实际上,除了MM_ANISOTROPIC
外,其他映射模式都是各向同性的,所以可以轻松绘制正方形和圆等图形。MM_ISOTROPIC
的特殊之处在于可以修改窗口范围和视口范围,直观表现是可以在x
轴和y
轴上等比缩放。
3、逻辑单位英寸和设备单位像素的关系 |
---|
《5、绘图基础》中提到过,GetDeviceCaps
获取到的LOGPIXELSX
和LOGPIXELSY
分别表示x
轴和y
轴方向的每英寸像素数(dpi
)。使用GetDeviceCaps
获取的HORZSIZE
、HORZRES
、VERTSIZE
、VERTRES
也可以计算出dpi
值,但和LOGPIXELSX
和LOGPIXELSY
是不相等的。
// 1毫米=0.039370078740157英寸
#define INCH 0.039370078740157
int pixelX = GetDeviceCaps(hdc, HORZRES); // x轴方向像素数
int pixelY = GetDeviceCaps(hdc, VERTRES); // y轴方向像素数
int phsX = GetDeviceCaps(hdc, HORZSIZE); // x轴方向物理尺寸mm
int phsY = GetDeviceCaps(hdc, VERTSIZE); // y轴方向物理尺寸mm
float DPIx = pixelX / (phsX * INCH); // x轴方向dpi(测试结果是:141.767441)
float DPIy = pixelY / (phsY * INCH); // y轴方向dpi(测试结果是:142.134720)
int logX = GetDeviceCaps(hdc, LOGPIXELSX); // LOGPIXELSX(测试结果是:96)
int logY = GetDeviceCaps(hdc, LOGPIXELSY); // LOGPIXELSY(测试结果是:96)
在《5、绘图基础》中,使用LOGPIXELSX
和LOGPIXELSY
测试计算特定字号字体的高度是正确的。但逻辑单位英寸和设备单位像素之间的转换应该使用HORZSIZE
、HORZRES
、VERTSIZE
、VERTRES
计算出来的DPIx
和DPIy
。
下面是测试代码和测试结果:黑色线是MM_LOENGLISH
映射模式下绘制的2英寸直线;红色线是MM_TEXT
映射模式下,以像素为单位,根据计算dpi
值进行单位转换绘制的2英寸直线。
SetMapMode(hdc, MM_LOENGLISH);
MoveToEx(hdc, 100, -100, NULL);
LineTo(hdc, 100, -300);
HPEN pen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
SelectObject(hdc, pen);
SetMapMode(hdc, MM_TEXT);
MoveToEx(hdc, 100, DPIy, NULL);
LineTo(hdc, 100, DPIy * 3);
dpi
还可以根据视口范围和窗口范围计算。
SetMapMode(hdc, MM_LOENGLISH);
SIZE windowExt;
GetWindowExtEx(hdc, &windowExt);
SIZE viewportExt;
GetViewportExtEx(hdc, &viewportExt);
// MM_LOENGLISH模式的窗口范围单位是0.01in,所以结果要*100,单位转换为1in
// MM_LOENGLISH模式逻辑坐标系y轴方向向上,所以y轴方向的窗口范围是负数
DPIx = (float)viewportExt.cx / (float)windowExt.cx * 100; // 141.802078
DPIy = (float)viewportExt.cy / (float)windowExt.cy * 100; // -142.105255
4、窗口范围和视口范围 |
---|
使用SetWindowExtEx
和SetViewportExtEx
可以分别设置窗口范围和视口范围。一般地,可以按照实际需要设置窗口范围,因为绘图直接面对窗口,使用逻辑坐标;而视口范围设置为实际客户区大小。
窗口范围和视口范围是对应的,或者说窗口范围内的图像是刚好可完成显示在视口范围内的。这里需要区分好视口和客户区的区别,默认情况下,视口就是整个客户区,所以窗口范围内的图像完全可以映射到整个客户区,此时的视口范围也没有特殊的含义。但是,如果使用SetViewportExtEx
设置了视口范围,此时的视口范围就具有了一定的含义。SetViewportExtEx
传参的单位是设备单位像素,它设置的视口范围定义了视口的宽度和高度。
SetMapMode(hdc, MM_ISOTROPIC);
SetWindowExtEx(hdc, 32767, 32767, NULL);
SetViewportExtEx(hdc, cyClient / 2, -cyClient / 2, NULL);
SetViewportOrgEx(hdc, 0, cyClient, NULL);
Rectangle(hdc, 0, 0, 32767, 32767);
按照上面代码设置视口范围和窗口范围后,视口实际是一个边长为cyClient / 2
的正方形。
成功设置视口范围后,真正的视口范围和设置的数值可能不一致。为了满足各向同性的性质,windows会根据窗口范围对视口范围进行调整,调整的原则是使尽可能多的图像显示在客户区。比如按下面代码设置视口范围和窗口范围:
SetMapMode(hdc, MM_ISOTROPIC);
SetWindowExtEx(hdc, 32767, 32767, NULL);
SetViewportExtEx(hdc, cxClient, -cyClient, NULL);
SetViewportOrgEx(hdc, 0, cyClient, NULL);
假设cxClient>cyClient
,即客户区宽度大于高度。为了满足各向同性,要么增大y
轴方向视口范围,要么减小x
轴方向视口范围。如果增大y
轴方向视口范围,必然造成部分图像超出客户区顶部。所以,windows会减小x
轴方向视口范围。
改变视口范围和窗口范围可以用于对图像进行x
方向和y方向的等比缩放:
视口范围 | 窗口范围 | 图像 |
---|---|---|
放大 | 缩小 | 放大 |
缩小 | 放大 | 缩小 |
5、窗口原点和视口原点 |
---|
使用SetWindowOrgEx
和SetViewportOrgEx
可以分别设置窗口原点和视口原点。默认的窗口原点和视口原点都是点(0,0)
。改变窗口原点或视口原点的直观表现是平移图像。
视口原点 | 窗口原点 | 图像 |
---|---|---|
(u,v) | (-u,-v) | x轴方向平移u个单位,y轴方向平移v个单位 |
5、映射过程 |
---|
映射过程具体可以看成:
- 把图像粘贴到设备坐标系,使两坐标系的
x
轴、y
轴、点(0,0)
分别重合 - 如果逻辑坐标系和设备坐标系的
y
轴方向相反,那么图像做一次关于x
轴的轴对称变换 - 平移图像,直到逻辑坐标系原点到达设备坐标系原点
- 按照视口范围和窗口范围的比例关系缩放图像