Transparent Blurry View Which Blurs Layout Underneath

Transparent blurry view which blurs layout underneath

This was on my mind for some time, and I just implemented it thanks to your question.

To be able to do this, we need to draw the layout that is beneath our blur layout into a bitmap. Than by using a blurring algorithm, we need to blur that bitmap and finally draw blurred bitmap as our blur layout's background.

Luckily android has cached drawing mechanism, so first part is easy. We can simply enable cached drawing for our beneath layout and use getDrawingCache() to acquire the bitmap from it.

Now we need a fast blurring algorithm. I used this https://stackoverflow.com/a/10028267/3133545

Here it is.

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.View;

import java.lang.ref.WeakReference;
import java.util.InputMismatchException;

/**
* A drawable that draws the target view as blurred using fast blur
* <p/>
* <p/>
* TODO:we might use setBounds() to draw only part a of the target view
* <p/>
* Created by 10uR on 24.5.2014.
*/
public class BlurDrawable extends Drawable {

private WeakReference<View> targetRef;
private Bitmap blurred;
private Paint paint;
private int radius;

public BlurDrawable(View target) {
this(target, 10);
}

public BlurDrawable(View target, int radius) {
this.targetRef = new WeakReference<View>(target);
setRadius(radius);
target.setDrawingCacheEnabled(true);
target.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_AUTO);
paint = new Paint();
paint.setAntiAlias(true);
paint.setFilterBitmap(true);
}

@Override
public void draw(Canvas canvas) {
if (blurred == null) {
View target = targetRef.get();
if (target != null) {
Bitmap bitmap = target.getDrawingCache(true);
if (bitmap == null) return;
blurred = fastBlur(bitmap, radius);
}
}
if (blurred != null && !blurred.isRecycled())
canvas.drawBitmap(blurred, 0, 0, paint);
}

/**
* Set the bluring radius that will be applied to target view's bitmap
*
* @param radius should be 0-100
*/
public void setRadius(int radius) {
if (radius < 0 || radius > 100)
throw new InputMismatchException("Radius must be 0 <= radius <= 100 !");
this.radius = radius;
if (blurred != null) {
blurred.recycle();
blurred = null;
}
invalidateSelf();
}

public int getRadius() {
return radius;
}

@Override
public void setAlpha(int alpha) {
}

@Override
public void setColorFilter(ColorFilter cf) {

}

@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}

/**
* from https://stackoverflow.com/a/10028267/3133545
* <p/>
* <p/>
* <p/>
* Stack Blur v1.0 from
* http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html
* <p/>
* 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
* <p/>
* 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.
* <p/>
* 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.
* <p/>
* If you are using this algorithm in your code please add
* the following line:
* <p/>
* Stack Blur Algorithm by Mario Klingemann <mario@quasimondo.com>
*/
private static Bitmap fastBlur(Bitmap sentBitmap, int radius) {

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;
}
}

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

return (bitmap);
}

}

Usage :

View beneathView = //the view that beneath blur view
View blurView= //blur View

BlurDrawable blurDrawable = new BlurDrawable(beneathView, radius);

blurView.setBackgroundDrawable(blurDrawable);

And how my test application looked like:

test application

I decided not to use this tho, because it is too hacky and not looking as cool as i thought it would be in first place.

How to set the blur level of a view background

Android doesn't provide this functionality for UI elements as iOS does. It's not part of the UI rendering engine and to obtain these kind of effects you need to use third-party libraries which will essentially take a snapshot of the drawing cache, reduce its size for a faster processing and then put the generated bitmap into a view.

Here are some example libraries:

  • BlurrView
  • Blurry

Please bear in mind that this will have a serious performance hit on mid-low end phones.

Create blurry transparent background effect

Now that the window flag is deprecated, you've got to blur yourself. I answered this elsewhere but here is how you can blur a view:

You can now use ScriptIntrinsicBlur from the RenderScript library to blur quickly. Here is how to access the RenderScript API. The following is a class I made to blur Views and Bitmaps:

import android.support.v8.renderscript.*;

public class BlurBuilder {
private static final float BITMAP_SCALE = 0.4f;
private static final float BLUR_RADIUS = 7.5f;

public static Bitmap blur(View v) {
return blur(v.getContext(), getScreenshot(v));
}

public static Bitmap blur(Context ctx, Bitmap image) {
int width = Math.round(image.getWidth() * BITMAP_SCALE);
int height = Math.round(image.getHeight() * BITMAP_SCALE);

Bitmap inputBitmap = Bitmap.createScaledBitmap(image, width, height, false);
Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap);

RenderScript rs = RenderScript.create(ctx);
ScriptIntrinsicBlur theIntrinsic = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
Allocation tmpIn = Allocation.createFromBitmap(rs, inputBitmap);
Allocation tmpOut = Allocation.createFromBitmap(rs, outputBitmap);
theIntrinsic.setRadius(BLUR_RADIUS);
theIntrinsic.setInput(tmpIn);
theIntrinsic.forEach(tmpOut);
tmpOut.copyTo(outputBitmap);

return outputBitmap;
}

private static Bitmap getScreenshot(View v) {
Bitmap b = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
v.draw(c);
return b;
}
}

To apply this to a fragment, add the following to onCreateView:

final Activity activity = getActivity();
final View content = activity.findViewById(android.R.id.content).getRootView();
if (content.getWidth() > 0) {
Bitmap image = BlurBuilder.blur(content);
window.setBackgroundDrawable(new BitmapDrawable(activity.getResources(), image));
} else {
content.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Bitmap image = BlurBuilder.blur(content);
window.setBackgroundDrawable(new BitmapDrawable(activity.getResources(), image));
}
});
}

NOTE: This solution requires min sdk to be API 17

EDIT: Renderscript is included into support v8 enabling this answer down to api 8. To enable it using gradle include these lines into your gradle file (from this answer) and use Renderscript from package android.support.v8.renderscript:

android {
...
defaultConfig {
...
renderscriptTargetApi *your target api*
renderscriptSupportModeEnabled true
}
...
}

JDialog with transparent background which blurs things underneath

After some testing it appears to be pretty tricky to get something like what you want to achieve. I ended up using a JLayeredPane which contains one layer for the blurred background and one for the actual content.

I am not entirely happy with the result but I didn't find a better solution now.
The problem is that Java does not provide a proper way to capture things behind the actual application which leads to the need of hiding the content before taking the screenshot. That also is the problem which causes the flickering you mentioned.
To avoid this I do only reaint the background if the application gets resized or moved. There should be put some more effort into this once really used, e.g. if the app gets minimized. But since this example cannot be resized ord minimized it's not really important right now.
The downside of this approach however is that it's completely ignoring whether or not the Background isn't static. If you use the application in front of a video for example, the current version does not change the background based on the videos current frame. This may be possible if you uncomment the Timer in my examples setVisible(...) but then we get the flickering back.

I'd suggest you to think about using JavaFx. Since CSS already provides a blur-functionality it may work for Fx as well. But since I didn't try that yet I cannot assure you it's working.

Long story short, here's the example. The blurring function is used from your link above.

import java.awt.AWTException;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.awt.image.ConvolveOp;
import java.awt.image.Kernel;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.SwingConstants;

@SuppressWarnings("serial")
public class BlurryFrame extends JFrame implements ActionListener, ComponentListener {

public static void main(String... sss) {

JLabel label1 = new JLabel("TestLabel 1");
label1.setSize(500, 100);
label1.setForeground(Color.RED);

JLabel label2 = new JLabel("TestLabel 2");
label2.setHorizontalAlignment(SwingConstants.CENTER);
label2.setForeground(Color.BLUE);

JPanel testPanel = new JPanel();
testPanel.setOpaque(false);
testPanel.setLayout(new BorderLayout());
testPanel.add(label1, BorderLayout.CENTER);
testPanel.add(label2, BorderLayout.SOUTH);

BlurryFrame frame = new BlurryFrame(800, 600, testPanel);
frame.setBackground(new Color(0, 0, 0, 100));
frame.setVisible(true);
}

private final BackgroundPanel backgroundPane;
private final JLayeredPane container;
private final JPanel containerPane;
private final Robot robot;

// This rectangle is going to be your screenshots bounds to keep the image only as big as necessary
private final Rectangle captureRect;

// This is going to be the blurred screenshot
private BufferedImage background;

public BlurryFrame() {
this(1280, 800);
}

public BlurryFrame(int width, int height) {
this(width, height, null);
}

public BlurryFrame(int width, int height, JComponent component) {

this.captureRect = new Rectangle();
try {
this.robot = new Robot();
} catch (AWTException e) {
throw new RuntimeException(e);
}

this.backgroundPane = new BackgroundPanel();
this.backgroundPane.setOpaque(false);
this.backgroundPane.setLayout(new BorderLayout());
this.backgroundPane.setBounds(0, 0, width, height);
this.backgroundPane.addComponentListener(this);

this.containerPane = new JPanel();
this.containerPane.setOpaque(false);
this.containerPane.setLayout(new BorderLayout());
this.containerPane.add(component, BorderLayout.CENTER);
this.containerPane.setBounds(0, 0, width, height);

this.setUndecorated(true);
this.setSize(width, height);
this.setLocationRelativeTo(null);

this.container = new JLayeredPane();
this.container.setOpaque(false);
this.container.setLayout(new BorderLayout());
this.container.add(this.backgroundPane, 1);
this.container.add(this.containerPane, 0);

this.getContentPane().add(this.container, BorderLayout.CENTER);
}

public void add(JComponent component) {
this.containerPane.add(component, BorderLayout.CENTER);
this.containerPane.repaint();
}

// This method does not really do much but ultimately triggers the screenshot by ending up in #componentHidden(...)
private void capture() {
this.containerPane.setVisible(false);
this.backgroundPane.setVisible(false);
this.repaint();
}

@Override
public void setVisible(boolean visible) {

super.setVisible(visible);
this.capture();

// XXX uncomment this if you want to see the flickering
// Timer timer = new Timer(60, this);
// timer.start();
}

@Override
public void setSize(int width, int height) {

super.setSize(width, height);

this.captureRect.setSize(width, height);
this.backgroundPane.setBounds(0, 0, width, height);
this.containerPane.setBounds(0, 0, width, height);

if (this.isVisible())
this.capture();
}

@Override
public void setSize(Dimension dimension) {

super.setSize(dimension);

this.captureRect.setSize(dimension);
this.backgroundPane.setBounds(0, 0, dimension.width, dimension.height);
this.containerPane.setBounds(0, 0, dimension.width, dimension.height);

if (this.isVisible())
this.capture();
}

@Override
public void setPreferredSize(Dimension dimension) {

super.setPreferredSize(dimension);

this.captureRect.setSize(dimension);
this.backgroundPane.setBounds(0, 0, dimension.width, dimension.height);
this.containerPane.setBounds(0, 0, dimension.width, dimension.height);

if (this.isVisible())
this.capture();
}

@Override
public void actionPerformed(ActionEvent e) {
this.capture();
}

int i = 0;

// This is where the magic happens. The capturing needs to be done here in order to assure the applications content has really been hidden
@Override
public void componentHidden(ComponentEvent e) {

int x = this.getLocationOnScreen().x;
int y = this.getLocationOnScreen().y;

this.captureRect.setLocation(x, y);

this.background = this.robot.createScreenCapture(this.captureRect);

//XXX uncomment this if you want to see what gets captured
// if (this.i < 1) {
// try {
// ImageIO.write(this.background, "png", new File(System.getProperty("user.home") + "\\test.png"));
// } catch (IOException ex) {
// ex.printStackTrace();
// }
// this.i++;
// }

this.containerPane.setVisible(true);
this.backgroundPane.setVisible(true);
this.repaint();
}

@Override
public void componentMoved(ComponentEvent e) {
this.capture();
}

@Override
public void componentResized(ComponentEvent e) {
this.capture();
}

private class BackgroundPanel extends JPanel {

private final float[] matrix = {
0.111f, 0.111f, 0.111f,
0.111f, 0.111f, 0.111f,
0.111f, 0.111f, 0.111f,
};

@Override
public void paintComponent(Graphics g) {

super.paintComponent(g);

if (BlurryFrame.this.background != null) {

BufferedImageOp op = new ConvolveOp(new Kernel(3, 3, this.matrix));
BufferedImage blurryBack = op.filter(BlurryFrame.this.background, null);

g.drawImage(blurryBack, 0, 0, null);
}
}
}

@Override
public void componentShown(ComponentEvent e) {
}
}


Related Topics



Leave a reply



Submit