双缓冲绘图分析

 1、Windows 绘图原理 
  我们在 Windows 环境下看到各种元素,如菜单、按钮、窗口、图像,从根本上说,都是“画”出来的。这时的屏幕,就相当于一块黑板,而 Windows 下的各种 GDI 要素,如画笔、画刷等,就相当于彩色粉笔了。我们在黑板上手工画图时,是一笔一划的,电脑亦然。只不过电脑的速度比手工快的太多,所以在我们看起来好像所有的图形文字都是同时出现的。 
2、普通绘图方式的局限 
  上述绘图方式我们暂且称之为普通绘图方式吧。虽然这种方式能满足相当一部分的绘图需要,但是当要绘制的对象太复杂,尤其是含有位图时,电脑便力不从心了。这时的画面会显示的很慢,对于运动的画面,会给人“卡”住了的感觉,总之一个字:不爽。 
3、解决之道:双缓冲 
  双缓冲的原理可以这样形象的理解:把电脑屏幕看作一块黑板。首先我们在内存环境中建立一个“虚拟“的黑板,然后在这块黑板上绘制复杂的图形,等图形全部绘制完毕的时候,再一次性的把内存中绘制好的图形“拷贝”到另一块黑板(屏幕)上。采取这种方法可以提高绘图速度,极大的改善绘图效果。

  实现过程如下:

  1、在内存中创建与画布一致的缓冲区

  2、在缓冲区画图

  3、将缓冲区位图拷贝到当前画布上

  4、释放内存缓冲区

绘图示例:

  Winform应用程序中添加一个新的窗体;窗体中放置三个timer 分别使用原始画图模式、bitmap双缓画图模式、BufferedGraphicsContext双缓冲画图模式,Timer的Inteval设置为10;另外的三组按钮分别控制timer的开关;

引用的命名空间:

  using System.Drawing;

  using System.Drawing.Drawing2D;

DateTime t1 = DateTime.Now;
Graphics g = this.CreateGraphics();
LinearGradientBrush brush;
if (flag)
{
brush = new LinearGradientBrush(new PointF(0.0f, 0.0f), new PointF(700.0f, 300.0f), Color.Blue, Color.Red);
flag = false;
}
else
{
brush = new LinearGradientBrush(new PointF(0.0f, 0.0f), new PointF(400.0f, 300.0f), Color.Blue, Color.Red);
flag = true;
}
for (int i = ; i < ; i++)
{
for (int j = ; j < ; j++)
{
g.FillEllipse(brush, j * , i * , , );
}
} DateTime t2 = DateTime.Now;
TimeSpan ts = t2 - t1;
float per = / ts.Milliseconds;
label1.Text = "速度:" + per.ToString() + "帧/s";

Timer1 基本画图模式

 DateTime t1 = DateTime.Now;
Bitmap bmp = new Bitmap(, );
Graphics g = Graphics.FromImage(bmp);
g.SmoothingMode = SmoothingMode.HighQuality;//画面呈现质量
g.PixelOffsetMode = PixelOffsetMode.HighQuality;//像素偏移模式
LinearGradientBrush brush;
if (flag)
{
brush = new LinearGradientBrush(new PointF(0.0f, 0.0f), new PointF(700.0f, 300.0f), Color.Blue, Color.Red);
flag = false;
}
else
{
brush = new LinearGradientBrush(new PointF(0.0f, 0.0f), new PointF(400.0f, 300.0f), Color.Blue, Color.Red);
flag = true;
}
for (int i = ; i < ; i++)
{
for (int j = ; j < ; j++)
{
g.FillEllipse(brush, j * , i * , , );
}
}
this.CreateGraphics().DrawImage(bmp, , );//把画布贴到画面上
DateTime t2 = DateTime.Now;
TimeSpan ts = t2 - t1;
float per = / ts.Milliseconds;
label2.Text = "速度:" + per.ToString() + "帧/s";

Timer2 Bitmap方法绘图

  绘图过程:

1、在内存中建立一块“虚拟画布”:

  Bitmap bmp = new Bitmap(600, 600);

2、获取这块内存画布的Graphics引用:

  Graphics g = Graphics.FromImage(bmp);

3、在这块内存画布上绘图:

  g.FillEllipse(brush, 100, 100, 100, 100);

4、将内存画布画到窗口中

  this.CreateGraphics().DrawImage(bmp, 0, 0);

 DateTime t1 = DateTime.Now;
BufferedGraphicsContext current = BufferedGraphicsManager.Current;//提供创建图形缓冲区的方法
BufferedGraphics bg;
bg = current.Allocate(this.CreateGraphics(), this.DisplayRectangle);
Graphics g = bg.Graphics;
LinearGradientBrush brush;
if (flag)
{
brush = new LinearGradientBrush(new PointF(0.0f, 0.0f), new PointF(700.0f, 300.0f), Color.Blue, Color.Red);
flag = false;
}
else
{
brush = new LinearGradientBrush(new PointF(0.0f, 0.0f), new PointF(400.0f, 300.0f), Color.Blue, Color.Red);
flag = true;
}
for (int i = ; i < ; i++)
{
for (int j = ; j < ; j++)
{
g.FillEllipse(brush, j * , i * , , );
}
} bg.Render();
bg.Dispose();
DateTime t2 = DateTime.Now;
TimeSpan ts = t2 - t1;
float per = / ts.Milliseconds;
label3.Text = "速度:" + per.ToString() + "帧/s";

Timer3 BufferedGraphicsContext双缓冲绘图

  绘图过程:

1、获得对 BufferedGraphicsContext 类的实例的引用。 
2、通过调用 BufferedGraphicsContext.Allocate 方法创建 BufferedGraphics 类的实例。

3、通过设臵 BufferedGraphics.Graphics 属性将图形绘制到图形缓冲区。 
4、当完成所有图形缓冲区中的绘制操作时,可调用 
BufferedGraphics.Render 方法将缓冲区的内容呈现到与该缓冲区关联的绘图图面或者指定的绘图图面。

5、完成呈现图形之后,对 BufferedGraphics 实例调用释放系统资源的 Dispose 方法。

绘图效果:

上述的例子中可以看出

  基本的画图模式中会明显的感觉到画面从上到下依次刷新,刷新speed为1~2帧/s  图像刷新速度很慢

  使用Bitmap绘图模式 看到的画面是在不停的闪烁,刷新speed为13~14帧/s  图像刷新速度加快很多

  使用BufferedGraphicsContext双缓冲模式 看到画面闪烁更快,刷新speed为27~28帧/s  图像刷新速度最快

其他绘图注意点:

inform中让pictureBox 显示的图片旋转

img.RotateFlip(RotateFlipType.Rotate90FlipNone);
顺时针旋转90度 RotateFlipType.Rotate90FlipNone
逆时针旋转90度 RotateFlipType.Rotate270FlipNone
水平翻转 RotateFlipType.Rotate180FlipY
垂直翻转 RotateFlipType.Rotate180FlipX

>>GraphicsPath 将相邻的点连接成线

Graphics g = this.CreateGraphics();
System.Drawing.Drawing2D.GraphicsPath path = new System.Drawing.Drawing2D.GraphicsPath();
for (int i = ; i < lstPoint.Count - ; i++)
{
path.AddLine(new Point(lstPoint[i], dicPoint[lstPoint[i]]), new Point(lstPoint[i + ], dicPoint[lstPoint[i + ]]));//示例中添加的是前后两个点位的x、y坐标
}
SolidBrush brush=new SolidBrush (c_Line);
g.DrawPath(new Pen(brush), path);

GraphicsPath

>>Graphics.DrawCurve可以将两条直线的连接部位做平滑处理

Graphics g = this.CreateGraphics();
Point[] array_Point = new Point[lstPoint.Count];
for (int i = ; i < lstPoint.Count; i++)
{
array_Point[i].X = lstPoint[i];
array_Point[i].Y = dicPoint[lstPoint[i]];
}
SolidBrush brush=new SolidBrush (c_Line);
g.DrawCurve(new Pen(brush), array_Point);

DrawCurve

>>GDI绘制正弦曲线

  void DrawSine()
{
//为了方便查看效果,在这里我定义了一个常量。
//它在定义数组的长度和for循环中都要用到。
const int size = ; double[] x = new double[size]; Graphics graphics = this.CreateGraphics();
Pen pen = new Pen(Color.Teal); //画正弦曲线的横轴间距参数。建议所用的值应该是 正数且是2的倍数。
//在这里采用2。
int val = ; float temp = 0.0f; //把画布下移100。为什么要这样做,只要你把这一句给注释掉,运行一下代码,
//你就会明白是为什么?
graphics.TranslateTransform(, ); for (int i = ; i < size; i++)
{
//改变32,实现正弦曲线宽度的变化。
//改100,实现正弦曲线高度的变化。
x[i] = Math.Sin( * Math.PI * i / ) * ; graphics.DrawLine(pen, i * val, temp, i * val + val / , (float)x[i]);
temp = (float)x[i];
}
}

c# 实现正弦曲线

>>GDI绘制线性渐变(使用LinearGradientBrush画刷)

Graphics g = this.panel1.CreateGraphics();
Pen pen = new Pen(Color.Red, );
g.DrawEllipse(pen, , , , );
Rectangle rect=new Rectangle (,,,);
LinearGradientBrush brush = new LinearGradientBrush(rect, Color.Blue, Color.Green, LinearGradientMode.BackwardDiagonal);
g.FillEllipse(brush, rect);

线性渐变

>>GDI实现多色线性渐变(使用LinearGradientBrush画刷)

Graphics g = this.panel2.CreateGraphics();
g.Clear(Color.White); //定义渐变颜色数组
Color[] colors =
{
Color.Blue,
Color.Green,
Color.Blue
}; float[] positions =
{
0.0f,
0.5f,
1.0f
}; //定义ColorBlend对象
ColorBlend colorBlend = new ColorBlend();
colorBlend.Colors = colors;
colorBlend.Positions = positions; Rectangle rect = new Rectangle(, , , );
//定义线型渐变画刷
using (LinearGradientBrush lBrush = new LinearGradientBrush(rect, Color.White, Color.Black, LinearGradientMode.Horizontal))
{ //设置渐变画刷的多色渐变信息
lBrush.InterpolationColors = colorBlend; g.FillRectangle(lBrush, rect);
}

多色渐变

>>GDI实现路径渐变(使用PathGradientBrush画刷)

private void button1_Click(object sender, EventArgs e)
{
Draw(pictureBox1.Handle, new Point(, ), new Size(, ));
} private void Draw(IntPtr winHandle, Point location, Size size)
{
Graphics g = Graphics.FromHwnd(winHandle);
GraphicsPath gp = new GraphicsPath();
Rectangle rec = new Rectangle(location, size);
// gp.AddRectangle(rec);
gp.AddEllipse(rec);
Color[] surroundColor = new Color[] { Color.LightGreen };
PathGradientBrush pb = new PathGradientBrush(gp);
pb.CenterColor = Color.Red;
pb.SurroundColors = surroundColor;
g.FillPath(pb, gp);
}

路径渐变

更多关于GDI颜色渐变的使用: https://msdn.microsoft.com/zh-cn/library/s6fxh562%28v=vs.110%29.aspx

c# GDI画图 双缓冲画图分析的更多相关文章

  1. C#-gdi画图,双缓冲画图,Paint事件的触发---ShinePans

    在使用gdi技术画图时,有时会发现图形线条不够流畅,或者在改变窗口大小时会闪烁不断的现象.(Use DoubleBuffer to solve it!)                         ...

  2. VC双缓冲画图技术介绍

    双缓冲画图,它是一种主要的图形图像画图技术.首先,它在内存中创建一个与屏幕画图区域一致的对象,然后将图形绘制到内存中的这个对象上,最后把这个对象上的图形数据一次性地拷贝并显示到屏幕上. 这样的技术能够 ...

  3. 简单的GDI+双缓冲的分析与实现

    为什么要使用双缓冲绘制 在进行多图元绘制的时候: 因为是要一个一个画上去,所以每画一个图元,系统就要做一次图形的绘制操作,图形的重绘是很占用资源的,特别当需要重绘的图形数量很多的时候,所造成的消耗就特 ...

  4. C#-gdi绘图,双缓冲绘图,Paint事件的触发

    一. 画面闪烁问题与双缓冲技术 1.1 导致画面闪烁的关键原因分析: 1  绘制窗口由于大小位置状态改变进行重绘操作时 绘图窗口内容或大小每改变一次,都要调用Paint事件进行重绘操作,该操作会使画面 ...

  5. Win32双缓冲画图原理

    网上有许多文章讲述了如何使用Visual C++程序实现双缓冲,都是用C++面向对象语言写的,可能对很多没有接触过面向对象语言的C语言初学者来说理解起来有些困难,并且有些好心人也只是把源代码贴上去,不 ...

  6. C++双缓冲多线程分析大文件词频

    实习生活告一段落,我正式从一名.NET程序员转入Java阵营,不得不说刚开始用Java的东西是多么的不习惯,但是经过三个月的使用与开发,我也发现了Java的优势:不在于语言,而在于开源.这意味着有更多 ...

  7. MFC中利用GDI+进行双缓冲作图的有关设置

    这里只是在遇到实际问题的时候提出的一种解决方法,用以处理闪屏问题. 首先要做的是对GDI的一个设置问题: 在应用程序类中添加一个保护权限数据成员 class C...App: {... private ...

  8. WinForm之GDI手动双缓冲技术

    private void button1_Click(object sender, EventArgs e) { Bitmap bmp = new Bitmap(this.picturebox.Wid ...

  9. GDI+实现双缓冲绘图方法一

    private void Form5_MouseMove(object sender, MouseEventArgs e) { int intOX = rectDrawArea.X; int intO ...

随机推荐

  1. 第三章:Web表单

    感谢作者 –> 原文链接 本文翻译自 The Flask Mega-Tutorial Part III: Web Forms 这是Flask Mega-Tutorial系列的第三部分,我将告诉你 ...

  2. TouTiao开源项目 分析笔记14 段子评论

    1.段子页面详情 1.1.先看看预览界面吧 左边的页面已经实现了,现在的目的就是要实现点击左侧的每一个item 然后跳转到右边相应的段子详情页面. 1.2.首先肯定有右侧这个活动==>JokeC ...

  3. 剑指Offer - 九度1283 - 第一个只出现一次的字符

    剑指Offer - 九度1283 - 第一个只出现一次的字符2013-11-21 21:13 题目描述: 在一个字符串(1<=字符串长度<=10000,全部由大写字母组成)中找到第一个只出 ...

  4. CSS系列(8) CSS后代选择器和子选择器详解

    一.CSS后代选择器详解 1,  生动介绍基本概念 一个标签嵌B在另一个标签A内部,B就是A的后代. 而且,B的后代也是A的后代,这就叫“子子孙孙无穷尽也”. 比如: <div> < ...

  5. ptmalloc,tcmalloc和jemalloc内存分配策略研究 ? I'm OWen..

    转摘于http://www.360doc.com/content/13/0915/09/8363527_314549949.shtml 最近看了glibc的ptmaoolc,Goolge的tcmall ...

  6. Python 两种方式实现斐波那契数列

    斐波那契数列指的是这样一个数列 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233,377,610,987,1597,2584,4181,6765,10946 ...

  7. Django学习笔记(二):使用Template让HTML、CSS参与网页建立

    Django学习笔记(二):使用Template让HTML.CSS参与网页建立 通过本文章实现: 了解Django中Template的使用 让HTML.CSS等参与网页建立 利用静态文件应用网页样式 ...

  8. Windows环境下,python webdriver环境搭建

    最近刚开始学习selenium,这是我从虫师的<selenium2自动测试实战--基于Python语言>这本书上学到搭建环境的步骤,里面有加上我的一些总结,希望对大家有所帮助!   准备工 ...

  9. 使用Cookie保存用户和密码然后自动登录

    login.html <!DOCTYPE html> <html lang="en"> <head> <meta charset=&quo ...

  10. Vue 使用Spread.js没有层级关系(隐藏与显示)

    Vue 使用Spread.js没有层级关系(隐藏与显示) 1.vue会给元素加一个监控属性.去掉 spread.js没有层级关系过半是column中值的问题