Add year 2009 to copyright info everywhere.
[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         gint 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 gint 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                                           gint has_alpha)
140 {
141         ColorManCache *cc;
142
143         color_man_lib_init();
144
145         cc = g_new0(ColorManCache, 1);
146         cc->refcount = 1;
147
148         cc->profile_in_type = in_type;
149         cc->profile_in_file = g_strdup(in_file);
150
151         cc->profile_out_type = out_type;
152         cc->profile_out_file = g_strdup(out_file);
153
154         cc->has_alpha = has_alpha;
155
156         cc->profile_in = color_man_cache_load_profile(cc->profile_in_type, cc->profile_in_file,
157                                                       in_data, in_data_len);
158         cc->profile_out = color_man_cache_load_profile(cc->profile_out_type, cc->profile_out_file,
159                                                        NULL, 0);
160
161         if (!cc->profile_in || !cc->profile_out)
162                 {
163                 DEBUG_1("failed to load color profile for %s: %d %s",
164                                   (!cc->profile_in) ? "input" : "screen",
165                                   (!cc->profile_in) ? cc->profile_in_type : cc->profile_out_type,
166                                   (!cc->profile_in) ? cc->profile_in_file : cc->profile_out_file);
167
168                 color_man_cache_unref(cc);
169                 return NULL;
170                 }
171
172         cc->transform = cmsCreateTransform(cc->profile_in,
173                                            (has_alpha) ? TYPE_RGBA_8 : TYPE_RGB_8,
174                                            cc->profile_out,
175                                            (has_alpha) ? TYPE_RGBA_8 : TYPE_RGB_8,
176                                            INTENT_PERCEPTUAL, 0);
177
178         if (!cc->transform)
179                 {
180                 DEBUG_1("failed to create color profile transform");
181
182                 color_man_cache_unref(cc);
183                 return NULL;
184                 }
185
186         if (cc->profile_in_type != COLOR_PROFILE_MEM)
187                 {
188                 cm_cache_list = g_list_append(cm_cache_list, cc);
189                 color_man_cache_ref(cc);
190                 }
191
192         return cc;
193 }
194
195 static void color_man_cache_free(ColorManCache *cc)
196 {
197         if (!cc) return;
198
199         cm_cache_list = g_list_remove(cm_cache_list, cc);
200         color_man_cache_unref(cc);
201 }
202
203 static void color_man_cache_reset(void)
204 {
205         while (cm_cache_list)
206                 {
207                 ColorManCache *cc;
208
209                 cc = cm_cache_list->data;
210                 color_man_cache_free(cc);
211                 }
212 }
213
214 static ColorManCache *color_man_cache_find(ColorManProfileType in_type, const gchar *in_file,
215                                            ColorManProfileType out_type, const gchar *out_file,
216                                            gint has_alpha)
217 {
218         GList *work;
219
220         work = cm_cache_list;
221         while (work)
222                 {
223                 ColorManCache *cc;
224                 gint match = FALSE;
225
226                 cc = work->data;
227                 work = work->next;
228
229                 if (cc->profile_in_type == in_type &&
230                     cc->profile_out_type == out_type &&
231                     cc->has_alpha == has_alpha)
232                         {
233                         match = TRUE;
234                         }
235
236                 if (match && cc->profile_in_type == COLOR_PROFILE_FILE)
237                         {
238                         match = (cc->profile_in_file && in_file &&
239                                  strcmp(cc->profile_in_file, in_file) == 0);
240                         }
241                 if (match && cc->profile_out_type == COLOR_PROFILE_FILE)
242                         {
243                         match = (cc->profile_out_file && out_file &&
244                                  strcmp(cc->profile_out_file, out_file) == 0);
245                         }
246
247                 if (match) return cc;
248                 }
249
250         return NULL;
251 }
252
253 static ColorManCache *color_man_cache_get(ColorManProfileType in_type, const gchar *in_file,
254                                           guchar *in_data, guint in_data_len,
255                                           ColorManProfileType out_type, const gchar *out_file,
256                                           gint has_alpha)
257 {
258         ColorManCache *cc;
259
260         cc = color_man_cache_find(in_type, in_file, out_type, out_file, has_alpha);
261         if (cc)
262                 {
263                 color_man_cache_ref(cc);
264                 return cc;
265                 }
266
267         return color_man_cache_new(in_type, in_file, in_data, in_data_len,
268                                    out_type, out_file, has_alpha);
269 }
270
271
272 /*
273  *-------------------------------------------------------------------
274  * color manager
275  *-------------------------------------------------------------------
276  */
277
278 static void color_man_done(ColorMan *cm, ColorManReturnType type)
279 {
280         if (cm->func_done)
281                 {
282                 cm->func_done(cm, type, cm->func_done_data);
283                 }
284 }
285
286 void color_man_correct_region(ColorMan *cm, GdkPixbuf *pixbuf, gint x, gint y, gint w, gint h)
287 {
288         ColorManCache *cc;
289         guchar *pix;
290         gint rs;
291         gint i;
292         gint pixbuf_width, pixbuf_height;
293
294
295         pixbuf_width = gdk_pixbuf_get_width(pixbuf);
296         pixbuf_height = gdk_pixbuf_get_height(pixbuf);
297
298         cc = cm->profile;
299
300         pix = gdk_pixbuf_get_pixels(pixbuf);
301         rs = gdk_pixbuf_get_rowstride(pixbuf);
302
303         w = MIN(w, pixbuf_width - x);
304         h = MIN(h, pixbuf_height - y);
305
306         pix += x * ((cc->has_alpha) ? 4 : 3);
307         for (i = 0; i < h; i++)
308                 {
309                 guchar *pbuf;
310
311                 pbuf = pix + ((y + i) * rs);
312
313                 cmsDoTransform(cc->transform, pbuf, pbuf, w);
314                 }
315
316 }
317
318 static gint color_man_idle_cb(gpointer data)
319 {
320         ColorMan *cm = data;
321         gint width, height;
322         gint rh;
323         if (!cm->pixbuf) return FALSE;
324
325         if (cm->imd &&
326             cm->pixbuf != image_get_pixbuf(cm->imd))
327                 {
328                 cm->idle_id = -1;
329                 color_man_done(cm, COLOR_RETURN_IMAGE_CHANGED);
330                 return FALSE;
331                 }
332
333         width = gdk_pixbuf_get_width(cm->pixbuf);
334         height = gdk_pixbuf_get_height(cm->pixbuf);
335
336         if (cm->row > height)
337                 {
338                 if (!cm->incremental_sync && cm->imd)
339                         {
340                         image_area_changed(cm->imd, 0, 0, width, height);
341                         }
342
343                 cm->idle_id = -1;
344                 color_man_done(cm, COLOR_RETURN_SUCCESS);
345                 return FALSE;
346                 }
347
348         rh = COLOR_MAN_CHUNK_SIZE / width + 1;
349         color_man_correct_region(cm, cm->pixbuf, 0, cm->row, width, rh);
350         if (cm->incremental_sync && cm->imd) image_area_changed(cm->imd, 0, cm->row, width, rh);
351         cm->row += rh;
352
353         return TRUE;
354 }
355
356 static ColorMan *color_man_new_real(ImageWindow *imd, GdkPixbuf *pixbuf,
357                                     ColorManProfileType input_type, const gchar *input_file,
358                                     guchar *input_data, guint input_data_len,
359                                     ColorManProfileType screen_type, const gchar *screen_file)
360 {
361         ColorMan *cm;
362         gint has_alpha;
363
364         if (imd) pixbuf = image_get_pixbuf(imd);
365
366         cm = g_new0(ColorMan, 1);
367         cm->imd = imd;
368         cm->pixbuf = pixbuf;
369         if (cm->pixbuf) g_object_ref(cm->pixbuf);
370
371         cm->incremental_sync = FALSE;
372         cm->row = 0;
373         cm->idle_id = -1;
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, 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 {
392         return color_man_new_real(imd, pixbuf,
393                                   input_type, input_file, NULL, 0,
394                                   screen_type, screen_file);
395 }
396
397 void color_man_start_bg(ColorMan *cm, ColorManDoneFunc done_func, gpointer done_data)
398 {
399         cm->func_done = done_func;
400         cm->func_done_data = done_data;
401         cm->idle_id = g_idle_add(color_man_idle_cb, cm);
402 }
403
404 ColorMan *color_man_new_embedded(ImageWindow *imd, GdkPixbuf *pixbuf,
405                                  guchar *input_data, guint input_data_len,
406                                  ColorManProfileType screen_type, const gchar *screen_file)
407 {
408         return color_man_new_real(imd, pixbuf,
409                                   COLOR_PROFILE_MEM, NULL, input_data, input_data_len,
410                                   screen_type, screen_file);
411 }
412
413 void color_man_free(ColorMan *cm)
414 {
415         if (!cm) return;
416
417         if (cm->idle_id != -1) g_source_remove(cm->idle_id);
418         if (cm->pixbuf) g_object_unref(cm->pixbuf);
419
420         color_man_cache_unref(cm->profile);
421
422         g_free(cm);
423 }
424
425 void color_man_update(void)
426 {
427         color_man_cache_reset();
428 }
429
430 #else /* define HAVE_LCMS */
431 /*** color support not enabled ***/
432
433
434 ColorMan *color_man_new(ImageWindow *imd, GdkPixbuf *pixbuf,
435                         ColorManProfileType input_type, const gchar *input_file,
436                         ColorManProfileType screen_type, const gchar *screen_file)
437 {
438         /* no op */
439         return NULL;
440 }
441
442 ColorMan *color_man_new_embedded(ImageWindow *imd, GdkPixbuf *pixbuf,
443                                  guchar *input_data, guint input_data_len,
444                                  ColorManProfileType screen_type, const gchar *screen_file)
445 {
446         /* no op */
447         return NULL;
448 }
449
450 void color_man_free(ColorMan *cm)
451 {
452         /* no op */
453 }
454
455 void color_man_update(void)
456 {
457         /* no op */
458 }
459
460 void color_man_correct_region(ColorMan *cm, GdkPixbuf *pixbuf, gint x, gint y, gint w, gint h)
461 {
462         /* no op */
463 }
464
465 void color_man_start_bg(ColorMan *cm, ColorManDoneFunc done_func, gpointer done_data)
466 {
467         /* no op */
468 }
469
470 #endif /* define HAVE_LCMS */
471 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */