In the world of computer graphics, porter-duff refers to a
method of alpha compositing. The math is complicated and, to be
honest, over my head. Fortunately,
Google employs some folks on the Android SDK team who are more math savvy than
me! Recently, I discovered the
Paint object in the android.graphics package can do some pretty slick
compositing out of the box.

The example that follows demonstrates how to load two
images onto a canvas, one on top of the other.  We will punch a “hole” in the center of the topmost layer so
that the bottom image shows through. This technique can come in handy for adding a frame to an existing
image, as well as a number of other effects.

Feel free to follow along with the step-by-step tutorial
below or download and import the entire project directly into Eclipse.

1. Create a new Android project in Eclipse. Target SDK 13 (Honeycomb) or better.

2. To keep things simple, we will lock the
activity into portrait mode.

 

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.authorwjf.wanted"
android:versionCode="1"
android:versionName="1.0" >

<uses-sdk
android:minSdkVersion="13"
android:targetSdkVersion="18" ></uses>

<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.authorwjf.wanted.MainActivity"
android:label="@string/app_name"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" ></action>

<category android:name="android.intent.category.LAUNCHER" ></category>
</intent-filter>
</activity>
</application>

</manifest>

3. In the /res/layouts folder, let’s create a simple
linear layout that holds a single image view.

 

activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#00ffffff">

<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:background="#00ffffff"
android:id="@+id/img" ></ImageView>

</LinearLayout>

4. We need a couple
images. For the background image,
I took a quick snapshot of the only model that was still awake at the time I
was writing this: my cat (Figure A).

Figure A

 

 

For my foreground image, I used GIMP
to throw together a wanted poster (Figure B).

Figure B

 

Both images need to be placed in /res/drawable-xhdpi.

5. The last step is to code up the
MainActivity.java file in the /src folder. The function names should be self-explanatory. In the on create override, we load both
images from the resource folder, punch a hole in the foreground, and then
overlay it on the background.

 

package com.authorwjf.wanted;

import android.os.Bundle;
import android.view.Display;
import android.view.WindowManager;
import android.widget.ImageView;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;

public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ImageView iv = (ImageView) findViewById(R.id.img);
Bitmap background = BitmapFactory.decodeResource(getResources(), R.drawable.mug_shot);
Bitmap foreground = BitmapFactory.decodeResource(getResources(), R.drawable.poster);
foreground = punchAHoleInABitmap(foreground);
iv.setImageBitmap(combineTwoBitmaps(background,foreground));
}

private Bitmap punchAHoleInABitmap(Bitmap foreground) {
Bitmap bitmap = Bitmap.createBitmap(foreground.getWidth(), foreground.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
canvas.drawBitmap(foreground, 0, 0, paint);
paint.setAntiAlias(true);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
float radius = (float)(getScreenSize().x *.35);
float x = (float) ((getScreenSize().x*.5) + (radius * .5));
float y = (float) ((getScreenSize().y*.5) + (radius * .5));
canvas.drawCircle(x, y, radius, paint);
return bitmap;
}

private Bitmap combineTwoBitmaps(Bitmap background, Bitmap foreground) {
Bitmap combinedBitmap = Bitmap.createBitmap(background.getWidth(), background.getHeight(), background.getConfig());
Canvas canvas = new Canvas(combinedBitmap);
Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG);
canvas.drawBitmap(background, 0, 0, paint);
canvas.drawBitmap(foreground, 0, 0, paint);
return combinedBitmap;
}

private Point getScreenSize() {
WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Display display = window.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
return size;
}

}

Ready to see porter-duff in action? Build the APK and load it to a device
or emulator (Figure C).

Figure C

 

 

Okay, clearly there are better uses for the android.graphics
package, but try not to dismiss the power of the couple functions being exhibited
here. Combing multiple bitmaps
onto a single canvas, along with the ability to define transparent areas
dynamically, may prove a handy addition to your
programmer’s toolbox.