Cairoを使う：（その10）曲線（ベジエ曲線）の描き方

### ベジエ（Bezier）曲線

Cairoで円弧以外の曲線を描きたい場合は、3次ベジエ曲線を用います。これにはcairo_curve_to()関数を用います。
次のdraw()関数を試してみて下さい。

```C
void draw_bezier(cairo_t *c, double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3)
{
  /* segments */
  cairo_set_source_rgb( c, 0.6, 0, 0 );
  cairo_set_line_width( c, 1 );
  cairo_move_to( c, x0, y0 );
  cairo_line_to( c, x1, y1 );
  cairo_line_to( c, x2, y2 );
  cairo_line_to( c, x3, y3 );
  cairo_stroke( c );
  /* control points */
  cairo_arc( c, x0, y0, 5, 0, 2*M_PI );
  cairo_fill( c );
  cairo_arc( c, x1, y1, 5, 0, 2*M_PI );
  cairo_fill( c );
  cairo_arc( c, x2, y2, 5, 0, 2*M_PI );
  cairo_fill( c );
  cairo_arc( c, x3, y3, 5, 0, 2*M_PI );
  cairo_fill( c );
  /* Bezier curve */
  cairo_set_source_rgb( c, 0, 0, 1 );
  cairo_set_line_width( c, 5 );
  cairo_move_to( c, x0, y0 );
  cairo_curve_to( c, x1, y1, x2, y2, x3, y3 );
  cairo_stroke( c );
}

void draw(cairo_t *c, int width, int height, int n)
{
  /* background */
  cairo_set_source_rgb( c, 1, 1, 1 );
  cairo_rectangle( c, 0, 0, width, height );
  cairo_fill( c );
  /* Bezier curve */
  draw_bezier( c,  50, 400, 100, 350, 250, 350, 300, 400 );
  draw_bezier( c,  50, 300, 150, 200, 200, 200, 300, 300 );
  draw_bezier( c,  50, 200, 200,  50, 150,  50, 300, 200 );

  draw_bezier( c, 380, 400, 450, 420, 520, 380, 590, 400 );
  draw_bezier( c, 380, 260, 450, 310, 520, 210, 590, 260 );
  draw_bezier( c, 380, 120, 450, 220, 520,  20, 590, 120 );
}
```
例によってM_PIを用いているので、math.hをインクルードするのを忘れないで下さい。出力は次のようになります。
![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706455/5e2c3120-8278-0c03-3abc-875b0eede4db.png)
このように、一つの3次ベジエ曲線セグメントは茶色で描いている4つの点（制御点, control point）で定義されます。これらのうち最初と最後の点は曲線セグメントの両端点となるので、経由点と呼ばれることもあります。経由点でない制御点をベジエ曲線が通過することはありません。
draw_bezier()関数の中では、まず制御点を結ぶ折れ線、次に制御点を示す円、最後にそれらが定義する曲線セグメントを描いています。

曲線セグメント描画部では、まず最初の制御点にcairo_move_to()で移動した後、cairo_curve_to()で残り4つの制御点を指定しています。これらから具体的に曲線がどのような規則に従って引かれるかの説明は別所に譲りますが、例に示した図から何となく雰囲気を掴んで頂けるのではないかと思います。3次ベジエ曲線は3次関数で表されるので、任意の曲線を表現できるわけではありません。

参考までに、上記draw_bezier()関数の最後にあるcairo_stroke()をcairo_fill()に換えると、出力は次のようになります。
![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706455/89c7277e-4428-6da9-0f6e-cf2e5289d559.png)


### 連続する滑らかな曲線

複数の曲線セグメントをつなげて長い曲線を描きたい場合、経由点を中央に含む隣接した３つの制御点は同一直線上になければなりません。これは、経由点とその隣の制御点を結ぶ線分は経由点において曲線と接する、という事実に基づきます。

上記のdraw_bezier()関数を流用して、draw()関数を次のように変えてみます。

```C
void draw(cairo_t *c, int width, int height, int n)
{
  /* background */
  cairo_set_source_rgb( c, 1, 1, 1 );
  cairo_rectangle( c, 0, 0, width, height );
  cairo_fill( c );
  /* Bezier curve */
  draw_bezier( c, 160, 240, 160, 320, 240, 400, 320, 400 );
  draw_bezier( c, 480, 240, 480, 320, 400, 400, 320, 400 );
  draw_bezier( c, 160, 240, 160, 160, 240,  80, 320,  80 );
  draw_bezier( c, 480, 240, 480, 160, 400,  80, 320,  80 );
}
```
出力は次のようになります。
![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706455/2f00cce5-e551-d646-3e8c-721ce69fe730.png)
円のように見えますが、3次曲線なので円ではありません。

上の例では「経由点を中央に含む隣接した3つの制御点」が同一直線上に乗るよう、座標を手動で与えていますが、自動化も比較的容易にできると思います。なお、この例のように経由点を挟む二つの制御点が経由点から等距離にある必要はありません。

### 曲線セグメントと直線セグメントが混在したパス

ベジエ曲線もパスですので、3つの点を与えなければならないという他はcairo_line_to()やcairo_rel_line_to()と同様に使うことができます。なお、cairo_rel_curve_to()という関数もあります。この場合、指定される三つの座標は最初の制御点からの相対的なものと解釈されます（つまりcairo_curve_to( c, x1, y1, x2, y2, x3, y3 )とcairo_rel_cuve_to( c, x1-x0, y1-y0, x2-x0, y2-y0, x3-x0, y3-y0 )は同じものです）。

また上の例のように、曲線セグメントの最初の制御点は必ずしもcairo_move_to()で指定する必要はありません。今描いているパスの最後の点が次の曲線セグメントの最初の制御点になります。

試しに次のdraw()関数を実行してみましょう。

```C
void draw(cairo_t *c, int width, int height, int n)
{
  /* background */
  cairo_set_source_rgb( c, 1, 1, 1 );
  cairo_rectangle( c, 0, 0, width, height );
  cairo_fill( c );
  /* scroll */
  cairo_move_to( c, 200, 150 );
  cairo_curve_to( c, 300, 250, 400, 50, 500, 150 );
  cairo_line_to( c, 500, 350 );
  cairo_curve_to( c, 400, 250, 300, 450, 200, 350 );
  cairo_close_path( c );
  cairo_set_source_rgb( c, 0, 1, 1 );
  cairo_fill_preserve( c );
  cairo_set_line_width( c, 10 );
  cairo_set_source_rgb( c, 0, 0, 1 );
  cairo_stroke( c );
}
```
出力は次のようになります。
![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706455/2fa934ca-b1d2-b64f-8306-a182e4971f71.png)
cairo_curve_to()とcairo_line_to()を併用していること、また最後にcairo_close_path()でパスを閉じていることに注意して下さい。