Создание миниатюр мультимедиа

Миниатюры мультимедиа предоставляют пользователям быстрый визуальный предварительный просмотр изображений и видео, что позволяет ускорить просмотр и сделать интерфейс приложения более визуально привлекательным и привлекательным. Поскольку миниатюры меньше полноразмерных носителей, они помогают экономить память, место для хранения и пропускную способность, одновременно повышая производительность просмотра мультимедиа.

В зависимости от типа файла и доступа к файлу, который у вас есть в вашем приложении и ваших медиаресурсах, вы можете создавать миниатюры различными способами.

Создайте миниатюру, используя библиотеку загрузки изображений.

Библиотеки загрузки изображений делают за вас большую часть тяжелой работы; они могут обрабатывать кэширование вместе с логикой для извлечения исходного мультимедиа из локального или сетевого ресурса на основе 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 в ContentResolver, начиная с Android 10, уровень API 29.

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

ImageDecoder , доступный начиная с Android 9, уровень API 28, имеет несколько надежных опций для повторной выборки изображения по мере его декодирования, чтобы предотвратить дополнительное использование памяти.

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

Используйте width и height из BitmapFactory.Options , чтобы установить размер выборки:

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 .

    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
}