Cairoを使う:(その4)図形をパターンで塗りつぶすで、次のような絵を描きました。
これを、次のような絵に変えたいと思います。
まず、コードを次のように変更します。
#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は使用していない)ので、次のような絵が出力されます。
さて、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()は不要です。出力結果は次のようになります。
なんだか期待と違うものが出ました。これは、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()を呼んでいる点にもご注意下さい。これを忘れるとストロークもクリップされてしまいます。