圖形學學習筆記3——區域填充
區域填充
概念
連通方式:
- 四連通:兩個畫素點上下相連或左右相連。
- 八連通:兩個畫素點上下或左右或對角相連。
區域定義方式:
- 內部定義(interior-defined): 區域內部所有畫素點單一值,邊界可以不是單一值。
- 邊界定義(boundary-defined): 邊界是單一值,內部所有是區域。
(a)和(b)是內部定義四連通,(c)是內部定義八連通,(d)既不是四連通也不是八連通,內部區域是四連通。
性質:
- 四連通區域的邊界必定是八連通
- 八連通區域的邊界必定是四連通
void flood-fill-8(x,y,old_value, new_value)
{
if( get_pix_value(x,y) == old_value)
{
set_pix_value(x,y,new_value);
flood-fill-8(x-1,y-1,old_value,new_value);
flood-fill-8(x-1,y,old_value,new_value);
flood-fill-8(x-1,y+1,old_value,new_value);
flood-fill-8(x,y-1,old_value,new_value);
flood-fill-8(x,y+1 ,old_value,new_value);
flood-fill-8(x+1,y-1,old_value,new_value);
flood-fill-8(x+1,y,old_value,new_value);
flood-fill-8(x+1,y+1,old_value,new_value);
}
}
注入填充演算法多次重複訪問畫素點,效率並不高。
邊界填充演算法
填充被邊界包圍的區域。填充一個八連通邊界包圍的區域:
void boundary-fill-8(x,y,boundary_value, new_value)
{
if( get_pix_value(x,y) != boundary_value)
{
set_pix_value(x,y,new_value);
boundary-fill-8(x-1,y,boundary_value,new_value);
boundary-fill-8(x,y-1,boundary_value,new_value);
boundary-fill-8(x,y+1,boundary_value,new_value);
boundary-fill-8(x+1,y,boundary_value,new_value);
}
}
內部訪問路徑按照四連通設計。
- 注入填充和邊界填充都是深度遞迴,耗記憶體。
掃描線演算法
該演算法只適用於四連通區域。
思路:一行一行的填充,將填充行的端點記錄,填充完該行,尋找上下行各自的待填充線段的端點,如此迴圈。
- 找區域內一行的右端點入棧。(一行可能有多個右端點)
- 取出棧內一點作為種子,按畫素向左填充直到邊界
- 在填充範圍的上一行和下一行尋找尚未被填充的右端點入棧。(得在
xleft 和xright 分別向左和右尋找直到邊界) - 若棧非空,重複2.
以上步驟中“左”“右”互換亦可。
該演算法需要不斷判斷是否在區域內,是否已經被填充,在尋找端點的時候進行了判斷,在填充的時候重複判斷,此處可以進行優化,因此就有了壓入區段掃描線演算法。
壓入區段掃描線演算法
普通掃描線將端點和行數壓棧,該改進的演算法將填充線段兩個端點和行數一起壓棧。
- 任意找區域內一點,分別向左和向右填充直到邊界,將左右端點和行數壓棧。
- 出棧一段線段,分別上一行和下一行搜尋
[xleft,xright] (可以超過左右邊界)。 - 搜尋未填充的畫素進行填充並將新得到的左右端點併入棧。
- 棧空結束,否則重複2.
該演算法本質是普通的掃描線演算法中將填充步驟在尋找端點時一併進行。
多邊形掃描轉化演算法
基本思想:任意一條水平掃描線穿過多邊形產生n個交點,每經過一個交點,要麼從區域內進入區域外,要麼從區域外進入區域內。即穿過奇數個交點進入區域內,經過偶數個交點進入區域外。將區域內線段進行填充。
- 注意:若交點正好是極值處的端點時,這樣的特殊交點屬於兩個線段,要麼一起捨棄,要麼當成兩個交點。若線段水平,則不考慮掃描線與其的交點。
從
- 新增交點,y剛好超過某線段
ymin 。 - 減少交點,y越過某線段
ymax 。 - 焦點x變化
1/k(k為所屬線段斜率) .
具體實現時,對每條掃描線建立一張活動表AET(active edge table),這是一個動態表。其中資料包含
- 相交邊的上端點y的值(y的最大值)。
- 交點橫座標 x
- 線段斜率倒數 1/k
表裡按照x排序。
為了增加AET更新效率,在建立一個邊表(edge table),該表示靜態表,程式初始化時建立。
邊表用桶式排序,為每一個y(掃描線)建立一個連結串列,每個線段下端點y對應的連結串列裡存該線段的資訊。
記錄的資料為:
- 上端點y的值
- 下端點x的值
- 線段斜率倒數 1/k
ET和AET資料結構相同,只在“x”記錄的資料不同,AET記錄的是計算出來的每次交點x,ET記錄的是下端點x。
掃描步驟:
- 在ET中找到最小
yi 。(第一個非空的桶) - 置空AET。
- 取出在ET中y對應掃描線連結串列,合併連結串列中的資料到AET,根據這些點填充
yi 行。 - 刪除資料中上端點y的值等於
yi 的資料項。 - 迭代一次AET中的x值,即加一個1/k。
yi 增加1,掃描下一行。重複步驟3.
在上面的步驟中,AET中的資料項每次都要步驟4中和當前y進行判斷,而如果ET中將上端點和下端點都記錄,遇到下端點時進入AET,遇到上端點時刪除ET中對應的下端點,應該還能提升效率。
該系列學習筆記主要參考 鄭州師範大學 柳朝陽的《計算機圖形學的概念與方法》,如需要查閱更詳細的公式推導,可參考原著。