生成媒体缩略图

媒体缩略图可让用户快速直观地预览图片和视频,从而加快浏览速度,同时让应用界面更具视觉吸引力和互动性。由于缩略图比完整尺寸的媒体小,因此有助于节省内存、存储空间和带宽,同时提升媒体浏览性能。

您可以通过多种不同的方式创建缩略图,具体取决于文件类型以及您在应用和媒体资源中的文件访问权限。

使用图片加载库创建缩略图

图片加载库会为您完成许多繁杂的工作;他们可以处理 以及从本地或网络提取源媒体的逻辑 根据 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 中的 widthheight 来设置示例 大小:

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
}