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 }