Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix memory leak in CompositeWithOpaqueBackground #4150

Merged
merged 1 commit into from
Dec 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -335,9 +335,13 @@ private void setAvatar(String url,
avatarRadius = avatarRadius36dp;
}

ImageLoadingHelper.loadAvatar(url, avatar, avatarRadius,
ImageLoadingHelper.loadAvatar(
url,
avatar,
avatarRadius,
statusDisplayOptions.animateAvatars(),
Collections.singletonList(new CompositeWithOpaqueBackground(avatar)));
Collections.singletonList(new CompositeWithOpaqueBackground(MaterialColors.getColor(avatar, android.R.attr.colorBackground)))
);
}

protected void setMetaData(@NonNull StatusViewData.Concrete statusViewData, @NonNull StatusDisplayOptions statusDisplayOptions, @NonNull StatusActionListener listener) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@ import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.Paint
import android.graphics.Shader
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.util.TypedValue
import android.view.View
import androidx.annotation.AttrRes
import androidx.core.content.ContextCompat
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import com.bumptech.glide.util.Util
Expand All @@ -48,25 +42,26 @@ import java.security.MessageDigest
* Fix this, by:
*
* - Creating a mask that matches the partially transparent areas of the image
* - Creating a new bitmap that, in the areas that match the mask, contains the same background
* drawable as the [ImageView].
* - Creating a new bitmap that, in the areas that match the mask, contains a background color
* - Composite the original image over the top
*
* So the partially transparent areas on the original image are composited over the original
* background, the fully transparent areas on the original image are left transparent.
*/
class CompositeWithOpaqueBackground(val view: View) : BitmapTransformation() {
class CompositeWithOpaqueBackground(val backgroundColor: Int) : BitmapTransformation() {

override fun equals(other: Any?): Boolean {
if (other is CompositeWithOpaqueBackground) {
return other.view == view
return other.backgroundColor == backgroundColor
}
return false
}

override fun hashCode() = Util.hashCode(ID.hashCode(), view.hashCode())
override fun hashCode() = Util.hashCode(ID.hashCode(), backgroundColor.hashCode())

override fun updateDiskCacheKey(messageDigest: MessageDigest) {
messageDigest.update(ID_BYTES)
messageDigest.update(ByteBuffer.allocate(4).putInt(view.hashCode()).array())
messageDigest.update(ByteBuffer.allocate(4).putInt(backgroundColor.hashCode()).array())
}

override fun transform(
Expand All @@ -78,20 +73,9 @@ class CompositeWithOpaqueBackground(val view: View) : BitmapTransformation() {
// If the input bitmap has no alpha channel then there's nothing to do
if (!toTransform.hasAlpha()) return toTransform

// Get the background drawable for this view, falling back to the given attribute
val backgroundDrawable = view.getFirstNonNullBackgroundOrAttr(android.R.attr.colorBackground)
backgroundDrawable ?: return toTransform

// Convert the background to a bitmap.
val backgroundBitmap = pool.get(outWidth, outHeight, Bitmap.Config.ARGB_8888)
when (backgroundDrawable) {
is ColorDrawable -> backgroundBitmap.eraseColor(backgroundDrawable.color)
else -> {
val backgroundCanvas = Canvas(backgroundBitmap)
backgroundDrawable.setBounds(0, 0, outWidth, outHeight)
backgroundDrawable.draw(backgroundCanvas)
}
}
backgroundBitmap.eraseColor(backgroundColor)

// Convert the alphaBitmap (where the alpha channel has 8bpp) to a mask of 1bpp
// TODO: toTransform.extractAlpha(paint, ...) could be used here, but I can't find any
Expand Down Expand Up @@ -143,24 +127,5 @@ class CompositeWithOpaqueBackground(val view: View) : BitmapTransformation() {
)
isAntiAlias = false
}

/**
* @param attr attribute reference for the default drawable if no background is set on
* this view or any of its ancestors.
* @return The first non-null background drawable from this view, or its ancestors,
* falling back to the attribute resource given by `attr` if none of the views have a
* background.
*/
fun View.getFirstNonNullBackgroundOrAttr(@AttrRes attr: Int): Drawable? =
background ?: (parent as? View)?.getFirstNonNullBackgroundOrAttr(attr) ?: run {
val v = TypedValue()
context.theme.resolveAttribute(attr, v, true)
// TODO: On API 29 can use v.isColorType here
if (v.type >= TypedValue.TYPE_FIRST_COLOR_INT && v.type <= TypedValue.TYPE_LAST_COLOR_INT) {
ColorDrawable(v.data)
} else {
ContextCompat.getDrawable(context, v.resourceId)
}
}
}
}