Skip to content

Commit

Permalink
Adds color value interpolation handling to tick-driven NativeAnimated…
Browse files Browse the repository at this point in the history
… driver

To avoid overcomplicating the base ValueAnimatedNode, this change uses memcpy to stuff the interpolated color value into the double `value` field in the ValueAnimatedNode, and reads it out as a color type if the output type is set.
  • Loading branch information
rozele committed Mar 28, 2024
1 parent 0d9d42c commit 20dcae8
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
#include "pch.h"

#include <UI.Composition.h>
#include <Utils/ValueUtils.h>
#include "ColorAnimatedNode.h"
#include "NativeAnimatedNodeManager.h"
#include <Utils/ValueUtils.h>

namespace Microsoft::ReactNative {
ColorAnimatedNode::ColorAnimatedNode(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,21 @@
#include "ExtrapolationType.h"
#include "InterpolationAnimatedNode.h"
#include "NativeAnimatedNodeManager.h"
#include "Utils/ValueUtils.h"

namespace Microsoft::ReactNative {

inline int32_t ColorToInt(winrt::Windows::UI::Color color) {
return static_cast<uint8_t>(color.A) << 24 | static_cast<uint8_t>(color.R) << 16 |
static_cast<uint8_t>(color.G) << 8 | static_cast<uint8_t>(color.B);
}

inline uint8_t ScaleByte(uint8_t min, uint8_t max, double ratio) {
const auto scaledValue = min + (max - min) * ratio;
const auto clampedValue = std::clamp(static_cast<uint32_t>(std::round(scaledValue)), 0u, 255u);
return static_cast<uint8_t>(clampedValue);
}

InterpolationAnimatedNode::InterpolationAnimatedNode(
int64_t tag,
const winrt::Microsoft::ReactNative::JSValueObject &config,
Expand All @@ -17,8 +30,18 @@ InterpolationAnimatedNode::InterpolationAnimatedNode(
for (const auto &rangeValue : config[s_inputRangeName].AsArray()) {
m_inputRanges.push_back(rangeValue.AsDouble());
}
for (const auto &rangeValue : config[s_outputRangeName].AsArray()) {
m_outputRanges.push_back(rangeValue.AsDouble());

const auto isColorOutput = config[s_outputTypeName].AsString() == s_colorOutputType;
if (!m_useComposition && isColorOutput) {
m_isColorOutput = true;
for (const auto &rangeValue : config[s_outputRangeName].AsArray()) {
m_colorOutputRanges.push_back(ColorFrom(rangeValue));
}
} else {
assert(!isColorOutput && "Color interpolation not supported");
for (const auto &rangeValue : config[s_outputRangeName].AsArray()) {
m_defaultOutputRanges.push_back(rangeValue.AsDouble());
}
}

m_extrapolateLeft = config[s_extrapolateLeftName].AsString();
Expand All @@ -33,7 +56,11 @@ void InterpolationAnimatedNode::Update() {

if (const auto manager = m_manager.lock()) {
if (const auto node = manager->GetValueAnimatedNode(m_parentTag)) {
RawValue(InterpolateValue(node->Value()));
if (m_isColorOutput) {
RawValue(InterpolateColor(node->Value()));
} else {
RawValue(InterpolateValue(node->Value()));
}
}
}
}
Expand Down Expand Up @@ -95,8 +122,9 @@ comp::ExpressionAnimation InterpolationAnimatedNode::CreateExpressionAnimation(
for (size_t i = 0; i < m_inputRanges.size(); i++) {
animation.SetScalarParameter(s_inputName.data() + std::to_wstring(i), static_cast<float>(m_inputRanges[i]));
}
for (size_t i = 0; i < m_outputRanges.size(); i++) {
animation.SetScalarParameter(s_outputName.data() + std::to_wstring(i), static_cast<float>(m_outputRanges[i]));
for (size_t i = 0; i < m_defaultOutputRanges.size(); i++) {
animation.SetScalarParameter(
s_outputName.data() + std::to_wstring(i), static_cast<float>(m_defaultOutputRanges[i]));
}
return animation;
}
Expand Down Expand Up @@ -173,7 +201,7 @@ winrt::hstring InterpolationAnimatedNode::GetRightExpression(
const winrt::hstring &value,
const winrt::hstring &rightInterpolateExpression) {
const auto lastInput = s_inputName.data() + std::to_wstring(m_inputRanges.size() - 1);
const auto lastOutput = s_outputName.data() + std::to_wstring(m_outputRanges.size() - 1);
const auto lastOutput = s_outputName.data() + std::to_wstring(m_defaultOutputRanges.size() - 1);
switch (ExtrapolationTypeFromString(m_extrapolateRight)) {
case ExtrapolationType::Clamp:
return value + L" > " + lastInput + L" ? " + lastOutput + L" : ";
Expand All @@ -200,10 +228,49 @@ double InterpolationAnimatedNode::InterpolateValue(double value) {
value,
m_inputRanges[index],
m_inputRanges[index + 1],
m_outputRanges[index],
m_outputRanges[index + 1],
m_defaultOutputRanges[index],
m_defaultOutputRanges[index + 1],
m_extrapolateLeft,
m_extrapolateRight);
}

double InterpolationAnimatedNode::InterpolateColor(double value) {
// Compute range index
size_t index = 1;
for (; index < m_inputRanges.size() - 1; ++index) {
if (m_inputRanges[index] >= value) {
break;
}
}
index--;

double result;
const auto outputMin = m_colorOutputRanges[index];
const auto outputMax = m_colorOutputRanges[index + 1];
const auto outputMinInt = ColorToInt(outputMin);
const auto outputMaxInt = ColorToInt(outputMax);
if (outputMin == outputMax) {
memcpy(&result, &outputMinInt, sizeof(int32_t));
return result;
}

const auto inputMin = m_inputRanges[index];
const auto inputMax = m_inputRanges[index + 1];
if (inputMin == inputMax) {
if (value <= inputMin) {
memcpy(&result, &outputMinInt, sizeof(int32_t));
} else {
memcpy(&result, &outputMaxInt, sizeof(int32_t));
}
return result;
}

const auto ratio = (value - inputMin) / (inputMax - inputMin);
const auto interpolatedColor = ScaleByte(outputMin.A, outputMax.A, ratio) << 24 |
ScaleByte(outputMin.R, outputMax.R, ratio) << 16 | ScaleByte(outputMin.G, outputMax.G, ratio) << 8 |
ScaleByte(outputMin.B, outputMax.B, ratio);
memcpy(&result, &interpolatedColor, sizeof(int32_t));
return result;
}

} // namespace Microsoft::ReactNative
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ class InterpolationAnimatedNode final : public ValueAnimatedNode {
virtual void OnDetachedFromNode(int64_t animatedNodeTag) override;
virtual void OnAttachToNode(int64_t animatedNodeTag) override;

bool IsColorValue() override {
return m_isColorOutput;
}

static constexpr std::string_view ExtrapolateTypeIdentity = "identity";
static constexpr std::string_view ExtrapolateTypeClamp = "clamp";
static constexpr std::string_view ExtrapolateTypeExtend = "extend";
Expand All @@ -35,11 +39,14 @@ class InterpolationAnimatedNode final : public ValueAnimatedNode {
winrt::hstring GetRightExpression(const winrt::hstring &, const winrt::hstring &rightInterpolateExpression);

double InterpolateValue(double value);
double InterpolateColor(double value);

comp::ExpressionAnimation m_rawValueAnimation{nullptr};
comp::ExpressionAnimation m_offsetAnimation{nullptr};
bool m_isColorOutput{false};
std::vector<double> m_inputRanges;
std::vector<double> m_outputRanges;
std::vector<double> m_defaultOutputRanges;
std::vector<winrt::Windows::UI::Color> m_colorOutputRanges;
std::string m_extrapolateLeft;
std::string m_extrapolateRight;

Expand All @@ -49,9 +56,12 @@ class InterpolationAnimatedNode final : public ValueAnimatedNode {

static constexpr std::string_view s_inputRangeName{"inputRange"};
static constexpr std::string_view s_outputRangeName{"outputRange"};
static constexpr std::string_view s_outputTypeName{"outputType"};
static constexpr std::string_view s_extrapolateLeftName{"extrapolateLeft"};
static constexpr std::string_view s_extrapolateRightName{"extrapolateRight"};

static constexpr std::string_view s_colorOutputType{"color"};

static constexpr std::wstring_view s_parentPropsName{L"p"};
static constexpr std::wstring_view s_inputName{L"i"};
static constexpr std::wstring_view s_outputName{L"o"};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ void PropsAnimatedNode::UpdateView() {
for (const auto &styleEntry : styleNode->GetMapping()) {
MakeAnimation(styleEntry.second, styleEntry.first);
}
} else if (valueNode->IsColorValue()) {
const auto value = valueNode->Value();
int32_t color;
memcpy(&color, &value, sizeof(int32_t));
m_props[entry.first] = color;
} else {
styleNode->CollectViewUpdates(m_props);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,14 @@ void StyleAnimatedNode::CollectViewUpdates(winrt::Microsoft::ReactNative::JSValu
if (const auto transformNode = manager->GetTransformAnimatedNode(propMapping.second)) {
transformNode->CollectViewUpdates(propsMap);
} else if (const auto node = manager->GetValueAnimatedNode(propMapping.second)) {
propsMap[propMapping.first] = node->Value();
if (node->IsColorValue()) {
const auto value = node->Value();
int32_t color;
memcpy(&color, &value, sizeof(int32_t));
propsMap[propMapping.first] = color;
} else {
propsMap[propMapping.first] = node->Value();
}
} else if (const auto node = manager->GetColorAnimatedNode(propMapping.second)) {
propsMap[propMapping.first] = node->GetColor();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ class ValueAnimatedNode : public AnimatedNode {
void OnValueUpdate();
void ValueListener(const ValueListenerCallback &callback);

virtual bool IsColorValue() {
return false;
}

comp::CompositionPropertySet PropertySet() {
return m_propertySet;
};
Expand Down

0 comments on commit 20dcae8

Please sign in to comment.