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