From d2b64a1db2e0070f90c77a88ccf1537173befb83 Mon Sep 17 00:00:00 2001 From: "Owen W. Taylor" Date: Tue, 2 Nov 2010 15:30:44 -0400 Subject: [PATCH] Switch to CSS interpretation of rgb() and rgba() colors CSS3 defines a somewhat odd syntax for rgba() colors - the rgb values are integers from 0 to 255 or percentages and the a value is a float from 0 to 1. To avoid increasing the total amount of confusion in the world, make gdk_rgb_to_string() and gdk_rgb_parse() follow this syntax rather than using floats for r, g, and b. https://bugzilla.gnome.org/show_bug.cgi?id=633762 --- gdk/gdkrgba.c | 91 ++++++++++++++++++++++++++++++++----------- gdk/tests/gdk-color.c | 29 +++++++------- 2 files changed, 85 insertions(+), 35 deletions(-) diff --git a/gdk/gdkrgba.c b/gdk/gdkrgba.c index b355631e0e..e50f47f4b5 100644 --- a/gdk/gdkrgba.c +++ b/gdk/gdkrgba.c @@ -72,6 +72,43 @@ gdk_rgba_free (GdkRGBA *rgba) g_slice_free (GdkRGBA, rgba); } +#define SKIP_WHITESPACES(s) while (*(s) == ' ') (s)++; + +/* Parses a single color component from a rgb() or rgba() specification + * according to CSS3 rules. Compared to exact CSS3 parsing we are liberal + * in what we accept as follows: + * + * - For non-percentage values, we accept floats in the range 0-255 + * not just [0-9]+ integers + * - For percentage values we accept any float, not just + * [ 0-9]+ | [0-9]* '.' [0-9]+ + * - We accept mixed percentages and non-percentages in a single + * rgb() or rgba() specification. + */ +static double +parse_rgb_value (const char *str, + char **endp) +{ + double number; + const char *p; + + number = g_ascii_strtod (str, endp); + + p = *endp; + + SKIP_WHITESPACES (p); + + if (*p == '%') + { + *endp = (char *)(p + 1); + return CLAMP(number / 100., 0., 1.); + } + else + { + return CLAMP(number / 255., 0., 1.); + } +} + /** * gdk_rgba_parse: * @spec: the string specifying the color @@ -100,8 +137,9 @@ gdk_rgba_free (GdkRGBA *rgba) * * * Where 'r', 'g', 'b' and 'a' are respectively the red, green, blue and - * alpha color values, parsed in the last 2 cases as double numbers in - * the range [0..1], any other value out of that range will be clamped. + * alpha color values. In the last two cases, r g and b are either integers + * in the range 0 to 255 or precentage values in the range 0% to 100%, and + * a is a floating point value in the range 0 to 1. * * Returns: %TRUE if the parsing succeeded **/ @@ -113,8 +151,6 @@ gdk_rgba_parse (const gchar *spec, gdouble r, g, b, a; gchar *str = (gchar *) spec; -#define SKIP_WHITESPACES(s) while (*(s) == ' ') (s)++; - if (strncmp (str, "rgba", 4) == 0) { has_alpha = TRUE; @@ -157,7 +193,7 @@ gdk_rgba_parse (const gchar *spec, /* Parse red */ SKIP_WHITESPACES (str); - r = g_ascii_strtod (str, &str); + r = parse_rgb_value (str, &str); SKIP_WHITESPACES (str); if (*str != ',') @@ -167,7 +203,7 @@ gdk_rgba_parse (const gchar *spec, /* Parse green */ SKIP_WHITESPACES (str); - g = g_ascii_strtod (str, &str); + g = parse_rgb_value (str, &str); SKIP_WHITESPACES (str); if (*str != ',') @@ -177,7 +213,7 @@ gdk_rgba_parse (const gchar *spec, /* Parse blue */ SKIP_WHITESPACES (str); - b = g_ascii_strtod (str, &str); + b = parse_rgb_value (str, &str); SKIP_WHITESPACES (str); if (has_alpha) @@ -195,8 +231,6 @@ gdk_rgba_parse (const gchar *spec, if (*str != ')') return FALSE; -#undef SKIP_WHITESPACES - if (rgba) { rgba->red = CLAMP (r, 0, 1); @@ -208,6 +242,8 @@ gdk_rgba_parse (const gchar *spec, return TRUE; } +#undef SKIP_WHITESPACES + /** * gdk_rgba_hash: * @p: a #GdkRGBA pointer. @@ -259,25 +295,36 @@ gdk_rgba_equal (gconstpointer p1, * gdk_rgba_to_string: * @rgba: a #GdkRGBA * - * Returns a textual specification of @rgba in the form - * rgba (r, g, b, a), where 'r', 'g', - * 'b' and 'a' represent the red, green, blue and alpha - * values respectively. + * Returns a textual specification of @rgba in the form rgb + * (r, g, b) or rgba (r, g, b, a), + * where 'r', 'g', 'b' and 'a' represent the red, green, blue and alpha values + * respectively. r, g, and b are integers in the range 0 to 255, and a + * is a floating point value in the range 0 to 1. + * + * (These string forms are string forms those supported by the CSS3 colors module) * * Returns: A newly allocated text string **/ gchar * gdk_rgba_to_string (const GdkRGBA *rgba) { - gchar red[G_ASCII_DTOSTR_BUF_SIZE]; - gchar green[G_ASCII_DTOSTR_BUF_SIZE]; - gchar blue[G_ASCII_DTOSTR_BUF_SIZE]; - gchar alpha[G_ASCII_DTOSTR_BUF_SIZE]; + if (rgba->alpha > 0.999) + { + return g_strdup_printf ("rgb(%d,%d,%d)", + (int)(0.5 + CLAMP (rgba->red, 0., 1.) * 255.), + (int)(0.5 + CLAMP (rgba->green, 0., 1.) * 255.), + (int)(0.5 + CLAMP (rgba->blue, 0., 1.) * 255.)); + } + else + { + gchar alpha[G_ASCII_DTOSTR_BUF_SIZE]; - g_ascii_dtostr (red, G_ASCII_DTOSTR_BUF_SIZE, CLAMP (rgba->red, 0, 1)); - g_ascii_dtostr (green, G_ASCII_DTOSTR_BUF_SIZE, CLAMP (rgba->green, 0, 1)); - g_ascii_dtostr (blue, G_ASCII_DTOSTR_BUF_SIZE, CLAMP (rgba->blue, 0, 1)); - g_ascii_dtostr (alpha, G_ASCII_DTOSTR_BUF_SIZE, CLAMP (rgba->alpha, 0, 1)); + g_ascii_dtostr (alpha, G_ASCII_DTOSTR_BUF_SIZE, CLAMP (rgba->alpha, 0, 1)); - return g_strdup_printf ("rgba(%s,%s,%s,%s)", red, green, blue, alpha); + return g_strdup_printf ("rgba(%d,%d,%d,%s)", + (int)(0.5 + CLAMP (rgba->red, 0., 1.) * 255.), + (int)(0.5 + CLAMP (rgba->green, 0., 1.) * 255.), + (int)(0.5 + CLAMP (rgba->blue, 0., 1.) * 255.), + alpha); + } } diff --git a/gdk/tests/gdk-color.c b/gdk/tests/gdk-color.c index b0736b8b3e..2dbe2ad248 100644 --- a/gdk/tests/gdk-color.c +++ b/gdk/tests/gdk-color.c @@ -14,23 +14,23 @@ test_color_parse (void) res = gdk_rgba_parse ("", &color); g_assert (!res); + expected.red = 100/255.; + expected.green = 90/255.; + expected.blue = 80/255.; + expected.alpha = 0.1; + res = gdk_rgba_parse ("rgba(100,90,80,0.1)", &color); + g_assert (res); + g_assert (gdk_rgba_equal (&color, &expected)); + expected.red = 0.4; expected.green = 0.3; expected.blue = 0.2; expected.alpha = 0.1; - res = gdk_rgba_parse ("rgba(0.4,0.3,0.2,0.1)", &color); + res = gdk_rgba_parse ("rgba(40%,30%,20%,0.1)", &color); g_assert (res); g_assert (gdk_rgba_equal (&color, &expected)); - res = gdk_rgba_parse ("rgba ( 0.4 , 0.3 , 0.2 , 0.1 )", &color); - g_assert (res); - g_assert (gdk_rgba_equal (&color, &expected)); - - expected.red = 0.4; - expected.green = 0.3; - expected.blue = 0.2; - expected.alpha = 1.0; - res = gdk_rgba_parse ("rgb(0.4,0.3,0.2)", &color); + res = gdk_rgba_parse ("rgba( 40 % , 30 % , 20 % , 0.1 )", &color); g_assert (res); g_assert (gdk_rgba_equal (&color, &expected)); @@ -61,10 +61,13 @@ test_color_to_string (void) gchar *res_en; gchar *orig; + /* Using /255. values for the r, g, b components should + * make sure they round-trip exactly without rounding + * from the double => integer => double conversions */ rgba.red = 1.0; - rgba.green = 0.5; - rgba.blue = 0.1; - rgba.alpha = 1.0; + rgba.green = 128/255.; + rgba.blue = 64/255.; + rgba.alpha = 0.5; orig = g_strdup (setlocale (LC_ALL, NULL)); res = gdk_rgba_to_string (&rgba);