VC中的GDI双缓冲绘图
- 格式:ppt
- 大小:639.00 KB
- 文档页数:9
C# WinForm利用GDI+的双缓冲技术来进步绘图效率前言进入.NET时代,Windows的绘图技术也从GDI晋级到了GDI+,从名字就能知道GDI+是对以前传统GDI 绘图技术的一次晋级,不过在微软几乎把所有的新技术都冠之.NET的情况下,GDI+竟然不叫做,还真让我感到有点意外了。
:〕GDI+在一种与设备无关的环境下提供了一套统一的绘图编程模型,极大的进步了Windows绘图编程的方便性,我们再也不用创立什么各种各样复杂的设备环境了,说实话,我如今想起来都头疼。
题归正传,关于如何进展GDI+的根本编程,我不能过多的加以描绘,假如有对此概念还不太清楚的朋友,建议先去理解一下相关的资料,我们在这里主要讨论的是一种进步绘图效率〔主要是动画效率〕的双缓冲技术在GDI+中的应用和实现。
实现目的为了能清楚的比照应用双缓冲技术前后的效果,我编写了一段程序来进展测试。
首先,我创立了一个普通的Windows Application,在主Form中,我放置了一个定时器:timer1,然后将它的Interval属性设置为10,然后在Form上放置两个按纽,分别用来控制定时器的开启和关闭,最后,我还放置了一个label控件,用来显示绘图的帧数。
测试程序在timer1的timer1_Tick事件中,我写下了如下的代码(其中flag是一个bool型标志变量):DateTime t1 = DateTime.Now;Graphics g = this.CreateGraphics();if(flag){brush = new LinearGradientBrush(new PointF(0.0f, 0.0f),new PointF(700.0f, 300.0f), Color.Red, Color.Blue);flag = false;}else{brush = new LinearGradientBrush(new PointF(0.0f, 0.0f),new PointF(700.0f, 300.0f), Color.Blue, Color.Red);flag = true;}for(int j = 0; j < 60; j ++){for(int i = 0; i < 60; i++){g.FillEllipse(brush, i * 10, j * 10, 10, 10);}}DateTime t2 = DateTime.Now;TimeSpan sp = t2 - t1;float per = 1000 / liseconds;运行后,我点击“开场〞按纽,效果如下列图所示:应用双缓冲以前的效果图〔帧数:5帧/秒〕正如大家所看到的,我在程序中使用循环画了几百个圆形,然后在每次的定时器脉冲事件中使用不同方向的线性渐变来对它们进展填充,形成了一个动画效果。
早前曾为此问题在CSDN发帖求助(GDI+ 如何使用双缓冲绘制图像),得到了一个GDI+下较可行的方法,虽然绘制效果比直接绘制要好一些,不过还不能跟GDI的双缓冲方式比肩。
现在,我终于找到了一个理想的实现方式,效果与GDI的实现不相上下,代码如下:/*C++ code*/RECT rc;GetClientRect(g_hwnd,&rc);Bitmap bmp(int(rc.right),int(rc.bottom));Graphics bmpGraphics(&bmp);bmpGraphics.SetSmoothingMode(SmoothingModeAntiAlias);/*Drawing on bitmap*/SolidBrush bkBrush(Color(0,0,0));bmpGraphics.FillRectangle(&bkBrush,0,0,rc.right,rc.bottom);/*Drawing on DC*/Graphics graphics(hdc);/*Important! Create a CacheBitmap object for quick drawing*/CachedBitmap cachedBmp(&bmp,&graphics);graphics.DrawCachedBitmap(&cachedBmp,0,0);以上的绘制代码最区别于网络上其他GDI+实现的一处就是,在最后添加了一个CacheBitmap对象用于快速绘制。
CacheBitmap是一个包含了bmp全部象素,并且针对graphics所关联的DC做过特别优化的位图对象。
这点可以从其构造参数上看到。
关于双缓冲的实现还有一点十分关键,虽然它不属于双缓冲实现的核心。
如果绘制需要经常的重绘背景,则需要自己拦截WM_ERASEBKGND消息,并在处理函数中什么也不做,即此消息发生时不重画背景,背景的重画在WM_PAINT中全权控制。
1创建画笔,画刷void CGraphView::OnDraw(CDC* pDC){CGraphDoc* pDoc = GetDocument( );ASSERT_V ALID(pDoc);// TODO: add draw code for native data here// 使用画笔第一步:创建一支新画笔CPen pen(PS_SOLID, 1, RGB(255,0,0));CPen pen(PS_DASHDOT, 1, RGB(255,0,0));//第二步:选择新画笔到设备环境中,同时保存旧画笔CPen * pOldPen = pDC->SelectObject(&pen);// 使用画刷第一步:创建一支新画刷CBrush brush(RGB(255,0,0)); //创建实体画刷CBrush brush(HS_CROSS, RGB(255,0,0)); //创建阴影模式画刷//第二步:选择新画刷到设备环境中,同时保存旧画刷CBrush * pOldBrush = pDC->SelectObject(&brush);// 常用的绘图函数////绘制一个彩色点pDC->TextOut(20, 20, "点");pDC->SetPixel(100, 40, RGB(255,0,0));//绘制直线pDC->TextOut(320, 20,"线段");pDC->MoveTo(400, 40);pDC->LineTo(500, 40);//绘制折线pDC->TextOut(20, 170, "折线");POINT polyline[4]={{240,240},{80,120},{240,120},{80,240}}; pDC->Polyline(polyline,4);//绘制矩形pDC->TextOut(320, 170, "矩形");pDC->Rectangle(390, 110, 600, 230);//绘制椭圆pDC->TextOut(20, 320, "椭圆");pDC->Ellipse(80, 260, 280, 380);//绘制多边形pDC->TextOut(320, 320, "多边形");POINT polygon[3]={{380,330},{530,260},{500,360}};pDC->Polygon(polygon,3);//第三步:恢复设备环境的旧画笔,以便释放新画笔// pDC->SelectObject(pOldPen);//第三步:恢复设备环境的旧画刷,以便释放新画刷// pDC->SelectObject(pOldBrush);}2创建字体的两种方法第一种创建字体的方法:使用CreateFont函数,如下定义:CFont::CreateFontBOOL CreateFont( int nHeight, //字体的高度int nWidth, //字体的宽度int nEscapement, //字体显示的角度int nOrientation, //字体的角度int nWeight, //字体的磅数BYTE bItalic, //斜体字体BYTE bUnderline, //带下划线的字体BYTE cStrikeOut, //带删除线的字体BYTE nCharSet, //所需的字符集BYTE nOutPrecision, //输出的精度BYTE nClipPrecision, //裁减的精度BYTE nQuality, //逻辑字体与输出设备的实际//字体之间的精度BYTE nPitchAndFamily, //字体间距和字体集LPCTSTR lpszFacename //字体名称);例子:font.CreateFont(12, // nHeight0, // nWidth0, // nEscapement0, // nOrientationFW_NORMAL, // nWeightFALSE, // bItalicFALSE, // bUnderline0, // cStrikeOutANSI_CHARSET, // nCharSetOUT_DEFAULT_PRECIS, // nOutPrecisionCLIP_DEFAULT_PRECIS, // nClipPrecision DEFAULT_QUALITY, // nQualityDEFAULT_PITCH | FF_SWISS, // nPitchAndFamily "Arial"); // lpszFacename一般只修改几项。
C#环境下GDI+绘图效率研究作者:温铭毅来源:《中国科技博览》2015年第05期[摘要]本文在简单介绍GDI和GDI+的基础上,指出GDI+在绘制动画时效率不足的劣势,并对三种不同的GDI+动画绘图方式进行了详细的阐述;最后实现了一个GDI+界面绘图程序,比较和验证了三种不同方式的绘图性能。
实验表明,使用双缓冲和bitblt结合的方式,可以有效的解决直接绘制方式的闪烁问题,提高绘图效率。
[关键词]GDI+;动态绘图;双缓冲;bitblt中图分类号:P631.84 文献标识码:A 文章编号:1009-914X(2015)05-0134-011.引言GDI是微软公司的著名的二维图形引擎,GDI+则是微软公司为了提高显示效果而推出的一种新型图形引擎。
GDI是Graphics Device Interface的缩写,含义是图形设备接口,它的主要任务是负责系统与绘图程序之间的信息交换,处理所有Windows程序的图形输出。
GDI+是Windows XP中的一个子系统,它主要负责在显示屏幕和打印设备输出有关信息,它是一组通过C++类实现的应用程序编程接口。
顾名思义,GDI+是以前版本GDI的继承者,出于兼容性考虑,Windows XP仍然支持以前版本的GDI,但是在开发新应用程序的时候,开发人员为了满足图形输出需要应该使用GDI+,因为GDI+对以前的Windows版本中GDI进行了优化,并添加了许多新的功能。
作为图形设备接口的GDI+使得应用程序开发人员在输出屏幕和打印机信息的时候无需考虑具体显示设备的细节,他们只需调用GDI+库输出的类的一些方法即可完成图形操作,真正的绘图工作由这些方法交给特定的设备驱动程序来完成,GDI+使得图形硬件和应用程序相互隔离.从而使开发人员编写设备无关的应用程序变得非常容易。
Microsoft Windows GDI+服务分为以下3个主要部分:(1)二维矢量图形。
矢量图形由图元组成,而图元则由一系列坐标系统的点集组成。
c# GDI+简单绘图最近对GDI+这个东西接触的比较多,也做了些简单的实例,比如绘图板,仿QQ截图等.最早接触这个类,是因为想做仿QQ截图的效果.巧的很,学会了如何做截图后,.NET课堂上老师也正巧要讲关于c#绘图方面的知识,并且我自己又在网上学习金老师的培训班,也是要用到这个类.在学习中有一些体会,所以准备把这些体会记下来,因为内容比较多,可能我会分几次写.废话不多说了,我们先来认识一下这个GDI+,看看它到底长什么样.GDI+:Graphics Device Interface Plus也就是图形设备接口,提供了各种丰富的图形图像处理功能;在C#.NET中,使用GDI+处理二维(2D)的图形和图像,使用DirectX处理三维(3D)的图形图像,图形图像处理用到的主要命名空间是System . Drawing:提供了对GDI+基本图形功能的访问,主要有Graphics 类、Bitmap类、从Brush类继承的类、Font类、Icon类、Image类、Pen类、Color类等.大概了解了什么是GDI+后,我们来看一下绘图要用到的主要工具,要画图,肯定要画板吧,在C#中画板可以通过Graphics这个类来创建,有了画板,总得弄个笔什么之类的吧,不然怎么画呀,难不成我们用手指画.笔又可以分好多种类,比如铅笔,画刷等.它们的区别主要是铅笔可以用来画线条,而画刷呢,嘿嘿,自己考虑下.在c#中我们可以用Pen,Brush类来实现类似功能.颜料则自然是用Color类了.有了工具,我们就可以开始动手了!(所需命名空间:using ;)实现效果:在空白窗体中画基本图形首先准备一个画板:创建一个画板主要有3种方式:A: 在窗体或控件的Paint事件中直接引用Graphics对象B: 利用窗体或某个控件的CreateGraphics方法C: 从继承自图像的任何对象创建Graphics对象这次我们就先以A为例说明问题:private void Form1_Paint(object sender, PaintEventArgs e){Graphics g = ; }然后,我们要只笔:private void Form1_Paint(object sender, PaintEventArgs e){Graphics g = ; Pen p = new Pen, 2);private void Form1_Paint(object sender, PaintEventArgs e){Graphics g = ; Pen p = new Pen, 2);1.首先我们来看下上一片中我们使用过的Pen.Pen的属性主要有: Color(颜色),DashCap(短划线终点形状),DashStyle(虚线样式),EndCap(线尾形状), StartCap(线头形状),Width(粗细)等.我们可以用Pen 来画虚线,带箭头的直线等Pen p = new Pen, 5);下来我们来看下Brush的使用作用:我们可以用画刷填充各种图形形状,如矩形、椭圆、扇形、多边形和封闭路径等,主要有几种不同类型的画刷:•SolidBrush:画刷最简单的形式,用纯色进行绘制•HatchBrush:类似于 SolidBrush,但是可以利用该类从大量预设的图案中选择绘制时要使用的图案,而不是纯色•TextureBrush:使用纹理(如图像)进行绘制•LinearGradientBrush:使用沿渐变混合的两种颜色进行绘制•PathGradientBrush :基于编程者定义的唯一路径,使用复杂的混合色渐变进行绘制我们这里只是简单介绍使用其中的几种:Graphics g = ();Rectangle rect = new Rectangle(10, 10, 50, 50);标轴变换在winform中的坐标轴和我们平时接触的平面直角坐标轴不同,winform中的坐标轴方向完全相反:窗体的左上角为原点(0,0),水平向左则X增大,垂直下向则Y增大接下来,我们来实际操作下,通过旋转坐标轴的方向来画出不同角度的图案,或通过更改坐标原点的位置来平衡坐标轴的位置.Graphics g = ();后我们来看下Graphics这个画板上我们还可以画什么其实我们上面用到的都是在画一些简单的图形,直线,矩形,扇形,圆孤等,我们还可以用它来绘制图片,这可以用它的DrawImage方法.这里我不详细讲解,大家有兴趣可以自己去MSDN了解下.我们后面会讲到的截图就会用到这个方法.感谢大家的支持,这几天从早忙到晚,一个字累呀!!!现在挺困的,但是又不习惯这么早睡觉,哎~~还是利用这个时间继续来写第三篇吧.前两篇已经基本向大家介绍了绘图的基本知识.那么,我就用我们上两篇所学的,做几个例子.我们先来做一个简单的----仿QQ截图,关于这个的例子其实网上已经有这方面的资料了,但是为了文章的完整性,还是觉得有必要讲解.我们先来看一下效果:(图1)(图2)接下来看看这是如何做到的.思路:聊天窗体上有一个截图按钮,点击按钮后,程序将整个屏幕画在一个新的全屏窗体上,然后显示这个窗体.因为是全屏的窗体,并且隐藏了菜单栏、工具栏等,所以在我们看来就好像是一个桌面的截图,然后在这个新窗体上画矩形,最后保存矩形中的内容并显示在原来的聊天窗体中.步骤:A.新建一个窗体.命名为Catch.然后设置这个窗体的FormBorderStyle为None,WindowState为Maximized.B.我们对代码进行编辑:using System;usingusing;using;using;using;usingnamespace Client{public partial class Catch : Form{public Catch(){InitializeComponent();}#region用户变量private Point DownPoint = ;建了Catch窗体后,我们在截图按钮(位于聊天窗体上)上加入以下事件:private void bCatch_Click(object sender, EventArgs e){if{(); [0].; [0].);是在绘制第二个图形时,我们先用与底色相同的颜色将上次绘制的图形重新绘制一下.但这往往需要底色为纯色时使用.2.我们并不直接将图形画在画板上,我们用一个图片A来保存原画板上的图片.然后再新建一个与图片A相同的图片B,将我们要绘制的图形画在该图片B上,然后再将该图片B 画在画板上.这样图片A并没有被改变.于是第二次画的时候我们还是同样新建一个与图片A相同的图片进行绘制.那么上一次的图形就不会被保留下来.问题也就解决了.前几篇我已经向大家介绍了如何使用GDI+来绘图,并做了一个截图的实例,这篇我向大家介绍下如何来做一个类似windows画图的工具.个人认为如果想做一个功能强大的绘图工具,那么单纯掌握GDI还远远不够,我的目前也只能做一个比较简单的绘图工具了.不足之处,欢迎大家讨论!先来看一下最终效果吧:主要实现功能:画直线,矩形,橡皮,圆形,切换颜色,打开图片,保存图片,清除图片,手动调节画布大小;软件刚启动时,为一张空白画布,我们可以直接在画布上绘画,也可以通过菜单中的“打开”,导入一张图片,然后我们就可以在这张图片上进行绘制。
gdi双缓冲绘图.txt30生命的美丽,永远展现在她的进取之中;就像大树的美丽,是展现在它负势向上高耸入云的蓬勃生机中;像雄鹰的美丽,是展现在它搏风击雨如苍天之魂的翱翔中;像江河的美丽,是展现在它波涛汹涌一泻千里的奔流中。
以下是最简单的双缓冲代码,注释的地方很重要:CRect rect;static BOOL bColor=false;GetClientRect(&rect);CDC dcMem;dcMem.CreateCompatibleDC(pDC); //创建与视图的设备相兼容的内存设备,新的设备不具有与原设备相同的设备属性与背景色.CBitmap bmp;bmp.CreateCompatibleBitmap(pDC,rect.right,rect.bottom); //创建一个与视图兼容的位图,只有根据原设备来创建位图,才能从设备中获取像素点组成位图,因为双缓冲需要保留原设备中已有的图像,因此需要调用这个方法CBitmap* pOldBmp=dcMem.SelectObject(&bmp); //选择位图,只是修改了设备属性,并没有真正绘图.if(bColor){dcMem.SelectStockObject(WHITE_PEN);}else{dcMem.SelectStockObject(BLACK_PEN);}bColor=!bColor;for(int i=0;i<rect.Width();i++) {dcMem.MoveTo(i,0);dcMem.LineTo(i,rect.bottom);}pDC->BitBlt(0,0,rect.Width(),rect.Height(),&dcMem,0,0,SRCCOPY); //将在内存中绘制好的图像重新显示到视图中,pDC与dcMem不必兼容.dcMem.SelectObject(pOldBmp);pOldBmp->DeleteObject();双缓冲技术绘图当数据量很大时,绘图可能需要几秒钟甚至更长的时间,而且有时还会出现闪烁现象,为了解决这些问题,可采用双缓冲技术来绘图。
使用双缓冲的图形可以减少或消除重绘显示图面时产生的闪烁。
使用双缓冲时,更新的图形首先被绘制到内存的缓冲区中,然后,此缓冲区的内容被迅速写入某些或所有显示的图面中。
显示图形的重写相对简短,这通常可以减少或消除有时在更新图形时出现的闪烁。
使用C# GDI+绘图,实现双缓冲绘图有几种方法,在这篇文章中,将介绍其中的一种——使用BufferedGraphics实现GDI+双缓冲绘图。
下面的代码示例演示如何使用BufferedGraphics对象绘制以下图形,这些图形使用几种类型的缓冲实现。
单击窗体将启动或停止一个计时器,该计时器将引起绘制更新。
绘制更新使您可以观察双缓冲的效果。
右击窗体将循环使用下列绘制模式:∙对于Form,直接绘制到Handle。
∙对使用OptimizedDoubleBuffer控件样式的OnPaint方法进行重写,以进行绘制∙对于不使用OptimizedDoubleBuffer控件样式的窗体方法,重写OnPaint方法以进行绘制。
在每种模式下都将绘制文本,以标识当前模式并描述按下每个鼠标按钮时发生的行为。
using System;using ponentModel;using System.Drawing;using System.Windows.Forms;namespace BufferingExample{public class BufferingExample : Form{private BufferedGraphicsContext context;private BufferedGraphics grafx;private byte bufferingMode;private string[] bufferingModeStrings ={ "Draw to Form without OptimizedDoubleBufferring control style","Draw to Form using OptimizedDoubleBuffering control style","Draw to HDC for form" };private System.Windows.Forms.Timer timer1;private byte count;public BufferingExample() : base(){// Configure the Form for this example.this.Text = "User double buffering";this.MouseDown += newMouseEventHandler(this.MouseDownHandler);this.Resize += new EventHandler(this.OnResize);this.SetStyle( ControlStyles.AllPaintingInWmPaint | erPaint, true );// Configure a timer to draw graphics updates.timer1 = new System.Windows.Forms.Timer();timer1.Interval = 200;timer1.Tick += new EventHandler(this.OnTimer);bufferingMode = 2;count = 0;// Retrieves the BufferedGraphicsContext for the// current application domain.context = BufferedGraphicsManager.Current;// Sets the maximum size for the primary graphics buffer// of the buffered graphics context for the application// domain. Any allocation requests for a buffer larger// than this will create a temporary buffered graphics// context to host the graphics buffer.context.MaximumBuffer = new Size(this.Width+1,this.Height+1);// Allocates a graphics buffer the size of this form// using the pixel format of the Graphics created by// the Form.CreateGraphics() method, which returns a// Graphics object that matches the pixel format of the form.grafx = context.Allocate(this.CreateGraphics(),new Rectangle( 0, 0, this.Width, this.Height ));// Draw the first frame to the buffer.DrawToBuffer(grafx.Graphics);}private void MouseDownHandler(object sender, MouseEventArgs e){if( e.Button == MouseButtons.Right ){// Cycle the buffering mode.if( ++bufferingMode > 2 )bufferingMode = 0;// If the previous buffering mode used// the OptimizedDoubleBuffering ControlStyle,// disable the control style.if( bufferingMode == 1 )this.SetStyle( ControlStyles.OptimizedDoubleBuffer, true );// If the current buffering mode uses// the OptimizedDoubleBuffering ControlStyle,// enabke the control style.if( bufferingMode == 2 )this.SetStyle( ControlStyles.OptimizedDoubleBuffer, false );// Cause the background to be cleared and redraw.count = 6;DrawToBuffer(grafx.Graphics);this.Refresh();}else{// Toggle whether the redraw timer is active.if( timer1.Enabled )timer1.Stop();elsetimer1.Start();}}private void OnTimer(object sender, EventArgs e){// Draw randomly positioned ellipses to the buffer.DrawToBuffer(grafx.Graphics);// If in bufferingMode 2, draw to the form's HDC.if( bufferingMode == 2 )// Render the graphics buffer to the form's HDC.grafx.Render(Graphics.FromHwnd(this.Handle));// If in bufferingMode 0 or 1, draw in the paint method.elsethis.Refresh();}private void OnResize(object sender, EventArgs e){// Re-create the graphics buffer for a new window size.context.MaximumBuffer = new Size(this.Width+1,this.Height+1);if( grafx != null ){grafx.Dispose();grafx = null;}grafx = context.Allocate(this.CreateGraphics(),new Rectangle( 0, 0, this.Width, this.Height ));// Cause the background to be cleared and redraw.count = 6;DrawToBuffer(grafx.Graphics);this.Refresh();}private void DrawToBuffer(Graphics g){// Clear the graphics buffer every five updates.if( ++count > 5 ){count = 0;grafx.Graphics.FillRectangle(Brushes.Black, 0, 0, this.Width, this.Height);}// Draw randomly positioned and colored ellipses.Random rnd = new Random();for( int i=0; i<20; i++ ){int px = rnd.Next(20,this.Width-40);int py = rnd.Next(20,this.Height-40);g.DrawEllipse(new Pen(Color.FromArgb(rnd.Next(0, 255),rnd.Next(0,255), rnd.Next(0,255)), 1),px, py, px+rnd.Next(0, this.Width-px-20), py+rnd.Next(0, this.Height-py-20));}// Draw information strings.g.DrawString("Buffering Mode:"+bufferingModeStrings[bufferingMode], new Font("Arial", 8), Brushes.White, 10, 10);g.DrawString("Right-click to cycle buffering mode", new Font("Arial", 8), Brushes.White, 10, 22);g.DrawString("Left-click to toggle timed display refresh", new Font("Arial", 8), Brushes.White, 10, 34);}protected override void OnPaint(PaintEventArgs e){grafx.Render(e.Graphics);}[STAThread]public static void Main(string[] args){Application.Run(new BufferingExample());}}}。
VC中的绘图有个比较棘手的问题是闪烁,双缓存是解决此类问题的一种方法,但是在系统绘图中,由于可能要加载滚动条,响应鼠标拖动等事件,导致传统的双缓存方法不一定适用,本文提出了一种解决方法能够用统一的框架内实现滚动条,鼠标图型拖动,视口转换以及双缓存绘图.关键字:双缓存,滚动条,鼠标拖动,VC,视口转换炫丽的软件效果能增强用户体验,用绘图方法展示动人效果就成为了必不可少的一个环节,VC提供了非常丰富的绘图API函数库,例如GDI+接口,但是用过这些接口函数的开发人员应该知道,有个非常头疼的问题是闪烁.如何解决这个问题,双缓存是一个非常好的办法.1.双缓存原理介绍在VC中进行绘图过程处理时,如果图形刷新很快,经常出现图形闪烁的现象。
利用先在内存绘制,然后拷贝到屏幕的办法可以消除屏幕闪烁,具体的方法是先在内存中创建一个与设备兼容的内存设备上下文,也就是开辟一快内存区来作为显示区域,然后在这个内存区进行绘制图形。
在绘制完成后利用BitBlt函数把内存的图形直接拷贝到屏幕上即可。
2.双缓存绘图实现前面简单的介绍了VC下双缓存的原理,在实现这一环节,我们的主要工作是将上面的想法实现.当面我们所用到的MFC应用程序是基于对话框,所以代码我们当前要放在OnPaint 函数里.void CTestScrollDlg::OnPaint(){CPaintDC dc(this);int nWidth = 1000;int nHeight = 1000;//随后建立与屏幕显示兼容的内存显示设备CDC MemDC; //首先定义一个显示设备对象CBitmap MemBitmap;//定义一个位图对象MemDC.CreateCompatibleDC(NULL);//这时还不能绘图,因为没有地方画//下面建立一个与屏幕显示兼容的位图,至于位图的大小嘛,可以用窗口的大小MemBitmap.CreateCompatibleBitmap(&dc,nWidth,nHeight);//将位图选入到内存显示设备中//只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上MemDC.SelectObject(&MemBitmap);//先用背景色将位图清除干净,这里我用的是白色作为背景//你也可以用自己应该用的颜色MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(255,255,255));//绘图MemDC.Ellipse(m_nEclipseRect);//将内存中的图拷贝到屏幕上进行显示dc.SetV iewportOrg(-m_nHScrollPos,-m_nVScrollPos);dc.BitBlt(0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY);//绘图完成后的清理MemBitmap.DeleteObject();MemDC.DeleteDC();CDialog::OnPaint();}这里面有一点要着重讲一下.几个变量的定义:1.nEclipseRect这是个矩形框,CRect类型,用于表示一个椭圆的四个值2.m_nHScrollPos是个int类型,用之于水平滚动条,表现当前X轴坐标值.3.m_nVScrollPos是个int类型,用之于垂直滚动条,表现当前的Y轴坐标值在本程序中,由于绘图的面积可能会比较大,我们采用了滚动条机制.下面列出来滚动条代码.void CTestScrollDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar){// TODO: Add your message handler code here and/or call defaultSCROLLINFO scrollinfo;GetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);// this->Invalidate(false);//InvalidateRect(NULL,TRUE);switch (nSBCode){case SB_LEFT:ScrollWindow((scrollinfo.nPos-scrollinfo.nMin)*10,0);scrollinfo.nPos = scrollinfo.nMin;SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);break;case SB_RIGHT:ScrollWindow((scrollinfo.nPos-scrollinfo.nMax)*10,0);scrollinfo.nPos = scrollinfo.nMax;SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);break;case SB_LINELEFT:scrollinfo.nPos -= 1;if (scrollinfo.nPos){scrollinfo.nPos = scrollinfo.nMin;break;}SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);ScrollWindow(10,0);break;case SB_LINERIGHT:scrollinfo.nPos += 1;if (scrollinfo.nPos>scrollinfo.nMax){scrollinfo.nPos = scrollinfo.nMax;break;}SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);ScrollWindow(-10,0);break;case SB_PAGELEFT:scrollinfo.nPos -= 5;if (scrollinfo.nPos){scrollinfo.nPos = scrollinfo.nMin;break;}SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);ScrollWindow(10*5,0);break;case SB_PAGERIGHT:scrollinfo.nPos += 5;if (scrollinfo.nPos>scrollinfo.nMax){scrollinfo.nPos = scrollinfo.nMax;break;}SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);ScrollWindow(-10*5,0);break;case SB_THUMBPOSITION:break;case SB_THUMBTRACK:ScrollWindow((scrollinfo.nPos-nPos)*10,0);scrollinfo.nPos = nPos;SetScrollInfo(SB_HORZ,&scrollinfo,SIF_ALL);break;case SB_ENDSCROLL:break;}m_nHScrollPos = scrollinfo.nPos*10;CDialog::OnHScroll(nSBCode, nPos, pScrollBar);}void CTestScrollDlg::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar) {// TODO: Add your message handler code here and/or call defaultSCROLLINFO scrollinfo;GetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);// this->Invalidate(false);//InvalidateRect(NULL,TRUE);switch (nSBCode){case SB_BOTTOM:ScrollWindow(0,(scrollinfo.nPos-scrollinfo.nMax)*10);scrollinfo.nPos = scrollinfo.nMax;SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);break;case SB_TOP:ScrollWindow(0,(scrollinfo.nPos-scrollinfo.nMin)*10);scrollinfo.nPos = scrollinfo.nMin;SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);break;case SB_LINEUP:scrollinfo.nPos -= 1;if (scrollinfo.nPos){scrollinfo.nPos = scrollinfo.nMin;break;}SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);ScrollWindow(0,10);break;case SB_LINEDOWN:scrollinfo.nPos += 1;if (scrollinfo.nPos>scrollinfo.nMax){scrollinfo.nPos = scrollinfo.nMax;break;}SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);ScrollWindow(0,-10);break;case SB_PAGEUP:scrollinfo.nPos -= 5;if (scrollinfo.nPos){scrollinfo.nPos = scrollinfo.nMin;break;}SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);ScrollWindow(0,10*5);break;case SB_PAGEDOWN:scrollinfo.nPos += 5;if (scrollinfo.nPos>scrollinfo.nMax){scrollinfo.nPos = scrollinfo.nMax;break;}SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);ScrollWindow(0,-10*5);break;case SB_ENDSCROLL:// MessageBox("SB_ENDSCROLL");break;case SB_THUMBPOSITION:// ScrollWindow(0,(scrollinfo.nPos-nPos)*10);// scrollinfo.nPos = nPos;// SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);break;case SB_THUMBTRACK:ScrollWindow(0,(scrollinfo.nPos-nPos)*10);scrollinfo.nPos = nPos;SetScrollInfo(SB_VERT,&scrollinfo,SIF_ALL);break;}m_nVScrollPos = scrollinfo.nPos*10;CDialog::OnVScroll(nSBCode, nPos, pScrollBar);}这段代码通用性比较强,可以直接粘到应用程序中使用.但是只要注意,由于在我们的应用程序中运行到了视口切换的概念,这是本地坐标系跟世界坐标系之间的切换,前面提到了二个变量用来标记当前坐标系的视口值.具体原理若不理解,请参考视口转换的原理.3.鼠标拖动事件实现前面的代码运行出来的效果,是一个对话框,然后在(0,0,200,200)的位置上出现一个椭圆,下面的工作,是想通过鼠标拖动这个椭圆,即实现鼠标的智能拖动.下面我们在对话框中添加鼠标拖动事件.void CTestScrollDlg::OnMouseMove(UINT nFlags, CPoint point){// TODO: Add your message handler code here and/or call default//dc.SetViewportOrg(-m_nHScrollPos,-m_nVScrollPos);// dc.Ellipse(m_nEclipseRect);int cx = point.x-m_nOrgPoint.x;int cy = point.y - m_nOrgPoint.y;m_nOrgPoint = point;point.x += m_nHScrollPos;point.y += m_nVScrollPos;CString str;str.Format("x= %d,y=%d",point.x,point.y);this->SetWindowText(str);// 判断当前的点是否在矩形框中,同时是否按下鼠标左键if(nFlags&MK_LBUTTON && m_nEclipseRect.PtInRect(point)){m_nEclipseRect.bottom += cy;m_nEclipseRect.top += cy;m_nEclipseRect.right += cx;m_nEclipseRect.left += cx;this->Invalidate();}CDialog::OnMouseMove(nFlags, point);}这里面有个变量m_nOrgPoint,是个类变量,用之于记录上一次的坐标值.4.再闪烁问题解决不过我们运行这个效果图后,会发现又出现了闪烁,这个是什么问题呢,我们不是已经添加了双缓存了吗.其实这个问题的产生的原因是因为我们在鼠标拖动时,也进行了Invalidate函数,而这个时候是不需要进行背景重绘,这个问题的解决是通过添加OnEraseBkgnd函数.在当前对话框中通过添加函数向导里,在Filter里选择Window.然后先中EraseBkgnd事件.BOOL CTestScrollDlg::OnEraseBkgnd(CDC* pDC){// TODO: Add your message handler code here and/or call defaultreturn true;return CDialog::OnEraseBkgnd(pDC);}再运行一下,就会发现闪烁不见了.5.总结双缓存绘图的问题几乎困扰过每一个开发者,虽然参考网上信息,能解决这一问题,但是在添加鼠标事件,添加滚动条事件,如何通过视口切换的方法,显示一张大图,目前并没有统一的解决办法,在实际工作,在统一解决这个问题时,也花了一些功夫,在解决后,整理了一下思路,以供开发人员参考.。
下面是实现本示例的几个步骤:一、创建一个Windows程序项目,在项目里面创建一个窗口,命名为:FormBuffering,在窗口上添加另两个按钮,分别命名为:btnStart和btnStop。
二、复制下面的源码,覆盖自动生成的代码:using System;using System.Collections.Generic;using ponentModel;using System.Data;using System.Drawing;using System.Text;using System.Windows.Forms;namespace ImageDoubleBuffer{public partial class FormBuffering : Form{private Bitmap _bufferImage;private int _count;private Timer _timer;public FormBuffering(){InitializeComponent();_bufferImage = new Bitmap(ClientSize.Width,ClientSize.Height);_timer = new Timer();_timer.Interval = 200;_timer.Tick += delegate(object sender, EventArgs e){_count++;if (_count > 6){_count = 0;}base.Invalidate();};btnStart.Click += delegate(object sender, EventArgs e) {_timer.Start();};btnStop.Click += delegate(object sender, EventArgs e) {_timer.Stop();_count = 0;};}///<summary>///当窗体改变大小时,重新初始化图片。
********************所有的GDI绘图函数使用的都是逻辑坐标(逻辑范围)**************************************系统默认情况下物理范围和逻辑范围是1:1 的对应关系*******************1. 首先定义类成员:CDC *m_pDC;CDC MemDC;CBitmap MemBitmap;CBitmap *pOldbitmap;LONG xRange; // 逻辑范围,x方向宽度LONG yRange; // 逻辑范围,y方向高度LONG nWidht; // 物理范围,x方向宽度LONG nHeight; // 物理范围,y方向高度2. 在类初始化函数中:m_pDC = this->GetDC(); // 获取设备上下文句柄CWnd *wnd = GetDlgItem(IDC_SHOWGRAPH); // 获取界面上显示图形的ID控件的句柄wnd->GetWindowRect(&rect); // 获取显示/画图区域大小(物理范围)ScreenToClient(&rect); // 转换为客户区坐标nWidth = rect.Width(); // 显示/画图区域x方向物理宽度nHeight = rect.Height(); // 显示/画图区域y方向物理高度3. 在自定义函数中,设置视口与窗口的比例关系:m_pDC->SetMapMode(MM_ANISOTROPIC); // 注意MM_ANISOTROPIC和MM_ISOTROPIC的区别m_pDC->SetWindowExt(XRange,-yRange); // 设定窗口尺寸范围,画图使用的逻辑范围,实现放大或是缩小,坐标方向↑和→为正向m_pDC->SetViewportExt(nWidth,nHeight); // 设定视口尺寸范围,客户区实际图形显示的区域范围,大小固定m_pDC->SetViewportOrg(rect.left,rect.bottom); //设定画图的逻辑原点坐标(0,0)在物理坐标的(rect.left,rect.bottom)点上4. 在自定义函数中,双缓冲技术的使用:MemDC.CreateCompatibleDC(m_pDC);// 创建内存兼容设备上下文MemBitmap.CreateCompatibleBitmap(m_pDC,xRange,yRange); // 创建内存兼容画布,大小由逻辑范围决定pOldbitmap =MemDC.SelectObject(&MemBitmap); // 将画布选入内存设备上下文MemDC.FillSolidRect(0,0,xRange,yRange,RGB(123,213,132)); // 对内存中的画布填充背景颜色,否则是默认的黑色// 画图操作,如画一条对角直线MemDC.MoveTo(0,0);MemDC.LineTo(xRange*0.9,yRange*0.9);// 将内存中的画图区域拷贝到界面的控件区域上去// 第1和第2个参数若是0时,则从物理坐标的(rect.left,rect.bottom)点上开始按上述指定的方向贴图m_pDC->BitBlt(0,0,xRange,yRange,&MemDC,0,0,SRCCOPY);5. 在类的析构函数中:MemDC.SelectObject(pOldbitmap);bitmap.DeleteObject();this->ReleaseDC(m_pDC);6. 至此,就完成了双缓冲及坐标缩放绘图的功能用VC做的画图程序,当所画的图形大于屏幕时,在拖动滚动条时屏幕就会出现严重的闪烁,为了解决这一问题,就得使用双缓冲来解决。
利用双缓存技术提高GDI+的绘图效果摘要在进行图像处理的软件中,一般的图形处理程序需要大量的绘图操作,首要解决的就是绘图的效率和效果问题:前者表现在性能方面,是否有延迟,一个小小的交互操作是否要等上几秒才能看到结果;后者表现在重新绘制或刷新的过程中出现闪烁?本文主要介绍GDI+程序中引用双缓冲技术以解决重绘时出现的闪烁问题—提高绘图效果。
关键字:GDI+;双缓存;快速;缓存位图;效果引言在使用GDI+的实际的绘图中,绘图窗口内容或大小每改变一次,都要调用Paint事件进行重绘操作,该操作会使画面重新刷新一次以维持窗口正常显示。
这样的效率非常低,不仅达不到性能要求,而且还可能会出现闪烁的现象。
刷新过程中会导致所有图元重新绘制,而各个图元的重绘操作并不会导致Paint事件发生,因此窗口的每一次刷新只会调用Paint事件一次。
窗口刷新一次的过程中,每一个图元的重绘都会立即显示到窗口,因此整个窗口中,只要是图元所在的位置,都在刷新,而刷新的时间是有差别的,闪烁现象自然会出现。
所以说,此时导致窗口闪烁现象的关键因素并不在于Paint事件调用的次数多少,而在于各个图元的重绘。
根据以上分析可知,当图元数目不多时,窗口刷新的位置也不多,窗口闪烁效果并不严重;当图元数目较多时,绘图窗口进行重绘的图元数量增加,绘图窗口每一次刷新都会导致较多的图元重新绘制,窗口的较多位置都在刷新,闪烁现象自然就会越来越严重。
特别是图元比较大绘制时间比较长时,闪烁问题会更加严重,因为时间延迟会更长。
1、双缓存技术为了解决画图的效率问题,我们引进了双缓存技术,双缓冲技术早在GDI的程序开发中,就已经开始使用。
在普通的绘图模式下,图像处理程序是按照设计的顺序,将被绘制的元素一个接一个地在目标设备上进行绘制。
何谓“双缓冲”?它的基本原理就是:先在内存中开辟一块虚拟画布,然后将所有需要画的图形先画在这块“虚拟画布”上,最后在一次性将整块画布画到真正的窗体上。
VC6屏幕绘图(GDI)基础在VC6中,屏幕绘图是通过设备环境(Device Context)实现的,它是GDI的的关键元素。
一、设备环境获取的一些函数:WINUSERAPI HDC WINAPI GetDC(HWND hWnd); //获取窗口的设备环境WINUSERAPI HDC WINAPI GetWindowDC( HWND hWnd);WINGDIAPI HDC WINAPI CreateCompatibleDC(HDC);二、设备环境的坐标:(1)、设备坐标:指的是显示设备或者打印设备坐标系下的坐标。
这种坐标以设备上的像素点为坐标单位,通常情况下,它的原点在窗口客户区的左上角。
(2)、逻辑坐标:指的是各种映射模式下的坐标。
(3)、物理坐标:指的是开发人员自定义的坐标。
三、设备环境的颜色COLORREF数据类型,用8位十六进制值0x00bbggrr表示一个RGB(红、绿、蓝)值,每个颜色分量的最大值为0xff。
获取一个COLORREF变量的各分量颜色值的宏:BYTE GetRValue(DWORD rgb); //取得红色分量值BYTE GetGValue(DWORD rgb); //取得绿色分量值BYET GetBValue(DWORD rgb); //取得蓝色分量值COLORREF RGB(BYTE red,BYTE green,BYTE blue);四、使用GDI对象定义GDI对象后,要用SelectObject()函数把定义的GDI对象选择用在当前的环境中。
例如:HDC hdc;HBRUSH hbr;hdc=GetDC(hwnd);hbr=CreateSolidBrush(RGB(255,0,0));SelectObject(hdc,hbr);五、使用画刷画刷(HBRUSH)可以用来填充指定区域的特性。
画刷通常包括填充颜色、填充图案、填充样式3种属性。
//创建实心画刷HBRUSH CreateSolidBrush(COLORREF );//创建填充画刷HBRUSH CreateHatchBrush(int index,COLORREF );//创建用位图作为填充的画刷HBRUSH CreatePatternBrush(HBITMAP );填充画刷参数index 指定的填充样式如下:HS_BDIAGONAL //从左到右往下的45度斜线HS_CROSS //十字线HS_DIAGCROSS //45度交叉线HS_FDIAGONAL //从左到右往上的45度斜线HS_HORIZONTAL //水平线HS_VERTICAL //垂直线位图填充参数hbmp 一般由下面函数获取://创建单色位图HBITMAP CreateBitmap(int width,int height,UINT nc,UINT colorbits,CONST VOID *bitmapdata); //连接图像HANDLE LoadImage(HINSTANCE hinstance,LPCTSTR imagename,UINT imagestyle,int width,int height,UINT );图1、自定义位图填充图像int bit[]={0x33,0x48,0x61,0x85,0x9e,0x23,0x56,0x88,0x33,0x48,0x61,0x85,0x9e,0x23,0x56,0x88,0x33,0x48,0x61,0x85,0x9e,0x23,0x56,0x88,0x33,0x48,0x61,0x85,0x9e,0x23,0x56,0x88,0x33,0x48,0x61,0x85,0x9e,0x23,0x56,0x88,0x33,0x48,0x61,0x85,0x9e,0x23,0x56,0x88,0x33,0x48,0x61,0x85,0x9e,0x23,0x56,0x88,0x33,0x48,0x61,0x85,0x9e,0x23,0x56,0x88,};int bit1[]={0x33e5,0x48e5,0x61e5,0x85e5,0x9ee5,0x23e5,0x56e5,0x88e5,0x33e5,0x48e5,0x61e5,0x85e5,0x9ee5,0x23e5,0x56e5,0x88e5,0x33e5,0x48e5,0x61e5,0x85e5,0x9ee5,0x23e5,0x56e5,0x88e5,0x33e5,0x48e5,0x61e5,0x85e5,0x9ee5,0x23e5,0x56e5,0x88e5,0x33e5,0x48e5,0x61e5,0x85e5,0x9ee5,0x23e5,0x56e5,0x88e5,0x33e5,0x48e5,0x61e5,0x85e5,0x9ee5,0x23e5,0x56e5,0x88e5,0x33e5,0x48e5,0x61e5,0x85e5,0x9ee5,0x23e5,0x56e5,0x88e5,0x33e5,0x48e5,0x61e5,0x85e5,0x9ee5,0x23e5,0x56e5,0x88e5,};0x33e51a,0x48e51a,0x61e51a,0x85e51a,0x9ee51a,0x23e51a,0x56e51a,0x88e51a,0x33e51a,0x48e51a,0x61e51a,0x85e51a,0x9ee51a,0x23e51a,0x56e51a,0x88e51a,0x33e51a,0x48e51a,0x61e51a,0x85e51a,0x9ee51a,0x23e51a,0x56e51a,0x88e51a,0x33e51a,0x48e51a,0x61e51a,0x85e51a,0x9ee51a,0x23e51a,0x56e51a,0x88e51a,0x33e51a,0x48e51a,0x61e51a,0x85e51a,0x9ee51a,0x23e51a,0x56e51a,0x88e51a,0x33e51a,0x48e51a,0x61e51a,0x85e51a,0x9ee51a,0x23e51a,0x56e51a,0x88e51a,0x33e51a,0x48e51a,0x61e51a,0x85e51a,0x9ee51a,0x23e51a,0x56e51a,0x88e51a,0x33e51a,0x48e51a,0x61e51a,0x85e51a,0x9ee51a,0x23e51a,0x56e51a,0x88e51a, };void patternbitmap(int x,int y,int width,int height,int psw,int psh,int pattern[],int bits){HDC hdc;hdc=GetDC(m_hwnd);HBITMAP hbmp=CreateBitmap(psw,psh,1,bits,pattern);HBRUSH hp=CreatePatternBrush(hbmp);SelectObject(hdc,hp);Rectangle(hdc,x,y,x+width,y+height);DeleteObject(hbmp);DeleteObject(hp);DeleteDC(hdc);}patternbitmap(50,50,100,100,8,8,bit,32);patternbitmap(200,50,100,100,8,8,bit1,32);patternbitmap(350,50,100,100,8,8,bit2,32);上面代码是图1的数据定义和实现过程。
一、 概述这段时间在研究GDI+双缓冲的实现方法,在网上花了很多时间都没有找到合适的示例,特别是针对VC6的。
后来通过对网上资料的分析,和对SDK/MFC的学习,实现了VC6下的GDI+双缓冲,把它写出来与大家分享,希望可以找到更好的实现方法。
GDI+的一个优点就是可以直接使用jpg图像,在这个示例中,我没有把图像文件放在资源中,而是动态读取,一是由于项目需求,图像数目是未知的;二是由于动态读文件效率低下,使用双缓冲更能体现出优势。
二、 分析在实现过程中,犯了一个错误,把读图像的方法放在了OnDraw之中,后来发现拖动滚动条闪烁的很严重,原因在于ScrollBar拖动时会产生消息激活OnDraw,这样的话,每拖动滚动条就读一次文件,重绘一次,效率当然低下了!我的实现方式是:更换图像后调用UpdateAllViews,内存DC的绘制都放在OnUpdate中操作,在内存绘制好后再在屏幕中贴图。
view sourceprint?1.// 贴上画布2.m_pOldBitmap = m_memDC.SelectObject(&m_memBitmap);3.m_memDC.FillSolidRect(0,0,2000,2000,RGB(100,100,100)); view sourceprint?01.// 贴图02.CString pStrFullPath = pDoc->m_pStrMapPath + pDoc->m_pStrMapName;ES_CONVERSION;04.LPWSTR wStr = A2W(pStrFullPath);05.Image img(wStr);06.pDoc->m_nMapWidth = img.GetWidth();07.pDoc->m_nMapHeight = img.GetHeight();08.Graphics g(m_memDC.GetSafeHdc());09.g.DrawImage(&img,0,0,img.GetWidth(),img.GetHeight()); view sourceprint?1.// 恢复2.m_memDC.SelectObject(m_pOldBitmap);三、截图图一 GDI+双缓冲效果四、结束语如果大家有更好的方法,请写出来分享。