1. 程式人生 > >Android快速實現動態模糊效果

Android快速實現動態模糊效果

寫在前面

現在,越來越多的App裡面使用了模糊效果,這種模糊效果稱之為高斯模糊。大家都知道,在Android平臺上進行模糊渲染是一個相當耗CPU也相當耗時的操作,一旦處理不好,卡頓是在所難免的。一般來說,考慮到效率,渲染一張圖片最好的方法是使用OpenGL,其次是使用C++/C,使用Java程式碼是效率是最低,速度也是最慢的。但是Android推出RenderScript之後,我們就有了選擇,測試表明,使用RederScript的渲染效率和使用C++/C不相上下,但是使用RenderScript卻比使用JNI簡單得多!同時,Android團隊提供了RenderScript的支援庫,使得在低版本的Android平臺上也能使用。

不過在使用RenderScript之前,對於模糊一張圖片,需要注意的是,我們應該儘量不要使用原尺寸解析度的圖片,最好將圖片縮小比例,這小渲染的效率要高一些,速度也更快一些。

什麼是RenderScript

RenderScript是一種低階的高效能程式語言,用於3D渲染和處理密集型計算(3D播放等和關於CPU密集型的計算)。一直以來Android 在繪圖效能的表現一直差強人意,引入NDK之後才有所改善,而在Honeycomb 中釋出了RenderScript這一殺手級在Framework 後,大大的增加了Android本地語言的執行能力和計算能力。現在網上介紹RenderScript的文章非常少,附上一篇部落格,大家可以能更好理解這門語言。

動態模糊的實現

使用之前,先要在Module build.gradle裡面作下面的定義:
MainActivity.java
package com.jackie.blurimage;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    private ImageView mBlurImage, mOriginImage;
    private SeekBar mSeekBar;
    private TextView mSeekProgress;

    private int mAlpha;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
        initData();
        initEvent();
    }

    private void initView() {
        mBlurImage = (ImageView) findViewById(R.id.blur_image);
        mOriginImage = (ImageView) findViewById(R.id.origin_image);
        mSeekBar = (SeekBar) findViewById(R.id.seek_bar);
        mSeekProgress = (TextView) findViewById(R.id.seek_progress);
    }

    private void initData() {
        // 獲取圖片
        Bitmap originBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.blur);
        Bitmap blurBitmap = BlurUtils.blur(this, originBitmap);

        // 填充模糊後的影象和原圖
        mBlurImage.setImageBitmap(blurBitmap);
        mOriginImage.setImageBitmap(originBitmap);
    }

    private void initEvent() {
        mSeekBar.setMax(100);

        mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                mAlpha = progress;

                mOriginImage.setAlpha((int) (255 - mAlpha * 2.55));
                mSeekProgress.setText(String.valueOf(mAlpha));
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });
    }
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_weight="1"
        android:layout_height="0dp">

        <ImageView
            android:id="@+id/blur_image"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="centerCrop"
            android:src="@drawable/blur"/>

        <ImageView
            android:id="@+id/origin_image"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="centerCrop"/>
    </FrameLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:orientation="vertical">

        <SeekBar
            android:id="@+id/seek_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="16dp"
            android:layout_marginRight="16dp"
            android:layout_marginTop="@dimen/activity_vertical_margin"/>

        <TextView
            android:id="@+id/seek_progress"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:text="0"
            android:textSize="24sp"/>
    </LinearLayout>
</LinearLayout>

從上面的程式碼可以看出,在FrameLayout上放了兩張圖片,然後動態更改圖片的透明度來達到動態模糊效果。
BlurUtils.java

package com.jackie.blurimage;

import android.content.Context;
import android.graphics.Bitmap;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RenderScript;
import android.renderscript.ScriptIntrinsicBlur;

/**
 * Created by Jackie on 2017/1/21.
 * 高斯模糊工具類
 */

public class BlurUtils {
    /**
     * 圖片縮放比例
     */
    private static final float SCALE_DEGREE = 0.4f;
    /**
     * 最大模糊度(在0.0到25.0之間)
     */
    private static final float BLUR_RADIUS = 25f;

    /**
     * 模糊圖片
     * @param context   上下文
     * @param bitmap    需要模糊的圖片
     * @return          模糊處理後的圖片
     */
    public static Bitmap blur(Context context,Bitmap bitmap) {
        //計算圖片縮小的長寬
        int width = Math.round(bitmap.getWidth() * SCALE_DEGREE);
        int height = Math.round(bitmap.getHeight() * SCALE_DEGREE);

        //將縮小後的圖片作為預渲染的圖片
        Bitmap inputBitmap = Bitmap.createScaledBitmap(bitmap, width, height, false);
        //建立一張渲染後的輸入圖片
        Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap);

        //建立RenderScript核心物件
        RenderScript renderScript = RenderScript.create(context);
        //建立一個模糊效果的RenderScript的工具物件
        ScriptIntrinsicBlur scriptIntrinsicBlur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));

        /**
         * 由於RenderScript並沒有使用VM來分配記憶體,所以需要使用Allocation類來建立和分配記憶體空間。
         * 建立Allocation物件的時候其實記憶體是空的,需要使用copyTo()將資料填充進去。
         */
        Allocation inputAllocation = Allocation.createFromBitmap(renderScript, inputBitmap);
        Allocation outputAllocation = Allocation.createFromBitmap(renderScript, outputBitmap);

        //設定渲染的模糊程度,25f是最大模糊度
        scriptIntrinsicBlur.setRadius(BLUR_RADIUS);
        //設定ScriptIntrinsicBlur物件的輸入記憶體
        scriptIntrinsicBlur.setInput(inputAllocation);
        //將ScriptIntrinsicBlur輸出資料儲存到輸出記憶體中
        scriptIntrinsicBlur.forEach(outputAllocation);

        //將資料填充到Allocation中
        outputAllocation.copyTo(outputBitmap);

        return outputBitmap;
    }
}

效果圖如下,妹紙一枚!