Cairoを使う：（その6）マスクとクリップ

[Cairoを使う：（その4）図形をパターンで塗りつぶす](https://qiita.com/zhidao/items/38267b2c4b935cf29f93)で、次のような絵を描きました。
![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706455/35838e05-5aa1-c26b-8bcc-3065fc9589a2.png)
これを、次のような絵に変えたいと思います。
![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706455/3c2a77a2-56ae-5cd3-8473-e52569c10c83.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 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 );
}

cairo_pattern_t *create_mask(int width, int height)
{
  cairo_pattern_t *mp;

  mp = cairo_pattern_create_radial( width/2, 5*height/8, height/8, width/2, 5*height/8, height/2 );
  cairo_pattern_add_color_stop_rgba( mp, 0, 0, 0, 0, 1 );
  cairo_pattern_add_color_stop_rgba( mp, 1, 0, 0, 0, 0 );
  return mp;
}

void draw_triangle(cairo_t *c, int width, int height)
{
  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 );
}

void draw(cairo_t *c, cairo_pattern_t *p, cairo_pattern_t *mp, 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_set_source( c, mp ); /* show mask pattern */
  draw_triangle( c, width, height );
  cairo_fill( c );
  cairo_set_source_rgb( c, 1, 0, 0 );
  draw_triangle( c, width, height );
  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;
  cairo_pattern_t *mp = 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 );
  mp = create_mask( width, height );

  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, mp, 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( mp );
  cairo_pattern_destroy( p );
  cairo_destroy( pc );
  cairo_surface_destroy( ps );

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

最初のcreate_pattern()は変えていません。その次のcreate_mask()で同心円状のマスクパターンを作っています。マスクはアルファ値だけが意味を持ちます。次のdraw_triangle()は三角形のパス引きを関数化しただけです。
draw()には、マスクパターンも引数に与えるようにしました。今はソースにcairo_set_source()でマスク（に使う予定の）パターンを指定している（元のタイルパターンpは使用していない）ので、次のような絵が出力されます。
![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706455/17b8ab13-34d6-1210-56cd-2595cb1d1260.png)

さて、draw()関数を次のように変えてみます。

```C
void draw(cairo_t *c, cairo_pattern_t *p, cairo_pattern_t *mp, 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_set_source( c, p );
  cairo_pattern_set_extend( p, CAIRO_EXTEND_REPEAT );
  draw_triangle( c, width, height );
  cairo_mask( c, mp );
  cairo_set_source_rgb( c, 1, 0, 0 );
  draw_triangle( c, width, height );
  cairo_set_line_width( c, 5 );
  cairo_stroke( c );
}
```
cairo_set_source()でパターンpを指定し直し、cairo_pattern_set_extend( p, CAIRO_EXTEND_REPEAT )を呼んでタイル化しています。ここは前と同じです。違いはcairo_fill()をcairo_mask()に置き換えた所です。cairo_mask()は「ソースに指定されたマスクを重ねて塗る」という動作をするので、cairo_fill()は不要です。出力結果は次のようになります。
![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706455/21bc3828-e028-91c0-f3ec-e2cd455f1e6a.png)
なんだか期待と違うものが出ました。これは、cairo_mask()がパスで区切られた領域を無視してソースを塗るためで、この点cairo_fill()と動作が異なります。マスクをパスで直接抽出する方法は無いので、cairo_clip()を用いて次のようにdraw()関数を変更します。

```C
void draw(cairo_t *c, cairo_pattern_t *p, cairo_pattern_t *mp, 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_set_source( c, p );
  cairo_pattern_set_extend( p, CAIRO_EXTEND_REPEAT );
  draw_triangle( c, width, height );
  cairo_clip( c );
  cairo_mask( c, mp );
  cairo_set_source_rgb( c, 1, 0, 0 );
  draw_triangle( c, width, height );
  cairo_set_line_width( c, 5 );
  cairo_reset_clip( c );
  cairo_stroke( c );
}
```
これで期待した絵が出たはずです。
cairo_stroke()の前にcairo_reset_clip()を呼んでいる点にもご注意下さい。これを忘れるとストロークもクリップされてしまいます。