LoginSignup
2
0

More than 3 years have passed since last update.

Cairoを使う:(その6)マスクとクリップ

Last updated at Posted at 2020-09-25

Cairoを使う:(その4)図形をパターンで塗りつぶすで、次のような絵を描きました。
test.png
これを、次のような絵に変えたいと思います。
test.png

まず、コードを次のように変更します。

#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

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

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
なんだか期待と違うものが出ました。これは、cairo_mask()がパスで区切られた領域を無視してソースを塗るためで、この点cairo_fill()と動作が異なります。マスクをパスで直接抽出する方法は無いので、cairo_clip()を用いて次のようにdraw()関数を変更します。

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()を呼んでいる点にもご注意下さい。これを忘れるとストロークもクリップされてしまいます。

2
0
0

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
2
0