Software Development

Punch a hole in a bitmap by using Android's porter-duff Xfer

Check out a handy algorithm for taking any bitmap on an Android user's phone and cutting out a circle or a rectangle.

 

android_091913.png
 

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

 

Android_cat_mugshot_031714.png
 

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

Figure B

Android_cat_wanted_031714.png
  

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

 

Android_cat_poster_031714.png
 

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.

 




About

William J Francis began programming computers at age eleven. Specializing in embedded and mobile platforms, he has more than 20 years of professional software engineering under his belt, including a four year stint in the US Army's Military Intellige...

0 comments