Thursday, July 30, 2009

Flex: Masking Images using other remotely loaded images

I had to do this recently, due to their being a very large number of images in the project I'm working on, which needed the backgrounds knocked out; it was easy to generate the masks in an automated way, it was not easy to alter the images to alpha out the backgrounds.

In Flex, they say one display object can work as a mask for another. This is true of course, but for dual remotely loaded images, properly making sure the images are entirely loaded via load.complete events, and so forth, is the key.

The following little technique shows this, code snippet below. This is proving to be quite a timesaver for the team as a whole, since the graphics guys don't have to worry about altering their entire catalog of images. I've already recommended that a back-end process should generate AND apply the masks so that Flash doesn't have to make 2x the requests for images (every image needs a mask): from a performance standpoint this is a bit of a nightmare, but, it's a solution that is moving the project forward and is easy enough on my end to implement.

Note that the images I'm using for masks are not the standard black/white masks you use in photoshop. They are PNG-8s, with everything BUT the shape you want to see alpha'd out, with 8 colors, not converted to sRGB, no metadata, etc. A roughly 400x400 image mask comes in at around 2kb with this profile.

I'm aware this isn't exactly new, and it's documented, but it's solving such a significant problem I figured I'd make it visible. I actually use this code snippet in an itemrenderer, so that I can just chuck an array of objects at a tilelist, and have the item renderer do the two-step loading.

Notice also:

- I'm combining pngs and jpgs, it doesn't matter it's all rendered as bitmaps by the client in the end anyway.
- I'm using a two-step loading chain; it seems you don't have to do this necessarily, but every now and then, it seems to fail if you don't. The two step ensures the mask is ready to go before the image every time.

import mx.controls.Image;
import mx.events.FlexEvent;

private var _mask : Image;
private function onCreationComplete ( event : FlexEvent ) : void
{
_mask = new Image ( );
_mask.cacheAsBitmap = true;
_mask.addEventListener ( Event.COMPLETE, onMaskLoadComplete );
_mask.load ( "images/testmasks/picturemask.png" );
}

private var _img : Image;
private function onMaskLoadComplete ( event : Event ) : void
{
_img = new Image ( );
_img.cacheAsBitmap = true;
_img.addEventListener ( Event.COMPLETE, onImageLoadComplete );
_img.load ( "images/testmasks/picture.jpg" );
}

private function onImageLoadComplete ( event : Event ) : void
{
addChild ( _img );
addChild ( _mask );
_img.mask = _mask;
}


As always, thanks for visiting.