【原创】基本图形生成:直线篇
在显示器或者绘图机等这些图形输出设备中,都可以看作存在一个网格。对应显示器上的每一个像素,绘图机画笔每一步的终点,都可以看作是网格上的一个网格点。这些网格点是有一定距离的,而所有图形的显示都是要由网格点组成,所以显示出来的图形不可能想原先那样精确。这就只能通过一些用于图形扫描算法近似地模拟。
下面就是一些经典的图形扫描算法。其主要的工作就是当图形由Q0(x0r,y0r)到Q1(x1r,y1r)时,Q0已经在网格上的网格点(xpi,ypi)上显示了,要在网格上显示Q1(x1r,y1r),到底是应该取网格点(xpi+1,ypi),还是网格点(xpi+1,ypi+1)呢?对应某种的图形有多种不同的算法,算法所实现的功能是一样的,就是上面的。比较各种算法,关键就要看那个算法的计算效率。
为了操作图形输出设备中的网格点,我定义了像素类:
生成直线:
1.DDA(数值微分法)
要点:通过斜率k=(y1r-y0r)/(x1r-x0r)与1比较,确定y1r-y0r与x1r-x0r那个大,从而决定下一点取网格点
P1(xp+1,yp),还是网格点P2(xp+1,yp+1)。
代码:
2.中点画线法
要点:比较直线L与x=x0+1间的真实交点Q(x1r,y1r)与两网格点P1,P2间的中点M((xp1+xp2)/2,(yp1+yp2) /2)进行比较。通过构造的判别式d=F(M)=F(xp+1,yp+0.5)=a(xp+1)+b(yp+0.5)+c与0的关系来判断取点。 (其中a=y0r-y1r,b=x1r-x0r,c=x0ry1r-x1ry0r)
当d<0,Q在M上方,取在上的P2;
当d>0,Q在M上方,取在上的P1;
当d=0,Q与M重合,取P1、P2均可。
其实所谓的判别式,就是相当于F(x,y)=ax+by+c=0这样一个直线方程,这个也是我们要在图形设备上显示的直 线。通过代入(xp+1,yp+0.5)并把方程与0比较,这相当于高中的数学学过的线性规划。
代码:
3.Bresenham算法:
要点:计算真实点Q(x1r,y1r)到两相近的网格点P1(xp+1,yp),P2(xp+1,yp+1)的距离d1,d2,通过d1-d2与0比较得出 Q靠近P1还是靠近P2,从而选出是取P1还是P2。通过递归的判别式来做:
设d1-d2=£p=(yp+1 -y1r)-(y1r-yp)=2(yp-y1r)+1
则d1'-d2'=£p+1=(yp+1 +1 -y2r)-(y2r-yp+1)
=£p+k-1 当£p>=0
or
=£p+k 当£p<0
其中k=dy/dx,直线的斜率
设定直线起点Q0(x0r,y0r),取在(xp1,yp1),取接下来的点Q1(x1r,y1r)时,£1=(yp1+1-y1r)-(y1r-yp1)=
2(yp1-y1r)+1=2k+1(取dx=1,dy=y1r-y0r=y1r-yp1)
以上是对应于dx>=dy>0的情况,当dy>dx>0时,要把x和y的位置交换。当dx<0或dy<0,取点时相应的 xpi+1=xpi-1或ypi+1=ypi-1。
代码:
dx>=dy>0的情况:
4.二步法
要点:
代码:
下面就是一些经典的图形扫描算法。其主要的工作就是当图形由Q0(x0r,y0r)到Q1(x1r,y1r)时,Q0已经在网格上的网格点(xpi,ypi)上显示了,要在网格上显示Q1(x1r,y1r),到底是应该取网格点(xpi+1,ypi),还是网格点(xpi+1,ypi+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 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;
}
}
public class Point
{
public float x;
public float y;
}
尽管扫描算法是一些底层的东西,用C#这样的高层的、面向对象的语言来描述确实有些别扭。但对于熟悉OO的人来说,这不失为一种抽离具体语言,直接进入算法核心的学习途径{
public float x;
public float y;
}
生成直线:
1.DDA(数值微分法)
要点:通过斜率k=(y1r-y0r)/(x1r-x0r)与1比较,确定y1r-y0r与x1r-x0r那个大,从而决定下一点取网格点
P1(xp+1,yp),还是网格点P2(xp+1,yp+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;
}
{
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=x0+1间的真实交点Q(x1r,y1r)与两网格点P1,P2间的中点M((xp1+xp2)/2,(yp1+yp2) /2)进行比较。通过构造的判别式d=F(M)=F(xp+1,yp+0.5)=a(xp+1)+b(yp+0.5)+c与0的关系来判断取点。 (其中a=y0r-y1r,b=x1r-x0r,c=x0ry1r-x1ry0r)
当d<0,Q在M上方,取在上的P2;
当d>0,Q在M上方,取在上的P1;
当d=0,Q与M重合,取P1、P2均可。
其实所谓的判别式,就是相当于F(x,y)=ax+by+c=0这样一个直线方程,这个也是我们要在图形设备上显示的直 线。通过代入(xp+1,yp+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;
}
{
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(x1r,y1r)到两相近的网格点P1(xp+1,yp),P2(xp+1,yp+1)的距离d1,d2,通过d1-d2与0比较得出 Q靠近P1还是靠近P2,从而选出是取P1还是P2。通过递归的判别式来做:
设d1-d2=£p=(yp+1 -y1r)-(y1r-yp)=2(yp-y1r)+1
则d1'-d2'=£p+1=(yp+1 +1 -y2r)-(y2r-yp+1)
=£p+k-1 当£p>=0
or
=£p+k 当£p<0
其中k=dy/dx,直线的斜率
设定直线起点Q0(x0r,y0r),取在(xp1,yp1),取接下来的点Q1(x1r,y1r)时,£1=(yp1+1-y1r)-(y1r-yp1)=
2(yp1-y1r)+1=2k+1(取dx=1,dy=y1r-y0r=y1r-yp1)
以上是对应于dx>=dy>0的情况,当dy>dx>0时,要把x和y的位置交换。当dx<0或dy<0,取点时相应的 xpi+1=xpi-1或ypi+1=ypi-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;
}
{
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.二步法
要点:
代码: