笔者采用Visual C++6.0开发图形数据支持系统,其中的难点正在于理解坐标系统的含义、各种映射模式的意义以及与逻辑坐标的转换。下面分几个部分逐一进行论述: |
一 坐标系统概述 众所周知,VC中的坐标系统分为逻辑坐标和设备坐标,逻辑坐标就是内存中虚拟的坐标,我们可以理解为看不见的,而设备坐标就是跟具体的设备相联系的坐标系统,如:显示器和打印机等。不同的映射模式决定了设备坐标和逻辑坐标之间的转换关系,也就是两种坐标系统在相互转换时,逻辑单位和设备单位之间的某种比例关系。这里需要说明的是:Windows中,只有对需要设备环境句柄作参数的GDI函数,映射方式才会起作用。 对于视口和窗口的概念比较的容易混淆,其实窗口和视口是与映射模式相关联的:映射方式就是用于从"窗口"(逻辑坐标)到"视口"(设备坐标)的映射。"视口"是基于设备坐标的,通常,视口与客户区相同;"窗口"是基于逻辑坐标的,逻辑坐标可以是像素、毫米、英寸等。 下面的公式是将窗口(逻辑)坐标转化为视口(设备)坐标: |
xViewport = (xWindow - xWinOrg)*xViewExt/xWinExt + xViewOrg yViewport = (yWindow - yWinOrg)*yViewExt/yWinExt + yViewOrg (xWindow,yWindow)是待转换的逻辑点,(xViewport,yViewport)是转换后的设备坐标。 |
设备坐标的视口原点(xViewOrg,yViewOrg)和逻辑坐标的窗口原点(xWinOrg,yWinOrg)默认情况下均被设置成(0,0),但具体情况下可以改变;(xWinExt,yWinExt)是逻辑坐标的窗口范围;(xViewExt,yViewExt)是设备坐标的窗口范围,在多数映射方式下,范围是映射方式所隐含的,不能改变。注意:每个范围自身没有什么意义,但是视口范围和窗口范围的比是逻辑单位转换为设备单位的换算因子。例如,对于MM_LOENGLISH模式,xViewExt/xWinExt 表示每0.01英寸(一个逻辑单位)中水平像素数。 |
二 映射模式及视图缩放 对于标准的映射模式,这里不作讨论,下面重点的说明MM_ISOTROPIC和MM_ANISOTROPIC两种映射模式,这两种模式用户可以控制逻辑单位和像素之间的比例。两种方式的概念上的差异在于各向同性和各向异性,也就是坐标的建立是否可以定义不同的方向和单位长度。下面举一个视图缩放的例子来具体说明。 |
Void CMyView::OnPrepareDC() { CView::OnPrepareDC(pDC,pInfo); CDC* pDC; pDC->SetMapMode(MM_ANISOTROPIC); int xLogPixPerInch = pDC->GetDeviceCaps(LOGPIXELSX); int yLogPixPerInch = pDC->GetDeviceCaps(LOGPIXELSY); pDC->SetWindowExt(100,100); pDC->SetViewportExt(xLogPixPerInch, yLogPixPerInch); } |
我们说过视口的幅度通常是屏幕客户区域的大小,而在上面的代码里我们并没有这样做。这样做可以吗?其实,在MM_TEXT的映射模式下,缺省的窗口范围和视口范围均为(1,1),从中可见,我们要设置的只是xViewExt/xWinExt的比值来决定显示比例的大小,而与xViewExt(视口范围)和xWinExt(窗口范围)的关系不大。GetDeviceCaps(LOGPIXELSX)和GetDeviceCaps(LOGPIXELSY)代表每逻辑英寸对应的水平像素点数;xLogPicPerInch/100代表每0.01逻辑单位对应的水平像素数目;那么100是什么意思呢?这就对应着每逻辑英寸对应的逻辑单位数目,也就是每逻辑单位对应于0.01逻辑英寸,有人会问,这不正是MM_LOENGLISH 方式吗?那么我们作做这样一个测试(见Demo),在OnDraw()中分别设置两种映射模式,看看到底有什么不同。 |
void CTestView::OnDraw(CDC* pDC) { CTestDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); pDC->SetMapMode(MM_ANISOTROPIC); int xLogPixPerInch = pDC->GetDeviceCaps(LOGPIXELSX); int yLogPixPerInch = pDC->GetDeviceCaps(LOGPIXELSY); pDC->SetWindowExt(100,100); pDC->SetViewportExt(xLogPixPerInch, yLogPixPerInch); pDC->Rectangle(100,100,200,200); //矩形的长度和宽度正好对应于一个逻辑英寸 pDC->SetMapMode(MM_LOENGLISH); pDC->Rectangle(100,-100,200,-200); //矩形的长度和宽度正好对应于一个实际英寸 } |
我们从结果中可以看出,显示在屏幕上时,两个正方形并不能重合,而在打印预览中可以重合。这是什么原因呢?前面我们说过,就是对应于MM_ANISOTROPIC映射方式下,100的含义是"每逻辑单位对应于0.01逻辑英寸",而在MM_LOENGLISH映射模式下,每逻辑单位对应的是0.01实际英寸,逻辑英寸要比实际英寸大一些,注意,这仅仅是对于显示器来说,有逻辑英寸和实际英寸的区别,在对于打印机而言,没有区别! 设置窗口不同的窗口幅度和视口幅度的比值就可以轻松实现缩放。 最后,用一个公式来说明以上论述的内容: |
图一 |
= 每逻辑单位对应的像素数目 = 每0.01逻辑英寸中水平像素的数目 各参数的意义: GetDeviceCaps(LOGPIXELSX): 每逻辑英寸对应的水平像素点数 100: 每逻辑英寸对应的逻辑单位数 xViewExt: 视口范围 xWinExt: 窗口范围 |
参考文献: 1 Windows 95程序设计 Charles Petzold[美] 郑全战 等译 2 Windows映射模式及相关问题的解决 孙刚 周振华 |
一、Windows中的映射模式
1、Windows定义映射模式的目的
经过我的综合,Windows定义映射模的目的又以下几个方 面:1、不同人的使用习惯。不同国家的,不同地区,以及不同的人因为习惯喜欢用不同的度量单位,有的人人喜欢用英寸,而有的人喜欢用公制中的厘米,毫米 等。其他的人又喜欢用另外一些单位。、2、使软件与硬件向分离开来。让开发的软件能够最大限度的与硬件无关。3、提供逻辑和物理的一种转换。就相当于银行 的利率。
2、默认的映射模式
默认的映射模式使MM_TEXT,它使以象素为单位的。X轴向左为正,Y轴向下为正。默认的坐标原点在左上角。
3、固定比例映射模式
固 定比例的映射模式有MM_LOMETRIC、MM_HIMETRIC、MM_LOENGLISH、MM_HIENGLISH、MM_TWIPS种。它们默 认的坐标原点都使在左上角。其区别在于每一个逻辑单位对应的物理大小不一样。所对用的逻辑单位分别为0.1毫米,0.01毫米,0.01英寸,0.001 英寸,1/1440英寸(0.0007英寸)。
4、可变比例映射模式
对于可变比例的映射模式用户可以自己定义一个逻辑单位代表的大小,其 大小可以任意。也可以让这个大小随环境改变而改变。有MM_ISOTROPIC,MM_ANISOTROPIC这两种映射模式。其逻辑单位的大小等于视口 范围和窗口范围的比值。两者的不同在于前者要求X轴和Y轴的度量单位必须相同,而后者没有这样的限制。
二、Windows中的几种坐标体系
1、屏幕坐标
屏幕坐标描述物理设备(显示器、打印机等)的一种坐标体系,坐标原点在屏幕的左上角,X轴向右为正,Y轴向下为正。度量单位是象素。原点、坐标轴方向、度量单位都是不能够改变的。
2、设备坐标(又称物理坐标)
设备坐标是描述在屏幕和打印机显示或打印的窗体的一种坐标体系。默认的坐标原点是在其客户区的左上角。X轴向右为正,Y轴向下为正。度量单位为象素。原点和坐标轴方向可以改变,但是度量单位不可以改变。
3、逻辑坐标
逻辑坐标是在程序中控制显示,打印使用的坐标体系。该坐标系与定义的映射模式密切相关。默认的映射模式是MM_TEXT。我们可以通过设置不同的映射模式来改变该坐标体系的默认行为。
三、逻辑坐标和设备坐标之间的转换
现有如下代码:
void CMapModeView::OnPaint()
{
CPaintDC dc(this);
//获取设备类的设置
CPoint ptOrgView,ptOrgWindow;
CSize sizeView,sizeWindow;
CString strMsg;
ptOrgView=dc.GetViewportOrg();//获取视口原点
ptOrgWindow=dc.GetWindowOrg();//获取窗口原点
sizeView=dc.GetViewportExt();//获取视口范围
sizeWindow=dc.GetWindowExt();//获取窗口范围
strMsg.Format(_T("Viewport Extent:(%d,%d),\tViewport Org:(%d,%d)\tWindow Extent:(%d,%d)\tWindow Org(%d,%d)"),
sizeView.cx,sizeView.cy,ptOrgView.x,ptOrgView.y,
sizeWindow.cx,sizeWindow.cy,ptOrgWindow.x,ptOrgWindow.y);
TRACE("%s\n",strMsg);
//设置映射模式以及原点
dc.SetMapMode(MM_TEXT);//设置映射模式
dc.SetWindowOrg(100,100);//设置窗口的坐标原点
dc.SetViewportOrg(200,200);//设置视口的坐标原点
dc.SetWindowExt(5,10);//改语句仅对可变比例映射模式有效
dc.SetViewportExt(1,1);//同上
ptOrgView=dc.GetViewportOrg();
ptOrgWindow=dc.GetWindowOrg();
sizeView=dc.GetViewportExt();
sizeWindow=dc.GetWindowExt();
strMsg.Format(_T("Viewport Extent:(%d,%d),\tViewport Org:(%d,%d)\tWindow Extent:(%d,%d)\tWindow Org(%d,%d)"),
sizeView.cx,sizeView.cy,ptOrgView.x,ptOrgView.y,
sizeWindow.cx,sizeWindow.cy,ptOrgWindow.x,ptOrgWindow.y);
TRACE("%s\n",strMsg);
//将点(300,400)从逻辑坐标体系映射到设备坐标体系。
CPoint ptMap;
ptMap=CPoint(300,400);
dc.LPtoDP(&ptMap);
strMsg.Format(_T("The Orginal Point(In LP):CPoint(300,400),Convert to DP is:CPoint(%d,%d)"),
ptMap.x,ptMap.y);
TRACE("%s\n",strMsg);
//将点(300,400)从设备坐标体系映射到逻辑坐标体系
ptMap=CPoint(300,400);
dc.DPtoLP(&ptMap);
strMsg.Format(_T("The Orginal Point(In DP):CPoint(300,400),Convert to LP is:CPoint(%d,%d)"),
ptMap.x,ptMap.y);
TRACE("%s\n",strMsg);
}
以上代码最后调试输出结果为:
Viewport Extent:(1,1), Viewport Org:(0,0) Window Extent:(1,1) Window Org(0,0)
Viewport Extent:(1,1), Viewport Org:(200,200) Window Extent:(1,1) Window Org(100,100)
The Orginal Point(In LP):CPoint(300,400),Convert to DP is:CPoint(400,500)
The Orginal Point(In DP):CPoint(300,400),Convert to LP is:CPoint(200,300)
按照MSDN上,函数SetWindowOrg(x,y)设定设备坐标下的点(x,y)对应于逻辑坐标的原点。SetVieportOrg(x,y)设定 逻辑坐标下点(x,y)对应逻辑坐标的原点。而实际上如果同时设置了逻辑坐标和设备坐标原点的话,那么以上的说法是错误的。
在默认映射模式MM_TEXT下,一个逻辑单位对应于设备坐标下的一个象素。改变默认原点以后的坐标体系如下图所示:
(0,0) Dx,Lx (0,0)
(100,100) Lx
(200,200) Dx
.(300,400)
Dy,Ly Ly Dy
在VC中坐标系的转换和数学中的数学转化是不一样的。在这里是以距离为标准。首先看一下如何把点(300,400)如何从设备坐标转换成逻辑坐标。
在设备坐标体系下,点(300,400)与Y轴的距离为100个逻辑单位。那么所对应的逻辑坐标也要满足与逻辑坐标Y轴的距离为100个单位。又1个逻辑单位对应1个象素。所以所对应的设备坐标的X值为100+100=200。同样可以出对应的逻辑坐标的Y值为300。
按 照同样的方法,我们也可以把逻辑坐标下的点(300,400)转换成设备坐标。在逻辑坐标下,点(300,400)与逻辑坐标Y轴的距离为200。那么在 设备坐标体系,相应的设备坐标与设备坐标Y轴的距离也要为200。又1个逻辑单位对应1个象素,所以对应的设备坐标X值为200+200=400。同样的 道理,可以求出对应的设备坐标Y值为500。
在这里,因为逻辑单位和设备单位一一对应,也可以把这个问题看作一个很简单的坐标平移问题来看。其结果是很显然的。
首先感谢你的劳动。不过你对SetViewportOrg和SetWindowOrg的描述我并不赞同。
API函数SetWindowOrgEx和SetViewportOrgEx在MSDN中的解释为:
The SetViewportOrgEx function sets the viewport origin of a device context by using the specified coordinates.
The SetWindowOrgEx function sets the window origin of the device context by using the specified coordinates
设定viewport origin或window origin为指定的坐标。
什么是origin呢?我找到的汉语资料上都说origin是坐标原点。但我对这种译法表示怀疑。MSDN上有一句话:
The effect of a transformation calculated in this way is that The system maps the window origin to the viewport origin and the window extents to the viewport extents,
转换的结果这样来计算:系统将window origin映射到viewport origin,将window extents映射到viewport extents.
这就是说转换后window origin和viewport origin在同一个点上。如我们把origin认为是原点的话,也就是说逻辑坐标的原点与设备坐标的原点重合。这显然是不对的。
因此,我认为这里所说的origin并不是原点。而是一个独立点,它与坐标系没有任何关系(MSDN上也有这么说过)。
那么到底是如何从逻辑坐标转换成设备坐标呢?
首先,要记住两点:1。设备坐标的(0, 0)点始终是客户区的左上角。
2。我们在绘图时指定的点全部都是逻辑坐标点。
MSDN上有公式:
Dx = ((Lx - WOx) * VEx / WEx) + VOx
The following variables are involved.
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
这个公式不是很好理解,可以这样来考虑。
设 有一个设备坐标点(x,y),设想一个有大小的坐标系,origin在(ox,oy),将各个坐标值乘以一个系数:VEx/WEx,这样坐标系就被缩放 了,并且缩放到跟逻辑坐标系一样大。然后将这个坐标系的origin和逻辑坐标系的origin重合。这样我们就可计算出逻辑坐标系的原点((0,0) 点,而不是origin)到乘以系数以后的(x,y)点的距离,这个距离就是逻辑坐标的值。如果要用一个公式来表示,就是上面的公式。MSDN上也是这样 做的。
首先非常感谢seaymx (笨小猪)将这个问题总结出来了,我记得我刚开始的时候也是非常头疼这个问题,现在后来者看到这个总结应该没任何麻烦了。
我们也都感谢yndfcd(YNDFCD),他将我们的疑虑打消了,确实是如他所说那些,任何时刻设备坐标 的(0,0)在客户区的左上角,这也是我以前琢磨出来的。
对于SetWindowOrgEx以及SetViewPortOrgEx中的ORIGIN解释我非常赞同,这个以前我也和seaymx (笨小猪)一样将它跟(0,0)混淆起来了,所以当这两个函数都使用的时候会得出矛盾。而我们将origin只是理解成一个参照点,那我们的疑虑就打消 了。同时使用这两个函数也不会有矛盾,用一个也一样讲的通。
好像yndfcd(YNDFCD)写的推算公司是不是写错了:
Dx = ((Lx - WOx) * VEx / WEx) + VOx
应该是:
Dx = ((Lx - VOx) * WEx / VEx) + WOx
我们来分析下面的实例:
比如:假设:
dc.SetMapMode(MM_TEXT);//设置映射模式
dc.SetWindowOrg(100,100);//设置窗口的坐标原点
dc.SetViewportOrg(200,200);//设置视口的坐标原点
逻辑坐标 点pA(100, 100) 转换为
设备坐标 后pB,那pB为(?,?)
我们怎样得出pB来呢?
从映射模式 MM_TEXT,看出来逻辑坐标X轴 向右为正, Y轴向下为正,单位为像素此时缩放比例始终为1。
而上面seaymx (笨小猪)也提到:设备坐标 X轴 向右为正, Y轴向下为正,单位为像
素,所以如果没有其它的设置的话,我们可以认为此时 逻辑坐标和设备坐标是一样的。
而dc.SetWindowOrg(100,100);//设置窗口的坐标原点
dc.SetViewportOrg(200,200);//设置视口的坐标原点
改变这两个坐标系统的参照点的对应关系,即: 设备坐标点(100, 100)(单位是设备坐标的单位)对应于 逻辑坐标点(200,200)(逻辑坐标的单位)。即我们把它们看成是一个重合点。
然后我们回到我们的问题上来,逻辑坐标(100,100)对于于它的参照点(200,200),我们发现距离是(100 -200, 100 -200),即(-100, -100),我们可以看到现在设备坐标的参照点(100,100),我们将距离(-100, -100)(注意:这里没有缩放关系,否则乘上一个缩放比例系数)加上参照点就是我们要的设备坐标点pB,
即 pB(0,0),也就是我们的设备原点(0,0)。
最后提醒一句,如yndfcd(YNDFCD)所说的设备坐标原点(0,0)始终在客户区的左上角,所以我们可以推出pB的显示位置。否则你怎样能知道它位于什么地方呢?
说白了,就是相对论的一个应用。一个坐标系统(A)上的坐标点相对于该坐标系统中参考点(ORIGIN)的距离,然后将此距离根据缩放比例转换成另一个坐标系统(B)的距离,然后根据B坐标系统的参考点(ORIGIN)以此距离来计算出此点的坐标值。