diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/ColorAnimatedNode.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/ColorAnimatedNode.cpp index e950ff35757..0272b2498e0 100644 --- a/vnext/Microsoft.ReactNative/Modules/Animated/ColorAnimatedNode.cpp +++ b/vnext/Microsoft.ReactNative/Modules/Animated/ColorAnimatedNode.cpp @@ -4,9 +4,9 @@ #include "pch.h" #include +#include #include "ColorAnimatedNode.h" #include "NativeAnimatedNodeManager.h" -#include namespace Microsoft::ReactNative { ColorAnimatedNode::ColorAnimatedNode( diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/InterpolationAnimatedNode.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/InterpolationAnimatedNode.cpp index 96beefa0137..f086f0aa1e3 100644 --- a/vnext/Microsoft.ReactNative/Modules/Animated/InterpolationAnimatedNode.cpp +++ b/vnext/Microsoft.ReactNative/Modules/Animated/InterpolationAnimatedNode.cpp @@ -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(color.A) << 24 | static_cast(color.R) << 16 | + static_cast(color.G) << 8 | static_cast(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(std::round(scaledValue)), 0u, 255u); + return static_cast(clampedValue); +} + InterpolationAnimatedNode::InterpolationAnimatedNode( int64_t tag, const winrt::Microsoft::ReactNative::JSValueObject &config, @@ -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(); @@ -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())); + } } } } @@ -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(m_inputRanges[i])); } - for (size_t i = 0; i < m_outputRanges.size(); i++) { - animation.SetScalarParameter(s_outputName.data() + std::to_wstring(i), static_cast(m_outputRanges[i])); + for (size_t i = 0; i < m_defaultOutputRanges.size(); i++) { + animation.SetScalarParameter( + s_outputName.data() + std::to_wstring(i), static_cast(m_defaultOutputRanges[i])); } return animation; } @@ -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" : "; @@ -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 diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/InterpolationAnimatedNode.h b/vnext/Microsoft.ReactNative/Modules/Animated/InterpolationAnimatedNode.h index 84321c4ee91..6d738584dd8 100644 --- a/vnext/Microsoft.ReactNative/Modules/Animated/InterpolationAnimatedNode.h +++ b/vnext/Microsoft.ReactNative/Modules/Animated/InterpolationAnimatedNode.h @@ -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"; @@ -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 m_inputRanges; - std::vector m_outputRanges; + std::vector m_defaultOutputRanges; + std::vector m_colorOutputRanges; std::string m_extrapolateLeft; std::string m_extrapolateRight; @@ -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"}; diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/PropsAnimatedNode.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/PropsAnimatedNode.cpp index 41670c75f49..472eefcece8 100644 --- a/vnext/Microsoft.ReactNative/Modules/Animated/PropsAnimatedNode.cpp +++ b/vnext/Microsoft.ReactNative/Modules/Animated/PropsAnimatedNode.cpp @@ -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); } diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/StyleAnimatedNode.cpp b/vnext/Microsoft.ReactNative/Modules/Animated/StyleAnimatedNode.cpp index d3c195c9d7c..a2fcd02e818 100644 --- a/vnext/Microsoft.ReactNative/Modules/Animated/StyleAnimatedNode.cpp +++ b/vnext/Microsoft.ReactNative/Modules/Animated/StyleAnimatedNode.cpp @@ -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(); } diff --git a/vnext/Microsoft.ReactNative/Modules/Animated/ValueAnimatedNode.h b/vnext/Microsoft.ReactNative/Modules/Animated/ValueAnimatedNode.h index 96281f8edfe..ac0f38680dc 100644 --- a/vnext/Microsoft.ReactNative/Modules/Animated/ValueAnimatedNode.h +++ b/vnext/Microsoft.ReactNative/Modules/Animated/ValueAnimatedNode.h @@ -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; };