/* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2005-11-18 * Description : a class to apply ICC color correction to image. * * Copyright (C) 2005-2006 by F.J. Cruz * Copyright (C) 2005-2007 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * This program 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 General Public License for more details. * * ============================================================ */ #include // TQt includes. #include #include #include // KDE includes. #include #include // Lcms includes. #include LCMS_HEADER #if LCMS_VERSION < 114 #define cmsTakeCopyright(profile) "Unknown" #endif // LCMS_VERSION < 114 // Local includes. #include "ddebug.h" #include "icctransform.h" namespace Digikam { class IccTransformPriv { public: IccTransformPriv() { has_embedded_profile = false; do_proof_profile = false; } bool do_proof_profile; bool has_embedded_profile; TQByteArray embedded_profile; TQByteArray input_profile; TQByteArray output_profile; TQByteArray proof_profile; }; IccTransform::IccTransform() { d = new IccTransformPriv; cmsErrorAction(LCMS_ERROR_SHOW); } IccTransform::~IccTransform() { delete d; } bool IccTransform::hasInputProfile() { return !(d->input_profile.isEmpty()); } bool IccTransform::hasOutputProfile() { return !(d->output_profile.isEmpty()); } TQByteArray IccTransform::embeddedProfile() const { return d->embedded_profile; } TQByteArray IccTransform::inputProfile() const { return d->input_profile; } TQByteArray IccTransform::outputProfile() const { return d->output_profile; } TQByteArray IccTransform::proofProfile() const { return d->proof_profile; } void IccTransform::getTransformType(bool do_proof_profile) { if (do_proof_profile) { d->do_proof_profile = true; } else { d->do_proof_profile = false; } } void IccTransform::getEmbeddedProfile(const DImg& image) { if (!image.getICCProfil().isNull()) { d->embedded_profile = image.getICCProfil(); d->has_embedded_profile = true; } } TQString IccTransform::getProfileDescription(const TQString& profile) { cmsHPROFILE _profile = cmsOpenProfileFromFile(TQFile::encodeName(profile), "r"); TQString _description = cmsTakeProductDesc(_profile); cmsCloseProfile(_profile); return _description; } int IccTransform::getRenderingIntent() { TDEConfig* config = kapp->config(); config->setGroup("Color Management"); return config->readNumEntry("RenderingIntent", 0); } bool IccTransform::getUseBPC() { TDEConfig* config = kapp->config(); config->setGroup("Color Management"); return config->readBoolEntry("BPCAlgorithm", false); } TQByteArray IccTransform::loadICCProfilFile(const TQString& filePath) { TQFile file(filePath); if ( !file.open(IO_ReadOnly) ) return TQByteArray(); TQByteArray data(file.size()); TQDataStream stream( &file ); stream.readRawBytes(data.data(), data.size()); file.close(); return data; } void IccTransform::setProfiles(const TQString& input_profile, const TQString& output_profile) { d->input_profile = loadICCProfilFile(input_profile); d->output_profile = loadICCProfilFile(output_profile); } void IccTransform::setProfiles(const TQString& input_profile, const TQString& output_profile, const TQString& proof_profile) { d->input_profile = loadICCProfilFile(input_profile); d->output_profile = loadICCProfilFile(output_profile); d->proof_profile = loadICCProfilFile(proof_profile); } void IccTransform::setProfiles(const TQString& output_profile) { d->output_profile = loadICCProfilFile(output_profile); } void IccTransform::setProfiles(const TQString& output_profile, const TQString& proof_profile, bool forProof) { if (forProof) { d->output_profile = loadICCProfilFile(output_profile); d->proof_profile = loadICCProfilFile(proof_profile); } } TQString IccTransform::getEmbeddedProfileDescriptor() { if (d->embedded_profile.isEmpty()) return TQString(); cmsHPROFILE tmpProfile = cmsOpenProfileFromMem(d->embedded_profile.data(), (DWORD)d->embedded_profile.size()); TQString embeddedProfileDescriptor = TQString(cmsTakeProductDesc(tmpProfile)); cmsCloseProfile(tmpProfile); return embeddedProfileDescriptor; } TQString IccTransform::getInputProfileDescriptor() { if (d->input_profile.isEmpty()) return TQString(); cmsHPROFILE tmpProfile = cmsOpenProfileFromMem(d->input_profile.data(), (DWORD)d->input_profile.size()); TQString embeddedProfileDescriptor = TQString(cmsTakeProductDesc(tmpProfile)); cmsCloseProfile(tmpProfile); return embeddedProfileDescriptor; } TQString IccTransform::getOutpoutProfileDescriptor() { if (d->output_profile.isEmpty()) return TQString(); cmsHPROFILE tmpProfile = cmsOpenProfileFromMem(d->output_profile.data(), (DWORD)d->output_profile.size()); TQString embeddedProfileDescriptor = TQString(cmsTakeProductDesc(tmpProfile)); cmsCloseProfile(tmpProfile); return embeddedProfileDescriptor; } TQString IccTransform::getProofProfileDescriptor() { if (d->proof_profile.isEmpty()) return TQString(); cmsHPROFILE tmpProfile = cmsOpenProfileFromMem(d->proof_profile.data(), (DWORD)d->proof_profile.size()); TQString embeddedProfileDescriptor = TQString(cmsTakeProductDesc(tmpProfile)); cmsCloseProfile(tmpProfile); return embeddedProfileDescriptor; } bool IccTransform::apply(DImg& image) { cmsHPROFILE inprofile=0, outprofile=0, proofprofile=0; cmsHTRANSFORM transform; int inputFormat = 0; int intent = INTENT_PERCEPTUAL; switch (getRenderingIntent()) { case 0: intent = INTENT_PERCEPTUAL; break; case 1: intent = INTENT_RELATIVE_COLORIMETRIC; break; case 2: intent = INTENT_SATURATION; break; case 3: intent = INTENT_ABSOLUTE_COLORIMETRIC; break; } //DDebug() << "Intent is: " << intent << endl; if (d->has_embedded_profile) { inprofile = cmsOpenProfileFromMem(d->embedded_profile.data(), (DWORD)d->embedded_profile.size()); } else { inprofile = cmsOpenProfileFromMem(d->input_profile.data(), (DWORD)d->input_profile.size()); } if (inprofile == NULL) { DDebug() << "Error: Input profile is NULL" << endl; cmsCloseProfile(inprofile); return false; } // if (d->has_embedded_profile) // { // outprofile = cmsOpenProfileFromMem(d->embedded_profile.data(), // (DWORD)d->embedded_profile.size()); // } // else // { outprofile = cmsOpenProfileFromMem(d->output_profile.data(), (DWORD)d->output_profile.size()); // } if (outprofile == NULL) { DDebug() << "Error: Output profile is NULL" << endl; cmsCloseProfile(outprofile); return false; } if (!d->do_proof_profile) { if (image.sixteenBit()) { if (image.hasAlpha()) { switch (cmsGetColorSpace(inprofile)) { case icSigGrayData: inputFormat = TYPE_GRAYA_16; break; case icSigCmykData: inputFormat = TYPE_CMYK_16; break; default: inputFormat = TYPE_BGRA_16; } transform = cmsCreateTransform( inprofile, inputFormat, outprofile, TYPE_BGRA_16, intent, cmsFLAGS_WHITEBLACKCOMPENSATION); if (!transform) { DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; return false; } } else { switch (cmsGetColorSpace(inprofile)) { case icSigGrayData: inputFormat = TYPE_GRAY_16; break; case icSigCmykData: inputFormat = TYPE_CMYK_16; break; default: inputFormat = TYPE_BGR_16; } transform = cmsCreateTransform( inprofile, inputFormat, outprofile, TYPE_BGR_16, intent, cmsFLAGS_WHITEBLACKCOMPENSATION); if (!transform) { DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; return false; } } } else { if (image.hasAlpha()) { switch (cmsGetColorSpace(inprofile)) { case icSigGrayData: inputFormat = TYPE_GRAYA_8; break; case icSigCmykData: inputFormat = TYPE_CMYK_8; break; default: inputFormat = TYPE_BGRA_8; } transform = cmsCreateTransform( inprofile, inputFormat, outprofile, TYPE_BGRA_8, intent, cmsFLAGS_WHITEBLACKCOMPENSATION); if (!transform) { DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; return false; } } else { switch (cmsGetColorSpace(inprofile)) { case icSigGrayData: inputFormat = TYPE_GRAYA_8; break; case icSigCmykData: inputFormat = TYPE_CMYK_8; //DDebug() << "input profile: cmyk no alpha" << endl; break; default: inputFormat = TYPE_BGR_8; //DDebug() << "input profile: default no alpha" << endl; } transform = cmsCreateTransform(inprofile, inputFormat, outprofile, TYPE_BGR_8, intent, cmsFLAGS_WHITEBLACKCOMPENSATION); if (!transform) { DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; return false; } } } } else { proofprofile = cmsOpenProfileFromMem(d->proof_profile.data(), (DWORD)d->proof_profile.size()); if (proofprofile == NULL) { DDebug() << "Error: Input profile is NULL" << endl; cmsCloseProfile(inprofile); cmsCloseProfile(outprofile); return false; } if (image.sixteenBit()) { if (image.hasAlpha()) { transform = cmsCreateProofingTransform( inprofile, TYPE_BGRA_16, outprofile, TYPE_BGRA_16, proofprofile, INTENT_ABSOLUTE_COLORIMETRIC, INTENT_ABSOLUTE_COLORIMETRIC, cmsFLAGS_WHITEBLACKCOMPENSATION); if (!transform) { DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; return false; } } else { transform = cmsCreateProofingTransform( inprofile, TYPE_BGR_16, outprofile, TYPE_BGR_16, proofprofile, INTENT_ABSOLUTE_COLORIMETRIC, INTENT_ABSOLUTE_COLORIMETRIC, cmsFLAGS_WHITEBLACKCOMPENSATION); if (!transform) { DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; return false; } } } else { if (image.hasAlpha()) { transform = cmsCreateProofingTransform( inprofile, TYPE_BGR_8, outprofile, TYPE_BGR_8, proofprofile, INTENT_ABSOLUTE_COLORIMETRIC, INTENT_ABSOLUTE_COLORIMETRIC, cmsFLAGS_WHITEBLACKCOMPENSATION); if (!transform) { DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; return false; } } else { transform = cmsCreateProofingTransform( inprofile, TYPE_BGR_8, outprofile, TYPE_BGR_8, proofprofile, INTENT_ABSOLUTE_COLORIMETRIC, INTENT_ABSOLUTE_COLORIMETRIC, cmsFLAGS_WHITEBLACKCOMPENSATION); if (!transform) { DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; return false; } } } } // We need to work using temp pixel buffer to apply ICC transformations. uchar transdata[image.bytesDepth()]; // Always working with uchar* prevent endianess problem. uchar *data = image.bits(); // We scan all image pixels one by one. for (uint i=0; i < image.width()*image.height()*image.bytesDepth(); i+=image.bytesDepth()) { // Apply ICC transformations. cmsDoTransform( transform, &data[i], &transdata[0], 1); // Copy buffer to source to update original image with ICC corrections. // Alpha channel is restored in all cases. memcpy (&data[i], &transdata[0], (image.bytesDepth() == 8) ? 6 : 3); } cmsDeleteTransform(transform); cmsCloseProfile(inprofile); cmsCloseProfile(outprofile); if (d->do_proof_profile) cmsCloseProfile(proofprofile); return true; } bool IccTransform::apply( DImg& image, TQByteArray& profile, int intent, bool useBPC, bool checkGamut, bool useBuiltin ) { cmsHPROFILE inprofile=0, outprofile=0, proofprofile=0; cmsHTRANSFORM transform; int transformFlags = 0, inputFormat = 0; switch (intent) { case 0: intent = INTENT_PERCEPTUAL; break; case 1: intent = INTENT_RELATIVE_COLORIMETRIC; break; case 2: intent = INTENT_SATURATION; break; case 3: intent = INTENT_ABSOLUTE_COLORIMETRIC; break; } //DDebug() << "Intent is: " << intent << endl; if (!profile.isNull()) { inprofile = cmsOpenProfileFromMem(profile.data(), (DWORD)profile.size()); } else if (useBuiltin) { inprofile = cmsCreate_sRGBProfile(); } else { inprofile = cmsOpenProfileFromMem(d->input_profile.data(), (DWORD)d->input_profile.size()); } if (inprofile == NULL) { DDebug() << "Error: Input profile is NULL" << endl; return false; } outprofile = cmsOpenProfileFromMem(d->output_profile.data(), (DWORD)d->output_profile.size()); if (outprofile == NULL) { DDebug() << "Error: Output profile is NULL" << endl; cmsCloseProfile(inprofile); return false; } if (useBPC) { transformFlags |= cmsFLAGS_WHITEBLACKCOMPENSATION; } if (!d->do_proof_profile) { if (image.sixteenBit()) { if (image.hasAlpha()) { switch (cmsGetColorSpace(inprofile)) { case icSigGrayData: inputFormat = TYPE_GRAYA_16; break; case icSigCmykData: inputFormat = TYPE_CMYK_16; break; default: inputFormat = TYPE_BGRA_16; } transform = cmsCreateTransform( inprofile, inputFormat, outprofile, TYPE_BGRA_16, intent, transformFlags); if (!transform) { DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; return false; } } else { switch (cmsGetColorSpace(inprofile)) { case icSigGrayData: inputFormat = TYPE_GRAY_16; break; case icSigCmykData: inputFormat = TYPE_CMYK_16; break; default: inputFormat = TYPE_BGR_16; } transform = cmsCreateTransform( inprofile, inputFormat, outprofile, TYPE_BGR_16, intent, transformFlags); if (!transform) { DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; return false; } } } else { if (image.hasAlpha()) { switch (cmsGetColorSpace(inprofile)) { case icSigGrayData: inputFormat = TYPE_GRAYA_8; break; case icSigCmykData: inputFormat = TYPE_CMYK_8; break; default: inputFormat = TYPE_BGRA_8; } transform = cmsCreateTransform( inprofile, inputFormat, outprofile, TYPE_BGRA_8, intent, transformFlags); if (!transform) { DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; return false; } } else { switch (cmsGetColorSpace(inprofile)) { case icSigGrayData: inputFormat = TYPE_GRAY_8; break; case icSigCmykData: inputFormat = TYPE_CMYK_8; break; default: inputFormat = TYPE_BGR_8; } transform = cmsCreateTransform( inprofile, inputFormat, outprofile, TYPE_BGR_8, intent, transformFlags); if (!transform) { DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; return false; } } } } else { proofprofile = cmsOpenProfileFromMem(d->proof_profile.data(), (DWORD)d->proof_profile.size()); if (proofprofile == NULL) { DDebug() << "Error: Input profile is NULL" << endl; cmsCloseProfile(inprofile); cmsCloseProfile(outprofile); return false; } transformFlags |= cmsFLAGS_SOFTPROOFING; if (checkGamut) { cmsSetAlarmCodes(126, 255, 255); transformFlags |= cmsFLAGS_GAMUTCHECK; } if (image.sixteenBit()) { if (image.hasAlpha()) { transform = cmsCreateProofingTransform( inprofile, TYPE_BGRA_16, outprofile, TYPE_BGRA_16, proofprofile, intent, intent, transformFlags); if (!transform) { DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; return false; } } else { transform = cmsCreateProofingTransform( inprofile, TYPE_BGR_16, outprofile, TYPE_BGR_16, proofprofile, intent, intent, transformFlags); if (!transform) { DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; return false; } } } else { if (image.hasAlpha()) { transform = cmsCreateProofingTransform( inprofile, TYPE_BGR_8, outprofile, TYPE_BGR_8, proofprofile, intent, intent, transformFlags); if (!transform) { DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; return false; } } else { transform = cmsCreateProofingTransform( inprofile, TYPE_BGR_8, outprofile, TYPE_BGR_8, proofprofile, intent, intent, transformFlags); if (!transform) { DDebug() << k_funcinfo << "LCMS internal error: cannot create a color transform instance" << endl; return false; } } } } //DDebug() << "Transform flags are: " << transformFlags << endl; // We need to work using temp pixel buffer to apply ICC transformations. uchar transdata[image.bytesDepth()]; // Always working with uchar* prevent endianess problem. uchar *data = image.bits(); // We scan all image pixels one by one. for (uint i=0; i < image.width()*image.height()*image.bytesDepth(); i+=image.bytesDepth()) { // Apply ICC transformations. cmsDoTransform( transform, &data[i], &transdata[0], 1); // Copy buffer to source to update original image with ICC corrections. // Alpha channel is restored in all cases. memcpy (&data[i], &transdata[0], (image.bytesDepth() == 8) ? 6 : 3); } cmsDeleteTransform(transform); cmsCloseProfile(inprofile); cmsCloseProfile(outprofile); if (d->do_proof_profile) cmsCloseProfile(proofprofile); return true; } } // NameSpace Digikam