• Tutorials
  • Blog
  • About
  • Contact
  • Making Flex DisplayObjects Zoom and Rotate (Multitouch Screencast)


    2010 - 02.04

    I have a friend who once said that the image rotate and zoom application is the ‘hello world’ of multitouch.  I thought that was a pretty astute observation.  In part one (of two) in this video tutorial, we’ll take a look at how to make any Flex component Rotatable and Scalable.  This class can be applied to images for the classic ‘hello world’ effect, but it can also be added to videos, data grids and containers making for a fun and engaging user experience.

    RotatableScalable.mov

    Here is the class that drives most of the application:

    RotatableScalable:

    
    package com.mlegrand
    {
    	import flash.display.DisplayObject;
    	import flash.events.TransformGestureEvent;
    	import flash.geom.Matrix;
    	import flash.geom.Point;
    	import flash.ui.Multitouch;
    	import flash.ui.MultitouchInputMode;
    
    	public class RotatableScalable
    	{
    
    		protected var disO:DisplayObject
    
    		public function RotatableScalable(displayObject:DisplayObject)
    		{
    			disO = displayObject;
    			if(Multitouch.supportsGestureEvents)
    			{
    				Multitouch.inputMode = MultitouchInputMode.GESTURE;
    				addGestureEventListeners()
    			}
    
    		}
    
    		protected function addGestureEventListeners():void
    		{
    			disO.addEventListener(TransformGestureEvent.GESTURE_ROTATE, gestureRotateHandler);
    			disO.addEventListener(TransformGestureEvent.GESTURE_ZOOM, gestureZoomHandler);
    		}
    
    		protected function gestureRotateHandler(event:TransformGestureEvent) : void
    		{
    			event.stopImmediatePropagation();
    			var m:Matrix = disO.transform.matrix;
    			var p:Point = m.transformPoint(new Point(disO.width/2, disO.height/2));
    			m.translate(-p.x, -p.y);
    			m.rotate(event.rotation*(Math.PI/180));
    			m.translate(p.x, p.y);
    			disO.transform.matrix = m;
    		}
    
    		protected function gestureZoomHandler(event:TransformGestureEvent):void
    		{
    			event.stopImmediatePropagation();
    			var m:Matrix = disO.transform.matrix;
    			var p:Point = m.transformPoint(new Point(disO.width/2, disO.height/2));
    			m.translate(-p.x, -p.y);
    			m.scale(event.scaleX, event.scaleY);
    			m.translate(p.x, p.y);
    			disO.transform.matrix = m;
    		}
    
    	}
    }
    

    Other notes:

    I noticed the matrix transform over at Justin Imhoff’s blog.  I highly recommend that you add his blog to your feed reader.  I also mentioned the Natural User Interface Group.  Their forums are very active and may have helpful ideas for anyone interested in multitouch development.

    Multitouch Ripple Effect (Screencast)


    2010 - 01.06

    This is a short screencast showing how to build a multitouch water ripple effect using David Lenaerts’s Shallow Water Container and pixel bender shadder classes.

    rippleEffect

    Here’s David’s Blog post and the source for his flash app.

    Here’s the code that we came up with for the screencast:

    <?xml version=”1.0″ encoding=”utf-8″?>
    <s:WindowedApplication xmlns:fx=”http://ns.adobe.com/mxml/2009″
    xmlns:s=”library://ns.adobe.com/flex/spark”
    xmlns:mx=”library://ns.adobe.com/flex/halo”
    width=”1010″ height=”670″ creationComplete=”windowedapplication1_creationCompleteHandler(event)”>
    <fx:Script>
    <![CDATA[
    import com.derschmale.fluids.ShallowWaterContainer;

    import flash.events.TouchEvent;
    import flash.ui.Multitouch;
    import flash.ui.MultitouchInputMode;

    import mx.events.FlexEvent;

    protected var ripple:ShallowWaterContainer;

    [Embed (source='CloudyWaves.jpg')]
    protected var CloudyWave:Class;

    protected var touchMap : Array  = [];

    protected function windowedapplication1_creationCompleteHandler(event:FlexEvent):void
    {
    Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
    addRipple();
    }

    protected function addRipple() : void
    {
    ripple = new ShallowWaterContainer(1000,650, 200, 200);
    ui.addChild(ripple);
    ripple.timeStep = 1;
    ripple.viscosity =0.2;
    ripple.relaxation = 0.15;
    ripple.relaxationSteps = 2;

    ripple.addChild(new CloudyWave());

    this.addEventListener(TouchEvent.TOUCH_BEGIN, touchBeginHandler);
    this.addEventListener(TouchEvent.TOUCH_END, touchEndHandler);
    this.addEventListener(TouchEvent.TOUCH_MOVE, touchMoveHandler);
    }

    protected function touchBeginHandler(event:TouchEvent):void
    {
    var key : String = event.touchPointID.toString();
    var touchPoint:Object = {};
    touchPoint.x = event.localX;
    touchPoint.y = event.localY;
    touchPoint.sizeX = event.sizeX;
    touchPoint.sizeY = event.sizeY;
    touchMap[ key ] = touchPoint;
    }

    protected function touchEndHandler(event:TouchEvent):void
    {
    var key : String = event.touchPointID.toString();
    delete touchMap [ key ];
    }

    protected function touchMoveHandler(event:TouchEvent):void
    {
    var key : String = event.touchPointID.toString();
    if(touchMap [ key ])
    {
    ripple.addVelocity( event.localX, event.localY,
    (event.localX – touchMap[key].x),
    (event.localY -  touchMap [ key ].y), 5, 1);
    ripple.addPressure(event.localX, event.localY, 7, 1);

    // update touchpoint in map

    touchMap[key].x = event.localX;
    touchMap[key].y = event.localY;
    touchMap[key].sizeX = event.sizeX;
    touchMap[key].sizeY = event.sizeY;
    }
    }
    ]]>
    </fx:Script>
    <fx:Declarations>
    <!– Place non-visual elements (e.g., services, value objects) here –>
    </fx:Declarations>

    <mx:UIComponent id=”ui” width=”100%” height=”100%” />

    </s:WindowedApplication>

    Multi-touch Mapping (Screencast)


    2009 - 12.12

    Andrew Trice gave a presentation at the Adobe Max conference talking about Multi-touch.

    mapping

    One of the presentations he gave was on using the new gesture events and the Modest Maps api to create a quick mapping application.

    In this tutorial we take a closer look at some of that code.

    Multi-Touch Eye Candy – Fire (Screencast)


    2009 - 12.02

    Howdy ladies and gentlemen,

    So in this tutorial we’re going to use a few blurs and color transforms to put together a fun multi-touch visual effect called Fire.

    PlayingWithFire(H264).mov

    Here’s the code for the fire class:

    package fire
    {
    	import flash.display.Bitmap;
    	import flash.display.BitmapData;
    	import flash.display.BlendMode;
    	import flash.display.StageAlign;
    	import flash.display.StageDisplayState;
    	import flash.events.Event;
    	import flash.events.MouseEvent;
    	import flash.events.TouchEvent;
    	import flash.filters.BlurFilter;
    	import flash.geom.ColorTransform;
    	import flash.geom.Matrix;
    	import flash.geom.Point;
    	import flash.geom.Rectangle;
    	import flash.system.Capabilities;
    	import flash.system.TouchscreenType;
    	import flash.ui.Multitouch;
    	import flash.ui.MultitouchInputMode;
    
    	import mx.core.UIComponent;
    
    	public class Fire extends UIComponent
    	{
    		protected var canvas:BitmapData;
    		protected var canvasBitmap:Bitmap;
    
    		protected var p1:Point;
    		protected var p2:Point;
    		protected var p3:Point;
    
    		protected var r:Rectangle;
    		protected var blobRect:Rectangle;
    
    		protected var bf:BlurFilter;
    		protected var ct:ColorTransform;
    		protected var ct2:ColorTransform;
    
    		protected var m:Matrix;
    		protected var starterCanvasSize:Number = 400;
    
    		protected var noise:BitmapData;
    
    		public function Fire()
    		{
    			super();
    			addEventListeners();
    		}
    
    		override protected function createChildren() : void
    		{
    			super.createChildren();
    			canvas = new BitmapData(starterCanvasSize, starterCanvasSize, false, 0);
    			canvasBitmap = new Bitmap(canvas);
    			addChild(canvasBitmap);
    
    			p1 = new Point();
    			p2 = new Point();
    			p3 = new Point();
    
    			r = new Rectangle(0,0, canvas.width, 2);
    
    			blobRect = new Rectangle();
    
    			bf = new BlurFilter(6, 6, 1);
    
    			ct = new ColorTransform(1.05, 1.03, 1.00, 1,-1, -1, -1, 0);
    			ct2 = new ColorTransform(1, 1.25, 2.5);
    
    			m = new Matrix();
    			m.translate(0, -1);
    
    			noise = new BitmapData(canvas.width, canvas.height);
    			noise.perlinNoise(6, 6, 3, 7, false, false, 1|2|4, true);
    		}
    
    		protected function addEventListeners():void
    		{
    			if(stage)
    			{
    				addedToStage(null);
    			}
    			else
    			{
    				addEventListener(Event.ADDED_TO_STAGE, addedToStage);
    			}
    
    			addEventListener(MouseEvent.MOUSE_MOVE, updateFireOnMouseMove, false, 0, true);
    			addEventListener(Event.ENTER_FRAME, updateFire, false, 0, true);
    
    			if(Capabilities.touchscreenType == TouchscreenType.FINGER)
    			{
    				Multitouch.inputMode = MultitouchInputMode.TOUCH_POINT;
    				addEventListener(TouchEvent.TOUCH_BEGIN, touchEventHandler, false, 0, true);
    				addEventListener(TouchEvent.TOUCH_END, touchEventHandler, false, 0, true);
    				addEventListener(TouchEvent.TOUCH_MOVE, touchEventHandler, false, 0, true);
    			}
    		}
    
    		protected function updateFireOnMouseMove(event:MouseEvent):void
    		{
    			r.y = (r.y+1) % noise.height;
    			if(stage)
    			{
    				drawBlob( 1, event.stageX, event.stageY);
    			}
    			//blur
    			canvas.applyFilter(canvas, canvas.rect, p1, bf);
    			canvas.draw(canvas, m, ct);
    		}
    
    		protected function touchEventHandler(event:TouchEvent):void
    		{
    			r.y = (r.y+1) % noise.height;
    			if(stage)
    			{
    				drawBlob(event.touchPointID, event.stageX, event.stageY);
    			}
    			//blur
    			canvas.applyFilter(canvas, canvas.rect, p1, bf);
    			canvas.draw(canvas, m, ct);
    		}
    
    		protected function addedToStage(event:Event):void
    		{
    			stage.addEventListener(Event.RESIZE, stageResized, false, 0, true);
    			removeEventListener(Event.ADDED_TO_STAGE, addedToStage);
    			stage.displayState = StageDisplayState.FULL_SCREEN_INTERACTIVE;
    		}
    
    		protected function stageResized(event:Event):void
    		{
    			stage.align = StageAlign.TOP_LEFT;
    			canvasBitmap.width = stage.width;
    			canvasBitmap.height = stage.height;
    		}
    
    		protected function drawBlob(id:int, mx:Number, my:Number) : void
    		{
    			p3.x = int(mx * starterCanvasSize/stage.stageWidth) - 4;
    			p3.y = int(my* starterCanvasSize/stage.stageHeight) - 4;
    			blobRect.left = p3.x;
    			blobRect.top = p3.y;
    			blobRect.bottom = p3.y + 8;
    			blobRect.right = p3.x + 8;
    			var m2:Matrix = new Matrix();
    			m2.translate(p3.x, p3.y);
    			canvas.draw(noise, m2, ct2, BlendMode.ADD, blobRect);
    		}
    
    		protected function updateFire(event:Event):void
    		{
    			r.y = (r.y+1) % noise.height;
    			canvas.applyFilter(canvas, canvas.rect, p1, bf);
    			canvas.draw(canvas, m, ct);
    		}
    	}
    }