diff --git a/plugins/color/lcms2engine/LcmsColorSpace.h b/plugins/color/lcms2engine/LcmsColorSpace.h index 84d193daf6..669ccbf44e 100644 --- a/plugins/color/lcms2engine/LcmsColorSpace.h +++ b/plugins/color/lcms2engine/LcmsColorSpace.h @@ -1,488 +1,493 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005-2006 C. Boemann * Copyright (c) 2004,2006-2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOLCMSCOLORSPACE_H_ #define KOLCMSCOLORSPACE_H_ #include #include #include #include +#include "kis_assert.h" + + class LcmsColorProfileContainer; class KoLcmsInfo { struct Private { cmsUInt32Number cmType; // The colorspace type as defined by littlecms cmsColorSpaceSignature colorSpaceSignature; // The colorspace signature as defined in icm/icc files }; public: KoLcmsInfo(cmsUInt32Number cmType, cmsColorSpaceSignature colorSpaceSignature) : d(new Private) { d->cmType = cmType; d->colorSpaceSignature = colorSpaceSignature; } virtual ~KoLcmsInfo() { delete d; } virtual quint32 colorSpaceType() const { return d->cmType; } virtual cmsColorSpaceSignature colorSpaceSignature() const { return d->colorSpaceSignature; } private: Private *const d; }; struct KoLcmsDefaultTransformations { cmsHTRANSFORM toRGB; cmsHTRANSFORM fromRGB; static cmsHPROFILE s_RGBProfile; static QMap< QString, QMap< LcmsColorProfileContainer *, KoLcmsDefaultTransformations * > > s_transformations; }; /** * This is the base class for all colorspaces that are based on the lcms library, for instance * RGB 8bits and 16bits, CMYK 8bits and 16bits, LAB... */ template class LcmsColorSpace : public KoColorSpaceAbstract<_CSTraits>, public KoLcmsInfo { struct KoLcmsColorTransformation : public KoColorTransformation { KoLcmsColorTransformation(const KoColorSpace *colorSpace) : KoColorTransformation() , m_colorSpace(colorSpace) { csProfile = 0; cmstransform = 0; cmsAlphaTransform = 0; profiles[0] = 0; profiles[1] = 0; profiles[2] = 0; } ~KoLcmsColorTransformation() override { if (cmstransform) { cmsDeleteTransform(cmstransform); } if (profiles[0] && profiles[0] != csProfile) { cmsCloseProfile(profiles[0]); } if (profiles[1] && profiles[1] != csProfile) { cmsCloseProfile(profiles[1]); } if (profiles[2] && profiles[2] != csProfile) { cmsCloseProfile(profiles[2]); } } void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override { cmsDoTransform(cmstransform, const_cast(src), dst, nPixels); qint32 numPixels = nPixels; qint32 pixelSize = m_colorSpace->pixelSize(); int index = 0; if (cmsAlphaTransform) { qreal *alpha = new qreal[nPixels]; qreal *dstalpha = new qreal[nPixels]; while (index < nPixels) { alpha[index] = m_colorSpace->opacityF(src); src += pixelSize; index++; } cmsDoTransform(cmsAlphaTransform, const_cast(alpha), static_cast(dstalpha), nPixels); for (int i = 0; i < numPixels; i++) { m_colorSpace->setOpacity(dst, dstalpha[i], 1); dst += pixelSize; } delete [] alpha; delete [] dstalpha; } else { while (numPixels > 0) { qreal alpha = m_colorSpace->opacityF(src); m_colorSpace->setOpacity(dst, alpha, 1); src += pixelSize; dst += pixelSize; numPixels--; } } } const KoColorSpace *m_colorSpace; cmsHPROFILE csProfile; cmsHPROFILE profiles[3]; cmsHTRANSFORM cmstransform; cmsHTRANSFORM cmsAlphaTransform; }; struct Private { mutable quint8 *qcolordata; // A small buffer for conversion from and to qcolor. KoLcmsDefaultTransformations *defaultTransformations; mutable cmsHPROFILE lastRGBProfile; // Last used profile to transform to/from RGB mutable cmsHTRANSFORM lastToRGB; // Last used transform to transform to RGB mutable cmsHTRANSFORM lastFromRGB; // Last used transform to transform from RGB LcmsColorProfileContainer *profile; KoColorProfile *colorProfile; QMutex mutex; }; protected: LcmsColorSpace(const QString &id, const QString &name, cmsUInt32Number cmType, cmsColorSpaceSignature colorSpaceSignature, KoColorProfile *p) : KoColorSpaceAbstract<_CSTraits>(id, name) , KoLcmsInfo(cmType, colorSpaceSignature) , d(new Private()) { Q_ASSERT(p); // No profile means the lcms color space can't work Q_ASSERT(profileIsCompatible(p)); d->profile = asLcmsProfile(p); Q_ASSERT(d->profile); d->colorProfile = p; d->qcolordata = 0; d->lastRGBProfile = 0; d->lastToRGB = 0; d->lastFromRGB = 0; d->defaultTransformations = 0; } ~LcmsColorSpace() override { delete d->colorProfile; delete[] d->qcolordata; delete d->defaultTransformations; delete d; } void init() { // Default pixel buffer for QColor conversion d->qcolordata = new quint8[3]; Q_CHECK_PTR(d->qcolordata); - Q_ASSERT(d->profile); + KIS_ASSERT(d->profile); if (KoLcmsDefaultTransformations::s_RGBProfile == 0) { KoLcmsDefaultTransformations::s_RGBProfile = cmsCreate_sRGBProfile(); } d->defaultTransformations = KoLcmsDefaultTransformations::s_transformations[this->id()][ d->profile]; if (!d->defaultTransformations) { d->defaultTransformations = new KoLcmsDefaultTransformations; d->defaultTransformations->fromRGB = cmsCreateTransform(KoLcmsDefaultTransformations::s_RGBProfile, TYPE_BGR_8, d->profile->lcmsProfile(), this->colorSpaceType(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); - Q_ASSERT(d->defaultTransformations->fromRGB); + KIS_SAFE_ASSERT_RECOVER_NOOP(d->defaultTransformations->fromRGB || !d->colorProfile->isSuitableForOutput()); + d->defaultTransformations->toRGB = cmsCreateTransform(d->profile->lcmsProfile(), this->colorSpaceType(), KoLcmsDefaultTransformations::s_RGBProfile, TYPE_BGR_8, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); - Q_ASSERT(d->defaultTransformations->toRGB); + KIS_SAFE_ASSERT_RECOVER_NOOP(d->defaultTransformations->toRGB); KoLcmsDefaultTransformations::s_transformations[ this->id()][ d->profile ] = d->defaultTransformations; } } public: bool hasHighDynamicRange() const override { return false; } const KoColorProfile *profile() const override { return d->colorProfile; } bool profileIsCompatible(const KoColorProfile *profile) const override { const IccColorProfile *p = dynamic_cast(profile); return (p && p->asLcms()->colorSpaceSignature() == colorSpaceSignature()); } void fromQColor(const QColor &color, quint8 *dst, const KoColorProfile *koprofile = 0) const override { QMutexLocker locker(&d->mutex); d->qcolordata[2] = color.red(); d->qcolordata[1] = color.green(); d->qcolordata[0] = color.blue(); LcmsColorProfileContainer *profile = asLcmsProfile(koprofile); if (profile == 0) { // Default sRGB - Q_ASSERT(d->defaultTransformations && d->defaultTransformations->fromRGB); + KIS_ASSERT(d->defaultTransformations && d->defaultTransformations->fromRGB); cmsDoTransform(d->defaultTransformations->fromRGB, d->qcolordata, dst, 1); } else { if (d->lastFromRGB == 0 || (d->lastFromRGB != 0 && d->lastRGBProfile != profile->lcmsProfile())) { d->lastFromRGB = cmsCreateTransform(profile->lcmsProfile(), TYPE_BGR_8, d->profile->lcmsProfile(), this->colorSpaceType(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); d->lastRGBProfile = profile->lcmsProfile(); } + KIS_ASSERT(d->lastFromRGB); cmsDoTransform(d->lastFromRGB, d->qcolordata, dst, 1); } this->setOpacity(dst, (quint8)(color.alpha()), 1); } void toQColor(const quint8 *src, QColor *c, const KoColorProfile *koprofile = 0) const override { QMutexLocker locker(&d->mutex); LcmsColorProfileContainer *profile = asLcmsProfile(koprofile); if (profile == 0) { // Default sRGB transform Q_ASSERT(d->defaultTransformations && d->defaultTransformations->toRGB); cmsDoTransform(d->defaultTransformations->toRGB, const_cast (src), d->qcolordata, 1); } else { if (d->lastToRGB == 0 || (d->lastToRGB != 0 && d->lastRGBProfile != profile->lcmsProfile())) { d->lastToRGB = cmsCreateTransform(d->profile->lcmsProfile(), this->colorSpaceType(), profile->lcmsProfile(), TYPE_BGR_8, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); d->lastRGBProfile = profile->lcmsProfile(); } cmsDoTransform(d->lastToRGB, const_cast (src), d->qcolordata, 1); } c->setRgb(d->qcolordata[2], d->qcolordata[1], d->qcolordata[0]); c->setAlpha(this->opacityU8(src)); } KoColorTransformation *createBrightnessContrastAdjustment(const quint16 *transferValues) const override { if (!d->profile) { return 0; } cmsToneCurve *transferFunctions[3]; transferFunctions[0] = cmsBuildTabulatedToneCurve16(0, 256, transferValues); transferFunctions[1] = cmsBuildGamma(0, 1.0); transferFunctions[2] = cmsBuildGamma(0, 1.0); KoLcmsColorTransformation *adj = new KoLcmsColorTransformation(this); adj->profiles[1] = cmsCreateLinearizationDeviceLink(cmsSigLabData, transferFunctions); cmsSetDeviceClass(adj->profiles[1], cmsSigAbstractClass); adj->profiles[0] = d->profile->lcmsProfile(); adj->profiles[2] = d->profile->lcmsProfile(); adj->cmstransform = cmsCreateMultiprofileTransform(adj->profiles, 3, this->colorSpaceType(), this->colorSpaceType(), KoColorConversionTransformation::adjustmentRenderingIntent(), KoColorConversionTransformation::adjustmentConversionFlags()); adj->csProfile = d->profile->lcmsProfile(); return adj; } KoColorTransformation *createPerChannelAdjustment(const quint16 *const *transferValues) const override { if (!d->profile) { return 0; } cmsToneCurve **transferFunctions = new cmsToneCurve*[ this->colorChannelCount()]; for (uint ch = 0; ch < this->colorChannelCount(); ch++) { transferFunctions[ch] = transferValues[ch] ? cmsBuildTabulatedToneCurve16(0, 256, transferValues[ch]) : cmsBuildGamma(0, 1.0); } cmsToneCurve **alphaTransferFunctions = new cmsToneCurve*[1]; alphaTransferFunctions[0] = transferValues[this->colorChannelCount()] ? cmsBuildTabulatedToneCurve16(0, 256, transferValues[this->colorChannelCount()]) : cmsBuildGamma(0, 1.0); KoLcmsColorTransformation *adj = new KoLcmsColorTransformation(this); adj->profiles[0] = cmsCreateLinearizationDeviceLink(this->colorSpaceSignature(), transferFunctions); adj->profiles[1] = cmsCreateLinearizationDeviceLink(cmsSigGrayData, alphaTransferFunctions); adj->profiles[2] = 0; adj->csProfile = d->profile->lcmsProfile(); adj->cmstransform = cmsCreateTransform(adj->profiles[0], this->colorSpaceType(), 0, this->colorSpaceType(), KoColorConversionTransformation::adjustmentRenderingIntent(), KoColorConversionTransformation::adjustmentConversionFlags()); adj->cmsAlphaTransform = cmsCreateTransform(adj->profiles[1], TYPE_GRAY_DBL, 0, TYPE_GRAY_DBL, KoColorConversionTransformation::adjustmentRenderingIntent(), KoColorConversionTransformation::adjustmentConversionFlags()); delete [] transferFunctions; delete [] alphaTransferFunctions; return adj; } quint8 difference(const quint8 *src1, const quint8 *src2) const override { quint8 lab1[8], lab2[8]; cmsCIELab labF1, labF2; if (this->opacityU8(src1) == OPACITY_TRANSPARENT_U8 || this->opacityU8(src2) == OPACITY_TRANSPARENT_U8) { return (this->opacityU8(src1) == this->opacityU8(src2) ? 0 : 255); } Q_ASSERT(this->toLabA16Converter()); this->toLabA16Converter()->transform(src1, lab1, 1); this->toLabA16Converter()->transform(src2, lab2, 1); cmsLabEncoded2Float(&labF1, (cmsUInt16Number *)lab1); cmsLabEncoded2Float(&labF2, (cmsUInt16Number *)lab2); qreal diff = cmsDeltaE(&labF1, &labF2); if (diff > 255.0) { return 255; } else { return quint8(diff); } } quint8 differenceA(const quint8 *src1, const quint8 *src2) const override { quint8 lab1[8]; quint8 lab2[8]; cmsCIELab labF1; cmsCIELab labF2; if (this->opacityU8(src1) == OPACITY_TRANSPARENT_U8 || this->opacityU8(src2) == OPACITY_TRANSPARENT_U8) { return (this->opacityU8(src1) == this->opacityU8(src2) ? 0 : 255); } Q_ASSERT(this->toLabA16Converter()); this->toLabA16Converter()->transform(src1, lab1, 1); this->toLabA16Converter()->transform(src2, lab2, 1); cmsLabEncoded2Float(&labF1, (cmsUInt16Number *)lab1); cmsLabEncoded2Float(&labF2, (cmsUInt16Number *)lab2); cmsFloat64Number dL; cmsFloat64Number da; cmsFloat64Number db; cmsFloat64Number dAlpha; dL = fabs((qreal)(labF1.L - labF2.L)); da = fabs((qreal)(labF1.a - labF2.a)); db = fabs((qreal)(labF1.b - labF2.b)); static const int LabAAlphaPos = 3; static const cmsFloat64Number alphaScale = 100.0 / KoColorSpaceMathsTraits::max; quint16 alpha1 = reinterpret_cast(lab1)[LabAAlphaPos]; quint16 alpha2 = reinterpret_cast(lab2)[LabAAlphaPos]; dAlpha = fabs((qreal)(alpha1 - alpha2)) * alphaScale; qreal diff = pow(dL * dL + da * da + db * db + dAlpha * dAlpha, 0.5); if (diff > 255.0) { return 255; } else { return quint8(diff); } } private: inline LcmsColorProfileContainer *lcmsProfile() const { return d->profile; } inline static LcmsColorProfileContainer *asLcmsProfile(const KoColorProfile *p) { if (!p) { return 0; } const IccColorProfile *iccp = dynamic_cast(p); if (!iccp) { return 0; } Q_ASSERT(iccp->asLcms()); return iccp->asLcms(); } Private *const d; }; /** * Base class for all LCMS based ColorSpace factories. */ class LcmsColorSpaceFactory : public KoColorSpaceFactory, private KoLcmsInfo { public: LcmsColorSpaceFactory(cmsUInt32Number cmType, cmsColorSpaceSignature colorSpaceSignature) : KoLcmsInfo(cmType, colorSpaceSignature) { } bool profileIsCompatible(const KoColorProfile *profile) const override { const IccColorProfile *p = dynamic_cast(profile); return (p && p->asLcms()->colorSpaceSignature() == colorSpaceSignature()); } QString colorSpaceEngine() const override { return "icc"; } bool isHdr() const override { return false; } int crossingCost() const override { return 1; } QList colorConversionLinks() const override; KoColorProfile *createColorProfile(const QByteArray &rawData) const override; }; #endif diff --git a/plugins/color/lcms2engine/colorprofiles/IccColorProfile.cpp b/plugins/color/lcms2engine/colorprofiles/IccColorProfile.cpp index f463f52438..f9c6e3bd68 100644 --- a/plugins/color/lcms2engine/colorprofiles/IccColorProfile.cpp +++ b/plugins/color/lcms2engine/colorprofiles/IccColorProfile.cpp @@ -1,389 +1,406 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "IccColorProfile.h" #include #include #include #include #include "QDebug" #include "LcmsColorProfileContainer.h" #include "lcms2.h" +#include "kis_assert.h" + + struct IccColorProfile::Data::Private { QByteArray rawData; }; IccColorProfile::Data::Data() : d(new Private) { } IccColorProfile::Data::Data(const QByteArray &rawData) : d(new Private) { d->rawData = rawData; } IccColorProfile::Data::~Data() { } QByteArray IccColorProfile::Data::rawData() { return d->rawData; } void IccColorProfile::Data::setRawData(const QByteArray &rawData) { d->rawData = rawData; } IccColorProfile::Container::Container() { } IccColorProfile::Container::~Container() { } struct IccColorProfile::Private { struct Shared { QScopedPointer data; QScopedPointer lcmsProfile; QVector uiMinMaxes; + bool canCreateCyclicTransform = false; }; QSharedPointer shared; }; IccColorProfile::IccColorProfile(const QString &fileName) : KoColorProfile(fileName), d(new Private) { // QSharedPointer lacks a reset in Qt 4.x d->shared = QSharedPointer(new Private::Shared()); d->shared->data.reset(new Data()); } IccColorProfile::IccColorProfile(const QByteArray &rawData) : KoColorProfile(QString()), d(new Private) { d->shared = QSharedPointer(new Private::Shared()); d->shared->data.reset(new Data()); setRawData(rawData); init(); } IccColorProfile::IccColorProfile(const IccColorProfile &rhs) : KoColorProfile(rhs) , d(new Private(*rhs.d)) { Q_ASSERT(d->shared); } IccColorProfile::~IccColorProfile() { Q_ASSERT(d->shared); } KoColorProfile *IccColorProfile::clone() const { return new IccColorProfile(*this); } QByteArray IccColorProfile::rawData() const { return d->shared->data->rawData(); } void IccColorProfile::setRawData(const QByteArray &rawData) { d->shared->data->setRawData(rawData); } bool IccColorProfile::valid() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->valid(); } return false; } float IccColorProfile::version() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->version(); } return 0.0; } bool IccColorProfile::isSuitableForOutput() const { if (d->shared->lcmsProfile) { - return d->shared->lcmsProfile->isSuitableForOutput(); + return d->shared->lcmsProfile->isSuitableForOutput() && d->shared->canCreateCyclicTransform; } return false; } bool IccColorProfile::isSuitableForPrinting() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->isSuitableForPrinting(); } return false; } bool IccColorProfile::isSuitableForDisplay() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->isSuitableForDisplay(); } return false; } bool IccColorProfile::supportsPerceptual() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->supportsPerceptual(); } return false; } bool IccColorProfile::supportsSaturation() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->supportsSaturation(); } return false; } bool IccColorProfile::supportsAbsolute() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->supportsAbsolute(); } return false; } bool IccColorProfile::supportsRelative() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->supportsRelative(); } return false; } bool IccColorProfile::hasColorants() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->hasColorants(); } return false; } bool IccColorProfile::hasTRC() const { if (d->shared->lcmsProfile) return d->shared->lcmsProfile->hasTRC(); return false; } bool IccColorProfile::isLinear() const { if (d->shared->lcmsProfile) return d->shared->lcmsProfile->isLinear(); return false; } QVector IccColorProfile::getColorantsXYZ() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->getColorantsXYZ(); } return QVector(9); } QVector IccColorProfile::getColorantsxyY() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->getColorantsxyY(); } return QVector(9); } QVector IccColorProfile::getWhitePointXYZ() const { QVector d50Dummy(3); d50Dummy << 0.9642 << 1.0000 << 0.8249; if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->getWhitePointXYZ(); } return d50Dummy; } QVector IccColorProfile::getWhitePointxyY() const { QVector d50Dummy(3); d50Dummy << 0.34773 << 0.35952 << 1.0; if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->getWhitePointxyY(); } return d50Dummy; } QVector IccColorProfile::getEstimatedTRC() const { QVector dummy(3); dummy.fill(2.2);//estimated sRGB trc. if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->getEstimatedTRC(); } return dummy; } void IccColorProfile::linearizeFloatValue(QVector & Value) const { if (d->shared->lcmsProfile) d->shared->lcmsProfile->LinearizeFloatValue(Value); } void IccColorProfile::delinearizeFloatValue(QVector & Value) const { if (d->shared->lcmsProfile) d->shared->lcmsProfile->DelinearizeFloatValue(Value); } void IccColorProfile::linearizeFloatValueFast(QVector & Value) const { if (d->shared->lcmsProfile) d->shared->lcmsProfile->LinearizeFloatValueFast(Value); } void IccColorProfile::delinearizeFloatValueFast(QVector &Value) const { if (d->shared->lcmsProfile) d->shared->lcmsProfile->DelinearizeFloatValueFast(Value); } QByteArray IccColorProfile::uniqueId() const { QByteArray dummy; if (d->shared->lcmsProfile) { dummy = d->shared->lcmsProfile->getProfileUniqueId(); } return dummy; } bool IccColorProfile::load() { QFile file(fileName()); file.open(QIODevice::ReadOnly); QByteArray rawData = file.readAll(); setRawData(rawData); file.close(); if (init()) { return true; } qWarning() << "Failed to load profile from " << fileName(); return false; } bool IccColorProfile::save() { return false; } bool IccColorProfile::init() { if (!d->shared->lcmsProfile) { d->shared->lcmsProfile.reset(new LcmsColorProfileContainer(d->shared->data.data())); } if (d->shared->lcmsProfile->init()) { setName(d->shared->lcmsProfile->name()); setInfo(d->shared->lcmsProfile->info()); setManufacturer(d->shared->lcmsProfile->manufacturer()); setCopyright(d->shared->lcmsProfile->copyright()); if (d->shared->lcmsProfile->valid()) { calculateFloatUIMinMax(); } return true; } else { return false; } } LcmsColorProfileContainer *IccColorProfile::asLcms() const { Q_ASSERT(d->shared->lcmsProfile); return d->shared->lcmsProfile.data(); } bool IccColorProfile::operator==(const KoColorProfile &rhs) const { const IccColorProfile *rhsIcc = dynamic_cast(&rhs); if (rhsIcc) { return d->shared == rhsIcc->d->shared; } return false; } const QVector &IccColorProfile::getFloatUIMinMax(void) const { Q_ASSERT(!d->shared->uiMinMaxes.isEmpty()); return d->shared->uiMinMaxes; } void IccColorProfile::calculateFloatUIMinMax(void) { QVector &ret = d->shared->uiMinMaxes; cmsHPROFILE cprofile = d->shared->lcmsProfile->lcmsProfile(); Q_ASSERT(cprofile); cmsColorSpaceSignature color_space_sig = cmsGetColorSpace(cprofile); unsigned int num_channels = cmsChannelsOf(color_space_sig); unsigned int color_space_mask = _cmsLCMScolorSpace(color_space_sig); Q_ASSERT(num_channels >= 1 && num_channels <= 4); // num_channels==1 is for grayscale, we need to handle it Q_ASSERT(color_space_mask); // to try to find the max range of float/doubles for this profile, // pass in min/max int and make the profile convert that // this is far from perfect, we need a better way, if possible to get the "bounds" of a profile uint16_t in_min_pixel[4] = {0, 0, 0, 0}; uint16_t in_max_pixel[4] = {0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF}; qreal out_min_pixel[4] = {0, 0, 0, 0}; qreal out_max_pixel[4] = {0, 0, 0, 0}; cmsHTRANSFORM trans = cmsCreateTransform( cprofile, (COLORSPACE_SH(color_space_mask) | CHANNELS_SH(num_channels) | BYTES_SH(2)), cprofile, (COLORSPACE_SH(color_space_mask) | FLOAT_SH(1) | CHANNELS_SH(num_channels) | BYTES_SH(0)), //NOTE THAT 'BYTES' FIELD IS SET TO ZERO ON DLB because 8 bytes overflows the bitfield INTENT_PERCEPTUAL, 0); // does the intent matter in this case? if (trans) { cmsDoTransform(trans, in_min_pixel, out_min_pixel, 1); cmsDoTransform(trans, in_max_pixel, out_max_pixel, 1); cmsDeleteTransform(trans); }//else, we'll just default to [0..1] below + // Some (calibration) proifles may have a weird RGB->XYZ transformation matrix, + // which is not invertible. Therefore, such profile cannot be used as + // a workspace color profile and we should convert the image to sRGB + // right on image loading + + // LCMS doesn't have a separate method for checking if conversion matrix + // is invertible, therefore we just try to create a simple transformation, + // where the profile is both, input and output. If the transformation + // is created successfully, then this profile is probably suitable for + // usage as a working color space. + + d->shared->canCreateCyclicTransform = bool(trans); + ret.resize(num_channels); for (unsigned int i = 0; i < num_channels; ++i) { if (out_min_pixel[i] < out_max_pixel[i]) { ret[i].minVal = out_min_pixel[i]; ret[i].maxVal = out_max_pixel[i]; } else { // apparently we can't even guarantee that converted_to_double(0x0000) < converted_to_double(0xFFFF) // assume [0..1] in such cases // we need to find a really solid way of determining the bounds of a profile, if possible ret[i].minVal = 0; ret[i].maxVal = 1; } } }