Merge branch 'oklab-support' into 'main'

oklab and oklch support

See merge request GNOME/gtk!7801
This commit is contained in:
Matthias Clasen
2024-10-12 18:44:03 +00:00
15 changed files with 847 additions and 106 deletions

View File

@@ -1,6 +1,7 @@
#include "config.h"
#include "gskgpuconicgradientopprivate.h"
#include "gskgpulineargradientopprivate.h"
#include "gskgpuframeprivate.h"
#include "gskgpuprintprivate.h"
@@ -97,4 +98,11 @@ gsk_gpu_conic_gradient_op (GskGpuFrame *frame,
instance->offsets0[1] = stops[1].offset;
gsk_gpu_color_to_float (&stops[0].color, ics, opacity, instance->color0);
instance->offsets0[0] = stops[0].offset;
gsk_adjust_hue (ics, hue_interp, instance->color0, instance->color1);
gsk_adjust_hue (ics, hue_interp, instance->color1, instance->color2);
gsk_adjust_hue (ics, hue_interp, instance->color2, instance->color3);
gsk_adjust_hue (ics, hue_interp, instance->color3, instance->color4);
gsk_adjust_hue (ics, hue_interp, instance->color4, instance->color5);
gsk_adjust_hue (ics, hue_interp, instance->color5, instance->color6);
}

View File

@@ -52,6 +52,78 @@ static const GskGpuShaderOpClass GSK_GPU_LINEAR_GRADIENT_OP_CLASS = {
gsk_gpu_lineargradient_setup_vao
};
void
gsk_adjust_hue (GdkColorState *ics,
GskHueInterpolation interp,
const float color1[4],
float color2[4])
{
float h1, h2;
float d;
if (!gdk_color_state_equal (ics, GDK_COLOR_STATE_OKLCH))
return;
h1 = color1[2];
h2 = color2[2];
d = h2 - h1;
while (d > 360)
{
h2 -= 360;
d = h2 - h1;
}
while (d < -360)
{
h2 += 360;
d = h2 - h1;
}
g_assert (fabsf (d) <= 360);
switch (interp)
{
case GSK_HUE_INTERPOLATION_SHORTER:
{
if (d > 180)
h2 -= 360;
else if (d < -180)
h2 += 360;
}
g_assert (fabsf (h2 - h1) <= 180);
break;
case GSK_HUE_INTERPOLATION_LONGER:
{
if (0 < d && d < 180)
h2 -= 360;
else if (-180 < d && d <= 0)
h2 += 360;
g_assert (fabsf (h2 - h1) >= 180);
}
break;
case GSK_HUE_INTERPOLATION_INCREASING:
if (h2 < h1)
h2 += 360;
d = h2 - h1;
g_assert (h1 <= h2);
break;
case GSK_HUE_INTERPOLATION_DECREASING:
if (h1 < h2)
h2 -= 360;
d = h2 - h1;
g_assert (h1 >= h2);
break;
default:
g_assert_not_reached ();
}
color2[2] = h2;
}
void
gsk_gpu_linear_gradient_op (GskGpuFrame *frame,
GskGpuShaderClip clip,
@@ -102,4 +174,11 @@ gsk_gpu_linear_gradient_op (GskGpuFrame *frame,
instance->offsets0[1] = stops[1].offset;
gsk_gpu_color_to_float (&stops[0].color, ics, opacity, instance->color0);
instance->offsets0[0] = stops[0].offset;
gsk_adjust_hue (ics, hue_interp, instance->color0, instance->color1);
gsk_adjust_hue (ics, hue_interp, instance->color1, instance->color2);
gsk_adjust_hue (ics, hue_interp, instance->color2, instance->color3);
gsk_adjust_hue (ics, hue_interp, instance->color3, instance->color4);
gsk_adjust_hue (ics, hue_interp, instance->color4, instance->color5);
gsk_adjust_hue (ics, hue_interp, instance->color5, instance->color6);
}

View File

@@ -23,5 +23,10 @@ void gsk_gpu_linear_gradient_op (GskGpuF
gsize n_stops);
void gsk_adjust_hue (GdkColorState *ics,
GskHueInterpolation interp,
const float color1[4],
float color2[4]);
G_END_DECLS

View File

@@ -1,6 +1,7 @@
#include "config.h"
#include "gskgpuradialgradientopprivate.h"
#include "gskgpulineargradientopprivate.h"
#include "gskgpuframeprivate.h"
#include "gskgpuprintprivate.h"
@@ -106,4 +107,11 @@ gsk_gpu_radial_gradient_op (GskGpuFrame *frame,
instance->offsets0[1] = stops[1].offset;
gsk_gpu_color_to_float (&stops[0].color, ics, opacity, instance->color0);
instance->offsets0[0] = stops[0].offset;
gsk_adjust_hue (ics, hue_interp, instance->color0, instance->color1);
gsk_adjust_hue (ics, hue_interp, instance->color1, instance->color2);
gsk_adjust_hue (ics, hue_interp, instance->color2, instance->color3);
gsk_adjust_hue (ics, hue_interp, instance->color3, instance->color4);
gsk_adjust_hue (ics, hue_interp, instance->color4, instance->color5);
gsk_adjust_hue (ics, hue_interp, instance->color5, instance->color6);
}

View File

@@ -111,6 +111,54 @@ const mat3 rec2020_from_srgb = mat3(
0.043303, 0.011360, 0.895380
);
const mat3 oklab_to_lms = mat3(
1.0, 1.0, 1.0,
0.3963377774, -0.1055613458, -0.0894841775,
0.2158037573, -0.0638541728, -1.2914855480
);
const mat3 lms_to_srgb = mat3(
4.0767416621, -1.2684380046, -0.0041960863,
-3.3077115913, 2.6097574011, -0.7034186147,
0.2309699292, -0.3413193965, 1.7076147010
);
const mat3 srgb_to_lms = mat3(
0.4122214708, 0.2119034982, 0.0883024619,
0.5363325363, 0.6806995451, 0.2817188376,
0.0514459929, 0.1073969566, 0.6299787005
);
const mat3 lms_to_oklab = mat3(
0.2104542553, 1.9779984951, 0.0259040371,
0.7936177850, -2.4285922050, 0.7827717662,
-0.0040720468, 0.4505937099, -0.8086757660
);
vec3
oklab_to_srgb_linear (vec3 color)
{
vec3 lms = oklab_to_lms * color;
lms = vec3 (pow (lms.r, 3.0),
pow (lms.g, 3.0),
pow (lms.b, 3.0));
return lms_to_srgb * lms;
}
vec3
srgb_linear_to_oklab (vec3 color)
{
vec3 lms = srgb_to_lms * color;
lms = vec3 (pow (lms.r, 1.0/3.0),
pow (lms.g, 1.0/3.0),
pow (lms.b, 1.0/3.0));
return lms_to_oklab * lms;
}
vec3
apply_eotf (vec3 color,
uint cs)
@@ -131,6 +179,9 @@ apply_eotf (vec3 color,
case GDK_COLOR_STATE_ID_REC2100_LINEAR:
return color;
case GDK_COLOR_STATE_ID_OKLAB:
return oklab_to_srgb_linear (color);
default:
return vec3(1.0, 0.0, 0.8);
}
@@ -156,6 +207,9 @@ apply_oetf (vec3 color,
case GDK_COLOR_STATE_ID_REC2100_LINEAR:
return color;
case GDK_COLOR_STATE_ID_OKLAB:
return srgb_linear_to_oklab (color);
default:
return vec3(0.0, 1.0, 0.8);
}
@@ -183,10 +237,71 @@ linear_color_space (uint cs)
case GDK_COLOR_STATE_ID_SRGB_LINEAR: return GDK_COLOR_STATE_ID_SRGB_LINEAR;
case GDK_COLOR_STATE_ID_REC2100_PQ: return GDK_COLOR_STATE_ID_REC2100_LINEAR;
case GDK_COLOR_STATE_ID_REC2100_LINEAR: return GDK_COLOR_STATE_ID_REC2100_LINEAR;
case GDK_COLOR_STATE_ID_OKLAB: return GDK_COLOR_STATE_ID_SRGB_LINEAR;
case GDK_COLOR_STATE_ID_OKLCH: return GDK_COLOR_STATE_ID_SRGB_LINEAR;
default: return 0u;
};
}
uint
rectangular_color_space (uint cs)
{
if (cs == GDK_COLOR_STATE_ID_OKLCH)
return GDK_COLOR_STATE_ID_OKLAB;
else
return cs;
}
#define M_PI 3.14159265358979323846
#define RAD_TO_DEG(x) ((x)*180.0/M_PI)
#define DEG_TO_RAD(x) ((x)*M_PI/180.0)
float
normalize_hue (float h)
{
while (h < 0.0)
h += 360.0;
while (h > 360.0)
h -= 360.0;
return h;
}
vec3
oklch_to_oklab (vec3 color)
{
color.z = normalize_hue (color.z);
return vec3 (color.x,
color.y * cos (DEG_TO_RAD (color.z)),
color.y * sin (DEG_TO_RAD (color.z)));
}
vec3
oklab_to_oklch (vec3 color)
{
return vec3 (color.x,
length (color.yz),
RAD_TO_DEG (atan (color.z, color.y)));
}
vec3
to_rect (vec3 color, uint from)
{
if (from == GDK_COLOR_STATE_ID_OKLCH)
return oklch_to_oklab (color);
else
return vec3(1, 0, 0.5);
}
vec3
to_polar (vec3 color, uint to)
{
if (to == GDK_COLOR_STATE_ID_OKLCH)
return oklab_to_oklch (color);
else
return vec3(1, 0, 0.5);
}
vec4
convert_color (vec4 color,
uint from,
@@ -199,17 +314,25 @@ convert_color (vec4 color,
if (from != to)
{
uint from_linear = linear_color_space (from);
uint to_linear = linear_color_space (to);
uint from_rectangular = rectangular_color_space (from);
uint to_rectangular = rectangular_color_space (to);
uint from_linear = linear_color_space (from_rectangular);
uint to_linear = linear_color_space (to_rectangular);
if (from_linear != from)
color.rgb = apply_eotf (color.rgb, from);
if (from_rectangular != from)
color.rgb = to_rect (color.rgb, from);
if (from_linear != from_rectangular)
color.rgb = apply_eotf (color.rgb, from_rectangular);
if (from_linear != to_linear)
color.rgb = convert_linear (color.rgb, from_linear, to_linear);
if (to_linear != to)
color.rgb = apply_oetf (color.rgb, to);
if (to_linear != to_rectangular)
color.rgb = apply_oetf (color.rgb, to_rectangular);
if (to_rectangular != to)
color.rgb = to_polar (color.rgb, to);
}
if (to_premul && (!from_premul || from != to))

View File

@@ -56,6 +56,8 @@
#define GDK_COLOR_STATE_ID_SRGB_LINEAR 1u
#define GDK_COLOR_STATE_ID_REC2100_PQ 2u
#define GDK_COLOR_STATE_ID_REC2100_LINEAR 3u
#define GDK_COLOR_STATE_ID_OKLAB 4u
#define GDK_COLOR_STATE_ID_OKLCH 5u
#define TOP 0u
#define RIGHT 1u

View File

@@ -358,6 +358,140 @@ gsk_linear_gradient_node_finalize (GskRenderNode *node)
parent_class->finalize (node);
}
static float
adjust_hue (GskHueInterpolation interp,
float h1,
float h2)
{
float d;
d = h2 - h1;
while (d > 360)
{
h2 -= 360;
d = h2 - h1;
}
while (d < -360)
{
h2 += 360;
d = h2 - h1;
}
g_assert (fabsf (d) <= 360);
switch (interp)
{
case GSK_HUE_INTERPOLATION_SHORTER:
{
if (d > 180)
h2 -= 360;
else if (d < -180)
h2 += 360;
}
g_assert (fabsf (h2 - h1) <= 180);
break;
case GSK_HUE_INTERPOLATION_LONGER:
{
if (0 < d && d < 180)
h2 -= 360;
else if (-180 < d && d <= 0)
h2 += 360;
g_assert (fabsf (h2 - h1) >= 180);
}
break;
case GSK_HUE_INTERPOLATION_INCREASING:
if (h2 < h1)
h2 += 360;
d = h2 - h1;
g_assert (h1 <= h2);
break;
case GSK_HUE_INTERPOLATION_DECREASING:
if (h1 < h2)
h2 -= 360;
d = h2 - h1;
g_assert (h1 >= h2);
break;
default:
g_assert_not_reached ();
}
return h2;
}
#define lerp(t,a,b) ((a) + (t) * ((b) - (a)))
typedef void (* ColorStopCallback) (float offset,
GdkColorState *ccs,
float values[4],
gpointer data);
static void
interpolate_color_stops (GdkColorState *ccs,
GdkColorState *interpolation,
GskHueInterpolation hue_interpolation,
float offset1,
GdkColor *color1,
float offset2,
GdkColor *color2,
ColorStopCallback callback,
gpointer data)
{
float values1[4];
float values2[4];
int n;
gdk_color_to_float (color1, interpolation, values1);
gdk_color_to_float (color2, interpolation, values2);
if (gdk_color_state_equal (interpolation, GDK_COLOR_STATE_OKLCH))
{
values2[2] = adjust_hue (hue_interpolation, values1[2], values2[2]);
/* don't make hue steps larger than 30° */
n = ceilf (fabsf (values2[2] - values1[2]) / 30);
}
else
{
/* just some steps */
n = 4;
}
for (int k = 1; k < n; k++)
{
float f = k / (float) n;
float values[4];
float offset;
GdkColor c;
values[0] = lerp (f, values1[0], values2[0]);
values[1] = lerp (f, values1[1], values2[1]);
values[2] = lerp (f, values1[2], values2[2]);
values[3] = lerp (f, values1[3], values2[3]);
offset = lerp (f, offset1, offset2);
gdk_color_init (&c, interpolation, values);
gdk_color_to_float (&c, ccs, values);
callback (offset, ccs, values, data);
gdk_color_finish (&c);
}
}
static void
add_color_stop_to_pattern (float offset,
GdkColorState *ccs,
float values[4],
gpointer data)
{
cairo_pattern_t *pattern = data;
cairo_pattern_add_color_stop_rgba (pattern, offset, values[0], values[1], values[2], values[3]);
}
static void
gsk_linear_gradient_node_draw (GskRenderNode *node,
cairo_t *cr,
@@ -378,18 +512,42 @@ gsk_linear_gradient_node_draw (GskRenderNode *node,
ccs,
0.0,
&self->stops[0].color);
for (i = 0; i < self->n_stops; i++)
{
if (!gdk_color_state_equal (self->interpolation, ccs))
interpolate_color_stops (ccs,
self->interpolation, self->hue_interpolation,
i > 0 ? self->stops[i-1].offset : 0,
i > 0 ? &self->stops[i-1].color : &self->stops[i].color,
self->stops[i].offset,
&self->stops[i].color,
add_color_stop_to_pattern,
pattern);
gdk_cairo_pattern_add_color_stop_color (pattern,
ccs,
self->stops[i].offset,
&self->stops[i].color);
}
if (self->stops[self->n_stops-1].offset < 1.0)
gdk_cairo_pattern_add_color_stop_color (pattern,
ccs,
1.0,
&self->stops[self->n_stops - 1].color);
{
if (!gdk_color_state_equal (self->interpolation, ccs))
interpolate_color_stops (ccs,
self->interpolation, self->hue_interpolation,
self->stops[self->n_stops-1].offset,
&self->stops[self->n_stops-1].color,
1,
&self->stops[self->n_stops-1].color,
add_color_stop_to_pattern,
pattern);
gdk_cairo_pattern_add_color_stop_color (pattern,
ccs,
1.0,
&self->stops[self->n_stops-1].color);
}
cairo_set_source (cr, pattern);
cairo_pattern_destroy (pattern);
@@ -925,18 +1083,42 @@ gsk_radial_gradient_node_draw (GskRenderNode *node,
ccs,
0.0,
&self->stops[0].color);
for (i = 0; i < self->n_stops; i++)
{
if (!gdk_color_state_equal (self->interpolation, ccs))
interpolate_color_stops (ccs,
self->interpolation, self->hue_interpolation,
i > 0 ? self->stops[i-1].offset : 0,
i > 0 ? &self->stops[i-1].color : &self->stops[i].color,
self->stops[i].offset,
&self->stops[i].color,
add_color_stop_to_pattern,
pattern);
gdk_cairo_pattern_add_color_stop_color (pattern,
ccs,
self->stops[i].offset,
&self->stops[i].color);
}
if (self->stops[self->n_stops-1].offset < 1.0)
gdk_cairo_pattern_add_color_stop_color (pattern,
ccs,
1.0,
&self->stops[self->n_stops-1].color);
{
if (!gdk_color_state_equal (self->interpolation, ccs))
interpolate_color_stops (ccs,
self->interpolation, self->hue_interpolation,
self->stops[self->n_stops-1].offset,
&self->stops[self->n_stops-1].color,
1,
&self->stops[self->n_stops-1].color,
add_color_stop_to_pattern,
pattern);
gdk_cairo_pattern_add_color_stop_color (pattern,
ccs,
1.0,
&self->stops[self->n_stops-1].color);
}
gdk_cairo_rect (cr, &node->bounds);
cairo_translate (cr, self->center.x, self->center.y);
@@ -1623,6 +1805,29 @@ gdk_rgba_color_interpolate (GdkRGBA *dest,
}
}
static void
add_color_stop_to_array (float offset,
GdkColorState *ccs,
float values[4],
gpointer data)
{
GArray *stops = data;
GskColorStop2 stop;
stop.offset = offset;
gdk_color_init (&stop.color, ccs, values);
g_array_append_val (stops, stop);
}
static void
clear_stop (gpointer data)
{
GskColorStop2 *stop = data;
gdk_color_finish (&stop->color);
}
static void
gsk_conic_gradient_node_draw (GskRenderNode *node,
cairo_t *cr,
@@ -1633,6 +1838,7 @@ gsk_conic_gradient_node_draw (GskRenderNode *node,
graphene_point_t corner;
float radius;
gsize i;
GArray *stops;
pattern = cairo_pattern_create_mesh ();
graphene_rect_get_top_right (&node->bounds, &corner);
@@ -1644,10 +1850,41 @@ gsk_conic_gradient_node_draw (GskRenderNode *node,
graphene_rect_get_top_left (&node->bounds, &corner);
radius = MAX (radius, graphene_point_distance (&self->center, &corner, NULL, NULL));
for (i = 0; i <= self->n_stops; i++)
stops = g_array_new (FALSE, TRUE, sizeof (GskColorStop2));
g_array_set_clear_func (stops, clear_stop);
if (gdk_color_state_equal (self->interpolation, ccs))
{
GskColorStop2 *stop1 = &self->stops[MAX (i, 1) - 1];
GskColorStop2 *stop2 = &self->stops[MIN (i, self->n_stops - 1)];
for (i = 0; i < self->n_stops; i++)
{
GskColorStop2 *stop = &self->stops[i];
g_array_append_val (stops, *stop);
/* take a ref, since clear_stop removes one */
gdk_color_state_ref (stop->color.color_state);
}
}
else
{
g_array_append_val (stops, self->stops[0]);
for (i = 1; i < self->n_stops; i++)
{
interpolate_color_stops (ccs,
self->interpolation, self->hue_interpolation,
self->stops[i-1].offset, &self->stops[i-1].color,
self->stops[i].offset, &self->stops[i].color,
add_color_stop_to_array,
stops);
g_array_append_val (stops, self->stops[i]);
/* take a ref, since clear_stop removes one */
gdk_color_state_ref (self->stops[i].color.color_state);
}
}
for (i = 0; i <= stops->len; i++)
{
GskColorStop2 *stop1 = &g_array_index (stops, GskColorStop2, MAX (i, 1) - 1);
GskColorStop2 *stop2 = &g_array_index (stops, GskColorStop2, MIN (i, stops->len - 1));
double offset1 = i > 0 ? stop1->offset : 0;
double offset2 = i < self->n_stops ? stop2->offset : 1;
double start_angle, end_angle;
@@ -1683,6 +1920,8 @@ gsk_conic_gradient_node_draw (GskRenderNode *node,
}
}
g_array_unref (stops);
cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD);
gdk_cairo_rect (cr, &node->bounds);

View File

@@ -628,6 +628,10 @@ parse_color_state (GtkCssParser *parser,
cs = gdk_color_state_get_rec2100_pq ();
else if (gtk_css_parser_try_ident (parser, "rec2100-linear"))
cs = gdk_color_state_get_rec2100_linear ();
else if (gtk_css_parser_try_ident (parser, "oklab"))
cs = gdk_color_state_get_oklab ();
else if (gtk_css_parser_try_ident (parser, "oklch"))
cs = gdk_color_state_get_oklch ();
else if (gtk_css_token_is (gtk_css_parser_get_token (parser), GTK_CSS_TOKEN_STRING))
{
char *name = gtk_css_parser_consume_string (parser);