Understanding the ColorMatrixFilter class!

I haven’t seen many people explore the ColorMatrixFilter class, probably because it appears daunting. I’m happy to report that I comprehend the mysteries of ColorMatrixFilter now, and I thought I’d share a technique we used in our new game, Space Kitty.

Before we jump into ColorMatrixFilter, let’s ponder a different, and more commonly-used, class first: ColorTransform . Using a ColorTransform, you can “tint” any DisplayObject with color. But there’s something sneaky you can do, as well — Notice that ColorTransform gives you two parameters for each of the four color channels: Red, Green, Blue, and Alpha. One parameter is the “multiplier,” which takes the input value and (predictably) multiplies this value by a constant. The other is the “offset,” which works like addition: adding (or subtracting) another constant from the value. These two functions are applied to all four color channels of each individual pixel. That’s how the magic works.

Allow me to spend a few paragraphs making sure this idea is lodged in your head. Imagine that you have a DisplayObject; one of its pixels has a red value of 128 (out of a range of 0-255. So this red is at a medium level.) We don’t care about any of the other color data right now; let’s just focus on the red.

Let’s say you have applied a ColorTransform to this DisplayObject. The ColorTransform has a redMultiplier of 0.5, so our working red value is now 64. (The red value is now somewhat darker.)

But the ColorTransform also has a redOffset of 128, which is then added to the pixel’s red value. The final red value is 192, so the output color has a fairly bright red component.

Keep in mind that we aren’t considering the green, blue, and alpha channels in the above steps. We’re just altering the red value, at this point.

Now for the first round of magic: There’s a slick way to colorize a black-and-white image, using a ColorTransform to “map” its black and white values to two destination colors of your choosing. Shades of grey will even be mapped to the spectrum of colors between your chosen destination colors. Here’s a quick-and-dirty .swf showing this in action. Every time you click the mouse, the application selects two new destination colors (you can see these values in your trace log) and creates a new ColorTransform that maps the black pixels to one destination color, and the white pixels to the other color. The grey pixels create a smooth blend between the two extremes.

How do we accomplish this? By using simple math. Suppose one of your destination colors has a medium red value (128). Mapping all the black pixels to this red value is the easiest thing in the world. Since black pixels have a red value of 0 (because they have no brightness whatsoever), we can set our redOffset to 128. …

Stepping through the math: The ColorTransform is about to crunch a black pixel. Since the red value of the pixel is already 0, it doesn’t matter what our redMultiplier is. Any number multiplied by zero is 0. Then, our redOffset (128) is added to the working red value.

Imagine doing this with green and blue, as well. Congratulations, you’ve just mapped your black pixels to any specific color you want.

Once you understand that concept, mapping the white pixels to a different destination color is easy. Suppose our second destination color has a red value of 192. Incoming white pixels will always have the maximum red value, which is 255, right?

Since we know our redOffset will add 128 to the final red value, we need to make up the difference between 192 and 128 (64!), using the redMultiplier alone. And since we know our incoming white pixels have red values of 255, we can set our redMultiplier to … 0.25 . (This is an approximation, for the sake of the example. 64 divided by 255 is 0.2509803 .)

Imagine that the ColorTransform is processing a white pixel; the incoming red value is 255, yes? Our redMultiplier acts first, and multiplies this value by 0.25. The working red value is now (approximately) 64. Then, the redOffset steps in, adds 128, and the final red value becomes the desired 192.

Again, the process for the green and blue channels is similar. Here’s some AS2 code that uses this technique, hopefully you can guess that a “ColorObj” is a little object that holds red, green and blue values, ranging from 0 to 255:


import flash.geom.ColorTransform;
import com.zacharcher.color.ColorObj;

class com.zacharcher.color.ColorGradient
{
	public var transform:ColorTransform;

	function ColorGradient( black:ColorObj, white:ColorObj ) {
		transform = new ColorTransform(
			(white.r - black.r) / 256,
			(white.g - black.g) / 256,
			(white.b - black.b) / 256,
			1.0,
			black.r,
			black.g,
			black.b,
			0.0
		);
	}
}

Ok. Let me wrap this up, because it’s 4 in the morning here in Portland! For Space Kitty, we wanted each cat to use a different color scheme, but without going to the trouble of creating dozens of MovieClips (one for each cat). We discovered that we can use ColorMatrixFilter to perform the same trick as with ColorTransform, but it’s more sophisticated because we can map to 4 (four!) destination colors at once.

First, Miles drew a cat using four specific colors: black, pure red (#ff0000), pure green (#00ff00), and pure blue (#0000ff):

RGB KITTY

Then I wrote the code. Here’s how ColorMatrixFilter crunches one pixel value, say we start by examining the red component: The value of the red channel is multiplied by a red multiplier (just like ColorTransform), and also three other multipliers (for green, blue and alpha). The four resulting values are added to four separate output “pools”. One pool will become the final red value; one will be the final green value; etc.

Then the incoming green channel is examined, and four different multipliers act on it, adding new values to the four pools. Then the incoming blue channel is evaluated in the same way, and finally the alpha channel is evaluated.

As a finishing touch, there are four additional values which are added to the pools, much like the offsets of the ColorTransform. All that math produces a simple result: four “pool” values, representing the new red, green, blue, and alpha values for one pixel. That’s how ColorMatrixFilter works.

And it lets us map four colors, not just two! The technique is just like our ColorTransform technique, except that we don’t need to handle the red, green, and blue of the destination color separately. We can handle them all at once, because we have multipliers that splice the color values onto each other: the incoming red channel can affect the green pool, and so on. Here’s the AS3 code we used… Our cat MovieClips featured red eyes, so the “redDest” in this code holds the cat’s desired eye color:


var matrix:Array = new Array();

// RED RESULT:
matrix = matrix.concat([
	(redDest.r - blackDest.r) / 255.0,
	(greenDest.r - blackDest.r) / 255.0,
	(blueDest.r - blackDest.r) / 255.0,
	0,
	blackDest.r
]);

// GREEN RESULT:
matrix = matrix.concat([
	(redDest.g - blackDest.g) / 255.0,
	(greenDest.g - blackDest.g) / 255.0,
	(blueDest.g - blackDest.g) / 255.0,
	0,
	blackDest.g
]);

// BLUE RESULT:
matrix = matrix.concat([
	(redDest.b - blackDest.b) / 255.0,
	(greenDest.b - blackDest.b) / 255.0,
	(blueDest.b - blackDest.b) / 255.0,
	0,
	blackDest.b
]);				

// ALPHA: for now, do not adjust opacity
matrix = matrix.concat([
	0,
	0,
	0,
	1,
	0
]);

var filter:ColorMatrixFilter = new ColorMatrixFilter( matrix );

And here’s how it looks in the game!

KITTY COLOR

I look forward to experimenting with this technique more in the future. You’ll notice we didn’t perform any magic with the alpha channel, although both ColorTransform and ColorMatrixFilter let you treat alpha in the same way as red, green and blue. So there are still interesting tricks waiting to be discovered!

One thought on “Understanding the ColorMatrixFilter class!

Leave a Reply

Your email address will not be published.