/* * CinePaint Color Management System * Copyright (C) 2004 Stefan Klein (kleins web de) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include "cms.h" #include #include #include #include #ifdef __FreeBSD__ # define USE_ALLOCA #else # undef USE_ALLOCA # define MAX_NUM_PROFILES 1024 /* ARGH */ #endif #ifdef USE_ALLOCA # include #endif #include "../lib/version.h" #include "convert.h" #include "fileops.h" #include "general.h" #include "gdisplay.h" #include "gimage_cmds.h" #include "pixelarea.h" #include "pixelrow.h" #include "rc.h" #include "tag.h" #define _(translate_string) translate_string /* parameter for the transform cache */ /* how many transform cache entries can be kept without being used by anyone * (for efficiency, since likely to be used again soon) * (transforms can be really big, at least 200K, up to megs) */ #define MAX_UNUSED_TRANSFORMS_IN_MEM 1 /* (note that this does not restrict the number of transforms that can be in use * at the same time from calling cms_get_transform, only the number that is kept * in mem or on disc while not being used for efficiency, * also, pre-calculated transforms are always cached on disc, no matter whether the * memory cache is exceeded) */ /* definition of lcms constants */ #define TYPE_RGBA_DBL (COLORSPACE_SH(PT_RGB)|EXTRA_SH(1)|CHANNELS_SH(3)|BYTES_SH(0)) #define TYPE_GRAYA_DBL (COLORSPACE_SH(PT_GRAY)|EXTRA_SH(1)|CHANNELS_SH(1)|BYTES_SH(0)) /*** TYPE DECLARATIONS ***/ /* the func used to transform, depending on the data being float or uint */ typedef void (*TransformFunc) (CMSTransform *transform, void *src_data, void *dest_data, int num_pixels); /*** TYPES ***/ /* an entry in the profile cache */ typedef struct _ProfileCacheEntry { CMSProfile *profile; gint ref_count; } ProfileCacheEntry; /* an entry in the profile cache, the transform is a transform in memory, the device_link_file is the file name of a temporary pre-calculated transform stored on disc, device_link_handle is the handle to it */ typedef struct _TransformCacheEntry { CMSTransform *transform; cmsHPROFILE device_link_file; gint ref_count; } TransformCacheEntry; /* a profile * cache_key is the key in the cache, handle the lcms handle */ struct _CMSProfile { gchar *cache_key; cmsHPROFILE handle; char *data; /* save original data for profile i/o */ size_t size; }; /* same for transform */ struct _CMSTransform { gchar *cache_key; cmsHTRANSFORM handle; int erase_alpha_from_4th_color; /* CMYK hack by beku */ }; /*** VARIABLE DECLARATIONS ***/ /* handler of display profile loaded in cms_init, according to user-preferences */ CMSProfile *display_profile = NULL; /* caches for profiles and transforms */ /* contains profile filename as key, profile handle as value */ static GHashTable *profile_cache = NULL; /* key build out of profile pointers + lcms parameters, contains devicelink handle as value */ static GHashTable *transform_cache = NULL; /* transform cache entries that are not in use, but kept in memory for efficiency will be few, < MAX_TRANSFORMS_KEPT_IN_MEM */ static GList *unused_transforms_kept_in_mem = NULL; /* buffer to return profile information in */ static CMSProfileInfo *profile_info_buffer; /*** FUNCTION DECLARATIONS ***/ /* initialize the current transformation by calculating it or reloading from disk */ gboolean cms_init_transform(GDisplay *display); /* function to empty the profile/transform cache hashtables, close the profiles*/ gboolean cms_empty_transform_cache(gpointer key, gpointer value, gpointer user_data); /* Auxiliaries to extract profile info */ const char* cms_get_long_profile_info (cmsHPROFILE hProfile); const char* cms_get_profile_keyname (cmsHPROFILE hProfile, char* mem); const char* cms_get_pcs_name (cmsHPROFILE hProfile); const char* cms_get_color_space_name (cmsHPROFILE hProfile); const char** cms_get_color_space_channel_names (cmsHPROFILE hProfile); const char* cms_get_device_class_name (cmsHPROFILE hProfile); /* workarround for some newer distributions with old lcms - beku */ #if (LCMS_VERSION <= 112) int _cmsLCMScolorSpace(icColorSpaceSignature ProfileSpace); #endif /*** * CACHE AUXILIARIES ***/ /* * empty the profile cache hashtable, closes the profiles, * used by cms_free */ static gboolean cms_delete_profile_from_cache(gpointer key, gpointer value, gpointer user_data) { ProfileCacheEntry *entry = (ProfileCacheEntry *)value; if (entry->profile->cache_key) g_free(entry->profile->cache_key); else g_warning ("%s:%d %s() entry->profile->cache_key allready freed or empty", __FILE__,__LINE__,__func__); if (entry->profile->handle) cmsCloseProfile(entry->profile->handle); else g_warning ("%s:%d %s() entry->profile->handle allready freed or empty", __FILE__,__LINE__,__func__); g_free(entry->profile); g_free(entry); return TRUE; } /* * delete the transform from disc (and therefore also mem) cache * used by cms_free and cms_return_transform */ static gboolean cms_delete_transform_from_cache(gpointer key, gpointer value, gpointer user_data) { TransformCacheEntry *cache_entry = (TransformCacheEntry *)value; if (cache_entry->transform->handle != NULL) { cmsDeleteTransform(cache_entry->transform->handle); } g_free(cache_entry->transform->cache_key); g_free(cache_entry->transform); g_free(cache_entry->device_link_file); cache_entry->device_link_file = NULL; g_free(cache_entry); return TRUE; } /* CMYK hack from beku */ void erase_alpha_from_4th_color (CMSTransform *transform) { transform->erase_alpha_from_4th_color = TRUE; } /*** * INITIALIZATION AND TIDYING ***/ /* * does initializations * loads input and display profile * and calculates standard display transform (input->display) * called from app_procs.c:app_init() */ void cms_init() { GList *remove_settings = NULL; GList *update_settings = NULL; cmsHPROFILE test = NULL; profile_cache = g_hash_table_new(g_str_hash, g_str_equal); transform_cache = g_hash_table_new(g_str_hash, g_str_equal); profile_info_buffer = g_new(CMSProfileInfo, 1); profile_info_buffer->manufacturer = NULL; profile_info_buffer->description = NULL; profile_info_buffer->pcs = NULL; profile_info_buffer->color_space_name = NULL; profile_info_buffer->color_space_channel_names = NULL; profile_info_buffer->device_class_name = NULL; profile_info_buffer->long_info = NULL; /* sanity checks of settings, remove failed settings */ /* suppress lcms errors while checking */ cmsErrorAction(LCMS_ERROR_IGNORE); /* 1. image profile */ if (cms_default_image_profile_name != NULL) { test = cmsOpenProfileFromFile(cms_default_image_profile_name, "r"); if (test == NULL) { g_warning ("Cannot open selected ICC assumed image profile: %s", cms_default_image_profile_name); cms_default_image_profile_name = NULL; remove_settings = g_list_append(remove_settings, "cms-default-image-profile-name"); } cmsCloseProfile(test); } /* 2. editing/workspace profile */ if (cms_workspace_profile_name != NULL) { test = cmsOpenProfileFromFile(cms_workspace_profile_name, "r"); if (test == NULL) { g_warning ("Cannot open selected ICC editing profile: %s", cms_workspace_profile_name); cms_workspace_profile_name = NULL; remove_settings = g_list_append(remove_settings, "cms-workspace-profile-name"); } cmsCloseProfile(test); } /* 3. display profile */ if (cms_display_profile_name != NULL) { test = cmsOpenProfileFromFile(cms_display_profile_name, "r"); if (test == NULL) { g_warning ("Cannot open selected ICC display profile: %s", cms_display_profile_name); cms_display_profile_name = NULL; remove_settings = g_list_append(remove_settings, "cms-display-profile-name"); } /* make sure it is a display profile */ else if (cmsGetDeviceClass (test) != icSigDisplayClass) { g_warning ("Selected display profile is not a display profile: %s", cms_display_profile_name); cms_display_profile_name = NULL; remove_settings = g_list_append(remove_settings, "cms-display-profile-name"); } else { /* open the display profile */ display_profile = cms_get_profile_from_file(cms_display_profile_name); } cmsCloseProfile(test); } /* 4. proof profile */ if (cms_default_proof_profile_name != NULL) { test = cmsOpenProfileFromFile(cms_default_proof_profile_name, "r"); if (test == NULL) { g_warning ("Cannot open selected ICC default proof profile: %s", cms_default_proof_profile_name); cms_default_proof_profile_name = NULL; remove_settings = g_list_append(remove_settings, "cms-default-proof-profile-name"); } cmsCloseProfile(test); } if (g_list_length(remove_settings) != 0) { save_gimprc(&update_settings, &remove_settings); } g_list_free(remove_settings); /* show lcms-errors, but don't stop the application */ cmsErrorAction(LCMS_ERROR_SHOW); } /* * tidies up, called from app_procs.c:app_exit_finish() */ void cms_free() { /* free the caches */ g_hash_table_foreach_remove(profile_cache, cms_delete_profile_from_cache, NULL); g_hash_table_destroy(profile_cache); g_hash_table_foreach_remove(transform_cache, cms_delete_transform_from_cache, NULL); g_hash_table_destroy(transform_cache); g_free(profile_info_buffer); } /*** * AUXILIARIES FOR EXTRACTING PROFILE INFO ***/ const char* cms_get_pcs_name (cmsHPROFILE hProfile) { static char pcs[4]; switch (cmsGetPCS (hProfile)) { case icSigXYZData: sprintf(pcs,"XYZ"); break; case icSigLabData: sprintf(pcs,"Lab"); break; default: pcs[0] = '\0'; } return pcs; } const char* cms_get_color_space_name (cmsHPROFILE hProfile) { static gchar name[10]; switch (cmsGetColorSpace (hProfile)) { case icSigXYZData: sprintf(name, "XYZ"); break; case icSigLabData: sprintf(name, "Lab"); break; case icSigLuvData: sprintf(name, "Luv"); break; case icSigYCbCrData: sprintf(name, "YCbCr"); break; case icSigYxyData: sprintf(name, "Yxy"); break; case icSigRgbData: sprintf(name, "Rgb"); break; case icSigGrayData: sprintf(name, "Gray"); break; case icSigHsvData: sprintf(name, "Hsv"); break; case icSigHlsData: sprintf(name, "Hls"); break; case icSigCmykData: sprintf(name, "Cmyk"); break; case icSigCmyData: sprintf(name, "Cmy"); break; case icSig2colorData: sprintf(name, "2color"); break; case icSig3colorData: sprintf(name, "3color"); break; case icSig4colorData: sprintf(name, "4color"); break; case icSig5colorData: sprintf(name, "5color"); break; case icSig6colorData: sprintf(name, "7color"); break; case icSig8colorData: sprintf(name, "8color"); break; case icSig9colorData: sprintf(name, "9color"); break; case icSig10colorData: sprintf(name, "10color"); break; case icSig11colorData: sprintf(name, "11color"); break; case icSig12colorData: sprintf(name, "12color"); break; case icSig13colorData: sprintf(name, "13color"); break; case icSig14colorData: sprintf(name, "14color"); break; case icSig15colorData: sprintf(name, "15color"); break; default: name[0] = '\0'; } return name; } const char** cms_get_color_space_channel_names (cmsHPROFILE hProfile) { static char** name = 0; const char** ret = 0; if(!name) { int i; name=(char**)calloc(sizeof(char**),4); for(i = 0; i < 4; ++i) name[i] = (char*)calloc(sizeof(char*),36); } ret = name; sprintf( name[3],_("Alpha")); switch (cmsGetColorSpace (hProfile)) { case icSigXYZData: sprintf( name[0],_("CIE X")); sprintf( name[1],_("CIE Y (Luminance)")); sprintf( name[2],_("CIE Z")); break; case icSigLabData: sprintf( name[0],_("CIE *L")); sprintf( name[1],_("CIE *a")); sprintf( name[2],_("CIE *b")); break; case icSigLuvData: sprintf( name[0],_("CIE *L")); sprintf( name[1],_("CIE *u")); sprintf( name[2],_("CIE *v")); break; case icSigYCbCrData: sprintf( name[0],_("Luminance Y")); sprintf( name[1],_("Colour b")); sprintf( name[2],_("Colour r")); break; case icSigYxyData: sprintf( name[0],_("CIE Y (Luminance)")); sprintf( name[1],_("CIE x")); sprintf( name[2],_("CIE y")); break; case icSigRgbData: sprintf( name[0],_("Red")); sprintf( name[1],_("Green")); sprintf( name[2],_("Blue")); break; case icSigGrayData: sprintf( name[0],_("Black")); break; case icSigHsvData: sprintf( name[0],_("Hue")); sprintf( name[1],_("Saturation")); sprintf( name[2],_("Value")); break; case icSigHlsData: sprintf( name[0],_("Hue")); sprintf( name[1],_("Lightness")); sprintf( name[2],_("Saturation")); break; case icSigCmykData: sprintf( name[0],_("Cyan")); sprintf( name[1],_("Magenta")); sprintf( name[2],_("Yellow")); sprintf( name[3],_("Black")); break; case icSigCmyData: sprintf( name[0],_("Cyan")); sprintf( name[1],_("Magenta")); sprintf( name[2],_("Yellow")); break; case icSig2colorData: sprintf( name[0],_("1. Colour")); sprintf( name[1],_("2. Colour")); break; case icSig3colorData: sprintf( name[0],_("1. Colour")); sprintf( name[1],_("2. Colour")); sprintf( name[2],_("3. Colour")); break; case icSig4colorData: sprintf( name[0],_("1. Colour")); sprintf( name[1],_("2. Colour")); sprintf( name[2],_("3. Colour")); sprintf( name[3],_("4. Colour")); break; case icSig5colorData: case icSig6colorData: case icSig7colorData: case icSig8colorData: case icSig9colorData: case icSig10colorData: case icSig11colorData: case icSig12colorData: case icSig13colorData: case icSig14colorData: case icSig15colorData: default: sprintf( name[0],_("1. Colour")); sprintf( name[1],_("2. Colour")); sprintf( name[2],_("3. Colour")); sprintf( name[3],_("4. Colour")); } return ret; } const char* cms_get_device_class_name (cmsHPROFILE hProfile) { static char class[15]; switch (cmsGetDeviceClass (hProfile)) { case icSigInputClass: sprintf(class, "Input"); break; case icSigDisplayClass: sprintf(class, "Display"); break; case icSigOutputClass: sprintf(class, "Output"); break; case icSigLinkClass: sprintf(class, "Link"); break; case icSigAbstractClass: sprintf(class, "Abstract"); break; case icSigColorSpaceClass: sprintf(class, "ColorSpace"); break; case icSigNamedColorClass: sprintf(class, "NamedColor"); break; default: class[0] = '\0'; } return class; } icUInt16Number icUInt16Value (icUInt16Number val) { #if BYTE_ORDER == LITTLE_ENDIAN #define BYTES 2 #define KORB 4 unsigned char *temp = (unsigned char*) &val; static unsigned char korb[KORB]; int i; int klein = 0, gross = BYTES - 1; icUInt16Number *erg; for (i = 0; i < KORB ; i++ ) korb[i] = (int) 0; /* leeren */ for (; klein < BYTES ; klein++ ) { korb[klein] = temp[gross--]; } erg = (icUInt16Number*) &korb[0]; return (icUInt16Number)*erg; #else return (icUInt16Number)val; #endif } const char * cms_get_profile_keyname (cmsHPROFILE hProfile, char* mem) { static char text[2048]; icDateTimeNumber *date = (icDateTimeNumber*)&mem[24]; sprintf(text, "%s", cms_get_long_profile_info(hProfile)); if(strlen(text) < 2000) { sprintf(&text[strlen(text)], "%d/%d/%d %d:%d:%d", icUInt16Value(date->day), icUInt16Value(date->month), icUInt16Value(date->year), icUInt16Value(date->hours), icUInt16Value(date->minutes), icUInt16Value(date->seconds)); } return text; } const char * cms_get_long_profile_info(cmsHPROFILE hProfile) { static char profile_info[2048]; gchar *text; cmsCIEXYZ WhitePt; int first = FALSE, min_len = 24, /* formatting */ len, i; text = malloc(256); #if LCMS_VERSION >= 113 /* formatting */ if (cmsIsTag(hProfile, icSigCopyrightTag)) { len = strlen (cmsTakeCopyright(hProfile)) /*rsr 16*/; if (len > min_len) min_len = len + 1; } #endif profile_info[0] = '\000'; if (cmsIsTag(hProfile, icSigProfileDescriptionTag)) { sprintf (text,_("Description: ")); sprintf (profile_info,"%s%s %s",profile_info,text, cmsTakeProductDesc(hProfile)); if (!first) { len = min_len - strlen (profile_info); for (i=0; i < len * 2.2; i++) { sprintf (profile_info,"%s ",profile_info); } } sprintf (profile_info,"%s\n",profile_info); } if (cmsIsTag(hProfile, icSigDeviceMfgDescTag)) { sprintf (text,_("Product: ")); sprintf (profile_info,"%s%s %s\n",profile_info,text, cmsTakeProductName(hProfile)); } #if LCMS_VERSION >= 112 if (cmsIsTag(hProfile, icSigDeviceMfgDescTag)) { sprintf (text,_("Manufacturer: ")); sprintf (profile_info,"%s%s %s\n",profile_info,text, cmsTakeManufacturer(hProfile)); } if (cmsIsTag(hProfile, icSigDeviceModelDescTag)) { sprintf (text,_("Model: ")); sprintf (profile_info,"%s%s %s\n",profile_info,text, cmsTakeModel(hProfile)); } #endif #if LCMS_VERSION >= 113 if (cmsIsTag(hProfile, icSigCopyrightTag)) { sprintf (text,_("Copyright: ")); sprintf (profile_info,"%s%s %s\n",profile_info,text, cmsTakeCopyright(hProfile)); } #endif sprintf (profile_info,"%s\n",profile_info); cmsTakeMediaWhitePoint (&WhitePt, hProfile); _cmsIdentifyWhitePoint (text, &WhitePt); sprintf (profile_info, "%s%s\n", profile_info, text); sprintf (text,_("Device Class: ")); sprintf (profile_info,"%s%s %s\n",profile_info,text, cms_get_device_class_name(hProfile)); sprintf (text,_("Color Space: ")); sprintf (profile_info,"%s%s %s\n",profile_info,text, cms_get_color_space_name(hProfile)); sprintf (text,_("PCS Space: ")); sprintf (profile_info,"%s%s %s",profile_info,text, cms_get_pcs_name(hProfile)); free (text); return profile_info; } /*** * INFORMATION FUNCTIONS ***/ /* * compiles a list of filenames (including path) of profiles in the given directory, filters by * profile class, class==CMS_ANY_PROFILECLASS gives all profiles, does not recur over dirs * returns a list of char * or NULL in case of error */ GSList * cms_read_icc_profile_dir(gchar *path, icProfileClassSignature class) { struct stat statbuf; DIR *dir; struct dirent *entry; GSList *return_list = NULL; if ((stat (path, &statbuf)) != 0) { g_warning ("cms_read_icc_profile_dir: Cannot stat icc profile directory %s", path); return NULL; } if (!S_ISDIR (statbuf.st_mode)) { g_warning ("cms_read_icc_profile_dir: path is not a directory: %s", path); return NULL; } dir = opendir (path); if (!dir) { g_warning ("cms_read_icc_profile_dir: cannot open icc profile directory %s", path); return NULL; } /* to avoid error messages for files that are not profiles/corrupt profiles stored in the profile directory */ cmsErrorAction(LCMS_ERROR_IGNORE); while ((entry = readdir (dir))) { char *file_name = entry->d_name; cmsHPROFILE profile; GString *file_path = g_string_new(NULL); if ((strcmp (file_name, "..") == 0) || (strcmp (file_name, ".") == 0)) { continue; } g_string_sprintf (file_path, "%s/%s", path, file_name); /* Open the file and try to read it using the lcms library - if this fails it was not a valid profile. */ profile = cmsOpenProfileFromFile (file_path->str, "r"); /* if it is a valid profile and of the given clas */ if ((profile != NULL) && ((class == CMS_ANY_PROFILECLASS) || (class == cmsGetDeviceClass (profile)))) { return_list = g_slist_append(return_list, (gpointer) file_path->str); } cmsCloseProfile (profile); g_string_free(file_path, FALSE); } closedir(dir); cmsErrorAction(LCMS_ERROR_SHOW); return return_list; } /* * compiles a list of filenames (including path) of all profiles in the global preference * cms_profile_path filters by * profile class, class==CMS_ANY_PROFILECLASS gives all profiles, does not recur over dirs * returns a list of char * or NULL in case of error */ GSList * cms_read_standard_profile_dirs(icProfileClassSignature class) { /* the temporary list to hold the names */ GSList* return_list = NULL; char *path = NULL; const char *home = GetDirHome(); char *directories = g_strdup(cms_profile_path); GString *file_path = g_string_new(NULL); /* process path by path, paths are separated by : */ char *remaining_directories = directories; char *token = xstrsep(&remaining_directories, ":"); GSList *sub_list = 0; while (token) { /* replace ~ by home dir */ if (*token == '~') { path = g_malloc(strlen(home) + strlen(token) + 2); sprintf(path, "%s%s", home, token + 1); } else { path = strdup(token); } sub_list = cms_read_icc_profile_dir(path, class); return_list = g_slist_concat(return_list, sub_list); g_free(path); token = xstrsep(&remaining_directories, ":"); } g_string_free(file_path, TRUE); g_free(directories); return return_list; } /* * gets meta information about the profile * (only the file_name and description for the moment) * returned in a fixed buffer that is overwritten by * subsequent calls */ CMSProfileInfo * cms_get_profile_info(CMSProfile *profile) { profile_info_buffer->manufacturer = cmsTakeManufacturer(profile->handle); profile_info_buffer->description = cmsTakeProductDesc(profile->handle); profile_info_buffer->pcs = cms_get_pcs_name(profile->handle); profile_info_buffer->color_space_name = cms_get_color_space_name(profile->handle); profile_info_buffer->color_space_channel_names = cms_get_color_space_channel_names(profile->handle); # ifdef DEBUG printf("%s:%d ",__FILE__,__LINE__); printf("%s\n",profile_info_buffer->color_space_channel_names[2]); # endif profile_info_buffer->device_class_name = cms_get_device_class_name(profile->handle); profile_info_buffer->long_info = cms_get_long_profile_info(profile->handle); return profile_info_buffer; } /* returns the lcms format corresponding to this tag */ DWORD cms_get_lcms_format(Tag tag, CMSProfile *profile) { gint lcms_data_format = 0; /* lets be specific to color space */ /* _cmsLCMScolorSpace is available since lcms-1.13 */ int color_space = _cmsLCMScolorSpace ( cmsGetColorSpace (profile->handle) ); int color_channels = _cmsChannelsOf ( cmsGetColorSpace (profile->handle) ); int precision = 2; int alpha = 0; lcms_data_format = (COLORSPACE_SH(color_space)|CHANNELS_SH(color_channels)); /* what bitdepth ? */ switch (tag_precision(tag)) { case PRECISION_U8: precision = 1; break; case PRECISION_U16: precision = 2; break; case PRECISION_FLOAT: precision = 0; break; default: g_warning ("%s:%d %s(): precision not supported", __FILE__,__LINE__,__func__); return 0; } lcms_data_format = (lcms_data_format | BYTES_SH(precision)); /* set the alpha bit in lcms tag */ switch (tag_format(tag)) { case FORMAT_GRAY: if (tag_alpha(tag) == ALPHA_YES) { if (color_channels == 1) { alpha = 1; } } else { if (color_channels == 2) { g_message ("Please add alpha to image before converting."); return 0; } else if (color_channels > 2) { g_message ("Grayscale cannot handle more than 2 color channels."); return 0; } } break; case FORMAT_RGB: if (tag_alpha(tag) == ALPHA_YES) { if (color_channels <= 3) { alpha = 4 - color_channels; } } else { if (color_channels == 4) { g_message ("Please add alpha to image before converting."); return 0; } else if (color_channels > 4) { g_message ("CainePaint cannot handle more than 4 color channels."); return 0; } } break; } if (alpha) lcms_data_format = (lcms_data_format|EXTRA_SH(alpha)); return lcms_data_format; } /*** * THE CORE: OBTAINING PROFILES AND TRANSFORMS AND TRANSFORMATION ***/ /* * loading an profile from file into memory */ char* cms_load_profile_to_mem (char* filename, size_t *order_size) { FILE *fp=NULL; char *ptr = NULL; size_t size = 0; if ((fp = fopen(filename, "r")) != NULL) { fseek(fp, 0, SEEK_END); size = ftell (fp); if (size > 0) { rewind (fp); if(*order_size > 0 && size > *order_size) { ptr = calloc (sizeof (char), *order_size); fread (ptr, sizeof (char), *order_size, fp); } else { ptr = calloc (sizeof (char), size); fread (ptr, sizeof (char), size, fp); *order_size = size; } } } fclose (fp); return ptr; } /* * get a handle to the profile in file filename * either from the cache or from the file * returns NULL on error */ CMSProfile * cms_get_profile_from_file(char *file_name) { CMSProfile *return_value; size_t size_order = 128; char *mem = 0; const char *keyname = 0; ProfileCacheEntry *cache_entry = 0; /* get profile information */ cmsHPROFILE profile = cmsOpenProfileFromFile (file_name, "r"); if (profile == NULL) { g_warning("cms_get_profile_from_file: cannot open profile: %s", file_name); return NULL; } mem = cms_load_profile_to_mem (file_name, &size_order); keyname = cms_get_profile_keyname (profile, mem); /* check hash table for profile */ cache_entry = g_hash_table_lookup(profile_cache, (gpointer) cms_get_profile_keyname(profile,mem)); if (cache_entry != NULL) { cache_entry->ref_count ++; return_value = cache_entry->profile; cmsCloseProfile(profile); if(mem) free(mem); return return_value; } /* if not in cache, generate new profile */ return_value = g_new(CMSProfile, 1); return_value->cache_key = strdup(cms_get_profile_keyname(profile,mem)); return_value->handle = profile; /* save an copy of the original icc profile to mem */ return_value->size = 0; return_value->data = cms_load_profile_to_mem (file_name, &(return_value->size)); cache_entry = g_new(ProfileCacheEntry, 1); cache_entry->ref_count = 1; cache_entry->profile = return_value; g_hash_table_insert(profile_cache, (gpointer) return_value->cache_key, (gpointer) cache_entry); if(mem) free(mem); return return_value; } /* registers a profile that lies in memory with the cms * (used for embedded profiles opened by plug-ins) * again we check the cache to see whether the profile is already * there, if it is, we use the cache handle and free the memory * (so never access it after the call to this function!) */ CMSProfile * cms_get_profile_from_mem(void *mem_pointer, DWORD size) { CMSProfile *return_value; ProfileCacheEntry *cache_entry = 0; /* get profile information */ cmsHPROFILE profile = cmsOpenProfileFromMem (mem_pointer, size); if (profile == NULL) { g_warning("cms_get_profile_from_mem: cannot open profile"); return NULL; } /* check hash table for profile */ cache_entry = g_hash_table_lookup(profile_cache, (gpointer) cms_get_profile_keyname(profile,mem_pointer)); if (cache_entry != NULL) { cache_entry->ref_count ++; return_value = cache_entry->profile; cmsCloseProfile(profile); /* free the memory */ g_free(mem_pointer); return return_value; } /* if not in cache, generate new profile */ return_value = g_new(CMSProfile, 1); return_value->cache_key = strdup(cms_get_profile_keyname(profile,mem_pointer)); return_value->handle = profile; cache_entry = g_new(ProfileCacheEntry, 1); cache_entry->ref_count = 1; cache_entry->profile = return_value; g_hash_table_insert(profile_cache, (gpointer) return_value->cache_key, (gpointer) cache_entry); /* copy profile data */ return_value->size = size; return_value->data = (char*) calloc (sizeof (char), size); memcpy (return_value->data, mem_pointer, size); return return_value; } /* returns the ICC profile data from an cms internal profile * (used for embeddeding profiles by file i/o plug-ins) * check for the existance of an profile else return NULL */ char * cms_get_profile_data(CMSProfile *profile, size_t *size) { char *return_value = NULL; /* get profile data */ if (profile == NULL) { g_print("cms_get_profile_data: no profile in image"); return NULL; } /* not relyable with internal lcms profiles: _cmsSaveProfileToMem (profile->handle, &return_value, size); */ if (profile->size) { return_value = (char*) calloc (sizeof (char), profile->size); memcpy (return_value, profile->data, profile->size); *size = profile->size; } return return_value; } /* get a handle to a (lcms built-in) lab profile with given white_point */ CMSProfile * cms_get_lab_profile(LPcmsCIExyY white_point) { CMSProfile *return_value; ProfileCacheEntry *cache_entry = 0; GString *hash_key = g_string_new(NULL); if (white_point) { g_string_sprintf(hash_key, "###LAB%f%f%f###", white_point->x, white_point->y, white_point->Y); } else { g_string_sprintf(hash_key, "###LAB###"); } cache_entry = g_hash_table_lookup(profile_cache, (gpointer) hash_key); if (cache_entry != NULL) { cache_entry->ref_count ++; return_value = cache_entry->profile; g_string_free(hash_key, TRUE); return return_value; } /* if not in cache, generate new profile */ return_value = g_new(CMSProfile, 1); return_value->handle = cmsCreateLabProfile (white_point); cmsAddTag(return_value->handle, icSigCopyrightTag, "no copyright, use freely"); return_value->cache_key = hash_key->str; return_value->data = NULL; return_value->size = 0; /* get first the profiles size and then allocate memory */ _cmsSaveProfileToMem (return_value->handle, NULL, &(return_value->size)); return_value->data = calloc (return_value->size, sizeof (char)); _cmsSaveProfileToMem (return_value->handle, return_value->data, &(return_value->size)); /* reopen due to limitations in _cmsSaveProfile/_cmsSaveProfileToMem */ cmsCloseProfile (return_value->handle); return_value->handle = cmsOpenProfileFromMem (return_value->data, return_value->size); if (!return_value->data) { return_value->size = 0; g_warning ("%s:%d %s() profile empty\n",__FILE__,__LINE__,__func__); g_string_free(hash_key, FALSE); return NULL; } cache_entry = g_new(ProfileCacheEntry, 1); cache_entry->ref_count = 1; cache_entry->profile = return_value; g_hash_table_insert(profile_cache, (gpointer) return_value->cache_key, (gpointer) cache_entry); g_string_free(hash_key, FALSE); return return_value; } /* get a handle to a (lcms built-in) xyz profile */ CMSProfile * cms_get_xyz_profile() { CMSProfile *return_value; ProfileCacheEntry *cache_entry = g_hash_table_lookup(profile_cache, "###XYZ###"); if (cache_entry != NULL) { cache_entry->ref_count ++; return_value = cache_entry->profile; return return_value; } /* if not in cache, generate new profile */ return_value = g_new(CMSProfile, 1); return_value->handle = cmsCreateXYZProfile(); cmsAddTag(return_value->handle, icSigCopyrightTag, "no copyright, use freely"); return_value->cache_key = "###XYZ###"; return_value->data = NULL; return_value->size = 0; /* get first the profiles size and then allocate memory */ _cmsSaveProfileToMem (return_value->handle, NULL, &(return_value->size)); return_value->data = calloc (return_value->size, sizeof (char)); _cmsSaveProfileToMem (return_value->handle, return_value->data, &(return_value->size)); /* reopen due to limitations in _cmsSaveProfile/_cmsSaveProfileToMem */ cmsCloseProfile (return_value->handle); return_value->handle = cmsOpenProfileFromMem (return_value->data, return_value->size); if (!return_value->data) { return_value->size = 0; g_warning ("%s:%d %s() profile data empty\n",__FILE__,__LINE__,__func__); return NULL; } cache_entry = g_new(ProfileCacheEntry, 1); cache_entry->ref_count = 1; cache_entry->profile = return_value; g_hash_table_insert(profile_cache, (gpointer) return_value->cache_key, (gpointer) cache_entry); return return_value; } /* get a handle to a (lcms built-in) srgb profile */ CMSProfile * cms_get_srgb_profile() { CMSProfile *return_value; ProfileCacheEntry *cache_entry = g_hash_table_lookup(profile_cache, "###SRGB###"); if (cache_entry != NULL) { cache_entry->ref_count ++; return_value = cache_entry->profile; return return_value; } /* if not in cache, generate new profile */ return_value = g_new(CMSProfile, 1); return_value->handle = cmsCreate_sRGBProfile(); cmsAddTag(return_value->handle, icSigCopyrightTag, "no copyright, use freely"); return_value->cache_key = "###SRGB###"; cache_entry = g_new(ProfileCacheEntry, 1); cache_entry->ref_count = 1; cache_entry->profile = return_value; g_hash_table_insert(profile_cache, (gpointer) return_value->cache_key, (gpointer) cache_entry); return_value->data = NULL; return_value->size = 0; _cmsSaveProfileToMem (return_value->handle, NULL, &(return_value->size)); return_value->data = (char*) calloc (sizeof (char), return_value->size); _cmsSaveProfileToMem (return_value->handle, return_value->data, &(return_value->size)); /* reopen due to limitations in _cmsSaveProfile/_cmsSaveProfileToMem */ cmsCloseProfile (return_value->handle); return_value->handle = cmsOpenProfileFromMem (return_value->data, return_value->size); if (!return_value->data || !return_value->size || !return_value->handle || !return_value->cache_key) { return_value->size = 0; g_warning ("%s:%d %s() profile corrupt\n",__FILE__,__LINE__,__func__); return NULL; } return return_value; } /* * makes a 'copy' of the profile handle * (essentially only admits a new user of the handle * by increasing the reference counter) * returns NULL if profile not found in cache (i.e. not open) */ CMSProfile * cms_duplicate_profile(CMSProfile *profile) { ProfileCacheEntry *entry = g_hash_table_lookup(profile_cache, (gpointer)profile->cache_key); if (entry == NULL) { g_warning("cms_duplicate_profile: profile not found in cache"); return NULL; } entry->ref_count++; return profile; } /* * return a profile to the cache */ gboolean cms_return_profile(CMSProfile *profile) { ProfileCacheEntry *entry = 0; /* search the cache for the profile * decreate ref_counter + possibly close profile */ if (profile == NULL) { g_warning("cms_return_profile: profile is NULL"); return FALSE; } entry = g_hash_table_lookup(profile_cache, (gpointer)profile->cache_key); if (entry == NULL) { g_warning("cms_return_profile: profile not found in cache"); return FALSE; } entry->ref_count--; if (entry->ref_count <= 0) { g_hash_table_remove(profile_cache, (gpointer)profile->cache_key); g_free(profile->cache_key); cmsCloseProfile(profile->handle); if (profile->size) g_free(profile->data); g_free(profile); } return TRUE; } /* * get a transform based * on input and output format * parameters as in lcms's createMultiTransform * returns NULL on error */ CMSTransform * cms_get_transform(GSList *profiles, DWORD lcms_input_format, DWORD lcms_output_format, int lcms_intent, DWORD lcms_flags) { /* turn profiles into an array as needed by lcms + check all profiles are registered with the profile cache + create hash key to check transform cache */ /* profiles list order: 1. image profile 2. look profiles (abstact profiles) 3. proofing profile , only if proofing is set (cmsFLAGS_SOFTPROOFING) 4. display profile */ int num_profiles = g_slist_length(profiles); #ifdef USE_ALLOCA cmsHPROFILE *profile_array; #else cmsHPROFILE profile_array[MAX_NUM_PROFILES]; #endif GString *hash_key = g_string_new(NULL); int i; CMSProfile *current_profile; GSList *iterator = profiles; cmsHTRANSFORM transform; TransformCacheEntry *cache_entry = 0; #ifdef USE_ALLOCA profile_array = (cmsHPROFILE)alloca(num_profiles*sizeof(cmsHPROFILE)); #endif if (num_profiles == 0) { g_warning("cms_get_transform: profile list is empty, cannot create transfrom"); return NULL; } /* TODO debugging */ #ifdef DEBUG printf ("%s:%d %s() Format %d|%d intent %d flags %d color %d|%d\n", __FILE__,__LINE__,__func__, lcms_input_format, lcms_output_format, lcms_intent, lcms_flags, T_COLORSPACE(lcms_input_format), T_COLORSPACE(lcms_output_format)); #endif if (!(int)lcms_output_format || !(int)lcms_output_format) { g_warning("%s:%d %s(): lcms_format wrong %d|%d", __FILE__,__LINE__,__func__,lcms_input_format, lcms_output_format); return NULL; } /* collect profiles in a list */ for (i=0; ((idata; if (g_hash_table_lookup(profile_cache, (gpointer)current_profile->cache_key) == NULL) { g_warning("cms_get_transform: at least one of the profiles is not registered. Need to register profiles before creating transform"); return NULL; } profile_array[i] = current_profile->handle; g_string_sprintfa(hash_key,"%p", current_profile->handle); iterator = g_slist_next(iterator); } g_string_sprintfa(hash_key,"%d%d%d%d%d", lcms_input_format,lcms_output_format, lcms_intent, lcms_flags, num_profiles); /* now check the cache */ cache_entry = g_hash_table_lookup(transform_cache, (gpointer) hash_key->str); /* if it was in the disc cache */ if (cache_entry != NULL && !(lcms_flags & cmsFLAGS_SOFTPROOFING)) { cache_entry->ref_count++; /* if it is not in memory, load it */ if (cache_entry->transform->handle == NULL) { cmsHPROFILE device_link = cmsOpenProfileFromFile(cache_entry->device_link_file, "r"); cache_entry->transform->handle = cmsCreateTransform(device_link, lcms_input_format, NULL, lcms_output_format, lcms_intent, lcms_flags); cmsCloseProfile(device_link); } /* else, if we are the first to use it again remove it from the list of unused transforms in mem */ else if (cache_entry->ref_count == 1) { unused_transforms_kept_in_mem = g_list_remove(unused_transforms_kept_in_mem, cache_entry); } g_string_free(hash_key, TRUE); return cache_entry->transform; } /* if no cache hit, create transform , do a proofing exeption */ if( lcms_flags & cmsFLAGS_SOFTPROOFING ) { /* For more than 3 profiles we have to create a new profile ... */ if(num_profiles > 3) { cmsHPROFILE look = 0; transform = cmsCreateMultiprofileTransform (profile_array, num_profiles - 2, lcms_input_format, NOCOLORSPACECHECK(lcms_output_format), lcms_intent, lcms_flags); if (!transform) { g_warning ("%s:%d ICC profile not valid.",__FILE__,__LINE__); return NULL; } look = cmsTransform2DeviceLink( transform, cmsFLAGS_GUESSDEVICECLASS ); cmsDeleteTransform (transform); /* ... and include the new profile in the proofing transform */ transform = cmsCreateProofingTransform(look, lcms_input_format, profile_array[num_profiles-1], lcms_output_format, profile_array[num_profiles-2], lcms_intent, INTENT_RELATIVE_COLORIMETRIC, lcms_flags); if (!transform) { g_warning ("%s:%d ICC profile not valid.",__FILE__,__LINE__); return NULL; } } else { /* just a proofing transform */ transform = cmsCreateProofingTransform(profile_array[0], lcms_input_format, profile_array[2], lcms_output_format, profile_array[1], lcms_intent, INTENT_RELATIVE_COLORIMETRIC, lcms_flags); if (!transform) { g_warning ("%s:%d ICC profile not valid.",__FILE__,__LINE__); return NULL; } } } else { /* no proofing */ transform = cmsCreateMultiprofileTransform (profile_array, num_profiles, lcms_input_format, lcms_output_format, lcms_intent, lcms_flags); if (!transform) { g_warning ("%s:%d ICC profile not valid.",__FILE__,__LINE__); return NULL; } } { /* save it to disk */ cmsHPROFILE devicelink = cmsTransform2DeviceLink(transform,0); char *file_name = file_temp_name("icc"); if(devicelink) { # ifdef DEBUG printf("%s:%d %s() %x %x \n",__FILE__,__LINE__,__func__,(int)devicelink,(int)file_name); # endif _cmsSaveProfile( devicelink, file_name ); cmsCloseProfile( devicelink ); } /* TODO debugging */ #ifdef DEBUG printf ("%s:%d %s() %s written\n",__FILE__,__LINE__,__func__, file_name); #endif ; /* and store a reference in the cache */ cache_entry = g_new(TransformCacheEntry, 1); cache_entry->ref_count = 1; cache_entry->transform = g_new(CMSTransform, 1); cache_entry->transform->cache_key = hash_key->str; cache_entry->transform->handle = transform; /* CMYK hack: do we see 4 color data? */ if (_cmsChannelsOf (cmsGetColorSpace(profile_array[0])) >= 4 && _cmsChannelsOf (cmsGetColorSpace(profile_array[num_profiles-1])) <= 3) { cache_entry->transform->erase_alpha_from_4th_color = TRUE; } else cache_entry->transform->erase_alpha_from_4th_color = FALSE; cache_entry->device_link_file = file_name; /* TODO insert me in transform_cache and ignore + remove */ if (!(num_profiles == 3 && (lcms_flags & cmsFLAGS_SOFTPROOFING) && (lcms_flags & cmsFLAGS_GAMUTCHECK) )) g_hash_table_insert(transform_cache, (gpointer) hash_key->str, (gpointer) cache_entry); } g_string_free(hash_key, FALSE); return cache_entry->transform; } /* * for convenience: get a transform usable for a canvas * (format is determined by this function) * returns NULL on error */ CMSTransform * cms_get_canvas_transform(GSList *profiles, Canvas *canvas, int lcms_intent, DWORD lcms_flags) { Tag t=canvas_tag(canvas); DWORD lcms_format_in, lcms_format_out; /* take the Input profile */ lcms_format_in = cms_get_lcms_format(t, (CMSProfile *)profiles->data); lcms_format_out = cms_get_lcms_format(t, (CMSProfile *) (g_slist_last(profiles)->data)); if (!lcms_format_in || !lcms_format_out) { g_warning("%s:%d %s(): wrong lcms_format %d|%d", __FILE__,__LINE__,__func__, lcms_format_in,lcms_format_out); return NULL; } #ifdef DEBUG printf ("%s:%d %s() Format %d|%d intent %d flags %d color %d\n", __FILE__,__LINE__,__func__, lcms_format_in, lcms_format_out, lcms_intent, lcms_flags, T_COLORSPACE(lcms_format_in)); #endif return cms_get_transform(profiles, lcms_format_in, lcms_format_out, lcms_intent, lcms_flags); } gboolean cms_return_transform(CMSTransform *transform) { TransformCacheEntry *cache_entry = g_hash_table_lookup(transform_cache, transform->cache_key); if (cache_entry == NULL) { g_warning("cms_return_transform: transform not found in cache"); return FALSE; } cache_entry->ref_count--; /* this transform is not used anymore, but we keep in memory while there is space for efficiency */ if (cache_entry->ref_count <= 0) { unused_transforms_kept_in_mem = g_list_append(unused_transforms_kept_in_mem, cache_entry); /* if the cache is full, kick out the oldest one */ if (g_list_length(unused_transforms_kept_in_mem) > MAX_UNUSED_TRANSFORMS_IN_MEM) { TransformCacheEntry *old_cache_entry = (TransformCacheEntry *)unused_transforms_kept_in_mem->data; cmsDeleteTransform(old_cache_entry->transform->handle); old_cache_entry->transform->handle = NULL; unused_transforms_kept_in_mem = g_list_remove(unused_transforms_kept_in_mem, old_cache_entry); } } return TRUE; } /* * get the display profile, if set */ CMSProfile * cms_get_display_profile() { return display_profile; } /* * set the display profile, return value indicates success */ gboolean cms_set_display_profile(CMSProfile *profile) { /* profile has to be registered */ if (g_hash_table_lookup(profile_cache, (gpointer)profile->cache_key) == NULL) { g_warning("cms_set_display_profile: profile is not registered. Cannot be set as display profile."); return FALSE; } display_profile = profile; gdisplays_update_full(); gdisplays_flush(); return TRUE; } /* transform a pixel buffer, src and dest have to be same precision */ void cms_transform_buffer (CMSTransform *transform, void *src_data, void *dest_data, int num_pixels, Precision precision) { TransformFunc transform_func; if (!transform || !transform->handle) { g_warning ("%s:%d %s(): transform empty",__FILE__,__LINE__,__func__); return; } if (!src_data || !dest_data) { g_warning ("%s:%d %s(): buffer not allocated",__FILE__,__LINE__,__func__); return; } switch (precision) { case PRECISION_U8: case PRECISION_U16: transform_func = cms_transform_uint; break; case PRECISION_FLOAT: transform_func = cms_transform_float; break; default: g_warning ("cms_transform: precision not supported"); return; } (*transform_func) (transform, src_data, dest_data, num_pixels); #ifdef DEBUG_ printf("%s:%d diff = %d\n",__FILE__,__LINE__,num_pixels); #endif /* part of the CMYK hack */ if ( transform->erase_alpha_from_4th_color ) { int i; guint8 *u8; guint16 *u16; float *f32; switch (precision) { case PRECISION_U8: u8 = dest_data; for (i = 3; i < num_pixels * 4 ; i += 4) u8[i] = 255; break; case PRECISION_U16: u16 = dest_data; for (i = 3; i < num_pixels * 4 ; i += 4) u16[i] = 65535; break; case PRECISION_FLOAT: f32 = dest_data; for (i = 3; i < num_pixels * 4 ; i += 4) f32[i] = 1.0; break; default: g_warning ("cms_transform: precision not supported"); return; } } } /* transforms a pixel area, src and dest have to be same precision */ void cms_transform_area(CMSTransform *transform, PixelArea *src_area, PixelArea *dest_area) { TransformFunc transform_func; Tag src_tag = pixelarea_tag(src_area); Tag dest_tag = pixelarea_tag(dest_area); PixelRow src_row_buffer, dest_row_buffer; guint h; void *src_data, *dest_data; guint num_pixels; void *pag; if (tag_precision(src_tag) != tag_precision(dest_tag)) { g_warning("cms_transform: src and dest area have to have same precision"); } if (!transform || !transform->handle) { g_warning ("%s:%d %s(): transform empty",__FILE__,__LINE__,__func__); return; } switch (tag_precision(src_tag)) { case PRECISION_U8: case PRECISION_U16: transform_func = cms_transform_uint; break; case PRECISION_FLOAT: transform_func = cms_transform_float; break; default: g_warning ("cms_transform: precision not supported"); return; } for (pag = pixelarea_register (1, src_area, dest_area); pag != NULL; pag = pixelarea_process (pag)) { h = pixelarea_height(src_area); num_pixels = pixelarea_width(dest_area); while (h--) { pixelarea_getdata(src_area, &src_row_buffer, h); pixelarea_getdata(dest_area, &dest_row_buffer, h); src_data = pixelrow_data(&src_row_buffer); dest_data = pixelrow_data(&dest_row_buffer); if (!dest_data || !src_data) g_warning ("%s:%d %s() buffer failed at hight %d\n", __FILE__,__LINE__,__func__, h); (*transform_func) (transform, src_data, dest_data, num_pixels); /* part of the CMYK hack */ if (tag_format(dest_tag) == FORMAT_RGB && tag_alpha(dest_tag) == ALPHA_YES && transform->erase_alpha_from_4th_color ) { int i; guint8 *u8; guint16 *u16; float *f32; switch (tag_precision(src_tag)) { case PRECISION_U8: u8 = dest_data; for (i = 3; i < num_pixels * 4 ; i += 4) u8[i] = 255; break; case PRECISION_U16: u16 = dest_data; for (i = 3; i < num_pixels * 4 ; i += 4) u16[i] = 65535; break; case PRECISION_FLOAT: f32 = dest_data; for (i = 3; i < num_pixels * 4 ; i += 4) f32[i] = 1.0; break; default: g_warning ("cms_transform: precision not supported"); return; } } } } } void cms_transform_uint(CMSTransform *transform, void *src_data, void *dest_data, int num_pixels) { /* easy, no previous conversion, lcms does it all */ if (!transform || !transform->handle) g_warning ("%s:%d %s() transform not allocated\n", __FILE__,__LINE__,__func__); if (!src_data || !dest_data) g_warning ("%s:%d %s() array not allocated\n", __FILE__,__LINE__,__func__); /* easy, no previous conversion, lcms does it all */ cmsDoTransform(transform->handle,src_data,dest_data,num_pixels); } void cms_transform_float(CMSTransform *transform, void *src_data, void *dest_data, int num_pixels) { /* need to convert data to double for lcms's convenience */ int i; float *src_fbuffer = (float *)src_data; float *dest_fbuffer = (float *)dest_data; double *dbuffer = 0; if (!transform || !transform->handle) g_warning ("%s:%d %s() transform not allocated\n", __FILE__,__LINE__,__func__); if (!src_data || !dest_data) g_warning ("%s:%d %s() array not allocated\n", __FILE__,__LINE__,__func__); dbuffer = malloc(sizeof(double) * num_pixels * 4); for (i=0; i < num_pixels * 4; i++) { dbuffer[i]=(double)src_fbuffer[i]; } cmsDoTransform(transform->handle,dbuffer,dbuffer,num_pixels); /* and convert back */ for (i=0; i < num_pixels * 4; i++) { dest_fbuffer[i]=(float)dbuffer[i]; } g_free(dbuffer); } /* workaround see declaration above - beku */ #if (LCMS_VERSION <= 112) int _cmsLCMScolorSpace(icColorSpaceSignature ProfileSpace) { switch (ProfileSpace) { case icSigGrayData: return PT_GRAY; case icSigRgbData: return PT_RGB; case icSigCmyData: return PT_CMY; case icSigCmykData: return PT_CMYK; case icSigYCbCrData:return PT_YCbCr; case icSigLuvData: return PT_YUV; case icSigXYZData: return PT_XYZ; case icSigLabData: return PT_Lab; case icSigLuvKData: return PT_YUVK; case icSigHsvData: return PT_HSV; case icSigHlsData: return PT_HLS; case icSigYxyData: return PT_Yxy; case icSig6colorData: case icSigHexachromeData: return PT_HiFi; default: return icMaxEnumData; } } #endif /*** * INTERFACE - DIALOGS. SHOULD GO IN A DIFFERENT FILE ***/ /* * COMMON INTERFACE ELEMENTS * used in various dialogs */ /* private prototypes */ static GtkWidget *cms_profile_menu_new(GtkTable *table, guint left, guint top, gboolean can_select_none); static GtkWidget *cms_intent_menu_new(GtkTable *table, guint left, guint top); static void cms_ok_cancel_buttons_new(GtkBox *action_area, GtkSignalFunc ok_callback, GtkSignalFunc cancel_callback, gpointer data); /* private functions */ static GtkWidget *cms_profile_menu_new(GtkTable *table, guint left, guint top, gboolean can_select_none) { GtkWidget *menu; GtkWidget *menuitem; GtkWidget *optionmenu; GSList *profile_file_names = cms_read_standard_profile_dirs(CMS_ANY_PROFILECLASS); GSList *iterator = profile_file_names; gchar *current_filename; CMSProfile *current_profile; CMSProfileInfo *current_profile_info; menu = gtk_menu_new (); if (can_select_none) { menuitem = gtk_menu_item_new_with_label("[none]"); gtk_menu_append (GTK_MENU (menu), menuitem); gtk_object_set_data(GTK_OBJECT(menuitem), "value", NULL); } while (iterator != NULL) { current_filename = iterator->data; current_profile = cms_get_profile_from_file(current_filename); current_profile_info = cms_get_profile_info(current_profile); menuitem = gtk_menu_item_new_with_label (current_profile_info->description); gtk_menu_append (GTK_MENU (menu), menuitem); gtk_object_set_data_full(GTK_OBJECT(menuitem), "value", (gpointer)current_filename, g_free); cms_return_profile(current_profile); iterator = g_slist_next(iterator); } if (profile_file_names != NULL) { g_slist_free(profile_file_names); } optionmenu = gtk_option_menu_new (); gtk_widget_set_usize(optionmenu, 200, 25); gtk_option_menu_set_menu (GTK_OPTION_MENU (optionmenu), menu); gtk_table_attach (GTK_TABLE (table), optionmenu, left, left+1, top, top+1, GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, 0, 0); return menu; } static GtkWidget *cms_intent_menu_new(GtkTable *table, guint left, guint top) { GtkWidget *menu = NULL; GtkWidget *menuitem = NULL; GtkWidget *optionmenu = NULL; guint8 *value = NULL; menu = gtk_menu_new (); menuitem = gtk_menu_item_new_with_label ("Perceptual"); gtk_menu_append (GTK_MENU (menu), menuitem); value = g_new(guint8, 1); *value = INTENT_PERCEPTUAL; gtk_object_set_data_full(GTK_OBJECT(menuitem), "value", (gpointer)value, g_free); menuitem = gtk_menu_item_new_with_label ("Relative Colorimetric"); gtk_menu_append (GTK_MENU (menu), menuitem); value = g_new(guint8, 1); *value = INTENT_RELATIVE_COLORIMETRIC; gtk_object_set_data_full(GTK_OBJECT(menuitem), "value", (gpointer)value, g_free); menuitem = gtk_menu_item_new_with_label ("Saturation"); gtk_menu_append (GTK_MENU (menu), menuitem); value = g_new(guint8, 1); *value = INTENT_SATURATION; gtk_object_set_data_full(GTK_OBJECT(menuitem), "value", (gpointer)value, g_free); menuitem = gtk_menu_item_new_with_label ("Absolute Colorimetric"); gtk_menu_append (GTK_MENU (menu), menuitem); value = g_new(guint8, 1); *value = INTENT_ABSOLUTE_COLORIMETRIC; gtk_object_set_data_full(GTK_OBJECT(menuitem), "value", (gpointer)value, g_free); gtk_menu_set_active (GTK_MENU (menu), cms_default_intent); optionmenu = gtk_option_menu_new (); gtk_widget_set_usize(optionmenu, 150, 25); gtk_option_menu_set_menu (GTK_OPTION_MENU (optionmenu), menu); gtk_table_attach (GTK_TABLE (table), optionmenu, left, left+1, top, top+1, GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, 0, 0); return menu; } static void cms_ok_cancel_buttons_new(GtkBox *action_area, GtkSignalFunc ok_callback, GtkSignalFunc cancel_callback, gpointer data) { GtkWidget *buttonbox = NULL; GtkWidget *button = NULL; buttonbox = gtk_hbutton_box_new(); gtk_button_box_set_spacing(GTK_BUTTON_BOX(buttonbox), 4); gtk_box_set_homogeneous(action_area, FALSE); gtk_box_pack_end (action_area, buttonbox, FALSE, FALSE, 0); button = gtk_button_new_with_label("OK"); gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(ok_callback), data); gtk_box_pack_start (GTK_BOX (buttonbox), button, FALSE, FALSE, 0); button = gtk_button_new_with_label("Cancel"); gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(cancel_callback), data); gtk_box_pack_start (GTK_BOX (buttonbox), button, FALSE, FALSE, 0); } /* * CONVERT DIALOG * menu: Image>Convert using ICC Profile... */ /* private types */ typedef struct _CMSConvertDialogData { GtkWidget *shell; GtkWidget *intent_menu; GtkWidget *profile_menu; GImage *image; } CMSConvertDialogData; /* private prototypes */ static void cms_convert_dialog_ok_callback(GtkWidget*, CMSConvertDialogData*); static void cms_convert_dialog_cancel_callback(GtkWidget*, CMSConvertDialogData*); /* public functions */ void cms_convert_dialog(GImage *image) { GtkWidget *table = NULL; GtkWidget *label = NULL; GtkWidget *vbox = NULL; CMSConvertDialogData *data=g_new(CMSConvertDialogData, 1); data->image = image; data->shell = gtk_dialog_new(); gtk_window_set_wmclass (GTK_WINDOW (data->shell), "cms_convert", PROGRAM_NAME); gtk_window_set_title (GTK_WINDOW (data->shell), "Select Profile and Intent"); gtk_window_set_position(GTK_WINDOW(data->shell), GTK_WIN_POS_CENTER); vbox = gtk_vbox_new(FALSE, 0); gtk_container_border_width(GTK_CONTAINER (vbox), 10); gtk_box_pack_start (GTK_BOX (GTK_DIALOG(data->shell)->vbox), vbox, TRUE, TRUE, 0); label = gtk_label_new("Please select the profile to convert to and the rendering intent to use:"); gtk_widget_set_usize(label, 350, 25); gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0); table = gtk_table_new(2, 2, FALSE); gtk_box_pack_start (GTK_BOX (vbox), table, TRUE, TRUE, 10); label = gtk_label_new("Profile:"); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_table_attach (GTK_TABLE (table), label, 0, 1, 0, 1, GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, 0, 0); data->profile_menu = cms_profile_menu_new(GTK_TABLE(table), 1, 0, FALSE); label = gtk_label_new("Rendering Intent:"); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_table_attach (GTK_TABLE (table), label, 0, 1, 1, 2, GTK_EXPAND|GTK_FILL, GTK_EXPAND|GTK_FILL, 0, 0); data->intent_menu = cms_intent_menu_new(GTK_TABLE(table), 1, 1); cms_ok_cancel_buttons_new(GTK_BOX(GTK_DIALOG(data->shell)->action_area), GTK_SIGNAL_FUNC(cms_convert_dialog_ok_callback), GTK_SIGNAL_FUNC(cms_convert_dialog_cancel_callback), data); gtk_widget_show_all(data->shell); } /* private functions */ static void cms_convert_dialog_ok_callback(GtkWidget *widget, CMSConvertDialogData *data) { GtkWidget *selected = NULL; char *profile = NULL; guint8 intent = cms_default_intent; /* get the dialog values */ selected = gtk_menu_get_active(GTK_MENU(data->profile_menu)); profile = (char *)gtk_object_get_data(GTK_OBJECT(selected), "value"); selected = gtk_menu_get_active(GTK_MENU(data->intent_menu)); intent = *((guint8 *)gtk_object_get_data(GTK_OBJECT(selected), "value")); /* convert */ convert_image_colorspace(data->image, cms_get_profile_from_file(profile), intent); /* destroy dialog */ cms_convert_dialog_cancel_callback(widget, data); } static void cms_convert_dialog_cancel_callback(GtkWidget *widget, CMSConvertDialogData *data) { gtk_widget_destroy(data->shell); g_free(data); } /* * ASSIGN DIALOG * menu: Image>Assign ICC Profile */ /* private types */ typedef struct _CMSAssignDialogData { GtkWidget *shell; GtkWidget *profile_menu; GImage *image; CMSProfileType type; } CMSAssignDialogData; /* private prototypes */ static void cms_assign_dialog_ok_callback (GtkWidget*, CMSAssignDialogData*); static void cms_assign_dialog_cancel_callback(GtkWidget*, CMSAssignDialogData*); /* public functions */ void cms_assign_dialog(GImage *image, CMSProfileType type) { GtkWidget *table = NULL; GtkWidget *label = NULL; GtkWidget *vbox = NULL; GtkWidget *alignment = NULL; CMSProfile *current_profile = NULL; CMSAssignDialogData *data=g_new(CMSAssignDialogData,1); char title[][32] = {"Select Image Profile", "Select Proof Profile"}; GString *profile_string = g_string_new(NULL); data->image = image; data->shell = gtk_dialog_new(); data->type = type; gtk_window_set_wmclass (GTK_WINDOW (data->shell), "cms_assign", PROGRAM_NAME); gtk_window_set_title (GTK_WINDOW (data->shell), title[type]); gtk_window_set_position(GTK_WINDOW(data->shell), GTK_WIN_POS_CENTER); vbox = gtk_vbox_new(FALSE, 0); gtk_container_border_width(GTK_CONTAINER (vbox), 10); gtk_box_pack_start (GTK_BOX (GTK_DIALOG(data->shell)->vbox), vbox, TRUE, TRUE, 0); table = gtk_table_new(3, 1, FALSE); gtk_box_pack_start (GTK_BOX (vbox), table, TRUE, TRUE, 0); if (type == ICC_IMAGE_PROFILE) current_profile = gimage_get_cms_profile(image); else if (type == ICC_PROOF_PROFILE) current_profile = gimage_get_cms_proof_profile(image); if (current_profile == NULL) { g_string_sprintf (profile_string, "[The currently assigned profile is: [none]]"); } else { g_string_sprintf (profile_string, "[The currently assigned profile is: %s]", cmsTakeProductDesc(current_profile->handle)); } label = gtk_label_new(profile_string->str); g_string_free(profile_string, FALSE); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL|GTK_EXPAND, GTK_FILL|GTK_EXPAND, 0, 0); alignment = gtk_alignment_new(0.0, 1.0, 0.0, 0.0); if (type == ICC_IMAGE_PROFILE) label = gtk_label_new("Please select the new image profile to assign:"); else if (type == ICC_PROOF_PROFILE) label = gtk_label_new("Please select the new proof profile to assign:"); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 1.0); gtk_container_add(GTK_CONTAINER(alignment), label); gtk_table_attach(GTK_TABLE(table), alignment, 0, 1, 1, 2, GTK_FILL|GTK_EXPAND, GTK_FILL|GTK_EXPAND, 0, 10); data->profile_menu = cms_profile_menu_new(GTK_TABLE(table), 0, 2, TRUE); cms_ok_cancel_buttons_new(GTK_BOX(GTK_DIALOG(data->shell)->action_area), GTK_SIGNAL_FUNC(cms_assign_dialog_ok_callback), GTK_SIGNAL_FUNC(cms_assign_dialog_cancel_callback), data); gtk_widget_show_all(data->shell); } /* private functions */ static void cms_assign_dialog_ok_callback(GtkWidget *widget, CMSAssignDialogData *data) { GtkWidget *selected = NULL; char *profile_name = NULL; /* get the dialog value */ selected = gtk_menu_get_active(GTK_MENU(data->profile_menu)); profile_name = (char *)gtk_object_get_data(GTK_OBJECT(selected), "value"); /* assign it */ if (profile_name != NULL) { if (data->type == ICC_IMAGE_PROFILE) gimage_set_cms_profile(data->image, cms_get_profile_from_file(profile_name)); else if (data->type == ICC_PROOF_PROFILE) gimage_set_cms_proof_profile(data->image, cms_get_profile_from_file(profile_name)); } else { if (data->type == ICC_IMAGE_PROFILE) { g_message("You set the image profile to [none]. Colormanagement will be\nswitched off for all displays of this image."); gimage_set_cms_profile(data->image, NULL); } else if (data->type == ICC_PROOF_PROFILE) { g_message("You set the proof profile to [none]. Proofing will be\nnot available off for all displays of this image."); gimage_set_cms_proof_profile(data->image, NULL); } } gdisplays_update_gimage (data->image->ID); gdisplays_flush(); /* destroy dialog */ cms_assign_dialog_cancel_callback(widget, data); } static void cms_assign_dialog_cancel_callback(GtkWidget *widget, CMSAssignDialogData *data) { gtk_widget_destroy(data->shell); g_free(data); } /* * OPEN ASSIGN DIALOG * modal dialog shown when opening image * if File>Preferences>Color Management>When Opening Image=prompt */ /* private types */ typedef struct _CMSOpenAssignDialogData { GtkWidget *shell; GtkWidget *default_radio; GtkWidget *profile_radio; GtkWidget *profile_menu; GtkWidget *always_default_check; GMainLoop *event_loop; GImage *image; } CMSOpenAssignDialogData; /* private prototypes */ static void cms_open_assign_dialog_ok_callback (GtkWidget*, CMSOpenAssignDialogData*); static void cms_open_assign_dialog_cancel_callback (GtkWidget*, CMSOpenAssignDialogData*); /* public functions */ void cms_open_assign_dialog(GImage *image) { GtkWidget *table = NULL; GtkWidget *label = NULL; GSList *radiogroup = NULL; GtkWidget *vbox = NULL; GtkWidget *alignment = NULL; GString *profile_string = g_string_new(NULL); CMSOpenAssignDialogData *data=g_new(CMSOpenAssignDialogData,1); data->image = image; data->shell = gtk_dialog_new(); gtk_window_set_wmclass (GTK_WINDOW (data->shell), "cms_open_assign", PROGRAM_NAME); gtk_window_set_title (GTK_WINDOW (data->shell), "Select Profile"); gtk_window_set_position(GTK_WINDOW(data->shell), GTK_WIN_POS_CENTER); vbox = gtk_vbox_new(FALSE, 0); gtk_container_border_width(GTK_CONTAINER (vbox), 10); gtk_box_pack_start (GTK_BOX (GTK_DIALOG(data->shell)->vbox), vbox, TRUE, TRUE, 0); /* message */ label = gtk_label_new("Please select the image profile to assign to the image:"); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_box_pack_start (GTK_BOX (vbox), label, TRUE, TRUE, 0); /* radio group */ table = gtk_table_new(2, 2, FALSE); gtk_box_pack_start (GTK_BOX (vbox), table, TRUE, TRUE, 10); data->profile_radio = gtk_radio_button_new_with_label(NULL, "Profile: "); radiogroup = gtk_radio_button_group(GTK_RADIO_BUTTON(data->profile_radio)); gtk_table_attach(GTK_TABLE(table), data->profile_radio, 0, 1, 0, 1, GTK_FILL|GTK_EXPAND, GTK_FILL|GTK_EXPAND, 0, 0); data->profile_menu = cms_profile_menu_new(GTK_TABLE(table), 1, 0, TRUE); if (cms_default_image_profile_name == NULL) { g_string_sprintf (profile_string, "The assumed image profile: [none]"); } else { g_string_sprintf (profile_string, "The assumed image profile: %s", cmsTakeProductDesc(cms_get_profile_from_file(cms_default_image_profile_name)->handle)); } data->default_radio = gtk_radio_button_new_with_label(radiogroup, profile_string->str); gtk_table_attach(GTK_TABLE(table), data->default_radio, 0, 2, 1, 2, GTK_FILL|GTK_EXPAND, GTK_FILL|GTK_EXPAND, 0, 0); g_string_free(profile_string, FALSE); /* default check box */ data->always_default_check = gtk_check_button_new_with_label("always use default profile"); alignment = gtk_alignment_new(0.0, 1.0, 0, 0); gtk_container_add(GTK_CONTAINER(alignment), data->always_default_check); gtk_box_pack_start (GTK_BOX (vbox), alignment, TRUE, TRUE, 5); cms_ok_cancel_buttons_new(GTK_BOX(GTK_DIALOG(data->shell)->action_area), GTK_SIGNAL_FUNC(cms_open_assign_dialog_ok_callback), GTK_SIGNAL_FUNC(cms_open_assign_dialog_cancel_callback), data); gtk_widget_show_all(data->shell); data->event_loop = g_main_new(FALSE); g_main_run(data->event_loop); g_main_destroy(data->event_loop); g_free(data); } /* private functions */ static void cms_open_assign_dialog_ok_callback(GtkWidget *widget, CMSOpenAssignDialogData *data) { GtkWidget *selected = NULL; char *profile_name = NULL; cmsHPROFILE profile_handle = NULL; /* get the profile */ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->default_radio))) { profile_name = cms_default_image_profile_name; } else { selected = gtk_menu_get_active(GTK_MENU(data->profile_menu)); profile_name = (char *)gtk_object_get_data(GTK_OBJECT(selected), "value"); } if (profile_name == NULL) { profile_handle = NULL; } else { profile_handle = cms_get_profile_from_file(profile_name); } /* assign it */ gimage_set_cms_profile(data->image, profile_handle); gdisplays_update_gimage (data->image->ID) ; /* refresh the profile name in the title bar */ gdisplays_update_title (data->image->ID); /* update cms_open_action (global preference variable) */ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->always_default_check))) { GList *update_settings = NULL; GList *remove_settings = NULL; cms_open_action = CMS_ASSIGN_DEFAULT; update_settings = g_list_append(update_settings, "cms-open-action"); save_gimprc(&update_settings, &remove_settings); g_list_free(update_settings); } /* destroy dialog */ cms_open_assign_dialog_cancel_callback(widget, data); } static void cms_open_assign_dialog_cancel_callback(GtkWidget *widget, CMSOpenAssignDialogData *data) { gtk_widget_destroy(data->shell); g_main_quit(data->event_loop); } /* * CONVERT ON OPEN PROMPT * modal dialog, shown when opening image * asks whether to convert to working colorspace if mismatch */ /* private types*/ typedef struct _CMSConvertOnOpenPromptData { GtkWidget *shell; GtkWidget *auto_convert_check; GMainLoop *event_loop; gboolean return_value; } CMSConvertOnOpenPromptData; /* private prototypes */ static void cms_convert_on_open_prompt_yes_callback (GtkWidget *widget, CMSConvertOnOpenPromptData *data); static void cms_convert_on_open_prompt_no_callback (GtkWidget *widget, CMSConvertOnOpenPromptData *data); /* public functions */ gboolean cms_convert_on_open_prompt(GImage *image) { GtkWidget *label; GtkWidget *button; GtkWidget *buttonbox; GtkWidget *table; GtkWidget *vbox; GtkWidget *alignment; gboolean return_value; CMSConvertOnOpenPromptData *data = g_new(CMSConvertOnOpenPromptData, 1); data->shell = gtk_dialog_new(); gtk_window_set_wmclass (GTK_WINDOW (data->shell), "cms_convert_profile_prompt", PROGRAM_NAME); gtk_window_set_title (GTK_WINDOW (data->shell), "Select Profile"); gtk_window_set_position(GTK_WINDOW(data->shell), GTK_WIN_POS_CENTER); vbox = gtk_vbox_new(FALSE, 0); gtk_container_border_width(GTK_CONTAINER (vbox), 10); gtk_box_pack_start (GTK_BOX (GTK_DIALOG(data->shell)->vbox), vbox, TRUE, TRUE, 0); /* message */ label = gtk_label_new ("The profile that was assigned to this image does not match your editing profile."); gtk_widget_set_usize (label, 350, 25); gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 0); /* current profiles */ table = gtk_table_new(2,2,FALSE); gtk_box_pack_start(GTK_BOX(vbox), table, TRUE, TRUE, 10); label = gtk_label_new("Editing profile:"); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL|GTK_EXPAND, GTK_FILL|GTK_EXPAND, 0, 0); label = gtk_label_new(strdup(cmsTakeProductDesc(cms_get_profile_from_file(cms_workspace_profile_name)->handle))); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_table_attach(GTK_TABLE(table), label, 1, 2, 0, 1, GTK_FILL|GTK_EXPAND, GTK_FILL|GTK_EXPAND, 0, 0); label = gtk_label_new("Assigned profile:"); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL|GTK_EXPAND, GTK_FILL|GTK_EXPAND, 0, 0); label = gtk_label_new(strdup(cmsTakeProductDesc(gimage_get_cms_profile(image)->handle))); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_table_attach(GTK_TABLE(table), label, 1, 2, 1, 2, GTK_FILL|GTK_EXPAND, GTK_FILL|GTK_EXPAND, 0, 0); /* alway convert checkbox */ data->auto_convert_check = gtk_check_button_new_with_label("Always convert automatically without asking"); alignment = gtk_alignment_new(0.0, 1.0, 0.0, 0.0); gtk_container_add(GTK_CONTAINER(alignment), data->auto_convert_check); gtk_box_pack_start(GTK_BOX(vbox), alignment, TRUE, TRUE, 5); /* buttons */ buttonbox = gtk_hbutton_box_new(); gtk_button_box_set_spacing(GTK_BUTTON_BOX(buttonbox), 4); gtk_box_set_homogeneous(GTK_BOX(GTK_DIALOG(data->shell)->action_area), FALSE); gtk_box_pack_end (GTK_BOX (GTK_DIALOG(data->shell)->action_area), buttonbox, FALSE, FALSE, 0); button = gtk_button_new_with_label("Yes"); gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(cms_convert_on_open_prompt_yes_callback), (gpointer)data); gtk_box_pack_start (GTK_BOX (buttonbox), button, FALSE, FALSE, 0); button = gtk_button_new_with_label("No"); gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(cms_convert_on_open_prompt_no_callback), (gpointer)data); gtk_box_pack_start (GTK_BOX (buttonbox), button, FALSE, FALSE, 0); gtk_widget_show_all(data->shell); data->event_loop = g_main_new(FALSE); g_main_run(data->event_loop); return_value = data->return_value; g_main_destroy(data->event_loop); g_free(data); return return_value; } /* private functions */ static void cms_convert_on_open_prompt_yes_callback (GtkWidget *widget, CMSConvertOnOpenPromptData *data) { data->return_value = TRUE; if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->auto_convert_check))) { /* cms_auto_convert is global preference variable */ GList *update_settings = NULL; GList *remove_settings = NULL; cms_mismatch_action = CMS_MISMATCH_AUTO_CONVERT; update_settings = g_list_append(update_settings, "cms-mismatch-action"); save_gimprc(&update_settings, &remove_settings); g_list_free(update_settings); } gtk_widget_destroy(data->shell); g_main_quit(data->event_loop); } static void cms_convert_on_open_prompt_no_callback (GtkWidget *widget, CMSConvertOnOpenPromptData *data) { data->return_value = FALSE; if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(data->auto_convert_check))) { /* cms_auto_convert is global preference variable */ cms_mismatch_action = CMS_MISMATCH_AUTO_CONVERT; } gtk_widget_destroy(data->shell); g_main_quit(data->event_loop); } /*cms_add_profile(*profile_list, profile){ } cms_remove_profile(*profile_list, profile) { } g_frees the transform and possibly deletes the tempfile cms free_transform(){ } */