【问题标题】:how do I create a line of arbitrary thickness using Bresenham?如何使用 Bresenham 创建任意厚度的线?
【发布时间】:2023-03-14 14:00:02
【问题描述】:

我目前正在使用 Bresenham 的算法来绘制线条,但它们(当然)粗细是一个像素。我的问题是绘制任意粗细线的最有效方法是什么?

我使用的语言是 C。

【问题讨论】:

  • 重新标记为“语言无关”,因为实现语言并不真正相关。
  • 这是一个相关的 SO 问题:stackoverflow.com/questions/101718/…
  • @banister:你有一些演示要和我们分享吗?

标签: algorithm graphics


【解决方案1】:

再取一个 Bresenham 循环,用它来修改原始直线在矩形方向上的开始和结束位置。 问题是要有效地找到正确的起点,而不是在绘制下一行时绘制任何像素两次(或跳过一个像素)。

可从 Github C code 获得工作和测试的 C 代码。

这里是一个测试页面,其中包含由该代码创建的一些示例行。 黑色像素是算法的起点。

【讨论】:

  • 如果可以的话,我会更多地支持这个答案,因为它是唯一一个将读者引导到 C 中干净、有效的实现——正如 OP 所要求的那样。
  • 这太聪明了!
【解决方案2】:

这是一个 paper and Delphi implementation 的修改版本的 Bresenham 绘制加粗线的算法。

您可能还想看看Anti-Grain Geometry,这是一个用于二维图形的高质量和高性能软件渲染的库。查看demo page 了解它的功能。


关于墨菲修改的布雷森汉姆线图的那篇论文看起来很有用,但仅链接的答案在这里的价值有限,所以这里对其进行一些总结。

有粗细的线是一个矩形。该算法使用外部 Bresenham 循环来沿着矩形的边缘之一步进,而无需实际绘制它。 Bresenham 循环在外循环的每一步绘制一条垂直线。通过不仅传递 (x, y) 坐标,还传递从外环到内环的误差项,它确保所有垂直线都“同相”,确保矩形被填充而没有间隙。每个像素集只设置一次。

【讨论】:

    【解决方案3】:

    我认为最好的方法是画一个矩形而不是一条线,因为有宽度的线是一个二维对象。尝试绘制一组平行线以避免过度绘制(以减少写入带宽)和不足绘制(丢失像素)将非常复杂。从起点和终点以及宽度计算矩形的角点并不难。

    因此,在下面的评论之后,执行此操作的过程是:-

    1. 创建一个与所需线条长度相同且宽度等于所需宽度的矩形,因此 (0,0) 到 (width,length)
    2. 使用 2D 变换将矩形角坐标旋转并平移到所需位置
    3. 使用硬件加速渲染器(例如 OpenGL 四边形*)或使用软件光栅器对旋转的矩形进行光栅化。它可以使用四边形光栅器或一对三角形(例如左上角和右下角)进行渲染。

    注意 *:如果您使用的是 OpenGL,您也可以同时执行第 2 步。当然,使用 OpenGL 确实意味着理解 OpenGL(大而复杂),而这个应用程序可能会使在开发的后期阶段实现这一点变得很棘手。

    【讨论】:

    • 我没有得到您的解决方案。请详细说明一下。
    • 只有当线条完全垂直或水平时才有效。
    • @Jonathan:任意粗细的线可以被认为是一个旋转的矩形,因此不仅限于垂直或水平线。当然,以任意角度绘制矩形会导致锯齿,除非您添加一些抗锯齿以使事物平滑一点。进行抗锯齿是一个完全独立的问题。
    • @Skizz 我的错误。我假设一个旋转的矩形实际上需要旋转一个矩形,这对于试图实现 Bresenham 算法的人来说是困难的。但是,您可以使用两个三角形绘制一个旋转的矩形,所以我撤回我的批评。如果你更新你的答案来描述如何绘制一个旋转的矩形,我会赞成。
    【解决方案4】:

    为了获得最佳精度,尤其是对于较粗的线条也有良好的性能,您可以将线条绘制为多边形。一些伪代码:

    draw_line(x1,y1,x2,y2,thickness)
      Point p[4];
      angle = atan2(y2-y1,x2-x1);
      p[0].x = x1 + thickness*cos(angle+PI/2);
      p[0].y = y1 + thickness*sin(angle+PI/2);
      p[1].x = x1 + thickness*cos(angle-PI/2);
      p[1].y = y1 + thickness*sin(angle-PI/2);
      p[2].x = x2 + thickness*cos(angle-PI/2);
      p[2].y = y2 + thickness*sin(angle-PI/2);
      p[3].x = x2 + thickness*cos(angle+PI/2);
      p[3].y = y2 + thickness*sin(angle+PI/2);
      draw_polygon(p,4)
    

    并且可以选择在每个端点画一个圆。

    【讨论】:

    • 在某种程度上,这将问题简化为更复杂的问题。为了让它工作(在 Python 中),我不得不调整一些东西:a)交换 sin 和 cos。 b) 不是乘以厚度,而是乘以厚度/2.0。
    • @Ant6n 我认为这是错误的角度。这应该是atan2(y2-y1, x2-x1)
    • 那么您希望如何实现draw_polygon(或者,实际上是fill_polygon)?
    • 如何实现 fill_polygon 是一个相当广泛的问题。根据环境和其他要求,有多种技术。在大多数情况下,有合适的库可用于基本绘图(希望使用硬件加速)。否则,一种常见的技术(如果保证多边形是完全凸的,例如在这种情况下)是有两个数组,其中包含多边形每一侧的 x 坐标并在它们之间绘制垂直线。但是您可能应该专门搜索有关绘制填充多边形的问题以获取更多详细信息。
    • “我如何实现这个算法?” “使用这个其他算法” “我如何实现这个其他算法?” “这很复杂”
    【解决方案5】:

    创建几乎任意粗细的线的最简单方法是首先进行 bresenham,然后根据需要应用尽可能多的 dilation 迭代。每个膨胀垫均等地填充线条的两侧。

    编辑:同样值得注意的是,这种方法具有很容易推广到 3D 的优点,因为 Bresenham 和膨胀都可以轻松推广到 3D。

    布雷森汉姆→厚度1:

    膨胀蒙版:

    0 1 0
    1 1 1
    0 1 0
    

    Bresenham + 1 膨胀 → 厚度 2

    Bresenham + 2 膨胀 → 厚度 3

    等等

    【讨论】:

    • 这是一个不错且简单的方法。您将如何为任意尺寸生成合适的蒙版?
    • 厚度取决于膨胀迭代的次数。但你问的很好。我注意到只需要一个面具。请参阅编辑后的答案。
    • 哦,我明白了,您可以反复将相同的蒙版应用于自身以增加大小,聪明! :)(链接页面上dilation的解释有点难懂)
    【解决方案6】:

    一些简单的使用路线:

    1. 对于任何宽度 n,其中 n 为奇数。对于绘制的任何点 p 还绘制 n/2 的上方/下方的点(如果线 > 45 度角改为并排绘制)。
      • 不是一条粗细合适的线,更像是斜体笔,但速度很快。
    2. 对于起点 p(x,y),选取点 t0 和 b,使它们以 p 为中心,但相隔 n 个像素。对于终点做同样的事情,导致 t1 b1。从 t0 -> t1, t1->b1, b1 -> t0, b0 -> t1 画线。填充生成的矩形。
      • 这里的技巧是选择点,使它们看起来与路径方向正交。
    3. 为直线上的每个点 p 画一个圆而不是画一个点。
      • 这具有使端点“干净”的优点,无论方向如何。
      • 除了第一个圆外,应该不需要渲染任何实心圆。
      • 有点慢

    【讨论】:

      【解决方案7】:

      http://members.chello.at/~easyfilter/bresenham.html

      此链接底部的示例是 javascript,但应该很容易适应 C。这是一种相当简单的抗锯齿算法来绘制可变粗细的线条。

      【讨论】:

        【解决方案8】:

        我假设您将绘制从一条边界线到另一条边界线的水平跨度,并在进行时通过 Bresenham 的方法计算每条线的 x 值(在一个循环中)。

        没试过。

        可能需要注意端点,以免它们看起来奇怪地被截断。

        【讨论】:

        • 是的,这也是我想到的策略,我同意终点需要一些思考。
        【解决方案9】:

        对于那些想要 Python 版本的人来说,代码(基于@Fabel 的回答):

        def drawline(x1,y1,x2,y2,**kwargs):  
            if kwargs.get('thickness')==None:
                thickness=1
            else:
                thickness=kwargs['thickness']
            if kwargs.get('roundcap')==None:
                roundcap=False
            else:
                roundcap=True
            angle = np.arctan2(y2-y1,x2-x1)
            xx = np.zeros(4)
            yy = np.zeros(4)
            xx[0] = np.round(x1 + thickness*np.cos(angle+np.pi/2))
            yy[0] = np.round(y1 + thickness*np.sin(angle+np.pi/2))
            xx[1] = np.round(x1 + thickness*np.cos(angle-np.pi/2))
            yy[1] = np.round(y1 + thickness*np.sin(angle-np.pi/2))
            xx[2] = np.round(x2 + thickness*np.cos(angle-np.pi/2))
            yy[2] = np.round(y2 + thickness*np.sin(angle-np.pi/2))
            xx[3] = np.round(x2 + thickness*np.cos(angle+np.pi/2))
            yy[3] = np.round(y2 + thickness*np.sin(angle+np.pi/2))
            u,v=polygon(xx,yy)    
            if roundcap:
                temp1x, temp1y = circle(x1,y1,thickness)
                temp2x, temp2y = circle(x1,y1,thickness)
                u = np.append(u,temp1x,temp2x)
                v = np.append(v,temp1y,temp2y)
            return u,v
        

        调用该函数时,您可以选择指定厚度和圆角。例如:

        drawline(10,10,50,50,thickness=3,roundcap=False)
        

        【讨论】:

          【解决方案10】:

          我经常这样做是为了生成用于多孔介质模拟的纤维和球体图像。我有一个很好的简单方法,使用一种非常标准的图像分析技术,称为“距离变换”。这需要访问一些图像分析包。我将 Python 与 Scipy 一起使用,所以这没问题。这是一个将随机分布的点转换为球体的演示:

          import scipy as sp
          import scipy.ndimage as spim
          
          im1 = sp.rand(100, 100) < 0.995  # Create random points in space
          dt = spim.distance_transform_edt(im1)
          im2 = dt < 5  # To create sphere with a radius of 5
          

          就是这样!对于非常大的图像,距离变换可能会很慢,但是那里有有效的版本。例如,ImageJ 有一个并行化的。显然,要创建粗纤维,您只需创建细纤维的图像,然后应用上面的步骤 2 和 3。

          【讨论】:

            【解决方案11】:

            对于我的嵌入式热敏打印机应用程序,使用 Bresenham 算法,这条线太细了。我没有 GL 或任何花哨的东西。我最终只是简单地减少 Y 值并在第一条线下画更多的线。每增加一个粗细数字就增加了一条线。从单色位图打印到热敏打印效果非常快速。

            【讨论】:

            • 我喜欢你的想法,你是如何测试/计算所需的线之间的距离以便模仿粗细的?是反复试验吗?
            • 每个点下面都有另一个点...没有计算;这条线明显更粗了。我最终使用了 3 行,每行的 Y 值都小于之前的值。任何小于 y 最小值的 y 都更改为 Y 最小值。
            • 对不起,我错过了关于控制粗细和控制线条宽度的困惑。
            • 如果你照你说的做,那么垂直线的粗细总是1,并且总是比粗细要求的长。
            【解决方案12】:

            前段时间我也遇到过同样的问题。 基于这个paper,我创建了一个Matlab 参考实现,我想在GitHub 上分享它。

            【讨论】:

              猜你喜欢
              • 2011-08-11
              • 2021-10-17
              • 2021-05-15
              • 1970-01-01
              • 1970-01-01
              • 2018-01-16
              • 1970-01-01
              • 2017-11-17
              • 2017-10-04
              相关资源
              最近更新 更多