Skip to content

Fix CrossfadePainter intrinsic size logic#3175

Merged
colinrtwhite merged 9 commits into
coil-kt:mainfrom
tak8997:placeholder_image_messes_when_crossfading
Oct 8, 2025
Merged

Fix CrossfadePainter intrinsic size logic#3175
colinrtwhite merged 9 commits into
coil-kt:mainfrom
tak8997:placeholder_image_messes_when_crossfading

Conversation

@tak8997
Copy link
Copy Markdown
Contributor

@tak8997 tak8997 commented Sep 14, 2025

Fix CrossfadePainter intrinsic size logic for proper ContentScale behavior

Solution

Refactored the intrinsic size computation logic to:

  • Prioritize the end image's intrinsic size when available
  • Fall back to start image's size only when end image size is unspecified

Changes

  • Ensures ContentScale is applied correctly to the final image regardless of placeholder dimensions

Motivation

Comment on lines -102 to -106
if (isStartSpecified && isEndSpecified) {
return Size(
width = maxOf(startSize.width, endSize.width),
height = maxOf(startSize.height, endSize.height),
)
Copy link
Copy Markdown
Contributor Author

@tak8997 tak8997 Sep 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When you initially wrote the logic, may I ask why you chose to use maxOf for startSize and endSize?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was to ensure that the placeholder isn't cut off. It's also to have consistency with CrossfadeDrawable.


if (isEndSpecified) {
return endSize
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, we could simply set the intrinsicSize based on the endSize. What do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be a significant behaviour change and I'm not sure about the ramifications. How would crossfading from a painter with an intrinsic size to a painter with no intrinsic size (e.g. ColorPainter) inside an AsyncImage with no constraints look? Would it have 0 size?

@colinrtwhite
Copy link
Copy Markdown
Member

colinrtwhite commented Sep 19, 2025

@tak8997 Thanks for looking into that bug. Unfortunately I don't think we can make this change without flagging as this is a fairly sizeable behaviour change for existing users. Could you:

  • Rebase on main. I added a couple CrossfadePainter tests as it didn't have any.
  • Add a configuration option (using the same Extras pattern as the previous PR) that is false by default to enable this new sizing behaviour.
  • Add a regression test in CrossfadePainterTest to ensure that this ticket is fixed.

You'll likely need to add a secondary @Deprecated(level = DeprecationLevel.HIDDEN) constructor to CrossfadePainter to preserve binary compatibility.

byungtak-lee and others added 3 commits September 27, 2025 12:22
…te scaling

* This change ensures that the ContentScale is applied correctly to the final image, regardless of the placeholder's dimensions.
@tak8997 tak8997 force-pushed the placeholder_image_messes_when_crossfading branch from e5c0719 to 683e131 Compare September 27, 2025 11:50
@tak8997
Copy link
Copy Markdown
Contributor Author

tak8997 commented Sep 27, 2025

@tak8997 Thanks for looking into that bug. Unfortunately I don't think we can make this change without flagging as this is a fairly sizeable behaviour change for existing users. Could you:

  • Rebase on main. I added a couple CrossfadePainter tests as it didn't have any.
  • Add a configuration option (using the same Extras pattern as the previous PR) that is false by default to enable this new sizing behaviour.
  • Add a regression test in CrossfadePainterTest to ensure that this ticket is fixed.

You'll likely need to add a secondary @Deprecated(level = DeprecationLevel.HIDDEN) constructor to CrossfadePainter to preserve binary compatibility.

78086de
683e131
ff6d243
sorry for late. please check this🙏

Copy link
Copy Markdown
Member

@colinrtwhite colinrtwhite left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for implementing this! Left two remaining comments otherwise LGTM

* @param preferExactIntrinsicSize If true, this drawable's intrinsic width/height will only be -1
* if [start] **and** [end] return -1 for that dimension. If false, the intrinsic width/height will
* be -1 if [start] **or** [end] return -1 for that dimension. This is useful for views that
* require an exact intrinsic size to scale the drawable.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a @param preferEndFirstIntrinsicSize entry to the method description? We can copy it from here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6ac3f3f
please check this

val timeSource: TimeSource = TimeSource.Monotonic,
val fadeStart: Boolean = true,
val preferExactIntrinsicSize: Boolean = false,
val preferEndFirstIntrinsicSize: Boolean = false
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to add a @Deprecated("Kept for binary compatibility.", level = DeprecationLevel.HIDDEN) version of this constructor without the preferEndFirstIntrinsicSize argument to preserve binary compatibility. Check out NetworkClient for an example .

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

50ab610
please check this

Copy link
Copy Markdown
Member

@colinrtwhite colinrtwhite left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for implementing this. Looks good!

@colinrtwhite colinrtwhite merged commit 890021e into coil-kt:main Oct 8, 2025
10 checks passed
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

Successfully merging this pull request may close these issues.

3 participants