File indexing completed on 2024-05-19 16:34:00

0001 /*
0002     SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "colordevice.h"
0008 #include "core/colorpipelinestage.h"
0009 #include "core/colortransformation.h"
0010 #include "core/output.h"
0011 #include "utils/common.h"
0012 
0013 #include "3rdparty/colortemperature.h"
0014 
0015 #include <QTimer>
0016 
0017 #include <lcms2.h>
0018 
0019 namespace KWin
0020 {
0021 
0022 struct CmsDeleter
0023 {
0024     void operator()(cmsToneCurve *toneCurve)
0025     {
0026         if (toneCurve) {
0027             cmsFreeToneCurve(toneCurve);
0028         }
0029     }
0030 };
0031 using UniqueToneCurvePtr = std::unique_ptr<cmsToneCurve, CmsDeleter>;
0032 
0033 class ColorDevicePrivate
0034 {
0035 public:
0036     enum DirtyToneCurveBit {
0037         DirtyTemperatureToneCurve = 0x1,
0038         DirtyBrightnessToneCurve = 0x2,
0039         DirtyCalibrationToneCurve = 0x4,
0040     };
0041     Q_DECLARE_FLAGS(DirtyToneCurves, DirtyToneCurveBit)
0042 
0043     void rebuildPipeline();
0044 
0045     void updateTemperatureToneCurves();
0046     void updateBrightnessToneCurves();
0047     void updateCalibrationToneCurves();
0048 
0049     Output *output;
0050     DirtyToneCurves dirtyCurves;
0051     QTimer *updateTimer;
0052     QString profile;
0053     uint brightness = 100;
0054     uint temperature = 6500;
0055 
0056     std::unique_ptr<ColorPipelineStage> temperatureStage;
0057     QVector3D temperatureFactors = QVector3D(1, 1, 1);
0058     std::unique_ptr<ColorPipelineStage> brightnessStage;
0059     QVector3D brightnessFactors = QVector3D(1, 1, 1);
0060     std::unique_ptr<ColorPipelineStage> calibrationStage;
0061 
0062     std::shared_ptr<ColorTransformation> transformation;
0063     // used if only limited per-channel multiplication is available
0064     QVector3D simpleTransformation = QVector3D(1, 1, 1);
0065 };
0066 
0067 void ColorDevicePrivate::rebuildPipeline()
0068 {
0069     if (dirtyCurves & DirtyCalibrationToneCurve) {
0070         updateCalibrationToneCurves();
0071     }
0072     if (dirtyCurves & DirtyBrightnessToneCurve) {
0073         updateBrightnessToneCurves();
0074     }
0075     if (dirtyCurves & DirtyTemperatureToneCurve) {
0076         updateTemperatureToneCurves();
0077     }
0078     dirtyCurves = DirtyToneCurves();
0079 
0080     std::vector<std::unique_ptr<ColorPipelineStage>> stages;
0081     if (calibrationStage) {
0082         if (auto s = calibrationStage->dup()) {
0083             stages.push_back(std::move(s));
0084         } else {
0085             return;
0086         }
0087     }
0088     if (brightnessStage) {
0089         if (auto s = brightnessStage->dup()) {
0090             stages.push_back(std::move(s));
0091         } else {
0092             return;
0093         }
0094     }
0095     if (temperatureStage) {
0096         if (auto s = temperatureStage->dup()) {
0097             stages.push_back(std::move(s));
0098         } else {
0099             return;
0100         }
0101     }
0102 
0103     const auto tmp = std::make_shared<ColorTransformation>(std::move(stages));
0104     if (tmp->valid()) {
0105         transformation = tmp;
0106         simpleTransformation = brightnessFactors * temperatureFactors;
0107     }
0108 }
0109 
0110 static qreal interpolate(qreal a, qreal b, qreal blendFactor)
0111 {
0112     return (1 - blendFactor) * a + blendFactor * b;
0113 }
0114 
0115 QString ColorDevice::profile() const
0116 {
0117     return d->profile;
0118 }
0119 
0120 void ColorDevicePrivate::updateTemperatureToneCurves()
0121 {
0122     temperatureStage.reset();
0123 
0124     if (temperature == 6500) {
0125         return;
0126     }
0127 
0128     // Note that cmsWhitePointFromTemp() returns a slightly green-ish white point.
0129     const int blackBodyColorIndex = ((temperature - 1000) / 100) * 3;
0130     const qreal blendFactor = (temperature % 100) / 100.0;
0131 
0132     const qreal xWhitePoint = interpolate(blackbodyColor[blackBodyColorIndex + 0],
0133                                           blackbodyColor[blackBodyColorIndex + 3],
0134                                           blendFactor);
0135     const qreal yWhitePoint = interpolate(blackbodyColor[blackBodyColorIndex + 1],
0136                                           blackbodyColor[blackBodyColorIndex + 4],
0137                                           blendFactor);
0138     const qreal zWhitePoint = interpolate(blackbodyColor[blackBodyColorIndex + 2],
0139                                           blackbodyColor[blackBodyColorIndex + 5],
0140                                           blendFactor);
0141 
0142     temperatureFactors = QVector3D(xWhitePoint, yWhitePoint, zWhitePoint);
0143 
0144     const double redCurveParams[] = {1.0, xWhitePoint, 0.0};
0145     const double greenCurveParams[] = {1.0, yWhitePoint, 0.0};
0146     const double blueCurveParams[] = {1.0, zWhitePoint, 0.0};
0147 
0148     UniqueToneCurvePtr redCurve(cmsBuildParametricToneCurve(nullptr, 2, redCurveParams));
0149     if (!redCurve) {
0150         qCWarning(KWIN_CORE) << "Failed to build the temperature tone curve for the red channel";
0151         return;
0152     }
0153     UniqueToneCurvePtr greenCurve(cmsBuildParametricToneCurve(nullptr, 2, greenCurveParams));
0154     if (!greenCurve) {
0155         qCWarning(KWIN_CORE) << "Failed to build the temperature tone curve for the green channel";
0156         return;
0157     }
0158     UniqueToneCurvePtr blueCurve(cmsBuildParametricToneCurve(nullptr, 2, blueCurveParams));
0159     if (!blueCurve) {
0160         qCWarning(KWIN_CORE) << "Failed to build the temperature tone curve for the blue channel";
0161         return;
0162     }
0163 
0164     // The ownership of the tone curves will be moved to the pipeline stage.
0165     cmsToneCurve *toneCurves[] = {redCurve.release(), greenCurve.release(), blueCurve.release()};
0166 
0167     temperatureStage = std::make_unique<ColorPipelineStage>(cmsStageAllocToneCurves(nullptr, 3, toneCurves));
0168     if (!temperatureStage) {
0169         qCWarning(KWIN_CORE) << "Failed to create the color temperature pipeline stage";
0170     }
0171 }
0172 
0173 void ColorDevicePrivate::updateBrightnessToneCurves()
0174 {
0175     brightnessStage.reset();
0176 
0177     if (brightness == 100) {
0178         return;
0179     }
0180 
0181     const double curveParams[] = {1.0, brightness / 100.0, 0.0};
0182     brightnessFactors = QVector3D(brightness / 100.0, brightness / 100.0, brightness / 100.0);
0183 
0184     UniqueToneCurvePtr redCurve(cmsBuildParametricToneCurve(nullptr, 2, curveParams));
0185     if (!redCurve) {
0186         qCWarning(KWIN_CORE) << "Failed to build the brightness tone curve for the red channel";
0187         return;
0188     }
0189 
0190     UniqueToneCurvePtr greenCurve(cmsBuildParametricToneCurve(nullptr, 2, curveParams));
0191     if (!greenCurve) {
0192         qCWarning(KWIN_CORE) << "Failed to build the brightness tone curve for the green channel";
0193         return;
0194     }
0195 
0196     UniqueToneCurvePtr blueCurve(cmsBuildParametricToneCurve(nullptr, 2, curveParams));
0197     if (!blueCurve) {
0198         qCWarning(KWIN_CORE) << "Failed to build the brightness tone curve for the blue channel";
0199         return;
0200     }
0201 
0202     // The ownership of the tone curves will be moved to the pipeline stage.
0203     cmsToneCurve *toneCurves[] = {redCurve.release(), greenCurve.release(), blueCurve.release()};
0204 
0205     brightnessStage = std::make_unique<ColorPipelineStage>(cmsStageAllocToneCurves(nullptr, 3, toneCurves));
0206     if (!brightnessStage) {
0207         qCWarning(KWIN_CORE) << "Failed to create the color brightness pipeline stage";
0208     }
0209 }
0210 
0211 void ColorDevicePrivate::updateCalibrationToneCurves()
0212 {
0213     calibrationStage.reset();
0214 
0215     if (profile.isNull()) {
0216         return;
0217     }
0218 
0219     cmsHPROFILE handle = cmsOpenProfileFromFile(profile.toUtf8(), "r");
0220     if (!handle) {
0221         qCWarning(KWIN_CORE) << "Failed to open color profile file:" << profile;
0222         return;
0223     }
0224 
0225     cmsToneCurve **vcgt = static_cast<cmsToneCurve **>(cmsReadTag(handle, cmsSigVcgtTag));
0226     if (!vcgt || !vcgt[0]) {
0227         qCWarning(KWIN_CORE) << "Profile" << profile << "has no VCGT tag";
0228     } else {
0229         // Need to duplicate the VCGT tone curves as they are owned by the profile.
0230         cmsToneCurve *toneCurves[] = {
0231             cmsDupToneCurve(vcgt[0]),
0232             cmsDupToneCurve(vcgt[1]),
0233             cmsDupToneCurve(vcgt[2]),
0234         };
0235         calibrationStage = std::make_unique<ColorPipelineStage>(cmsStageAllocToneCurves(nullptr, 3, toneCurves));
0236     }
0237 
0238     cmsCloseProfile(handle);
0239 }
0240 
0241 ColorDevice::ColorDevice(Output *output, QObject *parent)
0242     : QObject(parent)
0243     , d(new ColorDevicePrivate)
0244 {
0245     d->updateTimer = new QTimer(this);
0246     d->updateTimer->setSingleShot(true);
0247     connect(d->updateTimer, &QTimer::timeout, this, &ColorDevice::update);
0248     connect(output, &Output::dpmsModeChanged, this, [this, output]() {
0249         if (output->dpmsMode() == Output::DpmsMode::On) {
0250             update();
0251         }
0252     });
0253 
0254     d->output = output;
0255     scheduleUpdate();
0256 }
0257 
0258 ColorDevice::~ColorDevice()
0259 {
0260 }
0261 
0262 Output *ColorDevice::output() const
0263 {
0264     return d->output;
0265 }
0266 
0267 uint ColorDevice::brightness() const
0268 {
0269     return d->brightness;
0270 }
0271 
0272 void ColorDevice::setBrightness(uint brightness)
0273 {
0274     if (brightness > 100) {
0275         qCWarning(KWIN_CORE) << "Got invalid brightness value:" << brightness;
0276         brightness = 100;
0277     }
0278     if (d->brightness == brightness) {
0279         return;
0280     }
0281     d->brightness = brightness;
0282     d->dirtyCurves |= ColorDevicePrivate::DirtyBrightnessToneCurve;
0283     scheduleUpdate();
0284     Q_EMIT brightnessChanged();
0285 }
0286 
0287 uint ColorDevice::temperature() const
0288 {
0289     return d->temperature;
0290 }
0291 
0292 void ColorDevice::setTemperature(uint temperature)
0293 {
0294     if (temperature > 6500) {
0295         qCWarning(KWIN_CORE) << "Got invalid temperature value:" << temperature;
0296         temperature = 6500;
0297     }
0298     if (d->temperature == temperature) {
0299         return;
0300     }
0301     d->temperature = temperature;
0302     d->dirtyCurves |= ColorDevicePrivate::DirtyTemperatureToneCurve;
0303     scheduleUpdate();
0304     Q_EMIT temperatureChanged();
0305 }
0306 
0307 void ColorDevice::setProfile(const QString &profile)
0308 {
0309     if (d->profile == profile) {
0310         return;
0311     }
0312     d->profile = profile;
0313     d->dirtyCurves |= ColorDevicePrivate::DirtyCalibrationToneCurve;
0314     scheduleUpdate();
0315     Q_EMIT profileChanged();
0316 }
0317 
0318 void ColorDevice::update()
0319 {
0320     d->rebuildPipeline();
0321     if (!d->output->setGammaRamp(d->transformation)) {
0322         QMatrix3x3 ctm;
0323         ctm(0, 0) = d->simpleTransformation.x();
0324         ctm(1, 1) = d->simpleTransformation.y();
0325         ctm(2, 2) = d->simpleTransformation.z();
0326         d->output->setCTM(ctm);
0327     }
0328 }
0329 
0330 void ColorDevice::scheduleUpdate()
0331 {
0332     d->updateTimer->start();
0333 }
0334 
0335 } // namespace KWin