// SPDX-License-Identifier: GPL-2.0-or-later /** @file * TODO: insert short description here *//* * Authors: see git history * * Copyright (C) 2018 Authors * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #ifdef HAVE_CONFIG_H # include "config.h" // only include where actually required! #endif #define noDEBUG_LCMS #include #include #include #include #ifdef DEBUG_LCMS #include #endif // DEBUG_LCMS #include #include #include #include #include #ifdef _WIN32 #include #endif #include #include "xml/repr.h" #include "color.h" #include "color-profile.h" #include "cms-system.h" #include "color-profile-cms-fns.h" #include "attributes.h" #include "inkscape.h" #include "document.h" #include "preferences.h" #include #include #include "uri.h" #ifdef _WIN32 #include #endif // _WIN32 using Inkscape::ColorProfile; using Inkscape::ColorProfileImpl; namespace { cmsHPROFILE getSystemProfileHandle(); cmsHPROFILE getProofProfileHandle(); void loadProfiles(); Glib::ustring getNameFromProfile(cmsHPROFILE profile); } #ifdef DEBUG_LCMS extern guint update_in_progress; #define DEBUG_MESSAGE_SCISLAC(key, ...) \ {\ Inkscape::Preferences *prefs = Inkscape::Preferences::get();\ bool dump = prefs->getBool(Glib::ustring("/options/scislac/") + #key);\ bool dumpD = prefs->getBool(Glib::ustring("/options/scislac/") + #key"D");\ bool dumpD2 = prefs->getBool(Glib::ustring("/options/scislac/") + #key"D2");\ dumpD &= ( (update_in_progress == 0) || dumpD2 );\ if ( dump )\ {\ g_message( __VA_ARGS__ );\ \ }\ if ( dumpD )\ {\ GtkWidget *dialog = gtk_message_dialog_new(NULL,\ GTK_DIALOG_DESTROY_WITH_PARENT, \ GTK_MESSAGE_INFO, \ GTK_BUTTONS_OK, \ __VA_ARGS__ \ );\ g_signal_connect_swapped(dialog, "response",\ G_CALLBACK(gtk_widget_destroy), \ dialog); \ gtk_widget_show_all( dialog );\ }\ } #define DEBUG_MESSAGE(key, ...)\ {\ g_message( __VA_ARGS__ );\ } #else #define DEBUG_MESSAGE_SCISLAC(key, ...) #define DEBUG_MESSAGE(key, ...) #endif // DEBUG_LCMS namespace Inkscape { class ColorProfileImpl { public: static cmsHPROFILE _sRGBProf; static cmsHPROFILE _NullProf; ColorProfileImpl(); static cmsUInt32Number _getInputFormat( cmsColorSpaceSignature space ); static cmsHPROFILE getNULLProfile(); static cmsHPROFILE getSRGBProfile(); void _clearProfile(); cmsHPROFILE _profHandle; cmsProfileClassSignature _profileClass; cmsColorSpaceSignature _profileSpace; cmsHTRANSFORM _transf; cmsHTRANSFORM _revTransf; cmsHTRANSFORM _gamutTransf; }; cmsColorSpaceSignature asICColorSpaceSig(ColorSpaceSig const & sig) { return ColorSpaceSigWrapper(sig); } cmsProfileClassSignature asICColorProfileClassSig(ColorProfileClassSig const & sig) { return ColorProfileClassSigWrapper(sig); } } // namespace Inkscape ColorProfileImpl::ColorProfileImpl() : _profHandle(nullptr), _profileClass(cmsSigInputClass), _profileSpace(cmsSigRgbData), _transf(nullptr), _revTransf(nullptr), _gamutTransf(nullptr) { } cmsHPROFILE ColorProfileImpl::_sRGBProf = nullptr; cmsHPROFILE ColorProfileImpl::getSRGBProfile() { if ( !_sRGBProf ) { _sRGBProf = cmsCreate_sRGBProfile(); } return ColorProfileImpl::_sRGBProf; } cmsHPROFILE ColorProfileImpl::_NullProf = nullptr; cmsHPROFILE ColorProfileImpl::getNULLProfile() { if ( !_NullProf ) { _NullProf = cmsCreateNULLProfile(); } return _NullProf; } ColorProfile::FilePlusHome::FilePlusHome(Glib::ustring filename, bool isInHome) : filename(std::move(filename)), isInHome(isInHome) { } ColorProfile::FilePlusHome::FilePlusHome(const ColorProfile::FilePlusHome &filePlusHome) : FilePlusHome(filePlusHome.filename, filePlusHome.isInHome) { } bool ColorProfile::FilePlusHome::operator<(FilePlusHome const &other) const { // if one is from home folder, other from global folder, sort home folder first. cf bug 1457126 bool result; if (this->isInHome != other.isInHome) result = this->isInHome; else result = this->filename < other.filename; return result; } ColorProfile::FilePlusHomeAndName::FilePlusHomeAndName(ColorProfile::FilePlusHome filePlusHome, Glib::ustring name) : FilePlusHome(filePlusHome), name(std::move(name)) { } bool ColorProfile::FilePlusHomeAndName::operator<(ColorProfile::FilePlusHomeAndName const &other) const { bool result; if (this->isInHome != other.isInHome) result = this->isInHome; else result = this->name < other.name; return result; } ColorProfile::ColorProfile() : SPObject() { this->impl = new ColorProfileImpl(); this->href = nullptr; this->local = nullptr; this->name = nullptr; this->intentStr = nullptr; this->rendering_intent = Inkscape::RENDERING_INTENT_UNKNOWN; } ColorProfile::~ColorProfile() = default; bool ColorProfile::operator<(ColorProfile const &other) const { gchar *a_name_casefold = g_utf8_casefold(this->name, -1 ); gchar *b_name_casefold = g_utf8_casefold(other.name, -1 ); int result = g_strcmp0(a_name_casefold, b_name_casefold); g_free(a_name_casefold); g_free(b_name_casefold); return result < 0; } /** * Callback: free object */ void ColorProfile::release() { // Unregister ourselves if ( this->document ) { this->document->removeResource("iccprofile", this); } if ( this->href ) { g_free( this->href ); this->href = nullptr; } if ( this->local ) { g_free( this->local ); this->local = nullptr; } if ( this->name ) { g_free( this->name ); this->name = nullptr; } if ( this->intentStr ) { g_free( this->intentStr ); this->intentStr = nullptr; } this->impl->_clearProfile(); delete this->impl; this->impl = nullptr; } void ColorProfileImpl::_clearProfile() { _profileSpace = cmsSigRgbData; if ( _transf ) { cmsDeleteTransform( _transf ); _transf = nullptr; } if ( _revTransf ) { cmsDeleteTransform( _revTransf ); _revTransf = nullptr; } if ( _gamutTransf ) { cmsDeleteTransform( _gamutTransf ); _gamutTransf = nullptr; } if ( _profHandle ) { cmsCloseProfile( _profHandle ); _profHandle = nullptr; } } /** * Callback: set attributes from associated repr. */ void ColorProfile::build(SPDocument *document, Inkscape::XML::Node *repr) { g_assert(this->href == nullptr); g_assert(this->local == nullptr); g_assert(this->name == nullptr); g_assert(this->intentStr == nullptr); SPObject::build(document, repr); this->readAttr(SPAttr::XLINK_HREF); this->readAttr(SPAttr::ID); this->readAttr(SPAttr::LOCAL); this->readAttr(SPAttr::NAME); this->readAttr(SPAttr::RENDERING_INTENT); // Register if ( document ) { document->addResource( "iccprofile", this ); } } /** * Callback: set attribute. */ void ColorProfile::set(SPAttr key, gchar const *value) { switch (key) { case SPAttr::XLINK_HREF: if ( this->href ) { g_free( this->href ); this->href = nullptr; } if ( value ) { this->href = g_strdup( value ); if ( *this->href ) { // TODO open filename and URIs properly //FILE* fp = fopen_utf8name( filename, "r" ); //LCMSAPI cmsHPROFILE LCMSEXPORT cmsOpenProfileFromMem(LPVOID MemPtr, cmsUInt32Number dwSize); // Try to open relative SPDocument *doc = this->document; if (!doc) { doc = SP_ACTIVE_DOCUMENT; g_warning("this has no document. using active"); } //# 1. Get complete filename of document gchar const *docbase = doc->getDocumentFilename(); Inkscape::URI docUri(""); if (docbase) { // The file has already been saved docUri = Inkscape::URI::from_native_filename(docbase); } this->impl->_clearProfile(); try { auto hrefUri = Inkscape::URI(this->href, docUri); auto contents = hrefUri.getContents(); this->impl->_profHandle = cmsOpenProfileFromMem(contents.data(), contents.size()); } catch (...) { g_warning("Failed to open CMS profile URI '%.100s'", this->href); } if ( this->impl->_profHandle ) { this->impl->_profileSpace = cmsGetColorSpace( this->impl->_profHandle ); this->impl->_profileClass = cmsGetDeviceClass( this->impl->_profHandle ); } DEBUG_MESSAGE( lcmsOne, "cmsOpenProfileFromFile( '%s'...) = %p", fullname, (void*)this->impl->_profHandle ); } } this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SPAttr::LOCAL: if ( this->local ) { g_free( this->local ); this->local = nullptr; } this->local = g_strdup( value ); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SPAttr::NAME: if ( this->name ) { g_free( this->name ); this->name = nullptr; } this->name = g_strdup( value ); DEBUG_MESSAGE( lcmsTwo, " name set to '%s'", this->name ); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SPAttr::RENDERING_INTENT: if ( this->intentStr ) { g_free( this->intentStr ); this->intentStr = nullptr; } this->intentStr = g_strdup( value ); if ( value ) { if ( strcmp( value, "auto" ) == 0 ) { this->rendering_intent = RENDERING_INTENT_AUTO; } else if ( strcmp( value, "perceptual" ) == 0 ) { this->rendering_intent = RENDERING_INTENT_PERCEPTUAL; } else if ( strcmp( value, "relative-colorimetric" ) == 0 ) { this->rendering_intent = RENDERING_INTENT_RELATIVE_COLORIMETRIC; } else if ( strcmp( value, "saturation" ) == 0 ) { this->rendering_intent = RENDERING_INTENT_SATURATION; } else if ( strcmp( value, "absolute-colorimetric" ) == 0 ) { this->rendering_intent = RENDERING_INTENT_ABSOLUTE_COLORIMETRIC; } else { this->rendering_intent = RENDERING_INTENT_UNKNOWN; } } else { this->rendering_intent = RENDERING_INTENT_UNKNOWN; } this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; default: SPObject::set(key, value); break; } } /** * Callback: write attributes to associated repr. */ Inkscape::XML::Node* ColorProfile::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) { if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { repr = xml_doc->createElement("svg:color-profile"); } if ( (flags & SP_OBJECT_WRITE_ALL) || this->href ) { repr->setAttribute( "xlink:href", this->href ); } if ( (flags & SP_OBJECT_WRITE_ALL) || this->local ) { repr->setAttribute( "local", this->local ); } if ( (flags & SP_OBJECT_WRITE_ALL) || this->name ) { repr->setAttribute( "name", this->name ); } if ( (flags & SP_OBJECT_WRITE_ALL) || this->intentStr ) { repr->setAttribute( "rendering-intent", this->intentStr ); } SPObject::write(xml_doc, repr, flags); return repr; } struct MapMap { cmsColorSpaceSignature space; cmsUInt32Number inForm; }; cmsUInt32Number ColorProfileImpl::_getInputFormat( cmsColorSpaceSignature space ) { MapMap possible[] = { {cmsSigXYZData, TYPE_XYZ_16}, {cmsSigLabData, TYPE_Lab_16}, //cmsSigLuvData {cmsSigYCbCrData, TYPE_YCbCr_16}, {cmsSigYxyData, TYPE_Yxy_16}, {cmsSigRgbData, TYPE_RGB_16}, {cmsSigGrayData, TYPE_GRAY_16}, {cmsSigHsvData, TYPE_HSV_16}, {cmsSigHlsData, TYPE_HLS_16}, {cmsSigCmykData, TYPE_CMYK_16}, {cmsSigCmyData, TYPE_CMY_16}, }; int index = 0; for ( guint i = 0; i < G_N_ELEMENTS(possible); i++ ) { if ( possible[i].space == space ) { index = i; break; } } return possible[index].inForm; } static int getLcmsIntent( guint svgIntent ) { int intent = INTENT_PERCEPTUAL; switch ( svgIntent ) { case Inkscape::RENDERING_INTENT_RELATIVE_COLORIMETRIC: intent = INTENT_RELATIVE_COLORIMETRIC; break; case Inkscape::RENDERING_INTENT_SATURATION: intent = INTENT_SATURATION; break; case Inkscape::RENDERING_INTENT_ABSOLUTE_COLORIMETRIC: intent = INTENT_ABSOLUTE_COLORIMETRIC; break; case Inkscape::RENDERING_INTENT_PERCEPTUAL: case Inkscape::RENDERING_INTENT_UNKNOWN: case Inkscape::RENDERING_INTENT_AUTO: default: intent = INTENT_PERCEPTUAL; } return intent; } static ColorProfile *bruteFind(SPDocument *document, gchar const *name) { std::vector current = document->getResourceList("iccprofile"); for (auto *obj : current) { if (auto prof = dynamic_cast(obj)) { if ( prof->name && (strcmp(prof->name, name) == 0) ) { return prof; } } } return nullptr; } cmsHPROFILE Inkscape::CMSSystem::getHandle( SPDocument* document, guint* intent, gchar const* name ) { cmsHPROFILE prof = nullptr; auto *thing = bruteFind(document, name); if ( thing ) { prof = thing->impl->_profHandle; } if ( intent ) { *intent = thing ? thing->rendering_intent : (guint)RENDERING_INTENT_UNKNOWN; } DEBUG_MESSAGE( lcmsThree, " queried for profile of '%s'. Returning %p with intent of %d", name, prof, (intent? *intent:0) ); return prof; } Inkscape::ColorSpaceSig ColorProfile::getColorSpace() const { return ColorSpaceSigWrapper(impl->_profileSpace); } Inkscape::ColorProfileClassSig ColorProfile::getProfileClass() const { return ColorProfileClassSigWrapper(impl->_profileClass); } cmsHTRANSFORM ColorProfile::getTransfToSRGB8() { if ( !impl->_transf && impl->_profHandle ) { int intent = getLcmsIntent(rendering_intent); impl->_transf = cmsCreateTransform( impl->_profHandle, ColorProfileImpl::_getInputFormat(impl->_profileSpace), ColorProfileImpl::getSRGBProfile(), TYPE_RGBA_8, intent, 0 ); } return impl->_transf; } cmsHTRANSFORM ColorProfile::getTransfFromSRGB8() { if ( !impl->_revTransf && impl->_profHandle ) { int intent = getLcmsIntent(rendering_intent); impl->_revTransf = cmsCreateTransform( ColorProfileImpl::getSRGBProfile(), TYPE_RGBA_8, impl->_profHandle, ColorProfileImpl::_getInputFormat(impl->_profileSpace), intent, 0 ); } return impl->_revTransf; } cmsHTRANSFORM ColorProfile::getTransfGamutCheck() { if ( !impl->_gamutTransf ) { impl->_gamutTransf = cmsCreateProofingTransform(ColorProfileImpl::getSRGBProfile(), TYPE_BGRA_8, ColorProfileImpl::getNULLProfile(), TYPE_GRAY_8, impl->_profHandle, INTENT_RELATIVE_COLORIMETRIC, INTENT_RELATIVE_COLORIMETRIC, (cmsFLAGS_GAMUTCHECK | cmsFLAGS_SOFTPROOFING)); } return impl->_gamutTransf; } bool ColorProfile::GamutCheck(SPColor color) { guint32 val = color.toRGBA32(0); cmsUInt16Number oldAlarmCodes[cmsMAXCHANNELS] = {0}; cmsGetAlarmCodes(oldAlarmCodes); cmsUInt16Number newAlarmCodes[cmsMAXCHANNELS] = {0}; newAlarmCodes[0] = ~0; cmsSetAlarmCodes(newAlarmCodes); cmsUInt8Number outofgamut = 0; guchar check_color[4] = { static_cast(SP_RGBA32_R_U(val)), static_cast(SP_RGBA32_G_U(val)), static_cast(SP_RGBA32_B_U(val)), 255}; cmsHTRANSFORM gamutCheck = ColorProfile::getTransfGamutCheck(); if (gamutCheck) { cmsDoTransform(gamutCheck, &check_color, &outofgamut, 1); } cmsSetAlarmCodes(oldAlarmCodes); return (outofgamut != 0); } class ProfileInfo { public: ProfileInfo( cmsHPROFILE prof, Glib::ustring path ); Glib::ustring const& getName() {return _name;} Glib::ustring const& getPath() {return _path;} cmsColorSpaceSignature getSpace() {return _profileSpace;} cmsProfileClassSignature getClass() {return _profileClass;} private: Glib::ustring _path; Glib::ustring _name; cmsColorSpaceSignature _profileSpace; cmsProfileClassSignature _profileClass; }; ProfileInfo::ProfileInfo( cmsHPROFILE prof, Glib::ustring path ) : _path(std::move( path )), _name( getNameFromProfile(prof) ), _profileSpace( cmsGetColorSpace( prof ) ), _profileClass( cmsGetDeviceClass( prof ) ) { } static std::vector knownProfiles; std::vector Inkscape::CMSSystem::getDisplayNames() { loadProfiles(); std::vector result; for (auto & knownProfile : knownProfiles) { if ( knownProfile.getClass() == cmsSigDisplayClass && knownProfile.getSpace() == cmsSigRgbData ) { result.push_back( knownProfile.getName() ); } } std::sort(result.begin(), result.end()); return result; } std::vector Inkscape::CMSSystem::getSoftproofNames() { loadProfiles(); std::vector result; for (auto & knownProfile : knownProfiles) { if ( knownProfile.getClass() == cmsSigOutputClass ) { result.push_back( knownProfile.getName() ); } } std::sort(result.begin(), result.end()); return result; } Glib::ustring Inkscape::CMSSystem::getPathForProfile(Glib::ustring const& name) { loadProfiles(); Glib::ustring result; for (auto & knownProfile : knownProfiles) { if ( name == knownProfile.getName() ) { result = knownProfile.getPath(); break; } } return result; } void Inkscape::CMSSystem::doTransform(cmsHTRANSFORM transform, void *inBuf, void *outBuf, unsigned int size) { cmsDoTransform(transform, inBuf, outBuf, size); } bool Inkscape::CMSSystem::isPrintColorSpace(ColorProfile const *profile) { bool isPrint = false; if ( profile ) { ColorSpaceSigWrapper colorspace = profile->getColorSpace(); isPrint = (colorspace == cmsSigCmykData) || (colorspace == cmsSigCmyData); } return isPrint; } gint Inkscape::CMSSystem::getChannelCount(ColorProfile const *profile) { gint count = 0; if ( profile ) { count = cmsChannelsOf( asICColorSpaceSig(profile->getColorSpace()) ); } return count; } // the bool return value tells if it's a user's directory or a system location // note that this will treat places under $HOME as system directories when they are found via $XDG_DATA_DIRS std::set ColorProfile::getBaseProfileDirs() { static bool warnSet = false; if (!warnSet) { warnSet = true; } std::set sources; // first try user's local dir gchar* path = g_build_filename(g_get_user_data_dir(), "color", "icc", nullptr); sources.insert(FilePlusHome(path, true)); g_free(path); // search colord ICC store paths // (see https://github.com/hughsie/colord/blob/fe10f76536bb27614ced04e0ff944dc6fb4625c0/lib/colord/cd-icc-store.c#L590) // user store path = g_build_filename(g_get_user_data_dir(), "icc", nullptr); sources.insert(FilePlusHome(path, true)); g_free(path); path = g_build_filename(g_get_home_dir(), ".color", "icc", nullptr); sources.insert(FilePlusHome(path, true)); g_free(path); // machine store sources.insert(FilePlusHome("/var/lib/color/icc", false)); sources.insert(FilePlusHome("/var/lib/colord/icc", false)); const gchar* const * dataDirs = g_get_system_data_dirs(); for ( int i = 0; dataDirs[i]; i++ ) { gchar* path = g_build_filename(dataDirs[i], "color", "icc", nullptr); sources.insert(FilePlusHome(path, false)); g_free(path); } // On OS X: { sources.insert(FilePlusHome("/System/Library/ColorSync/Profiles", false)); sources.insert(FilePlusHome("/Library/ColorSync/Profiles", false)); gchar *path = g_build_filename(g_get_home_dir(), "Library", "ColorSync", "Profiles", nullptr); sources.insert(FilePlusHome(path, true)); g_free(path); } #ifdef _WIN32 wchar_t pathBuf[MAX_PATH + 1]; pathBuf[0] = 0; DWORD pathSize = sizeof(pathBuf); g_assert(sizeof(wchar_t) == sizeof(gunichar2)); if ( GetColorDirectoryW( NULL, pathBuf, &pathSize ) ) { gchar * utf8Path = g_utf16_to_utf8( (gunichar2*)(&pathBuf[0]), -1, NULL, NULL, NULL ); if ( !g_utf8_validate(utf8Path, -1, NULL) ) { g_warning( "GetColorDirectoryW() resulted in invalid UTF-8" ); } else { sources.insert(FilePlusHome(utf8Path, false)); } g_free( utf8Path ); } #endif // _WIN32 return sources; } static bool isIccFile( gchar const *filepath ) { bool isIccFile = false; GStatBuf st; if ( g_stat(filepath, &st) == 0 && (st.st_size > 128) ) { //0-3 == size //36-39 == 'acsp' 0x61637370 int fd = g_open( filepath, O_RDONLY, S_IRWXU); if ( fd != -1 ) { guchar scratch[40] = {0}; size_t len = sizeof(scratch); //size_t left = 40; ssize_t got = read(fd, scratch, len); if ( got != -1 ) { size_t calcSize = (scratch[0] << 24) | (scratch[1] << 16) | (scratch[2] << 8) | scratch[3]; if ( calcSize > 128 && calcSize <= static_cast(st.st_size) ) { isIccFile = (scratch[36] == 'a') && (scratch[37] == 'c') && (scratch[38] == 's') && (scratch[39] == 'p'); } } close(fd); if (isIccFile) { cmsHPROFILE prof = cmsOpenProfileFromFile( filepath, "r" ); if ( prof ) { cmsProfileClassSignature profClass = cmsGetDeviceClass(prof); if ( profClass == cmsSigNamedColorClass ) { isIccFile = false; // Ignore named color profiles for now. } cmsCloseProfile( prof ); } } } } return isIccFile; } std::set ColorProfile::getProfileFiles() { std::set files; using Inkscape::IO::Resource::get_filenames; for (auto &path: ColorProfile::getBaseProfileDirs()) { for(auto &filename: get_filenames(path.filename, {".icc", ".icm"})) { if ( isIccFile(filename.c_str()) ) { files.insert(FilePlusHome(filename, path.isInHome)); } } } return files; } std::set ColorProfile::getProfileFilesWithNames() { std::set result; for (auto &profile: getProfileFiles()) { cmsHPROFILE hProfile = cmsOpenProfileFromFile(profile.filename.c_str(), "r"); if ( hProfile ) { Glib::ustring name = getNameFromProfile(hProfile); result.insert( FilePlusHomeAndName(profile, name) ); cmsCloseProfile(hProfile); } } return result; } void errorHandlerCB(cmsContext /*contextID*/, cmsUInt32Number errorCode, char const *errorText) { g_message("lcms: Error %d", errorCode); g_message(" %p", errorText); //g_message("lcms: Error %d; %s", errorCode, errorText); } namespace { Glib::ustring getNameFromProfile(cmsHPROFILE profile) { Glib::ustring nameStr; if ( profile ) { cmsUInt32Number byteLen = cmsGetProfileInfo(profile, cmsInfoDescription, "en", "US", nullptr, 0); if (byteLen > 0) { // TODO investigate wchar_t and cmsGetProfileInfo() std::vector data(byteLen); cmsUInt32Number readLen = cmsGetProfileInfoASCII(profile, cmsInfoDescription, "en", "US", data.data(), data.size()); if (readLen < data.size()) { data.resize(readLen); } nameStr = Glib::ustring(data.begin(), data.end()); } if (nameStr.empty() || !g_utf8_validate(nameStr.c_str(), -1, nullptr)) { nameStr = _("(invalid UTF-8 string)"); } } return nameStr; } /** * This function loads or refreshes data in knownProfiles. * Call it at the start of every call that requires this data. */ void loadProfiles() { static bool error_handler_set = false; if (!error_handler_set) { //cmsSetLogErrorHandler(errorHandlerCB); //g_message("LCMS error handler set"); error_handler_set = true; } static bool profiles_searched = false; if ( !profiles_searched ) { knownProfiles.clear(); for (auto &profile: ColorProfile::getProfileFiles()) { cmsHPROFILE prof = cmsOpenProfileFromFile( profile.filename.c_str(), "r" ); if ( prof ) { ProfileInfo info( prof, Glib::filename_to_utf8( profile.filename.c_str() ) ); cmsCloseProfile( prof ); prof = nullptr; bool sameName = false; for(auto &knownProfile: knownProfiles) { if ( knownProfile.getName() == info.getName() ) { sameName = true; break; } } if ( !sameName ) { knownProfiles.push_back(info); } } } profiles_searched = true; } } } // namespace static bool gamutWarn = false; static Gdk::RGBA lastGamutColor("#808080"); static bool lastBPC = false; #if defined(cmsFLAGS_PRESERVEBLACK) static bool lastPreserveBlack = false; #endif // defined(cmsFLAGS_PRESERVEBLACK) static int lastIntent = INTENT_PERCEPTUAL; static int lastProofIntent = INTENT_PERCEPTUAL; static cmsHTRANSFORM transf = nullptr; namespace { cmsHPROFILE getSystemProfileHandle() { static cmsHPROFILE theOne = nullptr; static Glib::ustring lastURI; loadProfiles(); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); Glib::ustring uri = prefs->getString("/options/displayprofile/uri"); if ( !uri.empty() ) { if ( uri != lastURI ) { lastURI.clear(); if ( theOne ) { cmsCloseProfile( theOne ); } if ( transf ) { cmsDeleteTransform( transf ); transf = nullptr; } theOne = cmsOpenProfileFromFile( uri.data(), "r" ); if ( theOne ) { // a display profile must have the proper stuff cmsColorSpaceSignature space = cmsGetColorSpace(theOne); cmsProfileClassSignature profClass = cmsGetDeviceClass(theOne); if ( profClass != cmsSigDisplayClass ) { g_warning("Not a display profile"); cmsCloseProfile( theOne ); theOne = nullptr; } else if ( space != cmsSigRgbData ) { g_warning("Not an RGB profile"); cmsCloseProfile( theOne ); theOne = nullptr; } else { lastURI = uri; } } } } else if ( theOne ) { cmsCloseProfile( theOne ); theOne = nullptr; lastURI.clear(); if ( transf ) { cmsDeleteTransform( transf ); transf = nullptr; } } return theOne; } cmsHPROFILE getProofProfileHandle() { static cmsHPROFILE theOne = nullptr; static Glib::ustring lastURI; loadProfiles(); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); bool which = prefs->getBool( "/options/softproof/enable"); Glib::ustring uri = prefs->getString("/options/softproof/uri"); if ( which && !uri.empty() ) { if ( lastURI != uri ) { lastURI.clear(); if ( theOne ) { cmsCloseProfile( theOne ); } if ( transf ) { cmsDeleteTransform( transf ); transf = nullptr; } theOne = cmsOpenProfileFromFile( uri.data(), "r" ); if ( theOne ) { // a display profile must have the proper stuff cmsColorSpaceSignature space = cmsGetColorSpace(theOne); cmsProfileClassSignature profClass = cmsGetDeviceClass(theOne); (void)space; (void)profClass; /* if ( profClass != cmsSigDisplayClass ) { g_warning("Not a display profile"); cmsCloseProfile( theOne ); theOne = 0; } else if ( space != cmsSigRgbData ) { g_warning("Not an RGB profile"); cmsCloseProfile( theOne ); theOne = 0; } else { */ lastURI = uri; /* } */ } } } else if ( theOne ) { cmsCloseProfile( theOne ); theOne = nullptr; lastURI.clear(); if ( transf ) { cmsDeleteTransform( transf ); transf = nullptr; } } return theOne; } } // namespace static void free_transforms(); cmsHTRANSFORM Inkscape::CMSSystem::getDisplayTransform() { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); bool fromDisplay = prefs->getBool( "/options/displayprofile/from_display"); if ( fromDisplay ) { if ( transf ) { cmsDeleteTransform(transf); transf = nullptr; } return nullptr; } bool warn = prefs->getBool( "/options/softproof/gamutwarn"); int intent = prefs->getIntLimited( "/options/displayprofile/intent", 0, 0, 3 ); int proofIntent = prefs->getIntLimited( "/options/softproof/intent", 0, 0, 3 ); bool bpc = prefs->getBool( "/options/softproof/bpc"); #if defined(cmsFLAGS_PRESERVEBLACK) bool preserveBlack = prefs->getBool( "/options/softproof/preserveblack"); #endif //defined(cmsFLAGS_PRESERVEBLACK) Glib::ustring colorStr = prefs->getString("/options/softproof/gamutcolor"); Gdk::RGBA gamutColor( colorStr.empty() ? "#808080" : colorStr ); if ( (warn != gamutWarn) || (lastIntent != intent) || (lastProofIntent != proofIntent) || (bpc != lastBPC) #if defined(cmsFLAGS_PRESERVEBLACK) || (preserveBlack != lastPreserveBlack) #endif // defined(cmsFLAGS_PRESERVEBLACK) || (gamutColor != lastGamutColor) ) { gamutWarn = warn; free_transforms(); lastIntent = intent; lastProofIntent = proofIntent; lastBPC = bpc; #if defined(cmsFLAGS_PRESERVEBLACK) lastPreserveBlack = preserveBlack; #endif // defined(cmsFLAGS_PRESERVEBLACK) lastGamutColor = gamutColor; } // Fetch these now, as they might clear the transform as a side effect. cmsHPROFILE hprof = getSystemProfileHandle(); cmsHPROFILE proofProf = hprof ? getProofProfileHandle() : nullptr; if ( !transf ) { if ( hprof && proofProf ) { cmsUInt32Number dwFlags = cmsFLAGS_SOFTPROOFING; if ( gamutWarn ) { dwFlags |= cmsFLAGS_GAMUTCHECK; auto gamutColor_r = gamutColor.get_red_u(); auto gamutColor_g = gamutColor.get_green_u(); auto gamutColor_b = gamutColor.get_blue_u(); cmsUInt16Number newAlarmCodes[cmsMAXCHANNELS] = {0}; newAlarmCodes[0] = gamutColor_r; newAlarmCodes[1] = gamutColor_g; newAlarmCodes[2] = gamutColor_b; newAlarmCodes[3] = ~0; cmsSetAlarmCodes(newAlarmCodes); } if ( bpc ) { dwFlags |= cmsFLAGS_BLACKPOINTCOMPENSATION; } #if defined(cmsFLAGS_PRESERVEBLACK) if ( preserveBlack ) { dwFlags |= cmsFLAGS_PRESERVEBLACK; } #endif // defined(cmsFLAGS_PRESERVEBLACK) transf = cmsCreateProofingTransform( ColorProfileImpl::getSRGBProfile(), TYPE_BGRA_8, hprof, TYPE_BGRA_8, proofProf, intent, proofIntent, dwFlags ); } else if ( hprof ) { transf = cmsCreateTransform( ColorProfileImpl::getSRGBProfile(), TYPE_BGRA_8, hprof, TYPE_BGRA_8, intent, 0 ); } } return transf; } class MemProfile { public: MemProfile(); ~MemProfile(); std::string id; cmsHPROFILE hprof; cmsHTRANSFORM transf; }; MemProfile::MemProfile() : id(), hprof(nullptr), transf(nullptr) { } MemProfile::~MemProfile() = default; static std::vector perMonitorProfiles; void free_transforms() { if ( transf ) { cmsDeleteTransform(transf); transf = nullptr; } for ( auto profile : perMonitorProfiles ) { if ( profile.transf ) { cmsDeleteTransform(profile.transf); profile.transf = nullptr; } } } std::string Inkscape::CMSSystem::getDisplayId(int monitor) { std::string id; if ( monitor >= 0 && monitor < static_cast(perMonitorProfiles.size()) ) { MemProfile& item = perMonitorProfiles[monitor]; id = item.id; } return id; } Glib::ustring Inkscape::CMSSystem::setDisplayPer( gpointer buf, guint bufLen, int monitor ) { while ( static_cast(perMonitorProfiles.size()) <= monitor ) { MemProfile tmp; perMonitorProfiles.push_back(tmp); } MemProfile& item = perMonitorProfiles[monitor]; if ( item.hprof ) { cmsCloseProfile( item.hprof ); item.hprof = nullptr; } Glib::ustring id; if ( buf && bufLen ) { gsize len = bufLen; // len is an inout parameter id = Glib::Checksum::compute_checksum(Glib::Checksum::CHECKSUM_MD5, reinterpret_cast(buf), len); // Note: if this is not a valid profile, item.hprof will be set to null. item.hprof = cmsOpenProfileFromMem(buf, bufLen); } item.id = id; return id; } cmsHTRANSFORM Inkscape::CMSSystem::getDisplayPer(std::string const &id) { cmsHTRANSFORM result = nullptr; if ( id.empty() ) { return nullptr; } Inkscape::Preferences *prefs = Inkscape::Preferences::get(); bool found = false; for ( auto it2 = perMonitorProfiles.begin(); it2 != perMonitorProfiles.end() && !found; ++it2 ) { if ( id == it2->id ) { MemProfile& item = *it2; bool warn = prefs->getBool( "/options/softproof/gamutwarn"); int intent = prefs->getIntLimited( "/options/displayprofile/intent", 0, 0, 3 ); int proofIntent = prefs->getIntLimited( "/options/softproof/intent", 0, 0, 3 ); bool bpc = prefs->getBool( "/options/softproof/bpc"); #if defined(cmsFLAGS_PRESERVEBLACK) bool preserveBlack = prefs->getBool( "/options/softproof/preserveblack"); #endif //defined(cmsFLAGS_PRESERVEBLACK) Glib::ustring colorStr = prefs->getString("/options/softproof/gamutcolor"); Gdk::RGBA gamutColor( colorStr.empty() ? "#808080" : colorStr ); if ( (warn != gamutWarn) || (lastIntent != intent) || (lastProofIntent != proofIntent) || (bpc != lastBPC) #if defined(cmsFLAGS_PRESERVEBLACK) || (preserveBlack != lastPreserveBlack) #endif // defined(cmsFLAGS_PRESERVEBLACK) || (gamutColor != lastGamutColor) ) { gamutWarn = warn; free_transforms(); lastIntent = intent; lastProofIntent = proofIntent; lastBPC = bpc; #if defined(cmsFLAGS_PRESERVEBLACK) lastPreserveBlack = preserveBlack; #endif // defined(cmsFLAGS_PRESERVEBLACK) lastGamutColor = gamutColor; } // Fetch these now, as they might clear the transform as a side effect. cmsHPROFILE proofProf = item.hprof ? getProofProfileHandle() : nullptr; if ( !item.transf ) { if ( item.hprof && proofProf ) { cmsUInt32Number dwFlags = cmsFLAGS_SOFTPROOFING; if ( gamutWarn ) { dwFlags |= cmsFLAGS_GAMUTCHECK; auto gamutColor_r = gamutColor.get_red_u(); auto gamutColor_g = gamutColor.get_green_u(); auto gamutColor_b = gamutColor.get_blue_u(); cmsUInt16Number newAlarmCodes[cmsMAXCHANNELS] = {0}; newAlarmCodes[0] = gamutColor_r; newAlarmCodes[1] = gamutColor_g; newAlarmCodes[2] = gamutColor_b; newAlarmCodes[3] = ~0; cmsSetAlarmCodes(newAlarmCodes); } if ( bpc ) { dwFlags |= cmsFLAGS_BLACKPOINTCOMPENSATION; } #if defined(cmsFLAGS_PRESERVEBLACK) if ( preserveBlack ) { dwFlags |= cmsFLAGS_PRESERVEBLACK; } #endif // defined(cmsFLAGS_PRESERVEBLACK) item.transf = cmsCreateProofingTransform( ColorProfileImpl::getSRGBProfile(), TYPE_BGRA_8, item.hprof, TYPE_BGRA_8, proofProf, intent, proofIntent, dwFlags ); } else if ( item.hprof ) { item.transf = cmsCreateTransform( ColorProfileImpl::getSRGBProfile(), TYPE_BGRA_8, item.hprof, TYPE_BGRA_8, intent, 0 ); } } result = item.transf; found = true; } } return result; } /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :