閱讀290 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Android模煳圖像

在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. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout
  3.     xmlns:andro
  4.     android:layout_width="fill_parent"
  5.     android:layout_height="fill_parent"
  6.     android:gravity="center"
  7.     android:orientation="vertical" >
  8.      
  9.     <ImageView
  10.         android:
  11.         android:src="@drawable/broadstairs"
  12.         android:layout_width="match_parent"
  13.         android:layout_height="match_parent"
  14.         android:scaleType="matrix"
  15.         android:layout_centerInParent="true"/>

  16.     <TextView
  17.         android:
  18.         android:layout_width="wrap_content"
  19.         android:layout_height="wrap_content"
  20.         android:text="@string/hello"
  21.         android:layout_centerHorizontal="true"
  22.         android:textColor="<a href="https://www.jobbole.com/members/android/" rel="nofollow">@android</a>:color/white"
  23.         android:layout_marginTop="300dp"
  24.         android:textStyle="bold"
  25.         android:textSize="48sp"/>
  26. </RelativeLayout>
複製代碼



我們想要做的效果是模煳ImageView內位於TextView顯示區域的圖像,有效的模煳ImageView後的區域。我們最終使用的技術是拿到位於TextView區域的圖像副本進行模煳,然後再將模煳後的副本設定為TextView的背景。
我刻意的設計了這種布局,使圖像顯示實際大小,並從顯示屏的左上方開始。這樣讓隨後的位置計算簡單些,而且這裏討論的是模煳技術而不是圖像定位的數學算法。嚐試設定ImageView的屬性android:scaleType=”center”,就會發現定位出現錯亂。
此處有一個方法,它有3個參數,一個位圖(已在ImageView裏獲得),一個視圖(這是我們的TextView,但是該技術適用於任意的視圖類型,所以我們將在方法中進行支持),以及一個半徑用來控製模煳的程度。
  1. private void blur(Bitmap bkg, View view, float radius) {
  2.     Bitmap overlay = Bitmap.createBitmap(
  3.         view.getMeasuredWidth(),
  4.         view.getMeasuredHeight(),
  5.         Bitmap.Config.ARGB_8888);  
  6.     Canvas canvas = new Canvas(overlay);
  7.     canvas.drawBitmap(bkg, -view.getLeft(),
  8.         -view.getTop(), null);   
  9.     RenderScript rs = RenderScript.create(this);  
  10.     Allocation overlayAlloc = Allocation.createFromBitmap(
  11.         rs, overlay);  
  12.     ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(
  13.         rs, overlayAlloc.getElement());   
  14.     blur.setInput(overlayAlloc);  
  15.     blur.setRadius(radius);  
  16.     blur.forEach(overlayAlloc);  
  17.     overlayAlloc.copyTo(overlay);  
  18.     view.setBackground(new BitmapDrawable(
  19.         getResources(), overlay));   
  20.     rs.destroy();  
  21. }
複製代碼

我們要做的第一件事是創建一個新的位圖對象,用來保存想要模煳的圖像區域的副本。它的大小將會是我們視圖的尺寸(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行)。
這就是我們完整的基本模煳邏輯,但目前還不清楚什麼地方和什麼時候我們需要調用此方法。不幸的是關於這個問題不能簡單的進行回答,所以我們將在接下來的文章中討論。
平時我喜歡每篇文章都發布相關工作代碼,但是在本例中,我們還沒達到有一個可運行的完整的端到端的示例的程度。我保證在接下來的文章中進行調整。
via:
原文鏈接: stylingandroid   翻譯: 伯樂在線- fhdis
譯文鏈接: https://blog.jobbole.com/65353/




我們介紹了使用RenderScript使另一個視圖範圍內的圖片部分模煳。但是實際上,我們並沒有深入地調用這個方法來研究圖像模煳行為。原因是我們需要在性能方麵進行仔細考慮,這篇文章我們會進行更進一步地的探索。

調用這個方法最直白的方法是父布局的onDraw()。有經驗的開發者讀到這裏可能會開始搖頭,我們應該保持onDraw方法的實現盡可能有效。以前的文章中的代碼包括創建對象、位圖操作和切換到renderScript上下文。其中,OnDraw會降低幀速率。你可以不相信我的做法,但是可以通過測量並證明它是有效的。在後麵的係列中,我們就會這樣做。
如果布局是靜態的(即我們的布局不包含任何動畫),在布局時就不會改變待模煳的位置和範圍。隻有在布局改變時執行該操作才有意義,但前提是布局的所有視圖大小和位置根據布局變化測量和計算過。這裏有一個非常實用的技巧,可以注冊一個OnGlobalLayoutListener監聽函數。當布局發生改變的時候會調用onGlobalLayout()方法。當我們收到布局已經改變的通知時,注冊的OnPreDrawListener監聽函數的onPreDraw()方法會被調用每當執行onDraw方法。我們要做的第一件事情就是取消注冊onPreDraw()方法,這樣隻有在布局改變的時候才會被調用,而不是每次onDraw方法觸發時都調用。下麵可以執行模煳方法,從這個方法的返回值很重要,使用它可以讓我們放棄onDraw操作,重複之前的布局。這對在回調函數中修改布局非常有幫助,但是這裏不需要這麼做。所以返回true,繼續繪製。
我們的Activity代碼如下:
  1. public class MainActivity extends Activity {
  2.     private ImageView mImage;
  3.     private TextView mText;
  4.   
  5.     private OnPreDrawListener mPreDrawListener =
  6.         new OnPreDrawListener() {
  7.   
  8.         @Override
  9.         public boolean onPreDraw() {
  10.             ViewTreeObserver observer =  mText.getViewTreeObserver();
  11.             if(observer != null) {
  12.                 observer.removeOnPreDrawListener(this);
  13.             }
  14.             Drawable drawable = mImage.getDrawable();
  15.             if (drawable != null &&
  16.                 drawable instanceof BitmapDrawable) {
  17.                 Bitmap bitmap =
  18.                     ((BitmapDrawable) drawable).getBitmap();
  19.                 if (bitmap != null) {
  20.                     blur(bitmap, mText, 25);
  21.                 }
  22.             }
  23.             return true;
  24.         }
  25.     };
  26.   
  27.     private OnGlobalLayoutListener mLayoutListener =
  28.         new OnGlobalLayoutListener() {
  29.   
  30.         @Override
  31.         public void onGlobalLayout() {
  32.             ViewTreeObserver observer =   mText.getViewTreeObserver();
  33.             if(observer != null) {
  34.                 observer.addOnPreDrawListener(
  35.                     mPreDrawListener);
  36.             }
  37.         }
  38.     };
  39.   
  40.     /**
  41.      * Called when the activity is first created.
  42.      */
  43.     @Override
  44.     public void onCreate(Bundle savedInstanceState) {
  45.         super.onCreate(savedInstanceState);
  46.         setContentView(R.layout.main);
  47.         mImage = (ImageView) findViewById(R.id.image);
  48.         mText = (TextView)findViewById(R.id.text);
  49.         if (mImage != null && mText != null) {
  50.             ViewTreeObserver observer =
  51.                 mText.getViewTreeObserver();
  52.             if (observer != null) {
  53.                 observer.addOnGlobalLayoutListener(
  54.                         mLayoutListener);
  55.             }
  56.         }
  57.     }
  58.   
  59.     private void blur(Bitmap bkg, View view, float radius) {
  60.     ....
  61.     }
  62. }
複製代碼
在父布局覆蓋onDraw方法的優點是,可以在布局層次上任意附加OnPreDrawListener方法。因此需要自定義一個布局,這個布局是標準布局的子類,這樣就可以覆蓋onDraw方法。使用predrawlistener意味著可以非常容易在任意布局中添加。
最後,模煳圖片實現如下:

在下一篇文章中,我會更深入地回答為什麼要避免在onDraw中執行模煳操作,並且還會介紹有用的性能測量工具。
這篇文章的代碼在這裏

via:
原文鏈接: stylingandroid   翻譯: 伯樂在線- fhdis
譯文鏈接: https://blog.jobbole.com/65353/

最後更新:2017-04-03 12:56:25

  上一篇:go 九度題目1526:朋友圈
  下一篇:go BitmapFactory.Options.inSampleSize 的用法