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 }