#ifdef HAVE_CONFIG_H # include "config.h" #endif #define noDEBUG_LCMS #include #include #include #include #ifdef DEBUG_LCMS #include #endif // DEBUG_LCMS #include #include #include #ifdef WIN32 #ifndef _WIN32_WINDOWS // Allow use of features specific to Windows 98 or later. Required for correctly including icm.h #define _WIN32_WINDOWS 0x0410 #endif #include #endif #if HAVE_LIBLCMS2 # include #elif HAVE_LIBLCMS1 # include #endif // HAVE_LIBLCMS2 #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 { #if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) cmsHPROFILE getSystemProfileHandle(); cmsHPROFILE getProofProfileHandle(); void loadProfiles(); Glib::ustring getNameFromProfile(cmsHPROFILE profile); #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) } #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: #if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) static cmsHPROFILE _sRGBProf; static cmsHPROFILE _NullProf; #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) ColorProfileImpl(); #if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) 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; #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) }; #if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) cmsColorSpaceSignature asICColorSpaceSig(ColorSpaceSig const & sig) { return ColorSpaceSigWrapper(sig); } cmsProfileClassSignature asICColorProfileClassSig(ColorProfileClassSig const & sig) { return ColorProfileClassSigWrapper(sig); } #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) } // namespace Inkscape ColorProfileImpl::ColorProfileImpl() #if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) : _profHandle(0), _profileClass(cmsSigInputClass), _profileSpace(cmsSigRgbData), _transf(0), _revTransf(0), _gamutTransf(0) #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) { } #if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) cmsHPROFILE ColorProfileImpl::_sRGBProf = 0; cmsHPROFILE ColorProfileImpl::getSRGBProfile() { if ( !_sRGBProf ) { _sRGBProf = cmsCreate_sRGBProfile(); } return ColorProfileImpl::_sRGBProf; } cmsHPROFILE ColorProfileImpl::_NullProf = 0; cmsHPROFILE ColorProfileImpl::getNULLProfile() { if ( !_NullProf ) { _NullProf = cmsCreateNULLProfile(); } return _NullProf; } #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) ColorProfile::ColorProfile() : SPObject() { this->impl = new ColorProfileImpl(); this->href = 0; this->local = 0; this->name = 0; this->intentStr = 0; this->rendering_intent = Inkscape::RENDERING_INTENT_UNKNOWN; } ColorProfile::~ColorProfile() { } /** * 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 = 0; } if ( this->local ) { g_free( this->local ); this->local = 0; } if ( this->name ) { g_free( this->name ); this->name = 0; } if ( this->intentStr ) { g_free( this->intentStr ); this->intentStr = 0; } #if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) this->impl->_clearProfile(); #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) delete this->impl; this->impl = 0; } #if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) void ColorProfileImpl::_clearProfile() { _profileSpace = cmsSigRgbData; if ( _transf ) { cmsDeleteTransform( _transf ); _transf = 0; } if ( _revTransf ) { cmsDeleteTransform( _revTransf ); _revTransf = 0; } if ( _gamutTransf ) { cmsDeleteTransform( _gamutTransf ); _gamutTransf = 0; } if ( _profHandle ) { cmsCloseProfile( _profHandle ); _profHandle = 0; } } #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) /** * Callback: set attributes from associated repr. */ void ColorProfile::build(SPDocument *document, Inkscape::XML::Node *repr) { g_assert(this->href == 0); g_assert(this->local == 0); g_assert(this->name == 0); g_assert(this->intentStr == 0); SPObject::build(document, repr); this->readAttr( "xlink:href" ); this->readAttr( "id" ); this->readAttr( "local" ); this->readAttr( "name" ); this->readAttr( "rendering-intent" ); // Register if ( document ) { document->addResource( "iccprofile", this ); } } /** * Callback: set attribute. */ void ColorProfile::set(unsigned key, gchar const *value) { switch (key) { case SP_ATTR_XLINK_HREF: if ( this->href ) { g_free( this->href ); this->href = 0; } if ( value ) { this->href = g_strdup( value ); if ( *this->href ) { #if HAVE_LIBLCMS1 cmsErrorAction( LCMS_ERROR_SHOW ); #endif #if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) // 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 URI of document gchar const *docbase = doc->getURI(); gchar* escaped = g_uri_escape_string(this->href, "!*'();@=+$,/?#", TRUE); //g_message("docbase:%s\n", docbase); //org::w3c::dom::URI docUri(docbase); Inkscape::URI docUri(""); if (docbase) { // The file has already been saved docUri = Inkscape::URI::from_native_filename(docbase); } //# 2. Get href of icc file. we don't care if it's rel or abs //org::w3c::dom::URI hrefUri(escaped); Inkscape::URI hrefUri(escaped); //# 3. Resolve the href according the docBase. This follows // the w3c specs. All absolute and relative issues are considered std::string fullpath = hrefUri.getFullPath(docUri.getFullPath("")); gchar* fullname = g_uri_unescape_string(fullpath.c_str(), ""); this->impl->_clearProfile(); this->impl->_profHandle = cmsOpenProfileFromFile( fullname, "r" ); 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 ); g_free(escaped); escaped = 0; g_free(fullname); #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) } } this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_LOCAL: if ( this->local ) { g_free( this->local ); this->local = 0; } this->local = g_strdup( value ); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_NAME: if ( this->name ) { g_free( this->name ); this->name = 0; } this->name = g_strdup( value ); DEBUG_MESSAGE( lcmsTwo, " name set to '%s'", this->name ); this->requestModified(SP_OBJECT_MODIFIED_FLAG); break; case SP_ATTR_RENDERING_INTENT: if ( this->intentStr ) { g_free( this->intentStr ); this->intentStr = 0; } 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; } #if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) 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 SPObject* bruteFind( SPDocument* document, gchar const* name ) { SPObject* result = 0; std::vector current = document->getResourceList("iccprofile"); for (std::vector::const_iterator it = current.begin(); (!result) && (it != current.end()); ++it) { if ( IS_COLORPROFILE(*it) ) { ColorProfile* prof = COLORPROFILE(*it); if ( prof ) { if ( prof->name && (strcmp(prof->name, name) == 0) ) { result = SP_OBJECT(*it); break; } } } } return result; } cmsHPROFILE Inkscape::CMSSystem::getHandle( SPDocument* document, guint* intent, gchar const* name ) { cmsHPROFILE prof = 0; SPObject* thing = bruteFind( document, name ); if ( thing ) { prof = COLORPROFILE(thing)->impl->_profHandle; } if ( intent ) { *intent = thing ? COLORPROFILE(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); #if HAVE_LIBLCMS1 int alarm_r = 0; int alarm_g = 0; int alarm_b = 0; cmsGetAlarmCodes(&alarm_r, &alarm_g, &alarm_b); cmsSetAlarmCodes(255, 255, 255); #elif HAVE_LIBLCMS2 cmsUInt16Number oldAlarmCodes[cmsMAXCHANNELS] = {0}; cmsGetAlarmCodes(oldAlarmCodes); cmsUInt16Number newAlarmCodes[cmsMAXCHANNELS] = {0}; newAlarmCodes[0] = ~0; cmsSetAlarmCodes(newAlarmCodes); #endif // HAVE_LIBLCMS1 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); } #if HAVE_LIBLCMS1 cmsSetAlarmCodes(alarm_r, alarm_g, alarm_b); #elif HAVE_LIBLCMS2 cmsSetAlarmCodes(oldAlarmCodes); #endif // HAVE_LIBLCMS1 return (outofgamut != 0); } class ProfileInfo { public: ProfileInfo( cmsHPROFILE prof, Glib::ustring const & 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 const & path ) : _path( path ), _name( getNameFromProfile(prof) ), _profileSpace( cmsGetColorSpace( prof ) ), _profileClass( cmsGetDeviceClass( prof ) ) { } static std::vector knownProfiles; std::vector Inkscape::CMSSystem::getDisplayNames() { loadProfiles(); std::vector result; for ( std::vector::iterator it = knownProfiles.begin(); it != knownProfiles.end(); ++it ) { if ( it->getClass() == cmsSigDisplayClass && it->getSpace() == cmsSigRgbData ) { result.push_back( it->getName() ); } } std::sort(result.begin(), result.end()); return result; } std::vector Inkscape::CMSSystem::getSoftproofNames() { loadProfiles(); std::vector result; for ( std::vector::iterator it = knownProfiles.begin(); it != knownProfiles.end(); ++it ) { if ( it->getClass() == cmsSigOutputClass ) { result.push_back( it->getName() ); } } std::sort(result.begin(), result.end()); return result; } Glib::ustring Inkscape::CMSSystem::getPathForProfile(Glib::ustring const& name) { loadProfiles(); Glib::ustring result; for ( std::vector::iterator it = knownProfiles.begin(); it != knownProfiles.end(); ++it ) { if ( name == it->getName() ) { result = it->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 ) { #if HAVE_LIBLCMS1 count = _cmsChannelsOf( asICColorSpaceSig(profile->getColorSpace()) ); #elif HAVE_LIBLCMS2 count = cmsChannelsOf( asICColorSpaceSig(profile->getColorSpace()) ); #endif } return count; } #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) // sort home dir before the rest, and alphabetically oterhwise bool compareProfileBoolPair(const std::pair & a, const std::pair & b) { if (a.second != b.second) return a.second; // a comes first iff its home, i.e., second is true return a.first < b.first; } // 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::vector > ColorProfile::getBaseProfileDirs() { #if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) static bool warnSet = false; if (!warnSet) { #if HAVE_LIBLCMS1 cmsErrorAction( LCMS_ERROR_SHOW ); #endif warnSet = true; } #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) std::vector > sources; // first try user's local dir gchar* path = g_build_filename(g_get_user_data_dir(), "color", "icc", NULL); sources.push_back(std::make_pair(path, true)); g_free(path); 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", NULL); sources.push_back(std::make_pair(path, false)); g_free(path); } // On OS X: { bool onOSX = false; std::vector possible; possible.push_back("/System/Library/ColorSync/Profiles"); possible.push_back("/Library/ColorSync/Profiles"); for ( std::vector::const_iterator it = possible.begin(); it != possible.end(); ++it ) { if ( g_file_test(it->c_str(), G_FILE_TEST_EXISTS) && g_file_test(it->c_str(), G_FILE_TEST_IS_DIR) ) { sources.push_back(std::make_pair(it->c_str(), false)); onOSX = true; } } if ( onOSX ) { gchar* path = g_build_filename(g_get_home_dir(), "Library", "ColorSync", "Profiles", NULL); if ( g_file_test(path, G_FILE_TEST_EXISTS) && g_file_test(path, G_FILE_TEST_IS_DIR) ) { sources.push_back(std::make_pair(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.push_back(std::make_pair(utf8Path, false)); } g_free( utf8Path ); } #endif // WIN32 std::sort(sources.begin(), sources.end(), compareProfileBoolPair); std::vector >::iterator last = std::unique(sources.begin(), sources.end()); sources.erase(last, sources.end()); 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 defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) 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 ); } } #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) } } return isIccFile; } std::vector > ColorProfile::getProfileFiles() { std::vector > files; std::list > sources; { std::vector > tmp = ColorProfile::getBaseProfileDirs(); sources.insert(sources.begin(), tmp.begin(), tmp.end()); } for ( std::list >::const_iterator it = sources.begin(); it != sources.end(); ++it ) { if ( g_file_test( it->first.c_str(), G_FILE_TEST_EXISTS ) && g_file_test( it->first.c_str(), G_FILE_TEST_IS_DIR ) ) { GError *err = 0; GDir *dir = g_dir_open(it->first.c_str(), 0, &err); if (dir) { for (gchar const *file = g_dir_read_name(dir); file != NULL; file = g_dir_read_name(dir)) { gchar *filepath = g_build_filename(it->first.c_str(), file, NULL); if ( g_file_test( filepath, G_FILE_TEST_IS_DIR ) ) { sources.push_back( std::make_pair(filepath, it->second) ); } else { if ( isIccFile( filepath ) ) { files.push_back( std::make_pair(filepath, it->second) ); } } g_free(filepath); } g_dir_close(dir); dir = 0; } else { gchar *safeDir = Inkscape::IO::sanitizeString(it->first.c_str()); g_warning(_("Color profiles directory (%s) is unavailable."), safeDir); g_free(safeDir); } } } std::sort(files.begin(), files.end(), compareProfileBoolPair); std::vector >::iterator last = std::unique(files.begin(), files.end()); files.erase(last, files.end()); return files; } bool compareProfilePairByName(const std::pair, Glib::ustring> & a, const std::pair, Glib::ustring> & b) { if (a.first.second != b.first.second) return a.first.second; return a.second < b.second; } std::vector, Glib::ustring> > ColorProfile::getProfileFilesWithNames() { std::vector, Glib::ustring> > result; #if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) std::vector > files = getProfileFiles(); for ( std::vector >::const_iterator it = files.begin(); it != files.end(); ++it ) { cmsHPROFILE hProfile = cmsOpenProfileFromFile(it->first.c_str(), "r"); if ( hProfile ) { Glib::ustring name = getNameFromProfile(hProfile); result.push_back( std::make_pair(*it, name) ); cmsCloseProfile(hProfile); } } std::sort(result.begin(), result.end(), compareProfilePairByName); #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) return result; } #if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) #if HAVE_LIBLCMS1 int errorHandlerCB(int ErrorCode, const char *ErrorText) { g_message("lcms: Error %d; %s", ErrorCode, ErrorText); return 1; } #elif HAVE_LIBLCMS2 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); } #endif namespace { Glib::ustring getNameFromProfile(cmsHPROFILE profile) { Glib::ustring nameStr; if ( profile ) { #if HAVE_LIBLCMS1 gchar const *name = cmsTakeProductDesc(profile); if ( !name ) { name = cmsTakeProductName(profile); } if ( name && !g_utf8_validate(name, -1, NULL) ) { name = _("(invalid UTF-8 string)"); } nameStr = (name) ? name : C_("Profile name", "None"); #elif HAVE_LIBLCMS2 cmsUInt32Number byteLen = cmsGetProfileInfo(profile, cmsInfoDescription, "en", "US", NULL, 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, NULL)) { nameStr = _("(invalid UTF-8 string)"); } #endif } 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) { #if HAVE_LIBLCMS1 cmsSetErrorHandler(errorHandlerCB); #elif HAVE_LIBLCMS2 //cmsSetLogErrorHandler(errorHandlerCB); //g_message("LCMS error handler set"); #endif error_handler_set = true; } static bool profiles_searched = false; if ( !profiles_searched ) { knownProfiles.clear(); std::vector > files = ColorProfile::getProfileFiles(); for ( std::vector >::const_iterator it = files.begin(); it != files.end(); ++it ) { cmsHPROFILE prof = cmsOpenProfileFromFile( it->first.c_str(), "r" ); if ( prof ) { ProfileInfo info( prof, Glib::filename_to_utf8( it->first.c_str() ) ); cmsCloseProfile( prof ); prof = 0; bool sameName = false; for ( std::vector::iterator it = knownProfiles.begin(); it != knownProfiles.end(); ++it ) { if ( it->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 = 0; namespace { cmsHPROFILE getSystemProfileHandle() { static cmsHPROFILE theOne = 0; 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 = 0; } 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 = 0; } else if ( space != cmsSigRgbData ) { g_warning("Not an RGB profile"); cmsCloseProfile( theOne ); theOne = 0; } else { lastURI = uri; } } } } else if ( theOne ) { cmsCloseProfile( theOne ); theOne = 0; lastURI.clear(); if ( transf ) { cmsDeleteTransform( transf ); transf = 0; } } return theOne; } cmsHPROFILE getProofProfileHandle() { static cmsHPROFILE theOne = 0; 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 = 0; } 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 = 0; lastURI.clear(); if ( transf ) { cmsDeleteTransform( transf ); transf = 0; } } 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 = 0; } return 0; } 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() : 0; 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(); #if HAVE_LIBLCMS1 cmsSetAlarmCodes(gamutColor_r >> 8, gamutColor_g >> 8, gamutColor_b >> 8); #elif HAVE_LIBLCMS2 cmsUInt16Number newAlarmCodes[cmsMAXCHANNELS] = {0}; newAlarmCodes[0] = gamutColor_r; newAlarmCodes[1] = gamutColor_g; newAlarmCodes[2] = gamutColor_b; newAlarmCodes[3] = ~0; cmsSetAlarmCodes(newAlarmCodes); #endif } 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(0), transf(0) { } MemProfile::~MemProfile() { } static std::vector< std::vector > perMonitorProfiles; void free_transforms() { if ( transf ) { cmsDeleteTransform(transf); transf = 0; } for ( std::vector< std::vector >::iterator it = perMonitorProfiles.begin(); it != perMonitorProfiles.end(); ++it ) { for ( std::vector::iterator it2 = it->begin(); it2 != it->end(); ++it2 ) { if ( it2->transf ) { cmsDeleteTransform(it2->transf); it2->transf = 0; } } } } Glib::ustring Inkscape::CMSSystem::getDisplayId( int screen, int monitor ) { Glib::ustring id; if ( screen >= 0 && screen < static_cast(perMonitorProfiles.size()) ) { std::vector& row = perMonitorProfiles[screen]; if ( monitor >= 0 && monitor < static_cast(row.size()) ) { MemProfile& item = row[monitor]; id = item.id; } } return id; } Glib::ustring Inkscape::CMSSystem::setDisplayPer( gpointer buf, guint bufLen, int screen, int monitor ) { while ( static_cast(perMonitorProfiles.size()) <= screen ) { std::vector tmp; perMonitorProfiles.push_back(tmp); } std::vector& row = perMonitorProfiles[screen]; while ( static_cast(row.size()) <= monitor ) { MemProfile tmp; row.push_back(tmp); } MemProfile& item = row[monitor]; if ( item.hprof ) { cmsCloseProfile( item.hprof ); item.hprof = 0; } 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( Glib::ustring const& id ) { cmsHTRANSFORM result = 0; if ( id.empty() ) { return 0; } Inkscape::Preferences *prefs = Inkscape::Preferences::get(); bool found = false; for ( std::vector< std::vector >::iterator it = perMonitorProfiles.begin(); it != perMonitorProfiles.end() && !found; ++it ) { for ( std::vector::iterator it2 = it->begin(); it2 != it->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() : 0; 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(); #if HAVE_LIBLCMS1 cmsSetAlarmCodes(gamutColor_r >> 8, gamutColor_g >> 8, gamutColor_b >> 8); #elif HAVE_LIBLCMS2 cmsUInt16Number newAlarmCodes[cmsMAXCHANNELS] = {0}; newAlarmCodes[0] = gamutColor_r; newAlarmCodes[1] = gamutColor_g; newAlarmCodes[2] = gamutColor_b; newAlarmCodes[3] = ~0; cmsSetAlarmCodes(newAlarmCodes); #endif } 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; } #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) /* 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 :