The Gallery Application and the CameraRoll Class

1
282

The Gallery application is the display for the repository of images located on the SD card and accessible by various applications. Launch it, choose an image, and then select Menu→Share. A list of applications (such as Picasa, Messaging, and Email) appears, a convenient way to upload media from the device to another destination (see Figure 9-1).

The flash.media.CameraRoll class is a subclass of the EventDispatcher class. It gives you access to the Gallery. It is not supported for AIR desktop applications.

The Gallery application

Selecting an Image

You can test that your device supports browsing the Gallery by checking the supports BrowseForImage property:

import flash.media.CameraRoll;
if (CameraRoll.supportsBrowseForImage == false) {
trace(“this device does not support access to the Gallery”);
return;
}

If your device does support the Gallery, you can create an instance of the CameraRoll class. Make it a class variable, not a local variable, so that it does not lose scope:

var cameraRoll:CameraRoll = new CameraRoll();

You can add listeners for three events:

  • A MediaEvent.SELECT when the user selects an image:
    import flash.events.MediaEvent;
    cameraRoll.addEventListener(MediaEvent.SELECT, onSelect);
  • An Event.CANCEL event if the user opts out of the Gallery:
    import flash.events.Event;
    cameraRoll.addEventListener(Event.CANCEL, onCancel);
    function onCancel(event:Event):void {
    trace(“user left the Gallery”, event.type);
    }
  • An ErrorEvent.ERROR event if there is an issue in the process:
    import flash.events.ErrorEvent;
    cameraRoll.addEventListener(ErrorEvent.ERROR, onError);
    function onError(event:Event):void {
    trace(“Gallery error”, event.type);
    }

Call the browseForImage function to bring the Gallery application to the foreground:

cameraRoll.browseForImage();

Your application moves to the background and the Gallery interface is displayed, as shown in Figure 9-2.

The Gallery interface

When you select an image, a MediaEvent object is returned. Use its data property to reference the image and cast it as MediaPromise. Use a Loader object to load the image:

import flash.display.Loader;
import flash.events.IOErrorEvent;
import flash.events.MediaEvent;
import flash.media.MediaPromise;
function onSelect(event:MediaEvent):void {
var promise:MediaPromise = event.data as MediaPromise;
var loader:Loader = new Loader()
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onImageLoaded);
loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR,onError);
loader.loadFilePromise(promise);
}

The concept of MediaPromise was first introduced on the desktop in a drag-and-drop scenario where an object doesn’t yet exist in AIR but needs to be referenced. Access its file property if you want to retrieve the image name, its nativePath, or its url.

The url is the qualified domain name to use to load an image. The nativePath refers to the hierarchical directory structure:

promise.file.name;
promise.file.url;
promise.file.nativePath;

Let’s now display the image:

function onImageLoaded(event:Event):void {
addChild(event.currentTarget.content);
}

Only the upper-left portion of the image is visible. This is because the resolution of the camera device is much larger than your AIR application stage.

Let’s modify our code so that we can drag the image around and see all of its content. We will make the image a child of a sprite, which can be dragged around:

import flash.events.MouseEvent;
import flash.display.DisplayObject;
import flash.geom.Rectangle;
var rectangle:Rectangle;
function onImageLoaded(event:Event):void {
var container:Sprite = new Sprite();
var image:DisplayObject = event.currentTarget.content as DisplayObject;
container.addChild(image);
addChild(container);
// set a constraint rectangle to define the draggable area
rectangle = new Rectangle(0, 0,
-(image.width – stage.stageWidth),
-(image.height – stage.stageHeight)
);
container.addEventListener(MouseEvent.MOUSE_DOWN, onDown);
container.addEventListener(MouseEvent.MOUSE_UP, onUp);
}
function onDown(event:MouseEvent):void {
event.currentTarget.startDrag(false, rectangle);
}
function onUp(event:MouseEvent):void {
event.currentTarget.stopDrag();
}

It may be interesting to see the details of an image at its full resolution, but this might not result in the best user experience. Also, because camera resolution is so high on most devices, there is a risk of exhausting RAM and running out of memory.

Let’s now store the content in a BitmapData, display it in a Bitmap, and scale the bitmap to fit our stage in AIR. We will use the Nexus One as our benchmark first. Its camera has a resolution of 2,592×1,944. The default template size on AIR for Android is 800×480. To complicate things, the aspect ratio is different. In order to preserve the image fidelity and fill up the screen, you need to resize the aspect ratio to 800×600, but some of the image will be out of bounds.

Instead, let’s resize the image to 640×480. The image will not cover the whole stage, but it will be fully visible. Take this into account when designing your screen.

First, detect the orientation of your image. Resize it accordingly using constant values, and rotate the image if it is in landscape mode:

import flash.display.Bitmap;
import flash.display.BitmapData;
const MAX_HEIGHT:int = 640;
const MAX_WIDTH:int = 480;
function onImageLoaded(event:Event):void {
var bitmapData:BitmapData = Bitmap(event.target.content).bitmapData;
var bitmap:Bitmap = new Bitmap(bitmapData);
// determine the image orientation
var isPortrait:Boolean = (bitmapData.height/bitmapData.width) > 1.0;
if (isPortrait) {
bitmap.width = MAX_WIDTH;
bitmap.height = MAX_HEIGHT;
} else {
bitmap.width = MAX_HEIGHT;
bitmap.height = MAX_WIDTH;
// rotate a landscape image
bitmap.y = MAX_HEIGHT;
bitmap.rotation = -90;
}
addChild(bitmap);
}

The preceding code is customized to the Nexus One, and it will not display well for devices with a different camera resolution or screen size. We need a more universal solution.

The next example shows how to resize the image according to the dynamic dimension of both the image and the stage. This is the preferred approach for developing on multiple screens:

function onImageLoaded(event:Event):void {
var bitmapData:BitmapData = Bitmap(event.target.content).bitmapData;
var bitmap:Bitmap = new Bitmap(bitmapData);
// determine the image orientation
var isPortrait:Boolean = (bitmapData.height/bitmapData.width) > 1.0;
// choose the smallest value between stage width and height
var forRatio:int = Math.min(stage.stageHeight, stage.stageWidth);
// calculate the scaling ratio to apply to the image
var ratio:Number;
if (isPortrait) {
ratio = forRatio/bitmapData.width;
} else {
ratio = forRatio/bitmapData.height;
}
bitmap.width = bitmapData.width * ratio;
bitmap.height = bitmapData.height * ratio;
// rotate a landscape image and move down to fit to the top corner
if (!isPortrait) {
bitmap.y = bitmap.width;
bitmap.rotation = -90;
}
addChild(bitmap);
}

Beware that the browseForImage method is only meant to load images from the Gallery. It is not for loading images from the filesystem even if you navigate to the Gallery. Some devices bring up a dialog to choose between Gallery and Files. If you try to load an image via Files, the application throws an error. Until this bug is fixed, set a listener to catch the error and inform the user:

cameraRoll.browseForImage();
cameraRoll.addEventListener(ErrorEvent.ERROR, onError);
function onError(event:Event):void {
if (event.errorID == 2124) {
trace(“you can only load images from the Gallery”);
}
}

If you want to get a list of all the images in your Gallery, you can use the filesystem as follows:

var gallery:File = File.userDirectory.resolvePath(“DCIM/Camera”);
var myPhotos:Array = gallery.getDirectoryListing();
var bounds:int = myPhotos.length;
for (var i:uint = 0; i < bounds; i++) {
trace(myPhotos[i].name, myPhotos[i].nativePath);
}

Adding an Image

You can add an image to the Gallery from within AIR. To write data to the SD card, you must set permission for it:

<uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE” />

Check the supportsAddBitmapData property to verify that your device supports this feature:

import flash.media.CameraRoll;
if (CameraRoll.supportsAddBitmapData == false) {
trace(“You cannot add images to the Gallery.”);
return;
}

If this feature is supported, create an instance of CameraRoll and set an Event.COM PLETE listener. Call the addBitmapData function to save the image to the Gallery. In this example, a stage grab is saved.

This feature could be used for a drawing application in which the user can draw over time. The following code allows the user to save his drawing, reload it, and draw over it again:

var cameraRoll:CameraRoll;
cameraRoll = new CameraRoll();
cameraRoll.addEventListener(ErrorEvent.ERROR, onError);
cameraRoll.addEventListener(Event.COMPLETE, onComplete);
var bitmapData:BitmapData =
new BitmapData(stage.stageWidth, stage.stageHeight);
bitmapData.draw(stage);
cameraRoll.addBitmapData(bitmapData);
function onComplete(event:Event):void {
// image saved in gallery
}

Remember that the image that is saved is the same dimension as the stage, and therefore it has a much smaller resolution than the native camera. At the time of this writing, there is no option to specify a compression, to name the image, or to save it in a custom directory. AIR follows Android naming conventions, using the date and time of capture.

 

1 COMMENT

  1. In every course cenconted to programming i had there was some place where you can find and download examples from lessons to check the code and study it little more. Not sure about Multimedia authoring part 1 though, just don’t remember it was kind of not very involving course anyway, too fast, too neutral to students in my opinion Will be nice to have such a place for students in this course, so, if i miss one class, i can follow it’s learning curve Thanx!

Comments are closed.