Bitmap Class, Part 1: Basics
Bitmap Builder
The Bitmap and BitmapData classes in ActionScript provide useful ways of working with bitmapped graphics (JPEGs, GIFs, PNGs, and the like). These classes expose the numerical data that makes up a bitmapped graphic to script intervention.
At their most basic, these classes give you control of every pixel in a bitmap -- which, as you'll recall, is just a two-dimensional matrix or mosaic of color dots, or pixels. You can use this basic facility to do a huge variety of interesting things, from building your own images under script control, to filtering existing images, to combining or sequencing images, to contructing dynamic masks... and a whole lot more.
In the postings this week, we'll proceed as usual, from simple to more sophisticated applications. In this posting, we'll look at a very simple bitmap generator, then an image modifer, or sequential eraser. These examples will help establish the basic principles of working with Bitmap and BitmapData.
Later posts will cover animated image replacement, then image replacement with masking, combining the Bitmap class with the Drawing API.
I. Generating a Bitmap image
The demonstration at the top of this page generates a 400x400 bitmap made up of randomly selected color dots. (Reload the page if you want to see it in action.)
Here's how this trick is accomplished. Architecturally, this project is very simple: an otherwise blank main movie file (BMBuilder.fla), linked to a document class (BMBuilderDocClass.as). The action takes place on frame entry. There are no subsidiary MovieClips or image files involved.
Here's the entirety of the document class, omitting the package{} wrapper:
import flash.display.MovieClip;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.events.Event;
public class BMBuilderDocClass extends MovieClip
{
public var BMD = new BitmapData(400,400, false);
public var outMap:Bitmap;
public var YCo = 0;
function BMBuilderDocClass()
{
outMap = new Bitmap(BMD);
addChild(outMap);
addEventListener(Event.ENTER_FRAME, mapMaker);
}
function mapMaker(e:Event)
{
if(YCo < 400)
{
for(var i:uint=0; i<400; i++)
{
BMD.setPixel(i, YCo, Math.random()*0xFFFFFF);
}
removeChild(outMap);
outMap = new Bitmap(BMD);
addChild(outMap);
YCo ++;
}
else
{
removeEventListener(Event.ENTER_FRAME, mapMaker);
}
}
}
In addition to flash.display.MovieClip and flash.events.Event, which we need for any project involving an enter-frame loop, we import flash.display.Bitmap and flash.display.BitmapData, the two special classes we'll use to build our graphic.
In declarations, we set up BMD, an instance of BitmapData, giving it arguments for width, height, and transparency. (Note that transparency is not the same as alpha in this case.) We also set up BMD, which will hold instances of Bitmap, and a variable called YCo (short for Y-coordinate), which we'll use as a reference in our random-pixel loop, and as a control counter for that loop.
A word about the two new classes. BitmapData serves as an auxiliary for Bitmap, in much the same way that SoundTransform relates to SoundChannel, and that class in turn to Sound. That is, we use BitmapData to change the numerical values that make up our image, and Bitmap to wrap and deliver those values.
Yes, it's all a bit Baroque. But we've learned to love that feature of the new ActionScript, haven't we?
In the Constructor, we first instantiate outMap, using BMD as its data source. (BMD is currently empty or blank, but that's not a problem.) You may wonder why we're setting up a blank version of our bitmap at the start. We need to do this becuase we'll be building successive versions of this object as we go, and to keep them from accumulating, we need to remove an old one before we add a new version. We can only do that if we have a preliminary version to start with.
Next, we add a standard listener that activates our enter-frame loop. We'll worry about terminating that loop, and turning off the listener, in the callback.
The callback for ENTER_FRAME is named mapMaker. As you can see, it contains an if/else structure that distinguishes between two conditions. Either our main counting variable, YCo, is less than 400, or it's not. That is, either we still have lines to draw on the screen, or we don't. If we don't, then we remove the event listener, which turns off our enter-frame loop and effectively terminates the action.
While YCo is less than 400, we do a few interesting things that are probably worth repeating for close inspection.
for(var i:uint=0; i<400; i++)
{
BMD.setPixel(i, YCo, Math.random()*0xFFFFFF);
}
removeChild(outMap);
outMap = new Bitmap(BMD);
addChild(outMap);
YCo ++;
The first structure contained in this branch of the if/else is a for loop whose index runs from 0 to 399. This index is passed to the setPixel() method of our BitmapData class (BMD), along with YCo and a random color value. So for each integer value of YCo (0-399), we draw a line of 400 pixels, using YCo as (surprise!) the Y coordinate. Each pixel gets a random color.
Outside the 400-iteration loop, we first remove from the Display List the current version of our bitmapped graphic. (If we don't do this, we'll end up with 400 copies of the graphic stored in memory.) Next, we once again instantiate a Bitmap object, using the current BMD as its data source. We add that object to the Display List (i.e., we write it to the screen). In effect, all this removing and adding simply refreshes the display.
Finally, we increment YCo, which both moves our draw point vertically down the screen, and brings our loop count one step closer to conclusion.
II. Modifying an existing image
Sequential Line Replacement
In this second example, we reverse the action of the first. Instead of creating a bitmapped graphic from scratch, we take an existing graphic and (apparently) erase it, mapping each of its pixels sequentially to white (the page background color).
If the window on the right is blank, click restart to renew the action.
Architecture remains very simple: a blank main movie (seqLineToWhite.fla), linked to a document class (SLTWDocClass.as). One external graphic file, replace.jpg, is required. You can place this file anywhere you like. For reasons particular to this Web site, we've called it via an absolute URL from a permanent directory on our server.
The imports for this project are a bit more extensive than in the first example:
import flash.display.MovieClip; import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.Loader; import flash.net.URLRequest; import flash.utils.Timer; import flash.events.TimerEvent;
We need Loader and URLRequest to process the external graphic. We need Timer and TimerEvent because the loop in this project will be based on a Timer object, not an enter-frame callback. (We've made this choice arbitrarily, in order to show an alternative method of handling a loop.)
Declared variables are very similar to the first project:
public var myLoader:Loader; public var myReq:URLRequest; public var myBMD:BitmapData; public var Yco:int = 0; public var lineTimer:Timer;
The last variable in the set, LineTimer, accommodates our Timer object. Everything else should look very familiar, either from our first example, or earlier projects.
Likewise, there are not many surprises in the Constructor, which looks like this:
public function SLTWDocClass()
{
Yco = 0;
lineTimer = new Timer(10,400);
myLoader = new Loader();
myLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, imageGood);
myReq = new URLRequest("replace.jpg");
myLoader.load(myReq);
}
(Note that the file path given for the JPEG here is not the one actually used in the live example. We've edited for space. The actual URL is included in the downloadable source.)
We assign Yco (Y coordinate, or Y counter) an initial value of zero. We instantiate a Timer, setting delay to 10 milliseconds (very slight), and repetitions to 400, corresponding to the number of rows in our bitmapped graphic, which measures 400x400 pixels.
We also instantiate a Loader, then do all those things we usually do with that latter object, including assigning a callback function for its COMPLETE event, which signals full receipt of image data. From here, we hand off to the callback, imageGood(), which looks like this:
addChild(myLoader);
lineTimer.addEventListener(TimerEvent.TIMER, lineOut);
lineTimer.start();
As you can see, we add the graphic loaded by myLoader to the Display List, then establish a listener for our Timer, and start the Timer running.
The crucial action takes place in the final callback, lineOut(), which looks like this:
function lineOut(e:TimerEvent)
{
for(var i:int=0; i<400; i++)
{
Bitmap(myLoader.content).bitmapData.setPixel(i,Yco,0xFFFFFF);
}
Yco ++;
addChild(myLoader);
}
The basic technique here resembles that used in our first example. Each time the Timer object ticks over, we run a 400-iteration loop, in which we've installed a setPixel() operation, using the bitmapData property of the Bitmap object associated with the JPEG we loaded at the start. This loop replaces the color value in every pixel in a given horizontal line of the graphic with 0xFFFFFF, or white.
Outside the loop, we increment Yco, which tells us which horizontal line we're modifying, then re-add myLoader to the Display List (thus refreshing the screen).
Note that we don't need to test the value of Yco outside the loop, as we did in the first example, because here we're working with a Timer object which usefully shuts itself down after 400 repetitions. For this reason, there's no need to remove the associated event listener.
Technically, our graphic myLoader remains on the Display List at the end of our process. Since all its pixels have been set to white, it's now indistinguishable from the page background, and as good as invisible, or erased; but if we were being precise, we'd remove it.
III. Source files
Source files for both demonstrations in this posting can be found in the directory BitmapClass/basics, within MULTIMEDIA in the shared account on student-iat. Main movie for the first project is called BMBuilder.fla, and its associated class file is BMBuilderDocClass.as. For the second project, the files are SeqLineToWhite.fla and SLTWDocClass.as. We've added a restart button not described in these notes (to reduce complexity), so there's also a component called restartClass.as
|
|