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