1/* GDK - The GIMP Drawing Kit
2 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18/*
19 * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
20 * file for a list of people on the GTK+ Team. See the ChangeLog
21 * files for a list of changes. These files are distributed with
22 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
23 */
24
25#include "config.h"
26
27#include "gdkrgbaprivate.h"
28
29#include <string.h>
30#include <errno.h>
31#include <math.h>
32
33#include "gdkhslaprivate.h"
34
35G_DEFINE_BOXED_TYPE (GdkRGBA, gdk_rgba,
36 gdk_rgba_copy, gdk_rgba_free)
37
38/**
39 * GdkRGBA:
40 * @red: The intensity of the red channel from 0.0 to 1.0 inclusive
41 * @green: The intensity of the green channel from 0.0 to 1.0 inclusive
42 * @blue: The intensity of the blue channel from 0.0 to 1.0 inclusive
43 * @alpha: The opacity of the color from 0.0 for completely translucent to
44 * 1.0 for opaque
45 *
46 * A `GdkRGBA` is used to represent a color, in a way that is compatible
47 * with cairo’s notion of color.
48 *
49 * `GdkRGBA` is a convenient way to pass colors around. It’s based on
50 * cairo’s way to deal with colors and mirrors its behavior. All values
51 * are in the range from 0.0 to 1.0 inclusive. So the color
52 * (0.0, 0.0, 0.0, 0.0) represents transparent black and
53 * (1.0, 1.0, 1.0, 1.0) is opaque white. Other values will
54 * be clamped to this range when drawing.
55 */
56
57/**
58 * gdk_rgba_copy:
59 * @rgba: a `GdkRGBA`
60 *
61 * Makes a copy of a `GdkRGBA`.
62 *
63 * The result must be freed through [method@Gdk.RGBA.free].
64 *
65 * Returns: A newly allocated `GdkRGBA`, with the same contents as @rgba
66 */
67GdkRGBA *
68gdk_rgba_copy (const GdkRGBA *rgba)
69{
70 return g_slice_dup (GdkRGBA, rgba);
71}
72
73/**
74 * gdk_rgba_free:
75 * @rgba: a `GdkRGBA`
76 *
77 * Frees a `GdkRGBA`.
78 */
79void
80gdk_rgba_free (GdkRGBA *rgba)
81{
82 g_slice_free (GdkRGBA, rgba);
83}
84
85/**
86 * gdk_rgba_is_clear:
87 * @rgba: a `GdkRGBA`
88 *
89 * Checks if an @rgba value is transparent.
90 *
91 * That is, drawing with the value would not produce any change.
92 *
93 * Returns: %TRUE if the @rgba is clear
94 */
95gboolean
96gdk_rgba_is_clear (const GdkRGBA *rgba)
97{
98 return rgba->alpha < ((float) 0x00ff / (float) 0xffff);
99}
100
101/**
102 * gdk_rgba_is_opaque:
103 * @rgba: a `GdkRGBA`
104 *
105 * Checks if an @rgba value is opaque.
106 *
107 * That is, drawing with the value will not retain any results
108 * from previous contents.
109 *
110 * Returns: %TRUE if the @rgba is opaque
111 */
112gboolean
113gdk_rgba_is_opaque (const GdkRGBA *rgba)
114{
115 return rgba->alpha > ((float)0xff00 / (float)0xffff);
116}
117
118#define SKIP_WHITESPACES(s) while (*(s) == ' ') (s)++;
119
120/* Parses a single color component from a rgb() or rgba() specification
121 * according to CSS3 rules. Compared to exact CSS3 parsing we are liberal
122 * in what we accept as follows:
123 *
124 * - For non-percentage values, we accept floats in the range 0-255
125 * not just [0-9]+ integers
126 * - For percentage values we accept any float, not just [ 0-9]+ | [0-9]* “.” [0-9]+
127 * - We accept mixed percentages and non-percentages in a single
128 * rgb() or rgba() specification.
129 */
130static gboolean
131parse_rgb_value (const char *str,
132 char **endp,
133 double *number)
134{
135 const char *p;
136
137 *number = g_ascii_strtod (nptr: str, endptr: endp);
138 if (errno == ERANGE || *endp == str ||
139 isinf (*number) || isnan (*number))
140 return FALSE;
141
142 p = *endp;
143
144 SKIP_WHITESPACES (p);
145
146 if (*p == '%')
147 {
148 *endp = (char *)(p + 1);
149 *number = CLAMP(*number / 100., 0., 1.);
150 }
151 else
152 {
153 *number = CLAMP(*number / 255., 0., 1.);
154 }
155
156 return TRUE;
157}
158
159/**
160 * gdk_rgba_parse:
161 * @rgba: the `GdkRGBA` to fill in
162 * @spec: the string specifying the color
163 *
164 * Parses a textual representation of a color.
165 *
166 * The string can be either one of:
167 *
168 * - A standard name (Taken from the Css specification).
169 * - A hexadecimal value in the form “\#rgb”, “\#rrggbb”,
170 * “\#rrrgggbbb” or ”\#rrrrggggbbbb”
171 * - A hexadecimal value in the form “\#rgba”, “\#rrggbbaa”,
172 * or ”\#rrrrggggbbbbaaaa”
173 * - A RGB color in the form “rgb(r,g,b)” (In this case the color
174 * will have full opacity)
175 * - A RGBA color in the form “rgba(r,g,b,a)”
176 *
177 * Where “r”, “g”, “b” and “a” are respectively the red, green,
178 * blue and alpha color values. In the last two cases, “r”, “g”,
179 * and “b” are either integers in the range 0 to 255 or percentage
180 * values in the range 0% to 100%, and a is a floating point value
181 * in the range 0 to 1.
182 *
183 * Returns: %TRUE if the parsing succeeded
184 */
185gboolean
186gdk_rgba_parse (GdkRGBA *rgba,
187 const char *spec)
188{
189 gboolean has_alpha;
190 gboolean is_hsl;
191 double r, g, b, a;
192 char *str = (char *) spec;
193 char *p;
194
195 g_return_val_if_fail (spec != NULL, FALSE);
196
197
198 if (strncmp (s1: str, s2: "rgba", n: 4) == 0)
199 {
200 has_alpha = TRUE;
201 is_hsl = FALSE;
202 str += 4;
203 }
204 else if (strncmp (s1: str, s2: "rgb", n: 3) == 0)
205 {
206 has_alpha = FALSE;
207 is_hsl = FALSE;
208 a = 1;
209 str += 3;
210 }
211 else if (strncmp (s1: str, s2: "hsla", n: 4) == 0)
212 {
213 has_alpha = TRUE;
214 is_hsl = TRUE;
215 str += 4;
216 }
217 else if (strncmp (s1: str, s2: "hsl", n: 3) == 0)
218 {
219 has_alpha = FALSE;
220 is_hsl = TRUE;
221 a = 1;
222 str += 3;
223 }
224 else
225 {
226 PangoColor pango_color;
227 guint16 alpha;
228
229 /* Resort on PangoColor for rgb.txt color
230 * map and '#' prefixed colors
231 */
232 if (pango_color_parse_with_alpha (color: &pango_color, alpha: &alpha, spec: str))
233 {
234 if (rgba)
235 {
236 rgba->red = pango_color.red / 65535.;
237 rgba->green = pango_color.green / 65535.;
238 rgba->blue = pango_color.blue / 65535.;
239 rgba->alpha = alpha / 65535.;
240 }
241
242 return TRUE;
243 }
244 else
245 return FALSE;
246 }
247
248 SKIP_WHITESPACES (str);
249
250 if (*str != '(')
251 return FALSE;
252
253 str++;
254
255 /* Parse red */
256 SKIP_WHITESPACES (str);
257 if (!parse_rgb_value (str, endp: &str, number: &r))
258 return FALSE;
259 SKIP_WHITESPACES (str);
260
261 if (*str != ',')
262 return FALSE;
263
264 str++;
265
266 /* Parse green */
267 SKIP_WHITESPACES (str);
268 if (!parse_rgb_value (str, endp: &str, number: &g))
269 return FALSE;
270 SKIP_WHITESPACES (str);
271
272 if (*str != ',')
273 return FALSE;
274
275 str++;
276
277 /* Parse blue */
278 SKIP_WHITESPACES (str);
279 if (!parse_rgb_value (str, endp: &str, number: &b))
280 return FALSE;
281 SKIP_WHITESPACES (str);
282
283 if (has_alpha)
284 {
285 if (*str != ',')
286 return FALSE;
287
288 str++;
289
290 SKIP_WHITESPACES (str);
291 a = g_ascii_strtod (nptr: str, endptr: &p);
292 if (errno == ERANGE || p == str ||
293 isinf (a) || isnan (a))
294 return FALSE;
295 str = p;
296 SKIP_WHITESPACES (str);
297 }
298
299 if (*str != ')')
300 return FALSE;
301
302 str++;
303
304 SKIP_WHITESPACES (str);
305
306 if (*str != '\0')
307 return FALSE;
308
309 if (rgba)
310 {
311 if (is_hsl)
312 {
313 GdkHSLA hsla;
314 hsla.hue = r * 255;
315 hsla.saturation = CLAMP (g, 0, 1);
316 hsla.lightness = CLAMP (b, 0, 1);
317 hsla.alpha = CLAMP (a, 0, 1);
318 _gdk_rgba_init_from_hsla (rgba, hsla: &hsla);
319 }
320 else
321 {
322 rgba->red = CLAMP (r, 0, 1);
323 rgba->green = CLAMP (g, 0, 1);
324 rgba->blue = CLAMP (b, 0, 1);
325 rgba->alpha = CLAMP (a, 0, 1);
326 }
327 }
328
329 return TRUE;
330}
331
332#undef SKIP_WHITESPACES
333
334/**
335 * gdk_rgba_hash:
336 * @p: (type GdkRGBA): a `GdkRGBA`
337 *
338 * A hash function suitable for using for a hash
339 * table that stores `GdkRGBA`s.
340 *
341 * Returns: The hash value for @p
342 */
343guint
344gdk_rgba_hash (gconstpointer p)
345{
346 const GdkRGBA *rgba = p;
347
348 return ((guint) (rgba->red * 65535) +
349 ((guint) (rgba->green * 65535) << 11) +
350 ((guint) (rgba->blue * 65535) << 22) +
351 ((guint) (rgba->alpha * 65535) >> 6));
352}
353
354/**
355 * gdk_rgba_equal:
356 * @p1: (type GdkRGBA): a `GdkRGBA`
357 * @p2: (type GdkRGBA): another `GdkRGBA`
358 *
359 * Compares two `GdkRGBA` colors.
360 *
361 * Returns: %TRUE if the two colors compare equal
362 */
363gboolean
364gdk_rgba_equal (gconstpointer p1,
365 gconstpointer p2)
366{
367 const GdkRGBA *rgba1, *rgba2;
368
369 rgba1 = p1;
370 rgba2 = p2;
371
372 if (rgba1->red == rgba2->red &&
373 rgba1->green == rgba2->green &&
374 rgba1->blue == rgba2->blue &&
375 rgba1->alpha == rgba2->alpha)
376 return TRUE;
377
378 return FALSE;
379}
380
381/**
382 * gdk_rgba_to_string:
383 * @rgba: a `GdkRGBA`
384 *
385 * Returns a textual specification of @rgba in the form
386 * `rgb(r,g,b)` or `rgba(r,g,b,a)`, where “r”, “g”, “b” and
387 * “a” represent the red, green, blue and alpha values
388 * respectively. “r”, “g”, and “b” are represented as integers
389 * in the range 0 to 255, and “a” is represented as a floating
390 * point value in the range 0 to 1.
391 *
392 * These string forms are string forms that are supported by
393 * the CSS3 colors module, and can be parsed by [method@Gdk.RGBA.parse].
394 *
395 * Note that this string representation may lose some precision,
396 * since “r”, “g” and “b” are represented as 8-bit integers. If
397 * this is a concern, you should use a different representation.
398 *
399 * Returns: A newly allocated text string
400 */
401char *
402gdk_rgba_to_string (const GdkRGBA *rgba)
403{
404 if (rgba->alpha > 0.999)
405 {
406 return g_strdup_printf (format: "rgb(%d,%d,%d)",
407 (int)(0.5 + CLAMP (rgba->red, 0., 1.) * 255.),
408 (int)(0.5 + CLAMP (rgba->green, 0., 1.) * 255.),
409 (int)(0.5 + CLAMP (rgba->blue, 0., 1.) * 255.));
410 }
411 else
412 {
413 char alpha[G_ASCII_DTOSTR_BUF_SIZE];
414
415 g_ascii_formatd (buffer: alpha, G_ASCII_DTOSTR_BUF_SIZE, format: "%g", CLAMP (rgba->alpha, 0, 1));
416
417 return g_strdup_printf (format: "rgba(%d,%d,%d,%s)",
418 (int)(0.5 + CLAMP (rgba->red, 0., 1.) * 255.),
419 (int)(0.5 + CLAMP (rgba->green, 0., 1.) * 255.),
420 (int)(0.5 + CLAMP (rgba->blue, 0., 1.) * 255.),
421 alpha);
422 }
423}
424
425static gboolean
426parse_color_channel_value (GtkCssParser *parser,
427 float *value,
428 gboolean is_percentage)
429{
430 double dvalue;
431
432 if (is_percentage)
433 {
434 if (!gtk_css_parser_consume_percentage (self: parser, number: &dvalue))
435 return FALSE;
436
437 *value = CLAMP (dvalue, 0.0, 100.0) / 100.0;
438 return TRUE;
439 }
440 else
441 {
442 if (!gtk_css_parser_consume_number (self: parser, number: &dvalue))
443 return FALSE;
444
445 *value = CLAMP (dvalue, 0.0, 255.0) / 255.0;
446 return TRUE;
447 }
448}
449
450static guint
451parse_color_channel (GtkCssParser *parser,
452 guint arg,
453 gpointer data)
454{
455 GdkRGBA *rgba = data;
456 double dvalue;
457
458 switch (arg)
459 {
460 case 0:
461 /* We abuse rgba->alpha to store if we use percentages or numbers */
462 if (gtk_css_token_is (gtk_css_parser_get_token (parser), GTK_CSS_TOKEN_PERCENTAGE))
463 rgba->alpha = 1.0;
464 else
465 rgba->alpha = 0.0;
466
467 if (!parse_color_channel_value (parser, value: &rgba->red, is_percentage: rgba->alpha != 0.0))
468 return 0;
469 return 1;
470
471 case 1:
472 if (!parse_color_channel_value (parser, value: &rgba->green, is_percentage: rgba->alpha != 0.0))
473 return 0;
474 return 1;
475
476 case 2:
477 if (!parse_color_channel_value (parser, value: &rgba->blue, is_percentage: rgba->alpha != 0.0))
478 return 0;
479 return 1;
480
481 case 3:
482 if (!gtk_css_parser_consume_number (self: parser, number: &dvalue))
483 return 0;
484
485 rgba->alpha = CLAMP (dvalue, 0.0, 1.0);
486 return 1;
487
488 default:
489 g_assert_not_reached ();
490 return 0;
491 }
492}
493
494static guint
495parse_hsla_color_channel (GtkCssParser *parser,
496 guint arg,
497 gpointer data)
498{
499 GdkHSLA *hsla = data;
500 double dvalue;
501
502 switch (arg)
503 {
504 case 0:
505 if (!gtk_css_parser_consume_number (self: parser, number: &dvalue))
506 return 0;
507 hsla->hue = dvalue;
508 return 1;
509
510 case 1:
511 if (!gtk_css_parser_consume_percentage (self: parser, number: &dvalue))
512 return 0;
513 hsla->saturation = CLAMP (dvalue, 0.0, 100.0) / 100.0;
514 return 1;
515
516 case 2:
517 if (!gtk_css_parser_consume_percentage (self: parser, number: &dvalue))
518 return 0;
519 hsla->lightness = CLAMP (dvalue, 0.0, 100.0) / 100.0;
520 return 1;
521
522 case 3:
523 if (!gtk_css_parser_consume_number (self: parser, number: &dvalue))
524 return 0;
525
526 hsla->alpha = CLAMP (dvalue, 0.0, 1.0) / 1.0;
527 return 1;
528
529 default:
530 g_assert_not_reached ();
531 return 0;
532 }
533}
534
535static gboolean
536rgba_init_chars (GdkRGBA *rgba,
537 const char s[8])
538{
539 guint i;
540
541 for (i = 0; i < 8; i++)
542 {
543 if (!g_ascii_isxdigit (s[i]))
544 return FALSE;
545 }
546
547 rgba->red = (g_ascii_xdigit_value (c: s[0]) * 16 + g_ascii_xdigit_value (c: s[1])) / 255.0;
548 rgba->green = (g_ascii_xdigit_value (c: s[2]) * 16 + g_ascii_xdigit_value (c: s[3])) / 255.0;
549 rgba->blue = (g_ascii_xdigit_value (c: s[4]) * 16 + g_ascii_xdigit_value (c: s[5])) / 255.0;
550 rgba->alpha = (g_ascii_xdigit_value (c: s[6]) * 16 + g_ascii_xdigit_value (c: s[7])) / 255.0;
551
552 return TRUE;
553}
554
555gboolean
556gdk_rgba_parser_parse (GtkCssParser *parser,
557 GdkRGBA *rgba)
558{
559 const GtkCssToken *token;
560
561 token = gtk_css_parser_get_token (self: parser);
562 if (gtk_css_token_is_function (token, ident: "rgb"))
563 {
564 if (!gtk_css_parser_consume_function (self: parser, min_args: 3, max_args: 3, parse_func: parse_color_channel, data: rgba))
565 return FALSE;
566
567 rgba->alpha = 1.0;
568 return TRUE;
569 }
570 else if (gtk_css_token_is_function (token, ident: "rgba"))
571 {
572 return gtk_css_parser_consume_function (self: parser, min_args: 4, max_args: 4, parse_func: parse_color_channel, data: rgba);
573 }
574 else if (gtk_css_token_is_function (token, ident: "hsl") || gtk_css_token_is_function (token, ident: "hsla"))
575 {
576 GdkHSLA hsla;
577
578 hsla.alpha = 1.0;
579
580 if (!gtk_css_parser_consume_function (self: parser, min_args: 3, max_args: 4, parse_func: parse_hsla_color_channel, data: &hsla))
581 return FALSE;
582
583 _gdk_rgba_init_from_hsla (rgba, hsla: &hsla);
584 return TRUE;
585 }
586 else if (gtk_css_token_is (token, GTK_CSS_TOKEN_HASH_ID) ||
587 gtk_css_token_is (token, GTK_CSS_TOKEN_HASH_UNRESTRICTED))
588 {
589 const char *s = token->string.string;
590
591 switch (strlen (s: s))
592 {
593 case 3:
594 if (!rgba_init_chars (rgba, s: (char[8]) {s[0], s[0], s[1], s[1], s[2], s[2], 'F', 'F' }))
595 {
596 gtk_css_parser_error_value (self: parser, format: "Hash code is not a valid hex color.");
597 return FALSE;
598 }
599 break;
600
601 case 4:
602 if (!rgba_init_chars (rgba, s: (char[8]) {s[0], s[0], s[1], s[1], s[2], s[2], s[3], s[3] }))
603 {
604 gtk_css_parser_error_value (self: parser, format: "Hash code is not a valid hex color.");
605 return FALSE;
606 }
607 break;
608
609 case 6:
610 if (!rgba_init_chars (rgba, s: (char[8]) {s[0], s[1], s[2], s[3], s[4], s[5], 'F', 'F' }))
611 {
612 gtk_css_parser_error_value (self: parser, format: "Hash code is not a valid hex color.");
613 return FALSE;
614 }
615 break;
616
617 case 8:
618 if (!rgba_init_chars (rgba, s))
619 {
620 gtk_css_parser_error_value (self: parser, format: "Hash code is not a valid hex color.");
621 return FALSE;
622 }
623 break;
624
625 default:
626 gtk_css_parser_error_value (self: parser, format: "Hash code is not a valid hex color.");
627 return FALSE;
628 break;
629 }
630
631 gtk_css_parser_consume_token (self: parser);
632 return TRUE;
633 }
634 else if (gtk_css_token_is (token, GTK_CSS_TOKEN_IDENT))
635 {
636 if (gtk_css_token_is_ident (token, ident: "transparent"))
637 {
638 *rgba = (GdkRGBA) { 0, 0, 0, 0 };
639 }
640 else if (gdk_rgba_parse (rgba, spec: token->string.string))
641 {
642 /* everything's fine */
643 }
644 else
645 {
646 gtk_css_parser_error_syntax (self: parser, format: "\"%s\" is not a valid color name.", token->string.string);
647 return FALSE;
648 }
649
650 gtk_css_parser_consume_token (self: parser);
651 return TRUE;
652 }
653 else
654 {
655 gtk_css_parser_error_syntax (self: parser, format: "Expected a valid color.");
656 return FALSE;
657 }
658}
659

source code of gtk/gdk/gdkrgba.c