BitmapData Basics Part 1

In my previous post Bitmap Basics, i discussed creating bitmaps. This time i will go through some of the methods available to manipulate bitmap images.

If you have read Bitmap Basics, you hopefully remember that the visual data (the pixels) are held in the bitmapData property of the bitmap object; so we use flash.display.BitmapData methods to alter the image.

First, lets look at ways to ‘draw’ colours on the bitmap.

Setting pixels directly

To set the colour of an individual pixel we can use setPixel(…) to set the colour or setPixel32(…) to set the colour and alpha (if the bitmap supports transparency).

//non-transparent bitmap called bm previously created
var xpos:int = 20;
var ypos:int = 10;
var colour:uint = 0x00FF00;
//set pixel at 20 across and 10 down to green
bm.bitmapData.setPixel(xpos, ypos, colour);

If you are planning on calling setPixel(…) or setPixel32(…) a lot (in a loop for instance) then you can get a performance boost by calling lock() and unlock(…) before and after you change the pixels. Locking the bitmapData stops any changes being rendered on screen until you call unlock:

bm.bitmapData.lock();
//do lots of setPixel calls
bm.bitmapData.unlock();

The unlock method takes a Rectangle object as an optional argument, e.g: bm.bitmapData.unlock(new Rectangle(0, 0, 20, 20)); – this tells the flash player which part of the image has been updated. Whether to bother with lock/unlock depends on how many pixels are being updated – there’s no point in adding the overhead of calls to lock() and unlock(…) in the first example above as only 1 pixel is changed. As a general guide, if your setPixel call(s) are inside a loop then use lock/unlock, otherwise don’t – my logic being that if they aren’t in a loop, there won’t be many of them – if you had 100 lines of setPixel(…) calls all in a row, you’d put them in a loop right?

For larger (rectangular) areas there is fillRect(…)

//non-transparent bitmap called bm previously created
var rectangle:Rectangle = new Rectangle(20, 10, 15, 5);
var colour:uint = 0x00FF00;
//starting at 20 across and 10 down
//fill an area of 15 by 5 pixels with green
bm.bitmapData.fillRect(rectangle, colour);

There is also a floodFill(…) method that works just like the paint-bucket tool in most design tools:

//transparent bitmap called bm previously created
var xpos:int = 20;
var ypos:int = 10;
var colour:uint = 0xFF00FF00;
//starting at 20 across and 10 down
//flood fill with green at 100% alpha
bm.bitmapData.floodFill(xpos, ypos, colour);

Note that fillRect and floodFill require ARGB colours for transparent bitmaps – if you only pass an RGB colour the alpha is set to zero – so if you see a transparent area where you where expecting a solid colour, lack of an alpha is probably the culprit.

There are also 2 similar methods that allow you to fill a rectangular area and define specific colours for each pixel. They are setPixels(…) and setVector(…). Both operate in the same fashion but setPixels expects a ByteArray of uints:

//non-transparent bitmap called bm previously created
//byteArray called ba previously created and filled with 15x5=75 colour values (uints)
var rectangle:Rectangle = new Rectangle(20, 10, 15, 5);
//starting at 20 across and 10 down
//fill an area of 15 by 5 pixels with colours from byteArray
bm.bitmapData.setPixels(rectangle, ba);

And setVector expects a Vector of uints:

//non-transparent bitmap called bm previously created
//vector. called vec previously created and filled with 15x5=75 colour values (uints)
var rectangle:Rectangle = new Rectangle(20, 10, 15, 5);
//starting at 20 across and 10 down
//fill an area of 15 by 5 pixels with colours from vector
bm.bitmapData.setVector(rectangle, vec);

Each of the 4 set methods has a reciprical get method, so you can examine a pixel for its colour with getPixel or getPixel32, or grab a bunch of pixels colours with getPixels or getVector.

Here’s a lame example of using setPixel in an enterFrame loop, but it does hilight a couple of points to be aware of.

{BitmapDataExample1; click to restart}

Firstly, bitmap and bitmapData don’t dispatch mouse events so if you want your image to be clickable etc., you need to wrap it in a Sprite and listen to the sprite for mouse events (i actually added the listener to the stage for this example).

Secondly, you should call bitmapData.dispose() before replacing a bitmaps bitmapData object otherwise the old bitmapData object will hang around in memory with no way of accessing it. If my example didn’t call dispose(), then everytime you click on it, another copy of the image would be stored in memory – eventually all the computers memory will be used up and it will crash – not good!! If you’re aware of the importance of clearing up your event listeners, this is the same principle. Source code is available at the end of the post.

Copying

You may be thinking “ah ha! i can use getPixels and setPixels together move pixels around!”, and you’d be right. But if you dont need to manipulate the pixels between copying and pasting then there are some one-stop copying methods available.

To copy a block of pixels we can use copyPixels(…):

//bitmaps called bm1 and bm2 previously created
var src:BitmapData = bm1.bitmapData;
var srcRect:Rectangle = new Rectangle(50, 50, 100, 100);
var destPoint:Point = new Point(0, 0);
var alphaSrc:BitmapData = null;
var alphaPoint:Point = null;
var mergeAlpha:Boolean = false;
bm2.copyPixels(src, srcRect, destPoint, alphaSrc, alphaPoint, mergeAlpha);

This copies a 100×100 block starting at position 50,50 in bm1 to position 0,0 in bitmap bm2:

src – the bitmapData object to copy from.

srcRect – the rectangle area to copy. If the whole of the source is being copied, you can use the rect property of bitmapData ( var srcRect:Rectangle = src.rect; ) which always contains the dimensions of the bitmapData(ie: x=0, y=0, width=image width, height=image height).

destPoint – the x and y coordinates of the destination bitmapData object to copy to.

alphaSrc – an optional secondary source for the alpha channel (copyPixels will use src for alpha values if this is null).

alphaPoint – the x and y coordinates of the destination bitmap object to copy the seperate alpha channel to.

mergeAlpha – whether to include the alpha channel (true) or not (false) in the copying.

Of course, the source and destination can be the same object if you wish; and you don’t have to worry about keeping your copying within the bounds of the image – any clipping is gracefully handled under the hood.

To copy a single ARGB channel there is copyChannel(…):

//assume bitmaps bm1 and bm2 still exist
var src:BitmapData = bm1.bitmapData;
var srcRect:Rectangle = new Rectangle(50, 50, 100, 100);
var destPoint:Point = new Point(0, 0);
srcChannel:uint = BitmapDataChannel.GREEN;
destChannel:uint = BitmapDataChannel.RED;
bm2.copyChannel(src, srcRect, destPoint, srcChannel, destChannel);

This is very similar to copyPixels(…) – the first 3 parameters are identical. The other two determine the single ARGB channels to copy from and to. Here we are copying the green channel of the source to the red channel of the destination.

Copying deluxe

With the previous methods the source has to be a bitmapData object but what about Sprite, Shape, MovieClip, Stage, Button etc? They’re not bitmap objects but it would be nice to be able to copy them too. A fanfare of trumpets please as i present the draw(…) method!

With draw(…), we can copy any display object to a bitmapData object. Not only that, we can alter the image before it is hard-baked into a bitmapData object – scaling, rotating, colour transforming, blending and clipping can all be carried out during the process. Here’s an example:

//assume a movieclip called mc exists
var src:IBitmapDrawable = mc;
var matrix:Matrix = null;
var ct:ColorTransform = null;
var blendMode:String = null;
var clipRect:Rectangle = null;
var smoothing:Boolean = false;
//create 400x400 bitmapdata object
var bmd:BitmapData = new BitmapData(400, 400, false, 0x000000);
//copy movieclip to bitmapData object
bmd.draw(src, matrix, ct, blendMode, clipRect, smoothing);

src – the source display object to copy (all display objects implement IBitmapDrawable).

matrix – contains details on any transformations to carry out (scaling, rotating, translating).

ct – any colour transformations to carry out (eg: ct = new ColorTransform(1,1,1,0.5); to half the alpha). See flash.geom.ColorTransform for a full description.

blendMode – blending mode, if any. Use any public constant from the flash.display.BlendMode class.

clipRect – optionally specificies the source rectangle (works the same as srcRect in copyPixels above).

smoothing – whether to apply smoothing when scaling and rotating (true) or allow pixelation (false).

The above code will copy the mc object into bmd without any transformations (as they are all null). If we want to scale, rotate or translate (move up/down, left/right) then we can setup a suitable matrix object with the aid of a helper method and avoid some nasty maths:

//x scale
var sx:Number = 3;
//y scale
var sy:Number = 2;
//rotation (in radians)
var rot:Number = Math.PI/2;
//change in x position
var dx:Number = 0;
//change in y position
var dy:Number = 0;
//create <em>empty</em> matrix
var matrix:Matrix = new Matrix();
//apply transformations
matrix.createBox(sx, sy, rot, dx, dy);

This would scale the copied image to 300% width and 200% height, rotate it clockwise by 90 degrees (360 degrees = 2*Math.PI radians), and apply no translations before saving it to the bitmapData object.

Here’s an example:

{BitmapDataExample2; click to restart}

This example takes a sprite (on the left) and copies to the bitmap (on the right) on each frame, carrying out a matrix transformation each time. See the source for an full explanation.

Scrolling

If you want to scroll an image, it’s easy:

//bitmap called bm previously created
var dx:int = 2;
var dy:int = 1;
//scroll image 2 pixels to the right and 1 pixel down
bm.bitmapData.scroll(dx, dy);

The image always wraps around, and you can only scroll the whole bitmap at once, so if you just want to scroll part of the image you need to handle it yourself with a suitable copyPixels call.

Random Noise

There are two methods for generating noise, read about them in a previous post.

Next

That covers a fair bit of bitmapData’s capabilities. The next post will cover what’s left: applying filters and looking at colours.

If there is anything that you would like explained in more detail, leave a comment and i will do my best to oblige.

SOURCE CODE: https://github.com/LeeBurrows/blog-source-code

7 thoughts on “BitmapData Basics Part 1

  1. Pingback: Swf.hu 2.0 – flash és webfejlesztés » Heti linkajánló 2010/39

    1. Lee Post author

      hi umair.
      can you show me your code, especially the part where the error occurs, and i will take a look at it

      Reply
  2. Kevin Morris

    Great tutorial. Though I am also experiencing an issue with the setPixels method. What needs to be defined within the ByteArray? I think that is where I am stumbling.

    Reply
    1. Lee Post author

      hi Kevin

      the ByteArray should be filled with uint values – at least as many as width*height of the Rectangle object that you are also passing into SetPixels(). If you are filling the ByteArray ‘manually’ then use writeUnsignedInt() to populate your ByteArray with colours.

      hope that makes sense?

      Reply
    1. Lee Post author

      Hi Vinicius
      The floodfill method only replaces exact colour matches – so no smoothing i’m afraid. The threshold method may suit your needs (see BitmapData Basics Part 2). It allows colours over a given range to be replaced but its not a floodfill – it works on a given rectangle of pixels.

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>