package morphing { /** * @author Benjamin Jung * http://www.mukimuki.fr/flashblog/ */ import flash.display.BitmapData; import flash.display.DisplayObject; import flash.display.Shape; import flash.display.Sprite; import flash.geom.Matrix; import flash.geom.Point; public class Morpher { private var _bmp1:BitmapData; private var _bmp2:BitmapData; private var _bmp1Mesh:Mesh; private var _bmp2Mesh:Mesh; private var _nbMeshPts:int; private var _bmp1MeshMat:Array; private var _bmp2MeshMat:Array; private var _bShowMesh:Boolean; public function Morpher() { _bShowMesh = false; } /** * Initialisation of the morphing * @param bmp1 First image for the morphing process (source image) * @param bmp2 Second image for the morphing process (destination image) * @param bmp1Mesh Mesh of the first image * @param bmp2Mesh Mesh of the second image */ public function init(src:BitmapData, dest:BitmapData, bmp1Mesh:Mesh, bmp2Mesh:Mesh) :void { _bmp1 = src.clone(); _bmp2 = dest.clone(); _bmp1Mesh = bmp1Mesh; _bmp1MeshMat = computetInitTransformMatrix(_bmp1Mesh); _bmp2Mesh = bmp2Mesh; _bmp2MeshMat = computetInitTransformMatrix(_bmp2Mesh); _nbMeshPts = _bmp1Mesh.getNbPts(); } public function clear():void { if(_bmp1) _bmp1.dispose(); if(_bmp2) _bmp2.dispose(); } /** * To draw or not the mesh lines * @param b set to true if you want to draw the mesh lines */ public function showMesh(b:Boolean):void { _bShowMesh = b; } /** * Build the morphing intermediate image according the interpolation parameter * @param t interpolation paremeter * @return an object with the two warped images and the resulting image */ public function morph(t:Number):Object { var aDestMeshPts:Array = computeDestMeshPts(t); var warp1:Shape = computeWarpBmp(_bmp1, _bmp1Mesh.getPts(), aDestMeshPts, _bmp1Mesh.getTriangles(), _bmp1MeshMat); var warp2:Shape = computeWarpBmp(_bmp2, _bmp2Mesh.getPts(), aDestMeshPts, _bmp2Mesh.getTriangles(), _bmp2MeshMat); var result:Sprite = computeCrossDissolve(warp1, warp2, t); return {result:doToBmp(result), warp1:doToBmp(warp1), warp2:doToBmp(warp2)}; } // Private Methods private function getTransformMatrix(pt0:Point, pt1:Point, pt2:Point):Matrix { var w:Number = _bmp1.width; var h:Number = _bmp1.height; var mat:Matrix = new Matrix(); mat.a = (pt1.x - pt0.x) / w; mat.b = (pt1.y - pt0.y) / w; mat.c = (pt2.x - pt0.x) / h; mat.d = (pt2.y - pt0.y) / h; mat.tx = pt0.x; mat.ty = pt0.y; return mat; } private function computetInitTransformMatrix(mesh:Mesh):Array { var aPts:Array = mesh.getPts(); var aTriangles:Array = mesh.getTriangles(); var tri:MeshTriangle; var aReturn:Array = []; var mat:Matrix; for(var i:int = 0; i < aTriangles.length; ++i) { tri = aTriangles[i] as MeshTriangle; mat = getTransformMatrix(aPts[tri.p0], aPts[tri.p1], aPts[tri.p2]); mat.invert(); aReturn.push(mat); } return aReturn; } /** * Compute the intermediate mesh */ private function computeDestMeshPts(t:Number):Array { var aPts:Array = []; var aBmp1MeshPts:Array = _bmp1Mesh.getPts(); var aBmp2MeshPts:Array = _bmp2Mesh.getPts() for (var i:int = 0; i < _nbMeshPts; ++i) { var pt1:Point = aBmp1MeshPts[i]; var pt2:Point = aBmp2MeshPts[i]; // Point interpolation aPts.push(new Point(pt1.x + t * (pt2.x - pt1.x), pt1.y + t * (pt2.y - pt1.y))); } return aPts; } /** * Build warped image */ private function computeWarpBmp(bmp:BitmapData, aSrcMeshPts:Array, aDestMeshPts:Array, aMesh:Array, aInitMat:Array):Shape { var result:Shape = new Shape(); var ps0:Point; var ps1:Point; var ps2:Point; var pd0:Point; var pd1:Point; var pd2:Point; var tri:MeshTriangle; var mat:Matrix; for (var i:int = 0; i < aMesh.length; ++i) { tri = aMesh[i] as MeshTriangle; ps0 = aSrcMeshPts[tri.p0]; ps1 = aSrcMeshPts[tri.p1]; ps2 = aSrcMeshPts[tri.p2]; pd0 = aDestMeshPts[tri.p0]; pd1 = aDestMeshPts[tri.p1]; pd2 = aDestMeshPts[tri.p2]; mat = aInitMat[i].clone(); mat.concat(getTransformMatrix(pd0, pd1, pd2)); result.graphics.beginBitmapFill(bmp, mat, false, true); if(_bShowMesh) result.graphics.lineStyle(1, 0xFF0000); result.graphics.moveTo(pd0.x, pd0.y); result.graphics.lineTo(pd1.x, pd1.y); result.graphics.lineTo(pd2.x, pd2.y); result.graphics.lineTo(pd0.x, pd0.y); result.graphics.endFill(); } return result; } /** * Cross-dissolve the two warped images * */ private function computeCrossDissolve(warp1:Shape, warp2:Shape, t:Number):Sprite { var result:Sprite = new Sprite(); result.addChild(warp1); warp2.alpha = t; result.addChild(warp2); return result; } private function doToBmp(src:DisplayObject):BitmapData { var bmp:BitmapData = new BitmapData(src.width, src.height, false) bmp.draw(src); return bmp; } } }