我的CSDN博客同步發(fā)布:RenderScript 讓你的Android計(jì)算速度快的飛上天榜配!
在上一篇文章Android自動(dòng)手繪,圓你兒時(shí)畫(huà)家夢(mèng)吕晌! 中結(jié)尾提到蛋褥,我將介紹提升輪廓提取速度相關(guān)內(nèi)容,今天一起學(xué)習(xí)Android中的RenderScript睛驳±有模看完本文,你將學(xué)會(huì)如何使用并行計(jì)算技術(shù)乏沸,提高你的app中計(jì)算模塊速度淫茵,尤其是提升圖像處理中的復(fù)雜計(jì)算。
RenderScript介紹
根據(jù)Android官方網(wǎng)站的介紹:RenderScript是Android平臺(tái)上用于運(yùn)行計(jì)算密集任務(wù)的框架蹬跃。RenderScript主要是面向數(shù)據(jù)并行計(jì)算匙瘪,當(dāng)然了,RenderScript中使用串行計(jì)算效率也很好蝶缀。RenderScript是充分利用GPU丹喻,CPU的計(jì)算能力,由于不同的硬件對(duì)應(yīng)的并行執(zhí)行不同翁都,RenderScript會(huì)編譯2次碍论,首先是我們的PC編譯器編譯到apk中,然后在apk安裝的時(shí)候柄慰,再編譯一次鳍悠。這樣的好處是,可以充分利用不同的硬件坐搔,我們編寫(xiě)的代碼無(wú)需關(guān)心具體的硬件的不同藏研,都能寫(xiě)出高性能的代碼。
RenderScript相關(guān)文檔并不多概行,導(dǎo)致很難去學(xué)好RenderScript蠢挡。但是其實(shí)用起來(lái)并不復(fù)雜,結(jié)合SDK中的兩個(gè)例子和官方文檔占锯,基本可以入門(mén)了袒哥。
在使用RenderScript之前,請(qǐng)?jiān)趀clipse的project.properties加上:
renderscript.target=18
renderscript.support.mode=true
Hello RenderScript
概念說(shuō)太多沒(méi)啥用消略,先來(lái)一段簡(jiǎn)單代碼堡称。需求很簡(jiǎn)單,我們需要將一張圖片中的每個(gè)像素的顏色取反色艺演,即分別將255減去當(dāng)前像素點(diǎn)的R却紧、G桐臊、B,得到的新的RGB作為當(dāng)前像素點(diǎn)的新顏色晓殊。如果不用RenderScript断凶,實(shí)現(xiàn)起來(lái)也非常簡(jiǎn)單,通過(guò)兩個(gè)for循環(huán)巫俺,遍歷每個(gè)像素點(diǎn)认烁,然后替換像素就好,如下:
int width = mInBitmap.getWidth();
int height = mInBitmap.getHeight();
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
int color = mInBitmap.getPixel(x, y);
int r= 255-(Color.red(color) ;
int g= 255-(Color.green(color) ;
int b= 255-(Color.blue(color) ;
int c = Color.rgb(gray, gray, gray);
mOutBitmap.setPixel(x, y, c);
}
}
這是使用普通java代碼實(shí)現(xiàn)介汹,如果對(duì)一張較大的圖執(zhí)行這段代碼却嗡,其耗時(shí)可想而知!再去看看RenderScript是如何實(shí)現(xiàn)相同的功能的:
首先嘹承,在代碼目錄下(即包目錄下)創(chuàng)建rs文件窗价,取名可以任意,我們新建一個(gè)hello.rs文件:
#pragma version(1)
#pragma rs java_package_name(com.hc.renderscript)
uchar4 __attribute__((kernel)) invert(uchar4 in)
{
uchar4 out = in;
out.r =255- in.r;
out.g = 255-in.g;
out.b = 255-in.b;
return out;
}
看不懂叹卷?不要急撼港!我們一行一行解釋。仔細(xì)看會(huì)發(fā)現(xiàn)其實(shí)大部分跟C語(yǔ)言很像骤竹,首先#pragma是給編譯器看的,#pragma version(1)
是指版本號(hào)帝牡,目前只能選擇1,沒(méi)有更高的版本了瘤载。#pragma rs java_package_name(com.hc.renderscript)
是告訴編譯器否灾,包的名稱(chēng)卖擅。因?yàn)槊總€(gè)rs文件都會(huì)自動(dòng)生成對(duì)應(yīng)的Java代碼鸣奔,比如,我們新建的hello.rs文件惩阶,會(huì)自動(dòng)生成ScriptC_hello類(lèi)挎狸,因此,我們需要在rs聲明包的名稱(chēng)断楷。接下來(lái)比較重要的關(guān)鍵字__attribute__((kernel))
锨匆,它跟函數(shù)放在一起,用于聲明這個(gè)函數(shù)是個(gè)RenderScript核心函數(shù)冬筒,而不是一個(gè)可調(diào)用的函數(shù)恐锣。什么意思呢?其實(shí)可以這樣理解舞痰,就是這個(gè)函數(shù)不是個(gè)普通函數(shù)土榴,是用于并行計(jì)算的函數(shù)。我們不能顯式調(diào)用响牛,它是RenderScript內(nèi)部調(diào)用的函數(shù)玷禽。這時(shí)你可能會(huì)想赫段,既然我們不能顯式調(diào)用,那該怎么調(diào)用呢矢赁?別急糯笙,接下來(lái)為你揭曉。
我們繼續(xù)看到invert函數(shù)撩银,這個(gè)函數(shù)有個(gè)uchar4
類(lèi)型给涕,不用想,肯定表示占用4個(gè)字節(jié)额获,每個(gè)字節(jié)表示的取值范圍0~255稠炬。但是接下來(lái)的事情就很奇怪了,uchar4 in
中直接可以用in.r
咪啡、in.g
首启、in.b
分別取出rgb顏色。我猜想uchar4是個(gè)結(jié)構(gòu)體類(lèi)型撤摸,本來(lái)想去官網(wǎng)查看一下毅桃,找了很久沒(méi)找到。找到的童鞋麻煩告訴我一下准夷,我可以重新編輯這篇文章钥飞。但是就算沒(méi)找到,我們也可以理解的通衫嵌,其實(shí)读宙,如果從本質(zhì)上來(lái)說(shuō),它并不復(fù)雜楔绞,r表示第一個(gè)字節(jié)结闸,g表示第二個(gè)字節(jié),b表示第三個(gè)字節(jié)酒朵。甚至我們可以可以猜得到桦锄,還有in.a
表示透明度,然后我測(cè)試了一下蔫耽,發(fā)現(xiàn)真的編譯通過(guò)结耀。另外,從RenderScript Basics Tutorial這篇文章可以知道匙铡,還可以通過(guò)x
图甜、y
、z
鳖眼、w
分別取出對(duì)應(yīng)的第1黑毅、2、3具帮、4個(gè)字節(jié)博肋。也就是說(shuō)低斋,in.x
與in.r
都是一個(gè)意思.好了這里不再繼續(xù)糾結(jié)uchar4
.
RenderScript的核心我們編寫(xiě)完成了,從上面rs文件的invert函數(shù)我們知道匪凡,這個(gè)函數(shù)只對(duì)具體一個(gè)像素點(diǎn)操作膊畴,可是我們的圖片有width*height個(gè)像素點(diǎn),我們需要這些像素點(diǎn)并行執(zhí)行inver函數(shù)才能得到我們想要的結(jié)果病游。
我們?cè)倏纯碕ava代碼如何調(diào)用唇跨,使之并行計(jì)算。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mSrcImageView = (ImageView) findViewById(R.id.src);
mDstImageView = (ImageView) findViewById(R.id.dst);
mInBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
mOutBitmap = Bitmap.createBitmap(mInBitmap.getWidth(),mInBitmap.getHeight(), mInBitmap.getConfig());
mSrcImageView.setImageBitmap(mInBitmap);
RenderScript rs = RenderScript.create(this);
mScript = new ScriptC_hello(rs);
aIn = Allocation.createFromBitmap(rs, mInBitmap);
aOut = Allocation.createFromBitmap(rs, mInBitmap);
mScript.forEach_invert(aIn, aOut);
aOut.copyTo(mOutBitmap);
mDstImageView.setImageBitmap(mOutBitmap);
rs.destroy();
}
運(yùn)行出來(lái)的結(jié)果:
我們繼續(xù)解釋Java代碼:先看到第13行衬衬,創(chuàng)建的是一個(gè)RenderScript對(duì)象买猖。接下來(lái)是將我們編寫(xiě)的rs文件對(duì)應(yīng)的自動(dòng)生成的Java類(lèi)(即ScriptC_hello類(lèi))初始化。到目前為止滋尉,這些都很好理解玉控。緊接著是創(chuàng)建了兩個(gè)Allocation
對(duì)象,這個(gè)對(duì)象是干嘛用的呢狮惜?從名稱(chēng)可以看出高诺,它是用于分配內(nèi)存的,createFromBitmap
根據(jù)Bitmap分配內(nèi)存碾篡。為什么需要?jiǎng)?chuàng)建2個(gè)Allocation對(duì)象呢虱而?這主要是在執(zhí)行rs文件里面的并行函數(shù)時(shí)一個(gè)Allocation
類(lèi)型 aIn用于參數(shù)傳入,一個(gè)Allocation
類(lèi)型 aOut用于計(jì)算結(jié)果輸出开泽。這兩個(gè)Allocation的Element類(lèi)型必須相同牡拇,在函數(shù)調(diào)用時(shí)RenderScript會(huì)檢查,如果不想同會(huì)拋異常穆律。這里提到了Element
惠呼,Elemtent
是指Allocation
里的一項(xiàng)。比如我們要處理的是Bitmap
众旗,則Element
表示的類(lèi)型是像素罢杉。做并行計(jì)算時(shí)趟畏,aIn對(duì)應(yīng)的一個(gè)元素(Element
)的計(jì)算結(jié)果會(huì)放入aOut對(duì)應(yīng)的位置上贡歧。定位到代碼:mScript.forEach_invert(aIn, aOut);
我們的rs文件里面并沒(méi)有寫(xiě)forEach_invert
函數(shù),但是卻在ScriptC_hello
類(lèi)里面生成了這個(gè)函數(shù)赋秀。請(qǐng)注意利朵,我們編寫(xiě)了invert函數(shù),正因?yàn)槲覀兊膇nvert函數(shù)加了__attribute__((kernel))
關(guān)鍵字猎莲,所以绍弟,會(huì)生成forEach_invert
函數(shù),這個(gè)函數(shù)傳入的參數(shù)aIn和aOut我們都清楚了著洼,RenderScript會(huì)自動(dòng)將aIn里的每個(gè)元素(Element)并行的去執(zhí)行invert函數(shù).得到的結(jié)果放入aOut里樟遣。最后調(diào)用Allocation
的copyTo
函數(shù)把計(jì)算的結(jié)果轉(zhuǎn)入到Bitmap中而叼。
另外,值得注意的是豹悬,__attribute__((kernel))
修飾的函數(shù)葵陵,其形參該怎么寫(xiě),為啥我們這里是uchar4而不是uchar3或者是uint32之類(lèi)的呢瞻佛?我們?cè)撛趺创_定好這個(gè)參數(shù)呢脱篙?其實(shí),這主要是跟我們的需求有關(guān)伤柄,你可以根據(jù)需求改動(dòng)绊困。比如我們的aIn里的元素是像素,而一個(gè)像素有RGBA占4個(gè)字節(jié)适刀,因此我們寫(xiě)成uchar4作為形參秤朗。還有就是,后面還可以加形參uint32 x
,uint32 y
,uint32 z
笔喉。這些是可選項(xiàng)川梅,可以加也可以不加,不影響函數(shù)的調(diào)用,但是必須是uint32
類(lèi)型然遏。
還有個(gè)可選函數(shù)init()
贫途,在rs文件里的這個(gè)函數(shù)會(huì)指初始化時(shí)調(diào)用,并且只會(huì)調(diào)用一次待侵。
有時(shí)候我們希望返回的結(jié)果不止一個(gè)對(duì)象該怎么辦丢早?我們可以選擇使用全局變量,在rs文件中聲明全局變量秧倾,在rs文件的函數(shù)中把數(shù)據(jù)寫(xiě)入到rs文件的全局變量中怨酝。再?gòu)腏ava代碼中讀取rs的全局變量即可!那么在Java代碼中該怎么讀取和設(shè)置rs中的全局變量呢那先?答案是农猬,rs文件對(duì)應(yīng)生成的Java類(lèi)會(huì)自動(dòng)生成全局變量的get和set方法。比如售淡,在hello.rs文件中定義了全局變量int myVar
.自動(dòng)生成的ScriptC_hello類(lèi)中會(huì)自動(dòng)生成函數(shù):set_myVar(int v)
和get_myVar()
.這樣就可以訪問(wèn)rs文件中的全局變量了斤葱。
最后
回到最開(kāi)始說(shuō)的,提升上一篇文章的輪廓提取速度揖闸。如果沒(méi)有看過(guò)上一篇文章的請(qǐng)?zhí)^(guò)揍堕,或者是前去: Android自動(dòng)手繪,圓你兒時(shí)畫(huà)家夢(mèng)汤纸! 查看衩茸。我們?nèi)タ纯碨obel算法,主要分為2步贮泞,首先將彩色圖轉(zhuǎn)為灰度圖楞慈,在CommenUtils
類(lèi)的toGrayscale
函數(shù)中幔烛。然后再是調(diào)用Sobelsuanf ,在SobelUtils
類(lèi)的Sobel
函數(shù)。先看看toGrayscale
函數(shù):這個(gè)函數(shù)是直接調(diào)用系統(tǒng)的函數(shù)囊蓝,我們不去管说贝。在Sobel函數(shù)中,有兩個(gè)地方使用了兩個(gè)for循環(huán)慎颗,顯然可以通過(guò)RenderScript進(jìn)行并行計(jì)算乡恕,提升速度。篇幅原因俯萎,具體的實(shí)現(xiàn)這里就不提了傲宜。
源碼地址:RenderScript