首页 > 代码库 > 光栅图形学(一):直线段的扫描转换算法

光栅图形学(一):直线段的扫描转换算法

前言


  在数学上,理想的直线是没有宽度的,它是由无数个点构成的集合。对直线进行光栅化时,只能在显示器说给定的有限个像素组成的矩阵中,确定最佳逼近于该直线的一组像素,并且按扫描线顺序。

  本节介绍绘制线宽为一个像素的直线的三个常用算法:数值微分中点画线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)

  1. 若 d>=0 , 即中点代入原直线方程中的值大于0,即中点在目标直线上方,我们应该取下面的点(xp+1, yp)。判断下一个像素的位置时,应计算 d = F(xp+2,yp+0.5) = d + a,增量为 a。
  2. 若 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()

 

光栅图形学(一):直线段的扫描转换算法