r1190: Moved CPPFLAGS to pixmaps rule to avoid getting it twice.
[rox-filer.git] / ROX-Filer / src / pixmaps.c
blob894b231d1ab99308362f0ec165c719e89941c9d5
1 /*
2 * $Id$
4 * ROX-Filer, filer for the ROX desktop project
5 * Copyright (C) 2002, the ROX-Filer team.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 * Place, Suite 330, Boston, MA 02111-1307 USA
22 /* pixmaps.c - code for handling pixmaps */
24 #include "config.h"
25 #define PIXMAPS_C
27 /* Remove pixmaps from the cache when they haven't been accessed for
28 * this period of time (seconds).
31 #define PIXMAP_PURGE_TIME 1200
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <errno.h>
36 #include <fcntl.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
40 #include <gtk/gtk.h>
41 #include <gdk-pixbuf/gdk-pixbuf.h>
42 #include <gdk-pixbuf/gdk-pixbuf-loader.h>
44 #ifdef THUMBS_USE_LIBPNG
45 # include <stdarg.h>
46 # include <png.h>
47 # ifndef png_jmpbuf
48 # define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf)
49 # endif
50 #endif
52 #include "global.h"
54 #include "fscache.h"
55 #include "support.h"
56 #include "gui_support.h"
57 #include "pixmaps.h"
58 #include "main.h"
59 #include "filer.h"
60 #include "dir.h"
62 GFSCache *pixmap_cache = NULL;
64 static const char * bad_xpm[] = {
65 "12 12 3 1",
66 " c #000000000000",
67 ". c #FFFF00000000",
68 "x c #FFFFFFFFFFFF",
69 " ",
70 " ..xxxxxx.. ",
71 " ...xxxx... ",
72 " x...xx...x ",
73 " xx......xx ",
74 " xxx....xxx ",
75 " xxx....xxx ",
76 " xx......xx ",
77 " x...xx...x ",
78 " ...xxxx... ",
79 " ..xxxxxx.. ",
80 " "};
82 MaskedPixmap *im_error;
83 MaskedPixmap *im_unknown;
84 MaskedPixmap *im_symlink;
86 MaskedPixmap *im_unmounted;
87 MaskedPixmap *im_mounted;
88 MaskedPixmap *im_multiple;
89 MaskedPixmap *im_appdir;
91 MaskedPixmap *im_help;
92 MaskedPixmap *im_dirs;
94 /* Static prototypes */
96 static void load_default_pixmaps(void);
97 static MaskedPixmap *load(char *pathname, gpointer data);
98 static void ref(MaskedPixmap *mp, gpointer data);
99 static void unref(MaskedPixmap *mp, gpointer data);
100 static int getref(MaskedPixmap *mp);
101 static gint purge(gpointer data);
102 static MaskedPixmap *image_from_file(char *path);
103 static MaskedPixmap *get_bad_image(void);
104 static GdkPixbuf *scale_pixbuf(GdkPixbuf *src, int max_w, int max_h);
105 static GdkPixbuf *scale_pixbuf_up(GdkPixbuf *src, int max_w, int max_h);
106 static void got_thumb_data(GdkPixbufLoader *loader,
107 gint fd, GdkInputCondition cond);
108 static GdkPixbuf *get_thumbnail_for(char *path);
111 /****************************************************************
112 * EXTERNAL INTERFACE *
113 ****************************************************************/
115 void pixmaps_init(void)
117 #ifndef GTK2
118 gdk_rgb_init();
119 gtk_widget_push_visual(gdk_rgb_get_visual());
120 #endif
121 gtk_widget_push_colormap(gdk_rgb_get_cmap());
123 pixmap_cache = g_fscache_new((GFSLoadFunc) load,
124 (GFSRefFunc) ref,
125 (GFSRefFunc) unref,
126 (GFSGetRefFunc) getref,
127 NULL, /* Update func */
128 NULL);
130 gtk_timeout_add(10000, purge, NULL);
132 load_default_pixmaps();
135 /* 'name' is relative to app_dir. Always returns with a valid image. */
136 MaskedPixmap *load_pixmap(char *name)
138 MaskedPixmap *retval;
140 retval = image_from_file(make_path(app_dir, name)->str);
141 if (!retval)
142 retval = get_bad_image();
143 return retval;
146 /* Load all the standard pixmaps */
147 static void load_default_pixmaps(void)
149 im_error = load_pixmap("pixmaps/error.xpm");
150 im_unknown = load_pixmap("pixmaps/unknown.xpm");
151 im_symlink = load_pixmap("pixmaps/symlink.xpm");
153 im_unmounted = load_pixmap("pixmaps/mount.xpm");
154 im_mounted = load_pixmap("pixmaps/mounted.xpm");
155 im_multiple = load_pixmap("pixmaps/multiple.xpm");
156 im_appdir = load_pixmap("pixmaps/application.xpm");
158 im_help = load_pixmap("pixmaps/help.xpm");
159 im_dirs = load_pixmap("pixmaps/dirs.xpm");
162 void pixmap_ref(MaskedPixmap *mp)
164 ref(mp, NULL);
167 void pixmap_unref(MaskedPixmap *mp)
169 unref(mp, NULL);
172 void pixmap_make_huge(MaskedPixmap *mp)
174 GdkPixbuf *hg;
176 if (mp->huge_pixmap)
177 return;
179 g_return_if_fail(mp->huge_pixbuf != NULL);
181 hg = scale_pixbuf_up(mp->huge_pixbuf,
182 HUGE_WIDTH * 0.7,
183 HUGE_HEIGHT * 0.7);
185 if (hg)
187 gdk_pixbuf_render_pixmap_and_mask(hg,
188 &mp->huge_pixmap,
189 &mp->huge_mask,
190 128);
191 mp->huge_width = gdk_pixbuf_get_width(hg);
192 mp->huge_height = gdk_pixbuf_get_height(hg);
193 gdk_pixbuf_unref(hg);
196 if (!mp->huge_pixmap)
198 gdk_pixmap_ref(mp->pixmap);
199 if (mp->mask)
200 gdk_bitmap_ref(mp->mask);
201 mp->huge_pixmap = mp->pixmap;
202 mp->huge_mask = mp->mask;
203 mp->huge_width = mp->width;
204 mp->huge_height = mp->height;
208 void pixmap_make_small(MaskedPixmap *mp)
210 GdkPixbuf *sm;
212 if (mp->sm_pixmap)
213 return;
215 g_return_if_fail(mp->huge_pixbuf != NULL);
217 sm = scale_pixbuf(mp->huge_pixbuf, SMALL_WIDTH, SMALL_HEIGHT);
219 if (sm)
221 gdk_pixbuf_render_pixmap_and_mask(sm,
222 &mp->sm_pixmap,
223 &mp->sm_mask,
224 128);
225 mp->sm_width = gdk_pixbuf_get_width(sm);
226 mp->sm_height = gdk_pixbuf_get_height(sm);
227 gdk_pixbuf_unref(sm);
230 if (mp->sm_pixmap)
231 return;
233 gdk_pixmap_ref(mp->pixmap);
234 if (mp->mask)
235 gdk_bitmap_ref(mp->mask);
236 mp->sm_pixmap = mp->pixmap;
237 mp->sm_mask = mp->mask;
238 mp->sm_width = mp->width;
239 mp->sm_height = mp->height;
242 #ifdef GTK2
243 # define GET_LOADER(key) (g_object_get_data(G_OBJECT(loader), key))
244 # define SET_LOADER(key, data) (g_object_set_data(G_OBJECT(loader), key, data))
245 # define SET_LOADER_FULL(key, data, free) \
246 (g_object_set_data_full(G_OBJECT(loader), key, data, free))
247 #else
248 # define GET_LOADER(key) (gtk_object_get_data(GTK_OBJECT(loader), key))
249 # define SET_LOADER(key, data) \
250 (gtk_object_set_data(GTK_OBJECT(loader), key, data))
251 # define SET_LOADER_FULL(key, data, free) \
252 (gtk_object_set_data_full(GTK_OBJECT(loader), key, data, free))
253 #endif
255 /* Load image 'path' in the background and insert into pixmap_cache.
256 * Call callback(data, path) when done (path is NULL => error).
257 * If the image is already uptodate, or being created already, calls the
258 * callback right away.
260 void pixmap_background_thumb(gchar *path, GFunc callback, gpointer data)
262 GdkPixbufLoader *loader;
263 int fd, tag;
264 gboolean found;
265 MaskedPixmap *image;
266 GdkPixbuf *pixbuf;
268 image = g_fscache_lookup_full(pixmap_cache, path,
269 FSCACHE_LOOKUP_ONLY_NEW, &found);
271 if (found)
273 /* Thumbnail is known, or being created */
274 callback(data, NULL);
275 return;
278 pixbuf = get_thumbnail_for(path);
279 if (pixbuf)
281 MaskedPixmap *image;
283 image = image_from_pixbuf(pixbuf);
284 gdk_pixbuf_unref(pixbuf);
285 g_fscache_insert(pixmap_cache, path, image, TRUE);
286 callback(data, path);
287 return;
290 /* Add an entry, set to NULL, so no-one else tries to load this
291 * image.
293 g_fscache_insert(pixmap_cache, path, NULL, TRUE);
295 fd = mc_open(path, O_RDONLY | O_NONBLOCK);
296 if (fd == -1)
298 callback(data, NULL);
299 return;
302 loader = gdk_pixbuf_loader_new();
304 SET_LOADER_FULL("thumb-path", g_strdup(path), g_free);
305 SET_LOADER("thumb-callback", callback);
306 SET_LOADER("thumb-callback-data", data);
308 tag = gdk_input_add(fd, GDK_INPUT_READ,
309 (GdkInputFunction) got_thumb_data, loader);
311 SET_LOADER("thumb-input-tag", GINT_TO_POINTER(tag));
314 /****************************************************************
315 * INTERNAL FUNCTIONS *
316 ****************************************************************/
318 #ifdef THUMBS_USE_LIBPNG
319 /* Do this in a separate function to avoid problems with longjmp and
320 * non-volatile variables.
322 static gboolean png_save(png_structp png_ptr, png_infop info_ptr,
323 FILE *file, GdkPixbuf *pixbuf, GPtrArray *textarr)
325 gboolean has_alpha;
326 int width;
327 int height;
328 int bit_depth;
329 int rowstride;
330 guchar *pixels;
332 /* An error during saving will cause a jump back to here */
333 if (setjmp(png_jmpbuf(png_ptr)))
334 return FALSE;
336 png_init_io(png_ptr, file);
338 pixels = gdk_pixbuf_get_pixels(pixbuf);
339 width = gdk_pixbuf_get_width(pixbuf);
340 height = gdk_pixbuf_get_height(pixbuf);
341 has_alpha = gdk_pixbuf_get_has_alpha(pixbuf);
342 bit_depth = gdk_pixbuf_get_bits_per_sample(pixbuf);
343 rowstride = gdk_pixbuf_get_rowstride(pixbuf);
345 png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth,
346 PNG_COLOR_TYPE_RGB_ALPHA,
347 PNG_INTERLACE_NONE,
348 PNG_COMPRESSION_TYPE_DEFAULT,
349 PNG_FILTER_TYPE_DEFAULT);
352 /* Write image info */
353 png_write_info(png_ptr, info_ptr);
355 /* Write the image data */
356 if (has_alpha)
358 int row;
360 for (row = 0; row < height; row++, pixels += rowstride)
361 png_write_row(png_ptr, (png_bytep) pixels);
363 else /* convert RGB to RGBA */
365 guchar *rowbuf;
366 int col;
367 int row;
369 rowbuf = g_malloc(width * 4);
370 for (row = 0; row < height; row++, pixels += rowstride)
372 guchar *src = pixels;
373 guchar *dst = rowbuf;
374 for (col = 0; col < width; col++)
376 *dst++ = *src++;
377 *dst++ = *src++;
378 *dst++ = *src++;
379 *dst++ = 255;
381 png_write_row(png_ptr, (png_bytep) rowbuf);
383 g_free(rowbuf);
386 png_write_end(png_ptr, info_ptr);
388 return TRUE;
391 /* Saves pixbuf to a PNG file (non-interlaced RGBA).
392 * The variable arguments are key-text pairs and must be NULL-terminated.
393 * NOTE: unlike with gdk_pixbuf_save, keys do NOT have to be prefixed
394 * with "tEXt::".
395 * Returns TRUE if the image was saved, FALSE otherwise.
397 static gboolean pixbuf_save_png(GdkPixbuf *pixbuf, char *filename, ...)
399 FILE *file = NULL;
400 png_structp png_ptr = NULL;
401 png_infop info_ptr = NULL;
402 GPtrArray *textarr = NULL;
403 gboolean retval = FALSE;
404 va_list ap;
405 char *string;
407 g_return_val_if_fail(pixbuf != NULL, FALSE);
408 g_return_val_if_fail(filename != NULL, FALSE);
409 g_return_val_if_fail(*filename != '\0', FALSE);
411 file = fopen(filename, "wb");
412 if (!file)
413 goto err;
415 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
416 NULL, NULL, NULL);
417 if (!png_ptr)
418 goto err;
420 info_ptr = png_create_info_struct(png_ptr);
421 if (!info_ptr)
422 goto err;
424 /* Get optional key-text pairs. */
425 textarr = g_ptr_array_new();
426 va_start(ap, filename);
427 string = va_arg(ap, char *);
428 while (string != NULL)
430 png_text *pngtext;
432 pngtext = g_new(png_text, 1);
433 pngtext->key = string;
434 pngtext->text = va_arg(ap, char *);
435 pngtext->compression = PNG_TEXT_COMPRESSION_NONE;
436 /* (lang and translated_keyword not needed for COMP_NONE) */
437 png_set_text(png_ptr, info_ptr, pngtext, 1);
438 /* png_set_text() does NOT copy data, so we can't free
439 * it now -- store the pointers, they'll be freed later.
441 g_ptr_array_add(textarr, pngtext);
443 string = va_arg(ap, char *);
445 va_end(ap);
447 retval = png_save(png_ptr, info_ptr, file, pixbuf, textarr);
448 err:
449 if (file)
450 fclose(file);
451 if (png_ptr)
453 if (info_ptr)
454 png_destroy_write_struct(&png_ptr, &info_ptr);
455 else
456 png_destroy_write_struct(&png_ptr, (png_infopp) NULL);
458 if (textarr)
459 g_ptr_array_free(textarr, TRUE);
461 if (!retval)
462 g_warning("Error saving thumbnail '%s'", filename);
464 return retval;
467 static void destroy_key_value_fn(gpointer key, gpointer value, gpointer data)
469 g_free(key);
470 g_free(value);
473 /* Frees the pixel array of a pixbuf and the hash table associated with it. */
474 static void pixbuf_destroy_fn(guchar *pixels, gpointer data)
476 if (data != NULL)
478 g_hash_table_foreach((GHashTable *) data,
479 destroy_key_value_fn, NULL);
480 g_hash_table_destroy((GHashTable *) data);
483 g_free(pixels);
486 static GdkPixbuf *png_load(png_structp png_ptr, png_infop info_ptr,
487 FILE *file, GHashTable *text_hash)
489 png_uint_32 width, height, rowbytes, row;
490 int bit_depth, color_type, interlace_type;
491 guchar *pixels;
492 guchar *rowp;
494 /* An error during loading will cause a jump back to here */
495 if (setjmp(png_jmpbuf(png_ptr)))
496 return FALSE;
498 png_init_io(png_ptr, file);
500 /* We've already read 8 bytes from the file */
501 png_set_sig_bytes(png_ptr, 8);
503 png_read_info(png_ptr, info_ptr);
505 png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth,
506 &color_type, &interlace_type, NULL, NULL);
508 if (interlace_type != PNG_INTERLACE_NONE || width > 256 || height > 256)
509 return FALSE;
511 /* Change paletted images to RGB */
512 if (color_type == PNG_COLOR_TYPE_PALETTE)
513 png_set_palette_to_rgb(png_ptr);
515 /* Transform grayscale images of less than 8 to 8 bits */
516 if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
517 png_set_gray_1_2_4_to_8(png_ptr);
519 /* Add alpha channel if there's transparency info in the file */
520 if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
521 png_set_tRNS_to_alpha(png_ptr);
523 /* 16-bit RGB to 8-bit RGB */
524 if (bit_depth == 16)
525 png_set_strip_16(png_ptr);
527 /* Convert RGB to RGBA with an opacity value of 255 */
528 if (bit_depth == 8 && color_type == PNG_COLOR_TYPE_RGB)
529 png_set_filler(png_ptr, 255, PNG_FILLER_AFTER);
531 /* Expand bit depths of 1, 2 and 4 to 8 */
532 if (bit_depth < 8)
533 png_set_packing(png_ptr);
535 /* Grayscale to RGB */
536 if (color_type == PNG_COLOR_TYPE_GRAY ||
537 color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
538 png_set_gray_to_rgb(png_ptr);
540 png_read_update_info(png_ptr, info_ptr);
541 rowbytes = png_get_rowbytes(png_ptr, info_ptr);
543 /* Read rows */
544 pixels = g_malloc(rowbytes * height);
545 for (row = 0, rowp = pixels; row < height; row++, rowp += rowbytes)
546 png_read_row(png_ptr, rowp, NULL);
548 png_read_end(png_ptr, NULL);
550 /* Get key-text pairs and store them for later use */
551 if (text_hash)
553 png_textp text_ptr;
554 int num_text;
556 png_get_text(png_ptr, info_ptr, &text_ptr, &num_text);
557 while (--num_text >= 0)
559 g_hash_table_insert(text_hash,
560 g_strdup(text_ptr[num_text].key),
561 g_strdup(text_ptr[num_text].text));
565 return gdk_pixbuf_new_from_data(pixels,
566 GDK_COLORSPACE_RGB, TRUE, 8,
567 (int) width, (int) height, (int) rowbytes,
568 pixbuf_destroy_fn, text_hash);
571 /* Creates a pixbuf from a PNG file and returns it. If 'text_hash' is not
572 * NULL, it will be set to point to a hash table storing the image comments
573 * (key-text pairs).
574 * This is a support function for the thumbnail standard, so it will refuse
575 * to load interlaced images or images larger than 256x256 pixels.
576 * The hash table will be automatically destroyed when you destroy the pixbuf.
578 static GdkPixbuf *pixbuf_new_from_png(char *filename, GHashTable **text_hash)
580 FILE *file = NULL;
581 png_structp png_ptr = NULL;
582 png_infop info_ptr = NULL;
583 GdkPixbuf *retval = NULL;
584 char header[8];
586 g_return_val_if_fail(filename != NULL, NULL);
587 g_return_val_if_fail(*filename != '\0', NULL);
589 file = fopen(filename, "rb");
590 if (!file)
591 return NULL;
593 /* Is it a PNG file? */
594 if (fread(header, 1, 8, file) != 8 || png_sig_cmp(header, 0, 8))
595 goto err;
597 png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
598 NULL, NULL, NULL);
599 if (!png_ptr)
600 goto err;
602 info_ptr = png_create_info_struct(png_ptr);
603 if (!info_ptr)
604 goto err;
606 *text_hash = g_hash_table_new(g_str_hash, g_str_equal);
608 retval = png_load(png_ptr, info_ptr, file, *text_hash);
609 err:
610 if (file)
611 fclose(file);
613 if (png_ptr)
615 if (info_ptr)
616 png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
617 else
618 png_destroy_read_struct(&png_ptr, NULL, NULL);
621 if (!retval)
622 g_warning("Error loading thumbnail '%s'", filename);
624 return retval;
626 #endif /* THUMBS_USE_LIBPNG */
628 #if defined(GTK2) || defined(THUMBS_USE_LIBPNG)
629 /* Create a thumbnail file for this image.
630 * XXX: Thumbnails should be deleted somewhere!
632 static void save_thumbnail(char *path, GdkPixbuf *full, MaskedPixmap *image)
634 struct stat info;
635 int original_width, original_height;
636 GString *to;
637 char *md5, *swidth, *sheight, *ssize, *smtime, *uri;
638 mode_t old_mask;
639 int name_len;
641 /* If the source image was very small, don't bother saving */
642 if (gdk_pixbuf_get_width(full) * gdk_pixbuf_get_height(full) <
643 (HUGE_WIDTH * HUGE_HEIGHT * 3))
644 return;
646 original_width = gdk_pixbuf_get_width(full);
647 original_height = gdk_pixbuf_get_height(full);
649 if (mc_stat(path, &info) != 0)
650 return;
652 swidth = g_strdup_printf("%d", original_width);
653 sheight = g_strdup_printf("%d", original_height);
654 ssize = g_strdup_printf("%" SIZE_FMT, info.st_size);
655 smtime = g_strdup_printf("%ld", info.st_mtime);
657 path = pathdup(path);
658 uri = g_strconcat("file://", path, NULL);
659 md5 = md5_hash(uri);
660 g_free(path);
662 to = g_string_new(home_dir);
663 g_string_append(to, "/.thumbnails");
664 mkdir(to->str, 0700);
665 g_string_append(to, "/normal/");
666 mkdir(to->str, 0700);
667 g_string_append(to, md5);
668 name_len = to->len + 4; /* Truncate to this length when renaming */
669 g_string_sprintfa(to, ".png.ROX-Filer-%ld", (long) getpid());
671 g_free(md5);
673 old_mask = umask(0077);
674 #ifdef GTK2
675 gdk_pixbuf_save(image->huge_pixbuf,
676 to->str,
677 "png",
678 NULL,
679 "tEXt::Thumb::Image::Width", swidth,
680 "tEXt::Thumb::Image::Height", sheight,
681 "tEXt::Thumb::Size", ssize,
682 "tEXt::Thumb::MTime", smtime,
683 "tEXt::Thumb::URI", uri,
684 "tEXt::Software", PROJECT,
685 NULL);
686 #else
687 pixbuf_save_png(image->huge_pixbuf,
688 to->str,
689 "Thumb::Image::Width", swidth,
690 "Thumb::Image::Height", sheight,
691 "Thumb::Size", ssize,
692 "Thumb::MTime", smtime,
693 "Thumb::URI", uri,
694 "Software", PROJECT,
695 NULL);
696 #endif
697 umask(old_mask);
699 /* We create the file ###.png.ROX-Filer-PID and rename it to avoid
700 * a race condition if two program create the same thumb at
701 * once.
704 gchar *final;
706 final = g_strndup(to->str, name_len);
707 if (rename(to->str, final))
708 g_warning("Failed to rename '%s' to '%s': %s",
709 to->str, final, g_strerror(errno));
710 g_free(final);
713 g_string_free(to, TRUE);
714 g_free(swidth);
715 g_free(sheight);
716 g_free(ssize);
717 g_free(smtime);
718 g_free(uri);
720 #endif /* GTK2 or THUMBS_USE_LIBPNG */
722 /* Check if we have an up-to-date thumbnail for this image.
723 * If so, return it. Otherwise, returns NULL.
725 static GdkPixbuf *get_thumbnail_for(char *path)
727 GdkPixbuf *thumb = NULL;
728 #if defined(GTK2) || defined(THUMBS_USE_LIBPNG)
729 char *thumb_path, *md5, *uri;
730 char *ssize, *smtime;
731 struct stat info;
732 #ifndef GTK2
733 GHashTable *text_hash;
734 #endif
736 path = pathdup(path);
737 uri = g_strconcat("file://", path, NULL);
738 md5 = md5_hash(uri);
739 g_free(uri);
741 thumb_path = g_strdup_printf("%s/.thumbnails/normal/%s.png",
742 home_dir, md5);
743 g_free(md5);
745 #ifdef GTK2
746 thumb = gdk_pixbuf_new_from_file(thumb_path, NULL);
747 #else
748 thumb = pixbuf_new_from_png(thumb_path, &text_hash);
749 #endif
750 if (!thumb)
751 goto err;
753 /* Note that these don't need freeing... */
754 #ifdef GTK2
755 ssize = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::Size");
756 #else
757 ssize = g_hash_table_lookup(text_hash, "Thumb::Size");
758 #endif
759 if (!ssize)
760 goto err;
762 #ifdef GTK2
763 smtime = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::MTime");
764 #else
765 smtime = g_hash_table_lookup(text_hash, "Thumb::MTime");
766 #endif
767 if (!smtime)
768 goto err;
770 if (mc_stat(path, &info) != 0)
771 goto err;
773 if (info.st_mtime != atol(smtime) || info.st_size != atol(ssize))
774 goto err;
776 goto out;
777 err:
778 if (thumb)
779 gdk_pixbuf_unref(thumb);
780 thumb = NULL;
781 out:
782 g_free(path);
783 g_free(thumb_path);
784 #endif /* GTK2 or THUMBS_USE_LIBPNG */
785 return thumb;
788 /* Load the image 'path' and return a pointer to the resulting
789 * MaskedPixmap. NULL on failure.
790 * Doesn't check for thumbnails (this is for small icons).
792 static MaskedPixmap *image_from_file(char *path)
794 GdkPixbuf *pixbuf;
795 MaskedPixmap *image;
796 #ifdef GTK2
797 GError *error = NULL;
799 pixbuf = gdk_pixbuf_new_from_file(path, &error);
800 if (!pixbuf)
802 g_print("%s\n", error ? error->message : _("Unknown error"));
803 g_error_free(error);
805 #else
806 pixbuf = gdk_pixbuf_new_from_file(path);
807 #endif
808 if (!pixbuf)
809 return NULL;
811 image = image_from_pixbuf(pixbuf);
813 gdk_pixbuf_unref(pixbuf);
815 return image;
818 /* Scale src down to fit in max_w, max_h and return the new pixbuf.
819 * If src is small enough, then ref it and return that.
821 static GdkPixbuf *scale_pixbuf(GdkPixbuf *src, int max_w, int max_h)
823 int w, h;
825 w = gdk_pixbuf_get_width(src);
826 h = gdk_pixbuf_get_height(src);
828 if (w <= max_w && h <= max_h)
830 gdk_pixbuf_ref(src);
831 return src;
833 else
835 float scale_x = ((float) w) / max_w;
836 float scale_y = ((float) h) / max_h;
837 float scale = MAX(scale_x, scale_y);
838 int dest_w = w / scale;
839 int dest_h = h / scale;
841 return gdk_pixbuf_scale_simple(src,
842 MAX(dest_w, 1),
843 MAX(dest_h, 1),
844 GDK_INTERP_BILINEAR);
848 /* Scale src up to fit in max_w, max_h and return the new pixbuf.
849 * If src is that size or bigger, then ref it and return that.
851 static GdkPixbuf *scale_pixbuf_up(GdkPixbuf *src, int max_w, int max_h)
853 int w, h;
855 w = gdk_pixbuf_get_width(src);
856 h = gdk_pixbuf_get_height(src);
858 if (w == 0 || h == 0 || (w >= max_w && h >= max_h))
860 gdk_pixbuf_ref(src);
861 return src;
863 else
865 float scale_x = max_w / ((float) w);
866 float scale_y = max_h / ((float) h);
867 float scale = MIN(scale_x, scale_y);
869 return gdk_pixbuf_scale_simple(src,
870 w * scale,
871 h * scale,
872 GDK_INTERP_BILINEAR);
876 /* Turn a full-size pixbuf into a MaskedPixmap */
877 MaskedPixmap *image_from_pixbuf(GdkPixbuf *full_size)
879 MaskedPixmap *mp;
880 GdkPixbuf *huge_pixbuf, *normal_pixbuf;
881 GdkPixmap *pixmap;
882 GdkBitmap *mask;
884 g_return_val_if_fail(full_size != NULL, NULL);
886 huge_pixbuf = scale_pixbuf(full_size, HUGE_WIDTH, HUGE_HEIGHT);
887 g_return_val_if_fail(huge_pixbuf != NULL, NULL);
889 normal_pixbuf = scale_pixbuf(huge_pixbuf, ICON_WIDTH, ICON_HEIGHT);
890 g_return_val_if_fail(normal_pixbuf != NULL, NULL);
892 gdk_pixbuf_render_pixmap_and_mask(normal_pixbuf, &pixmap, &mask, 128);
894 if (!pixmap)
896 gdk_pixbuf_unref(huge_pixbuf);
897 gdk_pixbuf_unref(normal_pixbuf);
898 return NULL;
901 mp = g_new(MaskedPixmap, 1);
902 mp->huge_pixbuf = huge_pixbuf;
903 mp->ref = 1;
905 mp->pixmap = pixmap;
906 mp->mask = mask;
907 mp->width = gdk_pixbuf_get_width(normal_pixbuf);
908 mp->height = gdk_pixbuf_get_height(normal_pixbuf);
910 gdk_pixbuf_unref(normal_pixbuf);
912 mp->huge_pixmap = NULL;
913 mp->huge_mask = NULL;
914 mp->huge_width = -1;
915 mp->huge_height = -1;
917 mp->sm_pixmap = NULL;
918 mp->sm_mask = NULL;
919 mp->sm_width = -1;
920 mp->sm_height = -1;
922 return mp;
925 /* Return a pointer to the (static) bad image. The ref counter will ensure
926 * that the image is never freed.
928 static MaskedPixmap *get_bad_image(void)
930 GdkPixbuf *bad;
931 MaskedPixmap *mp;
933 bad = gdk_pixbuf_new_from_xpm_data(bad_xpm);
934 mp = image_from_pixbuf(bad);
935 gdk_pixbuf_unref(bad);
937 return mp;
940 static MaskedPixmap *load(char *pathname, gpointer user_data)
942 return image_from_file(pathname);
945 static void ref(MaskedPixmap *mp, gpointer data)
947 /* printf("[ ref %p %d->%d ]\n", mp, mp->ref, mp->ref + 1); */
949 if (mp)
950 mp->ref++;
953 static void unref(MaskedPixmap *mp, gpointer data)
955 /* printf("[ unref %p %d->%d ]\n", mp, mp->ref, mp->ref - 1); */
957 if (mp && --mp->ref == 0)
959 if (mp->huge_pixbuf)
961 gdk_pixbuf_unref(mp->huge_pixbuf);
964 if (mp->huge_pixmap)
965 gdk_pixmap_unref(mp->huge_pixmap);
966 if (mp->huge_mask)
967 gdk_bitmap_unref(mp->huge_mask);
969 if (mp->pixmap)
970 gdk_pixmap_unref(mp->pixmap);
971 if (mp->mask)
972 gdk_bitmap_unref(mp->mask);
974 if (mp->sm_pixmap)
975 gdk_pixmap_unref(mp->sm_pixmap);
976 if (mp->sm_mask)
977 gdk_bitmap_unref(mp->sm_mask);
979 g_free(mp);
983 static int getref(MaskedPixmap *mp)
985 return mp ? mp->ref : 0;
988 /* Called now and then to clear out old pixmaps */
989 static gint purge(gpointer data)
991 g_fscache_purge(pixmap_cache, PIXMAP_PURGE_TIME);
993 return TRUE;
996 static void got_thumb_data(GdkPixbufLoader *loader,
997 gint fd, GdkInputCondition cond)
999 GFunc callback;
1000 gchar *path;
1001 gpointer cbdata;
1002 char data[4096];
1003 int got, tag;
1005 got = read(fd, data, sizeof(data));
1007 #ifdef GTK2
1008 if (got > 0 && gdk_pixbuf_loader_write(loader, data, got, NULL))
1009 return;
1010 #else
1011 if (got > 0 && gdk_pixbuf_loader_write(loader, data, got))
1012 return;
1013 #endif
1015 /* Some kind of error, or end-of-file */
1017 path = GET_LOADER("thumb-path");
1019 tag = GPOINTER_TO_INT(GET_LOADER("thumb-input-tag"));
1020 gdk_input_remove(tag);
1022 #ifdef GTK2
1023 gdk_pixbuf_loader_close(loader, NULL);
1024 #else
1025 gdk_pixbuf_loader_close(loader);
1026 #endif
1028 if (got == 0)
1030 MaskedPixmap *image;
1031 GdkPixbuf *pixbuf;
1033 pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1034 if (pixbuf)
1036 gdk_pixbuf_ref(pixbuf);
1037 image = image_from_pixbuf(pixbuf);
1039 g_fscache_insert(pixmap_cache, path, image, FALSE);
1040 #if defined(GTK2) || defined(THUMBS_USE_LIBPNG)
1041 save_thumbnail(path, pixbuf, image);
1042 #endif
1043 gdk_pixbuf_unref(pixbuf);
1044 pixmap_unref(image);
1046 else
1047 got = -1;
1050 mc_close(fd);
1052 callback = GET_LOADER("thumb-callback");
1053 cbdata = GET_LOADER("thumb-callback-data");
1055 callback(cbdata, got != 0 ? NULL : path);
1057 #ifdef GTK2
1058 g_object_unref(G_OBJECT(loader));
1059 #else
1060 gtk_object_unref(GTK_OBJECT(loader));
1061 #endif