Duotone is a hot design trend in web design. Probably this movement started getting more traction at the beginning of 2016 when Spotify applied it to their summary of 2015 year in music.
Basically, duotone is a black and white imagery, where the white color is replaced with one color, black with another, and greyscale is a calculation between this two. You can see the example in the header of this post.
The answer to the question if it works well with Material Design and Android guidelines is a personal matter. I wanted to play with it, do some experiments and see if I can come up with something interesting.
Here are some examples of popular screens from Android apps changed to have a little bit of this duotone feeling.
I found it works best when you want the image to blend better with your branding colors, or with the color important for a particular section. Consider a background image where instead of adding a scrim for text protection, you can choose text color with high contrast of image duotone.
Another place where it feels right is a category screen, here the color is more important than a picture and user will feel a connection with it itself, not with images, which can change with time.
I general, it is a counter pattern of applying colors to UI extracted from photography (with a use of a library like Palette). This one is particularly strong in apps like music players or any portfolio apps.
But anyways, like I said, it’s a personal matter.
If you feel like you want to take a chance on this style, this is where I hope it will start being interesting. Actually, this is what this post is all about. It’s relatively easy to achieve the wanted effect in Photoshop or even Sketch. The main question is, how to do it programmatically on Android?
I’m happy to give an answer — color matrix and color filter.
Building a duotone color filter
Assuming you want to change any image downloaded from the internet to a duotone one, you have to do this three steps:
- Convert image to black and white.
- Adjust contrast (optional).
- Switch black and white with customs colors.
Here is a ready method that does exactly these three things and returns ColorFilter you can simply attach to your ImageView.
But if you are interested in color matrixes, stay with me.
To change image rendering on Android, without changing the source bitmap can be done by applying ColorFilters. In this case, a child class — ColorMatrixColorFilter created with a result of multiplying three different color matrixes. Color matrix is a transformation of RGBA channels of every pixel’s value when applied.
It has a form of 4×5 matrix and to apply multiple operations on Android use postConcat or preConcat method.
First let’s desaturate the image, make a black and white version of it. To do it, change all RGB channels to have the same value (R’=G’=B’). Just apply this matrix:
R G B A O R' [0.21 0.72 0.07 0 0] G' [0.21 0.72 0.07 0 0] B' [0.21 0.72 0.07 0 0] A' [0 0 0 1 0]
Take a look that to achieve more pleasant feeling I’m not applying average values of source RGB (which will be 0.33) but I’m using different weights based on luminance brightness of a color.
Second (optional) — play with contrast. You can find a contrast color matrix everywhere on the internet:
float contrast; float scale = contrast + 1.0f; float translate = (-0.5f * scale + 0.5f) * 255.0f;
R G B A O R' [scale 0 0 0 translate] G' [0 scale 0 0 translate] B' [0 0 scale 0 translate] A' [0 0 0 1 0 ]
Let’s start with the last part of the transformation. The color to be used instead of white will be called colorWhite, and the RGB channels will be called r1, g1, b1. Following the same pattern, the color which replaces black will be called colorBlack, and RGB channels accordingly r2, g2, b2.
To change black areas, change the offset value to r2, g2, b2. To change white areas, the output of matrix equation (R’, G’, B’) needs to be r1, g1, b1. At this point the image is in grayscale, the input values for all channels are equal. The input value of R=G=B. The calculation can be simplified to use only one input channel, and replace others with zeros. The final result will be to multiply the value of red channel by the difference of colorWhite and colorBlack channels.
float r1r2 = (r1 - r2) / 255f; float g1g2 = (g1 - g2) / 255f; float b1b2 = (b1 - b2) / 255f;
R G B A O R' [r1r2 0 0 0 r2] G' [g1g2 0 0 0 g2] B' [b1b2 0 0 0 b2] A' [0 0 0 1 0 ]
Well, that’s it. Hope you will experiment with this, playing with color matrixes is always fun. Again, the whole source code can be found on GitHub.
If you liked this article, maybe you will also like the next one. Consider subscribing to the newsletter to be notified.