[问题描述]
在图像处理和计算机图形学中,我们经常需要在屏幕上画直线操作.考虑给定平面上两个点A(x1,y1)以及B(x2,y2),我们的任务是实现在屏幕上画通过AB两点的直线,并找出这条直线通过的所有中间点的坐标.需要注意的是这里每个像素的坐标均为整数.
示例:
为了使算法尽可能简单,我们做以下假设:
- 我们画直线从左往右画
- x1<x2 并且 y1<y2
- 直线的斜率在[0,1]之间,我们从左下往右上方向画线.
最原始方案:
看了上述描述,首先浮现在我们脑海的是最最原始直接的方法,如下所示:
上述方法肯定可以正常工作,但是效率很慢.而Bresenham算法则是为了避免float数据类型的乘法和加法操作,并且避免了每次在for循环里执行对(mx+c)取整操作.
Bresenham算法:
考虑到图像或者屏幕上像素的特殊性,(x,y)取值均为整数数据类型.基于此性质,我们可以每次沿着x轴增加1,接着我们来选择下一个y,此时我们有两种选择:要么y增加1要么y依旧维持不变.换句话说我们沿直线方向从点(xk,yk)移动一个像素,下一个点我们有两个选择即(xk+1,yk)或者(xk+1,yk+1).如下图所示
如上图所示,我们沿着直线方向从左下角往右上角移动,从红色点(xk,yk)移动一个像素,那么直线上下一个点的位置可能会有两个选择,如图中绿色点所示,即(xk+1,yk+1)或者(xk+1,yk).具体选择这两个点里的哪一个,需要对比直线在xk+1处的y值距离这两个整数点那个距离更小.此时的伪代码逻辑如下:
void drawLine(x1,x2,y1,y2)
{
x = x1
y = y1
print(x,y)
while(x<x2)
{
x = x + 1
If we should increment Y
y = y + 1
print(x,y)
}
}
接着我们的问题转化为如何设计一个准则来判断每次x增加1,y的取值是否增加1.一个直观的判断准则如下:
- 将x’=x+1带入直线方程,计算处y’
- 比较y’与y+0.5的大小,如果y’>y+0.5,则下一个点取(x+1,y+1)否则取(x+1,y)
接着我们使用间接方法来实现上述过程:
- 不防假设直线方程为 y=mx+b,则每次x增加1,那么y将增加m
- 我们记变量 fraction 含义为从上一次y增加开始到现在的斜率的累计增加量,那么如果fraction>0.5,那么我们应该增加Y
此时的伪代码逻辑如下:
void drawLine(x1,x2,y1,y2)
{
x = x1
y = y1
print(x,y)
fraction=0
m = (y2-y1)/(x2-x1)
while(x<x2)
{
x = x + 1
fraction += m
if(fraction > 0.5)
{
y = y + 1
fraction -= 1
}
print(x,y)
}
}
改进除法和浮点运算操作:
上述算法还涉及浮点运算和浮点数的比较,运行的依旧较慢.实际上,我们可以进一步优化来消除浮点运算,我们注意到 m=(y2-y1)/(x2-x1)
此时我们可以通过在上式左右两侧同乘(x2-x1)来消除除法运算;
同时为了避免斜率增加量和0.5的比较操作,我们可以同时乘以2.
汇总后,得到如下公式:
slope_increment = m*(x2-x1)*2=(y2-y1)/(x2-x1) * (x2-x1) * 2 = 2(y2-y1)
此时伪代码优化后如下:
void drawLine(x1,x2,y1,y2)
{
x = x1
y = y1
print(x,y)
fraction=0
m = (y2-y1)*2
while(x<x2)
{
x = x + 1
fraction += m
if(fraction > x2-x1)
{
y = y + 1
fraction -= 2*(x2-x1)
}
print(x,y)
}
}
完整的python代码实现如下:
# Assumptions :
# 1) Line is drawn from left to right.
# 2) x1 < x2 and y1 < y2
# 3) Slope of the line is between 0 and 1.
# We draw a line from lower left to upper
def bresenham(x1, y1, x2, y2):
print("Input : A({},{}), B({},{})".format(x1,y1,x2,y2))
pt_list = []
x = x1
y = y1
pt_list.append((x,y))
fraction = 0
m = (y2-y1) * 2
diff_x = x2 - x1
while x < x2:
x = x + 1
fraction += m
if fraction > diff_x:
y = y+ 1
fraction = fraction - 2*diff_x
pt_list.append((x, y))
print("Output:",pt_list)
运行效果如下:
if __name__ == '__main__':
bresenham(x1=0, y1=0, x2=4, y2=4)
bresenham(x1=0, y1=0, x2=4, y2=2)
输出结果如下:
参考
关注公众号《AI算法之道》,获取更多AI算法资讯.