File indexing completed on 2024-05-19 04:29:29

0001 /*
0002  * SPDX-FileCopyrightText: 2016 Wolthera van Hovell tot Westerflier <griffinvalley@gmail.com>
0003  * SPDX-FileCopyrightText: 2022 Mathias Wein <lynx.mw+kde@gmail.com>
0004  *
0005  * SPDX-License-Identifier: GPL-3.0-or-later
0006  */
0007 
0008 #include "KisVisualColorModel.h"
0009 
0010 #include <QRect>
0011 #include <QVector>
0012 #include <QVector3D>
0013 #include <QVector4D>
0014 #include <QList>
0015 #include <QtMath>
0016 
0017 #include <KSharedConfig>
0018 #include <KConfigGroup>
0019 
0020 #include "KoColorConversions.h"
0021 #include "KoColorDisplayRendererInterface.h"
0022 #include "KoColorProfile.h"
0023 #include "KoChannelInfo.h"
0024 #include "KoColorModelStandardIds.h"
0025 #include "KisColorSelectorConfiguration.h"
0026 #include "kis_signal_compressor.h"
0027 #include "kis_debug.h"
0028 
0029 struct KisVisualColorModel::Private
0030 {
0031     KoColor currentcolor;
0032     const KoColorSpace *currentCS {0};
0033     bool exposureSupported {false};
0034     bool isRGBA {false};
0035     bool isLinear {false};
0036     bool applyGamma {false};
0037     bool allowUpdates {true};
0038     int logicalToMemoryPosition[4]; // map logical channel index to storage index for display
0039     int colorChannelCount {0};
0040     qreal gamma {2.2};
0041     qreal lumaRGB[3] {0.2126, 0.7152, 0.0722};
0042     QVector4D channelValues;
0043     QVector4D channelMaxValues;
0044     ColorModel modelRGB {ColorModel::HSV};
0045     ColorModel model {ColorModel::None};
0046     KisColorSelectorConfiguration acs_config;
0047 };
0048 
0049 KisVisualColorModel::KisVisualColorModel()
0050     : QObject(0)
0051     , m_d(new Private)
0052 {
0053     KConfigGroup cfg =  KSharedConfig::openConfig()->group("advancedColorSelector");
0054     m_d->acs_config = KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", KisColorSelectorConfiguration().toString()));
0055 
0056 }
0057 
0058 KisVisualColorModel::~KisVisualColorModel()
0059 {
0060 }
0061 
0062 void KisVisualColorModel::slotSetColor(const KoColor &c)
0063 {
0064     if (!m_d->allowUpdates) {
0065         return;
0066     }
0067 
0068     if (!m_d->currentCS) {
0069         m_d->currentcolor = c;
0070         slotSetColorSpace(c.colorSpace());
0071     }
0072     else {
0073         m_d->currentcolor = c.convertedTo(m_d->currentCS);
0074         m_d->channelValues = convertKoColorToChannelValues(m_d->currentcolor);
0075         emitChannelValues();
0076     }
0077 }
0078 
0079 void KisVisualColorModel::slotSetColorSpace(const KoColorSpace *cs)
0080 {
0081     if (!m_d->currentCS || *m_d->currentCS != *cs) {
0082         const KoColorSpace *csNew = cs;
0083 
0084         // PQ color space is not very suitable for color picking, substitute with linear one
0085         if (cs->colorModelId() == RGBAColorModelID
0086                 && KoColorSpaceRegistry::instance()->p2020PQProfile()
0087                 && cs->profile()->uniqueId() == KoColorSpaceRegistry::instance()->p2020PQProfile()->uniqueId()) {
0088 
0089             csNew = KoColorSpaceRegistry::instance()->
0090                     colorSpace(RGBAColorModelID.id(), Float32BitsColorDepthID.id(),
0091                                KoColorSpaceRegistry::instance()->p2020G10Profile());
0092         }
0093 
0094 
0095         // TODO: split off non-config related initializations
0096         loadColorSpace(csNew);
0097         m_d->currentcolor = KoColor(csNew);
0098         m_d->channelValues = convertKoColorToChannelValues(m_d->currentcolor);
0099         emit sigColorSpaceChanged();
0100     }
0101 }
0102 
0103 void KisVisualColorModel::slotSetChannelValues(const QVector4D &values)
0104 {
0105     if (!m_d->allowUpdates) {
0106         return;
0107     }
0108 
0109     quint32 changeFlags = 0;
0110     QVector4D newValues(0, 0, 0, 0);
0111     for (int i = 0; i < m_d->colorChannelCount; i++) {
0112         newValues[i] = values[i];
0113         changeFlags |= quint32(values[i] != m_d->channelValues[i]) << i;
0114     }
0115     if (changeFlags != 0) {
0116         m_d->allowUpdates = false;
0117         m_d->channelValues = newValues;
0118         m_d->currentcolor = convertChannelValuesToKoColor(newValues);
0119         emit sigChannelValuesChanged(m_d->channelValues, changeFlags);
0120         emit sigNewColor(m_d->currentcolor);
0121         m_d->allowUpdates = true;
0122     }
0123 }
0124 
0125 KoColor KisVisualColorModel::currentColor() const
0126 {
0127     return m_d->currentcolor;
0128 }
0129 
0130 QVector4D KisVisualColorModel::channelValues() const
0131 {
0132     return m_d->channelValues;
0133 }
0134 
0135 int KisVisualColorModel::colorChannelCount() const
0136 {
0137     return m_d->colorChannelCount;
0138 }
0139 
0140 KisVisualColorModel::ColorModel KisVisualColorModel::colorModel() const
0141 {
0142     return m_d->model;
0143 }
0144 
0145 QVector4D KisVisualColorModel::maxChannelValues() const
0146 {
0147     return m_d->channelMaxValues;
0148 }
0149 
0150 void KisVisualColorModel::setMaxChannelValues(const QVector4D &maxValues)
0151 {
0152     if (maxValues == m_d->channelMaxValues) {
0153         return;
0154     }
0155     m_d->channelMaxValues = maxValues;
0156     if (m_d->exposureSupported) {
0157         // need to re-scale our normalized channel values on exposure changes:
0158         m_d->channelValues = convertKoColorToChannelValues(m_d->currentcolor);
0159         emitChannelValues();
0160     }
0161 }
0162 
0163 void KisVisualColorModel::copyState(const KisVisualColorModel &other)
0164 {
0165     m_d->channelMaxValues = other.m_d->channelMaxValues;
0166     setRGBColorModel(other.m_d->modelRGB);
0167     if (other.colorSpace()) {
0168         slotSetColorSpace(other.colorSpace());
0169         slotSetChannelValues(other.channelValues());
0170     }
0171 }
0172 
0173 void KisVisualColorModel::setRGBColorModel(KisVisualColorModel::ColorModel model)
0174 {
0175     if (model == m_d->modelRGB) {
0176         return;
0177     }
0178     if (model >= ColorModel::HSV && model <= ColorModel::HSY) {
0179         m_d->modelRGB = model;
0180         if (m_d->isRGBA) {
0181             m_d->model = model;
0182             m_d->applyGamma = (m_d->isLinear && m_d->modelRGB != ColorModel::HSY);
0183             emit sigColorModelChanged();
0184             m_d->channelValues = convertKoColorToChannelValues(m_d->currentcolor);
0185             emitChannelValues();
0186         }
0187     }
0188 }
0189 
0190 const KoColorSpace *KisVisualColorModel::colorSpace() const
0191 {
0192     return m_d->currentCS;
0193 }
0194 
0195 bool KisVisualColorModel::isHSXModel() const
0196 {
0197     return (m_d->model >= ColorModel::HSV && m_d->model <= ColorModel::HSY);
0198 }
0199 
0200 bool KisVisualColorModel::supportsExposure() const
0201 {
0202     return (m_d->exposureSupported);
0203 }
0204 
0205 KoColor KisVisualColorModel::convertChannelValuesToKoColor(const QVector4D &values) const
0206 {
0207     KoColor c(m_d->currentCS);
0208     QVector4D baseValues(values);
0209     QVector<float> channelVec(c.colorSpace()->channelCount());
0210     channelVec.fill(1.0);
0211 
0212     if (m_d->model != ColorModel::Channel && m_d->isRGBA) {
0213 
0214         if (m_d->model == ColorModel::HSV) {
0215             HSVToRGB(values.x()*360, values.y(), values.z(), &baseValues[0], &baseValues[1], &baseValues[2]);
0216         }
0217         else if (m_d->model == ColorModel::HSL) {
0218             HSLToRGB(values.x()*360, values.y(), values.z(), &baseValues[0], &baseValues[1], &baseValues[2]);
0219         }
0220         else if (m_d->model == ColorModel::HSI) {
0221             // why suddenly qreal?
0222             qreal temp[3];
0223             HSIToRGB(values.x(), values.y(), values.z(), &temp[0], &temp[1], &temp[2]);
0224             baseValues.setX(temp[0]);
0225             baseValues.setY(temp[1]);
0226             baseValues.setZ(temp[2]);
0227         }
0228         else /*if (m_d->model == ColorModel::HSY)*/ {
0229             qreal temp[3];
0230             qreal Y = pow(values.z(), m_d->gamma);
0231             HSYToRGB(values.x(), values.y(), Y, &temp[0], &temp[1], &temp[2],
0232                     m_d->lumaRGB[0], m_d->lumaRGB[1], m_d->lumaRGB[2]);
0233             baseValues.setX(temp[0]);
0234             baseValues.setY(temp[1]);
0235             baseValues.setZ(temp[2]);
0236             if (!m_d->isLinear) {
0237                 // Note: not all profiles define a TRC necessary for (de-)linearization,
0238                 // substituting with a linear profiles would be better
0239                 QVector<qreal> tempVec({baseValues[0], baseValues[1], baseValues[2]});
0240                 if (m_d->exposureSupported) {
0241                     m_d->currentCS->profile()->delinearizeFloatValue(tempVec);
0242                 }
0243                 else {
0244                     m_d->currentCS->profile()->delinearizeFloatValueFast(tempVec);
0245                 }
0246                 baseValues = QVector4D(tempVec[0], tempVec[1], tempVec[2], 0);
0247             }
0248         }
0249         if (m_d->applyGamma) {
0250             for (int i=0; i<3; i++) {
0251                 baseValues[i] = pow(baseValues[i], 2.2);
0252             }
0253         }
0254     }
0255 
0256     if (m_d->exposureSupported) {
0257         baseValues *= m_d->channelMaxValues;
0258     }
0259 
0260     for (int i=0; i<m_d->colorChannelCount; i++) {
0261         channelVec[m_d->logicalToMemoryPosition[i]] = baseValues[i];
0262     }
0263 
0264     c.colorSpace()->fromNormalisedChannelsValue(c.data(), channelVec);
0265 
0266     return c;
0267 
0268 }
0269 
0270 QVector4D KisVisualColorModel::convertKoColorToChannelValues(KoColor c) const
0271 {
0272     if (c.colorSpace() != m_d->currentCS) {
0273         c.convertTo(m_d->currentCS);
0274     }
0275     QVector<float> channelVec (c.colorSpace()->channelCount());
0276     channelVec.fill(1.0);
0277     m_d->currentCS->normalisedChannelsValue(c.data(), channelVec);
0278     QVector4D channelValuesDisplay(0, 0, 0, 0), coordinates(0, 0, 0, 0);
0279 
0280     for (int i =0; i<m_d->colorChannelCount; i++) {
0281         channelValuesDisplay[i] = channelVec[m_d->logicalToMemoryPosition[i]];
0282     }
0283 
0284     if (m_d->exposureSupported) {
0285         channelValuesDisplay /= m_d->channelMaxValues;
0286     }
0287     if (m_d->model != ColorModel::Channel && m_d->isRGBA) {
0288         if (m_d->applyGamma) {
0289             for (int i=0; i<3; i++) {
0290                 channelValuesDisplay[i] = pow(channelValuesDisplay[i], 1/2.2);
0291             }
0292         }
0293         if (m_d->model == ColorModel::HSV) {
0294             QVector3D hsv;
0295             RGBToHSV(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsv[0], &hsv[1], &hsv[2]);
0296             hsv[0] /= 360;
0297             coordinates = QVector4D(hsv, 0.f);
0298         } else if (m_d->model == ColorModel::HSL) {
0299             QVector3D hsl;
0300             RGBToHSL(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsl[0], &hsl[1], &hsl[2]);
0301             hsl[0] /= 360;
0302             coordinates = QVector4D(hsl, 0.f);
0303         } else if (m_d->model == ColorModel::HSI) {
0304             qreal hsi[3];
0305             RGBToHSI(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsi[0], &hsi[1], &hsi[2]);
0306             coordinates = QVector4D(hsi[0], hsi[1], hsi[2], 0.f);
0307         } else if (m_d->model == ColorModel::HSY) {
0308             if (!m_d->isLinear) {
0309                 // Note: not all profiles define a TRC necessary for (de-)linearization,
0310                 // substituting with a linear profiles would be better
0311                 QVector<qreal> temp({channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2]});
0312                 m_d->currentCS->profile()->linearizeFloatValue(temp);
0313                 channelValuesDisplay = QVector4D(temp[0], temp[1], temp[2], 0);
0314             }
0315             qreal hsy[3];
0316             RGBToHSY(channelValuesDisplay[0], channelValuesDisplay[1], channelValuesDisplay[2], &hsy[0], &hsy[1], &hsy[2],
0317                      m_d->lumaRGB[0], m_d->lumaRGB[1], m_d->lumaRGB[2]);
0318             hsy[2] = pow(hsy[2], 1/m_d->gamma);
0319             coordinates = QVector4D(hsy[0], hsy[1], hsy[2], 0.f);
0320         }
0321         // if we couldn't determine a hue, keep last value
0322         if (coordinates[0] < 0) {
0323             coordinates[0] = m_d->channelValues[0];
0324         }
0325         for (int i=0; i<3; i++) {
0326             coordinates[i] = qBound(0.f, coordinates[i], 1.f);
0327         }
0328     } else {
0329         for (int i=0; i<4; i++) {
0330             coordinates[i] = qBound(0.f, channelValuesDisplay[i], 1.f);
0331         }
0332     }
0333     return coordinates;
0334 }
0335 
0336 void KisVisualColorModel::slotLoadACSConfig()
0337 {
0338     KConfigGroup cfg =  KSharedConfig::openConfig()->group("advancedColorSelector");
0339     m_d->acs_config = KisColorSelectorConfiguration::fromString(cfg.readEntry("colorSelectorConfiguration", KisColorSelectorConfiguration().toString()));
0340 
0341     m_d->gamma = cfg.readEntry("gamma", 2.2);
0342 
0343     KisVisualColorModel::ColorModel RGB_model = KisVisualColorModel::HSV;
0344 
0345     switch(m_d->acs_config.mainTypeParameter) {
0346 
0347     case KisColorSelectorConfiguration::SV:
0348     case KisColorSelectorConfiguration::SV2:
0349     case KisColorSelectorConfiguration::hsvSH:
0350     case KisColorSelectorConfiguration::VH:
0351         RGB_model = KisVisualColorModel::HSV;
0352         break;
0353 
0354     case KisColorSelectorConfiguration::SL:
0355     case KisColorSelectorConfiguration::hslSH:
0356     case KisColorSelectorConfiguration::LH:
0357         RGB_model = KisVisualColorModel::HSL;
0358         break;
0359 
0360     case KisColorSelectorConfiguration::SI:
0361     case KisColorSelectorConfiguration::hsiSH:
0362     case KisColorSelectorConfiguration::IH:
0363         RGB_model = KisVisualColorModel::HSI;
0364         break;
0365 
0366     case KisColorSelectorConfiguration::SY:
0367     case KisColorSelectorConfiguration::hsySH:
0368     case KisColorSelectorConfiguration::YH:
0369         RGB_model = KisVisualColorModel::HSY;
0370         break;
0371 
0372     default:
0373         KIS_SAFE_ASSERT_RECOVER_NOOP(false);
0374     }
0375     if (m_d->acs_config.mainType == KisColorSelectorConfiguration::Triangle) {
0376         //Triangle only really works in HSV mode.
0377         RGB_model = KisVisualColorModel::HSV;
0378     }
0379 
0380     setRGBColorModel(RGB_model);
0381 }
0382 
0383 void KisVisualColorModel::loadColorSpace(const KoColorSpace *cs)
0384 {
0385     const QList<KoChannelInfo *> channelList = cs->channels();
0386     int cCount = 0;
0387 
0388     for (int i = 0; i < channelList.size(); i++) {
0389         const KoChannelInfo *channel = channelList.at(i);
0390         if (channel->channelType() != KoChannelInfo::ALPHA) {
0391             quint32 logical = channel->displayPosition();
0392             if (logical > cs->alphaPos()) {
0393                 --logical;
0394             }
0395             m_d->logicalToMemoryPosition[logical] = i;
0396             m_d->channelMaxValues[logical] = channel->getUIMax();
0397             ++cCount;
0398         }
0399     }
0400     KIS_ASSERT_X(cCount < 5, "", "unsupported channel count!");
0401 
0402     m_d->colorChannelCount = cCount;
0403 
0404     if ((cs->colorDepthId() == Float16BitsColorDepthID
0405         || cs->colorDepthId() == Float32BitsColorDepthID
0406         || cs->colorDepthId() == Float64BitsColorDepthID)
0407         && cs->colorModelId() != LABAColorModelID
0408         && cs->colorModelId() != CMYKAColorModelID) {
0409         m_d->exposureSupported = true;
0410     } else {
0411         m_d->exposureSupported = false;
0412     }
0413     m_d->isRGBA = (cs->colorModelId() == RGBAColorModelID);
0414 
0415     const KoColorProfile *profile = cs->profile();
0416     m_d->isLinear = (profile && profile->isLinear());
0417 
0418     if (m_d->isRGBA) {
0419         m_d->applyGamma = (m_d->isLinear && m_d->modelRGB != ColorModel::HSY);
0420         // Note: only profiles that define colorants will give precise luma coefficients.
0421         // Maybe using the explicitly set values of the Advanced Color Selector is better?
0422         QVector <qreal> luma = cs->lumaCoefficients();
0423         memcpy(m_d->lumaRGB, luma.constData(), 3*sizeof(qreal));
0424         m_d->model = m_d->modelRGB;
0425     } else {
0426         m_d->model = KisVisualColorModel::Channel;
0427     }
0428 
0429     const KoColorSpace *oldCS = m_d->currentCS;
0430     m_d->currentCS = cs;
0431 
0432     if (!oldCS || (oldCS->colorModelId() != cs->colorModelId())) {
0433         emit sigColorModelChanged();
0434     }
0435 }
0436 
0437 void KisVisualColorModel::emitChannelValues()
0438 {
0439     bool updatesAllowed = m_d->allowUpdates;
0440     m_d->allowUpdates = false;
0441     emit sigChannelValuesChanged(m_d->channelValues, (1u << m_d->colorChannelCount) - 1);
0442     m_d->allowUpdates = updatesAllowed;
0443 }