打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
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相当于在原本连续的动画中嵌入了空白的画面,这个空白的画面由于和人眼中原本残留的图像反差非常大,所以便会破坏视觉残留产生的动画,给人的感觉就是,这个动画在不停的闪烁。
于是我们现在知道消除Step 1这个过程带来的影响,就能够避免在绘制的时候发生闪烁,不知道你会不会想,直接把Step 1略过不就得了?但我可以很负责任的告诉你……不行!如果只是单纯的略过Step 1,那么动画就会变成这样:

在视觉上就成了一个拖着残像尾巴运动的动画,相信大家一定见过这个:

那么我现在就可以解释双缓冲是怎样防止闪烁的了。
假如我们希望在屏幕S上展示动画,首先我们需要在内存中建立一个虚拟的画布C,然后我们所有的绘图操作都在C上进行,当绘制动画的一帧完毕后,我们啪唧~把C直接往S上一拍,这样就既不会出现拖尾巴,也不会出现Refresh时的短暂空白了,如下图所示,红色的框就代表那块虚拟的画布:

原理我们都知道了,但是实际应用的时候还是会遇到一些问题,这些问题涉及到C#本身的窗体的绘制机制,不过我们还是能够解决的。
当我们通过这种方法在pictureBox上绘图的时候,特别是pictureBox还存在背景图片的时候。我们就会遇到问题:

C#
// 初始化画板Bitmap image = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height);// 初始化图形面板Graphics g = Graphics.FromImage(image);// 绘图部分 Begin// ... ...// 绘图部分 EndpictureBox1.CreateGraphics().DrawImage(image, 0, 0);



画布g是透明的,所以在g上绘制后,贴在pictureBox上,背景图片还是会展示出来,但是问题也就来了,由于pictureBox的绘制机制问题,如果我在pictureBox上贴一张透明的图,其效果就是这样的:


不知道读者明白这是什么意思了么……如果将透明的图片贴上去,那其实和直接略掉刚才所说的Step 1别无二异!如果我们想要将之前的内容去除,就不得不再使用pictureBox1.Refresh()方法,而这样的话,显然会导致闪烁,那么该怎么办呢?
网上搜索了很多,也有很多方法,比如将窗体的Double Buffered属性设置为true,或者通过继承或者反射机制,将pictureBox的Double Buffered属性设置为true,但是经过实验,这些方法都不大灵光。于是我开始静下心来思考该怎么绕过这个问题。
其实理清思路后就会发现,我们这个问题所遇到的障碍就是不能影响pictureBox的背景图的展示,所以我们为何不把pictureBox的背景图片也提取出来,作为底层画布呢?
下面是我给出的解决方案:

C#

// 初始化画板Bitmap image = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height);
// 获取背景层Bitmap bg = (Bitmap)pictureBox1.BackgroundImage;
// 初始化画布Bitmap canvas = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height);
// 初始化图形面板Graphics g = Graphics.FromImage(image);Graphics gb = Graphics.FromImage(canvas);
// 绘图部分 Begin// ... ...// 绘图部分 End
gb.DrawImage(bg, 0, 0); // 先绘制背景层gb.DrawImage(image, 0, 0); // 再绘制绘画层
pictureBox1.BackgroundImage = canvas; // 设置为背景层
pictureBox1.Refresh();pictureBox1.CreateGraphics().DrawImage(canvas, 0, 0);



 

注意我标注的行,就是添加的部分。
pictureBox的Refresh()方法不会影响其背景层,所以我们将最后合成的画布直接贴在背景层上,这样再Refresh()就不会产生闪烁了,同时,由于系统会自动重绘背景层,所以在窗口最小化或者被遮挡过后,绘制的图像也不会消失,相当同时于免去了手动重绘之苦。这样一来,困扰我们的闪烁问题就彻底的被解决啦!
感觉自己不知不觉废了好多话呀……啦啦啦,这篇就到这喽。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
chrom浏览器调用摄像头操作
实例讲解利用HTML5 Canvas API操作图形旋转的方法
HTML5 canvas drawImage() 方法 缩放
手把手教你利用JS给图片打马赛克
移动端图片操作(二)
HTML 5 Canvas(画布)教程之图像处理
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服