Simplify vflist_get_formatted()
[geeqie.git] / src / color-man.c
1 /*
2  * Copyright (C) 2006 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "main.h"
23 #include "color-man.h"
24
25 #include "image.h"
26 #include "ui_fileops.h"
27
28
29 #ifdef HAVE_LCMS
30 /*** color support enabled ***/
31
32 #ifdef HAVE_LCMS2
33 #include <lcms2.h>
34 #else
35 #include <lcms.h>
36 #endif
37
38
39 typedef struct _ColorManCache ColorManCache;
40 struct _ColorManCache {
41         cmsHPROFILE   profile_in;
42         cmsHPROFILE   profile_out;
43         cmsHTRANSFORM transform;
44
45         ColorManProfileType profile_in_type;
46         gchar *profile_in_file;
47
48         ColorManProfileType profile_out_type;
49         gchar *profile_out_file;
50
51         gboolean has_alpha;
52
53         gint refcount;
54 };
55
56 /* pixels to transform per idle call */
57 #define COLOR_MAN_CHUNK_SIZE 81900
58
59
60 static void color_man_lib_init(void)
61 {
62         static gboolean init_done = FALSE;
63
64         if (init_done) return;
65         init_done = TRUE;
66
67 #ifndef HAVE_LCMS2
68         cmsErrorAction(LCMS_ERROR_IGNORE);
69 #endif
70 }
71
72 static cmsHPROFILE color_man_create_adobe_comp(void)
73 {
74         /* ClayRGB1998 is AdobeRGB compatible */
75 #include "ClayRGB1998_icc.h"
76         return cmsOpenProfileFromMem(ClayRGB1998_icc, ClayRGB1998_icc_len);
77 }
78
79 /*
80  *-------------------------------------------------------------------
81  * color transform cache
82  *-------------------------------------------------------------------
83  */
84
85 static GList *cm_cache_list = NULL;
86
87
88 static void color_man_cache_ref(ColorManCache *cc)
89 {
90         if (!cc) return;
91
92         cc->refcount++;
93 }
94
95 static void color_man_cache_unref(ColorManCache *cc)
96 {
97         if (!cc) return;
98
99         cc->refcount--;
100         if (cc->refcount < 1)
101                 {
102                 if (cc->transform) cmsDeleteTransform(cc->transform);
103                 if (cc->profile_in) cmsCloseProfile(cc->profile_in);
104                 if (cc->profile_out) cmsCloseProfile(cc->profile_out);
105
106                 g_free(cc->profile_in_file);
107                 g_free(cc->profile_out_file);
108
109                 g_free(cc);
110                 }
111 }
112
113 static cmsHPROFILE color_man_cache_load_profile(ColorManProfileType type, const gchar *file,
114                                                 guchar *data, guint data_len)
115 {
116         cmsHPROFILE profile = NULL;
117
118         switch (type)
119                 {
120                 case COLOR_PROFILE_FILE:
121                         if (file)
122                                 {
123                                 gchar *pathl;
124
125                                 pathl = path_from_utf8(file);
126                                 profile = cmsOpenProfileFromFile(pathl, "r");
127                                 g_free(pathl);
128                                 }
129                         break;
130                 case COLOR_PROFILE_SRGB:
131                         profile = cmsCreate_sRGBProfile();
132                         break;
133                 case COLOR_PROFILE_ADOBERGB:
134                         profile = color_man_create_adobe_comp();
135                         break;
136                 case COLOR_PROFILE_MEM:
137                         if (data)
138                                 {
139                                 profile = cmsOpenProfileFromMem(data, data_len);
140                                 }
141                         break;
142                 case COLOR_PROFILE_NONE:
143                 default:
144                         break;
145                 }
146
147         return profile;
148 }
149
150 static ColorManCache *color_man_cache_new(ColorManProfileType in_type, const gchar *in_file,
151                                           guchar *in_data, guint in_data_len,
152                                           ColorManProfileType out_type, const gchar *out_file,
153                                           guchar *out_data, guint out_data_len,
154                                           gboolean has_alpha)
155 {
156         ColorManCache *cc;
157
158         color_man_lib_init();
159
160         cc = g_new0(ColorManCache, 1);
161         cc->refcount = 1;
162
163         cc->profile_in_type = in_type;
164         cc->profile_in_file = g_strdup(in_file);
165
166         cc->profile_out_type = out_type;
167         cc->profile_out_file = g_strdup(out_file);
168
169         cc->has_alpha = has_alpha;
170
171         cc->profile_in = color_man_cache_load_profile(cc->profile_in_type, cc->profile_in_file,
172                                                       in_data, in_data_len);
173         cc->profile_out = color_man_cache_load_profile(cc->profile_out_type, cc->profile_out_file,
174                                                        out_data, out_data_len);
175
176         if (!cc->profile_in || !cc->profile_out)
177                 {
178                 DEBUG_1("failed to load color profile for %s: %d %s",
179                                   (!cc->profile_in) ? "input" : "screen",
180                                   (!cc->profile_in) ? cc->profile_in_type : cc->profile_out_type,
181                                   (!cc->profile_in) ? cc->profile_in_file : cc->profile_out_file);
182
183                 color_man_cache_unref(cc);
184                 return NULL;
185                 }
186
187         cc->transform = cmsCreateTransform(cc->profile_in,
188                                            (has_alpha) ? TYPE_RGBA_8 : TYPE_RGB_8,
189                                            cc->profile_out,
190                                            (has_alpha) ? TYPE_RGBA_8 : TYPE_RGB_8,
191                                            options->color_profile.render_intent, 0);
192
193         if (!cc->transform)
194                 {
195                 DEBUG_1("failed to create color profile transform");
196
197                 color_man_cache_unref(cc);
198                 return NULL;
199                 }
200
201         if (cc->profile_in_type != COLOR_PROFILE_MEM && cc->profile_out_type != COLOR_PROFILE_MEM )
202                 {
203                 cm_cache_list = g_list_append(cm_cache_list, cc);
204                 color_man_cache_ref(cc);
205                 }
206
207         return cc;
208 }
209
210 static void color_man_cache_free(ColorManCache *cc)
211 {
212         if (!cc) return;
213
214         cm_cache_list = g_list_remove(cm_cache_list, cc);
215         color_man_cache_unref(cc);
216 }
217
218 static void color_man_cache_reset(void)
219 {
220         while (cm_cache_list)
221                 {
222                 ColorManCache *cc;
223
224                 cc = cm_cache_list->data;
225                 color_man_cache_free(cc);
226                 }
227 }
228
229 static ColorManCache *color_man_cache_find(ColorManProfileType in_type, const gchar *in_file,
230                                            ColorManProfileType out_type, const gchar *out_file,
231                                            gboolean has_alpha)
232 {
233         GList *work;
234
235         work = cm_cache_list;
236         while (work)
237                 {
238                 ColorManCache *cc;
239                 gboolean match = FALSE;
240
241                 cc = work->data;
242                 work = work->next;
243
244                 if (cc->profile_in_type == in_type &&
245                     cc->profile_out_type == out_type &&
246                     cc->has_alpha == has_alpha)
247                         {
248                         match = TRUE;
249                         }
250
251                 if (match && cc->profile_in_type == COLOR_PROFILE_FILE)
252                         {
253                         match = (cc->profile_in_file && in_file &&
254                                  strcmp(cc->profile_in_file, in_file) == 0);
255                         }
256                 if (match && cc->profile_out_type == COLOR_PROFILE_FILE)
257                         {
258                         match = (cc->profile_out_file && out_file &&
259                                  strcmp(cc->profile_out_file, out_file) == 0);
260                         }
261
262                 if (match) return cc;
263                 }
264
265         return NULL;
266 }
267
268 static ColorManCache *color_man_cache_get(ColorManProfileType in_type, const gchar *in_file,
269                                           guchar *in_data, guint in_data_len,
270                                           ColorManProfileType out_type, const gchar *out_file,
271                                           guchar *out_data, guint out_data_len,
272                                           gboolean has_alpha)
273 {
274         ColorManCache *cc;
275
276         cc = color_man_cache_find(in_type, in_file, out_type, out_file, has_alpha);
277         if (cc)
278                 {
279                 color_man_cache_ref(cc);
280                 return cc;
281                 }
282
283         return color_man_cache_new(in_type, in_file, in_data, in_data_len,
284                                    out_type, out_file, out_data, out_data_len, has_alpha);
285 }
286
287
288 /*
289  *-------------------------------------------------------------------
290  * color manager
291  *-------------------------------------------------------------------
292  */
293
294 static void color_man_done(ColorMan *cm, ColorManReturnType type)
295 {
296         if (cm->func_done)
297                 {
298                 cm->func_done(cm, type, cm->func_done_data);
299                 }
300 }
301
302 void color_man_correct_region(ColorMan *cm, GdkPixbuf *pixbuf, gint x, gint y, gint w, gint h)
303 {
304         ColorManCache *cc;
305         guchar *pix;
306         gint rs;
307         gint i;
308         gint pixbuf_width, pixbuf_height;
309
310
311         pixbuf_width = gdk_pixbuf_get_width(pixbuf);
312         pixbuf_height = gdk_pixbuf_get_height(pixbuf);
313
314         cc = cm->profile;
315
316         pix = gdk_pixbuf_get_pixels(pixbuf);
317         rs = gdk_pixbuf_get_rowstride(pixbuf);
318
319         w = MIN(w, pixbuf_width - x);
320         h = MIN(h, pixbuf_height - y);
321
322         pix += x * ((cc->has_alpha) ? 4 : 3);
323         for (i = 0; i < h; i++)
324                 {
325                 guchar *pbuf;
326
327                 pbuf = pix + ((y + i) * rs);
328
329                 cmsDoTransform(cc->transform, pbuf, pbuf, w);
330                 }
331
332 }
333
334 static gboolean color_man_idle_cb(gpointer data)
335 {
336         ColorMan *cm = data;
337         gint width, height;
338         gint rh;
339
340         if (!cm->pixbuf) return FALSE;
341
342         if (cm->imd &&
343             cm->pixbuf != image_get_pixbuf(cm->imd))
344                 {
345                 cm->idle_id = 0;
346                 color_man_done(cm, COLOR_RETURN_IMAGE_CHANGED);
347                 return FALSE;
348                 }
349
350         width = gdk_pixbuf_get_width(cm->pixbuf);
351         height = gdk_pixbuf_get_height(cm->pixbuf);
352
353         if (cm->row > height)
354                 {
355                 if (!cm->incremental_sync && cm->imd)
356                         {
357                         image_area_changed(cm->imd, 0, 0, width, height);
358                         }
359
360                 cm->idle_id = 0;
361                 color_man_done(cm, COLOR_RETURN_SUCCESS);
362                 return FALSE;
363                 }
364
365         rh = COLOR_MAN_CHUNK_SIZE / width + 1;
366         color_man_correct_region(cm, cm->pixbuf, 0, cm->row, width, rh);
367         if (cm->incremental_sync && cm->imd) image_area_changed(cm->imd, 0, cm->row, width, rh);
368         cm->row += rh;
369
370         return TRUE;
371 }
372
373 static ColorMan *color_man_new_real(ImageWindow *imd, GdkPixbuf *pixbuf,
374                                     ColorManProfileType input_type, const gchar *input_file,
375                                     guchar *input_data, guint input_data_len,
376                                     ColorManProfileType screen_type, const gchar *screen_file,
377                                     guchar *screen_data, guint screen_data_len)
378 {
379         ColorMan *cm;
380         gboolean has_alpha;
381
382         if (imd) pixbuf = image_get_pixbuf(imd);
383
384         cm = g_new0(ColorMan, 1);
385         cm->imd = imd;
386         cm->pixbuf = pixbuf;
387         if (cm->pixbuf) g_object_ref(cm->pixbuf);
388
389         has_alpha = pixbuf ? gdk_pixbuf_get_has_alpha(pixbuf) : FALSE;
390
391         cm->profile = color_man_cache_get(input_type, input_file, input_data, input_data_len,
392                                           screen_type, screen_file, screen_data, screen_data_len, has_alpha);
393         if (!cm->profile)
394                 {
395                 color_man_free(cm);
396                 return NULL;
397                 }
398
399         return cm;
400 }
401
402 ColorMan *color_man_new(ImageWindow *imd, GdkPixbuf *pixbuf,
403                         ColorManProfileType input_type, const gchar *input_file,
404                         ColorManProfileType screen_type, const gchar *screen_file,
405                         guchar *screen_data, guint screen_data_len)
406 {
407         return color_man_new_real(imd, pixbuf,
408                                   input_type, input_file, NULL, 0,
409                                   screen_type, screen_file, screen_data, screen_data_len);
410 }
411
412 void color_man_start_bg(ColorMan *cm, ColorManDoneFunc done_func, gpointer done_data)
413 {
414         cm->func_done = done_func;
415         cm->func_done_data = done_data;
416         cm->idle_id = g_idle_add(color_man_idle_cb, cm);
417 }
418
419 ColorMan *color_man_new_embedded(ImageWindow *imd, GdkPixbuf *pixbuf,
420                                  guchar *input_data, guint input_data_len,
421                                  ColorManProfileType screen_type, const gchar *screen_file,
422                                  guchar *screen_data, guint screen_data_len)
423 {
424         return color_man_new_real(imd, pixbuf,
425                                   COLOR_PROFILE_MEM, NULL, input_data, input_data_len,
426                                   screen_type, screen_file, screen_data, screen_data_len);
427 }
428
429 static gchar *color_man_get_profile_name(ColorManProfileType type, cmsHPROFILE profile)
430 {
431         switch (type)
432                 {
433                 case COLOR_PROFILE_SRGB:
434                         return g_strdup(_("sRGB"));
435                 case COLOR_PROFILE_ADOBERGB:
436                         return g_strdup(_("Adobe RGB compatible"));
437                         break;
438                 case COLOR_PROFILE_MEM:
439                 case COLOR_PROFILE_FILE:
440                         if (profile)
441                                 {
442 #ifdef HAVE_LCMS2
443                                 char buffer[20];
444                                 buffer[0] = '\0';
445                                 cmsGetProfileInfoASCII(profile, cmsInfoDescription, "en", "US", buffer, 20);
446                                 buffer[19] = '\0'; /* Just to be sure */
447                                 return g_strdup(buffer);
448 #else
449                                 return g_strdup(cmsTakeProductName(profile));
450 #endif
451                                 }
452                         return g_strdup(_("Custom profile"));
453                         break;
454                 case COLOR_PROFILE_NONE:
455                 default:
456                         return g_strdup("");
457                 }
458 }
459
460 gboolean color_man_get_status(ColorMan *cm, gchar **image_profile, gchar **screen_profile)
461 {
462         ColorManCache *cc;
463         if (!cm) return FALSE;
464
465         cc = cm->profile;
466
467         if (image_profile) *image_profile = color_man_get_profile_name(cc->profile_in_type, cc->profile_in);
468         if (screen_profile) *screen_profile = color_man_get_profile_name(cc->profile_out_type, cc->profile_out);
469         return TRUE;
470 }
471
472 void color_man_free(ColorMan *cm)
473 {
474         if (!cm) return;
475
476         if (cm->idle_id) g_source_remove(cm->idle_id);
477         if (cm->pixbuf) g_object_unref(cm->pixbuf);
478
479         color_man_cache_unref(cm->profile);
480
481         g_free(cm);
482 }
483
484 void color_man_update(void)
485 {
486         color_man_cache_reset();
487 }
488
489 #else /* define HAVE_LCMS */
490 /*** color support not enabled ***/
491
492
493 ColorMan *color_man_new(ImageWindow *imd, GdkPixbuf *pixbuf,
494                         ColorManProfileType input_type, const gchar *input_file,
495                         ColorManProfileType screen_type, const gchar *screen_file,
496                         guchar *screen_data, guint screen_data_len)
497 {
498         /* no op */
499         return NULL;
500 }
501
502 ColorMan *color_man_new_embedded(ImageWindow *imd, GdkPixbuf *pixbuf,
503                                  guchar *input_data, guint input_data_len,
504                                  ColorManProfileType screen_type, const gchar *screen_file,
505                                  guchar *screen_data, guint screen_data_len)
506 {
507         /* no op */
508         return NULL;
509 }
510
511 void color_man_free(ColorMan *cm)
512 {
513         /* no op */
514 }
515
516 void color_man_update(void)
517 {
518         /* no op */
519 }
520
521 void color_man_correct_region(ColorMan *cm, GdkPixbuf *pixbuf, gint x, gint y, gint w, gint h)
522 {
523         /* no op */
524 }
525
526 void color_man_start_bg(ColorMan *cm, ColorManDoneFunc done_func, gpointer done_data)
527 {
528         /* no op */
529 }
530
531 gboolean color_man_get_status(ColorMan *cm, gchar **image_profile, gchar **screen_profile)
532 {
533         return FALSE;
534 }
535
536 #endif /* define HAVE_LCMS */
537 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */