Cairoを使う：（その1）入口

### 個人的な動機

[Cairo](https://www.cairographics.org/ "cairo")を使えるようになりたいと思いました。

- libX11を生で使うのはさすがに時代遅れも甚だしい（フォントまわりが特にしんどくなった）
- 種々画像フォーマットに簡単に出力できて嬉しい
- ベクトル画像が出力できて嬉しい

という理由によります。
英語のチュートリアルは[本家のもの](https://www.cairographics.org/tutorial/ "cairo tutorial")を含めいろいろあるのですが、日本語でとっつきやすい文書が欲しいと思い、自分で書くことにしました。本記事を書いている時点でバージョンは1.15.10、コーディングは全てC、コンパイラはgccで、Ubuntu上で実行してます。

### 最初のコード

次のようなコードを書きます。

```C
#include <cairo/cairo.h>

void draw(cairo_t *c, int width, int height)
{
  /* background */
  cairo_set_source_rgb( c, 1, 1, 1 );
  cairo_rectangle( c, 0, 0, width, height );
  cairo_fill( c );
  /* red rectangle */
  cairo_set_source_rgb( c, 1, 0, 0 );
  cairo_rectangle( c, width/4, height/4, width/2, height/2 );
  cairo_fill( c );
}

int main(int argc, char** argv)
{
  cairo_surface_t *cs;
  cairo_t *c;
  int width = 640, height = 480;

  cs = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, width, height );
  c = cairo_create( cs );

  draw( c, width, height );
  cairo_surface_write_to_png( cs, "test.png" );

  cairo_destroy( c );
  cairo_surface_destroy( cs );
  return 0;
}
```

これを-lcairo付きでコンパイルします。

```sh
% gcc test.c -lcairo
```

実行すると次のようなPNG画像ができます。
![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706455/b28bb727-0cdb-a4cb-399e-399aa1165f78.png)
短いコードですが、抽象化された描画操作をcairo_tインスタンス（コンテクスト）に対して行い、実際の画像出力はcairo_surface_tインスタンス（サーフェス）で行う、というCairoの基本的な考え方をお分かり頂けると思います。

### X11で出力

上記のコードを次のように変えます。

```C
#include <stdio.h>
#include <X11/Xutil.h>
#include <X11/Xlib.h>
#include <cairo/cairo.h>
#include <cairo/cairo-xlib.h>

void draw(cairo_t *c, int width, int height)
{
  （略）
}

int main(int argc, char** argv)
{
  Display *display;
  XEvent event;
  Window win;
  cairo_surface_t *cs;
  cairo_t *c;
  int width = 640, height = 480;

  display = XOpenDisplay( NULL );
  win = XCreateSimpleWindow( display, RootWindow( display, DefaultScreen(display) ),
    0, 0, width, height, 0,
    WhitePixel( display, DefaultScreen(display) ),
    BlackPixel( display, DefaultScreen(display) ) );
  XMapWindow( display, win );
  XSelectInput( display, win, ExposureMask );
  while( 1 ){
    XNextEvent( display, &event );
    if( event.type == Expose ) break;
  }

  cs = cairo_xlib_surface_create( display, win, DefaultVisual(display,0), width, height );
  c = cairo_create( cs );

  draw( c, width, height );
  XFlush( display );
  getchar();

  cairo_destroy( c );
  cairo_surface_destroy( cs );

  XDestroyWindow( display, win );
  XCloseDisplay( display );
  return 0;
}
```

コンパイル時には-lX11を追加します。

```sh
% gcc test.c -lcairo -lX11
```

新たに開かれたウィンドウに先ほどと同じ絵が出るはずです。

描画部分はdraw()関数にまとめているので、追加・変更された箇所は全てX11関連の操作です。
それらの説明は他所に譲りますが、最初のExposeイベントを拾った上でXFlush()してやらないと（私の環境では）ウィンドウ上に絵が出力されませんでした。

さらに、キーイベント等に対応するために次のようにコードを変更します。

```C
#include <stdio.h>
#include <X11/Xutil.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <X11/XKBlib.h>
#include <cairo/cairo.h>
#include <cairo/cairo-xlib.h>

void draw(cairo_t *c, int width, int height)
{
  （略）
}

int main(int argc, char** argv)
{
  Display *display;
  XEvent event;
  Window win;
  cairo_surface_t *cs = NULL;
  cairo_t *c = NULL;
  int width = 640, height = 480;
  int quit_flag = 0;

  display = XOpenDisplay( NULL );
  win = XCreateSimpleWindow( display, RootWindow( display, DefaultScreen(display) ),
    0, 0, width, height, 0,
    WhitePixel( display, DefaultScreen(display) ),
    BlackPixel( display, DefaultScreen(display) ) );
  XMapWindow( display, win );
  XSelectInput( display, win, ExposureMask | KeyPressMask | KeyReleaseMask );

  while( quit_flag != 1 ){
    XNextEvent( display, &event );
    switch( event.type ){
    case Expose:
    case ConfigureNotify:
      if( event.xexpose.count >= 1 ) break;
      cairo_destroy( c );
      cairo_surface_destroy( cs );
      width = event.xexpose.width;
      height = event.xexpose.height;
      cs = cairo_xlib_surface_create( display, win, DefaultVisual(display,0), width, height );
      c = cairo_create( cs );
      draw( c, width, height );
      break;
    case KeyPress:
      switch( XkbKeycodeToKeysym( display, event.xkey.keycode, 0, 0 ) ){
      case XK_q: case XK_Q: case XK_Escape:
        quit_flag = 1;
      default: ;
      }
      break;
    default: ;
    }
  }
  cairo_destroy( c );
  cairo_surface_destroy( cs );

  XDestroyWindow( display, win );
  XCloseDisplay( display );
  return 0;
}
```

リサイズイベントに対応しているのは念のためで、そのあたりがちょっと混み入ってしまいましたが、これでX11なプログラムを書く雛型ができました。具体的な描画の仕方は[次回](https://qiita.com/zhidao/items/1140e909a2964cd6ca52)以降に記します。