【问题标题】:How to use the Renderscript blurring effect without artifacts?如何在没有伪影的情况下使用 Renderscript 模糊效果?
【发布时间】:2016-04-06 09:55:00
【问题描述】:

背景

有很多地方(包括here)展示了如何使用 Renderscript 来模糊图像,例如:

@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();
    }
}

问题

通常效果很好,但是在使用某些图像和/或半径设置时,输出图像会出现看起来像扫描线的伪影:

我尝试过的

我发现有更好的模糊解决方案(如 here),但它们不使用 Renderscript,而且速度慢且消耗内存。

我也尝试使输入图像更小,但输出仍然存在扫描线伪影。

最后,我也报告了这件事,here

问题

是否可以在没有这些 Artifcats 的情况下使用 Renderscript 来模糊图像?我写的有什么问题吗?

【问题讨论】:

    标签: android bitmap blur renderscript


    【解决方案1】:

    问题出在我使用的算法上。感谢this github 项目,我发现了问题(可能没有使用正确的分配类型)并使用了更好的方法:

    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;
    }
    

    现在一切正常。

    【讨论】:

      【解决方案2】:

      原始版本中的工件是因为相同的输入分配被用作 IntrinsicBlur 的输出分配:

          blur.setInput(overlayAlloc);
          blur.setRadius(radius);
          blur.forEach(overlayAlloc);
      

      forEach(aOut) 计算高斯模糊并将结果保存到输出分配。由于该算法需要有关邻居的信息,因此在适当位置进行模糊可能会破坏用于后续计算的输入数据。

      【讨论】:

      • hmmmm.... 为什么不能检查分配是否相同,如果是,使用临时分配?另外,顺便说一句,位图本身可以用于输入和输出。
      • 这是正确的解释,苗。它还解释了伪影的水平线和直线,因为卷积在图像中逐行运行;并且水平线之间的距离与内核维度有关。该示例表明在重用分配时需要小心(除了一个脚本的分配被重用为另一个脚本的分配的情况,这样做很有效)。
      【解决方案3】:

      我使用下面的代码。成功了!

      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;
      }
      

      在 build.Gradle 中

      defaultConfig {
          applicationId "hello.test.app"
          minSdkVersion 16
          targetSdkVersion 22
          versionCode 1
          versionName "1.0"
          renderscriptTargetApi 18
          renderscriptSupportModeEnabled true
      }
      

      【讨论】:

      • 关于 inputBitmap.copy(inputBitmap.getConfig()... ,这是错误的,因为输入图像可以是配置 565。另外,我认为我的问题是我没有使用 createTyped 。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-07
      • 1970-01-01
      相关资源
      最近更新 更多