본문 바로가기
Android/Concepts

Camera를 이용하여 사진 촬영 및 저장하기

by JuHy_ 2020. 5. 2.

앱에서 사진을 촬영하여 얻기 위한 방법은 여러가지가 있다.

첫번째 방법은 사진 앱에 사진 촬영을 요청한 뒤 촬영된 jpeg 파일을 가져와 처리하는 것이다.

두번째 방법은 카메라 미리보기 화면을 앱에 넣은 뒤 화면을 캡쳐하여 사용하는 것이다.

 

두번째 방법은 앱을 새로 띄우지 않고 앱 내에서 처리 가능하다는 장점이 있지만.

카메라 설정이 기기마다 매우 다르기 때문에 예외 처리가 힘들어 첫번째 방법을 사용하는 것을 권장한다.

 

두가지 방법의 사용법을 하나씩 알아보자.

 

카메라 앱을 사용하여 촬영하기

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

먼저 manifest에 저장소에 읽고 쓰기 위한 권한을 요청해주자.

 

※ 저장소 접근 권한은 위험 권한이기 때문에 앱 실행 시 사용자에게 허가를 받아야 한다. 자세한 내용은 아래 글 참고.

https://ju-hy.tistory.com/51

 

Permission 획득하기

일반 권한과 위험 권한 기본적으로 특정 권한을 획득하기 위해서는 Manifest에 요청할 권한을 추가하기만 하면 된다. 예전에는 이런 권한들을 앱 설치 수락 과정에서 모두 획득이 가능했는데, 사용자들로부터 위험..

ju-hy.tistory.com

 

package com.juhy.myapplication;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

import java.io.File;
import java.util.Map;

public class MainActivity extends AppCompatActivity {

    ImageView imageView;

    File file;

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

        File sdcard = Environment.getExternalStorageDirectory();
        file = new File(sdcard, "capture.jpg");

        imageView = findViewById(R.id.imageView);

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                capture();
            }
        });
    }

    public void capture(){
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
        startActivityForResult(intent, 101);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if(requestCode == 101  && resultCode == Activity.RESULT_OK){
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inSampleSize = 8;
            Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
            imageView.setImageBitmap(bitmap);
        }
    }
}

먼저 버튼을 클릭했을 때 사진을 찍을 수 있도록 capture 함수를 만들어주자.

 

capture 함수에서는 카메라 기능이 있는 앱을 호출하도록 암시적 intent를 생성하면 된다.

이 때 File 객체를 만들어 촬영한 사진이 저장될 때 이름과 형식을 지정할 수 있다.

그리고 startActivityForResult()를 통해 카메라 앱을 요청한다.

 

사진 촬영이 끝난 후 결과가 넘어오면 onActivityResult() 함수를 통해 넘겨받은 사진을 ImageView에 띄워보자.

먼저 requestCode와 resultCode가 올바른지 확인한다.

그리고 BitmapFactory Option을 통해 이미지의 1/8 크기로 샘플링하도록 설정한 뒤, decodeFile()을 통해 불러오자.

마지막으로 불러온 Bitmap 객체를 ImageView에 띄워주면 된다.

 

사진 앱이 켜져 사진이 촬영되어 저장된 뒤,

저장된 파일을 앱에서 정상적으로 불러오는 것을 볼 수 있다.

 

1/8 크기로 샘플링했기 때문에 불러온 이미지의 화질이 낮아진 것이 보인다.

이 때 화질을 높이고 싶다면 inSampleSize 값을 낮춰주면 된다.

 

앱 내부에서 촬영하기

앱 내부에서 카메라에서 촬영중인 화면을 띄워주고 싶을 때는 SurfaceView를 사용해야 한다.

SurfaceView는 화면에 View를 그릴 때 쓰레드를 이용하여 그려주어,

연산이 많이 필요한 동영상이나 애니메이션 등을 화면에 효율적으로 띄워줄 수 있다.

띄워줄 화면은 SurfaceHolder를 통해 Surface를 미리 그려놓은 뒤 SurfaceView에 띄워주게 된다.

 

<uses-permission android:name="android.permission.CAMERA" />

먼저 manifest에 CAMERA 권한을 요청해준다.

 

※ 저장소 권한과 마찬가지로 위험 권한이므로 사용자에게 허용받는 코드를 구현해야 한다.

 

package com.juhy.myapplication;

import android.content.Context;
import android.hardware.Camera;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import java.io.IOException;

public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback{

    SurfaceHolder holder;
    Camera camera = null;

    public CameraSurfaceView(Context context) {
        super(context);

        init(context);
    }

    public CameraSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);

        init(context);
    }

    private void init(Context context){
        holder = getHolder();
        holder.addCallback(this);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        camera = Camera.open();

        try {
            camera.setPreviewDisplay(holder);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

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

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        camera.stopPreview();
        camera.release();
        camera = null;
    }

    public boolean capture(Camera.PictureCallback callback){
        if(camera != null){
            camera.takePicture(null, null, callback);
            return true;
        } else {
            return false;
        }
    }
}

이제 SurfaceView를 상속받아 클래스를 정의해주자.

정의할 메소드들은 다음과 같다.

 

CameraSurfaceView() - 생성자

init() - SurfaceHolder를 불러옴.

 

surfaceCreated() -  surface가 생성되었을 때 할 작업. Camera를 불러오고 미리보기 화면을 surface로 지정함.

surfaceChanged() - surface가 바뀌었을 때 할 작업. Camera 미리보기를 실행함.

surfaceDestroyed() - surface가 사라졌을 때 할 작업. Camera 미리보기를 멈추고 리소스를 해제함.

 

capture() - Camera 미리보기 중인 화면을 캡쳐함.

 

<com.juhy.myapplication.CameraSurfaceView
    android:id="@+id/surfaceview"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

레이아웃 파일에 만들어준 클래스로 View를 추가해준다.

 

package com.juhy.myapplication;

import androidx.appcompat.app.AppCompatActivity;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.hardware.Camera;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

public class MainActivity extends AppCompatActivity {

    CameraSurfaceView surfaceView;
    ImageView imageView;

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

        surfaceView = findViewById(R.id.surfaceview);
        imageView = findViewById(R.id.imageView);

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                capture();
            }
        });
    }

    public void capture(){
        surfaceView.capture(new Camera.PictureCallback() {
            @Override
            public void onPictureTaken(byte[] data, Camera camera) {
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inSampleSize = 8;
                Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
                imageView.setImageBitmap(bitmap);

                camera.startPreview();
            }
        });
    }
}

액티비티에서는 먼저 버튼을 클릭하면 capture() 함수를 호출하도록 하자.

 

capture() 함수에서는 만들어준 CameraSurfaceView 객체의 capture() 함수를 호출한다.

이 때 PictureCallback을 넘겨주어야 하는데 여기에 촬영한 사진을 넘겨받아 작업할 내용을 넣는다.

 

촬영한 사진은 byte 배열로 넘어오는데 이를 BitmapFactory를 통해 Bitmap으로 변환해주자.

그리고 변환된 Bitmap을 ImageView에 띄워주면 된다.

 

카메라 화면이 정상적으로 나타나며, 버튼을 누르면 이미지로 저장되어 ImageView에 나타난다.

 

※ 카메라 화면이 왼쪽으로 돌아가 있는 이유는 기본 설정이 가로 화면을 기준으로 구현되어 있기 때문이다.

이를 수정하고 싶다면 촬영한 이미지나 View를 오른쪽으로 90도 회전시켜주면 된다.

 

 

Reference

[부스트코스]안드로이드 프로그래밍

https://www.edwith.org/boostcourse-android