Mon Nov 27 01:23:23 2006 John Ellis <johne@verizon.net>
[geeqie.git] / src / color-man.c
1 /*
2  * GQview
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 "gqview.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         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->pixbuf != image_get_pixbuf(cm->imd))
315                 {
316                 cm->idle_id = -1;
317                 color_man_done(cm, COLOR_RETURN_IMAGE_CHANGED);
318                 return FALSE;
319                 }
320
321         width = gdk_pixbuf_get_width(cm->pixbuf);
322         height = gdk_pixbuf_get_height(cm->pixbuf);
323
324         if (cm->row > height)
325                 {
326                 cm->idle_id = -1;
327                 color_man_done(cm, COLOR_RETURN_SUCCESS);
328                 return FALSE;
329                 }
330
331         rh = COLOR_MAN_CHUNK_SIZE / width + 1;
332         color_man_correct_region(cm, 0, cm->row, width, rh, width, height);
333         cm->row += rh;
334
335         return TRUE;
336 }
337
338 static ColorMan *color_man_new_real(ImageWindow *imd,
339                                     ColorManProfileType input_type, const gchar *input_file,
340                                     unsigned char *input_data, guint input_data_len,
341                                     ColorManProfileType screen_type, const gchar *screen_file,
342                                     ColorManDoneFunc done_func, gpointer done_data)
343 {
344         ColorMan *cm;
345         GdkPixbuf *pixbuf;
346         gint has_alpha;
347
348         if (!imd) return NULL;
349
350         pixbuf = image_get_pixbuf(imd);
351         if (!pixbuf) return NULL;
352
353         cm = g_new0(ColorMan, 1);
354         cm->imd = imd;
355         cm->pixbuf = pixbuf;
356         cm->row = 0;
357         cm->idle_id = -1;
358
359         cm->func_done = done_func;
360         cm->func_done_data = done_data;
361
362         has_alpha = gdk_pixbuf_get_has_alpha(pixbuf);
363         cm->profile = color_man_cache_get(input_type, input_file, input_data, input_data_len,
364                                           screen_type, screen_file, has_alpha);
365         if (!cm->profile)
366                 {
367                 color_man_free(cm);
368                 return NULL;
369                 }
370
371         cm->idle_id = g_idle_add(color_man_idle_cb, cm);
372
373         return cm;
374 }
375
376 ColorMan *color_man_new(ImageWindow *imd,
377                         ColorManProfileType input_type, const gchar *input_file,
378                         ColorManProfileType screen_type, const gchar *screen_file,
379                         ColorManDoneFunc done_func, gpointer done_data)
380 {
381         return color_man_new_real(imd,
382                                   input_type, input_file, NULL, 0,
383                                   screen_type, screen_file,
384                                   done_func, done_data);
385 }
386
387 ColorMan *color_man_new_embedded(ImageWindow *imd,
388                                  unsigned char *input_data, guint input_data_len,
389                                  ColorManProfileType screen_type, const gchar *screen_file,
390                                  ColorManDoneFunc done_func, gpointer done_data)
391 {
392         return color_man_new_real(imd,
393                                   COLOR_PROFILE_MEM, NULL, input_data, input_data_len,
394                                   screen_type, screen_file,
395                                   done_func, done_data);
396 }
397
398 void color_man_free(ColorMan *cm)
399 {
400         if (!cm) return;
401
402         if (cm->idle_id != -1) g_source_remove(cm->idle_id);
403
404         color_man_cache_unref(cm->profile);
405
406         g_free(cm);
407 }
408
409 void color_man_update(void)
410 {
411         color_man_cache_reset();
412 }
413
414 #else
415 /*** color support not enabled ***/
416
417
418 ColorMan *color_man_new(ImageWindow *imd,
419                         ColorManProfileType input_type, const gchar *input_file,
420                         ColorManProfileType screen_type, const gchar *screen_file,
421                         ColorManDoneFunc don_func, gpointer done_data)
422 {
423         /* no op */
424         return NULL;
425 }
426
427 void color_man_free(ColorMan *cm)
428 {
429         /* no op */
430 }
431
432 void color_man_update(void)
433 {
434         /* no op */
435 }
436
437
438 #endif
439
440