color: add function to create 3x1D LUT from color curve

Some external API's (e.g. DRM/KMS) are not capable of dealing with
enumerated color curves. In such cases, we may need to create LUT's that
correspond to such curves and give them to such API's

So add function weston_color_curve_to_3x1D_LUT() to craft 3x1D LUT from
a color curve.

Signed-off-by: Leandro Ribeiro <leandro.ribeiro@collabora.com>
This commit is contained in:
Leandro Ribeiro 2025-01-30 10:16:20 -03:00 committed by Pekka Paalanen
parent 2ac863397c
commit 0af48f0415
5 changed files with 373 additions and 0 deletions

View file

@ -0,0 +1,182 @@
/*
* Copyright 2025 Collabora, Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "config.h"
#include "color.h"
#include "color-operations.h"
#include "shared/helpers.h"
#include "shared/weston-assert.h"
/**
* Clamp value to [0.0, 1.0], except pass NaN through.
*
* This function is not intended for hiding NaN.
*/
static float
ensure_unorm(float v)
{
if (v <= 0.0f)
return 0.0f;
if (v > 1.0f)
return 1.0f;
return v;
}
static float
linpow(float x, float g, float a, float b, float c, float d)
{
/* See WESTON_COLOR_CURVE_PARAMETRIC_TYPE_LINPOW for details about LINPOW. */
if (x >= d)
return pow((a * x) + b, g);
return c * x;
}
static void
sample_linpow(float params[3][MAX_PARAMS_PARAM_CURVE], uint32_t ch,
uint32_t len, bool clamp_input, float *in, float *out)
{
float g, a, b, c, d;
float x;
unsigned int i;
g = params[ch][0];
a = params[ch][1];
b = params[ch][2];
c = params[ch][3];
d = params[ch][4];
for (i = 0; i < len; i++) {
x = in[i];
if (clamp_input)
x = ensure_unorm(x);
/* LINPOW uses mirroring for negative input values. */
if (x < 0.0)
out[i] = -linpow(-x, g, a, b, c, d);
else
out[i] = linpow(x, g, a, b, c, d);
}
}
static float
powlin(float x, float g, float a, float b, float c, float d)
{
/* See WESTON_COLOR_CURVE_PARAMETRIC_TYPE_POWLIN for details about POWLIN. */
if (x >= d)
return a * pow(x, g) + b;
return c * x;
}
static void
sample_powlin(float params[3][MAX_PARAMS_PARAM_CURVE], uint32_t ch,
uint32_t len, bool clamp_input, float *in, float *out)
{
float g, a, b, c, d;
float x;
unsigned int i;
g = params[ch][0];
a = params[ch][1];
b = params[ch][2];
c = params[ch][3];
d = params[ch][4];
for (i = 0; i < len; i++) {
x = in[i];
if (clamp_input)
x = ensure_unorm(x);
/* POWLIN uses mirroring for negative input values. */
if (x < 0.0)
out[i] = -powlin(-x, g, a, b, c, d);
else
out[i] = powlin(x, g, a, b, c, d);
}
}
/**
* Given a color curve and a channel, sample an input.
*
* This handles the parametric curves (LINPOW, POWLIN, etc) and enumerated color
* curves. Others should result in failure.
*
* @param compositor The Weston compositor
* @param curve The color curve to be used to sample
* @param ch The curve color channel to sample from
* @param len The in and out arrays length
* @param in The input array to sample
* @param out The resulting array from sampling
* @returns True on success, false otherwise
*/
bool
weston_color_curve_sample(struct weston_compositor *compositor,
struct weston_color_curve *curve,
uint32_t ch, uint32_t len, float *in, float *out)
{
struct weston_color_curve_parametric parametric;
bool ret;
switch(curve->type) {
case WESTON_COLOR_CURVE_TYPE_ENUM:
/* Lower the enum curve to a param curve and we'll handle that below. */
ret = weston_color_curve_enum_get_parametric(compositor,
&curve->u.enumerated,
&parametric);
if (!ret)
return false;
goto param;
case WESTON_COLOR_CURVE_TYPE_PARAMETRIC:
/* Parametric curve, let's copy it and we'll handle that below. */
parametric = curve->u.parametric;
goto param;
case WESTON_COLOR_CURVE_TYPE_IDENTITY:
weston_assert_not_reached(compositor,
"no need to sample identity");
case WESTON_COLOR_CURVE_TYPE_LUT_3x1D:
weston_assert_not_reached(compositor,
"function does not handle LUT 3x1D");
}
weston_assert_not_reached(compositor, "unknown color curve");
param:
/* Sample from parametric curves. */
switch(parametric.type) {
case WESTON_COLOR_CURVE_PARAMETRIC_TYPE_LINPOW:
sample_linpow(parametric.params, ch, len, parametric.clamped_input, in, out);
return true;
case WESTON_COLOR_CURVE_PARAMETRIC_TYPE_POWLIN:
sample_powlin(parametric.params, ch, len, parametric.clamped_input, in, out);
return true;
}
weston_assert_not_reached(compositor, "unknown parametric color curve");
}

View file

@ -0,0 +1,36 @@
/*
* Copyright 2025 Collabora, Ltd.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice (including the
* next paragraph) shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef WESTON_COLOR_OPERATIONS_H
#define WESTON_COLOR_OPERATIONS_H
#include <libweston/libweston.h>
bool
weston_color_curve_sample(struct weston_compositor *compositor,
struct weston_color_curve *curve,
uint32_t ch, uint32_t len, float *in, float *out);
#endif /* WESTON_COLOR_OPERATIONS_H */

View file

@ -37,12 +37,14 @@
#include <string.h>
#include "color.h"
#include "color-operations.h"
#include "color-properties.h"
#include "id-number-allocator.h"
#include "libweston-internal.h"
#include <libweston/weston-log.h>
#include "shared/string-helpers.h"
#include "shared/helpers.h"
#include "shared/weston-assert.h"
#include "shared/xalloc.h"
#include "shared/weston-assert.h"
@ -266,6 +268,147 @@ weston_color_curve_enum_get_parametric(struct weston_compositor *compositor,
return true;
}
static bool
curve_to_lut_has_good_precision(struct weston_color_curve *curve)
{
struct weston_color_curve_enum *e = &curve->u.enumerated;
struct weston_color_curve_parametric *p = &curve->u.parametric;
float g;
unsigned int i;
if (curve->type == WESTON_COLOR_CURVE_TYPE_ENUM) {
if (e->tf_direction == WESTON_INVERSE_TF) {
if (e->tf->tf == WESTON_TF_ST2084_PQ ||
e->tf->tf == WESTON_TF_GAMMA22 ||
e->tf->tf == WESTON_TF_GAMMA28) {
/**
* These have bad precision in the indirect
* direction.
*/
return false;
}
if (e->tf->tf == WESTON_TF_POWER) {
/**
* Same as the above, but for parametric
* power-law transfer function. If g > 1.0
* it would result in bad precision.
*/
for (i = 0; i < 3; i++) {
g = e->params[i][0];
if (g > 1.0f)
return false;
}
}
} else {
if (e->tf->tf == WESTON_TF_POWER) {
/**
* For parametric power-law transfer function
* in the forward direction, g < 1.0 would
* result in bad precision.
*/
for (i = 0; i < 3; i++) {
g = e->params[i][0];
if (g < 1.0f)
return false;
}
}
}
} else if (curve->type == WESTON_COLOR_CURVE_TYPE_PARAMETRIC) {
switch(p->type) {
case WESTON_COLOR_CURVE_PARAMETRIC_TYPE_LINPOW:
case WESTON_COLOR_CURVE_PARAMETRIC_TYPE_POWLIN:
/**
* Both LINPOW and POWLIN have bad precision if g < 1.0.
*/
for (i = 0; i < 3; i++) {
g = p->params[i][0];
if (g < 1.0f)
return false;
}
break;
}
}
return true;
}
/**
* Given a xform and an enum corresponding to one of its curves (pre or post),
* returns a 3x1D LUT that corresponds to such curve.
*
* The 3x1D LUT returned looks like this: the first lut_size elements compose
* the LUT for the R channel, the next lut_size elements compose the LUT for the
* G channel and the last lut_size elements compose the LUT for the B channel.
*
* @param compositor The Weston compositor.
* @param xform The color transformation that owns the curve.
* @param step The curve step (pre or post) from the xform.
* @param lut_size The size of each LUT.
* @param err_msg Set on failure, untouched otherwise. Must be free()'d by caller.
* @return NULL on failure, the 3x1D LUT on success.
*/
WL_EXPORT float *
weston_color_curve_to_3x1D_LUT(struct weston_compositor *compositor,
struct weston_color_transform *xform,
enum weston_color_curve_step step,
uint32_t lut_size, char **err_msg)
{
struct weston_color_curve *curve;
float divider = lut_size - 1;
float *in, *lut;
unsigned int i, ch;
bool ret;
switch(step) {
case WESTON_COLOR_CURVE_STEP_PRE:
curve = &xform->pre_curve;
break;
case WESTON_COLOR_CURVE_STEP_POST:
curve = &xform->post_curve;
break;
default:
weston_assert_not_reached(compositor, "unknown curve step");
}
if (!curve_to_lut_has_good_precision(curve)) {
str_printf(err_msg, "can't create color LUT from curve, it would " \
"result in bad precision");
return NULL;
}
switch(curve->type) {
case WESTON_COLOR_CURVE_TYPE_LUT_3x1D:
lut = xzalloc(3 * lut_size * sizeof(*lut));
curve->u.lut_3x1d.fill_in(xform, lut, lut_size);
return lut;
case WESTON_COLOR_CURVE_TYPE_ENUM:
case WESTON_COLOR_CURVE_TYPE_PARAMETRIC:
lut = xzalloc(3 * lut_size * sizeof(*lut));
in = xzalloc(lut_size * sizeof(*lut));
for (i = 0; i < lut_size; i++)
in[i] = (float)i / divider;
for (ch = 0; ch < 3; ch++) {
ret = weston_color_curve_sample(compositor, curve, ch, lut_size,
in, &lut[ch * lut_size]);
if (!ret) {
free(lut);
lut = NULL;
str_printf(err_msg, "can't create color LUT from " \
"curve, failed to sample color curve");
break;
}
}
free(in);
return lut;
case WESTON_COLOR_CURVE_TYPE_IDENTITY:
weston_assert_not_reached(compositor,
"no reason to create LUT for identity curve");
}
weston_assert_not_reached(compositor, "unkown color curve");
}
/**
* Increase reference count of the color transform object
*

View file

@ -653,6 +653,17 @@ weston_color_curve_from_tf_info(struct weston_color_curve *curve,
const float tf_params[MAX_PARAMS_TF],
enum weston_tf_direction tf_direction);
enum weston_color_curve_step {
WESTON_COLOR_CURVE_STEP_PRE,
WESTON_COLOR_CURVE_STEP_POST,
};
float *
weston_color_curve_to_3x1D_LUT(struct weston_compositor *compositor,
struct weston_color_transform *xform,
enum weston_color_curve_step step,
uint32_t lut_size, char **err_msg);
struct weston_color_transform *
weston_color_transform_ref(struct weston_color_transform *xform);

View file

@ -18,6 +18,7 @@ srcs_libweston = [
'color-properties.c',
'color-management.c',
'color-noop.c',
'color-operations.c',
'color-profile-param-builder.c',
'compositor.c',
'content-protection.c',