基于Visual C++的GDI常用坐标系统及应用(一)
在Windows应用程序中,只要进行绘图,就要使用GDI坐标系统。Windows提供了几种映射方式,每一种映射都对应着一种坐标系。例如,绘制图形时,必须给出图形各个点在客户区的位置,其位置用x 和y两个坐标表示,x 表示横坐标,y表示纵坐标。在所有的GDI绘制函数中,这些坐标使用的是一种“逻辑单位”。当GDI函数将结果输出送到某个物理设备上时,Windows将逻辑坐标转换成设备坐标(如屏幕或打印机的像素点)。本文讨论了图形环境中的各个映射模式,包括它们是什么,怎么工作的,以及它们真正的含义。
一、基础知识
(一)逻辑坐标。逻 辑坐标与设备无关,缺省地,一个逻辑单位等于设备中的一个象素。它是实现“所见即所得”的基础。例如,当程序员调用LineTo函数绘制25.4mm(1 英 寸) 长的直线时,他只要使用合适的映射模式,那么就并不需要考虑输出的是何种设备。若设备是VGA显示器,Windows自动将其转化为96个像素点;若设备 是一个300dpi的激光打印机,Windows自动将其转化为300 个像素点。
(二)设备坐标。图形输出时,Windows将GDI函数中指定的逻辑坐标映射为设备坐标,在所有的设备坐标系统中,单位以像素点为准,水平值从左到右增大(正方向向右),垂直值从上到下增大(正方向向下)。Windows中包括以下3 种设备坐标,以满足各种不同需要:
1、客户区域坐标,包括应用程序的客户区域,客户区域的左上角为(0, 0)。GetClientRect取得窗口客户区(不包括非客户区)在客户区坐标系下的RECT坐标,可以得到窗口的大小,而不能得到相对屏幕的位置,因为这个矩阵是在客户区坐标系下(相对于窗口客户区的左上角)的
2、屏幕坐标,包括整个屏幕,屏幕的左上角为(0, 0)。屏幕坐标用在WM_MOVE消息中(对于非子窗口)以及下面的Windows 函数中:CreateWindow 和MoveWindow(都对于非子窗口)、GetMessage、GetCursorPos、GetWindowRect、WindowFromPoint 和SetBrushOrg 中。 用函数ClientToScreen 和ScreenToClient可以将客户区域坐标转换成屏幕区域坐标,或反之。
3、全窗口坐标,包括一个程序的整个窗口,包括标题条、菜单、滚动条和窗口框,窗口的左上角为(0,0)。使用GetWindowDC得到的窗口设备环境,可以将逻辑单位转换成窗口”坐标。
(三)映射。映射方式定义了Windows如何将GDI函数中指定的逻辑坐标映射为设备坐标。在下文中我们将介绍常用的映射方式。
此外,习惯上,我们将逻辑坐标所在的坐标系称为“窗口”;将设备坐标所在的坐标系称为“视口”。“窗口”依赖于逻辑坐标,可以是像素点、毫米或其他尺度。这一点请牢记,这对于下面的有关内容的理解至关重要。
二、默认的坐标系统
当在微软的窗口中进行绘图时,绘图的坐标原点在屏幕的左上角,任何物体在屏幕上定位都要参考这个坐标原点。在笛卡尔坐标系统中这个点被定义为坐标原点(0,0),水平坐标轴的正方向是从该点出发向右延伸,垂直坐标轴的正方向是从该点出发向下延伸。
图一、笛卡尔坐标系
这个坐标原点只是操作系统默认的坐标原点,所以如果你调用Ellipse(-100, -100, 100, 100)函数来绘制图形的话,你将得到一个圆,它的圆心位于屏幕的左上角,仅仅只有圆的四分之一部分(270度到360度的部分)显示在屏幕上。代码及效 果图如下
- // CGraphicView drawing
- void CGraphicView::OnDraw(CDC* pDC)
- {
- CGraphicDoc* pDoc = GetDocument();
- ASSERT_VALID(pDoc);
- if (!pDoc)
- return;
- // TODO: add draw code for native data here
- CPen PenBlue;
- PenBlue.CreatePen(PS_SOLID,1,RGB(0,12,255));
- pDC->SelectObject(&PenBlue);
- pDC->Ellipse(-100,-100,100,100);
- }
图二、代码效果图
按照同样的原理,你可以使用CPaintDC的方法或按照你的要求创建函数来绘制任何几何或非几何图形。例如,下面的代码绘制了两条相互垂直的直线,垂点位与窗口的中心:
- // CGraphicView drawing
- void CGraphicView::OnDraw(CDC* pDC)
- {
- CGraphicDoc* pDoc = GetDocument();
- ASSERT_VALID(pDoc);
- if (!pDoc)
- return;
- // TODO: add draw code for native data here
- CPen PenBlue;
- PenBlue.CreatePen(PS_SOLID, 1, RGB(0, 12, 255));
- pDC->SelectObject(&PenBlue);
- pDC->Ellipse(-100, -100, 100, 100);
- CPen PenBlack;
- PenBlack.CreatePen(PS_SOLID, 1, BLACK_PEN);
- pDC->SelectObject(&PenBlack);
- CRect Recto;
- // retrieve the size of the drawing area
- GetClientRect(&Recto);
- pDC->MoveTo(Recto.Width() / 2, 0);
- pDC->LineTo(Recto.Width() / 2, Recto.Height());
- pDC->MoveTo(0, Recto.Height() / 2);
- pDC->LineTo(Recto.Width(), Recto.Height() / 2);
- }
图三、代码效果图
三、更改坐标系统
正如上面所看到的,默认的坐标系统坐标原点位于窗口的左上角,水平轴的正方向向右,垂直轴的正方向向下。为了进一步说明这一点,让我们来绘制一个半径为50个单位,圆心位于(0,0)点,同时绘制一个连接(0,0)(100,100)两点的直线。
- // CGraphicView drawing
- void CGraphicView::OnDraw(CDC* pDC)
- {
- CGraphicDoc* pDoc = GetDocument();
- ASSERT_VALID(pDoc);
- if (!pDoc)
- return;
- // A circle whose center is at the origin (0, 0)
- pDC->Ellipse(-50, -50, 50, 50);
- // A line that starts at (0, 0) and ends at (100, 100)
- pDC->MoveTo(0, 0);
- pDC->LineTo(100, 100);
- }
图四、代码效果图
这种默认的坐标原点在大多数图形操作情况下是适用的,但并不是总适用,有时你需要控制坐标系统的原点,例如,很多CAD(图形辅助设计)应用程序就需要用户来定义坐标系统的原点。
MFC提供了各种函数来处理坐标定位及扩展绘制区域的问题,包括在屏幕上任意位置设置坐标原点的函数。因为你是在一个设备上下文上进行绘图操作,因此,你所需要做的就是调用CDC::SetViewportOrg()函数。这个函数重载了两个版本,这允许你使用X、Y坐标或是一个定义的Point点。这个函数的语法如下:
- SetViewportOrg(int X, int Y);
- SetViewportOrg(CPoint Pt);
用这个函数时只需要简单地说明哪儿是你想定义的坐标原点,如果使用函数的第二个版本,参数可以是一个POINT结构或是一个MFC提供的CPoint类。为了演示这个函数的效果,让我们将上例的坐标原点沿X轴正方向移动400个单位,Y轴正方向移动150个单位(相对屏幕坐标原点),这时绘制函数如下:
- void CGraphicView::OnDraw(CDC* pDC)
- {
- CGraphicDoc* pDoc = GetDocument();
- ASSERT_VALID(pDoc);
- if (!pDoc)
- return;
- pDC->SetViewportOrg(400, 150);
- // A circle whose center is at the origin (0, 0)
- pDC->Ellipse(-50, -50, 50, 50);
- // A line that starts at (0, 0) and ends at (100, 100)
- pDC->MoveTo(0, 0);
- pDC->LineTo(100, 100);
- }
图五、代码效果图
需要注意的是,你也可以相对于客户区域来指定坐标原点
- void CGraphicView::OnDraw(CDC* pDC)
- {
- CGraphicDoc* pDoc = GetDocument();
- ASSERT_VALID(pDoc);
- if (!pDoc)
- return;
- CRect Recto;
- // Retrieve the size of the drawing area
- GetClientRect(&Recto);
- pDC->SetViewportOrg(Recto.Width() / 2, Recto.Height() / 2);
- // A circle whose center is at the origin (0, 0)
- pDC->Ellipse(-50, -50, 50, 50);
- // A line that starts at (0, 0) and ends at (100, 100)
- pDC->MoveTo(0, 0);
- pDC->LineTo(100, 100);
- }

问题:如何进行区分以上两种设置坐标原点的方法?相同的函数,传入的参数的类型也一致啊?
图六、代码效果图
现在你已了解了如何设置坐标原点,让我们来将(380,220)点作为坐标原点,并绘制出笛卡尔的坐标轴:
- void CGraphicView::OnDraw(CDC* pDC)
- {
- CGraphicDoc* pDoc = GetDocument();
- ASSERT_VALID(pDoc);
- if (!pDoc)
- return;
- pDC->SetViewportOrg(380, 220);
- // Use a red pen
- CPen PenRed(PS_SOLID, 3, RGB(255, 0, 0));
- pDC->SelectObject(PenRed);
- // A circle whose center is at the origin (0, 0)
- pDC->Ellipse(-100, -100, 100, 100);
- // Use a blue pen
- CPen PenBlue(PS_SOLID, 1, RGB(0, 0, 255));
- pDC->SelectObject(PenBlue);
- // Horizontal axis
- pDC->MoveTo(-380, 0);
- pDC->LineTo(380, 0);
- // Vertical axis
- pDC->MoveTo(0, -220);
- pDC->LineTo(0, 220);
- }
图七、代码效果图
正如已经看到的,SetViewportOrg()函数可以更改设备上下文的坐标原点,同时,它也用来规定坐标轴的正方向,即水平轴向右,垂直轴向下:
为了说明这一点,下面来绘制一条黄色的45度角的直线:
- void CGraphicView::OnDraw(CDC* pDC)
- {
- CGraphicDoc* pDoc = GetDocument();
- ASSERT_VALID(pDoc);
- if (!pDoc)
- return;
- pDC->SetViewportOrg(380, 220);
- // Use a red pen
- CPen PenRed(PS_SOLID, 3, RGB(255, 0, 0));
- pDC->SelectObject(PenRed);
- // A circle whose center is at the origin (0, 0)
- pDC->Ellipse(-100, -100, 100, 100);
- // Use a blue pen
- CPen PenBlue(PS_SOLID, 1, RGB(0, 0, 255));
- pDC->SelectObject(PenBlue);
- // Horizontal axis
- pDC->MoveTo(-380, 0);
- pDC->LineTo(380, 0);
- // Vertical axis
- pDC->MoveTo(0, -220);
- pDC->LineTo(0, 220);
- //**************************************************
- // An orange pen
- CPen PenOrange(PS_SOLID, 5, RGB(255, 128, 0));
- pDC->SelectObject(PenOrange);
- // A diagonal line at 45 degrees
- pDC->MoveTo(0, 0);
- pDC->LineTo(120, 120);
- //**************************************************
- }
图九、代码效果图
正如你所看到的,我们的直线没有在45度位置,而是位于坐标系统的第四象限,造成这种情况的原因是默认的坐标系统。
关于OnDraw的解释如下:CView类及其派生类处理WM_PAINT的消息时候对应的消息处理函数是OnPaint()
只 是把绘图部分剥离出来了,放在OnDraw中处理(You must override this function to display your view of the document)。当然也可以重载OnPaint()函数,这样框架就不会自动调用OnDraw函数。下面摘自MFC源码的OnPaint()函数的定 义部分:
- /////////////////////////////////////////////////////////////////////////////
- // CView drawing support
- void CView::OnPaint()
- {
- // standard paint routine
- CPaintDC dc(this);
- OnPrepareDC(&dc);
- OnDraw(&dc);
- }
- //CView头文件中的函数声明部分
- .....
- afx_msg void OnPaint();
- virtual void OnDraw(CDC* pDC) = 0;
- ......
三、固定映射模式
为了控制设备上下文中的坐标轴的方向,可以使用CDC类的SetMapMode()函数,它的语法如下:
- int SetMapMode(int nMapMode);
这个函数将根据参数的设置的不同做两件事
一是控制坐标轴的方向;
二是坐标系统的单位长度。
这个函数的参数是用来定义映射模式的整型常量。它可能的值是:MM_TEXT, MM_LOENGLISH、MM_HIENGLISH、MM_ANISOTROPIC、MM_HIMETRIC, MM_ISOTROPIC、 MM_LOMETRIC, MM_TWIPS。
默认情况下使用MM_TEXT映射模式(就是我们常见的阅读模式,从左到右,从上到下)。换 句话说,如果你没有具体的规定某一映射模式,你的应用程序就将使用MM_TEXT映射模式。在这种映射模式下,设备上下文中的度量尺寸将使用默认的像素单 位,水平坐标轴正方向向右,垂直坐标轴正方向向下。例如,上面的OnPaint事件可以用下面的代码重写,它将产生同样的效果,仿佛没有使用映射模式。
- void CGraphicView::OnDraw(CDC* pDC)
- {
- CGraphicDoc* pDoc = GetDocument();
- ASSERT_VALID(pDoc);
- if (!pDoc)
- return;
- //******************************************
- pDC->SetMapMode(MM_TEXT); //默认
- //******************************************
- pDC->SetViewportOrg(380, 220);
- // Use a red pen
- CPen PenRed(PS_SOLID, 3, RGB(255, 0, 0));
- pDC->SelectObject(PenRed);
- // A circle whose center is at the origin (0, 0)
- pDC->Ellipse(-100, -100, 100, 100);
- // Use a blue pen
- CPen PenBlue(PS_SOLID, 1, RGB(0, 0, 255));
- pDC->SelectObject(PenBlue);
- // Horizontal axis
- pDC->MoveTo(-380, 0);
- pDC->LineTo(380, 0);
- // Vertical axis
- pDC->MoveTo(0, -220);
- pDC->LineTo(0, 220);
- //**************************************************
- // An orange pen
- CPen PenOrange(PS_SOLID, 5, RGB(255, 128, 0));
- pDC->SelectObject(PenOrange);
- // A diagonal line at 45 degrees
- pDC->MoveTo(0, 0);
- pDC->LineTo(120, 120);
- //**************************************************
- }
图十、代码效果图
MM_LOENGLISH模式,(METRIC是公制,ENGLISH是英制)与其他一些映射模式(不包括MM_TEXT模式)一样,执行两个动作,它改变坐标轴的方向,垂直坐标轴的正方向向上;
图十一、MM_LOENGLISH映射模式下的坐标系
此外,度量单位改为0.01英寸,这意味着你提供的坐标将除以100,观察上述代码的MM_LOENGLISH映射效果
- void CGraphicView::OnDraw(CDC* pDC)
- {
- CGraphicDoc* pDoc = GetDocument();
- ASSERT_VALID(pDoc);
- if (!pDoc)
- return;
- //******************************************
- pDC->SetMapMode(MM_LOENGLISH);
- //******************************************
- pDC->SetViewportOrg(380, 220);
- ......................
- }
图十二、代码效果图
正如你所看到的,直线现在位于坐标系的第一象限,同时,直线比以前缩短,圆也比以前的要小。
与MM_LOENGLISH映射模式相似,MM_HIENGLISH映射模式也是垂直坐标轴正向向上,只是它以0.001英寸为坐标单位,下面是它的效果:
- void CGraphicView::OnDraw(CDC* pDC)
- {
- CGraphicDoc* pDoc = GetDocument();
- ASSERT_VALID(pDoc);
- if (!pDoc)
- return;
- //******************************************
- pDC->SetMapMode(MM_HIENGLISH);
- //******************************************
- pDC->SetViewportOrg(380, 220);
- ...................
图十四、代码效果图
MM_LOMETRIC映射模式使用与上两种映射模式相同的坐标轴,不同的是MM_LOMETRIC使用0.1毫米为单位,下面是一个例子:
图十五、代码效果图
MM_HIMETRIC使用与上述三种映射模式相同的坐标系,但它的坐标单位是0.01毫米,下面例子:
图十六、代码效果图
MM_TWIPS映射模式将每个逻辑单位(像素)除以20,实际上一twip等于1/1440 英寸,坐标系统仍然与上面几种映射方式相同
图十七、代码效果图
总结这些映射模式:
MM_ANISOTROPIC:逻辑单位转换成具有任意比例轴的任意单位,用SetWindowExt和SetViewportExt函数可指定单位、方向和比例。
MM_HIENGLISH:每个逻辑单位转换为0.001英寸,X的正方向向右,Y的正方向向上。
MM_HIMETRIC:每个逻辑单位转换为0.01毫米,X正方向向右,Y的正方向向上。
MM_ISOTROPIC: 逻辑单位转换成具有均等比例轴的任意单位,即沿X轴的一个单位等于沿Y轴的一个单位,用SetWindowExt和SetViewportExt函数可以 指定该轴的单位和方向。图形设备界面(GDI)需要进行调整,以保证X和Y的单位保持相同大小(当设置窗口范围时,视口将被调整以达到单位大小相同)。
MM_LOENGLISH:每个逻辑单位转换为0.01英寸,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_ANISOTROPIC方式允许对X和Y坐标分别进行调整。
















浙公网安备 33010602011771号