3

I have picasso in my android app, so I can retrieve the image URL from firebase storage to my recyclerview and show it up, now, i want to add a placeholder so users can see when the image is loading, but, I want to do it the way every app does it, first bluring the image like a low size one (thumbnail) , and then show the original one (NOT-BLURED).

I have been searching into Stack Overflow, I found solutions that are great, like, making 2 picasso statements, one for the thumbnail and after that, onsucceed method brings me the original image, but I have a problem, I need 2 images , the thumbnail of the original one to be shown at first and then show the original full image.

So, how can I retrieve 2 images if i just upload one to the server?

Here is a little thing I found in SO:

Transformation blurTransformation = new Transformation() {
    @Override
    public Bitmap transform(Bitmap source) {
        Bitmap blurred = Blur.fastblur(LiveImageView.this.context, source, 10);
        source.recycle();
        return blurred;
    }
 
    @Override
    public String key() {
        return "blur()";
    }
};
 
Picasso.with(context)
    .load(thumbUrl) // thumbnail url goes here
    .placeholder(R.drawable.placeholder)
    .resize(imageViewWidth, imageViewHeight)
    .transform(blurTransformation)
    .into(imageView, new Callback() {
        @Override
        public void onSuccess() {
            Picasso.with(context)
                    .load(url) // image url goes here
                    .resize(imageViewWidth, imageViewHeight)
                    .placeholder(imageView.getDrawable())
                    .into(imageView);
        }
 
        @Override
        public void onError() {
        }
    });
vvvvv
  • 25,404
  • 19
  • 49
  • 81
  • Have you tried adding the placeholder image to your ImageView via XML (add placeholder image into drawable folder) and load the original Image from Picasso? or if you want to use Picasso for both images, manually upload the placeholder image to Firebase storage and get the download URL from the Firebase console. – RamithDR Sep 12 '16 at 04:18
  • @RamithDR yes, i can place a standard image in my drawable , but i dont want to ahow an standard image, i wanna shiw the image i upload to the server as a blured thumbnail before it fully load , take a look at this https://abdelhadymu.files.wordpress.com/2015/02/whatsapp.png?w=634 –  Sep 12 '16 at 13:48
  • Remember that the pica uploaded to the aerver are randoms and not the same ones everytime , thats why i cant place an standar placeholder –  Sep 12 '16 at 13:56
  • Check out my answer below – RamithDR Sep 12 '16 at 15:23

1 Answers1

3

Alright I figured it out, Gotta say that was the quite brain teaser!

Step 1

First of all you need to create a Blur Java class :

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Build;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RenderScript;
import android.renderscript.ScriptIntrinsicBlur;

/**
 * Created by RamithRD on 9/12/2016.
 */
public class Blur {

    @SuppressLint("NewApi")
    public static Bitmap fastblur(Context context, Bitmap sentBitmap, int radius) {

        if (Build.VERSION.SDK_INT > 16) {
            Bitmap bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);

            final RenderScript rs = RenderScript.create(context);
            final Allocation input = Allocation.createFromBitmap(rs, sentBitmap, Allocation.MipmapControl.MIPMAP_NONE,
                    Allocation.USAGE_SCRIPT);
            final Allocation output = Allocation.createTyped(rs, input.getType());
            final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
            script.setRadius(radius /* e.g. 3.f */);
            script.setInput(input);
            script.forEach(output);
            output.copyTo(bitmap);
            return bitmap;
        }

        // Stack Blur v1.0 from
        // http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html
        //
        // Java Author: Mario Klingemann <mario at quasimondo.com>
        // http://incubator.quasimondo.com
        // created Feburary 29, 2004
        // Android port : Yahel Bouaziz <yahel at kayenko.com>
        // http://www.kayenko.com
        // ported april 5th, 2012

        // This is a compromise between Gaussian Blur and Box blur
        // It creates much better looking blurs than Box Blur, but is
        // 7x faster than my Gaussian Blur implementation.
        //
        // I called it Stack Blur because this describes best how this
        // filter works internally: it creates a kind of moving stack
        // of colors whilst scanning through the image. Thereby it
        // just has to add one new block of color to the right side
        // of the stack and remove the leftmost color. The remaining
        // colors on the topmost layer of the stack are either added on
        // or reduced by one, depending on if they are on the right or
        // on the left side of the stack.
        //
        // If you are using this algorithm in your code please add
        // the following line:
        //
        // 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];
        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;
            }
        }

        bitmap.setPixels(pix, 0, w, 0, 0, w, h);
        return (bitmap);
    }
}

Blur class credits goes to : MichaelEvans - EtsyBlurExample

Step 2

Now create the blur Transformation :

Transformation blurTransformation = new Transformation() {
            @Override
            public Bitmap transform(Bitmap source) {
                Bitmap blurred = Blur.fastblur(MainActivity.this, source, 10);
                source.recycle();
                return blurred;
            }

            @Override
            public String key() {
                return "blur()";
            }
        };

Step 3

Loading the Image with Picasso :

Picasso.with(context)
    .load(url) // thumnail url goes here
    .transform(blurTransformation)
    .into(imageView, new Callback() {
        @Override
        public void onSuccess() {
            Picasso.with(context)
                    .load(url) // original image url goes here
                    .placeholder(imageView.getDrawable())
                    .into(imageView);
        }

        @Override
        public void onError() {
        }
    });

What happens is when I start the app, it first shows the original Image but blurred and then show shows the original image.

Tips I would do to make this even more efficient is, when uploading the Image to Firebase Storage you can shrink the size of Image and store it with the original Image (Can be easily done using the Firebase database, just store thumbnail and original image's download Urls) and when loading the image via Picasso you can load the thumbnail first and then the original image.

For testing purposes I just used the same Url.

For re-scaling images you'll find some good answers here

Here's a GIF from my sample app :

enter image description here
(source: giphy.com)

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
RamithDR
  • 2,103
  • 2
  • 25
  • 34
  • accuarate answer, really well made, thanks so much ! One more question @RamithDR , lets say i upload 2 pics instead of one in my storage like you said, it will be more easy to just bring the thumbnail url to the .thumbnail(URL) and then the original pic than having the same pic blured and then shown, the reason im doing this is because i just want to load really quick the preview before the user can see the final result, just for speed up purposes, i dont want them to wait untill the image is loaded , because they are seeing just a blank space and then after 2 sec the image pop up. –  Sep 12 '16 at 18:25
  • 1
    @ArmandoBarreda Yes, as I said if you're going to upload two images for thumbnail and original pic, make sure you scale down the image size of the thumbnail before uploading it, then the blurred thumbnail will be shown to the user very faster and it doesn't matter if the image quality is low because it's blurred ;) – RamithDR Sep 12 '16 at 18:33
  • 1
    Take a look at this [link] (http://stackoverflow.com/questions/25629230/get-image-urls-from-json-into-an-array) if you go to the IMAGE child in the json you can see it stores 3 diferent images, low_resolution , thumbnail and full_resolution with 3 diferents width and height, and then the app just call the thumbnail before the full image, thats what im pointing at :) @RamithDR –  Sep 12 '16 at 18:34