File indexing completed on 2024-06-16 04:15:58

0001 /*
0002  *  SPDX-FileCopyrightText: 2007-2008 Cyrille Berger <cberger@cberger.net>
0003  *  SPDX-FileCopyrightText: 2011 Srikanth Tiyyagura <srikanth.tulasiram@gmail.com>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.1-or-later
0006  */
0007 
0008 #include "IccColorSpaceEngine.h"
0009 
0010 #include <klocalizedstring.h>
0011 
0012 #include <KoColorModelStandardIds.h>
0013 #include <kis_assert.h>
0014 
0015 #include "LcmsColorSpace.h"
0016 
0017 // -- KoLcmsColorConversionTransformation --
0018 
0019 class KoLcmsColorConversionTransformation : public KoColorConversionTransformation
0020 {
0021 public:
0022     KoLcmsColorConversionTransformation(const KoColorSpace *srcCs, quint32 srcColorSpaceType, LcmsColorProfileContainer *srcProfile,
0023                                         const KoColorSpace *dstCs, quint32 dstColorSpaceType, LcmsColorProfileContainer *dstProfile,
0024                                         Intent renderingIntent,
0025                                         ConversionFlags conversionFlags)
0026         : KoColorConversionTransformation(srcCs, dstCs, renderingIntent, conversionFlags)
0027         , m_transform(0)
0028     {
0029         Q_ASSERT(srcCs);
0030         Q_ASSERT(dstCs);
0031         Q_ASSERT(renderingIntent < 4);
0032 
0033         if ((srcProfile->isLinear() || dstProfile->isLinear()) &&
0034             !conversionFlags.testFlag(KoColorConversionTransformation::NoOptimization)) {
0035 
0036             conversionFlags |= KoColorConversionTransformation::NoOptimization;
0037         }
0038         conversionFlags |= KoColorConversionTransformation::CopyAlpha;
0039 
0040         m_transform = cmsCreateTransform(srcProfile->lcmsProfile(),
0041                                          srcColorSpaceType,
0042                                          dstProfile->lcmsProfile(),
0043                                          dstColorSpaceType,
0044                                          renderingIntent,
0045                                          conversionFlags);
0046 
0047         Q_ASSERT(m_transform);
0048     }
0049 
0050     ~KoLcmsColorConversionTransformation() override
0051     {
0052         cmsDeleteTransform(m_transform);
0053     }
0054 
0055 public:
0056 
0057     void transform(const quint8 *src, quint8 *dst, qint32 numPixels) const override
0058     {
0059         Q_ASSERT(m_transform);
0060 
0061         cmsDoTransform(m_transform, const_cast<quint8 *>(src), dst, numPixels);
0062 
0063     }
0064 private:
0065     mutable cmsHTRANSFORM m_transform;
0066 };
0067 
0068 class KoLcmsColorProofingConversionTransformation : public KoColorProofingConversionTransformation
0069 {
0070 public:
0071     KoLcmsColorProofingConversionTransformation(const KoColorSpace *srcCs, quint32 srcColorSpaceType, LcmsColorProfileContainer *srcProfile,
0072                                                 const KoColorSpace *dstCs, quint32 dstColorSpaceType, LcmsColorProfileContainer *dstProfile,
0073                                                 const KoColorSpace *proofingSpace,
0074                                                 Intent renderingIntent,
0075                                                 Intent proofingIntent,
0076                                                 ConversionFlags conversionFlags,
0077                                                 quint8 *gamutWarning,
0078                                                 double adaptationState
0079                                                 )
0080         : KoColorProofingConversionTransformation(srcCs, dstCs, proofingSpace, renderingIntent, proofingIntent, conversionFlags, gamutWarning, adaptationState)
0081         , m_transform(0)
0082     {
0083         Q_ASSERT(srcCs);
0084         Q_ASSERT(dstCs);
0085         Q_ASSERT(renderingIntent < 4);
0086 
0087         if (srcCs->colorDepthId() == Integer8BitsColorDepthID
0088                 || srcCs->colorDepthId() == Integer16BitsColorDepthID) {
0089 
0090             if ((srcProfile->name().contains(QLatin1String("linear"), Qt::CaseInsensitive) ||
0091                  dstProfile->name().contains(QLatin1String("linear"), Qt::CaseInsensitive)) &&
0092                     !conversionFlags.testFlag(KoColorConversionTransformation::NoOptimization)) {
0093                 conversionFlags |= KoColorConversionTransformation::NoOptimization;
0094             }
0095         }
0096         conversionFlags |= KoColorConversionTransformation::CopyAlpha;
0097 
0098         quint16 alarm[cmsMAXCHANNELS];//this seems to be bgr???
0099         alarm[0] = (cmsUInt16Number)gamutWarning[2]*256;
0100         alarm[1] = (cmsUInt16Number)gamutWarning[1]*256;
0101         alarm[2] = (cmsUInt16Number)gamutWarning[0]*256;
0102         cmsSetAlarmCodes(alarm);
0103         cmsSetAdaptationState(adaptationState);
0104 
0105         KIS_ASSERT(dynamic_cast<const IccColorProfile *>(proofingSpace->profile()));
0106         m_transform = cmsCreateProofingTransform(srcProfile->lcmsProfile(),
0107                                                  srcColorSpaceType,
0108                                                  dstProfile->lcmsProfile(),
0109                                                  dstColorSpaceType,
0110                                                  dynamic_cast<const IccColorProfile *>(proofingSpace->profile())->asLcms()->lcmsProfile(),
0111                                                  renderingIntent,
0112                                                  proofingIntent,
0113                                                  conversionFlags);
0114         cmsSetAdaptationState(1);
0115 
0116         Q_ASSERT(m_transform);
0117     }
0118 
0119     ~KoLcmsColorProofingConversionTransformation() override
0120     {
0121         cmsDeleteTransform(m_transform);
0122     }
0123 
0124 public:
0125 
0126     void transform(const quint8 *src, quint8 *dst, qint32 numPixels) const override
0127     {
0128         Q_ASSERT(m_transform);
0129 
0130         cmsDoTransform(m_transform, const_cast<quint8 *>(src), dst, numPixels);
0131 
0132     }
0133 private:
0134     mutable cmsHTRANSFORM m_transform;
0135 };
0136 
0137 struct IccColorSpaceEngine::Private {
0138 };
0139 
0140 IccColorSpaceEngine::IccColorSpaceEngine() : KoColorSpaceEngine("icc", i18n("ICC Engine")), d(new Private)
0141 {
0142 }
0143 
0144 IccColorSpaceEngine::~IccColorSpaceEngine()
0145 {
0146     delete d;
0147 }
0148 
0149 const KoColorProfile* IccColorSpaceEngine::addProfile(const QString &filename)
0150 {
0151     KoColorSpaceRegistry *registry = KoColorSpaceRegistry::instance();
0152 
0153     KoColorProfile *profile = new IccColorProfile(filename);
0154     Q_CHECK_PTR(profile);
0155 
0156     // this our own loading code; sometimes it fails because of an lcms error
0157     profile->load();
0158 
0159     // and then lcms can read the profile from file itself without problems,
0160     // quite often, and we can initialize it
0161     if (!profile->valid()) {
0162         cmsHPROFILE cmsp = cmsOpenProfileFromFile(filename.toLatin1(), "r");
0163         if (cmsp) {
0164             profile = LcmsColorProfileContainer::createFromLcmsProfile(cmsp);
0165         }
0166     }
0167 
0168     if (profile->valid()) {
0169         dbgPigment << "Valid profile : " << profile->fileName() << profile->name();
0170         registry->addProfile(profile);
0171     } else {
0172         dbgPigment << "Invalid profile : " << profile->fileName() << profile->name();
0173         delete profile;
0174         profile = 0;
0175     }
0176 
0177     return profile;
0178 }
0179 
0180 const KoColorProfile* IccColorSpaceEngine::addProfile(const QByteArray &data)
0181 {
0182     KoColorSpaceRegistry *registry = KoColorSpaceRegistry::instance();
0183 
0184     KoColorProfile *profile = new IccColorProfile(data);
0185     Q_CHECK_PTR(profile);
0186 
0187     if (profile->valid()) {
0188         dbgPigment << "Valid profile : " << profile->fileName() << profile->name();
0189         registry->addProfile(profile);
0190     } else {
0191         dbgPigment << "Invalid profile : " << profile->fileName() << profile->name();
0192         delete profile;
0193         profile = 0;
0194     }
0195 
0196     return profile;
0197 }
0198 
0199 const KoColorProfile *IccColorSpaceEngine::getProfile(const QVector<double> &colorants, ColorPrimaries colorPrimaries, TransferCharacteristics transferFunction)
0200 {
0201     KoColorSpaceRegistry *registry = KoColorSpaceRegistry::instance();
0202 
0203     KIS_SAFE_ASSERT_RECOVER(
0204         (!colorants.isEmpty() || colorPrimaries != PRIMARIES_UNSPECIFIED)
0205         && transferFunction != TRC_UNSPECIFIED)
0206     {
0207         if (transferFunction == TRC_LINEAR) {
0208             colorPrimaries = PRIMARIES_ITU_R_BT_2020_2_AND_2100_0;
0209         } else {
0210             colorPrimaries = PRIMARIES_ITU_R_BT_709_5;
0211         }
0212 
0213         if (transferFunction == TRC_UNSPECIFIED) {
0214             transferFunction = TRC_IEC_61966_2_1;
0215         }
0216     }
0217 
0218     const KoColorProfile *profile = new IccColorProfile(colorants, colorPrimaries, transferFunction);
0219     Q_CHECK_PTR(profile);
0220 
0221     if (profile->valid()) {
0222         dbgPigment << "Valid profile : " << profile->fileName() << profile->name();
0223         registry->addProfile(profile);
0224     } else {
0225         dbgPigment << "Invalid profile : " << profile->fileName() << profile->name();
0226         delete profile;
0227         profile = nullptr;
0228     }
0229 
0230     return profile;
0231 }
0232 
0233 void IccColorSpaceEngine::removeProfile(const QString &filename)
0234 {
0235     KoColorSpaceRegistry *registry = KoColorSpaceRegistry::instance();
0236 
0237     KoColorProfile *profile = new IccColorProfile(filename);
0238     Q_CHECK_PTR(profile);
0239     profile->load();
0240 
0241     if (profile->valid() && registry->profileByName(profile->name())) {
0242         registry->removeProfile(profile);
0243     }
0244 }
0245 
0246 KoColorConversionTransformation *IccColorSpaceEngine::createColorTransformation(const KoColorSpace *srcColorSpace,
0247                                                                                 const KoColorSpace *dstColorSpace,
0248                                                                                 KoColorConversionTransformation::Intent renderingIntent,
0249                                                                                 KoColorConversionTransformation::ConversionFlags conversionFlags) const
0250 {
0251     KIS_ASSERT(srcColorSpace);
0252     KIS_ASSERT(dstColorSpace);
0253     KIS_ASSERT(dynamic_cast<const IccColorProfile *>(srcColorSpace->profile()));
0254     KIS_ASSERT(dynamic_cast<const IccColorProfile *>(dstColorSpace->profile()));
0255 
0256     return new KoLcmsColorConversionTransformation(
0257                 srcColorSpace, computeColorSpaceType(srcColorSpace),
0258                 dynamic_cast<const IccColorProfile *>(srcColorSpace->profile())->asLcms(), dstColorSpace, computeColorSpaceType(dstColorSpace),
0259                 dynamic_cast<const IccColorProfile *>(dstColorSpace->profile())->asLcms(), renderingIntent, conversionFlags);
0260 
0261 }
0262 KoColorProofingConversionTransformation *IccColorSpaceEngine::createColorProofingTransformation(const KoColorSpace *srcColorSpace,
0263                                                                                                 const KoColorSpace *dstColorSpace,
0264                                                                                                 const KoColorSpace *proofingSpace,
0265                                                                                                 KoColorConversionTransformation::Intent renderingIntent,
0266                                                                                                 KoColorConversionTransformation::Intent proofingIntent,
0267                                                                                                 KoColorConversionTransformation::ConversionFlags conversionFlags,
0268                                                                                                 quint8 *gamutWarning,
0269                                                                                                 double adaptationState) const
0270 {
0271     KIS_ASSERT(srcColorSpace);
0272     KIS_ASSERT(dstColorSpace);
0273     KIS_ASSERT(dynamic_cast<const IccColorProfile *>(srcColorSpace->profile()));
0274     KIS_ASSERT(dynamic_cast<const IccColorProfile *>(dstColorSpace->profile()));
0275 
0276     return new KoLcmsColorProofingConversionTransformation(
0277                 srcColorSpace, computeColorSpaceType(srcColorSpace),
0278                 dynamic_cast<const IccColorProfile *>(srcColorSpace->profile())->asLcms(), dstColorSpace, computeColorSpaceType(dstColorSpace),
0279                 dynamic_cast<const IccColorProfile *>(dstColorSpace->profile())->asLcms(), proofingSpace, renderingIntent, proofingIntent, conversionFlags, gamutWarning,
0280                 adaptationState
0281                 );
0282 }
0283 
0284 quint32 IccColorSpaceEngine::computeColorSpaceType(const KoColorSpace *cs) const
0285 {
0286     Q_ASSERT(cs);
0287 
0288     if (const KoLcmsInfo *lcmsInfo = dynamic_cast<const KoLcmsInfo *>(cs)) {
0289         return lcmsInfo->colorSpaceType();
0290     } else {
0291         QString modelId = cs->colorModelId().id();
0292         QString depthId = cs->colorDepthId().id();
0293         // Compute the depth part of the type
0294         quint32 depthType;
0295 
0296         if (depthId == Integer8BitsColorDepthID.id()) {
0297             depthType = BYTES_SH(1);
0298         } else if (depthId == Integer16BitsColorDepthID.id()) {
0299             depthType = BYTES_SH(2);
0300         } else if (depthId == Float16BitsColorDepthID.id()) {
0301             depthType = BYTES_SH(2) | FLOAT_SH(1);
0302         } else if (depthId == Float32BitsColorDepthID.id()) {
0303             depthType = BYTES_SH(4) | FLOAT_SH(1);
0304         } else if (depthId == Float64BitsColorDepthID.id()) {
0305             depthType = BYTES_SH(0) | FLOAT_SH(1);
0306         } else {
0307             qWarning() << "Unknown bit depth";
0308             return 0;
0309         }
0310         // Compute the model part of the type
0311         quint32 modelType = 0;
0312 
0313         if (modelId == RGBAColorModelID.id()) {
0314             if (depthId.startsWith(QLatin1Char('U'))) {
0315                 modelType = (COLORSPACE_SH(PT_RGB) | EXTRA_SH(1) | CHANNELS_SH(3) | DOSWAP_SH(1) | SWAPFIRST_SH(1));
0316             } else if (depthId.startsWith(QLatin1Char('F'))) {
0317                 modelType = (COLORSPACE_SH(PT_RGB) | EXTRA_SH(1) | CHANNELS_SH(3));
0318             }
0319         } else if (modelId == XYZAColorModelID.id()) {
0320             modelType = (COLORSPACE_SH(PT_XYZ) | EXTRA_SH(1) | CHANNELS_SH(3));
0321         } else if (modelId == LABAColorModelID.id()) {
0322             modelType = (COLORSPACE_SH(PT_Lab) | EXTRA_SH(1) | CHANNELS_SH(3));
0323         } else if (modelId == CMYKAColorModelID.id()) {
0324             modelType = (COLORSPACE_SH(PT_CMYK) | EXTRA_SH(1) | CHANNELS_SH(4));
0325         } else if (modelId == GrayAColorModelID.id()) {
0326             modelType = (COLORSPACE_SH(PT_GRAY) | EXTRA_SH(1) | CHANNELS_SH(1));
0327         } else if (modelId == GrayColorModelID.id()) {
0328             modelType = (COLORSPACE_SH(PT_GRAY) | CHANNELS_SH(1));
0329         } else if (modelId == YCbCrAColorModelID.id()) {
0330             modelType = (COLORSPACE_SH(PT_YCbCr) | EXTRA_SH(1) | CHANNELS_SH(3));
0331         } else {
0332             qWarning() << "Cannot convert colorspace to lcms modeltype";
0333             return 0;
0334         }
0335         return depthType | modelType;
0336     }
0337 }
0338 
0339 bool IccColorSpaceEngine::supportsColorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile) const
0340 {
0341     Q_UNUSED(colorDepthId);
0342     return colorModelId != RGBAColorModelID.id() || !profile || profile->name() != "High Dynamic Range UHDTV Wide Color Gamut Display (Rec. 2020) - SMPTE ST 2084 PQ EOTF";
0343 }