今日はパディングコードの改良版だよ。画像データを扱っていると、APIやOSによってはとかく受け付ける画像サイズに何らかの制限があったりすることがある。例えばOpenGLのデフォルトバージョンではテクスチャ画像のサイズは2のn乗数になってないとだめとか、WindowsのDIBにおける4バイト境界問題などがそうだ。そのような場合、画像そのものは変えないのだけれども、その制約に合わせるため本来の画像に加え余白部分を付け足したり取り除いたりして画像サイズを調整するという「パディング処理」が必要となる。
各ケースにおけるパディング処理の内容自体はこのように単純な内容なのだが、慣れていないとそれを記述するのはやたらと面倒だ。毎ケースわかりきったパディング処理をいちいち書くのははっきり言って時間の無駄だし、つまらないバグにはまったら半日仕事にもなりかねない。おまけに読み返すのも大変なのでもういやだ。そこでパディング処理を一手に引き受けてくれる、便利クラスを用意したぞ。
以前、4バイト境界パディング専門のパディング処理クラスを紹介したが今回のやつはそれに加えてデフォルトOpenGLテクスチャ対応や、パディングキャンセル機能もつき、しかもコアの処理は共通の基底クラスで行うようにしたというものだ。ソースは以下のようになった。
class PadderBase { public: PadderBase( const int width, const int height, const int depth3or4 ) :_width(width), _height(height), _depth3or4(depth3or4) { _lnsz = depth3or4 * width; _src_padding = (4-_lnsz%4)%4; } virtual ~PadderBase(){} // virtual // non-virtual template< class CopyToDstFunc > void DoPadding( void* src, bool is_src_padded, CopyToDstFunc& cpy ) { const size_t padded_lnsz = this->PaddedLineSize(); unsigned char* ln = GenZeroBuffer( padded_lnsz ); // 本来の高さ分 unsigned char* p = (unsigned char*)src; for( int i=0; i<_height; ++i ){ cpy( p, _lnsz ); p += _lnsz; cpy( ln, _num_padding ); if( is_src_padded ) p += _src_padding;// ★ }// i // 高さ方向パディング分 for( int i=0; i<_padded_height - _height; ++i ) cpy( ln, padded_lnsz ); delete [] ln; } private: inline unsigned char* GenZeroBuffer( const size_t& sz ){ unsigned char* buf = new unsigned char[ sz ]; std::fill( buf, buf+sz, 0 ); return buf; } POC_PROP( size_t, Width, _width ); POC_PROP( size_t, Height, _height ); POC_PROP( size_t, Depth3or4, _depth3or4 ); POC_PROP( size_t, LineSize, _lnsz ); POC_PROP( size_t, SrcPadding, _src_padding ); POC_PROP( size_t, PaddedWidth, _padded_width ); POC_PROP( size_t, PaddedHeight, _padded_height ); POC_PROP( size_t, NumPadding, _num_padding ); public: size_t PaddedLineSize( void ) const { return _lnsz + _num_padding; } size_t PaddedImageSize( void ) const { return (_lnsz + _num_padding) * _padded_height; } }; class FourByteBoundaryPadding: public PadderBase { public: // 画像幅方向のバイト数を、4バイト境界にします。 // 画像高さ方向は、何もしません。 FourByteBoundaryPadding( const int width, const int height, const int depth3or4 ) : PadderBase( width, height, depth3or4 ) { _padded_width = width;// _padded_height = height;// _num_padding = _src_padding;// }; }; class NextPow2Padding: public PadderBase { public: NextPow2Padding( const int width, const int height, const int depth3or4 ) : PadderBase( width, height, depth3or4 ) { _padded_width = NextPow2( _width );// _padded_height = NextPow2( _height );// _num_padding = (_padded_width-_width)*_depth3or4;// } ~NextPow2Padding(){} int NextPow2( const unsigned int x ){ // x より大きい、最小の2のべき乗数を計算します。 int cnt=1; for( int val=x; val>0; val/=2, cnt*=2 ); return 2*x==cnt ? x: cnt; } }; class CancelPadding: public PadderBase { // もと画像のパディングをキャンセルします。 public: CancelPadding( const int width, const int height, const int depth3or4 ) : PadderBase( width, height, depth3or4 ) { _padded_width = width; _padded_height = height; _num_padding = 0; }; }; struct Func_fwrite{ Func_fwrite( FILE* fp ):_fp(fp){} void operator()( void* src, size_t sz ){ fwrite( src, sizeof( char ), sz, _fp ); } FILE* _fp; }; struct Func_memcpy { Func_memcpy( char* dst ):_dst(dst){ _cnt=0;} void operator()( unsigned char* src, size_t sz ){ memcpy( _dst+_cnt, src, sizeof(char)*sz ); _cnt+=sz; } char* _dst; size_t _cnt; }; struct Func_reverse_copy { Func_reverse_copy( char* dst ):_dst(dst){ _cnt=0;} void operator()( unsigned char* src, size_t sz ){ std::reverse_copy( src, src+sizeof(char)*sz, _dst+_cnt ); _cnt+=sz; } char* _dst; size_t _cnt; };
なお、POC_PROPについては、こちらを参照してくれよな。これはsetter/getterを自動生成するために必要なものだ。一見ダラっと長いソースに見えてしまうかもしれないが、実際に使うのは、
- FourByteBoundaryPadding
- NextPow2Padding
- CancelPadding
と、データ転送方法を指定するおまけの便利ファンクタ群、
- Func_memcpy
- Func_fwrite
- Func_reverse_copy
を組み合わせて使うだけだ。とっても簡単だよ。では実際に使い方をシチュエーション別に見ていくとしよう。
- ケースその1()
「DIBより画像データ取得したが4バイトパディングがかかっていて、素直なピクセル並びの画像データになってないので困ってる。どうしよう。。」
- ケースその2()
「DIBより取得した画像データをそのままOpenGLのテクスチャとして使いたいが、拡張なしのOpenGLで行きたいので2の階乗サイズにパディングしなきゃ、たすけて!」
- ケースその3()
「素直なピクセル並びの画像データがあるんだけど、DIB作成するのに4バイト境界パディングしないといけない。あーもうめんどくさい!」
こんなとき、今回のPadderクラスはうってつけです。ではケース別に使用例を見ていきましょう。まずはケース1の場合。これはすでにかかっているパディング部分を取り除きたいという話だ。そこでCancelPaddingクラスのお出ましだ。使い方は以下のようになるよ。
::CancelPadding padder( bitmap.bmWidth, bitmap.bmHeight, 3 ); { char* buf2 = new char[ padder.PaddedImageSize() ]; padder.DoPadding( bitmap.bmBits, !0, Func_reverse_copy(buf2) );// padding glTexImage2D( GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGB, padder.PaddedWidth(), padder.PaddedHeight(), 0, GL_RGB, GL_UNSIGNED_BYTE, buf2 ); delete [] buf2; }
そう。うまく使えたね。まずはpaddingクラスを作成し、現在の画像サイズをとピクセルデプスをバイト単位でインプットする。するとpaddingクラスはパディング後のイメージに必要なメモリサイズを教えてくれるのでそれに見合ったメモリを確保して、そこに対してDoPadding処理をかけているんだ。ここではもと画像には4バイト境界パディングかかっているというフラグをONにしてDoPaddingしているね。彼の場合はGL_TEXTURE_RECTANGLE_ARB拡張を使っているので、2の階乗サイズにする必要はなかったんだけれども、もと画像に4バイトパディングがかかってしまっていたために困っていたというケースだね。
お次のケース2を見てみようじゃないか。この場合は以下のような使い方になる。
::NextPow2Padding padder( bitmap.bmWidth, bitmap.bmHeight, 3 ); { char* buf2 = new char[ padder.PaddedImageSize() ]; padder.DoPadding( bitmap.bmBits, !0, Func_reverse_copy(buf2) );// padding glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, padder.PaddedWidth(), padder.PaddedHeight(), 0, GL_RGB, GL_UNSIGNED_BYTE, buf2 ); delete [] buf2; }
どうだうまくできたぞ。この場合使うのはそう、NextPow2Paddingクラスだ。しかも元画像にはパディングがかかってますよという指定をしているね。
3つめのケースはどうだろう。おっとこの場合はこちらのソースがいい使用事例になってると思うから参考にしてくれ。では今日はこの辺で。Have a nice padding life!