Glide Frame Gauss Blur Picture Processing

Posted by stephenk on Sat, 22 Jun 2019 18:34:16 +0200

From: https://ligboy.org/?p=380

For those who don't need to see rubbish, please go directly Show me the code

For fuzzy processing, there are four general solutions:

  1. Algorithmic processing for Java implementation;
  2. Algorithmic processing for NDK implementation;
  3. RenderScript processing;
  4. openGL processing;

Generally speaking, the performance of these four schemes should be: 1 < 3 < 2 < 4 (performance of 2, 3 is very close), but RenderScript is the easiest way (obviously, platform independent).

RenderScript is a platform-independent, computation-intensive script provided by Android since SDK17 that uses C99 syntax scripts to implement high-performance computing and contains a large number of inline functions, including those we use today: ScriptIntrinsicBlur , which is an inline function of Gauss fuzzy processing, is used by general systems to compute shadows.

As mentioned above, RenderScript was introduced from SDK17, but the Android team has provided us with the RenderScript support library, and the introduction of RenderScript support libraries in Gradle-base projects has been extremely simple, requiring only 11 or 12 lines from module build.gradle:

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"
 
    defaultConfig {
        applicationId "org.ligboy.backsound"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
        renderscriptTargetApi 20
        renderscriptSupportModeEnabled true
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

Next, let's briefly talk about ScriptIntrinsicBlur. Although it is convenient to use it, it has drawbacks. Fuzzy radius cannot exceed 25.0f, but we can enlarge the radius by reducing the disguised sampling of the original image.

Glide is an excellent framework for a range of functions such as image loading and caching. It is elegant, uses gradients and is easy to expand. Glide provides a way to customize Transformation s to extend image processing

Here's how you customize the Transformation s. You won't need to go into details here. If you're interested, you can view the official Glide documentation: Transformation .Specifically, each Transformation has a String The callback of getId(), which is used to name the file when caching the processed picture, so that the next time the same picture uses the same Transformation, the cached file will be read directly without any actual processing, so the ID of our implementation needs to be associated with all the adjustment parameters, and the ID of the parameter change should be changed accordingly.Here's my custom BlurTransformation

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.support.annotation.FloatRange;
import android.support.v8.renderscript.Allocation;
import android.support.v8.renderscript.Element;
import android.support.v8.renderscript.RenderScript;
import android.support.v8.renderscript.ScriptIntrinsicBlur;

import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool;
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation;
import com.bumptech.glide.request.target.Target;

/**
 * Georgia Blur Transformation
 * <p>
 * @author Ligboy.Liu ligboy@gmail.com.
 */
public class BlurTransformation extends BitmapTransformation {

    private static final String ID = "org.ligboy.glide.BlurTransformation";

    public static final float DEFAULT_RADIUS = 25.0f;
    public static final float MAX_RADIUS = 25.0f;
    private static final float DEFAULT_SAMPLING = 1.0f;

    private Context mContext;
    private float mSampling = DEFAULT_SAMPLING;
    private float mRadius;
    private int mColor;

    public static class Builder {

        private Context mContext;
        private float mRadius = DEFAULT_RADIUS;
        private int mColor = Color.TRANSPARENT;

        public Builder(Context mContext) {
            this.mContext = mContext;
        }

        public float getRadius() {
            return mRadius;
        }

        public Builder setRadius(float radius) {
            mRadius = radius;
            return this;
        }

        public int getColor() {
            return mColor;
        }

        public Builder setColor(int color) {
            mColor = color;
            return this;
        }

        public BlurTransformation build() {
            return new BlurTransformation(mContext, mRadius, mColor);
        }

    }

    /**
     *
     * @param context Context
     * @param radius The blur's radius.
     * @param color The color filter for blurring.
     */
    public BlurTransformation(Context context, @FloatRange(from = 0.0f) float radius, int color) {
        super(context);
        mContext = context;
        if (radius > MAX_RADIUS) {
            mSampling = radius / 25.0f;
            mRadius = MAX_RADIUS;
        } else {
            mRadius = radius;
        }
        mColor = color;
    }
    /**
     *
     * @param context Context
     * @param radius The blur's radius.
     */
    public BlurTransformation(Context context, @FloatRange(from = 0.0f) float radius) {
        this(context, radius, Color.TRANSPARENT);
    }

    public BlurTransformation(Context context) {
        this(context, DEFAULT_RADIUS);
    }

    /**
     * Transforms the given {@link Bitmap} based on the given dimensions and returns the transformed
     * result.
     * <p/>
     * <p>
     * The provided Bitmap, toTransform, should not be recycled or returned to the pool. Glide will automatically
     * recycle and/or reuse toTransform if the transformation returns a different Bitmap. Similarly implementations
     * should never recycle or return Bitmaps that are returned as the result of this method. Recycling or returning
     * the provided and/or the returned Bitmap to the pool will lead to a variety of runtime exceptions and drawing
     * errors. See #408 for an example. If the implementation obtains and discards intermediate Bitmaps, they may
     * safely be returned to the BitmapPool and/or recycled.
     * </p>
     * <p/>
     * <p>
     * outWidth and outHeight will never be {@link Target#SIZE_ORIGINAL}, this
     * class converts them to be the size of the Bitmap we're going to transform before calling this method.
     * </p>
     *
     * @param pool        A {@link BitmapPool} that can be used to obtain and
     *                    return intermediate {@link Bitmap}s used in this transformation. For every
     *                    {@link Bitmap} obtained from the pool during this transformation, a
     *                    {@link Bitmap} must also be returned.
     * @param toTransform The {@link Bitmap} to transform.
     * @param outWidth    The ideal width of the transformed bitmap (the transformed width does not need to match exactly).
     * @param outHeight   The ideal height of the transformed bitmap (the transformed heightdoes not need to match
     */
    @Override
    protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
        boolean needScaled = mSampling == DEFAULT_SAMPLING;
        int originWidth = toTransform.getWidth();
        int originHeight = toTransform.getHeight();
        int width, height;
        if (needScaled) {
            width = originWidth;
            height = originHeight;
        } else {
            width = (int) (originWidth / mSampling);
            height = (int) (originHeight / mSampling);
        }
        //find a re-use bitmap
        Bitmap bitmap = pool.get(width, height, Bitmap.Config.ARGB_8888);
        if (bitmap == null) {
            bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        }

        Canvas canvas = new Canvas(bitmap);
        if (mSampling != DEFAULT_SAMPLING) {
            canvas.scale(1 / mSampling, 1 / mSampling);
        }
        Paint paint = new Paint();
        paint.setFlags(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
        PorterDuffColorFilter filter =
                new PorterDuffColorFilter(mColor, PorterDuff.Mode.SRC_ATOP);
        paint.setColorFilter(filter);
        canvas.drawBitmap(toTransform, 0, 0, paint);
// TIPS: Glide will take care of returning our original Bitmap to the BitmapPool for us,
// we needn't to recycle it. 
//        toTransform.recycle();  <--- Just for tips. by Ligboy

        RenderScript rs = RenderScript.create(mContext);
        Allocation input = Allocation.createFromBitmap(rs, bitmap, Allocation.MipmapControl.MIPMAP_NONE,
                Allocation.USAGE_SCRIPT);
        Allocation output = Allocation.createTyped(rs, input.getType());
        ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));

        blur.setInput(input);
        blur.setRadius(mRadius);
        blur.forEach(output);
        output.copyTo(bitmap);

        rs.destroy();

        if (needScaled) {
            return bitmap;
        } else {
            Bitmap scaled = Bitmap.createScaledBitmap(bitmap, originWidth, originHeight, true);
            bitmap.recycle();
            return scaled;
        }
    }

    /**
     * A method to get a unique identifier for this particular transformation that can be used as part of a cache key.
     * The fully qualified class name for this class is appropriate if written out, but getClass().getName() is not
     * because the name may be changed by proguard.
     * <p/>
     * <p>
     * If this transformation does not affect the data that will be stored in cache, returning an empty string here
     * is acceptable.
     * </p>
     *
     * @return A string that uniquely identifies this transformation.
     */
    @Override
    public String getId() {
        StringBuilder sb = new StringBuilder(ID);
        sb
                .append('-').append(mRadius)
                .append('-').append(mColor);
        return sb.toString();
    }
}

It is also very simple to use:

Glide.with(MainActivity.this).load(imageFile.getUrl())
         .transform(new BlurTransformation(MainActivity.this, 100))
         .crossFade()
         .into(mBackgroundImageView);
Gist: https://gist.github.com/ligboy/eee784aa57f40a615179

Topics: Android Gradle Java github