]> git.street.me.uk Git - andy/viking.git/blob - src/thumbnails.c
Really empty GPS realtime layers.
[andy/viking.git] / src / thumbnails.c
1 /*
2  * viking -- GPS Data and Topo Analyzer, Explorer, and Manager
3  *
4  * Copyright (C) 2003-2005, Evan Battaglia <gtoevan@gmx.net>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  */
21
22 /*
23  * Large (and important) sections of this file were adapted from
24  * ROX-Filer source code, Copyright (C) 2003, the ROX-Filer team,
25  * originally licensed under the GPL v2 or greater (as above).
26  *
27  */
28
29 #ifdef HAVE_CONFIG_H
30 #include "config.h"
31 #endif
32
33 #include <stdlib.h>
34 #ifdef HAVE_UNISTD_H
35 #include <unistd.h>
36 #endif
37 #include <errno.h>
38 #include <string.h>
39 #include <glib.h>
40 #include <glib/gstdio.h>
41 #include "viking.h"
42 #include "thumbnails.h"
43 #include "icons/icons.h"
44 #include "md5_hash.h"
45
46 #ifdef __CYGWIN__
47 #ifdef __CYGWIN_USE_BIG_TYPES__
48 #define ST_SIZE_FMT "%lld"
49 #else
50 #define ST_SIZE_FMT "%ld"
51 #endif
52 #else
53 /* FIXME -- on some systems this may need to me "lld", see ROX-Filer code */
54 #define ST_SIZE_FMT "%ld"
55 #endif
56
57 #undef MIN /* quit yer whining, gcc */
58 #undef MAX
59 #ifndef MAX
60 /* We need MAX macro and some system does not offer it */
61 #define MAX(a,b) (((a)>(b))?(a):(b))
62 #endif
63
64 #define HOME_DIR g_get_home_dir()
65
66 #ifdef WINDOWS
67 #define THUMB_DIR "\\THUMBNAILS\\" /* viking maps default viking\maps */
68 #define THUMB_SUB_DIR "normal\\"
69 #else
70 #define THUMB_DIR "/.thumbnails/"
71 #define THUMB_SUB_DIR "normal/"
72 #endif
73
74 #define PIXMAP_THUMB_SIZE  128
75
76 static GdkPixbuf *save_thumbnail(const char *pathname, GdkPixbuf *full);
77 static GdkPixbuf *child_create_thumbnail(const gchar *path);
78
79 gboolean a_thumbnails_exists ( const gchar *filename )
80 {
81   GdkPixbuf *pixbuf = a_thumbnails_get(filename);
82   if ( pixbuf )
83   {
84     g_object_unref ( G_OBJECT ( pixbuf ) );
85     return TRUE;
86   }
87   return FALSE;
88 }
89
90 GdkPixbuf *a_thumbnails_get_default ()
91 {
92   return gdk_pixbuf_from_pixdata ( &thumbnails_pixbuf, FALSE, NULL );
93 }
94
95 /* filename must be absolute. you could have a function to make sure it exists and absolutize it */
96
97 void a_thumbnails_create(const gchar *filename)
98 {
99   GdkPixbuf *pixbuf = a_thumbnails_get(filename);
100
101   if ( ! pixbuf )
102     pixbuf = child_create_thumbnail(filename);
103
104   if ( pixbuf )
105     g_object_unref (  G_OBJECT ( pixbuf ) );
106 }
107
108 GdkPixbuf *a_thumbnails_scale_pixbuf(GdkPixbuf *src, int max_w, int max_h)
109 {
110         int     w, h;
111
112         w = gdk_pixbuf_get_width(src);
113         h = gdk_pixbuf_get_height(src);
114
115         if (w <= max_w && h <= max_h)
116         {
117                 g_object_ref ( G_OBJECT ( src ) );
118                 return src;
119         }
120         else
121         {
122                 float scale_x = ((float) w) / max_w;
123                 float scale_y = ((float) h) / max_h;
124                 float scale = MAX(scale_x, scale_y);
125                 int dest_w = w / scale;
126                 int dest_h = h / scale;
127                 
128                 return gdk_pixbuf_scale_simple(src,
129                                                 MAX(dest_w, 1),
130                                                 MAX(dest_h, 1),
131                                                 GDK_INTERP_BILINEAR);
132         }
133 }
134
135 static GdkPixbuf *child_create_thumbnail(const gchar *path)
136 {
137         GdkPixbuf *image, *tmpbuf;
138
139         image = gdk_pixbuf_new_from_file(path, NULL);
140         if (!image)
141                 return NULL;
142
143         tmpbuf = gdk_pixbuf_apply_embedded_orientation(image);
144         g_object_unref(G_OBJECT(image));
145         image = tmpbuf;
146
147         if (image)
148         {
149                 GdkPixbuf *thumb = save_thumbnail(path, image);
150                 g_object_unref ( G_OBJECT ( image ) );
151                 return thumb;
152         }
153
154         return NULL;
155 }
156
157 static GdkPixbuf *save_thumbnail(const char *pathname, GdkPixbuf *full)
158 {
159         struct stat info;
160         gchar *path;
161         int original_width, original_height;
162         const gchar* orientation;
163         GString *to;
164         char *md5, *swidth, *sheight, *ssize, *smtime, *uri;
165         mode_t old_mask;
166         int name_len;
167         GdkPixbuf *thumb;
168
169         if (stat(pathname, &info) != 0)
170                 return NULL;
171
172         thumb = a_thumbnails_scale_pixbuf(full, PIXMAP_THUMB_SIZE, PIXMAP_THUMB_SIZE);
173
174         orientation = gdk_pixbuf_get_option (full, "orientation");
175
176         original_width = gdk_pixbuf_get_width(full);
177         original_height = gdk_pixbuf_get_height(full);
178
179
180         swidth = g_strdup_printf("%d", original_width);
181         sheight = g_strdup_printf("%d", original_height);
182         ssize = g_strdup_printf(ST_SIZE_FMT, info.st_size);
183         smtime = g_strdup_printf("%ld", (long) info.st_mtime);
184
185         path = file_realpath_dup(pathname);
186         uri = g_strconcat("file://", path, NULL);
187         md5 = md5_hash(uri);
188         g_free(path);
189
190         to = g_string_new(HOME_DIR);
191         g_string_append(to, THUMB_DIR);
192         g_string_append(to, THUMB_SUB_DIR);
193         if ( g_mkdir_with_parents(to->str, 0700) != 0 )
194                 g_warning ("%s: Failed to mkdir %s", __FUNCTION__, to->str );
195         g_string_append(to, md5);
196         name_len = to->len + 4; /* Truncate to this length when renaming */
197 #ifdef WINDOWS
198         g_string_append_printf(to, ".png.Viking");
199 #else
200         g_string_append_printf(to, ".png.Viking-%ld", (long) getpid());
201 #endif
202
203         g_free(md5);
204
205         // Thumb::URI must be in ISO-8859-1 encoding otherwise gdk_pixbuf_save() will fail
206         // - e.g. if characters such as 'ě' are encountered
207         // Also see http://en.wikipedia.org/wiki/ISO/IEC_8859-1
208         // ATM GLIB Manual doesn't specify in which version this function became available
209         //  find out that it's fairly recent so may break builds without this test
210 #if GLIB_CHECK_VERSION(2,40,0)
211         char *thumb_uri = g_str_to_ascii ( uri, NULL );
212 #else
213         char *thumb_uri = g_strdup ( uri );
214 #endif
215         old_mask = umask(0077);
216         GError *error = NULL;
217         gdk_pixbuf_save(thumb, to->str, "png", &error,
218                         "tEXt::Thumb::Image::Width", swidth,
219                         "tEXt::Thumb::Image::Height", sheight,
220                         "tEXt::Thumb::Size", ssize,
221                         "tEXt::Thumb::MTime", smtime,
222                         "tEXt::Thumb::URI", thumb_uri,
223                         "tEXt::Software", PROJECT,
224                         "tEXt::Software::Orientation", orientation ? orientation : "0",
225                         NULL);
226         umask(old_mask);
227         g_free(thumb_uri);
228
229         if (error) {
230                 g_warning ( "%s::%s", __FUNCTION__, error->message );
231                 g_error_free ( error );
232                 g_object_unref ( G_OBJECT(thumb) );
233                 thumb = NULL; /* return NULL */
234         }
235         else
236         /* We create the file ###.png.Viking-PID and rename it to avoid
237          * a race condition if two programs create the same thumb at
238          * once.
239          */
240         {
241                 gchar *final;
242
243                 final = g_strndup(to->str, name_len);
244                 if (rename(to->str, final))
245                 {
246                         g_warning("Failed to rename '%s' to '%s': %s",
247                                   to->str, final, g_strerror(errno));
248                         g_object_unref ( G_OBJECT(thumb) );
249                         thumb = NULL; /* return NULL */
250                 }
251
252                 g_free(final);
253         }
254
255         g_string_free(to, TRUE);
256         g_free(swidth);
257         g_free(sheight);
258         g_free(ssize);
259         g_free(smtime);
260         g_free(uri);
261
262         return thumb;
263 }
264
265
266 GdkPixbuf *a_thumbnails_get(const gchar *pathname)
267 {
268         GdkPixbuf *thumb = NULL;
269         char *thumb_path, *md5, *uri, *path;
270         const char *ssize, *smtime;
271         struct stat info;
272
273         path = file_realpath_dup(pathname);
274         uri = g_strconcat("file://", path, NULL);
275         md5 = md5_hash(uri);
276         g_free(uri);
277
278         thumb_path = g_strdup_printf("%s%s%s%s.png", HOME_DIR, THUMB_DIR, THUMB_SUB_DIR, md5);
279
280         g_free(md5);
281
282         thumb = gdk_pixbuf_new_from_file(thumb_path, NULL);
283         if (!thumb)
284                 goto err;
285
286         /* Note that these don't need freeing... */
287         ssize = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::Size");
288         if (!ssize)
289                 goto err;
290
291         smtime = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::MTime");
292         if (!smtime)
293                 goto err;
294
295         if (stat(path, &info) != 0)
296                 goto err;
297
298         if (info.st_mtime != atol(smtime) || info.st_size != atol(ssize))
299                 goto err;
300
301         goto out;
302 err:
303         if (thumb)
304                 g_object_unref ( G_OBJECT ( thumb ) );
305         thumb = NULL;
306 out:
307         g_free(path);
308         g_free(thumb_path);
309         return thumb;
310 }