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

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

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

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

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