package { import aether.utils.ImageUtil; import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.BlendMode; import flash.display.Shape; import flash.display.Sprite; import flash.events.Event; import flash.filters.BitmapFilterQuality; import flash.filters.BitmapFilterType; import flash.filters.GradientGlowFilter; import flash.geom.ColorTransform; import flash.geom.Matrix; import flash.geom.Rectangle; import flash.utils.ByteArray; [SWF(width=400, height=300, backgroundColor=0x000000)] /** * Draws the raw sound wave of a loaded sound playing in a circular pattern with * filters applied onto multiple planes that are rotates in 3D space. These planes * are then drawn into bitmap data and visually flipped in a mirror effect, creating * symmetrical shapes. */ public class graphic_flex_image_effects_11_Flex_MothFlowers extends Sprite { // the radius of the main circular pattern drawn onto the planes private const WAVE_RADIUS:uint = 250; // the colors used for the visualization private const WAVE_COLORS:Array = [0xFFFF66, 0xFFFFFF, 0x666699]; private var _soundController:graphic_flex_image_effects_11_Flex_SoundController; private var _planes:Vector.; private var _planesHolder:Sprite; private var _drawingPlane:Sprite; private var _drawingShape:Shape; private var _background:Bitmap; private var _backgroundShape:Shape; /** * Constructor. Creates assets in which the visualization will be drawn and loads sound. */ public function graphic_flex_image_effects_11_Flex_MothFlowers() { makeBackground(); makeDrawingPlane(); makePlanes(); _soundController = new graphic_flex_image_effects_11_Flex_SoundController("graphic-flex-image-effects-11-assets-SunriseStay.mp3"); _soundController.addEventListener(Event.CHANGE, onSoundChange); } /** * Creates background bitmap and background shape and places these on stage. */ private function makeBackground():void { // black shape will be drawn over previous rendering at low opacity to create ghosting effect _backgroundShape = new Shape(); _backgroundShape.graphics.beginFill(0); _backgroundShape.graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight); _backgroundShape.graphics.endFill(); addChild(_backgroundShape); _background = new Bitmap(); addChild(_background); } /** * Creates a single plane (sprite) into which a sound visualization will be drawn. * This is a nested shape within a sprite. A gradient glow filter is also applied. */ private function makeDrawingPlane():void { _drawingPlane = new Sprite(); _drawingShape = new Shape(); _drawingShape.x = stage.stageWidth/2; _drawingShape.y = stage.stageHeight/2; _drawingPlane.addChild(_drawingShape); _drawingPlane.filters = [ new GradientGlowFilter( 5, 45, WAVE_COLORS, [0, 1, 1], [0, 180, 255], 2, 2, 1, BitmapFilterQuality.MEDIUM, BitmapFilterType.FULL, true ) ]; } /** * Creates the three planes. These each consists of a bitmap within a sprite, the nested nature making * it easier to transform the image from a central registration point. All planes are held within a single * sprite. Each plane in rotated at a different angle around the y axis. */ private function makePlanes():void { _planes = new Vector.(); _planesHolder = new Sprite(); var plane:Bitmap; var planeHolder:Sprite; // used to center the sprites and their nested bitmaps var x:Number = stage.stageWidth/2; var y:Number = stage.stageHeight/2; var numPlanes:uint = 3; for (var i:uint = 0; i < numPlanes; i++) { plane = new Bitmap(); // offsetting by the halfwidth/halfheight will allow parent to be rotated // with the effect being that this bitmap is rotated around its center plane.x = -x; plane.y = -y; planeHolder = new Sprite(); planeHolder.x = x; planeHolder.y = y; // difference mode creates interesting colors planeHolder.blendMode = BlendMode.DIFFERENCE; planeHolder.addChild(plane); // results in all three planes being equally rotated in full circle planeHolder.rotationY = i*(180/numPlanes); _planes.push(plane); _planesHolder.addChild(planeHolder); } } /** * Updates the main visualization with new sound data. This draws into the main * drawing shape, which will have its data copied into each plane. */ private function updateDrawingShape():void { // raw sound wave data is captured var spectrumData:ByteArray = _soundController.getSoundSpectrum(false); _drawingShape.graphics.clear(); _drawingShape.graphics.lineStyle(1, 0xFFFFFF); var x:Number; var y:Number; var i:int = -1; var value:Number; var angle:Number; while (++i < 512) { // value is scaled to be within WAVE_RADIUS value value = Math.ceil(WAVE_RADIUS*spectrumData.readFloat()); // each value will be drawn at different angle around full circle angle = i/512*Math.PI*2; x = Math.cos(angle)*value; y = Math.sin(angle)*value; _drawingShape.graphics.moveTo(0, 0); _drawingShape.graphics.lineTo(x, y); } } /** * Updates each of the planes with the new drawn data. */ private function updatePlanes():void { var width:Number = stage.stageWidth; var height:Number = stage.stageHeight; // captures the pixel data of the drawing plane var capture:BitmapData = new BitmapData(width, height, true, 0x00000000); capture.draw(_drawingPlane); // draws data into each plane for each (var bitmap:Bitmap in _planes) { bitmap.bitmapData = capture.clone(); } } /** * Updates the background bitmap data with current visuals on the stage, scaled up and * set at a low opacity over the previous rendering. */ private function updateBackground():void { // capture pixels from all three planes var capture:BitmapData = ImageUtil.getBitmapData(_planesHolder); var matrix:Matrix = new Matrix(); // used to flip image horizontally matrix.scale(-1, 1); matrix.translate(width, 0); // only need to draw half of flipped data to get mirrow effect var rect:Rectangle = new Rectangle(0, 0, width/2, height); var halfData:BitmapData = capture.clone(); // fill half of cloned image with transparent pixels capture.fillRect(rect, 0x00000000); // fill other half with flipped image capture.draw(halfData, matrix, null, null, rect); // get last rendering on stage var stageCapture:BitmapData = ImageUtil.getBitmapData(this); // draw black shape at low opacity to reveal previous renderings stageCapture.draw(_backgroundShape, null, new ColorTransform(1, 1, 1, .3)); // draw new symmetrical image stageCapture.draw(capture); _background.bitmapData = stageCapture; } /** * Handler for when the sound changes (basically, an ENTER_FRAME while the sound is playing). * This calls the update methods in this class to redraw visualization. * * @param event Event dispatched by SoundController. */ private function onSoundChange(event:Event):void { updateDrawingShape(); updatePlanes(); updateBackground(); } } }