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
12 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.

Twitter <follow>

March 18th 2010 - 5:29pm

We've missed you Morris: RT @memotv: This guy is nuts. New trailer for Chris Morris' jihadist comedy Four Lions http://is.gd/aNzki

Discussion

12 Responses to Masking Transparent Bitmaps

Leave a Reply

  1. tf 1 year ago

    hi,

    in fact i think your first “cacheAsBitmap” approach can work if you :

    - 1 cacheAsBitmap the masked image

    myImage.cacheAsBitmap = true;

    -2 add your gradientmask to stage

    addChild( gradient );

    in my first tests it seems to works (an old flash8 memory…)
    hope it helps

    regards
    tf

    Reply to this comment

  2. Soulwire 1 year ago

    Hey TF,

    Talk about overlooking the obvious. You’re right – though I find it odd that a Bitmap would not be cached as a bitmap!! In the first example I cached the shape object as bitmap as it contained vector drawing, not the bitmap image as it seemed unecessary.

    I suppose the main advantage of the copyPixels approach is that you end up with a single object.

    Cheers for the pointer, you proved the point that its good to get these questions out there ;)

    Reply to this comment

  3. tf 1 year ago

    you’re welcome :)

    You’re right, each approach has it’s own pros and cons.
    It’s also true that having a Bitmap object that can be not cachedAsBitmap sounds really strange o_0
    Unfortunately that’s not the only strange thing in as…

    anyway, thanks a lot for sharing your experience

    Reply to this comment

  4. Soulwire 1 year ago

    “Unfortunately that’s not the only strange thing in as…”

    Haha; Amen to that mate! ;)

    Reply to this comment

  5. TroyWorks 1 year ago

    How funny, I ran into something similar last week. What I did was probably more hacky,

    Since I wasn’t looking for a gradient mask, I drew the vector mask into the green channel of a temporary bitmap and the alpha of the real bitmap into the blue channel, into the other then thresholded to get the areas that overlapped blue and green, then copied that into the alphachannel of the end bitmap.

    Reply to this comment

  6. Rob 1 year ago

    Bitmaps are rendered as bitmap-filled shapes so that they can be anti-aliased onto sub-pixels – turning on caching snaps them to exact pixels.

    Anyhow – very cool technique, thanks for posting!

    Reply to this comment

  7. Soulwire 1 year ago

    Cheers Rob! The whole bitmap cached bitmap thing makes much more sense now. Thanks for the note :)

    Reply to this comment

  8. Oscar H 9 months ago

    awesome! great help. good techniques..

    Reply to this comment

  9. Oscar H 9 months ago

    running debug player on firefox and I’m getting this error.

    ArgumentError: Error #2015: Invalid BitmapData.
    at flash.display::BitmapData()
    at Main()

    I thought you should know..

    Reply to this comment

  10. Ahmad Bagadood 8 months ago

    How about masking text “graneintly”?

    Reply to this comment

  11. Ahmad Bagadood 8 months ago

    I was able to do the text mask but when publish to a page internet explorer still shows the mask layer as a layer not a mask! any help is appreciated.

    Reply to this comment

  12. Winford Mathews 1 month ago

    Hello. greatjob. I did not expect this blogs on a sat.

    Reply to this comment