Bitmap Alpha Channel Masking using CopyPixels

Original image by Edwin Tofslie

I was having a few gradient alpha mask disabilities today and thought I should share my misery and consequential solution with anyone who might run into the same problems!

Update: TF pointed out a very simple solution – cacheAsBitmap WILL work, providing you cache the Bitmap as a Bitmap, as you would if you were working with two vector drawings. Why a Bitmap wouldn’t be cached as a Bitmap I don’t fully understand.
Still use the copyPixels method if you want to create a single object and discard the mask, but otherwise don’t listen to me overcomplicating things and do it old school!

As you know, since Flash Player 8 we’ve been able to use an alpha channel when masking display objects. A common implementation is to first draw a gradient that fades from white with opacity 1 to white with opacity 0 and then tell a display object to use this as its mask.

Apparently, however – it’s not always that simple…

In this instance, I was using an embedded image (though the same would be true for a loaded one) which was in PNG format and contained an alpha channel. The alpha channel was crucial as the image was an icon with a transparent background and faded edges. I needed to dynamically mask and fade out this image to create a reflection via code, rather than doing this all in Photoshop. The resulting Bitmap would be sitting on top of a colour changing background, and so transparency was needed on both the original icon shape and the fading reflection – a common enough requirement.

The “cacheAsBitmap” Approach

The simplest way to do this and the technique that I immediately reached for, is to create the mask – being a Shape object with a gradient drawn using its instance of the Graphic class – set it’s cacheAsBitmap property to true and then set the embedded Bitmap’s mask property to use this gradient.

The result:

Using cacheAsBitmap to create an Alpha gradient mask

The code:

[Embed(source = '../lib/image.png')]
private var image:Class;

var myImage:Bitmap = new image as Bitmap;
addChild( myImage );

var matrix:Matrix = new Matrix();
matrix.createGradientBox( myImage.width, myImage.height, Math.PI / 2 );

var gradient:Shape = new Shape();
gradient.graphics.beginGradientFill( 'linear', [0xFFFFFF, 0xFFFFFF], [1, 0], [0, 255], matrix );
gradient.graphics.drawRect(0, 0, myImage.width, myImage.height);
gradient.graphics.endFill();
gradient.cacheAsBitmap = true;

myImage.mask = gradient;

However, presumably due to the fact that the bitmap already contained an alpha channel, the alpha channel of the bitmap-cached gradient shape was ignored, and the result is a mask which is simply the entire area of the gradient box, much like the pre Flash Player 8 mask behaviour of being either on or off and nothing in between. (

The “CopyChannel” Approach

The next approach I tried was to draw the gradient to a new transparent bitmapdata and then use the copyChannel method to copy the alpha channel of the gradient bitmapdata to the bitmapdata of my image.

The result:

Using BitmapData.copyChannel to create an Alpha gradient mask

The code:

var gradientBitmap:BitmapData = new BitmapData( gradient.width, gradient.height, true, 0 );
gradientBitmap.draw( gradient );

var channel:uint = BitmapDataChannel.ALPHA;

myImage.bitmapData.copyChannel(gradientBitmap, gradientBitmap.rect, new Point(), channel, channel);

Again, an undesirable result – I was left with some kind of graphic monstrosity that was a Frankenstein combination of my wonderfully smooth faded source image and a gradient showing through wherever transparency should have been in the original.

The solution (CopyPixels)

The approach I used in the end was to create a new transparent bitmapdata with the same dimensions of my image, then to use the copyPixels method to copy over the pixels from my source image to the new bitmapdata. The copyPixels method also accepts a parameter for alphaBitmapData, so I passed my gradient bitmapdata to this. Finally, copyPixels also takes a Boolean value for mergeAlpha, so set this to true and the result is a brand new image with the RGB pixel data of the source image and an alpha channel which is a combination of the alpha channels from both the source and the gradient bitmap.

The result:

Using BitmapData. copyPixels to create an Alpha gradient mask

The code:

var gradientBitmap:BitmapData = new BitmapData( gradient.width, gradient.height, true, 0 );
gradientBitmap.draw( gradient );

var result:BitmapData = new BitmapData( myImage.width, myImage.height, true, 0 );
result.copyPixels(myImage.bitmapData, result.rect, new Point(), gradientBitmap, new Point(), true);

addChild( new Bitmap( result ) );

I’m sure I’ll be mocked by my fellow developers for having been kneecapped by this quite basic problem, but having glanced at a few forum posts it seems common enough. Therefore I felt sharing the solution might be of some benefit.

Full source code:

[Embed(source = '../lib/image.png')]
private var image:Class;

public function Main():void
{
	drawCheckerboardPattern();

	var myImage:Bitmap = new image as Bitmap;

	var matrix:Matrix = new Matrix();
	matrix.createGradientBox( myImage.width, myImage.height, Math.PI / 2 );

	var linear:String = GradientType.LINEAR;
	var colors:Array = [0xFFFFFF, 0xFFFFFF];
	var alphas:Array = [1.0, 0.0];
	var ratios:Array = [0.0, 255];
	var spread:String = SpreadMethod.PAD;

	var gradient:Shape = new Shape();
	gradient.graphics.beginGradientFill( linear, colors, alphas, ratios, matrix, spread );
	gradient.graphics.drawRect(0, 0, myImage.width, myImage.height);
	gradient.graphics.endFill();

	var gradientBitmap:BitmapData = new BitmapData( gradient.width, gradient.height, true, 0 );
	gradientBitmap.draw( gradient );

	var result:BitmapData = new BitmapData( myImage.width, myImage.height, true, 0 );
	result.copyPixels( myImage.bitmapData, result.rect, new Point(), gradientBitmap, new Point(), true );

	addChild( new Bitmap( result ) );
}

private function drawCheckerboardPattern():void
{
	var boxSize:int = 8;

	var pattern:BitmapData = new BitmapData( boxSize * 2, boxSize * 2, false );
	pattern.fillRect( new Rectangle( 0, 0, boxSize, boxSize ), 0xCCCCCC );
	pattern.fillRect( new Rectangle( boxSize, boxSize, boxSize, boxSize ), 0xCCCCCC );

	graphics.beginBitmapFill( pattern );
	graphics.drawRect( 0, 0, stage.stageWidth, stage.stageHeight );
	graphics.endFill();
}
Posted on 10 Dec 2008
Tagged
26 Comments
0 Trackbacks

Meta

Masking Transparent Bitmaps was posted on December 10th 2008 in the category Code / Actionscript 3.0, Programming and tagged; , , .

You can Leave a comment.


Warning: file_get_contents(http://search.twitter.com/search.atom?q=from:soulwire&rpp=1) [function.file-get-contents]: failed to open stream: HTTP request failed! HTTP/1.0 410 Gone in /home/soulwire/webapps/soulwire_blog/wp-content/themes/soulwire/functions.php on line 203

Discussion

18 Responses to Masking Transparent Bitmaps

Leave a Reply

  1. prago 6 years ago

    I wouldnt think I will ever post a comment below any blog post but…

    THANK YOU VERY MUCH FOR THIS SOLUTION! IT SAVED ME HELL LOT OF TIME. GREAT POST!

    Reply to this comment

  2. Kyle 5 years ago

    This solution saved me a ton of time – thanks so much. It seems like it’s the only way to apply a semi-transparent bitmap as a mask to another bitmap display object – well done.

    Reply to this comment

  3. taisen 5 years ago

    Thanx a lot ! You saved my day…

    Reply to this comment

  4. ahmed 5 years ago

    believe me this was very very helpful.

    Reply to this comment

  5. cyw 5 years ago

    great mate, thx for sharing

    Reply to this comment

  6. Derek 4 years ago

    Hi, I’m also not someone that posts back on a blog, but enjoyed reading this. My problem is that i have a slideshow component in flash on the stage and would like to mask half of it out to create a reflection with the invert of the content that i put into a movie clip. Could you please make a fla downloadable for me to look at as a sample. Please. Thanks in advance. You will be of great help if you can thanks again

    Reply to this comment