1 、屏幕坐标
屏幕坐标描述物理设备(显示器、打印机等)的一种坐标体系,坐标原点在屏幕的左上角, X 轴向右为正, Y 轴向下为正。度量单位是象素。原点、坐标轴方向、度量单位都是不能够改变的。
2 、设备坐标(又称物理坐标)
设备坐标是描述在屏幕和打印机显示或打印的窗体的一种坐标体系。默认的坐标原点是在其客户区的左上角。 X 轴向右为正, Y 轴向下为正。度量单位为象素。原点和坐标轴方向可以改变,但是度量单位不可以改变。
3 、逻辑坐标
逻辑坐标是在程序中控制显示,打印使用的坐标体系。该坐标系与定义的映射模式密切相关。默认的映射模式是 MM_TEXT 。我们可以通过设置不同的映射模式来改变该坐标体系的默认行为。
+--------------+ +-----------------+ +-----------------+ +-----------------------+
| 绘图代码 | | (窗口) | | (视口) | | (屏幕/打印机等) |
| 世界坐标系|---〉 | 页面坐标系 | -----〉| 设备坐标系 | ----〉| 物理坐标系 |
| | | | | | | |
+---------------+ +----------------+ +------------------+ +------------------------+
举个例子说,如果我们在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)点,这取决于两个坐标系之间的变换矩阵。
同样道理,对于上述四个坐标系,当系统从一个坐标系中复制指定矩形区域内的某个点到下一个坐标系时,它使用这两个坐标系之间的变换算法,根据点的原坐标计算得到点的像坐标。因此,一个图形在不同坐标系下,其尺寸、方向和形状都可能不同。注意一点,虽然这个变换是两个坐标系之间的,是针对物体整体而言的变换,但在系统在操作的时候,是逐点、逐行操作的。
注意:
1. CDC的所有成员函数都以逻辑坐标作为参数。
2. CWnd的成员函数都以设备坐标作为参数。
3. 所有点中测试之类的函数都应该考虑设备坐标,区域的定义应采用设备坐标。某些像CRect::PtInRect之类的函数只有在采用设备坐标参数时才会保证有正确的结果。
4. 将一切需要长期使用的值用逻辑坐标或物理坐标保存。
窗口原点与视口原点始终是重合的,我们用GDI画图的时候,代码采用的是逻辑坐标,也就是在页面坐标系中画图,画图所有的坐标值还是以页面坐标系为准的,整个过程并没有改变坐标系统。比如pDC->Rectangle(100,100,300,300),逻辑坐标原点还是在O。
在VC中鼠标坐标的坐标位置用设备坐标表示,但所有GDI绘图都用逻辑坐标表示,所以用鼠标绘图时,那么必须将设备坐标转换为逻辑坐标,可以使用CDC 函数DptoLP()将设备坐标转化为逻辑坐标,同样可以用LptoDP()将逻辑坐标转化为设备坐标。
GDI函数大致可分类为:
l 设备上下文函数(如GetDC、CreateDC、DeleteDC)
l 画线函数(如LineTo、Polyline、Arc)
l 填充画图函数(如Ellipse、FillRect、Pie)
l 画图属性函数(如SetBkColor、SetBkMode、SetTextColor)
l 文本、字体函数(如TextOut、GetFontData)
l 位图函数(如SetPixel、BitBlt、StretchBlt)
l 坐标函数(如DPtoLP、LPtoDP、ScreenToClient、ClientToScreen)
l 映射函数(如SetMapMode、SetWindowExtEx、SetViewportExtEx)
l 元文件函数(如PlayMetaFile、SetWinMetaFileBits)
l 区域函数(如FillRgn、FrameRgn、InvertRgn)
l 路径函数(如BeginPath、EndPath、StrokeAndFillPath)
l 裁剪函数(如SelectClipRgn、SelectClipPath)等。
在一个对话框客户区画Sin(X)的图象
void CDrawDlg::OnDrawSina()
{
CClientDC dc(this);
CRect rect;
GetClientRect(&rect);
dc.SetMapMode(MM_ISOTROPIC);
//设置窗口原点,设置窗口的坐标值是逻辑坐标,它是相对于页面坐标系页言的。
dc.SetWindowOrg(0, 0);
//设置窗口大小
dc.SetWindowExt(rect.right, rect.bottom);
//设置视口原点,坐标的值是逻辑坐标,它是相对于设备坐标系而言的。
dc.SetViewportOrg(0, rect.bottom / 2);
//设置视口大小,窗口与视口的都与客户区一样大。注意在设置视口的时候,由于采用的是MM_ISOROPIC映射模式,Y轴向上为正,所以这里设置视口是第二个参数为负。否则画出来的曲线就是相反的sin(x)曲线,视口大小的值是可以改变的,它直接影响到绘出来的图形的大小。
dc.SetViewportExt(rect.right, - rect.bottom);
//创建绘制正旋曲线的pen并将其选入设备上下文
CPen pen(PS_SOLID, 1, RGB(255, 0, 0));
HGDIOBJ oldObject = dc.SelectObject(pen.GetSafeHandle());
//绘制正旋曲线
dc.MoveTo(0, 0);
for (int i = 0; i < rect.right; i++)
{
dc.LineTo(i, 10*sin(i*1.0/PI));
}
//创建绘制x轴的pen并将其选入设备上下文
CPen penx(PS_SOLID, 1, RGB(0, 0, 255));
dc.SelectObject(penx.GetSafeHandle());
//绘制X轴
dc.MoveTo(0, 0);
dc.LineTo(rect.right, 0);
/ /恢复原先的pen
dc.SelectObject(oldObject);
图:在MM_ISOTROPIC映射模式下的坐标变换
上面的代码采用的是MM_ISOTROPIC映射,Y轴向上为正,定义窗口原点为(0,0),大小为客户区大小。在视口设置的时候,将视口原点放在客户区中间的左边点。由于设置视口的时候,将第二个参数设为负值,说明视口方向是向上的,这样如图中绿线所示的正弦曲线映射的时候就不会变成相反的图形。可见范围为粗线部分。
采用其它映射,Y轴向上为正的都与此类似,不同的只是显示的图形的比例大小
转自:http://www.51r.com/user2/chbq/archives/2006/257972.shtml
一、逻辑空间的坐标是如何转化为设备空间的坐标的?
让我们先来澄清逻辑坐标空间内部是如何转化的。
世界坐标空间到页面坐标空间的变换(二维affine(注三)变换):
此过程中涉及到的各种变换,比如:相等、平移、缩放、映像、旋转、剪切、合并等都 是通过为affine矩阵的各个成员指定适当的值来实现的。
这个矩阵所对应的结构如下:
typedef struct _XFORM {
FLOAT eM11;
FLOAT eM12;
FLOAT eM21;
FLOAT eM22;
FLOAT eDx;
FLOAT eDy;
} XFORM, *PXFORM;
一个世界坐标空间的点转换为页面坐标空间的点的公式为:
xpage=xworld*eM11+yworld*eM21+eDx;
ypage=xworld*eM12+yworld*eM22+eDy;(公式一)
其中xworld、yworld为世界坐标空间的点。xpage、ypage为上述点在页面坐标空间中 对 应的位置。至于与affine变换的数学属性及如何才能实现页面的相等、平移、缩放、映 像、旋转、剪切、合并此处不进行详细说明,因为那样将使这篇文章的规模膨胀许多(注四)。
页面坐标空间到设备坐标空间的转换:
这个过程涉及到几个概念,他们分别是:
视口原点:当前页面坐标空间所认为的设备坐标空间的原点位置。用SetViewportOrg Ex、GetViewportOrgEx分别进行设置和读取。用这两个函数进行操作时,所涉及的坐标为设备空间的坐标。
视口范围: 视口范围并不是一个绝对的用于表示设备坐标空间大小的值。而是一个 相 对值,它同窗口范围的比例最终决定页面坐标空间到设备坐标空间是一种缩小还是放大的转换。用SetViewportExtEx、GetViewportExtEx对视口范围进行存取。
窗口原点:页面坐标空间的原点。用SetWindowOrgEx、GetWindowOrgEx对窗口原点进行存取,所涉及的坐标为逻辑坐标。
窗口范围:见视口范围的说明。用SetWindowExtEx、GetWindowExtEx进行存取。
由了两个坐标空间的原点值和范围的比例值,在这两个坐标空间间进行坐标转换也就不 是什么太难的事了。比较容易的可以得出下面的公式:
页面坐标空间到设备坐标空间:
xdevice=(xpage-WOrgx)*VExtx /WExtx+VOrgx;
ydevice=(ypage-WOrgy)*VExty /WExty+VOrgy;(公二) 其中(WOrgx,WOrgy)为窗口原点。(VOrgx VOrgy,)为视口原点。(WExtx, WExt y)为窗口范围。(VExtx,VExty)为视口范围。
设备坐标空间到页面坐标空间的转换大家可以自己推导。为了更好的理解坐标空间的转换,我们将利用上述两组公式,动手来实现自己的LPtoDP。我们的这个函数将只适合nt类的平台。(9x没有世界坐标空间,会更简单)具体实现见源码2。实现MyLPtoDP的过程比较简单,此处仅对要用到的几个主要函数做些说明。
int GetGraphicsMode(
HDC hdc // handle to device context
);
这个函数用来得到指定DC的图形模式。图形模式有两种GM_COMPATIBLE和GM_ADVANCED
只有在GM_ADVANCED才可能使用世界坐标空间。可以用SetGraphicsMode在两者间切 换。
BOOL GetWorldTransform(
HDC hdc, // handle to device context
LPXFORM lpXform // transformation
);
此函数用来得到与当前DC相关联的affine矩阵。通过公式一,
应该可以知道缺省的affine矩阵具有{1.0,0,0,1.0,0,0}的形式。
MyLPtoDP虽然有返回值但此值无意义,并且实现过程中也并没有进行任何出错处理。