画像の透過表示
深い意味はありませんが、今回はテスト用画像ファイルにThe USC-SIPI Image DatabaseのMiscellaneousにある4.2.03mandrillを拝借します。こんな絵です。
元画像はTIFF形式なので、ImageMagick等を用いてPNGに変換しておいて下さい。
これを次のように半透明化することを最初の例題とします。
(その4)図形をパターンで塗りつぶすで画像からパターンを作成する方法を、(その6)マスクとクリップにてマスクパターンを描画ソースに重ねる方法をそれぞれ説明しました。これらを組み合わせれば出来そうではあります(事実できます)。が、ここではもう少し簡単な方法を説明します。パターンを経由せずサーフェスを直接マスク透過する方法です。ソースコードを次に示します。
# include <cairo/cairo.h>
void create_mask(cairo_t *cm, int width, int height)
{
cairo_set_source_rgba( cm, 1, 1, 1, 0.5 );
cairo_rectangle( cm, 0, 0, width, height );
cairo_fill( cm );
}
void draw(cairo_t *c, cairo_surface_t *s, cairo_surface_t *mask, int width, int height)
{
/* background */
cairo_set_source_rgb( c, 1, 1, 1 );
cairo_rectangle( c, 0, 0, width, height );
cairo_fill( c );
/* whole image (half-transparent) */
cairo_set_source_surface( c, s, 0, 0 );
cairo_mask_surface( c, mask, 0, 0 );
}
int main(int argc, char** argv)
{
cairo_surface_t *cs;
cairo_surface_t *img;
cairo_surface_t *mask;
cairo_t *c;
cairo_t *cm;
int width, height;
img = cairo_image_surface_create_from_png( "mandrill.png" );
width = cairo_image_surface_get_width( img );
height = cairo_image_surface_get_height( img );
cs = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, width, height );
c = cairo_create( cs );
mask = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, width, height );
cm = cairo_create( mask );
create_mask( cm, width, height );
draw( c, img, mask, width, height );
cairo_surface_write_to_png( cs, "test.png" );
cairo_destroy( cm );
cairo_surface_destroy( mask );
cairo_destroy( c );
cairo_surface_destroy( cs );
cairo_surface_destroy( img );
return 0;
}
最初に元画像(PNG形式)をcairo_image_surface_create_from_png()で読み込み、そのサイズをcairo_image_surface_get_width()およびcairo_image_surface_get_height()で取得しています(これらは(その9)変換行列で説明済みです)。その後、画像と同サイズの描画用サーフェスおよびマスク用サーフェスをcairo_image_surface_create()でそれぞれ作り、cairoコンテクスト化しています。
最初のcreate_mask()関数で全面半透明(アルファ値0.5)の長方形マスクを作成します((その4)で説明したように、コンテクストcmに描画すれば対応付けられたサーフェスmaskに即座に反映されます)。続くdraw()関数では、cairo_set_source_surface()で画像サーフェスをソースに指定し、cairo_mask_surface()でマスク用サーフェスを直接用いて転写しています。
横着して、X11は用いず直接PNGファイルに描画結果を出力しています。これが上掲の画像です。
画像を部分表示する
次の例では、画像の指定した部分のみ描画するようにしてみます。こんな図を得るのが目的です。
これは(その6)マスクとクリップで用いたcairo_clip()を応用すればできます。ソースコードを示します。
# include <cairo/cairo.h>
void draw_img_clip(cairo_t *c, cairo_surface_t *s, int i, int j, int width, int height)
{
cairo_set_source_surface( c, s, 0, 0 );
cairo_rectangle( c, width*i, height*j, width, height );
cairo_clip( c );
cairo_paint( c );
cairo_reset_clip( c );
}
void draw(cairo_t *c, cairo_surface_t *s, int width, int height)
{
/* background */
cairo_set_source_rgb( c, 1, 1, 1 );
cairo_rectangle( c, 0, 0, width, height );
cairo_fill( c );
/* image */
draw_img_clip( c, s, 0, 1, width/4, height/4 );
draw_img_clip( c, s, 1, 0, width/4, height/4 );
draw_img_clip( c, s, 1, 3, width/4, height/4 );
draw_img_clip( c, s, 2, 0, width/4, height/4 );
draw_img_clip( c, s, 2, 2, width/4, height/4 );
draw_img_clip( c, s, 3, 1, width/4, height/4 );
}
int main(int argc, char** argv)
{
cairo_surface_t *cs;
cairo_surface_t *img;
cairo_t *c;
int width, height;
img = cairo_image_surface_create_from_png( "mandrill.png" );
width = cairo_image_surface_get_width( img );
height = cairo_image_surface_get_height( img );
cs = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, width, height );
c = cairo_create( cs );
draw( c, img, width, height );
cairo_surface_write_to_png( cs, "test.png" );
cairo_surface_destroy( img );
cairo_destroy( c );
cairo_surface_destroy( cs );
return 0;
}
またも横着して結果を直接PNGファイルに出力しています。
最初のdraw_img_clip()関数が主要な処理で、コンテクストのソースに画像サーフェスを指定した後、width(ここでは元画像の幅の4分の1)×height(同じく元画像の高さの4分の1)ごとに区切ったうちのi+1行目j+1列目を描画します。cairo_rectangle()でこのような長方形の輪郭(パス)を作成し、cairo_clip()を呼ぶことで、この輪郭で囲われた範囲のソースをクリップします。cairo_paint()でクリップされた画像が転写されます。次の輪郭指定を受け付けるために、cairo_reset_clip()を呼んでクリップ解除しています。
画像の一部分を指定された位置に表示する
最後の例は次のような図を出力するものです。
コードを示します。
# include <cairo/cairo.h>
# define DIV 4
# define S 20
void draw_img_get_put(cairo_t *c, cairo_surface_t *s, int xs, int ys, int x, int y, int width, int height)
{
cairo_set_source_surface( c, s, x-xs, y-ys );
cairo_rectangle( c, x, y, width, height );
cairo_fill( c );
cairo_set_source_rgb( c, 1, 0, 0 );
cairo_rectangle( c, x, y, width, height );
cairo_stroke( c );
}
void draw(cairo_t *c, cairo_surface_t *s, int width, int height)
{
double w, h;
int i, j;
/* background */
cairo_set_source_rgb( c, 1, 1, 1 );
cairo_rectangle( c, 0, 0, width+S*(DIV-1), height+S*(DIV-1) );
cairo_fill( c );
/* image */
w = width / DIV;
h = height / DIV;
for( i=0; i<DIV; i++ )
for( j=0; j<DIV; j++ )
draw_img_get_put( c, s, w*j, h*i, (w+S)*j, (h+S)*i, w, h );
}
int main(int argc, char** argv)
{
cairo_surface_t *cs;
cairo_surface_t *img;
cairo_t *c;
int width, height;
img = cairo_image_surface_create_from_png( "mandrill.png" );
width = cairo_image_surface_get_width( img );
height = cairo_image_surface_get_height( img );
cs = cairo_image_surface_create( CAIRO_FORMAT_ARGB32, width+S*(DIV-1), height+S*(DIV-1) );
c = cairo_create( cs );
draw( c, img, width, height );
cairo_surface_write_to_png( cs, "test.png" );
cairo_surface_destroy( img );
cairo_destroy( c );
cairo_surface_destroy( cs );
return 0;
}
分割数DIVと隙間幅Sをマクロで指定しています。
最初のdraw_img_get_put( c, s, xs, ys, x, y, width, height )が一番大事で、ソースに指定した画像サーフェスの座標(xs,ys)から幅width、高さheightの部分をコンテクストcの座標(x,y)に貼り付ける処理になっています。
cairo_set_source_surface( c, s, x, y )はコンテクスト座標(x,y)を起点として指定のサーフェスを貼り付ける関数なので、ちょっと捻って起点を(x-xs, y-ys)にしてやれば、コンテクスト座標(x,y)に元画像の(xs,ys)が現れるわけです。