300x250 AD TOP

Showing posts with label Photoshop. Show all posts
Showing posts with label Photoshop. Show all posts

Tuesday, 6 March 2012

Tagged under:

Create a Card Flip Effect For Flash Player 10 using ActionScript 3




It surprises me that one of the most commented upon and downloaded classes from my blog is a card flipping effect. With that in mind, I thought it might be an interesting exercise to recreate this effect targeting Flash Player 10. As of the writing of this article, Flash Player 10 Beta (Astro) and the corresponding API documentation have been released. This article will step you through the classes used to achieve a card flipping effect using the new rotationY property that exists in Flash Player 10.


Requirements

TextMate or Your Choice of text editor
TRY BUY
Flash Player 10
Download
Sample files:
CardFlip_FP10.zip

Prerequisites

Before you get started you will need a means by which to publish Flash 10 SWF files. This process involves downloading a nightly build of the Flex 3 SDK. Lee Brimelow of gotoAndLearn.com has a great video tutorial You will also need to make sure that you have the Flash Player 10 Beta installed on your machine. This article also assumes a basic understanding of ActionScript 3. which steps you through the process of downloading and publishing your Flash 10 SWFs.
Getting Started
Let’s take a look at what we are going to be building before we dive into the code. Click on the image below to see the card flip effect (requires Flash Player 10):
Once you have Flash Player 10 installed and have your system setup to publish using the Flex 3 SDK you can get started writing the CardFlip class. Getting started with the class should be easy for those of you familiar with writing ActionScript 3 classes. This article will detail a simple card flip using external image files and external SWF files. To start, we will create the class using image files and then cover the changes that are made to use an external SWF in their place.
First let’s take a look at the classes we will be using in our package:
package{
 import flash.display.Sprite;
 import flash.display.StageQuality;
 import flash.display.Bitmap;
 import flash.events.*;
 import flash.geom.PerspectiveProjection;
 import flash.geom.Point;
 import flash.filters.DropShadowFilter;
 import gs.TweenMax;
As you can see we import a number of classes that will be leveraged to make our class function. Most of these are native classes that you have probably used before with the exception of PerspectiveProject and TweenMax. PerspectiveProject is a class that is new to Flash Player 10, we will get into its usage later.TweenMax is an excellent class which provides easy and efficient tweening for DisplayObject properties and filters. More about TweenMax is available at GreenSock.
Setting up the SWF for the Flex 3 SDK
If you are new to Flex or have not used the Flex SDK you may not have ever worked with Flex Meta Tags. The following code is pretty self-explanatory and is used to setup the stage for our SWF since we are not creating this content in FlexBuilder or the Flash IDE.
[SWF(width="600",height="400",backgroundColor="#FFFFFF",framerate="30")]
Creating the Class
Now that our stage has been setup we can start writing the class and defining our variables. In the example below you will see that I am using additional Flex Meta Tags. In this case I am using the Embed Meta Tag.This will allow me to import external assets into my SWF at compile time. In the example below I import front.jpg and back.jpg. On the line following each of the Embed Meta Tags I create a variable to hold a reference to the external assets. As you can see, the variables defined, CardFrontBitmap and CardBackBitmap are data typed as Class. This will allow me to easily create new instances of these items from within my class. In the code below you will also see variables defined to hold our images, drop shadow, rotation values and which side of the card is being viewed.
public class CardFlip extends Sprite{
[Embed(source='front.jpg')]
 public var CardFrontBitmap:Class;
 [Embed(source='back.jpg')]
 public var CardBackBitmap:Class;

 private var card:Sprite;
 private var cardFront:Sprite;
 private var cardBack:Sprite;
 private var cfb:Bitmap;
 private var cbb:Bitmap;

 private var ds:DropShadowFilter;

 private var cardRotationFront:Number = 0;
 private var cardRotationBack:Number = 45;
 private var side:String = "front";
Creating the Constructor
Now that our variables are out of the way we can setup the constructor for the class. Initially we setup our stage to keep our content from scaling. Next, we create a new DropShadowFilter to be used on our card to help achieve the effect of the card flipping. I also create a new Sprite named card, it will be used to hold all of the Sprites and external images that we will be using in the class.
public function CardFlip(){
 stage.quality = StageQuality.BEST;
 stage.scaleMode = "noScale";
 stage.showDefaultContextMenu = false;
 stage.align = "TL";

 ds = new DropShadowFilter(10, 45, 0x000000, 0.5, 5, 5, 1, 3, false, false, false);

 card = new Sprite();
 card.filters = [ds];
Remember the variables we set when we created our Embed Meta Tags? Now is the time to use them. Here I assign the variables cfb (which I made stand for “Card Front Bitmap”) and cbb (“Card Back Bitmap”) to new instances of our CardFrontBitmap and CardBackBitmap class. The cfb and cbb variables are bitmaps so that they are able to easily load in my external image files.
I want my card to have its registration point in the middle of itself instead of having a 0x and 0y registration.In order to do that I offset the position of cfb and cbb by half of their width and height. You will also notice that I set the scaleX property of the cbb to -1. This flips the image horizontally so that the back of the card appears correctly when the card is flipped. Had I not set the scaleX property here, the back of the card would appear flipped horizontally and the text would be backwards reading from right to left.
Once all of my properties are set I add cfb and cbb to two Sprite containers. I named the sprites cardFront and cardBack. I add these Sprites to the card Sprite I created earlier. You will also notice that I then initially set the visibility to false for the back of the card since we don’t need to see that yet.
cfb = new CardFrontBitmap();
 cfb.x = (cfb.width/2)*-1
 cfb.y = (cfb.height/2)*-1

 cardFront = new Sprite();
 cardFront.addChild(cfb);
 card.addChild(cardFront);

 cbb = new CardBackBitmap();
 cbb.scaleX = -1;
 cbb.x = (cbb.width/2);
 cbb.y = (cbb.height/2)*-1

 cardBack = new Sprite();
 cardBack.addChild(cbb);
 cardBack.visible = false;
 card.addChild(cardBack);


Now that we have our external images created and card face Sprites created and added to our card Sprite, we can add the card Sprite to the Display List. I pushed the x and y properties to 200 just to get my card away from the edge of the stage a bit. I also update the initial rotation of the card to my cardRotationFront variable that I defined.
addChild(card);
 card.x = 200;
 card.y = 200;

card.rotation = cardRotationFront;
Setting the PerspectiveProjection
PerspectiveProjection is new in Flash Player 10. Basically it works somewhat like a 3D camera. It will control the perspective in which we are looking at a give object. PersepectiveProjection defaults to the origin of the parent container. If we did not change our PerspectiveProjection projectionCenter, the card would not appear to be flipped when we set the rotationY property to 180. In order to make sure that we are looking dead center on the card from the front we set the perspectiveProjection projectionCenter to 200x and 200y so that it matches the location to which we have moved the card Sprite to on the stage. You can learn more about PerspectiveProjection by reading the Flash Player 10 API Documentation.
this.transform.perspectiveProjection.projectionCenter = new Point(200,200);
this.transform.perspectiveProjection.fieldOfView = 45;
Creating and Assigning the Flip
To finish off the constructor I add an event to the card Sprite that will call the flip method whenever the card is clicked.
card.addEventListener(MouseEvent.MOUSE_DOWN,flip)
}
Now that we have the flip method being called we can create the flip method itself. The flip method is a pretty basic function. It contains a basic switch statement which checks to see which side of the card we are currently viewing. Once that is determined, it uses the TweenMax class to handle several property changes.The beautiful thing about TweenMax is that it allows us to update multiple properties and filters in one call.If you are unfamiliar with the usage of TweenMax you can rad up on it at GreenSock.
In the flip method below, you will see that for flipping to the back we set the new rotationY property to 180, rotate the card to the specified rotation and modify the drop shadow. The same logic applies in the case of flipping the card from the back to the front again.
In addition to changing the properties mentioned above, we also created a parameter called onUpdate. This allows us to call a function every time the property values have been changed through the cycle of the tween. In this case, we call the setSideVisibility method which we will discuss next.
public function flip(e:MouseEvent):void{
   switch(side){
    case "front":
    side = "back";
    TweenMax.to(card, 1, {rotationY:180,rotation:cardRotationBack,onUpdate:setSideVisibility,dropShadowFilter:{blurX:5, blurY:5, alpha:0.5, distance:-10}});
    break;
    case "back":
    side = "front";
    TweenMax.to(card, 1, {rotationY:0,rotation:cardRotationFront,onUpdate:setSideVisibility,dropShadowFilter:{blurX:5, blurY:5, alpha:0.5, distance:10}});
    break;
   }
  }
Determining Which Side of the Card to Make Visible:
At this point all of the heavy lifting is done. All we need to do is determine which side of the card to show at any given point. This could be done any number of ways; I choose to keep it simple and change the visibility of the card face whenever the very edge of the card is being viewed. In the code below I look for a 90 degree angle in the tween cycle and use that opportunity to swap the visibility of my card faces.
private function setSideVisibility():void{
   if(card.rotationY >= 90){
    cardBack.visible = true;
   }else if(card.rotationY < 90){
    cardBack.visible = false;
   }
  }
 }
}
Wrapping Up and Using External SWFs
So, for the most part this is a pretty simple class to write and implement. It could easily be modified to suit whatever need you may have. To wrap up I thought it might be useful to show you a quick example of how you would use external SWF files in the card flip and use an object other than the card itself to call the flip method. The full class for this example is in the resources zip file for this article so I will only go into some of the basic modifications to the class above that I made.
Really, only two simple changes allow me to import external SWF files for use in the card flip. Take a look at the result below and scroll down to see the changes made:
The first change was a simple one, we simply need to change the source our oEmbed Meta Tags to load the SWFs:
[Embed(source='helloCardFront.swf')]
  public var CardFrontSWF:Class;
  [Embed(source='helloCardBack.swf')]
  public var CardBackSWF:Class;
  [Embed(source='flipButton.swf')]
  public var FlipButton:Class;
The next change is just as simple, we just need change cfb and cbb to Sprites instead of Bitmaps:
private var cfb:Sprite;
private var cbb:Sprite;
Again, the full version of the card flip using both SWFs and images is available in the resources zip for this article. It will be much easier to view them in your text editor in their full glory.
Conclusion
Well, that pretty much sums it up. Flash Player 10 looks like it is going to provide developers with a lot of new toys! As always, I would love to see any updates or creations you make using this class! Please shoot me a comment to show me your experiments and findings!
Tagged under:

Extending the Actionscript 3.0 / Flash CS3 Slideshow with XML




In this second part tutorial we’re going to add some more features to the slideshow application.
Those would be forwarding and rewinding slides, play and pause the slideshow and linkable slides. If you haven’t read the first part tutorial yet, you should do it before continue. You can read it here.

Requirements

Adobe Flash CS3
Try / Buy
Sample files:
slideshowprt2.zip

Adjust XML structure

To store the link of each slide, we just add it as an attribute to the xml file, or to be more accurate, on the image nodes. We also set the target window of the link, which might be neccesary to set in some cases. So, our xml is looking now like this:
<slideshow>
 <image src="images/fly.jpg" desc="Fly" link="http://www.wikipedia.org" target="_self"/>
 <image src="images/mouse.jpg" desc="Computer mouse" link="http://www.google.com" target="_blank"/>
 <image src="images/country.jpg" desc="Country" link="http://www.thetechlabs.com" target="_self"/>
 <image src="images/rope.jpg" desc="Rope" link="http://www.amazon.com" target="_blank"/>
 <image src="images/flower.jpg" desc="Flower" link="http://www.adobe.com" target="_self"/>
</slideshow>

Adding new objects

Since we know, how the xml looks like, we can move on to the knew stuff on the stage. In the mcInfo movieclip we’ve added 4 new buttons. Pause/Play and Forward/Rewind, which will later be assigned to event listeners. On the main stage is a invisible button as big as the size of the slides which will be used to open the links defined in the xml.
Alright, that’s already enough to go on with the coding part.

Adding new variables

First of all, we need to add some new variables to our script.
To know, if the slideshow is currently playing or paused, we set a flag:
var bolPlaying:Boolean = true;
And we also store the current slide link and target window in a variable:
var strLink:String = "";
var strTarget:String = "";

Init function

This is all we need for the variable. Now let’s take a look about what’s changing in the init function of the slideshow. We added the following two lines at the beginning of the function to prevent the user to click any button as long as the xml file hasn’t been loaded completely:
 mcInfo.visible = false;
 btnLink.visible = false;
We’ve also changed the function that will be called once the slideTimer event is fired. Now the function nextSlide() will be called, which we’ll create later:
 slideTimer.addEventListener(TimerEvent.TIMER, nextSlide);
On the last line of the initSlideshow() function we add the event listeners for the buttons. The play and button pause event listeners will be assigned to the same function togglePause(). The next and previous button will be asssigned to the nextSlide() and previousSlide() function. And last of all, we connect the invisible link button that covers the slide area with the goToWebsite() function. And since our slideshow will automatically be playing at the beginning, we hide the play button.
 btnLink.addEventListener(MouseEvent.CLICK, goToWebsite);
 mcInfo.btnPlay.addEventListener(MouseEvent.CLICK, togglePause);
 mcInfo.btnPause.addEventListener(MouseEvent.CLICK, togglePause);
 mcInfo.btnNext.addEventListener(MouseEvent.CLICK, nextSlide);
 mcInfo.btnPrevious.addEventListener(MouseEvent.CLICK, previousSlide);
 mcInfo.btnPlay.visible = false;

onXMLLoadComplete function

Once the xml is completely loaded, we need to show the mcInfo and the button link again. So we add the following two line of code to the function onXMLLoadComplete()


 mcInfo.visible = true;
 btnLink.visible = true;
And since our switchSlide() function will now need to have the index number of the slide, we add a zero as the parameter. We’ll explain the changes in the switchSlide() function later.
 switchSlide(0);

NextSlide/PreviousSide function

Let’s take a look at the nextSlide() and previousSlide() functions. They are called when clicking the next and previous button and the nextSlide() function is also called when the Timer event of the sliderTimer has been fired.
The nextSlide() function checks, if there are any slides left to move on. If so, the current switchSlide() function will be called with the next slide index. If there are no more slides left, then the parameter will be zero standing for the first slide.
The previousSlide() function works the same way. If there are slide we can go back, then the switchSlide() function will be called with the previous slide index. If we’re already on the first slide, then we start the slideshow from the last slide with calling the switchSlide() function with the parameter of the total slide count minus one since the array count begins with zero.
function nextSlide(e:Event = null):void {
 if(intCurrentSlide + 1 < intSlideCount)
  switchSlide(intCurrentSlide + 1);
 else
  switchSlide(0);

}

function previousSlide(e:Event = null):void {
 if(intCurrentSlide - 1 >= 0)
  switchSlide(intCurrentSlide - 1);
 else
  switchSlide(intSlideCount - 1);
}

SwitchSlide function

Since the nextSlide() and previousSlide() functions are now handling the checking for the slides, we can take out this part in the switchSlide() function.
To prevent the user from clicking too fast on the next and previous button, we check, if the tweener is still fading in the slides. If so, we just ignore the action. We’re doing this by adding the following if-statement to the first line of the switchSlide() function.
 if(!Tweener.isTweening(currentContainer)) {
Now we only need to set the new link and the target of it. This will be done exactly the same way as setting the description of the slide.
  strLink  = xmlSlideshow..image[intCurrentSlide].@link;
  strTarget = xmlSlideshow..image[intCurrentSlide].@target;

FadeSideIn function

The fadeSlideIn() function has also a small addition. We check if the slideshow is currently playing and show the number of seconds to the next slide. If the slideshow is paused, we show a status message. The text will be assigned to the lbl_info label.
 if(bolPlaying) {
  mcInfo.lbl_loading.text = "Next slide in " + TIMER_DELAY / 1000 + " sec.";
 } else {
  mcInfo.lbl_loading.text = "Slideshow paused";
 }

OnSlideFadeIn function

On the onSlideFadeIn() function we now need to check, if the slideshow is playing. If so, we can start the timer again:
 if(bolPlaying && !slideTimer.running)
  slideTimer.start();

TogglePause function

The togglePause() function will be called when the user clicks on the play and pause button. First, we check, if the slideshow is playing, if so, we show the play button, set the bolPlaying variable to false, change the status message of the lbl_info label to “Slideshow paused” and stop the timer.
If the slideshow is currently paused, we show the pause button, set the bolPlaying variable to true again, show the time to the next slide and restart the timer.
function togglePause(e:MouseEvent):void {
 if(bolPlaying) {
  mcInfo.btnPlay.visible = true;
  mcInfo.btnPause.visible = false;

  bolPlaying = false;
  mcInfo.lbl_loading.text = "Slideshow paused";
  slideTimer.stop();
 } else {
  mcInfo.btnPlay.visible = false;
  mcInfo.btnPause.visible = true;

  bolPlaying = true;
  mcInfo.lbl_loading.text = "Next slide in " + TIMER_DELAY / 1000 + " sec.";
  slideTimer.reset();
  slideTimer.start();
 }
}

GotToWebsite function

The last function we need to define is the goToWebsite() function. This function will be called once the user clicks on the invisible button. It will check, if the strLink variable is empty or null. If not, the link will be opened.
function goToWebsite(e:MouseEvent):void {
 if(strLink != "" && strLink != null) {
  navigateToURL(new URLRequest(strLink), strTarget);
 }
}
We’ve already reached the end of the second part of the slideshow tutorial. We hope that you enjoyed reading it and we appreciate any kind of feedback.

Note

The feature for clicking the invisible button only works, when you’re running the flash on a webserver or in the flash sdk.

Full code with comments

// import tweener
import caurina.transitions.Tweener;

// delay between slides
const TIMER_DELAY:int = 5000;
// fade time between slides
const FADE_TIME:Number = 1;

// flag for knowing if slideshow is playing
var bolPlaying:Boolean = true;
// reference to the current slider container
var currentContainer:Sprite;
// index of the current slide
var intCurrentSlide:int = -1;
// total slides
var intSlideCount:int;
// timer for switching slides
var slideTimer:Timer;
// slides holder
var sprContainer1:Sprite;
var sprContainer2:Sprite;
// slides loader
var slideLoader:Loader;
// current slide link
var strLink:String = "";
// current slide link target
var strTarget:String = "";
// url to slideshow xml
var strXMLPath:String = "slideshow-data.xml";
// slideshow xml loader
var xmlLoader:URLLoader;
// slideshow xml
var xmlSlideshow:XML;

function initSlideshow():void {
 // hide buttons, labels and link
 mcInfo.visible = false;
 btnLink.visible = false;

 // create new urlloader for xml file
 xmlLoader = new URLLoader();
 // add listener for complete event
 xmlLoader.addEventListener(Event.COMPLETE, onXMLLoadComplete);
 // load xml file
 xmlLoader.load(new URLRequest(strXMLPath));

 // create new timer with delay from constant
 slideTimer = new Timer(TIMER_DELAY);
 // add event listener for timer event
 slideTimer.addEventListener(TimerEvent.TIMER, nextSlide);

 // create 2 container sprite which will hold the slides and
 // add them to the masked movieclip
 sprContainer1 = new Sprite();
 sprContainer2 = new Sprite();
 mcSlideHolder.addChild(sprContainer1);
 mcSlideHolder.addChild(sprContainer2);

 // keep a reference of the container which is currently
 // in the front
 currentContainer = sprContainer2;

 // add event listeners for buttons
 btnLink.addEventListener(MouseEvent.CLICK, goToWebsite);
 mcInfo.btnPlay.addEventListener(MouseEvent.CLICK, togglePause);
 mcInfo.btnPause.addEventListener(MouseEvent.CLICK, togglePause);
 mcInfo.btnNext.addEventListener(MouseEvent.CLICK, nextSlide);
 mcInfo.btnPrevious.addEventListener(MouseEvent.CLICK, previousSlide);

 // hide play button
 mcInfo.btnPlay.visible = false;
}

function onXMLLoadComplete(e:Event):void {
 // show buttons, labels and link
 mcInfo.visible = true;
 btnLink.visible = true; 

 // create new xml with the received data
 xmlSlideshow = new XML(e.target.data);
 // get total slide count
 intSlideCount = xmlSlideshow..image.length();
 // switch the first slide without a delay
 switchSlide(0);
}

function fadeSlideIn(e:Event):void {
 // add loaded slide from slide loader to the
 // current container
 currentContainer.addChild(slideLoader.content);
 // clear preloader text
 mcInfo.lbl_loading.text = "";

 // check if the slideshow is currently playing
 // if so, show time to the next slide. If not, show
 // a status message
 if(bolPlaying) {
  mcInfo.lbl_loading.text = "Next slide in " + TIMER_DELAY / 1000 + " sec.";
 } else {
  mcInfo.lbl_loading.text = "Slideshow paused";
 }

 // fade the current container in and start the slide timer
 // when the tween is finished
 Tweener.addTween(currentContainer, {alpha:1, time:FADE_TIME, onComplete:onSlideFadeIn});
}

function onSlideFadeIn():void {
 // check, if the slideshow is currently playing
 // if so, start the timer again
 if(bolPlaying && !slideTimer.running)
  slideTimer.start();
}

function togglePause(e:MouseEvent):void {
 // check if the slideshow is currently playing
 if(bolPlaying) {
  // show play button
  mcInfo.btnPlay.visible = true;
  mcInfo.btnPause.visible = false;

  // set playing flag to false
  bolPlaying = false;
  // set status message
  mcInfo.lbl_loading.text = "Slideshow paused";
  // stop the timer
  slideTimer.stop();
 } else {
  // show pause button
  mcInfo.btnPlay.visible = false;
  mcInfo.btnPause.visible = true;

  // set playing flag to true
  bolPlaying = true;
  // show time to next slide
  mcInfo.lbl_loading.text = "Next slide in " + TIMER_DELAY / 1000 + " sec.";
  // reset and start timer
  slideTimer.reset();
  slideTimer.start();
 }
}

function switchSlide(intSlide:int):void {
 // check if the last slide is still fading in
 if(!Tweener.isTweening(currentContainer)) {
  // check, if the timer is running (needed for the
  // very first switch of the slide)
  if(slideTimer.running)
   slideTimer.stop();

  // change slide index
  intCurrentSlide = intSlide;

  // check which container is currently in the front and
  // assign currentContainer to the one that's in the back with
  // the old slide
  if(currentContainer == sprContainer2)
   currentContainer = sprContainer1;
  else
   currentContainer = sprContainer2;

  // hide the old slide
  currentContainer.alpha = 0;
  // bring the old slide to the front
  mcSlideHolder.swapChildren(sprContainer2, sprContainer1);

  // create a new loader for the slide
  slideLoader = new Loader();
  // add event listener when slide is loaded
  slideLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, fadeSlideIn);
  // add event listener for the progress
  slideLoader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, showProgress);
  // load the next slide
  slideLoader.load(new URLRequest(xmlSlideshow..image[intCurrentSlide].@src));

  // show description of the next slide
  mcInfo.lbl_description.text = xmlSlideshow..image[intCurrentSlide].@desc;

  // set link and link target variable of the slide
  strLink  = xmlSlideshow..image[intCurrentSlide].@link;
  strTarget = xmlSlideshow..image[intCurrentSlide].@target;

  // show current slide and total slides
  mcInfo.lbl_count.text = (intCurrentSlide + 1) + " / " + intSlideCount + " Slides";
 }
}

function showProgress(e:ProgressEvent):void {
 // show percentage of the bytes loaded from the current slide
 mcInfo.lbl_loading.text = "Loading..." + Math.ceil(e.bytesLoaded * 100 / e.bytesTotal) + "%";
}

function goToWebsite(e:MouseEvent):void {
 // check if the strLink is not empty and open the link in the
 // defined target window
 if(strLink != "" && strLink != null) {
  navigateToURL(new URLRequest(strLink), strTarget);
 }
}

function nextSlide(e:Event = null):void {
 // check, if there are any slides left, if so, increment slide
 // index
 if(intCurrentSlide + 1 < intSlideCount)
  switchSlide(intCurrentSlide + 1);
 // if not, start slideshow from beginning
 else
  switchSlide(0);

}

function previousSlide(e:Event = null):void {
 // check, if there are any slides left, if so, decrement slide
 // index
 if(intCurrentSlide - 1 >= 0)
  switchSlide(intCurrentSlide - 1);
 // if not, start slideshow from the last slide
 else
  switchSlide(intSlideCount - 1);
}

// init slideshow
initSlideshow();
Tagged under:

Simulating PicLens with Flex and Away3D – Part 1

This tutorial article is a Step by step guide for creating a PicLens type 3D photo viewer with Flex and Away3d. This is step 1, of a 3 part tutorial that will sweep many useful techniques used in web application design, Flex, and Flash 3D design.
In the final the result will be this.

Requirements

Pre Requisites

  • Intermediate programming skills
  • Moderate knowledge of AS3
  • Basic Familiarity with OOP
  • Basic knowledge of Flash CS3

Step 0

In this tutorial I will assume that you are familiar with the basics of building an Actionscript Away3D project in Flex. If you haven’t used these two technologies together before, it would be best if you go through my previous tutorial first. Due to the complexity of this application, I will need to skip some things to go faster. So, if something is not explained here, or suddenly a large chunk of code is dropped with no descriptions, it has been covered (hopefully) in the mentioned tutorial.

Step 1 – New Project

Lets get started. Create a new Actionscript Project in Flex and name it “PhotoViewer3D”. Then, download the libraries I listed in the requirements and paste them in the project’s folder.
Now, one of the things that will be covered here is a very simplified application architecture that could be a good starting point for any project you do that implies a moderate level of complexity. For this, all code will be placed in a folder structure, carefully separating functions between different classes, and hence emulating the basics of a Model View Controller design pattern. You should have a general grasp of it by the end of this series.
Right click on the project’s folder in the Flex Navigator and choose New -> Folder, then type “com/li/photoviewer/view” and press Finish. Naming folders this way is known as Reverse Domain Syntax, and is a good programming practice.

Step 2 – Setting up the scene

Go to PhotoViewer3D.as and build a basic main class:
package
{
     import flash.display.Sprite;
     import flash.display.StageAlign;
     import flash.display.StageScaleMode;

     import com.li.photoviewer.view.TestScene;

[SWF(backgroundColor="0x000000", frameRate="30")] 

     public class PhotoViewer3D extends Sprite
     {
          public function PhotoViewer3D()
          {
               stage.scaleMode  = StageScaleMode.NO_SCALE;
               stage.align  = StageAlign.TOP_LEFT;

               var testScene:TestScene = new TestScene();
               addChild(testScene);
          }
     }
}
Line 7 imports an away3D holding class that we haven’t made yet, so we should create it right away. Right click on the “view” folder and choose New -> Actionscript Class and name it “TestScene”. It should have the following code in it:
package com.li.photoviewer.view 

{ 

     import away3d.cameras.Camera3D;
     import away3d.containers.Scene3D;
     import away3d.containers.View3D;
     import away3d.core.clip.RectangleClipping;
     import away3d.core.math.Number3D; 

     import flash.display.Sprite;
     import flash.events.Event; 

     public class TestScene extends Sprite
     {
          private var scene:Scene3D;
          private var camera:Camera3D;
          private var view:View3D; 

          public function TestScene()
          {
               addEventListener(Event.ADDED_TO_STAGE,  init, false, 0, true);
          } 

          public function init(evt:Event):void
          {
               removeEventListener(Event.ADDED_TO_STAGE,  init); 

               initScene();
               initObjects();
               addEventListener(Event.ENTER_FRAME,  renderScene);
          } 

          private function initScene():void
          {
               scene  = new Scene3D(); 

               camera  = new Camera3D();
               camera.zoom  = 10;
               camera.focus  = 200;
               camera.z  = -2000; 

               view  = new View3D();
               view.camera  = camera;
               view.scene  = scene;
               view.x  = stage.stageWidth/2;
               view.y  = stage.stageHeight/2;
               view.clip  = new RectangleClipping(-stage.stageWidth/2,  -stage.stageHeight/2, stage.stageWidth/2, stage.stageHeight/2);
               addChild(view);
          } 

          private function initObjects():void
          {
                
          } 

          private function renderScene(evt:Event):void
          {
               camera.x  = 3*(mouseX - stage.stageWidth/2);
               camera.y  = 3*(mouseY - stage.stageHeight/2);
               camera.lookAt(new Number3D(0, 0, 0)); 

               view.render();
          }
     }
}
NOTE: If any of this shocked you, it has all been explained in the tutorial mentioned in Step 0.

Step 3 – Setting up our PhotoLoader component

Create a new Actionscript Class in the view folder and name it “PhotoLoader”. These will be our 3D photograph object. The class will extend Away3D’s Plane primitive, with a few additional features, more particularly, the ability to load an external image. So, lets start by setting up the plane:
package com.li.photoviewer.view
{
import away3d.primitives.Plane;

public class PhotoLoader extends Plane
     {
private var urlPath:String;

          public function PhotoLoader(urlPath:String)
          { 

               super();

               this.urlPath = urlPath;

               this.rotationX = 90;
               this.width = 640;
               this.height = 480;
               this.segmentsH = 4;
               this.segmentsW = 4;
          }
     }
}
As you can see, we’ve set up the constructor so that when we instantiate a PhotoLoader object, we pass it the image name it has to load as a parameter. Now, go back to TestScene, and in the “iniObjects()” method, make an instance of PhotoLoader and add it to the scene:
var testPhoto:PhotoLoader = new PhotoLoader("imgs/testImage.jpg");

scene.addChild(testPhoto);
The urlPath we passed this time will be irrelevant, but we’ll pass it anyway so no errors are thrown. Go ahead and test the application, you should see something like this:

Step 4 – Skinning the plane with Flash

First of all, we need to give the plane a skin or texture. Even though it will contain an image, it needs to contain something else while it’s loading, and somehow show progress for that load process. For this, we will design some graphics in Flash, and bring them to Flex with what I find to be a very practical and simple technique. We will use a fla file to generate a library.swf file that will contain some graphics for our application, and then embed that swf in Flex and extract the clips as we need them. This is a very useful technique, and I definitely recommend it for any Flex projects that contain design based vector graphics.
Open Flash, create a new AS3 Flash File and save it in your project’s folder, next to PhotoViewer3D.as. Once in it, select File -> Publish Settings and type “assets/library.swf” for the swf output path (deselect the html output). Now, create a new folder next to that fla called “assets” either from your file explorer or by right clicking the project’s folder in the Flex Navigator. Now, if you publish the fla you will see that library.swf shows up in your newly created assets folder.

Step 5 – Creating and extracting the graphics created in Flash

Great, now I wont be describing how to make the graphics in Flash since I believe you must be very familiar with this. You should download the fla and overwrite the one you just made. Sorry! Just wanted to make sure you understood what’s going on. Now, open the downloaded fla. You will see a clip called “LoaderFront” made up of a gray box and a progressBar clip which has in turn a sub clip. The important thing to notice here is that LoaderFront has a Linkage and its Base Class was changed to Sprite. Publish it again, go back to Flex, right click on library.swf in the assets folder and choose Refresh.
This is the only code you’ll need to pull those graphics from Flex, type it at the top of the PhotoLoader class (inside Public class…):
[Embed(source="assets/library.swf", symbol="LoaderFront")]
public var LoaderFront:Class;
You’ve just extracted the symbol from the swf’s library and assigned it to a LoaderFront class. If you create a new instance of this class (and add it to the display tree), you’ll have a graphics object that extends Sprite! And you can treat it as any other sprite. Great isn’t it?

Step 6 – Applying the graphics to the plane

Go back to PhotoLoader.as and create these variables for the class:
private var materialSprite:Sprite;
private var progressBar:Sprite;
private var progressBarSubClip:Sprite;
And then this method:
private function initClips():void
{
     materialSprite  = new LoaderFront();

     progressBar  = Sprite(materialSprite.getChildByName("progressBar"));
     progressBarSubClip  = Sprite(progressBar.getChildByName("subClip"));
}
Now we have references to all the clips that we need. You have to add the “initClips()” call at the end of the constructor class.
Next, type this new method under the last one and also make sure you call it from the constructor:
private function initMaterial():void
{
     var material:MovieMaterial = new MovieMaterial(materialSprite);
     material.smooth  = true;

     this.material = material;
}
And Test the application. You should now see that the plane has taken the graphics as its material. Your PhotoLoader class should look like this so far:
package com.li.photoviewer.view
{
     import away3d.materials.MovieMaterial;
     import away3d.primitives.Plane;
     import flash.display.Sprite;
     public class PhotoLoader extends Plane
     {
         [Embed(source="assets/library.swf", symbol="LoaderFront")]
          public var LoaderFront:Class;
          private var urlPath:String;
          private var materialSprite:Sprite;
          private var progressBar:Sprite;
          private var progressBarSubClip:Sprite;
          public function PhotoLoader(urlPath:String)
          {
               super();
               this.urlPath = urlPath;
               this.rotationX = 90;
               this.width = 640;
               this.height = 480;
               this.segmentsH = 4;
               this.segmentsW = 4;
               initClips();
               initMaterial();
          }
          private function initClips():void
          {
               materialSprite  = new LoaderFront();
               progressBar  = Sprite(materialSprite.getChildByName("progressBar"));
               progressBarSubClip  = Sprite(progressBar.getChildByName("subClip"));
          }
          private function initMaterial():void
          {
               var material:MovieMaterial = new MovieMaterial(materialSprite);
               material.smooth  = true;
               this.material = material;
          }
     }
}

Step 7 – Loading images into the plane

The final thing to do for Part 1 of this tutorial, is implement all the image loading features of our extended plane. For this, we will use a regular Loader instance and add a few event listeners to check for progress and complete events.
Add the loader variable next to the other class variables:
private var loader:Loader;
And call a new method under the “initMaterial()” call in the constructor as “initLoader()”, then build the method:
private function initLoader():void
{
     loader = new Loader();
     loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS,  progressHandler);
     loader.contentLoaderInfo.addEventListener(Event.INIT,  initHandler);
     materialSprite.addChild(loader);
     loader.alpha  = 0;
}
The loader is initialized, two listeners are registered, then the loader is injected to the plane’s material sprite completely invisible. I’ve made it invisible because I’m going to make it fade in once the image has been loaded into it.
Now, lets set up the handlers and a little “setProgress()” utilitary method:
private function progressHandler(evt:ProgressEvent):void
{
     setProgress(evt.bytesLoaded/evt.bytesTotal);
}
private function initHandler(evt:Event):void
{
     Tweener.addTween(loader,  {alpha:1, time:1, transition:"easeoutexpo"});
}
 
private function setProgress(value:Number):void
{
     progressBarSubClip.scaleX  = value;
}
Pretty straight forward. As the image downloads, the progress handler calls the “setProgress()” function which in turn sets the horizontal scale of the progress bar, and when the download completes, or more precisely when the loaded content is initialized, Tweener makes the loader fade in by animating its alpha. Don’t forget to import Tweener for this:
import caurina.transitions.Tweener;

Step 8 – Triggering the load

Next, lets trigger the load by adding a “loadImage()” method as the last method called in the constructor and defining it:
private function loadImage():void
{
     loader.unload();
     loader.alpha  = 0;
     setProgress(0);
     loader.load(new URLRequest(urlPath));
}
Finally, create the imgs folder in the project, download THESE images and paste them in it. Test the app, you should see the image loaded on the plane:

Watch the Demo HERE.
NOTE:
If you have any security issues at this point you should disable the compilers network for now. Do this by right clicking the projects folder, choosing properties, then clicking on the Actionscript Compiler tab and in the Additional Compiler Settings, type “-use-network=false”. Don’t forget to disable this when you go online… This is something anyone using Flex gets to know one way or the other for quickly getting over any sandbox issues.
Next:
On the next part we will continue to develop our application by using multiple instances of this PhotoLoader object, developing advanced camera motion and interactivity.
Tagged under:

Alternativa 3D Series – Tutorial 7 – Sprite3D

A common concept amongst 3D engines is the billboard. A billboard is usually a rectangle that is always orientated to face the camera, presenting the viewer with a face that that is always “front on” regardless of the cameras orientation. More often than not appropriately textured billboards are used in great numbers to represent effects like smoke, rain, snow or fire. This is because billboards require very little processing power: a rectangle is just two triangles, and most modern systems measure triangle throughput in the millions per second. But sometimes you just want to show a 2D object in a 3D world – maybe an icon that hovers over an object or perhaps some text that would be awkward or unpractical to render as a 3D object.

Requirements

View DemoDownload Source Files

Pre-Requesites

You should have read the first tutorial in this series, as it explains a lot of the underlying code that we build on for this tutorial.

Sprite3D

The Alternativa Sprite3D class performs the function of a billboard described above. It is a 2D rectangle that is always orientated to face the camera: you never see a Sprite3D side on. You can assign any material that extends the SpriteMaterial class to a Sprite3D, which with Alternativa 5.5 pretty much means the SpriteMaterial class. Even thought the Sprite3D itself is a rectangle, you can apply a texture with some transparency to display any shape on a Sprite3D. The demo application demonstrates this by displaying a number of circular textures (smiley faces) on the Sprite3D instances.
Another subtle, but important, fact about the Sprite3D class is that it’s geometry is not used to calculate the BSP tree. In simple terms a BSP tree holds all the scene geometry in a format that clearly identifies a polygon as being either in front of or behind any other polygon. It’s an efficient way to sort a 3D scene, and it allows the Alternativa 3D engine to properly render intersecting polygons. However, because the BSP tree is recalculated every time a new polygon is added, removed, moved or rotated you do have to be mindful of the performance impact that modifying 3D objects in the scene can have. Because the Sprite3D is not used in the calculation on the BSP tree it does not incur the same performance cost.
In the demo application each Sprite3D is contained in an instance of the SpriteBounce class.

SpriteBounce.as

package
{
 import alternativa.engine3d.core.Sprite3D;
 import alternativa.engine3d.materials.SpriteMaterial;
 import alternativa.types.Point3D;
 import alternativa.types.Texture;

 import mx.core.Application;

 public class BounceSprite extends MeshObject
 {
     protected static const BOX_SIZE:Number = 80;
     protected static const SPEED:Number = 10;
     protected static const SCALE:Number = 0.2;
     protected var direction:Point3D = null;

     public function BounceSprite()
     {
         super();
     }

     public function startupBounceSprite(texture:Texture):BounceSprite
     {
         var sprite:Sprite3D = new Sprite3D();
         // each Sprite3D needs its own SpriteMaterial, so create a new one with the supplied texture
         sprite.material = new SpriteMaterial(texture, Application.application.sliderAlpha.value, true);
         // set a random initial position
         sprite.coords = Point3D.random(-BOX_SIZE, BOX_SIZE, -BOX_SIZE, BOX_SIZE, -BOX_SIZE, BOX_SIZE);
         // set the initial scale
         sprite.scaleX = sprite.scaleY = sprite.scaleZ = Application.application.sliderScale.value;
         // create a random initial direction
         direction = Point3D.random(-1, 1, -1, 1, -1, 1);
         direction.normalize();
         // let the MeshObject deal with adding the Sprite3D to the scene
         super.startupModelObject(sprite);

         return this;
     }

     public override function enterFrame(dt:Number):void
     {
         super.enterFrame(dt);

         // find the new point to move to
         var newPoint:Point3D = direction.clone();
         newPoint.multiply(dt * SPEED);
         newPoint.add(model.coords);

         // keep the point inside a box whose size is defined by BOX_SIZE
         // if the point is outside the box, reverse the direction
         if (newPoint.x > BOX_SIZE)
         {
             newPoint.x = BOX_SIZE;
             direction.x = -1;
         }
         else if (newPoint.x < -BOX_SIZE)
         {
             newPoint.x = -BOX_SIZE;
             direction.x = 1;
         }

         if (newPoint.y > BOX_SIZE)
         {
             newPoint.y = BOX_SIZE;
             direction.y = -1;
         }
         else if (newPoint.y < -BOX_SIZE)
         {
             newPoint.y = -BOX_SIZE;
             direction.y = 1;
         }

         if (newPoint.z > BOX_SIZE)
         {
             newPoint.z = BOX_SIZE;
             direction.z = -1;
         }
         else if (newPoint.z < -BOX_SIZE)
         {
             newPoint.z = -BOX_SIZE;
             direction.z = 1;
         }

         // move the Sprite3D
         model.coords = newPoint;
         // update the alpha
         Sprite3D(model).material.alpha = Application.application.sliderAlpha.value;
         // update the scale
         Sprite3D(model).scaleX = Sprite3D(model).scaleY = Sprite3D(model).scaleZ = Application.application.sliderScale.value;
     }
 }
}
The SpriteBounce class extends the MeshObject class. We create a new function called startupBounceSprite, which holds the code that creates and initialises the Sprite3D. First we create a new instance of the Sprite3D class. The Sprite3D constructor optionally accepts a name parameter, but this has no relevance in our demo so we leave it out. Next we assign a texture to the Sprite3D through the material property. Each Sprite3D needs it’s own instance of a SpriteMaterial class. For an example of how this works take a look at the following code.
var s1:Sprite3D = new Sprite3D();
var s2:Sprite3D = new Sprite3D();
var tex:SpriteTextureMaterial = new SpriteTextureMaterial(someTexture);
s1.material = tex;
s2.material = tex;
In the example above, s1.material would be null, while s2.material would be equal to tex. The two instances of Sprite3D can not share a SpriteTextureMaterial. To avoid this you would do the following.
var s1:Sprite3D = new Sprite3D();
var s2:Sprite3D = new Sprite3D();
var tex1:SpriteTextureMaterial = new SpriteTextureMaterial(someTexture);
var tex2:SpriteTextureMaterial = new SpriteTextureMaterial(someTexture);
s1.material = tex1;
s2.material = tex2;
Now each Sprite3D has it’s own texture, and will be displayed properly in the scene.
Once it is assigned a texture we position the Sprite3D in the scene via the coords property. We then set the scale of the Sprite3D.
The scale property of the Sprite3D class is the only way to set it’s size. In the demo you can modify the scale using the slider on the left hand side of the screen. The slider only lets you select a value between 0 and 1, but you could set a scale of more than 1 if you wanted to.
With the Sprite3D created and initialised we create a random normalised vector to serve as the direction that the Sprite3D will initially move in. We then make a call to the startupModelObject from the base MeshObject class, supplying the Sprite3D we just created for the startupModelObject function to add to the scene.
The enterFrame function contains the code to move the Sprite3D and and contain it inside an area defined by the static BOX_SIZE property. If the Sprite3D moves outside of this box the direction it is moving in (as defined by the direction property) is reversed. It also updates the scale and transparency of the Sprite3D against the GUI sliders specified in the MXML file.

ApplicationManager.as

package
{
 import alternativa.engine3d.core.Mesh;
 import alternativa.engine3d.primitives.Plane;
 import alternativa.types.Point3D;

 import flash.display.DisplayObject;

 import mx.core.Application;

 /**
 * The ApplicationManager holds all program related logic.
 */
 public class ApplicationManager extends BaseObject
 {
     protected static const SPRITE3D_COUNT:int = 20;
     protected static const CAMERA_MOVEMENT_BOX:Number = 300;
     protected static const CAMERA_MOVEMENT_BOX_HALF:Number = CAMERA_MOVEMENT_BOX / 2;
     protected static const CAMERA_SPEED:Number = 100;
     protected var target:Point3D= null;
     protected static var instance:ApplicationManager = null;
     protected var verticalWall:MeshObject = null;
     protected var horizontalWall:MeshObject = null;
     public var rotateSpeed:Number = 90;

     /**
     * returns the singelton instance of the ApplicationManager
     */
     public static function get Instance():ApplicationManager
     {
         if (instance == null)
             instance = new ApplicationManager();
         return instance;
     }

     public function ApplicationManager()
     {
         super();
     }

     /**
     * Initialise the ApplicationManager
     */
     public function startupApplicationManager():ApplicationManager
     {
         super.startupBaseObject();

         // create the rotating planes
         updateWalls();

         // get a random target for the camera movement
         target = generateRandomTarget();

         // create the BounceSprite objects, assigning a random texture
         for (var i:int = 0; i < SPRITE3D_COUNT; ++i)
         {
             var rand:int = int(Math.random() * 4);
             switch (rand)
             {
             case 0:
                 new BounceSprite().startupBounceSprite(ResourceManager.BlueSmileyTex);
                 break;
             case 1:
                 new BounceSprite().startupBounceSprite(ResourceManager.RedSmileyTex);
                 break;
             case 2:
                 new BounceSprite().startupBounceSprite(ResourceManager.GreenSmileyTex);
                 break;
             case 3:
                 new BounceSprite().startupBounceSprite(ResourceManager.YellowSmileyTex);
                 break;
             }
         }
         return this;
     }

     public function updateWalls():void
     {
         // if the horizontalWall has not been created, and it is selected, create it
         if (Application.application.chkGreenWall.selected && horizontalWall == null)
         {
             horizontalWall = new MeshObject().startupModelObject(new Plane(175, 175, 4, 4));
             Mesh(horizontalWall.model).cloneMaterialToAllSurfaces(ResourceManager.GreenTex);
             horizontalWall.model.rotationX = Math.PI/2.0;
         }
         // if the horizontalWall has been created, and it is not selected, destroy it
         else if (!Application.application.chkGreenWall.selected && horizontalWall != null)
         {
             horizontalWall.shutdown();
             horizontalWall = null;
         }
         // if the verticalWall has not been created, and it is selected, create it
         if (Application.application.chkBlueWall.selected && verticalWall == null)
         {
             verticalWall = new MeshObject().startupModelObject(new Plane(175, 175, 4, 4));
             Mesh(verticalWall.model).cloneMaterialToAllSurfaces(ResourceManager.BlueTex);
         }
         // if the verticalWall has been created, and it is not selected, destroy it
         else if (!Application.application.chkBlueWall.selected && verticalWall != null)
         {
             verticalWall.shutdown();
             verticalWall = null;
         }
     }

     public override function enterFrame(dt:Number):void
     {
         // rotate the planes
         if (horizontalWall != null)
             horizontalWall.model.rotationX += rotateSpeed * Math.PI / 180 * dt;

         if (verticalWall != null)
             verticalWall.model.rotationY += rotateSpeed * Math.PI / 180 * dt;

         // move the camera around to a random position, if the option is selected
         if (Application.application.chkCameraMoves.selected)
         {
             var direction:Point3D = Point3D.difference(target, Application.application.engineManager.camera.coords);
             var distToMoveThisFrame:Number = dt * CAMERA_SPEED;

             // don't overshoot the target point
             if (Math.pow(distToMoveThisFrame, 2) >= direction.lengthSqr)
             {
                 distToMoveThisFrame = direction.length;
                 target = generateRandomTarget();
             }

             direction.normalize();
             direction.multiply(dt * CAMERA_SPEED);

             // reposition the camera
             Application.application.engineManager.camera.coords = Point3D.sum(Application.application.engineManager.camera.coords, direction);

             // look at the centre of the stage
             Application.application.engineManager.cameraController.lookAt(new Point3D());
         }
     }

     protected function generateRandomTarget():Point3D
     {
         // get a random target
         return Point3D.random(-CAMERA_MOVEMENT_BOX, CAMERA_MOVEMENT_BOX, -CAMERA_MOVEMENT_BOX, CAMERA_MOVEMENT_BOX, -CAMERA_MOVEMENT_BOX, CAMERA_MOVEMENT_BOX);
     }
 }
}
We use the ApplicationManager to create several instances of the BounceSprite class, as well as two rotating planes which are used to demonstrate that the Sprite3D‘s are properly depth sorted in the scene. Take a look at the startupApplicationManager function. Here we create the two rotating planes by calling updateWalls, and then get a random point in space for the camera to move to if the Move Camera checkbox has been selected. Next we enter a loop to create the SpriteBounce objects. The Texture that is assigned to the startupBounceSprite function is randomly selected from four Texture‘s created in the ResourceManager class. While each Sprite3D needs it’s own SpriteMaterial assigned to the material property, those SpriteMaterial‘s can share the underlying Texture class. We take advantage of this to save some memory by not creating a new Texture instance for each SpriteMaterial instance.
Take some time now to run the demo application. Pay close attention to the Sprite3D objects as they pass through the rotating planes (you can slow the speed that the planes rotate at using the GUI controls). Do you see how the Sprite3D‘s are either entirely in front or entirely behind the translucent plane? This is because the Sprite3D is only depth sorted in the scene, rather than being part of the BSP tree (which allows other 3D objects to intersect and still be rendered properly). Because the Sprite3D objects are all parallel to each other (they all face the camera) one Sprite3D can not intersect another. You will occasionally see an instance where a Sprite3D should intersect another 3D object (in this case the rotating planes), but is rendered as being completely in front of or behind that intersecting object. However these situations should be rare, and the performance boost from not having to recalculate the BSP tree is worth this minor inconvenience.
Because of their simplicity and the fact that they don’t participate in the construction of the BSP tree, Sprite3D‘s are a very useful way of displaying flat objects in a 3D scene while still maintaining a high frame rate.
Tagged under:

Create Random Torn Photos with Actionscript 3.0


One of the most powerful sets of classes in ActionScript 3 revolve around BitmapData and pixel manipulation.
In this tutorial we are going to look at dynamically creating a photo with torn edges by compositing several images together.

Requirements

I created this tutorial using Flash Builder 4 Beta but you can easily use Flex Builder 3 or Flash CS 3/4.
Download Source Files

Pre-Requesites

You will need to download the PSD file as well as the folder of images included in the source files package. You should also have a basic understanding of the Bitmap, BitmapData, and the Loader class. I will explain in each step what is going on so if you have never used the Bitmap or BitmapData class you should be fine. Here is an example of what we will end up with, refresh this tutorial to see different masks:
Before we get started lets take a moment to discuss exactly what we are going to do:
  1. We will need to load in our photo.
  2. Then we need to load in our supporting images: an alpha mask, image texture, and edges mask.
  3. We will copy over the BitmapData of our photo using the alpha mask to cut out an image with transparent edges.
  4. Add 3 layers of the texture image with a blend mode set to multiply.
  5. Cut out edges from our texture using the edges mask image.
  6. Rinse and repeat.
Now that we have a plan, lets get started!

Step 1: Creating A Preloader

We are going to set up a simple preloader since each image we generate is comprised of several “layers”. Create a new project called TornImageDemo and open up the Doc Class.
create_project
Lets use the following code in the Doc Class:
package
{
 import flash.display.Bitmap;
 import flash.display.Loader;
 import flash.display.Sprite;
 import flash.display.StageAlign;
 import flash.display.StageScaleMode;
 import flash.events.Event;
 import flash.events.IOErrorEvent;
 import flash.net.URLRequest;
 import flash.system.LoaderContext;
 import flash.utils.Dictionary;

 [SWF( backgroundColor="#B5AA97", framerate="31" )]
 /**
  * @author Jesse Freeman aka @theFlashBum | http://jessefreeman.com
  */
 public class TornImageDemo extends Sprite
 {

  private static const PHOTO : String = "photo";
  private static const MASK : String = "mask";
  private static const TEXTURE : String = "texture";
  private static const EDGES_MASK : String = "edges_mask";
  private static const BASE_URL : String = "images";
  private var loader : Loader = new Loader( );
  private var currentlyLoading : Object;
  private var preloadList : Array = new Array( {name:MASK, src:"photo_mask.png"}, {name:PHOTO, src:"photo.jpg"}, {name:TEXTURE, src:"photo_texture.jpg"}, {name:EDGES_MASK, src: "photo_edges_mask.png"} );
  private var layers : Dictionary = new Dictionary( true );
  private var context : LoaderContext;
  private var skinPath:String;

  public function TornImageDemo()
  {
   stage.scaleMode = StageScaleMode.NO_SCALE;
   stage.align = StageAlign.TOP_LEFT;

   context = new LoaderContext( );
   context.checkPolicyFile = true;

   skinPath = "/skin1/";
   preload( );
  }

  /**
   * Handles preloading our images. Checks to see how many are left then
   * calls loadNext or compositeImage.
   */
  protected function preload() : void
  {
   if (preloadList.length == 0)
   {
    init( );
   }
   else
   {
    loadNext( );
   }
  }

  /**
   * Loads the next item in the prelaodList
   */
  private function loadNext() : void
  {
   currentlyLoading = preloadList.shift( );
   loader.contentLoaderInfo.addEventListener( Event.COMPLETE, onLoad );
   loader.contentLoaderInfo.addEventListener( IOErrorEvent.IO_ERROR, onError );

   loader.load( new URLRequest( BASE_URL + skinPath + currentlyLoading.src ), context );
  }

  private function onError(event : IOErrorEvent) : void
  {
   trace("IOErrorEvent", event );
   preload();
  }

  /**
   * Handles onLoad, saves the BitmapData then calls preload
   */
  private function onLoad(event : Event) : void
  {
   loader.contentLoaderInfo.removeEventListener( Event.COMPLETE, onLoad );

   layers[currentlyLoading.name] = Bitmap( event.target.content ).bitmapData;

   currentlyLoading = null;

   preload( );
  }

  private function init():void
  {
   // Need to do something here
  }

 }
}
As you can see we are creating a simple “loop” through our preload, load and onLoad methods. We have also setup an array that contains each of our images we will preload. After setting up the stage we call the preload method and check the length of our preload array. If there is an item in the array we call load and begin loading it. Once it is loaded we save it to a dictionary then recall preload. Once preloading is done we call init and are ready to go. Lets talk about each of the images we are going to load.

Step 2: Creating the Image Mask Template

Lets open the PSD you downloaded in the Pre-Requesists part of this tutorial.
psd_tempate
As you can see I have set this up to show you what our end result will look like in Flash. If you check the layer comps you will see how each layer of the photo will need to be exported.
Lets talk about what each layer does:
layer_comps
  • Preview represents what the image will look like in flash when we apply the masks and textures. You do not need to export this.
  • Photo Mask represents the alpha mask we will be using to “cut” out transparent edges from photo. Alphas masks work just like regular masks except you use them when calling copyPixels on BitmapData.
  • Texture represents the actual photo texture we will be using to add some detail to our photo. The texture is also where we will get our edges from when using the edge mask
  • Edges Mask represents another alpha mask that we’ll use to cut out the edges around the photo. Edges represent any folded over pieces of paper or small tears around the edge of the image.
We will need to save out these layers and put them in our project. The Mask and Edge Mask should be PNG-24s and the Texture can be a JPEG. The images zip (you should have downloaded in the pre-requesites section) has an images folder that contains all of our outputted images for this template along with 2 others so we can randomize the mask we apply to the image. Here are some screen I took when creating this template showing you my file settings:
photo_mask
This is the photo mask as a PNG-24.
photo_texture This is the photo texture saved out as a JPEG. Notice how it is not transparent? This is because we will use the above alpha mask to cut out the parts of the image we don’t need.
edges_mask
Our final image is the edges mask. This is similar to our photo mask and should be a PNG-24. Once you unzip the images file, move it into your bin-debug folder or wherever you compile your final SWF. As you can see I have broken up each folder into skinX and in each set is a photo_mask.png, photo_edges_mask.png,a photo.jpg and photo_texture.jpg.

Step 3: Applying the layers

At this point we have hardcoded our class to load in mask skin1. Do a quick build and check your browser connections to make sure the photo, mask, edges mask and texture are correctly loading.
connections
Sometimes you have to play around with the local security settings of the Flash Player to enable local file access to the images. Likewise it is important to have full security access to these images when running this from a server them since we will be manipulating their BitmapData. Making sure you have a cross domain file is key when deploying these types of projects. Now that everything is loading lets add the following method:
/**
 * Composite image
 */
private function compositeImage() : void
{
 // Create Bitmap data for final image
 var bmd : BitmapData = new BitmapData( layers[PHOTO].width, layers[PHOTO].height, true, 0xffffff );

 // Get width and height for cutting out image
 var rect : Rectangle = new Rectangle( 0, 0, layers[PHOTO].width, layers[PHOTO].height );
 var pt : Point = new Point( 0, 0 );

 // This is our container while we apply the texture
 var imageComposite : BitmapData = new BitmapData( layers[PHOTO].width, layers[PHOTO].height, true, 0xffffff );

 // Copy pixel data from the photo over to the container using the layers[MASK] to cut out the shape
 imageComposite.copyPixels( layers[PHOTO], rect, pt, layers[MASK], null, true );

 if(layers[TEXTURE])
 {
  // Draw on top of container with the texture and apply Darken + Multiply blend modes
  imageComposite.draw( layers[TEXTURE], null, null, BlendMode.MULTIPLY, null, true );
  imageComposite.draw( layers[TEXTURE], null, null, BlendMode.MULTIPLY, null, true );
  imageComposite.draw( layers[TEXTURE], null, null, BlendMode.MULTIPLY, null, false );
 }

 if(layers[EDGES_MASK])
 {
  // Copy the edges on top of the the entire composited image
  imageComposite.copyPixels( layers[TEXTURE], rect, pt, layers[EDGES_MASK], null, true );
 }

 // Copy over the composite image to the BitmapData using the mask to cut out it's shape
 bmd.copyPixels( imageComposite, rect, pt, layers[MASK], null, true );

 // Create Bitamp to test display and add to stage
 var finalImage : Bitmap = new Bitmap( bmd );
 addChild( finalImage );
 finalImage.x = 50;
 finalImage.y = 50;
}
You will also need to import the following classes:
import flash.display.BitmapData;
import flash.display.BlendMode;
import flash.geom.Point;
import flash.geom.Rectangle;
Finally add the following method call to our init method:
compositeImage();
Now if you do a compile you should see the following image:
first_run_preview
Lets talk about what is going on under the hood of compositeImage. As you can see we are pulling each image out from the dictionary, and either copying out or drawing over the BitmapData if our main photo. Lets go over a few of the main actions going on here: First we need to set a temporary bitmap to store our composite image in.
// This is our container while we apply the texture
var imageComposite : BitmapData = new BitmapData( layers[PHOTO].width, layers[PHOTO].height, true, 0xffffff );
Next we will copy over the photo’s Bitmap Data using the photo_mask.png as an alpha mask. As I mentioned earlier this acts just like a mask and insures that the BitmapData we copy over has transparent edges.
// Copy pixel data from the photo over to the container using the layers[MASK] to cut out the shape
imageComposite.copyPixels( layers[PHOTO], rect, pt, layers[MASK], null, true );
Once we have a foundation of our photo we can apply the texture. Here we multiply the texture 3 times to our image to create a little depth and fill in the wrinkles of the photo.
if(layers[TEXTURE])
{
 // Draw on top of container with the texture and apply Darken + Multiply blend modes
 imageComposite.draw( layers[TEXTURE], null, null, BlendMode.MULTIPLY, null, true );
 imageComposite.draw( layers[TEXTURE], null, null, BlendMode.MULTIPLY, null, true );
 imageComposite.draw( layers[TEXTURE], null, null, BlendMode.MULTIPLY, null, false );
}
Finally we can create our edges by applying an alpha mask to the texture image and using copyPixels to back on top of our
if(layers[EDGES_MASK])
{
 // Copy the edges on top of the the entire composited image
 imageComposite.copyPixels( layers[TEXTURE], rect, pt, layers[EDGES_MASK], null, true );
}
Now with the compositing done we can copy over the imageComposite BitmapData to a clean BitmapData instance using the alpha mask one last time to clean up any transparency we want to preserve.
// Copy over the composite image to the BitmapData using the mask to cut out it's shape
bmd.copyPixels( imageComposite, rect, pt, layers[MASK], null, true );

// Create Bitamp to test display and add to stage
var finalImage : Bitmap = new Bitmap( bmd );
addChild( finalImage );
All done, see how easy this is. Of course I am sure this can be optimized even further but for the purposes of this quick example the effect works perfectly.

Step 4: Randomize

Since we know we have 6 sets of skins (in our images folder) it is really easy to randomize how our photo gets rendered. Simply replace the following line in our TornImageDemo constructor:
skinPath = "/skin1";
with the following:
skinPath = "/skin"+Math.round(Math.random()*5+1)+"/";
Now when you recompile and hit refresh a random number is added to our skin folder.

Conclusion

As you have seen, using Alpha Masks when copying pixel data is an incredibly easy technique to use. To give you an idea of how much file size this technique saves us let take a quick look at how big a PNG-24 from PhotoShop would be.
png_version
The above image from PhotoShop came out to 478k
first_run_preview
This image was dynamically created from the following images:
  • photo_edges_mask.png – 16k
  • photo_mask.png – 12k
  • photo_texture.jpg – 20k
  • photo.jpg – 56k
  • total – 104k
As you can see, even though we have loaded up 4 times as many images as the PhotoShop PNG, we have actually saved 374k. Also we can now apply this effect to any number of images on the fly verses creating a transparent PNG each time by hand. I hope you enjoyed this simple tutorial and I would love to see how you use it in your next project. Feel free to leave a comment with a link to what you have done.