Source Code and Comments

In this tutorial, we discuss loading images from the Camera Roll on your phone into an app and saving images of the screen or a part of the screen to the Camera Roll. Many new AS3 classes specific to AIR on mobile come into consideration. In particular, the CameraRoll class and its methods: browseForImage and addBitmapData. We provide here complete source code. If you want to see the app in action, open the Android Market on your phone, search for flashandmath, and locate Camera Roll Example on our apps list. Make sure that the version number is 1.1.0. You can then download and install the app. Below are sceen shots of the app.

Download

  • Download all source files corresponding to this app (Flash CS5 and CS5.5 formats): crcode.zip

Working with the Source Files

The zip file contains CamRollEx.fla - a Flash CS5.5 file and CamRollExCS5.fla - a Flash CS5 file. In Flash CS5.5, you simply open CamRollEx.fla. Under AIR for Android Publish settings, in General select Landscape, and Full Screen modes, under Deployment enter the location or create your p12 certificate and choose deployment settings that you want, under Permissions be sure to check WRITE_EXTERNAL_STORAGE.

When you connect your phone to your computer, it usually connects as a Mass Storage Device. With that setting, you have limited access to the SD card and you will not be able to access the Camera Roll. Pull the USB icon from the top bar and uncheck Mass Storage, by selecting, for example, Charge Only setting. Then the published app will work correctly.

You can use CamRollExCS5.fla if you have AIR for Android Extension for Flash CS5 installed.

Code and Comments

Below is complete Timeline source code with comments within the code.

import flash.display.Bitmap;

import flash.display.BitmapData;

import flash.display.Loader;

import flash.display.Sprite;

import flash.display.StageAlign;

import flash.display.StageScaleMode;

import flash.events.ErrorEvent;

import flash.events.Event;

import flash.events.MediaEvent;

import flash.events.MouseEvent;

import flash.media.CameraRoll;

import flash.media.MediaPromise;

import flash.display.MovieClip;

 

stage.align = StageAlign.TOP_LEFT;

stage.scaleMode = StageScaleMode.NO_SCALE;

/*
We create an instance of the AS3 CameraRoll class. The class is specific to AIR for mobile.
*/

var cr:CameraRoll=new CameraRoll();

/*
Loaded images and the 'stamp' will be kept in a Sprite 'container'. A MovieClip 'StampMc' was created on the stage and linked from the Library to AS3. We add a MovieClip imitating a stamp to images that we are saving to keep the tutorial simple. Instead you could apply filters to loaded images, draw on them, modify them in many ways and then save the resulting image to Camera Roll.
*/

var container:Sprite=new Sprite();

this.addChild(container);

var photoHolder:Sprite=new Sprite();

container.addChild(photoHolder);

var stamp:MovieClip=new StampMc();

stamp.x=250;

stamp.y=300;

stamp.rotation=-60;

container.addChild(stamp);

stamp.visible=false;

/*
mcPanel - the panel of buttons - was created on the stage as well as the infoBox and mcHelp clip. We move them to the front of the Display List.
*/

setChildIndex(mcPanel,numChildren-1);

setChildIndex(infoBox,numChildren-1);

setChildIndex(mcHelp,numChildren-1);

mcHelp.visible=false;

var loader:Loader;

init();

/*
Within the function 'init', we check if the device supports browsing and saving images. We use static properties of the CameraRoll class: CameraRoll.supportsBrowseForImage and CameraRoll.supportsAddBitmapData. We need both properties for the app to function properly.
*/

function init():void {

mcPanel.btnExit.addEventListener(MouseEvent.CLICK, exitApp);

if ((CameraRoll.supportsBrowseForImage == false) || (CameraRoll.supportsAddBitmapData == false)){

infoBox.text="Camera Roll browsing or saving not supported.";

return;

}

mcPanel.btnSearch.addEventListener(MouseEvent.CLICK,onSearch);

mcPanel.btnSave.addEventListener(MouseEvent.CLICK, saveImg);

mcPanel.btnHelp.addEventListener(MouseEvent.CLICK, showHelp);

}

/*
When the user selects an image from the Camera Roll, our instance 'cr' of CameraRoll dispatches an event MediaEvent.SELECT. If the user goes back to the app without selecting an image, the Event.CANCEL is dispatched.
*/

function addBrowseListeners():void {

cr.addEventListener(MediaEvent.SELECT, onImgSelect);

cr.addEventListener(Event.CANCEL, onCancel);

cr.addEventListener(ErrorEvent.ERROR, onError);

}

 

function removeBrowseListeners():void {

cr.removeEventListener(MediaEvent.SELECT, onImgSelect);

cr.removeEventListener(Event.CANCEL, onCancel);

cr.removeEventListener(ErrorEvent.ERROR, onError);

}

/*
When the button 'search camera roll' is tapped, the method 'browseForImage' of the CameraRoll class is called. The camera Roll is displayed and our app goes into the background.
*/

function onSearch(e:MouseEvent):void {

mcPanel.visible=false;

infoBox.text="";

addBrowseListeners();

cr.browseForImage();

}

 

function onCancel(event:Event):void {

infoBox.text="Search canceled";

removeBrowseListeners();

mcPanel.visible=true;

}

/*
In this app you can load only from the Camera Roll. Loading from File System will cause an error.
*/

function onError(event:ErrorEvent):void {

infoBox.text="Gallery error. You can only load from Gallery.";

mcPanel.visible=true;

}

/*
When MediaEvent.SELECT is dispatched, the event's 'data' is an instance of the AS3 class 'MediaPromise'. An instance of MediaPromise can be loaded into our app via Loader.loadFilePromise method.
*/

function onImgSelect(event:MediaEvent):void {

var promise:MediaPromise = event.data as MediaPromise;

removeBrowseListeners();

loader = new Loader();

loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onImageLoaded);

loader.contentLoaderInfo.addEventListener(ErrorEvent.ERROR, onError);

loader.loadFilePromise(promise);

}

/*
When our loader finished loading the 'file promise', the event Event.COMPLETE is dispatched. We cast the event.currentTarget.content as Bitmap to be able to extract the BitmapData information of the loaded image. Loaded images taken with a phones's camera will, in general, be much larger than the screen. We rescale them using 'ratio'.
*/

function onImageLoaded(event:Event):void {

var isPortrait:Boolean;

var bitmapData:BitmapData = Bitmap(event.currentTarget.content).bitmapData;

if(bitmapData.height > bitmapData.width){

isPortrait=true;

} else {

isPortrait=false;

}

var bitmap:Bitmap = new Bitmap(bitmapData);

//Calculate the scaling ratio to apply to the image.

var ratio:Number;

if(isPortrait){

ratio=Math.min(stage.stageHeight/bitmapData.width,
        stage.stageWidth/bitmapData.height);

ratio=Math.min(ratio,1);

bitmap.width = bitmapData.width * ratio;

bitmap.height = bitmapData.height * ratio;

bitmap.rotation=-90;

bitmap.y=bitmap.height;

} else {

ratio=Math.min(stage.stageHeight/bitmapData.height,
        stage.stageWidth/bitmapData.width);

ratio=Math.min(ratio,1);

bitmap.width = bitmapData.width * ratio;

bitmap.height = bitmapData.height * ratio;

}

if(photoHolder.numChildren>0){

photoHolder.removeChildAt(0);

}

photoHolder.addChild(bitmap);

mcPanel.visible = true;

stamp.visible=false;

loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, onImageLoaded);

loader.contentLoaderInfo.removeEventListener(ErrorEvent.ERROR, onError);

infoBox.text="";

}

/*
When the user taps on Exit button, the app quits.
*/

function exitApp(event:MouseEvent):void {

NativeApplication.nativeApplication.exit(0);

}

/*
To save the image from the screen to the Camera Roll we draw 'container' into a BitamapData object, 'bdToSave', and use the CameraRoll's class method 'addBitmapData'. The new image is saved. We 'stamp' the new image but, of course, we could have applied modifications to it, draw on it etc. The saved image will be of the size of the scaled loaded photo plus the stamp, lower resolution than images taken by the camera.
*/

function saveImg(event:MouseEvent):void {

mcPanel.visible=false;

infoBox.text="";

stamp.visible=true;

var bdToSave:BitmapData=new BitmapData(container.width,container.height);

bdToSave.draw(container);

cr.addEventListener(Event.COMPLETE, onSave);

cr.addBitmapData(bdToSave);

}

 

function onSave(event:Event):void {

infoBox.text="Image saved";

mcPanel.visible=true;

cr.removeEventListener(Event.COMPLETE, onSave);

}

 

function showHelp(e:MouseEvent):void {

mcHelp.visible=true;

container.visible=false;

mcHelp.addEventListener(MouseEvent.CLICK,hideHelp);

}

 

function hideHelp(e:MouseEvent):void {

mcHelp.visible=false;

container.visible=true;

mcHelp.removeEventListener(MouseEvent.CLICK,hideHelp);

}

Note:   We noticed that the BitmapData extracted within the handler onImageLoaded:

var bitmapData:BitmapData = Bitmap(event.currentTarget.content).bitmapData;

varies greatly from device to device. For example, on Droid 2 or Droid X, bitmapData is always horizontal, that is, bitmapData.width is always greater than bitmapData.height, even for photos taken in the portrait position of the phone. On Droid Bionic, images taken in portrait position have bitmapData.width smaller than bitmapData.height. Please, drop us a note about the app's behavior on other devices.

This tutorial was written by Barbara Kaskosz of flashandmath.

Back to AIR for Android              Back to Flash and Math Home

We welcome your comments, suggestions, and contributions. Click the Contact Us link below and email one of us.

Adobe®, Flash®, ActionScript®, Flex® are registered trademarks of Adobe Systems Incorporated.