]> git.street.me.uk Git - andy/viking.git/blob - src/thumbnails.c
Make window dimensions easier to be changed.
[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 #include <stdlib.h>
30 #include <sys/stat.h>
31 #include <errno.h>
32 #include <string.h>
33 #include "viking.h"
34 #include "thumbnails.h"
35 #include "thumbnails_pixbuf.h"
36
37 #ifdef __CYGWIN__
38 #ifdef __CYGWIN_USE_BIG_TYPES__
39 #define ST_SIZE_FMT "%lld"
40 #else
41 #define ST_SIZE_FMT "%ld"
42 #endif
43 #else
44 /* FIXME -- on some systems this may need to me "lld", see ROX-Filer code */
45 #define ST_SIZE_FMT "%ld"
46 #endif
47
48 #undef MIN /* quit yer whining, gcc */
49 #undef MAX
50 #include <sys/param.h> /* for realpath() */
51
52 #ifdef WINDOWS
53 #define HOME_DIR "C:\\VIKING"
54 #define THUMB_DIR "\\THUMBNAILS\\" /* viking maps default viking\maps */
55 #define THUMB_SUB_DIR "normal\\"
56 #define mkdir(a,b) mkdir(a)
57 #define MAX(a,b) (((a)>(b))?(a):(b))
58 #define realpath(X,Y) _fullpath(Y,X,MAX_PATH)
59
60 #else
61 #define HOME_DIR g_get_home_dir()
62 #define THUMB_DIR "/.thumbnails/"
63 #define THUMB_SUB_DIR "normal/"
64 #endif
65
66 #define PIXMAP_THUMB_SIZE  128
67
68 static char *md5_hash(const char *message);
69 static char *pathdup(const char *path);
70 static GdkPixbuf *save_thumbnail(const char *pathname, GdkPixbuf *full);
71 static GdkPixbuf *child_create_thumbnail(const gchar *path);
72
73 gboolean a_thumbnails_exists ( const gchar *filename )
74 {
75   GdkPixbuf *pixbuf = a_thumbnails_get(filename);
76   if ( pixbuf )
77   {
78     g_object_unref ( G_OBJECT ( pixbuf ) );
79     return TRUE;
80   }
81   return FALSE;
82 }
83
84 GdkPixbuf *a_thumbnails_get_default ()
85 {
86   return gdk_pixbuf_from_pixdata ( &tnnyl_pixbuf, FALSE, NULL );
87 }
88
89 /* filename must be absolute. you could have a function to make sure it exists and absolutize it */
90
91 void a_thumbnails_create(const gchar *filename)
92 {
93   GdkPixbuf *pixbuf = a_thumbnails_get(filename);
94
95   if ( ! pixbuf )
96     pixbuf = child_create_thumbnail(filename);
97   
98   if ( pixbuf )
99     g_object_unref (  G_OBJECT ( pixbuf ) );
100 }
101
102 GdkPixbuf *a_thumbnails_scale_pixbuf(GdkPixbuf *src, int max_w, int max_h)
103 {
104         int     w, h;
105
106         w = gdk_pixbuf_get_width(src);
107         h = gdk_pixbuf_get_height(src);
108
109         if (w <= max_w && h <= max_h)
110         {
111                 gdk_pixbuf_ref(src);
112                 return src;
113         }
114         else
115         {
116                 float scale_x = ((float) w) / max_w;
117                 float scale_y = ((float) h) / max_h;
118                 float scale = MAX(scale_x, scale_y);
119                 int dest_w = w / scale;
120                 int dest_h = h / scale;
121                 
122                 return gdk_pixbuf_scale_simple(src,
123                                                 MAX(dest_w, 1),
124                                                 MAX(dest_h, 1),
125                                                 GDK_INTERP_BILINEAR);
126         }
127 }
128
129 static GdkPixbuf *child_create_thumbnail(const gchar *path)
130 {
131         GdkPixbuf *image;
132
133         image = gdk_pixbuf_new_from_file(path, NULL);
134
135         if (image)
136         {
137                 GdkPixbuf *thumb = save_thumbnail(path, image);
138                 gdk_pixbuf_unref ( image );
139                 return thumb;
140         }
141
142         return NULL;
143 }
144
145 static GdkPixbuf *save_thumbnail(const char *pathname, GdkPixbuf *full)
146 {
147         struct stat info;
148         gchar *path;
149         int original_width, original_height;
150         GString *to;
151         char *md5, *swidth, *sheight, *ssize, *smtime, *uri;
152         mode_t old_mask;
153         int name_len;
154         GdkPixbuf *thumb;
155
156         if (stat(pathname, &info) != 0)
157                 return NULL;
158
159         thumb = a_thumbnails_scale_pixbuf(full, PIXMAP_THUMB_SIZE, PIXMAP_THUMB_SIZE);
160
161         original_width = gdk_pixbuf_get_width(full);
162         original_height = gdk_pixbuf_get_height(full);
163
164
165         swidth = g_strdup_printf("%d", original_width);
166         sheight = g_strdup_printf("%d", original_height);
167         ssize = g_strdup_printf(ST_SIZE_FMT, info.st_size);
168         smtime = g_strdup_printf("%ld", (long) info.st_mtime);
169
170         path = pathdup(pathname);
171         uri = g_strconcat("file://", path, NULL);
172         md5 = md5_hash(uri);
173         g_free(path);
174                 
175         to = g_string_new(HOME_DIR);
176 #ifndef WINDOWS
177         mkdir(to->str, 0700);
178 #endif
179         g_string_append(to, THUMB_DIR);
180         mkdir(to->str, 0700);
181         g_string_append(to, THUMB_SUB_DIR);
182         mkdir(to->str, 0700);
183         g_string_append(to, md5);
184         name_len = to->len + 4; /* Truncate to this length when renaming */
185 #ifdef WINDOWS
186         g_string_append_printf(to, ".png.Viking");
187 #else
188         g_string_append_printf(to, ".png.Viking-%ld", (long) getpid());
189 #endif
190
191         g_free(md5);
192
193         old_mask = umask(0077);
194         gdk_pixbuf_save(thumb, to->str, "png", NULL,
195                         "tEXt::Thumb::Image::Width", swidth,
196                         "tEXt::Thumb::Image::Height", sheight,
197                         "tEXt::Thumb::Size", ssize,
198                         "tEXt::Thumb::MTime", smtime,
199                         "tEXt::Thumb::URI", uri,
200                         "tEXt::Software", PROJECT,
201                         NULL);
202         umask(old_mask);
203
204         /* We create the file ###.png.ROX-Filer-PID and rename it to avoid
205          * a race condition if two programs create the same thumb at
206          * once.
207          */
208         {
209                 gchar *final;
210
211                 final = g_strndup(to->str, name_len);
212                 if (rename(to->str, final))
213                 {
214                         g_warning("Failed to rename '%s' to '%s': %s",
215                                   to->str, final, g_strerror(errno));
216                         g_object_unref ( G_OBJECT(thumb) );
217                         thumb = NULL; /* return NULL */
218                 }
219
220                 g_free(final);
221         }
222
223         g_string_free(to, TRUE);
224         g_free(swidth);
225         g_free(sheight);
226         g_free(ssize);
227         g_free(smtime);
228         g_free(uri);
229
230         return thumb;
231 }
232
233
234 GdkPixbuf *a_thumbnails_get(const gchar *pathname)
235 {
236         GdkPixbuf *thumb = NULL;
237         char *thumb_path, *md5, *uri, *path;
238         const char *ssize, *smtime;
239         struct stat info;
240
241         path = pathdup(pathname);
242         uri = g_strconcat("file://", path, NULL);
243         md5 = md5_hash(uri);
244         g_free(uri);
245         
246         thumb_path = g_strdup_printf("%s%s%s%s.png", HOME_DIR, THUMB_DIR, THUMB_SUB_DIR, md5);
247
248         g_free(md5);
249
250         thumb = gdk_pixbuf_new_from_file(thumb_path, NULL);
251         if (!thumb)
252                 goto err;
253
254         /* Note that these don't need freeing... */
255         ssize = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::Size");
256         if (!ssize)
257                 goto err;
258
259         smtime = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::MTime");
260         if (!smtime)
261                 goto err;
262
263         if (stat(path, &info) != 0)
264                 goto err;
265
266         if (info.st_mtime != atol(smtime) || info.st_size != atol(ssize))
267                 goto err;
268
269         goto out;
270 err:
271         if (thumb)
272                 gdk_pixbuf_unref(thumb);
273         thumb = NULL;
274 out:
275         g_free(path);
276         g_free(thumb_path);
277         return thumb;
278 }
279
280 /* pathdup() stuff */
281
282 static char *pathdup(const char *path)
283 {
284         char real[MAXPATHLEN];
285
286         g_return_val_if_fail(path != NULL, NULL);
287
288         if (realpath(path, real))
289                 return g_strdup(real);
290
291         return g_strdup(path);
292 }
293
294 /*
295  * This code implements the MD5 message-digest algorithm.
296  * The algorithm is due to Ron Rivest. The original code was
297  * written by Colin Plumb in 1993, and put in the public domain.
298  * 
299  * Modified to use glib datatypes. Put under GPL to simplify
300  * licensing for ROX-Filer. Taken from Debian's dpkg package.
301  *
302  */
303
304 #define md5byte unsigned char
305
306 typedef struct _MD5Context MD5Context;
307
308 struct _MD5Context {
309         guint32 buf[4];
310         guint32 bytes[2];
311         guint32 in[16];
312 };
313
314 static void MD5Init(MD5Context *ctx);
315 static void MD5Update(MD5Context *ctx, md5byte const *buf, unsigned len);
316 static char *MD5Final(MD5Context *ctx);
317 static void MD5Transform(guint32 buf[4], guint32 const in[16]);
318
319 #if G_BYTE_ORDER == G_BIG_ENDIAN
320 static void byteSwap(guint32 *buf, unsigned words)
321 {
322         md5byte *p = (md5byte *)buf;
323
324         do {
325                 *buf++ = (guint32)((unsigned)p[3] << 8 | p[2]) << 16 |
326                         ((unsigned)p[1] << 8 | p[0]);
327                 p += 4;
328         } while (--words);
329 }
330 #else
331 #define byteSwap(buf,words)
332 #endif
333
334 /*
335  * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
336  * initialization constants.
337  */
338 static void MD5Init(MD5Context *ctx)
339 {
340         ctx->buf[0] = 0x67452301;
341         ctx->buf[1] = 0xefcdab89;
342         ctx->buf[2] = 0x98badcfe;
343         ctx->buf[3] = 0x10325476;
344
345         ctx->bytes[0] = 0;
346         ctx->bytes[1] = 0;
347 }
348
349 /*
350  * Update context to reflect the concatenation of another buffer full
351  * of bytes.
352  */
353 static void MD5Update(MD5Context *ctx, md5byte const *buf, unsigned len)
354 {
355         guint32 t;
356
357         /* Update byte count */
358
359         t = ctx->bytes[0];
360         if ((ctx->bytes[0] = t + len) < t)
361                 ctx->bytes[1]++;        /* Carry from low to high */
362
363         t = 64 - (t & 0x3f);    /* Space available in ctx->in (at least 1) */
364         if (t > len) {
365                 memcpy((md5byte *)ctx->in + 64 - t, buf, len);
366                 return;
367         }
368         /* First chunk is an odd size */
369         memcpy((md5byte *)ctx->in + 64 - t, buf, t);
370         byteSwap(ctx->in, 16);
371         MD5Transform(ctx->buf, ctx->in);
372         buf += t;
373         len -= t;
374
375         /* Process data in 64-byte chunks */
376         while (len >= 64) {
377                 memcpy(ctx->in, buf, 64);
378                 byteSwap(ctx->in, 16);
379                 MD5Transform(ctx->buf, ctx->in);
380                 buf += 64;
381                 len -= 64;
382         }
383
384         /* Handle any remaining bytes of data. */
385         memcpy(ctx->in, buf, len);
386 }
387
388 /*
389  * Final wrapup - pad to 64-byte boundary with the bit pattern 
390  * 1 0* (64-bit count of bits processed, MSB-first)
391  * Returns the newly allocated string of the hash.
392  */
393 static char *MD5Final(MD5Context *ctx)
394 {
395         char *retval;
396         int i;
397         int count = ctx->bytes[0] & 0x3f;       /* Number of bytes in ctx->in */
398         md5byte *p = (md5byte *)ctx->in + count;
399         guint8  *bytes;
400
401         /* Set the first char of padding to 0x80.  There is always room. */
402         *p++ = 0x80;
403
404         /* Bytes of padding needed to make 56 bytes (-8..55) */
405         count = 56 - 1 - count;
406
407         if (count < 0) {        /* Padding forces an extra block */
408                 memset(p, 0, count + 8);
409                 byteSwap(ctx->in, 16);
410                 MD5Transform(ctx->buf, ctx->in);
411                 p = (md5byte *)ctx->in;
412                 count = 56;
413         }
414         memset(p, 0, count);
415         byteSwap(ctx->in, 14);
416
417         /* Append length in bits and transform */
418         ctx->in[14] = ctx->bytes[0] << 3;
419         ctx->in[15] = ctx->bytes[1] << 3 | ctx->bytes[0] >> 29;
420         MD5Transform(ctx->buf, ctx->in);
421
422         byteSwap(ctx->buf, 4);
423
424         retval = g_malloc(33);
425         bytes = (guint8 *) ctx->buf;
426         for (i = 0; i < 16; i++)
427                 sprintf(retval + (i * 2), "%02x", bytes[i]);
428         retval[32] = '\0';
429         
430         return retval;
431 }
432
433 # ifndef ASM_MD5
434
435 /* The four core functions - F1 is optimized somewhat */
436
437 /* #define F1(x, y, z) (x & y | ~x & z) */
438 #define F1(x, y, z) (z ^ (x & (y ^ z)))
439 #define F2(x, y, z) F1(z, x, y)
440 #define F3(x, y, z) (x ^ y ^ z)
441 #define F4(x, y, z) (y ^ (x | ~z))
442
443 /* This is the central step in the MD5 algorithm. */
444 #define MD5STEP(f,w,x,y,z,in,s) \
445          (w += f(x,y,z) + in, w = (w<<s | w>>(32-s)) + x)
446
447 /*
448  * The core of the MD5 algorithm, this alters an existing MD5 hash to
449  * reflect the addition of 16 longwords of new data.  MD5Update blocks
450  * the data and converts bytes into longwords for this routine.
451  */
452 static void MD5Transform(guint32 buf[4], guint32 const in[16])
453 {
454         register guint32 a, b, c, d;
455
456         a = buf[0];
457         b = buf[1];
458         c = buf[2];
459         d = buf[3];
460
461         MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
462         MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
463         MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
464         MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
465         MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
466         MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
467         MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
468         MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
469         MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
470         MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
471         MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
472         MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
473         MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
474         MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
475         MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
476         MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
477
478         MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
479         MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
480         MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
481         MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
482         MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
483         MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
484         MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
485         MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
486         MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
487         MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
488         MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
489         MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
490         MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
491         MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
492         MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
493         MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
494
495         MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
496         MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
497         MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
498         MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
499         MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
500         MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
501         MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
502         MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
503         MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
504         MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
505         MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
506         MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
507         MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
508         MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
509         MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
510         MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
511
512         MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
513         MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
514         MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
515         MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
516         MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
517         MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
518         MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
519         MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
520         MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
521         MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
522         MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
523         MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
524         MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
525         MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
526         MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
527         MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
528
529         buf[0] += a;
530         buf[1] += b;
531         buf[2] += c;
532         buf[3] += d;
533 }
534
535 # endif /* ASM_MD5 */
536
537 static char *md5_hash(const char *message)
538 {
539         MD5Context ctx;
540
541         MD5Init(&ctx);
542         MD5Update(&ctx, message, strlen(message));
543         return MD5Final(&ctx);
544 }