CDC双缓冲防闪屏绘图总结
- 格式:doc
- 大小:29.00 KB
- 文档页数:4
详解使⽤双缓存解决CanvasclearRect引起的闪屏问题前⾔今天⽤ canvas 做 H5 的时候遇到了闪屏问题。
闪烁效果如下图:问题简介功能简介H5 该部分的功能为:通过点击⼆级菜单,切换图⽚的遮罩或者更换背景。
因为功能简单,所以⽤了原⽣ canvas 实现这个功能。
但在使⽤clearRect清除画布的时候会出现闪烁的情况。
代码实现(问题代码)以下代码即为出现闪屏的关键代码,省略了图⽚的定义与 onload:// 点击⼆级菜单后,触发该函数更新画布updateCanvas(){const canvas = document.getElementById('canvas'); // 获取画布const ctx = canvas.getContext('2d');ctx.clearRect(0,0,1448,750); // 清空画布// 开始重绘ctx.drawImage(bg,0,0); // 背景... // 省略其他绘制过程}问题分析经过简单分析,得出闪屏的原因是clearRect清除画布后,绘制的时间较长导致出现闪屏的现象。
什么是双缓存来看⼀下⽹站中这篇⽂章对双缓存的解释:对图形进⾏编程时出现闪烁是⼀个常见问题。
需要多个复杂画图操作的图形操作可导致呈现的图像出现闪烁或具有不可接受的外观。
为解决这些问题,.NET Framework 提供了双缓冲功能。
双缓冲使⽤内容缓冲来解决与多个画图操作相关的闪烁问题。
启⽤双缓冲后,所有画图操作会⾸先呈现到内存缓冲⽽不是屏幕上的绘图图⾯。
所有画图操作完成后,内存缓冲会直接复制到与之关联的绘图图⾯。
由于屏幕上仅执⾏⼀个图形操作,因此与复杂画图操作相关的图像闪烁可得以消除。
使⽤双缓存解决问题以上引⽤,简单来说,主要问题就是绘制时间较长导致了闪屏,解决⽅法就是新建⼀个 canvas 作为缓存 canvas ,通过缓存canvas 完成绘制过程,绘制完成后,直接将缓存 canvas 复制到原来的 canvas,这样就可以解决绘制时间过长导致的闪屏问题。
提高二维矢量绘图效率之一般做法作者:朱金灿如何提高二维矢量绘图效率?这个问题很普遍。
最近在研究这个问题,在网上搜了一些资料,再结合自己的经验,谈谈自己的一些想法。
一.双缓存能提高绘图效率吗?网上有篇文章:绘图效率完整解决方案——三种手段提高GDI/GDI+绘图效率,其中提到一种方法是:1. 缓存——Bitmap或者DoubleBuffer。
缓存就是先把绘制的图形绘制到一张内存位图上,然后在一次性的贴位图,他可以提高绘图速度,也能避免闪烁。
DoubleBuffer=true是C#窗体的属性,设置了此属性估计系统本身会起用无效区的内存位图缓存,而不需要程序员Bitmap处理。
这里对双缓存的通常做法不作介绍,网上的相关资料很多。
说实话,我对使用双缓存能提升绘图效率表示怀疑,理由很简单,同是DC,同是绘制1000条线段,有什么理由内存DC就比窗口DC快(当然这个我没有作具体的测试,这个有空可以测试下)。
我还稍微怀疑使用双缓存绘图比直接使用窗口DC绘图还慢一些,理由有二:一是使用双缓存需要增加创建内存DC和内存位图的操作;二是使用双缓存还需要增加一个把内存DC拷贝到窗口DC的操作。
那么双缓存的主要作用是什么?其实就是解决绘图过程的闪烁问题,改善绘图效果。
二.Windows环境下二维绘图引擎的选取和绘图效率的一个重要相关因素是绘图引擎。
Windows环境下二维绘图引擎有多种选择:GDI、GDI+、DirectDraw、QT、Agg、Cairo、skia、Direct2D 、Direct3D、OpenGL等。
下面我逐一作一个简单的分析:GDI:微软原生的二维绘图引擎。
优点:微软的全力支持,作为操作系统核心层效率方面不用担心,支持多种开发框架(含语言):Win SDK、MFC、Delphi等。
缺点:基于过程,缺乏面向对象,使用起来不太方便,不支持反锯齿,不支持复杂的绘图效果(这个相对于GDI+而言)。
GDI+:微软后来推出的二维绘图引擎。
图形设备接口一、GDI、DC的概念1.GDI:(Graphics Device Interfase)图形设备接口,是一个应用程序与输出设备之间的中介。
一方面,GDI向应用程序提供一个与设备无关的编程环境,另一方面,它又以设备相关的格式和具体的设备打交道。
user32.dll2.DC:(Device Context)设备描述表,是一种Windows数据结构。
包括了与一个设备的绘制属性相关的信息。
所有的绘制操作通过一个设备描述表进行,绘制线条、形状和文本的Windows API 函数都与DC有关。
二、在Windows Application程序中画线1.定义两个全局变量用于记录鼠标按下的(x,y)坐标。
int nOrginX;int nOrginY;2.响应鼠标按下和鼠标抬起的消息:在Swich中加入case WM_LBUTTONDOWN:case WM_LBUTTONUP:3.在鼠标按下时记录鼠标按下的(x,y)坐标,查MSDN得知WM_LBUTTONDOWN lParam的低字存放x坐标,高字存放y坐标,将其取出存入nOrginX,nOrginY。
case WM_LBUTTONDOWN:nOrginX=lParam & 0x0000ffff;nOrginY=lParam >> 16 & 0x0000ffff;break;4.在鼠标抬起时画线:case WM_LBUTTONUP:HDC hdc;hdc=GetDC(hwnd);PAINTSTRUCT ps;::MoveToEx(hdc,nOrginX,nOrginY,NULL);::LineTo(hdc,LOWORD(lParam),HIWORD(lParam) );::ReleaseDC(hwnd,hdc);三、在MFC程序中画线:1.在CxxxView(其中xxx是你的工程名字)中响应鼠标按下和鼠标抬起的消息(因为只有CxxxView中才能接收到鼠标消息):使用ClassWizard加入WM_LBUTTONDOWN,WM_LBUTTONUP的消息响应函数OnLButtonDown, OnLButtonUp。
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一般只修改几项。
CDC双缓冲防闪屏绘图总结CDC在屏幕绘图可以用以下方法:CDC dc;dc.CreateCompatibleDC(NULL);CBitmap m_bitmap;m_bitmap.LoadBitmap(IDB_BITMAP1); //载入资源文件dc.SelectObject(&m_bitmap);CDC *pDC = GetDC();pDC->BitBlt(0,0,200,100,&dc,0,0,SRCCOPY);m_bitmap.DeleteObject();dc.DeleteDC();上面虽然可以在屏幕绘图,但是如里需要绘多张图,并且有重叠的部份,当鼠标改变窗口大小时,重叠部份有严重的闪烁。
非常难看CDC *pDC = GetDC();pDC->BitBlt(0,0,200,100,&dc,0,0,SRCCOPY);这个方式是直接在屏幕绘图,如果多张图,即多次调用此方法。
为了避免闪烁,可采用双缓冲的方法,不管你要绘多少张图,先把它们绘在一个内存DC,之后再在屏幕显示DC小知识点:CDC dc;这个是内存dc,使用BitBlt只会在内存中,不会在屏幕显示一旦dc=GetDC();此时dc就与屏幕有关了,BitBlt方法就会输出的屏幕CDC双缓冲防闪屏绘图实例下面的nWidth,nHeight是屏幕的宽高CDC *pDC=this->GetDC(); //此DC负责在屏幕显示CDC dc,sub_dc; //dc负责组建对话框整张大图,sub_dc负责里面小块dc.CreateCompatibleDC(NULL);sub_dc.CreateCompatibleDC(&dc);//注意参数,&dc就指定了sub_dc是基于dc的。
CBitmap bg_bmp,load_bmp;// bg_bmp负责构造背景,load_bmp负责从资源文件载入位图bg_bmp.CreateCompatibleBitmap(pDC,nWidth,nHeight); //先创建一张与对话框当前大小一致的位图dc.SelectObject(&bg_bmp);dc.FillSolidRect(0,0,nWidth,nHeight,RGB(236,236,234)); //构造背景bg_bmp.DeleteObject();//构造第一张图load_bmp.LoadBitmap(IDB_TITLE_L);//从资源载入第一张图sub_dc.SelectObject(&load_bmp);//放到sub_dc中,dc.BitBlt(0,0,16,28,&sub_dc,0,0,SRCCOPY); //把sub_dc的内容写到dc中,此时dc的内容并不会在屏幕显示load_bmp.DeleteObject();//构造第二张图load_bmp.LoadBitmap(IDB_TITLE_BG);//从资源载入第二张图sub_dc.SelectObject(&load_bmp); //放到sub_dc中,dc.StretchBlt(16,0,(nWidth-145),28,&sub_dc,0,0,1,28,SRCCOPY); //再把sub_dc 的内容写到dc中,这样第二张图就与第一张图合在一起变成了一张图load_bmp.DeleteObject();//输出到屏幕pDC->BitBlt(0,0,rc.Width(),rc.Height(),&dc,0,0,SRCCOPY);this->ReleaseDC(pDC);this->ReleaseDC(&dc);this->ReleaseDC(&sub_dc);OK,至此双缓冲绘图完全完成了。
C#双缓冲实现⽅法(可防⽌闪屏)本⽂实例讲述了C#双缓冲实现⽅法。
分享给⼤家供⼤家参考,具体如下:// 该调⽤是 Windows.Forms 窗体设计器所必需的。
InitializeComponent();// TODO: 在 InitComponent 调⽤后添加任何初始化this.SetStyle(ControlStyles.AllPaintingInWmPaint,true);//开启双缓冲this.SetStyle(ControlStyles.DoubleBuffer,true);this.SetStyle(erPaint,true);this.SetStyle(ControlStyles.ResizeRedraw,true);1、在内存中建⽴⼀块“虚拟画布”:Bitmap bmp = new Bitmap(600, 600);2、获取这块内存画布的Graphics引⽤:Graphics g = Graphics.FromImage(bmp);3、在这块内存画布上绘图:g.FillEllipse(brush, i * 10, j * 10, 10, 10);4、将内存画布画到窗⼝中this.CreateGraphics().DrawImage(bmp, 0, 0);还有的⽅式在构造函数中加如下代码代码⼀:SetStyle(erPaint, true);SetStyle(ControlStyles.AllPaintingInWmPaint, true); // 禁⽌擦除背景.SetStyle(ControlStyles.DoubleBuffer, true); // 双缓冲代码⼆:this.SetStyle(ControlStyles.DoubleBuffer | erPaint | ControlStyles.AllPaintingInWmPaint, true);this.UpdateStyles();更多关于C#相关内容感兴趣的读者可查看本站专题:《》、《》及《》希望本⽂所述对⼤家C#程序设计有所帮助。
屏幕无闪烁绘图少将在使用VC++编写某些绘图程序时,你可能会发现,窗口在每次重绘时都会发生闪烁。
特别是在绘制较复杂的图像时,这种闪烁将会更加剧烈。
这里介绍一种防止窗口绘图时闪烁的双缓冲技术。
首先,先解释一下什么叫双缓冲。
双缓冲,即在内存中创建一个与屏幕绘图区域一致的对象,先将图形绘制到内存中的这个对象上,再一次性将这个对象上的图形拷贝到屏幕上,这样能大大加快绘图的速度。
通过这个简短的解释,你是否对双缓冲有了初步的理解?不理解没关系,继续往下看。
双缓冲实现过程如下:1、在内存中创建与画布一致的缓冲区2、在缓冲区画图3、将缓冲区位图拷贝到当前画布上4、释放内存缓冲区举例:对话框中应用双缓冲绘图编译环境:操作系统Windows XP编译工具Visual C++ v6.01、新建一个基于对话框的应用程序在对话框中添加一个picture控件更改ID为IDC_FIGURE,在需要绘图的类的头文件中定义几个双缓冲用到的变量CRect rect; // 存储绘图控件的绘图区域CDC *pDC; // 控件的屏幕绘图设备指针CDC memDC; // 内存绘图设备CBitmap memBitmap; // 用于内存绘图的位图CBitmap* pOldBmp; // 备份旧的位图指针CWnd* pWnd; // 绘图控件的指针//添加一个成员函数:void DrawFigure(CDC *pDC);2、在对话框的初始化函数OnInitDialog()中添加以下代码pWnd = GetDlgItem(IDC_FIGURE); // 获得对话框上的picture控件的窗口句柄pWnd->GetClientRect(&rect); // 获取绘制区域pDC = pWnd->GetDC(); // 获得对话框上的picture的设备指针pOldBmp = NULL; // 将旧的位图指针置空// 创建内存绘图设备,使内存位图的DC与控件的DC关联memDC.CreateCompatibleDC(pDC);memBitmap.CreateCompatibleBitmap(pDC,rect.right,rect.bottom);pOldBmp = memDC.SelectObject(&memBitmap);SetTimer(1,100,NULL); // 启动定时器3、DrawFigure函数的定义void CMyDialog::DrawFigure(CDC *pDC) //CMyDialog,是需要绘图的对话框类{//声明画笔对象,及画笔指针CPen pen,*oldpen;//画一个矩形区域pDC->Rectangle(&rect);pDC->MoveTo(0,0);int x,y;for(int i =0; i<1000; i++){pen.CreatePen(PS_SOLID,1, RGB(rand()%255,rand()%255,rand()%255)); oldpen = pDC->SelectObject(&pen);x = rand()%rect.Width();y = rand()%rect.Height();pDC->LineTo(x,y);pDC->SelectObject(oldpen);pen.DeleteObject();}}4、添加Paint事件,在Paint事件处理代码中添加代码pDC->BitBlt(rect.left,rect.top,rect.right,rect.bottom,&memDC,0,0,SRCCOPY);5、添加Timer事件,在Timer事件处理函数中添加代码//在位图中画图片DrawFigure(&memDC);//使屏幕刷新OnPaint();6、在Close事件中添加代码,用来删除创建的对象,以释放内存memDC.SelectObject(pOldBmp);memDC.DeleteDC();memBitmap.DeleteObject();至此,对话框中应用双缓冲的简单例子就介绍完毕若在View视图中应用双缓冲,需要将OnEraseBkgnd(CDC* pDC)函数中的返回语句改为return TRUE;从而避免背景不同而形成闪烁。
[转载]MFC绘制动态曲线,⽤双缓冲绘图技术防闪烁先上效果图随着时间的推移,曲线向右平移,同时X轴的时间坐标跟着更新。
⼀、如何绘制动态曲线 所谓动画,都是⼀帧⼀帧的图像连续呈现在⽤户⾯前形成的。
所以如果你掌握了如何绘制静态曲线,那么学会绘制动态曲线也不远啦,只需要创建⼀个定时器(⽐如调⽤MFC中的SetTimmer函数),每隔⼀定时间(⽐如1ms),调⽤OnPaint或者OnDraw函数,绘制当前帧图像即可。
这⾥需要注意的是,绘制图像的代码需要写在OnPaint或者OnDraw函数中,因为当窗⼝失效(⽐如最⼩化)恢复后,会重新绘制当前窗⼝,窗⼝之前的⾃绘图像会丢失。
⽽把绘图代码写在OnPaint或者OnDraw中就是为了让窗⼝每次重绘时也能重绘你⾃⼰画的图像,避免出现窗⼝最⼩化再恢复后,⾃⼰画的图像丢失的尴尬情况。
另外绘制当前帧图像之前,记得⽤InvalidateRect函数清除上⼀帧图像,不然各帧图像会背景的堆叠。
⽐如我想清除窗⼝中(0,0)和(100,100)这两点确定的矩形中的图像,代码如下:CRect Rect;Rect.top = 0;Rect.left = 0;Rect.bottom = 100;Rect.right = 100;InvalidateRect(Rect); 根据上⾯的思路,我们每隔⼀定时间绘制⼀幅图像,可是如果每次绘制的图像都是完全相同的,那么图像看起来也是静态的。
如何让曲线动起来呢?我们需要为⾃⼰绘图的代码设计⼀个输⼊,即在当前时刻曲线上各个点的坐标信息。
随着时间的推移,令曲线上各个点的坐标随之变化,这样每次绘图都是基于当前时刻的曲线坐标绘制的,控制好曲线坐标的变化,也就能让你绘制的曲线乖乖的动起来。
上⾯提到了曲线上各个点的坐标信息,这个信息可以⽤多种数据结构储存,不过笔者推荐使⽤STL中的deque数据结构储存。
为什么呢?需求决定选择。
让我们先想想在绘制图像的过程中需要对这个数据进⾏哪些操作。
利用定时器和双缓冲技术在MFC中绘制动画作者:杜小甫来源:《数字技术与应用》2013年第08期摘要:对MFC中动画绘制涉及到的两个重要技术做较全面的总结和提炼,创建了一个通用性较强的动画绘制程序框架。
首先对MFC中定时器技术和双缓冲技术做出深入分析;其次创建一个动画绘制程序框架;最后通过几个动画程序验证该框架。
该程序框架已经应用在实际工作中,证明该框架是精炼有效的。
关键词:MFC 动画定时器双缓冲中图分类号:TP311.11 文献标识码:A 文章编号:1007-9416(2013)08-0129-011 定时器和双缓冲技术MFC是微软公司推出的软件开发架构[1],在MFC中绘制动画常见于各类软件开发。
动画绘制一种思路是利用循环语句加延迟函数的方式[2],另外一种思路就是利用定时器[3]定时更像图片绘制动画。
后者在绘制过程中可以响应其他事件,因此应用十分广泛。
1.1 定时器技术定时器可以向系统定时发送信号,触发OnTimer函数。
在定时器使用过程中涉及到三个常用函数,分别对应着使用定时器的三个步骤。
(1)创建定时器。
创建定时器使用SetTimer函数。
MFC中提供两个SetTimer函数,一个是全局函数,可以在程序的任意位置调用。
我们更常使用的是第二种SetTimer函数,由CWnd类重载。
函数有三个参数,分别是定时器编号、时间间隔和回调函数[4]地址。
(2)处理定时器信号。
MFC在OnTimer函数中处理定时器信号。
OnTimer函数具有一个参数,是捕捉到的定时器编号。
我们可以利用其区分不同的定时器信号,执行不同代码。
(3)销毁定时器。
定时器也会占用一定的系统资源,所以必须及时销毁不用的定时器,否则会影响系统运行效率。
MFC中使用KillTimer函数销毁定时器,该函数参数就是待销毁的定时器编号。
1.2 双缓冲技术在Windows平台上,应用程序的图像设备接口被抽象化为设备上下文(Device Content,DC)。
一、闪烁问题。
闪烁问题在MFC窗体中经常见到。
在网上碰到这些问题的层次不穷,解决方法也是多样的!但是最经典也最耐用的还是靠用双缓存解决!首先让我们来了解一下,为什么会产生屏幕闪烁问题:闪烁可以这样定义:当后面一幅图像以很快的速度画在前面一幅图像上时,在后面图像显示前,你可以很快看到前面那一个图像,这样的现象就是闪烁。
我认为,闪烁会让使用者对程序很不满,原因是:如果用户接口编码如此糟糕,那么程序的其他部分呢,如何能相信数据的正确性呢?一个具有平滑,快速相应的程序会给用户带来信心,这个道理很简单。
程序出现闪烁可以由多种形式造成,最常见的原因是窗口大小发生改变时,其内容重画造成闪烁。
仅仅画一次这是一个黄金法则,在任何计算机(Windows或者你使用的任何操作系统)上处理画法逻辑都需要遵循,即永远不要将同一像素画两次。
一个懒惰的程序员常常不愿意在画法逻辑上投入过多精力,而是采用简单的处理逻辑。
要避免闪烁,就需要确保不会出现重复绘制的情况发生。
现在,WIndows和计算机还是很笨的,除非你给他们指令,否则他们不会做任何事情。
如果闪烁的现象发生,那是因为你的程序刻意地多绘制了屏幕的某些区域造成的. 这个现象可能是因为一些明确的命令,或者一些被你忽视了的地方。
如果程序有闪烁的现象出现,你需要你知道如何找到好的方案去解决这个问题。
WM_ERASEBKGND通常,首先需要怀疑的是WM_ERASEBKGND消息。
当一个窗口的背景需要被擦除时,这个消息会被发送。
这是因为窗口的绘画通常经历了两个过程WM_ERASEBKGND: 清除背景WM_PAINT: 在上面绘制内容这两个过程让窗体在绘制内容时变得很简单,即:每次当收到WM_PAINT消息时,你知道已经有了一个新画布等待去绘制。
然而,画窗口两次(一次是通过WM_ERASEBKGND画背景,另外一次是WM_PAINT)将会导致窗口出现比较糟糕的闪烁现象。
只要看看标准的编辑框-打开Windows的写字板并改变窗口大小,就可以看到那种闪烁的效果。
绘图类CDC的理解与使用在MFC基础类库中有一个类是专门用来绘图的,这个类叫CDC类。
Windows中所有图形图像包括文字的输出都是通过CDC类完成的,因而CDC类又被称为绘图类。
说起CDC就必须先说DC,DC(Device Context)设备描述表或设备上下文。
在过去,不同的输出设备输出方法不同,以至于开发程序时还要针对具体的物理设备编写输出不同代码,极大地限制了程序开发效率,后来出现了Windows操作系统,它使用与设备无关的图形设备环境DC进行显示。
Windows为每一个设备配备了一个DC,DC里包含有输出设备的所有信息。
DC是一个中间平台,相当于一块画布,我们在向具体物理设备输出图像时先将图像绘制在物理设备的DC上,然后再由DC将图像显示到物理设备上。
为什么要这样做呢?DC这块画布是逻辑画布,我们在它上面绘制图像时无需考虑它的设备属性(逻辑画布没有设备属性),我们可以用同一组函数在不同的物理设备的DC上绘图,然后DC再根据其所包含的具体的物理设备描述表将图像绘制到输出设备。
这是一种与设备无关的图形输出方式,解除了在输出时必须考虑具体物理设备的限制,使我们在编程时只需调用绘图函数完成输出即可。
DC相当于是个资源,我们使用它时要获取它的句柄。
CDC类封装了与绘制图形有关的函数,加上Windows系统中的GDI函数即可完成所有图形的绘制。
CDC类所实例化的对象不仅包含这些成员函数,还有两个数据成员,其中一个就是所要输出的物理设备的DC的句柄。
一个CDC类的对象就是一个绘制者,它不仅有绘图方法还有绘图工具,并且它知道该往哪儿绘图。
CDC类还包含获取和修改属性、选取绘画工具的函数。
CDC有三个常用的派生类CClientDC、CPaintDC、CWindowDC,它们的功能和发挥作用的场合不同。
CClientDC:用于在除OnPaint()消息响应函数外的客户区绘图;CPaintDC:只能在OnPaint()消息响应函数内对客户区绘图;CWindowDC:可在整个框架当中任意地方绘图。
CDC双缓冲防闪屏绘图总结
CDC在屏幕绘图可以用以下方法:
CDC dc;
dc.CreateCompatibleDC(NULL);
CBitmap m_bitmap;
m_bitmap.LoadBitmap(IDB_BITMAP1); //载入资源文件
dc.SelectObject(&m_bitmap);
CDC *pDC = GetDC();
pDC->BitBlt(0,0,200,100,&dc,0,0,SRCCOPY);
m_bitmap.DeleteObject();
dc.DeleteDC();
上面虽然可以在屏幕绘图,但是如里需要绘多张图,并且有重叠的部份,当鼠标改变窗口大小时,重叠部份有严重的闪烁。
非常难看
CDC *pDC = GetDC();
pDC->BitBlt(0,0,200,100,&dc,0,0,SRCCOPY); 这个方式是直接在屏幕绘图,如果多张图,即多次调用此方法。
为了避免闪烁,可采用双缓冲的方法,不管你要绘多少张图,先把它们绘在一个内存DC,之后再在屏幕显示
DC小知识点:
CDC dc; 这个是内存dc, 使用BitBlt只会在内存中,不会在屏幕显示
一旦dc=GetDC(); 此时dc就与屏幕有关了,BitBlt方法就会输出的屏幕
CDC双缓冲防闪屏绘图实例
下面的nWidth,nHeight 是屏幕的宽高
CDC *pDC=this->GetDC(); //此DC负责在屏幕显示
CDC dc,sub_dc; //dc 负责组建对话框整张大图,sub_dc负责里面小块
dc.CreateCompatibleDC(NULL);
sub_dc.CreateCompatibleDC(&dc); //注意参数,&dc就指定了sub_dc是基于dc的。
CBitmap bg_bmp,load_bmp; // bg_bmp负责构造背景,load_bmp负责从资源文件载入位图
bg_bmp.CreateCompatibleBitmap(pDC,nWidth,nHeight); //先创建一张与对话框当前大小一致的位图
dc.SelectObject(&bg_bmp);
dc.FillSolidRect(0,0,nWidth,nHeight,RGB(236,236,234)); //构造背景
bg_bmp.DeleteObject();
//构造第一张图
load_bmp.LoadBitmap(IDB_TITLE_L); //从资源载入第一张图
sub_dc.SelectObject(&load_bmp); //放到sub_dc中,
dc.BitBlt(0,0,16,28,&sub_dc,0,0,SRCCOPY); //把sub_dc的内容写到dc中,此时dc的内容并不会在屏幕显示
load_bmp.DeleteObject();
//构造第二张图
load_bmp.LoadBitmap(IDB_TITLE_BG); //从资源载入第二张图
sub_dc.SelectObject(&load_bmp); //放到sub_dc中,
dc.StretchBlt(16,0,(nWidth-145),28,&sub_dc,0,0,1,28,SRCCOPY); //再把sub_dc的内容写到dc中,这样第二张图就与第一张图合在一起变成了一张图
load_bmp.DeleteObject();
//输出到屏幕
pDC->BitBlt(0,0,rc.Width(),rc.Height(),&dc,0,0,SRCCOPY);
this->ReleaseDC(pDC);
this->ReleaseDC(&dc);
this->ReleaseDC(&sub_dc);
OK,至此双缓冲绘图完全完成了。
单缓冲用两个dc, 比缓冲用了三个dc:dc,sub_dc , pDC
dc利用BitBlt方法合并成一张大图,pDC把dc的内存显示到屏幕、
这样,不管有多少张图,实际上只在屏幕输出一次,就避免了闪烁现象。