-
-
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
fix: Implement correct transparent color interpolation #8398
Conversation
fa1a973 to
d5538d1
Compare
tjzel
left a comment
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
|
piaskowyk
left a comment
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 👍
I think we could artificially extend inputs and outputs. When receiving (i.e. in RGB) interpolateColor(
progress,
[0, 0.5, 1],
['#ff0000ff' /* red */, 'transparent', '#0000ffff' /* blue */]
)we could effectively transform it as if the user invoked interpolateColor(
progress,
[0, 0.5, 0.5, 1],
['#ff0000ff' /* red */, '#ff000000' /* transparent red */, '#0000ff00' /* transparent blue */, '#0000ffff' /* blue */]
)This is a super simple, working PoC for RGB colors: // interpolateColor.ts
function processTransparency(
inputRange: number[],
outputRange: (string | number)[]
): void {
'worklet';
for (let i = 0; i < outputRange.length; i++) {
const color = outputRange[i];
if (color !== 'transparent') {
continue;
}
const previousColor = processColor(outputRange[i - 1]);
const redPrev = red(previousColor!);
const greenPrev = green(previousColor!);
const bluePrev = blue(previousColor!);
const transparentPrev = rgbaColor(redPrev, greenPrev, bluePrev, 0);
const nextColor = processColor(outputRange[i + 1]);
const redNext = red(nextColor!);
const greenNext = green(nextColor!);
const blueNext = blue(nextColor!);
const transparentNext = rgbaColor(redNext, greenNext, blueNext, 0);
outputRange.splice(i, 1, transparentPrev, transparentNext);
inputRange.splice(i, 1, inputRange[i], inputRange[i]);
}
}diff to try it out: diff --git a/apps/common-app/src/apps/reanimated/examples/ColorInterpolationExample.tsx b/apps/common-app/src/apps/reanimated/examples/ColorInterpolationExample.tsx
index 0cd8ef57d4..ec8f4c37cb 100644
--- a/apps/common-app/src/apps/reanimated/examples/ColorInterpolationExample.tsx
+++ b/apps/common-app/src/apps/reanimated/examples/ColorInterpolationExample.tsx
@@ -108,9 +108,15 @@ function ColorInterpolation({
function rgbInterpolation(color1: string, color2: string, progress: number) {
'worklet';
- return interpolateColor(progress, [0, 1], [color1, color2], 'RGB', {
- gamma: 1,
- });
+ return interpolateColor(
+ progress,
+ [0, 0.5, 1],
+ ['#ff0000ff', 'transparent', '#0000ffff'],
+ 'RGB',
+ {
+ gamma: 1,
+ }
+ );
}
function rgbGammaInterpolation(
@@ -236,4 +242,8 @@ const styles = StyleSheet.create({
color: 'black',
fontWeight: 'bold',
},
+ // eslint-disable-next-line react-native/no-unused-styles
+ dupsko: {
+ backgroundColor: 'transparent',
+ },
});
diff --git a/packages/react-native-reanimated/src/interpolateColor.ts b/packages/react-native-reanimated/src/interpolateColor.ts
index d4cbcd3261..0a3627c46a 100644
--- a/packages/react-native-reanimated/src/interpolateColor.ts
+++ b/packages/react-native-reanimated/src/interpolateColor.ts
@@ -276,6 +276,38 @@ const getInterpolateLAB = (
};
};
+function processTransparency(
+ inputRange: number[],
+ outputRange: (string | number)[]
+): void {
+ 'worklet';
+
+ for (let i = 0; i < outputRange.length; i++) {
+ const color = outputRange[i];
+ if (color !== 'transparent') {
+ continue;
+ }
+
+ const previousColor = processColor(outputRange[i - 1]);
+
+ const redPrev = red(previousColor!);
+ const greenPrev = green(previousColor!);
+ const bluePrev = blue(previousColor!);
+ const transparentPrev = rgbaColor(redPrev, greenPrev, bluePrev, 0);
+
+ const nextColor = processColor(outputRange[i + 1]);
+
+ const redNext = red(nextColor!);
+ const greenNext = green(nextColor!);
+ const blueNext = blue(nextColor!);
+
+ const transparentNext = rgbaColor(redNext, greenNext, blueNext, 0);
+
+ outputRange.splice(i, 1, transparentPrev, transparentNext);
+ inputRange.splice(i, 1, inputRange[i], inputRange[i]);
+ }
+}
+
/**
* Lets you map a value from a range of numbers to a range of colors using
* linear interpolation.
@@ -326,6 +358,8 @@ export function interpolateColor(
options
);
} else if (colorSpace === 'RGB') {
+ processTransparency(inputRange, outputRange);
+
return interpolateColorsRGB(
value,
inputRange, |
tjzel
left a comment
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.
Looks good overall, left some minor comments.
Summary
This PR fixes the
interpolateColorinterpolation 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