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 pixbufs (despite the name!) */
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_appdir
;
87 MaskedPixmap
*im_dirs
;
89 typedef struct _ChildThumbnail ChildThumbnail
;
91 /* There is one of these for each active child process */
92 struct _ChildThumbnail
{
98 /* Static prototypes */
100 static void load_default_pixmaps(void);
101 static gint
purge(gpointer data
);
102 static MaskedPixmap
*image_from_file(const 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 GdkPixbuf
*get_thumbnail_for(const char *path
);
107 static void thumbnail_child_done(ChildThumbnail
*info
);
108 static void child_create_thumbnail(const gchar
*path
);
109 static GdkPixbuf
*create_spotlight_pixbuf(GdkPixbuf
*src
, guint32 color
,
112 /****************************************************************
113 * EXTERNAL INTERFACE *
114 ****************************************************************/
116 void pixmaps_init(void)
118 gtk_widget_push_colormap(gdk_rgb_get_colormap());
120 pixmap_cache
= g_fscache_new((GFSLoadFunc
) image_from_file
, NULL
, NULL
);
122 gtk_timeout_add(10000, purge
, NULL
);
124 load_default_pixmaps();
127 /* Load image <appdir>/images/name.png.
128 * Always returns with a valid image.
130 MaskedPixmap
*load_pixmap(const char *name
)
133 MaskedPixmap
*retval
;
135 path
= g_strconcat(app_dir
, "/images/", name
, ".png", NULL
);
136 retval
= image_from_file(path
);
140 retval
= get_bad_image();
145 /* Create a MaskedPixmap from a GTK stock ID. Always returns
148 static MaskedPixmap
*mp_from_stock(const char *stock_id
)
150 GtkIconSet
*icon_set
;
152 MaskedPixmap
*retval
;
154 icon_set
= gtk_icon_factory_lookup_default(stock_id
);
156 return get_bad_image();
158 pixbuf
= gtk_icon_set_render_icon(icon_set
,
159 gtk_widget_get_default_style(), /* Gtk bug */
162 GTK_ICON_SIZE_DIALOG
,
165 retval
= masked_pixmap_new(pixbuf
);
166 gdk_pixbuf_unref(pixbuf
);
171 void pixmap_make_huge(MaskedPixmap
*mp
)
176 g_return_if_fail(mp
->src_pixbuf
!= NULL
);
178 mp
->huge_pixbuf
= scale_pixbuf_up(mp
->src_pixbuf
,
182 if (!mp
->huge_pixbuf
)
184 mp
->huge_pixbuf
= mp
->src_pixbuf
;
185 g_object_ref(mp
->huge_pixbuf
);
188 mp
->huge_pixbuf_lit
= create_spotlight_pixbuf(mp
->huge_pixbuf
,
190 mp
->huge_width
= gdk_pixbuf_get_width(mp
->huge_pixbuf
);
191 mp
->huge_height
= gdk_pixbuf_get_height(mp
->huge_pixbuf
);
194 void pixmap_make_small(MaskedPixmap
*mp
)
199 g_return_if_fail(mp
->src_pixbuf
!= NULL
);
201 mp
->sm_pixbuf
= scale_pixbuf(mp
->src_pixbuf
, SMALL_WIDTH
, SMALL_HEIGHT
);
205 mp
->sm_pixbuf
= mp
->src_pixbuf
;
206 g_object_ref(mp
->sm_pixbuf
);
209 mp
->sm_pixbuf_lit
= create_spotlight_pixbuf(mp
->sm_pixbuf
,
212 mp
->sm_width
= gdk_pixbuf_get_width(mp
->sm_pixbuf
);
213 mp
->sm_height
= gdk_pixbuf_get_height(mp
->sm_pixbuf
);
216 /* Load image 'path' in the background and insert into pixmap_cache.
217 * Call callback(data, path) when done (path is NULL => error).
218 * If the image is already uptodate, or being created already, calls the
219 * callback right away.
221 void pixmap_background_thumb(const gchar
*path
, GFunc callback
, gpointer data
)
227 ChildThumbnail
*info
;
229 image
= g_fscache_lookup_full(pixmap_cache
, path
,
230 FSCACHE_LOOKUP_ONLY_NEW
, &found
);
234 /* Thumbnail is known, or being created */
235 g_object_unref(image
);
236 callback(data
, NULL
);
240 g_return_if_fail(image
== NULL
);
242 pixbuf
= get_thumbnail_for(path
);
248 /* If the image is small, load it now */
249 if (mc_stat(path
, &finfo
) != 0)
251 callback(data
, NULL
);
255 if (finfo
.st_size
< SMALL_IMAGE_THRESHOLD
)
257 pixbuf
= gdk_pixbuf_new_from_file(path
, NULL
);
260 g_fscache_insert(pixmap_cache
,
262 callback(data
, NULL
);
272 image
= masked_pixmap_new(pixbuf
);
273 gdk_pixbuf_unref(pixbuf
);
274 g_fscache_insert(pixmap_cache
, path
, image
, TRUE
);
275 callback(data
, (gchar
*) path
);
279 /* Add an entry, set to NULL, so no-one else tries to load this
282 g_fscache_insert(pixmap_cache
, path
, NULL
, TRUE
);
288 delayed_error("fork(): %s", g_strerror(errno
));
289 callback(data
, NULL
);
295 /* We are the child process */
296 child_create_thumbnail(path
);
300 info
= g_new(ChildThumbnail
, 1);
301 info
->path
= g_strdup(path
);
302 info
->callback
= callback
;
304 on_child_death(child
, (CallbackFn
) thumbnail_child_done
, info
);
307 /****************************************************************
308 * INTERNAL FUNCTIONS *
309 ****************************************************************/
311 /* Create a thumbnail file for this image.
312 * XXX: Thumbnails should be deleted somewhere!
314 static void save_thumbnail(const char *pathname
, GdkPixbuf
*full
)
318 int original_width
, original_height
;
320 char *md5
, *swidth
, *sheight
, *ssize
, *smtime
, *uri
;
325 thumb
= scale_pixbuf(full
, HUGE_WIDTH
, HUGE_HEIGHT
);
327 original_width
= gdk_pixbuf_get_width(full
);
328 original_height
= gdk_pixbuf_get_height(full
);
330 if (mc_stat(pathname
, &info
) != 0)
333 swidth
= g_strdup_printf("%d", original_width
);
334 sheight
= g_strdup_printf("%d", original_height
);
335 ssize
= g_strdup_printf("%" SIZE_FMT
, info
.st_size
);
336 smtime
= g_strdup_printf("%ld", info
.st_mtime
);
338 path
= pathdup(pathname
);
339 uri
= g_strconcat("file://", path
, NULL
);
343 to
= g_string_new(home_dir
);
344 g_string_append(to
, "/.thumbnails");
345 mkdir(to
->str
, 0700);
346 g_string_append(to
, "/normal/");
347 mkdir(to
->str
, 0700);
348 g_string_append(to
, md5
);
349 name_len
= to
->len
+ 4; /* Truncate to this length when renaming */
350 g_string_sprintfa(to
, ".png.ROX-Filer-%ld", (long) getpid());
354 old_mask
= umask(0077);
355 gdk_pixbuf_save(thumb
, to
->str
, "png", NULL
,
356 "tEXt::Thumb::Image::Width", swidth
,
357 "tEXt::Thumb::Image::Height", sheight
,
358 "tEXt::Thumb::Size", ssize
,
359 "tEXt::Thumb::MTime", smtime
,
360 "tEXt::Thumb::URI", uri
,
361 "tEXt::Software", PROJECT
,
365 /* We create the file ###.png.ROX-Filer-PID and rename it to avoid
366 * a race condition if two programs create the same thumb at
372 final
= g_strndup(to
->str
, name_len
);
373 if (rename(to
->str
, final
))
374 g_warning("Failed to rename '%s' to '%s': %s",
375 to
->str
, final
, g_strerror(errno
));
379 g_string_free(to
, TRUE
);
387 /* Called in a subprocess. Load path and create the thumbnail
388 * file. Parent will notice when we die.
390 static void child_create_thumbnail(const gchar
*path
)
394 image
= gdk_pixbuf_new_from_file(path
, NULL
);
397 save_thumbnail(path
, image
);
399 /* (no need to unref, as we're about to exit) */
402 /* Called when the child process exits */
403 static void thumbnail_child_done(ChildThumbnail
*info
)
407 thumb
= get_thumbnail_for(info
->path
);
413 image
= masked_pixmap_new(thumb
);
414 g_object_unref(thumb
);
416 g_fscache_insert(pixmap_cache
, info
->path
, image
, FALSE
);
417 g_object_unref(image
);
419 info
->callback(info
->data
, info
->path
);
422 info
->callback(info
->data
, NULL
);
428 /* Check if we have an up-to-date thumbnail for this image.
429 * If so, return it. Otherwise, returns NULL.
431 static GdkPixbuf
*get_thumbnail_for(const char *pathname
)
433 GdkPixbuf
*thumb
= NULL
;
434 char *thumb_path
, *md5
, *uri
, *path
;
435 const char *ssize
, *smtime
;
438 path
= pathdup(pathname
);
439 uri
= g_strconcat("file://", path
, NULL
);
443 thumb_path
= g_strdup_printf("%s/.thumbnails/normal/%s.png",
447 thumb
= gdk_pixbuf_new_from_file(thumb_path
, NULL
);
451 /* Note that these don't need freeing... */
452 ssize
= gdk_pixbuf_get_option(thumb
, "tEXt::Thumb::Size");
456 smtime
= gdk_pixbuf_get_option(thumb
, "tEXt::Thumb::MTime");
460 if (mc_stat(path
, &info
) != 0)
463 if (info
.st_mtime
!= atol(smtime
) || info
.st_size
!= atol(ssize
))
469 gdk_pixbuf_unref(thumb
);
477 /* Load the image 'path' and return a pointer to the resulting
478 * MaskedPixmap. NULL on failure.
479 * Doesn't check for thumbnails (this is for small icons).
481 static MaskedPixmap
*image_from_file(const char *path
)
485 GError
*error
= NULL
;
487 pixbuf
= gdk_pixbuf_new_from_file(path
, &error
);
490 g_print("%s\n", error
? error
->message
: _("Unknown error"));
495 image
= masked_pixmap_new(pixbuf
);
497 gdk_pixbuf_unref(pixbuf
);
502 /* Scale src down to fit in max_w, max_h and return the new pixbuf.
503 * If src is small enough, then ref it and return that.
505 static GdkPixbuf
*scale_pixbuf(GdkPixbuf
*src
, int max_w
, int max_h
)
509 w
= gdk_pixbuf_get_width(src
);
510 h
= gdk_pixbuf_get_height(src
);
512 if (w
<= max_w
&& h
<= max_h
)
519 float scale_x
= ((float) w
) / max_w
;
520 float scale_y
= ((float) h
) / max_h
;
521 float scale
= MAX(scale_x
, scale_y
);
522 int dest_w
= w
/ scale
;
523 int dest_h
= h
/ scale
;
525 return gdk_pixbuf_scale_simple(src
,
528 GDK_INTERP_BILINEAR
);
532 /* Scale src up to fit in max_w, max_h and return the new pixbuf.
533 * If src is that size or bigger, then ref it and return that.
535 static GdkPixbuf
*scale_pixbuf_up(GdkPixbuf
*src
, int max_w
, int max_h
)
539 w
= gdk_pixbuf_get_width(src
);
540 h
= gdk_pixbuf_get_height(src
);
542 if (w
== 0 || h
== 0 || (w
>= max_w
&& h
>= max_h
))
549 float scale_x
= max_w
/ ((float) w
);
550 float scale_y
= max_h
/ ((float) h
);
551 float scale
= MIN(scale_x
, scale_y
);
553 return gdk_pixbuf_scale_simple(src
,
556 GDK_INTERP_BILINEAR
);
560 /* Return a pointer to the (static) bad image. The ref counter will ensure
561 * that the image is never freed.
563 static MaskedPixmap
*get_bad_image(void)
568 bad
= gdk_pixbuf_new_from_xpm_data(bad_xpm
);
569 mp
= masked_pixmap_new(bad
);
570 gdk_pixbuf_unref(bad
);
575 /* Called now and then to clear out old pixmaps */
576 static gint
purge(gpointer data
)
578 g_fscache_purge(pixmap_cache
, PIXMAP_PURGE_TIME
);
583 static gpointer parent_class
;
585 static void masked_pixmap_finialize(GObject
*object
)
587 MaskedPixmap
*mp
= (MaskedPixmap
*) object
;
591 g_object_unref(mp
->src_pixbuf
);
592 mp
->src_pixbuf
= NULL
;
597 g_object_unref(mp
->huge_pixbuf
);
598 mp
->huge_pixbuf
= NULL
;
600 if (mp
->huge_pixbuf_lit
)
602 g_object_unref(mp
->huge_pixbuf_lit
);
603 mp
->huge_pixbuf_lit
= NULL
;
608 g_object_unref(mp
->pixbuf
);
613 g_object_unref(mp
->pixbuf_lit
);
614 mp
->pixbuf_lit
= NULL
;
619 g_object_unref(mp
->sm_pixbuf
);
620 mp
->sm_pixbuf
= NULL
;
622 if (mp
->sm_pixbuf_lit
)
624 g_object_unref(mp
->sm_pixbuf_lit
);
625 mp
->sm_pixbuf_lit
= NULL
;
628 G_OBJECT_CLASS(parent_class
)->finalize(object
);
631 static void masked_pixmap_class_init(gpointer gclass
, gpointer data
)
633 GObjectClass
*object
= (GObjectClass
*) gclass
;
635 parent_class
= g_type_class_peek_parent(gclass
);
637 object
->finalize
= masked_pixmap_finialize
;
640 static void masked_pixmap_init(GTypeInstance
*object
, gpointer gclass
)
642 MaskedPixmap
*mp
= (MaskedPixmap
*) object
;
644 mp
->src_pixbuf
= NULL
;
646 mp
->huge_pixbuf
= NULL
;
647 mp
->huge_pixbuf_lit
= NULL
;
649 mp
->huge_height
= -1;
652 mp
->pixbuf_lit
= NULL
;
656 mp
->sm_pixbuf
= NULL
;
657 mp
->sm_pixbuf_lit
= NULL
;
662 static GType
masked_pixmap_get_type(void)
664 static GType type
= 0;
668 static const GTypeInfo info
=
670 sizeof (MaskedPixmapClass
),
671 NULL
, /* base_init */
672 NULL
, /* base_finalise */
673 masked_pixmap_class_init
,
674 NULL
, /* class_finalise */
675 NULL
, /* class_data */
676 sizeof(MaskedPixmap
),
681 type
= g_type_register_static(G_TYPE_OBJECT
, "MaskedPixmap",
688 MaskedPixmap
*masked_pixmap_new(GdkPixbuf
*full_size
)
691 GdkPixbuf
*src_pixbuf
, *normal_pixbuf
;
693 g_return_val_if_fail(full_size
!= NULL
, NULL
);
695 src_pixbuf
= scale_pixbuf(full_size
, HUGE_WIDTH
, HUGE_HEIGHT
);
696 g_return_val_if_fail(src_pixbuf
!= NULL
, NULL
);
698 normal_pixbuf
= scale_pixbuf(src_pixbuf
, ICON_WIDTH
, ICON_HEIGHT
);
699 g_return_val_if_fail(normal_pixbuf
!= NULL
, NULL
);
701 mp
= g_object_new(masked_pixmap_get_type(), NULL
);
703 mp
->src_pixbuf
= src_pixbuf
;
705 mp
->pixbuf
= normal_pixbuf
;
706 mp
->pixbuf_lit
= create_spotlight_pixbuf(normal_pixbuf
, 0x000099, 128);
707 mp
->width
= gdk_pixbuf_get_width(normal_pixbuf
);
708 mp
->height
= gdk_pixbuf_get_height(normal_pixbuf
);
713 /* Stolen from eel...and modified to colourize the pixbuf.
714 * 'alpha' is the transparency of 'color' (0xRRGGBB):
715 * 0 = fully opaque, 255 = fully transparent.
717 static GdkPixbuf
*create_spotlight_pixbuf(GdkPixbuf
*src
,
723 int width
, height
, has_alpha
, src_row_stride
, dst_row_stride
;
724 guchar
*target_pixels
, *original_pixels
;
725 guchar
*pixsrc
, *pixdest
;
729 n_channels
= gdk_pixbuf_get_n_channels(src
);
730 has_alpha
= gdk_pixbuf_get_has_alpha(src
);
731 width
= gdk_pixbuf_get_width(src
);
732 height
= gdk_pixbuf_get_height(src
);
734 g_return_val_if_fail(gdk_pixbuf_get_colorspace(src
) ==
735 GDK_COLORSPACE_RGB
, NULL
);
736 g_return_val_if_fail((!has_alpha
&& n_channels
== 3) ||
737 (has_alpha
&& n_channels
== 4), NULL
);
738 g_return_val_if_fail(gdk_pixbuf_get_bits_per_sample(src
) == 8, NULL
);
740 dest
= gdk_pixbuf_new(gdk_pixbuf_get_colorspace(src
), has_alpha
,
741 gdk_pixbuf_get_bits_per_sample(src
),
744 dst_row_stride
= gdk_pixbuf_get_rowstride(dest
);
745 src_row_stride
= gdk_pixbuf_get_rowstride(src
);
746 target_pixels
= gdk_pixbuf_get_pixels(dest
);
747 original_pixels
= gdk_pixbuf_get_pixels(src
);
749 r
= (color
& 0xff0000) >> 16;
750 g
= (color
& 0xff00) >> 8;
753 for (i
= 0; i
< height
; i
++)
757 pixdest
= target_pixels
+ i
* dst_row_stride
;
758 pixsrc
= original_pixels
+ i
* src_row_stride
;
759 for (j
= 0; j
< width
; j
++)
761 tmp
= (*pixsrc
++ * alpha
+ r
* (255 - alpha
)) / 255;
762 *pixdest
++ = (guchar
) MIN(255, tmp
);
763 tmp
= (*pixsrc
++ * alpha
+ g
* (255 - alpha
)) / 255;
764 *pixdest
++ = (guchar
) MIN(255, tmp
);
765 tmp
= (*pixsrc
++ * alpha
+ b
* (255 - alpha
)) / 255;
766 *pixdest
++ = (guchar
) MIN(255, tmp
);
768 *pixdest
++ = *pixsrc
++;
775 /* Load all the standard pixmaps. Also sets the default window icon. */
776 static void load_default_pixmaps(void)
779 GError
*error
= NULL
;
781 im_error
= mp_from_stock(GTK_STOCK_DIALOG_WARNING
);
782 im_unknown
= mp_from_stock(GTK_STOCK_DIALOG_QUESTION
);
783 im_symlink
= load_pixmap("symlink");
785 im_unmounted
= load_pixmap("mount");
786 im_mounted
= load_pixmap("mounted");
787 im_appdir
= load_pixmap("application");
789 im_dirs
= load_pixmap("dirs");
791 pixbuf
= gdk_pixbuf_new_from_file(
792 make_path(app_dir
, ".DirIcon")->str
, &error
);
797 icon_list
= g_list_append(NULL
, pixbuf
);
798 gtk_window_set_default_icon_list(icon_list
);
799 g_list_free(icon_list
);
801 g_object_unref(G_OBJECT(pixbuf
));
805 g_print("%s\n", error
? error
->message
: _("Unknown error"));