小結:主要的思想是,如果要顯示圖形,直接把該圖形當作一個document,在改寫document的時候,實際上就是改寫這個位元影像。然後在View中顯示的時候,直接把這個位元影像顯示出來就可以了。這需要兩個DC,一個DC是檯面上的顯示用的,一個DC是在記憶體中繪圖用的。兩者的關聯點就在於這個bitmap對象。需要明確幾個概念,由於MFC的類封裝了windows對象,所以由MFC類建立出來的對象就叫做“執行個體”,以與windows“對象”區分開來。
document/view的經典辦法是將圖形的資料存放區在document類裡面,view類只是根據這些資料繪圖。比如你要畫個圓,只是將圓心和半徑存在document裡面,view類根據這個裡面的資料在螢幕上面重新繪製。那麼,我們只需要隨機產生一次資料就可以了。
這樣還是存在效能的問題,於是我們開始考慮另外的解決方案。我們知道,將記憶體中的圖片原樣輸出到螢幕是很快的,這也是我們在dos時代經常做的事情,能不能在windows也重新利用呢?答案就是記憶體緩衝繪圖,我們今天的主題。
我們還是回到DC上來,既然DC是繪圖對象,我們也就可以自己來在記憶體裡面造一個,讓它等於我們想要的圖,圖(CBitmap)可以儲存在document 類裡面,每一次重新整理螢幕都只是將這個圖輸出到螢幕上面,每一次作圖都是在記憶體裡面繪製,儲存在document的圖裡面,必要時還可以將圖輸出到外存儲存。這樣既保證了速度,也解決了隨機的問題,在複雜作圖的情況下對記憶體的開銷也不大(總是一副圖片的大小)。這是一個很好的解決辦法,現在讓我們來實現它們。
我們在document類裡面儲存一個圖片
CBitmap m_bmpBuf;//這裡面儲存了我們做的圖,存在於記憶體中
在view類裡面我們需要將這個圖拷貝到螢幕上去
位於OnDraw(CDC *pDC)函數中:
| 下面這一部分就是CView負責的工作,僅僅需要的是顯示該bitmap |
CDC dcMem;//以下是輸出位元影像的標準操作
CBitmap *pOldBitmap = NULL;
dcMem.CreateCompatibleDC(NULL);
pOldBitmap = dcMem.SelectObject(&pDoc->m_bmpBuf);
BITMAP bmpinfo;
pDoc->m_bmpBuf.GetBitmap(&bmpinfo);
pDC->BitBlt(0,0,bmpinfo.bmWidth,bmpinfo.bmHeight,&dcMem,0,0,SRCCOPY);
dcMem.SelectObject(pOldBitmap);
dcMem.DeleteDC();
在我們需要畫圖的函數裡面,我們完成繪圖工作
下面就是設定Doc了
CBmpDrawDoc *pDoc = GetDocument(); //得到document中的bitmap對象
CDC *pDC = GetDC();//這個GetDC將得到當前客戶區的DC,也就是說,現在windows的DC對象已經建立了,並且由pDC同一負責
CDC dcMem;//前面已經講過,用CDC建立DC執行個體,不會建立任何的windows DC對象。所以這個dcMem現在還沒有任何的windows物件控點
dcMem.CreateCompatibleDC(NULL);//隨後建立與螢幕顯示相容的記憶體顯示裝置。這個時候dcMem就有了windows對象的控制代碼了
注意此時m_bmpBuf還沒有跟任何windows對象相關聯。pDoc->m_bmpBuf.DeleteObject();pDoc->m_bmpBuf.CreateCompatibleBitmap(pDC,100,100);//依附DC建立bitmap執行了上面這句話之後,bitmap執行個體就依附於一個windows的bitmap對象了。那麼為什麼要用pDC作為參數,而不用&dcMem呢?下面來看看MSDN關於該函數的解釋:
| CBitmap::CreateCompatibleBitmap BOOL CreateCompatibleBitmap( CDC* pDC, int nWidth, int nHeight );Return ValueNonzero if successful; otherwise 0.ParameterspDCSpecifies the device context.nWidthSpecifies the width (in pixels) of the bitmap.nHeightSpecifies the height (in pixels) of the bitmap.RemarksInitializes a bitmap that is compatible with the device specified by pDC. The bitmap has the same number of color planes or the same bits-per-pixel format as the specified device context. It can be selected as the current bitmap for any memory device that is compatible with the one specified by pDC.If pDC is a memory device context, the bitmap returned has the same format as the currently selected bitmap in that device context. A "memory device context" is a block of memory that represents a display surface. It can be used to prepare images in memory before copying them to the actual display surface of the compatible device.When a memory device context is created, GDI automatically selects a monochrome stock bitmap for it.Since a color memory device context can have either color or monochrome bitmaps selected, the format of the bitmap returned by the CreateCompatibleBitmap function is not always the same; however, the format of a compatible bitmap for a nonmemory device context is always in the format of the device.When you finish with the CBitmap object created with the CreateCompatibleBitmap function, first select the bitmap out of the device context, then delete the CBitmap object. |
也就是說,我們現在需要的是把我們的bitmap顯示在螢幕上,因此需要和現在的客戶區的DC(也就是Pdc來負責的)關聯起來,這樣bitmap的一些色彩屬性才會和pDC的一樣。但是記憶體DC的色彩沒有那麼多,這樣的話,繪製這個bitmap實際上只有幾種顏色了。可以作個實驗,如果這裡改成&menDC,那麼繪出來的方塊就是黑色的,而用pDC就是紅色的,也是我們設想的。
CBitmap *pOldBitmap = dcMem.SelectObject(&pDoc->m_bmpBuf);//調入了我們bitmap對象
dcMem.FillSolidRect(0,0,100,100,RGB(255,255,255));//這些時繪圖操作,隨便你^_^
dcMem.TextOut(0,0,"Hello,world!");
dcMem.Rectangle(20,20,40,40);
dcMem.FillSolidRect(40,40,50,50,RGB(255,0,0));
pDC->BitBlt(0,0,100,100,&dcMem,0,0,SRCCOPY);//第一次拷貝到螢幕
dcMem.SelectObject(pOldBitmap);
dcMem.DeleteDC();
全部的過程就是這樣,很簡單吧。以此為例子還可以實現2個緩衝或者多個緩衝等等,視具體情況而定。當然在緩衝區還可以實現很多進階的圖形操作,比如透明,合成等等,取決於具體的演算法,需要對記憶體直接操作(其實就是當年dos怎麼做,現在還怎麼做)。
再來解釋一下前面說的為什麼不能用全域變數儲存DC問題。其實DC也是用控制代碼來標識的,所以也具有控制代碼的不確定性,就是只能隨用隨取,不同時間兩次取得的是不同的(使用過檔案控制代碼地話,應該很容易理解的)。那麼我們用全域變數儲存的DC就沒什麼意義了,下次使用只是什麼也畫不出來。(這一點的理解可以這樣: DC需要佔用一定的記憶體,那麼在頻繁的頁面調度中,位置難免改變,於是用來標誌指標的控制代碼也就不同了)。