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