前言
在數學上,理想的直線是沒有寬度的,它是由無數個點構成的集合。對直線進行光柵化時,只能在顯示器說給定的有限個像素組成的矩陣中,確定最佳逼近於該直線的一組像素,並且按掃描線順序。
本節介紹繪制線寬為一個像素的直線的三個常用算法:數值微分,中點畫線和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()