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)
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
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 */
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 /* Image files smaller than this are loaded in the main process.
34 * Larger images are sent to a sub-process.
35 * If this is too small, then looking inside ~/.thumbnails will
36 * cause nasty effects ;-)
38 #define SMALL_IMAGE_THRESHOLD 50000
44 #include <sys/types.h>
53 #include "gui_support.h"
59 GFSCache
*pixmap_cache
= NULL
;
61 static const char * bad_xpm
[] = {
79 MaskedPixmap
*im_error
;
80 MaskedPixmap
*im_unknown
;
81 MaskedPixmap
*im_symlink
;
83 MaskedPixmap
*im_unmounted
;
84 MaskedPixmap
*im_mounted
;
85 MaskedPixmap
*im_multiple
;
86 MaskedPixmap
*im_appdir
;
88 MaskedPixmap
*im_help
;
89 MaskedPixmap
*im_dirs
;
91 typedef struct _ChildThumbnail ChildThumbnail
;
93 /* There is one of these for each active child process */
94 struct _ChildThumbnail
{
100 /* Static prototypes */
102 static void load_default_pixmaps(void);
103 static gint
purge(gpointer data
);
104 static MaskedPixmap
*image_from_file(const char *path
);
105 static MaskedPixmap
*get_bad_image(void);
106 static GdkPixbuf
*scale_pixbuf(GdkPixbuf
*src
, int max_w
, int max_h
);
107 static GdkPixbuf
*scale_pixbuf_up(GdkPixbuf
*src
, int max_w
, int max_h
);
108 static GdkPixbuf
*get_thumbnail_for(const char *path
);
109 static void thumbnail_child_done(ChildThumbnail
*info
);
110 static void child_create_thumbnail(const gchar
*path
);
111 static GdkPixbuf
*create_spotlight_pixbuf(GdkPixbuf
*src
);
113 /****************************************************************
114 * EXTERNAL INTERFACE *
115 ****************************************************************/
117 void pixmaps_init(void)
119 gtk_widget_push_colormap(gdk_rgb_get_colormap());
121 pixmap_cache
= g_fscache_new((GFSLoadFunc
) image_from_file
, NULL
, NULL
);
123 gtk_timeout_add(10000, purge
, NULL
);
125 load_default_pixmaps();
128 /* 'name' is relative to app_dir. Always returns with a valid image. */
129 MaskedPixmap
*load_pixmap(const char *name
)
131 MaskedPixmap
*retval
;
133 retval
= image_from_file(make_path(app_dir
, name
)->str
);
135 retval
= get_bad_image();
139 /* Load all the standard pixmaps */
140 static void load_default_pixmaps(void)
142 im_error
= load_pixmap("images/error.png");
143 im_unknown
= load_pixmap("images/unknown.png");
144 im_symlink
= load_pixmap("images/symlink.png");
146 im_unmounted
= load_pixmap("images/mount.png");
147 im_mounted
= load_pixmap("images/mounted.png");
148 im_multiple
= load_pixmap("images/multiple.png");
149 im_appdir
= load_pixmap("images/application.png");
151 im_help
= load_pixmap("images/help.png");
152 im_dirs
= load_pixmap("images/dirs.png");
155 void pixmap_make_huge(MaskedPixmap
*mp
)
160 g_return_if_fail(mp
->src_pixbuf
!= NULL
);
162 mp
->huge_pixbuf
= scale_pixbuf_up(mp
->src_pixbuf
,
166 if (!mp
->huge_pixbuf
)
168 mp
->huge_pixbuf
= mp
->src_pixbuf
;
169 g_object_ref(mp
->huge_pixbuf
);
172 mp
->huge_pixbuf_lit
= create_spotlight_pixbuf(mp
->huge_pixbuf
);
173 mp
->huge_width
= gdk_pixbuf_get_width(mp
->huge_pixbuf
);
174 mp
->huge_height
= gdk_pixbuf_get_height(mp
->huge_pixbuf
);
177 void pixmap_make_small(MaskedPixmap
*mp
)
182 g_return_if_fail(mp
->src_pixbuf
!= NULL
);
184 mp
->sm_pixbuf
= scale_pixbuf(mp
->src_pixbuf
, SMALL_WIDTH
, SMALL_HEIGHT
);
188 mp
->sm_pixbuf
= mp
->src_pixbuf
;
189 g_object_ref(mp
->sm_pixbuf
);
192 mp
->sm_width
= gdk_pixbuf_get_width(mp
->sm_pixbuf
);
193 mp
->sm_height
= gdk_pixbuf_get_height(mp
->sm_pixbuf
);
196 /* Load image 'path' in the background and insert into pixmap_cache.
197 * Call callback(data, path) when done (path is NULL => error).
198 * If the image is already uptodate, or being created already, calls the
199 * callback right away.
201 void pixmap_background_thumb(const gchar
*path
, GFunc callback
, gpointer data
)
207 ChildThumbnail
*info
;
209 image
= g_fscache_lookup_full(pixmap_cache
, path
,
210 FSCACHE_LOOKUP_ONLY_NEW
, &found
);
214 /* Thumbnail is known, or being created */
215 g_object_unref(image
);
216 callback(data
, NULL
);
220 g_return_if_fail(image
== NULL
);
222 pixbuf
= get_thumbnail_for(path
);
228 /* If the image is small, load it now */
229 if (mc_stat(path
, &finfo
) != 0)
231 callback(data
, NULL
);
235 if (finfo
.st_size
< SMALL_IMAGE_THRESHOLD
)
237 pixbuf
= gdk_pixbuf_new_from_file(path
, NULL
);
240 g_fscache_insert(pixmap_cache
,
242 callback(data
, NULL
);
252 image
= masked_pixmap_new(pixbuf
);
253 gdk_pixbuf_unref(pixbuf
);
254 g_fscache_insert(pixmap_cache
, path
, image
, TRUE
);
255 callback(data
, (gchar
*) path
);
259 /* Add an entry, set to NULL, so no-one else tries to load this
262 g_fscache_insert(pixmap_cache
, path
, NULL
, TRUE
);
268 delayed_error("fork(): %s", g_strerror(errno
));
269 callback(data
, NULL
);
275 /* We are the child process */
276 child_create_thumbnail(path
);
280 info
= g_new(ChildThumbnail
, 1);
281 info
->path
= g_strdup(path
);
282 info
->callback
= callback
;
284 on_child_death(child
, (CallbackFn
) thumbnail_child_done
, info
);
287 /****************************************************************
288 * INTERNAL FUNCTIONS *
289 ****************************************************************/
291 /* Create a thumbnail file for this image.
292 * XXX: Thumbnails should be deleted somewhere!
294 static void save_thumbnail(const char *pathname
, GdkPixbuf
*full
)
298 int original_width
, original_height
;
300 char *md5
, *swidth
, *sheight
, *ssize
, *smtime
, *uri
;
305 thumb
= scale_pixbuf(full
, HUGE_WIDTH
, HUGE_HEIGHT
);
307 original_width
= gdk_pixbuf_get_width(full
);
308 original_height
= gdk_pixbuf_get_height(full
);
310 if (mc_stat(pathname
, &info
) != 0)
313 swidth
= g_strdup_printf("%d", original_width
);
314 sheight
= g_strdup_printf("%d", original_height
);
315 ssize
= g_strdup_printf("%" SIZE_FMT
, info
.st_size
);
316 smtime
= g_strdup_printf("%ld", info
.st_mtime
);
318 path
= pathdup(pathname
);
319 uri
= g_strconcat("file://", path
, NULL
);
323 to
= g_string_new(home_dir
);
324 g_string_append(to
, "/.thumbnails");
325 mkdir(to
->str
, 0700);
326 g_string_append(to
, "/normal/");
327 mkdir(to
->str
, 0700);
328 g_string_append(to
, md5
);
329 name_len
= to
->len
+ 4; /* Truncate to this length when renaming */
330 g_string_sprintfa(to
, ".png.ROX-Filer-%ld", (long) getpid());
334 old_mask
= umask(0077);
335 gdk_pixbuf_save(thumb
, to
->str
, "png", NULL
,
336 "tEXt::Thumb::Image::Width", swidth
,
337 "tEXt::Thumb::Image::Height", sheight
,
338 "tEXt::Thumb::Size", ssize
,
339 "tEXt::Thumb::MTime", smtime
,
340 "tEXt::Thumb::URI", uri
,
341 "tEXt::Software", PROJECT
,
345 /* We create the file ###.png.ROX-Filer-PID and rename it to avoid
346 * a race condition if two programs create the same thumb at
352 final
= g_strndup(to
->str
, name_len
);
353 if (rename(to
->str
, final
))
354 g_warning("Failed to rename '%s' to '%s': %s",
355 to
->str
, final
, g_strerror(errno
));
359 g_string_free(to
, TRUE
);
367 /* Called in a subprocess. Load path and create the thumbnail
368 * file. Parent will notice when we die.
370 static void child_create_thumbnail(const gchar
*path
)
374 image
= gdk_pixbuf_new_from_file(path
, NULL
);
377 save_thumbnail(path
, image
);
379 /* (no need to unref, as we're about to exit) */
382 /* Called when the child process exits */
383 static void thumbnail_child_done(ChildThumbnail
*info
)
387 thumb
= get_thumbnail_for(info
->path
);
393 image
= masked_pixmap_new(thumb
);
394 g_object_unref(thumb
);
396 g_fscache_insert(pixmap_cache
, info
->path
, image
, FALSE
);
397 g_object_unref(image
);
399 info
->callback(info
->data
, info
->path
);
402 info
->callback(info
->data
, NULL
);
408 /* Check if we have an up-to-date thumbnail for this image.
409 * If so, return it. Otherwise, returns NULL.
411 static GdkPixbuf
*get_thumbnail_for(const char *pathname
)
413 GdkPixbuf
*thumb
= NULL
;
414 char *thumb_path
, *md5
, *uri
, *path
;
415 const char *ssize
, *smtime
;
418 path
= pathdup(pathname
);
419 uri
= g_strconcat("file://", path
, NULL
);
423 thumb_path
= g_strdup_printf("%s/.thumbnails/normal/%s.png",
427 thumb
= gdk_pixbuf_new_from_file(thumb_path
, NULL
);
431 /* Note that these don't need freeing... */
432 ssize
= gdk_pixbuf_get_option(thumb
, "tEXt::Thumb::Size");
436 smtime
= gdk_pixbuf_get_option(thumb
, "tEXt::Thumb::MTime");
440 if (mc_stat(path
, &info
) != 0)
443 if (info
.st_mtime
!= atol(smtime
) || info
.st_size
!= atol(ssize
))
449 gdk_pixbuf_unref(thumb
);
457 /* Load the image 'path' and return a pointer to the resulting
458 * MaskedPixmap. NULL on failure.
459 * Doesn't check for thumbnails (this is for small icons).
461 static MaskedPixmap
*image_from_file(const char *path
)
465 GError
*error
= NULL
;
467 pixbuf
= gdk_pixbuf_new_from_file(path
, &error
);
470 g_print("%s\n", error
? error
->message
: _("Unknown error"));
475 image
= masked_pixmap_new(pixbuf
);
477 gdk_pixbuf_unref(pixbuf
);
482 /* Scale src down to fit in max_w, max_h and return the new pixbuf.
483 * If src is small enough, then ref it and return that.
485 static GdkPixbuf
*scale_pixbuf(GdkPixbuf
*src
, int max_w
, int max_h
)
489 w
= gdk_pixbuf_get_width(src
);
490 h
= gdk_pixbuf_get_height(src
);
492 if (w
<= max_w
&& h
<= max_h
)
499 float scale_x
= ((float) w
) / max_w
;
500 float scale_y
= ((float) h
) / max_h
;
501 float scale
= MAX(scale_x
, scale_y
);
502 int dest_w
= w
/ scale
;
503 int dest_h
= h
/ scale
;
505 return gdk_pixbuf_scale_simple(src
,
508 GDK_INTERP_BILINEAR
);
512 /* Scale src up to fit in max_w, max_h and return the new pixbuf.
513 * If src is that size or bigger, then ref it and return that.
515 static GdkPixbuf
*scale_pixbuf_up(GdkPixbuf
*src
, int max_w
, int max_h
)
519 w
= gdk_pixbuf_get_width(src
);
520 h
= gdk_pixbuf_get_height(src
);
522 if (w
== 0 || h
== 0 || (w
>= max_w
&& h
>= max_h
))
529 float scale_x
= max_w
/ ((float) w
);
530 float scale_y
= max_h
/ ((float) h
);
531 float scale
= MIN(scale_x
, scale_y
);
533 return gdk_pixbuf_scale_simple(src
,
536 GDK_INTERP_BILINEAR
);
540 /* Return a pointer to the (static) bad image. The ref counter will ensure
541 * that the image is never freed.
543 static MaskedPixmap
*get_bad_image(void)
548 bad
= gdk_pixbuf_new_from_xpm_data(bad_xpm
);
549 mp
= masked_pixmap_new(bad
);
550 gdk_pixbuf_unref(bad
);
555 /* Called now and then to clear out old pixmaps */
556 static gint
purge(gpointer data
)
558 g_fscache_purge(pixmap_cache
, PIXMAP_PURGE_TIME
);
563 static gpointer parent_class
;
565 static void masked_pixmap_finialize(GObject
*object
)
567 MaskedPixmap
*mp
= (MaskedPixmap
*) object
;
571 g_object_unref(mp
->src_pixbuf
);
572 mp
->src_pixbuf
= NULL
;
577 g_object_unref(mp
->huge_pixbuf
);
578 mp
->huge_pixbuf
= NULL
;
580 if (mp
->huge_pixbuf_lit
)
582 g_object_unref(mp
->huge_pixbuf_lit
);
583 mp
->huge_pixbuf_lit
= NULL
;
588 g_object_unref(mp
->pixbuf
);
593 g_object_unref(mp
->pixmap
);
598 g_object_unref(mp
->mask
);
604 g_object_unref(mp
->sm_pixbuf
);
605 mp
->sm_pixbuf
= NULL
;
608 G_OBJECT_CLASS(parent_class
)->finalize(object
);
611 static void masked_pixmap_class_init(gpointer gclass
, gpointer data
)
613 GObjectClass
*object
= (GObjectClass
*) gclass
;
615 parent_class
= g_type_class_peek_parent(gclass
);
617 object
->finalize
= masked_pixmap_finialize
;
620 static void masked_pixmap_init(GTypeInstance
*object
, gpointer gclass
)
622 MaskedPixmap
*mp
= (MaskedPixmap
*) object
;
624 mp
->src_pixbuf
= NULL
;
626 mp
->huge_pixbuf
= NULL
;
627 mp
->huge_pixbuf_lit
= NULL
;
629 mp
->huge_height
= -1;
637 mp
->sm_pixbuf
= NULL
;
642 static GType
masked_pixmap_get_type(void)
644 static GType type
= 0;
648 static const GTypeInfo info
=
650 sizeof (MaskedPixmapClass
),
651 NULL
, /* base_init */
652 NULL
, /* base_finalise */
653 masked_pixmap_class_init
,
654 NULL
, /* class_finalise */
655 NULL
, /* class_data */
656 sizeof(MaskedPixmap
),
661 type
= g_type_register_static(G_TYPE_OBJECT
, "MaskedPixmap",
668 MaskedPixmap
*masked_pixmap_new(GdkPixbuf
*full_size
)
671 GdkPixbuf
*src_pixbuf
, *normal_pixbuf
;
675 g_return_val_if_fail(full_size
!= NULL
, NULL
);
677 src_pixbuf
= scale_pixbuf(full_size
, HUGE_WIDTH
, HUGE_HEIGHT
);
678 g_return_val_if_fail(src_pixbuf
!= NULL
, NULL
);
680 normal_pixbuf
= scale_pixbuf(src_pixbuf
, ICON_WIDTH
, ICON_HEIGHT
);
681 g_return_val_if_fail(normal_pixbuf
!= NULL
, NULL
);
683 gdk_pixbuf_render_pixmap_and_mask(normal_pixbuf
, &pixmap
, &mask
, 128);
687 gdk_pixbuf_unref(src_pixbuf
);
688 gdk_pixbuf_unref(normal_pixbuf
);
692 mp
= g_object_new(masked_pixmap_get_type(), NULL
);
694 mp
->src_pixbuf
= src_pixbuf
;
696 mp
->pixbuf
= normal_pixbuf
;
699 mp
->width
= gdk_pixbuf_get_width(normal_pixbuf
);
700 mp
->height
= gdk_pixbuf_get_height(normal_pixbuf
);
705 static guchar
lighten_component(guchar cur_value
)
707 int new_value
= cur_value
;
709 new_value
+= 48 + (new_value
>> 3);
713 return (guchar
) new_value
;
716 /* Stolen from eel */
717 static GdkPixbuf
*create_spotlight_pixbuf(GdkPixbuf
*src
)
721 int width
, height
, has_alpha
, src_row_stride
, dst_row_stride
;
722 guchar
*target_pixels
, *original_pixels
;
723 guchar
*pixsrc
, *pixdest
;
726 n_channels
= gdk_pixbuf_get_n_channels(src
);
727 has_alpha
= gdk_pixbuf_get_has_alpha(src
);
728 width
= gdk_pixbuf_get_width(src
);
729 height
= gdk_pixbuf_get_height(src
);
731 g_return_val_if_fail(gdk_pixbuf_get_colorspace(src
) ==
732 GDK_COLORSPACE_RGB
, NULL
);
733 g_return_val_if_fail((!has_alpha
&& n_channels
== 3) ||
734 (has_alpha
&& n_channels
== 4), NULL
);
735 g_return_val_if_fail(gdk_pixbuf_get_bits_per_sample(src
) == 8, NULL
);
737 dest
= gdk_pixbuf_new(gdk_pixbuf_get_colorspace(src
), has_alpha
,
738 gdk_pixbuf_get_bits_per_sample(src
),
741 dst_row_stride
= gdk_pixbuf_get_rowstride(dest
);
742 src_row_stride
= gdk_pixbuf_get_rowstride(src
);
743 target_pixels
= gdk_pixbuf_get_pixels(dest
);
744 original_pixels
= gdk_pixbuf_get_pixels(src
);
746 for (i
= 0; i
< height
; i
++)
748 pixdest
= target_pixels
+ i
* dst_row_stride
;
749 pixsrc
= original_pixels
+ i
* src_row_stride
;
750 for (j
= 0; j
< width
; j
++)
752 *pixdest
++ = (*pixsrc
++);
753 *pixdest
++ = (*pixsrc
++);
754 *pixdest
++ = lighten_component(*pixsrc
++);
756 *pixdest
++ = *pixsrc
++;