Skip to content

Commit 4d2695a

Browse files
szydlovskyMatiPl01
authored andcommitted
Add color interpolation (#14)
* init color interpolation * add gamma correction to r,g,b interpolation * fix some merge oopsies and add example * shorten animation * remove unneeded include * make review requested changes * final fixes (i hope) * fix 0xrrggbbaa number format
1 parent 4c3b206 commit 4d2695a

File tree

7 files changed

+148
-17
lines changed

7 files changed

+148
-17
lines changed
Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,61 @@
1-
// TODO: Implement this example
1+
import { StyleSheet, View, SafeAreaView } from 'react-native';
2+
3+
import React from 'react';
4+
import Animated from 'react-native-reanimated';
5+
26
export default function ColorsExample() {
3-
return null;
7+
return (
8+
<SafeAreaView style={styles.container}>
9+
<View style={styles.container}>
10+
<Animated.View
11+
style={{
12+
width: 200,
13+
height: 200,
14+
borderRadius: 30,
15+
// @ts-ignore TODO
16+
animationName: {
17+
from: {
18+
backgroundColor: 'red',
19+
transform: [{ rotate: '0deg' }],
20+
},
21+
'16.7%': {
22+
backgroundColor: 'orange',
23+
transform: [{ rotate: '60deg' }],
24+
},
25+
'33.4%': {
26+
backgroundColor: 'yellow',
27+
transform: [{ rotate: '120deg' }],
28+
},
29+
'50.1%': {
30+
backgroundColor: 'green',
31+
transform: [{ rotate: '180deg' }],
32+
},
33+
'66.8%': {
34+
backgroundColor: 'blue',
35+
transform: [{ rotate: '240deg' }],
36+
},
37+
'83.5%': {
38+
backgroundColor: 'indigo',
39+
transform: [{ rotate: '300deg' }],
40+
},
41+
to: {
42+
backgroundColor: 'violet',
43+
transform: [{ rotate: '360deg' }],
44+
},
45+
},
46+
animationDuration: '5s',
47+
animationTimingFunction: 'linear',
48+
}}
49+
/>
50+
</View>
51+
</SafeAreaView>
52+
);
453
}
54+
55+
const styles = StyleSheet.create({
56+
container: {
57+
flex: 1,
58+
alignItems: 'center',
59+
justifyContent: 'center',
60+
},
61+
});

packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ColorValueInterpolator.cpp

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,64 @@
22

33
namespace reanimated {
44

5-
int ColorValueInterpolator::convertValue(
5+
ColorArray ColorValueInterpolator::convertValue(
66
jsi::Runtime &rt,
77
const jsi::Value &value) const {
8-
throw std::runtime_error("[Reanimated] Not implemented");
8+
// We receive colors as decimals made from AARRGGBB format hexes
9+
unsigned color = static_cast<unsigned>(value.asNumber());
10+
ColorArray channels;
11+
channels[0] = (color << 8) >> 24; // Red
12+
channels[1] = (color << 16) >> 24; // Green
13+
channels[2] = (color << 24) >> 24; // Blue
14+
channels[3] = color >> 24; // Alpha
15+
return channels;
916
}
1017

1118
jsi::Value ColorValueInterpolator::convertToJSIValue(
1219
jsi::Runtime &rt,
13-
const int &value) const {
14-
throw std::runtime_error("[Reanimated] Not implemented");
20+
const ColorArray &value) const {
21+
double color =
22+
(value[3] << 24) | (value[0] << 16) | (value[1] << 8) | value[2];
23+
return jsi::Value(color);
1524
}
1625

17-
int ColorValueInterpolator::interpolate(
26+
ColorArray ColorValueInterpolator::interpolate(
1827
double localProgress,
19-
const int &fromValue,
20-
const int &toValue,
28+
const ColorArray &fromValue,
29+
const ColorArray &toValue,
2130
const InterpolationUpdateContext context) const {
22-
throw std::runtime_error("[Reanimated] Not implemented");
31+
ColorArray resultChannels;
32+
33+
// interpolate rgb cahnnels using gamma correction and alpha channel without it
34+
for (int i = 0; i < 4; i++) {
35+
double fromChannelValue =
36+
i == 3 ? fromValue[i] : toLinearSpace(fromValue[i]);
37+
double toChannelValue = i == 3 ? toValue[i] : toLinearSpace(toValue[i]);
38+
double interpolatedValue =
39+
(toChannelValue - fromChannelValue) * localProgress + fromChannelValue;
40+
resultChannels[i] =
41+
i == 3 ? interpolatedValue : toGammaCorrectedSpace(interpolatedValue);
42+
}
43+
44+
return resultChannels;
45+
}
46+
47+
double ColorValueInterpolator::toLinearSpace(uint8_t value) const {
48+
double normalized = double(value) / 255.0;
49+
if (normalized <= 0.04045) {
50+
return normalized / 12.92;
51+
} else {
52+
return std::pow((normalized + 0.055) / 1.055, 2.4);
53+
}
54+
}
55+
56+
uint8_t ColorValueInterpolator::toGammaCorrectedSpace(double value) const {
57+
if (value <= 0.0031308) {
58+
value = 12.92 * value;
59+
} else {
60+
value = 1.055 * std::pow(value, 1.0 / 2.4) - 0.055;
61+
}
62+
return value * 255.0 + 0.5;
2363
}
2464

2565
} // namespace reanimated

packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ColorValueInterpolator.h

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,23 @@
77

88
namespace reanimated {
99

10-
class ColorValueInterpolator : public ValueInterpolator<int> {
10+
class ColorValueInterpolator : public ValueInterpolator<ColorArray> {
1111
protected:
12-
int convertValue(jsi::Runtime &rt, const jsi::Value &value) const override;
12+
ColorArray convertValue(jsi::Runtime &rt, const jsi::Value &value)
13+
const override;
1314

14-
jsi::Value convertToJSIValue(jsi::Runtime &rt, const int &value)
15+
jsi::Value convertToJSIValue(jsi::Runtime &rt, const ColorArray &value)
1516
const override;
1617

17-
int interpolate(
18+
ColorArray interpolate(
1819
double localProgress,
19-
const int &fromValue,
20-
const int &toValue,
20+
const ColorArray &fromValue,
21+
const ColorArray &toValue,
2122
const InterpolationUpdateContext context) const override;
23+
24+
private:
25+
double toLinearSpace(uint8_t value) const;
26+
uint8_t toGammaCorrectedSpace(double value) const;
2227
};
2328

2429
} // namespace reanimated

packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ValueInterpolator.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ template class ValueInterpolator<int>;
133133
template class ValueInterpolator<double>;
134134
template class ValueInterpolator<std::string>;
135135
template class ValueInterpolator<std::vector<double>>;
136+
template class ValueInterpolator<ColorArray>;
136137
template class ValueInterpolator<RelativeInterpolatorValue>;
137138

138139
} // namespace reanimated

packages/react-native-reanimated/Common/cpp/reanimated/CSS/interpolation/values/ValueInterpolator.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ struct Keyframe {
2121
T value;
2222
};
2323

24+
using ColorArray = std::array<uint8_t, 4>;
25+
2426
template <typename T>
2527
class ValueInterpolator : public Interpolator {
2628
public:

packages/react-native-reanimated/src/Colors.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,3 +724,12 @@ export function toGammaSpace(
724724
res.push(RGBA[3]);
725725
return res as ParsedColorArray;
726726
}
727+
728+
export function processCSSAnimationColor(value: string | number) {
729+
if (typeof value === 'string') {
730+
return processColor(value);
731+
} else {
732+
// case of number format 0xRRGGBBAA format needs to be re-formatted
733+
return processColor(`#${value.toString(16).padStart(8, '0')}`);
734+
}
735+
}

packages/react-native-reanimated/src/css/normalization.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {
1414
NormalizedOffsetKeyframe,
1515
} from './types';
1616
import { ReanimatedError } from '../errors';
17+
import { processCSSAnimationColor } from '../Colors';
1718

1819
const VALID_ANIMATION_DIRECTIONS = [
1920
'normal',
@@ -45,6 +46,8 @@ export const ERROR_MESSAGES = {
4546
`[Reanimated] Unsupported timing function "${timingFunction}". Supported functions: linear, ease-in-out-back.`,
4647
invalidAnimationName: (animationName: any) =>
4748
`[Reanimated] Invalid animation "${animationName}". Expected an object containing keyframes.`,
49+
unsupportedColorFormat: (value: any, prop: string) =>
50+
`[Reanimated] Unsupported color format ${value} for ${prop} in CSS animation`,
4851
};
4952

5053
function normalizeTimeUnit(timeUnit: CSSAnimationTimeUnit): number | null {
@@ -241,7 +244,21 @@ export function handlePrimitiveValue(
241244
if (!keyframedStyle[prop]) {
242245
(keyframedStyle as any)[prop] = [];
243246
}
244-
(keyframedStyle[prop] as any[]).push({ offset, value });
247+
248+
let processedValue = value;
249+
if (
250+
prop.toLowerCase().includes('color') &&
251+
(typeof value === 'string' || typeof value === 'number')
252+
) {
253+
processedValue = processCSSAnimationColor(value);
254+
if (!processedValue) {
255+
throw new ReanimatedError(
256+
ERROR_MESSAGES.unsupportedColorFormat(value, prop)
257+
);
258+
}
259+
}
260+
261+
(keyframedStyle[prop] as any[]).push({ offset, value: processedValue });
245262
}
246263

247264
function addSubPropertyValue(

0 commit comments

Comments
 (0)