您的位置:首页 > 理论基础

计算机图形学 -- 光栅图形学扫描线填充多边形[转]

2012-08-08 10:06 471 查看

http://blog.sina.com.cn/s/blog_55a8a96d0100084k.html

  研究如何用一种颜色或图案来填充一个二维区域。一般来说,区域的封闭轮廓是简单的多边形。若轮廓线由曲线构成,则可将曲线转换成多条直线段顺连而成,此时,区域轮廓线仍然是一种多边形逼近。
  在计算机图形学中,多边形区域有两种重要的表示方法:顶点表示和点阵表示。所谓顶点表示,即是用多边形的顶点序列来表示多边形。这种表示直观、几何意义强、占内存少、易于进行几何变换,但由于它没有明确指出哪些像素在多边形内,故不能直接用于区域填充。所谓点阵表示,则是用位于多边形内的像素集合来刻画多边形。这种表示丢失了许多几何信息,但便于进行填充。
  根据区域的定义,可以采用不同的填充算法,其中最具代表性的是:适用于顶点表示的扫描线类算法和适应于点阵表示的种子填充算法。
  扫描线来源于光栅显示器的显示原理:对于屏幕上所有待显示像素的信息,将这些信息按从上到下、自左至右的方式显示。因此,扫描线多边形区域填充算法的基本原理是,待填充区域按y方向(x方向亦可)扫描线顺序扫描生成。具体实现时,首先按扫描线顺序,计算扫描线与多边形的相交区间;再用指定的颜色填充这些区间内的像素,即完成这一条扫描线的填充工作。区间的端点可以通过计算扫描线与多边形边界的交点获得。
  为了提高效率,在处理每一条扫描线时,仅对与它相交的多边形的边进行求交运算。我们把当前扫描线相交的边称为活性边(active edge),并把它们按扫描线交点x坐标递增的顺序存放在一个链表中,称此链表为活性边表(AET)。
  为了提高速度,假定当前扫描线与多边形某一条边的交点的x坐标为xi,则下一条扫描线与该点的交点不需要重新计算,而是通过增加一个增量⊿x来获得。对于直线ax+by+c=0,⊿x=-b/a为常数。
  另外使用增量法计算时,还需要知道一条边何时不再与下一条扫描线相交,以便及时把它从活性边表中删除出去。因此,活性边表结点的数据结构应保存如下内容:第1项保存当前扫描线与边的交点坐标x值;第2项保存从当前扫描线到下一条扫描线间x的增量⊿x;第3项保存该边所交的最高扫描线好ymax。
  为了方便活性边表的建立与更新,可为每一条扫描线建立一个边表(ET),存放在该扫描线第一次出现的边。也就是说,若某边的较低端点为ymin,则该边就放在扫描线ymin的边表中。
算法过程如下: void FillPolygon(多边形 polygon,int color)
{
for (各条扫描线,标示为i)
{
初始化边表头指针ET[i];
把ymin=i的边放入ET[i];
}
y=最低扫描线号;
初始化活性边表AET为空;
for (各条扫描线i)
{
把边表ET[i]中的边结点用插入排序法插入AET表,使之按x坐标递增顺序排列;
遍历AET表,把配对交点区间上的像素(x,y)填色;
遍历AET表,把ymax=i的结点从AET表中删除,并把ymax>i结点的x值递增⊿x;
若允许多边形的边自相交,则用冒泡排序法对AET表重新排序;
}
}   扫描线与多边形顶点相交时,必须正确的进行交点个数计算,否则,在进行填充时会出现错误。扫描线与多边形相交的边分处扫描线的两侧,则计一个交点;扫描线与多边形相交的边分处扫描线同侧,且yi<y(i-1),yi<y(i+1),则计2个交点,若yi>y(i-1),yi>y(i+1),则计0个交点;扫描线与多边形边界重合,则计1个交点。 代码如下:
void FillPolygon(LPPOINT lpPoints,int nCount, CDC *pDC, int nColor) { // 检查参数合法性 ASSERT_VALID(pDC); ASSERT(lpPoints);//点 ASSERT(nCount>2);//点的数目 ASSERT(nColor>=0); // 边结构数据类型 typedef struct Edge{ int ymax; // 边的最大y坐标 double x; // 与当前扫描线的交点x坐标 double dx; // 边所在直线斜率的倒数,增量 struct Edge * pNext; // 指向下一条边 }Edge, * LPEdge; int i,j,k; int y0,y1; // 扫描线的最大和最小y坐标 LPEdge pAET; // 活化边表头指针 LPEdge * pET; // 边表头指针 pAET=new Edge; // 初始化表头指针,第一个结点不用 pAET->pNext=NULL; // 获取y方向扫描线边界,要求保证y0<y1 y0=y1=lpPoints[0].y; for(i=1;i<nCount;i++) { if(lpPoints[i].y<y0) y0=lpPoints[i].y; else if(lpPoints[i].y>y1) y1=lpPoints[i].y; } if(y0>=y1) return; // 这种情况下不需要填充,结束 // 初始化边表,第一个结点不用,这样可以保证pET[i]非空 pET=new LPEdge[y1-y0+1]; for(i=0;i<=y1-y0;i++) // 扫描线在[y0,y1]范围内,仅对这部分建立边表,pET[0]对应扫描线y0 { pET[i]= new Edge; pET[i]->pNext=NULL; } for(i=0;i<nCount;i++) { j=(i+1)%nCount; // 组成边的下一点,之所以这么写是因为最后一个点与第一个点相连 if(lpPoints[i].y != lpPoints[j].y)// 如果该边不是水平的则加入边表,同时保证增量的有意义 { LPEdge peg; // 指向该边的指针 LPEdge ppeg; // 指向边指针的指针 // 构造边 peg =new Edge; k=(lpPoints[i].y>lpPoints[j].y)?i:j; peg->ymax=lpPoints[k].y; // 该边最大y坐标 k=(k==j)?i:j; peg->x=(double)lpPoints[k].x; // 该边与扫描线焦点x坐标 peg->dx=(double)(lpPoints[i].x-lpPoints[j].x)/(lpPoints[i].y-lpPoints[j].y);// 该边斜率的倒数 peg->pNext=NULL; // 插入边 ppeg=pET[lpPoints[k].y-y0]; while(ppeg->pNext) ppeg=ppeg->pNext; ppeg->pNext=peg; }// end if }// end for i // 扫描 for(i=y0;i<=y1;i++) { LPEdge peg0=pET[i-y0]->pNext; LPEdge peg1=pET[i-y0]; if(peg0)// 有新边加入 { while(peg1->pNext) peg1=peg1->pNext; peg1->pNext=pAET->pNext; pAET->pNext=peg0; } // 按照x递增排序pAET peg0=pAET; while(peg0->pNext) { LPEdge pegmax=peg0; LPEdge peg1=peg0; LPEdge pegi=NULL; while(peg1->pNext) { if(peg1->pNext->x>pegmax->pNext->x) pegmax=peg1; peg1=peg1->pNext; } pegi=pegmax->pNext; pegmax->pNext=pegi->pNext; pegi->pNext=pAET->pNext; pAET->pNext=pegi; if(peg0 == pAET) peg0=pegi; } // 遍历活边表,画线 peg0=pAET; while(peg0->pNext!=NULL && peg0->pNext->pNext!=NULL) { // 画一条线段从(peg0->pNext->x,i)到(peg0->pNext->pNext->x,i) pDC->MoveTo((int)peg0->pNext->x,i); pDC->LineTo((int)peg0->pNext->pNext->x,i); peg0=peg0->pNext->pNext; } // 把ymax=i的节点从活边表删除并把每个节点的x值递增dx peg0=pAET; while(peg0->pNext) { if(peg0->pNext->ymax < i+2) { peg1=peg0->pNext; peg0->pNext=peg0->pNext->pNext; //删除 delete peg1; continue; } peg0->pNext->x+=peg0->pNext->dx; //把每个节点的x值递增dx peg0=peg0->pNext; } } // 删除边表 for(i=0;i<y1-y0;i++) if(pET[i]) delete pET[i]; if(pAET) delete pAET; if(pET) delete[] pET; }
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐