Today's

길을 나서지 않으면 그 길에서 만날 수 있는 사람을 만날 수 없다

모바일 앱(안드로이드)

안드로이드 앱 만들기 : Cropper 이미지 자르기 Jetpack compose 에서도 ...

Billcorea 2023. 5. 7. 14:20
반응형

오늘은 이미지 자르기 (cropper)에 대한 이야기를 적어 보겠습니다. 해당 라이브러리의 출처는 다음과 같습니다. 

https://github.com/CanHub/Android-Image-Cropper

 

GitHub - CanHub/Android-Image-Cropper: Image Cropping Library for Android, optimised for Camera / Gallery.

Image Cropping Library for Android, optimised for Camera / Gallery. - GitHub - CanHub/Android-Image-Cropper: Image Cropping Library for Android, optimised for Camera / Gallery.

github.com

2021년쯤에 이 방법을  java 코드에 연결해 보았던 기억이 있으나 그때 사용 되었던 github는 이제 더 이상 관리가 되지 않고 있었고 그걸 fork 해 계속 유지하고 있는 코드를 발견하게 되었습니다. 

 

카메라 또는 갤러리에서 가져오는 이미지는 전체가 필요할 수 도 있지만,  문자 인식 등에 활용 하기 위해서는 필요한 부분만을 가져와야 하는 경우 등이 있습니다. 

 

이 떄 유용하게 활용될 수 있습니다.   

 

이미지 자르기 (cropper 기능)을 활용하는 방법은 먼저 gradle 파일에 다음과 같이 기술합니다. 

// image cropper
implementation("com.vanniktech:android-image-cropper:4.5.0")

현재는 4.5.0 이 마지막 버전입니다. 이 글을 보시는 시점에는 다시 확인해 보시는 것이 좋을 듯합니다. 

 

이제 코드 구현을 해 보겠습니다.  먼저 callback 을 구현합니다.  호출된 이후에 결과가 돌아오면 그걸 현재는 viewModel에 있는 변수에 저장해 볼 예정입니다.

private val customCropImage = registerForActivityResult(CropImageContract()) {
    if (it !is CropImage.CancelledResult) {
        dataViewModel.textRecognitionUri.value = it.uriContent
        Log.e("", "cropImage = ${it.uriContent}")
    }
}

 

이 응답을 받기 위해서 아래와 같이 호출을 시도합니다. 

 

private fun startCameraWithoutUri(includeCamera: Boolean, includeGallery: Boolean) {
    customCropImage.launch(
        CropImageContractOptions(
            uri = null,
            cropImageOptions = CropImageOptions(
                imageSourceIncludeCamera = includeCamera,
                imageSourceIncludeGallery = includeGallery,
            ),
        ),
    )
}

첫 번째 인수는 카메라 선택 가능 여부, 두 번째는 갤러리 선택 가능 여부입니다.  저는 두 개다 true로 실행을 했습니다. 

 

선택하기 화면

 

이렇게 activity가 실행이 되게 되는 이때 꼭 해야 할 것은 다음과 같이 manifest.xml 파일에 해당 activite을 추가해 주어야 합니다. 

 

<activity
    android:name="com.canhub.cropper.CropImageActivity"
    android:theme="@style/Base.Theme.AppCompat" />

또한 갤러리와 카메라  선택 하는 문구가 영문으로 표출되는 것이 기본 값인데, 그걸 한글로 표시 되게 하기 위해서 

values / strings.xml 에 다음과 같이 추가했습니다. 

 

 

<string name="ic_rotate_left_24">반시계 회전</string>
<string name="ic_rotate_right_24">회전</string>
<string name="crop_image_menu_crop">자르기</string>
<string name="ic_flip_24">반전</string>
<string name="ic_flip_24_horizontally">좌우반전</string>
<string name="ic_flip_24_vertically">상하반전</string>
<string name="pick_image_chooser_title">이미지 선택</string>
<string name="pick_image_camera">카메라로 촬영하기</string>
<string name="pick_image_gallery">저장된 이미지 읽어오기</string>

이런 문구들이 들어가므로 해서 화면에 한글로 표시가 진행됩니다. 

 

이제 실행하고 그 결과를 보겠습니다.

이미지 선택 전 후 모습

 

당초에는 전체 촬영된 이미지가 보였으나,   안내선을 이용하여 조정한 후 화면 상단의 자르기 버튼을 선택하면 오른쪽의 그림과 같이 선택된 부분만 이미지로 전달을 받아 보여 줄 수 있었습니다. 

 

위에서 선택된 결과를 viewModel에 있는 변수에 받았으니 그걸 그대로 Image에 넣어주는 것으로 그 코드를 정리가 되었습니다.  그 부분은 다음과 같습니다. 

dataViewModel.textRecognitionUri.value?.let {
    val source = ImageDecoder
        .createSource(context.contentResolver, it)
    bitmapPic.value = ImageDecoder.decodeBitmap(source)
    bitmapPic.value?.let { btm ->
        val quality = 20
        val baos = ByteArrayOutputStream()
        btm.compress(
            Bitmap.CompressFormat.WEBP_LOSSLESS,
            quality,
            baos
        )
        val b: ByteArray = baos.toByteArray()
        productImage = Base64.encodeToString(b, Base64.DEFAULT)

        Image(
            bitmap = btm.asImageBitmap(),
            contentDescription = "profile",
            contentScale = ContentScale.FillBounds,
            modifier = Modifier
                .clip(shape = RoundedCornerShape(16.dp))
                .width(screenWidth * .8f)
                .height(300.dp)
        )
    }
}

uri로 받아온 응답을 활용해서 이미지로 변환해 바로 보여주는 방식으로 말입니다.  Quality는 20으로 조정을 하기는 했으나, 그렇게 차이가 나는 것을 느낄 수 없었습니다. 

 

이제 이미지 자르기는 그렇게 어렵지 않게 구현을 해 볼 수 있을 것 같습니다.

 

반응형