滑鼠操作貝塞爾曲線
(C) 2013-2015 Conmajia
Updated on Feb. 22nd, 2015
1 貝塞爾曲線
貝塞爾曲線(The Bézier Curves),是一種在計算機圖形學中相當重要的參數曲線(三維空間中稱為貝塞爾曲面). 貝塞爾曲線由法國工程師皮埃爾·貝塞爾(Pierre Bézier)于1962年發表,他運用貝塞爾曲線來為汽車的主體進行設計.
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZwpmL0MTN181NwgDOwITO1MTMvwlNy8CXxAzMxAjMvw1ckF2bsBXdvwFdl5mLuR2cj5Set1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
接下來将從一次貝塞爾曲線開始(以下簡稱一次曲線. 類似的, N 次貝塞爾曲線稱為N次曲線),研究貝賽爾曲線的解析構造和原理。
1.1 一次貝塞爾曲線
給定兩點 P0 、 P1 ,一次曲線在幾何上是一條連接配接這兩點的直線,可以用下面的參數方程表示:
B(t)=P0+(P1−P0)t=(1−t)P0+tP1
其中, t∈[0,1] .
當參數 t 在定義域内變化時,曲線變化過程如下:
一次貝塞爾曲線函數中,參數t會經過由 P0 和 14P0P1 的直線.
1.2 二次貝塞爾曲線
二次曲線的路徑由給定點 P0 、 P1 、 P2 的函數 B(t) 确定:
B(t)=(1−t)2P0+2t(1−t)P1+t2P2
其中, t∈[0,1] .
為了生成二次曲線,可以借助于中間點 Q0 和 Q1 :
- 由 P0 至 P1 的連續點 Q0 ,描述一條一次曲線
- 由 P1 至 P2 的連續點 Q1 ,描述一條一次曲線
- 由 Q0 至 Q1 的連續點 B(t) ,描述一條二次曲線
以 t=0.25 為例,二次曲線如圖:
當參數 t 在定義域内變化時,其變化過程如圖:
1.3 三次貝塞爾曲線
為構造高次曲線,需要借助更多的中間點. 對于三次曲線,可由一次曲線描述的中間點Q0、 Q1 、 Q2 ,以及由二次曲線描述的點 R0 、 R1 來生成.
設 P0 、 P1 、 P2 、 P3 四個點定義了一條三次方貝塞爾曲線. 曲線從 P0 開始,至 P3 結束,其方向從 P1 到 P2 . 一般來說,曲線不會經過 P1 或 P2 . P0 和 P1 之間的間距,決定了曲線在轉而趨進 P3 之前,朝 P2 方向行走的長度.
曲線的參數形式為:
B(t)=P0(1−t)3+3P1t(1−t)2+3P2t2(1−t)+P3t3
其中, t∈[0,1] .
以 t=0.25 為例,三次曲線如圖:
當參數 t 在定義域内變化時,其變化過程如圖:
1.4 高次曲線
如果用BP0P1⋯Pn表示由 P0P1⋯Pn 決定的貝塞爾曲線,那麼高次曲線可以表示為:
B(t)=BP0P1⋯Pn(t)=(1−t)BP0P1⋯Pn(t)+tBP0P1⋯Pn(t)
2 實作
2.1 計算機繪圖
要“畫”出貝塞爾曲線,一般使用逐次逼近方式,需要進行較多的計算,然後繪制出來,類似這樣:
繪制的代碼可以在各類計算機圖形學書籍中找到.
2.2 在GDI+中實作
GDI+已經封裝好了貝塞爾曲線的繪制代碼,如果你想畫出貝塞爾曲線,調用
Graphics.DrawBezier
方法:
public void DrawBezier(Pen pen, Point pt1, Point ctrlPt1, Point pt2, Point ctrlPt2);
這是一個三次貝塞爾曲線,其中4個點分别為:起點,起點控制點,終點,終點控制點. 繪制出來的效果如下:
3 與滑鼠互動
怎麼實作Photoshop裡那樣可以調整的貝塞爾曲線呢?兩種方法:
- 輸入新參數生成曲線
- 用滑鼠互動調整曲線
很顯然第二種看起來比較好,那麼就來試試看.
例如想要獲得是這樣的效果:
這是一條三次貝塞爾曲線,圖中各點含義為:
幾個需要解決的問題:
- 表示貝塞爾曲線的錨點和控制點
- 繪制曲線和控制點/控制柄
- 滑鼠互動
接下來分别解決。.
3.1 錨點
對于三次貝塞爾曲線而言,有兩個錨點:
起始點
和
結束點
. 每個錨點有兩個坐标:本身坐标和控制點坐标. 于是,可以用這樣的類來描述:
// Anchor
public class AnchorPoint {
public static AnchorPoint Empty { get; }
public Point Point { get; set; }
public Point ControlPoint { get; set; }
}
3.2 三次貝塞爾曲線
三次曲線前面已經說過原理了,它的結構應該就是這樣的:
public class BezierSegment {
public AnchorPoint Begin;
public AnchorPoint End;
}
3.3 一個簡單的渲染器
為了簡單,就用一個最基本的渲染器來畫,代碼如下:
public class BezierRenderer {
public void DrawSegment(Graphics g, BezierSegment seg, Color color) {
using (Pen p = new Pen(color)) {
g.DrawBezier(
p,
seg.Begin.Point,
seg.Begin.ControlPoint,
seg.End.Point,
seg.End.ControlPoint);
}
public void DrawHandle(Graphics g, Point pt, bool solid, Color color) {
if (solid)
using (SolidBrush b = new SolidBrush(color))
g.FillRectangle(b, pt.X - , pt.Y - , , );
else
using (Pen p = new Pen(color))
g.DrawRectangle(b, pt.X - , pt.Y - , , );
}
public void DrawBar(Graphics g, Point pt1, Point pt2, Color color) {
using (Pen p = new Pen(color))
g.DrawLine(p, pt1, pt2);
}
}
繪制曲線和控制點及其搖桿:
// 畫曲線
renderer.DrawSegment(g, seg, Color.DimGray);
// 畫錨點
renderer.DrawHandle(g, seg.Begin.Point, false, Color.DimGray);
renderer.DrawHandle(g, seg.End.Point, false, Color.DimGray);
// 畫控制柄
renderer.DrawBar(g, seg.Begin.Point, seg.Begin.ControlPoint, Color.Black);
renderer.DrawHandle(g, seg.Begin.ControlPoint, false, Color.Black);
renderer.DrawBar(g, seg.End.Point, seg.End.ControlPoint, Color.Black);
renderer.DrawHandle(g, seg.End.ControlPoint, false, Color.Black);
// 目前控制柄為實心
if (target != null)
renderer.DrawHandle(g, target.ControlPoint, true, Color.Black);
畫出來的效果如下:
3.4 滑鼠互動
和滑鼠實際上是這樣互動的:
- 按下滑鼠,如果落點在某個控制點上,就表示選中了該點,否則落空
- 移動滑鼠,如果選中了某個控制點,則調整控制點坐标至新坐标,否則忽略
- 放開滑鼠,取消任何選擇
看起來隻需要處理
MouseDown
,
MouseMove
和
MouseUp
這三個事件就夠了.
先弄一個全局标記:
// 熱點檢測區
Rectangle rectBegin, rectEnd;
// 激活辨別
AnchorPoint target = null;
然後處理滑鼠事件.
3.4.1 MouseDown事件
if (rectBegin.Contains(e.Location))
target = seg.Begin;
else if (rectEnd.Contains(e.Location))
target = seg.End;
refresh();
3.4.2 MouseMove事件
if (target == null) return;
if (!this.ClientRectangle.Contains(e.Location)) return;
target.ControlPoint = e.Location;
refresh();
3.4.3 MouseUp事件
target = null;
refresh();
3.4.4 refresh()
方法
refresh()
簡便起見,
refresh()
方法隻作簡單的重新整理:
Invalidate();
更高效的重新整理應該隻處理髒區.
3.5 效果
最後的效果如圖:
修改渲染器,可以得到更多的圖像效果:
4 擴充和優化
要實作Photoshop那種曲線效果,需要多個錨點連接配接起來,用和本文類似的方法來畫。上面的方法在效率上也還有可以提高的地方。
(完)
(C) 2013-2015 Conmajia