Skip to content
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

Adds color animated nodes to tick-driven animations #12886

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Adds color value interpolation handling to tick-driven NativeAnimated…
… 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 29, 2024
commit 2c729532c8c8a597eb5edf9aa4010bccda038825
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 @@ -126,6 +126,11 @@ void PropsAnimatedNode::UpdateView() {
if (facade != FacadeType::None) {
MakeAnimation(entry.second, facade);
}
} else if (valueNode->IsColorValue()) {
const auto value = valueNode->Value();
int32_t color;
memcpy(&color, &value, sizeof(int32_t));
m_props[entry.first] = color;
} else {
m_props[entry.first] = valueNode->Value();
}
Expand All @@ -135,6 +140,11 @@ void PropsAnimatedNode::UpdateView() {
if (facade != FacadeType::None) {
MakeAnimation(entry.second, facade);
}
} else if (valueNode->IsColorValue()) {
const auto value = valueNode->Value();
int32_t color;
memcpy(&color, &value, sizeof(int32_t));
m_props[entry.first] = color;
} else {
m_props[entry.first] = colorNode->GetColor();
}
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