【寒江雪】扫描线区域填充算法

来源:互联网 发布:linux nfs配置 编辑:程序博客网 时间:2024/06/05 08:53

多边形可以用多个点进行描述。假定点是按逆时针排序给出的。这样一来要填充由这些点所围区域,就需要扫描这一区域内点的点。

如果我们知道区域内的所有顶点,显然,我们可以直接对点着色。但这样的输入就显得不太方便了。倘若我们以数学武装起来,可以把不方便变为方便,把繁琐的事化简,那就是世界上再美妙不过的事了。

现在我们得到的输入只有按逆时针排序的顶点。我们把点连成线,再以y轴为扫描线,自底相上求交点,再把交点配对,对两点间的点进行着色。

算法步骤如下:

求交点

排序

配对

着色

 

但是求交和排序的操作效率比较低,就需要优化。

不过,讲道理,排序算法效率基本上就那样,就看怎么选择罢了。{书上介绍的是使用插入排序。如果允许边自相交则使用冒泡排序。本人比较懒,直接调用了标准库的sort}

再来看求交点的方法。因为输入时按逆时针排序,那么每个顶点的左边和右边的点都是与它相邻的顶点,并且连接可以得到直线。由于是以y为扫描线进行扫描,y的递增量为1,那就需要考虑下一个交点的横坐标在哪。

【考虑当前被扫描的点为(xi,yi),下一点交点则为(x(i+1),y(i+1)),这里y(i+1)=yi+1】

那么问题就转换为求x(i+1)了

考虑直线方程

ax+by+c=0

由此可得

x=-b/a*(y)-c/a       (a!=0)

因此

xi = -b/a*(yi)-c/a

x(i+1)= -b/a*(y(i+1))-c/a=-b/a*(yi+1)-c/a=xi-b/a        (a!=0)

-b/a即为斜率1/k

 

我们约定a=0时,x(i+1)=xi+0。这种情况,该条直线是一条水平线,两个点的y值相等,我们是不会考虑水平线的。

 

这样按y递增扫描的方式,就可以一步一步求出交点来了。不过!!这是某条水平线与直线的交点,要考虑到什么时候,对某条直线的扫描终止,也就是迭代终止的条件。

再来看输入的点,那些按逆时针排序的点,它的左边和右边,我们以左边举例,如果它左边的点的纵坐标比它的纵坐标大,我们只需要在y递增到这个值的时候就可以放弃对这条直线的扫描了。

 

因此,我们引入这样一张表

表中的每个元素都是一个链表,每个y值对应一个链表。从最低的y_min向上扫描

而链表中的元素是这样的结构

x,dx,y_max;

x代表当前点的横坐标,

dx,就是要计算下一点横坐标的增量(1/k或者是0)

y_max就是终止条件。假设当前扫描线纵坐标为yi,若yi+1<y_max,则计算下一点并加入yi+1

所对应的链表中。供下次扫描

 

给出点(1,1),(4,1),(2,3),(4,5),(1,5)

得出下表

图片

从1开始往上叠加,一直到5,就可以计算出所有交点

再从1开始,对点进行配对,对区间进行填充

再求交点的时候,我们对以下特殊情况进行约定:

1.    扫描线单纯地与直线相交,只记一个交点。

特征就是有左右两边只有一个点的纵坐标比当前点的大

图片

2.    扫描线与直线重合,只记一个交点。

特征就是,左右两边有一个点的纵坐标和当前点的一样大

图片

3.    扫面线的点为底谷,记为两个点

特征就是左右两边的点的纵坐标比当前点的都高

图片

4.    扫描线的点为顶峰,不记点

特征就是左右两边的点的纵坐标比当前点的都低

图片

通用的描述方法就是,初始化表的时候,获取相邻点,当相邻点的纵坐标比当前点的大时,就将该点放入相应的表中。

 
 

至于水平线,要特别说明一下,扫描线填充算法对水平线是不在乎的,只需要有左右两端点,在填充的时候就可以给该水平线着色。

 
 

至于顶峰的点被忽略的问题,这个重要用一个数组标记一下,过后再对其进行着色就可以达到不遗失任何点的目的(本人就是这么干的,有强迫症)

 
 

还有排序算法的选择,经典的做法初始化表的时候先建立新边表,然后再扫描的过程,先把点从新边表按插入排序的方法插入活动边表,之后再扫描,更新出新的点并且还是按插入排序算法插入下一个链表中,这里用插入排序算法效率很高是因为,待插入链表中的数据是有序的,把一个元素插入一个有序的序列中,时间效率是O(n)的。但本人偷懒就直接使用sort,并使用自己写的比较函数来排序。

 
 

排序是二级排序,先按x由小到大递增,如果x相等,则按dx由小到大排

 
 

扫描线算法也可以用来绘制多边形,但是复杂度不如直接对点按顺序使用Bresenham直线算法好。

 

代码如下:

VOID ScanningLine_AreaFill(vector<Point>&point,COLORREF color,intstep,int pointSize)

{

   inty_min = 0x3f3f3f3f;

   inty_max = -1;

   for (int i =0; i <point.size(); i++) {

        y_min=min(point[i].y,y_min);

        y_max=max(point[i].y,y_max);

    }

 

   intshift = 0;//默认为不用平移

   if(y_min < 0) {

       //往上平移y_min的距离

        shift= abs(y_min);

        y_max+= shift;

    }

   vector<AET_NODE>*AET =new vector<AET_NODE>[y_max+1];

   vector<int>peek;

   for (int i =0; i <point.size(); i++) {

       floaty_left = (i == 0 ? point[point.size()- 1].y :point[i -1].y);

       floatx_left = (i == 0 ? point[point.size()- 1].x :point[i -1].x);

       floatdelta_left = GetDelta(point[i].x,point[i].y,x_left, y_left);//dy_left == 0 ? 0 : dx_left /dy_left;

 

       floatx_right = (i == point.size()- 1 ?point[0].x :point[i + 1].x);

       floaty_right = (i == point.size()- 1 ?point[0].y :point[i + 1].y);

       floatdelta_right = GetDelta(point[i].x,point[i].y,x_right, y_right);

       if(y_left > point[i].y)

            AET[point[i].y +shift].push_back(AET_NODE{ (float)point[i].x ,delta_left , (int)y_left  + shift });

       if(y_right > point[i].y)

            AET[point[i].y +shift].push_back(AET_NODE{ (float)point[i].x ,delta_right, (int)y_right + shift });

 

       if(y_left <= point[i].y&&y_right<=point[i].y)

            peek.push_back(i);

    }

 

   int y =y_min < 0 ? 0 : y_min;

   while (y<= y_max) {

        sort(AET[y].begin(),AET[y].end(),CompareAET_NODE);

       for (int i =0; i < AET[y].size(); i++) {

           if (y!= y_max && AET[y][i].y_max> y + 1) {

                AET[y+ 1].push_back(AET_NODE{ AET[y][i].x +AET[y][i].dx,AET[y][i].dx,AET[y][i].y_max});

            }

        }

        y++;

    }

 

    y= y_min < 0 ? 0 : y_min;

   while (y<= y_max) {

       for (int i =1; i < AET[y].size(); i+=2) {

           int x_signal_min= AET[y][i - 1].x>= 0 ? 1 : -1;

           intx_min = ((int)(abs(AET[y][i -1].x)+0.5f))*x_signal_min;

           intx_signal_max = AET[y][i].x>= 0 ? 1 : -1;

           intx_max = ((int)(abs(AET[y][i].x)+0.5f))*x_signal_max;

           for (int j =x_min; j <= x_max; j+=step) {

                DrawRectange(j,y - shift,pointSize,pointSize,color);

            }

        }

        y+=step;

    }

   for (int i =0; i < peek.size(); i++) {

        DrawRectange(point[peek[i]].x,point[peek[i]].y,pointSize,pointSize, color);

    }

   delete[]AET;

   return VOID();

}

 
 

多边形绘制算法:

VOID Polygon(vector<Point>&point,COLORREF color,int step, int pointSize)

{

    BresenhamInteger(point[0].x,point[0].y,point[point.size()- 1].x,point[point.size()- 1].y,color,step,pointSize);

   for (int i =1; i <point.size(); i++) {

        BresenhamInteger(point[i -1].x,point[i -1].y,point[i].x,point[i].y,color, step, pointSize);

    }

   return VOID();

}

0 0
原创粉丝点击