雙緩衝

雙緩衝

簡介

我們看電視時,看到的螢幕稱為OSD層,也就是說,只有在OSD層上顯示圖像我們才能看到。現在,我需要創建一個虛擬的、看不見但是可以在上面畫圖(比如說畫點、線)的OSD層,我稱之為offscreen(後台緩衝區)。這個offscreen存在於記憶體中,我們在上面畫圖,這個offscreen上面的東西可以顯示在OSD層上,需要一個創建這個offscreen的函式,返回這個offscreen的句柄(整型指針)、寬度、高度、指向新建offscreen數據緩衝區的指針,該緩衝區是一個在函式外創建的offscreen的數據緩衝區,大小是offscreen的高度*寬度*每個像素點數據的大小。閃爍是圖形編程的一個常見問題。需要多重複雜繪製操作的圖形操作會導致呈現的圖像閃爍或具有其他不可接受的外觀。雙緩衝的使用解決這些問題。雙緩衝使用記憶體緩衝區來解決由多重繪製操作造成的閃爍問題。當啟用雙緩衝時,所有繪製操作首先呈現到記憶體緩衝區,而不是螢幕上的繪圖圖面。所有繪製操作完成後,記憶體緩衝區直接複製到與其關聯的繪圖圖面。因為在螢幕上只執行一個圖形操作,所以消除了由複雜繪製操作造成的圖像閃爍。

實現方法,目錄,1實現方法,2騎馬抽,3J2me,4C#,C#雙緩衝解釋,GDI+的雙緩衝問題,5畫面閃爍,6使用雙緩衝,7具體實現,8Graphics創建方式,騎馬抽,J2me,C#,

實現方法

我們電視時,看到的螢幕稱為OSD層,也就是說,只有在OSD層上顯示圖像我們才能看到。在圖形圖象處理編程過程中,雙緩衝是一種基本的技術。我們知道,如果窗體在回響WM_PAINT訊息的時候要進行複雜的圖形處理,那么窗體在重繪時由於過頻的刷新而引起閃爍現象。

目錄

  • 1實現方法
  • C#雙緩衝解釋
  • GDI+的雙緩衝問題
  • 5畫面閃爍
  • 6使用雙緩衝
  • 7具體實現
  • 8Graphics創建方式

1實現方法

在圖形圖象處理編程過程中,雙緩衝是一種基本的技術。我們知道,如果窗體在回響WM_PAINT訊息的時候要進行複雜的圖形處理,那么窗體在重繪時由於過頻的刷新而引起閃爍現象。解決這一問題的有效方法就是雙緩衝技術。因為窗體在刷新時,總要有一個擦除原來圖象的過程OnEraseBkgnd,它利用背景色填充窗體繪圖區,然後在調用新的繪圖代碼進行重繪,這樣一擦一寫造成了圖象顏色的反差。當WM_PAINT的回響很頻繁的時候,這種反差也就越發明顯。於是我們就看到了閃爍現象。
雙緩衝我們會很自然的想到,避免背景色的填充是最直接的辦法。但是那樣的話,窗體上會變的一團糟。因為每次繪製圖象的時候都沒有將原來的圖象清除,造 成了圖象的殘留,於是窗體重繪時,畫面往往會變的亂七八糟。所以單純的禁止背景重繪是不夠的。我們還要進行重新繪圖,但要求速度很快,於是我們想到了使用 BitBlt函式。它可以支持圖形塊的複製,速度很快。我們可以先在記憶體中作圖,然後用此函式將做好的圖複製到前台,同時禁止背景刷新,這樣就消除了閃 爍。以上也就是雙緩衝繪圖的基本的思路。
首先給出實現的程式,然後再解釋,同樣是在OnDraw(CDC *pDC)中:
CDC MemDC; //首先定義一個顯示設備對象
CBitmap MemBitmap;//定義一個點陣圖對象
//隨後建立與螢幕顯示兼容的記憶體顯示設備
MemDC.CreateCompatibleDC(NULL);
//這時還不能繪圖,因為沒有地方畫 ^_^
//下面建立一個與螢幕顯示兼容的點陣圖,至於點陣圖的大小嘛,可以用視窗的大小,也可以自己定義(如:有滾動條時就要大於當前視窗的大小,在BitBlt時決定拷貝記憶體的哪部分到螢幕上)
MemBitmap.CreateCompatibleBitmap(pDC,nWidth,nHeight);
//將點陣圖選入到記憶體顯示設備中
//只有選入了點陣圖的記憶體顯示設備才有地方繪圖,畫到指定的點陣圖上
CBitmap *pOldBit=MemDC.SelectObject(&MemBitmap);
//先用背景色將點陣圖清除乾淨,這裡我用的是白色作為背景
//你也可以用自己應該用的顏色
MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(255,255,255));
//繪圖
MemDC.MoveTo(……);
MemDC.LineTo(……);
//將記憶體中的圖拷貝到螢幕上進行顯示
pDC->BitBlt(0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY);
//繪圖完成後的清理
//把前面的pOldBit選回來.在刪除MemBitmap之前要先從設備中移除它
MemDC.SelectObject(pOldBit);
MemBitmap.DeleteObject();
MemDC.DeleteDC();
雙緩衝(two way soft-closing)

2騎馬抽

也稱作:雙緩衝騎馬抽、豪華雙緩衝阻尼抽屜
即騎馬抽在完全拉出和關閉時都具有較好的緩衝效果,能有效避免噪音產生,把抽屜的運動慣性所產生的破壞力降低到最小,有效保護櫃體和抽屜本身,對延長整個櫥櫃和抽屜的使用壽命有明顯的效果;是一項很重要的實用新型功能發明;以歐洲波蘭GRASSHOPPER庫博五金獨家提出該理念和發明創造該產品。可繼續升級為聯動雙緩衝。

3J2me

雙緩衝使用自己創建的螢幕畫筆在創建畫筆的螢幕上作畫,然後再將畫出的螢幕做為Image的對象畫到改螢幕上,這樣就解決了螢幕閃爍
package com.redarmy.tes;
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
public class ExCanvas extends Canvas{
public Image offScreen;//離屏緩衝區
public Graphics offg; //離屏畫筆
public ExCanvas()
{
//繪製和螢幕一樣大小的緩衝區
offScreen = Image.createImage(this.getWidth(), this.getHeight());
//得到離屏緩衝區的畫筆
offg = offScreen.getGraphics();
}
protected void paint(Graphics arg0)
{
//繪製離屏緩衝區
arg0.drawImage(offScreen, 0, 0, Graphics.LEFT|Graphics.TOP);
}
}

4C#

C#雙緩衝解釋

簡單說就是當我們在進行畫圖操作時,系統並不是直接把內容呈現到螢幕
C#雙緩衝
上,而是先在記憶體中保存,然後一次性把結果輸出來,如果沒用雙緩衝的話,你會發現在畫圖過程中螢幕會閃的很厲害,因為後台一直在刷新,而如果等用戶畫完之後再輸出就不會出現這種情況,具體的做法,其實也就是先創建一個點陣圖對象,然後把內容保存在裡面,最後把圖呈現出來。

GDI+的雙緩衝問題

一直以來的誤區:.net1.1 和 .net 2.0 在處理控制項雙緩衝上是有區別的。
.net 1.1 中,使用:this.SetStyle(ControlStyles.DoubleBuffer, true);
.net 2.0中,使用:this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

5畫面閃爍

雙緩衝一、繪製視窗由於大小位置狀態改變進行重繪操作時
繪圖視窗內容或大小每改變一次,都要調用Paint事件進行重繪操作,該操作會使畫面重新刷新一次以維持視窗正常顯示。刷新過程中會導致所有圖元重新繪製,而各個圖元的重繪操作並不會導致Paint事件發生,因此視窗的每一次刷新只會調用Paint事件一次。視窗刷新一次的過程中,每一個圖元的重繪都會立即顯示到視窗,因此整個視窗中,只要是圖元所在的位置,都在刷新,而刷新的時間是有差別的,閃爍現象自然會出現。
所以說,此時導致視窗閃爍現象的關鍵因素並不在於Paint事件調用的次數多少,而在於各個圖元的重繪。
根據以上分析可知,當圖元數目不多時,視窗刷新的位置也不多,視窗閃爍效果並不嚴重;當圖元數目較多時,繪圖視窗進行重繪的圖元數量增加,繪圖視窗每一次刷新都會導致較多的圖元重新繪製,視窗的較多位置都在刷新,閃爍現象自然就會越來越嚴重。特別是圖元比較大繪製時間比較長時,閃爍問題會更加嚴重,因為時間延遲會更長。
解決上述問題的關鍵在於:視窗刷新一次的過程中,讓所有圖元同時顯示到視窗。
二、進行滑鼠跟蹤繪製操作或者對圖元進行變形操作時
當進行滑鼠跟蹤繪製操作或者對圖元進行變形操作時,Paint事件會頻繁發生,這會使視窗的刷新次數大大增加。雖然視窗刷新一次的過程中所有圖元同時顯示到視窗,但也會有時間延遲,因為此時視窗刷新的時間間隔遠小於圖元每一次顯示到視窗所用的時間。因此閃爍現象並不能完全消除!
所以說,此時導致視窗閃爍現象的關鍵因素在於Paint事件發生的次數多少。
解決此問題的關鍵在於:設定窗體或控制項的幾個關鍵屬性。

6使用雙緩衝

解決雙緩衝的關鍵技術:
1、設定顯示圖元控制項的幾個屬性: 必須要設定,否則效果不是很明顯!
this.SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw |
ControlStyles.AllPaintingInWmPaint, true);
2、視窗刷新一次的過程中,讓所有圖元同時顯示到視窗。
可以通過以下幾種方式實現,這幾種方式都涉及到Graphics對象的創建方式。

7具體實現

1、 利用默認雙緩衝
(1)在應用程式中使用雙緩衝的最簡便的方法是使用 .NET Framework 為窗體和控制項提供的默認雙緩衝。通過將 DoubleBuffered 屬性設定為 true
this.DoubleBuffered=true;
(2)使用 SetStyle 方法可以為 Windows 窗體和所創作的 Windows 控制項啟用默認雙緩衝。
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
2、 手工設定雙緩衝
.netframework提供了一個類BufferedGraphicsContext負責單獨分配和管理圖形緩衝區。每個應用程式域都有自己的默認 BufferedGraphicsContext 實例來管理此應用程式的所有默認雙緩衝。大多數情況下,每個應用程式只有一個應用程式域,所以每個應用程式通常只有一個默認 BufferedGraphicsContext。默認 BufferedGraphicsContext 實例由 BufferedGraphicsManager 類管理。通過管理BufferedGraphicsContext實現雙緩衝的步驟如下:
(1)獲得對 BufferedGraphicsContext 類的實例的引用。
(2)通過調用 BufferedGraphicsContext.Allocate 方法創建 BufferedGraphics 類的實例。
(3)通過設定 BufferedGraphics.Graphics 屬性將圖形繪製到圖形緩衝區。
(4)當完成所有圖形緩衝區中的繪製操作時,可調用 BufferedGraphics.Render 方法將緩衝區的內容呈現到與該緩衝區關聯的繪圖圖面或者指定的繪圖圖面。
(5)完成呈現圖形之後,對 BufferedGraphics 實例調用釋放系統資源的 Dispose 方法。
完整的例子,在一個400*400的矩形框內繪製10000個隨機生成的小圓。
BufferedGraphicsContext current = BufferedGraphicsManager.Current; //(1)
BufferedGraphics bg;
bg = current.Allocate(this.CreateGraphics(),this.DisplayRectangle); //(2)
Graphics g = bg.Graphics;//(3)
//隨機 寬400 高400
System.Random rnd = new Random();
int x,y,w,h,r,i;
for (i = 0; i < 10000; i++)
{
x = rnd.Next(400);
y = rnd.Next(400);
r = rnd.Next(20);
w = rnd.Next(10);
h = rnd.Next(10);
g.DrawEllipse(Pens.Blue, x, y, w, h);
}
bg.Render();//(4)
//bg.Render(this.CreateGraphics());
bg.Dispose();//(5)
3、 自己開闢一個緩衝區(如一個不顯示的Bitmap對象),在其中繪製完成後,再一次性顯示。
完整代碼如下:
Bitmap bt = new Bitmap(400, 400);
Graphics bg = Graphics.FromImage(bt);
System.Random rnd = new Random();
int x, y, w, h, r, i;
for (i = 0; i < 10000; i++)
{
x = rnd.Next(400);
y = rnd.Next(400);
r = rnd.Next(20);
w = rnd.Next(10);
h = rnd.Next(10);
bg.DrawEllipse(Pens.Blue, x, y, w, h);
}
this.CreateGraphics().DrawImage(bt, new Point(0, 0));
另外一個例子,差不多

8Graphics創建方式

a、在記憶體上創建一塊和顯示控制項相同大小的畫布,在這塊畫布上創建Graphics對象。
接著所有的圖元都在這塊畫布上繪製,繪製完成以後再使用該畫布覆蓋顯示控制項的背景,從而達到“顯示一次僅刷新一次”的效果!
實現代碼(在OnPaint方法中):
Rectangle rect = e.ClipRectangle;
Bitmap bufferimage = new Bitmap(this.Width, this.Height);
Graphics g = Graphics.FromImage(bufferimage);
g.Clear(this.BackColor);
g.SmoothingMode = SmoothingMode.HighQuality; //高質量
g.PixelOffsetMode = PixelOffsetMode.HighQuality; //高像素偏移質量
foreach (IShape drawobject in doc.drawObjectList)
{
if (rect.IntersectsWith(drawobject.Rect))
{
drawobject.Draw(g);
if (drawobject.TrackerState == config.Module.Core.TrackerState.Selected
&& this.CurrentOperator == Enum.Operator.Transfrom)//僅當編輯節點操作時顯示圖元熱點
{
drawobject.DrawTracker(g);
}
}
}
using (Graphics tg = e.Graphics)
{
tg.DrawImage(bufferimage, 0, 0); //把畫布貼到畫面上
}
b、直接在記憶體上創建Graphics對象:
Rectangle rect = e.ClipRectangle;
BufferedGraphicsContext currentContext = BufferedGraphicsManager.Current;
BufferedGraphics myBuffer = currentContext.Allocate(e.Graphics, e.ClipRectangle);
Graphics g = myBuffer.Graphics;
g.SmoothingMode = SmoothingMode.HighQuality;
g.PixelOffsetMode = PixelOffsetMode.HighSpeed;
g.Clear(this.BackColor);
foreach (IShape drawobject in doc.drawObjectList)
{
if (rect.IntersectsWith(drawobject.Rect))
{
drawobject.Draw(g);
if (drawobject.TrackerState == config.Module.Core.TrackerState.Selected
&& this.CurrentOperator == Enum.Operator.Transfrom)//僅當編輯節點操作時顯示圖元熱點
{
drawobject.DrawTracker(g);
}
}
}
myBuffer.Render(e.Graphics);
g.Dispose();
myBuffer.Dispose();//釋放資源
至此,雙緩衝問題解決,兩種方式的實現效果都一樣,但最後一種方式的占有的記憶體很少,不會出現記憶體泄露
接下來是對acdsee拖動圖片效果的實現。開始不懂雙緩衝,以為雙緩衝可以解決這個問題,結果發現使用了雙緩衝沒啥效果,請教了高人,然後修改了些代碼,完成這個效果。
圖片是在pictureBox1里。
Bitmap currentMap;
bool first = true;
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (zoom == 0)
{
if (e.Button == MouseButtons.Left) //dragging
mousedrag = e.Location;
Image myImage = myMap.GetMap();
currentMap = new Bitmap(myImage);
first = false;
}
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (zoom == 0&&!first)
{
Image img = new Bitmap(Size.Width, Size.Height);
Graphics g = Graphics.FromImage(img);
g.Clear(Color.Transparent);//圖片移動後顯示的底色
g.SmoothingMode = SmoothingMode.HighQuality; //高質量
g.PixelOffsetMode = PixelOffsetMode.HighQuality; //高像素偏移質量
g.DrawImageUnscaled(currentMap, new System.Drawing.Point(e.Location.X - mousedrag.X, e.Location.Y - mousedrag.Y));//在g中移動圖片,原圖在(0,0)畫的,所以直接用new System.Drawing.Point(e.Location.X - mousedrag.X, e.Location.Y - mousedrag.Y)就好。
g.Dispose();
pictureBox1.Image = img;//img是在滑鼠這個位置時生成被移動後的暫時的圖片
}
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
if (zoom == 0)
{
System.Drawing.Point pnt = new System.Drawing.Point(Width / 2 + (mousedrag.X - e.Location.X),
Height / 2 + (mousedrag.Y - e.Location.Y));
myMap.Center = myMap.ImageToWorld(pnt);
pictureBox1.Image = myMap.GetMap();
first = true;
}
}
說說思路,在滑鼠點下時創建一個bitmap,currentMap,用它來存放當前圖像。滑鼠移動時,根據滑鼠位置畫圖,最後,滑鼠up時,重新畫圖。
圖形圖象處理編程過程中,雙緩衝是一種基本的技術。我們知道,如果窗體在回響WM_PAINT訊息的時候要進行複雜的圖形處理,那么窗體在重繪時由於過頻的刷新而引起閃爍現象。解決這一問題的有效方法就是雙緩衝技術。因為窗體在刷新時,總要有一個擦除原來圖象的過程OnEraseBkgnd,它利用背景色填充窗體繪圖區,然後在調用新的繪圖代碼進行重繪,這樣一擦一寫造成了圖象顏色的反差。當WM_PAINT的回響很頻繁的時候,這種反差也就越發明顯。於是我們就看到了閃爍現象。
我們會很自然的想到,避免背景色的填充是最直接的辦法。但是那樣的話,窗體上會變的一團糟。因為每次繪製圖象的時候都沒有將原來的圖象清除,造 成了圖象的殘留,於是窗體重繪時,畫面往往會變的亂七八糟。所以單純的禁止背景重繪是不夠的。我們還要進行重新繪圖,但要求速度很快,於是我們想到了使用 BitBlt函式。它可以支持圖形塊的複製,速度很快。我們可以先在記憶體中作圖,然後用此函式將做好的圖複製到前台,同時禁止背景刷新,這樣就消除了閃 爍。以上也就是雙緩衝繪圖的基本的思路。
首先給出實現的程式,然後再解釋,同樣是在OnDraw(CDC *pDC)中:
CDC MemDC; //首先定義一個顯示設備對象
CBitmap MemBitmap;//定義一個點陣圖對象
//隨後建立與螢幕顯示兼容的記憶體顯示設備
MemDC.CreateCompatibleDC(NULL);
//這時還不能繪圖,因為沒有地方畫 ^_^
//下面建立一個與螢幕顯示兼容的點陣圖,至於點陣圖的大小嘛,可以用視窗的大小,也可以自己定義(如:有滾動條時就要大於當前視窗的大小,在BitBlt時決定拷貝記憶體的哪部分到螢幕上)
MemBitmap.CreateCompatibleBitmap(pDC,nWidth,nHeight);
//將點陣圖選入到記憶體顯示設備中
//只有選入了點陣圖的記憶體顯示設備才有地方繪圖,畫到指定的點陣圖上
CBitmap *pOldBit=MemDC.SelectObject(&MemBitmap);
//先用背景色將點陣圖清除乾淨,這裡我用的是白色作為背景
//你也可以用自己應該用的顏色
MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(255,255,255));
//繪圖
MemDC.MoveTo(……);
MemDC.LineTo(……);
//將記憶體中的圖拷貝到螢幕上進行顯示
pDC->BitBlt(0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY);
//繪圖完成後的清理
//把前面的pOldBit選回來.在刪除MemBitmap之前要先從設備中移除它
MemDC.SelectObject(pOldBit);
MemBitmap.DeleteObject();
MemDC.DeleteDC();
雙緩衝(two way soft-closing)

騎馬抽

也稱作:雙緩衝騎馬抽豪華雙緩衝阻尼抽屜
即騎馬抽在完全拉出和關閉時都具有較好的緩衝效果,能有效避免噪音產生,把抽屜的運動慣性所產生的破壞力降低到最小,有效保護櫃體和抽屜本身,對延長整個櫥櫃和抽屜的使用壽命有明顯的效果;是一項很重要的實用新型功能發明;以歐洲波蘭GRASSHOPPER庫博五金獨家提出該理念和發明創造該產品。可繼續升級為聯動雙緩衝。

J2me

使用自己創建的螢幕畫筆在創建畫筆的螢幕上作畫,然後再將畫出的螢幕做為Image的對象畫到改螢幕上,這樣就解決了螢幕閃爍
package com.redarmy.tes;
import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
public class ExCanvas extends Canvas{
public Image offScreen;//離屏緩衝區
public Graphics offg; //離屏畫筆
public ExCanvas()
{
//繪製和螢幕一樣大小的緩衝區
offScreen = Image.createImage(this.getWidth(), this.getHeight());
//得到離屏緩衝區的畫筆
offg = offScreen.getGraphics();
}
protected void paint(Graphics arg0)
{
//繪製離屏緩衝區
arg0.drawImage(offScreen, 0, 0, Graphics.LEFT|Graphics.TOP);
}
}

C#

C#雙緩衝解釋:
簡單說就是當我們在進行畫圖操作時,系統並不是直接把內容呈現到螢幕上,而是先在記憶體中保存,然後一次性把結果輸出來,如果沒用雙緩衝的話,你會發現在畫圖過程中螢幕會閃的很厲害,因為後台一直在刷新,而如果等用戶畫完之後再輸出就不會出現這種情況,具體的做法,其實也就是先創建一個點陣圖對象,然後把內容保存在裡面,最後把圖呈現出來。
GDI+的雙緩衝問題
一直以來的誤區:.net1.1 和 .net 2.0 在處理控制項雙緩衝上是有區別的。
.net 1.1 中,使用:this.SetStyle(ControlStyles.DoubleBuffer, true);
.net 2.0中,使用:this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
導致畫面閃爍的關鍵原因分析:
一、繪製視窗由於大小位置狀態改變進行重繪操作時
繪圖視窗內容或大小每改變一次,都要調用Paint事件進行重繪操作,該操作會使畫面重新刷新一次以維持視窗正常顯示。刷新過程中會導致所有圖元重新繪製,而各個圖元的重繪操作並不會導致Paint事件發生,因此視窗的每一次刷新只會調用Paint事件一次。視窗刷新一次的過程中,每一個圖元的重繪都會立即顯示到視窗,因此整個視窗中,只要是圖元所在的位置,都在刷新,而刷新的時間是有差別的,閃爍現象自然會出現。
所以說,此時導致視窗閃爍現象的關鍵因素並不在於Paint事件調用的次數多少,而在於各個圖元的重繪。
根據以上分析可知,當圖元數目不多時,視窗刷新的位置也不多,視窗閃爍效果並不嚴重;當圖元數目較多時,繪圖視窗進行重繪的圖元數量增加,繪圖視窗每一次刷新都會導致較多的圖元重新繪製,視窗的較多位置都在刷新,閃爍現象自然就會越來越嚴重。特別是圖元比較大繪製時間比較長時,閃爍問題會更加嚴重,因為時間延遲會更長。
解決上述問題的關鍵在於:視窗刷新一次的過程中,讓所有圖元同時顯示到視窗。
二、進行滑鼠跟蹤繪製操作或者對圖元進行變形操作時
當進行滑鼠跟蹤繪製操作或者對圖元進行變形操作時,Paint事件會頻繁發生,這會使視窗的刷新次數大大增加。雖然視窗刷新一次的過程中所有圖元同時顯示到視窗,但也會有時間延遲,因為此時視窗刷新的時間間隔遠小於圖元每一次顯示到視窗所用的時間。因此閃爍現象並不能完全消除!
所以說,此時導致視窗閃爍現象的關鍵因素在於Paint事件發生的次數多少。
解決此問題的關鍵在於:設定窗體或控制項的幾個關鍵屬性。
使用雙緩衝
解決雙緩衝的關鍵技術:
1、設定顯示圖元控制項的幾個屬性: 必須要設定,否則效果不是很明顯!
this.SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.ResizeRedraw |
ControlStyles.AllPaintingInWmPaint, true);
2、視窗刷新一次的過程中,讓所有圖元同時顯示到視窗。
可以通過以下幾種方式實現,這幾種方式都涉及到Graphics對象的創建方式。
具體實現
1、 利用默認雙緩衝
(1)在應用程式中使用雙緩衝的最簡便的方法是使用 .NET Framework 為窗體和控制項提供的默認雙緩衝。通過將 DoubleBuffered 屬性設定為 true。
this.DoubleBuffered=true;
(2)使用 SetStyle 方法可以為 Windows 窗體和所創作的 Windows 控制項啟用默認雙緩衝。
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
2、 手工設定雙緩衝
.netframework提供了一個類BufferedGraphicsContext負責單獨分配和管理圖形緩衝區。每個應用程式域都有自己的默認 BufferedGraphicsContext 實例來管理此應用程式的所有默認雙緩衝。大多數情況下,每個應用程式只有一個應用程式域,所以每個應用程式通常只有一個默認 BufferedGraphicsContext。默認 BufferedGraphicsContext 實例由 BufferedGraphicsManager 類管理。通過管理BufferedGraphicsContext實現雙緩衝的步驟如下:
(1)獲得對 BufferedGraphicsContext 類的實例的引用。
(2)通過調用 BufferedGraphicsContext.Allocate 方法創建 BufferedGraphics 類的實例。
(3)通過設定 BufferedGraphics.Graphics 屬性將圖形繪製到圖形緩衝區。
(4)當完成所有圖形緩衝區中的繪製操作時,可調用 BufferedGraphics.Render 方法將緩衝區的內容呈現到與該緩衝區關聯的繪圖圖面或者指定的繪圖圖面。
(5)完成呈現圖形之後,對 BufferedGraphics 實例調用釋放系統資源的 Dispose 方法。
完整的例子,在一個400*400的矩形框內繪製10000個隨機生成的小圓。
BufferedGraphicsContext current = BufferedGraphicsManager.Current; //(1)
BufferedGraphics bg;
bg = current.Allocate(this.CreateGraphics(),this.DisplayRectangle); //(2)
Graphics g = bg.Graphics;//(3)
//隨機 寬400 高400
System.Random rnd = new Random();
int x,y,w,h,r,i;
for (i = 0; i < 10000; i++)
{
x = rnd.Next(400);
y = rnd.Next(400);
r = rnd.Next(20);
w = rnd.Next(10);
h = rnd.Next(10);
g.DrawEllipse(Pens.Blue, x, y, w, h);
}
bg.Render();//(4)
//bg.Render(this.CreateGraphics());
bg.Dispose();//(5)
3、 自己開闢一個緩衝區(如一個不顯示的Bitmap對象),在其中繪製完成後,再一次性顯示。
完整代碼如下:
Bitmap bt = new Bitmap(400, 400);
Graphics bg = Graphics.FromImage(bt);
System.Random rnd = new Random();
int x, y, w, h, r, i;
for (i = 0; i < 10000; i++)
{
x = rnd.Next(400);
y = rnd.Next(400);
r = rnd.Next(20);
w = rnd.Next(10);
h = rnd.Next(10);
bg.DrawEllipse(Pens.Blue, x, y, w, h);
}
this.CreateGraphics().DrawImage(bt, new Point(0, 0));
另外一個例子,差不多
Graphics對象的創建方式:
a、在記憶體上創建一塊和顯示控制項相同大小的畫布,在這塊畫布上創建Graphics對象。
接著所有的圖元都在這塊畫布上繪製,繪製完成以後再使用該畫布覆蓋顯示控制項的背景,從而達到“顯示一次僅刷新一次”的效果!
實現代碼(在OnPaint方法中):
Rectangle rect = e.ClipRectangle;
Bitmap bufferimage = new Bitmap(this.Width, this.Height);
Graphics g = Graphics.FromImage(bufferimage);
g.Clear(this.BackColor);
g.SmoothingMode = SmoothingMode.HighQuality; //高質量
g.PixelOffsetMode = PixelOffsetMode.HighQuality; //高像素偏移質量
foreach (IShape drawobject in doc.drawObjectList)
{
if (rect.IntersectsWith(drawobject.Rect))
{
drawobject.Draw(g);
if (drawobject.TrackerState == config.Module.Core.TrackerState.Selected
&& this.CurrentOperator == Enum.Operator.Transfrom)//僅當編輯節點操作時顯示圖元熱點
{
drawobject.DrawTracker(g);
}
}
}
using (Graphics tg = e.Graphics)
{
tg.DrawImage(bufferimage, 0, 0); //把畫布貼到畫面上
}
b、直接在記憶體上創建Graphics對象:
Rectangle rect = e.ClipRectangle;
BufferedGraphicsContext currentContext = BufferedGraphicsManager.Current;
BufferedGraphics myBuffer = currentContext.Allocate(e.Graphics, e.ClipRectangle);
Graphics g = myBuffer.Graphics;
g.SmoothingMode = SmoothingMode.HighQuality;
g.PixelOffsetMode = PixelOffsetMode.HighSpeed;
g.Clear(this.BackColor);
foreach (IShape drawobject in doc.drawObjectList)
{
if (rect.IntersectsWith(drawobject.Rect))
{
drawobject.Draw(g);
if (drawobject.TrackerState == config.Module.Core.TrackerState.Selected
&& this.CurrentOperator == Enum.Operator.Transfrom)//僅當編輯節點操作時顯示圖元熱點
{
drawobject.DrawTracker(g);
}
}
}
myBuffer.Render(e.Graphics);
g.Dispose();
myBuffer.Dispose();//釋放資源
至此,雙緩衝問題解決,兩種方式的實現效果都一樣,但最後一種方式的占有的記憶體很少,不會出現記憶體泄露!
接下來是對acdsee拖動圖片效果的實現。開始不懂雙緩衝,以為雙緩衝可以解決這個問題,結果發現使用了雙緩衝沒啥效果,請教了高人,然後修改了些代碼,完成這個效果。
圖片是在pictureBox1里。
Bitmap currentMap;
bool first = true;
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
if (zoom == 0)
{
if (e.Button == MouseButtons.Left) //dragging
mousedrag = e.Location;
Image myImage = myMap.GetMap();
currentMap = new Bitmap(myImage);
first = false;
}
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (zoom == 0&&!first)
{
Image img = new Bitmap(Size.Width, Size.Height);
Graphics g = Graphics.FromImage(img);
g.Clear(Color.Transparent);//圖片移動後顯示的底色
g.SmoothingMode = SmoothingMode.HighQuality; //高質量
g.PixelOffsetMode = PixelOffsetMode.HighQuality; //高像素偏移質量
g.DrawImageUnscaled(currentMap, new System.Drawing.Point(e.Location.X - mousedrag.X, e.Location.Y - mousedrag.Y));//在g中移動圖片,原圖在(0,0)畫的,所以直接用new System.Drawing.Point(e.Location.X - mousedrag.X, e.Location.Y - mousedrag.Y)就好。
g.Dispose();
pictureBox1.Image = img;//img是在滑鼠這個位置時生成被移動後的暫時的圖片
}
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
if (zoom == 0)
{
System.Drawing.Point pnt = new System.Drawing.Point(Width / 2 + (mousedrag.X - e.Location.X),
Height / 2 + (mousedrag.Y - e.Location.Y));
myMap.Center = myMap.ImageToWorld(pnt);
pictureBox1.Image = myMap.GetMap();
first = true;
}
}
說說思路,在滑鼠點下時創建一個bitmap,currentMap,用它來存放當前圖像。滑鼠移動時,根據滑鼠位置畫圖,最後,滑鼠up時,重新畫圖。

相關詞條

熱門詞條

聯絡我們