2013년 7월 16일 화요일

[Android] 사진찍기와 카메라 미리보기

*중요

*.1 Manifest에 주가해야되는 권한
- <uses-permission android:name="android.permission.CAMERA"/>  //카메라 사용
- <uses-permission android:name="android.permission.FLASHLIGHT"/>  //카메라 플레쉬 사용

1. 카메라 미리보기 주요과정

1.1 카메라 미리보는 SurfaceView를 사용함 따라서, SurfaceView 사용 패턴을 그대로 사용한다.(SurfaceHolder를 이용한 SurfaceView 컨트롤)

1.2 카메라에게 서피스뷰를 알려주는 메서드
camera.setPreviewDisplay(mHolder);
1.3 미리보기에 시작 메서드
camera.startPreview();
2. 카메라 캡처 주요과정

- 이부분은 주관적인 부분으로 정확하지않을수 있습니다.

2.1 카메라 셔터를 눌렀을때 일어나는 이벤트를 정의한 인터페이스를 재정의한다.
 Camera.ShutterCallback suttercallback = new Camera.ShutterCallback() {
  public void onShutter() {...}
 };
2.2 카메라 캡처후 사진데이터가 Raw 혹은 JPEG 형식으로 메모리에 적제되면 그것을 어떻게 처리할것인지 처리하는 인터페이스를 제정의한다.
 Camera.PictureCallback picturecallback = new Camera.PictureCallback() {
  public void onPictureTaken(byte[] data, Camera camera) {...}
 };
2.3 카메라 캡처 이벤트가 발생한곳(버튼이거나 뷰 등등)에 메서드를 넣어준다. 그리고 2.1 / 2.2 에서 생성한 객체들을 매개변수에 넣어준다
camera.takePicture(suttercallback , null , picturecallback);
3. SurfaceView 재정의 주요과정(전형적인 패턴)

-가끔 디테일한 설정이 필요할때는 SurfaceView를 재정의 해서 사용한다.

3.1 클래스 재정의 할때 SurfaceView 와 SurfaceView 의 상태컨트롤을 위한 SurfaceHolder.Callback를 상속받아서 정의한다
class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback
3.2 재정의된 SurfaceView를 컨트롤할 내부 변수 SurfaceHolder 객체를 선언하고 생성자나 다른 함수를 안에서 SurfaceHolder 객체를 생성한다.
private SurfaceHolder mHolder;
mHolder = getHolder();
3.3 카메라가 SurfaceView를 독점하기 위해선 SurfaceHolder의 타입은 항상SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS로 설정한다.
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
3.4 SurfaceView 의상태(메모리 유무 ,사이즈변화 등등)는 SurfaceHolder.Callback 인터페이스 에 정의된 메서드로 처리한다.(기존 다른 리스너 방식과 유사)
-mHolder.addCallback(this); //콜백 인터페이스 홀더에 설정
-public void surfaceCreated(SurfaceHolder holder)  // SurfaceView가 생성됬을때 호출된다
-public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)  //SurfaceView 크기가 변화될때 호출된다
-public void surfaceDestroyed(SurfaceHolder holder)  //SurfaceView 메모리 헤제됬을때 호출된다

4. 예제
android version = jelly bean
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.hardware.Camera;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.view.Menu;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.Toast;


// SurfaceView를 이용해 미리보기 화면을 만든 후 사진찍기를 하는 방법에 대해 알 수 있습니다.
public class MainActivity extends Activity {
 
 // 캡처한 사진파일 저장할 이름
 public static String IMAGE_FILE = "capture.jpg";

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  // FrameLayout에 재정의한 CameraSurfaceView 추가
  final CameraSurfaceView cameraView = new CameraSurfaceView(
    getApplicationContext());
  FrameLayout previewFrame = (FrameLayout) findViewById(R.id.previewFrame);
  previewFrame.addView(cameraView);

  // 버튼을 눌렀을때 캡처
  Button saveBtn = (Button) findViewById(R.id.saveBtn);
  saveBtn.setOnClickListener(new View.OnClickListener() {
   public void onClick(View v) {
    
    // cameraView에 있는 capture() 메서드 실행
    cameraView.capture(new Camera.PictureCallback() {
     
     // JPEG 사진파일 생성후 호출됨
     // 찍은 사진을 처리
     // PictureCallback 인터페이스 에 있는 onPictureTaken() 메서드
     // byte[] data - 사진 데이타
     public void onPictureTaken(byte[] data, Camera camera) {
      try {
       
       // 사진데이타를 비트맵 객체로 저장
       Bitmap bitmap = BitmapFactory.decodeByteArray(data,0, data.length);
       
       // bitmap 이미지를 이용해 앨범에 저장
       // 내용재공자를 통해서 앨범에 저장
       String outUriStr = MediaStore.Images.Media.insertImage(getContentResolver(), bitmap,"Captured Image","Captured Image using Camera.");

       if (outUriStr == null) {
        Log.d("SampleCapture", "Image insert failed.");
        return;
       } else {
        Uri outUri = Uri.parse(outUriStr);
        sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,outUri));
       }

       Toast.makeText(getApplicationContext(),"카메라로 찍은 사진을 앨범에 저장했습니다.",Toast.LENGTH_LONG).show();

       // 다시 미리보기 화면 보여줌
       camera.startPreview();
      } catch (Exception e) {
       Log.e("SampleCapture", "Failed to insert image.", e);
      }
     }
    });
   }
  });

 }

 // 카메라 미리보기를 위해 SurfaceView 클래스 재정의
 private class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
  
  // SurfaceView를 컨트롤할 SurfaceHolder 객체 선언
  private SurfaceHolder mHolder;

  // 미리보기를 위한 카메라 객체 선언
  private Camera camera = null;

  public CameraSurfaceView(Context context) {
   super(context);
   
   // SurfaceHolder 객체 생성 getHolder()는 SurfaceView 내부 함수
   mHolder = getHolder();

   // SurfaceHolder.Callback 인터페이스 장착
   mHolder.addCallback(this);

   // 카메라가 SurfaceView를  독점하기 위한 타입 설정
   // 버퍼를 사용하지않음
   mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
  }

  // SurfaceView 가 메모리에 생성될때 호출된다.
  public void surfaceCreated(SurfaceHolder holder) {
   // 카메라를 사용할려고 한다는 설정
   camera = Camera.open();

   try {
    // 미리보기를 설정
    camera.setPreviewDisplay(mHolder);
   } catch (Exception e) {
    Log.e("CameraSurfaceView", "Failed to set camera preview.", e);
   }
  }
  
  // 보통 SurfaceView가 보여지기전 과 사이즈가 변화가있을때 호출된다.
  // 그래서 보여지기전인 surfaceCreated()가 호출된 다음 호출된다. 
  public void surfaceChanged(SurfaceHolder holder, int format, int width,
    int height) {

   // 카메라의 파라미터 값을 가져와서 미리보기 크기를 설정한다
   Camera.Parameters parameters= camera.getParameters();
   parameters.setPreviewSize(width, height);
   camera.setParameters(parameters);
   
   // 미리보기화면을 뿌려준다
   camera.startPreview();
  }

  // SurfaceView의 메모리가 해제되었을때 호출된다.
  // SurfaceView가 화면에 표시되지않을때(액티비티가 비활성화 될때) 호출된다.
  public void surfaceDestroyed(SurfaceHolder holder) {
   
   // 미리보기 중지
   camera.stopPreview();
   // 메모리 해제
   camera.release();
   camera = null;
  }

  
  // 사진을 찍을때 호출되는 함수 (스냅샷)
  public boolean capture(Camera.PictureCallback handler) {
   if (camera != null) {
    // 셔터후
    // Raw 이미지 생성후
    // JPE 이미지 생성후
    camera.takePicture(null, null, handler);
    return true;
   } else {
    return false;
   }
  }

 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  getMenuInflater().inflate(R.menu.activity_main, menu);
  return true;
 }
}

댓글 1개: