详述Bresenham线生成算法

给定直线的起始点和结束点,在显示设备上绘制该线段是计算机图形学中最基本的操作之一。本文将介绍的 Bresenham Line Drawing Algorithm 即为执行该操作的其中一个较为优化的算法。

计算机显示设备是由一个个像素组成的,该算法的目的即为“点亮”由起始像素点

(x0,x0)(x_0, x_0)

(x1,y1)(x_1, y_1)

举例:

example

为简化后续讨论,设置如下限定条件:

  1. 起始点在结束点左下方;

  2. x0<x1x_0<x_1

    y0<=y1y_0<=y_1

  3. 线段的斜率
    0k10\le k\le 1

  4. 对于区间
    [x0,x1][x_0,x_1]

    xx

    ,该位置仅存在一个像素点。

设置以上限定条件之后,我们可以得到一个十分粗糙的算法:

def easyway(x0,y0,x1,y1):
  m = (y2-y1)/(x2-x1)
  for x in range(x0, x1+1, 1):
    # find y (integer)
    y = round(mx+b)
    colorify(x,y)
复制代码

该算法是可行的,但在循环的每一步都需要计算

mx+bmx+b

yy

值的方法。

推导

由于线段的斜率在0到1之间,因此对于点

(xk,yk)(x_k,y_k)

(xk+1,yk)(x_k+1,y_k)

(xk+1,yk+1)(x_k+1,y_k+1)

kk

表示横坐标

xx

移动的次数,如图所示。

next point

因此,我们需要从

yky_k

yk+1y_k+1

y=mx+by = mx + b

yy

为直线上实际的值,

d1(k)d_1(k)

d2(k)d_2(k)

yky_k

yk+1y_k+1

y=m(xk+1)+bd1(k)=yykd2(k)=(yk+1)yy = m(x_k+1)+b \\ d_1(k) = y-y_k \\ d_2(k) = (y_k+1) - y

比较

d1(k)d_1(k)

d2(k)d_2(k)


  1. d1(k)d2(k)d_1(k) \le d_2(k)

    (xk+1,yk)(x_k+1, y_k)

  2. 反之,认为下个点应为
    (xk+1,yk+1)(x_k+1, y_k+1)

因此,我们只需要判断

d1(k)d2(k)d_1(k) - d_2(k)

d1(k)d2(k)=(yyk)((yk+1)y)=2yykyk1=2(m(xk+1)+b)2yk1=2m(xk+1)2yk+2b1\begin{aligned} d_1(k) - d_2(k) &= (y-y_k) - ((y_k+1)-y) \\ & = 2y - y_k - y_k -1 \\ & = 2(m(x_k+1)+b) - 2y_k - 1 \\ & = 2m(x_k+1) - 2y_k + 2b - 1 \\ \end{aligned}

同时,我们有:

b=y0mx0b = y_0 - mx_0

m=y1y0x1x0=dydxm = \frac{y_1-y_0}{x_1-x_0} = \frac{dy}{dx}

因此,进一步简化得到:

d1(k)d2(k)=2dydx(xk+1)2yk+2(y0dydxx0)1=2dydxxk2yk+c\begin{aligned} d_1(k) - d_2(k) &= 2\frac{dy}{dx}(x_k+1) - 2y_k+2(y_0-\frac{dy}{dx}x_0) -1 \\ &=2\frac{dy}{dx}x_k-2y_k+c \end{aligned}

其中

c=2dydx+2y02dydx1c = 2\frac{dy}{dx}+2y_0-2\frac{dy}{dx}-1

p(k)=d1(k)d2(k)p(k) = d_1(k) - d_2(k)

p(0)=d1(0)d2(0)=2dydx(x0+1)2y0+2(y0dydxx0)1=2dydx1\begin{aligned} p(0) &= d_1(0)-d_2(0) \\ & = 2\frac{dy}{dx}(x_0+1)-2y_0+2(y_0-\frac{dy}{dx}x_0)-1 \\ & = 2\frac{dy}{dx}-1 \end{aligned}

p(k+1)p(k+1)

p(k+1)=2dydxxk+12yk+1+c=2dydx(xk+1)2yk+1+c\begin{aligned} p(k+1) &= 2\frac{dy}{dx}x_{k+1}-2y_{k+1}+c\\ &= 2\frac{dy}{dx}(x_k+1)-2y_{k+1}+c \end{aligned}

所以:

p(k+1)p(k)=2dydx2(yk+1yk)\begin{aligned} p(k+1) - p(k) & = 2\frac{dy}{dx} - 2(y_{k+1}-y_k) \end{aligned}

因此:


  1. pk0p_k \le 0

    d1(k)d2(k)d_1(k) \le d_2(k)

    yk+1=yky_{k+1} = y_k

    p(k+1)=p(k)+2dydxp(k+1) = p(k)+2\frac{dy}{dx}

  2. 反之,
    p(k+1)=p(k)+2dydx2p(k+1) = p(k) + 2\frac{dy}{dx}-2

因此,综合以上,我们的代码即为:

def pk(x0,y0,x1,y1):
  dy = y1-y0
  dx = x1-x0
  p = 2*dy/dx - 1

  base_increment = 2*dy/dx
  colorify(x0,y0)
  while x0 <= x1:
    x0 += 1
    if p > 0:
      y0 += 1
      p -= 2
    p += base_increment
    colorify(x0,y0)
复制代码

整数化

上述算法仍然涉及浮点数的计算,下面我们考虑如何彻底取消浮点相关操作。

观察

p(k)p(k)

的表达式,涉及浮点操作的仅为

dydx\frac{dy}{dx}

dxdx

,注意

dx>0dx>0

P(k)P(k)

p(k)p(k)

正负性相同,有:

P(k)=dxp(k)=2dyxk2dxyk+cP(0)=2dydxP(k+1)P(k)=2dy2dx(yk+1yk)P(k) = dx * p(k) = 2dy * x_k-2dx * y_k+c \\ P(0) = 2dy - dx \\ P(k+1)-P(k) = 2dy - 2dx*(y_{k+1}-y_k)

同样的:


  1. Pk0P_k \le 0

    d1(k)d2(k)d_1(k) \le d_2(k)

    yk+1=yky_{k+1} = y_k

    P(k+1)=P(k)+2dyP(k+1) = P(k)+2dy

  2. 反之,
    P(k+1)=P(k)+2dy2dxP(k+1) = P(k) + 2dy-2dx

代码即变为:

def pk(x0,y0,x1,y1):
  dy = y1-y0
  dx = x1-x0
  p = 2*dy - dx

  base_increment = 2*dy
  colorify(x0,y0)
  while x0 <= x1:
    x0 += 1
    if p > 0:
      y0 += 1
      p -= 2*dx
    
    p += base_increment
    colorify(x0,y0)
复制代码

一般化

之前的讨论是基于文首给出的限定条件的:

  1. 起始点在结束点左下方;

  2. x0<x1x_0<x_1

    y0<=y1y_0<=y_1

  3. 线段的斜率
    0k10\le k\le 1

  4. 对于区间
    [x0,x1][x_0,x_1]

    xx

    ,该位置仅存在一个像素点。

下面我们将该算法一般化。

垂直线

x0=x1x_0 = x_1

yy

每次递增单位长度,然后染色即可。

if dx == 0:
  while y1!=y2:
    y1 += stepy
    colorify(x1,y1)
复制代码

dy<0, dx<0

当 dy<0 时,如下图:

dy<0

此时我们将点

(x1,y1)(x_1,y_1)

(x1,y1)(x_1,y_1')

y1y0=y0y1y_1'-y_0 = y_0 - y_1

yy

的情况,我们递减

yy

,则可以对称的绘制出原需要绘制的直线。

同理,当 dx < 0 时,如下图:

dx<0

同样的,此时我们将点

(x1,y1)(x_1,y_1)

(x1,y1)(x_1',y_1)

x1x0=x0x1x_1'-x_0 = x_0 - x_1

xx

的情况,我们递减

xx

,则可以对称的绘制出原需要绘制的直线。

当 dx 和 dy 均小于0时,需要对原线段作两次变换。

dx = x1-x0
dy = y1-y0
if dy < 0:
    dy = -dy
    stepy = -1
else:
    stepy = 1

if dx < 0:
    dx = -dx
    stepx = -1
else:
    stepx = 1

#  ....

while x0 != x1:
    x0 += stepx

    if p >= 0:
        y0 += stepy
        # ....
    
    # ....

# ....
复制代码

m > 1

当直线斜率大于 1 时,即 dy>dx,原直线方程为

y=mx+by=mx+b

x=1mybmx = \frac{1}{m}y-\frac{b}{m}

1m<1\frac{1}{m} < 1

Put it together

def bresenham(p1,p2):
    x0, y0 = p1
    x1, y1 = p2
    dx = x1-x0
    dy = y1-y0
    if dy < 0:
        dy = -dy
        stepy = -1
    else:
        stepy = 1

    if dx < 0:
        dx = -dx
        stepx = -1
    else:
        stepx = 1
    dy <<= 1
    dx <<= 1
    colorify(x0, y0)
    if dx == 0:
        while y0 != y1:
            y0 += stepy
            colorify(x0, y0)
    if dx > dy:
        fraction = dy-(dx >> 1)
        while x0 != x1:
            x0 += stepx

            if fraction >= 0:
                y0 += stepy
                fraction -= dx

            fraction += dy
            colorify(x0, y0)
    else:
        fraction = dx - (dy >> 1)
        while y0 != y1:
            if fraction >= 0:
                x0 += stepx
                fraction -= dy
            y0 += stepy
            fraction += dx
            colorify(x0, y0)
复制代码

时间复杂度为

O(x2x1)O(x_2-x_1)

O(1)O(1)

实际应用

使用 python 中的 matplotlib 模块对该算法进行可视化。

(1,1) -> (2,10):
example1

(1,1) -> (10,1):

example2

(10,8) -> (1,1):

example3

源码

github

参考

csustan.csustan.edu/~tom/Lectur…

en.wikipedia.org/wiki/Bresen…