多態的な画像クラスを作る2010/05/03作ってみたらうまくいったのでメモ。規模が大きくなっちゃったので,ソースをすべて載せることができません。デバッグもまだ十分じゃないので,ここでは,やろうと思えばできるよ,ということで。ヒントだけ書いておきます。多態的な画像クラスというのは,画像の持ち方や読み込み方/書き込み方が変わる場合でも,構成するクラスの内容を変更すること無しに機能を拡張したり,縮減したりする仕組みのことです。画像クラスなんつのは,画像処理に欠かせない一般的なクラスである一方,開発者が10人いたら10種類のクラスができちゃうくらい車輪の再発明が盛んなクラスだったりします。んなもんで,リファレンスはたくさん公開されている。多態的なクラスはあまり見ることがありません。そもそも特殊なので,汎用的とはいえません。ことで,作るにはちと工夫しないといけません。画像クラスには,例えば,白黒二値でピクセルの行列を保持するモノもあれば,8ビットのグレースケールで保持するものもあります。フルカラーの場合もあるだろうし,アルファチャンネルを持っていたいこともある。フーリエ変換のような複雑な数値計算処理を行う場合は,各ピクセルを浮動小数点型の複素数で持っていたいところ(ピクセルごとに実数部と虚数部を持つ必要もないんだけれど)。画像クラスを中心とするクラス群で必要不可欠な機能の中に,ファイル入出力の機能があります。これまた種類がたくさんある。おなじみの画像フォーマット,たくさんありますよね。した複数種類のデータ形式に対して,複数種類の読み書き方法を適用するには,どうすればいいでしょう。あたしが見た中で,最も最悪の実装は,次のような画像クラスです。buf_;};すべての読み書きクラスを,すべての画像クラスに実装するわけです。ここでは,8ビットのグレースケール画像を読み込むクラスだけを書いたけれども,同じようなクラスがたくさんあるんです。読み込みメソッドは,そのクラスごとに書かれている。こんなことしてると,例えば,サポートする画像ファイルが1つ増えるだけで,すべての画像クラスに読み書きのメソッドを追加することになるわけで,大工事になってしまいます。反対に,画像クラスの種類が増えた場合も大工事になる。ことで,考えたわけですけれど,これ,結局やらなきゃいけないのは次の点なんだと思います。画像の読み書きメソッドを画像クラスそのものから分離する。デフォルトのピクセル型を定義する。画像クラス一般を(内部バッファの型も含めて)抽象化する。最初の点として,画像の読み書きメソッドは,クラスとして分離します。ようなクラスを作るというわけです。読み込みクラスは,読み込み時にファイルの内容をいったん「デフォルトのピクセル型」に変換します。変換されたデフォルト型の値を画像クラスに読ませて,内部バッファに合うようにセットするわけです。この際,「デフォルトのピクセル型」を通じたインターフェイスは,画像クラスがみんな持っているメソッド(純粋仮想関数)としておく。読み込み時,ファイルのピクセル値をデフォルトのピクセル型に翻訳するのは,読み書きクラスの責任です。受け取ったデフォルトのピクセル型の値を,内部バッファに合うように収める責任は,画像クラスにあります。デフォルトのピクセル型は,例えばこんな具合に表せます。画像にしました(実質は24bit)。使った読み込みの場合)。クラスで正規化されることになります。画像フォーマットの違いはありません。この方法は,読み書きに時間がかかるので,メモリアクセスのオーバーヘッドよりも大きなオーバーヘッド(ファイルIO)のあるファイルの入出力や,画像フォーマットの交換くらいにしか使えないんじゃないかと思います。実際に画像処理をする場合は,各画像クラスに固有の速いメソッドを定義すべきです。クラスで正規化されることになります。画像クラスとしては,どんなファイルが来ても同じ読み方で読めるので,フォーマットが増えるたびにメソッドを増やすような愚かなことはしなくて済みます(読み書き用のクラスを1つづつ作るだけでいい)。これで読み書き方法が複数ある件については解決です。それでは,画像の種類(保持方法)が異なる件についてはどうでしょう。これには,継承(仮想関数)とテンプレートを使います。大体こんな感じの継承関係を作ります。型のポインタ(や参照)を通じて行うことにします。メソッドは仮想関数なので,基底クラスのポインタで受けてそのメソッドを呼んだら,生成時のクラス(派生クラス)のメソッドを呼ぶことができます。オブジェクトが来たのか,分かりません。分かっている必要がないんですね。いう関数に作ったそれを渡すだけ,という単純なものになります。した,継承と仮想関数の多態的な用法は,使いこなすとかなり便利です。初心者さんには混乱の元になるかもしれませんけど。最後に,内部バッファの型を吸収(抽象化)する方法についても触れておきます。これは継承関係を見れば明らかなんですけれど,テンプレートを使っています。本当だったら,このクラスはいらなくて,派生クラス(Image8u)だけでもいいんですけれど,型の管理は外に出しておいた方が,後々バッファの管理で重複したコードを書かなくて済むだろうという判断があります。ここは,ちと思いつきなところがあるので,今考えるとなくてもよかったかな,とか思ったり。ま,せっかくだからこのままで。画像の読み書きクラスについて基本的な構造ができたので,あとは対応できるフォーマットの数を増やしていくだけです。増産体制が整いましたです。