File indexing completed on 2025-03-09 04:09:22
0001 /* 0002 * KDE. Krita Project. 0003 * 0004 * SPDX-FileCopyrightText: 2021 Deif Lou <ginoba@gmail.com> 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include <QRegularExpression> 0010 0011 #include <kis_dom_utils.h> 0012 0013 #include "KisLevelsFilterConfiguration.h" 0014 0015 KisLevelsFilterConfiguration::KisLevelsFilterConfiguration(int channelCount, qint32 version, KisResourcesInterfaceSP resourcesInterface) 0016 : KisColorTransformationConfiguration(defaultName(), version, resourcesInterface) 0017 { 0018 setChannelCount(channelCount); 0019 setDefaults(); 0020 0021 } 0022 0023 KisLevelsFilterConfiguration::KisLevelsFilterConfiguration(int channelCount, KisResourcesInterfaceSP resourcesInterface) 0024 : KisLevelsFilterConfiguration(channelCount, defaultVersion(), resourcesInterface) 0025 {} 0026 0027 KisLevelsFilterConfiguration::KisLevelsFilterConfiguration(const KisLevelsFilterConfiguration &rhs) 0028 : KisColorTransformationConfiguration(rhs) 0029 , m_transfers(rhs.m_transfers) 0030 , m_lightnessTransfer(rhs.m_lightnessTransfer) 0031 {} 0032 0033 KisFilterConfigurationSP KisLevelsFilterConfiguration::clone() const 0034 { 0035 return new KisLevelsFilterConfiguration(*this); 0036 } 0037 0038 bool KisLevelsFilterConfiguration::isCompatible(const KisPaintDeviceSP dev) const 0039 { 0040 return useLightnessMode() || (int)dev->compositionSourceColorSpace()->channelCount() == channelCount(); 0041 } 0042 0043 const QVector<KisLevelsCurve> KisLevelsFilterConfiguration::levelsCurves() const 0044 { 0045 QVector<KisLevelsCurve> levelsCurves_; 0046 for (qint32 i = 0; i < channelCount(); ++i) { 0047 const QString levelsCurveStr = getString(QString("channel_") + KisDomUtils::toString(i), ""); 0048 levelsCurves_.append(levelsCurveStr.isEmpty() ? KisLevelsCurve() : KisLevelsCurve(levelsCurveStr)); 0049 } 0050 return levelsCurves_; 0051 } 0052 0053 const KisLevelsCurve KisLevelsFilterConfiguration::lightnessLevelsCurve() const 0054 { 0055 const QString levelsCurveStr = getString("lightness", ""); 0056 return levelsCurveStr.isEmpty() ? KisLevelsCurve() : KisLevelsCurve(levelsCurveStr); 0057 } 0058 0059 void KisLevelsFilterConfiguration::setLevelsCurves(const QVector<KisLevelsCurve> &newLevelsCurves) 0060 { 0061 for (int i = 0; i < newLevelsCurves.size(); ++i) { 0062 setProperty(QString("channel_") + KisDomUtils::toString(i), newLevelsCurves[i].toString()); 0063 } 0064 setChannelCount(newLevelsCurves.size()); 0065 updateTransfers(); 0066 } 0067 0068 void KisLevelsFilterConfiguration::setLightnessLevelsCurve(const KisLevelsCurve &newLightnessLevelsCurve) 0069 { 0070 setProperty("lightness", newLightnessLevelsCurve.toString()); 0071 } 0072 0073 void KisLevelsFilterConfiguration::updateTransfers() 0074 { 0075 const QVector<KisLevelsCurve> lc = levelsCurves(); 0076 m_transfers.resize(lc.size()); 0077 for (int i = 0; i < lc.size(); i++) { 0078 m_transfers[i] = lc[i].uint16Transfer(); 0079 } 0080 } 0081 0082 void KisLevelsFilterConfiguration::updateLightnessTransfer() 0083 { 0084 const KisLevelsCurve lightnessLevelsCurve_ = lightnessLevelsCurve(); 0085 0086 m_lightnessTransfer = lightnessLevelsCurve_.uint16Transfer(); 0087 } 0088 0089 const QVector<QVector<quint16>>& KisLevelsFilterConfiguration::transfers() const 0090 { 0091 return m_transfers; 0092 } 0093 0094 const QVector<quint16>& KisLevelsFilterConfiguration::lightnessTransfer() const 0095 { 0096 return m_lightnessTransfer; 0097 } 0098 0099 bool KisLevelsFilterConfiguration::useLightnessMode() const 0100 { 0101 const QString mode = getString("mode", ""); 0102 if (mode == "lightness") { 0103 return true; 0104 } else if (mode == "channels") { 0105 return false; 0106 } 0107 return defaultUseLightnessMode(); 0108 } 0109 0110 bool KisLevelsFilterConfiguration::showLogarithmicHistogram() const 0111 { 0112 const QString mode = getString("histogram_mode", ""); 0113 if (mode == "logarithmic") { 0114 return true; 0115 } else if (mode == "linear") { 0116 return false; 0117 } 0118 return defaultShowLogarithmicHistogram(); 0119 } 0120 0121 void KisLevelsFilterConfiguration::setUseLightnessMode(bool newUseLightnessMode) 0122 { 0123 setProperty("mode", newUseLightnessMode ? "lightness" : "channels"); 0124 } 0125 0126 void KisLevelsFilterConfiguration::setShowLogarithmicHistogram(bool newShowLogarithmicHistogram) 0127 { 0128 setProperty("histogram_mode", newShowLogarithmicHistogram ? "logarithmic" : "linear"); 0129 } 0130 0131 /** 0132 * The purpose of this function is to copy the values of the legacy 0133 * options (levels filter version < 2), that correspond to the lightness 0134 * levels adjustment, to the new and compact "lightness" option which 0135 * is used now. 0136 * Note that the "blackvalue", "whitevalue", "outblackvalue" and "outwhitevalue" 0137 * legacy properties span the range [0, 255] while the values in the "lightness" 0138 * property span the range [0, 1] 0139 */ 0140 void KisLevelsFilterConfiguration::setLightessLevelsCurveFromLegacyValues() 0141 { 0142 const double inputBlackPoint = static_cast<double>(getInt("blackvalue", 0)) / 255.0; 0143 const double inputWhitePoint = static_cast<double>(getInt("whitevalue", 255)) / 255.0; 0144 const double inputGamma = getDouble("gammavalue", 1.0); 0145 const double outputBlackPoint = static_cast<double>(getInt("outblackvalue", 0)) / 255.0; 0146 const double outputWhitePoint = static_cast<double>(getInt("outwhitevalue", 255)) / 255.0; 0147 KisColorTransformationConfiguration::setProperty( 0148 "lightness", 0149 KisLevelsCurve(inputBlackPoint, inputWhitePoint, inputGamma, outputBlackPoint, outputWhitePoint).toString() 0150 ); 0151 } 0152 0153 /** 0154 * The purpose of this function is to copy the values of the new and 0155 * compact "lightness" option, that correspond to the lightness levels 0156 * adjustment, to the legacy options that where used before version 2 of 0157 * the filter. Storing the legacy options as well as the new ones 0158 * improves backwards compatibility of documents. 0159 * Note that the "blackvalue", "whitevalue", "outblackvalue" and "outwhitevalue" 0160 * legacy properties span the range [0, 255] while the values in the "lightness" 0161 * property span the range [0, 1] 0162 */ 0163 void KisLevelsFilterConfiguration::setLegacyValuesFromLightnessLevelsCurve() 0164 { 0165 KisLevelsCurve lightnessLevelsCurve_ = lightnessLevelsCurve(); 0166 KisColorTransformationConfiguration::setProperty("blackvalue", static_cast<int>(qRound(lightnessLevelsCurve_.inputBlackPoint() * 255.0))); 0167 KisColorTransformationConfiguration::setProperty("whitevalue", static_cast<int>(qRound(lightnessLevelsCurve_.inputWhitePoint() * 255.0))); 0168 KisColorTransformationConfiguration::setProperty("gammavalue", lightnessLevelsCurve_.inputGamma()); 0169 KisColorTransformationConfiguration::setProperty("outblackvalue", static_cast<int>(qRound(lightnessLevelsCurve_.outputBlackPoint() * 255.0))); 0170 KisColorTransformationConfiguration::setProperty("outwhitevalue", static_cast<int>(qRound(lightnessLevelsCurve_.outputWhitePoint() * 255.0))); 0171 } 0172 0173 /** 0174 * The options may be changed directly using the "setProperty" method of 0175 * the "KisPropertiesConfiguration". In this case we must intercept the 0176 * action to update the transfer function luts after setting the property. 0177 * if some legacy property is set (lightness levels properties prior to 0178 * version 2) then the legacy properties are copied to the new and 0179 * compact "lightness" property. Conversely, if the "lightness" property 0180 * is set, its values are copied to the legacy properties. 0181 */ 0182 void KisLevelsFilterConfiguration::setProperty(const QString &name, const QVariant &value) 0183 { 0184 KisColorTransformationConfiguration::setProperty(name, value); 0185 0186 if (name == "lightness") { 0187 setLegacyValuesFromLightnessLevelsCurve(); 0188 updateLightnessTransfer(); 0189 } else if (name == "blackvalue" || name == "whitevalue" || name == "gammavalue" || 0190 name == "outblackvalue" || name == "outwhitevalue") { 0191 setLightessLevelsCurveFromLegacyValues(); 0192 updateLightnessTransfer(); 0193 } else if (QRegularExpression("channel_\\d+").match(name).hasMatch()) { 0194 updateTransfers(); 0195 } 0196 } 0197 0198 int KisLevelsFilterConfiguration::channelCount() const 0199 { 0200 return getInt("number_of_channels", 0); 0201 } 0202 0203 void KisLevelsFilterConfiguration::setChannelCount(int newChannelCount) 0204 { 0205 setProperty("number_of_channels", newChannelCount); 0206 } 0207 0208 void KisLevelsFilterConfiguration::fromLegacyXML(const QDomElement& root) 0209 { 0210 fromXML(root); 0211 } 0212 0213 void KisLevelsFilterConfiguration::fromXML(const QDomElement& root) 0214 { 0215 int version; 0216 version = root.attribute("version").toInt(); 0217 0218 QDomElement e = root.firstChild().toElement(); 0219 QString attributeName; 0220 KisLevelsCurve lightnessLevelsCurve; 0221 QVector<KisLevelsCurve> levelsCurves; 0222 bool lightnessMode = defaultUseLightnessMode(); 0223 bool logarithmicHistogram = defaultShowLogarithmicHistogram(); 0224 0225 if (version == 1) { 0226 while (!e.isNull()) { 0227 attributeName = e.attribute("name"); 0228 if (attributeName == "gammavalue") { 0229 const double value = KisDomUtils::toDouble(e.text()); 0230 lightnessLevelsCurve.setInputGamma(value); 0231 } else { 0232 const double value = KisDomUtils::toDouble(e.text()) / 255.0; 0233 if (attributeName == "blackvalue") { 0234 lightnessLevelsCurve.setInputBlackPoint(value); 0235 } else if (attributeName == "whitevalue") { 0236 lightnessLevelsCurve.setInputWhitePoint(value); 0237 } else if (attributeName == "outblackvalue") { 0238 lightnessLevelsCurve.setOutputBlackPoint(value); 0239 } else if (attributeName == "outwhitevalue") { 0240 lightnessLevelsCurve.setOutputWhitePoint(value); 0241 } 0242 } 0243 e = e.nextSiblingElement(); 0244 } 0245 } else if (version == 2) { 0246 int numChannels = 0; 0247 QHash<int, KisLevelsCurve> unsortedLevelsCurves; 0248 KisLevelsCurve levelsCurve; 0249 0250 while (!e.isNull()) { 0251 attributeName = e.attribute("name"); 0252 if (attributeName == "mode") { 0253 lightnessMode = e.text() != "channels"; 0254 } else if (attributeName == "histogram_mode") { 0255 logarithmicHistogram = e.text() == "logarithmic"; 0256 } else if (attributeName == "lightness") { 0257 lightnessLevelsCurve.fromString(e.text()); 0258 } else if (attributeName == "number_of_channels") { 0259 numChannels = e.text().toInt(); 0260 } else { 0261 const QRegularExpression rx("channel_(\\d+)"); 0262 const QRegularExpressionMatch match = rx.match(attributeName); 0263 if (match.hasMatch()) { 0264 const int index = match.captured(1).toInt(); 0265 if (!e.text().isEmpty()) { 0266 levelsCurve.fromString(e.text()); 0267 unsortedLevelsCurves[index] = levelsCurve; 0268 } 0269 } 0270 } 0271 e = e.nextSiblingElement(); 0272 } 0273 0274 for (int i = 0; i < numChannels; ++i) { 0275 if (unsortedLevelsCurves.contains(i)) { 0276 levelsCurves.append(unsortedLevelsCurves[i]); 0277 } else { 0278 levelsCurves.append(defaultLevelsCurve()); 0279 } 0280 } 0281 } 0282 0283 setVersion(defaultVersion()); 0284 setLevelsCurves(levelsCurves); 0285 setLightnessLevelsCurve(lightnessLevelsCurve); 0286 setUseLightnessMode(lightnessMode); 0287 setShowLogarithmicHistogram(logarithmicHistogram); 0288 } 0289 0290 void addParamNode(QDomDocument& doc, 0291 QDomElement& root, 0292 const QString &name, 0293 const QString &value, 0294 bool internal = false) 0295 { 0296 QDomText text = doc.createTextNode(value); 0297 QDomElement t = doc.createElement("param"); 0298 t.setAttribute("name", name); 0299 if (internal) { 0300 t.setAttribute("type", "internal"); 0301 } 0302 t.appendChild(text); 0303 root.appendChild(t); 0304 } 0305 0306 void KisLevelsFilterConfiguration::toXML(QDomDocument& doc, QDomElement& root) const 0307 { 0308 /** 0309 * levels curve param follows this format: 0310 * "input_black_point;input_white_point;input_gamma;output_black_point;output_white_point" 0311 * 0312 * For backwards compatibility, the "blackvalue" etc. values are also saved. 0313 * Those contain the values of the lightness mode levels. 0314 * 0315 * @code 0316 * <params version=2> 0317 * <param name="mode">lightness</param> 0318 * <param name="histogram_mode">logarithmic</param> 0319 * <param name="lightness">0;1;0.4;0;1</param> 0320 * <param name="number_of_channels">3</param> 0321 * <param name="channel_0">0;1;1;0;1</param> 0322 * <param name="channel_1">0.2;0.8;1.2;0.25;0.75</param> 0323 * <param name="channel_2">0;1;0.6;0;1</param> 0324 * 0325 * <param type="internal" name="blackvalue">0</param> 0326 * <param type="internal" name="whitevalue">255</param> 0327 * <param type="internal" name="gammavalue">1</param> 0328 * <param type="internal" name="outblackvalue">0</param> 0329 * <param type="internal" name="outwhitevalue">255</param> 0330 * </params> 0331 * @endcode 0332 */ 0333 0334 root.setAttribute("version", version()); 0335 0336 QDomText text; 0337 QDomElement t; 0338 0339 addParamNode(doc, root, "mode", useLightnessMode() ? "lightness" : "channels"); 0340 addParamNode(doc, root, "histogram_mode", showLogarithmicHistogram() ? "logarithmic" : "linear"); 0341 addParamNode(doc, root, "lightness", lightnessLevelsCurve().toString()); 0342 addParamNode(doc, root, "number_of_channels", KisDomUtils::toString(channelCount())); 0343 0344 const QVector<KisLevelsCurve> levelsCurves_ = levelsCurves(); 0345 for (int i = 0; i < levelsCurves_.size(); ++i) { 0346 const QString name = QString("channel_") + KisDomUtils::toString(i); 0347 const QString value = levelsCurves_[i].toString(); 0348 addParamNode(doc, root, name, value); 0349 } 0350 const KisLevelsCurve lightnessCurve_ = lightnessLevelsCurve(); 0351 addParamNode(doc, root, "blackvalue", KisDomUtils::toString(static_cast<int>(qRound(lightnessCurve_.inputBlackPoint() * 255.0))), true); 0352 addParamNode(doc, root, "whitevalue", KisDomUtils::toString(static_cast<int>(qRound(lightnessCurve_.inputWhitePoint() * 255.0))), true); 0353 addParamNode(doc, root, "gammavalue", KisDomUtils::toString(lightnessCurve_.inputGamma()), true); 0354 addParamNode(doc, root, "outblackvalue", KisDomUtils::toString(static_cast<int>(qRound(lightnessCurve_.outputBlackPoint() * 255.0))), true); 0355 addParamNode(doc, root, "outwhitevalue", KisDomUtils::toString(static_cast<int>(qRound(lightnessCurve_.outputWhitePoint() * 255.0))), true); 0356 } 0357 0358 void KisLevelsFilterConfiguration::setDefaults() 0359 { 0360 setUseLightnessMode(defaultUseLightnessMode()); 0361 setShowLogarithmicHistogram(defaultShowLogarithmicHistogram()); 0362 setLightnessLevelsCurve(defaultLevelsCurve()); 0363 0364 QVector<KisLevelsCurve> levelsCurves_; 0365 for (int i = 0; i < channelCount(); ++i) { 0366 levelsCurves_.append(defaultLevelsCurve()); 0367 } 0368 setLevelsCurves(levelsCurves_); 0369 0370 updateTransfers(); 0371 updateLightnessTransfer(); 0372 }