1 #ifdef HAVE_CONFIG_H
2 # include "config.h"
3 #endif
5 #define noDEBUG_LCMS
7 #include <glib/gstdio.h>
8 #include <sys/fcntl.h>
9 #include <gdkmm/color.h>
10 #include <glib/gi18n.h>
12 #ifdef DEBUG_LCMS
13 #include <gtk/gtkmessagedialog.h>
14 #endif // DEBUG_LCMS
16 #include <cstring>
17 #include <string>
19 #ifdef WIN32
20 #ifndef _WIN32_WINDOWS // Allow use of features specific to Windows 98 or later. Required for correctly including icm.h
21 #define _WIN32_WINDOWS 0x0410
22 #endif
23 #include <windows.h>
24 #endif
26 #include "xml/repr.h"
27 #include "color.h"
28 #include "color-profile.h"
29 #include "color-profile-fns.h"
30 #include "attributes.h"
31 #include "inkscape.h"
32 #include "document.h"
33 #include "preferences.h"
35 #include "dom/uri.h"
36 #include "dom/util/digest.h"
38 #ifdef WIN32
39 #include <icm.h>
40 #endif // WIN32
42 using Inkscape::ColorProfile;
43 using Inkscape::ColorProfileClass;
45 namespace Inkscape
46 {
47 #if ENABLE_LCMS
48 static cmsHPROFILE colorprofile_get_system_profile_handle();
49 static cmsHPROFILE colorprofile_get_proof_profile_handle();
50 #endif // ENABLE_LCMS
51 }
53 #ifdef DEBUG_LCMS
54 extern guint update_in_progress;
55 #define DEBUG_MESSAGE_SCISLAC(key, ...) \
56 {\
57 Inkscape::Preferences *prefs = Inkscape::Preferences::get();\
58 bool dump = prefs->getBool(Glib::ustring("/options/scislac/") + #key);\
59 bool dumpD = prefs->getBool(Glib::ustring("/options/scislac/") + #key"D");\
60 bool dumpD2 = prefs->getBool(Glib::ustring("/options/scislac/") + #key"D2");\
61 dumpD &= ( (update_in_progress == 0) || dumpD2 );\
62 if ( dump )\
63 {\
64 g_message( __VA_ARGS__ );\
65 \
66 }\
67 if ( dumpD )\
68 {\
69 GtkWidget *dialog = gtk_message_dialog_new(NULL,\
70 GTK_DIALOG_DESTROY_WITH_PARENT, \
71 GTK_MESSAGE_INFO, \
72 GTK_BUTTONS_OK, \
73 __VA_ARGS__ \
74 );\
75 g_signal_connect_swapped(dialog, "response",\
76 G_CALLBACK(gtk_widget_destroy), \
77 dialog); \
78 gtk_widget_show_all( dialog );\
79 }\
80 }
83 #define DEBUG_MESSAGE(key, ...)\
84 {\
85 g_message( __VA_ARGS__ );\
86 }
88 #endif // DEBUG_LCMS
90 static SPObjectClass *cprof_parent_class;
92 #if ENABLE_LCMS
94 cmsHPROFILE ColorProfile::_sRGBProf = 0;
96 cmsHPROFILE ColorProfile::getSRGBProfile() {
97 if ( !_sRGBProf ) {
98 _sRGBProf = cmsCreate_sRGBProfile();
99 }
100 return _sRGBProf;
101 }
103 cmsHPROFILE ColorProfile::_NullProf = 0;
105 cmsHPROFILE ColorProfile::getNULLProfile() {
106 if ( !_NullProf ) {
107 _NullProf = cmsCreateNULLProfile();
108 }
109 return _NullProf;
110 }
112 #endif // ENABLE_LCMS
114 /**
115 * Register ColorProfile class and return its type.
116 */
117 GType Inkscape::colorprofile_get_type()
118 {
119 return ColorProfile::getType();
120 }
122 GType ColorProfile::getType()
123 {
124 static GType type = 0;
125 if (!type) {
126 GTypeInfo info = {
127 sizeof(ColorProfileClass),
128 NULL, NULL,
129 (GClassInitFunc) ColorProfile::classInit,
130 NULL, NULL,
131 sizeof(ColorProfile),
132 16,
133 (GInstanceInitFunc) ColorProfile::init,
134 NULL, /* value_table */
135 };
136 type = g_type_register_static( SP_TYPE_OBJECT, "ColorProfile", &info, static_cast<GTypeFlags>(0) );
137 }
138 return type;
139 }
141 /**
142 * ColorProfile vtable initialization.
143 */
144 void ColorProfile::classInit( ColorProfileClass *klass )
145 {
146 SPObjectClass *sp_object_class = reinterpret_cast<SPObjectClass *>(klass);
148 cprof_parent_class = static_cast<SPObjectClass*>(g_type_class_ref(SP_TYPE_OBJECT));
150 sp_object_class->release = ColorProfile::release;
151 sp_object_class->build = ColorProfile::build;
152 sp_object_class->set = ColorProfile::set;
153 sp_object_class->write = ColorProfile::write;
154 }
156 /**
157 * Callback for ColorProfile object initialization.
158 */
159 void ColorProfile::init( ColorProfile *cprof )
160 {
161 cprof->href = 0;
162 cprof->local = 0;
163 cprof->name = 0;
164 cprof->intentStr = 0;
165 cprof->rendering_intent = Inkscape::RENDERING_INTENT_UNKNOWN;
166 #if ENABLE_LCMS
167 cprof->profHandle = 0;
168 cprof->_profileClass = icSigInputClass;
169 cprof->_profileSpace = icSigRgbData;
170 cprof->_transf = 0;
171 cprof->_revTransf = 0;
172 cprof->_gamutTransf = 0;
173 #endif // ENABLE_LCMS
174 }
176 /**
177 * Callback: free object
178 */
179 void ColorProfile::release( SPObject *object )
180 {
181 // Unregister ourselves
182 SPDocument* document = SP_OBJECT_DOCUMENT(object);
183 if ( document ) {
184 sp_document_remove_resource (SP_OBJECT_DOCUMENT (object), "iccprofile", SP_OBJECT (object));
185 }
187 ColorProfile *cprof = COLORPROFILE(object);
188 if ( cprof->href ) {
189 g_free( cprof->href );
190 cprof->href = 0;
191 }
193 if ( cprof->local ) {
194 g_free( cprof->local );
195 cprof->local = 0;
196 }
198 if ( cprof->name ) {
199 g_free( cprof->name );
200 cprof->name = 0;
201 }
203 if ( cprof->intentStr ) {
204 g_free( cprof->intentStr );
205 cprof->intentStr = 0;
206 }
208 #if ENABLE_LCMS
209 cprof->_clearProfile();
210 #endif // ENABLE_LCMS
211 }
213 #if ENABLE_LCMS
214 void ColorProfile::_clearProfile()
215 {
216 _profileSpace = icSigRgbData;
218 if ( _transf ) {
219 cmsDeleteTransform( _transf );
220 _transf = 0;
221 }
222 if ( _revTransf ) {
223 cmsDeleteTransform( _revTransf );
224 _revTransf = 0;
225 }
226 if ( _gamutTransf ) {
227 cmsDeleteTransform( _gamutTransf );
228 _gamutTransf = 0;
229 }
230 if ( profHandle ) {
231 cmsCloseProfile( profHandle );
232 profHandle = 0;
233 }
234 }
235 #endif // ENABLE_LCMS
237 /**
238 * Callback: set attributes from associated repr.
239 */
240 void ColorProfile::build( SPObject *object, SPDocument *document, Inkscape::XML::Node *repr )
241 {
242 ColorProfile *cprof = COLORPROFILE(object);
243 g_assert(cprof->href == 0);
244 g_assert(cprof->local == 0);
245 g_assert(cprof->name == 0);
246 g_assert(cprof->intentStr == 0);
248 if (cprof_parent_class->build) {
249 (* cprof_parent_class->build)(object, document, repr);
250 }
251 sp_object_read_attr( object, "xlink:href" );
252 sp_object_read_attr( object, "local" );
253 sp_object_read_attr( object, "name" );
254 sp_object_read_attr( object, "rendering-intent" );
256 // Register
257 if ( document ) {
258 sp_document_add_resource( document, "iccprofile", object );
259 }
260 }
262 /**
263 * Callback: set attribute.
264 */
265 void ColorProfile::set( SPObject *object, unsigned key, gchar const *value )
266 {
267 ColorProfile *cprof = COLORPROFILE(object);
269 switch (key) {
270 case SP_ATTR_XLINK_HREF:
271 if ( cprof->href ) {
272 g_free( cprof->href );
273 cprof->href = 0;
274 }
275 if ( value ) {
276 cprof->href = g_strdup( value );
277 if ( *cprof->href ) {
278 #if ENABLE_LCMS
279 cmsErrorAction( LCMS_ERROR_SHOW );
281 // TODO open filename and URIs properly
282 //FILE* fp = fopen_utf8name( filename, "r" );
283 //LCMSAPI cmsHPROFILE LCMSEXPORT cmsOpenProfileFromMem(LPVOID MemPtr, DWORD dwSize);
285 // Try to open relative
286 SPDocument *doc = SP_OBJECT_DOCUMENT(object);
287 if (!doc) {
288 doc = SP_ACTIVE_DOCUMENT;
289 g_warning("object has no document. using active");
290 }
291 //# 1. Get complete URI of document
292 gchar const *docbase = SP_DOCUMENT_URI( doc );
293 if (!docbase)
294 {
295 // Normal for files that have not yet been saved.
296 docbase = "";
297 }
299 gchar* escaped = g_uri_escape_string(cprof->href, "!*'();:@=+$,/?#[]", TRUE);
301 //g_message("docbase:%s\n", docbase);
302 org::w3c::dom::URI docUri(docbase);
303 //# 2. Get href of icc file. we don't care if it's rel or abs
304 org::w3c::dom::URI hrefUri(escaped);
305 //# 3. Resolve the href according the docBase. This follows
306 // the w3c specs. All absolute and relative issues are considered
307 org::w3c::dom::URI cprofUri = docUri.resolve(hrefUri);
308 gchar* fullname = g_uri_unescape_string(cprofUri.getNativePath().c_str(), "");
309 cprof->_clearProfile();
310 cprof->profHandle = cmsOpenProfileFromFile( fullname, "r" );
311 if ( cprof->profHandle ) {
312 cprof->_profileSpace = cmsGetColorSpace( cprof->profHandle );
313 cprof->_profileClass = cmsGetDeviceClass( cprof->profHandle );
314 }
315 #ifdef DEBUG_LCMS
316 DEBUG_MESSAGE( lcmsOne, "cmsOpenProfileFromFile( '%s'...) = %p", fullname, (void*)cprof->profHandle );
317 #endif // DEBUG_LCMS
318 g_free(escaped);
319 escaped = 0;
320 g_free(fullname);
321 #endif // ENABLE_LCMS
322 }
323 }
324 object->requestModified(SP_OBJECT_MODIFIED_FLAG);
325 break;
327 case SP_ATTR_LOCAL:
328 if ( cprof->local ) {
329 g_free( cprof->local );
330 cprof->local = 0;
331 }
332 cprof->local = g_strdup( value );
333 object->requestModified(SP_OBJECT_MODIFIED_FLAG);
334 break;
336 case SP_ATTR_NAME:
337 if ( cprof->name ) {
338 g_free( cprof->name );
339 cprof->name = 0;
340 }
341 cprof->name = g_strdup( value );
342 #ifdef DEBUG_LCMS
343 DEBUG_MESSAGE( lcmsTwo, "<color-profile> name set to '%s'", cprof->name );
344 #endif // DEBUG_LCMS
345 object->requestModified(SP_OBJECT_MODIFIED_FLAG);
346 break;
348 case SP_ATTR_RENDERING_INTENT:
349 if ( cprof->intentStr ) {
350 g_free( cprof->intentStr );
351 cprof->intentStr = 0;
352 }
353 cprof->intentStr = g_strdup( value );
355 if ( value ) {
356 if ( strcmp( value, "auto" ) == 0 ) {
357 cprof->rendering_intent = RENDERING_INTENT_AUTO;
358 } else if ( strcmp( value, "perceptual" ) == 0 ) {
359 cprof->rendering_intent = RENDERING_INTENT_PERCEPTUAL;
360 } else if ( strcmp( value, "relative-colorimetric" ) == 0 ) {
361 cprof->rendering_intent = RENDERING_INTENT_RELATIVE_COLORIMETRIC;
362 } else if ( strcmp( value, "saturation" ) == 0 ) {
363 cprof->rendering_intent = RENDERING_INTENT_SATURATION;
364 } else if ( strcmp( value, "absolute-colorimetric" ) == 0 ) {
365 cprof->rendering_intent = RENDERING_INTENT_ABSOLUTE_COLORIMETRIC;
366 } else {
367 cprof->rendering_intent = RENDERING_INTENT_UNKNOWN;
368 }
369 } else {
370 cprof->rendering_intent = RENDERING_INTENT_UNKNOWN;
371 }
373 object->requestModified(SP_OBJECT_MODIFIED_FLAG);
374 break;
376 default:
377 if (cprof_parent_class->set) {
378 (* cprof_parent_class->set)(object, key, value);
379 }
380 break;
381 }
383 }
385 /**
386 * Callback: write attributes to associated repr.
387 */
388 Inkscape::XML::Node* ColorProfile::write( SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags )
389 {
390 ColorProfile *cprof = COLORPROFILE(object);
392 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
393 repr = xml_doc->createElement("svg:color-profile");
394 }
396 if ( (flags & SP_OBJECT_WRITE_ALL) || cprof->href ) {
397 repr->setAttribute( "xlink:href", cprof->href );
398 }
400 if ( (flags & SP_OBJECT_WRITE_ALL) || cprof->local ) {
401 repr->setAttribute( "local", cprof->local );
402 }
404 if ( (flags & SP_OBJECT_WRITE_ALL) || cprof->name ) {
405 repr->setAttribute( "name", cprof->name );
406 }
408 if ( (flags & SP_OBJECT_WRITE_ALL) || cprof->intentStr ) {
409 repr->setAttribute( "rendering-intent", cprof->intentStr );
410 }
412 if (cprof_parent_class->write) {
413 (* cprof_parent_class->write)(object, xml_doc, repr, flags);
414 }
416 return repr;
417 }
420 #if ENABLE_LCMS
422 struct MapMap {
423 icColorSpaceSignature space;
424 DWORD inForm;
425 };
427 DWORD ColorProfile::_getInputFormat( icColorSpaceSignature space )
428 {
429 MapMap possible[] = {
430 {icSigXYZData, TYPE_XYZ_16},
431 {icSigLabData, TYPE_Lab_16},
432 //icSigLuvData
433 {icSigYCbCrData, TYPE_YCbCr_16},
434 {icSigYxyData, TYPE_Yxy_16},
435 {icSigRgbData, TYPE_RGB_16},
436 {icSigGrayData, TYPE_GRAY_16},
437 {icSigHsvData, TYPE_HSV_16},
438 {icSigHlsData, TYPE_HLS_16},
439 {icSigCmykData, TYPE_CMYK_16},
440 {icSigCmyData, TYPE_CMY_16},
441 };
443 int index = 0;
444 for ( guint i = 0; i < G_N_ELEMENTS(possible); i++ ) {
445 if ( possible[i].space == space ) {
446 index = i;
447 break;
448 }
449 }
451 return possible[index].inForm;
452 }
454 static int getLcmsIntent( guint svgIntent )
455 {
456 int intent = INTENT_PERCEPTUAL;
457 switch ( svgIntent ) {
458 case Inkscape::RENDERING_INTENT_RELATIVE_COLORIMETRIC:
459 intent = INTENT_RELATIVE_COLORIMETRIC;
460 break;
461 case Inkscape::RENDERING_INTENT_SATURATION:
462 intent = INTENT_SATURATION;
463 break;
464 case Inkscape::RENDERING_INTENT_ABSOLUTE_COLORIMETRIC:
465 intent = INTENT_ABSOLUTE_COLORIMETRIC;
466 break;
467 case Inkscape::RENDERING_INTENT_PERCEPTUAL:
468 case Inkscape::RENDERING_INTENT_UNKNOWN:
469 case Inkscape::RENDERING_INTENT_AUTO:
470 default:
471 intent = INTENT_PERCEPTUAL;
472 }
473 return intent;
474 }
476 static SPObject* bruteFind( SPDocument* document, gchar const* name )
477 {
478 SPObject* result = 0;
479 const GSList * current = sp_document_get_resource_list(document, "iccprofile");
480 while ( current && !result ) {
481 if ( IS_COLORPROFILE(current->data) ) {
482 ColorProfile* prof = COLORPROFILE(current->data);
483 if ( prof ) {
484 if ( prof->name && (strcmp(prof->name, name) == 0) ) {
485 result = SP_OBJECT(current->data);
486 break;
487 }
488 }
489 }
490 current = g_slist_next(current);
491 }
493 return result;
494 }
496 cmsHPROFILE Inkscape::colorprofile_get_handle( SPDocument* document, guint* intent, gchar const* name )
497 {
498 cmsHPROFILE prof = 0;
500 SPObject* thing = bruteFind( document, name );
501 if ( thing ) {
502 prof = COLORPROFILE(thing)->profHandle;
503 }
505 if ( intent ) {
506 *intent = thing ? COLORPROFILE(thing)->rendering_intent : (guint)RENDERING_INTENT_UNKNOWN;
507 }
509 #ifdef DEBUG_LCMS
510 DEBUG_MESSAGE( lcmsThree, "<color-profile> queried for profile of '%s'. Returning %p with intent of %d", name, prof, (intent? *intent:0) );
511 #endif // DEBUG_LCMS
513 return prof;
514 }
516 cmsHTRANSFORM ColorProfile::getTransfToSRGB8()
517 {
518 if ( !_transf && profHandle ) {
519 int intent = getLcmsIntent(rendering_intent);
520 _transf = cmsCreateTransform( profHandle, _getInputFormat(_profileSpace), getSRGBProfile(), TYPE_RGBA_8, intent, 0 );
521 }
522 return _transf;
523 }
525 cmsHTRANSFORM ColorProfile::getTransfFromSRGB8()
526 {
527 if ( !_revTransf && profHandle ) {
528 int intent = getLcmsIntent(rendering_intent);
529 _revTransf = cmsCreateTransform( getSRGBProfile(), TYPE_RGBA_8, profHandle, _getInputFormat(_profileSpace), intent, 0 );
530 }
531 return _revTransf;
532 }
534 cmsHTRANSFORM ColorProfile::getTransfGamutCheck()
535 {
536 if ( !_gamutTransf ) {
537 _gamutTransf = cmsCreateProofingTransform(getSRGBProfile(), TYPE_RGBA_8, getNULLProfile(), TYPE_GRAY_8, profHandle, INTENT_RELATIVE_COLORIMETRIC, INTENT_RELATIVE_COLORIMETRIC, (cmsFLAGS_GAMUTCHECK|cmsFLAGS_SOFTPROOFING));
538 }
539 return _gamutTransf;
540 }
542 bool ColorProfile::GamutCheck(SPColor color){
543 BYTE outofgamut = 0;
545 guint32 val = color.toRGBA32(0);
546 guchar check_color[4] = {
547 SP_RGBA32_R_U(val),
548 SP_RGBA32_G_U(val),
549 SP_RGBA32_B_U(val),
550 255};
552 int alarm_r, alarm_g, alarm_b;
553 cmsGetAlarmCodes(&alarm_r, &alarm_g, &alarm_b);
554 cmsSetAlarmCodes(255, 255, 255);
555 cmsDoTransform(ColorProfile::getTransfGamutCheck(), &check_color, &outofgamut, 1);
556 cmsSetAlarmCodes(alarm_r, alarm_g, alarm_b);
557 return (outofgamut == 255);
558 }
561 #include <io/sys.h>
563 class ProfileInfo
564 {
565 public:
566 ProfileInfo( cmsHPROFILE, Glib::ustring const & path );
568 Glib::ustring const& getName() {return _name;}
569 Glib::ustring const& getPath() {return _path;}
570 icColorSpaceSignature getSpace() {return _profileSpace;}
571 icProfileClassSignature getClass() {return _profileClass;}
573 private:
574 Glib::ustring _path;
575 Glib::ustring _name;
576 icColorSpaceSignature _profileSpace;
577 icProfileClassSignature _profileClass;
578 };
581 ProfileInfo::ProfileInfo( cmsHPROFILE prof, Glib::ustring const & path )
582 {
583 _path = path;
584 _name = cmsTakeProductDesc(prof);
585 _profileSpace = cmsGetColorSpace( prof );
586 _profileClass = cmsGetDeviceClass( prof );
587 }
591 static std::vector<ProfileInfo> knownProfiles;
593 std::vector<Glib::ustring> Inkscape::colorprofile_get_display_names()
594 {
595 std::vector<Glib::ustring> result;
597 for ( std::vector<ProfileInfo>::iterator it = knownProfiles.begin(); it != knownProfiles.end(); ++it ) {
598 if ( it->getClass() == icSigDisplayClass && it->getSpace() == icSigRgbData ) {
599 result.push_back( it->getName() );
600 }
601 }
603 return result;
604 }
606 std::vector<Glib::ustring> Inkscape::colorprofile_get_softproof_names()
607 {
608 std::vector<Glib::ustring> result;
610 for ( std::vector<ProfileInfo>::iterator it = knownProfiles.begin(); it != knownProfiles.end(); ++it ) {
611 if ( it->getClass() == icSigOutputClass ) {
612 result.push_back( it->getName() );
613 }
614 }
616 return result;
617 }
619 Glib::ustring Inkscape::get_path_for_profile(Glib::ustring const& name)
620 {
621 Glib::ustring result;
623 for ( std::vector<ProfileInfo>::iterator it = knownProfiles.begin(); it != knownProfiles.end(); ++it ) {
624 if ( name == it->getName() ) {
625 result = it->getPath();
626 break;
627 }
628 }
630 return result;
631 }
632 #endif // ENABLE_LCMS
634 std::list<Glib::ustring> ColorProfile::getBaseProfileDirs() {
635 #if ENABLE_LCMS
636 static bool warnSet = false;
637 if (!warnSet) {
638 cmsErrorAction( LCMS_ERROR_SHOW );
639 warnSet = true;
640 }
641 #endif // ENABLE_LCMS
642 std::list<Glib::ustring> sources;
644 gchar* base = profile_path("XXX");
645 {
646 gchar* base2 = g_path_get_dirname(base);
647 g_free(base);
648 base = base2;
649 base2 = g_path_get_dirname(base);
650 g_free(base);
651 base = base2;
652 }
654 // first try user's local dir
655 sources.push_back( g_build_filename(g_get_user_data_dir(), "color", "icc", NULL) );
658 const gchar* const * dataDirs = g_get_system_data_dirs();
659 for ( int i = 0; dataDirs[i]; i++ ) {
660 gchar* path = g_build_filename(dataDirs[i], "color", "icc", NULL);
661 sources.push_back(path);
662 g_free(path);
663 }
665 // On OS X:
666 {
667 bool onOSX = false;
668 std::list<Glib::ustring> possible;
669 possible.push_back("/System/Library/ColorSync/Profiles");
670 possible.push_back("/Library/ColorSync/Profiles");
671 for ( std::list<Glib::ustring>::const_iterator it = possible.begin(); it != possible.end(); ++it ) {
672 if ( g_file_test(it->c_str(), G_FILE_TEST_EXISTS) && g_file_test(it->c_str(), G_FILE_TEST_IS_DIR) ) {
673 sources.push_back(it->c_str());
674 onOSX = true;
675 }
676 }
677 if ( onOSX ) {
678 gchar* path = g_build_filename(g_get_home_dir(), "Library", "ColorSync", "Profiles", NULL);
679 if ( g_file_test(path, G_FILE_TEST_EXISTS) && g_file_test(path, G_FILE_TEST_IS_DIR) ) {
680 sources.push_back(path);
681 }
682 g_free(path);
683 }
684 }
686 #ifdef WIN32
687 wchar_t pathBuf[MAX_PATH + 1];
688 pathBuf[0] = 0;
689 DWORD pathSize = sizeof(pathBuf);
690 g_assert(sizeof(wchar_t) == sizeof(gunichar2));
691 if ( GetColorDirectoryW( NULL, pathBuf, &pathSize ) ) {
692 gchar * utf8Path = g_utf16_to_utf8( (gunichar2*)(&pathBuf[0]), -1, NULL, NULL, NULL );
693 if ( !g_utf8_validate(utf8Path, -1, NULL) ) {
694 g_warning( "GetColorDirectoryW() resulted in invalid UTF-8" );
695 } else {
696 sources.push_back(utf8Path);
697 }
698 g_free( utf8Path );
699 }
700 #endif // WIN32
702 return sources;
703 }
705 static bool isIccFile( gchar const *filepath )
706 {
707 bool isIccFile = false;
708 struct stat st;
709 if ( g_stat(filepath, &st) == 0 && (st.st_size > 128) ) {
710 //0-3 == size
711 //36-39 == 'acsp' 0x61637370
712 int fd = g_open( filepath, O_RDONLY, S_IRWXU);
713 if ( fd != -1 ) {
714 guchar scratch[40] = {0};
715 size_t len = sizeof(scratch);
717 //size_t left = 40;
718 ssize_t got = read(fd, scratch, len);
719 if ( got != -1 ) {
720 size_t calcSize = (scratch[0] << 24) | (scratch[1] << 16) | (scratch[2] << 8) | scratch[3];
721 if ( calcSize > 128 && calcSize <= static_cast<size_t>(st.st_size) ) {
722 isIccFile = (scratch[36] == 'a') && (scratch[37] == 'c') && (scratch[38] == 's') && (scratch[39] == 'p');
723 }
724 }
726 close(fd);
727 }
728 }
729 return isIccFile;
730 }
732 std::list<Glib::ustring> ColorProfile::getProfileFiles()
733 {
734 std::list<Glib::ustring> files;
736 std::list<Glib::ustring> sources = ColorProfile::getBaseProfileDirs();
737 for ( std::list<Glib::ustring>::const_iterator it = sources.begin(); it != sources.end(); ++it ) {
738 if ( g_file_test( it->c_str(), G_FILE_TEST_EXISTS ) && g_file_test( it->c_str(), G_FILE_TEST_IS_DIR ) ) {
739 GError *err = 0;
740 GDir *dir = g_dir_open(it->c_str(), 0, &err);
742 if (dir) {
743 for (gchar const *file = g_dir_read_name(dir); file != NULL; file = g_dir_read_name(dir)) {
744 gchar *filepath = g_build_filename(it->c_str(), file, NULL);
745 if ( g_file_test( filepath, G_FILE_TEST_IS_DIR ) ) {
746 sources.push_back(g_strdup(filepath));
747 } else {
748 if ( isIccFile( filepath ) ) {
749 files.push_back( filepath );
750 }
751 }
753 g_free(filepath);
754 }
755 g_dir_close(dir);
756 dir = 0;
757 } else {
758 gchar *safeDir = Inkscape::IO::sanitizeString(it->c_str());
759 g_warning(_("Color profiles directory (%s) is unavailable."), safeDir);
760 g_free(safeDir);
761 }
762 }
763 }
765 return files;
766 }
768 #if ENABLE_LCMS
769 static void findThings() {
770 std::list<Glib::ustring> files = ColorProfile::getProfileFiles();
772 for ( std::list<Glib::ustring>::const_iterator it = files.begin(); it != files.end(); ++it ) {
773 cmsHPROFILE prof = cmsOpenProfileFromFile( it->c_str(), "r" );
774 if ( prof ) {
775 ProfileInfo info( prof, Glib::filename_to_utf8( it->c_str() ) );
776 cmsCloseProfile( prof );
778 bool sameName = false;
779 for ( std::vector<ProfileInfo>::iterator it = knownProfiles.begin(); it != knownProfiles.end(); ++it ) {
780 if ( it->getName() == info.getName() ) {
781 sameName = true;
782 break;
783 }
784 }
786 if ( !sameName ) {
787 knownProfiles.push_back(info);
788 }
789 }
790 }
791 }
793 int errorHandlerCB(int ErrorCode, const char *ErrorText)
794 {
795 g_message("lcms: Error %d; %s", ErrorCode, ErrorText);
797 return 1;
798 }
800 static bool gamutWarn = false;
801 static Gdk::Color lastGamutColor("#808080");
802 static bool lastBPC = false;
803 #if defined(cmsFLAGS_PRESERVEBLACK)
804 static bool lastPreserveBlack = false;
805 #endif // defined(cmsFLAGS_PRESERVEBLACK)
806 static int lastIntent = INTENT_PERCEPTUAL;
807 static int lastProofIntent = INTENT_PERCEPTUAL;
808 static cmsHTRANSFORM transf = 0;
810 cmsHPROFILE Inkscape::colorprofile_get_system_profile_handle()
811 {
812 static cmsHPROFILE theOne = 0;
813 static Glib::ustring lastURI;
815 static bool init = false;
816 if ( !init ) {
817 cmsSetErrorHandler(errorHandlerCB);
819 findThings();
820 init = true;
821 }
823 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
824 Glib::ustring uri = prefs->getString("/options/displayprofile/uri");
826 if ( !uri.empty() ) {
827 if ( uri != lastURI ) {
828 lastURI.clear();
829 if ( theOne ) {
830 cmsCloseProfile( theOne );
831 }
832 if ( transf ) {
833 cmsDeleteTransform( transf );
834 transf = 0;
835 }
836 theOne = cmsOpenProfileFromFile( uri.data(), "r" );
837 if ( theOne ) {
838 // a display profile must have the proper stuff
839 icColorSpaceSignature space = cmsGetColorSpace(theOne);
840 icProfileClassSignature profClass = cmsGetDeviceClass(theOne);
842 if ( profClass != icSigDisplayClass ) {
843 g_warning("Not a display profile");
844 cmsCloseProfile( theOne );
845 theOne = 0;
846 } else if ( space != icSigRgbData ) {
847 g_warning("Not an RGB profile");
848 cmsCloseProfile( theOne );
849 theOne = 0;
850 } else {
851 lastURI = uri;
852 }
853 }
854 }
855 } else if ( theOne ) {
856 cmsCloseProfile( theOne );
857 theOne = 0;
858 lastURI.clear();
859 if ( transf ) {
860 cmsDeleteTransform( transf );
861 transf = 0;
862 }
863 }
865 return theOne;
866 }
869 cmsHPROFILE Inkscape::colorprofile_get_proof_profile_handle()
870 {
871 static cmsHPROFILE theOne = 0;
872 static Glib::ustring lastURI;
874 static bool init = false;
875 if ( !init ) {
876 cmsSetErrorHandler(errorHandlerCB);
878 findThings();
879 init = true;
880 }
882 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
883 bool which = prefs->getBool( "/options/softproof/enable");
884 Glib::ustring uri = prefs->getString("/options/softproof/uri");
886 if ( which && !uri.empty() ) {
887 if ( lastURI != uri ) {
888 lastURI.clear();
889 if ( theOne ) {
890 cmsCloseProfile( theOne );
891 }
892 if ( transf ) {
893 cmsDeleteTransform( transf );
894 transf = 0;
895 }
896 theOne = cmsOpenProfileFromFile( uri.data(), "r" );
897 if ( theOne ) {
898 // a display profile must have the proper stuff
899 icColorSpaceSignature space = cmsGetColorSpace(theOne);
900 icProfileClassSignature profClass = cmsGetDeviceClass(theOne);
902 (void)space;
903 (void)profClass;
904 /*
905 if ( profClass != icSigDisplayClass ) {
906 g_warning("Not a display profile");
907 cmsCloseProfile( theOne );
908 theOne = 0;
909 } else if ( space != icSigRgbData ) {
910 g_warning("Not an RGB profile");
911 cmsCloseProfile( theOne );
912 theOne = 0;
913 } else {
914 */
915 lastURI = uri;
916 /*
917 }
918 */
919 }
920 }
921 } else if ( theOne ) {
922 cmsCloseProfile( theOne );
923 theOne = 0;
924 lastURI.clear();
925 if ( transf ) {
926 cmsDeleteTransform( transf );
927 transf = 0;
928 }
929 }
931 return theOne;
932 }
934 static void free_transforms();
936 cmsHTRANSFORM Inkscape::colorprofile_get_display_transform()
937 {
938 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
939 bool fromDisplay = prefs->getBool( "/options/displayprofile/from_display");
940 if ( fromDisplay ) {
941 if ( transf ) {
942 cmsDeleteTransform(transf);
943 transf = 0;
944 }
945 return 0;
946 }
948 bool warn = prefs->getBool( "/options/softproof/gamutwarn");
949 int intent = prefs->getIntLimited( "/options/displayprofile/intent", 0, 0, 3 );
950 int proofIntent = prefs->getIntLimited( "/options/softproof/intent", 0, 0, 3 );
951 bool bpc = prefs->getBool( "/options/softproof/bpc");
952 #if defined(cmsFLAGS_PRESERVEBLACK)
953 bool preserveBlack = prefs->getBool( "/options/softproof/preserveblack");
954 #endif //defined(cmsFLAGS_PRESERVEBLACK)
955 Glib::ustring colorStr = prefs->getString("/options/softproof/gamutcolor");
956 Gdk::Color gamutColor( colorStr.empty() ? "#808080" : colorStr );
958 if ( (warn != gamutWarn)
959 || (lastIntent != intent)
960 || (lastProofIntent != proofIntent)
961 || (bpc != lastBPC)
962 #if defined(cmsFLAGS_PRESERVEBLACK)
963 || (preserveBlack != lastPreserveBlack)
964 #endif // defined(cmsFLAGS_PRESERVEBLACK)
965 || (gamutColor != lastGamutColor)
966 ) {
967 gamutWarn = warn;
968 free_transforms();
969 lastIntent = intent;
970 lastProofIntent = proofIntent;
971 lastBPC = bpc;
972 #if defined(cmsFLAGS_PRESERVEBLACK)
973 lastPreserveBlack = preserveBlack;
974 #endif // defined(cmsFLAGS_PRESERVEBLACK)
975 lastGamutColor = gamutColor;
976 }
978 // Fetch these now, as they might clear the transform as a side effect.
979 cmsHPROFILE hprof = Inkscape::colorprofile_get_system_profile_handle();
980 cmsHPROFILE proofProf = hprof ? Inkscape::colorprofile_get_proof_profile_handle() : 0;
982 if ( !transf ) {
983 if ( hprof && proofProf ) {
984 DWORD dwFlags = cmsFLAGS_SOFTPROOFING;
985 if ( gamutWarn ) {
986 dwFlags |= cmsFLAGS_GAMUTCHECK;
987 cmsSetAlarmCodes(gamutColor.get_red() >> 8, gamutColor.get_green() >> 8, gamutColor.get_blue() >> 8);
988 }
989 if ( bpc ) {
990 dwFlags |= cmsFLAGS_BLACKPOINTCOMPENSATION;
991 }
992 #if defined(cmsFLAGS_PRESERVEBLACK)
993 if ( preserveBlack ) {
994 dwFlags |= cmsFLAGS_PRESERVEBLACK;
995 }
996 #endif // defined(cmsFLAGS_PRESERVEBLACK)
997 transf = cmsCreateProofingTransform( ColorProfile::getSRGBProfile(), TYPE_RGBA_8, hprof, TYPE_RGBA_8, proofProf, intent, proofIntent, dwFlags );
998 } else if ( hprof ) {
999 transf = cmsCreateTransform( ColorProfile::getSRGBProfile(), TYPE_RGBA_8, hprof, TYPE_RGBA_8, intent, 0 );
1000 }
1001 }
1003 return transf;
1004 }
1007 class MemProfile {
1008 public:
1009 MemProfile();
1010 ~MemProfile();
1012 std::string id;
1013 cmsHPROFILE hprof;
1014 cmsHTRANSFORM transf;
1015 };
1017 MemProfile::MemProfile() :
1018 id(),
1019 hprof(0),
1020 transf(0)
1021 {
1022 }
1024 MemProfile::~MemProfile()
1025 {
1026 }
1028 static std::vector< std::vector<MemProfile> > perMonitorProfiles;
1030 void free_transforms()
1031 {
1032 if ( transf ) {
1033 cmsDeleteTransform(transf);
1034 transf = 0;
1035 }
1037 for ( std::vector< std::vector<MemProfile> >::iterator it = perMonitorProfiles.begin(); it != perMonitorProfiles.end(); ++it ) {
1038 for ( std::vector<MemProfile>::iterator it2 = it->begin(); it2 != it->end(); ++it2 ) {
1039 if ( it2->transf ) {
1040 cmsDeleteTransform(it2->transf);
1041 it2->transf = 0;
1042 }
1043 }
1044 }
1045 }
1047 Glib::ustring Inkscape::colorprofile_get_display_id( int screen, int monitor )
1048 {
1049 Glib::ustring id;
1051 if ( screen >= 0 && screen < static_cast<int>(perMonitorProfiles.size()) ) {
1052 std::vector<MemProfile>& row = perMonitorProfiles[screen];
1053 if ( monitor >= 0 && monitor < static_cast<int>(row.size()) ) {
1054 MemProfile& item = row[monitor];
1055 id = item.id;
1056 }
1057 }
1059 return id;
1060 }
1062 Glib::ustring Inkscape::colorprofile_set_display_per( gpointer buf, guint bufLen, int screen, int monitor )
1063 {
1064 Glib::ustring id;
1066 while ( static_cast<int>(perMonitorProfiles.size()) <= screen ) {
1067 std::vector<MemProfile> tmp;
1068 perMonitorProfiles.push_back(tmp);
1069 }
1070 std::vector<MemProfile>& row = perMonitorProfiles[screen];
1071 while ( static_cast<int>(row.size()) <= monitor ) {
1072 MemProfile tmp;
1073 row.push_back(tmp);
1074 }
1075 MemProfile& item = row[monitor];
1077 if ( item.hprof ) {
1078 cmsCloseProfile( item.hprof );
1079 item.hprof = 0;
1080 }
1081 id.clear();
1083 if ( buf && bufLen ) {
1084 id = Digest::hashHex(Digest::HASH_MD5,
1085 reinterpret_cast<unsigned char*>(buf), bufLen);
1087 // Note: if this is not a valid profile, item.hprof will be set to null.
1088 item.hprof = cmsOpenProfileFromMem(buf, bufLen);
1089 }
1090 item.id = id;
1092 return id;
1093 }
1095 cmsHTRANSFORM Inkscape::colorprofile_get_display_per( Glib::ustring const& id )
1096 {
1097 cmsHTRANSFORM result = 0;
1098 if ( id.empty() ) {
1099 return 0;
1100 }
1102 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1103 bool found = false;
1104 for ( std::vector< std::vector<MemProfile> >::iterator it = perMonitorProfiles.begin(); it != perMonitorProfiles.end() && !found; ++it ) {
1105 for ( std::vector<MemProfile>::iterator it2 = it->begin(); it2 != it->end() && !found; ++it2 ) {
1106 if ( id == it2->id ) {
1107 MemProfile& item = *it2;
1109 bool warn = prefs->getBool( "/options/softproof/gamutwarn");
1110 int intent = prefs->getIntLimited( "/options/displayprofile/intent", 0, 0, 3 );
1111 int proofIntent = prefs->getIntLimited( "/options/softproof/intent", 0, 0, 3 );
1112 bool bpc = prefs->getBool( "/options/softproof/bpc");
1113 #if defined(cmsFLAGS_PRESERVEBLACK)
1114 bool preserveBlack = prefs->getBool( "/options/softproof/preserveblack");
1115 #endif //defined(cmsFLAGS_PRESERVEBLACK)
1116 Glib::ustring colorStr = prefs->getString("/options/softproof/gamutcolor");
1117 Gdk::Color gamutColor( colorStr.empty() ? "#808080" : colorStr );
1119 if ( (warn != gamutWarn)
1120 || (lastIntent != intent)
1121 || (lastProofIntent != proofIntent)
1122 || (bpc != lastBPC)
1123 #if defined(cmsFLAGS_PRESERVEBLACK)
1124 || (preserveBlack != lastPreserveBlack)
1125 #endif // defined(cmsFLAGS_PRESERVEBLACK)
1126 || (gamutColor != lastGamutColor)
1127 ) {
1128 gamutWarn = warn;
1129 free_transforms();
1130 lastIntent = intent;
1131 lastProofIntent = proofIntent;
1132 lastBPC = bpc;
1133 #if defined(cmsFLAGS_PRESERVEBLACK)
1134 lastPreserveBlack = preserveBlack;
1135 #endif // defined(cmsFLAGS_PRESERVEBLACK)
1136 lastGamutColor = gamutColor;
1137 }
1139 // Fetch these now, as they might clear the transform as a side effect.
1140 cmsHPROFILE proofProf = item.hprof ? Inkscape::colorprofile_get_proof_profile_handle() : 0;
1142 if ( !item.transf ) {
1143 if ( item.hprof && proofProf ) {
1144 DWORD dwFlags = cmsFLAGS_SOFTPROOFING;
1145 if ( gamutWarn ) {
1146 dwFlags |= cmsFLAGS_GAMUTCHECK;
1147 cmsSetAlarmCodes(gamutColor.get_red() >> 8, gamutColor.get_green() >> 8, gamutColor.get_blue() >> 8);
1148 }
1149 if ( bpc ) {
1150 dwFlags |= cmsFLAGS_BLACKPOINTCOMPENSATION;
1151 }
1152 #if defined(cmsFLAGS_PRESERVEBLACK)
1153 if ( preserveBlack ) {
1154 dwFlags |= cmsFLAGS_PRESERVEBLACK;
1155 }
1156 #endif // defined(cmsFLAGS_PRESERVEBLACK)
1157 item.transf = cmsCreateProofingTransform( ColorProfile::getSRGBProfile(), TYPE_RGBA_8, item.hprof, TYPE_RGBA_8, proofProf, intent, proofIntent, dwFlags );
1158 } else if ( item.hprof ) {
1159 item.transf = cmsCreateTransform( ColorProfile::getSRGBProfile(), TYPE_RGBA_8, item.hprof, TYPE_RGBA_8, intent, 0 );
1160 }
1161 }
1163 result = item.transf;
1164 found = true;
1165 }
1166 }
1167 }
1169 return result;
1170 }
1174 #endif // ENABLE_LCMS
1176 /*
1177 Local Variables:
1178 mode:c++
1179 c-file-style:"stroustrup"
1180 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1181 indent-tabs-mode:nil
1182 fill-column:99
1183 End:
1184 */
1185 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :