DDA算法
數值微分法即DDA法(Digital Differential Analyzer),是一種基於直線的微分方程來生成直線的方法。
一、直線DDA算法描述:
設\((x_{1}, y_{1})\)和\((x_{2}, y_{2})\)分別為所求直線的起點和終點坐標,由直線的微分方程得
\(m\)為直線的斜率
可通過計算由x方向的增量△x引起\(y\)的改變來生成直線:
也可通過計算由y方向的增量△y引起\(x\)的改變來生成直線:
式(2-2)至(2-5)是遞推的。
二、直線DDA算法思想:
選定\(x_{2}-x_{1}\)和\(y_{2}-y_{1}\)中較大者作為步進方向(假設\(x_{2}-x_{1}\)較大),取該方向上的增量為一個像素單位(\(\Delta x=1\)),然后利用式(2-1)計算另一個方向的增量\(\Delta y = \Delta x \times m = m\)。通過遞推公式\((2-2)\)至\((2-5)\),把每次計算出的\((x_i+1,y_i+1)\)經取整后送到顯示器輸出,則得到掃描轉換后的直線。
之所以取\(x_{2}-x_{1}\)和\(y_{2}-y_{1}\)中較大者作為步進方向,是考慮沿着線段分布的像素應均勻,這在下圖中可看出。
另外,算法實現中還應注意直線的生成方向,以決定\(\Delta x\)及\(\Delta y\)是取正值還是負值。
三、直線DDA算法實現:
-
已知直線的兩端點坐標:\((x_{1}, y_{1})\)和\((x_{2}, y_{2})\)
-
已知畫線的顏色:color
-
計算兩個方向的變化量:
\[dx = x_{2}-x_{1}\\ dy = y_{2}-y_{1} \] -
求出兩個方向最大變化量的絕對值:
\[steps = max(|dx|, |dy|) \] -
計算兩個方向的增量(考慮了生成方向):
\[x_{in}=\frac{dx}{steps}\\ y_{in}=\frac{dy}{steps} \] -
設置初始像素坐標:
\[x=x_{1}\\y=y_{1} \] -
用循環實現直線的繪制:
for(i = 1; i <= steps; i++) {
// 在(x,y)處,以color色畫點
putpixel(x,y,color);
x=x+xin;
y=y+yin;
}
五、直線DDA算法特點:
該算法簡單,實現容易,但由於在循環中涉及實型數的運算,因此生成直線的速度較慢。
六、代碼實現
#include<Windows.h>
#include<graphics.h>
#include<conio.h>
#include<math.h>
void dda_line(int xa, int ya, int xb, int yb, int c);
int main(int argc, _TCHAR *argv[]) {
int gd = DETECT, gm; /*圖形屏幕初始化*/
initgraph(&gd, &gm, "");
dda_line(100, 100, 200, 200, 255);
getch();
closegraph();
return 0;
}
void dda_line(int xa, int ya, int xb, int yb, int c) {
float delta_x, delta_y, x, y;
int dx, dy, steps, k;
dx = xb - xa;
dy = yb - ya;
if (abs(dx) > abs(dy))steps = abs(dx);
else steps = abs(dy);
delta_x = (float) dx / (float) steps;
delta_y = (float) dy / (float) steps;
x = xa;
y = ya;
putpixel(x, y, c);
for (k = 1; k <= steps; k++) {
x += delta_x;
y += delta_y;
putpixel(x, y, c);
}
}
Bresenham算法
從上面介紹的DDA算法可以看到,由於在循環中涉及實型數據的加減運算,因此直線的生成速度較慢。
在生成直線的算法中,Bresenham算法是最有效的算法之一。Bresenham算法是一種基於誤差判別式來生成直線的方法。
一、直線Bresenham算法描述:
它也是采用遞推步進的辦法,令每次最大變化方向的坐標步進一個像素,同時另一個方向的坐標依據誤差判別式的符號來決定是否也要步進一個像素。
我們首先討論\(m={\frac{\Delta y}{\Delta x}}\),當\(0≤m≤1\)且\(x_{1}<x_{2}\)時的Bresenham算法。從DDA直線算法可知這些條件成立時,公式\((2-2)\)、\((2-3)\)可寫成:
有兩種Bresenham算法思想,它們各自從不同角度介紹了Bresenham算法思想,得出的誤差判別式都是一樣的。
二、直線Bresenham算法思想之一:
由於顯示直線的像素點只能取整數值坐標,可以假設直線上第i個像素點坐標為\((x_{i}, y_{i})\) ,它是直線上點\((x_{i}, y_{i})\) 的最佳近似,並且\(x_{i}=x_{i}\)(假設\(m<1\)),如下圖所示。那么,直線上下一個像素點的可能位置是\((x_{i}+1,y_{i})\)或\((x_{i}+1,y_{i}+1)\)。
由圖中可以知道,在\(x=x_{i}+1\)處,直線上點的y值是\(y=m(x_{i}+1)+b\),該點離像素點\((x_{i}+1, y_{i})\)和像素點\((x_{i}+1, y_{i}+1)\)的距離分別是\(d1\)和\(d2\):
這兩個距離差是
我們來分析公式\((2-10)\):
- 當此值為正時,\(d1>d2\),說明直線上理論點離\((x_{i}+1,y_{i}+1)\)像素較近,下一個像素點應取\((x_{i}+1,y_{i}+1)\)。
- 當此值為負時,\(d1<d2\),說明直線上理論點離\((x_{i}+1,y_{i})\)像素較近,則下一個像素點應取\((x_{i}+1,y_{i})\)。
- 當此值為零時,說明直線上理論點離上、下兩個像素點的距離相等,取哪個點都行,假設算法規定這種情況下取\((x_{i}+1,y_{i}+1)\)作為下一個像素點。
因此只要利用\((d1-d2)\)的符號就可以決定下一個像素點的選擇。為此,我們進一步定義一個新的判別式:
式\((2-11)\)中的\(\Delta x=(x_2-x_1)>0\),因此\(p_i\)與\((d_1-d_2)\)有相同的符號;這里\(\Delta y=y_2-y_1, m=\frac{\Delta y}{\Delta x}, c=2\Delta y+\Delta x(2b-1)\)。
下面對式\((2-11)\)作進一步處理,以便得出誤差判別遞推公式並消除常數c。
將式\((2-11)\)中的下標\(i\)改寫成\(i+1\),得到:
將式\((2-12)\)減去\((2-11)\),並利用\(x_i+1=x_i+1\),可得:
再假設直線的初始端點恰好是其像素點的坐標,即滿足:
由式\((2-11)\)和式\((2-14)\)得到p1的初始值:
這樣,我們可利用誤差判別變量,得到如下算法表示\((2-16)\):
- 初始
-
當\(p_i\ge0\)時
\[y_{i+1}=y_i+1\\ x_{i+1}=x_i+1\\ p_{i+1}=p_i+2(\Delta y-\Delta x) \] -
否則
\[y_{i+1}=y_i\\ x_{i+1}=x_i+1\\ p_{i+1}=p_i+2\Delta y \]
從式\((2-16)\)可以看出,第\(i+1\)步的判別變量\(pi+1\)僅與第\(i\)步的判別變量\(p_i\)、直線的兩個端點坐標分量差\(\Delta x\)和\(\Delta y\)有關,運算中只含有整數相加和乘2運算,而乘2可利用算術左移一位來完成,因此這個算法速度快並易於硬件實現。
三、直線Bresenham算法思想之二:
由於像素坐標的整數性,數學點\((x_i,y_i)\)與所取像素點\((x_i,y_{ir})\)間會引起誤差(\(\varepsilon_i\)),當\(x_i\)列上已用像素坐標\((x_i,y_{ir})\)表示直線上的點\((x_i,y_i)\),下一直線點\(B(x_{i+1},y_{i+1})\),是取像素點\(C(x_{i+1},y_{ir})\),還是\(D(x_{i+1},y_{(i+1)r})\)呢?
設\(A\)為\(CD\)邊的中點,正確的選擇:
若\(B\)點在\(A\)點上方,選擇\(D\)點; 否則,選\(C\)點。
用誤差式描述為:
求遞推公式:
當\(ε(x_{i+1})\ge0\)時,選\(D\)點,\(y_{(i+1)r} = y_{ir}+1\):
當\(ε(x_{i+1})<0\)時,選\(C\)點,\(y_{(i+1)r} = y_{ir}\):
初始時:
為了運算中不含實型數,同時不影響不等式的判斷,將方程兩邊同乘一正整數。
令方程兩邊同乘\(2\cdot\Delta x\),即\(d=2\cdot\Delta x·ε\),則:
初始時:
遞推式:
-
當\(d\ge0\)時:
{ d = d + 2(deltaY - deltaX); y++; x++; }
-
否則:
{ d = d + 2 * deltaY; x++; }
四、直線Bresenham算法實現
條件:\(0≤m≤1\)且\(x_1<x_2\)
- 輸入線段的兩個端點坐標和畫線顏色:\(x1,y1,x2,y2,color\);
- 設置像素坐標初值:\(x=x_1,y=y_1\);
- 設置初始誤差判別值:\(p=2\cdot\Delta y-\Delta x\);
- 分別計算:\(\Delta x=x_2-x_1, \Delta y=y_2-y_1\);
- 循環實現直線的生成:
for (x = x1; x <= x2; x++) {
putpixel(x, y, color);
if (p >= 0) {
y = y + 1;
p = p + 2 * (deltaY - deltaX);
} else {
p = p + 2 * deltaY;
}
}
五、直線Bresenham算法完善
現在我們修正\((2-16)\)公式,以適應對任何方向及任何斜率線段的繪制。如下圖所示,線段的方向可分為八種,從原點出發射向八個區。由線段按圖中所示的區域位置可決定xi+1和yi+1的變換規律。
容易證明:當線段處於①、④、⑧、⑤區時,以\(\mid\Delta x\mid\)和\(\mid\Delta y\mid\)代替前面公式中的\(\Delta x\)和\(\Delta y\),當線段處於②、③、⑥、⑦區時,將公式中的\(\mid\Delta x\mid\)和\(\mid\Delta y\mid\)對換,則上述兩公式仍有效
在線段起點區分線段方向
六、直線Bresenham算法特點
由於程序中不含實型數運算,因此速度快、效率高,是一種有效的畫線算法。
七、直線Bresenham算法程序
void Bresenhamline(int x1, int y1, int x2, int y2, int color) {
int x, y, dx, dy, s1, s2, p, temp, interchange, i;
x = x1;
y = y1;
dx = abs(x2 - x1);
dy = abs(y2 - y1);
if (x2 > x1) {
s1 = 1;
} else {
s1 = -1;
}
if (y2 > y1) {
s2 = 1;
} else {
s2 = -1;
}
if (dy > dx) {
temp = dx;
dx = dy;
dy = temp;
interchange = 1;
} else {
interchange = 0;
}
p = 2 * dy - dx;
for (i = 1; i <= dx; i++) {
putpixel(x, y, color);
if (p >= 0) {
if (interchange == 0) {
y = y + s2;
} else {
x = x + s1;
p = p - 2 * dx;
}
}
if (interchange == 0) {
x = x + s1;
} else {
y = y + s2;
}
p = p + 2 * dy;
}
}
#include <iostream>
#include <graphics.h>
using namespace std;
//中點畫線法
void line1(int x1, int y1, int x2, int y2) {
int x, y, d0, d1, d2, a, b;
y = y1;
a = y1 - y2; //直線方程中的a的算法
b = x2 - x1; //直線方程中的b的算法
d0 = 2 * a + b; //增量初始值
d1 = 2 * a; //當>=0時的增量
d2 = 2 * (a + b); //當<0時的增量
for (x = x1; x <= x2; x++) {
putpixel(x, y, GREEN); //打亮
if (d0 < 0) {
y++;
d0 += d2;
} else {
d0 += d1;
}
}
}
//Bresenham畫線算法
void line2(int x1, int y1, int x2, int y2) {
int x, y, dx, dy, d;
y = y1;
dx = x2 - x1;
dy = y2 - y1;
d = 2 * dy - dx; //增量d的初始值
for (x = x1; x <= x2; x++) {
putpixel(x, y, GREEN); //打亮
if (d < 0) {
d += 2 * dy;
} else {
y++;
d += 2 * dy - 2 * dx;
}
}
}
int main() {
initgraph(640, 480); //打開EGE初始化
line1(200, 160, 400, 400); //畫線
getch(); //等待用戶操作
closegraph(); //關閉圖形
return 0;
}