Writing Images out of Flash

There was a question on the forums recently about how to save/write an image out of Flash as a valid image file (such as a JPG or PNG). Ironically enough, this has come up in my office several times recently, so a couple weeks ago I built some resources to automate the process.

First off, we need the ability to write files out of Flash. As of Flash Player 10, we’ll be able to do this directly (hurray!). However, in the meantime we’re still dependent on a server-side technology like PHP. So, let’s get that out of the way first. Save the following script to a PHP enabled server at a publicly enabled URL:

<?php
    // com.gmacwilliam.images.ImageCaptureService
    // PHP file service.

    if (isset($_POST['tempfile']))
    {
        $temp = $_POST['tempfile'];
        $target = $_POST['newpath'];
        rename($temp, $target);
        echo "complete!";
    }
    else
    {
        $temp = "temp";
        $fp = fopen( $temp, 'wb' );
        fwrite( $fp, $GLOBALS[ 'HTTP_RAW_POST_DATA' ] );
        fclose( $fp );
        echo $temp;
    }
?>

The above is a dirt simple file-write script that I threw together in a couple minutes. Feel free to improve upon it with validation, type-checking, size restrictions, etc. And of course, nothing says that this file-write process needs to be done with PHP. Any server-side scripting language will work.

So, that gives us a place to send formatted image data to be written. Now we need to capture and encode image data within Flash. For encoding, we need the image resources provided in Adobe’s AS3 Corelib, available for free at: http://code.google.com/p/as3corelib/. Download the corelib and place the source in your script library.

Lastly, we need some logic to capture an image, send it through the encoder, then post the resulting data to our PHP service. To automate this process, I wrote a small class library. The package (com.gmacwilliam.images) contains one service abstract and two concrete service classes. Both the JPG and PNG capture classes are instantiated with a reference to the DisplayObject that you want to save an image of, and a file location on the server at which to place the file (the location should be formatted as: “path/filename.ext”. The “path/” value is relative to the PHP script’s location).

1) save to: com/gmacwilliam/images/ImageCaptureService.as
Note: update the “imageServiceURL” variable with the path to your PHP script.

package com.gmacwilliam.images
{
    import flash.events.EventDispatcher;
    import flash.events.Event;
    import flash.utils.ByteArray;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    import flash.net.URLRequestMethod;
    import flash.net.URLVariables;

    public class ImageCaptureService extends EventDispatcher
    {
        public static const FILE_WRITE_COMPLETE_EVENT:String = "ImageFileWriteComplete";
        public static var imageServiceURL:String = "http://your-image-service-path.php";

        private var _servicePath:String="";
        private var _filePath:String="";

        public function ImageCaptureService(filePath:String=""):void
        {
            super();
            _servicePath = ImageCaptureService.imageServiceURL;
            _filePath = filePath;
        }

        /* write
         * @desc: Writes image data to file.
         * @param: image data byte array.
         */

        protected function write(img:ByteArray):void
        {
            // configure data to send
            var req:URLRequest = new URLRequest(_servicePath);
            req.contentType = "application/octet-stream";
            req.method = URLRequestMethod.POST;
            req.data = img;

            // send data to file service
            var write:URLLoader = new URLLoader();
            write.addEventListener(Event.COMPLETE, this._onWriteComplete);
            write.load(req);
        }

        private function _onWriteComplete(evt:Event):void
        {
            // cleanup write operation
            var write:URLLoader = evt.target as URLLoader;
            write.removeEventListener(Event.COMPLETE, this._onWriteComplete);
            dispatchEvent(new Event(ImageCaptureService.FILE_WRITE_COMPLETE_EVENT));

            // move created file to target location
            _moveFile(write.data);
        }

        /* _moveFile
         * @desc: Moves created file to target location.
         * @param: reference to newly created file on server.
         */

        private function _moveFile(tempFile:String):void
        {
            var vars:URLVariables = new URLVariables();
            vars.tempfile = tempFile;
            vars.newpath = _filePath;

            var req:URLRequest = new URLRequest(_servicePath);
            req.method = URLRequestMethod.POST;
            req.data = vars;

            var moveto:URLLoader = new URLLoader();
            moveto.addEventListener(Event.COMPLETE, this._onMoveComplete);
            moveto.load(req);
        }

        private function _onMoveComplete(evt:Event):void
        {
            var moveto:URLLoader = evt.target as URLLoader;
            moveto.removeEventListener(Event.COMPLETE, this._onMoveComplete);
            dispatchEvent(new Event(Event.COMPLETE));
        }
    }
}

2) save to: com/gmacwilliam/images/JPGCapture.as
Implementation: new JPGCapture(clipToCapture:DisplayObject, filePath:String, [quality:int=0-100]);

package com.gmacwilliam.images
{
    import flash.display.DisplayObject;
    import flash.display.BitmapData;
    import com.adobe.images.JPGEncoder;

    public final class JPGCapture extends ImageCaptureService
    {
        public function JPGCapture(clip:DisplayObject, filePath:String="", quality:int=50):void
        {
            super(filePath);
            var jpg:JPGEncoder = new JPGEncoder(quality);
            var bmp:BitmapData = new BitmapData(clip.width, clip.height, false, 0xFFFFFF);
            bmp.draw(clip);
            write(jpg.encode(bmp));
        }
    }
}

3) save to: com/gmacwilliam/images/PNGCapture.as
Implementation: new PNGCapture(clipToCapture:DisplayObject, filePath:String);

package com.gmacwilliam.images
{
    import flash.display.DisplayObject;
    import flash.display.BitmapData;
    import com.adobe.images.PNGEncoder;

    public final class PNGCapture extends ImageCaptureService
    {
        public function PNGCapture(clip:DisplayObject, filePath:String=""):void
        {
            super(filePath);
            var bmp:BitmapData = new BitmapData(clip.width, clip.height, true, 0x00000000);
            bmp.draw(clip);
            write(PNGEncoder.encode(bmp));
        }
    }
}

This was a pretty fast and unrefined implementation that I threw together while bidding a project with this technology. As such, there’s some redundancy in the server communication. This service currently makes two server calls: one to post the data and a seperate call to move the file since I was running into problems with putting text and bianary data in the same server post. I remember reading something on Grant Skinner’s blog a while back about mixing data formats in a server post, so I’ll have to look that up and post and updated version once these service calls have been consolidated.

Advertisements

2 comments so far

  1. fatbuoy1 on

    Thanks a lot Greg, I’ll give this a go. This blog things a good idea.

  2. Boris on

    Great stuff!! It’s much easier if you don’t have to start from scratch. Thanx.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: