パスの終端モード
描画関数を次のようにします。
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 );
/* V */
cairo_set_source_rgb( c, 0, 0, 1 );
cairo_set_line_width( c, 30 );
cairo_move_to( c, width/4, height/8 );
cairo_rel_line_to( c, width/4, height/8 );
cairo_rel_line_to( c, width/4,-height/8 );
cairo_set_line_cap( c, CAIRO_LINE_CAP_SQUARE );
cairo_stroke( c );
cairo_move_to( c, width/4, 7*height/16 );
cairo_rel_line_to( c, width/4, height/8 );
cairo_rel_line_to( c, width/4,-height/8 );
cairo_set_line_cap( c, CAIRO_LINE_CAP_BUTT );
cairo_stroke( c );
cairo_move_to( c, width/4, 3*height/4 );
cairo_rel_line_to( c, width/4, height/8 );
cairo_rel_line_to( c, width/4,-height/8 );
cairo_set_line_cap( c, CAIRO_LINE_CAP_ROUND );
cairo_stroke( c );
}
cairo_set_line_cap()は、パスの終端部の描画方法を指定する関数です。効果が分かりやすくなるよう線の太さを30にしています。出力は次のようになります。
- cairo_set_line_cap( c, CAIRO_LINE_CAP_SQUARE )はパスの両端を太さ分延長
- cairo_set_line_cap( c, CAIRO_LINE_CAP_BUTT )はパスの両端を延長しない
- cairo_set_line_cap( c, CAIRO_LINE_CAP_ROUND )はパスの両端を丸くする
という効果が得られています。デフォルトではCAIRO_LINE_CAP_SQUAREが選ばれています。
パスの結合モード
お遊びですが、描画部のコードを次のようにしてみます。
# include <math.h>
void vert_star(double *x, double *y, double cx, double cy, double r, double a)
{
*x = cx - r * sin( a );
*y = cy - r * cos( a );
}
void mul_mat_inv_vec(double *x, double *y, double a, double b, double c, double d, double u, double v)
{
double det;
det = a * d - b * c;
*x = ( d * u - b * v ) / det;
*y = (-c * u + a * v ) / det;
}
void vert_cross(double *xc, double *yc, double x0, double y0, double x2, double y2, double x1, double y1, double x3, double y3)
{
mul_mat_inv_vec( xc, yc, y2-y0, -x2+x0, y3-y1, -x3+x1, (y2-y0)*x0-(x2-x0)*y0, (y3-y1)*x1-(x3-x1)*y1 );
}
void draw_star(cairo_t *c, int n, double cx, double cy, double r)
{
int i;
double x0, y0, x1, y1, x2, y2, x3, y3, xc, yc;
cairo_move_to( c, cx, cy-r );
for( i=0; i<n; i++ ){
vert_star( &x0, &y0, cx, cy, r, 2*M_PI*(i-1)/n );
vert_star( &x1, &y1, cx, cy, r, 2*M_PI* i /n );
vert_star( &x2, &y2, cx, cy, r, 2*M_PI*(i+1)/n );
vert_star( &x3, &y3, cx, cy, r, 2*M_PI*(i+2)/n );
vert_cross( &xc, &yc, x0, y0, x2, y2, x1, y1, x3, y3 );
cairo_line_to( c, xc, yc );
cairo_line_to( c, x2, y2 );
}
cairo_close_path( c );
}
void draw(cairo_t *c, int width, int height)
{
/* background */
cairo_set_source_rgb( c, 0, 0, 0.5 );
cairo_rectangle( c, 0, 0, width, height );
cairo_fill( c );
/* star */
draw_star( c, 5, width/4, height/4, height/8 );
draw_star( c, 6, width/2, height/4, height/8 );
draw_star( c, 8, 3*width/4, height/4, height/8 );
cairo_set_source_rgb( c, 0.8, 0.8, 0 );
cairo_fill_preserve( c );
cairo_set_source_rgb( c, 0.8, 0.6, 0 );
cairo_set_line_width( c, 5 );
cairo_stroke( c );
}
コンパイル時に-lmオプションを付けるのをお忘れなく。
% gcc test.c -lcairo -lX11 -lm
出力は次のようになります。
draw_star()は中心(cx,cy)に半径rの正n芒星を描く関数です。計算の意味の説明は省略します。配列使ってないので頂点の計算に無駄があることはご勘弁下さい。最後のcairo_close_path()は、閉じた多角形であることを指定するために必要です。
draw()関数を次のように変えてみます。
void draw(cairo_t *c, int width, int height)
{
/* background */
cairo_set_source_rgb( c, 0, 0, 0.5 );
cairo_rectangle( c, 0, 0, width, height );
cairo_fill( c );
/* star */
draw_star( c, 5, width/4, height/6, height/8 );
draw_star( c, 6, width/2, height/6, height/8 );
draw_star( c, 8, 3*width/4, height/6, height/8 );
cairo_set_source_rgb( c, 0.8, 0.8, 0 );
cairo_fill_preserve( c );
cairo_set_source_rgb( c, 0.8, 0.6, 0 );
cairo_set_line_width( c, 5 );
cairo_set_line_join( c, CAIRO_LINE_JOIN_MITER );
cairo_stroke( c );
draw_star( c, 5, width/4, height/2, height/8 );
draw_star( c, 6, width/2, height/2, height/8 );
draw_star( c, 8, 3*width/4, height/2, height/8 );
cairo_set_source_rgb( c, 0.8, 0.8, 0 );
cairo_fill_preserve( c );
cairo_set_source_rgb( c, 0.8, 0.6, 0 );
cairo_set_line_width( c, 5 );
cairo_set_line_join( c, CAIRO_LINE_JOIN_BEVEL );
cairo_stroke( c );
draw_star( c, 5, width/4, 5*height/6, height/8 );
draw_star( c, 6, width/2, 5*height/6, height/8 );
draw_star( c, 8, 3*width/4, 5*height/6, height/8 );
cairo_set_source_rgb( c, 0.8, 0.8, 0 );
cairo_fill_preserve( c );
cairo_set_source_rgb( c, 0.8, 0.6, 0 );
cairo_set_line_width( c, 5 );
cairo_set_line_join( c, CAIRO_LINE_JOIN_ROUND );
cairo_stroke( c );
}
出力は次のようになります。
ちょっと分かりにくいかも知れませんが、角の部分の描画方法が異なっています。
一番上の段では、ストローク出力前にcairo_set_line_join( c, CAIRO_LINE_JOIN_MITER )としています。角の部分は、角を作る線分が両方延長され尖った形になっています。デフォルトはこのモードです。
真ん中の段ではcairo_set_line_join( c, CAIRO_LINE_JOIN_BEVEL )としています。角の部分は、線分を単純に折り返したようになっています。
一番下の段ではcairo_set_line_join( c, CAIRO_LINE_JOIN_ROUND )としており、角が丸まった形になっています。
交差するパスの塗りつぶしモード
先ほどと違うやり方で、次のように五芒星を描いてみましょう。
# include <math.h>
void draw_star_edge(cairo_t *c, double cx, double cy, double r, double a)
{
cairo_line_to( c, cx - r * sin( a ), cy - r * cos( a ) );
}
void draw_star(cairo_t *c, double cx, double cy, double r)
{
cairo_move_to( c, cx, cy-r );
draw_star_edge( c, cx, cy, r, 2*M_PI*2/5 );
draw_star_edge( c, cx, cy, r, 2*M_PI*4/5 );
draw_star_edge( c, cx, cy, r, 2*M_PI*1/5 );
draw_star_edge( c, cx, cy, r, 2*M_PI*3/5 );
}
void draw(cairo_t *c, int width, int height)
{
/* background */
cairo_set_source_rgb( c, 0, 0, 0.5 );
cairo_rectangle( c, 0, 0, width, height );
cairo_fill( c );
/* star */
cairo_set_source_rgb( c, 0.8, 0.8, 0 );
draw_star( c, width/4, height/2, height/6 );
cairo_set_fill_rule( c, CAIRO_FILL_RULE_WINDING );
cairo_fill( c );
draw_star( c, 3*width/4, height/2, height/6 );
cairo_set_fill_rule( c, CAIRO_FILL_RULE_EVEN_ODD );
cairo_fill( c );
}
敢えてパスは閉じていません(fillしているだけなので、閉じても同じ結果になります)。cairo_set_fill_rule()は、パスの中の塗りつぶし方法を指定する関数です。出力は次のようになります。
cairo_set_fill_rule( c, CAIRO_FILL_RULE_WINDING )とすると、パスが交差していても関係なくその内側が全て塗りつぶされます。デフォルトではこのモードになっています。
cairo_set_fill_rule( c, CAIRO_FILL_RULE_EVEN_ODD )とすると、右側のようにパスの交差部分を境界として塗る領域と塗らない領域が選り分けられています。