-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
fix: Implement correct transparent color interpolation #8398
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
base: main
Are you sure you want to change the base?
Conversation
{ colorSpace: 'HSV' }, | ||
{ colorSpace: 'HSV', options: { useCorrectedHSVInterpolation: false } }, | ||
// LAB may produce slightly different results, but the differences are usually small | ||
{ colorSpace: 'LAB', eps: 1e-5 }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LAB colorspace returned sth like rgba(255, 0.00001, 0, 0)
. I think that it is because of the conversion of the color between colorspaces.
test.each([ | ||
['red', 0xff0000ff], | ||
['transparent', 0x00000000], | ||
['transparent', undefined], // Transparent cannot be represented as a number |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I decided to return undefined
for the transparent color as the null
value indicates that the value is invalid but the 'transparent'
string is valid but cannot be converted to the number via the processColor
function. That's why I decided to return undefined
instead to differentiate this case.
const names: Record<string, number> = { | ||
transparent: 0x00000000, | ||
const names: Record<string, number | undefined> = { | ||
transparent: undefined, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I explicitly store it as undefined
in order not to return null
. I prefer to return undefined
as the color is valid but not convertible to number, so the number representation is not defined.
|
||
// This can happen while interpolating between a 'transparent' and a non-transparent color. | ||
// We want to keep color channels unchanged but interpolate only the alpha channel. | ||
return narrowedRange.leftEdgeOutput ?? narrowedRange.rightEdgeOutput ?? 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When one of the interpolation pair values is undefined
(i.e. when we interpolate from 'transparent'
to a non-transparent color or from non-transparent to 'transparent'
), we want to preserve color channels from the non-transparent one and just interpolate the alpha
channel.
If both both are undefined
(interpolation from 'transparent'
to 'transparent'
), then we can just use 0 (interpolation of any 2 fully transparent colors is the same, no matter what values are used for color channels).
fa1a973
to
d5538d1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that much code necessary? Since the only interface for the user is interpolateColor
, can't we just convert transparent
in that function to a relevant color in the given color space and leave color interpolators unchanged?
If you have an idea how to represent such a I personally think that such representation doesn't exist as the RGB channels depend on the other color used in the interpolation pair. For example, when interpolating from Also consider this example: interpolateColor(
progress,
[0, 0.5, 1],
['rgb(122, 235, 52)', 'transparent', 'rgba(52, 58, 235, 100)']
) In this case, the
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the visual effect 👍
Summary
This PR fixes the
interpolateColor
interpolation between non-transparent colors and'transparent'
(keyword) color. The previous implementation was incorrect as it converted'transparent'
to the0x00000000
(black transparent), but the'transparent'
keyword is not just a black transparent color. Because of that, the interpolation from/to the transparent color always changed the color to black (the difference can be seen on the recordings below).Example recordings
If you pause recordings and compare, you can see that the Before one goes through a dirty yellow to transparent because it becomes more and more black on each animation step.
Simulator.Screen.Recording.-.iPhone.17.Pro.-.2025-10-15.at.20.03.31.mp4
Simulator.Screen.Recording.-.iPhone.17.Pro.-.2025-10-15.at.19.56.50.mp4
Example source code