Bresenham算法

参考其他:http://liar.pangwa.com/2009/04/12/bresenham/

 

直线光栅化

直线光栅化是指用像素点来模拟直线 . 比如下图中用蓝色的像素点来模拟红色的直线 . 图中坐标系是显示器上的坐标系 : x 轴向右 , y 轴向下 .

Bresenham算法_第1张图片

deltaX = endX – startX, deltaY = endY – startY. 那么斜率为 k = deltaY / deltaX. 我们先考虑简单的情况 : 0 < k < 1 即直线更贴近 x . 在这种情况下 deltaY < deltaX, 所以在光栅化的过程中 , y 轴上描的点比在 x 轴上描点少 . 那么就有一个很直观的光栅化算法 :

line_bresenham(startX, startY, endX, endY)

{

    deltaX = endX - startX;

    deltaY = endY - startY;

    k = deltaY / deltaX;

 

    for (x = startX, y = startY; x <= endX; ++x)

    {

         if ( 满足一定条件 )

        {

            ++y;

        }

        drawPixel(x, y);

    }

}

基于斜率 / 距离的两个简单直线光栅化算法

好了,貌似很简单 , 就剩一个问题 : “ 满足一定条件 是什么 ? 可以用斜率判断 , 也可以用上图中直线与光栅线交点 ( 红点 ) 与光栅点 ( 蓝点 ) 的距离来判断 . 继续用伪代码说话 :

// 算法 1: 用斜率判断

void line_bresenham_k(startX, startY, endX, endY)

{

    deltaX = endX - startX;

    deltaY = endY - startY;

    k = deltaY / deltaX;

 

    for (x = startX, y = startY; x <= endX; ++x)

    {

        if (x - startX != 0)

        {

            // 计算当前斜率

            currentK = (y - startY) / (x - startX);

 

            // 如果当前斜率 < k, 则增加 y 坐标

            if (currentK < k)

            {

                ++y

            }

        }

        drawPixel(x, y);

    }

}

 

// 算法 2: 用距离判断 . 计算直线与光栅线交点 y 坐标我们需要用到

// 直线方程 y = k (x - startX) + startY

line_bresenham_dist(startX, startY, endX, endY)

{

    deltaX = endX - startX;

    deltaY = endY - startY;

    k = deltaY / deltaX;

 

    for (x = startX, y = startY; x <= endX; ++x)

    {

        // 计算直线与光栅线交点的 y 坐标 , 以及与光栅点的距离

        ptY = k * (x - startX) + startY;

        dist = ptY - y;

 

        // 如果距离 > 0.5 或者 < -0.5, 说明我们需要增加 y

        // 将距离的绝对值控制在 0.5 之类

        if (dist > 0.5 || dist < -0.5)

        {

            ++y;

        }

        drawPixel(x, y);

    }

}

消灭浮点数 !

以上都是很直观的算法 , 下面不直观的来了 上面的算法都需要在循环体内执行乘法 , 准确的说 , 是进行浮点数的乘法 . 我们怎么能减少这些浮点数的乘法开销呢 ? 以基于距离的算法 2 为例 : 首先 , k 是一个浮点数 , 0.5 也是浮点数 . 我们可以通过将这些表达式都乘以 2 * deltaX ( 整数 ) 来解决浮点数的问题 . 伪代码 :

// 算法 3: 在算法 2 的基础上消灭浮点数 !

line_bresenham_dist(startX, startY, endX, endY)

{

    deltaX = endX - startX;

    deltaY = endY - startY;

 

    for (x = startX, y = startY; x <= endX; ++x)

    {

        // 计算直线与光栅线交点的 y 坐标 , 以及与光栅点的距离

        ptY1 = deltaY * (x - startX) + startY * deltaX;

        dist1 = ptY1 - y * deltaX;

        dist1 = dist1 << 1; // dist1 = dist1 * 2

 

        // 如果距离 > 0.5 或者 < -0.5, 说明我们需要增加 y

        // 将距离的绝对值控制在 0.5 之类

        if (dist1 > deltaX || dist < -deltaX)

        {

            ++y;

        }

        drawPixel(x, y);

    }

}

解析如何消灭浮点数

ptY 为直线与 Y 轴交点

ptY = k * (x - startX) + startY; 等同于 ptY = dy/dx * (x – startX) + startY; 其中 dy/dx 是浮点数 , 两边同时乘以 dx 即可消灭浮点数。

ptY*dx = dy*(x – startX) + dx*startY;

dy*(x – startX) + dx*startY – y*dx > 0.5 < -0.5 。而 0.5 也是浮点数,则两边同时乘以 2.

2*dy*(x – startX) + 2*dx*startY – 2*y*dx > 1 < -1 。而目前考虑直线在第一象限,则只要考虑 2*dy*(x – startX) + 2*dx*startY – 2*y*dx > 1

算法为

line_bresenham_dist (int startX , int startY , int endX , int endY )

{

    int dx = endX - startX ;

    int dy = endY - startY ;

    for (int x = startX , int y = startY ; x <= endX ; ++x )

    {

        if (2*dy *(x startX ) + 2*dx *startY 2*y *dx > 1)

        {

            y ++;

        }

        drawPixel (x , y );

    }

}

消灭乘法 !

圆满解决浮点数运算问题 ! 不过 乘法运算还在 . 消灭乘法问题的办法比较不直观 , 让我们想一想 : 还有什么办法能简化运算 . 直线方程已经不能再简化 , 所以唯一的突破口就是能不能利用递推 / 用上一次循环的计算结果推导下一次循环的计算结果 .

首先我们来看看在算法 2 的基础上 ( 因为算法 2 计算红点蓝点之间的距离 , 比较直观 ), 怎么通过第 n – 1 次循环计算出的 dist ( 设为 d1) 来推导出第 n 次循环的 dist ( 设为 d2). 先回顾一下 : dist = 直线与光栅线交点的 y 坐标 相应光栅点的 y 坐标 . 我们从几何上直观地考虑 : 在第 n 次循环中 , 我们先根据上一次循环所计算出来的 d1, 暂时令 d2 = d1 + k, 因为我们要保证 -0.5 < d2 < 0.5, d1 + k 满足 d1 + k > –0.5, 所以我们只需要考虑当 d1 + k > 0.5 , 我们需要将光栅点 y 坐标增加 1, 并且将 d2 减去 1. 显然 , y1 是第 n – 1 次循环中光栅点的 y 坐标 , y2 是第 n 次循环中光栅点的 y 坐标 . 我们有
1) d2 = d1 + k –  (y2 – y1)
2)
d1 + k > 0.5 y2 = y1 + 1, 否则 y2 = y1
我们已经能根据上面的两个关系式写出算法 , 不过为了消除乘法和浮点数 , 我们将这两个关系式两端同时乘以 2 * deltaX, 并且设 e = 2 * deltaX * d, 则我们有
3) e2 =  e1 + 2 * deltaY – 2 * deltaX * (y2 – y1)
4)
e1 + 2 * deltaY > deltaX y2 = y1 + 1, 否则 y2 = y1
终于 , 没有了乘法 (2 * deltaY 在循环体外计算且被简化为左移一位的运算 ), 没有了浮点数 , 根据关系式 3) 4), 写出算法 :

// 算法 4: 在算法 2, 3 的基础上利用递推消灭乘法和浮点数 !

line_bresenham(startX, startY, endX, endY)

{

    deltaX = endX - startX;

    deltaY = endY - startY;

    e = 0;

    deltaX2 = deltaX << 1;

    deltaY2 = deltaY << 1;

 

    drawPixel(startX, startY);

 

    for (x = startX + 1, y = startY; x <= endX; ++x)

    {

        // 关系式 3) e2 =  e1 + 2 * deltaY – 2 * deltaX * (y2 – y1)

        // 关系式 4) e2 + 2 * deltaY > deltaX y2 = y1 + 1, 否则 y2 = y1

         e += deltaY2;

        if (e > deltaX)

        {

            e -= deltaY2;

            ++y;

        }

        drawPixel(x, y);

    }

}

解析如何消灭乘法

e 为误差 , 开始为 0

line_bresenham_dist (int startX , int startY , int endX , int endY )

{

    double dx = endX - startX ;

    double dy = endY - startY ;

    double k = dy /dx ;

    double e = 0.0;

    drawPixel (startX ,startY );

    for (int x = startX +1, int y = startY ; x <= endX ; ++x )

    {

        e += k ;

        if (e > 0.5)

        {

            e -= 1;

            y ++;

        }

        drawPixel (x , y );

    }

}

两边同时乘以 2*dx

line_bresenham_dist (int startX , int startY , int endX , int endY )

{

    int dx = endX - startX ;

    int dy = endY - startY ;

    int dx2 = dx <<1;

    int dy2 = dy <<1;

    int e = 0*2*dx ;

    drawPixel (startX ,startY );

    for (int x = startX +1, int y = startY ; x <= endX ; ++x )

    {

        e += dy2 ;

        if (e > dx )

        {

            e -= dx2 ;

            y ++;

        }

        drawPixel (x , y );

    }

}

 

 

你可能感兴趣的:(Bresenham算法)