[Okta Webinar] Learn how to a build a cloud-first strategyRegister Now

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1739
  • Last Modified:

High Dpi Images from flash player by using ImageSnapshot class

Hi all,
I am working in a flex application.I want to get the print quality images from the ImageSnapshot.captureImage().This method increase the image size as I increase the dpi.But the problem is with the big dimension images.Let suppose my image size is 2880 * 2880. Then it can not give me the output with more then this size,no matter what dpi i mention.
I want to increase the dpi of my images upto 200. If any one have any idea please suggest me.
I am using the flash player 10.1.


with Regards,
Shardul
0
shardul_bartwal
Asked:
shardul_bartwal
  • 4
  • 3
2 Solutions
 
tomaugerdotcomCommented:
CaptureBitmapData caps dimensions at 2880 pixels for each side.

From dougmccune.com:
Bypassing the 2,880 pixel limit
Flash Player has a limitation of only allowing a single bitmap object to have a max width or height of 2,880 pixels. I’ve never run into this problem, but I guess people with massive monitors can have a problem. The ImageSnapshot class does some fancy footwork in the captureImage method that allows you to generate an encoded JPEG or PNG snapshot that is larger than 2,880 pixels. Basically it creates multiple BitmapData objects and stitches them together to form one final ByteArray. Look at the source code for ImageSnapshot and check out the captureAll method.

This gets around the 2,880 pixel limit, but there’s a 256 meg size limit on a single ByteArray object. From the documentation in the ImageSnapshot class: “This ByteArray is limited to around 256MB so scaled images with an area equivalent to about 8192×8192 will result in out of memory errors.

---------

I did some digging into the source code for captureImage() and here's the relvant piece:
// If scaleLimited, we limit snapshot to a maximum of 2880x2880
            // 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);
            }


-----------------


Do note the method signature:
public static function captureImage(source:*, dpi:Number=0, encoder:IImageEncoder=null, scaleLimited:Boolean=true):ImageSnapshot


So, have you tried:
mySnapshot:ImageSnapshot = ImageSnapshot.captureImage(mySource, 200, mx.graphics.codec.JPEGEncoder, false);

Note the last parameter == false, to disable scaleLimited...
0
 
tomaugerdotcomCommented:
Just for your reference (or anyone else's), here's the SC for ImageSnapshot in its entirety:

////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2003-2007 Adobe Systems Incorporated.
//  All Rights Reserved. The following is Source Code and is subject to all
//  restrictions on such code as contained in the End User License Agreement
//  accompanying this product.
//
////////////////////////////////////////////////////////////////////////////////

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;

/**
 * A helper class used to capture a snapshot of any Flash component that 
 * implements <code>flash.display.IBitmapDrawable</code>, including Flex UI
 * components.
 */
[RemoteClass(alias="flex.graphics.ImageSnapshot")]
public dynamic class ImageSnapshot
{
    include "../core/Version.as";

    /**
     * Constructor.
     */
    public function ImageSnapshot(width:int=0, height:int=0,
        data:ByteArray=null, contentType:String=null)
    {
        this.contentType = contentType;
        this.width = width;
        this.height = height;
        this.data = data;
    }

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

    //----------------------------------
    //  contentType
    //----------------------------------

    [Inspectable(category="General")]
    /**
     * The content type for the image encoding format that was used to
     * capture this snapshot.
     */
    public function get contentType():String
    {
        return _contentType;
    }

    public function set contentType(value:String):void
    {
        _contentType = value;
    }

    //----------------------------------
    //  data
    //----------------------------------

    [Inspectable(category="General")]
    /**
     * The encoded data representing the image snapshot.
     */
    public function get data():ByteArray
    {
        return _data;
    }

    public function set data(value:ByteArray):void
    {
        _data = value;
    }

    //----------------------------------
    //  height
    //----------------------------------

    [Inspectable(category="General")]
    /**
     * The image height in pixels.
     */
    public function get height():int
    {
        return _height;
    }

    public function set height(value:int):void
    {
        _height = value;
    }

    //----------------------------------
    //  width
    //----------------------------------

    [Inspectable(category="General")]
    /**
     * The image width in pixels.
     */
    public function get width():int
    {
        return _width;
    }

    public function set width(value:int):void
    {
        _width = value;
    }


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

    /**
     * A utility method to grab a raw snapshot of a UI component as BitmapData.
     * 
     * @param source A UI component that implements <code>flash.display.IBitmapDrawable</code>
     * @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.
     * @return BitmapData representing the captured snapshot.
     */
    public static function captureBitmapData(source:*, 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 == null)
                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;
            }

            data = new BitmapData(scaledWidth, scaledHeight);
            data.draw(source, matrix, colorTransform, blendMode, clipRect, smoothing);
        }
        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 A UI component that implements <code>flash.display.IBitmapDrawable</code>
     * @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. If 
     * an encoder is not provided, a default PNGEncoder is used.
     * @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.
     */
    public static function captureImage(source:*, 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 == null)
                encoder = new DEFAULT_ENCODER();

            var bytes:ByteArray;
            width = width * matrix.a;
            height = height * matrix.d;

            // If scaleLimited, we limit snapshot to a maximum of 2880x2880
            // 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 a <code>mx.graphics.ImageSnapshot</code>
     * 
     * @see #captureImage
     */
    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. 2880x2880 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 8192x8192 will
     * result in out of memory errors.
     */
    private static function captureAll(source:*, 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;
        var data:BitmapData = new BitmapData(topLeft.width, topLeft.height);
        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;
        }
    }

    /**
     * The default <code>mx.graphics.codec.IImageEncoder</code> implementation
     * used to capture images. The default encoder uses the PNG format.
     */
    public static var DEFAULT_ENCODER:Class = mx.graphics.codec.PNGEncoder;
    public static var MAX_BITMAP_DIMENSION:int = 2880;

    private var _contentType:String;
    private var _data:ByteArray;    
    private var _height:int;
    private var _width:int;
}

}

Open in new window

0
 
shardul_bartwalAuthor Commented:
Yes you are right according to the documentation.

I have already tried for the same.

You can view my source code this is just for demo.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
      layout="vertical" width="100%" height="100%" scriptTimeLimit="90">
<mx:Script>
<![CDATA[
      import mx.graphics.ImageSnapshot;

import mx.graphics.codec.JPEGEncoder;

private function saveImageToFileSystem():void
{

 var jPEGEncoder:JPEGEncoder = new JPEGEncoder(85);
 var imageSnapshot:ImageSnapshot = ImageSnapshot.captureImage(imgCanvas,72,jPEGEncoder,false);
 var fileReference:FileReference = new FileReference();
 fileReference.save(imageSnapshot.data, "img123.jpg");
               
}


]]>
</mx:Script>
<mx:Canvas width="2880" height="2880" id="imgCanvas">
      <mx:Image width="100%" height="100%" source="maxx.jpg" maintainAspectRatio="true"/>      
</mx:Canvas>

<mx:Button label="Take a Picture" click="saveImageToFileSystem()" x="340" y="20" />
</mx:Application>


I this application if i change my dpi more then 72 then it gives me error inside the ImageSnapshot class,at :-

 finally
        {
            if (source is IUIComponent)
                finishPrintObject(IUIComponent(source), normalState);
        }

If i tried to comment it then it simply gives me error related to the memory of the flash player,or sometimes script executed for long time.................



with Thnx,
Shardul
0
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
tomaugerdotcomCommented:
Not that you would want to ask your users to do this, but have you checked you Flash plugin's memory settings? It definitely sounds like a Flashplayer issue.
0
 
shardul_bartwalAuthor Commented:
Yes you are right....... This is again a very big issue for me, till date I have not got any solution and link for
'Error: Error #1502: A script has executed for longer than the default timeout period of 15 seconds.'.

Because by any means I am not able to execute the script for more then this time I have also tried it as a compiler argument as well as scriptTimeLimit property at application level.But no benefit.

Hence I am just hanging for the big bitmap Data.


with Thnx,
Shardul
0
 
tomaugerdotcomCommented:
Have you had any luck working with multiple, smaller BitmapData objects? And then perhaps stitching them together afterward? The way I would approach it is:

- capture multiple, smaller Rects at the resolution you want
- encode them as JPEGs, PNG or whatever
- send them server-side, to a Perl or PHP script
- use a server side library like GD or imageMagick and stitch them together into a larger image
- send the result back to the flash client as a ByteArray
- rebuild the BitmapData and print

OR

- don't send the result back to Flash - just write it, on the server, to a directory and provide Flash with the URI to that file
- Flash can let the user browse to that file and download it.

Yes I know, not ideal and way too much work, but if this is your requirement, maybe you'll need to pursue this avenue. Let me know how it goes.

Tom
0
 
shardul_bartwalAuthor Commented:
Hi Tom,
Yes I will try this asap from the server end and will let you know about this asap. Thnx for your kind coperation.




with Reagrds,
Shardul
0
 
CyanBlueCommented:
This question has been classified as abandoned and is being closed as part of the Cleanup Program. See my comment at the end of the question for more details.
0

Featured Post

Free Tool: Path Explorer

An intuitive utility to help find the CSS path to UI elements on a webpage. These paths are used frequently in a variety of front-end development and QA automation tasks.

One of a set of tools we're offering as a way of saying thank you for being a part of the community.

  • 4
  • 3
Tackle projects and never again get stuck behind a technical roadblock.
Join Now