标题：双缓冲
简介
我们看电视时，看到的屏幕称为OSD层，也就是说，只有在OSD层上显示图像我们才能看到。现在，我需要创建一个虚拟的、看不见但是可以在上面画图（比如说画点、线）的OSD层，我称之为offscreen（后台缓冲区）。这个offscreen存在于内存中，我们在上面画图，这个offscreen上面的东西可以显示在OSD层上，需要一个创建这个offscreen的函数，返回这个offscreen的句柄（整型指针）、宽度、高度、指向新建offscreen数据缓冲区的指针，该缓冲区是一个在函数外创建的offscreen的数据缓冲区，大小是offscreen的高度*宽度*每个像素点数据的大小。闪烁是图形编程的一个常见问题。需要多重复杂绘制操作的图形操作会导致呈现的图像闪烁或具有其他不可接受的外观。双缓冲的使用解决这些问题。双缓冲使用内存缓冲区来解决由多重绘制操作造成的闪烁问题。当启用双缓冲时，所有绘制操作首先呈现到内存缓冲区，而不是屏幕上的绘图图面。所有绘制操作完成后，内存缓冲区直接复制到与其关联的绘图图面。因为在屏幕上只执行一个图形操作，所以消除了由复杂绘制操作造成的图像闪烁。
如何实现双缓冲
在图形图象处理编程过程中,双缓冲是一种基本的技术。我们知道,如果窗体在响应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#双缓冲解释：
简单说就是当我们在进行画图操作时，系统并不是直接把内容呈现到屏幕  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时，重新画图。