Requirements

Prerequisite knowledge

General experience of building applications with Adobe Flash and ActionScript 3.

User level

Intermediate

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.

Examining the sample application

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:

  1. When the user clicks a button, the application prompts the user to select an image file (JPEG, PNG, or GIF) from his or her computer.
  2. The image is displayed on the screen, along with an interface for cropping the image (selecting a rectangular portion of the image to keep). The user can also select from one of two output file formats: JPEG or PNG.
  3. When the desired cropping and output format are selected, the user clicks a button and is prompted for a location at which to save the file. Once the user selects the destination file, the file is saved, and a "save complete" screen displays.

This sample application includes the following files:

  • Crop.fla: The main application file for Adobe Flash. This file contains movie clip symbols defining the major parts of the user interface. The code for the application is in separate ActionScript class files.
  • Crop.as: The main application source code. This class is as the document class for the FLIt contains the code that switches between parts of the user interface. It also contains the code that does the actual processing of the application—loading a local image file, taking a snapshot of the selected portion of the image, and saving that data to a local file.
  • LoadStateView.as, ProgressStateView.as, CropStateView.as, SaveCompleteStateView.as: The code for the user interface for each of the application's major states. Each class corresponds to a movie clip symbol in Crop.fla's library. (For example, LoadStateView.as defines the LoadStateView class, which is associated with the library symbol "LoadStateView".)
  • CropControl.as: The code for a component that provides the user interface for selecting the part of the image to keep.
  • BitString.as, JPGEncoder.as, PNGEncoder.as(in the folder "com.adobe.images"): ActionScript classes that perform the task of converting image data in a BitmapData object to a ByteArray containing the image data in a particular image file format (JPEG or PNG). These classes are from the open-source as3corelib project and are distributed under the New BSD license. For Flex, classes that offer the same functionality are also available in the Flex SDK, in the mx.graphics.codec package.
  • .flashProjectProperties: Flash CS4 project file for opening the application in the Flash CS4 Project panel.

To test the application, try the application in your browser (see Figure 1) or compile the source code and run the sample.

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.

Understanding the code

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:

  1. Opening a user-selected local file:
    • Calling the FileReference.browse() method prompts the user to select a file. When finished, the FileReference object dispatches a select event.
    • The 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.
  2. Accessing the loaded data (displaying the image on the screen)
    • Calling the 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.
    • The image is displayed on the screen and the user can select a rectangular area for the cropped output.
  3. Capturing and encoding the crop image data
    • A snapshot of the pixels in the user-selected rectangle is captured and saved in a BitmapData object using the BitmapData.draw() method.
    • The BitmapData object is converted into a ByteArray containing JPEG or PNG data, using the JPGEncoder or PNGEncoder class.
  4. Saving a file to a user-selected location
    • Calling the 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.

Opening a user-selected local file

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:

  1. Instantiates _loadFile as a FileReference object.
  2. Registers the method named 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).
  3. Creates a FileFilter object named 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)".
  4. Calls the FileReference object's 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:

  1. Removes itself as a listener to the select event.
  2. Shows a progress indicator for the loading operation (this code is not shown in the listing)/
  3. Registers the method named 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.
  4. Calls the FileReference object's 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.

Accessing the loaded data (displaying the image on the screen)

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:

  1. Removes itself as a listener of the _loadFile object's complete event.
  2. Creates a Loader instance named loader.
  3. Shows a progress bar showing the progress of the Loader object's loadBytes() method (not shown).
  4. Registers the method 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).
  5. Calls the Loader object's 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.

Capturing and encoding the crop image data

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:

  1. Obtain the coordinates of the rectangle that defines the portion of the image to keep.
  2. Take a snapshot of the pixels in that rectangle and store it in a BitmapData object.
  3. Convert the BitmapData object into JPEG or PNG image data.

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.

Obtain the crop rectangle coordinates

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.

Store a snapshot of the crop image pixels in a BitmapData object

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.

Convert the BitmapData object into JPEG or PNG data

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.

Saving a file to a user-selected location

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 box:

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:

  1. It creates a new FileReference object named saveFile, which performs the save operation.
  2. It registers methods as listeners for different events dispatched by the saveFile FileReference object:
    • The 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.
    • The 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.
  3. It calls the 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 ActionScript 3.0 Reference for the Adobe Flash Platform.

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