hythmbox-2.98/widgets/rb-fading-image.c

No issues found

  1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
  2  *
  3  *  Copyright (C) 2012 Jonathan Matthew <jonathan@d14n.org>
  4  *
  5  *  This program is free software; you can redistribute it and/or modify
  6  *  it under the terms of the GNU General Public License as published by
  7  *  the Free Software Foundation; either version 2 of the License, or
  8  *  (at your option) any later version.
  9  *
 10  *  The Rhythmbox authors hereby grant permission for non-GPL compatible
 11  *  GStreamer plugins to be used and distributed together with GStreamer
 12  *  and Rhythmbox. This permission is above and beyond the permissions granted
 13  *  by the GPL license by which Rhythmbox is covered. If you modify this code
 14  *  you may extend this exception to your version of the code, but you are not
 15  *  obligated to do so. If you do not wish to do so, delete this exception
 16  *  statement from your version.
 17  *
 18  *  This program is distributed in the hope that it will be useful,
 19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 21  *  GNU General Public License for more details.
 22  *
 23  *  You should have received a copy of the GNU General Public License
 24  *  along with this program; if not, write to the Free Software
 25  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 26  *
 27  */
 28 
 29 #include <config.h>
 30 
 31 #include <glib/gi18n.h>
 32 
 33 #include <widgets/rb-fading-image.h>
 34 #include <lib/rb-debug.h>
 35 #include <lib/rb-util.h>
 36 
 37 #define RENDER_FRAME_TIME	(1000 / 25)	/* fps? */
 38 #define BORDER_WIDTH		1.0
 39 
 40 #define MAX_TOOLTIP_SIZE	256
 41 
 42 static void rb_fading_image_class_init (RBFadingImageClass *klass);
 43 static void rb_fading_image_init (RBFadingImage *image);
 44 
 45 struct _RBFadingImagePrivate
 46 {
 47 	char *fallback_icon;
 48 	cairo_pattern_t *current_pat;
 49 	cairo_pattern_t *next_pat;
 50 	cairo_pattern_t *fallback_pat;
 51 	gdouble alpha;
 52 
 53 	GdkPixbuf *current;
 54 	int current_width;
 55 	int current_height;
 56 	GdkPixbuf *current_full;
 57 	GdkPixbuf *next;
 58 	GdkPixbuf *next_full;
 59 	GdkPixbuf *fallback;
 60 	GdkPixbufLoader *loader;
 61 	gboolean next_set;
 62 
 63 	guint64 start;
 64 	guint64 end;
 65 	gulong render_timer_id;
 66 };
 67 
 68 G_DEFINE_TYPE (RBFadingImage, rb_fading_image, GTK_TYPE_WIDGET)
 69 
 70 /**
 71  * SECTION:rb-fading-image
 72  * @short_description: image display widget that fades between two images
 73  *
 74  * This widget displays images, performing a simple fade transition between
 75  * them.  It also emits signals when URIs or pixbufs are dropped onto it.
 76  */
 77 
 78 enum
 79 {
 80 	PROP_0,
 81 	PROP_FALLBACK
 82 };
 83 
 84 enum
 85 {
 86 	URI_DROPPED,
 87 	PIXBUF_DROPPED,
 88 	LAST_SIGNAL
 89 };
 90 
 91 static guint signals[LAST_SIGNAL] = {0};
 92 
 93 static gboolean
 94 prepare_image (cairo_t *cr, cairo_pattern_t **save, GdkPixbuf *pixbuf)
 95 {
 96 	if (*save != NULL) {
 97 		cairo_set_source (cr, *save);
 98 		return TRUE;
 99 	}
100 
101 	if (pixbuf != NULL) {
102 		gdk_cairo_set_source_pixbuf (cr, pixbuf, 0.0, 0.0);
103 		*save = cairo_get_source (cr);
104 		cairo_pattern_reference (*save);
105 		return TRUE;
106 	} else {
107 		return FALSE;
108 	}
109 }
110 
111 static void
112 impl_realize (GtkWidget *widget)
113 {
114 	GtkAllocation allocation;
115 	GdkWindowAttr attributes;
116 	GdkWindow *window;
117 	int attributes_mask;
118 
119 	gtk_widget_set_realized (widget, TRUE);
120 
121 	gtk_widget_get_allocation (widget, &allocation);
122 
123 	attributes.x = allocation.x;
124 	attributes.y = allocation.y;
125 	attributes.width = allocation.width;
126 	attributes.height = allocation.height;
127 	attributes.wclass = GDK_INPUT_OUTPUT;
128 	attributes.window_type = GDK_WINDOW_CHILD;
129 	attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_FOCUS_CHANGE_MASK;
130 	attributes.visual = gtk_widget_get_visual (widget);
131 
132 	attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
133 
134 	window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
135 	gtk_widget_set_window (widget, window);
136 	gdk_window_set_user_data (window, widget);
137 
138 	gtk_widget_set_can_focus (widget, TRUE);
139 }
140 
141 static void
142 draw_image (cairo_t *cr, int image_width, int image_height, int width, int height, cairo_extend_t extend, double alpha, gboolean border)
143 {
144 	cairo_matrix_t matrix;
145 	cairo_save (cr);
146 
147 	if (border) {
148 		cairo_matrix_init_translate (&matrix,
149 					     - (BORDER_WIDTH + (width/2 - image_width/2)),
150 					     - (BORDER_WIDTH + (height/2 - image_height/2)));
151 	} else {
152 		cairo_matrix_init_translate (&matrix,
153 					     - (width/2 - image_width/2),
154 					     - (height/2 - image_height/2));
155 	}
156 	cairo_pattern_set_matrix (cairo_get_source (cr), &matrix);
157 	cairo_pattern_set_filter (cairo_get_source (cr), CAIRO_FILTER_BEST);
158 	cairo_pattern_set_extend (cairo_get_source (cr), extend);
159 	cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
160 
161 	cairo_rectangle (cr, BORDER_WIDTH, BORDER_WIDTH, width, height);
162 	cairo_clip (cr);
163 	cairo_paint_with_alpha (cr, alpha);
164 
165 	cairo_restore (cr);
166 }
167 
168 static void
169 render_current (RBFadingImage *image, cairo_t *cr, int width, int height, gboolean border)
170 {
171 	if (prepare_image (cr, &image->priv->current_pat, image->priv->current)) {
172 		draw_image (cr,
173 			    image->priv->current_width,
174 			    image->priv->current_height,
175 			    width,
176 			    height,
177 			    CAIRO_EXTEND_NONE,
178 			    1.0 - image->priv->alpha,
179 			    border);
180 	} else if (prepare_image (cr, &image->priv->fallback_pat, image->priv->fallback)) {
181 		draw_image (cr,
182 			    gdk_pixbuf_get_width (image->priv->fallback),
183 			    gdk_pixbuf_get_height (image->priv->fallback),
184 			    width,
185 			    height,
186 			    CAIRO_EXTEND_PAD,
187 			    1.0 - image->priv->alpha,
188 			    border);
189 	} else {
190 		cairo_save (cr);
191 		cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
192 		cairo_rectangle (cr,
193 				 border ? BORDER_WIDTH : 0,
194 				 border ? BORDER_WIDTH : 0,
195 				 width,
196 				 height);
197 		cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
198 		cairo_clip (cr);
199 		cairo_paint (cr);
200 		cairo_restore (cr);
201 	}
202 }
203 
204 static void
205 render_next (RBFadingImage *image, cairo_t *cr, int width, int height, gboolean border)
206 {
207 	if (image->priv->alpha < 0.001) {
208 		/* do nothing */
209 	} else if (prepare_image (cr, &image->priv->next_pat, image->priv->next)) {
210 		draw_image (cr,
211 			    gdk_pixbuf_get_width (image->priv->next),
212 			    gdk_pixbuf_get_height (image->priv->next),
213 			    width,
214 			    height,
215 			    CAIRO_EXTEND_NONE,
216 			    image->priv->alpha,
217 			    border);
218 	} else if (prepare_image (cr, &image->priv->fallback_pat, image->priv->fallback)) {
219 		draw_image (cr,
220 			    gdk_pixbuf_get_width (image->priv->fallback),
221 			    gdk_pixbuf_get_height (image->priv->fallback),
222 			    width,
223 			    height,
224 			    CAIRO_EXTEND_PAD,
225 			    image->priv->alpha,
226 			    border);
227 	} else {
228 		/* also do nothing */
229 	}
230 }
231 
232 static gboolean
233 impl_draw (GtkWidget *widget, cairo_t *cr)
234 {
235 	RBFadingImage *image;
236 	int border_width;
237 	int border_height;
238 	int width;
239 	int height;
240 
241 	width = gtk_widget_get_allocated_width (widget);
242 	height = gtk_widget_get_allocated_height (widget);
243 	border_width = width;
244 	border_height = height;
245 
246 	image = RB_FADING_IMAGE (widget);
247 	if (image->priv->alpha > 0.01) {
248 		if (image->priv->next) {
249 			border_width = gdk_pixbuf_get_width (image->priv->next) + 2 * BORDER_WIDTH;
250 			border_height = gdk_pixbuf_get_height (image->priv->next) + 2 * BORDER_WIDTH;
251 		}
252 	} else if (image->priv->current) {
253 		border_width = gdk_pixbuf_get_width (image->priv->current) + 2 * BORDER_WIDTH;
254 		border_height = gdk_pixbuf_get_height (image->priv->current) + 2 * BORDER_WIDTH;
255 	}
256 
257 	cairo_save (cr);
258 	cairo_set_line_width (cr, BORDER_WIDTH);
259 	cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
260 	cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
261 	cairo_rectangle (cr,
262 			 (width - border_width) / 2,
263 			 (height - border_height) / 2,
264 			 border_width,
265 			 border_height);
266 	cairo_stroke (cr);
267 	cairo_restore (cr);
268 
269 	width -= 2 * BORDER_WIDTH;
270 	height -= 2 * BORDER_WIDTH;
271 
272 	render_current (image, cr, width, height, TRUE);
273 	render_next (image, cr, width, height, TRUE);
274 
275 	return TRUE;
276 }
277 
278 static gboolean
279 impl_query_tooltip (GtkWidget *widget, int x, int y, gboolean keyboard_mode, GtkTooltip *tooltip)
280 {
281 	RBFadingImage *image = RB_FADING_IMAGE (widget);
282 	GdkPixbuf *scaled;
283 	GdkPixbuf *full;
284 
285 	if (image->priv->render_timer_id != 0) {
286 		full = image->priv->next_full;
287 		scaled = image->priv->next;
288 	} else {
289 		full = image->priv->current_full;
290 		scaled = image->priv->current;
291 	}
292 
293 	if (full == NULL) {
294 		gtk_tooltip_set_icon (tooltip, NULL);
295 		gtk_tooltip_set_text (tooltip, _("Drop artwork here"));
296 		return TRUE;
297 	} else if (full == scaled) {
298 		return FALSE;
299 	} else {
300 		gtk_tooltip_set_icon (tooltip, full);
301 		return TRUE;
302 	}
303 }
304 
305 static void
306 impl_drag_data_received (GtkWidget *widget,
307 			 GdkDragContext *context,
308 			 int x,
309 			 int y,
310 			 GtkSelectionData *selection,
311 			 guint info,
312 			 guint time_)
313 {
314 	GdkPixbuf *pixbuf;
315 	char **uris;
316 
317 	pixbuf = gtk_selection_data_get_pixbuf (selection);
318 	if (pixbuf != NULL) {
319 		g_signal_emit (widget, signals[PIXBUF_DROPPED], 0, pixbuf);
320 		g_object_unref (pixbuf);
321 		return;
322 	}
323 
324 	uris = gtk_selection_data_get_uris (selection);
325 	if (uris != NULL) {
326 		if (uris[0] != NULL) {
327 			g_signal_emit (widget, signals[URI_DROPPED], 0, uris[0]);
328 		}
329 
330 		g_strfreev (uris);
331 		return;
332 	}
333 
334 	rb_debug ("weird drag data received");
335 }
336 
337 static void
338 impl_drag_data_get (GtkWidget *widget, GdkDragContext *context, GtkSelectionData *selection, guint info, guint time_)
339 {
340 	RBFadingImage *image = RB_FADING_IMAGE (widget);
341 
342 	if (image->priv->current_full) {
343 		gtk_selection_data_set_pixbuf (selection, image->priv->current_full);
344 	}
345 
346 	/* might be nice if we could provide a uri here? */
347 }
348 
349 
350 static void
351 impl_finalize (GObject *object)
352 {
353 	RBFadingImage *image = RB_FADING_IMAGE (object);
354 
355 	g_free (image->priv->fallback_icon);
356 
357 	if (image->priv->current_pat != NULL) {
358 		cairo_pattern_destroy (image->priv->current_pat);
359 	}
360 	if (image->priv->next_pat != NULL) {
361 		cairo_pattern_destroy (image->priv->next_pat);
362 	}
363 	if (image->priv->fallback_pat != NULL) {
364 		cairo_pattern_destroy (image->priv->fallback_pat);
365 	}
366 
367 	G_OBJECT_CLASS (rb_fading_image_parent_class)->finalize (object);
368 }
369 
370 static void
371 impl_dispose (GObject *object)
372 {
373 	RBFadingImage *image = RB_FADING_IMAGE (object);
374 
375 	if (image->priv->render_timer_id != 0) {
376 		g_source_remove (image->priv->render_timer_id);
377 		image->priv->render_timer_id = 0;
378 	}
379 	if (image->priv->current != NULL) {
380 		g_object_unref (image->priv->current);
381 		image->priv->current = NULL;
382 	}
383 	if (image->priv->next != NULL) {
384 		g_object_unref (image->priv->next);
385 		image->priv->next = NULL;
386 	}
387 	if (image->priv->fallback != NULL) {
388 		g_object_unref (image->priv->fallback);
389 		image->priv->fallback = NULL;
390 	}
391 
392 	G_OBJECT_CLASS (rb_fading_image_parent_class)->dispose (object);
393 }
394 
395 static void
396 impl_constructed (GObject *object)
397 {
398 	RBFadingImage *image;
399 
400 	RB_CHAIN_GOBJECT_METHOD (rb_fading_image_parent_class, constructed, object);
401 
402 	image = RB_FADING_IMAGE (object);
403 
404 	gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (image)),
405 				     GTK_STYLE_CLASS_SPINNER);
406 
407 	if (image->priv->fallback_icon != NULL) {
408 		GError *error = NULL;
409 		image->priv->fallback =
410 			gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
411 						  image->priv->fallback_icon,
412 						  48,
413 						  GTK_ICON_LOOKUP_FORCE_SIZE,
414 						  &error);
415 		if (error != NULL) {
416 			g_warning ("couldn't load fallback icon %s: %s", image->priv->fallback_icon, error->message);
417 			g_clear_error (&error);
418 		}
419 	}
420 
421 	gtk_widget_set_has_tooltip (GTK_WIDGET (image), TRUE);
422 
423 	/* drag and drop target */
424 	gtk_drag_dest_set (GTK_WIDGET (image), GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY);
425 	gtk_drag_dest_add_image_targets (GTK_WIDGET (image));
426 	gtk_drag_dest_add_uri_targets (GTK_WIDGET (image));
427 
428 	/* drag and drop source */
429 	gtk_drag_source_set (GTK_WIDGET (image), GDK_BUTTON1_MASK, NULL, 0, GDK_ACTION_COPY);
430 	gtk_drag_source_add_image_targets (GTK_WIDGET (image));
431 }
432 
433 
434 static void
435 impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
436 {
437 	RBFadingImage *image = RB_FADING_IMAGE (object);
438 
439 	switch (prop_id) {
440 	case PROP_FALLBACK:
441 		g_value_set_string (value, image->priv->fallback_icon);
442 		break;
443 	default:
444 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
445 		break;
446 	}
447 }
448 
449 static void
450 impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
451 {
452 	RBFadingImage *image = RB_FADING_IMAGE (object);
453 
454 	switch (prop_id) {
455 	case PROP_FALLBACK:
456 		image->priv->fallback_icon = g_value_dup_string (value);
457 		break;
458 	default:
459 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
460 		break;
461 	}
462 }
463 
464 static void
465 rb_fading_image_init (RBFadingImage *image)
466 {
467 	image->priv = G_TYPE_INSTANCE_GET_PRIVATE (image, RB_TYPE_FADING_IMAGE, RBFadingImagePrivate);
468 }
469 
470 static void
471 rb_fading_image_class_init (RBFadingImageClass *klass)
472 {
473 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
474 	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
475 
476 	object_class->constructed = impl_constructed;
477 	object_class->dispose = impl_dispose;
478 	object_class->finalize = impl_finalize;
479 	object_class->set_property = impl_set_property;
480 	object_class->get_property = impl_get_property;
481 
482 	widget_class->realize = impl_realize;
483 	widget_class->draw = impl_draw;
484 	widget_class->query_tooltip = impl_query_tooltip;
485 	widget_class->drag_data_get = impl_drag_data_get;
486 	widget_class->drag_data_received = impl_drag_data_received;
487 
488 	/**
489 	 * RBFadingImage:fallback:
490 	 *
491 	 * Name of an icon to display when no image is available.
492 	 */
493 	g_object_class_install_property (object_class,
494 					 PROP_FALLBACK,
495 					 g_param_spec_string ("fallback",
496 							      "fallback",
497 							      "fallback icon name",
498 							      NULL,
499 							      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
500 
501 	/**
502 	 * RBFadingImage::uri-dropped
503 	 * @image: the #RBFadingImage
504 	 * @uri: the URI that was dropped
505 	 *
506 	 * Emitted when a URI is dragged and dropped on the image
507 	 */
508 	signals[URI_DROPPED] =
509 		g_signal_new ("uri-dropped",
510 			      G_OBJECT_CLASS_TYPE (object_class),
511 			      G_SIGNAL_RUN_LAST,
512 			      0,
513 			      NULL, NULL,
514 			      g_cclosure_marshal_VOID__STRING,
515 			      G_TYPE_NONE,
516 			      1, G_TYPE_STRING);
517 
518 	/**
519 	 * RBFadingImage::pixbuf-dropped
520 	 * @image: the #RBFadingImage
521 	 * @pixbuf: the pixbuf that was dropped
522 	 *
523 	 * Emitted when an image is dragged and dropped on the image
524 	 */
525 	signals[PIXBUF_DROPPED] =
526 		g_signal_new ("pixbuf-dropped",
527 			      G_OBJECT_CLASS_TYPE (object_class),
528 			      G_SIGNAL_RUN_LAST,
529 			      0,
530 			      NULL, NULL,
531 			      g_cclosure_marshal_VOID__OBJECT,
532 			      G_TYPE_NONE,
533 			      1, GDK_TYPE_PIXBUF);
534 
535 	g_type_class_add_private (klass, sizeof (RBFadingImagePrivate));
536 }
537 
538 
539 static GdkPixbuf *
540 scale_thumbnail_if_necessary (RBFadingImage *image, GdkPixbuf *pixbuf)
541 {
542 	int w, h;
543 	int pw, ph;
544 	int sw, sh;
545 	double factor;
546 
547 	w = gtk_widget_get_allocated_width (GTK_WIDGET (image)) - 2 * BORDER_WIDTH;
548 	h = gtk_widget_get_allocated_height (GTK_WIDGET (image)) - 2 * BORDER_WIDTH;
549 	pw = gdk_pixbuf_get_width (pixbuf);
550 	ph = gdk_pixbuf_get_height (pixbuf);
551 
552 	if (pw <= w && ph <= h) {
553 		return g_object_ref (pixbuf);
554 	}
555 
556 	if (pw > ph) {
557 		sw = w;
558 		factor = (double) w / pw;
559 		sh = (int)((double)ph * factor);
560 	} else {
561 		sh = h;
562 		factor = (double) h / ph;
563 		sw = (int)((double)pw * factor);
564 	}
565 
566 	return gdk_pixbuf_scale_simple (pixbuf, sw, sh, GDK_INTERP_HYPER);
567 }
568 
569 static GdkPixbuf *
570 scale_full_if_necessary (RBFadingImage *image, GdkPixbuf *pixbuf)
571 {
572 	int pw, ph;
573 	int sw, sh;
574 	double factor;
575 
576 	pw = gdk_pixbuf_get_width (pixbuf);
577 	ph = gdk_pixbuf_get_height (pixbuf);
578 
579 	if (pw <= MAX_TOOLTIP_SIZE && ph <= MAX_TOOLTIP_SIZE) {
580 		return g_object_ref (pixbuf);
581 	}
582 	if (pw > ph) {
583 		sw = MAX_TOOLTIP_SIZE;
584 		factor = (double) MAX_TOOLTIP_SIZE / pw;
585 		sh = (int)((double)ph * factor);
586 	} else {
587 		sh = MAX_TOOLTIP_SIZE;
588 		factor = (double) MAX_TOOLTIP_SIZE / ph;
589 		sw = (int)((double)pw * factor);
590 	}
591 
592 	return gdk_pixbuf_scale_simple (pixbuf, sw, sh, GDK_INTERP_HYPER);
593 }
594 
595 
596 static void
597 clear_next (RBFadingImage *image)
598 {
599 	if (image->priv->next_pat != NULL) {
600 		cairo_pattern_destroy (image->priv->next_pat);
601 		image->priv->next_pat = NULL;
602 	}
603 	if (image->priv->next != NULL) {
604 		g_object_unref (image->priv->next);
605 		image->priv->next = NULL;
606 	}
607 	if (image->priv->next_full != NULL) {
608 		g_object_unref (image->priv->next_full);
609 		image->priv->next_full = NULL;
610 	}
611 	image->priv->next_set = FALSE;
612 }
613 
614 static void
615 replace_current (RBFadingImage *image, GdkPixbuf *next, GdkPixbuf *next_full)
616 {
617 	if (image->priv->current_pat != NULL) {
618 		cairo_pattern_destroy (image->priv->current_pat);
619 		image->priv->current_pat = NULL;
620 	}
621 	if (image->priv->current != NULL) {
622 		g_object_unref (image->priv->current);
623 		image->priv->current = NULL;
624 	}
625 	if (image->priv->current_full != NULL) {
626 		g_object_unref (image->priv->current_full);
627 		image->priv->current_full = NULL;
628 	}
629 	if (next != NULL) {
630 		image->priv->current = g_object_ref (next);
631 		image->priv->current_width = gdk_pixbuf_get_width (image->priv->current);
632 		image->priv->current_height = gdk_pixbuf_get_height (image->priv->current);
633 	}
634 	if (next_full != NULL) {
635 		image->priv->current_full = g_object_ref (next_full);
636 	}
637 }
638 
639 static void
640 composite_into_current (RBFadingImage *image)
641 {
642 	cairo_t *cr;
643 	cairo_surface_t *dest;
644 	int width;
645 	int height;
646 
647 	width = gtk_widget_get_allocated_width (GTK_WIDGET (image)) - 2 * BORDER_WIDTH;
648 	height = gtk_widget_get_allocated_height (GTK_WIDGET (image)) - 2 * BORDER_WIDTH;
649 	dest = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
650 
651 	cr = cairo_create (dest);
652 	render_current (image, cr, width, height, FALSE);
653 	render_next (image, cr, width, height, FALSE);
654 
655 	if (image->priv->current_pat != NULL) {
656 		cairo_pattern_destroy (image->priv->current_pat);
657 	}
658 	image->priv->current_pat = cairo_pattern_create_for_surface (dest);
659 	image->priv->current_width = width;
660 	image->priv->current_height = height;
661 }
662 
663 /**
664  * rb_fading_image_set_pixbuf:
665  * @image: a #RBFadingImage
666  * @pixbuf: (transfer none): the next pixbuf to display
667  *
668  * Sets the next image to be displayed.
669  */
670 void
671 rb_fading_image_set_pixbuf (RBFadingImage *image, GdkPixbuf *pixbuf)
672 {
673 	GdkPixbuf *scaled = NULL;
674 	GdkPixbuf *full = NULL;
675 
676 	if (pixbuf != NULL) {
677 		scaled = scale_thumbnail_if_necessary (image, pixbuf);
678 		full = scale_full_if_necessary (image, pixbuf);
679 	}
680 
681 	if (image->priv->render_timer_id != 0) {
682 		composite_into_current (image);
683 		clear_next (image);
684 
685 		image->priv->next_full = full;
686 		image->priv->next = scaled;
687 		image->priv->next_set = TRUE;
688 	} else {
689 		clear_next (image);
690 		replace_current (image, scaled, full);
691 		gtk_widget_queue_draw (GTK_WIDGET (image));
692 		gtk_widget_trigger_tooltip_query (GTK_WIDGET (image));
693 
694 		if (scaled != NULL) {
695 			g_object_unref (scaled);
696 		}
697 		if (full != NULL) {
698 			g_object_unref (full);
699 		}
700 	}
701 }
702 
703 static gboolean
704 render_timer (RBFadingImage *image)
705 {
706 	gint64 now;
707 
708 	now = g_get_monotonic_time ();
709 
710 	/* calculate alpha, whether this is the last frame, etc. */
711 	if (image->priv->next != NULL || image->priv->current != NULL) {
712 		image->priv->alpha = (((double)now - image->priv->start) / (image->priv->end - image->priv->start));
713 		if (image->priv->alpha > 1.0)
714 			image->priv->alpha = 1.0;
715 
716 		gtk_widget_queue_draw (GTK_WIDGET (image));
717 	}
718 
719 	if (now >= image->priv->end) {
720 		replace_current (image, image->priv->next, image->priv->next_full);
721 		clear_next (image);
722 		gtk_widget_trigger_tooltip_query (GTK_WIDGET (image));
723 		image->priv->alpha = 0.0;
724 		image->priv->render_timer_id = 0;
725 		return FALSE;
726 	}
727 
728 	return TRUE;
729 }
730 
731 static void
732 update_render_timer (RBFadingImage *image)
733 {
734 	/* add timer if not already present */
735 	if (image->priv->render_timer_id == 0) {
736 		image->priv->render_timer_id = g_timeout_add (RENDER_FRAME_TIME,
737 							      (GSourceFunc) render_timer,
738 							      image);
739 	}
740 }
741 
742 /**
743  * rb_fading_image_start:
744  * @image: a #RBFadingImage
745  * @duration: length of fade in milliseconds
746  *
747  * Starts fading to the next image.  If no next image has been supplied,
748  * the fallback image will be used instead.  If the next image has been
749  * supplied, but has not finished loading yet, the fade will be delayed
750  * until it finishes.  If the previous fade has not yet finished,
751  * something tricky happens.
752  */
753 void
754 rb_fading_image_start (RBFadingImage *image, guint64 duration)
755 {
756 	image->priv->start = g_get_monotonic_time ();
757 	image->priv->end = image->priv->start + (duration * 1000);
758 
759 	if (image->priv->next_set) {
760 		replace_current (image, image->priv->next, image->priv->next_full);
761 		clear_next (image);
762 		image->priv->next_set = TRUE;
763 	}
764 
765 	update_render_timer (image);
766 }