יצירת תמונות ממוזערות של מדיה

תמונות ממוזערות של מדיה מספקות למשתמשים תצוגה מקדימה ויזואלית מהירה של תמונות וסרטונים, מה שמאפשר גלישה מהירה יותר, וגם מציג את ממשק האפליקציה בצורה ויזואלית יותר מושך ומעניין. מכיוון שהתמונות הממוזערות הן קטנות יותר ממדיה בגודל מלא, הם עוזרים לחסוך בזיכרון, בנפח האחסון וברוחב הפס, ובמקביל משפרים את המדיה ביצועי הגלישה.

בהתאם לסוג הקובץ ולגישה שיש לכם באפליקציה, וכן נכסי המדיה שלכם, אפשר ליצור תמונות ממוזערות במגוון דרכים.

יצירת תמונה ממוזערת באמצעות ספריית טעינת תמונות

ספריות טעינת התמונות עושות את העבודה הקשה בשבילך, הם יכולים לטפל לשמור במטמון יחד עם הלוגיקה לאחזור מדיה המקור מהרשת או מהרשת המקומית. על סמך 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, ובמאמר טעינה יעילה של קובצי bitmap גדולים מוסבר איך לעבוד עם תמונות גדולות.

יצירת תמונה ממוזערת מקובץ תמונה מקומי

יצירת תמונות ממוזערות כוללת התאמה יעילה לגודל קטן יותר תוך שמירה על איכות חזותית, הימנעות משימוש מוגזם בזיכרון, טיפול במגוון פורמטים של תמונות ושימוש נכון בנתוני Exif.

שיטת createImageThumbnail מבצעת את כל הפעולות האלה, בתנאי שיש לכם גישה לנתיב של קובץ התמונה.

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

אם יש לך רק את Uri, אפשר להשתמש בשיטה loadThumbnail Contentresolver החל ב-Android 10, רמת API 29.

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

ב-ImageDecoder, שזמין החל מ-Android 9, רמת API 28, יש כמה אפשרויות טובות לדגימה מחדש של התמונה בזמן פענוח שלה, כדי למנוע שימוש נוסף בזיכרון.

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

אפשר להשתמש ב-Bitmap מראש כדי ליצור תמונות ממוזערות לאפליקציות שמטרגטות קודם. גרסאות של 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()

כדי להגדיר את הדוגמה, משתמשים ב-width וב-height מ-BitmapFactory.Options size:

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
}