how to impress your friends by taking an image snapshot of a flash movie and automatically uploading the jpg to a server in three easy steps

The title for this recipe is a little long, but the use case is straightforward: I want to capture the visual appearance of a movieclip, encode it as a jpg, and then upload it to my server without any user interaction

I came to this problem while writing AsUnit tests for my Flash Actionscript 3 application. I wanted to test a feature where a user can select an image file from their desktop and then upload it to the server. My tests run in a harness separate from the rest of the application and they need to run automatically without any clicks required. For various security reasons the powerful FileReference object requires the user to actively select a file before any upload can occur. This restriction prevents unscrupulous RIA developers from using their powers for evil by sucking files off their user’s machines. For my purpose I could get around this limitation by simply creating a jpg within the swf movie itself to use for my upload. Lots of work for a unit test, but the idea of taking a snapshot picture of an application and posting it to a server intrigued me. I can think of several other relevant use cases, for instance handling an application fault by posting a screenshot along with rest of the relevant data to a logging server.

The tricky part turned out to be adding the proper headers to the encoded jpg ByteArray to create something suitable for a UrlRequest. It took some fumbling around but eventually I found my answer. Submitted for your approval: a couple of helpful code snippets and one primo reusable asset.

1) Create a BitmapData snapshot.

Assuming that target is the movieclip that you want to take a picture of

var bitmapData:BitmapData = new BitmapData(target.width, target.height, true, 0xFFFFFF);
var drawingRectangle:Rectangle =  new Rectangle(0, 0, target.width, target.height);
bitmapData.draw(target, new Matrix(), null, null, drawingRectangle, false);

The only insight I have to offer about using BitmapData is that crossdomain.xml may rear it’s ugly head if there are any external video assets involved. You can get around this by setting the checkPolicyFile property of any NetStream objects to true, and making sure that the appropriate policy files are present.

2) Encode a JPG or PNG ByteArray
Could have been a challenge, but thankfully Flash heavyweight Tinic Uro has published all the necessary code. They are distributed as part of Adobe’s AS3 Corelib, available on Google Code. The Corelib is chock full of useful utilities, I highly recommend giving it a look to see if there is anything in it you can use. Here is my usage of the JPGEncoder

import com.adobe.images.JPGEncoder;

var byteArray:ByteArray = new JPGEncoder().encode(bitmapData);

3) Post the ByteArray to the server
Now things get complicated. As I explained in the introduction, the FileReference object which is designed for uploading files to a server requires user interaction. We must POST the data using the UrlRequest and UrlLoader classes, however we run into a limitation of the Flash API. The data property of a UrlRequest can either be a UrlVariables object or a ByteArray object. There is no easy way to send name value pairs along with the JPG byte array. This is a big problem, because most upload applications will require a filename and other headers to accompany the raw file data. I wandered around in the wilderness for a little while, sending random requests and hoping that staring at the contents of the requests using the incredibly useful Charles HTTP monitor tool would magicaly reveal the answer to me. It didn’t.

I was nowhere until I found this blog post by Joa Ebert. Not surprisingly it turns out that there are a whole bunch of precisely formatted headers which must accompany the POST data to tell the server how to interpret the contents. The W3 site has more details. Joa’s code sample showed me how to take an input ByteArray and add all the necessary headers to make a full file upload request. Better yet, it worked on the first try. I modified the code to match my own style, turned it into a separate helper class, and added the ability to submit an arbitrary number of name value pairs.

First, the sample usage of UploadPostHelper

//assumed variable declarations
//var byteArray:ByteArray = jpg byte array created in step 2
//var fileName:String = "desiredfilename.jpg"
//var uploadPath:String = "http://destination.path.on/server"
//var parameters:Object = optional generic object containing name value pairs to accompany the post
//function onComplete(eventObj:Event):void {  handler for succesful loading of request
//function onError(eventObj:ErrorEvent):void {  handler for faulty loading of request

var urlRequest:URLRequest = new URLRequest();
urlRequest.url = uploadPath;
urlRequest.contentType = 'multipart/form-data; boundary=' + UploadPostHelper.getBoundary();
urlRequest.method = URLRequestMethod.POST;
urlRequest.data = UploadPostHelper.getPostData(file, parameters);
urlRequest.requestHeaders.push( new URLRequestHeader( 'Cache-Control', 'no-cache' ) );

var urlLoader:URLLoader = new URLLoader();
urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
urlLoader.addEventListener(Event.COMPLETE, onComplete);
urlLoader.addEventListener(IOErrorEvent.IO_ERROR, onError);
urlLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
urlLoader.load(urlRequest);

Finally the crown jewel of this blog entry, the UploadPostHelper class. Please use responsibly.

package {

    import flash.events.*;
    import flash.net.*;
    import flash.utils.ByteArray;
    import flash.utils.Endian;

    /**
     * Take a fileName, byteArray, and parameters object as input and return ByteArray post data suitable for a UrlRequest as output
     *
     * @see http://marstonstudio.com/?p=36
     * @see http://www.w3.org/TR/html4/interact/forms.html
     * @see http://www.jooce.com/blog/?p=143
     * @see http://www.jooce.com/blog/wp%2Dcontent/uploads/2007/06/uploadFile.txt
     * @see http://blog.je2050.de/2006/05/01/save-bytearray-to-file-with-php/
     *
     * @author Jonathan Marston
     * @version 2007.08.19
     *
     * This work is licensed under a Creative Commons Attribution NonCommercial ShareAlike 3.0 License.
     * @see http://creativecommons.org/licenses/by-nc-sa/3.0/
     *
     */

    public class UploadPostHelper {

        /**
         * Boundary used to break up different parts of the http POST body
         */

        private static var _boundary:String = "";

        /**
         * Get the boundary for the post.
         * Must be passed as part of the contentType of the UrlRequest
         */

        public static function getBoundary():String {

            if(_boundary.length == 0) {
                for (var i:int = 0; i < 0x20; i++ ) {
                    _boundary += String.fromCharCode( int( 97 + Math.random() * 25 ) );
                }
            }

            return _boundary;
        }

        /**
         * Create post data to send in a UrlRequest
         */

        public static function getPostData(fileName:String, byteArray:ByteArray, parameters:Object = null):ByteArray {

            var i: int;
            var bytes:String;

            var postData:ByteArray = new ByteArray();
            postData.endian = Endian.BIG_ENDIAN;

            //add Filename to parameters
            if(parameters == null) {
                parameters = new Object();
            }
            parameters.Filename = fileName;

            //add parameters to postData
            for(var name:String in parameters) {
                postData = BOUNDARY(postData);
                postData = LINEBREAK(postData);
                bytes = 'Content-Disposition: form-data; name="' + name + '"';
                for ( i = 0; i < bytes.length; i++ ) {
                    postData.writeByte( bytes.charCodeAt(i) );
                }
                postData = LINEBREAK(postData);
                postData = LINEBREAK(postData);
                postData.writeUTFBytes(parameters[name]);
                postData = LINEBREAK(postData);
            }

            //add Filedata to postData
            postData = BOUNDARY(postData);
            postData = LINEBREAK(postData);
            bytes = 'Content-Disposition: form-data; name="Filedata"; filename="';
            for ( i = 0; i < bytes.length; i++ ) {
                postData.writeByte( bytes.charCodeAt(i) );
            }
            postData.writeUTFBytes(fileName);
            postData = QUOTATIONMARK(postData);
            postData = LINEBREAK(postData);
            bytes = 'Content-Type: application/octet-stream';
            for ( i = 0; i < bytes.length; i++ ) {
                postData.writeByte( bytes.charCodeAt(i) );
            }
            postData = LINEBREAK(postData);
            postData = LINEBREAK(postData);
            postData.writeBytes(byteArray, 0, byteArray.length);
            postData = LINEBREAK(postData);

            //add upload filed to postData
            postData = LINEBREAK(postData);
            postData = BOUNDARY(postData);
            postData = LINEBREAK(postData);
            bytes = 'Content-Disposition: form-data; name="Upload"';
            for ( i = 0; i < bytes.length; i++ ) {
                postData.writeByte( bytes.charCodeAt(i) );
            }
            postData = LINEBREAK(postData);
            postData = LINEBREAK(postData);
            bytes = 'Submit Query';
            for ( i = 0; i < bytes.length; i++ ) {
                postData.writeByte( bytes.charCodeAt(i) );
            }
            postData = LINEBREAK(postData);

            //closing boundary
            postData = BOUNDARY(postData);
            postData = DOUBLEDASH(postData);

            return postData;
        }

        /**
         * Add a boundary to the PostData with leading doubledash
         */

        private static function BOUNDARY(p:ByteArray):ByteArray {
            var l:int = UploadPostHelper.getBoundary().length;

            p = DOUBLEDASH(p);
            for (var i:int = 0; i < l; i++ ) {
                p.writeByte( _boundary.charCodeAt( i ) );
            }
            return p;
        }

        /**
         * Add one linebreak
         */

        private static function LINEBREAK(p:ByteArray):ByteArray {
            p.writeShort(0x0d0a);
            return p;
        }

        /**
         * Add quotation mark
         */

        private static function QUOTATIONMARK(p:ByteArray):ByteArray {
            p.writeByte(0x22);
            return p;
        }

        /**
         * Add Double Dash
         */

        private static function DOUBLEDASH(p:ByteArray):ByteArray {
            p.writeShort(0x2d2d);
            return p;
        }

    }
}

Good stuff. Let me know what kind of luck you have trying to make this all work for you.

Creative Commons License
This work, unless otherwise expressly stated, is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.

54 comments ↓

#1 Troy Gardner on 09.12.07 at 2:57 pm

Sweet! Thanks so much for posting the code. One caveat though is when running HTTP Post to HTTPS, it only seems to works from the browser. Flash Player supports SSL get but not Post.

#2 Flavia on 09.19.07 at 9:19 am

I have undestood in your tutorial that it is possible convert a swf file to jpg file and uploading it to server but I would like to know if is it possible to convert movie clip to jpg in the action on click of the button.
I have a movie clip in the flash that all is modified by the user and in the end I want to generate a JPEG file to use it later.
Could you please help me to make this?

#3 robert on 09.23.07 at 3:32 pm

hi. nice blog . thanks.

#4 Mikko on 10.10.07 at 4:01 pm

Flavia,

The BitmapData method “draw” should do exactly that: convert a MovieClip to BitmapData object which you can then encode to JPEG. Check out the AS 3.0 reference for description of BitmapData class and examples.

Regards,
Mikko

Ps. Many thanks to the author for the excellent post!

#5 beatmapper on 10.24.07 at 1:18 pm

very nice!!
iv’e been looking for somthing like that for a long time. i couldn’t see how to post extra name value pairs with a binary data post, but your stuff seems right. i’m still testing it to fit to more generic sending of data, but it’s very simple and smart.
thank you for your post!

#6 beatmapper on 10.24.07 at 1:26 pm

just got back to say it worked! your’e the man, man!

#7 jacob on 11.08.07 at 3:34 am

so are you somehow accomplishing this without a server side script? if not, do you have an example of how one would write this out to a jpg?

#8 jacob on 11.09.07 at 3:20 pm

Works great. One thing I am wondering is that if Adobe will somehow cripple this as seems somewhat a subversive way of uploading data…

also, for anyone who was having the same issue as me it does require a php script. This one worked for me using MAMP

#9 jacob on 11.09.07 at 3:24 pm

doh, blog strip out my code comment, it was script 2 from this post (if the link goes through, i also put it as my website)

http://www.dynamicdrive.com/forums/showpost.php?p=43182&postcount=1

#10 Josh Santangelo on 11.16.07 at 6:12 pm

Thanks for this, it seems to work pretty well.

I made some changes though:

- Removed the addition of the “Filename” parameter to the parameters object.
- Added a “fieldName” parameter to getPostData to specify the form field name the image is posted as.
- Added a “contentType” parameter to getPostData to specify the content type (such as image/jpg) in case the web service is expecting a specific type.

Another suggestion: This would be very useful in Flex as a class which extends HTTPService.

#11 Amer on 11.20.07 at 3:33 am

Hey Jonathan… nice blog. Can you also please share the server side PHP script, I have been going nuts trying to write the PHP side.

#12 Amer on 11.20.07 at 3:47 am

Hmmm… Ignore my last post, I found the PHP script on Jacobs site!! :)

http://www.dynamicdrive.com/forums/showpost.php?p=43182&postcount=1

#13 Richard Willis » Blog Archive » The Young Man and the Sea on 11.30.07 at 12:04 pm

[...] So what’s down there in these low/mid levels? Its hard to tell because the further you go the darker and more primal it gets. Its like the sea. It’s deep, wide and powerful; it fills the space from horizon (screen left) to horizon (screen right). Whilst you are working it is your world and you probably just accept that it works without wondering how or why. Are you really going to start looking at the Unix kernels of your OS first thing on a Monday morning? No, I thought not. It’s unknown and the further down you go the murkier and more primal it gets. Right down there on the seabed, at the very hub of your machine; do you know what creatures live down there, their habitat, their behaviour, their primeval nature? If you chuck your net in expertly enough you can drag out some squid, some herring and God knows what else from even deeper down but it’s never you, the developer, who goes down there, it’s always your net. You may know how to fish the deep but do you really know what happens down there? When you create a new FileReference object in AS3, how far down into the darkness of the OS is the Flash Player – or whatever – going to retrieve stuff for you? Just a few days ago I created a new Flash file, instantiated a NetConnection class, called some PHP functionality on the backend, got some data back, encoded a PNG and saved it off to my hard drive. It took a few different attempts, a lot of mistakes along the way but eventually I got there. Throw your net in here: no fish. Try again over there: no fish. Then, with a bit more experience, change a few parametres; maybe wait till nightfall, sail a few miles further south, allow the net to sink a little deeper but drag it through the depths with more urgency and: bingo! Haddock for tea! PNGs on your desktop! [...]

#14 Dan Hassan’s Blog » Blog Archive » Project 1 - Let’s Make a Drawing! on 01.09.08 at 5:27 am

[...] into bitmap data and then uploaded to the cias server through php. The code I used for this is from Maston Development Studio. The drawings were brought into flash and are dynamically placed in a pseudo-3d crowd environment. [...]

#15 dave on 02.24.08 at 11:51 pm

Hey, I’m using the uploadposthelper and getting a wierd random extra bytes warning through charles on save which is stopping the images from saving in php. Anyone?????

#16 Paul on 02.27.08 at 5:10 pm

Hey, trying to save a working JPG out of Flash. I came across this post and implemented the UH Class with no issues and no errors being thrown. However there is also no JPG file being generated. Am I missing server-side scripting??

#17 ActionScript 3 Log » Blog Archive » Share the Image in Clipboard by Internet on 03.08.08 at 7:42 pm

[...] used this for uploading, corelib for encoding image and.., no, that’s [...]

#18 Alvin on 03.21.08 at 12:40 am

Hi, I just wanted to drop a note that your class was extremely helpful, I had written something similar but was halfway through before I lost the file somehow. Thanks

#19 Si on 03.31.08 at 5:34 am

hi, does anyone have a similar asp server side script that will save the image? Been trying myself but no luck yet. cheers

#20 Using AS3 to Upload and Encode Images - Substance Labs on 04.03.08 at 1:21 pm

[...] behind this project is a combination of elements from the Adobe AS3 Corelib, and the very handy UploadPostHelper Class created by Jonathan Marston. There is also a small PHP upload script included in the source, along [...]

#21 gustavo on 04.03.08 at 6:59 pm

i can’t make it work!!!!!!!!!

#22 gustavo on 04.03.08 at 11:55 pm

if ur looking for a fla sample…….try this one http://henryjones.us/articles/using-the-as3-jpeg-encoder ……………is the easyist…i’ve found so far

#23 Max Pellizzaro on 04.29.08 at 2:01 am

Hi there,
I have been using the class, together with a pho code to save the data.
Locally it works just great, but when I try to work on line, data just don’t reach my servers. I’m using AS3 (CS3), and when sniffind data it seams my server return a 500 Error. I have been digging and digging, also using google and other resources, I just can’t understand why it’s not working on line.
Can anyone try to give me a hand ?
Max.

#24 Max Pellizzaro on 04.30.08 at 3:44 pm

Hello Guys,
guess what. I have been able to get around my problem using a server with php5 instead of php4. Still don’t know why since the code I have used to receive the stream of data should have worked also for php4. But anyhow, this has solved the problem.
Max.

#25 Upload BitmapData Snapshot to Server in AS3 | Stephen Braitsch on 05.30.08 at 2:11 pm

[...] class which is a slightly modified version of the UploadPostHelper class written by Jonathan Marston which adds in the necessary headers required by the [...]

#26 Emerald Studios on 06.13.08 at 4:45 pm

Excellent work! Thanks!
But I found some problems (bugs?):

1. I used “Flex3 Night Builds”, so changed “import com.adobe.images.JPGEncoder;” to “import mx.graphics.codec.JPEGEncoder;” + “JPGEncoder” to “JPEGEncoder”

2. Replaced “build-in” name=”Filedata”, without it my Perl scripts don’t see upload files :)

3. In sample you forgot byteArray in “urlRequest.data = UploadPostHelper.getPostData(file,**here**, parameters);”

4. Options for MIME-types. No build-in “octet-steam”, replaced by options

I hope my tips be useful!

#27 Korneel on 10.10.08 at 11:21 am

I encountered a problem with the code above in the line:

urlRequest.data = UploadPostHelper.getPostData(file, parameters);

[code]getPostData()[/code] in de UploadPostHelper class expects the following arguments:

[code]fileName:String, byeArray:ByteArray, parameters:Object[/code]

So you should correct that line in the above example :-)

#28 me on 12.11.08 at 9:56 pm

I cannot upload any file in
http://www.jooce.com/

what i have to do?

can YOU help me?

#29 LLops Blog » Blog Archive » Envío de imágenes y datos con AS3 on 01.18.09 at 9:42 pm

[...] clase UploadPostHelper te permite pasar un nombre de archivo, un ByteArray y un Object y devuelve otro ByteArray con todo [...]

#30 astatic notes on 01.19.09 at 9:04 am

Multipart form data in as3 [Class version 1.1]…

During my last project I faced a problem to upload multipart/form-data to server using Flash. I need to do it using ActionScript only. The idea was to send to server an image generated in flash and several variables but all this data had to be POST.
I …

#31 Devel Blog - CoPLaS » Blog Archive » Flex 10 - upload files using URLRequest on 02.05.09 at 1:40 pm

[...] found interesting class -UploadPostHelper in this article. Next I merged that example with my old code – browse for file and changed the method from [...]

#32 tmu on 03.28.09 at 6:27 am

Great piece of code, just what I needed.

Now how can I send the file type in the urlRequest, so I can later check it in php

if ($_FILES["Filedata"]["type"] == “image/jpg”)
{

#33 harry on 04.16.09 at 12:28 am

thank a lot. It so helpfull.

#34 harry on 04.16.09 at 12:42 am

can I download the example file Sir ?
Thanks

#35 Prateek on 05.31.09 at 8:52 am

thanks a lot for UploadPostHelper class.
this is a very nice class that works fantastic.

#36 Nigam Shah on 06.22.09 at 1:08 am

You’re the best. I’ve been looking all over for this, and it works great.

#37 amol gathale on 08.07.09 at 10:21 am

I read the your article it is very nice to takl with webcam using flash and save images over server .but i want this article in asp.net. Please reply to my message

Thanks

#38 jeff on 10.06.09 at 11:58 am

I’ve used the AS3 script and the php script and I get an email with an attachment but the attachment is zero bytes long. How does one go about figuring out what I’m doing wrong. There are no extra files on the server. No errors with AS3 or php script (error log is empty). The onCompleteEvent fires no problem.

#39 James Printer on 10.10.09 at 1:28 pm

If you ever find a way to monitor uploading progress so a progress bar could be made while uploading (the events from URLLoader only seem to be for downloading the result), please let me know :)

#40 Danel Kirch on 03.06.10 at 7:59 pm

Really helpful package for images!

I use it as original to 90 %, other 10 % are added so i can pass URLVariables in URLRequest.data package.

Working like a charm!
Thank you for you time and effort that you put into this project :)

#41 Gani on 09.12.10 at 4:40 am

just incredible!! it works great and the asp.net page saves the file correctly. by the way, i couldn’t figure out how to navigate to that page after the upload is complete. i tried some navigateToURL stuff but the page won’t move anywhere.

#42 Asa Williams on 09.21.10 at 12:29 pm

Like James, I would also like to know how to get the progress of the upload. I have tried using the progressEvent, but it only fires at the very end when it is complete.

#43 Aldo Marsilio on 11.01.10 at 7:11 am

Like James and Asa, I would also like to know how to get the progress of the upload.

#44 Andre Mariano on 04.11.11 at 2:21 pm

Well, When I try to use the class it shows 31 erros.. among then, this one is shown a lot

“1012: The static attribute may be used only on definitions inside a class. ”

but everything seems ok

#45 bill on 05.24.11 at 10:23 am

Who can tell me?Code how to write in php ?

#46 Chris Fulton on 06.15.11 at 3:17 pm

Here is a prebuilt grabber that might be a helpful starting point for your project.

http://www.masonqm.com/articles/Poster-Frame-Generator-0-02-Released-125.html

#47 AS3, Flex, Flash, PureMVC, Games » Upload snapshot image from flash to server on 01.20.12 at 7:25 am

[...] ow-to-take-a-snapshot-of-a-flash-movie-and-automatically-upload-the-jpg-to-a-server-in-three-easy-st… [...]

#48 Flash Games Download» Blog Archive » Upload snapshot image from flash to server on 01.22.12 at 4:10 am

[...] ow-to-take-a-snapshot-of-a-flash-movie-and-automatically-upload-the-jpg-to-a-server-in-three-easy-st… [...]

#49 Dave on 03.06.12 at 8:54 pm

This is BRILLIANT. Thanks so much, you saved me countless hours on my latest project. Awesome job with the uploadPostHelper.

Keep up the good work!

#50 Jason on 03.07.12 at 1:53 pm

Any idea why this might be creating a jpg of a black square? The dimensions are correct, there’s just no image there.

#51 flash 截图图片上传的表单 代码 | 到达梦想 on 03.30.12 at 9:46 am

[...] and return ByteArray post data suitable for a UrlRequest as output      *      * @see http://marstonstudio.com/?p=36      * @see http://www.w3.org/TR/html4/interact/forms.html      * [...]

#52 Joel on 11.18.12 at 12:39 pm

Hi, I am using this UploadPostHelper Class to upload image to my Google App Engine application, the image was successfully uploaded to GAE server, but it returned the following error after the image was successfully uploaded:
=======================
[IOErrorEvent type="ioError" bubbles=false cancelable=false eventPhase=2 text="Error #2032: Stream Error. URL: http://ponpon-organet.appspot.com/_ah/upload/AMmfu6Y8CkXLEZKgIG3fHdLQoQwjQuoUTkf-3xgk4ysIb6f8rBgOQ92Ao9_xRB2gJvxHOzVwwZEdMjmcI9B4rOA8I-vE_gvrV8iYtcYgHlyQgAUSXN156b-BDTFzY_CRC6ZaY3OH6_-uN_wVxOvUBN6cRUXx1CBhkg/ALBNUaYAAAAAUKkACEkEMg3Ooe63TVXPsc8pZQe_M0pv/"]
=======================
Is it because of the UploadPostHelper Class didn’t provide the Field Name (e.g fieldName = file)?

#53 rob on 12.10.12 at 10:22 am

cant you just pass the name value pairs as a query string in the urlrequest and still pass the bytearray as the main data
eg
var saveJPG:URLRequest = new URLRequest (“save5.php?var0ne=”+somevar+”&varTwo=”+somevar2)

#54 Louis on 01.09.13 at 8:52 pm

Hey,

Impressive stuff.. In your particular case, looks like you indeed needed to go that low-level.. However, if anyone else like me lands here looking on how to upload a file with Adobe AIR/Flex, its a lot easier to use:

File.upload(req:URLRequest)
you can still pass extra arguments to the request and the advantage is that you don’t have to load the whole bytearray in memory before sending..

Leave a Comment