Skip to content
Draft
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <reanimated/CSS/common/transforms/TransformMatrix2D.h>
#include <reanimated/CSS/common/values/complex/CSSBoxShadow.h>

#include <reanimated/CSS/svg/values/SVGBrush.h>
#include <reanimated/CSS/svg/values/SVGLength.h>
#include <reanimated/CSS/svg/values/SVGStrokeDashArray.h>

Expand All @@ -31,8 +32,8 @@ namespace reanimated::css {
namespace {

// Private implementation details
const auto BLACK = CSSColor(0, 0, 0, 255);
const auto TRANSPARENT = CSSColor::Transparent;
const std::array<uint8_t, 4> BLACK = {0, 0, 0, 255};
const std::array<uint8_t, 4> TRANSPARENT = {0, 0, 0, 0};

InterpolatorFactoriesRecord mergeInterpolators(
const std::vector<InterpolatorFactoriesRecord> &maps) {
Expand Down Expand Up @@ -275,17 +276,17 @@ const InterpolatorFactoriesRecord IMAGE_INTERPOLATORS = mergeInterpolators(
// =================

const InterpolatorFactoriesRecord SVG_COLOR_INTERPOLATORS = {
{"color", value<CSSColor>(BLACK)},
{"color", value<SVGBrush>(BLACK)},
};

const InterpolatorFactoriesRecord SVG_FILL_INTERPOLATORS = {
{"fill", value<CSSColor>(BLACK)},
{"fill", value<SVGBrush>(BLACK)},
{"fillOpacity", value<CSSDouble>(1)},
{"fillRule", value<CSSInteger>(0)},
};

const InterpolatorFactoriesRecord SVG_STROKE_INTERPOLATORS = {
{"stroke", value<CSSColor>(BLACK)},
{"stroke", value<SVGBrush>(BLACK)},
{"strokeWidth", value<SVGLength>(1)},
{"strokeOpacity", value<CSSDouble>(1)},
{"strokeDasharray",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
#include <reanimated/CSS/common/values/CSSColor.h>
#include <reanimated/CSS/svg/values/SVGBrush.h>

namespace reanimated::css {

CSSColor::CSSColor()
: channels{0, 0, 0, 0}, colorType(ColorType::Transparent) {}
// CSSColorBase template implementations

CSSColor::CSSColor(ColorType colorType)
template <ColorTypeEnum TColorType, typename TDerived>
CSSColorBase<TColorType, TDerived>::CSSColorBase()
: channels{0, 0, 0, 0}, colorType(TColorType::Transparent) {}

template <ColorTypeEnum TColorType, typename TDerived>
CSSColorBase<TColorType, TDerived>::CSSColorBase(TColorType colorType)
: channels{0, 0, 0, 0}, colorType(colorType) {}

CSSColor::CSSColor(int64_t numberValue)
: channels{0, 0, 0, 0}, colorType(ColorType::Rgba) {
template <ColorTypeEnum TColorType, typename TDerived>
CSSColorBase<TColorType, TDerived>::CSSColorBase(int64_t numberValue)
: channels{0, 0, 0, 0}, colorType(TColorType::Rgba) {
uint32_t color;
// On Android, colors are represented as signed 32-bit integers. In JS, we use
// a bitwise operation (normalizedColor = normalizedColor | 0x0) to ensure the
Expand All @@ -26,128 +32,132 @@ CSSColor::CSSColor(int64_t numberValue)
channels[1] = (color >> 8) & 0xFF; // Green
channels[2] = color & 0xFF; // Blue
channels[3] = (color >> 24) & 0xFF; // Alpha
colorType = ColorType::Rgba;
colorType = TColorType::Rgba;
}

CSSColor::CSSColor(const std::string &colorString)
: channels{0, 0, 0, 0}, colorType(ColorType::Transparent) {
if (colorString == "transparent") {
colorType = ColorType::Transparent;
} else if (colorString == "currentColor") {
colorType = ColorType::CurrentColor;
} else {
throw std::invalid_argument(
"[Reanimated] CSSColor: Invalid string value: " + colorString);
}
}

CSSColor::CSSColor(const uint8_t r, const uint8_t g, const uint8_t b)
: channels{r, g, b, 255}, colorType(ColorType::Rgba) {}

CSSColor::CSSColor(
template <ColorTypeEnum TColorType, typename TDerived>
CSSColorBase<TColorType, TDerived>::CSSColorBase(
const uint8_t r,
const uint8_t g,
const uint8_t b,
const uint8_t a)
: channels{r, g, b, a}, colorType(ColorType::Rgba) {}
: channels{r, g, b, a}, colorType(TColorType::Rgba) {}

template <ColorTypeEnum TColorType, typename TDerived>
CSSColorBase<TColorType, TDerived>::CSSColorBase(ColorChannels colorChannels)
: channels{std::move(colorChannels)}, colorType(TColorType::Rgba) {}

template <ColorTypeEnum TColorType, typename TDerived>
bool CSSColorBase<TColorType, TDerived>::canConstruct(
jsi::Runtime &rt,
const jsi::Value &jsiValue) {
if (!jsiValue.isObject()) {
return false;
}
const auto &jsiObject = jsiValue.asObject(rt);
return jsiObject.hasProperty(rt, "colorType");
}

CSSColor::CSSColor(const ColorChannels &colorChannels)
: channels{colorChannels[0], colorChannels[1], colorChannels[2], colorChannels[3]},
colorType(ColorType::Rgba) {}
template <ColorTypeEnum TColorType, typename TDerived>
bool CSSColorBase<TColorType, TDerived>::canConstruct(
const folly::dynamic &value) {
return value.isObject() && value.count("colorType") > 0;
}

CSSColor::CSSColor(jsi::Runtime &rt, const jsi::Value &jsiValue)
: channels{0, 0, 0, 0}, colorType(ColorType::Transparent) {
if (jsiValue.isNumber()) {
*this = CSSColor(jsiValue.getNumber());
} else if (jsiValue.isString()) {
*this = CSSColor(jsiValue.getString(rt).utf8(rt));
} else {
*this = Transparent;
template <ColorTypeEnum TColorType, typename TDerived>
TDerived CSSColorBase<TColorType, TDerived>::interpolate(
double progress,
const TDerived &to) const {
ColorChannels fromChannels = channels;
ColorChannels toChannels = to.channels;

if (colorType == TColorType::Transparent) {
fromChannels = {toChannels[0], toChannels[1], toChannels[2], 0};
} else if (to.colorType == TColorType::Transparent) {
toChannels = {fromChannels[0], fromChannels[1], fromChannels[2], 0};
}
}

CSSColor::CSSColor(const folly::dynamic &value)
: channels{0, 0, 0, 0}, colorType(ColorType::Transparent) {
if (value.isNumber()) {
*this = CSSColor(value.asInt());
} else if (value.isString()) {
*this = CSSColor(value.getString());
} else {
*this = Transparent;
ColorChannels resultChannels;
for (size_t i = 0; i < 4; ++i) {
const auto &from = fromChannels[i];
const auto &to = toChannels[i];
// Cast one of operands to double to avoid unsigned int subtraction overflow
// (when from > to)
const double interpolated =
(static_cast<double>(to) - from) * progress + from;
resultChannels[i] =
static_cast<uint8_t>(std::round(std::clamp(interpolated, 0.0, 255.0)));
}
}

bool CSSColor::canConstruct(jsi::Runtime &rt, const jsi::Value &jsiValue) {
return jsiValue.isNumber() || jsiValue.isUndefined() ||
(jsiValue.isString() &&
isValidColorString(jsiValue.getString(rt).utf8(rt)));
return TDerived(std::move(resultChannels));
}

bool CSSColor::canConstruct(const folly::dynamic &value) {
return value.isNumber() || value.empty() ||
(value.isString() && isValidColorString(value.getString()));
template <ColorTypeEnum TColorType, typename TDerived>
bool CSSColorBase<TColorType, TDerived>::operator==(
const TDerived &other) const {
return colorType == other.colorType && channels == other.channels;
}

folly::dynamic CSSColor::toDynamic() const {
if (colorType == ColorType::Transparent) {
return 0x00000000;
template <ColorTypeEnum TColorType, typename TDerived>
std::pair<TColorType, jsi::Value>
CSSColorBase<TColorType, TDerived>::parseJSIValue(
jsi::Runtime &rt,
const jsi::Value &jsiValue) const {
const auto jsiObject = jsiValue.asObject(rt);
const auto colorType = static_cast<TColorType>(
jsiObject.getProperty(rt, "colorType").asNumber());

if (colorType == TColorType::Rgba) {
return {colorType, jsiObject.getProperty(rt, "value")};
}
return (channels[3] << 24) | (channels[0] << 16) | (channels[1] << 8) |
channels[2];
return {colorType, jsi::Value::undefined()};
}

std::string CSSColor::toString() const {
if (colorType == ColorType::Rgba) {
return "rgba(" + std::to_string(channels[0]) + "," +
std::to_string(channels[1]) + "," + std::to_string(channels[2]) + "," +
std::to_string(channels[3]) + ")";
}
if (colorType == ColorType::CurrentColor) {
return "currentColor";
template <ColorTypeEnum TColorType, typename TDerived>
std::pair<TColorType, folly::dynamic>
CSSColorBase<TColorType, TDerived>::parseDynamicValue(
const folly::dynamic &value) const {
const auto colorType = static_cast<TColorType>(value.at("colorType").asInt());

if (colorType == TColorType::Rgba) {
return {colorType, value.at("value")};
}
return "transparent";
return {colorType, folly::dynamic::object()};
}

CSSColor CSSColor::interpolate(const double progress, const CSSColor &to)
const {
if ((to.colorType == ColorType::Transparent &&
colorType == ColorType::Transparent) ||
colorType == ColorType::CurrentColor ||
to.colorType == ColorType::CurrentColor) {
return progress < 0.5 ? *this : to;
}
// CSSColor implementations

ColorChannels fromChannels = channels;
ColorChannels toChannels = to.channels;
if (colorType == ColorType::Transparent) {
fromChannels = {toChannels[0], toChannels[1], toChannels[2], 0};
} else if (to.colorType == ColorType::Transparent) {
toChannels = {fromChannels[0], fromChannels[1], fromChannels[2], 0};
CSSColor::CSSColor(jsi::Runtime &rt, const jsi::Value &jsiValue)
: CSSColorBase<CSSColorType, CSSColor>(CSSColorType::Transparent) {
const auto [colorType, value] = parseJSIValue(rt, jsiValue);
if (colorType == CSSColorType::Rgba) {
*this = CSSColor(value.asNumber());
}
}

ColorChannels resultChannels;
for (size_t i = 0; i < 4; ++i) {
resultChannels[i] =
interpolateChannel(fromChannels[i], toChannels[i], progress);
CSSColor::CSSColor(const folly::dynamic &dynamicValue)
: CSSColorBase<CSSColorType, CSSColor>(CSSColorType::Transparent) {
const auto [colorType, value] = parseDynamicValue(dynamicValue);
if (colorType == CSSColorType::Rgba) {
*this = CSSColor(value.asInt());
}

return CSSColor(resultChannels);
}

uint8_t CSSColor::interpolateChannel(
const uint8_t from,
const uint8_t to,
const double progress) {
// Cast one of operands to double to avoid unsigned int subtraction overflow
// (when from > to)
double interpolated = (static_cast<double>(to) - from) * progress + from;
return static_cast<uint8_t>(std::round(std::clamp(interpolated, 0.0, 255.0)));
folly::dynamic CSSColor::toDynamic() const {
if (colorType == CSSColorType::Transparent) {
return 0;
}
return (channels[3] << 24) | (channels[0] << 16) | (channels[1] << 8) |
channels[2];
}

bool CSSColor::operator==(const CSSColor &other) const {
return colorType == other.colorType && channels[0] == other.channels[0] &&
channels[1] == other.channels[1] && channels[2] == other.channels[2] &&
channels[3] == other.channels[3];
std::string CSSColor::toString() const {
if (colorType == CSSColorType::Rgba) {
return "rgba(" + std::to_string(channels[0]) + "," +
std::to_string(channels[1]) + "," + std::to_string(channels[2]) + "," +
std::to_string(channels[3]) + ")";
}
return "transparent";
}

#ifndef NDEBUG
Expand All @@ -159,8 +169,8 @@ std::ostream &operator<<(std::ostream &os, const CSSColor &colorValue) {

#endif // NDEBUG

bool CSSColor::isValidColorString(const std::string &colorString) {
return colorString == "transparent" || colorString == "currentColor";
}
// Explicit template instantiation
template struct CSSColorBase<CSSColorType, CSSColor>;
template struct CSSColorBase<SVGBrushType, SVGBrush>;

} // namespace reanimated::css
Original file line number Diff line number Diff line change
Expand Up @@ -4,52 +4,64 @@
#include <reanimated/CSS/common/values/CSSValue.h>

#include <string>
#include <utility>

namespace reanimated::css {

enum class ColorType {
Rgba,
Transparent,
CurrentColor, // for SVG
// Base class with common color value functionality

template <typename T>
concept ColorTypeEnum = std::is_enum_v<T> && requires {
T::Rgba;
T::Transparent;
};

struct CSSColor : public CSSSimpleValue<CSSColor> {
template <ColorTypeEnum TColorType, typename TDerived>
struct CSSColorBase : public CSSSimpleValue<TDerived> {
ColorChannels channels;
ColorType colorType;
TColorType colorType;

CSSColorBase();
explicit CSSColorBase(TColorType colorType);
explicit CSSColorBase(int64_t numberValue);

explicit CSSColorBase(uint8_t r, uint8_t g, uint8_t b, uint8_t a);
explicit CSSColorBase(ColorChannels colorChannels);

static const CSSColor Transparent;
static bool canConstruct(jsi::Runtime &rt, const jsi::Value &jsiValue);
static bool canConstruct(const folly::dynamic &value);

CSSColor();
explicit CSSColor(ColorType colorType);
explicit CSSColor(int64_t numberValue);
explicit CSSColor(const std::string &colorString);
TDerived interpolate(double progress, const TDerived &to) const override;

explicit CSSColor(uint8_t r, uint8_t g, uint8_t b);
explicit CSSColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a);
explicit CSSColor(const ColorChannels &colorChannels);
bool operator==(const TDerived &other) const;

protected:
virtual std::pair<TColorType, jsi::Value> parseJSIValue(
jsi::Runtime &rt,
const jsi::Value &jsiValue) const;
virtual std::pair<TColorType, folly::dynamic> parseDynamicValue(
const folly::dynamic &value) const;
};

enum class CSSColorType {
Rgba = 0,
Transparent = 1,
};

struct CSSColor : public CSSColorBase<CSSColorType, CSSColor> {
using CSSColorBase<CSSColorType, CSSColor>::CSSColorBase;
using CSSColorBase<CSSColorType, CSSColor>::canConstruct;
using CSSColorBase<CSSColorType, CSSColor>::operator==;

explicit CSSColor(jsi::Runtime &rt, const jsi::Value &jsiValue);
explicit CSSColor(const folly::dynamic &value);

static bool canConstruct(jsi::Runtime &rt, const jsi::Value &jsiValue);
static bool canConstruct(const folly::dynamic &value);

folly::dynamic toDynamic() const override;
std::string toString() const override;
CSSColor interpolate(double progress, const CSSColor &to) const override;

static uint8_t interpolateChannel(uint8_t from, uint8_t to, double progress);

bool operator==(const CSSColor &other) const;

#ifndef NDEBUG
friend std::ostream &operator<<(std::ostream &os, const CSSColor &colorValue);
#endif // NDEBUG

private:
static bool isValidColorString(const std::string &colorString);
};

inline const CSSColor CSSColor::Transparent(ColorType::Transparent);

} // namespace reanimated::css
Loading
Loading