Android ImageView blur animation

2019-02-05 06:04发布

I'm looking to add a blur animation to an ImageView, but with a set duration. So, for example I want an image to blur out over time.

I already have the method to blur the image, but what I need is to make it go from blur to none blur over, say, 2 seconds.

Can anybody help me out?

EDIT: This is the method I currently have to blur the image.

public Bitmap blur(Bitmap sentBitmap, int radius) {

    // Stack Blur Algorithm by Mario Klingemann <mario@quasimondo.com>

    Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);

    if (radius < 1) {
        return (null);
    }

    int w = bitmap.getWidth();
    int h = bitmap.getHeight();

    int[] pix = new int[w * h];
    Log.e("pix", w + " " + h + " " + pix.length);
    bitmap.getPixels(pix, 0, w, 0, 0, w, h);

    int wm = w - 1;
    int hm = h - 1;
    int wh = w * h;
    int div = radius + radius + 1;

    int r[] = new int[wh];
    int g[] = new int[wh];
    int b[] = new int[wh];
    int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
    int vmin[] = new int[Math.max(w, h)];

    int divsum = (div + 1) >> 1;
    divsum *= divsum;
    int dv[] = new int[256 * divsum];
    for (i = 0; i < 256 * divsum; i++) {
        dv[i] = (i / divsum);
    }

    yw = yi = 0;

    int[][] stack = new int[div][3];
    int stackpointer;
    int stackstart;
    int[] sir;
    int rbs;
    int r1 = radius + 1;
    int routsum, goutsum, boutsum;
    int rinsum, ginsum, binsum;

    for (y = 0; y < h; y++) {
        rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
        for (i = -radius; i <= radius; i++) {
            p = pix[yi + Math.min(wm, Math.max(i, 0))];
            sir = stack[i + radius];
            sir[0] = (p & 0xff0000) >> 16;
            sir[1] = (p & 0x00ff00) >> 8;
            sir[2] = (p & 0x0000ff);
            rbs = r1 - Math.abs(i);
            rsum += sir[0] * rbs;
            gsum += sir[1] * rbs;
            bsum += sir[2] * rbs;
            if (i > 0) {
                rinsum += sir[0];
                ginsum += sir[1];
                binsum += sir[2];
            } else {
                routsum += sir[0];
                goutsum += sir[1];
                boutsum += sir[2];
            }
        }
        stackpointer = radius;

        for (x = 0; x < w; x++) {

            r[yi] = dv[rsum];
            g[yi] = dv[gsum];
            b[yi] = dv[bsum];

            rsum -= routsum;
            gsum -= goutsum;
            bsum -= boutsum;

            stackstart = stackpointer - radius + div;
            sir = stack[stackstart % div];

            routsum -= sir[0];
            goutsum -= sir[1];
            boutsum -= sir[2];

            if (y == 0) {
                vmin[x] = Math.min(x + radius + 1, wm);
            }
            p = pix[yw + vmin[x]];

            sir[0] = (p & 0xff0000) >> 16;
            sir[1] = (p & 0x00ff00) >> 8;
            sir[2] = (p & 0x0000ff);

            rinsum += sir[0];
            ginsum += sir[1];
            binsum += sir[2];

            rsum += rinsum;
            gsum += ginsum;
            bsum += binsum;

            stackpointer = (stackpointer + 1) % div;
            sir = stack[(stackpointer) % div];

            routsum += sir[0];
            goutsum += sir[1];
            boutsum += sir[2];

            rinsum -= sir[0];
            ginsum -= sir[1];
            binsum -= sir[2];

            yi++;
        }
        yw += w;
    }
    for (x = 0; x < w; x++) {
        rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
        yp = -radius * w;
        for (i = -radius; i <= radius; i++) {
            yi = Math.max(0, yp) + x;

            sir = stack[i + radius];

            sir[0] = r[yi];
            sir[1] = g[yi];
            sir[2] = b[yi];

            rbs = r1 - Math.abs(i);

            rsum += r[yi] * rbs;
            gsum += g[yi] * rbs;
            bsum += b[yi] * rbs;

            if (i > 0) {
                rinsum += sir[0];
                ginsum += sir[1];
                binsum += sir[2];
            } else {
                routsum += sir[0];
                goutsum += sir[1];
                boutsum += sir[2];
            }

            if (i < hm) {
                yp += w;
            }
        }
        yi = x;
        stackpointer = radius;
        for (y = 0; y < h; y++) {
            // Preserve alpha channel: ( 0xff000000 & pix[yi] )
            pix[yi] = ( 0xff000000 & pix[yi] ) | ( dv[rsum] << 16 ) | ( dv[gsum] << 8 ) | dv[bsum];

            rsum -= routsum;
            gsum -= goutsum;
            bsum -= boutsum;

            stackstart = stackpointer - radius + div;
            sir = stack[stackstart % div];

            routsum -= sir[0];
            goutsum -= sir[1];
            boutsum -= sir[2];

            if (x == 0) {
                vmin[y] = Math.min(y + r1, hm) * w;
            }
            p = x + vmin[y];

            sir[0] = r[p];
            sir[1] = g[p];
            sir[2] = b[p];

            rinsum += sir[0];
            ginsum += sir[1];
            binsum += sir[2];

            rsum += rinsum;
            gsum += ginsum;
            bsum += binsum;

            stackpointer = (stackpointer + 1) % div;
            sir = stack[stackpointer];

            routsum += sir[0];
            goutsum += sir[1];
            boutsum += sir[2];

            rinsum -= sir[0];
            ginsum -= sir[1];
            binsum -= sir[2];

            yi += w;
        }
    }

    Log.e("pix", w + " " + h + " " + pix.length);
    bitmap.setPixels(pix, 0, w, 0, 0, w, h);

    return (bitmap);
}

5条回答
祖国的老花朵
2楼-- · 2019-02-05 06:13

Blur effects are always difficult on Android. Essentially you have to decide between looks and performance. The better the blur looks the longer it takes, and if the blurring itself is not instantaneous then you cannot really animate the blur.

You original blurring algorithm produces really good results, but because of that it is also very slow making a blur animation impossible. Just to demonstrate what it would take to effectively blur this image I have created a simple blur animation by scaling the bitmap down:

public class BlurAnimation extends Animation {

    private final ImageView imageView;
    private final Bitmap bitmap;
    private final float startValue;
    private final float stopValue;
    private final float difValue;

    private BlurAnimation(ImageView imageView, Bitmap bitmap, int startValue, int stopValue) {
        this.imageView = imageView;
        this.bitmap = bitmap;
        this.startValue = startValue;
        this.stopValue = stopValue;
        this.difValue = stopValue - startValue;
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        super.applyTransformation(interpolatedTime, t);

        int current = (int)(this.difValue * interpolatedTime + this.startValue + 0.5f);
        Bitmap blurred = quickBlur(this.bitmap, current);
        this.imageView.setImageBitmap(blurred);
    }

    public Bitmap quickBlur(Bitmap bitmap, int factor) {
        if(factor <= 0) {
            return Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
        }
        return Bitmap.createScaledBitmap(bitmap, bitmap.getWidth() / factor, bitmap.getHeight() / factor, true);
    }
}

This works quite well (even though there is still some lag), but the results cannot be compared with your blur algorithm, it just looks terrible:

enter image description here

So you see, it is very difficult to combine performance and good looks when it comes to blurring an image, but there are some options first and foremost RenderScript. RenderScript is very fast and has a built in gaussian blur filter. I have never used it, but from what I hear it might be the solution to your problem.

You can also try to load already scaled down versions of an image, this would produce the same effect as in the gif above, but would be even faster. The drawback is that using this in an Animation is again problematic, but if you just need a blurred image and you don't really care about quality then you should go for this option.

You can find more information about RenderScript and other fast blur options in this answer

查看更多
Viruses.
3楼-- · 2019-02-05 06:13

I won't prolong my answer too much with code because honestly I don't have it, but I'll try to point out the key things you must consider and a few useful links.

first, the approach:

  • blur as fast as possible
  • cache the blurred result
  • cross-fade

blur as fast as possible:

your algorithm looks really nice and I believe it does give a good effect, but the reality is that it's running in Java VM in a single thread. You will for absolutely sure have much better performance using RenderScript. That's because RenderScript auto-scales the rendering process to multi-processor and to the GPU.

the base code for it is below, taken directly from android-developers.blogspot.com:

RenderScript rs = RenderScript.create(theActivity);
ScriptIntrinsicBlur theIntrinsic = ScriptIntrinsicBlur.create(mRS, Element.U8_4(rs));;
Allocation tmpIn = Allocation.createFromBitmap(rs, inputBitmap);
Allocation tmpOut = Allocation.createFromBitmap(rs, outputBitmap);
theIntrinsic.setRadius(25.f);
theIntrinsic.setInput(tmpIn);
theIntrinsic.forEach(tmpOut);
tmpOut.copyTo(outputBitmap);

cache the blurred results:

That's because Blur takes time. There will also be a lot of memory implications on that and you must watch it carefully. You certainly won't be able to do it very well in a scrolling list that is recycling views and stuff. A tip maybe is to use Picasso library (LINK) for that and also handle your threading. Something like this:

Picasso
   .with(context)
   .load(/* asset/res/file/url */)
   .transform(new BlurTransformation(value))
   .into(target);

// The transform method is automatically called by Picasso in a background thread
public class BlurTransformation implements com.squareup.picasso.Transformation {
    private final float radius;

    // radius is corner radii in dp
    public BlurTransformation(final float radius) {
        this.radius = radius;
    }

    @Override
    public Bitmap transform(final Bitmap source) {
       // do the transformation and return it here
    }

    @Override
    public String key() {
        return "blur"+Float.toString(radius);
    }
}

// you can also use this to get it instantly
Picasso... all the code... .get();

cross-fade:

here is where you'll balance between performance and great looks. The reality is that a real animation is impossible without actually render every frame for every step, but with a cross-fade between a few key-frames you can achieve great results.

How many key-frames you'll use will depend on the performance you want, the amount of free memory on the device, etc.

If you're fine with just 2 key-frames (1 not blurred and 1 totally blurred) you can probably apply a TransitionDrawable to it. If you want a finer looking effect, you'll have to create a series of cross-fade transitions or probably create your own custom drawable.

for just 2 key-frames you can see an example on the Yahoo Weather app, if you would like to see an example with a few key-frames you want to check Muzei live wallpaper.

very useful links*

On the following link you'll see a nice discussion by Muzei creator (Roman Nurik, which is also one of the engineers at Google working for the Android) on how he achieved the key-frames, why that's the only way to do it and why, although more complex code, it creates at the end a much prettier result: (short version) https://plus.google.com/+RomanNurik/posts/2sTQ1X2Cb2Z (complete version) https://medium.com/@romannurik/serendipitous-ideas-3a1721a6f716

This link is the source code for Muzei live-wallpaper where you can check how he calculates the key-frames and animates the wallpaper: https://github.com/romannurik/muzei

Good luck and happy coding!

查看更多
女痞
4楼-- · 2019-02-05 06:15

I'm a little bit late to the party, but if I unstderstand you correctly you want to "fade in the blur" so it increases cnstantly?

You can achive this effect by stacking two ImageViews on top of each other. The lower one shows the untouched image, the upper one a blured one which is pre-rendered. The upper one must be invisible (alpha=0f). Next you fade in the upper ImageView and fade out the lower ImageView. This will given you the desired effect. You can customize the effect by playing with the timings and transperancies.

Good luck :)

查看更多
5楼-- · 2019-02-05 06:26

The blurring itself takes a lot of time, even on new devices. Animating the blur would be even worse. RenderScript wouldn't help much either. Your best bet is to cross-fade between a blurred and non-blurred images:

package com.example.simon.crossfadeblur;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;

public class MainActivity extends Activity {

    public Bitmap blur(Bitmap sentBitmap, int radius) {

        // Stack Blur Algorithm by Mario Klingemann <mario@quasimondo.com>

        Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);

        if (radius < 1) {
            return (null);
        }

        int w = bitmap.getWidth();
        int h = bitmap.getHeight();

        int[] pix = new int[w * h];
        Log.e("pix", w + " " + h + " " + pix.length);
        bitmap.getPixels(pix, 0, w, 0, 0, w, h);

        int wm = w - 1;
        int hm = h - 1;
        int wh = w * h;
        int div = radius + radius + 1;

        int r[] = new int[wh];
        int g[] = new int[wh];
        int b[] = new int[wh];
        int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
        int vmin[] = new int[Math.max(w, h)];

        int divsum = (div + 1) >> 1;
        divsum *= divsum;
        int dv[] = new int[256 * divsum];
        for (i = 0; i < 256 * divsum; i++) {
            dv[i] = (i / divsum);
        }

        yw = yi = 0;

        int[][] stack = new int[div][3];
        int stackpointer;
        int stackstart;
        int[] sir;
        int rbs;
        int r1 = radius + 1;
        int routsum, goutsum, boutsum;
        int rinsum, ginsum, binsum;

        for (y = 0; y < h; y++) {
            rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
            for (i = -radius; i <= radius; i++) {
                p = pix[yi + Math.min(wm, Math.max(i, 0))];
                sir = stack[i + radius];
                sir[0] = (p & 0xff0000) >> 16;
                sir[1] = (p & 0x00ff00) >> 8;
                sir[2] = (p & 0x0000ff);
                rbs = r1 - Math.abs(i);
                rsum += sir[0] * rbs;
                gsum += sir[1] * rbs;
                bsum += sir[2] * rbs;
                if (i > 0) {
                    rinsum += sir[0];
                    ginsum += sir[1];
                    binsum += sir[2];
                } else {
                    routsum += sir[0];
                    goutsum += sir[1];
                    boutsum += sir[2];
                }
            }
            stackpointer = radius;

            for (x = 0; x < w; x++) {

                r[yi] = dv[rsum];
                g[yi] = dv[gsum];
                b[yi] = dv[bsum];

                rsum -= routsum;
                gsum -= goutsum;
                bsum -= boutsum;

                stackstart = stackpointer - radius + div;
                sir = stack[stackstart % div];

                routsum -= sir[0];
                goutsum -= sir[1];
                boutsum -= sir[2];

                if (y == 0) {
                    vmin[x] = Math.min(x + radius + 1, wm);
                }
                p = pix[yw + vmin[x]];

                sir[0] = (p & 0xff0000) >> 16;
                sir[1] = (p & 0x00ff00) >> 8;
                sir[2] = (p & 0x0000ff);

                rinsum += sir[0];
                ginsum += sir[1];
                binsum += sir[2];

                rsum += rinsum;
                gsum += ginsum;
                bsum += binsum;

                stackpointer = (stackpointer + 1) % div;
                sir = stack[(stackpointer) % div];

                routsum += sir[0];
                goutsum += sir[1];
                boutsum += sir[2];

                rinsum -= sir[0];
                ginsum -= sir[1];
                binsum -= sir[2];

                yi++;
            }
            yw += w;
        }
        for (x = 0; x < w; x++) {
            rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
            yp = -radius * w;
            for (i = -radius; i <= radius; i++) {
                yi = Math.max(0, yp) + x;

                sir = stack[i + radius];

                sir[0] = r[yi];
                sir[1] = g[yi];
                sir[2] = b[yi];

                rbs = r1 - Math.abs(i);

                rsum += r[yi] * rbs;
                gsum += g[yi] * rbs;
                bsum += b[yi] * rbs;

                if (i > 0) {
                    rinsum += sir[0];
                    ginsum += sir[1];
                    binsum += sir[2];
                } else {
                    routsum += sir[0];
                    goutsum += sir[1];
                    boutsum += sir[2];
                }

                if (i < hm) {
                    yp += w;
                }
            }
            yi = x;
            stackpointer = radius;
            for (y = 0; y < h; y++) {
                // Preserve alpha channel: ( 0xff000000 & pix[yi] )
                pix[yi] = ( 0xff000000 & pix[yi] ) | ( dv[rsum] << 16 ) | ( dv[gsum] << 8 ) | dv[bsum];

                rsum -= routsum;
                gsum -= goutsum;
                bsum -= boutsum;

                stackstart = stackpointer - radius + div;
                sir = stack[stackstart % div];

                routsum -= sir[0];
                goutsum -= sir[1];
                boutsum -= sir[2];

                if (x == 0) {
                    vmin[y] = Math.min(y + r1, hm) * w;
                }
                p = x + vmin[y];

                sir[0] = r[p];
                sir[1] = g[p];
                sir[2] = b[p];

                rinsum += sir[0];
                ginsum += sir[1];
                binsum += sir[2];

                rsum += rinsum;
                gsum += ginsum;
                bsum += binsum;

                stackpointer = (stackpointer + 1) % div;
                sir = stack[stackpointer];

                routsum += sir[0];
                goutsum += sir[1];
                boutsum += sir[2];

                rinsum -= sir[0];
                ginsum -= sir[1];
                binsum -= sir[2];

                yi += w;
            }
        }

        Log.e("pix", w + " " + h + " " + pix.length);
        bitmap.setPixels(pix, 0, w, 0, 0, w, h);

        return (bitmap);
    }

    public class FadeView extends FrameLayout {
        private long mFadeDelay = 1000;
        private ImageView mFirst;
        private ImageView mSecond;
        private boolean mFirstShowing;

        public FadeView(Context context) {
            super(context);
            init(context);
        }

        public FadeView(Context context, AttributeSet attrs) {
            super(context, attrs);
            init(context);
        }

        public FadeView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            init(context);
        }

        private void init(Context c){
            mFirst = new ImageView(c);
            mSecond = new ImageView(c);

            mFirst.setAlpha(1.0f);
            mSecond.setAlpha(0.0f);

            mFirstShowing = true;

            addView(mFirst);
            addView(mSecond);
        }

        public void setFadeDelay(long fadeDelay) {
            mFadeDelay = fadeDelay;
        }

        public void ShowImage(Drawable d){
            if(mFirstShowing){
                mSecond.setImageDrawable(d);
                mSecond.animate().alpha(1.0f).setDuration(mFadeDelay);
                mFirst.animate().alpha(0.0f).setDuration(mFadeDelay);
            }else {
                mFirst.setImageDrawable(d);
                mSecond.animate().alpha(0.0f).setDuration(mFadeDelay);
                mFirst.animate().alpha(1.0f).setDuration(mFadeDelay);
            }

            mFirstShowing = !mFirstShowing;
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        final FadeView fw = new FadeView(this);
        setContentView(fw);

        fw.setOnClickListener(new View.OnClickListener() {

            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image);
            Bitmap blurredBitmap = blur(bitmap, 100);

            Drawable d1 = new BitmapDrawable(getResources(), blurredBitmap);
            Drawable d2 = getResources().getDrawable(R.drawable.image);
            boolean flag;

            @Override
            public void onClick(View view) {
                if(flag){
                    fw.ShowImage(d1);
                }else {
                    fw.ShowImage(d2);
                }
                flag = !flag;
            }
        });
    }

}

Note: The crossfade code was taken from: Source (animates on click)

Another way I can think of of doing this, is to use JQuery and animate the blur inside of a WebView.

查看更多
beautiful°
6楼-- · 2019-02-05 06:29
imageview = (ImageView) itemView.findViewById(R.id.imageView);

        BitmapDrawable drawable = (BitmapDrawable) imageview.getDrawable();
        Bitmap bitmap = drawable.getBitmap();
        Bitmap blurred = blurRenderScript(bitmap, 25);
        imageview.setImageBitmap(blurred);   

// these are blurring methods

@SuppressLint("NewApi")
    private Bitmap blurRenderScript(Bitmap smallBitmap, int radius) {

        try {
            smallBitmap = RGB565toARGB888(smallBitmap);
        } catch (Exception e) {
            e.printStackTrace();
        }


        Bitmap bitmap = Bitmap.createBitmap(
                smallBitmap.getWidth(), smallBitmap.getHeight(),
                Bitmap.Config.ARGB_8888);

        RenderScript renderScript = RenderScript.create(context);

        Allocation blurInput = Allocation.createFromBitmap(renderScript, smallBitmap);
        Allocation blurOutput = Allocation.createFromBitmap(renderScript, bitmap);

        ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(renderScript,
                Element.U8_4(renderScript));
        blur.setInput(blurInput);
        blur.setRadius(radius); // radius must be 0 < r <= 25
        blur.forEach(blurOutput);

        blurOutput.copyTo(bitmap);
        renderScript.destroy();

        return bitmap;

    }

    private Bitmap RGB565toARGB888(Bitmap img) throws Exception {
        int numPixels = img.getWidth() * img.getHeight();
        int[] pixels = new int[numPixels];

        //Get JPEG pixels.  Each int is the color values for one pixel.
        img.getPixels(pixels, 0, img.getWidth(), 0, 0, img.getWidth(), img.getHeight());

        //Create a Bitmap of the appropriate format.
        Bitmap result = Bitmap.createBitmap(img.getWidth(), img.getHeight(), Bitmap.Config.ARGB_8888);

        //Set RGB pixels.
        result.setPixels(pixels, 0, result.getWidth(), 0, 0, result.getWidth(), result.getHeight());
        return result;
    }
查看更多
登录 后发表回答