The Code

The main engine behind the image transformations in this tutorial is our custom BitmapTransformer class. You will find the description of the class - the ideas behind it as well as the list of public methods - in our Cube in Bloom: 3D Menu on a Cube with Perspective tutorial. In particular, on the page devoted to the BitmapTransformer class: Cube in Bloom: 3D Menu on a Cube with Perspective - The BitmapTransformer Class.

Free Transform Applet - The Code

Let us look at the code in the first of the two applets in this tutorial. We keep our comments in the form of comments within the code.

/*
We are importing the custom AS3 class, BitmapTransformer. For Flash to find the class the folder 'com' with the nested subfolders has to reside in the same directory as this 'fla' file.
*/

import com.flashandmath.bitmaps.BitmapTransformer;

/*
We are creating a Sprite, spContainer, that will contain the image as well as the circular draggable handles. spContainer is positioned (by default) at (0,0) in the main movie. Thus, the coordinates in spContainer are the same as the coordinates in the main movie.
*/

var spContainer:Sprite=new Sprite();

this.addChild(spContainer);

/*
The width and the height of our image. If you replace it with your own image, it suffices to change the values of the two variables below and the name 'bdLab' to the name of your image on the line after that and at the places in the script where the name appears.
*/

var imgWidth:Number=300;

var imgHeight:Number=225;

/*
We imported our 300 by 225 pixels jpeg image to the Library and linked it to AS3 with the name 'Lab'. When linked, the image becomes a subclass of the BitmapData class. The name of the subclass is 'Lab'. Below, we are creating an instance of 'Lab' and storing it in the variable 'bdLab'. A BitmapData object is not a DisplayObject and it cannot be added to the Display List. It is an object that holds all the pixel information about the image. We will use an instance of our custom BitmapTransformer class and its method 'mapBitmapData' to 'draw' the actual image in spContainer using the pixel information.
*/

var bdLab:BitmapData=new Lab(imgWidth,imgHeight);

/*
We are creating an instance of the BitmapTransformer class. We pass the dimensions of the image to the constructor as well as the horizontal and vertical number of subdivisions wchich the instance will use when triangulating the image. 5 and 5 are the default values. We increase them to 10 and 10 for a smoother effect.
*/

var bdTransform:BitmapTransformer=new BitmapTransformer(imgWidth,imgHeight,10,10);

/*
The initial position of the upper left corner of our image after the BitmapData object 'bdLab' is drawn by bdTransform when the applet starts.
*/

var imgX:Number=150;

var imgY:Number=95;

/*
The size of the circular handle and the bounds beyond which we don't want the handles to be dragged.
*/

var dotSize:Number=14;

var maxRight:Number=600-dotSize;

var maxBottom:Number=390-dotSize;

var minTop:Number=40+dotSize;

/*
We drew our circular handle on the Stage, converted it a MovieClip 'Dot', and stored in the Library. We linked the clip to AS3 under the name 'Dot'. Now we are creating four instances of 'Dot', mcCorner0, mcCorner1, etc., and adding them to the Display List as children of spContainer. They will serve as our draggable handles. Note that we are assgning a name to each instance using the 'name' property of the DisplayObject class.
*/

/*
We are also creating four variables curV0, ..., curV3 of datatype 'Point'. These variables will store the current x and y coordinates of each vertex of our distorted image. The x and y coordinates of the handles will at all times coincide with the values stored in curV0, curV1, etc. We need the curV0, curV1,... variables for use with the 'mapBitmapData' method of the BitmapTransform class.
*/

var mcCorner0:Dot=new Dot();

spContainer.addChild(mcCorner0);

mcCorner0.name="0";

var curV0:Point=new Point(imgX,imgY);

//We position initially the handles at the corners of our image.

mcCorner0.x=curV0.x;

mcCorner0.y=curV0.y;

var mcCorner1:Dot=new Dot();

spContainer.addChild(mcCorner1);

mcCorner1.name="1";

var curV1:Point=new Point(imgX+imgWidth,imgY);

mcCorner1.x=curV1.x;

mcCorner1.y=curV1.y;

var mcCorner2:Dot=new Dot();

spContainer.addChild(mcCorner2);

mcCorner2.name="2";

var curV2:Point=new Point(imgX+imgWidth,imgY+imgHeight);

mcCorner2.x=curV2.x;

mcCorner2.y=curV2.y;

var mcCorner3:Dot=new Dot();

spContainer.addChild(mcCorner3);

mcCorner3.name="3";

var curV3:Point=new Point(imgX,imgY+imgHeight);

mcCorner3.x=curV3.x;

mcCorner3.y=curV3.y;

/*
The next two variables will store the current handle being dragged and its name.
*/

var draggedCorner:MovieClip;

var curCornerName:String;

/*
We are using now our instance of the BitmapTransformer class to draw the pixel data from 'bdLab' into spContainer in the position corresponding to the position of vertices curV0,...,curV3. (More, precisely the class uses 'BitmapFill' and triangulation rather than the 'draw' method of the BitampData class. But that goes to internal workings of the class, so we do not have to be concerned about it.) We are using the public 'mapBitmapData' method of the BitmapTransformer class to draw the image. We will use this method repeatedly throughout the script to redraw the image in each new set of vertices.
*/

bdTransform.mapBitmapData(bdLab,curV0,curV1,curV2,curV3,spContainer);

//A few touches to improve appearance.

spContainer.filters = [ new DropShadowFilter(8,45,0x666666,1.0,4.0,4.0,1.0,1) ];

mcCorner0.alpha=0.3;

mcCorner1.alpha=0.3;

mcCorner2.alpha=0.3;

mcCorner3.alpha=0.3;

/*
Besides the BitmapTransformer's 'mapBitmapData' method, the way listeners are assigned to objects (or removed at the right time) is crucial to functioning of the applet. They are self-explanatory, though.
*/

mcCorner0.addEventListener(MouseEvent.MOUSE_DOWN, startCornerMove);

mcCorner1.addEventListener(MouseEvent.MOUSE_DOWN, startCornerMove);

mcCorner2.addEventListener(MouseEvent.MOUSE_DOWN, startCornerMove);

mcCorner3.addEventListener(MouseEvent.MOUSE_DOWN, startCornerMove);

 

function startCornerMove(evt:MouseEvent):void {

draggedCorner = MovieClip(evt.currentTarget);

curCornerName=draggedCorner.name;

spContainer.setChildIndex(draggedCorner,spContainer.numChildren-1);

stage.addEventListener(MouseEvent.MOUSE_MOVE, dragCorner);

stage.addEventListener(MouseEvent.MOUSE_UP, stopDragging);

}

 

function dragCorner(e:MouseEvent):void {

/*

We are clearing bitmap drawing in spContainer as the new image

will be drawn based on the new positions of the corners.

*/

spContainer.graphics.clear();

draggedCorner.x = spContainer.mouseX;

draggedCorner.y = spContainer.mouseY;

if(draggedCorner.x<dotSize){

draggedCorner.x=dotSize;

}

if(draggedCorner.x>maxRight){

draggedCorner.x=maxRight;

}

if(draggedCorner.y<minTop){

draggedCorner.y=minTop;

}

if(draggedCorner.y>maxBottom){

draggedCorner.y=maxBottom;

}

this["curV"+curCornerName].x=this["mcCorner"+curCornerName].x;

this["curV"+curCornerName].y=this["mcCorner"+curCornerName].y;

//We are drawing new image.

bdTransform.mapBitmapData(bdLab,curV0,curV1,curV2,curV3,spContainer);

e.updateAfterEvent();

}

 

function stopDragging(evt:MouseEvent):void {

stage.removeEventListener(MouseEvent.MOUSE_MOVE, dragCorner);

stage.removeEventListener(MouseEvent.MOUSE_UP, stopDragging);

}

 

btnReset.addEventListener(MouseEvent.CLICK,resetImg);

 

function resetImg(e:MouseEvent):void {

spContainer.graphics.clear();

curV0.x=imgX;

curV0.y=imgY;

curV1.x=imgX+imgWidth;

curV1.y=imgY;

curV2.x=imgX+imgWidth;

curV2.y=imgY+imgHeight;

curV3.x=imgX;

curV3.y=imgY+imgHeight;

mcCorner0.x=curV0.x;

mcCorner0.y=curV0.y;

mcCorner1.x=curV1.x;

mcCorner1.y=curV1.y;

mcCorner2.x=curV2.x;

mcCorner2.y=curV2.y;

mcCorner3.x=curV3.x;

mcCorner3.y=curV3.y;

bdTransform.mapBitmapData(bdLab,curV0,curV1,curV2,curV3,spContainer);

}

 

'Gummy' Bitmap Applet - The Code

Most of the code behind the 'gummy' bitmap applet is the same as above. The only difference is in adding a tweened motion that brings each vertex to its original position after the user releases it. That requires creating an instance of the AS3 Tween class and changing listeners a bit.

We tween the motion of the vertices using the idea that we introcuced in the tutorial: Tween Tricks in ActionScript 3. We create an auxiliary object, 'paramObj', with a property 't'. We use an instance of the Tween class, named 'tw', to 'tween' the property 't'; that is, to change 't' over time between two given values. Using TweeEvent.MOTION_CHANGE event, we retrieve the changing values of 't' to create the motion of a vertex from the release point to the original postion. Simply speaking, we use 't' to parametrize the segment between those two points. Let us look at the main portions of the code related to tweening.

/*
We import the built-need classes that we need for tweening (besides those that are automatically imported by Flash).
*/

import fl.transitions.Tween;

import fl.transitions.TweenEvent;

import fl.transitions.easing.*;

...........................

/*
We choose an easing function and create an object, 'paramObj', whose property 't' will be tweened. The variable 'tw' will store an instance of the Tween class that will be created when the user releases a vertex.
*/

var curEase:Function = Elastic.easeOut;

var paramObj:Object = {t:0};

var tw:Tween;

...........................

/*
Since we will be tweening the motion of each vertex, after being dragged, from its new to its original position, we need to remember the original x and y coordinates of our image's vertices. We also need to set up variables to hold the x and y coordinates of the starting point and the end point of each tween.
*/

var vertsXArray:Array=[imgX,imgX+imgWidth,imgX+imgWidth,imgX];

var vertsYArray:Array=[imgY,imgY,imgY+imgHeight,imgY+imgHeight];

var begX:Number;

var endX:Number;

var begY:Number;

var endY:Number;

...........................

/*
In this applet, the listener that stops dragging also removes all other listeners and creates an instance of the Tween class. The listeners will be reinstated after the tweened motion ends.
*/

function stopDragging(evt:MouseEvent):void {

stage.removeEventListener(MouseEvent.MOUSE_MOVE, dragCorner);

stage.removeEventListener(MouseEvent.MOUSE_UP, stopDragging);

mcCorner0.removeEventListener(MouseEvent.MOUSE_DOWN, startCornerMove);

mcCorner1.removeEventListener(MouseEvent.MOUSE_DOWN, startCornerMove);

mcCorner2.removeEventListener(MouseEvent.MOUSE_DOWN, startCornerMove);

mcCorner3.removeEventListener(MouseEvent.MOUSE_DOWN, startCornerMove);

begX=this["mcCorner"+curCornerName].x;

endX=vertsXArray[Number(curCornerName)];

begY=this["mcCorner"+curCornerName].y;

endY=vertsYArray[Number(curCornerName)];

/*

We are creating a tween, 'tw', that will tween the property 't' of 'paramObj'

from the value 0 to the value 1, with the easing function curEase. The tween's

duration is in seconds (the last parameter is set to 'true') and it will take

5 seconds. We set up listeners to tween events MOTION_CHANGE and MOTION_FINISH.

*/

tw = new Tween(paramObj, "t", curEase, 0, 1, 5, true);

tw.addEventListener(TweenEvent.MOTION_CHANGE, mover);

tw.addEventListener(TweenEvent.MOTION_FINISH, stopper);

}

 

function mover(tevt:TweenEvent):void {

/*

As our tween, 'tw', changes the value of 't', the x and y coordinates

of the corner that was dragged, as well as the x and y coordinates

of the corresponding vertex, 'curV..' of our image, change along the

segment from [begX,begY] to [endX,endY]. (We use the parametric representation

of a segment.) The image is then redrwan by the 'mapBimapData' method of

the BitmapTransformer class.

*/

this["mcCorner"+curCornerName].x=begX+paramObj.t*(endX-begX);

this["mcCorner"+curCornerName].y=begY+paramObj.t*(endY-begY);

this["curV"+curCornerName].x=this["mcCorner"+curCornerName].x;

this["curV"+curCornerName].y=this["mcCorner"+curCornerName].y;

spContainer.graphics.clear();

bdTransform.mapBitmapData(bdLab,curV0,curV1,curV2,curV3,spContainer);

}

/*
When the tweened motion ends, all listeners are reset.
*/

function stopper(tevt:TweenEvent):void {

tw.removeEventListener(TweenEvent.MOTION_CHANGE, mover);

tw.removeEventListener(TweenEvent.MOTION_FINISH, stopper);

mcCorner0.addEventListener(MouseEvent.MOUSE_DOWN, startCornerMove);

mcCorner1.addEventListener(MouseEvent.MOUSE_DOWN, startCornerMove);

mcCorner2.addEventListener(MouseEvent.MOUSE_DOWN, startCornerMove);

mcCorner3.addEventListener(MouseEvent.MOUSE_DOWN, startCornerMove);

}

 

Download

Download all the 'fla' and 'as' files corresponding to the applets in this tutorial in one zip file.

Back to Intermediate Tutorials              Back to Flash and Math Home

The site www.flashandmath.com is maintained by Doug Ensley (doug@flashandmath.com) and Barbara Kaskosz (barbara@flashandmath.com).
It has been developed with partial funding from the National Science Foundation and the Mathematical Association of America.