Blurring the Lines

Android RenderEffects #1: the blur effect

Chet Haase
Android Developers
6 min readNov 14, 2022

--

This article (and the next one) is essentially a written version of my part of a video that Sumir Kataria and I recorded for this year’s Android Developer Summit:

Here’s the video version of this article (and more!)

When I set about learning how to use the Blur effect, it wasn’t obvious to me how to incorporate it into an overall application, so I thought it might help to clarify what it is for, where it fits in Android’s overall rendering toolbox, and how to actually create and use blur effects.

First, A Word about Drawing on Android

At the most fundamental level, visual elements on Android (like Buttons, text, and other UI or custom elements) are generally drawn by calls to the Canvas APIs, like drawLine(), drawText(), drawBitmap(), and so on. Your code may not call these methods directly unless you are drawing objects in a custom view, but they are called on your behalf when the UI components in your application are drawn.

Most of these drawing commands provide three pieces of information to the rendering system. You can think of these as the what, where, and how information for drawing, where what is the operation itself (the “primitive” to draw), where is the placement (location and size) of the object, and how is the set of drawing attributes. It is these attributes that concern us today, because that’s where blur comes in.

Each drawing primitive tells the render what, where, and how to draw the object

Drawing attributes are provided by aPaint object, which has default attributes that can be changed by the caller (your app or the UI system on your behalf). Most of the Paint APIs are relatively simple and obvious, like setColor() for the color that primitives should be drawn, setStyle() for whether the object should be filled or “stroked” (for an object’s outline), plus a veritable plethora of text attributes that I won’t go into here.

There are also more powerful and complex attributes that you can assign to a Paint object. These include ColorFilter subclasses (like my personal favorite, ColorMatrixColorFilter, which deserves an award for the LongestAndMostRepetitiveClassRepetitiveNameEver) for altering the colors of primitives, and shaders. Shaders include various gradient objects plus bitmaps and provide source colors from which a drawing operation samples to provide the resulting colors of the geometry that is drawn. Using shaders allows you to, for example, fill a rectangle with a linear or circular gradient, or to use the values from a bitmap to fill or stroke that rectangle instead. (Teaser: There is a new ‘shader’ API in Android 13 which allows you to go way beyond these effects; stay tuned for the next article in this series for more on that).

And Then There is RenderEffect

All of the above APIs allow you to set up attributes for individual draw*() calls, when you want to affect individual drawing operations (such as drawing a line inside a custom view). But what if you want to use attributes for all drawing operations in a View? For example, what if you want to color-tint a button (which consists of several separate drawing operations internally), or apply a shader on a View?

This is where RenderEffect comes in. RenderEffect bundles together one or more shaders and applies them to an overall View— or to a RenderNode (the underlying rendering mechanism for Views)— to simplify things by having the renderer apply those effects to an entire View. You can use a single RenderEffect or chain several together to apply multiple effects.

When RenderEffect was introduced, in API level 31, it provided ways of collecting existing attribute effects like ColorFilter , Bitmap, and Shader into effects, as well as chaining them, with factory methods like these:

But RenderEffect also introduced a brand new drawing effect along the way: Blur.

Blurred Vision

In addition to objects that encapsulate existing Paint attributes, RenderEffect also introduced a new effect which enables easy blurring of View or RenderNode contents:

Using these methods, you can now easily create a blur effect on a View (or, using the second overload above, another RenderEffect) to blur the entire contents as they are rendered. Think of it as sending the original contents of the view through a filter which blurs it along the way. That is essentially what’s happening, though the actual way it accomplishes this is by rendering the contents offscreen, applying the blur, and then copying the blurred result to the original destination.

The radius parameters determine how large the blur is (how many pixels outside of each pixel in the source input are combined in each direction), and the TileMode determines what happens on the edges of the blur. This last parameter is necessary because a blur operates on pixels outside of the pixel being calculated, so it needs to know what to do when those other pixels lie outside of the input content.

Once you have created the blur, you can set it on a View by calling:

You could similarly set it on a RenderNode:

… And that’s it! Once you’ve set the RenderEffect, any drawing that happens in that object will use the effect that you set on it. If you want to change the attributes of the effect (such as the blur radius), you re-create and set it again, as above.

Example

I wrote a simple app to see how blurs could be used in a UI. Specifically, I wanted to demonstrate how a blur can be used to help “pop” the foreground content out from the background, much as camera focus helps isolate the picture subject from the background.

First, I got blurs working on the background. In this case, that background is a photo gallery; a layout which contains a set of picture thumbnails.

A layout containing picture thumbnails. Clicking on a picture shows an enlarged view of it.

Clicking on one of the pictures enlarges it and show a caption for that photo. Wouldn’t it be nice if we could blur the background so the rest of the pictures didn’t create too much visual noise when we are trying to focus on the foreground picture and its caption?

I added a SeekBar to the app to allow changing the blur dynamically. This isn’t something I’d need in a finished app (just pick a blur that works and stick with it; the user isn’t going to want to modify that kind of thing, so keep the UI simple). But I wanted to use it initially to play with different blurs, and to show how to recreate them with different parameters. seekBar passes in a value from 0 to 50 (the min/max values on the SeekBar UI component).

updateEffect() uses the progress value for a blur radius (I use the same value for both x and y). Important note: a value of 0 is used to indicate that the blur should be removed, which is done by setting RenderEffect to null. It turns out that asking for a 0-radius blur (mathematically equivalent to not blurring) will, er, crash. 0 is apparently not a value that the system expects when asking for a blur effect. This is poorly documented (we’re fixing that…), so I thought you might want to know in case you try this at home. I know I wondered what was happening when my initial code crashed trying to handle that value.

updateEffect() creates the RenderEffect (or nulls it out to remove it), with the progress value for the radius, then sets it on the picture layout, and voilà, we have blur:

Picture gallery with blur RenderEffect applied to the container

Now that we have blur working, it’s time to work on the zoomed-in picture on top. That effect is handled via the new AGSL RuntimeShader feature in Android 13, and is described in Part 2 of this series: AGSL: Made in the Shade(r).

--

--

Chet Haase
Android Developers

Past: Android development Present: Student, comedy writer Future: ???