blob: 451a4272b7e61ded03105c62334cce87be9fb850 [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 "GaussNewton.h"
#include "Macros.h"
#include "PortableMath.h"
#include "TransferFunction.h"
#include <limits.h>
#include <stdlib.h>
// f(x) = skcms_PolyTF{A,B,C,D}(x) =
// Cx x < D
// Ax^3 + Bx^2 + (1-A-B) x ≥ D
//
// We'll fit C and D directly, and then hold them constant
// and fit the other part using Gauss-Newton, subject to
// the constraint that both parts meet at x=D:
//
// CD = AD^3 + BD^2 + (1-A-B)
//
// This lets us solve for B, reducing the optimization problem
// for that part down to just a single parameter A:
//
// CD = A(D^3-1) + B(D^2-1) + 1
//
// CD - A(D^3-1) - 1
// B = -----------------
// D^2-1
//
// x^2-1
// f(x) = A(x^3-1) + ----- [CD - A(D^3-1) - 1] + 1
// D^2-1
//
// (x^2-1) (D^3-1)
// ∂f/∂A = x^3-1 - ---------------
// D^2-1
static float eval_poly_tf(float x, const void* ctx, const float P[4]) {
const skcms_PolyTF* tf = (const skcms_PolyTF*)ctx;
float A = P[0],
C = tf->C,
D = tf->D;
float B = (C*D - A*(D*D*D - 1) - 1) / (D*D - 1);
return x < D ? C*x
: A*x*x*x + B*x*x + (1-A-B);
}
static void grad_poly_tf(float x, const void* ctx, const float P[4], float dfdP[4]) {
const skcms_PolyTF* tf = (const skcms_PolyTF*)ctx;
(void)P;
float D = tf->D;
dfdP[0] = (x*x*x - 1) - (x*x-1)*(D*D*D-1)/(D*D-1);
}
static bool fit_poly_tf(const skcms_Curve* curve, skcms_PolyTF* tf) {
if (curve->table_entries > (uint32_t)INT_MAX) {
return false;
}
const int N = curve->table_entries == 0 ? 256
:(int)curve->table_entries;
// We'll test the quality of our fit by roundtripping through a skcms_TransferFunction,
// either the inverse of the curve itself if it is parametric, or of its approximation if not.
skcms_TransferFunction baseline;
float err;
if (curve->table_entries == 0) {
baseline = curve->parametric;
} else if (!skcms_ApproximateCurve(curve, &baseline, &err)) {
return false;
}
skcms_TransferFunction inv;
if (!skcms_TransferFunction_invert(&baseline, &inv)) {
return false;
}
const float kTolerances[] = { 1.5f / 65535.0f, 1.0f / 512.0f };
for (int t = 0; t < ARRAY_COUNT(kTolerances); t++) {
float f;
const int L = skcms_fit_linear(curve, N, kTolerances[t], &tf->C, &tf->D, &f);
if (f != 0) {
return false;
}
if (tf->D == 1) {
tf->A = 0;
tf->B = 0;
return true;
}
// Start with guess A = 0, i.e. f(x) = x^2, gamma = 2.
float P[4] = {0, 0,0,0};
for (int i = 0; i < 3; i++) {
if (!skcms_gauss_newton_step(skcms_eval_curve, curve,
eval_poly_tf, tf,
grad_poly_tf, tf,
P,
tf->D, 1, N-L)) {
goto NEXT;
}
}
float A = tf->A = P[0],
C = tf->C,
D = tf->D;
tf->B = (C*D - A*(D*D*D - 1) - 1) / (D*D - 1);
for (int i = 0; i < N; i++) {
float x = i * (1.0f/(N-1));
float rt = skcms_TransferFunction_eval(&inv, eval_poly_tf(x, tf, P));
if (!isfinitef_(rt)) {
goto NEXT;
}
const int tol = (i == 0 || i == N-1) ? 0
: N/256;
int ix = (int)((N-1) * rt + 0.5f);
if (abs(i - ix) > tol) {
goto NEXT;
}
}
return true;
NEXT: ;
}
return false;
}
void skcms_OptimizeForSpeed(skcms_ICCProfile* profile) {
for (int i = 0; profile->has_trc && i < 3; i++) {
profile->has_poly_tf[i] = fit_poly_tf(&profile->trc[i], &profile->poly_tf[i]);
}
}