gtk2/gdk/gdkrgba.c

659 lines
16 KiB
C
Raw Permalink Normal View History

/* GDK - The GIMP Drawing Kit
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
2012-02-27 13:01:10 +00:00
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
* file for a list of people on the GTK+ Team. See the ChangeLog
* files for a list of changes. These files are distributed with
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
*/
#include "config.h"
#include "gdkrgbaprivate.h"
#include <string.h>
#include <errno.h>
#include <math.h>
2021-08-29 02:08:47 +00:00
#include "gdkhslaprivate.h"
G_DEFINE_BOXED_TYPE (GdkRGBA, gdk_rgba,
gdk_rgba_copy, gdk_rgba_free)
2010-12-06 17:56:11 +00:00
/**
* GdkRGBA:
2011-02-09 07:43:07 +00:00
* @red: The intensity of the red channel from 0.0 to 1.0 inclusive
* @green: The intensity of the green channel from 0.0 to 1.0 inclusive
* @blue: The intensity of the blue channel from 0.0 to 1.0 inclusive
2010-12-06 17:56:11 +00:00
* @alpha: The opacity of the color from 0.0 for completely translucent to
2011-02-09 07:43:07 +00:00
* 1.0 for opaque
2010-12-06 17:56:11 +00:00
*
* A `GdkRGBA` is used to represent a color, in a way that is compatible
* with cairos notion of color.
*
* `GdkRGBA` is a convenient way to pass colors around. Its based on
* cairos way to deal with colors and mirrors its behavior. All values
* are in the range from 0.0 to 1.0 inclusive. So the color
* (0.0, 0.0, 0.0, 0.0) represents transparent black and
* (1.0, 1.0, 1.0, 1.0) is opaque white. Other values will
* be clamped to this range when drawing.
2010-12-06 17:56:11 +00:00
*/
/**
* gdk_rgba_copy:
* @rgba: a `GdkRGBA`
*
* Makes a copy of a `GdkRGBA`.
*
* The result must be freed through [method@Gdk.RGBA.free].
2011-02-09 07:43:07 +00:00
*
* Returns: A newly allocated `GdkRGBA`, with the same contents as @rgba
2011-02-09 07:43:07 +00:00
*/
GdkRGBA *
gdk_rgba_copy (const GdkRGBA *rgba)
{
return g_slice_dup (GdkRGBA, rgba);
}
/**
* gdk_rgba_free:
* @rgba: a `GdkRGBA`
*
* Frees a `GdkRGBA`.
2011-02-09 07:43:07 +00:00
*/
void
gdk_rgba_free (GdkRGBA *rgba)
{
g_slice_free (GdkRGBA, rgba);
}
/**
* gdk_rgba_is_clear:
* @rgba: a `GdkRGBA`
*
* Checks if an @rgba value is transparent.
*
* That is, drawing with the value would not produce any change.
*
* Returns: %TRUE if the @rgba is clear
*/
gboolean
gdk_rgba_is_clear (const GdkRGBA *rgba)
{
2019-12-10 13:22:52 +00:00
return rgba->alpha < ((float) 0x00ff / (float) 0xffff);
}
/**
* gdk_rgba_is_opaque:
* @rgba: a `GdkRGBA`
*
* Checks if an @rgba value is opaque.
*
* That is, drawing with the value will not retain any results
* from previous contents.
*
* Returns: %TRUE if the @rgba is opaque
*/
gboolean
gdk_rgba_is_opaque (const GdkRGBA *rgba)
{
2019-12-10 13:22:52 +00:00
return rgba->alpha > ((float)0xff00 / (float)0xffff);
}
#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 gboolean
2020-07-24 18:40:36 +00:00
parse_rgb_value (const char *str,
char **endp,
2020-07-24 20:32:16 +00:00
double *number)
{
const char *p;
*number = g_ascii_strtod (str, endp);
if (errno == ERANGE || *endp == str ||
isinf (*number) || isnan (*number))
return FALSE;
p = *endp;
SKIP_WHITESPACES (p);
if (*p == '%')
{
*endp = (char *)(p + 1);
*number = CLAMP(*number / 100., 0., 1.);
}
else
{
*number = CLAMP(*number / 255., 0., 1.);
}
return TRUE;
}
/**
* gdk_rgba_parse:
* @rgba: the `GdkRGBA` to fill in
* @spec: the string specifying the color
*
* Parses a textual representation of a color.
*
* The string can be either one of:
*
* - A standard name (Taken from the Css specification).
* - A hexadecimal value in the form \#rgb, \#rrggbb,
* \#rrrgggbbb or \#rrrrggggbbbb
* - A hexadecimal value in the form \#rgba, \#rrggbbaa,
* or \#rrrrggggbbbbaaaa
* - A RGB color in the form rgb(r,g,b) (In this case the color
* will have full opacity)
* - A RGBA color in the form rgba(r,g,b,a)
*
* Where r, g, b and a are respectively the red, green,
* blue and alpha color values. In the last two cases, r, g,
* and b are either integers in the range 0 to 255 or percentage
* 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
2011-02-09 07:43:07 +00:00
*/
gboolean
gdk_rgba_parse (GdkRGBA *rgba,
2020-07-24 18:40:36 +00:00
const char *spec)
{
gboolean has_alpha;
2021-08-29 02:08:47 +00:00
gboolean is_hsl;
2020-07-24 20:32:16 +00:00
double r, g, b, a;
2020-07-24 18:40:36 +00:00
char *str = (char *) spec;
char *p;
g_return_val_if_fail (spec != NULL, FALSE);
if (strncmp (str, "rgba", 4) == 0)
{
has_alpha = TRUE;
2021-08-29 02:08:47 +00:00
is_hsl = FALSE;
str += 4;
}
else if (strncmp (str, "rgb", 3) == 0)
{
has_alpha = FALSE;
2021-08-29 02:08:47 +00:00
is_hsl = FALSE;
a = 1;
str += 3;
}
else if (strncmp (str, "hsla", 4) == 0)
{
has_alpha = TRUE;
is_hsl = TRUE;
str += 4;
}
else if (strncmp (str, "hsl", 3) == 0)
{
has_alpha = FALSE;
is_hsl = TRUE;
a = 1;
str += 3;
}
else
{
PangoColor pango_color;
guint16 alpha;
/* Resort on PangoColor for rgb.txt color
2011-02-09 07:43:07 +00:00
* map and '#' prefixed colors
*/
if (pango_color_parse_with_alpha (&pango_color, &alpha, str))
{
if (rgba)
{
rgba->red = pango_color.red / 65535.;
rgba->green = pango_color.green / 65535.;
rgba->blue = pango_color.blue / 65535.;
rgba->alpha = alpha / 65535.;
}
return TRUE;
}
else
return FALSE;
}
SKIP_WHITESPACES (str);
if (*str != '(')
return FALSE;
str++;
/* Parse red */
SKIP_WHITESPACES (str);
if (!parse_rgb_value (str, &str, &r))
return FALSE;
SKIP_WHITESPACES (str);
if (*str != ',')
return FALSE;
str++;
/* Parse green */
SKIP_WHITESPACES (str);
if (!parse_rgb_value (str, &str, &g))
return FALSE;
SKIP_WHITESPACES (str);
if (*str != ',')
return FALSE;
str++;
/* Parse blue */
SKIP_WHITESPACES (str);
if (!parse_rgb_value (str, &str, &b))
return FALSE;
SKIP_WHITESPACES (str);
if (has_alpha)
{
if (*str != ',')
return FALSE;
str++;
SKIP_WHITESPACES (str);
a = g_ascii_strtod (str, &p);
if (errno == ERANGE || p == str ||
isinf (a) || isnan (a))
return FALSE;
str = p;
SKIP_WHITESPACES (str);
}
if (*str != ')')
return FALSE;
str++;
SKIP_WHITESPACES (str);
if (*str != '\0')
return FALSE;
if (rgba)
{
2021-08-29 02:08:47 +00:00
if (is_hsl)
{
GdkHSLA hsla;
hsla.hue = r * 255;
hsla.saturation = CLAMP (g, 0, 1);
hsla.lightness = CLAMP (b, 0, 1);
hsla.alpha = CLAMP (a, 0, 1);
_gdk_rgba_init_from_hsla (rgba, &hsla);
}
else
{
rgba->red = CLAMP (r, 0, 1);
rgba->green = CLAMP (g, 0, 1);
rgba->blue = CLAMP (b, 0, 1);
rgba->alpha = CLAMP (a, 0, 1);
}
}
return TRUE;
}
#undef SKIP_WHITESPACES
/**
* gdk_rgba_hash:
* @p: (type GdkRGBA): a `GdkRGBA`
*
* A hash function suitable for using for a hash
* table that stores `GdkRGBA`s.
*
* Returns: The hash value for @p
2011-02-09 07:43:07 +00:00
*/
guint
gdk_rgba_hash (gconstpointer p)
{
const GdkRGBA *rgba = p;
return ((guint) (rgba->red * 65535) +
2011-02-09 07:43:07 +00:00
((guint) (rgba->green * 65535) << 11) +
((guint) (rgba->blue * 65535) << 22) +
((guint) (rgba->alpha * 65535) >> 6));
}
/**
* gdk_rgba_equal:
* @p1: (type GdkRGBA): a `GdkRGBA`
* @p2: (type GdkRGBA): another `GdkRGBA`
*
* Compares two `GdkRGBA` colors.
*
* Returns: %TRUE if the two colors compare equal
2011-02-09 07:43:07 +00:00
*/
gboolean
gdk_rgba_equal (gconstpointer p1,
gconstpointer p2)
{
const GdkRGBA *rgba1, *rgba2;
rgba1 = p1;
rgba2 = p2;
if (rgba1->red == rgba2->red &&
rgba1->green == rgba2->green &&
rgba1->blue == rgba2->blue &&
rgba1->alpha == rgba2->alpha)
return TRUE;
return FALSE;
}
/**
* gdk_rgba_to_string:
* @rgba: a `GdkRGBA`
*
2011-02-09 07:43:07 +00:00
* 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 represented as integers
* in the range 0 to 255, and a is represented as a floating
* point value in the range 0 to 1.
*
* These string forms are string forms that are supported by
* the CSS3 colors module, and can be parsed by [method@Gdk.RGBA.parse].
2011-02-09 07:43:07 +00:00
*
* Note that this string representation may lose some precision,
* since r, g and b are represented as 8-bit integers. If
* this is a concern, you should use a different representation.
*
* Returns: A newly allocated text string
2011-02-09 07:43:07 +00:00
*/
2020-07-24 18:40:36 +00:00
char *
gdk_rgba_to_string (const GdkRGBA *rgba)
{
if (rgba->alpha > 0.999)
{
return g_strdup_printf ("rgb(%d,%d,%d)",
2011-02-09 07:43:07 +00:00
(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
{
2020-07-24 18:40:36 +00:00
char alpha[G_ASCII_DTOSTR_BUF_SIZE];
g_ascii_formatd (alpha, G_ASCII_DTOSTR_BUF_SIZE, "%g", CLAMP (rgba->alpha, 0, 1));
return g_strdup_printf ("rgba(%d,%d,%d,%s)",
2011-02-09 07:43:07 +00:00
(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);
}
}
static gboolean
parse_color_channel_value (GtkCssParser *parser,
2019-12-10 13:22:52 +00:00
float *value,
gboolean is_percentage)
{
2019-12-10 13:22:52 +00:00
double dvalue;
if (is_percentage)
{
2019-12-10 13:22:52 +00:00
if (!gtk_css_parser_consume_percentage (parser, &dvalue))
return FALSE;
2019-12-10 13:22:52 +00:00
*value = CLAMP (dvalue, 0.0, 100.0) / 100.0;
return TRUE;
}
else
{
2019-12-10 13:22:52 +00:00
if (!gtk_css_parser_consume_number (parser, &dvalue))
return FALSE;
2019-12-10 13:22:52 +00:00
*value = CLAMP (dvalue, 0.0, 255.0) / 255.0;
return TRUE;
}
}
static guint
parse_color_channel (GtkCssParser *parser,
guint arg,
gpointer data)
{
GdkRGBA *rgba = data;
2019-12-10 13:22:52 +00:00
double dvalue;
switch (arg)
{
case 0:
/* We abuse rgba->alpha to store if we use percentages or numbers */
if (gtk_css_token_is (gtk_css_parser_get_token (parser), GTK_CSS_TOKEN_PERCENTAGE))
rgba->alpha = 1.0;
else
rgba->alpha = 0.0;
if (!parse_color_channel_value (parser, &rgba->red, rgba->alpha != 0.0))
return 0;
return 1;
case 1:
if (!parse_color_channel_value (parser, &rgba->green, rgba->alpha != 0.0))
return 0;
return 1;
case 2:
if (!parse_color_channel_value (parser, &rgba->blue, rgba->alpha != 0.0))
return 0;
return 1;
case 3:
2019-12-10 13:22:52 +00:00
if (!gtk_css_parser_consume_number (parser, &dvalue))
return 0;
2019-12-10 13:22:52 +00:00
rgba->alpha = CLAMP (dvalue, 0.0, 1.0);
return 1;
default:
g_assert_not_reached ();
return 0;
}
}
2021-08-29 02:08:47 +00:00
static guint
parse_hsla_color_channel (GtkCssParser *parser,
guint arg,
gpointer data)
{
GdkHSLA *hsla = data;
double dvalue;
switch (arg)
{
case 0:
if (!gtk_css_parser_consume_number (parser, &dvalue))
return 0;
hsla->hue = dvalue;
return 1;
case 1:
if (!gtk_css_parser_consume_percentage (parser, &dvalue))
return 0;
hsla->saturation = CLAMP (dvalue, 0.0, 100.0) / 100.0;
return 1;
case 2:
if (!gtk_css_parser_consume_percentage (parser, &dvalue))
return 0;
hsla->lightness = CLAMP (dvalue, 0.0, 100.0) / 100.0;
return 1;
case 3:
if (!gtk_css_parser_consume_number (parser, &dvalue))
return 0;
hsla->alpha = CLAMP (dvalue, 0.0, 1.0) / 1.0;
return 1;
default:
g_assert_not_reached ();
return 0;
}
}
static gboolean
rgba_init_chars (GdkRGBA *rgba,
const char s[8])
{
guint i;
for (i = 0; i < 8; i++)
{
if (!g_ascii_isxdigit (s[i]))
return FALSE;
}
rgba->red = (g_ascii_xdigit_value (s[0]) * 16 + g_ascii_xdigit_value (s[1])) / 255.0;
rgba->green = (g_ascii_xdigit_value (s[2]) * 16 + g_ascii_xdigit_value (s[3])) / 255.0;
rgba->blue = (g_ascii_xdigit_value (s[4]) * 16 + g_ascii_xdigit_value (s[5])) / 255.0;
rgba->alpha = (g_ascii_xdigit_value (s[6]) * 16 + g_ascii_xdigit_value (s[7])) / 255.0;
return TRUE;
}
gboolean
gdk_rgba_parser_parse (GtkCssParser *parser,
GdkRGBA *rgba)
{
const GtkCssToken *token;
token = gtk_css_parser_get_token (parser);
if (gtk_css_token_is_function (token, "rgb"))
{
if (!gtk_css_parser_consume_function (parser, 3, 3, parse_color_channel, rgba))
return FALSE;
rgba->alpha = 1.0;
return TRUE;
}
else if (gtk_css_token_is_function (token, "rgba"))
{
return gtk_css_parser_consume_function (parser, 4, 4, parse_color_channel, rgba);
}
2021-08-29 02:08:47 +00:00
else if (gtk_css_token_is_function (token, "hsl") || gtk_css_token_is_function (token, "hsla"))
{
GdkHSLA hsla;
hsla.alpha = 1.0;
if (!gtk_css_parser_consume_function (parser, 3, 4, parse_hsla_color_channel, &hsla))
return FALSE;
_gdk_rgba_init_from_hsla (rgba, &hsla);
return TRUE;
}
else if (gtk_css_token_is (token, GTK_CSS_TOKEN_HASH_ID) ||
gtk_css_token_is (token, GTK_CSS_TOKEN_HASH_UNRESTRICTED))
{
const char *s = gtk_css_token_get_string (token);
switch (strlen (s))
{
case 3:
if (!rgba_init_chars (rgba, (char[8]) {s[0], s[0], s[1], s[1], s[2], s[2], 'F', 'F' }))
{
gtk_css_parser_error_value (parser, "Hash code is not a valid hex color.");
return FALSE;
}
break;
case 4:
if (!rgba_init_chars (rgba, (char[8]) {s[0], s[0], s[1], s[1], s[2], s[2], s[3], s[3] }))
{
gtk_css_parser_error_value (parser, "Hash code is not a valid hex color.");
return FALSE;
}
break;
case 6:
if (!rgba_init_chars (rgba, (char[8]) {s[0], s[1], s[2], s[3], s[4], s[5], 'F', 'F' }))
{
gtk_css_parser_error_value (parser, "Hash code is not a valid hex color.");
return FALSE;
}
break;
case 8:
if (!rgba_init_chars (rgba, s))
{
gtk_css_parser_error_value (parser, "Hash code is not a valid hex color.");
return FALSE;
}
break;
default:
gtk_css_parser_error_value (parser, "Hash code is not a valid hex color.");
return FALSE;
break;
}
gtk_css_parser_consume_token (parser);
return TRUE;
}
else if (gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
{
if (gtk_css_token_is_ident (token, "transparent"))
{
*rgba = (GdkRGBA) { 0, 0, 0, 0 };
}
else if (gdk_rgba_parse (rgba, gtk_css_token_get_string (token)))
{
/* everything's fine */
}
else
{
gtk_css_parser_error_syntax (parser, "\"%s\" is not a valid color name.", gtk_css_token_get_string (token));
return FALSE;
}
gtk_css_parser_consume_token (parser);
return TRUE;
}
else
{
gtk_css_parser_error_syntax (parser, "Expected a valid color.");
return FALSE;
}
}