So I finally got this working. What I wanted to do was to add a "Save As Image" option to charts and grids which are part of a client’s reporting suite that is being developed as an online Flex application; the idea being that this would work just like it does with images in a browser’s HTML pages, allowing the user to save an image to the desktop or anywhere else on the local filesystem.
Getting the image data is easy - the new ImageSnapshot class in Flex 3 allows you to easily take a Bitmap "snapshot" of any DisplayObject and keep it as a ByteArray. However, getting the captured image back to the user is a little trickier. Flash Player 9 and below has a security sandbox model that disallows your Flex application from sending a file straight back through the browser, so what we have to do is "reflect" the image data off a server page - in other words, get our user’s browser to handle a new request that uploads the image data back to the server and then downloads that exact same data again, with the right headers, back in the browser client.
On the Flex side, I wrote up a little class which handles all the mechanics of taking the snapshot and launching the "reflected" page:
package com.symmetri.Tools
{
import flash.display.IBitmapDrawable;
import flash.events.*;
import flash.net.*;
import flash.net.navigateToURL;
import flash.utils.ByteArray;
import mx.controls.*;
import mx.core.*;
import mx.graphics.ImageSnapshot;
import mx.graphics.codec.JPEGEncoder;
import mx.utils.*;
public class ImageExporter
{
private var _reflectUrl:String;
private var _snapshot:ImageSnapshot;
private var _rawData:ByteArray;
public function ImageExporter(reflectUrl:String)
{
_reflectUrl = reflectUrl;
}
// take a snapshot of a UI component as a JPG image
// pass dpi of zero to take it at native screen resolution
public function snapshotObjectAsJPGByteArray(source:IBitmapDrawable, dpi:Number = 0):ByteArray
{
// take the snapshot
_snapshot = ImageSnapshot.captureImage(source, dpi, new JPEGEncoder());
return (_snapshot.data as ByteArray);
}
// take a snapshot of a UI component as a JPG image
// and send it to the browser as a download file
// pass dpi of zero to take it at native screen resolution
public function downloadObjectAsJPG(source:IBitmapDrawable, dpi:Number = 0):void
{
_rawData = this.snapshotObjectAsJPGByteArray(source, dpi);
var _request:URLRequest = new URLRequest(_reflectUrl);
_request.method = URLRequestMethod.POST;
_request.contentType = “application/octet-stream“;
_request.data = _rawData;
navigateToURL(_request, “_self“);
}
}
}
On the server side, we have a blank ASPX page with the following code-behind (C#):
using System;
using System.Collections;
using System.Configuration;
using System.Data;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.IO;
public partial class ReflectJPG : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
// method “attachment” or “inline”
string method = (Request.QueryString[“method“] == null ? “attachment“: Request.QueryString[“method“].ToString());
// filename
string filename = (Request.QueryString[“filename“] == null ? “image.jpg“ : Request.QueryString[“filename“].ToString());
byte[] data = readPostedFile();
Response.ContentType = “image/jpg“;
Response.AddHeader(“Content-Length“, data.Length.ToString());
Response.AddHeader(“Content-disposition“, method + “; filename=“ + filename);
Response.BinaryWrite(data);
Response.End();
}
// read the posted file
private byte[] readPostedFile()
{
if (Request.ContentLength > 0)
{
byte[] buffer = new byte[Request.ContentLength];
using (BinaryReader br = new BinaryReader(Request.InputStream))
br.Read(buffer, 0, buffer.Length);
return buffer;
}
else
{
return null;
}
}
}
Finally, to use the ImageExporter class in Flex, I use the following pattern to download a JPG snapshot of the component viewReport:
var exporter:ImageExporter = new ImageExporter(”http://www.symmetri.com/ReflectJPG.aspx”);
exporter.downloadObjectAsJPG(viewReport);