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