天天看點

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

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

一、前言

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

       雙緩沖即在記憶體中建立一個與螢幕繪圖區域一緻的對象,先将圖形繪制到記憶體中的這個對象上,再一次性将這個對象上的圖形拷貝到螢幕上,這樣能大大加快繪圖的速度。雙緩沖實作過程如下:

  • 在記憶體中建立與畫布一緻的緩沖區;
  • 在緩沖區畫圖;
  • 将緩沖區位圖拷貝到目前畫布上;
  • 釋放記憶體緩沖區。

       在圖形圖象處理程式設計過程中,雙緩沖是一種基本的技術。我們知道,如果窗體在響應WM_PAINT消息的時候要進行複雜的圖形處理,那麼窗體在重繪時由于過頻的重新整理而引起閃爍現象。解決這一問題的有效方法就是雙緩沖技術。因為窗體在重新整理時,總要有一個擦除原來圖象的過程OnEraseBkgnd,它利用背景色填充窗體繪圖區,然後在調用新的繪圖代碼進行重繪,這樣一擦一寫造成了圖象顔色的反差。當WM_PAINT的響應很頻繁的時候,這種反差也就越發明顯。于是我們就看到了閃爍現象。 

       我們會很自然的想到,避免背景色的填充是最直接的辦法。但是那樣的話,窗體上會變的一團糟。因為每次繪制圖象的時候都沒有将原來的圖象清除,造成了圖象的殘留,于是窗體重繪時,畫面往往會變的亂七八糟。是以單純的禁止背景重繪是不夠的。我們還要進行重新繪圖,但要求速度很快,于是我們想到了使用BitBlt函數。它可以支援圖形塊的複制,速度很快。我們可以先在記憶體中作圖,然後用此函數将做好的圖複制到前台,同時禁止背景重新整理,這樣就消除了閃爍。以上也就是雙緩沖繪圖的基本的思路。 

二、了解WM_PAINT消息

1)WM_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提供的兩個函數DPtoLP和LPtoDP在裝置坐标及邏輯坐标之間互相轉換。

         DPtoLP:“裝置坐标”轉換成“邏輯坐标”;

         LPtoDP:“邏輯坐标”轉換成“裝置坐标”

3)坐标範例

       在WINDOW作業系統下,繪制的圖形預設坐标啟點從螢幕的左上角開始(0,0):橫坐标從0向右遞增,縱坐标從0向下遞增。每個圖形的坐标都以此相對坐标作為參考點。

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

1)      範例1

       啟始點作為作業系統預設值。如果你畫一個圖形:Ellipse(-100, -100, 100, 100),那麼你會擷取一個左上角為中心點的橢圓。是以,你僅能看到橢圓的右下角的1/4。

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

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

         您可以使用裝置上下文的方法,畫出各種任意規則或非規則圖形。下列代碼顯示如何畫出中心軸線。

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

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()方法,它可以是X與Y坐标或定義的點。

         SetViewportOrg(int x, int y)

         SetViewportOrg(CPoint point)

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

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)向下遞增。

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

4)      範例4

        範例3改變了視口的初始位置,假如我們也改變視窗的初始位置,看看效果如何?

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

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)向上遞增。測量機關也發生變化。

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

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);  

}

五、雙緩沖區

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

圖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();

}

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

圖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(...);

                   }

         }   

}

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

圖5-3白色的裁剪區域

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

圖5-4繪制的無效區域

        上述drawShape過程總是按照正方形、多邊形和橢圓的順序繪制。是以,銜接不會出現問題。

六、漫遊

       平移是指在同一平面内,将一個圖形整體按照某個直線方向移動一定的距離,這樣的圖形運動叫做圖形的平移運動,簡稱平移。平移不改變圖形的形狀和大小,平移後的圖形與原圖形上對應點連接配接的線段平行(或在同一條直線上)且相等。它是等距同構,是仿射空間中仿射變換的一種。它可以視為将同一個向量加到每點上,或将坐标系統的中心移動所得的結果。即是說,若是一個已知的向量,是空間中一點平移。

       漫遊主要是使用者使用滑鼠按住圖形進行平移,步長分為垂直和水準兩個方向的值。計算偏移量以滑鼠按下到滑鼠釋放時的內插補點。滾動條上的點選事件也會産生圖形的漫遊,一般滾動條點選一次預設為10個像素步長,可以使用函數SetScrollSizes設定滾動條範圍。        

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

圖6-1初始圖形位置(紅色邊框線區域内)

       在平面圖形中,圖形的平移會産生無效區域。主要有下列四種情形:

1)    情形1

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

圖6-2 OffsetX <0, OffsetY < 0

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

圖6-3移動後的無效區域

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

圖6-4無效區域分割成橫向與縱向

2)    情形2

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

圖6-5 OffsetX >0, OffsetY< 0

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

圖6-6移動後的無效區域

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

圖6-7無效區域分割成橫向與縱向

3)    情形3

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

圖6-8 OffsetX >0, OffsetY > 0

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

圖6-9移動後的無效區域

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

圖6-10無效區域分割成橫向與縱向  

4)    情形4

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

圖6-11 OffsetX < 0, OffsetY > 0

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

圖6-12移動後的無效區域

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

圖6-13無效區域分割成橫向與縱向

        根據上述幾種情形,我們可以把有效的圖形複制到裝置上下文中(m_pMemDC)新的目标區域,無效區域的圖形按照橫向與縱向分割,調用上述五的雙緩沖區函數draw(CRect rect),并産生無效區域消息。這樣的好處是當圖形密度大,圖層多而圖形複雜時,效率能明顯提高。

七、縮放

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

圖7-1視口(裝置)區域與視窗(邏輯)區域org(0,0)

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

圖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);

}

提高圖形高效繪圖機制的方法--舊事重拾一、前言二、了解WM_PAINT消息三、無效區域四、坐标五、雙緩沖區六、漫遊七、縮放八、GRID九、總結

圖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很多不一樣,它主要使用信号與槽的連結關系。再如QT的paintEvent的事件與WINDOWS的OnDraw消息,MFC的裝置上下文CDC可以在很多地方使用,而QT在WINDOWS下paintEvent内部的句柄QPainter不能超出該函數的調用範圍,否則失效。是以,上述的内容不一定适合于所有繪圖架構。

       本文主要讨論了圖形正常的一些技術,這些技術在WNDOWS作業系統很常見,也是提供圖形繪制效率的一些技術。像google和baidu地圖及GIS等圖形軟體,還有更多、更好的圖形技術解決方案。

繼續閱讀