9

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:

enter image description here

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?

Community
  • 1
  • 1
android developer
  • 114,585
  • 152
  • 739
  • 1,270

3 Answers3

7

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.

android developer
  • 114,585
  • 152
  • 739
  • 1,270
4

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.

Miao Wang
  • 1,120
  • 9
  • 12
  • hmmmm.... why can't it check if the allocations are the same, and if they are, use a temporary allocation? Also, btw, the bitmap itself can be used for both input and output. – android developer Apr 13 '16 at 07:15
  • that is the correct explanation, Miao. It also explains the horizontal and straight lines of the artefacts because the convolution runs line by line through the image; and the distance between the horizontal lines is related to the kernel dimension. The example shows one needs to be careful when reusing allocations (apart from the case where the outallocation of one script is reused as the inallocation of another script, which is efficient to to so). – Settembrini Apr 13 '16 at 10:16
-2

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
}
Nguyễn Trung Hiếu
  • 2,004
  • 1
  • 10
  • 22
  • About inputBitmap.copy(inputBitmap.getConfig()... , this is wrong because the input image can be of config 565. Also, I think my issue was that I didn't use createTyped . – android developer Apr 06 '16 at 12:32