第一:线(通过斜率 线上两个坐标确定线)
画斜率0
≤
k
≤1
的直线的Bresenham
画线算法的C
语言程序:
void BresenhamLine (int x
0, int y
0, int x
1, int y
1, long color)
{
int x, y, dx, dy;
float k, e;
dx = x
1-x
0;
dy = y
1- y
0;
e = -0.5;
x = x
0;
y = y
0;
if (dx = = 0)
{
for (i=0;i≤dy;i++)
{
DrawPixel (x, y+i, color);//画像素(x,y+i)
}
return;
}
k = dy/dx;
for (i=0;i≤dx;i++)
{
DrawPixel (x, y, color);//画像素(x,y)
x++;
e += k;
if (e≥0)
{
y++;
e--;
}
}
}
先考虑斜率k=dy/dx
≤1
的直线。如图2.1
所示,设直线
方程为
,其中,k
= dy/dx
。
假设当前像素的x
坐标已经确定为x
i
,其
y
坐标为y
i
,由于坐标(x
i
,y
i
)(i=0,1,
…)只能取整数,那么下一个像素的x
坐标
,而y
i
+1
的坐标有两种可能:
1)
保持不变,即y
i
+1
=y
i
;或者
2)
y
坐标递增1
,即y
i
+1
=y
i
+1
。
令
,
y
坐标是否增加1
取决于如图所示误差项d
i
的值。因为直线的起始点在像素中心,所以初始误差d
0
=0
。x
每增加1
,y
的值相应递增直线的斜率值
k
,即
。一旦d
i
+1
≥1
,就把它减去1
,这样保证
di
+1
在0
~1
之间。当d
i
+1
≥0.5
时,直线
与x
=x
i
+1
的垂线的交点最接近于当前像素(x
i
,y
i
)的右上方像素(x
i
+1
,y
i
+1
);而当d
i
+1<0.5
时,其交点更接近于(x
i
,y
i
)右边的像素(x
i
+1
,y
i
)。为方便计算,令e
0=
-0.5
,e
i
+1
=d
i
+1
-0.5
,增量为k
。当e
i
+1
≥0
时,取当前像素(x
i
,y
i
)的右上方像素(x
i
+1
,
y
i
+1
);而当e
i+1<0
时,更接近于右方像素(x
i
+1
,y
i
)。
第二 圆的基础知识
不失一般性,假设
圆的圆心位于坐标原点(如果圆心不在原点,可以通过坐标平移使其与原点重合),半径为R
。以原点为圆心的圆C
有四条对称轴:x=0,y=0,x=y
和x=-y
。若已知圆弧上一点P
1
=C
(x
, y
),利用其对称性便可以得到关于四条对称轴的其它7
个点,即:
P
2
=C
(x
,
-y
),
P3=C(-x, y),
P4=C(-x,-y),
P5=C(y,x),
P6=C(-y,x),
P7=C(y,-x),
P8=C(-y,-x)。
这种性质称为八对称性。因此,只要扫描转换八分之一圆弧,就可以通过圆弧的八对称性得到整个圆。
为了方便起见,考虑位于第一象限的四分之一圆弧。如果以点(0,R)为起点按顺时针方向生成圆,则在第一象限内y是x的单调递减函数。假设圆心和起点均精确地落在像素中心上。
如果已经知道圆弧上的一点(x,y),下一像素的选取有三种可能:正右方像素,右下角像素和正下方像素,分别用H,D和V表示,如图2.2所示。这三个像素的偏差的平方为:
。
令
,
。
如果 ,说明圆弧到D向像素的距离大于到H向像素的距离,因此,下一个像素应当取H向的像素(xi+1,yi);反之,下一个像素应当取D向的像素(xi+1,yi-1)。经过更进一步地分析后,可以得到:
如左 公式(2-2-1)
如左 公式(2-2-2)
我们可以按以下规则选取下一个像素作为圆弧的最佳逼近点:
- 当时,如果 ,则取为下一个像素点,否则取D为下一个像素点。
- 当 时,如果 ,则取D为下一个像素点,否则取V为下一个像素点;
- 当 时取D为下一个像素点。
|
为了提高计算速度,我们可以在Bresenham
画圆算法中采用只有加、减和移位(即:乘以2
)操作的递推公式如下:
a)
b)
c)
可见,只用加、减和移位操作便完全可以实现Bresenham画圆算法。和改进的Bre
senham画线算法一样,Bresenham画圆
算法具有很高的速度和效率,因此得到广泛的应用。
3椭圆
画椭圆
中心在原点、轴对齐的椭圆的非参数化方程为:
。
上式可用隐式方程表示为:
由于椭圆的对称性,仅考虑在第一象限的椭圆弧即可。椭圆弧的法向量计算公式为:
椭圆弧上斜率为-1的点将椭圆弧分为上、下部分,见图2.3所示。在上部分(区域2),法向量的y向分量较大,选择像素时增量Δy比较重要;在下部分(区域1),法向量的x分量较大,选择像素时增量Δx比较重要。下面我们分开进行讨论。
在区域2,设当前位置为点,下一个可能的点是像素点H和D,这时可构造判别式:
若<0,表示像素点H和D的中点在椭圆内,这时可取H为下一个像素点;若 >=0,表示像素点H和D的中点在椭圆外,这时应当取D为下一个像素点。所以,对于在区域2的椭圆弧,我们可以按左边的规则选取下一个像素作为椭圆弧的最佳逼近点:
画椭圆弧的规则:
如果 ,则取H为下一个像素点,这时有
新的判别式为:
。
否则 ,取D为下一个像素点,即
新的判别式为:
。
对于区域1的椭圆弧,同理可得:
如果 ,则选取下一个像素点的坐标为
新的判别式为:
。
否则 ,则选取下一个像素点的坐标为
新的判别式为:
。
对于其它三个象限的椭圆弧,可以利用对称性得到。
其它曲线
二次曲线的一般方程为
令
,
我们可以对二次曲线进行分类:
二次曲线也可以用参数方程表示为:
如果时,则r(t)是一条抛物线;当时,r(t)是一条双曲线;当时,r(t)是椭圆。
对于椭圆和圆弧,我们可以用前面的方法进行光栅化显示。对于双曲线,我们可以采用差分的方法进行光栅图形显示。
对于三次或三次以上的多项式曲线f(x,y)=0,可以采用递归空间子分算法进行光栅图形显示。其基本思想是:首先建立有顶点(
,
)和(
,
)构成的包围盒,如果曲线f(x,y)=0通过包围盒,而且包围盒的大小大于一个像素,则对包围盒再进行子分,直到包围盒只有一个像素大为止,然后用给定曲线的颜色输出;如果曲线f(x,y)=0不通过包围盒,则该区域用背景色显示,并忽略处理。
区域
填充
1) 多边形
由一系列首尾相连的直线段构成的图形称为多边形。如果在多边形内任意选取不相同的两点,其连线上的所有点均在该多边形内,这样的多边形称为凸多边形;否则,称为凹多边形。
2) 种子填充算法
种子填充算法又称为边界填充算法。其基本思想是:从多边形区域的一个内点开始,由内向外用给定的颜色画点直到边界为止。如果边界是以一种颜色指定的,则种子填充算法可逐个像素地处理直到遇到边界颜色为止。
种子填充算法常用四连通域和八连通域技术进行填充操作。
从区域内任意一点出发,通过上、下、左、右四个方向到达区域内的任意像素。用这种方法填充的区域就称为四连通域;这种填充方法称为四向连通算法。
从区域内任意一点出发,通过上、下、左、右、左上、左下、右上和右下八个方向到达区域内的任意像素。用这种方法填充的区域就称为八连通域;这种填充方法称为八向连通算法。
一般来说,八向连通算法可以填充四向连通区域,而四向连通算法有时不能填充八向连通区域。例如,八向连通填充算法能够正确填充如图2.4a所示的区域的内部,而四向连通填充算法只能完成如图2.4b的部分填充。
图2.4 四向连通填充算法
四向连通填充算法:
a) 种子像素压入栈中;
b) 如果栈为空,则转e);否则转c);
c) 弹出一个像素,并将该像素置成填充色;并判断该像素相邻的四连通像素是否为边界色或已经置成多边形的填充色,若不是,则将该像素压入栈;
d) 转b);
e) 结束。
四向连通填充方法可以用递归函数实现如下:
算法2.3 四向连通递归填充算法:
void BoundaryFill4(int x, int y, long FilledColor, long BoundaryColor)
{
long CurrentColor;
CurrentColor = GetPixelColor(x,y);
if (CurrentColor != BoundaryColor && CurrentColor != FilledColor)
{
SetColor(FilledColor);
SetPixel (x,y);
BoundaryFill4(x+1, y, FilledColor, BoundaryColor);
BoundaryFill4(x-1, y, FilledColor, BoundaryColor);
BoundaryFill4(x, y+1, FilledColor, BoundaryColor);
BoundaryFill4(x, y-1, FilledColor, BoundaryColor);
}
}
上述算法的优点是非常简单,缺点是需要大量栈空间来存储相邻的点。一个改进的方法就是:通过沿扫描线填充水平像素段,来处理四连通或八连通相邻点,这样就仅仅只需要将每个水平像素段的起始位置压入栈,而不需要将当前位置周围尚未处理的相邻像素都压入栈,从而可以节省大量的栈空间。
3) 其它填充算法
扫描线填充算法是另一个常用的多边形填充算法。其基本思想是:对于一个给定的多边形,用一组水平或垂直的扫描线进行扫描,分别求出每条扫描线与多边形的交点,这些交点将扫描线分割为相间排列的落在多边形内和多边形外的线段,将落在多边形内的所有线
段上的每个
像素点赋以给定的多边形填充色。具体算法可以参考第7章"消隐显示"的相关内容
点阵和矢量
点阵字符
在点阵字库中,每个字符由一个位图表示(如图2.5所示),并把它用一个称为字符掩膜的矩阵来表示,其中的每个元素都是一位二进制数,如果该位为1表示字符的笔画经过此位,该像素置为字符颜色;如果该位为0,表示字符的笔画不经过此位,该像素置为背景颜色。点阵字符的显示分为两步:首先从字库中将它的位图检索出来,然后将检索到的位图写到帧缓冲器中。
在实际应用中,同一个字符有多种字体(如宋体、楷体等),每种字体又有多种大小型号,因此字库的存储空间十分庞大。为了减少存储空间,一般采用压缩技术。
图2.5 字符的点阵表示和矢量轮廓表示
、
2) 矢量字符
矢量字符记录字符的笔画信息而不是整个位图,具有存储空间小,美观、变换方便等优点。例如:在AutoCAD中使用图形实体-形(Shape)-来定义矢量字符,其中,采用了直线和圆弧作为基本的笔画来对矢量字符进行描述。
对于字符的旋转、放大、缩小等几何变换,点阵字符需要对其位图中的每个象素进行变换,而矢量字符则只需要对其几何图素进行变换就可以了,例如:对直线笔画的两个端点进行变换,对圆弧的起点、终点、半径和圆心进行变换等等。
矢量字符的显示也分为两步。首先从字库中将它的字符信息。然后取出端点坐标,对其进行适当的几何变换,再根据各端点的标志显示出字符。
轮廓字形法是当今国际上最流行的一种字符表示方法,其压缩比大,且能保证字符质量。轮廓字形法采用直线、B样条/Bezier曲线的集合来描述一个字符的轮廓线。轮廓线构成一个或若干个封闭的平面区域。轮廓线定义加上一些指示横宽、竖宽、基点、基线等等控制信息就构成了字符的压缩数据。
光栅图形反走样基础
Bresenham直线算法生成的直线图形一般都呈阶梯状(见图2.1),实际上,这是光栅图形的一种走样现象。这种走样现象是由于采用离散量表示连续量引起的。通常,我们把由离散量表示连续量引起的失真称为走样;把减少或克服走样效果的技术称为反走样技术,简称反走样。
光栅图形的走样有如下几种:
a) 产生阶梯或锯齿形;
b) 细节或纹理绘制失真;
c) 狭小图形遗失;
d) 实时动画忽隐忽现、闪烁跳跃。
当走样严重时,可能导致意外的结果。例如,考虑图2.6 a)和b)所示的信号,它们是两组完全不同的信号,对它们用同一频率进行采样(见图2.6中的黑点 ),重建后的信号却相同。图2.6 c)或d)是图2.6 a)信号的走样,也是图2.6 b)信号的走样。造成走样的原因是由于采样频率太低造成的欠采样。根据采样定理,为了避免走样,采样频率至少应是信号最高频率的2倍。对于小于像素尺寸的图形对象,一方面,如果它未能覆盖像素中用于计算其属性的像素中点,则这个对象将不会显示出来;另一方面,如果它覆盖了像素中用于计算其属性的那一点,它将不恰当地代表整个像素的属性。图2.7就是这样的一个例子。当在光栅设备上显示图2.7 a)所示的一组细长的多边形时,由于仅仅当像素中心被这些矩形覆盖时该像素才被显示,因此造成狭小的图形遗失、图形细节失真,其结果如图2.7 b)所示。在动画序列中,这种走样现象会导致图形时隐时现,产生闪烁。图2.8是一个小卡通动画序列中的三幅画。如果像素的属性由其中心决定,则在第一帧中,这个小卡通人是不可见的,在第二帧小卡通人可见,但第三帧又不可见。这样,小卡通人给人的感觉不是在缓慢地连续前进,而是一明一暗地在闪烁。
为了提高图形质量,必须克服或减少走样现象。这就是本节研究的重点。
光栅图形的反走样方法主要有两类:
第一类是超采样或称后置滤波。这类算法的基本思想着眼于提高分辨率,虽然采用高分辨率的光栅图形显示器也是一个选择,但它受到客观条件的限制,而且也不经济。因此,我们往往采用软件实现的方法,即:将低分辨率的图形像素划分为许多子像素,在较高分辨率上对各子像素的颜色值或灰度值进行计算,然后采用某种平均算法,将原像素内的各子像素的颜色值或灰度值的平均值作为该像素显示的颜色值或灰度值,在较低分辨率的光栅图形设备上进行显示。
第二类方法称为前置滤波。即:把像素作为一个有限区域而不是一个面积为零的点来处理。