Tạo hình thu nhỏ nội dung nghe nhìn

Hình thu nhỏ nội dung nghe nhìn cung cấp cho người dùng bản xem trước nhanh về hình ảnh và video, giúp duyệt xem nhanh hơn trong khi vẫn làm cho giao diện ứng dụng trở nên hấp dẫn và thu hút hơn. Vì hình thu nhỏ nhỏ hơn nội dung nghe nhìn ở kích thước đầy đủ, chúng giúp tiết kiệm bộ nhớ, không gian lưu trữ và băng thông, đồng thời cải thiện nội dung nghe nhìn hiệu suất duyệt web.

Tuỳ thuộc vào loại tệp cũng như quyền truy cập vào tệp mà bạn có trong đơn đăng ký và nội dung truyền thông của bạn, bạn có thể tạo hình thu nhỏ theo nhiều cách khác nhau.

Tạo hình thu nhỏ bằng thư viện tải hình ảnh

Thư viện tải hình ảnh sẽ thực hiện rất nhiều công việc khó khăn cho bạn; họ có thể xử lý lưu vào bộ nhớ đệm cùng với logic để tìm nạp nội dung nghe nhìn nguồn từ cục bộ hoặc mạng dựa trên Uri. Mã sau đây minh hoạ cách sử dụng Thư viện tải hình ảnh Coil hoạt động với cả hình ảnh và video, và hoạt động trên tài nguyên cục bộ hoặc tài nguyên mạng.

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

Nếu có thể, hãy tạo hình thu nhỏ phía máy chủ. Xem phần Tải hình ảnh để biết thông tin chi tiết về cách tải hình ảnh bằng Compose và Tải bitmap lớn hiệu quả để được hướng dẫn về cách làm việc với các hình ảnh lớn.

Tạo hình thu nhỏ từ tệp hình ảnh trên thiết bị

Để tải hình thu nhỏ, bạn cần giảm quy mô hiệu quả mà vẫn giữ được hình ảnh chất lượng, tránh sử dụng bộ nhớ quá mức, xử lý nhiều loại hình ảnh định dạng khác và sử dụng đúng dữ liệu Exif.

Phương thức createImageThumbnail sẽ thực hiện tất cả các việc này, miễn là bạn có truy cập vào đường dẫn của tệp hình ảnh.

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

Nếu chỉ có Uri, bạn có thể sử dụng phương thức loadThumbnail trong ContentResolver kể từ Android 10, API cấp 29.

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

ImageDecoder (có sẵn từ Android 9, API cấp 28) có một số tuỳ chọn chắc chắn để lấy mẫu lại hình ảnh khi bạn giải mã hình ảnh đó để tránh sử dụng thêm bộ nhớ.

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

Bạn có thể sử dụng BitmapFactory để tạo hình thu nhỏ cho các ứng dụng nhắm đến các bản phát hành Android cũ hơn. BitmapFactory.Options có cài đặt để giải mã chỉ ranh giới của một hình ảnh nhằm mục đích lấy mẫu lại.

Trước tiên, chỉ giải mã các giới hạn của bitmap vào 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()

Sử dụng widthheight từ BitmapFactory.Options để đặt kích thước mẫu:

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

Giải mã luồng. Kích thước của hình ảnh thu được được lấy mẫu theo lũy thừa của 2 dựa trên inSampleSize.

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

Tạo hình thu nhỏ từ tệp video cục bộ

Việc tải hình thu nhỏ video cũng trải qua nhiều thử thách tương tự như với nhưng kích thước tệp có thể lớn hơn nhiều và khiến khung hình video đại diện không phải lúc nào cũng đơn giản như chọn khung hình đầu tiên khung hình của video.

Phương thức createVideoThumbnail là một lựa chọn chắc chắn nếu bạn có quyền truy cập vào đường dẫn của tệp video.

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

Nếu chỉ có quyền truy cập vào một Uri nội dung, bạn có thể sử dụng MediaMetadataRetriever.

Trước tiên, hãy kiểm tra xem video có hình thu nhỏ được nhúng hay không và sử dụng hình thu nhỏ đó nếu có thể:

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

Tìm nạp chiều rộng và chiều cao của video từ MediaMetadataRetriever đến tính hệ số tỷ lệ:

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)

Trên Android 9 trở lên (API cấp 28), MediaMetadataRetriever có thể trả về một tỷ lệ khung:

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

Nếu không, hãy trả về khung hình đầu tiên không được điều chỉnh tỷ lệ:

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