signed

QiShunwang

“诚信为本、客户至上”

映射模式详解

2021/6/3 15:46:19   来源:
1、视口、窗口

GDI绘图中涉及两个坐标系:逻辑坐标系和设备坐标系。逻辑坐标系对应窗口,设备坐标系对应视口。默认情况下,整个客户区就是视口。设备坐标系的点(0,0)在客户区左上角,x轴方向向右增加,y轴方向向下增加,单位是像素。窗口相当于画布,GDI绘图函数中坐标参数和尺寸参数都是基于窗口的逻辑坐标系。windows负责根据映射模式、窗口原点、窗口范围、视口原点、视口范围,把逻辑坐标系中的图像映射到设备坐标系中显示。

2、映射模式

windows提供了8种映射模式:

映射模式逻辑单位x轴方向y轴方向
MM_TEXT像素
MM_LOMETRIC0.1mm
MM_HIMETRIC0.01mm
MM_LOENGLISH0.01in
MM_HIENGLISH0.001in
MM_TWIPS1/1440in
MM_ISOTROPICx = y可选可选
MM_ANISOTROPICx != 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获取到的LOGPIXELSXLOGPIXELSY分别表示x轴和y轴方向的每英寸像素数(dpi)。使用GetDeviceCaps获取的HORZSIZEHORZRESVERTSIZEVERTRES也可以计算出dpi值,但和LOGPIXELSXLOGPIXELSY是不相等的。

// 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、绘图基础》中,使用LOGPIXELSXLOGPIXELSY测试计算特定字号字体的高度是正确的。但逻辑单位英寸和设备单位像素之间的转换应该使用HORZSIZEHORZRESVERTSIZEVERTRES计算出来的DPIxDPIy

下面是测试代码和测试结果:黑色线是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、窗口范围和视口范围

使用SetWindowExtExSetViewportExtEx可以分别设置窗口范围和视口范围。一般地,可以按照实际需要设置窗口范围,因为绘图直接面对窗口,使用逻辑坐标;而视口范围设置为实际客户区大小。

窗口范围和视口范围是对应的,或者说窗口范围内的图像是刚好可完成显示在视口范围内的。这里需要区分好视口和客户区的区别,默认情况下,视口就是整个客户区,所以窗口范围内的图像完全可以映射到整个客户区,此时的视口范围也没有特殊的含义。但是,如果使用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、窗口原点和视口原点

使用SetWindowOrgExSetViewportOrgEx可以分别设置窗口原点和视口原点。默认的窗口原点和视口原点都是点(0,0)。改变窗口原点或视口原点的直观表现是平移图像。

视口原点窗口原点图像
(u,v)(-u,-v)x轴方向平移u个单位,y轴方向平移v个单位
5、映射过程

映射过程具体可以看成:

  1. 把图像粘贴到设备坐标系,使两坐标系的x轴、y轴、点(0,0)分别重合
  2. 如果逻辑坐标系和设备坐标系的y轴方向相反,那么图像做一次关于x轴的轴对称变换
  3. 平移图像,直到逻辑坐标系原点到达设备坐标系原点
  4. 按照视口范围和窗口范围的比例关系缩放图像