このサイトでは、分析、カスタマイズされたコンテンツ、および広告に Cookie を使用します。このサイトを引き続き閲覧すると、Cookie の使用に同意するものと見なされます。
Hi, Developers,
straightapps.com
October 29, 2018, 作成、October 31, 2019, 追記
トップページ > Android 開発トップ > OpenGLES:24 ビット BMP 画像を読み込む

ここでは、OpenGLES テクスチャ作成に使用するため、24 ビット BMP 画像を読み込む方法を検討しています。

Android ネイティブアプリで画面を構成するには、それが3D表示でなくても、「テクスチャ」を使用しなくてはなりません。 今は2Dしか扱いませんが、それでも「面を作成して、それにテクスチャ(表面画像)を貼り付ける」という形をとる必要があります。

まずは背景画像など、透過が不要な画像を描画するために、ビットマップ画像を読み込んでテクスチャとしたいのですが、 特に簡単なクラスなどは用意されていないようである上、Windows のように自由にファイルにアクセスできるわけでもないようです。

ここでは、assets に置いた 24 ビットカラーのビットマップファイルから、縦横サイズとビットデータを取り出し、メモリに確保する部分を扱っています。すなわち、
void loadBmpRGBA
(AAssetManager* AssetManager, const char* filename);
として、CaxTexture クラスに実装している部分です。この関数についての詳細は、 「OpenGLES で2Dテクスチャを作成する」に記載しています。

このページ、および開発関連ページは、PC向けデザインとなっております。 画面サイズの小さいスマホでは、快適な表示が得られませんので、ご了承ください。
ご利用に際しては、必ずプライバシーポリシー(免責事項等)をご参照ください。
また、本サイトが初めての方は、まずこのページの注意事項をご覧ください。

24 ビット BMP ファイルの構成

投稿 October 29, 2018

ファイル構成が最も単純な、圧縮なしの 24 ビット BMP ファイルを対象とします。

ファイルの先頭から、 ビットマップ・ファイル・ヘッダー(BITMAPFILEHEADER)、 ビットマップ・インフォ・ヘッダー(BITMAPINFOHEADER)と続きます。 そして、1ピクセルが3バイトのデータが始まります。

構造体は、自身で定義しないといけないようですので、アプリに依存しない共通の定義として、 axCommon.h と名前を付けたファイルに定義しました。 ファイル名は、もちろん何でも構いません。BMP ファイルを解析する部分からだけ参照できれば、問題ありません。

// ビットマップ・ファイル・ヘッダー(14バイト)
//    【注意】4バイトアラインメントにより、bfTypeのあと、bfSizeがオフセット4に配置されます。
typedef struct tagBITMAPFILEHEADER {
	uint16_t bfType;		// [00] 2-byte(WORD) 識別子("BM" であること)
	uint32_t bfSize;		// [02] 4-byte(DWORD) ビットマップファイルのサイズ(アラインメントの影響を受ける)
	uint16_t bfReserved1;		// [06] 2-byte(WORD) 予約済み(0であること)
	uint16_t bfReserved2;		// [08] 2-byte(WORD) 予約済み(0であること)
	uint32_t bfOffBits;		// [10] 4-byte(DWORD) このヘッダの最初からビットデータへのオフセット
}
BITMAPFILEHEADER,
*PBITMAPFILEHEADER;

念のためですが、uint16_t は符号なし 16 ビット整数、uint32_t は符号なし 32 ビット整数です。 バイト数を正しく設定しないと、取得される値が正しくなくなりますので、型指定には注意が必要です。

// ビットマップ情報ヘッダ(40バイト)
typedef struct tagBITMAPINFOHEADER {
	uint32_t biSize;		// [00] 4-byte(DWORD) このデータのバイト数
	int32_t biWidth;		// [04] 4-byte(LONG) ビットマップの幅
	int32_t biHeight;		// [08] 4-byte(LONG) ビットマップの高さ(負のとき上から下へのDIB)
	uint16_t biPlanes;		// [12] 2-byte(WORD) プレーン数(1であること)
	uint16_t biBitCount;		//      2-byte(WORD) bits-per-pixel
	uint32_t biCompression;		// [16] 4-byte(DWORD) BI_RGB またはその他
	uint32_t biSizeImage;		// [20] 4-byte(DWORD) イメージのバイト数(BI_RGBのとき0も可)
	int32_t biXPelsPerMeter;	// [24] 4-byte(LONG) 水平解像度(pixels-per-meter)
	int32_t biYPelsPerMeter;	// [28] 4-byte(LONG) 垂直解像度(pixels-per-meter)
	uint32_t biClrUsed;		// [32] 4-byte(DWORD) カラーテーブルのインデックス数
	uint32_t biClrImportant;	// [36] 4-byte(DWORD) 使用しているカラーテーブル数(0のときすべて)
}
BITMAPINFOHEADER,
*PBITMAPINFOHEADER;

int32_t は符号付き 32 ビット整数です。

このあとにビットデータが続くわけですが、あとにまわします。

▲ページ先頭へ

アセットからのファイル読み込み

投稿 October 29, 2018

Java を使用した Android アプリだと、画像データは、解像度別に .Package の res 内にある drawable や drawable-hdpi に 入れると簡単にアクセスできるか、気にせずアクセスできると思いますが、ネイティブアプリでは自由度が高い代償に(?)、自分でやる必要があります。

とりあえず、データファイルは assets に置きます(ここにサブフォルダを作成して置くこともできます)。 assets は、ソリューションの .Package にあるフォルダで、ない場合は作成すればOKです。 assets にファイルを取り込むと、元の場所からコピーされたファイルが、プロジェクトの .Packaging にある assets に 単純にコピーされるようです。そしてそのまま、.apk ファイルに取り込まれるようです。

Debug でビルドすると、プロジェクトの .Packaging 内に、ARM などターゲット別のフォルダができて、そこに Debug サブフォルダが作成され、 .apk ファイルが作成されます。拡張子を .zip にすると中身が見れますので見てみると、単なる zip ファイルですので、 ビットマップイメージは高圧縮されていることがわかります。よって、.apk ファイルサイズを小さくするために、 (透過ビットがない画像を)JPEG や PNG で保存して展開しても、.apk ダウンロードサイズのメリットはほぼなく、 読み込みに時間がかかる分だけ無駄、と私は考えています。

assets からのファイルの読み込みは何をするにも(データファイルを参照するとか、 サウンドデータを読み込むとか)基本となるようですので、共通で利用する関数として用意しました。 コード自体は単純で、下記のようになりました。 一応、masaGX と名付けたネームスペース内に定義しています。

AAssetAAssetManager で始まる関数は、アセット系の関数で、Android Depelopers のここを参照しています。 ちなみに、先頭の A が、"Android" を意味しているようです。ミスタイプでダブっているわけではありません。

/**
* 【アセット】asset にあるファイルを読み込んで、メモリに格納します。
*/
void* assetLoad(AAssetManager* AssetManager, const char* name, uint32_t* size)
{
	AAsset* _asset = AAssetManager_open(AssetManager, name, AASSET_MODE_BUFFER);

	assert(_asset);

	size_t _size = AAsset_getLength(_asset);		// ファイルサイズを取得します
	void* _buf = malloc(_size);				// データバッファを用意します

	AAsset_read(_asset, _buf, _size);			// データを読み込みます
	AAsset_close(_asset);					// アセットをクローズします

	// パラメータでサイズを返す指定となっていたら、値を設定します。
	if (size) {
		*size = (uint32_t)_size;
	}

	// 確保したメモリへのポインタを返します。
	return _buf;
}

引数 AssetManager は、
AAssetManager* AssetManager = app ( struct android_app* ) ->activity->assetManager;
のようにして取得できる、アセットマネージャーと呼ばれるものです。
AAssetManager_open 関数で 引数 name で指定した名前のファイルをオープンします。 返される Asset* は、読み込み用のアクセスを指すもののようですので、FILE* と同じ感覚でしょう。

AAsset_getLength 関数でファイルサイズを取得し、malloc 関数でメモリを確保しています。 本当はメモリの確保が成功したかどうかを調べる必要があるとは思いますが、確保できるとして、 AAsset_read 関数で一気にメモリに読み込み、 AAsset_close 関数で、アクセスを終了します。 これで、assets にあるファイルの内容が、メモリに取り込まれたことになります。

引数で uint32_t* size が指定(NULL ではない)場合、ファイルサイズを設定し、確保したメモリへのポインタを返しています。 呼び出し側で、確保したメモリを解放する必要があります。
free(_buf);
のような呼び出しで良さそうです。

▲ページ先頭へ

BMP ファイルとして解析します

投稿 October 29, 2018

作成したコードでは、 CaxTexture ( 1つのテクスチャを扱うクラスとして作成したものです。 ) と名付けたクラスで BMP 画像を読み込んでいます。 どういう構成でも構いませんが、クラスになっていると扱いやすいかと思います。 このクラスのメンバー m_bits に、ビットマップ・ビットを読み込むまでを用意しました。

/**
*    assets から指定の BMP 画像を読み込み、RGBA データとして m_bits に格納します。
*/
void loadBmpRGBA(AAssetManager* AssetManager, const char* filename);

/**
*    画像データビット
*/
GLubyte *m_bits;

CaxTexture クラスのオブジェクト texture で、次のように呼び出しています。

AAssetManager* mgr = app->activity->assetManager;
texture.loadBmpRGBA(mgr, "image.bmp");

以降、loadBmpRGBA 関数の定義と読み込み部分です。
関数を分割して記載している都合で、変数の定義位置なども変えてあります。

void CaxTexture::loadBmpRGBA(AAssetManager* AssetManager, const char* filename)
{
	uint32_t size;

	// アセットから画像を読み込みます。
	char* data = (char*)masaGX::assetLoad(AssetManager, filename, &size);

data に malloc で確保されたメモリへのポインタが返され、ファイルの内容が読み込まれています。 BMP ファイルのヘッダーを解析し、必要なメモリを確保するまでが、次の部分です。
bmpGetHeaders 関数は、BMP ファイルのヘッダーを bfh と bih に取得する関数です。 あとで触れます。 deleteBits 関数は、m_bits にすでにメモリが確保されていたら解放する関数です。

	// ヘッダ情報から、画像サイズを取得します。
	BITMAPFILEHEADER bfh;
	BITMAPINFOHEADER bih;

	masaGX::bmpGetHeaders(&bfh, &bih, data);		// dataからヘッダ情報を取り出します

	m_width = bih.biWidth;					// 横サイズ
	m_height = bih.biHeight;				// 縦サイズ

	// 必要なサイズの RGBA メモリ領域を用意します。
	deleteBits();						// m_bits がすでに確保されていれば削除
	m_bits = new GLubyte[(m_width * 4) * m_height];		// 画像データ領域
	if (!m_bits) {
		return;						// メモリ不足
	}

m_bits にビットデータをコピーして、完了となります。

	unsigned int x, y;
	int n, r, base;

	// 画像データを設定します。
	base = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);	// BMPファイルのヘッダ部(54バイト)をスキップ
	for (y = 0; y < m_height; y++) {
		n = y * (m_width * 4);					// 書き込み対象ラインの先頭のオフセット
		for (x = 0; x < m_width; x++) {
			r = n + x * 4;
			m_bits[r + 2] = data[base];			// B
			m_bits[r + 1] = data[base + 1];			// G
			m_bits[r + 0] = data[base + 2];			// R
			m_bits[r + 3] = 255;				// A(255=不透明)

			// 画像データのインデックスを進めます。
			//    24ビットカラー固定であれば、3進めます。
			base += 3;
		}
	}

	// assetLoad で確保したメモリは、呼び出し側で解放する必要があります。
	free(data);
}

BMP ファイルのヘッダーを解析する bmpGetHeaders 関数です。 BMP ファイルをまるまる読み込んだメモリから、 BITMAPFILEHEADER 構造体と、 BITMAPINFOHEADER 構造体を取得します。
masaGX ネームスペース内で定義しています。

bool bmpGetHeaders(PBITMAPFILEHEADER bfh, PBITMAPINFOHEADER bih, char* data)
{
	char* off;

	// 引数のチェックを行います。
	if (!bfh || !bih || !data) {
		return false;
	}

	// BITMAPFILEHEADER にデータを設定します。
	bfh->bfType = valDataUint16(data);			// [00] 2-byte(WORD) 識別子("BM" であること)
	bfh->bfSize = valDataUint32(data + 2);			// [02] 4-byte(DWORD) ビットマップファイルのサイズ(アラインメントの影響を受ける)
	bfh->bfReserved1 = valDataUint16(data + 6);		// [06] 2-byte(WORD) 予約済み(0であること)
	bfh->bfReserved2 = valDataUint16(data + 8);		// [08] 2-byte(WORD) 予約済み(0であること)
	bfh->bfOffBits = valDataUint32(data + 10);		// [10] 4-byte(DWORD) このヘッダの最初からビットデータへのオフセット

	// BITMAPFILEHEADER のサイズだけ進めます。
	off = data + 14;

	// BITMAPINFOHEADER にデータを設定します。
	bih->biSize = valDataUint32(off);			// [00] 4-byte(DWORD) このデータのバイト数
	bih->biWidth = valDataInt32(off + 4);			// [04] 4-byte(LONG) ビットマップの幅
	bih->biHeight = valDataInt32(off + 8);			// [08] 4-byte(LONG) ビットマップの高さ(負のとき上から下へのDIB)
	bih->biPlanes = valDataUint16(off + 12);		// [12] 2-byte(WORD) プレーン数(1であること)
	bih->biBitCount = valDataUint16(off + 14);		//      2-byte(WORD) bits-per-pixel
	bih->biCompression = valDataUint32(off + 16);		// [16] 4-byte(DWORD) BI_RGB またはその他
	bih->biSizeImage = valDataUint32(off + 20);		// [20] 4-byte(DWORD) イメージのバイト数(BI_RGBのとき0も可)
	bih->biXPelsPerMeter = valDataInt32(off + 24);		// [24] 4-byte(LONG) 水平解像度(pixels-per-meter)
	bih->biYPelsPerMeter = valDataInt32(off + 28);		// [28] 4-byte(LONG) 垂直解像度(pixels-per-meter)
	bih->biClrUsed = valDataUint32(off + 32);		// [32] 4-byte(DWORD) カラーテーブルのインデックス数
	bih->biClrImportant = valDataUint32(off + 36);		// [36] 4-byte(DWORD) 使用しているカラーテーブル数(0のときすべて)

	return true;
}

valDataUint16 関数は、引数で渡したバイトデータから符号なし 16 ビット整数を取り出す関数です。 同様に、valDataUint32 関数は、符号なし 32 ビット整数を、 valDataInt32 関数は、符号あり 32 ビット整数を取り出す関数です。
いずれも masaGX ネームスペースに定義しています。

uint16_t valDataUint16(char* data)
{
	return *((uint16_t*)data);
}

int32_t valDataInt32(char* data)
{
	return *((int32_t*)data);
}

uint32_t valDataUint32(char* data)
{
	return *((uint32_t*)data);
}

ここまでで、assets に置いた BMP 画像を読み込み、 m_bits にビットデータをセット、 m_widthm_height に画像サイズが設定されました。

これで、2Dテクスチャを作成できるようになります。

▲ページ先頭へ


関連トピックス

OpenGLES で2Dテクスチャを作成する
OpenGLES を使用して2Dテクスチャを作成する方法について検討しています。


その他のおすすめ
おすすめ記事はありません。

JavaScriptが無効です
▲ページ先頭へ

© 2017-2018 StraightApps.com 無断転載を禁じます。No reproduction without permission.