坐标系统与坐标变换

坐标系统与坐标变换

win32程序使用坐标系统之间的变换完成图形的缩放、旋转、平移等输出操作。win32下面总共使用四个坐标空间:世界坐标系、页面坐标系、设备坐标系和物理坐标系(包括客户区、桌面或打印纸等)。每个坐标空间都是一个线性空间,用两个相互垂直的坐标轴定位两维的物体。

我们把改变一个物体的大小、方向和形状的算法称作“变换”。一个图形物体从一个坐标空间映射到另一个坐标空间的过程就是一个变换。最终,物体显示在一个物理设备上,通常是屏幕或者打印机。

1、四个坐标系的定义

页面空间和设备空间一起工作,在这两个空间下,程序可以使用设备相关的单位,例如毫米和英寸。但是在世界坐标系和页面坐标系下,都认为是逻辑坐标系,单位是逻辑单位1。

任何程序的绘图代码中使用的坐标都是从世界坐标系开始,直到物理坐标

系,最后得到(看到)输出结果。每两个坐标系之间,系统都采用一种变换方法,从前面的坐标空间复制(或者映射)一个矩形区域到下一个坐标空间。为了便于处理,如果程序调用了SetWorldTransform 函数,则这个映射过程从世界坐标系开始,否则从页面坐标系开始。用图形表示如下:

举个例子说,如果我们在DC 上面有个画线函数:

MoveTo(hDC, 0, 0);

LineTo(hDC, 10,10);

则如果我们没有使用SetWordTransform 函数,可以认为我们的画线操作就是在页面坐标系下面,0,10都是页面坐标系下的坐标。如果我们使用了SetWorldTransform 函数,则画线操作是在世界坐标系下面。页面坐标系中对应的这个线段是世界坐标系下面的线段(0,0) -> (10,10) 通过两个坐标空间之间的变换矩阵变换得到,结果可能还是(0,0)点到(10,10)点,也可能是(5,9)点到(-20,14)点,这取决于两个坐标系之间的变换矩阵。

同样道理,对于上述四个坐标系,当系统从一个坐标系中复制指定矩形区域内的某个点到下一个坐标系时,它使用这两个坐标系之间的变换算法,根据点的原坐标计算得到点的像坐标。因此,一个图形在不同坐标系下,其尺寸、方向和形状都可能不同。注意一点,虽然这个变换是两个坐标系之间的,是针对物体整体而言的变换,但在系统在操作的时候,是逐点、逐行操作的。

虽然很少用到SetWorldTransform 函数,但是应该掌握最基本的坐标系之间的线性变换矩阵,形式如下:

函数采用逻辑单位,缺省的变换矩阵是单位阵:

四个eM 参数给出旋转和缩放变换系数,eDx/eDy给出平移变换系数。 注意SetWorldTransform 函数要求DC 的图形模式是GM_ADVANCED,

可以用

SetGraphicsMode 设置,仅Windows NT/ 2000下面支持。缺省图形模式是GM_COMPATIBLE,兼容16位windows ,这个模式下不能使用该函数。下面简单介绍这两个坐标系之间的变换矩阵。

2、世界坐标系到页面坐标系的变换

(1)平移

物体上每个点进行水平和垂直的移动,eDx 和eDy 参数分别给出移动的尺寸。具体算法是:

x' = x + Dx

y' = y + Dy

其中 x' ,y' 是新的坐标, x ,y 是源坐标。Dx 是水平移动距离,Dy 是垂直移动距离。平移矩阵是:

(2)缩放

组成物体的每个水平行或者垂直行进行拉伸或者压缩。算法公式是:

y' = y * Dy

x' = x * Dx

其中Dy ,Dx 是缩放系数。用矩阵表示为:

(3)旋转

组成物体的每个点都相对于坐标原点旋转一个角度。算法公式是:

x' = (x * cos A) - (y * sin A)

y' = (x * sin A) + (y * cos A)

A 表示绕原点逆时针旋转的角度,用矩阵表示如下:

(4)变形

分为水平变形和垂直变形两种,举个例子说,一个矩形通过变形成为一个平行四边形。算法公式分别是:

x' = x + (Sx * y)

y' = y + (Sy * x)

其中Sx, Sy分别是变形系数。用矩阵表示为:

(5)镜像映射

例如水平翻转的公式是:

x' = –x

用矩阵表示是:

线性变换可以是上面几种变换中任意若干种的组合。最终的变换矩阵是一个3x3的矩阵。可以调用CombineTransform 进行两种变换的组合,也可以自己按照公式计算出变换矩阵。

强调一点,虽然可以通过SetWorldTransform 函数调用设置世界坐标系到页面坐标系的变换矩阵,但通常情况下,在我们的程序中,图形图像的变换并不是通过系统来完成的。而是程序自己完成的,因为自己做,可以更加灵活,容易控制,效率更高。因此不推荐使用SetWorldTransfor 函数,如果我们要做图形变换的显示,建议自己先用变换算法计算好,然后直接在页面坐标系下面作图。

3、页面坐标系到设备坐标系之间的变换

这个变换决定了与特定DC 相联系的映射模式,影响该DC 上的所有图形输出。映射模式本身就是一个缩放变换,决定了画图操作中一个单位的尺寸,

映射

模式也可以用于平移变换,某些情形下,映射模式会改变x,y 轴的坐标原点。首先来了解几个映射模式:

(1)映射模式说明

要设置映射模式,调用SetMapMode ,要获得当前的映射模式,调用GetMapMode 。

页面空间到设备空间的变换涉及到窗口或者视口中的点,从这个意义上讲,

窗口反映了页面空间中的逻辑坐标系统,而视口代表设备空间的设备坐标系统。窗口和视口都包含一个坐标原点和x/y轴。窗口中使用的参数是逻辑坐标,视口中使用的参数是设备坐标(像素)。系统根据坐标原点生成变换矩阵。这就意味着,窗口和视口分别负责给出从页面坐标空间到设备坐标空间映射变换矩阵的一半参数。

根据窗口和视口的坐标轴尺寸(最大坐标值),可以建立一个比例或者缩放系数,用于页面空间到设备空间的变换。对于前六种预定义映射模式,当调用SetMapMode 函数的时候,坐标轴的最大尺寸是由系统设置的,无法更改。后两种映射模式MM_ISOTROPIC和 MM_ANISOTROPIC下,需要定义坐标轴的最大尺寸,因此调用SetMapMode 之后,必须调用SetWindowExt 和SetViewportExt 进行设置。特别是在MM_ISOTROPIC映射模式下。必须注意先调用SetWindowExtEx 然后调用SetViewportExtEx 。

根据窗口和视口的坐标原点,可以建立页面空间到设备空间的线性变换的平移关系。通过函数SetWindowOrg 和SetViewportOrg 来设置原点。坐标原点和坐标轴的尺寸没有关系,因此任何映射模式下都可以设置坐标原点,改变映射模式也不会影响当前的坐标原点。由于这两个函数是相关的,所以通常使用一个即可,不必两个都调用。记住一点,无论是否调用这两个函数,设备坐标(0,0)永远是左上角。也可以用函数OffsetWindowOrg 或者OffsetViewportOrg 改变坐标原点。下面的公式给出了页面坐标空间到设备坐标空间之间的点的映射关系:

Dx = ((Lx - WOx) * VEx / WEx) + VOx

说明:

Dx 设备空间中的点(或者说单位)

Lx 逻辑单位 x (或者说页面空间中的单位)

WOx 窗口的 x 原点

VOx 视口中 x 原点

WEx 窗口的 x 轴尺寸

VEx 视口的 x 轴尺寸

对于y 方向的公式也是一样的。

函数LPtoDP 和DPtoLP 可以用来完成两个坐标空间点的变换的计算。

(2)关于前6种预定义映射模式

6种预定义映射模式中,只有MM_TEXT是设备相关的,其余都是设备无关的。缺省的映射模式是MM_TEXT,即一个逻辑单位等于一个像素。逻辑到设备的映射仅仅是一个平移关系,和程序设置的窗口和视口的坐标原点有关。视口和窗口的坐标轴尺寸都设置成1,从而形成一一映射。如果程序要显示准确的几何图形,可以使用MM_LOENGLIST模式,保证图形的形状在任何显示器和打印机下面都是一样的。如果仍然使用MM_TEXT,则可能在VGA 显示器上面显示的一个圆,到了EGA 显示器下就变成椭圆了,如果用300dpi 的激光打印机输出,则结果是很小的一个圆。

(3)关于后两种自定义映射模式

MM_ISOTROPIC 和 MM_ANISOTROPIC两个映射模式用于自定义。MM_ISOTROPIC可以保证逻辑单位在x 和y 方向是一致的。MM_ANISOTROPIC下允许设置成不同。例如一个 CAD 或者绘图程序可以用MM_ISOTROPIC模式,将逻辑单位设置成工程常用的1/64英寸,代码如下:

SetMapMode(hDC, MM_ISOTROPIC);

SetWindowExtEx(hDC, 64, 64, NULL);

SetViewportExtEx(hDC,GetDeviceCaps(hDC,LOGPIXELSX),GetDeviceCaps(hDC,

LOGPIXELSY), NULL);

4、设备坐标空间到物理设备的变换

设备空间到物理设备的变换在很多情况下是唯一的,是由系统控制的,主要的目的是保证设备坐标系的原点准确映射到物理设备的合适位置。比如屏幕上面显示的某个程序,窗口显示的位置要和窗口矩形在显存中的位置对应起来,移动窗口就是改变这个控制窗口输出的矩形在显存中的起点,反映在显示器屏幕上面就是程序窗口的移动。如果是在打印机dc 上面画图,则物理设备就是纸张了。这个变换通常是由系统负责控制的。因此没有函数用于设置这个变换,也没有函数获取相关的变换数据。

5、缺省的变换

如果程序创建了一个DC ,并立刻开始调用GDI 绘图函数,使用的变换过程就是:

缺省的页面空间→设备空间→客户区(物理设备空间) 的变换

除非程序调用SetGraphicsMode 和SetWorldTransform 函数,否则世界坐标空间→页面坐标空间是单位变换,可以认为没有变换。

页面空间→设备空间在MM_TEXT模式下是一一映射,即给定页面空间中的一点,对应设备空间中的相同点。前面已经指出,这个变换不是通过一个矩阵,而是通过用视口的宽度/窗口的宽度,视口的高度/窗口的高度两个公式来计算的。缺省情况下,视口的尺寸是1X1像素。窗口的尺寸是1X1页。

设备空间→物理设备(客户区,桌面或者打印机)的变换通常也是一一映射,即设备空间中的一点对应客户区或者打印机输出中的一个单位,目的是保证无论程序窗口如何在屏幕上面移动,最终的输出始终准确地反映设备空间中的图形。

注意MM_TEXT模式比较特殊,它的Y 坐标轴是向下的,其它映射模式的Y 坐标轴都是向上的。

6、下面用一个例子考察每个坐标变换函数的意义。

用classwizard 生成一个sdi 工程,视类选择从CScrollView 派生,然后添加如下代码:

voidCSdiscroView::OnDraw(CDC* pDC)

{

//用绿色填充一个圆形区域(中心[200,200],半径150)

CRectrect;

rect.SetRect(50,50,350,350);

CBrushbru;

bru.CreateSolidBrush(RGB(0,127,0));

CBrush *pBrushOld = pDC->SelectObject(&bru);

pDC->Ellipse(&rect);

pDC->SelectObject(pBrushOld);

bru.DeleteObject();

//输出坐标原点

pDC->TextOut(0,0,"(0,0)");

//画出坐标轴

pDC->MoveTo(0,0);

pDC->LineTo(500,0); //x轴

pDC->LineTo(490,5); //箭头

pDC->MoveTo(500,0);

pDC->LineTo(490,-5);

pDC->MoveTo(0,0);

pDC->LineTo(0,500); //y轴

pDC->LineTo(5,490);

pDC->MoveTo(0,500);

pDC->LineTo(-5,490);

}

voidCSdiscroView::OnInitialUpdate()

{

CScrollView::OnInitialUpdate();

CSizesizeTotal;

//设置整个窗口尺寸为1000x1000

sizeTotal.cx = sizeTotal.cy = 1000;

SetScrollSizes(MM_TEXT, sizeTotal);

}

实际上我们所有的画图操作都是在世界坐标系下面,由于没有使用SetWorldTransform ,所以世界坐标系和页面坐标系等价,可以认为我们的画图

操作就是在页面坐标系中。下面分别添加相应的函数调用,考察每个函数对输出的影响。

1) SetWindowOrg / SetViewportOrg (CDC类成员函数,对应api 函

数SetWindowExt / SetViewportExt)

voidCSdiscroView::OnDraw(CDC* pDC)

{

pDC->SetWindowOrg(-100,-50);//或者pDC->SetViewportOrg(100,50);

//

... //原来的代码

}

窗口没有滚动之前,视口坐标系的原点和窗口坐标系的原点重合,如果滚动窗口,相当于改变视口原点的位置,因此显示在屏幕上面的部分(视口里面的东西)就发生变化。现在我们不滚动窗口,而是调用SetWindowOrg 改变窗口原点的坐标,看看发生的变化。

SetWindowOrg(-100,-50)函数调用的意思是把窗口(页面坐标系)中的(-100,-50)点设置成窗口的原点,由于窗口和视口的原点永远是重合的,所以视口的(0,0)点现在就和窗口的(-100,-50)重合,而视口的(0,0)点就是程序客户区的左上角,因此设置的后果就是:绘图的输出向x/y轴的正方向移动了。编译运行以后可以看到,字符串"(0,0)"向右下平移了,好像向上、向左滚动了窗口一样。同样道理,如果SetWindowOry 里面使用的是(100,50),则效果等同于向下、向右滚动了窗口。

对应图形如下:

从效果上SetWindowOrg(-100,-50)和SetViewportOrg(100,50)是等价的。但是使用一下就会发现SetViewportOrg(100,50)以后如果滚动窗口的话,窗口的刷新有些问题,所以在CScrollView 里面用SetWindowOrg 比较好,对于非滚动形式的窗口,使用SetViewportOrg 比较直观一些。窗口对应的就是页面坐标系,也就是逻辑坐标系,视口对应的是设备坐标系。

2) 关于映射模式

上面的例子用的是缺省的映射模式MM_TEXT,现在改变一下映射模式,看看有什么变化。去掉设置原点的代码,加上:

voidCSdiscroView::OnDraw(CDC* pDC)

{

pDC->SetMapMode(MM_LOMETRIC);

//用绿色填充一个圆形区域(中心[200,200],半径150)

... //原来代码不变

}

运行一下看看,怎么Y 坐标轴和圆都不见了,原来这个模式下面,Y 轴是向上的。把程序里面的Y 坐标都改成负值:

voidCSdiscroView::OnDraw(CDC* pDC)

{

pDC->SetMapMode(MM_LOMETRIC);

//用绿色填充一个圆形区域(中心[200,200],半径150)

CRectrect;

rect.SetRect(50,-50,350,-350);

...

//用蓝色输出坐标原点

pDC->TextOut(0,0,"(0,0)");

//画出坐标轴

...

pDC->MoveTo(0,0);

pDC->LineTo(0,-500); //y轴

pDC->LineTo(5,-490);

pDC->MoveTo(0,-500);

pDC->LineTo(-5,-490);

}

运行一看,OK 都出来了,但是尺寸比原来小多了。原来每个逻辑单位被映射成0.1毫米。那么圆的直径是300,应该对应30毫米,用尺子在屏幕上面量一下吧,几乎就是30毫米啊:)。不相信,设置成MM_HIMETRIC,天啊,看不到了,可能太小了?把圆的半径加大:

rect.SetRect(50,-50,1350,-1350);

嗯,出来了,直径好像是1300*0.01 = 13毫米。既然这样MM_HIENGLISH和MM_LOENGLISH以及MM_TWIPS就不测试了。需要注意一点,SetMapMode 函数调用后,仅仅影响后续的画图函数,而它前面的画图函数仍然按照原来的映射模式输出。所以同一个绘图函数中,可以调用多次SetMapMode 改变映射模式,比如绘图单位在英寸和厘米之间,绘图的精度在0.01厘米和0.1厘米之间可以时刻根据需要进行切换。

3) SetWindowExt 和SetViewportExt

由于它们仅仅在MM_ISOTROPIC 模式下有效,所以这样做:

voidCSdiscroView::OnDraw(CDC* pDC)

{

pDC->SetMapMode(MM_ISOTROPIC );

CSizesizeOrg = pDC->SetWindowExt(200,100);

//查看返回值可以发现SetViewportExt 返回的是当前屏幕设置的分辨率1024 x 768,不过y 是负值,y 为什么是负值?

//因为MM_ISOTROPIC模式下,Y 轴是向下的。所以记得所有画图代码中的Y 坐标用正值!

sizeOrg =pDC->SetViewportExt(200,100);

...//画图代码

}

通过改变两个Set 函数中的参数值,发现系统自动管理x/y的比率关系,使圆形保持正确形状。而且图形的大小和参数有关:

假设SetWindowExt(xWin,yWin); SetViewportExt(xView,yView);则系统使用xView/xWin , yView/yWin两个比值中较小的一个作为x/y两个方向共同的压缩比例。最后图形的大小仅仅和这个缩放系数有关。如果两个系数都大于1,则系统使用1:1比例,并不放大图形。

4) DPtoLP 和LPtoDP

这两个函数用于逻辑坐标和设备坐标之间的转换。在MM_TEXT模式下两个坐标是一样的。现在设置成MM_LOMETRIC模式,看看它们的作用。

voidCSdiscroView::OnDraw(CDC* pDC)

{

//每个逻辑单位对应0.1毫米设备单位

pDC->SetMapMode(MM_LOMETRIC );

CPointp(100,200);

pDC->DPtoLP(&p,1);

CStringstr;

str.Format(" Use DPtoLP : (100,200) -> (%d,%d)\n",p.x,p.y);

TRACE(str);

p.x = 100; p.y = 200;

pDC->LPtoDP(&p,1);

str.Format(" Use LPtoDP : (100,200) -> (%d,%d)\n",p.x,p.y);

TRACE(str);

}

//调试窗口输出

Use DPtoLP : (100,200) -> (313,-625)

Use LPtoDP : (100,200) -> (32,-64)

可见设备坐标系中的(100,200)对应的是逻辑坐标系(窗口)中的(313,-625)一点,逻辑坐标系下面的(100,200)对应设备坐标系下面的(32,-64)点。注意这个变换结果是设备相关的。对于不同的dc 得到不同的结果。设置相同的都用屏幕dc ,用不同计算机测试,不同的显示器,不同的显示模式设置也会得到不同的变换结果。

这是什么意思呢?就是说窗口中,逻辑坐标(313,-625)在MM_LOMETRIC模式下,对应设备坐标系中X 方向距离原点31.3毫米,Y 方向距离原点62.5毫米的一个点,那个点在设备坐标系中坐标是(100,200),其实就是MM_TEXT模式下,逻辑坐标系下面的那个(100,200)点。同样道理,逻辑坐标(100,200)点,映射到设备坐标系中,是x 轴方向距离原点10mm ,y 轴方向距离原点-20mm 的点(注意方向),那个点的逻辑坐标是(32,-64)。也就是MM_TEXT模式下逻辑坐标系中的(32,-64)点。

最后要说明一点,OnEraseBkgnd(CDC* pDC)里面的DC 和OnDraw(CDC* pDC)里面的DC 有所不同啊。窗口的滚动对前者没有影响,也就是说无论窗口如何滚动,在OnEraseBkgnd 函数中输出的东西永远在视口固定的位置上,不受滚动影响。所以画图的时候,不要把背景和前景混淆了,什么函数就是干什么工作。

坐标系统与坐标变换

win32程序使用坐标系统之间的变换完成图形的缩放、旋转、平移等输出操作。win32下面总共使用四个坐标空间:世界坐标系、页面坐标系、设备坐标系和物理坐标系(包括客户区、桌面或打印纸等)。每个坐标空间都是一个线性空间,用两个相互垂直的坐标轴定位两维的物体。

我们把改变一个物体的大小、方向和形状的算法称作“变换”。一个图形物体从一个坐标空间映射到另一个坐标空间的过程就是一个变换。最终,物体显示在一个物理设备上,通常是屏幕或者打印机。

1、四个坐标系的定义

页面空间和设备空间一起工作,在这两个空间下,程序可以使用设备相关的单位,例如毫米和英寸。但是在世界坐标系和页面坐标系下,都认为是逻辑坐标系,单位是逻辑单位1。

任何程序的绘图代码中使用的坐标都是从世界坐标系开始,直到物理坐标

系,最后得到(看到)输出结果。每两个坐标系之间,系统都采用一种变换方法,从前面的坐标空间复制(或者映射)一个矩形区域到下一个坐标空间。为了便于处理,如果程序调用了SetWorldTransform 函数,则这个映射过程从世界坐标系开始,否则从页面坐标系开始。用图形表示如下:

举个例子说,如果我们在DC 上面有个画线函数:

MoveTo(hDC, 0, 0);

LineTo(hDC, 10,10);

则如果我们没有使用SetWordTransform 函数,可以认为我们的画线操作就是在页面坐标系下面,0,10都是页面坐标系下的坐标。如果我们使用了SetWorldTransform 函数,则画线操作是在世界坐标系下面。页面坐标系中对应的这个线段是世界坐标系下面的线段(0,0) -> (10,10) 通过两个坐标空间之间的变换矩阵变换得到,结果可能还是(0,0)点到(10,10)点,也可能是(5,9)点到(-20,14)点,这取决于两个坐标系之间的变换矩阵。

同样道理,对于上述四个坐标系,当系统从一个坐标系中复制指定矩形区域内的某个点到下一个坐标系时,它使用这两个坐标系之间的变换算法,根据点的原坐标计算得到点的像坐标。因此,一个图形在不同坐标系下,其尺寸、方向和形状都可能不同。注意一点,虽然这个变换是两个坐标系之间的,是针对物体整体而言的变换,但在系统在操作的时候,是逐点、逐行操作的。

虽然很少用到SetWorldTransform 函数,但是应该掌握最基本的坐标系之间的线性变换矩阵,形式如下:

函数采用逻辑单位,缺省的变换矩阵是单位阵:

四个eM 参数给出旋转和缩放变换系数,eDx/eDy给出平移变换系数。 注意SetWorldTransform 函数要求DC 的图形模式是GM_ADVANCED,

可以用

SetGraphicsMode 设置,仅Windows NT/ 2000下面支持。缺省图形模式是GM_COMPATIBLE,兼容16位windows ,这个模式下不能使用该函数。下面简单介绍这两个坐标系之间的变换矩阵。

2、世界坐标系到页面坐标系的变换

(1)平移

物体上每个点进行水平和垂直的移动,eDx 和eDy 参数分别给出移动的尺寸。具体算法是:

x' = x + Dx

y' = y + Dy

其中 x' ,y' 是新的坐标, x ,y 是源坐标。Dx 是水平移动距离,Dy 是垂直移动距离。平移矩阵是:

(2)缩放

组成物体的每个水平行或者垂直行进行拉伸或者压缩。算法公式是:

y' = y * Dy

x' = x * Dx

其中Dy ,Dx 是缩放系数。用矩阵表示为:

(3)旋转

组成物体的每个点都相对于坐标原点旋转一个角度。算法公式是:

x' = (x * cos A) - (y * sin A)

y' = (x * sin A) + (y * cos A)

A 表示绕原点逆时针旋转的角度,用矩阵表示如下:

(4)变形

分为水平变形和垂直变形两种,举个例子说,一个矩形通过变形成为一个平行四边形。算法公式分别是:

x' = x + (Sx * y)

y' = y + (Sy * x)

其中Sx, Sy分别是变形系数。用矩阵表示为:

(5)镜像映射

例如水平翻转的公式是:

x' = –x

用矩阵表示是:

线性变换可以是上面几种变换中任意若干种的组合。最终的变换矩阵是一个3x3的矩阵。可以调用CombineTransform 进行两种变换的组合,也可以自己按照公式计算出变换矩阵。

强调一点,虽然可以通过SetWorldTransform 函数调用设置世界坐标系到页面坐标系的变换矩阵,但通常情况下,在我们的程序中,图形图像的变换并不是通过系统来完成的。而是程序自己完成的,因为自己做,可以更加灵活,容易控制,效率更高。因此不推荐使用SetWorldTransfor 函数,如果我们要做图形变换的显示,建议自己先用变换算法计算好,然后直接在页面坐标系下面作图。

3、页面坐标系到设备坐标系之间的变换

这个变换决定了与特定DC 相联系的映射模式,影响该DC 上的所有图形输出。映射模式本身就是一个缩放变换,决定了画图操作中一个单位的尺寸,

映射

模式也可以用于平移变换,某些情形下,映射模式会改变x,y 轴的坐标原点。首先来了解几个映射模式:

(1)映射模式说明

要设置映射模式,调用SetMapMode ,要获得当前的映射模式,调用GetMapMode 。

页面空间到设备空间的变换涉及到窗口或者视口中的点,从这个意义上讲,

窗口反映了页面空间中的逻辑坐标系统,而视口代表设备空间的设备坐标系统。窗口和视口都包含一个坐标原点和x/y轴。窗口中使用的参数是逻辑坐标,视口中使用的参数是设备坐标(像素)。系统根据坐标原点生成变换矩阵。这就意味着,窗口和视口分别负责给出从页面坐标空间到设备坐标空间映射变换矩阵的一半参数。

根据窗口和视口的坐标轴尺寸(最大坐标值),可以建立一个比例或者缩放系数,用于页面空间到设备空间的变换。对于前六种预定义映射模式,当调用SetMapMode 函数的时候,坐标轴的最大尺寸是由系统设置的,无法更改。后两种映射模式MM_ISOTROPIC和 MM_ANISOTROPIC下,需要定义坐标轴的最大尺寸,因此调用SetMapMode 之后,必须调用SetWindowExt 和SetViewportExt 进行设置。特别是在MM_ISOTROPIC映射模式下。必须注意先调用SetWindowExtEx 然后调用SetViewportExtEx 。

根据窗口和视口的坐标原点,可以建立页面空间到设备空间的线性变换的平移关系。通过函数SetWindowOrg 和SetViewportOrg 来设置原点。坐标原点和坐标轴的尺寸没有关系,因此任何映射模式下都可以设置坐标原点,改变映射模式也不会影响当前的坐标原点。由于这两个函数是相关的,所以通常使用一个即可,不必两个都调用。记住一点,无论是否调用这两个函数,设备坐标(0,0)永远是左上角。也可以用函数OffsetWindowOrg 或者OffsetViewportOrg 改变坐标原点。下面的公式给出了页面坐标空间到设备坐标空间之间的点的映射关系:

Dx = ((Lx - WOx) * VEx / WEx) + VOx

说明:

Dx 设备空间中的点(或者说单位)

Lx 逻辑单位 x (或者说页面空间中的单位)

WOx 窗口的 x 原点

VOx 视口中 x 原点

WEx 窗口的 x 轴尺寸

VEx 视口的 x 轴尺寸

对于y 方向的公式也是一样的。

函数LPtoDP 和DPtoLP 可以用来完成两个坐标空间点的变换的计算。

(2)关于前6种预定义映射模式

6种预定义映射模式中,只有MM_TEXT是设备相关的,其余都是设备无关的。缺省的映射模式是MM_TEXT,即一个逻辑单位等于一个像素。逻辑到设备的映射仅仅是一个平移关系,和程序设置的窗口和视口的坐标原点有关。视口和窗口的坐标轴尺寸都设置成1,从而形成一一映射。如果程序要显示准确的几何图形,可以使用MM_LOENGLIST模式,保证图形的形状在任何显示器和打印机下面都是一样的。如果仍然使用MM_TEXT,则可能在VGA 显示器上面显示的一个圆,到了EGA 显示器下就变成椭圆了,如果用300dpi 的激光打印机输出,则结果是很小的一个圆。

(3)关于后两种自定义映射模式

MM_ISOTROPIC 和 MM_ANISOTROPIC两个映射模式用于自定义。MM_ISOTROPIC可以保证逻辑单位在x 和y 方向是一致的。MM_ANISOTROPIC下允许设置成不同。例如一个 CAD 或者绘图程序可以用MM_ISOTROPIC模式,将逻辑单位设置成工程常用的1/64英寸,代码如下:

SetMapMode(hDC, MM_ISOTROPIC);

SetWindowExtEx(hDC, 64, 64, NULL);

SetViewportExtEx(hDC,GetDeviceCaps(hDC,LOGPIXELSX),GetDeviceCaps(hDC,

LOGPIXELSY), NULL);

4、设备坐标空间到物理设备的变换

设备空间到物理设备的变换在很多情况下是唯一的,是由系统控制的,主要的目的是保证设备坐标系的原点准确映射到物理设备的合适位置。比如屏幕上面显示的某个程序,窗口显示的位置要和窗口矩形在显存中的位置对应起来,移动窗口就是改变这个控制窗口输出的矩形在显存中的起点,反映在显示器屏幕上面就是程序窗口的移动。如果是在打印机dc 上面画图,则物理设备就是纸张了。这个变换通常是由系统负责控制的。因此没有函数用于设置这个变换,也没有函数获取相关的变换数据。

5、缺省的变换

如果程序创建了一个DC ,并立刻开始调用GDI 绘图函数,使用的变换过程就是:

缺省的页面空间→设备空间→客户区(物理设备空间) 的变换

除非程序调用SetGraphicsMode 和SetWorldTransform 函数,否则世界坐标空间→页面坐标空间是单位变换,可以认为没有变换。

页面空间→设备空间在MM_TEXT模式下是一一映射,即给定页面空间中的一点,对应设备空间中的相同点。前面已经指出,这个变换不是通过一个矩阵,而是通过用视口的宽度/窗口的宽度,视口的高度/窗口的高度两个公式来计算的。缺省情况下,视口的尺寸是1X1像素。窗口的尺寸是1X1页。

设备空间→物理设备(客户区,桌面或者打印机)的变换通常也是一一映射,即设备空间中的一点对应客户区或者打印机输出中的一个单位,目的是保证无论程序窗口如何在屏幕上面移动,最终的输出始终准确地反映设备空间中的图形。

注意MM_TEXT模式比较特殊,它的Y 坐标轴是向下的,其它映射模式的Y 坐标轴都是向上的。

6、下面用一个例子考察每个坐标变换函数的意义。

用classwizard 生成一个sdi 工程,视类选择从CScrollView 派生,然后添加如下代码:

voidCSdiscroView::OnDraw(CDC* pDC)

{

//用绿色填充一个圆形区域(中心[200,200],半径150)

CRectrect;

rect.SetRect(50,50,350,350);

CBrushbru;

bru.CreateSolidBrush(RGB(0,127,0));

CBrush *pBrushOld = pDC->SelectObject(&bru);

pDC->Ellipse(&rect);

pDC->SelectObject(pBrushOld);

bru.DeleteObject();

//输出坐标原点

pDC->TextOut(0,0,"(0,0)");

//画出坐标轴

pDC->MoveTo(0,0);

pDC->LineTo(500,0); //x轴

pDC->LineTo(490,5); //箭头

pDC->MoveTo(500,0);

pDC->LineTo(490,-5);

pDC->MoveTo(0,0);

pDC->LineTo(0,500); //y轴

pDC->LineTo(5,490);

pDC->MoveTo(0,500);

pDC->LineTo(-5,490);

}

voidCSdiscroView::OnInitialUpdate()

{

CScrollView::OnInitialUpdate();

CSizesizeTotal;

//设置整个窗口尺寸为1000x1000

sizeTotal.cx = sizeTotal.cy = 1000;

SetScrollSizes(MM_TEXT, sizeTotal);

}

实际上我们所有的画图操作都是在世界坐标系下面,由于没有使用SetWorldTransform ,所以世界坐标系和页面坐标系等价,可以认为我们的画图

操作就是在页面坐标系中。下面分别添加相应的函数调用,考察每个函数对输出的影响。

1) SetWindowOrg / SetViewportOrg (CDC类成员函数,对应api 函

数SetWindowExt / SetViewportExt)

voidCSdiscroView::OnDraw(CDC* pDC)

{

pDC->SetWindowOrg(-100,-50);//或者pDC->SetViewportOrg(100,50);

//

... //原来的代码

}

窗口没有滚动之前,视口坐标系的原点和窗口坐标系的原点重合,如果滚动窗口,相当于改变视口原点的位置,因此显示在屏幕上面的部分(视口里面的东西)就发生变化。现在我们不滚动窗口,而是调用SetWindowOrg 改变窗口原点的坐标,看看发生的变化。

SetWindowOrg(-100,-50)函数调用的意思是把窗口(页面坐标系)中的(-100,-50)点设置成窗口的原点,由于窗口和视口的原点永远是重合的,所以视口的(0,0)点现在就和窗口的(-100,-50)重合,而视口的(0,0)点就是程序客户区的左上角,因此设置的后果就是:绘图的输出向x/y轴的正方向移动了。编译运行以后可以看到,字符串"(0,0)"向右下平移了,好像向上、向左滚动了窗口一样。同样道理,如果SetWindowOry 里面使用的是(100,50),则效果等同于向下、向右滚动了窗口。

对应图形如下:

从效果上SetWindowOrg(-100,-50)和SetViewportOrg(100,50)是等价的。但是使用一下就会发现SetViewportOrg(100,50)以后如果滚动窗口的话,窗口的刷新有些问题,所以在CScrollView 里面用SetWindowOrg 比较好,对于非滚动形式的窗口,使用SetViewportOrg 比较直观一些。窗口对应的就是页面坐标系,也就是逻辑坐标系,视口对应的是设备坐标系。

2) 关于映射模式

上面的例子用的是缺省的映射模式MM_TEXT,现在改变一下映射模式,看看有什么变化。去掉设置原点的代码,加上:

voidCSdiscroView::OnDraw(CDC* pDC)

{

pDC->SetMapMode(MM_LOMETRIC);

//用绿色填充一个圆形区域(中心[200,200],半径150)

... //原来代码不变

}

运行一下看看,怎么Y 坐标轴和圆都不见了,原来这个模式下面,Y 轴是向上的。把程序里面的Y 坐标都改成负值:

voidCSdiscroView::OnDraw(CDC* pDC)

{

pDC->SetMapMode(MM_LOMETRIC);

//用绿色填充一个圆形区域(中心[200,200],半径150)

CRectrect;

rect.SetRect(50,-50,350,-350);

...

//用蓝色输出坐标原点

pDC->TextOut(0,0,"(0,0)");

//画出坐标轴

...

pDC->MoveTo(0,0);

pDC->LineTo(0,-500); //y轴

pDC->LineTo(5,-490);

pDC->MoveTo(0,-500);

pDC->LineTo(-5,-490);

}

运行一看,OK 都出来了,但是尺寸比原来小多了。原来每个逻辑单位被映射成0.1毫米。那么圆的直径是300,应该对应30毫米,用尺子在屏幕上面量一下吧,几乎就是30毫米啊:)。不相信,设置成MM_HIMETRIC,天啊,看不到了,可能太小了?把圆的半径加大:

rect.SetRect(50,-50,1350,-1350);

嗯,出来了,直径好像是1300*0.01 = 13毫米。既然这样MM_HIENGLISH和MM_LOENGLISH以及MM_TWIPS就不测试了。需要注意一点,SetMapMode 函数调用后,仅仅影响后续的画图函数,而它前面的画图函数仍然按照原来的映射模式输出。所以同一个绘图函数中,可以调用多次SetMapMode 改变映射模式,比如绘图单位在英寸和厘米之间,绘图的精度在0.01厘米和0.1厘米之间可以时刻根据需要进行切换。

3) SetWindowExt 和SetViewportExt

由于它们仅仅在MM_ISOTROPIC 模式下有效,所以这样做:

voidCSdiscroView::OnDraw(CDC* pDC)

{

pDC->SetMapMode(MM_ISOTROPIC );

CSizesizeOrg = pDC->SetWindowExt(200,100);

//查看返回值可以发现SetViewportExt 返回的是当前屏幕设置的分辨率1024 x 768,不过y 是负值,y 为什么是负值?

//因为MM_ISOTROPIC模式下,Y 轴是向下的。所以记得所有画图代码中的Y 坐标用正值!

sizeOrg =pDC->SetViewportExt(200,100);

...//画图代码

}

通过改变两个Set 函数中的参数值,发现系统自动管理x/y的比率关系,使圆形保持正确形状。而且图形的大小和参数有关:

假设SetWindowExt(xWin,yWin); SetViewportExt(xView,yView);则系统使用xView/xWin , yView/yWin两个比值中较小的一个作为x/y两个方向共同的压缩比例。最后图形的大小仅仅和这个缩放系数有关。如果两个系数都大于1,则系统使用1:1比例,并不放大图形。

4) DPtoLP 和LPtoDP

这两个函数用于逻辑坐标和设备坐标之间的转换。在MM_TEXT模式下两个坐标是一样的。现在设置成MM_LOMETRIC模式,看看它们的作用。

voidCSdiscroView::OnDraw(CDC* pDC)

{

//每个逻辑单位对应0.1毫米设备单位

pDC->SetMapMode(MM_LOMETRIC );

CPointp(100,200);

pDC->DPtoLP(&p,1);

CStringstr;

str.Format(" Use DPtoLP : (100,200) -> (%d,%d)\n",p.x,p.y);

TRACE(str);

p.x = 100; p.y = 200;

pDC->LPtoDP(&p,1);

str.Format(" Use LPtoDP : (100,200) -> (%d,%d)\n",p.x,p.y);

TRACE(str);

}

//调试窗口输出

Use DPtoLP : (100,200) -> (313,-625)

Use LPtoDP : (100,200) -> (32,-64)

可见设备坐标系中的(100,200)对应的是逻辑坐标系(窗口)中的(313,-625)一点,逻辑坐标系下面的(100,200)对应设备坐标系下面的(32,-64)点。注意这个变换结果是设备相关的。对于不同的dc 得到不同的结果。设置相同的都用屏幕dc ,用不同计算机测试,不同的显示器,不同的显示模式设置也会得到不同的变换结果。

这是什么意思呢?就是说窗口中,逻辑坐标(313,-625)在MM_LOMETRIC模式下,对应设备坐标系中X 方向距离原点31.3毫米,Y 方向距离原点62.5毫米的一个点,那个点在设备坐标系中坐标是(100,200),其实就是MM_TEXT模式下,逻辑坐标系下面的那个(100,200)点。同样道理,逻辑坐标(100,200)点,映射到设备坐标系中,是x 轴方向距离原点10mm ,y 轴方向距离原点-20mm 的点(注意方向),那个点的逻辑坐标是(32,-64)。也就是MM_TEXT模式下逻辑坐标系中的(32,-64)点。

最后要说明一点,OnEraseBkgnd(CDC* pDC)里面的DC 和OnDraw(CDC* pDC)里面的DC 有所不同啊。窗口的滚动对前者没有影响,也就是说无论窗口如何滚动,在OnEraseBkgnd 函数中输出的东西永远在视口固定的位置上,不受滚动影响。所以画图的时候,不要把背景和前景混淆了,什么函数就是干什么工作。


相关内容

  • 坐标系统转换
  • 瑞得信息 版本 修订历史记录 目录 1. 背景 1.1 概念: 1.2 其它坐标系统: 1.2.1 变形1(不改变XY 方向,且不改变XY 增量方向): 1.2.2 变形2(不改变XY 方向,改变XY 增量方向): 1.2.3 变形3(改变XY 方向,且不改变XY 增量方向): 1.2.4 变形4( ...

  • 六自由度机器人焊接轨迹研究
  • 张用,郝卫东,朱博譞,李君,苗国强,刘芳平 (桂林电子科技大学 机电工程学院,广西 桂林 541004) 摘要:焊接轨迹是机器人焊接时所行走的轨迹,焊接轨迹算法是控制机器人焊接轨迹的数学模型,本文提出一种通过测量机器人基坐标系.工件坐标系与测量系统坐标系的齐次变换矩阵方法,计算出工件坐标系与焊接机器 ...

  • 空间数据的转换
  • 空间数据的转换 1. 空间数据的投影变换 ............................................................................................................................. 2 1 ...

  • 地图投影和坐标系统
  • 地图投影和坐标系统 在ArcGIS 中,每一个dataset 都有一个坐标系统.它的目的是在一个通用的坐标框架例如map 中集成其它地理数据图层.坐标系统允许你将datasets 集成到地图中,同时也做各种各样集成分析的操作,例如叠加不同数据源和坐标系统的图层. 什么是坐标系? 坐标系允许地理数据集 ...

  • 第七节 控制三维空间:坐标变换
  • 第七节 你将学到: 控制三维空间:坐标变换 中有条理的显示出来,你必须根据它们的关系来安排.在这一节中你将学到在场景中移动形状和物体.实际上你并没有移动它们,只是改变了坐标系统.对任何一个三维程序员来说在三维场景中正确摆放物体是很重要的工具.正如你将看到的一样,这将会对你描述物体和平移旋转物体都是很 ...

  • GPS大地坐标向地方坐标转换的
  • 第 1 期 2005 年 3 月 华东师范大学学报(自然科学版) Journal of East China Norrnal University (Natural Sc ience) No.l Mar. 2005 立章编号: 1000-5641 (200日 01-0073-05 GPS 大地坐标向 ...

  • 异步电动机动态数学模型
  • 目录 1 异步电动机动态数学模型........................................................................................ 2 1.1异步电动机动态数学模型的性质 . ....................... ...

  • 三坐标测量技术在汽车车身检测中的应用
  • 本文由hugeromt贡献 pdf文档可能在WAP端浏览体验不佳.建议您优先选择TXT,或下载源文件到本机查看. BA 工具技术 三维坐标测量技术在汽车车身检测中的应用 郑 摘 俊 邾继贵 天津大学 叶声华 要: 以汽车车身在线检测技术的三种检测手段为例, 讨论了三坐标测量机. 视觉检测系统和测量机 ...

  • 矢量控制高性能异步电机速度控制器设计_林立
  • DOI:10. 15934/j.cn k i . m icrom otors. 2005. 06. 015 矢量控制高性能异步电机速度控制器设计 林立 黄声华 矢量控制高性能异步电机速度控制器设计 林 立1, 黄声华2 (1. 邵阳学院, 邵阳, 422004; 2. 华中科技大学, 武汉, 430 ...