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