Membuat thumbnail media

Thumbnail media memberi pengguna pratinjau visual gambar dan video dengan cepat, memungkinkan penjelajahan yang lebih cepat sekaligus membuat antarmuka aplikasi lebih visual memikat dan memikat. Karena thumbnail lebih kecil daripada media berukuran penuh, thumbnail membantu menghemat memori, ruang penyimpanan, dan bandwidth sekaligus meningkatkan performa penjelajahan media.

Tergantung pada jenis file dan akses file yang Anda miliki dalam aplikasi dan aset media, Anda dapat membuat {i>thumbnail<i} dengan berbagai cara.

Membuat thumbnail menggunakan library pemuatan gambar

Library pemuatan gambar melakukan banyak bagian pekerjaan yang sulit untuk Anda; mereka dapat menangani ke cache bersama dengan logika untuk mengambil sumber media dari lokal atau jaringan resource berdasarkan Uri. Kode berikut menunjukkan penggunaan Library pemuatan gambar Coil berfungsi untuk gambar dan video, dan berfungsi pada sumber daya lokal atau jaringan.

// 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
)

Jika memungkinkan, buat thumbnail di sisi server. Lihat Memuat gambar untuk mengetahui detail tentang cara memuat gambar menggunakan Compose, dan Memuat bitmap besar secara efisien untuk mendapatkan panduan tentang cara menggunakan gambar berukuran besar.

Membuat thumbnail dari file gambar lokal

Mendapatkan gambar thumbnail melibatkan penurunan skala yang efisien sekaligus menjaga visual kualitas gambar, menghindari penggunaan memori yang berlebihan, menangani berbagai format file, dan penggunaan data Exif dengan benar.

Metode createImageThumbnail melakukan semua ini, asalkan Anda memiliki akses ke jalur file gambar.

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

Jika hanya memiliki Uri, Anda dapat menggunakan metode loadThumbnail di ContentResolver mulai dari Android 10, API level 29.

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

ImageDecoder, yang tersedia mulai dari Android 9, API level 28, memiliki beberapa opsi solid untuk mengambil sampel ulang gambar saat Anda mendekodenya untuk mencegah penggunaan memori ekstra.

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);

Anda dapat menggunakan BitmapFactory untuk membuat thumbnail bagi aplikasi yang menargetkan rilis Android sebelumnya. BitmapFactory.Options memiliki setelan untuk hanya mendekode batas tertentu pada gambar untuk pengambilan sampel ulang.

Pertama, dekode hanya batas bitmap ke 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()

Gunakan width dan height dari BitmapFactory.Options untuk menetapkan contoh ukuran:

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
        }
    }

Mendekode streaming. Ukuran gambar yang dihasilkan diambil sampelnya dengan pangkat dua berdasarkan inSampleSize.

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

Membuat thumbnail dari file video lokal

Mendapatkan gambar thumbnail video memunculkan banyak tantangan yang sama seperti mendapatkan {i>thumbnail<i} gambar, tetapi ukuran file bisa jauh lebih besar dan mendapatkan frame video yang representatif tidak selalu semudah memilih frame video.

Metode createVideoThumbnail adalah pilihan yang tepat jika Anda memiliki akses ke dari jalur file video.

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

Jika hanya memiliki akses ke URI konten, Anda dapat menggunakan MediaMetadataRetriever.

Pertama, periksa apakah video memiliki thumbnail yang disematkan, dan gunakan mungkin:

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));
    }

Ambil lebar dan tinggi video dari MediaMetadataRetriever untuk menghitung faktor penskalaan:

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)

Di Android 9+ (API level 28), MediaMetadataRetriever dapat menampilkan {i>frame<i}:

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
        }
    }

Jika tidak, tampilkan frame pertama tanpa diskalakan:

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