1 /* 2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.graphics; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.graphics.Shader.TileMode; 22 23 import libcore.util.NativeAllocationRegistry; 24 25 /** 26 * Intermediate rendering step used to render drawing commands with a corresponding 27 * visual effect. A {@link RenderEffect} can be configured on a {@link RenderNode} through 28 * {@link RenderNode#setRenderEffect(RenderEffect)} and will be applied when drawn through 29 * {@link Canvas#drawRenderNode(RenderNode)}. 30 * Additionally a {@link RenderEffect} can be applied to a View's backing RenderNode through 31 * {@link android.view.View#setRenderEffect(RenderEffect)} 32 */ 33 @android.ravenwood.annotation.RavenwoodKeepWholeClass 34 public final class RenderEffect { 35 36 private static class RenderEffectHolder { 37 public static final NativeAllocationRegistry RENDER_EFFECT_REGISTRY = 38 NativeAllocationRegistry.createMalloced( 39 RenderEffect.class.getClassLoader(), nativeGetFinalizer()); 40 } 41 42 /** 43 * Create a {@link RenderEffect} instance that will offset the drawing content 44 * by the provided x and y offset. 45 * @param offsetX offset along the x axis in pixels 46 * @param offsetY offset along the y axis in pixels 47 */ 48 @NonNull createOffsetEffect(float offsetX, float offsetY)49 public static RenderEffect createOffsetEffect(float offsetX, float offsetY) { 50 return new RenderEffect(nativeCreateOffsetEffect(offsetX, offsetY, 0)); 51 } 52 53 /** 54 * Create a {@link RenderEffect} instance with the provided x and y offset 55 * @param offsetX offset along the x axis in pixels 56 * @param offsetY offset along the y axis in pixels 57 * @param input target RenderEffect used to render in the offset coordinates. 58 */ 59 @NonNull createOffsetEffect( float offsetX, float offsetY, @NonNull RenderEffect input )60 public static RenderEffect createOffsetEffect( 61 float offsetX, 62 float offsetY, 63 @NonNull RenderEffect input 64 ) { 65 return new RenderEffect(nativeCreateOffsetEffect( 66 offsetX, 67 offsetY, 68 input.getNativeInstance() 69 ) 70 ); 71 } 72 73 /** 74 * Create a {@link RenderEffect} that blurs the contents of the optional input RenderEffect 75 * with the specified radius along the x and y axis. If no input RenderEffect is provided 76 * then all drawing commands issued with a {@link android.graphics.RenderNode} that this 77 * RenderEffect is installed in will be blurred 78 * @param radiusX Radius of blur along the X axis 79 * @param radiusY Radius of blur along the Y axis 80 * @param inputEffect Input RenderEffect that provides the content to be blurred, can be null 81 * to indicate that the drawing commands on the RenderNode are to be 82 * blurred instead of the input RenderEffect 83 * @param edgeTreatment Policy for how to blur content near edges of the blur kernel 84 */ 85 @NonNull createBlurEffect( float radiusX, float radiusY, @NonNull RenderEffect inputEffect, @NonNull TileMode edgeTreatment )86 public static RenderEffect createBlurEffect( 87 float radiusX, 88 float radiusY, 89 @NonNull RenderEffect inputEffect, 90 @NonNull TileMode edgeTreatment 91 ) { 92 long nativeInputEffect = inputEffect != null ? inputEffect.mNativeRenderEffect : 0; 93 return new RenderEffect( 94 nativeCreateBlurEffect( 95 radiusX, 96 radiusY, 97 nativeInputEffect, 98 edgeTreatment.nativeInt 99 ) 100 ); 101 } 102 103 /** 104 * Create a {@link RenderEffect} that blurs the contents of the 105 * {@link android.graphics.RenderNode} that this RenderEffect is installed on with the 106 * specified radius along the x and y axis. 107 * @param radiusX Radius of blur along the X axis 108 * @param radiusY Radius of blur along the Y axis 109 * @param edgeTreatment Policy for how to blur content near edges of the blur kernel 110 */ 111 @NonNull createBlurEffect( float radiusX, float radiusY, @NonNull TileMode edgeTreatment )112 public static RenderEffect createBlurEffect( 113 float radiusX, 114 float radiusY, 115 @NonNull TileMode edgeTreatment 116 ) { 117 return new RenderEffect( 118 nativeCreateBlurEffect( 119 radiusX, 120 radiusY, 121 0, 122 edgeTreatment.nativeInt 123 ) 124 ); 125 } 126 127 /** 128 * Create a {@link RenderEffect} that renders the contents of the input {@link Bitmap}. 129 * This is useful to create an input for other {@link RenderEffect} types such as 130 * {@link RenderEffect#createBlurEffect(float, float, RenderEffect, TileMode)} or 131 * {@link RenderEffect#createColorFilterEffect(ColorFilter, RenderEffect)} 132 * 133 * @param bitmap The source bitmap to be rendered by the created {@link RenderEffect} 134 */ 135 @NonNull createBitmapEffect(@onNull Bitmap bitmap)136 public static RenderEffect createBitmapEffect(@NonNull Bitmap bitmap) { 137 float right = bitmap.getWidth(); 138 float bottom = bitmap.getHeight(); 139 return new RenderEffect( 140 nativeCreateBitmapEffect( 141 bitmap.getNativeInstance(), 142 0f, 143 0f, 144 right, 145 bottom, 146 0f, 147 0f, 148 right, 149 bottom 150 ) 151 ); 152 } 153 154 /** 155 * Create a {@link RenderEffect} that renders the contents of the input {@link Bitmap}. 156 * This is useful to create an input for other {@link RenderEffect} types such as 157 * {@link RenderEffect#createBlurEffect(float, float, RenderEffect, TileMode)} or 158 * {@link RenderEffect#createColorFilterEffect(ColorFilter, RenderEffect)} 159 * 160 * @param bitmap The source bitmap to be rendered by the created {@link RenderEffect} 161 * @param src Optional subset of the bitmap to be part of the rendered output. If null 162 * is provided, the entire bitmap bounds are used. 163 * @param dst Bounds of the destination which the bitmap is translated and scaled to be 164 * drawn into within the bounds of the {@link RenderNode} this RenderEffect is 165 * installed on 166 */ 167 @NonNull createBitmapEffect( @onNull Bitmap bitmap, @Nullable Rect src, @NonNull Rect dst )168 public static RenderEffect createBitmapEffect( 169 @NonNull Bitmap bitmap, 170 @Nullable Rect src, 171 @NonNull Rect dst 172 ) { 173 long bitmapHandle = bitmap.getNativeInstance(); 174 int left = src == null ? 0 : src.left; 175 int top = src == null ? 0 : src.top; 176 int right = src == null ? bitmap.getWidth() : src.right; 177 int bottom = src == null ? bitmap.getHeight() : src.bottom; 178 return new RenderEffect( 179 nativeCreateBitmapEffect( 180 bitmapHandle, 181 left, 182 top, 183 right, 184 bottom, 185 dst.left, 186 dst.top, 187 dst.right, 188 dst.bottom 189 ) 190 ); 191 } 192 193 /** 194 * Create a {@link RenderEffect} that applies the color filter to the provided RenderEffect 195 * 196 * @param colorFilter ColorFilter applied to the content in the input RenderEffect 197 * @param renderEffect Source to be transformed by the specified {@link ColorFilter} 198 */ 199 @NonNull createColorFilterEffect( @onNull ColorFilter colorFilter, @NonNull RenderEffect renderEffect )200 public static RenderEffect createColorFilterEffect( 201 @NonNull ColorFilter colorFilter, 202 @NonNull RenderEffect renderEffect 203 ) { 204 return new RenderEffect( 205 nativeCreateColorFilterEffect( 206 colorFilter.getNativeInstance(), 207 renderEffect.getNativeInstance() 208 ) 209 ); 210 } 211 212 /** 213 * Create a {@link RenderEffect} that applies the color filter to the contents of the 214 * {@link android.graphics.RenderNode} that this RenderEffect is installed on 215 * @param colorFilter ColorFilter applied to the content in the input RenderEffect 216 */ 217 @NonNull createColorFilterEffect(@onNull ColorFilter colorFilter)218 public static RenderEffect createColorFilterEffect(@NonNull ColorFilter colorFilter) { 219 return new RenderEffect( 220 nativeCreateColorFilterEffect( 221 colorFilter.getNativeInstance(), 222 0 223 ) 224 ); 225 } 226 227 /** 228 * Create a {@link RenderEffect} that is a composition of 2 other {@link RenderEffect} instances 229 * combined by the specified {@link BlendMode} 230 * 231 * @param dst The Dst pixels used in blending 232 * @param src The Src pixels used in blending 233 * @param blendMode The {@link BlendMode} to be used to combine colors from the two 234 * {@link RenderEffect}s 235 */ 236 @NonNull createBlendModeEffect( @onNull RenderEffect dst, @NonNull RenderEffect src, @NonNull BlendMode blendMode )237 public static RenderEffect createBlendModeEffect( 238 @NonNull RenderEffect dst, 239 @NonNull RenderEffect src, 240 @NonNull BlendMode blendMode 241 ) { 242 return new RenderEffect( 243 nativeCreateBlendModeEffect( 244 dst.getNativeInstance(), 245 src.getNativeInstance(), 246 blendMode.getXfermode().porterDuffMode 247 ) 248 ); 249 } 250 251 /** 252 * Create a {@link RenderEffect} that composes 'inner' with 'outer', such that the results of 253 * 'inner' are treated as the source bitmap passed to 'outer', i.e. 254 * 255 * <pre> 256 * {@code 257 * result = outer(inner(source)) 258 * } 259 * </pre> 260 * 261 * Consumers should favor explicit chaining of {@link RenderEffect} instances at creation time 262 * rather than using chain effect. Chain effects are useful for situations where the input or 263 * output are provided from elsewhere and the input or output {@link RenderEffect} need to be 264 * changed. 265 * 266 * @param outer {@link RenderEffect} that consumes the output of {@param inner} as its input 267 * @param inner {@link RenderEffect} that is consumed as input by {@param outer} 268 */ 269 @NonNull createChainEffect( @onNull RenderEffect outer, @NonNull RenderEffect inner )270 public static RenderEffect createChainEffect( 271 @NonNull RenderEffect outer, 272 @NonNull RenderEffect inner 273 ) { 274 return new RenderEffect( 275 nativeCreateChainEffect( 276 outer.getNativeInstance(), 277 inner.getNativeInstance() 278 ) 279 ); 280 } 281 282 /** 283 * Create a {@link RenderEffect} that renders the contents of the input {@link Shader}. 284 * This is useful to create an input for other {@link RenderEffect} types such as 285 * {@link RenderEffect#createBlurEffect(float, float, RenderEffect, TileMode)} 286 * {@link RenderEffect#createBlurEffect(float, float, RenderEffect, TileMode)} or 287 * {@link RenderEffect#createColorFilterEffect(ColorFilter, RenderEffect)}. 288 */ 289 @NonNull createShaderEffect(@onNull Shader shader)290 public static RenderEffect createShaderEffect(@NonNull Shader shader) { 291 return new RenderEffect(nativeCreateShaderEffect(shader.getNativeInstance())); 292 } 293 294 /** 295 * Create a {@link RenderEffect} that executes the provided {@link RuntimeShader} and passes 296 * the contents of the {@link android.graphics.RenderNode} that this RenderEffect is installed 297 * on as an input to the shader. 298 * @param shader the runtime shader that will bind the inputShaderName to the RenderEffect input 299 * @param uniformShaderName the uniform name defined in the RuntimeShader's program to which 300 * the contents of the RenderNode will be bound 301 */ 302 @NonNull createRuntimeShaderEffect( @onNull RuntimeShader shader, @NonNull String uniformShaderName)303 public static RenderEffect createRuntimeShaderEffect( 304 @NonNull RuntimeShader shader, @NonNull String uniformShaderName) { 305 return new RenderEffect( 306 nativeCreateRuntimeShaderEffect(shader.getNativeShaderBuilder(), 307 uniformShaderName)); 308 } 309 310 private final long mNativeRenderEffect; 311 312 /* only constructed from static factory methods */ RenderEffect(long nativeRenderEffect)313 private RenderEffect(long nativeRenderEffect) { 314 mNativeRenderEffect = nativeRenderEffect; 315 RenderEffectHolder.RENDER_EFFECT_REGISTRY.registerNativeAllocation( 316 this, mNativeRenderEffect); 317 } 318 319 /** 320 * Obtain the pointer to the underlying RenderEffect to be configured 321 * on a RenderNode object via {@link RenderNode#setRenderEffect(RenderEffect)} 322 */ getNativeInstance()323 /* package */ long getNativeInstance() { 324 return mNativeRenderEffect; 325 } 326 nativeCreateOffsetEffect( float offsetX, float offsetY, long nativeInput)327 private static native long nativeCreateOffsetEffect( 328 float offsetX, float offsetY, long nativeInput); nativeCreateBlurEffect( float radiusX, float radiusY, long nativeInput, int edgeTreatment)329 private static native long nativeCreateBlurEffect( 330 float radiusX, float radiusY, long nativeInput, int edgeTreatment); nativeCreateBitmapEffect( long bitmapHandle, float srcLeft, float srcTop, float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight, float dstBottom)331 private static native long nativeCreateBitmapEffect( 332 long bitmapHandle, float srcLeft, float srcTop, float srcRight, float srcBottom, 333 float dstLeft, float dstTop, float dstRight, float dstBottom); nativeCreateColorFilterEffect(long colorFilter, long nativeInput)334 private static native long nativeCreateColorFilterEffect(long colorFilter, long nativeInput); nativeCreateBlendModeEffect(long dst, long src, int blendmode)335 private static native long nativeCreateBlendModeEffect(long dst, long src, int blendmode); nativeCreateChainEffect(long outer, long inner)336 private static native long nativeCreateChainEffect(long outer, long inner); nativeCreateShaderEffect(long shader)337 private static native long nativeCreateShaderEffect(long shader); nativeCreateRuntimeShaderEffect( long shaderBuilder, String inputShaderName)338 private static native long nativeCreateRuntimeShaderEffect( 339 long shaderBuilder, String inputShaderName); nativeGetFinalizer()340 private static native long nativeGetFinalizer(); 341 } 342