Android模糊图像

在Android中。我们能够实现非常多非常酷的处理图片的效果。

在2014年某次会议的讲演《图像的魔力》中,我介绍了当中的一部分。

当中的一项技术是怎样模糊图像。演示样例代码是使用RenderScript实现的,由于在Android中没有内置的可使用的简单的API。在这个系列中,我们将着眼于RenderScript模糊技术和JAVA实现模糊功能。我们还将进行一些基准測试。以了解每种方案的运行情况,并探讨获取最佳性能的可行方法。

让我们先从实现一个简单的能够运行的样例開始,使用RenderScript!对于没有使用过RenderScript的开发人员来说,这是一个让人心生恐惧的设想,由于RenderScript真的是非常难,是不是?是的,的确是。只是它也有一些事情真的非常easy,而模糊图像就是当中之中的一个。
对于不熟悉RenderScript的人来说,他是在API11中引入的,而且有一个compat库,提供RenderScript给API8及以后的版本号。它本质上是一个面向图形的本地计算框架。RenderScript引擎在运行期会选择最合适的处理器(CPU或者GPU核心。多核处理器间能够分解原子操作)来运行请求的操作。

本地语法基于C99。与OpenCL, CUDA, and GLSL的API类似。

假设这听起来非常可怕,请稍等一下。由于我们正要简化整个过程。由于它是一个框架。同意我们创建自己定义的内核来实现过滤与处理,它内置了不少能够使用的内核。当中的一个同意我们模糊图像。
后面的实例代码基于API17及更高版本号开发,我已经决定不使用compat库,由于在本文写作的时候,Android Studio还不支持。

此外,我们使用的模糊核心在API17后才引入,所以有最小SDK版本号为17的需求。

让我们深入到一个简单的样例,这里有一个非常easy的RelativeLayout。包括了一个ImageView,上层还有个TextView:
  1. <?xml version="1.0" encoding="utf-8"?

    >

  2. <RelativeLayout
  3.     xmlns:android="http://schemas.android.com/apk/res/android"
  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:id="@+id/image"
  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:id="@+id/text"
  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="http://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
译文链接: http://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意味着能够非常easy在随意布局中加入。

最后,模糊图片实现例如以下:

在下一篇文章中,我会更深入地回答为什么要避免在onDraw中运行模糊操作,而且还会介绍实用的性能測量工具。

这篇文章的代码在这里

via:
原文链接: stylingandroid   翻译: 伯乐在线- fhdis

posted on 2019-04-05 14:22  xfgnongmin  阅读(306)  评论(0编辑  收藏  举报

导航