Fix warning:
[geeqie.git] / src / thumb.c
1 /*
2  * Geeqie
3  * (C) 2004 John Ellis
4  * Copyright (C) 2008 The Geeqie Team
5  *
6  * Author: John Ellis
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13
14 #include "main.h"
15 #include "thumb.h"
16
17 #include "cache.h"
18 #include "image-load.h"
19 #include "filedata.h"
20 #include "pixbuf_util.h"
21 #include "thumb_standard.h"
22 #include "ui_fileops.h"
23 #include "exif.h"
24
25 #include <utime.h>
26
27
28 static void thumb_loader_error_cb(ImageLoader *il, gpointer data);
29 static void thumb_loader_setup(ThumbLoader *tl, const gchar *path);
30
31 static GdkPixbuf *get_xv_thumbnail(gchar *thumb_filename, gint max_w, gint max_h);
32
33
34 /*
35  *-----------------------------------------------------------------------------
36  * thumbnail routines: creation, caching, and maintenance (public)
37  *-----------------------------------------------------------------------------
38  */
39
40 static gint thumb_loader_save_to_cache(ThumbLoader *tl)
41 {
42         gchar *cache_dir;
43         gint success = FALSE;
44         mode_t mode = 0755;
45
46         if (!tl || !tl->fd || !tl->fd->thumb_pixbuf) return FALSE;
47
48         cache_dir = cache_get_location(CACHE_TYPE_THUMB, tl->fd->path, FALSE, &mode);
49
50         if (cache_ensure_dir_exists(cache_dir, mode))
51                 {
52                 gchar *cache_path;
53                 gchar *pathl;
54                 gchar *name = g_strconcat(filename_from_path(tl->fd->path), GQ_CACHE_EXT_THUMB, NULL);
55
56                 cache_path = g_build_filename(cache_dir, name, NULL);
57                 g_free(name);
58
59                 DEBUG_1("Saving thumb: %s", cache_path);
60
61                 pathl = path_from_utf8(cache_path);
62                 success = pixbuf_to_file_as_png(tl->fd->thumb_pixbuf, pathl);
63                 if (success)
64                         {
65                         struct utimbuf ut;
66                         /* set thumb time to that of source file */
67
68                         ut.actime = ut.modtime = filetime(tl->fd->path);
69                         if (ut.modtime > 0)
70                                 {
71                                 utime(pathl, &ut);
72                                 }
73                         }
74                 else
75                         {
76                         DEBUG_1("Saving failed: %s", pathl);
77                         }
78
79                 g_free(pathl);
80                 g_free(cache_path);
81                 }
82
83         g_free(cache_dir);
84
85         return success;
86 }
87
88 static gint thumb_loader_mark_failure(ThumbLoader *tl)
89 {
90         gchar *cache_dir;
91         gint success = FALSE;
92         mode_t mode = 0755;
93
94         if (!tl) return FALSE;
95
96         cache_dir = cache_get_location(CACHE_TYPE_THUMB, tl->fd->path, FALSE, &mode);
97
98         if (cache_ensure_dir_exists(cache_dir, mode))
99                 {
100                 gchar *cache_path;
101                 gchar *pathl;
102                 FILE *f;
103                 gchar *name = g_strconcat(filename_from_path(tl->fd->path), GQ_CACHE_EXT_THUMB, NULL);
104
105                 cache_path = g_build_filename(cache_dir, name, NULL);
106                 g_free(name);
107
108                 DEBUG_1("marking thumb failure: %s", cache_path);
109
110                 pathl = path_from_utf8(cache_path);
111                 f = fopen(pathl, "w");
112                 if (f)
113                         {
114                         struct utimbuf ut;
115
116                         fclose(f);
117
118                         ut.actime = ut.modtime = filetime(tl->fd->path);
119                         if (ut.modtime > 0)
120                                 {
121                                 utime(pathl, &ut);
122                                 }
123
124                         success = TRUE;
125                         }
126
127                 g_free(pathl);
128                 g_free(cache_path);
129                 }
130
131         g_free(cache_dir);
132         return success;
133 }
134
135 static void thumb_loader_percent_cb(ImageLoader *il, gdouble percent, gpointer data)
136 {
137         ThumbLoader *tl = data;
138
139         tl->percent_done = percent;
140
141         if (tl->func_progress) tl->func_progress(tl, tl->data);
142 }
143
144 static void thumb_loader_done_cb(ImageLoader *il, gpointer data)
145 {
146         ThumbLoader *tl = data;
147         GdkPixbuf *pixbuf;
148         gint pw, ph;
149         gint save;
150         GdkPixbuf *rotated = NULL;
151
152         DEBUG_1("thumb done: %s", tl->fd->path);
153
154         pixbuf = image_loader_get_pixbuf(tl->il);
155         if (!pixbuf)
156                 {
157                 DEBUG_1("...but no pixbuf: %s", tl->fd->path);
158                 thumb_loader_error_cb(tl->il, tl);
159                 return;
160                 }
161
162
163         if (!tl->cache_hit && options->image.exif_rotate_enable)
164                 {
165                 if (!tl->fd->exif_orientation)
166                         {
167                         ExifData *exif = exif_read_fd(tl->fd);
168                         gint orientation;
169
170                         if (exif && exif_get_integer(exif, "Exif.Image.Orientation", &orientation))
171                                 tl->fd->exif_orientation = orientation;
172                         else
173                                 tl->fd->exif_orientation = EXIF_ORIENTATION_TOP_LEFT;
174                         exif_free_fd(tl->fd, exif);
175                         }
176                 
177                 if (tl->fd->exif_orientation != EXIF_ORIENTATION_TOP_LEFT)
178                         {
179                         rotated = pixbuf_apply_orientation(pixbuf, tl->fd->exif_orientation);
180                         pixbuf = rotated;
181                         }
182                 }
183
184         pw = gdk_pixbuf_get_width(pixbuf);
185         ph = gdk_pixbuf_get_height(pixbuf);
186
187         if (tl->cache_hit && pw != tl->max_w && ph != tl->max_h)
188                 {
189                 /* requested thumbnail size may have changed, load original */
190                 DEBUG_1("thumbnail size mismatch, regenerating: %s", tl->fd->path);
191                 tl->cache_hit = FALSE;
192
193                 thumb_loader_setup(tl, tl->fd->path);
194
195                 if (!image_loader_start(tl->il, thumb_loader_done_cb, tl))
196                         {
197                         image_loader_free(tl->il);
198                         tl->il = NULL;
199
200                         DEBUG_1("regeneration failure: %s", tl->fd->path);
201                         thumb_loader_error_cb(tl->il, tl);
202                         }
203                 return;
204                 }
205
206         /* scale ?? */
207
208         if (pw > tl->max_w || ph > tl->max_h)
209                 {
210                 gint w, h;
211
212                 if (((double)tl->max_w / pw) < ((double)tl->max_h / ph))
213                         {
214                         w = tl->max_w;
215                         h = (double)w / pw * ph;
216                         if (h < 1) h = 1;
217                         }
218                 else
219                         {
220                         h = tl->max_h;
221                         w = (double)h / ph * pw;
222                         if (w < 1) w = 1;
223                         }
224                 
225                 if (tl->fd)
226                         {
227                         if (tl->fd->thumb_pixbuf) g_object_unref(tl->fd->thumb_pixbuf);
228                         tl->fd->thumb_pixbuf = gdk_pixbuf_scale_simple(pixbuf, w, h, (GdkInterpType)options->thumbnails.quality);
229                         }
230                 save = TRUE;
231                 }
232         else
233                 {
234                 if (tl->fd)
235                         {
236                         if (tl->fd->thumb_pixbuf) g_object_unref(tl->fd->thumb_pixbuf);
237                         tl->fd->thumb_pixbuf = pixbuf;
238                         gdk_pixbuf_ref(tl->fd->thumb_pixbuf);
239                         }
240                 save = il->shrunk;
241                 }
242
243         if (rotated) gdk_pixbuf_unref(rotated);
244         
245         /* save it ? */
246         if (tl->cache_enable && save)
247                 {
248                 thumb_loader_save_to_cache(tl);
249                 }
250
251         if (tl->func_done) tl->func_done(tl, tl->data);
252 }
253
254 static void thumb_loader_error_cb(ImageLoader *il, gpointer data)
255 {
256         ThumbLoader *tl = data;
257
258         /* if at least some of the image is available, go to done_cb */
259         if (image_loader_get_pixbuf(tl->il) != NULL)
260                 {
261                 thumb_loader_done_cb(il, data);
262                 return;
263                 }
264
265         DEBUG_1("thumb error: %s", tl->fd->path);
266
267         image_loader_free(tl->il);
268         tl->il = NULL;
269
270         if (tl->func_error) tl->func_error(tl, tl->data);
271 }
272
273 static gint thumb_loader_done_delay_cb(gpointer data)
274 {
275         ThumbLoader *tl = data;
276
277         tl->idle_done_id = -1;
278
279         if (tl->func_done) tl->func_done(tl, tl->data);
280
281         return FALSE;
282 }
283
284 static void thumb_loader_delay_done(ThumbLoader *tl)
285 {
286         if (tl->idle_done_id == -1) tl->idle_done_id = g_idle_add(thumb_loader_done_delay_cb, tl);
287 }
288
289 static void thumb_loader_setup(ThumbLoader *tl, const gchar *path)
290 {
291         FileData *fd = file_data_new_simple(path);
292         image_loader_free(tl->il);
293         tl->il = image_loader_new(fd);
294         file_data_unref(fd);
295
296         if (options->thumbnails.fast)
297                 {
298                 /* this will speed up jpegs by up to 3x in some cases */
299                 image_loader_set_requested_size(tl->il, tl->max_w, tl->max_h);
300                 }
301
302         image_loader_set_error_func(tl->il, thumb_loader_error_cb, tl);
303         if (tl->func_progress) image_loader_set_percent_func(tl->il, thumb_loader_percent_cb, tl);
304 }
305
306 void thumb_loader_set_callbacks(ThumbLoader *tl,
307                                 ThumbLoaderFunc func_done,
308                                 ThumbLoaderFunc func_error,
309                                 ThumbLoaderFunc func_progress,
310                                 gpointer data)
311 {
312         if (!tl) return;
313
314         if (tl->standard_loader)
315                 {
316                 thumb_loader_std_set_callbacks((ThumbLoaderStd *)tl,
317                                                (ThumbLoaderStdFunc) func_done,
318                                                (ThumbLoaderStdFunc) func_error,
319                                                (ThumbLoaderStdFunc) func_progress,
320                                                data);
321                 return;
322                 }
323
324         tl->func_done = func_done;
325         tl->func_error = func_error;
326         tl->func_progress = func_progress;
327
328         tl->data = data;
329 }
330
331 void thumb_loader_set_cache(ThumbLoader *tl, gint enable_cache, gint local, gint retry_failed)
332 {
333         if (!tl) return;
334
335         if (tl->standard_loader)
336                 {
337                 thumb_loader_std_set_cache((ThumbLoaderStd *)tl, enable_cache, local, retry_failed);
338                 return;
339                 }
340
341         tl->cache_enable = enable_cache;
342 #if 0
343         tl->cache_local = local;
344         tl->cache_retry = retry_failed;
345 #endif
346 }
347
348
349 gint thumb_loader_start(ThumbLoader *tl, FileData *fd)
350 {
351         gchar *cache_path = NULL;
352
353         if (!tl) return FALSE;
354
355         if (tl->standard_loader)
356                 {
357                 return thumb_loader_std_start((ThumbLoaderStd *)tl, fd);
358                 }
359
360         if (!tl->fd && !fd) return FALSE;
361
362         if (!tl->fd) tl->fd = file_data_ref(fd);
363
364         if (tl->fd->thumb_pixbuf) g_object_unref(tl->fd->thumb_pixbuf);
365         tl->fd->thumb_pixbuf = pixbuf_fallback(tl->fd, tl->max_w, tl->max_h);
366
367         if (tl->cache_enable)
368                 {
369                 cache_path = cache_find_location(CACHE_TYPE_THUMB, tl->fd->path);
370
371                 if (cache_path)
372                         {
373                         if (cache_time_valid(cache_path, tl->fd->path))
374                                 {
375                                 DEBUG_1("Found in cache:%s", tl->fd->path);
376
377                                 if (filesize(cache_path) == 0)
378                                         {
379                                         DEBUG_1("Broken image mark found:%s", cache_path);
380                                         g_free(cache_path);
381                                         return FALSE;
382                                         }
383
384                                 DEBUG_1("Cache location:%s", cache_path);
385                                 }
386                         else
387                                 {
388                                 g_free(cache_path);
389                                 cache_path = NULL;
390                                 }
391                         }
392                 }
393
394         if (!cache_path && options->thumbnails.use_xvpics)
395                 {
396                 if (tl->fd->thumb_pixbuf) g_object_unref(tl->fd->thumb_pixbuf);
397                 tl->fd->thumb_pixbuf = get_xv_thumbnail(tl->fd->path, tl->max_w, tl->max_h);
398                 if (tl->fd->thumb_pixbuf)
399                         {
400                         thumb_loader_delay_done(tl);
401                         return TRUE;
402                         }
403                 }
404
405         if (cache_path)
406                 {
407                 thumb_loader_setup(tl, cache_path);
408                 g_free(cache_path);
409                 tl->cache_hit = TRUE;
410                 }
411         else
412                 {
413                 thumb_loader_setup(tl, tl->fd->path);
414                 }
415
416         if (!image_loader_start(tl->il, thumb_loader_done_cb, tl))
417                 {
418                 /* try from original if cache attempt */
419                 if (tl->cache_hit)
420                         {
421                         tl->cache_hit = FALSE;
422                         log_printf("%s", _("Thumbnail image in cache failed to load, trying to recreate.\n"));
423
424                         thumb_loader_setup(tl, tl->fd->path);
425                         if (image_loader_start(tl->il, thumb_loader_done_cb, tl)) return TRUE;
426                         }
427                 /* mark failed thumbnail in cache with 0 byte file */
428                 if (tl->cache_enable)
429                         {
430                         thumb_loader_mark_failure(tl);
431                         }
432
433                 image_loader_free(tl->il);
434                 tl->il = NULL;
435                 return FALSE;
436                 }
437
438         return TRUE;
439 }
440
441 #if 0
442 gint thumb_loader_to_pixmap(ThumbLoader *tl, GdkPixmap **pixmap, GdkBitmap **mask)
443 {
444         if (!tl || !tl->pixbuf) return -1;
445
446         gdk_pixbuf_render_pixmap_and_mask(tl->pixbuf, pixmap, mask, 128);
447
448         return thumb_loader_get_space(tl);
449 }
450 #endif
451
452 GdkPixbuf *thumb_loader_get_pixbuf(ThumbLoader *tl)
453 {
454         GdkPixbuf *pixbuf;
455
456         if (tl && tl->standard_loader)
457                 {
458                 return thumb_loader_std_get_pixbuf((ThumbLoaderStd *)tl);
459                 }
460
461         if (tl && tl->fd && tl->fd->thumb_pixbuf)
462                 {
463                 pixbuf = tl->fd->thumb_pixbuf;
464                 g_object_ref(pixbuf);
465                 }
466         else
467                 {
468                 pixbuf = pixbuf_fallback(NULL, tl->max_w, tl->max_h);
469                 }
470
471         return pixbuf;
472 }
473
474 #if 0
475 gint thumb_loader_get_space(ThumbLoader *tl)
476 {
477         if (!tl) return 0;
478
479         if (tl->pixbuf) return (tl->max_w - gdk_pixbuf_get_width(tl->pixbuf));
480
481         return tl->max_w;
482 }
483 #endif
484
485 ThumbLoader *thumb_loader_new(gint width, gint height)
486 {
487         ThumbLoader *tl;
488
489         if (options->thumbnails.spec_standard)
490                 {
491                 return (ThumbLoader *)thumb_loader_std_new(width, height);
492                 }
493
494         tl = g_new0(ThumbLoader, 1);
495         tl->standard_loader = FALSE;
496         tl->fd = NULL;
497         tl->cache_enable = options->thumbnails.enable_caching;
498         tl->cache_hit = FALSE;
499         tl->percent_done = 0.0;
500         tl->max_w = width;
501         tl->max_h = height;
502
503         tl->il = NULL;
504
505         tl->idle_done_id = -1;
506
507         return tl;
508 }
509
510 void thumb_loader_free(ThumbLoader *tl)
511 {
512         if (!tl) return;
513
514         if (tl->standard_loader)
515                 {
516                 thumb_loader_std_free((ThumbLoaderStd *)tl);
517                 return;
518                 }
519
520         image_loader_free(tl->il);
521         file_data_unref(tl->fd);
522
523         if (tl->idle_done_id != -1) g_source_remove(tl->idle_done_id);
524
525         g_free(tl);
526 }
527
528 #if 0
529 gint thumb_from_xpm_d(const char **data, gint max_w, gint max_h, GdkPixmap **pixmap, GdkBitmap **mask)
530 {
531         GdkPixbuf *pixbuf;
532         gint w, h;
533
534         pixbuf = gdk_pixbuf_new_from_xpm_data(data);
535         w = gdk_pixbuf_get_width(pixbuf);
536         h = gdk_pixbuf_get_height(pixbuf);
537
538         if (pixbuf_scale_aspect(w, h, max_w, max_h, &w, &h))
539                 {
540                 /* scale */
541                 GdkPixbuf *tmp;
542
543                 tmp = pixbuf;
544                 pixbuf = gdk_pixbuf_scale_simple(tmp, w, h, GDK_INTERP_NEAREST);
545                 gdk_pixbuf_unref(tmp);
546                 }
547
548         gdk_pixbuf_render_pixmap_and_mask(pixbuf, pixmap, mask, 128);
549         gdk_pixbuf_unref(pixbuf);
550
551         return w;
552 }
553 #endif
554
555 /*
556  *-----------------------------------------------------------------------------
557  * xvpics thumbnail support, read-only (private)
558  *-----------------------------------------------------------------------------
559  */
560
561 /*
562  * xvpics code originally supplied by:
563  * "Diederen Damien" <D.Diederen@student.ulg.ac.be>
564  *
565  * Note: Code has been modified to fit the style of the other code, and to use
566  *       a few more glib-isms.
567  * 08-28-2000: Updated to return a gdk_pixbuf, Imlib is dieing a death here.
568  */
569
570 #define XV_BUFFER 2048
571 static guchar *load_xv_thumbnail(gchar *filename, gint *widthp, gint *heightp)
572 {
573         FILE *file;
574         gchar buffer[XV_BUFFER];
575         guchar *data;
576         gint width, height, depth;
577
578         file = fopen(filename, "rt");
579         if (!file) return NULL;
580
581         fgets(buffer, XV_BUFFER, file);
582         if (strncmp(buffer, "P7 332", 6) != 0)
583                 {
584                 fclose(file);
585                 return NULL;
586                 }
587
588         while (fgets(buffer, XV_BUFFER, file) && buffer[0] == '#') /* do_nothing() */;
589
590         if (sscanf(buffer, "%d %d %d", &width, &height, &depth) != 3)
591                 {
592                 fclose(file);
593                 return NULL;
594                 }
595
596         data = g_new(guchar, width * height);
597         fread(data, 1, width * height, file);
598
599         fclose(file);
600         *widthp = width;
601         *heightp = height;
602         return data;
603 }
604 #undef XV_BUFFER
605
606 static void free_rgb_buffer(guchar *pixels, gpointer data)
607 {
608         g_free(pixels);
609 }
610
611 static GdkPixbuf *get_xv_thumbnail(gchar *thumb_filename, gint max_w, gint max_h)
612 {
613         gint width, height;
614         gchar *thumb_name;
615         gchar *path;
616         gchar *directory;
617         gchar *name;
618         guchar *packed_data;
619
620         path = path_from_utf8(thumb_filename);
621         directory = g_path_get_dirname(path);
622         name = g_path_get_basename(path);
623         
624         thumb_name = g_build_filename(directory, ".xvpics", name, NULL);
625         
626         g_free(name);
627         g_free(directory);
628         g_free(path);
629
630         packed_data = load_xv_thumbnail(thumb_name, &width, &height);
631         g_free(thumb_name);
632
633         if (packed_data)
634                 {
635                 guchar *rgb_data;
636                 GdkPixbuf *pixbuf;
637                 gint i;
638
639                 rgb_data = g_new(guchar, width * height * 3);
640                 for (i = 0; i < width * height; i++)
641                         {
642                         rgb_data[i * 3 + 0] = (packed_data[i] >> 5) * 36;
643                         rgb_data[i * 3 + 1] = ((packed_data[i] & 28) >> 2) * 36;
644                         rgb_data[i * 3 + 2] = (packed_data[i] & 3) * 85;
645                         }
646                 g_free(packed_data);
647
648                 pixbuf = gdk_pixbuf_new_from_data(rgb_data, GDK_COLORSPACE_RGB, FALSE, 8,
649                                                   width, height, 3 * width, free_rgb_buffer, NULL);
650
651                 if (pixbuf_scale_aspect(width, height, max_w, max_h, &width, &height))
652                         {
653                         /* scale */
654                         GdkPixbuf *tmp;
655
656                         tmp = pixbuf;
657                         pixbuf = gdk_pixbuf_scale_simple(tmp, width, height, GDK_INTERP_NEAREST);
658                         gdk_pixbuf_unref(tmp);
659                         }
660
661                 return pixbuf;
662                 }
663
664         return NULL;
665 }