1、1Windows 应用程序绘制图形时使用的是一种逻辑单位,每个逻辑单位的大小由映射模式决定, 这个逻辑单位既可以与设备单位(屏幕或打印机上的一个像素点)相同,也可以是一种物理单 位(如毫米),还可以是用户自定义的一种单位。在 Windows 应用程序中,只要与输出有关系,都 要使用映射模式。本文的目的是帮助读者了解映射模式的一些基本知识,并对在使用中经常 出现的一些问题提出解决方案。一、映射模式基本知识当 Windows 应用程序在其客户区绘制图形时,必须给出在客户区的位置,其位置用x 和 y 两个坐标表示,x 表示横坐标,y 表示纵坐标。在所有的 GDI 绘制函数中,这些坐标使用的是一 种“
2、逻辑单位“ 。当 GDI 函数将输出送到某个物理设备上时,Windows 将逻辑坐标 转换成设备坐标(如屏幕或打印机的像素点)。逻辑坐标和设备坐标的转换是由映射模式决 定的。映射模式被储存在设备环境中。GetMapMode 函数用于从设备环境得到当前的映射模 式,SetMapMode 函数用于设置设备环境的映射模式。1.逻辑坐标逻辑坐标是独立于设备的,它与设备点的大小无关。使用逻辑单位,是实现“所见即所得“的基础。当程序员在调用一个画线的 GDI 函数 LineTo,画出 25.4mm(1 英寸) 长的线时,他并不需要考虑输出的是何种设备。若设备是 VGA 显示器,Windows 自动将其转化
3、为 96 个像素点;若设备是一个 300dpi 的激光打印机,Windows 自动将其转化为 300 个像素点。2.设备坐标Windows 将 GDI 函数中指定的逻辑坐标映射为设备坐标,在所有的设备坐标系统中,单位以像素点为准,水平值从左到右增大,垂直值从上到下增大。2Windows 中包括以下 3 种设备坐标,以满足各种不同需要:(1)客户区域坐标,包括应用程序的客户区域,客户区域的左上角为(0,0 )。(2)屏幕坐标,包括整个屏幕,屏幕的左上角为(0,0)。屏幕坐标用在 WM_MOVE 消息中(对于非子窗口)以及下面的 Windows 函数中:CreateWindow 和MoveWind
4、ow(都对于非子窗口)、GetMessage、 GetCursorPos、GetWindowRect、WindowFromPoint 和 SetBrushOrg中。用函数 ClientToScreen 和 ScreenToClient 可以将客户区域坐标转换成屏幕区域坐标,或反之。(3)全窗口坐标,包括一个程序的整个窗口,包括标题条、菜单、滚动条和窗口框,窗口的左上角为(0,0)。使用 GetWindowDC 得到的窗口设备环境,可以将逻辑单位转换成窗口坐标。3.逻辑坐标与设备坐标的转换方式映射方式定义了 Windows 如何将 GDI 函数中指定的逻辑坐标映射为设备坐标。要继续讨论映射方式我
5、们要介绍 Windows 有关映射模式的一些术语:我们将逻辑坐标所在的坐标系称为“ 窗口 “,将设备坐标所在的坐标系称为“视口“ 。“窗口“依赖于逻辑坐标,可以是像素点、毫米或程序员想要的其他尺度。“视口“依赖于设备坐标(像素点)。通常,视口和客户区域等同。但是,如果程序员用GetWindowDC 或 CreateDC 获取了一个设备环境,则视口也可以指全窗口坐标或屏幕坐标。点(0,0 )是客户区域的左上角。x 的值向右增加,y 的值向上增加。对于所有映射模式,Windows 都用下面两个公式将窗口坐标转换成视口坐标:xViewport=(xWindow-xWinOrg)*(xViewExt/
6、xWinExt)+xViewOrg3yViewport=(yWindow-yWinOrg)*(yViewExt/yWinExt)+yViewOrg其中,(xWindow,yWindows )是待转换的逻辑点,(xViewport,yViewport)是转换后的设备点。如果设备坐标是客户区域坐标或全窗口坐标,则 Windows 在画一个对象前,还必须将这些坐标转换成屏幕坐标。这两个公式使用了分别指定窗口和视口原点的点:(xWinOrg,yWinOrg)是逻辑坐标的窗口原点;(xViewOrg,yViewOrg)是设备坐标的视口原点。在缺省的设备环境中,这两个点均设置为(0,0),但它们可以改变。
7、此公式意味着,逻辑点(xWinOrg,yWinOrg)总被映射为设备点(xViewOrg,yViewOrg)。Windows 还能将视口(设备)坐标转换为窗口(逻辑)坐标:xWindow=(xViewport-xViewOrg)*(xWinExt/xViewExt)+xWinOrgyWindow=(yViewport-yViewOrg)*(yWinExt/yViewExt)+yWinOrg可以使用 Windows 提供的两个函数 DPtoLP 和 LPtoDP 在设备坐标及逻辑坐标之间互相转换。4.映射模式的种类Windows 定义了表 1 所列出的 8 种映射方式。上述映射模式中又可分成以下
8、 3 类:映 射 方 式 逻 辑 单 位 X 轴 增 加 Y 轴 增 加 毫 米MM_TEXT 像 素 点 右 下 与 设 备 有 关MM_LOMETRIC 0. 1mm 右 上 0.1MM_HIMETRIC 0. 01mm 右 上 0.01MM_LOENGLISH 0. 254mm 右 上 0.254MM_HIENGLISH 0. 0254mm 右 上 0.0254MM_TWIPS 0.0176mm 右 上 0.0176MM_ISOTROPIC 任 意(x=y) 可 选 可 选 可 设MM_ANISOTROPIC 任 意(x!=y) 可 选 可 选 可 设4MM_TEXT 映射模式这种映射模
9、式被称为“文本“映射方式,不是因为它对 于文本最合适,而是轴的方向与读文本的方向一致。Windows 提供了函数 SetViewportOrg 和SetWindowOrg 用来设置视口和窗口的原点。缺省的窗口原点和视口原点均为( 0,0),可以改变;缺省的窗 口范围和视口范围均为(1,1),不可改变。度量映射方式MM_LOMETRIC、MM_HIMETRIC、MM_LOENGLISH、MM_HIENGLISH 和MM_TWIPS 将 1 个逻辑单位映射为固定的实际单位,其中 1twip 等于0.0176mm(1/1440 英寸) 。其他映射模式对应的物理单位参见表 1。设置了映射模式以后,Wi
10、ndows 自动设置了窗口及视口的范围,范围本身的值并不重要,但范围比是一个固定的值,对于 MM_LOMETRIC, Windows 计算范围比 xViewExt/xWinExt=0.1mm 中水平像素的点数。自定义映射模式 MM_ISOTROPIC 和 MM_ANISOTROPIC 两种映射模式允许程序员设置自己的窗口和视口范围。MM_ISOTROPIC 和 MM_ANISOTROPIC 的区别是所设置的x 轴和 y 轴的的范围必须相同,而 MM_ANISOTROPIC 所设置的 x 轴和 y 轴的的范围可以不同。isotropi 的意思是“ 在所有方向相同“,anisotropic 的意思
11、正相反。自定义映射模式中窗口和视口的原点和范围都可以改变,程序员可以设置自己需要的映射模式。函数SetWindowExt 和 SetViewportExt 用于改变窗口和视口的范围。下面的代码将 1 个逻辑单位映射成 0.396mm(1/64 英寸)。SetMapMode(hDC,MM_ISOTROPIC);SetWindowExt(64,64);5SetViewportExt(hdc,GetDeviceCaps(hdc,LOGPIXELSX),GetDeviceCaps(hdc, LOGPIXELSY);二、与映射模式有关的问题的解决实际应用中,程序员会遇到一些与显示模式有关的问题。例如 O
12、LEServer 中映射模式 的设置、如何减少逻辑坐标与设备坐标间相互转换的误差等。下面,笔者就讨论一下这两个 问题的解决方法。1.OLEServer 中映射模式的设置方法开发 OLEServer 应用程序时,如果程序员直接调用 SetMapMode 函数将映射模式设置成度量映射方式中的一种后,在 Windows95/98 上程序会正常运行,但在 WindowsNT上对象显示的大小比边框小。经过笔者研究后,发现 WindowsNT 上 OLEServer 应使用基于逻辑英寸的映射方式。在讨论如何设置基于逻辑英寸的映射方式前,我们先介绍一下逻辑英寸的概念。Windows 在显示时以“ 逻辑英寸“
13、为单位,逻辑英寸比实际的英寸要大。如果 Windows程序使用实际英寸,则普通的 10 磅文本在显示器上就会小到几乎难以辨认,因此 Windows使用放大了的“逻辑英寸“ 来表示文本。逻辑英寸只影响显示,而不影响打印。使用 GetDeviceCaps 函数可得到当前设备的各种能力,其第一个参数 nIndex 指示要获取信息的类型。当 nIndex 为 HORZSIZE 和 VERTSIZE 时,可得到显示区域的宽度和高度;当 nIndex 为 HORZRES 和 VERTRES 时,可得到每个水平和垂直方向的像素数即分辨率;当 nIndex 的值为 LOGPIXELSX 和 LOGPIXELS
14、Y 时,可得到水平和垂直方向每逻辑英寸所含像素数。6在介绍了逻辑英寸的知识以后,很容易将 OLEServer 设置为基于逻辑英寸的映射模式。如果程序员仅仅调用 SetMapMode(hdc,MM_LOENGLISH)来设置映射模式,当前的映射模式为物理英寸,而不是逻辑英寸。设置逻辑英寸必须自定义窗口和视口的范围,使xViewExt/xWinExt =0.01 逻辑英寸中水平像素的点数,当xViewExt=LOGPIXELSX,xWinExt=100 时,其比值正好满足上述要求。以下是设置映射模式的代码。intxLogPixPerInch=GetDeviceCaps(hdc,LOGPIXELSX
15、);intyLogPixPerInch=GetDeviceCaps(hdc,LOGPIXELSY);SetMapMode(MM_ANISOTROPIC);SetWindowExt(100,100);SetViewportExt(xLogPixPerInch,yLogPixPerInch);上述代码中调用 SetMapMode 函数将映射模式设置为自定义的,该调用必须位于SetWindowExt 和 SetViewportExt 调用之前,否则设置将会无效。上述代码实际上将映射模式设置成逻辑 MM_LOENGLISH,若程序员需要设置逻辑MM_LOMETRIC、MM_HIMETRIC、MM_HI
16、ENGLISH 或 MM_TWIPS,只需修改上述代码中的 SetWindowExt 的参数,该参数实际上是每英寸所包含的各种映射模式下的单位数。根据表中各映射模式的参数,可得到表中每英寸所对应的各逻辑单位的个数。例如,要设置逻辑 MM_TWIPS,函数 SetWindowExt 中的参数为应 1440。2.逻辑坐标与设备坐标转换时误差的处理表 2映 射 模 式 每 英 寸 所 对 应 的 逻 辑 单 位 数MM_LOENGLISH 100MM_HIENGLISH 10007MM_LOMETRIC 254MM_HIMETRIC 2540MM_TWIPS 1440Windows 映射模式及相关问
17、题的解决当我们将映射模式设置成基于逻辑英寸的 MM_LOMETRIC 时,窗口的范围设为256,视口的范围设为 96(在 VGA 显示器下 LOGPIXELSX 的值),约 2.6 个逻辑单位对应 1 个像素,这显然会造成不小的误差,它会表现在应用程序的各个方面:客户区的一个部分没有被刷新;对象之间本来没有间距,却显示出有间距;对象在屏幕的不同位置上会缩小或增大一个像素等问题。可以采取以下两个步骤避免转换误差。(1)尽量选择窗口范围和视口范围比可以整除的映射方式,例如基于逻辑英寸的 MM_TWIPS 其窗口范围和视口范围比 1440/96,可简化为 15/1,从设备坐标转化为逻辑坐标时没有误差,从消除误差角度看,MM_TWIPS 比其他几个映射模式都要好。(2) 窗口范围和视口范围比不能整除时,也尽量将其简化,例如,当采用 0.3900mm 中的将 1 个逻辑单位映射成 1/64 英寸的映射方式时,其窗口范围和视口范围比值为 64/96,可简化为 2/3。如果我们将逻辑单位的值都取为 2 的倍数,设备单位的值都取为 3 的倍数,转换后就没有精度的丢失了。综上所述,如果我们能够根据映射模式值的特点,逻辑坐标和设备坐标都取经简化的窗口和视口范围值的倍数,则逻辑坐标和设备坐标间的转化将没有误差。