计算机图形学——多边形的扫描转换(基本光栅图形算法)
一、多边形扫描转换
在光栅图形中,区域是由【相连的】像素组成的集合,这些像素具有【相同的】属性值或者它们位于某边界线的内部
1、光栅图形的一个基本问题是把多边形的顶点表示转换为点阵表示。这种转换成为多边形的扫描转换。
2、多边形的扫描转换与区域填充问题是怎样在离散的像素集上表示一个连续的二维图形。
3、多边形有两种重要的表示方法:
(1)顶点表示:用多边形的定点序列来表示多边形
优点:直观、几何意义强、占内存少、易于进行几何变换
缺点:没有明确指出那些象素在多边形内,故不能直接用于上色
(2)点阵表示:是用位于多边形内的象素集合来刻画多边形
缺点:丢失了许多几何信息(eg:边界、顶点等)
但是【点阵表示是光栅显示系统显示时所需的表现形式。】
多边形的扫描转换就是把多边形的顶点表示转换为点阵表示,即从多边形的给定边界出发,求出位于其内部的各个像素,并将帧缓冲器内的各个对应元素设置相应的灰度或颜色。实际上就是多边形内的区域的着色过程。
4、多边形分类
二、X扫描线算法
X扫描线算法填充多边形的基本思想是按扫描线顺序,计算扫描线与多边形的相交区间,再用要求的颜色显示这些区间的象素,即完成填充工作。
区间的端点可以通过计算扫描线与多边形边界线的交点获得。
如扫描线y=3与多边形的边界相交于4点(2,3)、(4,3)、(7,3)、(9,3)
这四个点定义了扫描线从x=2到x=4,从x=7到x=9两个落在多边形内的区间,该区间内像素应取填充色。
算法的核心是按x递增顺序排列交点的x坐标序列。由此可得到扫描线算法步骤如下:
算法步骤:
1.确定多边形所占有的最大扫描线数,得到多边形定点的最小最大值(ymin和ymax);
2.从ymin到ymax每次用一条扫描线进行填充;
3.对一条扫描线填充的过程分为四个步骤:
a)求交点;
b)把所有交点按递增顺序排序;
c)交点配对(第一个和第二个,第三个和第四个);
d)区间填色。把相交区间内的像素置成多边形的颜色,相交区间外的像素置成背景色。
扫描线与多边形顶点相交时,交点的取舍问题【交点应保证为偶数个】
交点问题的处理:
为了计算每条扫描线与多边形各边的交点,最简单的方法是把多边形的所有边放在一个表中。在处理每条扫描线的时候,按顺序从表中取出所有的边,分别与扫描线求交。
但这个算法效率很低
因为关键问题是求交! 求交是很可怕的,求交的计算量非常大。‘
排序、配对、填色总是要的!
三、X扫描线算法的改进
扫描转换算法重要的意义是提出了图形学里两个重要的思想:
(1)扫描线:当处理图形图像时按一条条扫描线处理
(2)增量的思想。
已经知道X-扫描线算法效率低是因为求交麻烦,那求教点的时候能否也采用增量思想,每条扫描线的y值都知道,关键是求x值。
可以从三个方面改进:
1、在处理一条扫描线时,仅对与它相交的多边形的边(有效边)进行求交运算。
2、考虑扫描线的连贯性,也就是当前扫描线与各边交点顺序与下一条扫描线边与各边的交点顺序很可能相同或非常相似。
3、考虑多边形的连贯性,即当某条边与扫描线相交时它很可能与下一条扫描线也相交。
为了避免求交运算,需要引进一套特殊的数据结构
数据结构:
(1)活性边表(AET)
把当前扫描线相交的边称为活性边,并把它们按与扫描线交点x坐标递增的顺序存放在一个链表中。
上图中P4P1、P3P2是活性边,P4P3、P1P2是非活性边
(2)节点内容
x:当前扫描线与边的交点坐标
Δx:从当前扫描线到下一条扫描线间x的增量
ymax:该边所交的最高扫描线的坐标值
next:指向下一条边的指针
另外,需要知道一条边何时不再与下一条扫描线相交,以便及时把它从有效边表中删除出去,避免下一步进行无谓的计算。
一个具体的例子:
(3)新边表(NET)
为了方便活性边表的建立与更新,用来存放多边形的边的信息,分为4个步骤
1.构造一个纵向链表,长度为多边形所占有的最大扫描线数,链表的每个结点称为吊桶,对应多边形覆盖的每一条扫描线。
2.新边表(NET)挂在与【该边低端y值相同】的扫描线桶中。也就是说,存放在该扫描线第一次出现的边
也就是说,如果某边的较低端点为ymin,则该边就放在扫描线ymin的新边表中。
注意:水平边 不放到任何扫描线的NEL中,即水平边不参与分类。
ymax:该边的最大值;
xmin:该边较低点的x坐标值xmin;
1/k:该边的斜率
next:指向下一条具有相同较低端y坐标的边的指针
从上边的这个NET表里就知道多边形是从哪里开始的
从这个表里只有1、3、5、7处有边,从y=1开始做,而1这条线上有两条边进来了,然后就把这两条边放入活性表来处理。
3.每做一次新的扫描时,要对已有的边进行三个处理:
1.是否被去除掉;
2.若不被去除掉,就要对它的数据进行更新,x=x+1/k;
3.是否有新的边进来,新的边在NET里,可以插入排序插进来。
这个算法过程从来没有求交,这套数据结构使得你不用求交点!避免了求交运算。
小结:
为提高算法效率:
(1)增量的思想;
(2)连贯性思想;
(3)构建一套特殊的数据结构。
【缺点】这里的区间端点通过计算扫描线与多边形边界的交点获得,所以待填充区域的边界线必须预先知道,因此它的缺点所在是无法实现对未知边界的区域填充。
四、边缘填充算法
基本原理
采用对图像进行逐位求反的方法,免去对边排序的工作量。
对颜色M作偶数次求反运算,其结果还是M,而对M作奇数次求反运算的结果是M的反M 。在光栅图形中,如某区域已着上值为M的某种颜色,则上述求反运算得到的结果是:对区域作偶数次求反运算后,该区域的颜色不变;作奇数次求反运算后,该区域的颜色则变成值为反M的颜色。
边缘填充算法的实现
实现:对多边形P的每一非水平边上的各像素做向右求反运算即可,见下图,其中(a)为给定的多边形;(b)为对区域赋初值;(c),(d),(e)和(f)表示逐边向右求反。
五、边界标志算法
基本原理:
首先用一种特殊的颜色在帧缓冲器中将多边形的边界(水平边的部分边界除外)勾画出来。然后再把位于多边形内的各个像素着上所需的颜色。
实现原理:
步骤1:以值为boundary-color 的特殊颜色勾画多边形P的边界。设多边形顶点为Pi= (xi, yi),0≤i≤n, xi, yi均为整数;置Pn+1=P0。每一条扫描线上着上这种特殊颜色的点的个数必定是偶数(包括零)。
pixels[ ]是一个整数数组,表示像素的颜色。
public Image boundary() { Image image; for(i=0; i<=n; i++) //多边形的边数 { dy=p[i+1].y-p[i].y; dx=(p[i+1].x-p[i].x)/dy; if(dy>0) //水平边不考虑 { x=p[i].x; } else { x=p[i+1].x; } ymax=(Math.max(p[i].y,p[i+1].y));//确定边的上下端 ymin=(Math.min(p[i].y,p[i+1].y)); for (y=ymin+1; y<=ymax ; y++ ) { x=(int)(x+dx+.5); if(pixels[y*w+x]==blue)//边界颜色蓝色 { pixels[y*w+x+1]=blue; } else { pixels[y*w+x]=blue; } } } ImageProducer ip = new MemoryImageSource(w,h,pixels,0,w); //通过MemoryImageSource将数组中的像素产生一个图像,w,h分别表其宽度和高度 image = createImage(ip); return image; }
步骤2:设in_flag是一布尔变量。对每一条扫描线从左到右进行搜索,如果当前是像素位于多边形P内,则in_flag=true,需要填上值为polygon_color的颜色;否则该像素在多边形P外,需要填上值为background_color的颜色。
//maxx、maxy、minx、miny是获得的多边 形最小矩形包围盒边界值 public Image interior() { Image image; int maxx=150,minx=20,maxy=120,miny=20,l; for(y = miny - 1 ; y<= maxy ; y++) { in_flag = 0; //多边形内部标志变量 for( x = minx - 1; x<=maxx - 1; x++) { l = pixels[y*w+x];//获得当前像素颜色以判断是否是边界色 //多边形边界颜色blue if (l == blue) { if (in_flag == 0)//如果是边界,且第一/三次碰到边界,就置flag为1,表明其后的象素要置成多边形的颜色 { in_flag = 1; } else//如果是第二/四次遇到边界,就置flag为0,对其后的元素置背景色为白色。 { in_flag = 0; } } if (in_flag==1) { pixels[y*w+x]=blue; } //在多边形内部填充色蓝色 else { pixels[y*w+x]=white; } } } }