r1428: Alpha-blending for Small icons, too.
[rox-filer.git] / ROX-Filer / src / pixmaps.c
blobb60ab705f09dec57b09b0b6332cd47aa3fa5adf9
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 /* 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
40 #include <stdlib.h>
41 #include <stdio.h>
42 #include <errno.h>
43 #include <fcntl.h>
44 #include <sys/types.h>
45 #include <sys/stat.h>
47 #include <gtk/gtk.h>
49 #include "global.h"
51 #include "fscache.h"
52 #include "support.h"
53 #include "gui_support.h"
54 #include "pixmaps.h"
55 #include "main.h"
56 #include "filer.h"
57 #include "dir.h"
59 GFSCache *pixmap_cache = NULL;
61 static const char * bad_xpm[] = {
62 "12 12 3 1",
63 " c #000000000000",
64 ". c #FFFF00000000",
65 "x c #FFFFFFFFFFFF",
66 " ",
67 " ..xxxxxx.. ",
68 " ...xxxx... ",
69 " x...xx...x ",
70 " xx......xx ",
71 " xxx....xxx ",
72 " xxx....xxx ",
73 " xx......xx ",
74 " x...xx...x ",
75 " ...xxxx... ",
76 " ..xxxxxx.. ",
77 " "};
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 {
95 gchar *path;
96 GFunc callback;
97 gpointer data;
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);
134 if (!retval)
135 retval = get_bad_image();
136 return retval;
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)
157 if (mp->huge_pixbuf)
158 return;
160 g_return_if_fail(mp->src_pixbuf != NULL);
162 mp->huge_pixbuf = scale_pixbuf_up(mp->src_pixbuf,
163 HUGE_WIDTH * 0.7,
164 HUGE_HEIGHT * 0.7);
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)
179 if (mp->sm_pixbuf)
180 return;
182 g_return_if_fail(mp->src_pixbuf != NULL);
184 mp->sm_pixbuf = scale_pixbuf(mp->src_pixbuf, SMALL_WIDTH, SMALL_HEIGHT);
186 if (!mp->sm_pixbuf)
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)
203 gboolean found;
204 MaskedPixmap *image;
205 GdkPixbuf *pixbuf;
206 pid_t child;
207 ChildThumbnail *info;
209 image = g_fscache_lookup_full(pixmap_cache, path,
210 FSCACHE_LOOKUP_ONLY_NEW, &found);
212 if (found)
214 /* Thumbnail is known, or being created */
215 g_object_unref(image);
216 callback(data, NULL);
217 return;
220 g_return_if_fail(image == NULL);
222 pixbuf = get_thumbnail_for(path);
224 if (!pixbuf)
226 struct stat finfo;
228 /* If the image is small, load it now */
229 if (mc_stat(path, &finfo) != 0)
231 callback(data, NULL);
232 return;
235 if (finfo.st_size < SMALL_IMAGE_THRESHOLD)
237 pixbuf = gdk_pixbuf_new_from_file(path, NULL);
238 if (!pixbuf)
240 g_fscache_insert(pixmap_cache,
241 path, NULL, TRUE);
242 callback(data, NULL);
243 return;
248 if (pixbuf)
250 MaskedPixmap *image;
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);
256 return;
259 /* Add an entry, set to NULL, so no-one else tries to load this
260 * image.
262 g_fscache_insert(pixmap_cache, path, NULL, TRUE);
264 child = fork();
266 if (child == -1)
268 delayed_error("fork(): %s", g_strerror(errno));
269 callback(data, NULL);
270 return;
273 if (child == 0)
275 /* We are the child process */
276 child_create_thumbnail(path);
277 _exit(0);
280 info = g_new(ChildThumbnail, 1);
281 info->path = g_strdup(path);
282 info->callback = callback;
283 info->data = data;
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)
296 struct stat info;
297 gchar *path;
298 int original_width, original_height;
299 GString *to;
300 char *md5, *swidth, *sheight, *ssize, *smtime, *uri;
301 mode_t old_mask;
302 int name_len;
303 GdkPixbuf *thumb;
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)
311 return;
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);
320 md5 = md5_hash(uri);
321 g_free(path);
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());
332 g_free(md5);
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,
342 NULL);
343 umask(old_mask);
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
347 * once.
350 gchar *final;
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));
356 g_free(final);
359 g_string_free(to, TRUE);
360 g_free(swidth);
361 g_free(sheight);
362 g_free(ssize);
363 g_free(smtime);
364 g_free(uri);
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)
372 GdkPixbuf *image;
374 image = gdk_pixbuf_new_from_file(path, NULL);
376 if (image)
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)
385 GdkPixbuf *thumb;
387 thumb = get_thumbnail_for(info->path);
389 if (thumb)
391 MaskedPixmap *image;
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);
401 else
402 info->callback(info->data, NULL);
404 g_free(info->path);
405 g_free(info);
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;
416 struct stat info;
418 path = pathdup(pathname);
419 uri = g_strconcat("file://", path, NULL);
420 md5 = md5_hash(uri);
421 g_free(uri);
423 thumb_path = g_strdup_printf("%s/.thumbnails/normal/%s.png",
424 home_dir, md5);
425 g_free(md5);
427 thumb = gdk_pixbuf_new_from_file(thumb_path, NULL);
428 if (!thumb)
429 goto err;
431 /* Note that these don't need freeing... */
432 ssize = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::Size");
433 if (!ssize)
434 goto err;
436 smtime = gdk_pixbuf_get_option(thumb, "tEXt::Thumb::MTime");
437 if (!smtime)
438 goto err;
440 if (mc_stat(path, &info) != 0)
441 goto err;
443 if (info.st_mtime != atol(smtime) || info.st_size != atol(ssize))
444 goto err;
446 goto out;
447 err:
448 if (thumb)
449 gdk_pixbuf_unref(thumb);
450 thumb = NULL;
451 out:
452 g_free(path);
453 g_free(thumb_path);
454 return 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)
463 GdkPixbuf *pixbuf;
464 MaskedPixmap *image;
465 GError *error = NULL;
467 pixbuf = gdk_pixbuf_new_from_file(path, &error);
468 if (!pixbuf)
470 g_print("%s\n", error ? error->message : _("Unknown error"));
471 g_error_free(error);
472 return NULL;
475 image = masked_pixmap_new(pixbuf);
477 gdk_pixbuf_unref(pixbuf);
479 return image;
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)
487 int w, h;
489 w = gdk_pixbuf_get_width(src);
490 h = gdk_pixbuf_get_height(src);
492 if (w <= max_w && h <= max_h)
494 gdk_pixbuf_ref(src);
495 return src;
497 else
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,
506 MAX(dest_w, 1),
507 MAX(dest_h, 1),
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)
517 int w, 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))
524 gdk_pixbuf_ref(src);
525 return src;
527 else
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,
534 w * scale,
535 h * scale,
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)
545 GdkPixbuf *bad;
546 MaskedPixmap *mp;
548 bad = gdk_pixbuf_new_from_xpm_data(bad_xpm);
549 mp = masked_pixmap_new(bad);
550 gdk_pixbuf_unref(bad);
552 return mp;
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);
560 return TRUE;
563 static gpointer parent_class;
565 static void masked_pixmap_finialize(GObject *object)
567 MaskedPixmap *mp = (MaskedPixmap *) object;
569 if (mp->src_pixbuf)
571 g_object_unref(mp->src_pixbuf);
572 mp->src_pixbuf = NULL;
575 if (mp->huge_pixbuf)
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;
586 if (mp->pixbuf)
588 g_object_unref(mp->pixbuf);
589 mp->pixbuf = NULL;
591 if (mp->pixmap)
593 g_object_unref(mp->pixmap);
594 mp->pixmap = NULL;
596 if (mp->mask)
598 g_object_unref(mp->mask);
599 mp->mask = NULL;
602 if (mp->sm_pixbuf)
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;
628 mp->huge_width = -1;
629 mp->huge_height = -1;
631 mp->pixbuf = NULL;
632 mp->pixmap = NULL;
633 mp->mask = NULL;
634 mp->width = -1;
635 mp->height = -1;
637 mp->sm_pixbuf = NULL;
638 mp->sm_width = -1;
639 mp->sm_height = -1;
642 static GType masked_pixmap_get_type(void)
644 static GType type = 0;
646 if (!type)
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),
657 0, /* n_preallocs */
658 masked_pixmap_init
661 type = g_type_register_static(G_TYPE_OBJECT, "MaskedPixmap",
662 &info, 0);
665 return type;
668 MaskedPixmap *masked_pixmap_new(GdkPixbuf *full_size)
670 MaskedPixmap *mp;
671 GdkPixbuf *src_pixbuf, *normal_pixbuf;
672 GdkPixmap *pixmap;
673 GdkBitmap *mask;
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);
685 if (!pixmap)
687 gdk_pixbuf_unref(src_pixbuf);
688 gdk_pixbuf_unref(normal_pixbuf);
689 return NULL;
692 mp = g_object_new(masked_pixmap_get_type(), NULL);
694 mp->src_pixbuf = src_pixbuf;
696 mp->pixbuf = normal_pixbuf;
697 mp->pixmap = pixmap;
698 mp->mask = mask;
699 mp->width = gdk_pixbuf_get_width(normal_pixbuf);
700 mp->height = gdk_pixbuf_get_height(normal_pixbuf);
702 return mp;
705 static guchar lighten_component(guchar cur_value)
707 int new_value = cur_value;
709 new_value += 48 + (new_value >> 3);
710 if (new_value > 255)
711 new_value = 255;
713 return (guchar) new_value;
716 /* Stolen from eel */
717 static GdkPixbuf *create_spotlight_pixbuf(GdkPixbuf *src)
719 GdkPixbuf *dest;
720 int i, j;
721 int width, height, has_alpha, src_row_stride, dst_row_stride;
722 guchar *target_pixels, *original_pixels;
723 guchar *pixsrc, *pixdest;
724 gint n_channels;
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),
739 width, height);
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++);
755 if (has_alpha)
756 *pixdest++ = *pixsrc++;
760 return dest;