按照图像学书上的讲解,多边形的填充可以采用种子填充法和有序边表法。
种子填充法就是
- 在 多边形中选择一个种子点
- 若该点未被填充,则填充;否则返回;
- 检查该点周围的点(8个点或4个点),对每个点递归调用 2 .
这是采用递归实现的,当然也可以手工用栈来实现,差别不大。由于每次都是对单点操作,每个点需要考虑周围的8个或4个点,所以时间、空间开销都很大。很多同学用的递归来做,当图形过大时,整个程序就栈溢出了。自己用的手工栈,最开始在函数内部开的一个很大的Cpoint (用的MFC - -)数组,当时一运行就崩溃了,周围同学都做出来了,自己比较着急啊,没能很好的静下心来分析,虽然知道是堆栈溢出了,但是把数组改到自认为很小了还是错了。后来课后有时间静心调试了下,首先是代码算法部分有问题的(手工入栈的时候错了,应该首先把该点填充,再加入栈;否者会导致点被重复的加入栈中;),然后就是在入栈的时候没有考虑栈本本身越界问题,所以及时数组开的小,但程序还是因为数组越界崩溃了。最后,由于程序堆栈的限制,数组只能开到一定的大小,就把这个数组作为全局变量了。最近学了编译原理,也听同学说了,全局变量是放在静态区,不放入栈中,所以可以开的很大很大的。然后最后做出来了。效果很差。首先是数组真的必须开的很大很大。其次是效率非常低,能够很明显的看出填充的状态,因为是采用栈(递归),所以首先是往一个方向填充,这样当这个方向填充完毕后,会存在出栈的问题(递归返回),这时候图像没有被填充,所以结果就是填充似乎变成了两步,先填一半,过段时间再填一半。当然书上就优化方法,就是改写2,每次不再仅填充该点,而是填充该点在多边形内的十字形轨迹。这样应该是可以减少栈深度和绘制时间的,不过自己没有做 - -。
这个算法在实现时判断边界是通过GetPixel得到该点的像素颜色,然后确定是否是边界,所以这就导致了首先在填充之前得把边给绘制出来,然后呢,种子点选取是通过点击区域内的一个点来获得的,所以这种方法适合交互式画图,我想画图中的“填充”工具应该就是这么做的。
有序边表法:
有序边表的基本思想就是求出每条扫描线与多边形的边的交点,然后将交点之间的区域画线(填充)即可。
所以我们首先要知道这个多边形的每条边的信息,这个通过顶点可以构造出来。为了后边处理方便,我们令每条边的y都是递增的,即起点的y小于终点的y值。然后我们再对边排序,按照起点y值有小到大,起点y值相等时终点y值由小到大的顺序排序。通过这样的处理,我们可以知道扫描线的范围(即第一条表的起点y值到最后一条边终点y值)。
接着就是核心部分,求每条扫描线与多边形的交点。由于每条扫描线并非与多边形每条边相交,且存在这样的规律,总是在每个顶点的地方,一条扫描线开始与一条或多条边相交或与一条或多条边不再有交点。即是说,在扫描线y值等于一个顶点的y值时,我们才需要考虑扫描线与多边形交点的个数发生了变化(确定的说,若是增加了交点,则该交点就是该顶点),其余时刻,我们可以根据前一条扫描线与多边形的交点情况,简单的得出该条扫描线与多边形的情况(确定的说,由于多边形边为直线,假设我们从y值由小到大扫描,则该扫描线与一条边的交点和上一条扫描线与该边的交点存在这样的关系:xcur = (xpre+1/k),k为直线斜率)。具体的做法是:
- 求出每条边的斜率的倒数,存储在边的数据结构中
- 建立新边表,即是对于每个顶点(准确的说,是每条边的起点,因为终点是不可能有新加边的),求出该位置的扫描线与多边形新增的交点所在的边。
- 建立有序边表。建立
struct Node
{
float x ;
float deltaX ;
int yMax ;
struct Node * next ;
}的边表项,该项代表一个扫描线与多边形边的交点。
建立
struct AET
{
int y ;
struct Node * nodeList ;
}的数组,没一项就是一条扫描线与该多边形的所有交点。
构建该交点时,首先查看新边表中该扫描线是否有新边加入,若有,则将新边与扫描线的交点构造成Node节点按照x递增的顺序加入到nodeList中(这里注意,该交点就是边的顶点,且该同一个交点会被加入两次[细想一下,应该是如此的!这样也解决了有序边画线遇到的顶点问题])。然后查看上一条扫描线中的每一项node,若当前的y> yMax ,z则放弃该node,否则需要构建新的node,令x = x + deltaX , 加入到nodeList中。
- 根据构建的AET,对每条扫描线(即AET中每一项),每次取出两个node,画出(node1.x , y)到(node2.x ,y)的直线.这里存在一个问题,若多边形的边是水平的,那么该扫描线与这条边将有太多的交点,这样画效率太低(不过按照之前的逻辑处理,感觉也不会出问题,没有试验,健壮性很不好啊!!)。我们应该在由点构建边的时候就处理这种情况!若两点y值相同,则直接画出这条边(填充),且不将这条边加入到边的数据结构中。
参考了http://blog.csdn.net/orbit/article/details/7368996文章。应该讲得更清楚。。。
最后贴上代码吧:
1.种子填充:
#define MAX_POINTS 1366*768/2
CPoint stack[MAX_POINTS] ;
int top = 0 ;
stack[top].x = cp.x ;
stack[top].y = cp.y ;
pDC->SetPixel(cp,RGB(0,0,0)) ;
top++ ;
while(top != 0)
{
top-- ;
if(top >= MAX_POINTS -4)
{
MessageBox(_T("stack full")) ;
return ;
}
// cprintf("%d\n",top) ;
cp.x = stack[top].x ;
cp.y = stack[top].y ;
//get the points
CPoint lp , rp , up ,bp ;
lp.x = cp.x -1 ;
lp.y = cp.y ;
rp.x = cp.x + 1 ;
rp.y = cp.y ;
up.x = cp.x ;
up.y = cp.y + 1 ;
bp.x = cp.x ;
bp.y = cp.y -1 ;
//has been filled ?
if(pDC->GetPixel(lp) != RGB(0,0,0))
{
pDC->SetPixel(lp,RGB(0,0,0)) ;
stack[top].x = lp.x ;
stack[top].y = lp.y ;
top++ ;
}
if(pDC->GetPixel(rp) != RGB(0,0,0))
{
pDC->SetPixel(rp,RGB(0,0,0)) ;
stack[top].x = rp.x ;
stack[top].y = rp.y ;
top++ ;
}
if(pDC->GetPixel(up) != RGB(0,0,0))
{
pDC->SetPixel(up,RGB(0,0,0)) ;
stack[top].x = up.x ;
stack[top].y = up.y ;
top++ ;
}
if(pDC->GetPixel(bp) != RGB(0,0,0))
{
pDC->SetPixel(bp,RGB(0,0,0)) ;
stack[top].x = bp.x ;
stack[top].y = bp.y ;
top++ ;
}
}
2.有序边表:
定义数据结构
struct NETNode
{
int edgeOrder ;
struct NETNode * next ;
} ;
struct NETItem
{
int y ;
struct NETNode * nodeList ;
} ;
struct NET
{
vector<NETItem> data ;
int pos ;
} ;
struct AETNode
{
float x ;
float deltaX ;
int yMax ;
struct AETNode * next ;
} ;
struct AETItem
{
int y ;
struct AETNode * nodeList ;
} ;
class Edge
{
public :
CPoint startPnt ;
CPoint endPnt ;
float slopeReciprocal ;
bool hasAdded ;
Edge(CPoint pnt1 , CPoint pnt2 )
{
//make the point's 'y' increase
if(pnt1.y < pnt2.y)
{
startPnt.x = pnt1.x ;
startPnt.y = pnt1.y ;
endPnt.x = pnt2.x ;
endPnt.y = pnt2.y ;
}
else if(pnt1.y > pnt2.y)
{
startPnt.x = pnt2.x ;
startPnt.y = pnt2.y ;
endPnt.x = pnt1.x ;
endPnt.y = pnt1.y ;
}
else
{
return ;
}
slopeReciprocal = (static_cast<float>((endPnt.x - startPnt.x)))/(endPnt.y - startPnt.y ) ;
hasAdded = false ;
}
} ;
算法,写得很乱,C++的数据结构用得还不太好,之前用标准库用的太少了,感觉到了真正写东西的时候,有没有那么多心思自己来搞基础的数据结构了,还是用标准的来得好。
void CGraphics_1View::fillInner()
{
//first ,build the edges
vector<Edge> edges ;
for(int i = 0 ; i < pointsLen ; i++)
{
int cur = i ;
int nxt = (i+1)%pointsLen ;
if(points[cur].y != points[nxt].y)
{
Edge tmpEdge(points[cur], points[nxt]) ;
edges.push_back(tmpEdge) ;
}
else
{
//if the edge is horizontal , we don't add it to the edge table , we just draw it immediately
CDC * pDC = GetWindowDC() ;
for(int k = points[cur].x ; k < points[nxt].x ; k++)
{
pDC->SetPixel(k,points[cur].y,RGB(0,0,0)) ;
}
}
}
sort(edges.begin(),edges.end(),sortFn) ;
vector<Edge>::iterator it ;
for(it = edges.begin() ; it != edges.end() ; ++it)
{
cprintf("%d,%d,%d,%d\n",it->startPnt.x,it->startPnt.y,it->endPnt.x , it->endPnt.y) ;
}
// new edge table node
NET net ;
net.pos = 0 ;
for(i = 0 ; i < edges.size() ; i++)
{
if(edges.at(i).hasAdded == true)
{
continue ;
}
int y = edges.at(i).startPnt.y ;
//by this ,we can ensure the y is the only and increase
//build the NETItem
NETItem newItem ;
newItem.y = y ;
NETNode * newNode = new NETNode ;
newNode->edgeOrder = i ;
newNode->next = NULL ;
newItem.nodeList = newNode ;
edges.at(i).hasAdded = true ;
//find the
for(int j = 0 ; j < edges.size() ; j++)
{
if(edges.at(j).hasAdded)
{
continue ;
}
if(y>= edges.at(j).startPnt.y && y <= edges.at(i).endPnt.y)
{
newNode = new NETNode ;
newNode->edgeOrder = j ;
newNode->next = newItem.nodeList ;
newItem.nodeList = newNode ;
edges.at(j).hasAdded = true ;
}
}
net.data.push_back(newItem) ;
}
for(i = 0 ; i < net.data.size() ;i++)
{
cprintf("%d\t",net.data[i].y) ;
NETNode * pos = net.data[i].nodeList ;
while(pos != NULL)
{
cprintf("%d,",pos->edgeOrder) ;
pos = pos->next ;
}
cprintf("\n") ;
}
//build active edge table
int yMax = edges.at(edges.size()-1).endPnt.y ;
int yMin = edges.at(0).startPnt.y ;
int aetSize = yMax - yMin + 1 ;
vector<AETItem> aet(aetSize) ;
for(i = 0 ; i < aetSize ; i++)
{
aet[i].y = i + yMin ;
aet[i].nodeList = NULL ;
if(net.pos < net.data.size() && aet[i].y == net.data[net.pos].y)
{
//this line has new edge
//add it
NETNode * pos = net.data[net.pos].nodeList ;
while(pos != NULL)
{
AETNode * newNode = new AETNode ;
int edgeOrder = pos->edgeOrder ;
newNode->yMax = edges[edgeOrder].endPnt.y ;
newNode->x = edges[edgeOrder].startPnt.x ;
newNode->deltaX = edges[edgeOrder].slopeReciprocal ;
AETNode ** aetPos = & aet[i].nodeList ;
if(*aetPos == NULL)
{
newNode->next = NULL ;
aet[i].nodeList = newNode ;
}
else
{
while( (*aetPos) != NULL)
{
if(newNode->x <= (*aetPos)->x)
{
newNode->next = (*aetPos) ;
(*aetPos) = newNode ;
break ;
}
aetPos = &((*aetPos)->next) ;
}
if(*aetPos == NULL)
{
newNode->next = NULL ;
(*aetPos) = newNode ;
}
}
pos = pos->next ;
}
net.pos++ ;
}
//get the scan line and the polygen edge's crossover point by using the prevoius item
if(i != 0)
{
AETNode * preNodePos = aet[i-1].nodeList ;
while(preNodePos != NULL)
{
//if it is y > yMax , it will occure the white line , does not know the reason
if(aet[i].y >= preNodePos->yMax)
{
preNodePos = preNodePos->next ;
}
else
{
AETNode * newNode = new AETNode ;
newNode->deltaX = preNodePos->deltaX ;
newNode->x = preNodePos->x + preNodePos->deltaX ;
newNode->next = NULL ;
newNode->yMax = preNodePos->yMax ;
//insert
AETNode ** pForIns = & aet[i].nodeList ;
while(*pForIns != NULL)
{
if(newNode->x <= (*pForIns)->x)
{
newNode->next = *pForIns ;
(*pForIns) = newNode ;
break ;
}
pForIns = &((*pForIns)->next) ;
}
if(*pForIns == NULL)
{
(*pForIns) = newNode ;
}
preNodePos = preNodePos->next ;
}
}
}
}
//draw
CDC * pDC = GetWindowDC() ;
for(i = 0 ; i< aet.size() ; i++)
{
AETNode * pForDraw = aet[i].nodeList ;
while(pForDraw != NULL)
{
int x1 = static_cast<int>(pForDraw->x) ;
if(pForDraw->next != NULL)
pForDraw = pForDraw->next ;
int x2 = (int)pForDraw->x ;
for(int k = x1 ;k < x2 ; k++)
{
pDC->SetPixel(k,aet[i].y,RGB(0,0,0)) ;
}
if(pForDraw != NULL)
pForDraw = pForDraw->next ;
}
}
其中的sort调用cmp函数:
bool sortFn(Edge e1 , Edge e2)
{
return e1.startPnt.y < e2.startPnt.y || e1.endPnt.y < e2.endPnt.y ;
}