One of the great new features in the Flash 10 plugin is the option to read and write files locally between the browser and OS without involving a server side solution. This can come in extremely handy when you are trying to take processing load off the server and leverage the client's computer.
A perfect example is creating thumbnails. There are many instances where a site allows a user to upload an image and then save that along with some additional data on the site's server. When the image gets saved often a thumbnail and medium size version of the image get created. This means the server takes a few big hits: having the file upload to the server and then writing it and also creating and writing the smaller versions of the image. A jpeg from a decent digital camera can easily be 3, 4, 5+ megabytes. For most people there's going to be a wait in getting that file uploaded to the server and in most cases the site will likely never server that full size image up again. What if you could drastically cut the upload time and the amount of work the server needed to do to create thumbnails?
With Flash 10 it's now possible. Using the FileReference.load() method you can allow a user to select a file locally and load it directly into the Flash plugin without having to upload it to a server. Once the file has been loaded into Flash you can use the Bitmap and BitmapData classes to manipulate the file into a more manageable and usable size. For instance, I loaded a 2.9MB jpeg and converted it to a 125x93 jpeg and a 600x400 jpeg. The small size ended up being 8KB and the medium size was 60KB. Now I'm uploading a total of 68KB to the server instead of 2.9MB ... wow.
Here's the sample Flex code used to make this work. Nothing to special. The FileReference.browse() and FileReference.load() methods are used to select and load the image into the browser. Once the image has been loaded and you need to save it a few things need to happen. First the image data needs to get put into BitmapData and then encoded by the JPEGEncoder so it can be saved as a jpeg on the server. The final step is to base64 encode the image data before sending it down to the server. There is a way you can do this without doing the base 64 encoding but you can't send any additional parameters in the POST if you do. I think this is a much cleaner solution.
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute" horizontalAlign="left"
height="250" width="250"
creationComplete="init();">
<mx:Script>
<![CDATA[
import flash.utils.setTimeout;
import com.dgrigg.utils.Logger;
import mx.graphics.codec.JPEGEncoder;
import mx.utils.Base64Encoder;
import flash.net.FileReference;
import flash.net.FileFilter;
import mx.controls.Alert;
private var file:FileReference;
private function init():void
{
file = new FileReference();
file.addEventListener(Event.SELECT, handleFileSelect);
file.addEventListener(Event.COMPLETE, handleFileComplete);
}
private function selectFile(event:Event):void
{
var filter:Array = [new FileFilter("Images", "*.jpg;*.jpeg;*.png")];
file.browse(filter);
}
private function handleFileSelect(event:Event):void
{
file.load();
}
private function handleFileComplete(event:Event):void
{
image.source = file.data;
saveBtn.enabled = true;
}
private function saveFile(event:Event):void
{
//prepare the thumb image data for saving
var bmpData:BitmapData = new BitmapData(image.contentWidth, image.contentHeight);
bmpData.draw(image);
var jpg:JPEGEncoder = new JPEGEncoder(80);
var ba:ByteArray = jpg.encode(bmpData);
var base64:Base64Encoder = new Base64Encoder();
base64.encodeBytes(ba);
//save it
var loader:URLLoader = new URLLoader();
var request:URLRequest = new URLRequest("http://local.test.com/saveThumbnail.php");
request.method = URLRequestMethod.POST;
var data:URLVariables = new URLVariables();
data.fileData = base64;
data.fileName = "thumb_" + file.name;
request.data = data;
loader.addEventListener(Event.COMPLETE, handleImageSaved);
loader.load(request);
}
private function handleImageSaved(event:Event):void
{
var loader:URLLoader = event.target as URLLoader;
Alert.show("Thumbnail saved");
}
]]>
</mx:Script>
<mx:Button id="selectBtn" x="5" y="5"
label="Select Image" click="selectFile(event);"/>
<mx:Canvas x="5" y="35" width="129" height="129"
borderStyle="solid" borderColor="0x000000">
<mx:Image id="image" x="1" y="1" width="125" height="125"/>
</mx:Canvas>
<mx:Button id="saveBtn" x="5" y="175"
label="Save Thumbnail" click="saveFile(event);" enabled="false"/>
</mx:Application>
And here is the PHP code, super easy just read the POST variables to get the file data and file name, since the data was already base64 encoded you can put the file directly on the server without having to do a file open and file write.
<?
$file = base64_decode($_POST['fileData']);
file_put_contents($_POST['fileName'], $file);
echo "saved";
?> And if you are using Coldfusion, here's the snippet you need.
<cffile action="write"
file="#GetDirectoryFromPath(GetCurrentTemplatePath())#/cf_#FORM.filename#"
output="#ToBinary(FORM.filedata)#"/> Enjoy, your server will thank me.
Derrick Grigg is a Rich Internet Application (RIA) freelance contractor based in Toronto, Canada. He specializes in architecting and developing applications using a variety of technologies, most notably Flash, Flex and Coldfusion.
Jul 14, 2009 at 8:35 PM
This look very similar to a demo I made several months ago. But there is really no need to send back to PHP to save the file as you can also save locally with flash 10 meaning that Flash can resize and re-save your image with no need for a webserver: http://blog.kukiel.net/2009/02/file-manipulation-client-side-with.html
Jul 15, 2009 at 12:40 AM
Some similar code also works on flash player 9. Here is a blogpost about it
http://blog.arnomanders.nl/index.php/archives/how-to-save-files-from-flex-to-your-server-with-php/
Jul 15, 2009 at 8:45 AM
Paul, you are correct it could just be saved locally, however I was showing how you can take a huge load off the server by processing the image on the client and then saving the small version on the server, to be used later by the server for whatever purposes.
Anro, very similar however Flash 9 doesn't let you load local images from the user's OS, your example is useful only for graphics etc that have been drawn out using the graphics api or captured via webcam. I like the check on the server side for file size. You can do a client side check also, but it's always a good idea to validate everything on the server to, especially with a POST since anyone could potentially fire anything to the page and try to upload it.
Jul 15, 2009 at 9:13 AM
@Derrik Ahh yes your quite correct. My original idea was the same as yours. This could be applied to say FaceBooks photo uploads section. They resize all our images I can imagine this method saving millions or processor cycles for a large site. Did you happen to test if this now works in OSX and Linux as last time I tried I received errors.
Jul 16, 2009 at 9:46 AM
Hey Paul, I'm running it on OSX and it works perfect.
Dec 05, 2009 at 12:02 AM
Thanks again, this is just what I needed!
Feb 28, 2010 at 11:01 AM
Thank Derrick for the tutorial.
I am trying to do the same thing, select an image, load it, modify it and then save it on the server.
I like your solution but the drawback is that there is no progression event with URLLoader, as opposed to fileReference.
However, fileReference.data is read only ! I have been trying to override the fileReference.load function to be able to load another image again but so far no luck.
Do you think we could so something similar to what you did in your post 'Making a read only property bindable in Flex' ?
Thanks
Mar 03, 2010 at 6:46 AM
@ Yann,
Unfortunately I don't think there is any way to write data into the FileReference class (much like not being able to write file data into a multi-part form in html) due to security restrictions. In my experience, using the method I outlined in the post works well, you lose the progress information but you can use the ProgressBar with an indeterminate setting to at least show the user something is happening.
Derrick