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

Consider implementing contentScale in Compose without using Transformations #5391

Open
tunjid opened this issue Mar 28, 2024 · 0 comments
Open

Comments

@tunjid
Copy link

tunjid commented Mar 28, 2024

Glide compose version used: 1.0.0-beta01.

In GlideImage, the implementation of ContentScale is with a contentScaleTransform.

This causes a few issues:

  • The same image with different content scales applied will have multiple memory cache entries.
  • It is currently not possible to interpolate a content scale change.

Ideally the application of a content scale should not require the creation of separate images. The same image should be properly displayed in the bounds of the container and not have any extra processing applied to it for different content scales.

The bug is best demonstrated visually:

glide-bug.webm

It is possible to interpolate ContentScale as described in this blog post with the following code:

@Composable
fun ContentScale.interpolate(): ContentScale {
    var interpolation by remember {
        mutableFloatStateOf(1f)
    }
    var previousScale by remember {
        mutableStateOf(this)
    }

    val currentScale by remember {
        mutableStateOf(this)
    }.apply {
        if (value != this@interpolate) previousScale = when {
            interpolation == 1f -> value
            else -> CapturedContentScale(
                capturedInterpolation = interpolation,
                previousScale = previousScale,
                currentScale = value
            )
        }.also { interpolation = 0f }
        value = this@interpolate
    }

    LaunchedEffect(currentScale) {
        animate(
            initialValue = 0f,
            targetValue = 1f,
            animationSpec = spring(
                stiffness = 10f

            ),
            block = { progress, _ ->
                interpolation = progress
            },
        )
    }

    return remember {
        object : ContentScale {
            override fun computeScaleFactor(
                srcSize: Size,
                dstSize: Size
            ): ScaleFactor {
                val start = previousScale.computeScaleFactor(
                    srcSize = srcSize,
                    dstSize = dstSize
                )
                val stop = currentScale.computeScaleFactor(
                    srcSize = srcSize,
                    dstSize = dstSize
                )

                return if (start == stop) stop
                else lerp(
                    start = start,
                    stop = stop,
                    fraction = interpolation
                )
            }
        }
    }
}


private class CapturedContentScale(
    private val capturedInterpolation: Float,
    private val previousScale: ContentScale,
    private val currentScale: ContentScale,

    ) : ContentScale {
    override fun computeScaleFactor(
        srcSize: Size,
        dstSize: Size
    ): ScaleFactor = lerp(
        start = previousScale.computeScaleFactor(
            srcSize = srcSize,
            dstSize = dstSize
        ),
        stop = currentScale.computeScaleFactor(
            srcSize = srcSize,
            dstSize = dstSize
        ),
        fraction = capturedInterpolation
    )
}

In a comparison with Coil and a manual painter, Glide is unable to actually apply this interpolation. Consider the following:

                    Column(
                        verticalArrangement = Arrangement.Center,
                        horizontalAlignment = Alignment.CenterHorizontally
                    ) {
                        var contentScale by remember { mutableStateOf(ContentScale.Crop) }
                        ImageComparison(
                            imageUrl = IMAGE_URL,
                            contentDescription = null,
                            center = Alignment.Center,
                            crop = contentScale.interpolate(),
                            modifier = Modifier.size(200.dp),
                        )

                        val scales = remember {
                            listOf(
                                ContentScale.Crop to "Crop",
                                ContentScale.Fit to "Fit",
                                ContentScale.None to "None",
                                ContentScale.FillHeight to "FillHeight",
                                ContentScale.FillWidth to "FillWidth",
                                ContentScale.FillBounds to "Fill",
                            )
                        }
                        Row {
                            scales.forEach { (scale, label) ->
                                Text(
                                    text = label,
                                    textDecoration = if (contentScale == scale) TextDecoration.Underline else TextDecoration.None,
                                    modifier = Modifier
                                        .padding(8.dp)
                                        .clickable {
                                            contentScale = scale
                                        }
                                )
                            }
                        }
                    }

Where ImageComparison is:

@OptIn(ExperimentalGlideComposeApi::class)
@Composable
private fun ImageComparison(
    imageUrl: String,
    contentDescription: String?,
    center: Alignment,
    crop: ContentScale,
    modifier: Modifier
) {
    AsyncImage(
        modifier = modifier,
        model = imageUrl,
        contentDescription = contentDescription,
        alignment = center,
        contentScale = crop,
    )
    Text(text = "Coil")

    GlideImage(
        modifier = modifier,
        model = imageUrl,
        contentDescription = contentDescription,
        alignment = center,
        contentScale = crop,
    )
    Text(text = "Glide")

    ImageByUrl(
        modifier = modifier,
        imageUrl = imageUrl,
        contentDescription = contentDescription,
        alignment = center,
        contentScale = crop,
    )
    Text(text = "Manual Painter")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant