/* 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
* 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>
#include "gdkhslaprivate.h"
G_DEFINE_BOXED_TYPE (GdkRGBA, gdk_rgba,
gdk_rgba_copy, gdk_rgba_free)
/**
* GdkRGBA :
* @ 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
* @ alpha : The opacity of the color from 0 . 0 for completely translucent to
* 1 . 0 for opaque
*
* Represents a color , in a way that is compatible with cairo ’ s notion of color .
*
* ` GdkRGBA ` is a convenient way to pass colors around . It ’ s based on
* cairo ’ s 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 .
*/
/**
* gdk_rgba_copy :
* @ rgba : a ` GdkRGBA `
*
* Makes a copy of a ` GdkRGBA ` .
*
* The result must be freed through [ method @ Gdk . RGBA . free ] .
*
* Returns : A newly allocated ` GdkRGBA ` , with the same contents as @ rgba
*/
GdkRGBA *
gdk_rgba_copy (const GdkRGBA *rgba)
{
GdkRGBA *copy;
copy = g_new (GdkRGBA, 1 );
memcpy (copy, rgba, sizeof (GdkRGBA));
return copy;
}
/**
* gdk_rgba_free :
* @ rgba : a ` GdkRGBA `
*
* Frees a ` GdkRGBA ` .
*/
void
gdk_rgba_free (GdkRGBA *rgba)
{
g_free (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)
{
return _gdk_rgba_is_clear (rgba);
}
/**
* 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)
{
return _gdk_rgba_is_opaque (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 gboolean
parse_rgb_value (const char *str,
char **endp,
double *number,
int coord)
{
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
{
if (coord == 'h' )
{
*number = fmod (*number, 360 );
if (*number < 0 )
*number += 360 ;
*number = CLAMP (*number / 360 .0 , 0 ., 1 .);
}
else if (coord == 's' || coord == 'l' )
*number = CLAMP (*number / 100 .0 , 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 ) ”
* - A HSL color in the form “ hsl ( h , s , l ) ”
* - A HSLA color in the form “ hsla ( h , s , l , 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 . The range for “ h ” is 0 to 360 , and
* “ s ” , “ l ” can be either numbers in the range 0 to 100 or
* percentages .
*
* Returns : % TRUE if the parsing succeeded
*/
gboolean
gdk_rgba_parse (GdkRGBA *rgba,
const char *spec)
{
gboolean has_alpha;
gboolean is_hsl;
double r, g, b, a;
char *str = (char *) spec;
char *p;
g_return_val_if_fail (spec != NULL, FALSE );
if (strncmp (str, "rgba" , 4 ) == 0 )
{
has_alpha = TRUE ;
is_hsl = FALSE ;
str += 4 ;
}
else if (strncmp (str, "rgb" , 3 ) == 0 )
{
has_alpha = FALSE ;
is_hsl = FALSE ;
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 ;
str += 3 ;
}
else
{
PangoColor pango_color;
guint16 alpha;
/* Resort on PangoColor for rgb.txt color
* 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, is_hsl ? 'h' : 'r' ))
return FALSE ;
SKIP_WHITESPACES (str);
if (*str != ',' )
return FALSE ;
str++;
/* Parse green */
SKIP_WHITESPACES (str);
if (!parse_rgb_value (str, &str, &g, is_hsl ? 's' : 'g' ))
return FALSE ;
SKIP_WHITESPACES (str);
if (*str != ',' )
return FALSE ;
str++;
/* Parse blue */
SKIP_WHITESPACES (str);
if (!parse_rgb_value (str, &str, &b, is_hsl ? 'l' : '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);
}
else
{
a = 1 ;
}
if (*str != ')' )
return FALSE ;
str++;
SKIP_WHITESPACES (str);
if (*str != '\0' )
return FALSE ;
if (rgba)
{
if (is_hsl)
{
GdkHSLA hsla;
hsla.hue = r * 360 ;
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
*/
guint
gdk_rgba_hash (gconstpointer p)
{
const GdkRGBA *rgba = p;
return ((guint) (rgba->red * 65535 ) +
((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
*/
gboolean
(gdk_rgba_equal) (gconstpointer p1,
gconstpointer p2)
{
return _gdk_rgba_equal (p1, p2);
}
/**
* gdk_rgba_to_string :
* @ rgba : a ` GdkRGBA `
*
* 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 ] .
*
* 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
*/
char *
gdk_rgba_to_string (const GdkRGBA *rgba)
{
return g_string_free (gdk_rgba_print (rgba, g_string_new ("" )), FALSE );
}
/**
* gdk_rgba_print :
* @ rgba : a ` GdkRGBA `
* @ string : the string to print to
*
* Appends a representation of @ rgba to @ string .
*
* Returns : A newly allocated text string
*
* Since : 4 . 22
*/
GString *
gdk_rgba_print (const GdkRGBA *rgba,
GString *string)
{
if (rgba->alpha > 0 .999 )
{
g_string_append_printf (string,
"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
{
char alpha[G_ASCII_DTOSTR_BUF_SIZE];
g_ascii_formatd (alpha, G_ASCII_DTOSTR_BUF_SIZE, "%g" , CLAMP (rgba->alpha, 0 , 1 ));
g_string_append_printf (string,
"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);
}
return string;
}
static gboolean
parse_color_channel_value (GtkCssParser *parser,
float *value,
gboolean is_percentage)
{
double dvalue;
if (is_percentage)
{
if (!gtk_css_parser_consume_percentage (parser, &dvalue))
return FALSE ;
*value = CLAMP (dvalue, 0 .0 , 100 .0 ) / 100 .0 ;
return TRUE ;
}
else
{
if (!gtk_css_parser_consume_number (parser, &dvalue))
return FALSE ;
*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;
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 :
if (!gtk_css_parser_consume_number (parser, &dvalue))
return 0 ;
rgba->alpha = CLAMP (dvalue, 0 .0 , 1 .0 );
return 1 ;
default :
g_assert_not_reached ();
return 0 ;
}
}
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);
}
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 = GDK_RGBA_TRANSPARENT;
}
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 ;
}
}
Messung V0.5 in Prozent C=98 H=96 G=96
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-07-03)
¤
*© Formatika GbR, Deutschland