jamrubyのこれから

jamrubyをちゃんとやろうかなぁーと思いまして、
今回jamruby用にドメインとったりする作業をしています。
まだもうちょっと時間がかかりますが、
準備が出来次第jamrubyのgithubアカウントを別に用意したり、
エントランス用のページを用意したりしようかなと思います。

開発ももちろん続けます。
とりあえず現状でできる範囲を拡大して、

  • Ruby側への動的なメソッドなどの追加
  • Java側のメソッド呼び出し機能の提供
  • Android依存部分の切り出し(Android以外のPlatformのサポート)

などをしたいなぁと考えています。


とりあえず準備が出来次第、また改めてこのブログにでも続報を掲載しようと思います。

mrubyの話というかただのC言語の話

つい先日mrubyのissue trackingを使ってとある議論をしていました。
該当のページはこれですね。

Inappropriate assumption about the pointer address.

ここで何人かのお話にあったのは、ざっくりと以下の3つのことかなと思っています。
ちなみに内容はmrubyというよりはmruby中のコードがC言語の規格にてらして問題があるかどうかって話です。

  1. NULLが0でないケースがあって、その場合にpointerとvalueの変換および比較が問題になるケースがあるよってこと。
  2. intptr_tをvoid*に変換してさらにそれを元に戻せることが保証されてるか?
  3. intptr_tが完全にportableでないから使いたくないよ。


で、このうち1.については、いくつかの前提を置くことでクリアしていることがわかりました。
次の2.については、C言語の規格上、intptr_tをvoid*に変換することは許可されているので問題無いわけです。
(詳しくはC言語規格書のドラフト版(リンク先PDF注意)の「7.20.1.4 Integer types capable of holding object pointers」とか読むと良いですね。)
最後の3.については、intptr_tが関数ポインタを保持できないというお話かなと思います。
これは言語規格上許されていない(pointer to functionとpointer to objectまたはpointer to voidの相互変換ができると書いていない)ので、そもそもやっちゃダメよということですね。


話の中でunion使おうぜってことになってるんですが、なぜunionの話になってるのか私にはいまいち理解できないんだけど、とりあえずフォローアップの記事ということで。

jamrubyのサンプルアプリ作った

jamrubyを使ったサンプルアプリを作って公開しました。

当然buildにはjamrubyが必要で、jamrubyを使うってことはmrubyも必要になります。


ということで、ここに置いてあります。
Eclipseで適当にimportして使って下さい。

https://github.com/crimsonwoods/JamrubyApp

$ git clone https://github.com/crimsonwoods/JamrubyApp.git

起動するとこんな感じのちょーださださ手抜き画面が出てきます。

f:id:crimsonwoods:20120517013239p:plain

画面上部のEditTextにRubyのコードを書いて、「Run」ボタンを押して実行です。
最後に評価された値の内容が、画面下部のTextViewに吐き出されます。
標準出力へ印字した文字は、現状ではlogcatに吐き出すようになっています。


ということでなんとなーくRubyのコードが動いてるねーというのは見れるかなと思います。


ちなみにRubyのコードに対して何か値を渡したいときは、jamruby.runの第2引数以降に実行時引数を書くことができます。
ここに書いた引数はARGVを通してRubyのコードからアクセスすることができます。
中で何してるかは、コード見てください。


ソースコードのライセンスとか特に考えてなかったけど、何も考えずにmrubyと同じMITライセンスで良いかなぁとか。

mrubyをndk-buildするのに必要なもの公開しました。

ということで公開しました。

注意点は、buildするまえにbisonを忘れずに入れておくことと、
jniディレクトリ内でndk-buidを呼び出すことくらいです。
細かいことはREADMEにコマンドをそのまま書いておいたので、そちらを見てください。

https://github.com/crimsonwoods/mruby_ndk-build

Enjoy mruby!

jamrubyというものを作り始めました

せっかくmrubyを触ったので、mrubyの内部実装に使われている各種APIを直接Androidのアプリから触れる様にしたいなと思いました。
まぁ、やることと言えば頑張ってしこしこJNIのラッパーを書くだけなので、やればそのうちできるかなという感じですね。
ということで、作りはじめてみました。

名前はjamrubyという名前にしました。Javaのjaとmrubyをくっつけただけという非道いネーミングですが、細かいことは(ry

ソースコードはgithubに突っ込んであります。

https://github.com/crimsonwoods/jamruby

これはただのLibrary Projectなので、別でActivityを起こして、そこから参照するようにしてください。
JNIの部分はndk-buildするようになっていますが、buildにはmrubyのソースとビルド済みのバイナリが必要になります。
また、Android.mk中に、環境に合わせてmrubyの場所を記述する必要があります。
ビルドの手順なんかは、時間のあるときにまとめて公開できればなぁと思っています。


Activityの中ではこんな感じでrubyのcodeを呼んで実行できます。
ただし、出力は標準出力に吐き出されますので、そのままだと何が出力されているかはわかりません。
標準出力に吐き出されたものを見る場合は、↓の「Viewing stdout and stderr」にある設定を行なって下さい。

http://developer.android.com/guide/developing/tools/adb.html

try {
    Jamruby.getInstance().run("p \"Hello, mruby!\"");
} finally {
    Jamruby.getInstance().close();
}

このコードでは、mrubyで以下のことを行ったときと同様の動作を行います。

$ mruby -e 'p "Hello, mruby!"'

mrubyをAndroid用にNDKでビルドする

Rubyを勉強するか!と思い立ったので、とりあえずmrubyをbuildしてみます。

<追記>
ここにbuildに必要なものとか手順を公開しています。
</追記>

gccのcross-compileで普通にbuildしても良いのですが、それは先人がすでにやっているようなので、
NDKに付属しているndk-buildでbuildできるようにしてみたいと思います。

ビルドに必要なものでgccとか以外だと、

  1. Android NDK
  2. bison

くらいでしょうか。


やることはこんな感じ。

  1. mrubyのソースコード取得
  2. android用のディレクトリ作成
  3. ホスト用のbuild
  4. NDK用のAndroid.mk作成
  5. NDKでbuild


ということでやりましょう。

$ git clone https://github.com/mruby/mruby.git
$ cd mruby
$ mkdir -p android/jni
$ make

ここまでで、ホスト用のbuildが終わります。
次にAndroid.mkをandroid/jniに作ります。
こんな感じです。

LOCAL_PATH  := $(call my-dir)
BASE_CFLAGS := -Wall -Werror-implicit-function-declaration -O3
RUBY_ROOT   := ../..
YACC        := bison
YC          := $(RUBY_ROOT)/src/y.tab.c
YSRC        := $(RUBY_ROOT)/src/parse.y
DLIB        := $(RUBY_ROOT)/mrblib/mrblib.ctmp
RLIB        := $(RUBY_ROOT)/mrblib/mrblib.rbtmp
MRBS        := $(RUBY_ROOT)/mrblib/*.rb

include $(CLEAR_VARS)

$(shell $(YACC) -o $(YC) $(YSRC))

LOCAL_MODULE    := mruby
LOCAL_CFLAGS    := $(BASE_CFLAGS)
LOCAL_C_INCLUDES:= $(RUBY_ROOT)/include $(RUBY_ROOT)/src
LOCAL_SRC_FILES := $(RUBY_ROOT)/tools/mruby/mruby.c
LOCAL_LDLIBS    := -lm
LOCAL_STATIC_LIBRARIES := mruby_lib mrblib

include $(BUILD_EXECUTABLE)

include $(CLEAR_VARS)

LOCAL_MODULE           := mruby_lib
LOCAL_CFLAGS           := $(BASE_CFLAGS)
LOCAL_C_INCLUDES       := $(RUBY_ROOT)/include $(RUBY_ROOT)/src
LOCAL_SRC_FILES        := $(wildcard $(RUBY_ROOT)/src/*.c)
LOCAL_LDLIBS           :=
LOCAL_SHARED_LIBRARIES :=

include $(BUILD_STATIC_LIBRARY)

include $(CLEAR_VARS)

$(shell cat $(MRBS) > $(RLIB))
$(shell $(RUBY_ROOT)/bin/mrbc -Bmrblib_irep -o$(DLIB) $(RLIB))
$(shell cat $(RUBY_ROOT)/mrblib/init_mrblib.c $(DLIB) > $(RUBY_ROOT)/mrblib/mrblib.c)

LOCAL_MODULE           := mrblib
LOCAL_CFLAGS           := $(BASE_CFLAGS)
LOCAL_C_INCLUDES       := $(RUBY_ROOT)/include $(RUBY_ROOT)/src
LOCAL_SRC_FILES        := $(RUBY_ROOT)/mrblib/mrblib.c
LOCAL_LDLIBS           :=
LOCAL_SHARED_LIBRARIES :=

include $(BUILD_STATIC_LIBRARY)

もともと入っているMakefileを元に適当に作ってみました。

あとは適当にApplicaiton.mkとかを用意して、ndk-buildするだけですね。

OpenCVで特定の色領域だけ抜き出すコード(JNI版)

OpenCV for Androidで特定の色領域だけ抜き出すコードを書きました。


OpenCV for AndroidではMatクラスのnative instanceはMat.nativeObjで取得できます。
ここには単純にOpenCV内部のMatクラスへのポインタが格納されているので、えいやっとそいつを取ってきて、あとはゴニョゴニョするだけですね。
処理自体はHSV色空間でやっているので、RGB(A)→HSV→RGBAと変換しています。
具体的には特定範囲の色相で、且つ一定値以上の彩度・明度をもった色を残してあとは黒で塗るということをしています。

#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"

#include <android/log.h>

#define LOG_TAG "android_log_tag"
#define LOGD(...) (void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__);

static void throw_CvException(JNIEnv *env, cv::Exception &e);

JNIEXPORT void JNICALL Java_jni_class_fqn_colorMask
  (JNIEnv *env, jclass clazz, jlong src, jlong dst, jint minHue, jint maxHue, jint minSaturation, jint minValue)
{
    try {
        cv::Mat const *srcMat = static_cast<cv::Mat const *>(reinterpret_cast<void const *>(src));
        cv::Mat *dstMat = static_cast<cv::Mat*>(reinterpret_cast<void *>(dst));

        std::vector<cv::Mat> hsv;

        cv::Mat hsvMat;
        cv::cvtColor(*srcMat, hsvMat, CV_BGR2HSV, 3);
        cv::split(hsvMat, hsv);

        int const c = srcMat->cols;
        int const r = srcMat->rows;
        for (int y = 0; y < c; ++y) {
            for (int x = 0; x < r; ++x) {
                int const h = (hsv[0].at<uint8_t>(x, y) & 0xff) << 1;
                bool isMasked = false;
                if ((minHue <= h) && (h <= maxHue)) {
                    int const s = hsv[1].at<uint8_t>(x, y) & 0xff;
                    if (minSaturation <= s) {
                        int const v = hsv[2].at<uint8_t>(x, y) & 0xff;
                        if (minValue <= v) {
                            isMasked = true;
                        }
                    }
                }
                if (!isMasked) {
                    hsv[1].at<uint8_t>(x, y) = 0;
                    hsv[2].at<uint8_t>(x, y) = 0;
                }
            }
        }

        cv::Mat merged;
        cv::merge(hsv, merged);
        cv::cvtColor(merged, *dstMat, CV_HSV2BGR, 4);
    } catch (cv::Exception& e) {
        LOGD("colorMask() catched cv::Exception: %s", e.what());
        throw_CvException(env, e);
    }
}

static void throw_exception(JNIEnv *env, char const * const className, char const * const message)
{
    jclass cls = env->FindClass(className);
    if (!cls) {
        cls = env->FindClass("java/lang/Exception");
    }
    env->ThrowNew(cls, message);
    env->DeleteLocalRef(cls);
}

static void throw_CvException(JNIEnv *env, cv::Exception &e)
{
    throw_exception(env, "org/opencv/core/CvException", e.what());
}

NDKでbuildするにはopencvのshared libraryが必要なので、こんな感じでAndroid.mkを書いてみました。

LOCAL_PATH := $(call my-dir)
OPENCV_PATH := /path/to/opencv
OPENCV_LIBS := /path/to/opencv/libs

# dummy.
# copy libopencv_java.so into "obj/local/armeabi" manually.
include $(CLEAR_VARS)
LOCAL_MODULE    := opencv_java
include $(BUILD_SHARED_LIBRARY)

# build library
include $(CLEAR_VARS)

LOCAL_MODULE    := your_project
LOCAL_CPPFLAGS  := -Wall -Werror -O2
LOCAL_SRC_FILES := your_source.cpp
LOCAL_LDLIBS    += -llog
LOCAL_C_INCLUDES := $(OPENCV_PATH)/modules/core/include $(OPENCV_PATH)/modules/imgproc/include
LOCAL_SHARED_LIBRARIES := libopencv_java

include $(BUILD_SHARED_LIBRARY)

中に書いてあるように、libopencv_java.soをリンクさせるためにdummy projectを作っています。
こんな感じのshell scriptを書いて適当にlibopencv_java.soをコピると良いかと思います。

#!/bin/sh

OPENCV_LIB=/path/to/opencv/libs

mkdir -p ../obj/local/armeabi
mkdir -p ../obj/local/armeabi-v7a

cp $OPENCV_LIB/armeabi/libopencv_java.so ../obj/local/armeabi
cp $OPENCV_LIB/armeabi-v7a/libopencv_java.so ../obj/local/armeabi-v7a

ndk-build

if [ -e ../libs/armeabi/libopencv_java.so ]; then
    rm ../libs/armeabi/libopencv_java.so
fi
if [ -e ../libs/armeabi-v7a/libopencv_java.so ]; then
    rm ../libs/armeabi-v7a/libopencv_java.so
fi

armeabi-v7aがいらないひとは適当にけずってください。