Commit 8a1c10f0 authored by Niklas Haas's avatar Niklas Haas
Browse files

colorspace: add color temperature adjustment

In theory, this is not quite the right place to do this adjustment,
because it's applied in gamma light. But the alternative fix here, I
think, would be making `pl_renderer` smart enough to apply these
controls on its own. Plus, I don't think this feature is worth the extra
API complexity of somehow moving it to a separate location. The more we
can roll into this struct, the better.

Supporting this, a helper is provided to compute the CIE xy coordinates
for a given color temperature, as well as a function to compute a
chromatic adaptation matrix.
parent fabeb4f1
Pipeline #82907 passed with stages
in 9 minutes and 11 seconds
......@@ -617,6 +617,8 @@ static void update_settings(struct plplay *p)
nk_slider_float(nk, -M_PI, &adj->hue, M_PI, 0.01);
nk_label(nk, "Gamma:", NK_TEXT_LEFT);
nk_slider_float(nk, 0.0, &adj->gamma, 2.0, 0.01);
nk_label(nk, "Temperature:", NK_TEXT_LEFT);
nk_slider_float(nk, -1.0, &adj->temperature, 1.0, 0.01);
nk_tree_pop(nk);
}
......
......@@ -2,7 +2,7 @@ project('libplacebo', ['c', 'cpp'],
license: 'LGPL2.1+',
default_options: ['c_std=c99', 'cpp_std=c++11', 'warning_level=2'],
meson_version: '>=0.51',
version: '3.118.0',
version: '3.119.0',
)
# Version number
......
......@@ -364,11 +364,12 @@ void pl_color_space_infer(struct pl_color_space *space)
}
const struct pl_color_adjustment pl_color_adjustment_neutral = {
.brightness = 0.0,
.contrast = 1.0,
.saturation = 1.0,
.hue = 0.0,
.gamma = 1.0,
.brightness = 0.0,
.contrast = 1.0,
.saturation = 1.0,
.hue = 0.0,
.gamma = 1.0,
.temperature = 0.0,
};
void pl_chroma_location_offset(enum pl_chroma_location loc, float *x, float *y)
......@@ -404,6 +405,23 @@ void pl_chroma_location_offset(enum pl_chroma_location loc, float *x, float *y)
}
}
struct pl_cie_xy pl_white_from_temp(float temp)
{
temp = PL_CLAMP(temp, 2500, 25000);
double ti = 1000.0 / temp, ti2 = ti * ti, ti3 = ti2 * ti, x;
if (temp <= 7000) {
x = -4.6070 * ti3 + 2.9678 * ti2 + 0.09911 * ti + 0.244063;
} else {
x = -2.0064 * ti3 + 1.9018 * ti2 + 0.24748 * ti + 0.237040;
}
return (struct pl_cie_xy) {
.x = x,
.y = -3 * (x*x) + 2.87 * x - 0.275,
};
}
const struct pl_raw_primaries *pl_raw_primaries_get(enum pl_color_primaries prim)
{
/*
......@@ -628,6 +646,21 @@ static void apply_chromatic_adaptation(struct pl_cie_xy src,
pl_matrix3x3_mul(mat, &tmp);
}
struct pl_matrix3x3 pl_get_adaptation_matrix(struct pl_cie_xy src, struct pl_cie_xy dst)
{
// Use BT.709 primaries (with chosen white point) as an XYZ reference
struct pl_raw_primaries csp = *pl_raw_primaries_get(PL_COLOR_PRIM_BT_709);
csp.white = src;
struct pl_matrix3x3 rgb2xyz = pl_get_rgb2xyz_matrix(&csp);
struct pl_matrix3x3 xyz2rgb = rgb2xyz;
pl_matrix3x3_invert(&xyz2rgb);
apply_chromatic_adaptation(src, dst, &xyz2rgb);
pl_matrix3x3_mul(&xyz2rgb, &rgb2xyz);
return xyz2rgb;
}
const struct pl_cone_params pl_vision_normal = {PL_CONE_NONE, 1.0};
const struct pl_cone_params pl_vision_protanomaly = {PL_CONE_L, 0.5};
const struct pl_cone_params pl_vision_protanopia = {PL_CONE_L, 0.0};
......@@ -920,8 +953,6 @@ struct pl_transform3x3 pl_color_repr_decode(struct pl_color_repr *repr,
default: abort();
}
struct pl_transform3x3 out = { .mat = m };
// Apply hue and saturation in the correct way depending on the colorspace.
if (pl_color_system_is_ycbcr_like(repr->sys)) {
// Hue is equivalent to rotating input [U, V] subvector around the origin.
......@@ -929,13 +960,22 @@ struct pl_transform3x3 pl_color_repr_decode(struct pl_color_repr *repr,
float huecos = params->saturation * cos(params->hue);
float huesin = params->saturation * sin(params->hue);
for (int i = 0; i < 3; i++) {
float u = out.mat.m[i][1], v = out.mat.m[i][2];
out.mat.m[i][1] = huecos * u - huesin * v;
out.mat.m[i][2] = huesin * u + huecos * v;
float u = m.m[i][1], v = m.m[i][2];
m.m[i][1] = huecos * u - huesin * v;
m.m[i][2] = huesin * u + huecos * v;
}
}
// FIXME: apply saturation for RGB
// Apply color temperature adaptation, relative to BT.709 primaries
if (params->temperature) {
struct pl_cie_xy src = pl_white_from_temp(6500);
struct pl_cie_xy dst = pl_white_from_temp(6500 + 3500 * params->temperature);
struct pl_matrix3x3 adapt = pl_get_adaptation_matrix(src, dst);
pl_matrix3x3_rmul(&adapt, &m);
}
struct pl_transform3x3 out = { .mat = m };
int bit_depth = PL_DEF(repr->bits.sample_depth,
PL_DEF(repr->bits.color_depth, 8));
......
......@@ -163,6 +163,13 @@ void pl_matrix3x3_mul(struct pl_matrix3x3 *a, const struct pl_matrix3x3 *b)
}
}
void pl_matrix3x3_rmul(const struct pl_matrix3x3 *a, struct pl_matrix3x3 *b)
{
struct pl_matrix3x3 m = *a;
pl_matrix3x3_mul(&m, b);
*b = m;
}
const struct pl_transform3x3 pl_transform3x3_identity = {
.mat = {{
{ 1, 0, 0 },
......
......@@ -331,6 +331,8 @@ struct pl_color_adjustment {
float hue;
// Gamma adjustment. 1.0 = neutral, 0.0 = solid black
float gamma;
// Color temperature shift. 0.0 = 6500 K, -1.0 = 3000 K, 1.0 = 10000 K
float temperature;
};
// A struct pre-filled with all-neutral values.
......@@ -370,6 +372,12 @@ static inline float pl_cie_Z(struct pl_cie_xy xy) {
return (1 - xy.x - xy.y) / xy.y;
}
// Computes the CIE xy chromaticity coordinates of a CIE D-series illuminant
// with the given correlated color temperature.
//
// `temperature` must be betwen 2500 K and 25000 K, inclusive.
struct pl_cie_xy pl_white_from_temp(float temperature);
// Represents the raw physical primaries corresponding to a color space.
struct pl_raw_primaries {
struct pl_cie_xy red, green, blue, white;
......@@ -394,6 +402,10 @@ struct pl_matrix3x3 pl_get_color_mapping_matrix(const struct pl_raw_primaries *s
const struct pl_raw_primaries *dst,
enum pl_rendering_intent intent);
// Return a chromatic adaptation matrix, which converts from one white point to
// another, using the Bradford matrix. This is an RGB->RGB transformation.
struct pl_matrix3x3 pl_get_adaptation_matrix(struct pl_cie_xy src, struct pl_cie_xy dst);
// Returns true if 'b' is entirely contained in 'a'. Useful for figuring out if
// colorimetric clipping will occur or not.
bool pl_primaries_superset(const struct pl_raw_primaries *a,
......
......@@ -99,6 +99,10 @@ void pl_matrix3x3_invert(struct pl_matrix3x3 *mat);
// A := A * B
void pl_matrix3x3_mul(struct pl_matrix3x3 *a, const struct pl_matrix3x3 *b);
// Flipped version of `pl_matrix3x3_mul`.
// B := A * B
void pl_matrix3x3_rmul(const struct pl_matrix3x3 *a, struct pl_matrix3x3 *b);
// Represents an affine transformation, which is basically a 3x3 matrix
// together with a column vector to add onto the output.
struct pl_transform3x3 {
......
......@@ -304,4 +304,10 @@ int main()
// These models should round-trip green
TEST_CONE(pl_vision_normal, green);
// Color adaptation tests
struct pl_cie_xy d65 = pl_white_from_temp(6504);
struct pl_cie_xy d55 = pl_white_from_temp(5503);
REQUIRE(feq(d65.x, 0.31271, 1e-3) && feq(d65.y, 0.32902, 1e-3));
REQUIRE(feq(d55.x, 0.33242, 1e-3) && feq(d55.y, 0.34743, 1e-3));
}
......@@ -978,6 +978,7 @@ static void pl_render_tests(const struct pl_gpu *gpu)
.contrast = 0.9,
.saturation = 1.5,
.gamma = 0.8,
.temperature = 0.3,
};
REQUIRE(pl_render_image(rr, &image, &target, &params));
params = pl_render_default_params;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment