반응형

지난 시간에 카메라를 사용해서 이미지 뷰에 연결하는 방법을 공부했습니다.

카메라 캡처를 사용해서 이미지를 사용하면 해상도가 떨어지기 때문에 원본이 손실되는 문제가 발생했습니다.

이번 시간에는 원본 이미지 손실을 최소화할 수 있는 이미지 저장 방법에 대해서 알아보겠습니다.

지난 시간에 배운 코틀린을 이용한 카메라 이미지 저장 실행 화면입니다.

원본 이미지 해상도가 많이 떨어지는 것을 확인할 수 있습니다.

해상도를 유지하기 위해서 먼저 촬영된 이미지를 원본 그대로 갤러리에 저장 후 로드하는 형태로 변경해보겠습니다.

카메라 원보 이미지를 저장하기 위해서 먼저 이미지 경로 Uri를 생성합니다.

fun createImageUri(filename:String, mimeType:String):Uri?{
        var values = ContentValues()
        values.put(MediaStore.Images.Media.DISPLAY_NAME,filename)
        values.put(MediaStore.Images.Media.MIME_TYPE, mimeType)
        return contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
    }

ContentValues를 사용해서 이미지 Uri를 생성합니다.

contentResolver는 contentProvider과 비즈니스 로직의 중계자 역할을 담당합니다.

ContentValues는 contentResolver이 사용하는 데이터 정보라고 생각하면 됩니다.

ContentValues에 이미지 이름과 타입을 저장합니다.

카메라를 동작하기 위해서 dispatchTakePictureIntentEx 함수를 추가합니다.

private var photoURI : Uri? = null
    private val REQUEST_CREATE_EX = 3

    private fun dispatchTakePictureIntentEx()
    {
        val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
        val storageDir: File? = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
        val takePictureIntent : Intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
        val uri : Uri? =   createImageUri("JPEG_${timeStamp}_", "image/jpg")
        photoURI = uri
        takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
        startActivityForResult(takePictureIntent, REQUEST_CREATE_EX)
    }

전역 경로 photoURI, 이벤트 ID REQUEST_CREATE_EX를 선언합니다.

이전 시간에 선언한 함수와 동일하 구성이기 때문에 Ex를 붙여서 이름을 변경했습니다.

timeStamp를 사용해서 이미지 이름을 시간에 따라서 생성합니다.

CreateimageUri 함수를 호출해서 이미지를 생성하고 카메라를 실행합니다.

fun loadBitmapFromMediaStoreBy(photoUri: Uri) : Bitmap?{
        var image: Bitmap? = null
        try{
            image = if(Build.VERSION.SDK_INT > 27){
                val source: ImageDecoder.Source =
                    ImageDecoder.createSource(this.contentResolver, photoUri)
                ImageDecoder.decodeBitmap(source)

            }else{
                MediaStore.Images.Media.getBitmap(this.contentResolver, photoUri)
            }
        }catch(e:IOException){
            e.printStackTrace()
        }
        return image
    }

생성된 Uri 경로에 이미지를 MediaStore를 사용해서 읽어옵니다.

 btnCamera.setOnClickListener{
                if(checkPermission()){
                    //dispatchTakePictureIntent()
                    dispatchTakePictureIntentEx()
                }
            else{
                    requestPermission()
                }
        }

기존에 연결되어 있던 버튼 이벤트에서 신규로 선언한 dispatchTakePictureIntentEx 함수를 실행합니다.

else if( requestCode == REQUEST_CREATE_EX)
{
    if( photoURI != null)
    {
           val bitmap = loadBitmapFromMediaStoreBy(photoURI!!)
           GImageView.setImageBitmap(bitmap)
           photoURI = null
    }
}

onActivityResult 함수에서 이벤트 ID REQUEST_CREATE_EX를 필터링 후

이미지를 로드하는 loadBitmapFromMediaStoreBy 함수를 호출해서 이미지 뷰에 연결합니다.

카메라 촬영 후 갤러리를 확인하면 카메라 이미지를 확인할 수 있습니다.

원본 이미지를 사용해서 이미지뷰에 연결하면 해상도가 동일한 것을 확인할 수 있습니다.

카메라 이미지 해상도를 원본과 동일하게하기 위해서 별도 저장 후 로드하는 방법을 사용하게 가장 좋은 방법입니다.

이미지 정보가 시간에 따라서 변경되기 때문에 이름을 고정하면 한 개의 사진만 업데이트됩니다.

코틀린을 사용하면 코드가 매우 간결하기 때문에 매우 편리합니다.

객체를 주고받을 때 NULL 존재하기 때문에 ?를 사용하는 부분만 확인하면 어렵지 않은 코드입니다.

감사합니다.

 

반응형
반응형

오늘은 코틀린(Kotlin)을 사용해서 안드로이드 카메라 이벤트 이미지를 이미지 뷰에 출력해보겠습니다.

안드로이드 카메라를 사용하기 위해서 먼저 AndroidMainfest.xml에 속성을 추가해주세요.

<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-feature android:name="android.hardware.camera" android:required="true" />

 "required" 속성이 true일 경우는 반드시 카메라 사용 옵션입니다.

경우에 따라서 설정을 변경하면 됩니다.

카메라를 실행하기 위해서 activity_main.xml에 button을 추가합니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    >
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="horizontal">

        <Button
            android:id="@+id/btnGallery"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Gallery"
            tools:layout_editor_absoluteX="62dp"
            tools:layout_editor_absoluteY="16dp" />

        <Button
            android:id="@+id/btnCamera"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Camera"
            tools:layout_editor_absoluteY="76dp" />
    </LinearLayout>


    <ImageView
        android:id="@+id/GImageView"
        android:layout_width="match_parent"
        android:layout_height="500dp"
        tools:layout_editor_absoluteX="16dp"
        tools:layout_editor_absoluteY="124dp" />

    </LinearLayout>
    

</androidx.constraintlayout.widget.ConstraintLayout>

xml 추가 후 빌드하면 버튼이 정렬되어 출력됩니다.

"CAMERA" 버튼을 클릭하면 카메라를 실행하고, 촬영된 이미지를 하단 이미지 뷰에 추가하겠습니다.

카메라를 사용하기 위해서는 먼저 권한을 승인받아야 합니다.

 private fun requestPermission(){
        ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE,CAMERA),1)

    }
    private fun checkPermission():Boolean{

        return (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
                == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this,
        Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)

    }
    @Override
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)

        if( requestCode == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
            Toast.makeText(this, "권한 설정 OK", Toast.LENGTH_SHORT).show()
        }
        else
        {
            Toast.makeText(this, "권한 허용 안됨", Toast.LENGTH_SHORT).show()
        }
    }

ActivityCompat Class를 사용해서 카메라 사용 권한을 요청합니다.

checkPermission() 함수는 권한 여부를 확인할 수 있습니다.

onRequestPermissionsResult() 함수를 override 후 권한 승인에 따른 이벤트를 확인할 수 있습니다.

즉 권한이 없을 경우 앱 동작을 중단하거나, 메시지를 출력할 수 있습니다.

카메라를 실행하기 위해서 Intent를 실행합니다.

    private val REQUEST_IMAGE_CAPTURE = 2
    private fun dispatchTakePictureIntent() {
        Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
            takePictureIntent.resolveActivity(packageManager)?.also {
                startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
            }
        }
    }

권한이 승인되면 MediaStore.ACTION_IMAGE_CAPTURE argument를 사용해서 Intent를 실행합니다.

이젠 버튼 이벤트를 연결해서 dispatchTakePictureIntent() 함수를 실행합니다.

btnCamera.setOnClickListener{
                if(checkPermission()){
                    dispatchTakePictureIntent()
                }
            else{
                    requestPermission()
                }
        }

button event에서 checkPermission() 함수를 실행 후 권한이 없다면 권한을 재 요청합니다.

권한 승인 되었다면 카메라를 실행합니다.

정상적으로 카메라 실행 화면을 확인할 수 있습니다.

카메라 촬영 버튼을 클릭하면 이미지가 저장되면서 이벤트가 발생합니다.

이전 시간에 배운 onActivityResult 이벤트를 사용해서 이미지를 저장합니다.

@Override
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)

        if( resultCode == Activity.RESULT_OK) {
            if (requestCode == GALLERY) {
                var ImnageData: Uri? = data?.data
                Toast.makeText(this, ImnageData.toString(), Toast.LENGTH_SHORT).show()
                try {
                    val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, ImnageData)
                    GImageView.setImageBitmap(bitmap)
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
            else if( requestCode == REQUEST_IMAGE_CAPTURE)
            {
                val imageBitmap :Bitmap? = data?.extras?.get("data") as Bitmap
                GImageView.setImageBitmap(imageBitmap)
            }
        }
    }

requestCode가 REQUEST_IMAGE_CAPTURE일 경우 "data" 이미지를 이미지 뷰에 연결할 수 있습니다.

Intent를 사용한 뷰 이벤트는 대부분 onActivityResult를 사용해서 필터링이 가능합니다.

촬영 후 정상적으로 이미지 뷰에 촬영한 사진이 출력됩니다.

일반적인 카메라 이미지를 사용할 경우는 Intent를 사용하면 매우 편리합니다.

하지만, 카메라 기능을 제어할 수 없기 때문에 카메라 기능을 사용할 경우는 별도 뷰를 개발해야 합니다.

감사합니다.

https://developer.android.com/training/camera/photobasics?hl=ko

 

사진 촬영  |  Android 개발자  |  Android Developers

이 과정에서는 기존 카메라 애플리케이션을 사용하여 사진을 캡처하는 방법을 설명합니다. 클라이언트 앱을 실행하는 기기에서 촬영한 하늘 사진을 조합하여 세계 날씨 지도를 만드는 크라우

developer.android.com

 

반응형

+ Recent posts