LoginSignup
3
2

More than 3 years have passed since last update.

Cairoを使う:(その1)入口

Last updated at Posted at 2020-09-09

個人的な動機

Cairoを使えるようになりたいと思いました。

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

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

最初のコード

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

#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付きでコンパイルします。

% gcc test.c -lcairo

実行すると次のようなPNG画像ができます。
test.png
短いコードですが、抽象化された描画操作をcairo_tインスタンス(コンテクスト)に対して行い、実際の画像出力はcairo_surface_tインスタンス(サーフェス)で行う、というCairoの基本的な考え方をお分かり頂けると思います。

X11で出力

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

#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を追加します。

% gcc test.c -lcairo -lX11

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

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

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

#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なプログラムを書く雛型ができました。具体的な描画の仕方は次回以降に記します。

3
2
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2