Merge pull request #40868 from aaronfranke/color-html-rgba

Change Color HTML conversion from ARGB to RGBA
This commit is contained in:
Rémi Verschelde 2020-09-01 10:44:36 +02:00 committed by GitHub
commit eac32f082a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 129 additions and 172 deletions

View File

@ -261,33 +261,21 @@ Color Color::from_rgbe9995(uint32_t p_rgbe) {
return Color(rd, gd, bd, 1.0f);
}
static float _parse_col(const String &p_str, int p_ofs) {
int ig = 0;
static int _parse_col4(const String &p_str, int p_ofs) {
char character = p_str[p_ofs];
for (int i = 0; i < 2; i++) {
int c = p_str[i + p_ofs];
int v = 0;
if (c >= '0' && c <= '9') {
v = c - '0';
} else if (c >= 'a' && c <= 'f') {
v = c - 'a';
v += 10;
} else if (c >= 'A' && c <= 'F') {
v = c - 'A';
v += 10;
} else {
return -1;
}
if (i == 0) {
ig += v * 16;
} else {
ig += v;
}
if (character >= '0' && character <= '9') {
return character - '0';
} else if (character >= 'a' && character <= 'f') {
return character + (10 - 'a');
} else if (character >= 'A' && character <= 'F') {
return character + (10 - 'A');
}
return -1;
}
return ig;
static int _parse_col8(const String &p_str, int p_ofs) {
return _parse_col4(p_str, p_ofs) * 16 + _parse_col4(p_str, p_ofs + 1);
}
Color Color::inverted() const {
@ -302,49 +290,54 @@ Color Color::contrasted() const {
return c;
}
Color Color::html(const String &p_color) {
String color = p_color;
Color Color::html(const String &p_rgba) {
String color = p_rgba;
if (color.length() == 0) {
return Color();
}
if (color[0] == '#') {
color = color.substr(1, color.length() - 1);
}
if (color.length() == 3 || color.length() == 4) {
String exp_color;
for (int i = 0; i < color.length(); i++) {
exp_color += color[i];
exp_color += color[i];
}
color = exp_color;
color = color.substr(1);
}
// If enabled, use 1 hex digit per channel instead of 2.
// Other sizes aren't in the HTML/CSS spec but we could add them if desired.
bool is_shorthand = color.length() < 5;
bool alpha = false;
if (color.length() == 8) {
alpha = true;
} else if (color.length() == 6) {
alpha = false;
} else if (color.length() == 4) {
alpha = true;
} else if (color.length() == 3) {
alpha = false;
} else {
ERR_FAIL_V_MSG(Color(), "Invalid color code: " + p_color + ".");
ERR_FAIL_V_MSG(Color(), "Invalid color code: " + p_rgba + ".");
}
int a = 255;
if (alpha) {
a = _parse_col(color, 0);
ERR_FAIL_COND_V_MSG(a < 0, Color(), "Invalid color code: " + p_color + ".");
float r, g, b, a = 1.0;
if (is_shorthand) {
r = _parse_col4(color, 0) / 15.0;
g = _parse_col4(color, 1) / 15.0;
b = _parse_col4(color, 2) / 15.0;
if (alpha) {
a = _parse_col4(color, 3) / 15.0;
}
} else {
r = _parse_col8(color, 0) / 255.0;
g = _parse_col8(color, 2) / 255.0;
b = _parse_col8(color, 4) / 255.0;
if (alpha) {
a = _parse_col8(color, 6) / 255.0;
}
}
ERR_FAIL_COND_V_MSG(r < 0, Color(), "Invalid color code: " + p_rgba + ".");
ERR_FAIL_COND_V_MSG(g < 0, Color(), "Invalid color code: " + p_rgba + ".");
ERR_FAIL_COND_V_MSG(b < 0, Color(), "Invalid color code: " + p_rgba + ".");
ERR_FAIL_COND_V_MSG(a < 0, Color(), "Invalid color code: " + p_rgba + ".");
int from = alpha ? 2 : 0;
int r = _parse_col(color, from + 0);
ERR_FAIL_COND_V_MSG(r < 0, Color(), "Invalid color code: " + p_color + ".");
int g = _parse_col(color, from + 2);
ERR_FAIL_COND_V_MSG(g < 0, Color(), "Invalid color code: " + p_color + ".");
int b = _parse_col(color, from + 4);
ERR_FAIL_COND_V_MSG(b < 0, Color(), "Invalid color code: " + p_color + ".");
return Color(r / 255.0, g / 255.0, b / 255.0, a / 255.0);
return Color(r, g, b, a);
}
bool Color::html_is_valid(const String &p_color) {
@ -354,41 +347,22 @@ bool Color::html_is_valid(const String &p_color) {
return false;
}
if (color[0] == '#') {
color = color.substr(1, color.length() - 1);
color = color.substr(1);
}
bool alpha = false;
if (color.length() == 8) {
alpha = true;
} else if (color.length() == 6) {
alpha = false;
} else {
// Check if the amount of hex digits is valid.
int len = color.length();
if (!(len == 3 || len == 4 || len == 6 || len == 8)) {
return false;
}
if (alpha) {
int a = _parse_col(color, 0);
if (a < 0) {
// Check if each hex digit is valid.
for (int i = 0; i < len; i++) {
if (_parse_col4(color, i) == -1) {
return false;
}
}
int from = alpha ? 2 : 0;
int r = _parse_col(color, from + 0);
if (r < 0) {
return false;
}
int g = _parse_col(color, from + 2);
if (g < 0) {
return false;
}
int b = _parse_col(color, from + 4);
if (b < 0) {
return false;
}
return true;
}
@ -438,7 +412,7 @@ String Color::to_html(bool p_alpha) const {
txt += _to_hex(g);
txt += _to_hex(b);
if (p_alpha) {
txt = _to_hex(a) + txt;
txt += _to_hex(a);
}
return txt;
}

View File

@ -185,7 +185,7 @@ struct Color {
static Color hex(uint32_t p_hex);
static Color hex64(uint64_t p_hex);
static Color html(const String &p_color);
static Color html(const String &p_rgba);
static bool html_is_valid(const String &p_color);
static Color named(const String &p_name);
String to_html(bool p_alpha = true) const;

View File

@ -18,13 +18,21 @@
<argument index="0" name="from" type="String">
</argument>
<description>
Constructs a color from an HTML hexadecimal color string in ARGB or RGB format. See also [method @GDScript.ColorN].
Constructs a color from an HTML hexadecimal color string in RGB or RGBA format. See also [method @GDScript.ColorN].
[codeblock]
# Each of the following creates the same color RGBA(178, 217, 10, 255).
var c1 = Color("#ffb2d90a") # ARGB format with "#".
var c2 = Color("ffb2d90a") # ARGB format.
var c3 = Color("#b2d90a") # RGB format with "#".
var c4 = Color("b2d90a") # RGB format.
var c1 = Color("#b2d90aff") # RGBA format with "#".
var c2 = Color("b2d90aff") # RGBA format.
[/codeblock]
You can also use the "web color" short-hand form by only using 3 or 4 digits.
[codeblock]
# Each of the following creates the same color RGBA(17, 34, 51, 255).
var c3 = Color("#123") # RGB format with "#".
var c4 = Color("123") # RGB format.
var c1 = Color("#123f") # RGBA format with "#".
var c2 = Color("123f") # RGBA format.
[/codeblock]
</description>
</method>
@ -243,11 +251,11 @@
<argument index="0" name="with_alpha" type="bool" default="true">
</argument>
<description>
Returns the color's HTML hexadecimal color string in ARGB format (ex: [code]ff34f822[/code]).
Setting [code]with_alpha[/code] to [code]false[/code] excludes alpha from the hexadecimal string.
Returns the color's HTML hexadecimal color string in RGBA format (ex: [code]ff34f822[/code]).
Setting [code]with_alpha[/code] to [code]false[/code] excludes alpha from the hexadecimal string (and uses RGB instead of RGBA format).
[codeblock]
var c = Color(1, 1, 1, 0.5)
var s1 = c.to_html() # Returns "7fffffff"
var s1 = c.to_html() # Returns "ffffff7f"
var s2 = c.to_html(false) # Returns "ffffff"
[/codeblock]
</description>

View File

@ -206,8 +206,8 @@ void ThemeEditor::_save_template_cbk(String fname) {
file->store_line("; [value] examples:");
file->store_line("; ");
file->store_line("; Type.item = 6 ; numeric constant. ");
file->store_line("; Type.item = #FF00FF ; HTML color ");
file->store_line("; Type.item = #55FF00FF ; HTML color with alpha 55.");
file->store_line("; Type.item = #FF00FF ; HTML color (magenta).");
file->store_line("; Type.item = #FF00FF55 ; HTML color (magenta with alpha 0x55).");
file->store_line("; Type.item = icon(image.png) ; icon in a png file (relative to theme file).");
file->store_line("; Type.item = font(font.xres) ; font in a resource (relative to theme file).");
file->store_line("; Type.item = sbox(stylebox.xres) ; stylebox in a resource (relative to theme file).");

View File

@ -565,6 +565,9 @@ namespace Godot
rgba = rgba.Substring(1);
}
// If enabled, use 1 hex digit per channel instead of 2.
// Other sizes aren't in the HTML/CSS spec but we could add them if desired.
bool isShorthand = rgba.Length < 5;
bool alpha;
if (rgba.Length == 8)
@ -575,47 +578,60 @@ namespace Godot
{
alpha = false;
}
else if (rgba.Length == 4)
{
alpha = true;
}
else if (rgba.Length == 3)
{
alpha = false;
}
else
{
throw new ArgumentOutOfRangeException("Invalid color code. Length is " + rgba.Length + " but a length of 6 or 8 is expected: " + rgba);
}
if (alpha)
a = 1.0f;
if (isShorthand)
{
a = ParseCol8(rgba, 6) / 255f;
if (a < 0)
r = ParseCol4(rgba, 0) / 15f;
g = ParseCol4(rgba, 1) / 15f;
b = ParseCol4(rgba, 2) / 15f;
if (alpha)
{
throw new ArgumentOutOfRangeException("Invalid color code. Alpha part is not valid hexadecimal: " + rgba);
a = ParseCol4(rgba, 3) / 15f;
}
}
else
{
a = 1.0f;
r = ParseCol8(rgba, 0) / 255f;
g = ParseCol8(rgba, 2) / 255f;
b = ParseCol8(rgba, 4) / 255f;
if (alpha)
{
a = ParseCol8(rgba, 6) / 255f;
}
}
int from = alpha ? 2 : 0;
r = ParseCol8(rgba, 0) / 255f;
if (r < 0)
{
throw new ArgumentOutOfRangeException("Invalid color code. Red part is not valid hexadecimal: " + rgba);
}
g = ParseCol8(rgba, 2) / 255f;
if (g < 0)
{
throw new ArgumentOutOfRangeException("Invalid color code. Green part is not valid hexadecimal: " + rgba);
}
b = ParseCol8(rgba, 4) / 255f;
if (b < 0)
{
throw new ArgumentOutOfRangeException("Invalid color code. Blue part is not valid hexadecimal: " + rgba);
}
if (a < 0)
{
throw new ArgumentOutOfRangeException("Invalid color code. Alpha part is not valid hexadecimal: " + rgba);
}
}
/// <summary>
@ -751,45 +767,28 @@ namespace Godot
value = max;
}
private static int ParseCol4(string str, int ofs)
{
char character = str[ofs];
if (character >= '0' && character <= '9')
{
return character - '0';
}
else if (character >= 'a' && character <= 'f')
{
return character + (10 - 'a');
}
else if (character >= 'A' && character <= 'F')
{
return character + (10 - 'A');
}
return -1;
}
private static int ParseCol8(string str, int ofs)
{
int ig = 0;
for (int i = 0; i < 2; i++)
{
int c = str[i + ofs];
int v;
if (c >= '0' && c <= '9')
{
v = c - '0';
}
else if (c >= 'a' && c <= 'f')
{
v = c - 'a';
v += 10;
}
else if (c >= 'A' && c <= 'F')
{
v = c - 'A';
v += 10;
}
else
{
return -1;
}
if (i == 0)
{
ig += v * 16;
}
else
{
ig += v;
}
}
return ig;
return ParseCol4(str, ofs) * 16 + ParseCol4(str, ofs + 1);
}
private String ToHex32(float val)
@ -828,46 +827,24 @@ namespace Godot
if (color[0] == '#')
{
color = color.Substring(1, color.Length - 1);
color = color.Substring(1);
}
bool alpha;
switch (color.Length)
// Check if the amount of hex digits is valid.
int len = color.Length;
if (!(len == 3 || len == 4 || len == 6 || len == 8))
{
case 8:
alpha = true;
break;
case 6:
alpha = false;
break;
default:
return false;
return false;
}
if (alpha)
{
if (ParseCol8(color, 0) < 0)
// Check if each hex digit is valid.
for (int i = 0; i < len; i++) {
if (ParseCol4(color, i) == -1)
{
return false;
}
}
int from = alpha ? 2 : 0;
if (ParseCol8(color, from + 0) < 0)
{
return false;
}
if (ParseCol8(color, from + 2) < 0)
{
return false;
}
if (ParseCol8(color, from + 4) < 0)
{
return false;
}
return true;
}

View File

@ -39,9 +39,7 @@ namespace TestColor {
TEST_CASE("[Color] Constructor methods") {
const Color blue_rgba = Color(0.25098, 0.376471, 1, 0.501961);
// HTML currently uses ARGB notation, which is contrary to the CSS standard.
// This may be changed to RGBA in 4.0.
const Color blue_html = Color::html("#804060ff");
const Color blue_html = Color::html("#4060ff80");
const Color blue_hex = Color::hex(0x4060ff80);
const Color blue_hex64 = Color::hex64(0x4040'6060'ffff'8080);
@ -118,10 +116,10 @@ TEST_CASE("[Color] Conversion methods") {
const Color cyan_transparent = Color(0, 1, 1, 0);
CHECK_MESSAGE(
cyan.to_html() == "ff00ffff",
cyan.to_html() == "00ffffff",
"The returned RGB HTML color code should match the expected value.");
CHECK_MESSAGE(
cyan_transparent.to_html() == "0000ffff",
cyan_transparent.to_html() == "00ffff00",
"The returned RGBA HTML color code should match the expected value.");
CHECK_MESSAGE(
cyan.to_argb32() == 0xff00ffff,