13e1fb2dccc96fa62008e3dd8a18f3db6cdb65de
[geeqie.git] / src / thumb.c
1 /*
2  * GQview image viewer
3  * (C)1999 John Ellis
4  *
5  * Author: John Ellis
6  *
7  */
8
9 #include "gqview.h"
10 #include "icons/img_unknown.xpm" /* fixme! duplicate, included in image.c too */
11
12 #define THUMBNAIL_CACHE_DIR "/.gqview_thmb"
13
14 static guchar *load_xv_thumbnail(gchar *filename, gint *widthp, gint *heightp);
15 static void normalize_thumb(gint *width, gint *height);
16 static gint get_xv_thumbnail(gchar *thumb_filename, GdkPixmap **thumb_pixmap, GdkBitmap **thumb_mask);
17
18 /*
19  *-----------------------------------------------------------------------------
20  * thumbnail routines: creation, caching, and maintenance (public)
21  *-----------------------------------------------------------------------------
22  */
23
24 gint create_thumbnail(gchar *path, GdkPixmap **thumb_pixmap, GdkBitmap **thumb_mask)
25 {
26         gint width, height;
27         gint space;
28         GdkImlibImage *thumb = NULL;
29         GdkImlibImage *image = NULL;
30         gint cached = FALSE;
31
32         if (debug) printf("Gen thumbnail:%s\n",path);
33
34         /* if xvpics enabled, check for that first */
35         if (use_xvpics_thumbnails)
36                 {
37                 space = get_xv_thumbnail(path, thumb_pixmap, thumb_mask);
38                 if (space != -1)
39                         {
40                         if (debug) printf("XV thumbnail found, loaded\n");
41                         return space;
42                         }
43                 }
44
45         /* start load from cache */
46
47         if (enable_thumb_caching)
48                 {
49                 gchar *cache_path;
50                 cache_path = g_strconcat(homedir(), THUMBNAIL_CACHE_DIR, path, ".png", NULL);
51
52                 if (isfile(cache_path) && filetime(cache_path) >= filetime(path))
53                         {
54                         if (debug) printf("Found in cache:%s\n", path);
55                         image = gdk_imlib_load_image(cache_path);
56                         if (image && image->rgb_width != thumb_max_width && image->rgb_height != thumb_max_height)
57                                 {
58                                 if (debug) printf("Thumbnail size may have changed, reloading:%s\n", path);
59                                 unlink(cache_path);
60                                 gdk_imlib_destroy_image(image);
61                                 image = gdk_imlib_load_image(path);
62                                 }
63                         else
64                                 {
65                                 cached = TRUE;
66                                 }
67                         }
68                 else
69                         image = gdk_imlib_load_image(path);
70                 
71                 }
72         else
73                 image = gdk_imlib_load_image(path);
74
75         if (!image)
76                 {
77                 image = gdk_imlib_create_image_from_xpm_data((gchar **)img_unknown_xpm);
78                 cached = TRUE; /* no need to save a thumbnail of the unknown pixmap */
79                 }
80
81         if (image)
82                 {
83                 if (image->rgb_width > thumb_max_width || image->rgb_height > thumb_max_height)
84                         {
85                         if (((float)thumb_max_width / image->rgb_width) < ((float)thumb_max_height / image->rgb_height))
86                                 {
87                                 width = thumb_max_width;
88                                 height = (float)width / image->rgb_width * image->rgb_height;
89                                 if (height < 1) height = 1;
90                                 }
91                         else
92                                 {
93                                 height = thumb_max_height;
94                                 width = (float)height / image->rgb_height * image->rgb_width;
95                                 if (width < 1) width = 1;
96                                 }
97                         }
98                 else
99                         {
100                         width = image->rgb_width;
101                         height = image->rgb_height;
102                         cached = TRUE; /* don't cache images smaller than thumbnail size */
103                         }
104                 if (*thumb_pixmap) gdk_imlib_free_pixmap(*thumb_pixmap);
105                 *thumb_pixmap = NULL;
106                 *thumb_mask = NULL;
107
108         /* start save cache */
109
110                 if (enable_thumb_caching && !cached)
111                         {
112                         gchar *thumb_path;
113                         gchar *base_dir;
114                         gchar *thumb_dir;
115                         gchar *image_dir;
116
117                         /* attempt at speed-up? move this here */
118                         thumb = gdk_imlib_clone_scaled_image(image, width, height);
119                         gdk_imlib_destroy_image(image);
120                         image = NULL;
121
122                         base_dir = g_strconcat(homedir(), THUMBNAIL_CACHE_DIR, NULL);
123                         if (!isdir(base_dir))
124                                 {
125                                 if (debug) printf("creating thumbnail dir:%s\n", base_dir);
126                                 if (mkdir(base_dir, 0755) < 0)
127                                         printf(_("create dir failed: %s\n"), base_dir);
128                                 }
129
130                         image_dir = remove_level_from_path(path);
131                         thumb_dir = g_strconcat(base_dir, image_dir, NULL);
132                         g_free(image_dir);
133                         if (!isdir(thumb_dir))
134                                 {
135                                 gchar *p = thumb_dir;
136                                 while (p[0] != '\0')
137                                         {
138                                         p++;
139                                         if (p[0] == '/' || p[0] == '\0')
140                                                 {
141                                                 gint end = TRUE;
142                                                 if (p[0] != '\0')
143                                                         {
144                                                         p[0] = '\0';
145                                                         end = FALSE;
146                                                         }
147                                                 if (!isdir(thumb_dir))
148                                                         {
149                                                         if (debug) printf("creating sub dir:%s\n",thumb_dir);
150                                                         if (mkdir(thumb_dir, 0755) < 0)
151                                                                 printf(_("create dir failed: %s\n"), thumb_dir);
152                                                         }
153                                                 if (!end) p[0] = '/';
154                                                 }
155                                         }
156                                 }
157                         g_free(thumb_dir);
158
159                         thumb_path = g_strconcat(base_dir, path, ".png", NULL);
160                         if (debug) printf("Saving thumb: %s\n",thumb_path);
161
162                         gdk_imlib_save_image(thumb, thumb_path, NULL);
163
164                         g_free(base_dir);
165                         g_free(thumb_path);
166                         }
167                 else
168                         {
169                         thumb = image;
170                         }
171
172         /* end save cache */
173
174                 gdk_imlib_render(thumb, width, height);
175                 *thumb_pixmap = gdk_imlib_move_image(thumb);
176                 *thumb_mask = gdk_imlib_move_mask(thumb);
177                 if (*thumb_pixmap)
178                         space = thumb_max_width - width;
179                 gdk_imlib_destroy_image(thumb);
180                 thumb = NULL;
181                 }
182         else
183                 {
184                 space = -1;
185                 }
186         return space;
187 }
188
189 gint maintain_thumbnail_dir(gchar *dir, gint recursive)
190 {
191         gchar *thumb_dir;
192         gint base_length;
193         gint still_have_a_file = FALSE;
194
195         if (debug) printf("maintainance check: %s\n", dir);
196
197         base_length = strlen(homedir()) + strlen(THUMBNAIL_CACHE_DIR);
198         thumb_dir = g_strconcat(homedir(), THUMBNAIL_CACHE_DIR, dir, NULL);
199
200         if (isdir(thumb_dir))
201                 {
202                 DIR             *dp;
203                 struct dirent   *dirent;
204                 struct stat ent_sbuf;
205
206                 if((dp = opendir(thumb_dir))==NULL)
207                         {
208                                 /* dir not found */
209                                 g_free(thumb_dir);
210                                 return FALSE;
211                         }
212
213                 while ((dirent = readdir(dp)) != NULL)
214                         {
215                         /* skips removed files */
216                         if (dirent->d_ino > 0)
217                                 {
218                                 int l = 0;
219                                 gchar *path_buf;
220                                 if (strcmp(dirent->d_name, ".") == 0 || strcmp(dirent->d_name, "..") == 0)
221                                         continue;
222                                 path_buf = g_strconcat(thumb_dir, "/", dirent->d_name, NULL);
223                                 if (strlen(path_buf) > 4) l = strlen(path_buf) - 4;
224
225                                 if (stat(path_buf,&ent_sbuf) >= 0 && S_ISDIR(ent_sbuf.st_mode))
226                                         {
227                                         /* recurse dir then delete it */
228                                         gchar *rdir = g_strconcat(dir, "/", dirent->d_name, NULL);
229                                         if (recursive && !maintain_thumbnail_dir(rdir, TRUE))
230                                                 {
231                                                 if (debug) printf("Deleting thumb dir: %s\n",path_buf);
232                                                 if ( (rmdir (path_buf) < 0) )
233                                                         printf(_("Unable to delete dir: %s\n"), path_buf);
234                                                 }
235                                         else
236                                                 still_have_a_file = TRUE;
237                                         g_free(rdir);
238                                         }
239                                 else
240                                         {
241                                         gchar *fp = path_buf + l;
242                                         fp[0] = '\0';
243                                         if (strlen(path_buf) > base_length &&
244                                                         !isfile(path_buf + base_length))
245                                                 {
246                                                 fp[0] = '.';
247                                                 if (debug) printf("Deleting thumb: %s\n",path_buf);
248                                                 if ( (unlink (path_buf) < 0) )
249                                                         printf(_("failed to delete:%s\n"),path_buf);
250                                                 }
251                                         else
252                                                  still_have_a_file = TRUE;
253                                         }
254                                 g_free(path_buf);
255                                 }
256                         }
257                 closedir(dp);
258                 }
259         g_free(thumb_dir);
260         return still_have_a_file;
261 }
262
263 /*
264  *-----------------------------------------------------------------------------
265  * xvpics thumbnail support, read-only (private)
266  *-----------------------------------------------------------------------------
267  */
268
269 /*
270  * xvpics code originally supplied by:
271  * "Diederen Damien" <D.Diederen@student.ulg.ac.be>
272  *
273  * Note: Code has been modified to fit the style of the other code, and to use
274  *       a few more glib-isms.
275  */
276
277 #define XV_BUFFER 2048
278 static guchar *load_xv_thumbnail(gchar *filename, gint *widthp, gint *heightp)
279 {
280         FILE *file;
281         gchar buffer[XV_BUFFER];
282         guchar *data;
283         gint width, height, depth;
284
285         file = fopen(filename, "rt");
286         if(!file) return NULL;
287
288         fgets(buffer, XV_BUFFER, file);
289         if(strncmp(buffer, "P7 332", 6) != 0)
290                 {
291                 fclose(file);
292                 return NULL;
293                 }
294
295         while(fgets(buffer, XV_BUFFER, file) && buffer[0] == '#') /* do_nothing() */;
296
297         if(sscanf(buffer, "%d %d %d", &width, &height, &depth) != 3)
298                 {
299                 fclose(file);
300                 return NULL;
301                 }
302
303         data = g_new(guchar, width * height);
304         fread(data, 1, width * height, file);
305
306         fclose(file);
307         *widthp = width;
308         *heightp = height;
309         return data;
310 }
311 #undef XV_BUFFER
312
313 static void normalize_thumb(gint *width, gint *height)
314 {
315         if(*width > thumb_max_width || *height > thumb_max_height)
316                 {
317                 gfloat factor = MAX((gfloat) *width / thumb_max_width, (gfloat) *height / thumb_max_height);
318                 *width = (gfloat) *width / factor;
319                 *height = (gfloat) *height / factor;
320                 }
321 }
322
323 static gint get_xv_thumbnail(gchar *thumb_filename, GdkPixmap **thumb_pixmap, GdkBitmap **thumb_mask)
324 {
325         gint width, height;
326         gchar *thumb_name;
327         gchar *tmp_string;
328         gchar *last_slash;
329         guchar *packed_data;
330
331         tmp_string = g_strdup(thumb_filename);  
332         last_slash = strrchr(tmp_string, '/');
333         if(!last_slash) return -1;
334         *last_slash++ = '\0';
335
336         thumb_name = g_strconcat(tmp_string, "/.xvpics/", last_slash, NULL);
337         packed_data = load_xv_thumbnail(thumb_name, &width, &height);
338         g_free(tmp_string);
339         g_free(thumb_name);
340
341         if(packed_data)
342                 {
343                 guchar *rgb_data;
344                 GdkImlibImage *image;
345                 gint i;
346
347                 rgb_data = g_new(guchar, width * height * 3);
348                 for(i = 0; i < width * height; i++)
349                         {
350                         rgb_data[i * 3 + 0] = (packed_data[i] >> 5) * 36;
351                         rgb_data[i * 3 + 1] = ((packed_data[i] & 28) >> 2) * 36;
352                         rgb_data[i * 3 + 2] = (packed_data[i] & 3) * 85;
353                         }
354
355                 g_free(packed_data);
356                 image = gdk_imlib_create_image_from_data(rgb_data, NULL, width, height);
357                 g_free(rgb_data);
358                 normalize_thumb(&width, &height);
359                 gdk_imlib_render(image, width, height);
360         
361                 if(*thumb_pixmap) gdk_imlib_free_pixmap(*thumb_pixmap);
362
363                 *thumb_pixmap = gdk_imlib_move_image(image);
364                 *thumb_mask = gdk_imlib_move_mask(image);
365                 gdk_imlib_destroy_image(image);
366                 return thumb_max_width - width;
367                 }
368
369         return -1;
370 }
371