2015年4月27日 星期一

Ubuntu 15.04 與中文輸入法

前兩天發現代號 Vivid Vervet 的 Ubuntu 15.04 發佈了,原本也沒有特別想下載回來試試,畢竟我很久沒使用 Ubuntu 了。不過看到了 《Ubuntu 15.04 擁抱物聯網,支援無人機與機器人》這篇文章,提到針對物聯網裝置設計的 Snappy Ubuntu Core,覺得還頗有意思的,就乾脆下載 Ubuntu 15.04 回來試試囉!然後再找機會來研究一下 Snappy Ubuntu Core。

安裝完 Ubuntu 15.04 後,首先要搞定的就是中文輸入法。或許是太久沒使用 Ubuntu 了,我覺得 Ubuntu 15.04 的中文輸入法容易處理多了。首先,安裝完 Ubutnu 後,在語言設定 (Language Support) 裡確定將「正體中文 Chinese (traditional)」安裝好,通常我會將「正體中文 Chinese (traditional)」和「簡體中文 Chinese (simplified)」一併安裝。

此外,也在「Keyboard input method system」這項選擇「fcitx」。

接著確定是否安裝了對應的輸入法表格。

例如,我一向使用倉頡輸入法,而且也比較習慣使用第三代的拆碼方式 (其實是被微軟內建的新倉頡輸入法影響的),所以確定安裝了 fcitx-table-cangjie3 這個套件。若是使用新酷音輸入法的朋友,則是安裝 fcitx-chewing 這個套件。

登出再登入後,可以看到 Ubntu 右上角的輸入法圖示變更了,從那個圖示可以開啟 fcitx 的設定 (Fcitx Configuration),加入所使用的輸入法表格即可使用中文輸入法了。

例如,由於我使用了第三代拆碼的倉頡輸入法,於是加入了 Cangjie3 這項。

2014年1月15日 星期三

不規則拼貼圖的工具比較:PhotoCap vs. Shape Collage

之前在《使用 PhotoCap 製作不規則的拼貼圖》這篇文章裡說明如何使用 PhotoCap 這個軟體來製作不規則的拼貼圖。除了 PhotoCap 外,也還有不多軟體可以製作這樣的圖片,例如 Shape CollageCollageIt 等。

其中,在 Shape Collage 的網頁提到支援 Linux 的版本,但下載時卻發生 ShapeCollage-3.1.tar.gz 檔案連結失效的狀況。

另外,Shape Collage 和 CollageIt 都各有兩個版本,分別是 Free 和 Pro 兩種。以下是各自的差異:

這篇文章則另外比較了 PhotoCap 和 Shape Collage 製作不規則拼貼的結果。

首先,先來看一下 PhotoCap 的結果:

可以看出,PhotoCap 產生的不規則拼貼還是疊得太密了,但它可以直接在 PhotoCap 裡編輯,調整每一張相片的位置和擺放角度。

接著,來看看 Shape Collage 的結果:

Shape Collage 可以產生整齊排列和不規則排列的相片拼貼,但必須是 Shape Collage Pro 版本才可以再編輯生成的結果。至於 Free 版本會在生成的相片拼貼上擺放 Watermark,由於並不是十分搶眼的 Watermark,我覺得可以不用太在意,畢竟是免費使用該軟體,藉由一個淡淡且不搶戲的 Watermark 幫忙宣傳一下是無可厚非的。

2013年12月16日 星期一

使用 javah 產生 Native Support / JNI 的標頭檔

在《從 HelloJNI 開始 Android NDK 的使用》這篇文章裡,可以看到 HelloJNI++ 專案的 jni/hellojni.c 裡實作了這段程式碼:

jstring
Java_demo_example_hellojni_HelloActivity_stringFromJNI( JNIEnv* env,
                                                  jobject thiz )
{
    return (*env)->NewStringUTF(env, "Hello from JNI !");
} 

而在《使用 C++ 改寫 HelloJNI++》這篇文章裡,也可以在 jni/hellojni.cpp 看到了這段程式碼:

static const char *classPathName = "demo/example/hellojni/HelloActivity";

static JNINativeMethod methods[] = {
  {"stringFromJNI", "()Ljava/lang/String;", (void*)Java_demo_example_hellojni_HelloActivity_stringFromJNI },
}; 

這兩處的程式碼,都是對應到 HelloJNI++ 專案的 Java 程式碼:

package demo.example.hellojni;

public class HelloActivity extends Activity {

    public native String stringFromJNI();

} 

那麼,這裡的 Java_demo_example_hellojni_HelloActivity_stringFromJNI()Ljava/lang/String; 如何定義呢?在網路上找到的《JNI Examples for Android》文件提供了說明;此外,也可以使用 Java JDK 的 javah 這支程式來產生。

首先,我們來看一下 javah 的用途:

Usage: javah [options] <classes>

where [options] include:

        -help                 Print this help message and exit
        -classpath <path>     Path from which to load classes
        -bootclasspath <path> Path from which to load bootstrap classes
        -d <dir>              Output directory
        -o <file>             Output file (only one of -d or -o may be used)
        -jni                  Generate JNI-style header file (default)
        -version              Print version information
        -verbose              Enable verbose output
        -force                Always write output files

<classes> are specified with their fully qualified names (for
instance, java.lang.Object). 

javah 預設的用途,就是用來生成 JNI-style 的標頭檔。

那麼,我們來看一下怎麼使用它吧!最簡單的方法,開啟一個終端程式,然後到 Android 應用程式的專案路徑執行 javah 就可以了,例如以 HelloJNI++ 這個專案來示範。

方法非常簡單,在 HelloJNI++ 專案的路徑執行這個指令:

$ javah -classpath bin/classes -d jni/ demo.example.hellojni.HelloActivity 

其中,要注意的是 classpath 的路徑要指向 bin/classes,而不是 src/ 。執行完成後,就會在 jni/ 路徑下看到生成的標頭檔 demo_example_hellojni_HelloActivity.h 了。標頭檔 demo_example_hellojni_HelloActivity.h 的內容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class demo_example_hellojni_HelloActivity */

#ifndef _Included_demo_example_hellojni_HelloActivity
#define _Included_demo_example_hellojni_HelloActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     demo_example_hellojni_HelloActivity
 * Method:    stringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_demo_example_hellojni_HelloActivity_stringFromJNI
  (JNIEnv *, jobject);

/*
 * Class:     demo_example_hellojni_HelloActivity
 * Method:    unimplementedStringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_demo_example_hellojni_HelloActivity_unimplementedStringFromJNI
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif 

往後,只要在 Java 程式碼裡宣告了 native 的函式,那麼就需要使用 javah 來重新生成這個標題檔,很容易吧!

接著再來看看,既然使用了 Eclipse 這個開發環境,那麼能不能直接將 javah 的動作整合到 Eclipse 裡呢?方法就是使用 Eclipse 的 External Tools。

從 Eclipse 的 Run 下拉選單找到 External Tools,然後點擊 External Tools Configurations。

然後選擇 Program,並新建一個設定。

接著,開始填入以下內容:

  1. Name 的部份任意填入一個容易分辨的名稱。
  2. Location 填入 javah 的路徑,由於我在 Linux 下操作,因此一般為 /usr/bin/javah
  3. Working Directory 填入 ${workspace_loc:/HelloJNI++/bin},其中 HelloJNI++ 對應到 Android 應用程式專案的名稱。也可以按下 Browse Worksapce 鈕來選擇。
  4. Arguments 填入 -classpath ${workspace_loc:/HelloJNI++/bin/classes} -d ${workspace_loc:/HelloJNI++/jni} demo.example.hellojni.HelloActivity,這個是提供給 javah 的參數,其中 HelloJNI++ 同樣對應到 Android 應用程式專案的名稱。

接著,到 Refresh 頁面,勾選 Specific resources,然後按下右側的 Specific Resources 鈕,並將 HelloJNI++ 專案裡的 jni 及 src 兩個目錄勾選起來。

設定完成後,只要從 External Tools 執行這個項目,就會生成 HelloJNI++ 專案的 JNI 標頭檔了。

2013年12月14日 星期六

使用 C++ 改寫 HelloJNI++

在《從 HelloJNI 開始 Android NDK 的使用》這篇文章裡,JNI 裡的 Native Library 是以 C 完成的。接著,我們試著將它改成由 C++ 來完成。

首先,將 jni/hellojni.c 改名稱為 jni/hellojni.cpp,然後同時修改 jni/Android.mk 的內容:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := hellojni
LOCAL_SRC_FILES := hellojni.cpp

include $(BUILD_SHARED_LIBRARY) 

接著,修改 jni/hellojni.cpp 的內容:

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

jstring
Java_demo_example_hellojni_HelloActivity_stringFromJNI( JNIEnv* env,
                                                  jobject thiz )
{
 return env->NewStringUTF("Hello from JNI !");
} 

簡單說,請是將原本的 return (*env)->NewStringUTF(env, "Hello from JNI !"); 改成 return env->NewStringUTF("Hello from JNI !");

接著,執行 Build Project。但,此時可能會出現這樣的錯誤:

這是因為在 AndroidManifest.xml 指定的 minSdkVersion 和使用的 SDK 版本不一樣。

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" /> 

解決的方法很簡單,新建一個 jni/Application.mk,然後填入內容:

APP_PLATFORM := android-8 

這裡使用的版本必須和 minSdkVersion 相同。

最後,將編譯出來的 HelloJNI++.apk 下載到模擬器執行,卻出現了錯誤的狀況。

而,從 Logcat 也可以查看到以下的錯誤訊息:

E/AndroidRuntime(1365): FATAL EXCEPTION: main
E/AndroidRuntime(1365): Process: demo.example.hellojni, PID: 1365
E/AndroidRuntime(1365): java.lang.UnsatisfiedLinkError: Native method not found: demo.example.hellojni.HelloActivity.stringFromJNI:()Ljava/lang/String;
E/AndroidRuntime(1365):     at demo.example.hellojni.HelloActivity.stringFromJNI(Native Method)
E/AndroidRuntime(1365):     at demo.example.hellojni.HelloActivity.onCreate(HelloActivity.java:20)
E/AndroidRuntime(1365):     at android.app.Activity.performCreate(Activity.java:5243)
E/AndroidRuntime(1365):     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087)
E/AndroidRuntime(1365):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2140)
E/AndroidRuntime(1365):     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2226)
E/AndroidRuntime(1365):     at android.app.ActivityThread.access$700(ActivityThread.java:135)
E/AndroidRuntime(1365):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1397)
E/AndroidRuntime(1365):     at android.os.Handler.dispatchMessage(Handler.java:102)
E/AndroidRuntime(1365):     at android.os.Looper.loop(Looper.java:137)
E/AndroidRuntime(1365):     at android.app.ActivityThread.main(ActivityThread.java:4998)
E/AndroidRuntime(1365):     at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime(1365):     at java.lang.reflect.Method.invoke(Method.java:515)
E/AndroidRuntime(1365):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:777)
E/AndroidRuntime(1365):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:593)
E/AndroidRuntime(1365):     at dalvik.system.NativeStart.main(Native Method) 

在網路上可以找到一份《JNI Examples for Android》的文件,參考它的內容實作 JNI OnLoad() 的部份能處理這個問題。至於實作的內容,在 Android 的原始碼裡可以找到一份 SimpleJNI 的範例,在 https://github.com/android/platform_development/tree/master/samples/SimpleJNI 這裡可以查看程式碼。根據範例裡的 jni/native.cpp,我們可以修改 HelloJNI++ 的 jni/hellojni.cpp 的內容:

#include <jni.h>
#include <string.h>
#include <android/log.h>
#include <stdio.h>

#define LOG_TAG "HelloJNI++: hellojni.cpp"

jstring
Java_demo_example_hellojni_HelloActivity_stringFromJNI( JNIEnv* env,
                                                  jobject thiz )
{
    return env->NewStringUTF("Hello from JNI !");
}

static const char *classPathName = "demo/example/hellojni/HelloActivity";

static JNINativeMethod methods[] = {
  {"stringFromJNI", "()Ljava/lang/String;", (void*)Java_demo_example_hellojni_HelloActivity_stringFromJNI },
};

/*
 * Register several native methods for one class.
 */
static int registerNativeMethods(JNIEnv* env, const char* className,
    JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;

    clazz = env->FindClass(className);
    if (clazz == NULL) {
        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Native registration unable to find class '%s'", className);
        return JNI_FALSE;
    }
    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "RegisterNatives failed for '%s'", className);
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

/*
 * Register native methods for all classes we know about.
 *
 * returns JNI_TRUE on success.
 */
static int registerNatives(JNIEnv* env)
{
  if (!registerNativeMethods(env, classPathName,
                 methods, sizeof(methods) / sizeof(methods[0]))) {
    return JNI_FALSE;
  }

  return JNI_TRUE;
}


// ----------------------------------------------------------------------------

/*
 * This is called by the VM when the shared library is first loaded.
 */

typedef union {
    JNIEnv* env;
    void* venv;
} UnionJNIEnvToVoid;

jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    UnionJNIEnvToVoid uenv;
    uenv.venv = NULL;
    jint result = -1;
    JNIEnv* env = NULL;

    __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "JNI_OnLoad");

    if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: GetEnv failed");
        goto bail;
    }
    env = uenv.env;

    if (registerNatives(env) != JNI_TRUE) {
        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: registerNatives failed");
        goto bail;
    }

    result = JNI_VERSION_1_4;

bail:
    return result;
} 

此外,這裡用了 __android_log_print ,請再參考《在 JNI 程式碼使用 Logcat》修改 Android.mk,如此就完成了。

2013年12月13日 星期五

從 HelloJNI 開始 Android NDK 的使用

安裝及設定工具

從某個版本開始,在 Eclipse 的 Android Development Tools (ADT) 已經包含了對 NDK 的支援。因此在安裝 Android Development Tools (ADT) 時可以一併將 NDK plugins 安裝起來。

接著,在 Eclipse 的 Preferences 裡指定 Android SDK 的路徑,並指定 Android NDK 的路徑。在這裡,我分別將 Android SDK 及 Android NDK 放置在 /opt/android/android-sdk-linux/opt/android/android-ndk-r9b 這兩個路徑。

Android 應用程式專案設定

接著,就可以開始在 Android 應用程式專案裡使用了 Android NDK 了。

舉例來說,我建立了一個名字叫「HelloJNI++」的專案,在專案上點滑鼠右鍵,選擇 Android Tools 底下的 Add Native Support 來加入 JNI 的程式碼。

加入 Native Support 時,首先要先輸入 Native Library 的名字。這裡,我使用 hellojni 作為 Native Library 的名字,於是它將會產生一個名為 libhellojni.so 的檔案。

接著,這個步驟非常重要。在專案的 Properties 裡,找到 C/C++ General 裡的 Paths and Symbols,並在 Includes 裡加入 Android NDK 的標頭檔路徑。例如,這個專案指定的 Android API 為 19 (Android 4.4),因此我加入了 /opt/android/android-ndk-r9b/platforms/android-19/arch-arm/usr/include 這個路徑。

這個步驟非常重要,不可以省略!

範例程式碼

以下就可以開始在這個專案填入程式碼了。

Android Development Tools (ADT) 的 NDK plugins 預設會依 Native Library 的名字先建立一個 .cpp 的檔案。在這篇文章裡,由於我將 Native Library 的名字取作 hellojni,因此它會建立 jni/hellojni.cpp 這個檔案。這裡我將它更名為 jni/hellojni.c,並對應修改 jni/Android.mk 的內容:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := hellojni
LOCAL_SRC_FILES := hellojni.c

include $(BUILD_SHARED_LIBRARY) 

接著,修改專案的 Java 程式碼,內容如下:

package demo.example.hellojni;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.widget.TextView;

public class HelloActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        TextView  tv = new TextView(this);
        tv.setText( stringFromJNI() );
        setContentView(tv);
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.hello, menu);
        return true;
    }
    
    public native String stringFromJNI();

    static {
        System.loadLibrary("hellojni");
    }        
} 

然後,修改 jni/hello.c,將 stringFromJNI() 實作出來。

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

jstring
Java_demo_example_hellojni_HelloActivity_stringFromJNI( JNIEnv* env,
                                                  jobject thiz )
{
    return (*env)->NewStringUTF(env, "Hello from JNI !");
} 

最後,從 Eclipse 的 Project 下拉選單裡執行 Build Project,就完成編譯了。然後,可以將編出來的 HelloJNI++.apk 下載到模擬器或 Android 手機上執行驗證。

2013年12月12日 星期四

[彰化] 旅。咖啡

知道彰市化開了家「旅。咖啡」已經一陣子了,只是一直沒空來坐坐。前些日子,因為一個活動試喝了「旅。咖啡」的花生拿鐵,及聽了店老闆小王子分享搶救「台銀日式舊宿舍群」的故事,於是日前趁著傍晚回家前的空檔繞過來坐坐。

店裡的裝璜用了許多頗具歷史故事的物件,給人一種很舒服、溫暖的感覺。也許因為我也極為喜好這類東西吧!簡直是第一眼就愛上了這裡的裝璜了。

由於日前試喝過「花生拿鐵」,於是第一次來訪我決定試試另一個特別的產品「麵茶咖啡」。但,其實在隔天傍晚,我又跑來完整品嘗了一杯「花生拿鐵」。這兩個特色產品的口感都很棒,我非常推薦第一次來訪的朋友可以先從這兩個特色產品開始品嘗。

店裡的裝璜很有歷史的味道,牆上還有之前搶救「台銀日式舊宿舍群」時的相片。

啊!我超愛這樣的燈耶!

牆上這個也是在台銀日式舊宿舍群拆除時,從怪手中搶救回來的,若你有機會遇到小王子,可以請他好好跟你介紹這東西的來歷唷!

有時我會懷疑,在遠古的時代,我是不是和蠹魚妖有什麼關係呢?因為,我也超愛這樣有歷史味道的書本。這幾本書基本上已經快散了,要小心愛護唷!

靜下心看看這面牆,是不是感覺很棒呢?對了,牆上那個鐘是要上發條的,整點時還會響呢!那個聲音一整個很好聽。說實話,這個鐘讓我回想起以前家裡那個老舊、有單擺的掛鐘了。

晚上時,從外面看起來也很漂亮!對了,店裡面向窗外的那張長桌,桌面可是直接用一塊門板製作的,所以在桌面上還有一個鐵環呢!其實,店裡另外有三張方形桌子也是將門板鋸開製作成的。其中有一桌的桌面上,還有以前貼門神的痕跡唷!

旅。咖啡

地址:彰化市民權路241號

電話:04-7282972

email: tripcafetw@gmail.com

營業時間:週二~週日 10:00-22:00

部落格:http://trip-cafe-tw.blogspot.tw/

小王子的浮世隨筆Ⅱ.0 http://seo-worker.blogspot.tw/