| Index: ui/gfx/skia_color_space_util.cc
|
| diff --git a/ui/gfx/skia_color_space_util.cc b/ui/gfx/skia_color_space_util.cc
|
| index 2dda075fe65c1b129c6488b6019aea778c29132d..a4cfa3b1a5f2c80e46ea204b90edd680623ef4a8 100644
|
| --- a/ui/gfx/skia_color_space_util.cc
|
| +++ b/ui/gfx/skia_color_space_util.cc
|
| @@ -7,11 +7,341 @@
|
| #include <algorithm>
|
| #include <cmath>
|
|
|
| +#include "base/logging.h"
|
| +
|
| namespace gfx {
|
|
|
| namespace {
|
| +
|
| +// Evaluate the gradient of the linear component of fn
|
| +void SkTransferFnEvalGradientLinear(const SkColorSpaceTransferFn& fn,
|
| + float x,
|
| + float* d_fn_d_fC_at_x,
|
| + float* d_fn_d_fF_at_x) {
|
| + *d_fn_d_fC_at_x = x;
|
| + *d_fn_d_fF_at_x = 1.f;
|
| +}
|
| +
|
| +// Solve for the parameters fC and fF, given the parameter fD.
|
| +void SkTransferFnSolveLinear(SkColorSpaceTransferFn* fn,
|
| + const float* x,
|
| + const float* t,
|
| + size_t n) {
|
| + // If this has no linear segment, don't try to solve for one.
|
| + if (fn->fD <= 0) {
|
| + fn->fC = 1;
|
| + fn->fF = 0;
|
| + return;
|
| + }
|
| +
|
| + // Let ne_lhs be the left hand side of the normal equations, and let ne_rhs
|
| + // be the right hand side. This is a 4x4 matrix, but we will only update the
|
| + // upper-left 2x2 sub-matrix.
|
| + SkMatrix44 ne_lhs;
|
| + SkVector4 ne_rhs;
|
| + for (int row = 0; row < 2; ++row) {
|
| + for (int col = 0; col < 2; ++col) {
|
| + ne_lhs.set(row, col, 0);
|
| + }
|
| + }
|
| + for (int row = 0; row < 4; ++row)
|
| + ne_rhs.fData[row] = 0;
|
| +
|
| + // Add the contributions from each sample to the normal equations.
|
| + float x_linear_max = 0;
|
| + for (size_t i = 0; i < n; ++i) {
|
| + // Ignore points in the nonlinear segment.
|
| + if (x[i] >= fn->fD)
|
| + continue;
|
| + x_linear_max = std::max(x_linear_max, x[i]);
|
| +
|
| + // Let J be the gradient of fn with respect to parameters C and F, evaluated
|
| + // at this point.
|
| + float J[2];
|
| + SkTransferFnEvalGradientLinear(*fn, x[i], &J[0], &J[1]);
|
| +
|
| + // Let r be the residual at this point.
|
| + float r = t[i];
|
| +
|
| + // Update the normal equations left hand side with the outer product of J
|
| + // with itself.
|
| + for (int row = 0; row < 2; ++row) {
|
| + for (int col = 0; col < 2; ++col) {
|
| + ne_lhs.set(row, col, ne_lhs.get(row, col) + J[row] * J[col]);
|
| + }
|
| + // Update the normal equations right hand side the product of J with the
|
| + // residual
|
| + ne_rhs.fData[row] += J[row] * r;
|
| + }
|
| + }
|
| +
|
| + // If we only have a single x point at 0, that isn't enough to construct a
|
| + // linear segment, so add an additional point connecting to the nonlinear
|
| + // segment.
|
| + if (x_linear_max == 0) {
|
| + float J[2];
|
| + SkTransferFnEvalGradientLinear(*fn, fn->fD, &J[0], &J[1]);
|
| + float r = SkTransferFnEval(*fn, fn->fD);
|
| + for (int row = 0; row < 2; ++row) {
|
| + for (int col = 0; col < 2; ++col) {
|
| + ne_lhs.set(row, col, ne_lhs.get(row, col) + J[row] * J[col]);
|
| + }
|
| + ne_rhs.fData[row] += J[row] * r;
|
| + }
|
| + }
|
| +
|
| + // Solve the normal equations.
|
| + SkMatrix44 ne_lhs_inv;
|
| + bool invert_result = ne_lhs.invert(&ne_lhs_inv);
|
| + DCHECK(invert_result);
|
| + SkVector4 solution = ne_lhs_inv * ne_rhs;
|
| +
|
| + // Update the transfer function.
|
| + fn->fC = solution.fData[0];
|
| + fn->fF = solution.fData[1];
|
| +}
|
| +
|
| +// Evaluate the gradient of the nonlinear component of fn
|
| +void SkTransferFnEvalGradientNonlinear(const SkColorSpaceTransferFn& fn,
|
| + float x,
|
| + float* d_fn_d_fA_at_x,
|
| + float* d_fn_d_fB_at_x,
|
| + float* d_fn_d_fE_at_x,
|
| + float* d_fn_d_fG_at_x) {
|
| + float base = fn.fA * x + fn.fB;
|
| + if (base > 0.f) {
|
| + *d_fn_d_fA_at_x = fn.fG * x * std::pow(base, fn.fG - 1.f);
|
| + *d_fn_d_fB_at_x = fn.fG * std::pow(base, fn.fG - 1.f);
|
| + *d_fn_d_fE_at_x = 1.f;
|
| + *d_fn_d_fG_at_x = std::pow(base, fn.fG) * std::log(base);
|
| + } else {
|
| + *d_fn_d_fA_at_x = 0.f;
|
| + *d_fn_d_fB_at_x = 0.f;
|
| + *d_fn_d_fE_at_x = 0.f;
|
| + *d_fn_d_fG_at_x = 0.f;
|
| + }
|
| }
|
|
|
| +// Take one Gauss-Newton step updating fA, fB, fE, and fG, given fD.
|
| +bool SkTransferFnGaussNewtonStepNonlinear(SkColorSpaceTransferFn* fn,
|
| + float* error_Linfty_before,
|
| + const float* x,
|
| + const float* t,
|
| + size_t n) {
|
| + float kEpsilon = 1.f / 1024.f;
|
| + // Let ne_lhs be the left hand side of the normal equations, and let ne_rhs
|
| + // be the right hand side.
|
| + SkMatrix44 ne_lhs;
|
| + SkVector4 ne_rhs;
|
| + for (int row = 0; row < 4; ++row) {
|
| + for (int col = 0; col < 4; ++col) {
|
| + ne_lhs.set(row, col, 0);
|
| + }
|
| + ne_rhs.fData[row] = 0;
|
| + }
|
| +
|
| + // Add the contributions from each sample to the normal equations.
|
| + *error_Linfty_before = 0;
|
| + for (size_t i = 0; i < n; ++i) {
|
| + // Ignore points in the linear segment.
|
| + if (x[i] < fn->fD)
|
| + continue;
|
| + // Let J be the gradient of fn with respect to parameters A, B, E, and G,
|
| + // evaulated at this point.
|
| + float J[4];
|
| + SkTransferFnEvalGradientNonlinear(*fn, x[i], &J[0], &J[1], &J[2], &J[3]);
|
| + // Let r be the residual at this point;
|
| + float r = t[i] - SkTransferFnEval(*fn, x[i]);
|
| + *error_Linfty_before += std::abs(r);
|
| + // 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.set(row, col, ne_lhs.get(row, col) + J[row] * J[col]);
|
| + }
|
| + // Update the normal equations right hand side the product of J with the
|
| + // residual
|
| + ne_rhs.fData[row] += J[row] * r;
|
| + }
|
| + }
|
| +
|
| + // Note that if fG = 1, then the normal equations will be singular (because,
|
| + // when fG = 1, because fA and fE are equivalent parameters). To avoid
|
| + // problems, fix fE (row/column 3) in these circumstances.
|
| + if (std::abs(fn->fG - 1) < kEpsilon) {
|
| + for (int row = 0; row < 4; ++row) {
|
| + float value = (row == 2) ? 1.f : 0.f;
|
| + ne_lhs.set(row, 2, value);
|
| + ne_lhs.set(2, row, value);
|
| + }
|
| + ne_rhs.fData[2] = 0.f;
|
| + }
|
| +
|
| + // Solve the normal equations.
|
| + SkMatrix44 ne_lhs_inv;
|
| + if (!ne_lhs.invert(&ne_lhs_inv))
|
| + return false;
|
| + SkVector4 step = ne_lhs_inv * ne_rhs;
|
| +
|
| + // Update the transfer function.
|
| + fn->fA += step.fData[0];
|
| + fn->fB += step.fData[1];
|
| + fn->fE += step.fData[2];
|
| + fn->fG += step.fData[3];
|
| + return true;
|
| +}
|
| +
|
| +// Solve for fA, fB, fE, and fG, given fD. The initial value of |fn| is the
|
| +// point from which iteration starts.
|
| +bool SkTransferFnSolveNonlinear(SkColorSpaceTransferFn* fn,
|
| + const float* x,
|
| + const float* t,
|
| + size_t n) {
|
| + // Take a maximum of 16 Gauss-Newton steps.
|
| + const size_t kNumSteps = 16;
|
| +
|
| + // The L-infinity error before each step.
|
| + float step_error[kNumSteps] = {0};
|
| +
|
| + for (size_t step = 0; step < kNumSteps; ++step) {
|
| + // If the normal equations are singular, we can't continue.
|
| + if (!SkTransferFnGaussNewtonStepNonlinear(fn, &step_error[step], x, t, n))
|
| + return false;
|
| +
|
| + // If the error is inf or nan, we are clearly not converging.
|
| + if (std::isnan(step_error[step]) || std::isinf(step_error[step]))
|
| + return false;
|
| +
|
| + // If our error is non-negligable and increasing then we are not in the
|
| + // region of convergence.
|
| + const float kNonNegligbleErrorEpsilon = 1.f / 256.f;
|
| + const float kGrowthFactor = 1.25f;
|
| + if (step > 2 && step_error[step] > kNonNegligbleErrorEpsilon) {
|
| + if (step_error[step - 1] * kGrowthFactor < step_error[step] &&
|
| + step_error[step - 2] * kGrowthFactor < step_error[step - 1]) {
|
| + 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.f / 1024.f;
|
| + if (std::abs(fn->fA - 1.f) < kRoundEpsilon)
|
| + fn->fA = 1.f;
|
| + if (std::abs(fn->fB) < kRoundEpsilon)
|
| + fn->fB = 0;
|
| + if (std::abs(fn->fE) < kRoundEpsilon)
|
| + fn->fE = 0;
|
| + if (std::abs(fn->fG - 1.f) < kRoundEpsilon)
|
| + fn->fG = 1.f;
|
| + return true;
|
| +}
|
| +
|
| +void SkApproximateTransferFnInternal(const float* x,
|
| + const float* t,
|
| + size_t n,
|
| + SkColorSpaceTransferFn* fn,
|
| + float* error,
|
| + bool* nonlinear_fit_converged) {
|
| + // First, guess at a value of fD. Assume that the nonlinear segment applies
|
| + // to all x >= 0.1. This is generally a safe assumption (fD is usually less
|
| + // than 0.1).
|
| + fn->fD = 0.1f;
|
| +
|
| + // Do a nonlinear regression on the nonlinear segment. Use a number of guesses
|
| + // for the initial value of fG, because not all values will converge.
|
| + *nonlinear_fit_converged = false;
|
| + {
|
| + const size_t kNumInitialGammas = 4;
|
| + float initial_gammas[kNumInitialGammas] = {1.f, 2.f, 3.f, 0.5f};
|
| + for (size_t i = 0; i < kNumInitialGammas; ++i) {
|
| + fn->fG = initial_gammas[i];
|
| + fn->fA = 1;
|
| + fn->fB = 0;
|
| + fn->fC = 1;
|
| + fn->fE = 0;
|
| + fn->fF = 0;
|
| + if (SkTransferFnSolveNonlinear(fn, x, t, n)) {
|
| + *nonlinear_fit_converged = true;
|
| + break;
|
| + }
|
| + }
|
| + }
|
| +
|
| + // Now walk back fD from our initial guess to the point where our nonlinear
|
| + // fit no longer fits (or all the way to 0 if it fits).
|
| + if (*nonlinear_fit_converged) {
|
| + // Find the L-infinity error of this nonlinear fit (using our old fD value).
|
| + float max_error_in_nonlinear_fit = 0;
|
| + for (size_t i = 0; i < n; ++i) {
|
| + if (x[i] < fn->fD)
|
| + continue;
|
| + float error_at_xi = std::abs(t[i] - SkTransferFnEval(*fn, x[i]));
|
| + max_error_in_nonlinear_fit =
|
| + std::max(max_error_in_nonlinear_fit, error_at_xi);
|
| + }
|
| +
|
| + // Now find the maximum x value where this nonlinear fit is no longer
|
| + // accurate or no longer defined.
|
| + fn->fD = 0.f;
|
| + float max_x_where_nonlinear_does_not_fit = -1.f;
|
| + for (size_t i = 0; i < n; ++i) {
|
| + bool nonlinear_fits_xi = true;
|
| + if (fn->fA * x[i] + fn->fB < 0) {
|
| + // The nonlinear segment is undefined when fA * x + fB is less than 0.
|
| + nonlinear_fits_xi = false;
|
| + } else {
|
| + // Define "no longer accurate" as "has more than 10% more error than
|
| + // the maximum error in the fit segment".
|
| + float error_at_xi = std::abs(t[i] - SkTransferFnEval(*fn, x[i]));
|
| + 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 =
|
| + std::max(max_x_where_nonlinear_does_not_fit, x[i]);
|
| + }
|
| + }
|
| +
|
| + // Now let fD be the highest sample of x that is above the threshold where
|
| + // the nonlinear segment does not fit.
|
| + fn->fD = 1.f;
|
| + for (size_t i = 0; i < n; ++i) {
|
| + if (x[i] > max_x_where_nonlinear_does_not_fit)
|
| + fn->fD = std::min(fn->fD, x[i]);
|
| + }
|
| + } else {
|
| + // If the nonlinear fit didn't converge, then try a linear fit on the full
|
| + // range. Note that fD must be strictly greater than 1 because the nonlinear
|
| + // segment starts at fD.
|
| + const float kEpsilon = 1.f / 256.f;
|
| + fn->fD = 1.f + kEpsilon;
|
| + }
|
| +
|
| + // Compute the linear segment, now that we have our definitive fD.
|
| + SkTransferFnSolveLinear(fn, x, t, n);
|
| +
|
| + // If the nonlinear fit didn't converge, re-express the linear fit in
|
| + // canonical form as a nonlinear fit with fG as 1.
|
| + if (!*nonlinear_fit_converged) {
|
| + fn->fA = fn->fC;
|
| + fn->fB = fn->fF;
|
| + fn->fC = 1;
|
| + fn->fD = 0;
|
| + fn->fE = 0;
|
| + fn->fF = 0;
|
| + fn->fG = 1;
|
| + }
|
| +
|
| + // Return the L-infinity error of the total approximation.
|
| + *error = 0.f;
|
| + for (size_t i = 0; i < n; ++i)
|
| + *error = std::max(*error, std::abs(t[i] - SkTransferFnEval(*fn, x[i])));
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| float SkTransferFnEval(const SkColorSpaceTransferFn& fn, float x) {
|
| if (x < 0.f)
|
| return 0.f;
|
| @@ -61,6 +391,15 @@ bool SkTransferFnIsApproximatelyIdentity(const SkColorSpaceTransferFn& a) {
|
| return true;
|
| }
|
|
|
| +void SkApproximateTransferFn(const float* x,
|
| + const float* t,
|
| + size_t n,
|
| + SkColorSpaceTransferFn* fn,
|
| + float* error,
|
| + bool* nonlinear_fit_converged) {
|
| + SkApproximateTransferFnInternal(x, t, n, fn, error, nonlinear_fit_converged);
|
| +}
|
| +
|
| bool SkMatrixIsApproximatelyIdentity(const SkMatrix44& m) {
|
| const float kEpsilon = 1.f / 256.f;
|
| for (int i = 0; i < 4; ++i) {
|
|
|