1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2  *
3  * Copyright (C) 2010-2014 Richard Hughes <richard@hughsie.com>
4  *
5  * Licensed under the GNU Lesser General Public License Version 2.1
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
20  */
21 
22 /**
23  * SECTION:cd-color
24  * @short_description: Color object data functionality
25  *
26  * Functions to manipulate color values.
27  */
28 
29 #include "config.h"
30 
31 #include <math.h>
32 #include <glib-object.h>
33 #include <lcms2.h>
34 
35 #include "cd-color.h"
36 #include "cd-interp.h"
37 #include "cd-interp-akima.h"
38 #include "cd-interp-linear.h"
39 
40 /* this is private */
41 struct _CdColorSwatch {
42 	gchar		*name;
43 	CdColorLab	 value;
44 };
45 
46 /**
47  * cd_color_xyz_dup:
48  *
49  * Since: 0.1.27
50  **/
51 CdColorXYZ *
cd_color_xyz_dup(const CdColorXYZ * src)52 cd_color_xyz_dup (const CdColorXYZ *src)
53 {
54 	CdColorXYZ *dest;
55 	g_return_val_if_fail (src != NULL, NULL);
56 	dest = cd_color_xyz_new ();
57 	dest->X = src->X;
58 	dest->Y = src->Y;
59 	dest->Z = src->Z;
60 	return dest;
61 }
62 
63 /**
64  * cd_color_rgb_dup:
65  *
66  * Since: 0.1.27
67  **/
68 CdColorRGB *
cd_color_rgb_dup(const CdColorRGB * src)69 cd_color_rgb_dup (const CdColorRGB *src)
70 {
71 	CdColorRGB *dest;
72 	g_return_val_if_fail (src != NULL, NULL);
73 	dest = cd_color_rgb_new ();
74 	dest->R = src->R;
75 	dest->G = src->G;
76 	dest->B = src->B;
77 	return dest;
78 }
79 
80 /**
81  * cd_color_lab_dup:
82  *
83  * Since: 0.1.32
84  **/
85 CdColorLab *
cd_color_lab_dup(const CdColorLab * src)86 cd_color_lab_dup (const CdColorLab *src)
87 {
88 	CdColorLab *dest;
89 	g_return_val_if_fail (src != NULL, NULL);
90 	dest = cd_color_lab_new ();
91 	dest->L = src->L;
92 	dest->a = src->a;
93 	dest->b = src->b;
94 	return dest;
95 }
96 
97 /**
98  * cd_color_yxy_dup:
99  *
100  * Since: 0.1.27
101  **/
102 CdColorYxy *
cd_color_yxy_dup(const CdColorYxy * src)103 cd_color_yxy_dup (const CdColorYxy *src)
104 {
105 	CdColorYxy *dest;
106 	g_return_val_if_fail (src != NULL, NULL);
107 	dest = cd_color_yxy_new ();
108 	dest->Y = src->Y;
109 	dest->x = src->x;
110 	dest->y = src->y;
111 	return dest;
112 }
113 
114 /**
115  * cd_color_uvw_dup:
116  *
117  * Since: 1.1.6
118  **/
119 CdColorUVW *
cd_color_uvw_dup(const CdColorUVW * src)120 cd_color_uvw_dup (const CdColorUVW *src)
121 {
122 	CdColorUVW *dest;
123 	g_return_val_if_fail (src != NULL, NULL);
124 	dest = cd_color_uvw_new ();
125 	dest->U = src->U;
126 	dest->V = src->V;
127 	dest->W = src->W;
128 	return dest;
129 }
130 
131 /**
132  * cd_color_swatch_dup:
133  *
134  * Since: 0.1.32
135  **/
136 CdColorSwatch *
cd_color_swatch_dup(const CdColorSwatch * src)137 cd_color_swatch_dup (const CdColorSwatch *src)
138 {
139 	CdColorSwatch *dest;
140 	g_return_val_if_fail (src != NULL, NULL);
141 	dest = cd_color_swatch_new ();
142 	dest->name = g_strdup (src->name);
143 	cd_color_lab_copy (&src->value, &dest->value);
144 	return dest;
145 }
146 
147 /**
148  * cd_color_swatch_get_name:
149  *
150  * Since: 0.1.32
151  **/
152 const gchar *
cd_color_swatch_get_name(const CdColorSwatch * swatch)153 cd_color_swatch_get_name (const CdColorSwatch *swatch)
154 {
155 	g_return_val_if_fail (swatch != NULL, NULL);
156 	return swatch->name;
157 }
158 
159 /**
160  * cd_color_swatch_get_value:
161  *
162  * Since: 0.1.32
163  **/
164 const CdColorLab *
cd_color_swatch_get_value(const CdColorSwatch * swatch)165 cd_color_swatch_get_value (const CdColorSwatch *swatch)
166 {
167 	g_return_val_if_fail (swatch != NULL, NULL);
168 	return &swatch->value;
169 }
170 
171 /**
172  * cd_color_xyz_get_type:
173  *
174  * Gets a specific type.
175  *
176  * Return value: a #GType
177  *
178  * Since: 0.1.6
179  **/
180 GType
cd_color_xyz_get_type(void)181 cd_color_xyz_get_type (void)
182 {
183 	static GType type_id = 0;
184 	if (!type_id)
185 		type_id = g_boxed_type_register_static ("CdColorXYZ",
186 							(GBoxedCopyFunc) cd_color_xyz_dup,
187 							(GBoxedFreeFunc) cd_color_xyz_free);
188 	return type_id;
189 }
190 
191 /**
192  * cd_color_rgb_get_type:
193  *
194  * Gets a specific type.
195  *
196  * Return value: a #GType
197  *
198  * Since: 0.1.6
199  **/
200 GType
cd_color_rgb_get_type(void)201 cd_color_rgb_get_type (void)
202 {
203 	static GType type_id = 0;
204 	if (!type_id)
205 		type_id = g_boxed_type_register_static ("CdColorRGB",
206 							(GBoxedCopyFunc) cd_color_rgb_dup,
207 							(GBoxedFreeFunc) cd_color_rgb_free);
208 	return type_id;
209 }
210 
211 /**
212  * cd_color_lab_get_type:
213  *
214  * Gets a specific type.
215  *
216  * Return value: a #GType
217  *
218  * Since: 0.1.32
219  **/
220 GType
cd_color_lab_get_type(void)221 cd_color_lab_get_type (void)
222 {
223 	static GType type_id = 0;
224 	if (!type_id)
225 		type_id = g_boxed_type_register_static ("CdColorLab",
226 							(GBoxedCopyFunc) cd_color_lab_dup,
227 							(GBoxedFreeFunc) cd_color_lab_free);
228 	return type_id;
229 }
230 
231 /**
232  * cd_color_yxy_get_type:
233  *
234  * Gets a specific type.
235  *
236  * Return value: a #GType
237  *
238  * Since: 0.1.6
239  **/
240 GType
cd_color_yxy_get_type(void)241 cd_color_yxy_get_type (void)
242 {
243 	static GType type_id = 0;
244 	if (!type_id)
245 		type_id = g_boxed_type_register_static ("CdColorYxy",
246 							(GBoxedCopyFunc) cd_color_yxy_dup,
247 							(GBoxedFreeFunc) cd_color_yxy_free);
248 	return type_id;
249 }
250 
251 /**
252  * cd_color_uvw_get_type:
253  *
254  * Gets a specific type.
255  *
256  * Return value: a #GType
257  *
258  * Since: 1.1.6
259  **/
260 GType
cd_color_uvw_get_type(void)261 cd_color_uvw_get_type (void)
262 {
263 	static GType type_id = 0;
264 	if (!type_id)
265 		type_id = g_boxed_type_register_static ("CdColorUVW",
266 							(GBoxedCopyFunc) cd_color_uvw_dup,
267 							(GBoxedFreeFunc) cd_color_uvw_free);
268 	return type_id;
269 }
270 
271 /**
272  * cd_color_swatch_get_type:
273  *
274  * Gets a specific type.
275  *
276  * Return value: a #GType
277  *
278  * Since: 0.1.32
279  **/
280 GType
cd_color_swatch_get_type(void)281 cd_color_swatch_get_type (void)
282 {
283 	static GType type_id = 0;
284 	if (!type_id)
285 		type_id = g_boxed_type_register_static ("CdColorSwatch",
286 							(GBoxedCopyFunc) cd_color_swatch_dup,
287 							(GBoxedFreeFunc) cd_color_swatch_free);
288 	return type_id;
289 }
290 
291 /**
292  * cd_color_xyz_new:
293  *
294  * Allocates a color value.
295  *
296  * Return value: A newly allocated #CdColorXYZ object
297  *
298  * Since: 0.1.0
299  **/
300 CdColorXYZ *
cd_color_xyz_new(void)301 cd_color_xyz_new (void)
302 {
303 	return g_slice_new0 (CdColorXYZ);
304 }
305 
306 /**
307  * cd_color_rgb_new:
308  *
309  * Allocates a color value.
310  *
311  * Return value: A newly allocated #CdColorRGB object
312  *
313  * Since: 0.1.0
314  **/
315 CdColorRGB *
cd_color_rgb_new(void)316 cd_color_rgb_new (void)
317 {
318 	return g_slice_new0 (CdColorRGB);
319 }
320 
321 /**
322  * cd_color_lab_new:
323  *
324  * Allocates a color value.
325  *
326  * Return value: A newly allocated #CdColorLab object
327  *
328  * Since: 0.1.32
329  **/
330 CdColorLab *
cd_color_lab_new(void)331 cd_color_lab_new (void)
332 {
333 	return g_slice_new0 (CdColorLab);
334 }
335 
336 /**
337  * cd_color_yxy_new:
338  *
339  * Allocates a color value.
340  *
341  * Return value: A newly allocated #CdColorYxy object
342  *
343  * Since: 0.1.0
344  **/
345 CdColorYxy *
cd_color_yxy_new(void)346 cd_color_yxy_new (void)
347 {
348 	return g_slice_new0 (CdColorYxy);
349 }
350 
351 /**
352  * cd_color_uvw_new:
353  *
354  * Allocates a color value.
355  *
356  * Return value: A newly allocated #CdColorUVW object
357  *
358  * Since: 1.1.6
359  **/
360 CdColorUVW *
cd_color_uvw_new(void)361 cd_color_uvw_new (void)
362 {
363 	return g_slice_new0 (CdColorUVW);
364 }
365 
366 /**
367  * cd_color_swatch_new:
368  *
369  * Allocates a color value.
370  *
371  * Return value: A newly allocated #CdColorSwatch object
372  *
373  * Since: 0.1.32
374  **/
375 CdColorSwatch *
cd_color_swatch_new(void)376 cd_color_swatch_new (void)
377 {
378 	return g_slice_new0 (CdColorSwatch);
379 }
380 
381 /**
382  * cd_color_xyz_free:
383  * @src: the color object
384  *
385  * Deallocates a color value.
386  *
387  * Since: 0.1.0
388  **/
389 void
cd_color_xyz_free(CdColorXYZ * src)390 cd_color_xyz_free (CdColorXYZ *src)
391 {
392 	g_slice_free (CdColorXYZ, src);
393 }
394 
395 /**
396  * cd_color_rgb_free:
397  * @src: the color object
398  *
399  * Deallocates a color value.
400  *
401  * Since: 0.1.0
402  **/
403 void
cd_color_rgb_free(CdColorRGB * src)404 cd_color_rgb_free (CdColorRGB *src)
405 {
406 	g_slice_free (CdColorRGB, src);
407 }
408 
409 /**
410  * cd_color_lab_free:
411  * @src: the color object
412  *
413  * Deallocates a color value.
414  *
415  * Since: 0.1.32
416  **/
417 void
cd_color_lab_free(CdColorLab * src)418 cd_color_lab_free (CdColorLab *src)
419 {
420 	g_slice_free (CdColorLab, src);
421 }
422 
423 /**
424  * cd_color_yxy_free:
425  * @src: the color object
426  *
427  * Deallocates a color value.
428  *
429  * Since: 0.1.0
430  **/
431 void
cd_color_yxy_free(CdColorYxy * src)432 cd_color_yxy_free (CdColorYxy *src)
433 {
434 	g_slice_free (CdColorYxy, src);
435 }
436 
437 /**
438  * cd_color_uvw_free:
439  * @src: the color object
440  *
441  * Deallocates a color value.
442  *
443  * Since: 1.1.6
444  **/
445 void
cd_color_uvw_free(CdColorUVW * src)446 cd_color_uvw_free (CdColorUVW *src)
447 {
448 	g_slice_free (CdColorUVW, src);
449 }
450 
451 /**
452  * cd_color_swatch_free:
453  * @src: the color object
454  *
455  * Deallocates a color swatch.
456  *
457  * Since: 0.1.32
458  **/
459 void
cd_color_swatch_free(CdColorSwatch * src)460 cd_color_swatch_free (CdColorSwatch *src)
461 {
462 	g_free (src->name);
463 	g_slice_free (CdColorSwatch, src);
464 }
465 
466 /**
467  * cd_color_xyz_set:
468  * @dest: the destination color
469  * @X: component value
470  * @Y: component value
471  * @Z: component value
472  *
473  * Initialises a color value.
474  *
475  * Since: 0.1.27
476  **/
477 void
cd_color_xyz_set(CdColorXYZ * dest,gdouble X,gdouble Y,gdouble Z)478 cd_color_xyz_set (CdColorXYZ *dest, gdouble X, gdouble Y, gdouble Z)
479 {
480 	g_return_if_fail (dest != NULL);
481 
482 	dest->X = X;
483 	dest->Y = Y;
484 	dest->Z = Z;
485 }
486 
487 /**
488  * cd_color_xyz_clear:
489  * @dest: the destination color
490  *
491  * Initialises a color value.
492  *
493  * Since: 0.1.27
494  **/
495 void
cd_color_xyz_clear(CdColorXYZ * dest)496 cd_color_xyz_clear (CdColorXYZ *dest)
497 {
498 	g_return_if_fail (dest != NULL);
499 
500 	dest->X = 0.0f;
501 	dest->Y = 0.0f;
502 	dest->Z = 0.0f;
503 }
504 
505 /**
506  * cd_color_rgb_set:
507  * @dest: the destination color
508  * @R: component value
509  * @G: component value
510  * @B: component value
511  *
512  * Initialises a color value.
513  *
514  * Since: 0.1.27
515  **/
516 void
cd_color_rgb_set(CdColorRGB * dest,gdouble R,gdouble G,gdouble B)517 cd_color_rgb_set (CdColorRGB *dest, gdouble R, gdouble G, gdouble B)
518 {
519 	g_return_if_fail (dest != NULL);
520 
521 	dest->R = R;
522 	dest->G = G;
523 	dest->B = B;
524 }
525 
526 /**
527  * cd_color_lab_set:
528  * @dest: the destination color
529  * @L: component value
530  * @a: component value
531  * @b: component value
532  *
533  * Initialises a color value.
534  *
535  * Since: 0.1.32
536  **/
537 void
cd_color_lab_set(CdColorLab * dest,gdouble L,gdouble a,gdouble b)538 cd_color_lab_set (CdColorLab *dest, gdouble L, gdouble a, gdouble b)
539 {
540 	g_return_if_fail (dest != NULL);
541 
542 	dest->L = L;
543 	dest->a = a;
544 	dest->b = b;
545 }
546 
547 /**
548  * cd_color_lab_delta_e76:
549  * @p1: Lab value 1
550  * @p2: Lab value 2
551  *
552  * Calculates the ΔE of two colors using the 1976 formula.
553  *
554  * Return value: distance metric, where JND ΔE ≈ 2.3
555  *
556  * Since: 0.1.32
557  **/
558 gdouble
cd_color_lab_delta_e76(const CdColorLab * p1,const CdColorLab * p2)559 cd_color_lab_delta_e76 (const CdColorLab *p1, const CdColorLab *p2)
560 {
561 	return sqrt (pow (p2->L - p1->L, 2) +
562 		     pow (p2->a - p1->a, 2) +
563 		     pow (p2->b - p1->b, 2));
564 }
565 
566 /**
567  * cd_color_yxy_set:
568  * @dest: the destination color
569  * @Y: component value
570  * @x: component value
571  * @y: component value
572  *
573  * Initialises a color value.
574  *
575  * Since: 0.1.27
576  **/
577 void
cd_color_yxy_set(CdColorYxy * dest,gdouble Y,gdouble x,gdouble y)578 cd_color_yxy_set (CdColorYxy *dest, gdouble Y, gdouble x, gdouble y)
579 {
580 	g_return_if_fail (dest != NULL);
581 
582 	dest->Y = Y;
583 	dest->x = x;
584 	dest->y = y;
585 }
586 
587 /**
588  * cd_color_uvw_set:
589  * @dest: the destination color
590  * @U: component value
591  * @V: component value
592  * @W: component value
593  *
594  * Initialises a color value.
595  *
596  * Since: 1.1.6
597  **/
598 void
cd_color_uvw_set(CdColorUVW * dest,gdouble U,gdouble V,gdouble W)599 cd_color_uvw_set (CdColorUVW *dest, gdouble U, gdouble V, gdouble W)
600 {
601 	g_return_if_fail (dest != NULL);
602 
603 	dest->U = U;
604 	dest->V = V;
605 	dest->W = W;
606 }
607 
608 /**
609  * cd_color_swatch_set_name:
610  * @dest: the destination swatch
611  * @name: component name
612  *
613  * Initialises a swatch name.
614  *
615  * Since: 0.1.32
616  **/
617 void
cd_color_swatch_set_name(CdColorSwatch * dest,const gchar * name)618 cd_color_swatch_set_name (CdColorSwatch *dest, const gchar *name)
619 {
620 	g_return_if_fail (dest != NULL);
621 	g_return_if_fail (name != NULL);
622 	g_free (dest->name);
623 	dest->name = g_strdup (name);
624 }
625 
626 /**
627  * cd_color_swatch_set_value:
628  * @dest: the destination swatch
629  * @value: component value
630  *
631  * Initialises a swatch value.
632  *
633  * Since: 0.1.32
634  **/
635 void
cd_color_swatch_set_value(CdColorSwatch * dest,const CdColorLab * value)636 cd_color_swatch_set_value (CdColorSwatch *dest, const CdColorLab *value)
637 {
638 	g_return_if_fail (dest != NULL);
639 	g_return_if_fail (value != NULL);
640 	cd_color_lab_copy (value, &dest->value);
641 }
642 
643 /**
644  * cd_color_xyz_copy:
645  * @src: the source color
646  * @dest: the destination color
647  *
648  * Deep copies a color value.
649  *
650  * Since: 0.1.27
651  **/
652 void
cd_color_xyz_copy(const CdColorXYZ * src,CdColorXYZ * dest)653 cd_color_xyz_copy (const CdColorXYZ *src, CdColorXYZ *dest)
654 {
655 	g_return_if_fail (src != NULL);
656 	g_return_if_fail (dest != NULL);
657 
658 	dest->X = src->X;
659 	dest->Y = src->Y;
660 	dest->Z = src->Z;
661 }
662 
663 /**
664  * cd_color_yxy_copy:
665  * @src: the source color
666  * @dest: the destination color
667  *
668  * Deep copies a color value.
669  *
670  * Since: 0.1.27
671  **/
672 void
cd_color_yxy_copy(const CdColorYxy * src,CdColorYxy * dest)673 cd_color_yxy_copy (const CdColorYxy *src, CdColorYxy *dest)
674 {
675 	g_return_if_fail (src != NULL);
676 	g_return_if_fail (dest != NULL);
677 
678 	dest->Y = src->Y;
679 	dest->x = src->x;
680 	dest->y = src->y;
681 }
682 
683 /**
684  * cd_color_uvw_copy:
685  * @src: the source color
686  * @dest: the destination color
687  *
688  * Deep copies a color value.
689  *
690  * Since: 1.1.6
691  **/
692 void
cd_color_uvw_copy(const CdColorUVW * src,CdColorUVW * dest)693 cd_color_uvw_copy (const CdColorUVW *src, CdColorUVW *dest)
694 {
695 	g_return_if_fail (src != NULL);
696 	g_return_if_fail (dest != NULL);
697 
698 	dest->U = src->U;
699 	dest->V = src->V;
700 	dest->W = src->W;
701 }
702 
703 /**
704  * cd_color_lab_copy:
705  * @src: the source color
706  * @dest: the destination color
707  *
708  * Deep copies a color value.
709  *
710  * Since: 0.1.32
711  **/
712 void
cd_color_lab_copy(const CdColorLab * src,CdColorLab * dest)713 cd_color_lab_copy (const CdColorLab *src, CdColorLab *dest)
714 {
715 	g_return_if_fail (src != NULL);
716 	g_return_if_fail (dest != NULL);
717 
718 	dest->L = src->L;
719 	dest->a = src->a;
720 	dest->b = src->b;
721 }
722 
723 /**
724  * cd_color_rgb_copy:
725  * @src: the source color
726  * @dest: the destination color
727  *
728  * Deep copies a color value.
729  *
730  * Since: 0.1.27
731  **/
732 void
cd_color_rgb_copy(const CdColorRGB * src,CdColorRGB * dest)733 cd_color_rgb_copy (const CdColorRGB *src, CdColorRGB *dest)
734 {
735 	g_return_if_fail (src != NULL);
736 	g_return_if_fail (dest != NULL);
737 
738 	dest->R = src->R;
739 	dest->G = src->G;
740 	dest->B = src->B;
741 }
742 
743 /**
744  * cd_color_rgb8_to_rgb:
745  * @src: the source color
746  * @dest: the destination color
747  *
748  * Convert from one color format to another.
749  *
750  * Since: 0.1.27
751  **/
752 void
cd_color_rgb8_to_rgb(const CdColorRGB8 * src,CdColorRGB * dest)753 cd_color_rgb8_to_rgb (const CdColorRGB8 *src, CdColorRGB *dest)
754 {
755 	g_return_if_fail (src != NULL);
756 	g_return_if_fail (dest != NULL);
757 
758 	dest->R = (gdouble) src->R / 255.0f;
759 	dest->G = (gdouble) src->G / 255.0f;
760 	dest->B = (gdouble) src->B / 255.0f;
761 }
762 
763 /**
764  * cd_color_value_double_to_uint8:
765  **/
766 static guint8
cd_color_value_double_to_uint8(gdouble value)767 cd_color_value_double_to_uint8 (gdouble value)
768 {
769 	if (value < 0)
770 		return 0;
771 	if (value > 1.0f)
772 		return 255;
773 	return value * 255.0f;
774 }
775 
776 /**
777  * cd_color_rgb_to_rgb8:
778  * @src: the source color
779  * @dest: the destination color
780  *
781  * Convert from one color format to another.
782  *
783  * Since: 0.1.27
784  **/
785 void
cd_color_rgb_to_rgb8(const CdColorRGB * src,CdColorRGB8 * dest)786 cd_color_rgb_to_rgb8 (const CdColorRGB *src, CdColorRGB8 *dest)
787 {
788 	g_return_if_fail (src != NULL);
789 	g_return_if_fail (dest != NULL);
790 
791 	/* also deal with overflow and underflow */
792 	dest->R = cd_color_value_double_to_uint8 (src->R);
793 	dest->G = cd_color_value_double_to_uint8 (src->G);
794 	dest->B = cd_color_value_double_to_uint8 (src->B);
795 }
796 
797 /**
798  * cd_color_yxy_to_xyz:
799  * @src: the source color
800  * @dest: the destination color
801  *
802  * Convert from one color format to another.
803  *
804  * Since: 0.1.27
805  **/
806 void
cd_color_yxy_to_xyz(const CdColorYxy * src,CdColorXYZ * dest)807 cd_color_yxy_to_xyz (const CdColorYxy *src, CdColorXYZ *dest)
808 {
809 	g_return_if_fail (src != NULL);
810 	g_return_if_fail (dest != NULL);
811 
812 	g_assert (src->Y >= 0.0f);
813 	g_assert (src->x >= 0.0f);
814 	g_assert (src->y >= 0.0f);
815 	g_assert (src->Y <= 100.0f);
816 	g_assert (src->x <= 1.0f);
817 	g_assert (src->y <= 1.0f);
818 
819 	/* very small luminance */
820 	if (src->Y < 1e-6) {
821 		dest->X = 0.0f;
822 		dest->Y = 0.0f;
823 		dest->Z = 0.0f;
824 		return;
825 	}
826 
827 	dest->X = (src->x * src->Y) / src->y;
828 	dest->Y = src->Y;
829 	dest->Z = (1.0f - src->x - src->y) * src->Y / src->y;
830 }
831 
832 /**
833  * cd_color_xyz_normalize:
834  * @src: the source color
835  * @dest: the destination color
836  *
837  * Normalizes @src to y=1.0
838  *
839  * Since: 1.1.6
840  **/
841 void
cd_color_xyz_normalize(const CdColorXYZ * src,gdouble max,CdColorXYZ * dest)842 cd_color_xyz_normalize (const CdColorXYZ *src, gdouble max, CdColorXYZ *dest)
843 {
844 	dest->X = max * src->X / src->Y;
845 	dest->Z = max * src->Z / src->Y;
846 	dest->Y = max;
847 }
848 
849 /**
850  * cd_color_xyz_to_cct:
851  * @src: the source color
852  *
853  * Gets the correlated color temperature for the XYZ value.
854  *
855  * Since: 1.1.6
856  **/
857 gdouble
cd_color_xyz_to_cct(const CdColorXYZ * src)858 cd_color_xyz_to_cct (const CdColorXYZ *src)
859 {
860 	cmsCIExyY tmp;
861 	cmsCIEXYZ src_lcms;
862 	gboolean ret;
863 	gdouble value;
864 
865 	/* in case cmsFloat64Number != gdouble */
866 	src_lcms.X = src->X;
867 	src_lcms.Y = src->Y;
868 	src_lcms.Z = src->Z;
869 	cmsXYZ2xyY (&tmp, &src_lcms);
870 	ret = cmsTempFromWhitePoint (&value, &tmp);
871 	if (!ret)
872 		return -1.f;
873 	return value;
874 }
875 
876 /**
877  * cd_color_uvw_get_chroma_difference:
878  * @p1: color
879  * @p2: color
880  *
881  * Gets the chromaticity distance in the CIE 1960 UCS.
882  *
883  * Return value: The Euclidean distance
884  *
885  * Since: 1.1.6
886  **/
887 gdouble
cd_color_uvw_get_chroma_difference(const CdColorUVW * p1,const CdColorUVW * p2)888 cd_color_uvw_get_chroma_difference (const CdColorUVW *p1, const CdColorUVW *p2)
889 {
890 	return sqrt (pow ((p1->U - p2->U), 2) + pow ((p1->V - p2->V), 2));
891 }
892 
893 /**
894  * cd_color_uvw_set_planckian_locus:
895  * @dest: destination color
896  * @temp: temperature in Kelvin
897  *
898  * Sets the CIEUVW color from a Planckian locus of specific temperature.
899  *
900  * Since: 1.1.6
901  **/
902 void
cd_color_uvw_set_planckian_locus(CdColorUVW * dest,gdouble temp)903 cd_color_uvw_set_planckian_locus (CdColorUVW *dest, gdouble temp)
904 {
905 	dest->W = 1.0;
906 	dest->U = (0.860117757 +
907 		   (1.54118254 * temp * 1e-4) +
908 		   (1.28641212 * pow (temp, 2) * 1e-7)) /
909 		  (1.0 +
910 		   (8.42420235 * temp * 1e-4) +
911 		   (7.08145163 * pow (temp, 2) * 1e-7));
912 	dest->V = (0.317398726 +
913 		   (4.22806245 * temp * 1e-5) +
914 		   (4.20481691 * pow (temp, 2) * 1e-8)) /
915 		  (1.0 -
916 		   (2.89741816 * temp * 1e-5) +
917 		   (1.61456053 * pow (temp, 2) * 1e-7));
918 }
919 
920 /**
921  * cd_color_xyz_to_yxy:
922  * @src: the source color
923  * @dest: the destination color
924  *
925  * Convert from one color format to another.
926  *
927  * Since: 0.1.27
928  **/
929 void
cd_color_xyz_to_yxy(const CdColorXYZ * src,CdColorYxy * dest)930 cd_color_xyz_to_yxy (const CdColorXYZ *src, CdColorYxy *dest)
931 {
932 	gdouble sum;
933 
934 	g_return_if_fail (src != NULL);
935 	g_return_if_fail (dest != NULL);
936 
937 	/* prevent division by zero */
938 	sum = src->X + src->Y + src->Z;
939 	if (fabs (sum) < 1e-6) {
940 		cd_color_yxy_set (dest, 0.f, 0.f, 0.f);
941 		return;
942 	}
943 
944 	dest->Y = src->Y;
945 	dest->x = src->X / sum;
946 	dest->y = src->Y / sum;
947 }
948 
949 typedef struct {
950 	gdouble	 Y;
951 	gdouble	 u;
952 	gdouble	 v;
953 } CdColorYuv;
954 
955 static void
cd_color_xyz_to_yuv(const CdColorXYZ * src,CdColorYuv * dest)956 cd_color_xyz_to_yuv (const CdColorXYZ *src, CdColorYuv *dest)
957 {
958 	gdouble sum = src->X + 15 * src->Y + 3 * src->Z;
959 	dest->Y = src->Y;
960 	dest->u = 4 * src->X / sum;
961 	dest->v = 6 * src->Y / sum;
962 }
963 
964 /**
965  * cd_color_xyz_to_uvw:
966  * @src: the source color
967  * @whitepoint: the whitepoint
968  * @dest: the destination color
969  *
970  * Convert from one color format to another.
971  *
972  * Since: 1.1.6
973  **/
974 void
cd_color_xyz_to_uvw(const CdColorXYZ * src,const CdColorXYZ * whitepoint,CdColorUVW * dest)975 cd_color_xyz_to_uvw (const CdColorXYZ *src,
976 		     const CdColorXYZ *whitepoint,
977 		     CdColorUVW *dest)
978 {
979 	CdColorYuv wp;
980 	CdColorYuv tmp;
981 
982 	cd_color_xyz_to_yuv (whitepoint, &wp);
983 	cd_color_xyz_to_yuv (src, &tmp);
984 
985 	dest->W = 25 * pow (src->Y * 100.f / wp.Y, 1.f/3.f) - 17.f;
986 	dest->U = 13 * dest->W * (tmp.u - wp.u);
987 	dest->V = 13 * dest->W * (tmp.v - wp.v);
988 }
989 
990 /**
991  * cd_color_yxy_to_uvw:
992  * @src: the source color
993  * @dest: the destination color
994  *
995  * Convert from one color format to another.
996  *
997  * Since: 1.1.6
998  **/
999 void
cd_color_yxy_to_uvw(const CdColorYxy * src,CdColorUVW * dest)1000 cd_color_yxy_to_uvw (const CdColorYxy *src, CdColorUVW *dest)
1001 {
1002 	gdouble sum = (-2 * src->x) + (12 * src->y) + (3 * src->Y);
1003 	dest->U = (4 * src->x) / sum;
1004 	dest->V = (6 * src->y) / sum;
1005 	dest->W = src->Y;
1006 }
1007 
1008 /* source: https://github.com/jonls/redshift/blob/master/README-colorramp
1009  * use a Planckian curve below 5000K */
1010 static const CdColorRGB blackbody_data_d65plankian[] = {
1011 	{ 1.0000, 0.1817, 0.0000 }, /* 1000K */
1012 	{ 1.0000, 0.2550, 0.0000 }, /* 1100K */
1013 	{ 1.0000, 0.3094, 0.0000 }, /* 1200K */
1014 	{ 1.0000, 0.3536, 0.0000 }, /* ... */
1015 	{ 1.0000, 0.3909, 0.0000 },
1016 	{ 1.0000, 0.4232, 0.0000 },
1017 	{ 1.0000, 0.4516, 0.0000 },
1018 	{ 1.0000, 0.4768, 0.0000 },
1019 	{ 1.0000, 0.4992, 0.0000 },
1020 	{ 1.0000, 0.5194, 0.0000 },
1021 	{ 1.0000, 0.5436, 0.0868 },
1022 	{ 1.0000, 0.5662, 0.1407 },
1023 	{ 1.0000, 0.5873, 0.1836 },
1024 	{ 1.0000, 0.6072, 0.2214 },
1025 	{ 1.0000, 0.6260, 0.2559 },
1026 	{ 1.0000, 0.6437, 0.2882 },
1027 	{ 1.0000, 0.6605, 0.3187 },
1028 	{ 1.0000, 0.6765, 0.3479 },
1029 	{ 1.0000, 0.6916, 0.3758 },
1030 	{ 1.0000, 0.7060, 0.4027 },
1031 	{ 1.0000, 0.7198, 0.4286 },
1032 	{ 1.0000, 0.7329, 0.4537 },
1033 	{ 1.0000, 0.7454, 0.4779 },
1034 	{ 1.0000, 0.7574, 0.5015 },
1035 	{ 1.0000, 0.7689, 0.5243 },
1036 	{ 1.0000, 0.7799, 0.5464 },
1037 	{ 1.0000, 0.7904, 0.5679 },
1038 	{ 1.0000, 0.8005, 0.5888 },
1039 	{ 1.0000, 0.8102, 0.6092 },
1040 	{ 1.0000, 0.8196, 0.6289 },
1041 	{ 1.0000, 0.8285, 0.6482 },
1042 	{ 1.0000, 0.8372, 0.6669 },
1043 	{ 1.0000, 0.8455, 0.6851 },
1044 	{ 1.0000, 0.8535, 0.7028 },
1045 	{ 1.0000, 0.8612, 0.7201 },
1046 	{ 1.0000, 0.8686, 0.7369 },
1047 	{ 1.0000, 0.8758, 0.7533 },
1048 	{ 1.0000, 0.8827, 0.7692 },
1049 	{ 1.0000, 0.8893, 0.7848 },
1050 	{ 1.0000, 0.8958, 0.7999 },
1051 	{ 1.0000, 0.9020, 0.8147 },
1052 	{ 1.0000, 0.9096, 0.8284 },
1053 	{ 1.0000, 0.9171, 0.8419 },
1054 	{ 1.0000, 0.9244, 0.8552 },
1055 	{ 1.0000, 0.9316, 0.8684 },
1056 	{ 1.0000, 0.9385, 0.8813 },
1057 	{ 1.0000, 0.9454, 0.8940 },
1058 	{ 1.0000, 0.9520, 0.9066 },
1059 	{ 1.0000, 0.9585, 0.9189 },
1060 	{ 1.0000, 0.9649, 0.9311 },
1061 	{ 1.0000, 0.9711, 0.9431 },
1062 	{ 1.0000, 0.9771, 0.9548 },
1063 	{ 1.0000, 0.9831, 0.9664 },
1064 	{ 1.0000, 0.9888, 0.9778 },
1065 	{ 1.0000, 0.9945, 0.9890 },
1066 	{ 1.0000, 1.0000, 1.0000 }, /* 6500K */
1067 	{ 0.9895, 0.9935, 1.0000 },
1068 	{ 0.9794, 0.9872, 1.0000 },
1069 	{ 0.9698, 0.9812, 1.0000 },
1070 	{ 0.9605, 0.9754, 1.0000 },
1071 	{ 0.9516, 0.9698, 1.0000 },
1072 	{ 0.9430, 0.9644, 1.0000 },
1073 	{ 0.9348, 0.9592, 1.0000 },
1074 	{ 0.9269, 0.9542, 1.0000 },
1075 	{ 0.9193, 0.9494, 1.0000 },
1076 	{ 0.9119, 0.9447, 1.0000 },
1077 	{ 0.9049, 0.9402, 1.0000 },
1078 	{ 0.8981, 0.9358, 1.0000 },
1079 	{ 0.8915, 0.9316, 1.0000 },
1080 	{ 0.8852, 0.9275, 1.0000 },
1081 	{ 0.8791, 0.9236, 1.0000 },
1082 	{ 0.8732, 0.9197, 1.0000 },
1083 	{ 0.8674, 0.9160, 1.0000 },
1084 	{ 0.8619, 0.9125, 1.0000 },
1085 	{ 0.8566, 0.9090, 1.0000 },
1086 	{ 0.8514, 0.9056, 1.0000 },
1087 	{ 0.8464, 0.9023, 1.0000 },
1088 	{ 0.8415, 0.8991, 1.0000 },
1089 	{ 0.8368, 0.8960, 1.0000 },
1090 	{ 0.8323, 0.8930, 1.0000 },
1091 	{ 0.8278, 0.8901, 1.0000 },
1092 	{ 0.8235, 0.8873, 1.0000 },
1093 	{ 0.8194, 0.8845, 1.0000 },
1094 	{ 0.8153, 0.8818, 1.0000 },
1095 	{ 0.8114, 0.8792, 1.0000 },
1096 	{ 0.8075, 0.8767, 1.0000 },
1097 	{ 0.8038, 0.8742, 1.0000 },
1098 	{ 0.8002, 0.8718, 1.0000 },
1099 	{ 0.7967, 0.8694, 1.0000 },
1100 	{ 0.7932, 0.8671, 1.0000 },
1101 	{ 0.7898, 0.8649, 1.0000 } /* 10000K */
1102 };
1103 
1104 /* source: http://www.vendian.org/mncharity/dir3/blackbody/
1105  * rescaled to make exactly 6500K equal to full intensity in all
1106  * channels */
1107 static const CdColorRGB blackbody_data_d65modified[] = {
1108 	{ 1.0000, 0.0425, 0.0000 }, /* 1000K */
1109 	{ 1.0000, 0.0668, 0.0000 }, /* 1100K */
1110 	{ 1.0000, 0.0911, 0.0000 }, /* 1200K */
1111 	{ 1.0000, 0.1149, 0.0000 }, /* ... */
1112 	{ 1.0000, 0.1380, 0.0000 },
1113 	{ 1.0000, 0.1604, 0.0000 },
1114 	{ 1.0000, 0.1819, 0.0000 },
1115 	{ 1.0000, 0.2024, 0.0000 },
1116 	{ 1.0000, 0.2220, 0.0000 },
1117 	{ 1.0000, 0.2406, 0.0000 },
1118 	{ 1.0000, 0.2630, 0.0062 },
1119 	{ 1.0000, 0.2868, 0.0155 },
1120 	{ 1.0000, 0.3102, 0.0261 },
1121 	{ 1.0000, 0.3334, 0.0379 },
1122 	{ 1.0000, 0.3562, 0.0508 },
1123 	{ 1.0000, 0.3787, 0.0650 },
1124 	{ 1.0000, 0.4008, 0.0802 },
1125 	{ 1.0000, 0.4227, 0.0964 },
1126 	{ 1.0000, 0.4442, 0.1136 },
1127 	{ 1.0000, 0.4652, 0.1316 },
1128 	{ 1.0000, 0.4859, 0.1505 },
1129 	{ 1.0000, 0.5062, 0.1702 },
1130 	{ 1.0000, 0.5262, 0.1907 },
1131 	{ 1.0000, 0.5458, 0.2118 },
1132 	{ 1.0000, 0.5650, 0.2335 },
1133 	{ 1.0000, 0.5839, 0.2558 },
1134 	{ 1.0000, 0.6023, 0.2786 },
1135 	{ 1.0000, 0.6204, 0.3018 },
1136 	{ 1.0000, 0.6382, 0.3255 },
1137 	{ 1.0000, 0.6557, 0.3495 },
1138 	{ 1.0000, 0.6727, 0.3739 },
1139 	{ 1.0000, 0.6894, 0.3986 },
1140 	{ 1.0000, 0.7058, 0.4234 },
1141 	{ 1.0000, 0.7218, 0.4485 },
1142 	{ 1.0000, 0.7375, 0.4738 },
1143 	{ 1.0000, 0.7529, 0.4992 },
1144 	{ 1.0000, 0.7679, 0.5247 },
1145 	{ 1.0000, 0.7826, 0.5503 },
1146 	{ 1.0000, 0.7970, 0.5760 },
1147 	{ 1.0000, 0.8111, 0.6016 },
1148 	{ 1.0000, 0.8250, 0.6272 },
1149 	{ 1.0000, 0.8384, 0.6529 },
1150 	{ 1.0000, 0.8517, 0.6785 },
1151 	{ 1.0000, 0.8647, 0.7040 },
1152 	{ 1.0000, 0.8773, 0.7294 },
1153 	{ 1.0000, 0.8897, 0.7548 },
1154 	{ 1.0000, 0.9019, 0.7801 },
1155 	{ 1.0000, 0.9137, 0.8051 },
1156 	{ 1.0000, 0.9254, 0.8301 },
1157 	{ 1.0000, 0.9367, 0.8550 },
1158 	{ 1.0000, 0.9478, 0.8795 },
1159 	{ 1.0000, 0.9587, 0.9040 },
1160 	{ 1.0000, 0.9694, 0.9283 },
1161 	{ 1.0000, 0.9798, 0.9524 },
1162 	{ 1.0000, 0.9900, 0.9763 },
1163 	{ 1.0000, 1.0000, 1.0000 }, /* 6500K */
1164 	{ 0.9771, 0.9867, 1.0000 },
1165 	{ 0.9554, 0.9740, 1.0000 },
1166 	{ 0.9349, 0.9618, 1.0000 },
1167 	{ 0.9154, 0.9500, 1.0000 },
1168 	{ 0.8968, 0.9389, 1.0000 },
1169 	{ 0.8792, 0.9282, 1.0000 },
1170 	{ 0.8624, 0.9179, 1.0000 },
1171 	{ 0.8465, 0.9080, 1.0000 },
1172 	{ 0.8313, 0.8986, 1.0000 },
1173 	{ 0.8167, 0.8895, 1.0000 },
1174 	{ 0.8029, 0.8808, 1.0000 },
1175 	{ 0.7896, 0.8724, 1.0000 },
1176 	{ 0.7769, 0.8643, 1.0000 },
1177 	{ 0.7648, 0.8565, 1.0000 },
1178 	{ 0.7532, 0.8490, 1.0000 },
1179 	{ 0.7420, 0.8418, 1.0000 },
1180 	{ 0.7314, 0.8348, 1.0000 },
1181 	{ 0.7212, 0.8281, 1.0000 },
1182 	{ 0.7113, 0.8216, 1.0000 },
1183 	{ 0.7018, 0.8153, 1.0000 },
1184 	{ 0.6927, 0.8092, 1.0000 },
1185 	{ 0.6839, 0.8032, 1.0000 },
1186 	{ 0.6755, 0.7975, 1.0000 },
1187 	{ 0.6674, 0.7921, 1.0000 },
1188 	{ 0.6595, 0.7867, 1.0000 },
1189 	{ 0.6520, 0.7816, 1.0000 },
1190 	{ 0.6447, 0.7765, 1.0000 },
1191 	{ 0.6376, 0.7717, 1.0000 },
1192 	{ 0.6308, 0.7670, 1.0000 },
1193 	{ 0.6242, 0.7623, 1.0000 },
1194 	{ 0.6179, 0.7579, 1.0000 },
1195 	{ 0.6117, 0.7536, 1.0000 },
1196 	{ 0.6058, 0.7493, 1.0000 },
1197 	{ 0.6000, 0.7453, 1.0000 },
1198 	{ 0.5944, 0.7414, 1.0000 } /* 10000K */
1199 };
1200 
1201 /**
1202  * cd_color_rgb_interpolate:
1203  *
1204  * Since: 0.1.26
1205  **/
1206 void
cd_color_rgb_interpolate(const CdColorRGB * p1,const CdColorRGB * p2,gdouble index,CdColorRGB * result)1207 cd_color_rgb_interpolate (const CdColorRGB *p1,
1208 			  const CdColorRGB *p2,
1209 			  gdouble index,
1210 			  CdColorRGB *result)
1211 {
1212 	g_return_if_fail (p1 != NULL);
1213 	g_return_if_fail (p2 != NULL);
1214 	g_return_if_fail (index >= 0.0f);
1215 	g_return_if_fail (index <= 1.0f);
1216 	g_return_if_fail (result != NULL);
1217 	result->R = (1.0 - index) * p1->R + index * p2->R;
1218 	result->G = (1.0 - index) * p1->G + index * p2->G;
1219 	result->B = (1.0 - index) * p1->B + index * p2->B;
1220 }
1221 
1222 /**
1223  * cd_color_rgb_from_wavelength:
1224  * @dest: a #CdColorRGB for the RGB result
1225  * @wavelength: the wavelength roughly between 380nm and 780nm
1226  *
1227  * Set an RGB color which is roughly representative to the wavelength.
1228  *
1229  * Since: 1.3.4
1230  **/
1231 void
cd_color_rgb_from_wavelength(CdColorRGB * dest,gdouble wavelength)1232 cd_color_rgb_from_wavelength (CdColorRGB *dest, gdouble wavelength)
1233 {
1234 	const gdouble gamma = 0.80;
1235 	gdouble factor;
1236 
1237 	/* use the colors specified in
1238 	 * http://www.efg2.com/Lab/ScienceAndEngineering/Spectra.htm */
1239 	if (wavelength < 380) {
1240 		dest->R = 0.0;
1241 		dest->G = 0.0;
1242 		dest->B = 0.0;
1243 	} else if (wavelength < 440) {
1244 		dest->R = -(wavelength - 440.f) / (440.f - 380.f);
1245 		dest->G = 0.0;
1246 		dest->B = 1.0;
1247 	} else if (wavelength < 490) {
1248 		dest->R = 0.0;
1249 		dest->G = (wavelength - 440) / (490 - 440);
1250 		dest->B = 1.0;
1251 	} else if (wavelength < 510) {
1252 		dest->R = 0.0;
1253 		dest->G = 1.0;
1254 		dest->B = -(wavelength - 510) / (510 - 490);
1255 	} else if (wavelength < 580) {
1256 		dest->R = (wavelength - 510) / (580 - 510);
1257 		dest->G = 1.0;
1258 		dest->B = 0.0;
1259 	} else if (wavelength < 645) {
1260 		dest->R = 1.0;
1261 		dest->G = -(wavelength - 645) / (645 - 580);
1262 		dest->B = 0.0;
1263 	} else if (wavelength < 781) {
1264 		dest->R = 1.0;
1265 		dest->G = 0.0;
1266 		dest->B = 0.0;
1267 	} else {
1268 		dest->R = 0.0;
1269 		dest->G = 0.0;
1270 		dest->B = 0.0;
1271 	}
1272 
1273 	/* intensity should fall off near the vision limits */
1274 	if (wavelength >= 380 && wavelength < 420) {
1275 		factor = 0.3 + 0.7 * (wavelength - 380) / (420 - 380);
1276 	} else if (wavelength >= 420 && wavelength < 701) {
1277 		factor = 1.0;
1278 	} else if (wavelength >= 701 && wavelength < 781) {
1279 		factor = 0.3 + 0.7 * (780 - wavelength) / (780 - 700);
1280 	} else {
1281 		factor = 0.0;
1282 	};
1283 
1284 	/* scale by factor and then apply gamma */
1285 	if (dest->R > 0.f)
1286 		dest->R = pow (dest->R * factor, gamma);
1287 	if (dest->G > 0.f)
1288 		dest->G = pow (dest->G * factor, gamma);
1289 	if (dest->B > 0.f)
1290 		dest->B = pow (dest->B * factor, gamma);
1291 }
1292 
1293 /**
1294  * cd_color_rgb_array_is_monotonic:
1295  * @array: (element-type CdColorRGB): Input array
1296  *
1297  * Checks the array for monotonicity.
1298  *
1299  * Return value: %TRUE if the array is monotonic
1300  *
1301  * Since: 0.1.31
1302  **/
1303 gboolean
cd_color_rgb_array_is_monotonic(const GPtrArray * array)1304 cd_color_rgb_array_is_monotonic (const GPtrArray *array)
1305 {
1306 	CdColorRGB last_rgb;
1307 	CdColorRGB *rgb;
1308 	guint i;
1309 
1310 	g_return_val_if_fail (array != NULL, FALSE);
1311 
1312 	/* check if monotonic */
1313 	cd_color_rgb_set (&last_rgb, 0.0, 0.0, 0.0);
1314 	for (i = 0; i < array->len; i++) {
1315 		rgb = g_ptr_array_index (array, i);
1316 		if (rgb->R < last_rgb.R)
1317 			return FALSE;
1318 		if (rgb->G < last_rgb.G)
1319 			return FALSE;
1320 		if (rgb->B < last_rgb.B)
1321 			return FALSE;
1322 		cd_color_rgb_copy (rgb, &last_rgb);
1323 	}
1324 	return TRUE;
1325 }
1326 
1327 /**
1328  * cd_color_rgb_array_new:
1329  *
1330  * Creates a new RGB array.
1331  *
1332  * Return value: (element-type CdColorRGB) (transfer full): New array
1333  *
1334  * Since: 0.1.31
1335  **/
1336 GPtrArray *
cd_color_rgb_array_new(void)1337 cd_color_rgb_array_new (void)
1338 {
1339 	return g_ptr_array_new_with_free_func ((GDestroyNotify) cd_color_rgb_free);
1340 }
1341 
1342 /**
1343  * cd_color_rgb_array_interpolate:
1344  * @array: (element-type CdColorRGB): Input array
1345  * @new_length: the target length of the return array
1346  *
1347  * Interpolate the RGB array to a different size.
1348  * This uses the Akima interpolation algorithm unless the array would become
1349  * non-monotonic, in which case it falls back to linear interpolation.
1350  *
1351  * Return value: (element-type CdColorRGB) (transfer full): An array of size @new_length or %NULL
1352  *
1353  * Since: 0.1.31
1354  **/
1355 GPtrArray *
cd_color_rgb_array_interpolate(const GPtrArray * array,guint new_length)1356 cd_color_rgb_array_interpolate (const GPtrArray *array, guint new_length)
1357 {
1358 	CdColorRGB *rgb;
1359 	CdInterp *interp[3];
1360 	gboolean ret;
1361 	gdouble tmp;
1362 	GPtrArray *result = NULL;
1363 	guint i;
1364 	guint j;
1365 	guint m;
1366 
1367 	g_return_val_if_fail (array != NULL, NULL);
1368 	g_return_val_if_fail (new_length > 0, NULL);
1369 
1370 	/* check if monotonic */
1371 	ret = cd_color_rgb_array_is_monotonic (array);
1372 	if (!ret)
1373 		goto out;
1374 
1375 	/* create new array */
1376 	result = cd_color_rgb_array_new ();
1377 	for (i = 0; i < new_length; i++) {
1378 		rgb = cd_color_rgb_new ();
1379 		g_ptr_array_add (result, rgb);
1380 	}
1381 
1382 	/* try each interpolation method in turn */
1383 	for (m = 0; m < 2; m++) {
1384 
1385 		/* setup interpolation */
1386 		for (j = 0; j < 3; j++) {
1387 			if (m == 0)
1388 				interp[j] = cd_interp_akima_new ();
1389 			else if (m == 1)
1390 				interp[j] = cd_interp_linear_new ();
1391 		}
1392 
1393 		/* add data */
1394 		for (i = 0; i < array->len; i++) {
1395 			rgb = g_ptr_array_index (array, i);
1396 			tmp = (gdouble) i / (gdouble) (array->len - 1);
1397 			cd_interp_insert (interp[0], tmp, rgb->R);
1398 			cd_interp_insert (interp[1], tmp, rgb->G);
1399 			cd_interp_insert (interp[2], tmp, rgb->B);
1400 		}
1401 
1402 		/* do interpolation of array */
1403 		for (j = 0; j < 3; j++) {
1404 			ret = cd_interp_prepare (interp[j], NULL);
1405 			if (!ret)
1406 				break;
1407 		}
1408 		for (i = 0; i < new_length; i++) {
1409 			tmp = (gdouble) i / (gdouble) (new_length - 1);
1410 			rgb = g_ptr_array_index (result, i);
1411 			rgb->R = cd_interp_eval (interp[0], tmp, NULL);
1412 			rgb->G = cd_interp_eval (interp[1], tmp, NULL);
1413 			rgb->B = cd_interp_eval (interp[2], tmp, NULL);
1414 		}
1415 
1416 		/* tear down the interpolation */
1417 		for (j = 0; j < 3; j++)
1418 			g_object_unref (interp[j]);
1419 
1420 		/* check if monotonic */
1421 		ret = cd_color_rgb_array_is_monotonic (result);
1422 		if (ret)
1423 			break;
1424 
1425 		/* try harder */
1426 	}
1427 out:
1428 	return result;
1429 }
1430 
1431 /**
1432  * cd_color_get_blackbody_rgb_full:
1433  * @temp: the temperature in Kelvin
1434  * @result: the destination color
1435  * @flags: some #CdColorBlackbodyFlags, e.g. %CD_COLOR_BLACKBODY_FLAG_USE_PLANCKIAN
1436  *
1437  * Get the blackbody color for a specific temperature. If the temperature
1438  * range is outside 1000K to 10000K then the result is clipped.
1439  *
1440  * Return value: TRUE if @temp was in range and the result accurate
1441  *
1442  * Since: 1.3.5
1443  **/
1444 gboolean
cd_color_get_blackbody_rgb_full(gdouble temp,CdColorRGB * result,CdColorBlackbodyFlags flags)1445 cd_color_get_blackbody_rgb_full (gdouble temp,
1446 				 CdColorRGB *result,
1447 				 CdColorBlackbodyFlags flags)
1448 {
1449 	gboolean ret = TRUE;
1450 	gdouble alpha;
1451 	gint temp_index;
1452 	const CdColorRGB *blackbody_func = blackbody_data_d65modified;
1453 
1454 	/* use modified curve */
1455 	if (flags & CD_COLOR_BLACKBODY_FLAG_USE_PLANCKIAN)
1456 		blackbody_func = blackbody_data_d65plankian;
1457 
1458 	/* check lower bound */
1459 	if (temp < 1000) {
1460 		ret = FALSE;
1461 		temp = 1000;
1462 	}
1463 
1464 	/* check upper bound */
1465 	if (temp > 10000) {
1466 		ret = FALSE;
1467 		temp = 10000;
1468 	}
1469 
1470 	/* bilinear interpolate the blackbody data */
1471 	alpha = ((guint) temp % 100) / 100.0;
1472 	temp_index = ((guint) temp - 1000) / 100;
1473 	cd_color_rgb_interpolate (&blackbody_func[temp_index],
1474 				  &blackbody_func[temp_index + 1],
1475 				  alpha,
1476 				  result);
1477 	return ret;
1478 }
1479 
1480 /**
1481  * cd_color_get_blackbody_rgb:
1482  * @temp: the temperature in Kelvin
1483  * @result: the destination color
1484  *
1485  * Get the blackbody color for a specific temperature. If the temperature
1486  * range is outside 1000K to 10000K then the result is clipped.
1487  *
1488  * Return value: TRUE if @temp was in range and the result accurate
1489  *
1490  * Since: 0.1.26
1491  **/
1492 gboolean
cd_color_get_blackbody_rgb(guint temp,CdColorRGB * result)1493 cd_color_get_blackbody_rgb (guint temp, CdColorRGB *result)
1494 {
1495 	return cd_color_get_blackbody_rgb_full (temp, result,
1496 						CD_COLOR_BLACKBODY_FLAG_NONE);
1497 }
1498