core: introduce icc profile helper

This commit is contained in:
Xaver Hugl 2023-10-04 16:16:29 +02:00
parent 4d9f1453d0
commit 44ae4ba004
6 changed files with 137 additions and 39 deletions

View file

@ -49,6 +49,7 @@ target_sources(kwin PRIVATE
core/graphicsbuffer.cpp
core/graphicsbufferallocator.cpp
core/graphicsbufferview.cpp
core/iccprofile.cpp
core/inputbackend.cpp
core/inputdevice.cpp
core/output.cpp

View file

@ -7,6 +7,7 @@
#include "colordevice.h"
#include "core/colorpipelinestage.h"
#include "core/colortransformation.h"
#include "core/iccprofile.h"
#include "core/output.h"
#include "utils/common.h"
@ -49,7 +50,7 @@ public:
Output *output;
DirtyToneCurves dirtyCurves;
QTimer *updateTimer;
QString profile;
QString profilePath;
uint brightness = 100;
uint temperature = 6500;
@ -57,7 +58,7 @@ public:
QVector3D temperatureFactors = QVector3D(1, 1, 1);
std::unique_ptr<ColorPipelineStage> brightnessStage;
QVector3D brightnessFactors = QVector3D(1, 1, 1);
std::unique_ptr<ColorPipelineStage> calibrationStage;
std::unique_ptr<IccProfile> iccProfile;
std::shared_ptr<ColorTransformation> transformation;
// used if only limited per-channel multiplication is available
@ -78,13 +79,6 @@ void ColorDevicePrivate::rebuildPipeline()
dirtyCurves = DirtyToneCurves();
std::vector<std::unique_ptr<ColorPipelineStage>> stages;
if (calibrationStage) {
if (auto s = calibrationStage->dup()) {
stages.push_back(std::move(s));
} else {
return;
}
}
if (brightnessStage) {
if (auto s = brightnessStage->dup()) {
stages.push_back(std::move(s));
@ -101,6 +95,9 @@ void ColorDevicePrivate::rebuildPipeline()
}
const auto tmp = std::make_shared<ColorTransformation>(std::move(stages));
if (iccProfile && iccProfile->vcgt()) {
tmp->append(iccProfile->vcgt().get());
}
if (tmp->valid()) {
transformation = tmp;
simpleTransformation = brightnessFactors * temperatureFactors;
@ -114,7 +111,7 @@ static qreal interpolate(qreal a, qreal b, qreal blendFactor)
QString ColorDevice::profile() const
{
return d->profile;
return d->profilePath;
}
void ColorDevicePrivate::updateTemperatureToneCurves()
@ -210,32 +207,7 @@ void ColorDevicePrivate::updateBrightnessToneCurves()
void ColorDevicePrivate::updateCalibrationToneCurves()
{
calibrationStage.reset();
if (profile.isNull()) {
return;
}
cmsHPROFILE handle = cmsOpenProfileFromFile(profile.toUtf8(), "r");
if (!handle) {
qCWarning(KWIN_CORE) << "Failed to open color profile file:" << profile;
return;
}
cmsToneCurve **vcgt = static_cast<cmsToneCurve **>(cmsReadTag(handle, cmsSigVcgtTag));
if (!vcgt || !vcgt[0]) {
qCWarning(KWIN_CORE) << "Profile" << profile << "has no VCGT tag";
} else {
// Need to duplicate the VCGT tone curves as they are owned by the profile.
cmsToneCurve *toneCurves[] = {
cmsDupToneCurve(vcgt[0]),
cmsDupToneCurve(vcgt[1]),
cmsDupToneCurve(vcgt[2]),
};
calibrationStage = std::make_unique<ColorPipelineStage>(cmsStageAllocToneCurves(nullptr, 3, toneCurves));
}
cmsCloseProfile(handle);
iccProfile = IccProfile::load(profilePath);
}
ColorDevice::ColorDevice(Output *output, QObject *parent)
@ -306,10 +278,10 @@ void ColorDevice::setTemperature(uint temperature)
void ColorDevice::setProfile(const QString &profile)
{
if (d->profile == profile) {
if (d->profilePath == profile) {
return;
}
d->profile = profile;
d->profilePath = profile;
d->dirtyCurves |= ColorDevicePrivate::DirtyCalibrationToneCurve;
scheduleUpdate();
Q_EMIT profileChanged();

View file

@ -45,6 +45,19 @@ ColorTransformation::~ColorTransformation()
}
}
void ColorTransformation::append(ColorTransformation *transformation)
{
for (auto &stage : transformation->m_stages) {
auto dup = stage->dup();
if (!cmsPipelineInsertStage(m_pipeline, cmsAT_END, dup->stage())) {
qCWarning(KWIN_CORE) << "Failed to insert cmsPipeline stage!";
m_valid = false;
return;
}
m_stages.push_back(std::move(dup));
}
}
bool ColorTransformation::valid() const
{
return m_valid;

View file

@ -28,13 +28,15 @@ public:
ColorTransformation(std::vector<std::unique_ptr<ColorPipelineStage>> &&stages);
~ColorTransformation();
void append(ColorTransformation *transformation);
bool valid() const;
std::tuple<uint16_t, uint16_t, uint16_t> transform(uint16_t r, uint16_t g, uint16_t b) const;
private:
cmsPipeline *const m_pipeline;
const std::vector<std::unique_ptr<ColorPipelineStage>> m_stages;
std::vector<std::unique_ptr<ColorPipelineStage>> m_stages;
bool m_valid = true;
};

75
src/core/iccprofile.cpp Normal file
View file

@ -0,0 +1,75 @@
/*
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "iccprofile.h"
#include "colorlut.h"
#include "colorpipelinestage.h"
#include "colortransformation.h"
#include "utils/common.h"
#include <lcms2.h>
namespace KWin
{
IccProfile::IccProfile(cmsHPROFILE handle, const std::shared_ptr<ColorTransformation> &vcgt)
: m_handle(handle)
, m_vcgt(vcgt)
{
}
IccProfile::~IccProfile()
{
cmsCloseProfile(m_handle);
}
std::shared_ptr<ColorTransformation> IccProfile::vcgt() const
{
return m_vcgt;
}
std::unique_ptr<IccProfile> IccProfile::load(const QString &path)
{
if (path.isEmpty()) {
return nullptr;
}
cmsHPROFILE handle = cmsOpenProfileFromFile(path.toUtf8(), "r");
if (!handle) {
qCWarning(KWIN_CORE) << "Failed to open color profile file:" << path;
return nullptr;
}
if (cmsGetDeviceClass(handle) != cmsSigDisplayClass) {
qCWarning(KWIN_CORE) << "Only Display ICC profiles are supported";
return nullptr;
}
if (cmsGetPCS(handle) != cmsColorSpaceSignature::cmsSigXYZData) {
qCWarning(KWIN_CORE) << "Only ICC profiles with a XYZ connection space are supported";
return nullptr;
}
if (cmsGetColorSpace(handle) != cmsColorSpaceSignature::cmsSigRgbData) {
qCWarning(KWIN_CORE) << "Only ICC profiles with RGB color spaces are supported";
return nullptr;
}
std::shared_ptr<ColorTransformation> vcgt;
cmsToneCurve **vcgtTag = static_cast<cmsToneCurve **>(cmsReadTag(handle, cmsSigVcgtTag));
if (!vcgtTag || !vcgtTag[0]) {
qCWarning(KWIN_CORE) << "Profile" << path << "has no VCGT tag";
} else {
// Need to duplicate the VCGT tone curves as they are owned by the profile.
cmsToneCurve *toneCurves[] = {
cmsDupToneCurve(vcgtTag[0]),
cmsDupToneCurve(vcgtTag[1]),
cmsDupToneCurve(vcgtTag[2]),
};
std::vector<std::unique_ptr<ColorPipelineStage>> stages;
stages.push_back(std::make_unique<ColorPipelineStage>(cmsStageAllocToneCurves(nullptr, 3, toneCurves)));
vcgt = std::make_shared<ColorTransformation>(std::move(stages));
}
return std::make_unique<IccProfile>(handle, vcgt);
}
}

35
src/core/iccprofile.h Normal file
View file

@ -0,0 +1,35 @@
/*
SPDX-FileCopyrightText: 2023 Xaver Hugl <xaver.hugl@gmail.com>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#pragma once
#include "kwin_export.h"
#include <QString>
#include <memory>
typedef void *cmsHPROFILE;
namespace KWin
{
class ColorTransformation;
class KWIN_EXPORT IccProfile
{
public:
explicit IccProfile(cmsHPROFILE handle, const std::shared_ptr<ColorTransformation> &vcgt);
~IccProfile();
std::shared_ptr<ColorTransformation> vcgt() const;
static std::unique_ptr<IccProfile> load(const QString &path);
private:
cmsHPROFILE const m_handle;
const std::shared_ptr<ColorTransformation> m_vcgt;
};
}