diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 52e9eb1c87..ea6b8a323a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/colors/colordevice.cpp b/src/colors/colordevice.cpp index 356cd20276..f714b1ce2e 100644 --- a/src/colors/colordevice.cpp +++ b/src/colors/colordevice.cpp @@ -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 brightnessStage; QVector3D brightnessFactors = QVector3D(1, 1, 1); - std::unique_ptr calibrationStage; + std::unique_ptr iccProfile; std::shared_ptr transformation; // used if only limited per-channel multiplication is available @@ -78,13 +79,6 @@ void ColorDevicePrivate::rebuildPipeline() dirtyCurves = DirtyToneCurves(); std::vector> 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(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(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(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(); diff --git a/src/core/colortransformation.cpp b/src/core/colortransformation.cpp index 2e93d5016e..426e179934 100644 --- a/src/core/colortransformation.cpp +++ b/src/core/colortransformation.cpp @@ -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; diff --git a/src/core/colortransformation.h b/src/core/colortransformation.h index 51dd22783a..e0bfc5ed45 100644 --- a/src/core/colortransformation.h +++ b/src/core/colortransformation.h @@ -28,13 +28,15 @@ public: ColorTransformation(std::vector> &&stages); ~ColorTransformation(); + void append(ColorTransformation *transformation); + bool valid() const; std::tuple transform(uint16_t r, uint16_t g, uint16_t b) const; private: cmsPipeline *const m_pipeline; - const std::vector> m_stages; + std::vector> m_stages; bool m_valid = true; }; diff --git a/src/core/iccprofile.cpp b/src/core/iccprofile.cpp new file mode 100644 index 0000000000..eed88d5e40 --- /dev/null +++ b/src/core/iccprofile.cpp @@ -0,0 +1,75 @@ +/* + SPDX-FileCopyrightText: 2023 Xaver Hugl + + 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 + +namespace KWin +{ + +IccProfile::IccProfile(cmsHPROFILE handle, const std::shared_ptr &vcgt) + : m_handle(handle) + , m_vcgt(vcgt) +{ +} + +IccProfile::~IccProfile() +{ + cmsCloseProfile(m_handle); +} + +std::shared_ptr IccProfile::vcgt() const +{ + return m_vcgt; +} + +std::unique_ptr 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 vcgt; + cmsToneCurve **vcgtTag = static_cast(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> stages; + stages.push_back(std::make_unique(cmsStageAllocToneCurves(nullptr, 3, toneCurves))); + vcgt = std::make_shared(std::move(stages)); + } + + return std::make_unique(handle, vcgt); +} + +} diff --git a/src/core/iccprofile.h b/src/core/iccprofile.h new file mode 100644 index 0000000000..be5c919ecc --- /dev/null +++ b/src/core/iccprofile.h @@ -0,0 +1,35 @@ +/* + SPDX-FileCopyrightText: 2023 Xaver Hugl + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#pragma once + +#include "kwin_export.h" + +#include +#include + +typedef void *cmsHPROFILE; + +namespace KWin +{ + +class ColorTransformation; + +class KWIN_EXPORT IccProfile +{ +public: + explicit IccProfile(cmsHPROFILE handle, const std::shared_ptr &vcgt); + ~IccProfile(); + + std::shared_ptr vcgt() const; + + static std::unique_ptr load(const QString &path); + +private: + cmsHPROFILE const m_handle; + const std::shared_ptr m_vcgt; +}; + +}