二、扫描线算法(Scan-Line Filling)
扫描线算法适合对矢量图形进行区域填充,只需要直到多边形区域的几何位置,不需要指定种子点,适合计算机自动进行图形处理的场合使用,比如电脑游戏和三维CAD软件的渲染等等。
对矢量多边形区域填充,算法核心还是求交。《计算几何与图形学有关的几种常用算法》一文给出了判断点与多边形关系的算法――扫描交点的奇偶数判断算法,利用此算法可以判断一个点是否在多边形内,也就是是否需要填充,但是实际工程中使用的填充算法都是只使用求交的思想,并不直接使用这种求交算法。究其原因,除了算法效率问题之外,还存在一个光栅图形设备和矢量之间的转换问题。比如某个点位于非常靠近边界的临界位置,用矢量算法判断这个点应该是在多边形内,但是光栅化后,这个点在光栅图形设备上看就有可能是在多边形外边(矢量点没有大小概念,光栅图形设备的点有大小概念),因此,适用于矢量图形的填充算法必须适应光栅图形设备。
2.1扫描线算法的基本思想
扫描线填充算法的基本思想是:用水平扫描线从上到下(或从下到上)扫描由多条首尾相连的线段构成的多边形,每根扫描线与多边形的某些边产生一系列交点。将这些交点按照x坐标排序,将排序后的点两两成对,作为线段的两个端点,以所填的颜色画水平直线。多边形被扫描完毕后,颜色填充也就完成了。扫描线填充算法也可以归纳为以下4个步骤:
(1) 求交,计算扫描线与多边形的交点
(2) 交点排序,对第2步得到的交点按照x值从小到大进行排序;
(3) 颜色填充,对排序后的交点两两组成一个水平线段,以画线段的方式进行颜色填充;
(4) 是否完成多边形扫描?如果是就结束算法,如果不是就改变扫描线,然后转第1步继续处理;
整个算法的关键是第1步,需要用尽量少的计算量求出交点,还要考虑交点是线段端点的特殊情况,最后,交点的步进计算最好是整数,便于光栅设备输出显示。
对于每一条扫描线,如果每次都按照正常的线段求交算法进行计算,则计算量大,而且效率底下,如图(6)所示:
图(6) 多边形与扫描线示意图
观察多边形与扫描线的交点情况,可以得到以下两个特点:
(1) 每次只有相关的几条边可能与扫描线有交点,不必对所有的边进行求交计算;
(2) 相邻的扫描线与同一直线段的交点存在步进关系,这个关系与直线段所在直线的斜率有关;
第一个特点是显而易见的,为了减少计算量,扫描线算法需要维护一张由“活动边”组成的表,称为“活动边表(AET)”。例如扫描线4的“活动边表”由P1P2和P3P4两条边组成,而扫描线7的“活动边表”由P1P2、P6P1、P5P6和P4P5四条边组成。
第二个特点可以进一步证明,假设当前扫描线与多边形的某一条边的交点已经通过直线段求交算法计算出来,得到交点的坐标为(x, y),则下一条扫描线与这条边的交点不需要再求交计算,通过步进关系可以直接得到新交点坐标为(x + △x, y + 1)。前面提到过,步进关系△x是个常量,与直线的斜率有关,下面就来推导这个△x。
假设多边形某条边所在的直线方程是:ax + by + c = 0,扫描线yi和下一条扫描线yi+1与该边的两个交点分别是(xi,yi)和(xi+1,yi+1),则可得到以下两个等式:
axi + byi + c = 0 (等式 1)
axi+1 + byi+1 + c = 0 (等式 2)
由等式1可以得到等式3:
xi = -(byi + c) / a (等式 3)
同样,由等式2可以得到等式4:
xi+1 = -(byi+1 + c) / a (等式 4)
由等式 4 – 等式3可得到
xi+1 – xi = -b (yi+1 - yi) / a
由于扫描线存在yi+1 = yi + 1的关系,将代入上式即可得到:
xi+1 – xi = -b / a
即△x = -b / a,是个常量(直线斜率的倒数)。
“活动边表”是扫描线填充算法的核心,整个算法都是围绕者这张表进行处理的。要完整的定义“活动边表”,需要先定义边的数据结构。每条边都和扫描线有个交点,扫描线填充算法只关注交点的x坐标。每当处理下一条扫描线时,根据△x直接计算出新扫描线与边的交点x坐标,可以避免复杂的求交计算。一条边不会一直待在“活动边表”中,当扫描线与之没有交点时,要将其从“活动边表”中删除,判断是否有交点的依据就是看扫描线y是否大于这条边两个端点的y坐标值,为此,需要记录边的y坐标的最大值。根据以上分析,边的数据结构可以定义如下:
65 typedef struct tagEDGE 66 { 67 double xi; 68 double dx; 69 int ymax; 74 }EDGE; |
根据EDGE的定义,扫描线4和扫描线7的“活动边表”就分别如图(7)和图(8)所示: