ざっくり概要
- commonsguy/cwac-cameraというカメラ用のライブラリを使う
- Androidで写真を撮る
- 動作設定をする
- 起動・撮影・保存する
- 撮影した写真をあとで使う
- イベントバスのOttoを使う
カメラの動作設定
基本的には
cwac-camera/SimpleCameraHost.java
をベースに手を加えれば良さそう。
SingleShotMode
SingleShotModeとは、一般的なカメラアプリのように何枚も撮影するのではなく、
写真を一枚だけ撮影するモードのことで、useSingleShotMode
によって切り替えられる。
SingleShotModeの場合であれば、撮影した後にその撮影した写真を撮影領域に貼ってくれる。
そうでない場合は、また普通に撮影領域が動き出して、繰り返し撮影できる状態になっている。
例えばSingleShotModeしか使わないのであれば、SimpleCameraHost#useSingleShotMode
を
Overrideして
@Override
public boolean useSingleShotMode() {
return true;
// return super.useSingleShotMode();
// デフォルトではfalse
}
のようにすれば、SingleShotModeに固定することも出来る。
また、SimpleCameraHost
はBuilder パターン - Wikipediaでインスタンスを生成できるようになっているため、new SimpleCameraHost.Builder(this).useSingleShotMode(false).build();
等としても良い。
onAutoFocus時の音
デフォルトだとAutoFocus時に音がなってうるさいため、
@Override
public void onAutoFocus(boolean success, Camera camera) {
// super.onAutoFocus(success, camera);
}
とでもしておけばいい
なお、シャッター時の音は消し方が分からなかった。
写真のサイズ
SimpleCameraHost#getPictureSize
によって変更できる
デフォルトだと
public Camera.Size getPictureSize(PictureTransaction xact, Camera.Parameters parameters) {
return(CameraUtils.getLargestPictureSize(this, parameters));
}
となっており、メソッド名からして最大サイズの画像を撮ろうとしている。
普通のカメラアプリとか、画質が求められるようなアプリケーションであればこのままでいいはず。
汚くてもいいから画像サイズを小さくしたいという場合に、ひとまず写真のサイズを
画面サイズに合わせたものにする
dm77/barcodescannerのCameraPreview#getOptimalPreviewSizeを大いに参考にさせてもらった。
参考、というよりそのまま拝借している。
barcodescanner/LICENSEにあるようにApache License2.0なので問題ないはず...
@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すれば良い。
@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
メソッドを利用する
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
は以下の様な実装になっている。
@Override
public void saveImage(PictureTransaction xact, Bitmap bitmap) {
// no-op
}
加工することも考えるとBitmap
を使ったほうがいいような気もするので、
@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
撮影する
カメラの起動
Activity
上でCameraFragment
のインスタンスをcommit
すれば良い。
なお、カメラの撮影領域のレイアウトidはphoto_frame
としてある。
manager = getFragmentManager();
cameraFragment = new CameraFragment();
cameraFragment.setHost(new CustomCameraHost(this, manager));
manager.beginTransaction().add(R.id.photo_frame, cameraFragment).commit();
これだけでphoto_frame
部分がカメラとして動き出す。
AutoFocus
private void autoFocus() {
if (cameraFragment != null && cameraFragment.isVisible()) {
// if (!cameraFragment.isAutoFocusAvailable()) {
// cameraFragment.cancelAutoFocus();
// }
cameraFragment.cancelAutoFocus();
cameraFragment.autoFocus();
}
}
こんな感じでautoFocus
メソッドを用意しておく。
cameraFragment.autoFocus
する前にcancelAutoFocus
を呼んでいるが、
フォーカスしている最中に再度フォーカスしようとすると落ちるためである。
findViewById(R.id.photo_frame).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
autoFocus();
}
});
とでもしておけば、撮影領域をタップするとフォーカスするようになる。
takePicture
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を使った。
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
を参考にして
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用のシングルトンも用意する。
public final class EventBusHolder {
public static final MainThreadBus EVENT_BUS = new MainThreadBus(new Bus());
}
イベント用のクラスを実装する
public class SaveImageEvent {
private final File photoPath;
public SaveImageEvent(File photoPath) {
this.photoPath = photoPath;
}
public File getPhotoPath() {
return photoPath;
}
}
写真のパスをBusにpostする
EventBusHolder.EVENT_BUS.post(new SaveImageEvent(getPhotoPath()));
とでもするだけで良い。
postされた写真のパスを受け取る
@Subscribe
public void onSaveImage(SaveImageEvent event) {
Toast.makeText(getApplicationContext(), event.getPhotoPath().toString(),Toast.LENGTH_SHORT).show();
photoPath = event.getPhotoPath();
}
これだけで良い。
こうすると保存された写真のパスがToast
で表示される。
感想
Androidでカメラ使う時、みんなどうしているんだろう。
今回使ったcwac-cameraについて日本語で情報がなかったし、もしかしたら他の便利なライブラリがあるんだろうか。