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

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

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

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

ここでは、パッケージの assets フォルダに置いた 24 ビットカラーのビットマップファイルから、 縦横サイズとビットデータを取り出し、メモリに確保する部分までを扱っています。 すなわち、

void loadBmpRGBA(AAssetManager* AssetManager, const char* filename);

として、 CaxTexture クラス ( 自作した、1テクスチャを扱うクラスです。このあと詳細を記述します。 ) に実装している部分です。この関数についての詳細は、 「OpenGL ES で2Dテクスチャを作成する」に記載しています。

【 2021 年 3 月 6 日】
VS2019 による再スタートしたら、当時一気に欲張って実装したために、個別の機能がわかりにくくなってしまっていました。 順を追って見直し、それぞれのページの書き換えを進めています。

▼ セクション一覧

24 ビット BMP ファイルの構成
BITMAPFILEHEADER 構造体
BITMAPINFOHEADER 構造体
アセットからのファイル読み込み
assets からファイルを読み込む関数
BMP ファイルとして解析する
BMP ファイルのヘッダーを解析する関数

このページ、および開発関連ページは、PC向けデザインとなっております。 画面サイズの小さいスマホでは、快適な表示が得られませんので、ご了承ください。

ご利用に際しては、必ずプライバシーポリシー(免責事項等)をご参照ください。
また、本サイトが初めての方は、まずこのページの注意事項をご覧ください。

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

投稿 October 29, 2018、更新 March 6, 2021

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

ファイルの先頭から、
ビットマップ・ファイル・ヘッダー(BITMAPFILEHEADER)、
ビットマップ・インフォ・ヘッダー(BITMAPINFOHEADER
と続きます。

カラーパレット情報はなく、このあとすぐに 1 ピクセルが 3 バイトのデータが始まります。

構造体は自分で定義しないといけないようですので、アプリに依存しない共通の定義として、 axCommon.h と名前を付けた axCommonDef.h と名前を付けたファイルに定義しました。

ファイル名の先頭の ax は Android 開発用に作成したファイルであることを意味することにしています。 以前は axCommon.h としていましたが、新しく axCommonDef.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、更新 March 6, 2021

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

とりあえず、(イメージ)データファイルは assets フォルダを作成して、置くことにします。 .Packaging の中身は実際のフォルダと同等なので、さらにサブフォルダを作成して置き、アクセスすることもできます。

なお、アセットとは「資産」のことで、フォルダ名は assets と複数形にしておかないと、アクセスできないかも知れません。

assets は、ソリューションの <プロジェクト名>.Package にあるべきフォルダですが、 VS2015 でも VS2019 でも、自動で作成されたコードにはありませんので、手動で作成します。 <プロジェクト名>.Package を右クリックし、 追加新しいフォルダーと選択して、作成します。

作成した assets を右クリックし、 追加既存の項目と選んでファイルを取り込むと、 元の場所からコピーされたファイルが、プロジェクトの .Packaging にある assets フォルダに単純にコピーされるようです。 そしてそのまま、.apk ファイルに取り込まれるようです。

取り込まれる様子を確認してみます。

Debug でビルドし、エクスプローラーでソリューションのフォルダを確認すると、 プロジェクトの <プロジェクト名>.Packaging 内に、ARM や ARM64 などプラットフォーム別のフォルダができて、 そこに Debug サブフォルダが作成され、 .apk ファイルが作成されます。

.apk ファイルは拡張子を .zip にすると中身を確認することができます。 単なる zip ファイルですので、ビットマップイメージは高圧縮されていることがわかります。 よって、.apk ファイルサイズを小さくするために、 (透過ビットがない画像を)JPEG や PNG で保存して展開しても、.apk ダウンロードサイズのメリットはほぼなく、 読み込み ( アプリでのデコードの処理時間です。 ) に時間がかかる分だけ無駄、と私は考えています。

assets フォルダからのファイルの読み込みは、 何をするにも(データファイルを参照するとか、サウンドデータを読み込むとか)基本となるようですので、 共通で利用する関数として用意しました。 コード自体は単純で、下記のようになりました。

AAssetAAssetManager で始まる関数は、アセット系の関数で、 Android Depelopers の Asset を参照しています。 ちなみに、先頭の 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 で指定した名前のファイルをオープンします。 返される AAsset* は、読み込み用のアクセスを指すもののようですので、FILE* と同じ感覚でしょう。

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

これで、assets フォルダにあるファイルの内容が、メモリに取り込まれたことになります。

引数で uint32_t* size が指定されている(NULL ではない)場合、 それにファイルサイズを設定し、確保したメモリへのポインタを返しています。

この関数を呼び出した側で、確保したメモリを解放する必要があることに注意が必要です。

free(_buf);
のような呼び出しで良さそうです。

この読み込み処理は、画像を使用する前に1回だけ実行すればよいので、 今はとりあえず、main.cpp のメインループに入る前に実行します。

uint32_t sz;
void* buf = sa::saGX::assetLoad(state->activity->assetManager, "bgFinish.bmp", &sz);

/* ここで buf を使用して BMP をメモリに展開 */

free(buf);

assetLoad 関数は、sa::saGX ネームスペースに定義されているとしています。

▲ページ先頭へ

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

投稿 October 29, 2018、更新 March 6, 2021

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

namespace saAX
{
	class CaxTexture
	{
	public:
		// constructor
		CaxTexture();

		// destructor
		~CaxTexture();

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

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

axTexture.h に置いた定義は、こんなイメージです。 saAX ネームスペース内に、CaxTexture クラスを定義しています。

CaxTexture クラス ( 独自に作成した、1テクスチャを管理するクラスです。 ) のオブジェクト texture で、次のように呼び出しています。

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

以降、loadBmpRGBA 関数の定義と読み込み部分です。

関数を分割して記載している都合で、変数の定義位置などは変えてあります。 また、assetLoad 関数は、実際には外部関数として定義していますが、記載の都合上、クラス内にあるようになっています。

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

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

data に、指定したファイルデータがまるまる読み込まれた、malloc 関数で確保されたメモリへのポインタが返されます。 正式には、読み込みエラーもあり得るので、data を検証する必要があります。

BMP ファイルのヘッダーを解析し、m_bits に必要なメモリを確保するまでが、次の部分です。

	// ヘッダ情報から、画像サイズを取得します。
	BITMAPFILEHEADER bfh;
	BITMAPINFOHEADER bih;
	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;						// メモリ不足
	}

bmpGetHeaders 関数は、BMP ファイルのヘッダーを bfhbih に取得する関数です。 あとで触れます。 deleteBits 関数は、m_bits にすでにメモリが確保されていたら解放する関数です。

なお、bmpGetHeaders 関数も、実際には外部関数として定義していますが、記載の都合上、クラス内にあるようになっています。

m_bits にビットデータをコピーして、完了となります。 セットする値は、1ピクセル4バイトの RGBA データです。 GLubyte 型は、符号なし1バイトです。

	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);
}

【メモ】画像の幅にパディングがある(4の倍数でない)と、うまくいかなそうです。行ごとに base を再計算すれば良さそうです。

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

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);		// [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 ビット整数を取り出す関数です。

いずれもグローバルに定義しています(実際には、ネームスペースを作成して定義しています)。

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 テクスチャを作成できるようになります。

アイコン OpenGL ES で 2D テクスチャを作成する
テクスチャ管理クラス CaxTexture を拡張し、 読み込み済みのイメージデータから 2D テクスチャを作成する手順について、書いています。



▲ページ先頭へ
line
関連トピックス

OpenGL ES での色指定

Visual C++ で自動生成されたコードを参照し、OpenGL ES での背景色指定と描画について、書いています。

OpenGL ES で2Dテクスチャを作成する

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

Java クラス関数の作成と呼び出し

ネイティブアプリに Java クラスを実装し、JNI を利用して C/C++ コードから Java コードを呼び出す方法について、書いています。

line
その他のおすすめ
line

Android 開発に関する記事をまとめた Android 開発トップ もご覧ください。

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


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