package { import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.BlendMode; import flash.display.Loader; import flash.display.LoaderInfo; import flash.display.Shader; import flash.display.Shape; import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; import flash.geom.Matrix; import flash.geom.Point; import flash.net.FileFilter; import flash.net.FileReference; import flash.net.URLLoader; import flash.net.URLLoaderDataFormat; import flash.net.URLRequest; import flash.text.TextField; import flash.text.TextFormat; import flash.text.TextFormatAlign; import flash.utils.ByteArray; import flash.utils.describeType; [SWF(width=900, height=550, backgroundColor=0x333333)] /** * Class to test the application of blend modes. Two images are loaded and displayed, then copied and stacked * on the right of the stage. Clicking on the field with the blend mode name changes the blend mode. The colors * beneath the mouse in all three images (two loaded and the stacked composite) are displayed below the images. * Doubleclicking an image allows you to load in a new image in that position. */ public class graphic_flex_image_effects_02_Flex_BlendModes extends Sprite { // update this if the assets do not remain in the relative directory private static const ASSETS_DIRECTORY:String = "graphic-flex-image-effects-02-assets-"; // update this if another shader file is used private static const SHADER:String = ASSETS_DIRECTORY + "luminosity.pbj"; // update these to change the default images private static const IMAGE_0:String = ASSETS_DIRECTORY + "goat.jpg"; private static const IMAGE_1:String = ASSETS_DIRECTORY + "stagecoach.jpg"; private static const IMAGE_WIDTH:uint = 300; private static const IMAGE_HEIGHT:uint = 300; private static const COLOR_RECT_HEIGHT:uint = 50; private var _bitmapHolder:Sprite; private var _bitmap0:Bitmap; private var _bitmap1:Bitmap; private var _bitmap2:Bitmap; private var _bitmapLoading:Bitmap; private var _blendedBitmap:Bitmap; private var _colorRect:Shape; private var _colorLabel0:TextField; private var _colorLabel1:TextField; private var _colorLabel2:TextField; private var _targets:Shape; private var _mouseDown:Boolean; private var _blendModes:Array; private var _blendModeLabel:TextField; private var _file:FileReference; private var _colorPoint:Point; private var _shader:Shader; /** * Constructor. Simply calls init(). */ public function graphic_flex_image_effects_02_Flex_BlendModes() { init(); } /** * Initializes bitmap holders, stored an arrat of all blend modes and begins load of first image. */ private function init():void { _bitmapHolder = new Sprite(); _bitmapHolder.doubleClickEnabled = true; addChild(_bitmapHolder); _blendModes = []; // finds all the blend modes available to this sprite using describeType() and E4x // to grab the constant node from the XML that describeType returns for BlendMode var blendModes:XMLList = describeType(BlendMode).constant; // runs through all of the constants, which are all the names of the blend modes for each (var blendMode:XML in blendModes) { // pushes each constant name into the array _blendModes.push(blendMode.@name.toString()); } loadImage(IMAGE_0); } /** * Loads an image. * * @param imagePath The path to the image to load. */ private function loadImage(imagePath:String):void { var loader:Loader = new Loader(); loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onImageLoaded); loader.load(new URLRequest(imagePath)); } /** * Loads the shader specified in the class constant. */ private function loadShader():void { var loader:URLLoader = new URLLoader(); loader.dataFormat = URLLoaderDataFormat.BINARY; loader.addEventListener(Event.COMPLETE, onShaderLoaded); loader.load(new URLRequest(SHADER)); } /** * Adds an empty shape to the display list at the specified coordinate. * * @param x The x position for the shape. * @param y The y position for the shape. * * @return The shape created. */ private function addShape(x:Number, y:Number):Shape { var shape:Shape = new Shape(); shape.x = x; shape.y = y; addChild(shape); return shape; } /** * Adds a shape in which the colors of the hovered over pixels will be drawn as rectangles * as well as a shape in which the targets showing the mouse position in each image can be drawn. */ private function addColorShapes():void { _colorRect = addShape(0, IMAGE_HEIGHT); _targets = addShape(0, 0); } /** * Adds a textfield with the specified TextFormat at the specified x/y position. * * @param format The TextFormat to apply to the field. * @param x The x position for the field. * @param y The y position for the field. * * @return The field created. */ private function addTextField(format:TextFormat, x:Number, y:Number):TextField { var textField:TextField = new TextField(); textField.width = width; textField.multiline = true; textField.defaultTextFormat = format; textField.x = x; textField.y = y; addChild(textField); return textField; } /** * Adds three textfields to display the hex colors from each image based on mouse position. */ private function addColorLabels():void { var x:Number = 5; var y:Number = IMAGE_HEIGHT + COLOR_RECT_HEIGHT; var width:Number = IMAGE_WIDTH; var format:TextFormat = new TextFormat("Arial", 20, 0xFFFFFF); // uses a tab stop in the format to better align the color values format.tabStops = [50]; // place three fields, one for each image _colorLabel0 = addTextField(format, x, y); x += width; _colorLabel1 = addTextField(format, x, y); x += width; _colorLabel2 = addTextField(format, x, y); } /** * Adds a clickable textfield that will display the current blend mode. */ private function addBlendModeLabel():void { var labelHeight:uint = 100; var format:TextFormat = new TextFormat("Arial", labelHeight); format.align = TextFormatAlign.CENTER; _blendModeLabel = new TextField(); _blendModeLabel.selectable = false; _blendModeLabel.border = true; _blendModeLabel.background = true; _blendModeLabel.width = stage.stageWidth; _blendModeLabel.defaultTextFormat = format; _blendModeLabel.y = stage.stageHeight - labelHeight; // display initial blend mode _blendModeLabel.text = _blendModes[0]; // enable label to be clicked in order for blend mode to change _blendModeLabel.addEventListener(MouseEvent.CLICK, onBlendModeClick); addChild(_blendModeLabel); } /** * Enables mouse events on the bitmap holder. */ private function enableMouseEvents():void { _bitmapHolder.addEventListener(MouseEvent.DOUBLE_CLICK, onHolderDoubleClick); _bitmapHolder.addEventListener(MouseEvent.MOUSE_MOVE, onHolderMouseMove); _bitmapHolder.addEventListener(MouseEvent.MOUSE_DOWN, onHolderMouseDown); _bitmapHolder.addEventListener(MouseEvent.MOUSE_UP, onHolderMouseUp); } /** * Returns a string containing the hex value and the individual rgb values of a decimal number. * * @param color The decimal number representing a color. * * @return A string containing the hex and rgb values. */ private function formatColor(color:uint):String { var colorString:String = color.toString(16).toUpperCase(); // make sure we have 6 digits by adding 0's to the left while (colorString.length < 6) { colorString = "0" + colorString; } colorString = "hex:\t#" + colorString + "\n"; var red:uint = color >> 16 & 0xFF; var green:uint = color >> 8 & 0xFF; var blue:uint = color & 0xFF; colorString += "r:\t" + red + "\n"; colorString += "g:\t" + green + "\n"; colorString += "b:\t" + blue; return colorString; } /** * Draws a colored rectangle at the specified position. * * @param color The color to use as a fill. * @param x The x position for the rectangle. * @param y The y position for the rectangle. * @param width The width of the rectangle. * @param height The height of the rectangle. */ private function drawColorRect(color:uint, x:uint, y:uint, width:uint, height:uint):void { _colorRect.graphics.beginFill(color); _colorRect.graphics.drawRect(x, y, width, height); _colorRect.graphics.endFill(); } /** * Draws the colored rectangles and displays the textual color values based on mouse position. */ private function displayColorData():void { // only render if the mouse has been over an image if (_colorPoint) { _colorRect.graphics.clear(); // find color in first image based on mouse position var color:uint = _bitmap0.bitmapData.getPixel(_colorPoint.x, _colorPoint.y); _colorLabel0.text = formatColor(color); drawColorRect(color, 0, 0, IMAGE_WIDTH, COLOR_RECT_HEIGHT); // find color in second image based on mouse position color = _bitmap1.bitmapData.getPixel(_colorPoint.x, _colorPoint.y); _colorLabel1.text = formatColor(color); drawColorRect(color, IMAGE_WIDTH, 0, IMAGE_WIDTH, COLOR_RECT_HEIGHT); // draws all images, including the composite image, into a new BitmapData instance // so that the pixel value from the composite image can be retrieved var bitmapData:BitmapData = new BitmapData(IMAGE_WIDTH*3, IMAGE_HEIGHT); bitmapData.draw(_bitmapHolder); // find color in composite image based on mouse position color = bitmapData.getPixel(_colorPoint.x + IMAGE_WIDTH*2, _colorPoint.y); _colorLabel2.text = formatColor(color); drawColorRect(color, IMAGE_WIDTH*2, 0, IMAGE_WIDTH, COLOR_RECT_HEIGHT); bitmapData.dispose(); } } /** * Stores the relative point of the mouse for each image so that hovering over * one of the three images will result in color data being retrieved from all three images. */ private function setColorPoint(event:MouseEvent):void { var x:Number = event.localX; var y:Number = event.localY; // use modulo to find an x position between 0 and IMAGE_WIDTH, // no matter which image the mouse is over x %= IMAGE_WIDTH; _colorPoint = new Point(x, y); } /** * Handler for when the shader completes loading. * * @param event Event dispatched by URLLoader. */ private function onShaderLoaded(event:Event):void { var loader:URLLoader = event.target as URLLoader; _shader = new Shader(loader.data as ByteArray); } /** * Handler for when the blend mode label is clicked, applying new blend mode to composite image. * * @param event Event dispatched by _blendModeLabel TextField. */ private function onBlendModeClick(event:MouseEvent):void { // find current blend mode index var index:uint = _blendModes.indexOf(_blendModeLabel.text); // make sure that index of blend mode in array is within range if (++index >= _blendModes.length) { index = 0; } var blendMode:String = _blendModes[index]; _blendModeLabel.text = blendMode; // for the SHADER blend mode, apply shader to blendDhader property if (blendMode == "SHADER") { _blendedBitmap.blendShader = _shader; } _blendedBitmap.blendMode = BlendMode[blendMode]; // update color information displayColorData(); } /** * Handler for when a new image file is selected to be loaded, initiating the load. * * @param event Event dispatched by _file FileReference. */ private function onFileSelect(event:Event):void { _file.addEventListener(Event.COMPLETE, onImageLoadComplete); _file.load(); } /** * Handler for when a new image file completes loading. * * @param event Event dispatched by _file FileReference. */ private function onImageLoadComplete(event:Event):void { // have to use Loader to load bytes from the file data into a DisplayObject. var loader:Loader = new Loader(); loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLocalFileRead); loader.loadBytes(_file.data); } /** * Handler for when bytes from image file have been read into Loader instance. * * @param event Event dispatched by Loader. */ private function onLocalFileRead(event:Event):void { var loaderInfo:LoaderInfo = event.target as LoaderInfo; var bitmap:Bitmap = loaderInfo.content as Bitmap; // places image data into appropriate Bitmap instance _bitmapLoading.bitmapData = bitmap.bitmapData; // replaces image in stacked composite as well if (_bitmapLoading == _bitmap0) { _bitmap2.bitmapData = bitmap.bitmapData; } else { _blendedBitmap.bitmapData = bitmap.bitmapData; } } /** * Handler for when bitmap holder is doublclicked, opening file browse dialog. * * @param event Event dispatched by _bitmapHolder Sprite. */ private function onHolderDoubleClick(event:MouseEvent):void { var x:Number = stage.mouseX; var y:Number = stage.mouseY; _bitmapLoading = null; // find which bitmap was clicked and store it if (_bitmap0.hitTestPoint(x, y)) { _bitmapLoading = _bitmap0; } else if (_bitmap1.hitTestPoint(x, y)) { _bitmapLoading = _bitmap1; } // only open browse if one of the two images was clicked (not the composite) if (_bitmapLoading != null) { _file = new FileReference(); _file.addEventListener(Event.SELECT, onFileSelect); _file.browse([new FileFilter("Images", "*.jpg;*.gif;*.png")]); } } /** * Handler for when the mouse moves over the images. Updates the color data. * * @param event Event dispatched by _bitmapHolder Sprite. */ private function onHolderMouseMove(event:MouseEvent):void { // update the color information if the mouse is not depressed if (!_mouseDown) { setColorPoint(event); displayColorData(); event.updateAfterEvent(); } } /** * Handler for when the mouse is pressed while over the images. Draws targets over each image. * * @param event Event dispatched by _bitmapHolder Sprite. */ private function onHolderMouseDown(event:MouseEvent):void { // if the mouse was already down (and dragged off), update the color data now if (_mouseDown) { setColorPoint(event); displayColorData(); } _mouseDown = true; var x:Number = _colorPoint.x; var y:Number = _colorPoint.y; // draws targets for the relative mouse position on all three images var targetSize:uint = 5; _targets.graphics.clear(); _targets.graphics.lineStyle(1, 0xFF0000); for (var i:uint = 0; i < 3; i++) { _targets.graphics.moveTo(x, y-targetSize); _targets.graphics.lineTo(x, y+targetSize); _targets.graphics.moveTo(x-targetSize, y); _targets.graphics.lineTo(x+targetSize, y); x +=IMAGE_WIDTH; } } /** * Handler for when the mouse is released while over the images. Removes the drawn targets. * * @param event Event dispatched by _bitmapHolder Sprite. */ private function onHolderMouseUp(event:MouseEvent):void { _mouseDown = false; _targets.graphics.clear(); } /** * Handler for when the initial images complete loading. * * @param event Event dispatched by LoaderInfo. */ private function onImageLoaded(event:Event):void { var loaderInfo:LoaderInfo = event.target as LoaderInfo; var bitmap:Bitmap = loaderInfo.content as Bitmap; // scale the loaded images to match the size specified in the constants var matrix:Matrix = new Matrix(); matrix.scale(IMAGE_WIDTH/bitmap.width, IMAGE_HEIGHT/bitmap.height); var copiedImage:BitmapData = new BitmapData(IMAGE_WIDTH, IMAGE_HEIGHT); copiedImage.draw(bitmap.bitmapData, matrix); bitmap = new Bitmap(copiedImage); // blendedBitmap holds the composites on the right var blendedBitmap:Bitmap = new Bitmap(copiedImage); blendedBitmap.x = IMAGE_WIDTH*2; // add loaded bitmap (now scaled) and the copy for the composite to the stage _bitmapHolder.addChild(bitmap); _bitmapHolder.addChild(blendedBitmap); // if this is only first iamge, start loading of second if (_bitmapHolder.numChildren == 2) { _bitmap0 = bitmap; _bitmap2 = blendedBitmap; loadImage(IMAGE_1); // otherwise add the UI and load the shader } else { _bitmap1 = bitmap; _bitmap1.x = IMAGE_WIDTH; _blendedBitmap = blendedBitmap; addColorShapes(); addColorLabels(); addBlendModeLabel(); enableMouseEvents(); loadShader(); } } } }