MFC中的GDI绘图

 一.关于GDI的基本概念

什么是GDI?

Windows绘图的实质就是利用Windows提供的图形设备接口GDI(Graphics Device Interface)将图形绘制在显示器上。

 

在Windows操作系统中,动态链接库C:\WINDOWS\system32\gdi32.dll(GDI Client DLL)中定义了GDI函数,实现与设备无关的包括屏幕上输出像素、在打印机上输出硬拷贝以及绘制Windows用户界面功能。在Visual C++6.0中的头文件C:\Program Files\Microsoft Visual Studio\VC98\Include\wingdi.h和Visual Studio 2005中的头文件C:\Program Files\Microsoft Visual Studio 8\VC\PlatformSDK\Include\WinGDI.h是访问gdi32.dll库文件的钥匙。下面我们大致浏览一下wingdi.h(included in Windows.h)头文件:

 

/* Bitmap Header Definition */定义了BITMAP位图结构

 

/* Mapping Modes */定义了DC中的坐标映射方式,包括以下常用函数:

SetMapMode、SetViewportExtEx、SetViewportOrgEx、 SetWindowExtEx 、SetWindowOrgEx。

 

/* Stock Logical Objects */系统预定义的堆(STOCK)对象,包括BRUSH、PEN和FONT对象

 

/* Brush Styles */定义了画刷格式,包括SOLID、HOLLOW、HATCHED等格式

 

/* Hatch Styles */定义了画刷阴影格式,包括:

HS_VERTICAL    /* ||||| */

HS_FDIAGONAL /* \\\\\ */

HS_BDIAGONAL /* / */

HS_CROSS       /* +++++ */

HS_DIAGCROSS /* xxxxx */

 

/* Pen Styles */定义了画笔格式,包括SOLID、DASH、DOT等格式

什么是DC?

设备环境DC(Device Context),也称为设备描述表或设备上下文。

 

设备环境保存了绘图操作中一些共同需要设置的信息,如当前的画笔、画刷、字体和位图等图形对象及属性,以及坐标映射、颜色和背景等影响图形输出的绘图模式。形象的说,一个设备环境提供了一张画布和一些绘画的工具,我们可以使用不同格式、颜色的绘画工具在上面涂鸦。这里,设备环境中的“设备”是指任何类型的显示器或打印机等输出设备,绘图时,我们不必关心所使用设备的编程的原理和方法,所有的绘制操作必须通过设备环境进行间接的处理,Windows会自动将设备环境所描述的结构映射到相应的物理设备上。

 

从根本上来说,DC它是Windows内部使用的数据结构,它存储着向设备输出时说需要的信息,应用程序利用它定义图形对象及其属性,并实现应用程序、设备驱动程序和输出设备之间绘图命令的转换。要想调用GDI函数向某个区域输出文字或绘制图形,必须先取得或建立设备环境句柄,应用程序每一次绘图操作均按照设备环境中的设置的绘图属性进行。

 

设备环境不像其他Windows结构,在程序中不能直接存取设备环境结构,只能通过系统提供的一系列函数或使用设备环境的句柄HDC来间接地获取或设置设备环境结构中的各项属性,这些属性包括显示器高度和宽度、支持的颜色数和分辨率等。

MFC中与GDI有关的类

为了支持GDI绘图,MFC提供了两种重要的类:设备环境DC(Device Context)类,用于设置绘图属性和绘制图形;绘图对象类,封装了各种GDI绘图对象,包括画笔、刷子、字体、位图、调色板和区域。

 

在MFC中,CDC是设备环境类的基类,除了一般的窗口显示外,还用于基于桌面的全屏幕绘制和非屏幕显示的打印机输出。CDC类封装了所有图形输出函数,包括矢量、光栅和文本输出。CDC的派生类包括CClientDC、CPaintDC、WindowDC、CMetaFileDC。

  

 

  

(1)CPaintDC类是一个来自CDC的设备环境类。它在构造期间执行CWnd::BeginPaint,在析构期间执行CWnd::EndPaintEndPaint()除了释放设备环境外,还负责从消息队列中清除WM_PAINT消息。一个CPaintDC对象只在响应一个窗口重绘消息(WM_PAINT)的时候被使用,通常是在你的OnPaint消息处理成员函数中。因此,在处理窗口重画时,必须使用CPaintDC,否则WM_PAINT消息无法从消息队列中清除,将引起不断的窗口重画。

 

CPaintDC类成员:

 

数据成员

m_ps:包含了用于画客户区的PAINTSTRUCT 

m_hWnd: CPaintDC对象所附着的HWND 

 

构造函数CPaintDC:构造一个连接到指定的CWnd上的CPaintDC对象

 

(2)CClientDC(窗口客户区设备环境)类用于管理窗口用户区对应的显示上下文,它在构造时调用了Windows函数GetDC在析构时调用了ReleaseDC。这意味着和CClientDC对象相关的设备上下文是窗口的客户区。一般在响应非窗口重画消息(如键盘输入时绘制文本、鼠标绘图)绘图时要用到它。

 

CClientDC类的成员:

 

构造函数CClientDC:构造一个连接到CWnd上的CClientDC对象数据成员

数据成员m_hWnd:所在的有效窗口的HWND

 

(3)CWindowDC(窗口设备环境)类用于管理与整个窗口对应的显示上下文,包括它的结构和控件。它在构造的时候调用Windows函数GetWindowDC,在销毁的时候调用ReleaseDC这意味着CWindowDC对象可以访问CWnd的全部屏幕区域(包括客户区和非客户区)。它用于窗口(包括窗口边框、标题栏、控制按钮等)的绘制,除非要自己绘制窗口边框和按钮(如一些CD播放程序等),否则一般不用它。

 

 CWindowDC类成员:

 构造函数CWindowDC:构造一个CWindowDC对象

 数据成员 m_hWnd:与这个CWindowDC相关联的HWND句柄

 

(4)CMetaFileDC专门用于图元文件的绘制。图元文件记录一组GDI命令,可以通过这一组GDI命令重建图形输出。使用CMetaFileDC时,所有的图形输出命令会自动记录到一个与CMetaFileDC相关的图元文件中。

 

(5)此外我们还可以利用Windows内存DC进行绘图,此时涉及到屏幕DC和内存DC。把所要绘制的一切先在内存DC中进行绘制,之后全部搬到屏幕DC中,从而把所有繁琐的绘制过程都在内存DC中完成了,我们在屏幕上看到的是一幅完整的图画,所以不可能出现闪烁的情况。

 二.MFC中GDI绘图

    GDI绘图包括以下步骤:获取设备环境,设置坐标映射,创建绘图工具,调用DC绘图函数绘图。

1、获取设备环境

(1)在SDK编程中,获取设备环境的方法有两种:

 

<1>通过API函数BeginPaint。应用程序响应WM_PAINT消息进行图形刷新时主要通过BeginPaint函数获取设备环境,在消息处理函数返回前调用API函数EndPaint释放设备环境。

 

函数原型为:

WINUSERAPI HDC WINAPI BeginPaint( HWND hWnd,LPPAINTSTRUCT lpPaint);

 

    //以下为Win API示例::BeginPaint(HWND hWnd, LPPAINTSTRUCT lpPaint);

 

     case WM_PAINT://窗口客户区需要重绘

     {

         char szText[]="Hello World";

         PAINTSTRUCT ps;

         HDC hdc=::BeginPaint(hWnd,&ps);

         ::TextOut(hdc,10,10,szText,strlen(szText));

         ::EndPaint(hWnd,&ps);

         return 0;

}

 

MFC对BeginPaint进行了封装:

CDC* CWnd::BeginPaint(LPPAINTSTRUCT lpPaint);

等价于::BeginPaint(CWnd::m_hWnd, LPPAINTSTRUCT lpPaint);

 

<2>通过API函数GetDC。在非WM_PAINT消息处理函数中,需要调用GetDC来获取设备环境,调用API函数ReleaseDC来释放设备环境。

 

函数原型为:WINUSERAPI HDC WINAPI GetDC( HWND hWnd);

 

(2)在MFC中,MFC提供了不同类型的DC类,每一个类都封装了DC句柄,并且它们的构造函数自动调用获取DC的API函数,析构函数自动调用释放DC的API函数。因此,在程序中通过声明一个MFC设备环境类的对象就自动获取了一个DC,而当该对象被销毁时就自动释放了获取的DC。MFC AppWizard应用程序向导创建的OnDraw()函数自动支持所获取的DC。 

 

<1> CPaintDC构造函数:CPaintDC(CWnd* pWnd); 构造一个CPaintDC对象(pWnd指向一个CPaintDC对象所属的CWnd对象),准备用于绘画的应用程序窗口。

 

// BeginPaint

void CView::OnPaint()

{

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

         // TODO: Add your message handler code here

         OnPrepareDC(&dc);

         OnDraw(&dc)

}

当我们改变了窗口尺寸、移动窗口或恢复了先前被覆盖的部分,应用程序窗口就会收到一个Windows系统发送来的WM_PAINT消息,然后调用基类Cview的OnPaint函数或我们自己添加的消息处理函数OnPaint。我们可以在OnPaint函数中重绘窗口中重新可见的部分,但简单的处理办法是重绘整个窗口。上面的代码中,由于基类Cview的OnPaint函数调用了OnDraw虚函数,因此应用程序经常在OnDraw函数中绘制视图。

 

<2>CClientDC构造函数:CClientDC(CWnd* pWnd); 构造一个CClientDC对象,它将存取pWnd指向的CWnd的客户区。

 

// 鼠标左键事件处理

void CExView::OnLButtonDown(UINT nFlags, CPoint point)

{

          // TODO: 在此添加消息处理程序代码和/或调用默认值

          CClientDC dc(this);//定义客户区设备环境

          dc.LineTo(point);//绘制线段

}

CClientDC代表了窗口客户区对应的显示上下文,它在构造时调用了API函数GetDC,并将当前窗口的句柄m_hWnd作为函数参数;在析构时调用了API函数ReleaseDC。当在客户去绘图时,需要利用CClientDC类定义一个客户区设备环境句柄。

 

有时候需要访问与一个客户设备环境相关联的窗口对象,可以通过Attach函数把这个CClientDC的成员m_hWnd句柄传递给一个窗口对象,该窗口就是与客户区设备环境相关联的窗口。

 

CWnd::Attach,BOOL Attach( HWND hWndNew );

说明:将一个Windows窗口与CWnd对象相连接。

返回值:如果成功,则返回非零值;否则返回0。

参数:hWndNew指定了Windows窗口的句柄

 

<3>CWindowDC构造函数:CWindowDC( CWnd* pWnd );构造一个CWindowDC对象,它可以访问pWnd指向的CWnd对象的整个屏幕区域(包括客户区和非客户区)。比如我们在做屏幕保护程序时,一般以整个屏幕区域作为绘制区域。

2、设置坐标映射

(1)Windows坐标系统

 

Windows坐标系分为逻辑坐标系和设备坐标系两种,GDI支持这两种坐标系。一般而言,GDI的文本和图形输出函数使用逻辑坐标,而在客户区移动或按下鼠标的鼠标位置是采用设备坐标。

 

<1>逻辑坐标系是面向DC的坐标系,这种坐标不考虑具体的设备类型,在绘图时,Windows会根据当前设置的映射模式将逻辑坐标转换为设备坐标。

 

<2>设备坐标系是面向物理设备的坐标系,这种坐标以像素或设备所能表示的最小长度单位为单位,X轴方向向右,Y轴方向向下。设备坐标系的原点位置(0, 0)不限定在设备显示区域的左上角。

 

设备坐标系分为屏幕坐标系窗口坐标系客户区坐标系三种相互独立的坐标系。

 

屏幕坐标系以屏幕左上角为原点,一些与整个屏幕有关的函数均采用屏幕坐标,如GetCursorPos()、SetCursorPos()、CreateWindow()、MoveWindow()。弹出式菜单使用的也是屏幕坐标。

 

窗口坐标系以窗口左上角为坐标原点,它包括窗口标题栏、菜单栏和工具栏等范围。

 

客户区坐标系以窗口客户区左上角为原点,主要用于客户区的绘图输出和窗口消息的处理。鼠标消息的坐标参数使用客户区坐标,CDC类绘图成员函数使用与客户区坐标对应的逻辑坐标。

 

(2)坐标之间的相互转换

 

编程时,有时需要根据当前的具体情况进行三种设备坐标之间或与逻辑坐标的相互转换。

 

MFC提供了两个函数CDC::DPtoLP()CDC::LPtoDP()用于设备坐标与逻辑坐标之间的相互转换。

 

MFC提供了两个函数CWnd::ScreenToClient()CWnd::ClientToScreen()用于屏幕坐标与客户区坐标的相互转换。

 

(3)映射模式

 

映射模式确定了在绘制图形时所依据的坐标系,它定义了逻辑单位的实际大小、坐标增长方向,所有映射模式的坐标原点均在设备输出区域(如客户区或打印区)的左上角。此外,对于某些映射模式,用户还可以自定义窗口的长度和宽度,设置视图区的物理范围。

 

Windows定义了8种映射模式,见下表。

 

 

 

 

映射模式使得程序员可不必考虑输出设备的具体设备坐标系,而在一个统一的逻辑坐标系中进行图形的绘制。

 

当绘制的图形需要随着窗口的大小改变而自动改变的时候,一般选择MM_ISOTROPIC和MM_ANISOTROPIC映射方式。它们的唯一区别就是前者的X轴和Y轴的逻辑单位的大小是相同的,单词“isotropic”就是各个方向相等的意思,此映射方式适合绘制圆或正方形。而实际应用中,常常给X轴和Y轴取不同的比例,这时候选择MM_ANISOTROPIC映射方式。单词“anisotropic”就是各个方向相异的意思。

 

(4)自定义映射模式

 

“窗口”和“视口”的概念:

 

窗口(Window):对应逻辑坐标系上程序员设定的区域

 

视口(Viewport):对应实际输出设备上程序员设定的区域

 

窗口原点是指逻辑窗口坐标系的原点在视口(设备)坐标系中的位置,视口原点是指设备实际输出区域的原点。

 

除了映射模式,窗口和视口也是决定一个点的逻辑坐标如何转换为设备坐标的一个因素。一个点的逻辑坐标按照如下式子转换为设备坐标:

 

设备(视口)坐标 = 逻辑坐标 –窗口原点坐标 + 视口原点坐标

 

//定义坐标映射方式

WINGDIAPI int   WINAPI SetMapMode(HDC, int);

此API函数在MFC中封装为CDC::virtual int SetMapMode(int nMapMode);

 

//定义逻辑窗口区域,单位为逻辑单位(Logical)

WINGDIAPI BOOL WINAPI SetWindowExtEx (HDC, int, int, LPSIZE);

此API函数在MFC中封装为CDC::virtual CSize SetWindowExt(int cx, int cy);

 

//设置逻辑窗口的原点坐标,缺省原点为(0,0)。

WINGDIAPI BOOL WINAPI SetWindowOrgEx(HDC, int, int, LPPOINT);

此API函数在MFC中封装为CDC::CPoint SetWindowOrg(int x, int y);

 

注意:SetWindowOrg(Ex) 只有在映射模式为MM_ANISOTROPIC或MM_ISOTROPIC时才有意义。

 

//定义视口的坐标轴方向及区域、定义域和值域,单位为像素(Pixel)

WINGDIAPI BOOL WINAPI SetViewportExtEx(HDC, int, int, LPSIZE);

此API函数在MFC中封装为CDC::virtual CSize SetViewportExt(int cx, int cy);

 

注意:SetViewportExt(Ex) 只有在映射模式为MM_ANISOTROPIC或MM_ISOTROPIC时才有意义。

 

//设置视口的原点坐标,缺省原点为(0,0)。

WINGDIAPI BOOL WINAPI SetViewportOrgEx(HDC, int, int, LPPOINT);

此API函数在MFC中封装为CDC:: virtual CPoint SetViewportOrg(int x, int y);

 

参考:《GDI中的坐标映射问题》http://dev.csdn.net/article/12/12013.shtm

3、创建绘图工具并选入DC

有了画布,要绘图我们必须有画笔画刷。在Windows中有HPEN、HBRUSH等GDI对象,MFC对GDI对象进行了很好的封装,提供了封装GDI对象的类,如CPen、CBrush、CFont、CBitmap和CPalette等,这些类都是GDI对象类CGdiObject的派生类。

 

    一般先创建画笔(刷),然后调用CDC::SelectObject函数将画笔(刷)选入设备环境最为当前绘图工具,绘图完毕恢复设备环境以前的画笔(刷)对象,最后调用CGdiObject::DeleteObject函数删除画笔(刷)对象。

 

    这里需要注意的是,CGdiObject::DeleteObject函数彻底删除底层GDI对象(CPen和CBrush类的基类)。在MFC中,当对象销毁时会调用对象的析构函数自动删除对象,一般不必调用CGdiObject::DeleteObject删除GDI对象,因为如果设备环境还在使用一个GDI对象时,将引起应用程序崩溃或出现难以理解的运行错误。

 

   (1)创建画笔

BOOL CPen::CreatePen( int nPenStyle, int nWidth, COLORREF cfColor );

 

nPenStyle 指定画笔的风格。其可能取值的列表,请参见CPen构造函数中的nPenStyle参数。

 

nWidth 指定画笔的宽度。如果这个值为0,则不管是什么映射模式,以设备单位表示的宽度总是一个像素。

 

crColor 包含画笔的一个RGB颜色,为COLORREF结构。

 

此外,可通过CDC::SelectStockObject函数来调用系统预定义的库存笔对应的CGdiObject对象。

 

pOldPen = (Cpen*)pDC->SelectStockObject(BLACK_PEN);

 

(2)创建画刷

 

BOOL CBrush::CreateSolidBrush ( COLORREF crColor );

 

BOOL CBrush::CreateHatchBrush( int nIndex, COLORREF crColor );

 

参数: nIndex 指定画刷的阴影线风格。可取的值如下:

 

HS_HORIZONTAL   /* ==== */

 

HS_VERTICAL    /* ||||| */

 

HS_FDIAGONAL /* \\\\\ */

 

HS_BDIAGONAL /* / */

 

HS_CROSS       /* +++++ */

 

HS_DIAGCROSS /* xxxxx */

 

返回值:调用成功时返回非零值,否则为0。

 

此外,可通过CDC::SelectStockObject函数来调用系统预定义的库存画刷对应的CGdiObject对象。

 

pOldBrush = (CBrush*)pDC->SelectStockObject(BLACK_BRUSH);

 

(3)将画笔(刷)选入设备环境。

 

以下为MFC中默认映射方式下的GDI绘图的模块:

 

//先获取设备环境pDC

 

    CPen *pOldPen,newPen;

 

    CBrush *pOldBrush,newBrush1,newBrush2;

 

    //创建宽度为pixel的白色实线画笔

 

    newPen.CreatePen(PS_SOLID,1,RGB(0,0,0));

 

    //创建红色实线画刷

 

    newBrush1.CreateSolidBrush(RGB(255,0,0));

 

    //创建红色实线度的向下(从右到左)影线的阴影画刷

 

    newBrush2.CreateHatchBrush(HS_BDIAGONAL,RGB(255,0,0));

 

    //将newPen画笔和newBrush1画刷对象选入设备环境

 

    pOldPen = pDC->SelectObject(&newPen);

 

    pOldBrush = pDC->SelectObject(&newBrush1);

 

    //调用DC绘图函数绘图

 

    //……

 

    //绘图完毕,恢复原来画笔、画刷

 

    pDC->SelectObject(pOldPen);

 

pDC->SelectObject(pOldBrush);

 

//删除创建的画笔、画刷

 

// newPen.DeleteObject();

 

// newBrush1.DeleteObject();

 

// newBrush2.DeleteObject();

 

   (4)当绘制文本Text时,一般可以通过调用CDC::SetBkColor函数来设置背景颜色,调用CDC::SetTextColor函数来设置文字颜色,调用CDC::SetTextAlign函数设置文本对齐标记。

4、调用DC绘图函数绘图

GDI为提供了绘制基本图形的成员函数,在MFC中这些函数封装在CDC类中。

 

注意:绘图函数使用的坐标都是逻辑坐标。

 

常用CDC绘图函数

三.坐标映射实例

(1)建立单文档MFC项目Draw:New—>Projects—>MFC AppWizard(EXE)—>Single Document。

 

(2)找到CMainFrame::PreCreateWindow函数,在其中设置默认窗口大小为400 pixel*300 pixel。

 

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

 

{

    if( !CFrameWnd::PreCreateWindow(cs) )

        return FALSE;

 

    // TODO: Modify the Window class or styles here by modifying

    // the CREATESTRUCT cs

    cs.cx=400;

    cs.cy=300;

 

    return TRUE;

}

(3)添加OnPaint事件

 

资源管理器—>ClassView—>右击CDrawView 选择Add Windows Message Handler

 

—>WM_PAINT—> Add Handler

 

     void CDrawView::OnPaint()

     {

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

          // TODO: Add your message handler code here

          CRect cr;//矩形结构

          GetClientRect(&cr);//获得客户区窗口 

          int cx=cr.right;//右

            int cy=cr.bottom;//底

          dc.SetMapMode(MM_ISOTROPIC);//X=Y  

       dc.SetWindowExt(1000,1000);//设置逻辑窗口,默认窗口原点为(0,0)

       dc.SetViewportExt(cx,-cy);//定义输出视口,X右Y上为正

 

       dc.SetViewportOrg(cx/2,cy/2);//定义视口原点为客户区中心

       dc.Ellipse(-200,200,200,-200);//绘制椭圆与客户区外切的椭圆

       //绘制水平垂直的四条半径

       dc.MoveTo(0,0);     dc.LineTo(200,0);

       dc.MoveTo(0,0);     dc.LineTo(-200,0);

       dc.MoveTo(0,0);     dc.LineTo(0,200);

       dc.MoveTo(0,0);     dc.LineTo(0,-200);

       //执行F5进行Debug,在底端Output窗口中可以观察ClientRect

      TRACE( "ClientRect.x = %d, ClientRect.y = %d\n", cx, cy );

 }

 

运行结果如图1左。当改变窗口大小时,图中圆形状始终不变。

 

<1>将上面代码的第9行改为:dc.SetMapMode(MM_ANISOTROPIC);//X!=Y运行结果如图1右。

 

 

                       图1

我们发现,尽管上面代码的第13行dc.Ellipse(-200,200,200,-200);中定义的椭圆外接矩形逻辑上为正方形,但是显示的并不是圆,而是椭圆。

当我们改变窗口大小时,图中椭圆变形,甚至可能变为圆形。具体为:

保持窗口宽度不变时,减小高度,椭圆变得更扁;保持窗口高度不变时,减小宽度,椭圆变得更圆,当拉伸到客户区为正方形时,我们发现椭圆变成了圆!

<2>将上面代码的第9行改回dc.SetMapMode(MM_ISOTROPIC);//X=Y,第15行改为dc.LineTo(500,0); 第18行改为dc.LineTo(0,-500); 运行结果如图2左。

 

保持窗口高度不变,减小窗口宽度,使窗口宽度<窗口高度,运行结果如图2右。

 

          图2

<3>在将<2>中代码的第9行改回dc.SetMapMode(MM_ANISOTROPIC);//X!=Y,

 

运行结果如图3:

 

                            图3

当我们改变窗口大小时,dc.LineTo(500,0); dc.LineTo(0,-500);都是由原点(客户区中心)到客户区右端中心、底端中心的直线。

<4>将原代码中第10行dc.SetWindowExt(1000,1000);//设置逻辑窗口后添加dc.SetWindowOrg(100,100);设置逻辑窗口的原点为(100,100)。观察运行结果可知,图1中的图形整体向左向下分别移动了100个逻辑单位:

(-200,200,200,-200)——>(-200-100,200-100,200-100,-200-100)

 

若需要保持图1中的图形,则需要将涉及到的每个点加上(100,100),即:

 

     dc.Ellipse(-200+100,200+100,200+100,-200+100);

     //绘制水平垂直的四条半径

     dc.MoveTo(0+100,0+100);     dc.LineTo(200+100,0+100);

     dc.MoveTo(0+100,0+100);     dc.LineTo(-200+100,0+100);

     dc.MoveTo(0+100,0+100);     dc.LineTo(0+100,200+100);

     dc.MoveTo(0+100,0+100);     dc.LineTo(0+100,-200+100)

 

<1>逻辑窗口原点映射为视口原点

<2>逻辑窗口宽度和高度映射为视口宽度和高度

<3>当映射方式为MM_ISOTROPIC时,WindowExt.Width=WindowExt.Height,有效绘图区域为以视口宽高中的最小边为边长的正方形区域。比例因子为:

 

scaleX=scaleY=min{ViewportExt.Width, ViewportExt.Height }/WindowExt.Width

当映射方式为MM_ANISOTROPIC时,有效绘图区域为整个视口(这里为客户区)。比例因子为:

 

scaleX=ViewportExt.Width/WindowExt.Width

scaleY=ViewportExt.Height /WindowExt.Height。见图4.

<4>设备(视口)坐标 = (逻辑坐标–逻辑窗口原点坐标)×比例因子+视口原点坐标

 

             图4

以下分析中客户区大小为ClientRect=(388,200),逻辑窗口原点为WindowOrg=(100,100),基于(3)<4>中修改后的代码。

在上图4左中,nMapMode=MM_ISOTROPIC,椭圆外接矩形左上角逻辑坐标(-100,300)映射为客户区的以Pixel为单位的坐标为:

left_top_X= (-100-100)×(200/1000)+388/2=154 pixel

left_top_Y= (300-100)×(200/1000)+200/2=140 pixel

 

依此次方法可计算出右下角逻辑坐标(300,-100)映射为客户区的以Pixel为单位的坐标为:

right_bottom_X=234 pixel;right_bottom_Y=60 pixel

 

我们若在第9行dc.SetMapMode(MM_ISOTROPIC);//X=Y前添加CreatePen(PS_SOLID,2,RGB(255,0,0));dc.Ellipse(154,140,234,60);则可以发现,这个以2个像素宽的红色画笔绘制的(椭)圆刚好和设置映射模式后绘制的(椭)圆重合。但是我们改变窗口大小时,发现设置映射模式后绘制的(椭)圆按比例拉伸,但红色圆始终在原地且大小保持不变,这也说明了默认映射方式MM_TEXT是以X轴正方向朝右,Y轴正方向朝下的坐标系和1 pixel为单位进行绘制的。

 

同理,我们可以分析上图4右中,nMapMode=MM_ANISOTROPIC的情况下,CRect(116,140,272,60);为等效椭圆外接矩形。

 

(4)总结逻辑窗口坐标到设备视口坐标的映射方法:(#add 缺失,待补)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值