The PhoneGap Camera API provides an application with the ability to work with images, either captured directly from the camera or retrieved from the device’s photo repository. When retrieving an image, the API can return either a URI pointing to the image file on the device’s file system or the base64-encoded string representing the content from the image.
The API provides a single method, navigator.camera.getPicture,
which is used to retrieve an image, and a cameraOptions
object that’s used to define parameters around how the image is obtained, how it’s formatted, and more.
Applications can also use the PhoneGap Capture API to capture images using the camera. Refer to Chapter 12 for more information about this API. The Camera and Capture APIs are different enough that you will want to evaluate both before selecting an option for your application.
To obtain a picture from the device, an application should execute the following function:
navigator.camera.getPicture( onCameraSuccess, onCameraError,
cameraOptions );
Like other PhoneGap APIs, the call to getPicture
requires that you pass in two functions that are executed on success and failure of the call. In this case, they’re the onCameraSuccess
and onCameraError
functions. The onCameraSuccess
function is executed when an image is obtained (I’ll explain more about where the images come from and how you configure the API in the “Configuring Camera Options” section later in this chapter). The onCameraError
function is executed when the user cancels the process of retrieving an image once started or when an error occurs with the process.
Example 11-1 shows the Camera API being used with its default options. According to the PhoneGap API documentation, the cameraOptions
parameter is optional, but that turns out to be true for some platforms and false on others. Let’s take a look at the example application and then discuss the exceptions afterward.
<!DOCTYPE html>
<html>
<head>
<title>Example 11-1</title>
<meta http-equiv="Content-type" content="text/html;
charset=utf-8">
<meta name="viewport" id="viewport"
content="width=device-width, height=device-height,
initial-scale=1.0, maximum-scale=1.0, user-scalable=no;"
/>
<script type="text/javascript" charset="utf-8"
src="phonegap.js"></script>
<script type="text/javascript" charset="utf-8">
function onBodyLoad() {
document.addEventListener("deviceready", onDeviceReady,
false);
}
function onDeviceReady() {
navigator.notification.alert("onDeviceReady");
}
function takePhoto() {
navigator.camera.getPicture(onCameraSuccess,
onCameraError);
}
function onCameraSuccess(imageURL) {
navigator.notification.alert("onCameraSuccess: " +
imageURL);
}
function onCameraError(e) {
navigator.notification.alert("onCameraError: " + e);
}
</script>
</head>
<body onload="onBodyLoad()">
<h1>Example 11-1</h1>
<p>Using the PhoneGap Camera API<br />
<input type="button" value="Take a Picture"
onclick="takePhoto();">
</p>
</body>
</html>
In this application, there’s a simple page with a button that the user clicks to take a picture using the camera. When the button is clicked, the takePhoto
function is executed, which simply calls the getPicture
method, passing in the onCameraSuccess
and onCameraError
functions.
In this example, we’re not passing in a cameraOptions
object, so getPicture
will just use the default options of getting the image from the camera and returning a file URI pointing to where the image was stored after it was taken. Once an image has been obtained from the camera, the onCameraSuccess
function is called and passed to the URI pointing to the image file that was just created. In your applications, you’ll probably do something with the image URI, but in this case all the application does is display an alert and show the file URI passed to the function.
Figure 11-1 shows the Example 11-1 application running on an Apple iPhone.
When you click the Take a Picture button, there’s a slight delay, and then the standard camera application will open and allow you to take a picture. The delay can be quite long, so your application may want to display a “please wait” screen before calling the API. Once a picture has been taken, iOS will display the preview window shown in Figure 11-2. At this point, you can either retake the picture or click the Use button to return to the PhoneGap application.
Notice from the figure that there’s no way to cancel the process at this point. If the user initiates the taking of a picture in a PhoneGap application running on iOS, there’s no way to cancel the process and not take a picture.
Counterintuitive Process
In my testing, the picture capture process was not very user friendly; only BlackBerry provided an intuitive interface for this part of the process. On BlackBerry, after you take the picture, you’re immediately returned to the PhoneGap application.
For iOS and Android, you’re presented with a preview window you can use to validate that the picture is the one you want. While this is a good thing from the user’s standpoint, the way you transition from the preview screen back to the PhoneGap application can be a counterintuitive part of the process.
On iOS, you have to click the Use button shown in Figure 11-2, which makes some sense but may not be completely clear to the user what “use” means. On some flavors of the Android OS, there’s no label on the button; you have to know to click the paper clip icon highlighted in Figure 11-6. Fortunately, some Android devices display OK, Retake, and Cancel buttons on the preview window.
Be aware of these inconsistencies and take them it into account when creating applications that leverage the camera.
On iOS, when control returns to the calling program, the application displays an alert and shows the file URI for the image file just created, as shown in Figure 11-3.
One of the things to note about the iOS version of the application is that the file URI returned to the program references a temporary location that is available only to the application. If you take a look at Figure 11-3, you’ll see that the file URL refers to the following:
file://localhost/var/mobile/Applications/
169DF9CB-25D0-4EC8-85B2-380A6342E08D/tmp/photo_001.jpg
In this file URI, the file://localhost/var/mobile/Applications/
location refers to a file system area allocated to application data. The 169DF9CB-25D0-4EC8-85B2-380A6342E08D
part refers to a unique identifier associated with each iOS application. The tmp
folder refers to a temporary storage location allocated to the application; when the application closes, there’s a high likelihood that the temporary storage allocated to the application will be cleared, and you will lose access to the image file. If your application needs access to the image file at a later time, it will need to make a copy of the image file (using the File API described in Chapter 18) in a less volatile location before the application closes.
Absent Camera Simulators
One of the frustrating things about the iOS simulator and older Android emulators is that Google and Apple omitted camera simulators in their simulation products. When testing an application that uses the PhoneGap Camera API on one of these products, it will fail, even though the real devices support the capability. Newer Android emulators have apparently been outfitted with a camera simulator.
Fortunately, on iOS, the PhoneGap device.name
property (described in Chapter 16) will accurately report whether the application is running on a physical device or a simulator. An application could detect when it’s running on a simulator and retrieve an image from the photo library instead of the camera.
For Android, there’s no direct way to determine whether the application is running on an emulator or a physical device. When testing camera functionality, the emulators won’t work; you’ll have to resort to on-device testing exclusively.
When you run Example 11-1 on an Android or BlackBerry device, you’ll have problems. In my testing, on Android it takes a picture but then crashes the PhoneGap application as it returns picture information to the application. On BlackBerry, it won’t even take the picture; you click the button, and nothing happens. Apparently the default value for Camera.DestinationType
in cameraOptions
for those two platforms is DATA_URL,
which, because of memory limitations described elsewhere in this chapter, will cause an application to crash when a picture is taken at full resolution. This bug has been identified and should be fixed in PhoneGap 1.4.
To make the application work on Android and BlackBerry, you must modify the call to getPicture
to include a simple cameraOptions
object, as shown in the following example:
function takePhoto() {
navigator.camera.getPicture(onCameraSuccess, onCameraError,
{quality: 50,
destinationType: Camera.DestinationType.FILE_URI }
);
}
With that in place, you can run the application on BlackBerry and then click the Take a Picture button to see a screen similar to the one shown in Figure 11-4.
When you click the camera button at the bottom middle of the screen, the captured image will be returned to the PhoneGap application, as shown in Figure 11-5. Notice from the figure that the image file is stored in the default BlackBerry photo storage location, so unlike iOS, any pictures taken by the application will be available after the application terminates.
If you do not want your application’s photos to be left lying around after your application closes, you will need to manually delete the image file(s) once your application is through processing them. The application can use the PhoneGap File API (described in Chapter 18) to delete the file after the application is done with it.
Figure 11-6 shows Example 11-1 running on an Android device. In this case, the picture has already been taken, and what’s shown is the picture preview window that the Android OS provides users. The frustrating part of what’s shown in the figure is that from the user’s standpoint, it’s hard to know what to do next here. It’s likely clear to the user that he is previewing a picture he just took (he did just take the picture after all), and it’s possible that he will figure out that he can take another picture by clicking the camera image and can delete the image by clicking the trash can icon. The purpose of the paper clip icon, highlighted in Figure 11-6, is unclear, but when you click it, information about the selected image is returned to the PhoneGap application.
You might be asking yourself, what do I do with this image file URI once I get it back from the camera? Well, it’s a file pointer pointing to an image file, so once the application knows where the file is, it can read from the file, copy it somewhere else (using the PhoneGap File API, described in Chapter 18), or even pass the file URI to the PhoneGap application’s UI to display the image within the application.
Example 11-2 is a slightly modified version of Example 11-1. In this version, when the image URI is returned to the application, an HTML image tag is written to the index.html
page so the captured image will appear on the screen.
<!DOCTYPE html>
<html>
<head>
<title>Example 11-2</title>
<meta http-equiv="Content-type"
content="text/html; charset=utf-8">
<meta name="viewport" id="viewport"
content="width=device-width, height=device-height,
initial-scale=1.0, maximum-scale=1.0, user-scalable=no;"
/>
<script type="text/javascript" charset="utf-8"
src="phonegap.js"></script>
<script type="text/javascript" charset="utf-8">
function onBodyLoad() {
document.addEventListener("deviceready", onDeviceReady,
false);
}
function onDeviceReady() {
navigator.notification.alert("onDeviceReady");
}
function takePhoto() {
navigator.camera.getPicture(onCameraSuccess,
onCameraError,
{quality : 50,
destinationType : Camera.DestinationType.FILE_URI});
}
function onCameraSuccess(imageURL) {
//Get a handle to the image container div
ic = document.getElementById('imageContainer'),
//Then write an image tag out to the div using the
//URL we received from the camera application.
ic.innerHTML = '<img src="' + imageURL +
'" width="50%" />';
}
function onCameraError(e) {
console.log(e);
navigator.notification.alert("onCameraError: " + e +
" (" + e.code + ")");
}
</script>
</head>
<body onload="onBodyLoad()">
<h1>Example 11-2</h1>
<p>
Using the PhoneGap Camera API
<br />
<input type="button" value="Take a Picture"
onclick="takePhoto();">
<div id="imageContainer"></div>
</p>
</body>
</html>
The major change is in the onCameraSuccess
function shown here. It’s been rewritten so it grabs the content of the imageContainer <div>
and then replaces it with an <img>
tag that references the file URI returned by the getPicture
function.
function onCameraSuccess(imageURL) {
//Get a handle to the image container div
ic = document.getElementById('imageContainer'),
//Then write an image tag out to the div using the
//URL we received from the camera application.
ic.innerHTML = '<img src="' + imageURL + '" width="50%" />';
}
Figure 11-7 shows the application running on an Android device.
Now that you know how to take pictures using the camera, let’s talk about options you can use to configure how the process works. As you may remember from the previous section, when calling getPicture,
a developer can pass in a cameraOptions
object that defines parameters controlling how the picture is obtained. The cameraOptions
object supports the following properties:
• quality
• destinationType
• sourceType
• allowEdit
• encodingType
• targetWidth
• targetHeight
• mediaType
Each of these options will be described in greater detail in the following sections. Like with many other features of PhoneGap APIs, certain API options (such as allowEdit
in the Camera API) apply on only a limited number of mobile platforms.
Here’s an example of a fully configured cameraOptions
object you could use in one of your PhoneGap applications:
var cameraOptions = { quality : 75,
sourceType : Camera.PictureSourceType.CAMERA,
destinationType : Camera.DestinationType.FILE_URI,
allowEdit : true,
encodingType: Camera.EncodingType.JPEG,
targetWidth: 1024,
targetHeight: 768 };
When passed to the getPicture
function, this cameraOptions
object tells getPicture
to get the picture from the camera (sourceType),
return a file URI that points to the image file captured (destinationType),
allow the user to edit the picture before returning it to the program (allowEdit),
return the picture as a .jpeg
file (encodingType),
configure the encoded image file to use 75% image quality (quality),
and set the image dimensions to 1024 by 768 pixels (targetWidth
and targetHeight).
Now let’s describe each of the cameraOptions
properties in greater detail.
When working with smartphone cameras, higher-resolution optics in the camera lens plus limited memory storage and network bandwidth available to devices drove the need to be able to compress images so they took up less storage space and transmission bandwidth. As part of this compression process, standards such as the JPEG specification included support for using image quality to control compression rates when an image file is being saved. By using different image quality settings, defined as percentages, you can dramatically affect the physical size of an image file.
An image quality of 100% shows the image at its full capacity, with no reduction in image quality, and gives you the best possible picture. As you reduce the image quality, you will see some degradation in clarity in the image, but for most purposes it will be acceptable—only smaller in file size.
The quality
parameter allows a developer to specify the percentage image quality for a picture captured using the Camera API. In most cases, you will use values from 50% to 100% for your images. This is not so much because you care about image quality, but more because you need to reduce image quality in order to reduce image file size.
As you’ll see in the following section, developers can have an image file URI returned from a call to getPicture
or the actual raw, base64-encoded image file data. Using the image file URI is easy; it’s just a file pointer, and you’ve already seen examples here of how to use it in your applications. When obtaining raw image data from getPicture,
you have to deal with the fact that the device and the JavaScript interpreter on the device have limits on how much data they can process. As newer smartphones get higher and higher resolution cameras, you must reduce image quality so that a PhoneGap application can successfully process the returned image data. When processing raw data from a high-resolution picture at 100% quality, you’d be processing a huge string, and the application might just crash without telling you why (like we saw when using default options for cameraOptions
in Example 11-1). When you reduce image quality, you reduce the amount of data the application must process and increase the likelihood it will actually work.
Unfortunately, there is no guideline I can give you for how much you have to reduce your image quality to guarantee success. You’ll just have to guess and test and know that the value may differ on different platforms and even on different devices on the same platform. The folks working on PhoneGap recommend using 50% image quality (or lower) when working with raw image data.
To configure a cameraOptions
object to use a picture quality of 50%, use the following code:
quality : 50
According to the PhoneGap documentation, this option is ignored on the BlackBerry platform.
When capturing an image using getPicture,
applications will use destinationType
to control whether the image information is returned as a file URI pointing to the image file stored in device memory:
destinationType: Camera.DestinationType.FILE_URI
To receive the picture’s image data as a base64-encoded string value, use the following:
destinationType: Camera.DestinationType.DATA_URL
Working with file URIs is easy, as shown in Example 11-2. The application has a file pointer than can be manipulated within the application either by populating an HTML img
tag or when using the File API to copy the file to another location. Once you know where the file is, accessing the image file is a simple process.
Figure 11-8 shows the output from Example 11-1 when a destinationType
of Camera.DestinationType.DATA_URL
is used. As you can see from the figure, what you have to work with is just a huge string, which, as mentioned in the previous section, may cause memory overflow and crash your program if the string is too big.
Using this raw image data, you can still render the picture in the UI, but you’re more likely going to want to either store the data in a database or upload the data to a file server. There’s just too much risk in trying to manipulate the image on the mobile device.
The sourceType
parameter is used to define where getPicture
gets its picture from. When sourceType
is omitted, getPicture
will simply use the camera (Camera.SourceType.CAMERA)
to grab the picture. Applications can specify to use the device’s photo library using the following:
sourceType : Camera.SourceType.PHOTOLIBRARY
To retrieve photos from a saved photo album, use the following:
sourceType : Camera.SourceType.SAVEDPHOTOALBUM
On most platforms, specifying a sourceType
of SAVEDPHOTOALBUM
or PHOTOLIBRARY
does essentially the same thing. As shown in Figure 11-9, when the application makes a call to getPicture,
the device will open the photo library application and allow the user to first select a photo album and then select a single picture before returning the selected picture to the PhoneGap application.
On iOS devices, the two operate differently. When a sourceType
of PHOTOLIBRARY
is specified, the application behaves similarly to what is highlighted in Figure 11-9. When specifying a sourceType
of SAVEDPHOTOALBUM,
the application will open the standard iOS Camera Roll photo library and allow the user to select a picture from there.
According to the PhoneGap documentation, this option is ignored on the BlackBerry platform.
An iOS application can use the allowEdit
option to instruct getPicture
to allow the user to edit the selected image before returning it to the PhoneGap application. To configure a cameraOptions
object for this option, use the following:
allowEdit : true
Once enabled in an application, after the camera takes a picture, the device will display a screen similar to the one shown in Figure 11-10. At this point, the user can pinch, prod, and slide the picture around to fit the portion of the image they want to capture into the reticle shown in the figure. When the user clicks the Choose button, the edited picture is returned to the calling PhoneGap application.
A PhoneGap application uses the encodingType cameraOption
to tell getPicture
what kind of picture to take. Supported options are JPEG
and PNG,
with JPEG
being the default on most, if not all, platforms. To configure getPicture
to return a JPEG file, use the following:
encodingType: Camera.EncodingType.JPEG
To use PNG files, use the following:
encodingType: Camera.EncodingType.PNG
This option is not supported on all platforms; refer to the PhoneGap documentation for specifics.
The targetHeight
and targetWidth
parameters control the height and width of the image obtained using getPicture.
You can set either targetHeight
or targetWidth,
and the image will be scaled accordingly. If you specify both, the image will be scaled to the one that results in the smallest aspect ratio. Either way, the aspect ratio will be maintained.
Since there’s no way to programmatically determine the camera resolution or the supported aspect ratio before taking a picture, there is therefore no way to accurately set these values within an application without guessing or direct testing on each supported device.
To define a cameraOptions
object that specifies targetHeight
and targetWidth
for the image, use the following code:
targetHeight: 100, targetWidth: 100
Since many modern smartphones can typically store multiple media types in a photo library or photo library, the PhoneGap Camera API supports the addition of a mediaType
value in the cameraOptions
object in cases where the sourceType
is set to PHOTOLIBRARY
or SAVEDPHOTOALBUM.
The parameter supports the following options:
• DEFAULT
: Returns image information using the format specified in the destinationType
value
• ALLMEDIA
: Allows selection from all media types
• PICTURE
: Allows the selection of photographs only
• VIDEO
: Allows selection of video files only
When the option for VIDEO is selected, only a file URI will be returned to the calling program. Returning the raw video image data in a JavaScript String variable would certainly overload the JavaScript interpreter included in the browser and would most likely crash the application.
As with any computer or smartphone development, there are lots of places where things can go wrong. The purpose of this section is to highlight some of the ways you can tell what’s going on when the Camera API fails.
When the onCameraError
function fires, the Camera API passes in an error object that can be queried to determine the cause of the error. As shown in Figure 11-11, the error is a simple text message that tells what happened. In this case, the user clicked the Cancel button in Figure 11-10, so there’s no image information to return to the PhoneGap application.
When the application runs on a device that doesn’t have a camera, you will see an error similar to the one shown in Figure 11-12.
If your application is running on a device that doesn’t have a camera, it’s likely, but not guaranteed, that the onCameraError
function will be executed by the Camera API. If an application fails and you’re not sure why, don’t forget that the console log may contain information that can help. Figure 11-13 shows a portion of the iOS console with Example 11-1 running. Notice that when the I click the Take a Picture button, the console logs an error indicating that source type 1 (the camera) is not available.
This is one of those weird examples where even though the device supports a camera, Apple hasn’t decided it’s important enough to include that functionality in the device simulator. In this case, to be able to test on the iOS simulators, your application will need to check to see which device it’s running on and use a photo library rather than the camera in cases where the camera is not available.
If your application seems to be running properly but when you take a picture nothing happens or the application crashes, it’s likely caused by the application returning raw camera data (rather than a file URI) and the device isn’t capable of processing a string of that size. When this happens, try cranking down image quality (using the cameraOption quality
setting) to 50% or less to see whether this fixes the problem. If it does, then you’re going to have to do some work to determine the optimal image quality setting for your application and the devices it’s running on.