Archive for November, 2008|Monthly archive page

Creating a dynamically resizable Flash window.

MYTH: once you embed a SWF movie into an HTML page, you can’t change the size of the Flash window.

While that ol’ fixed-size-Flash whopper can be a handy excuse when dealing with clients who want the multimedia equivalent of a rocketship built on a non-existent budget, that doesn’t mean a dynamically scaling Flash window isn’t possible. It’s actually surprisingly easy and robust, and it opens the door for all kinds of flexible Flash development. For example, Lassie Shepherd (my game editor project) implements a dynamically scaling Flash window so that a developer can manipulate the dimensions of their game display on the fly within the editor tool. The Lassie game player also responds to the specified dimensions and adjusts the game window to the proper size. I most recently used this trick in a simple graphing application where the Flash window needed to adjust itself to its variable-height content. So, this is a pretty handy trick to have in your tool box and its very easy set up! Here it is:

function setWindowSize(w:int, h:int):void
{
    if (ExternalInterface.available)
    {
        var js:String = "function(){";
        js += "var flash=document.getElementById('"+ ExternalInterface.objectID +"');";
        js += "var win=flash.parentNode;";
        js += "flash.style.width='100%';";
        js += "flash.style.height='100%';";
        js += "win.style.width='"+ w +"px';";
        js += "win.style.height='"+ h +"px';";
        js += "}";
        ExternalInterface.call(js);
    }
}

Note that Flash’s ExternalInterface does not work on a local connection, so you’ll need to test implementations at an HTTP location. To have Flash objects respond to the window resize, listen to the stage for Event.RESIZE.

This process works using a few tricks. First, ExternalInterface is being used for JavaScript injection. Rather than having dependancies on external JavaScript, all required JavaScript is being composed within Flash and then called out to the browser where it is both parsed and processed. As far as the actual Flash window resize is concerned, we’re just setting the width and height of the Flash’s DOM container. This specific example fits the Flash object to its frame (width/height=100%), and then sets new dimentions on the parent container. However, you can customize the DOM usage to a specific implementation.

More on saving images out of Flash

I posted previously on generating a bitmap image of a MovieClip and writing it from Flash to a remote server. However, that previous post was a very early. Now that I’ve implemented the process within an actual project scenario, I’ll follow up here with some revised code that streamlines the task with greater efficiency.

1) Server-side. First thing first: you’ll need a PHP script (or other server-side flavor) to receive binary image data and write it to a file. This revised PHP script is even simpler than before; just save this to a PHP file and place it on a PHP enabled server:

<?php
    $file = $_GET['file'];
    $fp = fopen( $file, 'wb' );
    fwrite( $fp, $GLOBALS[ 'HTTP_RAW_POST_DATA' ] );
    fclose( $fp );
    echo $file;
?>

2) Image encoders. Next, you’ll need the Adobe AS3 corelib which has a collection of classes for JPG and PNG image encoding. Download and extract the corelib’s source library into your main class library. The following image service classes will build upon these image encoders.

3) Image services. Assemble the following three classes into a library. These classes will take care of capturing a DisplayObject’s image and then sending it to your server to be saved. Note: you’ll need to update the ImageCaptureService class member “serverPath” to point to your image service PHP file (from step #1).

3.a) Save this class as: com/gmacwilliam/images/ImageCaptureService.as

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";
        // UPDATE THIS...
        public static var servicePath:String = "http://www.yourServerHere.com/save_image.php";

        public function ImageCaptureService():void
        {
            super();
        }

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

        protected function write(img:ByteArray, filename:String):void
        {
            // configure data to send
            var req:URLRequest = new URLRequest(ImageCaptureService.servicePath +"?file="+ filename);
            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));
        }
    }
}

3.b) Save this class as: com/gmacwilliam/images/JPGCapture.as

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();
            var jpg:JPGEncoder = new JPGEncoder(quality);
            var bmp:BitmapData = new BitmapData(clip.width, clip.height, false, 0xFFFFFF);
            bmp.draw(clip);
            write(jpg.encode(bmp), filePath);
        }
    }
}

3.c) Save this class as: com/gmacwilliam/images/PNGCapture.as

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();
            var bmp:BitmapData = new BitmapData(clip.width, clip.height, true, 0x00000000);
            bmp.draw(clip);
            write(PNGEncoder.encode(bmp), filePath);
        }
    }
}

4) Instantiation. These services are extremely easy to use… Just instantiate them with a reference to a DisplayObject and a server path at which to write the image file. Also, the JPGCapture class will accept an optional quality setting (a number between 0 and 100).

var saveJPG:JPGCapture = new JPGCapture(myDisplayObject, "images/flashimg.jpg", 85);
var savePNG:PNGCapture = new PNGCapture(myDisplayObject, "images/flashimg.png");

You can listen to boh object for an “ImageCaptureService.FILE_WRITE_COMPLETE_EVENT” event to know when the image data has finished getting sent to the server.

While these image services are very similar to the ones in my last post, there has been an important change: they now send both binary and text data to the server in a single server call, rather than breaking the two formats out into seperate calls. This was achieved by dropping the text data (the filename) into GET variables so that POST could be allocated entirely to binary image data.

Go vote, then track results…

I’ll start with a friendly reminder to any US readers to PLEASE go out and vote today!

After that, I’ll encourage any eager onlookers to following this evening’s vote progression using the NPR (National Public Radio) interactive election map which I’ve been programming for the past two months:

http://www.npr.org/news/specials/election2008/2008-election-map.html

Here we go, America!