As miniaturas de mídia oferecem aos usuários uma prévia visual rápida de imagens e vídeos, tornando a navegação mais rápida e deixando a interface do app mais visualmente atrativa e envolvente. Como as miniaturas são menores que as mídias de tamanho normal, elas ajudam a economizar memória, espaço de armazenamento e largura de banda, além de melhorar o desempenho da navegação de mídia.
Dependendo do tipo de arquivo e do acesso a arquivos que você tem em seu aplicativo e seus recursos de mídia, é possível criar miniaturas de várias formas.
Criar uma miniatura usando uma biblioteca de carregamento de imagens
As bibliotecas de carregamento de imagens fazem grande parte do trabalho pesado para você. podem lidar com armazenamento em cache, além da lógica para buscar a mídia de origem do servidor com base em um Uri. O código a seguir demonstra o uso da biblioteca de carregamento de imagens Coil (link em inglês) para imagens e vídeos, e funciona em um recurso local ou de rede.
// 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
)
Se possível, crie miniaturas do lado do servidor. Consulte Como carregar imagens para saber como carregar imagens usando o Compose e Como carregar bitmaps grandes de maneira eficiente para saber como trabalhar com imagens grandes.
Criar uma miniatura de um arquivo de imagem local
A obtenção de imagens de miniatura envolve uma redução de escala eficiente, preservando a qualidade visual, evitando o uso excessivo de memória, lidando com vários formatos de imagem e fazendo uso correto dos dados Exif.
O método createImageThumbnail faz tudo isso, desde que você tenha
acesso ao caminho do arquivo de imagem.
val bitmap = ThumbnailUtils.createImageThumbnail(File(file_path), Size(640, 480), null)
Se você tiver apenas o Uri, poderá usar o método loadThumbnail no
ContentResolver a partir do Android 10, nível 29 da API.
val thumbnail: Bitmap =
applicationContext.contentResolver.loadThumbnail(
content-uri, Size(640, 480), null)
O ImageDecoder, disponível a partir do Android 9, nível 28 da API, tem algumas opções sólidas para reamostrar a imagem à medida que você a decodifica para evitar o uso de mais memória.
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);
É possível usar o BitmapFactory para criar miniaturas de apps destinados a versões anteriores do Android. BitmapFactory.Options tem uma configuração para decodificar apenas os limites de uma imagem para fins de reamostragem.
Primeiro, decodifique apenas os limites do bitmap no 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()
Usar width e height de BitmapFactory.Options para definir a amostra
tamanho:
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
}
}
Decodifique o stream. O tamanho da imagem resultante é amostrado por potências de dois
com base no inSampleSize.
options.inJustDecodeBounds = false
val decodeStream = context.contentResolver.openInputStream(uri)
val bitmap = BitmapFactory.decodeStream(decodeStream, null, options)
decodeStream?.close()
return bitmap
}
Criar uma miniatura a partir de um arquivo de vídeo local
A obtenção de imagens de miniaturas de vídeos envolve muitos dos mesmos desafios de obter miniaturas de imagens, mas os tamanhos dos arquivos podem ser muito maiores e frame de vídeo representativo nem sempre é tão simples quanto escolher o primeiro frame do vídeo.
O método createVideoThumbnail é uma boa escolha se você tiver acesso ao
caminho do arquivo de vídeo.
val bitmap = ThumbnailUtils.createVideoThumbnail(File(file_path), Size(640, 480), null)
Se você só tiver acesso a um URI de conteúdo, poderá usar
MediaMetadataRetriever
Primeiro, verifique se o vídeo tem uma miniatura incorporada e use-a se possível:
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));
}
Busque a largura e a altura do vídeo do MediaMetadataRetriever para
calcule o fator de escalonamento:
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)
No Android 9 e versões mais recentes (nível 28 da API), a MediaMetadataRetriever pode retornar
frame:
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
}
}
Caso contrário, retorne o primeiro frame sem escala:
// consider scaling this after the fact
val frame = mediaMetadataRetriever.frameAtTime
mediaMetadataRetriever.close()
return frame
}