Android NDK で既存の C コードを再利用する

Android Native Developer Kit の使い方を学ぶ

Android アプリケーション開発者の大半が使っている Android SDK (Software Developer Kit) では、Java プログラミング言語を使用しなければなりません。しかしその一方で、オンラインでは大量の C 言語のコードを入手することができます。Android NDK (Native Developer Kit) は、Android 開発者が既存の C ソース・コードを Android アプリケーションに組み込んで再利用するための開発キットです。このチュートリアルでは、C コードを使って基本的な画像処理操作を行う、Java プログラミング言語の画像処理アプリケーションを作成します。

Frank Ableson, Entrepreneur, Navitend

W. Frank Ableson は、妻の Nikki と子供たちと一緒にニュージャージー州北部に住む起業家です。モバイル・ソフトウェア、組み込み設計などを専門分野としています。彼は、『コードからわかる Android プログラミングのしくみ』(日経 BP 社、2010年) の著者であり、Linux Magazine のモバイル関連のエディターでもあります。



2011年 9月 09日

始める前に

NDK について探るそもそもの動機の 1 つは、NDK を使用することでオープンソース・プロジェクトを利用する機会が増えることにあります。オープンソースの多くは、C 言語で作成されているためです。このチュートリアルを最後まで終えると、C 言語で JNI (Java Native Interface) ライブラリーを作成して NDK (Native Development Kit) でコンパイルする方法、そしてこのライブラリーを Java 言語で作成された Android アプリケーションに組み込む方法を習得することができます。このチュートリアルで扱うサンプル・アプリケーションでは、未加工の画像データに対して基本的な画像処理操作を実行する方法を具体的に示します。さらに、Eclipse ビルド環境を拡張して、NDK プロジェクトを Android SDK プロジェクト・ファイルに統合する方法も学ぶことができます。これらの基礎知識を身につければ、既存のオープンソース・コードをより簡単に Android プラットフォームに移植できるようになるはずです。

このチュートリアルについて

Eclipse 環境で動作する Android NDK を紹介するこのチュートリアルでは、NDK を使って、C プログラミング言語を使用した機能を Android アプリケーションに追加します。まず始めに、NDK の概要とその一般的な使用シナリオを説明します。続いて画像処理の基本を説明した後、このチュートリアルのサンプル・アプリケーションである IBM Photo Phun の概要を紹介してデモンストレーションを行います。このアプリケーションは、SDK ベースの Java コードと NDK でコンパイルした C コードを組み合わせたものです。次に、NDK を操作する際に関与する技術、JNI (Java Native Interface) に話題を移します。そして、完成後のプロジェクトのソース・ファイルに目を向け、作成しようとしているアプリケーションのロードマップを説明します。その上で、手順を追ってアプリケーションを作成し、Java クラスと C ソース・ファイルについて解説します。最後に締めくくりとして、Eclipse ビルド環境をカスタマイズして、使いやすい Eclipse ビルド・プロセスに直接 NDK ツール・チェーンを統合します。

前提条件

このチュートリアルの内容を理解するには、Android SDK を使って Android アプリケーションを作成する作業に慣れていること、そして C プログラミング言語の基本的な知識を持っていることが必要です。さらに、以下のものを準備しておく必要があります。

  • Eclipse および ADT (Android Developer Tools) — 主要なコード・エディター、Java コンパイラー、および ADT プラグイン
  • Android SDK (Software Developer Kit)
  • Android NDK (Native Developer Kit)
  • PNG画像 — 画像処理操作のテストに使用する画像です。

このチュートリアルのサンプル・コードは、Eclipse V3.4.2 と Android SDK V8 (Android リリース 2.2 (Froyo) をサポート) をインストールした MacBook Pro で作成しました。チュートリアルで使用した NDK リリースは r4b です。これより前の Android NDK リリースには画像処理機能がないため、バージョン r4b 以降が必要となります。

以上のツールへのリンクについては、「参考文献」を参照してください。


Android NDK

まずは、Android NDK の概要と、この NDK を利用して Android プラットフォームを拡張する方法を説明します。Android SDK は極めて充実したプログラミング環境を提供しますが、Android NDK はその範囲をさらに広げ、専用のコードであるか、オープンソースのコードであるかに関わらず、既存のソース・コードを取り込んで、目的とする機能を素早く実現できるようにします。

NDK

NDK は、Android Web サイトから無料でダウンロードできるソフトウェア・リリースです。NDK には、C で作成された機能を Android アプリケーションに統合するために必要なコンポーネントがすべて組み込まれています。NDK の初期リリースでは最も基本的な機能のみが提供されていたため、かなりの制約がありました。その後、リリースを重ねるたびに、NDK はその機能を拡大していきました。バージョン r5 の NDK アプリケーションでは、ユーザー・インターフェースとイベント処理機能を含め、アプリケーションの大部分を C で直接作成することができるようになっています。このチュートリアルで具体的に説明する画像処理機能を可能にするフィーチャーは、r4b バージョンの NDK で導入されたものです。

NDK の一般的な使用目的は、アプリケーションのパフォーマンスを向上させること、そして既存の C コードを Android に移植して利用することの 2 つです。まず始めに、パフォーマンスの向上について検討すると、C でコードを作成しても、パフォーマンスの大幅な向上は保証されません。実際、適切にコーディングされた Java アプリケーションと比べた場合、出来の悪いネイティブ・コードの方がアプリケーションの実行に時間がかかる場合さえあります。アプリケーションのパフォーマンスを向上させるためには、慎重に C で作成された関数を使って、このチュートリアルで実例を用いて説明するようなメモリー・ベースの処理や計算を大量に行う処理を実行しなければなりません。具体的に言うと、NDK ではポインター演算を活用するアルゴリズムを使用するのがパフォーマンスの向上には特に有効です。NDK の一般的な使用目的の 2 つ目は、別のプラットフォーム (Linux など) を対象に作成された C コードの本体を移植することです。このチュートリアルでは、パフォーマンスの向上およびコードの再利用という 2 つのケースに焦点を当てる形で NDK の実際の使用方法を説明します。

NDK にはコンパイラーとビルド・スクリプトが含まれているため、ユーザーはインストール済みの NDK にビルド・プロセスを任せて、C ソース・ファイルの作成に専念することができます。NDK のビルド・プロセスを Eclipse 開発環境に統合するのは簡単です。その具体的な方法については、「Eclipse のカスタマイズ」のセクションで説明します。

アプリケーションそのものの話題に移る前に、少し寄り道をして、デジタル画像処理の基本について説明しておきます。

デジタル画像処理の基本

最近のコンピューター技術の楽しい側面と言えば、デジタル写真が登場し、至るところに普及していることです。デジタル写真は、子供のかわいい仕草を収めるだけではありません。デジタル画像は至るところにあり、携帯電話で撮ったスナップ写真から、結婚式で撮った正式な写真のアルバム、さらには深宇宙の画像に至るまで、さまざまなアプリケーションで使われています。デジタル画像は手軽に取り込んで、交換できるだけでなく、修正するのも簡単です。このチュートリアルでは、デジタル画像を修正する場合を例として取り上げます。サンプル・アプリケーションのコア機能となるのは、デジタル画像を修正する機能です。

デジタル画像の操作には、数え切れないほどの方法があります。以下に挙げる操作は、そのうちのほんの一部です。

  • 切り取り — 画像の一部を抽出
  • 拡大縮小 — 画像のサイズを変更
  • 回転 — 画像の向きを変更
  • 変換 — ある形式から別の形式への変換
  • サンプリング — 画像の密度を変更
  • ミキシング/モーフィング — 画像の外観を変更
  • フィルタリング — 画像の要素 (色や周波数など) を抽出
  • エッジ検出 — マシン・ビジョン・アプリケーションで、画像内の物体を識別するために使用
  • 圧縮 — 画像の保存サイズを縮小
  • ピクセル操作による画像の品質の向上
    • ヒストグラム平坦化
    • コントラスト
    • 輝度

上記の操作は、ピクセル単位で行われるものもあれば、マトリックス計算によって画像の一部分を一度に処理するものもあります。どの操作であるかに関わらず、すべての画像処理アルゴリズムには、加工されていない画像データの処理が関与してきます。このチュートリアルでは、Android 機器で C プログラミング言語によるピクセル操作とマトリックス操作を実行する例について説明します。


アプリケーションのアーキテクチャー

このセクションでは、チュートリアルで使用するサンプル・アプリケーションのアーキテクチャーを詳しく探るために、まず、完成した状態のプロジェクトの概要を紹介してから、その作成手順の主要なステップを順に追っていきます。このアプリケーションは、手順に沿って自力で構築することも、完成したプロジェクトを「参考文献」セクションからダウンロードすることもできます。

完成した状態のプロジェクト

このチュートリアルでは、IBM Photo Phun という単純な画像処理アプリケーションを実際に作成していきます。図 1 は、Eclipse IDE でソースと出力ファイルが見えるようにプロジェクトを展開したところのスクリーン・ショットです。

図 1. Eclipse におけるプロジェクトのビュー
Eclipse で IBM Photo Phun プロジェクトのすべての構成要素を展開したビューのスクリーン・ショット。

このアプリケーションの UI は、従来の Android 開発手法で構成されており、単純なレイアウト・ファイル (main.xml) と 1 つのアクティビティー (IBMPhotoPhun.java に実装) を使用します。プロジェクトのメイン・フォルダーの下には jni というフォルダーがあり、ここに置かれている 1 つの C ソース・ファイルに画像処理ルーチンが含まれています。NDK ツール・チェーンは、この C ソース・ファイルを libibmphotophun.so という名前の共有ライブラリー・ファイルにコンパイルし、コンパイル後のライブラリー・ファイルを libs フォルダーに保存します。ライブラリー・ファイルは、ターゲットとするハードウェア・プラットフォームまたはプロセッサー・アーキテクチャーごとに作成されます。表 1 に、アプリケーションのソース・ファイルを記載します。

表 1. アプリケーションに必要なソース・ファイル
ファイル注記
IBMPhotoPhun.javaUI およびアプリケーション・ロジックの Android Activity クラスを継承
Ibmphotophun.c画像処理ルーチンを実装
main.xmlアプリケーション UI のホーム・ページ
AndroidManifest.xmlAndroid アプリケーションのデプロイメント記述子
sampleimage.pngデモに使用する画像 (お手持ちの画像を代わりに使用するのでも構いません)
Android.mkNDK が JNI ライブラリーを作成するために使用する makefile のスニペット

実際に使える Android 開発環境がない場合は、この機会に Android ツールをインストールしてください。Android 開発環境のセットアップ方法について詳しくは、「参考文献」に記載されている必要なツールへのリンクと、Android アプリケーションの開発方法を説明している入門記事およびチュートリアルを参照してください。Android を十分に理解することが、このチュートリアルを理解する上でも役立ちます。

アーキテクチャーおよびアプリケーションの概要を説明したところで、Android 機器で実行中のアプリケーションを見てみましょう。

アプリケーションのデモンストレーション

最終的な完成品を念頭に置いて作業を始めると役に立つ場合があるので、アプリケーションをステップ・バイ・ステップで作成するプロセスに取り掛かる前に、完成したアプリケーションが実行されているところを見てみましょう。以降に示すスクリーン・ショットは、Android 2.2 (Froyo) を実行している Nexus One で撮りました。画像を取り込むために使用したのは、Android Developer Tools Eclipse プラグインの一部としてインストールされる Dalvik Debug Monitor Service (DDMS) ツールです。

図 2 に示すアプリケーションのホーム画面には、サンプル画像がロードされています。この画像を一目見れば、この「ラジオ向け」の顔のおかげで、私が TV 番組に出演する代わりにプログラマーとしての道を選ぶことになったことを理解していただけるはずです。皆さんがアプリケーションを作成するときには、この画像の代わりに、遠慮なくご自分の顔写真の画像を使ってください。

図 2. IBM Photo Phun アプリケーションのホーム画面
画面上端に「Reset (リセット)」、「Convert Image (画像変換)」、「Find Edges (エッジ検出)」、「+」ボタンなどが表示された Android 機器のスクリーン・ショット。ボタンの下には、著者の顔のスナップ写真が表示されています。

画面上端に並んだボタンは、画像を変更するために使用します。最初の「Reset (リセット)」ボタンは、画像を上記に示されている元のカラー画像に戻すためのボタンです。「Convert Image (画像変換)」ボタンを選択すると、画像はグレースケールに変換されます (図 3 を参照)。

図 3. グレースケール画像
図 2 と同じアプリケーションのスクリーン・ショットですが、画像はグレーのネガに変換されています。

Find Edges (エッジ検出)」ボタンは、元のカラー画像をグレースケールに変換してから、Sobel エッジ検出アルゴリズムを実行します。図 4 に、このエッジ検出アルゴリズムを実行した結果を示します。

図 4. エッジ検出
このスクリーン・ショットに示された白黒の画像は、検出されたエッジが白の輪郭線で描かれ、木炭画のように見えます。

エッジ検出アルゴリズムは、マシン・ビジョン・アプリケーションにおいて、多段階で行う画像処理操作の最初のステップとしてよく使用されています。図 4 に示した状態の画像は、最後の 2 つのボタンで各ピクセルの輝度を変更することによって、明暗を調整することができます。図 5 に、明るいバージョンのグレースケール画像を示します。

図 5. 輝度を上げた場合
このスクリーン・ショットには、図 4 の画像の輝度を上げて、黒ではなく明るいグレーでレンダリングした場合の画面が示されています。

図 6 に、今度は少し暗くしたエッジ画像を示します。

図 6. 輝度を下げる場合
このスクリーン・ショットには、図 5 の画像の輝度を下げた場合が示されています。画像はほとんどダーク・グレーになっていて、エッジの一部は明るいグレーの輪郭線で描かれていますが、詳細部分はほとんど見えません。

それでは、このアプリケーションの作成を始めましょう。


アプリケーションの作成

このセクションでは、Eclipse ADT プラグインに提供されているツールを使ってアプリケーションを作成します。Android 用のアプリケーションを作成するのに慣れていないとしても、このセクションの手順には簡単に従うことができるので、Android アプリケーションの作成方法を学べるはずです。「参考文献」セクションでは、Android アプリケーションの基本的な作成方法について参考になる記事とチュートリアルを紹介しています。

ADT の新規プロジェクト・ウィザード

Eclipse IDE 内では、ADT の新規プロジェクト・ウィザード (図 7 を参照) のおかげで、非常に簡単にアプリケーションを作成できるようになっています。

図 7. 新規 Android プロジェクトの作成
ビルド・ターゲットが Android 2.2 に設定された「New Android Project (新規 Android プロジェクト)」ウィザード画面のスクリーン・ショット

新規プロジェクト・ウィザードに入力するときには、以下の情報を提供します。

  1. 有効なプロジェクト名。
  2. ビルド・ターゲット。このプロジェクトのターゲット SDK プラットフォームには、Android V2.2 または Android V2.3 を指定する必要があります。
  3. 有効なアプリケーション名。
  4. パッケージ名。
  5. アクティビティー名。

ウィザード画面での入力が完了したら、「Finish (完了)」を選択します。「Next (次へ)」ボタンをクリックすると、このプロジェクトに付随する「Test (テスト)」プロジェクトを作成するよう求めるプロンプトが出されます。これは有用なステップですが、別の機会に行ってください。

プロジェクトを Eclipse に追加したら、アプリケーションに必要なソース・ファイルの実装を開始することができます。まずは、アプリケーションの UI 要素から取り掛かります。

ユーザー・インターフェースを実装する

このアプリケーションの UI は、かなり単純明快なものです。アプリケーションには、わずかな数の Button ウィジェットと選択された画像を表示する ImageView ウィジェットで構成された Activity が 1 つしかありません。多くの Android アプリケーションと同じく、UI は main.xml ファイルに定義されます (リスト 1 を参照)。

リスト 1. UI レイアウト・ファイル、main.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"
    android:background="#ffffffff"
    >
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    
    > 
        
<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/btnReset"
    android:text="Reset"
    android:visibility="visible"
    android:onClick="onResetImage"
/>
<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/btnConvert"
    android:text="Convert Image"
    android:visibility="visible"
    android:onClick="onConvertToGray"
/>
<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/btnFindEdges"
    android:text="Find Edges"
    android:visibility="visible"
    android:onClick="onFindEdges"
/>
<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/btnDimmer"
    android:text="- "
    android:visibility="visible"
    android:onClick="onDimmer"
/>
<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/btnBrighter"
    android:text=" +"
    android:visibility="visible"
    android:onClick="onBrighter"
/>
</LinearLayout>
<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:scaleType="centerCrop"
    android:layout_gravity="center_vertical|center_horizontal"
    android:id="@+id/ivDisplay"
/>
</LinearLayout>

2 つの LinearLayout 要素が使用されていることに注意してください。外側の LinearLayout 要素が UI の縦方向の配置を制御し、内側の LinearLayout 要素がその縦方向の配置内で UI の横方向の配置を行うように設定されます。画面上端に並ぶ Button ウィジェットのすべては、横方向のレイアウト要素に格納されます。ImageView ウィジェットは、表示する画像を中央に配置するようにセットアップされます。このウィジェットには id 属性があり、実行中にウィジェットのコンテンツを操作できるようになっています。

それぞれの Button ウィジェットには、onClick 属性があります。この属性の値は、ウィジェットを包含する Activity クラス内の public void メソッド (1 つの View 引数を取ります) に対応していなければなりません。この手法は、匿名ハンドラーを定義したり、実行時に要素にアクセスしたりすることなく、素早く簡単にクリック・ハンドラーをセットアップする方法となります。この手法で Button クリックを処理する方法についての詳細は、「参考文献」を参照してください。

レイアウト・ファイルに UI を定義した後は、この UI と連動する Activity コードを作成する必要があります。このコードは、Activity クラスを継承する IBMPhotoPhun.java ファイルに実装します。リスト 2 に、このコードを記載します。

リスト 2. IBM Photo Phun のインポートとクラスの宣言
/*
 * IBMPhotoPhun.java
 * 
 * Author: Frank Ableson
 * Contact Info: fableson@navitend.com
 */

package com.msi.ibm.ndk;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.graphics.BitmapFactory;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.view.View;
import android.widget.ImageView;


public class IBMPhotoPhun extends Activity {
    private String tag = "IBMPhotoPhun";
    private Bitmap bitmapOrig = null;
    private Bitmap bitmapGray = null;
    private Bitmap bitmapWip = null;
    private ImageView ivDisplay = null;
    
    
    
    // NDK STUFF
    static {
        System.loadLibrary("ibmphotophun");
    }
    public native void convertToGray(Bitmap bitmapIn,Bitmap bitmapOut);
    public native void changeBrightness(int direction,Bitmap bitmap);
    public native void findEdges(Bitmap bitmapIn,Bitmap bitmapOut);
    // END NDK STUFF
    
    
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Log.i(tag,"before image stuff");
        ivDisplay = (ImageView) findViewById(R.id.ivDisplay);
        
    
        // load bitmap from resources
        BitmapFactory.Options options = new BitmapFactory.Options();
        // Make sure it is 24 bit color as our image processing algorithm 
		// expects this format
        options.inPreferredConfig = Config.ARGB_8888;
        bitmapOrig = BitmapFactory.decodeResource(this.getResources(), 
R.drawable.sampleimage,options);
        if (bitmapOrig != null)
            ivDisplay.setImageBitmap(bitmapOrig);
      
    }
 
    public void onResetImage(View v) {
        Log.i(tag,"onResetImage");

        ivDisplay.setImageBitmap(bitmapOrig);
        
    }
 
    public void onFindEdges(View v) {
        Log.i(tag,"onFindEdges");

        // make sure our target bitmaps are happy
        bitmapGray = Bitmap.createBitmap(bitmapOrig.getWidth(),bitmapOrig.getHeight(),
Config.ALPHA_8);
        bitmapWip = Bitmap.createBitmap(bitmapOrig.getWidth(),bitmapOrig.getHeight(),
Config.ALPHA_8);
        // before finding edges, we need to convert this image to gray
        convertToGray(bitmapOrig,bitmapGray);
        // find edges in the image
        findEdges(bitmapGray,bitmapWip);
        ivDisplay.setImageBitmap(bitmapWip);
        
    }
    public void onConvertToGray(View v) {
        Log.i(tag,"onConvertToGray");
 
        bitmapWip = Bitmap.createBitmap(bitmapOrig.getWidth(),bitmapOrig.getHeight(),
Config.ALPHA_8);
        convertToGray(bitmapOrig,bitmapWip);
        ivDisplay.setImageBitmap(bitmapWip);
    }
    
    public void onDimmer(View v) {
        Log.i(tag,"onDimmer");
        
        changeBrightness(2,bitmapWip);
        ivDisplay.setImageBitmap(bitmapWip);
    }
    public void onBrighter(View v) {
        Log.i(tag,"onBrighter");
  
        changeBrightness(1,bitmapWip);
        ivDisplay.setImageBitmap(bitmapWip);
    }   
}

上記のコードを特筆すべきいくつかの内容に分けて、以下に説明します。

  1. メンバー変数には以下のものがあります。
    • tag — この変数は、デバッグ中に LogCat をフィルタリングできるように、すべてのロギング・ステートメントで使用されます。
    • bitmapOrig — この Bitmap には、元のカラー画像が保持されます。
    • bitmapGray — この Bitmap には画像のグレースケール・コピーが保持されるため、findEdges ルーチンの間、一時的にしか使用されません。
    • bitmapWip — この Bitmap には、輝度の値の変更によって変更されたグレースケール画像が保持されます。
    • ivDisplay — この ImageView は、main.xml レイアウト・ファイルに定義された ImageView への参照です。
  2. 「NDK STUFF」セクションには、以下の 4 つの行が含まれています。
    • ネイティブ・コードが含まれるライブラリーは、System.loadLibrary の呼び出しによってロードされます。このコードは、static として指定されたブロック内にあることに注意してください。これにより、ライブラリーはアプリケーションの起動時にロードされます。
    • convertToGray のプロトタイプ宣言。この関数は 2 つの引数を取ります。1 つはカラーの Bitmap、もう 1 つは、そのカラーの Bitmap のグレースケール・バージョンが取り込まれた Bitmap です。
    • changeBrightness のプロトタイプ宣言。この関数は 2 つの引数を取ります。1 つは、値の増減を表す整数です。もう 1 つの引数は、ピクセル単位で変更された Bitmap です。
    • findEdges のプロトタイプ宣言。この関数は 2 つの引数を取ります。1 つはグレースケールの Bitmap、もう 1 つは「エッジのみ」の画像を受け取る Bitmap です。
  3. onCreate メソッドは、R.layout.main によって識別されるレイアウトを拡張し、ImageView ウィジェットへの参照 (ivDisplay) を取得した後、リソースからカラー画像をロードします。
    • BitmapFactory メソッドが取る引数 options によって、画像を ARGB 形式でロードすることができます。A はアルファ・チャネルを表し、RGB はそれぞれ赤、緑、青を表します。オープンソースの画像処理ライブラリーの多くは、各ピクセルが RGB トリプレットで構成された 24 ビット・カラー (赤、緑、青のそれぞれに 8 ビット) の画像を期待します。各色の値の範囲は、0 から 255 です。Android プラットフォーム上の画像は、アルファ、赤、緑、青からなる 32 ビットの整数として保存されます。
    • ロードされた画像は、ImageView に表示されます。
  4. このクラスの残りのメソッドは、Button ウィジェットのクリック・ハンドラーに対応します。
    • onResetImage は、元のカラー画像を ImageView にロードします。
    • onConvertToGray は、ターゲットの Bitmap を 8 ビットの画像として作成し、convertToGray ネイティブ関数を呼び出します。この呼び出しによる画像 (bitmapWip) は ImageView に表示されます。
    • onFindEdges は、2 つの中間 Bitmap オブジェクトを作成し、カラー画像をグレースケール画像に変換してから findEdges ネイティブ関数を呼び出します。この呼び出しによる画像 (bitmapWip) は ImageView に表示されます。
    • onDimmeronBrighter は、それぞれ changeBrightness メソッドを呼び出して画像を変更します。この呼び出しによる画像 (bitmapWip) は ImageView に表示されます。

UI コードについての説明は以上です。次は、画像処理ルーチンを実装しますが、まずはその前に、ライブラリー自体を作成する必要があります。


NDK ファイルの作成

Android アプリケーションの UI とアプリケーション・ロジックは用意できました。次に必要なのは、画像処理関数を実装することです。それには、NDK を使って Java ネイティブ・ライブラリーを作成します。このサンプル・アプリケーションでは、パブリック・ドメインの C コードを使用して画像処理関数を実装し、これらの関数を Android アプリケーションが使用できるライブラリーにパッケージ化します。

ネイティブ・ライブラリーを作成する

NDK は共有ライブラリーを作成し、makefile システムを利用します。このプロジェクトのネイティブ・ライブラリーを作成するには、以下のステップを実行する必要があります。

  1. プロジェクト・ファイルの下に、jni という名前の新規フォルダーを作成します。
  2. jni フォルダー内に、Android.mk というファイルを作成します。このファイルに、ライブラリーを適切にビルドして名前を付けるための makefile コマンドが含まれることになります。
  3. jni フォルダー内に C ソース・ファイルを作成します。このファイルを、Android.mk ファイル内で参照します。このチュートリアルでは、この C ソース・ファイルに ibmphotophun.c という名前を付けます。

リスト 3 に、Android.mk ファイルの内容を記載します。

リスト 3. Android.mk ファイル
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE    := ibmphotophun
LOCAL_SRC_FILES := ibmphotophun.c
LOCAL_LDLIBS    := -llog -ljnigraphics
include $(BUILD_SHARED_LIBRARY)

この makefile (スニペット) は NDK に対して以下の処理を行うように指示をします。

  1. ibmphotophun.c ソース・ファイルを共有ライブラリーにコンパイルすること。
  2. 共有ライブラリーに名前を付けること。デフォルトでは、共有ライブラリーの命名規則は lib<モジュール名>.so という形になります。したがって、このプロジェクトでのファイル名は、libibmphotophun.so です。
  3. 必要な「入力」ライブラリーを指定すること。共有ライブラリーは、ロギング用の組み込みライブラリー・ファイル (liblog.so) と jni グラフィック用の組み込みライブラリー・ファイル (libjnigraphics.so) を利用します。LogCat へのエントリーの追加を可能にするロギング・ライブラリーは、プロジェクトの開発フェーズで重宝します。グラフィック・ライブラリーは、Android ビットマップとその画像データを処理するためのルーチンを提供します。

ibmphotophun.c ソース・ファイルには、いくつかの include 文と Android SDK での Color データ型に相当する argb 型の定義があります。リスト 4 に、画像ルーチンの部分を省いた ibmphotophun.c を記載します。画像ルーチンについては、次に説明します。

リスト 4. Ibmphotophun.c のマクロおよび include 文
/*
 * ibmphotophun.c
 * 
 * Author: Frank Ableson
 * Contact Info: fableson@msiservices.com
 */

#include <jni.h>
#include <android/log.h>
#include <android/bitmap.h>

#define  LOG_TAG    "libibmphotophun"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#define  LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)

typedef struct 
{
    uint8_t alpha;
    uint8_t red;
    uint8_t green;
    uint8_t blue;
} argb;

ロギング機能を呼び出す LOGI マクロと LOGE マクロは、機能の点で、Android SDK のLog.i()Log.e() にそれぞれ相当します。typedef struct キーワードで定義された argb データ型を使用することで、C コードは 32 ビットの整数で保存された 1 つのピクセルを構成する 4 つのデータ要素にアクセスすることができます。3 つの include 文は、それぞれ jni グルー、ロギング、およびビットマップ処理に必要な宣言を C コンパイラーに提供します。

いよいよ、画像処理ルーチンを実装しますが、コードを検討する前に、JNI 関数の命名規則を理解しておく必要があります。

Java コードはネイティブ関数を呼び出すときに、その関数名を、JNI 共有ライブラリーによってエクスポートされる関数にマッピングします。この展開 (修飾) された関数の命名規則は、「Java_<完全修飾クラス名>_<関数名>」です。

例えば、convertToGray 関数は、C コードでは Java_com_msi_ibm_ndk_IBMPhotoPhun_convertToGray として実装されます。

JNI 関数に渡す最初の 2 つの引数には、JNI 環境へのポインター、そして呼び出し側クラス・オブジェクト・インスタンスへのポインターが組み込まれます。JNI についての詳細は、「参考文献」セクションを参照してください。

ライブラリーを作成するのは、極めて簡単です。ターミナル・ウィンドウ (または DOS ウィンドウ) を開き、ソース・ファイルが保存されている jni フォルダーへとカレント・ディレクトリーを変更します。NDK がパスに含まれていることを確認してから、ndk-build スクリプトを実行します。このスクリプトには、ライブラリーを作成するために必要なグルー・コードがすべて含まれています。作成されたライブラリーは、jni フォルダーと同じレベルにある libs フォルダー (例えば、<プロジェクト・フォルダー>/libs/) に保存されます。

Android アプリケーションを Eclipse ADT プラグインでパッケージ化すると、自動的にライブラリー・ファイルが適切な形で組み込まれます。サポートされているハードウェア・プラットフォームごとに 1 つのライブラリー・ファイルが生成され、実行時に適切なライブラリーがロードされます。

次は、画像処理アルゴリズムを実装する方法を説明します。

画像処理アルゴリズムを実装する

サンプル・アプリケーションで使用する画像処理ルーチンは、多種多様なパブリック・ドメインのルーチンとアカデミックなルーチンを元に、画像処理愛好家である私個人の経験を加えて作成したものです。このルーチンに含まれる関数のうち、2 つはピクセル操作を使用し、1 つは必要最小限のマトリックス手法を利用します。まずは、リスト 5 に記載する convertToGray 関数を見てください。

リスト 5. convertToGray 関数
/*
convertToGray
Pixel operation
*/
JNIEXPORT void JNICALL Java_com_msi_ibm_ndk_IBMPhotoPhun_convertToGray(JNIEnv 
* env, jobject  obj, jobject bitmapcolor,jobject bitmapgray)
{
    AndroidBitmapInfo  infocolor;
    void*              pixelscolor; 
    AndroidBitmapInfo  infogray;
    void*              pixelsgray;
    int                ret;
    int             y;
    int             x;

     
    LOGI("convertToGray");
    if ((ret = AndroidBitmap_getInfo(env, bitmapcolor, &infocolor)) < 0) {
        LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
        return;
    }

    
    if ((ret = AndroidBitmap_getInfo(env, bitmapgray, &infogray)) < 0) {
        LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
        return;
    }

    
    LOGI("color image :: width is %d; height is %d; stride is %d; format is %d;flags is 
%d",infocolor.width,infocolor.height,infocolor.stride,infocolor.format,infocolor.flags);
    if (infocolor.format != ANDROID_BITMAP_FORMAT_RGBA_8888) {
        LOGE("Bitmap format is not RGBA_8888 !");
        return;
    }

    
    LOGI("gray image :: width is %d; height is %d; stride is %d; format is %d;flags is 
%d",infogray.width,infogray.height,infogray.stride,infogray.format,infogray.flags);
    if (infogray.format != ANDROID_BITMAP_FORMAT_A_8) {
        LOGE("Bitmap format is not A_8 !");
        return;
    }
  
    
    if ((ret = AndroidBitmap_lockPixels(env, bitmapcolor, &pixelscolor)) < 0) {
        LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
    }

    if ((ret = AndroidBitmap_lockPixels(env, bitmapgray, &pixelsgray)) < 0) {
        LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
    }

    // modify pixels with image processing algorithm
    
    for (y=0;y<infocolor.height;y++) {
        argb * line = (argb *) pixelscolor;
        uint8_t * grayline = (uint8_t *) pixelsgray;
        for (x=0;x<infocolor.width;x++) {
            grayline[x] = 0.3 * line[x].red + 0.59 * line[x].green + 0.11*line[x].blue;
        }
        
        pixelscolor = (char *)pixelscolor + infocolor.stride;
        pixelsgray = (char *) pixelsgray + infogray.stride;
    }
    
    LOGI("unlocking pixels");
    AndroidBitmap_unlockPixels(env, bitmapcolor);
    AndroidBitmap_unlockPixels(env, bitmapgray);

    
}

この関数は、呼び出し側 Java コードから 2 つの引数を受け取ります。1 つは ARGB フォーマットのカラーの Bitmap、もう 1 つはカラー画像のグレースケール・バージョンを取り込んだ 8 ビットのグレースケールの Bitmap です。このコードの内容を、以下に順を追って説明します。

  1. bitmap.h に定義されている AndroidBitmapInfo 構造体は、Bitmap オブジェクトについて学習するのに役立ちます。
  2. jnigraphics ライブラリーにある AndroidBitmap_getInfo 関数は、特定の Bitmap オブジェクトに関する情報を取得します。
  3. 次のステップでは、convertToGray 関数に渡されたビットマップが期待されるフォーマットであることを確認します。
  4. AndroidBitmap_lockPixels 関数は画像データをロックして、そのデータで直接操作を実行できるようにします。
  5. AndroidBitmap_unlockPixels 関数は以前にロックされたピクセル・データのロックを解除します。これらの関数は、「ロック/ロック解除ペア」として呼び出す必要があります。
  6. ロック関数とロック解除関数の間にあるのが、ピクセル操作です。

ポインターの魅力

C での画像処理アプリケーションには通常、ポインターが関わってきます。ポインターとは、メモリー・アドレスを指す変数であり、変数のデータ型によって、操作するメモリーの型とサイズが決まります。例えば、char は符号付き 8 ビットの値を意味します。したがって、char ポインター (char *) を使用すれば、8 ビットの値を参照して、そのポインターを介して操作を行うことができます。画像データは uint8_t として表現されます。uint8_t は、符号なし 8 ビットの値を意味し、各バイトが 0 から 255 までの範囲の値を保持します。24 ビットの画像の場合、3 つの符号なし 8 ビットの値の集合によって、画像データのピクセルが表現されます。

画像を処理するには、個々のデータ行を処理して、列を移動していかなければなりません。Bitmap 構造体には、ストライドとして知られるメンバーが含まれます。ストライドが表すのは、画像のデータ行の幅 (バイト単位) です。例えば、24 ビット・カラーにアルファ・チャネルを加えた画像の場合、ピクセルあたりのビット数は 32、つまり 4 バイトです。したがって、幅が 320 ピクセルの画像のストライドは、320*4 = 1,280 バイトということになります。8 ビットのグレースケール画像の場合、ピクセルあたりのビット数は 8、つまり 1 バイトなので、幅が 320 ピクセルのグレースケール・ビットマップのストライドは 320*1 = 320 バイトです。この情報を踏まえた上で、以下に説明するカラー画像をグレースケール画像に変換するための画像処理アルゴリズムを見て行きましょう。

  1. 画像データが「ロック」されると、画像データのベース・アドレスがポインターによって参照されます。使用されるポインターは、入力カラー画像データに対しては pixelscolor、出力グレースケール画像に対しては pixelsgray です。
  2. 2 つの for-next ループによって、画像全体を繰り返し処理することができます。
    1. 最初に、画像の高さを繰り返し処理します (「行」ごとに 1 つのパス)。行のカウントを取得するには、infocolor.height 値を使用します。
    2. 行のパススルーごとに、ポインターが、その行の画像データの最初の「列」に対応するメモリー・ロケーションに設定されます。
    3. 特定の行の列を繰り返し処理しながら、カラー・データの各ピクセルを、グレースケール値を表す単一の値に変更していきます。
    4. 行全体が変換されたら、ポインターを次の行に進めなければなりません。それには、メモリー内でスライド値の分だけ前方にジャンプします。

ピクセル指向の画像処理操作では例外なく、上記の形式に従います。例えば、リスト 6 の changeBrightness 関数を見てください。

リスト 6. changeBrightness 関数
/*
changeBrightness
Pixel Operation
*/
JNIEXPORT void JNICALL Java_com_msi_ibm_ndk_IBMPhotoPhun_changeBrightness(JNIEnv 
* env, jobject  obj, int direction,jobject bitmap)
{
    AndroidBitmapInfo  infogray;
    void*              pixelsgray;
    int                ret;
    int             y;
    int             x;
    uint8_t save;

    
    
    
    if ((ret = AndroidBitmap_getInfo(env, bitmap, &infogray)) < 0) {
        LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
        return;
    }

    
    LOGI("gray image :: width is %d; height is %d; stride is %d; format is %d;flags is 
%d",infogray.width,infogray.height,infogray.stride,infogray.format,infogray.flags);
    if (infogray.format != ANDROID_BITMAP_FORMAT_A_8) {
        LOGE("Bitmap format is not A_8 !");
        return;
    }
  
    
    if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixelsgray)) < 0) {
        LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
    }

    // modify pixels with image processing algorithm
    
    
    LOGI("time to modify pixels....");    
    for (y=0;y<infogray.height;y++) {
        uint8_t * grayline = (uint8_t *) pixelsgray;
        int v;
        for (x=0;x<infogray.width;x++) {
            v = (int) grayline[x];
            
            if (direction == 1)
                v -=5;
            else
                v += 5;
            if (v >= 255) {
                grayline[x] = 255;
            } else if (v <= 0) {
                grayline[x] = 0;
            } else {
                grayline[x] = (uint8_t) v;
            }
        }
        
        pixelsgray = (char *) pixelsgray + infogray.stride;
    }
    
    AndroidBitmap_unlockPixels(env, bitmap);

    
}

この関数は convertToGray 関数と同じように動作しますが、以下の違いがあります。

  1. この関数の場合、必要なのは 1 つのグレースケール・ビットマップだけです。渡される画像はその場で変更されます。
  2. この関数は、パスごとに、各ピクセルに対して値を 5 だけ足すか、5 だけ引きます。この定数は変更することもできます。5 という値を使用したのは、プラス・ボタンやマイナス・ボタンを押さなくても、パスごとの画像の変更が顕著にわかるようにするためです。
  3. ピクセル値は、0 から 255 の範囲に制限されています。これらの操作を符号なし変数で直接実行する場合には、値が (255 から 0 へ、あるいは 0 から 255 へと)「ひと回り」しやすいため、注意が必要です。私が changeBrightness 関数で最初に行った取り組みでは、例えば 252 に値 5 を追加すると、値が 2 になるという結果になりました。この効果は見ていて面白いものですが、それは私が目指していることではありません。そこで、v と名付けた整数を使用して、ピクセル・データを符号付き整数にキャストしてから、その値を 0 から 255 と比較することにしたわけです。

もう 1 つ、検討しなければならない画像処理アルゴリズムが残っています。それは、前の 2 つのピクセル指向の関数とは多少異なる働きをする findEdges 関数です。リスト 7 に findEdges 関数を記載します。

リスト 7. 画像内のアウトラインを検出する findEdges 関数
/*
findEdges
Matrix operation
*/
JNIEXPORT void JNICALL Java_com_msi_ibm_ndk_IBMPhotoPhun_findEdges(JNIEnv 
* env, jobject  obj, jobject bitmapgray,jobject bitmapedges)
{
    AndroidBitmapInfo  infogray;
    void*              pixelsgray;
    AndroidBitmapInfo  infoedges;
    void*              pixelsedge;
    int                ret;
    int             y;
    int             x;
    int             sumX,sumY,sum;
    int             i,j;
    int                Gx[3][3];
    int                Gy[3][3];
    uint8_t            *graydata;
    uint8_t            *edgedata;
    

    LOGI("findEdges running");    
    
    Gx[0][0] = -1;Gx[0][1] = 0;Gx[0][2] = 1;
    Gx[1][0] = -2;Gx[1][1] = 0;Gx[1][2] = 2;
    Gx[2][0] = -1;Gx[2][1] = 0;Gx[2][2] = 1;
    
    
    
    Gy[0][0] = 1;Gy[0][1] = 2;Gy[0][2] = 1;
    Gy[1][0] = 0;Gy[1][1] = 0;Gy[1][2] = 0;
    Gy[2][0] = -1;Gy[2][1] = -2;Gy[2][2] = -1;


    if ((ret = AndroidBitmap_getInfo(env, bitmapgray, &infogray)) < 0) {
        LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
        return;
    }

    
    if ((ret = AndroidBitmap_getInfo(env, bitmapedges, &infoedges)) < 0) {
        LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
        return;
    }

    
    
    LOGI("gray image :: width is %d; height is %d; stride is %d; format is %d;flags is
%d",infogray.width,infogray.height,infogray.stride,infogray.format,infogray.flags);
    if (infogray.format != ANDROID_BITMAP_FORMAT_A_8) {
        LOGE("Bitmap format is not A_8 !");
        return;
    }
  
    LOGI("color image :: width is %d; height is %d; stride is %d; format is %d;flags is
%d",infoedges.width,infoedges.height,infoedges.stride,infoedges.format,infoedges.flags);
    if (infoedges.format != ANDROID_BITMAP_FORMAT_A_8) {
        LOGE("Bitmap format is not A_8 !");
        return;
    }

    

    if ((ret = AndroidBitmap_lockPixels(env, bitmapgray, &pixelsgray)) < 0) {
        LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
    }

    if ((ret = AndroidBitmap_lockPixels(env, bitmapedges, &pixelsedge)) < 0) {
        LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
    }

    
    // modify pixels with image processing algorithm
    
    
    LOGI("time to modify pixels....");    
    
    graydata = (uint8_t *) pixelsgray;
    edgedata = (uint8_t *) pixelsedge;
    
    for (y=0;y<=infogray.height - 1;y++) {
        for (x=0;x<infogray.width -1;x++) {
            sumX = 0;
            sumY = 0;
            // check boundaries
            if (y==0 || y == infogray.height-1) {
                sum = 0;
            } else if (x == 0 || x == infogray.width -1) {
                sum = 0;
            } else {
                // calc X gradient
                for (i=-1;i<=1;i++) {
                    for (j=-1;j<=1;j++) {
                        sumX += (int) ( (*(graydata + x + i + (y + j) 
* infogray.stride)) * Gx[i+1][j+1]);
                    }
                }
                
                // calc Y gradient
                for (i=-1;i<=1;i++) {
                    for (j=-1;j<=1;j++) {
                        sumY += (int) ( (*(graydata + x + i + (y + j) 
* infogray.stride)) * Gy[i+1][j+1]);
                    }
                }
                
                sum = abs(sumX) + abs(sumY);
                
            }
            
            if (sum>255) sum = 255;
            if (sum<0) sum = 0;
            
            *(edgedata + x + y*infogray.width) = 255 - (uint8_t) sum;
            
            
            
        }
    }
    
    AndroidBitmap_unlockPixels(env, bitmapgray);
    AndroidBitmap_unlockPixels(env, bitmapedges);

    
}

findEdges ルーチンは、以下のように、前の 2 つの関数と多くの共通点があります。

  1. convertToGray 関数と同じく、この関数も 2 つのビットマップ・パラメーターを引数に取ります (ただし、この関数の場合は、両方ともグレースケールです)。
  2. ビットマップを調べ、ビットマップが期待されるフォーマットであることを確実にします。
  3. ビットマップ・ピクセルが必要に応じてロック、ロック解除されます。
  4. このアルゴリズムは、ソース画像の行と列を繰り返し処理します。

その一方、この関数は前の 2 つの関数とは異なり、ピクセル値自体に対して数学演算を行うのではなく、各ピクセルをその周囲のピクセルと比較します。この関数に実装されているアルゴリズムは、Sobel エッジ検出アルゴリズムのバリエーションです。上記の実装では、各ピクセルを、各方向 1 ピクセルの境界で、隣接するピクセルと比較しています。このアルゴリズムや他のアルゴリズムのバリエーションでは、これよりも大きな「境界」を使用して異なる結果を得る場合もあります。各ピクセルをその隣接ピクセルと比較することで、ピクセル間の差異を際立たせ、それによって「エッジ」を強調します。

このアルゴリズムに伴う計算については、2 つの理由から深く掘り下げることはしません。理由の 1 つは、計算そのものの説明はこのチュートリアルの範囲外だからです。そしてもう 1 つの理由は、このチュートリアルが目的とする、既存の C ソース・コードを (再) 利用する方法については、既存の画像処理アルゴリズムを使って実際に示されているからです。つまり、目的とする結果を実現するために、最初からやり直したり、既存のコードを Java 技術に移植したりする必要はありません。ポインター演算のおかげで、C は画像データを処理するには理想的な環境です。

画像処理アルゴリズムについての詳細は、「参考文献」を参照してください。


Eclipse のカスタマイズ

Eclipse IDE で作業する上で嬉しい点の 1 つは、コンパイルを実行する必要がめったにないことです。Eclipse IDE 内にファイルを保存すると、いつでもプロジェクトが自動的にビルドされます。この点は、Android SDK ファイル (つまり、Java ファイル) と Android XML ファイルには願ってもないことですが、NDK でビルドしたライブラリーについてはどうでしょう?その答えを今から探ります。

Eclipse 環境を拡張する

前述のとおり、ネイティブ・ライブラリーは ndk-build コマンドを実行するだけで簡単にビルドすることができます。ただし、平凡な演習以外の目的でプロジェクトを処理しているときには、Eclipse 環境からターミナル・ウィンドウやコマンド・ウィンドウへ移って ndk-build コマンドを実行し、それからまた Eclipse 環境に戻り、プロジェクト・ファイルのいずれかで何らかの操作を行うことで強制的に最新の状態に更新し、それによって完全なアプリケーションを再コンパイルして再パッケージ化するのでは面倒です。その解決策となるのは、Android プロジェクトに合わせてビルド設定をカスタマイズすることによって、Eclipse 環境を拡張することです。

ビルド設定を変更するには、まず、Android プロジェクトのプロパティーを表示して、リストから「Builders (ビルダー)」を選択します。そして新規ビルダーを追加し、そのビルダーをリストの先頭に移動します (図 8 を参照)。

図 8. ビルド設定の変更
Eclipse で IBM Photo Phun プロジェクト・プロパティーのビルダー・ページを編集する画面のスクリーン・ショット

各ビルダーには、4 つの構成タブがあります。まず、ビルダーの名前 (例えば、Build NDK Library) を設定してから、タブにデータを入力します。最初のタブ (「Main (メイン)」) では、実行可能ツールが置かれている場所と作業ディレクトリーを指定します。「Location (ロケーション)」には ndk-build ファイルを指定し、 「Working Directory (作業ディレクトリー)」には jni フォルダーを指定します (図 9 を参照)。

図 9. NDK に応じたビルダーのプロパティーのセットアップ
Eclipse で NDK エントリーの詳細を編集する「Main (メイン)」タブのスクリーン・ショット

このプロジェクトでは ndk-build だけが動作し、Eclipse ワークスペース内の他のツールは動作しないようにする必要があります。そのための設定は、「Refresh (リフレッシュ)」タブで行います (図 10 を参照)。

図 10. 「Refresh (リフレッシュ)」タブのセットアップ
Eclipse で NDK エントリーの詳細を編集する「Refresh (リフレッシュ)」タブのスクリーン・ショット

ライブラリーを再ビルドする必要があるのは、Android.mk ファイルまたは ibmphotophun.c ファイルのいずれかが変更された場合だけです。この設定をするには、「Build Options (ビルド・オプション)」のタブで「Specify Resources (リソースの指定)」ボタンをクリックして、jni フォルダーを選択します。また、ビルド・ツールを実行するタイミングを、対応するチェック・ボックスを選択して指定してください (図 10 を参照)。

図 11. 「Build Options (ビルド・オプション)」タブのセットアップ
Eclipse で NDK エントリーの詳細を編集する「Build Options (ビルド・オプション)」タブのスクリーン・ショット

OK」をクリックして設定内容を確認したら、この NDK ビルド・ツールが「Builders (ビルダー)」リストの先頭にくるまで「Up (上へ)」ボタンをクリックして、このツールをリストの最初のエントリーとして設定します (図 7 を参照)。

ビルダーが適切にセットアップされていることをテストするには、Eclipse 内で ibmphotophun.c ソース・ファイルを右クリックして選択し、テキスト・エディターで開きます。ファイルに単純な変更を加えて保存してください。すると、コンソール・ウィンドウに NDK ツール・チェーンの出力が表示されるはずです (図 11 を参照)。C コードにエラーがある場合には、該当する部分が赤で示されます。

図 12. Eclipse IDE のコンソールに表示された NDK 出力
gdb-setup を実行することによる Eclipse のコンソール出力の例を示すスクリーン・ショット

ビルド・プロセスに NDK を組み込むことによって、ビルド環境についてほとんど心配せずに、コードの作成に専念できるようになります。アプリケーション・ロジックを変更する必要が出てきたとしても、何も問題はありません。Java コードを変更して、ファイルを保存すればよいのです。画像処理アルゴリズムに微調整を加える場合も、心配ご無用です。C ルーチンを変更してファイルを保存するだけで、後のことはすべて、Eclipse と ADT プラグインが処理してくれます。


まとめ

このチュートリアルでは、Android NDK を使用して、C プログラミング言語の機能を統合する例を紹介しました。この例で使用した関数は、オープンソース/パブリック・ドメインの画像処理アルゴリズムを見本としたものです。Android NDK の助けがあれば、同じように、Android プラットフォームに対応した有効なあらゆる C コードを利用することができます。このチュートリアルでは、Eclipse 内で NDK を使用する仕組みの他、画像処理に関する基本的な概念も学びました。

参考文献

学ぶために

製品や技術を入手するために

  • Android SDK をダウンロードしてください。この公式 Android 開発者向けサイトでは、API リファレンスや Android に関する最新ニュースも入手できます。V1.5 以降が有効です。このチュートリアルで使用した Android SDK V8 は、Android リリース 2.2 (Froyo) をサポートします。
  • Android NDK をダウンロードしてください。チュートリアルで使用した NDK リリースは r4b です。
  • Android はオープンソースです。ソース・コードは、Android Open Source Project から入手できます。
  • 最新の Eclipse IDE を入手してください。このチュートリアルでは V3.4.2 を使用しました。
  • IBM ソフトウェアの試用版を使用して、次のオープンソース開発プロジェクトを革新してください。ダウンロード、あるいは DVD で入手できます。
  • IBM 製品の評価版をダウンロードするか、あるいは IBM SOA Sandbox のオンライン試用版で、DB2、Lotus、Rational、Tivoli、および WebSphere などが提供するアプリケーション開発ツールやミドルウェア製品を試してみてください。

議論するために

  • developerWorks blogs から developerWorks コミュニティーに加わってください。
  • developerWorks コミュニティーに参加してください。ここでは他の developerWorks ユーザーとのつながりを持てる他、開発者が主導するブログ、フォーラム、グループ、ウィキを調べることができます。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source
ArticleID=755560
ArticleTitle=Android NDK で既存の C コードを再利用する
publish-date=09092011