show color management status on statusbar
[geeqie.git] / src / color-man.c
1 /*
2  * Geeqie
3  * (C) 2006 John Ellis
4  * Copyright (C) 2008 - 2009 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13
14 #include "main.h"
15 #include "color-man.h"
16
17 #include "image.h"
18 #include "ui_fileops.h"
19
20
21 #ifdef HAVE_LCMS
22 /*** color support enabled ***/
23
24 #include <lcms.h>
25
26
27 typedef struct _ColorManCache ColorManCache;
28 struct _ColorManCache {
29         cmsHPROFILE   profile_in;
30         cmsHPROFILE   profile_out;
31         cmsHTRANSFORM transform;
32
33         ColorManProfileType profile_in_type;
34         gchar *profile_in_file;
35
36         ColorManProfileType profile_out_type;
37         gchar *profile_out_file;
38
39         gboolean has_alpha;
40
41         gint refcount;
42 };
43
44 /* pixels to transform per idle call */
45 #define COLOR_MAN_CHUNK_SIZE 81900
46
47
48 static void color_man_lib_init(void)
49 {
50         static gboolean init_done = FALSE;
51
52         if (init_done) return;
53         init_done = TRUE;
54
55         cmsErrorAction(LCMS_ERROR_IGNORE);
56 }
57
58 static cmsHPROFILE color_man_create_adobe_comp(void)
59 {
60         /* ClayRGB1998 is AdobeRGB compatible */
61 #include "ClayRGB1998_icc.h"
62         return cmsOpenProfileFromMem(ClayRGB1998_icc, ClayRGB1998_icc_len);
63 }
64
65 /*
66  *-------------------------------------------------------------------
67  * color transform cache
68  *-------------------------------------------------------------------
69  */
70
71 static GList *cm_cache_list = NULL;
72
73
74 static void color_man_cache_ref(ColorManCache *cc)
75 {
76         if (!cc) return;
77
78         cc->refcount++;
79 }
80
81 static void color_man_cache_unref(ColorManCache *cc)
82 {
83         if (!cc) return;
84
85         cc->refcount--;
86         if (cc->refcount < 1)
87                 {
88                 if (cc->transform) cmsDeleteTransform(cc->transform);
89                 if (cc->profile_in) cmsCloseProfile(cc->profile_in);
90                 if (cc->profile_out) cmsCloseProfile(cc->profile_out);
91
92                 g_free(cc->profile_in_file);
93                 g_free(cc->profile_out_file);
94
95                 g_free(cc);
96                 }
97 }
98
99 static cmsHPROFILE color_man_cache_load_profile(ColorManProfileType type, const gchar *file,
100                                                 guchar *data, guint data_len)
101 {
102         cmsHPROFILE profile = NULL;
103
104         switch (type)
105                 {
106                 case COLOR_PROFILE_FILE:
107                         if (file)
108                                 {
109                                 gchar *pathl;
110
111                                 pathl = path_from_utf8(file);
112                                 profile = cmsOpenProfileFromFile(pathl, "r");
113                                 g_free(pathl);
114                                 }
115                         break;
116                 case COLOR_PROFILE_SRGB:
117                         profile = cmsCreate_sRGBProfile();
118                         break;
119                 case COLOR_PROFILE_ADOBERGB:
120                         profile = color_man_create_adobe_comp();
121                         break;
122                 case COLOR_PROFILE_MEM:
123                         if (data)
124                                 {
125                                 profile = cmsOpenProfileFromMem(data, data_len);
126                                 }
127                         break;
128                 case COLOR_PROFILE_NONE:
129                 default:
130                         break;
131                 }
132
133         return profile;
134 }
135
136 static ColorManCache *color_man_cache_new(ColorManProfileType in_type, const gchar *in_file,
137                                           guchar *in_data, guint in_data_len,
138                                           ColorManProfileType out_type, const gchar *out_file,
139                                           guchar *out_data, guint out_data_len,
140                                           gboolean has_alpha)
141 {
142         ColorManCache *cc;
143
144         color_man_lib_init();
145
146         cc = g_new0(ColorManCache, 1);
147         cc->refcount = 1;
148
149         cc->profile_in_type = in_type;
150         cc->profile_in_file = g_strdup(in_file);
151
152         cc->profile_out_type = out_type;
153         cc->profile_out_file = g_strdup(out_file);
154
155         cc->has_alpha = has_alpha;
156
157         cc->profile_in = color_man_cache_load_profile(cc->profile_in_type, cc->profile_in_file,
158                                                       in_data, in_data_len);
159         cc->profile_out = color_man_cache_load_profile(cc->profile_out_type, cc->profile_out_file,
160                                                        out_data, out_data_len);
161
162         if (!cc->profile_in || !cc->profile_out)
163                 {
164                 DEBUG_1("failed to load color profile for %s: %d %s",
165                                   (!cc->profile_in) ? "input" : "screen",
166                                   (!cc->profile_in) ? cc->profile_in_type : cc->profile_out_type,
167                                   (!cc->profile_in) ? cc->profile_in_file : cc->profile_out_file);
168
169                 color_man_cache_unref(cc);
170                 return NULL;
171                 }
172
173         cc->transform = cmsCreateTransform(cc->profile_in,
174                                            (has_alpha) ? TYPE_RGBA_8 : TYPE_RGB_8,
175                                            cc->profile_out,
176                                            (has_alpha) ? TYPE_RGBA_8 : TYPE_RGB_8,
177                                            INTENT_PERCEPTUAL, 0);
178
179         if (!cc->transform)
180                 {
181                 DEBUG_1("failed to create color profile transform");
182
183                 color_man_cache_unref(cc);
184                 return NULL;
185                 }
186
187         if (cc->profile_in_type != COLOR_PROFILE_MEM && cc->profile_out_type != COLOR_PROFILE_MEM )
188                 {
189                 cm_cache_list = g_list_append(cm_cache_list, cc);
190                 color_man_cache_ref(cc);
191                 }
192
193         return cc;
194 }
195
196 static void color_man_cache_free(ColorManCache *cc)
197 {
198         if (!cc) return;
199
200         cm_cache_list = g_list_remove(cm_cache_list, cc);
201         color_man_cache_unref(cc);
202 }
203
204 static void color_man_cache_reset(void)
205 {
206         while (cm_cache_list)
207                 {
208                 ColorManCache *cc;
209
210                 cc = cm_cache_list->data;
211                 color_man_cache_free(cc);
212                 }
213 }
214
215 static ColorManCache *color_man_cache_find(ColorManProfileType in_type, const gchar *in_file,
216                                            ColorManProfileType out_type, const gchar *out_file,
217                                            gboolean has_alpha)
218 {
219         GList *work;
220
221         work = cm_cache_list;
222         while (work)
223                 {
224                 ColorManCache *cc;
225                 gboolean match = FALSE;
226
227                 cc = work->data;
228                 work = work->next;
229
230                 if (cc->profile_in_type == in_type &&
231                     cc->profile_out_type == out_type &&
232                     cc->has_alpha == has_alpha)
233                         {
234                         match = TRUE;
235                         }
236
237                 if (match && cc->profile_in_type == COLOR_PROFILE_FILE)
238                         {
239                         match = (cc->profile_in_file && in_file &&
240                                  strcmp(cc->profile_in_file, in_file) == 0);
241                         }
242                 if (match && cc->profile_out_type == COLOR_PROFILE_FILE)
243                         {
244                         match = (cc->profile_out_file && out_file &&
245                                  strcmp(cc->profile_out_file, out_file) == 0);
246                         }
247
248                 if (match) return cc;
249                 }
250
251         return NULL;
252 }
253
254 static ColorManCache *color_man_cache_get(ColorManProfileType in_type, const gchar *in_file,
255                                           guchar *in_data, guint in_data_len,
256                                           ColorManProfileType out_type, const gchar *out_file,
257                                           guchar *out_data, guint out_data_len,
258                                           gboolean has_alpha)
259 {
260         ColorManCache *cc;
261
262         cc = color_man_cache_find(in_type, in_file, out_type, out_file, has_alpha);
263         if (cc)
264                 {
265                 color_man_cache_ref(cc);
266                 return cc;
267                 }
268
269         return color_man_cache_new(in_type, in_file, in_data, in_data_len,
270                                    out_type, out_file, out_data, out_data_len, has_alpha);
271 }
272
273
274 /*
275  *-------------------------------------------------------------------
276  * color manager
277  *-------------------------------------------------------------------
278  */
279
280 static void color_man_done(ColorMan *cm, ColorManReturnType type)
281 {
282         if (cm->func_done)
283                 {
284                 cm->func_done(cm, type, cm->func_done_data);
285                 }
286 }
287
288 void color_man_correct_region(ColorMan *cm, GdkPixbuf *pixbuf, gint x, gint y, gint w, gint h)
289 {
290         ColorManCache *cc;
291         guchar *pix;
292         gint rs;
293         gint i;
294         gint pixbuf_width, pixbuf_height;
295
296
297         pixbuf_width = gdk_pixbuf_get_width(pixbuf);
298         pixbuf_height = gdk_pixbuf_get_height(pixbuf);
299
300         cc = cm->profile;
301
302         pix = gdk_pixbuf_get_pixels(pixbuf);
303         rs = gdk_pixbuf_get_rowstride(pixbuf);
304
305         w = MIN(w, pixbuf_width - x);
306         h = MIN(h, pixbuf_height - y);
307
308         pix += x * ((cc->has_alpha) ? 4 : 3);
309         for (i = 0; i < h; i++)
310                 {
311                 guchar *pbuf;
312
313                 pbuf = pix + ((y + i) * rs);
314
315                 cmsDoTransform(cc->transform, pbuf, pbuf, w);
316                 }
317
318 }
319
320 static gboolean color_man_idle_cb(gpointer data)
321 {
322         ColorMan *cm = data;
323         gint width, height;
324         gint rh;
325
326         if (!cm->pixbuf) return FALSE;
327
328         if (cm->imd &&
329             cm->pixbuf != image_get_pixbuf(cm->imd))
330                 {
331                 cm->idle_id = 0;
332                 color_man_done(cm, COLOR_RETURN_IMAGE_CHANGED);
333                 return FALSE;
334                 }
335
336         width = gdk_pixbuf_get_width(cm->pixbuf);
337         height = gdk_pixbuf_get_height(cm->pixbuf);
338
339         if (cm->row > height)
340                 {
341                 if (!cm->incremental_sync && cm->imd)
342                         {
343                         image_area_changed(cm->imd, 0, 0, width, height);
344                         }
345
346                 cm->idle_id = 0;
347                 color_man_done(cm, COLOR_RETURN_SUCCESS);
348                 return FALSE;
349                 }
350
351         rh = COLOR_MAN_CHUNK_SIZE / width + 1;
352         color_man_correct_region(cm, cm->pixbuf, 0, cm->row, width, rh);
353         if (cm->incremental_sync && cm->imd) image_area_changed(cm->imd, 0, cm->row, width, rh);
354         cm->row += rh;
355
356         return TRUE;
357 }
358
359 static ColorMan *color_man_new_real(ImageWindow *imd, GdkPixbuf *pixbuf,
360                                     ColorManProfileType input_type, const gchar *input_file,
361                                     guchar *input_data, guint input_data_len,
362                                     ColorManProfileType screen_type, const gchar *screen_file,
363                                     guchar *screen_data, guint screen_data_len)
364 {
365         ColorMan *cm;
366         gboolean has_alpha;
367
368         if (imd) pixbuf = image_get_pixbuf(imd);
369
370         cm = g_new0(ColorMan, 1);
371         cm->imd = imd;
372         cm->pixbuf = pixbuf;
373         if (cm->pixbuf) g_object_ref(cm->pixbuf);
374
375         has_alpha = pixbuf ? gdk_pixbuf_get_has_alpha(pixbuf) : FALSE;
376
377         cm->profile = color_man_cache_get(input_type, input_file, input_data, input_data_len,
378                                           screen_type, screen_file, screen_data, screen_data_len, has_alpha);
379         if (!cm->profile)
380                 {
381                 color_man_free(cm);
382                 return NULL;
383                 }
384
385         return cm;
386 }
387
388 ColorMan *color_man_new(ImageWindow *imd, GdkPixbuf *pixbuf,
389                         ColorManProfileType input_type, const gchar *input_file,
390                         ColorManProfileType screen_type, const gchar *screen_file,
391                         guchar *screen_data, guint screen_data_len)
392 {
393         return color_man_new_real(imd, pixbuf,
394                                   input_type, input_file, NULL, 0,
395                                   screen_type, screen_file, screen_data, screen_data_len);
396 }
397
398 void color_man_start_bg(ColorMan *cm, ColorManDoneFunc done_func, gpointer done_data)
399 {
400         cm->func_done = done_func;
401         cm->func_done_data = done_data;
402         cm->idle_id = g_idle_add(color_man_idle_cb, cm);
403 }
404
405 ColorMan *color_man_new_embedded(ImageWindow *imd, GdkPixbuf *pixbuf,
406                                  guchar *input_data, guint input_data_len,
407                                  ColorManProfileType screen_type, const gchar *screen_file,
408                                  guchar *screen_data, guint screen_data_len)
409 {
410         return color_man_new_real(imd, pixbuf,
411                                   COLOR_PROFILE_MEM, NULL, input_data, input_data_len,
412                                   screen_type, screen_file, screen_data, screen_data_len);
413 }
414
415 static gchar *color_man_get_profile_name(ColorManProfileType type, cmsHPROFILE profile)
416 {
417         switch (type)
418                 {
419                 case COLOR_PROFILE_SRGB:
420                         return g_strdup(_("sRGB"));
421                 case COLOR_PROFILE_ADOBERGB:
422                         return g_strdup(_("Adobe RGB compatible"));
423                         break;
424                 case COLOR_PROFILE_MEM:
425                 case COLOR_PROFILE_FILE:
426                         if (profile)
427                                 {
428                                 return g_strdup(cmsTakeProductName(profile));
429                                 }
430                         return g_strdup(_("Custom profile"));
431                         break;
432                 case COLOR_PROFILE_NONE:
433                 default:
434                         return g_strdup("");
435                 }
436 }
437
438 gboolean color_man_get_status(ColorMan *cm, gchar **image_profile, gchar **screen_profile)
439 {
440         ColorManCache *cc;
441         if (!cm) return FALSE;
442
443         cc = cm->profile;
444         
445         if (image_profile) *image_profile = color_man_get_profile_name(cc->profile_in_type, cc->profile_in);
446         if (screen_profile) *screen_profile = color_man_get_profile_name(cc->profile_out_type, cc->profile_out);
447         return TRUE;
448 }
449
450 void color_man_free(ColorMan *cm)
451 {
452         if (!cm) return;
453
454         if (cm->idle_id) g_source_remove(cm->idle_id);
455         if (cm->pixbuf) g_object_unref(cm->pixbuf);
456
457         color_man_cache_unref(cm->profile);
458
459         g_free(cm);
460 }
461
462 void color_man_update(void)
463 {
464         color_man_cache_reset();
465 }
466
467 #else /* define HAVE_LCMS */
468 /*** color support not enabled ***/
469
470
471 ColorMan *color_man_new(ImageWindow *imd, GdkPixbuf *pixbuf,
472                         ColorManProfileType input_type, const gchar *input_file,
473                         ColorManProfileType screen_type, const gchar *screen_file,
474                         guchar *screen_data, guint screen_data_len)
475 {
476         /* no op */
477         return NULL;
478 }
479
480 ColorMan *color_man_new_embedded(ImageWindow *imd, GdkPixbuf *pixbuf,
481                                  guchar *input_data, guint input_data_len,
482                                  ColorManProfileType screen_type, const gchar *screen_file,
483                                  guchar *screen_data, guint screen_data_len)
484 {
485         /* no op */
486         return NULL;
487 }
488
489 void color_man_free(ColorMan *cm)
490 {
491         /* no op */
492 }
493
494 void color_man_update(void)
495 {
496         /* no op */
497 }
498
499 void color_man_correct_region(ColorMan *cm, GdkPixbuf *pixbuf, gint x, gint y, gint w, gint h)
500 {
501         /* no op */
502 }
503
504 void color_man_start_bg(ColorMan *cm, ColorManDoneFunc done_func, gpointer done_data)
505 {
506         /* no op */
507 }
508
509 gboolean color_man_get_status(ColorMan *cm, gchar **image_profile, gchar **screen_profile)
510 {
511         return FALSE;
512 }
513
514 #endif /* define HAVE_LCMS */
515 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */