미디어 썸네일은 사용자에게 이미지와 동영상을 시각적으로 빠르게 미리 보여줍니다. 앱 인터페이스를 시각적으로 개선하고 탐색 속도를 높일 수 있습니다. 매력적이고 몰입도가 높다는 것을 의미합니다. 썸네일은 원본 크기 미디어보다 작기 때문에 메모리, 저장공간, 대역폭을 절약하면서 미디어를 개선하는 데 도움이 됩니다. 인터넷 사용 기록을 확인할 수 있습니다
파일 형식과 애플리케이션 및 해당 파일에 대한 액세스 권한에 따라 다양한 방법으로 썸네일을 만들 수 있습니다.
이미지 로드 라이브러리를 사용하여 썸네일 만들기
이미지 로드 라이브러리는 여러 까다로운 작업을 처리합니다. 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만 있는 경우 Android 10(API 수준 29)부터 ContentResolver에서 loadThumbnail 메서드를 사용할 수 있습니다.
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.Options의 width 및 height를 사용하여 샘플 크기를 설정합니다.
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
}
}
스트림을 디코딩합니다. 결과 이미지의 크기는 inSampleSize를 기반으로 2의 거듭제곱으로 샘플링됩니다.
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
}