From 34e8f3299455b28725fb611e4fb95b05faf6fb31 Mon Sep 17 00:00:00 2001 From: Colin White Date: Tue, 26 Apr 2022 01:45:40 -0400 Subject: [PATCH] Convert Dimension.Original to be Dimension.Undefined. (#1250) * Initial commit. * Refactor. * Tweak test. * Docs. * Fix. * Add test. * Tweak naming. * Docs. * Docs. * Docs. * Docs. --- coil-base/api/coil-base.api | 10 ++++---- .../coil/decode/BitmapFactoryDecoderTest.kt | 4 +-- .../java/coil/decode/BitmapFactoryDecoder.kt | 8 +++--- .../main/java/coil/request/RequestService.kt | 2 +- .../src/main/java/coil/size/Dimension.kt | 12 ++++----- coil-base/src/main/java/coil/size/Size.kt | 15 +++++++++-- .../main/java/coil/size/ViewSizeResolver.kt | 4 +-- .../transform/RoundedCornersTransformation.kt | 7 +++--- .../src/main/java/coil/util/DrawableUtils.kt | 9 +++---- coil-base/src/main/java/coil/util/Utils.kt | 19 ++++++++++++++ .../coil/memory/MemoryCacheServiceTest.kt | 12 ++++----- .../java/coil/size/ViewSizeResolverTest.kt | 2 +- .../java/coil/compose/AsyncImageTest.kt | 3 +-- .../src/main/java/coil/compose/AsyncImage.kt | 4 +-- .../java/coil/compose/AsyncImagePainter.kt | 4 +-- .../java/coil/decode/ImageDecoderDecoder.kt | 7 +++--- coil-gif/src/main/java/coil/util/Utils.kt | 20 +++++++++++++++ .../java/coil/decode/SvgDecoderTest.kt | 25 +++++++++++++++++++ .../src/main/java/coil/decode/SvgDecoder.kt | 21 +++++++++------- coil-svg/src/main/java/coil/util/Utils.kt | 13 ++++++++++ .../java/coil/decode/VideoFrameDecoder.kt | 22 +++++++++------- coil-video/src/main/java/coil/util/Utils.kt | 20 +++++++++++++++ 22 files changed, 179 insertions(+), 64 deletions(-) diff --git a/coil-base/api/coil-base.api b/coil-base/api/coil-base.api index 58f709e618..c36adaa074 100644 --- a/coil-base/api/coil-base.api +++ b/coil-base/api/coil-base.api @@ -764,11 +764,6 @@ public final class coil/size/-Sizes { public abstract class coil/size/Dimension { } -public final class coil/size/Dimension$Original : coil/size/Dimension { - public static final field INSTANCE Lcoil/size/Dimension$Original; - public fun toString ()Ljava/lang/String; -} - public final class coil/size/Dimension$Pixels : coil/size/Dimension { public final field px I public fun (I)V @@ -777,6 +772,11 @@ public final class coil/size/Dimension$Pixels : coil/size/Dimension { public fun toString ()Ljava/lang/String; } +public final class coil/size/Dimension$Undefined : coil/size/Dimension { + public static final field INSTANCE Lcoil/size/Dimension$Undefined; + public fun toString ()Ljava/lang/String; +} + public final class coil/size/Precision : java/lang/Enum { public static final field AUTOMATIC Lcoil/size/Precision; public static final field EXACT Lcoil/size/Precision; diff --git a/coil-base/src/androidTest/java/coil/decode/BitmapFactoryDecoderTest.kt b/coil-base/src/androidTest/java/coil/decode/BitmapFactoryDecoderTest.kt index d109aa6260..f11a6c2da5 100644 --- a/coil-base/src/androidTest/java/coil/decode/BitmapFactoryDecoderTest.kt +++ b/coil-base/src/androidTest/java/coil/decode/BitmapFactoryDecoderTest.kt @@ -57,7 +57,7 @@ class BitmapFactoryDecoderTest { fun undefinedWidth() = runTest { val result = decode( assetName = "normal.jpg", - size = Size(Dimension.Original, 100), + size = Size(Dimension.Undefined, 100), scale = Scale.FIT ) @@ -71,7 +71,7 @@ class BitmapFactoryDecoderTest { fun undefinedHeight() = runTest { val result = decode( assetName = "normal.jpg", - size = Size(100, Dimension.Original), + size = Size(100, Dimension.Undefined), scale = Scale.FIT ) diff --git a/coil-base/src/main/java/coil/decode/BitmapFactoryDecoder.kt b/coil-base/src/main/java/coil/decode/BitmapFactoryDecoder.kt index 18ea8e7335..c1d43336ea 100644 --- a/coil-base/src/main/java/coil/decode/BitmapFactoryDecoder.kt +++ b/coil-base/src/main/java/coil/decode/BitmapFactoryDecoder.kt @@ -7,10 +7,11 @@ import coil.ImageLoader import coil.fetch.SourceResult import coil.request.Options import coil.size.isOriginal -import coil.size.pxOrElse import coil.util.MIME_TYPE_JPEG +import coil.util.heightPx import coil.util.toDrawable import coil.util.toSoftware +import coil.util.widthPx import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit @@ -126,9 +127,8 @@ class BitmapFactoryDecoder @JvmOverloads constructor( val srcWidth = if (exifData.isSwapped) outHeight else outWidth val srcHeight = if (exifData.isSwapped) outWidth else outHeight - val dstSize = options.size - val dstWidth = dstSize.width.pxOrElse { srcWidth } - val dstHeight = dstSize.height.pxOrElse { srcHeight } + val dstWidth = options.size.widthPx(options.scale) { srcWidth } + val dstHeight = options.size.heightPx(options.scale) { srcHeight } // Calculate the image's sample size. inSampleSize = DecodeUtils.calculateInSampleSize( diff --git a/coil-base/src/main/java/coil/request/RequestService.kt b/coil-base/src/main/java/coil/request/RequestService.kt index 56003837b6..763c6b8776 100644 --- a/coil-base/src/main/java/coil/request/RequestService.kt +++ b/coil-base/src/main/java/coil/request/RequestService.kt @@ -74,7 +74,7 @@ internal class RequestService( config != Bitmap.Config.ALPHA_8 // Use `Scale.FIT` if either dimension is undefined. - val scale = if (size.width == Dimension.Original || size.height == Dimension.Original) { + val scale = if (size.width == Dimension.Undefined || size.height == Dimension.Undefined) { Scale.FIT } else { request.scale diff --git a/coil-base/src/main/java/coil/size/Dimension.kt b/coil-base/src/main/java/coil/size/Dimension.kt index 16a0af7244..95bd039d05 100644 --- a/coil-base/src/main/java/coil/size/Dimension.kt +++ b/coil-base/src/main/java/coil/size/Dimension.kt @@ -31,18 +31,18 @@ sealed class Dimension { } /** - * Represents the original pixel value of the source image. + * Represents an undefined pixel value. * - * i.e. if the image's original dimensions are 400x600 and this is used as the width, this - * should be treated as 400 pixels. + * E.g. given `Size(400, Dimension.Undefined)`, the image should be loaded to fit/fill a width + * of 400 pixels irrespective of the image's height. * * This value is typically used in cases where a dimension is unbounded (e.g. [WRAP_CONTENT], * `Constraints.Infinity`). * - * NOTE: If at least one dimension is [Original], [Options.scale] is always [Scale.FIT]. + * NOTE: If either dimension is [Undefined], [Options.scale] is always [Scale.FIT]. */ - object Original : Dimension() { - override fun toString() = "Dimension.Original" + object Undefined : Dimension() { + override fun toString() = "Dimension.Undefined" } } diff --git a/coil-base/src/main/java/coil/size/Size.kt b/coil-base/src/main/java/coil/size/Size.kt index beb6edeab0..7180e7145e 100644 --- a/coil-base/src/main/java/coil/size/Size.kt +++ b/coil-base/src/main/java/coil/size/Size.kt @@ -8,6 +8,17 @@ import coil.request.ImageRequest /** * Represents the target size of an image request. * + * Each [Size] is composed of two [Dimension]s, [width] and [height]. Each dimension determines + * by how much the source image should be scaled. A [Dimension] can either be a fixed pixel + * value or [Dimension.Undefined]. Examples: + * + * - Given `Size(400, 600)`, the image should be loaded to fit/fill a width of 400 pixels and a + * height of 600 pixels. + * - Given `Size(400, Dimension.Undefined)`, the image should be loaded to fit/fill a width of 400 + * pixels. + * - Given `Size(Dimension.Undefined, Dimension.Undefined)`, the image should not be scaled to + * fit/fill either width or height. i.e. it will be loaded at its original width/height. + * * @see ImageRequest.Builder.size * @see SizeResolver.size */ @@ -18,9 +29,9 @@ data class Size( companion object { /** - * A [Size] whose width and height are equal to the original dimensions of the source image. + * A [Size] whose width and height are not scaled. */ - @JvmField val ORIGINAL = Size(Dimension.Original, Dimension.Original) + @JvmField val ORIGINAL = Size(Dimension.Undefined, Dimension.Undefined) } } diff --git a/coil-base/src/main/java/coil/size/ViewSizeResolver.kt b/coil-base/src/main/java/coil/size/ViewSizeResolver.kt index 91d8add907..861a7a9de6 100644 --- a/coil-base/src/main/java/coil/size/ViewSizeResolver.kt +++ b/coil-base/src/main/java/coil/size/ViewSizeResolver.kt @@ -85,9 +85,9 @@ interface ViewSizeResolver : SizeResolver { ) private fun getDimension(paramSize: Int, viewSize: Int, paddingSize: Int): Dimension? { - // If the dimension is set to WRAP_CONTENT, use `Dimension.Original`. + // If the dimension is set to WRAP_CONTENT, then the dimension is undefined. if (paramSize == ViewGroup.LayoutParams.WRAP_CONTENT) { - return Dimension.Original + return Dimension.Undefined } // Assume the dimension will match the value in the view's layout params. diff --git a/coil-base/src/main/java/coil/transform/RoundedCornersTransformation.kt b/coil-base/src/main/java/coil/transform/RoundedCornersTransformation.kt index 6edebb320a..dfb05c3d88 100644 --- a/coil-base/src/main/java/coil/transform/RoundedCornersTransformation.kt +++ b/coil-base/src/main/java/coil/transform/RoundedCornersTransformation.kt @@ -15,8 +15,9 @@ import androidx.core.graphics.createBitmap import coil.decode.DecodeUtils import coil.size.Scale import coil.size.Size -import coil.size.pxOrElse +import coil.util.heightPx import coil.util.safeConfig +import coil.util.widthPx import kotlin.math.roundToInt /** @@ -51,8 +52,8 @@ class RoundedCornersTransformation( override suspend fun transform(input: Bitmap, size: Size): Bitmap { val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG) - val dstWidth = size.width.pxOrElse { input.width } - val dstHeight = size.height.pxOrElse { input.height } + val dstWidth = size.widthPx(Scale.FILL) { input.width } + val dstHeight = size.heightPx(Scale.FILL) { input.height } val multiplier = DecodeUtils.computeSizeMultiplier( srcWidth = input.width, srcHeight = input.height, diff --git a/coil-base/src/main/java/coil/util/DrawableUtils.kt b/coil-base/src/main/java/coil/util/DrawableUtils.kt index 8ecd7ab08c..df3864c245 100644 --- a/coil-base/src/main/java/coil/util/DrawableUtils.kt +++ b/coil-base/src/main/java/coil/util/DrawableUtils.kt @@ -13,7 +13,6 @@ import androidx.core.graphics.createBitmap import coil.decode.DecodeUtils import coil.size.Scale import coil.size.Size -import coil.size.pxOrElse import kotlin.math.roundToInt internal object DrawableUtils { @@ -52,8 +51,8 @@ internal object DrawableUtils { val multiplier = DecodeUtils.computeSizeMultiplier( srcWidth = srcWidth, srcHeight = srcHeight, - dstWidth = size.width.pxOrElse { srcWidth }, - dstHeight = size.height.pxOrElse { srcHeight }, + dstWidth = size.widthPx(scale) { srcWidth }, + dstHeight = size.heightPx(scale) { srcHeight }, scale = scale ) val bitmapWidth = (multiplier * srcWidth).roundToInt() @@ -87,8 +86,8 @@ internal object DrawableUtils { return DecodeUtils.computeSizeMultiplier( srcWidth = bitmap.width, srcHeight = bitmap.height, - dstWidth = size.width.pxOrElse { bitmap.width }, - dstHeight = size.height.pxOrElse { bitmap.height }, + dstWidth = size.widthPx(scale) { bitmap.width }, + dstHeight = size.heightPx(scale) { bitmap.height }, scale = scale ) == 1.0 } diff --git a/coil-base/src/main/java/coil/util/Utils.kt b/coil-base/src/main/java/coil/util/Utils.kt index 1ebfc6af0b..166862f7d6 100644 --- a/coil-base/src/main/java/coil/util/Utils.kt +++ b/coil-base/src/main/java/coil/util/Utils.kt @@ -38,7 +38,11 @@ import coil.memory.MemoryCache import coil.request.Parameters import coil.request.Tags import coil.request.ViewTargetRequestManager +import coil.size.Dimension import coil.size.Scale +import coil.size.Size +import coil.size.isOriginal +import coil.size.pxOrElse import coil.transform.Transformation import kotlinx.coroutines.Deferred import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -205,6 +209,21 @@ internal val Interceptor.Chain.eventListener: EventListener internal fun Int.isMinOrMax() = this == Int.MIN_VALUE || this == Int.MAX_VALUE +internal inline fun Size.widthPx(scale: Scale, original: () -> Int): Int { + return if (isOriginal) original() else width.toPx(scale) +} + +internal inline fun Size.heightPx(scale: Scale, original: () -> Int): Int { + return if (isOriginal) original() else height.toPx(scale) +} + +internal fun Dimension.toPx(scale: Scale) = pxOrElse { + when (scale) { + Scale.FILL -> Int.MIN_VALUE + Scale.FIT -> Int.MAX_VALUE + } +} + internal fun unsupported(): Nothing = error("Unsupported") internal const val ASSET_FILE_PATH_ROOT = "android_asset" diff --git a/coil-base/src/test/java/coil/memory/MemoryCacheServiceTest.kt b/coil-base/src/test/java/coil/memory/MemoryCacheServiceTest.kt index a171bcd4fa..26c9f737ee 100644 --- a/coil-base/src/test/java/coil/memory/MemoryCacheServiceTest.kt +++ b/coil-base/src/test/java/coil/memory/MemoryCacheServiceTest.kt @@ -442,37 +442,37 @@ class MemoryCacheServiceTest { request = request, cached = createBitmap(width = 400, height = 200), isSampled = true, - size = Size(400, Dimension.Original) + size = Size(400, Dimension.Undefined) )) assertTrue(service.isCacheValueValid( request = request, cached = createBitmap(width = 400, height = 200), isSampled = true, - size = Size(Dimension.Original, 200) + size = Size(Dimension.Undefined, 200) )) assertFalse(service.isCacheValueValid( request = request, cached = createBitmap(width = 400, height = 200), isSampled = true, - size = Size(450, Dimension.Original) + size = Size(450, Dimension.Undefined) )) assertFalse(service.isCacheValueValid( request = request, cached = createBitmap(width = 400, height = 200), isSampled = true, - size = Size(Dimension.Original, 250) + size = Size(Dimension.Undefined, 250) )) assertTrue(service.isCacheValueValid( request = request, cached = createBitmap(width = 400, height = 200), isSampled = false, - size = Size(450, Dimension.Original) + size = Size(450, Dimension.Undefined) )) assertTrue(service.isCacheValueValid( request = request, cached = createBitmap(width = 400, height = 200), isSampled = false, - size = Size(Dimension.Original, 250) + size = Size(Dimension.Undefined, 250) )) } diff --git a/coil-base/src/test/java/coil/size/ViewSizeResolverTest.kt b/coil-base/src/test/java/coil/size/ViewSizeResolverTest.kt index 86083937b3..cf5f3a72e2 100644 --- a/coil-base/src/test/java/coil/size/ViewSizeResolverTest.kt +++ b/coil-base/src/test/java/coil/size/ViewSizeResolverTest.kt @@ -96,6 +96,6 @@ class ViewSizeResolverTest { view.viewTreeObserver.dispatchOnPreDraw() val size = deferred.await() - assertEquals(Size(Dimension.Original, 100), size) + assertEquals(Size(Dimension.Undefined, 100), size) } } diff --git a/coil-compose-base/src/androidTest/java/coil/compose/AsyncImageTest.kt b/coil-compose-base/src/androidTest/java/coil/compose/AsyncImageTest.kt index 9be729d6e8..207ca73bf1 100644 --- a/coil-compose-base/src/androidTest/java/coil/compose/AsyncImageTest.kt +++ b/coil-compose-base/src/androidTest/java/coil/compose/AsyncImageTest.kt @@ -673,8 +673,7 @@ class AsyncImageTest { waitForRequestComplete() - // Equal to the source dimensions of 'sample.jpg'. - assertSampleLoadedBitmapSize(1024.0, 1326.0) + assertLoadedBitmapSize(SampleWidth, SampleHeight) } private fun waitForRequestComplete(finishedRequests: Int = 1) { diff --git a/coil-compose-base/src/main/java/coil/compose/AsyncImage.kt b/coil-compose-base/src/main/java/coil/compose/AsyncImage.kt index ccbb47aad3..3e156f7015 100644 --- a/coil-compose-base/src/main/java/coil/compose/AsyncImage.kt +++ b/coil-compose-base/src/main/java/coil/compose/AsyncImage.kt @@ -234,7 +234,7 @@ private fun Modifier.contentDescription(contentDescription: String?): Modifier { private fun Constraints.toSizeOrNull() = when { isZero -> null else -> CoilSize( - width = if (hasBoundedWidth) Dimension(maxWidth) else Dimension.Original, - height = if (hasBoundedHeight) Dimension(maxHeight) else Dimension.Original + width = if (hasBoundedWidth) Dimension(maxWidth) else Dimension.Undefined, + height = if (hasBoundedHeight) Dimension(maxHeight) else Dimension.Undefined ) } diff --git a/coil-compose-base/src/main/java/coil/compose/AsyncImagePainter.kt b/coil-compose-base/src/main/java/coil/compose/AsyncImagePainter.kt index 3b789ca02a..c161cc1c13 100644 --- a/coil-compose-base/src/main/java/coil/compose/AsyncImagePainter.kt +++ b/coil-compose-base/src/main/java/coil/compose/AsyncImagePainter.kt @@ -401,8 +401,8 @@ private val Size.isPositive get() = width >= 0.5 && height >= 0.5 private fun Size.toSizeOrNull() = when { isUnspecified -> CoilSize.ORIGINAL isPositive -> CoilSize( - width = if (width.isFinite()) Dimension(width.roundToInt()) else Dimension.Original, - height = if (height.isFinite()) Dimension(height.roundToInt()) else Dimension.Original + width = if (width.isFinite()) Dimension(width.roundToInt()) else Dimension.Undefined, + height = if (height.isFinite()) Dimension(height.roundToInt()) else Dimension.Undefined ) else -> null } diff --git a/coil-gif/src/main/java/coil/decode/ImageDecoderDecoder.kt b/coil-gif/src/main/java/coil/decode/ImageDecoderDecoder.kt index 40d6bda5b2..b411d38771 100644 --- a/coil-gif/src/main/java/coil/decode/ImageDecoderDecoder.kt +++ b/coil-gif/src/main/java/coil/decode/ImageDecoderDecoder.kt @@ -17,10 +17,11 @@ import coil.request.animatedTransformation import coil.request.animationEndCallback import coil.request.animationStartCallback import coil.request.repeatCount -import coil.size.pxOrElse import coil.util.animatable2CallbackOf import coil.util.asPostProcessor +import coil.util.heightPx import coil.util.isHardware +import coil.util.widthPx import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.withContext @@ -56,8 +57,8 @@ class ImageDecoderDecoder @JvmOverloads constructor( // Configure the output image's size. val (srcWidth, srcHeight) = info.size - val dstWidth = options.size.width.pxOrElse { srcWidth } - val dstHeight = options.size.height.pxOrElse { srcHeight } + val dstWidth = options.size.widthPx(options.scale) { srcWidth } + val dstHeight = options.size.heightPx(options.scale) { srcHeight } if (srcWidth > 0 && srcHeight > 0 && (srcWidth != dstWidth || srcHeight != dstHeight)) { val multiplier = DecodeUtils.computeSizeMultiplier( diff --git a/coil-gif/src/main/java/coil/util/Utils.kt b/coil-gif/src/main/java/coil/util/Utils.kt index 429a9ed7a2..580a9afa15 100644 --- a/coil-gif/src/main/java/coil/util/Utils.kt +++ b/coil-gif/src/main/java/coil/util/Utils.kt @@ -10,6 +10,11 @@ import android.graphics.drawable.Drawable import android.os.Build.VERSION.SDK_INT import androidx.annotation.RequiresApi import androidx.vectordrawable.graphics.drawable.Animatable2Compat +import coil.size.Dimension +import coil.size.Scale +import coil.size.Size +import coil.size.isOriginal +import coil.size.pxOrElse import coil.transform.AnimatedTransformation import coil.transform.PixelOpacity @@ -49,3 +54,18 @@ internal inline fun List.forEachIndices(action: (T) -> Unit) { internal val Bitmap.Config.isHardware: Boolean get() = SDK_INT >= 26 && this == Bitmap.Config.HARDWARE + +internal inline fun Size.widthPx(scale: Scale, original: () -> Int): Int { + return if (isOriginal) original() else width.toPx(scale) +} + +internal inline fun Size.heightPx(scale: Scale, original: () -> Int): Int { + return if (isOriginal) original() else height.toPx(scale) +} + +internal fun Dimension.toPx(scale: Scale) = pxOrElse { + when (scale) { + Scale.FILL -> Int.MIN_VALUE + Scale.FIT -> Int.MAX_VALUE + } +} diff --git a/coil-svg/src/androidTest/java/coil/decode/SvgDecoderTest.kt b/coil-svg/src/androidTest/java/coil/decode/SvgDecoderTest.kt index 13473522d9..305f696440 100644 --- a/coil-svg/src/androidTest/java/coil/decode/SvgDecoderTest.kt +++ b/coil-svg/src/androidTest/java/coil/decode/SvgDecoderTest.kt @@ -6,6 +6,7 @@ import androidx.test.core.app.ApplicationProvider import coil.ImageLoader import coil.fetch.SourceResult import coil.request.Options +import coil.size.Dimension import coil.size.Scale import coil.size.Size import coil.util.assertIsSimilarTo @@ -138,6 +139,30 @@ class SvgDecoderTest { drawable.bitmap.assertIsSimilarTo(expected) } + /** Regression test: https://github.com/coil-kt/coil/issues/1246 */ + @Test + fun oneDimensionIsUndefined() = runTest { + val source = context.assets.open("coil_logo.svg").source().buffer() + val options = Options( + context = context, + size = Size(Dimension.Undefined, 250), // coil_logo.svg's intrinsic dimensions are 200x200. + scale = Scale.FIT + ) + val result = assertNotNull( + decoderFactory.create( + result = source.asSourceResult(), + options = options, + imageLoader = ImageLoader(context) + )?.decode() + ) + + assertTrue(result.isSampled) + val drawable = assertIs(result.drawable) + + val expected = context.decodeBitmapAsset("coil_logo.png") + drawable.bitmap.assertIsSimilarTo(expected) + } + private fun BufferedSource.asSourceResult( mimeType: String? = null, dataSource: DataSource = DataSource.DISK diff --git a/coil-svg/src/main/java/coil/decode/SvgDecoder.kt b/coil-svg/src/main/java/coil/decode/SvgDecoder.kt index f5a6108fcd..5fb1f7f44a 100644 --- a/coil-svg/src/main/java/coil/decode/SvgDecoder.kt +++ b/coil-svg/src/main/java/coil/decode/SvgDecoder.kt @@ -7,7 +7,9 @@ import androidx.core.graphics.drawable.toDrawable import coil.ImageLoader import coil.fetch.SourceResult import coil.request.Options -import coil.size.Dimension +import coil.size.Scale +import coil.size.isOriginal +import coil.util.toPx import coil.util.toSoftware import com.caverock.androidsvg.SVG import kotlinx.coroutines.runInterruptible @@ -42,9 +44,7 @@ class SvgDecoder @JvmOverloads constructor( val bitmapWidth: Int val bitmapHeight: Int - val size = options.size - val dstWidth = size.width.pxOrElse(svgWidth) - val dstHeight = size.height.pxOrElse(svgHeight) + val (dstWidth, dstHeight) = getDstSize(svgWidth, svgHeight, options.scale) if (svgWidth > 0 && svgHeight > 0) { val multiplier = DecodeUtils.computeSizeMultiplier( srcWidth = svgWidth, @@ -77,11 +77,14 @@ class SvgDecoder @JvmOverloads constructor( ) } - private fun Dimension.pxOrElse(value: Float): Float { - return when { - this is Dimension.Pixels -> px.toFloat() - value > 0 -> value - else -> DEFAULT_SIZE + private fun getDstSize(srcWidth: Float, srcHeight: Float, scale: Scale): Pair { + if (options.size.isOriginal) { + val dstWidth = if (srcWidth > 0) srcWidth else DEFAULT_SIZE + val dstHeight = if (srcHeight > 0) srcHeight else DEFAULT_SIZE + return dstWidth to dstHeight + } else { + val (dstWidth, dstHeight) = options.size + return dstWidth.toPx(scale) to dstHeight.toPx(scale) } } diff --git a/coil-svg/src/main/java/coil/util/Utils.kt b/coil-svg/src/main/java/coil/util/Utils.kt index c0693115ee..5ec374d062 100644 --- a/coil-svg/src/main/java/coil/util/Utils.kt +++ b/coil-svg/src/main/java/coil/util/Utils.kt @@ -4,6 +4,8 @@ package coil.util import android.graphics.Bitmap import android.os.Build.VERSION.SDK_INT +import coil.size.Dimension +import coil.size.Scale import okio.BufferedSource import okio.ByteString @@ -30,3 +32,14 @@ internal val Bitmap.Config.isHardware: Boolean internal fun Bitmap.Config?.toSoftware(): Bitmap.Config { return if (this == null || isHardware) Bitmap.Config.ARGB_8888 else this } + +internal fun Dimension.toPx(scale: Scale): Float { + if (this is Dimension.Pixels) { + return px.toFloat() + } else { + return when (scale) { + Scale.FILL -> Float.MIN_VALUE + Scale.FIT -> Float.MAX_VALUE + } + } +} diff --git a/coil-video/src/main/java/coil/decode/VideoFrameDecoder.kt b/coil-video/src/main/java/coil/decode/VideoFrameDecoder.kt index 4cc9d2bf03..53ec2932d7 100644 --- a/coil-video/src/main/java/coil/decode/VideoFrameDecoder.kt +++ b/coil-video/src/main/java/coil/decode/VideoFrameDecoder.kt @@ -19,7 +19,9 @@ import coil.request.videoFrameOption import coil.size.Dimension.Pixels import coil.size.Size import coil.size.pxOrElse +import coil.util.heightPx import coil.util.use +import coil.util.widthPx import kotlin.math.roundToInt /** @@ -49,14 +51,20 @@ class VideoFrameDecoder( } val dstSize = if (srcWidth > 0 && srcHeight > 0) { + val dstWidth = options.size.widthPx(options.scale) { srcWidth } + val dstHeight = options.size.heightPx(options.scale) { srcHeight } val rawScale = DecodeUtils.computeSizeMultiplier( srcWidth = srcWidth, srcHeight = srcHeight, - dstWidth = options.size.width.pxOrElse { srcWidth }, - dstHeight = options.size.height.pxOrElse { srcHeight }, + dstWidth = dstWidth, + dstHeight = dstHeight, scale = options.scale ) - val scale = if (options.allowInexactSize) rawScale.coerceAtMost(1.0) else rawScale + val scale = if (options.allowInexactSize) { + rawScale.coerceAtMost(1.0) + } else { + rawScale + } val width = (scale * srcWidth).roundToInt() val height = (scale * srcHeight).roundToInt() Size(width, height) @@ -81,7 +89,7 @@ class VideoFrameDecoder( // https://developer.android.com/guide/topics/media/media-formats#video-formats checkNotNull(rawBitmap) { "Failed to decode frame at $frameMicros microseconds." } - val bitmap = normalizeBitmap(rawBitmap, dstSize, options) + val bitmap = normalizeBitmap(rawBitmap, dstSize) val isSampled = if (srcWidth > 0 && srcHeight > 0) { DecodeUtils.computeSizeMultiplier( @@ -103,11 +111,7 @@ class VideoFrameDecoder( } /** Return [inBitmap] or a copy of [inBitmap] that is valid for the input [options] and [size]. */ - private fun normalizeBitmap( - inBitmap: Bitmap, - size: Size, - options: Options - ): Bitmap { + private fun normalizeBitmap(inBitmap: Bitmap, size: Size): Bitmap { // Fast path: if the input bitmap is valid, return it. if (isConfigValid(inBitmap, options) && isSizeValid(inBitmap, options, size)) { return inBitmap diff --git a/coil-video/src/main/java/coil/util/Utils.kt b/coil-video/src/main/java/coil/util/Utils.kt index 87ac77135c..28050e2954 100644 --- a/coil-video/src/main/java/coil/util/Utils.kt +++ b/coil-video/src/main/java/coil/util/Utils.kt @@ -4,6 +4,11 @@ package coil.util import android.media.MediaMetadataRetriever import android.os.Build.VERSION.SDK_INT +import coil.size.Dimension +import coil.size.Scale +import coil.size.Size +import coil.size.isOriginal +import coil.size.pxOrElse /** [MediaMetadataRetriever] doesn't implement [AutoCloseable] until API 29. */ internal inline fun MediaMetadataRetriever.use(block: (MediaMetadataRetriever) -> T): T { @@ -18,3 +23,18 @@ internal inline fun MediaMetadataRetriever.use(block: (MediaMetadataRetrieve } } } + +internal inline fun Size.widthPx(scale: Scale, original: () -> Int): Int { + return if (isOriginal) original() else width.toPx(scale) +} + +internal inline fun Size.heightPx(scale: Scale, original: () -> Int): Int { + return if (isOriginal) original() else height.toPx(scale) +} + +internal fun Dimension.toPx(scale: Scale) = pxOrElse { + when (scale) { + Scale.FILL -> Int.MIN_VALUE + Scale.FIT -> Int.MAX_VALUE + } +}