總體介紹
1、 使用線性掃描算法畫一條線,線性離散點
2、 利用區域填充算法畫多邊形區域,區域離散的點
開發環境VS2012+OpenGL
開發平台 Intel core i5,Intel HD Graphics Family
設計思路
一、直線掃描算法
1、數值微分法(DDA)
已知過端點P0 (x0, y0), P1(x1, y1)的直線段L:y = kx + b,easy得知直線斜率為:k = (y1-y0)/(x1-x0)。(如果x1≠x0)。
我們如果|k|≤1,這樣x每添加1,y將添加k。而且保證x每添加1。y的增量不能大於1;如果|k| > 1,則應該將x和y互換。因為k是浮點數,因此算法中須要將y舍入為int型。並圓整到最接近的位置。
DDA算法在每次迭代中的x, y值是上一步的值加上一個增量獲得的,因此它是一個增量算法。
可是這樣的方法直觀。但效率太低,由於每一步須要一次浮點乘法和一次舍入運算。
2、中點畫線法
在直線斜率在0~1直接的情況下,設當前像素點為(x,y),那么它的下一個像素點就是p1(x+1,y)或者p2(x+1,y+1)。若稱p1和p2的中點M(px+1,y+0.5),Q為理想直線與x+1垂線的交點,當Q在M的下方時。p1即為下一個像素點,否則p2即為下一個像素點。
3、Bresenham算法
過各行各列象素中心構造一組虛擬網格線。
按直線從起點到終點的順序計算直線與各垂直網格線的交點,然后確定該列象素中與此交點近期的象素。該算法的巧妙之處在於採用增量計算,使得對於每一列,僅僅要檢查一個誤差項的符號,就能夠確定該列的所求象素。
如圖所看到的,設直線方程為yi+1=yi+k(xi+1-xi)+k。
如果列坐標象素已經確定為xi。其行坐標為yi。
那么下一個象素的列坐標為xi+1,而行坐標要么為yi,要么遞增1為yi+1。是否增1取決於誤差項d的值。誤差項d的初值d0=0,x坐標每添加1。d的值對應遞增直線的斜率值k。即d=d+k。一旦 d≥1,就把它減去1,這樣保證d在0、1之間。當d≥0.5時。直線與垂線x=xi+1交點最接近於當前象素(xi,yi)的右上方象素(xi+1,yi+1)。而當d<0.5時,更接近於右方象素(xi+1,yi)。為方便計算,令e=d-0.5,e的初值為-0.5,增量為k。當e≥0時,取當前象素(xi,yi)的右上方象素(xi+1。yi+1);而當e<0時,取(xi,yi)右方象素(xi+1。yi)。
二、區域填充算法
1、遞歸算法
從指定的種子點開始。向各個方向上搜索。逐個像素進行處理,直到遇到邊界,各種種子填充算法僅僅是在處理顏色和邊界的方式上有所不同。
2、掃描線算法
掃描線種子填充算法的基本步驟例如以下:當給定種子點(x, y)時,首先分別向左和向右兩個方向填充種子點所在掃描線上的位於給定區域的一個區段,同一時候記下這個區段的范圍[xLeft, xRight],然后確定與這一區段相連通的上、下兩條掃描線上位於給定區域內的區段,並依次保存下來。
重復這個過程,直到填充結束。
掃描線種子填充算法可由下列四個步驟實現:
(1) 初始化一個空的棧用於存放種子點,將種子點(x, y)入棧。
(2) 推斷棧是否為空,假設棧為空則結束算法,否則取出棧頂元素作為當前掃描線的種子點(x, y),y是當前的掃描線。
(3) 從種子點(x,y)出發。沿當前掃描線向左、右兩個方向填充。直到邊界。分別標記區段的左、右端點坐標為xLeft和xRight;
(4) 分別檢查與當前掃描線相鄰的y - 1和y + 1兩條掃描線在區間[xLeft,xRight]中的像素,從xLeft開始向xRight方向搜索。若存在非邊界且未填充的像素點,則找出這些相鄰的像素點中最右邊的一個,並將其作為種子點壓入棧中,然后返回第(2)步;
三、算法實現
總結及學習感悟
在學習直線掃描算法時,一開始總是畫不出來,后來發現這句glBegin(GL_POINTS);少了個S,沒有S就僅僅能畫一個點,細節非常重要。
學習區域填充算法時,主要的思路就是以一個點為起點。不斷探索周圍,假設在這個區域內,就填充顏色,假設遇到邊界就停止。
掃描線算法也是,先以某點畫一條直線,在區域內的線段部分就填充顏色。
我們就像被選中的一點一樣。周圍的一切對我們來說都是不可知的黑色,僅僅有不斷探索,才知道哪里是邊界。也可能也許沒有邊界,也許邊界的那邊又是一個更大的新世界······噗,我想多了。
源碼
void k1() //0<k<1 { glClear(GL_COLOR_BUFFER_BIT); glColor3f(0.0,0.0,0.0); glBegin(GL_POINTS); GLint x1=0,y1=0,x2=400,y2=200; GLint x=x1,y=y1; GLint dx=x2-x1,dy=y2-y1,dT=2*(dy-dx),dS=2*dy; GLint d=2*dy-dx; glVertex2i(x,y); while(x<x2) { x++; if(d<0) d=d+dS; else { y++; d=d+dT; } glVertex2i(x,y); } glEnd(); glFlush(); }
區域填充
#include "gl/glut.h" #include "windows.h" const int POINTNUM=7; //多邊形點數. /******定義結構體用於活性邊表AET和新邊表NET***********************************/ typedef struct XET { float x; float dx,ymax; XET* next; }AET,NET; /******定義點結構體point******************************************************/ struct point { float x; float y; } polypoint[POINTNUM]={250,50,550,150,550,400,250,250,100,350,100,100,120,30};//多邊形頂點 void PolyScan() { /******計算最高點的y坐標(掃描到此結束)****************************************/ int MaxY=0; int i; for(i=0;i<POINTNUM;i++) if(polypoint[i].y>MaxY) MaxY=polypoint[i].y; /*******初始化AET表***********************************************************/ AET *pAET=new AET; pAET->next=NULL; /******初始化NET表************************************************************/ NET *pNET[1024]; for(i=0;i<=MaxY;i++) { pNET[i]=new NET; pNET[i]->next=NULL; } glClear(GL_COLOR_BUFFER_BIT); //賦值的窗體顯示. glColor3f(0.0,0.0,0.0); //設置直線的顏色紅色 glBegin(GL_POINTS); /******掃描並建立NET表*********************************************************/ for(i=0;i<=MaxY;i++) { for(int j=0;j<POINTNUM;j++) if(polypoint[j].y==i) { //一個點跟前面的一個點形成一條線段。跟后面的點也形成線段 if(polypoint[(j-1+POINTNUM)%POINTNUM].y>polypoint[j].y) { NET *p=new NET; p->x=polypoint[j].x; p->ymax=polypoint[(j-1+POINTNUM)%POINTNUM].y; p->dx=(polypoint[(j-1+POINTNUM)%POINTNUM].x-polypoint[j].x)/(polypoint[(j-1+POINTNUM)%POINTNUM].y-polypoint[j].y); p->next=pNET[i]->next; pNET[i]->next=p; } if(polypoint[(j+1+POINTNUM)%POINTNUM].y>polypoint[j].y) { NET *p=new NET; p->x=polypoint[j].x; p->ymax=polypoint[(j+1+POINTNUM)%POINTNUM].y; p->dx=(polypoint[(j+1+POINTNUM)%POINTNUM].x-polypoint[j].x)/(polypoint[(j+1+POINTNUM)%POINTNUM].y-polypoint[j].y); p->next=pNET[i]->next; pNET[i]->next=p; } } } /******建立並更新活性邊表AET*****************************************************/ for(i=0;i<=MaxY;i++) { //計算新的交點x,更新AET NET *p=pAET->next; while(p) { p->x=p->x + p->dx; p=p->next; } //更新后新AET先排序*************************************************************/ //斷表排序,不再開辟空間 AET *tq=pAET; p=pAET->next; tq->next=NULL; while(p) { while(tq->next && p->x >= tq->next->x) tq=tq->next; NET *s=p->next; p->next=tq->next; tq->next=p; p=s; tq=pAET; } //(改進算法)先從AET表中刪除ymax==i的結點****************************************/ AET *q=pAET; p=q->next; while(p) { if(p->ymax==i) { q->next=p->next; delete p; p=q->next; } else { q=q->next; p=q->next; } } //將NET中的新點增加AET,並用插入法按X值遞增排序**********************************/ p=pNET[i]->next; q=pAET; while(p) { while(q->next && p->x >= q->next->x) q=q->next; NET *s=p->next; p->next=q->next; q->next=p; p=s; q=pAET; } /******配對填充顏色***************************************************************/ p=pAET->next; while(p && p->next) { for(float j=p->x;j<=p->next->x;j++) glVertex2i(static_cast<int>(j),i); p=p->next->next;//考慮端點情況 } } glEnd(); glFlush(); } void init(void) {glClearColor(1.0,1.0,1.0,0.0); //窗體的背景顏色設置為白色 glMatrixMode(GL_PROJECTION); gluOrtho2D(0.0,600.0,0.0,450.0); } void main(int argc,char* argv) { glutInit(&argc,&argv); //I初始化 GLUT. glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); //設置顯示模式:單個緩存和使用RGB模型 glutInitWindowPosition(50,100); //設置窗體的頂部和左邊位置 glutInitWindowSize(400,300); //設置窗體的高度和寬度 glutCreateWindow("An Example OpenGL Program"); //創建顯示窗體 init(); //調用初始化過程 glutDisplayFunc(PolyScan); //圖形的定義傳遞給我window. glutMainLoop(); //所有的圖形和等待 }
版權聲明:本文博客原創文章,博客,未經同意,不得轉載。