![第5章,[Win32 章節(jié)] :貝塞爾樣條曲線(一)](http://pic.xiahunao.cn/yaotu/第5章,[Win32 章節(jié)] :貝塞爾樣條曲線(一))
專欄導(dǎo)航上一篇第5章[Win32 章節(jié)] 邊框繪制函數(shù)的教材案例回到目錄下一篇第5章[Win32 章節(jié)] 貝塞爾樣條曲線二本節(jié)前言對于本節(jié)所講解的知識有可能你會需要時不時地參考本專欄的其它文章。真的遇到了需要參考之前的文章的知識點(diǎn)請你自行查閱。我呢也會提到一部分的參考課節(jié)。但是呢你不應(yīng)該依賴于我的主動提及。最好呢你自己能夠多去了解和查看本專欄目錄。關(guān)于貝塞爾曲線最初我是在學(xué)習(xí) 3ds max 的時候接觸到了它。后來呢我在學(xué)習(xí) PhotoShop 軟件的時候又一次接觸了它。沒想到后來在學(xué)習(xí) Windows API 編程的時候再一次學(xué)習(xí)了它。在 PhotoShop 里面用鋼筆工具繪制出來的曲線便是貝塞爾樣條曲線。沒有學(xué)過 PhotoShop 的話那么你依然可以跟著佩措爾德的教材來學(xué)習(xí)這個東西。本節(jié)內(nèi)容來自于對佩措爾德教材的大段抄錄。一. 本節(jié)內(nèi)容英文單詞 “spline”曲尺又稱為樣條曲線最初指的是一塊可以彎曲的物體可以是木頭、橡皮或者金屬條它可以用來在紙張上繪制曲線。例如如果有一些離散的點(diǎn)并且想在它們之間畫曲線無論是內(nèi)插擬合還是外插擬合都應(yīng)當(dāng)首先將這些點(diǎn)描繪在圖紙上。接著固定一個曲尺在這些點(diǎn)上使用鉛筆沿著曲尺繞著這些點(diǎn)彎曲的方向繪制曲線。當(dāng)然現(xiàn)在樣條曲線已經(jīng)變成了一個個數(shù)學(xué)公式。有很多種不同的樣條曲線但是貝塞爾樣條曲線是計算機(jī)圖形程序設(shè)計中用得最廣的。它是后來才加到操作系統(tǒng)的圖形工具庫中并且它出自一個幾乎不可能的來源20 世紀(jì) 60 年代法國雷諾汽車公司由手工設(shè)計車體使用粘土模型轉(zhuǎn)變到使用計算機(jī)輔助設(shè)計。這就需要用到一些數(shù)學(xué)工具而皮埃爾·貝濟(jì)埃Pierre Bezier提出了一套數(shù)學(xué)公式事實(shí)證明這些數(shù)學(xué)公式起到了很大的作用。從那時起二維的貝塞爾樣條曲線被認(rèn)為是在計算機(jī)圖形學(xué)中最有用的曲線之一僅次于直線和橢圓。在 PostScript 中貝塞爾樣條曲線用于所有的曲線甚至橢圓也由貝塞爾樣條曲線來近似。PostScript 字體的字符輪廓的定義也使用貝塞爾樣條曲線定義。TrueType 則使用另外一種相對簡單快速的樣條曲線公式。一條二維貝塞爾樣條曲線使用四個點(diǎn)來定義兩個端點(diǎn)和兩個控點(diǎn)。兩個端點(diǎn)表示曲線的起點(diǎn)和終點(diǎn)。控點(diǎn)就好像磁鐵一樣把曲線從兩個端點(diǎn)間的直線處吸彎。對此下述程序所示的 Bezier 交互式程序便是一個好的例證。/*--------------------------------------- BEZIER.C -- Bezier Splines Demo (c) Charles Petzold, 1998 水餃編程改編2026 ---------------------------------------*/ #include windows.h LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] TEXT(Bezier); HWND hwnd; MSG msg; WNDCLASS wndclass; wndclass.style CS_HREDRAW | CS_VREDRAW; wndclass.lpfnWndProc WndProc; wndclass.cbClsExtra 0; wndclass.cbWndExtra 0; wndclass.hInstance hInstance; wndclass.hIcon LoadIcon(NULL, IDI_APPLICATION); wndclass.hCursor LoadCursor(NULL, IDC_ARROW); wndclass.hbrBackground (HBRUSH)GetStockObject(WHITE_BRUSH); wndclass.lpszMenuName NULL; wndclass.lpszClassName szAppName; if (!RegisterClass(wndclass)) { MessageBox(NULL, TEXT(注冊窗口類失敗), szAppName, MB_ICONERROR); return 0; } hwnd CreateWindow(szAppName, TEXT(Bezier Splines), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); ShowWindow(hwnd, iCmdShow); UpdateWindow(hwnd); while (GetMessage(msg, NULL, 0, 0)) { TranslateMessage(msg); DispatchMessage(msg); } return msg.wParam; } void DrawBezier(HDC hdc, POINT apt[], BOOL flag_eraser) { HPEN hPen, hOldPen; COLORREF color; hPen hOldPen NULL; PolyBezier(hdc, apt, 4); if (TRUE flag_eraser) { color RGB(255, 0, 0); hPen CreatePen(PS_DASH, 1, color); hOldPen (HPEN)SelectObject(hdc, hPen); } MoveToEx(hdc, apt[0].x, apt[0].y, NULL); LineTo(hdc, apt[1].x, apt[1].y); MoveToEx(hdc, apt[2].x, apt[2].y, NULL); LineTo(hdc, apt[3].x, apt[3].y); if (TRUE flag_eraser) { SelectObject(hdc, hOldPen); DeleteObject(hPen); } } LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static POINT apt[4]; HDC hdc; int cxClient, cyClient; PAINTSTRUCT ps; switch (message) { case WM_SIZE: cxClient LOWORD(lParam); cyClient HIWORD(lParam); apt[0].x cxClient / 4; apt[0].y cyClient / 2; apt[1].x cxClient / 2; apt[1].y cyClient / 4; apt[2].x cxClient / 2; apt[2].y 3 * cyClient / 4; apt[3].x 3 * cxClient / 4; apt[3].y cyClient / 2; return 0; case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MOUSEMOVE: if (wParam MK_LBUTTON || wParam MK_RBUTTON) { hdc GetDC(hwnd); SelectObject(hdc, GetStockObject(WHITE_PEN)); DrawBezier(hdc, apt, FALSE); if (wParam MK_LBUTTON) { apt[1].x LOWORD(lParam); apt[1].y HIWORD(lParam); } if (wParam MK_RBUTTON) { apt[2].x LOWORD(lParam); apt[2].y HIWORD(lParam); } SelectObject(hdc, GetStockObject(BLACK_PEN)); DrawBezier(hdc, apt, TRUE); ReleaseDC(hwnd, hdc); } return 0; case WM_PAINT: InvalidateRect(hwnd, NULL, TRUE); hdc BeginPaint(hwnd, ps); DrawBezier(hdc, apt, TRUE); EndPaint(hwnd, ps); return 0; case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); }對于上述代碼塊中的代碼你暫時不需要理解。因?yàn)樗婕傲艘院蟮恼鹿?jié)才會講到的鼠標(biāo)消息的處理方法。此時你只需要能夠?qū)⑵渚幾g出來并且在運(yùn)行過程中了解這個程序的使用方法和運(yùn)行效果就可以了。程序編譯和運(yùn)行這種純軟件操作的問題如果你忘了可以參考下述文章。參考課節(jié)使用 VS2010 編寫 C語言程序參考課節(jié)用 VS2019 編寫C語言程序本節(jié)程序主要的代碼邏輯都是佩措爾德原教材的代碼邏輯。但是呢我也改動了一部分代碼邏輯。程序運(yùn)行的結(jié)果如下圖所示。圖1圖1 所示為默認(rèn)的運(yùn)行結(jié)果。注意在圖1 中黑色實(shí)線的部分才是貝塞爾曲線。紅色虛線的部分它并不是貝塞爾曲線而是輔助線。程序運(yùn)行起來以后你可以通過鼠標(biāo)左鍵單擊和左鍵拖動的操作來改變第一個控點(diǎn)的位置從而影響整體的貝塞爾曲線的形狀。你也可以通過鼠標(biāo)右鍵的單擊和右鍵拖動來改變第二個控點(diǎn)的位置從而影響整體的貝塞爾曲線的形狀。我相信你很快地就能夠理解大致的操作方式。這個程序我當(dāng)初在學(xué)習(xí)的時候我覺得它是一個很好玩的程序。在我這里我在運(yùn)行了程序以后沒有改變第一個控點(diǎn)的位置而是選擇去改變第二個控點(diǎn)的位置。形成的圖形如下所示。圖2除了繪制貝塞爾樣條曲線本身程序還從第一個控點(diǎn)向左邊的第一個端點(diǎn)也就是起點(diǎn)畫直線并從第二個控點(diǎn)向右邊的端點(diǎn)畫直線。由控點(diǎn)向端點(diǎn)繪制的直線屬于是輔助線。我將這個輔助線用虛線來繪制。用虛線來畫線條同樣是涉及了在以后的章節(jié)內(nèi)容中才會講到的知識。默認(rèn)地在客戶區(qū)中繪制線條采用的是實(shí)線。我們也可以用虛線來繪制線條。想要用虛線來畫線我們需要用到的是畫筆知識。畫筆知識本章的后面我們就會講到。此刻你有個數(shù)就行。程序中有關(guān)于畫筆的使用你暫時應(yīng)該是還看不懂涉及畫筆的代碼。所以再次強(qiáng)調(diào)本節(jié)程序你暫時不需要閱讀其中的代碼更不需要掌握其中的代碼邏輯。暫時地你只需要能夠?qū)⒈竟?jié)程序編譯出來運(yùn)行它掌握使用方法這就夠了。至于說想要很好地理解本節(jié)程序代碼你需要學(xué)習(xí)完本章后面會講到的畫筆知識也需要學(xué)習(xí)完以后的章節(jié)中會講到的鼠標(biāo)消息知識你才能看懂本節(jié)程序代碼。結(jié)束語本節(jié)就先到這里了。下一節(jié)我們接著講貝塞爾樣條曲線。專欄導(dǎo)航上一篇第5章[Win32 章節(jié)] 邊框繪制函數(shù)的教材案例回到目錄下一篇第5章[Win32 章節(jié)] 貝塞爾樣條曲線二