1. Windows坐标系统
坐标系主要分为两种:
- 逻辑坐标系:主要是在“虚拟界面”中绘图,是绘制到设备坐标系的准备阶段。
- 设备坐标系:由客户区坐标、窗口坐标、屏幕坐标组成,主要和显示设备有关。
在不同的空间中,各个坐标系及其坐标表示方法稍有不同。世界空间和页面空间的坐标系为平时熟悉的方式,x轴向右为正,y轴向上为正,设备空间中,y轴改为向下为正(要问为什么这么定,emm,自己问MS去)。
MS其实对绘制的流程进行了明确的定义,且在不同空间中进行操作时的职能进行了分离,使不同层级下的操作内容更明确。先说逻辑坐标系中的绘制,windows定义的绘图流程如下:
- 窗口上绘制大小和原点配置(可使用默认值);
- 世界空间上绘制(允许对图形的缩放、平移、旋转、裁剪、镜像);
- 页面空间绘制(设置映射模式SetMapMode,确定怎么画到设备空间上);
接下来是设备坐标系的绘制,流程如下:
- 视口上绘制大小和原点配置(可使用默认值);
- 设备空间绘制(仅允许进行平移操作);
- 物理设备显示(最终显示在对应的区域)。
注意点:viewPort 和 windowsPort,这两者的翻译是视口,窗口。视口是基于设备坐标(像素的),窗口是基于逻辑坐标的,默认情况下两者重合,但可设置视口坐标系的原点以及窗口坐标系的原点。
- 要注意区分视口坐标系原点跟视口原点的区别,窗口坐标系原点跟窗口原点的区别。
- 视口坐标系其实就是设备坐标系,它是固定不变的,因此设备原点永远是(0,0)。
- 视口原点是可变的,它默认处于设备原点,但是可以更改。
- 窗口坐标系指定了在窗口绘图时的参考点,参考点就是窗口坐标系的原点。
- 窗口原点默认等于窗口坐标系原点,但是窗口原点可以改变。
- 窗口原点永远跟视口原点匹配映射。//这个是最主要的
2. 常见函数及参数
世界空间绘制的常用函数:
函数 | 主要参数 | 作用 |
SetWorldTransform | CONST XFORM *lpXform | 对图形处理,包括缩放、平移、旋转、裁剪、镜像 |
SetGraphicsMode | int iMode: GM_COMPATIBLE(默认) GM_ADVANCED(可使用SetWorldTransform) | 设置特定设备上下文的图形模式 |
页面空间绘制常用参数:
函数 | 主要参数 | 作用 |
SetMapMode | int fnMapMode: MM_ANISORTOPIC:自定义缩放比例 MM_HIENGLISH:0.001inch-->1像素;x正轴向右,y正轴向上 MM_HIMETRIC:0.01毫米-->1像素;x正轴向右,y正轴向上 MM_ISOTROPIC:x和y方向的缩放比例相同 MM_LOENGLISH:0.01inch;x正轴向右,y正轴向上 MM_LOMETRIC:0.1毫米-->1像素;x正轴向右,y正轴向上 MM_TEXT:1:1拷贝;x正轴向右,y正轴向下 MM_TWIPS:1/1440inch-->1像素;x正轴向右,y正轴向上 | 设置映射模式SetMapMode,确定怎么画到设备空间上 |
SetWindowExtEx | int nXExtent, int nYExtent; | 设置绘制区域大小 |
SetWindowOrgEx | int X, int Y; | 设置绘制的窗口原点 |
设备空间常用参数:
函数 | 主要参数 | 作用 |
SetViewportExtEx | int nXExtent, int nYExtent; | 设置绘制区域大小 |
SetViewportOrgEx | int X, int Y; | 设置显示的视口原点 |
3. Examples
- 修改窗口原点
-
SetWindowOrgEx(dc.m_hDC, 200, 200, NULL); TextOut(dc.m_hDC, 200, 200, "123456", 6);
先在窗口中绘制, 最终转换到视口中显示。
-
- 修改视口原点
-
// viewpoint SetViewportOrgEx(dc.m_hDC, 100, 100, NULL); TextOut(dc.m_hDC, 200, 200, "123456", 6);
在窗口中的坐标点(200,200)绘制文字,而视口中原点坐标变为(100,100),经一定量的偏移,最终绘制在界面坐标(300,300) 处。
-
- 窗口和视口同时配置
-
// hybird SetWindowOrgEx(dc.m_hDC, 200, 200, NULL); SetViewportOrgEx(dc.m_hDC, 100, 100, NULL); TextOut(dc.m_hDC, 200, 200, "123456", 6);
绘制在坐标(100,100)的位置。这里为什么是这样呢?首先在窗口中绘制图形,之后以(200,200)为起点位置,将窗口中的内容复制到视口以(100,100)为起点的位置。
-
这里,假设本文的绘制位置更改为(250,300),在窗口和视口中绘制的位置又是多少呢?——窗口为(250,300),视口为(150,200),视口中的计算方法:150=(250-200)+100; 200=(300-200)+100;(这里SetMapMode为默认参数MM_TEXT,未配置缩放系数或修改坐标轴方向)。
-
- 配置缩放系数为0.5,即从窗口中2个单位的位置,在视口中表示为一个1单位。
-
可以看到,绘制的位置还是(100,100)。因为现在窗口中的(200,200)位置绘制了文本,但转换到视口(即显示设备)时,位置信息缩小了1倍。CRect rect; GetClientRect(&rect); SetMapMode(dc.m_hDC, MM_ANISOTROPIC); SetWindowExtEx(dc.m_hDC, rect.Width()*2, rect.Height()*2, NULL); SetViewportExtEx(dc.m_hDC, rect.Width(), rect.Height(), NULL); TextOut(dc.m_hDC, 200, 200, "123456", 6);
- 从窗口的页面空间转换到设备空间的公式,原文请见此:
-
Dx = ((Lx - WOx) * VEx / WEx) + VOx 其中: Dx x value in device units Lx x value in logical units (also known as page space units) WOx window x origin VOx viewport x origin WEx window x-extent VEx viewport x-extent
-
-
- 使用世界空间进行转换
-
// world transform int nGraphicsMode = SetGraphicsMode(dc.GetSafeHdc(), GM_ADVANCED); XFORM xForm; xForm.eM11 = (FLOAT) 0.5; xForm.eM12 = (FLOAT) 0.0; xForm.eM21 = (FLOAT) 0.0; xForm.eM22 = (FLOAT) 0.5; xForm.eDx = (FLOAT) 0.0; xForm.eDy = (FLOAT) 0.0; SetWorldTransform(dc.GetSafeHdc(), &xForm); //Rectangle(dc.GetSafeHdc(), 0, 0, 200, 300); TextOut(dc.m_hDC, 200, 200, "123456", 6);
另一种把图绘制在(100,100)处的方法就是使用世界空间,因为说来说去,图形最开始画的地方就在这里,而且在世界空间中能进行多种的图形操作。这里用到了SetWorldTransform、XFORM和SetGraphicsMode,这是必须使用的函数或者参数,具体使用方法各请看MSDN,那里有非常详细的介绍。
-
- 综合使用WindowOrg和ViewPortOrg
-
// using windowpoint & viewpoint SetMapMode(dc.m_hDC, MM_TEXT); SetViewportOrgEx(dc.GetSafeHdc(), 0, rect.bottom/10, NULL); SetWindowOrgEx(dc.GetSafeHdc(), -100, 0, NULL); dc.Rectangle(0, 0, 200, 300); TextOut(dc.m_hDC, 200, 200, "123456", 6);
主要明白绘图现在窗口上绘,再是在视口上显示,这个过程即可,转换过程中可能会有一定比例的缩放。
-
4. 引用
1. Using SetWorldTransform() to Rotate Basic Shapes by Any Angle
2. MSDN(官方文档,最最权威!)
3. 其他文档