Skip to content

Commit

Permalink
Convert Dimension.Original to be Dimension.Undefined. (#1250)
Browse files Browse the repository at this point in the history
* Initial commit.

* Refactor.

* Tweak test.

* Docs.

* Fix.

* Add test.

* Tweak naming.

* Docs.

* Docs.

* Docs.

* Docs.
  • Loading branch information
colinrtwhite committed Apr 26, 2022
1 parent d022725 commit 34e8f32
Show file tree
Hide file tree
Showing 22 changed files with 179 additions and 64 deletions.
10 changes: 5 additions & 5 deletions coil-base/api/coil-base.api
Expand Up @@ -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 <init> (I)V
Expand All @@ -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;
Expand Down
Expand Up @@ -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
)

Expand All @@ -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
)

Expand Down
8 changes: 4 additions & 4 deletions coil-base/src/main/java/coil/decode/BitmapFactoryDecoder.kt
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion coil-base/src/main/java/coil/request/RequestService.kt
Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions coil-base/src/main/java/coil/size/Dimension.kt
Expand Up @@ -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"
}
}

Expand Down
15 changes: 13 additions & 2 deletions coil-base/src/main/java/coil/size/Size.kt
Expand Up @@ -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
*/
Expand All @@ -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)
}
}

Expand Down
4 changes: 2 additions & 2 deletions coil-base/src/main/java/coil/size/ViewSizeResolver.kt
Expand Up @@ -85,9 +85,9 @@ interface ViewSizeResolver<T : View> : 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.
Expand Down
Expand Up @@ -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

/**
Expand Down Expand Up @@ -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,
Expand Down
9 changes: 4 additions & 5 deletions coil-base/src/main/java/coil/util/DrawableUtils.kt
Expand Up @@ -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 {
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
}
Expand Down
19 changes: 19 additions & 0 deletions coil-base/src/main/java/coil/util/Utils.kt
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down
12 changes: 6 additions & 6 deletions coil-base/src/test/java/coil/memory/MemoryCacheServiceTest.kt
Expand Up @@ -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)
))
}

Expand Down
2 changes: 1 addition & 1 deletion coil-base/src/test/java/coil/size/ViewSizeResolverTest.kt
Expand Up @@ -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)
}
}
Expand Up @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions coil-compose-base/src/main/java/coil/compose/AsyncImage.kt
Expand Up @@ -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
)
}
Expand Up @@ -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
}
Expand Down
7 changes: 4 additions & 3 deletions coil-gif/src/main/java/coil/decode/ImageDecoderDecoder.kt
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
20 changes: 20 additions & 0 deletions coil-gif/src/main/java/coil/util/Utils.kt
Expand Up @@ -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

Expand Down Expand Up @@ -49,3 +54,18 @@ internal inline fun <T> List<T>.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
}
}

0 comments on commit 34e8f32

Please sign in to comment.