Skip to content

Commit

Permalink
Fix incorrect scaling when converting a drawable to a bitmap. (#1084)
Browse files Browse the repository at this point in the history
* Fix incorrect scaling when converting a drawable to a bitmap.

* Docs.
  • Loading branch information
colinrtwhite authored Jan 14, 2022
1 parent b662485 commit 161813f
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 14 deletions.
36 changes: 22 additions & 14 deletions coil-base/src/main/java/coil/util/DrawableUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ internal object DrawableUtils {
// Fast path: return the underlying bitmap.
if (drawable is BitmapDrawable) {
val bitmap = drawable.bitmap
if (isConfigValid(bitmap, config) && isSizeValid(allowInexactSize, size, bitmap, scale)) {
if (isConfigValid(bitmap, config) && isSizeValid(allowInexactSize, bitmap, size, scale)) {
return bitmap
}
}
Expand All @@ -61,28 +61,36 @@ internal object DrawableUtils {

val bitmap = createBitmap(bitmapWidth, bitmapHeight, config.toSoftware())
safeDrawable.apply {
val (oldLeft, oldTop, oldRight, oldBottom) = safeDrawable.bounds
setBounds(0, 0, bitmapHeight, bitmapHeight)
val (oldLeft, oldTop, oldRight, oldBottom) = bounds
setBounds(0, 0, bitmapWidth, bitmapHeight)
draw(Canvas(bitmap))
setBounds(oldLeft, oldTop, oldRight, oldBottom)
}

return bitmap
}

private fun isConfigValid(bitmap: Bitmap, config: Bitmap.Config): Boolean {
return bitmap.config == config.toSoftware()
}

private fun isSizeValid(allowInexactSize: Boolean, size: Size, bitmap: Bitmap, scale: Scale): Boolean {
if (allowInexactSize) return true
val multiplier = DecodeUtils.computeSizeMultiplier(
srcWidth = bitmap.width,
srcHeight = bitmap.height,
dstWidth = size.width.pxOrElse { bitmap.width },
dstHeight = size.height.pxOrElse { bitmap.height },
scale = scale
)
return multiplier == 1.0
private fun isSizeValid(
allowInexactSize: Boolean,
bitmap: Bitmap,
size: Size,
scale: Scale
): Boolean {
if (allowInexactSize) {
// Any size is valid.
return true
} else {
// The bitmap must match the scaled dimensions of the destination exactly.
return DecodeUtils.computeSizeMultiplier(
srcWidth = bitmap.width,
srcHeight = bitmap.height,
dstWidth = size.width.pxOrElse { bitmap.width },
dstHeight = size.height.pxOrElse { bitmap.height },
scale = scale
) == 1.0
}
}
}
12 changes: 12 additions & 0 deletions coil-base/src/main/res/drawable/ic_100tb.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="512dp"
android:height="190dp"
android:viewportWidth="512"
android:viewportHeight="190">
<path
android:pathData="M49.443,2.097L0,53.649L24.447,75.822L42.179,62.534L42.179,186.983L94.903,186.983L94.903,2.097L49.443,2.097ZM463.231,129.49C438.591,145.414 411.94,147.587 384.482,138.685C370.602,134.187 359.7,124.419 349.226,114.571C333.198,99.5 320.283,81.695 306.51,64.677C292.155,46.939 277.028,30.172 257.481,17.913C241.288,7.758 224.061,1.336 204.835,1.11C174.68,0.755 149.491,12.086 129.905,35.011C124.846,40.935 120.695,47.39 117.626,54.371C115.642,58.882 110.895,70.361 110.027,76.67C110.754,75.32 114.536,65.86 115.253,64.504C117.126,60.265 119.002,57.924 121.227,54.96C125.181,49.698 129.973,45.209 136.124,41.971C162.474,28.104 198.608,33.089 220.83,53.322C240.369,71.112 256.579,91.934 274.418,111.272C291.087,129.34 306.835,148.33 325.374,164.561C343.36,180.309 364.044,189.459 388.583,189.209C431.297,188.776 468.379,160.49 479.44,119.122C480.691,114.44 482.139,108.624 481.602,102.226C477.568,113.519 472.234,123.671 463.231,129.49ZM230.832,123.95C220.835,131.299 210.57,138.194 197.059,134.946C180.18,130.887 168.962,120.621 165.312,103.645C161.903,87.789 166.688,74.06 179.414,63.52C185.143,58.775 191.495,56.011 198.377,54.811C201.922,54.191 206.778,54.751 210.592,54.903C197.92,45.524 182.435,41.757 167.119,41.974C140.957,42.342 124.824,53.03 115.107,77.338C111.194,87.128 107.898,97.336 109.076,108.049C112.808,133.117 124.418,154.059 144.736,169.347C173.249,190.799 204.613,195.399 237.901,182.056C258.957,173.617 275.457,155.683 290.767,139.739L255.695,99.845C248.901,107.513 239.121,117.856 230.832,123.95ZM438.91,38.463C438.91,31.373 440.622,24.687 443.638,18.778C422.195,3.253 394.702,-2.948 368.608,2.998C342.591,8.929 322.141,23.992 303.425,42.113C301.474,44.002 299.246,46.856 299.246,46.856L334.975,90.237L338.425,86.749C347.466,76.841 356.84,67.246 368.484,60.271C373.246,57.419 378.182,54.744 383.829,54.565C400.671,54.028 413.604,61.24 421.578,75.914C429.486,90.468 428.089,105.027 418.814,118.761C413.779,126.218 406.445,130.685 398.306,134.371C416.634,137.409 434.398,136.435 451.479,128.527C471.655,119.187 481.506,98.259 481.039,81.808C457.661,81.146 438.91,62.001 438.91,38.463Z"
android:fillColor="#919FA1"/>
<path
android:pathData="M479.529,20.279L479.529,28.591L471.161,28.591L471.161,57.814L462.367,57.814L462.367,28.591L453.81,28.591L453.81,20.279L479.529,20.279ZM498.002,49.484C499.425,49.484 500.534,49.055 501.323,48.25C502.115,47.445 502.537,46.374 502.537,45.087C502.537,43.746 502.115,43.674 501.271,42.923C500.427,42.171 499.32,41.796 498.002,41.796L492.729,41.796L492.729,49.484L498.002,49.484ZM497.263,34.986L497.263,34.879C498.37,34.879 499.32,34.557 500.058,33.913C500.797,33.271 501.165,32.412 501.165,31.393C501.165,30.321 500.797,29.463 500.058,28.819C499.32,28.177 498.37,27.855 497.263,27.855L492.729,27.855L492.729,34.986L497.263,34.986ZM498.582,20.294L498.74,20.133C500.374,20.133 501.904,20.401 503.275,20.884C504.647,21.367 505.807,22.063 506.756,22.975C507.705,23.886 508.442,24.959 508.97,26.193C509.498,27.426 509.762,28.713 509.762,30.107C509.762,31.072 509.989,32.389 509.673,33.194C509.357,33.998 508.987,34.696 508.513,35.338C508.038,35.982 507.511,36.519 506.93,36.948C506.35,37.376 505.823,37.644 505.349,37.805L505.349,37.859C506.192,38.073 506.651,38.043 507.442,38.578C508.232,39.116 508.918,39.759 509.498,40.563C510.078,41.368 510.553,42.279 510.922,43.298C511.29,44.316 511.502,45.336 511.502,46.407C511.502,48.07 511.237,49.571 510.711,50.966C510.183,52.36 509.393,53.54 508.337,54.558C507.282,55.577 505.965,56.382 504.329,56.971C502.695,57.561 500.797,57.829 498.582,57.829L484.08,57.829L484.08,20.294L498.582,20.294Z"
android:fillColor="#3390DA"/>
</vector>
33 changes: 33 additions & 0 deletions coil-base/src/test/java/coil/util/DrawableUtilsTest.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
package coil.util

import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.VectorDrawable
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.graphics.applyCanvas
import androidx.core.graphics.component1
import androidx.core.graphics.component2
import androidx.core.graphics.component3
import androidx.core.graphics.component4
import androidx.test.core.app.ApplicationProvider
import coil.base.R
import coil.size
import coil.size.Scale
import coil.size.Size
Expand Down Expand Up @@ -32,6 +41,30 @@ class DrawableUtilsTest {
assertEquals(size, output.size)
}

/** Regression test: https://github.com/coil-kt/coil/issues/1081 */
@Test
fun `rectangular vector is converted correctly`() {
val size = Size(200, 200)
val context: Context = ApplicationProvider.getApplicationContext()
val vector = AppCompatResources.getDrawable(context, R.drawable.ic_100tb)!!

val expected = createBitmap(200, 74).applyCanvas {
val (oldLeft, oldTop, oldRight, oldBottom) = vector.bounds
vector.setBounds(0, 0, width, height)
vector.draw(this)
vector.setBounds(oldLeft, oldTop, oldRight, oldBottom)
}
val actual = DrawableUtils.convertToBitmap(
drawable = vector,
config = Bitmap.Config.HARDWARE,
size = size,
scale = Scale.FIT,
allowInexactSize = true
)

actual.assertIsSimilarTo(expected)
}

@Test
fun `unimplemented intrinsic size does not crash`() {
val size = Size(200, 200)
Expand Down

0 comments on commit 161813f

Please sign in to comment.