`
ldb19890624
  • 浏览: 243688 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

算法系列之十二:多边形区域填充算法--改进的扫描线填充算法

 
阅读更多

三、改进的扫描线填充算法

扫描线填充算法的原理和实现都很简单,但是因为要同时维护“活动边表(AET)”和“新边表(NET)”,对存储空间的要求比较高。这两张表的部分内容是重复的,而且“新边表”在很多情况下都是一张稀疏表,如果能对其进行改进,避免出现两张表,就可以节省存储空间,同时省去从“边表”生成“新边表”的开销,同时也省去了用“新边表”维护“活动边表”的开销,基于这个原则可以对原始扫描线算法进行改进。

3.1重新设计“活动边表”

改进的算法仍然使用了“活动边表”的概念,但是不再构造独立的“活动边表”,而是直接在“边表”中划定一部分区间作为“活动边区间”,也就是说,把多边形的边分成两个子集,一个是与扫描线有交点的边的集合,另一个是与扫描线没有交点的边的集合。要达到这个目的,只需要对“活动边表”按照每条边的顶点ymax坐标排序即可。这个排序与原始扫描线算法中对“活动边表”的维护原理是一样的,因为只有边的ymax坐标区间内与扫描线有交点的边才可能是“活动边”。为了避免重复扫描整个“活动边表”,需要用一个first指针和一个last指针用于标识“活动边区间”。first指针之前的边都是已经处理过的边,同样,last指针之后的边都是还没有处理的边。每处理完一条扫描线,都要更新firstlast指针位置,调整last指针的位置将ymax大于当前扫描线的边纳入到“活动边区间”,同时调整first指针将处理完成的边排除在“活动边区间”之外。

如果调整last指针的依据是边的ymax是否大于当前扫描线,那么调整first指针的依据是什么?也就是如何判断一条边已经处理完了?方法是在边(EDGE)定义中增加一个dy(Δy)属性,这个属性被初始化成这条边在y方向上的长度,每处理完一条扫描线,dy都要做减一处理,当dy0时,就说明这条边已经不与扫描线相交了,可以被排除在活动边区间之外。改进的扫描线算法的“边”的完整定义如下:

7typedef struct tagEDGE2

8{

9 double xi;

10 double dx;

11 int ymax;

12 int dy;

17}EDGE2;

EDGE2定义中xidxymax的含义和原始算法中EDGE的定义相同,只是多了一个dy属性。

每当处理一条扫描线时,除了“活动边区间”的first指针和last指针需要调整之外,还要将first指针和last指针之间的“活动边”按照xi从小到大的顺序排序,以保证填充算法能够用正确的交点线段序列画线填充。因此,每次调整“活动边区间”的first指针和last指针之后,都要对“活动边区间”重新排序,也就是说“活动边区间”内的各边的位置并不固定,会随着扫描线的变化而相应地变化。

仍以图(6)所示的多边形为例,处理扫描线10时的“活动边表”状态如图(11-a)所示,而处理扫描线8时的“活动边表”状态则如图(11-b)所示。可以看出,当处理扫描线8时,“活动边区间”内的边的顺序有了调整,因为新加入的P6P1P1P2两条边与扫描线的交点坐标xiP5P6与扫描线的交点坐标xi小,因此排在P5P6前面。

图(11)改进的活动边表结构

3.2新“活动边表”的构造与调整

改进的扫描线算法的重点是“活动边表”的构造和调整。“活动边表”的构造方法如下:

(1) 首先剔除多边形各边的水平边,然后将剩下的边按照ymax的值从大到小顺序存入一个线性表中,表中第一个元素ymax值最大的表,最后一个元素是ymax值最小的边。对于各边中左、右顶点的情况需要和原始算法一样做调整,以免出现交点个数不正确的异常。这里对调整的策略再强调一下,调整都是针对边的终点进行的,对于图(10-a)所示的左顶点,需要先将P2点的坐标调整为(x2 – dx, y2 - 1),然后再求边的ymaxxidy。对于图(10-b)所示的右顶点,需要将P2点的坐标调整为(x2 + dx, y2 + 1),然后再求边的ymaxxidy

(2) 加入first指针和last指针,构成“活动边区间”。first指针和last指针之间的边都是和当前扫描线有交点的边或已经处理过的边,已经处理过的边的dy0,因此,对“活动边”扫描时需要忽略其中dy已经是0的边。这些已经处理过的边会加载在正常的边中,直到调整first指针时被剔除出“活动边区间”。

“活动边表”的调整指的是在处理完每根扫描线之后,更新“活动边表”中“活动边区间”内的各边的相关属性的值,比如递减dy的值,调整交点xi坐标的值等等。根据EDGE2的定义,每根扫描线处理完之后需要对“活动边区间”内的边做如下调整:

1)调整“活动边区间”中参与求交计算的各边的属性值,这些调整算法是:

dy = dy – 1;

xi = xi – dx;

2)调整“活动边区间”的first指针和last指针,使符合条件的新边加入到“活动边区间”,同时将处理完的边从“活动边区间”剔除。这些调整算法是:

if(first所指边的Δy0)

first=first+1;

if(last所指的下一条边的ymax大于下一扫描线的y)

last=last+1

3.3改进的扫描线填充算法实现

首先定义“活动边表”,这是一个线性表,每个元素是一条边的全部属性,同时还要包含first指针和last指针,其数据结构定义如下:

19typedef struct tagSP_EDGES_TABLE

20{

21 std::vector<EDGE2> slEdges;

22 int first;

23 int last;

24}SP_EDGES_TABLE;

改进的扫描线填充算法重点仍然是新“活动边表”的构造,这里给出构造新“活动边表”的算法实现:

36void InitScanLineEdgesTable(SP_EDGES_TABLE& spET, const Polygon& py)

37{

38 EDGE2 e;

39 for(int i = 0; i < py.GetPolyCount(); i++)

40 {

41 const Point& ps = py.pts[i];

42 const Point& pe = py.pts[(i + 1) % py.GetPolyCount()];

43 const Point& pee = py.pts[(i + 2) % py.GetPolyCount()];

44

51 if(pe.y != ps.y) //不处理水平线

52 {

53 e.dx = double(pe.x - ps.x) / double(pe.y - ps.y);

54 if(pe.y > ps.y)

55 {

56 if(pe.y < pee.y) //左顶点

57 {

58 e.xi = pe.x - e.dx;

59 e.ymax = pe.y - 1;

60 e.dy = e.ymax - ps.y + 1;

61 }

62 else

63 {

64 e.xi = pe.x;

65 e.ymax = pe.y;

66 e.dy = pe.y - ps.y + 1;

67 }

68 }

69 else //(pe.y < ps.y)

70 {

71 if(pe.y > pee.y) //右顶点

72 {

73 e.xi = ps.x;

74 e.ymax = ps.y;

75 e.dy = ps.y - (pe.y + 1) + 1;

76 }

77 else

78 {

79 e.xi = ps.x;

80 e.ymax = ps.y;

81 e.dy = ps.y - pe.y + 1;

82 }

83 }

84

85 InsertEdgeToEdgesTable(e, spET.slEdges);

86 }

87 }

88 spET.first = spET.last = 0;

89}

Polygon定义了一个多边形,其pts数组按照顺序存放了多边形的各个顶点,InitScanLineEdgesTable()函数从Polygon中依次取出三个顶点,前两个顶点构成当前处理的边,后一个顶点用于辅助判断是否是左、右顶点的情况,如果是左、右顶点的情况,就要对边的终点的坐标做调整(调整的方法在3.2小节已经描述)。调整完线段终点坐标后构造边e,然后由InsertEdgeToEdgesTable()函数将e插入到线性表中,插入操作满足线性表按照ymax从大到小有序,这个是插入排序的基本算法,这里就不再列出代码。

算法的另一个终点就是处理每条扫描线和“活动边表”的关系,计算出每条扫描线需要填充的区间。一下就是ProcessScanLineFill2()函数的实现:

189void ScanLinePolygonFill2(const Polygon& py, int color)

190{

191 assert(py.IsValid());

192

193 int ymin = 0;

194 int ymax = 0;

195 GetPolygonMinMax(py, ymin, ymax);

196 SP_EDGES_TABLE spET;

197 InitScanLineEdgesTable(spET, py);

198 HorizonEdgeFill(py, color); //水'cb?平'c6?边'b1?直'd6?接'bd?画'bb?线'cf?填'cc?充'b3?

199 ProcessScanLineFill2(spET, ymin, ymax, color);

200}

ProcessScanLineFill2()函数依次处理每条扫描线,根据3.2节的算法描述,UpdateEdgesTableActiveRange()函数和SortActiveRangeByX()函数更新“活动边区间”并对区间内的边排序,FillActiveRangeScanLine函数从“活动边区间”内依次取出两个交点组成填充区间,调用前面介绍的DrawHorizontalLine()函数完成画线填充,UpdateActiveRangeIntersection()函数则根据3.2节的算法描述更新参与求交计算的各边的属性值。这四个函数的实现都很简单,结合3.2节的算法描述很容易理解,此处仅列出代码,不做过多解释。

91void UpdateEdgesTableActiveRange(SP_EDGES_TABLE& spET, int yScan)

92{

93 std::vector<EDGE2>& slET = spET.slEdges;

94 int edgesCount = static_cast<int>(slET.size());

95 while((spET.last < (edgesCount - 1)) && (slET[spET.last + 1].ymax >= yScan))

96 {

97 spET.last++;

98 }

99

100 while(slET[spET.first].dy == 0)

101 {

102 spET.first++;

103 }

104}

125void FillActiveRangeScanLine(SP_EDGES_TABLE& spET, int yScan, int color)

126{

127 std::vector<EDGE2>& slET = spET.slEdges;

128 int pos = spET.first;

129

130 do

131 {

132 pos = GetIntersectionInActiveRange(spET, pos);

133 if(pos != -1)

134 {

135 int x1 = ROUND_INT(slET[pos].xi);

136 pos = GetIntersectionInActiveRange(spET, pos + 1);

137 if(pos != -1)

138 {

139 int x2 = ROUND_INT(slET[pos].xi);

140 pos++;

141 DrawHorizontalLine(x1, x2, yScan, color);

142 }

143 else

144 {

145 assert(false);

146 }

147 }

148 }while(pos != -1);

149}

150

151bool EdgeXiComparator2(EDGE2& e1, EDGE2& e2)

152{

153 return (e1.xi < e2.xi);

154}

155

156void SortActiveRangeByX(SP_EDGES_TABLE& spET)

157{

158 std::vector<EDGE2>& slET = spET.slEdges;

159

160 sort(slET.begin() + spET.first,

161 slET.begin() + spET.last + 1,

162 EdgeXiComparator2);

163}

164

165void UpdateActiveRangeIntersection(SP_EDGES_TABLE& spET)

166{

167 for(int pos = spET.first; pos <= spET.last; pos++)

168 {

169 if(spET.slEdges[pos].dy > 0)

170 {

171 spET.slEdges[pos].dy--;

172 spET.slEdges[pos].xi -= spET.slEdges[pos].dx;

173 }

174 }

175}

176

177void ProcessScanLineFill2(SP_EDGES_TABLE& spET,

178 int ymin, int ymax, int color)

179{

180 for (int yScan = ymax; yScan >= ymin; yScan--)

181 {

182 UpdateEdgesTableActiveRange(spET, yScan);

183 SortActiveRangeByX(spET);

184 FillActiveRangeScanLine(spET, yScan, color);

185 UpdateActiveRangeIntersection(spET);

186 }

187}

<下一篇:边标志填充算法>

分享到:
评论

相关推荐

    多边形填充扫描线算法

    - **定义渲染函数**:在OpenGL的绘制回调函数中,我们执行扫描线填充算法。首先绘制多边形的边界,然后使用扫描线方法填充内部。 - **调用`glBegin`和`glEnd`**:在`glBegin(GL_POLYGON)`和`glEnd()`之间,我们可以...

    多边形扫描填充算法

    多边形扫描填充算法是计算机图形学中的一个重要概念,它主要用于在二维平面上对闭合多边形内部进行颜色填充。这种技术广泛应用于各种图形软件、游戏开发以及图像处理等领域。学号作为填充图案,可以理解为将学号的...

    种子填充算法,扫描线填充算法,带报告

    用种子填充算法和扫描线填充算法等任意两种算法实现指定多边形的区域填充。 实验步骤 1. 复习有关算法,明确实验目的和要求; 2. 依据算法思想,绘制程序流程图(指定填充多边形); 3. 设计程序界面,要求操作方便...

    多边形扫描线填充(有序边算法实现)

    在C++ MFC(Microsoft Foundation Classes)环境中,我们可以构建一个用户界面,允许用户通过鼠标绘制多边形,然后应用扫描线填充算法来填充它们。 MFC是Microsoft开发的一个C++类库,用于构建Windows应用程序。它...

    MFC扫描线区域填充算法

    《MFC扫描线区域填充算法详解》 在计算机图形学领域,区域填充是一种常见的任务,它涉及将指定区域内填充特定的颜色或图案。MFC(Microsoft Foundation Classes)是微软提供的一套C++类库,用于构建Windows应用程序...

    多边形填充的扫描线算法实现

    常见的扫描转换算法包括逐点判断算法、扫描线算法、边缘填充算法以及边界标志算法。其中,扫描线算法是最常用的多边形扫描转换算法,它按照扫描线的顺序计算多边形内每一条扫描线的相交区间,并填充相应颜色的像素。...

    扫描线多边形填充算法实现

    本篇将详细介绍如何在Microsoft Foundation Classes (MFC) 框架中实现扫描线填充算法,这是一种高效且广泛应用的多边形填充方法。 扫描线填充算法的基本思想是将屏幕水平地分为许多扫描线,并逐行处理。首先,我们...

    多边形扫描线填充算法 多边形扫描线填充源代码 未橡皮筋实现 类实现的算法 开源

    多边形扫描线填充算法是计算机图形学中的一个重要概念,主要应用于二维图形的填充,例如在计算机绘图软件中绘制复杂形状。这个算法的核心思想是将屏幕上的每一水平线(扫描线)作为处理对象,通过判断扫描线与多边形...

    多边形区域填充算法

    首先,我们要理解的是扫描线填充算法,这是最基本的填充方法之一。扫描线填充算法主要基于水平扫描线的概念,将屏幕按行分割,然后遍历每一条扫描线,寻找与扫描线相交的多边形边界。其中,有序边表法是一种实现扫描...

    多边形的扫描填充算法

    - 扫描线算法是填充多边形的核心方法之一,它通过逐行处理屏幕上的扫描线来填充多边形。首先,确定多边形的顶点,并按y坐标排序。对于每一扫描线,找到与扫描线相交的所有边,形成边的集合。然后根据边的相对位置...

    python实现扫描线填充算法,可以画凹多边形,采用matplotlib模块绘制图形

    python实现扫描线填充算法,使用matplotlib模块将绘制的图形保存并画出来,可以画凹多边形

    多边形区域的扫描线填充、扫描线种子填充算法实现

    本文将深入探讨两种常见的填充算法:扫描线填充算法和种子填充算法,并在MFC(Microsoft Foundation Classes)框架下实现它们。 首先,扫描线填充算法是一种基于垂直扫描线的填充方法。其基本思想是从图形的最高点...

    扫描线法填充多边形(完整C++代码)(QT工程)

    综上所述,这个项目涉及了计算机图形学的基础理论,如扫描线填充算法,以及实际编程技巧,如使用C++和QT库进行图形绘制。同时,它也提醒我们在编程实践中应注重代码的可读性、效率和注释的完善,这对于长期的项目...

    基于MFC实现多边形填充算法完整代码

    种子填充算法,也称为扫描线算法或Bresenham-like算法,是一种简单而有效的填充方法。它的基本思想是从一个或多个人工设定的种子点开始,通过判断当前像素是否与已填充区域相邻,逐渐扩展填充到整个多边形内部。该...

    OpenCV实现的区域填充扫描线算法

    本文将深入探讨如何利用OpenCV来实现基于扫描线的区域填充算法,并介绍相关的关键概念和技术。 首先,我们要理解什么是扫描线算法。扫描线算法是一种逐行处理图像的策略,常用于光栅图形的处理。它从图像的顶部开始...

    vc++多边形扫描线填充算法,opengl

    在计算机图形学中,多边形扫描线填充算法是一种常用的技术,用于在屏幕上绘制出具有颜色的多边形。在给定的标题“vc++多边形扫描线填充算法,opengl”和描述中,我们可以推断这是一个使用C++编程语言,结合OpenGL库...

    C++ 多边形边缘填充算法

    3. **Scan Line算法**:这种方法是基于扫描线的思想,从上到下或者从下到上遍历图像的每一行,检测每行与多边形边的交点,然后在这两个交点之间填充颜色。对于非凸多边形,可能需要更复杂的数据结构和逻辑来处理交点...

    java多边形填充扫描线种子算法

    使用java编程 多边形画法:先选择画图中的多边形,然后在面板里单击鼠标左键,画点...扫描线种子填充的算法适合于任意图形,不会出现部分区域填补上的现象。 程序没有任何问题~ 有不明白的可以联系我~ qq:815366795~

    基于EasyX库下的扫描线填充算法

    扫描线填充算法是一种在二维图形处理中常见的技术,主要用于图像的内部填充,它通过一系列的逻辑操作将指定区域内的像素点染成特定的颜色。在基于EasyX库的应用中,这种算法能够有效地帮助开发者实现图形的内部填充...

Global site tag (gtag.js) - Google Analytics