Last active
May 23, 2023 10:14
-
-
Save stepango/040bc2a47595c9e7121e to your computer and use it in GitHub Desktop.
Glide compatible fast blur bitmap transformation.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.bandlab.bandlab.transformation; | |
import android.content.Context; | |
import android.graphics.Bitmap; | |
import android.graphics.Canvas; | |
import android.graphics.Color; | |
import android.graphics.Paint; | |
import android.graphics.PorterDuff; | |
import android.graphics.PorterDuffColorFilter; | |
import android.support.annotation.ColorRes; | |
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; | |
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation; | |
/** | |
* Glide transformation class for blur bitmap | |
*/ | |
public class BlurTransform extends BitmapTransformation { | |
public static final String TRANSFORMATION_ID = "com.bandlab.bandlab.BlurTransform"; | |
public static final int DEFAULT_SCALE_FACTOR = 5; | |
private static final int DEFAULT_BLUR_RADIUS = 21; | |
private static final int DEFAULT_FOREGROUND = Color.WHITE; | |
private final Context context; | |
private | |
@ColorRes | |
int foregroundColor; | |
private int blurRadius; | |
private int scaleFactor = DEFAULT_SCALE_FACTOR; | |
public BlurTransform(Context context) { | |
this(DEFAULT_BLUR_RADIUS, context); | |
} | |
public BlurTransform(int blurRadius, Context context) { | |
this(blurRadius, DEFAULT_FOREGROUND, context); | |
} | |
public BlurTransform(int blurRadius, @ColorRes int foregroundColor, Context context) { | |
super(context); | |
this.blurRadius = (int) (blurRadius * context.getResources().getDisplayMetrics().density); | |
this.foregroundColor = foregroundColor; | |
this.context = context; | |
setScaleFactor(DEFAULT_SCALE_FACTOR); | |
} | |
public static Bitmap getBluredBitmapFastBlur(Bitmap sentBitmap, int radius) { | |
// 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 <[email protected]> | |
if (radius < 1) { | |
return (null); | |
} | |
int w = sentBitmap.getWidth(); | |
int h = sentBitmap.getHeight(); | |
int[] pix = new int[w * h]; | |
sentBitmap.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; | |
} | |
} | |
sentBitmap.setPixels(pix, 0, w, 0, 0, w, h); | |
return (sentBitmap); | |
} | |
/** | |
* Scale factor for decrease bitmap size before blur processing. | |
* {@link #DEFAULT_SCALE_FACTOR} | |
*/ | |
private void setScaleFactor(int scaleFactor) { | |
if (scaleFactor < 1) { | |
throw new IllegalArgumentException("Scale factor must be >= 1"); | |
} | |
this.scaleFactor = scaleFactor; | |
} | |
@Override | |
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) { | |
if (toTransform.getConfig() != Bitmap.Config.ARGB_8888) { | |
toTransform = toTransform.copy(Bitmap.Config.ARGB_8888, true); | |
} | |
Bitmap bitmap = Bitmap.createScaledBitmap( | |
toTransform, | |
toTransform.getWidth() / scaleFactor, | |
toTransform.getHeight() / scaleFactor, | |
false | |
); | |
toTransform.recycle(); | |
//Add blur to bitmap | |
bitmap = getBluredBitmapFastBlur(bitmap, blurRadius / scaleFactor); | |
//Add foreground color if needed | |
if (foregroundColor > 0) { | |
addForegroundColor(bitmap, foregroundColor); | |
} | |
return bitmap; | |
} | |
private void addForegroundColor(Bitmap bitmap, @ColorRes int foregroundColor) { | |
Canvas canvas = new Canvas(bitmap); | |
Paint paint = new Paint(); | |
paint.setAntiAlias(true); | |
paint.setColorFilter(new PorterDuffColorFilter( | |
context.getResources().getColor(foregroundColor), | |
PorterDuff.Mode.SRC_ATOP) | |
); | |
canvas.drawBitmap(bitmap, 0, 0, paint); | |
} | |
@Override | |
public String getId() { | |
return TRANSFORMATION_ID + "(blurRadius = " + blurRadius + ", foreground = " + foregroundColor + ")"; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment