使用RenderScript实现高斯模糊(毛玻璃/磨砂)效果

kalf7426 8年前
   <p><strong>前言</strong></p>    <p>逛instagram的时候,偶然发现,instagram的对话框设计的很有意思,如下图:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/da5be863c4147ea7033138022b431337.jpg"></p>    <p>它的dialog的背景竟然是毛玻璃效果的,在我看来真漂亮,恩,对话框和迪丽热巴都漂亮。看到这么好的效果,当然就要开始搞事情了,自己动手实现差不多的效果。最终的实现效果如下图:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/6ecf0eebef202a64470f0b5c1b66c0e7.png"></p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/5317db4908d1ac6948d1b813b52d72fd.jpg"></p>    <p>分别实现了对话框背景的虚化和手动调节虚化程度。</p>    <p><strong>实现方法对比</strong></p>    <p>最开始想要实现毛玻璃效果时,我是一脸懵逼的,不知道如何下手。幸亏,有万能的Google。搜索之后发现常见的实现方法有4种,分别是:</p>    <ul>     <li>RenderScript</li>     <li>Java算法</li>     <li>NDK算法</li>     <li>openGL</li>    </ul>    <p>处理一整张图片这么大计算量的工作,openGL的性能最好,而用java实现肯定是最差的了。而RenderScript和NDK的性能相当,但是你懂得,NDK和openGL我无可奈何,综合考虑,RenderScript应该是最适合的。</p>    <p>但并不是说RenderScript就是完全没有问题的:</p>    <ol>     <li>模糊半径(radius)越大,性能要求越高,模糊半径不能超过25,所以并不能得到模糊度非常高的图片。</li>     <li>ScriptIntrinsicBlur在API 17时才被引入,如果需要在Android 4.2以下的设备上实现,就需要引入RenderScript Support Library,当然,安装包体积会相应的增大。</li>    </ol>    <p><strong>RenderScript实现</strong></p>    <p>首先在app目录下build.gradle文件中添加如下代码:</p>    <pre>  <code class="language-java">defaultConfig {           applicationId "io.github.marktony.gaussianblur"           minSdkVersion 19           targetSdkVersion 25           versionCode 1           versionName "1.0"           renderscriptTargetApi 19           renderscriptSupportModeEnabled true       }    </code></pre>    <p>RenderScriptIntrinsics提供了一些可以帮助我们快速实现各种图片处理的操作类,例如,ScriptIntrinsicBlur,可以简单高效实现 高斯模糊效果。</p>    <pre>  <code class="language-java">package io.github.marktony.gaussianblur;      import android.content.Context;   import android.graphics.Bitmap;   import android.support.annotation.IntRange;   import android.support.annotation.NonNull;   import android.support.v8.renderscript.Allocation;   import android.support.v8.renderscript.Element;   import android.support.v8.renderscript.RenderScript;   import android.support.v8.renderscript.ScriptIntrinsicBlur;      public class RenderScriptGaussianBlur {          private RenderScript renderScript;          public RenderScriptGaussianBlur(@NonNull Context context) {           this.renderScript = RenderScript.create(context);       }          public Bitmap gaussianBlur(@IntRange(from = 1, to = 25) int radius, Bitmap original) {           Allocation input = Allocation.createFromBitmap(renderScript, original);           Allocation output = Allocation.createTyped(renderScript, input.getType());           ScriptIntrinsicBlur scriptIntrinsicBlur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));           scriptIntrinsicBlur.setRadius(radius);           scriptIntrinsicBlur.setInput(input);           scriptIntrinsicBlur.forEach(output);           output.copyTo(original);           return original;       }      }    </code></pre>    <p>然后就可以直接使用RenderScriptGaussianBlur,愉快地根据SeekBar的值,实现不同程度的模糊了。</p>    <pre>  <code class="language-java">package io.github.marktony.gaussianblur;      import android.content.DialogInterface;   import android.graphics.Bitmap;   import android.graphics.BitmapFactory;   import android.support.v7.app.AlertDialog;   import android.support.v7.app.AppCompatActivity;   import android.os.Bundle;   import android.util.Log;   import android.view.View;   import android.view.Window;   import android.view.WindowManager;   import android.widget.FrameLayout;   import android.widget.ImageView;   import android.widget.LinearLayout;   import android.widget.SeekBar;   import android.widget.TextView;      public class MainActivity extends AppCompatActivity {          private ImageView imageView;       private ImageView container;       private LinearLayout layout;       private TextView textViewProgress;       private RenderScriptGaussianBlur blur;          @Override       protected void onCreate(Bundle savedInstanceState) {           super.onCreate(savedInstanceState);           setContentView(R.layout.activity_main);              imageView = (ImageView) findViewById(R.id.imageView);           container = (ImageView) findViewById(R.id.container);              container.setVisibility(View.GONE);              layout = (LinearLayout) findViewById(R.id.layout);              layout.setVisibility(View.VISIBLE);              SeekBar seekBar = (SeekBar) findViewById(R.id.seekBar);           textViewProgress = (TextView) findViewById(R.id.textViewProgress);           TextView textViewDialog = (TextView) findViewById(R.id.textViewDialog);           blur = new RenderScriptGaussianBlur(MainActivity.this);              seekBar.setMax(25);           seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {               @Override               public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {                   textViewProgress.setText(String.valueOf(progress));               }                  @Override               public void onStartTrackingTouch(SeekBar seekBar) {                  }                  @Override               public void onStopTrackingTouch(SeekBar seekBar) {                   int radius = seekBar.getProgress();                   if (radius < 1) {                       radius = 1;                   }                   Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);                   imageView.setImageBitmap(blur.gaussianBlur(radius, bitmap));               }           });              textViewDialog.setOnClickListener(new View.OnClickListener() {               @Override               public void onClick(View v) {                      container.setVisibility(View.VISIBLE);                      layout.setDrawingCacheEnabled(true);                   layout.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_LOW);                      Bitmap bitmap = layout.getDrawingCache();                      container.setImageBitmap(blur.gaussianBlur(25, bitmap));                      layout.setVisibility(View.INVISIBLE);                      AlertDialog dialog = new AlertDialog.Builder(MainActivity.this).create();                   dialog.setTitle("Title");                   dialog.setMessage("Message");                   dialog.setButton(DialogInterface.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() {                       @Override                       public void onClick(DialogInterface dialog, int which) {                           dialog.dismiss();                       }                   });                   dialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", new DialogInterface.OnClickListener() {                       @Override                       public void onClick(DialogInterface dialog, int which) {                          }                   });                   dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {                       @Override                       public void onCancel(DialogInterface dialog) {                          }                   });                      dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {                       @Override                       public void onCancel(DialogInterface dialog) {                           container.setVisibility(View.GONE);                           layout.setVisibility(View.VISIBLE);                       }                   });                      dialog.show();               }           });          }   }    </code></pre>    <p>在代码里做了一些view的可见性的操作,比较简单,相信你能看懂的。和instagram中dialog的实现有一点不同的是,我没有截取整个页面的bitmap,只是截取了actionbar下的内容,如果一定要实现一样的效果,调整一下页面的布局就可以了。这里不多说了。</p>    <p>是不是很简单呢?</p>    <p><strong>轮子</strong></p>    <p>除了RenderScript外,还有一些优秀的轮子:</p>    <ul>     <li><a href="/misc/goto?guid=4958868409969690689" rel="nofollow,noindex">500px-android-blur</a></li>     <li><a href="/misc/goto?guid=4959735268199192530" rel="nofollow,noindex">Blurry</a></li>     <li><a href="/misc/goto?guid=4959549999391176870" rel="nofollow,noindex">android-stackblur</a></li>     <li><a href="/misc/goto?guid=4959735268309566445" rel="nofollow,noindex">FastBlur</a> :Java算法实现</li>    </ul>    <p>BlurTestAndroid对不同类库的实现方式、采取的算法和所耗费的时间做了统计和比较,你也可以下载它的demo app,自行测试。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/2d250f0499788336729d753fd47cad10.png"></p>    <p> </p>    <p> </p>    <p>来自:http://mobile.51cto.com/android-528446.htm</p>    <p> </p>