안드로이드/안드로이드 꿀팁

[Android] CameraX 사용하여 사진 촬영 및 저장 (2)

욧닭 2021. 11. 23. 10:20
반응형

android jectpact

들어가며

이전 포스팅은 CameraX 라이브러리를 이용해 카메라 셋팅 하는 것 까지 다뤄 봤습니다 오늘은 파일 저장에 대한 전체적인 코드 설명을 하겠습니다.

본 포스팅 내용은 CameraX에 대한 기본 설명 코드 설명이 주 된 포스팅으로 아래의 깃허브 주소를 통해 코드와 같이 본다면 이해하기 쉬울 거예요
https://github.com/Junnnnnnnnnnn/demo_android_cameraX

 

GitHub - Junnnnnnnnnnn/demo_android_cameraX: jetpack cameraX를 활용한 사진 찍기 / 저장

jetpack cameraX를 활용한 사진 찍기 / 저장. Contribute to Junnnnnnnnnnn/demo_android_cameraX development by creating an account on GitHub.

github.com

사진 촬영 및 저장

사진을 촬영하기 위해선 기본적으로 카메라 퍼미션을 허용 해줘야 합니다.

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yotdark.example_camerax">
                                         .
                                         .
                                         .
    <uses-feature android:name="android.hardware.camera.any" />
    <uses-permission android:name="android.permission.CAMERA" />
                                         .
                                         .
                                         .

</manifest>

android.hardware.camera.anyandroid.permission.CAMERAManifast 에 등록 합니다.

CameraActivity

private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
        ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
}

AndroidManifest에 등록된 권한이 허용됬는지 확인하는 메서드 입니다. 권한이 있다면 1 없다면 0을 반환합니다.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.camera)
                               .
                               .
                               .

    if(allPermissionsGranted()){
        startCamera()
    }else{
        ActivityCompat.requestPermissions(
            this@CameraActivity,
            REQUIRED_PERMISSIONS,
            REQUEST_CODE_PERMISSIONS
        )
    }
                               .
                               .
                               .
}

권한이 있는지 없는지 확인 후 없다면 requestPermissionse 를 통해 권한 허용 팝업창을 띄워 줍니다.

 

위 코드를 통해 권한을 허용 했다면 사진이 저장될 곳을 지정 해야 합니다.

getExternalFilesDir

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yotdark.example_camerax">
                                       .
                                       .
                                       .
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.yotdark.example_camerax"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
    </application>

</manifest>

이미지가 저장될 경로를 저장한 xml파일을 FILE_PROVIDER_PATHS 에 지정 합니다.

xml/file_paths

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path name="my_images"
                   path="Android/data/com.yotdark.example_camerax/files/Pictures" />
</paths>

사용자 기기내의 외부 저장소의 경로를 설정하는 파일입니다. 경로는 개발자가 설정하기 나름입니다!

android studio 에서 외부저장소의 이미지 들을 확인 할 수 있습니다.

CameraActivity

private fun getOutputDirectory(): File {
        return getExternalFilesDir(Environment.DIRECTORY_PICTURES)!!
}

경로를 설정했다면 코드를 통해 설정한 경로를 불러 와야 합니다.
getExternalFilesDir과 인수인 Environment.DIRECTORY_PICTURES를 설정해줍니다.

이제 설정을 다 했습니다! 사진을 촬영한 후 외부 저장소 경로로 이미지를 저장 시키면 됩니다

TakePhoto

private fun takePhoto(){
    val imageCapture = imageCapture ?: return
    val photoFile = File(
        outputDirectory,
        SimpleDateFormat(FILENAME_FORMAT, Locale.KOREA)
            .format(System.currentTimeMillis()) + ".$extension"
    )

    val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()

    imageCapture.takePicture(
        outputOptions,
        ContextCompat.getMainExecutor(this@CameraActivity),
        object: ImageCapture.OnImageSavedCallback{
            override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
                val saveUri = Uri.fromFile(photoFile)
                ExifInterface(photoFile.absolutePath).run{
                  getAttributeInt(
                    ExifInterface.TAG_ORIENTATION,
                    ExifInterface.ORIENTATION_NORMAL).run {
                        Log.d(TAG,"$this")
                    }
                }

                Toast.makeText(this@CameraActivity,"저장되었습니다",Toast.LENGTH_SHORT).show()
                Log.d(TAG,"사진이 정상 촬영 됬습니다. ${saveUri.path}")

                val intent = Intent(this@CameraActivity, MainActivity::class.java)
                intent.putExtra("path", saveUri.path)
                setResult(200,intent)
                finish()
            }
            override fun onError(exception: ImageCaptureException) {
                Log.e(TAG,"촬영에 실패 했습니다",exception)
            }
        }
    )
}

코드가 되게 길죠..ㅎㅎ 하나식 분석하면 별 것 아니예요!!

imageCapture

val imageCapture = imageCapture ?: return

전역으로 선언한 ImageCapture 변수가 null 이라면 함수는 종료 됩니다. 예외처리라고 할 수 있죠

PhotoFile

val photoFile = File(
    outputDirectory,
    SimpleDateFormat(FILENAME_FORMAT, Locale.KOREA)
       .format(System.currentTimeMillis()) + ".$extension"
)

imageCapture 변수를 통해 사진을 등록하려면 photoFile 이 필요합니다.
File의 첫번째 인수 outputDirectory는 위 함수에서 가져온 외부 저장소 경로를 입력합니다.
두번재 인수로는 파일 이름을 지정 해 줍니다. 파일의 중복을 처리하기 위해 System.currentTimeMillis() 를 사용했습니다.
extension으로는 jpg, png 등이 작성됩니다.

OutputCapture

val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()

이미지 경로를 지정했으면 ImageCapture에 설정을 합니다.

ImageCapture.takePicture

imageCapture.takePicture(
    outputOptions,
    ContextCompat.getMainExecutor(this@CameraActivity),
    object: ImageCapture.OnImageSavedCallback{
        override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
            val saveUri = Uri.fromFile(photoFile)
            ExifInterface(photoFile.absolutePath).run {
                getAttributeInt(
                    ExifInterface.TAG_ORIENTATION,
                    ExifInterface.ORIENTATION_NORMAL
                ).run {
                    Log.d(TAG, "$this")
                }
            }

            Toast.makeText(this@CameraActivity, "저장되었습니다", Toast.LENGTH_SHORT).show()
            Log.d(TAG, "사진이 정상 촬영 됬습니다. ${saveUri.path}")

            val intent = Intent(this@CameraActivity, MainActivity::class.java)
            intent.putExtra("path", saveUri.path)
            setResult(200, intent)
            finish()

        }
        override fun onError(exception: ImageCaptureException) {
            Log.e(TAG,"촬영에 실패 했습니다",exception)
        }
    }
)

일단 첫 번째 인수로 outputOptions를 입력하고 두번째 인수는 ContextCompat.getMainExecutor(this@CameraActivity) 로 스레드를 등록합니다.

세번째 인수로는 ImageCapture.OnImageSavedCallback이라는 콜백 메서드를 등록합니다

 

콜백 메서드란

개발자가 따로 함수를 등록하지 않았지만 함수 스스로 어떤 특정한 이벤트가 발생 했을 때 자신을 호출한 함수 또는 사용자가 지정한 곳으로 돌아가는 현상을 말합니다.
그럼 onImageSaved 함수를 상속 받게 되는데 이 함수를 호출하면 앞서 설정한 outputOptions를 통해 이미지가 저장이 됩니다.

 

ExifInterface(photoFile.absolutePath).run {
                getAttributeInt(
                    ExifInterface.TAG_ORIENTATION,
                    ExifInterface.ORIENTATION_NORMAL
                ).run {
                    Log.d(TAG, "$this")
                }
            }

저장된 사진의 가로 세로 등 메타값을 알기 위해 ExifInterface를 사용했습니다.
만약 사용자가 화면을 portrait 로 설정을 했다면 가로 세로를 자동으로 구분 할 수 가 없어 따로 Surface 를 등록해야 합니다.
(이건 따로 포스팅 하겠습니다.)

val intent = Intent(this@CameraActivity, MainActivity::class.java)
intent.putExtra("path", saveUri.path)
setResult(200, intent)
finish()

마지막으로 인텐트를 통해 값을 해당 엑티비티를 호출한 곳으로 전달하는 과정입니다.

 

 

이렇게 전반적인 사진 저장에 대해 알아 보았습니다!
다음 포스팅에는 사용자가 세로 고정으로 사진을 촬영했을 때 가로 세로를 확인 하는 방법을 마지막으로 살펴보도록 하겠습니다.

반응형