Android Bitmap Blending – Color Channels

I recently was doing some investigation into doing image blending in android, and came across the quite powerful built-in capabilities of Paint. I was wondering if it was possible to combine several grayscale images into a color image, using the grayscale images as the color channels. If you are familiar with Photoshop or other graphics-editing programs, you are probably familiar with the concept of color channels, and how they are combined to create a color image. For this example, we will re-create the following image from its separated color channels:

The original, full color image

The original image. Original by Steve Berger Photography via flikr

Separating the image

To start off with, I separated the image into its three color channel images: red, green, and blue.

red channel

Red

green channel

Green

blue channel

Blue

Quick primer on color channels

“But wait,” you ask, “why are these color channels in black and white?”. A good question. As you may or may not know, digital images are generally represented by and presented with three colors of light – red, green, and blue (RGB). Each pixel in the image is comprised of three values representing the intensity of the red, green, and blue ‘parts’ of the pixel. The grayscale color channel images simply separate the red, green, or blue part of each pixel’s color and present them separately. The red channel image is very light (almost white) on the parrot’s body, since it is very red, but the green and blue channel images are almost black in that area. Using the right techniques, we can re-combine these images into the original, full-color version.

The Code

In order to ‘build’ our full color image from the individual channels, we start with the following code

private Bitmap getARGBImage()
{
	BitmapFactory.Options opt = new BitmapFactory.Options();
	opt.inPreferredConfig = Config.ARGB_8888;

	Bitmap red = BitmapFactory.decodeResource(getResources(),
			R.drawable.red, opt);
	Bitmap green = BitmapFactory.decodeResource(getResources(),
			R.drawable.green, opt);
	Bitmap blue = BitmapFactory.decodeResource(getResources(),
			R.drawable.blue, opt);

	int width = red.getWidth();
	int height = red.getHeight();

	Bitmap result = Bitmap.createBitmap(width, height, Config.ARGB_8888);
	result.eraseColor(Color.BLACK);

	// We will do more here...

	return result;
}

In this initial setup code, we create a BitmapFactory.Options object, which we use to read in our channel images. We tell it that we want to ise a configuration of ARGB_8888, and that is important. Any time you want to do image blending or color manipulation, you need to make sure you are in ARGB_8888 mode, not in RGB_565 mode. Up until 2.3, android will usully default to RGB_565 mode unless you explicitly tell it to do otherwise in order to save memory. The next bit of the code just reads in our channel images and creates a new, blank (black-filled) image that we will use to build our full-color image. If you were to build and run a project where you put the returned Bitmap from this function into an ImageView (go ahead, I’ll wait…), you would just get a black image. Adding this next bit of code will change that (replace the comment above)

Paint redP = new Paint();
redP.setShader(new BitmapShader(red, TileMode.CLAMP, TileMode.CLAMP));
redP.setColorFilter(new PorterDuffColorFilter(Color.RED, Mode.MULTIPLY));
redP.setXfermode(new PorterDuffXfermode(Mode.SCREEN));

Paint greenP = new Paint();
greenP.setShader(new BitmapShader(green, TileMode.CLAMP,TileMode.CLAMP));
greenP.setColorFilter(new PorterDuffColorFilter(Color.GREEN,Mode.MULTIPLY));
greenP.setXfermode(new PorterDuffXfermode(Mode.SCREEN));

Paint blueP = new Paint();
blueP.setShader(new BitmapShader(blue, TileMode.CLAMP, TileMode.CLAMP));
blueP.setColorFilter(new PorterDuffColorFilter(Color.BLUE,Mode.MULTIPLY));
blueP.setXfermode(new PorterDuffXfermode(Mode.SCREEN));

Canvas c = new Canvas(result);
c.drawRect(0, 0, width, height, redP);
c.drawRect(0, 0, width, height, greenP);
c.drawRect(0, 0, width, height, blueP);

It looks a lot more complicated than it actually is. I’ll walk through the red paint, since the process is almost exactly the same for each channel. The first thing we do is create a Paint object to hold our blending information. Next, we set the Shader of the Paint to a BitmapShader, passing in our red channel image. Now our Paint will draw the image.

Paint redP = new Paint();
redP.setShader(new BitmapShader(red, TileMode.CLAMP, TileMode.CLAMP));

Next, we set the ColorFilter of the paint, and we use a PorterDuffColorFilter with a mode of Multiply. We also pass in Color.Red to the ColorFilter. What this bit of code accomplishes is to turn our black and white image into a black and red image. The Multiply algorithm multiplies the values of each pixel (in this case, the shader image and pure red), leaving black areas black and generally darkening other areas. Since the white areas of the channel image really mean increased levels of red in the full color image, we make that translation here.

redP.setColorFilter(new PorterDuffColorFilter(Color.RED, Mode.MULTIPLY));

Finally, we set the Xfermode of the Paint to a PorterDuffXfermode, using the Screen algorithm this time. With the Screen algorithm, black areas become transparent, and light areas are generally lightened.

redP.setXfermode(new PorterDuffXfermode(Mode.SCREEN));

There are lots of build-in capabilities you can get by using PorterDuffColorFilters and PorterDuffXfermodes, but the various modes and operations they perform is a bit more math than I want to cover here. But if you find yourself needing to do basic blending or alpha manipulation, check out the documentation.

The last two paints for green and blue are exactly the same as the red paint, except for the color used in the ColorFilter. Now, if you were to run, you would get the original, full-color image as output!

Adding Alpha

The last trick I will show you is how to take a grayscale image and use it as an alpha (transparency) channel for your image. The image we will use is below.

alpha channel

Alpha - grayscale and fully opaque


The white area will stay opaque and the black area will be transparent.

In order for us to use this as the alpha channel for our image, we need it to have transparency where we want it. Unfortunately, it is fully opaque right now. Fortunately, we can change that fairly quickly. Right now, we have the data we want in the alpha channel in the rgb channels. So, to move that data where we want it, all we have to do is perform a bitwise shift on the value for each pixel, moving the red value into the alpha value. We can do this because android stores ARGB values as AARRGGBB (hex), so the red channel data is offset by 8 bits from the alpha. Also, since in a (true) grayscale image, the values at any given pixel for red, green, or blue will be the same, we can ignore the green and blue channels. Also, in a grayscale image, white will have a value of 255, and black a value of zero. An alpha value of 255 means opaque and 0 means transparent. So all we have to do is pull out the int pixel values for our grayscale image, shift them left, and we now have our proper alpha channel.

Bitmap alpha = Bitmap.createBitmap(width, height, Config.ARGB_8888);
int[] alphaPix = new int[width * height];
alphaGray.getPixels(alphaPix, 0, width, 0, 0, width, height);

int count = width * height;
for (int i = 0; i < count; ++i)
{
	alphaPix[i] = alphaPix[i] << 8;
}
alpha.setPixels(alphaPix, 0, width, 0, 0, width, height);

Paint alphaP = new Paint();
alphaP.setAntiAlias(true);
alphaP.setXfermode(new PorterDuffXfermode(Mode.DST_IN));

c.drawBitmap(alpha, 0, 0, alphaP);

We use a PorterDuffXfermode of DST_IN because that takes the alpha channel from the image we give it, but the color from whatever was there before – just what we want. The final code looks like this:

private Bitmap getARGBImage()
{
	BitmapFactory.Options opt = new BitmapFactory.Options();
	opt.inPreferredConfig = Config.ARGB_8888;

	Bitmap red = BitmapFactory.decodeResource(getResources(),
			R.drawable.red, opt);
	Bitmap green = BitmapFactory.decodeResource(getResources(),
			R.drawable.green, opt);
	Bitmap blue = BitmapFactory.decodeResource(getResources(),
			R.drawable.blue, opt);
	Bitmap alphaGray = BitmapFactory.decodeResource(getResources(),
			R.drawable.alpha, opt);

	int width = red.getWidth();
	int height = red.getHeight();

	Bitmap result = Bitmap.createBitmap(width, height, Config.ARGB_8888);
	result.eraseColor(Color.BLACK);

	Paint redP = new Paint();
	redP.setShader(new BitmapShader(red, TileMode.CLAMP, TileMode.CLAMP));
	redP.setColorFilter(new PorterDuffColorFilter(Color.RED, Mode.MULTIPLY));
	redP.setXfermode(new PorterDuffXfermode(Mode.SCREEN));

	Paint greenP = new Paint();
	greenP.setShader(new BitmapShader(green, TileMode.CLAMP,TileMode.CLAMP));
	greenP.setColorFilter(new PorterDuffColorFilter(Color.GREEN,Mode.MULTIPLY));
	greenP.setXfermode(new PorterDuffXfermode(Mode.SCREEN));

	Paint blueP = new Paint();
	blueP.setShader(new BitmapShader(blue, TileMode.CLAMP, TileMode.CLAMP));
	blueP.setColorFilter(new PorterDuffColorFilter(Color.BLUE,Mode.MULTIPLY));
	blueP.setXfermode(new PorterDuffXfermode(Mode.SCREEN));

	Canvas c = new Canvas(result);
	c.drawRect(0, 0, width, height, redP);
	c.drawRect(0, 0, width, height, greenP);
	c.drawRect(0, 0, width, height, blueP);

	Bitmap alpha = Bitmap.createBitmap(width, height, Config.ARGB_8888);
	int[] alphaPix = new int[width * height];
	alphaGray.getPixels(alphaPix, 0, width, 0, 0, width, height);

	int count = width * height;
	for (int i = 0; i < count; ++i)
	{
		alphaPix[i] = alphaPix[i] << 8;
	}
	alpha.setPixels(alphaPix, 0, width, 0, 0, width, height);

	Paint alphaP = new Paint();
	alphaP.setAntiAlias(true);
	alphaP.setXfermode(new PorterDuffXfermode(Mode.DST_IN));

	c.drawBitmap(alpha, 0, 0, alphaP);

	red.recycle();
	green.recycle();
	blue.recycle();
	alphaGray.recycle();
	alpha.recycle();

	return result;
}

Now just run, and marvel at your sweet new macaw with a palm frond mohawk.

the final result, showing an image with transparency

The final result

11 Comments

  1. depp1983
    May 4, 2011 at 11:46 am |

    Hi
    Thats a great tutorial!
    Many thanks!

  2. Shubham
    June 1, 2011 at 2:21 am |

    hi, Thanks for this tutorial.

    I am using canvas in my Android App, And I drawing multi images over canvas. Here I just want to invalid only single view, but when I call invalid() all views are invalidated.
    Is it possible in Android to invalid only single view.

    Thanks

  3. Jim
    August 7, 2011 at 5:10 pm |

    Thank you for this tutorial. It gives me some direction, although I need to fill in many blanks in my knowledge.

    I am completely new to image processing and alpha blending but require these for a project I am working on. If you know of a good learning resource you would recommend for a newbie in this area, I would appreciate it.

    (I lived for a dozen years just down the road in Cocoa Beach too!)

  4. Lanie
    August 7, 2011 at 11:26 pm |

    elow…i had a project about detecting the color of tomato.. and i don’t known where to start can to give advice..

  5. thinzar
    May 26, 2012 at 2:33 am |

    Thanks for your tutorial. Many thanks

  6. thinzar
    May 26, 2012 at 2:34 am |

    Thanks for your tutorial. Many thanks. But I want to know how to convert original image to red,green,blue and alpha channel from code .Many Thanks.

  7. Umar
    June 17, 2012 at 3:24 am |

    Dear If i want to repeat an image then it gave me run time error…
    “paint.setShader(new BitmapShader(bitmap, null, TileMode.REPEAT));”

    what is the reason.. any one please guide me…
    thxx!

  8. Mayank
    September 17, 2012 at 8:50 am |

    Nice detailed turorial, Thanks :)

  9. October 18, 2012 at 7:20 pm |

    This really helped transform my app from using AvoidXfermode. Thank you.

  10. Nandkumar R
    October 19, 2012 at 1:34 am |

    I read and used your code and played with it is great .. thanks for the article . I enjoyed reading and i am sure you too enjoyed writing..
    most beautiful article i have read on android so far.. and hope to see some more beautiful things from your creation .. happy writing again nandkumar

  11. December 1, 2012 at 4:00 am |

    Wow that was strange. I just wrote an really long comment but after I clicked submit my comment
    didn’t appear. Grrrr… well I’m not writing all that over
    again. Anyhow, just wanted to say wonderful
    blog!

4 Trackbacks

  1. By Tutorials. : PowenKo on October 28, 2011 at 12:59 pm

    [...] Remove background [...]

  2. By PowenKo, Android Tutorials | PowenKo on November 24, 2011 at 8:27 pm

    [...] Remove background [...]

  3. By test1 | PowenKo on December 26, 2011 at 2:10 am

    [...] Remove background [...]

  4. By Bitmap color channel blending « Firefly's space on February 17, 2013 at 3:52 pm

    [...] Android Bitmap Blending – Color Channels Like this:LikeBe the first to like this. [...]


© 2010 Kevin Dion

Switch to our mobile site