Android RenderScript
上回泰勞介紹了處理圖片特效的方法,這回要來介紹如何提升效能,也許很多人第一時間會想到 JNI,那確實是個不錯的選擇,不過泰勞想介紹一個有點不一樣、使用場合跟 JNI 不盡相同的 API,那就是 RenderScript!
參考資料:
[Developers]RenderScript Overview
[Developers]Type
[Developers]Element
[Developers]Allocation
使用 RenderScript 最大的優勢在於它可以實現「平行運算」,在擁有多核心處理器的裝置上速度一定會快上許多,同時它也支援 GPU。但因為是平行運算,有些場合是不能使用的,當然也有些場合特別適合,像是圖像處理就非常適合使用 RenderScript。
延續上一回的範例,這次改用 RenderScript 來實作負片效果,先準備一個簡單的 Context,作為主程式調用 RenderScript 的中繼站。
RenderScriptContext.java
接著是 RenderScript 腳本,它的語法、格式有點特別,泰勞一開始真的看不太習慣,時常懷疑這樣寫到底是不是對的。
Invert.rs
最後於主程式透過 RenderScriptContext 將 Bitmap 傳給 RenderScript 腳本處理。
特地找了一張色彩鮮明的圖片來進行測試,結果如下圖所示。
成功透過 RenderScript 實作負片效果,但還沒完,既然它主打的特點就是平行運算,當然要跟上回的方法做個速度比較,下方表格是泰勞使用 XZ1 Compact (硬體規格) 實際測試的結果(單位:毫秒)。
平均下來單純使用 Java 處理一次的時間是 120.4 毫秒,使用 RenderScript 處理一次的時間為 90.8 毫秒,看起來只快了一點,並沒有預期中那麼快。我們再試一次,先不管計算出來的效果如何,稍微讓計算的公式複雜一點,並且換上一張解析度為 5056x3792 的圖片,看看會不會有什麼不同,公式如下圖所示。(fac=0.003921, level=5)
第二次測試結果如下表所示。
平均下來單純使用 Java 處理一次的時間增加至 260.7 毫秒,而使用 RenderScript 處理一次的時間是 136.9 毫秒,這樣看起來差異就比較明顯了,第二次的測試 RS 的處理時間幾乎是單純使用 Java 的一半,同時可以合理的推測越是複雜的運算,越能表現出 RenderScript 的優勢,當然圖片的像素越多,也越能感受到它的強大。
參考資料:
[Developers]RenderScript Overview
[Developers]Type
[Developers]Element
[Developers]Allocation
使用 RenderScript 最大的優勢在於它可以實現「平行運算」,在擁有多核心處理器的裝置上速度一定會快上許多,同時它也支援 GPU。但因為是平行運算,有些場合是不能使用的,當然也有些場合特別適合,像是圖像處理就非常適合使用 RenderScript。
延續上一回的範例,這次改用 RenderScript 來實作負片效果,先準備一個簡單的 Context,作為主程式調用 RenderScript 的中繼站。
RenderScriptContext.java
public class RenderScriptContext {
// for RenderScript
private RenderScript rs;
// ScriptC 和底線為格式所需,Invert 是我的 RS 腳本名稱
private ScriptC_Invert scriptC;
// 用於設定 RS 物件類型 private Type rType;
// 將 Java 物件傳給 RS 處理的媒介
private Allocation allocIn;
private Allocation allocOut;
//
private Bitmap outputBitmap;
public RenderScriptContext(Context context, int width, int height) {
// 初始化 RS 的 Context
rs = RenderScript.create(context);
// 建立 ScriptC_Invert 的實例
scriptC = new ScriptC_Invert(rs);
// 定義元素類型與影像尺寸,這裡將元素類型設為 RGBA_8888
rType = Type.createXY(rs, Element.RGBA_8888(rs), width, height);
// 設定 Allocation 類型
allocIn = Allocation.createTyped(rs, rType, Allocation.USAGE_SCRIPT);
allocOut = Allocation.createTyped(rs, rType, Allocation.USAGE_SCRIPT);
// 最後用於返回給主程式
outputBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
}
public static void setPic(Bitmap bitmap) {
// 將 Bitmap 轉為 Allocation
allocIn = Allocation.createFromBitmap(rs, bitmap);
// 將 allocIn 傳給 RS腳本
// set_pic 的 pic 必須跟 RS 腳本裡宣告的物件名稱一樣
scriptC.set_pic(allocIn);
}
public static Bitmap getPic() {
// 執行 RS 腳本裡的 InvertBitmap 函式
// allocOut 接收 RS 回傳的結果
scriptC.forEach_InvertBitmap(allocOut);
// 將 allocOut 轉回 Bitmap
allocOut.copyTo(outputBitmap);
return outputBitmap;
}
}
// for RenderScript
private RenderScript rs;
// ScriptC 和底線為格式所需,Invert 是我的 RS 腳本名稱
private ScriptC_Invert scriptC;
// 用於設定 RS 物件類型 private Type rType;
// 將 Java 物件傳給 RS 處理的媒介
private Allocation allocIn;
private Allocation allocOut;
//
private Bitmap outputBitmap;
public RenderScriptContext(Context context, int width, int height) {
// 初始化 RS 的 Context
rs = RenderScript.create(context);
// 建立 ScriptC_Invert 的實例
scriptC = new ScriptC_Invert(rs);
// 定義元素類型與影像尺寸,這裡將元素類型設為 RGBA_8888
rType = Type.createXY(rs, Element.RGBA_8888(rs), width, height);
// 設定 Allocation 類型
allocIn = Allocation.createTyped(rs, rType, Allocation.USAGE_SCRIPT);
allocOut = Allocation.createTyped(rs, rType, Allocation.USAGE_SCRIPT);
// 最後用於返回給主程式
outputBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
}
public static void setPic(Bitmap bitmap) {
// 將 Bitmap 轉為 Allocation
allocIn = Allocation.createFromBitmap(rs, bitmap);
// 將 allocIn 傳給 RS腳本
// set_pic 的 pic 必須跟 RS 腳本裡宣告的物件名稱一樣
scriptC.set_pic(allocIn);
}
public static Bitmap getPic() {
// 執行 RS 腳本裡的 InvertBitmap 函式
// allocOut 接收 RS 回傳的結果
scriptC.forEach_InvertBitmap(allocOut);
// 將 allocOut 轉回 Bitmap
allocOut.copyTo(outputBitmap);
return outputBitmap;
}
}
接著是 RenderScript 腳本,它的語法、格式有點特別,泰勞一開始真的看不太習慣,時常懷疑這樣寫到底是不是對的。
Invert.rs
#pragma version(1) // RenderScript 版本
#pragma rs java_package_name(JAVA) // 調用此腳本的 package name
// 物件名稱 pic,因此 Java 設定此物件時必須使用 set_pic
rs_allocation pic;
uchar4 __attribute__((kernel)) InvertBitmap(uint32_t x,uint32_t y) {
// 將 pic 拆解,uchar4 用於處理以 32 位元為單位的元素
uchar4 color = rsGetElementAt_uchar4(pic, x, y);
// 反轉顏色
color.r = 255-color.r;
color.g = 255-color.g;
color.b = 255-color.b;
color.a = color.a;
// 返回
return color;
}
#pragma rs java_package_name(JAVA) // 調用此腳本的 package name
// 物件名稱 pic,因此 Java 設定此物件時必須使用 set_pic
rs_allocation pic;
uchar4 __attribute__((kernel)) InvertBitmap(uint32_t x,uint32_t y) {
// 將 pic 拆解,uchar4 用於處理以 32 位元為單位的元素
uchar4 color = rsGetElementAt_uchar4(pic, x, y);
// 反轉顏色
color.r = 255-color.r;
color.g = 255-color.g;
color.b = 255-color.b;
color.a = color.a;
// 返回
return color;
}
最後於主程式透過 RenderScriptContext 將 Bitmap 傳給 RenderScript 腳本處理。
RenderScriptContext rsContext = new RenderScriptContext(MainActivity.this, mBitmap.getWidth(), mBitmap.getHeight());
rsContext.setPic(mBitmap);
imageView_after.setImageBitmap(rsContext.getPic());
rsContext.setPic(mBitmap);
imageView_after.setImageBitmap(rsContext.getPic());
特地找了一張色彩鮮明的圖片來進行測試,結果如下圖所示。
成功透過 RenderScript 實作負片效果,但還沒完,既然它主打的特點就是平行運算,當然要跟上回的方法做個速度比較,下方表格是泰勞使用 XZ1 Compact (硬體規格) 實際測試的結果(單位:毫秒)。
Java | 114 | 142 | 106 | 134 | 119 | 133 | 102 | 125 | 122 | 107 | 120.4 |
RenderScript | 94 | 89 | 91 | 92 | 87 | 89 | 88 | 91 | 98 | 90 | 90.8 |
平均下來單純使用 Java 處理一次的時間是 120.4 毫秒,使用 RenderScript 處理一次的時間為 90.8 毫秒,看起來只快了一點,並沒有預期中那麼快。我們再試一次,先不管計算出來的效果如何,稍微讓計算的公式複雜一點,並且換上一張解析度為 5056x3792 的圖片,看看會不會有什麼不同,公式如下圖所示。(fac=0.003921, level=5)
第二次測試結果如下表所示。
Java | 258 | 266 | 267 | 262 | 265 | 255 | 264 | 256 | 254 | 260 | 260.7 |
RenderScript | 138 | 137 | 138 | 142 | 141 | 131 | 137 | 136 | 135 | 134 | 136.9 |
平均下來單純使用 Java 處理一次的時間增加至 260.7 毫秒,而使用 RenderScript 處理一次的時間是 136.9 毫秒,這樣看起來差異就比較明顯了,第二次的測試 RS 的處理時間幾乎是單純使用 Java 的一半,同時可以合理的推測越是複雜的運算,越能表現出 RenderScript 的優勢,當然圖片的像素越多,也越能感受到它的強大。
留言
張貼留言