Cairoを使う：（その11）画像の部分表示

### 画像の透過表示

深い意味はありませんが、今回はテスト用画像ファイルに[The USC-SIPI Image Database](http://sipi.usc.edu/database/database.php)の[Miscellaneous](http://sipi.usc.edu/database/database.php?volume=misc)にある[4.2.03mandrill](http://sipi.usc.edu/database/download.php?vol=misc&img=4.2.03)を拝借します。こんな絵です。
![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706455/32d44771-7045-d5d3-899a-bbd6ddd208b0.png)
元画像はTIFF形式なので、[ImageMagick](https://imagemagick.org/index.php)等を用いてPNGに変換しておいて下さい。

これを次のように半透明化することを最初の例題とします。
![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706455/1af12628-e30c-8265-73a4-099563499b82.png)
[（その4）図形をパターンで塗りつぶす](https://qiita.com/zhidao/items/38267b2c4b935cf29f93)で画像からパターンを作成する方法を、[（その6）マスクとクリップ](https://qiita.com/zhidao/items/5004eda529217bb8ff79)にてマスクパターンを描画ソースに重ねる方法をそれぞれ説明しました。これらを組み合わせれば出来そうではあります（事実できます）。が、ここではもう少し簡単な方法を説明します。パターンを経由せずサーフェスを直接マスク透過する方法です。ソースコードを次に示します。

```C
#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）変換行列](https://qiita.com/zhidao/items/5e4b6b2ab8e2db6feb15)で説明済みです）。その後、画像と同サイズの描画用サーフェスおよびマスク用サーフェスをcairo_image_surface_create()でそれぞれ作り、cairoコンテクスト化しています。

最初のcreate_mask()関数で全面半透明（アルファ値0.5）の長方形マスクを作成します（[（その4）](https://qiita.com/zhidao/items/38267b2c4b935cf29f93)で説明したように、コンテクストcmに描画すれば対応付けられたサーフェスmaskに即座に反映されます）。続くdraw()関数では、cairo_set_source_surface()で画像サーフェスをソースに指定し、cairo_mask_surface()でマスク用サーフェスを直接用いて転写しています。

横着して、X11は用いず直接PNGファイルに描画結果を出力しています。これが上掲の画像です。

### 画像を部分表示する

次の例では、画像の指定した部分のみ描画するようにしてみます。こんな図を得るのが目的です。
![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706455/056830fd-d530-6eb1-9d3b-3fedbd401235.png)
これは[（その6）マスクとクリップ](https://qiita.com/zhidao/items/5004eda529217bb8ff79)で用いたcairo_clip()を応用すればできます。ソースコードを示します。

```C
#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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/706455/de133e19-68fb-4d36-81dd-9d0c98ac4303.png)
コードを示します。

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