在显示器或者绘图机等这些图形输出设备中,都可以看作存在一个网格。对应显示器上的每一个像素,绘图机画笔每一步的终点,都可以看作是网格上的一个网格点。这些网格点是有一定距离的,而所有图形的显示都是要由网格点组成,所以显示出来的图形不可能想原先那样精确。这就只能通过一些用于图形扫描算法近似地模拟。
下面就是一些经典的图形扫描算法。其主要的工作就是当图形由Q
0(x
0r,y
0r)到Q
1(x
1r,y
1r)时,Q
0已经在网格上的网格点(x
pi,y
pi)上显示了,要在网格上显示Q
1(x
1r,y
1r),到底是应该取网格点(x
pi+1,y
pi),还是网格点(x
pi+1,y
pi+1)呢?对应某种的图形有多种不同的算法,算法所实现的功能是一样的,就是上面的。比较各种算法,关键就要看那个算法的计算效率。
为了操作图形输出设备中的网格点,我定义了像素类:
public class Pixel


{
public int _x;
public int _y;
public Pixel()

{
this._x = 0;
this._y = 0;
}
public Pixel(int x,int y)

{
this._x = x;
this._y = y;
}
}
而对于数学上的点,我定义了Point来操作
public class Point
{
public float x;
public float y;
}
尽管扫描算法是一些底层的东西,用C#这样的高层的、面向对象的语言来描述确实有些别扭。但对于熟悉OO的人来说,这不失为一种抽离具体语言,直接进入算法核心的学习途径
生成直线:
1.DDA(数值微分法)
要点:通过斜率k=(y
1r-y
0r)/(x
1r-x
0r)与1比较,确定y
1r-y
0r与x
1r-x
0r那个大,从而决定下一点取网格点
P
1(x
p+1,y
p),还是网格点P
2(x
p+1,y
p+1)。
代码:
public Pixel LineDDA(Point p0,Point p1,Pixel precedent)

{
Pixel descendent = new Pixel();
float k=Math.Abs((p1.y-p0.y)/(p1.x-p0.x));
if(k>1||k==1)

{
descendent._x = precedent._x + 1;
descendent._y = precedent._y + 1;
}
else

{
descendent._x = precedent._x + 1;
descendent._y = precedent._y + 1;
}
return descendent;
}
2.中点画线法
要点:比较直线L与x=x
0+1间的真实交点Q(x
1r,y
1r)与两网格点P
1,P
2间的中点M(
(xp1+xp2)/
2,
(yp1+yp2) /
2)进行比较。通过构造的判别式d=F(M)=F(x
p+1,y
p+0.5)=a(x
p+1)+b(y
p+0.5)+c与0的关系来判断取点。 (其中a=y
0r-y
1r,b=x
1r-x
0r,c=x
0ry
1r-x
1ry
0r)
当d<0,Q在M上方,取在上的P
2;
当d>0,Q在M上方,取在上的P
1;
当d=0,Q与M重合,取P
1、P
2均可。
其实所谓的判别式,就是相当于F(x,y)=ax+by+c=0这样一个直线方程,这个也是我们要在图形设备上显示的直 线。通过代入(x
p+1,y
p+0.5)并把方程与0比较,这相当于高中的数学学过的线性规划。
代码:
public Pixel LineMidPoint(Point p0,Point p1,Pixel precedent)

{
Pixel descendent = new Pixel();
float a = p0.y-p1.y;
float b = p1.x-p0.x;
float c = p0.x*p1.y-p1.x*p0.y;
float d=a*(precedent._x+1)+b*(precedent._y+0.5)+c;
if(d>0||d==0)

{
descendent._x = precedent._x + 1;
descendent._y = precedent._y + 1;
}
else

{
descendent._x = precedent._x + 1;
descendent._y = precedent._y + 1;
}
return descendent;
}
3.Bresenham算法:
要点:计算真实点Q(x
1r,y
1r)到两相近的网格点P
1(x
p+1,y
p),P
2(x
p+1,y
p+1)的距离d
1,d
2,通过d
1-d
2与0比较得出 Q靠近P
1还是靠近P
2,从而选出是取P
1还是P
2。通过递归的判别式来做:
设d
1-d
2=£
p=(y
p+1 -y
1r)-(y
1r-y
p)=2(y
p-y
1r)+1
则d
1'-d
2'=£
p+1=(y
p+1 +1 -y
2r)-(y
2r-y
p+1)
=£
p+k-1 当£
p>=0
or
=£
p+k 当£
p<0
其中k=dy/dx,直线的斜率
设定直线起点Q
0(x
0r,y
0r),取在(x
p1,y
p1),取接下来的点Q
1(x
1r,y
1r)时,£
1=(y
p1+1-y
1r)-(y
1r-y
p1)=
2(y
p1-y
1r)+1=2k+1(取dx=1,dy=y
1r-y
0r=y
1r-y
p1)
以上是对应于dx>=dy>0的情况,当dy>dx>0时,要把x和y的位置交换。当dx<0或dy<0,取点时相应的 x
pi+1=x
pi-1或y
pi+1=y
pi-1。
代码:
dx>=dy>0的情况:
public ArrayList LineBresenham(Point p0,Point p1)

{
ArrayList pixelArray = new ArrayList();
float k = (p1.y-p0.y)/(p1.x-p0.x);
float e = 2 * k - 1;
Pixel precedent = new Pixel((int)p0.x,(int)p0.y);
pixelArray.Add(precedent);
for(int i=0;i<p1.x-p0.x;i++)

{
if(e<0)

{
e = e + k;
Pixel descendent = new Pixel(precedent._x+1,precedent._y);
pixelArray.Add(descendent);
}
else

{
e = e + k - 1;
Pixel descendent = new Pixel(precedent._x+1,precedent._y+1);
pixelArray.Add(descendent);
}
}
return pixelArray;
}
4.二步法
要点:
代码: