mfc 绘图中的坐标空间转换问题

 Win32应用程序编程接口(API)使用四种坐标空间: 世界坐标系空间页面空间设备空间物理设备空间。世界坐标系空间和页面空间又称为 逻辑空间,物理设备空间通常指程序窗口的客户区,但是它也包含整个桌面、完整的窗口(包括框架、标题栏和菜单栏)或打印机的一页或绘图仪的一页纸。物理设备的尺寸根据显示器打印机或者绘图仪的不同而不同。

一、映射模式基本知识
   
当Windows应用程序在其客户区绘制图形时,必须给出在客户区的位置,其位置用x和y 两个坐标表示,x表示横坐标,y表示纵坐标。在所有的GDI绘制函数中,这些坐标使用的是一种"逻辑单位"。当GDI函数将输出送到某个物理设备上时,Windows将逻辑坐标转换成设备坐标(如屏幕或打印机的像素点)。逻辑坐标和设备坐标的转换是由映射模式决定的。映射模式被储存在设备环境中。GetMapMode函数用于从设备环境得到当前的映射模式,SetMapMode函数用于设置设备环境的映射模式。

   1.逻辑坐标

   逻辑坐标是独立于设备的,它与设备点的大小无关。使用逻辑单位,是实现"所见即所得"的基础。当程序员在调用一个画线的GDI函数LineTo,画出25.4mm(1英寸) 长的线时,他并不需要考虑输出的是何种设备。若设备是VGA显示器,Windows自动将其转化为96个像素点;若设备是一个300dpi的激光打印机,Windows自动将其转化为300个像素点。(各个书上都说世界坐标系空间和页面空间又称为逻辑空间,实际上逻辑坐标是系统采用的页面空间的坐标系,缺省情况下MM_TEXT规定逻辑坐标与设备坐标相同,如果程序员使用SetWorldTransform函数明确定义了world空间向page空间映射的公式,那么windows将进行这种映射,具体规则由SetWorldTransform函数定义,此时的逻辑空间是world空间如果没有出现SetWorldTransform函数,Windows将不进行world空间到page空间的映射,而直接进行page空间到device空间的映射,此时的逻辑空间是page空间

 )

   2.设备坐标

   Windows将GDI函数中指定的逻辑坐标映射为设备坐标,在所有的设备坐标系统中,单位以像素点为准,水平值从左到右增大,垂直值从上到下增大。

   Windows中包括以下3种设备坐标,以满足各种不同需要:

   (1)客户区域坐标,包括应用程序的客户区域,客户区域的左上角为(0,0)。

   (2)屏幕坐标,包括整个屏幕,屏幕的左上角为(0,0)。屏幕坐标用在WM_MOVE消息中(对于非子窗口)以及下面的Windows函数中:CreateWindowMoveWindow(都对于非子窗口)、GetMessageGetCursorPosGetWindowRectWindowFromPointSetBrushOrg中。用函数ClientToScreenScreenToClient可以将客户区域坐标转换成屏幕区域坐标,或反之。

   (3)全窗口坐标,包括一个程序的整个窗口,包括标题条、菜单、滚动条和窗口框,窗口的左上角为(0,0)。使用GetWindowDC得到的窗口设备环境,可以将逻辑单位转换成窗口坐标

   3.逻辑坐标与设备坐标的转换方式

      逻辑坐标和设备坐标即使在缺省模式(MM_TEXT)下其数值也未必一致,除了在以下两种情况下:一、窗口为非滚动窗口二、窗口为滚动窗口,但垂直滚动条位于滚动边框的最上端,水平滚动条位于最左端,但如果移动了滚动条这两种坐标就不一致了。

   映射方式定义了Windows如何将GDI函数中指定的逻辑坐标映射为设备坐标。要继续讨论映射方式我们要介绍Windows有关映射模式的一些术语:我们将逻辑坐标所在的坐标系称为"窗口",将设备坐标所在的坐标系称为"视口"

   "窗口"依赖于逻辑坐标,可以是像素点、毫米或程序员想要的其他尺度。

   "视口"依赖于设备坐标(像素点)。通常,视口和客户区域等同。但是,如果程序员用GetWindowDCCreateDC获取了一个设备环境,则视口也可以指全窗口坐标屏幕坐标。点(0,0)是客户区域的左上角。x的值向右增加,y的值向上增加。

   对于所有映射模式,Windows都用下面两个公式将窗口坐标转换成视口坐标

   xViewport=(xWindow-xWinOrg)*(xViewExt/xWinExt)+xViewOrg;
   yViewport=(yWindow-yWinOrg)*(yViewExt/yWinExt)+yViewOrg;

   其中,(xWindow,yWindows)是待转换的逻辑点,(xViewport,yViewport)是转换后的设备点。如果设备坐标是客户区域坐标或全窗口坐标,则Windows在画一个对象前,还必须将这些坐标转换成屏幕坐标

   这两个公式使用了分别指定窗口和视口原点的点:(xWinOrg,yWinOrg)是逻辑坐标的窗口原点;(xViewOrg,yViewOrg)是设备坐标的视口原点。在缺省的设备环境中,这两个点均设置为(0,0),但它们可以改变。此公式意味着,逻辑点(xWinOrg,yWinOrg)总被映射为设备点(xViewOrg,yViewOrg)

   Windows还能将视口(设备)坐标转换为窗口(逻辑)坐标

   xWindow=(xViewport-xViewOrg)*(xWinExt/xViewExt)+xWinOrg
   yWindow=(yViewport-yViewOrg)*(yWinExt/yViewExt)+yWinOrg

   可以使用Windows提供的两个函数DPtoLPLPtoDP在设备坐标及逻辑坐标之间互相转换。

   4.映射模式的种类

   Windows定义了表1所列出的8种映射方式。

   上述映射模式中又可分成以下3类:

映 射 方 式

逻 辑 单 位

轴 增 加

轴 增 加

毫 米

MM_TEXT

像 素 点

与 设 备 有 关

MM_LOMETRIC

0. 1mm

0.1

MM_HIMETRIC

0. 01mm

0.01

MM_LOENGLISH

0. 254mm

0.254

MM_HIENGLISH

0. 0254mm

0.0254

MM_TWIPS

0.0176mm

0.0176

MM_ISOTROPIC

任 意(x=y)

可 选

可 选

可 设

MM_ANISOTROPIC

任 意(x!=y)

可 选

可 选

可 设


   MM_TEXT
映射模式这种映射模式被称为"文本"映射方式,不是因为它对于文本最合适,而是轴的方向与读文本的方向一致。Windows提供了函数SetViewportOrg和SetWindowOrg 用来设置视口和窗口的原点。缺省的窗口原点和视口原点均为(0,0),可以改变;缺省的窗口范围和视口范围均为(1,1),不可改变。

   度量映射方式MM_LOMETRIC、MM_HIMETRIC、MM_LOENGLISH、MM_HIENGLISH和MM_TWIPS 将1个逻辑单位映射为固定的实际单位,其中1twip等于0.0176mm(1/1440英寸)。其他映射模式对应的物理单位参见表1。设置了映射模式以后,Windows自动设置了窗口及视口的范围,范围本身的值并不重要,但范围比是一个固定的值,对于MM_LOMETRIC,Windows计算范围比xViewExt/xWinExt=0.1mm中水平像素的点数。

   自定义映射模式MM_ISOTROPIC和MM_ANISOTROPIC两种映射模式允许程序员设置自己的窗口和视口范围。MM_ISOTROPIC和MM_ANISOTROPIC的区别是MM_ISOTROPIC所设置的x轴和y轴的的范围必须相同,而MM_ANISOTROPIC所设置的x轴和y轴的的范围可以不同。isotropi的意思是" 在所有方向相同",anisotropic的意思正相反。自定义映射模式中窗口和视口的原点和范围都可以改变,程序员可以设置自己需要的映射模式。函数SetWindowExt和SetViewportExt 用于改变窗口和视口的范围。下面的代码将1个逻辑单位映射成0.396mm(1/64英寸)。

   SetMapMode(hDC,MM_ISOTROPIC);
   SetWindowExt(64,64);
   SetViewportExt(hdc,GetDeviceCaps(hdc,LOGPIXELSX),GetDeviceCaps(hdc, LOGPIXELSY));

二、与映射模式有关的问题的解决

   
实际应用中,程序员会遇到一些与显示模式有关的问题。例如OLEServer中映射模式的设置、如何减少逻辑坐标与设备坐标间相互转换的误差等。下面,笔者就讨论一下这两个 问题的解决方法。

   1.OLEServer中映射模式的设置方法

   开发OLEServer应用程序时,如果程序员直接调用SetMapMode函数将映射模式设置成度量映射方式中的一种后,在Windows95/98上程序会正常运行,但在WindowsNT上对象显示的大小比边框小。经过笔者研究后,发现WindowsNT上OLEServer应使用基于逻辑英寸的映射方式。在讨论如何设置基于逻辑英寸的映射方式前,我们先介绍一下逻辑英寸的概念。

   Windows在显示时以"逻辑英寸"为单位,逻辑英寸比实际的英寸要大。如果Windows程序使用实际英寸,则普通的10磅文本在显示器上就会小到几乎难以辨认,因此Windows使用放大了的"逻辑英寸"来表示文本。逻辑英寸只影响显示,而不影响打印。

   使用GetDeviceCaps函数可得到当前设备的各种能力,其第一个参数nIndex指示要获取信息的类型。当nIndex为HORZSIZE和VERTSIZE时,可得到显示区域的宽度和高度;当nIndex 为HORZRES和VERTRES时,可得到每个水平和垂直方向的像素数即分辨率;当nIndex的值为LOGPIXELSX 和LOGPIXELSY时,可得到水平和垂直方向每逻辑英寸所含像素数。

   在介绍了逻辑英寸的知识以后,很容易将OLEServer设置为基于逻辑英寸的映射模式。如果程序员仅仅调用SetMapMode(hdc,MM_LOENGLISH)来设置映射模式,当前的映射模式为物理英寸,而不是逻辑英寸。设置逻辑英寸必须自定义窗口和视口的范围,使xViewExt/xWinExt =0.01逻辑英寸中水平像素的点数,当xViewExt=LOGPIXELSX,xWinExt=100时,其比值正好满足上述要求。

   以下是设置映射模式的代码。

intxLogPixPerInch=GetDeviceCaps(hdc,LOGPIXELSX);
intyLogPixPerInch=GetDeviceCaps(hdc,LOGPIXELSY);
SetMapMode(MM_ANISOTROPIC);
SetWindowExt(100,100);
SetViewportExt(xLogPixPerInch,yLogPixPerInch);

intxLogPixPerInch=GetDeviceCaps(hdc,LOGPIXELSX);
intyLogPixPerInch=GetDeviceCaps(hdc,LOGPIXELSY);
SetMapMode(MM_ANISOTROPIC);
SetWindowExt(100,100);
SetViewportExt(xLogPixPerInch,yLogPixPerInch);

   上述代码中调用SetMapMode函数将映射模式设置为自定义的,该调用必须位于SetWindowExt 和SetViewportExt调用之前,否则设置将会无效。
   上述代码实际上将映射模式设置成逻辑
MM_LOENGLISH,若程序员需要设置逻辑MM_LOMETRIC、MM_HIMETRIC、MM_HIENGLISH 或MM_TWIPS,只需修改上述代码中的SetWindowExt的参数,该参数实际上是每英寸所包含的各种映射模式下的单位数。根据表1中各映射模式的参数,可得到表2中每英寸所对应的各逻辑单位的个数。

   例如,要设置逻辑MM_TWIPS,函数SetWindowExt中的参数为应1440。

   2.逻辑坐标与设备坐标转换时误差的处理

表2

映 射 模 式

每 英 寸 所 对 应 的 逻 辑 单 位 数

MM_LOENGLISH

100

MM_HIENGLISH

1000

MM_LOMETRIC

254

MM_HIMETRIC

2540

MM_TWIPS

1440


   
当我们将映射模式设置成基于逻辑英寸的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的倍数,转换后就没有精度的丢失了。

   综上所述,如果我们能够根据映射模式值的特点,逻辑坐标和设备坐标都取经简化的窗口和视口范围值的倍数,则逻辑坐标和设备坐标间的转化将没有误差。


 
windows对 所有的消息 (如WM_SIZE,WM_MOUSEMOVE,WM_LBUTTONDOWN, WM_LBUTTONUP,所有的非GDI函数和少数GDI函数(如GetDeviceCaps函数)永远使用 设备坐标 在VC中 鼠标坐标的坐标位置设备坐标表示,但所有 GDI绘图都用 逻辑坐标表示,所以用鼠标绘图时,那么 必须将设备坐标转换为逻辑坐标,可以使用CDC函数 DPtoLP()将设备坐标转化为逻辑坐标,同样可以用LPtoDP()将逻辑坐标转化为设备坐标。
       CDC的LPtoDP和DPtoLP被用来作 逻辑坐标系和设备坐标系之间的相互转换 可以认为 CDC的所有成员函数都以逻辑坐标作为其参数;可以认为 CWnd的成员函数都以设备坐标 作为其参数;所有选中测试操作(如CRect::PtInRect)都应考虑设备坐标;注意如果用设备坐标来保存某点的坐 标时,如果用户对窗口进行一下滚动,则该点的坐标就不再有效了(因为设备坐标的(0,0)点发生了变化)。
 


视图中利用的是影射方式 MM_ANISOTROPIC,现在想把鼠标所在的点的坐标利用逻辑坐标给标出来,利用ScreenToClient()

setwindowExt() 、setviewportExt() 、setwindowOrg() 、setviewportorg() 的区别:

SetViewportExtSetWindowExt用来确定逻辑坐标下设备坐标下的尺寸对应关系 

SetWindowExt(int Lwidth, int Lheight) 
//window的宽(高)度,参数的单位为逻辑单位(Logical),如果参数为负值表示window相应的坐标轴与page空间相反。 

SetViewportExt(int Pwidth, int Pheight) 
//viewport的宽(高)度,参数的单位为像素(Pixel),如果参数为负值表示viewport相应的坐标轴与device空间相反。 

SetWindowOrg(int Lx, int Ly) 
//设置窗口的坐标原点 
SetViewportOrg(int Px, int Py) 
//设置逻辑坐标的原点   

所谓设备坐标与逻辑坐标只是相对的概念。 比如点pt(1000,1000),如果你认为他是 设备坐标,那么经过下变换,他就变成逻辑 坐标: 
dc.DPtoLP(&pt); 
如果你认为他是 逻辑坐标,那么经过下变换,他就变成设备坐标: 
dc.DPtoLP(&pt); 

是设备坐标还是逻辑坐标,与DC的影射模式(MapMode) 无关,任何影射模式都有设备坐标与逻辑坐标。其中,设备坐标 是统一的的,即都是指设备象素坐标。

CRect并无设备坐标或逻辑坐标之分,关键在于你怎么认为它和怎么用它。 一般dc所接受的参数是逻辑坐标窗口函数都是接受设备坐标。 所以不管是否用了DPtoLP或LPtoDP,Rectangle()所用参数它都认为是逻辑坐标


 


    
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值