首页 > 代码库 > 光栅图形学(一):直线段的扫描转换算法
光栅图形学(一):直线段的扫描转换算法
前言
在数学上,理想的直线是没有宽度的,它是由无数个点构成的集合。对直线进行光栅化时,只能在显示器说给定的有限个像素组成的矩阵中,确定最佳逼近于该直线的一组像素,并且按扫描线顺序。
本节介绍绘制线宽为一个像素的直线的三个常用算法:数值微分,中点画线和Bresenham算法。
数值微分法
已知过端点 P0(x0, y0),P1(x1, y1) 的直线段 L(P0, P1);直线斜率为 k = (y1 - y0) / (x1 - x0)。
于是 yi+1 = kxi+1 + b。
于是,x每增加1,y就增加k。画点的时候还需要判断 int(y+0.5) 向下取整。
1 // 数值微分法,伪代码 2 void DDAline(int x0, int y0, int x1, int y1) { 3 int dx, dy, x=x0, y=y0; 4 double k; 5 dx = x0 - x1; dy = y0 - y1; 6 k = dy / dx; 7 draw(x, y) 8 while (x <= x1) { 9 x += 1; 10 y += k; 11 draw(x, int(y+0.5)); 12 } 13 }
效果图如下(0,1)(5, 4):
中点画线法
假设线段 F(M) = ax + by + c = 0。(a=y0-y1; b=x1-x0; c=x0y1-x1y0)。
中点画线法的思想就是对于点(xp,yp)的下一个点 M(xp+1,y+0.5),拿这个中点和实际点比较,如果实际点在中点上方(F(M) < 0),则取(xp+1,yp+1)为下一个点。如果实际点在中点下方(即中点代入直线方程的值大于0,F(M) >= 0),则取(xp+1, yp)。
为了加速计算,我们通常采用增量的方法。
假设从(xp,yp)开始画线,d的初值d0 = F(x0+1, y0+0.5)= F(x0,y0)+a+0.5。(d = a+0.5b)
- 若 d>=0 , 即中点代入原直线方程中的值大于0,即中点在目标直线上方,我们应该取下面的点(xp+1, yp)。判断下一个像素的位置时,应计算 d = F(xp+2,yp+0.5) = d + a,增量为 a。
- 若 d < 0,即中点在目标直线的下方,即取中点上面的点(xp+1,yp+1)。判断下一个像素的位置时,应计算 d = F(xp+2, yp+1.5) = d + a + b,增量为 a+b。
因为我们只需要知道d的正负,所以可以调整 d‘ = 2a + b。
1 void Midpoint(int x0, int y0, int x1, int y1) { 2 int a, b, d1, d2, d, x, y; 3 a = y0 - y1; 4 b = x1 - x0; 5 d = 2*a + b; 6 d1 = 2*a, d2 = 2 * (a+b); 7 x = x0, y = y0; 8 draw(x, y); 9 while (x < x1) { 10 if (d < 0) 11 {x++, y++, d+=d2;} 12 else 13 {x++; d+=d1;} 14 draw(x, y); 15 } 16 }
效果如下图(0,1)(5, 4):
Bresenham算法
Bresenham算法也是采用增量的方法,y每次累加k,超过0.5的中点位置就取上面的点,然后增量减一。如果我们增量的起始值设置为 d-0.5。就可以直接和0比较。
每次和0.5比较都比较麻烦,所以假设一个格子的宽度为2dx,高为2dx,那么一个 k 就是2dy。
所以如果我们设置起始增量为 -dx(-0.5*2dx)。每次移动单位为1的长度,增量就增加2dy,然后拿着这个增量和 0 比较,如果大于0,取上面的点,然后增量要减少 2dx。
1 void IntegerBresenham(int x0, int y0, int x1, int y1) { 2 int x, y, dx, dy; 3 dx = x0-x1, dy = y0 - y1, e = -dx; 4 x = x0, y = y0; 5 for(i=0; i<=dx; ++i) { 6 draw(x, y); 7 x++, e=e+2*dy; 8 if (e >= 0) {y++; e = e-2*dx;} 9 } 10 }
下面是效果图:
完整代码
1 import matplotlib.pyplot as plt 2 from matplotlib.ticker import MultipleLocator 3 4 # 数值微分法 5 def DDA(x0, y0, x1, y1): 6 dy = y0 - y1 7 dx = x0 - x1 8 k = dy / dx 9 x = x0 10 y = y0 11 12 a = [] 13 b = [] 14 while (x <= x1): 15 a.append(x) 16 x += 1 17 b.append(int(y+0.5)) 18 y = y+k 19 plt.scatter(a, b, color=‘r‘) 20 21 # 中点画线法 22 def midpoint(x0, y0, x1, y1): 23 a = y0 - y1 24 b = x1 - x0 25 d = 2 * a + b 26 d1 = 2 * a 27 d2 = 2 * (a + b) 28 print(a, b, d1, d2, d) 29 x = x0 30 y = y0 31 32 a = [x0] 33 b = [y0] 34 while x < x1: 35 if d < 0: 36 x += 1 37 y += 1 38 d += d2 39 else: 40 x += 1 41 d += d1 42 a.append(x) 43 b.append(y) 44 plt.scatter(a, b, color=‘r‘) 45 46 def Bresenham(x0, y0, x1, y1): 47 dx = x1 - x0 48 dy = y1 - y0 49 e = -dx 50 x = x0 51 y = y0 52 53 a = [] 54 b = [] 55 while (x <= x1): 56 a.append(x) 57 b.append(y) 58 x += 1 59 e = e + 2 * dy 60 if e >= 0: 61 y += 1 62 e = e - 2 * dx 63 plt.scatter(a, b, color=‘r‘) 64 65 x_target = [0, 5] 66 y_target = [1, 4] 67 68 ax = plt.subplot(111); 69 plt.plot(x_target, y_target) 70 ax.xaxis.grid(True, which=‘major‘) 71 ax.yaxis.grid(True, which=‘major‘) 72 ax.xaxis.set_major_locator(MultipleLocator(1)) 73 ax.yaxis.set_major_locator(MultipleLocator(1)) 74 75 # DDA(0, 1, 5, 4) 76 # midpoint(0, 1, 5, 4) 77 Bresenham(0, 1, 5, 4) 78 plt.show()
光栅图形学(一):直线段的扫描转换算法