❤️Android 12 高斯模糊-RenderEffect❤️

8,514 阅读5分钟

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动

🔥 作者

作者:帅次

作者简介:CSDN博客专家,欢迎点赞、收藏、评论

粉丝福利: 公众号「帅次」一个分享Android 体系技术·相关知识·面试题库·技术互助·干货·资讯·高薪职位·教程的地方。

🔥 Android 12 高斯模糊

新功能:更易用的模糊、彩色滤镜等特效 。

新的 API 让你能更轻松地将常见图形效果应用到视图和渲染结构上。

  • 使用 RenderEffect 将模糊、色彩滤镜等效果应用于 RenderNode 或 View

  • 使用新的 Window.setBackgroundBlurRadius() API 为窗口背景创建雾面玻璃效果,

  • 使用 blurBehindRadius 来模糊窗口后面的所有内容。

咱们一个一个玩。

🔥 RenderEffect

💥 实现效果

    private void setBlur(){
        View.setRenderEffect(RenderEffect.createBlurEffect(3, 3, Shader.TileMode.REPEAT));
        ...
    }

使用特别简单,走你。

🌀 X 轴的模糊效果图

咱再看看代码

    private void setBlur(){
        agb.iv1.setRenderEffect(RenderEffect.createBlurEffect(3, 0, Shader.TileMode.CLAMP));
        agb.iv2.setRenderEffect(RenderEffect.createBlurEffect(8, 0, Shader.TileMode.REPEAT));
        agb.iv3.setRenderEffect(RenderEffect.createBlurEffect(18, 0 ,Shader.TileMode.MIRROR));
        agb.iv4.setRenderEffect(RenderEffect.createBlurEffect(36, 0,Shader.TileMode.DECAL));
    }

RenderEffect.createBlurEffect()的四个参数:

  • radiusX 沿 X 轴的模糊半径

  • radiusY 沿 Y 轴的模糊半径

  • inputEffect 模糊一次(传入 RenderEffect)

  • edgeTreatment 用于如何模糊模糊内核边缘附近的内容

下面两种仅看效果图。就不做代码设置了。

🌀 Y 轴的模糊效果图

🌀 XY同时模糊效果图

第四个参数对边缘模糊,效果图如下:

Shader.TileMode 提供了四个选项恕我没看出来。。

这里还有一堆方法等你玩。

注意:注意如此完美的画面只能在 Android 12(SDK31)及以上的设备上使用,其他版本的设备使用会导致崩溃,谨记谨记。 效果有了,下面咱们一起看看源码。

💥 源码

🌀 View.setRenderEffect()

    public void setRenderEffect(@Nullable RenderEffect renderEffect) {
        ...
    }

这个方法就是:renderEffect 应用于 View。 传入 null清除之前配置的RenderEffect 。这里咱们先看传入的 RenderEffect。

🌀 RenderEffect.createBlurEffect()

    public static RenderEffect createBlurEffect(
            float radiusX,
            float radiusY,
            @NonNull RenderEffect inputEffect,
            @NonNull TileMode edgeTreatment
    ) {
        long nativeInputEffect = inputEffect != null ? inputEffect.mNativeRenderEffect : 0;
        return new RenderEffect(
                nativeCreateBlurEffect(
                        radiusX,
                        radiusY,
                        nativeInputEffect,
                        edgeTreatment.nativeInt
                )
            );
    }

两个 createBlurEffect() 方法,分别为三参(模糊一次)和四参(模糊两次)。inputEffect 先进行了一次模糊。

看效果图:

模糊程度一样,但是实现方式不同:

    private void setBlur() {
        RenderEffect radiusXRenderEffect = RenderEffect.createBlurEffect(10, 0, Shader.TileMode.MIRROR);
        RenderEffect radiusYRenderEffect = RenderEffect.createBlurEffect(0, 10, Shader.TileMode.MIRROR);
        agb.iv1.setRenderEffect(RenderEffect.createBlurEffect(10, 10, Shader.TileMode.CLAMP));
        agb.iv2.setRenderEffect(RenderEffect.createBlurEffect(10, 10, Shader.TileMode.REPEAT));
        //自身radiusY 为 0 ,传入的radiusYRenderEffect设置的radiusY为10;
        agb.iv3.setRenderEffect(RenderEffect.createBlurEffect(10, 0, radiusYRenderEffect, Shader.TileMode.MIRROR));
        //自身radiusX 为 0 ,传入的radiusXRenderEffect设置的radiusX为10;
        agb.iv4.setRenderEffect(RenderEffect.createBlurEffect(0, 10, radiusXRenderEffect, Shader.TileMode.DECAL));
    }

这个方法返回一个 new RenderEffect(nativeCreateBlurEffect(...)。

那咱们去看看 nativeCreateBlurEffect()

🌀 nativeCreateBlurEffect()

frameworks/base/libs/hwui/jni/RenderEffect.cpp

static const JNINativeMethod gRenderEffectMethods[] = {
    ...
    {"nativeCreateBlurEffect", "(FFJI)J", (void*)createBlurEffect},
    ...
};

static jlong createBlurEffect(JNIEnv* env , jobject, jfloat radiusX,
        jfloat radiusY, jlong inputFilterHandle, jint edgeTreatment) {
    auto* inputImageFilter = reinterpret_cast<SkImageFilter*>(inputFilterHandle);
    sk_sp<SkImageFilter> blurFilter =
            SkImageFilters::Blur(
                    Blur::convertRadiusToSigma(radiusX),
                    Blur::convertRadiusToSigma(radiusY),
                    static_cast<SkTileMode>(edgeTreatment),
                    sk_ref_sp(inputImageFilter),
                    nullptr);
    return reinterpret_cast<jlong>(blurFilter.release());
}

这里有两个函数来处理我们传过来的模糊的值,咱进去看看。

🌀 convertRadiusToSigma(convertSigmaToRadius)

//该常数近似于在SkBlurMask::Blur()(1/sqrt(3)中,在软件路径的"高质量"模式下进行的缩放。
static const float BLUR_SIGMA_SCALE = 0.57735f;

float Blur::convertRadiusToSigma(float radius) {
    return radius > 0 ? BLUR_SIGMA_SCALE * radius + 0.5f : 0.0f;
}

float Blur::convertSigmaToRadius(float sigma) {
    return sigma > 0.5f ? (sigma - 0.5f) / BLUR_SIGMA_SCALE : 0.0f;
}

🌀 sk_ref_sp(inputImageFilter)

external/skia/include/core/SkRefCnt.h

/*
 *  返回包装提供的 ptr 的 sk_sp 并对其调用 ref (如果不为空)
 */
template <typename T> sk_sp<T> sk_ref_sp(T* obj) {
    //sk_sp<SkImageFilter> :
    return sk_sp<T>(SkSafeRef(obj));
}

//SkSafeRef:检查参数是否为非空,如果是,则调用 obj->ref() 并返回 obj。
template <typename T> static inline T* SkSafeRef(T* obj) {
    if (obj) {
        obj->ref();
    }
    return obj;
}

再往下走

🌀 SkImageFilters::Blur()


#define SK_Scalar1                  1.0f
#define SK_ScalarNearlyZero         (SK_Scalar1 / (1 << 12))

sk_sp<SkImageFilter> SkImageFilters::Blur(
        SkScalar sigmaX, SkScalar sigmaY, SkTileMode tileMode, sk_sp<SkImageFilter> input,
        const CropRect& cropRect) {
    if (sigmaX < SK_ScalarNearlyZero && sigmaY < SK_ScalarNearlyZero && !cropRect) {
        return input;
    }
    return sk_sp<SkImageFilter>(
          new SkBlurImageFilter(sigmaX, sigmaY, tileMode, input, cropRect));
}

附上最后的倔强

    constexpr sk_sp() : fPtr(nullptr) {}
    constexpr sk_sp(std::nullptr_t) : fPtr(nullptr) {}

    /**
     *  Shares the underlying object by calling ref(), so that both the argument and the newly
     *  created sk_sp both have a reference to it.
     */
    sk_sp(const sk_sp<T>& that) : fPtr(SkSafeRef(that.get())) {}
    template <typename U,
              typename = typename std::enable_if<std::is_convertible<U*, T*>::value>::type>
    sk_sp(const sk_sp<U>& that) : fPtr(SkSafeRef(that.get())) {}

    /**
     *  Move the underlying object from the argument to the newly created sk_sp. Afterwards only
     *  the new sk_sp will have a reference to the object, and the argument will point to null.
     *  No call to ref() or unref() will be made.
     */
    sk_sp(sk_sp<T>&& that) : fPtr(that.release()) {}
    template <typename U,
              typename = typename std::enable_if<std::is_convertible<U*, T*>::value>::type>
    sk_sp(sk_sp<U>&& that) : fPtr(that.release()) {}

    /**
     *  Adopt the bare pointer into the newly created sk_sp.
     *  No call to ref() or unref() will be made.
     */
    explicit sk_sp(T* obj) : fPtr(obj) {}

createBlurEffect() 得到 long 类型的 native 分配的的非零地址, 传入 new RenderEffect()

🌀 new RenderEffect()

    /* 构造方法:仅从静态工厂方法构造 */
    private RenderEffect(long nativeRenderEffect) {
        mNativeRenderEffect = nativeRenderEffect;
        RenderEffectHolder.RENDER_EFFECT_REGISTRY.registerNativeAllocation(
                this, mNativeRenderEffect);
    }

继续


    /**
      * @param classLoader ClassLoader 类加载器。
      * @param freeFunction 类型为 nativePtr 的本机函数的地址,用于释放这种本机分配
      * @return 由系统内存分配器分配的本机内存的 NativeAllocationRegistry。此版本更适合较小的对象(通常小于几百 KB)。
      */
    private static class RenderEffectHolder {
        public static final NativeAllocationRegistry RENDER_EFFECT_REGISTRY =
                NativeAllocationRegistry.createMalloced(
                        RenderEffect.class.getClassLoader(), nativeGetFinalizer());
    }

🌀 NativeAllocationRegistry.createMalloced()

libcore/luni/src/main/java/libcore/util/NativeAllocationRegistry.java

    @SystemApi(client = MODULE_LIBRARIES)
    public static NativeAllocationRegistry createMalloced(
            @NonNull ClassLoader classLoader, long freeFunction, long size) {
        return new NativeAllocationRegistry(classLoader, freeFunction, size, true);
    }

🌀 NativeAllocationRegistry()

libcore/luni/src/main/java/libcore/util/NativeAllocationRegistry.java

    private NativeAllocationRegistry(ClassLoader classLoader, long freeFunction, long size,
            boolean mallocAllocation) {
        if (size < 0) {
            throw new IllegalArgumentException("Invalid native allocation size: " + size);
        }
        this.classLoader = classLoader;
        this.freeFunction = freeFunction;
        this.size = mallocAllocation ? (size | IS_MALLOCED) : (size & ~IS_MALLOCED);
    }

既然拿到 NativeAllocationRegistry 那就继续调用其 registerNativeAllocation() 方法。

🌀 registerNativeAllocation ()

    @SystemApi(client = MODULE_LIBRARIES)
    @libcore.api.IntraCoreApi
    public @NonNull Runnable registerNativeAllocation(@NonNull Object referent, long nativePtr) {
        //当 referent 或nativePtr 为空
        ...
        CleanerThunk thunk;
        CleanerRunner result;
        try {
            thunk = new CleanerThunk();
            Cleaner cleaner = Cleaner.create(referent, thunk);
            result = new CleanerRunner(cleaner);
            registerNativeAllocation(this.size);
        } catch (VirtualMachineError vme /* probably OutOfMemoryError */) {
            applyFreeFunction(freeFunction, nativePtr);
            throw vme;
        } 
        // Enable the cleaner only after we can no longer throw anything, including OOME.
        thunk.setNativePtr(nativePtr);
        // Ensure that cleaner doesn't get invoked before we enable it.
        Reference.reachabilityFence(referent);
        return result;
    }

向 ART 注册新的 NativePtr 和关联的 Java 对象(也就是咱们设置的模糊类)。

返回的 Runnable 可用于在引用变得无法访问之前释放本机分配。如果运行时或使用 runnable 已经释放了本机分配,则 runnable 将不起作用。

RenderEffect 算是搞完了,咱们回到View.setRenderEffect()

🌀 View.setRenderEffect()

    public void setRenderEffect(@Nullable RenderEffect renderEffect) {
        if (mRenderNode.setRenderEffect(renderEffect)) {
            //视图属性更改(alpha、translationXY 等)的快速失效。
            invalidateViewProperty(true, true);
        }
    }

这里有个 mRenderNode.setRenderEffect(renderEffect)。咱们近距离观望一番。

🌀 mRenderNode 的创建

咱们先找找他是在什么地方创建的。

    public View(Context context) {
        ...
        //在View的构造方法中创建
        mRenderNode = RenderNode.create(getClass().getName(), new ViewAnimationHostBridge(this));
        ...
    }
    

🌀 RenderNode.create()

    /** @hide */
    public static RenderNode create(String name, @Nullable AnimationHost animationHost) {
        return new RenderNode(name, animationHost);
    }
    
    private RenderNode(String name, AnimationHost animationHost) {
        mNativeRenderNode = nCreate(name);
        //注册 Native Allocation。
        NoImagePreloadHolder.sRegistry.registerNativeAllocation(this, mNativeRenderNode);
        mAnimationHost = animationHost;
    }    

再往下感觉也看不到啥了 跟上面类似,看.cpp动态分配类的地址还是有点懵。让我缓缓~以后补充。