參考博文:算法
https://blog.csdn.net/xiaowei_cqu/article/details/7693985數據結構
https://blog.csdn.net/xiaowei_cqu/article/details/7712451框架
https://blog.csdn.net/wodownload2/article/details/52154207函數
https://blog.csdn.net/u013044116/article/details/49737585/.net
https://blog.csdn.net/guanyuqiu/article/details/53010025指針
通常來說,計算機的填充輪廓線的方法有兩大類:掃描轉換和種子填充。code
掃描轉換:按掃描線的順序肯定某一點是否位於多邊形或輪廓形範圍以內。這些算法通常從多邊形或輪廓線的「頂部」開始進行到「底部」。掃描轉換技術適用於光柵掃描設備和畫線顯示器。在畫線顯示器中用於畫剖面線或輪廓的陰影線。以下blog
種子填充:首先假定封閉輪廓線內某點是已知的。而後算法開始搜索與種子相鄰且位於輪廓線內的點。若是相鄰點不位於輪廓線內,那麼就已經到達輪廓線的邊界。若是相鄰點位於輪廓線內,那麼這個點就成爲新的種子點,而後繼續遞歸地搜索下去,種子填充算法只適用於光柵掃描設備。排序
多邊形在計算機中有頂點表示和點陣表示兩種。遞歸
頂點表示就是用多邊形的頂點序列來表示多邊形。點陣表示是用位於多邊形內的象素集合來表示多邊形。頂點表示佔內存少,幾何意義強,易於進行幾何變換;而點陣表示丟失了許多幾何信息(如邊界、頂點)。但光柵顯示圖形須要點陣表示形式。
多邊形的掃描轉換就是把多邊形的頂點表示轉換爲點陣表示。
填充多邊形最簡單的方法就是:
檢查光柵的每一像素是否位於多邊形內。可是這種效率實在過低了。有人曾經想到要用包圍盒來減小計算量,所謂包圍盒就是包含該多邊形的最小矩形。只有在包圍盒的那些點須要檢查。可是這個算法若是遇到下右圖也不見得是種好方法。
因而有人提出了多邊形的掃描轉換,在給定的掃描線上,像素這種特性只在多邊形的邊和該掃描線交點處纔會發生變化。因而咱們就能夠拿這些交點來操做,經過一系列算法進行填充,好比下圖:
咱們能夠看到,掃描線2和多邊形交於x=1和x=8,這兩個交點把掃描線分紅了三段:
x<1 多邊形外 1<=x<=8 多邊形內 x>8 多邊形外
咱們只須要把1<=x<=8,y=2,這段像素置成填充的值,而x<1,y=2;x>8,y=2這段像素置成背景色就能夠了。以此類推,只要是能求出交點,讓計算機把交點認識清楚,就可以準確無誤的完成填充。那麼怎麼「認識」交點呢?
關於這個方法,有人提出了簡單的奇偶掃描轉換算法,它的主要思路就是:在掃描線開始時,奇偶位設置爲0,表示掃描線在多邊形的外部;掃描線與多邊形第一次相交時,奇偶位設置爲1,表示掃描線如今在多邊形的內部;到下一個交點時,奇偶位設置爲0,表示掃描線已經過多邊形,又在多邊形的外部。當奇偶位爲0時,像素設置成多邊形背景色,不然,設置爲多邊形色。
後來有人提出了更爲有效的有序邊表算法,它的基本思想是將多邊形邊與掃描線的交點進行排序。這個算法是本文的重點,最後會用代碼實現。
1,首先來講下這個算法用到的數據結構:邊表NET+活性邊表AET。原理上講,填充的時候是根據活性邊表AET進行填充的,可是活性邊表AET的更新又是依據邊表NET。那麼NET到底存儲的是什麼呢,用「邊」的思路理解有點彆扭,在我看來這個NET存儲的就是多邊形頂點與掃描線相交的信息:
數據結構:
x 當前掃描線與邊的交點座標;dx從當前掃描線到下一條掃描線間x的增量((x2-x1)/(y2-y1));ymax 該邊所交的最高掃描線
數據結構代碼表示:
/*定義結構體用於活性邊表AET和新邊表NET*/ typedef struct XET { double x; double dx, ymax; XET* next; }AET,NET;
舉例:
對於下圖多邊形:
它的邊表NET就是:
咱們看到掃描線1,它與P2(5,1)相交對吧~, 那麼x的值就是5,因爲通過P2的邊有兩條,而這兩條邊y的最大值分別是2,3;斜率的倒數分別是-3,2.5。因而邊表NET頭結點1後面跟的兩個節點就這樣寫了
爲何掃描線4的邊表爲空呢,由於掃描線4與多邊形相交的邊p1p6,p3p4已經被記錄了,再也不是新邊了,因此不記錄了。若是仍是看不懂的話,看這個blog,講的一看就懂:
https://blog.csdn.net/wodownload2/article/details/52154207
邊表根據多邊形進行初始化的代碼以下:
/*初始化頭結點*/ NET *pNET[1024]; for (i = 0; i <= MaxY; i++){ pNET[i] = new NET; pNET[i]->dx = 0; pNET[i]->x = 0; pNET[i]->ymax = 0; pNET[i]->next = NULL; }
/*掃描並創建NET表*/ for (i = MinY; i <= MaxY; i++){ /*i表示掃描線,掃描線從多邊形的最底端開始,向上掃描*/ for (int j = 0; j < vertNum;j++) /*若是多邊形的該頂點與掃描線相交,判斷該點爲頂點的兩條直線是否在掃描線上方 *若是在上方,就記錄在邊表中,而且是頭插法記錄,結點並無按照x值進行排序,畢竟在更新AET的時候還要從新排一次 *因此NET表能夠暫時不排序 */ if (ThePolygon.m_Vertex[j].y == i){ /*筆畫前面的那個點*/ if (ThePolygon.m_Vertex[(j - 1 + vertNum) % vertNum].y > ThePolygon.m_Vertex[j].y){ NET *p = new NET; p->x = ThePolygon.m_Vertex[j].x; p->ymax = ThePolygon.m_Vertex[(j - 1 + vertNum) % vertNum].y; p->dx = double((ThePolygon.m_Vertex[(j - 1 + vertNum) % vertNum].x - ThePolygon.m_Vertex[j].x)) / double((ThePolygon.m_Vertex[(j - 1 + vertNum) % vertNum].y - ThePolygon.m_Vertex[j].y)); p->next = pNET[i]->next; pNET[i]->next = p; } /*筆畫後面的那個點*/ if (ThePolygon.m_Vertex[(j + 1 + vertNum) % vertNum].y > ThePolygon.m_Vertex[j].y){ NET *p = new NET; p->x = ThePolygon.m_Vertex[j].x; p->ymax = ThePolygon.m_Vertex[(j + 1 + vertNum) % vertNum].y; p->dx = double((ThePolygon.m_Vertex[(j + 1 + vertNum) % vertNum].x - ThePolygon.m_Vertex[j].x)) / double((ThePolygon.m_Vertex[(j + 1 + vertNum) % vertNum].y - ThePolygon.m_Vertex[j].y)); p->next = pNET[i]->next; pNET[i]->next = p; } } }
而後就是動態的更新活性邊表AET了, 更新的原則就是:
一、根據給出的多邊形頂點座標,創建NET表; 求出頂點座標中最大y值ymax和最小y值ymin。 二、初始化AET表指針,使它爲空。 三、執行下列步驟直至NET和AET都爲空. 3.一、如NET中的第y類非空,則將其中的全部邊取出並插入AET中; 3.二、若是有新邊插入AET,則對AET中各邊排序; 3.三、對AET中的邊兩兩配對,(1和2爲一對,3和4爲一對,…), 將每對邊中x座標按規則取整,得到有效的填充區段,再填充. 3.四、將當前掃描線縱座標y值遞值1; 3.五、若是AET表中某記錄的ymax=yj,則刪除該記錄 (由於每條邊被看做下閉上開的); 3.六、對AET中剩下的每一條邊的x遞增dx,即x' = x+ dx .
想要看到具體動態的步驟仍是參考這篇博文,給的太詳細了,看完確定能知道是怎麼個流程:
https://blog.csdn.net/wodownload2/article/details/52154207
更新AET的代碼以下:
/*創建並更新活性邊表AET*/ for (i = MinY; i <= MaxY; i++){ /*更新活性邊表AET,計算掃描線與邊的新的交點x,此時y值沒有達到臨界值的話*/ NET *p = pAET->next; while (p){ p->x = p->x + p->dx; p = p->next; } /*更新完之後,對活性邊表AET按照x值從小到大排序*/ 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++){ pDC->SetPixel(static_cast<int>(j), i,fillCol); } p = p->next->next; } }
因此整個函數的實現是這樣的:
void CCGPainterView::ScanlineConvertion(CDC *pDC, MyPolygon ThePolygon, COLORREF fillCol) { //Write your own scan-line convertion algorithm here. /*定義結構體用於活性邊表AET和新邊表NET*/ typedef struct XET { double x; double dx, ymax; XET* next; }AET,NET; //CPoint *ThePolygon.m_Vertex; int vertNum = ThePolygon.m_VerticeNumber; /*計算最高點y的座標,掃描線掃到y的最高點就結束*/ int MaxY = ThePolygon.m_Vertex[0].y; int MinY = ThePolygon.m_Vertex[0].y; int i; for (i = 1; i < vertNum; i++){ if (ThePolygon.m_Vertex[i].y>MaxY) MaxY = ThePolygon.m_Vertex[i].y; if (MinY > ThePolygon.m_Vertex[i].y) MinY = ThePolygon.m_Vertex[i].y; } /*初始化AET表,這是一個有頭結點的鏈表*/ AET *pAET = new AET; pAET->next = NULL; /*初始化NET表,這也是一個有頭結點的鏈表,頭結點的dx,x,ymax都初始化爲0*/ NET *pNET[1024]; for (i = 0; i <= MaxY; i++){ pNET[i] = new NET; pNET[i]->dx = 0; pNET[i]->x = 0; pNET[i]->ymax = 0; pNET[i]->next = NULL; } /*掃描並創建NET表*/ for (i = MinY; i <= MaxY; i++){ /*i表示掃描線,掃描線從多邊形的最底端開始,向上掃描*/ for (int j = 0; j < vertNum;j++) /*若是多邊形的該頂點與掃描線相交,判斷該點爲頂點的兩條直線是否在掃描線上方 *若是在上方,就記錄在邊表中,而且是頭插法記錄,結點並無按照x值進行排序,畢竟在更新AET的時候還要從新排一次 *因此NET表能夠暫時不排序 */ if (ThePolygon.m_Vertex[j].y == i){ /*筆畫前面的那個點*/ if (ThePolygon.m_Vertex[(j - 1 + vertNum) % vertNum].y > ThePolygon.m_Vertex[j].y){ NET *p = new NET; p->x = ThePolygon.m_Vertex[j].x; p->ymax = ThePolygon.m_Vertex[(j - 1 + vertNum) % vertNum].y; p->dx = double((ThePolygon.m_Vertex[(j - 1 + vertNum) % vertNum].x - ThePolygon.m_Vertex[j].x)) / double((ThePolygon.m_Vertex[(j - 1 + vertNum) % vertNum].y - ThePolygon.m_Vertex[j].y)); p->next = pNET[i]->next; pNET[i]->next = p; } /*筆畫後面的那個點*/ if (ThePolygon.m_Vertex[(j + 1 + vertNum) % vertNum].y > ThePolygon.m_Vertex[j].y){ NET *p = new NET; p->x = ThePolygon.m_Vertex[j].x; p->ymax = ThePolygon.m_Vertex[(j + 1 + vertNum) % vertNum].y; p->dx = double((ThePolygon.m_Vertex[(j + 1 + vertNum) % vertNum].x - ThePolygon.m_Vertex[j].x)) / double((ThePolygon.m_Vertex[(j + 1 + vertNum) % vertNum].y - ThePolygon.m_Vertex[j].y)); p->next = pNET[i]->next; pNET[i]->next = p; } } } /*創建並更新活性邊表AET*/ for (i = MinY; i <= MaxY; i++){ /*更新活性邊表AET,計算掃描線與邊的新的交點x,此時y值沒有達到臨界值的話*/ NET *p = pAET->next; while (p){ p->x = p->x + p->dx; p = p->next; } /*更新完之後,對活性邊表AET按照x值從小到大排序*/ 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++){ pDC->SetPixel(static_cast<int>(j), i,fillCol); } p = p->next->next; } } }
我是在vs2013下,MFC框架下執行,最後的執行效果以下,填充速度仍是很是快的:
本文主要是給出能運行的代碼,關於原理更爲詳盡的解釋請看參考blog。