summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeandro Ribeiro <leandro.ribeiro@collabora.com>2024-03-18 11:14:04 -0300
committerPekka Paalanen <pq@iki.fi>2024-03-26 11:23:26 +0000
commit9002667aa00e940e901e6233fa24728f46ce5c37 (patch)
treef46ef0aad45fc040214f5ecafe01cfa686a7dee5
parentd9e2eca13ff899cfdb3027e15eda0ecbc0c1320e (diff)
color: add support to parametric curves in weston_color_curve
Until now, all the curves would be represented with 3x1D LUT's. Now we support LINPOW and POWLIN curves (arbitrary names that we've picked). We can use these curves to represent LittleCMS curves type 1, 4 and their inverses -1, -4. The reason why we want that is because we gain precision using the parametric curves (compared to the LUT's); Surprisingly we had to increase the tolerance of the sRGB->adobeRGB MAT test. Our analysis is that the inverse EOTF power-law curve with exponent 1.0 / 2.2 amplifies errors more than the LUT, specially for input (optical) values closer to zero. That makes sense, because this curve is more sensible to input values closer to zero (i.e. little input variation results in lots of output variation). And this model makes sense, as humans are more capable of perceiving changes of light intensity in the dark. But the downside of all that is that for input values closer to zero, a little bit of noise increases significantly the error. Signed-off-by: Leandro Ribeiro <leandro.ribeiro@collabora.com>
-rw-r--r--libweston/color-lcms/color-lcms.h14
-rw-r--r--libweston/color-lcms/color-transform.c334
-rw-r--r--libweston/color.c4
-rw-r--r--libweston/color.h56
-rw-r--r--libweston/renderer-gl/fragment.glsl142
-rw-r--r--libweston/renderer-gl/gl-renderer-internal.h16
-rw-r--r--libweston/renderer-gl/gl-shader-config-color-transformation.c69
-rw-r--r--libweston/renderer-gl/gl-shaders.c41
-rw-r--r--tests/color-icc-output-test.c2
9 files changed, 668 insertions, 10 deletions
diff --git a/libweston/color-lcms/color-lcms.h b/libweston/color-lcms/color-lcms.h
index 1726553c..d99b7c77 100644
--- a/libweston/color-lcms/color-lcms.h
+++ b/libweston/color-lcms/color-lcms.h
@@ -176,12 +176,14 @@ struct cmlcms_color_transform {
struct cmlcms_color_transform_search_param search_key;
- /*
- * Cached data in case weston_color_transform needs them.
- * Pre-curve and post-curve refer to the weston_color_transform
- * pipeline elements and have no semantic meaning. They both are a
- * result of optimizing an arbitrary LittleCMS pipeline, not
- * e.g. EOTF or VCGT per se.
+ /**
+ * Cached data used when we can't translate the curves into parametric
+ * ones that we implement in the renderer. So when we need to fallback
+ * to LUT's, we use this data to compute them.
+ *
+ * These curves are a result of optimizing an arbitrary LittleCMS
+ * pipeline, so they have no semantic meaning (that means that they are
+ * not e.g. an EOTF).
*/
cmsToneCurve *pre_curve[3];
cmsToneCurve *post_curve[3];
diff --git a/libweston/color-lcms/color-transform.c b/libweston/color-lcms/color-transform.c
index 318cd31e..45e6c3b7 100644
--- a/libweston/color-lcms/color-transform.c
+++ b/libweston/color-lcms/color-transform.c
@@ -464,12 +464,340 @@ merge_curvesets(cmsPipeline **lut, cmsContext context_id)
return modified;
}
+static bool
+linpow_from_type_1(struct weston_compositor *compositor,
+ struct weston_color_curve *curve,
+ const float type_1_params[3][10], bool clamped_input)
+{
+ struct weston_color_curve_parametric *parametric = &curve->u.parametric;
+ unsigned int i;
+
+ curve->type = WESTON_COLOR_CURVE_TYPE_LINPOW;
+
+ parametric->clamped_input = clamped_input;
+
+ /* LittleCMS type 1 is the pure power-law curve, which is a special case
+ * of LINPOW.
+ *
+ * LINPOW is defined as:
+ *
+ * y = (a * x + b) ^ g | x >= d
+ * y = c * x | 0 <= x < d
+ *
+ * So for a = 1, b = 0, c = 1 and d = 0, we have:
+ *
+ * y = x ^ g | x >= 0
+ *
+ * As the pure power-law is only defined for values x >= 0 (because
+ * negative values raised to fractional exponents results in complex
+ * numbers), this is exactly the pure power-law curve.
+ */
+ for (i = 0; i < ARRAY_LENGTH(parametric->params); i++) {
+ parametric->params[i][0] = type_1_params[i][0]; /* g */
+ parametric->params[i][1] = 1.0f; /* a */
+ parametric->params[i][2] = 0.0f; /* b */
+ parametric->params[i][3] = 1.0f; /* c */
+ parametric->params[i][4] = 0.0f; /* d */
+ }
+
+ return true;
+}
+
+static bool
+linpow_from_type_1_inverse(struct weston_compositor *compositor,
+ struct weston_color_curve *curve,
+ const float type_1_params[3][10], bool clamped_input)
+{
+ struct weston_color_manager_lcms *cm = to_cmlcms(compositor->color_manager);
+ struct weston_color_curve_parametric *parametric = &curve->u.parametric;
+ float g;
+ const char *err_msg;
+ unsigned int i;
+
+ curve->type = WESTON_COLOR_CURVE_TYPE_LINPOW;
+
+ parametric->clamped_input = clamped_input;
+
+ /* LittleCMS type -1 (inverse of type 1) is the inverse of the pure
+ * power-law curve, which is a special case of LINPOW.
+ *
+ * The type 1 is defined as:
+ *
+ * y = x ^ g | x >= 0
+ *
+ * Computing its inverse, we have:
+ *
+ * y = x ^ (1 / g) | x >= 0
+ *
+ * LINPOW is defined as:
+ *
+ * y = (a * x + b) ^ g | x >= d
+ * y = c * x | 0 <= x < d
+ *
+ * So for a = 1, b = 0, c = 1 and d = 0, we have:
+ *
+ * y = x ^ g | x >= 0
+ *
+ * If we take the param g from type -1 and invert it, we can fit type -1
+ * into the curve above.
+ */
+ for (i = 0; i < ARRAY_LENGTH(parametric->params); i++) {
+ g = type_1_params[i][0];
+
+ if (g == 0.0f) {
+ err_msg = "WARNING: xform has a LittleCMS type -1 curve " \
+ "(inverse of pure power-law) with exponent 1 " \
+ "divided by 0, which is invalid";
+ goto err;
+ }
+
+ parametric->params[i][0] = 1.0f / g;
+ parametric->params[i][1] = 1.0f; /* a */
+ parametric->params[i][2] = 0.0f; /* b */
+ parametric->params[i][3] = 1.0f; /* c */
+ parametric->params[i][4] = 0.0f; /* d */
+ }
+
+ return true;
+
+err:
+ weston_log_scope_printf(cm->transforms_scope, "%s\n", err_msg);
+ return false;
+}
+
+static bool
+linpow_from_type_4(struct weston_compositor *compositor,
+ struct weston_color_curve *curve,
+ const float type_4_params[3][10], bool clamped_input)
+{
+ struct weston_color_manager_lcms *cm = to_cmlcms(compositor->color_manager);
+ struct weston_color_curve_parametric *parametric = &curve->u.parametric;
+ float g, a, b, c, d;
+ const char *err_msg;
+ unsigned int i;
+
+ curve->type = WESTON_COLOR_CURVE_TYPE_LINPOW;
+
+ parametric->clamped_input = clamped_input;
+
+ /* LittleCMS type 4 is almost exactly the same as LINPOW. So simply copy
+ * the params. No need to adjust anything.
+ *
+ * The only difference is that type 4 evaluates negative input values as
+ * is, and LINPOW handles negative input values using mirroring (i.e.
+ * for LINPOW being f(x) we'll compute -f(-x)).
+ *
+ * LINPOW is defined as:
+ *
+ * y = (a * x + b) ^ g | x >= d
+ * y = c * x | 0 <= x < d
+ */
+ for (i = 0; i < ARRAY_LENGTH(parametric->params); i++) {
+ g = type_4_params[i][0];
+ a = type_4_params[i][1];
+ b = type_4_params[i][2];
+ c = type_4_params[i][3];
+ d = type_4_params[i][4];
+
+ if (a < 0.0f) {
+ err_msg = "WARNING: xform has a LittleCMS type 4 curve " \
+ "with a < 0, which is unexpected";
+ goto err;
+ }
+
+ if (d < 0.0f) {
+ err_msg = "WARNING: xform has a LittleCMS type 4 curve " \
+ "with d < 0, which is unexpected";
+ goto err;
+ }
+
+ if (a * d + b < 0) {
+ err_msg = "WARNING: xform has a LittleCMS type 4 curve " \
+ "with a * d + b < 0, which is invalid";
+ goto err;
+ }
+
+ parametric->params[i][0] = g;
+ parametric->params[i][1] = a;
+ parametric->params[i][2] = b;
+ parametric->params[i][3] = c;
+ parametric->params[i][4] = d;
+ }
+
+ return true;
+
+err:
+ weston_log_scope_printf(cm->transforms_scope, "%s\n", err_msg);
+ return false;
+}
+
+static bool
+powlin_from_type_4_inverse(struct weston_compositor *compositor,
+ struct weston_color_curve *curve,
+ const float type_4_params[3][10], bool clamped_input)
+{
+ struct weston_color_manager_lcms *cm = to_cmlcms(compositor->color_manager);
+ struct weston_color_curve_parametric *parametric = &curve->u.parametric;
+ float g, a, b, c, d;
+ const char *err_msg;
+ unsigned int i;
+
+ curve->type = WESTON_COLOR_CURVE_TYPE_POWLIN;
+
+ parametric->clamped_input = clamped_input;
+
+ /* LittleCMS type -4 (inverse of type 4) fits into POWLIN. We need to
+ * adjust the params that LittleCMS gives us, like below. Do not forget
+ * that LittleCMS gives the params of the type 4 curve whose inverse
+ * is the one it wants to represent.
+ *
+ * Also, type -4 evaluates negative input values as is, and POWLIN
+ * handles negative input values using mirroring (i.e. for POWLIN being
+ * f(x) we'll compute -f(-x)). We do that to avoid negative values being
+ * raised to fractional exponents, what would result in complex numbers.
+ *
+ * The type 4 is defined as:
+ *
+ * y = (a * x + b) ^ g | x >= d
+ * y = c * x | else
+ *
+ * Computing its inverse, we have:
+ *
+ * y = ((x ^ (1 / g)) / a) - (b / a) | x >= c * d or (a * d + b) ^ g
+ * y = x / c | else
+ *
+ * POWLIN is defined as:
+ *
+ * y = (a * (x ^ g)) + b | x >= d
+ * y = c * x | 0 <= x < d
+ *
+ * So we need to take the params from LittleCMS and adjust:
+ *
+ * g ← 1 / g
+ * a ← 1 / a
+ * b ← -b / a
+ * c ← 1 / c
+ * d ← c * d
+ *
+ * Also, notice that c * d should be equal to (a * d + b) ^ g. But
+ * because of precision problems or a deliberate discontinuity in the
+ * function, that may not be true. So we may have a range of input
+ * values for POWLIN such that c * d <= x <= (a * d + b) ^ g. For these
+ * values, when evaluating POWLIN we need to decide with what segment
+ * we're going to evaluate the input. For the majority of POWLIN color
+ * curves created from type -4 we are expecting c * d ≈ (a * d + b) ^ g,
+ * so the different output produced by the two discontinuous segments
+ * would be so close that this wouldn't matter. But mathematically
+ * there's nothing that guarantees that the two discontinuous segments
+ * are close, and in this case the outputs would vary significantly.
+ * There's nothing we can do regarding that, so we'll arbitrarily choose
+ * one of the segments to compute the output.
+ */
+ for (i = 0; i < ARRAY_LENGTH(parametric->params); i++) {
+ g = type_4_params[i][0];
+ a = type_4_params[i][1];
+ b = type_4_params[i][2];
+ c = type_4_params[i][3];
+ d = type_4_params[i][4];
+
+ if (g == 0.0f) {
+ err_msg = "WARNING: xform has a LittleCMS type -4 curve " \
+ "but the param g of the original type 4 curve " \
+ "is zero, so the inverse is invalid";
+ goto err;
+ }
+
+ if (a == 0.0f) {
+ err_msg = "WARNING: xform has a LittleCMS type -4 curve " \
+ "but the param a of the original type 4 curve " \
+ "is zero, so the inverse is invalid";
+ goto err;
+ }
+
+ if (c == 0.0f) {
+ err_msg = "WARNING: xform has a LittleCMS type -4 curve " \
+ "but the param c of the original type 4 curve " \
+ "is zero, so the inverse is invalid";
+ goto err;
+ }
+
+ parametric->params[i][0] = 1.0f / g;
+ parametric->params[i][1] = 1.0f / a;
+ parametric->params[i][2] = -b / a;
+ parametric->params[i][3] = 1.0f / c;
+ parametric->params[i][4] = c * d;
+ }
+
+ return true;
+
+err:
+ weston_log_scope_printf(cm->transforms_scope, "%s\n", err_msg);
+ return false;
+}
+
enum color_transform_step {
PRE_CURVE,
POST_CURVE,
};
static bool
+translate_curve_element_parametric(struct cmlcms_color_transform *xform,
+ _cmsStageToneCurvesData *trc_data,
+ enum color_transform_step step)
+{
+ struct weston_compositor *compositor = xform->base.cm->compositor;
+ struct weston_color_curve *curve;
+ cmsInt32Number type;
+ float lcms_curveset_params[3][10];
+ bool clamped_input;
+ bool ret;
+
+ switch(step) {
+ case PRE_CURVE:
+ curve = &xform->base.pre_curve;
+ break;
+ case POST_CURVE:
+ curve = &xform->base.post_curve;
+ break;
+ default:
+ weston_assert_not_reached(compositor,
+ "curve should be a pre or post curve");
+ }
+
+ /* The curveset may not be a parametric one, in such case we have a
+ * fallback path. But if it is a parametric curve, we get the params for
+ * each color channel and also the parametric curve type (defined by
+ * LittleCMS). */
+ if (!get_parametric_curveset_params(compositor, trc_data, &type,
+ lcms_curveset_params, &clamped_input))
+ return false;
+
+ switch (type) {
+ case 1:
+ ret = linpow_from_type_1(compositor, curve,
+ lcms_curveset_params, clamped_input);
+ break;
+ case -1:
+ ret = linpow_from_type_1_inverse(compositor, curve,
+ lcms_curveset_params, clamped_input);
+ break;
+ case 4:
+ ret = linpow_from_type_4(compositor, curve,
+ lcms_curveset_params, clamped_input);
+ break;
+ case -4:
+ ret = powlin_from_type_4_inverse(compositor, curve,
+ lcms_curveset_params, clamped_input);
+ break;
+ default:
+ /* We don't implement the curve. */
+ ret = false;
+ }
+
+ return ret;
+}
+
+static bool
translate_curve_element_LUT(struct cmlcms_color_transform *xform,
_cmsStageToneCurvesData *trc_data,
enum color_transform_step step)
@@ -521,6 +849,12 @@ translate_curve_element(struct cmlcms_color_transform *xform,
if (trc_data->nCurves != 3)
return false;
+ /* First try to translate the curve to a parametric one. */
+ if (translate_curve_element_parametric(xform, trc_data, step))
+ return true;
+
+ /* Curve does not fit any of the parametric curves that we implement, so
+ * fallback to LUT. */
return translate_curve_element_LUT(xform, trc_data, step);
}
diff --git a/libweston/color.c b/libweston/color.c
index df403333..883a7326 100644
--- a/libweston/color.c
+++ b/libweston/color.c
@@ -182,6 +182,10 @@ curve_type_to_str(enum weston_color_curve_type curve_type)
return "identity";
case WESTON_COLOR_CURVE_TYPE_LUT_3x1D:
return "3x1D LUT";
+ case WESTON_COLOR_CURVE_TYPE_LINPOW:
+ return "linpow";
+ case WESTON_COLOR_CURVE_TYPE_POWLIN:
+ return "powlin";
}
return "???";
}
diff --git a/libweston/color.h b/libweston/color.h
index e0a40f5e..854f84fd 100644
--- a/libweston/color.h
+++ b/libweston/color.h
@@ -52,6 +52,50 @@ enum weston_color_curve_type {
/** Three-channel, one-dimensional look-up table */
WESTON_COLOR_CURVE_TYPE_LUT_3x1D,
+
+ /** Transfer function named LINPOW
+ *
+ * y = (a * x + b) ^ g | x >= d
+ * y = c * x | 0 <= x < d
+ *
+ * We gave it the name LINPOW because the first operation with the input
+ * x is a linear one, and then the result is raised to g.
+ *
+ * As all parametric curves, this one should be represented using struct
+ * weston_color_curve_parametric. For each color channel RGB we may have
+ * different params, see weston_color_curve_parametric::params.
+ *
+ * For LINPOW, the params g, a, b, c, and d are respectively
+ * params[channel][0], ... , params[channel][4].
+ *
+ * The input for all color channels may be clamped to [0.0, 1.0]. In
+ * such case, weston_color_curve_parametric::clamped_input is true.
+ * If the input is not clamped and LINPOW needs to evaluate a negative
+ * input value, it uses mirroring (i.e. -f(-x)).
+ */
+ WESTON_COLOR_CURVE_TYPE_LINPOW,
+
+ /** Transfer function named POWLIN
+ *
+ * y = (a * (x ^ g)) + b | x >= d
+ * y = c * x | 0 <= x < d
+ *
+ * We gave it the name POWLIN because the first operation with the input
+ * x is an exponential one, and then the result is multiplied by a.
+ *
+ * As all parametric curves, this one should be represented using struct
+ * weston_color_curve_parametric. For each color channel RGB we may have
+ * different params, see weston_color_curve_parametric::params.
+ *
+ * For POWLIN, the params g, a, b, c, and d are respectively
+ * params[channel][0], ... , params[channel][4].
+ *
+ * The input for all color channels may be clamped to [0.0, 1.0]. In
+ * such case, weston_color_curve_parametric::clamped_input is true.
+ * If the input is not clamped and POWLIN needs to evaluate a negative
+ * input value, it uses mirroring (i.e. -f(-x)).
+ */
+ WESTON_COLOR_CURVE_TYPE_POWLIN,
};
/** LUT_3x1D parameters */
@@ -80,6 +124,17 @@ struct weston_color_curve_lut_3x1d {
unsigned optimal_len;
};
+/** Parametric color curve parameters */
+struct weston_color_curve_parametric {
+ /* For each color channel we may have different curves. For each of
+ * them, we can have up to 10 params, depending on the curve type. The
+ * channels are in RGB order. */
+ float params[3][10];
+
+ /* The input of the curve should be clamped from 0.0 to 1.0? */
+ bool clamped_input;
+};
+
/**
* A scalar function for color encoding and decoding
*
@@ -99,6 +154,7 @@ struct weston_color_curve {
union {
/* identity: no parameters */
struct weston_color_curve_lut_3x1d lut_3x1d;
+ struct weston_color_curve_parametric parametric;
} u;
};
diff --git a/libweston/renderer-gl/fragment.glsl b/libweston/renderer-gl/fragment.glsl
index 1ea95b2c..b19d0427 100644
--- a/libweston/renderer-gl/fragment.glsl
+++ b/libweston/renderer-gl/fragment.glsl
@@ -46,6 +46,8 @@
/* enum gl_shader_color_curve */
#define SHADER_COLOR_CURVE_IDENTITY 0
#define SHADER_COLOR_CURVE_LUT_3x1D 1
+#define SHADER_COLOR_CURVE_LINPOW 2
+#define SHADER_COLOR_CURVE_POWLIN 3
/* enum gl_shader_color_mapping */
#define SHADER_COLOR_MAPPING_IDENTITY 0
@@ -124,10 +126,16 @@ uniform sampler2D tex1;
uniform sampler2D tex2;
uniform float view_alpha;
uniform vec4 unicolor;
+
uniform HIGHPRECISION sampler2D color_pre_curve_lut_2d;
uniform HIGHPRECISION vec2 color_pre_curve_lut_scale_offset;
+uniform HIGHPRECISION float color_pre_curve_params[30];
+uniform bool color_pre_curve_clamped_input;
+
uniform HIGHPRECISION sampler2D color_post_curve_lut_2d;
uniform HIGHPRECISION vec2 color_post_curve_lut_scale_offset;
+uniform HIGHPRECISION float color_post_curve_params[30];
+uniform bool color_post_curve_clamped_input;
#if DEF_COLOR_MAPPING == SHADER_COLOR_MAPPING_3DLUT
uniform HIGHPRECISION sampler3D color_mapping_lut_3d;
@@ -212,6 +220,120 @@ sample_color_pre_curve_lut_2d(float x, compile_const int row)
vec2(tx, (float(row) + 0.5) / 4.0)).x;
}
+float
+linpow(float x, float g, float a, float b, float c, float d)
+{
+ /* See WESTON_COLOR_CURVE_TYPE_LINPOW for details about LINPOW. */
+
+ if (x >= d)
+ return pow((a * x) + b, g);
+
+ return c * x;
+}
+
+float
+powlin(float x, float g, float a, float b, float c, float d)
+{
+ /* See WESTON_COLOR_CURVE_TYPE_POWLIN for details about POWLIN. */
+
+ if (x >= d)
+ return a * pow(x, g) + b;
+
+ return c * x;
+}
+
+float
+sample_color_pre_curve_linpow(float x, compile_const int color_channel)
+{
+ float g, a, b, c, d;
+
+ /* For each color channel we have 10 parameters. The params are
+ * linearized in an array of size 30, in RGB order. */
+ g = color_pre_curve_params[0 + (color_channel * 10)];
+ a = color_pre_curve_params[1 + (color_channel * 10)];
+ b = color_pre_curve_params[2 + (color_channel * 10)];
+ c = color_pre_curve_params[3 + (color_channel * 10)];
+ d = color_pre_curve_params[4 + (color_channel * 10)];
+
+ if (color_pre_curve_clamped_input)
+ x = clamp(x, 0.0, 1.0);
+
+ /* We use mirroring for negative input values. */
+ if (x < 0.0)
+ return -linpow(-x, g, a, b, c, d);
+
+ return linpow(x, g, a, b, c, d);
+}
+
+float
+sample_color_pre_curve_powlin(float x, compile_const int color_channel)
+{
+ float g, a, b, c, d;
+
+ /* For each color channel we have 10 parameters. The params are
+ * linearized in an array of size 30, in RGB order. */
+ g = color_pre_curve_params[0 + (color_channel * 10)];
+ a = color_pre_curve_params[1 + (color_channel * 10)];
+ b = color_pre_curve_params[2 + (color_channel * 10)];
+ c = color_pre_curve_params[3 + (color_channel * 10)];
+ d = color_pre_curve_params[4 + (color_channel * 10)];
+
+ if (color_pre_curve_clamped_input)
+ x = clamp(x, 0.0, 1.0);
+
+ /* We use mirroring for negative input values. */
+ if (x < 0.0)
+ return -powlin(-x, g, a, b, c, d);
+
+ return powlin(x, g, a, b, c, d);
+}
+
+float
+sample_color_post_curve_linpow(float x, compile_const int color_channel)
+{
+ float g, a, b, c, d;
+
+ /* For each color channel we have 10 parameters. The params are
+ * linearized in an array of size 30, in RGB order. */
+ g = color_post_curve_params[0 + (color_channel * 10)];
+ a = color_post_curve_params[1 + (color_channel * 10)];
+ b = color_post_curve_params[2 + (color_channel * 10)];
+ c = color_post_curve_params[3 + (color_channel * 10)];
+ d = color_post_curve_params[4 + (color_channel * 10)];
+
+ if (color_post_curve_clamped_input)
+ x = clamp(x, 0.0, 1.0);
+
+ /* We use mirroring for negative input values. */
+ if (x < 0.0)
+ return -linpow(-x, g, a, b, c, d);
+
+ return linpow(x, g, a, b, c, d);
+}
+
+float
+sample_color_post_curve_powlin(float x, compile_const int color_channel)
+{
+ float g, a, b, c, d;
+
+ /* For each color channel we have 10 parameters. The params are
+ * linearized in an array of size 30, in RGB order. */
+ g = color_post_curve_params[0 + (color_channel * 10)];
+ a = color_post_curve_params[1 + (color_channel * 10)];
+ b = color_post_curve_params[2 + (color_channel * 10)];
+ c = color_post_curve_params[3 + (color_channel * 10)];
+ d = color_post_curve_params[4 + (color_channel * 10)];
+
+ if (color_post_curve_clamped_input)
+ x = clamp(x, 0.0, 1.0);
+
+ /* We use mirroring for negative input values. */
+ if (x < 0.0)
+ return -powlin(-x, g, a, b, c, d);
+
+ return powlin(x, g, a, b, c, d);
+}
+
vec3
color_pre_curve(vec3 color)
{
@@ -224,6 +346,16 @@ color_pre_curve(vec3 color)
ret.g = sample_color_pre_curve_lut_2d(color.g, 1);
ret.b = sample_color_pre_curve_lut_2d(color.b, 2);
return ret;
+ } else if (c_color_pre_curve == SHADER_COLOR_CURVE_LINPOW) {
+ ret.r = sample_color_pre_curve_linpow(color.r, 0);
+ ret.g = sample_color_pre_curve_linpow(color.g, 1);
+ ret.b = sample_color_pre_curve_linpow(color.b, 2);
+ return ret;
+ } else if (c_color_pre_curve == SHADER_COLOR_CURVE_POWLIN) {
+ ret.r = sample_color_pre_curve_powlin(color.r, 0);
+ ret.g = sample_color_pre_curve_powlin(color.g, 1);
+ ret.b = sample_color_pre_curve_powlin(color.b, 2);
+ return ret;
} else {
/* Never reached, bad c_color_pre_curve. */
return vec3(1.0, 0.3, 1.0);
@@ -275,6 +407,16 @@ color_post_curve(vec3 color)
ret.g = sample_color_post_curve_lut_2d(color.g, 1);
ret.b = sample_color_post_curve_lut_2d(color.b, 2);
return ret;
+ } else if (c_color_post_curve == SHADER_COLOR_CURVE_LINPOW) {
+ ret.r = sample_color_post_curve_linpow(color.r, 0);
+ ret.g = sample_color_post_curve_linpow(color.g, 1);
+ ret.b = sample_color_post_curve_linpow(color.b, 2);
+ return ret;
+ } else if (c_color_post_curve == SHADER_COLOR_CURVE_POWLIN) {
+ ret.r = sample_color_post_curve_powlin(color.r, 0);
+ ret.g = sample_color_post_curve_powlin(color.g, 1);
+ ret.b = sample_color_post_curve_powlin(color.b, 2);
+ return ret;
} else {
/* Never reached, bad c_color_post_curve. */
return vec3(1.0, 0.3, 1.0);
diff --git a/libweston/renderer-gl/gl-renderer-internal.h b/libweston/renderer-gl/gl-renderer-internal.h
index 9d409c7a..80a75bdc 100644
--- a/libweston/renderer-gl/gl-renderer-internal.h
+++ b/libweston/renderer-gl/gl-renderer-internal.h
@@ -61,6 +61,8 @@ enum gl_shader_texture_variant {
enum gl_shader_color_curve {
SHADER_COLOR_CURVE_IDENTITY = 0,
SHADER_COLOR_CURVE_LUT_3x1D,
+ SHADER_COLOR_CURVE_LINPOW,
+ SHADER_COLOR_CURVE_POWLIN,
};
/* Keep the following in sync with fragment.glsl. */
@@ -87,14 +89,14 @@ struct gl_shader_requirements
bool input_is_premult:1;
bool green_tint:1;
- unsigned color_pre_curve:1; /* enum gl_shader_color_curve */
+ unsigned color_pre_curve:2; /* enum gl_shader_color_curve */
unsigned color_mapping:2; /* enum gl_shader_color_mapping */
- unsigned color_post_curve:1; /* enum gl_shader_color_curve */
+ unsigned color_post_curve:2; /* enum gl_shader_color_curve */
/*
* The total size of all bitfields plus pad_bits_ must fill up exactly
* how many bytes the compiler allocates for them together.
*/
- unsigned pad_bits_:21;
+ unsigned pad_bits_:19;
};
static_assert(sizeof(struct gl_shader_requirements) ==
4 /* total bitfield size in bytes */,
@@ -119,6 +121,10 @@ struct gl_shader_config {
GLuint tex;
GLfloat scale_offset[2];
} lut_3x1d;
+ struct {
+ GLfloat params[3][10];
+ GLboolean clamped_input;
+ } parametric;
} color_pre_curve;
union {
@@ -134,6 +140,10 @@ struct gl_shader_config {
GLuint tex;
GLfloat scale_offset[2];
} lut_3x1d;
+ struct {
+ GLfloat params[3][10];
+ GLboolean clamped_input;
+ } parametric;
} color_post_curve;
};
diff --git a/libweston/renderer-gl/gl-shader-config-color-transformation.c b/libweston/renderer-gl/gl-shader-config-color-transformation.c
index 49224d3d..d550cefd 100644
--- a/libweston/renderer-gl/gl-shader-config-color-transformation.c
+++ b/libweston/renderer-gl/gl-shader-config-color-transformation.c
@@ -47,6 +47,10 @@ struct gl_renderer_color_curve {
float scale;
float offset;
} lut_3x1d;
+ struct {
+ GLfloat params[3][10];
+ GLboolean clamped_input;
+ } parametric;
} u;
};
@@ -75,6 +79,8 @@ gl_renderer_color_curve_fini(struct gl_renderer_color_curve *gl_curve)
{
switch (gl_curve->type) {
case SHADER_COLOR_CURVE_IDENTITY:
+ case SHADER_COLOR_CURVE_LINPOW:
+ case SHADER_COLOR_CURVE_POWLIN:
break;
case SHADER_COLOR_CURVE_LUT_3x1D:
glDeleteTextures(1, &gl_curve->u.lut_3x1d.tex);
@@ -141,6 +147,37 @@ gl_renderer_color_transform_get(struct weston_color_transform *xform)
destroy_listener);
}
+static void
+gl_color_curve_parametric(struct gl_renderer_color_curve *gl_curve,
+ const struct weston_color_curve *curve)
+{
+ const struct weston_color_curve_parametric *parametric = &curve->u.parametric;
+
+ ARRAY_COPY(gl_curve->u.parametric.params, parametric->params);
+
+ gl_curve->u.parametric.clamped_input = parametric->clamped_input;
+}
+
+static bool
+gl_color_curve_linpow(struct gl_renderer_color_curve *gl_curve,
+ const struct weston_color_curve *curve)
+{
+ gl_curve->type = SHADER_COLOR_CURVE_LINPOW;
+ gl_color_curve_parametric(gl_curve, curve);
+
+ return true;
+}
+
+static bool
+gl_color_curve_powlin(struct gl_renderer_color_curve *gl_curve,
+ const struct weston_color_curve *curve)
+{
+ gl_curve->type = SHADER_COLOR_CURVE_POWLIN;
+ gl_color_curve_parametric(gl_curve, curve);
+
+ return true;
+}
+
static bool
gl_color_curve_lut_3x1d(struct gl_renderer *gr,
struct gl_renderer_color_curve *gl_curve,
@@ -267,6 +304,14 @@ gl_renderer_color_transform_from(struct gl_renderer *gr,
ok = gl_color_curve_lut_3x1d(gr, &gl_xform->pre_curve,
&xform->pre_curve, xform);
break;
+ case WESTON_COLOR_CURVE_TYPE_LINPOW:
+ ok = gl_color_curve_linpow(&gl_xform->pre_curve,
+ &xform->pre_curve);
+ break;
+ case WESTON_COLOR_CURVE_TYPE_POWLIN:
+ ok = gl_color_curve_powlin(&gl_xform->pre_curve,
+ &xform->pre_curve);
+ break;
}
if (!ok) {
gl_renderer_color_transform_destroy(gl_xform);
@@ -301,6 +346,14 @@ gl_renderer_color_transform_from(struct gl_renderer *gr,
ok = gl_color_curve_lut_3x1d(gr, &gl_xform->post_curve,
&xform->post_curve, xform);
break;
+ case WESTON_COLOR_CURVE_TYPE_LINPOW:
+ ok = gl_color_curve_linpow(&gl_xform->post_curve,
+ &xform->post_curve);
+ break;
+ case WESTON_COLOR_CURVE_TYPE_POWLIN:
+ ok = gl_color_curve_powlin(&gl_xform->post_curve,
+ &xform->post_curve);
+ break;
}
if (!ok) {
gl_renderer_color_transform_destroy(gl_xform);
@@ -331,6 +384,14 @@ gl_shader_config_set_color_transform(struct gl_renderer *gr,
sconf->color_pre_curve.lut_3x1d.scale_offset[0] = gl_xform->pre_curve.u.lut_3x1d.scale;
sconf->color_pre_curve.lut_3x1d.scale_offset[1] = gl_xform->pre_curve.u.lut_3x1d.offset;
break;
+ case SHADER_COLOR_CURVE_LINPOW:
+ case SHADER_COLOR_CURVE_POWLIN:
+ memcpy(sconf->color_pre_curve.parametric.params,
+ gl_xform->pre_curve.u.parametric.params,
+ sizeof(sconf->color_pre_curve.parametric.params));
+ sconf->color_pre_curve.parametric.clamped_input =
+ gl_xform->pre_curve.u.parametric.clamped_input;
+ break;
}
sconf->req.color_post_curve = gl_xform->post_curve.type;
@@ -342,6 +403,14 @@ gl_shader_config_set_color_transform(struct gl_renderer *gr,
sconf->color_post_curve.lut_3x1d.scale_offset[0] = gl_xform->post_curve.u.lut_3x1d.scale;
sconf->color_post_curve.lut_3x1d.scale_offset[1] = gl_xform->post_curve.u.lut_3x1d.offset;
break;
+ case SHADER_COLOR_CURVE_LINPOW:
+ case SHADER_COLOR_CURVE_POWLIN:
+ memcpy(&sconf->color_post_curve.parametric.params,
+ &gl_xform->post_curve.u.parametric.params,
+ sizeof(sconf->color_post_curve.parametric.params));
+ sconf->color_post_curve.parametric.clamped_input =
+ gl_xform->post_curve.u.parametric.clamped_input;
+ break;
}
sconf->req.color_mapping = gl_xform->mapping.type;
diff --git a/libweston/renderer-gl/gl-shaders.c b/libweston/renderer-gl/gl-shaders.c
index 999814d2..1f4b8a83 100644
--- a/libweston/renderer-gl/gl-shaders.c
+++ b/libweston/renderer-gl/gl-shaders.c
@@ -67,6 +67,10 @@ struct gl_shader {
GLint tex_2d_uniform;
GLint scale_offset_uniform;
} lut_3x1d;
+ struct {
+ GLint params_uniform;
+ GLint clamped_input_uniform;
+ } parametric;
} color_pre_curve;
union {
struct {
@@ -80,6 +84,10 @@ struct gl_shader {
GLint tex_2d_uniform;
GLint scale_offset_uniform;
} lut_3x1d;
+ struct {
+ GLint params_uniform;
+ GLint clamped_input_uniform;
+ } parametric;
} color_post_curve;
};
@@ -123,6 +131,8 @@ gl_shader_color_curve_to_string(enum gl_shader_color_curve kind)
#define CASERET(x) case x: return #x;
CASERET(SHADER_COLOR_CURVE_IDENTITY)
CASERET(SHADER_COLOR_CURVE_LUT_3x1D)
+ CASERET(SHADER_COLOR_CURVE_LINPOW)
+ CASERET(SHADER_COLOR_CURVE_POWLIN)
#undef CASERET
}
@@ -348,6 +358,13 @@ gl_shader_create(struct gl_renderer *gr,
switch(requirements->color_pre_curve) {
case SHADER_COLOR_CURVE_IDENTITY:
break;
+ case SHADER_COLOR_CURVE_LINPOW:
+ case SHADER_COLOR_CURVE_POWLIN:
+ shader->color_pre_curve.parametric.params_uniform =
+ glGetUniformLocation(shader->program, "color_pre_curve_params");
+ shader->color_pre_curve.parametric.clamped_input_uniform =
+ glGetUniformLocation(shader->program, "color_pre_curve_clamped_input");
+ break;
case SHADER_COLOR_CURVE_LUT_3x1D:
shader->color_pre_curve.lut_3x1d.tex_2d_uniform =
glGetUniformLocation(shader->program, "color_pre_curve_lut_2d");
@@ -359,6 +376,13 @@ gl_shader_create(struct gl_renderer *gr,
switch(requirements->color_post_curve) {
case SHADER_COLOR_CURVE_IDENTITY:
break;
+ case SHADER_COLOR_CURVE_LINPOW:
+ case SHADER_COLOR_CURVE_POWLIN:
+ shader->color_post_curve.parametric.params_uniform =
+ glGetUniformLocation(shader->program, "color_post_curve_params");
+ shader->color_post_curve.parametric.clamped_input_uniform =
+ glGetUniformLocation(shader->program, "color_post_curve_clamped_input");
+ break;
case SHADER_COLOR_CURVE_LUT_3x1D:
shader->color_post_curve.lut_3x1d.tex_2d_uniform =
glGetUniformLocation(shader->program, "color_post_curve_lut_2d");
@@ -595,6 +619,7 @@ gl_shader_load_config(struct gl_shader *shader,
{
GLint in_filter = sconf->input_tex_filter;
GLenum in_tgt;
+ GLsizei n_params;
int i;
glUniformMatrix4fv(shader->proj_uniform,
@@ -639,6 +664,14 @@ gl_shader_load_config(struct gl_shader *shader,
glUniform2fv(shader->color_pre_curve.lut_3x1d.scale_offset_uniform,
1, sconf->color_pre_curve.lut_3x1d.scale_offset);
break;
+ case SHADER_COLOR_CURVE_LINPOW:
+ case SHADER_COLOR_CURVE_POWLIN:
+ n_params = sizeof(sconf->color_pre_curve.parametric.params) / sizeof(GLfloat);
+ glUniform1fv(shader->color_pre_curve.parametric.params_uniform, n_params,
+ &sconf->color_pre_curve.parametric.params[0][0]);
+ glUniform1i(shader->color_pre_curve.parametric.clamped_input_uniform,
+ sconf->color_pre_curve.parametric.clamped_input);
+ break;
}
switch (sconf->req.color_mapping) {
@@ -677,6 +710,14 @@ gl_shader_load_config(struct gl_shader *shader,
glUniform2fv(shader->color_post_curve.lut_3x1d.scale_offset_uniform,
1, sconf->color_post_curve.lut_3x1d.scale_offset);
break;
+ case SHADER_COLOR_CURVE_LINPOW:
+ case SHADER_COLOR_CURVE_POWLIN:
+ n_params = sizeof(sconf->color_post_curve.parametric.params) / sizeof(GLfloat);
+ glUniform1fv(shader->color_post_curve.parametric.params_uniform, n_params,
+ &sconf->color_post_curve.parametric.params[0][0]);
+ glUniform1i(shader->color_post_curve.parametric.clamped_input_uniform,
+ sconf->color_post_curve.parametric.clamped_input);
+ break;
}
}
diff --git a/tests/color-icc-output-test.c b/tests/color-icc-output-test.c
index 700da141..38bdc354 100644
--- a/tests/color-icc-output-test.c
+++ b/tests/color-icc-output-test.c
@@ -136,7 +136,7 @@ static const struct setup_args my_setup_args[] = {
/* name, ref img, pipeline, tolerance, dim, profile type, clut tolerance, vcgt_exponents */
{ { "sRGB->sRGB MAT" }, 0, &pipeline_sRGB, 0.0, 0, PTYPE_MATRIX_SHAPER },
{ { "sRGB->sRGB MAT VCGT" }, 3, &pipeline_sRGB, 0.8, 0, PTYPE_MATRIX_SHAPER, 0.0000, {1.1, 1.2, 1.3} },
- { { "sRGB->adobeRGB MAT" }, 1, &pipeline_adobeRGB, 1.4, 0, PTYPE_MATRIX_SHAPER },
+ { { "sRGB->adobeRGB MAT" }, 1, &pipeline_adobeRGB, 1.6, 0, PTYPE_MATRIX_SHAPER },
{ { "sRGB->adobeRGB MAT VCGT" }, 4, &pipeline_adobeRGB, 1.0, 0, PTYPE_MATRIX_SHAPER, 0.0000, {1.1, 1.2, 1.3} },
{ { "sRGB->BT2020 MAT" }, 2, &pipeline_BT2020, 4.5, 0, PTYPE_MATRIX_SHAPER },
{ { "sRGB->sRGB CLUT" }, 0, &pipeline_sRGB, 0.0, 17, PTYPE_CLUT, 0.0005 },