Browse Source

RCSS: Support hsl and hsla colors (#674)

AmaiKinono 1 year ago
parent
commit
025b05df24
2 changed files with 99 additions and 23 deletions
  1. 90 23
      Source/Core/PropertyParserColour.cpp
  2. 9 0
      Tests/Source/UnitTests/Properties.cpp

+ 90 - 23
Source/Core/PropertyParserColour.cpp

@@ -27,10 +27,40 @@
  */
 
 #include "PropertyParserColour.h"
+#include <algorithm>
+#include <cmath>
 #include <string.h>
 
 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<float, 4>& 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<float, 4> 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

+ 9 - 0
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",