Overlays on Components in Flex 3  July 17th, 2009

Recently while working on a Flex app I needed to draw an image on top of a component which I didn't know the dimensions of until run-time. That is, I couldn't just make my overlay the exact size of the component and choose it's location in Photoshop. For example, here's a sort of 'placeholder' image we might want the user to interact with. Hover over it to see the desired effect we'll need in Flex:

Our target look, for example.

So the placeholder image in this case is a square, but that wont be the case every time — we need to support every size that is larger than the size of the overlay itself.

After some of the usual API doc digging it was clear that I'd need to use Graphics#beginBitmapFill, it's prototype looks like this: public function beginBitmapFill( bitmap:BitmapData, matrix:Matrix=null, repeat:Boolean=true, smooth:Boolean=false ):void. Looks fine, once you call this you can do something like: this.graphics.drawRect( 0, 0, this.width, this.height ); which will cover the entire component with whatever is in the BitmapData.

Attempt #1

beginBitmapFill at 0,0

So far so good, now let's try to draw the overlay at a bit of an offset, working towards centering it on the canvas: this.graphics.drawRect( 50, 50, overlay.width, overlay.height );.

Attempt #2

beginBitmapFill at 50,50

Oops. Notice the repeat argument — reading the documentation doesn't shed much light but #beginBitmapFill will always 'start' the drawing at the component's top-left corner!

Hey not a big deal, we'll just use the BitmapData#scroll method to shift the thing by a calculated amount to center it... Problem solved!

Attempt #3

beginBitmapFill after scrolling

Uhm... what!? How did that even happen? Now it's drawing the overlay at 0, 0 and at our offset amount... The image with offset is exceeding the bounds of the BitmapData causing the transparent edge to be pushed out and since it (from the docs) 'uses the edge pixels for the fill area outside the bitmap', we get the strange smear to the right and bottom of the canvas. In short, it looks like #scroll doesn't do what I think it does.

Finally...

It should have occurred to me from the beginning that the trick would be in the matrix argument to #beginBitmapFill. Rather than trying to shift the bitmap within itself, we simply apply a matrix to it during the fill. Now I'm no math wiz but this sort of thing is made terribly simple by the Matrix class. All that was needed was taking my offset calculation out of the call to BitmapData#scroll and putting it into a call to Matrix#translate — and...

beginBitmapFill after scrolling

Now with a bit of event handling we can make the effect:

The Source

And, assuming there's a Canvas instance around named canvas, the code could be:

One more thing...

This works perfectly if you're drawing on an element like a Canvas but if you want to put an overlay on an Image element, well, this wont work because the image rendering takes over and you wont see any work done with the graphics property.

Fortunately this is only a minor change. I'll leave it as an exercise for the reader to pick through this code, but the gist is that rather than drawing directly on the element you draw on an instance of Shape which you then add as a child and position on the image. Similarly to above this code assumes an Image instance named image.

This one might look like...

Also, you can download the full source for both Flex apps on this page.

Some social stuff: