blob: 9e23369c5a1b495042826de5464c9b9e17ee66b8 [file] [log] [blame]
/*
* Copyright 2018 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "../skcms.h"
#include "LinearAlgebra.h"
#include "Macros.h"
#include "TransferFunction.h"
#include <assert.h>
#include <math.h>
float skcms_TransferFunction_evalUnclamped(const skcms_TransferFunction* fn, float x) {
float sign = x < 0 ? -1.0f : 1.0f;
x *= sign;
return sign * (x < fn->d ? fn->c * x + fn->f
: powf(fn->a * x + fn->b, fn->g) + fn->e);
}
float skcms_TransferFunction_eval(const skcms_TransferFunction* fn, float x) {
float t = skcms_TransferFunction_evalUnclamped(fn, x);
return fminf(fmaxf(t, 0.0f), 1.0f);
}
// Evaluate the gradient of the nonlinear component of fn
static void tf_eval_gradient_nonlinear(const skcms_TransferFunction* fn,
float x,
float* d_fn_d_A_at_x,
float* d_fn_d_B_at_x,
float* d_fn_d_E_at_x,
float* d_fn_d_G_at_x) {
float base = fn->a * x + fn->b;
if (base > 0.0f) {
*d_fn_d_A_at_x = fn->g * x * powf(base, fn->g - 1.0f);
*d_fn_d_B_at_x = fn->g * powf(base, fn->g - 1.0f);
*d_fn_d_E_at_x = 1.0f;
*d_fn_d_G_at_x = powf(base, fn->g) * logf(base);
} else {
*d_fn_d_A_at_x = 0.0f;
*d_fn_d_B_at_x = 0.0f;
*d_fn_d_E_at_x = 0.0f;
*d_fn_d_G_at_x = 0.0f;
}
}
// Take one Gauss-Newton step updating A, B, E, and G, given D.
static bool tf_gauss_newton_step_nonlinear(skcms_TransferFunction* fn,
float* error_Linfty_after,
const float* x,
const float* t,
int n) {
// Let ne_lhs be the left hand side of the normal equations, and let ne_rhs
// be the right hand side. Zero the diagonal [sic] of |ne_lhs| and all of |ne_rhs|.
skcms_Matrix4x4 ne_lhs;
skcms_Vector4 ne_rhs;
for (int row = 0; row < 4; ++row) {
for (int col = 0; col < 4; ++col) {
ne_lhs.vals[row][col] = 0;
}
ne_rhs.vals[row] = 0;
}
// Add the contributions from each sample to the normal equations.
for (int i = 0; i < n; ++i) {
// Ignore points in the linear segment.
if (x[i] < fn->d) {
continue;
}
// Let J be the gradient of fn with respect to parameters A, B, E, and G,
// evaulated at this point.
skcms_Vector4 J;
tf_eval_gradient_nonlinear(fn, x[i], &J.vals[0], &J.vals[1], &J.vals[2], &J.vals[3]);
// Let r be the residual at this point;
float r = t[i] - skcms_TransferFunction_eval(fn, x[i]);
// Update the normal equations left hand side with the outer product of J
// with itself.
for (int row = 0; row < 4; ++row) {
for (int col = 0; col < 4; ++col) {
ne_lhs.vals[row][col] += J.vals[row] * J.vals[col];
}
// Update the normal equations right hand side the product of J with the
// residual
ne_rhs.vals[row] += J.vals[row] * r;
}
}
// Note that if G = 1, then the normal equations will be singular
// (because when G = 1, B and E are equivalent parameters).
// To avoid problems, fix E (row/column 3) in these circumstances.
float kEpsilonForG = 1.0f / 1024.0f;
if (fabsf(fn->g - 1.0f) < kEpsilonForG) {
for (int row = 0; row < 4; ++row) {
float value = (row == 2) ? 1.0f : 0.0f;
ne_lhs.vals[row][2] = value;
ne_lhs.vals[2][row] = value;
}
ne_rhs.vals[2] = 0.0f;
}
// Solve the normal equations.
skcms_Matrix4x4 ne_lhs_inv;
if (!skcms_Matrix4x4_invert(&ne_lhs, &ne_lhs_inv)) {
return false;
}
skcms_Vector4 step = skcms_Matrix4x4_Vector4_mul(&ne_lhs_inv, &ne_rhs);
// Update the transfer function.
fn->a += step.vals[0];
fn->b += step.vals[1];
fn->e += step.vals[2];
fn->g += step.vals[3];
// A should always be positive.
fn->a = fmaxf(fn->a, 0.0f);
// Ensure that fn be defined at D.
if (fn->a * fn->d + fn->b < 0.0f) {
fn->b = -fn->a * fn->d;
}
// Compute the Linfinity error.
*error_Linfty_after = 0;
for (int i = 0; i < n; ++i) {
if (x[i] >= fn->d) {
float error = fabsf(t[i] - skcms_TransferFunction_eval(fn, x[i]));
*error_Linfty_after = fmaxf(error, *error_Linfty_after);
}
}
return true;
}
// Solve for A, B, E, and G, given D. The initial value of |fn| is the
// point from which iteration starts.
static bool tf_solve_nonlinear(skcms_TransferFunction* fn,
const float* x,
const float* t,
int n) {
// Take a maximum of 16 Gauss-Newton steps.
enum { kNumSteps = 16 };
// The L-infinity error after each step.
float step_error[kNumSteps] = { 0 };
int step = 0;
for (;; ++step) {
// If the normal equations are singular, we can't continue.
if (!tf_gauss_newton_step_nonlinear(fn, &step_error[step], x, t, n)) {
return false;
}
// If the error is inf or nan, we are clearly not converging.
if (isnan(step_error[step]) || isinf(step_error[step])) {
return false;
}
// Stop if our error is tiny.
float kEarlyOutTinyErrorThreshold = (1.0f / 16.0f) / 256.0f;
if (step_error[step] < kEarlyOutTinyErrorThreshold) {
break;
}
// Stop if our error is not changing, or changing in the wrong direction.
if (step > 1) {
// If our error is is huge for two iterations, we're probably not in the
// region of convergence.
if (step_error[step] > 1.0f && step_error[step - 1] > 1.0f) {
return false;
}
// If our error didn't change by ~1%, assume we've converged as much as we
// are going to.
const float kEarlyOutByPercentChangeThreshold = 32.0f / 256.0f;
const float kMinimumPercentChange = 1.0f / 128.0f;
float percent_change =
fabsf(step_error[step] - step_error[step - 1]) / step_error[step];
if (percent_change < kMinimumPercentChange &&
step_error[step] < kEarlyOutByPercentChangeThreshold) {
break;
}
}
if (step == kNumSteps - 1) {
break;
}
}
// Declare failure if our error is obviously too high.
float kDidNotConvergeThreshold = 64.0f / 256.0f;
if (step_error[step] > kDidNotConvergeThreshold) {
return false;
}
// We've converged to a reasonable solution. If some of the parameters are
// extremely close to 0 or 1, set them to 0 or 1.
const float kRoundEpsilon = 1.0f / 1024.0f;
if (fabsf(fn->a - 1.0f) < kRoundEpsilon) {
fn->a = 1.0f;
}
if (fabsf(fn->b) < kRoundEpsilon) {
fn->b = 0;
}
if (fabsf(fn->e) < kRoundEpsilon) {
fn->e = 0;
}
if (fabsf(fn->g - 1.0f) < kRoundEpsilon) {
fn->g = 1.0f;
}
return true;
}
bool skcms_TransferFunction_approximate(skcms_TransferFunction* fn,
const float* x,
const float* t,
int n,
float* max_error) {
// First, guess at a value of D. Assume that the nonlinear segment applies
// to all x >= 0.15. This is generally a safe assumption (D is usually less
// than 0.1).
const float kLinearSegmentMaximum = 0.15f;
fn->d = kLinearSegmentMaximum;
// Do a nonlinear regression on the nonlinear segment. Use a number of guesses
// for the initial value of G, because not all values will converge.
bool nonlinear_fit_converged = false;
{
float initial_gammas[] = { 2.2f, 2.4f, 1.0f, 3.0f, 0.5f };
for (int i = 0; i < ARRAY_COUNT(initial_gammas); ++i) {
fn->g = initial_gammas[i];
fn->a = 1;
fn->b = 0;
fn->c = 1;
fn->e = 0;
fn->f = 0;
if (tf_solve_nonlinear(fn, x, t, n)) {
nonlinear_fit_converged = true;
break;
}
}
}
if (!nonlinear_fit_converged) {
return false;
}
// Now walk back D from our initial guess to the point where our nonlinear
// fit no longer fits (or all the way to 0 if it fits).
{
// Find the L-infinity error of this nonlinear fit (using our old D value).
float max_error_in_nonlinear_fit = 0;
for (int i = 0; i < n; ++i) {
if (x[i] < fn->d) {
continue;
}
float error_at_xi = fabsf(t[i] - skcms_TransferFunction_eval(fn, x[i]));
max_error_in_nonlinear_fit = fmaxf(max_error_in_nonlinear_fit, error_at_xi);
}
// Now find the maximum x value where this nonlinear fit is no longer
// accurate, no longer defined, or no longer nonnegative.
fn->d = 0.0f;
float max_x_where_nonlinear_does_not_fit = -1.0f;
for (int i = 0; i < n; ++i) {
if (x[i] >= kLinearSegmentMaximum) {
continue;
}
// The nonlinear segment is only undefined when A * x + B is
// nonnegative.
float fn_at_xi = -1;
if (fn->a * x[i] + fn->b >= 0) {
fn_at_xi = skcms_TransferFunction_evalUnclamped(fn, x[i]);
}
// If the value is negative (or undefined), say that the fit was bad.
bool nonlinear_fits_xi = true;
if (fn_at_xi < 0) {
nonlinear_fits_xi = false;
}
// Compute the error, and define "no longer accurate" as "has more than
// 10% more error than the maximum error in the fit segment".
if (nonlinear_fits_xi) {
float error_at_xi = fabsf(t[i] - fn_at_xi);
if (error_at_xi > 1.1f * max_error_in_nonlinear_fit) {
nonlinear_fits_xi = false;
}
}
if (!nonlinear_fits_xi) {
max_x_where_nonlinear_does_not_fit =
fmaxf(max_x_where_nonlinear_does_not_fit, x[i]);
}
}
// Now let D be the highest sample of x that is above the threshold where
// the nonlinear segment does not fit.
fn->d = 1.0f;
for (int i = 0; i < n; ++i) {
if (x[i] > max_x_where_nonlinear_does_not_fit) {
fn->d = fminf(fn->d, x[i]);
}
}
}
// Compute the linear segment, now that we have our definitive D.
if (fn->d <= 0) {
// If this has no linear segment, don't try to solve for one.
fn->c = 1;
fn->f = 0;
} else {
// Set the linear portion such that it go through the origin and be
// continuous with the nonlinear segment.
float fn_at_D = skcms_TransferFunction_eval(fn, fn->d);
fn->c = fn_at_D / fn->d;
fn->f = 0;
}
if (max_error) {
*max_error = 0;
for (int i = 0; i < n; ++i) {
float fn_of_xi = skcms_TransferFunction_eval(fn, x[i]);
float error_at_xi = fabsf(t[i] - fn_of_xi);
*max_error = fmaxf(*max_error, error_at_xi);
}
}
return true;
}
bool skcms_TransferFunction_invert(const skcms_TransferFunction* src, skcms_TransferFunction* dst) {
// Original equation is: y = (ax + b)^g + e for x >= d
// y = cx + f otherwise
//
// so 1st inverse is: (y - e)^(1/g) = ax + b
// x = ((y - e)^(1/g) - b) / a
//
// which can be re-written as: x = (1/a)(y - e)^(1/g) - b/a
// x = ((1/a)^g)^(1/g) * (y - e)^(1/g) - b/a
// x = ([(1/a)^g]y + [-((1/a)^g)e]) ^ [1/g] + [-b/a]
//
// and 2nd inverse is: x = (y - f) / c
// which can be re-written as: x = [1/c]y + [-f/c]
//
// and now both can be expressed in terms of the same parametric form as the
// original - parameters are enclosed in square brackets.
skcms_TransferFunction fn_inv = { 0, 0, 0, 0, 0, 0, 0 };
// Reject obviously malformed inputs
if (!isfinite(src->a + src->b + src->c + src->d + src->e + src->f + src->g)) {
return false;
}
bool has_nonlinear = (src->d <= 1);
bool has_linear = (src->d > 0);
// Is the linear section decreasing or not invertible?
if (has_linear && src->c <= 0) {
return false;
}
// Is the nonlinear section decreasing or not invertible?
if (has_nonlinear && (src->a <= 0 || src->g <= 0)) {
return false;
}
// If both segments are present, they need to line up
if (has_linear && has_nonlinear) {
float l_at_d = src->c * src->d + src->f;
float n_at_d = powf(src->a * src->d + src->b, src->g) + src->e;
if (fabsf(l_at_d - n_at_d) > 0.0001f) {
return false;
}
}
// Invert linear segment
if (has_linear) {
fn_inv.c = 1.0f / src->c;
fn_inv.f = -src->f / src->c;
}
// Invert nonlinear segment
if (has_nonlinear) {
fn_inv.g = 1.0f / src->g;
fn_inv.a = powf(1.0f / src->a, src->g);
fn_inv.b = -fn_inv.a * src->e;
fn_inv.e = -src->b / src->a;
}
if (!has_linear) {
fn_inv.d = 0;
} else if (!has_nonlinear) {
// Any value larger than 1 works
fn_inv.d = 2.0f;
} else {
fn_inv.d = src->c * src->d + src->f;
}
*dst = fn_inv;
return true;
}
bool skcms_IsSRGB(const skcms_TransferFunction* tf) {
return tf->g == 157286 / 65536.0f
&& tf->a == 62119 / 65536.0f
&& tf->b == 3417 / 65536.0f
&& tf->c == 5072 / 65536.0f
&& tf->d == 2651 / 65536.0f
&& tf->e == 0 / 65536.0f
&& tf->f == 0 / 65536.0f;
}