小白谈计算机图形学(一)画线篇之DDA算法,中点画线法,Bresenham画线法及相关改进详解
- 引言
- 如何画线
- 基本思想
- 数值微分法(DDA算法)
- 中点画线法
- Bresenham画线法
- Bresenham基本思路
- Bresenham画线改进
- 小结
- 超链接
引言
大家好,众所周知,计算机的图像显示是由一个一个像素组成,正如分子组成了世界,当像素合理分布,同样可以还原很多真实世界的场景。
如何画线
基本思想
已知过端点 p 0 ( x 0 , y 0 ) p_0(x_0,y_0) p0(x0,y0)和 p 1 ( x 1 , y 1 ) p_1(x_1,y_1) p1(x1,y1)的直线:
y = k x + b y=kx+b y=kx+b
直观的做法是把每个 x x x的值代入直线方程求出相应的 y y y值。取像素点 ( x , i n t ( y ) ) (x, int(y)) (x,int(y))作为当前点坐标。光栅直线算法属于图形学最底层的算法,因此要精益求精。
数值微分法(DDA算法)
数值微分基本思路
增量算法变乘法为加法,这里利用斜截式,首先 y i + 1 = y i + k Δ x y_{i+1}= y_i+ k\Delta x yi+1=yi+kΔx,当 Δ x = 1 \Delta x=1 Δx=1时:
y i + 1 = y i + k y_{i+1}= y_i+ k yi+1=yi+k
数值微分改进
- 此处,我们做细节处理,将像素格划分为上半和下半, 用 i n t ( y + 0.5 ) int(y+0.5) int(y+0.5)判断是上方还是下方涂色。

- 同时该算法只能画 ∣ k ∣ ≤ 1 |k| \leq1 ∣k∣≤1,超过则会出现离散的点,失真。可以采用:
{ y i + 1 = y i + k ( ︱ k ︱ < 1 , Δ x = 1 ) x i + 1 = x i + 1 k ( ︱ k ︱ > 1 , Δ y = 1 ) \left\{ \begin{aligned} y_{i+1} = y_i+k( ︱k︱<1,\Delta x =1)\\ \\ x_{i+1} = x_i+\frac{1}{k}( ︱k︱>1,\Delta y =1) \end{aligned} \right. ⎩⎪⎪⎪⎨⎪⎪⎪⎧yi+1=yi+k(︱k︱<1,Δx=1)xi+1=xi+k1(︱k︱>1,Δy=1)
- 优点:简单直观,迭代的算法
缺点:浮点运算,每步都需四舍五入取整。
中点画线法
中点画线引言
利用后一中点与前一中点的函数关系,采用了直线的一般式方程:
F ( x , y ) = y − y 1 − y 0 x 1 − x 0 x − b F(x,y)=y-\frac{y_1-y_0}{x_1-x_0}x-b F(x,y)=y−x1−x0y1−y0x−b
认为 Δ x > 0 \Delta x>0 Δx>0,即:
F ( x , y ) = ( Δ x ) y − ( Δ y ) x − ( Δ x ) b F(x,y)=(\Delta x)y-(\Delta y)x-(\Delta x)b F(x,y)=(Δx)y−(Δy)x−(Δx)b
直线方程将平面分为三个区域:
{ F ( x , y ) = 0 ( 直 线 上 的 点 ) F ( x , y ) > 0 ( 直 线 上 方 的 点 ) F ( x , y ) < 0 ( 直 线 下 方 的 点 ) \left\{ \begin{aligned} F(x, y) = 0(直线上的点)\\ F(x, y) > 0(直线上方的点) \\ F(x, y) < 0(直线下方的点) \end{aligned} \right. ⎩⎪⎨⎪⎧F(x,y)=0(直线上的点)F(x,y)>0(直线上方的点)F(x,y)<0(直线下方的点)

当 M M M在 Q Q Q的下方,则说明 P u P_u Pu离直线近,应为下一个像素点;当 M M M在 Q Q Q的上方,应取 P d P_d Pd 为下一点。但是…每一个像素的计算量是4个加法,两个乘法。比DDA算法的计算量大多了,毫无可取之处!
中点画线改进
根据上一次计算的 d d d值判断接下来 d d d值的变化
- 若 d < 0 d<0 d<0时,则取右上方像素 p u ( x i + 1 , y i + 1 ) p_u(x_i+1, y_i+1) pu(xi+1,yi+1)。判断再下一
像素,则要计算:
d n e w = F ( x i + 2 , y i + 1.5 ) = a ( x i + 1 ) + b ( y i + 0.5 ) + c + a = F ( x i + 1 , y i + 0.5 ) + a + b = d o l d + a + b \begin{aligned} d_{new}&=F(x_i+2,y_i+1.5)\\ &=a(x_i+1)+b(y_i+0.5)+c+a\\ &=F(x_i+1,y_i+0.5)+a+b\\ &=d_{old}+a+b \end{aligned} dnew=F(xi+2,yi+1.5)=a(xi+1)+b(yi+0.5)+c+a=F(xi+1,yi+0.5)+a+b=dold+a+b
- 若 d ≥ 0 d \geq 0 d≥0情况,则取正右方像素 ( x i + 1 , y i ) (x_i+1, y_i) (xi+1,yi), 判断下一个像素位置要计算:
d n e w = F ( x i + 2 , y i + 0.5 ) = a ( x i + 1 ) + b ( y i + 0.5 ) + c + a = F ( x i + 1 , y i + 0.5 ) + a = d o l d + a \begin{aligned} d_{new}&=F(x_i+2,y_i+0.5)\\ &=a(x_i+1)+b(y_i+0.5)+c+a\\ &=F(x_i+1,y_i+0.5)+a\\ &=d_{old}+a \end{aligned} dnew=F(xi+2,yi+0.5)=a(xi+1)+b(yi+0.5)+c+a=F(xi+1,yi+0.5)+a=dold+a
- 迭代需要初始值 d 0 d_0 d0
d 0 = F ( x 0 + 1 , y 0 + 0.5 ) = a ( x 0 + 1 ) + b ( y 0 + 0.5 ) + c = a + 0.5 b \begin{aligned} d_0&=F(x_0+1,y_0+0.5) \\&=a(x_0+1)+b(y_0+0.5)+c \\&=a+0.5b \end{aligned} d0=F(x0+1,y0+0.5)=a(x0+1)+b(y0+0.5)+c=a+0.5b
- 由于我们只用 d d d的符号,所以使用 2 d 2d 2d摆脱小数运算,更新后的公式为:
{ d 0 = 2 a + b d n e w = d o l d + 2 a + 2 b ( d < 0 ) d n e w = d o l d + 2 a ( d ≥ 0 ) \left\{ \begin{aligned} &d_0= 2a+b\\ &d_{new}=d_{old}+2a+2b(d<0) \\ &d_{new}=d_{old}+2a(d \geq 0) \end{aligned} \right. ⎩⎪⎨⎪⎧d0=2a+bdnew=dold+2a+2b(d<0)dnew=dold+2a(d≥0)
Bresenham画线法
Bresenham基本思路
采用增量计算,检查误差项的符号,就可以确定该列的所求像素。
- 直线的起始点在像素中心,所以误差项d的初值
{ d 0 = 0 d n e w = d o l d + k x i + 1 = x i + 1 ( 右 移 一 格 ) y i + 1 = { y i + 1 , d = d − 1 ( d ≥ 0.5 ) ( 上 移 一 格 ) y i ( d < 0.5 ) ( 保 持 不 动 ) \left\{ \begin{aligned} &d_0=0\\ &d_{new}=d_{old}+k\\ &x_{i+1}= x_i+1(右移一格)\\ &y_{i+1}= \left\{ \begin{aligned} &y_i+1,d=d-1&(d\geq0.5)(上移一格)\\ \\ &y_{i}&(d < 0.5)(保持不动) \end{aligned} \right. \\ \end{aligned} \right. ⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧d0=0dnew=dold+kxi+1=xi+1(右移一格)yi+1=⎩⎪⎨⎪⎧yi+1,d=d−1yi(d≥0.5)(上移一格)(d<0.5)(保持不动)

Bresenham画线改进
- 令 e = d − 0.5 e=d-0.5 e=d−0.5
{ e 0 = − 0.5 e n e w = e o l d + k x i + 1 = x i + 1 ( 右 移 一 格 ) y i + 1 = { y i + 1 , e = e − 1 ( e ≥ 0 ) ( 上 移 一 格 ) y i ( e < 0 ) ( 保 持 不 动 ) \left\{ \begin{aligned} &e_0=-0.5\\ &e_{new}=e_{old}+k\\ &x_{i+1}= x_i+1(右移一格)\\ &y_{i+1}= \left\{ \begin{aligned} &y_i+1,e=e-1&(e\geq0)(上移一格)\\ \\ &y_{i}&(e < 0)(保持不动) \end{aligned} \right. \\ \end{aligned} \right. ⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧e0=−0.5enew=eold+kxi+1=xi+1(右移一格)yi+1=⎩⎪⎨⎪⎧yi+1,e=e−1yi(e≥0)(上移一格)(e<0)(保持不动)
- e ∗ 2 e*2 e∗2将 e 0 e_0 e0变为整数,由于 p 0 ( x 0 , y 0 ) , p 1 ( x 1 , y 1 ) p_0(x_0,y_0),p_1(x_1,y_1) p0(x0,y0),p1(x1,y1)皆为整数,故 d ∗ Δ x d* \Delta x d∗Δx即为斜率乘 Δ x \Delta x Δx即为 Δ y \Delta y Δy为整数,故第二步改进令:
e = e ∗ 2 ∗ Δ x e=e*2*\Delta x e=e∗2∗Δx
判断 e e e的正负,更新后的公式为:
{ e = − Δ x ( 初 始 值 ) e n e w = e o l d + 2 Δ y ( 初 始 值 ) x i + 1 = x i + 1 ( 右 移 一 格 ) y i + 1 = { y i + 1 , e = e − 2 Δ x ( e ≥ 0 ) ( 上 移 一 格 ) y i ( e < 0 ) ( 保 持 不 动 ) \left\{ \begin{aligned} &e= -\Delta x(初始值)\\ &e_{new}= e_{old}+2\Delta y(初始值)\\ &x_{i+1}= x_{i}+1(右移一格)\\ &y_{i+1}= \left\{ \begin{aligned} &y_i+1,e=e-2\Delta x&(e\geq0)(上移一格)\\ \\ &y_{i}&(e < 0)(保持不动) \end{aligned} \right. \\ \end{aligned} \right. ⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧e=−Δx(初始值)enew=eold+2Δy(初始值)xi+1=xi+1(右移一格)yi+1=⎩⎪⎨⎪⎧yi+1,e=e−2Δxyi(e≥0)(上移一格)(e<0)(保持不动)
小结
想避免浮点运算,进行整数算法判断像素点位置,就必须判断符号,如中点画线法和Bresenhanm算法,判断大小不能搞成整数加法。
wuli放几张做好的图,大家康康c语言可以画图呀!




超链接
如果你还想了解其他内容:
小白谈计算机图形学(一)如何画线
小白谈计算机图形学(二)如何画圆
小白谈计算机图形学(三)二维图形裁剪
小白谈计算机图形学(四)二维三维图形变换—1