投稿    登录
  您好,欢迎光临崔庆才的博客,最近博主在忙复习考试,可能评论和友链不能及时回应,请谅解,至17.1.12解除!

OpenGL绘图实例三之种子填充算法

C/C++ 崔庆才 4993浏览 2评论

综述

博主研究了一下午加一晚上,终于把种子填充算法实现出来并把机器人填充完毕,路途很艰辛,不过也学到了很多,在此和大家一起分享。

吐槽

与我不是同学的小伙伴,请自动忽略,我是来吐槽教材的。

在此不得不吐槽一下,不得不说教材实在太坑爹了。对于种子填充算法的后半部分,下一个种子点的寻找过程中,从while(x<=xright)开始,我实在无法搞懂它里面的神逻辑,最初我认为它是对的,后来按照它的思路实现之后,填充基本上是错误的,比如圆角矩形下方的部分,它就无法正常填充。根本原因还是它的下一步种子点找错了,而博主依然在固执地DeBug,看看是不是我哪里编码有问题。后来,干脆放弃了书上的逻辑了,自己改写了搜寻下一个种子点的算法,最后终于成功。

另外,教材上的这些伪代码写得也是太伪,算了,这不是重点,言归正传。

基本梳理

在博主的研究过程中,遇到了许许多多的小问题,在这里统一做一下总结,也希望大家少走弯路,吸取我的经验教训。

1.点的定义

在这里我们避免不了要使用点,一个点包括了2个元素,一个是横坐标一个是纵坐标,所以我们可以直接把它定义为一个结构体。

这样的话,我们就可以直接声明一个 Point 类型的变量使用了,既方便又直观。

2.栈的使用

对于种子填充算法,肯定避免不了使用栈的,在这里博主分享一下一些使用心得。

栈的引入

C++代码中,可以直接用下面的代码来导入

注意,这里一定记得加上 using namespace std 这句话,否则会出现 stack 未定义的错误,哈哈哈,深有体会。

栈的定义

引入了栈之后,我们就可以直接来声明一个栈了

其中,需要加一个尖括号,尖括号中声明了 Point 类型,这样我们就可以使用它了

取栈顶元素

C++中取栈顶元素是很坑的,有一个top方法,还有一个pop方法。

其中top方法是只取得栈顶的元素而不移除它,pop方法是直接移除栈顶元素,没有返回值。

所以我们要想取出栈顶元素并移除的话,就要分别调用这两个方法

是不是不友好?不友好的话,那就自己去定义一个新方法吧,我就先不这么干啦。

判断栈非空

判断当前的栈是否已经为空,只需要调用empty方法就可以了

这里是一个while循环,如果栈为非空的话不断循环。

关于栈置空

教材中的种子填充算法中用到了栈置空,不过我感觉没有必要这么做,因为在方法最前面是新声明的栈变量,它一定是空的。不过如果非要置空的话,可以利用下面的代码

如果栈不为空,就一直取元素,就可以把它置空啦。

3.关于glColor3b和glColor3ub

这的确也是坑得博主不浅,之前一直在用 glColor3b 这个方法来定义颜色,奇怪的是 glColor3b(255,0,0) 竟然不是红色,而是黑色!就是因为这个颜色问题,导致我在比对颜色的过程中走了很多弯路。在这里做一下说明

glColor3b()需要传入的是byte类型,它的数值范围是-128-127,也就是有符号数,我传入255,由于越界了,255这个数就相当于-128,难怪不变红啊。

glColor3ub()需要传入的是unsigned byte类型,范围是0-255,无符号数,那么在这里我们传入255,0,0这三个数,就变成红色了。

4.取得某像素颜色

我想说的是,这也是个深坑啊,一下午的Debug全归它身上了。

获取某个像素的这个函数是

函数说明如下:

该函数总共有七个参数。前四个参数可以得到一个矩形,该矩形所包括的像素都会被读取出来。(第一、二个参数表示了矩形的左下角横、纵坐标,坐标以窗口最左下角为零,最右上角为最大值;第三、四个参数表示了矩形的宽度和高度)
第五个参数表示读取的内容,例如:GL_RGB就会依次读取像素的红、绿、蓝三种数据,GL_RGBA则会依次读取像素的红、绿、蓝、alpha四种数据,GL_RED则只读取像素的红色数据(类似的还有GL_GREEN,GL_BLUE,以及GL_ALPHA)。如果采用的不是RGBA颜色模式,而是采用颜色索引模式,则也可以使用GL_COLOR_INDEX来读取像素的颜色索引。目前仅需要知道这些,但实际上还可以读取其它内容,例如深度缓冲区的深度数据等。
第六个参数表示读取的内容保存到内存时所使用的格式,例如:GL_UNSIGNED_BYTE会把各种数据保存为GLubyte,GL_FLOAT会把各种数据保存为GLfloat等。
第七个参数表示一个指针,像素数据被读取后,将被保存到这个指针所表示的地址。注意,需要保证该地址有足够的可以使用的空间,以容纳读取的像素数据。例如一幅大小为256*256的图象,如果读取其RGB数据,且每一数据被保存为GLubyte。

好了,那么重点来了,这个方法的坐标基准点是在画布的左下角!!而我们绘图的基准点是在画布的正中心!!所以我在获取某个点的颜色的时候一直都是错误的结果,这样的话在使用的时候我们的xy坐标值就要加上画布宽高的一半才能正常获取到像素的颜色,希望大家一定注意!!

那么我们如何来使用呢?实例如下,首先定义GLByte的数组

另外还有画布的宽度高度的一半变量

我们可以调用如下的方法来获取(x,y)这个点像素的值

在这里第五个参数我们定义了 GL_RGB,第六个参数我们定义了 GL_UNSIGNED_BYTE,最后是传入了数组的引用。

所以在调用这个方法之后,iPixel数组里面的三个值就已经赋值为了该点的RGB值,可以拿来做下一步的判断。

比如我们边界可以定义为

在比较的时候就可以用下面的判别式

这里我们是依次比较了三个RGB值是否与边界的RGB值相等,不过,有意思的是,识别颜色的这个方法,黑色的RGB值会识别成1,1,1,而有时候在我调试的时候会识别为0,1,1。我在想是不是系统计算误差问题,如果真是的话,因为这个小小的误差就影响了我们的判别条件岂不是亏大了?那么在这里我就定义了一个方法,允许一定的误差,这个误差姑且就称为PS里面的容差吧。

那么我们的判定条件就改为了

这样系统误差便不会影响了。

5.下一个种子点的选取

教材上的种子点选取算法有点搞不懂,我按照上面的思路实现出来,在填充的时候出现了一系列问题。后来干脆放弃了教材中的方法,自己改写了一下。

思路大体上是这样的。

在填充完一行后,这一行最左边的像素点我们定义为(xLeft,y),最右边的像素我们定义为(xRight,y),扫描上一行找寻下一个种子点,这里y就要增加1,如果(xRight,y+1)这个点不是边界不是已经填充的点,那么这个点就可以作为种子点压入堆栈。如果这个点是边界或者是已经填充的点,那么就继续往左搜索,如果找到既不是边界又未填充的点,那么这个点就是种子点,压入堆栈。如果一直往左找到xLeft还是没有找到的话,就不存在下一个种子点了。下一行扫描线也是同样的原理,y要在这个基础上减去2即可。

恩,不知道大家有没有看懂,这是我自己想出来的方法,不敢保证完全正确,在此仅供参考

如果大家真的可以按照教材中的方法实现成功的话,希望告诉我一下,感激不尽。

方法实现

恩,重要的地方都已经点明了,下面就直接附上我的种子填充算法吧!

以上便是我实现的种子填充算法,仅供参考

在这里我们用到了glPoint画点的方法,这是我们定义的,方法如下,为了便于调试,每画一个点刷新一下,这样我们就可以看到绘制的全部动态效果。

以上便是画点的函数

方法使用

种子填充算法肯定要在我们绘制完机器人之后使用,任意选取某个四连通区域的点,传入xy坐标值还有要填充的颜色的RGB值,就可以成功实现填充。

在上一篇机器人的基础上,我们在画机器人的方法最后加入下面的代码,即可实现填充。

注意,有个很奇怪地方是绘制完了之后机器人就不见了,所以在这里加入了system(“pause”)方法来暂停一下就好啦。

其他的代码基本都是上一篇中的了,大家自行整理。

运行结果

运行结果截图如下

20150419015002

恩,就是这样!

总结

以上便是博主利用种子填充算法来实现的机器人的颜色填充,在此分享给大家,希望对大家有帮助!

如有问题和错误,欢迎大家给予我批评和指正,谢谢!

转载请注明:静觅 » OpenGL绘图实例三之种子填充算法

喜欢 (22)or分享 (0)

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请狠狠点击下面的

  1. 你好~大二学生正在学习opengl,对您的代码还有一些不明白的地方。border这个变量没有被定义啊?还有halfwidth和halfheight的赋值也不知道,是多少。很想实现您的代码~望解答~

    • 之前的代码已经没有了。borderColor就是你的边界颜色,你可以自己定义,比如现在的是黑色的。halfwidth和halfheight是一半的窗口的长度。根据你窗口大小而定