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ủ. Hãy 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à phần Tải bitmap lớn một cách hiệu quả để biết hướng dẫn về cách xử lý hình ảnh lớn.

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

Việc tạo hình thu nhỏ liên quan đến việc giảm tỷ lệ hiệu quả trong khi vẫn duy trì chất lượng hình ảnh, tránh sử dụng bộ nhớ quá mức, xử lý nhiều định dạng hình ảnh 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ể dùng BitmapFactory để tạo hình thu nhỏ cho việc nhắm mục tiêu ứng dụng sớm hơn Các bản phát hành Android. BitmapFactory.Options có một chế độ cài đặt để chỉ giải mã giới hạn của hình ảnh cho mục đích lấy mẫu lại.

Trước tiên, hãy chỉ giải mã các giới hạn của bitmap thành 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 mẫu kích thước:

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 luỹ 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ừ một tệp video trên thiết bị

Việc lấy hình thu nhỏ video cũng gặp nhiều thách thức tương tự như khi lấy hình thu nhỏ hình ảnh, nhưng kích thước tệp có thể lớn hơn nhiều và việc lấy 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 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));
    }

Truy xuất chiều rộng và chiều cao của video từ MediaMetadataRetriever để 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 chưa được điều chỉnh theo tỷ lệ:

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