cairo: 2 次元画像描画ライブラリ

書いた人:須藤功平

はじめに

objects-without-aa-and-alpha.png objects-with-aa-and-alpha.png

どちらの画像の方がきれいですか?

ここでは、文字や丸い線がなめらかで、色が透過していたりする方 がきれいだということにします。本稿で紹介する cairo の長所の 1 つ は、ここでいうきれいな画像を出力できるということです。

画像を PNG や PDF や SVG など複数のフォーマットで出力したくありませ んか?

異なるフォーマットで出力するために、全く別の出力モジュールを 作るのは面倒です。それぞれの形式で特有の部分 (例えば SVG のバー ジョンなど) だけ別々に作成し、線を描くなどの多くの部分を共有 すると楽です。つまり、出力フォーマットに依存しない高レベルな 描画 API を使用するということです。

cairo を使えばそれができます。rcairo を使えばそれが Ruby ででき ます。

cairo とは

cairo は 2 次元画像を描画するためのライブラリです。まず、cairo を用いて作成した画像を紹介します。

screenshot-arc.png screenshot-clip-image.png screenshot-clip.png screenshot-curve-rectangle.png screenshot-curve.png screenshot-dash.png screenshot-gradient.png screenshot-image-pattern.png screenshot-line-cap.png screenshot-line-join.png screenshot-text-extents.png screenshot-text-on-path.png

screenshot-text-path.png

2 次元画像用のライブラリはいくつかありますが、どうして cairo を 使うのか。cairo を使う最大の理由はこれです。

多くの有名プロジェクトが採用

cairo は Firefox や GTK+*1など有名プロジェクトの描画周りのバックエ ンドとして採用されています。他にも、SVG 描画ライブラリ librsvg や PDF 描画ライブラリ poppler など各種描画ライブラリのバックエン ドとしても採用されています。

多くのプロジェクトに採用されていると、多くのフィードバックが あり、開発が進むという効果があります。このため将来的に安心し て使えます。

cairo のよさ

どうして cairo が多くのプロジェクトで採用されているのか。ここ では cairo のよさを紹介します。cairo の特長は以下の 3 点です。

  • 高品質の出力
  • 描画コンテキストを用いた API
  • 複数出力のサポート

高品質の出力

cairo はアンチエイリアスをサポートしているため、文字や丸い線な どをなめらかな線で描画します。

cairo はベクトルベースの描画モデルを採用しています。この描画モ デルでは拡大・縮小しても画質が落ちません。

PNG や GIF などで使用している、各画素に色を割り当てて画像を描画 するモデルではそうはいきません。このモデルでは、拡大・縮小し た際に画素の数が増減します。画素の過不足を補うためにそれらの 画素の色に適当な色を割り当てて補う必要があります。この時に画 質が落ちる可能性があります。

図 lena.jpg が元の画像で、図 lena-resized.jpg が 1/4 に縮小してから 4 倍に拡大した画像です。画像がぼやけてしまっているのがわかりま す。これは、最初の縮小時に画素が欠落し、次の拡大時に欠落した 画素の色を周囲の画素の色から推測しているため、全体的に隣り合 う画素どうしが似た色になるためです。

lena.jpg lena-resized.jpg
lena.jpg lena-resized.jpg

一方、ベクトルベースの描画モデルでは、各画素の色という情報で はなく、どのように描画するかという情報を持っています。各画素 にどの色を割り当てるかは実際に描画するときに決定します。例え ば、線を引く場合は点 A から点 B まで赤で線を引く、という情報を持っ ていて、実際に描画するときに点 A から点 B までの間でどの画素に赤 を割り当てればよいのかを計算します。

raster-and-vector-draw-line.png

このようにすることで画素の色を適当に推測する必要がなくなり、 拡大・縮小しても画質を落とさずに描画することができます。

他にも、描画オブジェクトをどの程度透過させるかを指定するアル ファチャンネルやグラデーションなど高品質な出力をサポートする 機能があります。

描画コンテキストを用いた API

描画コンテキストとは描画関係の情報を管理するオブジェクトです。 この描画コンテキストを用いた API で描画するときは以下のような流 れになります。

  1. 1 つの点を動かし
  2. 動いた経路を描く

例えば、線を描く場合はこうなります。

  1. ここに移動して
  2. あそこまでまっすぐに移動して
  3. 今まで動いたところに線を描く

Ruby で書くとこうです。

 def draw_line(start_x, start_y, end_x, end_y)
   move_to(start_x, start_y)
   line_to(end_x, end_y)
   stroke
 end
context-draw-line.png

点がどのように移動してきたかは描画コンテキストが知っています。 move_to や line_to をすると、描画コンテキストには「ここに移動し た」、「あそこまでまっすぐに移動した」などという情報が追加さ れます。このような点が動きまわった経路をパスといいます。 「線を描く」といった描画処理はパスに対して行います。 stroke に引数がないのはそのためです。

この API では、「ここからあそこまで線を描く」というそのものの API がなく、単に線を描くだけなのに 3 つの操作が必要になります。 面倒ではありませんか*2。そ れでは、この描画コンテキストを用いる方法の利点はなんでしょう か。

描画コンテキストはパスを管理するだけではなく、変換行列も管理 しています。この変換行列というものを使えば、パスを作る部分を 変更することなく、パス全体を移動させたり、回転させたり、拡大 縮小したりできます。例えば、点(20, 40)から点(20, 80)までの線 を描くには以下のようになります。

 move_to(20, 40)
 line_to(20, 80)
 stroke

これを、全体的に右に 40 分ずらして、点(60, 40)から点(60, 80)ま での線を描くようにします。

context-move-by-matrix.png

変換行列を使わず、単純にやる場合はこうなるでしょう。

 move_to(60, 40)
 line_to(60, 80)
 stroke

変換行列を使えばこうなります。

 translate(40, 0)

 move_to(20, 40)
 line_to(20, 80)
 stroke

translate で x 座標だけを右に 40 分ずらしています*3。注目してほしいのは move_to と line_to の引数が変わっていないところです。 これは描画処理の部分をまったく変更せずに描画される結果全体を 移動させたり、回転させたり、拡大縮小できるということです。

たとえば、以下のように常に描画領域が 100x100 であるとして描画 処理の部分を描くことができます。scale は拡大/縮小率を 設定します。

 # width/height は実際の描画領域の幅/高さ
 scale(width / 100.0, height / 100.0)

 # width/height に関わらず左上から右下に対角線を描く
 move_to(0, 0)
 line_to(100, 100)
 stroke

この機能はサイズの違う結果を複数生成したい場合に便利です。サ ムネイル画像を用意したい場合や、ウィンドウサイズに合わせて内 容を表示したい場合などです。

複数出力のサポート

描画処理に cairo を利用すると再利用性が高くなります。cairo は PNG や PDF などの画像フォーマットとして出力するだけではなく、 GTK+ や Windows、Mac OS X のウィジェットに描画することもできま す。また、そのために move_to や stroke、 translate などといった描画関係の処理を変更する必要はあ りません。出力先が画像フォーマットでも、ウィジェットでも同じ ものを使用できます。これは cairo が描画内容を出力する部分を抽 象化しているからです。

cairo-multi-output.png

これは Web アプリケーションでもデスクトップアプリケーションで も嬉しい機能です。Web アプリケーションでは、ブラウザ上でのプ レビュー用として PNG 形式の画像を返し、印刷用として PDF 形式の画 像を返すことができます。デスクトップアプリケーションでは、ウィ ジェットへの描画内容をスクリーンショットとして PNG や PDF 形式で 出力することができます。しかも、これらは描画処理をまったく変 更することなく実現できます。

cairo の欠点

cairo の欠点はなんといっても画像効果をサポートしていないこと でしょう。

画像効果とは、「ぼかし」や「照明」といったものです。

blur.png

画像効果をサポートしていないと困る例をあげましょう。例えば、 「ぼかし」をサポートしていない場合はきれいな影を作ることが大 変です。少しずらしてグレーっぽい色を描画すれば影を作ることが できますが、ふわっとした影は簡単には作れません。Web 2.0 用の 画像ではこのふわっとした影が重要になる*4ということなので、この欠点は致命的かもしれません。

rcairo での実装

Ruby から cairo を使うためには rcairo というライブラリを使う必要が あります。rcairo は単なる cairo のラッパーではなく、cairo の API 上 に Ruby で書かれた便利な機能を追加しています。

開発版の rcairo で実験的に「ぼかし」風の画像効果を実装していま す。実験的というだけあってうまく動かない場合もあるのですが、 以下の実行例のようにアルファチャンネルがないものに対してはそ れっぽく動きます。ただ、動作は少し重いです。

pseudo-blur.png

本稿では「ぼかし」風画像効果については扱いません。使いかたは rcairo のリポジトリにあるサンプルファイルを参考にしてください。

http://webcvs.cairographics.org/rcairo/samples/blur.rb?revision=HEAD&view=markup

rcairo の欠点

rcairo を使うと Ruby らしい API で cairo を使うことができる半面、以 下のような欠点があります。

  • 拡張ライブラリなので通常のライブラリよりも導入が大変。
  • ドキュメントがない。

導入

導入に関しては、Windows*5/一部の Linux/*BSD/Mac OS X*6 にはパッケージが用意されているので、それを利用すれば負担を減 らすことができます。

rcairo 用の gem は RubyForge にアップロードされています。

 % sudo gem install cairo

ただし、gem にはバイナリは含まれていないので、gem のインストー ル時にビルドすることになります。そのため、gem を使ってもダウ ンロードする手間が減るくらいにしかならないかもしれません。

Ruby-GNOME2 プロジェクトのプロジェクトリーダであるむとうさん が Windows 版のバイナリを作成し、配布してくれています。このバ イナリをインストールすると Ruby-GNOME2 プロジェクトのライブラ リもインストールすることができるという特典付きです。

ドキュメント

ドキュメント不足はこれ以降の文章で補ってください。

基本的な使い方

それでは、ここからは cairo を用いた基本的な画像の作り方を説明し ます。

rcairo

Ruby で cairo を使うには rcairo を使います。rcairo は Ruby と cairo を つなぐ糊の役目を果たします。rcairo を使うことにより、より Ruby らしい方法で cairo を利用することができます。

1 つ例を挙げます。cairo には状態を保存する save と保存して おいた状態を復元する restore という API があります。これら は以下のようにペアで使います。

 save
 ...
 restore

save だけを呼んで restore を呼ぶことを忘れないため にブロックを使うこともできます。これはちょうど open のブ ロックの使いかたと同じです。

 save do
   ...
 end

流れ

rcairo で画像作成をする場合は以下のような流れになります。場合 によっては前処理や後処理の部分を省略することもできます*7

  1. サーフェス (Cairo::Surface) を作成する。
  2. 作成したサーフェス用のコンテキスト (Cairo::Context) を作成する。
  3. コンテキストに対して描画処理を行う。
  4. サーフェスに終了メッセージを送る。

サーフェスは「表面」という意味で、出力先を抽象化したオブジェ クトです。cairo では PNG 用/PDF 用/SVG 用/GTK+ 用のサーフェスなど いくつものサーフェスがサポートされていますが、どれも Cairo::Surface のサブクラスになっています。このようにたくさん あるサーフェスですが、私たちはサーフェスに直接触れる機会はほ とんどありません。多くの操作はコンテキストに対して行います。

例えるなら、サーフェスがキャンバスでコンテキストは画家です。 りんごの絵が欲しいとします。まず、絵を描くためのキャンバスを 用意し、イーゼル*8に掛けます (サーフェスの作成)。画家はキャンバスの前 に立ちます (サーフェス用のコンテキスト作成)。画家にりんごを 描いてくれるように頼みます (コンテキストに対する描画処理)。 描き終わったら画家にお礼をいい、りんごが描かれたキャンバスを しまいます (サーフェスへ終了メッセージの送信)。

surface-and-context.png

例: 日の丸

それでは、日の丸を描いてみましょう。出力結果はこうなります。

hinomaru.png

スクリプトは以下のようになります。

スクリプト: hinomaru.rb

 require 'cairo'

 format = Cairo::FORMAT_ARGB32
 width = 300
 height = 200
 radius = height / 3 # 半径

 surface = Cairo::ImageSurface.new(format, width, height)
 context = Cairo::Context.new(surface)

 # 背景
 context.set_source_rgb(1, 1, 1) # 白
 context.rectangle(0, 0, width, height)
 context.fill

 # 赤丸
 context.set_source_rgb(1, 0, 0) # 赤
 context.arc(width / 2, height / 2, radius, 0, 2 * Math::PI)
 context.fill

 surface.write_to_png("hinomaru.png")

以下の通り、順に説明します。

  1. Cairo::ImageSurface の作り方
  2. Cairo::Context の作り方
  3. 描画のしかた
  4. 出力

Cairo::ImageSurface の作り方

Cairo::ImageSurface はラスタ画像 (画素をたくさん集めて表現し た画像。ドット絵など。) を出力するためのサーフェスです。生の ラスタ画像を取り出すこともできますし、描いたラスタ画像を PNG 形式で出力することもできます。例では PNG 形式で出力しています。

Cairo::ImageSurface を作るには以下の 3 つの情報が必要です。

  • 各画素が持つ情報
  • 画像の幅
  • 画像の高さ

幅と高さが必要なのはわかると思います。問題は各画素が持つ情報 です。

各画素は RGB (Red, Green, Blue) で表現された色の情報やアルファ 値と呼ばれる画素がどのくらい透過するかという情報を持つことが できます。通常は色情報も透過情報もすべて使う Cairo::FORMAT_ARGB32 を指定すればよいでしょう。

幅 300、高さ 200 の画像を作成したい場合は、以下のように Cairo::ImageSurface を作ることになります。

 surface = Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32, 300, 200)

実は、1.4.0 以降の rcairo では Cairo::FORMAT_ARGB32 を省略して以下 のように書くこともできます。

 surface = Cairo::ImageSurface.new(300, 200)

さらに、現在はまだリリースされていない 1.6.0 以降 では Cairo::FORMAT_ を省略して以下のように書くこともできます。

 surface = Cairo::ImageSurface.new(:argb32, 300, 200)

他にも PNG 画像を読み込んで Cairo::ImageSurface を作る方法や、生 のラスタ画像データから作る方法もあります。詳しくはリファレン スマニュアルを参考にしてください。*9

Cairo::Context の作り方

サーフェスを作ったら、そのサーフェスを操作するためのコンテキ ストを作ります。

コンテキストの作り方は簡単です。操作対象のサーフェスを渡すだ けです。

 context = Cairo::Context.new(surface)

サーフェスへの操作はこのコンテキストを経由して行います。下準 備はこれでおしまいです。

描画のしかた

ここからが本番です。基本的に描画は以下のような流れになります。

  1. 色の設定
  2. パスを作る
  3. パスを描く (内側を塗りつぶす/沿って線を描く)

色の設定

色は RGB 形式で set_source_rgb を使って設定します。RGB は 0.0-1.0 までの間で指定します。

 context.set_source_rgb(1, 0, 0) # 赤
 context.set_source_rgb(0, 1, 0) # 青
 context.set_source_rgb(0, 0, 1) # 緑
 context.set_source_rgb(0, 0, 0) # 黒
 context.set_source_rgb(1, 1, 1) # 白
 context.set_source_rgb(1, 1, 0) # 黄
 context.set_source_rgb(0.9, 0.9, 0.9) # 薄いグレー

色だけではなく、アルファ値やグラデーションも設定できます。

色に関連するインターフェイスは、次のバージョンである rcairo 1.6.0 でかなり改善されます。まず、以下のように約 250 種類の有名 な色があらかじめ利用できます。

 Cairo::Color::RED
 Cairo::Color::WHITE
 Cairo::Color::ROSE
 ...

これらはコンテキストの色指定として利用できます。

 context.set_source_color(Cairo::Color::GREEN)

また、文字列を色に変換することもできます。

 Cairo::Color.parse("red")  # => Cairo::Color::RED
 Cairo::Color.parse("#f00") # => Cairo::Color::RED
 Cairo::Color.parse("#ff000033") # Cairo::Color::RED で
                                 # アルファ値が 0.2
                                 #  (51/255, "33".hex == 51)

実は、コンテキストの色指定に上述の文字列指定も可能になります。

 context.set_source_color("red") # == context.set_source_rgb(1, 0, 0)

他にも RGB だけではなく CMYK、HSV がサポートされます。もちろん、 RGB/CMYK/HSV 間での相互変換が可能です。

パスを作る

パスの操作にはパスを始める点を指定する move_to や現在の点 から直線を引く line_to などがあります。また、これらの基本的な 操作だけではなく、もう少し便利な四角形のパスを作る rectangle や丸を作る arc などがあります。cairo では提供していませんが、 rcairo では角が丸まった四角形のパスを作る rounded_rectangle を 用意しています。図は rectangle と rounded_rectangle の使用例です。

rectangle-and-rounded-rectangle.png

日の丸の例では、まず、背景を真っ白に塗りつぶすために画像全体 と同じ大きさの四角形のパスを作っています。

 context.rectangle(0, 0, width, height)

その後の赤い丸を描く処理では arc を使って丸いパスを作っていま す。

 context.arc(width / 2, height / 2, radius, 0, 2 * Math::PI)

arc には円の中心となる点を指定します。赤い丸を画像の中央に描 くためには縦横それぞれで真ん中の点を指定する必要があります。 width / 2 と height / 2 はそのための計算です。

arc には中央の点だけではなく、半径と円をどの角度からどの角度ま で描くかということも指定します。半径は英語では radius です。問 題は角度の指定方法です。

角度はラジアンで指定します。0-360°で一周を表す度数法 (Wikipedia にそう書いてあった) ではありません。ラジアンでは 0-2πで一周になります。度数からラジアンへは以下のように変換 できます。

 ラジアン = 度数 * (Math::PI / 180)

日の丸の赤い丸はどこも欠けていないので、一周分 (0 から 2 * Math::PI) の円を描いています。

 context.arc(..., 0, 2 * Math::PI)

8 等分されたうち 1 切れ食べられたホールケーキを描くときは以下の ようになります。

 context.arc(..., 0, (2 * Math::PI) * (7.0 / 8.0))

実は、今回の日の丸のようにどこも欠けていない円を描くために circle を使えます。

 context.circle(width / 2, height / 2, radius)

これは以下のように書くことと同じです。

 context.arc(width / 2, height / 2, radius, 0, 2 * Math::PI)

この完全な円を描くための便利なメソッド circle は cairo の API には ない、rcairo が拡張した API です。

パスを描く

パスを作っても描かなければ出力には何も現れません。パスを描く ためには以下のふたつの操作があります。

  • fill
  • stroke

fill はパスの内側を塗りつぶす操作で、stroke はパスに沿って線を 描く操作です。今回は fill だけを使いました。

fill も stroke もパスを描くとパスを消してしまいます。通常はこ の方が便利なのですが、パスが消されると困ることもあります。例 えば、赤い四角を描いて、その周りを黒く縁どりしたい場合です。

red-rectangle-with-black-border.png

この場合は fill_preserve と stroke_preserve を使います。

 context.set_source_rgb(1, 0, 0)
 context.rectangle(50, 50, 100, 100)

 # 赤い四角
 context.fill_preserve

 # 黒く縁どり
 context.set_source_rgb(0, 0, 0)
 context.stroke

より Ruby っぽく書けるように fill や stroke はブロックをとれます。

 context.set_source_rgb(1, 0, 0)
 context.rectangle(50, 50, 100, 100)
 context.fill

この例は以下のようにも書けます。

 context.fill do
   context.set_source_rgb(1, 0, 0)
   context.rectangle(50, 50, 100, 100)
 end

この書き方の利点は break を使って途中で処理をやめることができ たり、fill の対象が明示的になるということです。

 context.fill do
   context.set_source_rgb(1, 0, 0)
   break if error_occurs
   context.rectangle(50, 50, 100, 100)
 end

基本的な描画方法は以上の通りです。この他にもたくさん便利な操 作や高度な操作がありますが、描画の流れはここで紹介した以下の ようになります。

  1. 色の設定
  2. パスの作成
  3. パスの描画

それでは描画した内容を出力してみましょう。

出力

描画内容を出力する方法はサーフェスによって異なります。描画処 理では一切サーフェスを意識する必要はありませんでしたが、出力 のときは意識する必要があります。

Cairo::ImageSurface では write_to_png を使って PNG 形式で描画内容 を出力します。例では以下のようにサーフェス作成時に持っておい たサーフェスに対して write_to_png を呼び出していました。

 surface.write_to_png("hinomaru.png")

サーフェスはコンテキストから取り出すことができるので以下のよ うに書くこともできます。

 context.target.write_to_png("hinomaru.png")

Cairo::ImageSurface 以外のサーフェスでは finish を呼ぶことで出力 をするものが多いです。例えば、PDF を生成するサーフェスなどがそ うです。*10

 surface = Cairo::PDFSurface.new(...)
 context = Cairo::Context.new(surface)
 ...
 context.target.finish

基本の流れのまとめ

cairo を用いた画像の作成では、出力先を抽象化したサーフェスと サーフェスへの操作を担当するコンテキストを使いました。

サーフェスを意識するのは、最初にサーフェスを作成するときと、 最後にサーフェスから結果を取り出すときだけです。描画操作はコ ンテキストに対して行い、サーフェスを意識する必要はありません。

basic-conclusion.png

描画操作でサーフェスを意識する必要がないということは、PNG 形 式や PDF 形式など異なる出力先でも同じ描画処理ルーチンを使いま わすことができるということです。これは cairo を用いた場合の大 きなメリットです。

本稿ではグラデーションなどのより高度の機能の使いかたは紹介し ません。その代わり、他のライブラリと連携して使用する方法を紹 介します。

他のライブラリとの連携

cairo を出力モジュールとして使っているライブラリがいくつかあ ります。例えば、SVG 描画ライブラリの librsvg や PDF 描画ライブラ リの poppler などです。ここではそれらのライブラリを利用した例 を示します。

これらのライブラリと連携すると、cairo と各描画ライブラリは図の ような関係になります。

cairo-with-rendering-libraries.png

svg2png.rb

まずは SVG 描画ライブラリである librsvg と連携する例です。cairo は SVG 形式で出力できるライブラリですが、librsvg は SVG 形式のデー タを読み込んで描画するライブラリです。

Ruby から librsvg を利用するためには Ruby/RSVG が必要です。 Ruby/RSVG は Ruby-GNOME2 プロジェクトに含まれています。

ここでは、svg2png.rb という SVG ファイルを読み込んで、PNG 形式に 変換するスクリプトを作ります。単に変換するだけではつまらない ので拡大・縮小をサポートします。

スクリプト: svg2png.rb

 #!/usr/bin/env ruby

 require "rsvg2"

 if ARGV.size < 1
   puts "usage: #{$0} input.svg [scale_ratio]"
   exit(-1)
 end

 input, ratio = ARGV
 ratio ||= 1.0
 ratio = ratio.to_f

 handle = RSVG::Handle.new_from_file(input)

 dim = handle.dimensions
 width = dim.width * ratio
 height = dim.height * ratio

 surface = Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32, width, height)
 context = Cairo::Context.new(surface)

 context.scale(ratio, ratio)
 context.render_rsvg_handle(handle)

 surface.write_to_png(input.sub(/\.[^.]+$/i, '.png'))

使い方はこうなります。

 % ./svg2png.rb SVGファイル 拡大率

例えば、元の SVG 画像の半分の大きさの PNG 画像にしたい場合は以下 のようにします。

 % ./svg2png.rb input.svg 0.5

SVG 画像の読み込み

Ruby/RSVG を使うには以下のように rsvg2 を require します。

 require 'rsvg2'

もし、システムに rcairo がインストールされていれば自動で rcairo も読み込みます。そのため、require 'cairo'は必要はありません。

librsvg では SVG を操作するためにハンドルというオブジェクトを使 います。ハンドルとは車に付いているそれと同じです。車に付いて いるハンドルは車を操作しますが、librsvg のハンドルは SVG を操作 します。SVG 画像を読み込むのもハンドルですし、読み込んだ SVG 画 像を描画するのもハンドルです。

ハンドルは Ruby/RSVG では RSVG::Handle オブジェクトになります。 今回はファイルから SVG 画像を読み込み、RSVG::Handle オブジェク トを作ります。

 handle = RSVG::Handle.new_from_file(input)

これだけで SVG 画像を描画する準備が整います。あとは出力用のサー フェスと、そのサーフェスを操作するためのコンテキストを用意す るだけです。

出力サーフェスの用意

今回は PNG 画像として出力するため、サーフェスには Cairo::ImageSurface を使います。Cairo::ImageSurface を作るには 画像の幅と高さが必要でした。これらは変換元の SVG 画像の幅と高 さ、それと拡大率がわかれば求めることができます。

読み込んだ SVG 画像のサイズはハンドルが知っています。 dimensions メソッドは幅や高さなどを RSVG::DimensionData オブジェ クトとして返します。

 dim = handle.dimensions
 width = dim.width * ratio
 height = dim.height * ratio

今回は幅と高さだけが必要なので、以下のように to_a で配列化し、 幅と高さだけを取り出すという方法もあります *11

 width, height, = handle.dimensions.to_a
 width *= ratio
 height *= ratio

幅と高さがわかったのでサーフェスを作ります。

 surface = Cairo::ImageSurface.new(Cairo::FORMAT_ARGB32, width, height)

SVG の描画

ハンドルが持っている SVG 画像をサーフェスに描画するためにコン テキストを作ります。

 context = Cairo::Context.new(surface)

render_rsvg_handle でコンテキストにハンドルを描画します *12

 context.render_rsvg_handle(handle)

しかし、このままだと拡大率が反映されず、元の SVG 画像と同じ大き さで描画されてしまいます。拡大率を変更するには scale を使います。

 context.scale(ratio, ratio)

今回は縦も横も同じ拡大率を使うため、ratio が 2 つ並んでいます。

scale で拡大率を設定すると、それ以降は設定した拡大率で描画され ます。今回は拡大率に合わせて SVG 画像を描画したいので render_rsvg_handle の前に scale を呼ぶ必要があります。

 context.scale(ratio, ratio)
 context.render_rsvg_handle(handle)

これでサーフェスに SVG 画像と同じ内容が設定した拡大率で描画さ れます。あとは PNG 画像を出力するだけです。

 surface.write_to_png(input.sub(/\.[^.]+$/i, '.png'))

以上で SVG 画像を PNG 画像に変換できます。

image2pdf.rb

次は画像読み込み・操作ライブラリである GdkPixbuf と連携する例で す。GdkPixbuf は GTK+ に含まれていますが、X Window System など GUI 環境がなくても動作します。つまり、サーバ上で CGI のバックエ ンドとして利用することもできるということです。

Ruby から GdkPixbuf を利用するためには Ruby/GdkPixbuf2 が必要で す。Ruby/GdkPixbuf2 は Ruby-GNOME2 プロジェクトに含まれています。

rcairo と連携する例の前に、まず Ruby/GdkPixbuf2 の例を示します。

画像形式変換

GdkPixbuf は以下の 3 つの機能を提供します。

  • 画像の読み込み
  • 画像の書き出し
  • 簡単な画像操作 (回転・縮小・合成)

以下は PNG 形式の画像を 180°回転させ、JPEG 形式として保存する例で す。

スクリプト: png-to-inverted-jpeg.rb

 require 'gdk_pixbuf2'

 input = ARGV.shift
 output = input.sub(/\.png$/i, ".jpg")
 pixbuf = Gdk::Pixbuf.new(input)
 inverted_pixbuf = pixbuf.flip(false)
 inverted_pixbuf.save(output, "jpeg")

GdkPixbuf は画像形式を自動認識できます。そのため、任意の形式 の画像を PNG 形式に変換するスクリプトは以下のようになります。

スクリプト: image2png.rb

 require 'gdk_pixbuf2'

 input = ARGV.shift
 output = input.sub(/\.[^.]+$/i, ".png")
 Gdk::Pixbuf.new(input).save(output, "png")

GdkPixbuf がサポートしている画像形式は PNG や JPEG、GIF など 10 数種 類あります。GdkPixbuf の画像入出力モジュールは後から追加でイン ストールすることができ、前述の librsvg をインストールしてあれば SVG 形式も読み込むことができます。

画像を PDF 形式に変換

それでは、rcairo と Ruby/GdkPixbuf2 が連携する例として、画像を PDF 形式に変換するスクリプトを作ります。PDF 形式で出力するので、 サーフェスには今まで使ってきた Cairo::ImageSuface ではなく Cairo::PDFSurface を使います。

Cairo::PDFSurface を使うときは、show_page を呼ぶことを忘れない ようにしてください。これを忘れるとファイルには何も出力されま せん。

show_page はページという概念のないサーフェスでは省略できます。 例えば、今まで利用してきた 1 枚の画像を表している Cairo::ImageSurface がページという概念のないサーフェスです。実 際、今までの例では show_page を省略してきました。しかし、PDF 形 式のようにページという概念があるサーフェスは show_page のタイミ ングで描画内容の書き出しを行うので、省略すると望んだ結果を得 られません。Cairo::PDFSurface 以外では PostScript を出力する Cairo::PSSurface と SVG を出力する Cairo::SVGSurface が show_page を省略できません。

以下が画像を PDF 形式に変換するスクリプトです。

スクリプト: image2pdf.rb

 require 'gdk_pixbuf2'
 require 'cairo'

 input = ARGV.shift
 output = input.sub(/\.[^.]+$/i, ".pdf")

 pixbuf = Gdk::Pixbuf.new(input)
 width = pixbuf.width
 height = pixbuf.height

 surface = Cairo::PDFSurface.new(output, width, height)
 context = Cairo::Context.new(surface)

 context.set_source_pixbuf(pixbuf)
 context.paint

 context.show_page

 surface.finish

このスクリプトでるびまのロゴを変換すると以下のようになります。

rubima_logo.png rubima_logo.pdf

それでは、Cairo::PDFSurface オブジェクトの作成と Gdk::Pixbuf オ ブジェクトの描画について説明します。

GUI 環境がない場合

スクリプトの説明に入る前に、残念なお知らせがあります。このス クリプトは Ruby/GdkPixbuf2 0.16.0 より前のバージョンでは動きま せん。これより前のバージョンでも動くようにするには以下のよう にします。

 require 'gtk2' # 追加

 context.set_source_pixbuf(pixbuf)       # 変更前
 context.set_source_pixbuf(pixbuf, 0, 0) # 変更後

ただし、このスクリプトは X Window System など GUI 環境がないと動 作しません。ただし、どうやっても動作しないというわけではなく て、ちょっとした小細工をすると動作します。問題は以下の部分で す。

 require 'gtk2'

0.16.0 より前のバージョンでは rcairo と Ruby/GdkPixbuf2 を連携させ る部分は Ruby/GTK2 に含まれています。そして、最近の Ruby/GTK2 は require すると GUI 環境の初期化も行ってくれます。そのため、GUI 環 境がない場合は require しただけで例外が発生してしまいます。

rcairo と Ruby/GdkPixbuf2 の連携部分は GUI 環境がなくても動作しま す。つまり、GUI 環境の初期化が失敗したという例外は無視してもよ いのです。そこで、以下のようにします。

 begin
   require 'gtk2'
 rescue RuntimeError
 end

これで GUI 環境がなくても動作します。

Cairo::PDFSurface.new

それでは本題に入ります。

PDF 形式用のサーフェスは Cairo::PDFSurface です。今回はこのサー フェスを使います。Cairo::PDFSurface オブジェクトを作るために は 3 つの情報が必要になります。

  • 出力ファイル名
  • ページの幅
  • ページの高さ

幅と高さはポイントで指定します。今回は画像と同じ大きさのペー ジを作成するので以下のようになります。

 output = input.sub(/\.[^.]+$/i, ".pdf")
 width = pixbuf.width
 height = pixbuf.height

 surface = Cairo::PDFSurface.new(output, width, height)

実は、ファイルではなく、ネットワーク上やメモリ上に出力するこ ともできます。そのときは出力ファイル名ではなく、write メソッド を持ったオブジェクトを指定します。以下はメモリ上に PDF を出力 する例です。

 require 'stringio'
 output = StringIO.new("")
 surface = Cairo::PDFSurface.new(output, width, height)
 ...
 pdf = output.string

StringIO オブジェクトの代わりに Socket オブジェクトを指定すれば、 ネットワーク上に出力することができますし、$stdout を指定すれ ば標準出力に出力することもできます。

Gdk::Pixbuf の描画

Gdk::Pixbuf をコンテキストに描画する方法を説明する前に、コン テキストのソースについて説明しなければいけません。

ソースはカーボン紙のようなものです。パスを描いたり塗りつぶし たりすると、ソースの内容がソースの下にあるサーフェスに描画さ れます。

source-as-carbon-paper.png

しかし、普通のカーボン紙とは違って単色とは限りません。グラデー ションやサーフェスに描画した内容を使うことも出来ます。

several-sources.png

コンテキストはいつも必ず 1 つソースを持っています。今までは set_source_rgb で色を指定し、ソースを設定していました。この方 法だとパスは 1 色で単調に描かれます (または塗りつぶされます)。

画像を描画するには、画像の内容をソースに設定しパスを塗りつぶ します。もう少し細かくいうと、以下のような流れになります。

  1. 描画したい画像をソースに設定する
  2. 描画したい画像と同じ大きさのパスを作る
  3. そのパスを塗りつぶす

コードにするとこうなります。

 width = pixbuf.width
 height = pixbuf.height

 context.set_source_pixbuf(pixbuf)
 context.rectangle(0, 0, width, height)
 context.fill

実は、画像の描画には rectangle と fill の組合せよりも paint の方が 便利です。paint を使うとこうなります。

 context.set_source_pixbuf(pixbuf)
 context.paint

paint は現在設定されているソースでサーフェス全体を塗りつぶす 操作です。そのため、rectangle で行っていたパスを作るという操 作が不要になります。

paint の利点はパスを作らなくてもよいというだけではありません。 アルファ値を設定することもできます。例えば、以下のようにする と画像全体が半透明になって描画されます。

 context.set_source_pixbuf(pixbuf)
 context.paint(0.5)
paint-without-alpha-and-paint-with-alpha.png

後処理を忘れずに!

最初に image2pdf.rb のソースを紹介したときも触れましたが、忘れ てハマリやすいことなのでもう一度触れておきます。

今回使った Cairo::PDFSurface や SVG を出力するための Cairo::SVGSurface などページという概念があるサーフェスでは、各 ページの描画が終わった後に show_page を呼ぶことを忘れないでくだ さい。忘れると描画内容が正しく書き出されず、出力内容が壊れて しまいます。

また、全ての描画が終わった後に finish を呼ぶことも忘れないでく ださい。show_page と同様に出力内容が壊れてしまったり、出力され たはずと思っていたのにまだ出力されていなかった、ということが あります。

現在の安定版 cairo 1.4.x では finish に関してはサーフェスが GC され るときに内部で自動的に呼び出されるため省略可能と言えば省略可 能です。ただし、どのタイミングでサーフェスが GC されるかはいつ でも同じとは限らないため、GC に期待してスクリプトを書くことは 危険です。これはちょうど File オブジェクトが GC されるときに close されるという関係と似ています。

次の rcairo のリリース (おそらく 1.6.0) では File.open の ように以下のように書けるようになります。

 Cairo::ImageSurface.new(300, 300) do |surface|
   context = Cairo::Context.new(surface)
   ...
 end

この書き方では File.open と同じように、ブロックを抜けるときに 自動的に finish されます。

image2pdf-with-margin.rb

次は画像の描画方法をもう少し詳しく説明するための例です。前の 例と似ていますが少しだけ違います。今度の例も画像を PDF に変換 しますが、今度は画像に余白を付けて PDF に変換します。

image2pdf-with-margin-output.png

この例では左隅に画像を配置するのではなく、任意の場所に配置す る方法を示します。実際のスクリプトは以下のようになります。

スクリプト: image2pdf-with-margin.rb

 require 'gdk_pixbuf2'
 require 'cairo'

 input, margin = ARGV
 output = input.sub(/\.[^.]+$/i, ".pdf")

 margin ||= 10
 margin = Integer(margin)

 pixbuf = Gdk::Pixbuf.new(input)
 width = pixbuf.width + 2 * margin
 height = pixbuf.height + 2 * margin

 surface = Cairo::PDFSurface.new(output, width, height)
 context = Cairo::Context.new(surface)

 context.translate(margin, margin)
 context.set_source_pixbuf(pixbuf)
 context.paint

 context.show_page

 surface.finish

translate

画像の位置を左隅ではなく任意の位置に平行移動させてソースに設 定する場合は translate を使います。もうすっかり忘れてい ると思いますが、translate はパスを平行移動させるときに も使っていました。

 context.translate(margin, margin)
 context.set_source_pixbuf(pixbuf)
 context.paint

大事なことは set_source_pixbuf を呼ぶ前に translate を呼ぶことです。位置をずらして描画したいのだから描画操作であ る paint を呼ぶ前でもよさそうですが、それではうまくいきま せん。それはパスではなく、ソースをずらす必要があるからです。 一方、画像の位置はそのままで、画像の一部だけを描画したい場合 はパスをずらします。

translate-source-and-path.png

図では paint ではなく rectangle を使っていますが、これは paint は translate の影響を受けないからです。paint は無限大に広がる rectangle を指定してから fill していると考えてください。つまり、 paint の前に translate をしても translate の対象としたい rectangle が大きすぎて、translate で多少動かしたとしても全く影響を与えら れないのです。

pdf-thumbnail.rb

次は PDF 描画ライブラリである poppler と連携する例です。cairo は PDF 形式で出力できるライブラリですが、poppler は PDF 形式のデータ を読み込んで描画するライブラリです。

Ruby から poppler を利用するためには Ruby/Poppler が必要です。 Ruby/Poppler は Ruby-GNOME2 プロジェクトに含まれています。

ここでは、pdf-thumbnail.rb というスクリプトを作ります。このス クリプトは PDF ファイルを読み込んで、各ページを縮小し、一覧表示 した PDF を生成します。この例も X Window System など GUI 環境なしで 動きます。

注: ただし、6 月にリリースされるはずの poppler 0.6.0 が必要です。

以下に、入力 PDF と実行結果の PDF を示します。

スクリプト: pdf-thumbnail.rb

 #!/usr/bin/env ruby

 require "poppler"

 if ARGV.size < 1
   puts "usage: #{$0} input.pdf"
   exit(-1)
 end

 input = ARGV.shift
 output = input.sub(/(\.[^.]+)$/, '-thumb\1')

 columns = 3
 rows = 2
 pages_in_a_page = rows * columns

 x_ratio = 1.0 / columns
 y_ratio = 1.0 / rows

 input_uri = GLib.filename_to_uri(File.expand_path(input))
 document = Poppler::Document.new(input_uri)

 first_page = document[0]
 width, height = first_page.size
 surface = Cairo::PDFSurface.new(output, width, height)
 context = Cairo::Context.new(surface)

 width_per_page = width / columns
 height_per_page = height / rows

 need_show_page = true
 document.each_with_index do |page, i|
   row, column = i.divmod(columns)
   row = row.modulo(rows)
   x = width_per_page * column
   y = height_per_page * row
   context.save do
     context.translate(x, y)
     context.scale(x_ratio, y_ratio)
     context.render_poppler_page(page)

     context.set_source_rgb(0.2, 0.2, 0.2)
     context.rectangle(0, 0, width, height)
     context.stroke
   end

   need_show_page = ((i + 1) % pages_in_a_page).zero?
   context.show_page if need_show_page
 end
 context.show_page unless need_show_page

 surface.finish

スクリプトが長くなって非常に心苦しいです。

使い方はこうです。

 % ./pdf-thumbnail.rb PDF ファイル

生成される PDF ファイルの名前は元の PDF ファイル名に -thumb がつい たものになります。例えば、以下のように slide.pdf というファイル を指定した場合は slide-thumb.pdf というファイルができます。

 % ./pdf-thumbnail.rb slide.pdf

PDF 文書の読み込み

Ruby/Poppler を使うには以下のように poppler を require します。

 require "poppler"

もし、システムに rcairo がインストールされていれば自動で rcairo も読み込みます。そのため、cairo を require する必要はありません。 *13

poppler では PDF 文書がドキュメントオブジェクトに対応します。ド キュメントオブジェクトを作るには PDF 文書の URI を渡します。PDF 文書のファイル名ではなくて URI です。例えば、/tmp/xxx.pdf とい う PDF 文書を開きたい場合は file:///tmp/xxx.pdf と指定する必要が あります。

これを行うための慣用句が以下になります。

 input_uri = GLib.filename_to_uri(File.expand_path(input))
 document = Poppler::Document.new(input_uri)

しかし、これではさすがに使いづらいので、次のリリースから (お そらく Ruby-GNOME2 0.17) は以下のように書けます。

 document = Poppler::Document.new(input)

つまり、URI ではなく直接ファイル名を指定できるようになります。

サーフェスの作成

今回は PDF を生成するので、サーフェスは Cairo::PDFSurface を使い ます。Cairo::PDFSurface オブジェクトを作るために必要な情報は以 下の 3 つでした。

  • 出力ファイル名
  • ページの幅
  • ページの高さ

出力ファイル名は入力ファイル名に -thumb を付けることにしました。

 input = ARGV.shift
 output = input.sub(/(\.[^.]+)$/, '-thumb\1')

ページの幅と高さは元の PDF 文書の最初のページの幅と高さと同じに します。

ドキュメントオブジェクトから各ページへは配列のようにアクセス できます。例えば、3 ページには以下のようにアクセスできます。

 third_page = document[2]

各ページを順番にアクセスしたい場合は each が使えます。

 document.each do |page|
   ...
 end

本当に配列としてアクセスしたい場合は pages を使います。

 p document.pages # [#<Poppler::Page:...>, ...]

ページの幅と高さは size で取り出せます。

 first_page = document[0]
 width, height = first_page.size

これで最初のページの幅と高さを取得することが出来るようになり ました。以下のように Cairo::PDFSurface を作ることが出来ます。

 first_page = document[0]
 width, height = first_page.size
 surface = Cairo::PDFSurface.new(output, width, height)
 context = Cairo::Context.new(surface)

ページの描画

あとは、各ページを縮小して描画するだけです。ただし、1 ページに 縮小された複数のページを描画するため、各ページの描画位置を計 算し、並んで描画するようにしなければいけません。

pdf-thumbnail-pages.png

描画位置を計算する部分は本質的ではないため、ここでは省略しま す。このスクリプトの本質的な部分は以下のようになります。

x_ratio と y_ratio はそれぞれ x 方向、y 方向の拡大率です。図では横 に 3 ページ、縦に 2 ページ表示しているので、x 方向 (x_ratio) は 1/3 倍、y 方向 (y_ratio) は 1/2 倍の拡大率になります。

need_show_page は 1 ページに表示する最後の縮小ページを描画した ときに真になります。例えば、図だと 1 ページには 6 ページ分の縮小 ページを描画するので、6 ページ毎に真になります。

 need_show_page = true
 document.each_with_index do |page, i|
   ...
   context.save do
     context.translate(x, y)
     context.scale(x_ratio, y_ratio)
     context.render_poppler_page(page)
     ...
   end

   ...
   context.show_page if need_show_page
 end
 context.show_page unless need_show_page

描画する x/y 座標の変更を translate で行うのは image2pdf-with-margin.rb の例と同じです。元の描画対象を縮小さ せるために scale を使うのは svg2png.rb の例と同じです。

上記のコードで省略した以下の部分は縮小したページに枠を描画し ています。

 document.each_with_index do |page, i|
   ...
   context.save do
     ...
     context.set_source_rgb(0.2, 0.2, 0.2)
     context.rectangle(0, 0, width, height)
     context.stroke
   end
   ...
 end

rectangle が

 context.rectangle(x, y, width_per_page, height_per_page)

ではなく、

 context.rectangle(0, 0, width, height)

となっているのはその前にある

 context.translate(x, y)
 context.scale(x_ratio, y_ratio)

の影響を受けるからです。

注意

この例を動かすには 6 月にリリースされる予定の poppler 0.6.0 が必 要です。また、画像を含む PDF 文書を処理すると非常に重いです (これは cairo の問題)。

text2ps.rb

次はテキスト描画ライブラリである Pango と連携する例です。cairo 単体でもテキストを描画できますが、Pango と組み合わせることで、 より高度に描画することができます。例えば、センタリングや、一 部だけ文字の大きさを変えたり、下線を引いたりできます。

この例では入力されたファイルの中のテキストを A4 用紙に描画する PostScript ファイルに変換します。この例も X Window System など GUI 環境なしで動きます。

スクリプト: text2ps.rb

 require 'pango'

 input = ARGV.shift
 output = input.sub(/\.[^.]+$/i, ".ps")

 width = 595
 height = 842

 surface = Cairo::PSSurface.new(output, width, height)
 context = Cairo::Context.new(surface)

 layout = context.create_pango_layout
 layout.text = File.read(input)
 context.show_pango_layout(layout)

 context.show_page

 surface.finish

このスクリプトにスクリプト自身を与えると以下のようになります。

text2ps-to-text2ps.ps

レイアウト

Ruby/Pango を使うには以下のように pango を require します。

 require "pango"

もし、システムに rcairo がインストールされていれば自動で rcairo も読み込みます。そのため、cairo を require する必要はありません。 *14

Pango ではレイアウト (Pango::Layout) がテキストの描画のしかた を計算します。例えば、描画開始位置は中央揃えにしているか、右 揃えにしているかなどで変わります。1 行に入る文字数は、行の幅、 あるいはフォントやフォントの大きさなどで変わります。レイアウ トは与えられた整列方法やフォントから、描画開始位置や 1 行の文字 数などのような具体的な描画のしかたを計算します。

pango-layout-align.png pango-layout-characters-per-line.png

Pango を cairo と一緒に使う場合は、コンテキストからレイアウトを 作ります。

 layout = context.create_pango_layout

単にテキストをデフォルトの設定でそのまま描画するだけなら、こ れで十分です。

 layout.text = text
 context.show_pango_layout(layout)

用紙サイズで折り返したいなら幅を指定します。注意しなければい けないことは、幅は Pango の世界の単位で指定するということです。 ピクセルを Pango の世界の単位に変換するには Pango::SCALE を掛けます。

 layout.width = width * Pango::SCALE

折り返しの区切りは単語単位か文字単位になります。日本語ではど ちらも同じように扱われますが、英語では以下のように異なります。

単語単位:

 | layout.width  |
 |<------------->|
 long long long
 long long long
 long

文字単位:

 | layout.width  |
 |<------------->|
 long long long lo
 ng long long long

Pango での指定のしかたはそれぞれ以下のようになります。

 layout.wrap = Pango::WRAP_WORD # 単語単位
 layout.wrap = Pango::WRAP_CHAR # 文字単位

他にもレイアウトにはたくさんのオプションが設定できます。例え ば、以下のように入力テキストにマークアップをして背景色を設定 できたりします。

 markup = "t<span background='green'>ex</span>t"
 attr_list, text = Pango.parse_markup(markup)
 layout = context.create_pango_layout
 layout.text = text
 layout.attributes = attr_list
 context.show_pango_layout(layout)

実行すると図のように「ex」の部分の背景色だけが緑色になります。

pango-markup.png

image-viewer.rb

最後に、今まで紹介したライブラリだけではなく、GTK+ とも連携す る例を示しておきます。PDF/SVG/PNG/JPEG などを表示するアプリケー ションです。ウィンドウサイズを変更すると、それにあわせて表示し ている内容も拡大・縮小します。

Ruby/GTK2 に関してはここでは詳しく説明しませんが、GTK+ で描画す るためには以下のようにすればよいということだけわかれば読める と思います。

 drawing_area.signal_connect("expose-event") do |widget, event|
   context = widget.window.create_cairo_context
   # context を使って描画
 end

スクリプト: image-viewer.rb

 #!/usr/bin/env ruby

 require 'gtk2'
 require 'rsvg2'
 require 'poppler'

 if ARGV.size != 1
   puts "Usage: #{$0} IMAGE"
   exit 1
 end

 input = ARGV.first

 case input
 when /\.pdf/i
   document = Poppler::Document.new(input)
   size = Proc.new do
     document[0].size
   end
   draw = Proc.new do |context|
     context.render_poppler_page(document[0])
   end
 when /\.svg/i
   handle = RSVG::Handle.new_from_file(input)
   size = Proc.new do
     dim = handle.dimensions
     [dim.width, dim.height]
   end
   draw = Proc.new do |context|
     context.render_rsvg_handle(handle)
   end
 else
   pixbuf = Gdk::Pixbuf.new(input)
   size = Proc.new do
     [pixbuf.width, pixbuf.height]
   end
   draw = Proc.new do |context|
     context.set_source_pixbuf(pixbuf)
     context.paint
   end
 end

 window = Gtk::Window.new
 window.set_default_size(*size.call)
 window.signal_connect("destroy") do
   Gtk.main_quit
   false
 end

 drawing_area = Gtk::DrawingArea.new
 drawing_area.signal_connect("expose-event") do |widget, event|
   context = widget.window.create_cairo_context
   x, y, w, h = widget.allocation.to_a
   context.save do
     image_width, image_height = size.call
     context.scale(w / image_width.to_f, h / image_height.to_f)
     draw.call(context)
   end
   true
 end

 window.add(drawing_area)
 window.show_all

 Gtk.main

他のライブラリとの連携のまとめ

cairo は PNG や PDF など複数の出力をサポートするだけではなく、 Windows/Linux/Mac OS X など多くの環境で動作し、その環境上の GUI へ描画することもできます。このため、cairo を標準的な描画 API としてとらえ、採用するプロジェクトやライブラリが増えてき ています。本稿で紹介した GTK+ や librsvg、poppler などもそのひと つです。

これからも cairo に対応したライブラリは増えていくことでしょう。 みなさんのプログラムでも描画 API として cairo を採用してみてはい かがでしょうか。

まとめ

本稿では、まず rcairo の基本的な使いかたを紹介しました。次に、 cairo をサポートしている周辺ライブラリなどと連携する例を紹介し ました。これらのライブラリを使用して、SVG、画像ファイル、PDF 文書、素のテキストなどを cairo を使って描画しました。本稿では グラデーションなど rcairo 単体で行えるより高度な機能については 紹介していません。

また、以下の cairo の利点を紹介しました。

  • 同じ描画処理 (プログラム) で PDF/PNG/SVG/ディスプレイなど複数の出力先をサポートできる。
  • SVG/PDF/テキストなどを入力として扱える。

一方、欠点は以下の 2 点です。

  • 拡張ライブラリなので導入が大変な場合がある。
  • 「ぼかし」などの画像効果のサポートがない。

パッケージがある環境では簡単に導入できる場合もありますが、レ ンタルサーバなどを利用している場合は難しいかもしれません。

画像効果のサポートはあまり期待できないでしょう。cairo のメー リングリストで、「ぼかし」を使うには?、という質問が出ても誰 も返信しないのが現状です。

他のライブラリと連携して解決することもできます。例えば、画像 を PNG で出力し、その結果を RMagick で加工する、などです。

cairo は Firefox や GTK+ などの有名プロジェクトのバックエンドとし て採用されている将来性のあるプロジェクトです。次のリリースで は、Mac OS X の Quartz も正式なサポートに加わる予定です。EPS の出 力もサポートも予定されています。サーフェスを切り替えるだけで、 同じプログラムがますますいろいろな環境で動き、いろいろな出力 をサポートできるようになるでしょう。

次のステップ

最後に、本稿で触れていない cairo の使いかたを知るために参考にな る URL を紹介します。

http://www.tortall.net/mu/wiki/CairoTutorial

英語でしかも Python なのですが、cairo の世界についてわかりやすく書かれています。

http://www.cairographics.org/manual/

英語で書かれている cairo のドキュメントです。本稿で触れていない多くのことが書かれています。

著者について

須藤功平。すどうではなく、すとう。東京に出ていった親戚はすどうになった。

得意分野は一般受けしないライブラリ・アプリケーション開発。最近がんばっているのは rcairo や ActiveLdap。どちらも他の人が始めた、当時は死にかけていたプロジェクト。だいぶ息を吹き返してきたと思っているが、知っている人はほとんどいないはず。

あ、そうか。あんまりドキュメントを書かないからか。


*1 UNIX 環境でもっともよく使われている GUI ツールキットの 1 つ

*2 便利のために上述の draw_line のよ うなメソッドを作ればよいのかもしれませんが、そのようなメソッ ドは提供されていません。提供した方がよいでしょうか。

*3 + の方向 が右で、-の方向が左です。

*4 Web 2.0 Graphics 参照

*5 Ruby-GNOME2 プロジェクトが公開してい るバイナリ群に rcairo のバイナリも含まれている。

*6 MacPorts 用の Portfile: http://www.cozmixng.org/repos/dports/trunk/ruby/rb-rcairo/Portfile

*7 PNG 画像を作成するときは 4.を省略できます。GTK+ とともに使う場合は 1.と 4.を省略できます。

*8 キャンバスを載せる台のことをそういうみたい です。

*9 リファレンスマニュアル

*10 cairo 1.4.x からは finish は省略可能になりました。

*11 RSVG::DimensionData#to_ary を定義してもよいかもしれませ ん。よし、定義しよう。した。

*12 Ruby/RSVG が rcairo をサポートしていなければ Cairo::Context#render_rsvg_handle は定義されません。Ruby/RSVG が rcairo をサポートしているかは RSVG.cairo_available?で判断で きます。

*13 Ruby/Poppler が rcairo をサポートしているかは Poppler.cairo_available? で判断できます。

*14 Ruby/Pango が rcairo をサポートしているかは Pango.cairo_available? で判断できます。