Adobe
Products
Acrobat
Creative Cloud
Creative Suite
Digital Marketing Suite
Digital Publishing Suite
Elements
Photoshop
Touch Apps
Student and Teacher Editions
More products
Solutions
Creative tools for business
Digital marketing
Digital media
Education
Financial services
Government
Web Experience Management
More solutions
Learning Help Downloads Company
Buy
Home use for personal and home office
Education for students, educators, and staff
Business for small and medium businesses
Licensing programs for businesses, schools, and government
Special offers
Search
 
Info Sign in
Welcome,
My cart
My orders My Adobe
My Adobe
My orders
My information
My preferences
My products and services
Sign out
Why sign in? Sign in to manage your account and access trial downloads, product extensions, community areas, and more.
Adobe
Products Sections Buy   Search  
Solutions Company
Help Learning
Sign in Sign out My orders My Adobe
Preorder Estimated Availability Date. Your credit card will not be charged until the product is shipped. Estimated availability date is subject to change. Preorder Estimated Availability Date. Your credit card will not be charged until the product is ready to download. Estimated availability date is subject to change.
Qty:
Purchase requires verification of academic eligibility
Subtotal
Review and Checkout
Adobe Developer Connection / Flash Developer Center /

Saving Flash graphics as image files at runtime

by Clive Goodinson

Clive Goodinson
  • goodinson.com

Modified

29 September 2008

Page tools

Share on Facebook
Share on Twitter
Share on LinkedIn
Bookmark
Print
ActionScript Flash Professional CS3

Requirements

Prerequisite knowledge

Prior experience with server-side scripting is required. Basic familiarity with the Flash authoring environment is also helpful.

User Level

Intermediate

Required products

  • Flash Professional CS3 (Download trial)
  • Flash Player

Sample files

  • snapshot_class_as3.zip (781 KB)

Additional Requirements

Server supporting PHP 4 or later

The sample Snapshot Explorer application relies on a very simple PHP script. If you wish, you may translate it into another server-side scripting language such as ColdFusion or .NET.

Have you ever wanted to dynamically convert graphics into static image files at runtime, either for rapid display on your website or to allow your site visitors to download and save images to their computers? This tutorial describes the process of creating the Snapshot class, a custom ActionScript 3.0 class that accomplishes this goal.

For example, imagine that you're developing a drawing program that visitors to your web page can use to compose their own sketches online. Finished drawings are published in a gallery on your website, displayed as JPEG images embedded in HTML.

This project came to life when I developed the website Pixton, which features user-generated comics. Using our Flash editor, you can compose scenes with characters, props, and dialog. But Flash is needed only during the creation of a scene. Once the scene has been created, it doesn't make sense to reconstruct it in Flash every time it's viewed. We realized the more elegant solution would involve taking a snapshot of the scene and then saving it on the server as a JPEG file.

Note: After testing, we decided to save images as JPEGs rather than GIFs because we encountered color-shifting and dithering issues when saving Flash graphics to the GIF file format.

Getting started with the Snapshot Explorer

The Snapshot Explorer converts graphics generated at runtime into either JPEG or PNG image files (see screen shot in Figure 1). To test the live example, click the image to open the Snapshot Explorer. Then type some text in the box on the left, choose your options, and click Capture.

Snapshot Explorer

Figure 1. Snapshot Explorer (click to launch)

To see the Snapshot Explorer in action, choose PNG as the image format and Prompt to save as the action. When you click Capture, you're prompted to open or save a copy of the PNG file, which is a snapshot of the rounded rectangular area in the SWF.

If desired, try using the other options to load the snapshot image file into the Flash movie or display it in a new browser window. Now that you have a good understanding of what the Snapshot Explorer does, let's take a look under the hood and see how it works.

Examining the Snapshot class

In this section we'll analyze the project files and look at how the Snapshot Explorer is structured. If you haven't already, be sure to download the provided sample files and uncompress the ZIP so that you can review the contents.

The sample files consist of:

  • snapshot.flp: You can open this file in Flash CS3 for easy access to the other files.
  • snapshot-example.fla, snapshot-example.swf, Main.as: These files define the user interface of the Snapshot Explorer.
  • Snapshot.as: This is the ActionScript class file where the magic happens.

The Snapshot class depends on two public-domain classes that do not ship with Flash:

  • as3corelib: Download this package from as3corelib on Google Code. Copy the folder src/com to the classpath of your choice. I copied it to my default classpath, namely $(AppConfig)/ActionScript 3.0/Classes. You could also add a new global classpath in Flash by choosing Edit > Preferences > ActionScript > ActionScript 3.0 Settings. The Snapshot class requires these specific classes: com.adobe.images.PNGEncoder and com.adobe.images.JPGEncoder.
  • Base64: Download this class from Base64 on Dynamic Flash and copy the class to the classpath of your choice. The Base64 class facilitates the conversion from the binary format of ByteArray into a plain-text string, to make it easy for the Snapshot Explorer to transmit the data to the server via a POST variable.

The user interface

First, let's examine snapshot-example.fla and Main.as. The user interface includes the bare essentials:

  • An editable area the user can customize. In this example, the input text field is placed within a rounded rectangle. The containing movie clip has been scaled and rotated.
  • Radio buttons allow the user to choose the image format to generate, and specify how to save or display the snapshot image.
  • The Capture button triggers the event to make the Snapshot Explorer function, based on the selected radio button options.

Open the Actions panel to review the code of Main.as. The script begins by importing the required classes. The code automatically declares the Stage instances, so we just need to declare the two RadioButtonGroup instances we'll use to organize the radio buttons interface options into two groups (grpAction and grpFormat):

package { import com.goodinson.snapshot.*; import flash.display.*; import flash.events.*; import fl.controls.*; public class Main extends Sprite { private var grpAction:RadioButtonGroup; private var grpFormat:RadioButtonGroup;

In the Main() constructor function, you'll see that we start by hiding the interface. We use the ADDED_TO_STAGE event to trigger initialization of the interface because the radio buttons won't be ready to initialize during the constructor. When onReady() is triggered, we can be sure that all of the interface components have been fully loaded and then we can make the interface visible. As far as the user is concerned, this all happens instantaneously; but it's very important to always check that the data is loaded and ready before attempting to display it to ensure that everything will work as expected. Here's the constructor code:

function Main() { // hide UI while initializing visible = false; addEventListener(Event.ADDED_TO_STAGE, onReady); }

In the onReady() method, we initialize the radio button groups, set a CLICK action for the Capture button, and set the gateway URL for the Snapshot class. The gateway URL is the path to your server-side script that processes the image data sent from Flash. I'll discuss the gateway URL in more detail further.

private function onReady(evt:Event):void { // UI has been initialized visible = true; removeEventListener(Event.ADDED_TO_STAGE, onReady); // create radio button groups grpAction = new RadioButtonGroup("grpAction"); grpFormat = new RadioButtonGroup("grpFormat"); // initialize radio buttons optJPG.group = grpFormat; optJPG.value = Snapshot.JPG; optPNG.group = grpFormat; optPNG.value = Snapshot.PNG; grpFormat.selection = optJPG; optDisplay.group = grpAction; optDisplay.value = Snapshot.DISPLAY; optPrompt.group = grpAction; optPrompt.value = Snapshot.PROMPT; optLoad.group = grpAction; optLoad.value = Snapshot.LOAD; grpAction.selection = optLoad; // set the required Snapshot URL Snapshot.gateway = "http://localhost/_e/snapshot/snapshot.php"; btnCapture.addEventListener(MouseEvent.CLICK, onCapture); }

Once the interface is initialized, the user can type text into the input text field, choose their options and, when they're ready, click Capture. This triggers the onCapture() method of Main, which subsequently calls the capture method of the Snapshot class. That's basically all there is to snapshot-example.fla and Main.as.

The Snapshot class

Now let's turn our attention to the Snapshot class, which contains the logic and functionality that makes the Snapshot Explorer run. Inside the sample files, navigate to the snapshot folder and then open Snapshot.as in Flash CS3. The first bit of code in the Snapshot class imports all the dependent classes needed for this project:

package com.goodinson.snapshot { import com.goodinson.snapshot.*; import com.adobe.images.*; import com.dynamicflash.util.Base64; import flash.display.*; import flash.geom.*; import flash.net.*; import flash.events.*; import flash.utils.ByteArray;

After importing the dependent files, the next step is to declare some constants. The public constants correspond to the options accessed in Main.as; the private constants define values that you might want to change in order to tweak Snapshot's output. The public static variable gateway contains the path to the server-side script, which receives and processes the image data sent from Flash. The variable is assigned a value in Main.as rather than declared as a hard-coded constant in Snapshot.as, because you might want to reuse the Snapshot class later with different server-side contexts. Here's the part of the code that declares the constants:

public class Snapshot { // supported image file types public static const JPG:String = "jpg"; public static const PNG:String = "png"; // supported server-side actions public static const DISPLAY:String = "display"; public static const PROMPT:String = "prompt"; public static const LOAD:String = "load"; // default parameters private static const JPG_QUALITY_DEFAULT:uint = 80; private static const PIXEL_BUFFER:uint = 1; private static const DEFAULT_FILE_NAME:String = 'snapshot'; // path to server-side script public static var gateway:String; public static function capture(target:DisplayObject, options:Object):void {

The entry point into the Snapshot class is the capture method. This method is called with two arguments: a target DisplayObject (the display object to capture as a static image) and an options Object which tells the class which image format to generate, what to do with it and, if applicable, indicates the Loader instance to load the resulting JPEG or PNG. Here's the beginning of the capture method:

public static function capture(target:DisplayObject, options:Object):void {

The first step is to invoke the bounding rectangle of the DisplayObject to capture. We calculate the rectangle's coordinates relative to the target's parent in order to capture the rotation, scaling, and offset of the target.

Note: If you try to get the rectangle's coordinates with respect to the target itself (by setting the variable relative equal to target), you'll find that any transformations applied to the target are not captured.

So we use the following code to determine the coordinates of the area to be captured:

var relative:DisplayObject = target.parent; // get target bounding rectangle var rect:Rectangle = target.getBounds(relative);

Given the target's bounding coordinates, the next step is to create a BitmapData instance, where we'll store the captured pixels. A call to draw() renders the target into the BitmapData instance. Although we know the width and height of the area to capture, we also need to know its x- and y-offsets, otherwise we'd capture the graphics starting at (0,0). To see what would happen without setting these offsets, temporarily comment out the second argument to draw(), the Matrix instance. You'll find that the captured graphic depicts the target starting at (0,0) relative to the target's parent, and the lower and right-hand edges of the target get cut off.

While getBounds() returns a display object's bounds including any strokes, it doesn't always capture all of the pixels associated with the antialiasing of strokes. To compensate for this, we'll add a one-pixel buffer all the way around the target area to be captured. The Matrix instance, then, defines the correct transformation to capture the target DisplayObject in its entirety:

// capture within bounding rectangle; add a 1-pixel buffer around the perimeter to ensure that all anti-aliasing is included var bitmapData:BitmapData = new BitmapData(rect.width + PIXEL_BUFFER * 2, rect.height + PIXEL_BUFFER * 2); // capture the target into bitmapData bitmapData.draw(relative, new Matrix(1, 0, 0, 1, -rect.x + PIXEL_BUFFER, -rect.y + PIXEL_BUFFER));

The next step involves turning the BitmapData instance into a ByteArray. This is akin to converting a two-dimensional array into a one-dimensional array. It's the intermediary variable type we need in order to send the data to the server. Depending on the image format selected by the user, we use JPGEncoder or PNGEncoder. The implementation of these classes is slightly different. For JPGEncoder we need to first construct an instance of the class, passing the JPEG quality as an argument. Since compression isn't an option with the PNG format, the encode() method of PNGEncoder is static and so we call it without creating an instance.

Here's the code:

// encode image to ByteArray var byteArray:ByteArray; switch (options.format) { case JPG: // encode as JPG var jpgEncoder:JPGEncoder = new JPGEncoder(JPG_QUALITY_DEFAULT); byteArray = jpgEncoder.encode(bitmapData); break; case PNG: default: // encode as PNG byteArray = PNGEncoder.encode(bitmapData); break; }

The final step in preparing our captured image for server-side processing is to turn the ByteArray into a String. This is where the Base64 class I mentioned earlier comes in handy. A call to its static encodeByteArray() method turns our ByteArray into a single String, which can be sent along with any other data to the server as part of a set of POST variables:

data var byteArrayAsString:String = Base64.encodeByteArray(byteArray);

In the next part we'll prepare a URLRequest instance, which will send our data to the server. In order to preclude client-side caching, we'll add a random GET query string to the gateway URL. Along with the image data we'll send three other variables: the name of the image file to save (if applicable), the format of the image (JPEG or PNG), and a constant mapping to the action to be performed (Load into Flash, Prompt to save, or Open in browser).

What we do with the URLRequest instance also depends on the action selected by the user. If the Snapshot Explorer is opening the image file in a browser window, we have to call navigateToURL; otherwise, we use the load() method of the Loader instance that was placed in the user interface, like this:

// construct server-side URL to use to send image data var url:String = gateway + '?' + Math.random(); // determine name of file to be saved / displayed var fileName:String = DEFAULT_FILE_NAME + '.' + options.format; // create URL request var request:URLRequest = new URLRequest(url); // send data via POST method request.method = URLRequestMethod.POST; // set data to send var variables:URLVariables = new URLVariables(); variables.format = options.format; variables.action = options.action; variables.fileName = fileName; variables.image = byteArrayAsString; request.data = variables; if (options.action == LOAD) { // load image back into loadContainer options.loader.load(request); } else { navigateToURL(request, "_blank"); }

Server-side processing

The final step happens on the server. The script that processes the image data is quite simple. In this project we used PHP, but the script could just as easily be implemented using ColdFusion or any similar server-side scripting language.

First the script verifies the image format to determine what sort of HTTP header to output. Then, if the user selected the option to save the image to their computer, we add another header that instructs the browser to treat the script's output as a downloadable file. Otherwise, the browser loads the script directly as an image file. PHP's base64_decode() function is the inverse of Base64.encodeByteArray(), so all we need to do is decode the image data and send it directly to output:

<?php switch ($_POST["format"]) { case 'jpg': header('Content-Type: image/jpeg'); break; case 'png': default: header('Content-Type: image/png'); break; } if ($_POST['action'] == 'prompt') { header("Content-Disposition: attachment; filename=".$_POST['fileName']); } echo base64_decode($_POST["image"]); ?>

Where to go from here

The Snapshot class performs the basic function of capturing graphics generated in Flash at runtime, and generating a static image file that can be displayed in a browser window, saved to the user's machine or loaded into the SWF.

You can see this technique in action by creating a comic at pixton.com. Try it out. As you edit a scene, the visual data is constructed as Flash graphics. But as soon as you've saved a scene, the data is loaded back into Flash as a static JPEG file. Similarly, all of the comics published on the website consist of JPEGs, which makes for rapid retrieval and hopefully more chuckles per minute!

Creative Commons License
This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License

More Like This

  • Controlling the appearance of text elements with the Text Layout Framework
  • Using the Flash OSMF Media Player template
  • Preloading TLF runtime shared libraries in Flash Professional CS5.5
  • Getting started with the Adobe SiteCatalyst extension for Flash Professional CS5
  • Optimizing content for Apple iOS devices
  • Using screen orientation APIs for smartphone application development
  • Guide for Apple App Store submissions
  • Saving state in AIR applications for iOS devices
  • Creating ActionScript 3.0 components in Flash – Part 2: MenuBar component prototype
  • Exploring the new 3D features in Flash CS4 Professional

Flash User Forum

More
04/23/2012 Auto-Save and Auto-Recovery
04/23/2012 Open hyperlinks in new window/tab/pop-up ?
04/21/2012 PNG transparencies glitched
04/01/2010 Workaround for JSFL shape selection bug?

Flash Cookbooks

More
02/13/2012 Randomize an array
02/11/2012 How to create a Facebook fan page with Flash
02/08/2012 Digital Clock
01/18/2012 Recording webcam video & audio in a flv file on local drive

Products

  • Acrobat
  • Creative Cloud
  • Creative Suite
  • Digital Marketing Suite
  • Digital Publishing Suite
  • Elements
  • Mobile Apps
  • Photoshop
  • Touch Apps
  • Student and Teacher Editions

Solutions

  • Digital marketing
  • Digital media
  • Web Experience Management

Industries

  • Education
  • Financial services
  • Government

Help

  • Product help centers
  • Orders and returns
  • Downloading and installing
  • My Adobe

Learning

  • Adobe Developer Connection
  • Adobe TV
  • Training and certification
  • Forums
  • Design Center

Ways to buy

  • For personal and home office
  • For students, educators, and staff
  • For small and medium businesses
  • For businesses, schools, and government
  • Special offers

Downloads

  • Adobe Reader
  • Adobe Flash Player
  • Adobe AIR
  • Adobe Shockwave Player

Company

  • News room
  • Partner programs
  • Corporate social responsibility
  • Career opportunities
  • Investor Relations
  • Events
  • Legal
  • Security
  • Contact Adobe
Choose your region United States (Change)
Choose your region Close

North America

Europe, Middle East and Africa

Asia Pacific

  • Canada - English
  • Canada - Français
  • Latinoamérica
  • México
  • United States

South America

  • Brasil
  • Africa - English
  • Österreich - Deutsch
  • Belgium - English
  • Belgique - Français
  • België - Nederlands
  • България
  • Hrvatska
  • Česká republika
  • Danmark
  • Eastern Europe - English
  • Eesti
  • Suomi
  • France
  • Deutschland
  • Magyarország
  • Ireland
  • Israel - English
  • ישראל - עברית
  • Italia
  • Latvija
  • Lietuva
  • Luxembourg - Deutsch
  • Luxembourg - English
  • Luxembourg - Français
  • الشرق الأوسط وشمال أفريقيا - اللغة العربية
  • Middle East and North Africa - English
  • Moyen-Orient et Afrique du Nord - Français
  • Nederland
  • Norge
  • Polska
  • Portugal
  • România
  • Россия
  • Srbija
  • Slovensko
  • Slovenija
  • España
  • Sverige
  • Schweiz - Deutsch
  • Suisse - Français
  • Svizzera - Italiano
  • Türkiye
  • Україна
  • United Kingdom
  • Australia
  • 中国
  • 中國香港特別行政區
  • Hong Kong S.A.R. of China
  • India - English
  • 日本
  • 한국
  • New Zealand
  • 台灣

Southeast Asia

  • Includes Indonesia, Malaysia, Philippines, Singapore, Thailand, and Vietnam - English

Copyright © 2012 Adobe Systems Incorporated. All rights reserved.

Terms of Use | Privacy Policy and Cookies (Updated)

Ad Choices

Reviewed by TRUSTe: site privacy statement