PhoneGap* compatible Camera API Sample

This article describes the design and the implementation of the "PhoneGap compatible Camera API" sample application. The application demonstrates the use of the PhoneGap compatible Camera API to capture photos with device camera and to retrieve photos from the saved photo albums. It has the following capabilities: a user can take photos with device camera or select photos from existing photo albums. The user can change the settings with which the photos are to be taken, such as photo quality, dimentions and etc., as well. 

The application uses jQuery library as well as jQuery Mobile library (for styling).

The source code can be downloaded here.

Final Look

The application has 3 screens (also referenced to as pages):

  1. A home page where a user can choose the source of a picture (camera, photo library or saved photo albums), access the application settings with which the picture is to be captured and see the settings currently used. On the bottom of the page a link to the lisence information is attached.
  2. Picture capture settings page (settings changes may be saved or declined).
  3. Capture result page where the captured picture is displayed.

Below the screenshots of the Home and Settings Pages are provided. 

Design Considerations and Overall Structure

The application uses the PhoneGap Camera API to access the device camera to capture new photos, and photo albums to grab the existing photos stored on the device. Particularly, the application uses the Camera.getPicture(onCaptureSuccess, onCaptureError, [cameraOptions]) method to grab photos, and Camera.cleanup(onSuccess, onError) method (on iOS only) to remove temporary files created by the application. The 2.0.0 version of PhoneGap is concidered in this article.

Settings Object

Camera.getPicture() can be called with the variety of optional parameters which define the target photo characteristics, such as quality, photo height and width and etc. (please, see the reference for cameraOptions). The sample application provides user the ability to set cameraOptions: this parameters can be set on the Settings page and then are stored in the settings object (a global variable of class Settings). Here follows the code snippet showing declaration of the Settings class (please, see the reference for option roles explanation):

// Stores cameraOptions (optional parameters to customize camera settings) with which camera.getPicture() is called
var settings;
// Class representing a storage of cameraOptions (optional parameters to customize the camera settings) 
// with which camera.getPicture() is called
function Settings() {
    // Opening options:
    this.destinationType = Camera.DestinationType.FILE_URI;     // cameraOptions: destinationType
    this.sourceType = Camera.PictureSourceType.PHOTOLIBRARY;    // cameraOptions: sourceType
    this.mediaType = Camera.MediaType.PICTURE;                  // cameraOptions: mediaType
    // Photo quality and editing options:
    this.quality = 40;                                          // cameraOptions: quality
    this.targetWidth = 500;                                     // cameraOptions: targetWidth
    this.targetHeight = 500;                                    // cameraOptions: targetHeight
    this.allowEdit = true;                                      // cameraOptions: allowEdit
    this.correctOrientation = true;                             // cameraOptions: correctOrientation
    // Saving options:
    this.encodingType = Camera.EncodingType.JPEG;               // cameraOptions: encodingType
    this.saveToPhotoAlbum = true;                               // cameraOptions: saveToPhotoAlbum
    // iOS-specific (to specify popover location in iPad):
    this.popoverOptions = new CameraPopoverOptions(220, 600, 320, 480, Camera.PopoverArrowDirection.ARROW_DOWN);    // cameraOptions: popoverOptions
}

Settings Page

Camera options can be changed by user at the settings page. Only 3 options are unavailable there: 

  1. destinationType - is always set to  Camera.DestinationType.FILE_URI. This value is recommended because allows to avoid memory overhead; 
  2. popoverOptions - is an iPad specific option. It sets the size and position of the popover window; 
  3. sourceType - is actually set to the appropriate value everytime one of three "take-a-picture" buttons is chosen and pressed by user at the home screen.

The settings are accessed and changed via the HTML5 form which is the part of the Settings page:

<!-- Settings edit page -->
<div data-role="page" id="settings_page" data-theme="a">
    <div data-role="header">
        <a href="#home_page" id="settings_cancel_button" data-role="button" data-mini="true" class="ui-btn-left">Cancel</a>
        <h3>Picture Settings</h3>
        <a href="#home_page" id="settings_ok_button" data-role="button" data-theme="b" data-mini="true" class="ui-btn-right">Save</a>
    </div>
    <div data-role="content">
        <form name="settings_form" id="settings_form">
            .....                <!-- all form elements are defined here -->
      </form>
   </div>
</div>

The changes made by the user on the form may be eigther changed or declined. In the case of saving changes the function applySettings() is called. It saves the chosen settings into the settings object:

// Reads customized camera options from the settings_form and saves them to the settings object (cameraOptions storage)
function applySettings() {
    var settingsBatch = getElement("settings_form");
    if (settingsBatch == null) {
        return;
    }
    var newQuality = parseInt(settingsBatch.elements["quality_input"].value, 10);
    if (!isNaN(newQuality) && (newQuality <= 100) && (newQuality >= 0)) {
        settings.quality = newQuality;
    }
    var newWidth = parseInt(settingsBatch.elements["width_input"].value, 10);
    if (!isNaN(newWidth) && (newWidth <= 1500) && (newWidth >= 50)) {
        settings.targetWidth = newWidth;
    }
    var newHeight = parseInt(settingsBatch.elements["height_input"].value, 10);
    if (!isNaN(newHeight) && (newHeight <= 1500) && (newHeight >= 50)) {
        settings.targetHeight = newHeight;
    }
    settings.allowEdit = settingsBatch.elements["edit_input"].checked;
    settings.correctOrientation = settingsBatch.elements["orient_input"].checked;
    settings.saveToPhotoAlbum = (settingsBatch.elements["save_input"].options[settingsBatch.elements["save_input"].selectedIndex].value == "true") ? true : false;
    settings.encodingType = parseInt(settingsBatch.elements["encod_input"].options[settingsBatch.elements["encod_input"].selectedIndex].value, 10);
    settings.mediaType = parseInt(settingsBatch.elements["media_input"].options[settingsBatch.elements["media_input"].selectedIndex].value, 10);
    .....
}

In the case of rejecting settings changing, the settings object is not updated and it is used to restore the form values. Additionally, as the form elements are wrapped with jQuery Mobile UI wrappers (jQuery Mobile is used to beautify the whole application), jQM elements must be refreshed to show the programmatically performed form changes. This actions are performed by the function listed below:

// Applies camera options stored in the settings object to the settings_form and updates visible form elements accordingly.
// Is to be used when the user changes the settings_form elements state but does not intend to "save" this changes
function restoreSettings() {
    $("#quality_input").val(settings.quality).slider("refresh");
    $("#width_input").val(settings.targetWidth).slider("refresh");
    $("#height_input").val(settings.targetHeight).slider("refresh");
    if (settings.allowEdit) {
        $("#edit_input").attr("checked", true).checkboxradio("refresh");
    } else {
        $("#edit_input").removeAttr("checked").checkboxradio("refresh");
    }
    if (settings.correctOrientation) {
        $("#orient_input").attr("checked", true).checkboxradio("refresh");
    } else {
        $("#orient_input").removeAttr("checked").checkboxradio("refresh");  
    }
    var saveSwitch = $("#save_input");
    saveSwitch[0].selectedIndex = ((settings.saveToPhotoAlbum === true) ? 1 : 0);
    saveSwitch.slider("refresh");
    $("#encod_input").val(settings.encodingType).selectmenu("refresh");
    $("#media_input").val(settings.mediaType).selectmenu("refresh");
}

Photo Capturing

The photo capturing process is started by pressing any of three buttons on the Home screen. On button click, the onCapture(e) method is called. It sets the appropriate value for the sourceType camera option (eigther PHOTOLIBRARY, CAMERA or SAVEDPHOTOALBUM) and calls the Camera.getPicture() method with the options set to values stored in the settings object. The Home Page and onCapture() code snippets are listed below:

         index.html:

         <!-- Home Page (menu page) -->
        <div data-role="page" id="home_page" data-theme="a">
            ....
            <div data-role="content">
                <a href="#" data-role="button" id="open_camera_button">Capture a photo with camera</a>
                <a href="#" data-role="button" id="open_lib_button">Open Photo Library</a>
                <a href="#" data-role="button" id="open_alb_button">Open saved photo album</a>
                .....
            </div>
            .....
        </div>

camera,js:

// Calls camera.getPicture() with cameraOptions customised by user
function onCapture(e) {
    var callerId = getTargetId(e, "a");
    switch (callerId) {
        case "open_camera_button":
            settings.sourceType = Camera.PictureSourceType.CAMERA;
            break;
        case "open_lib_button":
            settings.sourceType = Camera.PictureSourceType.PHOTOLIBRARY;
            break;
        case "open_alb_button":
            settings.sourceType = Camera.PictureSourceType.SAVEDPHOTOALBUM;
            break;
        default:
            return;
    }
    navigator.camera.getPicture(onCaptureSuccess, onCaptureError, { quality : settings.quality, 
                                                                    destinationType : settings.destinationType, 
                                                                    sourceType : settings.sourceType, 
                                                                    allowEdit : settings.allowEdit, 
                                                                    encodingType : settings.encodingType,
                                                                    targetWidth : settings.targetWidth,
                                                                    targetHeight : settings.targetHeight,
                                                                    mediaType: settings.mediaType,
                                                                    saveToPhotoAlbum : settings.saveToPhotoAlbum,
                                                                    correctOrientation: settings.correctOrientation,
                                                                    popoverOptions : settings.popoverOptions
                                                                  });
}
// Shows photo captured by camera.getPicture()
function onCaptureSuccess(imageData) {
    var photo = getElement("pic");
    photo.style.display = "block";
    photo.src = imageData;
    $.mobile.changePage("#result_page", "slideup");
}
// camera.getPicture() callback function that provides an error message  
function onCaptureError(message) { }

On capture success the picture which was got is displayed on the Result screen. 

Temporary Files Removing

In iOS the pictures taken are stored in the application temporary folder. According to the convention, it is the developer's responsibility to clean up all the temporary files when they are not needed further. This application removes the temporary files each time it leaves the result page (it is also incorporated into the "exit" mode of the backbutton, but currently has no effect). The removing is performed by removeTemporaryFiles() method which calls the Camera.cleanup(onSuccess, onError) function. Camera.cleanup() should be used on iOS only, so iOS device detection is performed by isIOS() function:

// Removes all temporary files created by application. Is to be used when temporary files are not intended to be operated with further
function removeTemporaryFiles() {
    if (isIOS()) {
        navigator.camera.cleanup(onSuccess, onError); 
    }
    function onSuccess() {  }
    function onError(message) {  }
}
// Determines whether the current device is running iOS
function isIOS() {
    var iDevices = ["iPad", "iPhone", "iPod"];
    for (var i = 0; i < iDevices.length ; i++ ) {
        if( navigator.platform.indexOf(iDevices[i]) !== -1){ 
            return true; 
        }
    }
    return false;
}

Note:

Currently camera.cleanup() seems not to remove files on iPad, iOS 5 and 6 (though onSuccess() function is called, as well as in the case of other PhoneGap file-remove operations). Temporary directory is removed on application exit (e.g. on device switch off).

Back Button

As Android devices have the BackButton, its behaviour had to be redefined: if a user presses the BackButton having the Home screen active, the application exits skipping all the previous open pages history (here the removeTemporaryFiles method is incorporated where any actions may be added if neccesary, but in this version of the application it does not have any effect on Android). If the settings page is being left by the BackButton, the settings form restores its previous state:

// Overwrites the default behavior of the device back button
function onBackPress(e) {
    // Skip application history and exit application if the home page (menu page) is active
    if($.mobile.activePage.is("#home_page")){
        e.preventDefault();
        removeTemporaryFiles();
        navigator.app.exitApp();
    }
    else {
        // Do not save new cameraOptions and restore the previous state of the "settings_form" visual elements
        if ($.mobile.activePage.is("#settings_page")) {
            restoreSettings();
        }
        navigator.app.backHistory();
    }
}

onDeviceReady()

Calls to the PhoneGap compatible API (for example, calls to the Camera.getPicture() function) become safe only after the Cordova (PhoneGap) library is fully loaded and is ready to work with device. When it happens the "deviceready" event is fired. Because of that, most of the initializations and event bindings are postponed until that moment and are performed by the onDeviceReady() function which is a callback for the "deviceready" event:

index.html

<body onload="onLoad()"> ... </body>

camera.js

// Called on bodyLoad 
function onLoad() {
    document.addEventListener("deviceready", onDeviceReady, false);
    ....
}
// Called when Cordova is fully loaded (and calling to Cordova functions has become safe)
function onDeviceReady() {
    // Overwrite the default behavior of the device back button
    document.addEventListener("backbutton", onBackPress, false);
    fillSettingsInfo("settings_info");
    
   // Bind application button elements with their functionality
    ...              // binding elements and event callbacks 
}

Making the Application Pretty

The styles applied to the settings information table are stored in the camera.css. Other elements styles are defined with the help of jQuery Mobile library.

Test Results

The table below shows the CameraOptions support by the operating systems which were tested.

Notation:

+ -- the option is fully supported;

- -- the option is not supported.

Additional comments are provided in brackets:

Android 2.x and Android 4.x Devices Apple iOS* 5 Devices
quality + + (affects only temporary pictures. Pictures saved to photo album always are of a high quality)
destinationType  + +
sourceType  + (but Camera.PictureSourceType.PHOTOLIBRARY and Camera.PictureSourceType.SAVEDPHOTOALBUM open the same source) +
allowEdit  - +
encodingType - (a picture is always saved to JPG) - (a picture is always saved to JPG)
targetWidth + +
targetHeight + +
mediaType + +
correctOrientation + (but on Android 2.3 the option works only the first time the picture is captured with the getPicture() method. Possible problems: EXIF information is corrupted during the method call) - (pictures always have the correct orientation even if correction is disabled)
saveToPhotoAlbum + (works only if Camera.PictureSourceType.CAMERA and Camera.DestinationType.FILE_URI are chosen) +
popoverOptions - (not applicable: the option is iPad specific) + (this option is iPad specific)

Note:

Currently cameraOptions produce no effect and can not be tested in Ripple Emulator.

Devices Tested

  • Mobile devices:
    • Apple iPad* 2 tablet (iOS 5.1.1)
    • Sony Ericsson* Go smartphone (Android 2.3.7)
    • Samsung Galaxy Tab* 2 tablet (Android 4.0.3)