第8回 VBAでテクスチャを描画してみる(前編)
カンデラの開発者による連載コラムです。 第8回は、「第8回 VBAでテクスチャを描画してみる(前編)」です。
どういった画像を描画するか
前回実装したシステムは、1セルが1ピクセルで、それぞれのピクセルはRGBで色を決定するというものでした( 図1 )。では、このシステム上で簡単に画像を描画するにはどうすればいいでしょうか?前回は描画面の指定領域を指定色で塗りつぶすという処理を実装しました。描画とは、この指定領域( メモリ )に指定した値を書き込むことでした。この考えを少し拡張してみましょう。この指定領域に別の領域をコピーすることを考えてみます。理論上は図2のような結果が得られるはずです。この図2の右側の領域がこのシステムで読み込まれた任意の画像になれば、画像を描画できたことになります。ですので、今回は、この画像本体のフォーマットを策定し、そのフォーマットの画像を生成するツールを実装し、それを読み込む処理を実装しましょう。
画像フォーマットの策定
なるべく実装の手間を省きたいので、Excel側で読み込む処理を単純化したいと思います。シンプルに幅や高さなどが格納されてるヘッダ部分があり、数バイト後からデータ部が始まり、そのデータ部はRGBの羅列といった構造で今回は十分だと考えられます( 図3 )。C言語風で記述すればリスト1のようなフォーマットになります。しかし、このフォーマットは既存のどの形式とも異なります。そこで、まずは、既存の任意の画像をこのフォーマットに変換するツールを作ることになります。
C言語風データ構造( ソースコードを見る )
#define N_SWIMAGE_TYPE_RGB (0)
#define N_SWIMAGE_TYPE_RGBA (1)
typedef struct _SSWImage {
uint32_t unType;
uint32_t unWidth;
uint32_t unHeight;
uint32_t unPadding;
// 16Bytes.
uint8_t* pData;
} SSWImage;
画像変換ツール
さて、図3のようなフォーマットの画像を任意の画像から生成するツールを設計し、実装することになるのですが、このように既存の画像をターゲット上で読み込める画像データに変換することをコンバートと呼ぶこともあります。そしてコンバートするためのツールをコンバータと呼びます。ですので、今回はExcelを使ったソフトウェアレンダリングシステムで読み込むことのできる画像コンバータを作成することになります。大枠の処理の流れとしては、図4のようになります。要約してしまうと、既存の画像を読み込んで生のRGB(A)の並びを取得して、今回決めたフォーマットのバイナリ形式に変換する処理を実装することになります。既存の画像を読み込む処理については、stb_imageというC言語で書かれたOSSがあり、様々な画像データに対応しています。ですので、今回はこのstb_imageを活用します。こうなると、実装するべきなのは、生のRGB(A)の並びの前に、今回決めたフォーマットのヘッダ部分を入れ込んでファイルを出力すれば実装完了になります。細かいエラー処理などは省きますが、概ねリスト2のような実装をmain関数からコールすることになります。実装が済んだらテスト動作させてみます。図5のようなわかりやすい画像を用意して、コンバータを動作させ、その結果をバイナリエディタで開き、想定どおりのデータが入っていることを確認しましょう。図3のフォーマットに従い、最初の16バイトにはヘッダ部分が、次の16バイトからは3バイト( RGB )ずつデータが並んでいることが分かります( 図6 )。
C言語によるコンバータの処理( ソースコードを見る )
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image.h"
bool DoConvert( char* szSrc, char* szDst )
{
bool bRet = false;
int32_t nWidht = 0, nHeight = 0, nComp = 0;
char* pBuff = reinterpret_cast(
stbi_load( szSrc, &nWidth, &nHeight, &nComp, 0 )
);
if( pBuff ) {
FILE* fp = nullptr;
SSWImage sImage;
sImage.unType = 3 == nComp ? 0 : 1;
sImage.unWidth = static_cast( nWidth );
sImage.unHeight = static_cast( nHeight );
// 空白領域も念の為にクリア.
sImage.unPadding = 0;
// データ本体の指定.
sImage.pData = pBuff;
// この構造体を指定ファイル名に出力.
fp = fopen( szDst, "wb+" );
if( fp ) {
fwrite( &sImage, sizeof(uint32_t), 4, fp );
fwrite( sImage.pData, nWidth * nHeight * nComp, 1, fp );
fclose( fp );
fp = nullptr;
bRet = true;
}
stbi_image_free( pBuff );
}
return bRet;
}