简单的种子填充算法

扫描线种子填充算法

种子填充算法的思路就是通过区域的一点赋予指定的颜色,然后通过填充其周围的像素点,从而将填充颜色扩展到整个颜色区域的过程。

如何画图

我们已经知道有很多工具可以拿来画图,比如MFC或者是OpenGL都是画图很好的手段。这两个都是拥有很多适应方法的软件接口。但是我使用的是Easyx这个更加方便便捷的工具。

该工具可以在如下页面下载,安装至相应的编译器即可。

http://www.easyx.cn/

下载完成之后记得安装Easyx里面的Easyx_Help.chm,这里面有许多我们开发会运用到的方法。

我们也知道种子填充算法需要获取点的颜色和在某个点再画上你需要的颜色,所以需要用到如下方法

//这个是为了获取某坐标点的颜色
COLORREF getpixel(int x, int y);
//这个是为了在某个点画上你想要的颜色
void putpixel(int x, int y, COLORREF color);

简单的种子填充算法

我们需要的是通过getpixel方法来获取点的颜色,判断其是否与原颜色相同,如果不相同,则变化其颜色为新的 颜色,通过递归其4连通区域来达到有序在不越出区域的情况下,到达区域内的任意元素。

void FloodFill4_1(int x, int y, COLORREF oldcolor, COLORREF newcolor) {
     try {
         if (x > 100 && x < 300 && y > 100 && y < 300) {
             if (getpixel(x, y) == oldcolor)
             {
                 Sleep(1);
                 putpixel(x, y, newcolor);
                 FloodFill4_1(x, y + 1, oldcolor, newcolor);
                 FloodFill4_1(x, y - 1, oldcolor, newcolor);
                 FloodFill4_1(x - 1, y, oldcolor, newcolor);
                 FloodFill4_1(x + 1, y, oldcolor, newcolor);
             }
         }
     }
     catch (stack) {
     }
}

扫描线种子填充算法

扫描线填充可由以下4个步骤实现

  1. 初始化:堆栈置空。讲种子点(x,y)入栈
  2. 出栈:若栈空则结束。否则取栈顶元素(x,y),以y作为当前扫描线。
  3. 填充并确定种子点所在区段:从种子点(x,y)出发,沿当前扫描线向左、右两个方向填充,直到边界。分别标记区段的左、右端点坐标为xlxr
  4. 确定新的种子点:在区间[xl,xr]中检查与当前扫描线y上、下相邻的两条扫描线上的像素。若存在非边界、未填充的像素,则把每一区间的最右像素作为种子点压入堆栈,返回第2步。
void ScanFill4(int x, int y, int oldcolor, int newcolor)
 {
     int xl, xr, i;
     bool spanNeedFill;
     Seed pt;
     stack *stack1 =  new stack();
     InitStack(stack1);
     pt.x = x;
     pt.y = y;
     Push(stack1, pt);    //将前面生成的区段压入堆栈
     while (!IsEmpty(stack1))
     {
         Sleep(10);
         pt = Pop(stack1);
         y = pt.y;

         x = pt.x;
         while (getpixel(x, y) == oldcolor)//向右填充
         {
             putpixel(x, y, newcolor);
             x++;
         }
         xr = x - 1;

         x = pt.x - 1;
         while (getpixel(x, y) == oldcolor) //向左填充
         {
             putpixel(x, y, newcolor);
             x--;
         }
         xl = x + 1;


         //处理上一条扫描线
         x = xl;
         y = y + 1;
         while (x<xr)
         {
             spanNeedFill = false;
             while (getpixel(x, y) == oldcolor)
             {
                 spanNeedFill = true;
                 x++;
             }
             if (spanNeedFill)
             {
                 pt.x = x - 1;
                 pt.y = y;
                 Push(stack1, pt);
                 spanNeedFill = false;
             }
             while (getpixel(x, y) != oldcolor  && x<xr)
                 x++;
         }
         //处理下一条扫描线,代码与处理上一条扫描线类似
         x = xl;
         y = y - 2;
         while (x<xr)
         {
             spanNeedFill = false;
             while (getpixel(x, y) == oldcolor)
             {
                 spanNeedFill = true;
                 x++;
             }
             if (spanNeedFill)
             {
                 pt.x = x - 1;
                 pt.y = y;
                 Push(stack1, pt);
                 spanNeedFill = false;
             }
             while (getpixel(x, y) != oldcolor  && x<xr)
                 x++;
         }
     }
 }

上述算法对于每一个待填充区段,只需要压栈一次,而在递归算法中每一个像素都需要压栈一次。大大提高了区域填充的效率

算法中遇到的问题

在编写简单的种子算法中遇到了堆栈溢出的问题,这也是正常所在,递归每个像素都需要压栈,而Visal Studio的初始设定安全堆栈大小已经过于小了,临时解决办法为:
Project->Property->Linker->All Options中找到Stack Reserve Size 尽量将其调大就行了