Archives
Recent Posts
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
Today
Total
관리 메뉴

안드로이드 개발자의 창고

[52일차 Android] 사진 촬영하기 본문

Computer/Android

[52일차 Android] 사진 촬영하기

Wise-99 2023. 7. 24. 18:36

 

 

 

출처 : 안드로이드 앱스쿨 2기 윤재성 강사님 수업 PPT

 

 

 

📖 사진 촬영하기

  • 카메라 기능이 구현되어 있는 Activity를 실행하여 사진을 촬영할 수 있다.
  • 만약 카메라 기능이 구현되어 있는 애플리케이션이 다수 설치되어 있다면 앱을 선택하면 된다.
  • 기본 카메라 사용하는 방법을 사용하면 사진 원본이 아닌 썸네일 이미지를 가져오게 된다.
  • 촬영된 사진의 원본을 가져오려면 촬영된 사진을 파일로 저장한 다음 파일로부터 이미지 데이터를 가져오는 방식으로 개발해야 한다.

 

 

 

예제 코드

AndroidManifest.xml

    <application
    
        ...
        
        <!-- 촬영된 사진을 저장하는 프로바이더 -->
        <provider
            android:authorities="com.test.getpicture.file_provider"
            android:name="androidx.core.content.FileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_path"/>
        </provider>
            
        ...

 

 

 

xml / file_path.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path
        name="storage/emulated/0"
        path="."/>
</paths>

 

 

 

MainActivity.kt

class MainActivity : AppCompatActivity() {

    lateinit var activityMainBinding: ActivityMainBinding
    lateinit var cameraLauncher: ActivityResultLauncher<Intent>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        activityMainBinding = ActivityMainBinding.inflate(layoutInflater)

        // 사진 활영을 위한 런처
        val contract1 = ActivityResultContracts.StartActivityForResult()

        // 아래의 코드는 썸네일을 가져온다.(사진이 흐릿함)
        cameraLauncher = registerForActivityResult(contract1){
        
            // 사진을 촬영하고 촬영한 사진을 선택하고 돌아왔을 경우
            if(it?.resultCode == RESULT_OK){
            
                // Intent로부터 사진 데이터를 가져온다.
                // 안드로이드 버전 별로 분기
                if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
                
                    val bitmap = it.data?.getParcelableExtra("data", Bitmap::class.java)!!
                    activityMainBinding.imageView.setImageBitmap(bitmap)
                    
                } else {
                    val bitmap = it.data?.getParcelableExtra<Bitmap>("data")!!
                    activityMainBinding.imageView.setImageBitmap(bitmap)
                }
            }
        }

        activityMainBinding.run {
            // 아래의 코드는 썸네일을 가져온다.(사진이 흐릿함)
            button.setOnClickListener {
                // 사진 활영을 위한 Activity를 실행한다.
                val newIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
                cameraLauncher.launch(newIntent)
            }
        }

        setContentView(activityMainBinding.root)
    }
}

 

 

 

결과

  • 사진이 흐릿하며 실제 기기에서 테스트하면 사진이 회전되어 나타난다.

 


📖 사진 보정하기

  • ImageView는 특정 크기보다 큰 이미지는 표현할 수 없으며 이 크기는 단말기마다 다르다.
  • 이미지의 용량이 너무 크면 서버와 송수신할 때 데이터를 너무 많이 사용하게 된다.
  • 스마트폰을 세워서 촬영할 경우 사진이 90도로 회전되어 있다.
  • 이와 같은 이유로 카메라로 촬영된 사진은 보정해야 한다.

 

 

 

예제 코드

xml/file_path.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 실제 단말기에서도 동일한 경로 -->
    <external-path
        name="storage/emulated/0"
        path="."/>
</paths>

 

 

 

MainActivity.kt

class MainActivity : AppCompatActivity() {

    lateinit var activityMainBinding: ActivityMainBinding
    lateinit var cameraLauncher: ActivityResultLauncher<Intent>

    // 이미지가 저장될 위치
    lateinit var filePath : String
    // 저장된 파일에 접근하기 위한 Uri(import android.uri)
    lateinit var contentUri:Uri

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        activityMainBinding = ActivityMainBinding.inflate(layoutInflater)

        // 애플리케이션을 위한 외부 저장소 경로를 가져온다.
        // xml/file_path.xml에 등록한 경로를 가져온다.
        filePath = getExternalFilesDir(null).toString()

        // 사진 촬영을 위한 런처
        val contract1 = ActivityResultContracts.StartActivityForResult()

        cameraLauncher = registerForActivityResult(contract1){
            if (it?.resultCode == RESULT_OK){
                // uri를 이용해 이미지에 접근하여 Bitmap 객체로 생성한다.
                val bitmap = BitmapFactory.decodeFile(contentUri.path)

                // 이미지의 크기롤 조정한다.
                // 이미지의 축소/확대 비율을 구한다.
                val ratio = 1024.0 / bitmap.width
                // 세로 길이를 구한다.
                val targetHeight = (bitmap.height * ratio).toInt()

                // 크기를 조정한 Bitmap을 생성한다.
                val bitmap2 = Bitmap.createScaledBitmap(bitmap, 1024, targetHeight, false)

                // 회전 각도를 가져온다.
                val degree = getDegree(contentUri)

                // 회전 이미지를 생성하기 위한 변환 행렬
                val matrix = Matrix()
                matrix.postRotate(degree.toFloat())

                // 회전 행렬을 적용하여 회전된 이미지를 생성한다.
                // 원본 이미지, 원본 이미지에서의  X좌표, 원본 이미지에서의 Y좌표, 원본 가로 길이, 원본 세로 길이, 변환행렬, 필터 정보
                // 원본 이미지에서 지정된 x, y 좌표를 찍고 지정된 가로 세로 길이만큼의 이미지 데이터를 가져와 변환 행렬을 적용하여 이미지를 변환한다.
                val bitmap3 = Bitmap.createBitmap(bitmap2, 0,0, bitmap2.width, bitmap2.height, matrix, false)

                activityMainBinding.imageView.setImageBitmap(bitmap3)

                // 이미지 파일은 삭제한다.
                val file = File(contentUri.path)
                file.delete()
            }
        }


        activityMainBinding.run {

            button.setOnClickListener {
                val newIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)

                // 촬영될 사진이 저장될 파일 이름
                val now = System.currentTimeMillis()
                val fileName = "/temp_${now}.jpg"
                // 경로 + 파일 이름
                val picPath = "${filePath}/${fileName}"

                val file = File(picPath)

                // 사진이 저장될 경로를 관리할 uri 객체를 생성한다.
                contentUri = FileProvider.getUriForFile(this@MainActivity, "com.test.getpicture.file_provider", file)

                newIntent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri)
                cameraLauncher.launch(newIntent)
            }
        }

        setContentView(activityMainBinding.root)
    }

    // 이미지 파일에 기록되어 있는 회전 정보를 가져온다.
    fun getDegree(uri:Uri) : Int{
        var exifInterface:ExifInterface? = null

        // 사진 파일로부터 tag 정보를 관리하는 객체를 추출한다.
        // 안드로이드 버전별 분기
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
            val photoUri = MediaStore.setRequireOriginal(uri)
            // 스트림 추출
            val inputStream = contentResolver.openInputStream(photoUri)
            // ExifInterface 정보를 읽어 온다.
            exifInterface = ExifInterface(inputStream!!)
        } else {
            exifInterface = ExifInterface(uri.path!!)
        }

        var degree = 0

        if (exifInterface != null){
            // 각도 값을 가지고 온다.
            val orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1)

            when(orientation){
                ExifInterface.ORIENTATION_ROTATE_90 -> degree = 90
                ExifInterface.ORIENTATION_ROTATE_180 -> degree = 180
                ExifInterface.ORIENTATION_ROTATE_270 -> degree = 270
            }
        }

        return degree
    }
}

 

 

 

결과