시배's Android

TiTi Side Project | Bitmap을 갤러리 저장 or Share 하기 본문

Android/TiTi Side Project

TiTi Side Project | Bitmap을 갤러리 저장 or Share 하기

si8ae 2024. 3. 21. 22:56
 

TiTi Side Project | Composable 함수를 Bitmap으로 변환하기

Timer TiTi 프로젝트에서는 Card를 이미지로 변환하여 갤러리에 저장하거나 인스타그램에 공유하는 기능을 추가하려고 합니다. 이를 위해서는 Composable 함수로 구현된 View를 Bitmap으로 변환하는 로직

si8ae.tistory.com

갤러리 저장

먼저, 비트맵 객체를 PNG 포맷의 이미지 파일로 저장하는 과정을 살펴봅시다. 이를 위해 저는 먼저 저장할 파일의 위치와 이름을 정의해야 합니다. 여기서는 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)를 사용하여 공용 사진 디렉토리에 파일을 저장하고, 파일 이름은 "screenshot-" 뒤에 랜덤 UUID를 붙여 고유하게 만들었습니다.

val file = File(
    Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
    "screenshot-${UUID.randomUUID()}.png",
)

이제, writeBitmap 확장 함수를 사용하여 비트맵을 파일로 저장합니다. 이 함수는 File 객체를 확장하여, 비트맵을 지정된 포맷과 품질로 압축하고 파일에 쓰는 기능을 제공합니다.

private fun File.writeBitmap(bitmap: Bitmap, format: Bitmap.CompressFormat, quality: Int) {
    outputStream().use { out ->
        bitmap.compress(format, quality, out)
        out.flush()
    }
}

이미지 파일을 디스크에 저장한 후, 파일이 시스템의 미디어 스토어 데이터베이스에 등록되어야 다른 앱들(예: 갤러리 앱)이 이 파일을 인식할 수 있습니다. 이를 위해 MediaScannerConnection.scanFile 메소드를 사용하여 파일을 스캔하고 미디어 스토어에 등록합니다.

private suspend fun scanFilePath(context: Context, filePath: String): Uri? {
    return suspendCancellableCoroutine { continuation ->
        MediaScannerConnection.scanFile(
            context,
            arrayOf(filePath),
            arrayOf("image/png"),
        ) { _, scannedUri ->
            if (scannedUri == null) {
                continuation.cancel(Exception("File $filePath could not be scanned"))
            } else {
                continuation.resume(scannedUri)
            }
        }
    }
}

이 함수는 코루틴을 사용하여 비동기적으로 작동하며, 파일 스캔이 완료되면 해당 파일의 URI를 반환합니다. 만약 파일 스캔에 실패하면 예외를 발생시킵니다.

저장 없이 공유

비트맵 이미지를 임시 파일로 저장하고, 이 파일의 Uri를 반환합니다.

private fun Bitmap.getUri(context: Context): Uri {
    val cachePath = File(context.cacheDir, "images")
    cachePath.mkdirs()

    val file = File(cachePath, "screenshot-${UUID.randomUUID()}.png")
    file.writeBitmap(this, Bitmap.CompressFormat.PNG, 100)

    return FileProvider.getUriForFile(context, context.packageName + ".provider", file)
}
  1. 캐시 디렉토리 설정: 앱의 캐시 디렉토리 안에 "images"라는 새 폴더를 생성합니다. 이 폴더는 임시 이미지 파일을 저장하는 데 사용됩니다.
  2. 임시 파일 생성: "screenshot-" 뒤에 랜덤 UUID를 붙여 고유한 파일 이름을 생성하고, 이 이름으로 새 파일을 생성합니다.
  3. 비트맵 저장: File.writeBitmap 확장 함수를 사용하여 비트맵을 PNG 포맷으로 압축하고, 생성한 파일에 저장합니다.
  4. Uri 반환: FileProvider.getUriForFile 메소드를 사용하여 파일의 Uri를 안전하게 생성합니다. 이 Uri는 다른 앱과 파일을 공유할 때 사용됩니다.

저장된 이미지 파일의 Uri를 사용하여 공유 인텐트를 생성하고 실행합니다.

fun shareBitmap(context: Context, uri: Uri) {
    val intent = Intent(Intent.ACTION_SEND).apply {
        type = "image/png"
        putExtra(Intent.EXTRA_STREAM, uri)
        addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    }
    startActivity(context, createChooser(intent, "Share your image"), null)
}
  1. 인텐트 설정: Intent.ACTION_SEND 액션과 "image/png" 타입을 사용하여 새 인텐트를 생성합니다. 이는 이미지를 공유하기 위한 인텐트임을 나타냅니다.
  2. Uri 추가: putExtra 메소드를 사용하여 공유할 이미지 파일의 Uri를 인텐트에 추가합니다. 이를 통해 선택된 공유 앱이 이미지에 접근할 수 있습니다.
  3. 읽기 권한 부여: addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)를 호출하여 공유 대상 앱이 파일의 내용을 읽을 수 있도록 권한을 부여합니다.
  4. 인텐트 실행: startActivity 메소드와 Intent.createChooser를 사용하여 사용자에게 앱 선택기를 표시하고, 사용자가 이미지를 공유할 앱을 선택할 수 있도록 합니다.