미디어 썸네일 생성

미디어 썸네일은 사용자에게 이미지와 동영상을 시각적으로 빠르게 미리 보여줍니다. 탐색 속도를 높이면서 앱 인터페이스를 시각적으로 개선 매력적이고 몰입도가 높다는 것을 의미합니다. 썸네일은 원본 크기 미디어보다 작기 때문에 메모리, 저장공간, 대역폭을 절약하면서 미디어를 개선하는 데 도움이 됩니다. 인터넷 사용 기록을 확인할 수 있습니다

파일 형식과 애플리케이션 및 해당 파일에 대한 액세스 권한에 따라 다양한 방법으로 썸네일을 만들 수 있습니다.

이미지 로드 라이브러리를 사용하여 썸네일 만들기

이미지 로드 라이브러리는 많은 어려운 작업을 수행합니다. 처리할 수 있는 로컬 또는 네트워크에서 소스 미디어를 가져오는 로직과 캐싱 Uri를 기반으로 리소스를 반환합니다. 다음 코드는 이미지와 동영상 모두에 작동하며 로컬 또는 네트워크 리소스에서 작동하는 Coil 이미지 로드 라이브러리의 사용을 보여줍니다.

// Use Coil to create and display a thumbnail of a video or image with a specific height
// ImageLoader has its own memory and storage cache, and this one is configured to also
// load frames from videos
val videoEnabledLoader = ImageLoader.Builder(context)
    .components {
        add(VideoFrameDecoder.Factory())
    }.build()
// Coil requests images that match the size of the AsyncImage composable, but this allows
// for precise control of the height
val request = ImageRequest.Builder(context)
    .data(mediaUri)
    .size(Int.MAX_VALUE, THUMBNAIL_HEIGHT)
    .build()
AsyncImage(
    model = request,
    imageLoader = videoEnabledLoader,
    modifier = Modifier
        .clip(RoundedCornerShape(20))    ,
    contentDescription = null
)

가능하다면 서버 측에서 썸네일을 만드세요. 이미지 로드를 참고하세요. Compose를 사용하여 이미지를 로드하는 방법에 관한 자세한 내용과 큰 비트맵 로드하기 효율성을 참고하세요.

로컬 이미지 파일에서 썸네일 만들기

썸네일 이미지를 얻으려면 이미지 크기를 유지하면서 효율적으로 축소해야 함 과도한 메모리 사용 방지, 다양한 이미지 처리 Exif 데이터를 올바르게 사용해야 합니다.

createImageThumbnail 메서드는 이 작업을 모두 실행하며 다음과 같은 조건을 충족해야 합니다. 이미지 파일 경로에 액세스할 수 있습니다.

val bitmap = ThumbnailUtils.createImageThumbnail(File(file_path), Size(640, 480), null)

Uri만 있는 경우 다음에서 loadThumbnail 메서드를 사용할 수 있습니다. Android 10, API 수준 29부터 ContentResolver를 사용합니다.

val thumbnail: Bitmap =
        applicationContext.contentResolver.loadThumbnail(
        content-uri, Size(640, 480), null)

Android 9, API 수준 28부터 사용할 수 있는 ImageDecoder에는 추가 메모리를 방지하기 위해 디코딩하면서 이미지를 리샘플링하는 솔리드 옵션 사용합니다

class DecodeResampler(val size: Size, val signal: CancellationSignal?) : OnHeaderDecodedListener {
    private val size: Size

   override fun onHeaderDecoded(decoder: ImageDecoder, info: ImageInfo, source:
       // sample down if needed.
        val widthSample = info.size.width / size.width
        val heightSample = info.size.height / size.height
        val sample = min(widthSample, heightSample)
        if (sample > 1) {
            decoder.setTargetSampleSize(sample)
        }
    }
}

val resampler = DecoderResampler(size, null)
val source = ImageDecoder.createSource(context.contentResolver, imageUri)
val bitmap = ImageDecoder.decodeBitmap(source, resampler);

BitmapFactory를 사용하여 이전부터 타겟팅하는 앱의 썸네일을 만들 수 있습니다. Android 출시 BitmapFactory.Options에는 리샘플링을 목적으로 이미지의 경계만 디코딩하는 설정이 있습니다.

먼저 비트맵의 경계만 BitmapFactory.Options로 디코딩합니다.

private fun decodeResizedBitmap(context: Context, uri: Uri, size: Size): Bitmap?{
    val boundsStream = context.contentResolver.openInputStream(uri)
    val options = BitmapFactory.Options()
    options.inJustDecodeBounds = true
    BitmapFactory.decodeStream(boundsStream, null, options)
    boundsStream?.close()

BitmapFactory.Optionswidthheight를 사용하여 샘플을 설정합니다. 크기:

if ( options.outHeight != 0 ) {
        // we've got bounds
        val widthSample = options.outWidth / size.width
        val heightSample = options.outHeight / size.height
        val sample = min(widthSample, heightSample)
        if (sample > 1) {
            options.inSampleSize = sample
        }
    }

스트림을 디코딩합니다. 결과 이미지의 크기는 2의 거듭제곱으로 샘플링됩니다. inSampleSize를 기반으로 합니다.

    options.inJustDecodeBounds = false
    val decodeStream = context.contentResolver.openInputStream(uri)
    val bitmap =  BitmapFactory.decodeStream(decodeStream, null, options)
    decodeStream?.close()
    return bitmap
}

로컬 동영상 파일에서 썸네일 만들기

동영상 미리보기 이미지를 만들 때는 이미지 썸네일을 가져올 수 있지만 파일 크기가 훨씬 클 수 있으며 대표 동영상 프레임이 첫 번째 프레임의 첫 번째 프레임을 선택하는 것만큼 지정할 수 있습니다.

다음에 액세스할 수 있는 경우 createVideoThumbnail 메서드를 사용하는 것이 좋습니다. 동영상 파일의 경로입니다.

val bitmap = ThumbnailUtils.createVideoThumbnail(File(file_path), Size(640, 480), null)

콘텐츠 URI에만 액세스할 수 있다면 MediaMetadataRetriever

먼저 동영상에 삽입된 썸네일이 있는지 확인하고 가능:

private suspend fun getVideoThumbnailFromMediaMetadataRetriever(context: Context, uri: Uri, size: Size): Bitmap? {
    val mediaMetadataRetriever = MediaMetadataRetriever()
    mediaMetadataRetriever.setDataSource(context, uri)
    val thumbnailBytes = mediaMetadataRetriever.embeddedPicture
    val resizer = Resizer(size, null)
    ImageDecoder.createSource(context.contentResolver, uri)
    // use a built-in thumbnail if the media file has it
    thumbnailBytes?.let {
        return ImageDecoder.decodeBitmap(ImageDecoder.createSource(it));
    }

동영상의 너비와 높이를 MediaMetadataRetriever에서 가져옵니다. 다음과 같이 배율을 계산합니다.

val width = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)
            ?.toFloat() ?: size.width.toFloat()
    val height = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)
            ?.toFloat() ?: size.height.toFloat()
    val widthRatio = size.width.toFloat() / width
    val heightRatio = size.height.toFloat() / height
    val ratio = max(widthRatio, heightRatio)

Android 9 이상 (API 수준 28)에서 MediaMetadataRetriever는 조정된 프레임:

if (ratio > 1) {
        val requestedWidth = width * ratio
        val requestedHeight = height * ratio
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            val frame = mediaMetadataRetriever.getScaledFrameAtTime(
                -1, OPTION_PREVIOUS_SYNC,
                requestedWidth.toInt(), requestedHeight.toInt())
            mediaMetadataRetriever.close()
            return frame
        }
    }

그 외의 경우에는 조정되지 않은 첫 번째 프레임을 반환합니다.

    // consider scaling this after the fact
    val frame = mediaMetadataRetriever.frameAtTime
    mediaMetadataRetriever.close()
    return frame
}