Compare commits

...

8 Commits

Author SHA1 Message Date
Matthias Clasen
ebead0b629 Add rec2100-pq and rec2100-linear
These are HDR colorstates that we will need for HDR support.
2024-07-11 13:02:50 -04:00
Matthias Clasen
acce932e8d Add rec2020 and rec2020-linear
These are wide-gamut SDR colorstates and can be useful
as compoisting color states.
2024-07-11 13:02:50 -04:00
Matthias Clasen
ad9887457f Add oklab and oklch
These colorstates will mainly be interesting as interpolation
states for gradients.
2024-07-11 13:02:50 -04:00
Matthias Clasen
740e9c6857 Allow indirect conversions
When won't necessarily have a direct conversion function for every
pair of colorstates, but we guarantee that every colorstate can
convert to and from XYZ, so we can always do a conversion in two
steps.
2024-07-11 13:02:50 -04:00
Matthias Clasen
01ff202094 colorstate: Add xyz
This is mainly to test that our colorstate machinery is exensible.

We will also use xyz as the central hub for converting between
arbitrary color states.
2024-07-11 13:02:50 -04:00
Matthias Clasen
497585db37 colorstate: Small reorg
Introduce a helper macro.
2024-07-11 13:02:50 -04:00
Matthias Clasen
24a55b0ac1 Add tests for color state conversion
Test roundtrip tests between color states.

For now, this just tests srgb<>srgb-linear.
2024-07-11 13:02:50 -04:00
Matthias Clasen
1382cf916b colorstate: Consistent naming
Make the conversion functions use srgb_linear in their names, since
the name of the color state is srgb-linear.
2024-07-11 13:02:50 -04:00
7 changed files with 970 additions and 32 deletions

View File

@@ -103,6 +103,71 @@ gdk_color_state_get_srgb_linear (void)
return GDK_COLOR_STATE_SRGB_LINEAR;
}
/**
* gdk_color_state_get_xyz:
*
* Returns the color state object representing the XYZ color space.
*
* Since: 4.16
*/
GdkColorState *
gdk_color_state_get_xyz (void)
{
return GDK_COLOR_STATE_XYZ;
}
/**
* gdk_color_state_get_oklab:
*
* Returns the color state object representing the OKLAB color space.
*
* Since: 4.16
*/
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.
*
* Since: 4.16
*/
GdkColorState *
gdk_color_state_get_oklch (void)
{
return GDK_COLOR_STATE_OKLCH;
}
/**
* gdk_color_state_get_rec2020:
*
* Returns the color state object representing the rec2020 color space.
*
* Since: 4.16
*/
GdkColorState *
gdk_color_state_get_rec2020 (void)
{
return GDK_COLOR_STATE_REC2020;
}
/**
* gdk_color_state_get_rec2020_linear:
*
* Returns the color state object representing the linear rec2020 color space.
*
* Since: 4.16
*/
GdkColorState *
gdk_color_state_get_rec2020_linear (void)
{
return GDK_COLOR_STATE_REC2020_LINEAR;
}
/**
* gdk_color_state_equal:
* @self: a `GdkColorState`
@@ -127,7 +192,7 @@ gboolean
/* }}} */
/* {{{ Default implementation */
/* {{{ Vfuncs */
static gboolean
gdk_default_color_state_equal (GdkColorState *self,
GdkColorState *other)
@@ -151,6 +216,35 @@ gdk_default_color_state_get_no_srgb_tf (GdkColorState *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 COORDINATE_TRANSFORM(name, tf) \
static void \
name(GdkColorState *self, \
float (*values)[4], \
gsize n_values) \
{ \
for (gsize i = 0; i < n_values; i++) \
{ \
values[i][0] = tf (values[i][0]); \
values[i][1] = tf (values[i][1]); \
values[i][2] = tf (values[i][2]); \
} \
}
static inline float
srgb_oetf (float v)
{
@@ -169,48 +263,283 @@ srgb_eotf (float v)
return v / 12.92f;
}
static void
gdk_default_srgb_to_linear_srgb (GdkColorState *self,
float (*values)[4],
gsize n_values)
{
gsize i;
COORDINATE_TRANSFORM(gdk_default_srgb_to_srgb_linear, srgb_eotf)
COORDINATE_TRANSFORM(gdk_default_srgb_linear_to_srgb, srgb_oetf)
for (i = 0; i < n_values; i++)
static inline void
vec3_multiply (const float matrix[3][3],
const float vec[3],
float res[3])
{
res[0] = matrix[0][0] * vec[0] + matrix[0][1] * vec[1] + matrix[0][2] * vec[2];
res[1] = matrix[1][0] * vec[0] + matrix[1][1] * vec[1] + matrix[1][2] * vec[2];
res[2] = matrix[2][0] * vec[0] + matrix[2][1] * vec[1] + matrix[2][2] * vec[2];
}
#define LINEAR_TRANSFORM(name, matrix) \
static void \
name (GdkColorState *self, \
float (*values)[4], \
gsize n_values) \
{ \
for (gsize i = 0; i < n_values; i++) \
{ \
float res[3]; \
\
vec3_multiply (matrix, values[i], res); \
\
values[i][0] = res[0]; \
values[i][1] = res[1]; \
values[i][2] = res[2]; \
} \
}
static const float srgb_linear_to_xyz[3][3] = {
{ (506752.0 / 1228815.0), (87881.0 / 245763.0), (12673.0 / 70218.0) },
{ (87098.0 / 409605.0), (175762.0 / 245763.0), (12673.0 / 175545.0) },
{ ( 7918.0 / 409605.0), (87881.0 / 737289.0), (1001167.0 / 1053270.0) },
};
static const float xyz_to_srgb_linear[3][3] = {
{ (12831.0 / 3959.0), - (329.0 / 214.0), - (1974.0 / 3959.0) },
{ - (851781.0 / 878810.0), (1648619.0 / 878810.0), (36519.0 / 878810.0) },
{ (705.0 / 12673.0), - (2585.0 / 12673.0), (705.0 / 667.0) },
};
LINEAR_TRANSFORM(gdk_default_xyz_to_srgb_linear, xyz_to_srgb_linear)
LINEAR_TRANSFORM(gdk_default_srgb_linear_to_xyz, srgb_linear_to_xyz)
#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
gdk_default_oklab_to_oklch (GdkColorState *self,
float (*values)[4],
gsize n_values)
{
for (gsize i = 0; i < n_values; i++)
{
values[i][0] = srgb_eotf (values[i][0]);
values[i][1] = srgb_eotf (values[i][1]);
values[i][2] = srgb_eotf (values[i][2]);
float a = values[i][1];
float b = values[i][2];
float C, H;
C = hypotf (a, b);
H = RAD_TO_DEG (atan2 (b, a));
H = fmod (H, 360);
if (H < 0)
H += 360;
values[i][1] = C;
values[i][2] = H;
}
}
static void
gdk_default_srgb_linear_to_srgb (GdkColorState *self,
float (*values)[4],
gsize n_values)
gdk_default_oklch_to_oklab (GdkColorState *self,
float (*values)[4],
gsize n_values)
{
gsize i;
for (i = 0; i < n_values; i++)
for (gsize i = 0; i < n_values; i++)
{
values[i][0] = srgb_oetf (values[i][0]);
values[i][1] = srgb_oetf (values[i][1]);
values[i][2] = srgb_oetf (values[i][2]);
float C = values[i][1];
float H = values[i][2];
float a, b;
_sincosf (DEG_TO_RAD (H), &b, &a);
a *= C;
b *= C;
values[i][1] = a;
values[i][2] = b;
}
}
static GdkFloatColorConvert
gdk_default_color_state_get_convert_to (GdkColorState *color_state,
GdkColorState *target)
static const float oklab_to_lms[3][3] = {
{ 1, 0.3963377774, 0.2158037573 },
{ 1, - 0.1055613458, - 0.0638541728 },
{ 1, - 0.0894841775, - 1.2914855480 },
};
static const float lms_to_srgb_linear[3][3] = {
{ 4.0767416621, - 3.3077115913, 0.2309699292 },
{ - 1.2684380046, 2.6097574011, - 0.3413193965 },
{ - 0.0041960863, - 0.7034186147, 1.7076147010 },
};
#define SUM(a, b, i, j) ((a)[i][0] * (b)[0][j] + (a)[i][1] * (b)[1][j] + (a)[i][2] * (b)[2][j])
#define MATMUL(name, a, b) \
static const float name[3][3] = { \
{ SUM((a),(b),0,0), SUM((a),(b),0,1), SUM((a),(b),0,2) }, \
{ SUM((a),(b),1,0), SUM((a),(b),1,1), SUM((a),(b),1,2) }, \
{ SUM((a),(b),2,0), SUM((a),(b),2,1), SUM((a),(b),2,2) }, \
};
MATMUL(lms_to_xyz, lms_to_srgb_linear, srgb_linear_to_xyz)
static void
gdk_default_oklab_to_xyz (GdkColorState *self,
float (*values)[4],
gsize n_values)
{
GdkDefaultColorState *self = (GdkDefaultColorState *) color_state;
for (gsize i = 0; i < n_values; i++)
{
float lms[3];
if (!GDK_IS_DEFAULT_COLOR_STATE (target))
return NULL;
vec3_multiply (oklab_to_lms, values[i], lms);
return self->convert_to[GDK_DEFAULT_COLOR_STATE_ID (target)];
lms[0] = powf (lms[0], 3);
lms[1] = powf (lms[1], 3);
lms[2] = powf (lms[2], 3);
vec3_multiply (lms_to_xyz, lms, values[i]);
}
}
static const float srgb_linear_to_lms[3][3] = {
{ 0.4122214708, 0.5363325363, 0.0514459929 },
{ 0.2119034982, 0.6806995451, 0.1073969566 },
{ 0.0883024619, 0.2817188376, 0.6299787005 },
};
static const float lms_to_oklab[3][3] = {
{ 0.2104542553, 0.7936177850, - 0.0040720468 },
{ 1.9779984951, - 2.4285922050, 0.4505937099 },
{ 0.0259040371, 0.7827717662, - 0.8086757660 },
};
MATMUL(xyz_to_lms, xyz_to_srgb_linear, srgb_linear_to_lms)
static void
gdk_default_xyz_to_oklab (GdkColorState *self,
float (*values)[4],
gsize n_values)
{
for (gsize i = 0; i < n_values; i++)
{
float lms[3];
vec3_multiply (xyz_to_lms, values[i], lms);
lms[0] = cbrtf (lms[0]);
lms[1] = cbrtf (lms[1]);
lms[2] = cbrtf (lms[2]);
vec3_multiply (lms_to_oklab, lms, values[i]);
}
}
static inline float
rec2020_eotf (float v)
{
float alpha = 1.09929682680944;
float beta = 0.018053968510807;
int sign = v < 0 ? -1 : 1;
float abs = fabsf (v);
if (abs < beta * 4.5 )
return v/ 4.5;
else
return sign * powf ((abs + alpha - 1) / alpha, 1.0 / 0.45);
}
static inline float
rec2020_oetf (float v)
{
float alpha = 1.09929682680944;
float beta = 0.018053968510807;
int sign = v < 0 ? -1 : 1;
float abs = fabsf (v);
if (abs > beta)
return sign * (alpha * powf (abs, 0.45) - (alpha - 1));
else
return 4.5 * v;
}
COORDINATE_TRANSFORM(gdk_default_rec2020_to_rec2020_linear, rec2020_eotf)
COORDINATE_TRANSFORM(gdk_default_rec2020_linear_to_rec2020, rec2020_oetf)
static const float rec2020_linear_to_xyz[3][3] = {
{ (63426534.0 / 99577255.0), (20160776.0 / 139408157.0), (47086771.0 / 278816314.0) },
{ (26158966.0 / 99577255.0), (472592308.0 / 697040785.0), (8267143.0 / 139408157.0) },
{ ( 0 / 1), (19567812.0 / 697040785.0), (295819943.0 / 278816314.0) },
};
static const float xyz_to_rec2020_linear[3][3] = {
{ (30757411.0 / 17917100.0), - (6372589.0 / 17917100.0), - (4539589.0 / 17917100.0) },
{ - (19765991.0 / 29648200.0), (47925759.0 / 29648200.0), (467509.0 / 29648200.0) },
{ (792561.0 / 44930125.0), - (1921689.0 / 44930125.0), (42328811.0 / 44930125.0) },
};
LINEAR_TRANSFORM(gdk_default_rec2020_linear_to_xyz, rec2020_linear_to_xyz)
LINEAR_TRANSFORM(gdk_default_xyz_to_rec2020_linear, xyz_to_rec2020_linear)
static inline float
rec2100_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
rec2100_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);
}
COORDINATE_TRANSFORM(gdk_default_rec2100_pq_to_rec2100_linear, rec2100_pq_eotf)
COORDINATE_TRANSFORM(gdk_default_rec2100_linear_to_rec2100_pq, rec2100_pq_oetf)
#define CONCAT(name, f1, f2) \
static void \
name (GdkColorState *self, \
float (*values)[4], \
gsize n_values) \
{ \
f1 (self, values, n_values); \
f2 (self, values, n_values); \
}
CONCAT(gdk_default_xyz_to_srgb, gdk_default_xyz_to_srgb_linear, gdk_default_srgb_linear_to_srgb);
CONCAT(gdk_default_srgb_to_xyz, gdk_default_srgb_to_srgb_linear, gdk_default_srgb_linear_to_xyz);
CONCAT(gdk_default_oklch_to_xyz, gdk_default_oklch_to_oklab, gdk_default_oklab_to_xyz);
CONCAT(gdk_default_xyz_to_oklch, gdk_default_xyz_to_oklab, gdk_default_oklab_to_oklch);
CONCAT(gdk_default_rec2020_to_xyz, gdk_default_rec2020_to_rec2020_linear, gdk_default_rec2020_linear_to_xyz);
CONCAT(gdk_default_xyz_to_rec2020, gdk_default_xyz_to_rec2020_linear, gdk_default_rec2020_linear_to_rec2020);
CONCAT(gdk_default_rec2100_pq_to_xyz, gdk_default_rec2100_pq_to_rec2100_linear, gdk_default_rec2020_linear_to_xyz);
CONCAT(gdk_default_xyz_to_rec2100_pq, gdk_default_xyz_to_rec2020_linear, gdk_default_rec2100_linear_to_rec2100_pq);
/* }}} */
static const
GdkColorStateClass GDK_DEFAULT_COLOR_STATE_CLASS = {
.free = NULL, /* crash here if this ever happens */
@@ -231,7 +560,8 @@ GdkDefaultColorState gdk_default_color_states[] = {
.name = "srgb",
.no_srgb = GDK_COLOR_STATE_SRGB_LINEAR,
.convert_to = {
[GDK_COLOR_STATE_ID_SRGB_LINEAR] = gdk_default_srgb_to_linear_srgb,
[GDK_COLOR_STATE_ID_SRGB_LINEAR] = gdk_default_srgb_to_srgb_linear,
[GDK_COLOR_STATE_ID_XYZ] = gdk_default_srgb_to_xyz,
},
},
[GDK_COLOR_STATE_ID_SRGB_LINEAR] = {
@@ -245,6 +575,105 @@ GdkDefaultColorState gdk_default_color_states[] = {
.no_srgb = NULL,
.convert_to = {
[GDK_COLOR_STATE_ID_SRGB] = gdk_default_srgb_linear_to_srgb,
[GDK_COLOR_STATE_ID_XYZ] = gdk_default_srgb_linear_to_xyz,
},
},
[GDK_COLOR_STATE_ID_XYZ] = {
.parent = {
.klass = &GDK_DEFAULT_COLOR_STATE_CLASS,
.ref_count = 0,
.depth = GDK_MEMORY_FLOAT16,
.rendering_color_state = GDK_COLOR_STATE_XYZ,
},
.name = "xyz",
.no_srgb = NULL,
.convert_to = {
[GDK_COLOR_STATE_ID_SRGB] = gdk_default_xyz_to_srgb,
[GDK_COLOR_STATE_ID_SRGB_LINEAR] = gdk_default_xyz_to_srgb_linear,
[GDK_COLOR_STATE_ID_OKLAB] = gdk_default_xyz_to_oklab,
[GDK_COLOR_STATE_ID_OKLCH] = gdk_default_xyz_to_oklch,
[GDK_COLOR_STATE_ID_REC2020] = gdk_default_xyz_to_rec2020,
[GDK_COLOR_STATE_ID_REC2020_LINEAR] = gdk_default_xyz_to_rec2020_linear,
[GDK_COLOR_STATE_ID_REC2100_PQ] = gdk_default_xyz_to_rec2100_pq,
[GDK_COLOR_STATE_ID_REC2100_LINEAR] = gdk_default_xyz_to_rec2020_linear,
},
},
[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_LINEAR,
},
.name = "oklab",
.no_srgb = NULL,
.convert_to = {
[GDK_COLOR_STATE_ID_XYZ] = gdk_default_oklab_to_xyz,
},
},
[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_LINEAR,
},
.name = "oklch",
.no_srgb = NULL,
.convert_to = {
[GDK_COLOR_STATE_ID_XYZ] = gdk_default_oklch_to_xyz,
},
},
[GDK_COLOR_STATE_ID_REC2020] = {
.parent = {
.klass = &GDK_DEFAULT_COLOR_STATE_CLASS,
.ref_count = 0,
.depth = GDK_MEMORY_FLOAT16,
.rendering_color_state = GDK_COLOR_STATE_REC2020_LINEAR,
},
.name = "rec2020",
.no_srgb = NULL,
.convert_to = {
[GDK_COLOR_STATE_ID_XYZ] = gdk_default_rec2020_to_xyz,
},
},
[GDK_COLOR_STATE_ID_REC2020_LINEAR] = {
.parent = {
.klass = &GDK_DEFAULT_COLOR_STATE_CLASS,
.ref_count = 0,
.depth = GDK_MEMORY_FLOAT16,
.rendering_color_state = GDK_COLOR_STATE_REC2020_LINEAR,
},
.name = "rec2020-linear",
.no_srgb = NULL,
.convert_to = {
[GDK_COLOR_STATE_ID_XYZ] = gdk_default_rec2020_linear_to_xyz,
},
},
[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_XYZ] = gdk_default_rec2100_pq_to_xyz,
},
},
[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_XYZ] = gdk_default_rec2020_linear_to_xyz,
},
},
};

View File

@@ -43,6 +43,27 @@ GdkColorState * gdk_color_state_get_srgb (void);
GDK_AVAILABLE_IN_4_16
GdkColorState * gdk_color_state_get_srgb_linear (void);
GDK_AVAILABLE_IN_4_16
GdkColorState * gdk_color_state_get_xyz (void);
GDK_AVAILABLE_IN_4_16
GdkColorState * gdk_color_state_get_oklab (void);
GDK_AVAILABLE_IN_4_16
GdkColorState * gdk_color_state_get_oklch (void);
GDK_AVAILABLE_IN_4_16
GdkColorState * gdk_color_state_get_rec2020 (void);
GDK_AVAILABLE_IN_4_16
GdkColorState * gdk_color_state_get_rec2020_linear (void);
GDK_AVAILABLE_IN_4_16
GdkColorState * gdk_color_state_get_rec2100_pq (void);
GDK_AVAILABLE_IN_4_16
GdkColorState * gdk_color_state_get_rec2100_linear (void);
GDK_AVAILABLE_IN_4_16
gboolean gdk_color_state_equal (GdkColorState *self,
GdkColorState *other);

View File

@@ -10,6 +10,13 @@ typedef enum
{
GDK_COLOR_STATE_ID_SRGB,
GDK_COLOR_STATE_ID_SRGB_LINEAR,
GDK_COLOR_STATE_ID_XYZ,
GDK_COLOR_STATE_ID_OKLAB,
GDK_COLOR_STATE_ID_OKLCH,
GDK_COLOR_STATE_ID_REC2020,
GDK_COLOR_STATE_ID_REC2020_LINEAR,
GDK_COLOR_STATE_ID_REC2100_PQ,
GDK_COLOR_STATE_ID_REC2100_LINEAR,
GDK_COLOR_STATE_N_IDS
} GdkColorStateId;
@@ -53,8 +60,15 @@ struct _GdkDefaultColorState
extern GdkDefaultColorState gdk_default_color_states[GDK_COLOR_STATE_N_IDS];
#define GDK_COLOR_STATE_SRGB ((GdkColorState *) &gdk_default_color_states[GDK_COLOR_STATE_ID_SRGB])
#define GDK_COLOR_STATE_SRGB_LINEAR ((GdkColorState *) &gdk_default_color_states[GDK_COLOR_STATE_ID_SRGB_LINEAR])
#define GDK_COLOR_STATE_SRGB ((GdkColorState *) &gdk_default_color_states[GDK_COLOR_STATE_ID_SRGB])
#define GDK_COLOR_STATE_SRGB_LINEAR ((GdkColorState *) &gdk_default_color_states[GDK_COLOR_STATE_ID_SRGB_LINEAR])
#define GDK_COLOR_STATE_XYZ ((GdkColorState *) &gdk_default_color_states[GDK_COLOR_STATE_ID_XYZ])
#define GDK_COLOR_STATE_OKLAB ((GdkColorState *) &gdk_default_color_states[GDK_COLOR_STATE_ID_OKLAB])
#define GDK_COLOR_STATE_OKLCH ((GdkColorState *) &gdk_default_color_states[GDK_COLOR_STATE_ID_OKLCH])
#define GDK_COLOR_STATE_REC2020 ((GdkColorState *) &gdk_default_color_states[GDK_COLOR_STATE_ID_REC2020])
#define GDK_COLOR_STATE_REC2020_LINEAR ((GdkColorState *) &gdk_default_color_states[GDK_COLOR_STATE_ID_REC2020_LINEAR])
#define GDK_COLOR_STATE_REC2100_PQ ((GdkColorState *) &gdk_default_color_states[GDK_COLOR_STATE_ID_REC2100_PQ])
#define GDK_COLOR_STATE_REC2100_LINEAR ((GdkColorState *) &gdk_default_color_states[GDK_COLOR_STATE_ID_REC2100_LINEAR])
#define GDK_IS_DEFAULT_COLOR_STATE(c) ((GdkDefaultColorState *) (c) >= &gdk_default_color_states[0] && \
(GdkDefaultColorState *) (c) < &gdk_default_color_states[GDK_COLOR_STATE_N_IDS])

View File

@@ -2105,6 +2105,7 @@ gdk_memory_convert_color_state (guchar *data,
{
const GdkMemoryFormatDescription *desc = &memory_formats[format];
GdkFloatColorConvert convert_func;
GdkFloatColorConvert convert_func2 = NULL;
float (*tmp)[4];
if (gdk_color_state_equal (src_cs, dest_cs))
@@ -2126,8 +2127,14 @@ gdk_memory_convert_color_state (guchar *data,
}
convert_func = gdk_color_state_get_convert_to (src_cs, dest_cs);
/* FIXME: add fallback that goes via generic colorstate */
g_assert (convert_func);
if (!convert_func)
{
convert_func = gdk_color_state_get_convert_to (src_cs, GDK_COLOR_STATE_XYZ);
convert_func2 = gdk_color_state_get_convert_to (GDK_COLOR_STATE_XYZ, dest_cs);
g_assert (convert_func && convert_func2);
}
tmp = g_malloc (sizeof (*tmp) * width);
@@ -2139,6 +2146,8 @@ gdk_memory_convert_color_state (guchar *data,
unpremultiply (tmp, width);
convert_func (src_cs, tmp, width);
if (convert_func2)
convert_func2 (GDK_COLOR_STATE_XYZ, tmp, width);
if (desc->alpha == GDK_MEMORY_ALPHA_PREMULTIPLIED)
premultiply (tmp, width);

View File

@@ -54,6 +54,13 @@
#define GDK_COLOR_STATE_ID_SRGB 0u
#define GDK_COLOR_STATE_ID_SRGB_LINEAR 1u
#define GDK_COLOR_STATE_ID_XYZ 2u
#define GDK_COLOR_STATE_ID_OKLAB 3u
#define GDK_COLOR_STATE_ID_OKLCH 4u
#define GDK_COLOR_STATE_ID_REC2020 5u
#define GDK_COLOR_STATE_ID_REC2020_LINEAR 6u
#define GDK_COLOR_STATE_ID_REC2100_PQ 7u
#define GDK_COLOR_STATE_ID_REC2100_LINEAR 8u
#define TOP 0u
#define RIGHT 1u

View File

@@ -5,6 +5,368 @@
#define HAS_VARIATION(var) ((GSK_VARIATION & var) == var)
#define SOURCE_COLOR_STATE ((GSK_VARIATION >> VARIATION_SOURCE_SHIFT) & VARIATION_COLOR_STATE_MASK)
#define TARGET_COLOR_STATE ((GSK_VARIATION >> VARIATION_TARGET_SHIFT) & VARIATION_COLOR_STATE_MASK)
float
srgb_eotf (float v)
{
if (v >= 0.04045)
return pow (((v + 0.055) / (1.0 + 0.055)), 2.4);
else
return v / 12.92;
}
float
srgb_oetf (float v)
{
if (v > 0.0031308)
return 1.055 * pow (v, 1.0 / 2.4) - 0.055;
else
return 12.92 * v;
}
vec4
srgb_to_srgb_linear (vec4 color)
{
return vec4 (srgb_eotf (color.r),
srgb_eotf (color.g),
srgb_eotf (color.b),
color.a);
}
vec4
srgb_linear_to_srgb (vec4 color)
{
return vec4 (srgb_oetf (color.r),
srgb_oetf (color.g),
srgb_oetf (color.b),
color.a);
}
vec4
srgb_linear_to_xyz (vec4 color)
{
mat3 m = mat3 (506752.0 / 1228815.0, 87881.0 / 245763.0, 12673.0 / 70218.0,
87098.0 / 409605.0, 175762.0 / 245763.0, 12673.0 / 175545.0,
7918.0 / 409605.0, 87881.0 / 737289.0, 1001167.0 / 1053270.0);
return vec4 (color.rgb * m, color.a);
}
vec4
xyz_to_srgb_linear (vec4 color)
{
mat3 m = mat3 ( 12831.0 / 3959.0, -329.0 / 214.0, -1974.0 / 3959.0,
-851781.0 / 878810.0, 1648619.0 / 878810.0, 36519.0 / 878810.0,
705.0 / 12673.0, -2585.0 / 12673.0, 705.0 / 667.0);
return vec4 (color.xyz * m, color.a);
}
#define M_PI 3.1415926535897932384626433832795
#define RAD_TO_DEG(x) ((x)*180.0/M_PI)
#define DEG_TO_RAD(x) ((x)*M_PI/180.0)
vec4
oklab_to_oklch (vec4 color)
{
return vec4 (color.x,
length (color.yz),
RAD_TO_DEG (atan (color.z, color.y)),
color.a);
}
float
normalize_hue (float h)
{
while (h < 0.0)
h += 360.0;
while (h > 360.0)
h -= 360.0;
return h;
}
vec4
oklch_to_oklab (vec4 color)
{
color.z = normalize_hue (color.z);
return vec4 (color.x,
color.y * cos (DEG_TO_RAD (color.z)),
color.y * sin (DEG_TO_RAD(color.z)),
color.a);
}
vec4
oklab_to_xyz (vec4 color)
{
mat3 m1 = mat3 (1.0, 0.3963377774, 0.2158037573,
1.0, -0.1055613458, -0.0638541728,
1.0, -0.0894841775, -1.2914855480);
vec3 lms = color.rgb * m1;
lms = vec3 (pow (lms.x, 3.0),
pow (lms.y, 3.0),
pow (lms.z, 3.0));
mat3 m2 = mat3 ( 4.0767416621, -3.3077115913, 0.2309699292,
-1.2684380046, 2.6097574011, -0.3413193965,
-0.0041960863, -0.7034186147, 1.7076147010);
mat3 m3 = mat3 (506752.0 / 1228815.0, 87881.0 / 245763.0, 12673.0 / 70218.0,
87098.0 / 409605.0, 175762.0 / 245763.0, 12673.0 / 175545.0,
7918.0 / 409605.0, 87881.0 / 737289.0, 1001167.0 / 1053270.0);
mat3 m4 = m2 * m3;
vec3 rgb = lms * m4;
return vec4 (rgb, color.a);
}
vec4
xyz_to_oklab (vec4 color)
{
mat3 m1 = mat3 ( 12831.0 / 3959.0, -329.0 / 214.0, -1974.0 / 3959.0,
-851781.0 / 878810.0, 1648619.0 / 878810.0, 36519.0 / 878810.0,
705.0 / 12673.0, -2585.0 / 12673.0, 705.0 / 667.0);
mat3 m2 = mat3 (0.4122214708, 0.5363325363, 0.0514459929,
0.2119034982, 0.6806995451, 0.1073969566,
0.0883024619, 0.2817188376, 0.6299787005);
mat3 m3 = m1 * m2;
vec3 lms = color.rgb * m3;
lms = vec3 (pow (lms.x, 1.0/3.0),
pow (lms.y, 1.0/3.0),
pow (lms.z, 1.0/3.0));
mat3 m4 = mat3 (0.2104542553, 0.7936177850, -0.0040720468,
1.9779984951, -2.4285922050, 0.4505937099,
0.0259040371, 0.7827717662, -0.8086757660);
vec3 lab = lms * m4;
return vec4 (lab, color.a);
}
float
rec2020_eotf (float v)
{
float alpha = 1.09929682680944;
float beta = 0.018053968510807;
float sign = v < 0.0 ? -1.0 : 1.0;
float vabs = abs (v);
if (vabs < beta * 4.5)
return v / 4.5;
else
return sign * pow ((vabs + alpha - 1.0) / alpha, 1.0 / 0.45);
}
float
rec2020_oetf (float v)
{
float alpha = 1.09929682680944;
float beta = 0.018053968510807;
float sign = v < 0.0 ? -1.0 : 1.0;
float vabs = abs (v);
if (vabs > beta)
return sign * (alpha * pow (vabs, 0.45) - (alpha - 1.0));
else
return 4.5 * v;
}
vec4
rec2020_to_rec2020_linear (vec4 color)
{
return vec4 (rec2020_eotf (color.r),
rec2020_eotf (color.g),
rec2020_eotf (color.b),
color.a);
}
vec4
rec2020_linear_to_rec2020 (vec4 color)
{
return vec4 (rec2020_oetf (color.r),
rec2020_oetf (color.g),
rec2020_oetf (color.b),
color.a);
}
vec4
rec2020_linear_to_xyz (vec4 color)
{
mat3 m = mat3 (63426534.0 / 99577255.0, 20160776.0 / 139408157.0, 47086771.0 / 278816314.0,
26158966.0 / 99577255.0, 472592308.0 / 697040785.0, 8267143.0 / 139408157.0,
0.0, 19567812.0 / 697040785.0, 295819943.0 / 278816314.0);
return vec4 (color.rgb * m, color.a);
}
vec4
xyz_to_rec2020_linear (vec4 color)
{
mat3 m = mat3 ( 30757411.0 / 17917100.0, -6372589.0 / 17917100.0, -4539589.0 / 17917100.0,
-19765991.0 / 29648200.0, 47925759.0 / 29648200.0, 467509.0 / 29648200.0,
792561.0 / 44930125.0, -1921689.0 / 44930125.0, 42328811.0 / 44930125.0);
return vec4 (color.xyz * m, color.z);
}
float
rec2100_pq_eotf (float v)
{
float ninv = 16384.0 / 2610.0;
float minv = 32.0 / 2523.0;
float c1 = 3424.0 / 4096.0;
float c2 = 2413.0 / 128.0;
float c3 = 2392.0 / 128.0;
float x = pow (max ((pow (v, minv) - c1), 0.0) / (c2 - (c3 * (pow (v, minv)))), ninv);
return x * 10000.0 / 203.0;
}
float
rec2100_pq_oetf (float v)
{
float x = v * 203.0 / 10000.0;
float n = 2610.0 / 16384.0;
float m = 2523.0 / 32.0;
float c1 = 3424.0 / 4096.0;
float c2 = 2413.0 / 128.0;
float c3 = 2392.0 / 128.0;
return pow (((c1 + (c2 * pow (x, n))) / (1.0 + (c3 * pow (x, n)))), m);
}
vec4
rec2100_pq_to_rec2100_linear (vec4 color)
{
return vec4 (rec2100_pq_eotf (color.r),
rec2100_pq_eotf (color.g),
rec2100_pq_eotf (color.b),
color.a);
}
vec4
rec2100_linear_to_rec2100_pq (vec4 color)
{
return vec4 (rec2100_pq_oetf (color.r),
rec2100_pq_oetf (color.g),
rec2100_pq_oetf (color.b),
color.a);
}
#define CONCAT(f, f1, f2) vec4 f(vec4 color) { return f2(f1(color)); }
CONCAT(srgb_to_xyz, srgb_to_srgb_linear, srgb_linear_to_xyz)
CONCAT(xyz_to_srgb, xyz_to_srgb_linear, srgb_linear_to_srgb)
CONCAT(oklch_to_xyz, oklch_to_oklab, oklab_to_xyz)
CONCAT(xyz_to_oklch, xyz_to_oklab, oklab_to_oklch)
CONCAT(rec2020_to_xyz, rec2020_to_rec2020_linear, rec2020_linear_to_xyz)
CONCAT(xyz_to_rec2020, xyz_to_rec2020_linear, rec2020_linear_to_rec2020)
CONCAT(rec2100_pq_to_xyz, rec2100_pq_to_rec2100_linear, rec2020_linear_to_xyz)
CONCAT(xyz_to_rec2100_pq, xyz_to_rec2020_linear, rec2100_linear_to_rec2100_pq)
#define PAIR(_from_cs, _to_cs) ((_from_cs) << 16 | (_to_cs))
bool
do_conversion (vec4 color,
uint from_cs,
uint to_cs,
out vec4 result)
{
switch (PAIR (from_cs, to_cs))
{
case PAIR (GDK_COLOR_STATE_ID_SRGB, GDK_COLOR_STATE_ID_SRGB_LINEAR):
result = srgb_to_srgb_linear (color);
break;
case PAIR (GDK_COLOR_STATE_ID_SRGB_LINEAR, GDK_COLOR_STATE_ID_SRGB):
result = srgb_linear_to_srgb (color);
break;
case PAIR (GDK_COLOR_STATE_ID_SRGB_LINEAR, GDK_COLOR_STATE_ID_XYZ):
result = srgb_linear_to_xyz (color);
break;
case PAIR (GDK_COLOR_STATE_ID_SRGB, GDK_COLOR_STATE_ID_XYZ):
result = srgb_to_xyz (color);
break;
case PAIR (GDK_COLOR_STATE_ID_OKLAB, GDK_COLOR_STATE_ID_XYZ):
result = oklab_to_xyz (color);
break;
case PAIR (GDK_COLOR_STATE_ID_OKLCH, GDK_COLOR_STATE_ID_XYZ):
result = oklch_to_xyz (color);
break;
case PAIR (GDK_COLOR_STATE_ID_REC2020, GDK_COLOR_STATE_ID_XYZ):
result = rec2020_to_xyz (color);
break;
case PAIR (GDK_COLOR_STATE_ID_REC2020_LINEAR, GDK_COLOR_STATE_ID_XYZ):
result = rec2020_linear_to_xyz (color);
break;
case PAIR (GDK_COLOR_STATE_ID_REC2100_PQ, GDK_COLOR_STATE_ID_XYZ):
result = rec2100_pq_to_xyz (color);
break;
case PAIR (GDK_COLOR_STATE_ID_REC2100_LINEAR, GDK_COLOR_STATE_ID_XYZ):
result = rec2020_linear_to_xyz (color);
break;
case PAIR (GDK_COLOR_STATE_ID_XYZ, GDK_COLOR_STATE_ID_SRGB_LINEAR):
result = xyz_to_srgb_linear (color);
break;
case PAIR (GDK_COLOR_STATE_ID_XYZ, GDK_COLOR_STATE_ID_SRGB):
result = xyz_to_srgb (color);
break;
case PAIR (GDK_COLOR_STATE_ID_XYZ, GDK_COLOR_STATE_ID_OKLAB):
result = xyz_to_oklab (color);
break;
case PAIR (GDK_COLOR_STATE_ID_XYZ, GDK_COLOR_STATE_ID_OKLCH):
result = xyz_to_oklch (color);
break;
case PAIR (GDK_COLOR_STATE_ID_XYZ, GDK_COLOR_STATE_ID_REC2020):
result = xyz_to_rec2020 (color);
break;
case PAIR (GDK_COLOR_STATE_ID_XYZ, GDK_COLOR_STATE_ID_REC2020_LINEAR):
result = xyz_to_rec2020_linear (color);
break;
case PAIR (GDK_COLOR_STATE_ID_XYZ, GDK_COLOR_STATE_ID_REC2100_PQ):
result = xyz_to_rec2100_pq (color);
break;
case PAIR (GDK_COLOR_STATE_ID_XYZ, GDK_COLOR_STATE_ID_REC2100_LINEAR):
result = xyz_to_rec2020_linear (color);
break;
default:
return false;
}
return true;
}
vec4
color_convert (vec4 color)
{
vec4 result;
if (SOURCE_COLOR_STATE == TARGET_COLOR_STATE)
return color;
if (!do_conversion (color, SOURCE_COLOR_STATE, TARGET_COLOR_STATE, result))
{
do_conversion (color, SOURCE_COLOR_STATE, GDK_COLOR_STATE_ID_XYZ, result);
do_conversion (result, GDK_COLOR_STATE_ID_XYZ, TARGET_COLOR_STATE, result);
}
return result;
}
PASS(0) vec2 _pos;
PASS_FLAT(1) Rect _rect;
PASS(2) vec2 _tex_coord;

View File

@@ -1,4 +1,7 @@
#include <gdk/gdk.h>
#include <gdk/gdkcolorstateprivate.h>
#include <gdk/gdkmemoryformatprivate.h>
#include <math.h>
static void
test_srgb (void)
@@ -14,12 +17,105 @@ test_srgb (void)
g_assert_false (gdk_color_state_equal (srgb, srgb_linear));
}
static float
image_distance (const guchar *data,
const guchar *data2,
gsize width,
gsize height,
gsize stride)
{
float dist = 0;
for (gsize i = 0; i < height; i++)
{
const float *p = (const float *) (data + i * stride);
const float *p2 = (const float *) (data2 + i * stride);
for (gsize j = 0; j < width; j++)
{
float dr, dg, db, da;
dr = p[4 * j + 0] - p2[4 * j + 0];
dg = p[4 * j + 1] - p2[4 * j + 1];
db = p[4 * j + 2] - p2[4 * j + 2];
da = p[4 * j + 3] - p2[4 * j + 3];
dist = MAX (dist, dr * dr + dg * dg + db * db + da * da);
}
}
return sqrt (dist);
}
static void
test_convert (gconstpointer testdata)
{
GdkColorState *cs = (GdkColorState *) testdata;
char *path;
GdkTexture *texture;
GdkTextureDownloader *downloader;
GError *error = NULL;
GBytes *bytes;
const guchar *data;
guchar *data2;
gsize width, height;
gsize size;
gsize stride;
path = g_test_build_filename (G_TEST_DIST, "image-data", "image.png", NULL);
texture = gdk_texture_new_from_filename (path, &error);
g_assert_no_error (error);
width = gdk_texture_get_width (texture);
height = gdk_texture_get_height (texture);
downloader = gdk_texture_downloader_new (texture);
gdk_texture_downloader_set_format (downloader, GDK_MEMORY_R32G32B32A32_FLOAT);
bytes = gdk_texture_downloader_download_bytes (downloader, &stride);
data = g_bytes_get_data (bytes, &size);
data2 = g_memdup2 (data, size);
gdk_memory_convert_color_state (data2,
stride,
GDK_MEMORY_R32G32B32A32_FLOAT,
gdk_texture_get_color_state (texture),
cs,
width,
height);
gdk_memory_convert_color_state (data2,
stride,
GDK_MEMORY_R32G32B32A32_FLOAT,
cs,
gdk_texture_get_color_state (texture),
width,
height);
g_assert_true (image_distance (data, data2, width, height, stride) < 0.001);
g_free (data2);
g_bytes_unref (bytes);
gdk_texture_downloader_free (downloader);
g_object_unref (texture);
g_free (path);
}
int
main (int argc, char *argv[])
{
(g_test_init) (&argc, &argv, NULL);
g_test_add_func ("/colorstate/srgb", test_srgb);
g_test_add_data_func ("/colorstate/convert/srgb<->srgb-linear", GDK_COLOR_STATE_SRGB_LINEAR, test_convert);
g_test_add_data_func ("/colorstate/convert/srgb<->xyz", GDK_COLOR_STATE_XYZ, test_convert);
g_test_add_data_func ("/colorstate/convert/srgb<->oklab", GDK_COLOR_STATE_OKLAB, test_convert);
g_test_add_data_func ("/colorstate/convert/srgb<->oklch", GDK_COLOR_STATE_OKLCH, test_convert);
g_test_add_data_func ("/colorstate/convert/srgb<->rec2020", GDK_COLOR_STATE_REC2020, test_convert);
g_test_add_data_func ("/colorstate/convert/srgb<->rec2020-linear", GDK_COLOR_STATE_REC2020_LINEAR, test_convert);
g_test_add_data_func ("/colorstate/convert/srgb<->rec2100-pq", GDK_COLOR_STATE_REC2100_PQ, test_convert);
g_test_add_data_func ("/colorstate/convert/srgb<->rec2100-linear", GDK_COLOR_STATE_REC2100_LINEAR, test_convert);
return g_test_run ();
}