种子填充算法c语言代码实现,OpenGL绘图实例三之种子填充算法
綜述
博主研究了一下午加一晚上,終于把種子填充算法實現出來并把機器人填充完畢,路途很艱辛,不過也學到了很多,在此和大家一起分享。
吐槽
與我不是同學的小伙伴,請自動忽略,我是來吐槽教材的。 在此不得不吐槽一下,不得不說教材實在太坑爹了。對于種子填充算法的后半部分,下一個種子點的尋找過程中,從while(x<=xright)開始,我實在無法搞懂它里面的神邏輯,最初我認為它是對的,后來按照它的思路實現之后,填充基本上是錯誤的,比如圓角矩形下方的部分,它就無法正常填充。根本原因還是它的下一步種子點找錯了,而博主依然在固執地DeBug,看看是不是我哪里編碼有問題。后來,干脆放棄了書上的邏輯了,自己改寫了搜尋下一個種子點的算法,最后終于成功。 另外,教材上的這些偽代碼寫得也是太偽,算了,這不是重點,言歸正傳。
基本梳理
在博主的研究過程中,遇到了許許多多的小問題,在這里統一做一下總結,也希望大家少走彎路,吸取我的經驗教訓。
1.點的定義
在這里我們避免不了要使用點,一個點包括了2個元素,一個是橫坐標一個是縱坐標,所以我們可以直接把它定義為一個結構體。
1
2
3
4
5
struct Point
{
int x;
int y;
};
這樣的話,我們就可以直接聲明一個 Point 類型的變量使用了,既方便又直觀。
2.棧的使用
對于種子填充算法,肯定避免不了使用棧的,在這里博主分享一下一些使用心得。 棧的引入 C++代碼中,可以直接用下面的代碼來導入
1
2
#include
using namespace std;
注意,這里一定記得加上 using namespace std 這句話,否則會出現 stack 未定義的錯誤,哈哈哈,深有體會。 棧的定義 引入了棧之后,我們就可以直接來聲明一個棧了
1
stack pixelStack;
其中,需要加一個尖括號,尖括號中聲明了 Point 類型,這樣我們就可以使用它了 取棧頂元素 C++中取棧頂元素是很坑的,有一個top方法,還有一個pop方法。 其中top方法是只取得棧頂的元素而不移除它,pop方法是直接移除棧頂元素,沒有返回值。 所以我們要想取出棧頂元素并移除的話,就要分別調用這兩個方法
1
2
3
4
//獲取最頂端的元素
Point tempPoint=pixelStack.top();
//刪除最頂端的元素
pixelStack.pop();
是不是不友好?不友好的話,那就自己去定義一個新方法吧,我就先不這么干啦。 判斷棧非空 判斷當前的棧是否已經為空,只需要調用empty方法就可以了
1
2
3
while(!pixelStack.empty()){
//code
}
這里是一個while循環,如果棧為非空的話不斷循環。 關于棧置空 教材中的種子填充算法中用到了棧置空,不過我感覺沒有必要這么做,因為在方法最前面是新聲明的棧變量,它一定是空的。不過如果非要置空的話,可以利用下面的代碼
1
2
3
while(!pixelStack.empty()){
pixelStack.pop();
}
如果棧不為空,就一直取元素,就可以把它置空啦。
3.關于glColor3b和glColor3ub
這的確也是坑得博主不淺,之前一直在用 glColor3b 這個方法來定義顏色,奇怪的是 glColor3b(255,0,0) 竟然不是紅色,而是黑色!就是因為這個顏色問題,導致我在比對顏色的過程中走了很多彎路。在這里做一下說明 glColor3b()需要傳入的是byte類型,它的數值范圍是-128-127,也就是有符號數,我傳入255,由于越界了,255這個數就相當于-128,難怪不變紅啊。 glColor3ub()需要傳入的是unsigned byte類型,范圍是0-255,無符號數,那么在這里我們傳入255,0,0這三個數,就變成紅色了。
4.取得某像素顏色
我想說的是,這也是個深坑啊,一下午的Debug全歸它身上了。 獲取某個像素的這個函數是
1
void glReadPixels(GLint x,GLint y,GLsizesi width,GLsizei height,GLenum format,GLenum type,GLvoid *pixel);
函數說明如下:
該函數總共有七個參數。前四個參數可以得到一個矩形,該矩形所包括的像素都會被讀取出來。(第一、二個參數表示了矩形的左下角橫、縱坐標,坐標以窗口最左下角為零,最右上角為最大值;第三、四個參數表示了矩形的寬度和高度) 第五個參數表示讀取的內容,例如:GL_RGB就會依次讀取像素的紅、綠、藍三種數據,GL_RGBA則會依次讀取像素的紅、綠、藍、alpha四種數據,GL_RED則只讀取像素的紅色數據(類似的還有GL_GREEN,GL_BLUE,以及GL_ALPHA)。如果采用的不是RGBA顏色模式,而是采用顏色索引模式,則也可以使用GL_COLOR_INDEX來讀取像素的顏色索引。目前僅需要知道這些,但實際上還可以讀取其它內容,例如深度緩沖區的深度數據等。 第六個參數表示讀取的內容保存到內存時所使用的格式,例如:GL_UNSIGNED_BYTE會把各種數據保存為GLubyte,GL_FLOAT會把各種數據保存為GLfloat等。 第七個參數表示一個指針,像素數據被讀取后,將被保存到這個指針所表示的地址。注意,需要保證該地址有足夠的可以使用的空間,以容納讀取的像素數據。例如一幅大小為256*256的圖象,如果讀取其RGB數據,且每一數據被保存為GLubyte。
好了,那么重點來了,這個方法的坐標基準點是在畫布的左下角!!而我們繪圖的基準點是在畫布的正中心!!所以我在獲取某個點的顏色的時候一直都是錯誤的結果,這樣的話在使用的時候我們的xy坐標值就要加上畫布寬高的一半才能正常獲取到像素的顏色,希望大家一定注意!! 那么我們如何來使用呢?實例如下,首先定義GLByte的數組
1
GLubyte iPixel[3];
另外還有畫布的寬度高度的一半變量
1
int halfWidth,halfHeight;
我們可以調用如下的方法來獲取(x,y)這個點像素的值
1
glReadPixels(x+halfWidth,y+halfHeight,1,1,GL_RGB,GL_UNSIGNED_BYTE,&iPixel);
在這里第五個參數我們定義了 GL_RGB,第六個參數我們定義了 GL_UNSIGNED_BYTE,最后是傳入了數組的引用。 所以在調用這個方法之后,iPixel數組里面的三個值就已經賦值為了該點的RGB值,可以拿來做下一步的判斷。 比如我們邊界可以定義為
1
GLubyte oldColor[3]={255,255,255};
在比較的時候就可以用下面的判別式
1
iPixel[0]!=borderColor[0]&&iPixel[1]!=borderColor[1]&&iPixel[2]!=borderColor[2]
這里我們是依次比較了三個RGB值是否與邊界的RGB值相等,不過,有意思的是,識別顏色的這個方法,黑色的RGB值會識別成1,1,1,而有時候在我調試的時候會識別為0,1,1。我在想是不是系統計算誤差問題,如果真是的話,因為這個小小的誤差就影響了我們的判別條件豈不是虧大了?那么在這里我就定義了一個方法,允許一定的誤差,這個誤差姑且就稱為PS里面的容差吧。
1
2
3
4
5
6
7
8
9
10
//傳入兩個顏色的RGB值,比較是否相同,容差為dis
bool sameColor(int r1,int g1,int b1,int r2,int g2,int b2){
//容差度
int dis = 10;
if(abs(r1-r2)<=dis&&abs(g1-g2)<=dis&&abs(b1-b2)<=dis){
return true;
}else{
return false;
}
}
那么我們的判定條件就改為了
1
!sameColor(iPixel[0],iPixel[1],iPixel[2],borderColor[0],borderColor[1],borderColor[2])
這樣系統誤差便不會影響了。
5.下一個種子點的選取
教材上的種子點選取算法有點搞不懂,我按照上面的思路實現出來,在填充的時候出現了一系列問題。后來干脆放棄了教材中的方法,自己改寫了一下。 思路大體上是這樣的。
在填充完一行后,這一行最左邊的像素點我們定義為(xLeft,y),最右邊的像素我們定義為(xRight,y),掃描上一行找尋下一個種子點,這里y就要增加1,如果(xRight,y+1)這個點不是邊界不是已經填充的點,那么這個點就可以作為種子點壓入堆棧。如果這個點是邊界或者是已經填充的點,那么就繼續往左搜索,如果找到既不是邊界又未填充的點,那么這個點就是種子點,壓入堆棧。如果一直往左找到xLeft還是沒有找到的話,就不存在下一個種子點了。下一行掃描線也是同樣的原理,y要在這個基礎上減去2即可。
恩,不知道大家有沒有看懂,這是我自己想出來的方法,不敢保證完全正確,在此僅供參考。 如果大家真的可以按照教材中的方法實現成功的話,希望告訴我一下,感激不盡。
方法實現
恩,重要的地方都已經點明了,下面就直接附上我的種子填充算法吧!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
//種子填充算法
void zzFill(int startX,int startY,int r,int g,int b){
stack pixelStack;
//x,y是給定的種子像素點,rgb就是要填充的顏色的RGB值
Point point = {startX,startY};
pixelStack.push(point);
int saveX;
int xRight,xLeft;
int x,y;
//如果棧不為空
while(!pixelStack.empty()){
//獲取最頂端的元素
Point tempPoint=pixelStack.top();
//刪除最頂端的元素
pixelStack.pop();
saveX=tempPoint.x;
x=tempPoint.x;
y=tempPoint.y;
glReadPixels(x+halfWidth,y+halfHeight,1,1,GL_RGB,GL_UNSIGNED_BYTE,&iPixel);
//如果沒有到達右邊界,就填充
while(!sameColor(iPixel[0],iPixel[1],iPixel[2],borderColor[0],borderColor[1],borderColor[2])){
glPoint(x,y,r,g,b);
x=x+1;
glReadPixels(x+halfWidth,y+halfHeight,1,1,GL_RGB,GL_UNSIGNED_BYTE,&iPixel);
}
xRight=x-1;
x=saveX-1;
glReadPixels(x+halfWidth,y+halfWidth,1,1,GL_RGB,GL_UNSIGNED_BYTE,&iPixel);
//如果沒有到達左邊界,就填充
while(!sameColor(iPixel[0],iPixel[1],iPixel[2],borderColor[0],borderColor[1],borderColor[2])){
glPoint(x,y,r,g,b);
x=x-1;
glReadPixels(x+halfWidth,y+halfWidth,1,1,GL_RGB,GL_UNSIGNED_BYTE,&iPixel);
}
//保存左端點
xLeft=x+1;
//從右邊的點開始
x=xRight;
//檢查上端的掃描線
y=y+1;
while(x>=xLeft){
glReadPixels(x+halfWidth,y+halfWidth,1,1,GL_RGB,GL_UNSIGNED_BYTE,&iPixel);
if(!sameColor(iPixel[0],iPixel[1],iPixel[2],borderColor[0],borderColor[1],borderColor[2])&&!sameColor(iPixel[0],iPixel[1],iPixel[2],r,g,b)){
//如果上方的點不是邊界點,直接壓入
Point p={x,y};
pixelStack.push(p);
//壓入之后停止循環
break;
}else{
x--;
glReadPixels(x+halfWidth,y+halfWidth,1,1,GL_RGB,GL_UNSIGNED_BYTE,&iPixel);
}
}
//檢查下端的掃描線
y=y-2;
//從右邊的點開始
x=xRight;
while(x>=xLeft){
glReadPixels(x+halfWidth,y+halfWidth,1,1,GL_RGB,GL_UNSIGNED_BYTE,&iPixel);
if(!sameColor(iPixel[0],iPixel[1],iPixel[2],borderColor[0],borderColor[1],borderColor[2])&&!sameColor(iPixel[0],iPixel[1],iPixel[2],r,g,b)){
//如果上方的點不是邊界點,直接壓入
Point p={x,y};
//壓入之后停止循環
pixelStack.push(p);
break;
}else{
x--;
glReadPixels(x+halfWidth,y+halfWidth,1,1,GL_RGB,GL_UNSIGNED_BYTE,&iPixel);
}
}
}
}
以上便是我實現的種子填充算法,僅供參考 在這里我們用到了glPoint畫點的方法,這是我們定義的,方法如下,為了便于調試,每畫一個點刷新一下,這樣我們就可以看到繪制的全部動態效果。
1
2
3
4
5
6
7
8
9
//畫點
void glPoint(int x,int y,int r,int g,int b){
glColor3ub (r,g,b);
glPointSize(1);
glBegin(GL_POINTS);
glVertex2i(x,y);
glEnd();
glFlush();
}
以上便是畫點的函數
方法使用
種子填充算法肯定要在我們繪制完機器人之后使用,任意選取某個四連通區域的點,傳入xy坐標值還有要填充的顏色的RGB值,就可以成功實現填充。 在上一篇機器人的基礎上,我們在畫機器人的方法最后加入下面的代碼,即可實現填充。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//灰色:195,195,195
//黃色:255,243,0
//紅色:237,28,36
//深灰色:126,126,126
//脖子
zzFill(0,70,195,195,195);
//頭
zzFill(-50,110,195,195,195);
zzFill(0,93,195,195,195);
//肚子
zzFill(-50,0,195,195,195);
//耳朵
zzFill(-80,115,126,126,126);
zzFill(80,115,126,126,126);
//肚子三角
zzFill(-20,-10,255,243,0);
//肚子紅色圓
zzFill(0,0,237,28,36);
//zzFill(-50,0,128,255,33);
//大臂
zzFill(-90,30,126,126,126);
zzFill(90,30,126,126,126);
//小臂
zzFill(-90,-20,126,126,126);
zzFill(90,-20,126,126,126);
//手
zzFill(-75,40,195,195,195);
zzFill(75,40,195,195,195);
//手
zzFill(-95,-47,195,195,195);
zzFill(95,-47,195,195,195);
//大腿連接處
zzFill(-40,-64,195,195,195);
zzFill(40,-64,195,195,195);
//大腿
zzFill(-40,-100,126,126,126);
zzFill(40,-100,126,126,126);
//腳踝
zzFill(-40,-121,195,195,195);
zzFill(40,-121,195,195,195);
//腳掌
zzFill(-40,-130,126,126,126);
zzFill(40,-130,126,126,126);
system("pause");
注意,有個很奇怪地方是繪制完了之后機器人就不見了,所以在這里加入了system(“pause”)方法來暫停一下就好啦。 其他的代碼基本都是上一篇中的了,大家自行整理。
運行結果
運行結果截圖如下 恩,就是這樣!
總結
以上便是博主利用種子填充算法來實現的機器人的顏色填充,在此分享給大家,希望對大家有幫助! 如有問題和錯誤,歡迎大家給予我批評和指正,謝謝!
總結
以上是生活随笔為你收集整理的种子填充算法c语言代码实现,OpenGL绘图实例三之种子填充算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php跨域有那些方法,PHP跨域访问的3
- 下一篇: 聚观早报 | 华为官宣新机Pocket