提高图形高效绘图机制的方法--旧事重拾

如果把图形技术划分为化学元素周期表的话,那么我对它的了解也就是那些废铜烂铁了。

一、前言

       当图形数据量很大时,绘图可能需要几秒钟甚至更长的时间,而且有时还会出现闪烁现象,为了解决这些问题,可采用双缓冲技术来绘图。

       双缓冲即在内存中创建一个与屏幕绘图区域一致的对象,先将图形绘制到内存中的这个对象上,再一次性将这个对象上的图形拷贝到屏幕上,这样能大大加快绘图的速度。双缓冲实现过程如下:

  • 在内存中创建与画布一致的缓冲区;
  • 在缓冲区画图;
  • 将缓冲区位图拷贝到当前画布上;
  • 释放内存缓冲区。

       在图形图象处理编程过程中,双缓冲是一种基本的技术。我们知道,如果窗体在响应WM_PAINT消息的时候要进行复杂的图形处理,那么窗体在重绘时由于过频的刷新而引起闪烁现象。解决这一问题的有效方法就是双缓冲技术。因为窗体在刷新时,总要有一个擦除原来图象的过程OnEraseBkgnd,它利用背景色填充窗体绘图区,然后在调用新的绘图代码进行重绘,这样一擦一写造成了图象颜色的反差。当WM_PAINT的响应很频繁的时候,这种反差也就越发明显。于是我们就看到了闪烁现象。 

       我们会很自然的想到,避免背景色的填充是最直接的办法。但是那样的话,窗体上会变的一团糟。因为每次绘制图象的时候都没有将原来的图象清除,造成了图象的残留,于是窗体重绘时,画面往往会变的乱七八糟。所以单纯的禁止背景重绘是不够的。我们还要进行重新绘图,但要求速度很快,于是我们想到了使用BitBlt函数。它可以支持图形块的复制,速度很快。我们可以先在内存中作图,然后用此函数将做好的图复制到前台,同时禁止背景刷新,这样就消除了闪烁。以上也就是双缓冲绘图的基本的思路。 

二、了解WM_PAINT消息

1WM_PAINT message(MSDN解释)

    The WM_PAINT message is sent when the system or another application makes a request to paint a portion of an application's window. The message is sent when theUpdateWindow or RedrawWindow function is called, or by theDispatchMessage function when the application obtains aWM_PAINT message by using theGetMessage orPeekMessage function.

    A window receives this message through itsWindowProc function.

C++

 

LRESULT CALLBACK WindowProc(

 HWND hwnd,

 UINT  uMsg,

 WPARAM wParam,

 LPARAM lParam    

);

 

Parameters

wParam

This parameter is not used.

lParam

This parameter is not used.

Return value

    An application returns zero if it processes this message.

Remarks

    The WM_PAINT message is generated by the system and should not be sent by an application. To force a window to draw into a specific device context, use theWM_PRINT orWM_PRINTCLIENT message. Note that this requires the target window to support the WM_PRINTCLIENT message. Most common controls support theWM_PRINTCLIENT message.

    The DefWindowProc function validates the update region. The function may also send the WM_NCPAINT message to the window procedure if the window frame must be painted and send theWM_ERASEBKGND message if the window background must be erased.

    The system sends this message when there are no other messages in the application's message queue.DispatchMessage determines where to send the message; GetMessage determines which message to dispatch. GetMessage returns the WM_PAINT message when there are no other messages in the application's message queue, andDispatchMessage sends the message to the appropriate window procedure.

    A window may receive internal paint messages as a result of callingRedrawWindow with the RDW_INTERNALPAINT flag set. In this case, the window may not have an update region. An application should call theGetUpdateRect function to determine whether the window has an update region. If GetUpdateRect returns zero, the application should not call theBeginPaint andEndPaint functions.

    An application must check for any necessary internal painting by looking at its internal data structures for eachWM_PAINT message, because aWM_PAINT message may have been caused by both a non-NULL update region and a call toRedrawWindow with the RDW_INTERNALPAINT flag set.

   The system sends an internalWM_PAINT message only once. After an internalWM_PAINT message is returned fromGetMessage orPeekMessage or is sent to a window byUpdateWindow, the system does not post or send further WM_PAINT messages until the window is invalidated or untilRedrawWindow is called again with the RDW_INTERNALPAINT flag set.

        For some common controls, the defaultWM_PAINT message processing checks thewParam parameter. IfwParam is non-NULL, the control assumes that the value is an HDC and paints using that device context.

三、无效区域

         Windows通知窗口对象重绘用户区时,并非整个用户区都要重绘。例如,当一个覆盖了窗口用户区域的对话框消失时,只有被对话框覆盖的用户区需要重新绘制(被对话框覆盖的其它区域,例如标题栏、滚动杠等重绘问题由函数DefWindowProc负责。Windows向窗口对象发送WM_NCPAINT消息;窗口对象将这个消息交给DefWindowProc函数进行缺省处理;DefWindowProc绘制窗口对象的非用户区)。这个需要重绘的区域称为“无效矩形区”。     

         在任何情况下,Windows通知窗口对象需要重绘的区域总是一个矩形区域。在用户区中出现的一个无效矩形提示Windows在应用程序的消息队列中放置WM_PAINT消息,窗口对象仅在其用户区无效接收到WM_PAINT消息。

         各种排队的消息首先在应用程序的消息队列中按优先级排队,WM_PAINT有最低的优先别,它总是在队列中的其它消息都被处理完之后才被处理。因此,在应用程序处理其他消息时,有可能又出现新的无效矩形,这样Windows又要向消息队列中放置WM_PAINT消息。

         但是,Windows只为每个窗口对象保留一条WM_PAINT消息和一个“绘制信息结果”— PAINTSTRUCT,这个结构内包含有无效矩形区的坐标。当一个窗口对象出现新的无效矩形区时,Windows在向应用程序的消息队列中放置WM_PAINT消息之前,它首先检查在消息队列中是否已存在一条准备发送给该窗口对象的WM_PAINT消息。若该消息存在,Windows将这两条 WM_PAINT消息合并为一条WM_PAINT消息。该消息的“绘制信息结构”的无效矩形区将包含原来的两个WM_PAINT消息的无效矩形区。

         在窗口对象处理WM_PAINT消息时,通过函数BeginPaint可以获得无效矩形区的坐标。在其他情况下,只能通过调用函数GetUpdateRect()获得无效矩形区的坐标。

         无效矩形也是一个裁剪矩形,也就是说,Windows限制应用程序只能在这个区域中绘图。当使用PAINSTRUCT结构中的显示设备对象绘图时,Windows将裁剪掉在rcPaint域所标识的矩形之外所绘的图。

         应用程序可以使用函数InvalidateRect()产生一个的无效矩形。函数InvalidateRect()与之对立的函数ValidateRect()

         进行裁剪操作是费时间的,为了提高程序的运行效率,当用户区显示的内容不很复杂时(或可以很有效地重画整个用户区时),可以在调用BeginPaint()函数之前调用InvalidateRect函数使整个用户区无效,然后,应用程序在整个用户区上进行绘制。

         由于WM_PAINT消息的优先级很低,这样,由于窗口对象不能及时收到WM_PAINT消息而影响用户对屏幕对象的视觉感觉。为弥补这个缺陷,可以考虑使用函数UpdateWindow(),它在应用程序的消息队列中存在WM_PAINT消息的情况下,强使Windows立即向窗口对象发送WM_PAINT消息。例如,我们在漫游图形的时候,您可以很快在屏幕上看到移动之后的窗口。

          EndPaint不仅归还显示设备对象,同时,它还清除应用程序的消息队列中的WM_PAINT消息。当使用函数ValidateRect使用窗口对象不存在任何无效的矩形区域时,ValidateRect函数同样也清除消息队列中的WM_PAINT消息。

四、坐标

1)映射模式

映射方式

逻辑单位

X轴增加

Y轴增加

毫米

MM_TEXT

像素点

与设备有关

MM_LOMETRIC

0.1mm

0.1

MM_HIMETRIC

0.01mm

0.01

MM_LOENGLISH

0.254mm

0.254

MM_HIENGLISH

0.0254mm

0.0254

MM_TWIPS

0.0176mm

0.0176

MM_ISOTROPIC

任意(x=y)

可选

可选

可设

MM_ANISOTROPIC

任意(x!=y)

可选

可选

可设

          MM_TEXT映射模式这种映射模式被称为"文本"映射方式,不是因为它对于文本最合适,而是轴的方向与读文本的方向一致。

        Windows提供了函数SetViewportOrg和SetWindowOrg 用来设置视口和窗口的原点。缺省的窗口原点和视口原点均为(0,0),可以改变;缺省的窗口范围和视口范围均为(1,1)。

2)坐标转换

       映射方式定义了Windows如何将GDI函数中指定的逻辑坐标映射为设备坐标。映射方式我们要介绍Windows有关映射模式的一些术语:我们将逻辑坐标所在的坐标系称为“窗口”,将设备坐标所在的坐标系称为“视口”。“窗口”依赖于逻辑坐标,可以是像素点、毫米或其他尺度。“视口”依赖于设备坐标(像素点)。通常,视口和客户区域等同。

公式1:“窗口坐标”转换成“视口坐标”

         对于所有映射模式,Windows都用下面两个公式将窗口坐标转换成视口坐标:

         xViewport=(xWindow-xWinOrg)*(xViewExt/xWinExt)+xViewOrg

         yViewport=(yWindow-yWinOrg)*(yViewExt/yWinExt)+yViewOrg

         其中,(xWindow,yWindows)是待转换的逻辑点,(xViewport,yViewport)是转换后的设备点。如果设备坐标是客户区域坐标或全窗口坐标,则Windows在画一个对象前,还必须将这些坐标转换成屏幕坐标。

         这两个公式使用了分别指定窗口和视口原点的点:(xWinOrg,yWinOrg)是逻辑坐标的窗口原点;(xViewOrg,yViewOrg)是设备坐标的视口原点。在缺省的设备环境中,这两个点均设置为(0,0),但它们可以改变。此公式意味着,逻辑点(xWinOrg,yWinOrg)总被映射为设备点(xViewOrg,yViewOrg)。

公式2:“视口坐标”转换成“窗口坐标”

         Windows还能将视口(设备)坐标转换为窗口(逻辑)坐标:

         xWindow=(xViewport-xViewOrg)*(xWinExt/xViewExt)+xWinOrg

         yWindow=(yViewport-yViewOrg)*(yWinExt/yViewExt)+yWinOrg

公式3:Windows提供的坐标转换函数

         可以使用Windows提供的两个函数DPtoLPLPtoDP在设备坐标及逻辑坐标之间互相转换。

         DPtoLP:“设备坐标”转换成“逻辑坐标”;

         LPtoDP:“逻辑坐标”转换成“设备坐标”

3)坐标范例

       在WINDOW操作系统下,绘制的图形默认坐标启点从屏幕的左上角开始(0,0):横坐标从0向右递增,纵坐标从0向下递增。每个图形的坐标都以此相对坐标作为参考点。

1)      范例1

       启始点作为操作系统默认值。如果你画一个图形:Ellipse(-100, -100, 100, 100),那么你会获取一个左上角为中心点的椭圆。因此,你仅能看到椭圆的右下角的1/4。

void CTestviewportView::OnDraw(CDC* pDC)

{

         CPen bluePen(PS_SOLID, 1, RGB(0, 0, 255));   

         CPen *pOldPen = pDC->SelectObject(&bluePen);

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

 

         CPen redPen(PS_SOLID, 2, RGB(255, 0, 0));

         pDC->SelectObject(&redPen);

         // Diagonal line at 45 degrees starting at the origin (0, 0)

         CPoint pt0(0, 0), pt1(100, 100);

         pDC->MoveTo(pt0);

         pDC->LineTo(pt1);

 

         pDC->SelectObject(pOldPen);

}

2)      范例2

         您可以使用设备上下文的方法,画出各种任意规则或非规则图形。下列代码显示如何画出中心轴线。

void CTestviewportView::OnDraw(CDC* pDC)

{

         CRect rectCenter;

         // Retrive the size of the drawing area

         GetClientRect(&rectCenter);

 

         CPen bluePen;            

         bluePen.CreatePen(PS_SOLID, 1, RGB(0, 12, 255));

         CPen *pOldPen = pDC->SelectObject(&bluePen);

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

 

         CPen redPen(PS_SOLID, 2, RGB(255, 0, 0));

         pDC->SelectObject(&redPen);

         // Diagonal line at 45 degrees starting at the origin (0, 0)

         CPoint pt0(0, 0), pt1(100, 100);

         pDC->MoveTo(pt0);

         pDC->LineTo(pt1);

 

         CPen blackPen;

         blackPen.CreatePen(PS_SOLID, 1, BLACK_PEN);

         pDC->SelectObject(&blackPen);

 

         pDC->MoveTo(rectCenter.Width() / 2, 0);

         pDC->LineTo(rectCenter.Width() / 2, rectCenter.Height());

         pDC->MoveTo(0, rectCenter.Height() / 2);

         pDC->LineTo(rectCenter.Width(), rectCenter.Height() / 2);

 

         pDC->SelectObject(pOldPen);

}

3)      范例3

         MFC提供多种函数处理坐标位置和扩展绘图区域,其中包括您需要设置屏幕任意位置坐标。因此,您能调用CDC::SetViewportOrg()方法,它可以是XY坐标或定义的点。

         SetViewportOrg(int x, int y)

         SetViewportOrg(CPoint point)

 

void CTestviewportView::OnDraw(CDC* pDC)

{

         CRect rectCenter;

         // Retrive the size of the drawing area

         GetClientRect(&rectCenter);

         pDC->SetViewportOrg(rectCenter.Width() / 2, rectCenter.Height() / 2);

 

         CPen bluePen;            

         bluePen.CreatePen(PS_SOLID, 1, RGB(0, 12, 255));

         CPen *pOldPen = pDC->SelectObject(&bluePen);

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

 

         CPen redPen(PS_SOLID, 2, RGB(255, 0, 0));

         pDC->SelectObject(&redPen);

         // Diagonal line at 45 degrees starting at the origin (0, 0)

         CPoint pt0(0, 0), pt1(100, 100);

         pDC->MoveTo(pt0);

         pDC->LineTo(pt1);

 

         CPen blackPen;

         blackPen.CreatePen(PS_SOLID, 1, BLACK_PEN);

         pDC->SelectObject(&blackPen);

 

         // Horizontal axis

         pDC->MoveTo(-rectCenter.Width() / 2, 0);

         pDC->LineTo(rectCenter.Width() / 2, 0);

         // Vertical axis

         pDC->MoveTo(0, -rectCenter.Height());

         pDC->LineTo(0, rectCenter.Height());

 

         pDC->SelectObject(pOldPen);  

}

        很显然,SetViewportOrg方法能够改变CDC设备上下文的原始坐标,并且坐标方向保持不变。因此,水平坐标从(0,0)向右递增,垂直坐标从(0,0)向下递增。

 

4)      范例4

        范例3改变了视口的初始位置,假如我们也改变窗口的初始位置,看看效果如何?

void CTestviewportView::OnDraw(CDC* pDC)

{

         CRect rectCenter;

         // Retrive the size of the drawing area

         GetClientRect(&rectCenter);

         pDC->SetWindowOrg(100, 200);

         pDC->SetViewportOrg(300, 400);

 

         CPen bluePen;            

         bluePen.CreatePen(PS_SOLID, 1, RGB(0, 12, 255));

         CPen *pOldPen = pDC->SelectObject(&bluePen);

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

 

         CPen redPen(PS_SOLID, 2, RGB(255, 0, 0));

         pDC->SelectObject(&redPen);

         // Diagonal line at 45 degrees starting at the origin (0, 0)

         CPoint pt0(0, 0), pt1(100, 100);

         pDC->MoveTo(pt0);

         pDC->LineTo(pt1);

 

         CPen blackPen;

         blackPen.CreatePen(PS_SOLID, 1, BLACK_PEN);

         pDC->SelectObject(&blackPen);

 

         // Horizontal axis

         pDC->MoveTo(-rectCenter.Width() / 2, 0);

         pDC->LineTo(rectCenter.Width() / 2, 0);

         // Vertical axis

         pDC->MoveTo(0, -rectCenter.Height());

         pDC->LineTo(0, rectCenter.Height());

 

         pDC->SelectObject(pOldPen);    

   

         CPoint ptCenter(0, 0);

           pDC->LPtoDP(&ptCenter);   

}

根据公式1:可以得到中心点的位置。

        窗口(逻辑)坐标原点:WinOrg(100,200)

        视口(设备)坐标原点:ViewOrg(300,400)

        原始中心点:Point(0,0)

xViewport=(xWindow-xWinOrg)*(xViewExt/xWinExt)+xViewOrg

        =(0 – 100) * (1 / 1) + 300=200

yViewport=(yWindow-yWinOrg)*(yViewExt/yWinExt)+yViewOrg

        =(0 – 200) * (1 / 1) + 400=200

根据公式3:同样得到中心点的位置。

        CPoint ptCenter(0, 0);

        pDC->LPtoDP(&ptCenter); //此时值转换之后的值为:ptCenter(200, 200)

5)      范例5

      当我们设置CDC的映射模式时(默认为MM_TEXT),如MM_LOENGLISH映射模式。它主要改变垂直坐标的方向即坐标从(0,0)向上递增。测量单位也发生变化。

 

void CTestviewportView::OnDraw(CDC* pDC)

{

     pDC->SetMapMode(MM_LOENGLISH);        

 

         CRect rectCenter;

         // Retrive the size of the drawing area

         GetClientRect(&rectCenter);

         pDC->SetViewportOrg(rectCenter.Width() / 2, rectCenter.Height() / 2);

 

         CPen bluePen;            

         bluePen.CreatePen(PS_SOLID, 1, RGB(0, 12, 255));

         CPen *pOldPen = pDC->SelectObject(&bluePen);

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

 

         CPen redPen(PS_SOLID, 2, RGB(255, 0, 0));

         pDC->SelectObject(&redPen);

         // Diagonal line at 45 degrees starting at the origin (0, 0)

         CPoint pt0(0, 0), pt1(100, 100);

         pDC->MoveTo(pt0);

         pDC->LineTo(pt1);

 

         CPen blackPen;

         blackPen.CreatePen(PS_SOLID, 1, BLACK_PEN);

         pDC->SelectObject(&blackPen);

 

         // Horizontal axis

         pDC->MoveTo(-300, 0);

         pDC->LineTo(300, 0);

         // Vertical axis

         pDC->MoveTo(0, -200);

         pDC->LineTo(0, 200);

 

         pDC->SelectObject(pOldPen);  

}

五、双缓冲区

5-1原始图形

        假设图5-1中有3个叠加的图形,顺序分别为正方形、多边形和椭圆,其中正方形在最下层,椭圆在最上层。

      下面是OnDraw常规的简单操作流程:

  • 首先定义一个显示设备对象;
  •  定义一个位图对象;
  • 随后建立与屏幕显示兼容的内存显示设备;
  • 将位图选入到内存显示设备中,只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上;
  • 先用背景色将位图清除干净,这里以灰色作为背景;
  • 绘图所有链表图层及图形;
  • 将内存中的图拷贝到屏幕上进行显示;
  • 绘图完成后的清理把前面的pOldBit选回来,在删除MemBitmap之前要先从设备中移除它。

void CTestView::OnDraw(CDC* pDC)

{

         CDC MemDC;

         CBitmap MemBitmap;

         MemDC.CreateCompatibleDC(NULL);

         MemBitmap.CreateCompatibleBitmap(pDC, nWidth, nHeight);

         CBitmap *pOldBit=MemDC.SelectObject(&MemBitmap);

         MemDC.FillSolidRect(0 ,0, nWidth, nHeight, RGB(215, 215, 215));

        

         MemDC.DrawRect(…);

         MemDC.PolyLine(…);

         MemDC.Ellipse(..);

   

         pDC->BitBlt(0, 0, nWidth, nHeight, &MemDC, 0, 0, SRCCOPY);

         MemDC.SelectObject(pOldBit);

         MemBitmap.DeleteObject();

         MemDC.DeleteDC();

}

5-2更新的区域

         假设图5-2中多边形将是我们准备更新的图形,它的范围为图中选择的区域。更新时我们会发现多边形与正方形和椭圆都存在相交情形。

        为了提高图形绘制效率,下列的代码表示如何处理这种复杂过程(注意这里代码只是表现过程,不能以此编译),把下面的OnDraw过程对比上述的 OnDraw过程后,您将发现主要区别是拷贝内存m_pMemDC的图形,而不是直接内存中画图。

void CTestView::OnDraw(CDC *pDC)

{

         CDC MemDC;

         CBitmap MemBitmap;

         MemDC.CreateCompatibleDC(NULL);

         MemBitmap.CreateCompatibleBitmap(pDC, nWidth, nHeight);

         CBitmap *pOldBit = MemDC.SelectObject(&MemBitmap);

         MemDC.FillSolidRect(0 ,0, nWidth, nHeight, RGB(215, 215, 215));

 

         MemDC.BitBlt(0, 0, nWidth, nHeight, &m_pMemDC, 0, 0, SRCCOPY);

 

         pDC->BitBlt(0, 0, nWidth, nHeight, &MemDC, 0, 0, SRCCOPY);

         MemDC.SelectObject(pOldBit);

         MemBitmap.DeleteObject();

         MemDC.DeleteDC();

}

         无效时调用下列过程draw,在无效的区域绘制图形,并最终产生无效消息WM_PAINT。

void CTestView::draw(const CRect &dcRect)

{

         CRgn dcRgn;

         dcRgn.CreateRectRgnIndirect(&dcRect);

         draw(dcRgn);

         InvalidateRect(dcRect, TRUE);

         dcRgn.DeleteObject();

}

    下列代码中的成员变量m_pMemDC是预先创建的内存CDC。其主要过程为设置裁剪区域,填充背景色,然后绘制图形,最后恢复裁剪区域。

void CTestView::draw(const CRgn &dcRgn)

{

         CDC *pDC = m_pMemDC;

         CRect rectClient;

         GetClientRect(&rectClient);

         pDC->SelectClipRgn((HRGN)dcRgn.GetSafeHandle());

         CBrush brush;

         brush.CreateSolidBrush(RGB(215, 215, 215));

         pDC->FillRgn(&dcRgn, &brush);

 

         drawShape(pDC, dcRgn);

 

         pDC->SelectClipRgn(NULL);

 

         m_pMemDC->BitBlt(0, 0, rectClient.Width(), rectClient.Height(), pDC, 0, 0, SRCCOPY);            

}

         遍历所有图层及图形,绘制与区域dcRgn相交的图形:

void CTestView::drawShape(CDC *pDC,const CRgn &dcRgn)

{

         CRect  rcObj;

         for  (...)

         {

                   rcObj = pShape->GetBoundRect();

                   if  (!dcRgn.RectInRegion(&rcObj))

                   {

                            pShape->onDraw(...);

                   }

         }   

}

 

5-3白色的裁剪区域

5-4绘制的无效区域

        上述drawShape过程总是按照正方形、多边形和椭圆的顺序绘制。因此,衔接不会出现问题。

六、漫游

       平移是指在同一平面内,将一个图形整体按照某个直线方向移动一定的距离,这样的图形运动叫做图形的平移运动,简称平移。平移不改变图形的形状和大小,平移后的图形与原图形上对应点连接的线段平行(或在同一条直线上)且相等。它是等距同构,是仿射空间中仿射变换的一种。它可以视为将同一个向量加到每点上,或将坐标系统的中心移动所得的结果。即是说,若是一个已知的向量,是空间中一点平移。

       漫游主要是用户使用鼠标按住图形进行平移,步长分为垂直和水平两个方向的值。计算偏移量以鼠标按下到鼠标释放时的差值。滚动条上的点击事件也会产生图形的漫游,一般滚动条点击一次默认为10个像素步长,可以使用函数SetScrollSizes设置滚动条范围。        

6-1初始图形位置(红色边框线区域内)

       在平面图形中,图形的平移会产生无效区域。主要有下列四种情形:

1)    情形1

6-2 OffsetX <0, OffsetY < 0

6-3移动后的无效区域

6-4无效区域分割成横向与纵向

2)    情形2

6-5 OffsetX >0, OffsetY< 0

 

6-6移动后的无效区域

6-7无效区域分割成横向与纵向

3)    情形3

6-8 OffsetX >0, OffsetY > 0

6-9移动后的无效区域

6-10无效区域分割成横向与纵向  

4)    情形4

6-11 OffsetX < 0, OffsetY > 0

6-12移动后的无效区域

6-13无效区域分割成横向与纵向

        根据上述几种情形,我们可以把有效的图形复制到设备上下文中(m_pMemDC)新的目标区域,无效区域的图形按照横向与纵向分割,调用上述五的双缓冲区函数draw(CRect rect),并产生无效区域消息。这样的好处是当图形密度大,图层多而图形复杂时,效率能明显提高。

七、缩放

7-1视口(设备)区域与窗口(逻辑)区域org(0,0)

        

7-2正常的视口区域与窗口区域

void CTestviewportView::OnDraw(CDC* pDC)

{

         pDC->SetMapMode(MM_ANISOTROPIC);

        

         CPen redPen(PS_SOLID, 1, RGB(255, 0, 0));

         CBrush blueBrush(RGB(0, 0, 255));

 

         pDC->SelectObject(&redPen);

         pDC->SelectObject(blueBrush);

         // Draw a square with a red border and an blue background

         CRect rect(0, 0, 100, 100);     

         pDC->Rectangle(rect);

 

         CPen greenPen(PS_SOLID, 2, RGB(0, 255, 0));

         pDC->SelectObject(&greenPen);

         // Diagonal line at 45 degrees starting at the origin (0, 0)

         CPoint pt0(50, 50), pt1(150, 150);

         pDC->MoveTo(pt0);

         pDC->LineTo(pt1);

}

7-3缩小的视口区域与窗口区域

void CTestviewportView::OnDraw(CDC* pDC)

{

        // 把窗口范围放大2倍,也就是视口缩小1倍.

       double dXScale = 2.0;

       double dYScale = 2.0;

       

       CSize szWndExt(1366, 768);

       CSize szViewportExt(1366, 768);

       

        szWndExt.cx = (long) ((double) szWndExt.cx * dXScale);

        szWndExt.cx = (szWndExt.cx == 0L) ? 1L : szWndExt.cx;

        szWndExt.cy = (long) ((double) szWndExt.cy * dYScale);

        szWndExt.cy = (szWndExt.cy == 0L) ? 1L : szWndExt.cy;

         pDC->SetWindowExt(szWndExt);

         pDC->SetViewportExt(szViewportExt);

 

         CPen redPen(PS_SOLID, 1, RGB(255, 0, 0));

         CBrush blueBrush(RGB(0, 0, 255));

 

         pDC->SelectObject(&redPen);

         pDC->SelectObject(&blueBrush);

         // Draw a square with a red border and an aqua background

         CRect rect(0, 0, 100, 100);     

         pDC->Rectangle(rect);

 

         CPen greenPen(PS_SOLID, 2, RGB(0, 255, 0));

         pDC->SelectObject(&greenPen);

         // Diagonal line at 45 degrees starting at the origin (0, 0)

         CPoint pt0(50, 50), pt1(150, 150);

         pDC->MoveTo(pt0);

         pDC->LineTo(pt1);

}

         当我们图形需要缩放功能的时候,最常规的方法创建成员缩放变量:m_dXScale,m_dYScale,并在把所有点的坐标乘以这个变量。牵涉到坐标系的转换,这种方法一般不容易使用。所以,我们试图找到如何在同一个地方设置缩放比例,而且适用于所有的绘图代码,而不要操心系数的乘除。幸运的是,操作系统的图形库有一些映射模式,可以设置视口和窗口面积之比。

       自定义映射模式MM_ANISOTROPIC和MM_ISOTROPIC两种映射模式允许开发人员设置自己的窗口和视口范围。MM_ISOTROPIC和MM_ANISOTROPIC的区别是所设置的x轴和y轴的的范围必须相同(假如不同,则取x,y最小值,其值等于0没有意义),而MM_ANISOTROPIC所设置的x轴和y轴的的范围可以不同。

八、GRID

       当画布窗口的范围非常巨大,画面内容也很饱满,可以考虑网格技术解决图形显示效率的问题。网格法是以网格把窗口分割成一个个大小相同的单元格,反映绘制图形对象特征的一种地图表示方法。每一个网格就是一个区域图形的集合,其精度取决于网眼大小,网眼越小,精度越高。

        除了上面的网格法概念,还有十字链表和矩形树等数据结构方法,它们适用的范围有所不同。

九、总结

        MFC编程中离不开消息机制,而像QT图形方面的技术与WINDOWS很多不一样,它主要使用信号与槽的链接关系。再如QTpaintEvent的事件与WINDOWSOnDraw消息,MFC的设备上下文CDC可以在很多地方使用,而QTWINDOWSpaintEvent内部的句柄QPainter不能超出该函数的调用范围,否则失效。因此,上述的内容不一定适合于所有绘图框架。

       本文主要讨论了图形常规的一些技术,这些技术在WNDOWS操作系统很常见,也是提供图形绘制效率的一些技术。像googlebaidu地图及GIS等图形软件,还有更多、更好的图形技术解决方案。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值