From cccf7fdc718987857ae37105476a7b71b1c0bdc8 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 8 Oct 2026 11:15:28 +0200 Subject: [PATCH 1/2] perf(bitmap): usage Signed-off-by: alperozturk96 # Conflicts: # app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java --- .../client/documentscan/GeneratePDFUseCase.kt | 1 + .../jobs/gallery/GalleryImageGenerationJob.kt | 2 +- .../com/nextcloud/utils/BitmapExtensions.kt | 25 +++++------ .../nextcloud/utils/avatar/AvatarLoader.kt | 4 +- .../utils/thumbnail/FileThumbnailGenerator.kt | 19 ++++++++- .../android/ui/adapter/DiskLruImageCache.kt | 11 +++++ .../ui/preview/PreviewImageFragment.kt | 8 ++++ .../ui/preview/pdf/PreviewPdfAdapter.kt | 31 +++++++++----- .../com/owncloud/android/utils/BitmapUtils.kt | 42 +++++++++++++++++++ 9 files changed, 117 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/documentscan/GeneratePDFUseCase.kt b/app/src/main/java/com/nextcloud/client/documentscan/GeneratePDFUseCase.kt index 0f6cfa220e22..87bd71b87e17 100644 --- a/app/src/main/java/com/nextcloud/client/documentscan/GeneratePDFUseCase.kt +++ b/app/src/main/java/com/nextcloud/client/documentscan/GeneratePDFUseCase.kt @@ -52,6 +52,7 @@ class GeneratePDFUseCase @Inject constructor(private val logger: Logger) { val page = document.startPage(pageInfo) page.canvas.drawBitmap(bitmap, 0f, 0f, null) document.finishPage(page) + bitmap.recycle() } } diff --git a/app/src/main/java/com/nextcloud/client/jobs/gallery/GalleryImageGenerationJob.kt b/app/src/main/java/com/nextcloud/client/jobs/gallery/GalleryImageGenerationJob.kt index 7f6eddc05223..8c0ea4571f29 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/gallery/GalleryImageGenerationJob.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/gallery/GalleryImageGenerationJob.kt @@ -154,7 +154,7 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag var bitmap = BitmapUtils.decodeSampledBitmapFromFile(file.storagePath, pxW, pxH) ?: return null if (file.isPNG()) { - bitmap = ThumbnailsCacheManager.handlePNG(bitmap, pxW, pxH) + bitmap = BitmapUtils.centerCropOnPngBackground(bitmap, pxW, pxH) } val thumbnail = ThumbnailsCacheManager.addThumbnailToCache(cacheKey, bitmap, file.storagePath, pxW, pxH) diff --git a/app/src/main/java/com/nextcloud/utils/BitmapExtensions.kt b/app/src/main/java/com/nextcloud/utils/BitmapExtensions.kt index 104efca9972f..1a7c107021cd 100644 --- a/app/src/main/java/com/nextcloud/utils/BitmapExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/BitmapExtensions.kt @@ -22,11 +22,10 @@ private const val TAG = "BitmapExtension" fun Bitmap.allocationKilobyte(): Int = allocationByteCount.div(1024) /** - * Recursively scales down the Bitmap until its size allocation is within the specified size. + * Scales down the Bitmap step by step until its size allocation is within the specified size. * - * This function checks if the current Bitmap's size (in kilobytes) is already within - * the target size. If not, it scales the Bitmap down by a factor of `1.5` in both width and height - * and calls itself recursively until the size condition is met. + * Each step scales the previous result down by a factor of `1.5` in both width and height. Intermediate + * bitmaps are recycled as soon as the next step exists; the receiver is never recycled. * * @receiver Bitmap The original Bitmap to be resized. * @param targetKB The target size in kilobytes (KB) that the Bitmap should be reduced to. @@ -34,17 +33,19 @@ fun Bitmap.allocationKilobyte(): Int = allocationByteCount.div(1024) */ @Suppress("MagicNumber") fun Bitmap.scaleUntil(targetKB: Int): Bitmap { - if (allocationKilobyte() <= targetKB) { - return this - } - // 1.5 is used to gradually scale down while minimizing distortion val scaleRatio = 1.5 - val width = width.div(scaleRatio).toInt() - val height = height.div(scaleRatio).toInt() + var result = this + + while (result.allocationKilobyte() > targetKB) { + val scaled = result.scale(result.width.div(scaleRatio).toInt(), result.height.div(scaleRatio).toInt()) + if (result !== this && result !== scaled) { + result.recycle() + } + result = scaled + } - val scaledBitmap = scale(width, height) - return scaledBitmap.scaleUntil(targetKB) + return result } /** diff --git a/app/src/main/java/com/nextcloud/utils/avatar/AvatarLoader.kt b/app/src/main/java/com/nextcloud/utils/avatar/AvatarLoader.kt index 728c5ca66471..1a218f79da4b 100644 --- a/app/src/main/java/com/nextcloud/utils/avatar/AvatarLoader.kt +++ b/app/src/main/java/com/nextcloud/utils/avatar/AvatarLoader.kt @@ -120,13 +120,13 @@ class AvatarLoader @Inject constructor( } val bitmap = BitmapFactory.decodeStream(get.responseBodyAsStream) - val thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px) + val thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px, ThumbnailUtils.OPTIONS_RECYCLE_INPUT) if (thumbnail == null || newETag.isNullOrEmpty()) { return TextDrawable.createAvatar(request.user, request.avatarRadius) } - val avatar = ThumbnailsCacheManager.handlePNG(thumbnail, px, px) + val avatar = BitmapUtils.centerCropOnPngBackground(thumbnail, px, px) ThumbnailsCacheManager.addBitmapToCache(request.cacheKey(newETag), avatar) storeRefreshTimestamp(request) return toDrawable(request, avatar) diff --git a/app/src/main/java/com/nextcloud/utils/thumbnail/FileThumbnailGenerator.kt b/app/src/main/java/com/nextcloud/utils/thumbnail/FileThumbnailGenerator.kt index 49a32191b3c8..dc20143eee29 100644 --- a/app/src/main/java/com/nextcloud/utils/thumbnail/FileThumbnailGenerator.kt +++ b/app/src/main/java/com/nextcloud/utils/thumbnail/FileThumbnailGenerator.kt @@ -7,7 +7,9 @@ package com.nextcloud.utils.thumbnail +import android.content.ComponentCallbacks2 import android.content.Context +import android.content.res.Configuration import android.graphics.Bitmap import android.graphics.Color import android.graphics.drawable.Drawable @@ -52,7 +54,7 @@ class FileThumbnailGenerator @Inject constructor( private val viewThemeUtils: Provider, private val context: Context, private val accountManager: UserAccountManager -) { +) : ComponentCallbacks2 { companion object { private const val TAG = "FileThumbnailGenerator" @@ -70,6 +72,21 @@ class FileThumbnailGenerator @Inject constructor( private val tasks = CopyOnWriteArrayList() private val placeholders = mutableMapOf() + init { + context.applicationContext.registerComponentCallbacks(this) + } + + override fun onTrimMemory(level: Int) { + if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) { + synchronized(placeholders) { placeholders.clear() } + } + } + + override fun onConfigurationChanged(newConfig: Configuration) = Unit + + @Deprecated("Deprecated in Java") + override fun onLowMemory() = Unit + fun setThumbnail(file: OCFile, view: ImageView, arguments: ThumbnailArguments) { if (file.remoteId == null) { setLocalThumbnail(file, view, arguments) diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/DiskLruImageCache.kt b/app/src/main/java/com/owncloud/android/ui/adapter/DiskLruImageCache.kt index 6670da978211..f9f1ab711c64 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/DiskLruImageCache.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/DiskLruImageCache.kt @@ -58,6 +58,15 @@ constructor( null } + fun getDecodedSizeInKB(key: String): Int? = try { + readBounds(key.toValidKey()) + ?.takeIf { it.outWidth > 0 && it.outHeight > 0 } + ?.let { it.outWidth * it.outHeight * ARGB_8888_BYTES_PER_PIXEL / BYTES_PER_KB } + } catch (e: IOException) { + Log_OC.e(TAG, e.message, e) + null + } + fun containsKey(key: String): Boolean = try { diskCache.get(key.toValidKey())?.use { true } == true } catch (e: IOException) { @@ -125,6 +134,8 @@ constructor( private const val VALUE_COUNT = 1 private const val VALUE_INDEX = 0 private const val IO_BUFFER_SIZE = 8 * 1024 + private const val ARGB_8888_BYTES_PER_PIXEL = 4 + private const val BYTES_PER_KB = 1024 private val TAG = DiskLruImageCache::class.java.simpleName } diff --git a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt index ae30ce5e3344..a5315591a1e9 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/preview/PreviewImageFragment.kt @@ -332,9 +332,17 @@ class PreviewImageFragment : Log_OC.d(TAG, "onStop starts") loadBitmapTask?.cancel(true) loadBitmapTask = null + releaseOwnedBitmap() super.onStop() } + private fun releaseOwnedBitmap() { + val bitmap = ownedBitmap ?: return + binding.image.setImageDrawable(null) + bitmap.recycle() + ownedBitmap = null + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val menuHost: MenuHost = requireActivity() addMenuProvider(menuHost) diff --git a/app/src/main/java/com/owncloud/android/ui/preview/pdf/PreviewPdfAdapter.kt b/app/src/main/java/com/owncloud/android/ui/preview/pdf/PreviewPdfAdapter.kt index f5432f153332..3853b5c54bd2 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/pdf/PreviewPdfAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/preview/pdf/PreviewPdfAdapter.kt @@ -8,7 +8,6 @@ package com.owncloud.android.ui.preview.pdf import android.graphics.Bitmap -import android.graphics.Canvas import android.graphics.Color import android.graphics.pdf.PdfRenderer import android.view.LayoutInflater @@ -28,12 +27,24 @@ class PreviewPdfAdapter( class ViewHolder(val binding: PreviewPdfPageItemBinding, val onClickListener: (Bitmap) -> Unit) : RecyclerView.ViewHolder(binding.root) { + private var pageBitmap: Bitmap? = null + fun bind(bitmap: Bitmap) { + releaseBitmap() + pageBitmap = bitmap binding.page.setImageBitmap(bitmap) binding.root.setOnClickListener { onClickListener(bitmap) } } + + fun releaseBitmap() { + val bitmap = pageBitmap ?: return + binding.page.setImageDrawable(null) + binding.root.setOnClickListener(null) + bitmap.recycle() + pageBitmap = null + } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { @@ -49,19 +60,19 @@ class PreviewPdfAdapter( holder.bind(bitmap) } + override fun onViewRecycled(holder: ViewHolder) { + holder.releaseBitmap() + super.onViewRecycled(holder) + } + private fun renderPage(page: PdfRenderer.Page) = page.use { val bitmap = createBitmapForPage(it) page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY) bitmap } - private fun createBitmapForPage(page: PdfRenderer.Page): Bitmap { - val bitmap = createBitmap(screenWidth, (screenWidth.toFloat() / page.width * page.height).toInt()) - - val canvas = Canvas(bitmap) - canvas.drawColor(Color.WHITE) - canvas.drawBitmap(bitmap, 0f, 0f, null) - - return bitmap - } + private fun createBitmapForPage(page: PdfRenderer.Page): Bitmap = + createBitmap(screenWidth, (screenWidth.toFloat() / page.width * page.height).toInt()).apply { + eraseColor(Color.WHITE) + } } diff --git a/app/src/main/java/com/owncloud/android/utils/BitmapUtils.kt b/app/src/main/java/com/owncloud/android/utils/BitmapUtils.kt index 73489cfed6de..cbbadce1cb6f 100644 --- a/app/src/main/java/com/owncloud/android/utils/BitmapUtils.kt +++ b/app/src/main/java/com/owncloud/android/utils/BitmapUtils.kt @@ -22,6 +22,7 @@ import android.graphics.Rect import android.graphics.RectF import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable +import android.media.MediaMetadataRetriever import android.widget.ImageView import androidx.annotation.DimenRes import androidx.core.graphics.applyCanvas @@ -43,6 +44,7 @@ import com.owncloud.android.lib.resources.users.StatusType import com.owncloud.android.ui.StatusDrawable import java.security.MessageDigest import java.security.NoSuchAlgorithmException +import kotlin.math.max import kotlin.math.min import kotlin.math.roundToInt import com.nextcloud.utils.decodeSampledBitmapFromFile as decodeSampledBitmap @@ -124,6 +126,46 @@ object BitmapUtils { return bitmap.scale((scale * width).roundToInt(), (scale * height).roundToInt()) } + @JvmStatic + fun fitsInto(bitmap: Bitmap, px: Int): Boolean = max(bitmap.width, bitmap.height) <= px + + @JvmStatic + fun scaleToFit(bitmap: Bitmap, px: Int): Bitmap = + scaleBitmap(bitmap, px.toFloat(), bitmap.width, bitmap.height, max(bitmap.width, bitmap.height)) + + @JvmStatic + fun getLargestVideoDimension(retriever: MediaMetadataRetriever): Int { + val width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toIntOrNull() ?: 0 + val height = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toIntOrNull() ?: 0 + return max(width, height) + } + + @JvmStatic + fun centerCropOnPngBackground(source: Bitmap, width: Int, height: Int): Bitmap { + // a software Canvas cannot draw HARDWARE bitmaps, every other config can be drawn directly + val drawableSource = if (source.config == Bitmap.Config.HARDWARE) { + source.copy(Bitmap.Config.ARGB_8888, false) + } else { + source + } + + val scale = max(width.toFloat() / source.width, height.toFloat() / source.height) + val scaledWidth = scale * source.width + val scaledHeight = scale * source.height + val left = (width - scaledWidth) / 2 + val top = (height - scaledHeight) / 2 + val targetRect = RectF(left, top, left + scaledWidth, top + scaledHeight) + + return createBitmap(width, height).applyCanvas { + drawColor(resources.getColor(R.color.background_color_png, null)) + drawBitmap(drawableSource, null, targetRect, null) + }.also { + if (drawableSource !== source) { + drawableSource.recycle() + } + } + } + @Suppress("TooGenericExceptionCaught") private fun readExifOrientation(storagePath: String): Int = try { ExifInterface(storagePath).getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) From 44fa66237d6ed7a5dfa22759fe73713b8fb25860 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 12 Oct 2026 12:43:46 +0200 Subject: [PATCH 2/2] fix build Signed-off-by: alperozturk96 --- .../job/MediaThumbnailGenerationJob.kt | 33 ++++------ .../datamodel/ThumbnailsCacheManager.java | 64 ++++--------------- .../com/owncloud/android/utils/BitmapUtils.kt | 4 -- 3 files changed, 26 insertions(+), 75 deletions(-) diff --git a/app/src/main/java/com/nextcloud/utils/thumbnail/job/MediaThumbnailGenerationJob.kt b/app/src/main/java/com/nextcloud/utils/thumbnail/job/MediaThumbnailGenerationJob.kt index d8b7d5a1fc91..e4b0e841f4c9 100644 --- a/app/src/main/java/com/nextcloud/utils/thumbnail/job/MediaThumbnailGenerationJob.kt +++ b/app/src/main/java/com/nextcloud/utils/thumbnail/job/MediaThumbnailGenerationJob.kt @@ -70,34 +70,29 @@ class MediaThumbnailGenerationJob( return ThumbnailsCacheManager.addThumbnailToCache(key, bitmap, file.path, px, px) } - private fun generateVideoThumbnail(file: File, key: String): Bitmap? = - extractVideoFrame(file)?.let { frame -> downscaleAndCache(frame, file, key) } - - private fun downscaleAndCache(frame: Bitmap, file: File, key: String): Bitmap { - val px = ThumbnailsCacheManager.getThumbnailDimension() - val width = frame.width - val height = frame.height - val longestSide = maxOf(width, height) - - if (longestSide <= px) { - return frame - } - - val scaled = BitmapUtils.scaleBitmap(frame, px.toFloat(), width, height, longestSide) - return ThumbnailsCacheManager.addThumbnailToCache(key, scaled, file.path, px, px) - } - @Suppress("TooGenericExceptionCaught") - private fun extractVideoFrame(file: File): Bitmap? { + private fun generateVideoThumbnail(file: File, key: String): Bitmap? { if (!file.exists()) { Log_OC.w(TAG, "Cannot extract thumbnail: file does not exist") return null } + val px = ThumbnailsCacheManager.getThumbnailDimension() val retriever = MediaMetadataRetriever() return try { retriever.setDataSource(file.absolutePath) - retriever.getFrameAtTime(ANY_FRAME_TIME_US) + val isVideoLargerThanThumbnail = BitmapUtils.getLargestVideoDimension(retriever) > px + val frame = if (isVideoLargerThanThumbnail) { + retriever.getScaledFrameAtTime(ANY_FRAME_TIME_US, MediaMetadataRetriever.OPTION_CLOSEST_SYNC, px, px) + } else { + retriever.getFrameAtTime(ANY_FRAME_TIME_US) + } + + when { + frame == null -> null + !isVideoLargerThanThumbnail && BitmapUtils.fitsInto(frame, px) -> frame + else -> ThumbnailsCacheManager.addThumbnailToCache(key, frame, file.path, px, px) + } } catch (_: Throwable) { Log_OC.w(TAG, "Failed to create bitmap from video ${file.absolutePath}") null diff --git a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java index 744e39d835a4..c192d25860f8 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java @@ -17,9 +17,7 @@ import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; -import android.graphics.Canvas; import android.graphics.Point; -import android.graphics.RectF; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.media.ThumbnailUtils; @@ -68,10 +66,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import androidx.core.content.ContextCompat; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import static com.nextcloud.utils.extensions.ThumbnailsCacheManagerExtensionsKt.getExifOrientation; @@ -196,7 +192,7 @@ private static Point getScreenDimension() { */ public static Bitmap addThumbnailToCache(String imageKey, Bitmap bitmap, String path, int pxW, int pxH) { - Bitmap thumbnail = ThumbnailUtils.extractThumbnail(bitmap, pxW, pxH); + Bitmap thumbnail = ThumbnailUtils.extractThumbnail(bitmap, pxW, pxH, ThumbnailUtils.OPTIONS_RECYCLE_INPUT); // Rotate image, obeying exif tag int orientation = getExifOrientation(path); @@ -235,13 +231,8 @@ public static void addBitmapToCache(String key, Bitmap bitmap) { return; } - // Check if the bitmap is already cached - Bitmap cachedBitmap = mThumbnailCache.getBitmap(key); - if (cachedBitmap == null) { - cachedBitmap = mThumbnailCache.getScaledBitmap(key, bitmap.getWidth(), bitmap.getHeight()); - } - - if (cachedBitmap != null && BitmapExtensionsKt.allocationKilobyte(cachedBitmap) <= THUMBNAIL_SIZE_IN_KB) { + Integer cachedSizeInKB = mThumbnailCache.getDecodedSizeInKB(key); + if (cachedSizeInKB != null && cachedSizeInKB <= THUMBNAIL_SIZE_IN_KB) { Log_OC.d(TAG, "Cached version is already within size limits, no need to scale: " + key); return; } @@ -622,7 +613,7 @@ private Bitmap doThumbnailFromOCFileInBackground() { if (bitmap != null) { if (PNG_MIMETYPE.equalsIgnoreCase(ocFile.getMimeType())) { - bitmap = handlePNG(bitmap, pxW, pxH); + bitmap = BitmapUtils.centerCropOnPngBackground(bitmap, pxW, pxH); } thumbnail = addThumbnailToCache(imageKey, bitmap, ocFile.getStoragePath(), pxW, pxH); @@ -641,7 +632,7 @@ private Bitmap doThumbnailFromOCFileInBackground() { } if (resizedImage != null) { - thumbnail = ThumbnailUtils.extractThumbnail(resizedImage, pxW, pxH); + thumbnail = ThumbnailUtils.extractThumbnail(resizedImage, pxW, pxH, ThumbnailUtils.OPTIONS_RECYCLE_INPUT); Log_OC.d(TAG, "Thumbnail generated from resized image cache for file: " + file.getFileName()); } else { Log_OC.d(TAG, "No resized image cache available for file: " + file.getFileName()); @@ -682,7 +673,7 @@ private Bitmap doThumbnailFromOCFileInBackground() { try (InputStream inputStream = getMethod.getResponseBodyAsStream()) { Bitmap bitmap = BitmapFactory.decodeStream(inputStream); if (bitmap != null) { - thumbnail = ThumbnailUtils.extractThumbnail(bitmap, pxW, pxH); + thumbnail = ThumbnailUtils.extractThumbnail(bitmap, pxW, pxH, ThumbnailUtils.OPTIONS_RECYCLE_INPUT); Log_OC.d(TAG, "Thumbnail downloaded and extracted for file: " + file.getFileName()); } else { Log_OC.w(TAG, "Downloaded thumbnail bitmap is null for file: " + file.getFileName()); @@ -694,7 +685,7 @@ private Bitmap doThumbnailFromOCFileInBackground() { } if (thumbnail != null && PNG_MIMETYPE.equalsIgnoreCase(file.getMimeType())) { - thumbnail = handlePNG(thumbnail, pxW, pxH); + thumbnail = BitmapUtils.centerCropOnPngBackground(thumbnail, pxW, pxH); Log_OC.d(TAG, "Handled PNG thumbnail for downloaded file: " + file.getFileName()); } } catch (Exception e) { @@ -830,37 +821,6 @@ private ResizedImageGenerationTask getBitmapWorkerTask() { } } - /** - * adapted from ... - */ - public static Bitmap handlePNG(Bitmap source, int newWidth, int newHeight) { - Bitmap softwareBitmap = source.copy(Bitmap.Config.ARGB_8888, false); - - int sourceWidth = source.getWidth(); - int sourceHeight = source.getHeight(); - - float xScale = (float) newWidth / sourceWidth; - float yScale = (float) newHeight / sourceHeight; - float scale = Math.max(xScale, yScale); - - float scaledWidth = scale * sourceWidth; - float scaledHeight = scale * sourceHeight; - - float left = (newWidth - scaledWidth) / 2; - float top = (newHeight - scaledHeight) / 2; - - RectF targetRect = new RectF(left, top, left + scaledWidth, top + scaledHeight); - - Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888); - - Canvas canvas = new Canvas(dest); - int color = ContextCompat.getColor(MainApp.getAppContext(),R.color.background_color_png); - canvas.drawColor(color); - canvas.drawBitmap(softwareBitmap, null, targetRect, null); - - return dest; - } - public static void generateResizedImage(OCFile file) { Point p = getScreenDimension(); int pxW = p.x; @@ -872,7 +832,7 @@ public static void generateResizedImage(OCFile file) { if (bitmap != null) { // Handle PNG if (PNG_MIMETYPE.equalsIgnoreCase(file.getMimeType())) { - bitmap = handlePNG(bitmap, pxW, pxH); + bitmap = BitmapUtils.centerCropOnPngBackground(bitmap, pxW, pxH); } addThumbnailToCache(imageKey, bitmap, file.getStoragePath(), pxW, pxH); @@ -910,7 +870,7 @@ public static void generateThumbnailFromOCFile(OCFile file, User user, Context c if (status == HttpStatus.SC_OK) { InputStream inputStream = getMethod.getResponseBodyAsStream(); Bitmap bitmap = BitmapFactory.decodeStream(inputStream); - thumbnail = ThumbnailUtils.extractThumbnail(bitmap, pxW, pxH); + thumbnail = ThumbnailUtils.extractThumbnail(bitmap, pxW, pxH, ThumbnailUtils.OPTIONS_RECYCLE_INPUT); } else { client.exhaustResponse(getMethod.getResponseBodyAsStream()); } @@ -919,7 +879,7 @@ public static void generateThumbnailFromOCFile(OCFile file, User user, Context c if (thumbnail != null) { // Handle PNG if (PNG_MIMETYPE.equalsIgnoreCase(file.getMimeType())) { - thumbnail = handlePNG(thumbnail, pxW, pxH); + thumbnail = BitmapUtils.centerCropOnPngBackground(thumbnail, pxW, pxH); } Log_OC.d(TAG, "add thumbnail to cache: " + file.getFileName()); @@ -1013,7 +973,7 @@ public static Bitmap doResizedImageInBackground(OCFile file, FileDataStorageMana Bitmap bitmap = BitmapUtils.decodeSampledBitmapFromFile(file.getStoragePath(), pxW, pxH); if (bitmap != null) { if (OCFileExtensionsKt.isPNG(file)) { - bitmap = handlePNG(bitmap, pxW, pxH); + bitmap = BitmapUtils.centerCropOnPngBackground(bitmap, pxW, pxH); } thumbnail = addThumbnailToCache(imageKey, bitmap, file.getStoragePath(), pxW, pxH); file.setUpdateThumbnailNeeded(false); @@ -1028,7 +988,7 @@ public static Bitmap doResizedImageInBackground(OCFile file, FileDataStorageMana pxH)); if (thumbnail != null && PNG_MIMETYPE.equalsIgnoreCase(file.getMimeType())) { - thumbnail = handlePNG(thumbnail, thumbnail.getWidth(), thumbnail.getHeight()); + thumbnail = BitmapUtils.centerCropOnPngBackground(thumbnail, thumbnail.getWidth(), thumbnail.getHeight()); } if (thumbnail != null) { diff --git a/app/src/main/java/com/owncloud/android/utils/BitmapUtils.kt b/app/src/main/java/com/owncloud/android/utils/BitmapUtils.kt index cbbadce1cb6f..66ca4c0fe8f1 100644 --- a/app/src/main/java/com/owncloud/android/utils/BitmapUtils.kt +++ b/app/src/main/java/com/owncloud/android/utils/BitmapUtils.kt @@ -129,10 +129,6 @@ object BitmapUtils { @JvmStatic fun fitsInto(bitmap: Bitmap, px: Int): Boolean = max(bitmap.width, bitmap.height) <= px - @JvmStatic - fun scaleToFit(bitmap: Bitmap, px: Int): Bitmap = - scaleBitmap(bitmap, px.toFloat(), bitmap.width, bitmap.height, max(bitmap.width, bitmap.height)) - @JvmStatic fun getLargestVideoDimension(retriever: MediaMetadataRetriever): Int { val width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)?.toIntOrNull() ?: 0