[AlgorithmStaff] Bresenham快速直线算法

操作系统:Windows8.1

显卡:Nivida GTX965M

开发工具:Unity2017.3 | NativeC



最近在学习 Unity tilemap Brush 自定义笔刷功能时候,看到其直线笔刷 LineBrush 是采用 Bresenham 算法实现,故借此机会在这里记录下学习过程,并在最后给出完整实现。

Introduction



Bresenham 是光栅化的直线算法,或者说是通过像素来模拟直线。比如下图所示像素点来模拟红色的直线。

给定两个起点 P1(x1, y1) | P2(x2, y2),如何绘制两点之间的连线呢。这里假设斜率约束在,那么算法的过程如下:

  1. 绘制起点 (x1, y1)
  2. 绘制下一个点, X坐标加1,判断是否到终点,如果是则算法完成。否则找下一个点,由上图可知将要绘制的点不是右邻点,要么就是右上邻接点。
  3. 绘制点。
  4. 跳回第二步骤。
  5. 结束。

算法具体过程就是在每次绘制点的时候选取与直线的交点y坐标的差最小的那个点,如下图所示:

那么问题聚焦在如何找最近的点,逻辑上每次 x 都递增 1y 则增加 1 或不增加。具体上图,假设已经绘制到了 d1 点,那么接下来 x1,但是选择 d2 还是 u 点呢,直观上可以知道 d2 与目标直线和 x + 1 直线的交点比较近,即纵坐标之差小。换句话说 (x + 1, y + 1) 点纵坐标差大于 0.5。 所以选择 d2 ,其他点也按照该规则执行。

The Basic Bresenham



假设以 (x, y) 为绘制起点,一般情况下的直观想法是先求 m = dy /dxy 的增量),然后逐步递增 x, 设新的点为 x1 = x + j ,则 y1 = round(y + j * m) 。可以看到,这个过程涉及大量的浮点运算,效率上是比较低的(特别是在嵌入式应用中,DSP可以一周期内完成2次乘法,一次浮点却要上百个周期)。

下面我们来看一下 Bresenham 算法,如图1, (x, y +ε) 的下一个点为 (x, y + ε + m), 这里 ε 为累加误差。可以看出,当 ε+m < 0.5 时,绘制 (x + 1, y) 点,否则绘制 (x + 1, y + 1) 点。每次绘制后, ε 将更新为新值:

ε = ε + m ,如果 (ε + m) <0.5 (或表示为 2 * (ε + m) < 1 )

ε = ε + m – 1,其他情况

将上述公式都乘以 dx ,并将 ε * dx 用新符号 ξ 表示,可得

ξ = ξ + dy,如果 2 * (ξ + dy) < dx

ξ = ξ + dy – dx,其他情况

可以看到,此时运算已经全变为整数了。以下为算法的伪代码:

ξ ← 0,y ← y1

For x ← x1 to x2 do

  Plot Point at (x, y)

  If (2(ξ + dy) < dx)

    ξ ←ξ + dy

  Else

    y ← y + 1,ξ ←ξ + dy – dx

  End If

End For

Handing multiple slopes


在实际应用中,我们会发现,当 dy > dx 或出现上图右侧情况时,便得不到想要的结果,这是由于我们只考虑 dx > dy, 且 (x, y) 的增量均为正的情况所致。经过分析,需要考虑 8 种不同的分区情况,如下图所示:

当然,如果直接在算法中对8种情况分别枚举, 那重复代码便会显得十分臃肿,因此在设计算法时必须充分考虑上述各种情况的共性。比如右侧 X 正负 45 度分区仅仅是互为 Y 轴镜像的关系,在具体实现的时候设定正确增量方向即可。

Implementation



下面给出基于C语言的实现:

void draw_line(int x1, int y1, int x2, int y2)
{
    int dx = x2 - x1;
    int dy = y2 - y1;
    int ux = ((dx > 0) << 1) - 1;
    int uy = ((dy > 0) << 1) - 1;
    int x = x1, y = y1, eps;

    eps = 0; dx = abs(dx); dy = abs(dy);
    if (dx > dy)
    {
        for (x = x1; x != x2; x += ux)
        {
            printf("x = %d y = %d\n", x, y);
            eps += dy;
            if ((eps << 1) >= dx)
            {
                y += uy; eps -= dx;
            }
        }
    }
    else
    {
        for (y = y1; y != y2; y += uy)
        {
            printf("x = %d y = %d\n", x, y);
            eps += dx;
            if ((eps << 1) >= dy)
            {
                x += ux; eps -= dy;
            }
        }
    }
}

测试数据分别为绿色线段,起点 (1,1)  终点 (10,10)  和 蓝色线段,起点 (1, 10)  终点 (10,1) 。

测试数据分别为褐色线段,起点 (2,10)  终点 (4,1) 及起点 (6,9)  终点 (10,1)

通过将程序运行的测试数据填充的表格后,可以很直观的看到两点之间线段的连接路径。

Based on Unity



Unity 官方的例子中已经给出了基于C#实现,代码如下:

// http://ericw.ca/notes/bresenhams-line-algorithm-in-csharp.html
        public static IEnumerable<Vector2Int> GetPointsOnLine(Vector2Int p1, Vector2Int p2)
        {
            int x0 = p1.x;
            int y0 = p1.y;
            int x1 = p2.x;
            int y1 = p2.y;

            bool steep = Math.Abs(y1 - y0) > Math.Abs(x1 - x0);
            if (steep)
            {
                int t;
                t = x0; // swap x0 and y0
                x0 = y0;
                y0 = t;
                t = x1; // swap x1 and y1
                x1 = y1;
                y1 = t;
            }
            if (x0 > x1)
            {
                int t;
                t = x0; // swap x0 and x1
                x0 = x1;
                x1 = t;
                t = y0; // swap y0 and y1
                y0 = y1;
                y1 = t;
            }
            int dx = x1 - x0;
            int dy = Math.Abs(y1 - y0);
            int error = dx / 2;
            int ystep = (y0 < y1) ? 1 : -1;
            int y = y0;
            for (int x = x0; x <= x1; x++)
            {
                yield return new Vector2Int((steep ? y : x), (steep ? x : y));
                error = error - dy;
                if (error < 0)
                {
                    y += ystep;
                    error += dx;
                }
            }
            yield break;
        }

最后将之前的两组数据带入Unity验证结果一致性:

可以看到前文程序算法输出的数据结果与Unity采用的 C# 实现结果一致。

Summary



参考资料:

http://blog.csdn.net/jinbing_peng/article/details/44797993

https://www.cnblogs.com/gamesky/archive/2012/08/21/2648623.html

http://www.cnblogs.com/pheye/archive/2010/08/14/1799803.html

原文地址:https://www.cnblogs.com/heitao/p/8151487.html

时间: 2024-07-30 13:34:20

[AlgorithmStaff] Bresenham快速直线算法的相关文章

用中点Bresenham画直线算法绘制任意斜率直线

使用VC 6.0 mfc实现编程 刚学的图像学,挺难学的,show 代码吧 void CLineView::OnDraw(CDC* pDC) { CLineDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here int x1 ,x2 ,y1,y2 ; x1 = 0 ; y1 = 0 ;  x2 = 1000 ; y2 = 500 ; COLORREF c = RGB(25

Bresenham快速画直线算法

现在的计算机的图像的都是用像素表示的,无论是点.直线.圆或其他图形最终都会以点的形式显示.人们看到屏幕的直线只不过是模拟出来的,人眼不能分辨出来而已.那么计算机是如何画直线的呢,其实有比较多的算法,这里讲的是Bresenham的算法,是光栅化的画直线算法.直线光栅化是指用像素点来模拟直线,比如下图用蓝色的像素点来模拟红色的直线 给定两个点起点P1(x1, y1), P2(x2, y2),如何画它们直连的直线呢,即是如何得到上图所示的蓝色的点.假设直线的斜率0<k>0,直线在第一象限,Brese

图形学--(中点画线法+Bresenham画线算法)

编程环境:codeblocks+EGE库 用到的函数:putpixel(int x1,int y1,int color)  用某种颜色打亮一个坐标点. 这俩种算法都是用来在计算机上画一条直线的,那么我们为什么不直接用直线方程分别带点再打亮呢,这是因为,计算机中每个坐标点都是整数,而直线是由一个个像素点组合而成的,那么,直接将坐标点再进行四舍五入整数化就好了啊,的确,这是一种方法,但计算机中进行浮点数的四舍五入会使运算效率变差,因此真正画直线时是用到上边这俩种方法的. 1.中点画线法 只考虑当直线

Bresenham画线算法详解及其OpenGL编程实现

http://blog.csdn.net/xiajun07061225/article/details/7018719 Bresenham是由Bresenham提出的一种精确而有效地光栅线生成算法,该算法仅使用增量整数计算.另外,它还可以用于显示其它曲线. 我们以斜率大于0小于1的线段来进行考虑.以单位x间隔进行取样.每次绘制了当前像素点(xk,yk)之后,需要确定下一个要绘制的点是(xk+1,yk)还是(xk+1,yk+1),需要判断哪一个点像素点更接近线路径. 在取样位置,我们使用d1和d2

JAVA算法4——连通性问题之路径压缩的加权快速合并算法

能否找到一个保证线性时间性能的算法,这个问题非常难.还有一些改进加权快速合并算法的简单方法.理想情况下,我们希望每个结点直接连到其树根,但又不想像快速合并算法那样改变大量连线.我们可以简单地把所检查的所有结点连到根上,从而接近理想情况.我们可以很容易地实现此方法,方法名为压缩路径,在合并操作时,经过每条路径就加一条连线,也就是把一路上遇到的对应于每个顶点的id数组值都设为连到树根上.净结果就是几乎完全把树变平坦了,逼近快速查找法所获得的理想状态. 还有其他许多方法来实现路径压缩下面程序实现路径压

高维数据的快速最近邻算法FLANN

1.     简介 在计算机视觉和机器学习中,对于一个高维特征,找到训练数据中的最近邻计算代价是昂贵的.对于高维特征,目前来说最有效的方法是 the randomized k-d forest和the priority search k-means tree,而对于二值特征的匹配 multiple hierarchical clusteringtrees则比LSH方法更加有效. 目前来说,fast library for approximate nearest neighbors (FLANN)

[转]快速平方根算法

在3D图形编程中,经常要求平方根或平方根的倒数,例如:求向量的长度或将向量归一化.C数学函数库中的sqrt具有理想的精度,但对于3D游戏程式来说速度太慢.我们希望能够在保证足够的精度的同时,进一步提高速度. Carmack在QUAKE3中使用了下面的算法,它第一次在公众场合出现的时候,几乎震住了所有的人.据说该算法其实并不是Carmack发明的,它真正的作者是Nvidia的Gary Tarolli(未经证实). // // 计算参数x的平方根的倒数 // float InvSqrt (float

快速模糊算法

前段时间在网上看到一个快速模糊算法,性能很不错. 源博客: http://www.lellansin.com/super-fast-blur-%E6%A8%A1%E7%B3%8A%E7%AE%97%E6%B3%95.html 博主对其进行了简单的bug修正以及性能优化. 在博主机子上使用该算法对一张5000x3000的图片进行模糊处理,仅需500-600毫秒,速度非常快. 代码如下: /* * Super Fast Blur v1.1+ * Original author: Mario Klin

稀疏矩阵的普通转置与快速转置算法

稀疏矩阵的普通转置与快速转置算法 一般来说,对于系数矩阵,我们使用三元组来存储.即就是将矩阵的所有非零元素的三元组存放在一个顺序表中,如图所示: 注意一个转置的前提:该顺序表是排好序的,即行优先,列其次. 一.普通转置 这种算法比较简单,也很容易想到: 算法思想: 对M.data从头至尾扫描: ?第一次扫描时,将M.data中列号为1的三元组赋值到T.data中 ?第二次扫描时,将M.data中列号为2的三元组赋值到T.data中 ?依此类推,直至将M.data所有三元组赋值到T.data中 代