Interactive Designer and Developer
Collider Exhibit Series

Multitouch Magnifier – AS3 experiment

Fire up the SVN, there's an new experiment to check out.

multitouch magnifier - AS3 from Chris Yanc on Vimeo.

If you don't have all the files, you can find them here: http://code.google.com/p/multitouchas3experiments/source/checkout

For this one, you are going to need Flash CS3 or CS4 as far as I know. I don't know how FlashDevelop or Flex will translate. We will be using assets from the Library which are linked to AS files. I'm thinking it'd be best to go through the files.

touchMagnify.v1.fla

First thing you'll see on the stage is Nothing. Everything that is used in this app is pulled dynamically from the library at runtime.

library

You can ignore that first graphic, it was for early testing.

  • hiRez-CLE.jpg - This is the image we'll want to use to scale. You can use any image you want, just remember the scaling ratio you want to use. My screen has a resolution of 1024 X 768 and I want the imge to scale 3x, so this graphic is 3072 X 2304.
  • cleImage - this contains the image. And is set to export with a class name of cleImage. This is so it can be pulled from the library at runtime.
  • closeZoomMc - is a MovieClip to trigger the close functionality of the zoom areas, I didn't want to build that through code, I wanted something more customizable.
    • important: looking inside this MovieClip, you'll notice the top layer is an invisible shape. This is because of how I reference it's TouchEvent.
  • zoomBox - the most important piece, the structure of the mask area
    • layer 1 - the close button with an instance name of ItemCloseBtn
    • layer 2 - a border around the mask area (for decoration)
    • layer 3 - another invisible shape to create a stable touching area, instance name of hitAreaMc
    • layer 4 - the mask for the zoomed map, instance name of maskArea

Now 2 of these library items have external AS files, closeZoomMc and zoomBox .

app\touchMagnify\closeZoomMc.as

package app.touchMagnify {
	import flash.display.*;
	public class closeZoomMc extends MovieClip {
		public function closeZoomMc():void{
		}
	}
}

Nothing is actually in this one, just declaring that it exists. I probably don't even need it. But who knows, someone may want to add in code later.

app\touchMagnify\ZoomBox.as

package app.touchMagnify{
	import flash.display.*;
	import flash.geom.Point;
	import app.core.action.RotatableScalable;
	public class ZoomBox extends RotatableScalable {
 
		public var thisname:String;
 
		public function ZoomBox(intX, intY):void {
 
			this.x = intX;
			this.y = intY;
 
		}
	}
}

Starting to get interesting. We know we want the container to be rotatable and scalable. Luckily, we can use app.core.action.RotatableScalable; to do just that, and with the bug fix added to the original Touchlib files, I feel confident that it'll work great.

The only addition is we'll want to set the X and Y as soon as this MovieClip is created on the stage. And we'll want a way to identify it, so a quick public variable thisname and it's ready to go.

One last file to work with. The one that ties it all together:

app\touchMagnify\Magnify.as

package app.touchMagnify{
	import flash.display.*;
	import flash.text.*;
	import flash.events.Event;
	import flash.xml.*;
	import flash.net.*;
	import flash.events.TUIO;
	import flash.events.TouchEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.utils.Dictionary;
	import app.touchMagnify.ZoomBox;
 
	public class Magnify extends MovieClip {
 
		public var shrinkFactor:Number=1/3;
		public var magFactor:Number=1/shrinkFactor;
 
		public var scaledMap:cleImage =  new cleImage();
 
		private var activeZooms = new Dictionary(true);
		private var activeBoxes = new Dictionary(true);
		public var Zoomz:MovieClip = new MovieClip();
 
		public function Magnify():void {
			//--------connect to TUIO-----------------
			TUIO.init(this,'127.0.0.1',3000,'',false);
			trace("AppControl - TUIO Started");
			//----------------------------------------
 
			addChild(scaledMap);
			scaledMap.x=0;
			scaledMap.y=0;
			scaledMap.scaleX=shrinkFactor;
			scaledMap.scaleY=shrinkFactor;
			scaledMap.addEventListener(TouchEvent.MOUSE_DOWN,boardDown);
 
			addChild(Zoomz);
 
		}
		function boardDown(e:TouchEvent):void {
			var intX = e.stageX;
			var intY = e.stageY;
			var zoomMap:cleImage =  new cleImage();
			var newBox:ZoomBox = new ZoomBox(intX, intY);
			newBox.thisname = e.ID.toString();
 
			Zoomz.addChild(zoomMap);
			Zoomz.addChild(newBox);
 
			activeZooms[e.ID.toString()] = zoomMap;
			activeBoxes[e.ID.toString()] = newBox;
			activeZooms[newBox.thisname].x = (e.stageX * -2);
			activeZooms[newBox.thisname].y = (e.stageY * -2);
 
			zoomMap.mask = newBox.maskArea;
 
			newBox.hitAreaMc.addEventListener(TouchEvent.MOUSE_MOVE,positionZoom);
			newBox.ItemCloseBtn.addEventListener(TouchEvent.MOUSE_DOWN,closeZoom);
 
		}
		public function positionZoom(e:TouchEvent):void {
			var newItm = e.relatedObject.parent.parent;
			activeZooms[newItm.thisname].x = (newItm.x * -2);
			activeZooms[newItm.thisname].y = (newItm.y * -2);
			if (e.ID.toString() == newItm.thisname) {
				newItm.x = e.stageX;
				newItm.y = e.stageY;
			}
		}
 
		public function closeZoom(e:TouchEvent):void {
			var oldItm = e.relatedObject.parent.parent;
			oldItm.hitAreaMc.removeEventListener(TouchEvent.MOUSE_MOVE,positionZoom);
			oldItm.ItemCloseBtn.removeEventListener(TouchEvent.MOUSE_DOWN,closeZoom);
			Zoomz.removeChild(activeZooms[oldItm.thisname]);
			Zoomz.removeChild(activeBoxes[oldItm.thisname]);
			e.stopPropagation();
		}
 
	}
}

Now thats quite a bit of code. Lets digest it down a little.

	import flash.display.*;
	import flash.events.TUIO;
	import flash.events.TouchEvent;
	import flash.utils.Dictionary;
	import app.touchMagnify.ZoomBox;

Some of the imports a from and expansion to this, these are the important ones.

	public class Magnify extends MovieClip {
 
		public var shrinkFactor:Number=1/3;
		public var magFactor:Number=1/shrinkFactor;
 
		public var scaledMap:cleImage =  new cleImage();
 
		private var activeZooms = new Dictionary(true);
		private var activeBoxes = new Dictionary(true);
		public var Zoomz:MovieClip = new MovieClip();

And now we begin setting our variables.

  • Shrink factor, your going to need to change if you want a different zooming amount, just remember to change the size of the image to go with it. Just don't make it unimportable into Flash.
  • scaledMap is the declaration of the MovieClip which holds the image we want to load.
  • Then activeZoomz and activeBoxes help keep track of the zoom areas for future development mainly.
  • Zoomz is going to be an empty MovieClip we'll want to use to help contain all the zoom areas to make them easier to remove.
		public function Magnify():void {
			//--------connect to TUIO-----------------
			TUIO.init(this,'127.0.0.1',3000,'',false);
			trace("AppControl - TUIO Started");
			//----------------------------------------
 
			addChild(scaledMap);
			scaledMap.x=0;
			scaledMap.y=0;
			scaledMap.scaleX=shrinkFactor;
			scaledMap.scaleY=shrinkFactor;
			scaledMap.addEventListener(TouchEvent.MOUSE_DOWN,boardDown);
 
			addChild(Zoomz);
 
		}

this part adds the scaled down map to the stage, fitting it to the stage dimensions. It is reliant on the dimension of the graphic you are using so make sure your dimensions and scale calculations are correct. An event listener is added to the new image being displayed. And Zoomz is added on top of it awaiting zoom areas.

		function boardDown(e:TouchEvent):void {
			var intX = e.stageX;
			var intY = e.stageY;
			var zoomMap:cleImage =  new cleImage();
			var newBox:ZoomBox = new ZoomBox(intX, intY);
			newBox.thisname = e.ID.toString();
 
			Zoomz.addChild(zoomMap);
			Zoomz.addChild(newBox);
 
			activeZooms[e.ID.toString()] = zoomMap;
			activeBoxes[e.ID.toString()] = newBox;
			activeZooms[newBox.thisname].x = (e.stageX * -2);
			activeZooms[newBox.thisname].y = (e.stageY * -2);
 
			zoomMap.mask = newBox.maskArea;
 
			newBox.hitAreaMc.addEventListener(TouchEvent.MOUSE_MOVE,positionZoom);
			newBox.ItemCloseBtn.addEventListener(TouchEvent.MOUSE_DOWN,closeZoom);
 
		}

You touch the stage, what happens?

  1. intX and intY get the X and Y of where you are touching on the map.
  2. a new copy of zoomMap is declared
  3. a new zoomBox is delcared and is given thisname information
  4. they are both added into the Zoomz MovieClip the map graphic first, then the zoomBox
  5. they are both then added to dictionaries for help with garbage collecting later on
  6. the X and Y of the full scale image is repositioned so that the zoom spot matches the scaled down image's X and Y location
  7. the mask inside the zoomBox, maskArea, is applied as a mask to the full scale image
  8. Event listeners are added to the hit area of the zoomBox and the close button in the zoomBox
		public function positionZoom(e:TouchEvent):void {
			var newItm = e.relatedObject.parent.parent;
			activeZooms[newItm.thisname].x = (newItm.x * -2);
			activeZooms[newItm.thisname].y = (newItm.y * -2);
			if (e.ID.toString() == newItm.thisname) {
				newItm.x = e.stageX;
				newItm.y = e.stageY;
			}
		}

RotateScalable is controlling the functionality of the zoomBox itself, but something needs to make sure the position of the full scale image is adjusted whenever zoomBox is moved, that is the purpose of newBox.hitAreaMc.addEventListener(TouchEvent.MOUSE_MOVE,positionZoom);

Remember applying the ID of the original touch event to the zoomBox, we end up using that same information to figure out which zoomArea is being touched and which activeZooms to reposition with the Dictionary object.

		public function closeZoom(e:TouchEvent):void {
			var oldItm = e.relatedObject.parent.parent;
			oldItm.hitAreaMc.removeEventListener(TouchEvent.MOUSE_MOVE,positionZoom);
			oldItm.ItemCloseBtn.removeEventListener(TouchEvent.MOUSE_DOWN,closeZoom);
			Zoomz.removeChild(activeZooms[oldItm.thisname]);
			Zoomz.removeChild(activeBoxes[oldItm.thisname]);
			e.stopPropagation();
		}

The last step is removing the zoom areas when we touch the close button. Like the other function, using the relatedObject makes the most sense to me. That is what you are touching, you just need a little assurance that the pathing will always be the same.

The invisible button layer

Because of the .parent.parent path we want to take to reach the zoomBox MovieClip we'll use that invisible shape as the touchable area to path off of. Because of how Flash layers items, lets say this, all that's on the movie clip is the black background and the X, one time you touch you could be hitting the X and another time the background, the pathing structure could need to you check and double check exactly what your touching to path back to the original movie clip you want to reference. With the invisible shape, you know you are always starting at the same spot in the hierarchy.

Using e.stopPropagation();

At the end of coding this experiment, one thing was driving me crazy. When you would touch the close button. And error message would pop up. Saying something was undefined or something. I thought it was an issue with turning off the event listeners or removing the children from the Zoomz (which use the thisname reference to function). But it turned out that RotateScalable was still listening for events even after the zoomBox and full scale image was removed, the touch event would continue to bubble through (i think i use that term properly here). So we just need to stop that bubbling from happening and it's just one line of code. e.stopPropagation();

DONE!

Hope you enjoyed this one,
happy experimenting

Tags: , , , , ,

4 Responses to “Multitouch Magnifier – AS3 experiment”

  1. Ankit Singh says:

    How can I compile your project on CS4 ?

    I tried SVN checkout by the URL given, but it gives me an error.

    Thanks in Advance
    Ankit Singh

  2. Chris Yanc says:

    Hmm. Can you tell me what the error is that you are getting?

  3. HeyYou says:

    Thanks for all the work, I got this error when I compile the code,

    1119: Access of possibly undefined property maskArea through a reference with static type app.touchMagnify:ZoomBox.

    it points to this code:

    zoomMap.mask = newBox.maskArea;

    newBox.hitAreaMc.addEventListener(TouchEvent.MOUSE_MOVE,positionZoom);
    newBox.ItemCloseBtn.addEventListener(TouchEvent.MOUSE_DOWN,closeZoom);

    you know whats going on here?

  4. Chris Yanc says:

    Looks like it is having trouble finding the “maskArea” movie clip that creates the mask for the zoomed in image. Check the ZoomBox movie clip in the Flash Library and make sure the instance name matches.

Leave a Reply

fans of cyancdesign

What do you all think?

What developer tool[s] do you use to compile your SWF files?

View Results

Loading ... Loading ...

Shameless Attempts to Pay the Bills