個人的な動機
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画像ができます。
短いコードですが、抽象化された描画操作を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なプログラムを書く雛型ができました。具体的な描画の仕方は次回以降に記します。