【原创】基本图形生成:直线篇

在显示器或者绘图机等这些图形输出设备中,都可以看作存在一个网格。对应显示器上的每一个像素,绘图机画笔每一步的终点,都可以看作是网格上的一个网格点。这些网格点是有一定距离的,而所有图形的显示都是要由网格点组成,所以显示出来的图形不可能想原先那样精确。这就只能通过一些用于图形扫描算法近似地模拟。

下面就是一些经典的图形扫描算法。其主要的工作就是当图形由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 class Point
{
    
public float x;
    
public float y;
}
尽管扫描算法是一些底层的东西,用C#这样的高层的、面向对象的语言来描述确实有些别扭。但对于熟悉OO的人来说,这不失为一种抽离具体语言,直接进入算法核心的学习途径

生成直线:
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;
    }

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;
    }

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;
    }

4.二步法
     要点:

     代码:
posted @ 2008-03-18 16:29  斌伯  阅读(546)  评论(0编辑  收藏  举报