I am trying to make the bottom part of an Image blur for the view on top it like in the image.
I tried blurring it using Rendenscript but I am not able to blur only the part behind the view. :(
I have seen many libraries but almost all of them blur the entire image, but not a part of it.
Also, an important part is that I am using this inside a ViewPager and hence needs to be fast and dynamic something like this in IOS which redraws itself the moment image behind it changes.
Any help is appreciated. Thanks for stopping by!
My xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:orientation="vertical" >
<ImageView
android:id="@+id/image"
android:src="@drawable/broadstairs"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:layout_centerInParent="true"/>
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="200dp"
android:text="Hello World"
android:gravity="center"
android:layout_alignParentBottom="true"
android:textColor="@android:color/white"
android:textStyle="bold"
android:textSize="36sp"/>
</RelativeLayout>
My code:
BlurBuilder.java
public class BlurBuilder {
private static final float BITMAP_SCALE = 0.1f;
private static final float BLUR_RADIUS = 7.5f;
public static Bitmap blur(Context context, 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(context);
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;
}
@SuppressLint("NewApi")
public static void blur(final Context context, final Bitmap bitmap, final View view) {
new AsyncTask<Void, Void, Bitmap>() {
@Override
protected Bitmap doInBackground(Void... params) {
Paint paint = new Paint();
paint.setFilterBitmap(true);
Bitmap cropImage = Bitmap.createBitmap(bitmap, 0, bitmap.getHeight() - view.getHeight(), bitmap.getWidth(), view.getHeight());
return BlurBuilder.blur(context, cropImage);
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
if (bitmap != null) {
int sdk = android.os.Build.VERSION.SDK_INT;
if (sdk < android.os.Build.VERSION_CODES.JELLY_BEAN) {
view.setBackgroundDrawable(new BitmapDrawable(context.getResources(), bitmap));
} else {
view.setBackground(new BitmapDrawable(context.getResources(), bitmap));
}
}
}
}.execute();
}
}
In my MainActivity's onCreate I do:
BlurBuilder.blur(BitmapActivity.this, ((BitmapDrawable) mView.getDrawable()).getBitmap(), mDummyView);
Below is the result:
Add these two classes to your app,
1> BlurKit.Java
public class BlurKit {
private static BlurKit instance;
private RenderScript rs;
public static void init(Context context) {
if (instance != null) {
return;
}
instance = new BlurKit();
instance.rs = RenderScript.create(context);
}
public Bitmap blur(Bitmap src, int radius) {
final Allocation input = Allocation.createFromBitmap(rs, src);
final Allocation output = Allocation.createTyped(rs, input.getType());
final ScriptIntrinsicBlur script;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
script.setRadius(radius);
script.setInput(input);
script.forEach(output);
}
output.copyTo(src);
return src;
}
public Bitmap blur(View src, int radius) {
Bitmap bitmap = getBitmapForView(src, 1f);
return blur(bitmap, radius);
}
public Bitmap fastBlur(View src, int radius, float downscaleFactor) {
Bitmap bitmap = getBitmapForView(src, downscaleFactor);
return blur(bitmap, radius);
}
private Bitmap getBitmapForView(View src, float downscaleFactor) {
Bitmap bitmap = Bitmap.createBitmap(
(int) (src.getWidth() * downscaleFactor),
(int) (src.getHeight() * downscaleFactor),
Bitmap.Config.ARGB_4444
);
Canvas canvas = new Canvas(bitmap);
Matrix matrix = new Matrix();
matrix.preScale(downscaleFactor, downscaleFactor);
canvas.setMatrix(matrix);
src.draw(canvas);
return bitmap;
}
public static BlurKit getInstance() {
if (instance == null) {
throw new RuntimeException("BlurKit not initialized!");
}
return instance;
}
}
2> BlurLayout.Java
public class BlurLayout extends FrameLayout {
public static final float DEFAULT_DOWNSCALE_FACTOR = 0.12f;
public static final int DEFAULT_BLUR_RADIUS = 12;
public static final int DEFAULT_FPS = 60;
// Customizable attributes
/** Factor to scale the view bitmap with before blurring. */
private float mDownscaleFactor;
/** Blur radius passed directly to stackblur library. */
private int mBlurRadius;
/** Number of blur invalidations to do per second. */
private int mFPS;
// Calculated class dependencies
/** Reference to View for top-parent. For retrieval see {@link #getActivityView() getActivityView}. */
private WeakReference<View> mActivityView;
public BlurLayout(Context context) {
super(context, null);
}
public BlurLayout(Context context, AttributeSet attrs) {
super(context, attrs);
BlurKit.init(context);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.BlurLayout,
0, 0);
try {
mDownscaleFactor = a.getFloat(R.styleable.BlurLayout_downscaleFactor, DEFAULT_DOWNSCALE_FACTOR);
mBlurRadius = a.getInteger(R.styleable.BlurLayout_blurRadius, DEFAULT_BLUR_RADIUS);
mFPS = a.getInteger(R.styleable.BlurLayout_fps, DEFAULT_FPS);
} finally {
a.recycle();
}
if (mFPS > 0) {
Choreographer.getInstance().postFrameCallback(invalidationLoop);
}
}
/** Choreographer callback that re-draws the blur and schedules another callback. */
private Choreographer.FrameCallback invalidationLoop = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
invalidate();
Choreographer.getInstance().postFrameCallbackDelayed(this, 1000 / mFPS);
}
};
/**
* {@inheritDoc}
*/
@Override
public void invalidate() {
super.invalidate();
Bitmap bitmap = blur();
if (bitmap != null) {
setBackground(new BitmapDrawable(bitmap));
}
}
/**
* Recreates blur for content and sets it as the background.
*/
private Bitmap blur() {
if (getContext() == null) {
return null;
}
// Check the reference to the parent view.
// If not available, attempt to make it.
if (mActivityView == null || mActivityView.get() == null) {
mActivityView = new WeakReference<>(getActivityView());
if (mActivityView.get() == null) {
return null;
}
}
// Calculate the relative point to the parent view.
Point pointRelativeToActivityView = getPositionInScreen();
// Set alpha to 0 before creating the parent view bitmap.
// The blur view shouldn't be visible in the created bitmap.
setAlpha(0);
// Screen sizes for bound checks
int screenWidth = mActivityView.get().getWidth();
int screenHeight = mActivityView.get().getHeight();
// The final dimensions of the blurred bitmap.
int width = (int) (getWidth() * mDownscaleFactor);
int height = (int) (getHeight() * mDownscaleFactor);
// The X/Y position of where to crop the bitmap.
int x = (int) (pointRelativeToActivityView.x * mDownscaleFactor);
int y = (int) (pointRelativeToActivityView.y * mDownscaleFactor);
// Padding to add to crop pre-blur.
// Blurring straight to edges has side-effects so padding is added.
int xPadding = getWidth() / 8;
int yPadding = getHeight() / 8;
// Calculate padding independently for each side, checking edges.
int leftOffset = -xPadding;
leftOffset = x + leftOffset >= 0 ? leftOffset : 0;
int rightOffset = xPadding;
rightOffset = x + getWidth() + rightOffset <= screenWidth ? rightOffset : screenWidth - getWidth() - x;
int topOffset = -yPadding;
topOffset = y + topOffset >= 0 ? topOffset : 0;
int bottomOffset = yPadding;
bottomOffset = y + height + bottomOffset <= screenHeight ? bottomOffset : 0;
// Create parent view bitmap, cropped to the BlurLayout area with above padding.
Bitmap bitmap;
try {
bitmap = getDownscaledBitmapForView(
mActivityView.get(),
new Rect(
pointRelativeToActivityView.x + leftOffset,
pointRelativeToActivityView.y + topOffset,
pointRelativeToActivityView.x + getWidth() + Math.abs(leftOffset) + rightOffset,
pointRelativeToActivityView.y + getHeight() + Math.abs(topOffset) + bottomOffset
),
mDownscaleFactor
);
} catch (NullPointerException e) {
return null;
}
// Blur the bitmap.
bitmap = BlurKit.getInstance().blur(bitmap, mBlurRadius);
//Crop the bitmap again to remove the padding.
bitmap = Bitmap.createBitmap(
bitmap,
(int) (Math.abs(leftOffset) * mDownscaleFactor),
(int) (Math.abs(topOffset) * mDownscaleFactor),
width,
height
);
// Make self visible again.
setAlpha(1);
// Set background as blurred bitmap.
return bitmap;
}
/**
* Casts context to Activity and attempts to create a view reference using the window decor view.
* @return View reference for whole activity.
*/
private View getActivityView() {
Activity activity;
try {
activity = (Activity) getContext();
} catch (ClassCastException e) {
return null;
}
return activity.getWindow().getDecorView().findViewById(android.R.id.content);
}
/**
* Returns the position in screen. Left abstract to allow for specific implementations such as
* caching behavior.
*/
private Point getPositionInScreen() {
return getPositionInScreen(this);
}
/**
* Finds the Point of the parent view, and offsets result by self getX() and getY().
* @return Point determining position of the passed in view inside all of its ViewParents.
*/
private Point getPositionInScreen(View view) {
if (getParent() == null) {
return new Point();
}
ViewGroup parent;
try {
parent = (ViewGroup) view.getParent();
} catch (Exception e) {
return new Point();
}
if (parent == null) {
return new Point();
}
Point point = getPositionInScreen(parent);
point.offset((int) view.getX(), (int) view.getY());
return point;
}
/**
* Users a View reference to create a bitmap, and downscales it using the passed in factor.
* Uses a Rect to crop the view into the bitmap.
* @return Bitmap made from view, downscaled by downscaleFactor.
* @throws NullPointerException
*/
private Bitmap getDownscaledBitmapForView(View view, Rect crop, float downscaleFactor) throws NullPointerException {
View screenView = view.getRootView();
int width = (int) (crop.width() * downscaleFactor);
int height = (int) (crop.height() * downscaleFactor);
if (screenView.getWidth() <= 0 || screenView.getHeight() <= 0 || width <= 0 || height <= 0) {
throw new NullPointerException();
}
float dx = -crop.left * downscaleFactor;
float dy = -crop.top * downscaleFactor;
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
Canvas canvas = new Canvas(bitmap);
Matrix matrix = new Matrix();
matrix.preScale(downscaleFactor, downscaleFactor);
matrix.postTranslate(dx, dy);
canvas.setMatrix(matrix);
screenView.draw(canvas);
return bitmap;
}
/**
* Sets downscale factor to use pre-blur.
* See {@link #mDownscaleFactor}.
*/
public void setDownscaleFactor(float downscaleFactor) {
this.mDownscaleFactor = downscaleFactor;
invalidate();
}
/**
* Sets blur radius to use on downscaled bitmap.
* See {@link #mBlurRadius}.
*/
public void setBlurRadius(int blurRadius) {
this.mBlurRadius = blurRadius;
invalidate();
}
/**
* Sets FPS to invalidate blur with.
* See {@link #mFPS}.
*/
public void setFPS(int fps) {
this.mFPS = fps;
}
}
in XML file:
<FrameLayout
android:id="@+id/fl_uploadedView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/_10sdp">
<ImageView
android:id="@+id/dv_uploadedPic"
android:layout_width="match_parent"
android:layout_height="@dimen/_150sdp"
android:contentDescription="@string/app_name"
android:scaleType="centerCrop"
android:src="@color/gray" />
<BlurLayout
android:id="@+id/ll_blurView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom">
<TextView
android:id="@+id/tv_fileName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:padding="@dimen/_5sdp"
android:textSize="@dimen/_10sdp"
/>
</BlurLayout>
</FrameLayout>
Do not forget to add this to values > attr.xml
<!--
Blur Layout start
-->
<declare-styleable name="BlurLayout">
<attr name="downscaleFactor" format="float" />
<attr name="blurRadius" format="integer" />
<attr name="fps" format="integer" />
</declare-styleable>
<!--
Blur Layout end
-->