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