2012年3月4日 星期日

Android 開發筆記 - 實作 Mono 照片特效 @ Ubuntu 10.04

tw.study.rs_screenout


之前接觸 Renderscript - HelloCompute 時,第一次了解 Mono 特效是怎樣做的。這次則是要來筆記純用 Java 和 NDK 作的方式。


環境概述(以 Renderscript - HelloCompute 架構為例):


private Bitmap mBitmapIn;
private Bitmap mBitmapOut;


private Bitmap loadBitmap(int resource) {
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        return BitmapFactory.decodeResource(getResources(), resource, options);
}


protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);


        ...
        mBitmapIn = loadBitmap(R.drawable.data); // R.drawable.data 是一張圖片
        mBitmapOut = Bitmap.createBitmap(mBitmapIn.getWidth(), mBitmapIn.getHeight(), mBitmapIn.getConfig()); // 空資料,大小跟 mBitmapIn 一樣
        ...

        // doMonoByJava(); // use Java only
        // doMonoByNDK(); // use NDK
        // createScript();     // use Renderscript
        ...
}


純 Java 版:


private void doMonoByJava() {
        final float gMonoMult[] = {0.299f, 0.587f, 0.114f};
        int imageHeight = mBitmapIn.getHeight();
        int imageWidth = mBitmapIn.getWidth();

        /* only assign, 速度慢
        for( int i=0 ;i<imageWidth; ++i )
                for( int j=0;j<imageHeight; ++j)
                        mBitmapOut.setPixel(i, j, mBitmapIn.getPixel(i, j));
        */

        /* only assign, 速度快
        int[] pixels = new int[imageWidth*imageHeight];
        mBitmapIn.getPixels(pixels,0,imageWidth,0,0,imageWidth,imageHeight);
        mBitmapOut.setPixels(pixels, 0, imageWidth, 0, 0, imageWidth, imageHeight);
        */


        int[] pixels = new int[imageWidth*imageHeight];
        mBitmapIn.getPixels(pixels,0,imageWidth,0,0,imageWidth,imageHeight);
        for( int i=0 ; i<imageHeight*imageWidth ; ++i ) {
                int R = (pixels[i] >> 16) & 0xff; 
                int G = (pixels[i] >> 8) & 0xff;
                int B = pixels[i] & 0xff;

                int m = (int)((float)R*gMonoMult[0] + (float)G*gMonoMult[1] + (float)B*gMonoMult[2] );
                R = m;
                G = m;
                B = m;


                pixels[i] = 0xff000000 | (R << 16) | (G << 8) | B;
        }
        mBitmapOut.setPixels(pixels, 0, imageWidth, 0, 0, imageWidth, imageHeight);
}


使用 NDK 版:


HelloCompute.java:


package com.example.android.rs.hellocompute;

public class HelloCompute extends Activity {
        ...
        static {
                System.loadLibrary("my-jni");
        }
        public native void doByNDK( int [] pixels, int size);

        private void doMonoByNDK() {
                int imageHeight = mBitmapIn.getHeight();
                int imageWidth = mBitmapIn.getWidth();
                int size = imageWidth*imageHeight;
                int[] pixels = new int[size];
                mBitmapIn.getPixels(pixels,0,imageWidth,0,0,imageWidth,imageHeight);
                doByNDK(pixels,size);
                mBitmapOut.setPixels(pixels, 0, imageWidth, 0, 0, imageWidth, imageHeight);
        }
        ...
}


my-jni.c:


#include <jni.h>
void Java_com_example_android_rs_hellocompute_HelloCompute_doByNDK( JNIEnv* env, jobject thiz, jintArray pixels, jint size) {
        float gMonoMult[] = {0.299f, 0.587f, 0.114f};
        int i, R, G, B, m;


        jboolean isCopy = JNI_FALSE;
        jint *jpixels = (*env)->GetIntArrayElements(env, pixels, &isCopy);


        for(i=0 ; i<size ; ++i ) {
                R = (jpixels[i] >> 16) & 0xff; //bitwise shifting
                G = (jpixels[i] >> 8) & 0xff;
                B = jpixels[i] & 0xff;
                m = (int)((float)R*gMonoMult[0] + (float)G*gMonoMult[1] + (float)B*gMonoMult[2] );
                R = m;
                G = m;
                B = m;
                jpixels[i] = 0xff000000 | (R << 16) | (G << 8) | B;
        }
}


若要計算效率,可以再用這段程式碼:


Long timeCost = System.currentTimeMillis();

// do something ...

timeCost = System.currentTimeMillis() - timeCost;
System.out.println("Cost:"+timeCost);


2012年3月3日 星期六

[Android] 變更 CPU 狀態、使之 offline、降頻、超頻和穩頻 @ PandaBoard

之前被要求測試 Android 4.0 的 Renderscript 是否可以做平行處理,但是隨著工作量的增加,一直把這件事擺在後頭,今天,終於測試完了!最早之前,我想到的是修改 HelloCompute 範例中的 mono.rs 檔案,裡頭的 root 函式是一個工作單位,如果一台裝置是多 CPU 且系統支援平行處理的話,理論上一次會有多個工作單位在進行。因此想說擺一個 static 變數在 root 函式中,每次把數值加一並印出,依此觀察系統運作時,印出的數字是否不連續,如果不連續或有相同時,想要把這當作有平行的依據。今天則想到一個更簡單的妙招,就是在 root 函式中,用一個 loop 讓它不結束,如此一來,在雙核心的 CPU 下,就會看到兩次訊息,單核心就只會看到一個!理由很簡單,因為工作沒做完,就不會分配新的工作。


這在 Pandaboard 上測試很成功,我看到了兩次 root 函式印出的訊息,然而,我更想要驗證自己設計的方式是不是對的,那就必須找只有一個核心的裝置,甚至四核心的裝置測試,如果在一個 CPU 的裝置只看到一次,在四核心的裝置可以看到四次,那代表驗證成功了。結果把構想跟同事分享後,同事教了我一個密技!那就是手動去更改 CPU 的狀態!對 Pandaboard 來說,就是強制把一個 CPU 關掉(offline),慶幸的,驗證對了!


這個用法其實最常看到別人修改機子的 CPU 頻率,例如一台裝置他本身的 CPU 可以跑到 500 MHz,但為了省電、穩定、商業手段等理由,系統廠會降頻跑,例如用 300 MHz 運行等等,這些則可以透過系統設定檔進行操作的,也因此有人刷機改機追求好的效能。


此例只在雙核心的板子上,手動把一顆 CPU 關掉:


$ adb devices
List of devices attached
################ device
$ adb shell ls -R /sys | grep -i cpu
...
/sys/devices/system/cpu:
cpu0
cpu1
cpufreq
cpuidle
...
$ adb shell
root@android:/ # cd /sys/devices/system/cpu
root@android:/sys/devices/system/cpu # ls
cpu0
cpu1
cpufreq
cpuidle
kernel_max
offline
online
possible
present
root@android:/sys/devices/system/cpu # cat online
0-1
root@android:/sys/devices/system/cpu # cat cpu*/online
1
1
root@android:/sys/devices/system/cpu # echo "0" > cpu1/online
root@android:/sys/devices/system/cpu # cat cpu*/online
1
0
root@android:/sys/devices/system/cpu # cat online
0


在 Pandaboard 上,因為是雙核心架構,所以會看到 CPU0 和 CPU1 的資訊,接著分別印出 /sys/devices/system/cpu/cpu0/online 和 /sys/devices/system/cpu/cpu1/online 資訊,標記 1 代表都是運作中,同時印出 /sys/devices/system/cpu/online 會是 0-1 的資訊。接著使用 echo "0" > /sys/devices/system/cpu/cpu1/online ,代表令 CPU1 下線不運作,反之 echo "1" > /sys/devices/system/cpu/cpu1/online 則是讓他醒來運作。透過這個簡單的方式,就能讓控制裝置多核心的狀態啦!真是超方便的,但別忘了執行 adb shell 時,必須有 root 權限才行。


至於 CPU 調頻、穩頻,還沒親手測過,但原理很像,就順便筆記一下:


以 /sys/devices/system/cpu/cpu0 來說,裡頭 cpufreq 目錄,裡面又有 cpuinfo_cur_freq、cpuinfo_max_freq 和 cpuinfo_min_freq 等資訊,不難猜到吧?只要把 scaling_max_freq 跟 scaling_min_freq 設成一樣就是穩頻,把 scaling_max_freq 調高則是超頻吧,有空再來試試!穩頻應該很好用,可以在一樣的 CPU 狀態下收集測試數據。


2012年2月26日 星期日

[Python] 使用 Heroku 筆記 @ Windows 7


差不多該玩一下 Heroku 了,這跟 Google App Engine 一樣,是一套 PaaS 服務架構,仔細看才發現 Heroku 竟然是架設在 EC2 上面的服務,讓我想起 Dropbox 這有名的服務是使用 S3 架構,原來有不少成功的服務不見得從機房都自己打造呢!這樣的感觸滿特別的,不需堅持自己從零開始。使用 Heroku 跟 GAE 這類服務的主因之一,是想要妥善利用身邊的資源,不見得要去租一台虛擬機器來使用,雖然權限大能做的事很多,但一開始太多資源不見得是好事,除此之外,使用 Heroku 跟 GAE 的好處是碰到流量大時,錢砸下去就可以 Scaling ,這樣的投資似乎不差吧?最大的缺點是要去習慣這些架構,轉個角度來看,或許這就是雲端的成本吧?就像把想做的事寫成符合 Hadoop/HBase 架構一樣。


對 Heroku 來說,計費方式是以 process 單位小時的方式計價,例如免費帳號就是一個月使用 750 小時不用錢,使用一隻 process 一個月是 720 小時,所以就不用錢。原先是一隻 process 用一小時 0.05 美元,其中 process 分為 web process 跟 background process 兩種。只是 Heroku 資料庫免費空間僅 5MB 而已,像 GAE 則是 1GB 大小,但是 GAE 還有計算 in/out 的資料量等等。我還沒搞懂 Heroku 有沒計算流量以及是否有限制 library 的部分 :P


好啦,回過頭講一下在 Windows 的操作方式,準備資源:



依序安裝完 Heroku Windows、Python 2.7 後,把 virtualenv-1.7.1.2.tar.gz 解壓縮後,就可以準備操作了,主要參考 Heroku - Getting Started with Python on Heroku/Cedar 這篇,先用最簡單的方式體驗一下,其中有用到 python Flask,這是一個輕量型 MVC 的 framework 囉。


步驟:


Python 2.7 在 C:\Python27\python.exe;virtualenv 在 C:\virtualenv-1.7.1.2;heroku-toolbelt 預設在 C:\Program Files\Heroku。安裝完軟體後,直接用 cmd 運作即可。


建立空目錄,如 helloflask


C:\Users\user>mkdir helloflask && cd helloflask
C:\Users\user\helloflask>


建立 virtualenv 環境


C:\Users\user\helloflask>C:\Python27\python.exe C:\virtualenv-1.7.1.2\virtualenv.py venv --distribute
New python executable in venv\Scripts\python.exe
Installing distribute...done.
Installing pip...done.


啟用虛擬環境:


C:\Users\user\helloflask>venv\Scripts\activate
(venv) C:\Users\user\helloflask>


安裝 Flask:


(venv) C:\Users\user\helloflask>pip install flask


Downloading/unpacking flask
Downloading Flask-0.8.tar.gz (494Kb): 494Kb downloaded
Running setup.py egg_info for package flask
...
Downloading/unpacking Werkzeug>=0.6.1 (from flask)
Downloading Werkzeug-0.8.3.tar.gz (1.1Mb): 1.1Mb downloaded
Running setup.py egg_info for package Werkzeug
...
Downloading/unpacking Jinja2>=2.4 (from flask)
Downloading Jinja2-2.6.tar.gz (389Kb): 389Kb downloaded
Running setup.py egg_info for package Jinja2
...
Successfully installed flask Werkzeug Jinja2
Cleaning up...


建立 app.py 程式(C:\Users\user\helloflask\app.py):


import os


from flask import Flask
app = Flask(__name__)


@app.route('/')
def hello():
        return 'Hello World!'


if __name__ == '__main__':
        # Bind to PORT if defined, otherwise default to 5000.
        port = int(os.environ.get('PORT', 5000))
        app.run(host='0.0.0.0', port=port)


建立 Heroku 環境檔案:


(venv) C:\Users\user\helloflask>pip freeze > requirements.txt
(venv) C:\Users\user\helloflask>cat requirements.txt
Flask==0.8
Jinja2==2.6
Werkzeug==0.8.3
distribute==0.6.24
(venv) C:\Users\user\helloflask>echo web: python app.py > Procfile
(venv) C:\Users\user\helloflask>cat Procfile
web: python app.py


執行 web service:


(venv) C:\Users\user\helloflask>foreman start


接著就可以用瀏覽器瀏覽 http://localhost:5000 啦,可以看到印有 "Hello World!" 字樣,要結束的話就按 ctrl+c 囉


如此一來,就完成本地端的測試開發囉。至於 Deploy 的部分,則是好好看一下官網上面的介紹,這邊就不多提了。


整體上,覺得 GAE 比較簡單一點,過程中只需安裝 python 跟 GAE SDK 環境,但 Heroku 還須使用 Ruby 環境等,或許對已習慣用 Ruby 的人來說,是非常自在的 :D


2012年2月24日 星期五

行動化

筆電與麥當勞


越來越習慣帶著筆電的生活,有沒網路倒還好,沒資源有沒資源可以把玩的事,看看很久沒翻的電子書,不然就在本機端練習一下 GAE 也好(python跟GAE都有離線文件),快不快活只是一個角度的差別。有網路時就把玩網路,換個環境其實還不錯的,有聲響也不會打亂寫程式的步調,有人影也能輕鬆望個幾眼,麻煩的大概是上廁所也要把東西帶在身上,以及走路背著幾公斤的重訓。我想,也到了該認真投資一台好筆電的時刻了吧?


回顧一下,兩個月左右,接觸了硬體界的朋友,才發現硬體界似乎不是我想走的方向,這邊一樣高深莫測,但我開始比較清楚自己想要的是甚麼東西了,剛好幾天前跟 IC 界的聊天,他說目前 IC 廠很缺上層玩 ap 的人,只是十年了,玩上層 ap 仍被認為是一件簡單的事,彷彿純軟的、沒硬體成本的容易不被重視。這讓我想起工作上曾跟硬體界的朋友開會,他一頭就劈做 xxx 的有啥難的,不就想一下演算法就解掉了嗎?說真的我無法反駁,我無法證明問題的難易度,這不像硬體可以用看的、摸的、花錢的等等的估算。


無論如何,還是要珍惜手邊的資源,把能學能掙的要盡量把握,深信某一天某一刻一定會派上用場的 :)


2012年2月23日 星期四

Android 開發教學筆記 - 研究 Renderscript 以 Carousel 為例 @ PandaBoard


這個 Carousel 例子是一個類似翻頁動作的特效,仔細看一下,是不是覺得自己在一個圓心中間,被很多照片包圍住呢?這個例子是 10 張照片,等於你走進了一個 10 面牆的房間,隨著你觸動移動事件,等於房間每面牆近似以你為中心旋轉。猜測原理後,就可以去驗證實做了。


six
此圖為正六邊形。左下角用來推邊長 len,右上角用來推各點 P1, P2, ... 的座標,θ就是兩頂點的夾角,此外,定義右下角是第一象限,左下是第二。


首先,讓我驗證這是個正 n 邊形的效果,主要是看到這段程式碼:


@ initBitmaps(): // Calculate the length of the polygon
len = RADIUS * 2 * sin(M_PI/NUM_ITEMS);


這是計算正 n 邊形的邊長公式,其中 n = NUM_ITEMS,此例就是 10 張照片


接著,開始計算正 n 邊形各頂點的三維座標資訊:


此例是把 y 軸當作 0 來看代 (x,y,z) = ( sinθ * RADIUS, 0, -cosθ * RADIUS)


@ initBitmaps(): // Calculate the vertices of rectangles
vertices[i*3] = sin(angle * M_PI / 180) * RADIUS;
vertices[i*3 + 1] = 0;
vertices[i*3 + 2] = -cos(angle * M_PI / 180) * RADIUS;


畫出正 n 邊形:


@ displayCarousel(): // Draw the rectangles
for (int i = 0; i < 10; i++) {
        ...
        rsgDrawQuadTexCoords(...);
        ...
}


rsgDrawQuadTexCoords 則是輸入長方形四個座標點,但是每一個頂點傳入的參數還有多了兩個,該數值通常定義為 (u, v),其中 u = 0 代表左邊,反之右邊,而 v =0 代表上面,反之下面,因此此例依序透過 (u, v) = (0, 1), (0, 0), (1, 0), (1, 1)來描述頂點位置。其中 (0, 1) 和 (0, 0) 使用的頂點座標位置分別是 ( r*sinθ , -(len/2), -r*cosθ ) 和 ( r*sinθ , len/2, -r*cosθ ),還記得最初定義得座標系嗎? y 軸是縱軸,此例第一張照片左上角座標為 ( r*sinθ , len/2, -r*cosθ ),左下角為 ( r*sinθ , -(len/2), -r*cosθ ),至於第一張圖的右上角跟右下角得座標,那就是用正 n 邊形第二個頂點來計算,以此規則算出 10 張照片的座標位置


至於觸動時的旋轉效果,則是透過 rotate 的功能實作的:


@ displayCarousel():
// Load vertex matrix as model matrix
rs_matrix4x4 matrix;
rsMatrixLoadTranslate(&matrix, 0.0f, 0.0f, -400.0f); // camera position
rsMatrixRotate(&matrix, rot, 0.0f, 1.0f, 0.0f); // camera rotation
rsgProgramVertexLoadModelMatrix(&matrix);


先把 camera(觀看照片的位置?) 位置移到 (0,0,-400) 位置,由於此正 n 邊形半徑為 828,因此距離照片 428 各單位。接著讓 camera 的位置對 (0,1,0) 向量旋轉,就達成這個效果囉


其他資訊:


rsMatrixLoadPerspective 對應 OpenGL:gluPerspective,以 void gluPerspective( GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar); 來說,分別為視角、寬高(長)比、前景和背景,查 wiki 可知人的視角大約 120 度,魚眼是 180 度,在此例是 30 度視角為例,接著寬高(長)比沒啥問題,而 near 和 far 的參數,若有玩單眼相機的話,應該不會太陌生,在網路上找資料的結果,通常前景是用一個大於 0 的數值,如 0.1f 等。


rsMatrixLoadTranslate 對應 OpenGL: glTranslate,以 void glTranslated(GLdouble x, GLdouble y, GLdouble z); 來說,就是切換到 (x,y,z) 座標


rsMatrixRotate 對應 OpenGL: glRotate,以 void glRotated(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); 來說,代表以 (x,y,z) 向量為軸心,旋轉 angle 弧度。此外,(0,1,0) 和 (0,100,0) 都是一樣的向量,網路資料是說用 (0,1,0) 所造成的運算量較低。


而 rsMatrixLoadTranslate 和 rsMatrixRotate 定義可在 Renderscript Reference 查詢。