Hatena::ブログ(Diary)

npalの日記

2012-11-07

OpenGLでの描画内容の画像化と保存

OpenGLで描画した情報をピクセル単位で取得し、画像化および保存したかったので、調べてみました。

OpenGLのglReadBufferとglReadPixels関数を使うようです。
一つのまとまった小さなプログラムコードは、探した限りでは無かったので書いてみました。
OpenCVを画像の保存に使っていますが、取得したピクセル情報は変数dataBufferに入っているので、
あとは自分の好きなように格納できると思います。

void saveImage( const unsigned int imageWidth, const unsigned int imageHeight )
{
  const unsigned int channnelNum = 3; // RGBなら3, RGBAなら4
  void* dataBuffer = NULL;
  dataBuffer = ( GLubyte* )malloc( imageWidth * imageheight * channelNum );

  // 読み取るOpneGLのバッファを指定 GL_FRONT:フロントバッファ GL_BACK:バックバッファ
  glReadBuffer( GL_BACK );

  // OpenGLで画面に描画されている内容をバッファに格納
  glReadPixels(
	0,                 //読み取る領域の左下隅のx座標
	0,                 //読み取る領域の左下隅のy座標 //0 or getCurrentWidth() - 1
	imageWidth,             //読み取る領域の幅
	imageHeight,            //読み取る領域の高さ
	GL_BGR, //it means GL_BGR,           //取得したい色情報の形式
	GL_UNSIGNED_BYTE,  //読み取ったデータを保存する配列の型
	dataBuffer      //ビットマップのピクセルデータ(実際にはバイト配列)へのポインタ
	);

  GLubyte* p = static_cast<GLubyte*>( dataBuffer );
  std::string fname = "outputImage.jpg";
  IplImage* outImage = cvCreateImage( cvSize( imageWidth, imageHeight ), IPL_DEPTH_8U, 3 );

  for ( unsigned int j = 0; j < imageHeight; ++ j )
  {
    for ( unsigned int i = 0; i < imageWidth; ++i )
    {
      outImage->imageData[ ( imageHeight - j - 1 ) * outImage->widthStep + i * 3 + 0 ] = *p;
      outImage->imageData[ ( imageHeight - j - 1 ) * outImage->widthStep + i * 3 + 1 ] = *( p + 1 );
      outImage->imageData[ ( imageHeight - j - 1 ) * outImage->widthStep + i * 3 + 2 ] = *( p + 2 );
      p += 3;
    }
  }

  cvSaveImage( fname.c_str(), outImage );
}

廣瀬

2012-08-27

printfデバッグを極める

研究の実装に役立ちそうな、C++プログラミングのちょっとしたテクニックご紹介します。

一番シンプルなデバッグの方法といえばprintf関数でコンソールにメッセージを出力する方法だと思います。

ところが、あちこちにデバッグ用のprintfを書いておくと、だんだん出力される情報が多くなってきて困ったりします。そのうち過去のコードを探って不要になったprintfをコメントアウトして回る作業をする羽目になるでしょう。

せっかく書いたprintf文をいちいち消して回るのも面倒です。もしかしたらまた必要になるかもしれません。
消したり書いたりしないで済むために、出力する情報を選別できたら便利だと思いませんか?

今回はコンソールに出力するデバッグメッセージを"冗長性"と"チャンネル"の2種類の分類で管理する方法を説明します。といってもそれほど複雑なことをする必要はなく、次のようなコードを用意します。

//Channel Mask
#define CHANNEL_ANY          0xFFFFFFFF
#define CHANNEL_FILE_LOAD       0x00000001
#define CHANNEL_INVALID_VALUE     0x00000002

int g_verbosity = 3;
DBG_CHANNEL g_channel = CHANNEL_ANY;

void printDebugMsg( int verbosity, DBG_CHANNEL channel, const char* format, ... )
{
  if( g_verbosity >= verbosity ){
    if( (g_channel & channel) != 0 ){
      const int MAX_CHARS = 1023;

      va_list argList;
      va_start( argList, format );
      static char s_buffer[MAX_CHARS + 1];
      vsnprintf( s_buffer, MAX_CHARS, format, argList );
      va_end( argList );

      printf( s_buffer );
    }
  }
}

例えばこんな風につかいます。

//...何らかの処理...
printDebugMsg( 3, CHANNEL_FILE_LOAD, "Load File %s\n", fileName );

一番目と二番目の引数に管理用の情報を指定する以外はおなじみのprintfと同じように使えます。

一番目の引数に入れた値は 冗長性(verbosity)を表す整数です。数字が小さいほど重要な情報ということになります。
これを指定しておくことで、「冗長性3以下のメッセージのみ表示する」といったように、出力するメッセージを選別できます。

二番目の引数に指定したのはチャンネルです。コードの一番上でdefineしてますが、要はビットマスクです。
チャンネルの種類を追加する時は
...
#define CHANNEL_A  0x00000004
#define CHANNEL_B  0x00000008
...
のように2のべき数で指定しましょう。
これを指定しておくことで、「ファイル入出力に関するメッセージ(CHANNEL_FILE_LOAD)だけ表示する」といったフィルタリングができます。

この関数をprintfの代わりに使っておくことで、後から表示するメッセージを管理できます。
上のコードでは、グローバル変数のg_verbosityとg_channelを書き換えれば、
「任意の冗長性以下の任意のチャンネルのメッセージ」だけ表示させることができます。
説明のためにグローバルに置いてありますが、ちゃんとクラス化したものを記事の最後に掲載するので、もしよければ好きに使ってください。あるいはコードレビューも歓迎です。

ところで、printDebugMsg関数引数のところに見慣れない書き方があると思います。

void printDebugMsg( int verbosity, DBG_CHANNEL channel, const char* format, <span class="deco" style="color:#FF0000;">...</span> )

最後の"..."ってなんでしょう?

これは、知ってる人は知ってると思いますが、任意数の引数を表します。
任意数の引数をどうやって扱うかというと
まず、va_listという型の変数を用意します。(typedefされてる void* です)
va_startマクロ引数の始まり、つまり可変長引数の直前の引数を指定します。
vsnprintf関数を使って可変長引数を文字配列に書き込むことができます。
最後にva_endマクロで締めくくっておきましょう。お約束ってやつです。
もちろん文字配列に書き込む以外に、可変長引数を順番に取り出して使う方法もあります。詳しくはグーグル先生に。

以下にデバッグメッセージ用クラスを掲載します。

ヘッダファイル

#ifndef INCLUDED_DEBUG_PRINTER_H
#define INCLUDED_DEBUG_PRINTER_H

//Channel Mask
#define CHANNEL_ANY						0xFFFFFFFF
#define CHANNEL_FILE_LOAD				0x00000001
#define CHANNEL_INVALID_VALUE			0x00000002

class DebugPrinter
{
typedef unsigned int DBG_CHANNEL;

private:
	static int d_verbosity;
	static DBG_CHANNEL d_channel;

	DebugPrinter(){}
	DebugPrinter( const DebugPrinter& rhs );
	DebugPrinter& operator=( const DebugPrinter& rhs );

public:

	static void printDebugMsg( int verbosity, DBG_CHANNEL channel, const char* format, ... );
	static void setVerbosity( int verbosity ){ d_verbosity = verbosity; }
	static void setChannel( DBG_CHANNEL channel ){ d_channel = channel; }
};

#endif


ソースファイル

#include "DebugPrinter.h"
#include  <stdio.h>
#include <stdarg.h>

int DebugPrinter::d_verbosity = 3;
DebugPrinter::DBG_CHANNEL DebugPrinter::d_channel = CHANNEL_ANY;

void DebugPrinter::printDebugMsg( int verbosity, DBG_CHANNEL channel, const char* format, ... )
{
	if( d_verbosity >= verbosity ){
		if( (d_channel & channel) != 0 ){
			const int MAX_CHARS = 1023;

			va_list argList;
			va_start( argList, format );
			static char s_buffer[MAX_CHARS + 1];
			vsnprintf( s_buffer, MAX_CHARS, format, argList );
			va_end( argList );

			printf( s_buffer );
		}
	}
}

TABのインデントが無視されて表示されるようですね。
ちょっと見にくくなってすいません。

(山田)

参考文献 "ゲームエンジンアーキテクチャ", ジェイソン・グレゴリー 著, 大貫 宏美, 田中 幸 訳, 今給黎 隆, 桐山 忍, 鴨島 潤, 湊 和久 監修, ソフトバンク クリエイティブ株式会社.