/* 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 .
*/
#include "config.h"
#include "gdkcolorstateprivate.h"
#include
#include "gdkcolordefs.h"
#include
/**
* GdkColorState:
*
* A `GdkColorState` object provides the 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_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 float (* GdkTransferFunc) (float v);
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) \
{ \
values[0] = eotf (values[0]); \
values[1] = eotf (values[1]); \
values[2] = eotf (values[2]); \
} \
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) \
{ \
values[0] = nonlinear (values[0]); \
values[1] = nonlinear (values[1]); \
values[2] = nonlinear (values[2]); \
} \
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) \
{ \
values[0] = oetf (values[0]); \
values[1] = oetf (values[1]); \
values[2] = oetf (values[2]); \
} \
} \
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;
}
CONVERT_FUNC (oklch_to_oklab)
CONVERT_FUNC (oklab_to_oklch)
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_pq)
/* }}} */
/* {{{ 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 gboolean
gdk_color_state_check_inf_nan (const float src[4],
float dest[4])
{
if (isnan (src[0]) ||
isnan (src[1]) ||
isnan (src[2]) ||
isnan (src[3]))
{
dest = (float[4]) { 1.0, 0.0, 0.8, 1.0 };
return TRUE;
}
if (isinf (src[0]) ||
isinf (src[1]) ||
isinf (src[2]) ||
isinf (src[3]))
{
dest = (float[4]) { 0.0, 0.8, 1.0, 1.0 };
return TRUE;
}
return FALSE;
}
static void
gdk_color_state_clamp_0_1 (GdkColorState *self,
const float src[4],
float dest[4])
{
if (gdk_color_state_check_inf_nan (src, dest))
return;
dest[0] = CLAMP (src[0], 0.0f, 1.0f);
dest[1] = CLAMP (src[1], 0.0f, 1.0f);
dest[2] = CLAMP (src[2], 0.0f, 1.0f);
dest[3] = CLAMP (src[3], 0.0f, 1.0f);
}
static void
gdk_color_state_clamp_unbounded (GdkColorState *self,
const float src[4],
float dest[4])
{
if (gdk_color_state_check_inf_nan (src, dest))
return;
dest[0] = src[0];
dest[1] = src[1];
dest[2] = src[2];
dest[3] = CLAMP (src[3], 0.0f, 1.0f);
}
static void
gdk_default_color_state_clamp (GdkColorState *color_state,
const float in[4],
float out[4])
{
GdkDefaultColorState *self = (GdkDefaultColorState *) color_state;
self->clamp (color_state, in, out);
}
/* }}} */
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,
.clamp = gdk_default_color_state_clamp,
};
GdkDefaultColorState gdk_default_color_states[] = {
[GDK_COLOR_STATE_ID_SRGB] = {
.parent = {
.klass = &GDK_DEFAULT_COLOR_STATE_CLASS,
.ref_count = 0,
.depth = GDK_MEMORY_U8_SRGB,
.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,
[GDK_COLOR_STATE_ID_OKLAB] = gdk_convert_srgb_to_oklab,
[GDK_COLOR_STATE_ID_OKLCH] = gdk_convert_srgb_to_oklch,
},
.clamp = gdk_color_state_clamp_0_1,
.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,
.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,
[GDK_COLOR_STATE_ID_OKLAB] = gdk_convert_srgb_linear_to_oklab,
[GDK_COLOR_STATE_ID_OKLCH] = gdk_convert_srgb_linear_to_oklch,
},
.clamp = gdk_color_state_clamp_0_1,
.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,
.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,
[GDK_COLOR_STATE_ID_OKLAB] = gdk_convert_rec2100_pq_to_oklab,
[GDK_COLOR_STATE_ID_OKLCH] = gdk_convert_rec2100_pq_to_oklch,
},
.clamp = gdk_color_state_clamp_0_1,
.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,
.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,
[GDK_COLOR_STATE_ID_OKLAB] = gdk_convert_rec2100_linear_to_oklab,
[GDK_COLOR_STATE_ID_OKLCH] = gdk_convert_rec2100_linear_to_oklch,
},
.clamp = gdk_color_state_clamp_unbounded,
.cicp = { 9, 8, 0, 1 },
},
[GDK_COLOR_STATE_ID_OKLAB] = {
.parent = {
.klass = &GDK_DEFAULT_COLOR_STATE_CLASS,
.ref_count = 0,
.depth = GDK_MEMORY_FLOAT16,
.rendering_color_state = GDK_COLOR_STATE_SRGB,
},
.name = "oklab",
.no_srgb = NULL,
.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,
[GDK_COLOR_STATE_ID_OKLCH] = gdk_convert_oklab_to_oklch,
},
.cicp = { 0, 0, 0, 0 },
},
[GDK_COLOR_STATE_ID_OKLCH] = {
.parent = {
.klass = &GDK_DEFAULT_COLOR_STATE_CLASS,
.ref_count = 0,
.depth = GDK_MEMORY_FLOAT16,
.rendering_color_state = GDK_COLOR_STATE_SRGB,
},
.name = "oklch",
.no_srgb = NULL,
.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,
[GDK_COLOR_STATE_ID_OKLAB] = gdk_convert_oklch_to_oklab,
},
.cicp = { 0, 0, 0, 0 },
},
};
/* }}} */
/* {{{ 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];
GdkCicp cicp;
};
/* {{{ Conversion functions */
#define cicp ((GdkCicpColorState *)self)
TRANSFORM(cicp_to_srgb, cicp->eotf, cicp->to_srgb, NONE, IDENTITY, srgb_oetf)
TRANSFORM(cicp_to_srgb_linear, cicp->eotf, cicp->to_srgb, NONE, IDENTITY, NONE)
TRANSFORM(cicp_to_rec2100_pq, cicp->eotf, cicp->to_rec2020, NONE, IDENTITY, pq_oetf)
TRANSFORM(cicp_to_rec2100_linear, cicp->eotf, cicp->to_rec2020, NONE, IDENTITY, NONE)
TRANSFORM(cicp_from_srgb, srgb_eotf, cicp->from_srgb, NONE, IDENTITY, cicp->oetf)
TRANSFORM(cicp_from_srgb_linear, NONE, cicp->from_srgb, NONE, IDENTITY, cicp->oetf)
TRANSFORM(cicp_from_rec2100_pq, pq_eotf, cicp->from_rec2020, NONE, IDENTITY, cicp->oetf)
TRANSFORM(cicp_from_rec2100_linear, NONE, cicp->from_rec2020, NONE, IDENTITY, cicp->oetf)
#undef cicp
TRANSFORM_PAIR (cicp_to_oklab, cicp_to_srgb_linear, srgb_linear_to_oklab)
TRANSFORM_PAIR (cicp_from_oklab, oklab_to_srgb_linear, cicp_from_srgb_linear)
TRANSFORM_PAIR (cicp_to_oklch, cicp_to_srgb_linear, srgb_linear_to_oklch)
TRANSFORM_PAIR (cicp_from_oklch, oklch_to_srgb_linear, cicp_from_srgb_linear)
/* }}} */
/* {{{ 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_ID_OKLAB:
return gdk_convert_cicp_to_oklab;
case GDK_COLOR_STATE_ID_OKLCH:
return gdk_convert_cicp_to_oklch;
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_ID_OKLAB:
return gdk_convert_cicp_from_oklab;
case GDK_COLOR_STATE_ID_OKLCH:
return gdk_convert_cicp_from_oklch;
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,
.clamp = gdk_color_state_clamp_0_1,
};
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;
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 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:
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;
}
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;
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)
{
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 */
/*
* 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);
}
/*
* 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);
}
/*< private >
* gdk_color_state_clamp:
* @self: a `GdkColorState`
* @src: the values to clamp
* @dest: (out): location to store the result, may be identical to
* the src argument
*
* Clamps the values to be within the allowed ranges for the given
* color state.
*/
void
gdk_color_state_clamp (GdkColorState *self,
const float src[4],
float dest[4])
{
self->klass->clamp (self, src, dest);
}
/* }}} */
/* vim:set foldmethod=marker: */