Example of usage of TransparentTextTextView

A library that draws transparent text in a TextView

Today we will make TextView render its text as transparent so that we can see underneath the TextView, for example the activity background. This technique uses the Porter’s and Duff’s transfer modes. A ready-to-use library along with its source code can be found here.

How to use the library

First, set up the dependency in gradle (from jcenter):

Then, just use it in xml:

Or programmatically:

The code is available on GitHub.

The technique

The high-level idea is quite simple: use a mask and carve out this mask from the TextView background. We can break this down in steps:

1. Draw the mask in a buffer
2. Draw the TextView’s background in another buffer
3. Carve out the mask from the background buffer
4. Draw the result on the final canvas

It is easy to translate this into code. Let’s start with the high-level
onDraw() method:

The method drawMask() implements step 1. The buffer is simply a bitmap, mMaskBitmap. In order to draw into this bitmap, we need a canvas linked to it, so that we have an api to issue draw calls: we call it mMaskCanvas. Then the method becomes:

Note how we leverage the rendering code of TextView calling super.onDraw(). We cannot call simply draw() because it calls internally onDraw() and so we would enter an infinite cycle that will throw a stack overflow exception. Next we draw the background, steps 2 and 3:

First we draw the drawable mBackground on a second buffer mBackgroundCanvas and then we carve the mask out of it. The magic of the carving is in the paint object:

The second line sets what is called a Porter and Duff transfer mode (Xferm is for transfer or transformation). The two computer scientists wanted to define an algebra for composing digital images. This means giving mathematical functions that, given two images input, return as output a third image, the result. In concrete terms, these functions represent operations such as overlapping, superimposing, blending, carving out, etc. If you want, you can read on for a brief explanation or zero in directly in their paper.

Let’s dive into more detail: each pixel can be represented by four values or channels: alpha, red, green and blue. A function takes two pixels, i.e. 8 values, and outputs a new pixel. Looping through corresponding pixels in two images, we can create a whole new third image. Of course, the images should have the same size but this is generally trivial to fix.

Let’s see now how to define the carving function. We consider two pixels, one from the first image, or ‘source’, and one from the second image, or ‘destination’. We split the first pixel in alpha Sa (source alpha) and color Sc (source color), a combination of the red, green and blue channels. Similarly, the destination pixel is split in Da and Dc. The carving function, or in Android DST_OUT, is:

As you can see, the value of the destination pixel is in the result if and only if the alpha of the source pixel is 0, which means completely transparent. In high-level terms, if we set the TextView background to the destination and the mask to the source, a pixel in the background will be drawn if and only if it matches a transparent pixel in the mask. Since the text is drawn in black on a transparent background in the mask, the final effect will be exactly what we want.