「りも」トップページへ
TIPS index へ

ピクセルルンルンくるるんるん

Mar.18.2000
[rotation pigture]

GdkPixbuf の画像を回転変換する。
画像データを任意角度に回転、拡大縮小を含めて新規に生成する関数の作成。 結果画像加工フィルターとして扱える。


・ 画像を回転させたい

GdkPixbuf(Ver0.6)には画像を拡大縮小させつつコピーする関数がありますが、 回転加工する関数はありません。ひょっとしたら将来的に用意されるのかも しれませんが今現在はないということで作成してしまいましょう。

・ 回転の基礎

座標の回転演算には三角関数を用います。
回転前の座標を(x,y)、回転角をθ、回転後の座標を(rx,ry)とした場合、 原点(0,0)を中心に回転する式は

  rx = cosθ * x + sinθ * y
  ry = -sinθ * x + cosθ * y
で示されます。
拡大縮小をさせたい場合は単純に(rx,ry)に拡大率をかけます。

・ アルゴリズムその1 (rot1)

[fig01]

source picture を回転して destination picture に表示するのだから、 source picture の pixel をひとつひとつ回転させて、destination picture に 書き込みます。
rot1 source

[rot1 snapshot]


実際にやってみると綺麗な画像にならずにところどころ穴が空きます。 これは計算誤差が混ざっているからなのですが、だからといって精度が 上がれば無くなるというものでもなかったりします。良く考えてみれば わかることですが、このアルゴリズムでは source picture の持つ pixel 分 しか書き込まれないので拡大したとき正常に表示されません。 隙間空きまくりになるわけです。

・ アルゴリズムその2 (rot2)

[fig02]

rot1 の問題点は source picture を基準にして考えていたことにあります。 結果としては destination picture に表示するのだから、 destination picture を基準に計算をする必要があります。
そこで先程とはロジックを逆に考えます。
destination picture の各 pixel が「回転前の」source pixture のどの pixel にあたるかを計算します。回転計算としては逆演算といった感じに なります。こうすることで destination picture 側に抜けが生じることが なくなります。
rot2 source

[rot2 snapshot]


今度は抜けもなく期待した通りの結果となりました :-)

・ アルゴリズムその3 (rot3)

rot2 では 1 pixel 毎に三角関数を用いて回転演算をしているので、 処理負荷はかなりなものになっています。もうちょっと軽くはならない ものでしょうか。

[fig03]

destination picture 側で pixel を次々に処理していくことを考えます。 横方向にスキャンするように処理していくこと、およびそのラインを スキャンラインとします。このスキャンラインが source picture 側で どういった形になっているかというと、単純1軸回転であった場合、 上記挿絵のように単純に傾いた直線となっています。もちろんその傾きは 回転角度によります。
ということは source picture のスキャンは単純な一次式に置き換えらる わけです。回転演算をするのは最初の一回、destination picture の左上 の座標、およびスキャンラインの傾きを計算するときのみで良いことに なります。後は単純一次式、しかも加算のみで表現できるので単純な 処理系になり高速化が計れます。
rot3 source

傾きの表現とその計算は 32bit unsigned int で計算します。下16bitを 小数点扱いの下駄とした固定小数点演算とします。結果、pixel座標は 16bit の表現力である 32768 未満となりますが、現実問題としてそんなに 大きな pixbuf を持つことはできないであろうから今は良しとしておきます。
回転転送の中心となる for ループの部分は、単純ループと整数加算のみで 構成されていますので実にアセンブラ向けかと :-)

[rot3 snapshot]

静止画像ではわからないでしょうけども、rot2 と同じ画像処理をはるかに 軽い処理で実現できています。サンプルプログラムでは処理負荷のほとんどが X Window への描画で占められていて回転処理にはそんなにかかっていません。

・ アルゴリズムその4 (rot4)

上の画像群は JPEG 圧縮画像なのでわかりづらいかもしれませんが回転すると ラインや絵のエッジがぼろぼろになります。単純に回転しただけではある意味 しょうがないところなのですが、やはり気になるものですし、 なにより画像処理に使うには汚すぎます。
そこで回転処理に加えフィルタリング処理を加えより綺麗な画像をめざします。 先の source picture の変換時その座標計算は固定小数点により処理していました。 その際小数点部分を参照し、その値を元に隣の pixel と色をブレンドします。 この処理だけで等倍および拡大時のピクセルは滑らかにフィルタリングされ より綺麗な画像となります。

[rot4 snapshot]
わかりにくいけど綺麗になっています :-)

縮小時は拡大時と違ってオーバーサンプリングという手法を用います。 縮小表示時は source picture の pixel をとびとびに歯抜けの状態で拾う ことになります。その情報欠落が縮小時の画像荒れをもたらすので、 飛ばさないように全て拾い上げ混ぜて destination picture の pixel に 反映させます。
ソースの smooth_pixel という関数でその両方がインプリメントされて います。

rot4 source

拡大時のソフトエリアシングの効果は以下のような感じです。左が rot3 の画像、 右がソフトエリアシングされた rot4 の画像です。

[rot4 snapshot 2] [rot4 snapshot 3]

まあ、しょうがないといえばしょうがないのですが、ピクセルフィルタリングには 相当な負荷がかかり処理は重たいです。この処理はリアルタイムでなく 画像処理に向けたインプリメントですのでケースバイケースで rot3 と使い 分けることになると思います。

・ あとの展開

今回は画像処理を目指したので精度も優先し、そこそこ画像にもこだわった アルゴリズムやフィルタリングになっています。ところがゲームでリアルタイムに 処理しようという場合、もっと「いいかげん」でもよかったりするんですよね。 例えば回転角が64段階でも良かったり。32x32pixel でも十分だったり。
そういった場合、1 pixel ずつ真面目に計算しなくても、あらかじめ計算結果を テーブルで持っていてテーブル参照型コピーで済ませることによりいっそうの 高速化が期待できます。もっとも汎用性はなくなりますから、状況に応じて そういった処理を使い分けることになります。

・ あとがき

普段気軽にアルゴリズムとか言いますけど、実際にその違いによって全く結果 が変わるなんていう場面はなかなか目にする機会は無いのではないでしょうか。 結果は同じでも途中の手順の省略の仕方によって処理時間が大きく違って しまうものです。状況によっては省略(手を抜く)事によっていくらでも処理を 高速化できたりします。そういったあたりがプログラムの醍醐味ですよね。
もっとも表だって目立たない部分ではあるのでプログラマーの満足は自己満足と なることが多いわけですが :-)

・ サンプルプログラム

というわけで、今回のプログラムです。
サンプルソース(GdkPixbuf-0.6.0用)
(Size = 82,441 Byte)
サンプルソース(GdkPixbuf-0.7.0用)
(Size = 82,479 Byte)

rero2@yuumu.rim.or.jp