colorimetry: use 4x4 matrices for colorimetry transforms

This is so that offsets can be represented in the matrices and not just
scaled and rotated coordinate systems
This commit is contained in:
Xaver Hugl 2024-01-28 17:22:31 +01:00
parent 4dd1e91bda
commit b1414033ef
7 changed files with 41 additions and 64 deletions

View file

@ -9,7 +9,7 @@ in vec2 texcoord0;
uniform sampler2D src;
uniform float sdrBrightness;
uniform mat3 matrix1;
uniform mat4 toXYZD50;
uniform int Bsize;
uniform sampler2D Bsampler;
@ -39,7 +39,7 @@ void main()
vec4 tex = texture2D(src, texcoord0);
tex.rgb /= max(tex.a, 0.001);
tex.rgb /= sdrBrightness;
tex.rgb = matrix1 * tex.rgb;
tex.rgb = (toXYZD50 * vec4(tex.rgb, 1.0)).rgb;
if (Bsize > 0) {
tex.rgb = sample1DLut(tex.rgb, Bsampler, Bsize);
}

View file

@ -12,7 +12,7 @@ out vec4 fragColor;
uniform sampler2D src;
uniform float sdrBrightness;
uniform mat3 matrix1;
uniform mat4 toXYZD50;
uniform int Bsize;
uniform sampler2D Bsampler;
@ -42,7 +42,7 @@ void main()
vec4 tex = texture(src, texcoord0);
tex.rgb /= max(tex.a, 0.001);
tex.rgb /= sdrBrightness;
tex.rgb = matrix1 * tex.rgb;
tex.rgb = (toXYZD50 * vec4(tex.rgb, 1.0)).rgb;
if (Bsize > 0) {
tex.rgb = sample1DLut(tex.rgb, Bsampler, Bsize);
}

View file

@ -23,7 +23,7 @@ IccShader::IccShader()
m_locations = {
.src = m_shader->uniformLocation("src"),
.sdrBrightness = m_shader->uniformLocation("sdrBrightness"),
.matrix1 = m_shader->uniformLocation("matrix1"),
.toXYZD50 = m_shader->uniformLocation("toXYZD50"),
.bsize = m_shader->uniformLocation("Bsize"),
.bsampler = m_shader->uniformLocation("Bsampler"),
.matrix2 = m_shader->uniformLocation("matrix2"),
@ -45,7 +45,7 @@ static const QVector2D D50 = Colorimetry::xyzToXY(QVector3D(0.9642, 1.0, 0.8249)
bool IccShader::setProfile(const std::shared_ptr<IccProfile> &profile)
{
if (!profile) {
m_matrix1.setToIdentity();
m_toXYZD50.setToIdentity();
m_B.reset();
m_matrix2.setToIdentity();
m_M.reset();
@ -55,14 +55,14 @@ bool IccShader::setProfile(const std::shared_ptr<IccProfile> &profile)
}
if (m_profile != profile) {
const auto vcgt = profile->vcgt();
QMatrix3x3 matrix1;
QMatrix4x4 toXYZD50;
std::unique_ptr<GlLookUpTable> B;
QMatrix4x4 matrix2;
std::unique_ptr<GlLookUpTable> M;
std::unique_ptr<GlLookUpTable3D> C;
std::unique_ptr<GlLookUpTable> A;
if (const IccProfile::BToATagData *tag = profile->BtToATag()) {
matrix1 = Colorimetry::chromaticAdaptationMatrix(profile->colorimetry().white(), D50) * profile->colorimetry().toXYZ();
toXYZD50 = Colorimetry::chromaticAdaptationMatrix(profile->colorimetry().white(), D50) * profile->colorimetry().toXYZ();
if (tag->B) {
const auto sample = [&tag](size_t x) {
const float relativeX = x / double(lutSize - 1);
@ -129,7 +129,7 @@ bool IccShader::setProfile(const std::shared_ptr<IccProfile> &profile)
return false;
}
}
m_matrix1 = matrix1;
m_toXYZD50 = toXYZD50;
m_B = std::move(B);
m_matrix2 = matrix2;
m_M = std::move(M);
@ -150,11 +150,11 @@ void IccShader::setUniforms(const std::shared_ptr<IccProfile> &profile, float sd
// this failing can be silently ignored, it should only happen with GPU resets and gets corrected later
setProfile(profile);
QMatrix3x3 nightColor;
QMatrix4x4 nightColor;
nightColor(0, 0) = channelFactors.x();
nightColor(1, 1) = channelFactors.y();
nightColor(2, 2) = channelFactors.z();
m_shader->setUniform(m_locations.matrix1, m_matrix1 * nightColor);
m_shader->setUniform(m_locations.toXYZD50, m_toXYZD50 * nightColor);
m_shader->setUniform(m_locations.sdrBrightness, sdrBrightness);
glActiveTexture(GL_TEXTURE1);

View file

@ -33,7 +33,7 @@ private:
std::unique_ptr<GLShader> m_shader;
std::shared_ptr<IccProfile> m_profile;
QMatrix3x3 m_matrix1;
QMatrix4x4 m_toXYZD50;
std::unique_ptr<GlLookUpTable> m_B;
QMatrix4x4 m_matrix2;
std::unique_ptr<GlLookUpTable> m_M;
@ -43,7 +43,7 @@ private:
{
int src;
int sdrBrightness;
int matrix1;
int toXYZD50;
int bsize;
int bsampler;
int matrix2;

View file

@ -10,25 +10,9 @@
namespace KWin
{
static QMatrix3x3 inverse(const QMatrix3x3 &m)
static QMatrix4x4 matrixFromColumns(const QVector3D &first, const QVector3D &second, const QVector3D &third)
{
const double determinant = m(0, 0) * (m(1, 1) * m(2, 2) - m(2, 1) * m(1, 2)) - m(0, 1) * (m(1, 0) * m(2, 2) - m(1, 2) * m(2, 0)) + m(0, 2) * (m(1, 0) * m(2, 1) - m(1, 1) * m(2, 0));
QMatrix3x3 ret;
ret(0, 0) = (m(1, 1) * m(2, 2) - m(2, 1) * m(1, 2)) / determinant;
ret(0, 1) = (m(0, 2) * m(2, 1) - m(0, 1) * m(2, 2)) / determinant;
ret(0, 2) = (m(0, 1) * m(1, 2) - m(0, 2) * m(1, 1)) / determinant;
ret(1, 0) = (m(1, 2) * m(2, 0) - m(1, 0) * m(2, 2)) / determinant;
ret(1, 1) = (m(0, 0) * m(2, 2) - m(0, 2) * m(2, 0)) / determinant;
ret(1, 2) = (m(1, 0) * m(0, 2) - m(0, 0) * m(1, 2)) / determinant;
ret(2, 0) = (m(1, 0) * m(2, 1) - m(2, 0) * m(1, 1)) / determinant;
ret(2, 1) = (m(2, 0) * m(0, 1) - m(0, 0) * m(2, 1)) / determinant;
ret(2, 2) = (m(0, 0) * m(1, 1) - m(1, 0) * m(0, 1)) / determinant;
return ret;
}
static QMatrix3x3 matrixFromColumns(const QVector3D &first, const QVector3D &second, const QVector3D &third)
{
QMatrix3x3 ret;
QMatrix4x4 ret;
ret(0, 0) = first.x();
ret(1, 0) = first.y();
ret(2, 0) = first.z();
@ -41,14 +25,6 @@ static QMatrix3x3 matrixFromColumns(const QVector3D &first, const QVector3D &sec
return ret;
}
static QVector3D operator*(const QMatrix3x3 &mat, const QVector3D &v)
{
return QVector3D(
mat(0, 0) * v.x() + mat(0, 1) * v.y() + mat(0, 2) * v.z(),
mat(1, 0) * v.x() + mat(1, 1) * v.y() + mat(1, 2) * v.z(),
mat(2, 0) * v.x() + mat(2, 1) * v.y() + mat(2, 2) * v.z());
}
QVector3D Colorimetry::xyToXYZ(QVector2D xy)
{
return QVector3D(xy.x() / xy.y(), 1, (1 - xy.x() - xy.y()) / xy.y());
@ -60,10 +36,10 @@ QVector2D Colorimetry::xyzToXY(QVector3D xyz)
return QVector2D(xyz.x() / (xyz.x() + xyz.y() + xyz.z()), xyz.y() / (xyz.x() + xyz.y() + xyz.z()));
}
QMatrix3x3 Colorimetry::chromaticAdaptationMatrix(QVector2D sourceWhitepoint, QVector2D destinationWhitepoint)
QMatrix4x4 Colorimetry::chromaticAdaptationMatrix(QVector2D sourceWhitepoint, QVector2D destinationWhitepoint)
{
static const QMatrix3x3 bradford = []() {
QMatrix3x3 ret;
static const QMatrix4x4 bradford = []() {
QMatrix4x4 ret;
ret(0, 0) = 0.8951;
ret(0, 1) = 0.2664;
ret(0, 2) = -0.1614;
@ -75,8 +51,8 @@ QMatrix3x3 Colorimetry::chromaticAdaptationMatrix(QVector2D sourceWhitepoint, QV
ret(2, 2) = 1.0296;
return ret;
}();
static const QMatrix3x3 inverseBradford = []() {
QMatrix3x3 ret;
static const QMatrix4x4 inverseBradford = []() {
QMatrix4x4 ret;
ret(0, 0) = 0.9869929;
ret(0, 1) = -0.1470543;
ret(0, 2) = 0.1599627;
@ -89,19 +65,19 @@ QMatrix3x3 Colorimetry::chromaticAdaptationMatrix(QVector2D sourceWhitepoint, QV
return ret;
}();
if (sourceWhitepoint == destinationWhitepoint) {
return QMatrix3x3{};
return QMatrix4x4{};
}
const QVector3D factors = (bradford * xyToXYZ(destinationWhitepoint)) / (bradford * xyToXYZ(sourceWhitepoint));
QMatrix3x3 adaptation{};
QMatrix4x4 adaptation{};
adaptation(0, 0) = factors.x();
adaptation(1, 1) = factors.y();
adaptation(2, 2) = factors.z();
return inverseBradford * adaptation * bradford;
}
QMatrix3x3 Colorimetry::calculateToXYZMatrix(QVector3D red, QVector3D green, QVector3D blue, QVector3D white)
QMatrix4x4 Colorimetry::calculateToXYZMatrix(QVector3D red, QVector3D green, QVector3D blue, QVector3D white)
{
const auto component_scale = inverse(matrixFromColumns(red, green, blue)) * white;
const auto component_scale = (matrixFromColumns(red, green, blue)).inverted() * white;
return matrixFromColumns(red * component_scale.x(), green * component_scale.y(), blue * component_scale.z());
}
@ -111,7 +87,7 @@ Colorimetry::Colorimetry(QVector2D red, QVector2D green, QVector2D blue, QVector
, m_blue(blue)
, m_white(white)
, m_toXYZ(calculateToXYZMatrix(xyToXYZ(red), xyToXYZ(green), xyToXYZ(blue), xyToXYZ(white)))
, m_fromXYZ(inverse(m_toXYZ))
, m_fromXYZ(m_toXYZ.inverted())
{
}
@ -121,21 +97,21 @@ Colorimetry::Colorimetry(QVector3D red, QVector3D green, QVector3D blue, QVector
, m_blue(xyzToXY(blue))
, m_white(xyzToXY(white))
, m_toXYZ(calculateToXYZMatrix(red, green, blue, white))
, m_fromXYZ(inverse(m_toXYZ))
, m_fromXYZ(m_toXYZ.inverted())
{
}
const QMatrix3x3 &Colorimetry::toXYZ() const
const QMatrix4x4 &Colorimetry::toXYZ() const
{
return m_toXYZ;
}
const QMatrix3x3 &Colorimetry::fromXYZ() const
const QMatrix4x4 &Colorimetry::fromXYZ() const
{
return m_fromXYZ;
}
QMatrix3x3 Colorimetry::toOther(const Colorimetry &other) const
QMatrix4x4 Colorimetry::toOther(const Colorimetry &other) const
{
// rendering intent is relative colorimetric, so adapt to the different whitepoint
return other.fromXYZ() * chromaticAdaptationMatrix(this->white(), other.white()) * toXYZ();

View file

@ -6,7 +6,7 @@
#pragma once
#include <optional>
#include <QMatrix3x3>
#include <QMatrix4x4>
#include <QVector2D>
#include "kwin_export.h"
@ -39,9 +39,9 @@ public:
/**
* @returns a matrix adapting XYZ values from the source whitepoint to the destination whitepoint with the Bradford transform
*/
static QMatrix3x3 chromaticAdaptationMatrix(QVector2D sourceWhitepoint, QVector2D destinationWhitepoint);
static QMatrix4x4 chromaticAdaptationMatrix(QVector2D sourceWhitepoint, QVector2D destinationWhitepoint);
static QMatrix3x3 calculateToXYZMatrix(QVector3D red, QVector3D green, QVector3D blue, QVector3D white);
static QMatrix4x4 calculateToXYZMatrix(QVector3D red, QVector3D green, QVector3D blue, QVector3D white);
explicit Colorimetry(QVector2D red, QVector2D green, QVector2D blue, QVector2D white);
explicit Colorimetry(QVector3D red, QVector3D green, QVector3D blue, QVector3D white);
@ -49,16 +49,16 @@ public:
/**
* @returns a matrix that transforms from the linear RGB representation of colors in this colorimetry to the XYZ representation
*/
const QMatrix3x3 &toXYZ() const;
const QMatrix4x4 &toXYZ() const;
/**
* @returns a matrix that transforms from the XYZ representation to the linear RGB representation of colors in this colorimetry
*/
const QMatrix3x3 &fromXYZ() const;
const QMatrix4x4 &fromXYZ() const;
/**
* @returns a matrix that transforms from linear RGB in this colorimetry to linear RGB in the other colorimetry
* the rendering intent is relative colorimetric
*/
QMatrix3x3 toOther(const Colorimetry &colorimetry) const;
QMatrix4x4 toOther(const Colorimetry &colorimetry) const;
bool operator==(const Colorimetry &other) const;
bool operator==(NamedColorimetry name) const;
/**
@ -76,8 +76,8 @@ private:
QVector2D m_green;
QVector2D m_blue;
QVector2D m_white;
QMatrix3x3 m_toXYZ;
QMatrix3x3 m_fromXYZ;
QMatrix4x4 m_toXYZ;
QMatrix4x4 m_fromXYZ;
};
/**

View file

@ -4,7 +4,7 @@ const int PQ_EOTF = 2;
const int scRGB_EOTF = 3;
const int gamma22_EOTF = 4;
uniform mat3 colorimetryTransform;
uniform mat4 colorimetryTransform;
uniform int sourceNamedTransferFunction;
uniform int destinationNamedTransferFunction;
uniform float sdrBrightness;// in nits
@ -57,7 +57,7 @@ vec3 linearToSrgb(vec3 color) {
vec3 doTonemapping(vec3 color, float maxBrightness) {
// TODO do something better here
return clamp(color, vec3(0.0), vec3(maxBrightness));
return clamp(color.rgb, vec3(0.0), vec3(maxBrightness));
}
vec4 encodingToNits(vec4 color, int sourceTransferFunction) {
@ -81,7 +81,8 @@ vec4 encodingToNits(vec4 color, int sourceTransferFunction) {
vec4 sourceEncodingToNitsInDestinationColorspace(vec4 color) {
color = encodingToNits(color, sourceNamedTransferFunction);
return vec4(doTonemapping(colorimetryTransform * color.rgb, maxHdrBrightness), color.a);
color.rgb = (colorimetryTransform * vec4(color.rgb, 1.0)).rgb;
return vec4(doTonemapping(color.rgb, maxHdrBrightness), color.a);
}
vec4 nitsToEncoding(vec4 color, int destinationTransferFunction) {