图片控件上实现双缓冲绘图防止闪烁
- 格式:doc
- 大小:34.00 KB
- 文档页数:2
详解使⽤双缓存解决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,这样就可以解决绘制时间过长导致的闪屏问题。
C#在PictureBox中绘图防止闪烁的办法展开全文很久没发技术文章了啊……被人说装文艺了啊……我在乱说些啥吗…………最近学校开了数据结构的课设设计,说是允许使用C++,Java和C#来进行开发。
Java上上个学期学的,说实话,感觉真的不是很爽……或许是我电脑的缘故,也或许是心理作用,我总觉的NetBeans 一开就卡得不行!无论怎样都得不到在VS中开发和调试的那种爽快感,于是一度打算投奔C++的阵营,还为此买了Qt的书来学习,不过由于一直以来被诸多事情所扰(懒?),Qt的学习就停留在了……编译完毕。
好吧,我输了……C#,就决定是你了!那么进入正题吧。
在课程设计的过程中,我需要在窗体上进行图片的绘制,但是在实际的测试中发现了问题,那就是重绘的时候会发生闪烁,这个问题其实在大一的C语言课设的时候就出现过了,在程序绘制动画的高频率刷新的时候,也会产生闪烁,而那时候的解决办法,是对动画进行双缓冲(Double Buffering)处理。
在被双缓冲这个名词吓到之前,我们先来探讨下为什么重绘的时候会发生闪烁:说道动画的原理大家都懂,就是利用了人眼的视觉残留(Visual staying)现象,当一副画面进入人眼成像后,并不会立刻消失,而是仍会保留一小段时间,于是当连续的图像以很高的速度切换的时候,人眼会看到动态的影响,而不是处于切换中的单个图像。
这个过程可以参考图1:当这三幅图片以一定频率直接切换的时候,人们就会看到A貌似是在像右方移动。
那么为什么我们依据这个原理来编程绘制动画的时候会出现闪烁呢?是因为计算机的速度太慢不够给力么?当然不是!我们如果不加任何处理,就在画布C上进行绘图,那么计算机的处理过程是这样的:Step 1: 将C以背景色填充(也就是清除C上现有的内容)Step 2: 在C上按照要求绘制新的画面那么这样的过程会对动画产生怎样的影响呢?请看图2:看出和图1的差别了吧?Step1相当于在原本连续的动画中嵌入了空白的画面,这个空白的画面由于和人眼中原本残留的图像反差非常大,所以便会破坏视觉残留产生的动画,给人的感觉就是,这个动画在不停的闪烁。
VC无闪烁刷屏技术的实现---经验总结之防止窗口闪烁的方法也许我们都碰到过这种情况,当你想重画某个窗口的时候,或你需要每隔一段时间要进行重画窗口,窗口会不停的闪烁。
那么如何消除闪烁呢?借鉴了别人的经验,自己也总结一下,现将总结的几种方法介绍一下,供大家参考。
1、将Invalidate()替换为InvalidateRect()。
因为Invalidate()会导致整个窗口的图象重画,需要的时间比较长,而InvalidateRect()仅仅重画Rect区域内的内容,所需时间会少一些。
所以替换之后在很大程度上会减少闪烁。
如果你确实需要改善闪烁的情况,计算一个Rect所用的时间比起重画那些不需要重画的内容所需要的时间要少得多。
2、不要让系统擦除你的窗口。
系统在需要重画窗口的时候会帮你用指定的背景色来擦除窗口。
可是,也许需要重画的区域也许非常小。
或者,在你重画这些东西之间还要经过大量的计算才能开始。
这个时候你可以禁止系统擦掉原来的图象。
直到你已经计算好了所有的数据,自己把那些需要擦掉的部分用背景色覆盖掉(如:dc.FillRect(rect,&brush);rect是需要擦除的区域,brush是带背景色的刷子),再画上新的图形。
要禁止系统擦除你的窗口,可以重载OnEraseBkgnd()函数,让其直接返回TRUE就可以了。
如BOOL CMyWin::OnEraseBkgnd(CDC* pDC){return TRUE;//return CWnd::OnEraseBkgnd(pDC);//把系统原来的这条语句注释掉。
}3、有效的进行擦除。
擦除背景的时候,不要该擦不该擦的地方都擦。
比如,你在一个窗口上放了一个很大的Edit框,几乎占了整个窗口,那么你频繁的擦除整个窗口背景将导致Edit不停重画形成剧烈的闪烁。
事实上你可以CRgn创建一个需要擦除的区域,只擦除这一部分。
如GetClientRect(rectClient);rgn1.CreateRectRgnIndirect(rectClient);rgn2.CreateRectRgnIndirect(m_rectEdit);if(bineRgn(&rgn1,&rgn2,RGN_XOR) == ERROR)//处理后的rgn1只包括了Edit框之外的客户区域,这样,Edit将不会被我的背景覆盖而导致重画。
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#程序设计有所帮助。
import java.awt.BasicStroke;import java.awt.BorderLayout;import java.awt.Color;import java.awt.Dimension;import java.awt.Graphics;import java.awt.Graphics2D;import java.awt.Rectangle;import java.awt.event.MouseEvent;import java.awt.event.MouseListener;import java.awt.event.MouseMotionListener;import java.awt.event.WindowAdapter;import java.awt.event.WindowEvent;import java.awt.image.BufferedImage;import javax.swing.JFrame;import javax.swing.JPanel;public class BufferedDraw extends JPanel implements MouseListener,MouseMotionListener {Rectangle rect = new Rectangle(0, 0, 100, 50);BufferedImage bi = new BufferedImage(5, 5, BufferedImage.TYPE_INT_RGB); Graphics2D big;int last_x, last_y;boolean firstTime = true;Rectangle area;boolean pressOut = false;public BufferedDraw() {setBackground(Color.white);addMouseMotionListener(this);addMouseListener(this);}// Handles the event of the user pressing down the mouse button.public void mousePressed(MouseEvent e) {last_x = rect.x - e.getX();last_y = rect.y - e.getY();// Checks whether or not the cursor is inside of the rectangle while the // user is pressing themouse.if (rect.contains(e.getX(), e.getY())) {updateLocation(e);} else {pressOut = true;}}// Handles the event of a user dragging the mouse while holding down the // mouse button.public void mouseDragged(MouseEvent e) {if (!pressOut) {updateLocation(e);}}// Handles the event of a user releasing the mouse button.public void mouseReleased(MouseEvent e) {if (rect.contains(e.getX(), e.getY())) {updateLocation(e);}}public void mouseMoved(MouseEvent e) {}public void mouseClicked(MouseEvent e) {}public void mouseExited(MouseEvent e) {}public void mouseEntered(MouseEvent e) {}public void updateLocation(MouseEvent e) {rect.setLocation(last_x + e.getX(), last_y + e.getY());repaint();}public void paint(Graphics g) {update(g);}public void update(Graphics g) {Graphics2D g2 = (Graphics2D) g;if (firstTime) {Dimension dim = getSize();int w = dim.width;int h = dim.height;area = new Rectangle(dim);bi = (BufferedImage) createImage(w, h);big = bi.createGraphics();rect.setLocation(w / 2 - 50, h / 2 - 25); big.setStroke(new BasicStroke(8.0f));firstTime = false;}big.setColor(Color.white);big.clearRect(0, 0, area.width, area.height);big.setPaint(Color.red);big.draw(rect);big.setPaint(Color.blue);big.fill(rect);g2.drawImage(bi, 0, 0, this);}private boolean checkRect() {if (area == null) {return false;}if (area.contains(rect.x, rect.y, 100, 50)) { return true;}int new_x = rect.x;int new_y = rect.y;if ((rect.x + 100) > area.width) {new_x = area.width - 99;}if (rect.x < 0) {new_x = -1;}if ((rect.y + 50) > area.height) {new_y = area.height - 49;}if (rect.y < 0) {new_y = -1;}rect.setLocation(new_x, new_y);return false;}public static void main(String s[]) {JFrame f = new JFrame("BufferedShapeMover");f.addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e) {System.exit(0);}});f.getContentPane().setLayout(new BorderLayout());f.getContentPane().add(new BufferedDraw(), "Center");f.pack();f.setSize(new Dimension(550, 250));f.show();}}。
绘图的双缓冲技术简介幸运的是当编写一个典型的Windows 窗体程序时,窗体和控件的绘制、效果等操作是不需要特别加以考虑的。
这是为什么呢?因为通过使用.Net 框架,开发人员可以拖动一系列的控件到窗体上,并书写一些简单的与事件相关联的代码然后在IDE中按F5,一个完完全全的窗体程序就诞生了!所有控件都将自己绘制自己,窗体或者控件的大小和缩放都调整自如。
在这里经常会用到的,且需要引起一点注意的就是控件效果。
游戏,自定义图表控件以及屏幕保护程序的编写会需要程序员额外撰写用于响应Paint 事件的代码。
本文针对那些Windows 窗体开发人员并有助于他们在应用程序编制过程中使用简单的绘图技术。
首先,我们会讨论一些基本的绘图概念。
到底谁在负责进行绘制操作?Windows 窗体程序是如何知道何时该进行绘制的?那些绘制代码究竟被放置在哪里?之后,还将介绍图像绘制的双重缓冲区技术,你将会看到它是怎样工作的,怎样通过一个方法来实现缓存和实际显示的图像间的交替。
最后,我们将会探讨”智能无效区域”,实际就是仅仅重绘或者清除应用程序窗体上的无效部分,加快程序的显示和响应速度。
希望这些概念和技术能够引导读者阅读完本文,并且有助于更快和更有效的开发Windows 窗体程序。
Windows 窗体使用GDI+图像引擎,在本文中的所有绘图代码都会涉及使用托管的.Net 框架来操纵和使用Windows GDI+图像引擎。
尽管本文用于基本的窗体绘图操作,但是它同样提供了快速的、有效的且有助于提高程序性能的技术和方法。
所以,在通读本文之前建议读者对.Net框架有个基本的了解,包括Windows 窗体事件处理、简单的GDI+对象譬如Line,Pen和Brush等。
熟悉Visual Basic .Net或者C#编程语言。
概念Windows 应用程序是自己负责绘制的,当一个窗体”不干净”了,也就是说窗体改变了大小,或者部分被其它程序窗体遮盖,或者从最小化状态恢复时,程序都会收到需要绘制的信息。
屏幕无闪烁绘图少将在使用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;从而避免背景不同而形成闪烁。
1.添加图片控件
2.对话框初始化函数(或其他函数中获得控件的绘图指针)
/*******获得图片区绘图指针*******/
pWnd = GetDlgItem(IDC_STA TIC_SHOWPICTURE);//获得控件窗口
//获得控件大小,PictureRect是全局变量,用于存放控件大小
pWnd->GetClientRect(&PictureRect);
pDC = pWnd->GetDC();//定义pDC为控件绘图设备指针
3.内存中绘图并将图形复制到当前区域中
void CShockSensorConfiguratorDlg::OnPaint()
{
…;
else
{
if(m_iWitchPicture==3)//如果要显示的是第三张也就是自己绘制的图形
{
CBitmap memBitmap;
CBitmap* pOldBmp = NULL;
memDC.CreateCompatibleDC(pDC);
memBitmap.CreateCompatibleBitmap(pDC,PictureRect.right,PictureRect.bottom);
pOldBmp = memDC.SelectObject(&memBitmap);
memDC.BitBlt(PictureRect.left,PictureRect.top,PictureRect.right,PictureRect.bottom,pDC, 0,0,SRCCOPY);
memDC.FillSolidRect(1,1,PictureRect.right-2,PictureRect.bottom-2,RGB(255,255,255));
DrawPicture(&memDC);//自绘函数,传入的是内存指针,也就是在内存中绘图pDC->BitBlt(PictureRect.left,PictureRect.top,PictureRect.right,PictureRect.bottom,&m emDC,0,0,SRCCOPY);
memDC.SelectObject(pOldBmp);
memDC.DeleteDC();
memBitmap.DeleteObject();
}
else if(m_iWitchPicture==2)
{
CDC *pDC1;
pDC1=GetDC();
CBitmap m_bmpBK;
m_bmpBK.LoadBitmap(IDB_BITMAP_OPA);
BITMAP bitMap;//位图结构体
m_bmpBK.GetBitmap(&bitMap);//获得原图片尺寸
CDC dcMem; //目标DC
dcMem.CreateCompatibleDC(pDC1); //创建与dc兼容的内存DC
dcMem.SelectObject(&m_bmpBK);//将位图对象m_bmpBK选入内存DC
pDC1->StretchBlt(PictureRectPt.left,PictureRectPt.top,PictureRect.Width(),PictureRect. Height(),&dcMem,0,0,bitMap.bmWidth,bitMap.bmHeight,SRCCOPY);
dcMem.DeleteDC();
m_bmpBK.DeleteObject();
}
CPaintDC dc(this); //这个语句如果被删除会一直重绘
/*****************改变Group Box的背景颜色*****************/
CRect rect,rect1;
GetDlgItem(IDC_CONFIG1)->GetWindowRect(&rect); //获得控件的大小
ScreenToClient(&rect);
rect1.left=rect.left;
rect1.top=rect.top+7;
rect1.right=rect.right;
rect1.bottom=rect.bottom;
dc.FillSolidRect(rect1,CONFIGRECT);
}
}
Invalidate(FALSE);
UpdateWindow();
UpdateData(FALSE);//实现重绘,如果在重绘很频繁的时候双缓冲技术可以解决闪烁问题。