前の10件 5  6  7  8  9  10  11  12  13  14  15

記事一覧

2011年01月16日

Androidのウィジットを作る

test115_01.png
今回は簡単なAndroidウィジットを作成する。EasyProjectGenerator for Android Ver1.04以降を実行する。そして言語「Java」、プロジェクト種類「SimpleWidget」としてプロジェクトを自動生成する。

test115_02.png
これでウィジット用のプロジェクトができた。主なファイルは、javaソースコード「MainWidgetProvider.java」、ウィジットのレイアウトxml「widget.xml」、ウィジットの挙動xml「widget_info.xml」と「AndroidManifest.xml」内でのウィジット登録になる。

test115_03.png
そのままウィジットをAndroidエミュレーターに送り、実際に配置してみる。Eclipseでパッケージをエミュレーターにインストールする。

test115_04.png
そしてエミュレーターの「MENU」ボタンを押す。

test115_05.png
「Add」ボタンを押して、、、

test115_06.png
「Widgets」を選択する。

test115_07.png
そして一覧からインストールした「Test115」ウィジットを選ぶ。

test115_08.png
これでホーム画面に作成したウィジットを配置できた。

test115_09.png
不要になったらウィジットを長押ししてからゴミ箱へドラッグすればいい。






test115_11.png
実際のウィジットのソースコードを見る。まずはjavaで書かれたメイン処理。

ウィジットはAppWidgetProvider派生で作る。そしてonEnabled()、onUpdate()、onDeleted()、onReceive()に処理を実装する。 今回はonUpdateのみ処理して、更新されたときにImageViewへアイコンが表示されるようにした。
package com.Test115;

import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;


public class MainWidgetProvider extends AppWidgetProvider
{
	@Override
	public void onEnabled(Context context)
	{
		super.onEnabled(context);
	}


	@Override
	public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
	{
		//super.onUpdate(context,appWidgetManager,appWidgetIds);

		for(int nWidgetId : appWidgetIds)
		{
			RemoteViews remoteView = new RemoteViews(context.getPackageName(),R.layout.widget);

			remoteView.setImageViewResource(R.id.MainImageView,R.drawable.icon);
			appWidgetManager.updateAppWidget(nWidgetId,remoteView);
		}
	}


	@Override
	public void onDeleted(Context context, int[] appWidgetIds)
	{
		super.onDeleted(context,appWidgetIds);
	}


	@Override
	public void onReceive(Context context, Intent intent)
	{
		super.onReceive(context,intent);
	}
}

test115_12.png レイアウトは普通のアプリと同じようにXMLで指定できる。ただし利用できるView種は制限されていて、何でも使えるというわけではない。今回はImageViewとTextViewを配置した。
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:id="@+id/MainFrameLayout"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	
	<LinearLayout
		android:id="@+id/MainLinearLayout"
		android:layout_width="fill_parent"
		android:layout_height="fill_parent">
		<ImageView
			android:id="@+id/MainImageView"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"/>
		<TextView
			android:id="@+id/main_text"
			android:layout_width="fill_parent"
			android:layout_height="wrap_content"
			android:text="@string/app_name" />
	</LinearLayout>

</FrameLayout>

test115_13.png
ここではウィジットのサイズや更新間隔を指定する。

利用できるウィジットサイズは72/146/220/294/368dip...になる。 更新間隔は今回はゼロ=更新しないとした。この値の最小値は「1800000」(=30x60x1000msec=30分)となる。つまり時計の秒針や分針などはこの更新指定を使って描画できない。
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider 
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:initialLayout="@layout/widget"
	android:minWidth="146dip"
	android:minHeight="72dip"
	android:updatePeriodMillis="0">
	<!-- width/height set to ((nCells x 74) - 2)dip  -->
		<!-- 72dip, 146dip, 220dip, 294dip, 368dip... -->
</appwidget-provider>

test115_14.png 「AndroidManifest.xml」には、これまではアクティビティを登録していたが、今回はアクティビティはない。代わりにウィジットを登録する。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
	package="com.Test115"
	android:versionCode="1"
	android:versionName="1.0">

	<application android:icon="@drawable/icon" android:label="@string/app_name">

		<receiver android:name="MainWidgetProvider">
			<meta-data
				android:name="android.appwidget.provider"
				android:resource="@xml/widget_info" />
			<intent-filter>
				<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
			</intent-filter>
		</receiver>

	</application>

</manifest>


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

2011年01月15日

Androidでカメラのシャッター音を消したアプリの作り方のメモ

Androidでカメラのシャッター音を消したアプリの作り方のメモです。

カメラアプリを作る際に、
	private ShutterCallback _fnShutterCallback = new ShutterCallback();

	private final class ShutterCallback implements android.hardware.Camera.ShutterCallback
	{
		public void onShutter()
		{
				Log.i("camera", "onShutter");
				if (_cMediaShutterSound != null)
				{
//					_cMediaShutterSound.start();
				}
		}
	};
というように、シャッター音がならないコールバック関数を用意して撮影時に、
	camera.takePicture(_fnShutterCallback,_fnRawPictureCallback,new JpegPictureCallback());
というようにシャッターコールバックを呼べばシャッター音が無音のカメラアプリが作れるはず、、、なのに、実際にはシャッター音が鳴る。しかもコメント部分をはずせばシャッター音が二重に鳴るというおまけ付き。

Android OSのソースコードをざっと調べてみると、
source/frameworks/base/camera/libcameraservice/CameraService.cpp
の中に実装されているシャッターハンドラーが

     // snapshot taken
     void CameraService::Client::handleShutter(
         image_rect_type *size // The width and height of yuv picture for
                               // registerBuffer. If this is NULL, use the picture
                               // size from parameters.
     )
     {
         // Play shutter sound.
         if (mMediaPlayerClick.get() != NULL) {
             // do not play shutter sound if stream volume is 0
             // (typically because ringer mode is silent).
             int index;
             AudioSystem::getStreamVolumeIndex(AudioSystem::ENFORCED_AUDIBLE, &index);
             if (index != 0) {
                 mMediaPlayerClick->seekTo(0);
                 mMediaPlayerClick->start();
             }
         }
     

         //以下省略
というようになっていて、強制的に音がようになっている。シャッター音を消すオプションは存在せず、mMediaPlayerClickが失敗した場合のみ無音になる。


以上の理由から「シャッター音が鳴らないAndroidアプリは作れない」、、、はずだが実際には数多くの無音カメラアプリがリリースされているためできるはず。


というわけで、カメラのシャッター音を消す方法は、、、

■CameraService.cppで参照されているシャッター音のリソースファイルを無音にする

■シャッター音が鳴ってしまうcamera.takePicture()を使わずに撮影する

■ソースコードのシャッター音を鳴らす部分をコメントアウトしたlibcameraserviceをビルドして使う

■/dev/video1や/dev/msm_cameraなどのカメラデバイスを直接制御して撮影する

という4方法をすぐに思いつく。


「CameraService.cppで参照されているシャッター音のリソースファイルを無音にする」は、シャッター音の元ファイル"/system/media/audio/ui/camera_click.ogg"を無音のものに置き換えるというもの。
検索すれば情報もたくさんあるので簡単にシャッター音を無音にできる。ただしroot権限が必要になるため汎用的ではない。


「シャッター音が鳴ってしまうcamera.takePicture()を使わずに撮影する」は、例えばプレビュー用に生成された画像イメージを取得してそのまま撮影データとして利用するというもの。
簡単に実装できて、汎用的なシャッター音を消したカメラアプリを作成できる。この方法ではcamera.takePicture()を使った撮影とは異なり、ラグがなくプレビューを見たまま撮影できるためサクサク撮影可能というメリットがある。ただしプレビュー画像はcamera.takePicture()での画像よりも解像度が低いため、高解像度で撮影したい場合はこの方法は利用できない。


「ソースコードのシャッター音を鳴らす部分をコメントアウトしたlibcameraserviceをビルドして使う」は、Androidのソースコードをほぼそのまま利用するもの。
実際に試していないので、本当に無音化できるかどうかは不明。しかし、おそらくリリースされているカメラアプリはこの方法を利用して無音化しているはず。


「/dev/video1や/dev/msm_cameraなどのカメラデバイスを直接制御して撮影する」は、Androidという枠から抜け出てLinuxとしてカメラを扱うというもの。
試していないので、本当に直接制御できるのかは不明。また、デバイスに直接アクセスするため機種依存性が強くなるというデメリットもある。NDKが標準でlinux/videodev2.hを備えているなど、ハードルが高いようで低いように思える。
、、、ものの単純に「int fd = open("/dev/msm_camera",O_RDWR,0);」としてもデバイスが開けず難航しそうな雰囲気。/dev/msm_cameraがAndroid側で利用中なため開放作業やパーミッション変更などの前処理が必要になるのかな?デバイスが開けたら後は簡単にできそう。この方法はAndroid OSが用意しているカメラAPIとは異なり自由度が高い。そのため今後なんとかして動かせるようにしたい。現状ではAndroidもLinuxも知識がゼロに等しいので歯が立ちません。


2010年12月31日

EasyProjectGenerator for Androd Ver1.03

epg103_01.png
Android SDK/NDK用の雛形プロジェクトを簡単に自動生成できる「EasyProjectGenerator for Android」の新バージョンをアップしました。新たにC++やSTL、ログ出力機能やJavaの雛形プロジェクト生成に対応します。それに伴い前バージョンとは使い方が変わりました。

起動バッチファイルは「start_wizard.bat」に統合しています。



ダウンロードページ

epg103_02.png
「start_wizard.bat」を実行すると、最初に生成するプロジェクトの名前の入力が求められます。アルファベットと数字のみのプロジェクト名を指定してください。

epg103_03.png
次に開発言語選択(C++、C、Java)、雛形となるプロジェクト選択(HelloJNI、NativeView、Simple)などウイザードに沿って指定します。これらはそのまま[Enter]キーでデフォルトの値を使うこともできます。

epg103_04.png
バッチファイルが終わると、「EasyProjectGenerator for Android」のインストールフォルダ内にプロジェクト名のフォルダができます。これが自動生成されたプロジェクトのフォルダです。好きな場所に移動します。

epg103_05.png
そしてEclipseを起動して、「File」メニューにある「New」から「Android Project」を選択。

epg103_06.png
「Create project from existing source」にチェックを入れて、先ほど作成したプロジェクトのフォルダを指定して取り込みます。

epg103_07.png
これでEclipseからプロジェクトが使えるようになりました。

epg103_08.png
ネイティブコードのビルドは、プロジェクトフォルダ内にあるバッチファイル「_build_jni.bat」のダブルクリックで行えます。

epg103_09.png
これで作成された雛形プロジェクトを実行できました。





「EasyProjectGenerator for Android」のダウンロードページ

2010年12月30日

Android NDKが標準で備えるSTLを利用する

test114_01.png
Android NDKには標準でStandard Template Library「STL」が用意されている(ただし2010年末現在β実装)。今回はそれを使ってSTLプログラミングをしみる。
まずネイティブのソースコードをC++にするため拡張子をcppに変更する。

test114_02.png そしてSTLを利用したネイティブコードを実装する。
#include <string.h>
#include <jni.h>

#include <vector>
#include <string>
#include <iterator>
#include <algorithm>


extern "C" jstring
Java_com_Test114_Test114Act_stringFromJNI(JNIEnv* env,jobject thiz)
{
	std::vector<std::string>  vstrTest;

	//ランダムに追加
	vstrTest.push_back("A");
	vstrTest.push_back("D");
	vstrTest.push_back("C");
	vstrTest.push_back("B");

	//ソート
	std::sort(vstrTest.begin(),vstrTest.end());

	//結果の結合
	std::string		strOut;
	{
		std::vector<std::string>::iterator	ivnPos;

		for(ivnPos = vstrTest.begin(); ivnPos != vstrTest.end(); ivnPos++ )
			strOut += *ivnPos;
	}

	//出力
	return env->NewStringUTF(strOut.c_str());
}

test114_03.png 次にAndroid.mkを編集する。STL向けの設定項目はなく、単にソースファイルをcppに変えるだけだ。
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := Test114jni
LOCAL_SRC_FILES := Test114jni.cpp
LOCAL_LDLIBS    := -llog

include $(BUILD_SHARED_LIBRARY)

test114_04.png
このままではAndroid NDKはSTLを認識しない。STLを使えるようにするためにはApplication.mkファイルを用意する。jniフォルダを選択して右クリックして現れるメニューの「New」にある「File」を選択する。

test114_05.png
そしてApplication.mkファイルを作成する。

test114_06.png 「APP_STL := stlport_static」の1行を追加する。これがAndroid NDKでSTLを使うための設定だ。今回はstaticライブラリとした。
APP_STL := stlport_static

test114_07.png
これでビルド/実行すると、STLによってソートされた結果が表示された。






test114_08.png メイン環境では上のApplication.mkで正常にビルドが終わったのだが、Virtual PC上の開発環境では下のようなエラーが出て失敗しました。どこかAndroid NDKのパス設定がうまくいっていないようで、ビルド済みのlibstlport_static.aが見つかっていないようです。
C:\Users\assist\Desktop\EasyProjectGenerator\Test114>bash -i -c "ndk-build"
Compile++ thumb  : Test114jni <= Test114jni.cpp
StaticLibrary  : libstdc++.a
Prebuilt       : libstlport_static.a <= <NDK>/sources/cxx-stl/stlport/libs/armea
bi/
SharedLibrary  : libTest114jni.so
C:/android-ndk/toolchains/arm-linux-androideabi-4.4.3/prebuilt/windows/bin/../lib/gcc/arm-linux-androideabi/4.4.3/../../../../arm-linux-androideabi/bin/ld.exe:
C:/Users/assist/Desktop/EasyProjectGenerator/Test114/obj/local/armeabi/libstlport_static.a: No such file: Permission denied
collect2: ld returned 1 exit status
make: *** [/cygdrive/c/Users/assist/Desktop/EasyProjectGenerator/Test114/obj/local/armeabi/libTest114jni.so] Error 1

C:\Users\assist\Desktop\EasyProjectGenerator\Test114>pause
続行するには何かキーを押してください . . .

test114_09.png そんなときは、Application.mkに「STLPORT_FORCE_REBUILD := true」を追加すると、Android NDKに含まれるSTLのソースファイル一式が強制ビルドされてプロジェクトフォルダ内にlibstlport_static.aが生成、利用されるようになります。
APP_STL := stlport_static
STLPORT_FORCE_REBUILD := true

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

Android NDKでC++を利用する

Test113_01.png
これまでAndroidのネイティブコードをC言語で扱った。しかしC言語だとクラスは使えないし、メモリ確保も面倒なのでこれからはC++を利用する。まずソースコードの拡張子をcppに変更する。

Test113_02.png AndroidでJavaとネイティブの橋渡しをするJNIの記述がC言語とC++とでは異なる。コメントアウトしたのがC言語での書き方。C++の方がスマートだ。
#include <string.h>
#include <jni.h>
#include <android/log.h>


class CTest113
{
public:
	CTest113()
	{
		__android_log_write(ANDROID_LOG_DEBUG,"Test113","CTest113::CTest113()");
	}

	~CTest113()
	{
		__android_log_write(ANDROID_LOG_DEBUG,"Test113","CTest113::~CTest113()");
	}

	void	TestFunc()
	{
		__android_log_write(ANDROID_LOG_DEBUG,"Test113","CTest113::TestFunc()");
	}
};

extern "C" jstring
Java_com_Test113_Test113Act_stringFromJNI(JNIEnv* env,jobject thiz)
{
	CTest113*	pcTest;

	pcTest = new CTest113;
	pcTest->TestFunc();
	delete	pcTest;

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

Test113_03.png Android.mkでC言語からC++に変更するのはソースファイル指定の拡張子を変えるだけだ。今回はログ出力を利用するので-llogも追加した。
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := Test113jni
LOCAL_SRC_FILES := Test113jni.cpp
LOCAL_LDLIBS    := -llog

include $(BUILD_SHARED_LIBRARY)

Test113_04.png
これで実行するとC++のクラスが生成、破棄されてそれがログに出力されたことが、DDMSのLogCatで確認できた。

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

2010年12月29日

Android SDK/NDKのログ出力機能を使う

test112_01.png
Android SDK/NDKには開発者向けにログ出力機能が備わっている。今回はそれを試しに使ってみる。

test112_02.png
javaではandroid.util.LogをインポートすればLog.v()、Log.d()、Log.i()、Log.w()、Log.e()関数でログ出力できる。

Android SDK/NDKではリリースビルド/デバッグビルドという概念がない?ため、一般公開した後もログ出力機能が有効なままになってしまう。リリースビルド時などログ出力が不要なときに簡単に機能を削除できるように、LOCAL_LOGやLOCAL_LOGDを定義、それを使って手動制御した方がいい。
package com.Test112;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
import android.util.Log;

public class Test112Act extends Activity
{
	//ログを出力するならtrue、出力しないならfalse
	//リリースビルドではfalseにする
	final boolean LOCAL_LOGD = true;
	
	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		TextView	tv = new TextView(this);
		tv.setText( stringFromJNI() );
		setContentView(tv);
		
		//5種類のログを出力
		if(LOCAL_LOGD)
		{
			Log.v("Tag","Test Message : Log.v() for VERBOSE");
			Log.d("Tag","Test Message : Log.d() for DEBUG");
			Log.i("Tag","Test Message : Log.i() for INFO");
			Log.w("Tag","Test Message : Log.w() for WARN");
			Log.e("Tag","Test Message : Log.e() for ERROR");
		}
	}

	public native String  stringFromJNI();

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

test112_03.png ネイティブコードでログ出力機能を利用する場合は、Android.mkで「LOCAL_LDLIBS」に「-llog」のリンク指定を追加する。
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := Test112jni
LOCAL_SRC_FILES := Test112jni.c
LOCAL_LDLIBS    := -llog

include $(BUILD_SHARED_LIBRARY)

test112_04.png
実際の出力は__android_log_write()や__android_log_print()で行う。__android_log_write()は固定メッセージをログ出力、__android_log_print()はprintfのように書式化して出力できる。

ログの種別はANDROID_LOG_VERBOSE、ANDROID_LOG_DEBUG、ANDROID_LOG_INFO、ANDROID_LOG_WARN、ANDROID_LOG_ERROR、ANDROID_LOG_FATALなどの定数が利用できる。
//ログを出力する。ログ出力しないなら次の1行をコメントアウトすること
#define	LOCAL_LOG

#include <string.h>
#include <jni.h>

#ifdef	LOCAL_LOG
#include <android/log.h>
#endif

jstring
Java_com_Test112_Test112Act_stringFromJNI(JNIEnv* env,jobject thiz)
{
#ifdef	LOCAL_LOG
	//ログを出力
	__android_log_write(ANDROID_LOG_DEBUG,"Tag","Test Message : __android_log_write() with ANDROID_LOG_DEBUG");
	__android_log_print(ANDROID_LOG_DEBUG,"Tag","Test Message : __android_log_print(%s) with ANDROID_LOG_DEBUG","test string");

//	int	nTest = 123;
//	__android_log_assert("nTest == 123","Tag","Test Message : __android_log_assert(nTest == 123) with nTest=%d",nTest);
//	__android_log_assert("nTest != 123","Tag","Test Message : __android_log_assert(nTest != 123) with nTest=%d",nTest);
#endif

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

test112_05.png
ログ出力を実装して実行した。実行時の見た目は実装前と変わらない。

test112_06.png
出力されたログを見るには、Eclipseの「DDMS」画面の「LogCat」を利用する。

test112_07.png
ここにログ出力日時とログ出力関数を呼び出したときの引数が表示される。

test112_08.png
ログには種別があり、上部のボタンで表示レベルを切り換えれる。

VERBOSEでは全て表示、DEBUGではVERBOSE以外を表示、INFOではVERBOSEとDEBUG以外を表示、、、とV>D>I>W>Eの順番で表示されるログ種別が多い。

test112_09.png
フィルター機能も備え、タグなどを指定して絞り込み表示できる。

test112_10.png
絞り込みタブを削除するには「Delete Filter」ボタンを使う




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

Androidで画面のタッチ操作を検出する

test111_01.png
今回は画面のタッチ操作の検出方法を見てみる。
EasyProjectGenerator for AndroidでNativeViewプロジェクトを生成して、Eclipseに読み込む。そしてjavaのソースコードを編集する。

test111_02.png
Viewに対して、setOnTouchListner()によりOnTouchListerner()のOnTouch()を指定する。Javaのソースを本格的?にいじるのはこれが初めて。処理の流れはJavascript、文法方法はC++という雰囲気です。

タッチ開始、ドラッグ中、タッチ終了などのイベントはevent.getAction()、タッチ座標は戻り値がfloatというのが意味不明がevent.getX()とevent.getY()で取得できるようです。
package com.Test111;

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


public class Test111Act 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("Test111jni");
	}
}


class NativeView extends View
{
	private Bitmap _mBitmap;

	private static native void RenderBitmap(Bitmap bitmap);


	private	boolean	_bTouchDown;
	private	float	_fTouchX;
	private	float	_fTouchY;
	private	float	_fScrollX;
	private	float	_fScrollY;


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

		_bTouchDown	= false;
		_fTouchX	= 0.0f;
		_fTouchY	= 0.0f;
		_fScrollX	= 0.0f;
		_fScrollY	= 0.0f;

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

		_mBitmap = Bitmap.createBitmap(W, H, Bitmap.Config.RGB_565);
		
		this.setOnTouchListener(new OnTouchListener()
		{
			@Override
			public boolean onTouch(View v, MotionEvent event)
			{
				float	fTouchX			= event.getX();
				float	fTouchY			= event.getY();
//				float	fTouchSize		= event.getSize();
//				float	fTouchPressure	= event.getPressure();

				switch (event.getAction() & MotionEvent.ACTION_MASK)
				{
				case MotionEvent.ACTION_DOWN:
					_bTouchDown = true;
					_fTouchX = fTouchX;
					_fTouchY = fTouchY;
					break;

				case MotionEvent.ACTION_UP:
				case MotionEvent.ACTION_POINTER_UP:
					_bTouchDown = false;
					//break;			//break処理せずにinvalidate()まで実行する

				case MotionEvent.ACTION_MOVE:
					_fScrollX -= _fTouchX - event.getX();
					_fScrollY -= _fTouchY - event.getY();
					_fTouchX = fTouchX;
					_fTouchY = fTouchY;
					v.invalidate();
					break;
				}

				return true;
			}
		});
	}


	@Override protected void onDraw(Canvas canvas)
	{
		RenderBitmap(_mBitmap);

		int		nWidth	= _mBitmap.getWidth();
		int		nHeight	= _mBitmap.getHeight();

		Rect rcSrc = new Rect(0,0,nWidth, nHeight);
		Rect rcDst = new Rect((int)_fScrollX,(int)_fScrollY,(int)_fScrollX + nWidth,(int)_fScrollY + nHeight);
		canvas.drawBitmap(_mBitmap,rcSrc,rcDst,null);
	}
}

test111_03.png
これで忘れずにネイティブコードをndk-buildでビルドしてから実行すると、、、画面のドラッグでグラデーション描画領域を好きなように移動できるようになりました。




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

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で済ませたかったのですが、方法が分かりませんでした。今回の方法はかなり苦肉の策です。




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

2010年12月28日

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カード上でデータの圧縮/解凍が確認できました。




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

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画像を表示できた。



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

前の10件 5  6  7  8  9  10  11  12  13  14  15