colorstate: Add cicp support

This adds machinery to create colorstate objects from cicp
tuples, as well as a function to return a cicp tuple for a
colorstate.

Still missing: a conversion shader for non-default colorstates.
This commit is contained in:
Matthias Clasen 2024-07-15 08:08:57 -04:00
parent 4255230e36
commit d907c0a42e
3 changed files with 557 additions and 39 deletions

View File

@ -0,0 +1,84 @@
#pragma once
typedef struct _GdkCicp GdkCicp;
typedef enum
{
GDK_CICP_RANGE_NARROW,
GDK_CICP_RANGE_FULL,
} GdkCicpRange;
struct _GdkCicp
{
guint color_primaries;
guint transfer_function;
guint matrix_coefficients;
GdkCicpRange range;
};
/*< private >
* gdk_cicp_equal:
* @p1: a `GdkCicp`
* @p2: another `GdkCicp`
*
* Compare two cicp tuples for equality.
*
* Note that several cicp values are 'functionally equivalent'.
* If you are interested in that notion, use gdk_cicp_equivalent().
*
* Returns: whether @p1 and @p2 are equal
*/
static inline gboolean
gdk_cicp_equal (const GdkCicp *p1,
const GdkCicp *p2)
{
return p1->color_primaries == p2->color_primaries &&
p1->transfer_function == p2->transfer_function &&
p1->matrix_coefficients == p2->matrix_coefficients &&
p1->range == p2->range;
}
static inline void
gdk_cicp_normalize (const GdkCicp *orig,
GdkCicp *out)
{
memcpy (out, orig, sizeof (GdkCicp));
/* ntsc */
if (out->color_primaries == 6)
out->color_primaries = 5;
/* bt709 */
if (out->transfer_function == 6 ||
out->transfer_function == 14 ||
out->transfer_function == 15)
out->transfer_function = 1;
/* bt601 */
if (out->matrix_coefficients == 6)
out->matrix_coefficients = 5;
}
/*< private >
* gdk_cicp_equivalent:
* @p1: a `GdkCicp`
* @p2: another `GdkCicp`
*
* Determine whether two cicp tuples are functionally equivalent.
*
* Returns: whether @p1 and @p2 are functionally equivalent
*/
static inline gboolean
gdk_cicp_equivalent (const GdkCicp *p1,
const GdkCicp *p2)
{
GdkCicp n1, n2;
if (gdk_cicp_equal (p1, p2))
return TRUE;
gdk_cicp_normalize (p1, &n1);
gdk_cicp_normalize (p2, &n2);
return gdk_cicp_equal (&n1, &n2);
}

View File

@ -22,6 +22,8 @@
#include <math.h>
#include <glib/gi18n-lib.h>
/**
* GdkColorState:
*
@ -193,6 +195,12 @@ gboolean
/* }}} */
/* {{{ Conversion functions */
typedef float (* GdkTransferFunc) (float v);
typedef const float GdkColorMatrix[3][3];
#define IDENTITY ((float**)0)
#define NONE ((GdkTransferFunc)0)
#define TRANSFORM(name, eotf, matrix, oetf) \
static void \
name (GdkColorState *self, \
@ -201,9 +209,12 @@ name (GdkColorState *self, \
{ \
for (gsize i = 0; i < n_values; i++) \
{ \
values[i][0] = eotf (values[i][0]); \
values[i][1] = eotf (values[i][1]); \
values[i][2] = eotf (values[i][2]); \
if (eotf != NONE) \
{ \
values[i][0] = eotf (values[i][0]); \
values[i][1] = eotf (values[i][1]); \
values[i][2] = eotf (values[i][2]); \
} \
if ((float **)matrix != IDENTITY) \
{ \
float res[3]; \
@ -214,9 +225,12 @@ name (GdkColorState *self, \
values[i][1] = res[1]; \
values[i][2] = res[2]; \
} \
values[i][0] = oetf (values[i][0]); \
values[i][1] = oetf (values[i][1]); \
values[i][2] = oetf (values[i][2]); \
if (oetf != NONE) \
{ \
values[i][0] = oetf (values[i][0]); \
values[i][1] = oetf (values[i][1]); \
values[i][2] = oetf (values[i][2]); \
} \
} \
}
@ -265,29 +279,116 @@ pq_oetf (float v)
return powf (((c1 + (c2 * powf (x, n))) / (1 + (c3 * powf (x, n)))), m);
}
/* These matrices are derived by combining the standard abc_to_xyz onces:
*
* rec2020_to_srgb = srgb_to_xyz¹ * rec2020_to_xyz
* srgb_to_rec2020 = rec2020_to_xyz¹ * srgb_to_xyz
*
* These values were used here:
*
* static const float srgb_to_xyz[3][3] = {
* { 0.4125288, 0.3581642, 0.1774037 },
* { 0.2127102, 0.7163284, 0.0709615 },
* { 0.0193373, 0.1193881, 0.9343260 },
* }
*
* static const float rec2020_to_xyz[3][3] = {
* { 0.6369615, 0.1448079, 0.1663273 },
* { 0.2627016, 0.6788934, 0.0584050 },
* { 0.0000000, 0.0281098, 1.0449416 },
* };
*
* See http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
static inline float
bt709_eotf (float v)
{
if (v < 0.081)
return v / 4.5;
else
return powf ((v + 0.099) / 1.099, 1/0.45);
}
static inline float
bt709_oetf (float v)
{
if (v < 0.081)
return v * 4.5;
else
return 1.099 * powf (v, 0.45) - 0.099;
}
static inline float
hlg_eotf (float v)
{
const float a = 0.17883277;
const float b = 0.28466892;
const float c = 0.55991073;
if (v <= 0.5)
return (v * v) / 3;
else
return expf (((v - c) / a) + b) / 12.0;
}
static inline float
hlg_oetf (float v)
{
const float a = 0.17883277;
const float b = 0.28466892;
const float c = 0.55991073;
if (v <= 1/12.0)
return sqrtf (3 * v);
else
return a * logf (12 * v - b) + c;
}
/* See http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html
* for how to derive the abc_to_xyz matrices from chromaticity coordinates.
*/
static const float srgb_to_xyz[3][3] = {
{ 0.4125288, 0.3581642, 0.1774037 },
{ 0.2127102, 0.7163284, 0.0709615 },
{ 0.0193373, 0.1193881, 0.9343260 },
};
static const float xyz_to_srgb[3][3] = {
{ 3.2409699, -1.5373832, -0.4986108 },
{ -0.9692436, 1.8759675, 0.0415551 },
{ 0.0556301, -0.2039770, 1.0569715 },
};
static const float rec2020_to_xyz[3][3] = {
{ 0.6369615, 0.1448079, 0.1663273 },
{ 0.2627016, 0.6788934, 0.0584050 },
{ 0.0000000, 0.0281098, 1.0449416 },
};
static const float xyz_to_rec2020[3][3] = {
{ 1.7166512, -0.3556708, -0.2533663 },
{ -0.6666844, 1.6164812, 0.0157685 },
{ 0.0176399, -0.0427706, 0.9421031 },
};
static const float pal_to_xyz[3][3] = {
{ 0.4305538, 0.3415498, 0.1783523 },
{ 0.2220043, 0.7066548, 0.0713409 },
{ 0.0201822, 0.1295534, 0.9393222 },
};
static const float xyz_to_pal[3][3] = {
{ 3.0633611, -1.3933902, -0.4758237 },
{ -0.9692436, 1.8759675, 0.0415551 },
{ 0.0678610, -0.2287993, 1.0690896 },
};
static const float ntsc_to_xyz[3][3] = {
{ 0.3935209, 0.3652581, 0.1916769 },
{ 0.2123764, 0.7010599, 0.0865638 },
{ 0.0187391, 0.1119339, 0.9583847 },
};
static const float xyz_to_ntsc[3][3] = {
{ 3.5060033, -1.7397907, -0.5440583 },
{ -1.0690476, 1.9777789, 0.0351714 },
{ 0.0563066, -0.1969757, 1.0499523 },
};
static const float p3_to_xyz[3][3] = {
{ 0.4865709, 0.2656677, 0.1982173 },
{ 0.2289746, 0.6917385, 0.0792869 },
{ 0.0000000, 0.0451134, 1.0439444 },
};
static const float xyz_to_p3[3][3] = {
{ 2.4934969, -0.9313836, -0.4027108 },
{ -0.8294890, 1.7626641, 0.0236247 },
{ 0.0358458, -0.0761724, 0.9568845 },
};
/* premultiplied matrices for default conversions */
static const float rec2020_to_srgb[3][3] = {
{ 1.659944, -0.588220, -0.071724 },
{ -0.124350, 1.132559, -0.008210 },
@ -300,9 +401,6 @@ static const float srgb_to_rec2020[3][3] = {
{ 0.016649, 0.089510, 0.893842 },
};
#define IDENTITY ((float**)0)
#define NONE(x) x
TRANSFORM(gdk_default_srgb_to_srgb_linear, srgb_eotf, IDENTITY, NONE);
TRANSFORM(gdk_default_srgb_linear_to_srgb, NONE, IDENTITY, srgb_oetf)
TRANSFORM(gdk_default_rec2100_pq_to_rec2100_linear, pq_eotf, IDENTITY, NONE)
@ -316,9 +414,6 @@ TRANSFORM(gdk_default_rec2100_linear_to_srgb, NONE, rec2020_to_srgb,
TRANSFORM(gdk_default_srgb_to_rec2100_pq, srgb_eotf, srgb_to_rec2020, pq_oetf)
TRANSFORM(gdk_default_rec2100_pq_to_srgb, pq_eotf, rec2020_to_srgb, srgb_oetf)
#undef IDENTITY
#undef NONE
/* }}} */
/* {{{ Default implementation */
/* {{{ Vfuncs */
@ -352,22 +447,28 @@ gdk_default_color_state_get_convert_to (GdkColorState *color_state,
{
GdkDefaultColorState *self = (GdkDefaultColorState *) color_state;
if (!GDK_IS_DEFAULT_COLOR_STATE (target))
return NULL;
if (GDK_IS_DEFAULT_COLOR_STATE (target))
return self->convert_to[GDK_DEFAULT_COLOR_STATE_ID (target)];
return self->convert_to[GDK_DEFAULT_COLOR_STATE_ID (target)];
return NULL;
}
static GdkFloatColorConvert
gdk_default_color_state_get_convert_from (GdkColorState *color_state,
GdkColorState *source)
{
GdkDefaultColorState *self = (GdkDefaultColorState *) source;
/* This is ok because the default-to-default conversion functions
* don't use the passed colorstate at all.
*/
return gdk_default_color_state_get_convert_to (source, color_state);
}
if (!GDK_IS_DEFAULT_COLOR_STATE (color_state))
return NULL;
static const GdkCicp *
gdk_default_color_state_get_cicp (GdkColorState *color_state)
{
GdkDefaultColorState *self = (GdkDefaultColorState *) color_state;
return self->convert_to[GDK_DEFAULT_COLOR_STATE_ID (color_state)];
return &self->cicp;
}
/* }}} */
@ -379,6 +480,8 @@ GdkColorStateClass GDK_DEFAULT_COLOR_STATE_CLASS = {
.get_name = gdk_default_color_state_get_name,
.get_no_srgb_tf = gdk_default_color_state_get_no_srgb_tf,
.get_convert_to = gdk_default_color_state_get_convert_to,
.get_convert_from = gdk_default_color_state_get_convert_from,
.get_cicp = gdk_default_color_state_get_cicp,
};
GdkDefaultColorState gdk_default_color_states[] = {
@ -396,6 +499,7 @@ GdkDefaultColorState gdk_default_color_states[] = {
[GDK_COLOR_STATE_ID_REC2100_PQ] = gdk_default_srgb_to_rec2100_pq,
[GDK_COLOR_STATE_ID_REC2100_LINEAR] = gdk_default_srgb_to_rec2100_linear,
},
.cicp = { 1, 13, 0, 1 },
},
[GDK_COLOR_STATE_ID_SRGB_LINEAR] = {
.parent = {
@ -411,6 +515,7 @@ GdkDefaultColorState gdk_default_color_states[] = {
[GDK_COLOR_STATE_ID_REC2100_PQ] = gdk_default_srgb_linear_to_rec2100_pq,
[GDK_COLOR_STATE_ID_REC2100_LINEAR] = gdk_default_srgb_linear_to_rec2100_linear,
},
.cicp = { 1, 8, 0, 1 },
},
[GDK_COLOR_STATE_ID_REC2100_PQ] = {
.parent = {
@ -426,6 +531,7 @@ GdkDefaultColorState gdk_default_color_states[] = {
[GDK_COLOR_STATE_ID_SRGB_LINEAR] = gdk_default_rec2100_pq_to_srgb_linear,
[GDK_COLOR_STATE_ID_REC2100_LINEAR] = gdk_default_rec2100_pq_to_rec2100_linear,
},
.cicp = { 9, 16, 0, 1 },
},
[GDK_COLOR_STATE_ID_REC2100_LINEAR] = {
.parent = {
@ -441,9 +547,316 @@ GdkDefaultColorState gdk_default_color_states[] = {
[GDK_COLOR_STATE_ID_SRGB_LINEAR] = gdk_default_rec2100_linear_to_srgb_linear,
[GDK_COLOR_STATE_ID_REC2100_PQ] = gdk_default_rec2100_linear_to_rec2100_pq,
},
.cicp = { 9, 8, 0, 1 },
},
};
/* }}} */
/* {{{ Cicp implementation */
typedef struct _GdkCicpColorState GdkCicpColorState;
struct _GdkCicpColorState
{
GdkColorState parent;
GdkColorState *no_srgb;
const char *name;
GdkTransferFunc eotf;
GdkTransferFunc oetf;
float to_srgb[3][3];
float to_rec2020[3][3];
float from_srgb[3][3];
float from_rec2020[3][3];
GdkCicp cicp;
};
/* {{{ Conversion functions */
#define cicp ((GdkCicpColorState *)self)
TRANSFORM(gdk_cicp_to_srgb, cicp->eotf, cicp->to_srgb, srgb_oetf)
TRANSFORM(gdk_cicp_to_srgb_linear, cicp->eotf, cicp->to_srgb, NONE)
TRANSFORM(gdk_cicp_to_rec2100_pq, cicp->eotf, cicp->to_rec2020, pq_oetf)
TRANSFORM(gdk_cicp_to_rec2100_linear, cicp->eotf, cicp->to_rec2020, NONE)
TRANSFORM(gdk_cicp_from_srgb, srgb_eotf, cicp->from_srgb, cicp->oetf)
TRANSFORM(gdk_cicp_from_srgb_linear, NONE, cicp->from_srgb, cicp->oetf)
TRANSFORM(gdk_cicp_from_rec2100_pq, pq_eotf, cicp->from_rec2020, cicp->oetf)
TRANSFORM(gdk_cicp_from_rec2100_linear, NONE, cicp->from_rec2020, cicp->oetf)
#undef cicp
/* }}} */
/* {{{ Vfuncs */
static void
gdk_cicp_color_state_free (GdkColorState *cs)
{
GdkCicpColorState *self = (GdkCicpColorState *) cs;
if (self->no_srgb)
gdk_color_state_unref (self->no_srgb);
g_free (self);
}
static gboolean
gdk_cicp_color_state_equal (GdkColorState *self,
GdkColorState *other)
{
GdkCicpColorState *cs1 = (GdkCicpColorState *) self;
GdkCicpColorState *cs2 = (GdkCicpColorState *) other;
return gdk_cicp_equal (&cs1->cicp, &cs2->cicp);
}
static const char *
gdk_cicp_color_state_get_name (GdkColorState *self)
{
GdkCicpColorState *cs = (GdkCicpColorState *) self;
return cs->name;
}
static GdkColorState *
gdk_cicp_color_state_get_no_srgb_tf (GdkColorState *self)
{
GdkCicpColorState *cs = (GdkCicpColorState *) self;
return cs->no_srgb;
}
static GdkFloatColorConvert
gdk_cicp_color_state_get_convert_to (GdkColorState *self,
GdkColorState *target)
{
if (!GDK_IS_DEFAULT_COLOR_STATE (target))
return NULL;
switch (GDK_DEFAULT_COLOR_STATE_ID (target))
{
case GDK_COLOR_STATE_ID_SRGB:
return gdk_cicp_to_srgb;
case GDK_COLOR_STATE_ID_SRGB_LINEAR:
return gdk_cicp_to_srgb_linear;
case GDK_COLOR_STATE_ID_REC2100_PQ:
return gdk_cicp_to_rec2100_pq;
case GDK_COLOR_STATE_ID_REC2100_LINEAR:
return gdk_cicp_to_rec2100_linear;
case GDK_COLOR_STATE_N_IDS:
default:
g_assert_not_reached ();
}
return NULL;
}
static GdkFloatColorConvert
gdk_cicp_color_state_get_convert_from (GdkColorState *self,
GdkColorState *source)
{
if (!GDK_IS_DEFAULT_COLOR_STATE (source))
return NULL;
switch (GDK_DEFAULT_COLOR_STATE_ID (source))
{
case GDK_COLOR_STATE_ID_SRGB:
return gdk_cicp_from_srgb;
case GDK_COLOR_STATE_ID_SRGB_LINEAR:
return gdk_cicp_from_srgb_linear;
case GDK_COLOR_STATE_ID_REC2100_PQ:
return gdk_cicp_from_rec2100_pq;
case GDK_COLOR_STATE_ID_REC2100_LINEAR:
return gdk_cicp_from_rec2100_linear;
case GDK_COLOR_STATE_N_IDS:
default:
g_assert_not_reached ();
}
return NULL;
}
static const GdkCicp *
gdk_cicp_color_state_get_cicp (GdkColorState *color_state)
{
GdkCicpColorState *self = (GdkCicpColorState *) color_state;
return &self->cicp;
}
/* }}} */
static const
GdkColorStateClass GDK_CICP_COLOR_STATE_CLASS = {
.free = gdk_cicp_color_state_free,
.equal = gdk_cicp_color_state_equal,
.get_name = gdk_cicp_color_state_get_name,
.get_no_srgb_tf = gdk_cicp_color_state_get_no_srgb_tf,
.get_convert_to = gdk_cicp_color_state_get_convert_to,
.get_convert_from = gdk_cicp_color_state_get_convert_from,
.get_cicp = gdk_cicp_color_state_get_cicp,
};
static inline void
multiply (float res[3][3],
const float m1[3][3],
const float m2[3][3])
{
if ((float **) m1 == IDENTITY)
memcpy (res, m2, sizeof (float) * 3 * 3);
else if ((float **) m2 == IDENTITY)
memcpy (res, m1, sizeof (float) * 3 * 3);
else
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
res[i][j] = m1[i][0] * m2[0][j] + m1[i][1] * m2[1][j] + m1[i][2] * m2[2][j];
}
GdkColorState *
gdk_color_state_new_for_cicp (const GdkCicp *cicp,
GError **error)
{
GdkCicpColorState *self;
GdkTransferFunc eotf;
GdkTransferFunc oetf;
gconstpointer to_xyz;
gconstpointer from_xyz;
if (cicp->range == GDK_CICP_RANGE_NARROW || cicp->matrix_coefficients != 0)
{
g_set_error (error,
G_IO_ERROR, G_IO_ERROR_FAILED,
_("cicp: Narrow range or YUV not supported"));
return NULL;
}
if (cicp->color_primaries == 2 ||
cicp->transfer_function == 2 ||
cicp->matrix_coefficients == 2)
{
g_set_error (error,
G_IO_ERROR, G_IO_ERROR_FAILED,
_("cicp: Unspecified parameters not supported"));
return NULL;
}
for (guint i = 0; i < GDK_COLOR_STATE_N_IDS; i++)
{
if (gdk_cicp_equivalent (cicp, &gdk_default_color_states[i].cicp))
return (GdkColorState *) &gdk_default_color_states[i];
}
switch (cicp->transfer_function)
{
case 1:
case 6:
case 14:
case 15:
eotf = bt709_eotf;
oetf = bt709_oetf;
break;
case 8:
eotf = NONE;
oetf = NONE;
break;
case 13:
eotf = srgb_eotf;
oetf = srgb_oetf;
break;
case 16:
eotf = pq_eotf;
oetf = pq_oetf;
break;
case 18:
eotf = hlg_eotf;
oetf = hlg_oetf;
break;
default:
g_set_error (error,
G_IO_ERROR, G_IO_ERROR_FAILED,
_("cicp: Transfer function %u not supported"),
cicp->transfer_function);
return NULL;
}
switch (cicp->color_primaries)
{
case 1:
to_xyz = srgb_to_xyz;
from_xyz = xyz_to_srgb;
break;
case 5:
to_xyz = pal_to_xyz;
from_xyz = xyz_to_pal;
break;
case 6:
to_xyz = ntsc_to_xyz;
from_xyz = xyz_to_ntsc;
break;
case 9:
to_xyz = rec2020_to_xyz;
from_xyz = xyz_to_rec2020;
break;
case 10:
to_xyz = IDENTITY;
from_xyz = IDENTITY;
break;
case 12:
to_xyz = p3_to_xyz;
from_xyz = xyz_to_p3;
break;
default:
g_set_error (error,
G_IO_ERROR, G_IO_ERROR_FAILED,
_("cicp: Color primaries %u not supported"),
cicp->color_primaries);
return NULL;
}
self = g_new0 (GdkCicpColorState, 1);
self->parent.klass = &GDK_CICP_COLOR_STATE_CLASS;
self->parent.ref_count = 1;
/* sRGB is special-cased by being a default colorstate */
self->parent.rendering_color_state = GDK_COLOR_STATE_REC2100_LINEAR;
self->parent.depth = GDK_MEMORY_FLOAT16;
memcpy (&self->cicp, cicp, sizeof (GdkCicp));
self->eotf = eotf;
self->oetf = oetf;
multiply (self->to_srgb, xyz_to_srgb, to_xyz);
multiply (self->to_rec2020, xyz_to_rec2020, to_xyz);
multiply (self->from_srgb, from_xyz, srgb_to_xyz);
multiply (self->from_rec2020, from_xyz, rec2020_to_xyz);
self->name = g_strdup_printf ("cicp-%u/%u/%u/%u",
cicp->color_primaries,
cicp->transfer_function,
cicp->matrix_coefficients,
cicp->range);
if (cicp->transfer_function == 13)
{
GdkCicp no_srgb;
memcpy (&no_srgb, cicp, sizeof (GdkCicp));
no_srgb.transfer_function = 8;
self->no_srgb = gdk_color_state_new_for_cicp (&no_srgb, NULL);
}
return (GdkColorState *) self;
}
/* }}} */
/* {{{ Private API */

View File

@ -6,6 +6,8 @@
#include "gdkmemoryformatprivate.h"
#include "gdkrgba.h"
#include "gdkcicpparamsprivate.h"
typedef enum
{
GDK_COLOR_STATE_ID_SRGB,
@ -27,6 +29,7 @@ struct _GdkColorState
GdkColorState *rendering_color_state;
};
/* Note: self may be the source or the target colorstate */
typedef void (* GdkFloatColorConvert)(GdkColorState *self,
float (*values)[4],
gsize n_values);
@ -42,6 +45,7 @@ struct _GdkColorStateClass
GdkColorState *target);
GdkFloatColorConvert (* get_convert_from) (GdkColorState *self,
GdkColorState *source);
const GdkCicp * (* get_cicp) (GdkColorState *self);
};
typedef struct _GdkDefaultColorState GdkDefaultColorState;
@ -53,6 +57,8 @@ struct _GdkDefaultColorState
const char *name;
GdkColorState *no_srgb;
GdkFloatColorConvert convert_to[GDK_COLOR_STATE_N_IDS];
GdkCicp cicp;
};
extern GdkDefaultColorState gdk_default_color_states[GDK_COLOR_STATE_N_IDS];
@ -133,6 +139,9 @@ _gdk_color_state_equal (GdkColorState *self,
return self->klass->equal (self, other);
}
/* Note: the functions returned from this expect the source
* color state to be passed as self
*/
static inline GdkFloatColorConvert
gdk_color_state_get_convert_to (GdkColorState *self,
GdkColorState *target)
@ -140,6 +149,9 @@ gdk_color_state_get_convert_to (GdkColorState *self,
return self->klass->get_convert_to (self, target);
}
/* Note: the functions returned from this expect the target
* color state to be passed as self
*/
static inline GdkFloatColorConvert
gdk_color_state_get_convert_from (GdkColorState *self,
GdkColorState *source)
@ -165,3 +177,12 @@ gdk_color_state_from_rgba (GdkColorState *self,
convert = gdk_color_state_get_convert_to (GDK_COLOR_STATE_SRGB, self);
convert (GDK_COLOR_STATE_SRGB, (float(*)[4]) out_color, 1);
}
static inline const GdkCicp *
gdk_color_state_get_cicp (GdkColorState *self)
{
return self->klass->get_cicp (self);
}
GdkColorState * gdk_color_state_new_for_cicp (const GdkCicp *cicp,
GError **error);