高杉:逻辑坐标&设备坐标

来源:百度文库 编辑:九乡新闻网 时间:2024/04/30 02:25:57

CDC::SetMapMode 

virtual int SetMapMode( int nMapMode );

函数功能描述:该函数设置指定设备环境的映射方式,映射方式定义了将逻辑单位转换为设备单位的度量单位,并定义了设备的X、Y轴的方向。

nMapMode:指定新的映射方式,此参数可以是下面列出的任何一个值。

  MM_ANISOTROPIC:逻辑单位转换成具有任意比例轴的任意单位,用SetWindowExtEx和SetViewportExtEx函数可指定单位、方向和比例。

  MM_HIENGLISH:每个逻辑单位转换为0.001英寸,X的正方面向右,Y的正方向向上。

  MM_HIMETRIC:每个逻辑单位转换为0.01毫米,X正方向向右,Y的正方向向上。

  MM_ISOTROPIC:逻辑单位转换成具有均等比例轴的任意单位,即沿X轴的一个单位等于沿Y轴的一个单位,用和函数可以指定该轴的单位和方向。图形设备界面(GDI)需要进行调整,以保证X和Y的单位保持相同大小(当设置窗口范围时,视口将被调整以达到单位大小相同)。

  MM_LOENGLISH:每个逻辑单位转换为0.1英寸,X正方向向右,Y正方向向上。

  MM_LOMETRIC:每个逻辑单位转换为0.1毫米,X正方向向右,Y正方向向上。

  MM_TEXT:每个逻辑单位转换为一个图素,X正方向向右,Y正方向向下。

MM_TWIPS;每个逻辑单位转换为打印点的1/20(即1/1400英寸),X正方向向右,Y方向向上。

 

备注:

MM_TEXT方式允许应用程序以设备像素为单位来工作,像素的大小根据设备不同而不同。MM_HIENLISH, MM_HIMETRIC, MM_LOENGLISH, MM_LOMETRIC和MM_TWIPS方式对必须用物理意义单位(如英寸或毫米)制图的应用程序是非常有用的。MM_ISOTROPIC方式保证了1:1的纵横比。MM_HIENLISH方式允许对X和Y坐标分别进行调整。

 

 

SetViewportOrg与SetWindowOrg

CDC::SetWindowOrg
CPoint SetWindowOrg(int x ,int y );
CPoint SetWindowOrg(POINT point )

返回值:CPoint对象,是窗口初始位置的前一次取值(逻辑单位)。

参数: x 指定窗口初始位置的X逻辑坐标。
y 指定视图端口初始位置的Y逻辑坐标。
point 指定窗口初始位置。其值必须在设备坐标系统范围内。可以为该参数传递POINT结构或CPoint对象。

说明:
设置设备上下文的窗口初始位置。它和设备上下文窗口一起说明了GDI如何将逻辑坐标中的点映射到实际设备坐标中。换言之,它们说明了GDI如何将逻辑坐标转换为设备坐标。窗口初始位置表明在设备坐标系统中的点,GDI将视图端口初始位置与该点映射。窗口初始位置是由SetWindowOrg成员函数在逻辑坐标系统中指定的。GDI在映射其它点时遵从同样的过程,这需要窗口初始位置与视图端口初始位置的映射。例如,所有以窗口初始位置为中心的圆周上的点同样是以视图端口初始位置为中心的圆周上的点。同样地,通过窗口初始位置的直线上的所有点也将形成一条通过视图端口初始位置的直线。

在CMyStatic::OnPaint() 函数中测试

CPaintDC dc(this); // device context for painting

// TODO: Add your message handler code here
CRect rect;
GetClientRect(&rect);//取得控件大小
dc.SetMapMode(MM_ANISOTROPIC);//设置成可以改变作标轴方向和比例的,但下面没改变比例

1.先讨论X轴正向向左,Y轴正向向下的情况,即默认的状态


dc.SetWindowExt(100, 100);//逻辑上是长100个单位,宽100个单位
dc.SetViewportExt(200,200);//设备是长200像素,宽200像素

即一个逻辑单位 = 200 / 100 = 2个像素

如果上面第一行改成dc.SetWindowExt(100, 200);//那么,一个逻辑长单位就是2像素,逻辑高单位就是1个像素

同样的dc.Rectangle(0, 0, 10, 10);第一个就是正方形,而第二个就是长是宽2倍的长方形

2.现在谈谈关于设备原点和逻辑原点的问题。

我的理解是,设备原点永远在左上角,也就是最初的(0,0)的位置

逻辑原点是可以移动的,Y轴的正向是向上还是向下是可以改变的

现在考虑Y轴向下为正向时,逻辑原点的移动问题。

dc.SetWindowOrg(0, 0);//这个可省略
dc.SetViewportOrg(rect.right, rect.bottom);

这个就是把设备的(rect.right, rect.bottom)映射为逻辑原点(0, 0)

那就就相当于,把逻辑原点从原来设备原点(0,0)平移到了(rect.right, rect.bottom)

即整个作标系,向右平移了rect.right,向下平移了rect.bottom.

dc.SetViewportOrg(0, 0);//这个可省略
dc.SetWindowOrg(rect.right, rect.bottom);

这个是把逻辑的(rect.right, rect.bottom)映射为设备的(0, 0);也可以说是把设备的(0, 0)映射为逻辑的(rect.right, rect.bottom)

可以想像,设备原点(0, 0)永远在左上角,由于Y轴是向下为正,X轴向右为正,那逻辑原点就被向上移动了rect.bottom,向左移动了rect.right,这样,刚好逻辑坐标(rect.right, rect.bottom)落在了右上角的位置。

然而dc.Rectangle(0, 0, rect.right, rect.bottom);画出的长方形,就正好完全落在窗口这外了

要想和dc.SetViewportOrg(rect.right, rect.bottom);达到相同的效果,

必须要把逻辑原点向右和向下移动,也就是,移动动后的逻辑坐标(-rect.right, -rect.bottom);落在设备原点上,即把逻辑的(-rect.right, -rect.bottom)映射为设备的(0, 0);

dc.SetWindowOrg(-rect.right, -rect.bottom);

如果SetWindowOrg和SetViewportOrg同时使用,例如

dc.SetWindowOrg(x, y);
dc.SetViewportOrg(x1, y1);//不推荐同时用,因为通常会把自己给搞糊涂

即把逻辑坐标(x, y)映射为设备坐标(x1, y1),就相当于把逻辑原点向右移(x1 - x2), 向下移(y1 - y2)

综上所述,X轴正向向右,Y轴正向向下的情况下,

dc.SetWindowOrg(-rect.right, -rect.bottom);等价于dc.SetViewportOrg(rect.right, rect.bottom);

而dc.SetViewportOrg(rect.right, rect.bottom);和dc.SetViewportOrg(rect.right, rect.bottom);刚互补,同时运行则效果抵消。

3.现在讨论X轴正向向左,Y轴正向向上的情况

dc.SetWindowExt(100, -100);//逻辑上是长100个单位,宽100个单位
dc.SetViewportExt(200,200);//设备是长200像素,宽200像素

dc.SetWindowExt(100, 100);//逻辑上是长100个单位,宽100个单位
dc.SetViewportExt(200,-200);//设备是长200像素,宽200像素

如果

dc.SetWindowExt(100, -100);//逻辑上是长100个单位,宽100个单位
dc.SetViewportExt(200,-200);//设备是长200像素,宽200像素

则Y轴仍向下

以下面为例:

dc.SetWindowExt(100, -100);//逻辑上是长100个单位,宽100个单位
dc.SetViewportExt(200,200);//设备是长200像素,宽200像素

dc.SetWindowOrg(0, 0);//这个可省略
dc.SetViewportOrg(0, rect.bottom);

设备(0, rect.bottom)映射为逻辑原点(0, 0),即把逻辑原点,移到左下角了

dc.SetViewportOrg(0, 0);//这个可省略
dc.SetWindowOrg(0, rect.bottom);

逻辑(0, rect.bottom);映身为设备原点(0,0),即设备原点(0, 0)映射为逻辑(0, rect.bottom);由于Y轴向上,所以相当于把逻辑原点由设备原点,向下移动rect.bottom,这样,逻辑(0, rect.bottom)刚好落在设备原点(0,0)上。即逻辑原点移动到了左下角。

这时可以发现,dc.SetViewportOrg(0, rect.bottom);与dc.SetWindowOrg(0, rect.bottom);等价了

推广开来,即dc.SetViewportOrg(x, y);与dc.SetWindowOrg(-x,   y);等价

总结:保持X轴正向向右不变,当Y轴正向向下时

dc.SetViewportOrg(x, y);与dc.SetWindowOrg(-x,   -y);等价

当Y轴正向向上时

dc.SetViewportOrg(x, y);与dc.SetWindowOrg(-x,   y);等价

需要强调一点,设备原点永远不会移动,保持在(0,0),设备的坐标系永远不会变,变的是逻辑原点在设备坐标系中的位置和逻辑坐标轴的方向。

 

 

SetWindowExt和SetViewportExt

CRect rectClient;

GetClientRect(rectClient);

pDC->SetMapMode(MM_ANISOTROPIC);

pDC->SetWindowExt(CSize(1000,1000));

pDC->SetViewportExt(rectClient.right,-rectClient.bottom);

pDC->SetViewportOrg(rectClient.right/2,rectClient.bottom/2);

pDC->Ellipse(-500,-500,500,500);

 

可变比例映射模式,看到这一段的时候,一开始就把我的弄糊涂了。我一直没弄明白中间加红的几行代码是什么意思。把其注释掉,又没有原先的效果。在网上百度了半天。得出以下注释说明:

 

//SetWindowExe设定窗口尺寸,SetViewportExt设定视口尺寸。

//窗口尺寸以逻辑单位计算,视口尺寸以物理单位计算。

    CRect rectClient;

 

    GetClientRect(rectClient);//取窗口物理尺寸(单位:像素)

    pDC->SetMapMode(MM_ANISOTROPIC);

    pDC->SetWindowExt(1000, 1000);//窗口逻辑大小:1000*1000,

    pDC->SetViewportExt(rectClient.right, -rectClient.bottom);//改变Y坐标方向--viewport使用物理大小

    pDC->SetViewportOrg(rectClient.right / 2, rectClient.bottom / 2);//设置窗口中心点为坐标系原点--Viewport使用物理大小 

    pDC->Ellipse(CRect(-500, -500, 500, 500));//以逻辑单位画图---普通GDI API使用逻辑单位

 

//默认方式下,物理/逻辑值是1:1关系,可换用。但使用SetWindowExt/SetViewportExt后两者不可混用。

 

以上红色部分,我的解释是以物理的原点为坐标系,以逻辑的大小为单位画圆。后面的代码中会说明这一问题。

后来经过自己的捉摸,我想我终于搞清楚是怎么一回事情了。

所谓映射就是物理和逻辑的映射。使用GetClientRect方法后,获取到窗口的物理大小;然后再使用SetWindowExt,设置了窗口的逻辑大小,与之相对应的是SetViewportExt,也就是说在这里作了一个映射。SetWindowExt中的第一个参数

cx Specifies the x-extent (in logical units) of the window.

X宽度(可以这么理解吗?)与 SetViewportExt中的第一个参数

Cx Specifies the x-extent of the viewport (in device units).

相对应起来。好像中学的比例一样。逻辑宽度和物理宽度映射,逻辑高度和物理高度映射。这样,一旦映射关系确立之后,再使用后面的方法进一步的操作。

 

一开始的代码是在窗口中显示一个与之限定的圆,并且会随着窗口大小的改变亦会跟着改变。

 

我现在稍稍把其中的参数改变一下。

 

CRect rectClient;

GetClientRect(rectClient);

 

pDC->SetMapMode(MM_ANISOTROPIC);

pDC->SetWindowExt(CSize(800,800));

pDC->SetViewportExt(rectClient.right,-rectClient.bottom);

pDC->SetViewportOrg(rectClient.right/2,rectClient.bottom/2);

pDC->Ellipse(-500,-500,500,500);

 

注意上面红色突出显示的代码。我现在将逻辑大小变小了一些。现在注意一下实际在画图的代码中(绿色显示),我并没有修改其参数。现在将其编译运行。会发现,实现中的圆的轨迹会超出窗口。

只是把物理与逻辑之前的映射调整了一下。

 

CRect rectClient;

GetClientRect(rectClient);

 

pDC->SetMapMode(MM_ANISOTROPIC);

pDC->SetWindowExt(CSize(1000,1000));

pDC->SetViewportExt(rectClient.right,-rectClient.bottom);

pDC->SetViewportOrg(rectClient.right/2,rectClient.bottom/2);

pDC->Ellipse(0,0,500,500);

 

再调整一下参数,画出来的图你会发现,真正的成了二维坐标图。

 

经过以上一番测试,我想我应该明白每行代码的意思了。转换成自己的注释,应该更容易理解和记忆些。

 

          CRect rectClient;

          GetClientRect(rectClient); //获取物理设备大小

 

          pDC->SetMapMode(MM_ANISOTROPIC); //设置映射模式

          pDC->SetWindowExt(CSize(1000,1000));      //设备逻辑窗口大小(可能与物理窗口大小不一样)

          pDC->SetViewportExt(rectClient.right,-rectClient.bottom); //设置物理设备范围,为设定圆点作准备

          pDC->SetViewportOrg(rectClient.right/2,rectClient.bottom/2); //设置物理设备坐标原点,当然是在上一行代码的基础之上

          pDC->Ellipse(-500,-500,500,500); //以物理设置坐标原点为基础,以逻辑为单位,画圆。

 

可以改造一下,原来的代码,使之后容易理解一些:

          CRect rectClient;

          GetClientRect(rectClient);

 

          pDC->SetMapMode(MM_ANISOTROPIC);

          pDC->SetWindowExt(CSize(1000,1000));

          pDC->SetViewportExt(rectClient.right,-rectClient.bottom);

          pDC->SetViewportOrg(rectClient.left,rectClient.bottom); //设置窗口左下角为原点坐标

          pDC->Ellipse(0,0,1000,1000);