Android NDK用にlibpngをビルドして利用する
今回はlibpngを利用してAndroidネイティブ環境からPNG画像の表示を行う。
まずhttps://www.libpng.org /pub/png /libpng.htmlからlibpngのソースコードをダウンロードする。今回利用したのはlibpng 1.4.5だ。
次にEasyProjectGenerator for AndroidでNativeViewプロジェクトを作成する。
そして自動生成されたプロジェクトにあるjniフォルダ内に「libpng」フォルダを作成。先ほどダウンロードしたlibpngのソースコードのうち、ルートにある*.cと*.hファイルを作成したlibpngフォルダ内へ解凍する。
libpng 1.4.5はその処理にzlibを利用している。そのためさらにzlibをプロジェクトに加える。
ゼロからzlibを用意するのは面倒だ。そのため前に作成した「Android NDK用にzlibをビルドして利用する」エントリーでのプロジェクトファイルを流用する。「Test106.zip」をダウンロードする。
そしてダウンロードしたプロジェクトのjniフォルダにあるzlibフォルダとAndroid.mkを今回のプロジェクトに取り込む。その前にjniフォルダ直下にあったTest108jni.cとAndroid.mkをtest108というフォルダを作成、その中へ移動しておいた。
これまでの作業で作れたプロジェクトをEclipseにAndroidプロジェクトとして取り込む。これで図のようなフォルダ構造になった。

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)

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)

#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); }
ソースコードの準備ができたので、ndk-buildによりビルドする。
そして実行。まだ画像ファイルを用意していいないので何も表示されない。
Eclipseの「DDMS」画面で、「/mnt/sdcard/」を選択、「push a file on to device」ボタンからpngファイルを仮想SDカードへ転送する。もしもこの画面での転送に失敗する場合はadb pushコマンドによりコマンドラインから転送する。
実行すると画像が表示された。。。が、表示色がだいぶ変わっている。
原因を調べたところ、Android NDKのサンプルプロジェクト「bitmap-plasma」からコピペしていたmake565()関数内のバグでした。ここを修正する。
/* 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) ); }