Gil's profile at Stack Overflow Coding challenges at Project Euler

Draw 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 complete code sample can be found here.

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:

@Override
protected void onDraw(final Canvas canvas) {
    drawMask();
    drawBackground();
    canvas.drawBitmap(mBackgroundBitmap, 0.f, 0.f, null);
}

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:

// draw() calls onDraw() leading to stack overflow
@SuppressLint("WrongCall")
private void drawMask() {
    mMaskCanvas.drawColor(Color.BLACK, Mode.CLEAR);
    super.onDraw(mMaskCanvas);
}

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:

private void drawBackground() {
    mBackground.draw(mBackgroundCanvas);
    mBackgroundCanvas.drawBitmap(mMaskBitmap, 0.f, 0.f, mPaint);
}

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:

mPaint = new Paint();
mPaint.setXfermode(new PorterDuffXfermode(Mode.DST_OUT));

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:

carving(Sa, Sc, Da, Dc) = [Da * (1 - Sa), Dc * (1 - Sa)]
                              alpha           color

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.

The basics of bit manipulation

Talking with fellow developers, I realized that many feel confused when it comes down to bit manipulation. It is indeed something not used on a day-to-day basis, but nevertheless the Android framework relies on it heavily in for memory optimizations: when a boolean can do, a bit can do too. Examples are View and Window flags. This post sets out to demystify the basics of bit manipulation: afterwards it will feel no more difficult than using arrays.

First, before manipulating bits, we need containers for them: let's use plain Java ints for simplicity. For each int we have 32 positions to store bits. We will call 0 the least significant bit position and 31 the most significant one. Thinking bits as boolean elements in a container boils down bit problems to array problems: we only need to find out how to read and write such elements. We will write two methods, get() and set(), that given an int-container read and write respectively one of its elements.

static int get(int n, int i) {
    int shifted = n >> i;
    return shifted & 1;  // 0 or 1
}

The first line of get() divides n by 2^i (assuming n >= 0). If you think n in binary, this lops off the i least significant digits, filling in i zeros on the right. Example:

0b01001 >> 2 == 0b01 (rightmost 01 is lopped off)

The second line of get() is equivalent to shifted % 2 (assuming n >= 0). Think again shifted in binary: all digits of shifted will be 'and'-ed with a 0 except the least significant bit which will be 'and'-ed with a 1. This means everything will be zero except the least significant bit that will its value: we effectively conserved only the information about the parity of shifted. Now let's see the set() method.

static int set(int n, int i, int val) {
    return val == 0 ? clear(n, i) : set(n, i);
}

private static int clear(int n, int i) {
    int mask = ~(1 << i);
    return n & mask;
}

private static int set(int n, int i) {
    int mask = 1 << i;
    return n | mask;
}

We start splitting the general set() in two private methods, clear() and set(). The english espressions set and clear flag are standard jargon for setting a bit to 1 and clearing a bit to 0, and are used in Android too. First, consider the method clear(): we create a mask of the type 00..010..0 with a left shift, then we flip it to 11..101..1 by a bitwise negation. When we 'and' n with this mask we get a number which is the same as n, except having a 0 in the same place of the 0 in the mask: we cleared only one bit.

Now it is the turn of (private) set(): first, differently from arrays, ints are primitives, so we cannot hold a reference from outside and see the write: we need to create a new int, with the wanted bit set, and return it. Otherwise, the logic is similar to get(), except this time the mask looks like 00..010..0 and we 'or' it with n.

Espresso: Click on last item in AdapterView

In Espresso is quite easy to tap on the first element of an AdapterView, such as a ListView. This can be easily done calling DataIteraction.atPosition(0). Clicking on the last item though, is much more complicated. The last position is unknown to Espresso and extracting it stringing together a findViewById() and AdapterView.getCount() seems to defeat the purpose of using Espresso altogether.

Luckily there is a simple solution to this problem: let Espresso see the items in reversed order and then click on the first item. This can be accomplished with a custom AdapterViewProtocol.

AdapterViewProtocol is a simple interface that defines how Espresso interacts with AdapterViews. It supports four operations:

  1. Get all data items in the adapter
  2. Given a child view, return the corresponding data item
  3. Given a data item, ask whether it is displayed by some child view
  4. Force a data item to be displayed by a child view

For our use-case we just need to get the complete dataset and reverse it, then it can be used like this:

onData(instanceOf(MyAdapterItem.class))
    .atPosition(0)                                       
    .usingAdapterViewProtocol(new ReverseProtocol())
    .perform(click());

Here comes the code for the protocol. Note that the standard protocol is a private class so it can't be extended, so we delegate to it:

public class ReverseProdocol implements AdapterViewProtocol {
    private final AdapterViewProtocol delegate = standardProtocol();

    @Override
    public Iterable<AdaptedData> getDataInAdapterView(
            AdapterView<? extends Adapter> adapterView) {
        LinkedList<AdaptedData> result = new LinkedList<>();
        for (AdaptedData data : delegate.getDataInAdapterView(adapterView)) {
            result.addFirst(data);
        }
        return result;
    }

    @Override
    public Optional<AdaptedData> getDataRenderedByView(
            AdapterView<? extends Adapter> adapterView, View view) {
        return delegate.getDataRenderedByView(adapterView, view);
    }

    // Similarly delegate to the other two methods
    // ...
}