Androidでのフォトフレーム有りカメラの実装方法

フォトフレームがあるカメラアプリを作成しました。

#カメラの設定
まずカメラの実装です。
SurfaceviewでViewを形成し、カメラを設定します。
※CameraクラスはLollipop(API21)で非推奨となっております。

```java:CameraView
import java.io.IOException;

import android.content.Context;
import android.graphics.Canvas;
import android.hardware.Camera;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;

public class CameraView extends SurfaceView implements SurfaceHolder.Callback {
	
	private Camera mCamera;
	private View mView;
    private View photoFlameView;

	public CameraView(Context context, Camera mCamera) {
		super(context);
		
		this.mCamera = mCamera;
        // カメラの向きを変更（今回は縦固定のため）
		mCamera.setDisplayOrientation(90);
		
		// SurfaceHolderの取得とコールバック通知先の設定
        SurfaceHolder holder = getHolder();
        holder.addCallback(this);
        holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		try {
            // カメラインスタンスに、画像表示先を設定
			mCamera.setPreviewDisplay(holder);
			// プレビュー開始
		    mCamera.startPreview();
        } catch (IOException e) {
            Log.e(TAG, "The failure of the camera settings");
        }

	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {
	}

	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
	}

}
```

カメラビューが作成出来たら、メインとなるActivityでインスタンス化し、メイン画面に設定する。
※カメラをアプリの停止開始に対応するため、onResumeで作成しています。

```java:PhotoActivity
public class PhotoActivity extends Activity {
	
	private final String IMG_FOLDER_DERECTRY = "/Pictures/miiketankou/";
	
	private Camera mCamera;
	private CameraView mCameraView;
	private View photoFlameView;
	private TextView photoFrameText;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
	}

	@Override
	protected void onResume() {
        super.onResume();
	
        // カメラビューの設定
        try
        {
            mCamera = Camera.open();
            mCameraView = new CameraView(this, mCamera);
            setContentView(mCameraView);
        }
        catch(Exception e)
        {
            finish();
        }
    }

    @Override
    protected void onPause() {
        // Pauseでカメラの停止
        if(mCamera != null)
        {
            mCamera.release();
            mCamera = null;
        }
        super.onPause();
    }
```
これでカメラ（縦固定）のプレビューが開始されていると思います。
※Androidのカメラは、デフォルト設定で横画面が通常ですので、縦カメラにする場合は追加の設定が必要です。

#フォトフレームの追加
ではフォトフレームを追加します。
フォトフレームは先ほど設定したViewに重なりで設定します。
Viewならどの過程で作成しても大丈夫ですが、必ずコンテンツ以外は透過するように作成してください。

今回はフォトフレームビューをXMLで作成したものでインスタンス化します。
メインActivityのonResumeでインスタンス化、Viewの追加を行います。

```java:PhotoActivity
@Override
protected void onResume() {
    super.onResume();
		
    // カメラビューの設定
    try
    {
        mCamera = Camera.open();
        mCameraView = new CameraView(this, mCamera);
        setContentView(mCameraView);
	        
        // フォトフレームの追加
        photoFlameView = this.getLayoutInflater().inflate(R.layout.photoflame_layout, null);
        addContentView(photoFlameView, new LinearLayout.LayoutParams(
					ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT));
			
    }
    catch(Exception e)
    {
        finish();
    }
}
```
これでカメラにフォトフレームがオーバーレイされた状態で表示できていると思います。

#カメラ撮影
今回はまった部分は、カメラビューとフォトフレームビューが同時に撮影できないことでした。
それではカメラ撮影の実装に入ります。

今回は画面タッチで撮影するようにするため、カメラビューにタッチイベントを追加します。

```java:PhotoActivity
mCameraView.setOnClickListener(photoShotListner);
```
引数に設定している"photoShotListner"を作成します
photoShotListnerにはオートフォーカスのメソッドを追加します。

```java:PhotoActivity
private View.OnClickListener photoShotListner = new View.OnClickListener() {
		
		@Override
		public void onClick(View v) {
			mCamera.autoFocus(mAutoFocusCallback);
		}
	};
```

次にオートフォーカスのコールバックメソッドを作成します。
オートフォーカス後にCamera#takePictureメソッドでカメラプレビュー画像の取得機能を起動しますが、プレビュー画像はコールバックメソッドで取得します。
※フォルダの作成は別途

```java:PhotoActivity
private AutoFocusCallback mAutoFocusCallback = new AutoFocusCallback() {
		
		@Override
		public void onAutoFocus(boolean success, Camera camera) {
			// フォルダの作成
			MakeFolder.makeImageFolder(IMG_FOLDER_NAME);
			// カメラプレビュー画像取得の起動
            // カメラプレビューはコールバックメソッドの引数で取得する
			mCamera.takePicture(null, null, mPicJpgListener);
		}
		
	};
```

プレビュー画像取得のコールバックメソッドを作成します。
データ取得後の画像保存メソッドは別途作成していますので後述します。

```java:PhotoActivity
private final PictureCallback mPicJpgListener = new PictureCallback() {
		
		@Override
		public void onPictureTaken(byte[] data, Camera camera) {
			capcharScreen(data);
		}
	};
```

#キャプチャー画面の取得
それではキャプチャーの取得に入ります。
今回はカメラとフォトフレームを別々のViewで取得していますが、別々にキャプチャーを取得し重ねる方法を取ります。
※カメラビューをView#getDrawingCacheで取得すると、真っ黒の画面で保存されるため。

```java:PhotoActivity
private void capcharScreen(byte[] data){
    Bitmap cameraMap = BitmapFactory.decodeByteArray(data, 0,data.length, null);

    // 撮影プレビューの修正（縦画面では必要）
    int width = cameraMap.getWidth();
    int height = cameraMap.getHeight();
    Matrix matrix = new Matrix();
    matrix.postRotate(90);
    Bitmap cameraMap90 = Bitmap.createBitmap(cameraMap, 0, 0, width, height,matrix, true);

    // フォトフレーム部のキャプチャ取得
    photoFlameView.setDrawingCacheEnabled(false);
    photoFlameView.setDrawingCacheEnabled(true);
    Bitmap overlayMap = photoFlameView.getDrawingCache();
    	 
    // カメラとフレーム（オーバレイ）を重ねる
    Bitmap offBitmap = Bitmap.createBitmap(overlayMap.getWidth(),
    overlayMap.getHeight(), Bitmap.Config.ARGB_8888);
    Canvas offScreen = new Canvas(offBitmap);
    	 
    // 以下は実際の写真データとオーバーレイ画像を張り合わせる作業
    offScreen.drawBitmap(cameraMap90,null,
    new Rect(0, 0, overlayMap.getWidth(), overlayMap.getHeight()), null);
    offScreen.drawBitmap(overlayMap,null,
    new Rect(0, 0, overlayMap.getWidth(), overlayMap.getHeight()), null);

    // "offBitmap"が２つを張り合わせたBitmap（キャプチャ画像）となります。
}
```

###※ディスプレイサイズでキャプチャー画面を取得する場合
上記方法で実装した場合、端末によって画像が大きすぎることがあったので、サイズを調整しながら取得する方法を作成しました。
※記述内の#DisplaySizeCheckについては[画面サイズ取得方法](http://qiita.com/a_nishimura/items/f557138b2d67b9e1877c)を参考にしてください。


```java:PhotoActivity
private void capcharScreen(byte[] data){
    // ディスプレイサイズの取得（フルスクリーン）
    Point point = DisplaySizeCheck.getRealSize(this);
    int x = point.x;
    int y = point.y;

    // 撮影したデータを画面サイズに調整したBitmapに変換
    Bitmap cameraMap = BitmapFactory.decodeByteArray(data, 0, data.length, null);
    float scall = (float)x / (float)cameraMap.getWidth();
    Bitmap cameraMap2 = Bitmap.createScaledBitmap(cameraMap, (int)(cameraMap.getWidth() * scall), (int)(cameraMap.getHeight() * scall), false);
    cameraMap.recycle();

    // フォトフレーム部のキャプチャ取得
    flameView.setDrawingCacheEnabled(false);
    flameView.setDrawingCacheEnabled(true);
    Bitmap overlayMap = flameView.getDrawingCache();

    // キャンバスの生成
    Bitmap offBitmap = Bitmap.createBitmap(x, y, Bitmap.Config.ARGB_8888);
    Canvas offScreen = new Canvas(offBitmap);

    // サイズをレイアウトに合うように調整
    int wSpace = (cameraMap2.getWidth() - x) / 2;
    int hSpace = (cameraMap2.getHeight() - y) / 2;
    Rect posiRect = new Rect(wSpace, hSpace, cameraMap2.getWidth() - wSpace, cameraMap2.getHeight() - hSpace);
    Rect sizeRect = new Rect(0, 0, x, y);
    offScreen.drawBitmap(cameraMap2, posiRect, sizeRect, null);

    // フレームを追加
    offScreen.drawBitmap(overlayMap, null, new Rect(0, 0, x, y), null);

    // "offBitmap"が２つを張り合わせたBitmap（キャプチャ画像）となります。
    return offBitmap;

}
```

#画像の保存
最後に取得した画像をディレクトリに保存します。
今回は画像を端末ストレージに保存します。
先ほどの"capcharScreen"メソッド内に以下のソースを追加します。

```java:PhotoActivity
// 保存先のディレクトリを指定
String imgPath = Environment.getExternalStorageDirectory();
// フォルダを作成しているならimgPathにディレクトリを追加する
// imgPath + /sample/
    	
// ファイル名を追加（今回は日付からファイル名を作成）
imgPath += getNowDateStr() + ".jpg";

// ファイルの保存
FileOutputStream fos;
try {
    fos = new FileOutputStream(imgPath, true);
    offBitmap.compress(CompressFormat.JPEG,100,fos);
    fos.close();
    registAndroidDB(imgPath);
    		
    Toast.makeText(this, "写真を保存しました！", Toast.LENGTH_SHORT).show();
    		
} catch (Exception e) {
    Toast.makeText(this, "写真の保存に失敗しました。。。", Toast.LENGTH_SHORT).show();
}

// takePictureを起動するとカメラは停止するので、再開させる。
mCamera.startPreview();
```

これで写真を保存することができました。

※その他、作成したメソッドです。ご参考にどうぞ。

```java:getNowDateStr
private String getNowDateStr(){
    // 現在の時刻からファイル名を作成
    Date date = new Date(System.currentTimeMillis());
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS");
    	
    return simpleDateFormat.format(date);
}
```

```java:registAndroidDB
private void registAndroidDB(String path) {
    // アンドロイドのデータベースへ登録
    // (登録しないとギャラリーなどにすぐに反映されないため)
    ContentValues values = new ContentValues();
    ContentResolver contentResolver = PhotoActivity.this.getContentResolver();
    values.put(Images.Media.MIME_TYPE, "image/jpeg");
    values.put("_data", path);
    contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
}
```

#まとめ
実装は以上になります。
今回は各Viewのキャプチャー取得方法が異なる事が原因で実装に困っていました。
色々なサイトを参考にし今回の方法を見つけることができましたが、結論を言うとやっぱりAndroidは面倒。。。

今回は無事作成できましたが、もし他に良い方法があれば、是非ご教授ください。

[参考になったページ](http://cleanings.jp/develop/1919)