目前常见的显示设备为光栅化显示器。此类设备采用像素的方式显示图像,也就意味着要在光栅化显示器上画出完美的直线,倾角必须是45k度(k为整数)。
如果不满足角度为45的倍数,需要采用近似的方法,比如,可以选择像素点到实际直线的距离最短者。先考虑斜率介于0和1之间的情况:如上图所示,当$P_i$点被选择为直线的一部分时,$P_{i+1}$只可能是右侧或者右上侧的点。此时,可以由两点到直线的距离之差($r_i-q_i$)的符号判定选择何者。根据相似三角形原理,其符号必然与$r^{\prime}_i-q^{\prime}_i$相同。
又由于$\Delta a > 0$,所以上式符号与$(r^{\prime}_i-q^{\prime}_i)\Delta a$相同。令此变量为“误差值”,记为$\nabla$。Bresenham算法即是通过此误差值的正负来决定下一像素点的选取。但是算法不是每一步都重新计算此值,而是通过递推式更新。这是Bresenham算法高效的原因。
误差值的递推式如下:
$$ \begin{align} \nabla_1 &= 2\Delta b - \Delta a \\ \nabla_{i+1} &= \nabla_i + 2\Delta b - 2\Delta a\quad if \quad \nabla_i \ge 0 \\ \nabla_{i+1} &= \nabla_i + 2\Delta b \quad if \quad \nabla_i < 0 \end{align} $$递推关系很容易可以证明,如果需要,可以参考Bresenham的原论文,证明非常详细。
现在,我们需要将算法推广到整个二维平面。首先,忽略递推关系的变化,当$\nabla$分别为负和为正时,所对应的下一个像素点不再固定是右侧和右上侧(也就是x加一或者x、y都加一)。但是,x坐标和y坐标的增量可以简单地由线段起始矢量的符号确定。试看下图:
另外,当直线斜率的绝对值大于1时,算法每一步都保证y加一,而不是x加一。这种情况下需要将$\Delta a$ 和$\Delta b$调换,并修改相应的递推式。
说这么多空话可能不容易理解,试结合C++实现与注释理解:
|
|
以上代码效率可能不足。经过简化后,可以在几行内完成,但可读性下降很多1:
|
|
-
用C语言画直线, https://zhuanlan.zhihu.com/p/30553006 ↩︎