直線の描画

さて、Cairoグラフィックスライブラリの基礎を理解して描画を始めるための準備ができました。それでは一番単純な描画要素から始めましょう: 直線です。しかしその前にまずCairoの座標系について少しだけ知る必要があります。Cairoの座標系における原点はウィンドウの左上隅にあり、x軸の正の値は左、y軸の正の値は下となっています。

[Tip] Tip

Cairoグラフィックスライブラリは複数の出力ターゲット(X Window system, PNGイメージ, OpenGLなど)をサポートできるように書かれているので、ユーザー空間の座標系とデバイス空間のそれは区別されています。この2つの座標系を移す写像はデフォルトでは1対1のものであり、整数の値がスクリーン上のだいたいのピクセルに変換されます。しかし、この設定は必要ならば変えることができます。座標系をスケールしてウィンドウの幅や高さを0から1(単位矩形)の範囲で変更したり、アプリケーションに合った変換写像を使ったりするのはしばしば便利です。これはCairo::Context::scale()を使って行えます。

このコード例では小さいですが完全に機能するgtkmmプログラムを作成し、ウィンドウに直線を描画します。直線はパスを生成して筆でなぞることによって描画されます。パスはCairo::Context::move_to()Cairo::Context::line_to()を使って生成します。関数move_toの振る舞いは、ペンを紙から離して別の場所に置くのと似ています。最初の点とムーブした後の点では線は描画されません。2点間に線を引くには、line_to()関数を使ってください。

パスを生成し終えても、線はまだ目に見えません。パスを見えるようにするには関数stroke()を使ってください。これは現在のパスを、Cairo::Contextオブジェクトで指定した線の幅とスタイルでなぞります。なぞった後、現在のパスは消去されますから、次のパスを初めてください。

[Tip] Tip

多くのCairo描画関数は_preserve()派生を持っています。通常の描画関数 clip(), fill(), stroke()は現在のパスを消去します。しかし、_preserve()派生の方を使うと、現在のパスは保存されます。ですから次の描画関数は同じパスに対して使うことができます。

Figure 15.1. Drawing Area - Lines

Drawing Area - Lines

Source Code

File: myarea.h

#ifndef GTKMM_EXAMPLE_MYAREA_H
#define GTKMM_EXAMPLE_MYAREA_H

#include <gtkmm/drawingarea.h>

class MyArea : public Gtk::DrawingArea
{
public:
  MyArea();
  virtual ~MyArea();

protected:
  //Override default signal handler:
  virtual bool on_expose_event(GdkEventExpose* event);
};

#endif // GTKMM_EXAMPLE_MYAREA_H

File: myarea.cc

#include "myarea.h"
#include <cairomm/context.h>

MyArea::MyArea()
{
}

MyArea::~MyArea()
{
}

bool MyArea::on_expose_event(GdkEventExpose* event)
{
  // This is where we draw on the window
  Glib::RefPtr<Gdk::Window> window = get_window();
  if(window)
  {
    Gtk::Allocation allocation = get_allocation();
    const int width = allocation.get_width();
    const int height = allocation.get_height();

    // coordinates for the center of the window
    int xc, yc;
    xc = width / 2;
    yc = height / 2;

    Cairo::RefPtr<Cairo::Context> cr = window->create_cairo_context();
    cr->set_line_width(10.0);

    // clip to the area indicated by the expose event so that we only redraw
    // the portion of the window that needs to be redrawn
    cr->rectangle(event->area.x, event->area.y,
            event->area.width, event->area.height);
    cr->clip();

    // draw red lines out from the center of the window
    cr->set_source_rgb(0.8, 0.0, 0.0);
    cr->move_to(0, 0);
    cr->line_to(xc, yc);
    cr->line_to(0, height);
    cr->move_to(xc, yc);
    cr->line_to(width, yc);
    cr->stroke();
  }

  return true;
}

File: main.cc

#include "myarea.h"
#include <gtkmm/main.h>
#include <gtkmm/window.h>

int main(int argc, char** argv)
{
   Gtk::Main kit(argc, argv);

   Gtk::Window win;
   win.set_title("DrawingArea");

   MyArea area;
   win.add(area);
   area.show();

   Gtk::Main::run(win);

   return 0;
}

このプログラムにはクラス MyArea がひとつあります。クラスはGtk::DrawingAreaのサブクラスでありon_expose_event()メンバ関数を持っています。このメソッドは描画領域上の画像が再描画が必要なときに常に呼び出され、GdkEventExpose構造体へのポインタを受け取ります。そこにどの領域で再描画が必要なのか定義されています。プログラムではこれらの値を使って矩形のパスを生成します(rectangle()関数を使います)。それからパスをクリッピングします。clip()関数はクリップ領域を設定します。現在のクリップ領域は全ての描画操作に影響し、クリップ領域外で行われたサーフェスへの変更を全てマスクアウトします。これによって描画を再描画が必要な領域のみに行うよう制限することができます。実際に描画を行うコードでは、まず使いたい色をset_source_rgb()で設定します。これは引数として欲しい色の赤、緑、青の色相を取ります(有効な値は0から1の間です)。色を設定した後、新しいパスをmove_to()line_to()で生成し、これをstroke()でなぞります。

[Tip] 相対座標で描画する

上のコード例では全ての描画を絶対座標を使って行いました。しかし、相対座標を使うこともできます。例えば直線では、関数Cairo::Context::rel_line_to()を使ってください。

線のスタイル

基本的な直線を描くほかに、線をカスタマイズできるものがたくさんあります。既に線の色と幅の設定は例で見ましたが、同様のものがほかにもあります。

パスを構成するものが複数の線である場合、描画するときにそれらを決まったやり方で結合させたいかもしれません。Cairoは線の結合に三つのやり方を提供しています: Miter, Bevel, Round です。これらは次のように表示されます:

Figure 15.2. Different join types in Cairo

Different join types in Cairo

線の結合スタイルを設定するには次の関数を使ってください: Cairo::Context::set_line_join().

同様に、線の終端も異なったスタイルを持っています。デフォルトのスタイルは線の終わりまで正確に始まって終わるものです。これはButtキャップと呼ばれます。他のオプションには、Round (丸まった終端、線の終点を円の中心にする)や、Square (矩形の終端、線の終点を矩形の中心にする)があります。設定には次の関数を使ってください: Cairo::Context::set_line_cap().

この他にもダッシュラインを生成するなど、同様にカスタマイズできる設定がたくさんあります。詳しい情報については、Cairo APIドキュメントを見てください。