mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-11-12 20:00:09 +00:00
54e5cc296f
These are wide-gamut, HDR colorstates that we will need for HDR support.
469 lines
13 KiB
C
469 lines
13 KiB
C
/* 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"
|
|
|
|
#include "gdkcolorstateprivate.h"
|
|
|
|
#include <math.h>
|
|
|
|
/**
|
|
* 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 H.273 ColourPrimaries 1 with TransferCharacteristics 13 and MatrixCoefficients 0.
|
|
*
|
|
* 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 H.273 ColourPrimaries 1 with TransferCharacteristics 8 and MatrixCoefficients 0.
|
|
*
|
|
* 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 H.273 ColourPrimaries code point 9 with TransferCharacteristics 16.
|
|
*
|
|
* 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 H.273 ColourPrimaries code point 9 with TransferCharacteristics 8.
|
|
*
|
|
* 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_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);
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ 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 NULL;
|
|
|
|
return self->convert_to[GDK_DEFAULT_COLOR_STATE_ID (target)];
|
|
}
|
|
|
|
/* }}} */
|
|
/* {{{ Conversion functions */
|
|
|
|
#define TRANSFORM(name, eotf, matrix, oetf) \
|
|
static void \
|
|
name (GdkColorState *self, \
|
|
float (*values)[4], \
|
|
gsize n_values) \
|
|
{ \
|
|
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 ((float **)matrix != IDENTITY) \
|
|
{ \
|
|
float res[3]; \
|
|
res[0] = matrix[0][0] * values[i][0] + matrix[0][1] * values[i][1] + matrix[0][2] * values[i][2]; \
|
|
res[1] = matrix[1][0] * values[i][0] + matrix[1][1] * values[i][1] + matrix[1][2] * values[i][2]; \
|
|
res[2] = matrix[2][0] * values[i][0] + matrix[2][1] * values[i][1] + matrix[2][2] * values[i][2]; \
|
|
values[i][0] = res[0]; \
|
|
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]); \
|
|
} \
|
|
}
|
|
|
|
static inline float
|
|
srgb_oetf (float v)
|
|
{
|
|
if (v > 0.0031308f)
|
|
return 1.055f * powf (v, 1.f / 2.4f) - 0.055f;
|
|
else
|
|
return 12.92f * v;
|
|
}
|
|
|
|
static inline float
|
|
srgb_eotf (float v)
|
|
{
|
|
if (v >= 0.04045f)
|
|
return powf (((v + 0.055f) / (1.f + 0.055f)), 2.4f);
|
|
else
|
|
return v / 12.92f;
|
|
}
|
|
|
|
static inline float
|
|
pq_eotf (float v)
|
|
{
|
|
float ninv = (1 << 14) / 2610.0;
|
|
float minv = (1 << 5) / 2523.0;
|
|
float c1 = 3424.0 / (1 << 12);
|
|
float c2 = 2413.0 / (1 << 7);
|
|
float c3 = 2392.0 / (1 << 7);
|
|
|
|
float x = powf (MAX ((powf (v, minv) - c1), 0) / (c2 - (c3 * (powf (v, minv)))), ninv);
|
|
|
|
return x * 10000 / 203.0;
|
|
}
|
|
|
|
static inline float
|
|
pq_oetf (float v)
|
|
{
|
|
float x = v * 203.0 / 10000.0;
|
|
float n = 2610.0 / (1 << 14);
|
|
float m = 2523.0 / (1 << 5);
|
|
float c1 = 3424.0 / (1 << 12);
|
|
float c2 = 2413.0 / (1 << 7);
|
|
float c3 = 2392.0 / (1 << 7);
|
|
|
|
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
|
|
* for how to derive the abc_to_xyz matrices from chromaticity coordinates.
|
|
*/
|
|
|
|
static const float rec2020_to_srgb[3][3] = {
|
|
{ 1.659944, -0.588220, -0.071724 },
|
|
{ -0.124350, 1.132559, -0.008210 },
|
|
{ -0.018466, -0.102459, 1.120924 },
|
|
};
|
|
|
|
static const float srgb_to_rec2020[3][3] = {
|
|
{ 0.627610, 0.329815, 0.042574 },
|
|
{ 0.069029, 0.919817, 0.011154 },
|
|
{ 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)
|
|
TRANSFORM(gdk_default_rec2100_linear_to_rec2100_pq, NONE, IDENTITY, pq_oetf)
|
|
TRANSFORM(gdk_default_srgb_linear_to_rec2100_linear, NONE, srgb_to_rec2020, NONE)
|
|
TRANSFORM(gdk_default_rec2100_linear_to_srgb_linear, NONE, rec2020_to_srgb, NONE)
|
|
TRANSFORM(gdk_default_srgb_to_rec2100_linear, srgb_eotf, srgb_to_rec2020, NONE)
|
|
TRANSFORM(gdk_default_rec2100_pq_to_srgb_linear, pq_eotf, rec2020_to_srgb, NONE)
|
|
TRANSFORM(gdk_default_srgb_linear_to_rec2100_pq, NONE, srgb_to_rec2020, pq_oetf)
|
|
TRANSFORM(gdk_default_rec2100_linear_to_srgb, NONE, rec2020_to_srgb, srgb_oetf)
|
|
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
|
|
|
|
/* }}} */
|
|
|
|
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,
|
|
};
|
|
|
|
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_LINEAR,
|
|
},
|
|
.name = "srgb",
|
|
.no_srgb = GDK_COLOR_STATE_SRGB_LINEAR,
|
|
.convert_to = {
|
|
[GDK_COLOR_STATE_ID_SRGB_LINEAR] = gdk_default_srgb_to_srgb_linear,
|
|
[GDK_COLOR_STATE_ID_REC2100_PQ] = gdk_default_srgb_to_rec2100_pq,
|
|
[GDK_COLOR_STATE_ID_REC2100_LINEAR] = gdk_default_srgb_to_rec2100_linear,
|
|
},
|
|
},
|
|
[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,
|
|
},
|
|
.name = "srgb-linear",
|
|
.no_srgb = NULL,
|
|
.convert_to = {
|
|
[GDK_COLOR_STATE_ID_SRGB] = gdk_default_srgb_linear_to_srgb,
|
|
[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,
|
|
},
|
|
},
|
|
[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_LINEAR,
|
|
},
|
|
.name = "rec2100-pq",
|
|
.no_srgb = NULL,
|
|
.convert_to = {
|
|
[GDK_COLOR_STATE_ID_SRGB] = gdk_default_rec2100_pq_to_srgb,
|
|
[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,
|
|
},
|
|
},
|
|
[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,
|
|
},
|
|
.name = "rec2100-linear",
|
|
.no_srgb = NULL,
|
|
.convert_to = {
|
|
[GDK_COLOR_STATE_ID_SRGB] = gdk_default_rec2100_linear_to_srgb,
|
|
[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,
|
|
},
|
|
},
|
|
};
|
|
|
|
/* }}} */
|
|
/* {{{ Private API */
|
|
|
|
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);
|
|
}
|
|
|
|
/* }}} */
|
|
|
|
/* vim:set foldmethod=marker expandtab: */
|