1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
2 
3 gnomebg.c: Object for the desktop background.
4 
5 Copyright (C) 2000 Eazel, Inc.
6 Copyright (C) 2007-2008 Red Hat, Inc.
7 
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU Library General Public License as
10 published by the Free Software Foundation; either version 2 of the
11 License, or (at your option) any later version.
12 
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 Library General Public License for more details.
17 
18 You should have received a copy of the GNU Library General Public
19 License along with this program; if not, write to the
20 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 Boston, MA 02110-1301, USA.
22 
23 Derived from eel-background.c and eel-gdk-pixbuf-extensions.c by
24 Darin Adler <darin@eazel.com> and Ramiro Estrugo <ramiro@eazel.com>
25 
26 Author: Soren Sandmann <sandmann@redhat.com>
27 
28 */
29 
30 #include <string.h>
31 #include <math.h>
32 #include <stdarg.h>
33 #include <stdlib.h>
34 
35 #include <glib/gstdio.h>
36 #include <gio/gio.h>
37 
38 #include <cairo.h>
39 
40 #define GNOME_DESKTOP_USE_UNSTABLE_API
41 #include "gnome-bg.h"
42 #include "gnome-bg-slide-show.h"
43 #include "gnome-bg-crossfade.h"
44 
45 #define BG_KEY_PRIMARY_COLOR      "primary-color"
46 #define BG_KEY_SECONDARY_COLOR    "secondary-color"
47 #define BG_KEY_COLOR_TYPE         "color-shading-type"
48 #define BG_KEY_PICTURE_PLACEMENT  "picture-options"
49 #define BG_KEY_PICTURE_OPACITY    "picture-opacity"
50 #define BG_KEY_PICTURE_URI        "picture-uri"
51 
52 /* We keep the large pixbufs around if the next update
53    in the slideshow is less than 60 seconds away */
54 #define KEEP_EXPENSIVE_CACHE_SECS 60
55 
56 /* This is the size of the GdkRGB dither matrix, in order to avoid
57  * bad dithering when tiling the gradient
58  */
59 #define GRADIENT_PIXMAP_TILE_SIZE 128
60 #define THUMBNAIL_SIZE 256
61 
62 typedef struct FileCacheEntry FileCacheEntry;
63 #define CACHE_SIZE 4
64 
65 /*
66  *   Implementation of the GnomeBG class
67  */
68 struct _GnomeBG
69 {
70 	GObject                 parent_instance;
71 	char *			filename;
72 	GDesktopBackgroundStyle	placement;
73 	GDesktopBackgroundShading	color_type;
74 	GdkRGBA			primary;
75 	GdkRGBA			secondary;
76 
77 	GFileMonitor *		file_monitor;
78 
79 	guint                   changed_id;
80 	guint                   transitioned_id;
81 	guint                   blow_caches_id;
82 
83 	/* Cached information, only access through cache accessor functions */
84         GnomeBGSlideShow *	slideshow;
85 	time_t			file_mtime;
86 	GdkPixbuf *		pixbuf_cache;
87 	int			timeout_id;
88 
89 	GList *		        file_cache;
90 };
91 
92 struct _GnomeBGClass
93 {
94 	GObjectClass parent_class;
95 };
96 
97 enum {
98 	CHANGED,
99 	TRANSITIONED,
100 	N_SIGNALS
101 };
102 
103 static guint signals[N_SIGNALS] = { 0 };
104 
105 G_DEFINE_TYPE (GnomeBG, gnome_bg, G_TYPE_OBJECT)
106 
107 /* Pixbuf utils */
108 static void       pixbuf_average_value (GdkPixbuf  *pixbuf,
109                                         GdkRGBA    *result);
110 static GdkPixbuf *pixbuf_scale_to_fit  (GdkPixbuf  *src,
111 					int         max_width,
112 					int         max_height);
113 static GdkPixbuf *pixbuf_scale_to_min  (GdkPixbuf  *src,
114 					int         min_width,
115 					int         min_height);
116 static void       pixbuf_draw_gradient (GdkPixbuf    *pixbuf,
117 					gboolean      horizontal,
118 					GdkRGBA      *c1,
119 					GdkRGBA      *c2,
120 					GdkRectangle *rect);
121 static void       pixbuf_tile          (GdkPixbuf  *src,
122 					GdkPixbuf  *dest);
123 static void       pixbuf_blend         (GdkPixbuf  *src,
124 					GdkPixbuf  *dest,
125 					int         src_x,
126 					int         src_y,
127 					int         width,
128 					int         height,
129 					int         dest_x,
130 					int         dest_y,
131 					double      alpha);
132 
133 /* Thumbnail utilities */
134 static GdkPixbuf *create_thumbnail_for_filename (GnomeDesktopThumbnailFactory *factory,
135 						 const char            *filename);
136 static gboolean   get_thumb_annotations (GdkPixbuf             *thumb,
137 					 int                   *orig_width,
138 					 int                   *orig_height);
139 
140 /* Cache */
141 static GdkPixbuf *get_pixbuf_for_size  (GnomeBG               *bg,
142 					gint                   num_monitor,
143 					int                    width,
144 					int                    height);
145 static void       clear_cache          (GnomeBG               *bg);
146 static gboolean   is_different         (GnomeBG               *bg,
147 					const char            *filename);
148 static time_t     get_mtime            (const char            *filename);
149 static GdkPixbuf *create_img_thumbnail (GnomeBG               *bg,
150 					GnomeDesktopThumbnailFactory *factory,
151 					GdkScreen             *screen,
152 					int                    dest_width,
153 					int                    dest_height,
154 					int		       frame_num);
155 static GnomeBGSlideShow * get_as_slideshow    (GnomeBG               *bg,
156                                                const char 	      *filename);
157 static GnomeBGSlideShow *read_slideshow_file (const char *filename,
158 				       GError     **err);
159 
160 static void
color_from_string(const char * string,GdkRGBA * colorp)161 color_from_string (const char *string,
162 		   GdkRGBA   *colorp)
163 {
164 	/* If all else fails use black */
165 	gdk_rgba_parse (colorp, "black");
166 
167 	if (!string)
168 		return;
169 
170 	gdk_rgba_parse (colorp, string);
171 }
172 
173 static char *
color_to_string(const GdkRGBA * color)174 color_to_string (const GdkRGBA *color)
175 {
176 	return g_strdup_printf ("#%02x%02x%02x",
177 				(int) (0.5 + color->red * 255),
178 				(int) (0.5 + color->green * 255),
179 				(int) (0.5 + color->blue * 255));
180 }
181 
182 static gboolean
do_changed(GnomeBG * bg)183 do_changed (GnomeBG *bg)
184 {
185 	gboolean ignore_pending_change;
186 	bg->changed_id = 0;
187 
188 	ignore_pending_change =
189 		GPOINTER_TO_INT (g_object_get_data (G_OBJECT (bg),
190 						    "ignore-pending-change"));
191 
192 	if (!ignore_pending_change) {
193 		g_signal_emit (G_OBJECT (bg), signals[CHANGED], 0);
194 	}
195 
196 	return FALSE;
197 }
198 
199 static void
queue_changed(GnomeBG * bg)200 queue_changed (GnomeBG *bg)
201 {
202 	if (bg->changed_id > 0) {
203 		g_source_remove (bg->changed_id);
204 	}
205 
206 	/* We unset this here to allow apps to set it if they don't want
207 	   to get the change event. This is used by nautilus when it
208 	   gets the pixmap from the bg (due to a reason other than the changed
209 	   event). Because if there is no other change after this time the
210 	   pending changed event will just uselessly cause us to recreate
211 	   the pixmap. */
212 	g_object_set_data (G_OBJECT (bg), "ignore-pending-change",
213 			   GINT_TO_POINTER (FALSE));
214 	bg->changed_id = g_timeout_add_full (G_PRIORITY_LOW,
215 					     100,
216 					     (GSourceFunc)do_changed,
217 					     bg,
218 					     NULL);
219 }
220 
221 static gboolean
do_transitioned(GnomeBG * bg)222 do_transitioned (GnomeBG *bg)
223 {
224 	bg->transitioned_id = 0;
225 
226 	if (bg->pixbuf_cache) {
227 		g_object_unref (bg->pixbuf_cache);
228 		bg->pixbuf_cache = NULL;
229 	}
230 
231 	g_signal_emit (G_OBJECT (bg), signals[TRANSITIONED], 0);
232 
233 	return FALSE;
234 }
235 
236 static void
queue_transitioned(GnomeBG * bg)237 queue_transitioned (GnomeBG *bg)
238 {
239 	if (bg->transitioned_id > 0) {
240 		g_source_remove (bg->transitioned_id);
241 	}
242 
243 	bg->transitioned_id = g_timeout_add_full (G_PRIORITY_LOW,
244 					     100,
245 					     (GSourceFunc)do_transitioned,
246 					     bg,
247 					     NULL);
248 }
249 
250 static gboolean
bg_gsettings_mapping(GVariant * value,gpointer * result,gpointer user_data)251 bg_gsettings_mapping (GVariant *value,
252 			gpointer *result,
253 			gpointer user_data)
254 {
255 	const gchar *bg_key_value;
256 	char *filename = NULL;
257 
258 	/* The final fallback if nothing matches is with a NULL value. */
259 	if (value == NULL) {
260 		*result = NULL;
261 		return TRUE;
262 	}
263 
264 	bg_key_value = g_variant_get_string (value, NULL);
265 
266 	if (bg_key_value && *bg_key_value != '\0') {
267 		filename = g_filename_from_uri (bg_key_value, NULL, NULL);
268 
269 		if (filename != NULL && g_file_test (filename, G_FILE_TEST_EXISTS) == FALSE) {
270 			g_free (filename);
271 			return FALSE;
272 		}
273 
274 		if (filename != NULL) {
275 			*result = filename;
276 			return TRUE;
277 		}
278 	}
279 
280 	return FALSE;
281 }
282 
283 void
gnome_bg_load_from_preferences(GnomeBG * bg,GSettings * settings)284 gnome_bg_load_from_preferences (GnomeBG   *bg,
285 				GSettings *settings)
286 {
287 	char    *tmp;
288 	char    *filename;
289 	GDesktopBackgroundShading ctype;
290 	GdkRGBA c1, c2;
291 	GDesktopBackgroundStyle placement;
292 
293 	g_return_if_fail (GNOME_IS_BG (bg));
294 	g_return_if_fail (G_IS_SETTINGS (settings));
295 
296 	/* Filename */
297 	filename = g_settings_get_mapped (settings, BG_KEY_PICTURE_URI, bg_gsettings_mapping, NULL);
298 
299 	/* Colors */
300 	tmp = g_settings_get_string (settings, BG_KEY_PRIMARY_COLOR);
301 	color_from_string (tmp, &c1);
302 	g_free (tmp);
303 
304 	tmp = g_settings_get_string (settings, BG_KEY_SECONDARY_COLOR);
305 	color_from_string (tmp, &c2);
306 	g_free (tmp);
307 
308 	/* Color type */
309 	ctype = g_settings_get_enum (settings, BG_KEY_COLOR_TYPE);
310 
311 	/* Placement */
312 	placement = g_settings_get_enum (settings, BG_KEY_PICTURE_PLACEMENT);
313 
314 	gnome_bg_set_rgba (bg, ctype, &c1, &c2);
315 	gnome_bg_set_placement (bg, placement);
316 	gnome_bg_set_filename (bg, filename);
317 
318 	g_free (filename);
319 }
320 
321 void
gnome_bg_save_to_preferences(GnomeBG * bg,GSettings * settings)322 gnome_bg_save_to_preferences (GnomeBG   *bg,
323 			      GSettings *settings)
324 {
325 	gchar *primary;
326 	gchar *secondary;
327 	gchar *uri;
328 
329 	g_return_if_fail (GNOME_IS_BG (bg));
330 	g_return_if_fail (G_IS_SETTINGS (settings));
331 
332 	primary = color_to_string (&bg->primary);
333 	secondary = color_to_string (&bg->secondary);
334 
335 	g_settings_delay (settings);
336 
337 	uri = NULL;
338 	if (bg->filename != NULL)
339 		uri = g_filename_to_uri (bg->filename, NULL, NULL);
340 	if (uri == NULL)
341 		uri = g_strdup ("");
342 	g_settings_set_string (settings, BG_KEY_PICTURE_URI, uri);
343 	g_settings_set_string (settings, BG_KEY_PRIMARY_COLOR, primary);
344 	g_settings_set_string (settings, BG_KEY_SECONDARY_COLOR, secondary);
345 	g_settings_set_enum (settings, BG_KEY_COLOR_TYPE, bg->color_type);
346 	g_settings_set_enum (settings, BG_KEY_PICTURE_PLACEMENT, bg->placement);
347 
348 	/* Apply changes atomically. */
349 	g_settings_apply (settings);
350 
351 	g_free (primary);
352 	g_free (secondary);
353 	g_free (uri);
354 }
355 
356 
357 static void
gnome_bg_init(GnomeBG * bg)358 gnome_bg_init (GnomeBG *bg)
359 {
360 }
361 
362 static void
gnome_bg_dispose(GObject * object)363 gnome_bg_dispose (GObject *object)
364 {
365 	GnomeBG *bg = GNOME_BG (object);
366 
367 	if (bg->file_monitor) {
368 		g_object_unref (bg->file_monitor);
369 		bg->file_monitor = NULL;
370 	}
371 
372 	clear_cache (bg);
373 
374 	G_OBJECT_CLASS (gnome_bg_parent_class)->dispose (object);
375 }
376 
377 static void
gnome_bg_finalize(GObject * object)378 gnome_bg_finalize (GObject *object)
379 {
380 	GnomeBG *bg = GNOME_BG (object);
381 
382 	if (bg->changed_id != 0) {
383 		g_source_remove (bg->changed_id);
384 		bg->changed_id = 0;
385 	}
386 
387 	if (bg->transitioned_id != 0) {
388 		g_source_remove (bg->transitioned_id);
389 		bg->transitioned_id = 0;
390 	}
391 
392 	if (bg->blow_caches_id != 0) {
393 		g_source_remove (bg->blow_caches_id);
394 		bg->blow_caches_id = 0;
395 	}
396 
397 	g_free (bg->filename);
398 	bg->filename = NULL;
399 
400 	G_OBJECT_CLASS (gnome_bg_parent_class)->finalize (object);
401 }
402 
403 static void
gnome_bg_class_init(GnomeBGClass * klass)404 gnome_bg_class_init (GnomeBGClass *klass)
405 {
406 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
407 
408 	object_class->dispose = gnome_bg_dispose;
409 	object_class->finalize = gnome_bg_finalize;
410 
411 	signals[CHANGED] = g_signal_new ("changed",
412 					 G_OBJECT_CLASS_TYPE (object_class),
413 					 G_SIGNAL_RUN_LAST,
414 					 0,
415 					 NULL, NULL,
416 					 g_cclosure_marshal_VOID__VOID,
417 					 G_TYPE_NONE, 0);
418 
419 	signals[TRANSITIONED] = g_signal_new ("transitioned",
420 					 G_OBJECT_CLASS_TYPE (object_class),
421 					 G_SIGNAL_RUN_LAST,
422 					 0,
423 					 NULL, NULL,
424 					 g_cclosure_marshal_VOID__VOID,
425 					 G_TYPE_NONE, 0);
426 }
427 
428 GnomeBG *
gnome_bg_new(void)429 gnome_bg_new (void)
430 {
431 	return g_object_new (GNOME_TYPE_BG, NULL);
432 }
433 
434 void
gnome_bg_set_rgba(GnomeBG * bg,GDesktopBackgroundShading type,GdkRGBA * primary,GdkRGBA * secondary)435 gnome_bg_set_rgba (GnomeBG *bg,
436 		   GDesktopBackgroundShading type,
437 		   GdkRGBA *primary,
438 		   GdkRGBA *secondary)
439 {
440 	g_return_if_fail (bg != NULL);
441 	g_return_if_fail (primary != NULL);
442 
443 	if (bg->color_type != type			||
444 	    !gdk_rgba_equal (&bg->primary, primary)			||
445 	    (secondary && !gdk_rgba_equal (&bg->secondary, secondary))) {
446 
447 		bg->color_type = type;
448 		bg->primary = *primary;
449 		if (secondary) {
450 			bg->secondary = *secondary;
451 		}
452 
453 		queue_changed (bg);
454 	}
455 }
456 
457 void
gnome_bg_set_placement(GnomeBG * bg,GDesktopBackgroundStyle placement)458 gnome_bg_set_placement (GnomeBG                 *bg,
459 			GDesktopBackgroundStyle  placement)
460 {
461 	g_return_if_fail (bg != NULL);
462 
463 	if (bg->placement != placement) {
464 		bg->placement = placement;
465 
466 		queue_changed (bg);
467 	}
468 }
469 
470 GDesktopBackgroundStyle
gnome_bg_get_placement(GnomeBG * bg)471 gnome_bg_get_placement (GnomeBG *bg)
472 {
473 	g_return_val_if_fail (bg != NULL, -1);
474 
475 	return bg->placement;
476 }
477 
478 void
gnome_bg_get_rgba(GnomeBG * bg,GDesktopBackgroundShading * type,GdkRGBA * primary,GdkRGBA * secondary)479 gnome_bg_get_rgba (GnomeBG                   *bg,
480 		   GDesktopBackgroundShading *type,
481 		   GdkRGBA                   *primary,
482 		   GdkRGBA                   *secondary)
483 {
484 	g_return_if_fail (bg != NULL);
485 
486 	if (type)
487 		*type = bg->color_type;
488 
489 	if (primary)
490 		*primary = bg->primary;
491 
492 	if (secondary)
493 		*secondary = bg->secondary;
494 }
495 
496 const gchar *
gnome_bg_get_filename(GnomeBG * bg)497 gnome_bg_get_filename (GnomeBG *bg)
498 {
499 	g_return_val_if_fail (bg != NULL, NULL);
500 
501 	return bg->filename;
502 }
503 
504 static inline gchar *
get_wallpaper_cache_dir(void)505 get_wallpaper_cache_dir (void)
506 {
507 	return g_build_filename (g_get_user_cache_dir(), "wallpaper", NULL);
508 }
509 
510 static inline gchar *
get_wallpaper_cache_prefix_name(gint num_monitor,GDesktopBackgroundStyle placement,gint width,gint height)511 get_wallpaper_cache_prefix_name (gint                     num_monitor,
512 				 GDesktopBackgroundStyle  placement,
513 				 gint                     width,
514 				 gint                     height)
515 {
516 	return g_strdup_printf ("%i_%i_%i_%i", num_monitor, (gint) placement, width, height);
517 }
518 
519 static char *
get_wallpaper_cache_filename(const char * filename,gint num_monitor,GDesktopBackgroundStyle placement,gint width,gint height)520 get_wallpaper_cache_filename (const char              *filename,
521 			      gint                     num_monitor,
522 			      GDesktopBackgroundStyle  placement,
523 			      gint                     width,
524 			      gint                     height)
525 {
526 	gchar *cache_filename;
527 	gchar *cache_prefix_name;
528 	gchar *md5_filename;
529 	gchar *cache_basename;
530 	gchar *cache_dir;
531 
532 	md5_filename = g_compute_checksum_for_data (G_CHECKSUM_MD5, (const guchar *) filename, strlen (filename));
533 	cache_prefix_name = get_wallpaper_cache_prefix_name (num_monitor, placement, width, height);
534 	cache_basename = g_strdup_printf ("%s_%s", cache_prefix_name, md5_filename);
535 	cache_dir = get_wallpaper_cache_dir ();
536 	cache_filename = g_build_filename (cache_dir, cache_basename, NULL);
537 
538 	g_free (cache_prefix_name);
539 	g_free (md5_filename);
540 	g_free (cache_basename);
541 	g_free (cache_dir);
542 
543 	return cache_filename;
544 }
545 
546 static void
cleanup_cache_for_monitor(gchar * cache_dir,gint num_monitor)547 cleanup_cache_for_monitor (gchar *cache_dir,
548 			   gint   num_monitor)
549 {
550 	GDir            *g_cache_dir;
551 	gchar           *monitor_prefix;
552 	const gchar     *file;
553 
554 	g_cache_dir = g_dir_open (cache_dir, 0, NULL);
555 	monitor_prefix = g_strdup_printf ("%i_", num_monitor);
556 
557 	file = g_dir_read_name (g_cache_dir);
558 	while (file != NULL) {
559 		gchar *path;
560 
561 		path = g_build_filename (cache_dir, file, NULL);
562 		/* purge files with same monitor id */
563 		if (g_str_has_prefix (file, monitor_prefix) &&
564 		    g_file_test (path, G_FILE_TEST_IS_REGULAR))
565 			g_unlink (path);
566 
567 		g_free (path);
568 
569 		file = g_dir_read_name (g_cache_dir);
570 	}
571 
572 	g_free (monitor_prefix);
573 	g_dir_close (g_cache_dir);
574 }
575 
576 static gboolean
cache_file_is_valid(const char * filename,const char * cache_filename)577 cache_file_is_valid (const char *filename,
578 		     const char *cache_filename)
579 {
580 	time_t mtime;
581 	time_t cache_mtime;
582 
583 	if (!g_file_test (cache_filename, G_FILE_TEST_IS_REGULAR))
584 		return FALSE;
585 
586 	mtime = get_mtime (filename);
587 	cache_mtime = get_mtime (cache_filename);
588 
589 	return (mtime < cache_mtime);
590 }
591 
592 static void
refresh_cache_file(GnomeBG * bg,GdkPixbuf * new_pixbuf,gint num_monitor,gint width,gint height)593 refresh_cache_file (GnomeBG     *bg,
594 		    GdkPixbuf   *new_pixbuf,
595 		    gint         num_monitor,
596 		    gint         width,
597 		    gint         height)
598 {
599 	gchar           *cache_filename;
600 	gchar           *cache_dir;
601 	GdkPixbufFormat *format;
602 	gchar           *format_name;
603 
604 	if ((num_monitor == -1) || (width <= 300) || (height <= 300))
605 		return;
606 
607 	cache_filename = get_wallpaper_cache_filename (bg->filename, num_monitor, bg->placement, width, height);
608 	cache_dir = get_wallpaper_cache_dir ();
609 
610 	/* Only refresh scaled file on disk if useful (and don't cache slideshow) */
611 	if (!cache_file_is_valid (bg->filename, cache_filename)) {
612 		format = gdk_pixbuf_get_file_info (bg->filename, NULL, NULL);
613 
614 		if (format != NULL) {
615 			if (!g_file_test (cache_dir, G_FILE_TEST_IS_DIR)) {
616 				g_mkdir_with_parents (cache_dir, 0700);
617 			} else {
618 				cleanup_cache_for_monitor (cache_dir, num_monitor);
619 			}
620 
621 			format_name = gdk_pixbuf_format_get_name (format);
622 
623 			if (strcmp (format_name, "jpeg") == 0)
624 				gdk_pixbuf_save (new_pixbuf, cache_filename, format_name, NULL, "quality", "100", NULL);
625 			else
626 				gdk_pixbuf_save (new_pixbuf, cache_filename, format_name, NULL, NULL);
627 
628 			g_free (format_name);
629 		}
630 	}
631 
632 	g_free (cache_filename);
633 	g_free (cache_dir);
634 }
635 
636 static void
file_changed(GFileMonitor * file_monitor,GFile * child,GFile * other_file,GFileMonitorEvent event_type,gpointer user_data)637 file_changed (GFileMonitor *file_monitor,
638 	      GFile *child,
639 	      GFile *other_file,
640 	      GFileMonitorEvent event_type,
641 	      gpointer user_data)
642 {
643 	GnomeBG *bg = GNOME_BG (user_data);
644 
645 	clear_cache (bg);
646 	queue_changed (bg);
647 }
648 
649 void
gnome_bg_set_filename(GnomeBG * bg,const char * filename)650 gnome_bg_set_filename (GnomeBG     *bg,
651 		       const char  *filename)
652 {
653 	g_return_if_fail (bg != NULL);
654 
655 	if (is_different (bg, filename)) {
656 		g_free (bg->filename);
657 
658 		bg->filename = g_strdup (filename);
659 		bg->file_mtime = get_mtime (bg->filename);
660 
661 		if (bg->file_monitor) {
662 			g_object_unref (bg->file_monitor);
663 			bg->file_monitor = NULL;
664 		}
665 
666 		if (bg->filename) {
667 			GFile *f = g_file_new_for_path (bg->filename);
668 
669 			bg->file_monitor = g_file_monitor_file (f, 0, NULL, NULL);
670 			g_signal_connect (bg->file_monitor, "changed",
671 					  G_CALLBACK (file_changed), bg);
672 
673 			g_object_unref (f);
674 		}
675 
676 		clear_cache (bg);
677 
678 		queue_changed (bg);
679 	}
680 }
681 
682 static void
draw_color_area(GnomeBG * bg,GdkPixbuf * dest,GdkRectangle * rect)683 draw_color_area (GnomeBG *bg,
684 		 GdkPixbuf *dest,
685 		 GdkRectangle *rect)
686 {
687 	guint32 pixel;
688         GdkRectangle extent;
689 
690         extent.x = 0;
691         extent.y = 0;
692         extent.width = gdk_pixbuf_get_width (dest);
693         extent.height = gdk_pixbuf_get_height (dest);
694 
695         gdk_rectangle_intersect (rect, &extent, rect);
696 
697 	switch (bg->color_type) {
698 	case G_DESKTOP_BACKGROUND_SHADING_SOLID:
699 		/* not really a big deal to ignore the area of interest */
700 		pixel = ((int) (0.5 + bg->primary.red * 255) << 24)      |
701 			((int) (0.5 + bg->primary.green * 255) << 16)    |
702 			((int) (0.5 + bg->primary.blue * 255) << 8)      |
703 			(0xff);
704 
705 		gdk_pixbuf_fill (dest, pixel);
706 		break;
707 
708 	case G_DESKTOP_BACKGROUND_SHADING_HORIZONTAL:
709 		pixbuf_draw_gradient (dest, TRUE, &(bg->primary), &(bg->secondary), rect);
710 		break;
711 
712 	case G_DESKTOP_BACKGROUND_SHADING_VERTICAL:
713 		pixbuf_draw_gradient (dest, FALSE, &(bg->primary), &(bg->secondary), rect);
714 		break;
715 
716 	default:
717 		break;
718 	}
719 }
720 
721 static void
draw_color(GnomeBG * bg,GdkPixbuf * dest)722 draw_color (GnomeBG *bg,
723 	    GdkPixbuf *dest)
724 {
725 	GdkRectangle rect;
726 	rect.x = 0;
727 	rect.y = 0;
728 	rect.width = gdk_pixbuf_get_width (dest);
729 	rect.height = gdk_pixbuf_get_height (dest);
730 	draw_color_area (bg, dest, &rect);
731 }
732 
733 static GdkPixbuf *
pixbuf_clip_to_fit(GdkPixbuf * src,int max_width,int max_height)734 pixbuf_clip_to_fit (GdkPixbuf *src,
735 		    int        max_width,
736 		    int        max_height)
737 {
738 	int src_width, src_height;
739 	int w, h;
740 	int src_x, src_y;
741 	GdkPixbuf *pixbuf;
742 
743 	src_width = gdk_pixbuf_get_width (src);
744 	src_height = gdk_pixbuf_get_height (src);
745 
746 	if (src_width < max_width && src_height < max_height)
747 		return g_object_ref (src);
748 
749 	w = MIN(src_width, max_width);
750 	h = MIN(src_height, max_height);
751 
752 	pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
753 				 gdk_pixbuf_get_has_alpha (src),
754 				 8, w, h);
755 
756 	src_x = (src_width - w) / 2;
757 	src_y = (src_height - h) / 2;
758 	gdk_pixbuf_copy_area (src,
759 			      src_x, src_y,
760 			      w, h,
761 			      pixbuf,
762 			      0, 0);
763 	return pixbuf;
764 }
765 
766 static GdkPixbuf *
get_scaled_pixbuf(GDesktopBackgroundStyle placement,GdkPixbuf * pixbuf,int width,int height,int * x,int * y,int * w,int * h)767 get_scaled_pixbuf (GDesktopBackgroundStyle placement,
768 		   GdkPixbuf *pixbuf,
769 		   int width, int height,
770 		   int *x, int *y,
771 		   int *w, int *h)
772 {
773 	GdkPixbuf *new;
774 
775 #if 0
776 	g_print ("original_width: %d %d\n",
777 		 gdk_pixbuf_get_width (pixbuf),
778 		 gdk_pixbuf_get_height (pixbuf));
779 #endif
780 
781 	switch (placement) {
782 	case G_DESKTOP_BACKGROUND_STYLE_SPANNED:
783                 new = pixbuf_scale_to_fit (pixbuf, width, height);
784 		break;
785 	case G_DESKTOP_BACKGROUND_STYLE_ZOOM:
786 		new = pixbuf_scale_to_min (pixbuf, width, height);
787 		break;
788 
789 	case G_DESKTOP_BACKGROUND_STYLE_STRETCHED:
790 		new = gdk_pixbuf_scale_simple (pixbuf, width, height,
791 					       GDK_INTERP_BILINEAR);
792 		break;
793 
794 	case G_DESKTOP_BACKGROUND_STYLE_SCALED:
795 		new = pixbuf_scale_to_fit (pixbuf, width, height);
796 		break;
797 
798 	case G_DESKTOP_BACKGROUND_STYLE_NONE:
799 		/* This shouldn’t be true, but if it is, assert and
800 		 * fall through, in case assertions are disabled.
801 		 */
802 		g_assert_not_reached ();
803 	case G_DESKTOP_BACKGROUND_STYLE_CENTERED:
804 	case G_DESKTOP_BACKGROUND_STYLE_WALLPAPER:
805 	default:
806 		new = pixbuf_clip_to_fit (pixbuf, width, height);
807 		break;
808 	}
809 
810 	*w = gdk_pixbuf_get_width (new);
811 	*h = gdk_pixbuf_get_height (new);
812 	*x = (width - *w) / 2;
813 	*y = (height - *h) / 2;
814 
815 	return new;
816 }
817 
818 static void
draw_image_area(GnomeBG * bg,gint num_monitor,GdkPixbuf * pixbuf,GdkPixbuf * dest,GdkRectangle * area)819 draw_image_area (GnomeBG         *bg,
820 		 gint             num_monitor,
821 		 GdkPixbuf       *pixbuf,
822 		 GdkPixbuf       *dest,
823 		 GdkRectangle    *area)
824 {
825 	int dest_width = area->width;
826 	int dest_height = area->height;
827 	int x, y, w, h;
828 	GdkPixbuf *scaled;
829 
830 	if (!pixbuf)
831 		return;
832 
833 	scaled = get_scaled_pixbuf (bg->placement, pixbuf, dest_width, dest_height, &x, &y, &w, &h);
834 
835 	switch (bg->placement) {
836 	case G_DESKTOP_BACKGROUND_STYLE_WALLPAPER:
837 		pixbuf_tile (scaled, dest);
838 		break;
839 	case G_DESKTOP_BACKGROUND_STYLE_ZOOM:
840 	case G_DESKTOP_BACKGROUND_STYLE_CENTERED:
841 	case G_DESKTOP_BACKGROUND_STYLE_STRETCHED:
842 	case G_DESKTOP_BACKGROUND_STYLE_SCALED:
843 		pixbuf_blend (scaled, dest, 0, 0, w, h, x + area->x, y + area->y, 1.0);
844 		break;
845 	case G_DESKTOP_BACKGROUND_STYLE_SPANNED:
846 		pixbuf_blend (scaled, dest, 0, 0, w, h, x, y, 1.0);
847 		break;
848 	case G_DESKTOP_BACKGROUND_STYLE_NONE:
849 	default:
850 		g_assert_not_reached ();
851 		break;
852 	}
853 
854 	refresh_cache_file (bg, scaled, num_monitor, dest_width, dest_height);
855 
856 	g_object_unref (scaled);
857 }
858 
859 static void
draw_image_for_thumb(GnomeBG * bg,GdkPixbuf * pixbuf,GdkPixbuf * dest)860 draw_image_for_thumb (GnomeBG       *bg,
861 	    GdkPixbuf               *pixbuf,
862 	    GdkPixbuf               *dest)
863 {
864 	GdkRectangle rect;
865 
866 	rect.x = 0;
867 	rect.y = 0;
868 	rect.width = gdk_pixbuf_get_width (dest);
869 	rect.height = gdk_pixbuf_get_height (dest);
870 
871 	draw_image_area (bg, -1, pixbuf, dest, &rect);
872 }
873 
874 static void
draw_once(GnomeBG * bg,GdkPixbuf * dest)875 draw_once (GnomeBG   *bg,
876 	   GdkPixbuf *dest)
877 {
878 	GdkRectangle rect;
879 	GdkPixbuf   *pixbuf;
880 	gint         num_monitor;
881 
882 	/* we just draw on the whole screen */
883 	num_monitor = 0;
884 
885 	rect.x = 0;
886 	rect.y = 0;
887 	rect.width = gdk_pixbuf_get_width (dest);
888 	rect.height = gdk_pixbuf_get_height (dest);
889 
890 	pixbuf = get_pixbuf_for_size (bg, num_monitor, rect.width, rect.height);
891 	if (pixbuf) {
892 		GdkPixbuf *rotated;
893 
894 		rotated = gdk_pixbuf_apply_embedded_orientation (pixbuf);
895 		if (rotated != NULL) {
896 			g_object_unref (pixbuf);
897 			pixbuf = rotated;
898 		}
899 
900 		draw_image_area (bg,
901 				 num_monitor,
902 				 pixbuf,
903 				 dest,
904 				 &rect);
905 		g_object_unref (pixbuf);
906 	}
907 }
908 
909 void
gnome_bg_draw(GnomeBG * bg,GdkPixbuf * dest)910 gnome_bg_draw (GnomeBG   *bg,
911                GdkPixbuf *dest)
912 {
913 	draw_color (bg, dest);
914 	if (bg->placement != G_DESKTOP_BACKGROUND_STYLE_NONE) {
915 		draw_once (bg, dest);
916 	}
917 }
918 
919 gboolean
gnome_bg_has_multiple_sizes(GnomeBG * bg)920 gnome_bg_has_multiple_sizes (GnomeBG *bg)
921 {
922 	GnomeBGSlideShow *show;
923 	gboolean ret;
924 
925 	g_return_val_if_fail (bg != NULL, FALSE);
926 
927 	ret = FALSE;
928 
929 	show = get_as_slideshow (bg, bg->filename);
930 	if (show) {
931 		ret = gnome_bg_slide_show_get_has_multiple_sizes (show);
932 		g_object_unref (show);
933 	}
934 
935 	return ret;
936 }
937 
938 static void
gnome_bg_get_pixmap_size(GnomeBG * bg,int width,int height,int * pixmap_width,int * pixmap_height)939 gnome_bg_get_pixmap_size (GnomeBG   *bg,
940 			  int        width,
941 			  int        height,
942 			  int       *pixmap_width,
943 			  int       *pixmap_height)
944 {
945 	int dummy;
946 
947 	if (!pixmap_width)
948 		pixmap_width = &dummy;
949 	if (!pixmap_height)
950 		pixmap_height = &dummy;
951 
952 	*pixmap_width = width;
953 	*pixmap_height = height;
954 
955 	if (!bg->filename) {
956 		switch (bg->color_type) {
957 		case G_DESKTOP_BACKGROUND_SHADING_SOLID:
958 			*pixmap_width = 1;
959 			*pixmap_height = 1;
960 			break;
961 
962 		case G_DESKTOP_BACKGROUND_SHADING_HORIZONTAL:
963 		case G_DESKTOP_BACKGROUND_SHADING_VERTICAL:
964 		default:
965 			break;
966 		}
967 
968 		return;
969 	}
970 }
971 
972 /**
973  * gnome_bg_create_surface:
974  * @bg: GnomeBG
975  * @window:
976  * @width:
977  * @height:
978  *
979  * Create a surface that can be set as background for @window.
980  *
981  * Returns: %NULL on error (e.g. out of X connections)
982  **/
983 cairo_surface_t *
gnome_bg_create_surface(GnomeBG * bg,GdkWindow * window,int width,int height)984 gnome_bg_create_surface (GnomeBG	    *bg,
985 		 	 GdkWindow   *window,
986 			 int	     width,
987 			 int	     height)
988 {
989 	gint scale;
990 	int pm_width, pm_height;
991 	cairo_surface_t *surface;
992 	cairo_t *cr;
993 
994 	g_return_val_if_fail (bg != NULL, NULL);
995 	g_return_val_if_fail (window != NULL, NULL);
996 
997 	scale = gdk_window_get_scale_factor (window);
998 
999         if (bg->pixbuf_cache &&
1000             gdk_pixbuf_get_width (bg->pixbuf_cache) != width &&
1001             gdk_pixbuf_get_height (bg->pixbuf_cache) != height) {
1002                 g_object_unref (bg->pixbuf_cache);
1003                 bg->pixbuf_cache = NULL;
1004         }
1005 
1006 	/* has the side effect of loading and caching pixbuf only when in tile mode */
1007 	gnome_bg_get_pixmap_size (bg, width, height, &pm_width, &pm_height);
1008 	surface = gdk_window_create_similar_surface (window,
1009                                                      CAIRO_CONTENT_COLOR,
1010                                                      pm_width, pm_height);
1011 
1012 	if (surface == NULL)
1013 		return NULL;
1014 
1015 	cr = cairo_create (surface);
1016 	if (!bg->filename && bg->color_type == G_DESKTOP_BACKGROUND_SHADING_SOLID) {
1017 		gdk_cairo_set_source_rgba (cr, &(bg->primary));
1018 	}
1019 	else {
1020 		GdkPixbuf *pixbuf;
1021 		cairo_surface_t *pixbuf_surface;
1022 
1023 		pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8,
1024 					 scale * width, scale * height);
1025 		gnome_bg_draw (bg, pixbuf);
1026 
1027 		pixbuf_surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, 0, window);
1028 		cairo_set_source_surface (cr, pixbuf_surface, 0, 0);
1029 
1030 		cairo_surface_destroy (pixbuf_surface);
1031 		g_object_unref (pixbuf);
1032 	}
1033 
1034 	cairo_paint (cr);
1035 
1036 	cairo_destroy (cr);
1037 
1038 	return surface;
1039 }
1040 
1041 
1042 /* determine if a background is darker or lighter than average, to help
1043  * clients know what colors to draw on top with
1044  */
1045 gboolean
gnome_bg_is_dark(GnomeBG * bg,int width,int height)1046 gnome_bg_is_dark (GnomeBG *bg,
1047 		  int      width,
1048 		  int      height)
1049 {
1050 	GdkRGBA color;
1051 	gdouble intensity;
1052 	GdkPixbuf *pixbuf;
1053 
1054 	g_return_val_if_fail (bg != NULL, FALSE);
1055 
1056 	if (bg->color_type == G_DESKTOP_BACKGROUND_SHADING_SOLID) {
1057 		color = bg->primary;
1058 	} else {
1059 		color.red = (bg->primary.red + bg->secondary.red) / 2;
1060 		color.green = (bg->primary.green + bg->secondary.green) / 2;
1061 		color.blue = (bg->primary.blue + bg->secondary.blue) / 2;
1062 	}
1063 	pixbuf = get_pixbuf_for_size (bg, -1, width, height);
1064 	if (pixbuf) {
1065 		GdkRGBA average;
1066 
1067 		pixbuf_average_value (pixbuf, &average);
1068 
1069 		color.red = color.red * (1.0 - average.alpha) + average.red * average.alpha;
1070 		color.green = color.green * (1.0 - average.alpha) + average.green * average.alpha;
1071 		color.blue = color.blue * (1.0 - average.alpha) + average.blue * average.alpha;
1072 		g_object_unref (pixbuf);
1073 	}
1074 
1075 	intensity = color.red * 77 +
1076 		    color.green * 150 +
1077 		    color.blue * 28;
1078 
1079 	return intensity < 160; /* biased slightly to be dark */
1080 }
1081 
1082 static gboolean
get_original_size(const char * filename,int * orig_width,int * orig_height)1083 get_original_size (const char *filename,
1084 		   int        *orig_width,
1085 		   int        *orig_height)
1086 {
1087 	gboolean result;
1088 
1089         if (gdk_pixbuf_get_file_info (filename, orig_width, orig_height))
1090 		result = TRUE;
1091 	else
1092 		result = FALSE;
1093 
1094 	return result;
1095 }
1096 
1097 static const char *
get_filename_for_size(GnomeBG * bg,gint best_width,gint best_height)1098 get_filename_for_size (GnomeBG *bg, gint best_width, gint best_height)
1099 {
1100 	GnomeBGSlideShow *show;
1101         const char *file = NULL;
1102 
1103 	if (!bg->filename)
1104 		return NULL;
1105 
1106 	show = get_as_slideshow (bg, bg->filename);
1107 	if (!show) {
1108 		return bg->filename;
1109 	}
1110 
1111         gnome_bg_slide_show_get_current_slide (show, best_width, best_height, NULL, NULL, NULL, &file, NULL);
1112         g_object_unref (show);
1113 
1114         return file;
1115 }
1116 
1117 gboolean
gnome_bg_get_image_size(GnomeBG * bg,GnomeDesktopThumbnailFactory * factory,int best_width,int best_height,int * width,int * height)1118 gnome_bg_get_image_size (GnomeBG	       *bg,
1119 			 GnomeDesktopThumbnailFactory *factory,
1120 			 int                    best_width,
1121 			 int                    best_height,
1122 			 int		       *width,
1123 			 int		       *height)
1124 {
1125 	GdkPixbuf *thumb;
1126 	gboolean result = FALSE;
1127 	const gchar *filename;
1128 
1129 	g_return_val_if_fail (bg != NULL, FALSE);
1130 	g_return_val_if_fail (factory != NULL, FALSE);
1131 
1132 	if (!bg->filename)
1133 		return FALSE;
1134 
1135 	filename = get_filename_for_size (bg, best_width, best_height);
1136 	thumb = create_thumbnail_for_filename (factory, filename);
1137 	if (thumb) {
1138 		if (get_thumb_annotations (thumb, width, height))
1139 			result = TRUE;
1140 
1141 		g_object_unref (thumb);
1142 	}
1143 
1144 	if (!result) {
1145 		if (get_original_size (filename, width, height))
1146 			result = TRUE;
1147 	}
1148 
1149 	return result;
1150 }
1151 
1152 static double
fit_factor(int from_width,int from_height,int to_width,int to_height)1153 fit_factor (int from_width, int from_height,
1154 	    int to_width,   int to_height)
1155 {
1156 	return MIN (to_width  / (double) from_width, to_height / (double) from_height);
1157 }
1158 
1159 /**
1160  * gnome_bg_create_thumbnail:
1161  *
1162  * Returns: (transfer full): a #GdkPixbuf showing the background as a thumbnail
1163  */
1164 GdkPixbuf *
gnome_bg_create_thumbnail(GnomeBG * bg,GnomeDesktopThumbnailFactory * factory,GdkScreen * screen,int dest_width,int dest_height)1165 gnome_bg_create_thumbnail (GnomeBG               *bg,
1166 		           GnomeDesktopThumbnailFactory *factory,
1167 			   GdkScreen             *screen,
1168 			   int                    dest_width,
1169 			   int                    dest_height)
1170 {
1171 	GdkPixbuf *result;
1172 	GdkPixbuf *thumb;
1173 
1174 	g_return_val_if_fail (bg != NULL, NULL);
1175 
1176 	result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, dest_width, dest_height);
1177 
1178 	draw_color (bg, result);
1179 
1180 	if (bg->placement != G_DESKTOP_BACKGROUND_STYLE_NONE) {
1181 		thumb = create_img_thumbnail (bg, factory, screen, dest_width, dest_height, -1);
1182 
1183 		if (thumb) {
1184 			draw_image_for_thumb (bg, thumb, result);
1185 			g_object_unref (thumb);
1186 		}
1187 	}
1188 
1189 	return result;
1190 }
1191 
1192 /* Implementation of the pixbuf cache */
1193 struct _SlideShow
1194 {
1195 	gint ref_count;
1196 	double start_time;
1197 	double total_duration;
1198 
1199 	GQueue *slides;
1200 
1201 	gboolean has_multiple_sizes;
1202 
1203 	/* used during parsing */
1204 	struct tm start_tm;
1205 	GQueue *stack;
1206 };
1207 
1208 
1209 static GdkPixbuf *
blend(GdkPixbuf * p1,GdkPixbuf * p2,double alpha)1210 blend (GdkPixbuf *p1,
1211        GdkPixbuf *p2,
1212        double alpha)
1213 {
1214 	GdkPixbuf *result = gdk_pixbuf_copy (p1);
1215 	GdkPixbuf *tmp;
1216 
1217 	if (gdk_pixbuf_get_width (p2) != gdk_pixbuf_get_width (p1) ||
1218             gdk_pixbuf_get_height (p2) != gdk_pixbuf_get_height (p1)) {
1219 		tmp = gdk_pixbuf_scale_simple (p2,
1220 					       gdk_pixbuf_get_width (p1),
1221 					       gdk_pixbuf_get_height (p1),
1222 					       GDK_INTERP_BILINEAR);
1223 	}
1224         else {
1225 		tmp = g_object_ref (p2);
1226 	}
1227 
1228 	pixbuf_blend (tmp, result, 0, 0, -1, -1, 0, 0, alpha);
1229 
1230         g_object_unref (tmp);
1231 
1232 	return result;
1233 }
1234 
1235 typedef	enum {
1236 	PIXBUF,
1237 	SLIDESHOW,
1238 	THUMBNAIL
1239 } FileType;
1240 
1241 struct FileCacheEntry
1242 {
1243 	FileType type;
1244 	char *filename;
1245 	union {
1246 		GdkPixbuf *pixbuf;
1247 		GnomeBGSlideShow *slideshow;
1248 		GdkPixbuf *thumbnail;
1249 	} u;
1250 };
1251 
1252 static void
file_cache_entry_delete(FileCacheEntry * ent)1253 file_cache_entry_delete (FileCacheEntry *ent)
1254 {
1255 	g_free (ent->filename);
1256 
1257 	switch (ent->type) {
1258 	case PIXBUF:
1259 		g_object_unref (ent->u.pixbuf);
1260 		break;
1261 	case SLIDESHOW:
1262 		g_object_unref (ent->u.slideshow);
1263 		break;
1264 	case THUMBNAIL:
1265 		g_object_unref (ent->u.thumbnail);
1266 		break;
1267 	default:
1268 		break;
1269 	}
1270 
1271 	g_free (ent);
1272 }
1273 
1274 static void
bound_cache(GnomeBG * bg)1275 bound_cache (GnomeBG *bg)
1276 {
1277       while (g_list_length (bg->file_cache) >= CACHE_SIZE) {
1278 	      GList *last_link = g_list_last (bg->file_cache);
1279 	      FileCacheEntry *ent = last_link->data;
1280 
1281 	      file_cache_entry_delete (ent);
1282 
1283 	      bg->file_cache = g_list_delete_link (bg->file_cache, last_link);
1284       }
1285 }
1286 
1287 static const FileCacheEntry *
file_cache_lookup(GnomeBG * bg,FileType type,const char * filename)1288 file_cache_lookup (GnomeBG *bg, FileType type, const char *filename)
1289 {
1290 	GList *list;
1291 
1292 	for (list = bg->file_cache; list != NULL; list = list->next) {
1293 		FileCacheEntry *ent = list->data;
1294 
1295 		if (ent && ent->type == type &&
1296 		    strcmp (ent->filename, filename) == 0) {
1297 			return ent;
1298 		}
1299 	}
1300 
1301 	return NULL;
1302 }
1303 
1304 static FileCacheEntry *
file_cache_entry_new(GnomeBG * bg,FileType type,const char * filename)1305 file_cache_entry_new (GnomeBG *bg,
1306 		      FileType type,
1307 		      const char *filename)
1308 {
1309 	FileCacheEntry *ent = g_new0 (FileCacheEntry, 1);
1310 
1311 	g_assert (!file_cache_lookup (bg, type, filename));
1312 
1313 	ent->type = type;
1314 	ent->filename = g_strdup (filename);
1315 
1316 	bg->file_cache = g_list_prepend (bg->file_cache, ent);
1317 
1318 	bound_cache (bg);
1319 
1320 	return ent;
1321 }
1322 
1323 static void
file_cache_add_pixbuf(GnomeBG * bg,const char * filename,GdkPixbuf * pixbuf)1324 file_cache_add_pixbuf (GnomeBG *bg,
1325 		       const char *filename,
1326 		       GdkPixbuf *pixbuf)
1327 {
1328 	FileCacheEntry *ent = file_cache_entry_new (bg, PIXBUF, filename);
1329 	ent->u.pixbuf = g_object_ref (pixbuf);
1330 }
1331 
1332 static void
file_cache_add_thumbnail(GnomeBG * bg,const char * filename,GdkPixbuf * pixbuf)1333 file_cache_add_thumbnail (GnomeBG *bg,
1334 			  const char *filename,
1335 			  GdkPixbuf *pixbuf)
1336 {
1337 	FileCacheEntry *ent = file_cache_entry_new (bg, THUMBNAIL, filename);
1338 	ent->u.thumbnail = g_object_ref (pixbuf);
1339 }
1340 
1341 static void
file_cache_add_slide_show(GnomeBG * bg,const char * filename,GnomeBGSlideShow * show)1342 file_cache_add_slide_show (GnomeBG *bg,
1343 			   const char *filename,
1344 			   GnomeBGSlideShow *show)
1345 {
1346 	FileCacheEntry *ent = file_cache_entry_new (bg, SLIDESHOW, filename);
1347 	ent->u.slideshow = g_object_ref (show);
1348 }
1349 
1350 static GdkPixbuf *
load_from_cache_file(GnomeBG * bg,const char * filename,gint num_monitor,gint best_width,gint best_height)1351 load_from_cache_file (GnomeBG    *bg,
1352 		      const char *filename,
1353 		      gint        num_monitor,
1354 		      gint        best_width,
1355 		      gint        best_height)
1356 {
1357 	GdkPixbuf *pixbuf = NULL;
1358 	gchar *cache_filename;
1359 
1360 	cache_filename = get_wallpaper_cache_filename (filename, num_monitor, bg->placement, best_width, best_height);
1361 	if (cache_file_is_valid (filename, cache_filename))
1362 		pixbuf = gdk_pixbuf_new_from_file (cache_filename, NULL);
1363 	g_free (cache_filename);
1364 
1365 	return pixbuf;
1366 }
1367 
1368 static GdkPixbuf *
get_as_pixbuf_for_size(GnomeBG * bg,const char * filename,gint num_monitor,gint best_width,gint best_height)1369 get_as_pixbuf_for_size (GnomeBG    *bg,
1370 			const char *filename,
1371 			gint        num_monitor,
1372 			gint        best_width,
1373 			gint        best_height)
1374 {
1375 	const FileCacheEntry *ent;
1376 	if ((ent = file_cache_lookup (bg, PIXBUF, filename))) {
1377 		return g_object_ref (ent->u.pixbuf);
1378 	}
1379 	else {
1380 		GdkPixbufFormat *format;
1381 		GdkPixbuf *pixbuf;
1382                 gchar *tmp;
1383 		pixbuf = NULL;
1384 
1385 		/* Try to hit local cache first if relevant */
1386 		if (num_monitor != -1)
1387 			pixbuf = load_from_cache_file (bg, filename, num_monitor, best_width, best_height);
1388 
1389 		if (!pixbuf) {
1390 			/* If scalable choose maximum size */
1391 			format = gdk_pixbuf_get_file_info (filename, NULL, NULL);
1392 
1393 			if (format != NULL) {
1394 				tmp = gdk_pixbuf_format_get_name (format);
1395 			} else {
1396 				tmp = NULL;
1397 			}
1398 
1399 			if (tmp != NULL &&
1400 			    strcmp (tmp, "svg") == 0 &&
1401 			    (best_width > 0 && best_height > 0) &&
1402 			    (bg->placement == G_DESKTOP_BACKGROUND_STYLE_STRETCHED ||
1403 			     bg->placement == G_DESKTOP_BACKGROUND_STYLE_SCALED ||
1404 			     bg->placement == G_DESKTOP_BACKGROUND_STYLE_ZOOM))
1405 				pixbuf = gdk_pixbuf_new_from_file_at_size (filename, best_width, best_height, NULL);
1406 			else
1407 				pixbuf = gdk_pixbuf_new_from_file (filename, NULL);
1408 			g_free (tmp);
1409 		}
1410 
1411 		if (pixbuf)
1412 			file_cache_add_pixbuf (bg, filename, pixbuf);
1413 
1414 		return pixbuf;
1415 	}
1416 }
1417 
1418 static GnomeBGSlideShow *
get_as_slideshow(GnomeBG * bg,const char * filename)1419 get_as_slideshow (GnomeBG *bg, const char *filename)
1420 {
1421 	const FileCacheEntry *ent;
1422 	if ((ent = file_cache_lookup (bg, SLIDESHOW, filename))) {
1423 		return g_object_ref (ent->u.slideshow);
1424 	}
1425 	else {
1426 		GnomeBGSlideShow *show = read_slideshow_file (filename, NULL);
1427 
1428 		if (show)
1429 			file_cache_add_slide_show (bg, filename, show);
1430 
1431 		return show;
1432 	}
1433 }
1434 
1435 static GdkPixbuf *
get_as_thumbnail(GnomeBG * bg,GnomeDesktopThumbnailFactory * factory,const char * filename)1436 get_as_thumbnail (GnomeBG *bg, GnomeDesktopThumbnailFactory *factory, const char *filename)
1437 {
1438 	const FileCacheEntry *ent;
1439 	if ((ent = file_cache_lookup (bg, THUMBNAIL, filename))) {
1440 		return g_object_ref (ent->u.thumbnail);
1441 	}
1442 	else {
1443 		GdkPixbuf *thumb = create_thumbnail_for_filename (factory, filename);
1444 
1445 		if (thumb)
1446 			file_cache_add_thumbnail (bg, filename, thumb);
1447 
1448 		return thumb;
1449 	}
1450 }
1451 
1452 static gboolean
blow_expensive_caches(gpointer data)1453 blow_expensive_caches (gpointer data)
1454 {
1455 	GnomeBG *bg = data;
1456 	GList *list, *next;
1457 
1458 	bg->blow_caches_id = 0;
1459 
1460 	for (list = bg->file_cache; list != NULL; list = next) {
1461 		FileCacheEntry *ent = list->data;
1462 		next = list->next;
1463 
1464 		if (ent->type == PIXBUF) {
1465 			file_cache_entry_delete (ent);
1466 			bg->file_cache = g_list_delete_link (bg->file_cache,
1467 							     list);
1468 		}
1469 	}
1470 
1471 	if (bg->pixbuf_cache) {
1472 		g_object_unref (bg->pixbuf_cache);
1473 		bg->pixbuf_cache = NULL;
1474 	}
1475 
1476 	return FALSE;
1477 }
1478 
1479 static void
blow_expensive_caches_in_idle(GnomeBG * bg)1480 blow_expensive_caches_in_idle (GnomeBG *bg)
1481 {
1482 	if (bg->blow_caches_id == 0) {
1483 		bg->blow_caches_id =
1484 			g_idle_add (blow_expensive_caches,
1485 				    bg);
1486 	}
1487 }
1488 
1489 
1490 static gboolean
on_timeout(gpointer data)1491 on_timeout (gpointer data)
1492 {
1493 	GnomeBG *bg = data;
1494 
1495 	bg->timeout_id = 0;
1496 
1497 	queue_transitioned (bg);
1498 
1499 	return FALSE;
1500 }
1501 
1502 static double
get_slide_timeout(gboolean is_fixed,gdouble duration)1503 get_slide_timeout (gboolean is_fixed,
1504                    gdouble  duration)
1505 {
1506 	double timeout;
1507 	if (is_fixed) {
1508 		timeout = duration;
1509 	} else {
1510 		/* Maybe the number of steps should be configurable? */
1511 
1512 		/* In the worst case we will do a fade from 0 to 256, which mean
1513 		 * we will never use more than 255 steps, however in most cases
1514 		 * the first and last value are similar and users can't percieve
1515 		 * changes in pixel values as small as 1/255th. So, lets not waste
1516 		 * CPU cycles on transitioning to often.
1517 		 *
1518 		 * 64 steps is enough for each step to be just detectable in a 16bit
1519 		 * color mode in the worst case, so we'll use this as an approximation
1520 		 * of whats detectable.
1521 		 */
1522 		timeout = duration / 64.0;
1523 	}
1524 	return timeout;
1525 }
1526 
1527 static void
ensure_timeout(GnomeBG * bg,gdouble timeout)1528 ensure_timeout (GnomeBG *bg,
1529                 gdouble  timeout)
1530 {
1531 	if (!bg->timeout_id) {
1532 		/* G_MAXUINT means "only one slide" */
1533 		if (timeout < G_MAXUINT) {
1534 			bg->timeout_id = g_timeout_add_full (
1535 				G_PRIORITY_LOW,
1536 				timeout * 1000, on_timeout, bg, NULL);
1537 		}
1538 	}
1539 }
1540 
1541 static time_t
get_mtime(const char * filename)1542 get_mtime (const char *filename)
1543 {
1544 	GFile     *file;
1545 	GFileInfo *info;
1546 	time_t     mtime;
1547 
1548 	mtime = (time_t)-1;
1549 
1550 	if (filename) {
1551 		file = g_file_new_for_path (filename);
1552 		info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED,
1553 					  G_FILE_QUERY_INFO_NONE, NULL, NULL);
1554 		if (info) {
1555 			mtime = g_file_info_get_attribute_uint64 (info,
1556 								  G_FILE_ATTRIBUTE_TIME_MODIFIED);
1557 			g_object_unref (info);
1558 		}
1559 		g_object_unref (file);
1560 	}
1561 
1562 	return mtime;
1563 }
1564 
1565 static GdkPixbuf *
scale_thumbnail(GDesktopBackgroundStyle placement,const char * filename,GdkPixbuf * thumb,GdkScreen * screen,int dest_width,int dest_height)1566 scale_thumbnail (GDesktopBackgroundStyle placement,
1567 		 const char *filename,
1568 		 GdkPixbuf *thumb,
1569 		 GdkScreen *screen,
1570 		 int	    dest_width,
1571 		 int	    dest_height)
1572 {
1573 	int o_width;
1574 	int o_height;
1575 
1576 	if (placement != G_DESKTOP_BACKGROUND_STYLE_WALLPAPER &&
1577 	    placement != G_DESKTOP_BACKGROUND_STYLE_CENTERED) {
1578 
1579 		/* In this case, the pixbuf will be scaled to fit the screen anyway,
1580 		 * so just return the pixbuf here
1581 		 */
1582 		return g_object_ref (thumb);
1583 	}
1584 
1585 	if (get_thumb_annotations (thumb, &o_width, &o_height)		||
1586 	    (filename && get_original_size (filename, &o_width, &o_height))) {
1587 
1588 		int scr_height = gdk_screen_get_height (screen);
1589 		int scr_width = gdk_screen_get_width (screen);
1590 		int thumb_width = gdk_pixbuf_get_width (thumb);
1591 		int thumb_height = gdk_pixbuf_get_height (thumb);
1592 		double screen_to_dest = fit_factor (scr_width, scr_height,
1593 						    dest_width, dest_height);
1594 		double thumb_to_orig  = fit_factor (thumb_width, thumb_height,
1595 						    o_width, o_height);
1596 		double f = thumb_to_orig * screen_to_dest;
1597 		int new_width, new_height;
1598 
1599 		new_width = floor (thumb_width * f + 0.5);
1600 		new_height = floor (thumb_height * f + 0.5);
1601 
1602 		if (placement == G_DESKTOP_BACKGROUND_STYLE_WALLPAPER) {
1603 			/* Heuristic to make sure tiles don't become so small that
1604 			 * they turn into a blur.
1605 			 *
1606 			 * This is strictly speaking incorrect, but the resulting
1607 			 * thumbnail gives a much better idea what the background
1608 			 * will actually look like.
1609 			 */
1610 
1611 			if ((new_width < 32 || new_height < 32) &&
1612 			    (new_width < o_width / 4 || new_height < o_height / 4)) {
1613 				new_width = o_width / 4;
1614 				new_height = o_height / 4;
1615 			}
1616 		}
1617 
1618 		thumb = gdk_pixbuf_scale_simple (thumb, new_width, new_height,
1619 						 GDK_INTERP_BILINEAR);
1620 	}
1621 	else
1622 		g_object_ref (thumb);
1623 
1624 	return thumb;
1625 }
1626 
1627 /* frame_num determines which slide to thumbnail.
1628  * -1 means 'current slide'.
1629  */
1630 static GdkPixbuf *
create_img_thumbnail(GnomeBG * bg,GnomeDesktopThumbnailFactory * factory,GdkScreen * screen,int dest_width,int dest_height,int frame_num)1631 create_img_thumbnail (GnomeBG                      *bg,
1632 		      GnomeDesktopThumbnailFactory *factory,
1633 		      GdkScreen                    *screen,
1634 		      int                           dest_width,
1635 		      int                           dest_height,
1636 		      int                           frame_num)
1637 {
1638 	if (bg->filename) {
1639 		GdkPixbuf *thumb;
1640 
1641 		thumb = get_as_thumbnail (bg, factory, bg->filename);
1642 
1643 		if (thumb) {
1644 			GdkPixbuf *result;
1645 			result = scale_thumbnail (bg->placement,
1646 						  bg->filename,
1647 						  thumb,
1648 						  screen,
1649 						  dest_width,
1650 						  dest_height);
1651 			g_object_unref (thumb);
1652 			return result;
1653 		}
1654 		else {
1655 			GnomeBGSlideShow *show = get_as_slideshow (bg, bg->filename);
1656 
1657 			if (show) {
1658 				double alpha;
1659 				double duration;
1660                                 gboolean is_fixed;
1661                                 const char *file1;
1662                                 const char *file2;
1663                                 GdkPixbuf *tmp;
1664 
1665 				if (frame_num == -1)
1666 					gnome_bg_slide_show_get_current_slide (show,
1667                                                                                dest_width,
1668                                                                                dest_height,
1669                                                                                &alpha,
1670                                                                                &duration,
1671                                                                                &is_fixed,
1672                                                                                &file1,
1673                                                                                &file2);
1674 				else
1675 					gnome_bg_slide_show_get_slide (show,
1676                                                                        frame_num,
1677                                                                        dest_width,
1678                                                                        dest_height,
1679                                                                        &alpha,
1680                                                                        &duration,
1681                                                                        &is_fixed,
1682                                                                        &file1,
1683                                                                        &file2);
1684 
1685 				if (is_fixed) {
1686 					tmp = get_as_thumbnail (bg, factory, file1);
1687 					if (tmp) {
1688 						thumb = scale_thumbnail (bg->placement,
1689 									 file1,
1690 									 tmp,
1691 									 screen,
1692 									 dest_width,
1693 									 dest_height);
1694 						g_object_unref (tmp);
1695 					}
1696 				}
1697 				else {
1698 					GdkPixbuf *p1, *p2;
1699 					p1 = get_as_thumbnail (bg, factory, file1);
1700 					p2 = get_as_thumbnail (bg, factory, file2);
1701 
1702 					if (p1 && p2) {
1703 						GdkPixbuf *thumb1, *thumb2;
1704 
1705 						thumb1 = scale_thumbnail (bg->placement,
1706 									  file1,
1707 									  p1,
1708 									  screen,
1709 									  dest_width,
1710 									  dest_height);
1711 
1712 						thumb2 = scale_thumbnail (bg->placement,
1713 									  file2,
1714 									  p2,
1715 									  screen,
1716 									  dest_width,
1717 									  dest_height);
1718 
1719 						thumb = blend (thumb1, thumb2, alpha);
1720 
1721 						g_object_unref (thumb1);
1722 						g_object_unref (thumb2);
1723 					}
1724 					if (p1)
1725 						g_object_unref (p1);
1726 					if (p2)
1727 						g_object_unref (p2);
1728 				}
1729 
1730 				ensure_timeout (bg, (guint)get_slide_timeout (is_fixed, duration));
1731 
1732 				g_object_unref (show);
1733 			}
1734 		}
1735 
1736 		return thumb;
1737 	}
1738 
1739 	return NULL;
1740 }
1741 
1742 static GdkPixbuf *
get_pixbuf_for_size(GnomeBG * bg,gint num_monitor,gint best_width,gint best_height)1743 get_pixbuf_for_size (GnomeBG *bg,
1744 		     gint num_monitor,
1745 		     gint best_width,
1746 		     gint best_height)
1747 {
1748 	guint time_until_next_change;
1749 	gboolean hit_cache = FALSE;
1750 
1751 	/* only hit the cache if the aspect ratio matches */
1752 	if (bg->pixbuf_cache) {
1753 		int width, height;
1754 		width = gdk_pixbuf_get_width (bg->pixbuf_cache);
1755 		height = gdk_pixbuf_get_height (bg->pixbuf_cache);
1756 		hit_cache = 0.2 > fabs ((best_width / (double)best_height) - (width / (double)height));
1757 		if (!hit_cache) {
1758 			g_object_unref (bg->pixbuf_cache);
1759 			bg->pixbuf_cache = NULL;
1760 		}
1761 	}
1762 
1763 	if (!hit_cache && bg->filename) {
1764 		bg->file_mtime = get_mtime (bg->filename);
1765 
1766 		bg->pixbuf_cache = get_as_pixbuf_for_size (bg, bg->filename, num_monitor, best_width, best_height);
1767 		time_until_next_change = G_MAXUINT;
1768 		if (!bg->pixbuf_cache) {
1769 			GnomeBGSlideShow *show = get_as_slideshow (bg, bg->filename);
1770 
1771 			if (show) {
1772 				double alpha;
1773                                 double duration;
1774                                 gboolean is_fixed;
1775                                 const char *file1;
1776                                 const char *file2;
1777 
1778 				g_object_ref (show);
1779 
1780 				gnome_bg_slide_show_get_current_slide (show,
1781                                                                        best_width,
1782                                                                        best_height,
1783                                                                        &alpha,
1784                                                                        &duration,
1785                                                                        &is_fixed,
1786                                                                        &file1,
1787                                                                        &file2);
1788 				time_until_next_change = (guint)get_slide_timeout (is_fixed, duration);
1789 				if (is_fixed) {
1790 					bg->pixbuf_cache = get_as_pixbuf_for_size (bg, file1, num_monitor, best_width, best_height);
1791 				}
1792 				else {
1793 					GdkPixbuf *p1, *p2;
1794 					p1 = get_as_pixbuf_for_size (bg, file1, num_monitor, best_width, best_height);
1795 					p2 = get_as_pixbuf_for_size (bg, file2, num_monitor, best_width, best_height);
1796 
1797 					if (p1 && p2) {
1798 						bg->pixbuf_cache = blend (p1, p2, alpha);
1799 					}
1800 					if (p1)
1801 						g_object_unref (p1);
1802 					if (p2)
1803 						g_object_unref (p2);
1804 				}
1805 
1806 				ensure_timeout (bg, time_until_next_change);
1807 
1808 				g_object_unref (show);
1809 			}
1810 		}
1811 
1812 		/* If the next slideshow step is a long time away then
1813 		   we blow away the expensive stuff (large pixbufs) from
1814 		   the cache */
1815 		if (time_until_next_change > KEEP_EXPENSIVE_CACHE_SECS)
1816 		    blow_expensive_caches_in_idle (bg);
1817 	}
1818 
1819 	if (bg->pixbuf_cache)
1820 		g_object_ref (bg->pixbuf_cache);
1821 
1822 	return bg->pixbuf_cache;
1823 }
1824 
1825 static gboolean
is_different(GnomeBG * bg,const char * filename)1826 is_different (GnomeBG    *bg,
1827 	      const char *filename)
1828 {
1829 	if (!filename && bg->filename) {
1830 		return TRUE;
1831 	}
1832 	else if (filename && !bg->filename) {
1833 		return TRUE;
1834 	}
1835 	else if (!filename && !bg->filename) {
1836 		return FALSE;
1837 	}
1838 	else {
1839 		time_t mtime = get_mtime (filename);
1840 
1841 		if (mtime != bg->file_mtime)
1842 			return TRUE;
1843 
1844 		if (strcmp (filename, bg->filename) != 0)
1845 			return TRUE;
1846 
1847 		return FALSE;
1848 	}
1849 }
1850 
1851 static void
clear_cache(GnomeBG * bg)1852 clear_cache (GnomeBG *bg)
1853 {
1854 	GList *list;
1855 
1856 	if (bg->file_cache) {
1857 		for (list = bg->file_cache; list != NULL; list = list->next) {
1858 			FileCacheEntry *ent = list->data;
1859 
1860 			file_cache_entry_delete (ent);
1861 		}
1862 		g_list_free (bg->file_cache);
1863 		bg->file_cache = NULL;
1864 	}
1865 
1866 	if (bg->pixbuf_cache) {
1867 		g_object_unref (bg->pixbuf_cache);
1868 
1869 		bg->pixbuf_cache = NULL;
1870 	}
1871 
1872 	if (bg->timeout_id) {
1873 		g_source_remove (bg->timeout_id);
1874 
1875 		bg->timeout_id = 0;
1876 	}
1877 }
1878 
1879 /* Pixbuf utilities */
1880 static void
pixbuf_average_value(GdkPixbuf * pixbuf,GdkRGBA * result)1881 pixbuf_average_value (GdkPixbuf *pixbuf,
1882                       GdkRGBA   *result)
1883 {
1884 	guint64 a_total, r_total, g_total, b_total;
1885 	guint row, column;
1886 	int row_stride;
1887 	const guchar *pixels, *p;
1888 	int r, g, b, a;
1889 	guint64 dividend;
1890 	guint width, height;
1891 	gdouble dd;
1892 
1893 	width = gdk_pixbuf_get_width (pixbuf);
1894 	height = gdk_pixbuf_get_height (pixbuf);
1895 	row_stride = gdk_pixbuf_get_rowstride (pixbuf);
1896 	pixels = gdk_pixbuf_get_pixels (pixbuf);
1897 
1898 	/* iterate through the pixbuf, counting up each component */
1899 	a_total = 0;
1900 	r_total = 0;
1901 	g_total = 0;
1902 	b_total = 0;
1903 
1904 	if (gdk_pixbuf_get_has_alpha (pixbuf)) {
1905 		for (row = 0; row < height; row++) {
1906 			p = pixels + (row * row_stride);
1907 			for (column = 0; column < width; column++) {
1908 				r = *p++;
1909 				g = *p++;
1910 				b = *p++;
1911 				a = *p++;
1912 
1913 				a_total += a;
1914 				r_total += r * a;
1915 				g_total += g * a;
1916 				b_total += b * a;
1917 			}
1918 		}
1919 		dividend = height * width * 0xFF;
1920 		a_total *= 0xFF;
1921 	} else {
1922 		for (row = 0; row < height; row++) {
1923 			p = pixels + (row * row_stride);
1924 			for (column = 0; column < width; column++) {
1925 				r = *p++;
1926 				g = *p++;
1927 				b = *p++;
1928 
1929 				r_total += r;
1930 				g_total += g;
1931 				b_total += b;
1932 			}
1933 		}
1934 		dividend = height * width;
1935 		a_total = dividend * 0xFF;
1936 	}
1937 
1938 	dd = dividend * 0xFF;
1939 	result->alpha = a_total / dd;
1940 	result->red = r_total / dd;
1941 	result->green = g_total / dd;
1942 	result->blue = b_total / dd;
1943 }
1944 
1945 static GdkPixbuf *
pixbuf_scale_to_fit(GdkPixbuf * src,int max_width,int max_height)1946 pixbuf_scale_to_fit (GdkPixbuf *src, int max_width, int max_height)
1947 {
1948 	double factor;
1949 	int src_width, src_height;
1950 	int new_width, new_height;
1951 
1952 	src_width = gdk_pixbuf_get_width (src);
1953 	src_height = gdk_pixbuf_get_height (src);
1954 
1955 	factor = MIN (max_width  / (double) src_width, max_height / (double) src_height);
1956 
1957 	new_width  = floor (src_width * factor + 0.5);
1958 	new_height = floor (src_height * factor + 0.5);
1959 
1960 	return gdk_pixbuf_scale_simple (src, new_width, new_height, GDK_INTERP_BILINEAR);
1961 }
1962 
1963 static GdkPixbuf *
pixbuf_scale_to_min(GdkPixbuf * src,int min_width,int min_height)1964 pixbuf_scale_to_min (GdkPixbuf *src, int min_width, int min_height)
1965 {
1966 	double factor;
1967 	int src_width, src_height;
1968 	int new_width, new_height;
1969 	GdkPixbuf *dest;
1970 
1971 	src_width = gdk_pixbuf_get_width (src);
1972 	src_height = gdk_pixbuf_get_height (src);
1973 
1974 	factor = MAX (min_width / (double) src_width, min_height / (double) src_height);
1975 
1976 	new_width = floor (src_width * factor + 0.5);
1977 	new_height = floor (src_height * factor + 0.5);
1978 
1979 	dest = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
1980 			       gdk_pixbuf_get_has_alpha (src),
1981 			       8, min_width, min_height);
1982 	if (!dest)
1983 		return NULL;
1984 
1985 	/* crop the result */
1986 	gdk_pixbuf_scale (src, dest,
1987 			  0, 0,
1988 			  min_width, min_height,
1989 			  (new_width - min_width) / -2,
1990 			  (new_height - min_height) / -2,
1991 			  factor,
1992 			  factor,
1993 			  GDK_INTERP_BILINEAR);
1994 	return dest;
1995 }
1996 
1997 static guchar *
create_gradient(const GdkRGBA * primary,const GdkRGBA * secondary,int n_pixels)1998 create_gradient (const GdkRGBA *primary,
1999 		 const GdkRGBA *secondary,
2000 		 int	        n_pixels)
2001 {
2002 	guchar *result = g_malloc (n_pixels * 3);
2003 	int i;
2004 
2005 	for (i = 0; i < n_pixels; ++i) {
2006 		double ratio = (i + 0.5) / n_pixels;
2007 
2008 		result[3 * i + 0] = (int) (0.5 + (primary->red * (1 - ratio) + secondary->red * ratio) * 255);
2009 		result[3 * i + 1] = (int) (0.5 + (primary->green * (1 - ratio) + secondary->green * ratio) * 255);
2010 		result[3 * i + 2] = (int) (0.5 + (primary->blue * (1 - ratio) + secondary->blue * ratio) * 255);
2011 	}
2012 
2013 	return result;
2014 }
2015 
2016 static void
pixbuf_draw_gradient(GdkPixbuf * pixbuf,gboolean horizontal,GdkRGBA * primary,GdkRGBA * secondary,GdkRectangle * rect)2017 pixbuf_draw_gradient (GdkPixbuf    *pixbuf,
2018 		      gboolean      horizontal,
2019 		      GdkRGBA      *primary,
2020 		      GdkRGBA      *secondary,
2021 		      GdkRectangle *rect)
2022 {
2023 	int width;
2024 	int height;
2025 	int rowstride;
2026 	guchar *dst;
2027 	int n_channels = 3;
2028 
2029 	rowstride = gdk_pixbuf_get_rowstride (pixbuf);
2030 	width = rect->width;
2031 	height = rect->height;
2032 	dst = gdk_pixbuf_get_pixels (pixbuf) + rect->x * n_channels + rowstride * rect->y;
2033 
2034 	if (horizontal) {
2035 		guchar *gradient = create_gradient (primary, secondary, width);
2036 		int copy_bytes_per_row = width * n_channels;
2037 		int i;
2038 
2039 		for (i = 0; i < height; i++) {
2040 			guchar *d;
2041 			d = dst + rowstride * i;
2042 			memcpy (d, gradient, copy_bytes_per_row);
2043 		}
2044 		g_free (gradient);
2045 	} else {
2046 		guchar *gb, *gradient;
2047 		int i;
2048 
2049 		gradient = create_gradient (primary, secondary, height);
2050 		for (i = 0; i < height; i++) {
2051 			int j;
2052 			guchar *d;
2053 
2054 			d = dst + rowstride * i;
2055 			gb = gradient + n_channels * i;
2056 			for (j = width; j > 0; j--) {
2057 				int k;
2058 
2059 				for (k = 0; k < n_channels; k++) {
2060 					*(d++) = gb[k];
2061 				}
2062 			}
2063 		}
2064 
2065 		g_free (gradient);
2066 	}
2067 }
2068 
2069 static void
pixbuf_blend(GdkPixbuf * src,GdkPixbuf * dest,int src_x,int src_y,int src_width,int src_height,int dest_x,int dest_y,double alpha)2070 pixbuf_blend (GdkPixbuf *src,
2071 	      GdkPixbuf *dest,
2072 	      int	 src_x,
2073 	      int	 src_y,
2074 	      int	 src_width,
2075 	      int        src_height,
2076 	      int	 dest_x,
2077 	      int	 dest_y,
2078 	      double	 alpha)
2079 {
2080 	int dest_width = gdk_pixbuf_get_width (dest);
2081 	int dest_height = gdk_pixbuf_get_height (dest);
2082 	int offset_x = dest_x - src_x;
2083 	int offset_y = dest_y - src_y;
2084 
2085 	if (src_width < 0)
2086 		src_width = gdk_pixbuf_get_width (src);
2087 
2088 	if (src_height < 0)
2089 		src_height = gdk_pixbuf_get_height (src);
2090 
2091 	if (dest_x < 0)
2092 		dest_x = 0;
2093 
2094 	if (dest_y < 0)
2095 		dest_y = 0;
2096 
2097 	if (dest_x + src_width > dest_width) {
2098 		src_width = dest_width - dest_x;
2099 	}
2100 
2101 	if (dest_y + src_height > dest_height) {
2102 		src_height = dest_height - dest_y;
2103 	}
2104 
2105 	gdk_pixbuf_composite (src, dest,
2106 			      dest_x, dest_y,
2107 			      src_width, src_height,
2108 			      offset_x, offset_y,
2109 			      1, 1, GDK_INTERP_NEAREST,
2110 			      alpha * 0xFF + 0.5);
2111 }
2112 
2113 static void
pixbuf_tile(GdkPixbuf * src,GdkPixbuf * dest)2114 pixbuf_tile (GdkPixbuf *src, GdkPixbuf *dest)
2115 {
2116 	int x, y;
2117 	int tile_width, tile_height;
2118 	int dest_width = gdk_pixbuf_get_width (dest);
2119 	int dest_height = gdk_pixbuf_get_height (dest);
2120 	tile_width = gdk_pixbuf_get_width (src);
2121 	tile_height = gdk_pixbuf_get_height (src);
2122 
2123 	for (y = 0; y < dest_height; y += tile_height) {
2124 		for (x = 0; x < dest_width; x += tile_width) {
2125 			pixbuf_blend (src, dest, 0, 0,
2126 				      tile_width, tile_height, x, y, 1.0);
2127 		}
2128 	}
2129 }
2130 
2131 static GnomeBGSlideShow *
read_slideshow_file(const char * filename,GError ** err)2132 read_slideshow_file (const char *filename,
2133 		     GError     **err)
2134 {
2135         GnomeBGSlideShow *show;
2136 
2137         show = gnome_bg_slide_show_new (filename);
2138 
2139         if (!gnome_bg_slide_show_load (show, err)) {
2140             g_object_unref (show);
2141             return NULL;
2142         }
2143 
2144         return show;
2145 }
2146 
2147 /* Thumbnail utilities */
2148 static GdkPixbuf *
create_thumbnail_for_filename(GnomeDesktopThumbnailFactory * factory,const char * filename)2149 create_thumbnail_for_filename (GnomeDesktopThumbnailFactory *factory,
2150 			       const char            *filename)
2151 {
2152 	char *thumb;
2153 	time_t mtime;
2154 	GdkPixbuf *orig, *result = NULL;
2155 	char *uri;
2156 
2157 	mtime = get_mtime (filename);
2158 
2159 	if (mtime == (time_t)-1)
2160 		return NULL;
2161 
2162 	uri = g_filename_to_uri (filename, NULL, NULL);
2163 
2164 	if (uri == NULL)
2165 		return NULL;
2166 
2167 	thumb = gnome_desktop_thumbnail_factory_lookup (factory, uri, mtime);
2168 
2169 	if (thumb) {
2170 		result = gdk_pixbuf_new_from_file (thumb, NULL);
2171 		g_free (thumb);
2172 	}
2173 	else {
2174 		orig = gdk_pixbuf_new_from_file (filename, NULL);
2175 		if (orig) {
2176 			int orig_width, orig_height;
2177 			GdkPixbuf *rotated;
2178 
2179 			rotated = gdk_pixbuf_apply_embedded_orientation (orig);
2180 			if (rotated != NULL) {
2181 				g_object_unref (orig);
2182 				orig = rotated;
2183 			}
2184 
2185 			orig_width = gdk_pixbuf_get_width (orig);
2186 			orig_height = gdk_pixbuf_get_height (orig);
2187 
2188 			result = pixbuf_scale_to_fit (orig, THUMBNAIL_SIZE, THUMBNAIL_SIZE);
2189 
2190 			g_object_set_data_full (G_OBJECT (result), "gnome-thumbnail-height",
2191 						g_strdup_printf ("%d", orig_height), g_free);
2192 			g_object_set_data_full (G_OBJECT (result), "gnome-thumbnail-width",
2193 						g_strdup_printf ("%d", orig_width), g_free);
2194 
2195 			g_object_unref (orig);
2196 
2197 			gnome_desktop_thumbnail_factory_save_thumbnail (factory, result, uri, mtime);
2198 		}
2199 		else {
2200 			gnome_desktop_thumbnail_factory_create_failed_thumbnail (factory, uri, mtime);
2201 		}
2202 	}
2203 
2204 	g_free (uri);
2205 
2206 	return result;
2207 }
2208 
2209 static gboolean
get_thumb_annotations(GdkPixbuf * thumb,int * orig_width,int * orig_height)2210 get_thumb_annotations (GdkPixbuf *thumb,
2211 		       int	 *orig_width,
2212 		       int	 *orig_height)
2213 {
2214 	char *end;
2215 	const char *wstr, *hstr;
2216 
2217 	wstr = gdk_pixbuf_get_option (thumb, "tEXt::Thumb::Image::Width");
2218 	hstr = gdk_pixbuf_get_option (thumb, "tEXt::Thumb::Image::Height");
2219 
2220 	if (hstr && wstr) {
2221 		*orig_width = strtol (wstr, &end, 10);
2222 		if (*end != 0)
2223 			return FALSE;
2224 
2225 		*orig_height = strtol (hstr, &end, 10);
2226 		if (*end != 0)
2227 			return FALSE;
2228 
2229 		return TRUE;
2230 	}
2231 
2232 	return FALSE;
2233 }
2234 
2235 /*
2236  * Returns whether the background is a slideshow.
2237  */
2238 gboolean
gnome_bg_changes_with_time(GnomeBG * bg)2239 gnome_bg_changes_with_time (GnomeBG *bg)
2240 {
2241 	GnomeBGSlideShow *show;
2242 	gboolean ret = FALSE;
2243 
2244 	g_return_val_if_fail (bg != NULL, FALSE);
2245 
2246 	if (!bg->filename)
2247 		return FALSE;
2248 
2249 	show = get_as_slideshow (bg, bg->filename);
2250 	if (show) {
2251 		ret = gnome_bg_slide_show_get_num_slides (show) > 1;
2252 		g_object_unref (show);
2253 	}
2254 
2255 	return ret;
2256 }
2257 
2258 /**
2259  * gnome_bg_create_frame_thumbnail:
2260  *
2261  * Creates a thumbnail for a certain frame, where 'frame' is somewhat
2262  * vaguely defined as 'suitable point to show while single-stepping
2263  * through the slideshow'.
2264  *
2265  * Returns: (transfer full): the newly created thumbnail or
2266  * or NULL if frame_num is out of bounds.
2267  */
2268 GdkPixbuf *
gnome_bg_create_frame_thumbnail(GnomeBG * bg,GnomeDesktopThumbnailFactory * factory,GdkScreen * screen,int dest_width,int dest_height,int frame_num)2269 gnome_bg_create_frame_thumbnail (GnomeBG			*bg,
2270 				 GnomeDesktopThumbnailFactory	*factory,
2271 				 GdkScreen			*screen,
2272 				 int				 dest_width,
2273 				 int				 dest_height,
2274 				 int				 frame_num)
2275 {
2276 	GnomeBGSlideShow *show;
2277 	GdkPixbuf *result;
2278 	GdkPixbuf *thumb;
2279         int skipped;
2280         gboolean is_fixed;
2281 
2282 	g_return_val_if_fail (bg != NULL, FALSE);
2283 
2284 	show = get_as_slideshow (bg, bg->filename);
2285 
2286 	if (!show)
2287 		return NULL;
2288 
2289 
2290 	if (frame_num < 0 || frame_num >= gnome_bg_slide_show_get_num_slides (show))
2291 		return NULL;
2292 
2293 
2294         gnome_bg_slide_show_get_slide (show, frame_num, dest_width, dest_height, NULL, NULL, &is_fixed, NULL, NULL);
2295 
2296 	skipped = 0;
2297         while (!is_fixed) {
2298             skipped++;
2299             gnome_bg_slide_show_get_slide (show, frame_num, dest_width, dest_height, NULL, NULL, &is_fixed, NULL, NULL);
2300         }
2301 
2302 	result = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, dest_width, dest_height);
2303 
2304 	draw_color (bg, result);
2305 
2306 	if (bg->placement != G_DESKTOP_BACKGROUND_STYLE_NONE) {
2307 		thumb = create_img_thumbnail (bg, factory, screen, dest_width, dest_height, frame_num + skipped);
2308 
2309 		if (thumb) {
2310 			draw_image_for_thumb (bg, thumb, result);
2311 			g_object_unref (thumb);
2312 		}
2313 	}
2314 
2315 	return result;
2316 }
2317 
2318