文章目录
- 引言
- 默认双缓冲
- SetStyle
- 手动管理双缓冲图形
- BufferedGraphicsManager缓冲图形管理器
- BufferedGraphicsContext 缓冲图形上下文
- BufferedGraphics 图形缓冲区
- 验证双缓冲的效果(Gif动画显示非正常速度)
- 结束语
- 性能对比
引言
图形编程中一个常见的问题就是闪烁,当需要绘制多个复杂的图形时可能导致渲染的图像出现闪烁或其他不可接受的形式。
.Net中提供了双缓冲(Double buffering)来解决这一问题。
双缓冲使用内存缓冲区来解决多个绘制操作相关的闪烁问题。启用双缓冲后,所有的绘制操作都会首先渲染到内存的缓冲区,到所有绘制操作完成后,再直接复制到与其关联的绘图表面上。由于在屏幕上仅执行一次图形操作,因此消除了闪烁等问题
默认双缓冲
在.Net中要启用控件的默认双缓冲功能,只需将设置DoubleBuffered为True或调用SetStyle方法。
SetStyle
原型:
protected void SetStyle (System.Windows.Forms.ControlStyles flag, bool value);
ControlStyles枚举
值 | 说明 |
---|---|
AllPaintingInWmPaint | 如果为 true,则控件忽略窗口消息 WM_ERASEBKGND 以减少闪烁。 仅当将 UserPaint 位设置为 true 时,才应用此样式。 |
DoubleBuffer | 如果为 true,则在缓冲区中进行绘制,并且完成后将结果输出到屏幕。 双缓冲可以防止因重绘控件而引起的闪烁。 如果将 DoubleBuffer 设置为 true,则还应将 UserPaint 和 AllPaintingInWmPaint 设置为 true。 |
UserPaint | 如果为 true,则会由控件而不是由操作系统来绘制控件自身。 如果 false,则不会引发 Paint 事件。 此样式仅适用于从 Control 派生的类。 |
OptimizedDoubleBuffer | 如果为 true,则控件将首先绘制到缓冲区而不是直接绘制到屏幕,这可以减少闪烁。 如果将此属性设置为 true,则还应将 AllPaintingInWmPaint 设置为 true |
ResizeRedraw | 如果为 true,则控件会在调整大小时进行重绘。 |
除了设置DoubleBuffered = True,还可以
this.SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint,
true);
this.UpdateStyles();
手动管理双缓冲图形
手动管理双缓冲图形,需要用到BufferedGraphics和
BufferedGraphicsManager缓冲图形管理器
原型:
public static class BufferedGraphicsManager
作用:提供对应用程序域的主缓冲图形上下文对象的访问。
BufferedGraphicsManager类允许你实现自定义双缓冲。一般是获取其属性Current为 BufferedGraphicsContext类型。
BufferedGraphicsContext 缓冲图形上下文
原型:
public sealed class BufferedGraphicsContext : IDisposable
作用:提供创建可用于双缓冲图形缓冲区的方法。
1、通过 BufferedGraphicsManager.Current获取到一个BufferedGraphicsContext 对象
2、设置MaximumBuffer为最大缓冲区大小(一般是显示图像的宽+1和高+1,缓冲区大小的内存为被长期占用,而图像大小缓冲区大小时会被临时使用,直到该对象被释放)。
3、调用Allocate方法,分配一个指定大小的缓冲区图形(BufferedGraphics)
context = BufferedGraphicsManager.Current;
context.MaximumBuffer = new Size(gifImage.Width + 1, gifImage.Height + 1);
bufferdGraphics = context.Allocate(e.Graphics, new Rectangle(0, 0, gifImage.Width, gifImage.Height));
BufferedGraphics 图形缓冲区
原型:
public sealed class BufferedGraphics : IDisposable
作用:提供用于双缓冲的图形缓冲区。
1、其属性Graphics可用于绘制图形
2、再调用Render方法,绘制到指定的Graphics上。
验证双缓冲的效果(Gif动画显示非正常速度)
通过鼠标点击窗体控制切换不同的效果,分别是
- 禁用双缓冲模式
- 启用默认双缓冲模式
- 自定义管理双缓冲模式
先看看不同方式下的效果
1、不启用双缓冲时,屏幕严重闪烁
2、启用双缓冲时,默认和自定义效果(不知使用方法上有问题)差不多。
3、注意,这个是用来测试绘制效果的,实际的Gif播放不是这样使用。
public partial class FrmDoubleBufferd : Form
{
public FrmDoubleBufferd()
{
InitializeComponent();
}
private void FrmDoubleBufferd_Load(object sender, EventArgs e)
{
Init();
}
/// <summary>
/// 双缓冲类型
/// 0-禁用双缓冲
/// 1-默认双缓冲
/// 2-手管理双缓冲
/// </summary>
private int BufferdType = -1;
private void FrmDoubleBufferd_Click(object sender, EventArgs e)
{
BufferdType++;
if (BufferdType > 2) BufferdType = 0;
if (BufferdType == 0)
{
this.DoubleBuffered = false;
this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, false);
}
else
{
this.DoubleBuffered = true;
this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint, true);
}
CurrFrameIndex = 0;
TotalCount = 0;
elaspedTime = new ConcurrentBag<(long, long)>();
dt = DateTime.Now;
NextGifImage();
}
private int CurrFrameIndex = 0;
private int FrameCount = 0;
FrameDimension Dimension;
private Image GifImage = null;
private Image AIWoman = null;
Rectangle srcRect;
private BufferedGraphicsContext context;
private BufferedGraphics bufferdGraphics;
private void Init()
{
GifImage = Image.FromFile("Heartbeat.gif");
AIWoman = Image.FromFile("AIWoman.png");
srcRect = new Rectangle(0, 0, AIWoman.Width, AIWoman.Height);
if (ImageAnimator.CanAnimate(GifImage))
{
Dimension = new FrameDimension(GifImage.FrameDimensionsList[0]);
FrameCount = GifImage.GetFrameCount(Dimension);
}
context = BufferedGraphicsManager.Current;
context.MaximumBuffer = new Size(GifImage.Width + 1, GifImage.Height + 1);
bufferdGraphics = context.Allocate(this.CreateGraphics(), new Rectangle(0, 0, GifImage.Width, GifImage.Height));
NextGifImage();
}
Random random = new Random((int)DateTime.Now.Ticks);
private void FrmDoubleBufferd_Paint(object sender, PaintEventArgs e)
{
var dstRect = new Rectangle(random.Next(100, 600), random.Next(10, 600), 400, 400);
e.Graphics.DrawImage(GifImage, 0, 0, GifImage.Width, GifImage.Height);
e.Graphics.DrawImage(AIWoman, dstRect, srcRect, GraphicsUnit.Pixel);
if (BufferdType == 0)
{
e.Graphics.DrawString($"禁用双缓冲模式", Font, Brushes.Red, new PointF(20, 20));
DrawElaspedTime(e.Graphics);
}
else if (BufferdType == 1)
{
e.Graphics.DrawString($"默认双缓冲模式", Font, Brushes.Red, new PointF(20, 20));
DrawElaspedTime(e.Graphics);
}
else
{
return;
}
//绘制结束后,显示下一帧
NextGifImage();
}
ConcurrentBag<(long, long)> elaspedTime = new ConcurrentBag<(long, long)>();
private void DrawElaspedTime(Graphics g)
{
int height = 40;
foreach (var time in elaspedTime)
{
g.DrawString($"{time.Item1},耗时:{time.Item2}", Font, Brushes.Red, new PointF(20, height));
height += 20;
}
}
private long TotalCount= 0;
DateTime dt = DateTime.Now;
bool redraw= false;
//切换Gif显示帧
private void NextGifImage()
{
do
{
TotalCount++;
CurrFrameIndex++;
if (CurrFrameIndex >= FrameCount)
{
CurrFrameIndex = 0;
}
if (CurrFrameIndex % 50 == 0)
{
var elasped = (CurrFrameIndex, (long)(DateTime.Now - dt).TotalMilliseconds);
elaspedTime.Add(elasped);
dt = DateTime.Now;
}
//更新为下一帧
GifImage.SelectActiveFrame(Dimension, CurrFrameIndex);
ImageAnimator.UpdateFrames(GifImage);
if (BufferdType == 2)
{//自定义管理双缓冲时,不在Paint事件中更新
var dstRect = new Rectangle(random.Next(100, 600), random.Next(10, 400), 400, 400);
bufferdGraphics.Graphics.Clear(Color.White);
bufferdGraphics.Graphics.DrawImage(GifImage, 0, 0, GifImage.Width, GifImage.Height);
bufferdGraphics.Graphics.DrawImage(AIWoman, dstRect, srcRect, GraphicsUnit.Pixel);
bufferdGraphics.Graphics.DrawString($"自定义管理双缓冲模式", Font, Brushes.Red, new PointF(20, 20));
DrawElaspedTime(bufferdGraphics.Graphics);
bufferdGraphics.Render();
Application.DoEvents();
redraw = true;
}
else
{//触发重绘
this.Invalidate();
redraw = false;
}
}
while(redraw);
}
private void FrmDoubleBufferd_FormClosing(object sender, FormClosingEventArgs e)
{
bufferdGraphics.Dispose();
GifImage.Dispose();
AIWoman.Dispose();
}
}
结束语
性能对比
- 自定义管理双缓冲
- 默认双缓冲
左上角的数字意义是,每绘制50帧(绘制Gif的同时还绘制了美女)的耗时(单位ms)。
原本想测试下,自定义管理双缓冲会不会比默认启动的双缓冲性能要高,不知是使用的方法不问题,还是其他什么原因,没感觉到有更高的性能(反而觉得慢了些),如果您有更好的例子可以说明,麻烦留言,万分感谢。
再次强调,实际的Gif动画播放不是像本文那样实现的,可通过ImageAnimator类的相关方法实现,本文之所以这样写,本是想着每绘制完一帧后,开始处理下一帧,看哪种方法更快。
在未完全吃透自定义管理双缓冲情况,建议还是用默认的双缓冲就可以了。