////////////////////////////////////////////////////////////////////////////////
//
//  ADOBE SYSTEMS INCORPORATED
//  Copyright 2010 Adobe Systems Incorporated
//  All Rights Reserved.
//
//  NOTICE: Adobe permits you to use, modify, and distribute this file
//  in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////

package spark.primitives
{

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.display.DisplayObjectContainer;
import flash.display.Graphics;
import flash.display.Loader;
import flash.display.LoaderInfo;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.HTTPStatusEvent;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.events.SecurityErrorEvent;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.net.URLRequest;
import flash.system.ApplicationDomain;
import flash.system.Capabilities;
import flash.system.LoaderContext;
import flash.utils.ByteArray;

import mx.core.FlexGlobals;
import mx.core.IInvalidating;
import mx.core.mx_internal;
import mx.events.FlexEvent;
import mx.graphics.BitmapFillMode;
import mx.graphics.BitmapScaleMode;
import mx.graphics.BitmapSmoothingQuality;
import mx.utils.DensityUtil;
import mx.utils.LoaderUtil;

import spark.core.ContentRequest;
import spark.core.DisplayObjectSharingMode;
import spark.core.IContentLoader;
import spark.layouts.HorizontalAlign;
import spark.layouts.VerticalAlign;
import spark.primitives.supportClasses.GraphicElement;
import spark.utils.MultiDPIBitmapSource;

use namespace mx_internal;

//--------------------------------------
//  Events
//--------------------------------------

/**
 *  Dispatched when content loading is complete. This
 *  event is only dispatched for url and ByteArray based
 *  sources (those sources requiring a Loader).
 *
 *  <p>Note that for content loaded via Loader, both
 *  <code>ready</code> and <code>complete</code> events
 *  are dispatched.</p>  For other source types such as
 *  embeds, only <code>ready</code> is dispatched.
 *
 *  @eventType flash.events.Event.COMPLETE
 *
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 2.0
 *  @productversion Flex 4.5
 */
[Event(name="complete", type="flash.events.Event")]

/**
 *  Dispatched when a network request is made over HTTP
 *  and Flash Player or AIR can detect the HTTP status code.
 *
 *  @eventType flash.events.HTTPStatusEvent.HTTP_STATUS
 *
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 2.0
 *  @productversion Flex 4.5
 */
[Event(name="httpStatus", type="flash.events.HTTPStatusEvent")]

/**
 *  Dispatched when an input/output error occurs.
 *  @see flash.events.IOErrorEvent
 *
 *  @eventType flash.events.IOErrorEvent.IO_ERROR
 *
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 2.0
 *  @productversion Flex 4.5
 */
[Event(name="ioError", type="flash.events.IOErrorEvent")]

/**
 *  Dispatched when content is loading.
 *
 *  <p><strong>Note:</strong>
 *  The <code>progress</code> event is not guaranteed to be dispatched.
 *  The <code>complete</code> event may be received, without any
 *  <code>progress</code> events being dispatched.
 *  This can happen when the loaded content is a local file.</p>
 *
 *  @eventType flash.events.ProgressEvent.PROGRESS
 *
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 2.0
 *  @productversion Flex 4.5
 */
[Event(name="progress", type="flash.events.ProgressEvent")]

/**
 *  Dispatched when content loading is complete.  Unlike the
 *  <code>complete</code> event, this event is dispatched for
 *  all source types.
 *
 *  <p>Note that for content loaded via Loader, both
 *  <code>ready</code> and <code>complete</code> events
 *  are dispatched.</p>  For other source types such as
 *  embeds, only <code>ready</code> is dispatched.
 *
 *  @eventType mx.events.FlexEvent.READY
 *
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 2.0
 *  @productversion Flex 4.5
 */
[Event(name="ready", type="mx.events.FlexEvent")]

/**
 *  Dispatched when a security error occurs.
 *  @see flash.events.SecurityErrorEvent
 *
 *  @eventType flash.events.SecurityErrorEvent.SECURITY_ERROR
 *
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 2.0
 *  @productversion Flex 4.5
 */
[Event(name="securityError", type="flash.events.SecurityErrorEvent")]

/**
 *  A BitmapImage element defines a rectangular region in its parent element's
 *  coordinate space, filled with bitmap data drawn from a source file or
 *  source URL.
 *
 *  @includeExample examples/BitmapImageExample.mxml
 *
 *  @langversion 3.0
 *  @playerversion Flash 10
 *  @playerversion AIR 2.0
 *  @productversion Flex 4.0
 */
public class BitmapImage extends GraphicElement
{
    include "../core/Version.as";

    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------

    /**
     *  Constructor.
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function BitmapImage()
    {
        super();

        // Typically, this should not be mirrored.
        layoutDirection = "ltr";
    }

    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------

    private var _scaleGridBottom:Number;
    private var _scaleGridLeft:Number;
    private var _scaleGridRight:Number;
    private var _scaleGridTop:Number;
    private var bitmapDataCreated:Boolean;
    private static var matrix:Matrix = new Matrix();
    private var cachedSourceGrid:Array;
    private var cachedDestGrid:Array;
    private var imageWidth:Number = NaN;
    private var imageHeight:Number = NaN;
    private var loadedContent:DisplayObject;
    private var loadingContent:Object;
    private var previousUnscaledWidth:Number;
    private var previousUnscaledHeight:Number;
    private var sourceInvalid:Boolean;
    private var loadFailed:Boolean;

    //----------------------------------
    //  bitmapData
    //----------------------------------

    private var _bitmapData:BitmapData;

    /**
     *  Returns a copy of the BitmapData object representing
     *  the currently loaded image content (unscaled).  This property
     *  is <code>null</code> for untrusted cross domain content.
     *
     *  @default null
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 2.0
     *  @productversion Flex 4.5
     */
    public function get bitmapData():BitmapData
    {
        return _bitmapData ? _bitmapData.clone() : _bitmapData;
    }

    //----------------------------------
    //  bytesLoaded
    //----------------------------------

    private var _bytesLoaded:Number = NaN;

    /**
     *  The number of bytes of the image already loaded.
     *  Only relevant for images loaded by request URL.
     *
     *  @default NaN
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 2.0
     *  @productversion Flex 4.5
     */
    public function get bytesLoaded():Number
    {
        return _bytesLoaded;
    }

    //----------------------------------
    //  bytesTotal
    //----------------------------------

    private var _bytesTotal:Number = NaN;

    /**
     *  The total image data in bytes loaded or pending load.
     *  Only relevant for images loaded by request URL.
     *
     *  @default NaN
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 2.0
     *  @productversion Flex 4.5
     */
    public function get bytesTotal():Number
    {
        return _bytesTotal;
    }

    //----------------------------------
    //  clearOnLoad
    //----------------------------------

    private var _clearOnLoad:Boolean = true;

    /**
     *  Denotes whether or not to clear previous
     *  image content prior to loading new content.
     *
     *  @default true
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 2.0
     *  @productversion Flex 4.5
     */
    public function get clearOnLoad():Boolean
    {
        return _clearOnLoad;
    }

    /**
     *  @private
     */
    public function set clearOnLoad(value:Boolean):void
    {
        _clearOnLoad = value;
    }

    //----------------------------------
    //  contentLoaderGrouping
    //----------------------------------

    private var _contentLoaderGrouping:String;

    /**
     *  Optional content grouping identifier to pass to the an
     *  associated IContentLoader instance's load() method.
     *  This property is only considered when a valid contentLoader
     *  is assigned.
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4.5
     */
    public function get contentLoaderGrouping():String
    {
        return _contentLoaderGrouping;
    }

    /**
     *  @private
     */
    public function set contentLoaderGrouping(value:String):void
    {
        if (value != _contentLoaderGrouping)
        {
            _contentLoaderGrouping = value;
            invalidateProperties();
        }
    }

    //----------------------------------
    //  contentLoader
    //----------------------------------

    private var _contentLoader:IContentLoader;
    private var contentLoaderInvalid:Boolean;

    /**
     *  Optional custom image loader (e.g. image cache or queue) to
     *  associate with content loader client.
     *
     *  @default null
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4.5
     */
    public function get contentLoader():IContentLoader
    {
        return _contentLoader;
    }

    /**
     *  @private
     */
    public function set contentLoader(value:IContentLoader):void
    {
        if (value != _contentLoader)
        {
            _contentLoader = value;
            contentLoaderInvalid = true;
            invalidateProperties();
        }
    }

    //----------------------------------
    //  fillMode
    //----------------------------------

    /**
     *  @private
     */
    protected var _fillMode:String = BitmapFillMode.SCALE;

    [Inspectable(category="General", enumeration="clip,repeat,scale", defaultValue="scale")]

    /**
     *  Determines how the bitmap fills in the dimensions. If you set the value
     *  of this property in a tag, use the string (such as "repeat"). If you set the value of
     *  this property in ActionScript, use the constant (such as <code>mx.graphics.BitmapFillMode.CLIP</code>).
     *
     *  <p>When set to <code>BitmapFillMode.CLIP</code> ("clip"), the bitmap
     *  ends at the edge of the region.</p>
     *
     *  <p>When set to <code>BitmapFillMode.REPEAT</code> ("repeat"), the bitmap
     *  repeats to fill the region.</p>
     *
     *  <p>When set to <code>BitmapFillMode.SCALE</code> ("scale"), the bitmap
     *  stretches to fill the region.</p>
     *
     *  @default <code>BitmapFillMode.SCALE</code>
     *
     *  @see mx.graphics.BitmapFillMode
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get fillMode():String
    {
        return _fillMode;
    }

    /**
     *  @private
     */
    public function set fillMode(value:String):void
    {
        if (value != _fillMode)
        {
            _fillMode = value;
            invalidateDisplayList();
        }
    }

    //----------------------------------
    //  horizontalAlign
    //----------------------------------

    /**
     *  @private
     */
    private var _horizontalAlign:String = HorizontalAlign.CENTER;

    [Inspectable(category="General", enumeration="left,right,center", defaultValue="center")]

    /**
     *  The horizontal alignment of the content when it does not have
     *  a one-to-one aspect ratio and <code>scaleMode</code> is set to
     *  <code>mx.graphics.BitmapScaleMode.LETTERBOX</code>.
     *
     *  <p>Can be one of <code>HorizontalAlign.LEFT</code> ("left"),
     *  <code>HorizontalAlign.RIGHT</code> ("right"), or
     *  <code>HorizontalAlign.CENTER</code> ("center").</p>
     *
     *  <p>This property is only applicable when <code>fillMode</code> is set to
     *  to <code>mx.graphics.BitmapFillMode.SCALE</code> ("scale").</p>
     *
     *  @default <code>HorizontalAlign.CENTER</code>
     *
     *  @see mx.graphics.BitmapFillMode
     *  @see mx.graphics.BitmapScaleMode
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get horizontalAlign():String
    {
        return _horizontalAlign;
    }

    /**
     *  @private
     */
    public function set horizontalAlign(value:String):void
    {
        if (value == _horizontalAlign)
            return;

        _horizontalAlign = value;
        invalidateDisplayList();
    }

    /**
     *  @private
     */
    private function getHorizontalAlignValue():Number
    {
        if (_horizontalAlign == HorizontalAlign.LEFT)
            return 0;
        else if (_horizontalAlign == HorizontalAlign.RIGHT)
            return 1;
        else
            return 0.5;
    }

    //----------------------------------
    //  preliminaryHeight
    //----------------------------------

    /**
     *  @private
     */
    private var _preliminaryHeight:Number = NaN;

    /**
     *  Provides an estimate to use for height when the "measured" bounds
     *  of the image is requested by layout, but the image data has
     *  yet to complete loading. When NaN the measured height is 0 until
     *  the image has finished loading.
     *
     *  @default NaN
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4.5
     */
    public function get preliminaryHeight():Number
    {
        return _preliminaryHeight;
    }

    /**
     *  @private
     */
    public function set preliminaryHeight(value:Number):void
    {
        if (value != _preliminaryHeight)
        {
            _preliminaryHeight = value;
            invalidateSize();
        }
    }

    //----------------------------------
    //  preliminaryWidth
    //----------------------------------

    /**
     *  @private
     */
    private var _preliminaryWidth:Number = NaN;

    /**
     *  Provides an estimate to use for width when the "measured" bounds
     *  of the image is requested by layout, but the image data has
     *  yet to complete loading. When NaN the measured width is 0 until
     *  the image has finished loading.
     *
     *  @default NaN
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4.5
     */
    public function get preliminaryWidth():Number
    {
        return _preliminaryWidth;
    }

    /**
     *  @private
     */
    public function set preliminaryWidth(value:Number):void
    {
        if (value != _preliminaryWidth)
        {
            _preliminaryWidth = value;
            invalidateSize();
        }
    }

    //----------------------------------
    //  scaleMode
    //----------------------------------

    /**
     *  @private
     */
    private var _scaleMode:String = BitmapScaleMode.STRETCH;

    [Inspectable(category="General", enumeration="stretch,letterbox,zoom", defaultValue="stretch")]

    /**
     *  Determines how the image is scaled when <code>fillMode</code> is set to
     *  <code>mx.graphics.BitmapFillMode.SCALE</code>.
     *
     *  <p>When set to <code>mx.graphics.BitmapScaleMode.STRETCH</code> ("stretch"),
     *  the image is stretched to fit.</p>
     *
     *  <p>When set to <code>BitmapScaleMode.LETTERBOX</code> ("letterbox"),
     *  the image is scaled with respect to the original unscaled image's
     *  aspect ratio.</p>
     *
     *  <p>When set to <code>BitmapScaleMode.ZOOM</code> ("zoom"),
     *  the image is scaled to fit with respect to the original unscaled image's
     *  aspect ratio. This results in cropping on one axis.</p>
     *
     *  @default <code>BitmapScaleMode.STRETCH</code>
     *
     *  @see mx.graphics.BitmapFillMode
     *  @see mx.graphics.BitmapScaleMode
     *
     *  @langversion 3.0
     *  @playerversion Flash 10.1
     *  @playerversion AIR 2.0
     *  @productversion Flex 4.5
     */
    public function get scaleMode():String
    {
        return _scaleMode;
    }

    /**
     *  @private
     */
    public function set scaleMode(value:String):void
    {
        if (value == _scaleMode)
            return;

        _scaleMode = value;
        invalidateDisplayList();
    }

    //----------------------------------
    //  smooth
    //----------------------------------

    private var _smooth:Boolean = false;

    [Inspectable(category="General", enumeration="true,false", defaultValue="false")]

    /**
     *  @copy flash.display.GraphicsBitmapFill#smooth
     *
     *  @default false
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function set smooth(value:Boolean):void
    {
        if (value != _smooth)
        {
            _smooth = value;
            invalidateDisplayList();
        }
    }

    /**
     *  @private
     */
    public function get smooth():Boolean
    {
        return _smooth;
    }

    //----------------------------------
    //  smoothingQuality
    //----------------------------------

    private var _smoothingQuality:String = BitmapSmoothingQuality.DEFAULT;

    [Inspectable(category="General", enumeration="default,high", defaultValue="default")]

    /**
     *  Determines how the image is down-scaled.  When set to
     *  <code>BitmapSmoothingQuality.HIGH</code>, the image is resampled (if data
     *  is from a trusted source) to achieve a higher quality result.
     *  If set to <code>BitmapSmoothingQuality.DEFAULT</code>, the default stage
     *  quality for scaled bitmap fills is used.
     *
     *  @default <code>BitmapSmoothingQuality.DEFAULT</code>
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4.5
     */
    public function set smoothingQuality(value:String):void
    {
        if (value != _smoothingQuality)
        {
            _smoothingQuality = value;
            invalidateDisplayList();
        }
    }

    /**
     *  @private
     */
    public function get smoothingQuality():String
    {
        return _smoothingQuality;
    }

    //----------------------------------
    //  source
    //----------------------------------

    private var _source:Object;

    [Bindable("sourceChanged")]
    [Inspectable(category="General")]

    /**
     *  The source used for the bitmap fill. The fill can render from various
     *  graphical sources, including the following:
     *  <ul>
     *   <li>A Bitmap or BitmapData instance.</li>
     *   <li>A class representing a subclass of DisplayObject. The BitmapFill
     *       instantiates the class and creates a bitmap rendering of it.</li>
     *   <li>An instance of a DisplayObject. The BitmapFill copies it into a
     *       Bitmap for filling.</li>
     *   <li>The name of an external image file. </li>
     *  </ul>
     *
     *  <p>If you use an image file for the source, it can be of type PNG, GIF,
     *  or JPG.</p>
     *
     *  <p>To specify an embedded image source, you can use the &#64;Embed directive,
     *  as the following example shows:
     *  <pre>
     *  source="&#64;Embed('&lt;i&gt;image_location&lt;/i&gt;')"
     *  </pre>
     *  </p>
     *
     *  <p>The image location can be specified via a URL, URLRequest, or file
     *  reference. If it is a file reference, its location is relative to the
     *  location of the file that is being compiled.</p>
     *
     *  <p>The BitmapImage class is designed to work with embedded images or
     *  images that are loaded at run time.</p>
     *
     *  <p>If the source is a Bitmap or BitmapData instance or is an external
     *  image file, it is the responsibility of the caller to dispose of the
     *  source once it is no longer needed. If ImageLoader created the BitmapData
     *  instance, then it will dispose of the BitmapData once the source has
     *  changed.</p>
     *
     *  @see flash.display.Bitmap
     *  @see flash.display.BitmapData
     *  @see mx.graphics.BitmapFill
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    public function get source():Object
    {
        return _source;
    }

    /**
     *  @private
     */
    public function set source(value:Object):void
    {
        if (value != _source)
        {
            // Remove listeners from any previous load instance and clear
            // our reference to any existing load-event dispatcher. This ensures
            // for example that we do not receive further load related events
            // our previous content before we can consider the new source.
            clearLoadingContent();

            // Remove any event listeners from previous source.
            removeAddedToStageHandler(_source);

            _source = value;
            sourceInvalid = true;
            loadFailed = false;
            invalidateProperties();
            dispatchEvent(new Event("sourceChanged"));
        }
    }

    //----------------------------------
    //  sourceHeight
    //----------------------------------

    /**
     *  Provides the unscaled height of the original image data.
     *
     *  @default NaN
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4.5
     */
    public function get sourceHeight():Number
    {
        return imageHeight;
    }

    //----------------------------------
    //  sourceWidth
    //----------------------------------

    /**
     *  Provides the unscaled width of the original image data.
     *
     *  @default NaN
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4.5
     */
    public function get sourceWidth():Number
    {
        return imageWidth;
    }

    //----------------------------------
    //  trustedSource
    //----------------------------------

    private var _trustedSource:Boolean = true;

    /**
     *  A read-only flag denoting whether the currently loaded
     *  content is considered loaded form a source whose security
     *  policy allows for cross domain image access.
     *  When <code>false</code>, advanced bitmap operations such as high quality scaling,
     *  and tiling are not permitted.  This flag is set once an
     *  image has been fully loaded.
     *
     *  @default true
     *
     *  @langversion 3.0
     *  @playerversion Flash 9
     *  @playerversion AIR 1.5
     *  @productversion Flex 4.5
     */
    public function get trustedSource():Boolean
    {
        return _trustedSource;
    }

    //----------------------------------
    //  verticalAlign
    //----------------------------------

    /**
     *  @private
     */
    private var _verticalAlign:String = VerticalAlign.MIDDLE;

    [Inspectable(category="General", enumeration="top,bottom,middle", defaultValue="middle")]

    /**
     *  The vertical alignment of the content when it does not have
     *  a one-to-one aspect ratio and scaleMode is set to
     *  <code>mx.graphics.BitmapScaleMode.LETTERBOX</code>.
     *
     *  <p>Can be one of <code>VerticalAlign.TOP</code> ("top"),
     *  <code>VerticalAlign.BOTTOM</code> ("bottom"), or
     *  <code>VerticalAlign.MIDDLE</code> ("middle").</p>
     *
     *  <p>This property is only applicable when scaleMode is set to
     *  to <code>BitmapFillMode.SCALE</code> ("scale").</p>
     *
     *  @default <code>VerticalAlign.MIDDLE</code>
     *
     *  @see mx.graphics.BitmapFillMode
     *  @see mx.graphics.BitmapScaleMode
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4.5
     */
    public function get verticalAlign():String
    {
        return _verticalAlign;
    }

    /**
     *  @private
     */
    public function set verticalAlign(value:String):void
    {
        if (value == _verticalAlign)
            return;

        _verticalAlign = value;
        invalidateDisplayList();
    }

    /**
     *  @private
     */
    private function getVerticalAlignValue():Number
    {
        if (_verticalAlign == VerticalAlign.TOP)
            return 0;
        else if (_verticalAlign == VerticalAlign.BOTTOM)
            return 1;
        else
            return 0.5;
    }

    //--------------------------------------------------------------------------
    //
    //  GraphicElement Overrides
    //
    //--------------------------------------------------------------------------

    /**
     *  @inheritDoc
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    override protected function commitProperties():void
    {
        validateSource();
        super.commitProperties();
    }

    /**
     *  @inheritDoc
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    override protected function measure():void
    {
        var dpiScale:Number = 1;
        var app:Object = FlexGlobals.topLevelApplication;
        if ("applicationDPI" in app && "runtimeDPI" in app && source is MultiDPIBitmapSource)
            dpiScale = app.runtimeDPI / app.applicationDPI;

        if (loadedContent)
        {
            // Return size of our loaded image content.
            measuredWidth = imageWidth;
            measuredHeight = imageHeight;
            if (dpiScale != 1) // density scaling may be in effect
            {
                measuredWidth /= dpiScale;
                measuredHeight /= dpiScale;
            }
        }
        else if (_bitmapData)
        {
            // Return size of our bitmap data.
            measuredWidth = _bitmapData.width;
            measuredHeight = _bitmapData.height;
            if (dpiScale != 1) // density scaling may be in effect
            {
                measuredWidth /= dpiScale;
                measuredHeight /= dpiScale;
            }
        }
        else
        {
            // If we are loading new content we keep the old measured width/height to avoid
            // sizing to 0,0 for a frame unnecessarily. Otherwise we fall back to 0 unless
            // a preliminaryWidth/Height is set.
            var usePreviousSize:Boolean = !(_source == null || _source == "" || loadFailed);
            var previousWidth:Number = usePreviousSize ? measuredWidth : 0;
            var previousHeight:Number = usePreviousSize ? measuredHeight : 0;

            measuredWidth = !isNaN(_preliminaryWidth) && (previousWidth == 0) ?
                _preliminaryWidth : previousWidth;
            measuredHeight = !isNaN(_preliminaryHeight) && (previousHeight == 0) ?
                _preliminaryHeight : previousHeight;

            return;
        }

        // Consider aspectRatio
        if (maintainAspectRatio && measuredWidth > 0 && measuredHeight > 0)
        {
            if(!isNaN(explicitWidth) && isNaN(explicitHeight) &&
                isNaN(percentHeight))
            {
                measuredHeight = explicitWidth/measuredWidth * measuredHeight;
            }
            else if (!isNaN(explicitHeight) && isNaN(explicitWidth) &&
                isNaN(percentWidth))
            {
                measuredWidth = explicitHeight/measuredHeight * measuredWidth;
            }
            else if (!isNaN(percentWidth) && isNaN(explicitHeight) &&
                isNaN(percentHeight) && width > 0)
            {
                measuredHeight = width/measuredWidth * measuredHeight;
            }
            else if (!isNaN(percentHeight) && isNaN(explicitWidth) &&
                isNaN(percentWidth) && height > 0)
            {
                measuredWidth = height/measuredHeight * measuredWidth;
            }
        }
    }

    /**
     *  @inheritDoc
     *
     *  @langversion 3.0
     *  @playerversion Flash 10
     *  @playerversion AIR 1.5
     *  @productversion Flex 4
     */
    override protected function updateDisplayList(unscaledWidth:Number,
                                                  unscaledHeight:Number):void
    {
        super.updateDisplayList(unscaledWidth, unscaledHeight);

        var adjustedHeight:Number = unscaledHeight;
        var adjustedWidth:Number = unscaledWidth;
        var isZoom:Boolean = (_fillMode == BitmapFillMode.SCALE) && 
                             (_scaleMode == BitmapScaleMode.ZOOM);

        // Consider aspectRatio
        var aspectRatio:Number = unscaledWidth/unscaledHeight;
        var imageAspectRatio:Number = imageWidth/imageHeight;
        if (maintainAspectRatio)
        {
            if (!isNaN(imageAspectRatio))
            {
                if (imageAspectRatio > aspectRatio)
                    adjustedHeight = unscaledWidth / imageAspectRatio;
                else
                    adjustedWidth = unscaledHeight * imageAspectRatio;
                
                if ((!isNaN(percentWidth) && isNaN(percentHeight) && isNaN(explicitHeight)) ||
                    (!isNaN(percentHeight) && isNaN(percentWidth) && isNaN(explicitWidth)))
                {
                    if (aspectRatio != imageAspectRatio)
                    {
                        invalidateSize();
                        return;
                    }
                }
            }
        }

        if (!_bitmapData || !drawnDisplayObject || !(drawnDisplayObject is Sprite))
        {
            if (loadedContent)
            {
                // We are hosting a display object so let's scale, align, and clip
                // as necessary.

                if (_fillMode == BitmapFillMode.SCALE)
                {
                    loadedContent.width = adjustedWidth;
                    loadedContent.height = adjustedHeight;
                }

                loadedContent.y = loadedContent.x = 0;

                // Align our loaded content as necessary if our width
                // and/or height is larger than our content height.
                if (maintainAspectRatio || _fillMode == BitmapFillMode.CLIP || isZoom)
                {
                    var contentWidth:Number = (_fillMode == BitmapFillMode.CLIP || isZoom) ?
                        imageWidth : adjustedWidth;
                    var contentHeight:Number = (_fillMode == BitmapFillMode.CLIP || isZoom) ?
                        imageHeight : adjustedHeight;

                    if (unscaledHeight > contentHeight)
                        loadedContent.y = Math.floor((unscaledHeight - contentHeight)
                            * getVerticalAlignValue());

                    if (unscaledWidth > contentWidth)
                        loadedContent.x = Math.floor((unscaledWidth - contentWidth)
                            * getHorizontalAlignValue());
                }

                // Setup clip rect if appropriate.
                loadedContent.scrollRect = (_fillMode == BitmapFillMode.CLIP || isZoom) ?
                    new Rectangle(0, 0, unscaledWidth, unscaledHeight) : null;

            }
            return;
        }

        // The base GraphicElement class has cleared the graphics for us.
        var g:Graphics = Sprite(drawnDisplayObject).graphics;

        g.lineStyle();
        var repeatBitmap:Boolean = false;
        var fillScaleX:Number = 1;
        var fillScaleY:Number = 1;
        var roundedDrawX:Number = Math.round(drawX);
        var roundedDrawY:Number = Math.round(drawY);
        var fillWidth:Number = adjustedWidth;
        var fillHeight:Number = adjustedHeight;
        
        if (_bitmapData)
        {
            switch(_fillMode)
            {
                case BitmapFillMode.REPEAT:
                {
                    repeatBitmap = true;
                    break;
                }
                case BitmapFillMode.SCALE:
                {
                    if (isZoom)
                    {
                        var widthRatio:Number = adjustedWidth / _bitmapData.width;
                        var heightRatio:Number = adjustedHeight / _bitmapData.height;
                        
                        if (widthRatio < heightRatio)
                            fillScaleX = fillScaleY = (adjustedHeight / _bitmapData.height);
                        else
                            fillScaleX = fillScaleY = (adjustedWidth / _bitmapData.width);
                    }
                    else
                    {
                        fillScaleX = adjustedWidth / _bitmapData.width;
                        fillScaleY = adjustedHeight / _bitmapData.height;
                    }
                    break;
                }
                case BitmapFillMode.CLIP:
                {
                    fillWidth = Math.min(adjustedWidth, _bitmapData.width);
                    fillHeight = Math.min(adjustedHeight, _bitmapData.height);
                    break;
                }
            }
        }

        // If no scaleGrid is defined or if fillMode != SCALE, just draw
        // the entire rect
        if (_fillMode != BitmapFillMode.SCALE ||
            isNaN(_scaleGridTop) ||
            isNaN(_scaleGridBottom) ||
            isNaN(_scaleGridLeft) ||
            isNaN(_scaleGridRight))
        {
            var sampledScale:Boolean = _smooth &&
                (_smoothingQuality == BitmapSmoothingQuality.HIGH) &&
                (_fillMode == BitmapFillMode.SCALE);
            var sampleWidth:Number = fillWidth;
            var sampleHeight:Number = fillHeight;
            
            if (isZoom)
            {
                sampleWidth = _bitmapData.width * fillScaleX;
                sampleHeight = _bitmapData.height * fillScaleY;
            }

            var b:BitmapData = sampledScale ? resample(_bitmapData, sampleWidth, sampleHeight) : _bitmapData;

            if (sampledScale && (_fillMode == BitmapFillMode.SCALE))
            {
                if (isZoom)
                {
                    fillScaleX = fillScaleY = 1;
                }
                else if (_fillMode == BitmapFillMode.SCALE)
                {
                    fillScaleX = adjustedWidth / b.width;
                    fillScaleY = adjustedHeight / b.height;
                }
            }
            
            var cHeight:Number = b.height * fillScaleX;
            var cWidth:Number = b.width * fillScaleY;

            // Align our bitmap content as necessary if our width
            // and/or height is larger than our content height.
            if (maintainAspectRatio || _fillMode == BitmapFillMode.CLIP || isZoom)
            {
                if (unscaledHeight > cHeight)
                    roundedDrawY = roundedDrawY + Math.floor((unscaledHeight - cHeight)
                        * getVerticalAlignValue());

                if (unscaledWidth > cWidth)
                    roundedDrawX = roundedDrawX + Math.floor((unscaledWidth - cWidth)
                        * getHorizontalAlignValue());
            }
            
            var translateX:Number = roundedDrawX;
            var translateY:Number = roundedDrawY;
            
            if (isZoom)
            {
                if (cWidth > unscaledWidth)
                    translateX = translateX + ((unscaledWidth - cWidth) * getHorizontalAlignValue());
                else if (cHeight > unscaledHeight)
                    translateY = translateY + ((unscaledHeight - cHeight) * getVerticalAlignValue());
            }

            matrix.identity();
            if (!(sampledScale && (maintainAspectRatio || isZoom))) 
                matrix.scale(fillScaleX, fillScaleY);
            matrix.translate(translateX, translateY);
            g.beginBitmapFill(b, matrix, repeatBitmap, _smooth);
            g.drawRect(roundedDrawX, roundedDrawY, fillWidth, fillHeight);
            g.endFill();
        }
        else
        {
            // If we have scaleGrid, we draw 9 sections, each with a different scale factor based
            // on the grid region.

            if (cachedSourceGrid == null)
            {
                // Generate the 16 points of the source (unscaled) grid
                cachedSourceGrid = [];
                cachedSourceGrid.push([new Point(0, 0), new Point(_scaleGridLeft, 0),
                    new Point(_scaleGridRight, 0), new Point(_bitmapData.width, 0)]);
                cachedSourceGrid.push([new Point(0, _scaleGridTop), new Point(_scaleGridLeft, _scaleGridTop),
                    new Point(_scaleGridRight, _scaleGridTop), new Point(_bitmapData.width, _scaleGridTop)]);
                cachedSourceGrid.push([new Point(0, _scaleGridBottom), new Point(_scaleGridLeft, _scaleGridBottom),
                    new Point(_scaleGridRight, _scaleGridBottom), new Point(_bitmapData.width, _scaleGridBottom)]);
                cachedSourceGrid.push([new Point(0, _bitmapData.height), new Point(_scaleGridLeft, _bitmapData.height),
                    new Point(_scaleGridRight, _bitmapData.height), new Point(_bitmapData.width, _bitmapData.height)]);
            }

            if (cachedDestGrid == null ||
                previousUnscaledWidth != unscaledWidth ||
                previousUnscaledHeight != unscaledHeight)
            {
                // Generate teh 16 points of the destination (scaled) grid
                var destScaleGridBottom:Number = unscaledHeight - (_bitmapData.height - _scaleGridBottom);
                var destScaleGridRight:Number = unscaledWidth - (_bitmapData.width - _scaleGridRight);
                cachedDestGrid = [];
                cachedDestGrid.push([new Point(0, 0), new Point(_scaleGridLeft, 0),
                    new Point(destScaleGridRight, 0), new Point(unscaledWidth, 0)]);
                cachedDestGrid.push([new Point(0, _scaleGridTop), new Point(_scaleGridLeft, _scaleGridTop),
                    new Point(destScaleGridRight, _scaleGridTop), new Point(unscaledWidth, _scaleGridTop)]);
                cachedDestGrid.push([new Point(0, destScaleGridBottom), new Point(_scaleGridLeft, destScaleGridBottom),
                    new Point(destScaleGridRight, destScaleGridBottom), new Point(unscaledWidth, destScaleGridBottom)]);
                cachedDestGrid.push([new Point(0, unscaledHeight), new Point(_scaleGridLeft, unscaledHeight),
                    new Point(destScaleGridRight, unscaledHeight), new Point(unscaledWidth, unscaledHeight)]);
            }

            var sourceSection:Rectangle = new Rectangle();
            var destSection:Rectangle = new Rectangle();

            // Iterate over the columns and rows. We draw each of the nine sections at a calculated
            // scale and translation.
            for (var rowIndex:int=0; rowIndex < 3; rowIndex++)
            {
                for (var colIndex:int = 0; colIndex < 3; colIndex++)
                {
                    // Create the source and destination rectangles for the current section
                    sourceSection.topLeft = cachedSourceGrid[rowIndex][colIndex];
                    sourceSection.bottomRight = cachedSourceGrid[rowIndex+1][colIndex+1];

                    destSection.topLeft = cachedDestGrid[rowIndex][colIndex];
                    destSection.bottomRight = cachedDestGrid[rowIndex+1][colIndex+1];

                    matrix.identity();
                    // Scale the bitmap by the ratio between the source and destination dimensions
                    matrix.scale(destSection.width / sourceSection.width, destSection.height / sourceSection.height);
                    // Translate based on the difference between the source and destination coordinates,
                    // making sure to account for the new scale.
                    matrix.translate(destSection.x - sourceSection.x * matrix.a, destSection.y - sourceSection.y * matrix.d);
                    matrix.translate(roundedDrawX, roundedDrawY);

                    // Draw the bitmap for the current section
                    g.beginBitmapFill(_bitmapData, matrix);
                    g.drawRect(destSection.x + roundedDrawX, destSection.y + roundedDrawY, destSection.width, destSection.height);
                    g.endFill();
                }
            }
        }

        previousUnscaledWidth = unscaledWidth;
        previousUnscaledHeight = unscaledHeight;
    }

    /**
     *  @private
     */
    override protected function get needsDisplayObject():Boolean
    {
        // If we are hosting untrusted content the hosted loader becomes
        // our display object.
        return !trustedSource || super.needsDisplayObject;
    }

    //--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     *  Utility function that sets the underlying bitmapData property.
     */
    protected function setBitmapData(bitmapData:BitmapData,
                                     internallyCreated:Boolean = false):void
    {
        // Clear previous bitmapData
        clearBitmapData();

        // Reset imageWidth/Height, as we aren't
        // loading external content so no reason to
        // cache our existing image width and height.
        imageWidth = imageHeight = NaN;

        // Clear any currently loading content.
        clearLoadingContent();

        if (bitmapData)
        {
            bitmapDataCreated = internallyCreated;

            _bitmapData = bitmapData;

            imageWidth = bitmapData.width;
            imageHeight = bitmapData.height;

            // Flush the cached scale grid points
            cachedSourceGrid = null;
            cachedDestGrid = null;

            // Dispatch ready event
            dispatchEvent(new FlexEvent(FlexEvent.READY));
        }

        if (!explicitHeight || !explicitWidth)
            invalidateSize();
        invalidateDisplayList();
    }

    /**
     *  @private
     *  Utility method which analyzes our source and either acquires the
     *  associated bitmap data immediately or queues up a remote request
     *  as necessary.
     */
    mx_internal function applySource():void
    {
        var value:Object = _source;
        var bitmapData:BitmapData;
        var tmpSprite:DisplayObject;

        if (value is MultiDPIBitmapSource)
            value = getActualValue(value as MultiDPIBitmapSource);

        // Clear the previous scaleGrid properties
        _scaleGridLeft = NaN;
        _scaleGridRight = NaN;
        _scaleGridTop = NaN;
        _scaleGridBottom = NaN;
        var currentBitmapCreated:Boolean = false;

        // Reset byte counts and _trustedSource
        _bytesLoaded = NaN;
        _bytesTotal = NaN;
        _trustedSource = true;

        // We'll need to reconsider display object sharing.
        invalidateDisplayObjectSharing();
        invalidateDisplayList();

        // Remove any previously hosted content prior to loading
        // new content (if applicable).
        if (_clearOnLoad)
            removePreviousContent();

        if (value is Class)
        {
            var cls:Class = Class(value);
            value = new cls();
            currentBitmapCreated = true;
        }
        else if (value is String || value is URLRequest)
        {
            loadExternal(value);
        }
        else if (value is ByteArray)
        {
            loadFromBytes(value as ByteArray);
        }

        if (value is BitmapData)
        {
            bitmapData = value as BitmapData;
        }
        else if (value is Bitmap)
        {
            bitmapData = value.bitmapData;
        }
        else if (value is DisplayObject)
        {
            tmpSprite = value as DisplayObject;

            if ((tmpSprite.width == 0 || tmpSprite.height == 0) && !tmpSprite.stage )
            {
                // If our source DisplayObject has yet to be assigned a stage,
                // and doesn't have valid bounds, it is not ready to be captured,
                // so we defer bitmap capture until it is added to the display list.
                tmpSprite.addEventListener(Event.ADDED_TO_STAGE, source_addedToStageHandler);
                return;
            }
        }
        else if (value == null)
        {
            // This will set source to null
        }
        else
        {
            // We're loading external content or we
            // we have an unsupported source value.
            return;
        }

        if (!bitmapData && tmpSprite)
        {
            // We must ensure any IInvalidating sources
            // are properly validated before capturing.
            if (tmpSprite is IInvalidating)
                IInvalidating(tmpSprite).validateNow();

            // Return immediately if our input source has 0 bounds else
            // our BitmapData constructor will RTE.
            if (tmpSprite.width == 0 || tmpSprite.height == 0)
                return;

            bitmapData = new BitmapData(tmpSprite.width, tmpSprite.height, true, 0);
            bitmapData.draw(tmpSprite, new Matrix(), tmpSprite.transform.colorTransform);
            currentBitmapCreated = true;

            if (tmpSprite.scale9Grid)
            {
                _scaleGridLeft = tmpSprite.scale9Grid.left;
                _scaleGridRight = tmpSprite.scale9Grid.right;
                _scaleGridTop = tmpSprite.scale9Grid.top;
                _scaleGridBottom = tmpSprite.scale9Grid.bottom;
            }
        }

        // Remove any previously hosted content prior to assigning
        // new bitmap data (if we haven't already previously).
        if (!_clearOnLoad)
            removePreviousContent();

        setBitmapData(bitmapData, currentBitmapCreated);
    }

    /**
     *  @private
     *  Figure out which source to use
     */
    mx_internal function getActualValue(values:MultiDPIBitmapSource):Object
    {
        var app:Object = FlexGlobals.topLevelApplication;
        var dpi:Number;
        if ("runtimeDPI" in app)
            dpi = app["runtimeDPI"];
        else
            dpi = DensityUtil.getRuntimeDPI();
        return values.getSource(dpi);
    }

    /**
     *  @private
     */
    mx_internal function loadExternal(source:Object):void
    {
        // Ensure we handle unicode characters properly.
        if (source is String)
        {
           var url:String = source as String;
           source = LoaderUtil.OSToPlayerURI(url, LoaderUtil.isLocal(url));
        }

        if (contentLoader)
        {
            // We defer our load request to the configured content loader.
            var contentRequest:ContentRequest = contentLoader.load(source, contentLoaderGrouping);

            if (contentRequest.complete)
            {
                // No need to attach listeners as we've received a fully complete
                // request result.
                contentComplete(contentRequest.content);
            }
            else
            {
                // Attach load-event listeners to our ContentRequest instance.
                loadingContent = contentRequest;
                attachLoadingListeners();
            }
        }
        else
        {
            var loader:Loader = new Loader();
            var loaderContext:LoaderContext = new LoaderContext();

            // Attach load-event listeners to our LoaderInfo instance.
            loadingContent = loader.contentLoaderInfo;
            attachLoadingListeners();

            try
            {
                loaderContext.checkPolicyFile = true;
                var urlRequest:URLRequest = source is URLRequest ?
                    source as URLRequest : new URLRequest(source as String);
                loader.load(urlRequest, loaderContext);
            }
            catch (error:SecurityError)
            {
                handleSecurityError(error);
            }
        }
    }

    /**
     *  @private
     */
    mx_internal function loadFromBytes(source:ByteArray):void
    {
        var loader:Loader = new Loader();
        var loaderContext:LoaderContext = new LoaderContext();
        loadingContent = loader.contentLoaderInfo;
        attachLoadingListeners();

        try
        {
            loader.loadBytes(source as ByteArray, loaderContext);
        }
        catch (error:SecurityError)
        {
            handleSecurityError(error);
        }
    }

    /**
     * @private
     * Utility function used for higher quality image scaling. Essentially we
     * simply step down our bitmap size by half resulting in a much higher result
     * though taking potentially multiple passes to accomplish.
     */
    protected static function resample(bitmapData:BitmapData, newWidth:uint,
                                       newHeight:uint):BitmapData
    {
        var finalScale:Number = Math.max(newWidth/bitmapData.width,
            newHeight/bitmapData.height);

        var finalData:BitmapData = bitmapData;

        if (finalScale > 1)
        {
            finalData = new BitmapData(bitmapData.width * finalScale,
                bitmapData.height * finalScale, true, 0);

            finalData.draw(bitmapData, new Matrix(finalScale, 0, 0,
                finalScale), null, null, null, true);

            return finalData;
        }

        var drop:Number = .5;
        var initialScale:Number = finalScale;

        while (initialScale/drop < 1)
            initialScale /= drop;

        var w:Number = Math.floor(bitmapData.width * initialScale);
        var h:Number = Math.floor(bitmapData.height * initialScale);
        var bd:BitmapData = new BitmapData(w, h, bitmapData.transparent, 0);

        bd.draw(finalData, new Matrix(initialScale, 0, 0, initialScale),
            null, null, null, true);
        finalData = bd;

        for (var scale:Number = initialScale * drop;
            Math.round(scale * 1000) >= Math.round(finalScale * 1000);
            scale *= drop)
        {
            w = Math.floor(bitmapData.width * scale);
            h = Math.floor(bitmapData.height * scale);
            bd = new BitmapData(w, h, bitmapData.transparent, 0);

            bd.draw(finalData, new Matrix(drop, 0, 0, drop), null, null, null, true);
            finalData.dispose();
            finalData = bd;
        }

        return finalData;
    }

    /**
     * @private
     * Invoked upon completion of a load request.
     */
    protected function contentComplete(content:Object):void
    {
        if (content is LoaderInfo)
        {
            // Clear any previous bitmap data or loader instance.
            setBitmapData(null);
            removePreviousContent();

            var loaderInfo:LoaderInfo = content as LoaderInfo;

            if (loaderInfo.childAllowsParent)
            {
                // For trusted content we adopt the loaded BitmapData.
                var image:Bitmap = Bitmap(loaderInfo.content);
                setBitmapData(image.bitmapData);
            }
            else
            {
                // For untrusted content we must host the acquired Loader
                // instance directly as our DisplayObject.
                displayObjectSharingMode = DisplayObjectSharingMode.OWNS_UNSHARED_OBJECT;
                invalidateDisplayObjectSharing();

                // Create a content holder to use as our display object.
                var contentHolder:Sprite = new Sprite();
                setDisplayObject(contentHolder);
                loadedContent = loaderInfo.loader;
                contentHolder.addChild(loadedContent);

                // Retain our source image width and height.
                imageWidth = loaderInfo.width;
                imageHeight = loaderInfo.height ;

                // Update
                if (!explicitHeight || !explicitWidth)
                    invalidateSize();
                invalidateDisplayList();

                // Denote that we are hosting an untrusted image and as such some
                // features requiring access to the bitmap data will no longer
                // function.
                _trustedSource = false;

                // Dispatch ready event
                dispatchEvent(new FlexEvent(FlexEvent.READY));
            }
        }
        else
        {
            if (content is BitmapData)
                setBitmapData(content as BitmapData);
        }
    }

    /**
     * @private
     * Returns true if are to consider aspect ratio while scaling.
     */
    private function get maintainAspectRatio():Boolean
    {
        return (_scaleMode == BitmapScaleMode.LETTERBOX && _fillMode == BitmapFillMode.SCALE);
    }

    /**
     * @private
     * Removes any previously loaded content prior to loading new.
     */
    private function removePreviousContent():void
    {
        if (loadedContent && loadedContent.parent)
        {
            displayObjectSharingMode = DisplayObjectSharingMode.USES_SHARED_OBJECT;
            invalidateDisplayObjectSharing();
            loadedContent.parent.removeChild(loadedContent);
            loadedContent = null;
            setDisplayObject(null);
            imageWidth = imageHeight = NaN;
        }
        else if (drawnDisplayObject)
        {
            Sprite(drawnDisplayObject).graphics.clear();
            clearBitmapData();
        }
    }

    /**
     *  @private
     */
    private function clearLoadingContent():void
    {
        if (loadingContent is LoaderInfo && LoaderInfo(loadingContent).loader)
        {
            try
            {
                LoaderInfo(loadingContent).loader.close();
            }
            catch (e:Error)
            {
                // Ignore
            }
        }

        removeLoadingListeners();
        loadingContent = null;
    }

    /**
     *  @private
     */
    private function clearBitmapData():void
    {
        if (_bitmapData)
        {
            // Dispose the bitmap if we created it
            if (bitmapDataCreated)
                _bitmapData.dispose();
            _bitmapData = null;
        }
    }

    /**
     *  @private
     */
    private function attachLoadingListeners():void
    {
        if (loadingContent)
        {
            loadingContent.addEventListener(Event.COMPLETE,
                loader_completeHandler, false, 0, true);
            loadingContent.addEventListener(IOErrorEvent.IO_ERROR,
                loader_ioErrorHandler, false, 0, true);
            loadingContent.addEventListener(ProgressEvent.PROGRESS,
                loader_progressHandler, false, 0, true);
            loadingContent.addEventListener(SecurityErrorEvent.SECURITY_ERROR,
                loader_securityErrorHandler, false, 0, true);
            loadingContent.addEventListener(HTTPStatusEvent.HTTP_STATUS,
                dispatchEvent, false, 0, true);
        }
    }

    /**
     *  @private
     */
    private function removeLoadingListeners():void
    {
        if (loadingContent)
        {
            loadingContent.removeEventListener(Event.COMPLETE,
                loader_completeHandler);
            loadingContent.removeEventListener(IOErrorEvent.IO_ERROR,
                loader_ioErrorHandler);
            loadingContent.removeEventListener(ProgressEvent.PROGRESS,
                loader_progressHandler);
            loadingContent.removeEventListener(SecurityErrorEvent.SECURITY_ERROR,
                loader_securityErrorHandler);
            loadingContent.removeEventListener(HTTPStatusEvent.HTTP_STATUS,
                dispatchEvent);
        }
    }


    /**
     *  @private
     *  Utility method which is invoked to initiate loading of our
     *  source.
     */
    mx_internal function validateSource():void
    {
        if (sourceInvalid || contentLoaderInvalid)
        {
            applySource();
            sourceInvalid = false;
            contentLoaderInvalid = false;
        }
    }

    //--------------------------------------------------------------------------
    //
    //  Event handlers
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    mx_internal function loader_completeHandler(event:Event):void
    {
        try
        {
            var loaderInfo:LoaderInfo = (event.target is ContentRequest) ?
                event.target.content as LoaderInfo : event.target as LoaderInfo;

            if (loaderInfo.bytesLoaded)
            {
                _bytesLoaded = _bytesTotal;
                contentComplete(loaderInfo);
            }
        }
        catch (error:SecurityError)
        {
            handleSecurityError(error);
        }

        dispatchEvent(event);

        // Remove any event listeners from load-event dispatcher.
        clearLoadingContent();
    }

    /**
     *  @private
     */
    private function loader_ioErrorHandler(error:IOErrorEvent):void
    {
        // forward the event, only in the case of a listener, else
        // an RTE will occur.
        if (hasEventListener(error.type))
            dispatchEvent(error);

        // clear any current image and remove any event listeners from
        // load-event  dispatcher.
        setBitmapData(null);
        loadFailed = true;
    }

    /**
     *  @private
     */
    private function loader_securityErrorHandler(error:SecurityErrorEvent):void
    {
        dispatchEvent(error);

        // clear any current image and remove any event listeners from
        // load-event  dispatcher.
        setBitmapData(null);
        loadFailed = true;
    }

    /**
     *  @private
     */
    private function loader_progressHandler(progressEvent:ProgressEvent):void
    {
        _bytesLoaded = progressEvent.bytesLoaded;
        _bytesTotal = progressEvent.bytesTotal;

        // forward the event
        dispatchEvent(progressEvent);
    }

    /**
     *  @private
     */
    private function handleSecurityError(error:SecurityError):void
    {
        dispatchEvent(new SecurityErrorEvent(SecurityErrorEvent.SECURITY_ERROR,
            false, false, error.message));

        // clear any current image and remove any event listeners from
        // load-event  dispatcher.
        setBitmapData(null);
        loadFailed = true;
    }

    /**
     *  @private
     *  Used for deferral of bitmap capture when an assigned source
     *  has yet to be added to the stage.
     */
    private function source_addedToStageHandler(event:Event):void
    {
        removeAddedToStageHandler(event.target);
        applySource();
    }

    /**
     *  @private
     */
    private function removeAddedToStageHandler(target:Object):void
    {
        if (target && target is DisplayObject)
            target.removeEventListener(Event.ADDED_TO_STAGE, source_addedToStageHandler);
    }
}
}
