• 直线算法


    DDA画线算法

    一.算法介绍

    DDA是一种增量算法,也就是说通过对前一个点在X和Y轴方向上加上一个增量,从而得到一个新点得坐标。这个算法要求先算出直线的斜率,然后从起点开始,确定最佳逼近于直线

    的y坐标。假设起点的坐标为整数,直线方程为y=kx+b,k的取值在0到1之间,x每递增1,y相应地递增k。因为像素的坐标是整数,所以y需要进行取整处理。对新坐标行四舍五入得到

    整型y值,确定一个要渲染得像素点。从而画出一条直线。

    二.算法解析:

    当 | k | <= 1时:

    已知过端点 P0(x0, y0),P1(x1, y1) 的直线段 L(P0, P1);直线斜率为 k = (y1 - y0) / (x1 - x0)。于是 yi+1 = kxi+1 + b。

    1.x每递增1,y相应地递增k,每次计算点的坐标,我只需要x+1,y+k就行了,避免了乘除,而只有加减。

               ↓

    2.通过增量得到下一个点后,可知此坐标肯定会与实际像素点有偏差(实际的直线并不是和像素点完全重合的)。所以采取四舍五入得方式来平衡差值,但是

    C/C++中取整是直接舍去小数值,所以采取加上0.5再取整,即int(y+0.5)。从而达到一个四舍五入的目的。

    当 | k | > 1时:

    若仍然使 k = (y1 - y0) / (x1 - x0),则会导致斜率大于1。从而在x递增1的时候y可能会出现递增大于1的数,使得像素点不相邻,直线不连续,出现断点的情况。

    此时应令k = (x1 - x0) / (y1 - y0),然后执行y+1,x+k 。最后再对x四舍五入int(x+0.5)。

    三、代码

    // 数值微分法,伪代码。通过此代码可理解算法的基本思路

    当 | k | <= 1时:

    当 | k | > 1时:

    综上可得:

    中点画线算法

          图1                    图2

    引用这2个图是因为实际像素为图1,图2中为了方便算法的描绘,采用像素中心来代替整个像素点。所以不要单纯由图2产生错误理解,要联系实际的物理分布。

    一、算法介绍

    这里的中点指的是直线与虚拟网格交点Q的上下像素点的连线中点M。将M与Q点进行比较,若Q与M重合或者在M之下,则渲染P1点。else,渲染上方

    的P2点。从而渲染出一条直线。

    二、算法解析

    1.设坐标的单位为1,斜率k<1,起点和终点为 (x0, y0)和(x1, y1),则F(x, y) = ax + by + c, 其中a = y0 - y1, b = x1 - x0,c = x0y1 - x1y0

    a,b,c是如何得到的?

    解:

    已知直线上两点求直线的一般式方程 

    已知直线上的两点P1(X1,Y1) P2(X2,Y2), P1 P2两点不重合。
    对于AX+BY+C=0:
    当x1=x2时,直线方程为x-x1=0
    当y1=y2时,直线方程为y-y1=0
    当x1≠x2,y1≠y2时,直线的斜率k = (y2-y1) / (x2-x1)
    故直线方程为y-y1 = (y2-y1) / (x2-x1) × (x-x1)
    即x2y-x1y-x2y1+x1y1 = (y2-y1)x - x1(y2-y1)
    即(y2-y1)x-(x2-x1)y-x1(y2-y1)+(x2-x1)y1=0
    即(y2-y1)x+(x1-x2)y+x2y1-x1y2=0 ①
    可以发现,当x1=x2或y1=y2时,①式仍然成立。所以直线AX+BY+C=0的一般式方程就是:
    A = Y2 - Y1
    B = X1 - X2
    C = X2*Y1 - X1*Y2
     

    2.算法流程

    当 | k | <= 1时

    对于直线上的点,F(x, y) = 0;直线上方的点,F(x, y) > 0;直线下方的点,F(x, y) < 0.  因此,判断M在Q的上方还是下方,只需将M点带入方程计算判别式:

    d = F(M) = F(xp + 1, yp + 0.5) = a(xp + 1) + b(yp + 0.5) + c(下一个点x轴加1,y轴取中点所以加0.5
                 ↓
    当d >= 0时,取正右方像素p1也就是取直线下方的点。(此处易产生误解,要记住代入的是中点,F(M)>=0说明中点在直线之上或在直线上所以取靠近直线的点,也就是下方的点
    在这种情况下欲判断再下一个像素应取哪个,应计算:d1 = F(xp +1 + 1, yp + 0.5) = a(xp + 1 + 1) + b(yp + 0.5) + c  = d + a,故增量为a。
    解释:要渲染的点是直线下方的点,也就是(xp + 1, yp),所以再下一个中点仍然是(xp + 1 +1, yp + 0.5)。只要d>=0则中点y值不改变。
                 ↓
    当d<0时,此时则取右上方的像素p2,这种情况下要判断再下一个像素,则应该计算:
    d2 = F(xp +1 + 1, yp + 1 + 0.5) = a(xp + 1 + 1) + b(yp + 1 + 0.5) + c  = d + a + b,d的增量为 a + b。
    解释:下一个点的坐标是(xp + 1, yp + 1),再下一个中点是(xp + 1 +1, yp + 1 + 0.5),只要d<0则中点y值+1。
                ↓
    d的初始值,第一个像素应取左端点(x0, y0),相应的判别式为:d0 =F(x0 + 1, y0 + 0.5) = a(x0 + 1) + b(yp + 0.5) + c=  ax0 + by0 + c + a + 0.5b= F(x0, y0) + a + 0.5b
    由于(x0, y0)在直线上,所以F(x0, y0) = 0,因此,d的初始值为a + 0.5b。d的初始值包含小数,因此可以用2d来代替d。

    解释:为何要用2d代替d?这是为了来摆脱浮点运算,只进行整数的加法,相比于DDA算法进一步提高了效率。而d只是用来比较正负,所以乘以倍数不会改变符号。

     

    当 | k | > 1时

    方法与DDA同理

     

    三、代码

    以下代码为|k|<=1时,只包含整数运算的方法:

    void MidPointLine(int x0, int y0, int x1, int y1, int color)

    {

         int a, b, delta1, delta2, d, x, y;

         a = y0 - y1;

         b = x1 - x0;

         d = 2 * a + b;      //求d的初值

         delta1 = 2 * a;

         delta2 = 2 * (a + b);   //两种不同情况下的增量

         x = x0;

         y = y0;

         DrawPixel(x, y, color);

         while (x < x1)

         {

              if (d < 0)

              {

                   ++x;      //取右上方的点

                   ++y;

                   d += delta2;

              }

             else

              {

                   ++x;      //取正右方的点

                   d += delta1;

              }

              DrawPixel(x, y, color);

         }

    }
     
     
    Bresenham画线算法
    一、算法介绍
    该算法是通过各行、各列像素中心构造一组虚拟网格线,按照直线起点到终点的顺序,计算直线与各垂直网格线的交点,然后根据误差项的符号确定该列像素点中与此交点最近的元素。

    二、算法解析

    y

    1.如图,假设每次x + 1,而y要么不变,要么递增1,是否递增1取决于误差项d的值(如上图所示)。因为直线起始点在像素中心,所以误差项d的初始值为0,x下标每增加1,d的值相应递增直线的斜率值,即d = d + k.  一旦d >= 1,将它的值减去1,使得d的值总保持在0到1之间。

    当d >= 0.5时,直线与x + 1列垂直网格线交点最接近于当前像素的右上方像素点(x + 1, y + 1),d < 0.5时则为正右方像素(x + 1, y)。

    2.为了将算法也改进为整数加减,所以要进行变动。

    改进1:大小判定简化到正负判定

    令 e = d - 0.5,当e >= 0时 ,下一个像素值y递增1,e < 0时y不递增。此时e初始值为-0.5。每走一步e=e+k,当渲染后需再次对e进行判定,以保证其相对区间。

    当e>0时,e=e-1。(此处-1操作会产生误解,可以转换对象来看,e只是为了转换d的判别形式,所以可以直接看d。e>0与d>0.5一致,下一次取点d>1,此时d-1所以e-1

     ↓

    改进2:去除0.5这个浮点数运算

    两边同乘2得:2e=2d-1,2e=2e+2k

    改进3:去除求斜率k=dy/dx的除法运算

    两边再同乘dx得:2dx*e = 2dx*d - dx,2dx*e = 2dx*e + 2dx*k = 2dx*e + 2dy。令e=2dx*e得,e=e-dx,e=e+2dy。同理,在e>0时执行的e=e-1变为e=e-2dx。

    结果:初始值d=0,化简上式可得 ①e0 = -dx   ②e = e + 2dy ③e=e-2dx

    最后总结一下具体步骤:

    1.输入直线的两个端点P0(x0,y0)和P1(x1,y1)

    2.计算初始值Δx、Δy、e=-Δx、x=x0,y=y0

    3.绘制点(x,y)

    4.e更新为e+2Δy,判断e的符号,若e>0,则(x,y)更新为(x+1,y+1),同时将e更新为e-2Δx;else 将(x,y) 更新为(x+1,y)

    5.当直线没有画完时,重复3和4,否则结束。

    三、代码

    //伪代码如下

    void Bresenham(int x0, int y0, int x1, int y1, int color)

    {

       int x, y, dx, dy;

       float k, e;

       dx = x1 - x0;

       dy = y1 - y0;

       e = -dx

       x = x0;

       y = y0;

       for (int i = 0; i <= dx; ++i)

       {

          DrawPixel(x, y, color);

          x += 1;

          e = e + 2 * dy;

          if (e >= 0)   // 伪代码,实际比较浮点数不这样进行比较

          {

              y += 1;

              e = e - 2 * dx;

          }

       }

    }

     

    三种算法的对比

    一、DDA

    采用的是浮点数运算,不易于硬件实现。

    二、中点画线算法

    只有整数运算,不含乘除,可用硬件实现。相对于DDA提高了效率

    三、Bresenham算法

    1.在渲染点的循环中没有计算直线的斜率,不用做除法。

    2.不用浮点数,只做整数加法和乘2运算,乘2运算可用硬件位移实现。

    3.应用范围不拘束于直线的方程形式

    4.Bresenham算法”其实“比较像数值微分法,也是增量算法,但是相对来说更利于硬件实现。

     

    四、总结

    中点画线算法比DDA效率更高,Bresenham算法比中点画线算法的应用范围更广。

  • 相关阅读:
    图标库
    AndroidManifest中注册application
    两个App之间的跳转 并传值
    Fresco加载显示gif图片
    弹出PopupWindow背景变暗的实现
    判断网络是否可用
    Java的安全性和可移植性
    DBUtils
    Observer
    IO
  • 原文地址:https://www.cnblogs.com/jingrui/p/9644296.html
Copyright © 2020-2023  润新知