【原创】基本图形生成:直线篇
在显示器或者绘图机等这些图形输出设备中,都可以看作存在一个网格。对应显示器上的每一个像素,绘图机画笔每一步的终点,都可以看作是网格上的一个网格点。这些网格点是有一定距离的,而所有图形的显示都是要由网格点组成,所以显示出来的图形不可能想原先那样精确。这就只能通过一些用于图形扫描算法近似地模拟。
下面就是一些经典的图形扫描算法。其主要的工作就是当图形由Q0(x0r,y0r)到Q1(x1r,y1r)时,Q0已经在网格上的网格点(xpi,ypi)上显示了,要在网格上显示Q1(x1r,y1r),到底是应该取网格点(xpi+1,ypi),还是网格点(xpi+1,ypi+1)呢?对应某种的图形有多种不同的算法,算法所实现的功能是一样的,就是上面的。比较各种算法,关键就要看那个算法的计算效率。
为了操作图形输出设备中的网格点,我定义了像素类:
 public class Pixel
public class Pixel
 {
{
 public int _x;
    public int _x;
 public int _y;
    public int _y;
 
    
 public Pixel()
    public Pixel()
 {
    {
 this._x = 0;
        this._x = 0;
 this._y = 0;
        this._y = 0;
 }
    }
 
    
 public Pixel(int x,int y)
    public Pixel(int x,int y)
 {
    {
 this._x = x;
        this._x = x;
 this._y = y;
        this._y = y;
 }
    }
 }
而对于数学上的点,我定义了Point来操作
}
而对于数学上的点,我定义了Point来操作
生成直线:
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)
public Pixel LineDDA(Point p0,Point p1,Pixel precedent)
 {
    {
 Pixel descendent = new Pixel();
        Pixel descendent = new Pixel();
 float k=Math.Abs((p1.y-p0.y)/(p1.x-p0.x));
        float k=Math.Abs((p1.y-p0.y)/(p1.x-p0.x));
 
        
 if(k>1||k==1)
        if(k>1||k==1)
 {
        {
 descendent._x = precedent._x + 1;
            descendent._x = precedent._x + 1;
 descendent._y = precedent._y + 1;
            descendent._y = precedent._y + 1;
 }
        }
 else
        else
 {
        {
 descendent._x = precedent._x + 1;
            descendent._x = precedent._x + 1;
 descendent._y = precedent._y + 1;
            descendent._y = precedent._y + 1;
 }
        }
 return descendent;
        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)
public Pixel LineMidPoint(Point p0,Point p1,Pixel precedent)
 {
    {
 Pixel descendent = new Pixel();
        Pixel descendent = new Pixel();
 float a = p0.y-p1.y;
        float a = p0.y-p1.y;
 float b = p1.x-p0.x;
        float b = p1.x-p0.x;
 float c = p0.x*p1.y-p1.x*p0.y;
        float c = p0.x*p1.y-p1.x*p0.y;
 float d=a*(precedent._x+1)+b*(precedent._y+0.5)+c;
        float d=a*(precedent._x+1)+b*(precedent._y+0.5)+c;
 
        
 if(d>0||d==0)
        if(d>0||d==0)
 {
        {
 descendent._x = precedent._x + 1;
            descendent._x = precedent._x + 1;
 descendent._y = precedent._y + 1;
            descendent._y = precedent._y + 1;
 }
        }
 else
        else
 {
        {
 descendent._x = precedent._x + 1;
            descendent._x = precedent._x + 1;
 descendent._y = precedent._y + 1;
            descendent._y = precedent._y + 1;
 }
        }
 return descendent;
        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)
public ArrayList LineBresenham(Point p0,Point p1)
 {
    {
 ArrayList pixelArray = new ArrayList();
        ArrayList pixelArray = new ArrayList();
 float k = (p1.y-p0.y)/(p1.x-p0.x);
        float k = (p1.y-p0.y)/(p1.x-p0.x);
 float e = 2 * k - 1;
        float e = 2 * k - 1;
 
        
 Pixel precedent = new Pixel((int)p0.x,(int)p0.y);
        Pixel precedent = new Pixel((int)p0.x,(int)p0.y);
 pixelArray.Add(precedent);
        pixelArray.Add(precedent);
 
        
 for(int i=0;i<p1.x-p0.x;i++)
        for(int i=0;i<p1.x-p0.x;i++)
 {
        {
 if(e<0)
            if(e<0)
 {
            {
 e = e + k;
                e = e + k;
 Pixel descendent = new Pixel(precedent._x+1,precedent._y);
                Pixel descendent = new Pixel(precedent._x+1,precedent._y);
 pixelArray.Add(descendent);
                pixelArray.Add(descendent);
 }
            }
 else
            else
 {
            {
 e = e + k - 1;
                e = e + k - 1;
 Pixel descendent = new Pixel(precedent._x+1,precedent._y+1);
                Pixel descendent = new Pixel(precedent._x+1,precedent._y+1);
 pixelArray.Add(descendent);
                pixelArray.Add(descendent);
 }
            }
 }
        }
 return pixelArray;
        return pixelArray;
 }
    }
4.二步法
要点:
代码:
下面就是一些经典的图形扫描算法。其主要的工作就是当图形由Q0(x0r,y0r)到Q1(x1r,y1r)时,Q0已经在网格上的网格点(xpi,ypi)上显示了,要在网格上显示Q1(x1r,y1r),到底是应该取网格点(xpi+1,ypi),还是网格点(xpi+1,ypi+1)呢?对应某种的图形有多种不同的算法,算法所实现的功能是一样的,就是上面的。比较各种算法,关键就要看那个算法的计算效率。
为了操作图形输出设备中的网格点,我定义了像素类:
 public class Pixel
public class Pixel {
{ public int _x;
    public int _x; public int _y;
    public int _y; 
     public Pixel()
    public Pixel() {
    { this._x = 0;
        this._x = 0; this._y = 0;
        this._y = 0; }
    } 
     public Pixel(int x,int y)
    public Pixel(int x,int y) {
    { this._x = x;
        this._x = x; this._y = y;
        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)
public Pixel LineDDA(Point p0,Point p1,Pixel precedent) {
    { Pixel descendent = new Pixel();
        Pixel descendent = new Pixel(); float k=Math.Abs((p1.y-p0.y)/(p1.x-p0.x));
        float k=Math.Abs((p1.y-p0.y)/(p1.x-p0.x)); 
         if(k>1||k==1)
        if(k>1||k==1) {
        { descendent._x = precedent._x + 1;
            descendent._x = precedent._x + 1; descendent._y = precedent._y + 1;
            descendent._y = precedent._y + 1; }
        } else
        else {
        { descendent._x = precedent._x + 1;
            descendent._x = precedent._x + 1; descendent._y = precedent._y + 1;
            descendent._y = precedent._y + 1; }
        } return descendent;
        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)
public Pixel LineMidPoint(Point p0,Point p1,Pixel precedent) {
    { Pixel descendent = new Pixel();
        Pixel descendent = new Pixel(); float a = p0.y-p1.y;
        float a = p0.y-p1.y; float b = p1.x-p0.x;
        float b = p1.x-p0.x; float c = p0.x*p1.y-p1.x*p0.y;
        float c = p0.x*p1.y-p1.x*p0.y; float d=a*(precedent._x+1)+b*(precedent._y+0.5)+c;
        float d=a*(precedent._x+1)+b*(precedent._y+0.5)+c; 
         if(d>0||d==0)
        if(d>0||d==0) {
        { descendent._x = precedent._x + 1;
            descendent._x = precedent._x + 1; descendent._y = precedent._y + 1;
            descendent._y = precedent._y + 1; }
        } else
        else {
        { descendent._x = precedent._x + 1;
            descendent._x = precedent._x + 1; descendent._y = precedent._y + 1;
            descendent._y = precedent._y + 1; }
        } return descendent;
        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)
public ArrayList LineBresenham(Point p0,Point p1) {
    { ArrayList pixelArray = new ArrayList();
        ArrayList pixelArray = new ArrayList(); float k = (p1.y-p0.y)/(p1.x-p0.x);
        float k = (p1.y-p0.y)/(p1.x-p0.x); float e = 2 * k - 1;
        float e = 2 * k - 1; 
         Pixel precedent = new Pixel((int)p0.x,(int)p0.y);
        Pixel precedent = new Pixel((int)p0.x,(int)p0.y); pixelArray.Add(precedent);
        pixelArray.Add(precedent); 
         for(int i=0;i<p1.x-p0.x;i++)
        for(int i=0;i<p1.x-p0.x;i++) {
        { if(e<0)
            if(e<0) {
            { e = e + k;
                e = e + k; Pixel descendent = new Pixel(precedent._x+1,precedent._y);
                Pixel descendent = new Pixel(precedent._x+1,precedent._y); pixelArray.Add(descendent);
                pixelArray.Add(descendent); }
            } else
            else {
            { e = e + k - 1;
                e = e + k - 1; Pixel descendent = new Pixel(precedent._x+1,precedent._y+1);
                Pixel descendent = new Pixel(precedent._x+1,precedent._y+1); pixelArray.Add(descendent);
                pixelArray.Add(descendent); }
            } }
        } return pixelArray;
        return pixelArray; }
    }4.二步法
要点:
代码:
 
                     
                    
                 
                    
                


 
     
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号