种子填充算法c语言代码实现,OpenGL绘图实例三之种子填充算法
綜述
博主研究了一下午加一晚上,終于把種子填充算法實(shí)現(xiàn)出來并把機(jī)器人填充完畢,路途很艱辛,不過也學(xué)到了很多,在此和大家一起分享。
吐槽
與我不是同學(xué)的小伙伴,請自動忽略,我是來吐槽教材的。 在此不得不吐槽一下,不得不說教材實(shí)在太坑爹了。對于種子填充算法的后半部分,下一個種子點(diǎn)的尋找過程中,從while(x<=xright)開始,我實(shí)在無法搞懂它里面的神邏輯,最初我認(rèn)為它是對的,后來按照它的思路實(shí)現(xiàn)之后,填充基本上是錯誤的,比如圓角矩形下方的部分,它就無法正常填充。根本原因還是它的下一步種子點(diǎn)找錯了,而博主依然在固執(zhí)地DeBug,看看是不是我哪里編碼有問題。后來,干脆放棄了書上的邏輯了,自己改寫了搜尋下一個種子點(diǎn)的算法,最后終于成功。 另外,教材上的這些偽代碼寫得也是太偽,算了,這不是重點(diǎn),言歸正傳。
基本梳理
在博主的研究過程中,遇到了許許多多的小問題,在這里統(tǒng)一做一下總結(jié),也希望大家少走彎路,吸取我的經(jīng)驗(yàn)教訓(xùn)。
1.點(diǎn)的定義
在這里我們避免不了要使用點(diǎn),一個點(diǎn)包括了2個元素,一個是橫坐標(biāo)一個是縱坐標(biāo),所以我們可以直接把它定義為一個結(jié)構(gòu)體。
1
2
3
4
5
struct Point
{
int x;
int y;
};
這樣的話,我們就可以直接聲明一個 Point 類型的變量使用了,既方便又直觀。
2.棧的使用
對于種子填充算法,肯定避免不了使用棧的,在這里博主分享一下一些使用心得。 棧的引入 C++代碼中,可以直接用下面的代碼來導(dǎo)入
1
2
#include
using namespace std;
注意,這里一定記得加上 using namespace std 這句話,否則會出現(xiàn) stack 未定義的錯誤,哈哈哈,深有體會。 棧的定義 引入了棧之后,我們就可以直接來聲明一個棧了
1
stack pixelStack;
其中,需要加一個尖括號,尖括號中聲明了 Point 類型,這樣我們就可以使用它了 取棧頂元素 C++中取棧頂元素是很坑的,有一個top方法,還有一個pop方法。 其中top方法是只取得棧頂?shù)脑囟灰瞥?#xff0c;pop方法是直接移除棧頂元素,沒有返回值。 所以我們要想取出棧頂元素并移除的話,就要分別調(diào)用這兩個方法
1
2
3
4
//獲取最頂端的元素
Point tempPoint=pixelStack.top();
//刪除最頂端的元素
pixelStack.pop();
是不是不友好?不友好的話,那就自己去定義一個新方法吧,我就先不這么干啦。 判斷棧非空 判斷當(dāng)前的棧是否已經(jīng)為空,只需要調(diào)用empty方法就可以了
1
2
3
while(!pixelStack.empty()){
//code
}
這里是一個while循環(huán),如果棧為非空的話不斷循環(huán)。 關(guān)于棧置空 教材中的種子填充算法中用到了棧置空,不過我感覺沒有必要這么做,因?yàn)樵诜椒ㄗ钋懊媸切侣暶鞯臈W兞?#xff0c;它一定是空的。不過如果非要置空的話,可以利用下面的代碼
1
2
3
while(!pixelStack.empty()){
pixelStack.pop();
}
如果棧不為空,就一直取元素,就可以把它置空啦。
3.關(guān)于glColor3b和glColor3ub
這的確也是坑得博主不淺,之前一直在用 glColor3b 這個方法來定義顏色,奇怪的是 glColor3b(255,0,0) 竟然不是紅色,而是黑色!就是因?yàn)檫@個顏色問題,導(dǎo)致我在比對顏色的過程中走了很多彎路。在這里做一下說明 glColor3b()需要傳入的是byte類型,它的數(shù)值范圍是-128-127,也就是有符號數(shù),我傳入255,由于越界了,255這個數(shù)就相當(dāng)于-128,難怪不變紅啊。 glColor3ub()需要傳入的是unsigned byte類型,范圍是0-255,無符號數(shù),那么在這里我們傳入255,0,0這三個數(shù),就變成紅色了。
4.取得某像素顏色
我想說的是,這也是個深坑啊,一下午的Debug全歸它身上了。 獲取某個像素的這個函數(shù)是
1
void glReadPixels(GLint x,GLint y,GLsizesi width,GLsizei height,GLenum format,GLenum type,GLvoid *pixel);
函數(shù)說明如下:
該函數(shù)總共有七個參數(shù)。前四個參數(shù)可以得到一個矩形,該矩形所包括的像素都會被讀取出來。(第一、二個參數(shù)表示了矩形的左下角橫、縱坐標(biāo),坐標(biāo)以窗口最左下角為零,最右上角為最大值;第三、四個參數(shù)表示了矩形的寬度和高度) 第五個參數(shù)表示讀取的內(nèi)容,例如:GL_RGB就會依次讀取像素的紅、綠、藍(lán)三種數(shù)據(jù),GL_RGBA則會依次讀取像素的紅、綠、藍(lán)、alpha四種數(shù)據(jù),GL_RED則只讀取像素的紅色數(shù)據(jù)(類似的還有GL_GREEN,GL_BLUE,以及GL_ALPHA)。如果采用的不是RGBA顏色模式,而是采用顏色索引模式,則也可以使用GL_COLOR_INDEX來讀取像素的顏色索引。目前僅需要知道這些,但實(shí)際上還可以讀取其它內(nèi)容,例如深度緩沖區(qū)的深度數(shù)據(jù)等。 第六個參數(shù)表示讀取的內(nèi)容保存到內(nèi)存時所使用的格式,例如:GL_UNSIGNED_BYTE會把各種數(shù)據(jù)保存為GLubyte,GL_FLOAT會把各種數(shù)據(jù)保存為GLfloat等。 第七個參數(shù)表示一個指針,像素數(shù)據(jù)被讀取后,將被保存到這個指針?biāo)硎镜牡刂贰W⒁?#xff0c;需要保證該地址有足夠的可以使用的空間,以容納讀取的像素數(shù)據(jù)。例如一幅大小為256*256的圖象,如果讀取其RGB數(shù)據(jù),且每一數(shù)據(jù)被保存為GLubyte。
好了,那么重點(diǎn)來了,這個方法的坐標(biāo)基準(zhǔn)點(diǎn)是在畫布的左下角!!而我們繪圖的基準(zhǔn)點(diǎn)是在畫布的正中心!!所以我在獲取某個點(diǎn)的顏色的時候一直都是錯誤的結(jié)果,這樣的話在使用的時候我們的xy坐標(biāo)值就要加上畫布寬高的一半才能正常獲取到像素的顏色,希望大家一定注意!! 那么我們?nèi)绾蝸硎褂媚?#xff1f;實(shí)例如下,首先定義GLByte的數(shù)組
1
GLubyte iPixel[3];
另外還有畫布的寬度高度的一半變量
1
int halfWidth,halfHeight;
我們可以調(diào)用如下的方法來獲取(x,y)這個點(diǎn)像素的值
1
glReadPixels(x+halfWidth,y+halfHeight,1,1,GL_RGB,GL_UNSIGNED_BYTE,&iPixel);
在這里第五個參數(shù)我們定義了 GL_RGB,第六個參數(shù)我們定義了 GL_UNSIGNED_BYTE,最后是傳入了數(shù)組的引用。 所以在調(diào)用這個方法之后,iPixel數(shù)組里面的三個值就已經(jīng)賦值為了該點(diǎn)的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,而有時候在我調(diào)試的時候會識別為0,1,1。我在想是不是系統(tǒng)計算誤差問題,如果真是的話,因?yàn)檫@個小小的誤差就影響了我們的判別條件豈不是虧大了?那么在這里我就定義了一個方法,允許一定的誤差,這個誤差姑且就稱為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])
這樣系統(tǒng)誤差便不會影響了。
5.下一個種子點(diǎn)的選取
教材上的種子點(diǎn)選取算法有點(diǎn)搞不懂,我按照上面的思路實(shí)現(xiàn)出來,在填充的時候出現(xiàn)了一系列問題。后來干脆放棄了教材中的方法,自己改寫了一下。 思路大體上是這樣的。
在填充完一行后,這一行最左邊的像素點(diǎn)我們定義為(xLeft,y),最右邊的像素我們定義為(xRight,y),掃描上一行找尋下一個種子點(diǎn),這里y就要增加1,如果(xRight,y+1)這個點(diǎn)不是邊界不是已經(jīng)填充的點(diǎn),那么這個點(diǎn)就可以作為種子點(diǎn)壓入堆棧。如果這個點(diǎn)是邊界或者是已經(jīng)填充的點(diǎn),那么就繼續(xù)往左搜索,如果找到既不是邊界又未填充的點(diǎn),那么這個點(diǎn)就是種子點(diǎn),壓入堆棧。如果一直往左找到xLeft還是沒有找到的話,就不存在下一個種子點(diǎn)了。下一行掃描線也是同樣的原理,y要在這個基礎(chǔ)上減去2即可。
恩,不知道大家有沒有看懂,這是我自己想出來的方法,不敢保證完全正確,在此僅供參考。 如果大家真的可以按照教材中的方法實(shí)現(xiàn)成功的話,希望告訴我一下,感激不盡。
方法實(shí)現(xiàn)
恩,重要的地方都已經(jīng)點(diǎn)明了,下面就直接附上我的種子填充算法吧!
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是給定的種子像素點(diǎn),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);
//如果沒有到達(dá)右邊界,就填充
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);
//如果沒有到達(dá)左邊界,就填充
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);
}
//保存左端點(diǎn)
xLeft=x+1;
//從右邊的點(diǎn)開始
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)){
//如果上方的點(diǎn)不是邊界點(diǎn),直接壓入
Point p={x,y};
pixelStack.push(p);
//壓入之后停止循環(huán)
break;
}else{
x--;
glReadPixels(x+halfWidth,y+halfWidth,1,1,GL_RGB,GL_UNSIGNED_BYTE,&iPixel);
}
}
//檢查下端的掃描線
y=y-2;
//從右邊的點(diǎn)開始
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)){
//如果上方的點(diǎn)不是邊界點(diǎn),直接壓入
Point p={x,y};
//壓入之后停止循環(huán)
pixelStack.push(p);
break;
}else{
x--;
glReadPixels(x+halfWidth,y+halfWidth,1,1,GL_RGB,GL_UNSIGNED_BYTE,&iPixel);
}
}
}
}
以上便是我實(shí)現(xiàn)的種子填充算法,僅供參考 在這里我們用到了glPoint畫點(diǎn)的方法,這是我們定義的,方法如下,為了便于調(diào)試,每畫一個點(diǎn)刷新一下,這樣我們就可以看到繪制的全部動態(tài)效果。
1
2
3
4
5
6
7
8
9
//畫點(diǎn)
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();
}
以上便是畫點(diǎn)的函數(shù)
方法使用
種子填充算法肯定要在我們繪制完機(jī)器人之后使用,任意選取某個四連通區(qū)域的點(diǎn),傳入xy坐標(biāo)值還有要填充的顏色的RGB值,就可以成功實(shí)現(xiàn)填充。 在上一篇機(jī)器人的基礎(chǔ)上,我們在畫機(jī)器人的方法最后加入下面的代碼,即可實(shí)現(xiàn)填充。
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");
注意,有個很奇怪地方是繪制完了之后機(jī)器人就不見了,所以在這里加入了system(“pause”)方法來暫停一下就好啦。 其他的代碼基本都是上一篇中的了,大家自行整理。
運(yùn)行結(jié)果
運(yùn)行結(jié)果截圖如下 恩,就是這樣!
總結(jié)
以上便是博主利用種子填充算法來實(shí)現(xiàn)的機(jī)器人的顏色填充,在此分享給大家,希望對大家有幫助! 如有問題和錯誤,歡迎大家給予我批評和指正,謝謝!
總結(jié)
以上是生活随笔為你收集整理的种子填充算法c语言代码实现,OpenGL绘图实例三之种子填充算法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php跨域有那些方法,PHP跨域访问的3
- 下一篇: 聚观早报 | 华为官宣新机Pocket