blob: d02e5c1aa295094abb64e09ecd9c8984924cf8ff [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.IBitmapDrawable;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.display.Stage;
import flash.geom.ColorTransform;
import flash.geom.Matrix;
import flash.geom.Rectangle;
import flash.system.Capabilities;
import flash.utils.ByteArray;
import flash.utils.getDefinitionByName;
import mx.core.IFlexDisplayObject;
import mx.core.IUIComponent;
import mx.core.UIComponent;
import mx.graphics.codec.IImageEncoder;
import mx.graphics.codec.PNGEncoder;
import mx.utils.Base64Encoder;
[RemoteClass(alias="flex.graphics.ImageSnapshot")]
/**
* A helper class used to capture a snapshot of any Flash component
* that implements <code>flash.display.IBitmapDrawable</code>,
* including Flex UIComponents.
*
* <p>An instance of this class can be sent as a RemoteObject
* to Adobe's LiveCycle Data Services to generate
* a PDF file of a client-side image.
* If you need to specify additional properties of the image
* beyond its <code>contentType</code>, <code>width</code>,
* and <code>height</code> properties, you should set name/value pairs
* on the <code>properties</code> object.</p>
*
* <p>In earlier versions of Flex, you set these additional
* properties on the ImageSnapshot instance itself.
* This class is still dynamic in order to allow that,
* but in a future version of Flex it might no longer be dynamic.</p>
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public dynamic class ImageSnapshot
{
include "../core/Version.as";
//--------------------------------------------------------------------------
//
// Class constants
//
//--------------------------------------------------------------------------
/**
* The maximum width and height of a Bitmap.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public static const MAX_BITMAP_DIMENSION:int = 2880;
//--------------------------------------------------------------------------
//
// Class variables
//
//--------------------------------------------------------------------------
/**
* The default <code>mx.graphics.codec.IImageEncoder</code> implementation
* used to capture images. The two implementations are PNGEncoder and
* JPEGEncoder. The default encoder uses the PNG format.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public static var defaultEncoder:Class = PNGEncoder;
//--------------------------------------------------------------------------
//
// Class methods
//
//--------------------------------------------------------------------------
/**
* A utility method to grab a raw snapshot of a UI component as BitmapData.
*
* @param source An object that implements the
* <code>flash.display.IBitmapDrawable</code> interface.
*
* @param matrix A Matrix object used to scale, rotate, or translate
* the coordinates of the captured bitmap.
* If you do not want to apply a matrix transformation to the image,
* set this parameter to an identity matrix,
* created with the default new Matrix() constructor, or pass a null value.
*
* @param colorTransform A ColorTransform
* object that you use to adjust the color values of the bitmap. If no object
* is supplied, the bitmap image's colors are not transformed. If you must pass
* this parameter but you do not want to transform the image, set this parameter
* to a ColorTransform object created with the default new ColorTransform() constructor.
*
* @param blendMode A string value, from the flash.display.BlendMode
* class, specifying the blend mode to be applied to the resulting bitmap.
*
* @param clipRect A Rectangle object that defines the
* area of the source object to draw. If you do not supply this value, no clipping
* occurs and the entire source object is drawn.
*
* @param smoothing A Boolean value that determines whether a
* BitmapData object is smoothed when scaled.
*
* @return A BitmapData object representing the captured snapshot or null if
* the source has no visible bounds.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public static function captureBitmapData(
source:IBitmapDrawable, matrix:Matrix = null,
colorTransform:ColorTransform = null,
blendMode:String = null,
clipRect:Rectangle = null,
smoothing:Boolean = false):BitmapData
{
var data:BitmapData;
var width:int;
var height:int;
var normalState:Array;
if (source is IUIComponent)
normalState = prepareToPrintObject(IUIComponent(source));
try
{
if (source != null)
{
if (source is DisplayObject)
{
width = DisplayObject(source).width;
height = DisplayObject(source).height;
}
else if (source is BitmapData)
{
width = BitmapData(source).width;
height = BitmapData(source).height;
}
else if (source is IFlexDisplayObject)
{
width = IFlexDisplayObject(source).width;
height = IFlexDisplayObject(source).height;
}
}
// We default to an identity matrix
// which will match screen resolution
if (!matrix)
matrix = new Matrix(1, 0, 0, 1);
var scaledWidth:Number = width * matrix.a;
var scaledHeight:Number = height * matrix.d;
var reductionScale:Number = 1;
// Cap width to BitmapData max of 2880 pixels
if (scaledWidth > MAX_BITMAP_DIMENSION)
{
reductionScale = scaledWidth / MAX_BITMAP_DIMENSION;
scaledWidth = MAX_BITMAP_DIMENSION;
scaledHeight = scaledHeight / reductionScale;
matrix.a = scaledWidth / width;
matrix.d = scaledHeight / height;
}
// Cap height to BitmapData max of 2880 pixels
if (scaledHeight > MAX_BITMAP_DIMENSION)
{
reductionScale = scaledHeight / MAX_BITMAP_DIMENSION;
scaledHeight = MAX_BITMAP_DIMENSION;
scaledWidth = scaledWidth / reductionScale;
matrix.a = scaledWidth / width;
matrix.d = scaledHeight / height;
}
// the fill should be transparent: 0xARGB -> 0x00000000
// only explicitly drawn pixels will show up
data = new BitmapData(scaledWidth, scaledHeight, true, 0x00000000);
data.draw(source, matrix, colorTransform,
blendMode, clipRect, smoothing);
}
catch(e:Error)
{
data = null;
}
finally
{
if (source is IUIComponent)
finishPrintObject(IUIComponent(source), normalState);
}
return data;
}
/**
* A utility method to grab a snapshot of a component, scaled to a specific
* resolution (in dpi) and encoded into a specific image format.
*
* @param source An object that implements the
* <code>flash.display.IBitmapDrawable</code> interface.
*
* @param dpi The resolution in dots per inch.
* If a resolution is not provided,
* the current on-screen resolution is used by default.
*
* @param encoder The image format used to encode the raw bitmap. The two
* encoders are PNGEncoder and JPEGEncoder. If an encoder is not provided,
* the default is PNGEncoder.
*
* @param scaleLimited The maximum width or height of a bitmap in Flash
* is 2880 pixels - if scaleLimited is set to true the resolution will be
* reduced proportionately to fit within 2880 pixels, otherwise, if
* scaleLimited is false, smaller snapshot windows will be taken and
* stitched together to capture a larger image.
* The default is true.
*
* @return An ImageSnapshot holding an encoded captured snapshot
* and associated image metadata.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public static function captureImage(
source:IBitmapDrawable, dpi:Number = 0,
encoder:IImageEncoder = null,
scaleLimited:Boolean = true):ImageSnapshot
{
var snapshot:ImageSnapshot;
// Calculate scaling factor based on current screen resolution (dpi)
var screenDPI:Number = Capabilities.screenDPI;
if (dpi <= 0)
dpi = screenDPI;
// Create a transformation matrix to scale image to desired resolution
var scale:Number = dpi / screenDPI;
var matrix:Matrix = new Matrix(scale, 0, 0, scale);
var width:int;
var height:int;
var normalState:Array;
if (source is IUIComponent)
normalState = prepareToPrintObject(IUIComponent(source));
try
{
if (source != null)
{
if (source is DisplayObject)
{
width = DisplayObject(source).width;
height = DisplayObject(source).height;
}
else if (source is BitmapData)
{
width = BitmapData(source).width;
height = BitmapData(source).height;
}
else if (source is IFlexDisplayObject)
{
width = IFlexDisplayObject(source).width;
height = IFlexDisplayObject(source).height;
}
}
// Use an image encoder on raw pixels to reduce size
if (!encoder)
encoder = new defaultEncoder();
var bytes:ByteArray;
width = width * matrix.a;
height = height * matrix.d;
// If scaleLimited, we limit snapshot to a maximum of 2880 x 2880
// pixels irrespective of the requested dpi
if (scaleLimited ||
(width <= MAX_BITMAP_DIMENSION &&
height <= MAX_BITMAP_DIMENSION))
{
var data:BitmapData = captureBitmapData(source, matrix);
var bitmap:Bitmap = new Bitmap(data);
width = bitmap.width;
height = bitmap.height;
bytes = encoder.encode(data);
}
else
{
// We scale to the requested dpi and try to capture the
// entire snapshot as a raw bitmap ByteArray
var bounds:Rectangle = new Rectangle(0, 0, width, height);
bytes = captureAll(source, bounds, matrix);
bytes = encoder.encodeByteArray(bytes, width, height);
}
snapshot = new ImageSnapshot(width, height, bytes,
encoder.contentType);
}
finally
{
if (source is IUIComponent)
finishPrintObject(IUIComponent(source), normalState);
}
return snapshot;
}
/**
* A utility method to convert an ImageSnapshot into a Base-64 encoded
* String for transmission in text based serialization formats such as XML.
*
* @param snapshot An image captured as an
* <code>mx.graphics.ImageSnapshot</code>.
*
* @return A string representing the base64 encoded snapshot.
*
* @see #captureImage
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public static function encodeImageAsBase64(snapshot:ImageSnapshot):String
{
var bytes:ByteArray = snapshot.data;
// Convert to Base64 encoded String
var base64:Base64Encoder = new Base64Encoder();
base64.encodeBytes(bytes);
var base64Image:String = base64.drain();
return base64Image;
}
/**
* @private
* Attempts to capture as much of an image for the requested bounds by
* splitting the scaled source into rectangular windows that fit inside
* the maximum size of a single BitmapData instance,
* i.e. 2880 x 2880 pixels, and stitching the windows together
* into a larger bitmap with the raw pixels returned as a ByteArray.
* This ByteArray is limited to around 256MB so scaled images with an area
* equivalent to about 8192 x 8192 will result in out-of-memory errors.
*/
private static function captureAll(source:IBitmapDrawable, bounds:Rectangle,
matrix:Matrix,
colorTransform:ColorTransform = null,
blendMode:String = null,
clipRect:Rectangle = null,
smoothing:Boolean = false):ByteArray
{
var currentMatrix:Matrix = matrix.clone();
var topLeft:Rectangle = bounds.clone();
var topRight:Rectangle;
var bottomLeft:Rectangle;
var bottomRight:Rectangle;
// Check if the requested bounds exceeds the maximum width for
// a bitmap...
if (bounds.width > MAX_BITMAP_DIMENSION)
{
topLeft.width = MAX_BITMAP_DIMENSION;
topRight = new Rectangle();
topRight.x = topLeft.width;
topRight.y = bounds.y;
topRight.width = bounds.width - topLeft.width;
topRight.height = bounds.height;
}
// Check if the requested bounds exceeds the maximum height for
// a bitmap...
if (bounds.height > MAX_BITMAP_DIMENSION)
{
topLeft.height = MAX_BITMAP_DIMENSION;
if (topRight != null)
topRight.height = topLeft.height;
bottomLeft = new Rectangle();
bottomLeft.x = bounds.x;
bottomLeft.y = topLeft.height;
bottomLeft.width = topLeft.width;
bottomLeft.height = bounds.height - topLeft.height;
if (bounds.width > MAX_BITMAP_DIMENSION)
{
bottomRight = new Rectangle();
bottomRight.x = topLeft.width;
bottomRight.y = topLeft.height;
bottomRight.width = bounds.width - topLeft.width;
bottomRight.height = bounds.height - topLeft.height;
}
}
// Capture top-left window
currentMatrix.translate(-topLeft.x, -topLeft.y);
topLeft.x = 0;
topLeft.y = 0;
// the fill should be transparent: 0xARGB -> 0x00000000
// only explicitly drawn pixels will show up
var data:BitmapData = new BitmapData(topLeft.width, topLeft.height, true, 0x00000000);
data.draw(source, currentMatrix, colorTransform,
blendMode, clipRect, smoothing);
var pixels:ByteArray = data.getPixels(topLeft);
pixels.position = 0;
// If bounds width exceeded maximum dimensions for a bitmap, we
// also need to capture the top-right window (recursively, until we
// have a window width less that the max). These right side rows have
// to be merged to the right of each left side row.
if (topRight != null)
{
currentMatrix = matrix.clone();
currentMatrix.translate(-topRight.x, -topRight.y);
topRight.x = 0;
topRight.y = 0;
var topRightPixels:ByteArray =
captureAll(source, topRight, currentMatrix);
pixels = mergePixelRows(pixels, topLeft.width, topRightPixels,
topRight.width, topRight.height);
}
// If bounds height exceeded the maximum dimension for a bitmap, we
// also need to capture the bottom-left window (recursively, until we
// have a window height less than the max). These rows are appended
// to the end of the current 32-bit, 4 channel bitmap as a ByteArray.
if (bottomLeft != null)
{
currentMatrix = matrix.clone();
currentMatrix.translate(-bottomLeft.x, -bottomLeft.y);
bottomLeft.x = 0;
bottomLeft.y = 0;
var bottomLeftPixels:ByteArray = captureAll(source, bottomLeft,
currentMatrix);
// If both the bounds width and bounds height exceeded the maximum
// dimensions for a bitmap, we now must to capture the bottom-right
// window (recursively, until we have a window with less than the
// max width and/or height). These right side rows have to be merged
// to the right of each left side row.
if (bottomRight != null)
{
currentMatrix = matrix.clone();
currentMatrix.translate(-bottomRight.x, -bottomRight.y);
bottomRight.x = 0;
bottomRight.y = 0;
var bottomRightPixels:ByteArray =
captureAll(source, bottomRight, currentMatrix);
bottomLeftPixels = mergePixelRows(bottomLeftPixels,
bottomLeft.width,
bottomRightPixels,
bottomRight.width,
bottomRight.height);
}
// Append bottomLeft pixels to the end of the ByteArray of pixels
pixels.position = pixels.length;
pixels.writeBytes(bottomLeftPixels);
}
pixels.position = 0;
return pixels;
}
/**
* @private
* Copies the rows of the right hand side of an image onto the ends of
* the rows of the left hand side of an image. The left and right hand
* sides must be of equal height.
*/
private static function mergePixelRows(left:ByteArray, leftWidth:int,
right:ByteArray, rightWidth:int,
rightHeight:int):ByteArray
{
var merged:ByteArray = new ByteArray();
var leftByteWidth:int = leftWidth * 4;
var rightByteWidth:int = rightWidth * 4;
for (var i:int = 0; i < rightHeight; i++)
{
merged.writeBytes(left, i * leftByteWidth, leftByteWidth);
merged.writeBytes(right, i * rightByteWidth, rightByteWidth);
}
merged.position = 0;
return merged;
}
/**
* @private
* Prepare the target and its parents for image capture.
*/
private static function prepareToPrintObject(target:IUIComponent):Array
{
var normalStates:Array = [];
var obj:DisplayObject = target is DisplayObject ?
DisplayObject(target) :
null;
var index:Number = 0;
while (obj != null)
{
if (obj is UIComponent)
{
normalStates[index++] =
UIComponent(obj).prepareToPrint(UIComponent(target));
}
else if (obj is DisplayObject && !(obj is Stage))
{
normalStates[index++] = DisplayObject(obj).mask;
DisplayObject(obj).mask = null;
}
obj = obj.parent is DisplayObject ?
DisplayObject(obj.parent) :
null;
}
return normalStates;
}
/**
* @private
* Reverts the target and its parents back to a pre-capture state.
*/
private static function finishPrintObject(target:IUIComponent,
normalStates:Array):void
{
var obj:DisplayObject = target is DisplayObject ?
DisplayObject(target) :
null;
var index:Number = 0;
while (obj != null)
{
if (obj is UIComponent)
{
UIComponent(obj).finishPrint(normalStates[index++], UIComponent(target));
}
else if (obj is DisplayObject && !(obj is Stage))
{
DisplayObject(obj).mask = normalStates[index++];
}
obj = (obj.parent is DisplayObject) ? DisplayObject(obj.parent) : null;
}
}
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @param width Width of the image.
*
* @param height Height of the image.
*
* @param data A byte array to contain the image.
*
* @param contentType The encoder format type for the image,
* either PNGEncoder or JPEGEncoder.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function ImageSnapshot(width:int = 0, height:int = 0,
data:ByteArray = null,
contentType:String = null)
{
super();
this.contentType = contentType;
this.width = width;
this.height = height;
this.data = data;
}
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// contentType
//----------------------------------
/**
* @private
* Storage for the contentType property.
*/
private var _contentType:String;
[Inspectable(category="General")]
/**
* The MIME content type for the image encoding format
* that was used to capture this snapshot. For PNG format
* images, the MIME type is "image/png". For JPG or JPEG
* images, the MIME type is "image/jpeg"
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get contentType():String
{
return _contentType;
}
/**
* @private
*/
public function set contentType(value:String):void
{
_contentType = value;
}
//----------------------------------
// data
//----------------------------------
/**
* @private
* Storage for the data property.
*/
private var _data:ByteArray;
[Inspectable(category="General")]
/**
* The encoded data representing the image snapshot.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get data():ByteArray
{
return _data;
}
/**
* @private
*/
public function set data(value:ByteArray):void
{
_data = value;
}
//----------------------------------
// height
//----------------------------------
/**
* @private
* Storage for the height property.
*/
private var _height:int;
[Inspectable(category="General")]
/**
* The image height in pixels.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get height():int
{
return _height;
}
/**
* @private
*/
public function set height(value:int):void
{
_height = value;
}
//----------------------------------
// properties
//----------------------------------
/**
* @private
* Storage for the properties property.
*/
private var _properties:Object = {};
/**
* An Object containing name/value pairs
* specifying additional properties of the image.
*
* <p>You generally supply such information
* only when sending an ImageSnapshot instance
* to Adobe's LiveCycle Data Services
* in order to generate a PDF file.
* You can either set the entire object
* or set individual name/value pairs
* on the pre-existing empty Object.</p>
*
* @default {}
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get properties():Object
{
return _properties;
}
/**
* @private
*/
public function set properties(value:Object):void
{
_properties = value;
}
//----------------------------------
// width
//----------------------------------
/**
* @private
* Storage for the width property.
*/
private var _width:int;
[Inspectable(category="General")]
/**
* The image width in pixels.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get width():int
{
return _width;
}
/**
* @private
*/
public function set width(value:int):void
{
_width = value;
}
}
}