Bresenham 算法详解

这篇文章讲的非常详细,这里列出几点注意事项


1、bresenham的起始点是整数点,所有初始的 error值 由 y1 来计算, 即 m - 0.5

2、当error值大于0时,取了上面的点,A点变成了 y+1 上的A点,所以必须减去1 才能做正确判断。具体参照下面内容的公式推导。

贴上AS3代码

package
{
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.display.StageScaleMode;
	import flash.events.UncaughtErrorEvent;
	import flash.geom.Point;

	[SWF(frameRate="60", backgroundColor="0",height="600",width="800")]
	public class LineTest extends Sprite
	{
		private var bmd:BitmapData = new BitmapData(800,600, false, 0);
		private var bmp:Bitmap = new Bitmap(bmd);
		private var error_factor:Number = 0.5;
		public function LineTest()
		{
			stage.scaleMode = StageScaleMode.NO_SCALE;
			addChild(bmp);
			
			draw_help3(new Point(0,300), new Point(800,300), 0xffffff);
			draw_help3(new Point(400,0), new Point(400,600), 0xffffff);
			draw_help3(new Point(400,300), new Point(500,500),0x00ffff);
			draw_help3(new Point(400,300), new Point(500,400),0x00ff00);
			draw_help3(new Point(400,300), new Point(300,500),0xffff00);
			draw_help3(new Point(400,300), new Point(300,400),0x0000ff);
			
			draw_help3(new Point(100,600), new Point(800,10),0xff0000);
		}
		
		private function draw_help3(start:Point, end:Point, color:uint):void
		{
			bmd.lock();
			var dx:int = end.x - start.x;
			var dy:int = end.y - start.y;
			
			var x:int = start.x;
			var y:int = start.y;
			
			var xInc:int;
			var yInc:int;
			var i:int;
			
			if(dx >=0)
			{
				xInc = 1;
			}else
			{
				xInc = -1;
				dx = -dx;//总共要走多少步
			}
			
			if(dy >= 0)
			{
				yInc = 1;
			}else
			{
				yInc = -1;
				dy = -dy;
			}
			
			//比较值的时候,都是按照第一象限来比较,只不过,步进的时候,按照 xInc, yInc 来步进
			var k2dx:int = 2*dy;
			var error2dx:int = k2dx - 1;
			
			var k2dy:int = 2*dx;
			var error2dy:int = k2dy - 1;
			
			if(dx >= dy)//近X轴线
			{
				for(i = 0; i <= dx ; i++)
				{
					bmd.setPixel32(x,y, color);
					if(error2dx > 0)
					{
						y += yInc;
						error2dx = error2dx + k2dx - 2*dx;
					}else
					{
						error2dx = error2dx + k2dx;
					}
					x += xInc;
				}
			}else//近Y轴线
			{
				for(i = 0; i <= dy ; i++)
				{
					bmd.setPixel32(x,y, color);
					if(error2dy > 0)
					{
						x += xInc;
						error2dy = error2dy + k2dy - 2*dy;
					}else
					{
						error2dy = error2dy + k2dy;
					}
					y += yInc;
				}
			}
			
			bmd.unlock();
		}
		
		private function draw_help2(start:Point, end:Point):void
		{
			bmd.lock();
			var dx:int = end.x - start.x;
			var dy:int = end.y - start.y;
			
			var x:int = start.x;
			var y:int = start.y;
			
			var k2dx:int = 2*dy;
			var error2dx:int = k2dx - 1;
			
			for(var i:int = 0; i < dx ; i++)
			{
				bmd.setPixel32(x,y, 0xffff00);
				if(error2dx > 0)
				{
					y++;
					error2dx = error2dx + k2dx - 2*dx;
				}else
				{
					error2dx = error2dx + k2dx;
				}
				x++;
			}
			bmd.unlock();
		}
		
		private function draw_help(start:Point, end:Point):void
		{
			bmd.lock();
			//必须是整点
			var dx:int = end.x - start.x;
			var dy:int = end.y - start.y;
			
			var x:int = start.x;
			var y:int = start.y;
			
			var k:Number = dy/dx;
			var error:Number = k - 0.5;//初始值
			
			for(var i:int = 0; i < dx ; i++)
			{
				bmd.setPixel32(x,y, 0xffffff);
				if(error > 0)//取右上的点
				{
					y++;
					error = error + k - 1;
				}else
				{
					error = error + k;
				}
				x++;
			}
			bmd.unlock();
		}
	}
}

贴图:


----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

http://wenku.baidu.com/link?url=ova6h39VQ4ilLMIQ51qHpjeuuKu7yLtD1M53NQQc6rUSI9UHymh2r2V8ZZdn6R3bForxZzFf65KO_JCY6flz5A4TU8F4uEHs0N82iCmdQwG

从上面介绍的DDA算法可以看到,由于在循环中涉及实型数据的加减运算,因此直线的生成速度较慢。

  在生成直线的算法中,Bresenham算法是最有效的算法之一。Bresenham算法是一种基于误差判别式来生成直线的方法。

  一、直线Bresenham算法描述:

  它也是采用递推步进的办法,令每次最大变化方向的坐标步进一个象素,同时另一个方向的坐标依据误差判别式的符号来决定是否也要步进一个象素。

  我们首先讨论m=y/x,当0≤m≤1且x1<x2时的Bresenham算法。从DDA直线算法可知这些条件成立时,公式(2-2)、(2-3)可写成:

xi+1=xi+1(2-6)
yi+1=yi+m(2-7)

  有两种Bresenham算法思想,它们各自从不同角度介绍了Bresenham算法思想,得出的误差判别式都是一样的。

  二、直线Bresenham算法思想之一:

  由于显示直线的象素点只能取整数值坐标,可以假设直线上第i个象素点坐标为(xiyi),它是直线上点(xi,yi)的最佳近似,并且xi=xi(假设m<1),如下图所示。那么,直线上下一个象素点的可能位置是(xi+1,yi)或(xi+1,yi+1)。

 

  由图中可以知道,在x= x i+1处,直线上点的y值是y =m( x i+1)+b,该点离象素点( x i+1, y i)和象素点( x i+1, y i+1)的距离分别是d 1和d 2: 

d1=y-yi=m(xi+1)+b-yi(2-8)
d2=(yi+1)-y=(yi+1)-m(xi+1)-b(2-9)

  这两个距离差是

d1-d2=2m(xi+1)-2yi+2b-1(2-10)

  我们来分析公式(2-10):
  (1)当此值为正时,d 1>d 2,说明直线上理论点离( x i+1, y i+1)象素较近,下一个象素点应取( xi+1, y i+1)。
  (2)当此值为负时,d 1<d 2,说明直线上理论点离( x i+1, y i)象素较近,则下一个象素点应取( x i+1, y i)。
  (3)当此值为零时,说明直线上理论点离上、下两个象素点的距离相等,取哪个点都行,假设算法规定这种情况下取( x i+1, y i+1)作为下一个象素点。

  因此只要利用(d1-d2)的符号就可以决定下一个象素点的选择。为此,我们进一步定义一个新的判别式:

pi=x×(d1-d2)=2y·xi-2x·yi+c(2-11)


  式(2-11)中的x=(x2-x1)>0,因此pi与(d1-d2)有相同的符号;

  这里y=y2-y1m=y/x;c=2y+x(2b-1)。

  下面对式(2-11)作进一步处理,以便得出误差判别递推公式并消除常数c。

  将式(2-11)中的下标i改写成i+1,得到:

pi+1=2y·xi+1-2x·yi+1+c(2-12)

  将式(2-12)减去(2-11),并利用xi+1=xi+1,可得:

pi+1= pi+2y-2x·(yi+1-yi)(2-13)

  再假设直线的初始端点恰好是其象素点的坐标,即满足:

y1=mx1+b(2-14)

  由式(2-11)和式(2-14)得到p1的初始值:

p1=2y-x(2-15)

  这样,我们可利用误差判别变量,得到如下算法表示:

 初始     p1=2y-x(2-16)
当pi≥0时: yi+1=yi+1,
      xi+1=xi+1,
      pi+1=pi+2(y-x)

否则:   yi+1=yi,
      xi+1=xi+1, 
      pi+1=pi+2y

 

  从式(2-16)可以看出,第i+1步的判别变量pi+1仅与第i步的判别变量pi、直线的两个端点坐标分量差x和y有关,运算中只含有整数相加和乘2运算,而乘2可利用算术左移一位来完成,因此这个算法速度快并易于硬件实现。

  三、直线Bresenham算法思想之二:

  由于象素坐标的整数性,数学点(xi,yi)与所取象素点(xi,yir)间会引起误差(εi),当xi列上已用象素坐标(xi,yir)表示直线上的点(xi,yi),下一直线点B(xi+1,yi+1),是取象素点C(xi+1,yir ),还是D(xi+1,y(i+1)r)呢?

  设ACD边的中点,正确的选择:

  若B点在A点上方,选择D点; 否则,选C点。

 

  用误差式描述为:

ε(xi+1)=BC-AC=(yi+1-yir)-0.5(2-8')

  求递推公式:

ε(xi+2)=(yi+2-y(i+1)r)-0.5 = yi+1+m-y(i+1)r-0.5(2-9')

  当ε(xi+1)≥0时,选D点,y(i+1)r = yir+1

ε(xi+2)= yi+1+m-yir-1-0.5=ε(xi+1)+m-1(2-10')

  当ε(xi+1)﹤0时,选C点,y(i+1)r = yir

ε(xi+2)= yi+1+m-yir-0.5=ε(xi+1)+m(2-11')

  初始时:

ε(xs+1)=BC-AC=m-0.5(2-12')

  为了运算中不含实型数,同时不影响不等式的判断,将方程两边同乘一正整数。

  令方程两边同乘2·Δx,即d=2·Δx·ε,则:

  初始时:

= 2·Δy-Δx(2-13')

  递推式:

当d≥0时:{ d=d+2·(Δy-Δx);
      y++;
      x++;
     }
否则:  { d=d+2·Δy;
      x++;
     }
(2-14')

  

  四、直线Bresenham算法实现:

  条件:0≤m≤1且x1<x2

  1、输入线段的两个端点坐标和画线颜色:x1,y1,x2,y2,color;
  2、设置象素坐标初值:x=x1,y=y1;
  3、设置初始误差判别值:p=2·Δy-Δx;
  4、分别计算:Δx=x2-x1、Δy=y2-y1;
  5、循环实现直线的生成:
   for(x=x1x<=x2;x++)
   { putpixel(x,y,color) ;
    if(p>=0)
     { y=y+1;
      p=p+2·(Δy-Δx);
     }
    else
     { p=p+2·Δy;
     }

   }

  五、直线Bresenham算法完善:

  现在我们修正(2-16)公式,以适应对任何方向及任何斜率线段的绘制。如下图所示,线段的方向可分为八种,从原点出发射向八个区。由线段按图中所示的区域位置可决定xi+1和yi+1的变换规律。

  容易证明:当线段处于①、④、⑧、⑤区时,以|x|和|y|代替前面公式中的x和y,当线段处于②、③、⑥、⑦区时,将公式中的|x|和|y|对换,则上述两公式仍有效。

在线段起点区分线段方向

  六、直线Bresenham算法演示:

   
斜率小于1 斜率大于1

  七、直线Bresenham算法特点:

  由于程序中不含实型数运算,因此速度快、效率高,是一种有效的画线算法。

  八、直线Bresenham算法程序:

void Bresenhamline (int x1,int y1,int x2,int y2,int color)
{
 int x, y, dx, dy, s1, s2, p, temp, interchange, i;
 x=x1;
 y=y1;
 dx=abs(x2-x1);
 dy=abs(y2-y1);

 if(x2>x1)
  s1=1;
 else
  s1=-1;

 if(y2>y1)
  s2=1;
 else
  s2=-1;

 if(dy>dx)
 {
  temp=dx;
  dx=dy;
  dy=temp;
  interchange=1;
 }
 else
  interchange=0;

 p=2*dy-dx;
 for(i=1;i<=dx;i++)
 {
  putpixel(x,y,color);
  if(p>=0)
  {
   if(interchange= =0)
    y=y+s2;
   else
    x=x+s1;
   p=p-2*dx;
  }
  if(interchange= =0)
   x=x+s1; 
  else
   y=y+s2;
  p=p+2*dy;
 }
}

http://cg.sjtu.edu.cn/lecture_site/chap2/mainframe212.htm

2.1.2 Bresenham算法

 

    Bresenham算法是计算机图形学典型的直线光栅化算法。

  • 从另一个角度看直线光栅化显示算法的原理
    • 由直线的斜率确定选择在x方向或y方向上每次递增(减)1个单位,另一变量的递增(减)量为0或1,它取决于实际直线与最近光栅网格点的距离,这个距离的最大误差为0.5。

     

  • 1)Bresenham的基本原理

     

    • 假定直线斜率k在0~1之间。此时,只需考虑x方向每次递增1个单位,决定y方向每次递增0或1。

          直线当前点为(xi,y)
          直线当前光栅点为(xi,yi)

          下一个直线的点应为(xi+1,y+k)
          下一个直线的光栅点
              或为右光栅点(xi+1,yi)(y方向递增量0)
              或为右上光栅点(xi+1,yi+1)(y方向递增量1)

          

      记直线与它垂直方向最近的下光栅点的误差为d,有:d=(y+k)–yi,且

          0≤d≤1
          当d<0.5:下一个象素应取右光栅点(xi+1,yi)
          当d≥0.5:下一个象素应取右上光栅点(xi+1,yi+1)

      如果直线的(起)端点在整数点上,误差项d的初值:d0=0,
      x坐标每增加1,d的值相应递增直线的斜率值k,即:d=d + k。
      一旦d≥1,就把它减去1,保证d的相对性,且在0-1之间。

      令e=d-0.5,关于d的判别式和初值可简化成:

          e的初值e0= -0.5,增量亦为k;
          e<0时,取当前象素(xi,yi)的右方象素(xi+1,yi);
          e>0时,取当前象素(xi,yi)的右上方象素(xi+1,yi+1); 
          e=0时,可任取上、下光栅点显示。

      Bresenham算法的构思巧妙:它引入动态误差e,当x方向每次递增1个单位,可根据e的符号决定y方向每次递增 0 或 1。

          e<0,y方向不递增
          e>0,y方向递增1
          x方向每次递增1个单位,e = e + k

      因为e是相对量,所以当e>0时,表明e的计值将进入下一个参考点(上升一个光栅点),此时须:e = e - 1

       

  • 2)Bresenham算法的实施——Rogers 版

     

    • 通过(0,0)的所求直线的斜率大于0.5,它与x=1直线的交点离y=1直线较近,离y=0直线较远,因此取光栅点(1,1)比(1,0)更逼近直线;
      如果斜率小于0.5,则反之;
      当斜率等于0.5,没有确定的选择标准,但本算法选择(1,1)

      程序

       

      • //Bresenham's line resterization algorithm for the first octal.
        //The line end points are (xs,ys) and (xe,ye) assumed not equal.
        // Round is the integer function.
        // x,y, ∆x, ∆y are the integer, Error is the real.
        //initialize variables
        x=xs
        y=ys
        ∆x = xe -xs
        ∆y = ye -ys
        //initialize e to compensate for a nonzero intercept
        Error =∆y/∆x-0.5
        //begin the main loop
        for i=1 to ∆x
            WritePixel (x, y, value)
            if (Error ≥0) then
                y=y+1
                Error = Error -1
            end if
            x=x+1
            Error = Error +∆y/∆x
        next i
        finish

       

  • 3)整数Bresenham算法

     

    • 上述Bresenham算法在计算直线斜率和误差项时要用到浮点运算和除法,采用整数算术运算和避免除法可以加快算法的速度。

      由于上述Bresenham算法中只用到误差项(初值Error =∆y/∆x-0.5)的符号

      因此只需作如下的简单变换:

          NError = 2*Error*∆x

      即可得到整数算法,这使本算法便于硬件(固件)实现。

      程序

       

      • //Bresenham's integer line resterization algorithm for the first octal.
        //The line end points are (xs,ys) and (xe,ye) assumed not equal. All variables are assumed integer.
        //initialize variables
        x=xs
        y=ys
        ∆x = xe -xs
        ∆y = ye -ys
        //initialize e to compensate for a nonzero intercept
        NError =2*∆y-∆x                 //Error =∆y/∆x-0.5
        //begin the main loop
        for i=1 to ∆x
            WritePixel (x, y)
            if (NError >=0) then
                y=y+1
                NError = NError –2*∆x  //Error = Error -1
            end if
            x=x+1
            NError = NError +2*∆y       //Error = Error +∆y/∆x
        next i
        finish

       

  • 4)一般Bresenham算法

     

    • 要使第一个八卦的Bresenham算法适用于一般直线,只需对以下2点作出改造:
      当直线的斜率|k|>1时,改成y的增量总是1,再用Bresenham误差判别式确定x变量是否需要增加1;
      x或y的增量可能是“+1”或“-1”,视直线所在的象限决定。

      程序

       

      • //Bresenham's integer line resterization algorithm for all quadrnts
        //The line end points are (xs,ys) and (xe,ye) assumed not equal. All variables are assumed integer.
        //initialize variables
        x=xs
        y=ys
        ∆x = abs(xe -xs)        //∆x = xe -xs
        ∆y = abs(ye -ys)        //∆y = ye -ys
        sx = isign(xe -xs)
        sy = isign(ye -ys)
        //Swap ∆x and ∆y depending on the slope of the line.
        if ∆y>∆x then
            Swap(∆x,∆y)
            Flag=1
        else
            Flag=0
        end if
        //initialize the error term to compensate for a nonezero intercept
        NError =2*∆y-∆x
        //begin the main loop
        for i=1 to ∆x
            WritePixel(x, y , value)
            if (Nerror>=0) then
                if (Flag) then     //∆y>∆x,Y=Y+1
                    x=x+sx
                else
                    y=y+sy
                end if             // End of Flag
                NError = NError –2*∆x
            end if                 // End of Nerror
             if (Flag) then        //∆y>∆x,X=X+1
                y=y+sy
            else
                x=x+sx
            end if
            NError = NError +2*∆y
        next i
        finish

       

  • 例子

 

 

 



  • 19
    点赞
  • 102
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值