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