1 /* This file is part of GEGL
2 *
3 * GEGL is free software; you can redistribute it and/or
4 * modify it under the terms of the GNU Lesser General Public
5 * License as published by the Free Software Foundation; either
6 * version 3 of the License, or (at your option) any later version.
7 *
8 * GEGL is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public
14 * License along with GEGL; if not, see <https://www.gnu.org/licenses/>.
15 *
16 * Copyright 2006 Martin Nordholts <enselic@hotmail.com>
17 */
18
19 #include "config.h"
20
21 #include <string.h>
22
23 #include <glib-object.h>
24
25 #include "gegl.h"
26 #include "gegl-types-internal.h"
27 #include "gegl-color.h"
28
29
30 enum
31 {
32 PROP_0,
33 PROP_STRING
34 };
35
36 typedef struct _ColorNameEntity ColorNameEntity;
37
38 struct _GeglColorPrivate
39 {
40 const Babl *format;
41
42 union
43 {
44 guint8 pixel[40];
45 gdouble alignment;
46 };
47 };
48
49 struct _ColorNameEntity
50 {
51 const gchar *color_name;
52 const gfloat rgba_color[4];
53 };
54
55 static gboolean parse_float_argument_list (float rgba_color[4],
56 GScanner *scanner,
57 gint num_arguments);
58 static gboolean parse_color_name (float rgba_color[4],
59 const gchar *color_string);
60 static gboolean parse_hex (float rgba_color[4],
61 const gchar *color_string);
62 static void set_property (GObject *gobject,
63 guint prop_id,
64 const GValue *value,
65 GParamSpec *pspec);
66 static void get_property (GObject *gobject,
67 guint prop_id,
68 GValue *value,
69 GParamSpec *pspec);
70
71 /* These color names are based on those defined in the HTML 4.01 standard. See
72 * http://www.w3.org/TR/html4/types.html#h-6.5
73 *
74 * Note: these values are stored with gamma
75 */
76 static const ColorNameEntity color_names[] =
77 {
78 { "black", { 0.f, 0.f, 0.f, 1.f } },
79 { "silver", { 0.75294f, 0.75294f, 0.75294f, 1.f } },
80 { "gray", { 0.50196f, 0.50196f, 0.50196f, 1.f } },
81 { "white", { 1.f, 1.f, 1.f, 1.f } },
82 { "maroon", { 0.50196f, 0.f, 0.f, 1.f } },
83 { "red", { 1.f, 0.f, 0.f, 1.f } },
84 { "purple", { 0.50196f, 0.f, 0.50196f, 1.f } },
85 { "fuchsia", { 1.f, 0.f, 1.f, 1.f } },
86 { "green", { 0.f, 0.50196f, 0.f, 1.f } },
87 { "lime", { 0.f, 1.f, 0.f, 1.f } },
88 { "olive", { 0.50196f, 0.50196f, 0.f, 1.f } },
89 { "yellow", { 1.f, 1.f, 0.f, 1.f } },
90 { "navy", { 0.f, 0.f, 0.50196f, 1.f } },
91 { "blue", { 0.f, 0.f, 1.f, 1.f } },
92 { "teal", { 0.f, 0.50196f, 0.50196f, 1.f } },
93 { "aqua", { 0.f, 1.f, 1.f, 1.f } },
94 { "none", { 0.f, 0.f, 0.f, 0.f } },
95 { "transparent", { 0.f, 0.f, 0.f, 0.f } }
96 };
97
98 /* Copied into GeglColor:s instances when parsing a color from a string fails. */
99 static const gfloat parsing_error_color[4] = { 0.f, 1.f, 1.f, 0.67f };
100
101 /* Copied into all GeglColor:s at their instantiation. */
102 static const gfloat init_color[4] = { 1.f, 1.f, 1.f, 1.f };
103
G_DEFINE_TYPE_WITH_PRIVATE(GeglColor,gegl_color,G_TYPE_OBJECT)104 G_DEFINE_TYPE_WITH_PRIVATE (GeglColor, gegl_color, G_TYPE_OBJECT)
105
106
107 static void
108 gegl_color_init (GeglColor *self)
109 {
110 self->priv = gegl_color_get_instance_private ((self));
111
112 self->priv->format = gegl_babl_rgba_linear_float ();
113
114 memcpy (self->priv->pixel, init_color, sizeof (init_color));
115 }
116
117 static void
gegl_color_class_init(GeglColorClass * klass)118 gegl_color_class_init (GeglColorClass *klass)
119 {
120 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
121
122 gobject_class->set_property = set_property;
123 gobject_class->get_property = get_property;
124
125 g_object_class_install_property (gobject_class, PROP_STRING,
126 g_param_spec_string ("string",
127 "String",
128 "A String representation of the GeglColor",
129 "",
130 G_PARAM_READWRITE |
131 G_PARAM_STATIC_STRINGS));
132 }
133
134 static gboolean
parse_float_argument_list(float rgba_color[4],GScanner * scanner,gint num_arguments)135 parse_float_argument_list (float rgba_color[4],
136 GScanner *scanner,
137 gint num_arguments)
138 {
139 GTokenType token_type;
140 GTokenValue token_value;
141 gint i;
142
143 /* Make sure there is a leading '(' */
144 if (g_scanner_get_next_token (scanner) != G_TOKEN_LEFT_PAREN)
145 {
146 return FALSE;
147 }
148
149 /* Iterate through the arguments and copy each value
150 * to the rgba_color array of GeglColor.
151 */
152 for (i = 0; i < num_arguments; ++i)
153 {
154 switch (g_scanner_get_next_token (scanner))
155 {
156 case G_TOKEN_FLOAT:
157 token_value = g_scanner_cur_value (scanner);
158 rgba_color[i] = token_value.v_float;
159 break;
160
161 case G_TOKEN_INT:
162 token_value = g_scanner_cur_value (scanner);
163 rgba_color[i] = token_value.v_int64;
164 break;
165
166 default:
167 return FALSE;
168 }
169
170 /* Verify that there is a ',' after each float, except the last one */
171 if (i < (num_arguments - 1))
172 {
173 token_type = g_scanner_get_next_token (scanner);
174 if (token_type != G_TOKEN_COMMA)
175 {
176 return FALSE;
177 }
178 }
179 }
180
181 /* Make sure there is a trailing ')' and that that is the last token. */
182 if (g_scanner_get_next_token (scanner) == G_TOKEN_RIGHT_PAREN &&
183 g_scanner_get_next_token (scanner) == G_TOKEN_EOF)
184 {
185 return TRUE;
186 }
187
188 return FALSE;
189 }
190
191 static gboolean
parse_color_name(float rgba_color[4],const gchar * color_string)192 parse_color_name (float rgba_color[4],
193 const gchar *color_string)
194 {
195 gsize i;
196
197 for (i = 0; i < G_N_ELEMENTS (color_names); ++i)
198 {
199 if (g_ascii_strcasecmp (color_names[i].color_name, color_string) == 0)
200 {
201 memcpy (rgba_color, color_names[i].rgba_color, sizeof (color_names[i].rgba_color));
202 return TRUE;
203 }
204 }
205
206 return FALSE;
207 }
208
209 static gboolean
parse_hex(float rgba_color[4],const gchar * color_string)210 parse_hex (float rgba_color[4],
211 const gchar *color_string)
212 {
213 gint i;
214 gsize string_length = strlen (color_string);
215
216 if (string_length == 7 || /* #rrggbb */
217 string_length == 9) /* #rrggbbaa */
218 {
219 gint num_iterations = (string_length - 1) / 2;
220 for (i = 0; i < num_iterations; ++i)
221 {
222 if (g_ascii_isxdigit (color_string[2 * i + 1]) &&
223 g_ascii_isxdigit (color_string[2 * i + 2]))
224 {
225 rgba_color[i] = (g_ascii_xdigit_value (color_string[2 * i + 1]) << 4 |
226 g_ascii_xdigit_value (color_string[2 * i + 2])) / 255.f;
227 }
228 else
229 {
230 return FALSE;
231 }
232 }
233
234 /* Successful #rrggbb(aa) parsing! */
235 return TRUE;
236 }
237 else if (string_length == 4 || /* #rgb */
238 string_length == 5) /* #rgba */
239 {
240 gint num_iterations = string_length - 1;
241 for (i = 0; i < num_iterations; ++i)
242 {
243 if (g_ascii_isxdigit (color_string[i + 1]))
244 {
245 rgba_color[i] = (g_ascii_xdigit_value (color_string[i + 1]) << 4 |
246 g_ascii_xdigit_value (color_string[i + 1])) / 255.f;
247 }
248 else
249 {
250 return FALSE;
251 }
252 }
253
254 /* Successful #rgb(a) parsing! */
255 return TRUE;
256 }
257
258 /* String was of unsupported length. */
259 return FALSE;
260 }
261
262 #if 0
263 const gfloat *
264 gegl_color_float4 (GeglColor *self)
265 {
266 g_return_val_if_fail (GEGL_IS_COLOR (self), NULL);
267 return &self->rgba_color[0];
268 }
269 #endif
270
271 void
gegl_color_set_pixel(GeglColor * color,const Babl * format,const void * pixel)272 gegl_color_set_pixel (GeglColor *color,
273 const Babl *format,
274 const void *pixel)
275 {
276 gint bpp;
277
278 g_return_if_fail (GEGL_IS_COLOR (color));
279 g_return_if_fail (format);
280 g_return_if_fail (pixel);
281
282 bpp = babl_format_get_bytes_per_pixel (format);
283
284 if (bpp <= sizeof (color->priv->pixel))
285 color->priv->format = format;
286 else
287 color->priv->format = gegl_babl_rgba_linear_float ();
288
289 babl_process (babl_fish (format, color->priv->format),
290 pixel, color->priv->pixel, 1);
291 }
292
293 const Babl *
gegl_color_get_format(GeglColor * color)294 gegl_color_get_format (GeglColor *color)
295 {
296 g_return_val_if_fail (GEGL_IS_COLOR (color), NULL);
297 return color->priv->format;
298 }
299
300 void
gegl_color_get_pixel(GeglColor * color,const Babl * format,void * pixel)301 gegl_color_get_pixel (GeglColor *color,
302 const Babl *format,
303 void *pixel)
304 {
305 g_return_if_fail (GEGL_IS_COLOR (color));
306 g_return_if_fail (format);
307 g_return_if_fail (pixel);
308
309 babl_process (babl_fish (color->priv->format, format),
310 color->priv->pixel, pixel, 1);
311 }
312
313 void
gegl_color_set_rgba(GeglColor * self,gdouble r,gdouble g,gdouble b,gdouble a)314 gegl_color_set_rgba (GeglColor *self,
315 gdouble r,
316 gdouble g,
317 gdouble b,
318 gdouble a)
319 {
320 const gfloat rgba[4] = {r, g, b, a};
321
322 g_return_if_fail (GEGL_IS_COLOR (self));
323
324 gegl_color_set_pixel (self, gegl_babl_rgba_linear_float (), rgba);
325 }
326
327 void
gegl_color_get_rgba(GeglColor * self,gdouble * r,gdouble * g,gdouble * b,gdouble * a)328 gegl_color_get_rgba (GeglColor *self,
329 gdouble *r,
330 gdouble *g,
331 gdouble *b,
332 gdouble *a)
333 {
334 gfloat rgba[4];
335
336 g_return_if_fail (GEGL_IS_COLOR (self));
337
338 gegl_color_get_pixel (self, gegl_babl_rgba_linear_float (), rgba);
339
340 if (r) *r = rgba[0];
341 if (g) *g = rgba[1];
342 if (b) *b = rgba[2];
343 if (a) *a = rgba[3];
344 }
345
346 static void
gegl_color_set_from_string(GeglColor * self,const gchar * color_string)347 gegl_color_set_from_string (GeglColor *self,
348 const gchar *color_string)
349 {
350 GScanner *scanner;
351 GTokenType token_type;
352 GTokenValue token_value;
353 gboolean color_parsing_successfull;
354 float cmyka[5] = {0.0, 0.0, 0.0, 1.0, 1.0};
355 const Babl *format = gegl_babl_rgba_float ();
356 float *rgba=&cmyka[0];
357
358 scanner = g_scanner_new (NULL);
359 scanner->config->cpair_comment_single = "";
360 g_scanner_input_text (scanner, color_string, strlen (color_string));
361
362 token_type = g_scanner_get_next_token (scanner);
363 token_value = g_scanner_cur_value (scanner);
364
365 if (token_type == G_TOKEN_IDENTIFIER &&
366 g_ascii_strcasecmp (token_value.v_identifier, "cmyk") == 0)
367 {
368 color_parsing_successfull = parse_float_argument_list (cmyka, scanner, 4);
369 for (int i = 0; i<4;i++)
370 cmyka[i] /= 100.0;
371
372 format = babl_format ("CMYK float");
373 }
374 else if (token_type == G_TOKEN_IDENTIFIER &&
375 g_ascii_strcasecmp (token_value.v_identifier, "cmyka") == 0)
376 {
377 color_parsing_successfull = parse_float_argument_list (cmyka, scanner, 5);
378 for (int i = 0; i<4;i++)
379 cmyka[i] /= 100.0;
380 format = babl_format ("CMYKA float");
381 }
382 else if (token_type == G_TOKEN_IDENTIFIER &&
383 g_ascii_strcasecmp (token_value.v_identifier, "rgb") == 0)
384 {
385 color_parsing_successfull = parse_float_argument_list (rgba, scanner, 3);
386 format = gegl_babl_rgba_linear_float ();
387 }
388 else if (token_type == G_TOKEN_IDENTIFIER &&
389 g_ascii_strcasecmp (token_value.v_identifier, "rgba") == 0)
390 {
391 rgba[3] = 1.0;
392 color_parsing_successfull = parse_float_argument_list (rgba, scanner, 4);
393 format = gegl_babl_rgba_linear_float ();
394 }
395 else if (token_type == '#')
396 {
397 color_parsing_successfull = parse_hex (rgba, color_string);
398 }
399 else if (token_type == G_TOKEN_IDENTIFIER)
400 {
401 color_parsing_successfull = parse_color_name (rgba, color_string);
402 }
403 else
404 {
405 color_parsing_successfull = FALSE;
406 }
407
408 if (color_parsing_successfull)
409 {
410 gegl_color_set_pixel(self, format, cmyka);
411 }
412 else
413 {
414 gegl_color_set_pixel(self,
415 gegl_babl_rgba_linear_float (),
416 parsing_error_color);
417
418 g_warning ("Parsing of color string \"%s\" into GeglColor failed! "
419 "Using transparent cyan instead",
420 color_string);
421 }
422
423 g_scanner_destroy (scanner);
424 }
425
426 static gchar *
gegl_color_get_string(GeglColor * color)427 gegl_color_get_string (GeglColor *color)
428 {
429 gfloat rgba[4];
430
431 gegl_color_get_pixel (color, gegl_babl_rgba_linear_float (), rgba);
432
433 if (babl_get_model_flags (color->priv->format) & BABL_MODEL_FLAG_CMYK)
434 {
435 gfloat cmyka[5];
436 gchar buf [5][G_ASCII_DTOSTR_BUF_SIZE];
437 gegl_color_get_pixel (color, babl_format ("CMYKA float"), cmyka);
438 g_ascii_formatd (buf[0], G_ASCII_DTOSTR_BUF_SIZE, "%1.1f", cmyka[0]*100);
439 g_ascii_formatd (buf[1], G_ASCII_DTOSTR_BUF_SIZE, "%1.1f", cmyka[1]*100);
440 g_ascii_formatd (buf[2], G_ASCII_DTOSTR_BUF_SIZE, "%1.1f", cmyka[2]*100);
441 g_ascii_formatd (buf[3], G_ASCII_DTOSTR_BUF_SIZE, "%1.1f", cmyka[3]*100);
442 g_ascii_formatd (buf[4], G_ASCII_DTOSTR_BUF_SIZE, "%1.1f", cmyka[3]);
443 if (cmyka[4] == 1.0)
444 {
445 return g_strdup_printf ("cmyk(%s, %s, %s, %s)", buf[0], buf[1], buf[2], buf[3]);
446 }
447 else
448 {
449 return g_strdup_printf ("cmyka(%s, %s, %s, %s, %s)", buf[0], buf[1], buf[2], buf[3], buf[4]);
450 }
451 }
452
453 if (rgba[3] == 1.0)
454 {
455 gchar buf [3][G_ASCII_DTOSTR_BUF_SIZE];
456 g_ascii_formatd (buf[0], G_ASCII_DTOSTR_BUF_SIZE, "%1.3f", rgba[0]);
457 g_ascii_formatd (buf[1], G_ASCII_DTOSTR_BUF_SIZE, "%1.3f", rgba[1]);
458 g_ascii_formatd (buf[2], G_ASCII_DTOSTR_BUF_SIZE, "%1.3f", rgba[2]);
459 return g_strdup_printf ("rgb(%s, %s, %s)", buf[0], buf[1], buf[2]);
460 }
461 else
462 {
463 gchar buf [4][G_ASCII_DTOSTR_BUF_SIZE];
464 g_ascii_formatd (buf[0], G_ASCII_DTOSTR_BUF_SIZE, "%1.3f", rgba[0]);
465 g_ascii_formatd (buf[1], G_ASCII_DTOSTR_BUF_SIZE, "%1.3f", rgba[1]);
466 g_ascii_formatd (buf[2], G_ASCII_DTOSTR_BUF_SIZE, "%1.3f", rgba[2]);
467 g_ascii_formatd (buf[3], G_ASCII_DTOSTR_BUF_SIZE, "%1.3f", rgba[3]);
468 return g_strdup_printf ("rgba(%s, %s, %s, %s)", buf[0], buf[1], buf[2], buf[3]);
469 }
470 }
471
472
473 static void
set_property(GObject * gobject,guint property_id,const GValue * value,GParamSpec * pspec)474 set_property (GObject *gobject,
475 guint property_id,
476 const GValue *value,
477 GParamSpec *pspec)
478 {
479 GeglColor *color = GEGL_COLOR (gobject);
480
481 switch (property_id)
482 {
483 case PROP_STRING:
484 gegl_color_set_from_string (color, g_value_get_string (value));
485 break;
486
487 default:
488 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, pspec);
489 break;
490 }
491 }
492
493 static void
get_property(GObject * gobject,guint property_id,GValue * value,GParamSpec * pspec)494 get_property (GObject *gobject,
495 guint property_id,
496 GValue *value,
497 GParamSpec *pspec)
498 {
499 GeglColor *color = GEGL_COLOR (gobject);
500
501 switch (property_id)
502 {
503 case PROP_STRING:
504 {
505 gchar *string = gegl_color_get_string (color);
506 g_value_set_string (value, string);
507 g_free (string);
508 }
509 break;
510
511 default:
512 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, property_id, pspec);
513 break;
514 }
515 }
516
517 GeglColor *
gegl_color_new(const gchar * string)518 gegl_color_new (const gchar *string)
519 {
520 if (string)
521 return g_object_new (GEGL_TYPE_COLOR, "string", string, NULL);
522
523 return g_object_new (GEGL_TYPE_COLOR, NULL);
524 }
525
526 GeglColor *
gegl_color_duplicate(GeglColor * color)527 gegl_color_duplicate (GeglColor *color)
528 {
529 GeglColor *new;
530
531 g_return_val_if_fail (GEGL_IS_COLOR (color), NULL);
532
533 new = g_object_new (GEGL_TYPE_COLOR, NULL);
534
535 memcpy (new->priv, color->priv, sizeof (GeglColorPrivate));
536
537 return new;
538 }
539
540 /* --------------------------------------------------------------------------
541 * A GParamSpec class to describe behavior of GeglColor as an object property
542 * follows.
543 * --------------------------------------------------------------------------
544 */
545
546 #define GEGL_PARAM_COLOR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GEGL_TYPE_PARAM_COLOR, GeglParamColor))
547 #define GEGL_IS_PARAM_COLOR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GEGL_TYPE_PARAM_COLOR))
548
549 typedef struct _GeglParamColor GeglParamColor;
550
551 struct _GeglParamColor
552 {
553 GParamSpec parent_instance;
554
555 GeglColor *default_color;
556 };
557
558 static void
gegl_param_color_init(GParamSpec * self)559 gegl_param_color_init (GParamSpec *self)
560 {
561 GEGL_PARAM_COLOR (self)->default_color = NULL;
562 }
563
564 GeglColor *
gegl_param_spec_color_get_default(GParamSpec * self)565 gegl_param_spec_color_get_default (GParamSpec *self)
566 {
567 return GEGL_PARAM_COLOR (self)->default_color;
568 }
569
570 static void
gegl_param_color_finalize(GParamSpec * self)571 gegl_param_color_finalize (GParamSpec *self)
572 {
573 GeglParamColor *param_color = GEGL_PARAM_COLOR (self);
574 GParamSpecClass *parent_class = g_type_class_peek (g_type_parent (GEGL_TYPE_PARAM_COLOR));
575
576 g_clear_object (¶m_color->default_color);
577
578 parent_class->finalize (self);
579 }
580
581 static void
gegl_param_color_set_default(GParamSpec * param_spec,GValue * value)582 gegl_param_color_set_default (GParamSpec *param_spec,
583 GValue *value)
584 {
585 GeglParamColor *gegl_color = GEGL_PARAM_COLOR (param_spec);
586
587 if (gegl_color->default_color)
588 g_value_take_object (value, gegl_color_duplicate (gegl_color->default_color));
589 }
590
591 GType
gegl_param_color_get_type(void)592 gegl_param_color_get_type (void)
593 {
594 static GType param_color_type = 0;
595
596 if (G_UNLIKELY (param_color_type == 0))
597 {
598 static GParamSpecTypeInfo param_color_type_info = {
599 sizeof (GeglParamColor),
600 0,
601 gegl_param_color_init,
602 0,
603 gegl_param_color_finalize,
604 gegl_param_color_set_default,
605 NULL,
606 NULL
607 };
608 param_color_type_info.value_type = GEGL_TYPE_COLOR;
609
610 param_color_type = g_param_type_register_static ("GeglParamColor",
611 ¶m_color_type_info);
612 }
613
614 return param_color_type;
615 }
616
617 GParamSpec *
gegl_param_spec_color(const gchar * name,const gchar * nick,const gchar * blurb,GeglColor * default_color,GParamFlags flags)618 gegl_param_spec_color (const gchar *name,
619 const gchar *nick,
620 const gchar *blurb,
621 GeglColor *default_color,
622 GParamFlags flags)
623 {
624 GeglParamColor *param_color;
625
626 param_color = g_param_spec_internal (GEGL_TYPE_PARAM_COLOR,
627 name, nick, blurb, flags);
628
629 param_color->default_color = default_color;
630 if (default_color)
631 g_object_ref (default_color);
632
633 return G_PARAM_SPEC (param_color);
634 }
635
636 GParamSpec *
gegl_param_spec_color_from_string(const gchar * name,const gchar * nick,const gchar * blurb,const gchar * default_color_string,GParamFlags flags)637 gegl_param_spec_color_from_string (const gchar *name,
638 const gchar *nick,
639 const gchar *blurb,
640 const gchar *default_color_string,
641 GParamFlags flags)
642 {
643 GeglParamColor *param_color;
644
645 param_color = g_param_spec_internal (GEGL_TYPE_PARAM_COLOR,
646 name, nick, blurb, flags);
647
648 param_color->default_color = g_object_new (GEGL_TYPE_COLOR,
649 "string", default_color_string,
650 NULL);
651
652 return G_PARAM_SPEC (param_color);
653 }
654