In general, users don't expect an application running in a web browser to access files on their computer's hard drive. However, with the increase in browser-based rich Internet applications, it is desirable for some applications to access a user-selected file or save a file to a user-selected location. Versions of Adobe Flash Player that support the FileReference class allow ActionScript applications to upload user-selected files to a server or download files from a server to a user-selected location. However, prior to Flash Player 10, there was no way to access the contents of the file within the application or save application data to a local file unless you passed the data to a server first.
Starting in Flash Player 10, the FileReference class can additionally open user-selected local files and save data to a user-selected location without a server round trip. This greatly simplifies the process of creating a rich Internet application that loads a local file to use in the application or saves data from the application to a local file. For security reasons, ActionScript code running in Flash Player can't directly access the local file system to open or save a file. Using the technique described in this Quick Start, the code always causes a dialog box to appear, prompting the user to select a file to open or a file location to save the file. In addition, even when the user has selected a file to open, there is no way to determine the local path where the file is located. Only the name of the file is available.
The Crop Rectangle sample application, shown in Figure 1, demonstrates how to load data from a file the user selects from his or her computer, use that file in an application, then create and save data to a file on the user's computer. The application workflow is as follows:
Figure 1. The Crop Rectangle application. (This is the actual application running in your browser, not a screenshot.)
Note: This is a sample application provided, as is, for instructional purposes.
To make the most of this Quick Start, you need the following software and files:
This sample application includes the following files:
To test the application, try the application in your browser (see Figure 1) or compile the source code and run the sample.
General experience of building applications with Adobe Flash and ActionScript is suggested.
The Crop Rectangle example uses FileReference objects to open and save files on the user's computer. It uses the Loader class and the Bitmap class to display the image on the screen. It uses the BitmapData and ByteArray classes to create the cropped portion of the image and encode it as data that can be saved on the user's computer.
Note: The example also uses other classes, primarily display objects and user interface components, to define the application user interface. This Quick Start does not describe all the functionality of the user interface or other parts of the application.
The following is an overview of the steps that the application goes through to open, process, and save files:
Opening a user-selected local file:
FileReference.browse() method prompts the user
to select a file. When finished, the FileReference object dispatches a select event.FileReference.load() method loads the selected file into a variable. When finished, it dispatches a complete event. The file data is available in the FileReference object's data property.Accessing the loaded data (displaying the image on the screen)
Loader.loadBytes() method converts the image
file data from the FileReference.data property into a Bitmap
instance. When finished, the LoaderInfo object in the Loader's contentLoaderInfo property dispatches a complete event.Capturing and encoding the crop image data
BitmapData.draw() method.Saving a file to a user-selected location
FileReference.save() method prompts the user to
select a save location. The data to save is passed as an argument to the save() method. When the save() operation finishes, the FileReference object
dispatches a complete event. If there is an error, it dispatches an ioError event.These steps are explained in greater detail in the remainder of this Quick Start.
The Crop Rectangle application uses a FileReference object
to represent the file that the user selects and loads. In this example, the
code is in the Crop class. The process of loading a file involves two steps:
prompting the user to select a file using the FileReference.browse() method and reading the file data into Flash Player using the FileReference.open() method. Each of these tasks uses events to notify you when its operation is
complete. Consequently, in the source code, this process is divided among
several methods.
The first method is the startLoadingFile() method. This method is called when the user clicks the button on the first
screen of the application. It then prompts the user to select an image file to
open. Here is the source code for the startLoadingFile() method:
private var _loadFile:FileReference;
private function startLoadingFile():void
{
_loadFile = new FileReference();
_loadFile.addEventListener(Event.SELECT, selectHandler);
var fileFilter:FileFilter = new FileFilter("Images: (*.jpeg, *.jpg, *.gif, *.png)", "*.jpeg; *.jpg; *.gif; *.png");
_loadFile.browse([fileFilter]);
}
The FileReference object that is used to select and open the
file is a private instance variable named _loadFile. An instance
variable is used because _loadFile is used in various methods in the
Crop class, so it needs to be available throughout the class.
Within the startLoadingFile() method, the code does the
following:
_loadFile as a FileReference object.selectHandler() as a listener for the select event. The select event is dispatched after the user is prompted to select a file and has
selected a file (rather than canceling the dialog box).fileFilter. The
FileFilter object specifies the set of file types that the user can select; in
this case, files with the extensions .jpeg, .jpg, .gif, and .png. It also
defines the description of those file types that appears in the dialog box:
"Images: (*.jpeg, *.jpg, *.gif, *.png)".browse() method. The browse() method causes Flash Player to display the dialog box that allows the user to
select the file to open.When the user selects a file and clicks the Open button in
the dialog box, the FileReference object dispatches the select event and calls
the selectHandler() method. The selectHandler() method performs the next step of the
file-loading process: loading the selected file into Flash Player's memory:
private function selectHandler(event:Event):void
{
_loadFile.removeEventListener(Event.SELECT, selectHandler);
// ... display the progress bar for the loading operation ...
_loadFile.addEventListener(Event.COMPLETE, loadCompleteHandler);
_loadFile.load();
}
The selectHandler() method code does four things:
select event.loadCompleteHandler() as a listener for
the _loadFile object's complete event. A FileReference object dispatches the complete event when it
finishes reading a file's data from the disk into memory.load() method. The load() method starts the process of reading the file into memory where your code can
access its contents.When the file loading operation finishes, _loadFile dispatches the complete event and calls the method named loadCompleteHandler().
That method's functionality is described in the next section.
When you use a FileReference object's load() method to load
data from a file, the file's content is loaded into the FileReference object's data property. The FileReference.data property is a ByteArray object
containing the raw binary data from the file. You use the ByteArray class
methods to read the file's contents and use them in your application. For
example, if the file is from a word processing application, you might want to
extract parts of the text and display them on the screen. You could even create
your own custom file format and load and save files in that format in your
application.
In the case of the Crop Rectangle application, the loaded
file contains image data, in either JPEG, PNG, or GIF format. In this
application, we don't want to read parts of the file data; we just want to use
the data to display the image on the screen. To display raw image bytes on the
screen, you use the Loader class's loadBytes() method. You pass the bytes
(as a ByteArray) to the Loader object, and it uses those bytes to create an
ActionScript display object. The type of display object it creates depends on
the type of data that's passed in. In this case, because the application only
allows the user to select image files, the Loader creates a Bitmap object from
the image file datThe end result is exactly the same as if you had used the
Loader to load an image file from a web server. Like other processes described
in this Quick Start, a Loader object's loadBytes() method
doesn't necessarily create a display object instantaneously. Instead, the
LoaderInfo object corresponding to the Loader object dispatches a complete event when the Loader finishes converting the bytes into a display object. At
that point you know the display object can be added to the display list.
As described in the previous section, when a FileReference object
finishes loading a file from disk into memory, it dispatches the complete event. In the Crop Rectangle application, the FileReference object named _loadFile loads a user-selected image file. When the loading is complete, the loadCompleteHandler() method is called because it is registered as a listener for the complete event.
The loadCompleteHandler() method begins the process
of displaying the image on the screen:
private function loadCompleteHandler(event:Event):void
{
_loadFile.removeEventListener(Event.COMPLETE, loadCompleteHandler);
var loader:Loader = new Loader();
// ... display the progress bar for converting the image data to a display object ...
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadBytesHandler);
loader.loadBytes(_loadFile.data);
}
The loadCompleteHandler() method does the
following:
_loadFile object's complete event.loader.loadBytes() method (not shown).loadBytesHandler() to be called when the Loader
finishes creating a display object (specifically, when the LoaderInfo object in
the Loader object's contentLoaderInfo property dispatches a complete event).loadBytes() method, passing it the
ByteArray containing the content of the loaded file (the _loadFile.data property).When the Loader object's loadBytes() operation
finishes, the LoaderInfo object in its contentLoaderInfo property dispatches a complete event. In this example, it calls the loadBytesHandler() method. The following is the source code for the loadBytesHandler() method:
private function loadBytesHandler(event:Event):void
{
var loaderInfo:LoaderInfo = (event.target as LoaderInfo);
loaderInfo.removeEventListener(Event.COMPLETE, loadBytesHandler);
showImage(loaderInfo.content);
}
The loadBytesHandler() method does two things.
First, it removes itself as a listener for the LoaderInfo object's complete event. Next, it calls a method named showImage(), passing it
the newly-created display object (the loaderInfo.content property).
The showImage() method is too large to include
here, but the things it does are straightforward display object manipulation
tasks. It creates a Sprite named _imageContainer that serves as a
container for the image. It takes the display object created from the loaded
image file and adds it to the display list by calling the _imageContainer object's addChild() method. It then resizes the image if necessary to fit it in the visible area in
the application, and centers it on the screen.
Once the user has selected the part of the image that should be kept and the output format (JPEG or PNG) for the image, he or she clicks the "Save image" button. At that point, the application needs to do a few things to prepare to save the cropped image to the user's computer:
These steps, as well as others described in a subsequent
section, occur in the Crop class's cropAndSave() method. That method is
called when the user clicks the Save image button.
First, the code obtains the coordinates of the rectangle corresponding to the selected part of the image, using the following line of code:
var cropRect:Rectangle = _cropStateView.getCropRect();
The Rectangle object named cropRectangle contains
the coordinates of the selected part of the image. The Rectangle is obtained by
calling a method named getCropRect() on the object named _cropStateView.
The _cropStateView object is an instance of the CropStateView class.That class defines the code
for the user interface that's displayed at this point in the application. This Quick
Start doesn't describe the details of how that Rectangle object is calculated;
it's sufficient to note that the cropRect object is now available and
contains the coordinates of the selected rectangular region.
Next, the code creates a BitmapData object named imageData,
using the following code:
var imageData:BitmapData = new BitmapData(cropRect.width, cropRect.height);
The imageData BitmapData object will contain the
pixel data for the cropped image. The BitmapData object is created with its
width and height matching the width and height properties of
the cropRect Rectangle. Because of this, when the pixels are copied into imageData,
only an area the size of the selected rectangle is copied.
The next lines of code create a Matrix object named shiftOrigin.
This object defines the position in the original image that becomes the top
left corner of the region copied into the imageData BitmapData object. The
following code creates this object:
var shiftOrigin:Matrix = new Matrix(); shiftOrigin.translate(-cropRect.x, -cropRect.y);
The Matrix object's translate() method
causes the source image for the pixel copying operation to shift by the
specified number of pixels. (The actual display object on the screen doesn't
move—only the conceptual position of the display object relative to the
starting coordinate of the pixel data snapshot.) Notice that the arguments that
are passed to the translate() method are negative. Conceptually,
you can think of the BitmapData object as a camera that takes a snapshot of
pixels. However, the camera's position is fixed, so the image being
"photographed" needs to shift so that the designated crop area is
captured. The image must shift up and to the left the same amount as the x and
y coordinates of the Rectangle, so that the top-left corner of the selected
rectangle lines up with the conceptual top-left corner of the
"camer" Consequently, the translate() arguments
are negative.
Next, the pixels from the image on the Stage are copied into
the BitmapData object by calling its draw() method:
imageData.draw(_imageContainer, shiftOrigin);
The draw() method copies a display object's pixel
data into a BitmapData object. The parameters passed to the draw() method are:
_imageContainer: the Sprite that contains the loaded image
on the Stage (the source display object).shiftOrigin: the Matrix object that specifies the starting
location for the pixel snapshot, as described previously.With the pixel data snapshot of the selected rectangle in
the imageData BitmapData object, that image data can be converted to the appropriate image
format. In the Crop Rectangle sample application, there are two possible output
formats: JPEG and PNG. The following code determines which of the option buttons
corresponding to the two formats is selected. The code then uses the
appropriate encoder to create a ByteArray (encodedImage)
containing the image data converted to the specified format.
var encodedImage:ByteArray;
if (_cropStateView.outputFormat == CropStateView.JPEG)
{
var jpgEncoder:JPGEncoder = new JPGEncoder(85);
encodedImage = jpgEncoder.encode(imageData);
}
else
{
encodedImage = PNGEncoder.encode(imageData);
}
The _cropStateView variable is an instance of the
CropStateView class and contains the user interface code for this part of the
application. It has a property, outputFormat, whose value indicates
which of the output format option buttons is selected. The CropStateView class
also defines two constants, CropStateView.JPEG and CropStateView.PNG,
representing the possible values of the outputFormat property.
The JPGEncoder class and the PNGEncoder class are two classes that convert BitmapData objects to ByteArray objects containing image data in those formats. These classes are from the open-source as3corelib project and are distributed under the New BSD license.
The preceding section describes how the rectangular crop
region that the user selects is captured into a BitmapData object and then
converted to a ByteArray object named encodedImage. The next
task is to save that ByteArray data as a file on the user's computer. Like
opening a file, saving a file is a multistep operation, with events that
indicate when the steps of the operation finish. Continuing in the cropAndSave() method, the code next creates a suggested file name to use in the save dialog:
var fileNameRegExp:RegExp = /^(?P<fileName>.*)\..*$/;
var outputFileName:String = fileNameRegExp.exec(_loadFile.name).fileName + "_crop";
if (_cropStateView.outputFormat == CropStateView.JPEG)
{
outputFileName += ".jpg";
}
else
{
outputFileName += ".png";
}
In this case, we want the default file name to be the
original file name (without extension), plus the text "_crop", plus
the file extension corresponding to the selected file format. The code uses a
regular expression object (fileNameRegExp) to get the original file name
without extension. The regular expression object's exec() method executes
the regular expression pattern on whatever String is passed as an argument. In
the code, the _loadFile.name property is passed to the exec() method. Remember that _loadFile is a FileReference object
corresponding to the original image file the user opens in the beginning. That
FileReference object's name property contains the full file name of
the selected file, including its extension. Because the regular expression
pattern uses a named capturing group named fileName, when the exec() method is called, the result has a property named fileName containing the
file name without its extension. The String "_crop" is
concatenated to the end of that result, and the whole thing is saved in the
variable outputFileName.
All that remains is to add the file extension corresponding
to the output file type that the user selects. The conditional statement checks
which output format is selected and adds the appropriate extension (".jpg" or ".png")
to outputFileName.
Next, the code begins the operation of saving the JPEG or PNG image data to a file, as shown here:
var saveFile:FileReference = new FileReference(); saveFile.addEventListener(Event.COMPLETE, saveCompleteHandler); saveFile.addEventListener(IOErrorEvent.IO_ERROR, saveIOErrorHandler); saveFile.save(encodedImage, outputFileName);
The code in this listing does three things:
saveFile, which
performs the save operation.It registers methods as listeners for different events dispatched by the saveFile FileReference object:
saveCompleteHandler() method as a listener for the complete event, which is dispatched when the
user selects the save destination and Flash Player finishes writing the file to
the disk.saveIOErrorHandler() method as a listener for the ioError event, which is dispatched when an
error prevents Flash Player from saving the file to the selected location.saveFile FileReference object's save() method. The save() method opens a dialog box prompting the user to
select a location to save the file. It accepts two parameters. The object
passed for the first parameter is encodedImage—the ByteArray containing
the data to save into the file (in this case, the JPEG or PNG image data). The
value passed as the second parameter is the outputFileName variable. That value is used as the suggested file name in the Save dialog box.
The user can always change the file name, but this is the only way to specify a
recommended file extension, so for that reason it's helpful to provide a
default name.Assuming the user doesn't cancel the save operation, the saveFile FileReference object starts saving the data to the disk. In this case, because
a ByteArray is passed to the save() method, the ByteArray's contents are
saved directly to the disk. If a different type of object is passed to the save() method, the data is written to the file in a different format. For example, a
String or XML object is written as a text file containing the String or XML
value. For a list of all the file format options, see the FileReference.save() listing in the Flash CS4 Professional ActionScript 3.0 Language Reference.
When the file is saved successfully, the FileReference
object dispatches a complete event. In the preceding code, the saveCompleteHandler() method is registered as a listener for that event, so when the file saves
successfully, the FileReference object calls that method. The saveCompleteHandler() method code is not included in this Quick Start. It simply performs clean-up
operations (removing event listeners) and causes the user interface to display
a message that the file was saved.
If there is an error while the FileReference object attempts
to save the file, it dispatches an ioError event instead. In that case, the
FileReference object calls the saveIOErrorHandler() method. That method
simply cleans up the event listeners and displays the error message.
Related Flash Quick Starts
H. Paul Robertson is a senior ActionScript developer/writer for the Flash Platform documentation team at Adobe Systems, currently working on Adobe AIR. Previously he worked as a web applications developer and has been the author or technical editor/reviewer of several books for Peachpit Press and O'Reilly Media. Paul is an Adobe Certified developer and holds a Masters degree in Instructional Systems Technology from Indiana University. When he's not developing RIAs and code libraries, writing about developing RIAs, or speaking at conferences about developing RIAs, Paul spends his time taking pictures, collecting kitchen gadgets, and trying to catch up to his three children's level of mastery of Legos, Star Wars trivia, and other equally important subjects.