봄날은 갔다. 이제 그 정신으로 공부하자

android Bitmap과 함께하는 즐거운 시간 본문

android Tip

android Bitmap과 함께하는 즐거운 시간

길재의 그 정신으로 공부하자 2021. 3. 15. 12:47

해당 글은 Android 기기의 갤러리에서 사진을 읽어와 리스트로 보여주거나 큰 이미지를 보여주는 작업을 하는 개발자들에게 도움을

주고자 작성하였습니다.

 

사실은 이렇게라도 기록을 해놔야 제가 다음에 작업을 할때 안까먹을 것 같아서 ㅠ_ㅠ

 

비트맵에 대해 쓸 말이 많아 글을 여러 개로 나누어서 작성하도록 하겠습니다. ^___^;;;

 

갤러리에서 사진을 가져와 화면에 보여주는게 별거 아닌 것 같아도 제대로 개발하면 나름 고급 기술이 많이 들어갑니다. 

하나하나 풀어보겠습니다.

 

갤러리에서 사진을 가져오는 것은 지난번 글(https://als2019.tistory.com/54)에서 자세히 설명하였으니 넘어가고 이번 글에서는 갤러리에서 사진을 효율적으로 그리고 제대로 읽어오는 것을 중점으로 설명하겠습니다.

 

 

이전 기억을 되살려…

아래와 같이 코딩해 폴더에 있는 이미지 목록을 읽어왔다고 전제하겠습니다.

fun loadImageList(contentResolver: ContentResolver){
    val searchParams = MediaStore.MediaColumns.BUCKET_ID + " = " + folder.id

    val columns = arrayOf(MediaStore.Images.Media._ID, MediaStore.Images.Media.DATA)
    val cursor = contentResolver.query(
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
        columns,
        searchParams,
        null,
        "${MediaStore.Images.Media.DATE_TAKEN} DESC"
    )

    cursor?.let {imageCursor ->
        var position = 0
        while (imageCursor.moveToNext()) {
            val dataGalleryImage = DataGalleryImage(position, imageCursor.getLong(INDEX_IMAGE_ID), imageCursor.getString(INDEX_IMAGE_URL).toString())
            items.value.add(dataGalleryImage)
            position += 1
        }
        imageCursor.close()
    }
}

 

이렇게 읽어온 이미지를 화면에 보여주려면 아래와 같이 함수 한 줄이면 됩니다.

@BindingAdapter("loadStorageImage")
@JvmStatic
fun loadStorageImage(view : ImageView, url: String){
    view.load(File(url))
}

 

여담으로 이미지 읽어오는 load() 함수 부분은  Coil를 사용했습니다. ^___^v

Volley, Fresco, … 등 여러 이미지 라이브러리를 사용하다 최근 Coil이라는게 있다고 해서 한번 사용해보았는데

이거 사용하기도 편하고 가볍게 사용하기에 여러모로 좋은 라이브러리 같네요.

 

각설하고 이렇게 이미지를 보여주게되면 대 참사가 일어나게됩니다.

물론, Coil이랑은 상관 없습니다.

대참사의 이유는 폰에 저장된 이미지가 겁나 커서 읽어오는데 시간이 오래걸리는게 문제니까요.

 

그래서 갤러리에 저장된 이미지를 리스트에서 보여줄때는 android에서 제공하는 Thumbnail 관련 함수를 사용하는 것을 추전합니다.

이거 은근히 모르는 개발자들 조금 있더라구요…

 

고해상도 이미지가 필요 없는 리스트 등에서 썸네일 이미지를 로드하고자 하면 contentResolver.loadThumbnail()함수를 사용해야 합니다.

 

우선 단말 저장소의 Uri를 읽어온 후 Uri와 함께 읽어올 썸네일 이미지의 사이즈를 지정해 contentResolver.loadThumbnail() 함수를 호출하기만하면 썸네일 이미지를 받을 수 있습니다.

이제 이걸 ImageView에 넣어주기만 하면 됩니다.

 

자세한 코드는 아래와 같습니다.

@BindingAdapter("loadThumbnailImage")
@JvmStatic
fun loadThumbnailImage(view: ImageView, id: Long){
    val thumbUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
    val thumbnail = view.context.contentResolver.loadThumbnail(thumbUri, Size(200, 200), null)
    view.load(thumbnail)
}

 

그런데 contentResolver.loadThumbnail() 함수는 android ver Q부터 지원을 합니다.

이전 버전에 대응하기 위해서는 조금 번거롭더라도 이전 버전에서 썸네일을 읽어오는 방법을 같이 사용해야합니다.

Q 이전 버전에서는 썸네일을 MediaStore.Images.Thumbnails.getThumbnail() 함수에서 지원했었습니다.

 

이부분을 반영하여 썸네일을 읽어오는 부분을 수정하면 아래와 같습니다.

    val thumbnail = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        view.context.contentResolver.loadThumbnail(thumbUri, Size(200, 200), null)
    } else {
        MediaStore.Images.Thumbnails.getThumbnail(
            view.context.contentResolver, id,
            MediaStore.Images.Thumbnails.MINI_KIND,
            BitmapFactory.Options())
    }

 

이렇게 구현하면 리스트를 쭉 스크롤해도 이미지 로딩이 쾌적하게 되는 것을 확인할 수 있습니다.

 

이게 끝이냐 !!! 

 

그렇지 않습니다. (개발 만만치 않아요… ㅠ_ㅠ)

 

 

불행하게도 내 단말은 이미지가 잘 보이지만 남의 단말에서는 이미지가 반쯤 돌아가서 보이는 경우가 있습니다.

 

울컥~~~

 

이문제는 제 경험상 주로 LG 단말기에서만 발생하는 문제였습니다.

단말기에서 사진을 찍어 저장할 때 사진만 저장되는게 아니라 부가적인 정보까지 같이 저장됩니다.

이 정보에 이 사진을 어디어디에서 찍었고, 사이즈가 얼마고, 어떤 기기에서 찍었고, 등등의 정보와 함께 이 사진 90도 돌아갔다~~ 라는 식으로 Rotate 정보도 같이 저장됩니다.


삼성단말기의 경우 Rotate 정보가 저장되면서 사진도 같이 돌려주지만 LG다말기의 경우 Rotate 정보만 저장하고 사진을 돌려주지 않습니다. 즉, 개발자가 직접 돌려야 합니다.

 

이미지가 반쯤 돌아가 보이는 경우를 방지하기 위해 이 정보를 확인해서 적당히 돌려줘야 합니다.

 

사진의 Rotate 정보는 MediaStore의 MediaStore.Images.Media.ORIENTATION 값을 사용하거나 ExifInterface를 사용해 저장된 사진의 Rotate정보를 불러 올 수 있습니다.

DB에서 이미지 정보를 읽어올 때 MediaStore.Images.Media.ORIENTATION 값을 추가해서 읽어오거나 사진을 읽어온 후,

ExitInterface를 사용해서 사진에서 직접 Rotate정보를 읽어와야 합니다.

 

ExitInterface 사용하는 방법은 아래와 같습니다.

 

아까 읽어온 이미지 Uri를 사용해야 합니다.

ExifInterface의 getAttributeInt()함수를 사용해 이 사진의 이미지가 얼마나 돌아갔는지 얻어 올 수 있습니다.

 

자! 이제 이미지가 얼마나 돌아갔는지 알게되었으니 그걸 다시 돌려서 보여주면 됩니다.

val inputStream = context.contentResolver.openInputStream(uri)
inputStream?.let {
    val exifInterface = ExifInterface(it)
    it.close()
    val orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
    var matrix = Matrix()
    var rotateBitmap: Bitmap? = null
    when(orientation){
        ExifInterface.ORIENTATION_ROTATE_90 -> 
        ExifInterface.ORIENTATION_ROTATE_180 -> 
        ExifInterface.ORIENTATION_ROTATE_270 ->
        else -> 
    }
    … 
}

 

 

오늘은 여기까지 하고 다음에는 이렇게까지 해서 힘들게 읽어온 Bitmap 이미지를 처리하는 방법에 대해 설명하도록 하겠습니다.

Comments