最近在做使用GDI+绘制K线界面发现传统的GDI+绘制方式效率比较低,根本无法满足K线界面及时刷新的速度要求。
所以做了个GDI+绘制图形界面的试验,改试验主要在一个600×600的区域内每隔10MS绘制60×60个点,每隔10MS改变其颜色,并记录每次绘制的帧数作为比较GDI+绘图性能的依据(这个界面是摘自网上)
在这里定义个一个timer控件,刷新时间设为10ms,tick事件里写入一下代码
DateTime t = DateTime.Now;
ClientDC = this.CreateGraphics();
if (flag)
{
brush =
brush1;
flag = false;
}
else
{
brush = brush2;
flag = true;
}
for (int j = 0; j < 60; j++)
{
for (int i = 0; i < 60; i++)
{
ClientDC.FillEllipse(brush, i * 10, j * 10, 10, 10);
}
}
DateTime t2 = DateTime.Now;
TimeSpan sp = t2 - t;
float per = 1000 / sp.Milliseconds;
this.label1.Text = "速度:" + per.ToString() + "帧/秒";
这里我没有使用任何的双缓冲技术,直接向显示设备中画入图形,运行程序发现帧数仅为5到7帧,闪烁现象明显。
我简要分析下原因,GDI+画图,是将所有的图形先换入显卡的缓存中看,显卡会每隔一段时间将显存中的内容输出到显示器中,如果在这个显示周期内,无法将所有的图形都放到显存中,就会出现闪烁的现象,那改如何消除闪烁呢答案就是双缓冲。
双缓冲的主要思路是在内存中开辟一块和要进行画图区域大小一致的内存区域,我们一般使用BitMap类来创建这个内存区域,然后将所有的图形先画到这个内存里,画完之后, 在一起放入到显卡显存中,这样就不存在显示的内容会在不同的的显示周期内显示到设备上,也就从消除了闪烁。
双缓冲的主要代码如下:
DateTime t = DateTime.Now;
memDC = Graphics.FromImage(bitmap);
if (flag)
{
brush =
brush1;
flag = false;
}
else
{
brush = brush2;
flag = true;
}
for (int j = 0; j < 60; j++)
{
for (int i = 0; i < 60; i++)
{
memDC.FillEllipse(brush, i * 10, j * 10, 10, 10);
}
}
this.CreateGraphics().DrawImage(bitmap,new PointF(600f,600f)
);
DateTime t2 = DateTime.Now;
TimeSpan sp = t2 - t;
float per = 1000 / sp.Milliseconds;
this.label1.Text = "速度:" + per.ToString() + "帧/秒";
使用双缓冲后运行程序,帧数可达到14到17帧,是不是用双缓冲技术的两倍, memDC = Graphics.FromImage(bitmap); 这里从bitmap创建一个Graphics对象
然后向bitmap中画如图形memDC.FillEllipse(brush, i * 10, j * 10, 10, 10); 最后所有的图形绘制完毕之后,在同一放入显示设备中的显存里this.CreateGraphics().DrawImage(bitmap,new PointF(600f,600f)。
双缓冲技术有效的解决了GDI+绘图时屏幕闪烁的问题,但对于K线图这种图形较多的程序来说,仅仅使用双缓冲技术还是不够的。
DrawImage函数效率是十分低的,这拖慢了内存图形画到显存中进行显示的效率, 解决的办法是引入bitblt来进行从内存中向显存中进行绘图,bitblt是gdi32.dll中的一个函数,使用P/Invoke的方式使托管代码调用非托管代码:
[System.Runtime.InteropServices.DllImport("gdi32.dll")]
private static extern int BitBlt(
IntPtr hdcDest, // handle to destination DC (device context)
int nXDest, // x-coord of destination upper-left corner
int nYDest, // y-coord of destination upper-left corner
int nWidth, // width of destination rectangle
int nHeight, // height of destination rectangle
IntPtr hdcSrc, // handle to source DC
int nXSrc, // x-coordinate of source upper-left corner
int nYSrc, // y-coordinate of source upper-left corner
System.Int32 dwRop // raster operation code
);
修改后的tick事件代码如下:
try
{
DateTime t = DateTime.Now;
offScreenDC = Graphics.FromImage(bitmap);
ClientDC = this.CreateGraphics();
ClientDC.SetClip(new RectangleF(0, 0, 600, 600));
hdc = ClientDC.GetHdc();
memdc = GDI.CreateCompatibleDC(hdc);
GDI.SelectObject(memdc, bitmap.GetHbitmap());
memDC = Graphics.FromHdc(memdc);
if (flag)
{
brush =
brush1;
flag = false;
}
else
{
brush = brush2;
flag = true;
}
for (int j = 0; j < 60; j++)
{
for (int i = 0; i < 60; i++)
{
memDC.FillEllipse(brush, i * 10, j * 10, 10, 10);
}
}
GDI.GDIBitBlt(hdc, 0, 0, 600, 600, memdc, 0, 0, 13369376);
DateTime t2 = DateTime.Now;
TimeSpan sp = t2 - t;
float per = 1000 / sp.Milliseconds;
this.label1.Text = "速度:" + per.ToString() + "帧/秒";
}
finally
{
ClientDC.ReleaseHdc(hdc);
GDI.DeleteDC(memdc);
GDI.DeleteObject(bitmap.GetHbitmap());
//bitmap.Dispose();
}
使用bitblt进行绘图后帧数可达到23至26帧左右。