PhoneGap(Cordova)で CameraAPI まわりの調査と Android エミュレータでの確認

とりあえず公式ドキュメントを読む

PhoneGap(Cordova)はわりと公式ドキュメントが親切だしそれほど難しくはないので素直に読んでいけばいい。
公式ドキュメント::Apache Cordova API Documentation

CameraAPI と CaptureAPI がある

基本的にはカメラを使おうとしてるから CameraAPI でいいんだけど、CaptureAPI というのは音声、動画、画像をひっくるめた API という感じらしい。カメラは使用頻度が高いから別途用意されているという感じなのかな。実際 CameraAPI は画像をとるだけでなくカメラロールからの選択などの API を提供してくれているし画質設定など各種オプションも高機能に用意されている。
参考::ASCII.jp:PhoneGapでiPhoneのカメラアプリを作ろう|古籏一浩のJavaScriptラボ

CameraAPI を使う

もうこれは正直公式ドキュメントそのままですね。PhoneGap(Cordova)の基本にのっとった形で「パーミッションを解除しておく」「画像をとるイベントを発火する」「それぞれ成功時と失敗時のコールバック関数を用意しておく」というそれだけ。ただ、data スキームで保存される形態と URI で指定するというあたりと、写真を撮るのかカメラロールからとってくるのかというアクションの違いがある。で、写真をとった場合はアプリの一時ファイルに保存される。また写真をとった直後はカメラロールに保存されないので別途保存する必要がありそう。

Android エミュレータでのカメラの使用

まず ADV で SD カードを作成してマウントしておく。でないと一時ファイルも保存できない。で、Android 2.x 系 3.x 系まではエミュレータでカメラを扱おうとするにはけっこうなハックが必要っぽい感じだったけど、 4.x 系(API 14)からはコマンドラインからオプションを渡してやることでウェブカメラからカメラを扱えるのでとても楽。なので、今回は 4.0 で作業した。だから普段の挙動は 4.x でたしかめて 2.x は実機のテストでいいんじゃないかなとか思う。で、 4.x 系でのエミュレータでカメラを使えるようにするには以下のエントリを参考にした。
参考::Android エミュレータで、Web カメラを使って撮影して画像を取り出す。 : logical error
参考::エミュレータでPCのウェブカメラを使う | Tech Booster
ただし SDK のバージョンの違いなのか、ちょっと引数が違ったので手元の環境では以下のようにした。

# SDK にコマンドがあるのでそれのヘルプを見る
/path/to/android-sdk/tools/emulator -help |less
# ウェブカメラデバイスの確認
# emulator ADVの名前 -webcam-list
/path/to/android-sdk/tools/emulator @4x -webcam-list
# List of web cameras connected to the computer:
# Camera 'webcam0' is connected to device 'webcam0' on channel 0 using pixel format 'UYVY'
# なので webcam0 を指定して ADV を起動
/path/to/android-sdk/tools/emulator @4x -camera-front webcam0

これでおk。

画像を保存して扱う

これは以下の技評での連載で一連の流れが乗ってるのでそのままだと思う
技評::もっと使おうPhoneGap/Cordova 2.0.0|gihyo.jp … 技術評論社
FileAPI は前に扱った。これも素直だけどこうして組み合わせて考えなきゃいけないのは若干面倒だけど、まあ仕方ない。ポイントはカメラでとった画像は最初は一時ファイルとして保存されるからそれを永続的なファイルとして扱うように移動してやるところか。具体的に該当の部分を引用する。

var persistentDirectoryEntry;
document.addEventListener('deviceready', init, false);

function init() {
  // あらかじめPERSISTENTファイルシステムを要求し,DirectoryEntryオブジェクトを取得しておく
  window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, getPersistentDirectoryEntry, failFS);

  document.getElementById('camera').addEventListener('click', camera, false);
}

function getPersistentDirectoryEntry(fileSystem) {
  persistentDirectoryEntry = fileSystem.root;
}

function camera() {
  sourceType = Camera.PictureSourceType.PHOTOLIBRARY;
  if ( 'Android' === device.platform ) {
    sourceType = Camera.PictureSourceType.CAMERA;
  }
  navigator.camera.getPicture(getPictureSuccess, failCamera,
  {
    quality: 50,
    destinationType: Camera.DestinationType.File_URI,
    sourceType: sourceType
  });
}

function getPictureSuccess(uri) {
  // Camera APIから渡されるファイルパスと,resolveLocalFileSystemURIメソッドを用いて
  // TEMPORARYファイルシステムに作成された画像ファイルのFileEntryオブジェクトを取得する
  window.resolveLocalFileSystemURI(uri, moveToPersistent, failFS);
}

function moveToPersistent(fileEntry) {
  // あらかじめ取得しておいたPERSISTENTファイルシステムのDirectoryEntryオブジェクトと
  // resolveLocalFileSystemURIメソッドで取得したFileEntryオブジェクトを用いて,ファイルをPERSISTENTに移動
  fileEntry.moveTo(persistentDirectoryEntry, fileEntry.name, moveToSuccess, failFS);
}

function moveToSuccess(fileEntry) {
  alert('画像ファイル「' + fileEntry.name + '」を移動しました。\n' + fileEntry.fullPath);
}

function failCamera(message) {
  alert('カメラ操作に失敗しました。\n' + message);
}

function failFS(error) {
  alert('ファイルシステム操作に失敗しました。\nエラーコード: ' + error.code);
}

やってることは素直だけどこうして組み合わせて考えると戸惑うので、こういうサンプルはとてもありがたい。特に window.resolveLocalFileSystemURI メソッドを使う辺りは言われてみればたしかにという感じ。

まあ

こうしてまとめるとそれほど苦難ではないですね。4.x 系でエミュレータでのデバッグが楽になったのもすごく大きい。まあなにに時間をとられたかというと設定を変更してはエミュレータを再起動して動かしてってのがいちいち重いのがアレですね(これでも MacBookAir 2012 なので SSD としてもかなり高速なはずなんだけど)。