/* gdkcolorstate.c
*
* Copyright 2024 Matthias Clasen
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation ; either
* version 2 of the License , or ( at your option ) any later version .
*
* This library is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library . If not , see < http : //www.gnu.org/licenses/>.
*/
#include "config.h"
#define GDK_COLOR_STATE_IMPL
#include "gdkcolorstateprivate.h"
#include <math.h>
#include "gdkcolordefs.h"
#include <glib/gi18n-lib.h>
/**
* GdkColorState :
*
* Provides information to interpret colors and pixels in a variety of ways .
*
* They are also known as
* [ * color spaces * ] ( https : //en.wikipedia.org/wiki/Color_space).
*
* Crucially , GTK knows how to convert colors from one color
* state to another .
*
* ` GdkColorState ` objects are immutable and therefore threadsafe .
*
* Since : 4 . 16
*/
G_DEFINE_BOXED_TYPE (GdkColorState, gdk_color_state,
gdk_color_state_ref, gdk_color_state_unref);
/* {{{ Public API */
/**
* gdk_color_state_ref :
* @ self : a ` GdkColorState `
*
* Increase the reference count of @ self .
*
* Returns : the object that was passed in
*
* Since : 4 . 16
*/
GdkColorState *
(gdk_color_state_ref) (GdkColorState *self)
{
return _gdk_color_state_ref (self);
}
/**
* gdk_color_state_unref :
* @ self : a ` GdkColorState `
*
* Decrease the reference count of @ self .
*
* Unless @ self is static , it will be freed
* when the reference count reaches zero .
*
* Since : 4 . 16
*/
void
(gdk_color_state_unref) (GdkColorState *self)
{
_gdk_color_state_unref (self);
}
/**
* gdk_color_state_get_srgb :
*
* Returns the color state object representing the sRGB color space .
*
* This color state uses the primaries defined by BT . 709 - 6 and the transfer function
* defined by IEC 61966 - 2 - 1 .
*
* It is equivalent to the [ Cicp ] ( class . CicpParams . html ) tuple 1 / 13 / 0 / 1 .
*
* See e . g . [ the CSS Color Module ] ( https : //www.w3.org/TR/css-color-4/#predefined-sRGB)
* for details about this colorstate .
*
* Returns : the color state object for sRGB
*
* Since : 4 . 16
*/
GdkColorState *
gdk_color_state_get_srgb (void )
{
return GDK_COLOR_STATE_SRGB;
}
/**
* gdk_color_state_get_srgb_linear :
*
* Returns the color state object representing the linearized sRGB color space .
*
* This color state uses the primaries defined by BT . 709 - 6 and a linear transfer function .
*
* It is equivalent to the [ Cicp ] ( class . CicpParams . html ) tuple 1 / 8 / 0 / 1 .
*
* See e . g . [ the CSS Color Module ] ( https : //www.w3.org/TR/css-color-4/#predefined-sRGB-linear)
* for details about this colorstate .
*
* Returns : the color state object for linearized sRGB
*
* Since : 4 . 16
*/
GdkColorState *
gdk_color_state_get_srgb_linear (void )
{
return GDK_COLOR_STATE_SRGB_LINEAR;
}
/**
* gdk_color_state_get_rec2100_pq :
*
* Returns the color state object representing the rec2100 - pq color space .
*
* This color state uses the primaries defined by BT . 2020 - 2 and BT . 2100 - 0 and the transfer
* function defined by SMPTE ST 2084 and BT . 2100 - 2 .
*
* It is equivalent to the [ Cicp ] ( class . CicpParams . html ) tuple 9 / 16 / 0 / 1 .
*
* See e . g . [ the CSS HDR Module ] ( https : //drafts.csswg.org/css-color-hdr/#valdef-color-rec2100-pq)
* for details about this colorstate .
*
* Returns : the color state object for rec2100 - pq
*
* Since : 4 . 16
*/
GdkColorState *
gdk_color_state_get_rec2100_pq (void )
{
return GDK_COLOR_STATE_REC2100_PQ;
}
/**
* gdk_color_state_get_rec2100_linear :
*
* Returns the color state object representing the linear rec2100 color space .
*
* This color state uses the primaries defined by BT . 2020 - 2 and BT . 2100 - 0 and a linear
* transfer function .
*
* It is equivalent to the [ Cicp ] ( class . CicpParams . html ) tuple 9 / 8 / 0 / 1 .
*
* See e . g . [ the CSS HDR Module ] ( https : //drafts.csswg.org/css-color-hdr/#valdef-color-rec2100-linear)
* for details about this colorstate .
*
* Returns : the color state object for linearized rec2100
*
* Since : 4 . 16
*/
GdkColorState *
gdk_color_state_get_rec2100_linear (void )
{
return GDK_COLOR_STATE_REC2100_LINEAR;
}
/**
* gdk_color_state_get_oklab :
*
* Returns the color state object representing the oklab color space .
*
* This is a perceptually uniform color state .
*
* Returns : the color state object for oklab
*
* Since : 4 . 18
*/
GdkColorState *
gdk_color_state_get_oklab (void )
{
return GDK_COLOR_STATE_OKLAB;
}
/**
* gdk_color_state_get_oklch :
*
* Returns the color state object representing the oklch color space .
*
* This is the polar variant of oklab , in which the hue is encoded as
* a polar coordinate .
*
* Returns : the color state object for oklch
*
* Since : 4 . 18
*/
GdkColorState *
gdk_color_state_get_oklch (void )
{
return GDK_COLOR_STATE_OKLCH;
}
/**
* gdk_color_state_equal :
* @ self : a ` GdkColorState `
* @ other : another ` GdkColorStatee `
*
* Compares two ` GdkColorStates ` for equality .
*
* Note that this function is not guaranteed to be perfect and two objects
* describing the same color state may compare not equal . However , different
* color states will never compare equal .
*
* Returns : % TRUE if the two color states compare equal
*
* Since : 4 . 16
*/
gboolean
(gdk_color_state_equal) (GdkColorState *self,
GdkColorState *other)
{
return _gdk_color_state_equal (self, other);
}
/**
* gdk_color_state_equivalent :
* @ self : a ` GdkColorState `
* @ other : another ` GdkColorStatee `
*
* Compares two ` GdkColorStates ` for equivalence .
*
* Two objects that represent the same color state should be equivalent ,
* even though they may not be equal in the sense of [ method @ Gdk . ColorState . equal ] .
*
* Returns : % TRUE if the two color states are equivalent
*
* Since : 4 . 20
*/
gboolean
(gdk_color_state_equivalent) (GdkColorState *self,
GdkColorState *other)
{
return _gdk_color_state_equivalent (self, other);
}
/**
* gdk_color_state_create_cicp_params :
* @ self : a ` GdkColorState `
*
* Create a [ class @ Gdk . CicpParams ] representing the colorstate .
*
* It is not guaranteed that every ` GdkColorState ` can be
* represented with Cicp parameters . If that is the case ,
* this function returns ` NULL ` .
*
* Returns : ( transfer full ) ( nullable ) : A new [ class @ Gdk . CicpParams ]
*
* Since : 4 . 16
*/
GdkCicpParams *
gdk_color_state_create_cicp_params (GdkColorState *self)
{
const GdkCicp *cicp = gdk_color_state_get_cicp (self);
if (cicp)
return gdk_cicp_params_new_for_cicp (cicp);
return NULL;
}
/* }}} */
/* {{{ Conversion functions */
typedef void (* GdkTransferFunc) (float v[3 ]);
typedef void (* GdkConvertFunc) (GdkColorState *self,
float values[4 ]);
typedef const float GdkColorMatrix[9 ];
#define IDENTITY ((float *)0 )
#define NONE ((GdkTransferFunc)0 )
#define CONVERT_FUNC(name) \
static void \
gdk_convert_ ## name (GdkColorState *self, \
float (*values)[4 ], \
gsize n_values) \
{ \
for (gsize i = 0 ; i < n_values; i++) \
{ \
name (self, values[i]); \
} \
}
#define TRANSFORM(name, eotf, matrix, nonlinear, matrix2, oetf) \
static inline void \
name (GdkColorState *self, \
float values[4 ]) \
{ \
if (eotf != NONE) \
eotf (values); \
if (matrix != IDENTITY) \
{ \
float res[3 ]; \
res[0 ] = matrix[0 ] * values[0 ] + matrix[1 ] * values[1 ] + matrix[2 ] * values[2 ]; \
res[1 ] = matrix[3 ] * values[0 ] + matrix[4 ] * values[1 ] + matrix[5 ] * values[2 ]; \
res[2 ] = matrix[6 ] * values[0 ] + matrix[7 ] * values[1 ] + matrix[8 ] * values[2 ]; \
values[0 ] = res[0 ]; \
values[1 ] = res[1 ]; \
values[2 ] = res[2 ]; \
} \
if (nonlinear != NONE) \
nonlinear (values); \
if (matrix2 != IDENTITY) \
{ \
float res[3 ]; \
res[0 ] = matrix2[0 ] * values[0 ] + matrix2[1 ] * values[1 ] + matrix2[2 ] * values[2 ]; \
res[1 ] = matrix2[3 ] * values[0 ] + matrix2[4 ] * values[1 ] + matrix2[5 ] * values[2 ]; \
res[2 ] = matrix2[6 ] * values[0 ] + matrix2[7 ] * values[1 ] + matrix2[8 ] * values[2 ]; \
values[0 ] = res[0 ]; \
values[1 ] = res[1 ]; \
values[2 ] = res[2 ]; \
} \
if (oetf != NONE) \
oetf (values); \
} \
CONVERT_FUNC (name)
#define TRANSFORM_PAIR(name, func1, func2) \
static inline void \
name (GdkColorState *self, \
float values[4 ]) \
{ \
func1 (self, values); \
func2 (self, values); \
} \
CONVERT_FUNC (name)
TRANSFORM(srgb_to_srgb_linear, srgb_eotf, IDENTITY, NONE, IDENTITY, NONE)
TRANSFORM(srgb_linear_to_srgb, NONE, IDENTITY, NONE, IDENTITY, srgb_oetf)
TRANSFORM(rec2100_pq_to_rec2100_linear, pq_eotf, IDENTITY, NONE, IDENTITY, NONE)
TRANSFORM(rec2100_linear_to_rec2100_pq, NONE, IDENTITY, NONE, IDENTITY, pq_oetf)
TRANSFORM(srgb_linear_to_rec2100_linear, NONE, srgb_to_rec2020, NONE, IDENTITY, NONE)
TRANSFORM(rec2100_linear_to_srgb_linear, NONE, rec2020_to_srgb, NONE, IDENTITY, NONE)
TRANSFORM(srgb_to_rec2100_linear, srgb_eotf, srgb_to_rec2020, NONE, IDENTITY, NONE)
TRANSFORM(rec2100_pq_to_srgb_linear, pq_eotf, rec2020_to_srgb, NONE, IDENTITY, NONE)
TRANSFORM(srgb_linear_to_rec2100_pq, NONE, srgb_to_rec2020, NONE, IDENTITY, pq_oetf)
TRANSFORM(rec2100_linear_to_srgb, NONE, rec2020_to_srgb, NONE, IDENTITY, srgb_oetf)
TRANSFORM(srgb_to_rec2100_pq, srgb_eotf, srgb_to_rec2020, NONE, IDENTITY, pq_oetf)
TRANSFORM(rec2100_pq_to_srgb, pq_eotf, rec2020_to_srgb, NONE, IDENTITY, srgb_oetf)
TRANSFORM(oklab_to_srgb_linear, NONE, oklab_to_lms, from_oklab_nl, lms_to_srgb, NONE)
TRANSFORM(oklab_to_srgb, NONE, oklab_to_lms, from_oklab_nl, lms_to_srgb, srgb_oetf)
TRANSFORM(oklab_to_rec2100_linear, NONE, oklab_to_lms, from_oklab_nl, lms_to_rec2020, NONE)
TRANSFORM(oklab_to_rec2100_pq, NONE, oklab_to_lms, from_oklab_nl, lms_to_rec2020, pq_oetf)
TRANSFORM(srgb_linear_to_oklab, NONE, srgb_to_lms, to_oklab_nl, lms_to_oklab, NONE)
TRANSFORM(srgb_to_oklab, srgb_eotf, srgb_to_lms, to_oklab_nl, lms_to_oklab, NONE)
TRANSFORM(rec2100_linear_to_oklab, NONE, rec2020_to_lms, to_oklab_nl, lms_to_oklab, NONE)
TRANSFORM(rec2100_pq_to_oklab, pq_eotf, rec2020_to_lms, to_oklab_nl, lms_to_oklab, NONE)
#define DEG_TO_RAD(x) ((x) * G_PI / 180 )
#define RAD_TO_DEG(x) ((x) * 180 / G_PI)
static inline void
_sincosf (float angle,
float *out_s,
float *out_c)
{
#ifdef HAVE_SINCOSF
sincosf (angle, out_s, out_c);
#else
*out_s = sinf (angle);
*out_c = cosf (angle);
#endif
}
static void
oklch_to_oklab (GdkColorState *self,
float values[4 ])
{
float L, C, H, a, b;
L = values[0 ];
C = values[1 ];
H = values[2 ];
_sincosf (DEG_TO_RAD (H), &b, &a);
a *= C;
b *= C;
values[0 ] = L;
values[1 ] = a;
values[2 ] = b;
}
static void
oklab_to_oklch (GdkColorState *self,
float values[4 ])
{
float L, a, b, C, H;
L = values[0 ];
a = values[1 ];
b = values[2 ];
C = hypotf (a, b);
H = RAD_TO_DEG (atan2 (b, a));
H = fmod (H, 360 );
if (H < 0 )
H += 360 ;
values[0 ] = L;
values[1 ] = C;
values[2 ] = H;
}
TRANSFORM_PAIR (srgb_to_oklch, srgb_to_oklab, oklab_to_oklch)
TRANSFORM_PAIR (srgb_linear_to_oklch, srgb_linear_to_oklab, oklab_to_oklch)
TRANSFORM_PAIR (rec2100_pq_to_oklch, rec2100_pq_to_oklab, oklab_to_oklch)
TRANSFORM_PAIR (rec2100_linear_to_oklch, rec2100_linear_to_oklab, oklab_to_oklch)
TRANSFORM_PAIR (oklch_to_srgb, oklch_to_oklab, oklab_to_srgb)
TRANSFORM_PAIR (oklch_to_srgb_linear, oklch_to_oklab, oklab_to_srgb_linear)
TRANSFORM_PAIR (oklch_to_rec2100_pq, oklch_to_oklab, oklab_to_rec2100_pq)
TRANSFORM_PAIR (oklch_to_rec2100_linear, oklch_to_oklab, oklab_to_rec2100_linear)
/* }}} */
/* {{{ Default implementation */
/* {{{ Vfuncs */
static gboolean
gdk_default_color_state_equal (GdkColorState *self,
GdkColorState *other)
{
return self == other;
}
static const char *
gdk_default_color_state_get_name (GdkColorState *color_state)
{
GdkDefaultColorState *self = (GdkDefaultColorState *) color_state;
return self->name;
}
static GdkColorState *
gdk_default_color_state_get_no_srgb_tf (GdkColorState *color_state)
{
GdkDefaultColorState *self = (GdkDefaultColorState *) color_state;
return self->no_srgb;
}
static GdkFloatColorConvert
gdk_default_color_state_get_convert_to (GdkColorState *color_state,
GdkColorState *target)
{
GdkDefaultColorState *self = (GdkDefaultColorState *) color_state;
if (GDK_IS_DEFAULT_COLOR_STATE (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)
{
/* 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);
}
static const GdkCicp *
gdk_default_color_state_get_cicp (GdkColorState *color_state)
{
GdkDefaultColorState *self = (GdkDefaultColorState *) color_state;
if (self->cicp.color_primaries == 0 )
return NULL;
return &self->cicp;
}
/* }}} */
static const
GdkColorStateClass GDK_DEFAULT_COLOR_STATE_CLASS = {
.free = NULL, /* crash here if this ever happens */
.equal = gdk_default_color_state_equal,
.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[] = {
[GDK_COLOR_STATE_ID_SRGB] = {
.parent = {
.klass = &GDK_DEFAULT_COLOR_STATE_CLASS,
.ref_count = 0 ,
.depth = GDK_MEMORY_U8,
.hue_channel = GDK_COLOR_CHANNEL_ALPHA,
.rendering_color_state = GDK_COLOR_STATE_SRGB,
.rendering_color_state_linear = GDK_COLOR_STATE_SRGB_LINEAR,
},
.name = "srgb" ,
.no_srgb = GDK_COLOR_STATE_SRGB_LINEAR,
.convert_to = {
[GDK_COLOR_STATE_ID_SRGB_LINEAR] = gdk_convert_srgb_to_srgb_linear,
[GDK_COLOR_STATE_ID_REC2100_PQ] = gdk_convert_srgb_to_rec2100_pq,
[GDK_COLOR_STATE_ID_REC2100_LINEAR] = gdk_convert_srgb_to_rec2100_linear,
},
.cicp = { 1 , 13 , 0 , 1 },
},
[GDK_COLOR_STATE_ID_SRGB_LINEAR] = {
.parent = {
.klass = &GDK_DEFAULT_COLOR_STATE_CLASS,
.ref_count = 0 ,
.depth = GDK_MEMORY_U8,
.hue_channel = GDK_COLOR_CHANNEL_ALPHA,
.rendering_color_state = GDK_COLOR_STATE_SRGB_LINEAR,
.rendering_color_state_linear = GDK_COLOR_STATE_SRGB_LINEAR,
},
.name = "srgb-linear" ,
.no_srgb = NULL,
.convert_to = {
[GDK_COLOR_STATE_ID_SRGB] = gdk_convert_srgb_linear_to_srgb,
[GDK_COLOR_STATE_ID_REC2100_PQ] = gdk_convert_srgb_linear_to_rec2100_pq,
[GDK_COLOR_STATE_ID_REC2100_LINEAR] = gdk_convert_srgb_linear_to_rec2100_linear,
},
.cicp = { 1 , 8 , 0 , 1 },
},
[GDK_COLOR_STATE_ID_REC2100_PQ] = {
.parent = {
.klass = &GDK_DEFAULT_COLOR_STATE_CLASS,
.ref_count = 0 ,
.depth = GDK_MEMORY_FLOAT16,
.hue_channel = GDK_COLOR_CHANNEL_ALPHA,
.rendering_color_state = GDK_COLOR_STATE_REC2100_PQ,
.rendering_color_state_linear = GDK_COLOR_STATE_REC2100_LINEAR,
},
.name = "rec2100-pq" ,
.no_srgb = NULL,
.convert_to = {
[GDK_COLOR_STATE_ID_SRGB] = gdk_convert_rec2100_pq_to_srgb,
[GDK_COLOR_STATE_ID_SRGB_LINEAR] = gdk_convert_rec2100_pq_to_srgb_linear,
[GDK_COLOR_STATE_ID_REC2100_LINEAR] = gdk_convert_rec2100_pq_to_rec2100_linear,
},
.cicp = { 9 , 16 , 0 , 1 },
},
[GDK_COLOR_STATE_ID_REC2100_LINEAR] = {
.parent = {
.klass = &GDK_DEFAULT_COLOR_STATE_CLASS,
.ref_count = 0 ,
.depth = GDK_MEMORY_FLOAT16,
.hue_channel = GDK_COLOR_CHANNEL_ALPHA,
.rendering_color_state = GDK_COLOR_STATE_REC2100_LINEAR,
.rendering_color_state_linear = GDK_COLOR_STATE_REC2100_LINEAR,
},
.name = "rec2100-linear" ,
.no_srgb = NULL,
.convert_to = {
[GDK_COLOR_STATE_ID_SRGB] = gdk_convert_rec2100_linear_to_srgb,
[GDK_COLOR_STATE_ID_SRGB_LINEAR] = gdk_convert_rec2100_linear_to_srgb_linear,
[GDK_COLOR_STATE_ID_REC2100_PQ] = gdk_convert_rec2100_linear_to_rec2100_pq,
},
.cicp = { 9 , 8 , 0 , 1 },
},
};
G_STATIC_ASSERT (G_N_ELEMENTS (gdk_default_color_states) == GDK_COLOR_STATE_N_IDS);
static gboolean
gdk_builtin_color_state_equal (GdkColorState *self,
GdkColorState *other)
{
return self == other;
}
static const char *
gdk_builtin_color_state_get_name (GdkColorState *color_state)
{
GdkBuiltinColorState *self = (GdkBuiltinColorState *) color_state;
return self->name;
}
/*<private>
* gdk_color_state_get_hue_channel :
* @ self : the color state
* @ out_channel : ( out caller - allocates ) : the channel
*
* Checks if the color state has a hue channel and returns it if
* there is one .
*
* Unlike other channels , hue channels are periodic , so they may
* need different treatment in various places .
*
* Returns : true if the color state has a hue channel
**/
gboolean
gdk_color_state_get_hue_channel (GdkColorState *self,
GdkColorChannel *out_channel)
{
if (self->hue_channel == GDK_COLOR_CHANNEL_ALPHA)
return FALSE ;
*out_channel = self->hue_channel;
return TRUE ;
}
static GdkColorState *
gdk_builtin_color_state_get_no_srgb_tf (GdkColorState *color_state)
{
return NULL;
}
static GdkFloatColorConvert
gdk_builtin_color_state_get_convert_to (GdkColorState *color_state,
GdkColorState *target)
{
GdkBuiltinColorState *self = (GdkBuiltinColorState *) color_state;
if (GDK_IS_DEFAULT_COLOR_STATE (target))
return self->convert_to[GDK_DEFAULT_COLOR_STATE_ID (target)];
return NULL;
}
static GdkFloatColorConvert
gdk_builtin_color_state_get_convert_from (GdkColorState *color_state,
GdkColorState *source)
{
GdkBuiltinColorState *self = (GdkBuiltinColorState *) color_state;
if (GDK_IS_DEFAULT_COLOR_STATE (source))
return self->convert_from[GDK_DEFAULT_COLOR_STATE_ID (source)];
return NULL;
}
static const GdkCicp *
gdk_builtin_color_state_get_cicp (GdkColorState *color_state)
{
return NULL;
}
/* }}} */
static const
GdkColorStateClass GDK_BUILTIN_COLOR_STATE_CLASS = {
.free = NULL, /* crash here if this ever happens */
.equal = gdk_builtin_color_state_equal,
.get_name = gdk_builtin_color_state_get_name,
.get_no_srgb_tf = gdk_builtin_color_state_get_no_srgb_tf,
.get_convert_to = gdk_builtin_color_state_get_convert_to,
.get_convert_from = gdk_builtin_color_state_get_convert_from,
.get_cicp = gdk_builtin_color_state_get_cicp,
};
GdkBuiltinColorState gdk_builtin_color_states[] = {
[GDK_BUILTIN_COLOR_STATE_ID_OKLAB] = {
.parent = {
.klass = &GDK_BUILTIN_COLOR_STATE_CLASS,
.ref_count = 0 ,
.depth = GDK_MEMORY_FLOAT16,
.hue_channel = GDK_COLOR_CHANNEL_ALPHA,
.rendering_color_state = GDK_COLOR_STATE_SRGB,
},
.name = "oklab" ,
.convert_to = {
[GDK_COLOR_STATE_ID_SRGB] = gdk_convert_oklab_to_srgb,
[GDK_COLOR_STATE_ID_SRGB_LINEAR] = gdk_convert_oklab_to_srgb_linear,
[GDK_COLOR_STATE_ID_REC2100_PQ] = gdk_convert_oklab_to_rec2100_pq,
[GDK_COLOR_STATE_ID_REC2100_LINEAR] = gdk_convert_oklab_to_rec2100_linear,
},
.convert_from = {
[GDK_COLOR_STATE_ID_SRGB] = gdk_convert_srgb_to_oklab,
[GDK_COLOR_STATE_ID_SRGB_LINEAR] = gdk_convert_srgb_linear_to_oklab,
[GDK_COLOR_STATE_ID_REC2100_PQ] = gdk_convert_rec2100_pq_to_oklab,
[GDK_COLOR_STATE_ID_REC2100_LINEAR] = gdk_convert_rec2100_linear_to_oklab,
},
},
[GDK_BUILTIN_COLOR_STATE_ID_OKLCH] = {
.parent = {
.klass = &GDK_BUILTIN_COLOR_STATE_CLASS,
.ref_count = 0 ,
.depth = GDK_MEMORY_FLOAT16,
.hue_channel = GDK_COLOR_CHANNEL_BLUE,
.rendering_color_state = GDK_COLOR_STATE_SRGB,
},
.name = "oklch" ,
.convert_to = {
[GDK_COLOR_STATE_ID_SRGB] = gdk_convert_oklch_to_srgb,
[GDK_COLOR_STATE_ID_SRGB_LINEAR] = gdk_convert_oklch_to_srgb_linear,
[GDK_COLOR_STATE_ID_REC2100_PQ] = gdk_convert_oklch_to_rec2100_pq,
[GDK_COLOR_STATE_ID_REC2100_LINEAR] = gdk_convert_oklch_to_rec2100_linear,
},
.convert_from = {
[GDK_COLOR_STATE_ID_SRGB] = gdk_convert_srgb_to_oklch,
[GDK_COLOR_STATE_ID_SRGB_LINEAR] = gdk_convert_srgb_linear_to_oklch,
[GDK_COLOR_STATE_ID_REC2100_PQ] = gdk_convert_rec2100_pq_to_oklch,
[GDK_COLOR_STATE_ID_REC2100_LINEAR] = gdk_convert_rec2100_linear_to_oklch,
},
},
};
/* }}} */
/* {{{ Cicp implementation */
typedef struct _GdkCicpColorState GdkCicpColorState;
struct _GdkCicpColorState
{
GdkColorState parent;
GdkColorState *no_srgb;
char *name;
GdkTransferFunc eotf;
GdkTransferFunc oetf;
float to_srgb[9 ];
float to_rec2020[9 ];
float from_srgb[9 ];
float from_rec2020[9 ];
const float *from_yuv;
const float *to_yuv;
GdkCicp cicp;
};
/* {{{ Conversion functions */
#define TRANSFORM_FROM_CICP(name, matrix, oetf) \
static void \
name (GdkColorState *color_state, \
float (*values)[4 ], \
gsize n_values) \
{ \
GdkCicpColorState *self = (GdkCicpColorState *) color_state; \
\
transform_from_cicp (self, oetf, self->matrix, values, n_values); \
}
static void
transform_from_cicp (GdkCicpColorState *self,
GdkTransferFunc oetf,
float matrix[9 ],
float (*values)[4 ],
gsize n_values)
{
for (gsize i = 0 ; i < n_values; i++)
{
if (self->cicp.range == GDK_CICP_RANGE_NARROW)
{
values[i][0 ] = CLAMP ((values[i][0 ] - 16 .0 /255 .0 ) * 255 .0 / 224 .0 , 0 .0 , 1 .0 );
values[i][1 ] = CLAMP ((values[i][1 ] - 16 .0 /255 .0 ) * 255 .0 / 219 .0 , 0 .0 , 1 .0 );
values[i][2 ] = CLAMP ((values[i][2 ] - 16 .0 /255 .0 ) * 255 .0 / 224 .0 , 0 .0 , 1 .0 );
}
if (self->from_yuv)
{
float res[3 ];
values[i][0 ] -= 0 .5 ;
values[i][2 ] -= 0 .5 ;
res[0 ] = self->from_yuv[0 ] * values[i][0 ] + self->from_yuv[1 ] * values[i][1 ] + self->from_yuv[2 ] * values[i][2 ];
res[1 ] = self->from_yuv[3 ] * values[i][0 ] + self->from_yuv[4 ] * values[i][1 ] + self->from_yuv[5 ] * values[i][2 ];
res[2 ] = self->from_yuv[6 ] * values[i][0 ] + self->from_yuv[7 ] * values[i][1 ] + self->from_yuv[8 ] * values[i][2 ];
values[i][0 ] = res[0 ];
values[i][1 ] = res[1 ];
values[i][2 ] = res[2 ];
}
if (self->eotf != NONE)
self->eotf (values[i]);
if (matrix != IDENTITY)
{
float res[3 ];
res[0 ] = matrix[0 ] * values[i][0 ] + matrix[1 ] * values[i][1 ] + matrix[2 ] * values[i][2 ];
res[1 ] = matrix[3 ] * values[i][0 ] + matrix[4 ] * values[i][1 ] + matrix[5 ] * values[i][2 ];
res[2 ] = matrix[6 ] * values[i][0 ] + matrix[7 ] * values[i][1 ] + matrix[8 ] * values[i][2 ];
values[i][0 ] = res[0 ];
values[i][1 ] = res[1 ];
values[i][2 ] = res[2 ];
}
if (oetf != NONE)
oetf (values[i]);
}
}
#define TRANSFORM_TO_CICP(name, eotf, matrix) \
static void \
name (GdkColorState *color_state, \
float (*values)[4 ], \
gsize n_values) \
{ \
GdkCicpColorState *self = (GdkCicpColorState *) color_state; \
\
transform_to_cicp (self, eotf, self->matrix, values, n_values); \
}
static void
transform_to_cicp (GdkCicpColorState *self,
GdkTransferFunc eotf,
float matrix[9 ],
float (*values)[4 ],
gsize n_values)
{
for (gsize i = 0 ; i < n_values; i++)
{
if (eotf != NONE)
eotf (values[i]);
if (matrix != IDENTITY)
{
float res[3 ];
res[0 ] = matrix[0 ] * values[i][0 ] + matrix[1 ] * values[i][1 ] + matrix[2 ] * values[i][2 ];
res[1 ] = matrix[3 ] * values[i][0 ] + matrix[4 ] * values[i][1 ] + matrix[5 ] * values[i][2 ];
res[2 ] = matrix[6 ] * values[i][0 ] + matrix[7 ] * values[i][1 ] + matrix[8 ] * values[i][2 ];
values[i][0 ] = res[0 ];
values[i][1 ] = res[1 ];
values[i][2 ] = res[2 ];
}
else
{
float tmp = values[i][2 ];
values[i][2 ] = values[i][0 ];
values[i][0 ] = values[i][1 ];
values[i][1 ] = tmp;
}
if (self->oetf != NONE)
self->oetf (values[i]);
if (self->to_yuv)
{
float res[3 ];
res[0 ] = self->to_yuv[0 ] * values[i][0 ] + self->to_yuv[1 ] * values[i][1 ] + self->to_yuv[2 ] * values[i][2 ];
res[1 ] = self->to_yuv[3 ] * values[i][0 ] + self->to_yuv[4 ] * values[i][1 ] + self->to_yuv[5 ] * values[i][2 ];
res[2 ] = self->to_yuv[6 ] * values[i][0 ] + self->to_yuv[7 ] * values[i][1 ] + self->to_yuv[8 ] * values[i][2 ];
values[i][0 ] = res[0 ] + 0 .5 ;
values[i][1 ] = res[1 ];
values[i][2 ] = res[2 ] + 0 .5 ;
}
values[i][0 ] = CLAMP (values[i][0 ], 0 .0 , 1 .0 );
values[i][1 ] = CLAMP (values[i][1 ], 0 .0 , 1 .0 );
values[i][2 ] = CLAMP (values[i][2 ], 0 .0 , 1 .0 );
if (self->cicp.range == GDK_CICP_RANGE_NARROW)
{
values[i][0 ] = values[i][0 ] * 224 .0 / 255 .0 + 16 .0 / 255 .0 ;
values[i][1 ] = values[i][1 ] * 219 .0 / 255 .0 + 16 .0 / 255 .0 ;
values[i][2 ] = values[i][2 ] * 224 .0 / 255 .0 + 16 .0 / 255 .0 ;
}
}
}
TRANSFORM_FROM_CICP(gdk_convert_cicp_to_srgb, to_srgb, srgb_oetf)
TRANSFORM_FROM_CICP(gdk_convert_cicp_to_srgb_linear, to_srgb, NONE)
TRANSFORM_FROM_CICP(gdk_convert_cicp_to_rec2100_pq, to_rec2020, pq_oetf)
TRANSFORM_FROM_CICP(gdk_convert_cicp_to_rec2100_linear, to_rec2020, NONE)
TRANSFORM_TO_CICP(gdk_convert_cicp_from_srgb, srgb_eotf, from_srgb)
TRANSFORM_TO_CICP(gdk_convert_cicp_from_srgb_linear, NONE, from_srgb)
TRANSFORM_TO_CICP(gdk_convert_cicp_from_rec2100_pq, pq_eotf, from_rec2020)
TRANSFORM_TO_CICP(gdk_convert_cicp_from_rec2100_linear, NONE, from_rec2020)
/* }}} */
/* {{{ Vfuncs */
static void
gdk_cicp_color_state_free (GdkColorState *cs)
{
GdkCicpColorState *self = (GdkCicpColorState *) cs;
g_free (self->name);
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_convert_cicp_to_srgb;
case GDK_COLOR_STATE_ID_SRGB_LINEAR:
return gdk_convert_cicp_to_srgb_linear;
case GDK_COLOR_STATE_ID_REC2100_PQ:
return gdk_convert_cicp_to_rec2100_pq;
case GDK_COLOR_STATE_ID_REC2100_LINEAR:
return gdk_convert_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_convert_cicp_from_srgb;
case GDK_COLOR_STATE_ID_SRGB_LINEAR:
return gdk_convert_cicp_from_srgb_linear;
case GDK_COLOR_STATE_ID_REC2100_PQ:
return gdk_convert_cicp_from_rec2100_pq;
case GDK_COLOR_STATE_ID_REC2100_LINEAR:
return gdk_convert_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 float *
multiply (float res[9 ],
const float m1[9 ],
const float m2[9 ])
{
#define IDX(i,j) 3 *i+j
for (int i = 0 ; i < 3 ; i++)
for (int j = 0 ; j < 3 ; j++)
res[IDX(i,j)] = m1[IDX(i,0 )] * m2[IDX(0 ,j)]
+ m1[IDX(i,1 )] * m2[IDX(1 ,j)]
+ m1[IDX(i,2 )] * m2[IDX(2 ,j)];
return res;
}
GdkColorState *
gdk_color_state_new_for_cicp (const GdkCicp *cicp,
GError **error)
{
GdkCicpColorState *self;
GdkTransferFunc eotf;
GdkTransferFunc oetf;
gconstpointer to_xyz;
gconstpointer from_xyz;
gconstpointer to_yuv = NULL;
gconstpointer from_yuv = 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_equal (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 4 :
eotf = gamma22_eotf;
oetf = gamma22_oetf;
break ;
case 5 :
eotf = gamma28_eotf;
oetf = gamma28_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 :
case 7 :
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 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;
}
switch (cicp->matrix_coefficients)
{
case 0 :
to_yuv = IDENTITY;
from_yuv = IDENTITY;
break ;
case 1 :
to_yuv = rgb_to_bt709;
from_yuv = bt709_to_rgb;
break ;
case 5 :
case 6 :
to_yuv = rgb_to_bt601;
from_yuv = bt601_to_rgb;
break ;
case 9 :
to_yuv = rgb_to_bt2020;
from_yuv = bt2020_to_rgb;
break ;
default :
g_set_error (error,
G_IO_ERROR, G_IO_ERROR_FAILED,
_("cicp: Matrix coefficients %u, %s not supported" ),
cicp->matrix_coefficients,
cicp->range == GDK_CICP_RANGE_NARROW ? "narrow" : "full" );
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_PQ;
self->parent.rendering_color_state_linear = GDK_COLOR_STATE_REC2100_LINEAR;
self->parent.depth = GDK_MEMORY_FLOAT16;
self->parent.hue_channel = GDK_COLOR_CHANNEL_ALPHA;
memcpy (&self->cicp, cicp, sizeof (GdkCicp));
self->to_yuv = to_yuv;
self->from_yuv = from_yuv;
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 )
{
self->no_srgb = gdk_color_state_new_for_cicp (&(GdkCicp) {
cicp->color_primaries,
8 ,
cicp->matrix_coefficients,
cicp->range },
NULL);
}
return (GdkColorState *) self;
}
/* }}} */
/* {{{ Private API */
/*<private>
* gdk_color_state_get_name :
* @ self : a colorstate
*
* Returns the name of @ self .
*
* This is * not * a translated , user - visible string .
*
* Returns : ( transfer none ) : a name for representing the color state
* in diagnostic output
*/
const char *
gdk_color_state_get_name (GdkColorState *self)
{
return self->klass->get_name (self);
}
/*<private>
* gdk_color_state_get_no_srgb_tf :
* @ self : a colorstate
*
* This function checks if the colorstate uses an sRGB transfer function
* as final operation . In that case , it is suitable for use with GL_SRGB
* ( and the Vulkan equivalents ) .
*
* If it is suitable , the colorstate without the transfer function is
* returned . Otherwise , this function returns NULL .
*
* Returns : ( transfer none ) : the colorstate without sRGB transfer function .
**/
GdkColorState *
gdk_color_state_get_no_srgb_tf (GdkColorState *self)
{
if (!GDK_DEBUG_CHECK (LINEAR))
return FALSE ;
return self->klass->get_no_srgb_tf (self);
}
GdkColorState *
gdk_color_state_yuv (void )
{
static GdkColorState *yuv_cs;
if (g_once_init_enter_pointer (&yuv_cs))
{
GdkColorState *cs;
cs = gdk_color_state_new_for_cicp (&(GdkCicp) { 1 , 13 , 5 , 0 }, NULL);
g_assert (cs);
g_once_init_leave_pointer (&yuv_cs, cs);
}
return yuv_cs;
}
/* }}} */
/* vim:set foldmethod=marker: */
Messung V0.5 in Prozent C=97 H=97 G=96
¤ Dauer der Verarbeitung: 0.8 Sekunden
(vorverarbeitet am 2026-07-03)
¤
*© Formatika GbR, Deutschland