/** DiffTool class author: $Author: gijsbert $ version: $Revision: 78 $ modified: $Date: 2007-08-07 15:07:27 -0400 (Tue, 07 Aug 2007) $ copyright: Gijsbert de Haan. Diff tool takes to images and allows a mask to be edited show parts of one or the other. */ import flash.geom.Matrix; import flash.geom.Point; import flash.geom.Rectangle; import flash.geom.Transform; class DiffTool { static var app : DiffTool; private var a: MovieClip; private var b: MovieClip; private var ui: MovieClip; private var myMCL: MovieClipLoader; private var aloc: String; private var bloc: String; private var swidth : Number; private var sheight: Number; private var pivotx: Number; private var pivoty: Number; private var angle: Number; private var ap: Point; private var m: Matrix; private var maxside: Number; private var pickPivot: Boolean; private var pickLine: Boolean; private var pickDir: Number; private var debug: Boolean; private var animateAngleStep: Number; // Trace interesting info from the clip. static function tr(mc : MovieClip) { trace(mc + " @ " + mc._x + "," + mc._y + " s " + mc._width + "," + mc._height); trace(" " + mc._xscale + "," + mc._yscale + " depth " + mc.getDepth()); } // Constructor. function DiffTool() { // Create movie clips for a and b. a = _root.createEmptyMovieClip("a", 1); b = _root.createEmptyMovieClip("b", 2); m = new Matrix(); // Create overlay for ui. ui = _root.createEmptyMovieClip("ui", 3); ui.transform = new Transform(ui); ui.transform.matrix = m; // Setup state. pickPivot = pickLine = false; debug = false; animateAngleStep = 0; // Setup listeners. myMCL = new MovieClipLoader(); myMCL.addListener(this); Mouse.addListener(this); Key.addListener(this); // Adjust to size changes. Stage.addListener(this); } // Set angle as degrees. function setAngleDegree(adeg: Number) { angle = adeg * Math.PI / 180; updateMatrix(); } // updateMatrix: Should be called after any changes to pivot or angle function updateMatrix() { ap = new Point(Math.cos(angle), Math.sin(angle)); m.identity(); m.rotate(angle); m.translate(pivotx, pivoty); ui.transform.matrix = m; } // Set two clips to compare. function setAB(aloc: String, bloc: String) { trace("aloc = " + aloc); trace("bloc = " + bloc); this.aloc = aloc; this.bloc = bloc; myMCL.loadClip(aloc, a); myMCL.loadClip(bloc, b); } // Reset pivot and angle. function reset() { pivotx = Stage.width / 2.0; pivoty = Stage.height / 2.0; setAngleDegree(-90); trace("pivot " + pivotx + ", " + pivoty + " angle " + angle); } // onResize: Stage listener. function onResize() { trace("onResize " + Stage.width + ", " + Stage.height); // FIXME: how to handle scale? some weird interaction with _root, seems to scale // up in height but not in width so one gets really stretched in x. // scaleClipToStageSize(a); // scaleClipToStageSize(b); // What happens if we scale _root. // Reposition pivot. var s : Number = Stage.width / swidth; pivotx *= s; pivoty *= s; // Keep stage size. swidth = Stage.width; sheight = Stage.height; maxside = Math.sqrt(Stage.width * Stage.width + Stage.height * Stage.height); trace("maxside " + maxside); updateMatrix(); update(); } // onLoad: should be called when root is onLoad'ed. function onLoad() { trace("DiffTool.onLoad"); trace("Stage " + Stage.width + ", " + Stage.height); tr(_root); // Keep stage size. swidth = Stage.width; sheight = Stage.height; maxside = Math.sqrt(Stage.width * Stage.width + Stage.height * Stage.height); trace("maxside " + maxside); reset(); } // MovieClipLoader listener. function onLoadInit( target_mc : MovieClip ) { trace( "DiffTool.onLoadInit" ); trace( "Stage " + Stage.width + ", " + Stage.height ); tr( target_mc ); tr( _root ); tr( a ); tr( b ); // Inject original size. target_mc.owidth = target_mc._width; target_mc.oheight = target_mc._height; // Scale to stage size. if ( target_mc.width == 0 ) { // No original size, must be an swf. target_mc._width = Stage.width; target_mc._height = Stage.height; target_mc.play(); } else { scaleClipToStageSize( target_mc ); } // If target is b then we can add the mask. if ( target_mc == b ) { update(); } } // scaleClipToStageSize: Scale image to stage size, maintinging image ratio, we use // the original size function scaleClipToStageSize(mc : MovieClip ) { var s : Number; if (mc.owidth - Stage.width > mc.oheight - Stage.height) { s = Stage.width / mc.owidth; } else { s = Stage.height / mc.oheight; } mc._xscale = s * 100; mc._yscale = s * 100; } // Create the mask and associate with b. function createMask() { // Create mask movie clip. var mask: MovieClip = b.createEmptyMovieClip("mask", 2); // Map pivot to b image space. (_*scale are in percentage!) var p : Point = new Point(pivotx * 100 / b._xscale, pivoty * 100 / b._yscale); // Compute original size. var ow: Number = b._width * 100 / b._xscale; var oh: Number = b._height * 100 / b._yscale; // Define line big enough to guarantee intersection with the bounding box. // FIXME: what if the image is bigger and we scale down for canvas? var l: Line = new Line(p.x + ap.x * maxside, p.y + ap.y * maxside, p.x - ap.x * maxside, p.y - ap.y * maxside); // Define rectangle around b. var r: Rectangle = new Rectangle(0, 0, ow, oh); // Get intersection between line and rectangle. var pi: Array = l.intersectRectangle(r); if ( debug ) { trace("angle " + angle + " " + ap); trace("p = " + p); trace("l = " + l); trace("r = " + r); trace("pi = " + pi); } if ( pi.length == 2 ) { // Make sure the intersection point in the most positive direction is first. if ( GeomUtils.parameterPointOnLine( pi[0], p, ap ) < GeomUtils.parameterPointOnLine( pi[1], p, ap ) ) { pi.reverse(); if ( debug ) { trace("pi = " + pi); } } // Array with all the corners. var pts : Array = [ new Point(0, 0), new Point(ow, 0), new Point(ow, oh), new Point(0, oh) ]; // Find starting point in pts, look for 2 consequetive points surrounding the // first intersection. while ( !( ( MathUtils.equal(pts[0].x, pi[0].x) || MathUtils.equal(pts[0].y, pi[0].y ) ) && ( MathUtils.equal(pts[1].x, pi[0].x) || MathUtils.equal(pts[1].y, pi[0].y) ) ) ) { // Rotate pts. pts.push(pts.shift()); } if ( debug ) { trace("pts " + pts); } mask.beginFill(0xffffff, 100); mask.moveTo(pi[0].x, pi[0].y); for ( var i: Number = 0; i < pts.length; ++i) { if (l.distancePoint(pts[i]) < 0) { mask.lineTo(pts[i].x, pts[i].y); } } mask.lineTo(pi[1].x, pi[1].y); mask.endFill(); } else { // Is it all masked or nothing? if (l.distancePoint(r.topLeft) < 0) { mask.beginFill(0xffffff, 100); mask.drawRectangle(r); mask.endFill(); } } // This makes it use alpha, ie a mask with more then on/off. // We don't need this for the moment. // b.cacheAsBitmap = true; // mask.cacheAsBitmap = true; // Now set mask to m. b.setMask(mask); } // Update UI! function updateUI() { ui.clear(); ui.beginFill(0x000000, 100); ui.drawCircle(0, 0, 10); ui.endFill(); ui.lineStyle(2,0xffff00); ui.drawCircle(0, 0, 10); ui.moveTo(10, 0); ui.lineTo(maxside, 0); ui.moveTo(-10, 0); ui.lineTo(-maxside, 0); // For matrix reference. // ui.moveTo(0, 10); // ui.lineTo(0, maxside); } // Update mask and ui. function update() { createMask(); updateUI(); } // Mouse listener. function onMouseDown() { var x: Number = _root._xmouse; var y: Number = _root._ymouse; var d: Number = PickUtils.pickCircle(pivotx, pivoty, 10, x, y); if ( d < 10 ) { pickPivot = true; } else { var l: Line = new Line( pivotx - maxside * ap.x, pivoty - maxside * ap.y, pivotx + maxside * ap.x, pivoty + maxside * ap.y ); if ( Math.abs( l.distance( x, y ) ) < 6 ) { // Close enough. pickLine = true; // Always pick on the positive side so we don't get any flipping, just // flip direction. var ponl : Point = l.nearest( x, y ); pickDir = GeomUtils.parameterPointOnLine( ponl, new Point( pivotx, pivoty ), ap ) < 0 ? -1 : 1; } else { animateAngle( false ); } } } // Mouse listener. function onMouseUp() { pickPivot = false; pickLine = false; } // Mouse listener. function onMouseMove() { if ( pickPivot ) { pivotx = _root._xmouse; pivoty = _root._ymouse; updateMatrix(); update(); } else if ( pickLine ) { angle = Math.atan2( pickDir * ( _root._ymouse - pivoty ), pickDir * ( _root._xmouse - pivotx ) ); updateMatrix(); update(); } } // Enable/disable animation. function animateAngle( animate : Boolean ) { if ( animate ) { // Just using 'this' in the callback does not work, make a ref and use that. var bla : DiffTool = this; a.onEnterFrame = function() { bla.angle += bla.animateAngleStep; bla.updateMatrix(); bla.update(); } } else { a.onEnterFrame = null; animateAngleStep = 0; } } // Key listener. function onKeyDown() { switch(Key.getCode()) { case Key.SPACE: { animateAngle( false ); } break; case Key.UP: { angle += 0.1; updateMatrix(); update(); } break; case Key.DOWN: { angle -= 0.1; updateMatrix(); update(); } break; } switch(Key.getAscii()) { case 'a'.charCodeAt(0): { animateAngleStep += 0.025; animateAngle( true ); } break; case 'd'.charCodeAt(0): { debug = ! debug; update(); } break; case 'p'.charCodeAt(0): { trace( "play" ); a.play(); b.play(); } break; case 's'.charCodeAt(0): { trace( "stop" ); a.stop(); b.stop(); } break; case 'r'.charCodeAt(0): { reset(); update(); } break; case 'R'.charCodeAt(0): { setAB(aloc, bloc); update(); } break; } } // entry point static function main(mc:Object) { trace( "DiffTool.main" ); Stage.scaleMode = "noScale"; Stage.align = "TL"; app = new DiffTool(); if (_root.hasOwnProperty("contentA") && _root.hasOwnProperty("contentB")) { app.setAB(_root.contentA, _root.contentB); } else { app.setAB("flower.png", "flower.java.jpg"); // app.setAB("flower.png", "FuncMovie.swf"); } _root.onLoad = function() { DiffTool.app.onLoad(); } } }