The Effect

In this How-To we show how to accomplish a common but not entirely straightforward task of zooming in on a specific point of an image (or any other DisplayObject) and how to pan an image. We use the AS3 MatrixTransformer class from fl.motion package. We also show how to coordinate simultaneous mouse and keyboard interactions. Click the screen shot below to open the example in a new window:

Download

  • Download all source files corresponding to this effect: zoom.zip

The main tool that we use to accomplish zooming on a specific point is the MatrixTransformer class in fl.motion package. We presented this useful class in our recent tutorial: How to Rotate a Display Object About an Arbitrary Point in AS3 Flash.

Comments

The MatrixTransformer class does not include a method specifically for zooming on a point, which we should technically describe as rescaling with a specified anchor point. Simply changing the scaleX and scaleY property of a display object is always anchored at the registration point (0,0) of the display object. If we were managing the desired transformation in terms of basic operations, we would think of it as "translating the desired anchor point to (0,0), scaling with anchor (0,0), and translating back." The matchInternalPointWithExternal method in the MatrixTransformer class is based on this idea but is slightly more general. Specifically, the call

MatrixTransformer.matchInternalPointWithExternal(matrix, internalpoint, externalpoint)

takes a point in a child object (internalpoint) and a point in its parent object (externalpoint), and then adjusts matrix appropriately to include a translation that lines these points up. So if matrix is the transform matrix for the child object, this will make matrix include the information to line up the points. So if the child's transform matrix is set to be this modified matrix, then the intermalpoint and externalpoint will align.

We take advantage of this with the following logic. On the click to the mouse, we store the clicked point relative to the image coordinate system and its parent (called spImage) coordinate system. These are by definition lined up to start with. Then we scale the image, which makes the clicked point in the image coordinate system line up differently. The process described above brings these two points back into alignment by appropriately translating the image. The rest of the code below simply manages importing the picture and simple drag and drop functionality.

Code

We first import the MatrixTransformer class.

import fl.motion.MatrixTransformer;

Initially hide the zoom in/out cursors. Note that mcIn and mcOut were created on the stage and converted to movieclips using Modify > Convert to Symbol with the registration point set in the center. We converted the header text to a movieclip as well, mcHeader, to make it invisible while the image is loading.

mcIn.visible = false;

mcOut.visible = false;

mcHeader.visible=false;

//We created a dynamic text field, infoBox, on the stage to display loading info.

infoBox.wordWrap=true;

infoBox.mouseEnabled=false;

The board Sprite provides a static background on which the image will reside.

var board:Sprite=new Sprite();

addChild(board);

We create variables for the width and height of the board so we can scale the picture accordingly. These settings should be chosen with consideration of the pixel dimensions of the original picture.

var boardWidth:int=538;

var boardHeight:int=300;

The position of board will detrmine the position of your picture within the main movie.

board.x=90;

board.y=90;

The board will have a background so we can have the whole board listen for mouse events. The "image sprite" created below will be a child of the board.

board.graphics.beginFill(0xDDDDDD);

board.graphics.drawRect(0,0,boardWidth,boardHeight);

board.graphics.endFill();

The Sprite spImage is where we will place the image -- it is a Sprite so we can attach listeners for response to mouse events to implement drag & drop.

var spImage:Sprite = new Sprite();

board.addChild(spImage);

Create a mask for spImage with shape identical to the board. This will keep a scaled or moved picture from "leaving" the designated board area.

var boardMask:Shape=new Shape();

boardMask.graphics.beginFill(0xDDDDDD);

boardMask.graphics.drawRect(0,0,boardWidth,boardHeight);

boardMask.graphics.endFill();

board.addChild(boardMask);

spImage.mask = boardMask;

The variable scaleFactor for the proportion of scaling corresponding to each 'click' of the mouse wheel. The value 0.8 reflects 80% scaling when zooming out and 125% scaling when zooming in. The variable minScale reflects the smallest the image can be -- this is reset in the initPic function to be the scale at which the image fits on the board. The maxScale of 2.0 (this means that the maximum zoom is to a scale of 200% of the original image size) is hardcoded by the line below and depends on how blurry an image you can put up with.

var scaleFactor:Number = 0.8;

var minScale:Number = 0.25;

var maxScale:Number = 2.0;

var image:Bitmap;

Declare variable for loading of image from an external file. Replace Kazanie_Skargi.jpg with the name of your image or the address of your image.

var loader:Loader = new Loader();

loader.load(new URLRequest("Kazanie_Skargi.jpg"));

loader.contentLoaderInfo.addEventListener(Event.COMPLETE,initPic);

loader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS,updateInfo);

infoBox.text="Loading begins";

 

function updateInfo(e:ProgressEvent):void {

infoBox.text="Loading: "+String(Math.floor(e.bytesLoaded/1024))+" KB of "+ String(Math.floor(e.bytesTotal/1024))+" KB.";

}

We need a global variable for the Bitmap object (image) so it can be manipulated (scaled, to be specific) by the handler function (zoom) for the mouse click event.

function initPic(e:Event):void {

infoBox.text="";

infoBox.visible=false;

mcHeader.visible=true;

image=Bitmap(loader.content);

minScale = boardWidth/image.width;

image.scaleX=minScale;

image.scaleY=minScale;

We add the image and the cursor movie clips to spImage, in this order to get proper layering. Since spImage is masked, so will be its children.

spImage.addChild(image);

spImage.addChild(mcIn);

spImage.addChild(mcOut);

We use extremely basic drag and drop functionality. See the basic Drag and Drop tutorial on the flashandmath.com site for different models that allow for more flexibility.

spImage.addEventListener(MouseEvent.MOUSE_DOWN, startDragging);

stage.addEventListener(MouseEvent.MOUSE_UP, stopDragging);

 

// Clean up listeners & loader now that the load is complete.

loader.contentLoaderInfo.removeEventListener(Event.COMPLETE,initPic);

loader.contentLoaderInfo.removeEventListener(ProgressEvent.PROGRESS,updateInfo);

loader=null;

 

// Drop shadows are always cool

board.filters=[ new DropShadowFilter() ];

 

// A click on the image sprite is handled by the zoom function below.

spImage.addEventListener(MouseEvent.CLICK, zoom);

}

The following event handlers for mouse_down and mouse_up events enable basic drag-and-drop functionality.

function startDragging(mev:MouseEvent):void {

spImage.startDrag();

}

function stopDragging(mev:MouseEvent):void {

spImage.stopDrag();

}

The following event handler for the click event performs zooming in and out via MatrixTransformer methods.

function zoom(mev:MouseEvent):void {

// If neither or both shift key & ctrl key are depressed, then we do nothing.

if ((!mev.shiftKey)&&(!mev.ctrlKey)) {

return;

}

if ((mev.shiftKey)&&(mev.ctrlKey)) {

return;

}

// Declare a variable for the transform matrix of the image object.

var mat:Matrix;

Get the point at the mouse in terms of image coordinates and (its parent) spImage coordinates. These points are aligned when the mouse click happens but after image is scaled, they will not be aligned anymore.

var externalCenter:Point=new Point(spImage.mouseX,spImage.mouseY);

var internalCenter:Point=new Point(image.mouseX,image.mouseY);

We scaled image up (but bounded by maxScale) or down (bounded by minScale) depending on whether the shift key or the ctrl key is down while the mouse click happens.

if (mev.shiftKey) {

image.scaleX = Math.max(scaleFactor*image.scaleX, minScale);

image.scaleY = Math.max(scaleFactor*image.scaleY, minScale);

}

if (mev.ctrlKey) {

image.scaleX = Math.min(1/scaleFactor*image.scaleX, maxScale);

image.scaleY = Math.min(1/scaleFactor*image.scaleY, maxScale);

}

The mat matrix is the transformation matrix for the scaled version of image; i.e., the version that no longer has internalCenter and externalCenter aligned.

mat = image.transform.matrix.clone();

The matchInternalPointWithExternal method of the MatrixTransformer class changes the mat matrix to include a translation that makes the point in terms of image coordinates line up with the point in terms of spImage coordinates.

MatrixTransformer.matchInternalPointWithExternal(mat,internalCenter,externalCenter);

// Applying mat to the enlarged image aligns internalCenter and externalCenter.

image.transform.matrix=mat;

}

We use the ctrl and shift keys to display the two different cursors that were created on the stage.

stage.addEventListener(KeyboardEvent.KEY_DOWN, keyHandler);

stage.addEventListener(KeyboardEvent.KEY_UP, keyHandler);

 

function keyHandler(ke:KeyboardEvent):void {

// No matter what key is changed, move the cursors to the mouse position.

mcIn.x = spImage.mouseX;

mcIn.y = spImage.mouseY;

mcOut.x = spImage.mouseX;

mcOut.y = spImage.mouseY;

 

// The visible cursor will correctly correspond to a depressed key.

mcIn.visible = ke.ctrlKey;

mcOut.visible = ke.shiftKey;

 

// If either key (ctrl or shift) is depressed, we hide the mouse.

if (ke.ctrlKey || ke.shiftKey) {

Mouse.hide();} else {

Mouse.show();

}

}

Back to How-Tos and Tips              Back to Flash and Math Home

We welcome your comments, suggestions, and contributions. Click the Contact Us link below and email one of us.

Adobe®, Flash®, ActionScript®, Flex® are registered trademarks of Adobe Systems Incorporated.