Tuesday, July 17, 2012

How to make a pixel fade effect using AS3: Part 1

In this tutorial I'll teach you how to create a picture fade-in effect using pixel particles that smoothly fly to their assigned position.

The effect I'm talking about can be seen below, in the end of this part.

I will be using Adobe Flash and FlashDevelop in this tutorial. As long as you understand the code I provide, you'll have no problems implementing this class in your project using whatever IDE you like.

The reason I'm using Adobe Flash is because the effect is inside a MovieClip that can be easily put anywhere in the project and work the same.

Open Flash, create a new AS3 document. Save it somewhere, and create a MovieClip with class path "pixel_fade". Set its coordinates to 0,0.

Inside of this MC, create another MovieClip - give it an id "content". It doesn't matter where or what you put inside the "content" movie clip, as long as it follows these two rules:

1. Whatever you have inside "content" must heve top left corner coordinates 0,0.

2. The width and height of content must be round numbers. For example, 320 is good, 320.47 is bad.

Now create pixel_fade.as in the same location as the Flash project file. I'm going to write the class in FlashDevelop, but you don't have to. Use any text editor you like, this includes the built-in actionscript editor in Flash.

Open the package and import these classes:

package  
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.TimerEvent;
import flash.geom.Rectangle;
import flash.utils.getTimer;
import flash.utils.Timer;

Now create the public class pixel_fade that extends MovieClip and declare a bunch of private variables. These variables are:

public class pixel_fade extends MovieClip
{
private var bitmapData:BitmapData;
private var particleData:BitmapData;
private var particleScreen:Bitmap;
private var staticData:BitmapData;
private var pixels:Array = [];
private var step:int = 250;
private var prevstep:int = 0;
private var startX:int = 0;
private var startY:int = 0;
private var actionTimer:Timer;

I will explain each one's meaning on the go.

Create the pixel_fade() constructor, and start adding lines of code.

Firstly, we call the stop() method of the main timeline. This is optional, but by stopping the main timeline we ensure that the animation will finish before the movie will advance to the next frame.

// stop main
(root as MovieClip).stop();

Now we take a snapshot of the content and turn it into a BitmapData object. Apply this value to bitmapData, and then make content invisible:

// capture pic
bitmapData = new BitmapData(content.width, content.height, true, 0xffffffff);
bitmapData.draw(content);
content.alpha = 0;

I want to place my animation in the center of the flash movie, so I set startX and startY values by calculating where the top left corner of the content should be:

// set start pos
startX = stage.stageWidth / 2 - content.width / 2;
startY = stage.stageHeight / 2 - content.height / 2;

Then we create an empty particleData BitmapData with the same size as content. Apply this data to particleScreen Bitmap object and add particleScreen to the movie clip:

// create particle screen
particleData = new BitmapData(stage.stageWidth, stage.stageHeight, true, 0xffffffff);
particleScreen = new Bitmap(particleData);
addChild(particleScreen);

We also create a completely empty staticData BitmapData with the same sizes:

// create blank static screen
staticData = new BitmapData(stage.stageWidth, stage.stageHeight, true, 0xffffffff);

Now call getData() function, which will collect all pixel data, and then add an ENTER_FRAME event listener:

// start by getting all pixel data
getData();
// add listeners
addEventListener(Event.ENTER_FRAME, everyFrame);

Full constructor:

public function pixel_fade() 
{
// stop main
(root as MovieClip).stop();
// capture pic
bitmapData = new BitmapData(content.width, content.height, true, 0xffffffff);
bitmapData.draw(content);
content.alpha = 0;
// set start pos
startX = stage.stageWidth / 2 - content.width / 2;
startY = stage.stageHeight / 2 - content.height / 2;
// create particle screen
particleData = new BitmapData(stage.stageWidth, stage.stageHeight, true, 0xffffffff);
particleScreen = new Bitmap(particleData);
addChild(particleScreen);
// create blank static screen
staticData = new BitmapData(stage.stageWidth, stage.stageHeight, true, 0xffffffff);
// start by getting all pixel data
getData();
// add listeners
addEventListener(Event.ENTER_FRAME, everyFrame);
}

The ENTER_FRAME event handler is empty for now:

private function everyFrame(evt:Event):void {
}

The getData() function is supposed to go through all pixels in the bitmapData object and add them to the pixels array. We need to store coordinates, "go" coordinates, color and a boolean "go" value.

Firstly declare 2 variables in the function - one a Number with value getTimer() and another is an empty uint named col:

var startTimer:Number = getTimer();
var col:uint;

Loop through all rows and columns, then check if the pixel in those coordinates is not white (we skip white, because white is the background of our movie) then add the pixel to pixels array like this:

for (var i:int = 0; i < content.width; i++) {
for (var u:int = 0; u < content.height; u++) {
col = bitmapData.getPixel32(i, u);
if(col != 0xffffffff){
pixels.push( { x:0, y:0, gx:startX+i, gy:startY+u, c:col, go:false } );
}
}
}

The difference between x, y and gx, gy is that the x and y are current coordinates of the pixel, while gx and gy are the destination coordinates.

After that, add a few lines to calculate how much time it took to loop through all the pixels for debugging:

var nowTimer:Number = getTimer();
var elapsed:Number = nowTimer - startTimer;
trace("total pixels: " + pixels.length + ", time elapsed: " + elapsed + "ms");

Now we can start a Timer for 4 seconds, which will loop the movie by play()ing the main timeline when the timer is up:

// start timer
actionTimer = new Timer(4000, 1);
actionTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimer);
actionTimer.start();

Full function:

private function getData():void {
var startTimer:Number = getTimer();
var col:uint;
for (var i:int = 0; i < content.width; i++) {
for (var u:int = 0; u < content.height; u++) {
col = bitmapData.getPixel32(i, u);
if(col != 0xffffffff){
pixels.push( { x:0, y:0, gx:startX+i, gy:startY+u, c:col, go:false } );
}
}
}
var nowTimer:Number = getTimer();
var elapsed:Number = nowTimer - startTimer;
trace("total pixels: " + pixels.length + ", time elapsed: " + elapsed + "ms");
// start timer
actionTimer = new Timer(4000, 1);
actionTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimer);
actionTimer.start();
}

The timer event handler:

private function onTimer(evt:TimerEvent):void {
(root as MovieClip).play();
}

And the full class so far:

package  
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.TimerEvent;
import flash.geom.Rectangle;
import flash.utils.getTimer;
import flash.utils.Timer;
/**
 * ...
 * @author Kirill Poletaev
 */
public class pixel_fade extends MovieClip
{
private var bitmapData:BitmapData;
private var particleData:BitmapData;
private var particleScreen:Bitmap;
private var staticData:BitmapData;
private var pixels:Array = [];
private var step:int = 250;
private var prevstep:int = 0;
private var startX:int = 0;
private var startY:int = 0;
private var actionTimer:Timer;

public function pixel_fade() 
{
// stop main
(root as MovieClip).stop();
// capture pic
bitmapData = new BitmapData(content.width, content.height, true, 0xffffffff);
bitmapData.draw(content);
content.alpha = 0;
// set start pos
startX = stage.stageWidth / 2 - content.width / 2;
startY = stage.stageHeight / 2 - content.height / 2;
// create particle screen
particleData = new BitmapData(stage.stageWidth, stage.stageHeight, true, 0xffffffff);
particleScreen = new Bitmap(particleData);
addChild(particleScreen);
// create blank static screen
staticData = new BitmapData(stage.stageWidth, stage.stageHeight, true, 0xffffffff);
// start by getting all pixel data
getData();
// add listeners
addEventListener(Event.ENTER_FRAME, everyFrame);
}

private function everyFrame(evt:Event):void {
}

private function getData():void {
var startTimer:Number = getTimer();
var col:uint;
for (var i:int = 0; i < content.width; i++) {
for (var u:int = 0; u < content.height; u++) {
col = bitmapData.getPixel32(i, u);
if(col != 0xffffffff){
pixels.push( { x:0, y:0, gx:startX+i, gy:startY+u, c:col, go:false } );
}
}
}
var nowTimer:Number = getTimer();
var elapsed:Number = nowTimer - startTimer;
trace("total pixels: " + pixels.length + ", time elapsed: " + elapsed + "ms");
// start timer
actionTimer = new Timer(4000, 1);
actionTimer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimer);
actionTimer.start();
}

private function onTimer(evt:TimerEvent):void {
(root as MovieClip).play();
}

}

}

The code now gathers all the necessary data and is ready to animate particles. We'll work on that in the next part.

Here's what we will eventually create:


Thanks for reading!

2 comments:

Jury said...

Really nice sand effect! I need this tutorial.:)

Zulu said...

Hi!
How to make the opposite effect of dissolution of the logo in the sand and blowing away?
Thanks.

Post a Comment