An alternative to bitmap transparency in Flash

Published on Thu 24 Dec 2009 by Will


When it comes to using transparent bitmap images in Flash often you will find using a PNG is your best option. However, a major caveat with using PNG images is the significant increase in file size compared to non-transparent image types such as JPEGs and (non-transparent) GIFs. This often becomes an issue when working on projects with limited allocated file size, such as banner ads.

A transparent PNG of the above image has a file size of approximately 109kB

Lately I’ve been toying with an alternative to PNGs… using JPEGs. “Wait a minute…”, you say, “JPEGs don’t support transparency!”. That’s correct but by making use of Flash’s BitmapData class we can merge two non-transparent images together to make a single transparent image. Even though this method actually requires 2 separate JPEGs the total combined file size is almost always significantly smaller than a single PNG!

Understanding channels

Bitmap images are made up of 3 separate colours, red, green and blue. Transparent images are special as they contain a forth channel called an alpha channel. Our challenge here is to create this forth channel for our image. But how?

The solution is to use two images of the same subject. One is our source image (on the left) the other our mask image (on the right). Using Photoshop I have output our source image on a coloured background and our mask image in a high-contrast inverted black and white. The important thing to note there is that even though our mask image appears black and white it still contains red, green an blue channels.

The above 2 JPEGs have a combined file size of only 29kB

How these two images are loaded is really up to you. Whether they are compiled into your application or loaded at runtime doesn’t really matter. What matters is we have access to both our source image and mask image’s BitmapData object. As this is a somewhat advanced topic I am assuming you know how to either dynamically load images or instantiate compiled objects.

Channel copying

We must create a new BitmapData instance which we will copy our source image to. We do this as we need our BitmapData object to contain an alpha channel. By default BitmapData objects are constructed with transparency set to true so we only need to pass the dimensions our new image needs to be. For this we simply use the same dimensions as our source image as we will be doing a straight copy of it. To copy our source image we use the copyPixels method of the BitmapData class.

// sourceBMD is our source image's BitmapData object
var bmd:BitmapData = new BitmapData(sourceBMD.width, sourceBMD.height);
bmd.copyPixels(sourceBMD, sourceBMD.rect, new Point());

Once we’ve copied our source image to the new transparency-ready BitmapData object we now copy any of the 3 available channels of our mask image to the alpha channel of our new BitmapData object. I say ANY as it really shouldn’t matter which channel you choose however I find copying the red channel of the mask image usually provides the best results. We use the copyChannel method of the BitmapData class to do this.

// maskBMD is our mask image's BitmapData object
bmd.copyChannel(maskBMD, maskBMD.rect, new Point(), BitmapDataChannel.RED, BitmapDataChannel.ALPHA);
var bm:Bitmap = new Bitmap(bmd);
addChild(bm);

Once this is done you should have a transparent image created from 2 non-transparent images! At this point you are free to dispose of your source and mask BitmapData objects as they are no longer needed.

This content requires Flash.

The resulting combined image.

As you can see the resulting image is a fairly convincing alternative to using a single file-size heavy PNG. If you’re wondering why our source image is on a dark green background it is because this is needed for our alpha channel to blend against. I’ve chosen a neutral colour similar to the subject. If the background was simply white you will likely see a faint white outline around the images as our alpha layer blends against the other 3 channels.

To simplify this already simple process I have created a custom class called ChannelMergedBitmapData which extends BitmapData and handles the above process for you. You simply pass the source and mask BitmapData objects and it does the rest.

var bmd:BitmapData = new ChannelMergedBitmapData(sourceBMD, maskBMD);
var bm:Bitmap = new Bitmap(bmd);
addChild(bm);

You are welcome to download the class here.

Did you enjoy this post?

Why not subscribe via RSS or follow Ahrooga on Twitter for alerts on new posts :)

You may also like:

Comments

Tung - 14 September, 2010

man that is really usedful - wished I looked at this for Pepperjack!

Jon - 07 February, 2011

The black background of the mask jpg appears in the final masked image... in black

yannick armspach - 27 March, 2011

Hi, I writed the same class...
But in my project, my class bug when i load lot of image.

I should try with your class !

But i can't download it. "acces deny".

Will Dady - 27 March, 2011

Yannick, thanks. Yes the link was broken. It is now fixed.

yannick armspach - 28 March, 2011

Thanks.

I use your class... and it's a strange problem !

When i make a for loop with masked bitmap, my swf bug only in the mac's browser (firefox,safari,...)! the browser crash, but it work directly in the flash player.

Also , All work on a PC !?!

I think is a Jobs's joke !

Special thanks to apple !!!

to try : http://wordpressrc.y-visuel.com

Jeremy Daley - 29 March, 2011

This is easy to achieve on the timeline as well using Multiply and Screen blendmodes.

Where transparency once existed, you use an all white background. That would go on the top layer. But then you'd keep what you have in your black and white image and "screen" it on the bottom layer.

This post explains in further detail:
http://daleyjem.blogspot.com/2008/08/optimizing-flash-filesizes-for-banner.html

yannick armspach - 29 March, 2011

thank for the tips, Jeremy ! is the way.

But i think, the best methode is like this :

create 1 mc ( mcHolder ) and include 2 other mc.
First ( mcImage ) for the jpg image and the another ( mcMask ) for the png mask.

And in the script :

//2 clip as Bitmap to keep the png mask.
mcHolder.mcImage.cacheAsBitmap = true
mcHolder.mcMask.cacheAsBitmap = true

//set mask.
mcHolder.mcImage.mask = mcHolder.mcMask;

//cache all as bitmap for better quality.
mcHolder.cacheAsBitmap = true

This methode is lightest. Why? we don't Know! héhé

Dan - 23 June, 2011

very cool, you can also do it fairly easily by converting your png's to swf's (using a tool like png2swf or using the ide) and then loading .swf images, flash uses jpg compression whilst keeping the transparency so you can cut down heaps of file size.