diff options
author | Nader Jawad <njawad@google.com> | 2020-09-24 21:35:03 -0700 |
---|---|---|
committer | Nader Jawad <njawad@google.com> | 2020-09-29 14:20:02 -0700 |
commit | 390d6e8586c276716b693be0594328e6debea5c7 (patch) | |
tree | d683df303b927623991320052af6a2bf0aabc6f5 | |
parent | 3f9f18eaf18b9cd172df5a7504c59e1b20c76740 (diff) |
Added RenderEffect property on RenderNode
Introduced RenderEffect API to handle consuming
SkImageFilter parameters on RenderNode objects
in order to support blur.
Updated SilkFX demo to use RenderEffect
APIs on RenderNode instead of BlurShader
Bug: 168549524
Test: Added tests to RenderNode CTS test cases
Change-Id: I5005a322a6d75438dd104e6915630264406cf771
-rw-r--r-- | graphics/java/android/graphics/RenderEffect.java | 145 | ||||
-rw-r--r-- | graphics/java/android/graphics/RenderNode.java | 20 | ||||
-rw-r--r-- | libs/hwui/Android.bp | 1 | ||||
-rw-r--r-- | libs/hwui/RenderProperties.cpp | 7 | ||||
-rw-r--r-- | libs/hwui/RenderProperties.h | 7 | ||||
-rw-r--r-- | libs/hwui/apex/LayoutlibLoader.cpp | 2 | ||||
-rw-r--r-- | libs/hwui/apex/jni_runtime.cpp | 2 | ||||
-rw-r--r-- | libs/hwui/hwui/Paint.h | 1 | ||||
-rw-r--r-- | libs/hwui/jni/RenderEffect.cpp | 69 | ||||
-rw-r--r-- | libs/hwui/jni/android_graphics_RenderNode.cpp | 7 | ||||
-rw-r--r-- | libs/hwui/pipeline/skia/RenderNodeDrawable.cpp | 4 | ||||
-rw-r--r-- | tests/HwAccelerationTest/AndroidManifest.xml | 2 | ||||
-rw-r--r-- | tests/HwAccelerationTest/res/layout/image_filter_activity.xml | 28 | ||||
-rw-r--r-- | tests/HwAccelerationTest/src/com/android/test/hwui/BlurActivity.java | 86 | ||||
-rw-r--r-- | tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt | 60 |
15 files changed, 402 insertions, 39 deletions
diff --git a/graphics/java/android/graphics/RenderEffect.java b/graphics/java/android/graphics/RenderEffect.java new file mode 100644 index 000000000000..9fc0c8eb9d90 --- /dev/null +++ b/graphics/java/android/graphics/RenderEffect.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics; + +import android.annotation.NonNull; +import android.graphics.Shader.TileMode; + +import libcore.util.NativeAllocationRegistry; + +/** + * Intermediate rendering step used to render drawing commands with a corresponding + * visual effect + * + * @hide + */ +public final class RenderEffect { + + private static class RenderEffectHolder { + public static final NativeAllocationRegistry RENDER_EFFECT_REGISTRY = + NativeAllocationRegistry.createMalloced( + RenderEffect.class.getClassLoader(), nativeGetFinalizer()); + } + + /** + * Create a {@link RenderEffect} instance that will offset the drawing content + * by the provided x and y offset. + * @param offsetX offset along the x axis in pixels + * @param offsetY offset along the y axis in pixels + */ + @NonNull + public static RenderEffect createOffsetEffect(float offsetX, float offsetY) { + return new RenderEffect(nativeCreateOffsetEffect(offsetX, offsetY, 0)); + } + + /** + * Create a {@link RenderEffect} instance with the provided x and y offset + * @param offsetX offset along the x axis in pixels + * @param offsetY offset along the y axis in pixels + * @param input target RenderEffect used to render in the offset coordinates. + */ + @NonNull + public static RenderEffect createOffsetEffect( + float offsetX, + float offsetY, + @NonNull RenderEffect input + ) { + return new RenderEffect(nativeCreateOffsetEffect( + offsetX, + offsetY, + input.getNativeInstance() + ) + ); + } + + /** + * Create a {@link RenderEffect} that blurs the contents of the optional input RenderEffect + * with the specified radius along the x and y axis. If no input RenderEffect is provided + * then all drawing commands issued with a {@link android.graphics.RenderNode} that this + * RenderEffect is installed in will be blurred + * @param radiusX Radius of blur along the X axis + * @param radiusY Radius of blur along the Y axis + * @param inputEffect Input RenderEffect that provides the content to be blurred, can be null + * to indicate that the drawing commands on the RenderNode are to be + * blurred instead of the input RenderEffect + * @param edgeTreatment Policy for how to blur content near edges of the blur kernel + */ + @NonNull + 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 + ) + ); + } + + /** + * Create a {@link RenderEffect} that blurs the contents of the + * {@link android.graphics.RenderNode} that this RenderEffect is installed on with the + * specified radius along hte x and y axis. + * @param radiusX Radius of blur along the X axis + * @param radiusY Radius of blur along the Y axis + * @param edgeTreatment Policy for how to blur content near edges of the blur kernel + */ + @NonNull + public static RenderEffect createBlurEffect( + float radiusX, + float radiusY, + @NonNull TileMode edgeTreatment + ) { + return new RenderEffect( + nativeCreateBlurEffect( + radiusX, + radiusY, + 0, + edgeTreatment.nativeInt + ) + ); + } + + private final long mNativeRenderEffect; + + /* only constructed from static factory methods */ + private RenderEffect(long nativeRenderEffect) { + mNativeRenderEffect = nativeRenderEffect; + RenderEffectHolder.RENDER_EFFECT_REGISTRY.registerNativeAllocation( + this, mNativeRenderEffect); + } + + /** + * Obtain the pointer to the underlying RenderEffect to be configured + * on a RenderNode object via {@link RenderNode#setRenderEffect(RenderEffect)} + */ + /* package */ long getNativeInstance() { + return mNativeRenderEffect; + } + + private static native long nativeCreateOffsetEffect( + float offsetX, float offsetY, long nativeInput); + private static native long nativeCreateBlurEffect( + float radiusX, float radiusY, long nativeInput, int edgeTreatment); + private static native long nativeGetFinalizer(); +} diff --git a/graphics/java/android/graphics/RenderNode.java b/graphics/java/android/graphics/RenderNode.java index 8aacbc7bc109..d812c1a5595d 100644 --- a/graphics/java/android/graphics/RenderNode.java +++ b/graphics/java/android/graphics/RenderNode.java @@ -850,6 +850,23 @@ public final class RenderNode { } /** + * Configure the {@link android.graphics.RenderEffect} to apply to this RenderNode. This + * will apply a visual effect to the end result of the contents of this RenderNode before + * it is drawn into the destination. For example if + * {@link RenderEffect#createBlurEffect(float, float, RenderEffect, Shader.TileMode)} + * is provided, the contents will be drawn in a separate layer, then this layer will + * be blurred when this RenderNode is drawn into the destination. + * @param renderEffect to be applied to the RenderNode. Passing null clears all previously + * configured RenderEffects + * + * @hide + */ + public void setRenderEffect(@Nullable RenderEffect renderEffect) { + nSetRenderEffect(mNativeRenderNode, + renderEffect != null ? renderEffect.getNativeInstance() : 0); + } + + /** * Returns the translucency level of this display list. * * @return A value between 0.0f and 1.0f @@ -1655,6 +1672,9 @@ public final class RenderNode { private static native boolean nSetAlpha(long renderNode, float alpha); @CriticalNative + private static native void nSetRenderEffect(long renderNode, long renderEffect); + + @CriticalNative private static native boolean nSetHasOverlappingRendering(long renderNode, boolean hasOverlappingRendering); diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index 90d2537d97a8..9f4c9a1eddfa 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -327,6 +327,7 @@ cc_defaults { "jni/PathMeasure.cpp", "jni/Picture.cpp", "jni/Shader.cpp", + "jni/RenderEffect.cpp", "jni/Typeface.cpp", "jni/Utils.cpp", "jni/YuvToJpegEncoder.cpp", diff --git a/libs/hwui/RenderProperties.cpp b/libs/hwui/RenderProperties.cpp index ff9cf45cdc73..8fba9cf21df1 100644 --- a/libs/hwui/RenderProperties.cpp +++ b/libs/hwui/RenderProperties.cpp @@ -49,6 +49,12 @@ bool LayerProperties::setColorFilter(SkColorFilter* filter) { return true; } +bool LayerProperties::setImageFilter(SkImageFilter* imageFilter) { + if(mImageFilter.get() == imageFilter) return false; + mImageFilter = sk_ref_sp(imageFilter); + return true; +} + bool LayerProperties::setFromPaint(const SkPaint* paint) { bool changed = false; changed |= setAlpha(static_cast<uint8_t>(PaintUtils::getAlphaDirect(paint))); @@ -63,6 +69,7 @@ LayerProperties& LayerProperties::operator=(const LayerProperties& other) { setAlpha(other.alpha()); setXferMode(other.xferMode()); setColorFilter(other.getColorFilter()); + setImageFilter(other.getImageFilter()); return *this; } diff --git a/libs/hwui/RenderProperties.h b/libs/hwui/RenderProperties.h index ef4cd1f1eb62..aeb60e6ce355 100644 --- a/libs/hwui/RenderProperties.h +++ b/libs/hwui/RenderProperties.h @@ -27,6 +27,7 @@ #include "utils/PaintUtils.h" #include <SkBlendMode.h> +#include <SkImageFilter.h> #include <SkCamera.h> #include <SkColor.h> #include <SkMatrix.h> @@ -93,6 +94,10 @@ public: SkColorFilter* getColorFilter() const { return mColorFilter.get(); } + bool setImageFilter(SkImageFilter* imageFilter); + + SkImageFilter* getImageFilter() const { return mImageFilter.get(); } + // Sets alpha, xfermode, and colorfilter from an SkPaint // paint may be NULL, in which case defaults will be set bool setFromPaint(const SkPaint* paint); @@ -118,6 +123,7 @@ private: uint8_t mAlpha; SkBlendMode mMode; sk_sp<SkColorFilter> mColorFilter; + sk_sp<SkImageFilter> mImageFilter; }; /* @@ -541,6 +547,7 @@ public: bool promotedToLayer() const { return mLayerProperties.mType == LayerType::None && fitsOnLayer() && (mComputedFields.mNeedLayerForFunctors || + mLayerProperties.mImageFilter != nullptr || (!MathUtils::isZero(mPrimitiveFields.mAlpha) && mPrimitiveFields.mAlpha < 1 && mPrimitiveFields.mHasOverlappingRendering)); } diff --git a/libs/hwui/apex/LayoutlibLoader.cpp b/libs/hwui/apex/LayoutlibLoader.cpp index 4bbf1214bdcf..dca10e29cbb8 100644 --- a/libs/hwui/apex/LayoutlibLoader.cpp +++ b/libs/hwui/apex/LayoutlibLoader.cpp @@ -47,6 +47,7 @@ extern int register_android_graphics_MaskFilter(JNIEnv* env); extern int register_android_graphics_NinePatch(JNIEnv*); extern int register_android_graphics_PathEffect(JNIEnv* env); extern int register_android_graphics_Shader(JNIEnv* env); +extern int register_android_graphics_RenderEffect(JNIEnv* env); extern int register_android_graphics_Typeface(JNIEnv* env); namespace android { @@ -108,6 +109,7 @@ static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = { {"android.graphics.RecordingCanvas", REG_JNI(register_android_view_DisplayListCanvas)}, // {"android.graphics.Region", REG_JNI(register_android_graphics_Region)}, {"android.graphics.Shader", REG_JNI(register_android_graphics_Shader)}, + {"android.graphics.RenderEffect", REG_JNI(register_android_graphics_RenderEffect)}, {"android.graphics.Typeface", REG_JNI(register_android_graphics_Typeface)}, {"android.graphics.animation.NativeInterpolatorFactory", REG_JNI(register_android_graphics_animation_NativeInterpolatorFactory)}, diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp index 12e2e8135278..deccc802dbb2 100644 --- a/libs/hwui/apex/jni_runtime.cpp +++ b/libs/hwui/apex/jni_runtime.cpp @@ -43,6 +43,7 @@ extern int register_android_graphics_Movie(JNIEnv* env); extern int register_android_graphics_NinePatch(JNIEnv*); extern int register_android_graphics_PathEffect(JNIEnv* env); extern int register_android_graphics_Shader(JNIEnv* env); +extern int register_android_graphics_RenderEffect(JNIEnv* env); extern int register_android_graphics_Typeface(JNIEnv* env); extern int register_android_graphics_YuvImage(JNIEnv* env); @@ -123,6 +124,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_graphics_Picture), REG_JNI(register_android_graphics_Region), REG_JNI(register_android_graphics_Shader), + REG_JNI(register_android_graphics_RenderEffect), REG_JNI(register_android_graphics_TextureLayer), REG_JNI(register_android_graphics_Typeface), REG_JNI(register_android_graphics_YuvImage), diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h index 0bb689c19079..4e2016a41f5e 100644 --- a/libs/hwui/hwui/Paint.h +++ b/libs/hwui/hwui/Paint.h @@ -157,7 +157,6 @@ public: private: using SkPaint::setShader; - using SkPaint::setImageFilter; SkFont mFont; sk_sp<SkDrawLooper> mLooper; diff --git a/libs/hwui/jni/RenderEffect.cpp b/libs/hwui/jni/RenderEffect.cpp new file mode 100644 index 000000000000..0ebd0ca720d8 --- /dev/null +++ b/libs/hwui/jni/RenderEffect.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "Bitmap.h" +#include "GraphicsJNI.h" +#include "SkImageFilter.h" +#include "SkImageFilters.h" +#include "graphics_jni_helpers.h" +#include "utils/Blur.h" +#include <utils/Log.h> + +using namespace android::uirenderer; + +static jlong createOffsetEffect( + JNIEnv* env, + jobject, + jfloat offsetX, + jfloat offsetY, + jlong inputFilterHandle +) { + auto* inputFilter = reinterpret_cast<const SkImageFilter*>(inputFilterHandle); + sk_sp<SkImageFilter> offset = SkImageFilters::Offset(offsetX, offsetY, sk_ref_sp(inputFilter)); + return reinterpret_cast<jlong>(offset.release()); +} + +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()); +} + +static void RenderEffect_safeUnref(SkImageFilter* filter) { + SkSafeUnref(filter); +} + +static jlong getRenderEffectFinalizer(JNIEnv*, jobject) { + return static_cast<jlong>(reinterpret_cast<uintptr_t>(&RenderEffect_safeUnref)); +} + +static const JNINativeMethod gRenderEffectMethods[] = { + {"nativeGetFinalizer", "()J", (void*)getRenderEffectFinalizer}, + {"nativeCreateOffsetEffect", "(FFJ)J", (void*)createOffsetEffect}, + {"nativeCreateBlurEffect", "(FFJI)J", (void*)createBlurEffect} +}; + +int register_android_graphics_RenderEffect(JNIEnv* env) { + android::RegisterMethodsOrDie(env, "android/graphics/RenderEffect", + gRenderEffectMethods, NELEM(gRenderEffectMethods)); + return 0; +}
\ No newline at end of file diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp index 85c802b40459..4b4aa92b97b7 100644 --- a/libs/hwui/jni/android_graphics_RenderNode.cpp +++ b/libs/hwui/jni/android_graphics_RenderNode.cpp @@ -215,6 +215,12 @@ static jboolean android_view_RenderNode_setAlpha(CRITICAL_JNI_PARAMS_COMMA jlong return SET_AND_DIRTY(setAlpha, alpha, RenderNode::ALPHA); } +static jboolean android_view_RenderNode_setRenderEffect(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, + jlong renderEffectPtr) { + SkImageFilter* imageFilter = reinterpret_cast<SkImageFilter*>(renderEffectPtr); + return SET_AND_DIRTY(mutateLayerProperties().setImageFilter, imageFilter, RenderNode::GENERIC); +} + static jboolean android_view_RenderNode_setHasOverlappingRendering(CRITICAL_JNI_PARAMS_COMMA jlong renderNodePtr, bool hasOverlappingRendering) { return SET_AND_DIRTY(setHasOverlappingRendering, hasOverlappingRendering, @@ -690,6 +696,7 @@ static const JNINativeMethod gMethods[] = { { "nSetRevealClip", "(JZFFF)Z", (void*) android_view_RenderNode_setRevealClip }, { "nSetAlpha", "(JF)Z", (void*) android_view_RenderNode_setAlpha }, + { "nSetRenderEffect", "(JJ)V", (void*) android_view_RenderNode_setRenderEffect }, { "nSetHasOverlappingRendering", "(JZ)Z", (void*) android_view_RenderNode_setHasOverlappingRendering }, { "nSetUsageHint", "(JI)V", (void*) android_view_RenderNode_setUsageHint }, diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp index 00ceb2d84f9e..1473b3e5abb7 100644 --- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp +++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp @@ -172,10 +172,12 @@ static bool layerNeedsPaint(const LayerProperties& properties, float alphaMultip SkPaint* paint) { paint->setFilterQuality(kLow_SkFilterQuality); if (alphaMultiplier < 1.0f || properties.alpha() < 255 || - properties.xferMode() != SkBlendMode::kSrcOver || properties.getColorFilter() != nullptr) { + properties.xferMode() != SkBlendMode::kSrcOver || properties.getColorFilter() != nullptr || + properties.getImageFilter() != nullptr) { paint->setAlpha(properties.alpha() * alphaMultiplier); paint->setBlendMode(properties.xferMode()); paint->setColorFilter(sk_ref_sp(properties.getColorFilter())); + paint->setImageFilter(sk_ref_sp(properties.getImageFilter())); return true; } return false; diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index 05a59ef7fc72..9a2def935f5d 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -745,7 +745,7 @@ </activity> <activity android:name="BlurActivity" - android:label="Shaders/Blur" + android:label="RenderEffect/Blur" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN"/> diff --git a/tests/HwAccelerationTest/res/layout/image_filter_activity.xml b/tests/HwAccelerationTest/res/layout/image_filter_activity.xml new file mode 100644 index 000000000000..a0ee67ae0bef --- /dev/null +++ b/tests/HwAccelerationTest/res/layout/image_filter_activity.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2020 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center"> + + <ImageView + android:id="@+id/image_filter_test_view" + android:background="#FF0000" + android:layout_width="200dp" + android:layout_height="200dp" /> +</FrameLayout>
\ No newline at end of file diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/BlurActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/BlurActivity.java index 033fb0ec35d2..e4ca7881f796 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/BlurActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/BlurActivity.java @@ -18,11 +18,12 @@ package com.android.test.hwui; import android.app.Activity; import android.content.Context; -import android.graphics.BlurShader; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.LinearGradient; import android.graphics.Paint; +import android.graphics.RenderEffect; +import android.graphics.RenderNode; import android.graphics.Shader; import android.os.Bundle; import android.view.Gravity; @@ -51,16 +52,27 @@ public class BlurActivity extends Activity { } public static class BlurGradientView extends View { - private BlurShader mBlurShader = null; - private Paint mPaint; + private final float mBlurRadius = 25f; + private final Paint mPaint; + private final RenderNode mRenderNode; public BlurGradientView(Context c) { super(c); + mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mRenderNode = new RenderNode("BlurGradientView"); + mRenderNode.setRenderEffect( + RenderEffect.createBlurEffect( + mBlurRadius, + mBlurRadius, + null, + Shader.TileMode.DECAL + ) + ); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - if (changed || mBlurShader == null) { + if (changed) { LinearGradient gradient = new LinearGradient( 0f, 0f, @@ -70,41 +82,81 @@ public class BlurActivity extends Activity { Color.YELLOW, Shader.TileMode.CLAMP ); - mBlurShader = new BlurShader(30f, 40f, gradient); - mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mPaint.setShader(mBlurShader); + + mPaint.setShader(gradient); + + final int width = right - left; + final int height = bottom - top; + mRenderNode.setPosition(0, 0, width, height); + + Canvas canvas = mRenderNode.beginRecording(); + canvas.drawRect( + mBlurRadius * 2, + mBlurRadius * 2, + width - mBlurRadius * 2, + height - mBlurRadius * 2, + mPaint + ); + mRenderNode.endRecording(); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); - canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint); + canvas.drawRenderNode(mRenderNode); } } public static class BlurView extends View { - private final BlurShader mBlurShader; private final Paint mPaint; + private final RenderNode mRenderNode; + private final float mBlurRadius = 20f; public BlurView(Context c) { super(c); - mBlurShader = new BlurShader(20f, 20f, null); mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mPaint.setShader(mBlurShader); + mRenderNode = new RenderNode("blurNode"); + mRenderNode.setRenderEffect( + RenderEffect.createBlurEffect( + mBlurRadius, + mBlurRadius, + null, + Shader.TileMode.DECAL + ) + ); } @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + if (changed) { + int width = right - left; + int height = bottom - top; + mRenderNode.setPosition(0, 0, width, height); + Canvas canvas = mRenderNode.beginRecording(width, height); + mPaint.setColor(Color.BLUE); + + canvas.drawRect( + mBlurRadius * 2, + mBlurRadius * 2, + width - mBlurRadius * 2, + height - mBlurRadius * 2, + mPaint + ); - mPaint.setColor(Color.BLUE); - canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint); + mPaint.setColor(Color.RED); + canvas.drawCircle((right - left) / 2f, (bottom - top) / 2f, 50f, mPaint); + + mRenderNode.endRecording(); + } + } - mPaint.setColor(Color.RED); - canvas.drawCircle(getWidth() / 2f, getHeight() / 2f, 50f, mPaint); + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + canvas.drawRenderNode(mRenderNode); } } } diff --git a/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt b/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt index 711758476a62..2f2578b87f35 100644 --- a/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt +++ b/tests/SilkFX/src/com/android/test/silkfx/materials/GlassView.kt @@ -20,13 +20,13 @@ import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.BitmapShader import android.graphics.BlendMode -import android.graphics.BlurShader import android.graphics.Canvas import android.graphics.Color import android.graphics.Outline import android.graphics.Paint -import android.graphics.RadialGradient import android.graphics.Rect +import android.graphics.RenderEffect +import android.graphics.RenderNode import android.graphics.Shader import android.hardware.Sensor import android.hardware.SensorEvent @@ -36,7 +36,6 @@ import android.util.AttributeSet import android.view.View import android.view.ViewOutlineProvider import android.widget.FrameLayout -import com.android.internal.graphics.ColorUtils import com.android.test.silkfx.R import kotlin.math.sin import kotlin.math.sqrt @@ -152,10 +151,19 @@ class GlassView(context: Context, attributeSet: AttributeSet) : FrameLayout(cont var blurRadius = 150f set(value) { field = value - blurPaint.shader = BlurShader(value, value, null) + renderNode.setRenderEffect( + RenderEffect.createBlurEffect(value, value, Shader.TileMode.CLAMP)) invalidate() } + private var renderNodeIsDirty = true + private val renderNode = RenderNode("GlassRenderNode") + + override fun invalidate() { + renderNodeIsDirty = true + super.invalidate() + } + init { setWillNotDraw(false) materialPaint.blendMode = BlendMode.SOFT_LIGHT @@ -164,7 +172,6 @@ class GlassView(context: Context, attributeSet: AttributeSet) : FrameLayout(cont scrimPaint.alpha = (scrimOpacity * 255).toInt() noisePaint.alpha = (noiseOpacity * 255).toInt() materialPaint.alpha = (materialOpacity * 255).toInt() - blurPaint.shader = BlurShader(blurRadius, blurRadius, null) outlineProvider = object : ViewOutlineProvider() { override fun getOutline(view: View?, outline: Outline?) { outline?.setRoundRect(Rect(0, 0, width, height), 100f) @@ -184,20 +191,8 @@ class GlassView(context: Context, attributeSet: AttributeSet) : FrameLayout(cont } override fun onDraw(canvas: Canvas?) { - src.set(-width / 2, -height / 2, width / 2, height / 2) - src.scale(1.0f + zoom) - val centerX = left + width / 2 - val centerY = top + height / 2 - val textureXOffset = (textureTranslationMultiplier * gyroYRotation).toInt() - val textureYOffset = (textureTranslationMultiplier * gyroXRotation).toInt() - src.set(src.left + centerX + textureXOffset, src.top + centerY + textureYOffset, - src.right + centerX + textureXOffset, src.bottom + centerY + textureYOffset) - - dst.set(0, 0, width, height) - canvas?.drawBitmap(backgroundBitmap, src, dst, blurPaint) - canvas?.drawRect(dst, materialPaint) - canvas?.drawRect(dst, noisePaint) - canvas?.drawRect(dst, scrimPaint) + updateGlassRenderNode() + canvas?.drawRenderNode(renderNode) } fun resetGyroOffsets() { @@ -205,4 +200,31 @@ class GlassView(context: Context, attributeSet: AttributeSet) : FrameLayout(cont gyroYRotation = 0f invalidate() } + + private fun updateGlassRenderNode() { + if (renderNodeIsDirty) { + renderNode.setPosition(0, 0, getWidth(), getHeight()) + + val canvas = renderNode.beginRecording() + + src.set(-width / 2, -height / 2, width / 2, height / 2) + src.scale(1.0f + zoom) + val centerX = left + width / 2 + val centerY = top + height / 2 + val textureXOffset = (textureTranslationMultiplier * gyroYRotation).toInt() + val textureYOffset = (textureTranslationMultiplier * gyroXRotation).toInt() + src.set(src.left + centerX + textureXOffset, src.top + centerY + textureYOffset, + src.right + centerX + textureXOffset, src.bottom + centerY + textureYOffset) + + dst.set(0, 0, width, height) + canvas.drawBitmap(backgroundBitmap, src, dst, blurPaint) + canvas.drawRect(dst, materialPaint) + canvas.drawRect(dst, noisePaint) + canvas.drawRect(dst, scrimPaint) + + renderNode.endRecording() + + renderNodeIsDirty = false + } + } }
\ No newline at end of file |