计算机图形学-DDA Bresenham算法 中点画线法
DDA
DDA是数字微分分析式(Digital Differential Analyzer)的缩写。设直线之起点为(x1,y1),终点为(x2,y2),则斜率m为:
m=(y_2-y_1)/(x_2-x_1 )=dy/dx
直线中的每一点坐标都可以由前一点坐标变化一个增量(Dx, Dy)而得到,即表示为递归式:
xi+1=xi+Dx
yi+1=yi+Dy
并有关系: Dy = m · Dx
递归式的初值为直线的起点(x1,y1),这样,就可以用加法来生成一条直线。具体方法是:
按照直线从(x1, y1)到(x2, y2)的方向不同,分为8个象限(图1)。对于方向在第1a象限内的直线而言,Dx = 1,Dy = m。对于方向在第1b象限内的直线而言,取值Dy = 1,Dx = 1/m。各象限中直线生成时Dx,Dy的取值列在表1之中。
当|dx|>|dy|时
|Dx|=1, |Dy|=m;
否则:
Dx=1/m, |Dy|=1;
Dx,Dy的符号与dx,dy的符号相同。
def DDALine(x0, y0, x1, y1): # 数值微分法画直线
x0 = round(x0) # 对坐标值取整
x1 = round(x1)
y0 = round(y0)
y1 = round(y1)
dx = x1 - x0
dy = y1 - y0
if dx == 0: # 斜率不存在
for y in range(y0, y1 + 1): # 由于range函数取值不包含y1,因此要加1
DrawPixel(x0, y)
return
k = dy / dx
if -1 <= k <= 1: # -1<=k<=1
y = y0
for x in range(x0, x1 + 1): # 对变量x取整
DrawPixel(x, round(y))
y = y + k
else:
x = x0
if k > 1: # k>1
for y in range(y0, y1 + 1): # 对变量y取整,xy调换顺序
DrawPixel(int(x + 0.5), y)
x = x + 1 / k
else:
y = y0 # k<-1
while y >= y1: # 让y递减,x从小到大的顺序画线
DrawPixel(round(x), y)
y = y - 1
x = x - 1 / k
Bresenham算法
这个算法由Bresenham在1965年提出。设直线从起点(x1, y1)到终点(x2, y2)。直线可表示为方程y=mx+b。其中
b = y1 - m * x1
m=(y_2-y_1)/(x_2-x_1 )=dy/dx
我们的讨论先将直线方向限于1a象限(图2)。在这种情况下,当直线光栅化时,x每次都增加1个单元,即xi+1=xi+1;而y的相应增加应当小于1。为了光栅化,yi+1只可能选择如下两种位置之一(图2)。
图2 中,yi+1的位置选择yi+1=yi 或者 yi+1=yi+1。选择的原则是看精确值y与yi及yi+1的距离d1及d2的大小而定。计算式为:
y = m(xi+1)+b (1)
d1 = y-yi (2)
d2 = yi+1-y (3)
如果d1-d2>0,则yi+1=yi+1,否则yi+1=yi。因此算法的关键在于简便地求出d1-d2的符号。将式(1),(2),(3)代入d1-d2,得
d1-d2=2y-2yi-1=2m(xi+1)-2yi+2b-1
用dx乘等式两边,并以Pi=dx(d1-d2)代入上述等式,得
Pi=2xidy-2yidx+2dy+dx(2b-1) (4)
d1-d2是我们用以判断符号的误差。由于在1a象限,dx总大于0,所以Pi仍旧可以用作判断符号的误差。Pi+1为:
Pi+1=Pi+2dy-2dx(yi+1-yi) (5)
误差的初值P1,可将x1, y1,和b代入式(4)中的xi, yi而得到:
P1=2dy-dx (6)
Bresenham算法的优点是:
① 不用浮点数,只用整数;
② 只做整数加减法和乘2运算,而乘2运算可以用硬件移位实现。
③ Bresenham算法速度很快,并适于用硬件实现。
# def Bresenhamline(x0, y0, x1, y1):
# dx = x1 - x0
# dy = y1 - y0
# x = round(x0)
# y = round(y0)
# if dx == 0: # 当直线斜率不存在
# for y in range(round(y0), round(y1)):
# DrawPixel(x, y)
# return
# k = dy / dx
# e = -0.5
# if 0 <= dy <= dx: # 0<=k<=1
# for i in range(0, dx):
# DrawPixel(x, y)
# x = x + 1
# e = e + k
# if e >= 0:
# y = y + 1
# e = e - 1
# elif 0 <= dx < dy: # k>1
# for i in range(0, dy):
# DrawPixel(x, y)
# y = y + 1
# e = e + 1 / k
# if e >= 0:
# x = x + 1
# e = e - 1
# elif dy <= 0 and dx > 0 and abs(dy) <= dx: # -1<=k<0
# e = 0.5
# for i in range(0, dx):
# DrawPixel(x, y)
# x = x + 1
# e = e + k
# if e <= 0:
# y = y - 1
# e = e + 1
# else: # k<-1
# e = 0.5
# for i in range(0, -dy):
# DrawPixel(x, y)
# y = y - 1
# e = e + 1 / k
# if e <= 0:
# x = x + 1
# e = e + 1
def IntegerBresenhamline(x0, y0, x1, y1):
x0 = round(x0) # 对坐标值取整
x1 = round(x1)
y0 = round(y0)
y1 = round(y1)
if x1 < x0:
tx = x1
x1 = x0
x0 = tx
ty = y1
y1 = y0
y0 = ty
dx = x1 - x0
dy = y1 - y0
x = x0
y = y0
if dx == 0: # k不存在
for y in range(y0, y1 + 1):
DrawPixel(x0, y)
elif 0 <= dy <= dx: # 0<=k<=1
e = -dx
for i in range(0, dx + 1):
DrawPixel(x, y)
x = x + 1
e = e + 2 * dy
if e >= 0:
y = y + 1
e = e - 2 * dx
elif 0 < dx < dy: # k>1
e = -dy
for i in range(0, dy + 1):
DrawPixel(x, y)
y = y + 1
e = e + 2 * dx
if e >= 0:
x = x + 1
e = e - 2 * dy
elif dy < 0 and dx > 0 and abs(dy) <= dx: # -1<=-k<0
e = dx
for i in range(0, dx + 1):
DrawPixel(x, y)
x = x + 1
e = e + 2 * dy
if e <= 0:
y = y - 1
e = e + 2 * dx
else: # k<-1
e = -dy
for i in range(0, -dy):
DrawPixel(x, y)
y = y - 1
e = e - 2 * dx
if e <= 0:
x = x + 1
e = e - 2 * dy
中点画线法
假定直线斜率k在0~1之间,当前象素点为(xp,yp),则下一个象素点有两种可选择点(xp+1,yp)或P2(xp+1,yp+1)。若P1与P2的中点(xp+1,yp+0.5)称为M,Q为理想直线与x=xp+1垂线的交点。当M在Q的下方时,则取P2应为下一个象素点;当M在Q的上方时,则取P1为下一个象素点。这就是中点画线法的基本原理。
图3中点画线法每步迭代涉及的象素和中点示意图
def MidpointLine(x0, y0, x1, y1): # 中点画线法
x0 = round(x0) # 对坐标值取整
x1 = round(x1)
y0 = round(y0)
y1 = round(y1)
if x1 < x0: # 使画线的顺序是x从小到大的顺序
tx = x1
x1 = x0
x0 = tx
ty = y1
y1 = y0
y0 = ty
a = y0 - y1
b = x1 - x0
d1 = 2 * a + b
d2 = a - 2 * b
x = x0
y = y0
DrawPixel(x, y) # 画出初始点
if a * b <= 0: # k>0或k不存在
if a + b >= 0: # 0<=k<=1
while x <= x1:
if d1 < 0: # 取中点上方的点
y = y + 1
d1 += 2*(a + b)
else: # 取中点下方
d1 += 2*a
x = x + 1
DrawPixel(x, y)
else: # k>1或k不存在
while y <= y1:
if d1 < 0:
d1 = d1 + 2*b
else:
d1 = d1 + 2*(a + b)
x = x + 1
y = y + 1
DrawPixel(x, y)
else: # k<0
if a <= b: # -1<=k<0
while x <= x1:
if d2 >= 0:
y = y - 1
d2 += 2*(a - b)
else:
d2 += 2*a
x = x + 1
DrawPixel(x, y)
else:
while y >= y1: # k<-1
if d2 > 0:
d2 = d2 - 2*b
else:
d2 = d2 + 2*(a - b)
x = x + 1
y = y - 1
DrawPixel(x, y)
附加:需要的其他部分
import math
import turtle
turtle.hideturtle() # 掩藏箭头
turtle.delay(0)
def DrawPixel(x, y, self=None): # 画点
turtle.up()
turtle.goto(x, y)
turtle.down()
turtle.dot(3)
def DrawArrowhead(): # 画箭头
turtle.pensize(1)
turtle.color('black', 'black')
turtle.begin_fill()
turtle.left(120)
turtle.forward(5)
turtle.right(150)
turtle.forward(10)
turtle.right(120)
turtle.forward(10)
turtle.right(150)
turtle.forward(5)
turtle.end_fill()
def DrawCoordinates(): # 画坐标系
turtle.up()
turtle.goto(-200, 0)
turtle.down()
turtle.forward(400)
DrawArrowhead()
turtle.up()
turtle.goto(0, -200)
turtle.down()
turtle.left(30)
turtle.forward(400)
DrawArrowhead()
感谢!