blob: d86c2d774ac066c02642805178c81f4103dbacc8 [file]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "skia/ext/skcms_ext.h"
#include <cmath>
#include <numeric>
#include "base/check.h"
#include "third_party/skia/include/core/SkColorSpace.h"
namespace skcms {
namespace {
// Use skcms_TransferFunction_eval and skcms_TransferFunction_invert once they
// produce correct results.
// https://crbug.com/450045076
float TransferFunction_eval(const skcms_TransferFunction& fn, float x) {
const float sign = x < 0 ? -1 : 1;
const float abs_x = std::abs(x);
if (abs_x <= fn.d) {
return sign * (fn.c * abs_x + fn.f);
}
float base = std::max(fn.a * abs_x + fn.b, 0.f);
if (fn.g == 1.f) {
return sign * (base + fn.e);
}
return sign * (std::pow(base, fn.g) + fn.e);
}
// This is a direct copy-paste of skcms_TransferFunction_invert, but with the
// std math functions instead of the inaccurate approximations.
bool TransferFunction_invert(const skcms_TransferFunction& src,
skcms_TransferFunction& inv) {
inv = skcms_TransferFunction({0, 0, 0, 0, 0, 0, 0});
float d_l = src.c * src.d + src.f,
d_r = std::pow(src.a * src.d + src.b, src.g) + src.e;
if (std::abs(d_l - d_r) > 1 / 512.0f) {
return false;
}
inv.d = d_l;
if (inv.d > 0) {
inv.c = 1.0f / src.c;
inv.f = -src.f / src.c;
}
float k = std::pow(src.a, -src.g);
inv.g = 1.0f / src.g;
inv.a = k;
inv.b = -k * src.e;
inv.e = -src.b / src.a;
if (inv.a < 0) {
return false;
}
if (inv.a * inv.d + inv.b < 0) {
inv.b = -inv.a * inv.d;
}
float s = TransferFunction_eval(src, 1.0f);
if (!std::isfinite(s)) {
return false;
}
float sign = s < 0 ? -1.0f : 1.0f;
s *= sign;
if (s < inv.d) {
inv.f = 1.0f - sign * inv.c * s;
} else {
inv.e = 1.0f - sign * std::pow(inv.a * s + inv.b, inv.g);
}
return true;
}
} // namespace
Vector3 Matrix3x3_apply(const skcms_Matrix3x3& m, const Vector3& v) {
return Vector3{{
m.vals[0][0] * v.vals[0] + m.vals[0][1] * v.vals[1] +
m.vals[0][2] * v.vals[2],
m.vals[1][0] * v.vals[0] + m.vals[1][1] * v.vals[1] +
m.vals[1][2] * v.vals[2],
m.vals[2][0] * v.vals[0] + m.vals[2][1] * v.vals[1] +
m.vals[2][2] * v.vals[2],
}};
}
Vector3 Matrix3x3_apply_inverse(const skcms_Matrix3x3& m,
const Vector3& v,
bool* succeeded) {
// Early out for the identity matrix.
if (Equal(m, SkNamedGamut::kXYZ)) {
return v;
}
return Matrix3x3_apply(Matrix3x3_invert(m, succeeded), v);
}
skcms_Matrix3x3 Matrix3x3_invert(const skcms_Matrix3x3& m, bool* succeeded) {
skcms_Matrix3x3 m_inv = {{{1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 0.f, 1.f}}};
bool result = skcms_Matrix3x3_invert(&m, &m_inv);
if (succeeded) {
*succeeded = result;
} else {
CHECK(result);
}
return m_inv;
}
Vector3 TransferFunction_apply(const skcms_TransferFunction& trfn,
const Vector3& v) {
return Vector3{{
TransferFunction_eval(trfn, v.vals[0]),
TransferFunction_eval(trfn, v.vals[1]),
TransferFunction_eval(trfn, v.vals[2]),
}};
}
Vector3 TransferFunction_apply_inverse(const skcms_TransferFunction& trfn,
const Vector3& v,
bool* succeeded) {
// Early out for the identity transfer function.
if (Equal(trfn, SkNamedTransferFn::kLinear)) {
return v;
}
skcms_TransferFunction trfn_inv = SkNamedTransferFn::kLinear;
bool result = TransferFunction_invert(trfn, trfn_inv);
if (succeeded) {
*succeeded = result;
} else {
CHECK(result);
}
return TransferFunction_apply(trfn_inv, v);
}
bool Equal(const skcms_TransferFunction& a, const skcms_TransferFunction& b) {
return a.a == b.a && a.b == b.b && a.c == b.c && a.d == b.d && a.e == b.e &&
a.f == b.f && a.g == b.g;
}
bool Equal(const skcms_Matrix3x3& a, const skcms_Matrix3x3& b) {
return a.vals[0][0] == b.vals[0][0] && a.vals[0][1] == b.vals[0][1] &&
a.vals[0][2] == b.vals[0][2] && a.vals[1][0] == b.vals[1][0] &&
a.vals[1][1] == b.vals[1][1] && a.vals[1][2] == b.vals[1][2] &&
a.vals[2][0] == b.vals[2][0] && a.vals[2][1] == b.vals[2][1] &&
a.vals[2][2] == b.vals[2][2];
}
} // namespace skcms