Solved

High Dpi Images from flash player by using ImageSnapshot class

Posted on 2010-11-28
9
1,675 Views
Last Modified: 2012-05-10
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
Comment
Question by:shardul_bartwal
  • 4
  • 3
9 Comments
 
LVL 14

Accepted Solution

by:
tomaugerdotcom earned 500 total points
ID: 34238708
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
 
LVL 14

Expert Comment

by:tomaugerdotcom
ID: 34238715
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
 

Author Comment

by:shardul_bartwal
ID: 34260432
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
 
LVL 14

Expert Comment

by:tomaugerdotcom
ID: 34267331
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
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 

Author Comment

by:shardul_bartwal
ID: 34270305
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
 
LVL 14

Assisted Solution

by:tomaugerdotcom
tomaugerdotcom earned 500 total points
ID: 34270486
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
 

Author Comment

by:shardul_bartwal
ID: 34272947
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
 
LVL 37

Expert Comment

by:CyanBlue
ID: 34824223
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

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

I know the transition can be hard. We got used to the ease of use ActionScript 2 had, but honestly, it became problematic later on, especially if designers were involved in the project and found it easy to add code as they saw fit. So, this artic…
Introduction This article is primarily concerned with ActionScript 3 and generally specific to AVM2.  Most suggestions would apply to ActionScript 2 as well, and I've noted those tips that differ between AS2 and AS3. With the advent of ActionS…
The goal of the tutorial is to teach the user how to select which audio input to use. Once you have an audio input plugged into the laptop or computer, you will go into the audio input settings and choose which audio input you want to use.
This Micro Tutorial will teach to how to utilize bit rate in Adobe Flash Media Live Encoder.

706 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

22 Experts available now in Live!

Get 1:1 Help Now