Saving An Image To HD With ActionScript Part 1

Since Flash Player 10, we have been able to save files directly to the users system (you could do it before, but annoyingly you had to send the file to the server and then get the server to send it back to the user – a real pain!).

Now, it couldn’t be simpler:

var file:FileReference = new FileReference();
file.save(myData, "myFileName.ext");

Where myFileName.ext is the default file name and myData is the object to be saved. myData can be either a string, xml, a byteArray or any object with a toString() method.

When save(…) is called, a file browser appears, allowing the user to select a location for the file and optionally change the file name from the default. To determine what happens, you can listen to the FileReference object for Event.COMPLETE (user clicked save in file browser and file has successfully been saved) and Event.CANCEL (user clicked cancel in the file browser).

For all types except byteArray, the saved file will be a UTF-8 text file (containing either the string, formatted xml or the result of the passed objects’ toString() method). For a byteArray, the resulting file is in binary format. For instance, this code will save a text file containing the current date (ie: new Date().toString() ):

var file:FileReference = new FileReference();
file.save(new Date(), "myFile.txt");

The Security Gotcha

If you run the code above, it won’t work. That’s because the save(…) method has a built-in security ‘feature’ (or royal pain in the arse depending on your point of view) – basically, save(…) can only be called from code that is running in response to a user event (such as a mouse click). So you can’t call save(…) from an ENTER_FRAME function, or in response to a TimerEvent etc. It seems a bit over the top to me – the user gets a file browser popup so it’s not as if save(…) can save/overwrite files behind the scenes without the users’ help. Anyway, that’s the way it is, so that’s what we have to deal with.

Usually, you’d have a save button for the user anyway so you can just call save(…) from within the MouseEvent.CLICK handler and it will work just fine; but you can’t do things like automatically save a log file.

Saving Images

Using a byteArray allows us to save to any file format we wish (assuming we know the specification of that file format) but for the purposes of this post, we’ll stick to images. The as3corelib AS3 code library includes handy classes for converting bitmapData objects to byteArrays that are formatted as either jpeg or png so we’ll use those (they are also included as part of the Flex SDK) – there are plenty of handy classes in the library so check out the link for more details on whats included (the relevant files are included in the source code zip below).

The as3corelib classes are called JPEGEncoder and PNGEncoder and using them is simple. For the jpeg encoder, we just create an instance of it (passing the quality as the argument) and then pass some bitmapData to the encode(…) method. The result is a byteArray containing a jpeg formatted image:

var encoder:JPEGEncoder = new JPEGEncoder(80);
var byteArray:ByteArray = encoder.encode(myBitmapData);

For pngs, it’s even less code. For PNGEncoder, the encode(…) method is static so we call the method directly on the class:

var byteArray:ByteArray = PNGEncoder.encode(myBitmapData);

Now we can pass the byteArray to the save(…) method and bingo! Here’s a fully working example that creates a random bitmap and saves it as a jpeg at 80% quality:

{SavingImagesExample1}

package
{
	import com.adobe.images.JPGEncoder;
	import com.adobe.images.PNGEncoder;

	import fl.controls.Button;

	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.MouseEvent;
	import flash.events.ProgressEvent;
	import flash.geom.Rectangle;
	import flash.net.FileReference;
	import flash.utils.ByteArray;

	[SWF(backgroundColor="#F0F0F0", frameRate="30", width="400", height="400")]
	public class SavingImagesExample1 extends Sprite
	{
		private var bitmap:Bitmap;
		private var file:FileReference;
		private var ba:ByteArray;
		private var btnSave:Button;

		public function SavingImagesExample1()
		{
			//create a bitmap image to save
			buildBitmap();
			//add a button to allow user to initiate saving
			buildControls();
			//setup FileReference object
			buildFileRef();
		}

		private function buildBitmap():void
		{
			//create a 300x300 bitmap
			bitmap = new Bitmap(new BitmapData(300, 300, false, 0x000000));
			bitmap.x = 50;
			bitmap.y = 50;
			//fill bitmap with random coloured squares
			var rect:Rectangle = new Rectangle();
			for (var i:uint=0;i<100;i++)
			{
				rect.x = Math.random()*300;
				rect.y = Math.random()*300;
				rect.width = 10+Math.random()*30;
				rect.height = 10+Math.random()*30;
				bitmap.bitmapData.fillRect(rect, Math.random()*0xFFFFFF);
			}
			//add bitmap to stage
			addChild(bitmap);
		}

		private function buildControls():void
		{
			//add save button
			btnSave = new Button();
			btnSave.label = "Save Bitmap";
			btnSave.x = 100;
			btnSave.y = 15;
			btnSave.width = 200;
			btnSave.height = 20;
			btnSave.addEventListener(MouseEvent.CLICK, clickHandler);
			addChild(btnSave);
		}

		private function buildFileRef():void
		{
			file = new FileReference();
			//add all available listeners
			file.addEventListener(Event.OPEN, fileEventHandler);
			file.addEventListener(ProgressEvent.PROGRESS, fileEventHandler);
			file.addEventListener(Event.COMPLETE, fileEventHandler);
			file.addEventListener(Event.CANCEL, fileEventHandler);
			file.addEventListener(IOErrorEvent.IO_ERROR, fileEventHandler);
		}

		private function clickHandler(event:MouseEvent):void
		{
			//convert to JPG
			var encoder:JPGEncoder = new JPGEncoder(80);
			ba = encoder.encode(bitmap.bitmapData);
			//save resulting byteArray
			saveImage();
		}

		private function fileEventHandler(event:Event):void
		{
			trace(event.toString());
		}

		private function saveImage():void
		{
			//save byteArray
			file.save(ba, "myImage.jpg");
		}

	}
}

To save as a png instead, we just remove the first two lines of clickHandler(…) and replace them with ba = PNGEncoder.encode(bitmap.bitmapData); and secondly, change the default file name in saveImage(…) to (eg) “myImage.png”.

This example uses a generated bitmap but there’s no reason why you couldn’t take a snapshot of any part of your swf and save that as an image (hint: use bitmapData.draw(…) as i describe in BitmapData Basics Part 1).

If you start experimenting with bitmap sizes or have a slow computer, you’ll notice that it can take a few seconds for the encode(…) methods to run (png is usually quicker than jpg) and the swf ‘hangs’ while this happens because all the processing takes place within the space of one frame. In part 2, i will show you how i tweaked the as3corelib classes to get around that problem.

In the meantime, happy new year!

SOURCE CODE: https://github.com/LeeBurrows/blog-source-code

10 thoughts on “Saving An Image To HD With ActionScript Part 1

  1. Pingback: Lee Burrows » Saving An Image To HD With ActionScript Part 1 | Flash Designers

  2. Rasyid

    I have successfully do that method, but I have a problem to load it from the saved file back to JPG. Any idea?

    Reply
  3. Jxyiliu

    The PNGEncoder.encode function call is very slow on Mobile devices. Most of time is used on for loop. How can it be improved?

    Thanks

    Reply
    1. Lee Post author

      hi Jxyiliu

      There’s no easy answer – mobile devices are a lot slower than desktop pc’s, so any long-running code is going to be notably slower.

      If you need more speed, you’ll need to use Native Extensions (either create one yourself or pray that someone else has done the hard work already).

      Alternatively, starting with Flash Player 11.3, the BitmapData class will have a built-in encoding method (see docs) that uses the operating system to do the encoding (so should be a lot faster).

      Reply
  4. Iqbal Adiyat

    hi Lee.

    currently i make a simple photo editing app for my thesis. i use this PNGencoder to save the image i load from local drive. but, the image i could save from app is the original one, not the edited one. can u plz help me resolve it? thx so much :)

    Reply
  5. Nucky Delgado

    Soo much useful information, thank you very much man!!

    I haven’t had the time to test the code, so could you tell me if the user will be able to give a custom name to the file in the browser, or should I request a string input to do it so before calling the file.save() method?

    Nucky, UK

    Reply
  6. Lee Post author

    @Nucky Delgado

    Yes Nucky, user can enter a new file name in the file browser; however, there is a bug/feature…

    In SavingImagesExample1 above, click on “save bitmap” and leave the file name as it is (“myImage”) – this will save the file as “myImage.jpg”. But, if you amend the file name, you also need to include the file extension – eg, “myImage1.jpg” not “myImage1″

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>