Yearly Archives: 2015

2015-12-30

パンのトースト

トースターが欲しいけど置く場所が無いからオーブンレンジのトースト機能を使おうと思ったら電子レンジで作るトーストのレシピを見つけた。

電子レンジで目玉焼きトースト by ジュリさんぼ★ クックパッド

ハムやベーコンは無かったのでソーセージのスライスで代用。ビスマルク風。

うーん、十分美味しいんだけど、やっぱり食感はグニャッとしてる。

自宅の電子レンジにはトースト機能が付いているのだけど、どうもピンと来なくて使ってこなかった。電子レンジの庫内でパンを一切れ焼くのがどうにも無駄な気がしてしまって。時間もかかるんじゃ無いだろうか? いや、やってみれば案外慣れるのかもしれない。しばらく試してみるか……。

本当は専用のトースターが欲しいところだけど置く場所が無いんだよね。キッチンは半分物置になってしまっているので。

2015-12-30

釜揚げ氷見うどん

お土産の氷見うどん(乾麺)が残っている。ざるで食べるのが一番美味しいと聞いたのだけど、さすがにこの寒さ。それだとどうにも食べる気がしない。

かといってかけうどんにするのはどうにも野暮ったい。付け汁で食べたい。

そうだ、温かい付け汁にすれば良いんだ。あつもり? いや、ざるにあけて水で締めるのは冷たいから嫌だ。ざるも洗わないと行けなくなるし。

となれば水で締めない、いわゆる釜揚げに相当するのだろう。

固いのは嫌なので、ゆで時間はしっかりと。

うん、これはうまい。

やっぱり寒い日は温かい方が美味しい。

2015-10-14

2015秋の新番組

今期はどうでしょうか。今のところビビっと来るものはあまり無いかなぁと。

10/01 18:00 TX かみさまみならい ヒミツのここたま
10/01 25:29 NTV ルパン三世 新シリーズ
10/01 25:46 TBS Lance N’Masques(ランス・アンド・マスクス)
10/01 26:05 TX × きょーふ!ゾンビ猫
10/01 26:16 TBS ヤング ブラック・ジャック
10/02 23:00 MX ハッカドール (※ウルトラスーパーアニメタイム)
10/02 23:00 MX 影鰐-KAGEWANI- (※ウルトラスーパーアニメタイム)
10/02 23:00 MX ミス・モノクローム -The Animation- 3 (第3期) (※ウルトラスーパーアニメタイム)
10/02 24:30 MX ヘヴィーオブジェクト
10/02 25:55 TBS K RETURN OF KINGS
10/03 –:-- NTV Peeping Life TV シーズン1 ??
10/03 09:14 TX いとしのムーコ
10/03 17:35 NHK THUNDERBIRDS ARE GO (サンダーバード・アー・ゴー)
10/03 23:30 MX 学戦都市アスタリスク
10/04 17:00 TBS × 機動戦士ガンダム 鉄血のオルフェンズ
10/04 22:00 MX DIABOLIK LOVERS -MORE,BLOOD-(第2期)
10/04 22:15 MX 雨色ココア Rainy colorへようこそ!(第2期)
10/04 22:27 MX 小森さんは断れない!
10/04 22:30 MX コメット・ルシファー
10/04 23:00 MX コンクリート・レボルティオ 超人幻想
10/04 23:30 MX × 進撃!巨人中学校
10/04 24:30 MX 落第騎士の英雄譚
10/04 25:05 TX ワンパンマン
10/05 24:00 MX スタミュ -高校星歌劇-
10/05 25:00 MX × JKめし!
10/05 25:35 TX おそ松さん
10/06 25:05 MX × アニサン劇場
10/07 18:45 ETV 探偵チームKZ(カッズ)事件ノート
10/07 23:30 MX Dance with Devils
10/07 24:30 MX 櫻子さんの足下には死体が埋まっている
10/07 25:05 MX 対魔導学園35試験小隊
10/08 22:30 MX 俺がお嬢様学校に「庶民サンプル」としてゲッツされた件
10/08 24:55 CX すべてがFになる -THE PERFECT INSIDER-
10/09 25:23 TX × 牙狼-紅蓮ノ月- (第2期)
10/10 22:30 MX ご注文はうさぎですか?? (第2期)
10/12 24:30 MX ヴァルキリードライヴ マーメイド
10/12 25:11 MX あにトレ!EX
10/14 07:24 TX PEANUTS スヌーピー -ショートアニメ-
2015-08-05 ,

Boost.TestをAndroid NDKで使う(android-ndk-r10e)

Android上でBoost.Testを使ったテストをしてみました。

Android NDKでは通常のAndroidアプリケーションではない、main()から始まるネイティブの実行ファイル(つまりWindowsで言うところのコンソールアプリと言えば良い?)をビルドできるようになっています。これを使えばAndroid端末上でテストを実行できます。

以下に例を書いておきます。hello_testというディレクトリを作って、以下のファイルを用意し、run.shを実行するとテストをビルドして端末へアップロードして実行します。ライブラリをビルドするのは面倒くさいので、Boostはヘッダーだけの使用にとどめてあります。

hello_test/jni/Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_CPP_FEATURES += exceptions
LOCAL_CPP_FEATURES += rtti
LOCAL_MODULE := hello_test #←実行ファイル名になる。

# ソースファイルはワイルドカードで取得してみる。
# LOCAL_PATHはjniディレクトリを指している。
# カレントディレクトリは一つ上なので注意。
# LOCAL_SRC_FILESにはjniディレクトリからの相対パスで指定する必要がある。
SRC_FILES = $(wildcard $(LOCAL_PATH)/*.cpp)
LOCAL_SRC_FILES := $(SRC_FILES:$(LOCAL_PATH)/%=%)

# Boost C++ Libraryヘッダーファイルへのパスを指定する。
LOCAL_C_INCLUDES += c:/boost_he_no_path_wo_shiteisuru/boost_1_56_0

# これが無いとAndroid 5.0以降で実行できない。
LOCAL_CFLAGS += -pie -fPIE
LOCAL_LDFLAGS += -pie -fPIE

include $(BUILD_EXECUTABLE) #実行ファイルをビルドする。

実行ファイルを生成するには、最後に include $(BUILD_EXECUTABLE) を指定するのがミソです。

LOCAL_MODULE は実行ファイルの名前になります。

Boostのヘッダーへのパスは LOCAL_C_INCLUDES で指定してください。

実行時に"error: only position independent executables (PIE) are supported" というエラーが出ることがあります。Android 5.0以降だと出るようです。 -pie -fPIE というオプションを指定すると大丈夫なようです。

hello_test/jni/Application.mk

APP_ABI := all
APP_STL := c++_static
APP_PLATFORM := android-14 #低いとコンパイルが通らない

APP_PLATFORM を指定しなかったり、APIレベルが低いと必要な関数が無いためにコンパイルエラーにとなります。

hello_test/jni/main.cpp

#define BOOST_TEST_MAIN
#include <boost/test/included/unit_test.hpp>

hello_test/jni/test_hoge.cpp

#define BOOST_TEST_NO_LIB
#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_SUITE(hoge_test)

BOOST_AUTO_TEST_CASE(test1)
{
    int a = 1;
    int b = 2;
    BOOST_CHECK_EQUAL(a + b, 3);
    BOOST_CHECK_EQUAL(a - b, -1);
    BOOST_CHECK_EQUAL(a / b, 0);
}

BOOST_AUTO_TEST_CASE(test2)
{
    double a = 1.0;
    double b = 2.0;

    BOOST_CHECK_EQUAL(a + b, 3.0);
    BOOST_CHECK_EQUAL(a - b, -1.0);
    BOOST_CHECK_EQUAL(a / b, 0.5);
}

BOOST_AUTO_TEST_SUITE_END()

hello_test/run.sh

#!/usr/bin/env bash

dir=/data/local/tmp/hello_test
bin=hello_test

# Build
ndk-build

# Detect Device's ABI
abi=$(adb shell getprop ro.product.cpu.abi | cut -d, -f1 | tr -d '\r')
echo "abi=$abi"

# Make directory
echo "mkdir $dir"
adb shell "mkdir $dir 2>/dev/null"

# Upload
adb push libs/$abi/$bin $dir

# Execute
adb shell "chmod 755 $dir/$bin"
adb shell $dir/$bin

# Delete
adb shell rm $dir/$bin
adb shell rmdir $dir

接続している端末のABIを取得するには、 adb shell getprop ro.product.cpu.abi を実行すればよいみたいです。カンマ区切りで複数列挙される場合があります。

/data/local/tmp というディレクトリにアップロードして実行し、終わったら削除します。

2015-07-24

2015夏の新番組

なんとか1~2話程度見終わりました。

2015-06-28 ,

C++によるAndroid NDK NativeActivityサンプル

Android NDKのnative-activityサンプルってC言語で書かれているので分かりづらいですよね、ということで、C++に書き直してみました。

Win32のメッセージループまわりをクラス化したことがある人なら馴染みのある形だと思います。

まずは表示まわりと加速度センサーをログ出力する部分を除いた純粋なアプリケーションクラスだけの例です。

#include <android/log.h>
#include <android_native_app_glue.h>

#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "nativeactivitytest", __VA_ARGS__))
#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "nativeactivitytest", __VA_ARGS__))
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "nativeactivitytest", __VA_ARGS__))

class ThisApp
{
    android_app *app_;

    struct SavedState
    {
    };
    SavedState state_;
public:
    explicit ThisApp(android_app *app)
        : app_(app)
    {
        app->userData = this;
        app->onAppCmd = handleCmdStatic;
        app->onInputEvent = handleInputStatic;
    }
    ThisApp(const ThisApp &) = delete;
    ThisApp &operator=(const ThisApp &) = delete;
    ~ThisApp()
    {
        app_->userData = nullptr;
        app_->onAppCmd = nullptr;
        app_->onInputEvent = nullptr;
    }

private:
    static void handleCmdStatic(android_app *app, int32_t cmd)
    {
        reinterpret_cast<ThisApp *>(app->userData)->handleCmd(cmd);
    }
    static int32_t handleInputStatic(android_app *app, AInputEvent *event)
    {
        return reinterpret_cast<ThisApp *>(app->userData)->handleInput(event);
    }

    void handleCmd(int32_t cmd)
    {
        switch(cmd){
        // Window
        case APP_CMD_INIT_WINDOW: LOGI("handleCmd(APP_CMD_INIT_WINDOW)"); break;
        case APP_CMD_TERM_WINDOW: LOGI("handleCmd(APP_CMD_TERM_WINDOW)"); break;
        case APP_CMD_WINDOW_RESIZED: LOGI("handleCmd(APP_CMD_WINDOW_RESIZED)"); break;
        case APP_CMD_WINDOW_REDRAW_NEEDED: LOGI("handleCmd(APP_CMD_WINDOW_REDRAW_NEEDED)"); break;
        case APP_CMD_CONTENT_RECT_CHANGED: LOGI("handleCmd(APP_CMD_CONTENT_RECT_CHANGED)"); break;

        case APP_CMD_GAINED_FOCUS: LOGI("handleCmd(APP_CMD_GAINED_FOCUS)"); break;
        case APP_CMD_LOST_FOCUS: LOGI("handleCmd(APP_CMD_LOST_FOCUS)"); break;

        // Activity State
        case APP_CMD_START: LOGI("handleCmd(APP_CMD_START)"); break;
        case APP_CMD_RESUME: LOGI("handleCmd(APP_CMD_RESUME)"); break;
        case APP_CMD_PAUSE: LOGI("handleCmd(APP_CMD_PAUSE)"); break;
        case APP_CMD_STOP: LOGI("handleCmd(APP_CMD_STOP)"); break;
        case APP_CMD_DESTROY: LOGI("handleCmd(APP_CMD_DESTROY)"); break;

        case APP_CMD_SAVE_STATE:
            LOGI("handleCmd(APP_CMD_SAVE_STATE)");
            backupState();
            break;
        }
    }
    int32_t handleInput(AInputEvent *event)
    {
        LOGI("handleInput(type=%d, deviceId=%d, source=%d)",
             AInputEvent_getType(event),
             AInputEvent_getDeviceId(event),
             AInputEvent_getSource(event));

        switch(AInputEvent_getType(event)){
        case AINPUT_EVENT_TYPE_KEY:
            LOGI("handleKeyEvent(action=%d, flags=0x%x, keycode=%d, scancode=%d, meta=%d, repeatcount=%d)",
                 AKeyEvent_getAction(event),
                 AKeyEvent_getFlags(event),
                 AKeyEvent_getKeyCode(event),
                 AKeyEvent_getScanCode(event),
                 AKeyEvent_getMetaState(event),
                 AKeyEvent_getRepeatCount(event));
            //return 1;
            break;
        case AINPUT_EVENT_TYPE_MOTION:
            LOGI("handleMotionEvent(action=%d, flags=0x%x, meta=%d, buttonstate=%d, edgeflag=%d, p0x=%f, p0y=%f",
                 AMotionEvent_getAction(event),
                 AMotionEvent_getFlags(event),
                 AMotionEvent_getMetaState(event),
                 AMotionEvent_getButtonState(event),
                 AMotionEvent_getEdgeFlags(event),
                 AMotionEvent_getX(event, 0),
                 AMotionEvent_getY(event, 0));
            //size_t AMotionEvent_getPointerCount
            //d AMotionEvent_getPointerId
            //d AMotionEvent_getToolType
            //f AMotionEvent_getRawX
            //...etc(see:<ndkdir>/platforms/android-*/arch-*/usr/include/android/input.h)
            //return 1;
            break;
        }
        return 0;
    }

    void backupState()
    {
        LOGI("backupState");
        app_->savedStateSize = sizeof(SavedState);
        app_->savedState = malloc(sizeof(SavedState));
        *reinterpret_cast<SavedState *>(app_->savedState) = state_;
    }

    void restoreState()
    {
        LOGI("restoreState");
        if(app_->savedState && app_->savedStateSize == sizeof(SavedState)){
            state_ = *reinterpret_cast<const SavedState *>(app_->savedState);
        }
    }


    int getNextEventDelayTimeMilli()
    {
        // 遅くとも一定時間後までに processInEventLoop() が呼ばれるようにするには、
        // ここでミリ秒を返す。
        // それより早い時間で呼ばれることもある。
        return -1;
    }
    void processInEventLoop()
    {
        // なんらかのイベントが処理された後または、
        // getNextEventDelayTimeMilli()が返した時間経過したときに呼ばれる。
    }

    int looperIdNext_ = LOOPER_ID_USER;
    int getNewLooperId()
    {
        return looperIdNext_++;
    }
    void processUserLooperId(int looperId)
    {
        LOGI("processUserLooperId looperId=%d", looperId);
        // ユーザー定義のLooper ID(LOOPER_ID_USER以上の値)を持つイベントが発生したときに呼ばれる。
    }


    void destroyApplication()
    {
        LOGI("destroyApplication");
    }

public:
    void run()
    {
        // Restore app state
        restoreState();

        // event loop
        for(;;){
            int ident;
            int events;
            android_poll_source* source;

            while((ident = ALooper_pollAll(getNextEventDelayTimeMilli(), NULL, &events, (void**)&source)) >= 0){
                // Process this event.
                if(ident >= 0 && ident < LOOPER_ID_USER){
                    // Dispatch LOOPER_ID_MAIN, LOOPER_ID_INPUT
                    if (source != NULL) {
                        source->process(app_, source);
                    }
                }
                else if(ident >= LOOPER_ID_USER){
                    processUserLooperId(ident);
                }

                // Check if we are exiting.
                if(app_->destroyRequested != 0){
                    destroyApplication();
                    return;
                }
            }

            processInEventLoop();
        }
    }
};

void android_main(android_app *state)
{
    app_dummy();

    ThisApp thisApp(state);
    thisApp.run();
}
  • アクティビティの状態変更やタッチ入力など、イベントの発生時にログを出力するようにしました。どのタイミングでどの関数が呼ばれるのかを把握することが大切です。
  • アクティビティの状態を保存・復元するコードのひな形も一応入ってます。複雑なアプリの場合はちゃんとしたシリアライズの仕組みを入れるべきでしょう。
  • イベントループのタイムアウトを決定する仕組みのひな形が入ってます。このあたりを使うと独自のタイマークラスなんかが作れるんじゃないかと思います。
  • ユーザー定義のLooper IDという概念がandroid_native_app_glueにあるのですが、それを処理する仕組みも入ってます。native-activityサンプルでは加速度センサーイベントの処理にユーザー定義のIDを使っています。でも、加速度センサーの場合はコールバックを使っても実装できるので、必ずしもユーザー定義のIDを使わなくても良い気がします。

加速度センサー

native-activityサンプルのように加速度センサーの値をログへ出力するには、上のソースを次のように改変します。元のサンプルではLOOPER_ID_USERを使用していましたが、調べたところASensorManager_createEventQueueのコールバック引数を使えば使わずに済みそうだったのでそのようにしてみました。

#include <android/sensor.h>

//..略...

class SensorMonitor
{
    ASensorManager *sensorManager_ = nullptr;
    const ASensor *accelerometerSensor_ = nullptr;
    ASensorEventQueue *sensorEventQueue_ = nullptr;
public:
    SensorMonitor(){}
    SensorMonitor(const SensorMonitor &) = delete;
    SensorMonitor &operator=(const SensorMonitor &) = delete;
    ~SensorMonitor()
    {
        destroy();
    }
    void init(android_app *app)
    {
        if(!sensorEventQueue_){
            sensorManager_ = ASensorManager_getInstance();
            accelerometerSensor_ = ASensorManager_getDefaultSensor(sensorManager_, ASENSOR_TYPE_ACCELEROMETER);
            sensorEventQueue_ = ASensorManager_createEventQueue(sensorManager_, app->looper, ALOOPER_POLL_CALLBACK, handleSensorStatic, this);
        }
    }
    void destroy()
    {
        stop();
        if(sensorEventQueue_){
            ASensorManager_destroyEventQueue(sensorManager_, sensorEventQueue_);
            sensorEventQueue_ = nullptr;
        }
        accelerometerSensor_ = nullptr;
    }
    void start()
    {
        LOGI("Sensor start");
        if(accelerometerSensor_){
            ASensorEventQueue_enableSensor(sensorEventQueue_, accelerometerSensor_);
            ASensorEventQueue_setEventRate(sensorEventQueue_, accelerometerSensor_, (1000L/60)*1000);
        }
    }
    void stop()
    {
        LOGI("Sensor stop");
        if(accelerometerSensor_){
            ASensorEventQueue_disableSensor(sensorEventQueue_, accelerometerSensor_);
        }
    }
private:
    static int handleSensorStatic(int fd, int events, void *data)
    {
        reinterpret_cast<SensorMonitor *>(data)->handleSensor();
        return 1;
    }

    void handleSensor()
    {
        LOGI("Sensor process");
        if(accelerometerSensor_){
            ASensorEvent event;
            while(ASensorEventQueue_getEvents(sensorEventQueue_, &event, 1) > 0){
                LOGI("accelerometer: x=%f y=%f z=%f",
                     event.acceleration.x,
                     event.acceleration.y,
                     event.acceleration.z);
            }
        }
    }
};

class ThisApp
{
    //...略...
    SensorMonitor sensor_;

public:
    explicit ThisApp(android_app *app)
        : app_(app)
    {
        //...略...
        sensor_.init(app);
    }
    //...略...

    void handleCmd(int32_t cmd)
    {
        //...略...
        case APP_CMD_GAINED_FOCUS:
            LOGI("handleCmd(APP_CMD_GAINED_FOCUS)");
            // When our app gains focus, we start monitoring the accelerometer, etc.
            sensor_.start();
            break;
        case APP_CMD_LOST_FOCUS:
            LOGI("handleCmd(APP_CMD_LOST_FOCUS)");
            // When our app loses focus, we stop monitoring the accelerometer, etc.
            // This is to avoid consuming battery while not being used.
            sensor_.stop();
        //...略...
    }
    //...略...

OpenGL ES

OpenGL ES2.0を使って画面に三角形を表示するには次のように改変します。

#include <memory>
#include <string>
#include <EGL/egl.h>
#include <GLES2/gl2.h>

...略...

// OpenGL Utilities

template<typename FunGetIv, typename FunGetLog>
std::string getGLLogStr(GLuint obj, FunGetIv funGetIv, FunGetLog funGetLog)
{
    GLint len = 0;
    funGetIv(obj, GL_INFO_LOG_LENGTH, &len);
    if(len > 1){
        std::unique_ptr<char[]> infoLog(new char[len]);
        funGetLog(obj, len, nullptr, infoLog.get());
        return std::string(infoLog.get());
    }
    else{
        return std::string();
    }
}
std::string getGLShaderLogInfo(GLuint obj)
{
    return getGLLogStr(obj, glGetShaderiv, glGetShaderInfoLog);
}
std::string getGLProgramLogInfo(GLuint obj)
{
    return getGLLogStr(obj, glGetProgramiv, glGetProgramInfoLog);
}

GLuint loadShader(GLenum type, const char *shaderSrc)
{
    GLuint shader = glCreateShader(type);
    if(shader == 0){
        return 0;
    }

    glShaderSource(shader, 1, &shaderSrc, nullptr);

    glCompileShader(shader);

    GLint compiled = 0;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
    if(!compiled){
        LOGI("glCompileShader failed.\n Error:(%s)\n", getGLShaderLogInfo(shader).c_str());
        glDeleteShader(shader);
        return 0;
    }

    return shader;
}

/**
 * OpenGLES 2.0の初期化からフレームレンダリングまでをカバーしたフレームワークです。
 *
 * このクラスを継承して必要な仮想関数をオーバーライドしてください。
 * リソースの読み込み、解放を行うloadResources()、termResources()やinitializeContextState()、フレームのレンダリングを行うdrawFrame()をオーバーライドしてください。
 *
 * そしてネイティブウィンドウ(ANativeWindow *)が有効になった時点でinitWindow()を、無効になった時点でtermWindow()を呼び出すようにします。
 * ウィンドウへの描画が必要になった時点でpresentFrame()を呼び出してください。
 */
class GLEnvironment
{
    EGLDisplay display_;

    EGLSurface surface_; //window surface
    EGLConfig config_; //frame buffer configuration
    EGLint screenWidth_;
    EGLint screenHeight_;

    EGLContext context_;
public:
    GLEnvironment()
        : display_(EGL_NO_DISPLAY),
          surface_(EGL_NO_SURFACE),
          config_(nullptr),
          screenWidth_(), screenHeight_(),
          context_(EGL_NO_CONTEXT)
    {}
    ~GLEnvironment()
    {
        destroy();
    }
    GLEnvironment(const GLEnvironment &) = delete;
    GLEnvironment &operator=(const GLEnvironment &) = delete;

    bool create(ANativeWindow *window)
    {
        return initContext(window);
    }
    void destroy()
    {
        disconnectDisplay();
    }
    bool recreate(ANativeWindow *window)
    {
        destroy();
        return create(window);
    }

    //
    // Attach/Detach ANativeWindow
    //

    static const bool preserveEGLContextOnPause = true;
    bool initWindow(ANativeWindow *window)
    {
        LOGI("initWindow");
        if(preserveEGLContextOnPause && hasDisplay() && !hasSurface() && hasContext() && isContextInitialized()){
            // Resume (Try create window surface by same config)
            LOGI("Trying to resume");
            if(createSurface(window, config_) && makeContextCurrent(window)){
                LOGI("resume succeeded");
                return true;
            }
            LOGI("resume failed");
        }
        // Recreate all objects.
        return recreate(window);
    }

    void termWindow()
    {
        if(!preserveEGLContextOnPause){
            destroyContext();
        }
        destroySurface();
    }


    //
    // Presentation
    //

    bool presentFrame(ANativeWindow *window)
    {
        if(!isContextInitialized()){
            LOGE("Context not initialized");
            return false;
        }

        drawFrame();
        if(!swap()){
            recreate(window);
            drawFrame();
            if(!swap()){
                LOGE("Swap failed twice");
                return false;
            }
        }
        return true;
    }

private:
    bool swap()
    {
        if(hasSurface()){
            LOGI("swap");
            if(!eglSwapBuffers(display_, surface_)){
                LOGI("eglSwapBuffers failed");
                return false;
            }
        }
        return true;
    }

    virtual void drawFrame(){}

    //
    // Display
    //
private:
    bool hasDisplay() const {return display_ != EGL_NO_DISPLAY;}

    bool connectDisplay()
    {
        if(display_ == EGL_NO_DISPLAY){
            // Get current display.
            const EGLDisplay uninitializedDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
            if(uninitializedDisplay == EGL_NO_DISPLAY){
                // Do not terminate display.
                LOGE("eglGetDisplay failed");
                return false;
            }
            if(!eglInitialize(uninitializedDisplay, 0, 0)){
                LOGE("eglInitialize failed");
                return false;
            }
            display_ = uninitializedDisplay;
        }
        return true;
    }
    void disconnectDisplay()
    {
        destroyContext();
        destroySurface();

        if(display_ != EGL_NO_DISPLAY){
            LOGI("eglTerminate");
            eglTerminate(display_);
            display_ = EGL_NO_DISPLAY;
        }
    }

    //
    // Config
    //
private:
    static EGLConfig chooseConfig(EGLDisplay display)
    {
        struct DesiredConfig
        {
            int depth;
            DesiredConfig(int depth):depth(depth){}
        };
        const DesiredConfig desiredConfigs[] = {DesiredConfig(24), DesiredConfig(16)};
        const DesiredConfig * const dcEnd = desiredConfigs + sizeof(desiredConfigs)/sizeof(desiredConfigs[0]);
        const DesiredConfig *dc;
        EGLConfig config = nullptr;
        for(dc = desiredConfigs; dc != dcEnd; ++dc){
            const EGLint desiredAttribs[] = {
                EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, //Request opengl ES2.0
                EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
                EGL_BLUE_SIZE, 8,
                EGL_GREEN_SIZE, 8,
                EGL_RED_SIZE, 8,
                EGL_DEPTH_SIZE, dc->depth,
                EGL_NONE };

            EGLint numConfigs;
            if(!eglChooseConfig(display, desiredAttribs, &config, 1, &numConfigs)){
                LOGE("eglChooseConfig failed");
                return nullptr;
            }
            if(numConfigs == 1){
                break;
            }
        }
        if(config == nullptr){
            LOGE("no EGL config");
            return nullptr;
        }
        LOGI("match config depth=%d", dc->depth);
        return config;
    }


    //
    // Window Surface
    //
private:
    bool hasSurface() const { return surface_ != EGL_NO_SURFACE;}

    bool createSurface(ANativeWindow *window, EGLConfig desiredConfig = nullptr)
    {
        if(!connectDisplay()){
            return false;
        }

        if(surface_ == EGL_NO_SURFACE){
            const EGLConfig config = desiredConfig ? desiredConfig : chooseConfig(display_);
            if(!config){
                LOGE("chooseConfig failed");
                return false;
            }

            // ANativeWindow_setBuffersGeometry
            EGLint format;
            eglGetConfigAttrib(display_, config, EGL_NATIVE_VISUAL_ID, &format);
            ANativeWindow_setBuffersGeometry(window, 0, 0, format);

            // Create surface.
            surface_ = eglCreateWindowSurface(display_, config, window, nullptr);
            if(surface_ == EGL_NO_SURFACE){
                LOGE("eglCreateWindowSurface failed. error=%d", eglGetError());
                return false;
            }
            config_ = config;

            eglQuerySurface(display_, surface_, EGL_WIDTH, &screenWidth_);
            eglQuerySurface(display_, surface_, EGL_HEIGHT, &screenHeight_);
            LOGI("screen size %d x %d", screenWidth_, screenHeight_);
        }
        return true;
    }
    void destroySurface()
    {
        //detachCurrentContext(); // 後続のunloadResourcesのために今はdetachしないことにする。

        if(surface_ != EGL_NO_SURFACE){
            LOGI("eglDestroySurface(window surface)");
            eglDestroySurface(display_, surface_);
            surface_ = EGL_NO_SURFACE;
        }
    }
public:
    int getScreenWidth() const {return screenWidth_;}
    int getScreenHeight() const {return screenHeight_;}

    //
    // Context
    //
private:
    bool hasContext() const{ return context_ != EGL_NO_CONTEXT;}
    bool createContext(ANativeWindow *window)
    {
        if(!createSurface(window)){ //Needs display_ & config_
            return false;
        }

        if(context_ == EGL_NO_CONTEXT){
            const EGLint attribs[] = {
                EGL_CONTEXT_CLIENT_VERSION, 2, //Request opengl ES2.0
                EGL_NONE };
            context_ = eglCreateContext(display_, config_, nullptr, attribs);
            if(context_ == EGL_NO_CONTEXT){
                LOGE("eglCreateContext failed");
                return false;
            }
        }
        return true;
    }
    void destroyContext()
    {
        termContext();

        if(context_ != EGL_NO_CONTEXT){
            LOGI("eglMakeCurrent null");
            eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
            LOGI("eglDestroyContext");
            eglDestroyContext(display_, context_);
            context_ = EGL_NO_CONTEXT;
        }
    }

        // Current Context

    bool makeContextCurrent(ANativeWindow *window)
    {
        if(!createSurface(window)){
            return false;
        }
        if(!createContext(window)){
            return false;
        }

        LOGI("eglMakeCurrent");
        if(!eglMakeCurrent(display_, surface_, surface_, context_)){
            LOGE( "eglMakeCurrent failed" );
            return false;
        }
        return true;
    }
    void detachCurrentContext()
    {
        if(display_ != EGL_NO_DISPLAY){
            LOGI("detachCurrentContext");
            eglMakeCurrent(display_, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
        }
    }
    bool isContextCurrent()
    {
        return context_ != EGL_NO_CONTEXT && eglGetCurrentContext() == context_;
    }

        // Context Initialization

    bool contextInitialized_ = false;
protected:
    bool isContextInitialized() const {return contextInitialized_;}
private:
    bool initContext(ANativeWindow *window)
    {
        if(!makeContextCurrent(window)){
            return false;
        }
        if(!contextInitialized_){
            if(!loadResources()){
                return false;
            }
            initializeContextState();
            contextInitialized_ = true;
        }
        return true;
    }
    void termContext()
    {
        if(contextInitialized_){
            unloadResources();
            contextInitialized_ = false;
        }
    }

    virtual bool loadResources(){return true;}
    virtual void unloadResources(){}
    virtual void initializeContextState(){}
};



class ThisAppGraphics : public GLEnvironment
{
    GLuint vertexShader_;
    GLuint fragmentShader_;
    GLuint programObject_;
public:
    ThisAppGraphics()
        : GLEnvironment(),
          vertexShader_(0),
          fragmentShader_(0),
          programObject_(0)
    {
    }
private:
    virtual bool loadResources() override
    {
        LOGI("loadResources");

        const char vShaderStr[] =
            "attribute vec4 vPosition;"
            "void main(){"
            "  gl_Position = vPosition;"
            "}";
        const char fShaderStr[] =
            "precision mediump float;"
            "void main(){"
            "  gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0);"
            "}";
        vertexShader_ = loadShader(GL_VERTEX_SHADER, vShaderStr);
        fragmentShader_ = loadShader(GL_FRAGMENT_SHADER, fShaderStr);
        programObject_ = glCreateProgram();

        glAttachShader(programObject_, vertexShader_);
        glAttachShader(programObject_, fragmentShader_);
        glBindAttribLocation(programObject_, 0, "vPosition");
        glLinkProgram(programObject_);

        GLint linked = 0;
        glGetProgramiv(programObject_, GL_LINK_STATUS, &linked);
        if(!linked){
            LOGI("glLinkProgram failed.\n Error:(%s)\n", getGLProgramLogInfo(programObject_).c_str());
            return false;
        }

        return true;
    }
    virtual void unloadResources() override
    {
        LOGI("unloadResources");
        if(programObject_){
            glDeleteProgram(programObject_);
            programObject_ = 0;
        }
        if(vertexShader_){
            glDeleteShader(vertexShader_);
            vertexShader_ = 0;
        }
        if(fragmentShader_){
            glDeleteShader(fragmentShader_);
            fragmentShader_ = 0;
        }
    }
    virtual void initializeContextState() override
    {
        glEnable(GL_CULL_FACE);
        glDisable(GL_DEPTH_TEST);
        glViewport(0, 0, getScreenWidth(), getScreenHeight());
    }

    virtual void drawFrame() override
    {
        LOGI("drawFrame");
        GLfloat vertices[] = {
            0.0f, 0.5f, 0.0f,
            -0.5f, -0.5f, 0.0f,
            0.5f, -0.5f, 0.0f
        };

        glClearColor(0, 0, 1, 1);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glUseProgram(programObject_);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, vertices);
        glEnableVertexAttribArray(0);
        glDrawArrays(GL_TRIANGLES, 0, 3);
    }
};

class ThisApp
{
    ...略...
    ThisAppGraphics gl_;
    ...略...

    void handleCmd(int32_t cmd)
    {
        switch(cmd){
        case APP_CMD_INIT_WINDOW:
            LOGI("handleCmd(APP_CMD_INIT_WINDOW)");
            // The window is being shown, get it ready.
            if(app_->window){
                gl_.initWindow(app_->window);
                gl_.presentFrame(app_->window);
            }
            break;
        case APP_CMD_TERM_WINDOW:
            LOGI("handleCmd(APP_CMD_TERM_WINDOW)");
            // The window is being hidden or closed, clean it up.
            gl_.termWindow();
            break;

    ...略...

他のプロジェクトでも使い回せるようなコードはGLEnvironmentクラスに収め、このプロジェクトに固有な描画コードはThisAppGraphicsクラスに収めてみました。ThisAppクラスからはapp_->windowが有効になったときにinitWindow()やpresentFrame()を、app_->windowが無効になったときにtermWindow()を呼び出すだけです。

気になった点をいくつか。

  • エミュレータだとPauseしてResumeすると画面が真っ暗になります。コンテキストがロストしているのかもしれませんが特にエラーが起きている様子も無いので原因がよく分かりません。Teapotサンプルも同じ現象が起きます。上ソースのpreserveEGLContextOnPauseをfalseにすれば(EGLContextをPause時に破棄してResume時に作り直せば)解消します。
  • preserveEGLContextOnPauseをfalseにするとポーズ・レジュームのたびにリソースを作り直します。opengl/java/android/opengl/GLSurfaceView.java - platform/frameworks/base - Git at Google を見ると、Q3Dimension MSM7500のときは必ずコンテキストを作り直すようにする等のコードが入っているので、そのような判定も追加すべきかもしれません。
  • 画面を回転させるとNativeActivityは終了して再起動します。これはそういうものっぽいです。嫌ならNativeActivityを使わずにJavaで作ったActivityから呼び出す形で何とかするしかないかも。(2015/08/05追記: APIレベルによるみたい? KOBE GDG: 回転時にActivityを破棄させない方法 )
  • ndk_helperのGLContextクラスを使おうかなと思ったのですが、ソースを追っていて気になるところがあった(EGLオブジェクトを破棄せずに作り直しているパスがある気がする)ので自分で実装しました。
2015-06-12

暑い

今日、窓を開けていても少し暑いと感じました。梅雨っぽいはっきりしない天候で終始曇っていたと思うのですが、風が冷たくなくなったのでしょうか。少し湿気も感じます。やれやれ、もう夏はすぐそこなのですね。

2015-06-12 , ,

Android SDK プロジェクトの作り方, バージョン管理, local.propertiesアップデート, キーの生成, NDKまで

Android SDKのコマンドラインを使ったAndroidアプリプロジェクトの作成方法を記しておきます。しばらく離れているとすぐに忘れるので備忘録として(というかこうして書くことでより記憶に定着させるため)。

前提: SDK, JDK, ant等必要な物をインストールしてパスを通してあること

プロジェクトの作成

android --help create project

で、ヘルプが出るので参考に。

android※1 create project -n MyAwesomeApp -t 10※2  -p ./MyAwesomeApp※3 -k com.example.my_awesome_app -a MyAwesomeApp※4
  • ※1: Cygwin等からはandroid.batとするとよい。
  • ※2: -tの値(target ID)は環境によって異なる? android list targets で表示される。
  • ※3: カレントディレクトリへ作るなら単にピリオドのみでOK。
  • ※4: Activity名。デフォルトでアプリアイコンの下に表示される名前になる。

参考: http://developer.android.com/tools/help/android.html

コンパイルは次のようにする。

cd MyAwesomeApp
ant debug

インストールはAVDか端末を接続した上で ant debug install とする。

バージョン管理に入れる(Subversionやgit等)

バージョン管理では次のファイルを無視する(.gitignore等)。

  • local.properties
  • bin
  • gen
  • libs
  • obj (NDK使用時)

バージョン管理から取り出したときの作業

リポドシリから取り出したときはlocal.propertiesが無いので生成する必要がある。

cd MyAwesomeApp
android update project -p .

キーストアの情報が必要ならlocal.propertiesへ追記する。

キーの生成方法

キーの生成は次のようにする。(keytoolはJDKのbinの中)

keytool -genkey -v -keystore my_awesome_app.keystore -alias my_awesome_app -keyalg RSA -keysize 2048 -validity 10000

local.propertiesにキーの情報を追加する。

key.store=my_awesome_app.keystore
key.alias=my_awesome_app

次のようにしてリリースビルドできるようになるはず。

cd MyAwesomeApp
ant release

参考: http://developer.android.com/tools/publishing/app-signing.html

NDK

NDKを使いたい場合はjniディレクトリを作る。NDKのsamplesディレクトリを参考にすると良い。

プロジェクトディレクトリ下にjniディレクトリを作り、次のファイルを配置する。

  • Android.mk
  • Application.mk
  • *.cpp, *.h等

ビルドはプロジェクトディレクトリトップで ndk-build とし、成功したら、普通に ant debug 等。(参考: https://developer.android.com/ndk/guides/ndk-build.html)

AndroidManifest.xml は必要に応じて修正すること。特にNativeActivityを使う場合は修正が必要。

各ファイルの例を次に記す。(android-ndk-r10e時点。バージョンによってオプションは変わっていくかも?)

jni/Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_CPP_FEATURES += exceptions
LOCAL_CPP_FEATURES += rtti
LOCAL_MODULE := my-awesome-app-jni
LOCAL_SRC_FILES := my-awesome-app-jni.cpp

include $(BUILD_SHARED_LIBRARY)
# include $(BUILD_EXECUTABLE) ←main()から実行する場合はこちら。

例外やRTTIのないC++なんて98年以前の感じですね。今更耐えられないので有効。(r10e現在デフォルト無効)

現在のバージョンではC++11はデフォルトで使えるらしいです。ライブラリはまた別の話ですが。

参考: https://developer.android.com/ndk/guides/android_mk.html

jni/Application.mk

APP_ABI := all
APP_STL := c++_static

stlport(stlport_static)にはstd::to_stringが無かったのでclangのライブラリ(c++_static)を指定しました。

参考: https://developer.android.com/ndk/guides/application_mk.html

jni/my-awesome-app-jni.cpp

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

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_my_1awesome_1app_MyAwesomeApp_nativeExampleFun(
    JNIEnv *env,
    jobject thisj,
    jstring exampleStr,
    int exampleInt,
    jobject exampleObj)
{
    struct UTFChars
    {
        JNIEnv *env;
        jstring str;
        const char *chars;
        UTFChars(JNIEnv *env, jstring str):env(env), str(str), chars(env->GetStringUTFChars(str, nullptr)){}
        ~UTFChars(){if(chars){env->ReleaseStringUTFChars(str, chars);}}
        const char *get() const {return chars;}
    };
    const std::string result = UTFChars(env, exampleStr).get() + std::to_string(exampleInt);
    return env->NewStringUTF(result.c_str());
}

関数名はパッケージ名やクラス名に合わせる。パッケージ名に_(アンダースコア)が入っているときは_の後に1が必要なので注意。_のままだとランタイムでエラーになる。

JNIに詳しくないんですが、こういうことをするための文字列ライブラリってないんですか?

src/com/example/my_awesome_app/MyAwesomeApp.java

//...略...
import android.graphics.Bitmap;
import android.widget.Toast;

public class MyAwesomeApp extends Activity
{
    //...略...

    // ネイティブ関数の宣言
    static {
        System.loadLibrary("my-awesome-app-jni"); //LOCAL_MODULEで指定した名前
    }
    native String nativeExampleFun(String exampleStr, int exampleInt, Bitmap exampleObj);

    //...略...
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // ネイティブ関数を呼び出してみる
        String result = nativeExampleFun("test", 123, null);
        Toast.makeText(this, result, Toast.LENGTH_LONG).show(); //test123と表示される
    }
}

AndroidManifest.xml (NativeActivity使用時の例)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.example.my_awesome_app"
      android:versionCode="1"
      android:versionName="1.0">

  <uses-sdk android:minSdkVersion="14" /><!--←SDKのバージョン-->
  <uses-feature android:glEsVersion="0x00020000"></uses-feature><!--←OpenGL ESを使う場合、そのバージョン-->

  <application android:label="@string/app_name" android:icon="@drawable/ic_launcher" android:hasCode="false"><!--←Javaのコードを使わない場合はhasCodeをfalseにすること-->
        <activity android:name="android.app.NativeActivity"
                  android:label="@string/app_name"
                  android:configChanges="orientation|keyboardHidden"><!-- nameはNativeActivityを使うならこの値で固定。configChangesは端末回転時にActivityを破棄しない設定らしいけど、APIレベルによっては効かないらしい? -->

            <meta-data android:name="android.app.lib_name"
                       android:value="my-awesome-app-jni" /><!-- ←valueはAndroid.mkのLOCAL_MODULEで指定したモジュール名 -->

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
2015-04-22

Photoshopのスクリプトで現在のスクリプトのパスを求める方法

$.fileName で現在のスクリプトへのパスが得られるらしい。

Extendscript current script path - Stack Overflow

古いPhotoshopではこの手は使えないらしいので、わざと例外を発生させてファイル名を得る方法もあるらしい。すげぇ。

JSX(Adobeの方)でスクリプトファイル自身のpathを求める - Qiita

ちなみにFileやFolderのプロパティ、メソッド一覧はこちら。

JavaScript Tools Guide - javascript_tools_guide.pdf

2015-04-17

暑い

昨日から日中部屋が暑くなってきました。閉めきっていると30度近くまで行きます。でも窓を開けると涼しい風が通り抜けて気持ちいいですね。このくらいの気候が一番過ごしやすいかな。外出するなら今の時期ですね。どこか行きたいなぁ。