From 10005564c5403bc3c8190642659c89f0d0356d9c Mon Sep 17 00:00:00 2001 From: AmaiKinono Date: Sun, 22 Sep 2024 03:25:14 +0800 Subject: [PATCH] RCSS: support hsl and hsla colors --- Source/Core/PropertyParserColour.cpp | 113 ++++++++++++++++++++------ Tests/Source/UnitTests/Properties.cpp | 9 ++ 2 files changed, 99 insertions(+), 23 deletions(-) diff --git a/Source/Core/PropertyParserColour.cpp b/Source/Core/PropertyParserColour.cpp index c0bc8f7d8..03b842d88 100644 --- a/Source/Core/PropertyParserColour.cpp +++ b/Source/Core/PropertyParserColour.cpp @@ -27,10 +27,40 @@ */ #include "PropertyParserColour.h" +#include +#include #include namespace Rml { +// Helper function for hsl->rgb conversion. +static float HSL_f(float h, float s, float l, float n) +{ + float k = std::fmod((n + h * (1.0f / 30.0f)), 12.0f); + float a = s * std::min(l, 1.0f - l); + return l - a * std::max(-1.0f, std::min({k - 3.0f, 9.0f - k, 1.0f})); +} + +// Ref: https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB_alternative +static void HSLAToRGBA(Array& vals) +{ + if (vals[1] == 0.0f) + { + vals[0] = vals[1] = vals[2]; + } + else + { + float h = std::fmod(vals[0], 360.0f); + if (h < 0) + h += 360.0f; + float s = vals[1]; + float l = vals[2]; + vals[0] = HSL_f(h, s, l, 0.0f); + vals[1] = HSL_f(h, s, l, 8.0f); + vals[2] = HSL_f(h, s, l, 4.0f); + } +} + const PropertyParserColour::ColourMap PropertyParserColour::html_colours = { {"black", Colourb(0, 0, 0)}, {"silver", Colourb(192, 192, 192)}, @@ -116,7 +146,7 @@ bool PropertyParserColour::ParseColour(Colourb& colour, const String& value) colour[i] = (byte)(tens * 16 + ones); } } - else if (value.substr(0, 3) == "rgb") + else if (value.substr(0, 3) == "rgb" || value.substr(0, 3) == "hsl") { StringList values; values.reserve(4); @@ -129,33 +159,70 @@ bool PropertyParserColour::ParseColour(Colourb& colour, const String& value) StringUtilities::ExpandString(values, value.substr(begin_values, value.rfind(')') - begin_values), ','); - // Check if we're parsing an 'rgba' or 'rgb' colour declaration. - if (value.size() > 3 && value[3] == 'a') + if (value.substr(0, 3) == "rgb") { - if (values.size() != 4) - return false; + // Check if we're parsing an 'rgba' or 'rgb' colour declaration. + if (value.size() > 3 && value[3] == 'a') + { + if (values.size() != 4) + return false; + } + else + { + if (values.size() != 3) + return false; + + values.push_back("255"); + } + + // Parse the RGBA values. + for (int i = 0; i < 4; ++i) + { + int component; + + // We're parsing a percentage value. + if (values[i].size() > 0 && values[i][values[i].size() - 1] == '%') + component = int((float)atof(values[i].substr(0, values[i].size() - 1).c_str()) * (255.0f / 100.0f)); + // We're parsing a 0 -> 255 integer value. + else + component = atoi(values[i].c_str()); + + colour[i] = (byte)(Math::Clamp(component, 0, 255)); + } } else { - if (values.size() != 3) - return false; - - values.push_back("255"); - } - - // Parse the three RGB values. - for (int i = 0; i < 4; ++i) - { - int component; - - // We're parsing a percentage value. - if (values[i].size() > 0 && values[i][values[i].size() - 1] == '%') - component = int((float)atof(values[i].substr(0, values[i].size() - 1).c_str()) * (255.0f / 100.0f)); - // We're parsing a 0 -> 255 integer value. + // Check if we're parsing an 'hsla' or 'hsl' colour declaration. + if (value.size() > 3 && value[3] == 'a') + { + if (values.size() != 4) + return false; + } else - component = atoi(values[i].c_str()); - - colour[i] = (byte)(Math::Clamp(component, 0, 255)); + { + if (values.size() != 3) + return false; + + values.push_back("1.0"); + } + + // Parse the HSLA values. + Array vals; + // H is a number in degrees, A is a number between 0.0 and 1.0. + for (int i : {0, 3}) + vals[i] = (float)atof(values[i].c_str()); + // S and L are percentage values. + for (int i : {1, 2}) + if (values[i].size() > 0 && values[i][values[i].size() - 1] == '%') + vals[i] = (float)atof(values[i].substr(0, values[i].size() - 1).c_str()) * (1.0f / 100.0f); + else + return false; + + HSLAToRGBA(vals); + for (int i = 0; i < 4; ++i) + { + colour[i] = (byte)(Math::Clamp((int)(vals[i] * 255.0f), 0, 255)); + } } } else diff --git a/Tests/Source/UnitTests/Properties.cpp b/Tests/Source/UnitTests/Properties.cpp index c563d0ce1..4dce5cbaf 100644 --- a/Tests/Source/UnitTests/Properties.cpp +++ b/Tests/Source/UnitTests/Properties.cpp @@ -142,6 +142,15 @@ TEST_CASE("Properties") ColorStop{ColourbPremultiplied(0, 150, 0, 150), NumericValue{10.f, Unit::DP}}, }, }, + { + "hsl(10000, 0%, 50%), hsl(240, 100%, 50%) 50%, hsla(-240, 100%, 50%, 0.5) 10dp", + "#7f7f7f, #0000ff 50%, #00ff007f 10dp", + { + ColorStop{ColourbPremultiplied(127, 127, 127), NumericValue{}}, + ColorStop{ColourbPremultiplied(0, 0, 255), NumericValue{50.f, Unit::PERCENT}}, + ColorStop{ColourbPremultiplied(0, 127, 0, 127), NumericValue{10.f, Unit::DP}}, + }, + }, { "red 50px 20%, blue 10in", "#ff0000 50px, #ff0000 20%, #0000ff 10in",