From acce932e8dd701d0b32c7b54d315c7fb8ec2cac7 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Wed, 10 Jul 2024 16:23:53 -0400 Subject: [PATCH] Add rec2020 and rec2020-linear These are wide-gamut SDR colorstates and can be useful as compoisting color states. --- gdk/gdkcolorstate.c | 103 +++++++++++++++++++++++++++++ gdk/gdkcolorstate.h | 6 ++ gdk/gdkcolorstateprivate.h | 14 ++-- gsk/gpu/shaders/enums.glsl | 2 + gsk/gpu/shaders/gskgpuconvert.glsl | 81 +++++++++++++++++++++++ testsuite/gdk/colorstate.c | 14 ++-- 6 files changed, 208 insertions(+), 12 deletions(-) diff --git a/gdk/gdkcolorstate.c b/gdk/gdkcolorstate.c index 89779e77e0..b6f8656aad 100644 --- a/gdk/gdkcolorstate.c +++ b/gdk/gdkcolorstate.c @@ -142,6 +142,32 @@ 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` @@ -415,6 +441,53 @@ gdk_default_xyz_to_oklab (GdkColorState *self, } } +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) + #define CONCAT(name, f1, f2) \ static void \ name (GdkColorState *self, \ @@ -429,6 +502,8 @@ CONCAT(gdk_default_xyz_to_srgb, gdk_default_xyz_to_srgb_linear, gdk_default_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); /* }}} */ @@ -484,6 +559,8 @@ GdkDefaultColorState gdk_default_color_states[] = { [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_OKLAB] = { @@ -512,6 +589,32 @@ GdkDefaultColorState gdk_default_color_states[] = { [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, + }, + }, }; /* }}} */ diff --git a/gdk/gdkcolorstate.h b/gdk/gdkcolorstate.h index 1f9101801b..c2305d0458 100644 --- a/gdk/gdkcolorstate.h +++ b/gdk/gdkcolorstate.h @@ -52,6 +52,12 @@ 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 gboolean gdk_color_state_equal (GdkColorState *self, GdkColorState *other); diff --git a/gdk/gdkcolorstateprivate.h b/gdk/gdkcolorstateprivate.h index 52f90c8328..70cc0aaeaa 100644 --- a/gdk/gdkcolorstateprivate.h +++ b/gdk/gdkcolorstateprivate.h @@ -13,6 +13,8 @@ typedef enum 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_N_IDS } GdkColorStateId; @@ -56,11 +58,13 @@ 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_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_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_IS_DEFAULT_COLOR_STATE(c) ((GdkDefaultColorState *) (c) >= &gdk_default_color_states[0] && \ (GdkDefaultColorState *) (c) < &gdk_default_color_states[GDK_COLOR_STATE_N_IDS]) diff --git a/gsk/gpu/shaders/enums.glsl b/gsk/gpu/shaders/enums.glsl index 0b8d4850ac..ff34c1822a 100644 --- a/gsk/gpu/shaders/enums.glsl +++ b/gsk/gpu/shaders/enums.glsl @@ -57,6 +57,8 @@ #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 TOP 0u #define RIGHT 1u diff --git a/gsk/gpu/shaders/gskgpuconvert.glsl b/gsk/gpu/shaders/gskgpuconvert.glsl index c96b939790..d7d46543d9 100644 --- a/gsk/gpu/shaders/gskgpuconvert.glsl +++ b/gsk/gpu/shaders/gskgpuconvert.glsl @@ -153,12 +153,81 @@ xyz_to_oklab (vec4 color) 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); +} + #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) #define PAIR(_from_cs, _to_cs) ((_from_cs) << 16 | (_to_cs)) @@ -188,6 +257,12 @@ do_conversion (vec4 color, 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_XYZ, GDK_COLOR_STATE_ID_SRGB_LINEAR): result = xyz_to_srgb_linear (color); break; @@ -200,6 +275,12 @@ do_conversion (vec4 color, 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; default: return false; diff --git a/testsuite/gdk/colorstate.c b/testsuite/gdk/colorstate.c index caf8f4f2e3..f61fb43493 100644 --- a/testsuite/gdk/colorstate.c +++ b/testsuite/gdk/colorstate.c @@ -50,7 +50,7 @@ image_distance (const guchar *data, static void test_convert (gconstpointer testdata) { - GdkColorState *cs; + GdkColorState *cs = (GdkColorState *) testdata; char *path; GdkTexture *texture; GdkTextureDownloader *downloader; @@ -62,8 +62,6 @@ test_convert (gconstpointer testdata) gsize size; gsize stride; - cs = gdk_color_state_get_by_id ((GdkColorStateId) GPOINTER_TO_UINT (testdata)); - path = g_test_build_filename (G_TEST_DIST, "image-data", "image.png", NULL); texture = gdk_texture_new_from_filename (path, &error); @@ -110,10 +108,12 @@ 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", GUINT_TO_POINTER (GDK_COLOR_STATE_ID_SRGB_LINEAR), test_convert); - g_test_add_data_func ("/colorstate/convert/srgb<->xyz", GUINT_TO_POINTER (GDK_COLOR_STATE_ID_XYZ), test_convert); - g_test_add_data_func ("/colorstate/convert/srgb<->oklab", GUINT_TO_POINTER (GDK_COLOR_STATE_ID_OKLAB), test_convert); - g_test_add_data_func ("/colorstate/convert/srgb<->oklch", GUINT_TO_POINTER (GDK_COLOR_STATE_ID_OKLCH), test_convert); + 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); return g_test_run (); }