1 /* LIBGIMP - The GIMP Library
2  * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
3  *
4  * gimpwidgets.c
5  * Copyright (C) 2000 Michael Natterer <mitch@gimp.org>
6  *
7  * This library is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 3 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library.  If not, see
19  * <https://www.gnu.org/licenses/>.
20  */
21 
22 #include "config.h"
23 
24 #include <lcms2.h>
25 
26 #include <gegl.h>
27 #include <gtk/gtk.h>
28 
29 #ifdef G_OS_WIN32
30 #ifdef _WIN32_WINNT
31 #undef _WIN32_WINNT
32 #endif
33 #define _WIN32_WINNT 0x0600
34 #include <windows.h>
35 #include <icm.h>
36 #endif
37 
38 #ifdef GDK_WINDOWING_QUARTZ
39 #include <Carbon/Carbon.h>
40 #include <ApplicationServices/ApplicationServices.h>
41 #include <CoreServices/CoreServices.h>
42 #endif
43 
44 #include "libgimpcolor/gimpcolor.h"
45 #include "libgimpconfig/gimpconfig.h"
46 
47 #include "gimpwidgetstypes.h"
48 
49 #include "gimp3migration.h"
50 #include "gimpsizeentry.h"
51 #include "gimpwidgetsutils.h"
52 
53 #include "libgimp/libgimp-intl.h"
54 
55 
56 /**
57  * SECTION: gimpwidgetsutils
58  * @title: GimpWidgetsUtils
59  * @short_description: A collection of helper functions.
60  *
61  * A collection of helper functions.
62  **/
63 
64 
65 static GtkWidget *
find_mnemonic_widget(GtkWidget * widget,gint level)66 find_mnemonic_widget (GtkWidget *widget,
67                       gint       level)
68 {
69   gboolean can_focus;
70 
71   g_object_get (widget, "can-focus", &can_focus, NULL);
72 
73   if (GTK_WIDGET_GET_CLASS (widget)->activate_signal ||
74       can_focus                                      ||
75       GTK_WIDGET_GET_CLASS (widget)->mnemonic_activate !=
76       GTK_WIDGET_CLASS (g_type_class_peek (GTK_TYPE_WIDGET))->mnemonic_activate)
77     {
78       return widget;
79     }
80 
81   if (GIMP_IS_SIZE_ENTRY (widget))
82     {
83       GimpSizeEntry *entry = GIMP_SIZE_ENTRY (widget);
84 
85       return gimp_size_entry_get_help_widget (entry,
86                                               entry->number_of_fields - 1);
87     }
88   else if (GTK_IS_CONTAINER (widget))
89     {
90       GtkWidget *mnemonic_widget = NULL;
91       GList     *children;
92       GList     *list;
93 
94       children = gtk_container_get_children (GTK_CONTAINER (widget));
95 
96       for (list = children; list; list = g_list_next (list))
97         {
98           mnemonic_widget = find_mnemonic_widget (list->data, level + 1);
99 
100           if (mnemonic_widget)
101             break;
102         }
103 
104       g_list_free (children);
105 
106       return mnemonic_widget;
107     }
108 
109   return NULL;
110 }
111 
112 /**
113  * gimp_table_attach_aligned:
114  * @table:      The #GtkTable the widgets will be attached to.
115  * @column:     The column to start with.
116  * @row:        The row to attach the widgets.
117  * @label_text: The text for the #GtkLabel which will be attached left of
118  *              the widget.
119  * @xalign:     The horizontal alignment of the #GtkLabel.
120  * @yalign:     The vertical alignment of the #GtkLabel.
121  * @widget:     The #GtkWidget to attach right of the label.
122  * @colspan:    The number of columns the widget will use.
123  * @left_align: %TRUE if the widget should be left-aligned.
124  *
125  * Note that the @label_text can be %NULL and that the widget will be
126  * attached starting at (@column + 1) in this case, too.
127  *
128  * Returns: The created #GtkLabel.
129  **/
130 GtkWidget *
gimp_table_attach_aligned(GtkTable * table,gint column,gint row,const gchar * label_text,gfloat xalign,gfloat yalign,GtkWidget * widget,gint colspan,gboolean left_align)131 gimp_table_attach_aligned (GtkTable    *table,
132                            gint         column,
133                            gint         row,
134                            const gchar *label_text,
135                            gfloat       xalign,
136                            gfloat       yalign,
137                            GtkWidget   *widget,
138                            gint         colspan,
139                            gboolean     left_align)
140 {
141   GtkWidget *label = NULL;
142 
143   if (label_text)
144     {
145       GtkWidget *mnemonic_widget;
146 
147       label = gtk_label_new_with_mnemonic (label_text);
148       gtk_label_set_xalign (GTK_LABEL (label), xalign);
149       gtk_label_set_yalign (GTK_LABEL (label), yalign);
150       gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
151       gtk_table_attach (table, label,
152                         column, column + 1,
153                         row, row + 1,
154                         GTK_FILL, GTK_FILL, 0, 0);
155       gtk_widget_show (label);
156 
157       mnemonic_widget = find_mnemonic_widget (widget, 0);
158 
159       if (mnemonic_widget)
160         gtk_label_set_mnemonic_widget (GTK_LABEL (label), mnemonic_widget);
161     }
162 
163   if (left_align)
164     {
165       GtkWidget *hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
166 
167       gtk_box_pack_start (GTK_BOX (hbox), widget, FALSE, FALSE, 0);
168       gtk_widget_show (widget);
169 
170       widget = hbox;
171     }
172 
173   gtk_table_attach (table, widget,
174                     column + 1, column + 1 + colspan,
175                     row, row + 1,
176                     GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
177 
178   gtk_widget_show (widget);
179 
180   return label;
181 }
182 
183 /**
184  * gimp_label_set_attributes:
185  * @label: a #GtkLabel
186  * @...:   a list of PangoAttrType and value pairs terminated by -1.
187  *
188  * Sets Pango attributes on a #GtkLabel in a more convenient way than
189  * gtk_label_set_attributes().
190  *
191  * This function is useful if you want to change the font attributes
192  * of a #GtkLabel. This is an alternative to using PangoMarkup which
193  * is slow to parse and awkward to handle in an i18n-friendly way.
194  *
195  * The attributes are set on the complete label, from start to end. If
196  * you need to set attributes on part of the label, you will have to
197  * use the PangoAttributes API directly.
198  *
199  * Since: 2.2
200  **/
201 void
gimp_label_set_attributes(GtkLabel * label,...)202 gimp_label_set_attributes (GtkLabel *label,
203                            ...)
204 {
205   PangoAttribute *attr  = NULL;
206   PangoAttrList  *attrs;
207   va_list         args;
208 
209   g_return_if_fail (GTK_IS_LABEL (label));
210 
211   attrs = pango_attr_list_new ();
212 
213   va_start (args, label);
214 
215   do
216     {
217       PangoAttrType attr_type = va_arg (args, PangoAttrType);
218 
219       if (attr_type == -1)
220         attr_type = PANGO_ATTR_INVALID;
221 
222       switch (attr_type)
223         {
224         case PANGO_ATTR_LANGUAGE:
225           attr = pango_attr_language_new (va_arg (args, PangoLanguage *));
226           break;
227 
228         case PANGO_ATTR_FAMILY:
229           attr = pango_attr_family_new (va_arg (args, const gchar *));
230           break;
231 
232         case PANGO_ATTR_STYLE:
233           attr = pango_attr_style_new (va_arg (args, PangoStyle));
234           break;
235 
236         case PANGO_ATTR_WEIGHT:
237           attr = pango_attr_weight_new (va_arg (args, PangoWeight));
238           break;
239 
240         case PANGO_ATTR_VARIANT:
241           attr = pango_attr_variant_new (va_arg (args, PangoVariant));
242           break;
243 
244         case PANGO_ATTR_STRETCH:
245           attr = pango_attr_stretch_new (va_arg (args, PangoStretch));
246           break;
247 
248         case PANGO_ATTR_SIZE:
249           attr = pango_attr_size_new (va_arg (args, gint));
250           break;
251 
252         case PANGO_ATTR_FONT_DESC:
253           attr = pango_attr_font_desc_new (va_arg (args,
254                                                    const PangoFontDescription *));
255           break;
256 
257         case PANGO_ATTR_FOREGROUND:
258           {
259             const PangoColor *color = va_arg (args, const PangoColor *);
260 
261             attr = pango_attr_foreground_new (color->red,
262                                               color->green,
263                                               color->blue);
264           }
265           break;
266 
267         case PANGO_ATTR_BACKGROUND:
268           {
269             const PangoColor *color = va_arg (args, const PangoColor *);
270 
271             attr = pango_attr_background_new (color->red,
272                                               color->green,
273                                               color->blue);
274           }
275           break;
276 
277         case PANGO_ATTR_UNDERLINE:
278           attr = pango_attr_underline_new (va_arg (args, PangoUnderline));
279           break;
280 
281         case PANGO_ATTR_STRIKETHROUGH:
282           attr = pango_attr_strikethrough_new (va_arg (args, gboolean));
283           break;
284 
285         case PANGO_ATTR_RISE:
286           attr = pango_attr_rise_new (va_arg (args, gint));
287           break;
288 
289         case PANGO_ATTR_SCALE:
290           attr = pango_attr_scale_new (va_arg (args, gdouble));
291           break;
292 
293         default:
294           g_warning ("%s: invalid PangoAttribute type %d",
295                      G_STRFUNC, attr_type);
296         case PANGO_ATTR_INVALID:
297           attr = NULL;
298           break;
299         }
300 
301       if (attr)
302         {
303           attr->start_index = 0;
304           attr->end_index   = -1;
305           pango_attr_list_insert (attrs, attr);
306         }
307     }
308   while (attr);
309 
310   va_end (args);
311 
312   gtk_label_set_attributes (label, attrs);
313   pango_attr_list_unref (attrs);
314 }
315 
316 gint
gimp_widget_get_monitor(GtkWidget * widget)317 gimp_widget_get_monitor (GtkWidget *widget)
318 {
319   GdkWindow     *window;
320   GdkScreen     *screen;
321   GtkAllocation  allocation;
322   gint           x, y;
323 
324   g_return_val_if_fail (GTK_IS_WIDGET (widget), 0);
325 
326   window = gtk_widget_get_window (widget);
327 
328   if (! window)
329     return gimp_get_monitor_at_pointer (&screen);
330 
331   screen = gtk_widget_get_screen (widget);
332 
333   gdk_window_get_origin (window, &x, &y);
334   gtk_widget_get_allocation (widget, &allocation);
335 
336   if (! gtk_widget_get_has_window (widget))
337     {
338       x += allocation.x;
339       y += allocation.y;
340     }
341 
342   x += allocation.width  / 2;
343   y += allocation.height / 2;
344 
345   return gdk_screen_get_monitor_at_point (screen, x, y);
346 }
347 
348 gint
gimp_get_monitor_at_pointer(GdkScreen ** screen)349 gimp_get_monitor_at_pointer (GdkScreen **screen)
350 {
351   gint x, y;
352 
353   g_return_val_if_fail (screen != NULL, 0);
354 
355   gdk_display_get_pointer (gdk_display_get_default (),
356                            screen, &x, &y, NULL);
357 
358   return gdk_screen_get_monitor_at_point (*screen, x, y);
359 }
360 
361 typedef void (* MonitorChangedCallback) (GtkWidget *, gpointer);
362 
363 typedef struct
364 {
365   GtkWidget *widget;
366   gint       monitor;
367 
368   MonitorChangedCallback callback;
369   gpointer               user_data;
370 } TrackMonitorData;
371 
372 static gboolean
track_monitor_configure_event(GtkWidget * toplevel,GdkEvent * event,TrackMonitorData * track_data)373 track_monitor_configure_event (GtkWidget        *toplevel,
374                                GdkEvent         *event,
375                                TrackMonitorData *track_data)
376 {
377   gint monitor = gimp_widget_get_monitor (toplevel);
378 
379   if (monitor != track_data->monitor)
380     {
381       track_data->monitor = monitor;
382 
383       track_data->callback (track_data->widget, track_data->user_data);
384     }
385 
386   return FALSE;
387 }
388 
389 static void
track_monitor_hierarchy_changed(GtkWidget * widget,GtkWidget * previous_toplevel,TrackMonitorData * track_data)390 track_monitor_hierarchy_changed (GtkWidget        *widget,
391                                  GtkWidget        *previous_toplevel,
392                                  TrackMonitorData *track_data)
393 {
394   GtkWidget *toplevel;
395 
396   if (previous_toplevel)
397     {
398       g_signal_handlers_disconnect_by_func (previous_toplevel,
399                                             track_monitor_configure_event,
400                                             track_data);
401     }
402 
403   toplevel = gtk_widget_get_toplevel (widget);
404 
405   if (GTK_IS_WINDOW (toplevel))
406     {
407       GClosure *closure;
408       gint      monitor;
409 
410       closure = g_cclosure_new (G_CALLBACK (track_monitor_configure_event),
411                                 track_data, NULL);
412       g_object_watch_closure (G_OBJECT (widget), closure);
413       g_signal_connect_closure (toplevel, "configure-event", closure, FALSE);
414 
415       monitor = gimp_widget_get_monitor (toplevel);
416 
417       if (monitor != track_data->monitor)
418         {
419           track_data->monitor = monitor;
420 
421           track_data->callback (track_data->widget, track_data->user_data);
422         }
423     }
424 }
425 
426 /**
427  * gimp_widget_track_monitor:
428  * @widget:                   a #GtkWidget
429  * @monitor_changed_callback: the callback when @widget's monitor changes
430  * @user_data:                data passed to @monitor_changed_callback
431  *
432  * This function behaves as if #GtkWidget had a signal
433  *
434  * GtkWidget::monitor_changed(GtkWidget *widget, gpointer user_data)
435  *
436  * That is emitted whenever @widget's toplevel window is moved from
437  * one monitor to another. This function automatically connects to
438  * the right toplevel #GtkWindow, even across moving @widget between
439  * toplevel windows.
440  *
441  * Note that this function tracks the toplevel, not @widget itself, so
442  * all a window's widgets are always considered to be on the same
443  * monitor. This is because this function is mainly used for fetching
444  * the new monitor's color profile, and it makes little sense to use
445  * different profiles for the widgets of one window.
446  *
447  * Since: 2.10
448  **/
449 void
gimp_widget_track_monitor(GtkWidget * widget,GCallback monitor_changed_callback,gpointer user_data)450 gimp_widget_track_monitor (GtkWidget *widget,
451                            GCallback  monitor_changed_callback,
452                            gpointer   user_data)
453 {
454   TrackMonitorData *track_data;
455   GtkWidget        *toplevel;
456 
457   g_return_if_fail (GTK_IS_WIDGET (widget));
458   g_return_if_fail (monitor_changed_callback != NULL);
459 
460   track_data = g_new0 (TrackMonitorData, 1);
461 
462   track_data->widget    = widget;
463   track_data->callback  = (MonitorChangedCallback) monitor_changed_callback;
464   track_data->user_data = user_data;
465 
466   g_object_weak_ref (G_OBJECT (widget), (GWeakNotify) g_free, track_data);
467 
468   g_signal_connect (widget, "hierarchy-changed",
469                     G_CALLBACK (track_monitor_hierarchy_changed),
470                     track_data);
471 
472   toplevel = gtk_widget_get_toplevel (widget);
473 
474   if (GTK_IS_WINDOW (toplevel))
475     track_monitor_hierarchy_changed (widget, NULL, track_data);
476 }
477 
478 /**
479  * gimp_screen_get_color_profile:
480  * @screen:  a #GdkScreen
481  * @monitor: the monitor number
482  *
483  * This function returns the #GimpColorProfile of monitor number @monitor
484  * of @screen, or %NULL if there is no profile configured.
485  *
486  * Since: 2.10
487  *
488  * Return value: the monitor's #GimpColorProfile, or %NULL.
489  **/
490 GimpColorProfile *
gimp_screen_get_color_profile(GdkScreen * screen,gint monitor)491 gimp_screen_get_color_profile (GdkScreen *screen,
492                                gint       monitor)
493 {
494   GimpColorProfile *profile = NULL;
495 
496   g_return_val_if_fail (GDK_IS_SCREEN (screen), NULL);
497   g_return_val_if_fail (monitor >= 0, NULL);
498   g_return_val_if_fail (monitor < gdk_screen_get_n_monitors (screen), NULL);
499 
500 #if defined GDK_WINDOWING_X11
501   {
502     GdkAtom  type    = GDK_NONE;
503     gint     format  = 0;
504     gint     nitems  = 0;
505     gchar   *atom_name;
506     guchar  *data    = NULL;
507 
508     if (monitor > 0)
509       atom_name = g_strdup_printf ("_ICC_PROFILE_%d", monitor);
510     else
511       atom_name = g_strdup ("_ICC_PROFILE");
512 
513     if (gdk_property_get (gdk_screen_get_root_window (screen),
514                           gdk_atom_intern (atom_name, FALSE),
515                           GDK_NONE,
516                           0, 64 * 1024 * 1024, FALSE,
517                           &type, &format, &nitems, &data) && nitems > 0)
518       {
519         profile = gimp_color_profile_new_from_icc_profile (data, nitems,
520                                                            NULL);
521         g_free (data);
522       }
523 
524     g_free (atom_name);
525   }
526 #elif defined GDK_WINDOWING_QUARTZ
527   {
528     CGColorSpaceRef space = NULL;
529 
530     space = CGDisplayCopyColorSpace (monitor);
531 
532     if (space)
533       {
534         CFDataRef data;
535 
536         data = CGColorSpaceCopyICCProfile (space);
537 
538         if (data)
539           {
540             UInt8 *buffer = g_malloc (CFDataGetLength (data));
541 
542             /* We cannot use CFDataGetBytesPtr(), because that returns
543              * a const pointer where cmsOpenProfileFromMem wants a
544              * non-const pointer.
545              */
546             CFDataGetBytes (data, CFRangeMake (0, CFDataGetLength (data)),
547                             buffer);
548 
549             profile = gimp_color_profile_new_from_icc_profile (buffer,
550                                                                CFDataGetLength (data),
551                                                                NULL);
552 
553             g_free (buffer);
554             CFRelease (data);
555           }
556 
557         CFRelease (space);
558       }
559   }
560 #elif defined G_OS_WIN32
561   {
562     GdkRectangle   monitor_geometry;
563     POINT          point;
564     gint           offsetx = GetSystemMetrics (SM_XVIRTUALSCREEN);
565     gint           offsety = GetSystemMetrics (SM_YVIRTUALSCREEN);
566     HMONITOR       monitor_handle;
567     MONITORINFOEX  info;
568     DISPLAY_DEVICE display_device;
569 
570     info.cbSize       = sizeof (MONITORINFOEX);
571     display_device.cb = sizeof (DISPLAY_DEVICE);
572 
573     /* If the first monitor is not set as the main monitor,
574      * the monitor variable may not match the index used in
575      * EnumDisplayDevices(devicename, index, displaydevice, flags).
576      */
577     gdk_screen_get_monitor_geometry (screen, monitor, &monitor_geometry);
578     point.x = monitor_geometry.x + offsetx;
579     point.y = monitor_geometry.y + offsety;
580     monitor_handle = MonitorFromPoint (point, MONITOR_DEFAULTTONEAREST);
581 
582     if (GetMonitorInfo (monitor_handle, (LPMONITORINFO)&info))
583       {
584         if (EnumDisplayDevices (info.szDevice, 0, &display_device, 0))
585           {
586             gchar                        *device_key = g_convert (display_device.DeviceKey, -1, "UTF-16LE", "WINDOWS-1252", NULL, NULL, NULL);
587             gchar                        *filename   = NULL;
588             gchar                        *dir        = NULL;
589             gchar                        *fullpath   = NULL;
590             GFile                        *file;
591             DWORD                         len        = 0;
592             gboolean                      per_user;
593             WCS_PROFILE_MANAGEMENT_SCOPE  scope;
594 
595             WcsGetUsePerUserProfiles ((LPWSTR)device_key, CLASS_MONITOR, &per_user);
596             scope = per_user ? WCS_PROFILE_MANAGEMENT_SCOPE_CURRENT_USER : WCS_PROFILE_MANAGEMENT_SCOPE_SYSTEM_WIDE;
597 
598             if (WcsGetDefaultColorProfileSize (scope,
599                                                (LPWSTR)device_key,
600                                                CPT_ICC,
601                                                CPST_NONE,
602                                                0,
603                                                &len))
604               {
605                 gchar *filename_utf16 = g_new (gchar, len);
606 
607                 WcsGetDefaultColorProfile (scope,
608                                            (LPWSTR)device_key,
609                                            CPT_ICC,
610                                            CPST_NONE,
611                                            0,
612                                            len,
613                                            (LPWSTR)filename_utf16);
614 
615                 /* filename_utf16 must be native endian */
616                 filename = g_utf16_to_utf8 ((gunichar2 *)filename_utf16, -1, NULL, NULL, NULL);
617                 g_free (filename_utf16);
618               }
619             else
620               {
621                 /* Due to a bug in Windows, the meanings of LCS_sRGB and
622                  * LCS_WINDOWS_COLOR_SPACE are swapped.
623                  */
624                 GetStandardColorSpaceProfile (NULL, LCS_sRGB, NULL, &len);
625                 filename = g_new (gchar, len);
626                 GetStandardColorSpaceProfile (NULL, LCS_sRGB, filename, &len);
627               }
628 
629             GetColorDirectory (NULL, NULL, &len);
630             dir = g_new (gchar, len);
631             GetColorDirectory (NULL, dir, &len);
632 
633             fullpath = g_build_filename (dir, filename, NULL);
634             file = g_file_new_for_path (fullpath);
635 
636             profile = gimp_color_profile_new_from_file (file, NULL);
637             g_object_unref (file);
638 
639             g_free (fullpath);
640             g_free (dir);
641             g_free (filename);
642             g_free (device_key);
643           }
644       }
645   }
646 #endif
647 
648   return profile;
649 }
650 
651 GimpColorProfile *
gimp_widget_get_color_profile(GtkWidget * widget)652 gimp_widget_get_color_profile (GtkWidget *widget)
653 {
654   GdkScreen *screen;
655   gint       monitor;
656 
657   g_return_val_if_fail (widget == NULL || GTK_IS_WIDGET (widget), NULL);
658 
659   if (widget)
660     {
661       screen  = gtk_widget_get_screen (widget);
662       monitor = gimp_widget_get_monitor (widget);
663     }
664   else
665     {
666       screen  = gdk_screen_get_default ();
667       monitor = 0;
668     }
669 
670   return gimp_screen_get_color_profile (screen, monitor);
671 }
672 
673 static GimpColorProfile *
get_display_profile(GtkWidget * widget,GimpColorConfig * config)674 get_display_profile (GtkWidget       *widget,
675                      GimpColorConfig *config)
676 {
677   GimpColorProfile *profile = NULL;
678 
679   if (gimp_color_config_get_display_profile_from_gdk (config))
680     /* get the toplevel's profile so all a window's colors look the same */
681     profile = gimp_widget_get_color_profile (gtk_widget_get_toplevel (widget));
682 
683   if (! profile)
684     profile = gimp_color_config_get_display_color_profile (config, NULL);
685 
686   if (! profile)
687     profile = gimp_color_profile_new_rgb_srgb ();
688 
689   return profile;
690 }
691 
692 typedef struct _TransformCache TransformCache;
693 
694 struct _TransformCache
695 {
696   GimpColorTransform *transform;
697 
698   GimpColorConfig    *config;
699   GimpColorProfile   *src_profile;
700   const Babl         *src_format;
701   GimpColorProfile   *dest_profile;
702   const Babl         *dest_format;
703   GimpColorProfile   *proof_profile;
704 
705   gulong              notify_id;
706 };
707 
708 static GList    *transform_caches = NULL;
709 static gboolean  debug_cache      = FALSE;
710 
711 static gboolean
profiles_equal(GimpColorProfile * profile1,GimpColorProfile * profile2)712 profiles_equal (GimpColorProfile *profile1,
713                 GimpColorProfile *profile2)
714 {
715   return ((profile1 == NULL && profile2 == NULL) ||
716           (profile1 != NULL && profile2 != NULL &&
717            gimp_color_profile_is_equal (profile1, profile2)));
718 }
719 
720 static TransformCache *
transform_cache_get(GimpColorConfig * config,GimpColorProfile * src_profile,const Babl * src_format,GimpColorProfile * dest_profile,const Babl * dest_format,GimpColorProfile * proof_profile)721 transform_cache_get (GimpColorConfig  *config,
722                      GimpColorProfile *src_profile,
723                      const Babl       *src_format,
724                      GimpColorProfile *dest_profile,
725                      const Babl       *dest_format,
726                      GimpColorProfile *proof_profile)
727 {
728   GList *list;
729 
730   for (list = transform_caches; list; list = g_list_next (list))
731     {
732       TransformCache *cache = list->data;
733 
734       if (config      == cache->config                        &&
735           src_format  == cache->src_format                    &&
736           dest_format == cache->dest_format                   &&
737           profiles_equal (src_profile,   cache->src_profile)  &&
738           profiles_equal (dest_profile,  cache->dest_profile) &&
739           profiles_equal (proof_profile, cache->proof_profile))
740         {
741           if (debug_cache)
742             g_printerr ("found cache %p\n", cache);
743 
744           return cache;
745         }
746     }
747 
748   return NULL;
749 }
750 
751 static void
transform_cache_config_notify(GObject * config,const GParamSpec * pspec,TransformCache * cache)752 transform_cache_config_notify (GObject          *config,
753                                const GParamSpec *pspec,
754                                TransformCache   *cache)
755 {
756   transform_caches = g_list_remove (transform_caches, cache);
757 
758   g_signal_handler_disconnect (config, cache->notify_id);
759 
760   if (cache->transform)
761     g_object_unref (cache->transform);
762 
763   g_object_unref (cache->src_profile);
764   g_object_unref (cache->dest_profile);
765 
766   if (cache->proof_profile)
767     g_object_unref (cache->proof_profile);
768 
769   g_free (cache);
770 
771   if (debug_cache)
772     g_printerr ("deleted cache %p\n", cache);
773 }
774 
775 GimpColorTransform *
gimp_widget_get_color_transform(GtkWidget * widget,GimpColorConfig * config,GimpColorProfile * src_profile,const Babl * src_format,const Babl * dest_format)776 gimp_widget_get_color_transform (GtkWidget        *widget,
777                                  GimpColorConfig  *config,
778                                  GimpColorProfile *src_profile,
779                                  const Babl       *src_format,
780                                  const Babl       *dest_format)
781 {
782   static gboolean     initialized   = FALSE;
783   GimpColorProfile   *dest_profile  = NULL;
784   GimpColorProfile   *proof_profile = NULL;
785   TransformCache     *cache;
786 
787   g_return_val_if_fail (widget == NULL || GTK_IS_WIDGET (widget), NULL);
788   g_return_val_if_fail (GIMP_IS_COLOR_CONFIG (config), NULL);
789   g_return_val_if_fail (GIMP_IS_COLOR_PROFILE (src_profile), NULL);
790   g_return_val_if_fail (src_format != NULL, NULL);
791   g_return_val_if_fail (dest_format != NULL, NULL);
792 
793   if (G_UNLIKELY (! initialized))
794     {
795       initialized = TRUE;
796 
797       debug_cache = g_getenv ("GIMP_DEBUG_TRANSFORM_CACHE") != NULL;
798     }
799 
800   switch (gimp_color_config_get_mode (config))
801     {
802     case GIMP_COLOR_MANAGEMENT_OFF:
803       return NULL;
804 
805     case GIMP_COLOR_MANAGEMENT_SOFTPROOF:
806       proof_profile = gimp_color_config_get_simulation_color_profile (config,
807                                                                       NULL);
808       /*  fallthru  */
809 
810     case GIMP_COLOR_MANAGEMENT_DISPLAY:
811       dest_profile = get_display_profile (widget, config);
812       break;
813     }
814 
815   cache = transform_cache_get (config,
816                                src_profile,
817                                src_format,
818                                dest_profile,
819                                dest_format,
820                                proof_profile);
821 
822   if (cache)
823     {
824       g_object_unref (dest_profile);
825 
826       if (proof_profile)
827         g_object_unref (proof_profile);
828 
829       if (cache->transform)
830         return g_object_ref (cache->transform);
831 
832       return NULL;
833     }
834 
835   if (! proof_profile &&
836       gimp_color_profile_is_equal (src_profile, dest_profile))
837     {
838       g_object_unref (dest_profile);
839 
840       return NULL;
841     }
842 
843   cache = g_new0 (TransformCache, 1);
844 
845   if (debug_cache)
846     g_printerr ("creating cache %p\n", cache);
847 
848   cache->config        = g_object_ref (config);
849   cache->src_profile   = g_object_ref (src_profile);
850   cache->src_format    = src_format;
851   cache->dest_profile  = dest_profile;
852   cache->dest_format   = dest_format;
853   cache->proof_profile = proof_profile;
854 
855   cache->notify_id =
856     g_signal_connect (cache->config, "notify",
857                       G_CALLBACK (transform_cache_config_notify),
858                       cache);
859 
860   transform_caches = g_list_prepend (transform_caches, cache);
861 
862   if (cache->proof_profile)
863     {
864       GimpColorTransformFlags flags = 0;
865 
866       if (gimp_color_config_get_simulation_bpc (config))
867         flags |= GIMP_COLOR_TRANSFORM_FLAGS_BLACK_POINT_COMPENSATION;
868 
869       if (! gimp_color_config_get_simulation_optimize (config))
870         flags |= GIMP_COLOR_TRANSFORM_FLAGS_NOOPTIMIZE;
871 
872       if (gimp_color_config_get_simulation_gamut_check (config))
873         {
874           cmsUInt16Number alarmCodes[cmsMAXCHANNELS] = { 0, };
875           guchar          r, g, b;
876 
877           flags |= GIMP_COLOR_TRANSFORM_FLAGS_GAMUT_CHECK;
878 
879           gimp_rgb_get_uchar (&config->out_of_gamut_color, &r, &g, &b);
880 
881           alarmCodes[0] = (cmsUInt16Number) r * 256;
882           alarmCodes[1] = (cmsUInt16Number) g * 256;
883           alarmCodes[2] = (cmsUInt16Number) b * 256;
884 
885           cmsSetAlarmCodes (alarmCodes);
886         }
887 
888       cache->transform =
889         gimp_color_transform_new_proofing (cache->src_profile,
890                                            cache->src_format,
891                                            cache->dest_profile,
892                                            cache->dest_format,
893                                            cache->proof_profile,
894                                            gimp_color_config_get_simulation_intent (config),
895                                            gimp_color_config_get_display_intent (config),
896                                            flags);
897     }
898   else
899     {
900       GimpColorTransformFlags flags = 0;
901 
902       if (gimp_color_config_get_display_bpc (config))
903         flags |= GIMP_COLOR_TRANSFORM_FLAGS_BLACK_POINT_COMPENSATION;
904 
905       if (! gimp_color_config_get_display_optimize (config))
906         flags |= GIMP_COLOR_TRANSFORM_FLAGS_NOOPTIMIZE;
907 
908       cache->transform =
909         gimp_color_transform_new (cache->src_profile,
910                                   cache->src_format,
911                                   cache->dest_profile,
912                                   cache->dest_format,
913                                   gimp_color_config_get_display_intent (config),
914                                   flags);
915     }
916 
917   if (cache->transform)
918     return g_object_ref (cache->transform);
919 
920   return NULL;
921 }
922