image post-processing (rotation and color management) moved to
[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 void color_man_correct_region(ColorMan *cm, GdkPixbuf *pixbuf, gint x, gint y, gint w, gint h)
281 {
282         ColorManCache *cc;
283         guchar *pix;
284         gint rs;
285         gint i;
286         gint pixbuf_width, pixbuf_height;
287         
288
289         pixbuf_width = gdk_pixbuf_get_width(pixbuf);
290         pixbuf_height = gdk_pixbuf_get_height(pixbuf);
291
292         cc = cm->profile;
293
294         pix = gdk_pixbuf_get_pixels(pixbuf);
295         rs = gdk_pixbuf_get_rowstride(pixbuf);
296
297         w = MIN(w, pixbuf_width - x);
298         h = MIN(h, pixbuf_height - y);
299
300         pix += x * ((cc->has_alpha) ? 4 : 3);
301         for (i = 0; i < h; i++)
302                 {
303                 guchar *pbuf;
304
305                 pbuf = pix + ((y + i) * rs);
306                 
307                 cmsDoTransform(cc->transform, pbuf, pbuf, w);
308                 }
309
310 }
311
312 static gint color_man_idle_cb(gpointer data)
313 {
314         ColorMan *cm = data;
315         gint width, height;
316         gint rh;
317         if (!cm->pixbuf) return FALSE;
318
319         if (cm->imd &&
320             cm->pixbuf != image_get_pixbuf(cm->imd))
321                 {
322                 cm->idle_id = -1;
323                 color_man_done(cm, COLOR_RETURN_IMAGE_CHANGED);
324                 return FALSE;
325                 }
326
327         width = gdk_pixbuf_get_width(cm->pixbuf);
328         height = gdk_pixbuf_get_height(cm->pixbuf);
329
330         if (cm->row > height)
331                 {
332                 if (!cm->incremental_sync && cm->imd)
333                         {
334                         image_area_changed(cm->imd, 0, 0, width, height);
335                         }
336
337                 cm->idle_id = -1;
338                 color_man_done(cm, COLOR_RETURN_SUCCESS);
339                 return FALSE;
340                 }
341
342         rh = COLOR_MAN_CHUNK_SIZE / width + 1;
343         color_man_correct_region(cm, cm->pixbuf, 0, cm->row, width, rh);
344         if (cm->incremental_sync && cm->imd) image_area_changed(cm->imd, 0, cm->row, width, rh);
345         cm->row += rh;
346
347         return TRUE;
348 }
349
350 static ColorMan *color_man_new_real(ImageWindow *imd, GdkPixbuf *pixbuf,
351                                     ColorManProfileType input_type, const gchar *input_file,
352                                     unsigned char *input_data, guint input_data_len,
353                                     ColorManProfileType screen_type, const gchar *screen_file)
354 {
355         ColorMan *cm;
356         gint has_alpha;
357
358         if (imd) pixbuf = image_get_pixbuf(imd);
359
360         cm = g_new0(ColorMan, 1);
361         cm->imd = imd;
362         cm->pixbuf = pixbuf;
363         if (cm->pixbuf) g_object_ref(cm->pixbuf);
364
365         cm->incremental_sync = FALSE;
366         cm->row = 0;
367         cm->idle_id = -1;
368
369         has_alpha = pixbuf ? gdk_pixbuf_get_has_alpha(pixbuf) : FALSE;
370
371         cm->profile = color_man_cache_get(input_type, input_file, input_data, input_data_len,
372                                           screen_type, screen_file, has_alpha);
373         if (!cm->profile)
374                 {
375                 color_man_free(cm);
376                 return NULL;
377                 }
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 {
386         return color_man_new_real(imd, pixbuf,
387                                   input_type, input_file, NULL, 0,
388                                   screen_type, screen_file);
389 }
390
391 void color_man_start_bg(ColorMan *cm, ColorManDoneFunc done_func, gpointer done_data)
392 {
393         cm->func_done = done_func;
394         cm->func_done_data = done_data;
395         cm->idle_id = g_idle_add(color_man_idle_cb, cm);
396 }
397
398 ColorMan *color_man_new_embedded(ImageWindow *imd, GdkPixbuf *pixbuf,
399                                  unsigned char *input_data, guint input_data_len,
400                                  ColorManProfileType screen_type, const gchar *screen_file)
401 {
402         return color_man_new_real(imd, pixbuf,
403                                   COLOR_PROFILE_MEM, NULL, input_data, input_data_len,
404                                   screen_type, screen_file);
405 }
406
407 void color_man_free(ColorMan *cm)
408 {
409         if (!cm) return;
410
411         if (cm->idle_id != -1) g_source_remove(cm->idle_id);
412         if (cm->pixbuf) g_object_unref(cm->pixbuf);
413
414         color_man_cache_unref(cm->profile);
415
416         g_free(cm);
417 }
418
419 void color_man_update(void)
420 {
421         color_man_cache_reset();
422 }
423
424 #else
425 /*** color support not enabled ***/
426
427
428 ColorMan *color_man_new(ImageWindow *imd, GdkPixbuf *pixbuf,
429                         ColorManProfileType input_type, const gchar *input_file,
430                         ColorManProfileType screen_type, const gchar *screen_file)
431 {
432         /* no op */
433         return NULL;
434 }
435
436 ColorMan *color_man_new_embedded(ImageWindow *imd, GdkPixbuf *pixbuf,
437                                  unsigned char *input_data, guint input_data_len,
438                                  ColorManProfileType screen_type, const gchar *screen_file)
439 {
440         /* no op */
441         return NULL;
442 }
443
444 void color_man_free(ColorMan *cm)
445 {
446         /* no op */
447 }
448
449 void color_man_update(void)
450 {
451         /* no op */
452 }
453
454 void color_man_correct_region(ColorMan *cm, GdkPixbuf *pixbuf, gint x, gint y, gint w, gint h)
455 {
456         /* no op */
457 }
458
459 void color_man_start_bg(ColorMan *cm, ColorManDoneFunc done_func, gpointer done_data)
460 {
461         /* no op */
462 }
463
464 #endif
465
466