`
wgq837051
  • 浏览: 86017 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

MFC中页面坐标系到设备坐标系之间的变换

MFC 
阅读更多

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

这个变换决定了与特定DC相联系的映射模式,影响该DC上的所有图形输出。映射模式本身就是一个缩放变换,决定了画图操作中一个单位的尺寸,映射模式也可以用于平移变换,某些情形下,映射模式会改变x,y轴的坐标原点。首先来了解几个映射模式:

(1)映射模式说明

-------------------------------------------------------------------------
映射模式 描述
-------------------------------------------------------------------------
MM_ANISOTROPIC 每个页面空间的单位映射为程序定义的设备空间的单位。两个坐标
  轴的缩放尺寸可以不一致(例如,一个世界坐标系下面的园在指定
  设备上可能显示为椭圆)。坐标轴的方向也是程序定义的。

MM_HIENGLISH 每个页面空间的单位映射成设备空间中的0.001英寸。x轴向右,y轴向上。

MM_HIMETRIC 每个页面空间的单位映射成设备空间中的0.01毫米。x轴向右,y轴向上。
 
MM_ISOTROPIC  每个页面空间的单位映射为程序定义的设备空间的单位。两个坐标轴
  的缩放尺寸一样。坐标轴的方向由程序定义。

MM_LOENGLISH  每个页面空间的单位映射成设备空间中的0.01英寸。x轴向右,y轴向上。

MM_LOMETRIC  每个页面空间的单位映射成设备空间中的0.1毫米。x轴向右,y轴向上。

MM_TEXT  每个页面空间的单位映射成一个像素。就是说无缩放。如果也没有平移变换,
  则本映射模式下的页面空间和物理设备坐标空间等价。x轴向右,y轴向下。

MM_TWIPS  每个页面空间的单位映射成打印机点的1/20(1/1440英寸)。x轴向右,y轴向上。
------------------------------------------------------------------------------

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

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

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

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

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

Dx    设备空间中的点(或者说单位)
Lx    逻辑单位 x (或者说页面空间中的单位)
WOx   窗口的 x 原点
VOx   视口中 x 原点
WEx   窗口的 x轴尺寸
VEx   视口的 x轴尺寸

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

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

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

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派生,然后添加如下代码:

void CSdiscroView::OnDraw(CDC* pDC)
{
 //用绿色填充一个圆形区域(中心[200,200],半径150)
 CRect rect;
 rect.SetRect(50,50,350,350);
 CBrush bru;
 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);
}

void CSdiscroView::OnInitialUpdate()
{
 CScrollView::OnInitialUpdate();

 CSize sizeTotal;
 //设置整个窗口尺寸为1000x1000
 sizeTotal.cx = sizeTotal.cy = 1000;
 SetScrollSizes(MM_TEXT, sizeTotal);
}

实际上我们所有的画图操作都是在世界坐标系下面,由于没有使用SetWorldTransform,所以世界坐标系和页面坐标系等价,可以认为我们的画图操作就是在页面坐标系中。下面分别添加相应的函数调用,考察每个函数对输出的影响。

1、SetWindowOrg / SetViewportOrg (CDC类成员函数,对应api函数SetWindowOrgEx / SetViewportOrgEx

void CSdiscroView::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),则效果等同于向下、向右滚动了窗口。
对应图形如下:
                                                  
                                                    /-----视口的原点
                                        (-100,-50) +-------------+
    /-----视口的原点                               |   (视口)    |
   +-------------+-------+---->页面坐标系x         +    +--------+-----------+----->页面坐标系x
   |(0,0)        |       |                         |    | (0,0)  |           |
   |             |       |                         |    |        |           |
   |   (视口)    |       |                         |    |        |           |
   | (屏幕上的)  |       |                         |    |        |           |
   | (部分    )  |       |   设置SetWindowOrg后    |    |        |           |
   |             |       |  ---------------------〉+----+--------+           |
   +-------------+       |                              |                    |
   |                     |                              |                    |
   | 整个窗口比视口大    |                              |    窗口            |
   | 有些部分需要滚动    |                              |                    |
   | 才能显示出来        |                              |                    |
   +---------------------+                              +--------------------+
   |                                                    |
  \/ Y                                                 \/ Y

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

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

void CSdiscroView::OnDraw(CDC* pDC)
{

 pDC->SetMapMode(MM_LOMETRIC);
 //用绿色填充一个圆形区域(中心[200,200],半径150)
... //原来代码不变
}
运行一下看看,怎么Y坐标轴和圆都不见了,原来这个模式下面,Y轴是向上的。把程序里面的Y坐标都改成负值:

void CSdiscroView::OnDraw(CDC* pDC)
{
 pDC->SetMapMode(MM_LOMETRIC);
 //用绿色填充一个圆形区域(中心[200,200],半径150)
 CRect rect;
 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 模式下有效,所以这样做:

void CSdiscroView::OnDraw(CDC* pDC)
{
 pDC->SetMapMode(MM_ISOTROPIC  );
 CSize sizeOrg = pDC->SetWindowExt(200,100);

 //查看返回值可以发现SetViewportExt返回的是当前屏幕设置的分辨率1024 x 768,不过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模式,看看它们的作用。

void CSdiscroView::OnDraw(CDC* pDC)
{
 //每个逻辑单位对应0.1毫米设备单位
 pDC->SetMapMode(MM_LOMETRIC );

 CPoint p(100,200);
 pDC->DPtoLP(&p,1);
 CString str;
 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函数中输出的东西永远在视口固定的位置上,不受滚动影响。所以画图的时候,不要把背景和前景混淆了,什么函数就是干什么工作。

分享到:
评论

相关推荐

    VC MFC坐标系统与坐标变换

    坐标系统与坐标变换 坐标系统与坐标变换 坐标系统与坐标变换 坐标系统与坐标变换

    MFC绘图映射模式设备坐标问题

    Windows 绘图映射模式,对于逻辑坐标系和设备坐标系的转换及其何时设备坐标系的变化,以帮助程序理解的方式说明设备坐标系的在不同模式下不同的情形。

    vc++环境下MFC界面坐标转换源码

    在vc++环境下,两坐标系之间转换。界面简单实用,MFC界面。采用原始数据源为摄影测量数据,常用的摄影坐标系之间转换。

    计算机图形学透视投影变换(报告中含核心代码).docx

    1.在世界坐标系中定义一个立方体(由6个面组成),并给定观察点在世界坐标系中的位置(a,b,c)以及观察坐标系的方位角θ,俯仰角φ和姿态角α,另外再给定投影面离观察点的距离D,在屏幕上画出立方体的透视投影图形。...

    计算机图形学透视投影变换实验报告

    1.在世界坐标系中定义一个立方体(由6个面组成),并给定观察点在世界坐标系中的位置(a,b,c)以及观察坐标系的方位角θ,俯仰角φ和姿态角α,另外再给定投影面离观察点的距离D,在屏幕上画出立方体的透视投影图形。...

    高精度校正、坐标系间校正C++Demo

    该库主要用于高精度校正,总共三个文件...作用:用于求解两个坐标系之间的转换关系,求得之后,坐标1和坐标2便形成一个高精度的映射关系。 应用Demo是用VS2010 MFC写的 需要输入的数据:两个坐标系的M行N列坐标数据

    OnPrepareDC

    许多MFC库函数只能在...在设置了设备环境的映射模式及相应的参数以后,CDC的LPtoDP和DPtoLP函数可以用来在逻辑坐标系和设备做表系之间进行转换。 在CView的虚函数OnPrepareDC中设置映射模式要比在OnDraw函数中要好。

    MFC&OpenGL;改进的多边形扫描转换及区域填充

    用c++进行编写,用鼠标左击描绘顶点,右击把最后一个点和第一个点进行连接构造多边形,双击进行多边形颜色填充。这程序把屏幕坐标系转为GL坐标系,用OpenGL进行填充,填充时间根据多边形大小而定,最多50个顶点。

    mfc平台下两种动画的实现

    在vc6.0 环境下使用opengl平台实现动画的两种方法,即 可循环调用一组图片实现帧动画,也可通过变换模型坐标系绘图的方法来实现,

    BLH与XYZ的转换

    用MFC实现了BLH到XYZ的相互转换,可批量和单点转换,简单式操作。

    MFC上机实习编辑器

    最后两列是点的坐标,注意是浮点型的笛卡尔坐标(方向是上、右),并非vc中的缺省的坐标系(方向是下、右)。 Lin1.txt文件中,记录了线的连接关系(没有曲线,只有折线段),如下图所示: 图 32 lin文件中的内容 ...

    VC滚动视图(CScrollView类)的局部更新算法示例

    不管是那种情况,可以将update region转换至“用户坐标系”,遍历用户在此坐标系中打算绘制的对象,确定哪些对象落在了update region内,以便进行重绘。这样,在CScrollView::OnDraw/OnPaint中仅需绘制少量对象即可...

    Liang-Barshy2.zip

    梁友栋直线裁剪算法,在单文档里面可直接操作,vs2010版本的,下载可直接运行;实现了屏幕坐标系对笛卡尔坐标系的转换

    OpenGL 系统开发的源代码

    3.1.2 坐标系与坐标变换 3.1.3 矩阵操作 3.2 几何变换 3.2.1 平移变换 3.2.2 旋转变换 3.2.3 缩放变换 3.2.4 变换次序 3.2.5 实例介绍 3.3 投影变换 3.3.1 透视投影 3.3.2 正交投影 3.4 视口...

    VC驿站基础班无KEY高清C++教程下载地址

    ②、坐标系的变换。 11、通用对话框讲解 ①、文件选择对话框; ②、目录选择对话框; ③、让对话框程序支持拖拽。 12、菜单操作 ①、对话框程序添加菜单; ②、右键弹出式菜单; ③、菜单项的启用与禁用。 ...

    VC++模拟GPS接受信息分析并显示源程序

    GPS 这是一个用VC++写的模拟程序,用以模拟GPS接受信息并分析将结果显示出来,数据来源是卫星,通过定时读取而得来,只要能够保证定时读取到数据,就可以进行分析,确定卫星的位置,然后演示不同坐标系的转换,...

    VC全景图拼接算法源码(毕业设计+论文)

    通过偏移量将A和B放在同一坐标系实现拼接。  有些情况下图像亮度相差较大,为减少亮度对拼接效果的影响,提高定位精度需对图像进行亮度调节。主要方法有:直方图匹配和函数变换(类似于photoshop中的调整)。此步...

    计算机图形学制作时钟源代码

    用MFC VC++实现的时钟源代码 // MFCFrame1View.cpp : implementation of the CMFCFrame1View class // #include "stdafx.h" #include "MFCFrame1.h" #include "MFCFrame1Doc.h" #include "MFCFrame1View.h" #...

    人体动作捕捉软件设计

    本文涉及的人体动作的捕捉系统的整体框架由下位机和上位机组成。下位机为分布 ...将人体各个肢体在导航系的坐标转换为屏幕坐标,完成对人体模型的驱动。 从最终的演示动作捕捉效果来看,人体动作跟踪效果良好。

Global site tag (gtag.js) - Google Analytics