2011/02/06

魔法使いの弟子(その2-1)

とりあえずブログを作ってみた。まだ方向性は決まっていない。

ぼくはいつも前後関係が逆で、物事をやり始めた後で目的を見出す人なので仕方ない。パソコンや iPhone も、買った後で何に使うかを決めたくらいだし。

たぶんこのブログは身辺雑記とかお散歩カメラの写真とかをぺたぺた貼りまくる事になる気がする。

とはいえ、ただ写真を貼るだけではあまりおもしろくないので、遊び心を込めて、セロテープでぺたっと留めてみる事にした。
うん、おもしろい(どこが)。でも毎回加工するのは手間がかかる。そうだ、SD カード内の写真を一括してセロテープぺったん画像に変換するようなプログラムを作ろう。




今回は Java に加えて Processing も用いる。用いなくても何とかなるとは思うが、生産性が全然違うのだ。
 
まず、セロテープの画像を Photoshop 等で適当に作る。これは半透明が望ましい。


これを、PImage オブジェクトとして保存しておく。

一方、写真の方も、ディジタルカメラで撮影した状態のままではサイズが大きすぎるため、適度にリサイズする必要がある。

ここでは、写真の長辺が 320 ピクセルとなるように縮小した。もう少し大きくても良さそうだ。

Processing で、写真とセロテープをうまく合成すると、こんな画像が出来上がる。ここまでは割と簡単である。


これを標準の save メソッドでそのままファイルに書き出す事も可能だが、残念ながら Processing 単体では透過画像が作れない(非透過画像でよいならばここまでで充分である)。

というわけで、これを何とかしよう。



まず、昨日のプログラムの流用が考えられる。しかし、あれは『半透明』のピクセルの存在を一切考慮していない。すなわち、完全に透けているか、完全に不透明かという二者択一だった。

今回は『セロテープ』を半透明にしたい。それだけでなく、アンチエイリシングされたエッジも背景と融合するようにアルファチャネルをグラデーションさせたい。

そこで、背景を白黒2種類用意し、両者における各画素の輝度値を比較する事によって当該画素の不透明度および RGB 値を推定する事にした。

下図は、白い背景の画像と黒い背景の画像の輝度の差分絶対値を視覚化した例である。差分絶対値が大きくなればなるほど背景を透過すると言える事から、どの程度背景を抜くべきかの定量化は容易である。
黒背景画像と白背景画像の差分絶対値を視覚化した例(下)。
黒くなればなるほど前景色の非透過率は高くなる。

背景色を cbg 、前景色を cfg 、不透明度を α とすると、それらを合成した画素値 c'
として得られる。

黒背景における画像の輝度値を c'black 、白背景色の輝度値を c'white とし、両者の差分絶対値から不透明度を以下のように算出する。
この数式は、前景色の不透明度が高いほど前景色の変化が少ないという直感に合致する。尚、輝度値は 0.299r + 0.587g + 0.114b と計算する。

次に、黒色の画素値が 0 である事を利用して、 c'black および α から前景色を次のように得る(あ、ごめん。こっちの c'black は輝度値ではなく RGB 値だった)。
計算誤差によって RGB いずれかが 255 を超えた場合は適当に丸めてしまう事にした。しょせん自分のブログで使うだけなので、そこまでクオリティは高くなくてもよかろう。

以上から、透過画像の ARGB 値を得る事が(一応)できた事になる。

後は昨日やった通り、Java 側でアルファチャネル付き BufferedImage オブジェクトを新規に作り、各ピクセルに ARGB 値を放りこんで行けば完成である。めでたし。



今回のつまづきポイントは、非同期で呼ばれる draw メソッドを何とかして制御するのがやたら厄介だった事であった。1枚の画像を変換するだけならば対処しやすいが、ディレクトリ内のファイル群を一括処理しようとした瞬間に共有資源を共食いし始めて死んだ。

今さらプログラムを大工事する気力など残っていなかったため、結局 draw を万能メソッドにする事で、共有資源を介するデータのやりとり自体を排除する事にした。かなり気持ち悪い方法だが。

というわけで、ソースの内部はぐちゃぐちゃだが、手持ちのデジタルハリネズミに入っていた SD カードの中身を一括変換する事に成功した。せっかくだし、いくつか上げておこう。



いくつかと言いながらたった2つしかなかった。これからがんばる。