Include debug.h from main.h since debug macros may be used anywhere in the code.
[geeqie.git] / src / color-man.c
1 /*
2  * Geeqie
3  * (C) 2006 John Ellis
4  * Copyright (C) 2008 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_LCMS_LCMS_H
25   #include <lcms/lcms.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         gint 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 gint init_done = FALSE;
55
56         if (init_done) return;
57         init_done = TRUE;
58
59         cmsErrorAction(LCMS_ERROR_IGNORE);
60 }
61
62 static cmsHPROFILE color_man_create_adobe_comp(void)
63 {
64         /* ClayRGB1998 is AdobeRGB compatible */
65 #include "ClayRGB1998_icc.h"
66         return cmsOpenProfileFromMem(ClayRGB1998_icc, ClayRGB1998_icc_len);
67 }
68
69 /*
70  *-------------------------------------------------------------------
71  * color transform cache
72  *-------------------------------------------------------------------
73  */
74
75 static GList *cm_cache_list = NULL;
76
77
78 static void color_man_cache_ref(ColorManCache *cc)
79 {
80         if (!cc) return;
81
82         cc->refcount++;
83 }
84
85 static void color_man_cache_unref(ColorManCache *cc)
86 {
87         if (!cc) return;
88
89         cc->refcount--;
90         if (cc->refcount < 1)
91                 {
92                 if (cc->transform) cmsDeleteTransform(cc->transform);
93                 if (cc->profile_in) cmsCloseProfile(cc->profile_in);
94                 if (cc->profile_out) cmsCloseProfile(cc->profile_out);
95
96                 g_free(cc->profile_in_file);
97                 g_free(cc->profile_out_file);
98
99                 g_free(cc);
100                 }
101 }
102
103 static cmsHPROFILE color_man_cache_load_profile(ColorManProfileType type, const gchar *file,
104                                                 unsigned char *data, guint data_len)
105 {
106         cmsHPROFILE profile = NULL;
107
108         switch (type)
109                 {
110                 case COLOR_PROFILE_FILE:
111                         if (file)
112                                 {
113                                 gchar *pathl;
114
115                                 pathl = path_from_utf8(file);
116                                 profile = cmsOpenProfileFromFile(pathl, "r");
117                                 g_free(pathl);
118                                 }
119                         break;
120                 case COLOR_PROFILE_SRGB:
121                         profile = cmsCreate_sRGBProfile();
122                         break;
123                 case COLOR_PROFILE_ADOBERGB:
124                         profile = color_man_create_adobe_comp();
125                         break;
126                 case COLOR_PROFILE_MEM:
127                         if (data)
128                                 {
129                                 profile = cmsOpenProfileFromMem(data, data_len);
130                                 }
131                         break;
132                 case COLOR_PROFILE_NONE:
133                 default:
134                         break;
135                 }
136
137         return profile;
138 }
139
140 static ColorManCache *color_man_cache_new(ColorManProfileType in_type, const gchar *in_file,
141                                           unsigned char *in_data, guint in_data_len,
142                                           ColorManProfileType out_type, const gchar *out_file,
143                                           gint has_alpha)
144 {
145         ColorManCache *cc;
146
147         color_man_lib_init();
148
149         cc = g_new0(ColorManCache, 1);
150         cc->refcount = 1;
151
152         cc->profile_in_type = in_type;
153         cc->profile_in_file = g_strdup(in_file);
154
155         cc->profile_out_type = out_type;
156         cc->profile_out_file = g_strdup(out_file);
157
158         cc->has_alpha = has_alpha;
159
160         cc->profile_in = color_man_cache_load_profile(cc->profile_in_type, cc->profile_in_file,
161                                                       in_data, in_data_len);
162         cc->profile_out = color_man_cache_load_profile(cc->profile_out_type, cc->profile_out_file,
163                                                        NULL, 0);
164
165         if (!cc->profile_in || !cc->profile_out)
166                 {
167                 DEBUG_1("failed to load color profile for %s: %d %s",
168                                   (!cc->profile_in) ? "input" : "screen",
169                                   (!cc->profile_in) ? cc->profile_in_type : cc->profile_out_type,
170                                   (!cc->profile_in) ? cc->profile_in_file : cc->profile_out_file);
171
172                 color_man_cache_unref(cc);
173                 return NULL;
174                 }
175
176         cc->transform = cmsCreateTransform(cc->profile_in,
177                                            (has_alpha) ? TYPE_RGBA_8 : TYPE_RGB_8,
178                                            cc->profile_out,
179                                            (has_alpha) ? TYPE_RGBA_8 : TYPE_RGB_8,
180                                            INTENT_PERCEPTUAL, 0);
181
182         if (!cc->transform)
183                 {
184                 DEBUG_1("failed to create color profile transform");
185
186                 color_man_cache_unref(cc);
187                 return NULL;
188                 }
189
190         if (cc->profile_in_type != COLOR_PROFILE_MEM)
191                 {
192                 cm_cache_list = g_list_append(cm_cache_list, cc);
193                 color_man_cache_ref(cc);
194                 }
195
196         return cc;
197 }
198
199 static void color_man_cache_free(ColorManCache *cc)
200 {
201         if (!cc) return;
202
203         cm_cache_list = g_list_remove(cm_cache_list, cc);
204         color_man_cache_unref(cc);
205 }
206
207 static void color_man_cache_reset(void)
208 {
209         while (cm_cache_list)
210                 {
211                 ColorManCache *cc;
212
213                 cc = cm_cache_list->data;
214                 color_man_cache_free(cc);
215                 }
216 }
217
218 static ColorManCache *color_man_cache_find(ColorManProfileType in_type, const gchar *in_file,
219                                            ColorManProfileType out_type, const gchar *out_file,
220                                            gint has_alpha)
221 {
222         GList *work;
223
224         work = cm_cache_list;
225         while (work)
226                 {
227                 ColorManCache *cc;
228                 gint match = FALSE;
229
230                 cc = work->data;
231                 work = work->next;
232
233                 if (cc->profile_in_type == in_type &&
234                     cc->profile_out_type == out_type &&
235                     cc->has_alpha == has_alpha)
236                         {
237                         match = TRUE;
238                         }
239
240                 if (match && cc->profile_in_type == COLOR_PROFILE_FILE)
241                         {
242                         match = (cc->profile_in_file && in_file &&
243                                  strcmp(cc->profile_in_file, in_file) == 0);
244                         }
245                 if (match && cc->profile_out_type == COLOR_PROFILE_FILE)
246                         {
247                         match = (cc->profile_out_file && out_file &&
248                                  strcmp(cc->profile_out_file, out_file) == 0);
249                         }
250
251                 if (match) return cc;
252                 }
253
254         return NULL;
255 }
256
257 static ColorManCache *color_man_cache_get(ColorManProfileType in_type, const gchar *in_file,
258                                           unsigned char *in_data, guint in_data_len,
259                                           ColorManProfileType out_type, const gchar *out_file,
260                                           gint has_alpha)
261 {
262         ColorManCache *cc;
263
264         cc = color_man_cache_find(in_type, in_file, out_type, out_file, has_alpha);
265         if (cc)
266                 {
267                 color_man_cache_ref(cc);
268                 return cc;
269                 }
270
271         return color_man_cache_new(in_type, in_file, in_data, in_data_len,
272                                    out_type, out_file, has_alpha);
273 }
274
275
276 /*
277  *-------------------------------------------------------------------
278  * color manager
279  *-------------------------------------------------------------------
280  */
281
282 static void color_man_done(ColorMan *cm, ColorManReturnType type)
283 {
284         if (cm->func_done)
285                 {
286                 cm->func_done(cm, type, cm->func_done_data);
287                 }
288 }
289
290 void color_man_correct_region(ColorMan *cm, GdkPixbuf *pixbuf, gint x, gint y, gint w, gint h)
291 {
292         ColorManCache *cc;
293         guchar *pix;
294         gint rs;
295         gint i;
296         gint pixbuf_width, pixbuf_height;
297
298
299         pixbuf_width = gdk_pixbuf_get_width(pixbuf);
300         pixbuf_height = gdk_pixbuf_get_height(pixbuf);
301
302         cc = cm->profile;
303
304         pix = gdk_pixbuf_get_pixels(pixbuf);
305         rs = gdk_pixbuf_get_rowstride(pixbuf);
306
307         w = MIN(w, pixbuf_width - x);
308         h = MIN(h, pixbuf_height - y);
309
310         pix += x * ((cc->has_alpha) ? 4 : 3);
311         for (i = 0; i < h; i++)
312                 {
313                 guchar *pbuf;
314
315                 pbuf = pix + ((y + i) * rs);
316
317                 cmsDoTransform(cc->transform, pbuf, pbuf, w);
318                 }
319
320 }
321
322 static gint color_man_idle_cb(gpointer data)
323 {
324         ColorMan *cm = data;
325         gint width, height;
326         gint rh;
327         if (!cm->pixbuf) return FALSE;
328
329         if (cm->imd &&
330             cm->pixbuf != image_get_pixbuf(cm->imd))
331                 {
332                 cm->idle_id = -1;
333                 color_man_done(cm, COLOR_RETURN_IMAGE_CHANGED);
334                 return FALSE;
335                 }
336
337         width = gdk_pixbuf_get_width(cm->pixbuf);
338         height = gdk_pixbuf_get_height(cm->pixbuf);
339
340         if (cm->row > height)
341                 {
342                 if (!cm->incremental_sync && cm->imd)
343                         {
344                         image_area_changed(cm->imd, 0, 0, width, height);
345                         }
346
347                 cm->idle_id = -1;
348                 color_man_done(cm, COLOR_RETURN_SUCCESS);
349                 return FALSE;
350                 }
351
352         rh = COLOR_MAN_CHUNK_SIZE / width + 1;
353         color_man_correct_region(cm, cm->pixbuf, 0, cm->row, width, rh);
354         if (cm->incremental_sync && cm->imd) image_area_changed(cm->imd, 0, cm->row, width, rh);
355         cm->row += rh;
356
357         return TRUE;
358 }
359
360 static ColorMan *color_man_new_real(ImageWindow *imd, GdkPixbuf *pixbuf,
361                                     ColorManProfileType input_type, const gchar *input_file,
362                                     unsigned char *input_data, guint input_data_len,
363                                     ColorManProfileType screen_type, const gchar *screen_file)
364 {
365         ColorMan *cm;
366         gint 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         cm->incremental_sync = FALSE;
376         cm->row = 0;
377         cm->idle_id = -1;
378
379         has_alpha = pixbuf ? gdk_pixbuf_get_has_alpha(pixbuf) : FALSE;
380
381         cm->profile = color_man_cache_get(input_type, input_file, input_data, input_data_len,
382                                           screen_type, screen_file, has_alpha);
383         if (!cm->profile)
384                 {
385                 color_man_free(cm);
386                 return NULL;
387                 }
388
389         return cm;
390 }
391
392 ColorMan *color_man_new(ImageWindow *imd, GdkPixbuf *pixbuf,
393                         ColorManProfileType input_type, const gchar *input_file,
394                         ColorManProfileType screen_type, const gchar *screen_file)
395 {
396         return color_man_new_real(imd, pixbuf,
397                                   input_type, input_file, NULL, 0,
398                                   screen_type, screen_file);
399 }
400
401 void color_man_start_bg(ColorMan *cm, ColorManDoneFunc done_func, gpointer done_data)
402 {
403         cm->func_done = done_func;
404         cm->func_done_data = done_data;
405         cm->idle_id = g_idle_add(color_man_idle_cb, cm);
406 }
407
408 ColorMan *color_man_new_embedded(ImageWindow *imd, GdkPixbuf *pixbuf,
409                                  unsigned char *input_data, guint input_data_len,
410                                  ColorManProfileType screen_type, const gchar *screen_file)
411 {
412         return color_man_new_real(imd, pixbuf,
413                                   COLOR_PROFILE_MEM, NULL, input_data, input_data_len,
414                                   screen_type, screen_file);
415 }
416
417 void color_man_free(ColorMan *cm)
418 {
419         if (!cm) return;
420
421         if (cm->idle_id != -1) g_source_remove(cm->idle_id);
422         if (cm->pixbuf) g_object_unref(cm->pixbuf);
423
424         color_man_cache_unref(cm->profile);
425
426         g_free(cm);
427 }
428
429 void color_man_update(void)
430 {
431         color_man_cache_reset();
432 }
433
434 #else
435 /*** color support not enabled ***/
436
437
438 ColorMan *color_man_new(ImageWindow *imd, GdkPixbuf *pixbuf,
439                         ColorManProfileType input_type, const gchar *input_file,
440                         ColorManProfileType screen_type, const gchar *screen_file)
441 {
442         /* no op */
443         return NULL;
444 }
445
446 ColorMan *color_man_new_embedded(ImageWindow *imd, GdkPixbuf *pixbuf,
447                                  unsigned char *input_data, guint input_data_len,
448                                  ColorManProfileType screen_type, const gchar *screen_file)
449 {
450         /* no op */
451         return NULL;
452 }
453
454 void color_man_free(ColorMan *cm)
455 {
456         /* no op */
457 }
458
459 void color_man_update(void)
460 {
461         /* no op */
462 }
463
464 void color_man_correct_region(ColorMan *cm, GdkPixbuf *pixbuf, gint x, gint y, gint w, gint h)
465 {
466         /* no op */
467 }
468
469 void color_man_start_bg(ColorMan *cm, ColorManDoneFunc done_func, gpointer done_data)
470 {
471         /* no op */
472 }
473
474 #endif