產生媒體縮圖

媒體縮圖可讓使用者快速預覽圖片和影片,加快瀏覽速度,同時讓應用程式介面更具視覺吸引力和互動性。由於縮圖小於原尺寸的媒體 協助節省記憶體、儲存空間和頻寬,同時改善媒體品質 瀏覽效能。

視檔案類型和應用程式及媒體資產中的檔案存取權而定,您可以透過各種方式建立縮圖。

使用圖片載入庫建立縮圖

圖片載入程式庫可為您處理大部分的繁瑣作業就能處理 快取以及從本機或網路擷取來源媒體的邏輯 以 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
}