[gnome-photos] Borrow eog-image.[ch], eog-print.[ch] and friends from Eye of GNOME



commit 980f9d5fca6836e84c36b5962f96a0229b20890d
Author: Debarshi Ray <debarshir gnome org>
Date:   Sat Sep 8 19:25:58 2012 +0200

    Borrow eog-image.[ch], eog-print.[ch] and friends from Eye of GNOME
    
    The following changes were made:
    + removed EogJobModel
    + removed EogJobThumbnail
    + removed thumbnailing code from EogImage
    + replaced Eog marshallers with ones from Photos
    
    eog-image-jpeg.[ch] was left out. It will be used if required in the
    future.
    
    New dependencies are:
    + exempi-2.0 >= 1.99.5
    + gtk+-unix-print-3.0
    + lcms2
    + libexif >= 0.6.14
    + librsvg-2.0 >= 2.26.0
    + zlib

 configure.ac                  |   37 +
 po/POTFILES.in                |    5 +
 src/Makefile.am               |   36 +
 src/eog-enums.h               |   37 +
 src/eog-exif-util.c           |  325 ++++++
 src/eog-exif-util.h           |   59 +
 src/eog-image-private.h       |   97 ++
 src/eog-image-save-info.c     |  147 +++
 src/eog-image-save-info.h     |   57 +
 src/eog-image.c               | 2475 +++++++++++++++++++++++++++++++++++++++++
 src/eog-image.h               |  220 ++++
 src/eog-job-queue.c           |  212 ++++
 src/eog-job-queue.h           |   42 +
 src/eog-jobs.c                |  690 ++++++++++++
 src/eog-jobs.h                |  228 ++++
 src/eog-metadata-reader-jpg.c |  671 +++++++++++
 src/eog-metadata-reader-jpg.h |   55 +
 src/eog-metadata-reader-png.c |  646 +++++++++++
 src/eog-metadata-reader-png.h |   55 +
 src/eog-metadata-reader.c     |  132 +++
 src/eog-metadata-reader.h     |  112 ++
 src/eog-print-image-setup.c   | 1139 +++++++++++++++++++
 src/eog-print-image-setup.h   |   72 ++
 src/eog-print.c               |  521 +++++++++
 src/eog-print.h               |   49 +
 src/eog-transform.c           |  449 ++++++++
 src/eog-transform.h           |   76 ++
 src/eog-uri-converter.c       |  988 ++++++++++++++++
 src/eog-uri-converter.h       |  107 ++
 src/eog-util.c                |  506 +++++++++
 src/eog-util.h                |   74 ++
 src/photos-marshalers.list    |    1 +
 32 files changed, 10320 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 47f77eb..ef13ef6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -26,7 +26,10 @@ GLIB_GSETTINGS
 
 CLUTTER_GTK_MIN_VERSION=1.3.2
 CLUTTER_MIN_VERSION=1.11
+EXEMPI_MIN_VERSION=1.99.5
 GLIB_MIN_VERSION=2.28
+LIBEXIF_MIN_VERSION=0.6.14
+LIBRSVG_MIN_VERSION=2.26.0
 
 GNOME_COMMON_INIT
 GNOME_COMPILE_WARNINGS([maximum])
@@ -43,9 +46,39 @@ AM_GLIB_GNU_GETTEXT
 
 LT_INIT
 
+# ****************************************************************
+# Support for nl_langinfo (_NL_MEASUREMENT_MEASUREMENT) (optional)
+# ****************************************************************
+AC_MSG_CHECKING([for _NL_MEASUREMENT_MEASUREMENT])
+AC_TRY_LINK([#include <langinfo.h>], [
+char c;
+c = *((unsigned char *)  nl_langinfo(_NL_MEASUREMENT_MEASUREMENT));
+], have_langinfo_measurement=yes, have_langinfo_measurement=no)
+AC_MSG_RESULT($have_langinfo_measurement)
+if test "$have_langinfo_measurement" = "yes"; then
+  AC_DEFINE([HAVE__NL_MEASUREMENT_MEASUREMENT], [1],
+            [Define if _NL_MEASUREMENT_MEASUREMENT is available])
+fi
+
+# ***************
+# ZLIB (required)
+# ***************
+
+have_zlib=yes
+AC_CHECK_HEADERS([zlib.h],
+	[AC_CHECK_LIB([z], [inflate],
+		[AC_CHECK_LIB([z], [crc32], [], [have_zlib=no])],
+		[have_zlib=no])],
+	[have_zlib=no])
+
+if test x$have_zlib = xno; then
+	AC_MSG_ERROR([No sufficient zlib library found on your system.])
+fi
+
 PKG_CHECK_MODULES(CAIRO, [cairo])
 PKG_CHECK_MODULES(CLUTTER, [clutter-1.0 >= $CLUTTER_MIN_VERSION])
 PKG_CHECK_MODULES(CLUTTER_GTK, [clutter-gtk-1.0 >= $CLUTTER_GTK_MIN_VERSION])
+PKG_CHECK_MODULES(EXEMPI, [exempi-2.0 >= $EXEMPI_MIN_VERSION])
 PKG_CHECK_MODULES(GDK_PIXBUF, [gdk-pixbuf-2.0])
 PKG_CHECK_MODULES(GLIB, [glib-2.0 >= $GLIB_MIN_VERSION])
 PKG_CHECK_MODULES(GIO, [gio-2.0])
@@ -57,6 +90,10 @@ PKG_CHECK_MODULES(GOA, [goa-1.0])
 AC_DEFINE([GOA_API_IS_SUBJECT_TO_CHANGE], [], [We are aware that GOA's API can change])
 
 PKG_CHECK_MODULES(GTK, [gtk+-3.0])
+PKG_CHECK_MODULES(GTK_UNIX_PRINT, [gtk+-unix-print-3.0])
+PKG_CHECK_MODULES(LCMS, [lcms2])
+PKG_CHECK_MODULES(LIBEXIF, [libexif >= $LIBEXIF_MIN_VERSION])
+PKG_CHECK_MODULES(LIBRSVG, [librsvg-2.0 >= $LIBRSVG_MIN_VERSION])
 PKG_CHECK_MODULES(TRACKER, [tracker-sparql-0.14])
 
 AC_CONFIG_FILES([
diff --git a/po/POTFILES.in b/po/POTFILES.in
index e85a1a3..6a0cdf7 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -2,6 +2,11 @@
 # Please keep this list sorted alphabetically.
 data/gnome-photos.desktop.in.in
 data/org.gnome.photos.gschema.xml.in
+src/eog-exif-util.c
+src/eog-image.c
+src/eog-print-image-setup.c
+src/eog-uri-converter.c
+src/eog-util.c
 src/gd-main-toolbar.c
 src/photos-application.c
 src/photos-load-more-button.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 5ee0d94..3237272 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -15,8 +15,34 @@ gnome_photos_SOURCES = \
 	$(gnome_photos_built_sources) \
 	eog-debug.c \
 	eog-debug.h \
+	eog-enums.h \
+	eog-exif-util.c \
+	eog-exif-util.h \
+	eog-image.c \
+	eog-image.h \
+	eog-image-private.h \
+	eog-image-save-info.c \
+	eog-image-save-info.h \
+	eog-job-queue.c \
+	eog-job-queue.h \
+	eog-jobs.c \
+	eog-jobs.h \
+	eog-metadata-reader.c \
+	eog-metadata-reader.h \
+	eog-metadata-reader-jpg.c \
+	eog-metadata-reader-jpg.h \
+	eog-metadata-reader-png.c \
+	eog-metadata-reader-png.h \
+	eog-print-image-setup.c \
+	eog-print-image-setup.h \
 	eog-print-preview.c \
 	eog-print-preview.h \
+	eog-transform.c \
+	eog-transform.h \
+	eog-uri-converter.c \
+	eog-uri-converter.h \
+	eog-util.c \
+	eog-util.h \
 	gd-main-icon-view.c \
 	gd-main-icon-view.h \
 	gd-main-list-view.c \
@@ -124,12 +150,17 @@ AM_CPPFLAGS = \
 	$(CAIRO_CFLAGS) \
 	$(CLUTTER_CFLAGS) \
 	$(CLUTTER_GTK_CFLAGS) \
+	$(EXEMPI_CFLAGS) \
 	$(GDK_PIXBUF_CFLAGS) \
 	$(GIO_CFLAGS) \
 	$(GLIB_CFLAGS) \
 	$(GNOME_DESKTOP_CFLAGS) \
 	$(GOA_CFLAGS) \
 	$(GTK_CFLAGS) \
+	$(GTK_UNIX_PRINT_CFLAGS) \
+	$(LCMS_CFLAGS) \
+	$(LIBEXIF_CFLAGS) \
+	$(LIBRSVG_CFLAGS) \
 	$(TRACKER_CFLAGS) \
 	$(NULL)
 
@@ -139,12 +170,17 @@ gnome_photos_LDADD = \
 	$(CAIRO_LIBS) \
 	$(CLUTTER_LIBS) \
 	$(CLUTTER_GTK_LIBS) \
+	$(EXEMPI_LIBS) \
 	$(GDK_PIXBUF_LIBS) \
 	$(GIO_LIBS) \
 	$(GLIB_LIBS) \
 	$(GNOME_DESKTOP_LIBS) \
 	$(GOA_LIBS) \
 	$(GTK_LIBS) \
+	$(GTK_UNIX_PRINT_LIBS) \
+	$(LCMS_LIBS) \
+	$(LIBEXIF_LIBS) \
+	$(LIBRSVG_LIBS) \
 	$(TRACKER_LIBS) \
 	$(LIBM) \
 	$(NULL)
diff --git a/src/eog-enums.h b/src/eog-enums.h
new file mode 100644
index 0000000..a3e08fa
--- /dev/null
+++ b/src/eog-enums.h
@@ -0,0 +1,37 @@
+/* Eye of GNOME - General enumerations.
+ *
+ * Copyright (C) 2007-2008 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EOG_ENUMS__
+#define __EOG_ENUMS__
+
+typedef enum {
+	EOG_IMAGE_DATA_IMAGE     = 1 << 0,
+	EOG_IMAGE_DATA_DIMENSION = 1 << 1,
+	EOG_IMAGE_DATA_EXIF      = 1 << 2,
+	EOG_IMAGE_DATA_XMP       = 1 << 3
+} EogImageData;
+
+#define EOG_IMAGE_DATA_ALL  (EOG_IMAGE_DATA_IMAGE |     \
+			     EOG_IMAGE_DATA_DIMENSION | \
+			     EOG_IMAGE_DATA_EXIF |      \
+			     EOG_IMAGE_DATA_XMP)
+
+#endif
diff --git a/src/eog-exif-util.c b/src/eog-exif-util.c
new file mode 100644
index 0000000..a11a3a7
--- /dev/null
+++ b/src/eog-exif-util.c
@@ -0,0 +1,325 @@
+/* Eye Of Gnome - EXIF Utilities
+ *
+ * Copyright (C) 2006-2007 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ * Author: Claudio Saavedra <csaavedra alumnos utalca cl>
+ * Author: Felix Riemann <felix hsgheli de>
+ *
+ * Based on code by:
+ *	- Jens Finke <jens gnome org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef HAVE_STRPTIME
+#define _XOPEN_SOURCE
+#define _XOPEN_SOURCE_EXTENDED 1
+#endif
+#include <time.h>
+
+#include "eog-exif-util.h"
+#include "eog-util.h"
+
+#include <string.h>
+#include <glib/gi18n.h>
+
+#define DATE_BUF_SIZE 200
+
+/* gboolean <-> gpointer conversion macros taken from gedit */
+#ifndef GBOOLEAN_TO_POINTER
+#define GBOOLEAN_TO_POINTER(i) (GINT_TO_POINTER ((i) ? 2 : 1))
+#endif
+#ifndef GPOINTER_TO_BOOLEAN
+#define GPOINTER_TO_BOOLEAN(i) ((gboolean) ((GPOINTER_TO_INT (i) == 2) ? TRUE : FALSE))
+#endif
+
+typedef ExifData EogExifData;
+
+/* Define EogExifData type */
+G_DEFINE_BOXED_TYPE(EogExifData, eog_exif_data, eog_exif_data_copy, eog_exif_data_free)
+
+static gpointer
+_check_strptime_updates_wday (gpointer data)
+{
+	struct tm tm;
+
+	memset (&tm, '\0', sizeof (tm));
+	strptime ("2008:12:24 20:30:45", "%Y:%m:%d %T", &tm);
+	/* Check if tm.tm_wday is set to Wednesday (3) now */
+	return GBOOLEAN_TO_POINTER (tm.tm_wday == 3);
+}
+
+/**
+ * _calculate_wday_yday:
+ * @tm: A struct tm that should be updated.
+ *
+ * Ensure tm_wday and tm_yday are set correctly in a struct tm.
+ * The other date (dmy) values must have been set already.
+ **/
+static void
+_calculate_wday_yday (struct tm *tm)
+{
+	GDate *exif_date;
+	struct tm tmp_tm;
+
+	exif_date = g_date_new_dmy (tm->tm_mday,
+				    tm->tm_mon+1,
+				    tm->tm_year+1900);
+
+	g_return_if_fail (exif_date != NULL && g_date_valid (exif_date));
+
+	// Use this to get GLib <-> libc corrected values
+	g_date_to_struct_tm (exif_date, &tmp_tm);
+	g_date_free (exif_date);
+
+	tm->tm_wday = tmp_tm.tm_wday;
+	tm->tm_yday = tmp_tm.tm_yday;
+}
+
+#ifdef HAVE_STRPTIME
+static gchar *
+eog_exif_util_format_date_with_strptime (const gchar *date)
+{
+	static GOnce strptime_updates_wday = G_ONCE_INIT;
+	gchar *new_date = NULL;
+	gchar tmp_date[DATE_BUF_SIZE];
+	gchar *p;
+	gsize dlen;
+	struct tm tm;
+
+	memset (&tm, '\0', sizeof (tm));
+	p = strptime (date, "%Y:%m:%d %T", &tm);
+
+	if (p == date + strlen (date)) {
+		g_once (&strptime_updates_wday,
+			_check_strptime_updates_wday,
+			NULL);
+
+		// Ensure tm.tm_wday and tm.tm_yday are set
+		if (!GPOINTER_TO_BOOLEAN (strptime_updates_wday.retval))
+			_calculate_wday_yday (&tm);
+
+		/* A strftime-formatted string, to display the date the image was taken.  */
+		dlen = strftime (tmp_date, DATE_BUF_SIZE * sizeof(gchar), _("%a, %d %B %Y  %X"), &tm);
+		new_date = g_strndup (tmp_date, dlen);
+	}
+
+	return new_date;
+}
+#else
+static gchar *
+eog_exif_util_format_date_by_hand (const gchar *date)
+{
+	int year, month, day, hour, minutes, seconds;
+	int result;
+	gchar *new_date = NULL;
+
+	result = sscanf (date, "%d:%d:%d %d:%d:%d",
+			 &year, &month, &day, &hour, &minutes, &seconds);
+
+	if (result < 3 || !g_date_valid_dmy (day, month, year)) {
+		return NULL;
+	} else {
+		gchar tmp_date[DATE_BUF_SIZE];
+		gsize dlen;
+		time_t secs;
+		struct tm tm;
+
+		memset (&tm, '\0', sizeof (tm));
+		tm.tm_mday = day;
+		tm.tm_mon = month-1;
+		tm.tm_year = year-1900;
+		// Calculate tm.tm_wday
+		_calculate_wday_yday (&tm);
+
+		if (result < 5) {
+		  	/* A strftime-formatted string, to display the date the image was taken, for the case we don't have the time.  */
+			dlen = strftime (tmp_date, DATE_BUF_SIZE * sizeof(gchar), _("%a, %d %B %Y"), &tm);
+		} else {
+			tm.tm_sec = result < 6 ? 0 : seconds;
+			tm.tm_min = minutes;
+			tm.tm_hour = hour;
+			/* A strftime-formatted string, to display the date the image was taken.  */
+			dlen = strftime (tmp_date, DATE_BUF_SIZE * sizeof(gchar), _("%a, %d %B %Y  %X"), &tm);
+		}
+
+		if (dlen == 0)
+			return NULL;
+		else
+			new_date = g_strndup (tmp_date, dlen);
+	}
+	return new_date;
+}
+#endif /* HAVE_STRPTIME */
+
+/**
+ * eog_exif_util_format_date:
+ * @date: a date string following Exif specifications
+ *
+ * Takes a date string formatted after Exif specifications and generates a
+ * more readable, possibly localized, string out of it.
+ *
+ * Returns: a newly allocated date string formatted according to the
+ * current locale.
+ */
+gchar *
+eog_exif_util_format_date (const gchar *date)
+{
+	gchar *new_date;
+#ifdef HAVE_STRPTIME
+	new_date = eog_exif_util_format_date_with_strptime (date);
+#else
+	new_date = eog_exif_util_format_date_by_hand (date);
+#endif /* HAVE_STRPTIME */
+	return new_date;
+}
+
+void
+eog_exif_util_set_label_text (GtkLabel *label,
+			      EogExifData *exif_data,
+			      gint tag_id)
+{
+	gchar exif_buffer[512];
+	const gchar *buf_ptr;
+	gchar *label_text = NULL;
+
+	g_return_if_fail (GTK_IS_LABEL (label));
+
+	if (exif_data) {
+		buf_ptr = eog_exif_data_get_value (exif_data, tag_id,
+						   exif_buffer, 512);
+
+		if (tag_id == EXIF_TAG_DATE_TIME_ORIGINAL && buf_ptr)
+			label_text = eog_exif_util_format_date (buf_ptr);
+		else
+			label_text = eog_util_make_valid_utf8 (buf_ptr);
+	}
+
+	gtk_label_set_text (label, label_text);
+	g_free (label_text);
+}
+
+void
+eog_exif_util_set_focal_length_label_text (GtkLabel *label,
+					   EogExifData *exif_data)
+{
+	ExifEntry *entry = NULL, *entry35mm = NULL;
+	ExifByteOrder byte_order;
+	gfloat f_val = 0.0;
+	gchar *fl_text = NULL,*fl35_text = NULL;
+
+	/* If no ExifData is supplied the label will be
+	 * cleared later as fl35_text is NULL. */
+	if (exif_data != NULL) {
+		entry = exif_data_get_entry (exif_data, EXIF_TAG_FOCAL_LENGTH);
+		entry35mm = exif_data_get_entry (exif_data,
+					    EXIF_TAG_FOCAL_LENGTH_IN_35MM_FILM);
+		byte_order = exif_data_get_byte_order (exif_data);
+	}
+
+	if (entry && G_LIKELY (entry->format == EXIF_FORMAT_RATIONAL)) {
+		ExifRational value;
+
+		/* Decode value by hand as libexif is not necessarily returning
+		 * it in the format we want it to be.
+		 */
+		value = exif_get_rational (entry->data, byte_order);
+		/* Guard against div by zero */
+		if (G_LIKELY(value.denominator != 0))
+			f_val = (gfloat)value.numerator/
+				(gfloat)value.denominator;
+
+		/* TRANSLATORS: This is the actual focal length used when
+		   the image was taken.*/
+		fl_text = g_strdup_printf (_("%.1f (lens)"), f_val);
+
+	}
+	if (entry35mm && G_LIKELY (entry35mm->format == EXIF_FORMAT_SHORT)) {
+		ExifShort s_val;
+
+		s_val = exif_get_short (entry35mm->data, byte_order);
+
+		/* Print as float to get a similar look as above. */
+		/* TRANSLATORS: This is the equivalent focal length assuming
+		   a 35mm film camera. */
+		fl35_text = g_strdup_printf(_("%.1f (35mm film)"),(float)s_val);
+	}
+
+	if (fl_text) {
+		if (fl35_text) {
+			gchar *merged_txt;
+
+			merged_txt = g_strconcat (fl35_text,", ", fl_text, NULL);
+			gtk_label_set_text (label, merged_txt);
+			g_free (merged_txt);
+		} else {
+			gtk_label_set_text (label, fl_text);
+		}
+	} else {
+		/* This will also clear the label if no ExifData was supplied */
+		gtk_label_set_text (label, fl35_text);
+	}
+
+	g_free (fl35_text);
+	g_free (fl_text);
+}
+
+/**
+ * eog_exif_data_get_value:
+ * @exif_data: pointer to an <structname>ExifData</structname> struct
+ * @tag_id: the requested tag's id. See <filename>exif-tag.h</filename>
+ * from the libexif package for possible values (e.g. %EXIF_TAG_EXPOSURE_MODE).
+ * @buffer: a pre-allocated output buffer
+ * @buf_size: size of @buffer
+ *
+ * Convenience function to extract a string representation of an Exif tag
+ * directly from an <structname>ExifData</structname> struct. The string is
+ * written into @buffer as far as @buf_size permits.
+ *
+ * Returns: a pointer to @buffer.
+ */
+const gchar *
+eog_exif_data_get_value (EogExifData *exif_data, gint tag_id, gchar *buffer, guint buf_size)
+{
+	ExifEntry *exif_entry;
+	const gchar *exif_value;
+
+        exif_entry = exif_data_get_entry (exif_data, tag_id);
+
+	buffer[0] = 0;
+
+	exif_value = exif_entry_get_value (exif_entry, buffer, buf_size);
+
+	return exif_value;
+}
+
+EogExifData *
+eog_exif_data_copy (EogExifData *data)
+{
+	exif_data_ref (data);
+
+	return data;
+}
+
+void
+eog_exif_data_free (EogExifData *data)
+{
+	exif_data_unref (data);
+}
diff --git a/src/eog-exif-util.h b/src/eog-exif-util.h
new file mode 100644
index 0000000..7646234
--- /dev/null
+++ b/src/eog-exif-util.h
@@ -0,0 +1,59 @@
+/* Eye Of Gnome - EXIF Utilities
+ *
+ * Copyright (C) 2006-2007 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ * Author: Claudio Saavedra <csaavedra alumnos utalca cl>
+ * Author: Felix Riemann <felix hsgheli de>
+ *
+ * Based on code by:
+ *	- Jens Finke <jens gnome org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EOG_EXIF_UTIL_H__
+#define __EOG_EXIF_UTIL_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <libexif/exif-data.h>
+
+G_BEGIN_DECLS
+
+#define EOG_TYPE_EXIF_DATA eog_exif_data_get_type ()
+
+gchar       *eog_exif_util_format_date           (const gchar *date);
+void         eog_exif_util_set_label_text        (GtkLabel *label,
+                                                  ExifData *exif_data,
+                                                  gint tag_id);
+
+void         eog_exif_util_set_focal_length_label_text (GtkLabel *label,
+                                                        ExifData *exif_data);
+
+
+const gchar *eog_exif_data_get_value             (ExifData *exif_data,
+                                                  gint tag_id, gchar *buffer,
+                                                  guint buf_size);
+
+GType        eog_exif_data_get_type              (void) G_GNUC_CONST;
+
+ExifData    *eog_exif_data_copy                  (ExifData *data);
+void         eog_exif_data_free                  (ExifData *data);
+
+G_END_DECLS
+
+#endif /* __EOG_EXIF_UTIL_H__ */
diff --git a/src/eog-image-private.h b/src/eog-image-private.h
new file mode 100644
index 0000000..3af4e90
--- /dev/null
+++ b/src/eog-image-private.h
@@ -0,0 +1,97 @@
+/* Eye Of Gnome - Image Private Data
+ *
+ * Copyright (C) 2007 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EOG_IMAGE_PRIVATE_H__
+#define __EOG_IMAGE_PRIVATE_H__
+
+#include "eog-image.h"
+#ifdef HAVE_RSVG
+#include <librsvg/rsvg.h>
+#endif
+
+G_BEGIN_DECLS
+
+struct _EogImagePrivate {
+	GFile            *file;
+
+	EogImageStatus    status;
+	EogImageStatus    prev_status;
+	gboolean          is_monitored;
+        EogImageMetadataStatus metadata_status;
+
+	GdkPixbuf        *image;
+	GdkPixbufAnimation     *anim;
+	GdkPixbufAnimationIter *anim_iter;
+	gboolean          is_playing;
+	GdkPixbuf        *thumbnail;
+#ifdef HAVE_RSVG
+	RsvgHandle       *svg;
+#endif
+
+	gint              width;
+	gint              height;
+
+	goffset           bytes;
+	gchar            *file_type;
+	gboolean          threadsafe_format;
+
+	/* Holds EXIF raw data */
+	guint             exif_chunk_len;
+	guchar           *exif_chunk;
+
+	/* Holds IPTC raw data */
+	guchar           *iptc_chunk;
+	guint             iptc_chunk_len;
+
+	gboolean          modified;
+	gboolean          file_is_changed;
+
+	gboolean          autorotate;
+	gint              orientation;
+#ifdef HAVE_EXIF
+	ExifData         *exif;
+#endif
+#ifdef HAVE_EXEMPI
+ 	XmpPtr   xmp;
+#endif
+
+#ifdef HAVE_LCMS
+	cmsHPROFILE       profile;
+#endif
+
+	gchar            *caption;
+
+	gchar            *collate_key;
+
+	GMutex            status_mutex;
+
+	gboolean          cancel_loading;
+	guint             data_ref_count;
+
+	GSList           *undo_stack;
+
+	EogTransform     *trans;
+	EogTransform     *trans_autorotate;
+};
+
+G_END_DECLS
+
+#endif /* __EOG_IMAGE_PRIVATE_H__ */
diff --git a/src/eog-image-save-info.c b/src/eog-image-save-info.c
new file mode 100644
index 0000000..c7844d7
--- /dev/null
+++ b/src/eog-image-save-info.c
@@ -0,0 +1,147 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include "eog-image-save-info.h"
+#include "eog-image-private.h"
+#include "eog-image.h"
+#include "photos-utils.h"
+
+G_DEFINE_TYPE (EogImageSaveInfo, eog_image_save_info, G_TYPE_OBJECT)
+
+static void
+eog_image_save_info_dispose (GObject *object)
+{
+	EogImageSaveInfo *info = EOG_IMAGE_SAVE_INFO (object);
+
+	if (info->file != NULL) {
+		g_object_unref (info->file);
+		info->file = NULL;
+	}
+
+	if (info->format != NULL) {
+		g_free (info->format);
+		info->format = NULL;
+	}
+
+	G_OBJECT_CLASS (eog_image_save_info_parent_class)->dispose (object);
+}
+
+static void
+eog_image_save_info_init (EogImageSaveInfo *obj)
+{
+
+}
+
+static void
+eog_image_save_info_class_init (EogImageSaveInfoClass *klass)
+{
+	GObjectClass *object_class = (GObjectClass*) klass;
+
+	object_class->dispose = eog_image_save_info_dispose;
+}
+
+/* is_local_uri:
+ *
+ * Checks if the URI points to a local file system. This tests simply
+ * if the URI scheme is 'file'. This function is used to ensure that
+ * we can write to the path-part of the URI with non-VFS aware
+ * filesystem calls.
+ */
+static gboolean
+is_local_file (GFile *file)
+{
+	char *scheme;
+	gboolean ret;
+
+	g_return_val_if_fail (file != NULL, FALSE);
+
+	scheme = g_file_get_uri_scheme (file);
+
+	ret = (g_ascii_strcasecmp (scheme, "file") == 0);
+	g_free (scheme);
+	return ret;
+}
+
+static char*
+get_save_file_type_by_file (GFile *file)
+{
+	GdkPixbufFormat *format;
+	char *type = NULL;
+
+	format = photos_utils_get_pixbuf_format (file);
+	if (format != NULL) {
+		type = gdk_pixbuf_format_get_name (format);
+	}
+
+	return type;
+}
+
+EogImageSaveInfo*
+eog_image_save_info_new_from_image (EogImage *image)
+{
+	EogImageSaveInfo *info = NULL;
+
+	g_return_val_if_fail (EOG_IS_IMAGE (image), NULL);
+
+	info = g_object_new (EOG_TYPE_IMAGE_SAVE_INFO, NULL);
+
+	info->file         = eog_image_get_file (image);
+	info->format       = g_strdup (image->priv->file_type);
+	info->exists       = g_file_query_exists (info->file, NULL);
+	info->local        = is_local_file (info->file);
+        info->has_metadata = eog_image_has_data (image, EOG_IMAGE_DATA_EXIF);
+	info->modified     = eog_image_is_modified (image);
+	info->overwrite    = FALSE;
+
+	info->jpeg_quality = -1.0;
+
+	return info;
+}
+
+EogImageSaveInfo*
+eog_image_save_info_new_from_uri (const char *txt_uri, GdkPixbufFormat *format)
+{
+	GFile *file;
+	EogImageSaveInfo *info;
+
+	g_return_val_if_fail (txt_uri != NULL, NULL);
+
+	file = g_file_new_for_uri (txt_uri);
+
+	info = eog_image_save_info_new_from_file (file, format);
+
+	g_object_unref (file);
+
+	return info;
+}
+
+EogImageSaveInfo*
+eog_image_save_info_new_from_file (GFile *file, GdkPixbufFormat *format)
+{
+	EogImageSaveInfo *info;
+
+	g_return_val_if_fail (file != NULL, NULL);
+
+	info = g_object_new (EOG_TYPE_IMAGE_SAVE_INFO, NULL);
+
+	info->file = g_object_ref (file);
+	if (format == NULL) {
+		info->format = get_save_file_type_by_file (info->file);
+	}
+	else {
+		info->format = gdk_pixbuf_format_get_name (format);
+	}
+	info->exists       = g_file_query_exists (file, NULL);
+	info->local        = is_local_file (file);
+        info->has_metadata = FALSE;
+	info->modified     = FALSE;
+	info->overwrite    = FALSE;
+
+	info->jpeg_quality = -1.0;
+
+	g_assert (info->format != NULL);
+
+	return info;
+}
diff --git a/src/eog-image-save-info.h b/src/eog-image-save-info.h
new file mode 100644
index 0000000..33d6b27
--- /dev/null
+++ b/src/eog-image-save-info.h
@@ -0,0 +1,57 @@
+#ifndef _EOG_IMAGE_SAVE_INFO_H_
+#define _EOG_IMAGE_SAVE_INFO_H_
+
+#include <glib-object.h>
+#include <gio/gio.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+G_BEGIN_DECLS
+
+#ifndef __EOG_IMAGE_DECLR__
+#define __EOG_IMAGE_DECLR__
+typedef struct _EogImage EogImage;
+#endif
+
+#define EOG_TYPE_IMAGE_SAVE_INFO            (eog_image_save_info_get_type ())
+#define EOG_IMAGE_SAVE_INFO(o)         (G_TYPE_CHECK_INSTANCE_CAST ((o), EOG_TYPE_IMAGE_SAVE_INFO, EogImageSaveInfo))
+#define EOG_IMAGE_SAVE_INFO_CLASS(k)   (G_TYPE_CHECK_CLASS_CAST((k), EOG_TYPE_IMAGE_SAVE_INFO, EogImageSaveInfoClass))
+#define EOG_IS_IMAGE_SAVE_INFO(o)         (G_TYPE_CHECK_INSTANCE_TYPE ((o), EOG_TYPE_IMAGE_SAVE_INFO))
+#define EOG_IS_IMAGE_SAVE_INFO_CLASS(k)   (G_TYPE_CHECK_CLASS_TYPE ((k), EOG_TYPE_IMAGE_SAVE_INFO))
+#define EOG_IMAGE_SAVE_INFO_GET_CLASS(o)  (G_TYPE_INSTANCE_GET_CLASS ((o), EOG_TYPE_IMAGE_SAVE_INFO, EogImageSaveInfoClass))
+
+typedef struct _EogImageSaveInfo EogImageSaveInfo;
+typedef struct _EogImageSaveInfoClass EogImageSaveInfoClass;
+
+struct _EogImageSaveInfo {
+	GObject parent;
+
+	GFile       *file;
+	char        *format;
+	gboolean     exists;
+	gboolean     local;
+	gboolean     has_metadata;
+	gboolean     modified;
+	gboolean     overwrite;
+
+	float        jpeg_quality; /* valid range: [0.0 ... 1.0] */
+};
+
+struct _EogImageSaveInfoClass {
+	GObjectClass parent_klass;
+};
+
+#define EOG_FILE_FORMAT_JPEG   "jpeg"
+
+GType             eog_image_save_info_get_type         (void) G_GNUC_CONST;
+
+EogImageSaveInfo *eog_image_save_info_new_from_image   (EogImage *image);
+
+EogImageSaveInfo *eog_image_save_info_new_from_uri     (const char      *uri,
+							GdkPixbufFormat *format);
+
+EogImageSaveInfo *eog_image_save_info_new_from_file    (GFile           *file,
+							GdkPixbufFormat *format);
+
+G_END_DECLS
+
+#endif /* _EOG_IMAGE_SAVE_INFO_H_ */
diff --git a/src/eog-image.c b/src/eog-image.c
new file mode 100644
index 0000000..c9fe899
--- /dev/null
+++ b/src/eog-image.c
@@ -0,0 +1,2475 @@
+/* Eye Of Gnome - Image
+ *
+ * Copyright (C) 2006 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define GDK_PIXBUF_ENABLE_BACKEND
+
+#include "eog-image.h"
+#include "eog-image-private.h"
+#include "eog-debug.h"
+
+#ifdef HAVE_JPEG
+#include "eog-image-jpeg.h"
+#endif
+
+#include "eog-metadata-reader.h"
+#include "eog-image-save-info.h"
+#include "eog-transform.h"
+#include "eog-util.h"
+#include "eog-jobs.h"
+#include "photos-marshalers.h"
+#include "photos-utils.h"
+
+#include <unistd.h>
+#include <string.h>
+
+#include <glib.h>
+#include <glib-object.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#ifdef HAVE_EXIF
+#include "eog-exif-util.h"
+#include <libexif/exif-data.h>
+#include <libexif/exif-utils.h>
+#include <libexif/exif-loader.h>
+#endif
+
+#ifdef HAVE_LCMS
+#include <lcms2.h>
+#ifndef EXIF_TAG_GAMMA
+#define EXIF_TAG_GAMMA 0xa500
+#endif
+#endif
+
+#ifdef HAVE_RSVG
+#include <librsvg/rsvg.h>
+#include <librsvg/librsvg-features.h>
+#endif
+
+#define EOG_IMAGE_GET_PRIVATE(object) \
+	(G_TYPE_INSTANCE_GET_PRIVATE ((object), EOG_TYPE_IMAGE, EogImagePrivate))
+
+G_DEFINE_TYPE (EogImage, eog_image, G_TYPE_OBJECT)
+
+enum {
+	SIGNAL_CHANGED,
+	SIGNAL_SIZE_PREPARED,
+	SIGNAL_SAVE_PROGRESS,
+	SIGNAL_NEXT_FRAME,
+	SIGNAL_FILE_CHANGED,
+	SIGNAL_LAST
+};
+
+static gint signals[SIGNAL_LAST];
+
+static GList *supported_mime_types = NULL;
+
+#define EOG_IMAGE_READ_BUFFER_SIZE 65535
+
+static void
+eog_image_free_mem_private (EogImage *image)
+{
+	EogImagePrivate *priv;
+
+	priv = image->priv;
+
+	if (priv->status == EOG_IMAGE_STATUS_LOADING) {
+		eog_image_cancel_load (image);
+	} else {
+		if (priv->anim_iter != NULL) {
+			g_object_unref (priv->anim_iter);
+			priv->anim_iter = NULL;
+		}
+
+		if (priv->anim != NULL) {
+			g_object_unref (priv->anim);
+			priv->anim = NULL;
+		}
+
+		priv->is_playing = FALSE;
+
+		if (priv->image != NULL) {
+			g_object_unref (priv->image);
+			priv->image = NULL;
+		}
+
+#ifdef HAVE_RSVG
+		if (priv->svg != NULL) {
+			g_object_unref (priv->svg);
+			priv->svg = NULL;
+		}
+#endif
+
+#ifdef HAVE_EXIF
+		if (priv->exif != NULL) {
+			exif_data_unref (priv->exif);
+			priv->exif = NULL;
+		}
+#endif
+
+		if (priv->exif_chunk != NULL) {
+			g_free (priv->exif_chunk);
+			priv->exif_chunk = NULL;
+		}
+
+		priv->exif_chunk_len = 0;
+
+#ifdef HAVE_EXEMPI
+		if (priv->xmp != NULL) {
+			xmp_free (priv->xmp);
+			priv->xmp = NULL;
+		}
+#endif
+
+#ifdef HAVE_LCMS
+		if (priv->profile != NULL) {
+			cmsCloseProfile (priv->profile);
+			priv->profile = NULL;
+		}
+#endif
+
+		priv->status = EOG_IMAGE_STATUS_UNKNOWN;
+		priv->metadata_status = EOG_IMAGE_METADATA_NOT_READ;
+	}
+}
+
+static void
+eog_image_dispose (GObject *object)
+{
+	EogImagePrivate *priv;
+
+	priv = EOG_IMAGE (object)->priv;
+
+	eog_image_free_mem_private (EOG_IMAGE (object));
+
+	if (priv->file) {
+		g_object_unref (priv->file);
+		priv->file = NULL;
+	}
+
+	if (priv->caption) {
+		g_free (priv->caption);
+		priv->caption = NULL;
+	}
+
+	if (priv->collate_key) {
+		g_free (priv->collate_key);
+		priv->collate_key = NULL;
+	}
+
+	if (priv->file_type) {
+		g_free (priv->file_type);
+		priv->file_type = NULL;
+	}
+
+	g_mutex_clear (&priv->status_mutex);
+
+	if (priv->trans) {
+		g_object_unref (priv->trans);
+		priv->trans = NULL;
+	}
+
+	if (priv->trans_autorotate) {
+		g_object_unref (priv->trans_autorotate);
+		priv->trans_autorotate = NULL;
+	}
+
+	if (priv->undo_stack) {
+		g_slist_foreach (priv->undo_stack, (GFunc) g_object_unref, NULL);
+		g_slist_free (priv->undo_stack);
+		priv->undo_stack = NULL;
+	}
+
+	G_OBJECT_CLASS (eog_image_parent_class)->dispose (object);
+}
+
+static void
+eog_image_finalize (GObject *object)
+{
+	EogImagePrivate *priv;
+
+	priv = EOG_IMAGE (object)->priv;
+
+	g_mutex_clear (&priv->status_mutex);
+
+	G_OBJECT_CLASS (eog_image_parent_class)->finalize (object);
+}
+
+static void
+eog_image_class_init (EogImageClass *klass)
+{
+	GObjectClass *object_class = (GObjectClass*) klass;
+
+	object_class->dispose = eog_image_dispose;
+	object_class->finalize = eog_image_finalize;
+
+	signals[SIGNAL_SIZE_PREPARED] =
+		g_signal_new ("size-prepared",
+			      EOG_TYPE_IMAGE,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (EogImageClass, size_prepared),
+			      NULL, NULL,
+			      _photos_marshal_VOID__INT_INT,
+			      G_TYPE_NONE, 2,
+			      G_TYPE_INT,
+			      G_TYPE_INT);
+
+	signals[SIGNAL_CHANGED] =
+		g_signal_new ("changed",
+			      EOG_TYPE_IMAGE,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (EogImageClass, changed),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE, 0);
+
+	signals[SIGNAL_SAVE_PROGRESS] =
+		g_signal_new ("save-progress",
+			      EOG_TYPE_IMAGE,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (EogImageClass, save_progress),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__FLOAT,
+			      G_TYPE_NONE, 1,
+			      G_TYPE_FLOAT);
+ 	/**
+ 	 * EogImage::next-frame:
+  	 * @img: the object which received the signal.
+	 * @delay: number of milliseconds the current frame will be displayed.
+	 *
+	 * The ::next-frame signal will be emitted each time an animated image
+	 * advances to the next frame.
+	 */
+	signals[SIGNAL_NEXT_FRAME] =
+		g_signal_new ("next-frame",
+			      EOG_TYPE_IMAGE,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (EogImageClass, next_frame),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__INT,
+			      G_TYPE_NONE, 1,
+			      G_TYPE_INT);
+
+	signals[SIGNAL_FILE_CHANGED] = g_signal_new ("file-changed",
+						     EOG_TYPE_IMAGE,
+						     G_SIGNAL_RUN_LAST,
+						     G_STRUCT_OFFSET (EogImageClass, file_changed),
+						     NULL, NULL,
+						     g_cclosure_marshal_VOID__VOID,
+						     G_TYPE_NONE, 0);
+
+	g_type_class_add_private (object_class, sizeof (EogImagePrivate));
+}
+
+static void
+eog_image_init (EogImage *img)
+{
+	img->priv = EOG_IMAGE_GET_PRIVATE (img);
+
+	img->priv->file = NULL;
+	img->priv->image = NULL;
+	img->priv->anim = NULL;
+	img->priv->anim_iter = NULL;
+	img->priv->is_playing = FALSE;
+	img->priv->width = -1;
+	img->priv->height = -1;
+	img->priv->modified = FALSE;
+	img->priv->file_is_changed = FALSE;
+	g_mutex_init (&img->priv->status_mutex);
+	img->priv->status = EOG_IMAGE_STATUS_UNKNOWN;
+        img->priv->metadata_status = EOG_IMAGE_METADATA_NOT_READ;
+	img->priv->is_monitored = FALSE;
+	img->priv->undo_stack = NULL;
+	img->priv->trans = NULL;
+	img->priv->trans_autorotate = NULL;
+	img->priv->data_ref_count = 0;
+#ifdef HAVE_EXIF
+	img->priv->orientation = 0;
+	img->priv->autorotate = FALSE;
+	img->priv->exif = NULL;
+#endif
+#ifdef HAVE_EXEMPI
+	img->priv->xmp = NULL;
+#endif
+#ifdef HAVE_LCMS
+	img->priv->profile = NULL;
+#endif
+#ifdef HAVE_RSVG
+	img->priv->svg = NULL;
+#endif
+}
+
+EogImage *
+eog_image_new (const char *txt_uri)
+{
+	EogImage *img;
+
+	img = EOG_IMAGE (g_object_new (EOG_TYPE_IMAGE, NULL));
+
+	img->priv->file = g_file_new_for_uri (txt_uri);
+
+	return img;
+}
+
+EogImage *
+eog_image_new_file (GFile *file)
+{
+	EogImage *img;
+
+	img = EOG_IMAGE (g_object_new (EOG_TYPE_IMAGE, NULL));
+
+	img->priv->file = g_object_ref (file);
+
+	return img;
+}
+
+GQuark
+eog_image_error_quark (void)
+{
+	static GQuark q = 0;
+
+	if (q == 0) {
+		q = g_quark_from_static_string ("eog-image-error-quark");
+	}
+
+	return q;
+}
+
+static void
+eog_image_update_exif_data (EogImage *image)
+{
+#ifdef HAVE_EXIF
+	EogImagePrivate *priv;
+	ExifEntry *entry;
+	ExifByteOrder bo;
+
+	eog_debug (DEBUG_IMAGE_DATA);
+
+	g_return_if_fail (EOG_IS_IMAGE (image));
+
+	priv = image->priv;
+
+	if (priv->exif == NULL) return;
+
+	bo = exif_data_get_byte_order (priv->exif);
+
+	/* Update image width */
+	entry = exif_data_get_entry (priv->exif, EXIF_TAG_PIXEL_X_DIMENSION);
+	if (entry != NULL && (priv->width >= 0)) {
+		if (entry->format == EXIF_FORMAT_LONG)
+			exif_set_long (entry->data, bo, priv->width);
+		else if (entry->format == EXIF_FORMAT_SHORT)
+			exif_set_short (entry->data, bo, priv->width);
+		else
+			g_warning ("Exif entry has unsupported size");
+	}
+
+	/* Update image height */
+	entry = exif_data_get_entry (priv->exif, EXIF_TAG_PIXEL_Y_DIMENSION);
+	if (entry != NULL && (priv->height >= 0)) {
+		if (entry->format == EXIF_FORMAT_LONG)
+			exif_set_long (entry->data, bo, priv->height);
+		else if (entry->format == EXIF_FORMAT_SHORT)
+			exif_set_short (entry->data, bo, priv->height);
+		else
+			g_warning ("Exif entry has unsupported size");
+	}
+
+	/* Update image orientation */
+	entry = exif_data_get_entry (priv->exif, EXIF_TAG_ORIENTATION);
+	if (entry != NULL) {
+		if (entry->format == EXIF_FORMAT_LONG)
+			exif_set_long (entry->data, bo, 1);
+		else if (entry->format == EXIF_FORMAT_SHORT)
+			exif_set_short (entry->data, bo, 1);
+		else
+			g_warning ("Exif entry has unsupported size");
+
+		priv->orientation = 1;
+	}
+#endif
+}
+
+static void
+eog_image_real_transform (EogImage     *img,
+			  EogTransform *trans,
+			  gboolean      is_undo,
+			  EogJob       *job)
+{
+	EogImagePrivate *priv;
+	GdkPixbuf *transformed;
+	gboolean modified = FALSE;
+
+	g_return_if_fail (EOG_IS_IMAGE (img));
+	g_return_if_fail (EOG_IS_TRANSFORM (trans));
+
+	priv = img->priv;
+
+	if (priv->image != NULL) {
+		transformed = eog_transform_apply (trans, priv->image, job);
+
+		g_object_unref (priv->image);
+		priv->image = transformed;
+
+		priv->width = gdk_pixbuf_get_width (transformed);
+		priv->height = gdk_pixbuf_get_height (transformed);
+
+		modified = TRUE;
+	}
+
+	if (modified) {
+		priv->modified = TRUE;
+		eog_image_update_exif_data (img);
+	}
+
+	if (priv->trans == NULL) {
+		g_object_ref (trans);
+		priv->trans = trans;
+	} else {
+		EogTransform *composition;
+
+		composition = eog_transform_compose (priv->trans, trans);
+
+		g_object_unref (priv->trans);
+
+		priv->trans = composition;
+	}
+
+	if (!is_undo) {
+		g_object_ref (trans);
+		priv->undo_stack = g_slist_prepend (priv->undo_stack, trans);
+	}
+}
+
+static gboolean
+do_emit_size_prepared_signal (EogImage *img)
+{
+	g_signal_emit (img, signals[SIGNAL_SIZE_PREPARED], 0,
+		       img->priv->width, img->priv->height);
+	return FALSE;
+}
+
+static void
+eog_image_emit_size_prepared (EogImage *img)
+{
+	gdk_threads_add_idle_full (G_PRIORITY_DEFAULT_IDLE,
+				   (GSourceFunc) do_emit_size_prepared_signal,
+				   g_object_ref (img), g_object_unref);
+}
+
+static gboolean
+check_loader_threadsafety (GdkPixbufLoader *loader, gboolean *result)
+{
+	GdkPixbufFormat *format;
+	gboolean ret_val = FALSE;
+
+	format = gdk_pixbuf_loader_get_format (loader);
+	if (format) {
+		ret_val = TRUE;
+		if (result)
+		/* FIXME: We should not be accessing this struct internals
+ 		 * directly. Keep track of bug #469209 to fix that. */
+			*result = format->flags & GDK_PIXBUF_FORMAT_THREADSAFE;
+	}
+
+	return ret_val;
+}
+
+static void
+eog_image_pre_size_prepared (GdkPixbufLoader *loader,
+			     gint width,
+			     gint height,
+			     gpointer data)
+{
+	EogImage *img;
+
+	eog_debug (DEBUG_IMAGE_LOAD);
+
+	g_return_if_fail (EOG_IS_IMAGE (data));
+
+	img = EOG_IMAGE (data);
+	check_loader_threadsafety (loader, &img->priv->threadsafe_format);
+}
+
+static void
+eog_image_size_prepared (GdkPixbufLoader *loader,
+			 gint             width,
+			 gint             height,
+			 gpointer         data)
+{
+	EogImage *img;
+
+	eog_debug (DEBUG_IMAGE_LOAD);
+
+	g_return_if_fail (EOG_IS_IMAGE (data));
+
+	img = EOG_IMAGE (data);
+
+	g_mutex_lock (&img->priv->status_mutex);
+
+	img->priv->width = width;
+	img->priv->height = height;
+
+	g_mutex_unlock (&img->priv->status_mutex);
+
+#ifdef HAVE_EXIF
+	if (img->priv->threadsafe_format && (!img->priv->autorotate || img->priv->exif))
+#else
+	if (img->priv->threadsafe_format)
+#endif
+		eog_image_emit_size_prepared (img);
+}
+
+static EogMetadataReader*
+check_for_metadata_img_format (EogImage *img, guchar *buffer, guint bytes_read)
+{
+	EogMetadataReader *md_reader = NULL;
+
+	eog_debug_message (DEBUG_IMAGE_DATA, "Check image format for jpeg: %x%x - length: %i",
+			   buffer[0], buffer[1], bytes_read);
+
+	if (bytes_read >= 2) {
+		/* SOI (start of image) marker for JPEGs is 0xFFD8 */
+		if ((buffer[0] == 0xFF) && (buffer[1] == 0xD8)) {
+			md_reader = eog_metadata_reader_new (EOG_METADATA_JPEG);
+		}
+		if (bytes_read >= 8 &&
+		    memcmp (buffer, "\x89PNG\x0D\x0A\x1a\x0A", 8) == 0) {
+			md_reader = eog_metadata_reader_new (EOG_METADATA_PNG);
+		}
+	}
+
+	return md_reader;
+}
+
+static gboolean
+eog_image_needs_transformation (EogImage *img)
+{
+	g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+
+	return (img->priv->trans != NULL || img->priv->trans_autorotate != NULL);
+}
+
+static gboolean
+eog_image_apply_transformations (EogImage *img, GError **error)
+{
+	GdkPixbuf *transformed = NULL;
+	EogTransform *composition = NULL;
+	EogImagePrivate *priv;
+
+	g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+
+	priv = img->priv;
+
+	if (priv->trans == NULL && priv->trans_autorotate == NULL) {
+		return TRUE;
+	}
+
+	if (priv->image == NULL) {
+		g_set_error (error,
+			     EOG_IMAGE_ERROR,
+			     EOG_IMAGE_ERROR_NOT_LOADED,
+			     _("Transformation on unloaded image."));
+
+		return FALSE;
+	}
+
+	if (priv->trans != NULL && priv->trans_autorotate != NULL) {
+		composition = eog_transform_compose (priv->trans,
+						     priv->trans_autorotate);
+	} else if (priv->trans != NULL) {
+		composition = g_object_ref (priv->trans);
+	} else if (priv->trans_autorotate != NULL) {
+		composition = g_object_ref (priv->trans_autorotate);
+	}
+
+	if (composition != NULL) {
+		transformed = eog_transform_apply (composition, priv->image, NULL);
+	}
+
+	g_object_unref (priv->image);
+	priv->image = transformed;
+
+	if (transformed != NULL) {
+		priv->width = gdk_pixbuf_get_width (priv->image);
+		priv->height = gdk_pixbuf_get_height (priv->image);
+	} else {
+		g_set_error (error,
+			     EOG_IMAGE_ERROR,
+			     EOG_IMAGE_ERROR_GENERIC,
+			     _("Transformation failed."));
+ 	}
+
+	g_object_unref (composition);
+
+	return (transformed != NULL);
+}
+
+static void
+eog_image_get_file_info (EogImage *img,
+			 goffset *bytes,
+			 gchar **mime_type,
+			 GError **error)
+{
+	GFileInfo *file_info;
+
+	file_info = g_file_query_info (img->priv->file,
+				       G_FILE_ATTRIBUTE_STANDARD_SIZE ","
+				       G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+				       0, NULL, error);
+
+	if (file_info == NULL) {
+		if (bytes)
+			*bytes = 0;
+
+		if (mime_type)
+			*mime_type = NULL;
+
+		g_set_error (error,
+			     EOG_IMAGE_ERROR,
+			     EOG_IMAGE_ERROR_VFS,
+			     "Error in getting image file info");
+	} else {
+		if (bytes)
+			*bytes = g_file_info_get_size (file_info);
+
+		if (mime_type)
+			*mime_type = g_strdup (g_file_info_get_content_type (file_info));
+		g_object_unref (file_info);
+	}
+}
+
+#ifdef HAVE_LCMS
+void
+eog_image_apply_display_profile (EogImage *img, cmsHPROFILE screen)
+{
+	EogImagePrivate *priv;
+	cmsHTRANSFORM transform;
+	gint row, width, rows, stride;
+	guchar *p;
+
+	g_return_if_fail (img != NULL);
+
+	priv = img->priv;
+
+	if (screen == NULL || priv->profile == NULL) return;
+
+	/* TODO: support other colorspaces than RGB */
+	if (cmsGetColorSpace (priv->profile) != cmsSigRgbData ||
+	    cmsGetColorSpace (screen) != cmsSigRgbData) {
+		eog_debug_message (DEBUG_LCMS, "One or both ICC profiles not in RGB colorspace; not correcting");
+		return;
+	}
+
+	/* TODO: find the right way to colorcorrect RGBA images */
+	if (gdk_pixbuf_get_has_alpha (priv->image)) {
+		eog_debug_message (DEBUG_LCMS, "Colorcorrecting RGBA images is unsupported.");
+		return;
+	}
+
+	transform = cmsCreateTransform (priv->profile,
+				        TYPE_RGB_8,
+				        screen,
+				        TYPE_RGB_8,
+				        INTENT_PERCEPTUAL,
+				        0);
+
+	if (G_LIKELY (transform != NULL)) {
+		rows = gdk_pixbuf_get_height (priv->image);
+		width = gdk_pixbuf_get_width (priv->image);
+		stride = gdk_pixbuf_get_rowstride (priv->image);
+		p = gdk_pixbuf_get_pixels (priv->image);
+
+		for (row = 0; row < rows; ++row) {
+			cmsDoTransform (transform, p, p, width);
+			p += stride;
+		}
+		cmsDeleteTransform (transform);
+	}
+}
+
+static void
+eog_image_set_icc_data (EogImage *img, EogMetadataReader *md_reader)
+{
+	EogImagePrivate *priv = img->priv;
+
+	priv->profile = eog_metadata_reader_get_icc_profile (md_reader);
+
+
+}
+#endif
+
+static void
+eog_image_set_orientation (EogImage *img)
+{
+	EogImagePrivate *priv;
+#ifdef HAVE_EXIF
+	ExifData* exif;
+#endif
+
+	g_return_if_fail (EOG_IS_IMAGE (img));
+
+	priv = img->priv;
+
+#ifdef HAVE_EXIF
+	exif = (ExifData*) eog_image_get_exif_info (img);
+
+	if (exif != NULL) {
+		ExifByteOrder o = exif_data_get_byte_order (exif);
+
+		ExifEntry *entry = exif_data_get_entry (exif,
+							EXIF_TAG_ORIENTATION);
+
+		if (entry && entry->data != NULL) {
+			priv->orientation = exif_get_short (entry->data, o);
+		}
+
+		exif_data_unref (exif);
+	} else
+#endif
+	{
+		GdkPixbuf *pbuf;
+
+		pbuf = eog_image_get_pixbuf (img);
+
+		if (pbuf) {
+			const gchar *o_str;
+
+			o_str = gdk_pixbuf_get_option (pbuf, "orientation");
+			if (o_str) {
+				short t = (short) g_ascii_strtoll (o_str,
+								   NULL, 10);
+				if (t >= 0 && t < 9)
+					priv->orientation = t;
+			}
+			g_object_unref (pbuf);
+		}
+	}
+
+	if (priv->orientation > 4 &&
+	    priv->orientation < 9) {
+		gint tmp;
+
+		tmp = priv->width;
+		priv->width = priv->height;
+		priv->height = tmp;
+	}
+}
+
+static void
+eog_image_real_autorotate (EogImage *img)
+{
+	static const EogTransformType lookup[8] = {EOG_TRANSFORM_NONE,
+					     EOG_TRANSFORM_FLIP_HORIZONTAL,
+					     EOG_TRANSFORM_ROT_180,
+					     EOG_TRANSFORM_FLIP_VERTICAL,
+					     EOG_TRANSFORM_TRANSPOSE,
+					     EOG_TRANSFORM_ROT_90,
+					     EOG_TRANSFORM_TRANSVERSE,
+					     EOG_TRANSFORM_ROT_270};
+	EogImagePrivate *priv;
+	EogTransformType type;
+
+	g_return_if_fail (EOG_IS_IMAGE (img));
+
+	priv = img->priv;
+
+	type = (priv->orientation >= 1 && priv->orientation <= 8 ?
+		lookup[priv->orientation - 1] : EOG_TRANSFORM_NONE);
+
+	if (type != EOG_TRANSFORM_NONE) {
+		img->priv->trans_autorotate = eog_transform_new (type);
+	}
+
+	/* Disable auto orientation for next loads */
+	priv->autorotate = FALSE;
+}
+
+void
+eog_image_autorotate (EogImage *img)
+{
+	g_return_if_fail (EOG_IS_IMAGE (img));
+
+	/* Schedule auto orientation */
+	img->priv->autorotate = TRUE;
+}
+
+#ifdef HAVE_EXEMPI
+static void
+eog_image_set_xmp_data (EogImage *img, EogMetadataReader *md_reader)
+{
+	EogImagePrivate *priv;
+
+	g_return_if_fail (EOG_IS_IMAGE (img));
+
+	priv = img->priv;
+
+	if (priv->xmp) {
+		xmp_free (priv->xmp);
+	}
+	priv->xmp = eog_metadata_reader_get_xmp_data (md_reader);
+}
+#endif
+
+static void
+eog_image_set_exif_data (EogImage *img, EogMetadataReader *md_reader)
+{
+	EogImagePrivate *priv;
+
+	g_return_if_fail (EOG_IS_IMAGE (img));
+
+	priv = img->priv;
+
+#ifdef HAVE_EXIF
+	g_mutex_lock (&priv->status_mutex);
+	if (priv->exif) {
+		exif_data_unref (priv->exif);
+	}
+	priv->exif = eog_metadata_reader_get_exif_data (md_reader);
+	g_mutex_unlock (&priv->status_mutex);
+
+	priv->exif_chunk = NULL;
+	priv->exif_chunk_len = 0;
+
+	/* EXIF data is already available, set the image orientation */
+	if (priv->autorotate) {
+		eog_image_set_orientation (img);
+
+		/* Emit size prepared signal if we have the size */
+		if (priv->width > 0 &&
+		    priv->height > 0) {
+			eog_image_emit_size_prepared (img);
+		}
+	}
+#else
+	if (priv->exif_chunk) {
+		g_free (priv->exif_chunk);
+	}
+	eog_metadata_reader_get_exif_chunk (md_reader,
+					    &priv->exif_chunk,
+					    &priv->exif_chunk_len);
+#endif
+}
+
+static gboolean
+eog_image_real_load (EogImage *img,
+		     guint     data2read,
+		     EogJob   *job,
+		     GError  **error)
+{
+	EogImagePrivate *priv;
+	GFileInputStream *input_stream;
+	EogMetadataReader *md_reader = NULL;
+	GdkPixbufFormat *format;
+	gchar *mime_type;
+	GdkPixbufLoader *loader = NULL;
+	guchar *buffer;
+	goffset bytes_read, bytes_read_total = 0;
+	gboolean failed = FALSE;
+	gboolean first_run = TRUE;
+	gboolean set_metadata = TRUE;
+	gboolean use_rsvg = FALSE;
+	gboolean read_image_data = (data2read & EOG_IMAGE_DATA_IMAGE);
+	gboolean read_only_dimension = (data2read & EOG_IMAGE_DATA_DIMENSION) &&
+				  ((data2read ^ EOG_IMAGE_DATA_DIMENSION) == 0);
+
+
+	priv = img->priv;
+
+ 	g_assert (!read_image_data || priv->image == NULL);
+
+	if (read_image_data && priv->file_type != NULL) {
+		g_free (priv->file_type);
+		priv->file_type = NULL;
+	}
+
+	priv->threadsafe_format = FALSE;
+
+	eog_image_get_file_info (img, &priv->bytes, &mime_type, error);
+
+	if (error && *error) {
+		g_free (mime_type);
+		return FALSE;
+	}
+
+	input_stream = g_file_read (priv->file, NULL, error);
+
+	if (input_stream == NULL) {
+		g_free (mime_type);
+
+		if (error != NULL) {
+			g_clear_error (error);
+			g_set_error (error,
+				     EOG_IMAGE_ERROR,
+				     EOG_IMAGE_ERROR_VFS,
+				     "Failed to open input stream for file");
+		}
+		return FALSE;
+	}
+
+	buffer = g_new0 (guchar, EOG_IMAGE_READ_BUFFER_SIZE);
+
+	if (read_image_data || read_only_dimension) {
+		gboolean checked_threadsafety = FALSE;
+
+#ifdef HAVE_RSVG
+		if (priv->svg != NULL) {
+			g_object_unref (priv->svg);
+			priv->svg = NULL;
+		}
+
+		if (!strcmp (mime_type, "image/svg+xml")
+#if LIBRSVG_CHECK_FEATURE(SVGZ)
+		    || !strcmp (mime_type, "image/svg+xml-compressed")
+#endif
+		) {
+			gchar *file_path;
+			/* Keep the object for rendering */
+			priv->svg = rsvg_handle_new ();
+			use_rsvg = (priv->svg != NULL);
+			file_path = g_file_get_path (priv->file);
+			rsvg_handle_set_base_uri (priv->svg, file_path);
+			g_free (file_path);
+		}
+#endif
+
+		if (!use_rsvg) {
+			loader = gdk_pixbuf_loader_new_with_mime_type (mime_type, error);
+
+			if (error && *error) {
+				g_error_free (*error);
+				*error = NULL;
+
+				loader = gdk_pixbuf_loader_new ();
+			} else {
+				/* The mimetype-based loader should know the
+				 * format here already. */
+				checked_threadsafety = check_loader_threadsafety (loader, &priv->threadsafe_format);
+			}
+
+		/* This is used to detect non-threadsafe loaders and disable
+ 		 * any possible asyncronous task that could bring deadlocks
+ 		 * to image loading process. */
+			if (!checked_threadsafety)
+				g_signal_connect (loader,
+					  "size-prepared",
+					  G_CALLBACK (eog_image_pre_size_prepared),
+					  img);
+
+			g_signal_connect_object (G_OBJECT (loader),
+					 "size-prepared",
+					 G_CALLBACK (eog_image_size_prepared),
+					 img,
+					 0);
+		}
+	}
+	g_free (mime_type);
+
+	while (!priv->cancel_loading) {
+		/* FIXME: make this async */
+		bytes_read = g_input_stream_read (G_INPUT_STREAM (input_stream),
+						  buffer,
+						  EOG_IMAGE_READ_BUFFER_SIZE,
+						  NULL, error);
+
+		if (bytes_read == 0) {
+			/* End of the file */
+			break;
+		} else if (bytes_read == -1) {
+			failed = TRUE;
+
+			g_set_error (error,
+				     EOG_IMAGE_ERROR,
+				     EOG_IMAGE_ERROR_VFS,
+				     "Failed to read from input stream");
+
+			break;
+		}
+
+		if ((read_image_data || read_only_dimension)) {
+#ifdef HAVE_RSVG
+			if (use_rsvg) {
+				gboolean res;
+
+				res = rsvg_handle_write (priv->svg, buffer,
+							 bytes_read, error);
+
+				if (G_UNLIKELY (!res)) {
+					failed = TRUE;
+					break;
+				}
+			} else
+#endif
+			if (!gdk_pixbuf_loader_write (loader, buffer, bytes_read, error)) {
+				failed = TRUE;
+				break;
+			}
+		}
+
+		bytes_read_total += bytes_read;
+
+		if (job != NULL) {
+			float progress = (float) bytes_read_total / (float) priv->bytes;
+			eog_job_set_progress (job, progress);
+		}
+
+		if (first_run) {
+			md_reader = check_for_metadata_img_format (img, buffer, bytes_read);
+
+			if (md_reader == NULL) {
+				if (data2read == EOG_IMAGE_DATA_EXIF) {
+					g_set_error (error,
+						     EOG_IMAGE_ERROR,
+                                	             EOG_IMAGE_ERROR_GENERIC,
+                                	             _("EXIF not supported for this file format."));
+					break;
+				}
+
+				if (priv->threadsafe_format)
+					eog_image_emit_size_prepared (img);
+
+                                priv->metadata_status = EOG_IMAGE_METADATA_NOT_AVAILABLE;
+                        }
+
+			first_run = FALSE;
+		}
+
+		if (md_reader != NULL) {
+			eog_metadata_reader_consume (md_reader, buffer, bytes_read);
+
+			if (eog_metadata_reader_finished (md_reader)) {
+				if (set_metadata) {
+					eog_image_set_exif_data (img, md_reader);
+
+#ifdef HAVE_LCMS
+					eog_image_set_icc_data (img, md_reader);
+#endif
+
+#ifdef HAVE_EXEMPI
+					eog_image_set_xmp_data (img, md_reader);
+#endif
+					set_metadata = FALSE;
+                                        priv->metadata_status = EOG_IMAGE_METADATA_READY;
+				}
+
+				if (data2read == EOG_IMAGE_DATA_EXIF)
+					break;
+			}
+		}
+
+		if (read_only_dimension &&
+		    eog_image_has_data (img, EOG_IMAGE_DATA_DIMENSION)) {
+			break;
+		}
+	}
+
+	if (read_image_data || read_only_dimension) {
+#ifdef HAVE_RSVG
+		if (use_rsvg) {
+			rsvg_handle_close (priv->svg, error);
+		} else
+#endif
+		if (failed) {
+			gdk_pixbuf_loader_close (loader, NULL);
+		} else if (!gdk_pixbuf_loader_close (loader, error)) {
+			if (gdk_pixbuf_loader_get_pixbuf (loader) != NULL) {
+				/* Clear error in order to support partial
+				 * images as well. */
+				g_clear_error (error);
+			}
+	        }
+        }
+
+	g_free (buffer);
+
+	g_object_unref (G_OBJECT (input_stream));
+
+	failed = (failed ||
+		  priv->cancel_loading ||
+		  bytes_read_total == 0 ||
+		  (error && *error != NULL));
+
+	if (failed) {
+		if (priv->cancel_loading) {
+			priv->cancel_loading = FALSE;
+			priv->status = EOG_IMAGE_STATUS_UNKNOWN;
+		} else {
+			priv->status = EOG_IMAGE_STATUS_FAILED;
+		}
+	} else if (read_image_data) {
+		if (priv->image != NULL) {
+			g_object_unref (priv->image);
+		}
+
+#ifdef HAVE_RSVG
+		if (use_rsvg) {
+			priv->image = rsvg_handle_get_pixbuf (priv->svg);
+		} else
+#endif
+		{
+
+		priv->anim = gdk_pixbuf_loader_get_animation (loader);
+
+		if (gdk_pixbuf_animation_is_static_image (priv->anim)) {
+			priv->image = gdk_pixbuf_animation_get_static_image (priv->anim);
+			priv->anim = NULL;
+		} else {
+			priv->anim_iter = gdk_pixbuf_animation_get_iter (priv->anim,NULL);
+			priv->image = gdk_pixbuf_animation_iter_get_pixbuf (priv->anim_iter);
+		}
+
+		}
+
+		if (G_LIKELY (priv->image != NULL)) {
+			if (!use_rsvg)
+				g_object_ref (priv->image);
+
+			priv->width = gdk_pixbuf_get_width (priv->image);
+			priv->height = gdk_pixbuf_get_height (priv->image);
+
+			if (use_rsvg) {
+				format = NULL;
+				priv->file_type = g_strdup ("svg");
+			} else {
+				format = gdk_pixbuf_loader_get_format (loader);
+			}
+
+			if (format != NULL) {
+				priv->file_type = gdk_pixbuf_format_get_name (format);
+			}
+
+			priv->file_is_changed = FALSE;
+
+			/* Set orientation again for safety, eg. if we don't
+			 * have Exif data or HAVE_EXIF is undefined. */
+			eog_image_set_orientation (img);
+
+			/* If it's non-threadsafe loader, then trigger window
+ 			 * showing in the end of the process. */
+			if (!priv->threadsafe_format)
+				eog_image_emit_size_prepared (img);
+		} else {
+			/* Some loaders don't report errors correctly.
+			 * Error will be set below. */
+			failed = TRUE;
+			priv->status = EOG_IMAGE_STATUS_FAILED;
+		}
+	}
+
+	if (loader != NULL) {
+		g_object_unref (loader);
+	}
+
+	if (md_reader != NULL) {
+		g_object_unref (md_reader);
+		md_reader = NULL;
+	}
+
+	/* Catch-all in case of poor-error reporting */
+	if (failed && error && *error == NULL) {
+		g_set_error (error,
+			     EOG_IMAGE_ERROR,
+			     EOG_IMAGE_ERROR_GENERIC,
+			     _("Image loading failed."));
+	}
+
+	return !failed;
+}
+
+gboolean
+eog_image_has_data (EogImage *img, EogImageData req_data)
+{
+	EogImagePrivate *priv;
+	gboolean has_data = TRUE;
+
+	g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+
+	priv = img->priv;
+
+	if ((req_data & EOG_IMAGE_DATA_IMAGE) > 0) {
+		req_data = (req_data & ~EOG_IMAGE_DATA_IMAGE);
+		has_data = has_data && (priv->image != NULL);
+	}
+
+	if ((req_data & EOG_IMAGE_DATA_DIMENSION) > 0 ) {
+		req_data = (req_data & ~EOG_IMAGE_DATA_DIMENSION);
+		has_data = has_data && (priv->width >= 0) && (priv->height >= 0);
+	}
+
+	if ((req_data & EOG_IMAGE_DATA_EXIF) > 0) {
+		req_data = (req_data & ~EOG_IMAGE_DATA_EXIF);
+#ifdef HAVE_EXIF
+		has_data = has_data && (priv->exif != NULL);
+#else
+		has_data = has_data && (priv->exif_chunk != NULL);
+#endif
+	}
+
+	if ((req_data & EOG_IMAGE_DATA_XMP) > 0) {
+		req_data = (req_data & ~EOG_IMAGE_DATA_XMP);
+#ifdef HAVE_EXEMPI
+		has_data = has_data && (priv->xmp != NULL);
+#endif
+	}
+
+	if (req_data != 0) {
+		g_warning ("Asking for unknown data, remaining: %i\n", req_data);
+		has_data = FALSE;
+	}
+
+	return has_data;
+}
+
+gboolean
+eog_image_load (EogImage *img, EogImageData data2read, EogJob *job, GError **error)
+{
+	EogImagePrivate *priv;
+	gboolean success = FALSE;
+
+	eog_debug (DEBUG_IMAGE_LOAD);
+
+	g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+
+	priv = EOG_IMAGE (img)->priv;
+
+	if (data2read == 0) {
+		return TRUE;
+	}
+
+	if (eog_image_has_data (img, data2read)) {
+		return TRUE;
+	}
+
+	priv->status = EOG_IMAGE_STATUS_LOADING;
+
+	success = eog_image_real_load (img, data2read, job, error);
+
+
+	/* Check that the metadata was loaded at least once before
+	 * trying to autorotate. Also only an image load job should try to
+	 * autorotate an image. */
+	if (priv->autorotate &&
+#ifdef HAVE_EXIF
+	    priv->metadata_status != EOG_IMAGE_METADATA_NOT_READ &&
+#endif
+	    data2read & EOG_IMAGE_DATA_IMAGE) {
+		eog_image_real_autorotate (img);
+	}
+
+	if (success && eog_image_needs_transformation (img)) {
+		success = eog_image_apply_transformations (img, error);
+	}
+
+	if (success) {
+		priv->status = EOG_IMAGE_STATUS_LOADED;
+	} else {
+		priv->status = EOG_IMAGE_STATUS_FAILED;
+	}
+
+	return success;
+}
+
+/**
+ * eog_image_get_pixbuf:
+ * @img: a #EogImage
+ *
+ * Gets the #GdkPixbuf of the image
+ *
+ * Returns: (transfer full): a #GdkPixbuf
+ **/
+GdkPixbuf *
+eog_image_get_pixbuf (EogImage *img)
+{
+	GdkPixbuf *image = NULL;
+
+	g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+	g_mutex_lock (&img->priv->status_mutex);
+	image = img->priv->image;
+	g_mutex_unlock (&img->priv->status_mutex);
+
+	if (image != NULL) {
+		g_object_ref (image);
+	}
+
+	return image;
+}
+
+#ifdef HAVE_LCMS
+cmsHPROFILE
+eog_image_get_profile (EogImage *img)
+{
+	g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+	return img->priv->profile;
+}
+#endif
+
+void
+eog_image_get_size (EogImage *img, int *width, int *height)
+{
+	EogImagePrivate *priv;
+
+	g_return_if_fail (EOG_IS_IMAGE (img));
+
+	priv = img->priv;
+
+	*width = priv->width;
+	*height = priv->height;
+}
+
+void
+eog_image_transform (EogImage *img, EogTransform *trans, EogJob *job)
+{
+	eog_image_real_transform (img, trans, FALSE, job);
+}
+
+void
+eog_image_undo (EogImage *img)
+{
+	EogImagePrivate *priv;
+	EogTransform *trans;
+	EogTransform *inverse;
+
+	g_return_if_fail (EOG_IS_IMAGE (img));
+
+	priv = img->priv;
+
+	if (priv->undo_stack != NULL) {
+		trans = EOG_TRANSFORM (priv->undo_stack->data);
+
+		inverse = eog_transform_reverse (trans);
+
+		eog_image_real_transform (img, inverse, TRUE, NULL);
+
+		priv->undo_stack = g_slist_delete_link (priv->undo_stack, priv->undo_stack);
+
+		g_object_unref (trans);
+		g_object_unref (inverse);
+
+		if (eog_transform_is_identity (priv->trans)) {
+			g_object_unref (priv->trans);
+			priv->trans = NULL;
+		}
+	}
+
+	priv->modified = (priv->undo_stack != NULL);
+}
+
+static GFile *
+tmp_file_get (void)
+{
+	GFile *tmp_file;
+	char *tmp_file_path;
+	gint fd;
+
+	tmp_file_path = g_build_filename (g_get_tmp_dir (), "eog-save-XXXXXX", NULL);
+	fd = g_mkstemp (tmp_file_path);
+	if (fd == -1) {
+		g_free (tmp_file_path);
+		return NULL;
+	}
+	else {
+		tmp_file = g_file_new_for_path (tmp_file_path);
+		g_free (tmp_file_path);
+		return tmp_file;
+	}
+}
+
+static void
+transfer_progress_cb (goffset cur_bytes,
+		      goffset total_bytes,
+		      gpointer user_data)
+{
+	EogImage *image = EOG_IMAGE (user_data);
+
+	if (cur_bytes > 0) {
+		g_signal_emit (G_OBJECT(image),
+			       signals[SIGNAL_SAVE_PROGRESS],
+			       0,
+			       (gfloat) cur_bytes / (gfloat) total_bytes);
+	}
+}
+
+static void
+tmp_file_restore_unix_attributes (GFile *temp_file,
+				  GFile *target_file)
+{
+	GFileInfo *file_info;
+	guint      uid;
+	guint      gid;
+	guint      mode;
+	guint      mode_mask = 00600;
+
+	GError    *error = NULL;
+
+	g_return_if_fail (G_IS_FILE (temp_file));
+	g_return_if_fail (G_IS_FILE (target_file));
+
+	/* check if file exists */
+	if (!g_file_query_exists (target_file, NULL)) {
+		eog_debug_message (DEBUG_IMAGE_SAVE,
+				   "Target file doesn't exist. Setting default attributes.");
+		return;
+	}
+
+	/* retrieve UID, GID, and MODE of the original file info */
+	file_info = g_file_query_info (target_file,
+				       "unix::uid,unix::gid,unix::mode",
+				       G_FILE_QUERY_INFO_NONE,
+				       NULL,
+				       &error);
+
+	/* check that there aren't any error */
+	if (error != NULL) {
+		eog_debug_message (DEBUG_IMAGE_SAVE,
+				   "File information not available. Setting default attributes.");
+
+		/* free objects */
+		g_object_unref (file_info);
+		g_clear_error (&error);
+
+		return;
+	}
+
+	/* save UID, GID and MODE values */
+	uid = g_file_info_get_attribute_uint32 (file_info,
+						G_FILE_ATTRIBUTE_UNIX_UID);
+
+	gid = g_file_info_get_attribute_uint32 (file_info,
+						G_FILE_ATTRIBUTE_UNIX_GID);
+
+	mode = g_file_info_get_attribute_uint32 (file_info,
+						 G_FILE_ATTRIBUTE_UNIX_MODE);
+
+	/* apply default mode mask to file mode */
+	mode |= mode_mask;
+
+	/* restore original UID, GID, and MODE into the temporal file */
+	g_file_set_attribute_uint32 (temp_file,
+				     G_FILE_ATTRIBUTE_UNIX_UID,
+				     uid,
+				     G_FILE_QUERY_INFO_NONE,
+				     NULL,
+				     &error);
+
+	/* check that there aren't any error */
+	if (error != NULL) {
+		eog_debug_message (DEBUG_IMAGE_SAVE,
+				   "You do not have the permissions necessary to change the file UID.");
+
+		g_clear_error (&error);
+	}
+
+	g_file_set_attribute_uint32 (temp_file,
+				     G_FILE_ATTRIBUTE_UNIX_GID,
+				     gid,
+				     G_FILE_QUERY_INFO_NONE,
+				     NULL,
+				     &error);
+
+	/* check that there aren't any error */
+	if (error != NULL) {
+		eog_debug_message (DEBUG_IMAGE_SAVE,
+				   "You do not have the permissions necessary to change the file GID. Setting user default GID.");
+
+		g_clear_error (&error);
+	}
+
+	g_file_set_attribute_uint32 (temp_file,
+				     G_FILE_ATTRIBUTE_UNIX_MODE,
+				     mode,
+				     G_FILE_QUERY_INFO_NONE,
+				     NULL,
+				     &error);
+
+	/* check that there aren't any error */
+	if (error != NULL) {
+		eog_debug_message (DEBUG_IMAGE_SAVE,
+				   "You do not have the permissions necessary to change the file MODE.");
+
+		g_clear_error (&error);
+	}
+
+	/* free objects */
+	g_object_unref (file_info);
+}
+
+static gboolean
+tmp_file_move_to_uri (EogImage *image,
+		      GFile *tmpfile,
+		      GFile *file,
+		      gboolean overwrite,
+		      GError **error)
+{
+	gboolean result;
+	GError *ioerror = NULL;
+
+	/* try to restore target file unix attributes */
+	tmp_file_restore_unix_attributes (tmpfile, file);
+
+	/* replace target file with temporal file */
+	result = g_file_move (tmpfile,
+			      file,
+			      (overwrite ? G_FILE_COPY_OVERWRITE : 0) |
+			      G_FILE_COPY_ALL_METADATA,
+			      NULL,
+			      (GFileProgressCallback) transfer_progress_cb,
+			      image,
+			      &ioerror);
+
+	if (result == FALSE) {
+		if (g_error_matches (ioerror, G_IO_ERROR,
+				     G_IO_ERROR_EXISTS)) {
+			g_set_error (error, EOG_IMAGE_ERROR,
+				     EOG_IMAGE_ERROR_FILE_EXISTS,
+				     "File exists");
+		} else {
+			g_set_error (error, EOG_IMAGE_ERROR,
+				     EOG_IMAGE_ERROR_VFS,
+				     "VFS error moving the temp file");
+		}
+		g_clear_error (&ioerror);
+	}
+
+	return result;
+}
+
+static gboolean
+tmp_file_delete (GFile *tmpfile)
+{
+	gboolean result;
+	GError *err = NULL;
+
+	if (tmpfile == NULL) return FALSE;
+
+	result = g_file_delete (tmpfile, NULL, &err);
+	if (result == FALSE) {
+		char *tmpfile_path;
+		if (err != NULL) {
+			if (err->code == G_IO_ERROR_NOT_FOUND) {
+				g_error_free (err);
+				return TRUE;
+			}
+			g_error_free (err);
+		}
+		tmpfile_path = g_file_get_path (tmpfile);
+		g_warning ("Couldn't delete temporary file: %s", tmpfile_path);
+		g_free (tmpfile_path);
+	}
+
+	return result;
+}
+
+static void
+eog_image_reset_modifications (EogImage *image)
+{
+	EogImagePrivate *priv;
+
+	g_return_if_fail (EOG_IS_IMAGE (image));
+
+	priv = image->priv;
+
+	g_slist_foreach (priv->undo_stack, (GFunc) g_object_unref, NULL);
+	g_slist_free (priv->undo_stack);
+	priv->undo_stack = NULL;
+
+	if (priv->trans != NULL) {
+		g_object_unref (priv->trans);
+		priv->trans = NULL;
+	}
+
+	if (priv->trans_autorotate != NULL) {
+		g_object_unref (priv->trans_autorotate);
+		priv->trans_autorotate = NULL;
+	}
+
+	priv->modified = FALSE;
+}
+
+static void
+eog_image_link_with_target (EogImage *image, EogImageSaveInfo *target)
+{
+	EogImagePrivate *priv;
+
+	g_return_if_fail (EOG_IS_IMAGE (image));
+	g_return_if_fail (EOG_IS_IMAGE_SAVE_INFO (target));
+
+	priv = image->priv;
+
+	/* update file location */
+	if (priv->file != NULL) {
+		g_object_unref (priv->file);
+	}
+	priv->file = g_object_ref (target->file);
+
+	/* Clear caption and caption key, these will be
+	 * updated on next eog_image_get_caption call.
+	 */
+	if (priv->caption != NULL) {
+		g_free (priv->caption);
+		priv->caption = NULL;
+	}
+	if (priv->collate_key != NULL) {
+		g_free (priv->collate_key);
+		priv->collate_key = NULL;
+	}
+
+	/* update file format */
+	if (priv->file_type != NULL) {
+		g_free (priv->file_type);
+	}
+	priv->file_type = g_strdup (target->format);
+}
+
+static gboolean
+check_if_file_is_writable (GFile *file)
+{
+	GFile           *file_to_check;
+	GFileInfo	*file_info;
+	GError		*error = NULL;
+	gboolean	 is_writable;
+
+	g_return_val_if_fail (G_IS_FILE (file), FALSE);
+
+	/* check if file exists */
+	if (!g_file_query_exists (file, NULL)) {
+		eog_debug_message (DEBUG_IMAGE_SAVE, "File doesn't exist. "
+				   "Checking parent directory.");
+
+		file_to_check = g_file_get_parent (file);
+	} else {
+		/* Add another ref so we don't need to split between file and
+		 * parent file again after querying and can simply unref it. */
+		file_to_check = g_object_ref (file);
+	}
+
+	/* recover file information */
+	file_info = g_file_query_info (file_to_check,
+				       G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE,
+				       G_FILE_QUERY_INFO_NONE,
+				       NULL,
+				       &error);
+
+	/* we assume that the image can't be saved when
+	   we can't retrieve any file information */
+	if (G_UNLIKELY (file_info == NULL)) {
+		eog_debug_message (DEBUG_IMAGE_SAVE,
+				   "Couldn't query file info: %s",
+				   error->message);
+		g_error_free (error);
+		g_object_unref (file_to_check);
+		return FALSE;
+	}
+
+	/* check if file can be writed */
+	is_writable = g_file_info_get_attribute_boolean (file_info,
+							 G_FILE_ATTRIBUTE_ACCESS_CAN_WRITE);
+
+	/* free objects */
+	g_object_unref (file_info);
+	g_object_unref (file_to_check);
+
+	return is_writable;
+}
+
+gboolean
+eog_image_save_by_info (EogImage *img, EogImageSaveInfo *source, GError **error)
+{
+	EogImagePrivate *priv;
+	EogImageStatus prev_status;
+	gboolean success = FALSE;
+	GFile *tmp_file;
+	char *tmp_file_path;
+
+	g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+	g_return_val_if_fail (EOG_IS_IMAGE_SAVE_INFO (source), FALSE);
+
+	priv = img->priv;
+
+	prev_status = priv->status;
+
+	/* Image is now being saved */
+	priv->status = EOG_IMAGE_STATUS_SAVING;
+
+	/* see if we need any saving at all */
+	if (source->exists && !source->modified) {
+		return TRUE;
+	}
+
+	/* fail if there is no image to save */
+	if (priv->image == NULL) {
+		g_set_error (error, EOG_IMAGE_ERROR,
+			     EOG_IMAGE_ERROR_NOT_LOADED,
+			     _("No image loaded."));
+		return FALSE;
+	}
+
+	/* fail if there is not write rights to save */
+	if (!check_if_file_is_writable (priv->file)) {
+		g_set_error (error, EOG_IMAGE_ERROR,
+			     EOG_IMAGE_ERROR_NOT_SAVED,
+			     _("You do not have the permissions necessary to save the file."));
+		return FALSE;
+	}
+
+	/* generate temporary file */
+	tmp_file = tmp_file_get ();
+
+	if (tmp_file == NULL) {
+		g_set_error (error, EOG_IMAGE_ERROR,
+			     EOG_IMAGE_ERROR_TMP_FILE_FAILED,
+			     _("Temporary file creation failed."));
+		return FALSE;
+	}
+
+	tmp_file_path = g_file_get_path (tmp_file);
+
+#ifdef HAVE_JPEG
+	/* determine kind of saving */
+	if ((g_ascii_strcasecmp (source->format, EOG_FILE_FORMAT_JPEG) == 0) &&
+	    source->exists && source->modified)
+	{
+		success = eog_image_jpeg_save_file (img, tmp_file_path, source, NULL, error);
+	}
+#endif
+
+	if (!success && (*error == NULL)) {
+		success = gdk_pixbuf_save (priv->image, tmp_file_path, source->format, error, NULL);
+	}
+
+	if (success) {
+		/* try to move result file to target uri */
+		success = tmp_file_move_to_uri (img, tmp_file, priv->file, TRUE /*overwrite*/, error);
+	}
+
+	if (success) {
+		eog_image_reset_modifications (img);
+	}
+
+	tmp_file_delete (tmp_file);
+
+	g_free (tmp_file_path);
+	g_object_unref (tmp_file);
+
+	priv->status = prev_status;
+
+	return success;
+}
+
+static gboolean
+eog_image_copy_file (EogImage *image, EogImageSaveInfo *source, EogImageSaveInfo *target, GError **error)
+{
+	gboolean result;
+	GError *ioerror = NULL;
+
+	g_return_val_if_fail (EOG_IS_IMAGE_SAVE_INFO (source), FALSE);
+	g_return_val_if_fail (EOG_IS_IMAGE_SAVE_INFO (target), FALSE);
+
+	/* copy the image */
+	result = g_file_copy (source->file,
+			      target->file,
+			      (target->overwrite ? G_FILE_COPY_OVERWRITE : 0) |
+			      G_FILE_COPY_ALL_METADATA,
+			      NULL,
+			      EOG_IS_IMAGE (image) ? transfer_progress_cb :NULL,
+			      image,
+			      &ioerror);
+
+	if (result == FALSE) {
+		if (ioerror->code == G_IO_ERROR_EXISTS) {
+			g_set_error (error, EOG_IMAGE_ERROR,
+				     EOG_IMAGE_ERROR_FILE_EXISTS,
+				     "%s", ioerror->message);
+		} else {
+		g_set_error (error, EOG_IMAGE_ERROR,
+			     EOG_IMAGE_ERROR_VFS,
+			     "%s", ioerror->message);
+		}
+		g_error_free (ioerror);
+	} else {
+		/* reset NAUTILUS-ICON-POSITION metadata attribute */
+		g_file_set_attribute (target->file,
+				      "metadata::nautilus-icon-position",
+				      G_FILE_ATTRIBUTE_TYPE_INVALID,
+				      NULL,
+				      G_FILE_QUERY_INFO_NONE,
+				      NULL,
+				      NULL);
+	}
+
+	return result;
+}
+
+gboolean
+eog_image_save_as_by_info (EogImage *img, EogImageSaveInfo *source, EogImageSaveInfo *target, GError **error)
+{
+	EogImagePrivate *priv;
+	gboolean success = FALSE;
+	char *tmp_file_path;
+	GFile *tmp_file;
+	gboolean direct_copy = FALSE;
+
+	g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+	g_return_val_if_fail (EOG_IS_IMAGE_SAVE_INFO (source), FALSE);
+	g_return_val_if_fail (EOG_IS_IMAGE_SAVE_INFO (target), FALSE);
+
+	priv = img->priv;
+
+	/* fail if there is no image to save */
+	if (priv->image == NULL) {
+		g_set_error (error,
+			     EOG_IMAGE_ERROR,
+			     EOG_IMAGE_ERROR_NOT_LOADED,
+			     _("No image loaded."));
+
+		return FALSE;
+	}
+
+	/* fail if there is not write rights to save on target */
+	if (!check_if_file_is_writable (target->file)) {
+		g_set_error (error, EOG_IMAGE_ERROR,
+			     EOG_IMAGE_ERROR_NOT_SAVED,
+			     _("You do not have the permissions necessary to save the file."));
+		return FALSE;
+	}
+
+	/* generate temporary file name */
+	tmp_file = tmp_file_get ();
+
+	if (tmp_file == NULL) {
+		g_set_error (error,
+			     EOG_IMAGE_ERROR,
+			     EOG_IMAGE_ERROR_TMP_FILE_FAILED,
+			     _("Temporary file creation failed."));
+
+		return FALSE;
+	}
+	tmp_file_path = g_file_get_path (tmp_file);
+
+	/* determine kind of saving */
+	if (g_ascii_strcasecmp (source->format, target->format) == 0 && !source->modified) {
+		success = eog_image_copy_file (img, source, target, error);
+		direct_copy = success;
+	}
+
+#ifdef HAVE_JPEG
+	else if ((g_ascii_strcasecmp (source->format, EOG_FILE_FORMAT_JPEG) == 0 && source->exists) ||
+		 (g_ascii_strcasecmp (target->format, EOG_FILE_FORMAT_JPEG) == 0))
+	{
+		success = eog_image_jpeg_save_file (img, tmp_file_path, source, target, error);
+	}
+#endif
+
+	if (!success && (*error == NULL)) {
+		success = gdk_pixbuf_save (priv->image, tmp_file_path, target->format, error, NULL);
+	}
+
+	if (success && !direct_copy) { /* not required if we alredy copied the file directly */
+		/* try to move result file to target uri */
+		success = tmp_file_move_to_uri (img, tmp_file, target->file, target->overwrite, error);
+	}
+
+	if (success) {
+		/* update image information to new uri */
+		eog_image_reset_modifications (img);
+		eog_image_link_with_target (img, target);
+	}
+
+	tmp_file_delete (tmp_file);
+	g_object_unref (tmp_file);
+	g_free (tmp_file_path);
+
+	priv->status = EOG_IMAGE_STATUS_UNKNOWN;
+
+	return success;
+}
+
+
+/*
+ * This function is extracted from
+ * File: nautilus/libnautilus-private/nautilus-file.c
+ * Revision: 1.309
+ * Author: Darin Adler <darin bentspoon com>
+ */
+static gboolean
+have_broken_filenames (void)
+{
+	static gboolean initialized = FALSE;
+	static gboolean broken;
+
+	if (initialized) {
+		return broken;
+	}
+
+	broken = g_getenv ("G_BROKEN_FILENAMES") != NULL;
+
+	initialized = TRUE;
+
+	return broken;
+}
+
+/*
+ * This function is inspired by
+ * nautilus/libnautilus-private/nautilus-file.c:nautilus_file_get_display_name_nocopy
+ * Revision: 1.309
+ * Author: Darin Adler <darin bentspoon com>
+ */
+const gchar*
+eog_image_get_caption (EogImage *img)
+{
+	EogImagePrivate *priv;
+	char *name;
+	char *utf8_name;
+	char *scheme;
+	gboolean validated = FALSE;
+	gboolean broken_filenames;
+
+	g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+	priv = img->priv;
+
+	if (priv->file == NULL) return NULL;
+
+	if (priv->caption != NULL)
+		/* Use cached caption string */
+		return priv->caption;
+
+	name = g_file_get_basename (priv->file);
+	scheme = g_file_get_uri_scheme (priv->file);
+
+	if (name != NULL && g_ascii_strcasecmp (scheme, "file") == 0) {
+		/* Support the G_BROKEN_FILENAMES feature of
+		 * glib by using g_filename_to_utf8 to convert
+		 * local filenames to UTF-8. Also do the same
+		 * thing with any local filename that does not
+		 * validate as good UTF-8.
+		 */
+		broken_filenames = have_broken_filenames ();
+
+		if (broken_filenames || !g_utf8_validate (name, -1, NULL)) {
+			utf8_name = g_locale_to_utf8 (name, -1, NULL, NULL, NULL);
+			if (utf8_name != NULL) {
+				g_free (name);
+				name = utf8_name;
+				/* Guaranteed to be correct utf8 here */
+				validated = TRUE;
+			}
+		} else if (!broken_filenames) {
+			/* name was valid, no need to re-validate */
+			validated = TRUE;
+		}
+	}
+
+	if (!validated && !g_utf8_validate (name, -1, NULL)) {
+		if (name == NULL) {
+			name = g_strdup ("[Invalid Unicode]");
+		} else {
+			utf8_name = eog_util_make_valid_utf8 (name);
+			g_free (name);
+			name = utf8_name;
+		}
+	}
+
+	priv->caption = name;
+
+	if (priv->caption == NULL) {
+		char *short_str;
+
+		short_str = g_file_get_basename (priv->file);
+		if (g_utf8_validate (short_str, -1, NULL)) {
+			priv->caption = g_strdup (short_str);
+		} else {
+			priv->caption = g_filename_to_utf8 (short_str, -1, NULL, NULL, NULL);
+		}
+		g_free (short_str);
+	}
+	g_free (scheme);
+
+	return priv->caption;
+}
+
+const gchar*
+eog_image_get_collate_key (EogImage *img)
+{
+	EogImagePrivate *priv;
+
+	g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+	priv = img->priv;
+
+	if (priv->collate_key == NULL) {
+		const char *caption;
+
+		caption = eog_image_get_caption (img);
+
+		priv->collate_key = g_utf8_collate_key_for_filename (caption, -1);
+	}
+
+	return priv->collate_key;
+}
+
+void
+eog_image_cancel_load (EogImage *img)
+{
+	EogImagePrivate *priv;
+
+	g_return_if_fail (EOG_IS_IMAGE (img));
+
+	priv = img->priv;
+
+	g_mutex_lock (&priv->status_mutex);
+
+	if (priv->status == EOG_IMAGE_STATUS_LOADING) {
+		priv->cancel_loading = TRUE;
+	}
+
+	g_mutex_unlock (&priv->status_mutex);
+}
+
+#ifdef HAVE_EXIF
+ExifData *
+eog_image_get_exif_info (EogImage *img)
+{
+	EogImagePrivate *priv;
+	ExifData *data = NULL;
+
+	g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+	priv = img->priv;
+
+	g_mutex_lock (&priv->status_mutex);
+
+	exif_data_ref (priv->exif);
+	data = priv->exif;
+
+	g_mutex_unlock (&priv->status_mutex);
+
+	return data;
+}
+#endif
+
+/**
+ * eog_image_get_xmp_info:
+ * @img: a #EogImage
+ *
+ * Gets the XMP info for @img or NULL if compiled without
+ * libexempi support.
+ *
+ * Returns: (transfer full): the xmp data
+ **/
+gpointer
+eog_image_get_xmp_info (EogImage *img)
+{
+ 	gpointer data = NULL;
+
+ 	g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+#ifdef HAVE_EXEMPI
+	EogImagePrivate *priv;
+ 	priv = img->priv;
+
+	g_mutex_lock (&priv->status_mutex);
+ 	data = (gpointer) xmp_copy (priv->xmp);
+	g_mutex_unlock (&priv->status_mutex);
+#endif
+
+ 	return data;
+}
+
+
+/**
+ * eog_image_get_file:
+ * @img: a #EogImage
+ *
+ * Gets the #GFile associated with @img
+ *
+ * Returns: (transfer full): a #GFile
+ **/
+GFile *
+eog_image_get_file (EogImage *img)
+{
+	g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+	return g_object_ref (img->priv->file);
+}
+
+gboolean
+eog_image_is_modified (EogImage *img)
+{
+	g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+
+	return img->priv->modified;
+}
+
+goffset
+eog_image_get_bytes (EogImage *img)
+{
+	g_return_val_if_fail (EOG_IS_IMAGE (img), 0);
+
+	return img->priv->bytes;
+}
+
+void
+eog_image_modified (EogImage *img)
+{
+	g_return_if_fail (EOG_IS_IMAGE (img));
+
+	g_signal_emit (G_OBJECT (img), signals[SIGNAL_CHANGED], 0);
+}
+
+gchar*
+eog_image_get_uri_for_display (EogImage *img)
+{
+	EogImagePrivate *priv;
+	gchar *uri_str = NULL;
+	gchar *str = NULL;
+
+	g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+	priv = img->priv;
+
+	if (priv->file != NULL) {
+		uri_str = g_file_get_uri (priv->file);
+
+		if (uri_str != NULL) {
+			str = g_uri_unescape_string (uri_str, NULL);
+			g_free (uri_str);
+		}
+	}
+
+	return str;
+}
+
+EogImageStatus
+eog_image_get_status (EogImage *img)
+{
+	g_return_val_if_fail (EOG_IS_IMAGE (img), EOG_IMAGE_STATUS_UNKNOWN);
+
+	return img->priv->status;
+}
+
+/**
+ * eog_image_get_metadata_status:
+ * @img: a #EogImage
+ *
+ * Returns the current status of the image metadata, that is,
+ * whether the metadata has not been read yet, is ready, or not available at all.
+ *
+ * Returns: one of #EogImageMetadataStatus
+ **/
+EogImageMetadataStatus
+eog_image_get_metadata_status (EogImage *img)
+{
+        g_return_val_if_fail (EOG_IS_IMAGE (img), EOG_IMAGE_METADATA_NOT_AVAILABLE);
+
+        return img->priv->metadata_status;
+}
+
+void
+eog_image_data_ref (EogImage *img)
+{
+	g_return_if_fail (EOG_IS_IMAGE (img));
+
+	g_object_ref (G_OBJECT (img));
+	img->priv->data_ref_count++;
+
+	g_assert (img->priv->data_ref_count <= G_OBJECT (img)->ref_count);
+}
+
+void
+eog_image_data_unref (EogImage *img)
+{
+	g_return_if_fail (EOG_IS_IMAGE (img));
+
+	if (img->priv->data_ref_count > 0) {
+		img->priv->data_ref_count--;
+	} else {
+		g_warning ("More image data unrefs than refs.");
+	}
+
+	if (img->priv->data_ref_count == 0) {
+		eog_image_free_mem_private (img);
+	}
+
+	g_object_unref (G_OBJECT (img));
+
+	g_assert (img->priv->data_ref_count <= G_OBJECT (img)->ref_count);
+}
+
+static gint
+compare_quarks (gconstpointer a, gconstpointer b)
+{
+	GQuark quark;
+
+	quark = g_quark_from_string ((const gchar *) a);
+
+	return quark - GPOINTER_TO_INT (b);
+}
+
+/**
+ * eog_image_get_supported_mime_types:
+ *
+ * Gets the list of supported mimetypes
+ *
+ * Returns: (transfer none)(element-type utf8): a #GList of supported mimetypes
+ **/
+GList *
+eog_image_get_supported_mime_types (void)
+{
+	GSList *format_list, *it;
+	gchar **mime_types;
+	int i;
+
+	if (!supported_mime_types) {
+		format_list = gdk_pixbuf_get_formats ();
+
+		for (it = format_list; it != NULL; it = it->next) {
+			mime_types =
+				gdk_pixbuf_format_get_mime_types ((GdkPixbufFormat *) it->data);
+
+			for (i = 0; mime_types[i] != NULL; i++) {
+				supported_mime_types =
+					g_list_prepend (supported_mime_types,
+							g_strdup (mime_types[i]));
+			}
+
+			g_strfreev (mime_types);
+		}
+
+		supported_mime_types = g_list_sort (supported_mime_types,
+						    (GCompareFunc) compare_quarks);
+
+		g_slist_free (format_list);
+	}
+
+	return supported_mime_types;
+}
+
+gboolean
+eog_image_is_supported_mime_type (const char *mime_type)
+{
+	GList *supported_mime_types, *result;
+	GQuark quark;
+
+	if (mime_type == NULL) {
+		return FALSE;
+	}
+
+	supported_mime_types = eog_image_get_supported_mime_types ();
+
+	quark = g_quark_from_string (mime_type);
+
+	result = g_list_find_custom (supported_mime_types,
+				     GINT_TO_POINTER (quark),
+				     (GCompareFunc) compare_quarks);
+
+	return (result != NULL);
+}
+
+static gboolean
+eog_image_iter_advance (EogImage *img)
+{
+	EogImagePrivate *priv;
+ 	gboolean new_frame;
+
+	g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+	g_return_val_if_fail (GDK_IS_PIXBUF_ANIMATION_ITER (img->priv->anim_iter), FALSE);
+
+	priv = img->priv;
+
+	if ((new_frame = gdk_pixbuf_animation_iter_advance (img->priv->anim_iter, NULL)) == TRUE)
+	  {
+		g_mutex_lock (&priv->status_mutex);
+		g_object_unref (priv->image);
+		priv->image = gdk_pixbuf_animation_iter_get_pixbuf (priv->anim_iter);
+	 	g_object_ref (priv->image);
+		/* keep the transformation over time */
+		if (EOG_IS_TRANSFORM (priv->trans)) {
+			GdkPixbuf* transformed = eog_transform_apply (priv->trans, priv->image, NULL);
+			g_object_unref (priv->image);
+			priv->image = transformed;
+			priv->width = gdk_pixbuf_get_width (transformed);
+			priv->height = gdk_pixbuf_get_height (transformed);
+		}
+		g_mutex_unlock (&priv->status_mutex);
+		/* Emit next frame signal so we can update the display */
+		g_signal_emit (img, signals[SIGNAL_NEXT_FRAME], 0,
+			       gdk_pixbuf_animation_iter_get_delay_time (priv->anim_iter));
+	  }
+
+	return new_frame;
+}
+
+/**
+ * eog_image_is_animation:
+ * @img: a #EogImage
+ *
+ * Checks whether a given image is animated.
+ *
+ * Returns: #TRUE if it is an animated image, #FALSE otherwise.
+ *
+ **/
+gboolean
+eog_image_is_animation (EogImage *img)
+{
+	g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+	return img->priv->anim != NULL;
+}
+
+static gboolean
+private_timeout (gpointer data)
+{
+	EogImage *img = EOG_IMAGE (data);
+	EogImagePrivate *priv = img->priv;
+
+	if (eog_image_is_animation (img) &&
+	    !g_source_is_destroyed (g_main_current_source ()) &&
+	    priv->is_playing) {
+		while (eog_image_iter_advance (img) != TRUE) {}; /* cpu-sucking ? */
+			g_timeout_add (gdk_pixbuf_animation_iter_get_delay_time (priv->anim_iter), private_timeout, img);
+	 		return FALSE;
+ 	}
+	priv->is_playing = FALSE;
+	return FALSE; /* stop playing */
+}
+
+/**
+ * eog_image_start_animation:
+ * @img: a #EogImage
+ *
+ * Starts playing an animated image.
+ *
+ * Returns: %TRUE on success, %FALSE if @img is already playing or isn't an animated image.
+ **/
+gboolean
+eog_image_start_animation (EogImage *img)
+{
+	EogImagePrivate *priv;
+
+	g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+	priv = img->priv;
+
+	if (!eog_image_is_animation (img) || priv->is_playing)
+		return FALSE;
+
+	g_mutex_lock (&priv->status_mutex);
+	g_object_ref (priv->anim_iter);
+	priv->is_playing = TRUE;
+	g_mutex_unlock (&priv->status_mutex);
+
+ 	g_timeout_add (gdk_pixbuf_animation_iter_get_delay_time (priv->anim_iter), private_timeout, img);
+
+	return TRUE;
+}
+
+#ifdef HAVE_RSVG
+gboolean
+eog_image_is_svg (EogImage *img)
+{
+	g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+
+	return (img->priv->svg != NULL);
+}
+
+RsvgHandle *
+eog_image_get_svg (EogImage *img)
+{
+	g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+	return img->priv->svg;
+}
+#endif
+
+
+EogTransform *
+eog_image_get_transform (EogImage *img)
+{
+	g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+	return img->priv->trans;
+}
+
+EogTransform*
+eog_image_get_autorotate_transform (EogImage *img)
+{
+	g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+	return img->priv->trans_autorotate;
+}
+
+/**
+ * eog_image_file_changed:
+ * @img: a #EogImage
+ *
+ * Marks the image file contents as changed. Also, emits
+ * EogImage::file-changed signal.
+ **/
+void
+eog_image_file_changed (EogImage *img)
+{
+	g_return_if_fail (EOG_IS_IMAGE (img));
+
+	img->priv->file_is_changed = TRUE;
+	g_signal_emit (img, signals[SIGNAL_FILE_CHANGED], 0);
+}
+
+gboolean
+eog_image_is_file_changed (EogImage *img)
+{
+	g_return_val_if_fail (EOG_IS_IMAGE (img), TRUE);
+
+	return img->priv->file_is_changed;
+}
+
+/**
+ * eog_image_is_file_writable:
+ * @img: a #EogImage
+ *
+ * Evaluate if the user has write permission on the image file.
+ *
+ * Returns: %TRUE on success, %FALSE if the user hasn't write permissions on it,
+ * or @img is not an #EogImage.
+ **/
+gboolean
+eog_image_is_file_writable (EogImage *img)
+{
+	gboolean is_writable;
+
+	g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+
+	is_writable = check_if_file_is_writable (img->priv->file);
+
+	return is_writable;
+}
+
+gboolean
+eog_image_is_jpeg (EogImage *img)
+{
+	g_return_val_if_fail (EOG_IS_IMAGE (img), FALSE);
+
+	return ((img->priv->file_type != NULL) && (g_ascii_strcasecmp (img->priv->file_type, EOG_FILE_FORMAT_JPEG) == 0));
+}
diff --git a/src/eog-image.h b/src/eog-image.h
new file mode 100644
index 0000000..3d1ed52
--- /dev/null
+++ b/src/eog-image.h
@@ -0,0 +1,220 @@
+/* Eye Of Gnome - Image
+ *
+ * Copyright (C) 2007 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EOG_IMAGE_H__
+#define __EOG_IMAGE_H__
+
+#include "eog-jobs.h"
+#include "eog-transform.h"
+#include "eog-image-save-info.h"
+#include "eog-enums.h"
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#ifdef HAVE_EXIF
+#include <libexif/exif-data.h>
+#include "eog-exif-util.h"
+#endif
+
+#ifdef HAVE_LCMS
+#include <lcms2.h>
+#endif
+
+#ifdef HAVE_EXEMPI
+#include <exempi/xmp.h>
+#endif
+
+#ifdef HAVE_RSVG
+#include <librsvg/rsvg.h>
+#endif
+
+G_BEGIN_DECLS
+
+#ifndef __EOG_IMAGE_DECLR__
+#define __EOG_IMAGE_DECLR__
+typedef struct _EogImage EogImage;
+#endif
+typedef struct _EogImageClass EogImageClass;
+typedef struct _EogImagePrivate EogImagePrivate;
+
+#define EOG_TYPE_IMAGE            (eog_image_get_type ())
+#define EOG_IMAGE(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), EOG_TYPE_IMAGE, EogImage))
+#define EOG_IMAGE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST((klass),  EOG_TYPE_IMAGE, EogImageClass))
+#define EOG_IS_IMAGE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOG_TYPE_IMAGE))
+#define EOG_IS_IMAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),  EOG_TYPE_IMAGE))
+#define EOG_IMAGE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj),  EOG_TYPE_IMAGE, EogImageClass))
+
+typedef enum {
+	EOG_IMAGE_ERROR_SAVE_NOT_LOCAL,
+	EOG_IMAGE_ERROR_NOT_LOADED,
+	EOG_IMAGE_ERROR_NOT_SAVED,
+	EOG_IMAGE_ERROR_VFS,
+	EOG_IMAGE_ERROR_FILE_EXISTS,
+	EOG_IMAGE_ERROR_TMP_FILE_FAILED,
+	EOG_IMAGE_ERROR_GENERIC,
+	EOG_IMAGE_ERROR_UNKNOWN
+} EogImageError;
+
+#define EOG_IMAGE_ERROR eog_image_error_quark ()
+
+typedef enum {
+	EOG_IMAGE_STATUS_UNKNOWN,
+	EOG_IMAGE_STATUS_LOADING,
+	EOG_IMAGE_STATUS_LOADED,
+	EOG_IMAGE_STATUS_SAVING,
+	EOG_IMAGE_STATUS_FAILED
+} EogImageStatus;
+
+typedef enum {
+  EOG_IMAGE_METADATA_NOT_READ,
+  EOG_IMAGE_METADATA_NOT_AVAILABLE,
+  EOG_IMAGE_METADATA_READY
+} EogImageMetadataStatus;
+
+struct _EogImage {
+	GObject parent;
+
+	EogImagePrivate *priv;
+};
+
+struct _EogImageClass {
+	GObjectClass parent_class;
+
+	void (* changed) 	   (EogImage *img);
+
+	void (* size_prepared)     (EogImage *img,
+				    int       width,
+				    int       height);
+
+	void (* save_progress)     (EogImage *img,
+				    gfloat    progress);
+
+	void (* next_frame)        (EogImage *img,
+				    gint delay);
+
+	void (* file_changed)      (EogImage *img);
+};
+
+GType	          eog_image_get_type	             (void) G_GNUC_CONST;
+
+GQuark            eog_image_error_quark              (void);
+
+EogImage         *eog_image_new                      (const char *txt_uri);
+
+EogImage         *eog_image_new_file                 (GFile *file);
+
+gboolean          eog_image_load                     (EogImage   *img,
+					              EogImageData data2read,
+					              EogJob     *job,
+					              GError    **error);
+
+void              eog_image_cancel_load              (EogImage   *img);
+
+gboolean          eog_image_has_data                 (EogImage   *img,
+					              EogImageData data);
+
+void              eog_image_data_ref                 (EogImage   *img);
+
+void              eog_image_data_unref               (EogImage   *img);
+
+gboolean          eog_image_save_as_by_info          (EogImage   *img,
+		      			              EogImageSaveInfo *source,
+		      			              EogImageSaveInfo *target,
+		      			              GError    **error);
+
+gboolean          eog_image_save_by_info             (EogImage   *img,
+					              EogImageSaveInfo *source,
+					              GError    **error);
+
+GdkPixbuf*        eog_image_get_pixbuf               (EogImage   *img);
+
+void              eog_image_get_size                 (EogImage   *img,
+					              gint       *width,
+					              gint       *height);
+
+goffset           eog_image_get_bytes                (EogImage   *img);
+
+gboolean          eog_image_is_modified              (EogImage   *img);
+
+void              eog_image_modified                 (EogImage   *img);
+
+const gchar*      eog_image_get_caption              (EogImage   *img);
+
+const gchar      *eog_image_get_collate_key          (EogImage   *img);
+
+#if HAVE_EXIF
+ExifData*      eog_image_get_exif_info            (EogImage   *img);
+#endif
+
+gpointer          eog_image_get_xmp_info             (EogImage   *img);
+
+GFile*            eog_image_get_file                 (EogImage   *img);
+
+gchar*            eog_image_get_uri_for_display      (EogImage   *img);
+
+EogImageStatus    eog_image_get_status               (EogImage   *img);
+
+EogImageMetadataStatus eog_image_get_metadata_status (EogImage   *img);
+
+void              eog_image_transform                (EogImage   *img,
+						      EogTransform *trans,
+						      EogJob     *job);
+
+void              eog_image_autorotate               (EogImage   *img);
+
+#ifdef HAVE_LCMS
+cmsHPROFILE       eog_image_get_profile              (EogImage    *img);
+
+void              eog_image_apply_display_profile    (EogImage    *img,
+						      cmsHPROFILE  display_profile);
+#endif
+
+void              eog_image_undo                     (EogImage   *img);
+
+GList		 *eog_image_get_supported_mime_types (void);
+
+gboolean          eog_image_is_supported_mime_type   (const char *mime_type);
+
+gboolean          eog_image_is_animation             (EogImage *img);
+
+gboolean          eog_image_start_animation          (EogImage *img);
+
+#ifdef HAVE_RSVG
+gboolean          eog_image_is_svg                   (EogImage *img);
+RsvgHandle       *eog_image_get_svg                  (EogImage *img);
+#endif
+
+EogTransform     *eog_image_get_transform            (EogImage *img);
+EogTransform     *eog_image_get_autorotate_transform (EogImage *img);
+
+gboolean          eog_image_is_jpeg                  (EogImage *img);
+
+void              eog_image_file_changed             (EogImage *img);
+
+gboolean          eog_image_is_file_changed          (EogImage *img);
+
+gboolean          eog_image_is_file_writable         (EogImage *img);
+
+G_END_DECLS
+
+#endif /* __EOG_IMAGE_H__ */
diff --git a/src/eog-job-queue.c b/src/eog-job-queue.c
new file mode 100644
index 0000000..ca79396
--- /dev/null
+++ b/src/eog-job-queue.c
@@ -0,0 +1,212 @@
+/* Eye Of Gnome - Jobs Queue
+ *
+ * Copyright (C) 2006 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ *
+ * Based on evince code (shell/ev-job-queue.c) by:
+ * 	- Martin Kretzschmar <martink gnome org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "eog-jobs.h"
+#include "eog-job-queue.h"
+
+static GCond  render_cond;
+static GMutex eog_queue_mutex;
+
+static GQueue *load_queue = NULL;
+static GQueue *transform_queue = NULL;
+static GQueue *save_queue = NULL;
+static GQueue *copy_queue = NULL;
+
+static gboolean
+remove_job_from_queue (GQueue *queue, EogJob *job)
+{
+	GList *list;
+
+	list = g_queue_find (queue, job);
+
+	if (list) {
+		g_object_unref (G_OBJECT (job));
+		g_queue_delete_link (queue, list);
+
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+static void
+add_job_to_queue_locked (GQueue *queue, EogJob  *job)
+{
+	g_object_ref (job);
+	g_queue_push_tail (queue, job);
+	g_cond_broadcast (&render_cond);
+}
+
+static gboolean
+notify_finished (GObject *job)
+{
+	eog_job_finished (EOG_JOB (job));
+
+	return FALSE;
+}
+
+static void
+handle_job (EogJob *job)
+{
+	g_object_ref (G_OBJECT (job));
+
+	// Do the EOG_JOB cast for safety
+	eog_job_run (EOG_JOB (job));
+
+	g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
+			 (GSourceFunc) notify_finished,
+			 job,
+			 g_object_unref);
+}
+
+static gboolean
+no_jobs_available_unlocked (void)
+{
+	return  g_queue_is_empty (load_queue)      &&
+		g_queue_is_empty (transform_queue) &&
+		g_queue_is_empty (save_queue) &&
+		g_queue_is_empty (copy_queue);
+}
+
+static EogJob *
+search_for_jobs_unlocked (void)
+{
+	EogJob *job;
+
+	job = (EogJob *) g_queue_pop_head (load_queue);
+	if (job)
+		return job;
+
+	job = (EogJob *) g_queue_pop_head (transform_queue);
+	if (job)
+		return job;
+
+	job = (EogJob *) g_queue_pop_head (save_queue);
+	if (job)
+		return job;
+
+	job = (EogJob *) g_queue_pop_head (copy_queue);
+	if (job)
+		return job;
+
+	return NULL;
+}
+
+static gpointer
+eog_render_thread (gpointer data)
+{
+	while (TRUE) {
+		EogJob *job;
+
+		g_mutex_lock (&eog_queue_mutex);
+
+		if (no_jobs_available_unlocked ()) {
+			g_cond_wait (&render_cond, &eog_queue_mutex);
+		}
+
+		job = search_for_jobs_unlocked ();
+
+		g_mutex_unlock (&eog_queue_mutex);
+
+		/* Now that we have our job, we handle it */
+		if (job) {
+			handle_job (job);
+			g_object_unref (G_OBJECT (job));
+		}
+	}
+	return NULL;
+
+}
+
+void
+eog_job_queue_init (void)
+{
+	load_queue = g_queue_new ();
+	transform_queue = g_queue_new ();
+	save_queue = g_queue_new ();
+	copy_queue = g_queue_new ();
+
+	/* Unref to detach the thread */
+	g_thread_unref (g_thread_new ("EogJobQueue", eog_render_thread, NULL));
+}
+
+static GQueue *
+find_queue (EogJob *job)
+{
+	if (EOG_IS_JOB_LOAD (job)) {
+		return load_queue;
+	} else if (EOG_IS_JOB_TRANSFORM (job)) {
+		return transform_queue;
+	} else if (EOG_IS_JOB_SAVE (job)) {
+		return save_queue;
+	} else if (EOG_IS_JOB_COPY (job)) {
+		return copy_queue;
+	}
+
+	g_assert_not_reached ();
+
+	return NULL;
+}
+
+void
+eog_job_queue_add_job (EogJob *job)
+{
+	GQueue *queue;
+
+	g_return_if_fail (EOG_IS_JOB (job));
+
+	queue = find_queue (job);
+
+	g_mutex_lock (&eog_queue_mutex);
+
+	add_job_to_queue_locked (queue, job);
+
+	g_mutex_unlock (&eog_queue_mutex);
+}
+
+gboolean
+eog_job_queue_remove_job (EogJob *job)
+{
+	gboolean retval = FALSE;
+
+	g_return_val_if_fail (EOG_IS_JOB (job), FALSE);
+
+	g_mutex_lock (&eog_queue_mutex);
+
+	if (EOG_IS_JOB_LOAD (job)) {
+		retval = remove_job_from_queue (load_queue, job);
+	} else if (EOG_IS_JOB_TRANSFORM (job)) {
+		retval = remove_job_from_queue (transform_queue, job);
+	} else if (EOG_IS_JOB_SAVE (job)) {
+		retval = remove_job_from_queue (save_queue, job);
+	} else if (EOG_IS_JOB_COPY (job)) {
+		retval = remove_job_from_queue (copy_queue, job);
+	} else {
+		g_assert_not_reached ();
+	}
+
+	g_mutex_unlock (&eog_queue_mutex);
+
+	return retval;
+}
diff --git a/src/eog-job-queue.h b/src/eog-job-queue.h
new file mode 100644
index 0000000..2bed060
--- /dev/null
+++ b/src/eog-job-queue.h
@@ -0,0 +1,42 @@
+/* Eye Of Gnome - Jobs Queue
+ *
+ * Copyright (C) 2006 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ *
+ * Based on evince code (shell/ev-job-queue.h) by:
+ * 	- Martin Kretzschmar <martink gnome org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EOG_JOB_QUEUE_H__
+#define __EOG_JOB_QUEUE_H__
+
+#include "eog-jobs.h"
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+void     eog_job_queue_init       (void);
+
+void     eog_job_queue_add_job    (EogJob    *job);
+
+gboolean eog_job_queue_remove_job (EogJob    *job);
+
+G_END_DECLS
+
+#endif /* __EOG_JOB_QUEUE_H__ */
diff --git a/src/eog-jobs.c b/src/eog-jobs.c
new file mode 100644
index 0000000..25ecd28
--- /dev/null
+++ b/src/eog-jobs.c
@@ -0,0 +1,690 @@
+/* Eye Of Gnome - Jobs
+ *
+ * Copyright (C) 2006 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ *
+ * Based on evince code (shell/ev-jobs.c) by:
+ * 	- Martin Kretzschmar <martink gnome org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "eog-uri-converter.h"
+#include "eog-jobs.h"
+#include "eog-job-queue.h"
+#include "eog-image.h"
+#include "eog-transform.h"
+#include "photos-utils.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#define EOG_JOB_GET_PRIVATE(object) \
+	(G_TYPE_INSTANCE_GET_PRIVATE ((object), EOG_TYPE_JOB, EogJobPrivate))
+
+G_DEFINE_TYPE (EogJob, eog_job, G_TYPE_OBJECT);
+G_DEFINE_TYPE (EogJobLoad, eog_job_load, EOG_TYPE_JOB);
+G_DEFINE_TYPE (EogJobTransform, eog_job_transform, EOG_TYPE_JOB);
+G_DEFINE_TYPE (EogJobSave, eog_job_save, EOG_TYPE_JOB);
+G_DEFINE_TYPE (EogJobSaveAs, eog_job_save_as, EOG_TYPE_JOB_SAVE);
+G_DEFINE_TYPE (EogJobCopy, eog_job_copy, EOG_TYPE_JOB);
+
+enum
+{
+	SIGNAL_FINISHED,
+	SIGNAL_PROGRESS,
+	SIGNAL_LAST_SIGNAL
+};
+
+static guint job_signals[SIGNAL_LAST_SIGNAL];
+
+static void eog_job_copy_run      (EogJob *ejob);
+static void eog_job_load_run 	  (EogJob *ejob);
+static void eog_job_save_run      (EogJob *job);
+static void eog_job_save_as_run   (EogJob *job);
+static void eog_job_transform_run (EogJob *ejob);
+
+static void eog_job_init (EogJob *job)
+{
+	/* NOTE: We need to allocate the mutex here so the ABI stays the same when it used to use g_mutex_new */
+	job->mutex = g_malloc (sizeof (GMutex));
+	g_mutex_init (job->mutex);
+	job->progress = 0.0;
+}
+
+static void
+eog_job_dispose (GObject *object)
+{
+	EogJob *job;
+
+	job = EOG_JOB (object);
+
+	if (job->error) {
+		g_error_free (job->error);
+		job->error = NULL;
+	}
+
+	if (job->mutex) {
+		g_mutex_clear (job->mutex);
+		g_free (job->mutex);
+	}
+
+	(* G_OBJECT_CLASS (eog_job_parent_class)->dispose) (object);
+}
+
+static void
+eog_job_run_default (EogJob *job)
+{
+	g_critical ("Class \"%s\" does not implement the required run action",
+		    G_OBJECT_CLASS_NAME (G_OBJECT_GET_CLASS (job)));
+}
+
+static void
+eog_job_class_init (EogJobClass *class)
+{
+	GObjectClass *oclass;
+
+	oclass = G_OBJECT_CLASS (class);
+
+	oclass->dispose = eog_job_dispose;
+
+	class->run = eog_job_run_default;
+
+	job_signals [SIGNAL_FINISHED] =
+		g_signal_new ("finished",
+			      EOG_TYPE_JOB,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (EogJobClass, finished),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE, 0);
+
+	job_signals [SIGNAL_PROGRESS] =
+		g_signal_new ("progress",
+			      EOG_TYPE_JOB,
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (EogJobClass, progress),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__FLOAT,
+			      G_TYPE_NONE, 1,
+			      G_TYPE_FLOAT);
+}
+
+void
+eog_job_finished (EogJob *job)
+{
+	g_return_if_fail (EOG_IS_JOB (job));
+
+	g_signal_emit (job, job_signals[SIGNAL_FINISHED], 0);
+}
+
+/**
+ * eog_job_run:
+ * @job: the job to execute.
+ *
+ * Executes the job passed as @job. Usually there is no need to call this
+ * on your own. Jobs should be executed by using the EogJobQueue.
+ **/
+void
+eog_job_run (EogJob *job)
+{
+	EogJobClass *class;
+
+	g_return_if_fail (EOG_IS_JOB (job));
+
+	class = EOG_JOB_GET_CLASS (job);
+	if (class->run)
+		class->run (job);
+	else
+		eog_job_run_default (job);
+}
+static gboolean
+notify_progress (gpointer data)
+{
+	EogJob *job = EOG_JOB (data);
+
+	g_signal_emit (job, job_signals[SIGNAL_PROGRESS], 0, job->progress);
+
+	return FALSE;
+}
+
+void
+eog_job_set_progress (EogJob *job, float progress)
+{
+	g_return_if_fail (EOG_IS_JOB (job));
+	g_return_if_fail (progress >= 0.0 && progress <= 1.0);
+
+	g_mutex_lock (job->mutex);
+	job->progress = progress;
+	g_mutex_unlock (job->mutex);
+
+	g_idle_add (notify_progress, job);
+}
+
+static void eog_job_load_init (EogJobLoad *job) { /* Do Nothing */ }
+
+static void
+eog_job_load_dispose (GObject *object)
+{
+	EogJobLoad *job;
+
+	job = EOG_JOB_LOAD (object);
+
+	if (job->image) {
+		g_object_unref (job->image);
+		job->image = NULL;
+	}
+
+	(* G_OBJECT_CLASS (eog_job_load_parent_class)->dispose) (object);
+}
+
+static void
+eog_job_load_class_init (EogJobLoadClass *class)
+{
+	GObjectClass *oclass;
+
+	oclass = G_OBJECT_CLASS (class);
+
+	oclass->dispose = eog_job_load_dispose;
+	EOG_JOB_CLASS (class)->run = eog_job_load_run;
+}
+
+EogJob *
+eog_job_load_new (EogImage *image, EogImageData data)
+{
+	EogJobLoad *job;
+
+	job = g_object_new (EOG_TYPE_JOB_LOAD, NULL);
+
+	if (image) {
+		job->image = g_object_ref (image);
+	}
+	job->data = data;
+
+	return EOG_JOB (job);
+}
+
+static void
+eog_job_load_run (EogJob *job)
+{
+	g_return_if_fail (EOG_IS_JOB_LOAD (job));
+
+	if (job->error) {
+	        g_error_free (job->error);
+		job->error = NULL;
+	}
+
+	eog_image_load (EOG_IMAGE (EOG_JOB_LOAD (job)->image),
+			EOG_JOB_LOAD (job)->data,
+			job,
+			&job->error);
+
+	job->finished = TRUE;
+}
+
+static void eog_job_transform_init (EogJobTransform *job) { /* Do Nothing */ }
+
+static void
+eog_job_transform_dispose (GObject *object)
+{
+	EogJobTransform *job;
+
+	job = EOG_JOB_TRANSFORM (object);
+
+	if (job->trans) {
+		g_object_unref (job->trans);
+		job->trans = NULL;
+	}
+
+	g_list_foreach (job->images, (GFunc) g_object_unref, NULL);
+	g_list_free (job->images);
+
+	(* G_OBJECT_CLASS (eog_job_transform_parent_class)->dispose) (object);
+}
+
+static void
+eog_job_transform_class_init (EogJobTransformClass *class)
+{
+	GObjectClass *oclass;
+
+	oclass = G_OBJECT_CLASS (class);
+
+	oclass->dispose = eog_job_transform_dispose;
+
+	EOG_JOB_CLASS (class)->run = eog_job_transform_run;
+}
+
+EogJob *
+eog_job_transform_new (GList *images, EogTransform *trans)
+{
+	EogJobTransform *job;
+
+	job = g_object_new (EOG_TYPE_JOB_TRANSFORM, NULL);
+
+	if (trans) {
+		job->trans = g_object_ref (trans);
+	} else {
+		job->trans = NULL;
+	}
+
+	job->images = images;
+
+	return EOG_JOB (job);
+}
+
+static gboolean
+eog_job_transform_image_modified (gpointer data)
+{
+	g_return_val_if_fail (EOG_IS_IMAGE (data), FALSE);
+
+	eog_image_modified (EOG_IMAGE (data));
+	g_object_unref (G_OBJECT (data));
+
+	return FALSE;
+}
+
+void
+eog_job_transform_run (EogJob *ejob)
+{
+	EogJobTransform *job;
+	GList *it;
+
+	g_return_if_fail (EOG_IS_JOB_TRANSFORM (ejob));
+
+	job = EOG_JOB_TRANSFORM (ejob);
+
+	if (ejob->error) {
+	        g_error_free (ejob->error);
+		ejob->error = NULL;
+	}
+
+	for (it = job->images; it != NULL; it = it->next) {
+		EogImage *image = EOG_IMAGE (it->data);
+
+		if (job->trans == NULL) {
+			eog_image_undo (image);
+		} else {
+			eog_image_transform (image, job->trans, ejob);
+		}
+
+		if (eog_image_is_modified (image) || job->trans == NULL) {
+			g_object_ref (image);
+			g_idle_add (eog_job_transform_image_modified, image);
+		}
+	}
+
+	ejob->finished = TRUE;
+}
+
+static void eog_job_save_init (EogJobSave *job) { /* do nothing */ }
+
+static void
+eog_job_save_dispose (GObject *object)
+{
+	EogJobSave *job;
+
+	job = EOG_JOB_SAVE (object);
+
+	if (job->images) {
+		g_list_foreach (job->images, (GFunc) g_object_unref, NULL);
+		g_list_free (job->images);
+		job->images = NULL;
+	}
+
+	(* G_OBJECT_CLASS (eog_job_save_parent_class)->dispose) (object);
+}
+
+static void
+eog_job_save_class_init (EogJobSaveClass *class)
+{
+	G_OBJECT_CLASS (class)->dispose = eog_job_save_dispose;
+	EOG_JOB_CLASS (class)->run = eog_job_save_run;
+}
+
+EogJob *
+eog_job_save_new (GList *images)
+{
+	EogJobSave *job;
+
+	job = g_object_new (EOG_TYPE_JOB_SAVE, NULL);
+
+	job->images = images;
+	job->current_image = NULL;
+
+	return EOG_JOB (job);
+}
+
+static void
+save_progress_handler (EogImage *image, gfloat progress, gpointer data)
+{
+	EogJobSave *job = EOG_JOB_SAVE (data);
+	guint n_images = g_list_length (job->images);
+	gfloat job_progress;
+
+	job_progress = (job->current_pos / (gfloat) n_images) + (progress / n_images);
+
+	eog_job_set_progress (EOG_JOB (job), job_progress);
+}
+
+static void
+eog_job_save_run (EogJob *ejob)
+{
+	EogJobSave *job;
+	GList *it;
+
+	g_return_if_fail (EOG_IS_JOB_SAVE (ejob));
+
+	job = EOG_JOB_SAVE (ejob);
+
+	job->current_pos = 0;
+
+	for (it = job->images; it != NULL; it = it->next, job->current_pos++) {
+		EogImage *image = EOG_IMAGE (it->data);
+		EogImageSaveInfo *save_info = NULL;
+		gulong handler_id = 0;
+		gboolean success = FALSE;
+
+		job->current_image = image;
+
+		/* Make sure the image doesn't go away while saving */
+		eog_image_data_ref (image);
+
+		if (!eog_image_has_data (image, EOG_IMAGE_DATA_ALL)) {
+			EogImageMetadataStatus m_status;
+			gint data2load = 0;
+
+			m_status = eog_image_get_metadata_status (image);
+			if (!eog_image_has_data (image, EOG_IMAGE_DATA_IMAGE)) {
+				// Queue full read in this case
+				data2load = EOG_IMAGE_DATA_ALL;
+			} else if (m_status == EOG_IMAGE_METADATA_NOT_READ)
+			{
+				// Load only if we haven't read it yet
+				data2load = EOG_IMAGE_DATA_EXIF
+						| EOG_IMAGE_DATA_XMP;
+			}
+
+			if (data2load != 0) {
+				eog_image_load (image,
+						data2load,
+						NULL,
+						&ejob->error);
+			}
+		}
+
+		handler_id = g_signal_connect (G_OBJECT (image),
+					       "save-progress",
+				               G_CALLBACK (save_progress_handler),
+					       job);
+
+		save_info = eog_image_save_info_new_from_image (image);
+
+		success = eog_image_save_by_info (image,
+						  save_info,
+						  &ejob->error);
+
+		if (save_info)
+			g_object_unref (save_info);
+
+		if (handler_id != 0)
+			g_signal_handler_disconnect (G_OBJECT (image), handler_id);
+
+		eog_image_data_unref (image);
+
+		if (!success) break;
+	}
+
+	ejob->finished = TRUE;
+}
+
+static void eog_job_save_as_init (EogJobSaveAs *job) { /* do nothing */ }
+
+static void eog_job_save_as_dispose (GObject *object)
+{
+	EogJobSaveAs *job = EOG_JOB_SAVE_AS (object);
+
+	if (job->converter != NULL) {
+		g_object_unref (job->converter);
+		job->converter = NULL;
+	}
+
+	if (job->file != NULL) {
+		g_object_unref (job->file);
+		job->file = NULL;
+	}
+
+	(* G_OBJECT_CLASS (eog_job_save_as_parent_class)->dispose) (object);
+}
+
+static void
+eog_job_save_as_class_init (EogJobSaveAsClass *class)
+{
+	G_OBJECT_CLASS (class)->dispose = eog_job_save_as_dispose;
+	EOG_JOB_CLASS (class)->run = eog_job_save_as_run;
+}
+
+EogJob *
+eog_job_save_as_new (GList *images, EogURIConverter *converter, GFile *file)
+{
+	EogJobSaveAs *job;
+
+	g_assert (converter != NULL || g_list_length (images) == 1);
+
+	job = g_object_new (EOG_TYPE_JOB_SAVE_AS, NULL);
+
+	EOG_JOB_SAVE(job)->images = images;
+
+	job->converter = converter ? g_object_ref (converter) : NULL;
+	job->file = file ? g_object_ref (file) : NULL;
+
+	return EOG_JOB (job);
+}
+
+static void
+eog_job_save_as_run (EogJob *ejob)
+{
+	EogJobSave *job;
+	EogJobSaveAs *saveas_job;
+	GList *it;
+	guint n_images;
+
+	g_return_if_fail (EOG_IS_JOB_SAVE_AS (ejob));
+
+	job = EOG_JOB_SAVE (ejob);
+
+	n_images = g_list_length (job->images);
+
+	saveas_job = EOG_JOB_SAVE_AS (job);
+
+	job->current_pos = 0;
+
+	for (it = job->images; it != NULL; it = it->next, job->current_pos++) {
+		GdkPixbufFormat *format;
+		EogImageSaveInfo *src_info, *dest_info;
+		EogImage *image = EOG_IMAGE (it->data);
+		gboolean success = FALSE;
+		gulong handler_id = 0;
+
+		job->current_image = image;
+
+		eog_image_data_ref (image);
+
+		if (!eog_image_has_data (image, EOG_IMAGE_DATA_ALL)) {
+			EogImageMetadataStatus m_status;
+			gint data2load = 0;
+
+			m_status = eog_image_get_metadata_status (image);
+			if (!eog_image_has_data (image, EOG_IMAGE_DATA_IMAGE)) {
+				// Queue full read in this case
+				data2load = EOG_IMAGE_DATA_ALL;
+			} else if (m_status == EOG_IMAGE_METADATA_NOT_READ)
+			{
+				// Load only if we haven't read it yet
+				data2load = EOG_IMAGE_DATA_EXIF
+						| EOG_IMAGE_DATA_XMP;
+			}
+
+			if (data2load != 0) {
+				eog_image_load (image,
+						data2load,
+						NULL,
+						&ejob->error);
+			}
+		}
+
+
+		g_assert (ejob->error == NULL);
+
+		handler_id = g_signal_connect (G_OBJECT (image),
+					       "save-progress",
+				               G_CALLBACK (save_progress_handler),
+					       job);
+
+		src_info = eog_image_save_info_new_from_image (image);
+
+		if (n_images == 1) {
+			g_assert (saveas_job->file != NULL);
+
+			format = photos_utils_get_pixbuf_format (saveas_job->file);
+
+			dest_info = eog_image_save_info_new_from_file (saveas_job->file,
+								       format);
+
+		/* SaveAsDialog has already secured permission to overwrite */
+			if (dest_info->exists) {
+				dest_info->overwrite = TRUE;
+			}
+		} else {
+			GFile *dest_file;
+			gboolean result;
+
+			result = eog_uri_converter_do (saveas_job->converter,
+						       image,
+						       &dest_file,
+						       &format,
+						       NULL);
+
+			g_assert (result);
+
+			dest_info = eog_image_save_info_new_from_file (dest_file,
+								       format);
+		}
+
+		success = eog_image_save_as_by_info (image,
+						     src_info,
+						     dest_info,
+						     &ejob->error);
+
+		if (src_info)
+			g_object_unref (src_info);
+
+		if (dest_info)
+			g_object_unref (dest_info);
+
+		if (handler_id != 0)
+			g_signal_handler_disconnect (G_OBJECT (image), handler_id);
+
+		eog_image_data_unref (image);
+
+		if (!success)
+			break;
+	}
+
+	ejob->finished = TRUE;
+}
+
+static void eog_job_copy_init (EogJobCopy *job) { /* do nothing */};
+
+static void
+eog_job_copy_dispose (GObject *object)
+{
+	EogJobCopy *job = EOG_JOB_COPY (object);
+
+	if (job->dest) {
+		g_free (job->dest);
+		job->dest = NULL;
+	}
+
+	(* G_OBJECT_CLASS (eog_job_copy_parent_class)->dispose) (object);
+}
+
+static void
+eog_job_copy_class_init (EogJobCopyClass *class)
+{
+	G_OBJECT_CLASS (class)->dispose = eog_job_copy_dispose;
+	EOG_JOB_CLASS (class)->run = eog_job_copy_run;
+}
+
+EogJob *
+eog_job_copy_new (GList *images, const gchar *dest)
+{
+	EogJobCopy *job;
+
+	g_assert (images != NULL && dest != NULL);
+
+	job = g_object_new (EOG_TYPE_JOB_COPY, NULL);
+
+	job->images = images;
+	job->dest = g_strdup (dest);
+
+	return EOG_JOB (job);
+}
+
+static void
+eog_job_copy_progress_callback (goffset current_num_bytes,
+				goffset total_num_bytes,
+				gpointer user_data)
+{
+	gfloat job_progress;
+	guint n_images;
+	EogJobCopy *job;
+
+	job = EOG_JOB_COPY (user_data);
+	n_images = g_list_length (job->images);
+
+	job_progress =  ((current_num_bytes / (gfloat) total_num_bytes) + job->current_pos)/n_images;
+
+	eog_job_set_progress (EOG_JOB (job), job_progress);
+}
+
+void
+eog_job_copy_run (EogJob *ejob)
+{
+	EogJobCopy *job;
+	GList *it;
+	GFile *src, *dest;
+	gchar *filename, *dest_filename;
+
+	g_return_if_fail (EOG_IS_JOB_COPY (ejob));
+
+	job = EOG_JOB_COPY (ejob);
+	job->current_pos = 0;
+
+	for (it = job->images; it != NULL; it = g_list_next (it), job->current_pos++) {
+		src = (GFile *) it->data;
+		filename = g_file_get_basename (src);
+		dest_filename = g_build_filename (job->dest, filename, NULL);
+		dest = g_file_new_for_path (dest_filename);
+
+		g_file_copy (src, dest,
+			     G_FILE_COPY_OVERWRITE, NULL,
+			     eog_job_copy_progress_callback, job,
+			     &ejob->error);
+		g_free (filename);
+		g_free (dest_filename);
+	}
+
+	ejob->finished = TRUE;
+}
diff --git a/src/eog-jobs.h b/src/eog-jobs.h
new file mode 100644
index 0000000..400be0d
--- /dev/null
+++ b/src/eog-jobs.h
@@ -0,0 +1,228 @@
+/* Eye Of Gnome - Jobs
+ *
+ * Copyright (C) 2006 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ *
+ * Based on evince code (shell/ev-jobs.h) by:
+ * 	- Martin Kretzschmar <martink gnome org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EOG_JOBS_H__
+#define __EOG_JOBS_H__
+
+#include "eog-transform.h"
+#include "eog-enums.h"
+
+#include <glib.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#ifndef __EOG_IMAGE_DECLR__
+#define __EOG_IMAGE_DECLR__
+  typedef struct _EogImage EogImage;
+#endif
+
+#ifndef __EOG_URI_CONVERTER_DECLR__
+#define __EOG_URI_CONVERTER_DECLR__
+typedef struct _EogURIConverter EogURIConverter;
+#endif
+
+#ifndef __EOG_JOB_DECLR__
+#define __EOG_JOB_DECLR__
+typedef struct _EogJob EogJob;
+#endif
+typedef struct _EogJobClass EogJobClass;
+
+typedef struct _EogJobLoad EogJobLoad;
+typedef struct _EogJobLoadClass EogJobLoadClass;
+
+typedef struct _EogJobTransform EogJobTransform;
+typedef struct _EogJobTransformClass EogJobTransformClass;
+
+typedef struct _EogJobSave EogJobSave;
+typedef struct _EogJobSaveClass EogJobSaveClass;
+
+typedef struct _EogJobSaveAs EogJobSaveAs;
+typedef struct _EogJobSaveAsClass EogJobSaveAsClass;
+
+typedef struct _EogJobCopy EogJobCopy;
+typedef struct _EogJobCopyClass EogJobCopyClass;
+
+#define EOG_TYPE_JOB		       (eog_job_get_type())
+#define EOG_JOB(obj)		       (G_TYPE_CHECK_INSTANCE_CAST((obj), EOG_TYPE_JOB, EogJob))
+#define EOG_JOB_CLASS(klass)	       (G_TYPE_CHECK_CLASS_CAST((klass),  EOG_TYPE_JOB, EogJobClass))
+#define EOG_IS_JOB(obj)	               (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOG_TYPE_JOB))
+#define EOG_JOB_GET_CLASS(obj)         (G_TYPE_INSTANCE_GET_CLASS ((obj), EOG_TYPE_JOB, EogJobClass))
+
+#define EOG_TYPE_JOB_LOAD	       (eog_job_load_get_type())
+#define EOG_JOB_LOAD(obj)	       (G_TYPE_CHECK_INSTANCE_CAST((obj), EOG_TYPE_JOB_LOAD, EogJobLoad))
+#define EOG_JOB_LOAD_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST((klass),  EOG_TYPE_JOB_LOAD, EogJobLoadClass))
+#define EOG_IS_JOB_LOAD(obj)	       (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOG_TYPE_JOB_LOAD))
+
+#define EOG_TYPE_JOB_TRANSFORM	       (eog_job_transform_get_type())
+#define EOG_JOB_TRANSFORM(obj)	       (G_TYPE_CHECK_INSTANCE_CAST((obj), EOG_TYPE_JOB_TRANSFORM, EogJobTransform))
+#define EOG_JOB_TRANSFORM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),  EOG_TYPE_JOB_TRANSFORM, EogJobTransformClass))
+#define EOG_IS_JOB_TRANSFORM(obj)      (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOG_TYPE_JOB_TRANSFORM))
+
+#define EOG_TYPE_JOB_SAVE              (eog_job_save_get_type())
+#define EOG_JOB_SAVE(obj)              (G_TYPE_CHECK_INSTANCE_CAST((obj), EOG_TYPE_JOB_SAVE, EogJobSave))
+#define EOG_JOB_SAVE_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST((klass), EOG_TYPE_JOB_SAVE, EogJobSaveClass))
+#define EOG_IS_JOB_SAVE(obj)           (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOG_TYPE_JOB_SAVE))
+#define EOG_JOB_SAVE_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), EOG_TYPE_JOB_SAVE, EogJobSaveClass))
+
+#define EOG_TYPE_JOB_SAVE_AS           (eog_job_save_as_get_type())
+#define EOG_JOB_SAVE_AS(obj)           (G_TYPE_CHECK_INSTANCE_CAST((obj), EOG_TYPE_JOB_SAVE_AS, EogJobSaveAs))
+#define EOG_JOB_SAVE_AS_CLASS(klass)   (G_TYPE_CHECK_CLASS_CAST((klass), EOG_TYPE_JOB_SAVE_AS, EogJobSaveAsClass))
+#define EOG_IS_JOB_SAVE_AS(obj)        (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOG_TYPE_JOB_SAVE_AS))
+
+#define EOG_TYPE_JOB_COPY	       (eog_job_copy_get_type())
+#define EOG_JOB_COPY(obj)	       (G_TYPE_CHECK_INSTANCE_CAST((obj), EOG_TYPE_JOB_COPY, EogJobCopy))
+#define EOG_JOB_COPY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),  EOG_TYPE_JOB_COPY, EogJobCopyClass))
+#define EOG_IS_JOB_COPY(obj)      (G_TYPE_CHECK_INSTANCE_TYPE((obj), EOG_TYPE_JOB_COPY))
+
+
+struct _EogJob
+{
+	GObject  parent;
+
+	GError   *error;
+	GMutex   *mutex;
+	float     progress;
+	gboolean  finished;
+};
+
+struct _EogJobClass
+{
+	GObjectClass parent_class;
+
+	void    (* finished) (EogJob *job);
+	void    (* progress) (EogJob *job, float progress);
+	void    (*run)       (EogJob *job);
+};
+
+struct _EogJobLoad
+{
+	EogJob        parent;
+	EogImage     *image;
+	EogImageData  data;
+};
+
+struct _EogJobLoadClass
+{
+	EogJobClass parent_class;
+};
+
+struct _EogJobTransform
+{
+	EogJob        parent;
+	GList        *images;
+	EogTransform *trans;
+};
+
+struct _EogJobTransformClass
+{
+        EogJobClass parent_class;
+};
+
+typedef enum {
+	EOG_SAVE_RESPONSE_NONE,
+	EOG_SAVE_RESPONSE_RETRY,
+	EOG_SAVE_RESPONSE_SKIP,
+	EOG_SAVE_RESPONSE_OVERWRITE,
+	EOG_SAVE_RESPONSE_CANCEL,
+	EOG_SAVE_RESPONSE_LAST
+} EogJobSaveResponse;
+
+struct _EogJobSave
+{
+	EogJob    parent;
+	GList	 *images;
+	guint      current_pos;
+	EogImage *current_image;
+};
+
+struct _EogJobSaveClass
+{
+	EogJobClass parent_class;
+};
+
+struct _EogJobSaveAs
+{
+	EogJobSave       parent;
+	EogURIConverter *converter;
+	GFile           *file;
+};
+
+struct _EogJobSaveAsClass
+{
+	EogJobSaveClass parent;
+};
+
+struct _EogJobCopy
+{
+	EogJob parent;
+	GList *images;
+	guint current_pos;
+	gchar *dest;
+};
+
+struct _EogJobCopyClass
+{
+	EogJobClass parent_class;
+};
+
+/* base job class */
+GType           eog_job_get_type           (void) G_GNUC_CONST;
+void            eog_job_finished           (EogJob          *job);
+void            eog_job_run                (EogJob          *job);
+void            eog_job_set_progress       (EogJob          *job,
+					    float            progress);
+
+/* EogJobThumbnail */
+GType           eog_job_thumbnail_get_type (void) G_GNUC_CONST;
+EogJob         *eog_job_thumbnail_new      (EogImage     *image);
+
+/* EogJobLoad */
+GType           eog_job_load_get_type      (void) G_GNUC_CONST;
+EogJob 	       *eog_job_load_new 	   (EogImage        *image,
+					    EogImageData     data);
+
+/* EogJobTransform */
+GType 		eog_job_transform_get_type (void) G_GNUC_CONST;
+EogJob 	       *eog_job_transform_new      (GList           *images,
+					    EogTransform    *trans);
+
+/* EogJobSave */
+GType		eog_job_save_get_type      (void) G_GNUC_CONST;
+EogJob         *eog_job_save_new           (GList           *images);
+
+/* EogJobSaveAs */
+GType		eog_job_save_as_get_type   (void) G_GNUC_CONST;
+EogJob         *eog_job_save_as_new        (GList           *images,
+					    EogURIConverter *converter,
+					    GFile           *file);
+
+/*EogJobCopy */
+GType          eog_job_copy_get_type      (void) G_GNUC_CONST;
+EogJob        *eog_job_copy_new           (GList            *images,
+					   const gchar      *dest);
+
+G_END_DECLS
+
+#endif /* __EOG_JOBS_H__ */
diff --git a/src/eog-metadata-reader-jpg.c b/src/eog-metadata-reader-jpg.c
new file mode 100644
index 0000000..9598f67
--- /dev/null
+++ b/src/eog-metadata-reader-jpg.c
@@ -0,0 +1,671 @@
+/* Eye Of GNOME -- JPEG Metadata Reader
+ *
+ * Copyright (C) 2008 The Free Software Foundation
+ *
+ * Author: Felix Riemann <friemann svn gnome org>
+ *
+ * Based on the original EogMetadataReader code.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "eog-metadata-reader.h"
+#include "eog-metadata-reader-jpg.h"
+#include "eog-debug.h"
+
+typedef enum {
+	EMR_READ = 0,
+	EMR_READ_SIZE_HIGH_BYTE,
+	EMR_READ_SIZE_LOW_BYTE,
+	EMR_READ_MARKER,
+	EMR_SKIP_BYTES,
+	EMR_READ_APP1,
+	EMR_READ_EXIF,
+	EMR_READ_XMP,
+	EMR_READ_ICC,
+	EMR_READ_IPTC,
+	EMR_FINISHED
+} EogMetadataReaderState;
+
+typedef enum {
+	EJA_EXIF = 0,
+	EJA_XMP,
+	EJA_OTHER
+} EogJpegApp1Type;
+
+
+#define EOG_JPEG_MARKER_START   0xFF
+#define EOG_JPEG_MARKER_SOI     0xD8
+#define EOG_JPEG_MARKER_APP1	0xE1
+#define EOG_JPEG_MARKER_APP2	0xE2
+#define EOG_JPEG_MARKER_APP14	0xED
+
+#define IS_FINISHED(priv) (priv->state == EMR_READ  && \
+                           priv->exif_chunk != NULL && \
+                           priv->icc_chunk  != NULL && \
+                           priv->iptc_chunk != NULL && \
+                           priv->xmp_chunk  != NULL)
+
+struct _EogMetadataReaderJpgPrivate {
+	EogMetadataReaderState  state;
+
+	/* data fields */
+	guint    exif_len;
+	gpointer exif_chunk;
+
+	gpointer iptc_chunk;
+	guint	 iptc_len;
+
+	guint icc_len;
+	gpointer icc_chunk;
+
+	gpointer xmp_chunk;
+	guint xmp_len;
+
+	/* management fields */
+	int      size;
+	int      last_marker;
+	int      bytes_read;
+};
+
+#define EOG_METADATA_READER_JPG_GET_PRIVATE(object) \
+	(G_TYPE_INSTANCE_GET_PRIVATE ((object), EOG_TYPE_METADATA_READER_JPG, EogMetadataReaderJpgPrivate))
+
+static void
+eog_metadata_reader_jpg_init_emr_iface (gpointer g_iface, gpointer iface_data);
+
+
+G_DEFINE_TYPE_WITH_CODE (EogMetadataReaderJpg, eog_metadata_reader_jpg,
+			 G_TYPE_OBJECT,
+			 G_IMPLEMENT_INTERFACE (EOG_TYPE_METADATA_READER,
+			 		eog_metadata_reader_jpg_init_emr_iface))
+
+
+static void
+eog_metadata_reader_jpg_dispose (GObject *object)
+{
+	EogMetadataReaderJpg *emr = EOG_METADATA_READER_JPG (object);
+
+	if (emr->priv->exif_chunk != NULL) {
+		g_free (emr->priv->exif_chunk);
+		emr->priv->exif_chunk = NULL;
+	}
+
+	if (emr->priv->iptc_chunk != NULL) {
+		g_free (emr->priv->iptc_chunk);
+		emr->priv->iptc_chunk = NULL;
+	}
+
+	if (emr->priv->xmp_chunk != NULL) {
+		g_free (emr->priv->xmp_chunk);
+		emr->priv->xmp_chunk = NULL;
+	}
+
+	if (emr->priv->icc_chunk != NULL) {
+		g_free (emr->priv->icc_chunk);
+		emr->priv->icc_chunk = NULL;
+	}
+
+	G_OBJECT_CLASS (eog_metadata_reader_jpg_parent_class)->dispose (object);
+}
+
+static void
+eog_metadata_reader_jpg_init (EogMetadataReaderJpg *obj)
+{
+	EogMetadataReaderJpgPrivate *priv;
+
+	priv = obj->priv =  EOG_METADATA_READER_JPG_GET_PRIVATE (obj);
+	priv->exif_chunk = NULL;
+	priv->exif_len = 0;
+	priv->iptc_chunk = NULL;
+	priv->iptc_len = 0;
+	priv->icc_chunk = NULL;
+	priv->icc_len = 0;
+}
+
+static void
+eog_metadata_reader_jpg_class_init (EogMetadataReaderJpgClass *klass)
+{
+	GObjectClass *object_class = (GObjectClass*) klass;
+
+	object_class->dispose = eog_metadata_reader_jpg_dispose;
+
+	g_type_class_add_private (klass, sizeof (EogMetadataReaderJpgPrivate));
+}
+
+static gboolean
+eog_metadata_reader_jpg_finished (EogMetadataReaderJpg *emr)
+{
+	g_return_val_if_fail (EOG_IS_METADATA_READER_JPG (emr), TRUE);
+
+	return (emr->priv->state == EMR_FINISHED);
+}
+
+
+static EogJpegApp1Type
+eog_metadata_identify_app1 (gchar *buf, guint len)
+{
+ 	if (len < 5) {
+ 		return EJA_OTHER;
+ 	}
+
+ 	if (len < 29) {
+ 		return (strncmp ("Exif", buf, 5) == 0 ? EJA_EXIF : EJA_OTHER);
+ 	}
+
+ 	if (strncmp ("Exif", buf, 5) == 0) {
+ 		return EJA_EXIF;
+ 	} else if (strncmp ("http://ns.adobe.com/xap/1.0/";, buf, 29) == 0) {
+ 		return EJA_XMP;
+ 	}
+
+ 	return EJA_OTHER;
+}
+
+static void
+eog_metadata_reader_get_next_block (EogMetadataReaderJpgPrivate* priv,
+				    guchar *chunk,
+				    int* i,
+				    const guchar *buf,
+				    int len,
+				    EogMetadataReaderState state)
+{
+	if (*i + priv->size < len) {
+		/* read data in one block */
+		memcpy ((guchar*) (chunk) + priv->bytes_read, &buf[*i], priv->size);
+		priv->state = EMR_READ;
+		*i = *i + priv->size - 1; /* the for-loop consumes the other byte */
+	} else {
+		int chunk_len = len - *i;
+		memcpy ((guchar*) (chunk) + priv->bytes_read, &buf[*i], chunk_len);
+		priv->bytes_read += chunk_len; /* bytes already read */
+		priv->size = (*i + priv->size) - len; /* remaining data to read */
+		*i = len - 1;
+		priv->state = state;
+	}
+}
+
+static void
+eog_metadata_reader_jpg_consume (EogMetadataReaderJpg *emr, const guchar *buf, guint len)
+{
+	EogMetadataReaderJpgPrivate *priv;
+ 	EogJpegApp1Type app1_type;
+	int i;
+	EogMetadataReaderState next_state = EMR_READ;
+	guchar *chunk = NULL;
+
+	g_return_if_fail (EOG_IS_METADATA_READER_JPG (emr));
+
+	priv = emr->priv;
+
+	if (priv->state == EMR_FINISHED) return;
+
+	for (i = 0; (i < len) && (priv->state != EMR_FINISHED); i++) {
+
+		switch (priv->state) {
+		case EMR_READ:
+			if (buf[i] == EOG_JPEG_MARKER_START) {
+				priv->state = EMR_READ_MARKER;
+			}
+			else {
+				priv->state = EMR_FINISHED;
+			}
+			break;
+
+		case EMR_READ_MARKER:
+			if ((buf [i] & 0xF0) == 0xE0 || buf[i] == 0xFE) {
+			/* we are reading some sort of APPxx or COM marker */
+				/* these are always followed by 2 bytes of size information */
+				priv->last_marker = buf [i];
+				priv->size = 0;
+				priv->state = EMR_READ_SIZE_HIGH_BYTE;
+
+				eog_debug_message (DEBUG_IMAGE_DATA, "APPx or COM Marker Found: %x", priv->last_marker);
+			}
+			else {
+				/* otherwise simply consume the byte */
+				priv->state = EMR_READ;
+			}
+			break;
+
+		case EMR_READ_SIZE_HIGH_BYTE:
+			priv->size = (buf [i] & 0xff) << 8;
+			priv->state = EMR_READ_SIZE_LOW_BYTE;
+			break;
+
+		case EMR_READ_SIZE_LOW_BYTE:
+			priv->size |= (buf [i] & 0xff);
+
+			if (priv->size > 2)  /* ignore the two size-bytes */
+				priv->size -= 2;
+
+			if (priv->size == 0) {
+				priv->state = EMR_READ;
+			} else if (priv->last_marker == EOG_JPEG_MARKER_APP1 &&
+				   ((priv->exif_chunk == NULL) || (priv->xmp_chunk == NULL)))
+			{
+				priv->state = EMR_READ_APP1;
+			} else if (priv->last_marker == EOG_JPEG_MARKER_APP2 &&
+				   priv->icc_chunk == NULL && priv->size > 14)
+			{
+	 			/* Chunk has 14 bytes identification data */
+				priv->state = EMR_READ_ICC;
+			} else if (priv->last_marker == EOG_JPEG_MARKER_APP14 &&
+				priv->iptc_chunk == NULL)
+			{
+				priv->state = EMR_READ_IPTC;
+			} else {
+				priv->state = EMR_SKIP_BYTES;
+			}
+
+			priv->last_marker = 0;
+			break;
+
+		case EMR_SKIP_BYTES:
+			eog_debug_message (DEBUG_IMAGE_DATA, "Skip bytes: %i", priv->size);
+
+			if (i + priv->size < len) {
+				i = i + priv->size - 1; /* the for-loop consumes the other byte */
+				priv->size = 0;
+			}
+			else {
+				priv->size = (i + priv->size) - len;
+				i = len - 1;
+			}
+			if (priv->size == 0) { /* don't need to skip any more bytes */
+				priv->state = EMR_READ;
+			}
+			break;
+
+		case EMR_READ_APP1:
+			eog_debug_message (DEBUG_IMAGE_DATA, "Read APP1 data, Length: %i", priv->size);
+
+			app1_type = eog_metadata_identify_app1 ((gchar*) &buf[i], priv->size);
+
+			switch (app1_type) {
+			case EJA_EXIF:
+				if (priv->exif_chunk == NULL) {
+					priv->exif_chunk = g_new0 (guchar, priv->size);
+					priv->exif_len = priv->size;
+					priv->bytes_read = 0;
+					chunk = priv->exif_chunk;
+					next_state = EMR_READ_EXIF;
+				} else {
+					chunk = NULL;
+					priv->state = EMR_SKIP_BYTES;
+				}
+				break;
+			case EJA_XMP:
+				if (priv->xmp_chunk == NULL) {
+					priv->xmp_chunk = g_new0 (guchar, priv->size);
+					priv->xmp_len = priv->size;
+					priv->bytes_read = 0;
+					chunk = priv->xmp_chunk;
+					next_state = EMR_READ_XMP;
+				} else {
+					chunk = NULL;
+					priv->state = EMR_SKIP_BYTES;
+				}
+				break;
+			case EJA_OTHER:
+			default:
+				/* skip unknown data */
+				chunk = NULL;
+				priv->state = EMR_SKIP_BYTES;
+				break;
+			}
+
+			if (chunk) {
+				eog_metadata_reader_get_next_block (priv, chunk,
+								    &i, buf,
+								    len,
+								    next_state);
+			}
+
+			if (IS_FINISHED(priv))
+				priv->state = EMR_FINISHED;
+			break;
+
+		case EMR_READ_EXIF:
+			eog_debug_message (DEBUG_IMAGE_DATA, "Read continuation of EXIF data, length: %i", priv->size);
+			{
+ 				eog_metadata_reader_get_next_block (priv, priv->exif_chunk,
+ 								    &i, buf, len, EMR_READ_EXIF);
+			}
+			if (IS_FINISHED(priv))
+				priv->state = EMR_FINISHED;
+			break;
+
+		case EMR_READ_XMP:
+			eog_debug_message (DEBUG_IMAGE_DATA, "Read continuation of XMP data, length: %i", priv->size);
+			{
+				eog_metadata_reader_get_next_block (priv, priv->xmp_chunk,
+ 								    &i, buf, len, EMR_READ_XMP);
+			}
+			if (IS_FINISHED (priv))
+				priv->state = EMR_FINISHED;
+			break;
+
+		case EMR_READ_ICC:
+			eog_debug_message (DEBUG_IMAGE_DATA,
+					   "Read continuation of ICC data, "
+					   "length: %i", priv->size);
+
+			if (priv->icc_chunk == NULL) {
+				priv->icc_chunk = g_new0 (guchar, priv->size);
+				priv->icc_len = priv->size;
+				priv->bytes_read = 0;
+			}
+
+			eog_metadata_reader_get_next_block (priv,
+							    priv->icc_chunk,
+							    &i, buf, len,
+							    EMR_READ_ICC);
+
+			/* Test that the chunk actually contains ICC data. */
+			if (priv->state == EMR_READ && priv->icc_chunk) {
+			    	const char* icc_chunk = priv->icc_chunk;
+				gboolean valid = TRUE;
+
+				/* Chunk should begin with the
+				 * ICC_PROFILE\0 identifier */
+				valid &= strncmp (icc_chunk,
+						  "ICC_PROFILE\0",12) == 0;
+				/* Make sure this is the first and only
+				 * ICC chunk in the file as we don't
+				 * support merging chunks yet. */
+				valid &=  *(guint16*)(icc_chunk+12) == 0x101;
+
+				if (!valid) {
+					/* This no ICC data. Throw it away. */
+					eog_debug_message (DEBUG_IMAGE_DATA,
+					"Supposed ICC chunk didn't validate. "
+					"Ignoring.");
+					g_free (priv->icc_chunk);
+					priv->icc_chunk = NULL;
+					priv->icc_len = 0;
+				}
+			}
+
+			if (IS_FINISHED(priv))
+				priv->state = EMR_FINISHED;
+			break;
+
+		case EMR_READ_IPTC:
+			eog_debug_message (DEBUG_IMAGE_DATA,
+					   "Read continuation of IPTC data, "
+					   "length: %i", priv->size);
+
+			if (priv->iptc_chunk == NULL) {
+				priv->iptc_chunk = g_new0 (guchar, priv->size);
+				priv->iptc_len = priv->size;
+				priv->bytes_read = 0;
+			}
+
+			eog_metadata_reader_get_next_block (priv,
+							    priv->iptc_chunk,
+							    &i, buf, len,
+							    EMR_READ_IPTC);
+
+			if (IS_FINISHED(priv))
+				priv->state = EMR_FINISHED;
+			break;
+
+		default:
+			g_assert_not_reached ();
+		}
+	}
+}
+
+/* Returns the raw exif data. NOTE: The caller of this function becomes
+ * the new owner of this piece of memory and is responsible for freeing it!
+ */
+static void
+eog_metadata_reader_jpg_get_exif_chunk (EogMetadataReaderJpg *emr, guchar **data, guint *len)
+{
+	EogMetadataReaderJpgPrivate *priv;
+
+	g_return_if_fail (EOG_IS_METADATA_READER (emr));
+	priv = emr->priv;
+
+	*data = (guchar*) priv->exif_chunk;
+	*len = priv->exif_len;
+
+	priv->exif_chunk = NULL;
+	priv->exif_len = 0;
+}
+
+#ifdef HAVE_EXIF
+static gpointer
+eog_metadata_reader_jpg_get_exif_data (EogMetadataReaderJpg *emr)
+{
+	EogMetadataReaderJpgPrivate *priv;
+	ExifData *data = NULL;
+
+	g_return_val_if_fail (EOG_IS_METADATA_READER (emr), NULL);
+	priv = emr->priv;
+
+	if (priv->exif_chunk != NULL) {
+		data = exif_data_new_from_data (priv->exif_chunk, priv->exif_len);
+	}
+
+	return data;
+}
+#endif
+
+
+#ifdef HAVE_EXEMPI
+
+/* skip the signature */
+#define EOG_XMP_OFFSET (29)
+
+static gpointer
+eog_metadata_reader_jpg_get_xmp_data (EogMetadataReaderJpg *emr )
+{
+	EogMetadataReaderJpgPrivate *priv;
+	XmpPtr xmp = NULL;
+
+	g_return_val_if_fail (EOG_IS_METADATA_READER (emr), NULL);
+
+	priv = emr->priv;
+
+	if (priv->xmp_chunk != NULL) {
+		xmp = xmp_new (priv->xmp_chunk+EOG_XMP_OFFSET,
+			       priv->xmp_len-EOG_XMP_OFFSET);
+	}
+
+	return (gpointer)xmp;
+}
+#endif
+
+/*
+ * FIXME: very broken, assumes the profile fits in a single chunk.  Change to
+ * parse the sections and construct a single memory chunk, or maybe even parse
+ * the profile.
+ */
+#ifdef HAVE_LCMS
+static gpointer
+eog_metadata_reader_jpg_get_icc_profile (EogMetadataReaderJpg *emr)
+{
+	EogMetadataReaderJpgPrivate *priv;
+	cmsHPROFILE profile = NULL;
+
+	g_return_val_if_fail (EOG_IS_METADATA_READER (emr), NULL);
+
+	priv = emr->priv;
+
+	if (priv->icc_chunk) {
+		profile = cmsOpenProfileFromMem(priv->icc_chunk + 14, priv->icc_len - 14);
+
+		if (profile) {
+			eog_debug_message (DEBUG_LCMS, "JPEG has ICC profile");
+		} else {
+			eog_debug_message (DEBUG_LCMS, "JPEG has invalid ICC profile");
+		}
+	}
+
+#ifdef HAVE_EXIF
+	if (!profile && priv->exif_chunk != NULL) {
+		ExifEntry *entry;
+		ExifByteOrder o;
+		gint color_space;
+		ExifData *exif = eog_metadata_reader_jpg_get_exif_data (emr);
+
+		if (!exif) return NULL;
+
+		o = exif_data_get_byte_order (exif);
+
+		entry = exif_data_get_entry (exif, EXIF_TAG_COLOR_SPACE);
+
+		if (entry == NULL) {
+			exif_data_unref (exif);
+			return NULL;
+		}
+
+		color_space = exif_get_short (entry->data, o);
+
+		switch (color_space) {
+		case 1:
+			eog_debug_message (DEBUG_LCMS, "JPEG is sRGB");
+
+			profile = cmsCreate_sRGBProfile ();
+
+			break;
+		case 2:
+			eog_debug_message (DEBUG_LCMS, "JPEG is Adobe RGB (Disabled)");
+
+			/* TODO: create Adobe RGB profile */
+			//profile = cmsCreate_Adobe1998Profile ();
+
+			break;
+		case 0xFFFF:
+		  	{
+			cmsCIExyY whitepoint;
+			cmsCIExyYTRIPLE primaries;
+			cmsToneCurve *gamma[3];
+			double gammaValue;
+			ExifRational r;
+
+			const int offset = exif_format_get_size (EXIF_FORMAT_RATIONAL);
+
+			entry = exif_data_get_entry (exif, EXIF_TAG_WHITE_POINT);
+
+			if (entry && entry->components == 2) {
+				r = exif_get_rational (entry->data, o);
+				whitepoint.x = (double) r.numerator / r.denominator;
+
+				r = exif_get_rational (entry->data + offset, o);
+				whitepoint.y = (double) r.numerator / r.denominator;
+				whitepoint.Y = 1.0;
+			} else {
+				eog_debug_message (DEBUG_LCMS, "No whitepoint found");
+				break;
+			}
+
+			entry = exif_data_get_entry (exif, EXIF_TAG_PRIMARY_CHROMATICITIES);
+
+			if (entry && entry->components == 6) {
+				r = exif_get_rational (entry->data + 0 * offset, o);
+				primaries.Red.x = (double) r.numerator / r.denominator;
+
+				r = exif_get_rational (entry->data + 1 * offset, o);
+				primaries.Red.y = (double) r.numerator / r.denominator;
+
+				r = exif_get_rational (entry->data + 2 * offset, o);
+				primaries.Green.x = (double) r.numerator / r.denominator;
+
+				r = exif_get_rational (entry->data + 3 * offset, o);
+				primaries.Green.y = (double) r.numerator / r.denominator;
+
+				r = exif_get_rational (entry->data + 4 * offset, o);
+				primaries.Blue.x = (double) r.numerator / r.denominator;
+
+				r = exif_get_rational (entry->data + 5 * offset, o);
+				primaries.Blue.y = (double) r.numerator / r.denominator;
+
+				primaries.Red.Y = primaries.Green.Y = primaries.Blue.Y = 1.0;
+			} else {
+				eog_debug_message (DEBUG_LCMS, "No primary chromaticities found");
+				break;
+			}
+
+			entry = exif_data_get_entry (exif, EXIF_TAG_GAMMA);
+
+			if (entry) {
+				r = exif_get_rational (entry->data, o);
+				gammaValue = (double) r.numerator / r.denominator;
+			} else {
+				eog_debug_message (DEBUG_LCMS, "No gamma found");
+				gammaValue = 2.2;
+			}
+
+			gamma[0] = gamma[1] = gamma[2] = cmsBuildGamma (NULL, gammaValue);
+
+			profile = cmsCreateRGBProfile (&whitepoint, &primaries, gamma);
+
+			cmsFreeToneCurve(gamma[0]);
+
+			eog_debug_message (DEBUG_LCMS, "JPEG is calibrated");
+
+			break;
+			}
+		}
+
+		exif_data_unref (exif);
+	}
+#endif
+	return profile;
+}
+#endif
+
+static void
+eog_metadata_reader_jpg_init_emr_iface (gpointer g_iface, gpointer iface_data)
+{
+	EogMetadataReaderInterface *iface;
+
+	iface = (EogMetadataReaderInterface*)g_iface;
+
+	iface->consume =
+		(void (*) (EogMetadataReader *self, const guchar *buf, guint len))
+			eog_metadata_reader_jpg_consume;
+	iface->finished =
+		(gboolean (*) (EogMetadataReader *self))
+			eog_metadata_reader_jpg_finished;
+	iface->get_raw_exif =
+		(void (*) (EogMetadataReader *self, guchar **data, guint *len))
+			eog_metadata_reader_jpg_get_exif_chunk;
+#ifdef HAVE_EXIF
+	iface->get_exif_data =
+		(gpointer (*) (EogMetadataReader *self))
+			eog_metadata_reader_jpg_get_exif_data;
+#endif
+#ifdef HAVE_LCMS
+	iface->get_icc_profile =
+		(gpointer (*) (EogMetadataReader *self))
+			eog_metadata_reader_jpg_get_icc_profile;
+#endif
+#ifdef HAVE_EXEMPI
+	iface->get_xmp_ptr =
+		(gpointer (*) (EogMetadataReader *self))
+			eog_metadata_reader_jpg_get_xmp_data;
+#endif
+}
+
diff --git a/src/eog-metadata-reader-jpg.h b/src/eog-metadata-reader-jpg.h
new file mode 100644
index 0000000..e772569
--- /dev/null
+++ b/src/eog-metadata-reader-jpg.h
@@ -0,0 +1,55 @@
+/* Eye Of GNOME -- JPEG Metadata Reader
+ *
+ * Copyright (C) 2008 The Free Software Foundation
+ *
+ * Author: Felix Riemann <friemann svn gnome org>
+ *
+ * Based on the original EogMetadataReader code.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _EOG_METADATA_READER_JPG_H_
+#define _EOG_METADATA_READER_JPG_H_
+
+G_BEGIN_DECLS
+
+#define EOG_TYPE_METADATA_READER_JPG		(eog_metadata_reader_jpg_get_type ())
+#define EOG_METADATA_READER_JPG(o)         	(G_TYPE_CHECK_INSTANCE_CAST ((o),EOG_TYPE_METADATA_READER_JPG, EogMetadataReaderJpg))
+#define EOG_METADATA_READER_JPG_CLASS(k)   	(G_TYPE_CHECK_CLASS_CAST((k), EOG_TYPE_METADATA_READER_JPG, EogMetadataReaderJpgClass))
+#define EOG_IS_METADATA_READER_JPG(o)      	(G_TYPE_CHECK_INSTANCE_TYPE ((o), EOG_TYPE_METADATA_READER_JPG))
+#define EOG_IS_METADATA_READER_JPG_CLASS(k)   	(G_TYPE_CHECK_CLASS_TYPE ((k), EOG_TYPE_METADATA_READER_JPG))
+#define EOG_METADATA_READER_JPG_GET_CLASS(o)  	(G_TYPE_INSTANCE_GET_CLASS ((o), EOG_TYPE_METADATA_READER_JPG, EogMetadataReaderJpgClass))
+
+typedef struct _EogMetadataReaderJpg EogMetadataReaderJpg;
+typedef struct _EogMetadataReaderJpgClass EogMetadataReaderJpgClass;
+typedef struct _EogMetadataReaderJpgPrivate EogMetadataReaderJpgPrivate;
+
+struct _EogMetadataReaderJpg {
+	GObject parent;
+
+	EogMetadataReaderJpgPrivate *priv;
+};
+
+struct _EogMetadataReaderJpgClass {
+	GObjectClass parent_klass;
+};
+
+G_GNUC_INTERNAL
+GType		      eog_metadata_reader_jpg_get_type	(void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* _EOG_METADATA_READER_JPG_H_ */
diff --git a/src/eog-metadata-reader-png.c b/src/eog-metadata-reader-png.c
new file mode 100644
index 0000000..ec5994d
--- /dev/null
+++ b/src/eog-metadata-reader-png.c
@@ -0,0 +1,646 @@
+/* Eye Of GNOME -- PNG Metadata Reader
+ *
+ * Copyright (C) 2008 The Free Software Foundation
+ *
+ * Author: Felix Riemann <friemann svn gnome org>
+ *
+ * Based on the old EogMetadataReader code.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <zlib.h>
+
+#include "eog-metadata-reader.h"
+#include "eog-metadata-reader-png.h"
+#include "eog-debug.h"
+
+typedef enum {
+	EMR_READ_MAGIC,
+	EMR_READ_SIZE_HIGH_HIGH_BYTE,
+	EMR_READ_SIZE_HIGH_LOW_BYTE,
+	EMR_READ_SIZE_LOW_HIGH_BYTE,
+	EMR_READ_SIZE_LOW_LOW_BYTE,
+	EMR_READ_CHUNK_NAME,
+	EMR_SKIP_BYTES,
+	EMR_CHECK_CRC,
+	EMR_SKIP_CRC,
+	EMR_READ_XMP_ITXT,
+	EMR_READ_ICCP,
+	EMR_READ_SRGB,
+	EMR_READ_CHRM,
+	EMR_READ_GAMA,
+	EMR_FINISHED
+} EogMetadataReaderPngState;
+
+#if 0
+#define IS_FINISHED(priv) (priv->icc_chunk  != NULL && \
+                           priv->xmp_chunk  != NULL)
+#endif
+
+struct _EogMetadataReaderPngPrivate {
+	EogMetadataReaderPngState  state;
+
+	/* data fields */
+	guint32  icc_len;
+	gpointer icc_chunk;
+
+	gpointer xmp_chunk;
+	guint32  xmp_len;
+
+	guint32	 sRGB_len;
+	gpointer sRGB_chunk;
+
+	gpointer cHRM_chunk;
+	guint32	 cHRM_len;
+
+	guint32	 gAMA_len;
+	gpointer gAMA_chunk;
+
+	/* management fields */
+	gsize      size;
+	gsize      bytes_read;
+	guint	   sub_step;
+	guchar	   chunk_name[4];
+	gpointer   *crc_chunk;
+	guint32	   *crc_len;
+	guint32    target_crc;
+	gboolean   hasIHDR;
+};
+
+#define EOG_METADATA_READER_PNG_GET_PRIVATE(object) \
+	(G_TYPE_INSTANCE_GET_PRIVATE ((object), EOG_TYPE_METADATA_READER_PNG, EogMetadataReaderPngPrivate))
+
+static void
+eog_metadata_reader_png_init_emr_iface (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE (EogMetadataReaderPng, eog_metadata_reader_png,
+			 G_TYPE_OBJECT,
+			 G_IMPLEMENT_INTERFACE (EOG_TYPE_METADATA_READER,
+			 		eog_metadata_reader_png_init_emr_iface))
+
+static void
+eog_metadata_reader_png_dispose (GObject *object)
+{
+	EogMetadataReaderPng *emr = EOG_METADATA_READER_PNG (object);
+	EogMetadataReaderPngPrivate *priv = emr->priv;
+
+	g_free (priv->xmp_chunk);
+	priv->xmp_chunk = NULL;
+
+	g_free (priv->icc_chunk);
+	priv->icc_chunk = NULL;
+
+	g_free (priv->sRGB_chunk);
+	priv->sRGB_chunk = NULL;
+
+	g_free (priv->cHRM_chunk);
+	priv->cHRM_chunk = NULL;
+
+	g_free (priv->gAMA_chunk);
+	priv->gAMA_chunk = NULL;
+
+	G_OBJECT_CLASS (eog_metadata_reader_png_parent_class)->dispose (object);
+}
+
+static void
+eog_metadata_reader_png_init (EogMetadataReaderPng *obj)
+{
+	EogMetadataReaderPngPrivate *priv;
+
+	priv = obj->priv =  EOG_METADATA_READER_PNG_GET_PRIVATE (obj);
+	priv->icc_chunk = NULL;
+	priv->icc_len = 0;
+	priv->xmp_chunk = NULL;
+	priv->xmp_len = 0;
+	priv->sRGB_chunk = NULL;
+	priv->sRGB_len = 0;
+	priv->cHRM_chunk = NULL;
+	priv->cHRM_len = 0;
+	priv->gAMA_chunk = NULL;
+	priv->gAMA_len = 0;
+
+	priv->sub_step = 0;
+	priv->state = EMR_READ_MAGIC;
+	priv->hasIHDR = FALSE;
+}
+
+static void
+eog_metadata_reader_png_class_init (EogMetadataReaderPngClass *klass)
+{
+	GObjectClass *object_class = (GObjectClass*) klass;
+
+	object_class->dispose = eog_metadata_reader_png_dispose;
+
+	g_type_class_add_private (klass, sizeof (EogMetadataReaderPngPrivate));
+}
+
+static gboolean
+eog_metadata_reader_png_finished (EogMetadataReaderPng *emr)
+{
+	g_return_val_if_fail (EOG_IS_METADATA_READER_PNG (emr), TRUE);
+
+	return (emr->priv->state == EMR_FINISHED);
+}
+
+
+static void
+eog_metadata_reader_png_get_next_block (EogMetadataReaderPngPrivate* priv,
+				    	guchar *chunk,
+					int* i,
+					const guchar *buf,
+					int len,
+					EogMetadataReaderPngState state)
+{
+	if (*i + priv->size < len) {
+		/* read data in one block */
+		memcpy ((guchar*) (chunk) + priv->bytes_read, &buf[*i], priv->size);
+		priv->state = EMR_CHECK_CRC;
+		*i = *i + priv->size - 1; /* the for-loop consumes the other byte */
+		priv->size = 0;
+	} else {
+		int chunk_len = len - *i;
+		memcpy ((guchar*) (chunk) + priv->bytes_read, &buf[*i], chunk_len);
+		priv->bytes_read += chunk_len; /* bytes already read */
+		priv->size = (*i + priv->size) - len; /* remaining data to read */
+		*i = len - 1;
+		priv->state = state;
+	}
+}
+
+static void
+eog_metadata_reader_png_consume (EogMetadataReaderPng *emr, const guchar *buf, guint len)
+{
+	EogMetadataReaderPngPrivate *priv;
+ 	int i;
+	guint32 chunk_crc;
+	static const gchar PNGMAGIC[8] = "\x89PNG\x0D\x0A\x1a\x0A";
+
+	g_return_if_fail (EOG_IS_METADATA_READER_PNG (emr));
+
+	priv = emr->priv;
+
+	if (priv->state == EMR_FINISHED) return;
+
+	for (i = 0; (i < len) && (priv->state != EMR_FINISHED); i++) {
+
+		switch (priv->state) {
+		case EMR_READ_MAGIC:
+			/* Check PNG magic string */
+			if (priv->sub_step < 8 &&
+			    (gchar)buf[i] == PNGMAGIC[priv->sub_step]) {
+			    	if (priv->sub_step == 7)
+					priv->state = EMR_READ_SIZE_HIGH_HIGH_BYTE;
+				priv->sub_step++;
+			} else {
+				priv->state = EMR_FINISHED;
+			}
+			break;
+		case EMR_READ_SIZE_HIGH_HIGH_BYTE:
+			/* Read the high byte of the size's high word */
+			priv->size |= (buf[i] & 0xFF) << 24;
+			priv->state = EMR_READ_SIZE_HIGH_LOW_BYTE;
+			break;
+		case EMR_READ_SIZE_HIGH_LOW_BYTE:
+			/* Read the low byte of the size's high word */
+			priv->size |= (buf[i] & 0xFF) << 16;
+			priv->state = EMR_READ_SIZE_LOW_HIGH_BYTE;
+			break;
+		case EMR_READ_SIZE_LOW_HIGH_BYTE:
+			/* Read the high byte of the size's low word */
+			priv->size |= (buf [i] & 0xff) << 8;
+			priv->state = EMR_READ_SIZE_LOW_LOW_BYTE;
+			break;
+		case EMR_READ_SIZE_LOW_LOW_BYTE:
+			/* Read the high byte of the size's low word */
+			priv->size |= (buf [i] & 0xff);
+			/* The maximum chunk length is 2^31-1 */
+			if (G_LIKELY (priv->size <= (guint32) 0x7fffffff)) {
+				priv->state = EMR_READ_CHUNK_NAME;
+				/* Make sure sub_step is 0 before next step */
+				priv->sub_step = 0;
+			} else {
+				priv->state = EMR_FINISHED;
+				eog_debug_message (DEBUG_IMAGE_DATA,
+						   "chunk size larger than "
+						   "2^31-1; stopping parser");
+			}
+
+			break;
+		case EMR_READ_CHUNK_NAME:
+			/* Read the 4-byte chunk name */
+			if (priv->sub_step > 3)
+				g_assert_not_reached ();
+
+			priv->chunk_name[priv->sub_step] = buf[i];
+
+			if (priv->sub_step++ != 3)
+				break;
+
+			if (G_UNLIKELY (!priv->hasIHDR)) {
+				/* IHDR should be the first chunk in a PNG */
+				if (priv->size == 13
+				    && memcmp (priv->chunk_name, "IHDR", 4) == 0){
+					priv->hasIHDR = TRUE;
+				} else {
+					/* Stop parsing if it is not */
+					priv->state = EMR_FINISHED;
+				}
+			}
+
+			/* Try to identify the chunk by its name.
+			 * Already do some sanity checks where possible */
+			if (memcmp (priv->chunk_name, "iTXt", 4) == 0 &&
+			    priv->size > (22 + 54) && priv->xmp_chunk == NULL) {
+				priv->state = EMR_READ_XMP_ITXT;
+			} else if (memcmp (priv->chunk_name, "iCCP", 4) == 0 &&
+				   priv->icc_chunk == NULL) {
+				priv->state = EMR_READ_ICCP;
+			} else if (memcmp (priv->chunk_name, "sRGB", 4) == 0 &&
+				   priv->sRGB_chunk == NULL && priv->size == 1) {
+				priv->state = EMR_READ_SRGB;
+			} else if (memcmp (priv->chunk_name, "cHRM", 4) == 0 &&
+				   priv->cHRM_chunk == NULL && priv->size == 32) {
+				priv->state = EMR_READ_CHRM;
+			} else if (memcmp (priv->chunk_name, "gAMA", 4) == 0 &&
+				   priv->gAMA_chunk == NULL && priv->size == 4) {
+				priv->state = EMR_READ_GAMA;
+			} else if (memcmp (priv->chunk_name, "IEND", 4) == 0) {
+				priv->state = EMR_FINISHED;
+			} else {
+				/* Skip chunk + 4-byte CRC32 value */
+				priv->size += 4;
+				priv->state = EMR_SKIP_BYTES;
+			}
+			priv->sub_step = 0;
+			break;
+		case EMR_SKIP_CRC:
+			/* Skip the 4-byte CRC32 value following every chunk */
+			priv->size = 4;
+		case EMR_SKIP_BYTES:
+		/* Skip chunk and start reading the size of the next one */
+			eog_debug_message (DEBUG_IMAGE_DATA,
+					   "Skip bytes: %" G_GSIZE_FORMAT,
+					   priv->size);
+
+			if (i + priv->size < len) {
+				i = i + priv->size - 1; /* the for-loop consumes the other byte */
+				priv->size = 0;
+				priv->state = EMR_READ_SIZE_HIGH_HIGH_BYTE;
+			}
+			else {
+				priv->size = (i + priv->size) - len;
+				i = len - 1;
+			}
+			break;
+		case EMR_CHECK_CRC:
+			/* Read the chunks CRC32 value from the file,... */
+			if (priv->sub_step == 0)
+				priv->target_crc = 0;
+
+			priv->target_crc |= buf[i] << ((3 - priv->sub_step) * 8);
+
+			if (priv->sub_step++ != 3)
+				break;
+
+			/* ...generate the chunks CRC32,... */
+			chunk_crc = crc32 (crc32 (0L, Z_NULL, 0), priv->chunk_name, 4);
+			chunk_crc = crc32 (chunk_crc, *priv->crc_chunk, *priv->crc_len);
+
+			eog_debug_message (DEBUG_IMAGE_DATA, "Checking CRC: Chunk: 0x%X - Target: 0x%X", chunk_crc, priv->target_crc);
+
+			/* ...and check if they match. If they don't throw
+			 * the chunk away and stop parsing. */
+			if (priv->target_crc == chunk_crc) {
+				priv->state = EMR_READ_SIZE_HIGH_HIGH_BYTE;
+			} else {
+				g_free (*priv->crc_chunk);
+				*priv->crc_chunk = NULL;
+				*priv->crc_len = 0;
+				/* Stop parsing for security reasons */
+				priv->state = EMR_FINISHED;
+			}
+			priv->sub_step = 0;
+			break;
+		case EMR_READ_XMP_ITXT:
+			/* Extract an iTXt chunk possibly containing
+			 * an XMP packet */
+			eog_debug_message (DEBUG_IMAGE_DATA,
+					   "Read XMP Chunk - size: %"
+					   G_GSIZE_FORMAT, priv->size);
+
+			if (priv->xmp_chunk == NULL) {
+				priv->xmp_chunk = g_new0 (guchar, priv->size);
+				priv->xmp_len = priv->size;
+				priv->crc_len = &priv->xmp_len;
+				priv->bytes_read = 0;
+				priv->crc_chunk = &priv->xmp_chunk;
+			}
+			eog_metadata_reader_png_get_next_block (priv,
+							    priv->xmp_chunk,
+							    &i, buf, len,
+							    EMR_READ_XMP_ITXT);
+
+			if (priv->state == EMR_CHECK_CRC) {
+				/* Check if it is actually an XMP chunk.
+				 * Throw it away if not.
+				 * The check has 4 extra \0's to check
+				 * if the chunk is configured correctly. */
+				if (memcmp (priv->xmp_chunk, "XML:com.adobe.xmp\0\0\0\0\0", 22) != 0) {
+					priv->state = EMR_SKIP_CRC;
+					g_free (priv->xmp_chunk);
+					priv->xmp_chunk = NULL;
+					priv->xmp_len = 0;
+				}
+			}
+			break;
+		case EMR_READ_ICCP:
+			/* Extract an iCCP chunk containing a
+			 * deflated ICC profile. */
+			eog_debug_message (DEBUG_IMAGE_DATA,
+					   "Read ICC Chunk - size: %"
+					   G_GSIZE_FORMAT, priv->size);
+
+			if (priv->icc_chunk == NULL) {
+				priv->icc_chunk = g_new0 (guchar, priv->size);
+				priv->icc_len = priv->size;
+				priv->crc_len = &priv->icc_len;
+				priv->bytes_read = 0;
+				priv->crc_chunk = &priv->icc_chunk;
+			}
+
+			eog_metadata_reader_png_get_next_block (priv,
+							    priv->icc_chunk,
+							    &i, buf, len,
+							    EMR_READ_ICCP);
+			break;
+		case EMR_READ_SRGB:
+			/* Extract the sRGB chunk. Marks the image data as
+			 * being in sRGB colorspace. */
+			eog_debug_message (DEBUG_IMAGE_DATA,
+					   "Read sRGB Chunk - value: %u", *(buf+i));
+
+			if (priv->sRGB_chunk == NULL) {
+				priv->sRGB_chunk = g_new0 (guchar, priv->size);
+				priv->sRGB_len = priv->size;
+				priv->crc_len = &priv->sRGB_len;
+				priv->bytes_read = 0;
+				priv->crc_chunk = &priv->sRGB_chunk;
+			}
+
+			eog_metadata_reader_png_get_next_block (priv,
+							    priv->sRGB_chunk,
+							    &i, buf, len,
+							    EMR_READ_SRGB);
+			break;
+		case EMR_READ_CHRM:
+			/* Extract the cHRM chunk. Contains the coordinates of
+			 * the image's whitepoint and primary chromacities. */
+			eog_debug_message (DEBUG_IMAGE_DATA,
+					   "Read cHRM Chunk - size: %"
+					   G_GSIZE_FORMAT, priv->size);
+
+			if (priv->cHRM_chunk == NULL) {
+				priv->cHRM_chunk = g_new0 (guchar, priv->size);
+				priv->cHRM_len = priv->size;
+				priv->crc_len = &priv->cHRM_len;
+				priv->bytes_read = 0;
+				priv->crc_chunk = &priv->cHRM_chunk;
+			}
+
+			eog_metadata_reader_png_get_next_block (priv,
+							    priv->cHRM_chunk,
+							    &i, buf, len,
+							    EMR_READ_ICCP);
+			break;
+		case EMR_READ_GAMA:
+			/* Extract the gAMA chunk containing the
+			 * image's gamma value */
+			eog_debug_message (DEBUG_IMAGE_DATA,
+					   "Read gAMA-Chunk - size: %"
+					   G_GSIZE_FORMAT, priv->size);
+
+			if (priv->gAMA_chunk == NULL) {
+				priv->gAMA_chunk = g_new0 (guchar, priv->size);
+				priv->gAMA_len = priv->size;
+				priv->crc_len = &priv->gAMA_len;
+				priv->bytes_read = 0;
+				priv->crc_chunk = &priv->gAMA_chunk;
+			}
+
+			eog_metadata_reader_png_get_next_block (priv,
+							    priv->gAMA_chunk,
+							    &i, buf, len,
+							    EMR_READ_ICCP);
+			break;
+		default:
+			g_assert_not_reached ();
+		}
+	}
+}
+
+#ifdef HAVE_EXEMPI
+
+/* skip the chunk ID */
+#define EOG_XMP_OFFSET (22)
+
+static gpointer
+eog_metadata_reader_png_get_xmp_data (EogMetadataReaderPng *emr )
+{
+	EogMetadataReaderPngPrivate *priv;
+	XmpPtr xmp = NULL;
+
+	g_return_val_if_fail (EOG_IS_METADATA_READER_PNG (emr), NULL);
+
+	priv = emr->priv;
+
+	if (priv->xmp_chunk != NULL) {
+		xmp = xmp_new (priv->xmp_chunk+EOG_XMP_OFFSET,
+			       priv->xmp_len-EOG_XMP_OFFSET);
+	}
+
+	return (gpointer) xmp;
+}
+#endif
+
+#ifdef HAVE_LCMS
+
+#define EXTRACT_DOUBLE_UINT_BLOCK_OFFSET(chunk,offset,divider) \
+		(double)(GUINT32_FROM_BE(*((guint32*)((chunk)+((offset)*4))))/(double)(divider))
+
+/* This is the amount of memory the inflate output buffer gets increased by
+ * while decompressing the ICC profile */
+#define EOG_ICC_INFLATE_BUFFER_STEP 1024
+
+/* I haven't seen ICC profiles larger than 1MB yet.
+ * A maximum output buffer of 5MB should be enough. */
+#define EOG_ICC_INFLATE_BUFFER_LIMIT (1024*1024*5)
+
+static gpointer
+eog_metadata_reader_png_get_icc_profile (EogMetadataReaderPng *emr)
+{
+	EogMetadataReaderPngPrivate *priv;
+	cmsHPROFILE profile = NULL;
+
+	g_return_val_if_fail (EOG_IS_METADATA_READER_PNG (emr), NULL);
+
+	priv = emr->priv;
+
+	if (priv->icc_chunk) {
+		gpointer outbuf;
+		gsize offset = 0;
+		z_stream zstr;
+		int z_ret;
+
+		/* Use default allocation functions */
+		zstr.zalloc = Z_NULL;
+		zstr.zfree = Z_NULL;
+		zstr.opaque = Z_NULL;
+
+		/* Skip the name of the ICC profile */
+		while (*((guchar*)priv->icc_chunk+offset) != '\0')
+			offset++;
+		/* Ensure the compression method (deflate) */
+		if (*((guchar*)priv->icc_chunk+(++offset)) != '\0')
+			return NULL;
+		++offset; //offset now points to the start of the deflated data
+
+		/* Prepare the zlib data structure for decompression */
+		zstr.next_in = priv->icc_chunk + offset;
+		zstr.avail_in = priv->icc_len - offset;
+		if (inflateInit (&zstr) != Z_OK) {
+			return NULL;
+		}
+
+		/* Prepare output buffer and make zlib aware of it */
+		outbuf = g_malloc (EOG_ICC_INFLATE_BUFFER_STEP);
+		zstr.next_out = outbuf;
+		zstr.avail_out = EOG_ICC_INFLATE_BUFFER_STEP;
+
+		do {
+			if (zstr.avail_out == 0) {
+				/* The output buffer was not large enough to
+				 * hold all the decompressed data. Increase its
+				 * size and continue decompression. */
+				gsize new_size = zstr.total_out + EOG_ICC_INFLATE_BUFFER_STEP;
+
+				if (G_UNLIKELY (new_size > EOG_ICC_INFLATE_BUFFER_LIMIT)) {
+					/* Enforce a memory limit for the output
+					 * buffer to avoid possible OOM cases */
+					inflateEnd (&zstr);
+					g_free (outbuf);
+					eog_debug_message (DEBUG_IMAGE_DATA, "ICC profile is too large. Ignoring.");
+					return NULL;
+				}
+				outbuf = g_realloc(outbuf, new_size);
+				zstr.avail_out = EOG_ICC_INFLATE_BUFFER_STEP;
+				zstr.next_out = outbuf + zstr.total_out;
+			}
+			z_ret = inflate (&zstr, Z_SYNC_FLUSH);
+		} while (z_ret == Z_OK);
+
+		if (G_UNLIKELY (z_ret != Z_STREAM_END)) {
+			eog_debug_message (DEBUG_IMAGE_DATA, "Error while inflating ICC profile: %s (%d)", zstr.msg, z_ret);
+			inflateEnd (&zstr);
+			g_free (outbuf);
+			return NULL;
+		}
+
+		profile = cmsOpenProfileFromMem(outbuf, zstr.total_out);
+		inflateEnd (&zstr);
+		g_free (outbuf);
+
+		eog_debug_message (DEBUG_LCMS, "PNG has %s ICC profile", profile ? "valid" : "invalid");
+	}
+
+	if (!profile && priv->sRGB_chunk) {
+		eog_debug_message (DEBUG_LCMS, "PNG is sRGB");
+		/* If the file has an sRGB chunk the image data is in the sRGB
+		 * colorspace. lcms has a built-in sRGB profile. */
+
+		profile = cmsCreate_sRGBProfile ();
+	}
+
+	if (!profile && priv->cHRM_chunk) {
+		cmsCIExyY whitepoint;
+		cmsCIExyYTRIPLE primaries;
+		cmsToneCurve *gamma[3];
+		double gammaValue = 2.2; // 2.2 should be a sane default gamma
+
+		/* This uglyness extracts the chromacity and whitepoint values
+		 * from a PNG's cHRM chunk. These can be accurate up to the
+		 * 5th decimal point.
+		 * They are saved as integer values multiplied by 100000. */
+
+		eog_debug_message (DEBUG_LCMS, "Trying to calculate color profile");
+
+		whitepoint.x = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 0, 100000);
+		whitepoint.y = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 1, 100000);
+
+		primaries.Red.x = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 2, 100000);
+		primaries.Red.y = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 3, 100000);
+		primaries.Green.x = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 4, 100000);
+		primaries.Green.y = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 5, 100000);
+		primaries.Blue.x = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 6, 100000);
+		primaries.Blue.y = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 7, 100000);
+
+		primaries.Red.Y = primaries.Green.Y = primaries.Blue.Y = 1.0;
+
+		/* If the gAMA_chunk is present use its value which is saved
+		 * the same way as the whitepoint. Use 2.2 as default value if
+		 * the chunk is not present. */
+		if (priv->gAMA_chunk)
+			gammaValue = (double) 1.0/EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->gAMA_chunk, 0, 100000);
+
+		gamma[0] = gamma[1] = gamma[2] = cmsBuildGamma (NULL, gammaValue);
+
+		profile = cmsCreateRGBProfile (&whitepoint, &primaries, gamma);
+
+		cmsFreeToneCurve(gamma[0]);
+	}
+
+	return profile;
+}
+#endif
+
+static void
+eog_metadata_reader_png_init_emr_iface (gpointer g_iface, gpointer iface_data)
+{
+	EogMetadataReaderInterface *iface;
+
+	iface = (EogMetadataReaderInterface*) g_iface;
+
+	iface->consume =
+		(void (*) (EogMetadataReader *self, const guchar *buf, guint len))
+			eog_metadata_reader_png_consume;
+	iface->finished =
+		(gboolean (*) (EogMetadataReader *self))
+			eog_metadata_reader_png_finished;
+#ifdef HAVE_LCMS
+	iface->get_icc_profile =
+		(cmsHPROFILE (*) (EogMetadataReader *self))
+			eog_metadata_reader_png_get_icc_profile;
+#endif
+#ifdef HAVE_EXEMPI
+	iface->get_xmp_ptr =
+		(gpointer (*) (EogMetadataReader *self))
+			eog_metadata_reader_png_get_xmp_data;
+#endif
+}
diff --git a/src/eog-metadata-reader-png.h b/src/eog-metadata-reader-png.h
new file mode 100644
index 0000000..da41b40
--- /dev/null
+++ b/src/eog-metadata-reader-png.h
@@ -0,0 +1,55 @@
+/* Eye Of GNOME -- PNG Metadata Reader
+ *
+ * Copyright (C) 2008 The Free Software Foundation
+ *
+ * Author: Felix Riemann <friemann svn gnome org>
+ *
+ * Based on the old EogMetadataReader code.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _EOG_METADATA_READER_PNG_H_
+#define _EOG_METADATA_READER_PNG_H_
+
+G_BEGIN_DECLS
+
+#define EOG_TYPE_METADATA_READER_PNG		(eog_metadata_reader_png_get_type ())
+#define EOG_METADATA_READER_PNG(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), EOG_TYPE_METADATA_READER_PNG, EogMetadataReaderPng))
+#define EOG_METADATA_READER_PNG_CLASS(k)	(G_TYPE_CHECK_CLASS_CAST((k), EOG_TYPE_METADATA_READER_PNG, EogMetadataReaderPngClass))
+#define EOG_IS_METADATA_READER_PNG(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), EOG_TYPE_METADATA_READER_PNG))
+#define EOG_IS_METADATA_READER_PNG_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), EOG_TYPE_METADATA_READER_PNG))
+#define EOG_METADATA_READER_PNG_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), EOG_TYPE_METADATA_READER_PNG, EogMetadataReaderPngClass))
+
+typedef struct _EogMetadataReaderPng EogMetadataReaderPng;
+typedef struct _EogMetadataReaderPngClass EogMetadataReaderPngClass;
+typedef struct _EogMetadataReaderPngPrivate EogMetadataReaderPngPrivate;
+
+struct _EogMetadataReaderPng {
+	GObject parent;
+
+	EogMetadataReaderPngPrivate *priv;
+};
+
+struct _EogMetadataReaderPngClass {
+	GObjectClass parent_klass;
+};
+
+G_GNUC_INTERNAL
+GType		      eog_metadata_reader_png_get_type	(void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* _EOG_METADATA_READER_PNG_H_ */
diff --git a/src/eog-metadata-reader.c b/src/eog-metadata-reader.c
new file mode 100644
index 0000000..29fb70b
--- /dev/null
+++ b/src/eog-metadata-reader.c
@@ -0,0 +1,132 @@
+/* Eye Of GNOME -- Metadata Reader Interface
+ *
+ * Copyright (C) 2008-2011 The Free Software Foundation
+ *
+ * Author: Felix Riemann <friemann svn gnome org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "eog-metadata-reader.h"
+#include "eog-metadata-reader-jpg.h"
+#include "eog-metadata-reader-png.h"
+#include "eog-debug.h"
+
+G_DEFINE_INTERFACE (EogMetadataReader, eog_metadata_reader, G_TYPE_INVALID)
+
+EogMetadataReader*
+eog_metadata_reader_new (EogMetadataFileType type)
+{
+	EogMetadataReader *emr;
+
+	switch (type) {
+	case EOG_METADATA_JPEG:
+		emr = EOG_METADATA_READER (g_object_new (EOG_TYPE_METADATA_READER_JPG, NULL));
+		break;
+	case EOG_METADATA_PNG:
+		emr = EOG_METADATA_READER (g_object_new (EOG_TYPE_METADATA_READER_PNG, NULL));
+		break;
+	default:
+		emr = NULL;
+		break;
+	}
+
+	return emr;
+}
+
+gboolean
+eog_metadata_reader_finished (EogMetadataReader *emr)
+{
+	g_return_val_if_fail (EOG_IS_METADATA_READER (emr), TRUE);
+
+	return EOG_METADATA_READER_GET_INTERFACE (emr)->finished (emr);
+}
+
+
+void
+eog_metadata_reader_consume (EogMetadataReader *emr, const guchar *buf, guint len)
+{
+	EOG_METADATA_READER_GET_INTERFACE (emr)->consume (emr, buf, len);
+}
+
+/* Returns the raw exif data. NOTE: The caller of this function becomes
+ * the new owner of this piece of memory and is responsible for freeing it!
+ */
+void
+eog_metadata_reader_get_exif_chunk (EogMetadataReader *emr, guchar **data, guint *len)
+{
+	g_return_if_fail (data != NULL && len != NULL);
+
+	EOG_METADATA_READER_GET_INTERFACE (emr)->get_raw_exif (emr, data, len);
+}
+
+#ifdef HAVE_EXIF
+ExifData*
+eog_metadata_reader_get_exif_data (EogMetadataReader *emr)
+{
+	return EOG_METADATA_READER_GET_INTERFACE (emr)->get_exif_data (emr);
+}
+#endif
+
+#ifdef HAVE_EXEMPI
+XmpPtr
+eog_metadata_reader_get_xmp_data (EogMetadataReader *emr)
+{
+	return EOG_METADATA_READER_GET_INTERFACE (emr)->get_xmp_ptr (emr);
+}
+#endif
+
+#ifdef HAVE_LCMS
+cmsHPROFILE
+eog_metadata_reader_get_icc_profile (EogMetadataReader *emr)
+{
+	return EOG_METADATA_READER_GET_INTERFACE (emr)->get_icc_profile (emr);
+}
+#endif
+
+/* Default vfunc that simply clears the output if not overriden by the
+   implementing class. This mimics the old behavour of get_exif_chunk(). */
+static void
+_eog_metadata_reader_default_get_raw_exif (EogMetadataReader *emr,
+					   guchar **data, guint *length)
+{
+	g_return_if_fail (data != NULL && length != NULL);
+
+	*data = NULL;
+	*length = 0;
+}
+
+/* Default vfunc that simply returns NULL if not overriden by the implementing
+   class. Mimics the old fallback behaviour of the getter functions. */
+static gpointer
+_eog_metadata_reader_default_get_null (EogMetadataReader *emr)
+{
+	return NULL;
+}
+
+static void
+eog_metadata_reader_default_init (EogMetadataReaderInterface *iface)
+{
+	/* consume and finished are required to be implemented */
+	/* Not-implemented funcs return NULL by default */
+	iface->get_raw_exif = _eog_metadata_reader_default_get_raw_exif;
+	iface->get_exif_data = _eog_metadata_reader_default_get_null;
+	iface->get_icc_profile = _eog_metadata_reader_default_get_null;
+	iface->get_xmp_ptr = _eog_metadata_reader_default_get_null;
+}
diff --git a/src/eog-metadata-reader.h b/src/eog-metadata-reader.h
new file mode 100644
index 0000000..81e976b
--- /dev/null
+++ b/src/eog-metadata-reader.h
@@ -0,0 +1,112 @@
+/* Eye Of GNOME -- Metadata Reader Interface
+ *
+ * Copyright (C) 2008 The Free Software Foundation
+ *
+ * Author: Felix Riemann <friemann svn gnome org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _EOG_METADATA_READER_H_
+#define _EOG_METADATA_READER_H_
+
+#include <glib-object.h>
+#if HAVE_EXIF
+#include "eog-exif-util.h"
+#endif
+#if HAVE_EXEMPI
+#include <exempi/xmp.h>
+#endif
+#if HAVE_LCMS
+#include <lcms2.h>
+#endif
+
+G_BEGIN_DECLS
+
+#define EOG_TYPE_METADATA_READER	      (eog_metadata_reader_get_type ())
+#define EOG_METADATA_READER(o)		      (G_TYPE_CHECK_INSTANCE_CAST ((o), EOG_TYPE_METADATA_READER, EogMetadataReader))
+#define EOG_IS_METADATA_READER(o)	      (G_TYPE_CHECK_INSTANCE_TYPE ((o), EOG_TYPE_METADATA_READER))
+#define EOG_METADATA_READER_GET_INTERFACE(o)  (G_TYPE_INSTANCE_GET_INTERFACE ((o), EOG_TYPE_METADATA_READER, EogMetadataReaderInterface))
+
+typedef struct _EogMetadataReader EogMetadataReader;
+typedef struct _EogMetadataReaderInterface EogMetadataReaderInterface;
+
+struct _EogMetadataReaderInterface {
+	GTypeInterface parent;
+
+	void		(*consume)		(EogMetadataReader *self,
+						 const guchar *buf,
+						 guint len);
+
+	gboolean	(*finished)		(EogMetadataReader *self);
+
+	void		(*get_raw_exif)		(EogMetadataReader *self,
+						 guchar **data,
+						 guint *len);
+
+	gpointer	(*get_exif_data)	(EogMetadataReader *self);
+
+	gpointer	(*get_icc_profile)	(EogMetadataReader *self);
+
+	gpointer	(*get_xmp_ptr)		(EogMetadataReader *self);
+};
+
+typedef enum {
+	EOG_METADATA_JPEG,
+	EOG_METADATA_PNG
+} EogMetadataFileType;
+
+G_GNUC_INTERNAL
+GType                eog_metadata_reader_get_type	(void) G_GNUC_CONST;
+
+G_GNUC_INTERNAL
+EogMetadataReader*   eog_metadata_reader_new 		(EogMetadataFileType type);
+
+G_GNUC_INTERNAL
+void                 eog_metadata_reader_consume	(EogMetadataReader *emr,
+							 const guchar *buf,
+							 guint len);
+
+G_GNUC_INTERNAL
+gboolean             eog_metadata_reader_finished	(EogMetadataReader *emr);
+
+G_GNUC_INTERNAL
+void                 eog_metadata_reader_get_exif_chunk (EogMetadataReader *emr,
+							 guchar **data,
+							 guint *len);
+
+#ifdef HAVE_EXIF
+G_GNUC_INTERNAL
+ExifData*         eog_metadata_reader_get_exif_data	(EogMetadataReader *emr);
+#endif
+
+#ifdef HAVE_EXEMPI
+G_GNUC_INTERNAL
+XmpPtr	     	     eog_metadata_reader_get_xmp_data	(EogMetadataReader *emr);
+#endif
+
+#if 0
+gpointer             eog_metadata_reader_get_iptc_chunk	(EogMetadataReader *emr);
+IptcData*            eog_metadata_reader_get_iptc_data	(EogMetadataReader *emr);
+#endif
+
+#ifdef HAVE_LCMS
+G_GNUC_INTERNAL
+cmsHPROFILE          eog_metadata_reader_get_icc_profile (EogMetadataReader *emr);
+#endif
+
+G_END_DECLS
+
+#endif /* _EOG_METADATA_READER_H_ */
diff --git a/src/eog-print-image-setup.c b/src/eog-print-image-setup.c
new file mode 100644
index 0000000..4c7a726
--- /dev/null
+++ b/src/eog-print-image-setup.c
@@ -0,0 +1,1139 @@
+/* Eye Of GNOME -- Print Dialog Custom Widget
+ *
+ * Copyright (C) 2006-2007 The Free Software Foundation
+ *
+ * Author: Claudio Saavedra <csaavedra alumnos utalca cl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <gtk/gtk.h>
+#include <gtk/gtkunixprint.h>
+
+#include <glib/gi18n.h>
+#include <glib/gprintf.h>
+
+#ifdef HAVE__NL_MEASUREMENT_MEASUREMENT
+#include <langinfo.h>
+#endif
+
+#include "eog-print-image-setup.h"
+#include "eog-print-preview.h"
+
+/**
+ * SECTION:
+ * @Title: Printing Custom Widget
+ * @Short_Description: a custom widget to setup image prints.
+ * @Stability_Level: Internal
+ * @See_Also: #EogPrintPreview
+ *
+ * This widget is to be used as the custom widget in a #GtkPrintUnixDialog in
+ * EOG. Through it, you can set the position and scaling of a image
+ * interactively.
+ */
+
+#define EOG_PRINT_IMAGE_SETUP_GET_PRIVATE(object) \
+	(G_TYPE_INSTANCE_GET_PRIVATE ((object), EOG_TYPE_PRINT_IMAGE_SETUP, EogPrintImageSetupPrivate))
+
+G_DEFINE_TYPE (EogPrintImageSetup, eog_print_image_setup, GTK_TYPE_GRID);
+
+struct _EogPrintImageSetupPrivate {
+	GtkWidget *left;
+	GtkWidget *right;
+	GtkWidget *top;
+	GtkWidget *bottom;
+
+	GtkWidget *center;
+
+	GtkWidget *width;
+	GtkWidget *height;
+
+	GtkWidget *scaling;
+	GtkWidget *unit;
+
+	GtkUnit current_unit;
+
+	EogImage *image;
+	GtkPageSetup *page_setup;
+
+	GtkWidget *preview;
+};
+
+enum {
+	PROP_0,
+	PROP_IMAGE,
+	PROP_PAGE_SETUP
+};
+
+enum {
+	CENTER_NONE,
+	CENTER_HORIZONTAL,
+	CENTER_VERTICAL,
+	CENTER_BOTH
+};
+
+enum {
+	CHANGE_HORIZ,
+	CHANGE_VERT
+};
+
+enum {
+	UNIT_INCH,
+	UNIT_MM
+};
+
+#define FACTOR_INCH_TO_MM 25.4
+#define FACTOR_INCH_TO_PIXEL 72.
+#define FACTOR_MM_TO_INCH 0.03937007874015748
+#define FACTOR_MM_TO_PIXEL 2.834645669
+
+static void eog_print_image_setup_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
+static void eog_print_image_setup_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+
+static void on_left_value_changed   (GtkSpinButton *spinbutton, gpointer user_data);
+static void on_right_value_changed  (GtkSpinButton *spinbutton, gpointer user_data);
+static void on_top_value_changed    (GtkSpinButton *spinbutton, gpointer user_data);
+static void on_bottom_value_changed (GtkSpinButton *spinbutton, gpointer user_data);
+
+static void on_width_value_changed  (GtkSpinButton *spinbutton, gpointer user_data);
+static void on_height_value_changed (GtkSpinButton *spinbutton, gpointer user_data);
+
+
+static void
+block_handlers (EogPrintImageSetup *setup)
+{
+	EogPrintImageSetupPrivate *priv = setup->priv;
+
+	g_signal_handlers_block_by_func (priv->left, on_left_value_changed, setup);
+	g_signal_handlers_block_by_func (priv->right, on_right_value_changed, setup);
+	g_signal_handlers_block_by_func (priv->width, on_width_value_changed, setup);
+	g_signal_handlers_block_by_func (priv->top, on_top_value_changed, setup);
+	g_signal_handlers_block_by_func (priv->bottom, on_bottom_value_changed, setup);
+	g_signal_handlers_block_by_func (priv->height, on_height_value_changed, setup);
+}
+
+static void
+unblock_handlers (EogPrintImageSetup *setup)
+{
+	EogPrintImageSetupPrivate *priv = setup->priv;
+
+	g_signal_handlers_unblock_by_func (priv->left, on_left_value_changed, setup);
+	g_signal_handlers_unblock_by_func (priv->right, on_right_value_changed, setup);
+	g_signal_handlers_unblock_by_func (priv->width, on_width_value_changed, setup);
+	g_signal_handlers_unblock_by_func (priv->top, on_top_value_changed, setup);
+	g_signal_handlers_unblock_by_func (priv->bottom, on_bottom_value_changed, setup);
+	g_signal_handlers_unblock_by_func (priv->height, on_height_value_changed, setup);
+}
+
+static gdouble
+get_scale_to_px_factor (EogPrintImageSetup *setup)
+{
+	gdouble factor = 0.;
+
+	switch (setup->priv->current_unit) {
+	case GTK_UNIT_MM:
+		factor = FACTOR_MM_TO_PIXEL;
+		break;
+	case GTK_UNIT_INCH:
+		factor = FACTOR_INCH_TO_PIXEL;
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+
+	return factor;
+}
+
+static gdouble
+get_max_percentage (EogPrintImageSetup *setup)
+{
+	EogPrintImageSetupPrivate *priv = setup->priv;
+	gdouble p_width, p_height;
+	gdouble width, height;
+	gint pix_width, pix_height;
+	gdouble perc;
+
+	p_width = gtk_page_setup_get_page_width (priv->page_setup, GTK_UNIT_INCH);
+	p_height = gtk_page_setup_get_page_height (priv->page_setup, GTK_UNIT_INCH);
+
+	eog_image_get_size (priv->image, &pix_width, &pix_height);
+
+	width  = (gdouble)pix_width/FACTOR_INCH_TO_PIXEL;
+	height = (gdouble)pix_height/FACTOR_INCH_TO_PIXEL;
+
+	if (p_width > width && p_height > height) {
+		perc = 1.;
+	} else {
+		perc = MIN (p_width/width, p_height/height);
+	}
+
+	return perc;
+}
+
+static void
+center (gdouble page_width,
+	gdouble width,
+	GtkSpinButton *s_left,
+	GtkSpinButton *s_right)
+{
+	gdouble left, right;
+
+	left = (page_width - width)/2;
+	right = page_width - left - width;
+	gtk_spin_button_set_value (s_left, left);
+	gtk_spin_button_set_value (s_right, right);
+}
+
+static void
+on_center_changed (GtkComboBox *combobox,
+		   gpointer user_data)
+{
+	EogPrintImageSetup *setup;
+	EogPrintImageSetupPrivate *priv;
+	gint active;
+
+	setup = EOG_PRINT_IMAGE_SETUP (user_data);
+	priv = setup->priv;
+
+	active = gtk_combo_box_get_active (combobox);
+
+	switch (active) {
+	case CENTER_HORIZONTAL:
+		center (gtk_page_setup_get_page_width (priv->page_setup,
+						       priv->current_unit),
+			gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->width)),
+			GTK_SPIN_BUTTON (priv->left),
+			GTK_SPIN_BUTTON (priv->right));
+		break;
+	case CENTER_VERTICAL:
+		center (gtk_page_setup_get_page_height (priv->page_setup,
+							priv->current_unit),
+			gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->height)),
+			GTK_SPIN_BUTTON (priv->top),
+			GTK_SPIN_BUTTON (priv->bottom));
+		break;
+	case CENTER_BOTH:
+		center (gtk_page_setup_get_page_width (priv->page_setup,
+						       priv->current_unit),
+			gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->width)),
+			GTK_SPIN_BUTTON (priv->left),
+			GTK_SPIN_BUTTON (priv->right));
+		center (gtk_page_setup_get_page_height (priv->page_setup,
+							priv->current_unit),
+			gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->height)),
+			GTK_SPIN_BUTTON (priv->top),
+			GTK_SPIN_BUTTON (priv->bottom));
+		break;
+	case CENTER_NONE:
+	default:
+		break;
+	}
+
+	gtk_combo_box_set_active (combobox, active);
+}
+
+static void
+update_image_pos_ranges (EogPrintImageSetup *setup,
+			 gdouble page_width,
+			 gdouble page_height,
+			 gdouble width,
+			 gdouble height)
+{
+	EogPrintImageSetupPrivate *priv;
+
+	priv = setup->priv;
+
+	gtk_spin_button_set_range (GTK_SPIN_BUTTON (priv->left),
+				   0, page_width - width);
+	gtk_spin_button_set_range (GTK_SPIN_BUTTON (priv->right),
+				   0, page_width - width);
+	gtk_spin_button_set_range (GTK_SPIN_BUTTON (priv->top),
+				   0, page_height - height);
+	gtk_spin_button_set_range (GTK_SPIN_BUTTON (priv->bottom),
+				   0, page_height - height);
+}
+
+static gboolean
+on_scale_changed (GtkRange     *range,
+		  gpointer      user_data)
+{
+	gdouble scale;
+	gdouble width, height;
+	gint pix_width, pix_height;
+	gdouble left, right, top, bottom;
+	gdouble page_width, page_height;
+	EogPrintImageSetupPrivate *priv;
+	EogPrintImageSetup *setup;
+	gdouble factor;
+	EogImage *image;
+
+	setup = EOG_PRINT_IMAGE_SETUP (user_data);
+	priv = setup->priv;
+
+	gtk_combo_box_set_active (GTK_COMBO_BOX (priv->center), CENTER_NONE);
+
+	image = priv->image;
+	eog_image_get_size (image, &pix_width, &pix_height);
+
+	factor = get_scale_to_px_factor (setup);
+
+	width = (gdouble)pix_width/factor;
+	height = (gdouble)pix_height/factor;
+
+	left = gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->left));
+	top = gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->top));
+
+	scale = CLAMP (0.01*gtk_range_get_value (range), 0, get_max_percentage (setup));
+
+ 	eog_print_preview_set_scale (EOG_PRINT_PREVIEW (priv->preview), scale);
+
+	width  *= scale;
+	height *= scale;
+
+	page_width = gtk_page_setup_get_page_width (priv->page_setup, priv->current_unit);
+	page_height = gtk_page_setup_get_page_height (priv->page_setup, priv->current_unit);
+
+	update_image_pos_ranges (setup, page_width, page_height, width, height);
+
+	right = page_width - left - width;
+	bottom = page_height - top - height;
+
+	gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->width), width);
+	gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->height), height);
+	gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->right), right);
+	gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->bottom), bottom);
+
+	return FALSE;
+}
+
+static gchar *
+on_scale_format_value (GtkScale *scale,
+		       gdouble value)
+{
+	return g_strdup_printf ("%i%%", (gint)value);
+}
+
+static void
+position_values_changed (EogPrintImageSetup *setup,
+			 GtkWidget *w_changed,
+			 GtkWidget *w_to_update,
+			 GtkWidget *w_size,
+			 gdouble total_size,
+			 gint change)
+{
+	EogPrintImageSetupPrivate *priv;
+	gdouble changed, to_update, size;
+	gdouble pos;
+
+	priv = setup->priv;
+	size = gtk_spin_button_get_value (GTK_SPIN_BUTTON (w_size));
+	changed = gtk_spin_button_get_value (GTK_SPIN_BUTTON (w_changed));
+
+	to_update = total_size - changed - size;
+	gtk_spin_button_set_value (GTK_SPIN_BUTTON (w_to_update), to_update);
+	gtk_combo_box_set_active (GTK_COMBO_BOX (priv->center), CENTER_NONE);
+
+	switch (change) {
+	case CHANGE_HORIZ:
+		pos = gtk_spin_button_get_value (GTK_SPIN_BUTTON (setup->priv->left));
+		if (setup->priv->current_unit == GTK_UNIT_MM) {
+			pos *= FACTOR_MM_TO_INCH;
+		}
+ 		eog_print_preview_set_image_position (EOG_PRINT_PREVIEW (priv->preview), pos, -1);
+		break;
+	case CHANGE_VERT:
+		pos = gtk_spin_button_get_value (GTK_SPIN_BUTTON (setup->priv->top));
+		if (setup->priv->current_unit == GTK_UNIT_MM) {
+			pos *= FACTOR_MM_TO_INCH;
+		}
+		eog_print_preview_set_image_position (EOG_PRINT_PREVIEW (priv->preview), -1, pos);
+		break;
+	}
+}
+
+static void
+on_left_value_changed (GtkSpinButton *spinbutton,
+		       gpointer       user_data)
+{
+	EogPrintImageSetup *setup;
+	EogPrintImageSetupPrivate *priv;
+
+	setup = EOG_PRINT_IMAGE_SETUP (user_data);
+	priv = setup->priv;
+
+	position_values_changed (setup,
+				 priv->left, priv->right, priv->width,
+				 gtk_page_setup_get_page_width (priv->page_setup,
+								priv->current_unit),
+				 CHANGE_HORIZ);
+}
+
+static void
+on_right_value_changed (GtkSpinButton *spinbutton,
+			gpointer       user_data)
+{
+	EogPrintImageSetupPrivate *priv;
+
+	priv = EOG_PRINT_IMAGE_SETUP (user_data)->priv;
+
+	position_values_changed (EOG_PRINT_IMAGE_SETUP (user_data),
+				 priv->right, priv->left, priv->width,
+				 gtk_page_setup_get_page_width (priv->page_setup,
+								priv->current_unit),
+				 CHANGE_HORIZ);
+}
+
+static void
+on_top_value_changed (GtkSpinButton *spinbutton,
+		      gpointer       user_data)
+{
+	EogPrintImageSetupPrivate *priv;
+
+	priv = EOG_PRINT_IMAGE_SETUP (user_data)->priv;
+
+	position_values_changed (EOG_PRINT_IMAGE_SETUP (user_data),
+				 priv->top, priv->bottom, priv->height,
+				 gtk_page_setup_get_page_height (priv->page_setup,
+								 priv->current_unit),
+				 CHANGE_VERT);
+}
+
+static void
+on_bottom_value_changed (GtkSpinButton *spinbutton,
+			 gpointer       user_data)
+{
+	EogPrintImageSetupPrivate *priv;
+
+	priv = EOG_PRINT_IMAGE_SETUP (user_data)->priv;
+
+	position_values_changed (EOG_PRINT_IMAGE_SETUP (user_data),
+				 priv->bottom, priv->top, priv->height,
+				 gtk_page_setup_get_page_height (priv->page_setup,
+								 priv->current_unit),
+				 CHANGE_VERT);
+}
+
+static void
+size_changed (EogPrintImageSetup *setup,
+	      GtkWidget *w_size_x,
+	      GtkWidget *w_size_y,
+	      GtkWidget *w_margin_x_1,
+	      GtkWidget *w_margin_x_2,
+	      GtkWidget *w_margin_y_1,
+	      GtkWidget *w_margin_y_2,
+	      gdouble page_size_x,
+	      gdouble page_size_y,
+	      gint change)
+{
+	EogPrintImageSetupPrivate *priv;
+	gdouble margin_x_1, margin_x_2;
+	gdouble margin_y_1, margin_y_2;
+	gdouble orig_size_x = -1, orig_size_y = -1, scale;
+	gdouble size_x, size_y;
+	gint pix_width, pix_height;
+	gdouble factor;
+
+	priv = setup->priv;
+
+	size_x = gtk_spin_button_get_value (GTK_SPIN_BUTTON (w_size_x));
+	margin_x_1 = gtk_spin_button_get_value (GTK_SPIN_BUTTON (w_margin_x_1));
+	margin_y_1 = gtk_spin_button_get_value (GTK_SPIN_BUTTON (w_margin_y_1));
+
+	eog_image_get_size (priv->image, &pix_width, &pix_height);
+
+	factor = get_scale_to_px_factor (setup);
+
+	switch (change) {
+	case CHANGE_HORIZ:
+		orig_size_x = (gdouble) pix_width / factor;
+		orig_size_y = (gdouble) pix_height / factor;
+		break;
+	case CHANGE_VERT:
+		orig_size_y = (gdouble) pix_width / factor;
+		orig_size_x = (gdouble) pix_height / factor;
+		break;
+	}
+
+	scale = CLAMP (size_x / orig_size_x, 0, 1);
+
+	size_y = scale * orig_size_y;
+
+	margin_x_2 = page_size_x - margin_x_1 - size_x;
+	margin_y_2 = page_size_y - margin_y_1 - size_y;
+
+ 	eog_print_preview_set_scale (EOG_PRINT_PREVIEW (priv->preview), scale);
+
+	switch (change) {
+	case CHANGE_HORIZ:
+		update_image_pos_ranges (setup, page_size_x, page_size_y, size_x, size_y);
+		break;
+	case CHANGE_VERT:
+		update_image_pos_ranges (setup, page_size_y, page_size_x, size_y, size_x);
+		break;
+	}
+
+	gtk_range_set_value (GTK_RANGE (priv->scaling), 100*scale);
+
+	gtk_spin_button_set_value (GTK_SPIN_BUTTON (w_margin_x_2), margin_x_2);
+	gtk_spin_button_set_value (GTK_SPIN_BUTTON (w_size_y), size_y);
+	gtk_spin_button_set_value (GTK_SPIN_BUTTON (w_margin_y_2), margin_y_2);
+
+	gtk_combo_box_set_active (GTK_COMBO_BOX (priv->center), CENTER_NONE);
+}
+
+static void
+on_width_value_changed (GtkSpinButton *spinbutton,
+			gpointer       user_data)
+{
+	EogPrintImageSetupPrivate *priv = EOG_PRINT_IMAGE_SETUP (user_data)->priv;
+
+	size_changed (EOG_PRINT_IMAGE_SETUP (user_data),
+		      priv->width, priv->height,
+		      priv->left, priv->right,
+		      priv->top, priv->bottom,
+		      gtk_page_setup_get_page_width (priv->page_setup,
+						     priv->current_unit),
+		      gtk_page_setup_get_page_height (priv->page_setup,
+						      priv->current_unit),
+		      CHANGE_HORIZ);
+}
+
+static void
+on_height_value_changed (GtkSpinButton *spinbutton,
+			 gpointer       user_data)
+{
+	EogPrintImageSetupPrivate *priv = EOG_PRINT_IMAGE_SETUP (user_data)->priv;
+
+	size_changed (EOG_PRINT_IMAGE_SETUP (user_data),
+		      priv->height, priv->width,
+		      priv->top, priv->bottom,
+		      priv->left, priv->right,
+		      gtk_page_setup_get_page_height (priv->page_setup,
+						     priv->current_unit),
+		      gtk_page_setup_get_page_width (priv->page_setup,
+						     priv->current_unit),
+		      CHANGE_VERT);
+}
+
+static void
+change_unit (GtkSpinButton *spinbutton,
+	     gdouble factor,
+	     gint digits,
+	     gdouble step,
+	     gdouble page)
+{
+	gdouble value;
+	gdouble range;
+
+	gtk_spin_button_get_range (spinbutton, NULL, &range);
+	range *= factor;
+
+	value = gtk_spin_button_get_value (spinbutton);
+	value *= factor;
+
+	gtk_spin_button_set_range (spinbutton, 0, range);
+	gtk_spin_button_set_value (spinbutton, value);
+	gtk_spin_button_set_digits (spinbutton, digits);
+	gtk_spin_button_set_increments  (spinbutton, step, page);
+}
+
+static void
+set_scale_unit (EogPrintImageSetup *setup,
+		GtkUnit unit)
+{
+	EogPrintImageSetupPrivate *priv = setup->priv;
+	gdouble factor;
+	gdouble step, page;
+	gint digits;
+
+	if (G_UNLIKELY (priv->current_unit == unit))
+		return;
+
+	switch (unit) {
+	case GTK_UNIT_MM:
+		factor = FACTOR_INCH_TO_MM;
+		digits = 0;
+		step = 1;
+		page = 10;
+		break;
+	case GTK_UNIT_INCH:
+		factor = FACTOR_MM_TO_INCH;
+		digits = 2;
+		step = 0.01;
+		page = 0.1;
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+
+ 	block_handlers (setup);
+
+	change_unit (GTK_SPIN_BUTTON (priv->width), factor, digits, step, page);
+	change_unit (GTK_SPIN_BUTTON (priv->height), factor, digits, step, page);
+	change_unit (GTK_SPIN_BUTTON (priv->left), factor, digits, step, page);
+	change_unit (GTK_SPIN_BUTTON (priv->right), factor, digits, step, page);
+	change_unit (GTK_SPIN_BUTTON (priv->top), factor, digits, step, page);
+	change_unit (GTK_SPIN_BUTTON (priv->bottom), factor, digits, step, page);
+
+ 	unblock_handlers (setup);
+
+	priv->current_unit = unit;
+}
+
+static void
+on_unit_changed (GtkComboBox *combobox,
+		 gpointer user_data)
+{
+	GtkUnit unit = GTK_UNIT_INCH;
+
+	switch (gtk_combo_box_get_active (combobox)) {
+	case UNIT_INCH:
+		unit = GTK_UNIT_INCH;
+		break;
+	case UNIT_MM:
+		unit = GTK_UNIT_MM;
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+
+	set_scale_unit (EOG_PRINT_IMAGE_SETUP (user_data), unit);
+}
+
+static void
+on_preview_image_moved (EogPrintPreview *preview,
+			gpointer user_data)
+{
+	EogPrintImageSetupPrivate *priv = EOG_PRINT_IMAGE_SETUP (user_data)->priv;
+	gdouble x, y;
+
+	eog_print_preview_get_image_position (preview, &x, &y);
+
+	if (priv->current_unit == GTK_UNIT_MM) {
+		x *= FACTOR_INCH_TO_MM;
+		y *= FACTOR_INCH_TO_MM;
+	}
+
+	gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->left), x);
+	gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->top), y);
+}
+
+static gboolean
+on_preview_image_scrolled (GtkWidget *widget,
+			   GdkEventScroll *event,
+			   gpointer user_data)
+{
+	EogPrintImageSetupPrivate *priv = EOG_PRINT_IMAGE_SETUP (user_data)->priv;
+	EogPrintPreview *preview = EOG_PRINT_PREVIEW (widget);
+	gfloat scale;
+
+	scale = eog_print_preview_get_scale (preview);
+
+	if (!eog_print_preview_point_in_image_area (preview,
+						    event->x, event->y))
+	{
+		return FALSE;
+	}
+
+	switch (event->direction) {
+	case GDK_SCROLL_UP:
+		/* scale up */
+		scale *= 1.1;
+		break;
+	case GDK_SCROLL_DOWN:
+		/* scale down */
+		scale *= 0.9;
+		break;
+	default:
+		return FALSE;
+		break;
+	}
+
+	gtk_range_set_value (GTK_RANGE (priv->scaling), 100*scale);
+
+	return TRUE;
+}
+
+static gboolean
+on_preview_image_key_pressed (GtkWidget *widget,
+			      GdkEventKey *event,
+			      gpointer user_data)
+{
+	EogPrintImageSetupPrivate *priv = EOG_PRINT_IMAGE_SETUP (user_data)->priv;
+	EogPrintPreview *preview = EOG_PRINT_PREVIEW (widget);
+	gfloat scale;
+
+	scale = eog_print_preview_get_scale (preview);
+
+	switch (event->keyval) {
+	case GDK_KEY_KP_Add:
+	case GDK_KEY_plus:
+		/* scale up */
+		scale *= 1.1;
+		break;
+	case GDK_KEY_KP_Subtract:
+	case GDK_KEY_minus:
+		/* scale down */
+		scale *= 0.9;
+		break;
+	default:
+		return FALSE;
+		break;
+	}
+
+	gtk_range_set_value (GTK_RANGE (priv->scaling), 100*scale);
+
+	return TRUE;
+}
+
+/* Function taken from gtkprintunixdialog.c */
+static GtkWidget *
+wrap_in_frame (const gchar *label,
+               GtkWidget   *child)
+{
+	GtkWidget *frame, *alignment, *label_widget;
+	gchar *bold_text;
+
+	label_widget = gtk_label_new ("");
+	gtk_misc_set_alignment (GTK_MISC (label_widget), 0.0, 0.5);
+	gtk_widget_show (label_widget);
+
+	bold_text = g_markup_printf_escaped ("<b>%s</b>", label);
+	gtk_label_set_markup (GTK_LABEL (label_widget), bold_text);
+	g_free (bold_text);
+
+	frame = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+	gtk_box_pack_start (GTK_BOX (frame), label_widget, FALSE, FALSE, 0);
+
+	alignment = gtk_alignment_new (0.0, 0.0, 1.0, 1.0);
+	gtk_alignment_set_padding (GTK_ALIGNMENT (alignment),
+				   0, 0, 12, 0);
+	gtk_box_pack_start (GTK_BOX (frame), alignment, FALSE, FALSE, 0);
+
+	gtk_container_add (GTK_CONTAINER (alignment), child);
+
+	gtk_widget_show (frame);
+	gtk_widget_show (alignment);
+
+	return frame;
+}
+
+static GtkWidget *
+grid_attach_spin_button_with_label (GtkWidget *grid,
+				    const gchar* text_label,
+				    gint left, gint top)
+{
+	GtkWidget *label, *spin_button;
+
+	label = gtk_label_new_with_mnemonic (text_label);
+	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+	spin_button = gtk_spin_button_new_with_range (0, 100, 0.01);
+	gtk_spin_button_set_digits (GTK_SPIN_BUTTON (spin_button), 2);
+	gtk_entry_set_width_chars (GTK_ENTRY (spin_button), 6);
+	gtk_grid_attach (GTK_GRID (grid), label, left, top, 1, 1);
+	gtk_grid_attach_next_to (GTK_GRID (grid), spin_button, label,
+				 GTK_POS_RIGHT, 1, 1);
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label), spin_button);
+
+	return spin_button;
+}
+
+
+static void
+eog_print_image_setup_set_property (GObject      *object,
+				    guint         prop_id,
+				    const GValue *value,
+				    GParamSpec   *pspec)
+{
+	EogPrintImageSetup *setup = EOG_PRINT_IMAGE_SETUP (object);
+	EogPrintImageSetupPrivate *priv = setup->priv;
+	GdkPixbuf *pixbuf;
+
+	switch (prop_id) {
+	case PROP_IMAGE:
+		if (priv->image) {
+			g_object_unref (priv->image);
+		}
+		priv->image = EOG_IMAGE (g_value_dup_object (value));
+		if (EOG_IS_IMAGE (priv->image)) {
+			pixbuf = eog_image_get_pixbuf (priv->image);
+			g_object_set (priv->preview, "image",
+				      pixbuf, NULL);
+			g_object_unref (pixbuf);
+		}
+		break;
+	case PROP_PAGE_SETUP:
+		priv->page_setup = g_value_get_object (value);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+	}
+}
+
+static void
+eog_print_image_setup_get_property (GObject    *object,
+				    guint       prop_id,
+				    GValue     *value,
+				    GParamSpec *pspec)
+{
+	EogPrintImageSetup *setup = EOG_PRINT_IMAGE_SETUP (object);
+	EogPrintImageSetupPrivate *priv = setup->priv;
+
+	switch (prop_id) {
+	case PROP_IMAGE:
+		g_value_set_object (value, priv->image);
+		break;
+	case PROP_PAGE_SETUP:
+		g_value_set_object (value, priv->page_setup);
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+	}
+}
+
+static void
+set_initial_values (EogPrintImageSetup *setup)
+{
+	EogPrintImageSetupPrivate *priv;
+	GtkPageSetup *page_setup;
+	EogImage *image;
+	gdouble page_width, page_height;
+	gint pix_width, pix_height;
+	gdouble factor;
+	gdouble width, height;
+	gdouble max_perc;
+
+	priv = setup->priv;
+	page_setup = priv->page_setup;
+	image = priv->image;
+
+	factor = get_scale_to_px_factor (setup);
+
+	eog_image_get_size (image, &pix_width, &pix_height);
+	width = (gdouble)pix_width/factor;
+	height = (gdouble)pix_height/factor;
+
+	max_perc = get_max_percentage (setup);
+
+	width *= max_perc;
+	height *= max_perc;
+
+	gtk_range_set_range (GTK_RANGE (priv->scaling), 1, 100*max_perc);
+	gtk_range_set_increments (GTK_RANGE (priv->scaling), max_perc, 10*max_perc);
+	gtk_range_set_value (GTK_RANGE (priv->scaling), 100*max_perc);
+
+	eog_print_preview_set_scale (EOG_PRINT_PREVIEW (priv->preview), max_perc);
+	gtk_spin_button_set_range (GTK_SPIN_BUTTON (priv->width), 0, width);
+	gtk_spin_button_set_range (GTK_SPIN_BUTTON (priv->height), 0, height);
+	gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->width), width);
+	gtk_spin_button_set_value (GTK_SPIN_BUTTON (priv->height), height);
+
+	gtk_combo_box_set_active (GTK_COMBO_BOX (priv->center),
+				  CENTER_BOTH);
+
+	center (gtk_page_setup_get_page_width (priv->page_setup, priv->current_unit),
+		gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->width)),
+		GTK_SPIN_BUTTON (priv->left), GTK_SPIN_BUTTON (priv->right));
+	center (gtk_page_setup_get_page_height (priv->page_setup, priv->current_unit),
+		gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->height)),
+		GTK_SPIN_BUTTON (priv->top), GTK_SPIN_BUTTON (priv->bottom));
+
+	page_width = gtk_page_setup_get_page_width (page_setup, priv->current_unit);
+	page_height = gtk_page_setup_get_page_height (page_setup, priv->current_unit);
+
+	update_image_pos_ranges (setup, page_width, page_height, width, height);
+
+
+}
+
+static void
+connect_signals (EogPrintImageSetup *setup)
+{
+	EogPrintImageSetupPrivate *priv;
+
+	priv = setup->priv;
+
+	g_signal_connect (G_OBJECT (priv->left), "value-changed",
+			  G_CALLBACK (on_left_value_changed), setup);
+	g_signal_connect (G_OBJECT (priv->right), "value-changed",
+			  G_CALLBACK (on_right_value_changed), setup);
+	g_signal_connect (G_OBJECT (priv->top), "value-changed",
+			  G_CALLBACK (on_top_value_changed), setup);
+	g_signal_connect (G_OBJECT (priv->bottom), "value-changed",
+			  G_CALLBACK (on_bottom_value_changed), setup);
+	g_signal_connect (G_OBJECT (priv->width), "value-changed",
+			  G_CALLBACK (on_width_value_changed), setup);
+	g_signal_connect (G_OBJECT (priv->height), "value-changed",
+			  G_CALLBACK (on_height_value_changed), setup);
+	g_signal_connect (G_OBJECT (priv->scaling), "value-changed",
+			  G_CALLBACK (on_scale_changed), setup);
+	g_signal_connect (G_OBJECT (priv->scaling), "format-value",
+			  G_CALLBACK (on_scale_format_value), NULL);
+	g_signal_connect (G_OBJECT (priv->preview), "image-moved",
+			  G_CALLBACK (on_preview_image_moved), setup);
+	g_signal_connect (G_OBJECT (priv->preview), "scroll-event",
+			  G_CALLBACK (on_preview_image_scrolled), setup);
+	g_signal_connect (G_OBJECT (priv->preview), "key-press-event",
+			  G_CALLBACK (on_preview_image_key_pressed), setup);
+}
+
+static void
+eog_print_image_setup_class_init (EogPrintImageSetupClass *class)
+{
+	GObjectClass *object_class = (GObjectClass *)class;
+
+	object_class->set_property = eog_print_image_setup_set_property;
+	object_class->get_property = eog_print_image_setup_get_property;
+
+	g_object_class_install_property (object_class, PROP_IMAGE,
+					 g_param_spec_object ("image",
+							      _("Image"),
+							      _("The image whose printing properties will be set up"),
+							      EOG_TYPE_IMAGE,
+							      G_PARAM_READWRITE));
+
+	g_object_class_install_property (object_class, PROP_PAGE_SETUP,
+					 g_param_spec_object ("page-setup",
+							      _("Page Setup"),
+							      _("The information for the page where the image will be printed"),
+							      GTK_TYPE_PAGE_SETUP,
+							      G_PARAM_READWRITE));
+
+	g_type_class_add_private (class, sizeof (EogPrintImageSetupPrivate));
+}
+
+static void
+eog_print_image_setup_init (EogPrintImageSetup *setup)
+{
+	GtkWidget *frame;
+	GtkWidget *grid;
+	GtkWidget *label;
+	GtkWidget *hscale;
+	GtkWidget *combobox;
+	EogPrintImageSetupPrivate *priv;
+
+#ifdef HAVE__NL_MEASUREMENT_MEASUREMENT
+	gchar *locale_scale = NULL;
+#endif
+
+	priv = setup->priv = EOG_PRINT_IMAGE_SETUP_GET_PRIVATE (setup);
+
+	priv->image = NULL;
+
+	grid = gtk_grid_new ();
+	gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
+	gtk_grid_set_column_spacing (GTK_GRID (grid), 12);
+	frame = wrap_in_frame (_("Position"), grid);
+	gtk_grid_attach (GTK_GRID (setup), frame, 0, 0, 1, 1);
+
+	priv->left = grid_attach_spin_button_with_label (grid,
+							 _("_Left:"), 0, 0);
+	priv->right = grid_attach_spin_button_with_label (grid,
+							  _("_Right:"), 0, 1);
+	priv->top = grid_attach_spin_button_with_label (grid, _("_Top:"), 2, 0);
+	priv->bottom = grid_attach_spin_button_with_label (grid, _("_Bottom:"),
+							   2, 1);
+
+	label = gtk_label_new_with_mnemonic (_("C_enter:"));
+	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+
+	combobox = gtk_combo_box_text_new ();
+	gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (combobox),
+				        CENTER_NONE, _("None"));
+	gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (combobox),
+				        CENTER_HORIZONTAL, _("Horizontal"));
+	gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (combobox),
+				        CENTER_VERTICAL, _("Vertical"));
+	gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (combobox),
+				        CENTER_BOTH, _("Both"));
+	gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), CENTER_NONE);
+	/* Attach combobox below right margin spinbutton and span until end */
+	gtk_grid_attach_next_to (GTK_GRID (grid), combobox, priv->right,
+				 GTK_POS_BOTTOM, 3, 1);
+	/* Attach the label to the left of the combobox */
+	gtk_grid_attach_next_to (GTK_GRID (grid), label, combobox, GTK_POS_LEFT,
+				 1, 1);
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label), combobox);
+	priv->center = combobox;
+	g_signal_connect (G_OBJECT (combobox), "changed",
+			  G_CALLBACK (on_center_changed), setup);
+
+	grid = gtk_grid_new ();
+	gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
+	gtk_grid_set_column_spacing (GTK_GRID (grid), 12);
+	frame = wrap_in_frame (_("Size"), grid);
+	gtk_grid_attach (GTK_GRID (setup), frame, 0, 1, 1, 1);
+
+	priv->width = grid_attach_spin_button_with_label (grid, _("_Width:"),
+							   0, 0);
+	priv->height = grid_attach_spin_button_with_label (grid, _("_Height:"),
+							    2, 0);
+
+	label = gtk_label_new_with_mnemonic (_("_Scaling:"));
+	hscale = gtk_scale_new_with_range (GTK_ORIENTATION_HORIZONTAL, 1, 100, 1);
+	gtk_scale_set_value_pos (GTK_SCALE (hscale), GTK_POS_RIGHT);
+	gtk_range_set_value (GTK_RANGE (hscale), 100);
+	gtk_grid_attach_next_to (GTK_GRID (grid), hscale, priv->width,
+				 GTK_POS_BOTTOM, 3, 1);
+	gtk_grid_attach_next_to (GTK_GRID (grid), label, hscale, GTK_POS_LEFT,
+				 1, 1);
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label), hscale);
+	priv->scaling = hscale;
+
+	label = gtk_label_new_with_mnemonic (_("_Unit:"));
+	gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
+
+	combobox = gtk_combo_box_text_new ();
+	gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (combobox), UNIT_MM,
+					_("Millimeters"));
+	gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (combobox),
+					UNIT_INCH, _("Inches"));
+
+#ifdef HAVE__NL_MEASUREMENT_MEASUREMENT
+	locale_scale = nl_langinfo (_NL_MEASUREMENT_MEASUREMENT);
+	if (locale_scale && locale_scale[0] == 2) {
+		gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), UNIT_INCH);
+		set_scale_unit (setup, GTK_UNIT_INCH);
+	} else
+#endif
+	{
+		gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), UNIT_MM);
+		set_scale_unit (setup, GTK_UNIT_MM);
+	}
+
+	gtk_grid_attach_next_to (GTK_GRID (grid), combobox, hscale,
+				 GTK_POS_BOTTOM, 3, 1);
+	gtk_grid_attach_next_to (GTK_GRID (grid), label, combobox, GTK_POS_LEFT,
+				 1, 1);
+
+	gtk_label_set_mnemonic_widget (GTK_LABEL (label), combobox);
+	priv->unit = combobox;
+	g_signal_connect (G_OBJECT (combobox), "changed",
+			  G_CALLBACK (on_unit_changed), setup);
+
+	priv->preview = eog_print_preview_new ();
+
+	/* FIXME: This shouldn't be set by hand */
+	gtk_widget_set_size_request (priv->preview, 250, 250);
+
+	frame = wrap_in_frame (_("Preview"), priv->preview);
+	/* The preview widget needs to span the whole grid height */
+	gtk_grid_attach (GTK_GRID (setup), frame, 1, 0, 1, 2);
+
+	gtk_widget_show_all (GTK_WIDGET (setup));
+}
+
+
+/**
+ * eog_print_image_setup_new:
+ * @image: the #EogImage to print
+ * @page_setup: a #GtkPageSetup specifying the page where
+ * the image will be print
+ *
+ * Creates a new #EogPrintImageSetup widget, to be used as a custom
+ * widget in a #GtkPrintUnixDialog. This widgets allows to set
+ * the image position and scale in a page.
+ *
+ * Returns: a new #EogPrintImageSetup
+ **/
+GtkWidget *
+eog_print_image_setup_new (EogImage *image, GtkPageSetup *page_setup)
+{
+	GtkWidget *setup;
+	GtkWidget *preview;
+
+	setup = g_object_new (EOG_TYPE_PRINT_IMAGE_SETUP,
+			     "orientation", GTK_ORIENTATION_VERTICAL,
+			     "row-spacing", 18,
+			     "column-spacing", 18,
+			     "border-width", 12,
+			     "image", image,
+			     "page-setup", page_setup,
+			     NULL);
+
+	set_initial_values (EOG_PRINT_IMAGE_SETUP (setup));
+
+	preview = EOG_PRINT_IMAGE_SETUP (setup)->priv->preview;
+	eog_print_preview_set_from_page_setup (EOG_PRINT_PREVIEW (preview),
+					       page_setup);
+
+	connect_signals (EOG_PRINT_IMAGE_SETUP (setup));
+
+	return setup;
+}
+
+/**
+ * eog_print_image_setup_get_options:
+ * @setup: a #EogPrintImageSetup
+ * @left: a pointer where to store the image's left position
+ * @top: a pointer where to store the image's top position
+ * @scale: a pointer where to store the image's scale
+ * @unit: a pointer where to store the #GtkUnit used by the @left and @top values.
+ *
+ * Gets the options set by the #EogPrintImageSetup.
+ **/
+void
+eog_print_image_setup_get_options (EogPrintImageSetup *setup,
+				   gdouble *left,
+				   gdouble *top,
+				   gdouble *scale,
+				   GtkUnit *unit)
+{
+	EogPrintImageSetupPrivate *priv;
+
+	g_return_if_fail (EOG_IS_PRINT_IMAGE_SETUP (setup));
+
+	priv = setup->priv;
+
+	*left = gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->left));
+	*top = gtk_spin_button_get_value (GTK_SPIN_BUTTON (priv->top));
+	*scale = gtk_range_get_value (GTK_RANGE (priv->scaling));
+	*unit = priv->current_unit;
+}
+
+void
+eog_print_image_setup_update (GtkPrintOperation *operation,
+			      GtkWidget         *custom_widget,
+			      GtkPageSetup      *page_setup,
+			      GtkPrintSettings  *print_settings,
+			      gpointer           user_data)
+{
+	GtkWidget *preview;
+	gdouble    pos_x;
+	gdouble    pos_y;
+	EogPrintImageSetup *setup;
+
+	setup = EOG_PRINT_IMAGE_SETUP (custom_widget);
+
+	setup->priv->page_setup = gtk_page_setup_copy (page_setup);
+
+	set_initial_values (EOG_PRINT_IMAGE_SETUP (setup));
+
+	preview = EOG_PRINT_IMAGE_SETUP (setup)->priv->preview;
+	eog_print_preview_set_from_page_setup (EOG_PRINT_PREVIEW (preview),
+					       setup->priv->page_setup);
+
+	pos_x = gtk_spin_button_get_value (GTK_SPIN_BUTTON (setup->priv->left));
+	pos_y = gtk_spin_button_get_value (GTK_SPIN_BUTTON (setup->priv->top));
+	if (setup->priv->current_unit == GTK_UNIT_MM) {
+		pos_x *= FACTOR_MM_TO_INCH;
+		pos_y *= FACTOR_MM_TO_INCH;
+	}
+	eog_print_preview_set_image_position (EOG_PRINT_PREVIEW (setup->priv->preview), pos_x, pos_y);
+}
diff --git a/src/eog-print-image-setup.h b/src/eog-print-image-setup.h
new file mode 100644
index 0000000..bf76171
--- /dev/null
+++ b/src/eog-print-image-setup.h
@@ -0,0 +1,72 @@
+/* Eye Of GNOME -- Print Dialog Custom Widget
+ *
+ * Copyright (C) 2006-2007 The Free Software Foundation
+ *
+ * Author: Claudio Saavedra <csaavedra alumnos utalca cl>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "eog-image.h"
+
+#ifndef EOG_PRINT_IMAGE_SETUP_H
+#define EOG_PRINT_IMAGE_SETUP_H
+
+G_BEGIN_DECLS
+
+typedef struct _EogPrintImageSetup         EogPrintImageSetup;
+typedef struct _EogPrintImageSetupClass    EogPrintImageSetupClass;
+typedef struct _EogPrintImageSetupPrivate  EogPrintImageSetupPrivate;
+
+#define EOG_TYPE_PRINT_IMAGE_SETUP            (eog_print_image_setup_get_type ())
+#define EOG_PRINT_IMAGE_SETUP(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), EOG_TYPE_PRINT_IMAGE_SETUP, EogPrintImageSetup))
+#define EOG_PRINT_IMAGE_SETUP_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), EOG_TYPE_PRINT_IMAGE_SETUP, EogPrintImageSetupClass))
+#define EOG_IS_PRINT_IMAGE_SETUP(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EOG_TYPE_PRINT_IMAGE_SETUP))
+#define EOG_IS_PRINT_IMAGE_SETUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EOG_TYPE_PRINT_IMAGE_SETUP))
+#define EOG_PRINT_IMAGE_SETUP_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), EOG_TYPE_PRINT_IMAGE_SETUP, EogPrintImageSetupClass))
+
+struct _EogPrintImageSetup {
+	GtkGrid parent_instance;
+
+	EogPrintImageSetupPrivate *priv;
+};
+
+struct _EogPrintImageSetupClass {
+	GtkGridClass parent_class;
+};
+
+G_GNUC_INTERNAL
+GType		  eog_print_image_setup_get_type    (void) G_GNUC_CONST;
+
+G_GNUC_INTERNAL
+GtkWidget        *eog_print_image_setup_new         (EogImage     *image,
+						     GtkPageSetup *page_setup);
+
+G_GNUC_INTERNAL
+void              eog_print_image_setup_get_options (EogPrintImageSetup *setup,
+						     gdouble            *left,
+						     gdouble            *top,
+						     gdouble            *scale,
+						     GtkUnit            *unit);
+G_GNUC_INTERNAL
+void              eog_print_image_setup_update      (GtkPrintOperation *operation,
+						     GtkWidget         *custom_widget,
+						     GtkPageSetup      *page_setup,
+						     GtkPrintSettings  *print_settings,
+						     gpointer           user_data);
+
+G_END_DECLS
+
+#endif /* EOG_PRINT_IMAGE_SETUP_H */
diff --git a/src/eog-print.c b/src/eog-print.c
new file mode 100644
index 0000000..6a68c5b
--- /dev/null
+++ b/src/eog-print.c
@@ -0,0 +1,521 @@
+/* Eye of Gnome - Print Operations
+ *
+ * Copyright (C) 2005-2008 The Free Software Foundation
+ *
+ * Author: Claudio Saavedra <csaavedra gnome org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include "eog-image.h"
+#include "eog-print.h"
+#include "eog-print-image-setup.h"
+#include "eog-util.h"
+#include "eog-debug.h"
+
+#ifdef HAVE_RSVG
+#include <librsvg/rsvg-cairo.h>
+#endif
+
+#define EOG_PRINT_SETTINGS_FILE "eog-print-settings.ini"
+#define EOG_PAGE_SETUP_GROUP "Page Setup"
+#define EOG_PRINT_SETTINGS_GROUP "Print Settings"
+
+typedef struct {
+	EogImage *image;
+	gdouble left_margin;
+	gdouble top_margin;
+	gdouble scale_factor;
+	GtkUnit unit;
+} EogPrintData;
+
+/* art_affine_flip modified to work with cairo_matrix_t */
+static void
+_eog_cairo_matrix_flip (cairo_matrix_t *dst, const cairo_matrix_t *src, gboolean horiz, gboolean vert)
+{
+	dst->xx = horiz ? -src->xx : src->xx;
+	dst->yx = horiz ? -src->yx : src->yx;
+	dst->xy = vert ? -src->xy : src->xy;
+	dst->yy = vert ? -src->yy : src->yy;
+	dst->x0 = horiz ? -src->x0 : src->x0;
+	dst->y0 = vert ? -src->y0 : src->y0;
+}
+
+static gboolean
+_cairo_ctx_supports_jpg_metadata (cairo_t *cr)
+{
+	cairo_surface_t *surface = cairo_get_target (cr);
+	cairo_surface_type_t type = cairo_surface_get_type (surface);
+
+	/* Based on cairo-1.10 */
+	return (type == CAIRO_SURFACE_TYPE_PDF || type == CAIRO_SURFACE_TYPE_PS ||
+		type == CAIRO_SURFACE_TYPE_SVG || type == CAIRO_SURFACE_TYPE_WIN32_PRINTING);
+}
+
+static void
+eog_print_draw_page (GtkPrintOperation *operation,
+		     GtkPrintContext   *context,
+		     gint               page_nr,
+		     gpointer           user_data)
+{
+	cairo_t *cr;
+	gdouble dpi_x, dpi_y;
+	gdouble x0, y0;
+	gdouble scale_factor;
+	gdouble p_width, p_height;
+	gint width, height;
+	EogPrintData *data;
+	GtkPageSetup *page_setup;
+
+	eog_debug (DEBUG_PRINTING);
+
+	data = (EogPrintData *) user_data;
+
+	scale_factor = data->scale_factor/100;
+
+	dpi_x = gtk_print_context_get_dpi_x (context);
+	dpi_y = gtk_print_context_get_dpi_y (context);
+
+	switch (data->unit) {
+	case GTK_UNIT_INCH:
+		x0 = data->left_margin * dpi_x;
+		y0 = data->top_margin  * dpi_y;
+		break;
+	case GTK_UNIT_MM:
+		x0 = data->left_margin * dpi_x/25.4;
+		y0 = data->top_margin  * dpi_y/25.4;
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+
+	cr = gtk_print_context_get_cairo_context (context);
+
+	cairo_translate (cr, x0, y0);
+
+	page_setup = gtk_print_context_get_page_setup (context);
+	p_width =  gtk_page_setup_get_page_width (page_setup, GTK_UNIT_POINTS);
+	p_height = gtk_page_setup_get_page_height (page_setup, GTK_UNIT_POINTS);
+
+	eog_image_get_size (data->image, &width, &height);
+
+	/* this is both a workaround for a bug in cairo's PDF backend, and
+	   a way to ensure we are not printing outside the page margins */
+	cairo_rectangle (cr, 0, 0, MIN (width*scale_factor, p_width), MIN (height*scale_factor, p_height));
+	cairo_clip (cr);
+
+	cairo_scale (cr, scale_factor, scale_factor);
+
+#ifdef HAVE_RSVG
+	if (eog_image_is_svg (data->image))
+	{
+		RsvgHandle *svg = eog_image_get_svg (data->image);
+
+		rsvg_handle_render_cairo (svg, cr);
+		return;
+	} else
+#endif
+	/* JPEGs can be attached to the cairo surface which simply embeds the JPEG file into the
+	 * destination PDF skipping (PNG-)recompression. This should reduce PDF sizes enormously. */
+	if (eog_image_is_jpeg (data->image) && _cairo_ctx_supports_jpg_metadata (cr))
+	{
+		GFile *file;
+		char *img_data;
+		gsize data_len;
+		cairo_surface_t *surface = NULL;
+
+		eog_debug_message (DEBUG_PRINTING, "Attaching image to cairo surface");
+
+		file = eog_image_get_file (data->image);
+		if (g_file_load_contents (file, NULL, &img_data, &data_len, NULL, NULL))
+		{
+			EogTransform *tf = eog_image_get_transform (data->image);
+			EogTransform *auto_tf = eog_image_get_autorotate_transform (data->image);
+			cairo_matrix_t mx, mx2;
+
+			if (!tf && auto_tf) {
+				/* If only autorotation data present,
+				 * make it the normal rotation. */
+				tf = auto_tf;
+				auto_tf = NULL;
+			}
+
+			/* Care must be taken with height and width values. They are not the original
+			 * values but were affected by the transformation. As the surface needs to be
+			 * generated using the original dimensions they might need to be flipped. */
+			if (tf) {
+				if (auto_tf) {
+					/* If we have an autorotation apply
+					 * it before the others */
+					tf = eog_transform_compose (auto_tf, tf);
+				}
+
+				switch (eog_transform_get_transform_type (tf)) {
+					case EOG_TRANSFORM_ROT_90:
+						surface = cairo_image_surface_create (
+								CAIRO_FORMAT_RGB24, height, width);
+						cairo_rotate (cr, 90.0 * (G_PI/180.0));
+						cairo_translate (cr, 0.0, -width);
+						break;
+					case EOG_TRANSFORM_ROT_180:
+						surface = cairo_image_surface_create (
+								CAIRO_FORMAT_RGB24, width, height);
+						cairo_rotate (cr, 180.0 * (G_PI/180.0));
+						cairo_translate (cr, -width, -height);
+						break;
+					case EOG_TRANSFORM_ROT_270:
+						surface = cairo_image_surface_create (
+								CAIRO_FORMAT_RGB24, height, width);
+						cairo_rotate (cr, 270.0 * (G_PI/180.0));
+						cairo_translate (cr, -height, 0.0);
+						break;
+					case EOG_TRANSFORM_FLIP_HORIZONTAL:
+						surface = cairo_image_surface_create (
+								CAIRO_FORMAT_RGB24, width, height);
+						cairo_matrix_init_identity (&mx);
+						_eog_cairo_matrix_flip (&mx2, &mx, TRUE, FALSE);
+						cairo_transform (cr, &mx2);
+						cairo_translate (cr, -width, 0.0);
+						break;
+					case EOG_TRANSFORM_FLIP_VERTICAL:
+						surface = cairo_image_surface_create (
+								CAIRO_FORMAT_RGB24, width, height);
+						cairo_matrix_init_identity (&mx);
+						_eog_cairo_matrix_flip (&mx2, &mx, FALSE, TRUE);
+						cairo_transform (cr, &mx2);
+						cairo_translate (cr, 0.0, -height);
+						break;
+					case EOG_TRANSFORM_TRANSPOSE:
+						surface = cairo_image_surface_create (
+								CAIRO_FORMAT_RGB24, height, width);
+						cairo_matrix_init_rotate (&mx, 90.0 * (G_PI/180.0));
+						cairo_matrix_init_identity (&mx2);
+						_eog_cairo_matrix_flip (&mx2, &mx2, TRUE, FALSE);
+						cairo_matrix_multiply (&mx2, &mx, &mx2);
+						cairo_transform (cr, &mx2);
+						break;
+					case EOG_TRANSFORM_TRANSVERSE:
+						surface = cairo_image_surface_create (
+								CAIRO_FORMAT_RGB24, height, width);
+						cairo_matrix_init_rotate (&mx, 90.0 * (G_PI/180.0));
+						cairo_matrix_init_identity (&mx2);
+						_eog_cairo_matrix_flip (&mx2, &mx2, FALSE, TRUE);
+						cairo_matrix_multiply (&mx2, &mx, &mx2);
+						cairo_transform (cr, &mx2);
+						cairo_translate (cr, -height , -width);
+						break;
+					case EOG_TRANSFORM_NONE:
+					default:
+						surface = cairo_image_surface_create (
+								CAIRO_FORMAT_RGB24, width, height);
+						break;
+				}
+			}
+
+			if (!surface)
+				surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24,
+								      width, height);
+			cairo_surface_set_mime_data (surface,
+			                             CAIRO_MIME_TYPE_JPEG,
+			                             (unsigned char*)img_data, data_len,
+						     g_free, img_data);
+			cairo_set_source_surface (cr, surface, 0, 0);
+			cairo_paint (cr);
+			cairo_surface_destroy (surface);
+			g_object_unref (file);
+			return;
+		}
+		g_object_unref (file);
+
+	}
+
+	{
+		GdkPixbuf *pixbuf;
+
+		pixbuf = eog_image_get_pixbuf (data->image);
+		gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
+ 		cairo_paint (cr);
+		g_object_unref (pixbuf);
+	}
+}
+
+static GObject *
+eog_print_create_custom_widget (GtkPrintOperation *operation,
+				       gpointer user_data)
+{
+	GtkPageSetup *page_setup;
+	EogPrintData *data;
+
+	eog_debug (DEBUG_PRINTING);
+
+	data = (EogPrintData *)user_data;
+
+	page_setup = gtk_print_operation_get_default_page_setup (operation);
+
+	if (page_setup == NULL)
+		page_setup = gtk_page_setup_new ();
+
+	return G_OBJECT (eog_print_image_setup_new (data->image, page_setup));
+}
+
+static void
+eog_print_custom_widget_apply (GtkPrintOperation *operation,
+			       GtkWidget         *widget,
+			       gpointer           user_data)
+{
+	EogPrintData *data;
+	gdouble left_margin, top_margin, scale_factor;
+	GtkUnit unit;
+
+	eog_debug (DEBUG_PRINTING);
+
+	data = (EogPrintData *)user_data;
+
+	eog_print_image_setup_get_options (EOG_PRINT_IMAGE_SETUP (widget),
+					   &left_margin, &top_margin,
+					   &scale_factor, &unit);
+
+	data->left_margin = left_margin;
+	data->top_margin = top_margin;
+	data->scale_factor = scale_factor;
+	data->unit = unit;
+}
+
+static void
+eog_print_end_print (GtkPrintOperation *operation,
+		     GtkPrintContext   *context,
+		     gpointer           user_data)
+{
+	EogPrintData *data = (EogPrintData*) user_data;
+
+	eog_debug (DEBUG_PRINTING);
+
+	g_object_unref (data->image);
+	g_slice_free (EogPrintData, data);
+}
+
+GtkPrintOperation *
+eog_print_operation_new (EogImage *image,
+			 GtkPrintSettings *print_settings,
+			 GtkPageSetup *page_setup)
+{
+	GtkPrintOperation *print;
+	EogPrintData *data;
+	gint width, height;
+
+	eog_debug (DEBUG_PRINTING);
+
+	print = gtk_print_operation_new ();
+
+	data = g_slice_new0 (EogPrintData);
+
+	data->left_margin = 0;
+	data->top_margin = 0;
+	data->scale_factor = 100;
+	data->image = g_object_ref (image);
+	data->unit = GTK_UNIT_INCH;
+
+	eog_image_get_size (image, &width, &height);
+
+	if (page_setup == NULL)
+		page_setup = gtk_page_setup_new ();
+
+	if (height >= width) {
+		gtk_page_setup_set_orientation (page_setup,
+						GTK_PAGE_ORIENTATION_PORTRAIT);
+	} else {
+		gtk_page_setup_set_orientation (page_setup,
+						GTK_PAGE_ORIENTATION_LANDSCAPE);
+	}
+
+	gtk_print_operation_set_print_settings (print, print_settings);
+	gtk_print_operation_set_default_page_setup (print,
+						    page_setup);
+	gtk_print_operation_set_n_pages (print, 1);
+	gtk_print_operation_set_job_name (print,
+					  eog_image_get_caption (image));
+	gtk_print_operation_set_embed_page_setup (print, TRUE);
+
+	g_signal_connect (print, "draw_page",
+			  G_CALLBACK (eog_print_draw_page),
+			  data);
+	g_signal_connect (print, "create-custom-widget",
+			  G_CALLBACK (eog_print_create_custom_widget),
+			  data);
+	g_signal_connect (print, "custom-widget-apply",
+			  G_CALLBACK (eog_print_custom_widget_apply),
+			  data);
+	g_signal_connect (print, "end-print",
+			  G_CALLBACK (eog_print_end_print),
+			  data);
+	g_signal_connect (print, "update-custom-widget",
+			  G_CALLBACK (eog_print_image_setup_update),
+			  data);
+
+	gtk_print_operation_set_custom_tab_label (print, _("Image Settings"));
+
+	return print;
+}
+
+static GKeyFile *
+eog_print_get_key_file (void)
+{
+	GKeyFile *key_file;
+	GError *error = NULL;
+	gchar *filename;
+	GFile *file;
+	const gchar *dot_dir = eog_util_dot_dir ();
+
+	filename = g_build_filename (dot_dir, EOG_PRINT_SETTINGS_FILE, NULL);
+
+	file = g_file_new_for_path (filename);
+	key_file = g_key_file_new ();
+
+	if (g_file_query_exists (file, NULL)) {
+		g_key_file_load_from_file (key_file, filename,
+					   G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS,
+					   &error);
+		if (error) {
+			g_warning ("Error loading print settings file: %s", error->message);
+			g_error_free (error);
+			g_object_unref (file);
+			g_free (filename);
+			g_key_file_free (key_file);
+			return NULL;
+		}
+	}
+
+	g_object_unref (file);
+	g_free (filename);
+
+	return key_file;
+}
+
+GtkPageSetup *
+eog_print_get_page_setup (void)
+{
+	GtkPageSetup *page_setup;
+	GKeyFile *key_file;
+	GError *error = NULL;
+
+	key_file = eog_print_get_key_file ();
+
+	if (key_file && g_key_file_has_group (key_file, EOG_PAGE_SETUP_GROUP)) {
+		page_setup = gtk_page_setup_new_from_key_file (key_file, EOG_PAGE_SETUP_GROUP, &error);
+	} else {
+		page_setup = gtk_page_setup_new ();
+	}
+
+	if (error) {
+		page_setup = gtk_page_setup_new ();
+
+		g_warning ("Error loading print settings file: %s", error->message);
+		g_error_free (error);
+	}
+
+	if (key_file)
+		g_key_file_free (key_file);
+
+	return page_setup;
+}
+
+static void
+eog_print_save_key_file (GKeyFile *key_file)
+{
+	gchar *filename;
+	gchar *data;
+	GError *error = NULL;
+	const gchar *dot_dir = eog_util_dot_dir ();
+
+	filename = g_build_filename (dot_dir, EOG_PRINT_SETTINGS_FILE, NULL);
+
+	data = g_key_file_to_data (key_file, NULL, NULL);
+
+	g_file_set_contents (filename, data, -1, &error);
+
+	if (error) {
+		g_warning ("Error saving print settings file: %s", error->message);
+		g_error_free (error);
+	}
+
+	g_free (filename);
+	g_free (data);
+}
+
+void
+eog_print_set_page_setup (GtkPageSetup *page_setup)
+{
+	GKeyFile *key_file;
+
+	key_file = eog_print_get_key_file ();
+
+	if (key_file == NULL) {
+		key_file = g_key_file_new ();
+	}
+
+	gtk_page_setup_to_key_file (page_setup, key_file, EOG_PAGE_SETUP_GROUP);
+	eog_print_save_key_file (key_file);
+
+	g_key_file_free (key_file);
+}
+
+GtkPrintSettings *
+eog_print_get_print_settings (void)
+{
+	GtkPrintSettings *print_settings;
+	GError *error = NULL;
+	GKeyFile *key_file;
+
+	key_file = eog_print_get_key_file ();
+
+	if (key_file && g_key_file_has_group (key_file, EOG_PRINT_SETTINGS_GROUP)) {
+		print_settings = gtk_print_settings_new_from_key_file (key_file, EOG_PRINT_SETTINGS_GROUP, &error);
+	} else {
+		print_settings = gtk_print_settings_new ();
+	}
+
+	if (error) {
+		print_settings = gtk_print_settings_new ();
+
+		g_warning ("Error loading print settings file: %s", error->message);
+		g_error_free (error);
+	}
+
+	if (key_file)
+		g_key_file_free (key_file);
+
+	return print_settings;
+}
+
+void
+eog_print_set_print_settings (GtkPrintSettings *print_settings)
+{
+	GKeyFile *key_file;
+
+	key_file = eog_print_get_key_file ();
+
+	if (key_file == NULL) {
+		key_file = g_key_file_new ();
+	}
+
+	gtk_print_settings_to_key_file (print_settings, key_file, EOG_PRINT_SETTINGS_GROUP);
+	eog_print_save_key_file (key_file);
+
+	g_key_file_free (key_file);
+}
diff --git a/src/eog-print.h b/src/eog-print.h
new file mode 100644
index 0000000..a0fedbb
--- /dev/null
+++ b/src/eog-print.h
@@ -0,0 +1,49 @@
+/* Eye of Gnome - Print Operations
+ *
+ * Copyright (C) 2005-2008 The Free Software Foundation
+ *
+ * Author: Claudio Saavedra <csaavedra gnome org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EOG_PRINT_H__
+#define __EOG_PRINT_H__
+
+#include "eog-image.h"
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+G_GNUC_INTERNAL
+GtkPrintOperation*    eog_print_operation_new (EogImage *image,
+					       GtkPrintSettings *print_settings,
+					       GtkPageSetup *page_setup);
+
+G_GNUC_INTERNAL
+GtkPageSetup*         eog_print_get_page_setup (void);
+
+G_GNUC_INTERNAL
+void                  eog_print_set_page_setup (GtkPageSetup *page_setup);
+
+G_GNUC_INTERNAL
+GtkPrintSettings *    eog_print_get_print_settings (void);
+
+G_GNUC_INTERNAL
+void                  eog_print_set_print_settings (GtkPrintSettings *print_settings);
+
+G_END_DECLS
+
+#endif /* __EOG_PRINT_H__ */
diff --git a/src/eog-transform.c b/src/eog-transform.c
new file mode 100644
index 0000000..bcd0d75
--- /dev/null
+++ b/src/eog-transform.c
@@ -0,0 +1,449 @@
+/* Eye Of GNOME -- Affine Transformations
+ *
+ * Copyright (C) 2003-2009 The Free Software Foundation
+ *
+ * Portions based on code from libart_lgpl by Raph Levien.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <time.h>
+#include <stdlib.h>
+#include <math.h>
+#include <gtk/gtk.h>
+#include <cairo/cairo.h>
+
+#include "eog-transform.h"
+#include "eog-jobs.h"
+
+/* The number of progress updates per transformation */
+#define EOG_TRANSFORM_N_PROG_UPDATES 20
+
+struct _EogTransformPrivate {
+	cairo_matrix_t affine;
+};
+
+typedef struct {
+	gdouble x;
+	gdouble y;
+} EogPoint;
+
+/* Convert degrees into radians */
+#define EOG_DEG_TO_RAD(degree) ((degree) * (G_PI/180.0)) 
+
+#define EOG_TRANSFORM_GET_PRIVATE(object) \
+	(G_TYPE_INSTANCE_GET_PRIVATE ((object), EOG_TYPE_TRANSFORM, EogTransformPrivate))
+
+G_DEFINE_TYPE (EogTransform, eog_transform, G_TYPE_OBJECT)
+
+static void
+eog_transform_init (EogTransform *trans)
+{
+	trans->priv = EOG_TRANSFORM_GET_PRIVATE (trans);
+}
+
+static void
+eog_transform_class_init (EogTransformClass *klass)
+{
+	g_type_class_add_private (klass, sizeof (EogTransformPrivate));
+}
+
+/**
+ * eog_transform_apply:
+ * @trans: a #EogTransform
+ * @pixbuf: a #GdkPixbuf
+ * @job: a #EogJob
+ *
+ * Applies the transformation in @trans to @pixbuf, setting its progress in @job.
+ *
+ * Returns: (transfer full): A new #GdkPixbuf with the transformation applied.
+ **/
+GdkPixbuf*
+eog_transform_apply (EogTransform *trans, GdkPixbuf *pixbuf, EogJob *job)
+{
+	EogPoint dest_top_left;
+	EogPoint dest_bottom_right;
+	EogPoint vertices[4] = { {0, 0}, {1, 0}, {1, 1}, {0, 1} };
+	double r_det;
+	int inverted [6];
+	EogPoint dest;
+
+	int src_width;
+	int src_height;
+	int src_rowstride;
+	int src_n_channels;
+	guchar *src_buffer;
+
+	GdkPixbuf *dest_pixbuf;
+	int dest_width;
+	int dest_height;
+	int dest_rowstride;
+	int dest_n_channels;
+	guchar *dest_buffer;
+
+	guchar *src_pos;
+	guchar *dest_pos;
+	int dx, dy, sx, sy;
+	int i, x, y;
+
+	int progress_delta;
+
+	g_return_val_if_fail (pixbuf != NULL, NULL);
+
+	g_object_ref (pixbuf);
+
+	src_width = gdk_pixbuf_get_width (pixbuf);
+	src_height = gdk_pixbuf_get_height (pixbuf);
+	src_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+	src_n_channels = gdk_pixbuf_get_n_channels (pixbuf);
+	src_buffer = gdk_pixbuf_get_pixels (pixbuf);
+
+	/* find out the dimension of the destination pixbuf */
+	dest_top_left.x = 100000;
+	dest_top_left.y = 100000;
+	dest_bottom_right.x = -100000;
+	dest_bottom_right.y = -100000;
+
+	for (i = 0; i < 4; i++) {
+		dest.x = vertices[i].x * (src_width - 1);
+		dest.y = vertices[i].y * (src_height -1);
+
+		cairo_matrix_transform_point (&trans->priv->affine,
+					      &dest.x, &dest.y);
+
+		dest_top_left.x = MIN (dest_top_left.x, dest.x);
+		dest_top_left.y = MIN (dest_top_left.y, dest.y);
+
+		dest_bottom_right.x = MAX (dest_bottom_right.x, dest.x);
+		dest_bottom_right.y = MAX (dest_bottom_right.y, dest.y);
+	}
+
+	/* create the resulting pixbuf */
+	dest_width = abs (dest_bottom_right.x - dest_top_left.x + 1);
+	dest_height = abs (dest_bottom_right.y - dest_top_left.y + 1);
+
+	dest_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+			       gdk_pixbuf_get_has_alpha (pixbuf),
+			       gdk_pixbuf_get_bits_per_sample (pixbuf),
+			       dest_width,
+			       dest_height);
+	dest_rowstride = gdk_pixbuf_get_rowstride (dest_pixbuf);
+	dest_n_channels = gdk_pixbuf_get_n_channels (dest_pixbuf);
+	dest_buffer = gdk_pixbuf_get_pixels (dest_pixbuf);
+
+	/* invert the matrix so that we can compute the source pixel
+	   from the target pixel and convert the values to integer
+	   ones (faster!)  FIXME: Maybe we can do some more
+	   improvements by using special mmx/3dnow features if
+	   available.
+	*/
+	r_det = 1.0 / (trans->priv->affine.xx * trans->priv->affine.yy - trans->priv->affine.yx * trans->priv->affine.xy);
+	inverted[0] =  trans->priv->affine.yy * r_det;
+	inverted[1] = -trans->priv->affine.yx * r_det;
+	inverted[2] = -trans->priv->affine.xy * r_det;
+	inverted[3] =  trans->priv->affine.xx * r_det;
+	inverted[4] = -trans->priv->affine.x0 * inverted[0] - trans->priv->affine.y0 * inverted[2];
+	inverted[5] = -trans->priv->affine.x0 * inverted[1] - trans->priv->affine.y0 * inverted[3];
+
+	progress_delta = MAX (1, dest_height / EOG_TRANSFORM_N_PROG_UPDATES);
+
+	/*
+	 * for every destination pixel (dx,dy) compute the source pixel (sx, sy)
+	 * and copy the color values
+	 */
+	for (y = 0, dy = dest_top_left.y; y < dest_height; y++, dy++) {
+		for (x = 0, dx = dest_top_left.x; x < dest_width; x++, dx++) {
+
+			sx = dx * inverted[0] + dy * inverted[2] + inverted[4];
+			sy = dx * inverted[1] + dy * inverted[3] + inverted[5];
+
+			if (sx >= 0 && sx < src_width && sy >= 0 && sy < src_height) {
+				src_pos  = src_buffer  + sy * src_rowstride  + sx * src_n_channels;
+				dest_pos = dest_buffer +  y * dest_rowstride +  x * dest_n_channels;
+
+				for (i = 0; i <  src_n_channels; i++) {
+					dest_pos[i] = src_pos[i];
+				}
+			}
+		}
+
+		if (job != NULL && y % progress_delta == 0) {
+			gfloat progress;
+
+			progress = (gfloat) (y + 1.0) / (gfloat) dest_height;
+
+			eog_job_set_progress (job, progress);
+		}
+	}
+
+	g_object_unref (pixbuf);
+
+	if (job != NULL) {
+		eog_job_set_progress (job, 1.0);
+	}
+
+	return dest_pixbuf;
+}
+
+static void
+_eog_cairo_matrix_copy (const cairo_matrix_t *src, cairo_matrix_t *dest)
+{
+	cairo_matrix_init (dest, src->xx, src->yx, src->xy, src->yy, src->x0, src->y0);
+}
+
+#define DOUBLE_EQUAL_MAX_DIFF 1e-6
+#define DOUBLE_EQUAL(a,b) (fabs (a - b) < DOUBLE_EQUAL_MAX_DIFF)
+/* art_affine_equal modified to work with cairo_matrix_t */
+static gboolean
+_eog_cairo_matrix_equal (const cairo_matrix_t *a, const cairo_matrix_t *b)
+{
+	return (DOUBLE_EQUAL (a->xx, b->xx) && DOUBLE_EQUAL (a->yx, b->yx) &&
+		DOUBLE_EQUAL (a->xy, b->xy) && DOUBLE_EQUAL (a->yy, b->yy) &&
+		DOUBLE_EQUAL (a->x0, b->x0) && DOUBLE_EQUAL (a->y0, b->y0) );
+}
+
+/* art_affine_flip modified to work with cairo_matrix_t */
+static void
+_eog_cairo_matrix_flip (cairo_matrix_t *dst, const cairo_matrix_t *src, gboolean horiz, gboolean vert)
+{
+	dst->xx = horiz ? -src->xx : src->xx;
+	dst->yx = horiz ? -src->yx : src->yx;
+	dst->xy = vert ? -src->xy : src->xy;
+	dst->yy = vert ? -src->yy : src->yy;
+	dst->x0 = horiz ? -src->x0 : src->x0;
+	dst->y0 = vert ? -src->y0 : src->y0;
+}
+
+/**
+ * eog_transform_reverse:
+ * @trans: a #EogTransform
+ *
+ * Creates the reverse transformation of @trans
+ *
+ * Returns: (transfer full): a new transformation
+ **/
+EogTransform*
+eog_transform_reverse (EogTransform *trans)
+{
+	EogTransform *reverse;
+
+	g_return_val_if_fail (EOG_IS_TRANSFORM (trans), NULL);
+
+	reverse = EOG_TRANSFORM (g_object_new (EOG_TYPE_TRANSFORM, NULL));
+
+	_eog_cairo_matrix_copy (&trans->priv->affine, &reverse->priv->affine);
+
+	g_return_val_if_fail (cairo_matrix_invert (&reverse->priv->affine) == CAIRO_STATUS_SUCCESS, reverse);
+
+	return reverse;
+}
+
+/**
+ * eog_transform_compose:
+ * @trans: a #EogTransform
+ * @compose: another #EogTransform
+ *
+ * 
+ *
+ * Returns: (transfer full): a new transform
+ **/
+EogTransform*
+eog_transform_compose (EogTransform *trans, EogTransform *compose)
+{
+	EogTransform *composition;
+
+	g_return_val_if_fail (EOG_IS_TRANSFORM (trans), NULL);
+	g_return_val_if_fail (EOG_IS_TRANSFORM (compose), NULL);
+
+	composition = EOG_TRANSFORM (g_object_new (EOG_TYPE_TRANSFORM, NULL));
+
+	cairo_matrix_multiply (&composition->priv->affine,
+			       &trans->priv->affine,
+			       &compose->priv->affine);
+
+	return composition;
+}
+
+gboolean
+eog_transform_is_identity (EogTransform *trans)
+{
+	static const cairo_matrix_t identity = { 1, 0, 0, 1, 0, 0 };
+
+	g_return_val_if_fail (EOG_IS_TRANSFORM (trans), FALSE);
+
+	return _eog_cairo_matrix_equal (&identity, &trans->priv->affine);
+}
+
+EogTransform*
+eog_transform_identity_new (void)
+{
+	EogTransform *trans;
+
+	trans = EOG_TRANSFORM (g_object_new (EOG_TYPE_TRANSFORM, NULL));
+
+	cairo_matrix_init_identity (&trans->priv->affine);
+
+	return trans;
+}
+
+EogTransform*
+eog_transform_rotate_new (int degree)
+{
+	EogTransform *trans;
+
+	trans = EOG_TRANSFORM (g_object_new (EOG_TYPE_TRANSFORM, NULL));
+
+	cairo_matrix_init_rotate (&trans->priv->affine, EOG_DEG_TO_RAD(degree));
+
+	return trans;
+}
+
+EogTransform*
+eog_transform_flip_new   (EogTransformType type)
+{
+	EogTransform *trans;
+	gboolean horiz, vert;
+
+	trans = EOG_TRANSFORM (g_object_new (EOG_TYPE_TRANSFORM, NULL));
+
+	cairo_matrix_init_identity (&trans->priv->affine);
+
+	horiz = (type == EOG_TRANSFORM_FLIP_HORIZONTAL);
+	vert = (type == EOG_TRANSFORM_FLIP_VERTICAL);
+
+	_eog_cairo_matrix_flip (&trans->priv->affine,
+				&trans->priv->affine,
+				horiz, vert);
+
+	return trans;
+}
+
+EogTransform*
+eog_transform_new (EogTransformType type)
+{
+	EogTransform *trans = NULL;
+	EogTransform *temp1 = NULL, *temp2 = NULL;
+
+	switch (type) {
+	case EOG_TRANSFORM_NONE:
+		trans = eog_transform_identity_new ();
+		break;
+	case EOG_TRANSFORM_FLIP_HORIZONTAL:
+		trans = eog_transform_flip_new (EOG_TRANSFORM_FLIP_HORIZONTAL);
+		break;
+	case EOG_TRANSFORM_ROT_180:
+		trans = eog_transform_rotate_new (180);
+		break;
+	case EOG_TRANSFORM_FLIP_VERTICAL:
+		trans = eog_transform_flip_new (EOG_TRANSFORM_FLIP_VERTICAL);
+		break;
+	case EOG_TRANSFORM_TRANSPOSE:
+		temp1 = eog_transform_rotate_new (90);
+		temp2 = eog_transform_flip_new (EOG_TRANSFORM_FLIP_HORIZONTAL);
+		trans = eog_transform_compose (temp1, temp2);
+		g_object_unref (temp1);
+		g_object_unref (temp2);
+		break;
+	case EOG_TRANSFORM_ROT_90:
+		trans = eog_transform_rotate_new (90);
+		break;
+	case EOG_TRANSFORM_TRANSVERSE:
+		temp1 = eog_transform_rotate_new (90);
+		temp2 = eog_transform_flip_new (EOG_TRANSFORM_FLIP_VERTICAL);
+		trans = eog_transform_compose (temp1, temp2);
+		g_object_unref (temp1);
+		g_object_unref (temp2);
+		break;
+	case EOG_TRANSFORM_ROT_270:
+		trans = eog_transform_rotate_new (270);
+		break;
+	default:
+		trans = eog_transform_identity_new ();
+		break;
+	}
+
+	return trans;
+}
+
+EogTransformType
+eog_transform_get_transform_type (EogTransform *trans)
+{
+	cairo_matrix_t affine, a1, a2;
+	EogTransformPrivate *priv;
+
+	g_return_val_if_fail (EOG_IS_TRANSFORM (trans), EOG_TRANSFORM_NONE);
+
+	priv = trans->priv;
+
+	cairo_matrix_init_rotate (&affine, EOG_DEG_TO_RAD(90));
+	if (_eog_cairo_matrix_equal (&affine, &priv->affine)) {
+		return EOG_TRANSFORM_ROT_90;
+	}
+
+	cairo_matrix_init_rotate (&affine, EOG_DEG_TO_RAD(180));
+	if (_eog_cairo_matrix_equal (&affine, &priv->affine)) {
+		return EOG_TRANSFORM_ROT_180;
+	}
+
+	cairo_matrix_init_rotate (&affine, EOG_DEG_TO_RAD(270));
+	if (_eog_cairo_matrix_equal (&affine, &priv->affine)) {
+		return EOG_TRANSFORM_ROT_270;
+	}
+
+	cairo_matrix_init_identity (&affine);
+	_eog_cairo_matrix_flip (&affine, &affine, TRUE, FALSE);
+	if (_eog_cairo_matrix_equal (&affine, &priv->affine)) {
+		return EOG_TRANSFORM_FLIP_HORIZONTAL;
+	}
+
+	cairo_matrix_init_identity (&affine);
+	_eog_cairo_matrix_flip (&affine, &affine, FALSE, TRUE);
+	if (_eog_cairo_matrix_equal (&affine, &priv->affine)) {
+		return EOG_TRANSFORM_FLIP_VERTICAL;
+	}
+
+	cairo_matrix_init_rotate (&a1, EOG_DEG_TO_RAD(90));
+	cairo_matrix_init_identity (&a2);
+	_eog_cairo_matrix_flip (&a2, &a2, TRUE, FALSE);
+	cairo_matrix_multiply(&affine, &a1, &a2);
+	if (_eog_cairo_matrix_equal (&affine, &priv->affine)) {
+		return EOG_TRANSFORM_TRANSPOSE;
+	}
+
+	/* A transversion is a 180Â rotation followed by a transposition */
+	/* Reuse the transposition from the previous step for this. */
+	cairo_matrix_init_rotate (&a1, EOG_DEG_TO_RAD(180));
+	cairo_matrix_multiply(&a2, &a1, &affine);
+	if (_eog_cairo_matrix_equal (&a2, &priv->affine)) {
+		return EOG_TRANSFORM_TRANSVERSE;
+	}
+
+	return EOG_TRANSFORM_NONE;
+}
+
+gboolean
+eog_transform_get_affine (EogTransform *trans, cairo_matrix_t *affine)
+{
+	g_return_val_if_fail (EOG_IS_TRANSFORM (trans), FALSE);
+
+	_eog_cairo_matrix_copy (&trans->priv->affine, affine);
+
+	return TRUE;
+}
+
diff --git a/src/eog-transform.h b/src/eog-transform.h
new file mode 100644
index 0000000..4dee8a3
--- /dev/null
+++ b/src/eog-transform.h
@@ -0,0 +1,76 @@
+#ifndef _EOG_TRANSFORM_H_
+#define _EOG_TRANSFORM_H_
+
+#include <cairo.h>
+#include <glib-object.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+G_BEGIN_DECLS
+
+#ifndef __EOG_JOB_DECLR__
+#define __EOG_JOB_DECLR__
+typedef struct _EogJob EogJob;
+#endif
+
+typedef enum {
+	EOG_TRANSFORM_NONE,
+	EOG_TRANSFORM_ROT_90,
+	EOG_TRANSFORM_ROT_180,
+	EOG_TRANSFORM_ROT_270,
+	EOG_TRANSFORM_FLIP_HORIZONTAL,
+	EOG_TRANSFORM_FLIP_VERTICAL,
+	EOG_TRANSFORM_TRANSPOSE,
+	EOG_TRANSFORM_TRANSVERSE
+} EogTransformType;
+
+#define EOG_TYPE_TRANSFORM          (eog_transform_get_type ())
+#define EOG_TRANSFORM(o)            (G_TYPE_CHECK_INSTANCE_CAST ((o), EOG_TYPE_TRANSFORM, EogTransform))
+#define EOG_TRANSFORM_CLASS(k)      (G_TYPE_CHECK_CLASS_CAST((k), EOG_TYPE_TRANSFORM, EogTransformClass))
+#define EOG_IS_TRANSFORM(o)         (G_TYPE_CHECK_INSTANCE_TYPE ((o), EOG_TYPE_TRANSFORM))
+#define EOG_IS_TRANSFORM_CLASS(k)   (G_TYPE_CHECK_CLASS_TYPE ((k), EOG_TYPE_TRANSFORM))
+#define EOG_TRANSFORM_GET_CLASS(o)  (G_TYPE_INSTANCE_GET_CLASS ((o), EOG_TYPE_TRANSFORM, EogTransformClass))
+
+/* =========================================
+
+    GObjecat wrapper around an affine transformation
+
+   ----------------------------------------*/
+
+typedef struct _EogTransform EogTransform;
+typedef struct _EogTransformClass EogTransformClass;
+typedef struct _EogTransformPrivate EogTransformPrivate;
+
+struct _EogTransform {
+	GObject parent;
+
+	EogTransformPrivate *priv;
+};
+
+struct _EogTransformClass {
+	GObjectClass parent_klass;
+};
+
+GType         eog_transform_get_type (void) G_GNUC_CONST;
+
+GdkPixbuf*    eog_transform_apply   (EogTransform *trans, GdkPixbuf *pixbuf, EogJob *job);
+EogTransform* eog_transform_reverse (EogTransform *trans);
+EogTransform* eog_transform_compose (EogTransform *trans, EogTransform *compose);
+gboolean      eog_transform_is_identity (EogTransform *trans);
+
+EogTransform* eog_transform_identity_new (void);
+EogTransform* eog_transform_rotate_new (int degree);
+EogTransform* eog_transform_flip_new   (EogTransformType type /* only EOG_TRANSFORM_FLIP_* are valid */);
+#if 0
+EogTransform* eog_transform_scale_new  (double sx, double sy);
+#endif
+EogTransform* eog_transform_new (EogTransformType trans);
+
+EogTransformType eog_transform_get_transform_type (EogTransform *trans);
+
+gboolean         eog_transform_get_affine (EogTransform *trans, cairo_matrix_t *affine);
+
+G_END_DECLS
+
+#endif /* _EOG_TRANSFORM_H_ */
+
+
diff --git a/src/eog-uri-converter.c b/src/eog-uri-converter.c
new file mode 100644
index 0000000..baa6923
--- /dev/null
+++ b/src/eog-uri-converter.c
@@ -0,0 +1,988 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+#include <string.h>
+#include <glib.h>
+
+#include "eog-uri-converter.h"
+#include "photos-utils.h"
+
+enum {
+	PROP_0,
+	PROP_CONVERT_SPACES,
+	PROP_SPACE_CHARACTER,
+	PROP_COUNTER_START,
+	PROP_COUNTER_N_DIGITS,
+	PROP_N_IMAGES
+};
+
+typedef struct {
+	EogUCType  type;
+	union {
+		char    *string;  /* if type == EOG_UC_STRING */
+		gulong  counter;  /* if type == EOG_UC_COUNTER */
+	} data;
+} EogUCToken;
+
+
+struct _EogURIConverterPrivate {
+	GFile           *base_file;
+	GList           *token_list;
+	char            *suffix;
+	GdkPixbufFormat *img_format;
+	gboolean        requires_exif;
+
+	/* options */
+	gboolean convert_spaces;
+	gchar    space_character;
+	gulong   counter_start;
+	guint    counter_n_digits;
+};
+
+static void eog_uri_converter_set_property (GObject      *object,
+					    guint         property_id,
+					    const GValue *value,
+					    GParamSpec   *pspec);
+
+static void eog_uri_converter_get_property (GObject    *object,
+					    guint       property_id,
+					    GValue     *value,
+					    GParamSpec *pspec);
+
+#define EOG_URI_CONVERTER_GET_PRIVATE(object) \
+	(G_TYPE_INSTANCE_GET_PRIVATE ((object), EOG_TYPE_URI_CONVERTER, EogURIConverterPrivate))
+
+G_DEFINE_TYPE (EogURIConverter, eog_uri_converter, G_TYPE_OBJECT)
+
+static void
+free_token (gpointer data)
+{
+	EogUCToken *token = (EogUCToken*) data;
+
+	if (token->type == EOG_UC_STRING) {
+		g_free (token->data.string);
+	}
+
+	g_slice_free (EogUCToken, token);
+}
+
+static void
+eog_uri_converter_dispose (GObject *object)
+{
+	EogURIConverter *instance = EOG_URI_CONVERTER (object);
+	EogURIConverterPrivate *priv;
+
+	priv = instance->priv;
+
+	if (priv->base_file) {
+		g_object_unref (priv->base_file);
+		priv->base_file = NULL;
+	}
+
+	if (priv->token_list) {
+		g_list_foreach (priv->token_list, (GFunc) free_token, NULL);
+		g_list_free (priv->token_list);
+		priv->token_list = NULL;
+	}
+
+	if (priv->suffix) {
+		g_free (priv->suffix);
+		priv->suffix = NULL;
+	}
+
+
+	G_OBJECT_CLASS (eog_uri_converter_parent_class)->dispose (object);
+}
+
+static void
+eog_uri_converter_init (EogURIConverter *obj)
+{
+	EogURIConverterPrivate *priv;
+
+	priv = obj->priv = EOG_URI_CONVERTER_GET_PRIVATE (obj);
+
+	priv->convert_spaces   = FALSE;
+	priv->space_character  = '_';
+	priv->counter_start    = 0;
+	priv->counter_n_digits = 1;
+	priv->requires_exif     = FALSE;
+}
+
+static void
+eog_uri_converter_class_init (EogURIConverterClass *klass)
+{
+	GObjectClass *object_class = (GObjectClass*) klass;
+
+	object_class->dispose = eog_uri_converter_dispose;
+
+        /* GObjectClass */
+        object_class->set_property = eog_uri_converter_set_property;
+        object_class->get_property = eog_uri_converter_get_property;
+
+        /* Properties */
+        g_object_class_install_property (
+                object_class,
+                PROP_CONVERT_SPACES,
+                g_param_spec_boolean ("convert-spaces", NULL, NULL,
+				      FALSE, G_PARAM_READWRITE));
+
+        g_object_class_install_property (
+                object_class,
+                PROP_SPACE_CHARACTER,
+                g_param_spec_char ("space-character", NULL, NULL,
+				   ' ', '~', '_', G_PARAM_READWRITE));
+
+       g_object_class_install_property (
+                object_class,
+                PROP_COUNTER_START,
+                g_param_spec_ulong ("counter-start", NULL, NULL,
+                                   0,
+                                   G_MAXULONG,
+                                   1,
+                                   G_PARAM_READWRITE));
+
+       g_object_class_install_property (
+                object_class,
+                PROP_COUNTER_N_DIGITS,
+                g_param_spec_uint ("counter-n-digits", NULL, NULL,
+				  1,
+				  G_MAXUINT,
+				  1,
+				  G_PARAM_READWRITE));
+
+
+       g_object_class_install_property (
+                object_class,
+                PROP_N_IMAGES,
+                g_param_spec_uint ("n-images", NULL, NULL,
+				  1,
+				  G_MAXUINT,
+				  1,
+				  G_PARAM_WRITABLE));
+
+	g_type_class_add_private (klass, sizeof (EogURIConverterPrivate));
+}
+
+GQuark
+eog_uc_error_quark (void)
+{
+	static GQuark q = 0;
+	if (q == 0)
+		q = g_quark_from_static_string ("eog-uri-converter-error-quark");
+
+	return q;
+}
+
+
+static void
+eog_uri_converter_set_property (GObject      *object,
+                                guint         property_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+	EogURIConverter *conv;
+	EogURIConverterPrivate *priv;
+
+        g_return_if_fail (EOG_IS_URI_CONVERTER (object));
+
+        conv = EOG_URI_CONVERTER (object);
+	priv = conv->priv;
+
+        switch (property_id)
+        {
+	case PROP_CONVERT_SPACES:
+		priv->convert_spaces = g_value_get_boolean (value);
+		break;
+
+	case PROP_SPACE_CHARACTER:
+		priv->space_character = g_value_get_schar (value);
+		break;
+
+	case PROP_COUNTER_START:
+	{
+		guint new_n_digits;
+
+		priv->counter_start = g_value_get_ulong (value);
+
+		new_n_digits = ceil (log10 (priv->counter_start + pow (10, priv->counter_n_digits) - 1));
+
+		if (new_n_digits != priv->counter_n_digits) {
+			priv->counter_n_digits = ceil (MIN (log10 (G_MAXULONG), new_n_digits));
+		}
+		break;
+	}
+
+	case PROP_COUNTER_N_DIGITS:
+		priv->counter_n_digits = ceil (MIN (log10 (G_MAXULONG), g_value_get_uint (value)));
+		break;
+
+	case PROP_N_IMAGES:
+		priv->counter_n_digits = ceil (MIN (log10 (G_MAXULONG),
+						    log10 (priv->counter_start + g_value_get_uint (value))));
+		break;
+
+        default:
+                g_assert_not_reached ();
+        }
+}
+
+static void
+eog_uri_converter_get_property (GObject    *object,
+                                guint       property_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+	EogURIConverter *conv;
+	EogURIConverterPrivate *priv;
+
+        g_return_if_fail (EOG_IS_URI_CONVERTER (object));
+
+        conv = EOG_URI_CONVERTER (object);
+	priv = conv->priv;
+
+        switch (property_id)
+        {
+	case PROP_CONVERT_SPACES:
+		g_value_set_boolean (value, priv->convert_spaces);
+		break;
+
+	case PROP_SPACE_CHARACTER:
+		g_value_set_schar (value, priv->space_character);
+		break;
+
+	case PROP_COUNTER_START:
+		g_value_set_ulong (value, priv->counter_start);
+		break;
+
+	case PROP_COUNTER_N_DIGITS:
+		g_value_set_uint (value, priv->counter_n_digits);
+		break;
+
+        default:
+                g_assert_not_reached ();
+	}
+}
+
+/* parser states */
+enum {
+	PARSER_NONE,
+	PARSER_STRING,
+	PARSER_TOKEN
+};
+
+static EogUCToken*
+create_token_string (const char *string, int substr_start, int substr_len)
+{
+	char *start_byte;
+	char *end_byte;
+	int n_bytes;
+	EogUCToken *token;
+
+	if (string == NULL) return NULL;
+	if (substr_len <= 0) return NULL;
+
+	start_byte = g_utf8_offset_to_pointer (string, substr_start);
+	end_byte = g_utf8_offset_to_pointer (string, substr_start + substr_len);
+
+	/* FIXME: is this right? */
+	n_bytes = end_byte - start_byte;
+
+	token = g_slice_new0 (EogUCToken);
+	token->type = EOG_UC_STRING;
+	token->data.string = g_new0 (char, n_bytes);
+	token->data.string = g_utf8_strncpy (token->data.string, start_byte, substr_len);
+
+	return token;
+}
+
+static EogUCToken*
+create_token_counter (int start_counter)
+{
+	EogUCToken *token;
+
+	token = g_slice_new0 (EogUCToken);
+	token->type = EOG_UC_COUNTER;
+	token->data.counter = 0;
+
+	return token;
+}
+
+static EogUCToken*
+create_token_other (EogUCType type)
+{
+	EogUCToken *token;
+
+	token = g_slice_new0 (EogUCToken);
+	token->type = type;
+
+	return token;
+}
+
+static GList*
+eog_uri_converter_parse_string (EogURIConverter *conv, const char *string)
+{
+	EogURIConverterPrivate *priv;
+	GList *list = NULL;
+	gulong len;
+	int i;
+	int state = PARSER_NONE;
+	int start = -1;
+	int substr_len = 0;
+	gunichar c;
+	const char *s;
+	EogUCToken *token;
+
+	g_return_val_if_fail (EOG_IS_URI_CONVERTER (conv), NULL);
+
+	priv = conv->priv;
+
+	if (string == NULL) return NULL;
+
+	if (!g_utf8_validate (string, -1, NULL))
+		return NULL;
+
+	len = g_utf8_strlen (string, -1);
+	s = string;
+
+	for (i = 0; i < len; i++) {
+		c = g_utf8_get_char (s);
+		token = NULL;
+
+		switch (state) {
+		case PARSER_NONE:
+			if (c == '%') {
+				start = -1;
+				state = PARSER_TOKEN;
+			} else {
+				start = i;
+				substr_len = 1;
+				state = PARSER_STRING;
+			}
+			break;
+
+		case PARSER_STRING:
+			if (c == '%') {
+				if (start != -1) {
+					token = create_token_string (string, start, substr_len);
+				}
+
+				state = PARSER_TOKEN;
+				start = -1;
+			} else {
+				substr_len++;
+			}
+			break;
+
+		case PARSER_TOKEN: {
+			EogUCType type = EOG_UC_END;
+
+			if (c == 'f') {
+				type = EOG_UC_FILENAME;
+			}
+			else if (c == 'n') {
+				type = EOG_UC_COUNTER;
+				token = create_token_counter (priv->counter_start);
+			}
+			else if (c == 'c') {
+				type = EOG_UC_COMMENT;
+			}
+			else if (c == 'd') {
+				type = EOG_UC_DATE;
+			}
+			else if (c == 't') {
+				type = EOG_UC_TIME;
+			}
+			else if (c == 'a') {
+				type = EOG_UC_DAY;
+			}
+			else if (c == 'm') {
+				type = EOG_UC_MONTH;
+			}
+			else if (c == 'y') {
+				type = EOG_UC_YEAR;
+			}
+			else if (c == 'h') {
+				type = EOG_UC_HOUR;
+			}
+			else if (c == 'i') {
+				type = EOG_UC_MINUTE;
+			}
+			else if (c == 's') {
+				type = EOG_UC_SECOND;
+			}
+
+			if (type != EOG_UC_END && token == NULL) {
+				token = create_token_other (type);
+				priv->requires_exif = TRUE;
+			}
+			state = PARSER_NONE;
+			break;
+		}
+		default:
+			g_assert_not_reached ();
+		}
+
+
+		if (token != NULL) {
+			list = g_list_append (list, token);
+		}
+
+		s = g_utf8_next_char (s);
+	}
+
+	if (state != PARSER_TOKEN && start >= 0) {
+		/* add remaining chars as string token */
+		list = g_list_append (list, create_token_string (string, start, substr_len));
+	}
+
+	return list;
+}
+
+void
+eog_uri_converter_print_list (EogURIConverter *conv)
+{
+	EogURIConverterPrivate *priv;
+	GList *it;
+
+	g_return_if_fail (EOG_URI_CONVERTER (conv));
+
+	priv = conv->priv;
+
+	for (it = priv->token_list; it != NULL; it = it->next) {
+		EogUCToken *token;
+		char *str;
+
+		token = (EogUCToken*) it->data;
+
+		switch (token->type) {
+		case EOG_UC_STRING:
+			str = g_strdup_printf ("string [%s]", token->data.string);
+			break;
+		case EOG_UC_FILENAME:
+			str = "filename";
+			break;
+		case EOG_UC_COUNTER:
+			str = g_strdup_printf ("counter [%lu]", token->data.counter);
+			break;
+		case EOG_UC_COMMENT:
+			str = "comment";
+			break;
+		case EOG_UC_DATE:
+			str = "date";
+			break;
+		case EOG_UC_TIME:
+			str = "time";
+			break;
+		case EOG_UC_DAY:
+			str = "day";
+			break;
+		case EOG_UC_MONTH:
+			str = "month";
+			break;
+		case EOG_UC_YEAR:
+			str = "year";
+			break;
+		case EOG_UC_HOUR:
+			str = "hour";
+			break;
+		case EOG_UC_MINUTE:
+			str = "minute";
+			break;
+		case EOG_UC_SECOND:
+			str = "second";
+			break;
+		default:
+			str = "unknown";
+			break;
+		}
+
+		g_print ("- %s\n", str);
+
+		if (token->type == EOG_UC_STRING || token->type == EOG_UC_COUNTER) {
+			g_free (str);
+		}
+	}
+}
+
+
+EogURIConverter*
+eog_uri_converter_new (GFile *base_file, GdkPixbufFormat *img_format, const char *format_str)
+{
+	EogURIConverter *conv;
+
+	g_return_val_if_fail (format_str != NULL, NULL);
+
+	conv = g_object_new (EOG_TYPE_URI_CONVERTER, NULL);
+
+	if (base_file != NULL) {
+		conv->priv->base_file  = g_object_ref (base_file);
+	}
+	else {
+		conv->priv->base_file = NULL;
+	}
+	conv->priv->img_format = img_format;
+	conv->priv->token_list = eog_uri_converter_parse_string (conv, format_str);
+
+	return conv;
+}
+
+static GFile*
+get_file_directory (EogURIConverter *conv, EogImage *image)
+{
+	GFile *file = NULL;
+	EogURIConverterPrivate *priv;
+
+	g_return_val_if_fail (EOG_IS_URI_CONVERTER (conv), NULL);
+	g_return_val_if_fail (EOG_IS_IMAGE (image), NULL);
+
+	priv = conv->priv;
+
+	if (priv->base_file != NULL) {
+		file = g_object_ref (priv->base_file);
+	}
+	else {
+		GFile *img_file;
+
+		img_file = eog_image_get_file (image);
+		g_assert (img_file != NULL);
+
+		file = g_file_get_parent (img_file);
+
+		g_object_unref (img_file);
+	}
+
+	return file;
+}
+
+static void
+split_filename (GFile *file, char **name, char **suffix)
+{
+	char *basename;
+	char *suffix_start;
+	guint len;
+
+	*name = NULL;
+	*suffix = NULL;
+
+        /* get unescaped string */
+	basename = g_file_get_basename (file);
+
+	/* FIXME: does this work for all locales? */
+	suffix_start = g_utf8_strrchr (basename, -1, '.');
+
+	if (suffix_start == NULL) { /* no suffix found */
+		*name = g_strdup (basename);
+	}
+	else {
+		len = (suffix_start - basename);
+		*name = g_strndup (basename, len);
+
+		len = strlen (basename) - len - 1;
+		*suffix = g_strndup (suffix_start+1, len);
+	}
+
+	g_free (basename);
+}
+
+static GString*
+append_filename (GString *str, EogImage *img)
+{
+	/* appends the name of the original file without
+	   filetype suffix */
+	GFile *img_file;
+	char *name;
+	char *suffix;
+	GString *result;
+
+	img_file = eog_image_get_file (img);
+	split_filename (img_file, &name, &suffix);
+
+	result = g_string_append (str, name);
+
+	g_free (name);
+	g_free (suffix);
+
+	g_object_unref (img_file);
+
+	return result;
+}
+
+static GString*
+append_counter (GString *str, gulong counter,  EogURIConverter *conv)
+{
+	EogURIConverterPrivate *priv;
+
+	priv = conv->priv;
+
+	g_string_append_printf (str, "%.*lu", priv->counter_n_digits, counter);
+
+	return str;
+}
+
+
+static void
+build_absolute_file (EogURIConverter *conv, EogImage *image, GString *str,  /* input  */
+		     GFile **file, GdkPixbufFormat **format)                /* output */
+{
+	GFile *dir_file;
+	EogURIConverterPrivate *priv;
+
+	*file = NULL;
+	if (format != NULL)
+		*format = NULL;
+
+	g_return_if_fail (EOG_IS_URI_CONVERTER (conv));
+	g_return_if_fail (EOG_IS_IMAGE (image));
+	g_return_if_fail (file != NULL);
+	g_return_if_fail (str != NULL);
+
+	priv = conv->priv;
+
+	dir_file = get_file_directory (conv, image);
+	g_assert (dir_file != NULL);
+
+	if (priv->img_format == NULL) {
+		/* use same file type/suffix */
+		char *name;
+		char *old_suffix;
+		GFile *img_file;
+
+		img_file = eog_image_get_file (image);
+		split_filename (img_file, &name, &old_suffix);
+
+		g_assert (old_suffix != NULL);
+
+		g_string_append_unichar (str, '.');
+		g_string_append (str, old_suffix);
+
+		if (format != NULL)
+			*format = photos_utils_get_pixbuf_format_by_suffix (old_suffix);
+
+		g_object_unref (img_file);
+	} else {
+		if (priv->suffix == NULL)
+			priv->suffix = photos_utils_get_pixbuf_common_suffix (priv->img_format);
+
+		g_string_append_unichar (str, '.');
+		g_string_append (str, priv->suffix);
+
+		if (format != NULL)
+			*format = priv->img_format;
+	}
+
+	*file = g_file_get_child (dir_file, str->str);
+
+	g_object_unref (dir_file);
+}
+
+
+static GString*
+replace_remove_chars (GString *str, gboolean convert_spaces, gunichar space_char)
+{
+	GString *result;
+	guint len;
+	char *s;
+	int i;
+	gunichar c;
+
+	g_return_val_if_fail (str != NULL, NULL);
+
+	if (!g_utf8_validate (str->str, -1, NULL))
+	    return NULL;
+
+	result = g_string_new (NULL);
+
+	len = g_utf8_strlen (str->str, -1);
+	s = str->str;
+
+	for (i = 0; i < len; i++, s = g_utf8_next_char (s)) {
+		c = g_utf8_get_char (s);
+
+		if (c == '/') {
+			continue;
+		}
+		else if (g_unichar_isspace (c) && convert_spaces) {
+			result = g_string_append_unichar (result, space_char);
+		}
+		else {
+			result = g_string_append_unichar (result, c);
+		}
+	}
+
+	/* ensure maximum length of 250 characters */
+	len = MIN (result->len, 250);
+	result = g_string_truncate (result, len);
+
+	return result;
+}
+
+/*
+ * This function converts the uri of the EogImage object, according to the
+ * EogUCToken list. The absolute uri (converted filename appended to base uri)
+ * is returned in uri and the image format will be in the format pointer.
+ */
+gboolean
+eog_uri_converter_do (EogURIConverter *conv, EogImage *image,
+		      GFile **file, GdkPixbufFormat **format, GError **error)
+{
+	EogURIConverterPrivate *priv;
+	GList *it;
+	GString *str;
+	GString *repl_str;
+
+	g_return_val_if_fail (EOG_IS_URI_CONVERTER (conv), FALSE);
+
+	priv = conv->priv;
+
+	*file = NULL;
+	if (format != NULL)
+		*format = NULL;
+
+	str = g_string_new ("");
+
+	for (it = priv->token_list; it != NULL; it = it->next) {
+		EogUCToken *token = (EogUCToken*) it->data;
+
+		switch (token->type) {
+		case EOG_UC_STRING:
+			str = g_string_append (str, token->data.string);
+			break;
+
+		case EOG_UC_FILENAME:
+			str = append_filename (str, image);
+			break;
+
+		case EOG_UC_COUNTER: {
+			if (token->data.counter < priv->counter_start)
+				token->data.counter = priv->counter_start;
+
+			str = append_counter (str, token->data.counter++, conv);
+			break;
+		}
+#if 0
+		case EOG_UC_COMMENT:
+			str = g_string_append_printf ();
+			str = "comment";
+			break;
+		case EOG_UC_DATE:
+			str = "date";
+			break;
+		case EOG_UC_TIME:
+			str = "time";
+			break;
+		case EOG_UC_DAY:
+			str = "day";
+			break;
+		case EOG_UC_MONTH:
+			str = "month";
+			break;
+		case EOG_UC_YEAR:
+			str = "year";
+			break;
+		case EOG_UC_HOUR:
+			str = "hour";
+			break;
+		case EOG_UC_MINUTE:
+			str = "minute";
+			break;
+		case EOG_UC_SECOND:
+			str = "second";
+			break;
+#endif
+		default:
+		/* skip all others */
+
+			break;
+		}
+	}
+
+	repl_str = replace_remove_chars (str, priv->convert_spaces, priv->space_character);
+
+	if (repl_str->len > 0) {
+		build_absolute_file (conv, image, repl_str, file, format);
+	}
+
+	g_string_free (repl_str, TRUE);
+	g_string_free (str, TRUE);
+
+
+	return (*file != NULL);
+}
+
+
+char*
+eog_uri_converter_preview (const char *format_str, EogImage *img, GdkPixbufFormat *format,
+			   gulong counter, guint n_images,
+			   gboolean convert_spaces, gunichar space_char)
+{
+	GString *str;
+	GString *repl_str;
+	guint n_digits;
+	guint len;
+	int i;
+	const char *s;
+	gunichar c;
+	char *filename;
+	gboolean token_next;
+
+	g_return_val_if_fail (format_str != NULL, NULL);
+	g_return_val_if_fail (EOG_IS_IMAGE (img), NULL);
+
+	if (n_images == 0) return NULL;
+
+	n_digits = ceil (MIN (log10 (G_MAXULONG), MAX (log10 (counter), log10 (n_images))));
+
+	str = g_string_new ("");
+
+	if (!g_utf8_validate (format_str, -1, NULL))
+	    return NULL;
+
+	len = g_utf8_strlen (format_str, -1);
+	s = format_str;
+	token_next = FALSE;
+
+	for (i = 0; i < len; i++, s = g_utf8_next_char (s)) {
+		c = g_utf8_get_char (s);
+
+		if (token_next) {
+			if (c == 'f') {
+				str = append_filename (str, img);
+			}
+			else if (c == 'n') {
+				g_string_append_printf (str, "%.*lu",
+							n_digits ,counter);
+
+			}
+#if 0                   /* ignore the rest for now */
+			else if (c == 'c') {
+				type = EOG_UC_COMMENT;
+			}
+			else if (c == 'd') {
+				type = EOG_UC_DATE;
+			}
+			else if (c == 't') {
+				type = EOG_UC_TIME;
+			}
+			else if (c == 'a') {
+				type = EOG_UC_DAY;
+			}
+			else if (c == 'm') {
+				type = EOG_UC_MONTH;
+			}
+			else if (c == 'y') {
+				type = EOG_UC_YEAR;
+			}
+			else if (c == 'h') {
+				type = EOG_UC_HOUR;
+			}
+			else if (c == 'i') {
+				type = EOG_UC_MINUTE;
+			}
+			else if (c == 's') {
+				type = EOG_UC_SECOND;
+			}
+#endif
+			token_next = FALSE;
+		}
+		else if (c == '%') {
+			token_next = TRUE;
+		}
+		else {
+			str = g_string_append_unichar (str, c);
+		}
+	}
+
+
+	filename = NULL;
+	repl_str = replace_remove_chars (str, convert_spaces, space_char);
+
+	if (repl_str->len > 0) {
+		if (format == NULL) {
+			/* use same file type/suffix */
+			char *name;
+			char *old_suffix;
+			GFile *img_file;
+
+			img_file = eog_image_get_file (img);
+			split_filename (img_file, &name, &old_suffix);
+
+			g_assert (old_suffix != NULL);
+
+			g_string_append_unichar (repl_str, '.');
+			g_string_append (repl_str, old_suffix);
+
+			g_free (old_suffix);
+			g_free (name);
+			g_object_unref (img_file);
+		}
+		else {
+			char *suffix = photos_utils_get_pixbuf_common_suffix (format);
+
+			g_string_append_unichar (repl_str, '.');
+			g_string_append (repl_str, suffix);
+
+			g_free (suffix);
+		}
+
+		filename = repl_str->str;
+	}
+
+	g_string_free (repl_str, FALSE);
+	g_string_free (str, TRUE);
+
+	return filename;
+}
+
+gboolean
+eog_uri_converter_requires_exif (EogURIConverter *converter)
+{
+	g_return_val_if_fail (EOG_IS_URI_CONVERTER (converter), FALSE);
+
+	return converter->priv->requires_exif;
+}
+
+gboolean
+eog_uri_converter_check (EogURIConverter *converter, GList *img_list, GError **error)
+{
+	GList *it;
+	GList *file_list = NULL;
+	gboolean all_different = TRUE;
+
+	g_return_val_if_fail (EOG_IS_URI_CONVERTER (converter), FALSE);
+
+	/* convert all image uris */
+	for (it = img_list; it != NULL; it = it->next) {
+		gboolean result;
+		GFile *file;
+		GError *conv_error = NULL;
+
+		result = eog_uri_converter_do (converter, EOG_IMAGE (it->data),
+					       &file, NULL, &conv_error);
+
+		if (result) {
+			file_list = g_list_prepend (file_list, file);
+		}
+	}
+
+	/* check for all different uris */
+	for (it = file_list; it != NULL && all_different; it = it->next) {
+		GList *p;
+		GFile *file;
+
+		file = (GFile*) it->data;
+
+		for (p = it->next; p != NULL && all_different; p = p->next) {
+			all_different = !g_file_equal (file, (GFile*) p->data);
+		}
+	}
+
+	if (!all_different) {
+		g_set_error (error, EOG_UC_ERROR,
+			     EOG_UC_ERROR_EQUAL_FILENAMES,
+			     _("At least two file names are equal."));
+	}
+
+	return all_different;
+}
diff --git a/src/eog-uri-converter.h b/src/eog-uri-converter.h
new file mode 100644
index 0000000..21b5505
--- /dev/null
+++ b/src/eog-uri-converter.h
@@ -0,0 +1,107 @@
+#ifndef _EOG_URI_CONVERTER_H_
+#define _EOG_URI_CONVERTER_H_
+
+#include <glib-object.h>
+#include <glib/gi18n.h>
+#include "eog-image.h"
+
+G_BEGIN_DECLS
+
+#define EOG_TYPE_URI_CONVERTER          (eog_uri_converter_get_type ())
+#define EOG_URI_CONVERTER(o)            (G_TYPE_CHECK_INSTANCE_CAST ((o), EOG_TYPE_URI_CONVERTER, EogURIConverter))
+#define EOG_URI_CONVERTER_CLASS(k)      (G_TYPE_CHECK_CLASS_CAST((k), EOG_TYPE_URI_CONVERTER, EogURIConverterClass))
+#define EOG_IS_URI_CONVERTER(o)         (G_TYPE_CHECK_INSTANCE_TYPE ((o), EOG_TYPE_URI_CONVERTER))
+#define EOG_IS_URI_CONVERTER_CLASS(k)   (G_TYPE_CHECK_CLASS_TYPE ((k), EOG_TYPE_URI_CONVERTER))
+#define EOG_URI_CONVERTER_GET_CLASS(o)  (G_TYPE_INSTANCE_GET_CLASS ((o), EOG_TYPE_URI_CONVERTER, EogURIConverterClass))
+
+#ifndef __EOG_URI_CONVERTER_DECLR__
+#define __EOG_URI_CONVERTER_DECLR__
+typedef struct _EogURIConverter EogURIConverter;
+#endif
+typedef struct _EogURIConverterClass EogURIConverterClass;
+typedef struct _EogURIConverterPrivate EogURIConverterPrivate;
+
+typedef enum {
+	EOG_UC_STRING,
+	EOG_UC_FILENAME,
+	EOG_UC_COUNTER,
+	EOG_UC_COMMENT,
+	EOG_UC_DATE,
+	EOG_UC_TIME,
+	EOG_UC_DAY,
+	EOG_UC_MONTH,
+	EOG_UC_YEAR,
+	EOG_UC_HOUR,
+	EOG_UC_MINUTE,
+	EOG_UC_SECOND,
+	EOG_UC_END
+} EogUCType;
+
+typedef struct {
+	char     *description;
+	char     *rep;
+	gboolean req_exif;
+} EogUCInfo;
+
+typedef enum {
+	EOG_UC_ERROR_INVALID_UNICODE,
+	EOG_UC_ERROR_INVALID_CHARACTER,
+	EOG_UC_ERROR_EQUAL_FILENAMES,
+	EOG_UC_ERROR_UNKNOWN
+} EogUCError;
+
+#define EOG_UC_ERROR eog_uc_error_quark ()
+
+
+struct _EogURIConverter {
+	GObject parent;
+
+	EogURIConverterPrivate *priv;
+};
+
+struct _EogURIConverterClass {
+	GObjectClass parent_klass;
+};
+
+G_GNUC_INTERNAL
+GType              eog_uri_converter_get_type      (void) G_GNUC_CONST;
+
+G_GNUC_INTERNAL
+GQuark             eog_uc_error_quark              (void);
+
+G_GNUC_INTERNAL
+EogURIConverter*   eog_uri_converter_new           (GFile *base_file,
+                                                    GdkPixbufFormat *img_format,
+						    const char *format_string);
+
+G_GNUC_INTERNAL
+gboolean           eog_uri_converter_check         (EogURIConverter *converter,
+                                                    GList *img_list,
+                                                    GError **error);
+
+G_GNUC_INTERNAL
+gboolean           eog_uri_converter_requires_exif (EogURIConverter *converter);
+
+G_GNUC_INTERNAL
+gboolean           eog_uri_converter_do            (EogURIConverter *converter,
+                                                    EogImage *image,
+                                                    GFile **file,
+                                                    GdkPixbufFormat **format,
+                                                    GError **error);
+
+G_GNUC_INTERNAL
+char*              eog_uri_converter_preview       (const char *format_str,
+                                                    EogImage *img,
+                                                    GdkPixbufFormat *format,
+						    gulong counter,
+						    guint n_images,
+						    gboolean convert_spaces,
+						    gunichar space_char);
+
+/* for debugging purpose only */
+G_GNUC_INTERNAL
+void                eog_uri_converter_print_list (EogURIConverter *conv);
+
+G_END_DECLS
+
+#endif /* _EOG_URI_CONVERTER_H_ */
diff --git a/src/eog-util.c b/src/eog-util.c
new file mode 100644
index 0000000..0460aa1
--- /dev/null
+++ b/src/eog-util.c
@@ -0,0 +1,506 @@
+/* Eye Of Gnome - General Utilities
+ *
+ * Copyright (C) 2006 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ *
+ * Based on code by:
+ *	- Jens Finke <jens gnome org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/time.h>
+#ifdef HAVE_STRPTIME
+#define _XOPEN_SOURCE
+#endif /* HAVE_STRPTIME */
+
+#include <time.h>
+
+#include "eog-util.h"
+#include "eog-debug.h"
+
+#include <errno.h>
+#include <string.h>
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+
+void
+eog_util_show_help (const gchar *section, GtkWindow *parent)
+{
+	GError *error = NULL;
+	gchar *uri = NULL;
+
+	if (section)
+		uri = g_strdup_printf ("help:eog/%s", section);
+
+	gtk_show_uri (NULL, ((uri != NULL) ? uri : "help:eog"),
+		      gtk_get_current_event_time (), &error);
+
+	g_free (uri);
+
+	if (error) {
+		GtkWidget *dialog;
+
+		dialog = gtk_message_dialog_new (parent,
+						 0,
+						 GTK_MESSAGE_ERROR,
+						 GTK_BUTTONS_OK,
+						 _("Could not display help for Image Viewer"));
+
+		gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+							  "%s", error->message);
+
+		g_signal_connect_swapped (dialog, "response",
+					  G_CALLBACK (gtk_widget_destroy),
+					  dialog);
+		gtk_widget_show (dialog);
+
+		g_error_free (error);
+	}
+}
+
+gchar *
+eog_util_make_valid_utf8 (const gchar *str)
+{
+	GString *string;
+	const char *remainder, *invalid;
+	int remaining_bytes, valid_bytes;
+
+	string = NULL;
+	remainder = str;
+	remaining_bytes = strlen (str);
+
+	while (remaining_bytes != 0) {
+		if (g_utf8_validate (remainder, remaining_bytes, &invalid)) {
+			break;
+		}
+
+		valid_bytes = invalid - remainder;
+
+		if (string == NULL) {
+			string = g_string_sized_new (remaining_bytes);
+		}
+
+		g_string_append_len (string, remainder, valid_bytes);
+		g_string_append_c (string, '?');
+
+		remaining_bytes -= valid_bytes + 1;
+		remainder = invalid + 1;
+	}
+
+	if (string == NULL) {
+		return g_strdup (str);
+	}
+
+	g_string_append (string, remainder);
+	g_string_append (string, _(" (invalid Unicode)"));
+
+	g_assert (g_utf8_validate (string->str, -1, NULL));
+
+	return g_string_free (string, FALSE);
+}
+
+GSList*
+eog_util_parse_uri_string_list_to_file_list (const gchar *uri_list)
+{
+	GSList* file_list = NULL;
+	gsize i = 0;
+	gchar **uris;
+
+	uris = g_uri_list_extract_uris (uri_list);
+
+	while (uris[i] != NULL) {
+		file_list = g_slist_append (file_list, g_file_new_for_uri (uris[i]));
+		i++;
+	}
+
+	g_strfreev (uris);
+
+	return file_list;
+}
+
+GSList*
+eog_util_string_list_to_file_list (GSList *string_list)
+{
+	GSList *it = NULL;
+	GSList *file_list = NULL;
+
+	for (it = string_list; it != NULL; it = it->next) {
+		char *uri_str;
+
+		uri_str = (gchar *) it->data;
+
+		file_list = g_slist_prepend (file_list,
+					     g_file_new_for_uri (uri_str));
+	}
+
+	return g_slist_reverse (file_list);
+}
+
+GSList*
+eog_util_strings_to_file_list (gchar **strings)
+{
+	int i;
+ 	GSList *file_list = NULL;
+
+	for (i = 0; strings[i]; i++) {
+ 		file_list = g_slist_prepend (file_list,
+					      g_file_new_for_uri (strings[i]));
+ 	}
+
+ 	return g_slist_reverse (file_list);
+}
+
+GSList*
+eog_util_string_array_to_list (const gchar **files, gboolean create_uri)
+{
+	gint i;
+	GSList *list = NULL;
+
+	if (files == NULL) return list;
+
+	for (i = 0; files[i]; i++) {
+		char *str;
+
+		if (create_uri) {
+			GFile *file;
+
+			file = g_file_new_for_commandline_arg (files[i]);
+			str = g_file_get_uri (file);
+
+			g_object_unref (file);
+		} else {
+			str = g_strdup (files[i]);
+		}
+
+		if (str) {
+			list = g_slist_prepend (list, g_strdup (str));
+			g_free (str);
+		}
+	}
+
+	return g_slist_reverse (list);
+}
+
+gchar **
+eog_util_string_array_make_absolute (gchar **files)
+{
+	int i;
+	int size;
+	gchar **abs_files;
+	GFile *file;
+
+	if (files == NULL)
+		return NULL;
+
+	size = g_strv_length (files);
+
+	/* Ensure new list is NULL-terminated */
+	abs_files = g_new0 (gchar *, size+1);
+
+	for (i = 0; i < size; i++) {
+		file = g_file_new_for_commandline_arg (files[i]);
+		abs_files[i] = g_file_get_uri (file);
+
+		g_object_unref (file);
+	}
+
+	return abs_files;
+}
+
+static gchar *dot_dir = NULL;
+static void migrate_config_folder (const gchar* new_folder);
+
+static gboolean
+ensure_dir_exists (const char *dir)
+{
+	if (g_file_test (dir, G_FILE_TEST_IS_DIR))
+		return TRUE;
+
+	if (g_mkdir_with_parents (dir, 0700) == 0) {
+		/* If the folder is created try migrating from the old folder */
+		migrate_config_folder (dir);
+		return TRUE;
+	}
+
+	if (errno == EEXIST)
+		return g_file_test (dir, G_FILE_TEST_IS_DIR);
+
+	g_warning ("Failed to create directory %s: %s", dir, strerror (errno));
+	return FALSE;
+}
+
+const gchar *
+eog_util_dot_dir (void)
+{
+	if (dot_dir == NULL) {
+		gboolean exists;
+
+		dot_dir = g_build_filename (g_get_user_config_dir (),
+					    "eog", NULL);
+
+		exists = ensure_dir_exists (dot_dir);
+		if (G_UNLIKELY (!exists)) {
+			static gboolean printed_warning = FALSE;
+
+			if (!printed_warning) {
+				g_warning ("EOG could not save some of your preferences in its settings directory due to a file with the same name (%s) blocking its creation. Please remove that file, or move it away.", dot_dir);
+				printed_warning = TRUE;
+			}
+			g_free (dot_dir);
+			dot_dir = NULL;
+			return NULL;
+		}
+	}
+
+	return dot_dir;
+}
+
+static void migrate_config_file (const gchar *old_filename, const gchar* new_filename)
+{
+	GFile *old_file, *new_file;
+	GError *error = NULL;
+
+	if (!g_file_test (old_filename, G_FILE_TEST_IS_REGULAR))
+		return;
+
+	old_file = g_file_new_for_path (old_filename);
+	new_file = g_file_new_for_path (new_filename);
+
+	if (!g_file_move (old_file, new_file, G_FILE_COPY_NONE, NULL, NULL, NULL, &error)) {
+		g_warning ("Could not migrate config file %s: %s\n", old_filename, error->message);
+		g_error_free (error);
+	}
+	g_object_unref (new_file);
+	g_object_unref (old_file);
+}
+
+static void migrate_config_folder (const gchar* new_dir)
+{
+	gchar* old_dir = g_build_filename (g_get_home_dir (), ".gnome2",
+					   "eog", NULL);
+	gchar* old_filename = NULL;
+	gchar* new_filename = NULL;
+	GError *error = NULL;
+	GFile *dir_file = NULL;
+	gsize i;
+	static const gchar *old_files[] = { "eog-print-settings.ini",
+					    "eog_toolbar.xml",
+					    NULL };
+
+	if(!g_file_test (old_dir, G_FILE_TEST_IS_DIR)) {
+		/* Nothing to migrate */
+		g_free (old_dir);
+		return;
+	}
+
+	eog_debug (DEBUG_PREFERENCES);
+
+	for (i = 0; old_files[i] != NULL; i++) {
+		old_filename = g_build_filename (old_dir,
+						old_files[i], NULL);
+		new_filename = g_build_filename (new_dir,
+						old_files[i], NULL);
+
+		migrate_config_file (old_filename, new_filename);
+
+		g_free (new_filename);
+		g_free (old_filename);
+	}
+
+	/* Migrate accels file */
+	old_filename = g_build_filename (g_get_home_dir (), ".gnome2",
+					 "accels", "eog", NULL);
+	/* move file to ~/.config/eog/accels if its not already there */
+	new_filename = g_build_filename (new_dir, "accels", NULL);
+
+	migrate_config_file (old_filename, new_filename);
+
+	g_free (new_filename);
+	g_free (old_filename);
+
+	dir_file = g_file_new_for_path (old_dir);
+	if (!g_file_delete (dir_file, NULL, &error)) {
+		g_warning ("An error occured while deleting the old config folder %s: %s\n", old_dir, error->message);
+		g_error_free (error);
+	}
+	g_object_unref (dir_file);
+	g_free(old_dir);
+}
+
+/* Based on eel_filename_strip_extension() */
+
+/**
+ * eog_util_filename_get_extension:
+ * @filename: a filename
+ *
+ * Returns a reasonably good guess of the file extension of @filename.
+ *
+ * Returns: a newly allocated string with the file extension of @filename.
+ **/
+char *
+eog_util_filename_get_extension (const char * filename)
+{
+	char *begin, *begin2;
+
+	if (filename == NULL) {
+		return NULL;
+	}
+
+	begin = strrchr (filename, '.');
+
+	if (begin && begin != filename) {
+		if (strcmp (begin, ".gz") == 0 ||
+		    strcmp (begin, ".bz2") == 0 ||
+		    strcmp (begin, ".sit") == 0 ||
+		    strcmp (begin, ".Z") == 0) {
+			begin2 = begin - 1;
+			while (begin2 > filename &&
+			       *begin2 != '.') {
+				begin2--;
+			}
+			if (begin2 != filename) {
+				begin = begin2;
+			}
+		}
+		begin ++;
+	} else {
+		return NULL;
+	}
+
+	return g_strdup (begin);
+}
+
+
+/**
+ * eog_util_file_is_persistent:
+ * @file: a #GFile
+ *
+ * Checks whether @file is a non-removable local mount.
+ *
+ * Returns: %TRUE if @file is in a non-removable mount,
+ * %FALSE otherwise or when it is remote.
+ **/
+gboolean
+eog_util_file_is_persistent (GFile *file)
+{
+	GMount *mount;
+
+	if (!g_file_is_native (file))
+		return FALSE;
+
+	mount = g_file_find_enclosing_mount (file, NULL, NULL);
+	if (mount) {
+		if (g_mount_can_unmount (mount)) {
+			return FALSE;
+		}
+	}
+
+	return TRUE;
+}
+
+static void
+_eog_util_show_file_in_filemanager_fallback (GFile *file, GdkScreen *screen)
+{
+	gchar *uri = NULL;
+	GError *error = NULL;
+	guint32 timestamp = gtk_get_current_event_time ();
+
+	if (g_file_query_file_type (file, 0, NULL) == G_FILE_TYPE_DIRECTORY) {
+		uri = g_file_get_uri (file);
+	} else {
+		/* If input file is not a directory we must open it's
+		   folder/parent to avoid opening the file itself     */
+		GFile *parent_file;
+
+		parent_file = g_file_get_parent (file);
+		if (G_LIKELY (parent_file))
+			uri = g_file_get_uri (parent_file);
+		g_object_unref (parent_file);
+	}
+
+	if (uri && !gtk_show_uri (screen, uri, timestamp, &error)) {
+		g_warning ("Couldn't show containing folder \"%s\": %s", uri,
+			   error->message);
+		g_error_free (error);
+	}
+
+	g_free (uri);
+}
+
+void
+eog_util_show_file_in_filemanager (GFile *file, GdkScreen *screen)
+{
+	GDBusProxy *proxy;
+	gboolean done = FALSE;
+
+	g_return_if_fail (file != NULL);
+
+	proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION,
+				G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
+				G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+				NULL, "org.freedesktop.FileManager1",
+				"/org/freedesktop/FileManager1",
+				"org.freedesktop.FileManager1",
+				NULL, NULL);
+
+	if (proxy) {
+		gchar *uri = g_file_get_uri (file);
+		gchar *startup_id;
+		GVariant *params, *result;
+		GVariantBuilder builder;
+
+		g_variant_builder_init (&builder,
+					G_VARIANT_TYPE ("as"));
+		g_variant_builder_add (&builder, "s", uri);
+
+		/* This seems to be the expected format, as other values
+		   cause the filemanager window not to get focus. */
+		startup_id = g_strdup_printf("_TIME%u",
+					     gtk_get_current_event_time());
+
+		/* params is floating! */
+		params = g_variant_new ("(ass)", &builder, startup_id);
+
+		g_free (startup_id);
+		g_variant_builder_clear (&builder);
+
+		/* Floating params-GVariant is consumed here */
+		result = g_dbus_proxy_call_sync (proxy, "ShowItems",
+						 params, G_DBUS_CALL_FLAGS_NONE,
+						 -1, NULL, NULL);
+
+		/* Receiving a non-NULL result counts as a successful call. */
+		if (G_LIKELY (result != NULL)) {
+			done = TRUE;
+			g_variant_unref (result);
+		}
+
+		g_free (uri);
+		g_object_unref (proxy);
+	}
+
+	/* Fallback to gtk_show_uri() if launch over DBus is not possible */
+	if (!done)
+		_eog_util_show_file_in_filemanager_fallback (file, screen);
+}
diff --git a/src/eog-util.h b/src/eog-util.h
new file mode 100644
index 0000000..fe8d58e
--- /dev/null
+++ b/src/eog-util.h
@@ -0,0 +1,74 @@
+/* Eye Of Gnome - General Utilities
+ *
+ * Copyright (C) 2006 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <lucasr gnome org>
+ *
+ * Based on code by:
+ *	- Jens Finke <jens gnome org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __EOG_UTIL_H__
+#define __EOG_UTIL_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+G_GNUC_INTERNAL
+void     eog_util_show_help                  (const gchar *section,
+					      GtkWindow   *parent);
+
+G_GNUC_INTERNAL
+gchar   *eog_util_make_valid_utf8            (const gchar *name);
+
+G_GNUC_INTERNAL
+GSList  *eog_util_parse_uri_string_list_to_file_list (const gchar *uri_list);
+
+G_GNUC_INTERNAL
+GSList  *eog_util_string_list_to_file_list    (GSList *string_list);
+
+G_GNUC_INTERNAL
+GSList  *eog_util_strings_to_file_list        (gchar **strings);
+
+G_GNUC_INTERNAL
+GSList  *eog_util_string_array_to_list       (const gchar **files,
+	 				      gboolean create_uri);
+
+G_GNUC_INTERNAL
+gchar  **eog_util_string_array_make_absolute (gchar **files);
+
+G_GNUC_INTERNAL
+gboolean eog_util_launch_desktop_file        (const gchar *filename,
+					      guint32      user_time);
+
+G_GNUC_INTERNAL
+const    gchar *eog_util_dot_dir             (void);
+
+G_GNUC_INTERNAL
+char *  eog_util_filename_get_extension      (const char * filename);
+
+G_GNUC_INTERNAL
+gboolean eog_util_file_is_persistent (GFile *file);
+
+G_GNUC_INTERNAL
+void     eog_util_show_file_in_filemanager   (GFile *file,
+					      GdkScreen *screen);
+
+G_END_DECLS
+
+#endif /* __EOG_UTIL_H__ */
diff --git a/src/photos-marshalers.list b/src/photos-marshalers.list
index 438c55c..fad4924 100644
--- a/src/photos-marshalers.list
+++ b/src/photos-marshalers.list
@@ -1 +1,2 @@
 VOID:ENUM,ENUM
+VOID:INT,INT



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]