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