Androidのカメラを使うプログラムを書いてみた [Android開発]
Androidの開発を初めて、そろそろ一か月ぐらいが過ぎようとしています。そろそろ自分だけのアプリを書いてみようと思って、カメラを使ったアプリケーションを作ってみました。
こんな風にEvery Little Thingの持田香織さんを表示させて、いつでも2ショットが撮れてしまうと言うくだらないアプリです(笑)
アプリの開発のポイントをまとめました。
カメラの制御にはCameraクラスを使う。
カメラのプレビューを表示させるにはSurfaceViewクラスを使う。
マニフェストファイルにカメラを利用するためのパーミッションを追加。
オーバーレイ表示はカメラのプレビューの上に透明な画像を表示するだけで可能。
オーバーレイ表示とカメラから取得した画像を貼り付けて、一枚の画像として保存する。
ポイントはたったこれだけなのです。実際書いてみると凄く簡単です。
カメラの制御
カメラはCameraクラスで制御しますが、このカメラの操作を行うタイミングはSurfaceViewが生成されたり、破棄されるタイミングに依存させます。そのため、SurfaceViewの状態を監視するリスナーを付けます。これらはonCreateメソッドの中でします。
SurfaceView surface = new SurfaceView(this);//以下はお決まりの実装。
SurfaceHolder holder = surface.getHolder();// SurfaceViewにリスナーを登録
holder.addCallback(surfaceListener);setContentView(surface);
SurfaceHolder.Callback リスナー
// カメラのプレビューを表示するSurfaceViewのリスナー
private SurfaceHolder.Callback surfaceListener = new SurfaceHolder.Callback() {// SurfaceViewが生成されたらカメラをオープンする
public void surfaceCreated(SurfaceHolder holder) {camera = Camera.open();try {
camera.setPreviewDisplay(holder);} catch (Exception e) {
e.printStackTrace();}}// SurfaceViewが破棄されるタイミングでカメラを開放する
public void surfaceDestroyed(SurfaceHolder holder) {camera.stopPreview();camera.release();camera= null;
}public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {
camera.startPreview();}
以上でカメラの映像を表示できるプログラムが完成します。
オーバーレイ表示
オーバーレイ表示はViewクラスをオーバーライドしたクラスを作り、透明な画像を表示するだけです。オーバーレイ表示に見立てたViewクラスは以下の通りです。
public class OverLayView extends View {private Bitmap icon;
int width;
int height;
public OverLayView(Context context) {
super(context);
setDrawingCacheEnabled(true);
icon = BitmapFactory.decodeResource(context.getResources(), R.drawable.mochida);setFocusable(true);
}protected void onSizeChanged(int w, int h, int oldw, int oldh){//ビューのサイズを取得
width= w;height= h;}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);
canvas.drawColor(Color.TRANSPARENT);//位置を調整しています。x,y軸を調整することで、表示位置を変更できます。
canvas.drawBitmap(icon, width-232,0, null);
}}
そして、onCreateで、surfaceviewを付けた直後ぐらいに
overlay = new OverLayView(this);addContentView(overlay, new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));
と記述して貼り付けてしまいます。これでカメラのプレビューの上に画像が表示されます。
保存
最後に保存ですが、シャッターボタンなどを押したタイミングでcameraクラスのtakePictureメソッドを実行させます。例えば、タッチイベントなどにすると以下のようになります。
public boolean onTouchEvent(MotionEvent event) {if (mCamera != null && mInProgress == false) {// イメージの取得を開始する。リスナーを設定する
mCamera.takePicture(mShutterListener, // シャッター後
rawListener, // Rawイメージ生成後
jpegListener); // JPEイメージ生成後
mInProgress = true;
}return super.onTouchEvent(event);}
takePictureの第2、第3引数にはそれぞれ、rawデータとjpegが生成された時のリスナーを付けます。つまり、それぞれのリスナー内で保存する処理を書きます。
PictureCallbackリスナー
// PictureCallback jpegなどが生成された後に呼び出される
private PictureCallback jpegListener= new PictureCallback() {@Overridepublic void onPictureTaken(byte[] data, Camera camera) {// 保存する処理
}};
保存処理
保存はカメラのプレビューイメージとオーバーレイイメージを重ね塗りして保存します。
//カメラのイメージ
Bitmap cameraMap = BitmapFactory.decodeByteArray(data, 0,data.length, null);
// オーバーレイイメージ viewから画像を取得。
Bitmap overlayMap = overlay.getDrawingCache();//空のイメージを作成
Bitmap offBitmap = Bitmap.createBitmap(cameraMap.getWidth(),cameraMap.getHeight(), Bitmap.Config.ARGB_8888);Canvas offScreen = new Canvas(offBitmap);
offScreen.drawBitmap(cameraMap,null,
new Rect(0, 0, cameraMap.getWidth(), cameraMap.getHeight()), null);offScreen.drawBitmap(overlayMap,null,
new Rect(0, 0, cameraMap.getWidth(), cameraMap.getHeight()), null);// 保存 "sample"はファイル名
MediaStore.Images.Media.insertImage(getContentResolver(),offBitmap, "sample", null);
問題点とまとめ
じつは問題点がありまして、作成後の画像ファイルの持田さんの画像が縦長になってしまっています。どうしてこんな事になってしまうのかまだ分かっていません。これはなんとかしたいのですが。。。
ですが、カメラのオーバーレイ表示とそれを保存する方法がいかに簡単に実装できるのか伝えることが出来ていたら幸いです。携帯電話でこんな処理がこんなに簡単にできてしまうAndroidは私のような3流プログラマでもそこそこ出来てしまうので、とても面白いと感じています。
私も買いました。Android開発者のバイブル的存在。
Android Hacks ―プロが教えるテクニック & ツール
- 著者: 株式会社ブリリアントサービス
- 出版社: オライリージャパン
- メーカー: オライリージャパン
Androidこのアプリがすごい!―本当に使えるアプリを厳選紹介!最新アプリカタログ (アスペクトムック)
- 出版社: アスペクト
- メーカー: アスペクト
すごいアプリですね!
使ってみたいのですが、マーケットなどで公開はされないのでしょうか?
by K3 (2010-11-30 15:29)
K3さん ありがとうございます。
いやいや(汗)ある意味凄いアプリですが、全然大した事ないですし、何より肖像権的にヤバイのでマーケットに公開する予定は無いです。
by taiseiko (2010-11-30 15:50)
それは残念です。
taiseikoさんのプログラムに刺激され、自分でもやってみようと
四苦八苦したのですが上手くいかず(笑)もう少し頑張ってみたいと思います。
これからもちょくちょくブログ拝見させてもらいますね。
by K3 (2010-11-30 16:27)
K3 さん
それでは、ちょっと縦長表示が直ったらソースコードの公開を考えてみますね。
自由に色々と変えてみてください。
by taiseiko (2010-11-30 16:50)
>taiseikoさん
なかなか思うようには行きませんね。
専門書を買って、もっと簡単な物から取り組んでみたいと思います。ソースコードの公開、楽しみにしています。
by K3 (2010-12-05 13:11)
K3さん
ちょっと別件がなかなか思うようにいかず、遅れてて申し訳ないです。
http://www.atmarkit.co.jp/fjava/rensai4/android17/android17_1.html
代わりと言ってはなんですが、この上の記事なんかは凄く参考になると思います。
by taiseiko (2010-12-05 22:00)
参考記事どうもありがとうございます。
あれから少し勉強して、再度取り組んでみました。
しかし、どうしても撮った写真にオーバーレイが重なりません。
つまりただ写真を撮ってるだけ、という状況です(笑)
もし差支えなければ、
>オーバーレイ表示とカメラから取得した画像を貼り付けて、一枚の画像として保存する。
この辺りについて、taiseikoさんが参考にされたサイトや書籍等ございましたら、お教え頂けませんでしょうか?
お手隙の時にコメント頂けると幸いです。
by K3 (2010-12-24 08:57)
K3さん
ソース公開遅れて申し訳ないです。
考えられるのはオーバレイのViewにsetDrawingCacheEnabled(true);
を書いていないような気がします。もしそこに問題が無いのでしたら、次は保存処理が間違っている可能性があります。
保存に関してはエントリーの中に書いてある保存処理をコピーして書けば良いのですが、簡単に説明しますと、空のバッファーイメージを作成して、そこにカメラの映像を先に描いた後に、オーバーレイ表示の画像を描くイメージです。
オーバーレイは透過PNGなので、それだけで重なった画像になります。
by taiseiko (2010-12-24 09:29)
お早いお返事ありがとうございます。
>setDrawingCacheEnabled(true);
これは記述しております。ですので問題は保存処理でしょうか。
overlay.getDrawingCache();を調べると
nullが返ってくるので、データの受け渡しが上手く行われていないのかもしれません。もう少し調べてみたいと思います。
P.S.ソースの件、楽しみにしてます。
by K3 (2010-12-25 07:20)
今回このようなカメラアプリを作りたいと思うのですが、いくつかの疑問があるので教えていただきたいです。
持田かおりさんの部分を違う画像に変更して作りたいのですが、
どうすればよろしいのでしょうか?
ご返信お待ちしております。お願いします。
by KKG (2011-11-30 10:29)
KKGさん
上のソースだとicon = BitmapFactory.decodeResource(context.getResources(), R.drawable.mochida);
で持田さんの画像を指定しています。
res/drawable/ 内に変更したい画像を置いて、プログラムから呼び出すと変更できます。その場合、R.drawable.変更したファイル名になります。ファイルは小文字が良いでしょう。
by taiseiko (2011-11-30 19:08)
ありがとうございます。
プログラムのエラーが生じるのですが、どうしてでしょうか?
教えていただけたら幸いです。
by KKG (2011-12-09 11:21)
KKGさん
どういうエラーなのか教えて頂けないと、私にも分からないですよ。
by taiseiko (2011-12-09 12:56)
このようなプログラムですけど、どこが間違っているか教えていただけませんか?
package jp.co.se.andoroid.Newcamera;
import android.app.Activity;
import android.os.Bundle;
SurfaceView surface = new SurfaceView(this);
SurfaceHolder holder = surface.getHolder();
holder.addCallback(surfaceListener);
setContentView(surface);
private@ SurfaceHolder.Callback surfaceListener = new SurfaceHolder.Callback() {
public void surfaceCreated(SurfaceHolder holder) {
camera = Camera.open();
try {
camera.setPreviewDisplay(holder);
} catch (Exception e) {
e.printStackTrace();
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
camera.stopPreview();
camera.release();
camera= null;
}
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
camera.startPreview();
}
public class OverLayView extends View {
private Bitmap icon;
int width;
int height;
public OverLayView(Context context) {
super(context);
setDrawingCacheEnabled(true);
icon = BitmapFactory.decodeResource(context.getResources(), R.drawable.annzenn);
setFocusable(true);
}
protected void onSizeChanged(int w, int h, int oldw, int oldh){
width= w;
height= h;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.TRANSPARENT);
canvas.drawBitmap(icon, width-232,0, null);
}
{
overlay = new OverLayView(this);
addContentView(overlay, new LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));
public boolean onTouchEvent;MotionEvent event; {
if (mCamera != null && mInProgress == false) {
mCamera.takePicture(mShutterListener,
rawListener,
jpegListener);
mInProgress = true;
}
return super.onTouchEvent(event);
}
private PictureCallback jpegListener= new PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
}
};
Bitmap cameraMap = BitmapFactory.decodeByteArray(data, 0,
data.length, null);
Bitmap overlayMap = overlay.getDrawingCache();
Bitmap offBitmap = Bitmap.createBitmap(cameraMap.getWidth(),
cameraMap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas offScreen = new Canvas(offBitmap);
offScreen.drawBitmap(
cameraMap,
null,
new Rect(0, 0, cameraMap.getWidth(), cameraMap.getHeight()), null);
offScreen.drawBitmap(
overlayMap,
null,
new Rect(0, 0, cameraMap.getWidth(), cameraMap.getHeight()), null);
MediaStore.Images.Media.insertImage(getContentResolver(),
offBitmap, "sample", null);
by KKG (2011-12-09 13:33)
KKGさん
OverLayViewクラスをインスタンス化している場所ですが、これはMainとなるActivity内のonCreateで行います。
上のサンプルでは
MainActivityクラスと
OverLayViewクラスから成り立っています。
どこまで理解されているのか分かりませんが、上のコードだとActivityがそもそも無いのではないでしょうか?
http://www.atmarkit.co.jp/fjava/rensai4/android17/android17_1.html
こちらのサンプルで画像を変更するところからはじめるのをオススメします。
by taiseiko (2011-12-09 15:50)
このプログラムは、いくつのソースコードを使用しているのですか?
できれば詳しく教えていただけませんか?
by KKG (2011-12-19 14:16)
mInProgress = false のタイミングがわかりません。教えてください。
by nomo (2012-03-09 07:05)