在Android中,我們可以實現很多很酷的處理圖片的效果。在2014年某次會議的講演《影象的魔力》中,我介紹了其中的一部分。其中的一項技術是如何模糊影象,示例程式碼是使用RenderScript實現的,因為在Android中沒有內建的可使用的簡單的API。在這個系列中,我們將著眼於RenderScript模糊技術和JAVA實現模糊功能。我們還將進行一些基準測試,以瞭解每種方案的執行情況,並探討獲取最佳效能的可行方法。
讓我們先從實現一個簡單的可以執行的例子開始,使用RenderScript!對於沒有使用過RenderScript的開發者來說,這是一個讓人心生恐懼的設想,因為RenderScript真的是很難,是不是?是的,的確是。不過它也有一些事情真的很簡單,而模糊影象就是其中之一。
對於不熟悉RenderScript的人來說,他是在API11中引入的,並且有一個compat庫,提供RenderScript給API8及以後的版本。它本質上是一個面向圖形的本地計算框架。RenderScript引擎在執行期會選擇最合適的處理器(CPU或者GPU核心,多核處理器間可以分解原子操作)來執行請求的操作。本地語法基於C99,與OpenCL, CUDA, and GLSL的API相似。
如果這聽起來很可怕,請稍等一下,因為我們正要簡化整個過程。因為它是一個框架,允許我們建立自定義的核心來實現過濾與處理,它內建了不少可以使用的核心,其中的一個允許我們模糊影象。
後面的例項程式碼基於API17及更高版本開發,我已經決定不使用compat庫,因為在本文寫作的時候,Android Studio還不支援。此外,我們使用的模糊核心在API17後才引入,所以有最小SDK版本為17的需求。
讓我們深入到一個簡單的例子,這裡有一個非常簡單的RelativeLayout,包含了一個ImageView,上層還有個TextView:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
; html-script: false ] <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="center" android:orientation="vertical" > <ImageView android:id="@+id/image" android:src="@drawable/broadstairs" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="matrix" android:layout_centerInParent="true"/> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/hello" android:layout_centerHorizontal="true" android:textColor="@android:color/white" android:layout_marginTop="300dp" android:textStyle="bold" android:textSize="48sp"/> </RelativeLayout> |
我們想要做的效果是模糊ImageView內位於TextView顯示區域的影象,有效的模糊ImageView後的區域。我們最終使用的技術是拿到位於TextView區域的影象副本進行模糊,然後再將模糊後的副本設定為TextView的背景。
我刻意的設計了這種佈局,使影象顯示實際大小,並從螢幕的左上方開始。這樣讓隨後的位置計算簡單些,而且這裡討論的是模糊技術而不是影象定位的數學演算法。嘗試設定ImageView的屬性android:scaleType=”center”,就會發現定位出現錯亂。
此處有一個方法,它有3個引數,一個點陣圖(已在ImageView裡獲得),一個檢視(這是我們的TextView,但是該技術適用於任意的檢視型別,所以我們將在方法中進行支援),以及一個半徑用來控制模糊的程度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
; html-script: false ] private void blur(Bitmap bkg, View view, float radius) { Bitmap overlay = Bitmap.createBitmap( view.getMeasuredWidth(), view.getMeasuredHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(overlay); canvas.drawBitmap(bkg, -view.getLeft(), -view.getTop(), null); RenderScript rs = RenderScript.create(this); Allocation overlayAlloc = Allocation.createFromBitmap( rs, overlay); ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create( rs, overlayAlloc.getElement()); blur.setInput(overlayAlloc); blur.setRadius(radius); blur.forEach(overlayAlloc); overlayAlloc.copyTo(overlay); view.setBackground(new BitmapDrawable( getResources(), overlay)); rs.destroy(); } |
我們要做的第一件事是建立一個新的點陣圖物件,用來儲存想要模糊的影象區域的副本。它的大小將會是我們檢視的尺寸(2-5行)。
現在我們將空的點陣圖包裝到畫布中,我們可以在上面繪圖(7行)。
接下來的步驟我稱為”快取剪下”,我們拷貝點陣圖中的處於檢視顯示區域內的那部分影象(9-10行)。
接下來我們必須做的事情是構建一個RenderScript物件上下文,在其中我們執行模糊操作(12行)。
RenderScript是一個本地環境,它在自己的記憶體空間內執行,所以我們不能簡單的傳遞點陣圖引用,我們需要在JAVA和RenderScript記憶體區域進行序列化。這使用了一個分配例項來完成,這是在RenderScript記憶體區域中建立和引用物件的方式,為我們的點陣圖建立一個分配會將點陣圖的內容拷貝的分配區域中(14-15行)。
現在我們建立一個 ScriptIntrinsicBlur例項,它建立合適的RenderScript模糊指令碼,並且是指令碼物件的Java介面,它讓我們能夠控制它。(17-18行)。
指令碼的輸入定義了我們需要執行模糊的點陣圖源(20行)。
現在我們設定半徑來控制模糊強度(22行)。
forEach方法執行模糊操作,引數代表了結果的輸出位置。我們將它寫回源分配地址以減少建立的物件數量(24行)。
現在模糊完成了,但是我們必須把模糊後的影象拷貝回JAVA記憶體區域(26行)。
最後將模糊後的點陣圖包裝到BitmapDrawable內,並設定為檢視的背景(28-29行)。
我們已經使用完RenderScript上下文,清理掉。這樣做也將自動刪除我們的分配(31行)。
這就是我們完整的基本模糊邏輯,但目前還不清楚什麼地方和什麼時候我們需要呼叫此方法。不幸的是關於這個問題不能簡單的進行回答,所以我們將在接下來的文章中討論。
平時我喜歡每篇文章都發布相關工作程式碼,但是在本例中,我們還沒達到有一個可執行的完整的端到端的示例的程度。我保證在接下來的文章中進行調整。