From 9f44975b4a23ec6bf3398e8dc24862ac49acbe3a Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Mon, 21 Feb 2022 16:54:51 +0000 Subject: [PATCH] tranforming images for adaptive shortcuts within glides transformations - avoid creating new bitmaps each time the room list changes --- .../home/AdaptiveIconTransformation.kt | 54 +++++++++++++++++++ .../app/features/home/AvatarRenderer.kt | 45 +++++++++++----- .../app/features/home/ShortcutCreator.kt | 14 ++--- 3 files changed, 92 insertions(+), 21 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/AdaptiveIconTransformation.kt diff --git a/vector/src/main/java/im/vector/app/features/home/AdaptiveIconTransformation.kt b/vector/src/main/java/im/vector/app/features/home/AdaptiveIconTransformation.kt new file mode 100644 index 0000000000..9efd842e58 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/AdaptiveIconTransformation.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home + +import android.graphics.Bitmap +import android.graphics.Canvas +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool +import com.bumptech.glide.load.resource.bitmap.BitmapTransformation +import com.bumptech.glide.util.Util +import java.nio.ByteBuffer +import java.security.MessageDigest + +private const val ADAPTIVE_TRANSFORMATION_ID = "adaptive-icon-transform" +private val ID_BYTES = ADAPTIVE_TRANSFORMATION_ID.toByteArray() + +class AdaptiveIconTransformation(private val adaptiveIconSize: Int, private val adaptiveIconOuterSides: Float) : BitmapTransformation() { + + override fun updateDiskCacheKey(messageDigest: MessageDigest) { + messageDigest.update(ID_BYTES) + messageDigest.update(ByteBuffer.allocate(4).putInt(adaptiveIconSize).putFloat(adaptiveIconOuterSides).array()) + } + + override fun transform(pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap { + val insetBmp = Bitmap.createBitmap(adaptiveIconSize, adaptiveIconSize, Bitmap.Config.ARGB_8888) + val canvas = Canvas(insetBmp) + canvas.drawBitmap(toTransform, adaptiveIconOuterSides, adaptiveIconOuterSides, null) + canvas.setBitmap(null) + return insetBmp + } + + override fun equals(other: Any?): Boolean { + return if (other is AdaptiveIconTransformation) { + other.adaptiveIconSize == adaptiveIconSize && other.adaptiveIconOuterSides == adaptiveIconOuterSides + } else { + false + } + } + + override fun hashCode() = Util.hashCode(ADAPTIVE_TRANSFORMATION_ID.hashCode(), Util.hashCode(adaptiveIconSize, Util.hashCode(adaptiveIconOuterSides))) +} diff --git a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt index 2ee3233637..d8a8409f5a 100644 --- a/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt +++ b/vector/src/main/java/im/vector/app/features/home/AvatarRenderer.kt @@ -26,6 +26,7 @@ import androidx.core.graphics.drawable.toBitmap import com.amulyakhare.textdrawable.TextDrawable import com.bumptech.glide.load.MultiTransformation import com.bumptech.glide.load.Transformation +import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.resource.bitmap.CenterCrop import com.bumptech.glide.load.resource.bitmap.CircleCrop import com.bumptech.glide.load.resource.bitmap.RoundedCorners @@ -157,25 +158,41 @@ class AvatarRenderer @Inject constructor(private val activeSessionHolder: Active fun shortcutDrawable(glideRequests: GlideRequests, matrixItem: MatrixItem, iconSize: Int): Bitmap { return glideRequests .asBitmap() - .let { - val resolvedUrl = resolvedUrl(matrixItem.avatarUrl) - if (resolvedUrl != null) { - it.load(resolvedUrl) - } else { - val avatarColor = matrixItemColorProvider.getColor(matrixItem) - it.load(TextDrawable.builder() - .beginConfig() - .bold() - .endConfig() - .buildRect(matrixItem.firstLetterOfDisplayName(), avatarColor) - .toBitmap(width = iconSize, height = iconSize)) - } - } + .avatarOrText(matrixItem, iconSize) .apply(RequestOptions.centerCropTransform()) .submit(iconSize, iconSize) .get() } + @AnyThread + @Throws + fun adaptiveShortcutDrawable(glideRequests: GlideRequests, matrixItem: MatrixItem, iconSize: Int, adaptiveIconSize: Int, adaptiveIconOuterSides: Float): Bitmap { + return glideRequests + .asBitmap() + .avatarOrText(matrixItem, iconSize) + .transform(CenterCrop(), AdaptiveIconTransformation(adaptiveIconSize, adaptiveIconOuterSides)) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .submit(adaptiveIconSize, adaptiveIconSize) + .get() + } + + private fun GlideRequest.avatarOrText(matrixItem: MatrixItem, iconSize: Int): GlideRequest { + return this.let { + val resolvedUrl = resolvedUrl(matrixItem.avatarUrl) + if (resolvedUrl != null) { + it.load(resolvedUrl) + } else { + val avatarColor = matrixItemColorProvider.getColor(matrixItem) + it.load(TextDrawable.builder() + .beginConfig() + .bold() + .endConfig() + .buildRect(matrixItem.firstLetterOfDisplayName(), avatarColor) + .toBitmap(width = iconSize, height = iconSize)) + } + } + } + @UiThread fun renderBlur(matrixItem: MatrixItem, imageView: ImageView, diff --git a/vector/src/main/java/im/vector/app/features/home/ShortcutCreator.kt b/vector/src/main/java/im/vector/app/features/home/ShortcutCreator.kt index ee7edc021d..082d318cc7 100644 --- a/vector/src/main/java/im/vector/app/features/home/ShortcutCreator.kt +++ b/vector/src/main/java/im/vector/app/features/home/ShortcutCreator.kt @@ -19,7 +19,6 @@ package im.vector.app.features.home import android.content.Context import android.content.pm.ShortcutInfo import android.graphics.Bitmap -import android.graphics.Canvas import android.os.Build import androidx.annotation.WorkerThread import androidx.core.content.pm.ShortcutInfoCompat @@ -61,7 +60,12 @@ class ShortcutCreator @Inject constructor( fun create(roomSummary: RoomSummary, rank: Int = 1): ShortcutInfoCompat { val intent = RoomDetailActivity.shortcutIntent(context, roomSummary.roomId) val bitmap = try { - avatarRenderer.shortcutDrawable(GlideApp.with(context), roomSummary.toMatrixItem(), iconSize) + val glideRequests = GlideApp.with(context) + val matrixItem = roomSummary.toMatrixItem() + when (useAdaptiveIcon) { + true -> avatarRenderer.adaptiveShortcutDrawable(glideRequests, matrixItem, iconSize, adaptiveIconSize, adaptiveIconOuterSides.toFloat()) + false -> avatarRenderer.shortcutDrawable(glideRequests, matrixItem, iconSize) + } } catch (failure: Throwable) { null } @@ -83,11 +87,7 @@ class ShortcutCreator @Inject constructor( private fun Bitmap.toProfileImageIcon(): IconCompat { return if (useAdaptiveIcon) { - val insetBmp = Bitmap.createBitmap(adaptiveIconSize, adaptiveIconSize, Bitmap.Config.ARGB_8888) - val canvas = Canvas(insetBmp) - canvas.drawBitmap(this, adaptiveIconOuterSides.toFloat(), adaptiveIconOuterSides.toFloat(), null) - - IconCompat.createWithAdaptiveBitmap(insetBmp) + IconCompat.createWithAdaptiveBitmap(this) } else { IconCompat.createWithBitmap(this) }