//////////////////////////////////////////////////////////////////////////////// // // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. // The ASF licenses this file to You under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance with // the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// package mx.graphics { import flash.display.BitmapData; import flash.display.Graphics; import flash.display.Shape; import flash.filters.DropShadowFilter; import flash.geom.Matrix; import flash.geom.Point; import flash.geom.Rectangle; import mx.core.FlexShape; import mx.utils.GraphicsUtil; /** * Drop shadows are typically created using the DropShadowFilter class. * However, the DropShadowFilter, like all bitmap filters, * can be computationally expensive. * If the DropShadowFilter is applied to a DisplayObject, * then the drop shadow is recalculated * whenever the appearance of the object changes. * If the DisplayObject is animated (using a Resize effect, for example), * then the presence of drop shadows hurts the animation refresh rate. * *

This class optimizes drop shadows for a common case. * If you are applying a drop shadow to a rectangularly-shaped object * whose edges fall on pixel boundaries, then this class should * be used instead of using the DropShadowFilter directly.

* *

This class accepts the first four parameters that are passed * to DropShadowFilter: alpha, angle, * color, and distance. * In addition, this class accepts the corner radii for each of the four * corners of the rectangularly-shaped object that is casting a shadow.

* *

Once those 8 values have been set, * this class pre-computes the drop shadow in an offscreen Bitmap. * When the drawShadow() method is called, pieces of the * precomputed drop shadow are copied onto the passed-in Graphics object.

* * @see flash.filters.DropShadowFilter * @see flash.display.DisplayObject * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public class RectangularDropShadow { include "../core/Version.as"; //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function RectangularDropShadow() { super(); } //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- /** * @private * The drop shadow is rendered into this BitmapData object, * which is later copied to the passed-in Graphics */ private var shadow:BitmapData; /** * @private */ private var leftShadow:BitmapData; /** * @private */ private var rightShadow:BitmapData; /** * @private */ private var topShadow:BitmapData; /** * @private */ private var bottomShadow:BitmapData; /** * @private * Remembers whether any of the public properties have changed * since the most recent call to drawDropShadow(). */ private var changed:Boolean = true; //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //---------------------------------- // alpha //---------------------------------- /** * @private * Storage for the alpha property. */ private var _alpha:Number = 0.4; [Inspectable] /** * @copy flash.filters.DropShadowFilter#alpha * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get alpha():Number { return _alpha; } /** * @private */ public function set alpha(value:Number):void { if (_alpha != value) { _alpha = value; changed = true; } } //---------------------------------- // angle //---------------------------------- /** * @private * Storage for the angle property. */ private var _angle:Number = 45.0; [Inspectable] /** * @copy flash.filters.DropShadowFilter#angle * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get angle():Number { return _angle; } /** * @private */ public function set angle(value:Number):void { if (_angle != value) { _angle = value; changed = true; } } //---------------------------------- // color //---------------------------------- /** * @private * Storage for the color property. */ private var _color:int = 0; [Inspectable] /** * @copy flash.filters.DropShadowFilter#color * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get color():int { return _color; } /** * @private */ public function set color(value:int):void { if (_color != value) { _color = value; changed = true; } } //---------------------------------- // distance //---------------------------------- /** * @private * Storage for the distance property. */ private var _distance:Number = 4.0; [Inspectable] /** * @copy flash.filters.DropShadowFilter#distance * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get distance():Number { return _distance; } /** * @private */ public function set distance(value:Number):void { if (_distance != value) { _distance = value; changed = true; } } //---------------------------------- // tlRadius //---------------------------------- /** * @private * Storage for the tlRadius property. */ private var _tlRadius:Number = 0; [Inspectable] /** * The corner radius of the top left corner * of the rounded rectangle that is casting the shadow. * May be zero for non-rounded rectangles. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get tlRadius():Number { return _tlRadius; } /** * @private */ public function set tlRadius(value:Number):void { if (_tlRadius != value) { _tlRadius = value; changed = true; } } //---------------------------------- // trRadius //---------------------------------- /** * @private * Storage for the trRadius property. */ private var _trRadius:Number = 0; [Inspectable] /** * The corner radius of the top right corner * of the rounded rectangle that is casting the shadow. * May be zero for non-rounded rectangles. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get trRadius():Number { return _trRadius; } /** * @private */ public function set trRadius(value:Number):void { if (_trRadius != value) { _trRadius = value; changed = true; } } //---------------------------------- // blRadius //---------------------------------- /** * @private * Storage for the blRadius property. */ private var _blRadius:Number = 0; [Inspectable] /** * The corner radius of the bottom left corner * of the rounded rectangle that is casting the shadow. * May be zero for non-rounded * rectangles. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get blRadius():Number { return _blRadius; } /** * @private */ public function set blRadius(value:Number):void { if (_blRadius != value) { _blRadius = value; changed = true; } } //---------------------------------- // brRadius //---------------------------------- /** * @private * Storage for the brRadius property. */ private var _brRadius:Number = 0; [Inspectable] /** * The corner radius of the bottom right corner * of the rounded rectangle that is casting the shadow. * May be zero for non-rounded rectangles. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get brRadius():Number { return _brRadius; } /** * @private */ public function set brRadius(value:Number):void { if (_brRadius != value) { _brRadius = value; changed = true; } } //---------------------------------- // blurX //---------------------------------- /** * @private * Storage for the brRadius property. */ private var _blurX:Number = 4; [Inspectable] /** * The amount of horizontal blur. * @default 4 * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 4 */ public function get blurX():Number { return _blurX; } /** * @private */ public function set blurX(value:Number):void { if (_blurX != value) { _blurX = value; changed = true; } } //---------------------------------- // blurY //---------------------------------- /** * @private * Storage for the brRadius property. */ private var _blurY:Number = 4; [Inspectable] /** * The amount of vertical blur. * @default 4 * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 4 */ public function get blurY():Number { return _blurY; } /** * @private */ public function set blurY(value:Number):void { if (_blurY != value) { _blurY = value; changed = true; } } //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- /** * Renders the shadow on the screen. * * @param g The Graphics object on which to draw the shadow. * * @param x The horizontal offset of the drop shadow, * based on the Graphics object's position. * * @param y The vertical offset of the drop shadow, * based on the Graphics object's position. * * @param width The width of the shadow, in pixels. * * @param height The height of the shadow, in pixels. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function drawShadow(g:Graphics, x:Number, y:Number, width:Number, height:Number):void { // If any parameters of the shadow have changed, // then regenerate the offscreen bitmaps. if (changed) { createShadowBitmaps(); changed = false; } width = Math.ceil(width); height = Math.ceil(height); // Determine the thickness of the shadow along each of the edges var leftThickness:int = leftShadow ? leftShadow.width : 0; var rightThickness:int = rightShadow ? rightShadow.width : 0; var topThickness:int = topShadow ? topShadow.height : 0; var bottomThickness:int = bottomShadow ? bottomShadow.height : 0; var widthThickness:int = leftThickness + rightThickness; var heightThickness:int = topThickness + bottomThickness; var maxCornerHeight:Number = (height + heightThickness) / 2; var maxCornerWidth:Number = (width + widthThickness) / 2; var matrix:Matrix = new Matrix(); // Copy the corners of the shadow bitmap onto the graphics object if (leftShadow || topShadow) { var tlWidth:Number = Math.min(tlRadius + widthThickness, maxCornerWidth); var tlHeight:Number = Math.min(tlRadius + heightThickness, maxCornerHeight); matrix.tx = x - leftThickness; matrix.ty = y - topThickness; // Do not repeat the bitmap fill, its edge pixels will be used // to fill the remaining space g.beginBitmapFill(shadow, matrix, false); g.drawRect(x - leftThickness, y - topThickness, tlWidth, tlHeight); g.endFill(); } if (rightShadow || topShadow) { var trWidth:Number = Math.min(trRadius + widthThickness, maxCornerWidth); var trHeight:Number = Math.min(trRadius + heightThickness, maxCornerHeight); matrix.tx = x + width + rightThickness - shadow.width; matrix.ty = y - topThickness; g.beginBitmapFill(shadow, matrix, false); g.drawRect(x + width + rightThickness - trWidth, y - topThickness, trWidth, trHeight); g.endFill(); } if (leftShadow || bottomShadow) { var blWidth:Number = Math.min(blRadius + widthThickness, maxCornerWidth); var blHeight:Number = Math.min(blRadius + heightThickness, maxCornerHeight); matrix.tx = x - leftThickness; matrix.ty = y + height + bottomThickness - shadow.height; g.beginBitmapFill(shadow, matrix, false); g.drawRect(x - leftThickness, y + height + bottomThickness - blHeight, blWidth, blHeight); g.endFill(); } if (rightShadow || bottomShadow) { var brWidth:Number = Math.min(brRadius + widthThickness, maxCornerWidth); var brHeight:Number = Math.min(brRadius + heightThickness, maxCornerHeight); matrix.tx = x + width + rightThickness - shadow.width; matrix.ty = y + height + bottomThickness - shadow.height; g.beginBitmapFill(shadow, matrix, false); g.drawRect(x + width + rightThickness - brWidth, y + height + bottomThickness - brHeight, brWidth, brHeight); g.endFill(); } // Copy the sides of the shadow bitmap onto the graphics object if (leftShadow) { matrix.tx = x - leftThickness; matrix.ty = 0; g.beginBitmapFill(leftShadow, matrix, false); g.drawRect(x - leftThickness, y - topThickness + tlHeight, leftThickness, height + topThickness + bottomThickness - tlHeight - blHeight); g.endFill(); } if (rightShadow) { matrix.tx = x + width; matrix.ty = 0; g.beginBitmapFill(rightShadow, matrix, false); g.drawRect(x + width, y - topThickness + trHeight, rightThickness, height + topThickness + bottomThickness - trHeight - brHeight); g.endFill(); } if (topShadow) { matrix.tx = 0; matrix.ty = y - topThickness; g.beginBitmapFill(topShadow, matrix, false); g.drawRect(x - leftThickness + tlWidth, y - topThickness, width + leftThickness + rightThickness - tlWidth - trWidth, topThickness); g.endFill(); } if (bottomShadow) { matrix.tx = 0; matrix.ty = y + height; g.beginBitmapFill(bottomShadow, matrix, false); g.drawRect(x - leftThickness + blWidth, y + height, width + leftThickness + rightThickness - blWidth - brWidth, bottomThickness); g.endFill(); } } /** * @private * Render the drop shadow for the rounded rectangle * in a small BitmapData object. * The shadow will be copied onto the graphics object * passed into drawDropShadow(). */ private function createShadowBitmaps():void { // Create a Shape containing a round rectangle that the // specified corner radii and very short sides. var roundRectWidth:Number = Math.max(tlRadius, blRadius) + 3 * Math.max(Math.abs(distance), 2) + Math.max(trRadius, brRadius); var roundRectHeight:Number = Math.max(tlRadius, trRadius) + 3 * Math.max(Math.abs(distance), 2) + Math.max(blRadius, brRadius); if (roundRectWidth < 0 || roundRectHeight < 0) return; var roundRect:Shape = new FlexShape(); var g:Graphics = roundRect.graphics; g.beginFill(0xFFFFFF); GraphicsUtil.drawRoundRectComplex( g, 0, 0, roundRectWidth, roundRectHeight, tlRadius, trRadius, blRadius, brRadius); g.endFill(); // Copy the round rectangle into a BitmapData object var roundRectBitmap:BitmapData = new BitmapData( roundRectWidth, roundRectHeight, true, 0x00000000); roundRectBitmap.draw(roundRect, new Matrix()); // Get the size of the drop shadow that will be cast by this // rounded rectangle. var filter:DropShadowFilter = new DropShadowFilter(distance, angle, color, alpha, blurX, blurY); filter.knockout = true; var inputRect:Rectangle = new Rectangle(0, 0, roundRectWidth, roundRectHeight); var outputRect:Rectangle = roundRectBitmap.generateFilterRect(inputRect, filter); // Determine the thickness of each edge of the drop shadow var leftThickness:Number = inputRect.left - outputRect.left; var rightThickness:Number = outputRect.right - inputRect.right; var topThickness:Number = inputRect.top - outputRect.top; var bottomThickness:Number = outputRect.bottom - inputRect.bottom; // Create a BitmapData object large enough to contain the // rounded rectangle and its drop shadow. Render the drop // shadow into this BitmapData shadow = new BitmapData(outputRect.width, outputRect.height); shadow.applyFilter(roundRectBitmap, inputRect, new Point(leftThickness, topThickness), filter); // For each of the four sides of the round rectangle, create a copy // of the drop shadow in a separate BitmapData object var origin:Point = new Point(0, 0); var rect:Rectangle = new Rectangle(); if (leftThickness > 0) { rect.x = 0; rect.y = tlRadius + topThickness + bottomThickness; rect.width = leftThickness; rect.height = 1; leftShadow = new BitmapData(leftThickness, 1); leftShadow.copyPixels(shadow, rect, origin); } else { leftShadow = null; } if (rightThickness > 0) { rect.x = shadow.width - rightThickness; rect.y = trRadius + topThickness + bottomThickness; rect.width = rightThickness; rect.height = 1; rightShadow = new BitmapData(rightThickness, 1); rightShadow.copyPixels(shadow, rect, origin); } else { rightShadow = null; } if (topThickness > 0) { rect.x = tlRadius + leftThickness + rightThickness; rect.y = 0; rect.width = 1; rect.height = topThickness; topShadow = new BitmapData(1, topThickness); topShadow.copyPixels(shadow, rect, origin); } else { topShadow = null; } if (bottomThickness > 0) { rect.x = blRadius + leftThickness + rightThickness; rect.y = shadow.height - bottomThickness; rect.width = 1; rect.height = bottomThickness; bottomShadow = new BitmapData(1, bottomThickness); bottomShadow.copyPixels(shadow, rect, origin); } else { bottomShadow = null; } } } }