読者です 読者をやめる 読者になる 読者になる

ます’s Blog - どうでもいい記事100選

どうでもいい記事100選

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を一度終了してから確認してみてください。
リフレッシュの設定はやっているのですが、自分の環境だと上手く行かない場合が結構あったので。。。_| ̄|○


オリジナル:
f:id:masugata:20110405125239:image:medium
モザイク:
f:id:masugata:20110331141802:image:medium
魚眼レンズ風:
f:id:masugata:20110412162040:image:medium


ただ、ここまでくると何がなんだか。。。という感じです。Android開発をやっているようで違うような、みたいな。
単なるC言語での開発ではなくてJNI固有のお作法なんかを学ばないといけないし、Eclipseとの連携も中途半端すぎるし、JITが効いたらソコソコ高速に動作するし、ここまでしてやる必要あるのかな?とか思う場合もあるので、適用する場合は充分に検討した上で判断してください。。。これで本当にネタ切れ。_| ̄|○