2 * ROX-Filer, filer for the ROX desktop project
3 * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17 * Place, Suite 330, Boston, MA 02111-1307 USA
20 /* pixmaps.c - code for handling pixbufs (despite the name!) */
25 /* Remove pixmaps from the cache when they haven't been accessed for
26 * this period of time (seconds).
29 #define PIXMAP_PURGE_TIME 1200
30 #define PIXMAP_THUMB_SIZE 128
31 #define PIXMAP_THUMB_TOO_OLD_TIME 5
37 #include <sys/types.h>
47 #include "gui_support.h"
58 GFSCache
*pixmap_cache
= NULL
;
59 GFSCache
*desktop_icon_cache
= NULL
;
61 static const char * bad_xpm
[] = {
79 MaskedPixmap
*im_error
;
80 MaskedPixmap
*im_unknown
;
82 MaskedPixmap
*im_appdir
;
84 MaskedPixmap
*im_dirs
;
86 GtkIconSize mount_icon_size
= -1;
88 typedef struct _ChildThumbnail ChildThumbnail
;
90 /* There is one of these for each active child process */
91 struct _ChildThumbnail
{
97 static const char *stocks
[] = {
98 ROX_STOCK_SHOW_DETAILS
,
99 ROX_STOCK_SHOW_HIDDEN
,
107 /* Static prototypes */
109 static void load_default_pixmaps(void);
110 static gint
purge(gpointer data
);
111 static MaskedPixmap
*image_from_file(const char *path
);
112 static MaskedPixmap
*image_from_desktop_file(const char *path
);
113 static MaskedPixmap
*get_bad_image(void);
114 static GdkPixbuf
*scale_pixbuf_up(GdkPixbuf
*src
, int max_w
, int max_h
);
115 static GdkPixbuf
*get_thumbnail_for(const char *path
);
116 static void thumbnail_child_done(ChildThumbnail
*info
);
117 static void child_create_thumbnail(const gchar
*path
, MIME_type
*type
);
118 static GList
*thumbs_purge_cache(Option
*option
, xmlNode
*node
, guchar
*label
);
119 static gchar
*thumbnail_path(const gchar
*path
);
120 static gchar
*thumbnail_program(MIME_type
*type
);
121 static GdkPixbuf
*extract_tiff_thumbnail(const gchar
*path
);
123 /****************************************************************
124 * EXTERNAL INTERFACE *
125 ****************************************************************/
127 void pixmaps_init(void)
129 GtkIconFactory
*factory
;
132 gtk_widget_push_colormap(gdk_rgb_get_colormap());
134 pixmap_cache
= g_fscache_new((GFSLoadFunc
) image_from_file
, NULL
, NULL
);
135 desktop_icon_cache
= g_fscache_new((GFSLoadFunc
) image_from_desktop_file
, NULL
, NULL
);
137 g_timeout_add(10000, purge
, NULL
);
139 factory
= gtk_icon_factory_new();
140 for (i
= 0; i
< G_N_ELEMENTS(stocks
); i
++)
143 GError
*error
= NULL
;
146 const gchar
*name
= stocks
[i
];
148 path
= g_strconcat(app_dir
, "/images/", name
, ".png", NULL
);
149 pixbuf
= gdk_pixbuf_new_from_file(path
, &error
);
152 g_warning("%s", error
->message
);
154 pixbuf
= gdk_pixbuf_new_from_xpm_data(bad_xpm
);
158 iset
= gtk_icon_set_new_from_pixbuf(pixbuf
);
159 g_object_unref(G_OBJECT(pixbuf
));
160 gtk_icon_factory_add(factory
, name
, iset
);
161 gtk_icon_set_unref(iset
);
163 gtk_icon_factory_add_default(factory
);
165 mount_icon_size
= gtk_icon_size_register("rox-mount-size", 14, 14);
167 load_default_pixmaps();
169 option_register_widget("thumbs-purge-cache", thumbs_purge_cache
);
172 /* Load image <appdir>/images/name.png.
173 * Always returns with a valid image.
175 MaskedPixmap
*load_pixmap(const char *name
)
178 MaskedPixmap
*retval
;
180 path
= g_strconcat(app_dir
, "/images/", name
, ".png", NULL
);
181 retval
= image_from_file(path
);
185 retval
= get_bad_image();
190 /* Create a MaskedPixmap from a GTK stock ID. Always returns
193 static MaskedPixmap
*mp_from_stock(const char *stock_id
, int size
)
195 GtkIconSet
*icon_set
;
197 MaskedPixmap
*retval
;
199 /*icon_set = gtk_icon_factory_lookup_default(stock_id);*/
200 icon_set
= gtk_style_lookup_icon_set(gtk_widget_get_default_style(),
203 return get_bad_image();
205 pixbuf
= gtk_icon_set_render_icon(icon_set
,
206 gtk_widget_get_default_style(), /* Gtk bug */
212 retval
= masked_pixmap_new(pixbuf
);
213 g_object_unref(pixbuf
);
218 void pixmap_make_huge(MaskedPixmap
*mp
)
223 g_return_if_fail(mp
->src_pixbuf
!= NULL
);
225 /* Limit to small size now, otherwise they get scaled up in mixed mode.
228 mp
->huge_pixbuf
= scale_pixbuf_up(mp
->src_pixbuf
,
229 SMALL_WIDTH
, SMALL_HEIGHT
);
231 if (!mp
->huge_pixbuf
)
233 mp
->huge_pixbuf
= mp
->src_pixbuf
;
234 g_object_ref(mp
->huge_pixbuf
);
237 mp
->huge_width
= gdk_pixbuf_get_width(mp
->huge_pixbuf
);
238 mp
->huge_height
= gdk_pixbuf_get_height(mp
->huge_pixbuf
);
241 void pixmap_make_small(MaskedPixmap
*mp
)
246 g_return_if_fail(mp
->src_pixbuf
!= NULL
);
248 mp
->sm_pixbuf
= scale_pixbuf(mp
->src_pixbuf
, SMALL_WIDTH
, SMALL_HEIGHT
);
252 mp
->sm_pixbuf
= mp
->src_pixbuf
;
253 g_object_ref(mp
->sm_pixbuf
);
256 mp
->sm_width
= gdk_pixbuf_get_width(mp
->sm_pixbuf
);
257 mp
->sm_height
= gdk_pixbuf_get_height(mp
->sm_pixbuf
);
260 /* Load image 'path' in the background and insert into pixmap_cache.
261 * Call callback(data, path) when done (path is NULL => error).
262 * If the image is already uptodate, or being created already, calls the
263 * callback right away.
265 void pixmap_background_thumb(const gchar
*path
, GFunc callback
, gpointer data
)
270 ChildThumbnail
*info
;
274 image
= pixmap_try_thumb(path
, TRUE
);
278 /* Thumbnail loaded */
279 callback(data
, path
);
283 /* Is it currently being created? */
284 image
= g_fscache_lookup_full(pixmap_cache
, path
,
285 FSCACHE_LOOKUP_ONLY_NEW
, &found
);
289 /* Thumbnail is known, or being created */
291 g_object_unref(image
);
292 callback(data
, image
? path
: NULL
);
296 /* Not in memory, nor in the thumbnails directory. We need to
299 type
= type_from_path(path
);
303 /* Add an entry, set to NULL, so no-one else tries to load this
306 g_fscache_insert(pixmap_cache
, path
, NULL
, TRUE
);
308 thumb_prog
= thumbnail_program(type
);
310 /* Only attempt to load 'images' types ourselves */
311 if (thumb_prog
== NULL
&& strcmp(type
->media_type
, "image") != 0)
313 callback(data
, NULL
);
314 return; /* Don't know how to handle this type */
322 delayed_error("fork(): %s", g_strerror(errno
));
323 callback(data
, NULL
);
329 /* We are the child process. (We are sloppy with freeing
330 memory, but since we go away very quickly, that's ok.) */
335 item
= diritem_new(g_basename(thumb_prog
));
337 diritem_restat(thumb_prog
, item
, NULL
);
338 if (item
->flags
& ITEM_FLAG_APPDIR
)
339 thumb_prog
= g_strconcat(thumb_prog
, "/AppRun",
342 execl(thumb_prog
, thumb_prog
, path
,
343 thumbnail_path(path
),
344 g_strdup_printf("%d", PIXMAP_THUMB_SIZE
),
349 child_create_thumbnail(path
, type
);
355 info
= g_new(ChildThumbnail
, 1);
356 info
->path
= g_strdup(path
);
357 info
->callback
= callback
;
359 on_child_death(child
, (CallbackFn
) thumbnail_child_done
, info
);
363 * Return the thumbnail for a file, only if available. If the
364 * can_load flags is set this includes loading from the cache, otherwise
365 * only if already in memory
367 MaskedPixmap
*pixmap_try_thumb(const gchar
*path
, gboolean can_load
)
373 image
= g_fscache_lookup_full(pixmap_cache
, path
,
374 FSCACHE_LOOKUP_ONLY_NEW
, &found
);
378 /* Thumbnail is known, or being created */
386 pixbuf
= get_thumbnail_for(path
);
390 struct stat info1
, info2
;
393 /* Skip zero-byte files. They're either empty, or
394 * special (may cause us to hang, e.g. /proc/kmsg). */
395 if (mc_stat(path
, &info1
) == 0 && info1
.st_size
== 0) {
399 dir
= g_path_get_dirname(path
);
401 /* If the image itself is in ~/.thumbnails, load it now
402 * (ie, don't create thumbnails for thumbnails!).
404 if (mc_stat(dir
, &info1
) != 0)
411 if (mc_stat(make_path(home_dir
, ".thumbnails/normal"),
413 info1
.st_dev
== info2
.st_dev
&&
414 info1
.st_ino
== info2
.st_ino
)
416 pixbuf
= rox_pixbuf_new_from_file_at_scale(path
,
417 PIXMAP_THUMB_SIZE
, PIXMAP_THUMB_SIZE
,
430 image
= masked_pixmap_new(pixbuf
);
431 g_object_unref(pixbuf
);
432 g_fscache_insert(pixmap_cache
, path
, image
, TRUE
);
439 /****************************************************************
440 * INTERNAL FUNCTIONS *
441 ****************************************************************/
443 /* Create a thumbnail file for this image */
444 static void save_thumbnail(const char *pathname
, GdkPixbuf
*full
)
448 int original_width
, original_height
;
450 char *md5
, *swidth
, *sheight
, *ssize
, *smtime
, *uri
;
455 thumb
= scale_pixbuf(full
, PIXMAP_THUMB_SIZE
, PIXMAP_THUMB_SIZE
);
457 original_width
= gdk_pixbuf_get_width(full
);
458 original_height
= gdk_pixbuf_get_height(full
);
460 if (mc_stat(pathname
, &info
) != 0)
463 swidth
= g_strdup_printf("%d", original_width
);
464 sheight
= g_strdup_printf("%d", original_height
);
465 ssize
= g_strdup_printf("%" SIZE_FMT
, info
.st_size
);
466 smtime
= g_strdup_printf("%ld", (long) info
.st_mtime
);
468 path
= pathdup(pathname
);
469 uri
= g_filename_to_uri(path
, NULL
, NULL
);
471 uri
= g_strconcat("file://", path
, NULL
);
475 to
= g_string_new(home_dir
);
476 g_string_append(to
, "/.thumbnails");
477 mkdir(to
->str
, 0700);
478 g_string_append(to
, "/normal/");
479 mkdir(to
->str
, 0700);
480 g_string_append(to
, md5
);
481 name_len
= to
->len
+ 4; /* Truncate to this length when renaming */
482 g_string_append_printf(to
, ".png.ROX-Filer-%ld", (long) getpid());
486 old_mask
= umask(0077);
487 gdk_pixbuf_save(thumb
, to
->str
, "png", NULL
,
488 "tEXt::Thumb::Image::Width", swidth
,
489 "tEXt::Thumb::Image::Height", sheight
,
490 "tEXt::Thumb::Size", ssize
,
491 "tEXt::Thumb::MTime", smtime
,
492 "tEXt::Thumb::URI", uri
,
493 "tEXt::Software", PROJECT
,
497 /* We create the file ###.png.ROX-Filer-PID and rename it to avoid
498 * a race condition if two programs create the same thumb at
504 final
= g_strndup(to
->str
, name_len
);
505 if (rename(to
->str
, final
))
506 g_warning("Failed to rename '%s' to '%s': %s",
507 to
->str
, final
, g_strerror(errno
));
511 g_string_free(to
, TRUE
);
519 static gchar
*thumbnail_path(const char *path
)
525 uri
= g_filename_to_uri(path
, NULL
, NULL
);
527 uri
= g_strconcat("file://", path
, NULL
);
530 to
= g_string_new(home_dir
);
531 g_string_append(to
, "/.thumbnails");
532 mkdir(to
->str
, 0700);
533 g_string_append(to
, "/normal/");
534 mkdir(to
->str
, 0700);
535 g_string_append(to
, md5
);
536 g_string_append(to
, ".png");
542 g_string_free(to
, FALSE
);
547 /* Return a program to create thumbnails for files of this type.
548 * NULL to try to make it ourself (using gdk).
551 static gchar
*thumbnail_program(MIME_type
*type
)
559 leaf
= g_strconcat(type
->media_type
, "_", type
->subtype
, NULL
);
560 path
= choices_find_xdg_path_load(leaf
, "MIME-thumb", SITE
);
567 path
= choices_find_xdg_path_load(type
->media_type
, "MIME-thumb",
573 /* Called in a subprocess. Load path and create the thumbnail
574 * file. Parent will notice when we die.
576 static void child_create_thumbnail(const gchar
*path
, MIME_type
*type
)
578 GdkPixbuf
*image
=NULL
;
580 if(strcmp(type
->subtype
, "jpeg")==0)
581 image
=extract_tiff_thumbnail(path
);
584 image
= rox_pixbuf_new_from_file_at_scale(path
,
585 PIXMAP_THUMB_SIZE
, PIXMAP_THUMB_SIZE
, TRUE
, NULL
);
588 save_thumbnail(path
, image
);
590 /* (no need to unref, as we're about to exit) */
593 /* Called when the child process exits */
594 static void thumbnail_child_done(ChildThumbnail
*info
)
598 thumb
= get_thumbnail_for(info
->path
);
604 image
= masked_pixmap_new(thumb
);
605 g_object_unref(thumb
);
607 g_fscache_insert(pixmap_cache
, info
->path
, image
, FALSE
);
608 g_object_unref(image
);
610 info
->callback(info
->data
, info
->path
);
613 info
->callback(info
->data
, NULL
);
619 /* Check if we have an up-to-date thumbnail for this image.
620 * If so, return it. Otherwise, returns NULL.
622 static GdkPixbuf
*get_thumbnail_for(const char *pathname
)
624 GdkPixbuf
*thumb
= NULL
;
625 char *thumb_path
, *md5
, *uri
, *path
;
626 const char *ssize
, *smtime
;
630 path
= pathdup(pathname
);
631 uri
= g_filename_to_uri(path
, NULL
, NULL
);
633 uri
= g_strconcat("file://", path
, NULL
);
637 thumb_path
= g_strdup_printf("%s/.thumbnails/normal/%s.png",
641 thumb
= gdk_pixbuf_new_from_file(thumb_path
, NULL
);
645 /* Note that these don't need freeing... */
646 ssize
= gdk_pixbuf_get_option(thumb
, "tEXt::Thumb::Size");
647 /* This is optional, so don't flag an error if it is missing */
649 smtime
= gdk_pixbuf_get_option(thumb
, "tEXt::Thumb::MTime");
653 if (mc_stat(path
, &info
) != 0)
656 ttime
=(time_t) atol(smtime
);
658 if (info
.st_mtime
!= ttime
&& now
>ttime
+PIXMAP_THUMB_TOO_OLD_TIME
)
661 if (ssize
&& info
.st_size
< atol(ssize
))
667 g_object_unref(thumb
);
675 /* Load the image 'path' and return a pointer to the resulting
676 * MaskedPixmap. NULL on failure.
677 * Doesn't check for thumbnails (this is for small icons).
679 static MaskedPixmap
*image_from_file(const char *path
)
683 GError
*error
= NULL
;
685 pixbuf
= gdk_pixbuf_new_from_file(path
, &error
);
688 g_warning("%s\n", error
->message
);
693 image
= masked_pixmap_new(pixbuf
);
695 g_object_unref(pixbuf
);
700 /* Load this icon named by this .desktop file from the current theme.
703 static MaskedPixmap
*image_from_desktop_file(const char *path
)
705 GError
*error
= NULL
;
706 MaskedPixmap
*image
= NULL
;
709 icon
= get_value_from_desktop_file(path
,
710 "Desktop Entry", "Icon", &error
);
713 g_warning("Failed to parse .desktop file '%s':\n%s",
714 path
, error
->message
);
721 image
= image_from_file(icon
);
728 /* For some unknown reason, some icon names have extensions.
731 extension
= strrchr(icon
, '.');
732 if (extension
&& (strcmp(extension
, ".png") == 0
733 || strcmp(extension
, ".xpm") == 0
734 || strcmp(extension
, ".svg") == 0))
739 /* SVG reader is very noisy, so redirect stderr to stdout */
742 pixbuf
= theme_load_icon(icon
, HUGE_WIDTH
, 0, NULL
);
747 goto err
; /* Might just not be in the theme */
749 image
= masked_pixmap_new(pixbuf
);
750 g_object_unref(pixbuf
);
760 /* Scale src down to fit in max_w, max_h and return the new pixbuf.
761 * If src is small enough, then ref it and return that.
763 GdkPixbuf
*scale_pixbuf(GdkPixbuf
*src
, int max_w
, int max_h
)
767 w
= gdk_pixbuf_get_width(src
);
768 h
= gdk_pixbuf_get_height(src
);
770 if (w
<= max_w
&& h
<= max_h
)
777 float scale_x
= ((float) w
) / max_w
;
778 float scale_y
= ((float) h
) / max_h
;
779 float scale
= MAX(scale_x
, scale_y
);
780 int dest_w
= w
/ scale
;
781 int dest_h
= h
/ scale
;
783 return gdk_pixbuf_scale_simple(src
,
786 GDK_INTERP_BILINEAR
);
790 /* Scale src up to fit in max_w, max_h and return the new pixbuf.
791 * If src is that size or bigger, then ref it and return that.
793 static GdkPixbuf
*scale_pixbuf_up(GdkPixbuf
*src
, int max_w
, int max_h
)
797 w
= gdk_pixbuf_get_width(src
);
798 h
= gdk_pixbuf_get_height(src
);
800 if (w
== 0 || h
== 0 || w
>= max_w
|| h
>= max_h
)
807 float scale_x
= max_w
/ ((float) w
);
808 float scale_y
= max_h
/ ((float) h
);
809 float scale
= MIN(scale_x
, scale_y
);
811 return gdk_pixbuf_scale_simple(src
,
814 GDK_INTERP_BILINEAR
);
818 /* Return a pointer to the (static) bad image. The ref counter will ensure
819 * that the image is never freed.
821 static MaskedPixmap
*get_bad_image(void)
826 bad
= gdk_pixbuf_new_from_xpm_data(bad_xpm
);
827 mp
= masked_pixmap_new(bad
);
833 /* Called now and then to clear out old pixmaps */
834 static gint
purge(gpointer data
)
836 g_fscache_purge(pixmap_cache
, PIXMAP_PURGE_TIME
);
841 static gpointer parent_class
;
843 static void masked_pixmap_finialize(GObject
*object
)
845 MaskedPixmap
*mp
= (MaskedPixmap
*) object
;
849 g_object_unref(mp
->src_pixbuf
);
850 mp
->src_pixbuf
= NULL
;
855 g_object_unref(mp
->huge_pixbuf
);
856 mp
->huge_pixbuf
= NULL
;
860 g_object_unref(mp
->pixbuf
);
866 g_object_unref(mp
->sm_pixbuf
);
867 mp
->sm_pixbuf
= NULL
;
870 G_OBJECT_CLASS(parent_class
)->finalize(object
);
873 static void masked_pixmap_class_init(gpointer gclass
, gpointer data
)
875 GObjectClass
*object
= (GObjectClass
*) gclass
;
877 parent_class
= g_type_class_peek_parent(gclass
);
879 object
->finalize
= masked_pixmap_finialize
;
882 static void masked_pixmap_init(GTypeInstance
*object
, gpointer gclass
)
884 MaskedPixmap
*mp
= (MaskedPixmap
*) object
;
886 mp
->src_pixbuf
= NULL
;
888 mp
->huge_pixbuf
= NULL
;
890 mp
->huge_height
= -1;
896 mp
->sm_pixbuf
= NULL
;
901 static GType
masked_pixmap_get_type(void)
903 static GType type
= 0;
907 static const GTypeInfo info
=
909 sizeof (MaskedPixmapClass
),
910 NULL
, /* base_init */
911 NULL
, /* base_finalise */
912 masked_pixmap_class_init
,
913 NULL
, /* class_finalise */
914 NULL
, /* class_data */
915 sizeof(MaskedPixmap
),
920 type
= g_type_register_static(G_TYPE_OBJECT
, "MaskedPixmap",
927 MaskedPixmap
*masked_pixmap_new(GdkPixbuf
*full_size
)
930 GdkPixbuf
*src_pixbuf
, *normal_pixbuf
;
932 g_return_val_if_fail(full_size
!= NULL
, NULL
);
934 src_pixbuf
= scale_pixbuf(full_size
, HUGE_WIDTH
, HUGE_HEIGHT
);
935 g_return_val_if_fail(src_pixbuf
!= NULL
, NULL
);
937 normal_pixbuf
= scale_pixbuf(src_pixbuf
, ICON_WIDTH
, ICON_HEIGHT
);
938 g_return_val_if_fail(normal_pixbuf
!= NULL
, NULL
);
940 mp
= g_object_new(masked_pixmap_get_type(), NULL
);
942 mp
->src_pixbuf
= src_pixbuf
;
944 mp
->pixbuf
= normal_pixbuf
;
945 mp
->width
= gdk_pixbuf_get_width(normal_pixbuf
);
946 mp
->height
= gdk_pixbuf_get_height(normal_pixbuf
);
951 /* Load all the standard pixmaps. Also sets the default window icon. */
952 static void load_default_pixmaps(void)
955 GError
*error
= NULL
;
957 im_error
= mp_from_stock(GTK_STOCK_DIALOG_WARNING
,
958 GTK_ICON_SIZE_DIALOG
);
959 im_unknown
= mp_from_stock(GTK_STOCK_DIALOG_QUESTION
,
960 GTK_ICON_SIZE_DIALOG
);
962 im_dirs
= load_pixmap("dirs");
963 im_appdir
= load_pixmap("application");
965 pixbuf
= gdk_pixbuf_new_from_file(
966 make_path(app_dir
, ".DirIcon"), &error
);
971 icon_list
= g_list_append(NULL
, pixbuf
);
972 gtk_window_set_default_icon_list(icon_list
);
973 g_list_free(icon_list
);
975 g_object_unref(G_OBJECT(pixbuf
));
979 g_warning("%s\n", error
->message
);
984 /* Also purges memory cache */
985 static void purge_disk_cache(GtkWidget
*button
, gpointer data
)
992 g_fscache_purge(pixmap_cache
, 0);
994 path
= g_strconcat(home_dir
, "/.thumbnails/normal/", NULL
);
999 report_error(_("Can't delete thumbnails in %s:\n%s"),
1000 path
, g_strerror(errno
));
1004 while ((ent
= readdir(dir
)))
1006 if (ent
->d_name
[0] == '.')
1008 list
= g_list_prepend(list
,
1009 g_strconcat(path
, ent
->d_name
, NULL
));
1016 action_delete(list
);
1017 destroy_glist(&list
);
1020 info_message(_("There are no thumbnails to delete"));
1025 static GList
*thumbs_purge_cache(Option
*option
, xmlNode
*node
, guchar
*label
)
1027 GtkWidget
*button
, *align
;
1029 g_return_val_if_fail(option
== NULL
, NULL
);
1031 align
= gtk_alignment_new(0, 0.5, 0, 0);
1032 button
= button_new_mixed(GTK_STOCK_CLEAR
,
1033 _("Purge thumbnails disk cache"));
1034 gtk_container_add(GTK_CONTAINER(align
), button
);
1035 g_signal_connect(button
, "clicked", G_CALLBACK(purge_disk_cache
), NULL
);
1037 return g_list_append(NULL
, align
);
1041 * Based on Thierry Bousch's public domain exifdump.py.
1044 #define JPEG_FORMAT 0x201
1045 #define JPEG_FORMAT_LENGTH 0x202
1048 * Extract n-byte integer in Motorola (big-endian) format
1050 static inline long long s2n_motorola(const unsigned char *p
, int len
)
1055 for(i
=0; i
<len
; i
++)
1056 a
=(a
<<8) | (int)(p
[i
]);
1062 * Extract n-byte integer in Intel (little-endian) format
1064 static inline long long s2n_intel(const unsigned char *p
, int len
)
1069 for(i
=0; i
<len
; i
++)
1070 a
=a
| (((int) p
[i
]) << (i
*8));
1076 * Extract n-byte integer from data
1078 static int s2n(const unsigned char *dat
, int off
, int len
, char format
)
1080 const unsigned char *p
=dat
+off
;
1084 return s2n_intel(p
, len
);
1087 return s2n_motorola(p
, len
);
1094 * Load header of JPEG/Exif file and attempt to extract the embedded
1095 * thumbnail. Return NULL on failure.
1097 static GdkPixbuf
*extract_tiff_thumbnail(const gchar
*path
)
1100 unsigned char header
[256];
1103 unsigned char *data
;
1106 int thumb
=0, tlength
=0;
1107 GdkPixbuf
*buf
=NULL
;
1109 in
=fopen(path
, "rb");
1114 /* Check for Exif format */
1115 n
=fread(header
, 1, 12, in
);
1116 if(n
!=12 || strncmp((char *) header
, "\377\330\377\341", 4)!=0 ||
1117 strncmp((char *)header
+6, "Exif", 4)!=0) {
1123 length
=header
[4]*256+header
[5];
1124 data
=g_new(unsigned char, length
);
1125 n
=fread(data
, 1, length
, in
);
1126 fclose(in
); /* File no longer needed */
1132 /* Big or little endian (as 'M' or 'I') */
1135 /* Skip over main section */
1136 ifd
=s2n(data
, 4, 4, format
);
1137 entries
=s2n(data
, ifd
, 2, format
);
1139 /* Second section contains data on thumbnail */
1140 ifd
=s2n(data
, ifd
+2+12*entries
, 4, format
);
1141 entries
=s2n(data
, ifd
, 2, format
);
1143 /* Loop over the entries */
1144 for(i
=0; i
<entries
; i
++) {
1145 int entry
=ifd
+2+12*i
;
1146 int tag
=s2n(data
, entry
, 2, format
);
1147 int type
=s2n(data
, entry
+2, 2, format
);
1150 count
=s2n(data
, entry
+4, 4, format
);
1154 int val
=(int) s2n(data
, offset
, 4, format
);
1156 /* Only interested in two entries, the location of the thumbnail
1159 case JPEG_FORMAT
: thumb
=val
; break;
1160 case JPEG_FORMAT_LENGTH
: tlength
=val
; break;
1165 if(thumb
&& tlength
) {
1167 GdkPixbufLoader
*loader
;
1169 /* Don't read outside the header (some files have incorrect data) */
1170 if(thumb
+tlength
>length
)
1171 tlength
=length
-thumb
;
1173 loader
=gdk_pixbuf_loader_new();
1174 gdk_pixbuf_loader_write(loader
, data
+thumb
, tlength
, &err
);
1180 gdk_pixbuf_loader_close(loader
, &err
);
1186 buf
=gdk_pixbuf_loader_get_pixbuf(loader
);
1187 g_object_ref(buf
); /* Ref the image before we unref the loader */
1188 g_object_unref(loader
);