提高圖形高效繪圖機制的方法--舊事重拾

如果把圖形技術劃分爲化學元素週期表的話,那麼我對它的瞭解也就是那些廢銅爛鐵了。

一、前言

       當圖形數據量很大時,繪圖可能需要幾秒鐘甚至更長的時間,而且有時還會出現閃爍現象,爲了解決這些問題,可採用雙緩衝技術來繪圖。

       雙緩衝即在內存中創建一個與屏幕繪圖區域一致的對象,先將圖形繪製到內存中的這個對象上,再一次性將這個對象上的圖形拷貝到屏幕上,這樣能大大加快繪圖的速度。雙緩衝實現過程如下:

  • 在內存中創建與畫布一致的緩衝區;
  • 在緩衝區畫圖;
  • 將緩衝區位圖拷貝到當前畫布上;
  • 釋放內存緩衝區。

       在圖形圖象處理編程過程中,雙緩衝是一種基本的技術。我們知道,如果窗體在響應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等圖形軟件,還有更多、更好的圖形技術解決方案。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章