LoginSignup
1
0

More than 3 years have passed since last update.

Cairoを使う:(その11)画像の部分表示

Last updated at Posted at 2020-10-20

画像の透過表示

深い意味はありませんが、今回はテスト用画像ファイルにThe USC-SIPI Image DatabaseMiscellaneousにある4.2.03mandrillを拝借します。こんな絵です。
test.png
元画像はTIFF形式なので、ImageMagick等を用いてPNGに変換しておいて下さい。

これを次のように半透明化することを最初の例題とします。
test.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ファイルに描画結果を出力しています。これが上掲の画像です。

画像を部分表示する

次の例では、画像の指定した部分のみ描画するようにしてみます。こんな図を得るのが目的です。
test.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()を呼んでクリップ解除しています。

画像の一部分を指定された位置に表示する

最後の例は次のような図を出力するものです。
test.png
コードを示します。

#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)が現れるわけです。

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