Android NDK(JNI - Java Native Interface)による処理の高速化
Android NDK(JNI - Java Native Interface)による処理の高速化に挑戦。
ここではモザイク処理と魚眼レンズ風処理を対象に画像を加工。
まずは新規プロジェクトを作成します。
プロジェクト名 :「exampleAndroidNDK」と入力 ビルド・ターゲット:「Android2.2」にチェックをつける アプリケーション名:「example Android NDK」と入力 パッケージ名 :「com.example.nativeEffect」と入力 Create Activity : チェックボタンにチェックをつけて「nativeEffectActivity」と入力 Min SDK Version :「8」と入力
レイアウトの XML を以下のように編集します。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ImageView android:id="@+id/imageview_id" android:layout_width="fill_parent" android:layout_height="fill_parent" android:src="@drawable/original" /> </LinearLayout>
プログラム・ファイルを以下のように編集します。
package com.example.nativeEffect; import android.app.Activity; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.ImageView; public class nativeEffectActivity extends Activity implements OnClickListener { private int flg = 0; static { System.loadLibrary( "nativeEffect" ); } /** Called when the activity is first created. */ @Override public void onCreate( Bundle savedInstanceState ){ super.onCreate( savedInstanceState ); setContentView( R.layout.main ); ImageView imageView = (ImageView)findViewById( R.id.imageview_id ); imageView.setOnClickListener( this ); } public void onClick( View view ){ ImageView imageView = (ImageView)findViewById( R.id.imageview_id ); if( this.flg == 0 ){ this.flg = 1; imageView.setImageBitmap( this.effectPixelizationNative( BitmapFactory.decodeResource( getResources( ), R.drawable.original ).copy( Bitmap.Config.ARGB_8888, true ) ) ); } else if( this.flg == 1 ){ this.flg = 2; imageView.setImageBitmap( this.effectFisheyeNative( BitmapFactory.decodeResource( getResources( ), R.drawable.original ).copy( Bitmap.Config.ARGB_8888, true ) ) ); } else { this.flg = 0; imageView.setImageResource( R.drawable.original ); } } private native int nativeEffectPixelization( int [] pixels, int width, int height ); private Bitmap effectPixelizationNative( Bitmap bitmap ){ if( bitmap == null ){ bitmap = BitmapFactory.decodeResource( getResources( ), R.drawable.original ).copy( Bitmap.Config.ARGB_8888, true ); } if( bitmap == null ){ return bitmap; } if( bitmap.isMutable( ) != true ){ bitmap = bitmap.copy( Bitmap.Config.ARGB_8888, true ); } int height = bitmap.getHeight( ); int width = bitmap.getWidth( ); int[] pixels = new int[( width * height )]; bitmap.getPixels( pixels, 0, width, 0, 0, width, height ); int result = nativeEffectPixelization( pixels, width, height ); Log.d( "DEBUG", "result=" + result ); bitmap.setPixels( pixels, 0, width, 0, 0, width, height ); return bitmap; } private native int nativeEffectFisheye( int [] pixelsSrc, int [] pixelsDest, int width, int height ); private Bitmap effectFisheyeNative( Bitmap bitmap ){ if( bitmap == null ){ bitmap = BitmapFactory.decodeResource( getResources( ), R.drawable.original ).copy( Bitmap.Config.ARGB_8888, true ); } if( bitmap == null ){ return bitmap; } if( bitmap.isMutable( ) != true ){ bitmap = bitmap.copy( Bitmap.Config.ARGB_8888, true ); } int height = bitmap.getHeight( ); int width = bitmap.getWidth( ); int[] pixelsSrc = new int[( width * height )]; int[] pixelsDest = new int[( width * height )]; bitmap.getPixels( pixelsSrc, 0, width, 0, 0, width, height ); int result = nativeEffectFisheye( pixelsSrc, pixelsDest, width, height ); Log.d( "DEBUG", "result=" + result ); bitmap.setPixels( pixelsDest, 0, width, 0, 0, width, height ); return bitmap; } }
System.loadLibraryとnativeというキーワードがポイントです(ぉ
次からはコマンドラインによる作業です。
Windows環境だとCygwinを導入しないといけないようなので作業はIntel Macで行いました。
Cygwinを導入するくらいだったらVMware Playerを使ってLinuxを導入するよ。。。_| ̄|○
事前にAndroid NDKをダウンロードしておいてください。
自分の環境では/eclipse/android_ndk以下にファイル群を展開しているので適時読み替えてください。
まずはプロジェクトのルート・ディレクトリに移動してjniフォルダを作成します。
自分の環境では/eclipse/workspace/exampleAndroidNDK/がプロジェクトのルート・ディレクトリになっているので適時読み替えてください。
% cd /eclipse/workspace/exampleAndroidNDK/ % mkdir jni
次にヘッダ・ファイルを作成します。
以下のコマンドを実行すればクラス・ファイルから自動的に抽出してくれます。
% javah -classpath bin -o jni/nativeEffect.h com.example.nativeEffect.nativeEffectActivity
次にAndroid.mkとApplication.mkを以下のように作成します。
% cat jni/Android.mk LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := nativeEffect LOCAL_SRC_FILES := nativeEffect.c include $(BUILD_SHARED_LIBRARY) % cat jni/Application.mk APP_PLATFORM := android-8
次にプログラム・ファイルを以下のように作成します。
% cat jni/nativeEffect.c #include <math.h> #include "nativeEffect.h" JNIEXPORT jint JNICALL Java_com_example_nativeEffect_nativeEffectActivity_nativeEffectPixelization( JNIEnv *env, jobject thiz, jintArray colors, jint width, jint height ){ jint *pixels = (*env)->GetIntArrayElements( env, colors, 0 ); int i, j, ii, jj; int size = 10; for( i = 0; i < width; i += size ){ for( j = 0; j < height; j += size ){ int a, aa, r, rr, g, gg , b, bb, counts; a = aa = r = rr = g = gg = b = bb = counts = 0; for( ii = 0; ii < size; ++ii ){ for( jj = 0; jj < size; ++jj ){ if( ( i + ii ) < 0 || width <= ( i + ii ) || ( j + jj ) < 0 || height <= ( j + jj ) ){ continue; } aa = pixels[( ( i + ii ) + ( j + jj ) * width )] & 0xFF000000; rr = pixels[( ( i + ii ) + ( j + jj ) * width )] & 0x00FF0000; rr = rr >> 16; gg = pixels[( ( i + ii ) + ( j + jj ) * width )] & 0x0000FF00; gg = gg >> 8; bb = pixels[( ( i + ii ) + ( j + jj ) * width )] & 0x000000FF; a += aa; r += rr; g += gg; b += bb; counts++; } } aa = a / counts; rr = r / counts; gg = g / counts; bb = b / counts; for( ii = 0; ii < size; ++ii ){ for( jj = 0; jj < size; ++jj ){ if( ( i + ii ) < 0 || width <= ( i + ii ) || ( j + jj ) < 0 || height <= ( j + jj ) ){ continue; } pixels[( ( i + ii ) + ( j + jj ) * width )] = aa | ( rr << 16 ) | ( gg << 8 ) | bb; } } } } (*env)->ReleaseIntArrayElements( env, colors, pixels, 0 ); return 0; } JNIEXPORT jint JNICALL Java_com_example_nativeEffect_nativeEffectActivity_nativeEffectFisheye( JNIEnv *env, jobject thiz, jintArray colorsSrc, jintArray colorsDest, jint width, jint height ){ jint *pixelsSrc = (*env)->GetIntArrayElements( env, colorsSrc, 0 ); jint *pixelsDest = (*env)->GetIntArrayElements( env, colorsDest, 0 ); int XX, YY; int indexMax = ( width * ( height -1 ) + ( width - 1 ) ); int weight = 40; double radius; if( height < width ){ radius = width / 2; } else { radius = height / 2; } for( YY = 0; YY < height; ++YY ){ for( XX = 0; XX < width; ++XX ){ double rp = sqrt( (double)( pow( (double)( weight ), 2 ) + pow( (double)( XX - width / 2 ), 2 ) + pow( (double)( YY - height / 2 ), 2 ) ) ); int XXX = (int)( ( rp * ( XX - width / 2 ) ) / radius + width / 2 ); int YYY = (int)( ( rp * ( YY - height / 2 ) ) / radius + height / 2 ); int srcIndex1 = ( width * YYY + XXX ); int srcIndex2 = srcIndex1 + 1; int srcIndex3 = srcIndex1 + 2; int destIndex1 = ( width * YY + XX ); int destIndex2 = destIndex1 + 1; int destIndex3 = destIndex1 + 2; if( 0 <= XXX && XXX < width && 0 <= YYY && YYY < height ){ if( srcIndex1 <= indexMax && destIndex1 <= indexMax ){ pixelsDest[destIndex1] = pixelsSrc[srcIndex1]; } else if( destIndex1 <= indexMax ){ pixelsDest[destIndex1] = 255 | ( 0 << 16 ) | ( 0 << 8 ) | 0; } if( srcIndex2 <= indexMax && destIndex2 <= indexMax ){ pixelsDest[destIndex2] = pixelsSrc[srcIndex2]; } else if( destIndex2 <= indexMax ){ pixelsDest[destIndex2] = 255 | ( 0 << 16 ) | ( 0 << 8 ) | 0; } if( srcIndex3 <= indexMax && destIndex3 <= indexMax ){ pixelsDest[destIndex2] = pixelsSrc[srcIndex2]; } else if( destIndex3 <= indexMax ){ pixelsDest[destIndex3] = 255 | ( 0 << 16 ) | ( 0 << 8 ) | 0; } } else if( destIndex1 <= indexMax ){ pixelsDest[destIndex1] = 255 | ( 0 << 16 ) | ( 0 << 8 ) | 0; } } } (*env)->ReleaseIntArrayElements( env, colorsSrc, pixelsSrc, 0 ); (*env)->ReleaseIntArrayElements( env, colorsDest, pixelsDest, 0 ); return 0; }
次にndk-buildコマンドを実行してバイナリ・ファイルを作成します。
事前に各種コマンドへのパスを通しておいてください。
% cat ~/.bash_profile export PATH=$PATH:/eclipse/android_ndk:/eclipse/android_sdk/platform-tools:/eclipse/android_sdk/tools % ndk-build Compile thumb : nativeEffect <= nativeEffect.c SharedLibrary : libnativeEffect.so Install : libnativeEffect.so => libs/armeabi/libnativeEffect.so %
問題なければ上記のようなメッセージが表示されます。
最後にEclipse上からアプリケーションを実行します。
アプリケーション実行時にロード関係のエラーが出る場合はEclipseを一度終了してから確認してみてください。
リフレッシュの設定はやっているのですが、自分の環境だと上手く行かない場合が結構あったので。。。_| ̄|○
ただ、ここまでくると何がなんだか。。。という感じです。Android開発をやっているようで違うような、みたいな。
単なるC言語での開発ではなくてJNI固有のお作法なんかを学ばないといけないし、Eclipseとの連携も中途半端すぎるし、JITが効いたらソコソコ高速に動作するし、ここまでしてやる必要あるのかな?とか思う場合もあるので、適用する場合は充分に検討した上で判断してください。。。これで本当にネタ切れ。_| ̄|○