Background
There are plenty of places (including here) to show how to use Renderscript to blur images, as such:
#TargetApi(VERSION_CODES.JELLY_BEAN_MR1)
public static Bitmap renderScriptBlur(Context context, Bitmap srcBitmap, #FloatRange(from = 0.0f, to = 25.0f) float radius) {
if (srcBitmap == null)
return null;
Bitmap outputBitmap = null;
RenderScript rs = null;
try {
rs = RenderScript.create(context);
outputBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(outputBitmap);
canvas.drawBitmap(srcBitmap, 0, 0, null);
Allocation overlayAlloc = Allocation.createFromBitmap(rs, outputBitmap);
ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
blur.setInput(overlayAlloc);
blur.setRadius(radius);
blur.forEach(overlayAlloc);
overlayAlloc.copyTo(outputBitmap);
return outputBitmap;
} catch (Exception ex) {
if (outputBitmap != null)
outputBitmap.recycle();
return srcBitmap;
} finally {
if (rs != null)
rs.destroy();
}
}
The problem
Usually it works great, but when using some images and/or radius-settings, the output image has artifacts that look like scan-lines:
What I've tried
I've found that there is a nicer blurring solutions (like here), but they don't use Renderscript and are also slow and memory-consuming.
I've also tried to make the input image smaller, but the output still has scanlines artifacts.
Finally, I've also reported about this, here.
The question
Is it possible to use Renderscript to blur images without those Artifcats? Is there anything wrong with what I wrote?
The problem was with the algorithm I used. Thanks to this github project, I've found the issue (which is probably not using the correct type fot the allocation) and used a nicer approach:
private static final AtomicReference<RenderScript> sRenderscript = new AtomicReference<>();
public static Bitmap blur(Context context, Bitmap bitmap) {
return blur(context, bitmap, 4, false, false);
}
public static Bitmap blur(Context context, Bitmap bitmap, float radius) {
return blur(context, bitmap, radius, false, false);
}
public static Bitmap blur(Context context, Bitmap bitmapOriginal, #FloatRange(from = 0.0f, to = 25.0f) float radius, boolean overrideOriginal, boolean recycleOriginal) {
if (bitmapOriginal == null || bitmapOriginal.isRecycled())
return null;
RenderScript rs = sRenderscript.get();
if (rs == null)
if (!sRenderscript.compareAndSet(null, rs = RenderScript.create(context)) && rs != null)
rs.destroy();
else
rs = sRenderscript.get();
final Bitmap inputBitmap = bitmapOriginal.getConfig() == Config.ARGB_8888 ? bitmapOriginal : bitmapOriginal.copy(Config.ARGB_8888, true);
final Bitmap outputBitmap = overrideOriginal ? bitmapOriginal : Bitmap.createBitmap(bitmapOriginal.getWidth(), bitmapOriginal.getHeight(), Config.ARGB_8888);
final Allocation input = Allocation.createFromBitmap(rs, inputBitmap);
final Allocation output = Allocation.createTyped(rs, input.getType());
final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
script.setRadius(radius);
script.setInput(input);
script.forEach(output);
if (recycleOriginal && !overrideOriginal)
bitmapOriginal.recycle();
output.copyTo(outputBitmap);
return outputBitmap;
}
Now it all works nicely.
The artifact in the original version is because the same input Allocation was used as the output Allocation for IntrinsicBlur:
blur.setInput(overlayAlloc);
blur.setRadius(radius);
blur.forEach(overlayAlloc);
forEach(aOut) computes the Gaussian blur and saves the result to the output Allocation. Since the algorithm requires information about the neighbors, doing blur in place may corrupt the input data for sub-sequent calculations.
I use code below. It worked!
public static Bitmap blurRenderScript(Context context, Bitmap inputBitmap, int radius) {
Bitmap outputBitmap = inputBitmap.copy(inputBitmap.getConfig(), true);
RenderScript renderScript = RenderScript.create(context);
Allocation blurInput = Allocation.createFromBitmap(renderScript, inputBitmap, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
Allocation blurOutput = Allocation.createFromBitmap(renderScript, outputBitmap);
ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(renderScript,
Element.U8_4(renderScript));
blur.setInput(blurInput);
blur.setRadius(radius); // radius must be 0 < r <= 25
blur.forEach(blurOutput);
blurOutput.copyTo(outputBitmap);
renderScript.destroy();
return outputBitmap;
}
in build.Gradle
defaultConfig {
applicationId "hello.test.app"
minSdkVersion 16
targetSdkVersion 22
versionCode 1
versionName "1.0"
renderscriptTargetApi 18
renderscriptSupportModeEnabled true
}
Related
I want to blur a Bitmap object in Android, currently I have the following code which blurs an ImageView:
private fun blurImageView(radius: Float) {
if (Build.VERSION.SDK_INT >= 31) {
binding.activityMainImageView.setRenderEffect(
RenderEffect.createBlurEffect(radius, radius, Shader.TileMode.CLAMP)
)
}
}
I want to get the underlying Bitmap object, so I tried to achieve that by doing the following:
binding.activityMainImageView.drawToBitmap()
But it doesn't seem to be working.
So how would I go around simply blurring a Bitmap object, is this possible with RenderScript at all? If not, what are my options to create a blur effect on a Bitmap object and get the underlying Bitmap object?
The developer documentation has given no info on how you could go around doing this.
You can create a bitmap blur effect in Android using RenderScript API!
The code maybe like this:
public Bitmap blur(Bitmap image) {
if (null == image){
return null;
}
Bitmap outputBitmap = Bitmap.createBitmap(image);
final RenderScript renderScript = RenderScript.create(this);
Allocation tmpIn = Allocation.createFromBitmap(renderScript, image);
Allocation tmpOut = Allocation.createFromBitmap(renderScript, outputBitmap);
//Intrinsic Gausian blur filter
ScriptIntrinsicBlur theIntrinsic = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));
theIntrinsic.setRadius(25); //should be 0-25
theIntrinsic.setInput(tmpIn);
theIntrinsic.forEach(tmpOut);
tmpOut.copyTo(outputBitmap);
return outputBitmap;
}
Then use:
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test_pic);
Bitmap blurredBitmap = blur(bitmap);
Hope this can help you!
I am trying to blur image in using Glide, hence I have created a custom BlurTransformation like following
public class BlurTransformation extends BitmapTransformation {
private RenderScript rs;
public BlurTransformation(Context context) {
super();
rs = RenderScript.create(context);
}
#RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
#Override
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
Bitmap blurredBitmap = toTransform.copy(Bitmap.Config.ARGB_8888, true);
// Load up an instance of the specific script that we want to use.
ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
// Allocate memory for Renderscript to work with
Allocation input = Allocation.createFromBitmap(rs, blurredBitmap, Allocation.MipmapControl.MIPMAP_FULL, Allocation.USAGE_SHARED);
Allocation output = Allocation.createTyped(rs, input.getType());
script.setInput(input);
// Set the blur radius
script.setRadius(25);
// Start the ScriptIntrinisicBlur
script.forEach(output);
// Copy the output to the blurred bitmap
output.copyTo(blurredBitmap);
return blurredBitmap;
}
#Override
public void updateDiskCacheKey(MessageDigest messageDigest) {
messageDigest.update("blur transformation".getBytes());
}
}
It does the job , but the only problem is it doesn't work when I put script.setRadius(25) over 25.
I know there is a library for this https://github.com/wasabeef/glide-transformations , but I do not want to use it.
According to the documents, max blur radius is 25. https://developer.android.com/reference/android/renderscript/ScriptIntrinsicBlur.html#setRadius(float)
Hello I have a problem with blur images..
I want in one Imageview set blur background and original image..
I try put all of one imageview.. now I testing two imageviews but it's the same..
my code
// Then later, when you want to display image
ImageLoader.getInstance().displayImage(listKone.get(i).getCestaObrazok(), imgHorse); // Default options will be used
// ↑ thise method have link where are my image and download image and show it in imgHorse ImageView
imgHorseB=imgHorse;
ImageLoader.getInstance().displayImage(listKone.get(i).getCestaObrazok(), imgHorseB); // Default options will be used
BitmapDrawable drawable = (BitmapDrawable) imgHorseB.getDrawable();
Bitmap bitmap = drawable.getBitmap();
Bitmap blurredBitmap = blur(bitmap);
imgHorseB.setImageBitmap(blurredBitmap);
public Bitmap blur(final Bitmap image) {
if (null == image) return null;
Bitmap outputBitmap = Bitmap.createBitmap(image);
final RenderScript renderScript = RenderScript.create(activity);
Allocation tmpIn = Allocation.createFromBitmap(renderScript, outputBitmap);
Allocation tmpOut = Allocation.createFromBitmap(renderScript, outputBitmap);
//Intrinsic Gausian blur filter
ScriptIntrinsicBlur theIntrinsic = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));
theIntrinsic.setRadius(BLUR_RADIUS);
theIntrinsic.setInput(tmpIn);
theIntrinsic.forEach(tmpOut);
tmpOut.copyTo(outputBitmap);
return outputBitmap;
}
after methode blur have blure image in all bitmaps.. If debbug code blur effect nothing but on thise line theIntrinsic.forEach(tmpOut);is blur image in both bitmapsimage and enter code here I don't know why..
and after finish thise methode blur image is in bluredBitmap and bitmap but it's wrong..
Please help me
#SuppressLint("NewApi")
public static Bitmap blurRenderScript(Context context,Bitmap smallBitmap, int radius) {
try {
smallBitmap = RGB565toARGB888(smallBitmap);
} catch (Exception e) {
e.printStackTrace();
}
Bitmap bitmap = Bitmap.createBitmap(
smallBitmap.getWidth(), smallBitmap.getHeight(),
Bitmap.Config.ARGB_8888);
RenderScript renderScript = RenderScript.create(context);
Allocation blurInput = Allocation.createFromBitmap(renderScript, smallBitmap);
Allocation blurOutput = Allocation.createFromBitmap(renderScript, bitmap);
ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(renderScript,
Element.U8_4(renderScript));
blur.setInput(blurInput);
blur.setRadius(radius); // radius must be 0 < r <= 25
blur.forEach(blurOutput);
blurOutput.copyTo(bitmap);
renderScript.destroy();
return bitmap;
}
Bitmap blurred = blurRenderScript(activity,bitmap, 25);
//second parametre is radius
imgHorseB.setScaleType(ImageView.ScaleType.FIT_XY);
imgHorseB.setImageBitmap(blurred);
I want blur to ImageView
This code not work and on ScriptIntrinsicBlur line force close
Blur Class :
private static final float BLUR_RADIUS = 20f;
public Bitmap blur(Bitmap image) {
if (null == image)
return null;
Bitmap outputBitmap = Bitmap.createBitmap(image);
final RenderScript renderScript = RenderScript.create(this);
Allocation tmpIn = Allocation.createFromBitmap(renderScript, image);
Allocation tmpOut = Allocation.createFromBitmap(renderScript, outputBitmap);
ScriptIntrinsicBlur theIntrinsic = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));
theIntrinsic.setRadius(BLUR_RADIUS);
theIntrinsic.setInput(tmpIn);
theIntrinsic.forEach(tmpOut);
tmpOut.copyTo(outputBitmap);
return outputBitmap;
}
Set blur code :
bmp = BitmapFactory.decodeStream(urlImage.openConnection().getInputStream());
bmp = blur(bmp);
imgPost.setImageBitmap(bmp);
And erro log :
http://i.stack.imgur.com/sL2BT.jpg
Per the ScriptIntrinsicBlur documentation, the class was added in API 17 and therefore does not exist prior to that. You can use the Renderscript Support Library to gain access Renderscript back to API 8, including android.support.v8.renderscript.ScriptIntrinsicBlur
Using the glide android library I get the image as a bitmap (see glide documentation) and then I try to blur the bitmap, using renderscript and ScriptIntrinsicBlur, which is a gaussian blur. (Taken from this stackoverflow post)
Glide.with(getApplicationContext())
.load(ImageUrl)
.asBitmap()
.into(new SimpleTarget<Bitmap>(300,200) {
#Override
public void onResourceReady(Bitmap resource, GlideAnimation glideAnimation) {
RenderScript rs = RenderScript.create(mContext); // context = this. this referring to the activity
final Allocation input = Allocation.createFromBitmap( rs, resource, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT );
final Allocation output = Allocation.createTyped( rs, input.getType() );
final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create( rs, Element.U8_4( rs ) );
script.setRadius(8f);
script.setInput(input);
script.forEach(output);
output.copyTo(resource);
mImageView.setImageBitmap(resource);
}
});
The problem is that this is the output, rather than a blurred image:
Any help would be much appreciated thanks. :)
As it's support only U8_4 and U8 format. You'll have to convert your bitmap to ARGB_8888 before you send it to RenderScript by this example.
Bitmap U8_4Bitmap;
if(sentBitmap.getConfig() == Bitmap.Config.ARGB_8888) {
U8_4Bitmap = sentBitmap;
} else {
U8_4Bitmap = sentBitmap.copy(Bitmap.Config.ARGB_8888, true);
}
//==============================
Bitmap bitmap = Bitmap.createBitmap(U8_4Bitmap.getWidth(), U8_4Bitmap.getHeight(), U8_4Bitmap.getConfig());
final RenderScript rs = RenderScript.create(context);
final Allocation input = Allocation.createFromBitmap(rs,
U8_4Bitmap,
Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT);
final Allocation output = Allocation.createTyped(rs, input.getType());
final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, output.getElement());
script.setRadius(radius);
script.setInput(input);
script.forEach(output);
output.copyTo(bitmap);
rs.destroy();
return bitmap;
Is it possible that the input image is not a U8_4 (i.e. RGBA8888)? Can you switch from using "Element.U8_4(rs)" to instead use "output.getElement()"? That would probably do the right thing. If it turns out that the image is not RGBA8888, you might at least get a Java exception describing what the underlying format is (if it isn't something supported with our Blur).