| 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轴的轴对称变换 - 平移图像,直到逻辑坐标系原点到达设备坐标系原点
- 按照视口范围和窗口范围的比例关系缩放图像
博客介绍了绘图中的逻辑坐标系和设备坐标系,前者对应窗口,后者对应视口。Windows提供8种映射模式,多数为各向同性。还说明了视口范围和窗口范围的关系及作用,可用于图像等比缩放,改变原点能平移图像,最后阐述了映射过程。
704





