4、境界マーク塗りつぶしアルゴリズム
ラスター表示面では、ポリゴンは閉じています。これは、特定の境界線の色で囲まれた閉じた領域です。塗りつぶしは行ごとに行われます。つまり、スキャンラインを使用して、ポリゴンを行ごとに交差させ、交差のペアの間を塗りつぶします。境界マーク塗りつぶしアルゴリズムは、ライン処理を塗りつぶすマークとして境界または境界色を使用します。正確には、境界マーク塗りつぶしアルゴリズムは特定の塗りつぶしアルゴリズムを指すのではなく、スキャンラインの連続性の概念を使用するタイプの塗りつぶしアルゴリズムの一般的な用語です。そのようなアルゴリズムには多くの種類があり、この記事ではいくつか紹介します。
まず、エッジ中心のエッジ塗りつぶしアルゴリズムを紹介します。この境界マーキングアルゴリズムの基本的な考え方は、次のとおりです。各スキャンラインと各ポリゴンサイドの交点(xi、yi)について、スキャンラインの交点の右側の交点すべてのピクセルが補完され、最後の塗りつぶしが完了するまで、ポリゴンの各辺が順番に処理されます。ここで、補完された定義を導入し、点Mの色を仮定し、結果として得られる色点は補完されますM '= A-M、Aは非常に大きな数値で、少なくともすべての有効なすべての色値よりも優れています。補数の定義によれば、Mとしてマークされたラスタービットマップの特定の領域の色値に対して偶数の補数演算が実行されると、領域の色は変更されず、奇数の補数演算が実行される間、領域の色はMになります'色。アルゴリズムは、2つのステップとして簡単に説明できます。
1、描画ウィンドウの背景色はM '色に設定されます。
2.多角形の非水平辺ごとに、辺の各ピクセルから右側の余りを計算します。
アルゴリズムの処理プロセスを図(12)に示します。左側はポリゴンの形状で、右側は各エッジを処理した後の塗りつぶされた領域の色です。初期の背景色はM 'です。処理後、塗りつぶす必要のある領域は奇数の補数の後、最終的な色は、塗りつぶされる正しい値Mであり、塗りつぶされていない領域は偶数回補われ、それでも背景色M 'です。
図(12)エッジ充填アルゴリズムの処理プロセス
アルゴリズムは、非常に単純である表示ラスタビットマップのため、我々はまだ、以前に使用した方法を使用して、デジタル・マトリクスを使用して、ラスタビットマップの領域を示し、行列の各位置で、ピクセルを表す0 - 9カラー値を表します。この例示的なアルゴリズム9は最大値Aを示し、0は無効な領域を示し、カラー値は有効です。1 - 。8。
87 void EdgeCenterMarkFill (const Polygon & py 、 int color ) 88 { 89 std ::ベクトル< EDGE3 > et ; 90 91 InitScanLineEdgesTable ( et 、 py ); //エッジテーブルを初期化する 92 93 FillBackground ( A - color ); //塗りつぶされた領域全体の背景色を補う 94 for_each (ら。開始)(ら。エンド()、 EdgeScanMarkColor ); //は、順番に各エッジを処理 95 } |
76 void EdgeScanMarkColor ( EDGE3 & e ) 77 { 78 のための(int型、Y = E 。 YMAX ; Y > = E 。 YMIN ; Y - ) 79 { 80 INT X = ROUND_INT ( E 。 XI )。 81 ComplementScanLineColor ( x 、 MAX_X_CORD 、 y ); 82 83 e 。xi- = e 。dx ; 84 } 85 } |
这个算法非常简单,所有的函数实现也很简单,InitScanLineEdgesTable()函数前面已经介绍过,FillBackground()函数将填充背景初始化为要填充颜色的取补颜色,EdgeScanMarkColor()函数负责对每条非水平边进行处理,逐条扫描线进行颜色取补,ComplementScanLineColor()函数负责对y扫描线上[x1, x2]区间的点的颜色值取补。
以上以边为中心的填充算法的优点是简单,缺点是对于复杂多边形,每一象素可能被访问多次(多次取补),效率不高。考虑对此算法改进,人们提出了栅栏填充算法。栅栏填充算法的基本思想是:经过多边形的某个顶点,在多边形内部建立一个与扫描线垂直的“栅栏”,当扫描线与多边形边有交点,就将交点与栅栏之间的象素取补。若交点位于栅栏左边,则将交点之右,栅栏之左的所有象素取补;若交点位于栅栏右边,则将栅栏之右,交点之左的象素取补。
仍以图(12)所示的多边形为例,假设经过P4点建立一条栅栏,则改进的栅栏填充算法处理过程就如图(13)所示:
图(13)栅栏填充算法的处理过程
栅栏填充算法的实现和以边为中心的边缘填充算法类似,只是对每条边的扫描线取补处理的范围控制有区别,算法需要指定一个“栅栏”:
115 void EdgeFenceMarkFill(const Polygon& py, int fence, int color) 116 { 117 std::vector<EDGE3> et; 118 119 InitScanLineEdgesTable(et, py);//初始化边表 120 121 FillBackground(A - color); //对整个填充区域背景颜色取补 122 for_each(et.begin(), et.end(), 123 std::bind2nd(std::ptr_fun(FenceScanMarkColor), fence));//依次处理每一条边 124 } |
97 void FenceScanMarkColor(EDGE3 e, int fence) 98 { 99 for(int y = e.ymax; y >= e.ymin; y--) 100 { 101 int x = ROUND_INT(e.xi); 102 if(x > fence) 103 { 104 ComplementScanLineColor(fence, x, y); 105 } 106 else 107 { 108 ComplementScanLineColor(x, fence - 1, y); 109 } 110 111 e.xi -= e.dx; 112 } 113 } |
注意FenceScanMarkColor()函数和EdgeScanMarkColor()函数的区别,就是这点区别使得栅栏填充算法主动减少了很多像素被访问的次数,而多边形之外的像素也不会被多余处理,效率提高了不少。
栅栏填充算法只是减少了被重复访问的象素的数目,但仍有一些象素会被重复访问。从图(13)中很容易看出这一点。下面介绍的边标志算法利用扫描线的连贯性,对每个像素只访问一次即可完成多边形区域的填充。边标志算法的思想是设置一个标志,沿着扫描线从左到右访问多边形所在区域的像素的时候,用这个标志标识是否扫描到了多边形的边界。如果碰到边界(边界用特殊的颜色标识),则改变标识状态,接下来根据标识状态决定扫描到的像素点是否要填充成指定颜色。
在实施边标志填充算法通常先求出多边形所在图像区域的最小包围盒,以便缩小扫描范围,加快填充速度。完整的填充算法如下:
5 void EdgeMarkFill(int xmin, int xmax, int ymin, int ymax, 6 int boundarycolor, int color) 7 { 8 int flag = -1; 9 for(int y = ymin; y <= ymax; y++) 10 { 11 flag = -1; 12 for(int x = xmin; x <= xmax; x++) 13 { 14 if(GetPixelColor(x, y) == boundarycolor) 15 { 16 flag = -flag; 17 } 18 if(flag == 1) 19 { 20 SetPixelColor(x, y, color); 21 } 22 } 23 } 24 } |
可以看到该算法思想简单,实现容易。算法既不需要初始化边表、求交点、交点排序等额外操作,也不需要使用链表、堆栈等数据结构。不过,边标志算法虽然简单,但是使用时对边界的处理要格外小心,否则很容易出错。比如上下顶点之类的局部极值点,会造成标志的奇偶计数不匹配,填充时会出现“抽丝”现象,也就是扫描线上不该填充的区段被填上色,而应该填充的区段却没有填上色。再比如边界像素点重复的问题,多边形的边界是利用直线的生成算法依次画出的,当扫描线遇到斜率小于1的边时,容易产生重复的边界点,如图(14)所示,引起标志的无序改变,从而造成填充错误。
图(14)斜率小于1的边的光栅扫描转换问题
至此,本文完整介绍了8中常见的多边形区域填充算法,拖拖沓沓写了一个多月,总算完成了,居然有将近30页,总计13000多字(24000多可见字符),毕业以后就没写过这么长的文档了,感慨一下。本文给出的示例代码都是为了演示,基于可读性而设计的,大家在实际的图形处理软件中看到的算法实现可能和本文的示例算法“大相径庭”,但是基本原理都是一样的。
参考资料:
【1】计算几何:算法设计与分析 周培德 清华大学出版社 2005年
【2】计算几何:算法与应用 德贝尔赫(邓俊辉译) 清华大学出版社 2005年
【3】计算机图形学 孙家广、杨常贵 清华大学出版社 1995年