Cairoを使う：（その4）図形をパターンで塗りつぶす

### タイルパターンの作成と塗りつぶし

ちょっと長くなりますが、次のようなコードを走らせてみます。

```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 create_pattern(cairo_t *pc, int patwidth, int patheight)
{
  /* white */
  cairo_set_source_rgb( pc, 1, 1, 1 );
  cairo_rectangle( pc, 0, 0, patwidth, patheight );
  cairo_fill( pc );
  /* pink */
  cairo_set_source_rgb( pc, 1, 0.6, 0.8 );
  cairo_rectangle( pc, 0, 0, patwidth/2, patheight/2 );
  cairo_fill_preserve( pc );
  cairo_rectangle( pc, patwidth/2, patheight/2, patwidth/2, patheight/2 );
  cairo_fill( pc );
}

void draw(cairo_t *c, cairo_pattern_t *p, int width, int height)
{
  /* background */
  cairo_set_source_rgb( c, 1, 1, 1 );
  cairo_rectangle( c, 0, 0, width, height );
  cairo_fill( c );
  /* triangle */
  cairo_move_to( c, width/2, height/8 );
  cairo_line_to( c, width/8, 7*height/8 );
  cairo_line_to( c, 7*width/8, 7*height/8 );
  cairo_close_path( c );
  cairo_set_source( c, p );
  cairo_pattern_set_extend( p, CAIRO_EXTEND_REPEAT );
  cairo_fill_preserve( c );
  cairo_set_source_rgb( c, 1, 0, 0 );
  cairo_set_line_width( c, 5 );
  cairo_stroke( c );
}

int main(int argc, char** argv)
{
  Display *display;
  XEvent event;
  Window win;
  cairo_surface_t *cs = NULL;
  cairo_t *c = NULL;
  cairo_surface_t *ps = NULL;
  cairo_t *pc = NULL;
  cairo_pattern_t *p = NULL;
  int width = 640, height = 480;
  int patwidth = 80, patheight = 60;
  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 );

  ps = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, patwidth, patheight );
  pc = cairo_create( ps );
  p = cairo_pattern_create_for_surface( ps );
  create_pattern( pc, patwidth, patheight );

  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, p, width, height );
      break;
    case KeyPress:
      switch( XkbKeycodeToKeysym( display, event.xkey.keycode, 0, 0 ) ){
      case XK_q: case XK_Q: case XK_Escape: return -1;
      }
      break;
    default: ;
    }
  }
  cairo_destroy( c );
  cairo_surface_destroy( cs );

  cairo_pattern_destroy( p );
  cairo_destroy( pc );
  cairo_surface_destroy( ps );

  XDestroyWindow( display, win );
  XCloseDisplay( display );
  return 0;
}
```
出力は次のようになります。

![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706455/35838e05-5aa1-c26b-8bcc-3065fc9589a2.png)

サーフェスpsに対応付けたコンテクストpcにタイルパターンを描画しパターン化した上で、そのパターンpを使ってサーフェスcsに対応付けたコンテクストcに描画する、ということをやっています。サーフェス、コンテクスト、パターンの関係に注意して下さい。

順を追ってみていきましょう。まずmain()関数の中で、

```C
  ps = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, patwidth, patheight );
  pc = cairo_create( ps );
  p = cairo_pattern_create_for_surface( ps );
  create_pattern( pc, patwidth, patheight );
```
とし、タイルパターン用のサーフェスpsと、それに対応付けるコンテクストpcおよびパターンpを生成しています。
create_pattern()関数は上記コードの最初に定義されているユーザ関数ですが、長方形のサーフェス全体を白く塗った後に、左上4分の1と右下4分の1にピンク色の長方形を描いていることはお分かりになると思います。

draw()関数には第2引数にパターンを与えられるようにしました。コンテクストpcに描画しているので、すでにサーフェスpsをパターンとして利用できる状態にあります。cairo_set_source()を呼ぶことで、パターンpをコンテクストcのソースとしています。これだとcの左上隅にpが貼り付けられるだけなのですが、さらに

```C
  cairo_pattern_set_extend( p, CAIRO_EXTEND_REPEAT );
```
とすることで、同じパターンを繰り返し表示するよう指定しています。これにより上に示したような出力になるわけです。

ウインドウをマウスで適当にリサイズして頂くとお分かりになると思いますが、タイルパターンの原点は描画サーフェスの左上隅に固定されており、繰り返しはここを基準としています。

### 画像ファイルをタイルパターン化する

上記を応用して、タイルパターン用のサーフェスを画像ファイルから生成してみましょう。テスト用画像ファイルには、[The USC-SIPI Image Database](http://sipi.usc.edu/database/database.php)の[Miscellaneous](http://sipi.usc.edu/database/database.php?volume=misc)から4.2.01splashを拝借します。
![](http://sipi.usc.edu/database/preview/misc/4.2.01.png)

次のコードを実行します。

```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, cairo_pattern_t *p, int width, int height)
{
  /* background */
  cairo_set_source_rgb( c, 1, 1, 1 );
  cairo_rectangle( c, 0, 0, width, height );
  cairo_fill( c );
  /* triangle */
  cairo_move_to( c, width/2, height/8 );
  cairo_line_to( c, width/8, 7*height/8 );
  cairo_line_to( c, 7*width/8, 7*height/8 );
  cairo_close_path( c );
  cairo_set_source( c, p );
  cairo_pattern_set_extend( p, CAIRO_EXTEND_REPEAT );
  cairo_fill_preserve( c );
  cairo_set_source_rgb( c, 1, 0, 0 );
  cairo_set_line_width( c, 5 );
  cairo_stroke( c );
}

int main(int argc, char** argv)
{
  Display *display;
  XEvent event;
  Window win;
  cairo_surface_t *cs = NULL;
  cairo_t *c = NULL;
  cairo_surface_t *ps = NULL;
  cairo_pattern_t *p = 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 );

  ps = cairo_image_surface_create_from_png( "splash.png" );
  p = cairo_pattern_create_for_surface( ps );

  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, p, width, height );
      break;
    case KeyPress:
      switch( XkbKeycodeToKeysym( display, event.xkey.keycode, 0, 0 ) ){
      case XK_q: case XK_Q: case XK_Escape: return -1;
      }
      break;
    default: ;
    }
  }
  cairo_destroy( c );
  cairo_surface_destroy( cs );

  cairo_pattern_destroy( p );
  cairo_surface_destroy( ps );

  XDestroyWindow( display, win );
  XCloseDisplay( display );
  return 0;
}
```
今度はサーフェスをcairo_image_surface_create_from_png()でPNG画像から作成しているので、コンテクストpcは不要です。出力は次のようになります。
![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706455/77fb328c-e1f6-825f-e577-2ed3b0d4de66.png)