9

So I'm trying to blur an image as fast as possible(instant feel like), as the activity needs to be updated as I press the Blur button.

The problem I am having is that, I cannot find a Blur that works quick enough... Note: The blur, preferably a Gaussian blur, doesn't need to be the best quality at all..

I tried out the following, but it takes a few seconds, is there anyway this code could be made to run quicker in sacrifice of quality ? Or are there any other alternatives? I would look into GPU stuff, but this blur is really just an effect related to the UI and only happens when I press open a transparent activity sized as a small box...

Any Ideas?

static Bitmap fastblur(Bitmap sentBitmap, int radius, int fromX, int fromY,
    int width, int height) {

// 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 = width;
int h = height;

int[] pix = new int[w * h];

bitmap.getPixels(pix, 0, w, fromX, fromY, 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;

int originRadius = radius;
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;
}

radius = originRadius;

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++) {
        pix[yi] = 0xff000000 | (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, fromX, fromY, w, h);

return (bitmap);

}

rennoDeniro
  • 920
  • 2
  • 8
  • 19
  • https://futurestud.io/tutorials/how-to-blur-images-efficiently-with-androids-renderscript This worked for me. – AndroidDev Apr 21 '20 at 11:27

2 Answers2

10

Try to scale down the image 2, 4, 8, ... times and then scale it up again. That is fast. Otherwise implement it in renderscript.

If you want more than the scaling you can look at this code snippet in renderscript. It does the same kind of bluring as given in another answer. The same algorithm can be implemented in Java and is an optimization of the other answer. This code blurs one line. To blur a bitmap you should invoke this for all lines and then the same for all columns (you need to reimplement it to handle columns). To get a quick blur just do this once. If you want a better looking blur do it several times. I usually only do it twice.

The reason for doing one line is that I tried to parallelize the algorithm, that gave some improvement and is really simple in renderscript. I invoked the below code for all lines in parallel, and then the same for all columns.

int W = 8;
uchar4 *in;
uchar4 *out;    

int N;
float invN;    

uint32_t nx;
uint32_t ny;    

void init_calc() {
  N = 2*W+1;
  invN = 1.0f/N;    

  nx = rsAllocationGetDimX(rsGetAllocation(in));
  ny = rsAllocationGetDimY(rsGetAllocation(in));
}    

void root(const ushort *v_in) {
  float4 sum = 0;    

  uchar4 *head = in + *v_in * nx;
  uchar4 *tail = head;
  uchar4 *p = out + *v_in * nx;    

  uchar4 *hpw = head + W;
  uchar4 *hpn = head + N;
  uchar4 *hpx = head + nx;
  uchar4 *hpxmw = head + nx - W - 1;    

  while (head < hpw) {
      sum += rsUnpackColor8888(*head++);
  }    

  while (head < hpn) {
      sum += rsUnpackColor8888(*head++);
      *p++ = rsPackColorTo8888(sum*invN);
  }    

  while (head < hpx) {
    sum += rsUnpackColor8888(*head++);
    sum -= rsUnpackColor8888(*tail++);
    *p++ = rsPackColorTo8888(sum*invN);
  }    

  while (tail < hpxmw) {
      sum -= rsUnpackColor8888(*tail++);
      *p++ = rsPackColorTo8888(sum*invN);
  }
}

Here is for the vertical bluring:

int W = 8;
uchar4 *in;
uchar4 *out;    

int N;
float invN;    

uint32_t nx;
uint32_t ny;    

void init_calc() {
  N = 2*W+1;
  invN = 1.0f/N;    

  nx = rsAllocationGetDimX(rsGetAllocation(in));
  ny = rsAllocationGetDimY(rsGetAllocation(in));
}    

void root(const ushort *v_in) {
  float4 sum = 0;    

  uchar4 *head = in + *v_in;
  uchar4 *tail = head;
  uchar4 *hpw = head + nx*W;
  uchar4 *hpn = head + nx*N;
  uchar4 *hpy = head + nx*ny;
  uchar4 *hpymw = head + nx*(ny-W-1);    

  uchar4 *p = out + *v_in;    

  while (head < hpw) {
      sum += rsUnpackColor8888(*head);
      head += nx;
  }    

  while (head < hpn) {
      sum += rsUnpackColor8888(*head);
      *p = rsPackColorTo8888(sum*invN);
      head += nx;
      p += nx;
  }    

  while (head < hpy) {
      sum += rsUnpackColor8888(*head);
      sum -= rsUnpackColor8888(*tail);
      *p = rsPackColorTo8888(sum*invN);
      head += nx;
      tail += nx;
      p += nx;
  }    

  while (tail < hpymw) {
      sum -= rsUnpackColor8888(*tail);
      *p = rsPackColorTo8888(sum*invN);
      tail += nx;
      p += nx;
  }
}

And here is the Java code that calls into the rs code:

private RenderScript mRS;
private ScriptC_horzblur mHorizontalScript;
private ScriptC_vertblur mVerticalScript;
private ScriptC_blur mBlurScript;

private Allocation alloc1;
private Allocation alloc2;

private void hblur(int radius, Allocation index, Allocation in, Allocation out) {
    mHorizontalScript.set_W(radius);
    mHorizontalScript.bind_in(in);
    mHorizontalScript.bind_out(out);
    mHorizontalScript.invoke_init_calc();
    mHorizontalScript.forEach_root(index);
}

private void vblur(int radius, Allocation index, Allocation in, Allocation out) {
    mHorizontalScript.set_W(radius);
    mVerticalScript.bind_in(in);
    mVerticalScript.bind_out(out);
    mVerticalScript.invoke_init_calc();
    mVerticalScript.forEach_root(index);
}

Bitmap blur(Bitmap org, int radius) {
    Bitmap out = Bitmap.createBitmap(org.getWidth(), org.getHeight(), org.getConfig());

    blur(org, out, radius);

    return out;
}

private Allocation createIndex(int size) {
    Element element = Element.U16(mRS);
    Allocation allocation = Allocation.createSized(mRS, element, size);
    short[] rows = new short[size];
    for (int i = 0; i < rows.length; i++) rows[i] = (short)i;
    allocation.copyFrom(rows);

    return allocation;
}

private void blur(Bitmap src, Bitmap dst, int r) {
    Allocation alloc1 = Allocation.createFromBitmap(mRS, src);
    Allocation alloc2 = Allocation.createTyped(mRS, alloc1.getType());

    Allocation hIndexAllocation = createIndex(alloc1.getType().getY());
    Allocation vIndexAllocation = createIndex(alloc1.getType().getX());

    // Iteration 1
    hblur(r, hIndexAllocation, alloc1, alloc2);
    vblur(r, vIndexAllocation, alloc2, alloc1);
    // Iteration 2
    hblur(r, hIndexAllocation, alloc1, alloc2);
    vblur(r, vIndexAllocation, alloc2, alloc1);
    // Add more iterations if you like or simply make a loop
    alloc1.copyTo(dst);
}
Tobias Ritzau
  • 3,327
  • 2
  • 18
  • 29
  • how do I go about coding that ? – rennoDeniro Nov 18 '12 at 00:13
  • look at this: http://developer.android.com/reference/android/graphics/Bitmap.html#createScaledBitmap(android.graphics.Bitmap, int, int, boolean) – Tobias Ritzau Nov 18 '12 at 11:41
  • this looks interesting, anywhere i can read to go ahead an implement this? im new to render script and quite basic in android – rennoDeniro Nov 18 '12 at 20:55
  • I'm having problems understanding how to implement the ScriptC_... part, i really dont get how that works? And searching around hasnt given much help either :s – rennoDeniro Nov 18 '12 at 22:21
  • 1
    Those classes are generated. Put the *.rs files with your java files. Look at this for more info: http://developer.android.com/guide/topics/graphics/renderscript/graphics.html Do you use eclipse or some other IDE? – Tobias Ritzau Nov 18 '12 at 23:33
  • Hey, Ive got it compiling an im at this stage, doing: mScript.bind_in(mIN); mScript.bind_out(mOu); mScript.invoke_init_calc(); mScript.forEach_root(); mOu.copyTo(viewBm); – rennoDeniro Nov 19 '12 at 23:23
  • But on the ".forEach" i get a type mismatch u16 :S ? Any ideas what i am doing wrong? – rennoDeniro Nov 19 '12 at 23:24
  • I must confess that this was a long time ago, and I just played around with it. I hope the code works. I will update the answer with the calls from Java. The U16 array is an index into the bitmap that decides how to split it into pieces. – Tobias Ritzau Nov 19 '12 at 23:45
  • Thank you for your help so far. You've missed out what types the following are and where I need to declare them: mHorizontalScript, mBitmapIn, alloc1,alloc2,r,dur,alpha. What types and where are they instantiated? – rennoDeniro Nov 20 '12 at 01:23
  • It is basically all there now. – Tobias Ritzau Nov 20 '12 at 10:44
  • Hello, thank you for helping me understand renderscript's basics, But the current blur method doesn't seem to be getting any decent desired gaussian blur. Also copyToSrc causes crashes when trying to blur again. – rennoDeniro Nov 20 '12 at 18:12
  • I cleaned the code, most of all I removed the test code I had that started out with a really small blur radius and then increased it every time `blur()` was called. Now the radius is a parameter instead. Take a look at it and update it. I also added comments to show how you should increase the number of iterations. I must say that I'm impressed that you got it running :) – Tobias Ritzau Nov 20 '12 at 21:22
  • Just checked it out, looks a little better indeed, thanks for all your help! – rennoDeniro Nov 20 '12 at 22:32
  • I think you can take it the reference to mBlurScript. – StackOverflowed Aug 17 '13 at 16:40
  • Trying to get this sample working. Everything looks okay except for the forEach_root(index) calls on each of the scripts. The compiler complains that it can't find those methods... Any ideas? – Eagle11 Nov 20 '14 at 20:24
  • @Eagle11 did you copy the files as is? Mostly, are the functions still named root? Have you made sure that you can call any renderscript code from Java? – Tobias Ritzau Dec 02 '14 at 17:32
8

Gaussian blur is expensive to do accurately. A much faster approximation can be done by just iteratively averaging the pixels. It's still expensive to blur the image a lot but you can redraw between each iteration to at least give instant feedback and a nice animation of the image blurring.

static void blurfast(Bitmap bmp, int radius) {
  int w = bmp.getWidth();
  int h = bmp.getHeight();
  int[] pix = new int[w * h];
  bmp.getPixels(pix, 0, w, 0, 0, w, h);

  for(int r = radius; r >= 1; r /= 2) {
    for(int i = r; i < h - r; i++) {
      for(int j = r; j < w - r; j++) {
        int tl = pix[(i - r) * w + j - r];
        int tr = pix[(i - r) * w + j + r];
        int tc = pix[(i - r) * w + j];
        int bl = pix[(i + r) * w + j - r];
        int br = pix[(i + r) * w + j + r];
        int bc = pix[(i + r) * w + j];
        int cl = pix[i * w + j - r];
        int cr = pix[i * w + j + r];

        pix[(i * w) + j] = 0xFF000000 |
            (((tl & 0xFF) + (tr & 0xFF) + (tc & 0xFF) + (bl & 0xFF) + (br & 0xFF) + (bc & 0xFF) + (cl & 0xFF) + (cr & 0xFF)) >> 3) & 0xFF |
            (((tl & 0xFF00) + (tr & 0xFF00) + (tc & 0xFF00) + (bl & 0xFF00) + (br & 0xFF00) + (bc & 0xFF00) + (cl & 0xFF00) + (cr & 0xFF00)) >> 3) & 0xFF00 |
            (((tl & 0xFF0000) + (tr & 0xFF0000) + (tc & 0xFF0000) + (bl & 0xFF0000) + (br & 0xFF0000) + (bc & 0xFF0000) + (cl & 0xFF0000) + (cr & 0xFF0000)) >> 3) & 0xFF0000;
      }
    }
  }
  bmp.setPixels(pix, 0, w, 0, 0, w, h);
}
gordy
  • 9,360
  • 1
  • 31
  • 43
  • This code recalculates the same sums several times. If you want this kind of bluring see my answer. – Tobias Ritzau Nov 18 '12 at 20:20
  • be sure to specify radius in powers of 2 - like 8, 16 or 32 – gordy Nov 18 '12 at 22:48
  • I have, 32 provides a better results but when I get the image, the blur only seems to apply to half of the image :s, how can I get it apply to the whole image? – rennoDeniro Nov 19 '12 at 19:33
  • @rennoDeniro there was a bug where the entire image was not processed. I just edited the answer and corrected the bug, let me know if the blur is fast and blurry enough – gordy Nov 20 '12 at 05:04
  • Hello, Unfortunately this blur isn't Gaussian enough and takes time :( – rennoDeniro Nov 20 '12 at 18:21