« 2010年05月 | メイン | 2011年01月 »

前の10件 1  2  3

2010年12月 記事一覧

2010年12月27日

Android JNIプロジェクト用のウイザードを使う(EasyProjectGenerator for Android Ver 1.00)

epg01.png
ネイティブなAndroid開発は、雛形となるプロジェクトを作るのが面倒で、ちょっとしたソースコードテストをしにくい。そのため、簡単にAndroidプロジェクトを自動生成する「EasyProjectGenerator for Android」を作りました。

EasyProjectGenerator for Android Ver 1.00」をダウンロードしてデスクトップへ解凍する。これでインストールが終ります。

epg02.png
インストールしたフォルダにある「HelloJni.bat」を実行する。

epg03.png
すると自動生成するAndroidプロジェクト名を入力するダイヤログが開く。今回は「Test103」とした。

epg04.png
するとEasyProjectGenerator for Androidのインストールフォルダ内にプロジェクト名と同じフォルダができる。これが自動生成されたAndroidプロジェクトです。

epg05.png
実際に自動生成したプロジェクトを利用するためEclipseを起動し、「File」メニューの「New」にある「Android Project」を選択する。

epg06.png
そして「Create project from existing source」を選択し、先ほど生成されたフォルダを指定する。

epg07.png
これでEclipseにAndroidプロジェクトとして取り込めた。

epg08.png
プロジェクトフォルダ内には「_build.jni.bat」がある。これを実行すると、、、

epg09.png
ダブルクリックひとつでAndroidネイティブソースコードがビルドされる。



もしもbashが見つからないというエラーが生じる場合は、環境変数のパスに「c:\cygwin\bin」を追加する。

epg10.png
次にEclipseでプロジェクトを右クリックして現れるメニューの「Run As」にある「Android Application」を選択する。

epg11.png
するとAndroidエミュレーターが起動して、自動生成したネイティブアプリが実行される。



まだまだ未完成な部分もあるが、これからはこの「EasyProjectGenerator for Android」を利用して雛形プロジェクトを生成、利用する。


AndroidネイティブアプリからSDカードへファイルを保存する

test104_01.png
今回はAndroidネイティブアプリからSDカードへファイルを読み書きしてみる。
前回紹介したEasyProjectGenerator for Android Ver 1.00を利用してAndroidプロジェクトを生成、Eclipseに取り込む。

test104_02.png そしてjniフォルダにあるネイティブソースコードにファイル関連処理を入れる。ソースコードを編集したら忘れずにファイル保存する。
#include <string.h>
#include <jni.h>
#include <stdio.h>

jstring
Java_com_Test104_Test104Act_stringFromJNI(JNIEnv* env,jobject thiz)
{
	char*	pszRet;
	char	pszBuff[256];
	FILE	*fp;

	/* file WRITE */
	fp = fopen("/sdcard/test.txt","w");
	if(fp)
	{
		fputs("Test line.",fp);
		fclose(fp);
	}

	pszBuff[0] = NULL;
	pszRet = NULL;

	/* file READ */
	fp = fopen("/sdcard/test.txt","r");
	if(fp)
	{
		pszRet = fgets(pszBuff,256,fp);
		fclose(fp);
	}

	if(pszRet)
	    return (*env)->NewStringUTF(env, pszBuff);
	else
	    return (*env)->NewStringUTF(env, "Error.");
}

test104_03.png
そしてプロジェクトフォルダ内にあるバッチファイルを利用してネイティブコードをビルドする。

test104_04.png
ネイティブのビルドが終わったら、Eclipse上から実行する。

test104_05.png
これでAndroidエミュレーター上でエラーが表示されず、実行が確認できた。

test104_06.png Eclipseの「DDMS」画面の「File Explorer」見ると、実際にファイルが保存されていた。
本来であればAndroidManifest.xmlにandroid. permission. WRITE_EXTERNAL_STORAGEを設定する必要があると思うのだがなくても動作するようだ。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>



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

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

test105_01.png
今回はオープンソースのデータベース「SQLite」をAndroid NDKから呼び出して利用してみる。

まずhttp://www.sqlite.org/ download.htmlからSQLiteのソースコードをダウンロードする。利用したバージョンはSQLite 3.7.4だ。

test105_02.png
次にEasyProjectGenerator for Android Ver 1.00を利用してAndroid NDKプロジェクトを生成、Eclipseに取り込む。

test105_03.png
そしてプロジェクトフォルダのjniフォルダの下に「sqlite」というフォルダを作り、中に先ほどダウンロードしたSQLiteのソースコードを一式解凍する。

test105_04.png
さらに自動生成されていたソースコードとmakefileを「test105」というフォルダを作り、その中へ移動する。

test105_05.png
これでプロジェクトフォルダ内が図のようになった。

test105_06.png
ここでjniフォルダ直下にAndroid.mkファイルを新規作成する。

test105_07.png jniフォルダ直下にAndroid.mkファイルの中に1行追加。サブフォルダのmakefileを検索してビルドされるようにした。
include $(call all-subdir-makefiles)

test105_08.png 次にsqliteフォルダ直下にAndroid.mkファイルを新規作成し、内容を編集する。
このmakefileはAndroidプロジェクトのhttp://code.google.com/ p/rowboat/内で使われていたものを改変した。
#
# from rowboat-external-sqlite-master and modified
#

LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_SRC_FILES := sqlite3.c

ifneq ($(TARGET_ARCH),arm)
LOCAL_LDLIBS += -lpthread -ldl
endif

LOCAL_CFLAGS += -DHAVE_USLEEP=1 -DSQLITE_DEFAULT_JOURNAL_SIZE_LIMIT=1048576 -DSQLITE_THREADSAFE=1 -DNDEBUG=1 -DSQLITE_ENABLE_MEMORY_MANAGEMENT=1 -DSQLITE_DEFAULT_AUTOVACUUM=1 -DSQLITE_TEMP_STORE=3 -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_BACKWARDS -DSQLITE_ENABLE_POISON -Dfdatasync=fsync

ifneq ($(TARGET_SIMULATOR),true)
LOCAL_SHARED_LIBRARIES := libdl
endif

LOCAL_MODULE:= libsqlite
#new sqlite 3.5.6 no longer support external allocator
#LOCAL_CFLAGS += -DSQLITE_OMIT_MEMORY_ALLOCATION
LOCAL_C_INCLUDES += $(call include-path-for, system-core)/cutils
LOCAL_SHARED_LIBRARIES += liblog \
            libicuuc \
            libicui18n \
            libutils

# include android specific methods
LOCAL_WHOLE_STATIC_LIBRARIES := libsqlite3_android

include $(BUILD_SHARED_LIBRARY)

test105_09.png 次にJNIにより実行される関数でSQLiteが利用されるようにする。
#include <string.h>
#include <jni.h>

#include "../sqlite/sqlite3.h"

jstring
Java_com_Test105_Test105Act_stringFromJNI(JNIEnv* env,jobject thiz)
{
	char	pszRet[256];
	strcpy(pszRet,"Failed.");

	sqlite3 *db;
	int rc = sqlite3_open("/sdcard/a.db", &db);
	sqlite3_stmt *st;

	rc = sqlite3_prepare(db, "CREATE TABLE test(id INTEGER PRIMARY KEY,name TEXT);", -1, &st, NULL);
	rc = sqlite3_step(st);
	rc = sqlite3_finalize(st);

	rc = sqlite3_prepare(db, "INSERT INTO test(name) VALUES (?);", -1, &st, NULL);
	sqlite3_bind_text(st, 1, "abcdef", -1, SQLITE_STATIC);
	rc = sqlite3_step(st);
	rc = sqlite3_finalize(st);

	rc = sqlite3_prepare(db, "SELECT name FROM test", -1, &st, NULL);
	while((rc = sqlite3_step(st)) == SQLITE_ROW)
	{
		char* pszName = (char*)sqlite3_column_text(st, 0);
		strcpy(pszRet,pszName);
		break;
	}
	sqlite3_finalize(st);

	sqlite3_close(db);

	return (*env)->NewStringUTF(env, pszRet);
}

test105_10.png そして共有ライブラリとしてSQLiteが読み込まれるようにAndroid.mkを編集する。
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := Test105jni
LOCAL_SRC_FILES := Test105jni.c

LOCAL_SHARED_LIBRARIES := libsqlite

include $(BUILD_SHARED_LIBRARY)

test105_11.png
これでndk-buildによりネイティブコードをビルドする。

test105_12.png
そしてプロジェクトをAndroidエミュレーターで実行。

test105_13.png
これで正常に動いた。

test105_14.png
EclipseのDDMSから仮想SDカード内にSQLiteが作成したデータベースファイルも確認できる。




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

2010年12月28日

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

test106_01.png
今回はデータ圧縮/解凍用のzlibをAndroidのネイティブ環境から利用してみる。基本的な流れは前回のSQLiteを利用したときと同じだ。

http://www.zlib.net/からzlibのソースコードをダウンロードする。今回はzlib 1.2.5を利用した。

test106_02.png
次にEasyProjectGenerator for Android Ver 1.00を利用してAndroid NDKプロジェクトを生成して、jniフォルダ直下に「zlb」というフォルダを作る。そして、そこへダウンロードしたzlibのソースファイルのうち、上位のフォルダに入っている*.cと*.hファイルをすべて解凍する。

test106_03.png
さらにjniフォルダ直下のソースファイルとmakefileも新しく「Test106」というフォルダを作り、そこへ移動しておく。

test106_04.png
ここまでの操作でできたプロジェクトフォルダをEclipseにAndroidプロジェクトとして取り込んだ。

test106_05.png
次にビルド用のAndroid.mkファイルを順次作成する。まずはjniフォルダの直下に作る。

test106_06.png jniフォルダの直下のAndroid.mkファイルはその下のサブフォルダをビルド対象にさせる。
include $(call all-subdir-makefiles)

test106_07.png 次にzlibのビルド用Android.mkをzlibフォルダ内に作成する。 解凍したソースファイルのうち、minigzip.cとexample.cはmain関数を含んだテスト用なのでビルド対象からは除外する。
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := libzip
LOCAL_LDLIBS := -lz
LOCAL_SRC_FILES := adler32.c compress.c crc32.c deflate.c  gzclose.c gzlib.c gzread.c gzwrite.c infback.c inffast.c inflate.c inftrees.c trees.c uncompr.c zutil.c

include $(BUILD_STATIC_LIBRARY)

test106_08.png 次にJNI用のメインとなるソースファイルを編集する。
#include <string.h>
#include <jni.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <zlib.h>



int CompressTest(int bCompress,const char* pszDstFile,const char* pszSrcFile)
{
	int		ret;
	int		nFd;
	char*	pszMapped;
	long	nFileSize;

	//処理対象ファイルを開く
	nFd = open(pszSrcFile,O_RDONLY);
	if(nFd < 0)
		return	0;

	//処理対象ファイルのサイズ取得
	{
		struct stat	sFileStat;

		ret = fstat(nFd,&sFileStat);
		if(ret < 0)
		{
			close(nFd);
			return	0;
		}
		nFileSize = sFileStat.st_size;
	}

	//処理対象ファイルをメモリマップドファイルにする
	pszMapped = (char*)mmap(NULL,nFileSize,PROT_READ,MAP_SHARED,nFd,0);
	if(pszMapped == MAP_FAILED)
	{
		close(nFd);
		return	0;
	}

	//メイン処理
	{
		Bytef*	pcbCompData;
		long	nCompLen;

		if(bCompress)
		{
			//圧縮処理

			nCompLen = nFileSize * 1.1 + 13;
			pcbCompData = (Bytef*)malloc(nCompLen);

			compress(pcbCompData,&nCompLen,pszMapped,nFileSize);
		}
		else
		{
			//解凍処理

			//元サイズ取得&バッファ確保
			nCompLen = *(long*)pszMapped;
			pcbCompData = (Bytef*)malloc(nCompLen);

			char*	pTmp;

			//サイズが保存されていた分だけポインタを進めて解凍処理
			pTmp = pszMapped;
			pTmp += sizeof(nFileSize);

			uncompress(pcbCompData,&nCompLen,pTmp,nFileSize - sizeof(nFileSize));
		}

		{
			FILE	*fp;
			fp = fopen(pszDstFile,"w");
			if(fp)
			{
				if(bCompress)
				{
					//圧縮時は圧縮前のサイズを保存
					fwrite(&nFileSize,sizeof(nFileSize),1,fp);
				}

				fwrite(pcbCompData,1,nCompLen,fp);
				fclose(fp);
			}
		}

		free(pcbCompData);
	}

	//メモリマップドファイル開放
	munmap(pszMapped,nFileSize);

	close(nFd);

	return	1;
}

jstring
Java_com_Test106_Test106Act_stringFromJNI(JNIEnv* env,jobject thiz)
{
	//テスト用ファイル作成
	FILE	*fp;
	fp = fopen("/sdcard/test.txt","w");
	if(fp)
	{
		fputs("--------------------------------------------------",fp);
		fputs("----------this is compress data-------------------",fp);
		fputs("--------------------------------------------------",fp);
		fputs("Test line.Test line.Test line.Test line.Test line.",fp);
		fputs("Test line.Test line.Test line.Test line.Test line.",fp);
		fputs("Test line.Test line.Test line.Test line.Test line.",fp);
		fputs("--------------------------------------------------",fp);
		fclose(fp);
	}

	//圧縮
	CompressTest(1,"/sdcard/test.cmp","/sdcard/test.txt");

	//解凍
	CompressTest(0,"/sdcard/decomp.txt","/sdcard/test.cmp");

	return (*env)->NewStringUTF(env, "Test106text");
}

test106_09.png さらにAndroid.mk内でzlibが利用されるようにする。
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := Test106jni
LOCAL_SRC_FILES := Test106jni.c

LOCAL_STATIC_LIBRARIES := libzip

LOCAL_C_INCLUDES += $(call my-dir)/../zlib

include $(BUILD_SHARED_LIBRARY)


test106_10.png
以上の操作でプロジェクトの設定が終わった。ndk-buildによりネイティブコードをビルドする。

test106_11.png
そしてAndroidプロジェクトを実行する。

test106_12.png
これでAndroidエミュレーター内で実行された。

test106_13.png
EclipseのDDMSから仮想SDカード内を見ると、圧縮前のtest.txt、圧縮したtest.cmp、解凍したdecomp.txtの3ファイルを確認できた。



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

Android NDKの「bitmap-plasma」のソースコードを見る

plasma01.png
今回はAndroid NDKに含まれるサンプルプロジェクト「bitmap-plasma」を見てみる。このプロジェクトはネイティブコードからの画面描画を行うサンプルだ。

EclipseでAndroidプロジェクトを作成する。その際に、「Create project from existing source」をチェックして、Android NDKのサンプルフォルダにある「bitmap-plasma」を指定する。

plasma02.png
これでEclipseに「bitmap-plasma」プロジェクトが取り込まれた。

plasma03.png
ネイティブコードのビルドにわざわざCygwinを開くのは面倒なので、プロジェクトフォルダ内にEasyProjectGeneratorにあるビルド用のバッチファイルをコピーしておく。

plasma04.png
jniフォルダ内にApplication.mkというmakeファイルがある。これは不要っぽいので削除した。(本当は必要なのかもしれませんが、、、)

plasma05.png
そしてndk-buildによりネイティブコードをビルドする。

plasma06.png
最後にEclipseでプロジェクトを実行する。

plasma07.png
これでAndroidエミュレーターでサンプルプロジェクトが実行された。







plasma08.png
サンプルソースコードを少し見てみる。Plasma.javaのOnDraw()内でネイティブの描画処理が呼ばれている。また描画後すぐにinvalidate()が呼ばれるために描画領域が無効化されて再描画していた。

plasma09.png
次にネイティブコードのplasma.cを見てみる。メイン処理の中では、javaから渡された引数をAndroidBitmap_lockPixels()によりメモリとして取得、AndroidBitmap_unlockPixels()により開放、その間に描画がされていた。

plasma10.png
さらに見ると、AndroidBitmap_lockPixels()により取得したメモリには、x方向のピクセルデータ1列をfor()によりy軸方向に回していた。別に疑っていた訳ではないが、本当にベタなピクセルデータとしてネイティブ側からアクセスできるようだ。


Androidネイティブによる描画処理をする

test107_01.png
前回はAndroid NDKに含まれるネイティブ描画のサンプルプロジェクト「bitmap-plasma」を見た。今回はそのプロジェクトを参考にネイティブ描画処理部分のみを抜き出した雛形プロジェクトを作る。

EasyProjectGenerator for Android Ver 1.00を利用してAndroid NDKプロジェクトを生成してEclipseに取り込む。

test107_02.png まずはjavaのソースコードを編集する。新たにNativeViewというビュークラスを作り、そこでネイティブ描画処理が呼ばれるようにした。
package com.Test107;

import android.app.Activity;
import android.os.Bundle;
import android.content.Context;
import android.view.View;
import android.graphics.Bitmap;
import android.graphics.Canvas;

public class Test107Act extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(new NativeView(this));
    }

    static {
        System.loadLibrary("Test107jni");
    }
}

class NativeView extends View
{
    private Bitmap mBitmap;

    private static native void RenderBitmap(Bitmap bitmap);

    public NativeView(Context context)
    {
        super(context);

        final int W = 200;
        final int H = 200;

        mBitmap = Bitmap.createBitmap(W, H, Bitmap.Config.RGB_565);
    }

    @Override protected void onDraw(Canvas canvas)
    {
    	RenderBitmap(mBitmap);
        canvas.drawBitmap(mBitmap, 0, 0, null);
    }
}

test107_03.png 次にネイティブ描画処理を実装する。今回はグラデーションが描画した。
#include <jni.h>
#include <android/bitmap.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 void DrawBitmap(AndroidBitmapInfo* pBitmapInfo, void* pPixels)
{
    int		yy;

    for(yy = 0; yy < pBitmapInfo->height; yy++)
    {
        int		xx;
        uint16_t*  pLine = (uint16_t*)pPixels;

        for(xx = 0; xx < pBitmapInfo->width; xx++)
        {
        	pLine[xx] = make565(xx % 256,yy % 256,0);
        }

        // go to next line
        pPixels = (char*)pPixels + pBitmapInfo->stride;
    }
}




JNIEXPORT void JNICALL Java_com_Test107_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);

    AndroidBitmap_unlockPixels(env, bitmap);
}

test107_04.png Android.mkファイルも編集しておく。今回はログ出力していないので-llogは除外するしても構わない。
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := Test107jni
LOCAL_SRC_FILES := Test107jni.c
LOCAL_LDLIBS    := -lm -llog -ljnigraphics

include $(BUILD_SHARED_LIBRARY)

test107_05.png
ソースコードの編集が終わったらファイルを保存してndk-buildによりビルドする。

test107_06.png
そしてAndroidエミュレーターで実行するとネイティブの描画処理によるグラデーションが表示された。

※実際にはネイティブ側ではビットマップ処理のみ。描画はjava側で行っています、、、




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

EasyProjectGenerator for Android

「EasyProjectGenerator for Android」はAndroid用のプロジェクトを自動生成するフリーウエアです。

プロジェクト種類とプロジェクト名を指定するだけで簡単にAndroidアプリ(含ネイティブアプリ)用のプロジェクトを自動生成できます。

具体的な利用方法はこちらのエントリーを参照してください。


EasyProjectGenerator for Android Ver 1.04
EasyProjectGenerator for Android Ver 1.03
EasyProjectGenerator for Android Ver 1.02
EasyProjectGenerator for Android Ver 1.01
EasyProjectGenerator for Android Ver 1.00


epg11.png
「HelloJNI」タイプの自動生成されたプロジェクト実行画面。

EasyProjectGenerator for Android Ver 1.00から対応。

test107_06.png
「NativeView」タイプの自動生成されたプロジェクト実行画面。

EasyProjectGenerator for Android Ver 1.01から対応。

simplewidget.png
「SimpleWidget」タイプの自動生成されたプロジェクトをウィジットとして配置した画面。
アイコンとテキストがウィジットとして表示される。

EasyProjectGenerator for Android Ver 1.04から対応。

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 NDKが標準で備えるzlibを利用する

test109_01.png
自分でビルドしたライブラリファイルをプロジェクトで利用するための方法を探している過程で、Android NDKのフォルダ内にzlibのヘッダーファイルを見つけました。場所は「C:\android-ndk\platforms\android-9\arch-arm\usr\include\」です。以前、「Android NDK用にzlibをビルドして利用する」や「Android NDK用にlibpngをビルドして利用する」で自分でzlibのソースコードをビルド/利用しましたが、無駄な行為だったようです。
今回はこの標準zlibを使った圧縮/解凍処理をします。

test109_02.png まずEasyProjectGenerator for AndroidのHelloJni.batでプロジェクトを作成します。そして含まれるAndroid.mkに次の1行を追加します。このlibzというのが標準のzlibでした。
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := Test109jni
LOCAL_SRC_FILES := Test109jni.c
LOCAL_LDLIBS    := -lz

include $(BUILD_SHARED_LIBRARY)

test109_03.png そしてメイン処理を実装します。基本的に「Android NDK用にzlibをビルドして利用する」のときとまったく同じです。
#include <string.h>
#include <jni.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <zlib.h>


int CompressTest(int bCompress,const char* pszDstFile,const char* pszSrcFile)
{
	int		ret;
	int		nFd;
	char*	pszMapped;
	long	nFileSize;

	//処理対象ファイルを開く
	nFd = open(pszSrcFile,O_RDONLY);
	if(nFd < 0)
		return	0;

	//処理対象ファイルのサイズ取得
	{
		struct stat	sFileStat;

		ret = fstat(nFd,&sFileStat);
		if(ret < 0)
		{
			close(nFd);
			return	0;
		}
		nFileSize = sFileStat.st_size;
	}

	//処理対象ファイルをメモリマップドファイルにする
	pszMapped = (char*)mmap(NULL,nFileSize,PROT_READ,MAP_SHARED,nFd,0);
	if(pszMapped == MAP_FAILED)
	{
		close(nFd);
		return	0;
	}

	//メイン処理
	{
		Bytef*	pcbCompData;
		long	nCompLen;

		if(bCompress)
		{
			//圧縮処理

			nCompLen = nFileSize * 1.1 + 13;
			pcbCompData = (Bytef*)malloc(nCompLen);

			compress(pcbCompData,&nCompLen,pszMapped,nFileSize);
		}
		else
		{
			//解凍処理

			//元サイズ取得&バッファ確保
			nCompLen = *(long*)pszMapped;
			pcbCompData = (Bytef*)malloc(nCompLen);

			char*	pTmp;

			//サイズが保存されていた分だけポインタを進めて解凍処理
			pTmp = pszMapped;
			pTmp += sizeof(nFileSize);

			uncompress(pcbCompData,&nCompLen,pTmp,nFileSize - sizeof(nFileSize));
		}

		{
			FILE	*fp;
			fp = fopen(pszDstFile,"w");
			if(fp)
			{
				if(bCompress)
				{
					//圧縮時は圧縮前のサイズを保存
					fwrite(&nFileSize,sizeof(nFileSize),1,fp);
				}

				fwrite(pcbCompData,1,nCompLen,fp);
				fclose(fp);
			}
		}

		free(pcbCompData);
	}

	//メモリマップドファイル開放
	munmap(pszMapped,nFileSize);

	close(nFd);

	return	1;
}


jstring
Java_com_Test109_Test109Act_stringFromJNI(JNIEnv* env,jobject thiz)
{
	//テスト用ファイル作成
	FILE	*fp;
	fp = fopen("/sdcard/test.txt","w");
	if(fp)
	{
		fputs("--------------------------------------------------",fp);
		fputs("----------this is compress data-------------------",fp);
		fputs("--------------------------------------------------",fp);
		fputs("Test line.Test line.Test line.Test line.Test line.",fp);
		fputs("Test line.Test line.Test line.Test line.Test line.",fp);
		fputs("Test line.Test line.Test line.Test line.Test line.",fp);
		fputs("--------------------------------------------------",fp);
		fclose(fp);
	}

	//圧縮
	CompressTest(1,"/sdcard/test.cmp","/sdcard/test.txt");

	//解凍
	CompressTest(0,"/sdcard/decomp.txt","/sdcard/test.cmp");

	return (*env)->NewStringUTF(env, "Test109text");
}

test109_04.png
これで実行すると仮想SDカード上でデータの圧縮/解凍が確認できました。




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

2010年12月29日

libpngをAndroid NDKの標準ライブラリに登録する

test110_01.png
以前扱った「Android NDK用にlibpngをビルドして利用する」では、libpngをそのソースコードからビルドして利用した。毎回libpngをビルドするのは面倒なので、そのときにビルドしてできたバイナリファイルを、Android NDKの標準ライブラリとして登録してしまう。

まずはlibpngのソースコードに含まれていたpng.hとpngconf.hを"C:\android-ndk\platforms\android-xxx\arch-arm\usr\include\"へコピーする。

test110_02.png
次に「Android NDK用にlibpngをビルドして利用する」のプロジェクトをビルドして得られたlibpng.aを"C:\android-ndk\platforms\android-xxx\arch-arm\usr\lib\"へコピーする。これでAndroid NDK標準ライブラリへの登録作業は終りだ。

libpngでは内部でzlibを利用しているが、zlibはAndroid NDKの標準ライブラリとして組み込み済みなためコピーする必要はない。



test110_03.png 次に実際に使ってみる。標準ライブラリとして登録したlibpngを利用するときは、Android.mkのLOCAL_LDLIBSに「-lz -lpng」を指定する。
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := Test110jni
LOCAL_SRC_FILES := Test110jni.c
LOCAL_LDLIBS    := -lz -lpng -llog -ljnigraphics

include $(BUILD_SHARED_LIBRARY)

test110_04.png 描画コードは「Android NDK用にlibpngをビルドして利用する」と同じものを利用した。
#include <jni.h>
#include <android/bitmap.h>
#include <stdlib.h>
#include <png.h>

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


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

test110_05.png
これで仮想SDカードに保存したPNG画像を表示できた。

本当はAndroid NDKの標準ライブラリとしての登録は避けて、LOCAL_PREBUILT_LIBSを利用したmakefileで済ませたかったのですが、方法が分かりませんでした。今回の方法はかなり苦肉の策です。




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

前の10件 1  2  3





usefullcode@gmail.com

About 2010年12月

2010年12月にブログ「UsefullCode.net」に投稿されたすべてのエントリーです。

前の記事は2010年05月です。

次の記事は2011年01月です。

他にも多くのエントリーがあります。メインページ記事一覧も見てください。