3.从程序员的角度来看坐标映射
坐标映射在程序员的眼中就是要根据自己实际问题的要求,构造出一个满足要求的逻辑空间。所谓的满足要求就是指每一个我们在程序中使用的点,都能出现在physical device上我们预期的相应位置。由于device空间到physical device空间是一对一的映射,因此,我们完全可以将绘图目的地看成device空间,所构造出的逻辑空间也只需正确映射到device空间就可以了。
3.1 page空间 → device空间
如果我们不使用world空间,此时的逻辑空间就是page空间。下面来看如何确定它的三个要素:单位刻度值、方向、原点。
首先要使用SetMapMode(int)函数选择映射模式。这其中有6种事先已经定义好了的模式,可以直接拿来就用,比如MM_HIMETRIC模式表示page空间的单位刻度是0.01毫米,x轴正向向右,y轴正向向上,原点与device空间的原点重合。如果此时程序中有一条值为10的线段,那么在程序员的眼中,这就是一条10×0.01=0.1毫米的线段,不管使用多大分辨率的显示器它都是这么长,我们甚至可以用尺子在屏幕上量量试试。如果选择预定义的映射模式,相当于微软已经为我们构造好了page空间,下面的事我们就都不用做了。
但是很多时候,微软的东西不一定适合我们,此时就要将映射模式设定为MM_ISOTROPIC或者MM_ANISOTROPIC,使用下面的四个函数定义我们自己的坐标系:
SetWindowExt(int Lwidth, int Lheight) //参数的单位为逻辑单位(Logical),如果参数为负值表示window相应的坐标轴与page空间相反。
SetViewportExt(int Pwidth, int Pheight) //参数的单位为像素(Pixel),如果参数为负值表示viewport相应的坐标轴与device空间相反。
SetWindowOrg(int Lx, int Ly)。
SetViewportOrg(int Px, int Py)。
这四个函数提出了两个新的概念:window和viewport,它们分别与page空间和device空间对应,但请记住并不是对等。引入它们的目的仅仅是为了确定page空间的单位刻度、方向、原点。
1.x轴的单位刻度=| Pwidth | / | Lwidth |。
这表示x轴上一个逻辑单位等于多少个像素。下面举例加以解释。
比如我们先通过GetDeviceCap(LOGPIXELSX)获得在我们的显示器上每英寸等于多少个像素,设为p,然后我们将它赋给Pwidth,将Lwidth赋成2,即Pwidth / Lwidth=p / 2。那么,此时page空间x轴上的单位刻度就是p / 2个像素;又由于p个像素是代表一个英寸的,所以此时的page空间x轴上的单位刻度同时也是半个英寸。
请注意这个例子中,虽说viewport的x方向“范围”是p个像素,但是device空间x轴的“范围”决不仅仅是p个像素,而是2^27个像素,至于可视的范围到底是多少,则取决于物理设备空间。
2.x轴的方向:这个好确定,Lwidth与Pwidth同号,则page空间的x轴方向与device空间x轴方向相同,否则相反。
3.原点。这个就有一点麻烦了,我们需将window与viewport进行重叠,包括原点和坐标轴方向,然后才可以确定page空间的原点。下面通过一个例子来加以说明。
例:假设我们通过下面的语句构造了一个page空间:
SetMapeMode(MM_ANISOTROPIC);
SetWindowExt(10, 100);
SetWindowOrg(0, -100);
SetViewportExt(20, 200);
SetViewPortOrg(0,-200);
(由于100个逻辑单位相当于200个像素,因此我将它们的示意长度画成一样。)
从这些语句中我们可以很快确定出page空间的单位刻度(比如y轴上每逻辑单位200 / 100=2个像素),以及y轴的方向与device空间相同(100与200同号),但是page空间的原点在哪里呢?请看:
首先我们分别在page空间中画出window坐标系、在device空间中画出viewport坐标系(如图2的左边部分)。然后由于例子中的window坐标方向与viewport相反,还需将page空间翻转(见图2中间部分)。最后将window与viewport重叠(见图2右边部分),使它们的原点和坐标方向都一致。此时我们可以清楚地看到,page空间的原点就对应于device空间的原点,而且方向也和它相同。
通过以上的1、2、3点我们就可以完全确定一个适合我们自己要求的page空间,当我们不要world空间时,它就是逻辑空间。
另外还有一个问题就是要注意MM_ANISOTROPIC与MM_ISOTROPIC的区别。对于前者来说,x方向的单位刻度与y方向的单位刻度可以不同(当然也可以相同),但是后者x方向的单位刻度与y方向的单位刻度一定是相同的,如果通过计算window与viewport范围的比值得到两个方向的单位刻度值不同,那么将会以较小的那个为准。
3.2 world空间 → page空间
有时候我们需要从一个倾斜的角度显示一个圆或者其它什么图形,但是我们在使用绘图语句时,心目中仍然要当这个圆正对着我们来考虑问题,因为只有这样,我们在构造图形时的思维才不至于混乱,怎么实现呢?就可以通过加上world空间达到这个目的。由于一般很少使用这种映射,我在这里只以一个例子简单加以说明。
void CSampleView::DrawShearCircle()
{
CClientDC dc(this);
dc.SetMapMode(MM_ANISOTROPIC); //映射模式设定为各向异性。
//以下语句将page空间最小刻度值设为1mm,原点位于客户区矩形中心,x正向向右,y正向向上。
dc.SetWindowExt(1, -1);
int PperMMX = dc.GetDeviceCaps(HORZRES) / dc.GetDeviceCaps(HORZSIZE);
int PperMMY = dc.GetDeviceCaps(VERTRES) / dc.GetDeviceCaps(VERTSIZE);
dc.SetViewportExt(PperMMX, PperMMY);
CRect cr;
GetClientRect(&cr);
dc.SetViewportOrg(cr.right/2, cr.bottom/2);
dc.SetWindowOrg(0, 0);
//以下语句设置world空间到page空间的映射规则,将会产生一个y轴的剪切。
SetGraphicsMode(dc.GetSafeHdc(), GM_ADVANCED); //一定要首先打开GM_ADVANCED。
XFORM xf;
xf.eM11 = 1.0;
xf.eM12 = 1.0; //y轴方向的剪切常量为1.0
xf.eM21 = 0.0; //x轴方向的剪切常量为0.0
xf.eM22 = 1.0;
xf.eDx = 0.0;
xf.eDy = 0.0;
SetWorldTransform(dc.GetSafeHdc(), &xf);
dc.Rectangle(-50, 50, 50, -50); //这个矩形的中心在客户区中心,长度为100mm。不过由于设置了world空间,尽管从语句上来看是一个正方形,但是实际显示的却是一个锐角为45°的菱形。
dc.Ellipse(-50, 50, 50, -50); //尽管从语句上来看是一个圆,但是实际显示的却是一个椭圆。
}