上篇博文讲到了填充算法的扫描线填充,这篇博文讲解另一大算法思路----------种子填充。

一、概念

种子填充算法假设在多边形或区域内部至少有一个像素是已知的。然后设法找到区域内所有其他像素,并对它们进行填充。区域可以用内部定义边界定义

如果是内部定义,那么,区域内部所有像素具有同一种颜色或值,而区域外的所有像素具有另一种颜色或值,如下图:

如果是边界定义,那么区域边界上所有像素均具有特定的值或颜色。区域内部的所有像素均不取这一特定值。然而,边界外的像素则可具有与边界相同的值。如下图:

填充内部定义区域的算法成为泛填充算法(flood fill algorithm),填充边界定义区域的算法称为边界填充算法

本篇blog着重讲解边界填充算法

二、种子填充算法

简单的种子填充算法

对于边界填充算法可以采用堆栈。采用堆栈的简单种子填充算法思路如下:

种子像素压入堆栈
当堆栈非空时从堆栈中弹出一个像素将该像素置成所要求的值检查每个与当前像素邻接的4连通像素是否是边界像素或者是否已置成所要求的值。若是上述两种情况之一, 则略而不计。否则把该像素压入堆栈。

常见的这种填充有四连通种子边界填充还有八连通种子边界填充,以四连通种子边界填充为例,写下伪代码:

#simple seed fill algorithm for 4-connected boundary-defined regions
#简单的四连通种子边界填充算法
#Seed(x,y) is the seed pixel:这是种子像素
#Push is a function for placing a pixel on the stack:Push是栈的push
#Pop is function for removing a pixel on the stack:Pop是栈的PopPixel(x,y) = Seed(x,y)#initialize stack
Push Pixel(x,y)
while(stack not empty)#get a pixel from the stack:从栈中获取像素Pop Pixel(x,y)# <>:表示不具有if Pixel(x,y) <> New value then Pixel(x,y) = New valueend if#examine the surrounding pixels to see if they should be placed onto the stack:判断是否具有边界值或新值if(Pixel(x+1,y) <> New value and Pixel(x+1,y) <> Boundary value) thenPush Pixel(x+1,y)end ifif(Pixel(x,y+1) <> New value and Pixel(x,y+1) <> Boundary value) thenPush Pixel(x,y+1)end ifif(Pixel(x-1,y) <> New value and Pixel(x-1,y) <> Boundary value) thenPush Pixel(x-1,y)end ifif(Pixel(x,y-1) <> New value and Pixel(x,y-1) <> Boundary value) thenPush Pixel(x,y-1)end if
end while

可能有些人看完了伪代码之后还是懵逼状态,那么我们就举个例子来讲该算法的具体流程:

例1.填充由顶点(1,1),(8,1),(8,4),(6,6)以及(1,6)所决定的边界定义多边形区域,取种子像素是(4,3)。具体的填充流程从种子像素出发,跟着绿色箭头走:

(这图是我用windows自带的画图工具画的,各位看官还有没有更好的画这种图的工具啊,求告知,用mspaint一点一点的画真的一把辛酸泪啊o(╥﹏╥)o)

这个算法从种子像素开始,先检查种子像素右侧的像素是否是新值和边界值,发现都不是,就把(5,3)压入stack,然后把上侧(4,4),左侧(4,3),下侧(4,2)压入stack。也就是从把种子像素从右侧像素开始逆时针压入堆栈。压完了以后,开始检查stack不为空,于是pop,就pop出了(4,2)。于是以(4,2)为种子像素开始,又一轮压入循环,凡是被判为种子像素的像素都会用正红色填充。所以这幅图里的很多像素可能会被压入多次,当算法走到像素(5,5)的时候,由于(5,5)周围的像素具有边界值或新值,就没有像素压入堆栈了。于是Pop(7,4),接着往下填充。当达到像素(7,1)的时候,周围像素或已置成新值(正红色)或为边界像素,至此多边形已全部填充完毕,因此只从堆栈中弹出像素而不再填充新的像素,直到堆栈为空时算法停止。

代码如下:

void CCGPainterView::BoundaryFill_4Connection(CDC *pDC, CPoint startPoint, COLORREF fillCol, COLORREF boundaryCol)
{//Write your boundary fill algorithm here.CPoint fillPoint = startPoint;COLORREF currentCol = pDC->GetPixel(fillPoint);if(currentCol != boundaryCol&&currentCol != fillCol){pDC->SetPixelV(fillPoint, fillCol);fillPoint.y = startPoint.y+1;   //(x, y+1)BoundaryFill_4Connection(pDC, fillPoint, fillCol, boundaryCol);fillPoint.y = startPoint.y-1;    //(x, y-1)BoundaryFill_4Connection(pDC, fillPoint, fillCol, boundaryCol);fillPoint.x = startPoint.x-1;fillPoint.y = startPoint.y;     //(x-1, y)BoundaryFill_4Connection(pDC, fillPoint, fillCol, boundaryCol);fillPoint.x = startPoint.x+1;    //(x+1, y)BoundaryFill_4Connection(pDC, fillPoint, fillCol, boundaryCol);}
}

扫描线种子填充算法

我们可以看到刚刚的算法其实效率很慢,一来说,堆栈可能会很大占用内存,二来说,堆栈中还包含了很多重复和不必要的信息。

扫描线种子填充算法就克服了这种缺陷:在任意不间断扫描线区段中只取一个种子像素。不间断区段的意思就是指一条扫描线上的一组相邻像素。算法的大概过程是:

从包含种子像素的堆栈中弹出区段的种子像素
沿着扫描线对包含种子像素的区段左右像素进行填充,直至遇到边界像素为止,从而填满包含种子像素的区段。区段内最左的和最右的像素记为Xleft和Xright。在Xleft<=x<=Xright范围内,检查与当前扫描线相邻的上下两条扫描线是否全为边界像素或者前面已经填充过的像素。
如果这些扫描线既不包括边界元素,也不包括已填充的像素,那么在Xleft<=x<=Xright中把每一个区段的最右像素取做种子像素并压入堆栈

算法的伪代码如下:

#scan line seed fill algorithm:扫描线种子填充
#Seed(x,y) is the seed pixel:这是种子像素
#Pop is a function for removing a pixel from the stack:Pop是栈的Pop
#Push is a function for placing a pixel on the stack:Push是栈的push#initialize stack:初始化栈Push Seed(x,y)while(stack not empty)#get the seed pixel and set it to the new value:把种子像素弹出,填充种子像素Pop Pixel(x,y)Pixel(x,y) = Fill value#save the x coordinate of the seed pixel:保存种子像素的x坐标Savex = x#fill the span to the right of the seed pixel:种子像素的x坐标加1,向右走一步x = x+1#从种子像素向右填充,直到遇到边界像素停止while Pixel(x,y) <> Boundary valuePixel(x,y) = Fill valuex = x+1end while#save the extreme right pixel:保存区段的右端点XrightXright = x-1#reset the x coordinate to the value for the seed pixel:把x重置为种子像素的x坐标值x = Savex#fill the span to the left of the seed pixel:种子像素的x坐标减1,向左走一步x = x-1while Pixel(x,y) <> Boundary value:从种子像素向左填充,直到遇到边界像素停止Pixel(x,y) = Fill vaex = x-1end while#save the extreme left pixel:保存区段的左端点XleftXleft = x+1#reset the x coordinate to the value for the seed pixel:把x重置为种子像素的x坐标值x = Savex#check that the scan line above is neither a polygon boundary nor #has been previously completely filled; if not,seed the scan line#start at the left edge of the scan line subsan#检查上面一条扫描线是不是边界线或者有没有被填充过,如果没有,从这条扫描线的左边界开始扫描x = Xlefty = y+1while(x<=Xright)#seed the scan line above:扫描上面的一条扫描线Pflag = 0while(Pixel(x,y) <> Boundary value and Pixel(x,y) <> Fill value and x < Xright)if Pflag = 0 then Pflag = 1x = x+1end while#push the extreme right pixel onto the stack:把该条扫描线最右端压入堆栈if Pflag = 1 thenif(x = Xright and Pixel(x,y) <> Boundary value and Pixel(x,y) <> Fill value) thenPush Pixel(x,y)elsePush Pixel(x-1,y)end ifPflag = 0end if#continue checking in case the span is interrupted:继续检查Xenter = xwhile((Pixel(x,y) = Boundary value or Pixel(x,y) = Fill value) and x<Xright)x = x+1end while#make sure that the pixel coordinate is incrementedif x = Xenter then x = x+1end while#check that the scan line below is not a polygon boundary nor has been#previously completely filled#this algorithm is exactly the same as that for checking the scan line#above except that y = y-1 is substituted for y = y+1#检查下面一条扫描线是否是边界线或者是否被填充过,如果没有,从左边界开始扫描,代码和上面一样,略
end while
Finish

举个例子:算法填充该多边形,种子像素为(5,7)

多边形种子像素(5,7)压入堆栈。
算法开始:
将(5,7)像素作为区段种子从堆栈中退出。向右向左填充种子所在的区段
找到的端点是Xright=9,Xleft=1检查上面一条扫描线,它不是边界线,也尚未填充,在1<=x<=9范围内最右的像素是(8,8)。这个像素就被标记为1
接着检查下面的一条扫描线,它既非边界线,也未被填充,在1<=x<=9这个范围内有两个子区段,
左边子区段取像素(3,6)为种子,用2来标记,然后把它压入堆栈
右边子区段取像素(9,6)为种子,用3来标记它,压入堆栈算法一遍结束算法继续从堆栈中推出顶部像素。这里,算法逐条往下填充多边形右边扫描线区域。依次压入的是(10,5),(10,4),(10,3)并填充
在(10,3)的时候,向左以及向右依次填充该区段得到Xleft=1和Xright=10,检查上面的扫描线,得到左边子区段的种子像素是(3,4)
把它压入堆栈。右边的子区段已填充。检查下面的扫描线,得到左边子区段的种子像素(3,2),以及右边子区段的种子像素(10,2),这两个像素也被压入堆栈。然后多边形依次弹出种子点进行填充。最后弹出的是标记为1的像素种子点,并填充这条扫描线。这时候没有新像素压入堆栈,此时堆栈已空,多边形填满。算法结束

因此完成的程序代码应该是这样的:

void CCGPainterView::ScanLineFill(CDC *pDC, CPoint startPoint, COLORREF fillCol, COLORREF boundaryCol)
{//Write your boundary fill algorithm here.//定义堆栈CArray<CPoint, CPoint>Stack;CPoint fillPoint = startPoint;COLORREF currentCol = pDC->GetPixel(startPoint);//最左点,最右点int xl, xr;//标志位bool spanNeedFill;//种子点CPoint pt;Stack.RemoveAll();//设置堆栈为空pt.x = fillPoint.x;pt.y = fillPoint.y;Stack.Add(pt);//种子点压入堆栈while (Stack.GetSize() != 0){pt= Stack[Stack.GetSize() - 1];//取出种子点Stack.RemoveAt(Stack.GetSize() - 1);//堆栈减短fillPoint.y = pt.y;fillPoint.x = pt.x;//从种子开始向右填充while (pDC->GetPixel(fillPoint.x, fillPoint.y) == currentCol){pDC->SetPixelV(fillPoint, fillCol); fillPoint.x++;}  xr = fillPoint.x - 1;fillPoint.x = pt.x - 1; //从种子开始向左填充while (pDC->GetPixel(fillPoint.x, fillPoint.y) == currentCol){    pDC->SetPixelV(fillPoint, fillCol);fillPoint.x--;} xl = fillPoint.x + 1;//处理上一条扫描线和下一条扫描线for (int I = 0; I < 2; I++) {fillPoint.x = xl;if (I == 0) fillPoint.y = fillPoint.y + 1;else fillPoint.y = fillPoint.y - 2;while (fillPoint.x < xr){spanNeedFill = FALSE;while (pDC->GetPixel(fillPoint.x, fillPoint.y) == currentCol){spanNeedFill = TRUE;fillPoint.x++;}//待填充区搜索完毕if (spanNeedFill){//右端点作为种子入栈pt.x = fillPoint.x - 1;pt.y = fillPoint.y;Stack.Add(pt);spanNeedFill = FALSE;}//继续向右检查以防遗漏while (pDC->GetPixel(fillPoint.x, fillPoint.y) != currentCol && fillPoint.x < xr) fillPoint.x++;}//上一条扫描线上检查完毕}}}

最后的实验效果是这样的:

三 、泛滥填充

代码如下:

void CCGPainterView::BoundaryFill_4Connection(CDC *pDC, CPoint startPoint, COLORREF fillCol, COLORREF backgroundCol)
{//Write your boundary fill algorithm here.CPoint fillPoint = startPoint;         //设起始点为填充点backgroundCol = RGB(255, 255, 255);    //设置背景颜色为白色COLORREF currentCol = pDC->GetPixel(fillPoint);if(currentCol == backgroundCol)        //判断当前像素点的颜色是否和背景颜色一致{pDC->SetPixelV(fillPoint, fillCol);fillPoint.y = startPoint.y+1;    //递归(x, y+1)点BoundaryFill_4Connection(pDC, fillPoint, fillCol, backgroundCol);fillPoint.y = startPoint.y-1;       //递归(x, y-1)点BoundaryFill_4Connection(pDC, fillPoint, fillCol, backgroundCol);fillPoint.x = startPoint.x-1;fillPoint.y = startPoint.y;        //递归(x-1, y)点BoundaryFill_4Connection(pDC, fillPoint, fillCol, backgroundCol);fillPoint.x = startPoint.x+1;       //递归(x+1, y)点BoundaryFill_4Connection(pDC, fillPoint, fillCol, backgroundCol);}
}

图形学初步--------种子填充算法相关推荐

  1. java实现种子填充算法,Java编写图形学的种子填充算法

    用C写的图形学填充算法已经很多了,看到不少帖子都是在问关于如何用Java编写图形学的填充算法,说来也巧,我刚好要做一个这个方面的实验,用的是扫描线种子填充算法,由于时间仓促,代码质量可能不算很高,希望 ...

  2. 图形学初步----------多边形填充算法

    参考博文: https://blog.csdn.net/xiaowei_cqu/article/details/7693985 https://blog.csdn.net/xiaowei_cqu/ar ...

  3. c语言 连通域算法 递归,VC++ 6.0编写计算机图形学中的种子填充算法,想用递归的八向连通域,求助!...

    VC++ 6.0编写计算机图形学中的种子填充算法,想用递归的八向连通域,求助!0 填充函数代码如下: void CComputerGraphicsView::PolygonFill2()//区域填充函 ...

  4. 种子填充算法----计算机图形学

    种子填充算法: 种子填充算法的基本思想是:从多边形区域的一个内点开始,由内向外用给定的颜色画点直到边界为止. 区域 可以由内部点或边界来定义,一般都采用边界定义,即区域边界上所有像素被置为特定值,而区 ...

  5. 多边形区域填充算法--扫描线种子填充算法

    分享一下我老师大神的人工智能教程.零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow http://bl ...

  6. 【Algorithm】种子填充算法

    转载:https://www.cnblogs.com/icmzn/p/5065306.html [平面区域填充算法]是计算机图形学领域的一个很重要的算法,区域填充即给出一个区域的边界 (也可以是没有边 ...

  7. c++实现种子填充算法与扫描线算法

    前言 默认您已经配置好相关环境. 如若没有,可以自行搜索"EasyX"下载安装. 本教程主要从代码方面讲解算法的实现. 至于理论部分还请大家自行搜索其他文章.(该类文章非常多) 种 ...

  8. matlab中种子填充算法

    因为作业要求用简单种子填充和线扫描填充,所以在网上找了一个用matlab写的简单种子填充算法. https://www.cnblogs.com/tiandsp/archive/2012/12/06/2 ...

  9. 计算机图形学 OpenGl-种子填充算法画红黄绿交通灯

    一.实验原理 我就不多叙述了,课本上说的已经够多了,其次我自己也是借鉴别人的代码写出来的 二.上机环境 VS2010 三.代码运行效果 四.完整代码 #include <GL/glut.h> ...

最新文章

  1. Alan Kay等专家领衔,北京智源大会6月24日精彩预告
  2. 优雅的缓存解决方案--设置过期时间
  3. C#判断文件及文件夹是否存在并创建
  4. 以太坊智能合约开发:让合约接受转账
  5. Java SSH 集成框架开发中的错误解决
  6. 计算机专业介绍范文英文,计算机专业个人简历英文范文
  7. QT5开发及实例学习之十七Qt5双缓冲机制
  8. (6)Python集合
  9. 项目管理中的小组周报模板
  10. 基于java的教学评价系统的设计与实现
  11. 考研:无穷小微积分的不适症
  12. aardio - 【库】FlexCell表格组件
  13. 微信native支付
  14. vbs进阶——实用函数之msgbox篇
  15. kubectl 命令详解(三十二):rollout pause
  16. android动手写平滑滚动歌词控件
  17. 叠加dgv中相同的行信息
  18. 招商软文如何写:推广诱人的广告——文芳阁传媒有话说
  19. 吴恩达深度学习课程值不值得学?四晚学完的高手给你建议
  20. 【报错解决】expected single matching bean but found 2

热门文章

  1. 【Linux】Ubuntu 18.04网易云音乐安装后无法打开问题解决
  2. 电脑网页游戏打不开?
  3. 基于Zotero和坚果云的大规模文献同步管理环境配置及常用功能介绍(超详细)
  4. 你知道什么是自带PLC的网关吗?
  5. 黑苹果NVIDIA显卡驱动程序【 WebDriver-378.05.05.25f01+支持 macOS 10.12.6 Sierra (16G29)版本】
  6. FISCO-BCOS应用实战:区块链实战应用开发分享
  7. 从杀慢查询入手来预防 MySQL 雪崩的办法
  8. pgsql将为NULL或空字符串的字段替换为指定默认值,格式化时间戳,用指定分隔符截取字符串等操作
  9. java开发规范--编程规约--集合处理
  10. 《南朝凶猛》 轩辕鸿鸣