Skip to content

Color mix support #193

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
22 changes: 22 additions & 0 deletions svgnative/src/SVGDocumentImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1167,6 +1167,8 @@ static void ResolveColorImpl(const ColorMap& colorMap, const ColorImpl& colorImp
}
else if (SVGNative::holds_alternative<Color>(colorImpl))
color = SVGNative::get<Color>(colorImpl);
else if (SVGNative::holds_alternative<ColorMixPtr>(colorImpl))
color = SVGNative::get<ColorMixPtr>(colorImpl)->BlendedColor(colorMap);
else
// Can only be reached if fallback color value of var() is currentColor.
color = Color{{0.0f, 0.0f, 0.0f, 1.0f}};
Expand Down Expand Up @@ -1211,10 +1213,30 @@ static void ResolvePaintImpl(const ColorMap& colorMap, const PaintImpl& internal
else if (SVGNative::holds_alternative<ColorKeys>(internalPaint))
// currentColor is the only possible enum value for now.
paint = currentColor;
else if (SVGNative::holds_alternative<ColorMixPtr>(internalPaint))
paint = SVGNative::get<ColorMixPtr>(internalPaint)->BlendedColor(colorMap);
else
SVG_ASSERT_MSG(false, "Unhandled PaintImpl type");
}

constexpr float BlendChannel(float from, float to, float progress)
{
return from + (to - from) * progress;
}

Color ColorMix::BlendedColor(const ColorMap& colorMap) const
{
Color resolvedColor1;
ResolveColorImpl(colorMap, color1, resolvedColor1);
Color resolvedColor2;
ResolveColorImpl(colorMap, color2, resolvedColor2);
resolvedColor1[0] = BlendChannel(resolvedColor1[0], resolvedColor2[0], blendProgress);
resolvedColor1[1] = BlendChannel(resolvedColor1[1], resolvedColor2[1], blendProgress);
resolvedColor1[2] = BlendChannel(resolvedColor1[2], resolvedColor2[2], blendProgress);
resolvedColor1[3] = BlendChannel(resolvedColor1[3], resolvedColor2[3], blendProgress);
return resolvedColor1;
}

void SVGDocumentImpl::TraverseTree(const ColorMap& colorMap, const Element& element)
{
// Inheritance doesn't work for override styles. Since override styles
Expand Down
16 changes: 14 additions & 2 deletions svgnative/src/SVGDocumentImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,22 @@ enum class ColorKeys
kCurrentColor
};

struct ColorMix;
using ColorMixPtr = std::shared_ptr<ColorMix>;

using Variable = std::pair<std::string, Color>;
using ColorImpl = SVGNative::variant<Color, Variable, ColorKeys>;
using PaintImpl = SVGNative::variant<Color, GradientImpl, Variable, ColorKeys>;
using ColorImpl = SVGNative::variant<Color, Variable, ColorKeys, ColorMixPtr>;
using PaintImpl = SVGNative::variant<Color, GradientImpl, Variable, ColorKeys, ColorMixPtr>;
using ColorStopImpl = std::tuple<float, ColorImpl, float>;
struct ColorMix
{
ColorImpl color1;
ColorImpl color2;
float blendProgress{};

Color BlendedColor(const ColorMap& colorMap) const;
};

#ifdef STYLE_SUPPORT
using PropertySet = StyleSheet::CssPropertySet;
#else
Expand Down
78 changes: 77 additions & 1 deletion svgnative/src/SVGStringParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1087,7 +1087,7 @@ static bool ParseColor(CharIt& pos, const CharIt& end, ColorImpl& paint, bool su
result = SVGDocumentImpl::Result::kSuccess;
return true;
}
if (char4string.compare("var(") == 0)
else if (char4string.compare("var(") == 0)
{
result = SVGDocumentImpl::Result::kInvalid;
pos += 4;
Expand Down Expand Up @@ -1122,6 +1122,82 @@ static bool ParseColor(CharIt& pos, const CharIt& end, ColorImpl& paint, bool su
result = SVGDocumentImpl::Result::kSuccess;
return true;
}
else if (char4string.compare("colo") == 0)
{
if (std::distance(pos, end) > 10 && std::string(pos + 4, pos + 10).compare("r-mix(") == 0)
{
pos += 10;
if (!SkipOptWsp(pos, end))
return false;
if (std::distance(pos, end) > 2 && *pos == 'i' && *(pos + 1) == 'n' && isWsp(*(pos + 2)))
pos += 3;
else
return false;

if (!SkipOptWsp(pos, end))
return false;
if (std::distance(pos, end) > 4 && std::string(pos, pos + 4).compare("srgb") == 0)
pos += 4;
else
return false;
if (!SkipOptWspDelimiterOptWsp(pos, end))
return false;

auto colorMixPtr = std::make_shared<ColorMix>();

auto resultColor{SVGDocumentImpl::Result::kInvalid};
if (!ParseColor(pos, end, colorMixPtr->color1, supportsCurrentColor, resultColor)
|| resultColor != SVGDocumentImpl::Result::kSuccess)
return false;
float blendProgress1{ 50.f };
bool hasBlendProgress1{};
auto tempPos = pos;
if (tempPos != end && isWsp(*tempPos) && SkipOptWsp(tempPos, end)
&& ParseFloatingPoint(tempPos, end, blendProgress1)
&& tempPos != end && *tempPos == '%')
{
pos = tempPos + 1;
hasBlendProgress1 = true;
}

if (!SkipOptWspDelimiterOptWsp(pos, end))
return false;

resultColor = SVGDocumentImpl::Result::kInvalid;
if (!ParseColor(pos, end, colorMixPtr->color2, supportsCurrentColor, resultColor)
|| resultColor != SVGDocumentImpl::Result::kSuccess)
return false;
float blendProgress2{ 50.f };
bool hasBlendProgress2{};
tempPos = pos;
if (tempPos != end && isWsp(*tempPos) && SkipOptWsp(tempPos, end)
&& ParseFloatingPoint(tempPos, end, blendProgress2)
&& tempPos != end && *tempPos == '%')
{
pos = tempPos + 1;
hasBlendProgress2 = true;
}

if (!SkipOptWsp(pos, end))
return false;
if (pos == end || *pos != ')')
return false;
pos++;

blendProgress1 /= 100.f;
blendProgress2 /= 100.f;
if (!hasBlendProgress1 && hasBlendProgress2)
blendProgress1 = 1 - blendProgress2;
else if (hasBlendProgress1 && !hasBlendProgress2)
blendProgress2 = 1 - blendProgress1;

colorMixPtr->blendProgress = blendProgress2 / (blendProgress1 + blendProgress2);

paint = colorMixPtr;
result = SVGDocumentImpl::Result::kSuccess;
return true;
}
}
}

// Parse CSS named Colors.
Expand Down
26 changes: 12 additions & 14 deletions svgnative/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
set (SOURCE_FILES
${CMAKE_CURRENT_SOURCE_DIR}/../src/Interval.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../src/Interval.h
)
add_executable(intervalTests interval-tests.cpp ${SOURCE_FILES})
target_include_directories(intervalTests PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../src")
target_link_libraries(intervalTests SVGNativeViewerLib)
target_link_libraries(intervalTests gtest_main)
add_test(NAME interval_tests COMMAND intervalTests)

set (SOURCE_FILES
${CMAKE_CURRENT_SOURCE_DIR}/../src/Interval.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../src/Rect.cpp
)
add_executable(rectangleTests rectangle-tests.cpp ${SOURCE_FILES})
target_include_directories(rectangleTests PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../src")
target_link_libraries(rectangleTests SVGNativeViewerLib)
target_link_libraries(rectangleTests gtest_main)
add_test(NAME rectangle_tests COMMAND rectangleTests)

set (TEST_FILES
interval-tests.cpp
rectangle-tests.cpp
parsing-tests.cpp
)
add_executable(unitTesting ${TEST_FILES} ${SOURCE_FILES})
target_include_directories(unitTesting PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/../src")
target_link_libraries(unitTesting SVGNativeViewerLib)
target_link_libraries(unitTesting gtest_main)
add_test(NAME interval_tests COMMAND unitTesting)
add_test(NAME rectangle_tests COMMAND unitTesting)
add_test(NAME parsing_tests COMMAND unitTesting)

# TODO: For now we just use the Skia port, but later on we should
# extend this and generalize this so that all the ports are equally
Expand Down
66 changes: 66 additions & 0 deletions svgnative/tests/parsing-tests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
Copyright 2023 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/

#include "gtest/gtest.h"
#include "gtest/gtest-spi.h"

#include <svgnative/SVGRenderer.h>
#include "SVGDocumentImpl.h"
#include "SVGStringParser.h"

using namespace SVGNative;

using BeforeAfterPath = std::pair<std::string, Color>;

class ParsingTestFixture : public ::testing::TestWithParam<BeforeAfterPath> {
};

TEST_P(ParsingTestFixture, color_mix)
{
const auto& param = GetParam();

static const ColorMap colorMap{
{ "myBlue", Color{0.f, 0.f, 1.f, 1.f}},
{ "myRed", Color{1.f, 0.f, 0.f, 1.f}},
{ "myGreen", Color{0.f, 0.5f, 0.f, 1.f}},
};

ColorImpl colorImpl;
auto parsingResult = SVGStringParser::ParseColor(param.first, colorImpl);

auto wasParsingSuccessful = parsingResult == SVGDocumentImpl::Result::kSuccess;
EXPECT_EQ(wasParsingSuccessful, true);
EXPECT_EQ(colorImpl.type() == typeid(ColorMixPtr), true);

auto blendedColor = SVGNative::get<ColorMixPtr>(colorImpl)->BlendedColor(colorMap);
auto& expectedColor = param.second;
EXPECT_FLOAT_EQ(blendedColor[0], expectedColor[0]);
EXPECT_FLOAT_EQ(blendedColor[1], expectedColor[1]);
EXPECT_FLOAT_EQ(blendedColor[2], expectedColor[2]);
EXPECT_FLOAT_EQ(blendedColor[3], expectedColor[3]);
}

INSTANTIATE_TEST_SUITE_P(ColorMixParsing, ParsingTestFixture,
::testing::ValuesIn(std::vector<BeforeAfterPath>{
{
"color-mix(in srgb, black, white 80%)",
Color{{.8f, .8f, .8f, 1.f}}
},
{
"color-mix(in srgb, black 80%, white)",
Color{{.2f, .2f, .2f, 1.f}}
},
{
"color-mix(in srgb, rgb(255, 127, 0) 80%, rgba(0, 127, 0, 1.0))",
Color{{.2f, .2f, .2f, 1.f}}
}
}));