目录
- 前驱知识
- 多边形的扫描转换
- 有效边表填充算法
- 原理
- 边界像素处理原则
- 怎么算交点
- 有效边
- 桶表与边表
- 桶表表示法
- 边缘填充算法
- 填充过程
- 在这里插入图片描述
- 区域填充算法/种子填充算法
- 种子填充算法
- 扫描线种子填充算法 (更有效)
前驱知识
- 了解扫描转换的基本概念。
- 熟练掌握多边形有效边表填充算法。
- 掌握多边形边缘填充算法。
- 熟练掌握区域四邻接点和八邻接点区域填充算法。
- 掌握区域扫描线种子填充算法。
无论使用哪种着色模式,都意味着要使用指定颜色为多边形边界内的每一个像素着色。
多边形的表示 有两种:⑴顶点表示法;⑵点阵表示法
多边形的扫描转换
定义:将多边形的描述从顶点表示法变换到点阵表示法的过程,称为多边形的扫描转换。
即从多边形的顶点信息出发,求出位于多边形内部的各个像素点信息,并将其颜色值写入帧缓冲的相应单元中。
多边形可以使用平面着色模式(flat shading mode)或光滑着色模式(smooth shading mode)填充。
多边形填充的主要算法是扫描线算法。
先确定多边形覆盖的扫描线条数,对每一条扫描线,计算扫描线与多边形边界的交点区间,如果能判断该区间在多边形内部,则将其内的像素绘制为指定的颜色。
扫描线算法在处理每条扫描线时,需要与多边形的所有边求交,处理效率很低。改进的算法是有效边表算法。
对一条扫描线的填充一般分为以下4个步骤
- 求交:计算扫描线与多边形各边的交点;
- 排序:把扫描线上所有交点按递增顺序进行排序;
- 配对:将第一个交点与第二个交点,第三个交点与第四个交点等等进行配对,每对交点代表扫描线与多边形的一个相交区间。
- 着色:把区间内的像素置为填充色。
种子填充算法是从区域内的一个种子位置开始,由内向外用填充颜色绘制种子及其相邻像素直到颜色不同的边界像素为止。
种子填充算法主要分为4邻接点算法和8邻接点算法。
有效边表填充算法
原理
填充原理是按照扫描线从小到大的移动顺序,计算当前扫描线与有效边的交点,然后把这些交点按x值递增的顺序进行排序、配对,以确定填充区间,最后用指定颜色填充区间内的所有像素,即完成填充工作。
有效边表填充算法已成为目前最为有效的多边形填充算法之一。
边界像素处理原则
填充左下角为(1,1),右上角为(3,3)的正方形时,若将边界上的所有像素全部填充,就得到图示的结果。
在多边形填充过程中,常采用“左闭右开”和“下闭上开”的原则对边界像素进行处理。
怎么算交点
这里 边 相对于 扫描线 的位置都是上下关系,不是左右关系。
有效边
为了确定在哪条扫描线上插入了新边,就需要构造一个边表(edge table,ET),用以存放扫描线上多边形各条边出现的信息。
因为水平边的1/k为∞,并且水平边本身就是扫描线,在建立边表时可以不予考虑。
桶表与边表
为了确定在哪条扫描线上插入了新边,就需要构造一个边表(edge table,ET),用以存放扫描线上多边形各条边出现的信息。因为水平边的1/k为∞,并且水平边本身就是扫描线,在建立边表时可以不予考虑。
桶表表示法
桶表是按照扫描线顺序管理边出现情况的一个数据结构。
- 构造一个纵向扫描线链表,链表的长度为多边形所占有的最大扫描线数,链表的每个结点称为桶(bucket),对应多边形覆盖的每一条扫描线。
桶类的代码
class CBucket
{
public:
CBucket();
virtual ~CBucket();
public:
int ScanLine; //扫描线
CAET *p; //桶上的边表指针
CBucket *next;
};
边缘填充算法
远离/定义:
边缘填充算法是先求出多边形的每条边与扫描线的交点,然后将交点右侧的所有像素颜色全部取为补色(或反色)。按任意顺序处理完多边形的所有边后,就完成了多边形的填充任务。
边缘填充算法利用了图像处理中的求“补”或求“反”的概念,对于黑白图像,求补就是把RGB(1,1,1)(白色)的像素置为RGB(0,0,0)(黑色),反之亦然;对于彩色图像,求补就是将背景色置为填充色,反之亦然。求补的一条基本性质是一个像素求补两次就恢复为原色。如果多边形内部的像素被求补偶数次,保持原色,如果被求补奇数次,显示填充色。
填充过程
过程:
(下图顺序)从左到右从上到下
代码:
void CTestView::FillPolygon(CDC *pDC)
{
COLORREF BClr=RGB(255,255,255);//背景色
COLORREF FClr=GetClr;//填充色
int yMin,yMax;//边的最小y值与最大y值
double x,y,k;//x,y当前点,k斜率的倒数
for(int i=0;i<7;i++)//循环多边形所有边
{
int j=(i+1)%7;
k=(P[i].x-P[j].x)/(P[i].y-P[j].y);//计算1/k
if(P[i].y<P[j].y)//得到每条边y的最大值与最小值
{
yMin=Round(P[i].y);
yMax=Round(P[j].y);
x=P[i].x;//得到x|ymin
}
else
{
yMin=Round(P[j].y);
yMax=Round(P[i].y);
x=P[j].x;
}
for(y=yMin;y<yMax;y++)//沿每一条边循环扫描线
{
//对每一条扫描线与边的交点的右侧像素循环
for(int m=Round(x);m<MaxX;m++)
//MaxX为包围盒的右边界
{
if(FClr==pDC->GetPixel(m,Round(y))) pDC->SetPixelV(m,Round(y),BClr);
else
pDC->SetPixelV(m,Round(y),FClr);
}
x+=k;
}
}
}
区域填充算法/种子填充算法
原理
种子填充算法是从区域内任一个种子像素位置开始,由内向外将填充色扩散到整个多边形区域的填充过程。
优点是能对具有任意复杂闭合边界的区域进行填充。
种子填充算法
定义;
- 从种子像素点开始,使用四邻接点方式搜索下一像素点的填充算法称为四邻接点填充算法。
- 从种子像素点开始,使用八邻接点方式搜索下一像素点的填充算法称为八邻接点填充算法。
八邻接点填充算法的设计和四邻接点填充算法基本相似,只要把搜索方式由四邻接点修改为八邻接点即可。
种子填充算法一般要求区域边界色和填充色不同,输入参数只有种子坐标位置和填充颜色。
原理
代码:
void CTestView::FillPolygon(CDC *pDC)//填充多边形
{
COLORREF BoundaryClr=RGB(0,0,0);//边界色
COLORREF PixelClr;//当前像素的颜色
pHead=new CStackNode;//建立栈头结点
pHead->next=NULL;//栈的头结点总是为空
Push(Seed); //种子像素入栈
while(NULL!=pHead->next)//如果栈不为空
{
CP2 PopPoint;
Pop(PopPoint); //种子像素出栈
pDC->SetPixelV(Round(PopPoint.x),
Round(PopPoint.y),SeedClr);
PointLeft.x=PopPoint.x-1;//左方像素
PointLeft.y=PopPoint.y;
PixelClr=pDC->GetPixel(Round(PointLeft.x),
Round(PointLeft.y));
if(BoundaryClr!=PixelClr && SeedClr!=PixelClr)
Push(PointLeft);//左方像素入栈
PointTop.x=PopPoint.x;
PointTop.y=PopPoint.y+1;//上方像素
PixelClr=pDC->GetPixel(Round(PointTop.x),
Round(PointTop.y));
if(BoundaryClr!=PixelClr && SeedClr!=PixelClr)
Push(PointTop); //上方像素入栈
PointRight.x=PopPoint.x+1;//右方像素
PointRight.y=PopPoint.y;
PixelClr=pDC->GetPixel(Round(PointRight.x),
Round(PointRight.y));
if(BoundaryClr!=PixelClr && SeedClr!=PixelClr)
Push(PointRight);//右方像素入栈
PointBottom.x=PopPoint.x;
PointBottom.y=PopPoint.y-1;//下方像素
PixelClr=pDC->GetPixel(Round(PointBottom.x),
Round(PointBottom.y));
if(BoundaryClr!=PixelClr && SeedClr!=PixelClr)
Push(PointBottom);//下方像素入栈
}
pDC->TextOut(rect.left+50,rect.bottom-20,"填充完毕");
delete pHead;
pHead = NULL;
}