blob: 1113173fb8c1019eea302d556ba1f6f2e9ba0b28 [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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.
*
* <p>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.</p>
*
* <p>This class accepts the first four parameters that are passed
* to DropShadowFilter: <code>alpha</code>, <code>angle</code>,
* <code>color</code>, and <code>distance</code>.
* In addition, this class accepts the corner radii for each of the four
* corners of the rectangularly-shaped object that is casting a shadow.</p>
*
* <p>Once those 8 values have been set,
* this class pre-computes the drop shadow in an offscreen Bitmap.
* When the <code>drawShadow()</code> method is called, pieces of the
* precomputed drop shadow are copied onto the passed-in Graphics object.</p>
*
* @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;
}
}
}
}