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.






