r1107: Added a configure check for iconv.h.
[rox-filer.git] / ROX-Filer / src / pixmaps.c
blobef45b561a9e3ed5b0b681acddd4aa277a00fe9bb
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);
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);
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, "/96x96/");
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/96x96/%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.
791 static MaskedPixmap *image_from_file(char *path)
793 GdkPixbuf *pixbuf;
794 MaskedPixmap *image;
795 #ifdef GTK2
796 GError *error = NULL;
798 pixbuf = get_thumbnail_for(path);
799 if (!pixbuf)
800 pixbuf = gdk_pixbuf_new_from_file(path, &error);
801 if (!pixbuf)
803 g_print("%s\n", error ? error->message : _("Unknown error"));
804 g_error_free(error);
806 #elif defined(THUMBS_USE_LIBPNG)
807 pixbuf = get_thumbnail_for(path);
808 if (!pixbuf)
809 pixbuf = gdk_pixbuf_new_from_file(path);
810 #else
811 pixbuf = gdk_pixbuf_new_from_file(path);
812 #endif
813 if (!pixbuf)
814 return NULL;
816 image = image_from_pixbuf(pixbuf);
818 gdk_pixbuf_unref(pixbuf);
820 return image;
823 /* Scale src down to fit in max_w, max_h and return the new pixbuf.
824 * If src is small enough, then ref it and return that.
826 static GdkPixbuf *scale_pixbuf(GdkPixbuf *src, int max_w, int max_h)
828 int w, h;
830 w = gdk_pixbuf_get_width(src);
831 h = gdk_pixbuf_get_height(src);
833 if (w <= max_w && h <= max_h)
835 gdk_pixbuf_ref(src);
836 return src;
838 else
840 float scale_x = ((float) w) / max_w;
841 float scale_y = ((float) h) / max_h;
842 float scale = MAX(scale_x, scale_y);
843 int dest_w = w / scale;
844 int dest_h = h / scale;
846 return gdk_pixbuf_scale_simple(src,
847 MAX(dest_w, 1),
848 MAX(dest_h, 1),
849 GDK_INTERP_BILINEAR);
853 /* Scale src up to fit in max_w, max_h and return the new pixbuf.
854 * If src is that size or bigger, then ref it and return that.
856 static GdkPixbuf *scale_pixbuf_up(GdkPixbuf *src, int max_w, int max_h)
858 int w, h;
860 w = gdk_pixbuf_get_width(src);
861 h = gdk_pixbuf_get_height(src);
863 if (w == 0 || h == 0 || (w >= max_w && h >= max_h))
865 gdk_pixbuf_ref(src);
866 return src;
868 else
870 float scale_x = max_w / ((float) w);
871 float scale_y = max_h / ((float) h);
872 float scale = MIN(scale_x, scale_y);
874 return gdk_pixbuf_scale_simple(src,
875 w * scale,
876 h * scale,
877 GDK_INTERP_BILINEAR);
881 /* Turn a full-size pixbuf into a MaskedPixmap */
882 MaskedPixmap *image_from_pixbuf(GdkPixbuf *full_size)
884 MaskedPixmap *mp;
885 GdkPixbuf *huge_pixbuf, *normal_pixbuf;
886 GdkPixmap *pixmap;
887 GdkBitmap *mask;
889 g_return_val_if_fail(full_size != NULL, NULL);
891 huge_pixbuf = scale_pixbuf(full_size, HUGE_WIDTH, HUGE_HEIGHT);
892 g_return_val_if_fail(huge_pixbuf != NULL, NULL);
894 normal_pixbuf = scale_pixbuf(huge_pixbuf, ICON_WIDTH, ICON_HEIGHT);
895 g_return_val_if_fail(normal_pixbuf != NULL, NULL);
897 gdk_pixbuf_render_pixmap_and_mask(normal_pixbuf, &pixmap, &mask, 128);
899 if (!pixmap)
901 gdk_pixbuf_unref(huge_pixbuf);
902 gdk_pixbuf_unref(normal_pixbuf);
903 return NULL;
906 mp = g_new(MaskedPixmap, 1);
907 mp->huge_pixbuf = huge_pixbuf;
908 mp->ref = 1;
910 mp->pixmap = pixmap;
911 mp->mask = mask;
912 mp->width = gdk_pixbuf_get_width(normal_pixbuf);
913 mp->height = gdk_pixbuf_get_height(normal_pixbuf);
915 gdk_pixbuf_unref(normal_pixbuf);
917 mp->huge_pixmap = NULL;
918 mp->huge_mask = NULL;
919 mp->huge_width = -1;
920 mp->huge_height = -1;
922 mp->sm_pixmap = NULL;
923 mp->sm_mask = NULL;
924 mp->sm_width = -1;
925 mp->sm_height = -1;
927 return mp;
930 /* Return a pointer to the (static) bad image. The ref counter will ensure
931 * that the image is never freed.
933 static MaskedPixmap *get_bad_image(void)
935 GdkPixbuf *bad;
936 MaskedPixmap *mp;
938 bad = gdk_pixbuf_new_from_xpm_data(bad_xpm);
939 mp = image_from_pixbuf(bad);
940 gdk_pixbuf_unref(bad);
942 return mp;
945 static MaskedPixmap *load(char *pathname, gpointer user_data)
947 return image_from_file(pathname);
950 static void ref(MaskedPixmap *mp, gpointer data)
952 /* printf("[ ref %p %d->%d ]\n", mp, mp->ref, mp->ref + 1); */
954 if (mp)
955 mp->ref++;
958 static void unref(MaskedPixmap *mp, gpointer data)
960 /* printf("[ unref %p %d->%d ]\n", mp, mp->ref, mp->ref - 1); */
962 if (mp && --mp->ref == 0)
964 if (mp->huge_pixbuf)
966 gdk_pixbuf_unref(mp->huge_pixbuf);
969 if (mp->huge_pixmap)
970 gdk_pixmap_unref(mp->huge_pixmap);
971 if (mp->huge_mask)
972 gdk_bitmap_unref(mp->huge_mask);
974 if (mp->pixmap)
975 gdk_pixmap_unref(mp->pixmap);
976 if (mp->mask)
977 gdk_bitmap_unref(mp->mask);
979 if (mp->sm_pixmap)
980 gdk_pixmap_unref(mp->sm_pixmap);
981 if (mp->sm_mask)
982 gdk_bitmap_unref(mp->sm_mask);
984 g_free(mp);
988 static int getref(MaskedPixmap *mp)
990 return mp ? mp->ref : 0;
993 /* Called now and then to clear out old pixmaps */
994 static gint purge(gpointer data)
996 g_fscache_purge(pixmap_cache, PIXMAP_PURGE_TIME);
998 return TRUE;
1001 static void got_thumb_data(GdkPixbufLoader *loader,
1002 gint fd, GdkInputCondition cond)
1004 GFunc callback;
1005 gchar *path;
1006 gpointer cbdata;
1007 char data[4096];
1008 int got, tag;
1010 got = read(fd, data, sizeof(data));
1012 #ifdef GTK2
1013 if (got > 0 && gdk_pixbuf_loader_write(loader, data, got, NULL))
1014 return;
1015 #else
1016 if (got > 0 && gdk_pixbuf_loader_write(loader, data, got))
1017 return;
1018 #endif
1020 /* Some kind of error, or end-of-file */
1022 path = GET_LOADER("thumb-path");
1024 tag = GPOINTER_TO_INT(GET_LOADER("thumb-input-tag"));
1025 gdk_input_remove(tag);
1027 #ifdef GTK2
1028 gdk_pixbuf_loader_close(loader, NULL);
1029 #else
1030 gdk_pixbuf_loader_close(loader);
1031 #endif
1033 if (got == 0)
1035 MaskedPixmap *image;
1036 GdkPixbuf *pixbuf;
1038 pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1039 if (pixbuf)
1041 gdk_pixbuf_ref(pixbuf);
1042 image = image_from_pixbuf(pixbuf);
1044 g_fscache_insert(pixmap_cache, path, image);
1045 #if defined(GTK2) || defined(THUMBS_USE_LIBPNG)
1046 save_thumbnail(path, pixbuf, image);
1047 #endif
1048 gdk_pixbuf_unref(pixbuf);
1049 pixmap_unref(image);
1051 else
1052 got = -1;
1055 mc_close(fd);
1057 callback = GET_LOADER("thumb-callback");
1058 cbdata = GET_LOADER("thumb-callback-data");
1060 callback(cbdata, got != 0 ? NULL : path);
1062 #ifdef GTK2
1063 g_object_unref(G_OBJECT(loader));
1064 #else
1065 gtk_object_unref(GTK_OBJECT(loader));
1066 #endif