多邊形區域填充算法--掃描線種子填充算法

From http://blog.csdn.net/orbit/article/details/7343236算法

1.3掃描線種子填充算法函數

        1.1和1.2節介紹的兩種種子填充算法的優勢是很是簡單,缺點是使用了遞歸算法,這不但須要大量棧空間來存儲相鄰的點,並且效率不高。爲了減小算法中的遞歸調用,節省棧空間的使用,人們提出了不少改進算法,其中一種就是掃描線種子填充算法。掃描線種子填充算法再也不採用遞歸的方式處理「4-聯通」和「8-聯通」的相鄰點,而是經過沿水平掃描線填充像素段,一段一段地來處理「4-聯通」和「8-聯通」的相鄰點。這樣算法處理過程當中就只須要將每一個水平像素段的起始點位置壓入一個特殊的棧,而不須要象遞歸算法那樣將當前位置周圍還沒有處理的全部相鄰點都壓入堆棧,從而能夠節省堆棧空間。應該說,掃描線填充算法只是一種避免遞歸,提升效率的思想,前面提到的注入填充算法和邊界填充算法均可以改進成掃描線填充算法,下面介紹的就是結合了邊界填充算法的掃描線種子填充算法。學習

        掃描線種子填充算法的基本過程以下:當給定種子點(x, y)時,首先分別向左和向右兩個方向填充種子點所在掃描線上的位於給定區域的一個區段,同時記下這個區段的範圍[xLeft, xRight],而後肯定與這一區段相連通的上、下兩條掃描線上位於給定區域內的區段,並依次保存下來。反覆這個過程,直到填充結束。spa

掃描線種子填充算法可由下列四個步驟實現:.net

 

(1) 初始化一個空的棧用於存放種子點,將種子點(x, y)入棧;blog

 

(2) 判斷棧是否爲空,若是棧爲空則結束算法,不然取出棧頂元素做爲當前掃描線的種子點(x, y),y是當前的掃描線;遞歸

 

(3) 從種子點(x, y)出發,沿當前掃描線向左、右兩個方向填充,直到邊界。分別標記區段的左、右端點座標爲xLeft和xRight;ip

 

(4) 分別檢查與當前掃描線相鄰的y - 1和y + 1兩條掃描線在區間[xLeft, xRight]中的像素,從xLeft開始向xRight方向搜索,若存在非邊界且未填充的像素點,則找出這些相鄰的像素點中最右邊的一個,並將其做爲種子點壓入棧中,而後返回第(2)步;ci

 

這個算法中最關鍵的是第(4)步,就是從當前掃描線的上一條掃描線和下一條掃描線中尋找新的種子點。這裏比較難理解的一點就是爲何只是檢查新掃描線上區間[xLeft, xRight]中的像素?若是新掃描線的實際範圍比這個區間大(並且不連續)怎麼處理?我查了不少計算機圖形學的書籍和論文,好像都沒有對此作過特殊說明,這使得不少人在學習這門課程時對此有揮之不去的疑惑。本着「毀人」不倦的思想,本文就羅嗦解釋一下,但願能解除你們的疑惑。get

        若是新掃描線上實際點的區間比當前掃描線的[xLeft, xRight]區間大,並且是連續的狀況下,算法的第(3)步就處理了這種狀況。如圖(4)所示:

圖(4) 新掃描線區間增大且連續的狀況

假設當前處理的掃描線是黃色點所在的第7行,則通過第3步處理後能夠獲得一個區間[6,10]。而後第4步操做,從相鄰的第6行和第8行兩條掃描線的第6列開始向右搜索,肯定紅色的兩個點分別是第6行和第8行的種子點,因而按照順序將(6, 10)和(8, 10)兩個種子點入棧。接下來的循環會處理(8, 10)這個種子點,根據算法第3步說明,會從(8, 10)開始向左和向右填充,因爲中間沒有邊界點,所以填充會直到遇到邊界爲止,因此儘管第8行實際區域比第7行的區間[6,10]大,可是仍然獲得了正確的填充。

        若是新掃描線上實際點的區間比當前掃描線的[xLeft, xRight]區間大,並且中間有邊界點的狀況,算法又是怎麼處理呢?算法描述中雖然沒有明確對這種狀況的處理方法,可是第4步肯定上、下相鄰掃描線的種子點的方法,以及靠右取點的原則,實際上暗含了從相鄰掃描線繞過障礙點的方法。下面以圖(5)爲例說明:

圖(5) 新掃描線區間增大且不連續的狀況

算法第3步處理完第5行後,肯定了區間[7, 9],相鄰的第4行雖然實際範圍比區間[7, 9]大,可是由於被(4, 6)這個邊界點阻礙,使得在肯定種子點(4, 9)後向左填充只能填充右邊的第7列到第10列之間的區域,而左邊的第3列到第5列之間的區域沒有填充。雖然做爲第5行的相鄰行,第一次對第4行的掃描根據靠右原則只肯定了(4, 9)一個種子點。可是對第3行處理完後,第4行的左邊部分做爲第3行下邊的相鄰行,再次獲得掃描的機會。第3行的區間是[3, 9],向左跨過了第6列這個障礙點,第2次掃描第4行的時候就從第3列開始,向右找,能夠肯定種子點(4, 5)。這樣第4行就有了兩個種子點,就能夠被完整地填充了。

        因而可知,對於有障礙點的行,經過相鄰邊的關係,能夠跨越障礙點,經過屢次掃描獲得完整的填充,算法已經隱含了對這種狀況的處理。根據本節總結的四個步驟,掃描線種子填充算法的實現以下:

263 void ScanLineSeedFill(int x,int y, int new_color, int boundary_color)

264 {

265     std::stack<Point> stk;

266 

267     stk.push(Point(x, y));//第1步,種子點入站

268     while(!stk.empty())

269     {

270         Point seed = stk.top();//第2步,取當前種子點

271         stk.pop();

272 

273         //第3步,向左右填充

274         int count = FillLineRight(seed.x, seed.y, new_color, boundary_color);//向'cf?右'd3?填'cc?充'b3?

275         int xRight = seed.x+ count - 1;

276         count = FillLineLeft(seed.x- 1, seed.y, new_color, boundary_color);//向'cf?左'd7?填'cc?充'b3?

277         int xLeft = seed.x- count;

278 

279         //第4步,處理相鄰兩條掃描線

280         SearchLineNewSeed(stk, xLeft, xRight, seed.y- 1, new_color, boundary_color);

281         SearchLineNewSeed(stk, xLeft, xRight, seed.y+ 1, new_color, boundary_color);

282     }

283 }

FillLineRight()和FillLineLeft()兩個函數就是從種子點分別向右和向左填充顏色,直到遇到邊界點,同時返回填充的點的個數。這兩個函數返回填充點的個數是爲了正確調整當前種子點所在的掃描線的區間[xLeft, xRight]。SearchLineNewSeed()函數完成算法第4步所描述的操做,就是在新掃描線上尋找種子點,並將種子點入棧,新掃描線的區間是xLeft和xRight參數肯定的:

234 void SearchLineNewSeed(std::stack<Point>& stk, int xLeft,int xRight,

235                       int y, int new_color, int boundary_color)

236 {

237     int xt = xLeft;

238     bool findNewSeed = false;

239 

240     while(xt <= xRight)

241     {

242         findNewSeed = false;

243         while(IsPixelValid(xt, y, new_color, boundary_color)&& (xt < xRight))

244         {

245             findNewSeed= true;

246             xt++;

247         }

248         if(findNewSeed)

249         {

250             if(IsPixelValid(xt, y, new_color, boundary_color)&& (xt == xRight))

251                 stk.push(Point(xt, y));

252             else

253                 stk.push(Point(xt- 1, y));

254         }

255 

256         /*向右跳過內部的無效點(處理區間右端有障礙點的狀況)*/

257         int xspan = SkipInvalidInLine(xt, y, xRight, new_color, boundary_color);

258         xt += (xspan ==0) ? 1 : xspan;

259         /*處理特殊狀況,以退出while(x<=xright)循環*/

260     }

261 }

 

最外層的while循環是爲了保證區間[xLeft, xRight]右端被障礙點分隔成多段的狀況可以獲得正確處理,經過外層while循環,能夠確保爲每一段都找到一個種子點(對於障礙點在區間左端的狀況,請參考圖(5)所示實例的解釋,是隱含在算法中完成的)。內層的while循環只是爲了找到每一段最右端的一個可填充點做爲種子點。SkipInvalidInLine()函數的做用就是跳過區間內的障礙點,肯定下一個分隔段的開始位置。循環內的最後一行代碼有點奇怪,其實只是用了一個小「詭計」,確保在遇到真正的邊界點時循環可以正確退出。這不是一個值得稱道的作法,實現此類軟件控制有更好的方法,本文這樣作的目的只是爲了使代碼簡短一些,讓讀者把注意力集中在算法處理邏輯上,而不是冗雜難懂的循環控制條件上。

        算法的實現其實就在ScanLineSeedFill()和SearchLineNewSeed()兩個函數中,神祕的掃描線種子填充算法也並不複雜,對吧?至此,種子填充算法的幾種常見算法都已經介紹完畢,接下來將介紹兩種適合矢量圖形區域填充的填充算法,分別是掃描線算法和邊標誌填充算法,注意適合矢量圖形的掃描線填充算法有時又被稱爲「有序邊表法」,和掃描線種子填充算法是有區別的。

相關文章
相關標籤/搜索