RenderEffect in Android 12

Learn how to use the new RenderEffect API in Android 12 to efficiently add custom styles to your views like blurs, saturation, offset, and more. By Diayan Siat.

Leave a rating/review
Download materials
Save for later
Share

With so many social media and photo-sharing apps, it’s quite common nowadays to apply filters to images before sharing them. Having the ability to do that within the Android OS makes things easier and more efficient. Prior to Android 12, the process was much more complicated, as you had to define RenderNodes by interacting with the Canvas and then apply the effects directly with transformations and algorithms.

In Android 12, however, Google introduced the RenderEffect API. This enables developers to effortlessly apply graphic effects such as blurs, color filters and more to Views.

In this tutorial, you’ll create an app that lets a user apply different graphic effects to a photo. During the process, you’ll:

  • Learn about the RenderEffect API.
  • Explore key concepts like RenderNodes, Display List and Canvas.
  • Learn how to use RenderEffect to implement simple graphic effects like blur, color filters and offset.
  • Use multiple effects to create chain effects.
  • Implement a full-screen blur.

If you’re new to Kotlin, check out our Kotlin introduction tutorial. If you’re completely new to Android development, familiarize yourself with our Beginning Android Development tutorials first.

Since Android 12 is still in beta at the time of writing, you can read on how to Set up an Android emulator with an Android 12 system image.

Note: This tutorial assumes you know the basics of Android development. You’ll need the latest stable version of Android Studio or a minimum version of 4.2 to run this project.

Getting Started

Download the project materials by clicking Download Materials at the top or bottom of this tutorial. Open the starter project in Android Studio.

The app, called Instafilter, allows you to apply graphic effects to a photo of an adorable dog. Right now, it can’t do much, but you’ll add functionality to perform several types of effects soon.

Build and run. You’ll see the following:

Instafilter app with dog photo and various checkboxes and slider bars

The main files in the project are:

  • MainActivity: The entry point of your app. It’s where you’ll write all your code.
  • activity_main.xml: Contains all of the app’s user interface (UI) design.

Adding Effects Before RenderEffect

Adding graphic effects to Views was possible before the introduction of the RenderEffect API. The two main ways to achieve this were:

  • Using third-party libraries like Glide and Blurry.
  • Using the RenderScript API, which involves writing your own algorithm.

Two issues you may have to deal with if using these methods are:

  • Potential random bugs caused by external libraries.
  • Poor performance on certain devices caused by CPU, GPU and memory limitations.

Luckily, RenderEffect solves these issues at the GPU level since the Android OS itself handles the rendering.

Understanding RenderEffect

RenderEffect is an intermediary used to render drawing commands with corresponding visual effects. It allows users to apply effects such as blurs, color filters, shader effects and more to Views and rendering hierarchies. This means the effects are added to Views when they’re drawn on the screen at the GPU level.

It does this by leveraging the existing rendering pipeline to minimize excess calculation, resulting in better performance.

Android uses RenderNodes to render all its Views on the GPU to improve performance. RenderEffect hooks into this implementation to apply effects on the hardware-accelerated rendering pipeline.

Rendering in Android

Android renders views on the GPU, and it uses RenderNodes to build hardware-accelerated rendering hierarchies to ensure better performance. Each RenderNode contains display lists as well as a set of properties that affect the rendering of the display list.

The display list is produced on the main thread and then synced with the RenderThread. The RenderThread issues the display list operations to the GPU, which then renders the view.

Note: The RenderThread only talks to the GPU.

A typical GPU Rendering graph of most apps prior to this API will show lots of inefficiencies:

A visual representation of GPU rendering on Android

To learn more about rendering in Android, check out this video from Google I/O.

Understanding RenderNode

Introduced in API 29, Android uses RenderNodes to build hardware-accelerated rendering hierarchies.

RenderNodes break down complex scenes, rendering content into smaller chunks that can individually be updated more cheaply. Instead of redrawing an entire scene, updating a portion of it requires updating the display list or properties of a small number of RenderNodes. Only when the content of a RenderNode has to be modified does its display list need to be rerecorded.

For example, when a user clicks an item in a RecyclerView, only that item’s display list is updated instead of rerecording the entire RecyclerView’s RenderNode. Another example is a TextView that has different paragraphs in it. Only an updated paragraph’s display list will be updated and the changes applied.

Understanding the Display List

A display list is a structure that stores the rendering information. This means that it stores rendering commands for Views. Graphic commands in Canvas — like drawBackground(), drawDrawable() and drawLine() — all end up as operations in a display list. So the display list is a compact way of representing those operations and the parameters to the operations.

Understanding the Canvas

A Canvas is a 2D drawing surface that provides drawing commands for drawing to a bitmap. The Canvas provides graphic commands/operations like drawBackground(), drawDrawable() and drawText() of views that end up as operations in a display list in the rendering pipeline.

Adding Some Awesome Effects

Enough of the boring theory, it’s now time to apply some effects to your cute dog photo. To add any effects using RenderEffect, you need to follow three main steps:

  1. Create an instance of RenderEffect.
  2. Implement RenderEffect’s factory static method.
  3. Then, set the effect to the appropriate view.

You’ll be working through a bunch of TODOs to complete this tutorial.

Adding Blur

To blur a view, you first need to implement the createBlurEffect static factory method, which RenderEffect provides. Do this by replacing the TODO() under //TODO 1: Add blur effect with:

return RenderEffect.createBlurEffect(radiusX, radiusY, shader)

The code above returns a blur RenderEffect object by implementing the createBlurEffect factory method, which takes three arguments. The first two arguments, radiusX and radiusY, specify the horizontal and vertical radii to which the blur effect should be rendered. Then, the third parameter indicates how to render TileMode at the edges.

Now, replace //TODO 4: Add blur effect with:

      //1
      if (isChecked) {
        binding.saturationCheck.isClickable = false
        binding.offsetEffectsCheck.isClickable = false
        binding.chainEffectCheck.isClickable = false 
        //2
        binding.blurSlider.isEnabled = true 
        //3
        val blurEffect = createBlurEffect(DEFAULT_BLUR, DEFAULT_BLUR, Shader.TileMode.MIRROR) 
        //4
        binding.imageView.setRenderEffect(blurEffect)
      } else {
        //5
        binding.imageView.setRenderEffect(null) 
        //6
        binding.blurSlider.isEnabled = false

        binding.saturationCheck.isClickable = true
        binding.offsetEffectsCheck.isClickable = true
        binding.chainEffectCheck.isClickable = true
      }

In the code above, here’s what’s happening:

  1. Determine if blurCheckBox is checked.
  2. If it’s checked, enable the blur slider.
  3. Create a blur object by passing in default values for the X and Y radii.
  4. Apply the blur effect to the view.
  5. If blurCheckBox isn’t checked, remove the blur from the view. Removing blur is as easy as setting the RenderEffect to null.
  6. Disable blur slider.
Note: In the code above, all checkboxes except Blur Fullscreen are disabled when Blur is checked, and enabled when it’s unchecked. Throughout the rest of the tutorial, this behavior is repeated for each effect enabled via the checked box.

Build and run. Check Blur, and you’ll see something like this:

Instafilter with Blur box checked and dog image blurred