Androidでcwac-cameraを使って写真撮影する


# ざっくり概要
- [commonsguy/cwac-camera](https://github.com/commonsguy/cwac-camera)というカメラ用のライブラリを使う
- Androidで写真を撮る
	- 動作設定をする
	- 起動・撮影・保存する
- 撮影した写真をあとで使う
	- イベントバスの[Otto](http://square.github.io/otto/)を使う

# カメラの動作設定

基本的には
[cwac-camera/SimpleCameraHost.java](https://github.com/commonsguy/cwac-camera/blob/master/camera%2Fsrc%2Fcom%2Fcommonsware%2Fcwac%2Fcamera%2FSimpleCameraHost.java)

をベースに手を加えれば良さそう。

## SingleShotMode

SingleShotModeとは、一般的なカメラアプリのように何枚も撮影するのではなく、
写真を一枚だけ撮影するモードのことで、`useSingleShotMode`によって切り替えられる。
SingleShotModeの場合であれば、撮影した後にその撮影した写真を撮影領域に貼ってくれる。
そうでない場合は、また普通に撮影領域が動き出して、繰り返し撮影できる状態になっている。
例えばSingleShotModeしか使わないのであれば、`SimpleCameraHost#useSingleShotMode`を
Overrideして

```Java
@Override
public boolean useSingleShotMode() {
    return true;
    // return super.useSingleShotMode();
    // デフォルトではfalse
}
```

のようにすれば、SingleShotModeに固定することも出来る。
また、`SimpleCameraHost`は[Builder パターン - Wikipedia](http://ja.wikipedia.org/wiki/Builder_%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3)でインスタンスを生成できるようになっているため、`new SimpleCameraHost.Builder(this).useSingleShotMode(false).build();`等としても良い。

## onAutoFocus時の音

デフォルトだとAutoFocus時に音がなってうるさいため、

```Java
@Override
public void onAutoFocus(boolean success, Camera camera) {
    // super.onAutoFocus(success, camera);
}
```

とでもしておけばいい
なお、シャッター時の音は消し方が分からなかった。

## 写真のサイズ

`SimpleCameraHost#getPictureSize`によって変更できる

デフォルトだと

```Java
public Camera.Size getPictureSize(PictureTransaction xact, Camera.Parameters parameters) {
    return(CameraUtils.getLargestPictureSize(this, parameters));
}
```

となっており、メソッド名からして最大サイズの画像を撮ろうとしている。
普通のカメラアプリとか、画質が求められるようなアプリケーションであればこのままでいいはず。

汚くてもいいから画像サイズを小さくしたいという場合に、ひとまず写真のサイズを
画面サイズに合わせたものにする

[dm77/barcodescanner](https://github.com/dm77/barcodescanner)の[CameraPreview#getOptimalPreviewSize](https://github.com/dm77/barcodescanner/blob/master/core%2Fsrc%2Fmain%2Fjava%2Fme%2Fdm7%2Fbarcodescanner%2Fcore%2FCameraPreview.java)を大いに参考にさせてもらった。
参考、というよりそのまま拝借している。
[barcodescanner/LICENSE](https://github.com/dm77/barcodescanner/blob/master/LICENSE)にあるようにApache License2.0なので問題ないはず...

```Java
@Override
public Camera.Size getPictureSize(PictureTransaction xact, Camera.Parameters parameters) {
    List<Camera.Size> sizes = parameters.getSupportedPictureSizes();
    Point screenResolution = DisplayUtils.getScreenResolution(getContext());
    int w = screenResolution.x;
    int h = screenResolution.y;

    final double ASPECT_TOLERANCE = 0.1;
    double targetRatio = (double) w / h;
    if (sizes == null) return null;

    Camera.Size optimalSize = null;
    double minDiff = Double.MAX_VALUE;

    int targetHeight = h;

    for (Camera.Size size : sizes) {
        double ratio = (double) size.width / size.height;
        if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
        if (Math.abs(size.height - targetHeight) < minDiff) {
            optimalSize = size;
            minDiff = Math.abs(size.height - targetHeight);
        }
    }

    if (optimalSize == null) {
        minDiff = Double.MAX_VALUE;
        for (Camera.Size size : sizes) {
            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }
    }
    return optimalSize;
}
```

## 写真を保存するパス, ファイル名

- ディレクトリ
	- `getPhotoDirectory`
- ファイル名
	- `getPhotoFilename`

をOverrideすれば良い。

```Java
@Override
protected File getPhotoDirectory() {
    // SDカード内のこのアプリ用ディレクトリを取得し、その中にphotoというディレクトリを作成する
    return context.getExternalFilesDir("photo");
}

@Override
protected String getPhotoFilename() {
    // TimeZoneを合わせておく
    String ts=
            new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.JAPAN).format(new Date());
    return("myapp_photo_" + ts + ".jpg");
}
```

## 写真を保存する

`saveImage`メソッドを利用する

```Java
public void saveImage(com.commonsware.cwac.camera.PictureTransaction xact, android.graphics.Bitmap bitmap);

public void saveImage(com.commonsware.cwac.camera.PictureTransaction xact, byte[] image);
```

の2つが用意されており、写真撮影時に`Bitmap`か`byte[]`を選択することで上記の`saveImage`が適切に呼ばれるが、デフォルトでは`byte[]`を使うようになっている上に、`Bitmap`用の`saveImage`は以下の様な実装になっている。

```Java
@Override
public void saveImage(PictureTransaction xact, Bitmap bitmap) {
    // no-op
}
```

加工することも考えると`Bitmap`を使ったほうがいいような気もするので、

```Java
@Override
public void saveImage(PictureTransaction xact, Bitmap bitmap) {
    ByteArrayOutputStream stream = new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
    byte[] image = stream.toByteArray();
    super.saveImage(xact, image);
}
```

とでもすれば動く。
ちなみに、`bitmap.compress`の第二引数は圧縮率なので、ここで100より小さい値を与えれば小さく出来る。
[Bitmap | Android Developers](http://developer.android.com/reference/android/graphics/Bitmap.html#compress(android.graphics.Bitmap.CompressFormat,%20int,%20java.io.OutputStream))

# 撮影する

## カメラの起動

`Activity`上で`CameraFragment`のインスタンスを`commit`すれば良い。
なお、カメラの撮影領域のレイアウトidは`photo_frame`としてある。

```Java
manager = getFragmentManager();
cameraFragment = new CameraFragment();
cameraFragment.setHost(new CustomCameraHost(this, manager));
manager.beginTransaction().add(R.id.photo_frame, cameraFragment).commit();
```

これだけで`photo_frame`部分がカメラとして動き出す。

## AutoFocus

```Java
private void autoFocus() {
    if (cameraFragment != null && cameraFragment.isVisible()) {
        // if (!cameraFragment.isAutoFocusAvailable()) {
        //     cameraFragment.cancelAutoFocus();
        // }
        cameraFragment.cancelAutoFocus();
        cameraFragment.autoFocus();
    }
}
```
こんな感じで`autoFocus`メソッドを用意しておく。
`cameraFragment.autoFocus`する前に`cancelAutoFocus`を呼んでいるが、
フォーカスしている最中に再度フォーカスしようとすると落ちるためである。

```Java
findViewById(R.id.photo_frame).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        autoFocus();
    }
});
```

とでもしておけば、撮影領域をタップするとフォーカスするようになる。

## takePicture

```Java
private void takePicture() {
    if (cameraFragment != null && cameraFragment.isVisible()) {
        // cameraFragment.takePicture(boolean needBitmap, boolean needByteArray);
        cameraFragment.takePicture(true, false);  // bitmapで保存
        // cameraFragment.takePicture(false, true);  // byte[]で保存
    }
}
```

この`takePicture`メソッドを適当に`Button`の`onClick`で実行されるようにでもしておけば良い。
`cameraFragment.takePicture`の引数によって、上で説明した`saveImage`のどちらが呼ばれるか変わる。

### SingleShotModeの場合

SingleShotModeである場合、`cameraFragment.takePicture`が呼ばれると、撮影領域が撮影した写真に置き換わって確認画面となる。
もとに戻すためには`cameraFragment.restartPreview()`を呼べば良い。
撮影領域が再度撮影モードに切り替わる。

なお、確認画面にはなるものの写真はしっかりと保存されている。
なぜ、この確認画面で保存するかどうか選べるような設計になっていないのか...

# 撮影した写真の扱い

保存された写真のパスを親となる`Activity`で使いたい場合に、いい感じにするにはどうやればいいか分からなかった。
そこで[Otto](http://square.github.io/otto/)を使った。
`saveImage`が呼ばれたタイミングで`getPhotoPath()`の結果をpostするようにした。

### ottoのBusを拡張する

`saveImage`の際に`Bus`に`post`しようとするとCameraHostがMainのThreadではない、というエラーが出るので、
http://stackoverflow.com/questions/15431768/how-to-send-event-from-service-to-activity-with-otto-event-bus
を参考にして

```Java:MainThreadBus.java
public class MainThreadBus extends Bus {
    private static final String TAG = MainThreadBus.class.getSimpleName();
    private final Bus mBus;
    private final Handler mHandler = new Handler(Looper.getMainLooper());

    public MainThreadBus(final Bus bus) {
        if (bus == null) {
            throw new NullPointerException("bus must not be null");
        }
        mBus = bus;
    }

    @Override
    public void register(Object obj) {
        mBus.register(obj);
    }

    @Override
    public void unregister(Object obj) {
        mBus.unregister(obj);
    }

    @Override
    public void post(final Object event) {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            mBus.post(event);
        } else {
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    mBus.post(event);
                }
            });
        }
    }
}
```

を用意しておく。

### EventBus用のシングルトンも用意する。

```Java:EventBusHolder.java
public final class EventBusHolder {
    public static final MainThreadBus EVENT_BUS = new MainThreadBus(new Bus());
}
```

### イベント用のクラスを実装する

```Java:SaveImageEvent.java
public class SaveImageEvent {
    private final File photoPath;

    public SaveImageEvent(File photoPath) {
        this.photoPath = photoPath;
    }
    public File getPhotoPath() {
        return photoPath;
    }
}
```

## 写真のパスをBusにpostする

```Java:SimpleCameraHost#saveImage
EventBusHolder.EVENT_BUS.post(new SaveImageEvent(getPhotoPath()));
```

とでもするだけで良い。

## postされた写真のパスを受け取る

```Java:撮影したActivity
@Subscribe
public void onSaveImage(SaveImageEvent event) {
    Toast.makeText(getApplicationContext(), event.getPhotoPath().toString(),Toast.LENGTH_SHORT).show();
    photoPath = event.getPhotoPath();
}
```

これだけで良い。
こうすると保存された写真のパスが`Toast`で表示される。

# 感想

Androidでカメラ使う時、みんなどうしているんだろう。
今回使ったcwac-cameraについて日本語で情報がなかったし、もしかしたら他の便利なライブラリがあるんだろうか。