티스토리 뷰

Intent 객체를 사용하여 카메라 어플을 실행한 뒤 사진을 찍는 방법은 그리 복잡하지 않다. 또한 단말의 카메라 앱을 사용하기 때문에 단말의 카메라 앱이 기본으로 제공하는 기능을 그대로 사용할 수 있다. 하지만 단순히 사진만 찍는 것이 아닌 화면에 증강현실이나 그래픽 등을 보여주려고 한다면, Intent 객체로 어플을 실행시키는 것 이외에 다른 작업이 필요하다.

 

카메라 미리보기 코드를 작성하여, 제작한 앱에서 직접 사진을 찍도록 구성할 수도 있다. 이 때는 일반 View가 아니라 SurfaceView 객체를 사용하는데, 우선 이 SurfaceView객체에 대하여 알아보도록 하자.

 

SurfaceView 객체는 아래 그림처럼 SurfaceHolder에 의해 제어되며, SurfaceHolder는 setPreviewDisplay(Surface sv) 메서드로 미리 보기를 설정해주는 역할을 한다.

필요한 초기화 작업이 종료되면, 카메라 객체의 startPreview() 메서드를 호출할 수 있으며 이 때부터 카메라로 입력된 영상을 SurfaceView로 화면에 보여준다. 이 때  Type은 반드시 SURFACE_TYPE_PUSH_BUFFERS 가 되어야 한다.

 

SURFACE_TYPE_PUSH_BUFFERS이 SurfaceView의 타입일 때는 미리보기 화면 위에 어떤 그래픽을 그리는 동작이 제한된다. 때문에 별도의 뷰를 하나 더 생성하여 그래픽을 처리하고 그 배경을 투명하게 하는 중첩을 주로 사용한다.

 

이제 예제를 한번 확인해보자.


우선, 권한 설정부터 해주자.

 

◎AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.samplecapture2" >

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

    <uses-feature android:name="android.hardware.camera" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.SampleCapture2" >
        <activity android:name=".MainActivity" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

 

MainActivity는 아래와 같이 작성하여 Camera 미리보기를 확인하며 버튼을 클릭했을 때 해당 화면을 파일로 저장하도록 한다. 

 

◎MainActivity.java

package com.example.samplecapture2;

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.provider.MediaStore;
import android.util.Log;
import android.view.*;
import android.widget.Button;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import com.yanzhenjie.permission.AndPermission;
import com.yanzhenjie.permission.runtime.Permission;

public class MainActivity extends AppCompatActivity {
    CameraSurfaceView cameraView;

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

        AndPermission.with(this)
                .runtime()
                .permission(Permission.READ_EXTERNAL_STORAGE,
                        Permission.WRITE_EXTERNAL_STORAGE,
                        Permission.CAMERA
                )
                .start();

        FrameLayout previewFrame = findViewById(R.id.previewFrame);
        cameraView = new CameraSurfaceView(this);
        previewFrame.addView(cameraView);

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

    }

    public void takePicture() {
        // PictureCallback 인터페이스를 구현하는 부분: 사진을 찍은 결과를 처리하는 코드를 작성
        // onPictureTaken() 메서드가 자동으로 호출되며, 캡처한 이미지가 Byte[] 의 형태로 인자로 들어온다.
        cameraView.capture(new Camera.PictureCallback() {
            public void onPictureTaken(byte[] data, Camera camera) {
                try{
                    Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);

                    // insertImage() 메서드의 인자로 내용 제공자를 제어할 ContentResolve 객체,
                    // 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));
                    }

                    camera.startPreview();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }


    class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback {

        private SurfaceHolder mHolder;
        private Camera camera = null;

        // 생성자: SurfaceHolder 객체 참조 후 설정
        public CameraSurfaceView(Context context) {
            super(context);
            
            // SurfaceHolder 참조
            mHolder = getHolder();
            
            // 이 클래스에서 구현된 Callback 객체를 지정
            mHolder.addCallback(this);
        }

        // SurfaceView 가 생성될 때 카메라 객체를 참조한 후 미리보기 화면으로 Holder 객체 설정
        public void surfaceCreated(@NonNull SurfaceHolder holder) {
            // 카메라 켜기
            camera = Camera.open();
            
            // 가로 -> 세로로 카메라 모드 변경
            setCameraOrientation();
            try{
                // 카메라 객체에서 참조할 SurfaceHolder 지정
                camera.setPreviewDisplay(mHolder);
            } catch(Exception e) {
                e.printStackTrace();
            }
        }

        // SurfaceView 의 화면 크기가 바뀌는 등의 변경 시점에 미리보기 시작
        public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
            // 미리보기 시작
            camera.startPreview();
        }

        // SurfaceView 가 없어질 때 미리보기 중지
        public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
            camera.stopPreview();
            camera.release();
            camera = null;
        }

        // 카메라 객체의 takePicture() 메서드를 호출하여 사진 촬영
        public boolean capture(Camera.PictureCallback handler) {
            if(camera != null) {
                camera.takePicture(null, null, handler);
                return true;
            } else {
                return false;
            }
        }

        // 카메라의 미리보기 Default 값이 가로이기 때문에 세로 모드로 수정해줘야 한다.
        public void setCameraOrientation() {
            if (camera == null) {
                return;
            }
            Camera.CameraInfo info = new Camera.CameraInfo();
            Camera.getCameraInfo(0, info);

            WindowManager manager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
            int rotation = manager.getDefaultDisplay().getRotation();

            int degrees = 0;

            switch (rotation) {
                case Surface.ROTATION_0: degrees = 0; break;
                case Surface.ROTATION_90: degrees = 90; break;
                case Surface.ROTATION_180: degrees = 180; break;
                case Surface.ROTATION_270: degrees = 270; break;
            }

            int result;
            if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                result = (info.orientation + degrees) % 360;
                result = (360 - result) % 360;
            } else {
                result = (info.orientation - degrees + 360) % 360;
            }

            camera.setDisplayOrientation(result);
        }
    }


}

Comments