diff --git a/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyImageLayer.kt b/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyImageLayer.kt index 70f7d9f..3482db6 100644 --- a/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyImageLayer.kt +++ b/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/render/helpers/ApplyImageLayer.kt @@ -8,12 +8,12 @@ import androidx.media3.common.Effect import androidx.media3.common.util.UnstableApi import androidx.media3.effect.BitmapOverlay import androidx.media3.effect.OverlayEffect -import ch.waio.pro_video_editor.src.features.render.utils.getRotatedVideoDimensions import java.io.File import java.nio.ByteBuffer import androidx.core.graphics.scale import androidx.media3.effect.StaticOverlaySettings import androidx.media3.effect.TimestampWrapper +import ch.waio.pro_video_editor.src.features.render.models.ImageLayer /** * Applies static image overlay on video. @@ -38,7 +38,7 @@ import androidx.media3.effect.TimestampWrapper fun applyImageLayer( videoEffects: MutableList, inputFile: File, - imageLayers: List, + imageLayers: List, rotationDegrees: Float, cropWidth: Int?, cropHeight: Int?, @@ -90,7 +90,8 @@ fun applyTimedImageLayers( // Scale to target size if provided val sizedBitmap = if (layer.width != null && layer.height != null) { val scaled = layerBitmap.scale(layer.width.toInt(), layer.height.toInt()) - layerBitmap.recycle() + // scale() may return the same object when dimensions already match + if (scaled !== layerBitmap) layerBitmap.recycle() scaled } else { layerBitmap @@ -106,13 +107,8 @@ fun applyTimedImageLayers( if (isStretched) { // Stretch image to fill the entire video frame - val scaledOverlay = if (sizedBitmap.width != videoWidth || sizedBitmap.height != videoHeight) { - val scaled = sizedBitmap.scale(videoWidth, videoHeight) - sizedBitmap.recycle() - scaled - } else { - sizedBitmap - } + val scaledOverlay = sizedBitmap.scale(videoWidth, videoHeight) + if (scaledOverlay !== sizedBitmap) sizedBitmap.recycle() val unpremultiplied = unpremultiplyAlpha(scaledOverlay) if (unpremultiplied !== scaledOverlay) scaledOverlay.recycle() diff --git a/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/thumbnail/ThumbnailGenerator.kt b/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/thumbnail/ThumbnailGenerator.kt index ac87d2c..8899394 100644 --- a/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/thumbnail/ThumbnailGenerator.kt +++ b/android/src/main/kotlin/ch/waio/pro_video_editor/src/features/thumbnail/ThumbnailGenerator.kt @@ -18,6 +18,7 @@ import kotlinx.coroutines.withContext import java.io.ByteArrayOutputStream import java.io.File import java.util.concurrent.atomic.AtomicInteger +import androidx.core.graphics.scale /** * Service for generating video thumbnail images. @@ -140,13 +141,18 @@ class ThumbnailGenerator(private val context: Context) { if (bitmap != null) { val resized = resizeBitmapKeepingAspect(bitmap, outputWidth, outputHeight, boxFit) - val bytes = compressBitmap(resized, outputFormat, jpegQuality) - thumbnails[index] = bytes - val duration = System.currentTimeMillis() - startTime - Log.d( - THUMBNAIL_TAG, - "✅ [$index] Generated in $duration ms (${bytes.size} bytes)" - ) + try { + val bytes = compressBitmap(resized, outputFormat, jpegQuality) + thumbnails[index] = bytes + val duration = System.currentTimeMillis() - startTime + Log.d( + THUMBNAIL_TAG, + "✅ [$index] Generated in $duration ms (${bytes.size} bytes)" + ) + } finally { + if (resized !== bitmap) bitmap.recycle() + resized.recycle() + } } else { Log.w(THUMBNAIL_TAG, "[$index] ❌ Null frame at ${timeUs / 1000} ms") } @@ -217,13 +223,18 @@ class ThumbnailGenerator(private val context: Context) { if (bitmap != null) { val resized = resizeBitmapKeepingAspect(bitmap, outputWidth, outputHeight, boxFit) - val bytes = compressBitmap(resized, outputFormat, jpegQuality) - thumbnails[index] = bytes - val duration = System.currentTimeMillis() - startTime - Log.d( - THUMBNAIL_TAG, - "[$index] ✅ ${timeUs / 1000} ms in $duration ms (${bytes.size} bytes)" - ) + try { + val bytes = compressBitmap(resized, outputFormat, jpegQuality) + thumbnails[index] = bytes + val duration = System.currentTimeMillis() - startTime + Log.d( + THUMBNAIL_TAG, + "[$index] ✅ ${timeUs / 1000} ms in $duration ms (${bytes.size} bytes)" + ) + } finally { + if (resized !== bitmap) bitmap.recycle() + resized.recycle() + } } else { Log.w(THUMBNAIL_TAG, "[$index] ❌ Null frame at ${timeUs / 1000} ms") } @@ -328,7 +339,7 @@ class ThumbnailGenerator(private val context: Context) { val resizedWidth = (originalWidth * scale).toInt() val resizedHeight = (originalHeight * scale).toInt() - return Bitmap.createScaledBitmap(original, resizedWidth, resizedHeight, true) + return original.scale(resizedWidth, resizedHeight) } /**