一、綜述
三種直線段繪制方法:DDA算法、B算法和中點分割法。
在MFC環境中測試上述三種算法並對比分析三種算法的誤差及效率。
二、程序框架
MFC程序:
cgDrawLineView.h為視圖層的頭文件,負責聲明各種成員變量和成員函數;
cgDrawLineView.cpp為視圖層的源文件,負責實現直線的三種繪制、誤差分析及messageBox顯示。
CSelectControl.h為窗口面板中的按鍵及文本定義成員變量及成員函數。
CSelectControl.cpp實現面板的功能,如點擊按鍵繪制圖像、分析誤差等。
三、算法描述
1. DDA算法
原理:根據直線的微分方程計算dy=m*dx
(1)將給定端點作為輸入參數;
(2)初始化,初值加上0.5確保精度;
(3)比較起止點水平和垂直的差值大數作為計算步數steps;
(4)每步計算dx,dy分別為差數除以steps;
(5)從起始點開始確定相鄰兩點間的增量並進行遞推計算。
2. B算法
(1)設定interChange、Xsign、Ysign便於判斷位置並計算誤差初值e = 2 * min - max;(min和max分別為水平距離和垂直距離的最值)
(2)設置點(Xi, Yi) 的顏色值,並求下一誤差ei+1;
如果 ei >= 0 則ei+1 =ei+m-1;
否則ei+1 = ei + m;
(3)根據不同象限,確定X和Y變化符號的正負,進行下一次(2)(3)循環直至結束;
3. 中點分割法
原理:遞歸二分法
結束條件:|P1-P2|<=1
(1)將直線段求中點坐標,若可以細分,則進行一次遞歸;
(2)如果中點坐標無法繼續遞歸,則設置坐標的顏色值;
(3)執行至所有點都完成了顏色值的設置,程序結束。
四、處理流程
主要處理流程為:
-
在DrawLineView.h中定義
double ddaError, ddaSmooth, bError, bSmooth, mpError, mpSmooth;
void MidPointline(CDC* pDC, float x1, float y1, float x2, float y2);
-
在DrawLineView.cpp中完成初始賦值、函數具體實現以及誤差、時間、平滑度計算和messageBox的彈出
-
在IDD_SELECTCONTROL中新添加button(MidPoint Line)且添加類向導,完善void CCSelectControl::OnClickedMidpointline()函數
-
在DrawLineView.cpp中完成pDoc->m_opMode ==2的調用程序基本完成
五、運行結果
當點擊DDA Line時調用DDA直線生成函數
當點擊B Line時調用B直線生成函數
當點擊MidPoint時調用中點分割直線生成函數
點擊Comparision時先后調用三種直線生成函數,
並彈出調用時間(受限於電腦運行速度原因只運行單次,
計算次數for循環注釋掉了,如有需要可取消注釋,重新生成結果)、誤差和光滑
其中,RunTime值越低表示效率越高,Error越小表示誤差越小,Smooth越小表示直線越光滑。
六、實驗總結
-
通過本次實驗,首先我對VS的MFC有了一個初步了解與掌握
-
對於三種常用的繪制直線算法: 數值微分(DDA)算法、中點算法、Bresenham算法有了更加深入的理解,特別是三種算法在繪制效率上的差別:
DDA算法: 復雜度:加法+取整
優點:避免了y=kx+b 方程中的浮點乘法。
缺點:需浮點數加法及取整運算,不利於硬件實現。
中點算法:
中點算法與DDA相比,主要是加法運算和浮點數運算,但是優化后,省去了浮點運算。
主要優點:算法簡單,無乘除,只有移位操作,尤其適用硬件實現。
Bresenham算法:
相比之下,Bresenham算法是計算機圖形學使用最廣泛的直線光柵化算法。Bresenham算法是每個象素只需一個整數加法,其優點還有就是可以用於其他二次曲線。
- 這次實驗學生的主要困難在於如何實現信息的回顯,起初因為對於這個名詞的不太了解,上網查詢時總是得不到想要的結果,因為主要搜索的內容是如何在新分割出來的窗口上顯示文字內容,在上課詢問過老師之后有了了解與理解,感覺問題不大,選擇通過static Text來進行,靜態的顯示沒有問題,但是運行后動態的回顯總是完成失敗,雖然失敗但是多次嘗試也讓學生對於函數之間關系與彼此之間調用有了更好的理解,當時嘗試更改的內容還有部分在源代碼里注釋掉了,可以查看痕跡,最后也沒有實現static Text的回顯,仿照老師給出資料完成messageBox信息彈出,之后實驗課會認真了解static text回顯信息的方法,而后進行改正,收獲良多,謝謝。
核心代碼:
DDA:
void Ccg2020YBHDrawLineView::DDAline(CDC* pDC, int x1, int y1, int x2, int y2)
{
Ccg2020YBHDrawLineDoc* pDoc = GetDocument();
int steps;
float m, x, y, dx, dy;
//誤差分析
double distance = 0.0;//生成點到理想直線的距離
double kaverage = 0.0;//生成點和起點間斜率平均值
x = x1 + 0.5f;
y = y1 + 0.5f;
steps = abs(x2 - x1) > abs(y2 - y1) ? abs(x2 - x1) : abs(y2 - y1);
dx = (float)(x2 - x1) / steps;
dy = (float)(y2 - y1) / steps;
//計算直線斜率和截距
m = (float)(y2 - y1) / (float)(x2 - x1);
float b = y2 - m*x2;
for (int i = 0; i < steps; i++) {
pDC->SetPixel((int)x + m_wndWidth / 2,
(int)(m_wndHeight / 2 - y), RGB(255, 0, 0));
//誤差計算
distance += fabs(x*m + b - y) / sqrt(m*m + 1);
kaverage += fabs((y - y1) / (x - x1));
x += dx;
y += dy;
}
ddaError = distance / steps;
ddaSmooth = fabs(kaverage / steps - m);
}
B:
void Ccg2020YBHDrawLineView::Bline(CDC* pDC, int x1, int y1, int x2, int y2)
{
int x, y, dx, dy, e, xSign, ySign, interChange = 0;
dx = abs(x2 - x1);
dy = abs(y2 - y1);
//誤差分析
double distance = 0.0f;//生成點到理想直線的距離
double kaverage = 0.0f;//生成點和起點間斜率平均值
//計算直線斜率和截距
float m = (float)(y2 - y1) / (float)(x2 - x1);
float b = y2 - m*x2;
if (dx < dy) {
int temp;
interChange = 1;
temp = dx;
dx = dy;
dy = temp;
}
xSign = (x2 > x1) ? 1 : -1;
ySign = (y2 > y1) ? 1 : -1;
x = x1;
y = y1;
e = 2 * dy - dx;
for (int i = 0; i <= dx; i++) {
pDC->SetPixel(x + m_wndWidth / 2,
m_wndHeight / 2 - y, RGB(0, 0, 255));
//誤差計算
distance += fabs(x*m + b - y) / sqrt(m*m + 1);
if(x!=x1)
kaverage += fabs(y - y1) / fabs(x - x1);
if (e > 0) {
e = e - 2 * dx;
if (interChange)
x += xSign;
else
y += ySign;
}
if (interChange)
y += ySign;
else
x += xSign;
e = e + 2 * dy;
}
bError = distance / dx;
bSmooth = fabs(kaverage / dx - m);
}
中點分割:
//利用遞歸原理,將(x1,y1)和(x2,y2)兩點間的線段不斷二分,
//直到分成的子線段的兩個端點的距離小於一個像素,然后對子線段進行描繪。
void Ccg2020YBHDrawLineView::MidPointline(CDC* pDC, float x1, float y1, float x2, float y2)
{
//誤差分析
double distance = 0.0f;//生成點到理想直線的距離
double kaverage = 0.0f;//生成點和起點間斜率平均值
float x = 0, y = 0;
int steps = fabs(x1 - x2) > fabs(y1 - y2) ? fabs(x1 - x2) : fabs(y1 - y2);
//計算直線斜率和截距
float m = (float)(y2 - y1) / (float)(x2 - x1);
float b = y2 - m*x2;
if (fabs(x1 - x2) <= 1 && fabs(y1 - y2) <= 1) {
pDC->SetPixel((int)((x1 + x2) / 2 )+ m_wndWidth / 2,
m_wndHeight / 2 - int((y1 + y2) / 2), RGB(0, 255, 0));
//誤差計算
x = (float)(x1 + x2) / 2;
y = (float)(y1 + y2) / 2;
distance += fabs(x*m + b - y) / sqrt(m*m + 1);
kaverage += fabs((y - y1) / (x - x1));
}
else
{
MidPointline(pDC, x1, y1, (x1 + x2) / 2, (y1 + y2) / 2);
MidPointline(pDC, (x1 + x2) / 2, (y1 + y2) / 2, x2, y2);
}
mpError = distance / steps;
mpSmooth = fabs(kaverage / steps - m);
}