Android NDK用にlibpngをビルドして利用する

test108_01.png
今回はlibpngを利用してAndroidネイティブ環境からPNG画像の表示を行う。
まずhttp://www.libpng.org /pub/png /libpng.htmlからlibpngのソースコードをダウンロードする。今回利用したのはlibpng 1.4.5だ。

test108_02.png
次にEasyProjectGenerator for AndroidでNativeViewプロジェクトを作成する。

test108_03.png
そして自動生成されたプロジェクトにあるjniフォルダ内に「libpng」フォルダを作成。先ほどダウンロードしたlibpngのソースコードのうち、ルートにある*.cと*.hファイルを作成したlibpngフォルダ内へ解凍する。

test108_04.png
libpng 1.4.5はその処理にzlibを利用している。そのためさらにzlibをプロジェクトに加える。
ゼロからzlibを用意するのは面倒だ。そのため前に作成した「Android NDK用にzlibをビルドして利用する」エントリーでのプロジェクトファイルを流用する。「Test106.zip」をダウンロードする。

test108_05.png
そしてダウンロードしたプロジェクトのjniフォルダにあるzlibフォルダとAndroid.mkを今回のプロジェクトに取り込む。その前にjniフォルダ直下にあったTest108jni.cとAndroid.mkをtest108というフォルダを作成、その中へ移動しておいた。

test108_06.png
これまでの作業で作れたプロジェクトをEclipseにAndroidプロジェクトとして取り込む。これで図のようなフォルダ構造になった。

test108_07.png さらに必要なファイルを順次作成する。まずはlibpngフォルダ内にAndroid.mkを作る。 libpngのソースコードのうちexample.cとpngtest.cはmain()を含んだテスト用なのでビルドの必要はない。
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := libpng
LOCAL_LDLIBS := -lz
LOCAL_SRC_FILES := png.c pngerror.c pngget.c pngmem.c pngpread.c pngread.c pngrio.c pngrtran.c pngrutil.c pngset.c pngtrans.c pngwio.c pngwrite.c pngwtran.c pngwutil.c

include $(BUILD_STATIC_LIBRARY)

test108_08.png 次にtest108フォルダ内のAndroid.mkを修正する。
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := Test108jni
LOCAL_SRC_FILES := Test108jni.c
LOCAL_LDLIBS    := -lz -llog -ljnigraphics

LOCAL_C_INCLUDES += $(LOCAL_PATH)/../zlib
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../libpng
LOCAL_STATIC_LIBRARIES := libzip libpng

include $(BUILD_SHARED_LIBRARY)

test108_09.png そして最後に実際の描画処理を実装する。今回は画像ファイル「/sdcard/test.png」を読み込んで表示する。
#include <jni.h>
#include <android/bitmap.h>
#include <stdlib.h>
#include <png.h>


/* this function is from Android NDK bitmap-plasma */
static uint16_t  make565(int red, int green, int blue)
{
	return (uint16_t)( ((red   << 8) & 0xf800) |
					   ((green << 2) & 0x03e0) |
					   ((blue  >> 3) & 0x001f) );
}



static int DrawBitmap(AndroidBitmapInfo* pBitmapInfo, void* pPixels, const char* pszImageFile)
{
	FILE	*fp;

	//画像ファイルの読み込み
	fp = fopen(pszImageFile,"r");
	if(fp == NULL)
		return	0;

	int		bSucceeded = 0;

	png_bytepp	ppRowImage = NULL;
	png_structp	pPng = NULL;
	png_infop	pInfo = NULL;

	while(1)
	{
		pPng = png_create_read_struct(PNG_LIBPNG_VER_STRING,NULL,NULL,NULL);
		if(pPng == NULL)
			break;

		pInfo = png_create_info_struct(pPng);
		if(pInfo == NULL)
			break;

		//ファイルポインタの設定
		png_init_io(pPng,fp);

		png_uint_32	nWidth;
		png_uint_32	nHeight;
		int		nBitDepth;
		int		nColorType;
		int		nIntMethod;
		int		nCompMethod;
		int		nFilterMethod;
		png_uint_32	nImageBytes;
		png_uint_32	nRowBytes;

		//画像情報取得
		png_read_info(pPng,pInfo);
		png_get_IHDR(pPng,pInfo,&nWidth,&nHeight,&nBitDepth,&nColorType,&nIntMethod,&nCompMethod,&nFilterMethod);
		nRowBytes	= png_get_rowbytes(pPng,pInfo);
		nImageBytes	= nHeight * nRowBytes;

		//画像データ用バッファーの確保
		ppRowImage = (png_bytepp)malloc(sizeof(png_bytep) * nHeight + nImageBytes);
		if(ppRowImage == NULL)
			break;

		int		yy;

		//画像データ用バッファーの初期化
		{
			png_bytep	pRowImage;

			pRowImage = (png_bytep)&(ppRowImage[nHeight]);
			for(yy = 0; yy < nHeight; yy++)
			{
				ppRowImage[yy] = pRowImage;
				pRowImage += nRowBytes;
			}
		}


		//画像ファイル読み込み
		png_read_image(pPng,ppRowImage);


		//描画
		for(yy = 0; yy < pBitmapInfo->height && yy < nHeight; yy++)
		{
			int		xx;
			uint16_t*	pLine;
			char*		pImagePixel;

			pLine = (uint16_t*)pPixels;					//ビットマップの行データ
			pImagePixel = (char*)(ppRowImage[yy]);		//画像ファイルの行データ

			for(xx = 0; xx < pBitmapInfo->width && xx < nWidth; xx++)
			{
				//■■注意■■1ピクセル3バイト前提で処理している。しかも画像ファイルが1ピクセル3バイトかどうかはチェックしていない!
				pLine[xx] = make565(pImagePixel[0],pImagePixel[1],pImagePixel[2]);
				pImagePixel += 3;
			}

			pPixels = (char*)pPixels + pBitmapInfo->stride;
		}

		bSucceeded = 1;
		break;				//必須忘れないこと!
	}


	if(ppRowImage)
	{
		free(ppRowImage);
		ppRowImage = NULL;
	}

	if(pPng)
	{
		if(pInfo)
			png_destroy_read_struct(&pPng,&pInfo,NULL);
		else
			png_destroy_read_struct(&pPng,NULL,NULL);

		pPng = NULL;
		pInfo = NULL;
	}

	fclose(fp);

	return	bSucceeded;
}



JNIEXPORT void JNICALL Java_com_Test108_NativeView_RenderBitmap(JNIEnv * env, jobject  obj, jobject bitmap)
{
	AndroidBitmapInfo  info;
	void*			  pixels;
	int				ret;

	if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0)
		return;

	if (info.format != ANDROID_BITMAP_FORMAT_RGB_565)
		return;

	if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0)
		return;

	DrawBitmap(&info,pixels,"/sdcard/test.png");

	AndroidBitmap_unlockPixels(env, bitmap);
}


test108_10.png
ソースコードの準備ができたので、ndk-buildによりビルドする。

test108_11.png
そして実行。まだ画像ファイルを用意していいないので何も表示されない。

test108_12.png
表示用のpng画像ファイルを用意する。

test108_13.png
Eclipseの「DDMS」画面で、「/mnt/sdcard/」を選択、「push a file on to device」ボタンからpngファイルを仮想SDカードへ転送する。もしもこの画面での転送に失敗する場合はadb pushコマンドによりコマンドラインから転送する。

test108_14.png
これで表示用のpng画像を用意できた。

test108_15.png
実行すると画像が表示された。。。が、表示色がだいぶ変わっている。

test108_16.png
原因を調べたところ、Android NDKのサンプルプロジェクト「bitmap-plasma」からコピペしていたmake565()関数内のバグでした。ここを修正する。

※EasyProjectGenerator for Android Ver1.02で修正します。
/* this function is from Android NDK bitmap-plasma , and modify for green color bug */
static uint16_t  make565(int red, int green, int blue)
{
	return (uint16_t)( ((red   << 8) & 0xf800) |
					   ((green << 3) & 0x07e0) |
					   ((blue  >> 3) & 0x001f) );
}

test108_17.png
これで減色による色変化が若干あるものの、PNG画像を表示できた。



プロジェクトファイルをダウンロード


カテゴリー「android」 のエントリー