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 | #include "gdkrgba.h" |
27 | #include <string.h> |
28 | #include <errno.h> |
29 | #include <math.h> |
30 | |
31 | #include "fallback-c89.c" |
32 | |
33 | /** |
34 | * SECTION:rgba_colors |
35 | * @Short_description: RGBA colors |
36 | * @Title: RGBA Colors |
37 | * |
38 | * #GdkRGBA is a convenient way to pass rgba colors around. |
39 | * It’s based on cairo’s way to deal with colors and mirrors its behavior. |
40 | * All values are in the range from 0.0 to 1.0 inclusive. So the color |
41 | * (0.0, 0.0, 0.0, 0.0) represents transparent black and |
42 | * (1.0, 1.0, 1.0, 1.0) is opaque white. Other values will be clamped |
43 | * to this range when drawing. |
44 | */ |
45 | |
46 | G_DEFINE_BOXED_TYPE (GdkRGBA, gdk_rgba, |
47 | gdk_rgba_copy, gdk_rgba_free) |
48 | |
49 | /** |
50 | * GdkRGBA: |
51 | * @red: The intensity of the red channel from 0.0 to 1.0 inclusive |
52 | * @green: The intensity of the green channel from 0.0 to 1.0 inclusive |
53 | * @blue: The intensity of the blue channel from 0.0 to 1.0 inclusive |
54 | * @alpha: The opacity of the color from 0.0 for completely translucent to |
55 | * 1.0 for opaque |
56 | * |
57 | * A #GdkRGBA is used to represent a (possibly translucent) |
58 | * color, in a way that is compatible with cairos notion of color. |
59 | */ |
60 | |
61 | /** |
62 | * gdk_rgba_copy: |
63 | * @rgba: a #GdkRGBA |
64 | * |
65 | * Makes a copy of a #GdkRGBA. |
66 | * |
67 | * The result must be freed through gdk_rgba_free(). |
68 | * |
69 | * Returns: A newly allocated #GdkRGBA, with the same contents as @rgba |
70 | * |
71 | * Since: 3.0 |
72 | */ |
73 | GdkRGBA * |
74 | gdk_rgba_copy (const GdkRGBA *rgba) |
75 | { |
76 | return g_slice_dup (GdkRGBA, rgba); |
77 | } |
78 | |
79 | /** |
80 | * gdk_rgba_free: |
81 | * @rgba: a #GdkRGBA |
82 | * |
83 | * Frees a #GdkRGBA created with gdk_rgba_copy() |
84 | * |
85 | * Since: 3.0 |
86 | */ |
87 | void |
88 | gdk_rgba_free (GdkRGBA *rgba) |
89 | { |
90 | g_slice_free (GdkRGBA, rgba); |
91 | } |
92 | |
93 | #define SKIP_WHITESPACES(s) while (*(s) == ' ') (s)++; |
94 | |
95 | /* Parses a single color component from a rgb() or rgba() specification |
96 | * according to CSS3 rules. Compared to exact CSS3 parsing we are liberal |
97 | * in what we accept as follows: |
98 | * |
99 | * - For non-percentage values, we accept floats in the range 0-255 |
100 | * not just [0-9]+ integers |
101 | * - For percentage values we accept any float, not just |
102 | * [ 0-9]+ | [0-9]* “.” [0-9]+ |
103 | * - We accept mixed percentages and non-percentages in a single |
104 | * rgb() or rgba() specification. |
105 | */ |
106 | static gboolean |
107 | parse_rgb_value (const gchar *str, |
108 | gchar **endp, |
109 | gdouble *number) |
110 | { |
111 | const char *p; |
112 | |
113 | *number = g_ascii_strtod (str, endp); |
114 | if (errno == ERANGE || *endp == str || |
115 | isinf (*number) || isnan (*number)) |
116 | return FALSE; |
117 | |
118 | p = *endp; |
119 | |
120 | SKIP_WHITESPACES (p); |
121 | |
122 | if (*p == '%') |
123 | { |
124 | *endp = (char *)(p + 1); |
125 | *number = CLAMP(*number / 100., 0., 1.); |
126 | } |
127 | else |
128 | { |
129 | *number = CLAMP(*number / 255., 0., 1.); |
130 | } |
131 | |
132 | return TRUE; |
133 | } |
134 | |
135 | /** |
136 | * gdk_rgba_parse: |
137 | * @rgba: the #GdkRGBA to fill in |
138 | * @spec: the string specifying the color |
139 | * |
140 | * Parses a textual representation of a color, filling in |
141 | * the @red, @green, @blue and @alpha fields of the @rgba #GdkRGBA. |
142 | * |
143 | * The string can be either one of: |
144 | * - A standard name (Taken from the X11 rgb.txt file). |
145 | * - A hexadecimal value in the form “\#rgb”, “\#rrggbb”, |
146 | * “\#rrrgggbbb” or ”\#rrrrggggbbbb” |
147 | * - A RGB color in the form “rgb(r,g,b)” (In this case the color will |
148 | * have full opacity) |
149 | * - A RGBA color in the form “rgba(r,g,b,a)” |
150 | * |
151 | * Where “r”, “g”, “b” and “a” are respectively the red, green, blue and |
152 | * alpha color values. In the last two cases, r g and b are either integers |
153 | * in the range 0 to 255 or precentage values in the range 0% to 100%, and |
154 | * a is a floating point value in the range 0 to 1. |
155 | * |
156 | * Returns: %TRUE if the parsing succeeded |
157 | * |
158 | * Since: 3.0 |
159 | */ |
160 | gboolean |
161 | gdk_rgba_parse (GdkRGBA *rgba, |
162 | const gchar *spec) |
163 | { |
164 | gboolean has_alpha; |
165 | gdouble r, g, b, a; |
166 | gchar *str = (gchar *) spec; |
167 | gchar *p; |
168 | |
169 | g_return_val_if_fail (spec != NULL, FALSE); |
170 | |
171 | |
172 | if (strncmp (str, "rgba" , 4) == 0) |
173 | { |
174 | has_alpha = TRUE; |
175 | str += 4; |
176 | } |
177 | else if (strncmp (str, "rgb" , 3) == 0) |
178 | { |
179 | has_alpha = FALSE; |
180 | a = 1; |
181 | str += 3; |
182 | } |
183 | else |
184 | { |
185 | PangoColor pango_color; |
186 | |
187 | /* Resort on PangoColor for rgb.txt color |
188 | * map and '#' prefixed colors |
189 | */ |
190 | if (pango_color_parse (&pango_color, str)) |
191 | { |
192 | if (rgba) |
193 | { |
194 | rgba->red = pango_color.red / 65535.; |
195 | rgba->green = pango_color.green / 65535.; |
196 | rgba->blue = pango_color.blue / 65535.; |
197 | rgba->alpha = 1; |
198 | } |
199 | |
200 | return TRUE; |
201 | } |
202 | else |
203 | return FALSE; |
204 | } |
205 | |
206 | SKIP_WHITESPACES (str); |
207 | |
208 | if (*str != '(') |
209 | return FALSE; |
210 | |
211 | str++; |
212 | |
213 | /* Parse red */ |
214 | SKIP_WHITESPACES (str); |
215 | if (!parse_rgb_value (str, &str, &r)) |
216 | return FALSE; |
217 | SKIP_WHITESPACES (str); |
218 | |
219 | if (*str != ',') |
220 | return FALSE; |
221 | |
222 | str++; |
223 | |
224 | /* Parse green */ |
225 | SKIP_WHITESPACES (str); |
226 | if (!parse_rgb_value (str, &str, &g)) |
227 | return FALSE; |
228 | SKIP_WHITESPACES (str); |
229 | |
230 | if (*str != ',') |
231 | return FALSE; |
232 | |
233 | str++; |
234 | |
235 | /* Parse blue */ |
236 | SKIP_WHITESPACES (str); |
237 | if (!parse_rgb_value (str, &str, &b)) |
238 | return FALSE; |
239 | SKIP_WHITESPACES (str); |
240 | |
241 | if (has_alpha) |
242 | { |
243 | if (*str != ',') |
244 | return FALSE; |
245 | |
246 | str++; |
247 | |
248 | SKIP_WHITESPACES (str); |
249 | a = g_ascii_strtod (str, &p); |
250 | if (errno == ERANGE || p == str || |
251 | isinf (a) || isnan (a)) |
252 | return FALSE; |
253 | str = p; |
254 | SKIP_WHITESPACES (str); |
255 | } |
256 | |
257 | if (*str != ')') |
258 | return FALSE; |
259 | |
260 | str++; |
261 | |
262 | SKIP_WHITESPACES (str); |
263 | |
264 | if (*str != '\0') |
265 | return FALSE; |
266 | |
267 | if (rgba) |
268 | { |
269 | rgba->red = CLAMP (r, 0, 1); |
270 | rgba->green = CLAMP (g, 0, 1); |
271 | rgba->blue = CLAMP (b, 0, 1); |
272 | rgba->alpha = CLAMP (a, 0, 1); |
273 | } |
274 | |
275 | return TRUE; |
276 | } |
277 | |
278 | #undef SKIP_WHITESPACES |
279 | |
280 | /** |
281 | * gdk_rgba_hash: |
282 | * @p: (type GdkRGBA): a #GdkRGBA pointer |
283 | * |
284 | * A hash function suitable for using for a hash |
285 | * table that stores #GdkRGBAs. |
286 | * |
287 | * Returns: The hash value for @p |
288 | * |
289 | * Since: 3.0 |
290 | */ |
291 | guint |
292 | gdk_rgba_hash (gconstpointer p) |
293 | { |
294 | const GdkRGBA *rgba = p; |
295 | |
296 | return ((guint) (rgba->red * 65535) + |
297 | ((guint) (rgba->green * 65535) << 11) + |
298 | ((guint) (rgba->blue * 65535) << 22) + |
299 | ((guint) (rgba->alpha * 65535) >> 6)); |
300 | } |
301 | |
302 | /** |
303 | * gdk_rgba_equal: |
304 | * @p1: (type GdkRGBA): a #GdkRGBA pointer |
305 | * @p2: (type GdkRGBA): another #GdkRGBA pointer |
306 | * |
307 | * Compares two RGBA colors. |
308 | * |
309 | * Returns: %TRUE if the two colors compare equal |
310 | * |
311 | * Since: 3.0 |
312 | */ |
313 | gboolean |
314 | gdk_rgba_equal (gconstpointer p1, |
315 | gconstpointer p2) |
316 | { |
317 | const GdkRGBA *rgba1, *rgba2; |
318 | |
319 | rgba1 = p1; |
320 | rgba2 = p2; |
321 | |
322 | if (rgba1->red == rgba2->red && |
323 | rgba1->green == rgba2->green && |
324 | rgba1->blue == rgba2->blue && |
325 | rgba1->alpha == rgba2->alpha) |
326 | return TRUE; |
327 | |
328 | return FALSE; |
329 | } |
330 | |
331 | /** |
332 | * gdk_rgba_to_string: |
333 | * @rgba: a #GdkRGBA |
334 | * |
335 | * Returns a textual specification of @rgba in the form |
336 | * `rgb (r, g, b)` or |
337 | * `rgba (r, g, b, a)`, |
338 | * where “r”, “g”, “b” and “a” represent the red, green, |
339 | * blue and alpha values respectively. r, g, and b are |
340 | * represented as integers in the range 0 to 255, and a |
341 | * is represented as floating point value in the range 0 to 1. |
342 | * |
343 | * These string forms are string forms those supported by |
344 | * the CSS3 colors module, and can be parsed by gdk_rgba_parse(). |
345 | * |
346 | * Note that this string representation may lose some |
347 | * precision, since r, g and b are represented as 8-bit |
348 | * integers. If this is a concern, you should use a |
349 | * different representation. |
350 | * |
351 | * Returns: A newly allocated text string |
352 | * |
353 | * Since: 3.0 |
354 | */ |
355 | gchar * |
356 | gdk_rgba_to_string (const GdkRGBA *rgba) |
357 | { |
358 | if (rgba->alpha > 0.999) |
359 | { |
360 | return g_strdup_printf ("rgb(%d,%d,%d)" , |
361 | (int)(0.5 + CLAMP (rgba->red, 0., 1.) * 255.), |
362 | (int)(0.5 + CLAMP (rgba->green, 0., 1.) * 255.), |
363 | (int)(0.5 + CLAMP (rgba->blue, 0., 1.) * 255.)); |
364 | } |
365 | else |
366 | { |
367 | gchar alpha[G_ASCII_DTOSTR_BUF_SIZE]; |
368 | |
369 | g_ascii_formatd (alpha, G_ASCII_DTOSTR_BUF_SIZE, "%g" , CLAMP (rgba->alpha, 0, 1)); |
370 | |
371 | return g_strdup_printf ("rgba(%d,%d,%d,%s)" , |
372 | (int)(0.5 + CLAMP (rgba->red, 0., 1.) * 255.), |
373 | (int)(0.5 + CLAMP (rgba->green, 0., 1.) * 255.), |
374 | (int)(0.5 + CLAMP (rgba->blue, 0., 1.) * 255.), |
375 | alpha); |
376 | } |
377 | } |
378 | |