I’ve been creating a lot of AS3 generated graphics over at Art From Code and have faced the problem of how to get the generated bitmaps (or vectors) out to files. For a lot of the files, they are quick experiments and I do a quick screenshot with Jing. For others, I’ve created AIR apps that let you save out files to the file system. But it dawned on me the other day, that in Flash 10, the FileReference class has a save method.
By following the instructions here, I got Flex builder set up to compile Flash 10 movies. By including the framework swc in my project I get access to the JPEGEncoder and PNGEncoder classes. These take a BitmapData and return a ByteArray. You can then pass that ByteArray to FileReference.save() to save the encoded JPG / PNG to local disk, even from a plain old SWF.
I figure I’ll be using this functionality a lot to save BitmapDatas, so I created a new class called SavingBitmap which allows you to save the with a single method call. Here’s the class:
[as]package com.bit101
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.net.FileReference;
import flash.utils.ByteArray;
import mx.graphics.codec.IImageEncoder;
import mx.graphics.codec.JPEGEncoder;
import mx.graphics.codec.PNGEncoder;
public class SavingBitmap extends Bitmap
{
private var _fileRef:FileReference;
private var _encoder:IImageEncoder;
private var _jpegQuality:Number = 80;
private var _encoderType:String = JPEG;
public static const JPEG:String = “jpeg”;
public static const PNG:String = “png”;
public function SavingBitmap(bitmapData:BitmapData=null, pixelSnapping:String=“auto”, smoothing:Boolean=false)
{
super(bitmapData, pixelSnapping, smoothing);
_fileRef = new FileReference();
_encoder = new JPEGEncoder(_jpegQuality);
}
public function set jpegQuality(value:Number):void
{
_jpegQuality = value;
if(_encoder is JPEGEncoder)
{
_encoder = new JPEGEncoder(_jpegQuality);
}
}
public function get jpegQuality():Number
{
return _jpegQuality;
}
public function set encoderType(value:String):void
{
_encoderType = value;
if(_encoderType == JPEG)
{
_encoder = new JPEGEncoder(_jpegQuality);
}
else
{
_encoder = new PNGEncoder();
}
}
public function get encoderType():String
{
return _encoderType;
}
public function save(defaultFileName:String = null):void
{
var ba:ByteArray = _encoder.encode(bitmapData);
_fileRef.save(ba, defaultFileName);
ba.clear();
}
}
}[/as]
As you can see, the class extends Bitmap, so you can use it just like any other Bitmap. You create a usual BitmapData and instead of wrapping it in a Bitmap, you wrap it in a SavingBitmap. When you are ready to save it, you just call the SavingBitmap’s save() method. This encodes the BitmapData to either a PNG or JPG and sends it to the FileReference.save method. A dialog will open up for the user to select a place to save the file and it will be saved. There are other methods to choose what encoder to use and the JPG quality.
Here’s an example of it in use. A really rough example, but it shows that it works. In case you weren’t following along, Flash 10 player is required:
[kml_flashembed movie=“https://www.bit-101.com/2003/wp-content/uploads/2008/08/saving2.swf” height=“640″ width=“620″ /]
Draw on the white canvas then click save to save it. Open the saved file to see that it is what you drew. Here’s the code that went into making that:
[as]package
{
import com.bit101.SavingBitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
public class Saving2 extends Sprite
{
private var _bmp:SavingBitmap;
private var _color:uint;
private var _bmpd:BitmapData;
public function Saving2()
{
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
_bmpd = new BitmapData(600, 600, false, 0xffffff);
_bmp = new SavingBitmap(_bmpd);
_bmp.x = 10;
_bmp.y = 10;
addChild(_bmp);
stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
var saveBtn:TextField = new TextField();
saveBtn.text = “Save”;
saveBtn.selectable = false;
saveBtn.background = true;
saveBtn.border = true;
saveBtn.autoSize = TextFieldAutoSize.LEFT;
saveBtn.height = saveBtn.textHeight;
addChild(saveBtn);
saveBtn.x = 280;
saveBtn.y = 620;
saveBtn.addEventListener(MouseEvent.CLICK, onClick);
}
private function onClick(event:MouseEvent):void
{
_bmp.save(“drawing.jpg”);
}
private function onMouseDown(event:MouseEvent):void
{
_color = Math.random() * 0xffffff;
addEventListener(Event.ENTER_FRAME, onEnterFrame);
stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
}
private function onEnterFrame(event:Event):void
{
for(var i:int = 0; i < 100; i++) { _bmpd.setPixel(_bmp.mouseX + Math.random() * 50 - 25, _bmp.mouseY + Math.random() * 50 - 25, _color); } } private function onMouseUp(event:MouseEvent):void { removeEventListener(Event.ENTER_FRAME, onEnterFrame); stage.removeEventListener(MouseEvent.MOUSE_UP, onMouseUp); } } }[/as] The whole saving functionality is encapsulated in this one method that gets called when you click the save button: [as]private function onClick(event:MouseEvent):void { _bmp.save(“drawing.jpg”); }[/as] Pretty damn easy. One caveat is that the saving MUST happen on a button click / mouse event. Otherwise you’ll get a security error. Another caveat is that the Flash 10 compiling in Flex Builder is pretty rough still. It isn’t the easiest thing to get set up, and as of this writing, a LOT of the code hinting is missing. Specifically, you won’t get code hinting for BitmapData and you’ll have to add the import statement manually. This happens for some other stuff as well. This had me convinced that it wasn’t working at all, but once you add the imports and write the code, FB recognizes it as correct and it compiles just fine. One final caveat is that once you call save, there is a lag while the encoding happens. You might want to try to sneak in some kind of user message there, that the encoding is happening. This might be a bit rough though because anything you try to display won’t show up until the method completes. And if you try to delay the encoding, then the FileReference.save method won’t be being called directly from the mouse click and will fail… So, you might want to amend the class to separate the encoding and the saving. But the above is all I need for my own purposes of personal use. Thought I’d share it.