25 | * If you return false from this method, you'll be responsible to swap bitmaps in your 26 | * {@link BlurAlgorithm#blur(Bitmap, float)} implementation 27 | * (assign input bitmap to your field and return the instance algorithm just blurred). 28 | */ 29 | boolean canModifyBitmap(); 30 | 31 | /** 32 | * Retrieve the {@link android.graphics.Bitmap.Config} on which the {@link BlurAlgorithm} 33 | * can actually work. 34 | * 35 | * @return bitmap config supported by the given blur algorithm. 36 | */ 37 | @NonNull 38 | Bitmap.Config getSupportedBitmapConfig(); 39 | 40 | float scaleFactor(); 41 | 42 | void render(@NonNull Canvas canvas, @NonNull Bitmap bitmap); 43 | } 44 | -------------------------------------------------------------------------------- /library/src/main/java/eightbitlab/com/blurview/BlurController.java: -------------------------------------------------------------------------------- 1 | package eightbitlab.com.blurview; 2 | 3 | import android.graphics.Canvas; 4 | 5 | public interface BlurController extends BlurViewFacade { 6 | 7 | float DEFAULT_SCALE_FACTOR = 6f; 8 | float DEFAULT_BLUR_RADIUS = 16f; 9 | 10 | /** 11 | * Draws blurred content on given canvas 12 | * 13 | * @return true if BlurView should proceed with drawing itself and its children 14 | */ 15 | boolean draw(Canvas canvas); 16 | 17 | /** 18 | * Must be used to notify Controller when BlurView's size has changed 19 | */ 20 | void updateBlurViewSize(); 21 | 22 | /** 23 | * Frees allocated resources 24 | */ 25 | void destroy(); 26 | } 27 | -------------------------------------------------------------------------------- /library/src/main/java/eightbitlab/com/blurview/BlurView.java: -------------------------------------------------------------------------------- 1 | package eightbitlab.com.blurview; 2 | 3 | import static eightbitlab.com.blurview.PreDrawBlurController.TRANSPARENT; 4 | 5 | import android.content.Context; 6 | import android.content.res.TypedArray; 7 | import android.graphics.Canvas; 8 | import android.os.Build; 9 | import android.util.AttributeSet; 10 | import android.util.Log; 11 | import android.view.ViewGroup; 12 | import android.widget.FrameLayout; 13 | 14 | import androidx.annotation.ColorInt; 15 | import androidx.annotation.NonNull; 16 | import androidx.annotation.RequiresApi; 17 | 18 | /** 19 | * FrameLayout that blurs its underlying content. 20 | * Can have children and draw them over blurred background. 21 | */ 22 | public class BlurView extends FrameLayout { 23 | 24 | private static final String TAG = BlurView.class.getSimpleName(); 25 | 26 | BlurController blurController = new NoOpController(); 27 | 28 | @ColorInt 29 | private int overlayColor; 30 | private boolean blurAutoUpdate = true; 31 | 32 | public BlurView(Context context) { 33 | super(context); 34 | init(null, 0); 35 | } 36 | 37 | public BlurView(Context context, AttributeSet attrs) { 38 | super(context, attrs); 39 | init(attrs, 0); 40 | } 41 | 42 | public BlurView(Context context, AttributeSet attrs, int defStyleAttr) { 43 | super(context, attrs, defStyleAttr); 44 | init(attrs, defStyleAttr); 45 | } 46 | 47 | private void init(AttributeSet attrs, int defStyleAttr) { 48 | TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.BlurView, defStyleAttr, 0); 49 | overlayColor = a.getColor(R.styleable.BlurView_blurOverlayColor, TRANSPARENT); 50 | a.recycle(); 51 | } 52 | 53 | @Override 54 | public void draw(Canvas canvas) { 55 | boolean shouldDraw = blurController.draw(canvas); 56 | if (shouldDraw) { 57 | super.draw(canvas); 58 | } 59 | } 60 | 61 | @Override 62 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 63 | super.onSizeChanged(w, h, oldw, oldh); 64 | blurController.updateBlurViewSize(); 65 | } 66 | 67 | @Override 68 | protected void onDetachedFromWindow() { 69 | super.onDetachedFromWindow(); 70 | blurController.setBlurAutoUpdate(false); 71 | } 72 | 73 | @Override 74 | protected void onAttachedToWindow() { 75 | super.onAttachedToWindow(); 76 | if (!isHardwareAccelerated()) { 77 | Log.e(TAG, "BlurView can't be used in not hardware-accelerated window!"); 78 | } else { 79 | blurController.setBlurAutoUpdate(this.blurAutoUpdate); 80 | } 81 | } 82 | 83 | /** 84 | * @param rootView root to start blur from. 85 | * Can be Activity's root content layout (android.R.id.content) 86 | * or (preferably) some of your layouts. The lower amount of Views are in the root, the better for performance. 87 | * @param algorithm sets the blur algorithm 88 | * @return {@link BlurView} to setup needed params. 89 | */ 90 | public BlurViewFacade setupWith(@NonNull ViewGroup rootView, BlurAlgorithm algorithm) { 91 | this.blurController.destroy(); 92 | BlurController blurController = new PreDrawBlurController(this, rootView, overlayColor, algorithm); 93 | this.blurController = blurController; 94 | 95 | return blurController; 96 | } 97 | 98 | /** 99 | * @param rootView root to start blur from. 100 | * Can be Activity's root content layout (android.R.id.content) 101 | * or (preferably) some of your layouts. The lower amount of Views are in the root, the better for performance. 102 | *
103 | * BlurAlgorithm is automatically picked based on the API version. 104 | * It uses RenderEffectBlur on API 31+, and RenderScriptBlur on older versions. 105 | * @return {@link BlurView} to setup needed params. 106 | */ 107 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) 108 | public BlurViewFacade setupWith(@NonNull ViewGroup rootView) { 109 | return setupWith(rootView, getBlurAlgorithm()); 110 | } 111 | 112 | // Setters duplicated to be able to conveniently change these settings outside of setupWith chain 113 | 114 | /** 115 | * @see BlurViewFacade#setBlurRadius(float) 116 | */ 117 | public BlurViewFacade setBlurRadius(float radius) { 118 | return blurController.setBlurRadius(radius); 119 | } 120 | 121 | /** 122 | * @see BlurViewFacade#setOverlayColor(int) 123 | */ 124 | public BlurViewFacade setOverlayColor(@ColorInt int overlayColor) { 125 | this.overlayColor = overlayColor; 126 | return blurController.setOverlayColor(overlayColor); 127 | } 128 | 129 | /** 130 | * @see BlurViewFacade#setBlurAutoUpdate(boolean) 131 | */ 132 | public BlurViewFacade setBlurAutoUpdate(boolean enabled) { 133 | this.blurAutoUpdate = enabled; 134 | return blurController.setBlurAutoUpdate(enabled); 135 | } 136 | 137 | /** 138 | * @see BlurViewFacade#setBlurEnabled(boolean) 139 | */ 140 | public BlurViewFacade setBlurEnabled(boolean enabled) { 141 | return blurController.setBlurEnabled(enabled); 142 | } 143 | 144 | @NonNull 145 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1) 146 | private BlurAlgorithm getBlurAlgorithm() { 147 | BlurAlgorithm algorithm; 148 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { 149 | algorithm = new RenderEffectBlur(); 150 | } else { 151 | algorithm = new RenderScriptBlur(getContext()); 152 | } 153 | return algorithm; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /library/src/main/java/eightbitlab/com/blurview/BlurViewCanvas.java: -------------------------------------------------------------------------------- 1 | package eightbitlab.com.blurview; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Canvas; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | // Serves purely as a marker of a Canvas used in BlurView 9 | // to skip drawing itself and other BlurViews on the View hierarchy snapshot 10 | public class BlurViewCanvas extends Canvas { 11 | public BlurViewCanvas(@NonNull Bitmap bitmap) { 12 | super(bitmap); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /library/src/main/java/eightbitlab/com/blurview/BlurViewFacade.java: -------------------------------------------------------------------------------- 1 | package eightbitlab.com.blurview; 2 | 3 | import android.graphics.drawable.Drawable; 4 | 5 | import androidx.annotation.ColorInt; 6 | import androidx.annotation.Nullable; 7 | 8 | public interface BlurViewFacade { 9 | 10 | /** 11 | * Enables/disables the blur. Enabled by default 12 | * 13 | * @param enabled true to enable, false otherwise 14 | * @return {@link BlurViewFacade} 15 | */ 16 | BlurViewFacade setBlurEnabled(boolean enabled); 17 | 18 | /** 19 | * Can be used to stop blur auto update or resume if it was stopped before. 20 | * Enabled by default. 21 | * 22 | * @return {@link BlurViewFacade} 23 | */ 24 | BlurViewFacade setBlurAutoUpdate(boolean enabled); 25 | 26 | /** 27 | * @param frameClearDrawable sets the drawable to draw before view hierarchy. 28 | * Can be used to draw Activity's window background if your root layout doesn't provide any background 29 | * Optional, by default frame is cleared with a transparent color. 30 | * @return {@link BlurViewFacade} 31 | */ 32 | BlurViewFacade setFrameClearDrawable(@Nullable Drawable frameClearDrawable); 33 | 34 | /** 35 | * @param radius sets the blur radius 36 | * Default value is {@link BlurController#DEFAULT_BLUR_RADIUS} 37 | * @return {@link BlurViewFacade} 38 | */ 39 | BlurViewFacade setBlurRadius(float radius); 40 | 41 | /** 42 | * Sets the color overlay to be drawn on top of blurred content 43 | * 44 | * @param overlayColor int color 45 | * @return {@link BlurViewFacade} 46 | */ 47 | BlurViewFacade setOverlayColor(@ColorInt int overlayColor); 48 | } 49 | -------------------------------------------------------------------------------- /library/src/main/java/eightbitlab/com/blurview/NoOpController.java: -------------------------------------------------------------------------------- 1 | package eightbitlab.com.blurview; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.drawable.Drawable; 5 | 6 | import androidx.annotation.Nullable; 7 | 8 | // Used in edit mode and in case if no BlurController was set 9 | public class NoOpController implements BlurController { 10 | @Override 11 | public boolean draw(Canvas canvas) { 12 | return true; 13 | } 14 | 15 | @Override 16 | public void updateBlurViewSize() { 17 | } 18 | 19 | @Override 20 | public void destroy() { 21 | } 22 | 23 | @Override 24 | public BlurViewFacade setBlurRadius(float radius) { 25 | return this; 26 | } 27 | 28 | @Override 29 | public BlurViewFacade setOverlayColor(int overlayColor) { 30 | return this; 31 | } 32 | 33 | @Override 34 | public BlurViewFacade setFrameClearDrawable(@Nullable Drawable windowBackground) { 35 | return this; 36 | } 37 | 38 | @Override 39 | public BlurViewFacade setBlurEnabled(boolean enabled) { 40 | return this; 41 | } 42 | 43 | @Override 44 | public BlurViewFacade setBlurAutoUpdate(boolean enabled) { 45 | return this; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /library/src/main/java/eightbitlab/com/blurview/PreDrawBlurController.java: -------------------------------------------------------------------------------- 1 | package eightbitlab.com.blurview; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.drawable.Drawable; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.view.ViewTreeObserver; 10 | 11 | import androidx.annotation.ColorInt; 12 | import androidx.annotation.NonNull; 13 | import androidx.annotation.Nullable; 14 | 15 | /** 16 | * Blur Controller that handles all blur logic for the attached View. 17 | * It honors View size changes, View animation and Visibility changes. 18 | *
19 | * The basic idea is to draw the view hierarchy on a bitmap, excluding the attached View, 20 | * then blur and draw it on the system Canvas. 21 | *
22 | * It uses {@link ViewTreeObserver.OnPreDrawListener} to detect when 23 | * blur should be updated. 24 | *
25 | */
26 | public final class PreDrawBlurController implements BlurController {
27 |
28 | @ColorInt
29 | public static final int TRANSPARENT = 0;
30 |
31 | private float blurRadius = DEFAULT_BLUR_RADIUS;
32 |
33 | private final BlurAlgorithm blurAlgorithm;
34 | private BlurViewCanvas internalCanvas;
35 | private Bitmap internalBitmap;
36 |
37 | @SuppressWarnings("WeakerAccess")
38 | final View blurView;
39 | private int overlayColor;
40 | private final ViewGroup rootView;
41 | private final int[] rootLocation = new int[2];
42 | private final int[] blurViewLocation = new int[2];
43 |
44 | private final ViewTreeObserver.OnPreDrawListener drawListener = new ViewTreeObserver.OnPreDrawListener() {
45 | @Override
46 | public boolean onPreDraw() {
47 | // Not invalidating a View here, just updating the Bitmap.
48 | // This relies on the HW accelerated bitmap drawing behavior in Android
49 | // If the bitmap was drawn on HW accelerated canvas, it holds a reference to it and on next
50 | // drawing pass the updated content of the bitmap will be rendered on the screen
51 | updateBlur();
52 | return true;
53 | }
54 | };
55 |
56 | private boolean blurEnabled = true;
57 | private boolean initialized;
58 |
59 | @Nullable
60 | private Drawable frameClearDrawable;
61 |
62 | /**
63 | * @param blurView View which will draw it's blurred underlying content
64 | * @param rootView Root View where blurView's underlying content starts drawing.
65 | * Can be Activity's root content layout (android.R.id.content)
66 | * @param algorithm sets the blur algorithm
67 | */
68 | public PreDrawBlurController(@NonNull View blurView, @NonNull ViewGroup rootView, @ColorInt int overlayColor, BlurAlgorithm algorithm) {
69 | this.rootView = rootView;
70 | this.blurView = blurView;
71 | this.overlayColor = overlayColor;
72 | this.blurAlgorithm = algorithm;
73 | if (algorithm instanceof RenderEffectBlur) {
74 | // noinspection NewApi
75 | ((RenderEffectBlur) algorithm).setContext(blurView.getContext());
76 | }
77 |
78 | int measuredWidth = blurView.getMeasuredWidth();
79 | int measuredHeight = blurView.getMeasuredHeight();
80 |
81 | init(measuredWidth, measuredHeight);
82 | }
83 |
84 | @SuppressWarnings("WeakerAccess")
85 | void init(int measuredWidth, int measuredHeight) {
86 | setBlurAutoUpdate(true);
87 | SizeScaler sizeScaler = new SizeScaler(blurAlgorithm.scaleFactor());
88 | if (sizeScaler.isZeroSized(measuredWidth, measuredHeight)) {
89 | // Will be initialized later when the View reports a size change
90 | blurView.setWillNotDraw(true);
91 | return;
92 | }
93 |
94 | blurView.setWillNotDraw(false);
95 | SizeScaler.Size bitmapSize = sizeScaler.scale(measuredWidth, measuredHeight);
96 | internalBitmap = Bitmap.createBitmap(bitmapSize.width, bitmapSize.height, blurAlgorithm.getSupportedBitmapConfig());
97 | internalCanvas = new BlurViewCanvas(internalBitmap);
98 | initialized = true;
99 | // Usually it's not needed, because `onPreDraw` updates the blur anyway.
100 | // But it handles cases when the PreDraw listener is attached to a different Window, for example
101 | // when the BlurView is in a Dialog window, but the root is in the Activity.
102 | // Previously it was done in `draw`, but it was causing potential side effects and Jetpack Compose crashes
103 | updateBlur();
104 | }
105 |
106 | @SuppressWarnings("WeakerAccess")
107 | void updateBlur() {
108 | if (!blurEnabled || !initialized) {
109 | return;
110 | }
111 |
112 | if (frameClearDrawable == null) {
113 | internalBitmap.eraseColor(Color.TRANSPARENT);
114 | } else {
115 | frameClearDrawable.draw(internalCanvas);
116 | }
117 |
118 | internalCanvas.save();
119 | setupInternalCanvasMatrix();
120 | rootView.draw(internalCanvas);
121 | internalCanvas.restore();
122 |
123 | blurAndSave();
124 | }
125 |
126 | /**
127 | * Set up matrix to draw starting from blurView's position
128 | */
129 | private void setupInternalCanvasMatrix() {
130 | rootView.getLocationOnScreen(rootLocation);
131 | blurView.getLocationOnScreen(blurViewLocation);
132 |
133 | int left = blurViewLocation[0] - rootLocation[0];
134 | int top = blurViewLocation[1] - rootLocation[1];
135 |
136 | // https://github.com/Dimezis/BlurView/issues/128
137 | float scaleFactorH = (float) blurView.getHeight() / internalBitmap.getHeight();
138 | float scaleFactorW = (float) blurView.getWidth() / internalBitmap.getWidth();
139 |
140 | float scaledLeftPosition = -left / scaleFactorW;
141 | float scaledTopPosition = -top / scaleFactorH;
142 |
143 | internalCanvas.translate(scaledLeftPosition, scaledTopPosition);
144 | internalCanvas.scale(1 / scaleFactorW, 1 / scaleFactorH);
145 | }
146 |
147 | @Override
148 | public boolean draw(Canvas canvas) {
149 | if (!blurEnabled || !initialized) {
150 | return true;
151 | }
152 | // Not blurring itself or other BlurViews to not cause recursive draw calls
153 | // Related: https://github.com/Dimezis/BlurView/issues/110
154 | if (canvas instanceof BlurViewCanvas) {
155 | return false;
156 | }
157 |
158 | // https://github.com/Dimezis/BlurView/issues/128
159 | float scaleFactorH = (float) blurView.getHeight() / internalBitmap.getHeight();
160 | float scaleFactorW = (float) blurView.getWidth() / internalBitmap.getWidth();
161 |
162 | canvas.save();
163 | canvas.scale(scaleFactorW, scaleFactorH);
164 | blurAlgorithm.render(canvas, internalBitmap);
165 | canvas.restore();
166 | if (overlayColor != TRANSPARENT) {
167 | canvas.drawColor(overlayColor);
168 | }
169 | return true;
170 | }
171 |
172 | private void blurAndSave() {
173 | internalBitmap = blurAlgorithm.blur(internalBitmap, blurRadius);
174 | if (!blurAlgorithm.canModifyBitmap()) {
175 | internalCanvas.setBitmap(internalBitmap);
176 | }
177 | }
178 |
179 | @Override
180 | public void updateBlurViewSize() {
181 | int measuredWidth = blurView.getMeasuredWidth();
182 | int measuredHeight = blurView.getMeasuredHeight();
183 |
184 | init(measuredWidth, measuredHeight);
185 | }
186 |
187 | @Override
188 | public void destroy() {
189 | setBlurAutoUpdate(false);
190 | blurAlgorithm.destroy();
191 | initialized = false;
192 | }
193 |
194 | @Override
195 | public BlurViewFacade setBlurRadius(float radius) {
196 | this.blurRadius = radius;
197 | return this;
198 | }
199 |
200 | @Override
201 | public BlurViewFacade setFrameClearDrawable(@Nullable Drawable frameClearDrawable) {
202 | this.frameClearDrawable = frameClearDrawable;
203 | return this;
204 | }
205 |
206 | @Override
207 | public BlurViewFacade setBlurEnabled(boolean enabled) {
208 | this.blurEnabled = enabled;
209 | setBlurAutoUpdate(enabled);
210 | blurView.invalidate();
211 | return this;
212 | }
213 |
214 | public BlurViewFacade setBlurAutoUpdate(final boolean enabled) {
215 | rootView.getViewTreeObserver().removeOnPreDrawListener(drawListener);
216 | blurView.getViewTreeObserver().removeOnPreDrawListener(drawListener);
217 | if (enabled) {
218 | rootView.getViewTreeObserver().addOnPreDrawListener(drawListener);
219 | // Track changes in the blurView window too, for example if it's in a bottom sheet dialog
220 | if (rootView.getWindowId() != blurView.getWindowId()) {
221 | blurView.getViewTreeObserver().addOnPreDrawListener(drawListener);
222 | }
223 | }
224 | return this;
225 | }
226 |
227 | @Override
228 | public BlurViewFacade setOverlayColor(int overlayColor) {
229 | if (this.overlayColor != overlayColor) {
230 | this.overlayColor = overlayColor;
231 | blurView.invalidate();
232 | }
233 | return this;
234 | }
235 | }
236 |
--------------------------------------------------------------------------------
/library/src/main/java/eightbitlab/com/blurview/RenderEffectBlur.java:
--------------------------------------------------------------------------------
1 | package eightbitlab.com.blurview;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.graphics.Canvas;
6 | import android.graphics.RenderEffect;
7 | import android.graphics.RenderNode;
8 | import android.graphics.Shader;
9 | import android.os.Build;
10 |
11 | import androidx.annotation.NonNull;
12 | import androidx.annotation.Nullable;
13 | import androidx.annotation.RequiresApi;
14 |
15 | /**
16 | * Leverages the new RenderEffect.createBlurEffect API to perform blur.
17 | * Hardware accelerated.
18 | * Blur is performed on a separate thread - native RenderThread.
19 | * It doesn't block the Main thread, however it can still cause an FPS drop,
20 | * because it's just in a different part of the rendering pipeline.
21 | */
22 | @RequiresApi(Build.VERSION_CODES.S)
23 | public class RenderEffectBlur implements BlurAlgorithm {
24 |
25 | private final RenderNode node = new RenderNode("BlurViewNode");
26 |
27 | private int height, width;
28 | private float lastBlurRadius = 1f;
29 |
30 | @Nullable
31 | public BlurAlgorithm fallbackAlgorithm;
32 | private Context context;
33 |
34 | public RenderEffectBlur() {
35 | }
36 |
37 | @Override
38 | public Bitmap blur(@NonNull Bitmap bitmap, float blurRadius) {
39 | lastBlurRadius = blurRadius;
40 |
41 | if (bitmap.getHeight() != height || bitmap.getWidth() != width) {
42 | height = bitmap.getHeight();
43 | width = bitmap.getWidth();
44 | node.setPosition(0, 0, width, height);
45 | }
46 | Canvas canvas = node.beginRecording();
47 | canvas.drawBitmap(bitmap, 0, 0, null);
48 | node.endRecording();
49 | node.setRenderEffect(RenderEffect.createBlurEffect(blurRadius, blurRadius, Shader.TileMode.MIRROR));
50 | // returning not blurred bitmap, because the rendering relies on the RenderNode
51 | return bitmap;
52 | }
53 |
54 | @Override
55 | public void destroy() {
56 | node.discardDisplayList();
57 | if (fallbackAlgorithm != null) {
58 | fallbackAlgorithm.destroy();
59 | }
60 | }
61 |
62 | @Override
63 | public boolean canModifyBitmap() {
64 | return true;
65 | }
66 |
67 | @NonNull
68 | @Override
69 | public Bitmap.Config getSupportedBitmapConfig() {
70 | return Bitmap.Config.ARGB_8888;
71 | }
72 |
73 | @Override
74 | public float scaleFactor() {
75 | return BlurController.DEFAULT_SCALE_FACTOR;
76 | }
77 |
78 | @Override
79 | public void render(@NonNull Canvas canvas, @NonNull Bitmap bitmap) {
80 | if (canvas.isHardwareAccelerated()) {
81 | canvas.drawRenderNode(node);
82 | } else {
83 | if (fallbackAlgorithm == null) {
84 | fallbackAlgorithm = new RenderScriptBlur(context);
85 | }
86 | fallbackAlgorithm.blur(bitmap, lastBlurRadius);
87 | fallbackAlgorithm.render(canvas, bitmap);
88 | }
89 | }
90 |
91 | void setContext(@NonNull Context context) {
92 | this.context = context;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/library/src/main/java/eightbitlab/com/blurview/RenderScriptBlur.java:
--------------------------------------------------------------------------------
1 | package eightbitlab.com.blurview;
2 |
3 | import static eightbitlab.com.blurview.BlurController.DEFAULT_SCALE_FACTOR;
4 |
5 | import android.content.Context;
6 | import android.graphics.Bitmap;
7 | import android.graphics.Canvas;
8 | import android.graphics.Paint;
9 | import android.os.Build;
10 | import android.renderscript.Allocation;
11 | import android.renderscript.Element;
12 | import android.renderscript.RenderScript;
13 | import android.renderscript.ScriptIntrinsicBlur;
14 |
15 | import androidx.annotation.NonNull;
16 | import androidx.annotation.RequiresApi;
17 |
18 | /**
19 | * Blur using RenderScript, processed on GPU when device drivers support it.
20 | * Requires API 17+
21 | *
22 | * @deprecated because RenderScript is deprecated and its hardware acceleration is not guaranteed.
23 | * RenderEffectBlur is the best alternative at the moment.
24 | */
25 | @Deprecated
26 | public class RenderScriptBlur implements BlurAlgorithm {
27 | private final Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG);
28 | private final RenderScript renderScript;
29 | private final ScriptIntrinsicBlur blurScript;
30 | private Allocation outAllocation;
31 |
32 | private int lastBitmapWidth = -1;
33 | private int lastBitmapHeight = -1;
34 |
35 | /**
36 | * @param context Context to create the {@link RenderScript}
37 | */
38 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
39 | public RenderScriptBlur(@NonNull Context context) {
40 | renderScript = RenderScript.create(context);
41 | blurScript = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));
42 | }
43 |
44 | private boolean canReuseAllocation(@NonNull Bitmap bitmap) {
45 | return bitmap.getHeight() == lastBitmapHeight && bitmap.getWidth() == lastBitmapWidth;
46 | }
47 |
48 | /**
49 | * @param bitmap bitmap to blur
50 | * @param blurRadius blur radius (1..25)
51 | * @return blurred bitmap
52 | */
53 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
54 | @Override
55 | public Bitmap blur(@NonNull Bitmap bitmap, float blurRadius) {
56 | //Allocation will use the same backing array of pixels as bitmap if created with USAGE_SHARED flag
57 | Allocation inAllocation = Allocation.createFromBitmap(renderScript, bitmap);
58 |
59 | if (!canReuseAllocation(bitmap)) {
60 | if (outAllocation != null) {
61 | outAllocation.destroy();
62 | }
63 | outAllocation = Allocation.createTyped(renderScript, inAllocation.getType());
64 | lastBitmapWidth = bitmap.getWidth();
65 | lastBitmapHeight = bitmap.getHeight();
66 | }
67 |
68 | blurScript.setRadius(blurRadius);
69 | blurScript.setInput(inAllocation);
70 | //do not use inAllocation in forEach. it will cause visual artifacts on blurred Bitmap
71 | blurScript.forEach(outAllocation);
72 | outAllocation.copyTo(bitmap);
73 |
74 | inAllocation.destroy();
75 | return bitmap;
76 | }
77 |
78 | @Override
79 | public final void destroy() {
80 | blurScript.destroy();
81 | renderScript.destroy();
82 | if (outAllocation != null) {
83 | outAllocation.destroy();
84 | }
85 | }
86 |
87 | @Override
88 | public boolean canModifyBitmap() {
89 | return true;
90 | }
91 |
92 | @NonNull
93 | @Override
94 | public Bitmap.Config getSupportedBitmapConfig() {
95 | return Bitmap.Config.ARGB_8888;
96 | }
97 |
98 | @Override
99 | public float scaleFactor() {
100 | return DEFAULT_SCALE_FACTOR;
101 | }
102 |
103 | @Override
104 | public void render(@NonNull Canvas canvas, @NonNull Bitmap bitmap) {
105 | canvas.drawBitmap(bitmap, 0f, 0f, paint);
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/library/src/main/java/eightbitlab/com/blurview/SizeScaler.java:
--------------------------------------------------------------------------------
1 | package eightbitlab.com.blurview;
2 |
3 | /**
4 | * Scales width and height by [scaleFactor],
5 | * and then rounds the size proportionally so the width is divisible by [ROUNDING_VALUE]
6 | */
7 | public class SizeScaler {
8 |
9 | // Bitmap size should be divisible by ROUNDING_VALUE to meet stride requirement.
10 | // This will help avoiding an extra bitmap allocation when passing the bitmap to RenderScript for blur.
11 | // Usually it's 16, but on Samsung devices it's 64 for some reason.
12 | private static final int ROUNDING_VALUE = 64;
13 | private final float scaleFactor;
14 |
15 | public SizeScaler(float scaleFactor) {
16 | this.scaleFactor = scaleFactor;
17 | }
18 |
19 | Size scale(int width, int height) {
20 | int nonRoundedScaledWidth = downscaleSize(width);
21 | int scaledWidth = roundSize(nonRoundedScaledWidth);
22 | //Only width has to be aligned to ROUNDING_VALUE
23 | float roundingScaleFactor = (float) width / scaledWidth;
24 | //Ceiling because rounding or flooring might leave empty space on the View's bottom
25 | int scaledHeight = (int) Math.ceil(height / roundingScaleFactor);
26 |
27 | return new Size(scaledWidth, scaledHeight, roundingScaleFactor);
28 | }
29 |
30 | boolean isZeroSized(int measuredWidth, int measuredHeight) {
31 | return downscaleSize(measuredHeight) == 0 || downscaleSize(measuredWidth) == 0;
32 | }
33 |
34 | /**
35 | * Rounds a value to the nearest divisible by {@link #ROUNDING_VALUE} to meet stride requirement
36 | */
37 | private int roundSize(int value) {
38 | if (value % ROUNDING_VALUE == 0) {
39 | return value;
40 | }
41 | return value - (value % ROUNDING_VALUE) + ROUNDING_VALUE;
42 | }
43 |
44 | private int downscaleSize(float value) {
45 | return (int) Math.ceil(value / scaleFactor);
46 | }
47 |
48 | static class Size {
49 |
50 | final int width;
51 | final int height;
52 | // TODO this is probably not needed anymore
53 | final float scaleFactor;
54 |
55 | Size(int width, int height, float scaleFactor) {
56 | this.width = width;
57 | this.height = height;
58 | this.scaleFactor = scaleFactor;
59 | }
60 |
61 | @Override
62 | public boolean equals(Object o) {
63 | if (this == o) return true;
64 | if (o == null || getClass() != o.getClass()) return false;
65 |
66 | Size size = (Size) o;
67 |
68 | if (width != size.width) return false;
69 | if (height != size.height) return false;
70 | return Float.compare(size.scaleFactor, scaleFactor) == 0;
71 | }
72 |
73 | @Override
74 | public int hashCode() {
75 | int result = width;
76 | result = 31 * result + height;
77 | result = 31 * result + (scaleFactor != +0.0f ? Float.floatToIntBits(scaleFactor) : 0);
78 | return result;
79 | }
80 |
81 | @Override
82 | public String toString() {
83 | return "Size{" +
84 | "width=" + width +
85 | ", height=" + height +
86 | ", scaleFactor=" + scaleFactor +
87 | '}';
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/library/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |