实验六 扫描线填充算法
扫描线填充算法讲解

扫描线算法(S c a n-L i n e F i l l i n g)扫描线算法适合对矢量图形进行区域填充,只需要直到多边形区域的几何位置,不需要指定种子点,适合计算机自动进行图形处理的场合使用,比如电脑游戏和三维CAD软件的渲染等等。
对矢量多边形区域填充,算法核心还是求交。
《计算几何与图形学有关的几种常用算法》一文给出了判断点与多边形关系的算法――扫描交点的奇偶数判断算法,利用此算法可以判断一个点是否在多边形内,也就是是否需要填充,但是实际工程中使用的填充算法都是只使用求交的思想,并不直接使用这种求交算法。
究其原因,除了算法效率问题之外,还存在一个光栅图形设备和矢量之间的转换问题。
比如某个点位于非常靠近边界的临界位置,用矢量算法判断这个点应该是在多边形内,但是光栅化后,这个点在光栅图形设备上看就有可能是在多边形外边(矢量点没有大小概念,光栅图形设备的点有大小概念),因此,适用于矢量图形的填充算法必须适应光栅图形设备。
2.1扫描线算法的基本思想扫描线填充算法的基本思想是:用水平扫描线从上到下(或从下到上)扫描由多条首尾相连的线段构成的多边形,每根扫描线与多边形的某些边产生一系列交点。
将这些交点按照x坐标排序,将排序后的点两两成对,作为线段的两个端点,以所填的颜色画水平直线。
多边形被扫描完毕后,颜色填充也就完成了。
扫描线填充算法也可以归纳为以下4个步骤:(1)求交,计算扫描线与多边形的交点(2)交点排序,对第2步得到的交点按照x值从小到大进行排序;(3)颜色填充,对排序后的交点两两组成一个水平线段,以画线段的方式进行颜色填充;(4)是否完成多边形扫描?如果是就结束算法,如果不是就改变扫描线,然后转第1步继续处理;整个算法的关键是第1步,需要用尽量少的计算量求出交点,还要考虑交点是线段端点的特殊情况,最后,交点的步进计算最好是整数,便于光栅设备输出显示。
对于每一条扫描线,如果每次都按照正常的线段求交算法进行计算,则计算量大,而且效率底下,如图(6)所示:图(6)多边形与扫描线示意图观察多边形与扫描线的交点情况,可以得到以下两个特点:(1)每次只有相关的几条边可能与扫描线有交点,不必对所有的边进行求交计算;(2)相邻的扫描线与同一直线段的交点存在步进关系,这个关系与直线段所在直线的斜率有关;第一个特点是显而易见的,为了减少计算量,扫描线算法需要维护一张由“活动边”组成的表,称为“活动边表(AET)”。
区域填充的扫描线算法

计算机图形学——区域填充的扫描线算法NORTHWESTUNIVER SITY一、实验目的1.通过实验,进一步理解和掌握几种常用多边形填充算法的基本原理2.掌握多边形区域填充算法的基本过程3.掌握在C/C++环境下用多边形填充算法编程实现指定多边形的填充。
4.利用TC2.0编写区域填充的扫描线算法。
二、实验内容算法基本思想:首先填充种子点所在扫描线上位于区域内的区段,然后确定与该区段相邻的上下两条扫描线上位于区域内的区段,并依次将各区段的起始位置保存, 这些区段分别被用区域边界色显示的像素点所包围。
随后,逐步取出一开始点并重复上述过程,直到所保存各区段都填充完毕为止。
算法描述:扫描线填充算法一般包括四个步骤:求交、排序、交点配对、区域填充。
正确求得扫描线与区域填内外轮廓线的交点是算法成败的关键问题。
另一方面,采用合适的数据结构又可以简化操作、提高算法的效率。
本论文由于采用链表结构记录轮廓线和交点,无需焦点排序的过程,因而提高了算法效率。
扫描线来源于光栅显示器的显示原理:对于屏幕上所有待显示像素的信息,将这些信息按从上到下、自左至右的方式显示。
扫描线多边形区域填充算法是按扫描线顺序,计算扫描线与多边形的相交区间,再用要求的颜色显示这些区间的象素,即完成填充工作。
区间的端点可以通过计算扫描线与多边形边界线的交点获得。
对于一条扫描线,多边形的填充过程可以分为四个步骤:(1)求交:计算扫描线与多边形各边的交点;(2)排序:把所有交点按x值递增顺序排序;(3)配对:第一个与第二个,第三个与第四个等等;每对交点代表扫描线与多边形的一个相交区间;(4)填色:把相交区间内的象素置成多边形颜色;三、实验原理扫描线填充算法的基本过程如下:当给定种子点(x,y)时,首先填充种子点所在扫描线上的位于给定区域的一个区段,然后确定与这一区段相连通的上、下两条扫描线上位于给定区域内的区段,并依次保存下来。
反复这个过程,直到填充结束。
扫描线填充算法讲解

扫描线算法(Scan-Line F illing)扫描线算法适合对矢量图形进行区域填充,只需要直到多边形区域的几何位置,不需要指定种子点,适合计算机自动进行图形处理的场合使用,比如电脑游戏和三维CAD软件的渲染等等。
对矢量多边形区域填充,算法核心还是求交。
《计算几何与图形学有关的几种常用算法》一文给出了判断点与多边形关系的算法――扫描交点的奇偶数判断算法,利用此算法可以判断一个点是否在多边形内,也就是是否需要填充,但是实际工程中使用的填充算法都是只使用求交的思想,并不直接使用这种求交算法。
究其原因,除了算法效率问题之外,还存在一个光栅图形设备和矢量之间的转换问题。
比如某个点位于非常靠近边界的临界位置,用矢量算法判断这个点应该是在多边形内,但是光栅化后,这个点在光栅图形设备上看就有可能是在多边形外边(矢量点没有大小概念,光栅图形设备的点有大小概念),因此,适用于矢量图形的填充算法必须适应光栅图形设备。
2.1扫描线算法的基本思想扫描线填充算法的基本思想是:用水平扫描线从上到下(或从下到上)扫描由多条首尾相连的线段构成的多边形,每根扫描线与多边形的某些边产生一系列交点。
将这些交点按照x坐标排序,将排序后的点两两成对,作为线段的两个端点,以所填的颜色画水平直线。
多边形被扫描完毕后,颜色填充也就完成了。
扫描线填充算法也可以归纳为以下4个步骤:(1)求交,计算扫描线与多边形的交点(2)交点排序,对第2步得到的交点按照x值从小到大进行排序;(3)颜色填充,对排序后的交点两两组成一个水平线段,以画线段的方式进行颜色填充;(4)是否完成多边形扫描?如果是就结束算法,如果不是就改变扫描线,然后转第1步继续处理;整个算法的关键是第1步,需要用尽量少的计算量求出交点,还要考虑交点是线段端点的特殊情况,最后,交点的步进计算最好是整数,便于光栅设备输出显示。
对于每一条扫描线,如果每次都按照正常的线段求交算法进行计算,则计算量大,而且效率底下,如图(6)所示:图(6)多边形与扫描线示意图观察多边形与扫描线的交点情况,可以得到以下两个特点:(1)每次只有相关的几条边可能与扫描线有交点,不必对所有的边进行求交计算;(2)相邻的扫描线与同一直线段的交点存在步进关系,这个关系与直线段所在直线的斜率有关;第一个特点是显而易见的,为了减少计算量,扫描线算法需要维护一张由“活动边”组成的表,称为“活动边表(AET)”。
扫描线填充算法试验报告

扫描线填充算法试验报告1.概要设计:使用的算法是扫描线填充算法,其中建立了五个类,类Application1是JBuilder9自动生成的主类,类POINT包含顶点的坐标信息(x,y),类Edge包含每条边的信息(ymax,xmin,1/m),而类Bc是在类Frame1利用Collections.sort()排序时的继承自类Comparator 的一个类,Frame1是主界面,算法的主要实现都在此类中。
Frame1中的createET()是建立吊桶的函数,scan()是扫描填充的函数。
其中createET()和scan()采用书上的数据结构和算法。
createET()中数据结构如下:1.对每条边建立对象2.将对象存到一个链表中3.在将这个链表存到另一个链表中4.循环以上操作以建立ET表5.调整这时的链表得到真正的ET表scan()中算法如下:1.计算所有的多边形顶点中y的最大和最小值,以此作为扫描线的处理范围2.对处理范围内的每条扫描线建立有序边表3.对处理范围内的每条扫描线,重复下列步骤:(1)用有序边表建立当前扫描线的活化边表(2)从活化边表中依次取出一对交点画线(3)为下一条扫描线更新活化边表,即增加交点的x值和删除不再相交的边(4)重排活化边表2.实现1.建立吊桶:每个点(POINT)都和自己前后的点相比,以y值从小到大将边(Edge)存到一个ArrayList中,其中要求出这条边的斜率。
在一个for循环语句中,将上面建立的对应每个顶点的边表加入另一个ArrayList中,如果没有边表,则加入一个空的ArrayList。
调整整个ET表,将非极值点的ymax值相上移一格。
2.扫描算法:令y=yminAET表中置ET表中的边While(AET.size()!=0)循环:Collections.sort(AET,new Bc() )对每对奇偶点画线if 扫描线y=ymax 从AET表中删除这些边对于其他边x=x+1/my++按照y的值再次把边从ET表中加入AET表中循环结束算法结束3.心得体会这次的作业主要是参照书上的算法,自己并没有做过多的改进,感觉上程序较慢,这可能与1. Java的速度较慢;2. 程序中生成的对象较多有关。
扫描线填色算法

扫描线填色算法扫描线填色算法1、基本原理见幻灯2、多边形区域填充步骤(1)输入欲填充多边形的顶点数及其顶点坐标。
这里,顶点数为实际顶点数加1,最后一个顶点坐标与第一个顶点坐标相同。
(2)计算所有多边形顶点坐标中y的最大值和最小值,以此作为扫描线的处理范围。
(3)对处理范围内的每条扫描线建立有序边表。
(4)对处理范围内的每条扫描线重复下列步骤:A. 用有序边表建立当前扫描线的活化边表;B. 从活化边表中依次取出一对交点,对该两个交点内的像素进行填充;C. 为下一条扫描线更新活化边表,即增加交点的x值和删除不再相交的边。
D. 重排活化边表。
3、数据结构活化边表结点数据结构可以定义为:typedef struct tEdge{float x ; /*当前扫描线与边的交点的x值*/float dx ; /*从当前扫描线到下一条扫描线之间的x增量*/int ymax ; /*边所交的最高扫描线号*/}Edge;4、有序边表填充算法的C语言描述#include"graphics.h"#define WINDOW_HEIGHT 480#define NULL 0typedef struct tEdge /*设置有序边表和活化边表数据结构*/{int ymax;float x, dx;struct tEdge *next;}Edge;typedef struct point{ int x,y;}POINT;/*按交点x的升序对边进行插入排序*/void InsertEdge(Edge * list,Edge * edge) /*list为有序边表,edge为待插入的边*/{Edge * p,*q=list;p=q->next;while(p!=NULL){ if(edge->xx)p=NULL; /*停止循环,edge应插入到链表的头*/else /* 遍历list */{ q=p;p=p->next;}}edge->next=q->next; /*q指向edge应插入位置的前一个结点,edge插入到q之后*/q->next=edge;}/*计算下一条非水平线的y值*/int yNext(int k,int cnt,POINT * pts){int j;if((k+1)>(cnt-1))j=0;elsej=k+1;while(pts[k].y==pts[j].y)if((j+1)>(cnt-1))j=0;elsej++;return(pts[j].y);}/*生成有序边表的一条边*/void MakeEdgeRec(POINT lower,POINT upper,int yComp,Edge * edge,Edge * edges[]){ edge->dx=(float)(upper.x-lower.x)/(upper.y-lower.y);edge->x=lower.x;if(upper.y<ycomp)< bdsfid="129" p=""></ycomp)<>edge->ymax=upper.y-1;elseedge->ymax=upper.y;InsertEdge(edges[lower.y],edge);}/* 建立有序边表,每次从顶点数组中取出两个点,调用MakeEdgeRec来生成一条边,并调用InsertEdge把建好的边插入到有序边表中*/void BuildEdgeList(int cnt,POINT * pts, Edge * edges[]){Edge *edge;POINT v1,v2;int i,yPrev=pts[cnt-2].y;v1.x=pts[cnt-1].x;v1.y=pts[cnt-1].y;for(i=0;i<cnt;i++)< bdsfid="144" p=""></cnt;i++)<>{ v2=pts[i];if(v1.y!=v2.y) /* nonhorizontal line */{edge=(Edge *)malloc(sizeof(Edge));if(v1.y<="" bdsfid="150" edge="" p="">MakeEdgeRec(v1,v2,yNext(i,cnt,pts),edge,edges);else /*down-going edge */MakeEdgeRec(v2,v1,yPrev,edge,edges);}yPrev=v1.y;v1=v2;}}void BuildActiveList(int scan,Edge * active,Edge * edges[]) /*建立活化边表*/{Edge *p,*q;p=edges[scan]->next;while(p){ q=p->next;InsertEdge(active,p);p=q;}}void FillScan(int scan,Edge * active,int color) /* 对当前扫描线填充*/{Edge *p1, *p2;int i;p1=active->next;while(p1){p2=p1->next;for(i=p1->x;ix;i++)putpixel((int)i,scan,color);p1=p2->next;}}void DeleteAfter(Edge *q) /* 删除不再相交的边*/{Edge *p=q->next;q->next=p->next;free(p);}/*Delete completed edges. Update 'xIntersect' field for others */void UpdateActiveList(int scan,Edge * active) /* 为下一条扫描线进行更新*/{Edge *q=active,*p=active->next;while(p)if(scan>=p->ymax) /* 如果当前扫描线scan的值大于p所指向的边的ymax,则删除该边*/{ p=p->next;DeleteAfter(q);}else /* 否则p所指向的边的x值加一个dx */{ p->x=p->x+p->dx;q=p;p=p->next;}}void ResortActiveList(Edge * active) /* 重排活化边表*/{Edge *q,*p=active->next;active->next=NULL;while(p){ q=p->next;InsertEdge(active,p);p=q;}}void ScanFill(int cnt,POINT *pts,int color) /* cnt为多边形的顶点数,pts 为顶点坐标数组*/{Edge * edges[WINDOW_HEIGHT],*active;int i, scan, scanmax = 0, scanmin = WINDOW_HEIGHT;for (i = 0; i < cnt-1; i++) /* 求填充区域的扫描线最大值scanmax 和最小值scanmin*/{ if (scanmax < pts [i].y ) scanmax = pts [i].y;if (scanmin > pts [i].y ) scanmin = pts [i].y;}for(scan=scanmin;scan<=scanmax;scan++) /*初始化每条扫描线的边链表*/{ edges[scan]=(Edge *)malloc(sizeof(Edge));edges[scan]->next=NULL;}BuildEdgeList(cnt,pts,edges); /* 用顶点坐标pts建立有序边表,放在edges中*/active=(Edge *)malloc(sizeof(Edge)); /* 初始化活化边表*/active->next=NULL;for(scan=scanmin;scan<=scanmax;scan++){BuildActiveList(scan,active,edges); /* 建立当前扫描线scan的活化边表*/if(active->next){ FillScan(scan,active,color); /* 填充当前扫描线*/UpdateActiveList(scan,active); /*为下一条扫描线更新活化边表*/ResortActiveList(active); /*重排活化边表*/}}}。
扫描线种子填充算法

扫描线种子填充算法扫描线种子填充算法的基本过程如下:当给定种子点(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)步;这个算法中最关键的是第(4)步,就是从当前扫描线的上一条扫描线和下一条扫描线中寻找新的种子点。
如果新扫描线上实际点的区间比当前扫描线的[xLeft, xRight]区间大,而且是连续的情况下,算法的第(3)步就处理了这种情况。
如图所示:新扫描线区间增大且连续的情况假设当前处理的扫描线是黄色点所在的第7行,则经过第3步处理后可以得到一个区间[6,10]。
然后第4步操作,从相邻的第6行和第8行两条扫描线的第6列开始向右搜索,确定红色的两个点分别是第6行和第8行的种子点,于是按照顺序将(6, 10)和(8, 10)两个种子点入栈。
接下来的循环会处理(8, 10)这个种子点,根据算法第3步说明,会从(8, 10)开始向左和向右填充,由于中间没有边界点,因此填充会直到遇到边界为止,所以尽管第8行实际区域比第7行的区间[6,10]大,但是仍然得到了正确的填充。
扫描线填充算法讲解

扫描线算法(Scan-Line F illing)扫描线算法适合对矢量图形进行区域填充,只需要直到多边形区域的几何位置,不需要指定种子点,适合计算机自动进行图形处理的场合使用,比如电脑游戏和三维CAD软件的渲染等等。
对矢量多边形区域填充,算法核心还是求交。
《计算几何与图形学有关的几种常用算法》一文给出了判断点与多边形关系的算法――扫描交点的奇偶数判断算法,利用此算法可以判断一个点是否在多边形内,也就是是否需要填充,但是实际工程中使用的填充算法都是只使用求交的思想,并不直接使用这种求交算法。
究其原因,除了算法效率问题之外,还存在一个光栅图形设备和矢量之间的转换问题。
比如某个点位于非常靠近边界的临界位置,用矢量算法判断这个点应该是在多边形内,但是光栅化后,这个点在光栅图形设备上看就有可能是在多边形外边(矢量点没有大小概念,光栅图形设备的点有大小概念),因此,适用于矢量图形的填充算法必须适应光栅图形设备。
扫描线算法的基本思想扫描线填充算法的基本思想是:用水平扫描线从上到下(或从下到上)扫描由多条首尾相连的线段构成的多边形,每根扫描线与多边形的某些边产生一系列交点。
将这些交点按照x坐标排序,将排序后的点两两成对,作为线段的两个端点,以所填的颜色画水平直线。
多边形被扫描完毕后,颜色填充也就完成了。
扫描线填充算法也可以归纳为以下4个步骤:(1)求交,计算扫描线与多边形的交点(2)交点排序,对第2步得到的交点按照x值从小到大进行排序;(3)颜色填充,对排序后的交点两两组成一个水平线段,以画线段的方式进行颜色填充;(4)是否完成多边形扫描?如果是就结束算法,如果不是就改变扫描线,然后转第1步继续处理;整个算法的关键是第1步,需要用尽量少的计算量求出交点,还要考虑交点是线段端点的特殊情况,最后,交点的步进计算最好是整数,便于光栅设备输出显示。
对于每一条扫描线,如果每次都按照正常的线段求交算法进行计算,则计算量大,而且效率底下,如图(6)所示:图(6)多边形与扫描线示意图观察多边形与扫描线的交点情况,可以得到以下两个特点:(1)每次只有相关的几条边可能与扫描线有交点,不必对所有的边进行求交计算;(2)相邻的扫描线与同一直线段的交点存在步进关系,这个关系与直线段所在直线的斜率有关;第一个特点是显而易见的,为了减少计算量,扫描线算法需要维护一张由“活动边”组成的表,称为“活动边表(AET)”。
计算机图形学实验扫描线种子填充算法

实验二4-10一、实验题目扫描线种子填充算法是通过扫描线来填充多边形内的水平像素段,处理每条扫描线时仅需将其最右端像素入栈,可以有效提高填充效率。
请使用MFC编程填充图4-60所示的空心体汉字(四连通),填充效果如图4-61所示。
二、实验思想扫描线种子填充算法:先将种子像素入栈,种子像素为栈底像素,如果栈不为空,执行如下4步操作。
(1)栈顶像素出栈。
(2)沿扫描线对出栈像素的左右像素进行填充,直至遇到边界像素为止。
即每出栈一个像素,就对区域内包含该像素的整个连续区间进行填充。
(3)同时记录该区间,将区间最左端像素记为x left,最右端像素记为x right。
(4)在区间〔x left,x right〕中检查与当前扫描线相邻的上下两条扫描线的有关像素是否全为边界像素或已填充像素,若存在非边界且未填充的像素,则把未填充区间的最右端像素取作种子像素入栈。
三、实验代码void CTestView::OnLButtonDown(UINT nFlags, CPoint point)//左键按下函数{// TODO: Add your message handler code here and/or call defaultSeed=point;//选择种子位置CharFill();//进行填充CView::OnLButtonDown(nFlags, point);}void CTestView::CharFill()//文字填充函数{CRect Rect;GetClientRect(&Rect);CClientDC dc(this);COLORREF BoundColor;//边界色int Width=Rect.right-Rect.left;int Hight=Rect.bottom-Rect.top ;int Flag;int x0,y0,x,y;CPoint Point;std::vector<CPoint> FillBuffle;//定义CPoint类型的数组序列对象FillBuffle.reserve(10);//定义数组序列的大小FillBuffle.push_back(CPoint(Seed)); //把种子结点压入数组序列BoundColor=RGB(0,0,0);//定义边界色为黑色while(!FillBuffle.empty())//如果数组序列非空{Point=FillBuffle.front();//弹出数组序列头元素x=Point.x;y=Point.y;FillBuffle.erase(FillBuffle.begin());//清除数组序列内的元素dc.SetPixel(Point,Fillcolor);//绘制像素//判断像素的位置是否在图形内部x0=x+1;//右方判断while(dc.GetPixel(x0,y)!=BoundColor&&dc.GetPixel(x0,y)!=Fillcolor) {x0=x0+1;if(x0>=Width)//到达屏幕最右端{MessageBox("种子超出范围","警告");RedrawWindow();return;}}y0=y+1;//下方判断while(dc.GetPixel(x,y0)!=BoundColor&&dc.GetPixel(x,y0)!=Fillcolor) {y0=y0+1;if(y0>=Hight)//到达屏幕最下端{MessageBox("种子超出范围","警告");RedrawWindow();return;}}RightPoint.x=x0;//右边界内的左邻点x0=x-1;while(dc.GetPixel(x0,y)!=Fillcolor&&dc.GetPixel(x0,y)!=BoundColor){dc.SetPixel(x0,y,Fillcolor);x0=x0-1;if(x0<=0)//到达屏幕最左端{MessageBox("种子超出范围","警告");RedrawWindow();return;}}y0=y-1;while(dc.GetPixel(x,y0)!=BoundColor&&dc.GetPixel(x,y0)!=Fillcolor){y0=y0-1;if(y0<=0)//到达屏幕最上端{MessageBox("种子超出范围","警告");RedrawWindow();return;}}LeftPoint.x=x0+1;//左边界内的右邻点x0=LeftPoint.x;y=y+1;//下一条扫描线while(x0<RightPoint.x){Flag=0;while((dc.GetPixel(x0,y)!=Fillcolor)&&(dc.GetPixel(x0,y)!=BoundColor)) {if(Flag==0)Flag=1;x0++ ;}if(Flag==1){if((x0==RightPoint.x)&&(dc.GetPixel(x0,y)!=Fillcolor)&&(dc.GetPixel(x0,y)!=BoundColor))FillBuffle.push_back(CPoint(x0,y));//进入数组序列else{FillBuffle.push_back(CPoint(x0-1,y));}Flag=0;}PointNext.x=x0;while(((dc.GetPixel(x0,y)==Fillcolor)&&(x0<RightPoint.x))||((dc.GetPixel(x0,y)==BoundColor) &&(x0<RightPoint.x))){x0 ++;}}x0=LeftPoint.x;y=y-2;while(x0<RightPoint.x){Flag=0;while((dc.GetPixel(x0,y)!=Fillcolor)&&(dc.GetPixel(x0,y)!=BoundColor)&&(x0<RightPoint.x)) {if(Flag==0)Flag=1;x0++ ;}if(Flag==1){if((x0==RightPoint.x)&&(dc.GetPixel(x0,y)!=Fillcolor)&&(dc.GetPixel(x0,y)!=BoundColor))FillBuffle.push_back(CPoint(x0,y));else{FillBuffle.push_back(CPoint(x0-1,y));}Flag=0;}PointNext.x=x0;while((dc.GetPixel(x0,y)==Fillcolor&&x0<RightPoint.x)||(dc.GetPixel(x0,y)==BoundColor&&x 0<RightPoint.x)){x0++;}}}FillBuffle.clear();return;}void CTestView::OnMENUFill(){// TODO: Add your command handler code hereRedrawWindow();MessageBox("请在空心字体内部单击鼠标左键!","提示");}四、实验结果截图。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
实验六扫描线填充算法一、实验目的编写多边形的扫描线填充算法程序,加深对扫描线算法的理解,验证算法的正确性。
二、实验任务(2学时)编写多边形的扫描线填充算法程序,利用数组实现AET,考虑与链表实现程序的不同。
三、实验内容1、算法对一条扫描线的填充一般分为以下4个步骤:(1)求交:计算扫描线与多边形各边的交点;(2)排序:把扫描线上所有交点按递增顺序进行排序;(3)配对:将第一个交点与第二个交点,第三个交点与第四个交点等等进行配对,每对交点代表扫描线与多边形的一个相交区间。
(4)着色:把区间内的像素置为填充色。
2、成员函数的关系主程序名为fill_area(count, x, y),其中参数x, y是两个一维数组,存放多边形顶点(共c ount个)的x和y坐标。
它调用8个子程序,彼此之间的调用关系图1所示为:图1 fill_area的程序结构3、算法的程序设计步骤1:创建“S_L_Fill”工程文件;步骤2:创建类class:“EACH_ENTRY”。
在工作区“S_L_Fill classes”单击右键-→“new class”-→选择类型“Generic Class”名称为“EACH_ENTRY”,添加成员变量(添加至“class EACH_ENTRY { public:”之内):int y_top;float x_int;int delta_y;float x_change_per_scan;步骤3:包含头文件,同时初始化定义多边形顶点数目。
在“class CS_L_FillView : public Cview……”之前添加代码“#include EACH_ENTRY.h”及“#define MAX_POINT 9”。
#define MAX_POINT 9#include "EACH_ENTRY.h"步骤4:在类“class CS_L_FillView”中添加成员变量(鼠标双击工作区“CS_L_FillView”,代码添加至“class CS_L_FillView : public Cview {protected: ……public:之后”):EACH_ENTRY sides[MAX_POINT];int x[MAX_POINT],y[MAX_POINT];int side_count,first_s,last_s,scan,bottomscan,x_int_count;步骤5:利用构造函数“CS_L_FillView::CS_L_FillView()”初始化顶点坐标(鼠标双击工作区“CS_L_FillView”,代码添加至“CS_L_FillView()之内”):x[0]=200;y[0]=100;x[1]=240;y[1]=160;x[2]=220;y[2]=340;x[3]=330;y[3]=100;x[4]=400;y[4]=180;x[5]=300;y[5]=400;x[6]=170;y[6]=380;x[7]=120;y[7]=440;x[8]=100;y[8]=220;步骤6:在“class CS_L_FillView”下添加实现不同功能的成员函数。
在工作区“CS_L_FillView”上单击鼠标右键,选择“Add Member Function”,分别完成以下成员函数的添加:(1)void put_in_sides_list(int entry,int x1,int y1,int x2,int y2,int next_y)函数说明:put_in_sides_list子程序的主要功能是将一条边存入活性边表之内。
操作步骤是:对该边判别是否左顶点或右顶点,如果将入边之终点删去,按照y_top的大小在活性边表中找到该点的合适位置,y值较大者,排在活性边表的靠前位置。
void put_in_sides_list(int entry,int x1,int y1,int x2,int y2,int next_y)// entry为剔除水平边之后的第entry条边,x1, y1,为起点,x2, y2为终点,next_y为终点相邻的下一个顶点y坐标{int maxy;float x2_temp,x_change_temp;x_change_temp=(float)(x2-x1)/(float)(y2-y1);//计算1/kx2_temp=float(x2);if((y2>y1)&&(y2<next_y))//x2,y2是左顶点,则(x2-1/m,y2-1)终点下缩{y2--;x2_temp-=x_change_temp;}else{if((y2<y1)&&(y2>next_y)) //x2,y2是右顶点,则(x2+1/m,y2+1)终点上缩{y2++;x2_temp+=x_change_temp;}}maxy=(y1>y2)?y1:y2;while((entry>1)&&(maxy>sides[entry-2].y_top)){sides[entry-1]=sides[entry-2];entry--;}// sides[]为边数组,边的y_top值越小,在数组中越靠后sides[entry-1].y_top=maxy;sides[entry-1].delta_y=abs(y2-y1)+1;if(y1>y2)// x2,y2为右顶点,扫描线与起点先求交sides[entry-1].x_int=float(x1);else// x2,y2左顶点,扫描线与终点先求交sides[entry-1].x_int=x2_temp;sides[entry-1].x_change_per_scan=x_change_temp;}(2)void sort_on_bigger_y(int n,CDC* pDC)函数说明:sort_on_bigger_y子程序的主要功能是按照输入的多边形,建立起活性边表。
操作步骤是:对每条边加以判断:如非水平边则调用put_in_side_list子程序放入活性边来;如是水平边则直接画出。
void sort_on_bigger_y(int n,CDC* pDC)//按照输入的多边形建立活性链表{int k,x1,y1;side_count=0;//全局变量,记录所有非水平边数目y1=y[n-1];x1=x[n-1];//(Pn-1,P0)为第一条边,开始建表bottomscan=y[n-1];for(k=0;k<n;k++)//sides数组存放所有非水平边,并且按照y值的由大到小顺序{if(y1!=y[k]&&(k+1)<=(n-1)){side_count++;put_in_sides_list(side_count,x1,y1,x[k],y[k],y[k+1]);}if(y1!=y[k]&&(k+1)==n)//当前边非水平边{side_count++;put_in_sides_list(side_count,x1,y1,x[k],y[k],y[0]);}if(y1==y[k])//如果平行就直接画出直线{side_count++;CPen myPen(PS_DASH,2,RGB(255,0,0));pDC->SelectObject(&myPen);pDC->MoveTo((short)x1,(short)y1);pDC->LineTo((short)x[k],(short)y[k]);}if(y[k]<bottomscan)// bottomscan为全局变量,为所有顶点的最小y值bottomscan=y[k];y1=y[k];x1=x[k];} //end for}(3)void update_first_and_last(int count,int scan)函数说明:update_first_and_last子程序的主要功能是刷新活性边表的first和last两根指针的所指位置,以保证指针指出激活边的范围。
void update_first_and_last(int count,int scan){//下一条边的y_top>当前扫描线scan,last_s下移while((sides[last_s+1].y_top>=scan)&&((last_s+1)<count))last_s++;while(sides[first_s].delta_y==0)first_s++;}(4)void swap(EACH_ENTRY *a,EACH_ENTRY *b)函数说明:swap子程序的主要功能是交换活性边表中两条相邻位置边的彼此位置。
void swap(EACH_ENTRY *a,EACH_ENTRY *b)//为使交换成功,需用引用或者指针。
{int i_temp;float f_temp;i_temp=a->y_top;a->y_top=b->y_top;b->y_top=i_temp;//y_top交换f_temp=a->x_int;a->x_int=b->x_int;b->x_int=f_temp;//x_int交换i_temp=a->delta_y;a->delta_y=b->delta_y;b->delta_y=i_temp;//delta_y交换f_temp=a->x_change_per_scan;a->x_change_per_scan=b->x_change_per_scan;b->x_change_per_scan=f_temp;//x_change_per_scan交换}(5)void sort_on_x(int entry,int first_s)函数说明:sort_on_x子程序主要功能是将一条边sides[retry]在活性表边的first到entry 之间按照x_int大小,递增排序。
操作步骤是:检查位于entry的边的x_int是否小于位置entry-1的边的x_int,如是,调用swap子程序交换两条边的彼此位置。
void sort_on_x(int entry,int first_s){int ent1=entry;int ent2=entry;while(1){ent1--;if((sides[ent2].x_int<sides[ent1].x_int)&&(sides[ent1].delta_y!=0))//如果两个的x_int需要交换则交换之{swap(&sides[ent2],&sides[ent1]);ent2=ent1;}if(ent1==first_s-1)break;}}(6)void process_x_intersections(int scan,int first_s,int last_s)函数说明:process_x_intersections子程序的主要功能是对活性边表中的激活边(即位于first和last之间的,并且delta_y> 0的边)按照x_int的大小排序。