In Android Weekly issue # 297 there was a link to a library named ColorPickerPreference which I found interesting. One aspect of it was a picker which included a wheel but I was mildly disappointed to see that it did not include a mechanism of adjusting the brightness value, and was implemented using a bitmap resource which was only provided at a single density, meaning that it will not necessarily scale well on different devices. In this short series we’ll look at how we can actually render a wheel dynamically.

In the previous article we created our RenderScript script for generating our colour wheel graphic, but now we need to actually call it from the BitmapGenerator class that we created in Part 1. This requires a little effort because we have to we are having to marshall things to and from the native environment in which the RenderScript engine will run.

Before we get stuck in to that, there is a utility class that will help to simplify our code:

This has an immutable, non-null property named value along with a private, mutable, nullable property named _backing. Whenever value is retrieved, we first check if _backing is null and if so call a creator() function (that was passed to the constructor) in order to create a new instance and store it in _backing. For subsequent gets the value from _backing will be returned. On the face of it, this looks like we could just use a lazy delegate, however it is the clear() function which adds the extra functionality that we need. When it is called, it will invoke a cleanup() function (if one was supplied in the constructor), and then set _backing to null. So the next retrieval of value will cause a new instance of the object to be created and store in _backing.

Hopefully, a quick example will show why this is useful:

Here we have a property named generated which is an AutoCreate instance. Initially the _backing property is null, but when we retrieve generated.value a new bitmap of the correct size will be generated because the lambda on the AutoCreate constructor will be called: Bitmap.createBitmap(size.width, size.height, config). When the size changes (in setSize()) we call generated.clear() which will invoke recycle() on the bitmap (the argument on the AutoCreate constructor), and the _backing property will be set to null. When when we retrieve generated.value next, then a new bitmap will be created with the new size.

We also create AutoCreate instances for the Allocation which is used to pass the bitmap data in to the RenderScript kernel, and the script instance itself:

generatedAllocation is created from the bitmap dimensions that are retrieved from bitmap.vale (which is our AutoCreate instance that we just looked at).

Currently this will not run because the creator lambda for colourWheelScript requires a variable named renderscript. This is actually the RenderScript context that the script will be run within and it is fairly costly to setup and tear down, so we will keep a single instance alive for the lifetime of the BitmapCreator instance to avoid any unnecessary overhead:

The creation of the RenderScript context actually begins as soon as the BitmapGenerator instance is created. This is done through a coroutine wish runs on CommonPool (i.e. a background task) and the Deferred object is stored as a val named rsCreation. Once the RenderScript context had been created it is stored to the property named _renderscript.

The rsCreation instance is quite useful because is allows us to wait for the background task to complete if it hasn’t already. The generate() function shows this:

Depending on how soon the generate() function is called after the BitmapGenerator instance has been created then the RenderScript context creation may have completed, or it may still be in progress. By calling await() from a background task means that when it returns, the rsCreation task will be complete. This helps us to avoid a race condition when we try and use the RenderScript context before the background task to create it has completed.

So once we the following line, we know that RenderScript is ready to go. We first get the Bitmap we want to draw to (which may dynamically create it thanks to our AutoCreate class), and then perform the drawing operation – we’ll look at this in detail in a moment. Once the draw is complete then we make a callback on the UI thread to an observer to indicate that the Bitmap has changed – this observer is our custom View, and will update the image.

The final piece is the draw() function:

generatedAllocation is an AutoCreate instance which will create the Allocation on demand. The Allocation is what gets passed to our script as the second argument to the colourWheel() function that we looked at in the previous post. We then copy the contents of the Bitmap in to the Allocation instance.

Next we have colourWheelScript which is am AutoCreate instance. Once again this will be created on demand but unlike the Bitmap and Allocation objects this will not need to be re-created if the size of the bitmap changes. However, like the RenderScript context, it can be quite expensive to keep creating and destroying each time we need it, so we use an AutoCreate object to enable us to create it as we need it, but also properly clean it up when we’re done with it.

The ScriptC_ColourWheel class is a Java wrapper around our RenderScript script that is generated by the RenderScript compiler (you can take a look at the generated code in app/build/generated/source/rs after building the project), and this means we don’t have to directly call native code from within our Java / Kotlin – we just use the wrapper. The invoke_colourWheel() function is a wrapper around the colourWheel() function in our RenderScript script, and takes the same three arguments: the first is a reference to the script object itself, the second is our allocation, and the third is our brightness value in the range 0.0-1.0. This invokes the kernel for each pixel in the bitmap which was copied to the allocation, and our colour wheel graphic is created.

Finally we need to copy the contents of the Allocation back to the bitmap and we’re done.

So if we run this we see the following behaviour (there are some compression artefacts on the video, but the real things is blemish-free, I promise!):

So we can instantly see a vast improvement upon the two seconds that it was taking to render each brightness change by performing this in the JVM, but just how much? I did some benchmarking and on the Pixel XL it was taking just over 40ms to generate an 896×896 image. So while this is still not fast enough for buttery smooth 60fps animations, it is actually more than adequate to update in a timely manner when the user changes the value of the brightness slider, so I’m happy that this is now good enough.

The source code for this article is available here.

© , Mark Allison. All rights reserved.

Colour Wheel – Part 3 by Styling Android is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
Permissions beyond the scope of this license may be available at

Source link
thanks you RSS link


Please enter your comment!
Please enter your name here