إنشاء صور مصغّرة للوسائط

تتيح الصور المصغّرة للوسائط للمستخدمين معاينة مرئية سريعة للصور والفيديوهات السماح بالتصفح بشكل أسرع مع جعل واجهة التطبيق أكثر وضوحًا جذابة وجذابة. وبما أنّ الصور المصغّرة أصغر حجمًا من الوسائط بالحجم الكامل، تساعد في توفير الذاكرة ومساحة التخزين ومعدّل نقل البيانات مع تحسين أداء التصفح في الوسائط.

استنادًا إلى نوع الملف وإمكانية الوصول إلى الملف في تطبيقك ومواد عرض الوسائط، يمكنك إنشاء صور مصغّرة بطرق مختلفة.

إنشاء صورة مصغّرة باستخدام مكتبة لتحميل الصور

تتولّى مكتبات تحميل الصور إنجاز المهام الصعبة نيابةً عنك. يمكنه التعامل مع التخزين المؤقت إلى جانب المنطق لجلب وسائط المصدر من الشبكة المحلية أو الشبكة بناءً على 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 فقط، يمكنك استخدام طريقة loadThumbnail في ContentResolver بدءًا من Android 10، المستوى 29 لواجهة برمجة التطبيقات.

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

توفّر فئة ImageDecoder، المتوفّرة اعتبارًا من Android 9، المستوى 28 من واجهة برمجة التطبيقات، بعض options الفعّالة لإعادة تحليل الصورة أثناء فك ترميزها لمنع استخدام موارد ذاكرة إضافية.

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 الأقدم. يتضمن Bitmapvan.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()

استخدِم width وheight من BitmapFactory.Options لإعداد العيّنة. الحجم:

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)

في الإصدار 9 من نظام التشغيل Android والإصدارات الأحدث (المستوى 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
}