File indexing completed on 2024-06-16 04:15:58
0001 /* 0002 * SPDX-FileCopyrightText: 2002 Patrick Julien <freak@codepimps.org> 0003 * SPDX-FileCopyrightText: 2005-2006 C. Boemann <cbo@boemann.dk> 0004 * SPDX-FileCopyrightText: 2004, 2006-2007 Cyrille Berger <cberger@cberger.net> 0005 * SPDX-FileCopyrightText: 2020 L. E. Segovia <amy@amyspark.me> 0006 * 0007 * SPDX-License-Identifier: LGPL-2.1-or-later 0008 */ 0009 0010 #ifndef KOLCMSCOLORSPACE_H_ 0011 #define KOLCMSCOLORSPACE_H_ 0012 0013 #include <array> 0014 #include <kis_lockless_stack.h> 0015 #include <KoColorSpaceAbstract.h> 0016 0017 #include "colorprofiles/LcmsColorProfileContainer.h" 0018 #include "kis_assert.h" 0019 0020 0021 class LcmsColorProfileContainer; 0022 0023 class KoLcmsInfo 0024 { 0025 struct Private { 0026 cmsUInt32Number cmType; // The colorspace type as defined by littlecms 0027 cmsColorSpaceSignature colorSpaceSignature; // The colorspace signature as defined in icm/icc files 0028 }; 0029 0030 public: 0031 0032 KoLcmsInfo(cmsUInt32Number cmType, cmsColorSpaceSignature colorSpaceSignature) 0033 : d(new Private) 0034 { 0035 d->cmType = cmType; 0036 d->colorSpaceSignature = colorSpaceSignature; 0037 } 0038 0039 virtual ~KoLcmsInfo() 0040 { 0041 delete d; 0042 } 0043 0044 virtual quint32 colorSpaceType() const 0045 { 0046 return d->cmType; 0047 } 0048 0049 virtual cmsColorSpaceSignature colorSpaceSignature() const 0050 { 0051 return d->colorSpaceSignature; 0052 } 0053 0054 private: 0055 Private *const d; 0056 }; 0057 0058 struct KoLcmsDefaultTransformations { 0059 cmsHTRANSFORM toRGB; 0060 cmsHTRANSFORM toRGB16; 0061 cmsHTRANSFORM fromRGB; 0062 static cmsHPROFILE s_RGBProfile; 0063 static QMap< QString, QMap< LcmsColorProfileContainer *, KoLcmsDefaultTransformations * > > s_transformations; 0064 }; 0065 0066 /** 0067 * This is the base class for all colorspaces that are based on the lcms library, for instance 0068 * RGB 8bits and 16bits, CMYK 8bits and 16bits, LAB... 0069 */ 0070 template<class _CSTraits> 0071 class LcmsColorSpace : public KoColorSpaceAbstract<_CSTraits>, public KoLcmsInfo 0072 { 0073 struct KoLcmsColorTransformation : public KoColorTransformation { 0074 0075 KoLcmsColorTransformation(const KoColorSpace *colorSpace) 0076 : KoColorTransformation() 0077 , m_colorSpace(colorSpace) 0078 { 0079 csProfile = 0; 0080 cmstransform = 0; 0081 cmsAlphaTransform = 0; 0082 profiles[0] = 0; 0083 profiles[1] = 0; 0084 profiles[2] = 0; 0085 } 0086 0087 ~KoLcmsColorTransformation() override 0088 { 0089 0090 if (cmstransform) { 0091 cmsDeleteTransform(cmstransform); 0092 } 0093 if (profiles[0] && profiles[0] != csProfile) { 0094 cmsCloseProfile(profiles[0]); 0095 } 0096 if (profiles[1] && profiles[1] != csProfile) { 0097 cmsCloseProfile(profiles[1]); 0098 } 0099 if (profiles[2] && profiles[2] != csProfile) { 0100 cmsCloseProfile(profiles[2]); 0101 } 0102 } 0103 0104 void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override 0105 { 0106 cmsDoTransform(cmstransform, const_cast<quint8 *>(src), dst, nPixels); 0107 0108 qint32 numPixels = nPixels; 0109 qint32 pixelSize = m_colorSpace->pixelSize(); 0110 int index = 0; 0111 0112 if (cmsAlphaTransform) { 0113 float *alpha = new float[nPixels]; 0114 float *dstalpha = new float[nPixels]; 0115 0116 while (index < nPixels) { 0117 alpha[index] = m_colorSpace->opacityF(src); 0118 src += pixelSize; 0119 index++; 0120 } 0121 0122 cmsDoTransform(cmsAlphaTransform, const_cast<float *>(alpha), static_cast<float *>(dstalpha), nPixels); 0123 for (int i = 0; i < numPixels; i++) { 0124 m_colorSpace->setOpacity(dst, dstalpha[i], 1); 0125 dst += pixelSize; 0126 } 0127 0128 delete [] alpha; 0129 delete [] dstalpha; 0130 } else { 0131 while (numPixels > 0) { 0132 qreal alpha = m_colorSpace->opacityF(src); 0133 m_colorSpace->setOpacity(dst, alpha, 1); 0134 src += pixelSize; 0135 dst += pixelSize; 0136 numPixels--; 0137 } 0138 } 0139 } 0140 0141 const KoColorSpace *m_colorSpace; 0142 cmsHPROFILE csProfile; 0143 cmsHPROFILE profiles[3]; 0144 cmsHTRANSFORM cmstransform; 0145 cmsHTRANSFORM cmsAlphaTransform; 0146 }; 0147 0148 struct KisLcmsLastTransformation { 0149 cmsHPROFILE profile = nullptr; // Last used profile to transform to/from RGB 0150 cmsHTRANSFORM transform = nullptr; // Last used transform to/from RGB 0151 0152 ~KisLcmsLastTransformation() 0153 { 0154 if (transform) 0155 cmsDeleteTransform(transform); 0156 } 0157 }; 0158 0159 typedef QSharedPointer<KisLcmsLastTransformation> KisLcmsLastTransformationSP; 0160 0161 typedef KisLocklessStack<KisLcmsLastTransformationSP> KisLcmsTransformationStack; 0162 0163 struct Private { 0164 KoLcmsDefaultTransformations *defaultTransformations; 0165 0166 KisLcmsTransformationStack fromRGBCachedTransformations; // Last used transforms 0167 KisLcmsTransformationStack toRGBCachedTransformations; // Last used transforms 0168 KisLcmsTransformationStack toRGB16CachedTransformations; // Last used transforms 0169 0170 LcmsColorProfileContainer *profile; 0171 KoColorProfile *colorProfile; 0172 }; 0173 0174 protected: 0175 0176 LcmsColorSpace(const QString &id, 0177 const QString &name, 0178 cmsUInt32Number cmType, 0179 cmsColorSpaceSignature colorSpaceSignature, 0180 KoColorProfile *p) 0181 : KoColorSpaceAbstract<_CSTraits>(id, name) 0182 , KoLcmsInfo(cmType, colorSpaceSignature) 0183 , d(new Private()) 0184 { 0185 Q_ASSERT(p); // No profile means the lcms color space can't work 0186 Q_ASSERT(profileIsCompatible(p)); 0187 d->profile = asLcmsProfile(p); 0188 Q_ASSERT(d->profile); 0189 d->colorProfile = p; 0190 d->defaultTransformations = 0; 0191 } 0192 0193 ~LcmsColorSpace() override 0194 { 0195 delete d->colorProfile; 0196 delete d->defaultTransformations; 0197 delete d; 0198 } 0199 0200 void init() 0201 { 0202 KIS_ASSERT(d->profile); 0203 0204 if (KoLcmsDefaultTransformations::s_RGBProfile == 0) { 0205 KoLcmsDefaultTransformations::s_RGBProfile = cmsCreate_sRGBProfile(); 0206 } 0207 d->defaultTransformations = KoLcmsDefaultTransformations::s_transformations[this->id()][ d->profile]; 0208 if (!d->defaultTransformations) { 0209 KoColorConversionTransformation::ConversionFlags conversionFlags = KoColorConversionTransformation::internalConversionFlags(); 0210 d->defaultTransformations = new KoLcmsDefaultTransformations; 0211 d->defaultTransformations->fromRGB = cmsCreateTransform(KoLcmsDefaultTransformations::s_RGBProfile, 0212 TYPE_BGR_8, 0213 d->profile->lcmsProfile(), 0214 this->colorSpaceType(), 0215 KoColorConversionTransformation::internalRenderingIntent(), 0216 conversionFlags); 0217 KIS_SAFE_ASSERT_RECOVER_NOOP(d->defaultTransformations->fromRGB || !d->colorProfile->isSuitableForOutput()); 0218 0219 // LCMS has a too optimistic optimization when transforming from linear color spaces 0220 if (d->profile->isLinear()) { 0221 conversionFlags |= KoColorConversionTransformation::NoOptimization; 0222 } 0223 0224 d->defaultTransformations->toRGB = cmsCreateTransform(d->profile->lcmsProfile(), 0225 this->colorSpaceType(), 0226 KoLcmsDefaultTransformations::s_RGBProfile, 0227 TYPE_BGR_8, 0228 KoColorConversionTransformation::internalRenderingIntent(), 0229 conversionFlags); 0230 KIS_SAFE_ASSERT_RECOVER_NOOP(d->defaultTransformations->toRGB); 0231 0232 d->defaultTransformations->toRGB16 = cmsCreateTransform(d->profile->lcmsProfile(), 0233 this->colorSpaceType(), 0234 KoLcmsDefaultTransformations::s_RGBProfile, 0235 TYPE_BGR_16, 0236 KoColorConversionTransformation::internalRenderingIntent(), 0237 conversionFlags); 0238 KIS_SAFE_ASSERT_RECOVER_NOOP(d->defaultTransformations->toRGB16); 0239 0240 KoLcmsDefaultTransformations::s_transformations[ this->id()][ d->profile ] = d->defaultTransformations; 0241 } 0242 } 0243 0244 public: 0245 0246 bool hasHighDynamicRange() const override 0247 { 0248 return false; 0249 } 0250 0251 const KoColorProfile *profile() const override 0252 { 0253 return d->colorProfile; 0254 } 0255 0256 bool profileIsCompatible(const KoColorProfile *profile) const override 0257 { 0258 const IccColorProfile *p = dynamic_cast<const IccColorProfile *>(profile); 0259 return (p && p->asLcms()->colorSpaceSignature() == colorSpaceSignature()); 0260 } 0261 0262 void fromQColor(const QColor &color, quint8 *dst) const override 0263 { 0264 std::array<quint8, 3> qcolordata; 0265 0266 qcolordata[2] = static_cast<quint8>(color.red()); 0267 qcolordata[1] = static_cast<quint8>(color.green()); 0268 qcolordata[0] = static_cast<quint8>(color.blue()); 0269 0270 // Default sRGB 0271 KIS_ASSERT(d->defaultTransformations && d->defaultTransformations->fromRGB); 0272 cmsDoTransform(d->defaultTransformations->fromRGB, qcolordata.data(), dst, 1); 0273 0274 this->setOpacity(dst, static_cast<quint8>(color.alpha()), 1); 0275 } 0276 0277 void toQColor(const quint8 *src, QColor *color) const override 0278 { 0279 std::array<quint8, 3> qcolordata; 0280 0281 // Default sRGB transform 0282 KIS_ASSERT(d->defaultTransformations && d->defaultTransformations->toRGB); 0283 cmsDoTransform(d->defaultTransformations->toRGB, src, qcolordata.data(), 1); 0284 0285 color->setRgb(qcolordata[2], qcolordata[1], qcolordata[0]); 0286 color->setAlpha(this->opacityU8(src)); 0287 } 0288 0289 void toQColor16(const quint8 *src, QColor *color) const override 0290 { 0291 std::array<quint16, 3> qcolordata; 0292 0293 // Default sRGB transform 0294 Q_ASSERT(d->defaultTransformations && d->defaultTransformations->toRGB16); 0295 cmsDoTransform(d->defaultTransformations->toRGB16, src, qcolordata.data(), 1); 0296 0297 color->setRgba64(QRgba64::fromRgba64(qcolordata[2], qcolordata[1], qcolordata[0], 0x0000)); 0298 color->setAlpha(this->opacityU8(src)); 0299 } 0300 0301 KoColorTransformation *createBrightnessContrastAdjustment(const quint16 *transferValues) const override 0302 { 0303 if (!d->profile) { 0304 return 0; 0305 } 0306 0307 cmsToneCurve *transferFunctions[3]; 0308 transferFunctions[0] = cmsBuildTabulatedToneCurve16(0, 256, transferValues); 0309 transferFunctions[1] = cmsBuildGamma(0, 1.0); 0310 transferFunctions[2] = cmsBuildGamma(0, 1.0); 0311 0312 KoLcmsColorTransformation *adj = new KoLcmsColorTransformation(this); 0313 adj->profiles[1] = cmsCreateLinearizationDeviceLink(cmsSigLabData, transferFunctions); 0314 cmsSetDeviceClass(adj->profiles[1], cmsSigAbstractClass); 0315 0316 adj->profiles[0] = d->profile->lcmsProfile(); 0317 adj->profiles[2] = d->profile->lcmsProfile(); 0318 adj->cmstransform = cmsCreateMultiprofileTransform(adj->profiles, 3, this->colorSpaceType(), this->colorSpaceType(), 0319 KoColorConversionTransformation::adjustmentRenderingIntent(), 0320 KoColorConversionTransformation::adjustmentConversionFlags()); 0321 adj->csProfile = d->profile->lcmsProfile(); 0322 return adj; 0323 } 0324 0325 KoColorTransformation *createPerChannelAdjustment(const quint16 *const *transferValues) const override 0326 { 0327 if (!d->profile) { 0328 return 0; 0329 } 0330 0331 cmsToneCurve **transferFunctions = new cmsToneCurve*[ this->colorChannelCount()]; 0332 0333 for (uint ch = 0; ch < this->colorChannelCount(); ch++) { 0334 transferFunctions[ch] = transferValues[ch] ? 0335 cmsBuildTabulatedToneCurve16(0, 256, transferValues[ch]) : 0336 cmsBuildGamma(0, 1.0); 0337 } 0338 0339 cmsToneCurve **alphaTransferFunctions = new cmsToneCurve*[1]; 0340 alphaTransferFunctions[0] = transferValues[this->colorChannelCount()] ? 0341 cmsBuildTabulatedToneCurve16(0, 256, transferValues[this->colorChannelCount()]) : 0342 cmsBuildGamma(0, 1.0); 0343 0344 KoLcmsColorTransformation *adj = new KoLcmsColorTransformation(this); 0345 adj->profiles[0] = cmsCreateLinearizationDeviceLink(this->colorSpaceSignature(), transferFunctions); 0346 adj->profiles[1] = cmsCreateLinearizationDeviceLink(cmsSigGrayData, alphaTransferFunctions); 0347 adj->profiles[2] = 0; 0348 adj->csProfile = d->profile->lcmsProfile(); 0349 adj->cmstransform = cmsCreateTransform(adj->profiles[0], this->colorSpaceType(), 0, this->colorSpaceType(), 0350 KoColorConversionTransformation::adjustmentRenderingIntent(), 0351 KoColorConversionTransformation::adjustmentConversionFlags()); 0352 0353 adj->cmsAlphaTransform = cmsCreateTransform(adj->profiles[1], TYPE_GRAY_FLT, 0, TYPE_GRAY_FLT, 0354 KoColorConversionTransformation::adjustmentRenderingIntent(), 0355 KoColorConversionTransformation::adjustmentConversionFlags()); 0356 0357 delete [] transferFunctions; 0358 delete [] alphaTransferFunctions; 0359 return adj; 0360 } 0361 0362 quint8 difference(const quint8 *src1, const quint8 *src2) const override 0363 { 0364 quint8 lab1[8], lab2[8]; 0365 cmsCIELab labF1, labF2; 0366 0367 if (this->opacityU8(src1) == OPACITY_TRANSPARENT_U8 0368 || this->opacityU8(src2) == OPACITY_TRANSPARENT_U8) { 0369 return (this->opacityU8(src1) == this->opacityU8(src2) ? 0 : 255); 0370 } 0371 Q_ASSERT(this->toLabA16Converter()); 0372 this->toLabA16Converter()->transform(src1, lab1, 1); 0373 this->toLabA16Converter()->transform(src2, lab2, 1); 0374 cmsLabEncoded2Float(&labF1, (cmsUInt16Number *)lab1); 0375 cmsLabEncoded2Float(&labF2, (cmsUInt16Number *)lab2); 0376 qreal diff = cmsDeltaE(&labF1, &labF2); 0377 0378 if (diff > 255.0) { 0379 return 255; 0380 } else { 0381 return quint8(diff); 0382 } 0383 } 0384 0385 quint8 differenceA(const quint8 *src1, const quint8 *src2) const override 0386 { 0387 quint8 lab1[8]; 0388 quint8 lab2[8]; 0389 cmsCIELab labF1; 0390 cmsCIELab labF2; 0391 0392 if (this->opacityU8(src1) == OPACITY_TRANSPARENT_U8 0393 || this->opacityU8(src2) == OPACITY_TRANSPARENT_U8) { 0394 0395 0396 const qreal alphaScale = 100.0 / 255.0; 0397 return qRound(alphaScale * qAbs(this->opacityU8(src1) - this->opacityU8(src2))); 0398 } 0399 Q_ASSERT(this->toLabA16Converter()); 0400 this->toLabA16Converter()->transform(src1, lab1, 1); 0401 this->toLabA16Converter()->transform(src2, lab2, 1); 0402 cmsLabEncoded2Float(&labF1, (cmsUInt16Number *)lab1); 0403 cmsLabEncoded2Float(&labF2, (cmsUInt16Number *)lab2); 0404 0405 cmsFloat64Number dL; 0406 cmsFloat64Number da; 0407 cmsFloat64Number db; 0408 cmsFloat64Number dAlpha; 0409 0410 dL = fabs((qreal)(labF1.L - labF2.L)); 0411 da = fabs((qreal)(labF1.a - labF2.a)); 0412 db = fabs((qreal)(labF1.b - labF2.b)); 0413 0414 static const int LabAAlphaPos = 3; 0415 static const cmsFloat64Number alphaScale = 100.0 / KoColorSpaceMathsTraits<quint16>::max; 0416 quint16 alpha1 = reinterpret_cast<quint16 *>(lab1)[LabAAlphaPos]; 0417 quint16 alpha2 = reinterpret_cast<quint16 *>(lab2)[LabAAlphaPos]; 0418 dAlpha = fabs((qreal)(alpha1 - alpha2)) * alphaScale; 0419 0420 qreal diff = pow(dL * dL + da * da + db * db + dAlpha * dAlpha, 0.5); 0421 0422 if (diff > 255.0) { 0423 return 255; 0424 } else { 0425 return quint8(diff); 0426 } 0427 } 0428 0429 private: 0430 0431 inline LcmsColorProfileContainer *lcmsProfile() const 0432 { 0433 return d->profile; 0434 } 0435 0436 inline static LcmsColorProfileContainer *asLcmsProfile(const KoColorProfile *p) 0437 { 0438 if (!p) { 0439 return 0; 0440 } 0441 0442 const IccColorProfile *iccp = dynamic_cast<const IccColorProfile *>(p); 0443 0444 if (!iccp) { 0445 return 0; 0446 } 0447 0448 Q_ASSERT(iccp->asLcms()); 0449 0450 return iccp->asLcms(); 0451 } 0452 0453 Private *const d; 0454 }; 0455 0456 /** 0457 * Base class for all LCMS based ColorSpace factories. 0458 */ 0459 class LcmsColorSpaceFactory : public KoColorSpaceFactory, private KoLcmsInfo 0460 { 0461 public: 0462 LcmsColorSpaceFactory(cmsUInt32Number cmType, cmsColorSpaceSignature colorSpaceSignature) 0463 : KoLcmsInfo(cmType, colorSpaceSignature) 0464 { 0465 } 0466 0467 bool profileIsCompatible(const KoColorProfile *profile) const override 0468 { 0469 const IccColorProfile *p = dynamic_cast<const IccColorProfile *>(profile); 0470 return (p && p->asLcms()->colorSpaceSignature() == colorSpaceSignature()); 0471 } 0472 0473 QString colorSpaceEngine() const override 0474 { 0475 return "icc"; 0476 } 0477 0478 bool isHdr() const override 0479 { 0480 return false; 0481 } 0482 0483 int crossingCost() const override { 0484 return 1; 0485 } 0486 0487 QList<KoColorConversionTransformationFactory *> colorConversionLinks() const override; 0488 KoColorProfile *createColorProfile(const QByteArray &rawData) const override; 0489 }; 0490 0491 #endif