[gthumb/ext] [image rotation] added basic image rotation tools



commit 74b20f15a9063e692f364b1184f4ab957797f038
Author: Paolo Bacchilega <paobac src gnome org>
Date:   Mon Aug 17 13:05:22 2009 +0200

    [image rotation] added basic image rotation tools

 configure.ac                                       |   19 +
 extensions/Makefile.am                             |    1 +
 extensions/exiv2/exiv2-utils.cpp                   |  441 +++++----
 extensions/exiv2/exiv2-utils.h                     |   20 +-
 extensions/exiv2/exiv2.extension.in.in             |    3 +-
 extensions/exiv2/gth-metadata-provider-exiv2.c     |    5 +-
 extensions/exiv2/main.c                            |   46 +
 extensions/image_rotation/Makefile.am              |   47 +
 extensions/image_rotation/actions.c                |   65 ++
 extensions/image_rotation/actions.h                |   33 +
 extensions/image_rotation/callbacks.c              |  114 +++
 extensions/image_rotation/callbacks.h              |   31 +
 extensions/image_rotation/gth-transform-task.c     |  226 +++++
 extensions/image_rotation/gth-transform-task.h     |   58 ++
 .../image_rotation/image_rotation.extension.in.in  |   10 +
 extensions/image_rotation/jmemorydest.c            |  129 +++
 extensions/image_rotation/jmemorysrc.c             |  138 +++
 extensions/image_rotation/jpegtran.c               |  348 +++++++
 extensions/image_rotation/jpegtran.h               |   56 +
 extensions/image_rotation/main.c                   |   63 ++
 extensions/image_rotation/rotation-utils.c         |  357 +++++++
 extensions/image_rotation/rotation-utils.h         |   46 +
 extensions/image_rotation/transupp.c               | 1059 ++++++++++++++++++++
 extensions/image_rotation/transupp.h               |  157 +++
 gthumb/Makefile.am                                 |    1 +
 gthumb/dlg-extensions.c                            |    4 +-
 gthumb/gio-utils.c                                 |    2 +-
 gthumb/glib-utils.c                                |  163 +++
 gthumb/glib-utils.h                                |    3 +
 gthumb/gth-extensions.c                            |  101 ++-
 gthumb/gth-extensions.h                            |   13 +-
 gthumb/gth-hook.c                                  |  121 ++-
 gthumb/gth-hook.h                                  |   31 +-
 gthumb/gth-image-loader.c                          |    3 +-
 gthumb/gth-main.c                                  |    2 +-
 35 files changed, 3644 insertions(+), 272 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 3581102..92a591c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -201,6 +201,23 @@ AC_SUBST(WARNINGS)
 
 dnl ===========================================================================
 
+AC_MSG_CHECKING(JPEG Support)
+AC_ARG_ENABLE([jpeg],
+	      [AC_HELP_STRING([--disable-jpeg],[do not compile code that uses the libjpeg library])],,
+	      [enable_jpeg=yes])
+AC_CHECK_LIB(jpeg, jpeg_destroy_decompress,
+             [enable_jpeg=yes],
+	     [enable_jpeg=no])
+AC_DEFINE(HAVE_LIBJPEG, 1, [Define to 1 if libjpeg support is included])
+if test "x$enable_jpeg" = "xyes"; then
+	JPEG_LIBS='-ljpeg -lm -lz'
+fi
+AC_SUBST(JPEG_LIBS)
+AM_CONDITIONAL(ENABLE_JPEG_TOOLS, test "x$enable_jpeg" = xyes)
+AC_MSG_RESULT($enable_jpeg)
+
+dnl ===========================================================================
+
 AC_CONFIG_FILES([
 Makefile
 copy-n-paste/Makefile
@@ -232,6 +249,7 @@ extensions/file_tools/Makefile
 extensions/file_tools/data/Makefile
 extensions/file_tools/data/ui/Makefile
 extensions/file_viewer/Makefile
+extensions/image_rotation/Makefile
 extensions/image_viewer/Makefile
 extensions/image_viewer/data/Makefile
 extensions/image_viewer/data/ui/Makefile
@@ -266,4 +284,5 @@ Configuration:
 	Run in place         : ${enable_run_in_place}
 	Build tests          : $ENABLE_TEST_SUITE
 	Exiv2 support        : ${enable_exiv2}
+	JPEG tools           : ${enable_jpeg}
 "
diff --git a/extensions/Makefile.am b/extensions/Makefile.am
index 5064623..aaae30d 100644
--- a/extensions/Makefile.am
+++ b/extensions/Makefile.am
@@ -5,6 +5,7 @@ SUBDIRS = 		\
 	file_manager	\
 	file_tools	\
 	file_viewer	\
+	image_rotation	\
 	image_viewer	\
 	list_tools	\
 	photo_importer	\
diff --git a/extensions/exiv2/exiv2-utils.cpp b/extensions/exiv2/exiv2-utils.cpp
index 1ebb9f3..4826882 100644
--- a/extensions/exiv2/exiv2-utils.cpp
+++ b/extensions/exiv2/exiv2-utils.cpp
@@ -281,16 +281,91 @@ get_exif_default_category (const Exiv2::Exifdatum &md)
 }
 
 
+static void
+exiv2_read_metadata (Exiv2::Image::AutoPtr  image,
+		     GFileInfo             *info)
+{
+	image->readMetadata();
+
+	Exiv2::ExifData &exifData = image->exifData();
+	if (! exifData.empty()) {
+		Exiv2::ExifData::const_iterator end = exifData.end();
+		for (Exiv2::ExifData::const_iterator md = exifData.begin(); md != end; ++md) {
+			stringstream value;
+			value << *md;
+
+			stringstream short_name;
+			if (md->ifdId () > Exiv2::ifd1Id) {
+				// Must be a MakerNote - include group name
+				short_name << md->groupName() << "." << md->tagName();
+			}
+			else {
+				// Normal exif tag - just use tag name
+				short_name << md->tagName();
+			}
+
+			set_file_info (info,
+				       md->key().c_str(),
+				       short_name.str().c_str(),
+				       value.str().c_str(),
+				       md->toString().c_str(),
+				       get_exif_default_category (*md));
+		}
+	}
+
+	Exiv2::IptcData &iptcData = image->iptcData();
+	if (! iptcData.empty()) {
+		Exiv2::IptcData::iterator end = iptcData.end();
+		for (Exiv2::IptcData::iterator md = iptcData.begin(); md != end; ++md) {
+			stringstream value;
+			value << *md;
+
+			stringstream short_name;
+			short_name << md->tagName();
+
+			set_file_info (info,
+				       md->key().c_str(),
+				       short_name.str().c_str(),
+				       value.str().c_str(),
+				       md->toString().c_str(),
+				       "Iptc");
+		}
+	}
+
+	Exiv2::XmpData &xmpData = image->xmpData();
+	if (! xmpData.empty()) {
+		Exiv2::XmpData::iterator end = xmpData.end();
+		for (Exiv2::XmpData::iterator md = xmpData.begin(); md != end; ++md) {
+			stringstream value;
+			value << *md;
+
+			stringstream short_name;
+			short_name << md->groupName() << "." << md->tagName();
+
+			set_file_info (info,
+				       md->key().c_str(),
+				       short_name.str().c_str(),
+				       value.str().c_str(),
+				       md->toString().c_str(),
+				       "Xmp::Embedded");
+		}
+	}
+
+	set_attributes_from_tagsets (info);
+}
+
+
 /*
- * exiv2_read_metadata
+ * exiv2_read_metadata_from_file
  * reads metadata from image files
  * code relies heavily on example1 from the exiv2 website
  * http://www.exiv2.org/example1.html
  */
 extern "C"
 gboolean
-exiv2_read_metadata (GFile     *file,
-		     GFileInfo *info)
+exiv2_read_metadata_from_file (GFile      *file,
+			       GFileInfo  *info,
+			       GError    **error)
 {
 	try {
 		char *path;
@@ -300,79 +375,53 @@ exiv2_read_metadata (GFile     *file,
 		g_free (path);
 
 		if (image.get() == 0) {
-			//die silently if image cannot be opened
+			if (error != NULL)
+				*error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, _("Invalid file format"));
 			return FALSE;
 		}
-		image->readMetadata();
-
-		Exiv2::ExifData &exifData = image->exifData();
-		if (! exifData.empty()) {
-			Exiv2::ExifData::const_iterator end = exifData.end();
-			for (Exiv2::ExifData::const_iterator md = exifData.begin(); md != end; ++md) {
-				stringstream value;
-				value << *md;
-
-				stringstream short_name;
-				if (md->ifdId () > Exiv2::ifd1Id) {
-					// Must be a MakerNote - include group name
-					short_name << md->groupName() << "." << md->tagName();
-				}
-				else {
-					// Normal exif tag - just use tag name
-					short_name << md->tagName();
-				}
-
-				set_file_info (info,
-					       md->key().c_str(),
-					       short_name.str().c_str(),
-					       value.str().c_str(),
-					       md->toString().c_str(),
-					       get_exif_default_category (*md));
-			}
-		}
 
-		Exiv2::IptcData &iptcData = image->iptcData();
-		if (! iptcData.empty()) {
-			Exiv2::IptcData::iterator end = iptcData.end();
-			for (Exiv2::IptcData::iterator md = iptcData.begin(); md != end; ++md) {
-				stringstream value;
-				value << *md;
-
-				stringstream short_name;
-				short_name << md->tagName();
+		exiv2_read_metadata (image, info);
+	}
+	/*catch (Exiv2::AnyError& e) {
+		std::cerr << "Caught Exiv2 exception '" << e << "'\n";
+		return FALSE;
+	}*/
+	catch (char *msg) {
+		if (error != NULL)
+			*error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, msg);
+		return FALSE;
+	}
 
-				set_file_info (info,
-					       md->key().c_str(),
-					       short_name.str().c_str(),
-					       value.str().c_str(),
-					       md->toString().c_str(),
-					       "Iptc");
-			}
-		}
+	return TRUE;
+}
 
-		Exiv2::XmpData &xmpData = image->xmpData();
-		if (! xmpData.empty()) {
-			Exiv2::XmpData::iterator end = xmpData.end();
-			for (Exiv2::XmpData::iterator md = xmpData.begin(); md != end; ++md) {
-				stringstream value;
-				value << *md;
 
-				stringstream short_name;
-				short_name << md->groupName() << "." << md->tagName();
+extern "C"
+gboolean
+exiv2_read_metadata_from_buffer (void       *buffer,
+				 gsize       buffer_size,
+				 GFileInfo  *info,
+				 GError    **error)
+{
+	try {
+		Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open ((Exiv2::byte*) buffer, buffer_size);
 
-				set_file_info (info,
-					       md->key().c_str(),
-					       short_name.str().c_str(),
-					       value.str().c_str(),
-					       md->toString().c_str(),
-					       "Xmp::Embedded");
-			}
+		if (image.get() == 0) {
+			if (error != NULL)
+				*error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, _("Invalid file format"));
+			return FALSE;
 		}
 
-		set_attributes_from_tagsets (info);
+		exiv2_read_metadata (image, info);
 	}
-	catch (Exiv2::AnyError& e) {
-		std::cerr << "Caught Exiv2 exception '" << e << "'\n";
+	catch (Exiv2::Error) {
+		if (error != NULL)
+			*error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, _("Invalid file format"));
+		return FALSE;
+	}
+	catch (char *msg) {
+		if (error != NULL)
+			*error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, msg);
 		return FALSE;
 	}
 
@@ -451,155 +500,201 @@ mandatory_string (Exiv2::ExifData &checkdata,
 }
 
 
-extern "C"
-void
-exiv2_write_metadata (SavePixbufData *data)
+static Exiv2::DataBuf
+exiv2_write_metadata_private (Exiv2::Image::AutoPtr  image,
+			      GFileInfo             *info,
+			      GdkPixbuf             *pixbuf)
 {
-	try {
-		Exiv2::Image::AutoPtr image1 = Exiv2::ImageFactory::open ((Exiv2::byte*) data->buffer, data->buffer_size);
-		g_assert (image1.get() != 0);
+	char **attributes;
+	int    i;
 
-		image1->readMetadata();
+	image->readMetadata();
 
-		char **attributes;
-		int i;
+	// EXIF Data
 
-		// EXIF Data
+	Exiv2::ExifData &ed = image->exifData();
+	attributes = g_file_info_list_attributes (info, "Exif");
+	for (i = 0; attributes[i] != NULL; i++) {
+		GthMetadata *metadatum;
+		char *key;
 
-		Exiv2::ExifData &ed = image1->exifData();
-		attributes = g_file_info_list_attributes (data->file_data->info, "Exif");
-		for (i = 0; attributes[i] != NULL; i++) {
-			GthMetadata *metadatum;
-			char *key;
+		metadatum = (GthMetadata *) g_file_info_get_attribute_object (info, attributes[i]);
+		key = exiv2_key_from_attribute (attributes[i]);
 
-			metadatum = (GthMetadata *) g_file_info_get_attribute_object (data->file_data->info, attributes[i]);
-			key = exiv2_key_from_attribute (attributes[i]);
+		try {
+			ed[key] = gth_metadata_get_raw (metadatum);
+		}
+		catch (...) {
+			/* we don't care about invalid key errors */
+		}
 
-			try {
-				ed[key] = gth_metadata_get_raw (metadatum);
-			}
-			catch (...) {
-				/* we don't care about invalid key errors */
-			}
+		g_free (key);
+	}
+	g_strfreev (attributes);
 
-			g_free (key);
-		}
-		g_strfreev (attributes);
+	// Mandatory tags - add if not already present
 
-		// Mandatory tags - add if not already present
+	mandatory_int (ed, "Exif.Image.XResolution", 72);
+	mandatory_int (ed, "Exif.Image.YResolution", 72);
+	mandatory_int (ed, "Exif.Image.ResolutionUnit", 2);
+	mandatory_int (ed, "Exif.Image.YCbCrPositioning", 1);
+	mandatory_int (ed, "Exif.Photo.ColorSpace", 1);
+	mandatory_string (ed, "Exif.Photo.ExifVersion", "48 50 50 49");
+	mandatory_string (ed, "Exif.Photo.ComponentsConfiguration", "1 2 3 0");
+	mandatory_string (ed, "Exif.Photo.FlashpixVersion", "48 49 48 48");
 
-		mandatory_int (ed, "Exif.Image.XResolution", 72);
-		mandatory_int (ed, "Exif.Image.YResolution", 72);
-		mandatory_int (ed, "Exif.Image.ResolutionUnit", 2);
-		mandatory_int (ed, "Exif.Image.YCbCrPositioning", 1);
-		mandatory_int (ed, "Exif.Photo.ColorSpace", 1);
-		mandatory_string (ed, "Exif.Photo.ExifVersion", "48 50 50 49");
-		mandatory_string (ed, "Exif.Photo.ComponentsConfiguration", "1 2 3 0");
-		mandatory_string (ed, "Exif.Photo.FlashpixVersion", "48 49 48 48");
+	// Overwrite the software tag
 
-		// Overwrite the software tag
+	ed["Exif.Image.Software"] = PACKAGE " " VERSION;
 
-		ed["Exif.Image.Software"] = PACKAGE " " VERSION;
+	// Update the dimension tags with actual image values
 
-		// Update the dimension tags with actual image values
+	int width = 0;
+	int height = 0;
 
-		int width = gdk_pixbuf_get_width (data->pixbuf);
+	if (pixbuf != NULL) {
+		width = gdk_pixbuf_get_width (pixbuf);
 		if (width > 0)
 			ed["Exif.Photo.PixelXDimension"] = width;
 
-		int height = gdk_pixbuf_get_height (data->pixbuf);
+		height = gdk_pixbuf_get_height (pixbuf);
 		if (height > 0)
 			ed["Exif.Photo.PixelYDimension"] = height;
+	}
 
-		// Update the thumbnail
-
-		Exiv2::ExifThumb thumb(image1->exifData());
-		if ((width > 0) && (height > 0)) {
-			GdkPixbuf *thumb_pixbuf;
-			char      *buffer;
-			gsize      buffer_size;
-
-			scale_keeping_ratio (&width, &height, 128, 128, FALSE);
-			thumb_pixbuf = _gdk_pixbuf_scale_simple_safe (data->pixbuf, width, height, GDK_INTERP_BILINEAR);
-			if (gdk_pixbuf_save_to_buffer (thumb_pixbuf, &buffer, &buffer_size, "jpeg", NULL, NULL)) {
-				thumb.setJpegThumbnail ((Exiv2::byte*) buffer, buffer_size);
-				ed["Exif.Thumbnail.XResolution"] = 72;
-				ed["Exif.Thumbnail.YResolution"] = 72;
-				ed["Exif.Thumbnail.ResolutionUnit"] =  2;
-				g_free (buffer);
-			}
-			else
-				thumb.erase();
-
-			g_object_unref (thumb_pixbuf);
+	// Update the thumbnail
+
+	Exiv2::ExifThumb thumb(image->exifData());
+	if ((pixbuf != NULL) && (width > 0) && (height > 0)) {
+		GdkPixbuf *thumb_pixbuf;
+		char      *buffer;
+		gsize      buffer_size;
+
+		scale_keeping_ratio (&width, &height, 128, 128, FALSE);
+		thumb_pixbuf = _gdk_pixbuf_scale_simple_safe (pixbuf, width, height, GDK_INTERP_BILINEAR);
+		if (gdk_pixbuf_save_to_buffer (thumb_pixbuf, &buffer, &buffer_size, "jpeg", NULL, NULL)) {
+			thumb.setJpegThumbnail ((Exiv2::byte*) buffer, buffer_size);
+			ed["Exif.Thumbnail.XResolution"] = 72;
+			ed["Exif.Thumbnail.YResolution"] = 72;
+			ed["Exif.Thumbnail.ResolutionUnit"] =  2;
+			g_free (buffer);
 		}
 		else
 			thumb.erase();
 
-		// Update the DateTime tag
+		g_object_unref (thumb_pixbuf);
+	}
+	else
+		thumb.erase();
 
-		GTimeVal current_time;
-		g_get_current_time (&current_time);
-		char *date_time = _g_time_val_to_exif_date (&current_time);
-		ed["Exif.Image.DateTime"] = date_time;
-		g_free (date_time);
+	// Update the DateTime tag
 
-		// IPTC Data
+	GTimeVal current_time;
+	g_get_current_time (&current_time);
+	char *date_time = _g_time_val_to_exif_date (&current_time);
+	ed["Exif.Image.DateTime"] = date_time;
+	g_free (date_time);
 
-		Exiv2::IptcData &id = image1->iptcData();
-		attributes = g_file_info_list_attributes (data->file_data->info, "Iptc");
-		for (i = 0; attributes[i] != NULL; i++) {
-			GthMetadata *metadatum = (GthMetadata *) g_file_info_get_attribute_object (data->file_data->info, attributes[i]);
-			char *key = exiv2_key_from_attribute (attributes[i]);
+	// IPTC Data
 
-			try {
-				id[key] = gth_metadata_get_raw (metadatum);
-			}
-			catch (...) {
-				/* we don't care about invalid key errors */
-			}
+	Exiv2::IptcData &id = image->iptcData();
+	attributes = g_file_info_list_attributes (info, "Iptc");
+	for (i = 0; attributes[i] != NULL; i++) {
+		GthMetadata *metadatum = (GthMetadata *) g_file_info_get_attribute_object (info, attributes[i]);
+		char *key = exiv2_key_from_attribute (attributes[i]);
 
-			g_free (key);
+		try {
+			id[key] = gth_metadata_get_raw (metadatum);
+		}
+		catch (...) {
+			/* we don't care about invalid key errors */
 		}
-		g_strfreev (attributes);
 
-		// XMP Data
+		g_free (key);
+	}
+	g_strfreev (attributes);
 
-		Exiv2::XmpData &xd = image1->xmpData();
-		attributes = g_file_info_list_attributes (data->file_data->info, "Xmp");
-		for (i = 0; attributes[i] != NULL; i++) {
-			GthMetadata *metadatum = (GthMetadata *) g_file_info_get_attribute_object (data->file_data->info, attributes[i]);
-			char *key = exiv2_key_from_attribute (attributes[i]);
+	// XMP Data
 
-			// Remove existing tags of the same type.
-			// Seems to be needed for storing category keywords.
-			// Not exactly sure why!
-			Exiv2::XmpData::iterator iter = xd.findKey (Exiv2::XmpKey (key));
-			if (iter != xd.end ())
-				xd.erase (iter);
+	Exiv2::XmpData &xd = image->xmpData();
+	attributes = g_file_info_list_attributes (info, "Xmp");
+	for (i = 0; attributes[i] != NULL; i++) {
+		GthMetadata *metadatum = (GthMetadata *) g_file_info_get_attribute_object (info, attributes[i]);
+		char *key = exiv2_key_from_attribute (attributes[i]);
 
-			try {
-				ed[key] = gth_metadata_get_raw (metadatum);
-			}
-			catch (...) {
-				/* we don't care about invalid key errors */
-			}
+		// Remove existing tags of the same type.
+		// Seems to be needed for storing category keywords.
+		// Not exactly sure why!
+		Exiv2::XmpData::iterator iter = xd.findKey (Exiv2::XmpKey (key));
+		if (iter != xd.end ())
+			xd.erase (iter);
 
-			g_free (key);
+		try {
+			ed[key] = gth_metadata_get_raw (metadatum);
 		}
-		g_strfreev (attributes);
+		catch (...) {
+			/* we don't care about invalid key errors */
+		}
+
+		g_free (key);
+	}
+	g_strfreev (attributes);
+
+	image->writeMetadata();
+	Exiv2::BasicIo &io = image->io();
+	io.open();
+
+	return io.read(io.size());
+}
+
+
+extern "C"
+gboolean
+exiv2_write_metadata (SavePixbufData *data)
+{
+	try {
+		Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open ((Exiv2::byte*) data->buffer, data->buffer_size);
+		g_assert (image.get() != 0);
 
-		// overwrite existing metadata with new metadata
-		image1->writeMetadata();
+		Exiv2::DataBuf buf = exiv2_write_metadata_private (image, data->file_data->info, data->pixbuf);
 
-		Exiv2::BasicIo &io = image1->io();
-		io.open();
-		Exiv2::DataBuf buf = io.read(io.size());
 		g_free (data->buffer);
 		data->buffer = g_memdup (buf.pData_, buf.size_);
 		data->buffer_size = buf.size_;
 	}
 	catch (char *msg) {
-		*data->error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, msg);
+		if (data->error != NULL)
+			*data->error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, msg);
+		return FALSE;
 	}
+
+	return TRUE;
+}
+
+
+extern "C"
+gboolean
+exiv2_write_metadata_to_buffer (void      **buffer,
+				gsize      *buffer_size,
+				GFileInfo  *info,
+				GdkPixbuf  *pixbuf,
+				GError    **error)
+{
+	try {
+		Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open ((Exiv2::byte*) *buffer, *buffer_size);
+		g_assert (image.get() != 0);
+
+		Exiv2::DataBuf buf = exiv2_write_metadata_private (image, info, pixbuf);
+
+		g_free (*buffer);
+		*buffer = g_memdup (buf.pData_, buf.size_);
+		*buffer_size = buf.size_;
+	}
+	catch (char *msg) {
+		if (error != NULL)
+			*error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, msg);
+		return FALSE;
+	}
+
+	return TRUE;
 }
diff --git a/extensions/exiv2/exiv2-utils.h b/extensions/exiv2/exiv2-utils.h
index 7bc2759..4447de1 100644
--- a/extensions/exiv2/exiv2-utils.h
+++ b/extensions/exiv2/exiv2-utils.h
@@ -29,11 +29,21 @@
 
 G_BEGIN_DECLS
 
-gboolean  exiv2_read_metadata   (GFile          *file,
-				 GFileInfo      *info);
-gboolean  exiv2_read_sidecar    (GFile          *file,
-				 GFileInfo      *info);
-void      exiv2_write_metadata  (SavePixbufData *data);
+gboolean  exiv2_read_metadata_from_file    (GFile           *file,
+					    GFileInfo       *info,
+					    GError         **error);
+gboolean  exiv2_read_metadata_from_buffer  (void            *buffer,
+					    gsize            buffer_size,
+					    GFileInfo       *info,
+					    GError         **error);
+gboolean  exiv2_read_sidecar               (GFile           *file,
+					    GFileInfo       *info);
+gboolean  exiv2_write_metadata  	   (SavePixbufData  *data);
+gboolean  exiv2_write_metadata_to_buffer   (void           **buffer,
+					    gsize           *buffer_size,
+					    GFileInfo       *info,
+					    GdkPixbuf       *pixbuf, /* optional */
+					    GError         **error);
 
 G_END_DECLS
 
diff --git a/extensions/exiv2/exiv2.extension.in.in b/extensions/exiv2/exiv2.extension.in.in
index 37c2bd0..596a656 100644
--- a/extensions/exiv2/exiv2.extension.in.in
+++ b/extensions/exiv2/exiv2.extension.in.in
@@ -1,10 +1,11 @@
 [Extension]
-_Name=Image metadata
+_Name=EXIF, IPTC, XMP support
 _Description=Read and write exif/iptc and xmp metadata.
 _Authors=gthumb development team
 Copyright=Copyright © 2009 The Free Software Foundation, Inc.
 Version=1.0
 
 [Loader]
+After=image_rotation
 Type=module
 File=%LIBRARY%
diff --git a/extensions/exiv2/gth-metadata-provider-exiv2.c b/extensions/exiv2/gth-metadata-provider-exiv2.c
index d55b56e..35ae8b3 100644
--- a/extensions/exiv2/gth-metadata-provider-exiv2.c
+++ b/extensions/exiv2/gth-metadata-provider-exiv2.c
@@ -51,10 +51,9 @@ gth_metadata_provider_exiv2_read (GthMetadataProvider *self,
 	GthFileData *sidecar_file_data;
 
 	/* this function is executed in a secondary thread, so calling
-	 * slow sync functions, such as obtain_local_file, is not a
-	 * problem. */
+	 * slow sync functions is not a problem. */
 
-	exiv2_read_metadata (file_data->file, file_data->info);
+	exiv2_read_metadata_from_file (file_data->file, file_data->info, NULL);
 
 	/* sidecar data */
 
diff --git a/extensions/exiv2/main.c b/extensions/exiv2/main.c
index 9bc85d6..0549582 100644
--- a/extensions/exiv2/main.c
+++ b/extensions/exiv2/main.c
@@ -144,6 +144,50 @@ GthMetadataInfo exiv2_metadata_info[] = {
 };
 
 
+static void
+update_exif_dimensions (GFileInfo    *info,
+		        GthTransform  transform)
+{
+	g_return_if_fail (info != NULL);
+
+	if ((transform == GTH_TRANSFORM_ROTATE_90)
+	    || (transform == GTH_TRANSFORM_ROTATE_270)
+	    || (transform == GTH_TRANSFORM_TRANSPOSE)
+	    || (transform == GTH_TRANSFORM_TRANSVERSE))
+	{
+		_g_file_info_swap_attributes (info, "Exif::Photo::PixelXDimension", "Exif::Photo::PixelYDimension");
+		_g_file_info_swap_attributes (info, "Exif::Image::XResolution", "Exif::Image::YResolution");
+		_g_file_info_swap_attributes (info, "Exif::Photo::FocalPlaneXResolution", "Exif::Photo::FocalPlaneYResolution");
+		_g_file_info_swap_attributes (info, "Exif::Image::ImageWidth", "Exif::Image::ImageLength");
+		_g_file_info_swap_attributes (info, "Exif::Iop::RelatedImageWidth", "Exif::Iop::RelatedImageLength");
+	}
+}
+
+
+static void
+exiv2_jpeg_tran_cb (void         **out_buffer,
+		    gsize         *out_buffer_size,
+		    GthTransform  *transform)
+{
+	GFileInfo *info;
+
+	info = g_file_info_new ();
+	if (exiv2_read_metadata_from_buffer (*out_buffer, *out_buffer_size, info, NULL)) {
+		GthMetadata *metadata;
+
+		update_exif_dimensions (info, *transform);
+
+		metadata = g_object_new (GTH_TYPE_METADATA, "raw", "1", NULL);
+		g_file_info_set_attribute_object (info, "Exif::Image::Orientation", G_OBJECT (metadata));
+		exiv2_write_metadata_to_buffer (out_buffer, out_buffer_size, info, NULL, NULL);
+
+		g_object_unref (metadata);
+	}
+
+	g_object_unref (info);
+}
+
+
 G_MODULE_EXPORT void
 gthumb_extension_activate (void)
 {
@@ -151,6 +195,8 @@ gthumb_extension_activate (void)
 	gth_main_register_metadata_info_v (exiv2_metadata_info);
 	gth_main_register_metadata_provider (GTH_TYPE_METADATA_PROVIDER_EXIV2);
 	gth_hook_add_callback ("save-pixbuf", 10, G_CALLBACK (exiv2_write_metadata), NULL);
+	if (gth_hook_present ("jpegtran-after"))
+		gth_hook_add_callback ("jpegtran-after", 10, G_CALLBACK (exiv2_jpeg_tran_cb), NULL);
 }
 
 
diff --git a/extensions/image_rotation/Makefile.am b/extensions/image_rotation/Makefile.am
new file mode 100644
index 0000000..c3b739b
--- /dev/null
+++ b/extensions/image_rotation/Makefile.am
@@ -0,0 +1,47 @@
+extensiondir = $(libdir)/gthumb-2.0/extensions
+extension_LTLIBRARIES = libimage_rotation.la
+
+if ENABLE_JPEG_TOOLS
+JPEG_FILES =				\
+	jmemorydest.c			\
+	jmemorysrc.c			\
+	jpegtran.c			\
+	jpegtran.h			\
+	transupp.c			\
+	transupp.h
+else
+JPEG_FILES =
+endif
+
+libimage_rotation_la_SOURCES = 		\
+	$(JPEG_FILES)			\
+	actions.c			\
+	actions.h			\
+	callbacks.c			\
+	callbacks.h			\
+	gth-transform-task.c		\
+	gth-transform-task.h		\
+	main.c				\
+	rotation-utils.c		\
+	rotation-utils.h		
+
+libimage_rotation_la_CFLAGS = $(GTHUMB_CFLAGS) $(DISABLE_DEPRECATED) $(WARNINGS) -I$(top_srcdir) -I$(top_builddir)/gthumb 
+libimage_rotation_la_LDFLAGS = $(EXTENSION_LIBTOOL_FLAGS)
+libimage_rotation_la_LIBADD = $(GTHUMB_LIBS) 
+libimage_rotation_la_DEPENDENCIES = $(top_builddir)/gthumb/gthumb$(EXEEXT)
+
+extensioninidir = $(extensiondir)
+extensionini_in_files = image_rotation.extension.in.in
+extensionini_DATA = $(extensionini_in_files:.extension.in.in=.extension)
+
+%.extension.in: %.extension.in.in $(extension_LTLIBRARIES)
+	sed -e "s|%LIBRARY%|`. ./$(extension_LTLIBRARIES) && echo $$dlname`|" \
+	$< > $@
+
+%.extension: %.extension.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< $@
+
+EXTRA_DIST = $(extensionini_in_files) 
+
+DISTCLEANFILES = $(extensionini_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/image_rotation/actions.c b/extensions/image_rotation/actions.c
new file mode 100644
index 0000000..9eb9f5f
--- /dev/null
+++ b/extensions/image_rotation/actions.c
@@ -0,0 +1,65 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <gthumb.h>
+#include "gth-transform-task.h"
+
+
+void
+gth_browser_activate_action_tool_rotate_right (GtkAction  *action,
+					       GthBrowser *browser)
+{
+	GList   *items;
+	GList   *file_list;
+	GthTask *task;
+
+	items = gth_file_selection_get_selected (GTH_FILE_SELECTION (gth_browser_get_file_list_view (browser)));
+	file_list = gth_file_list_get_files (GTH_FILE_LIST (gth_browser_get_file_list (browser)), items);
+	task = gth_transform_task_new (browser, file_list, GTH_TRANSFORM_ROTATE_90);
+	gth_browser_exec_task (browser, task, FALSE);
+
+	g_object_unref (task);
+	_g_object_list_unref (file_list);
+	_gtk_tree_path_list_free (items);
+}
+
+
+void
+gth_browser_activate_action_tool_rotate_left (GtkAction  *action,
+					      GthBrowser *browser)
+{
+	GList   *items;
+	GList   *file_list;
+	GthTask *task;
+
+	items = gth_file_selection_get_selected (GTH_FILE_SELECTION (gth_browser_get_file_list_view (browser)));
+	file_list = gth_file_list_get_files (GTH_FILE_LIST (gth_browser_get_file_list (browser)), items);
+	task = gth_transform_task_new (browser, file_list, GTH_TRANSFORM_ROTATE_270);
+	gth_browser_exec_task (browser, task, FALSE);
+
+	g_object_unref (task);
+	_g_object_list_unref (file_list);
+	_gtk_tree_path_list_free (items);
+}
diff --git a/extensions/image_rotation/actions.h b/extensions/image_rotation/actions.h
new file mode 100644
index 0000000..e698e4f
--- /dev/null
+++ b/extensions/image_rotation/actions.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef ACTIONS_H
+#define ACTIONS_H
+
+#include <gtk/gtk.h>
+
+#define DEFINE_ACTION(x) void x (GtkAction *action, gpointer data);
+
+DEFINE_ACTION(gth_browser_activate_action_tool_rotate_right)
+DEFINE_ACTION(gth_browser_activate_action_tool_rotate_left)
+
+#endif /* ACTIONS_H */
diff --git a/extensions/image_rotation/callbacks.c b/extensions/image_rotation/callbacks.c
new file mode 100644
index 0000000..8a9998c
--- /dev/null
+++ b/extensions/image_rotation/callbacks.c
@@ -0,0 +1,114 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include <glib-object.h>
+#include <gthumb.h>
+#include "actions.h"
+
+#define BROWSER_DATA_KEY "image-rotation-browser-data"
+
+
+static const char *fixed_ui_info =
+"<ui>"
+"  <popup name='ListToolsPopup'>"
+"    <placeholder name='Tools'>"
+"      <menuitem name='RotateRight' action='Tool_RotateRight'/>"
+"      <menuitem name='RotateLeft' action='Tool_RotateLeft'/>"
+"    </placeholder>"
+"  </popup>"
+"</ui>";
+
+
+static GtkActionEntry action_entries[] = {
+	{ "Tool_RotateRight", NULL,
+	  N_("Rotate right"), NULL,
+	  N_("Rotate the selected to the right"),
+	  G_CALLBACK (gth_browser_activate_action_tool_rotate_right) },
+
+	{ "Tool_RotateLeft", NULL,
+	  N_("Rotate left"), NULL,
+	  N_("Rotate the selected to the left"),
+	  G_CALLBACK (gth_browser_activate_action_tool_rotate_left) },
+};
+
+
+typedef struct {
+	GtkActionGroup *action_group;
+} BrowserData;
+
+
+static void
+browser_data_free (BrowserData *data)
+{
+	g_free (data);
+}
+
+
+void
+ir__gth_browser_construct_cb (GthBrowser *browser)
+{
+	BrowserData *data;
+	GError      *error = NULL;
+
+	g_return_if_fail (GTH_IS_BROWSER (browser));
+
+	data = g_new0 (BrowserData, 1);
+	data->action_group = gtk_action_group_new ("Image Rotation Actions");
+	gtk_action_group_set_translation_domain (data->action_group, NULL);
+	gtk_action_group_add_actions (data->action_group,
+				      action_entries,
+				      G_N_ELEMENTS (action_entries),
+				      browser);
+	gtk_ui_manager_insert_action_group (gth_browser_get_ui_manager (browser), data->action_group, 0);
+
+	if (! gtk_ui_manager_add_ui_from_string (gth_browser_get_ui_manager (browser), fixed_ui_info, -1, &error)) {
+		g_message ("building menus failed: %s", error->message);
+		g_clear_error (&error);
+	}
+
+	g_object_set_data_full (G_OBJECT (browser), BROWSER_DATA_KEY, data, (GDestroyNotify) browser_data_free);
+}
+
+
+void
+ir__gth_browser_update_sensitivity_cb (GthBrowser *browser)
+{
+	BrowserData *data;
+	GtkAction   *action;
+	int          n_selected;
+	gboolean     sensitive;
+
+	data = g_object_get_data (G_OBJECT (browser), BROWSER_DATA_KEY);
+	g_return_if_fail (data != NULL);
+
+	n_selected = gth_file_selection_get_n_selected (GTH_FILE_SELECTION (gth_browser_get_file_list_view (browser)));
+	sensitive = n_selected > 0;
+
+	action = gtk_action_group_get_action (data->action_group, "Tool_RotateRight");
+	g_object_set (action, "sensitive", sensitive, NULL);
+
+	action = gtk_action_group_get_action (data->action_group, "Tool_RotateLeft");
+	g_object_set (action, "sensitive", sensitive, NULL);
+}
diff --git a/extensions/image_rotation/callbacks.h b/extensions/image_rotation/callbacks.h
new file mode 100644
index 0000000..f1991e8
--- /dev/null
+++ b/extensions/image_rotation/callbacks.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef CALLBACKS_H
+#define CALLBACKS_H
+
+#include <gthumb.h>
+
+void  ir__gth_browser_construct_cb          (GthBrowser *browser);
+void  ir__gth_browser_update_sensitivity_cb (GthBrowser *browser);
+
+#endif /* CALLBACKS_H */
diff --git a/extensions/image_rotation/gth-transform-task.c b/extensions/image_rotation/gth-transform-task.c
new file mode 100644
index 0000000..211c187
--- /dev/null
+++ b/extensions/image_rotation/gth-transform-task.c
@@ -0,0 +1,226 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "gth-transform-task.h"
+#include "rotation-utils.h"
+
+
+struct _GthTransformTaskPrivate {
+	GthBrowser    *browser;
+	GCancellable  *cancellable;
+	GList         *file_list;
+	GList         *current;
+	GthTransform   transform;
+	JpegMcuAction  default_action;
+};
+
+
+static gpointer parent_class = NULL;
+
+
+static void
+gth_transform_task_finalize (GObject *object)
+{
+	GthTransformTask *self;
+
+	self = GTH_TRANSFORM_TASK (object);
+
+	_g_object_list_unref (self->priv->file_list);
+	_g_object_unref (self->priv->cancellable);
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void transform_current_file (GthTransformTask *self);
+
+
+static void
+transform_next_file (GthTransformTask *self)
+{
+	/*self->priv->default_action = JPEG_MCU_ACTION_ABORT;*/
+	self->priv->current = self->priv->current->next;
+	transform_current_file (self);
+}
+
+
+static void
+trim_response_cb (JpegMcuAction action,
+		  gpointer      user_data)
+{
+	GthTransformTask *self = user_data;
+
+	if (action != JPEG_MCU_ACTION_ABORT) {
+		self->priv->default_action = action;
+		transform_current_file (self);
+	}
+	else
+		transform_next_file (self);
+}
+
+
+static void
+transform_file_ready_cb (GError   *error,
+			 gpointer  user_data)
+{
+	GthTransformTask *self = user_data;
+
+	if (error != NULL) {
+		if (g_error_matches (error, JPEG_ERROR, JPEG_ERROR_MCU)) {
+			GthFileData *file_data;
+
+			g_clear_error (&error);
+
+			file_data = self->priv->current->data;
+			ask_whether_to_trim (GTK_WINDOW (self->priv->browser),
+					     file_data,
+					     trim_response_cb,
+					     self);
+
+			return;
+		}
+
+		gth_task_completed (GTH_TASK (self), error);
+		return;
+	}
+
+	transform_next_file (self);
+}
+
+
+static void
+transform_current_file (GthTransformTask *self)
+{
+	GthFileData *file_data;
+
+	if (self->priv->current == NULL) {
+		gth_task_completed (GTH_TASK (self), NULL);
+		return;
+	}
+
+	file_data = self->priv->current->data;
+	apply_transformation_async (file_data,
+				    self->priv->transform,
+				    self->priv->default_action,
+				    self->priv->cancellable,
+				    transform_file_ready_cb,
+				    self);
+}
+
+
+static void
+gth_transform_task_exec (GthTask *task)
+{
+	GthTransformTask *self;
+
+	g_return_if_fail (GTH_IS_TRANSFORM_TASK (task));
+
+	self = GTH_TRANSFORM_TASK (task);
+
+	self->priv->current = self->priv->file_list;
+	transform_current_file (self);
+}
+
+
+static void
+gth_transform_task_cancel (GthTask *task)
+{
+	GthTransformTask *self;
+
+	g_return_if_fail (GTH_IS_TRANSFORM_TASK (task));
+
+	self = GTH_TRANSFORM_TASK (task);
+
+	g_cancellable_cancel (self->priv->cancellable);
+}
+
+
+static void
+gth_transform_task_class_init (GthTransformTaskClass *klass)
+{
+	GObjectClass *object_class;
+	GthTaskClass *task_class;
+
+	parent_class = g_type_class_peek_parent (klass);
+	g_type_class_add_private (klass, sizeof (GthTransformTaskPrivate));
+
+	object_class = G_OBJECT_CLASS (klass);
+	object_class->finalize = gth_transform_task_finalize;
+
+	task_class = GTH_TASK_CLASS (klass);
+	task_class->exec = gth_transform_task_exec;
+	task_class->cancel = gth_transform_task_cancel;
+}
+
+
+static void
+gth_transform_task_init (GthTransformTask *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_TRANSFORM_TASK, GthTransformTaskPrivate);
+	self->priv->cancellable = g_cancellable_new ();
+	self->priv->default_action = JPEG_MCU_ACTION_ABORT; /* FIXME: save a gconf value for this */
+}
+
+
+GType
+gth_transform_task_get_type (void)
+{
+	static GType type = 0;
+
+	if (! type) {
+		GTypeInfo type_info = {
+			sizeof (GthTransformTaskClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) gth_transform_task_class_init,
+			NULL,
+			NULL,
+			sizeof (GthTransformTask),
+			0,
+			(GInstanceInitFunc) gth_transform_task_init
+		};
+
+		type = g_type_register_static (GTH_TYPE_TASK,
+					       "GthTransformTask",
+					       &type_info,
+					       0);
+	}
+
+	return type;
+}
+
+
+GthTask *
+gth_transform_task_new (GthBrowser   *browser,
+			GList        *file_list,
+			GthTransform  transform)
+{
+	GthTransformTask *self;
+
+	self = GTH_TRANSFORM_TASK (g_object_new (GTH_TYPE_TRANSFORM_TASK, NULL));
+	self->priv->browser = browser;
+	self->priv->file_list = _g_object_list_ref (file_list);
+	self->priv->transform = transform;
+
+	return (GthTask *) self;
+}
diff --git a/extensions/image_rotation/gth-transform-task.h b/extensions/image_rotation/gth-transform-task.h
new file mode 100644
index 0000000..8454052
--- /dev/null
+++ b/extensions/image_rotation/gth-transform-task.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 The Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_TRANSFORM_TASK_H
+#define GTH_TRANSFORM_TASK_H
+
+#include <glib.h>
+#include <gthumb.h>
+
+G_BEGIN_DECLS
+
+#define GTH_TYPE_TRANSFORM_TASK            (gth_transform_task_get_type ())
+#define GTH_TRANSFORM_TASK(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_TRANSFORM_TASK, GthTransformTask))
+#define GTH_TRANSFORM_TASK_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TYPE_TRANSFORM_TASK, GthTransformTaskClass))
+#define GTH_IS_TRANSFORM_TASK(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_TRANSFORM_TASK))
+#define GTH_IS_TRANSFORM_TASK_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_TRANSFORM_TASK))
+#define GTH_TRANSFORM_TASK_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), GTH_TYPE_TRANSFORM_TASK, GthTransformTaskClass))
+
+typedef struct _GthTransformTask        GthTransformTask;
+typedef struct _GthTransformTaskClass   GthTransformTaskClass;
+typedef struct _GthTransformTaskPrivate GthTransformTaskPrivate;
+
+struct _GthTransformTask {
+	GthTask __parent;
+	GthTransformTaskPrivate *priv;
+};
+
+struct _GthTransformTaskClass {
+	GthTaskClass __parent;
+};
+
+GType         gth_transform_task_get_type     (void);
+GthTask *     gth_transform_task_new          (GthBrowser   *browser,
+					       GList        *file_list,
+					       GthTransform  transform);
+
+G_END_DECLS
+
+#endif /* GTH_TRANSFORM_TASK_H */
diff --git a/extensions/image_rotation/image_rotation.extension.in.in b/extensions/image_rotation/image_rotation.extension.in.in
new file mode 100644
index 0000000..38e86fd
--- /dev/null
+++ b/extensions/image_rotation/image_rotation.extension.in.in
@@ -0,0 +1,10 @@
+[Extension]
+_Name=Image rotation
+_Description=Allows to rotate images without data loss.
+_Authors=gthumb development team
+Copyright=Copyright © 2009 The Free Software Foundation, Inc.
+Version=1.0
+
+[Loader]
+Type=module
+File=%LIBRARY%
diff --git a/extensions/image_rotation/jmemorydest.c b/extensions/image_rotation/jmemorydest.c
new file mode 100644
index 0000000..9ee2079
--- /dev/null
+++ b/extensions/image_rotation/jmemorydest.c
@@ -0,0 +1,129 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 The Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <string.h>
+#include <stdio.h>
+#include <jpeglib.h>
+#include <glib.h>
+#include <gio/gio.h>
+
+
+#define TMP_BUF_SIZE  4096
+#define JPEG_ERROR(cinfo,code)  \
+  ((cinfo)->err->msg_code = (code), \
+   (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo)))
+
+
+typedef struct {
+	struct jpeg_destination_mgr pub;
+
+	void   **out_buffer;
+	gsize   *out_buffer_size;
+	gsize    bytes_written;
+	JOCTET  *tmp_buffer;
+} mem_destination_mgr;
+
+typedef mem_destination_mgr * mem_dest_ptr;
+
+
+static void
+init_destination (j_compress_ptr cinfo)
+{
+	mem_dest_ptr dest = (mem_dest_ptr) cinfo->dest;
+
+	*dest->out_buffer = NULL;
+	*dest->out_buffer_size = 0;
+	dest->tmp_buffer = (JOCTET *)
+		(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo,
+					    JPOOL_IMAGE,
+				            TMP_BUF_SIZE * sizeof (JOCTET));
+	dest->bytes_written = 0;
+	dest->pub.next_output_byte = dest->tmp_buffer;
+	dest->pub.free_in_buffer = TMP_BUF_SIZE;
+}
+
+
+static gboolean
+empty_output_buffer (j_compress_ptr cinfo)
+{
+	mem_dest_ptr dest = (mem_dest_ptr) cinfo->dest;
+
+	*dest->out_buffer = g_realloc (*dest->out_buffer, *dest->out_buffer_size + TMP_BUF_SIZE);
+	if (*dest->out_buffer != NULL) {
+		*dest->out_buffer_size = *dest->out_buffer_size + TMP_BUF_SIZE;
+		memcpy (*dest->out_buffer + dest->bytes_written, dest->tmp_buffer, TMP_BUF_SIZE);
+		dest->bytes_written += TMP_BUF_SIZE;
+	}
+	else
+		JPEG_ERROR (cinfo, G_IO_ERROR_FAILED);
+
+	dest->pub.next_output_byte = dest->tmp_buffer;
+	dest->pub.free_in_buffer = TMP_BUF_SIZE;
+
+	return TRUE;
+}
+
+
+static void
+term_destination (j_compress_ptr cinfo)
+{
+	mem_dest_ptr dest = (mem_dest_ptr) cinfo->dest;
+	size_t      datacount;
+
+	datacount = TMP_BUF_SIZE - dest->pub.free_in_buffer;
+	if (datacount > 0) {
+		*dest->out_buffer = g_realloc (*dest->out_buffer, *dest->out_buffer_size + datacount);
+		if (*dest->out_buffer != NULL) {
+			*dest->out_buffer_size = *dest->out_buffer_size + datacount;
+			memcpy (*dest->out_buffer + dest->bytes_written, dest->tmp_buffer, datacount);
+			dest->bytes_written += datacount;
+		}
+		else
+			JPEG_ERROR (cinfo, G_IO_ERROR_FAILED);
+	}
+}
+
+
+void
+_jpeg_memory_dest (j_compress_ptr   cinfo,
+		   void           **out_buffer,
+		   gsize           *out_buffer_size)
+{
+	mem_dest_ptr dest;
+
+	if (cinfo->dest == NULL) {
+		cinfo->dest = (struct jpeg_destination_mgr *)
+			(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo,
+						    JPOOL_PERMANENT,
+						    sizeof (mem_destination_mgr));
+	}
+
+	dest = (mem_dest_ptr) cinfo->dest;
+	dest->pub.init_destination = init_destination;
+	dest->pub.empty_output_buffer = empty_output_buffer;
+	dest->pub.term_destination = term_destination;
+	dest->out_buffer = out_buffer;
+	dest->out_buffer_size = out_buffer_size;
+	dest->bytes_written = 0;
+}
diff --git a/extensions/image_rotation/jmemorysrc.c b/extensions/image_rotation/jmemorysrc.c
new file mode 100644
index 0000000..f6dc7a8
--- /dev/null
+++ b/extensions/image_rotation/jmemorysrc.c
@@ -0,0 +1,138 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 The Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <string.h>
+#include <stdio.h>
+#include <jpeglib.h>
+#include <glib.h>
+#include <gio/gio.h>
+
+
+#define TMP_BUF_SIZE  4096
+#define JPEG_ERROR(cinfo,code)  \
+  ((cinfo)->err->msg_code = (code), \
+   (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo)))
+
+
+typedef struct {
+	struct jpeg_source_mgr pub;
+
+	JOCTET  *in_buffer;
+	gsize    in_buffer_size;
+	goffset  bytes_read;
+	JOCTET  *tmp_buffer;
+} mem_source_mgr;
+
+typedef mem_source_mgr * mem_src_ptr;
+
+
+static void
+init_source (j_decompress_ptr cinfo)
+{
+	mem_src_ptr src = (mem_src_ptr) cinfo->src;
+	src->bytes_read = 0;
+}
+
+
+static gboolean
+fill_input_buffer (j_decompress_ptr cinfo)
+{
+	mem_src_ptr src = (mem_src_ptr) cinfo->src;
+	size_t      nbytes;
+
+	if (src->bytes_read + TMP_BUF_SIZE > src->in_buffer_size)
+		nbytes = src->in_buffer_size - src->bytes_read;
+	else
+		nbytes = TMP_BUF_SIZE;
+
+	if (nbytes <= 0) {
+		if (src->bytes_read == 0)
+			JPEG_ERROR (cinfo, G_IO_ERROR_NOT_FOUND);
+
+		/* Insert a fake EOI marker */
+		src->tmp_buffer[0] = (JOCTET) 0xFF;
+		src->tmp_buffer[1] = (JOCTET) JPEG_EOI;
+		nbytes = 2;
+	}
+	else
+		memcpy (src->tmp_buffer, src->in_buffer + src->bytes_read, nbytes);
+
+	src->pub.next_input_byte = src->tmp_buffer;
+	src->pub.bytes_in_buffer = nbytes;
+	src->bytes_read += nbytes;
+
+	return TRUE;
+}
+
+
+static void
+skip_input_data (j_decompress_ptr cinfo,
+		 long             num_bytes)
+{
+	mem_src_ptr src = (mem_src_ptr) cinfo->src;
+
+	src->bytes_read += num_bytes;
+	if (src->bytes_read < 0)
+		src->bytes_read = 0;
+	fill_input_buffer (cinfo);
+}
+
+
+static void
+term_source (j_decompress_ptr cinfo)
+{
+	/* void */
+}
+
+
+void
+_jpeg_memory_src (j_decompress_ptr  cinfo,
+		  void             *in_buffer,
+		  gsize             in_buffer_size)
+{
+	mem_src_ptr src;
+
+	if (cinfo->src == NULL) {
+		cinfo->src = (struct jpeg_source_mgr *)
+			(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo,
+						    JPOOL_PERMANENT,
+						    sizeof (mem_source_mgr));
+		src = (mem_src_ptr) cinfo->src;
+		src->tmp_buffer = (JOCTET *)
+			(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo,
+						    JPOOL_PERMANENT,
+						    TMP_BUF_SIZE * sizeof(JOCTET));
+	}
+
+	src = (mem_src_ptr) cinfo->src;
+	src->pub.init_source = init_source;
+	src->pub.fill_input_buffer = fill_input_buffer;
+	src->pub.skip_input_data = skip_input_data;
+	src->pub.resync_to_restart = jpeg_resync_to_restart;
+	src->pub.term_source = term_source;
+	src->in_buffer = (JOCTET *) in_buffer;
+	src->in_buffer_size = in_buffer_size;
+	src->pub.bytes_in_buffer = 0;
+	src->pub.next_input_byte = NULL;
+}
diff --git a/extensions/image_rotation/jpegtran.c b/extensions/image_rotation/jpegtran.c
new file mode 100644
index 0000000..a71c370
--- /dev/null
+++ b/extensions/image_rotation/jpegtran.c
@@ -0,0 +1,348 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2001, 2002, 2009 The Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+/* based upon file jpegtran.c from the libjpeg package, original copyright
+ * note follows:
+ *
+ * jpegtran.c
+ *
+ * Copyright (C) 1995-1997, Thomas G. Lane.
+ * This file is part of the Independent JPEG Group's software.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * This file contains a command-line user interface for JPEG transcoding.
+ * It is very similar to cjpeg.c, but provides lossless transcoding between
+ * different JPEG file formats.  It also provides some lossless and sort-of-
+ * lossless transformations of JPEG data.
+ */
+
+
+#include <config.h>
+
+#ifdef HAVE_LIBJPEG
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <setjmp.h>
+#include <string.h>
+#include <jpeglib.h>
+#include <glib.h>
+#include <gthumb.h>
+#include "transupp.h"
+#include "jpegtran.h"
+
+
+GQuark
+jpeg_error_quark (void)
+{
+	static GQuark quark;
+
+        if (! quark)
+                quark = g_quark_from_static_string ("jpeg_error");
+
+        return quark;
+}
+
+
+/* error handler data */
+struct error_handler_data {
+	struct jpeg_error_mgr   pub;
+	sigjmp_buf              setjmp_buffer;
+        GError                **error;
+};
+
+
+static void
+fatal_error_handler (j_common_ptr cinfo)
+{
+	struct error_handler_data *errmgr;
+        char buffer[JMSG_LENGTH_MAX];
+
+	errmgr = (struct error_handler_data *) cinfo->err;
+
+        /* Create the message */
+        (* cinfo->err->format_message) (cinfo, buffer);
+
+        if ((errmgr->error != NULL) && (*errmgr->error == NULL))
+                g_set_error (errmgr->error,
+			     JPEG_ERROR,
+			     JPEG_ERROR_FAILED,
+			     "Error interpreting JPEG image\n\n%s",
+                             buffer);
+
+	siglongjmp (errmgr->setjmp_buffer, 1);
+        g_assert_not_reached ();
+}
+
+
+static void
+output_message_handler (j_common_ptr cinfo)
+{
+	/* void */
+}
+
+
+static boolean
+jtransform_perfect_transform (JDIMENSION  image_width,
+			      JDIMENSION  image_height,
+			      int         MCU_width,
+			      int         MCU_height,
+			      JXFORM_CODE transform)
+{
+	boolean result = TRUE;
+
+	/* This function determines if it is possible to perform a lossless
+	   jpeg transformation without trimming, based on the image dimensions
+	   and MCU size. Further details at http://jpegclub.org/jpegtran. */
+
+	switch (transform) {
+	case JXFORM_FLIP_H:
+	case JXFORM_ROT_270:
+		if (image_width % (JDIMENSION) MCU_width)
+			result = FALSE;
+		break;
+	case JXFORM_FLIP_V:
+	case JXFORM_ROT_90:
+		if (image_height % (JDIMENSION) MCU_height)
+			result = FALSE;
+		break;
+	case JXFORM_TRANSVERSE:
+	case JXFORM_ROT_180:
+		if (image_width % (JDIMENSION) MCU_width)
+			result = FALSE;
+		if (image_height % (JDIMENSION) MCU_height)
+			result = FALSE;
+		break;
+	default:
+		break;
+	}
+
+	return result;
+}
+
+
+static gboolean
+jpegtran_internal (struct jpeg_decompress_struct  *srcinfo,
+		   struct jpeg_compress_struct    *dstinfo,
+		   GthTransform                    transformation,
+		   JCOPY_OPTION                    option,
+		   JpegMcuAction                   mcu_action,
+		   GError                        **error)
+{
+	jpeg_transform_info  transformoption;
+	jvirt_barray_ptr    *src_coef_arrays;
+	jvirt_barray_ptr    *dst_coef_arrays;
+	JXFORM_CODE          transform;
+
+	switch (transformation) {
+	case GTH_TRANSFORM_NONE:
+		transform = JXFORM_NONE;
+		break;
+	case GTH_TRANSFORM_FLIP_H:
+		transform = JXFORM_FLIP_H;
+		break;
+	case GTH_TRANSFORM_FLIP_V:
+		transform = JXFORM_FLIP_V;
+		break;
+	case GTH_TRANSFORM_TRANSPOSE:
+		transform = JXFORM_TRANSPOSE;
+		break;
+	case GTH_TRANSFORM_TRANSVERSE:
+		transform = JXFORM_TRANSVERSE;
+		break;
+	case GTH_TRANSFORM_ROTATE_90:
+		transform = JXFORM_ROT_90;
+		break;
+	case GTH_TRANSFORM_ROTATE_180:
+		transform = JXFORM_ROT_180;
+		break;
+	case GTH_TRANSFORM_ROTATE_270:
+		transform = JXFORM_ROT_270;
+		break;
+	}
+
+	transformoption.transform = transform;
+	transformoption.trim = (mcu_action == JPEG_MCU_ACTION_TRIM);
+	transformoption.force_grayscale = FALSE;
+
+	/* Enable saving of extra markers that we want to copy */
+	jcopy_markers_setup (srcinfo, option);
+
+	/* Read file header */
+	(void) jpeg_read_header (srcinfo, TRUE);
+
+	/* Check JPEG Minimal Coding Unit (mcu) */
+	if ((mcu_action == JPEG_MCU_ACTION_ABORT)
+	    && ! jtransform_perfect_transform (srcinfo->image_width,
+					       srcinfo->image_height,
+					       srcinfo->max_h_samp_factor * DCTSIZE,
+					       srcinfo->max_v_samp_factor * DCTSIZE,
+					       transform))
+	{
+		if (error != NULL)
+                	g_set_error (error, JPEG_ERROR, JPEG_ERROR_MCU, "MCU Error");
+		return FALSE;
+	}
+
+	/* Any space needed by a transform option must be requested before
+	 * jpeg_read_coefficients so that memory allocation will be done right.
+	 */
+	jtransform_request_workspace (srcinfo, &transformoption);
+
+	/* Read source file as DCT coefficients */
+	src_coef_arrays = jpeg_read_coefficients (srcinfo);
+
+	/* Initialize destination compression parameters from source values */
+	jpeg_copy_critical_parameters (srcinfo, dstinfo);
+
+
+	/* Do not output a JFIF marker for EXIF thumbnails.
+	 * This is not the optimal way to detect the difference
+	 * between a thumbnail and a normal image, but it works
+	 * well for gThumb. */
+	if (option == JCOPYOPT_NONE)
+		dstinfo->write_JFIF_header = FALSE;
+
+	/* Adjust the markers to create a standard EXIF file if an EXIF marker
+	 * is present in the input. By default, libjpeg creates a JFIF file,
+	 * which is incompatible with the EXIF standard. */
+	jcopy_markers_exif (srcinfo, dstinfo, option);
+
+	/* Adjust destination parameters if required by transform options;
+	 * also find out which set of coefficient arrays will hold the output.
+	 */
+	dst_coef_arrays = jtransform_adjust_parameters (srcinfo,
+							dstinfo,
+							src_coef_arrays,
+							&transformoption);
+
+	/* Start compressor (note no image data is actually written here) */
+	jpeg_write_coefficients (dstinfo, dst_coef_arrays);
+
+	/* Copy to the output file any extra markers that we want to
+	 * preserve */
+	jcopy_markers_execute (srcinfo, dstinfo, option);
+
+	/* Execute image transformation, if any */
+	jtransform_execute_transformation (srcinfo,
+					   dstinfo,
+					   src_coef_arrays,
+					   &transformoption);
+
+	/* Finish compression */
+	jpeg_finish_compress (dstinfo);
+	jpeg_finish_decompress (srcinfo);
+
+	return TRUE;
+}
+
+
+void _jpeg_memory_src  (j_decompress_ptr   cinfo,
+		        void              *in_buffer,
+		        gsize              in_buffer_size);
+void _jpeg_memory_dest (j_compress_ptr     cinfo,
+			void             **out_buffer,
+			gsize             *out_buffer_size);
+
+
+gboolean
+jpegtran (void           *in_buffer,
+	  gsize           in_buffer_size,
+	  void          **out_buffer,
+	  gsize          *out_buffer_size,
+	  GthTransform    transformation,
+	  JpegMcuAction   mcu_action,
+	  GError        **error)
+{
+	struct jpeg_decompress_struct  srcinfo;
+	struct jpeg_compress_struct    dstinfo;
+	struct error_handler_data      jsrcerr, jdsterr;
+	gboolean                       success;
+
+	*out_buffer = NULL;
+	*out_buffer_size = 0;
+
+	/* Initialize the JPEG decompression object with default error
+	 * handling. */
+	srcinfo.err = jpeg_std_error (&(jsrcerr.pub));
+	jsrcerr.pub.error_exit = fatal_error_handler;
+	jsrcerr.pub.output_message = output_message_handler;
+	jsrcerr.error = error;
+
+	jpeg_create_decompress (&srcinfo);
+
+	/* Initialize the JPEG compression object with default error
+	 * handling. */
+	dstinfo.err = jpeg_std_error (&(jdsterr.pub));
+	jdsterr.pub.error_exit = fatal_error_handler;
+	jdsterr.pub.output_message = output_message_handler;
+	jdsterr.error = error;
+
+	jpeg_create_compress (&dstinfo);
+
+	dstinfo.err->trace_level = 0;
+	dstinfo.arith_code = FALSE;
+	dstinfo.optimize_coding = FALSE;
+
+	jsrcerr.pub.trace_level = jdsterr.pub.trace_level;
+	srcinfo.mem->max_memory_to_use = dstinfo.mem->max_memory_to_use;
+
+	/* Decompression error handler */
+	if (sigsetjmp (jsrcerr.setjmp_buffer, 1)) {
+		jpeg_destroy_compress (&dstinfo);
+		jpeg_destroy_decompress (&srcinfo);
+		return FALSE;
+	}
+
+	/* Compression error handler */
+	if (sigsetjmp (jdsterr.setjmp_buffer, 1)) {
+		jpeg_destroy_compress (&dstinfo);
+		jpeg_destroy_decompress (&srcinfo);
+		return FALSE;
+	}
+
+	/* Specify data source for decompression */
+	_jpeg_memory_src (&srcinfo, in_buffer, in_buffer_size);
+
+	/* Specify data destination for compression */
+	_jpeg_memory_dest (&dstinfo, out_buffer, out_buffer_size);
+
+	/* Apply transformation */
+	success = jpegtran_internal (&srcinfo, &dstinfo, transformation, JCOPYOPT_ALL, mcu_action, error);
+
+	/* Release memory */
+	jpeg_destroy_compress (&dstinfo);
+	jpeg_destroy_decompress (&srcinfo);
+
+	if (success) {
+		gth_hook_invoke ("jpegtran-after", out_buffer, out_buffer_size, &transformation);
+	}
+	else {
+		g_free (*out_buffer);
+		*out_buffer_size = 0;
+	}
+
+	return success;
+}
+
+#endif /* HAVE_LIBJPEG */
diff --git a/extensions/image_rotation/jpegtran.h b/extensions/image_rotation/jpegtran.h
new file mode 100644
index 0000000..ec62a5f
--- /dev/null
+++ b/extensions/image_rotation/jpegtran.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2001, 2002, 2009 The Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef JPEGTRAN_H
+#define JPEGTRAN_H
+
+#include <config.h>
+#include <glib.h>
+#include "transupp.h"
+
+
+typedef enum {
+	JPEG_ERROR_FAILED,
+	JPEG_ERROR_MCU
+} JpegErrorCode;
+
+
+#define JPEG_ERROR jpeg_error_quark ()
+GQuark jpeg_error_quark (void);
+
+
+typedef enum {
+	JPEG_MCU_ACTION_TRIM,
+	JPEG_MCU_ACTION_DONT_TRIM,
+	JPEG_MCU_ACTION_ABORT
+} JpegMcuAction;
+
+
+gboolean   jpegtran  (void           *in_buffer,
+		      gsize           in_buffer_size,
+		      void          **out_buffer,
+		      gsize          *out_buffer_size,
+		      GthTransform    transformation,
+		      JpegMcuAction   mcu_action,
+		      GError        **error);
+
+#endif /* JPEGTRAN_H */
diff --git a/extensions/image_rotation/main.c b/extensions/image_rotation/main.c
new file mode 100644
index 0000000..eeba6c7
--- /dev/null
+++ b/extensions/image_rotation/main.c
@@ -0,0 +1,63 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include "callbacks.h"
+
+
+G_MODULE_EXPORT void
+gthumb_extension_activate (void)
+{
+	/**
+	 * Called after successfully rotating a jpeg image
+	 *
+	 * @out_buffer (void **): pointer to file data
+	 * @out_buffer_size (gsize *): pointer to file data size
+	 * @tranform (GthTransform *): the transformation applied to the file
+	 **/
+	gth_hook_register ("jpegtran-after", 3);
+
+	gth_hook_add_callback ("gth-browser-construct", 10, G_CALLBACK (ir__gth_browser_construct_cb), NULL);
+	gth_hook_add_callback ("gth-browser-update-sensitivity", 10, G_CALLBACK (ir__gth_browser_update_sensitivity_cb), NULL);
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_deactivate (void)
+{
+}
+
+
+G_MODULE_EXPORT gboolean
+gthumb_extension_is_configurable (void)
+{
+	return FALSE;
+}
+
+
+G_MODULE_EXPORT void
+gthumb_extension_configure (GtkWindow *parent)
+{
+}
diff --git a/extensions/image_rotation/rotation-utils.c b/extensions/image_rotation/rotation-utils.c
new file mode 100644
index 0000000..8c691e3
--- /dev/null
+++ b/extensions/image_rotation/rotation-utils.c
@@ -0,0 +1,357 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2007, 2009 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include "rotation-utils.h"
+
+
+enum {
+	GTH_RESPONSE_TRIM
+};
+
+
+typedef struct {
+	GtkWidget        *dialog;
+	GtkWindow        *parent_window;
+	gboolean          parent_is_modal;
+	TrimResponseFunc  done_func;
+	gpointer          done_data;
+} AskTrimData;
+
+
+static void
+ask_whether_to_trim_response_cb (GtkDialog *dialog,
+                                 gint       response,
+                                 gpointer   user_data)
+{
+	AskTrimData *data = user_data;
+
+ 	gtk_widget_destroy (data->dialog);
+	if (data->parent_is_modal)
+		gtk_window_set_modal (data->parent_window, TRUE);
+
+	if (data->done_func != NULL) {
+		JpegMcuAction action;
+
+		switch (response) {
+		case GTH_RESPONSE_TRIM:
+			action = JPEG_MCU_ACTION_TRIM;
+			break;
+		case GTK_RESPONSE_OK:
+			action = JPEG_MCU_ACTION_DONT_TRIM;
+			break;
+		default:
+			action = JPEG_MCU_ACTION_ABORT;
+			break;
+		}
+		data->done_func (action, data->done_data);
+	}
+
+	g_free (data);
+}
+
+
+void
+ask_whether_to_trim (GtkWindow        *parent_window,
+		     GthFileData      *file_data,
+		     TrimResponseFunc  done_func,
+		     gpointer          done_data)
+{
+	AskTrimData *data;
+	char        *filename;
+	char        *msg;
+
+	/* If the user disabled the warning dialog trim the image */
+
+	/*
+	 * FIXME
+	 if (! eel_gconf_get_boolean (PREF_MSG_JPEG_MCU_WARNING, TRUE)) {
+		if (done_func != NULL)
+			done_func (JPEG_MCU_ACTION_TRIM, done_data);
+		return;
+	}
+	*/
+
+	/*
+	 * Image dimensions are not multiples of the jpeg minimal coding unit (mcu).
+	 * Warn about possible image distortions along one or more edges.
+	 */
+
+	data = g_new0 (AskTrimData, 1);
+	data->done_func = done_func;
+	data->done_data = done_data;
+	data->parent_window = parent_window;
+	data->parent_is_modal = FALSE;
+
+	if (parent_window != NULL) {
+		data->parent_is_modal = gtk_window_get_modal (parent_window);
+		if (data->parent_is_modal)
+			gtk_window_set_modal (data->parent_window, FALSE);
+	}
+
+	filename = g_file_get_parse_name (file_data->file);
+	msg = g_strdup_printf (_("Problem transforming the image: %s"), filename);
+	data->dialog = _gtk_message_dialog_new (parent_window,
+						GTK_DIALOG_MODAL,
+						GTK_STOCK_DIALOG_WARNING,
+						msg,
+						_("This transformation may introduce small image distortions along "
+                                                  "one or more edges, because the image dimensions are not multiples of 8.\n\nThe distortion "
+						  "is reversible, however. If the resulting image is unacceptable, simply apply the reverse "
+						  "transformation to return to the original image.\n\nYou can also choose to discard (or trim) any "
+						  "untransformable edge pixels. For practical use, this mode gives the best looking results, "
+						  "but the transformation is not strictly lossless anymore."),
+						_("_Trim"), GTH_RESPONSE_TRIM,
+					        GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+					        _("_Accept distortion"), GTK_RESPONSE_OK,
+					        NULL);
+	gtk_dialog_set_default_response (GTK_DIALOG (data->dialog), GTK_RESPONSE_OK);
+	g_signal_connect (G_OBJECT (data->dialog),
+			  "response",
+			  G_CALLBACK (ask_whether_to_trim_response_cb),
+			  data);
+	gtk_widget_show (data->dialog);
+
+	g_free (msg);
+	g_free (filename);
+}
+
+
+/* -- get_next_transformation -- */
+
+
+static GthTransform
+get_next_value_rotation_90 (GthTransform value)
+{
+	static GthTransform new_value [8] = {6, 7, 8, 5, 2, 3, 4, 1};
+	return new_value[value - 1];
+}
+
+
+static GthTransform
+get_next_value_mirror (GthTransform value)
+{
+	static GthTransform new_value [8] = {2, 1, 4, 3, 6, 5, 8, 7};
+	return new_value[value - 1];
+}
+
+
+static GthTransform
+get_next_value_flip (GthTransform value)
+{
+	static GthTransform new_value [8] = {4, 3, 2, 1, 8, 7, 6, 5};
+	return new_value[value - 1];
+}
+
+
+GthTransform
+get_next_transformation (GthTransform original,
+			 GthTransform transform)
+{
+	GthTransform result;
+
+	result = ((original >= 1) && (original <= 8)) ? original : GTH_TRANSFORM_NONE;
+	switch (transform) {
+	case GTH_TRANSFORM_NONE:
+		break;
+	case GTH_TRANSFORM_ROTATE_90:
+		result = get_next_value_rotation_90 (result);
+		break;
+	case GTH_TRANSFORM_ROTATE_180:
+		result = get_next_value_rotation_90 (result);
+		result = get_next_value_rotation_90 (result);
+		break;
+	case GTH_TRANSFORM_ROTATE_270:
+		result = get_next_value_rotation_90 (result);
+		result = get_next_value_rotation_90 (result);
+		result = get_next_value_rotation_90 (result);
+		break;
+	case GTH_TRANSFORM_FLIP_H:
+		result = get_next_value_mirror (result);
+		break;
+	case GTH_TRANSFORM_FLIP_V:
+		result = get_next_value_flip (result);
+		break;
+	case GTH_TRANSFORM_TRANSPOSE:
+		result = get_next_value_rotation_90 (result);
+		result = get_next_value_mirror (result);
+		break;
+	case GTH_TRANSFORM_TRANSVERSE:
+		result = get_next_value_rotation_90 (result);
+		result = get_next_value_flip (result);
+		break;
+	}
+
+	return result;
+}
+
+
+/* -- apply_transformation_async -- */
+
+
+typedef struct {
+	GthFileData   *file_data;
+	GthTransform   transform;
+	JpegMcuAction  mcu_action;
+	GCancellable  *cancellable;
+	ReadyFunc      ready_func;
+	gpointer       user_data;
+} TransformatioData;
+
+
+static void
+transformation_data_free (TransformatioData *tdata)
+{
+	_g_object_unref (tdata->file_data);
+	_g_object_unref (tdata->cancellable);
+	g_free (tdata);
+}
+
+
+static void
+write_file_ready_cb (void     *buffer,
+		     gsize     count,
+		     GError   *error,
+		     gpointer  user_data)
+{
+	TransformatioData *tdata = user_data;
+
+	g_free (buffer);
+	tdata->ready_func (error, tdata->user_data);
+	transformation_data_free (tdata);
+}
+
+
+static void
+pixbuf_saved_cb (GthFileData *file_data,
+		 GError      *error,
+		 gpointer     user_data)
+{
+	TransformatioData *tdata = user_data;
+
+	tdata->ready_func (error, tdata->user_data);
+	transformation_data_free (tdata);
+}
+
+
+static void
+file_buffer_ready_cb (void     *buffer,
+		      gsize     count,
+		      GError   *error,
+		      gpointer  user_data)
+{
+	TransformatioData *tdata = user_data;
+
+	if (error != NULL) {
+		tdata->ready_func (error, tdata->user_data);
+		transformation_data_free (tdata);
+		return;
+	}
+
+#ifdef HAVE_LIBJPEG
+	if (g_content_type_equals (gth_file_data_get_mime_type (tdata->file_data), "image/jpeg")) {
+		void  *out_buffer;
+		gsize  out_buffer_size;
+
+		if (! jpegtran (buffer,
+				count,
+				&out_buffer,
+				&out_buffer_size,
+				tdata->transform,
+				tdata->mcu_action,
+				&error))
+		{
+			tdata->ready_func (error, tdata->user_data);
+			transformation_data_free (tdata);
+			return;
+		}
+
+		g_write_file_async (tdata->file_data->file,
+				    out_buffer,
+		    		    out_buffer_size,
+				    G_PRIORITY_DEFAULT,
+				    tdata->cancellable,
+				    write_file_ready_cb,
+				    tdata);
+	}
+	else
+#endif /* HAVE_LIBJPEG */
+	{
+		GInputStream *istream;
+		GdkPixbuf    *original_pixbuf;
+		GdkPixbuf    *transformed_pixbuf;
+		char         *pixbuf_type;
+
+		istream = g_memory_input_stream_new_from_data (buffer, count, NULL);
+		original_pixbuf = gdk_pixbuf_new_from_stream (istream, tdata->cancellable, &error);
+		if (original_pixbuf == NULL) {
+			tdata->ready_func (error, tdata->user_data);
+			transformation_data_free (tdata);
+			return;
+		}
+
+		transformed_pixbuf = _gdk_pixbuf_transform (original_pixbuf, tdata->transform);
+		pixbuf_type = get_pixbuf_type_from_mime_type (gth_file_data_get_mime_type (tdata->file_data));
+		_gdk_pixbuf_save_async (transformed_pixbuf,
+					tdata->file_data,
+					pixbuf_type,
+					NULL,
+					NULL,
+					pixbuf_saved_cb,
+					tdata);
+
+		g_free (pixbuf_type);
+		g_object_unref (transformed_pixbuf);
+		g_object_unref (original_pixbuf);
+		g_object_unref (istream);
+	}
+}
+
+
+void
+apply_transformation_async (GthFileData   *file_data,
+			    GthTransform   transform,
+			    JpegMcuAction  mcu_action,
+			    GCancellable  *cancellable,
+			    ReadyFunc      ready_func,
+			    gpointer       user_data)
+{
+	TransformatioData *tdata;
+
+	tdata = g_new0 (TransformatioData, 1);
+	tdata->file_data = g_object_ref (file_data);
+	tdata->transform = transform;
+	tdata->mcu_action = mcu_action;
+	tdata->cancellable = _g_object_ref (cancellable);
+	tdata->ready_func = ready_func;
+	tdata->user_data = user_data;
+
+	g_load_file_async (tdata->file_data->file,
+			   G_PRIORITY_DEFAULT,
+			   tdata->cancellable,
+			   file_buffer_ready_cb,
+			   tdata);
+}
diff --git a/extensions/image_rotation/rotation-utils.h b/extensions/image_rotation/rotation-utils.h
new file mode 100644
index 0000000..0f153d9
--- /dev/null
+++ b/extensions/image_rotation/rotation-utils.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2005, 2009 Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef ROTATION_UTILS_H
+#define ROTATION_UTILS_H
+
+#include <config.h>
+#include <gtk/gtk.h>
+#include <gthumb.h>
+#include "jpegtran.h"
+
+typedef void (*TrimResponseFunc) (JpegMcuAction action, gpointer user_data);
+
+void            ask_whether_to_trim            (GtkWindow        *parent_window,
+		     				GthFileData      *file_data,
+		     				TrimResponseFunc  done_func,
+		     				gpointer          done_data);
+GthTransform	get_next_transformation	       (GthTransform      original,
+						GthTransform      transform);
+void            apply_transformation_async     (GthFileData      *file_data,
+						GthTransform      transform,
+						JpegMcuAction     mcu_action,
+						GCancellable     *cancellable,
+						ReadyFunc         ready_func,
+						gpointer          data);
+
+#endif /* ROTATION_UTILS_H */
diff --git a/extensions/image_rotation/transupp.c b/extensions/image_rotation/transupp.c
new file mode 100644
index 0000000..c9daa76
--- /dev/null
+++ b/extensions/image_rotation/transupp.c
@@ -0,0 +1,1059 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2001, 2002 The Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+/* based upon file transupp.c from the libjpeg package, original copyright
+ * note follows:
+.*
+ *
+ * transupp.c
+ *
+ * Copyright (C) 1997, Thomas G. Lane.
+ * This file is part of the Independent JPEG Group's software.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * This file contains image transformation routines and other utility code
+ * used by the jpegtran sample application.  These are NOT part of the core
+ * JPEG library.  But we keep these routines separate from jpegtran.c to
+ * ease the task of maintaining jpegtran-like programs that have other user
+ * interfaces.
+ */
+
+#include <config.h>
+
+#define SAVE_MARKERS_SUPPORTED 1
+
+#include <stdio.h>
+#include <jpeglib.h>
+#include "transupp.h"		/* My own external interface */
+
+#ifndef MAX
+#define MAX(a, b)  (((a) > (b)) ? (a) : (b))
+#endif
+
+#ifdef __CYGWIN__
+#define height_in_blocks height_in_data_units
+#define width_in_blocks  width_in_data_units
+#endif
+
+enum {
+  JERR_CONVERSION_NOTIMPL
+};
+
+#define ERREXIT(cinfo,code)  \
+  ((cinfo)->err->msg_code = (code), \
+   (*(cinfo)->err->error_exit) ((j_common_ptr) (cinfo)))
+
+
+static long
+jround_up (long a, long b)
+/* Compute a rounded up to next multiple of b, ie, ceil(a/b)*b */
+/* Assumes a >= 0, b > 0 */
+{
+  a += b - 1L;
+  return a - (a % b);
+}
+
+
+static void
+jcopy_block_row (JBLOCKROW input_row, JBLOCKROW output_row,
+		 JDIMENSION num_blocks)
+/* Copy a row of coefficient blocks from one place to another. */
+{
+  register JCOEFPTR inptr, outptr;
+  register long count;
+
+  inptr = (JCOEFPTR) input_row;
+  outptr = (JCOEFPTR) output_row;
+  for (count = (long) num_blocks * DCTSIZE2; count > 0; count--) {
+    *outptr++ = *inptr++;
+  }
+}
+
+
+/*
+ * Lossless image transformation routines.  These routines work on DCT
+ * coefficient arrays and thus do not require any lossy decompression
+ * or recompression of the image.
+ * Thanks to Guido Vollbeding for the initial design and code of this feature.
+ *
+ * Horizontal flipping is done in-place, using a single top-to-bottom
+ * pass through the virtual source array.  It will thus be much the
+ * fastest option for images larger than main memory.
+ *
+ * The other routines require a set of destination virtual arrays, so they
+ * need twice as much memory as jpegtran normally does.  The destination
+ * arrays are always written in normal scan order (top to bottom) because
+ * the virtual array manager expects this.  The source arrays will be scanned
+ * in the corresponding order, which means multiple passes through the source
+ * arrays for most of the transforms.  That could result in much thrashing
+ * if the image is larger than main memory.
+ *
+ * Some notes about the operating environment of the individual transform
+ * routines:
+ * 1. Both the source and destination virtual arrays are allocated from the
+ *    source JPEG object, and therefore should be manipulated by calling the
+ *    source's memory manager.
+ * 2. The destination's component count should be used.  It may be smaller
+ *    than the source's when forcing to grayscale.
+ * 3. Likewise the destination's sampling factors should be used.  When
+ *    forcing to grayscale the destination's sampling factors will be all 1,
+ *    and we may as well take that as the effective iMCU size.
+ * 4. When "trim" is in effect, the destination's dimensions will be the
+ *    trimmed values but the source's will be untrimmed.
+ * 5. All the routines assume that the source and destination buffers are
+ *    padded out to a full iMCU boundary.  This is true, although for the
+ *    source buffer it is an undocumented property of jdcoefct.c.
+ * Notes 2,3,4 boil down to this: generally we should use the destination's
+ * dimensions and ignore the source's.
+ */
+
+
+static void
+do_flip_h (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+	   jvirt_barray_ptr *src_coef_arrays)
+/* Horizontal flip; done in-place, so no separate dest array is required */
+{
+  JDIMENSION MCU_cols, comp_width, blk_x, blk_y;
+  int ci, k, offset_y;
+  JBLOCKARRAY buffer;
+  JCOEFPTR ptr1, ptr2;
+  JCOEF temp1, temp2;
+  jpeg_component_info *compptr;
+
+  /* Horizontal mirroring of DCT blocks is accomplished by swapping
+   * pairs of blocks in-place.  Within a DCT block, we perform horizontal
+   * mirroring by changing the signs of odd-numbered columns.
+   * Partial iMCUs at the right edge are left untouched.
+   */
+  MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE);
+
+  for (ci = 0; ci < dstinfo->num_components; ci++) {
+    compptr = dstinfo->comp_info + ci;
+    comp_width = MCU_cols * compptr->h_samp_factor;
+    for (blk_y = 0; blk_y < compptr->height_in_blocks;
+	 blk_y += compptr->v_samp_factor) {
+      buffer = (*srcinfo->mem->access_virt_barray)
+	((j_common_ptr) srcinfo, src_coef_arrays[ci], blk_y,
+	 (JDIMENSION) compptr->v_samp_factor, TRUE);
+      for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+	for (blk_x = 0; blk_x * 2 < comp_width; blk_x++) {
+	  ptr1 = buffer[offset_y][blk_x];
+	  ptr2 = buffer[offset_y][comp_width - blk_x - 1];
+	  /* this unrolled loop doesn't need to know which row it's on... */
+	  for (k = 0; k < DCTSIZE2; k += 2) {
+	    temp1 = *ptr1;	/* swap even column */
+	    temp2 = *ptr2;
+	    *ptr1++ = temp2;
+	    *ptr2++ = temp1;
+	    temp1 = *ptr1;	/* swap odd column with sign change */
+	    temp2 = *ptr2;
+	    *ptr1++ = -temp2;
+	    *ptr2++ = -temp1;
+	  }
+	}
+      }
+    }
+  }
+}
+
+
+static void
+do_flip_v (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+	   jvirt_barray_ptr *src_coef_arrays,
+	   jvirt_barray_ptr *dst_coef_arrays)
+/* Vertical flip */
+{
+  JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y;
+  int ci, i, j, offset_y;
+  JBLOCKARRAY src_buffer, dst_buffer;
+  JBLOCKROW src_row_ptr, dst_row_ptr;
+  JCOEFPTR src_ptr, dst_ptr;
+  jpeg_component_info *compptr;
+
+  /* We output into a separate array because we can't touch different
+   * rows of the source virtual array simultaneously.  Otherwise, this
+   * is a pretty straightforward analog of horizontal flip.
+   * Within a DCT block, vertical mirroring is done by changing the signs
+   * of odd-numbered rows.
+   * Partial iMCUs at the bottom edge are copied verbatim.
+   */
+  MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE);
+
+  for (ci = 0; ci < dstinfo->num_components; ci++) {
+    compptr = dstinfo->comp_info + ci;
+    comp_height = MCU_rows * compptr->v_samp_factor;
+    for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+	 dst_blk_y += compptr->v_samp_factor) {
+      dst_buffer = (*srcinfo->mem->access_virt_barray)
+	((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+	 (JDIMENSION) compptr->v_samp_factor, TRUE);
+      if (dst_blk_y < comp_height) {
+	/* Row is within the mirrorable area. */
+	src_buffer = (*srcinfo->mem->access_virt_barray)
+	  ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+	   comp_height - dst_blk_y - (JDIMENSION) compptr->v_samp_factor,
+	   (JDIMENSION) compptr->v_samp_factor, FALSE);
+      } else {
+	/* Bottom-edge blocks will be copied verbatim. */
+	src_buffer = (*srcinfo->mem->access_virt_barray)
+	  ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_y,
+	   (JDIMENSION) compptr->v_samp_factor, FALSE);
+      }
+      for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+	if (dst_blk_y < comp_height) {
+	  /* Row is within the mirrorable area. */
+	  dst_row_ptr = dst_buffer[offset_y];
+	  src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1];
+	  for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+	       dst_blk_x++) {
+	    dst_ptr = dst_row_ptr[dst_blk_x];
+	    src_ptr = src_row_ptr[dst_blk_x];
+	    for (i = 0; i < DCTSIZE; i += 2) {
+	      /* copy even row */
+	      for (j = 0; j < DCTSIZE; j++)
+		*dst_ptr++ = *src_ptr++;
+	      /* copy odd row with sign change */
+	      for (j = 0; j < DCTSIZE; j++)
+		*dst_ptr++ = - *src_ptr++;
+	    }
+	  }
+	} else {
+	  /* Just copy row verbatim. */
+	  jcopy_block_row(src_buffer[offset_y], dst_buffer[offset_y],
+			  compptr->width_in_blocks);
+	}
+      }
+    }
+  }
+}
+
+
+static void
+do_transpose (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+	      jvirt_barray_ptr *src_coef_arrays,
+	      jvirt_barray_ptr *dst_coef_arrays)
+/* Transpose source into destination */
+{
+  JDIMENSION dst_blk_x, dst_blk_y;
+  int ci, i, j, offset_x, offset_y;
+  JBLOCKARRAY src_buffer, dst_buffer;
+  JCOEFPTR src_ptr, dst_ptr;
+  jpeg_component_info *compptr;
+
+  /* Transposing pixels within a block just requires transposing the
+   * DCT coefficients.
+   * Partial iMCUs at the edges require no special treatment; we simply
+   * process all the available DCT blocks for every component.
+   */
+  for (ci = 0; ci < dstinfo->num_components; ci++) {
+    compptr = dstinfo->comp_info + ci;
+    for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+	 dst_blk_y += compptr->v_samp_factor) {
+      dst_buffer = (*srcinfo->mem->access_virt_barray)
+	((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+	 (JDIMENSION) compptr->v_samp_factor, TRUE);
+      for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+	for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+	     dst_blk_x += compptr->h_samp_factor) {
+	  src_buffer = (*srcinfo->mem->access_virt_barray)
+	    ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x,
+	     (JDIMENSION) compptr->h_samp_factor, FALSE);
+	  for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+	    src_ptr = src_buffer[offset_x][dst_blk_y + offset_y];
+	    dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+	    for (i = 0; i < DCTSIZE; i++)
+	      for (j = 0; j < DCTSIZE; j++)
+		dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+	  }
+	}
+      }
+    }
+  }
+}
+
+
+static void
+do_rot_90 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+	   jvirt_barray_ptr *src_coef_arrays,
+	   jvirt_barray_ptr *dst_coef_arrays)
+/* 90 degree rotation is equivalent to
+ *   1. Transposing the image;
+ *   2. Horizontal mirroring.
+ * These two steps are merged into a single processing routine.
+ */
+{
+  JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y;
+  int ci, i, j, offset_x, offset_y;
+  JBLOCKARRAY src_buffer, dst_buffer;
+  JCOEFPTR src_ptr, dst_ptr;
+  jpeg_component_info *compptr;
+
+  /* Because of the horizontal mirror step, we can't process partial iMCUs
+   * at the (output) right edge properly.  They just get transposed and
+   * not mirrored.
+   */
+  MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE);
+
+  for (ci = 0; ci < dstinfo->num_components; ci++) {
+    compptr = dstinfo->comp_info + ci;
+    comp_width = MCU_cols * compptr->h_samp_factor;
+    for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+	 dst_blk_y += compptr->v_samp_factor) {
+      dst_buffer = (*srcinfo->mem->access_virt_barray)
+	((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+	 (JDIMENSION) compptr->v_samp_factor, TRUE);
+      for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+	for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+	     dst_blk_x += compptr->h_samp_factor) {
+	  src_buffer = (*srcinfo->mem->access_virt_barray)
+	    ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x,
+	     (JDIMENSION) compptr->h_samp_factor, FALSE);
+	  for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+	    src_ptr = src_buffer[offset_x][dst_blk_y + offset_y];
+	    if (dst_blk_x < comp_width) {
+	      /* Block is within the mirrorable area. */
+	      dst_ptr = dst_buffer[offset_y]
+		[comp_width - dst_blk_x - offset_x - 1];
+	      for (i = 0; i < DCTSIZE; i++) {
+		for (j = 0; j < DCTSIZE; j++)
+		  dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+		i++;
+		for (j = 0; j < DCTSIZE; j++)
+		  dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+	      }
+	    } else {
+	      /* Edge blocks are transposed but not mirrored. */
+	      dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+	      for (i = 0; i < DCTSIZE; i++)
+		for (j = 0; j < DCTSIZE; j++)
+		  dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+	    }
+	  }
+	}
+      }
+    }
+  }
+}
+
+
+static void
+do_rot_270 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+	    jvirt_barray_ptr *src_coef_arrays,
+	    jvirt_barray_ptr *dst_coef_arrays)
+/* 270 degree rotation is equivalent to
+ *   1. Horizontal mirroring;
+ *   2. Transposing the image.
+ * These two steps are merged into a single processing routine.
+ */
+{
+  JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y;
+  int ci, i, j, offset_x, offset_y;
+  JBLOCKARRAY src_buffer, dst_buffer;
+  JCOEFPTR src_ptr, dst_ptr;
+  jpeg_component_info *compptr;
+
+  /* Because of the horizontal mirror step, we can't process partial iMCUs
+   * at the (output) bottom edge properly.  They just get transposed and
+   * not mirrored.
+   */
+  MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE);
+
+  for (ci = 0; ci < dstinfo->num_components; ci++) {
+    compptr = dstinfo->comp_info + ci;
+    comp_height = MCU_rows * compptr->v_samp_factor;
+    for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+	 dst_blk_y += compptr->v_samp_factor) {
+      dst_buffer = (*srcinfo->mem->access_virt_barray)
+	((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+	 (JDIMENSION) compptr->v_samp_factor, TRUE);
+      for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+	for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+	     dst_blk_x += compptr->h_samp_factor) {
+	  src_buffer = (*srcinfo->mem->access_virt_barray)
+	    ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x,
+	     (JDIMENSION) compptr->h_samp_factor, FALSE);
+	  for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+	    dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+	    if (dst_blk_y < comp_height) {
+	      /* Block is within the mirrorable area. */
+	      src_ptr = src_buffer[offset_x]
+		[comp_height - dst_blk_y - offset_y - 1];
+	      for (i = 0; i < DCTSIZE; i++) {
+		for (j = 0; j < DCTSIZE; j++) {
+		  dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+		  j++;
+		  dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+		}
+	      }
+	    } else {
+	      /* Edge blocks are transposed but not mirrored. */
+	      src_ptr = src_buffer[offset_x][dst_blk_y + offset_y];
+	      for (i = 0; i < DCTSIZE; i++)
+		for (j = 0; j < DCTSIZE; j++)
+		  dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+	    }
+	  }
+	}
+      }
+    }
+  }
+}
+
+
+static void
+do_rot_180 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+	    jvirt_barray_ptr *src_coef_arrays,
+	    jvirt_barray_ptr *dst_coef_arrays)
+/* 180 degree rotation is equivalent to
+ *   1. Vertical mirroring;
+ *   2. Horizontal mirroring.
+ * These two steps are merged into a single processing routine.
+ */
+{
+  JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y;
+  int ci, i, j, offset_y;
+  JBLOCKARRAY src_buffer, dst_buffer;
+  JBLOCKROW src_row_ptr, dst_row_ptr;
+  JCOEFPTR src_ptr, dst_ptr;
+  jpeg_component_info *compptr;
+
+  MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE);
+  MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE);
+
+  for (ci = 0; ci < dstinfo->num_components; ci++) {
+    compptr = dstinfo->comp_info + ci;
+    comp_width = MCU_cols * compptr->h_samp_factor;
+    comp_height = MCU_rows * compptr->v_samp_factor;
+    for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+	 dst_blk_y += compptr->v_samp_factor) {
+      dst_buffer = (*srcinfo->mem->access_virt_barray)
+	((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+	 (JDIMENSION) compptr->v_samp_factor, TRUE);
+      if (dst_blk_y < comp_height) {
+	/* Row is within the vertically mirrorable area. */
+	src_buffer = (*srcinfo->mem->access_virt_barray)
+	  ((j_common_ptr) srcinfo, src_coef_arrays[ci],
+	   comp_height - dst_blk_y - (JDIMENSION) compptr->v_samp_factor,
+	   (JDIMENSION) compptr->v_samp_factor, FALSE);
+      } else {
+	/* Bottom-edge rows are only mirrored horizontally. */
+	src_buffer = (*srcinfo->mem->access_virt_barray)
+	  ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_y,
+	   (JDIMENSION) compptr->v_samp_factor, FALSE);
+      }
+      for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+	if (dst_blk_y < comp_height) {
+	  /* Row is within the mirrorable area. */
+	  dst_row_ptr = dst_buffer[offset_y];
+	  src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1];
+	  /* Process the blocks that can be mirrored both ways. */
+	  for (dst_blk_x = 0; dst_blk_x < comp_width; dst_blk_x++) {
+	    dst_ptr = dst_row_ptr[dst_blk_x];
+	    src_ptr = src_row_ptr[comp_width - dst_blk_x - 1];
+	    for (i = 0; i < DCTSIZE; i += 2) {
+	      /* For even row, negate every odd column. */
+	      for (j = 0; j < DCTSIZE; j += 2) {
+		*dst_ptr++ = *src_ptr++;
+		*dst_ptr++ = - *src_ptr++;
+	      }
+	      /* For odd row, negate every even column. */
+	      for (j = 0; j < DCTSIZE; j += 2) {
+		*dst_ptr++ = - *src_ptr++;
+		*dst_ptr++ = *src_ptr++;
+	      }
+	    }
+	  }
+	  /* Any remaining right-edge blocks are only mirrored vertically. */
+	  for (; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) {
+	    dst_ptr = dst_row_ptr[dst_blk_x];
+	    src_ptr = src_row_ptr[dst_blk_x];
+	    for (i = 0; i < DCTSIZE; i += 2) {
+	      for (j = 0; j < DCTSIZE; j++)
+		*dst_ptr++ = *src_ptr++;
+	      for (j = 0; j < DCTSIZE; j++)
+		*dst_ptr++ = - *src_ptr++;
+	    }
+	  }
+	} else {
+	  /* Remaining rows are just mirrored horizontally. */
+	  dst_row_ptr = dst_buffer[offset_y];
+	  src_row_ptr = src_buffer[offset_y];
+	  /* Process the blocks that can be mirrored. */
+	  for (dst_blk_x = 0; dst_blk_x < comp_width; dst_blk_x++) {
+	    dst_ptr = dst_row_ptr[dst_blk_x];
+	    src_ptr = src_row_ptr[comp_width - dst_blk_x - 1];
+	    for (i = 0; i < DCTSIZE2; i += 2) {
+	      *dst_ptr++ = *src_ptr++;
+	      *dst_ptr++ = - *src_ptr++;
+	    }
+	  }
+	  /* Any remaining right-edge blocks are only copied. */
+	  for (; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) {
+	    dst_ptr = dst_row_ptr[dst_blk_x];
+	    src_ptr = src_row_ptr[dst_blk_x];
+	    for (i = 0; i < DCTSIZE2; i++)
+	      *dst_ptr++ = *src_ptr++;
+	  }
+	}
+      }
+    }
+  }
+}
+
+
+static void
+do_transverse (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+	       jvirt_barray_ptr *src_coef_arrays,
+	       jvirt_barray_ptr *dst_coef_arrays)
+/* Transverse transpose is equivalent to
+ *   1. 180 degree rotation;
+ *   2. Transposition;
+ * or
+ *   1. Horizontal mirroring;
+ *   2. Transposition;
+ *   3. Horizontal mirroring.
+ * These steps are merged into a single processing routine.
+ */
+{
+  JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y;
+  int ci, i, j, offset_x, offset_y;
+  JBLOCKARRAY src_buffer, dst_buffer;
+  JCOEFPTR src_ptr, dst_ptr;
+  jpeg_component_info *compptr;
+
+  MCU_cols = dstinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE);
+  MCU_rows = dstinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE);
+
+  for (ci = 0; ci < dstinfo->num_components; ci++) {
+    compptr = dstinfo->comp_info + ci;
+    comp_width = MCU_cols * compptr->h_samp_factor;
+    comp_height = MCU_rows * compptr->v_samp_factor;
+    for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
+	 dst_blk_y += compptr->v_samp_factor) {
+      dst_buffer = (*srcinfo->mem->access_virt_barray)
+	((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
+	 (JDIMENSION) compptr->v_samp_factor, TRUE);
+      for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
+	for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
+	     dst_blk_x += compptr->h_samp_factor) {
+	  src_buffer = (*srcinfo->mem->access_virt_barray)
+	    ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x,
+	     (JDIMENSION) compptr->h_samp_factor, FALSE);
+	  for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
+	    if (dst_blk_y < comp_height) {
+	      src_ptr = src_buffer[offset_x]
+		[comp_height - dst_blk_y - offset_y - 1];
+	      if (dst_blk_x < comp_width) {
+		/* Block is within the mirrorable area. */
+		dst_ptr = dst_buffer[offset_y]
+		  [comp_width - dst_blk_x - offset_x - 1];
+		for (i = 0; i < DCTSIZE; i++) {
+		  for (j = 0; j < DCTSIZE; j++) {
+		    dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+		    j++;
+		    dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+		  }
+		  i++;
+		  for (j = 0; j < DCTSIZE; j++) {
+		    dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+		    j++;
+		    dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+		  }
+		}
+	      } else {
+		/* Right-edge blocks are mirrored in y only */
+		dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+		for (i = 0; i < DCTSIZE; i++) {
+		  for (j = 0; j < DCTSIZE; j++) {
+		    dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+		    j++;
+		    dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+		  }
+		}
+	      }
+	    } else {
+	      src_ptr = src_buffer[offset_x][dst_blk_y + offset_y];
+	      if (dst_blk_x < comp_width) {
+		/* Bottom-edge blocks are mirrored in x only */
+		dst_ptr = dst_buffer[offset_y]
+		  [comp_width - dst_blk_x - offset_x - 1];
+		for (i = 0; i < DCTSIZE; i++) {
+		  for (j = 0; j < DCTSIZE; j++)
+		    dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+		  i++;
+		  for (j = 0; j < DCTSIZE; j++)
+		    dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
+		}
+	      } else {
+		/* At lower right corner, just transpose, no mirroring */
+		dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
+		for (i = 0; i < DCTSIZE; i++)
+		  for (j = 0; j < DCTSIZE; j++)
+		    dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
+	      }
+	    }
+	  }
+	}
+      }
+    }
+  }
+}
+
+
+/* Request any required workspace.
+ *
+ * We allocate the workspace virtual arrays from the source decompression
+ * object, so that all the arrays (both the original data and the workspace)
+ * will be taken into account while making memory management decisions.
+ * Hence, this routine must be called after jpeg_read_header (which reads
+ * the image dimensions) and before jpeg_read_coefficients (which realizes
+ * the source's virtual arrays).
+ */
+
+void
+jtransform_request_workspace (j_decompress_ptr srcinfo,
+			      jpeg_transform_info *info)
+{
+  jvirt_barray_ptr *coef_arrays = NULL;
+  jpeg_component_info *compptr;
+  int ci;
+
+  if (info->force_grayscale &&
+      srcinfo->jpeg_color_space == JCS_YCbCr &&
+      srcinfo->num_components == 3) {
+    /* We'll only process the first component */
+    info->num_components = 1;
+  } else {
+    /* Process all the components */
+    info->num_components = srcinfo->num_components;
+  }
+
+  switch (info->transform) {
+  case JXFORM_NONE:
+  case JXFORM_FLIP_H:
+    /* Don't need a workspace array */
+    break;
+  case JXFORM_FLIP_V:
+  case JXFORM_ROT_180:
+    /* Need workspace arrays having same dimensions as source image.
+     * Note that we allocate arrays padded out to the next iMCU boundary,
+     * so that transform routines need not worry about missing edge blocks.
+     */
+    coef_arrays = (jvirt_barray_ptr *)
+      (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE,
+	sizeof(jvirt_barray_ptr) * info->num_components);
+    for (ci = 0; ci < info->num_components; ci++) {
+      compptr = srcinfo->comp_info + ci;
+      coef_arrays[ci] = (*srcinfo->mem->request_virt_barray)
+	((j_common_ptr) srcinfo, JPOOL_IMAGE, FALSE,
+	 (JDIMENSION) jround_up((long) compptr->width_in_blocks,
+				(long) compptr->h_samp_factor),
+	 (JDIMENSION) jround_up((long) compptr->height_in_blocks,
+				(long) compptr->v_samp_factor),
+	 (JDIMENSION) compptr->v_samp_factor);
+    }
+    break;
+  case JXFORM_TRANSPOSE:
+  case JXFORM_TRANSVERSE:
+  case JXFORM_ROT_90:
+  case JXFORM_ROT_270:
+    /* Need workspace arrays having transposed dimensions.
+     * Note that we allocate arrays padded out to the next iMCU boundary,
+     * so that transform routines need not worry about missing edge blocks.
+     */
+    coef_arrays = (jvirt_barray_ptr *)
+      (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE,
+	sizeof(jvirt_barray_ptr) * info->num_components);
+    for (ci = 0; ci < info->num_components; ci++) {
+      compptr = srcinfo->comp_info + ci;
+      coef_arrays[ci] = (*srcinfo->mem->request_virt_barray)
+	((j_common_ptr) srcinfo, JPOOL_IMAGE, FALSE,
+	 (JDIMENSION) jround_up((long) compptr->height_in_blocks,
+				(long) compptr->v_samp_factor),
+	 (JDIMENSION) jround_up((long) compptr->width_in_blocks,
+				(long) compptr->h_samp_factor),
+	 (JDIMENSION) compptr->h_samp_factor);
+    }
+    break;
+  }
+  info->workspace_coef_arrays = coef_arrays;
+}
+
+
+/* Transpose destination image parameters */
+
+static void
+transpose_critical_parameters (j_compress_ptr dstinfo)
+{
+  int tblno, i, j, ci, itemp;
+  jpeg_component_info *compptr;
+  JQUANT_TBL *qtblptr;
+  JDIMENSION dtemp;
+  UINT16 qtemp;
+
+  /* Transpose basic image dimensions */
+  dtemp = dstinfo->image_width;
+  dstinfo->image_width = dstinfo->image_height;
+  dstinfo->image_height = dtemp;
+
+  /* Transpose sampling factors */
+  for (ci = 0; ci < dstinfo->num_components; ci++) {
+    compptr = dstinfo->comp_info + ci;
+    itemp = compptr->h_samp_factor;
+    compptr->h_samp_factor = compptr->v_samp_factor;
+    compptr->v_samp_factor = itemp;
+  }
+
+  /* Transpose quantization tables */
+  for (tblno = 0; tblno < NUM_QUANT_TBLS; tblno++) {
+    qtblptr = dstinfo->quant_tbl_ptrs[tblno];
+    if (qtblptr != NULL) {
+      for (i = 0; i < DCTSIZE; i++) {
+	for (j = 0; j < i; j++) {
+	  qtemp = qtblptr->quantval[i*DCTSIZE+j];
+	  qtblptr->quantval[i*DCTSIZE+j] = qtblptr->quantval[j*DCTSIZE+i];
+	  qtblptr->quantval[j*DCTSIZE+i] = qtemp;
+	}
+      }
+    }
+  }
+}
+
+
+/* Trim off any partial iMCUs on the indicated destination edge */
+
+static void
+trim_right_edge (j_compress_ptr dstinfo)
+{
+  int ci, max_h_samp_factor;
+  JDIMENSION MCU_cols;
+
+  /* We have to compute max_h_samp_factor ourselves,
+   * because it hasn't been set yet in the destination
+   * (and we don't want to use the source's value).
+   */
+  max_h_samp_factor = 1;
+  for (ci = 0; ci < dstinfo->num_components; ci++) {
+    int h_samp_factor = dstinfo->comp_info[ci].h_samp_factor;
+    max_h_samp_factor = MAX(max_h_samp_factor, h_samp_factor);
+  }
+  MCU_cols = dstinfo->image_width / (max_h_samp_factor * DCTSIZE);
+  if (MCU_cols > 0)		/* can't trim to 0 pixels */
+    dstinfo->image_width = MCU_cols * (max_h_samp_factor * DCTSIZE);
+}
+
+static void
+trim_bottom_edge (j_compress_ptr dstinfo)
+{
+  int ci, max_v_samp_factor;
+  JDIMENSION MCU_rows;
+
+  /* We have to compute max_v_samp_factor ourselves,
+   * because it hasn't been set yet in the destination
+   * (and we don't want to use the source's value).
+   */
+  max_v_samp_factor = 1;
+  for (ci = 0; ci < dstinfo->num_components; ci++) {
+    int v_samp_factor = dstinfo->comp_info[ci].v_samp_factor;
+    max_v_samp_factor = MAX(max_v_samp_factor, v_samp_factor);
+  }
+  MCU_rows = dstinfo->image_height / (max_v_samp_factor * DCTSIZE);
+  if (MCU_rows > 0)		/* can't trim to 0 pixels */
+    dstinfo->image_height = MCU_rows * (max_v_samp_factor * DCTSIZE);
+}
+
+
+/* Adjust output image parameters as needed.
+ *
+ * This must be called after jpeg_copy_critical_parameters()
+ * and before jpeg_write_coefficients().
+ *
+ * The return value is the set of virtual coefficient arrays to be written
+ * (either the ones allocated by jtransform_request_workspace, or the
+ * original source data arrays).  The caller will need to pass this value
+ * to jpeg_write_coefficients().
+ */
+
+jvirt_barray_ptr *
+jtransform_adjust_parameters (j_decompress_ptr srcinfo,
+			      j_compress_ptr dstinfo,
+			      jvirt_barray_ptr *src_coef_arrays,
+			      jpeg_transform_info *info)
+{
+  /* If force-to-grayscale is requested, adjust destination parameters */
+  if (info->force_grayscale) {
+    /* We use jpeg_set_colorspace to make sure subsidiary settings get fixed
+     * properly.  Among other things, the target h_samp_factor & v_samp_factor
+     * will get set to 1, which typically won't match the source.
+     * In fact we do this even if the source is already grayscale; that
+     * provides an easy way of coercing a grayscale JPEG with funny sampling
+     * factors to the customary 1,1.  (Some decoders fail on other factors.)
+     */
+    if ((dstinfo->jpeg_color_space == JCS_YCbCr &&
+	 dstinfo->num_components == 3) ||
+	(dstinfo->jpeg_color_space == JCS_GRAYSCALE &&
+	 dstinfo->num_components == 1)) {
+      /* We have to preserve the source's quantization table number. */
+      int sv_quant_tbl_no = dstinfo->comp_info[0].quant_tbl_no;
+      jpeg_set_colorspace(dstinfo, JCS_GRAYSCALE);
+      dstinfo->comp_info[0].quant_tbl_no = sv_quant_tbl_no;
+    } else {
+      /* Sorry, can't do it */
+      ERREXIT(dstinfo, JERR_CONVERSION_NOTIMPL);
+    }
+  }
+
+  /* Correct the destination's image dimensions etc if necessary */
+  switch (info->transform) {
+  case JXFORM_NONE:
+    /* Nothing to do */
+    break;
+  case JXFORM_FLIP_H:
+    if (info->trim)
+      trim_right_edge(dstinfo);
+    break;
+  case JXFORM_FLIP_V:
+    if (info->trim)
+      trim_bottom_edge(dstinfo);
+    break;
+  case JXFORM_TRANSPOSE:
+    transpose_critical_parameters(dstinfo);
+    /* transpose does NOT have to trim anything */
+    break;
+  case JXFORM_TRANSVERSE:
+    transpose_critical_parameters(dstinfo);
+    if (info->trim) {
+      trim_right_edge(dstinfo);
+      trim_bottom_edge(dstinfo);
+    }
+    break;
+  case JXFORM_ROT_90:
+    transpose_critical_parameters(dstinfo);
+    if (info->trim)
+      trim_right_edge(dstinfo);
+    break;
+  case JXFORM_ROT_180:
+    if (info->trim) {
+      trim_right_edge(dstinfo);
+      trim_bottom_edge(dstinfo);
+    }
+    break;
+  case JXFORM_ROT_270:
+    transpose_critical_parameters(dstinfo);
+    if (info->trim)
+      trim_bottom_edge(dstinfo);
+    break;
+  }
+
+  /* Return the appropriate output data set */
+  if (info->workspace_coef_arrays != NULL)
+    return info->workspace_coef_arrays;
+  return src_coef_arrays;
+}
+
+
+/* Execute the actual transformation, if any.
+ *
+ * This must be called *after* jpeg_write_coefficients, because it depends
+ * on jpeg_write_coefficients to have computed subsidiary values such as
+ * the per-component width and height fields in the destination object.
+ *
+ * Note that some transformations will modify the source data arrays!
+ */
+
+void
+jtransform_execute_transformation (j_decompress_ptr srcinfo,
+				   j_compress_ptr dstinfo,
+				   jvirt_barray_ptr *src_coef_arrays,
+				   jpeg_transform_info *info)
+{
+  jvirt_barray_ptr *dst_coef_arrays = info->workspace_coef_arrays;
+
+  switch (info->transform) {
+  case JXFORM_NONE:
+    break;
+  case JXFORM_FLIP_H:
+    do_flip_h(srcinfo, dstinfo, src_coef_arrays);
+    break;
+  case JXFORM_FLIP_V:
+    do_flip_v(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
+    break;
+  case JXFORM_TRANSPOSE:
+    do_transpose(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
+    break;
+  case JXFORM_TRANSVERSE:
+    do_transverse(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
+    break;
+  case JXFORM_ROT_90:
+    do_rot_90(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
+    break;
+  case JXFORM_ROT_180:
+    do_rot_180(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
+    break;
+  case JXFORM_ROT_270:
+    do_rot_270(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
+    break;
+  }
+}
+
+
+/* Setup decompression object to save desired markers in memory.
+ * This must be called before jpeg_read_header() to have the desired effect.
+ */
+
+void
+jcopy_markers_setup (j_decompress_ptr srcinfo, JCOPY_OPTION option)
+{
+#ifdef SAVE_MARKERS_SUPPORTED
+  int m;
+
+  /* Save comments except under NONE option */
+  if (option != JCOPYOPT_NONE) {
+    jpeg_save_markers(srcinfo, JPEG_COM, 0xFFFF);
+  }
+  /* Save all types of APPn markers iff ALL option */
+  if (option == JCOPYOPT_ALL) {
+    for (m = 0; m < 16; m++)
+      jpeg_save_markers(srcinfo, JPEG_APP0 + m, 0xFFFF);
+  }
+#endif /* SAVE_MARKERS_SUPPORTED */
+}
+
+/* Adjust the markers to create a standard EXIF file if an EXIF marker
+ * is present in the input. By default, libjpeg creates a JFIF file, 
+ * which is incompatible with the EXIF standard.
+ *
+ * This must be called after jpeg_copy_critical_parameters()
+ * and before jpeg_write_coefficients().
+ */
+
+void
+jcopy_markers_exif (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+		    JCOPY_OPTION option)
+{
+	/* In the current implementation, we don't actually need to examine the
+	 * option flag here; we just analyse everything that got saved by
+	 * jcopy_markers_setup. */
+
+	jpeg_saved_marker_ptr cur_marker, prev_marker;
+
+	/* Look for an EXIF marker. */
+	prev_marker = NULL;
+	cur_marker = srcinfo->marker_list;
+	while (cur_marker != NULL) {
+		if (cur_marker->marker == JPEG_APP0+1 &&
+			cur_marker->data_length >= 6 &&
+			GETJOCTET(cur_marker->data[0]) == 0x45 &&
+			GETJOCTET(cur_marker->data[1]) == 0x78 &&
+			GETJOCTET(cur_marker->data[2]) == 0x69 &&
+			GETJOCTET(cur_marker->data[3]) == 0x66 &&
+			GETJOCTET(cur_marker->data[4]) == 0 &&
+			GETJOCTET(cur_marker->data[5]) == 0)
+			break; /* Found an EXIF marker. */
+		prev_marker = cur_marker;
+		cur_marker = cur_marker->next;
+	}
+	/* Do not emit a (incompatible) JFIF marker if an EXIF marker is found. 
+	 * The EXIF standard requires the EXIF marker to be the first extra marker, 
+	 * but JFIF has the same requirement. */
+	if (cur_marker != NULL) {
+		dstinfo->write_JFIF_header = FALSE;
+	}
+	/* Force the EXIF marker to be the first marker (if necessary).
+	 * This will also recover EXIF files that where converted to JFIF 
+	 * by non EXIF-aware software (if the EXIF marker is still present). */
+	if (cur_marker != NULL && prev_marker != NULL) {
+		prev_marker->next = cur_marker->next;
+		cur_marker->next = srcinfo->marker_list;
+		srcinfo->marker_list = cur_marker;
+	}
+}
+
+/* Copy markers saved in the given source object to the destination object.
+ * This should be called just after jpeg_start_compress() or
+ * jpeg_write_coefficients().
+ * Note that those routines will have written the SOI, and also the
+ * JFIF APP0 or Adobe APP14 markers if selected.
+ */
+
+void
+jcopy_markers_execute (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
+		       JCOPY_OPTION option)
+{
+  jpeg_saved_marker_ptr marker;
+
+	/* If the first marker is an EXIF marker, we do not
+	 * copy any (incompatible) JFIF marker, by pretending 
+	 * the library already wrote one. This should have no 
+	 * effect unless jcopy_markers_exif was used before.*/
+	marker = srcinfo->marker_list;
+	if (marker != NULL && 
+		marker->marker == JPEG_APP0+1 &&
+		marker->data_length >= 6 &&
+		GETJOCTET(marker->data[0]) == 0x45 &&
+		GETJOCTET(marker->data[1]) == 0x78 &&
+		GETJOCTET(marker->data[2]) == 0x69 &&
+		GETJOCTET(marker->data[3]) == 0x66 &&
+		GETJOCTET(marker->data[4]) == 0 &&
+		GETJOCTET(marker->data[5]) == 0)
+		dstinfo->write_JFIF_header = TRUE;
+
+  /* In the current implementation, we don't actually need to examine the
+   * option flag here; we just copy everything that got saved.
+   * But to avoid confusion, we do not output JFIF and Adobe APP14 markers
+   * if the encoder library already wrote one.
+   */
+  for (marker = srcinfo->marker_list; marker != NULL; marker = marker->next) {
+    if (dstinfo->write_JFIF_header &&
+	marker->marker == JPEG_APP0 &&
+	marker->data_length >= 5 &&
+	GETJOCTET(marker->data[0]) == 0x4A &&
+	GETJOCTET(marker->data[1]) == 0x46 &&
+	GETJOCTET(marker->data[2]) == 0x49 &&
+	GETJOCTET(marker->data[3]) == 0x46 &&
+	GETJOCTET(marker->data[4]) == 0)
+      continue;			/* reject duplicate JFIF */
+    if (dstinfo->write_Adobe_marker &&
+	marker->marker == JPEG_APP0+14 &&
+	marker->data_length >= 5 &&
+	GETJOCTET(marker->data[0]) == 0x41 &&
+	GETJOCTET(marker->data[1]) == 0x64 &&
+	GETJOCTET(marker->data[2]) == 0x6F &&
+	GETJOCTET(marker->data[3]) == 0x62 &&
+	GETJOCTET(marker->data[4]) == 0x65)
+      continue;			/* reject duplicate Adobe */
+
+#ifdef NEED_FAR_POINTERS
+    /* We could use jpeg_write_marker if the data weren't FAR... */
+    {
+      unsigned int i;
+      jpeg_write_m_header(dstinfo, marker->marker, marker->data_length);
+      for (i = 0; i < marker->data_length; i++)
+	jpeg_write_m_byte(dstinfo, marker->data[i]);
+    }
+#else
+    jpeg_write_marker(dstinfo, marker->marker,
+		      marker->data, marker->data_length);
+#endif
+  }
+}
+
diff --git a/extensions/image_rotation/transupp.h b/extensions/image_rotation/transupp.h
new file mode 100644
index 0000000..1708ef4
--- /dev/null
+++ b/extensions/image_rotation/transupp.h
@@ -0,0 +1,157 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2001, 2002 The Free Software Foundation, Inc.
+ *
+ *  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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+/* based upon file transupp.h from the libjpeg package, original copyright 
+ * note follows:
+ *
+ * transupp.h
+ *
+ * Copyright (C) 1997, Thomas G. Lane.
+ * This file is part of the Independent JPEG Group's software.
+ * For conditions of distribution and use, see the accompanying README file.
+ *
+ * This file contains declarations for image transformation routines and
+ * other utility code used by the jpegtran sample application.  These are
+ * NOT part of the core JPEG library.  But we keep these routines separate
+ * from jpegtran.c to ease the task of maintaining jpegtran-like programs
+ * that have other user interfaces.
+ *
+ * NOTE: all the routines declared here have very specific requirements
+ * about when they are to be executed during the reading and writing of the
+ * source and destination files.  See the comments in transupp.c, or see
+ * jpegtran.c for an example of correct usage.
+ */
+
+/*
+ * Codes for supported types of image transformations.
+ */
+
+
+#ifndef TRANSUPP_H
+#define TRANSUPP_H
+
+#include <jpeglib.h>
+
+
+typedef enum {
+	JXFORM_NONE,		/* no transformation */
+	JXFORM_FLIP_H,		/* horizontal flip */
+	JXFORM_FLIP_V,		/* vertical flip */
+	JXFORM_TRANSPOSE,	/* transpose across UL-to-LR axis */
+	JXFORM_TRANSVERSE,	/* transpose across UR-to-LL axis */
+	JXFORM_ROT_90,		/* 90-degree clockwise rotation */
+	JXFORM_ROT_180,		/* 180-degree rotation */
+	JXFORM_ROT_270		/* 270-degree clockwise (or 90 ccw) */
+} JXFORM_CODE;
+
+/*
+ * Although rotating and flipping data expressed as DCT coefficients is not
+ * hard, there is an asymmetry in the JPEG format specification for images
+ * whose dimensions aren't multiples of the iMCU size.  The right and bottom
+ * image edges are padded out to the next iMCU boundary with junk data; but
+ * no padding is possible at the top and left edges.  If we were to flip
+ * the whole image including the pad data, then pad garbage would become
+ * visible at the top and/or left, and real pixels would disappear into the
+ * pad margins --- perhaps permanently, since encoders & decoders may not
+ * bother to preserve DCT blocks that appear to be completely outside the
+ * nominal image area.  So, we have to exclude any partial iMCUs from the
+ * basic transformation.
+ *
+ * Transpose is the only transformation that can handle partial iMCUs at the
+ * right and bottom edges completely cleanly.  flip_h can flip partial iMCUs
+ * at the bottom, but leaves any partial iMCUs at the right edge untouched.
+ * Similarly flip_v leaves any partial iMCUs at the bottom edge untouched.
+ * The other transforms are defined as combinations of these basic transforms
+ * and process edge blocks in a way that preserves the equivalence.
+ *
+ * The "trim" option causes untransformable partial iMCUs to be dropped;
+ * this is not strictly lossless, but it usually gives the best-looking
+ * result for odd-size images.  Note that when this option is active,
+ * the expected mathematical equivalences between the transforms may not hold.
+ * (For example, -rot 270 -trim trims only the bottom edge, but -rot 90 -trim
+ * followed by -rot 180 -trim trims both edges.)
+ *
+ * We also offer a "force to grayscale" option, which simply discards the
+ * chrominance channels of a YCbCr image.  This is lossless in the sense that
+ * the luminance channel is preserved exactly.  It's not the same kind of
+ * thing as the rotate/flip transformations, but it's convenient to handle it
+ * as part of this package, mainly because the transformation routines have to
+ * be aware of the option to know how many components to work on.
+ */
+
+typedef struct {
+  /* Options: set by caller */
+  JXFORM_CODE transform;	/* image transform operator */
+  boolean trim;			/* if TRUE, trim partial MCUs as needed */
+  boolean force_grayscale;	/* if TRUE, convert color image to grayscale */
+
+  /* Internal workspace: caller should not touch these */
+  int num_components;		/* # of components in workspace */
+  jvirt_barray_ptr * workspace_coef_arrays; /* workspace for transformations */
+} jpeg_transform_info;
+
+
+/* Request any required workspace */
+void jtransform_request_workspace   (j_decompress_ptr     srcinfo, 
+				     jpeg_transform_info *info);
+
+/* Adjust output image parameters */
+
+jvirt_barray_ptr * jtransform_adjust_parameters (j_decompress_ptr     srcinfo, 
+						 j_compress_ptr       dstinfo,
+						 jvirt_barray_ptr    *src_coef_arrays,
+						 jpeg_transform_info *info);
+
+/* Execute the actual transformation, if any */
+void jtransform_execute_transformation (j_decompress_ptr     srcinfo, 
+					j_compress_ptr       dstinfo,
+					jvirt_barray_ptr    *src_coef_arrays,
+					jpeg_transform_info *info);
+
+
+/*
+ * Support for copying optional markers from source to destination file.
+ */
+
+typedef enum {
+	JCOPYOPT_NONE,		/* copy no optional markers */
+	JCOPYOPT_COMMENTS,	/* copy only comment (COM) markers */
+	JCOPYOPT_ALL		/* copy all optional markers */
+} JCOPY_OPTION;
+
+#define JCOPYOPT_DEFAULT  JCOPYOPT_COMMENTS	/* recommended default */
+
+/* Setup decompression object to save desired markers in memory */
+void jcopy_markers_setup (j_decompress_ptr srcinfo, 
+			  JCOPY_OPTION     option);
+
+void jcopy_markers_exif (j_decompress_ptr srcinfo, 
+			 j_compress_ptr   dstinfo,
+			 JCOPY_OPTION     option);
+
+/* Copy markers saved in the given source object to the destination object */
+void jcopy_markers_execute (j_decompress_ptr srcinfo, 
+			    j_compress_ptr   dstinfo,
+			    JCOPY_OPTION     option);
+
+
+#endif /* TRANSUPP_H */
diff --git a/gthumb/Makefile.am b/gthumb/Makefile.am
index e61f858..23f7437 100644
--- a/gthumb/Makefile.am
+++ b/gthumb/Makefile.am
@@ -217,6 +217,7 @@ gthumb_LDADD =						\
 	$(top_builddir)/copy-n-paste/libeggsmclient.la	\
 	$(GTHUMB_LIBS)					\
 	$(EXIV2_LIBS)					\
+	$(JPEG_LIBS)					\
 	$(NULL)	
 
 if RUN_IN_PLACE
diff --git a/gthumb/dlg-extensions.c b/gthumb/dlg-extensions.c
index 0d7de83..7c4a41a 100644
--- a/gthumb/dlg-extensions.c
+++ b/gthumb/dlg-extensions.c
@@ -207,13 +207,13 @@ cell_renderer_toggle_toggled_cb (GtkCellRendererToggle *cell_renderer,
 			if (! gth_extension_manager_activate (gth_main_get_default_extension_manager (), description->id, &error))
 				_gtk_error_dialog_from_gerror_run (GTK_WINDOW (data->dialog), _("Could not activate the extension"), &error);
 			else
-				gtk_list_store_set (data->list_store, &iter, 0, description,-1);
+				gtk_list_store_set (data->list_store, &iter, 0, description, -1);
 		}
 		else {
 			if (! gth_extension_manager_deactivate (gth_main_get_default_extension_manager (), description->id, &error))
 				_gtk_error_dialog_from_gerror_run (GTK_WINDOW (data->dialog), _("Could not deactivate the extension"), &error);
 			else
-				gtk_list_store_set (data->list_store, &iter, 0, description,-1);
+				gtk_list_store_set (data->list_store, &iter, 0, description, -1);
 		}
 
 		g_object_unref (description);
diff --git a/gthumb/gio-utils.c b/gthumb/gio-utils.c
index f940f4f..95e2a1e 100644
--- a/gthumb/gio-utils.c
+++ b/gthumb/gio-utils.c
@@ -1797,7 +1797,7 @@ typedef struct {
 	BufferReadyCallback  callback;
 	gpointer             user_data;
 	GInputStream        *stream;
-	char                 tmp_buffer[BUFFER_SIZE];
+	guchar               tmp_buffer[BUFFER_SIZE];
 	void                *buffer;
 	gsize                count;
 } LoadData;
diff --git a/gthumb/glib-utils.c b/gthumb/glib-utils.c
index 236c2a1..485e9b5 100644
--- a/gthumb/glib-utils.c
+++ b/gthumb/glib-utils.c
@@ -2027,6 +2027,169 @@ _g_file_attributes_matches (const char *attributes,
 }
 
 
+/* -- _g_file_info_swap_attributes -- */
+
+
+typedef struct {
+	GFileAttributeType type;
+	union {
+		char      *string;
+		char     **stringv;
+		gboolean   boolean;
+		guint32    uint32;
+		gint32     int32;
+		guint64    uint64;
+		gint64     int64;
+		gpointer   object;
+	} v;
+} _GFileAttributeValue;
+
+
+static void
+_g_file_attribute_value_free (_GFileAttributeValue *attr_value)
+{
+	switch (attr_value->type) {
+	case G_FILE_ATTRIBUTE_TYPE_STRING:
+	case G_FILE_ATTRIBUTE_TYPE_BYTE_STRING:
+		g_free (attr_value->v.string);
+		break;
+/* FIXME: add if glib >= 2.22
+	case G_FILE_ATTRIBUTE_TYPE_STRINGV:
+		g_strfreev (attr_value->v.stringv);
+		break;
+*/
+	case G_FILE_ATTRIBUTE_TYPE_OBJECT:
+		g_object_unref (attr_value->v.object);
+		break;
+	default:
+		break;
+	}
+
+	g_free (attr_value);
+}
+
+
+static _GFileAttributeValue *
+_g_file_info_get_value (GFileInfo  *info,
+			const char *attr)
+{
+	_GFileAttributeValue  *attr_value;
+	GFileAttributeType     type;
+	gpointer               value;
+	GFileAttributeStatus   status;
+
+	attr_value = g_new (_GFileAttributeValue, 1);
+	attr_value->type = G_FILE_ATTRIBUTE_TYPE_INVALID;
+
+	if (! g_file_info_get_attribute_data (info, attr, &type, &value, &status))
+		return attr_value;
+
+	attr_value->type = type;
+	switch (type) {
+	case G_FILE_ATTRIBUTE_TYPE_INVALID:
+		break;
+	case G_FILE_ATTRIBUTE_TYPE_STRING:
+	case G_FILE_ATTRIBUTE_TYPE_BYTE_STRING:
+		attr_value->v.string = g_strdup ((char *) value);
+		break;
+/* FIXME: add if glib >= 2.22
+	case G_FILE_ATTRIBUTE_TYPE_STRINGV:
+		attr_value->v.stringv = g_strdupv ((char **) value);
+		break;
+*/
+	case G_FILE_ATTRIBUTE_TYPE_BOOLEAN:
+		attr_value->v.boolean = * ((gboolean *) value);
+		break;
+	case G_FILE_ATTRIBUTE_TYPE_UINT32:
+		attr_value->v.uint32 = * ((guint32 *) value);
+		break;
+	case G_FILE_ATTRIBUTE_TYPE_INT32:
+		attr_value->v.int32 = * ((gint32 *) value);
+		break;
+	case G_FILE_ATTRIBUTE_TYPE_UINT64:
+		attr_value->v.uint64 = * ((guint64 *) value);
+		break;
+	case G_FILE_ATTRIBUTE_TYPE_INT64:
+		attr_value->v.int64 = * ((gint64 *) value);
+		break;
+	case G_FILE_ATTRIBUTE_TYPE_OBJECT:
+		attr_value->v.object = g_object_ref ((GObject *) value);
+		break;
+	default:
+		g_warning ("unknown attribute type: %d", type);
+		break;
+	}
+
+	return attr_value;
+}
+
+
+static void
+_g_file_info_set_value (GFileInfo            *info,
+			const char           *attr,
+			_GFileAttributeValue *attr_value)
+{
+	gpointer value = NULL;
+
+	if (attr_value->type == G_FILE_ATTRIBUTE_TYPE_INVALID)
+		return;
+
+	switch (attr_value->type) {
+	case G_FILE_ATTRIBUTE_TYPE_STRING:
+	case G_FILE_ATTRIBUTE_TYPE_BYTE_STRING:
+		value = attr_value->v.string;
+		break;
+/* FIXME: add if glib >= 2.22
+	case G_FILE_ATTRIBUTE_TYPE_STRINGV:
+		value = attr_value->v.stringv;
+		break;
+*/
+	case G_FILE_ATTRIBUTE_TYPE_BOOLEAN:
+		value = &attr_value->v.boolean;
+		break;
+	case G_FILE_ATTRIBUTE_TYPE_UINT32:
+		value = &attr_value->v.uint32;
+		break;
+	case G_FILE_ATTRIBUTE_TYPE_INT32:
+		value = &attr_value->v.int32;
+		break;
+	case G_FILE_ATTRIBUTE_TYPE_UINT64:
+		value = &attr_value->v.uint64;
+		break;
+	case G_FILE_ATTRIBUTE_TYPE_INT64:
+		value = &attr_value->v.int64;
+		break;
+	case G_FILE_ATTRIBUTE_TYPE_OBJECT:
+		value = attr_value->v.object;
+		break;
+	default:
+		g_warning ("Unknown attribute type: %d", attr_value->type);
+		break;
+	}
+
+	g_file_info_set_attribute (info, attr, attr_value->type, value);
+}
+
+
+void
+_g_file_info_swap_attributes (GFileInfo  *info,
+			      const char *attr1,
+			      const char *attr2)
+{
+	_GFileAttributeValue *value1;
+	_GFileAttributeValue *value2;
+
+	value1 = _g_file_info_get_value (info, attr1);
+	value2 = _g_file_info_get_value (info, attr2);
+
+	_g_file_info_set_value (info, attr1, value2);
+	_g_file_info_set_value (info, attr2, value1);
+
+	_g_file_attribute_value_free (value1);
+	_g_file_attribute_value_free (value2);
+}
+
+
 gboolean
 _g_mime_type_is_image (const char *mime_type)
 {
diff --git a/gthumb/glib-utils.h b/gthumb/glib-utils.h
index d5caccd..bd901b3 100644
--- a/gthumb/glib-utils.h
+++ b/gthumb/glib-utils.h
@@ -245,6 +245,9 @@ GFile *         _g_file_append_path              (GFile      *file,
 						  const char *path);
 gboolean        _g_file_attributes_matches       (const char *attributes,
 						  const char *mask);
+void            _g_file_info_swap_attributes     (GFileInfo  *info,
+						  const char *attr1,
+						  const char *attr2);
 gboolean        _g_mime_type_is_image            (const char *mime_type);
 gboolean        _g_mime_type_is_video            (const char *mime_type);
 gboolean        _g_mime_type_is_audio            (const char *mime_type);
diff --git a/gthumb/gth-extensions.c b/gthumb/gth-extensions.c
index 2c2e7b1..baf61f8 100644
--- a/gthumb/gth-extensions.c
+++ b/gthumb/gth-extensions.c
@@ -37,7 +37,8 @@ static gpointer gth_extension_parent_class = NULL;
 
 
 static gboolean
-gth_extension_base_open (GthExtension *self)
+gth_extension_base_open (GthExtension  *self,
+			 GError       **error)
 {
 	g_return_val_if_fail (GTH_IS_EXTENSION (self), FALSE);
 	return FALSE;
@@ -132,9 +133,10 @@ gth_extension_get_type (void)
 
 
 gboolean
-gth_extension_open (GthExtension *self)
+gth_extension_open (GthExtension  *self,
+		    GError       **error)
 {
-	return GTH_EXTENSION_GET_CLASS (self)->open (self);
+	return GTH_EXTENSION_GET_CLASS (self)->open (self, error);
 }
 
 
@@ -197,7 +199,8 @@ static gpointer gth_extension_module_parent_class = NULL;
 
 
 static gboolean
-gth_extension_module_real_open (GthExtension *base)
+gth_extension_module_real_open (GthExtension  *base,
+				GError       **error)
 {
 	GthExtensionModule *self;
 	char               *file_name;
@@ -222,7 +225,8 @@ gth_extension_module_real_open (GthExtension *base)
 	g_free (file_name);
 
 	if (self->priv->module == NULL) {
-		/*g_warning ("could not open the module `%s`: %s\n", self->priv->module_name, g_module_error ());*/
+		if (error != NULL)
+			*error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, _("Could not open the module `%s`: %s"), self->priv->module_name, g_module_error ());
 	}
 
 	return self->priv->module != NULL;
@@ -459,6 +463,7 @@ gth_extension_description_finalize (GObject *obj)
 	g_free (self->loader_type);
 	g_free (self->loader_file);
 	g_strfreev (self->loader_requires);
+	g_strfreev (self->loader_after);
 	_g_object_unref (self->priv->extension);
 
 	G_OBJECT_CLASS (gth_extension_description_parent_class)->finalize (obj);
@@ -533,6 +538,7 @@ gth_extension_description_load_from_file (GthExtensionDescription *desc,
 	desc->loader_type = g_key_file_get_string (key_file, "Loader", "Type", NULL);
 	desc->loader_file = g_key_file_get_string (key_file, "Loader", "File", NULL);
 	desc->loader_requires = g_key_file_get_string_list (key_file, "Loader", "Requires", NULL, NULL);
+	desc->loader_after = g_key_file_get_string_list (key_file, "Loader", "After", NULL, NULL);
 
 	g_free (basename);
 	g_free (file_path);
@@ -701,8 +707,9 @@ gth_extension_manager_new (void)
 
 
 gboolean
-gth_extension_manager_open (GthExtensionManager *manager,
-			    const char          *extension_name)
+gth_extension_manager_open (GthExtensionManager  *manager,
+			    const char           *extension_name,
+			    GError              **error)
 {
 	GthExtensionDescription *description;
 
@@ -719,7 +726,12 @@ gth_extension_manager_open (GthExtensionManager *manager,
 
 	g_return_val_if_fail (description->priv->extension != NULL, FALSE);
 
-	description->priv->opened = gth_extension_open (description->priv->extension);
+	description->priv->opened = gth_extension_open (description->priv->extension, error);
+
+	if (! description->priv->opened) {
+		g_object_unref (description->priv->extension);
+		description->priv->extension = NULL;
+	}
 
 	return description->priv->opened;
 }
@@ -732,7 +744,7 @@ gth_extension_manager_activate (GthExtensionManager  *manager,
 {
 	GthExtensionDescription *description;
 
-	if (! gth_extension_manager_open (manager, extension_name))
+	if (! gth_extension_manager_open (manager, extension_name, error))
 		return FALSE;
 
 	description = g_hash_table_lookup (manager->priv->extensions, extension_name);
@@ -786,7 +798,7 @@ gth_extension_manager_deactivate (GthExtensionManager  *manager,
 	GList                   *required_by;
 	GList                   *scan;
 
-	if (! gth_extension_manager_open (manager, extension_name))
+	if (! gth_extension_manager_open (manager, extension_name, error))
 		return TRUE;
 
 	description = g_hash_table_lookup (manager->priv->extensions, extension_name);
@@ -825,3 +837,72 @@ gth_extension_manager_get_description (GthExtensionManager *manager,
 {
 	return g_hash_table_lookup (manager->priv->extensions, extension_name);
 }
+
+
+static GList *
+get_extension_optional_dependencies (GthExtensionManager     *manager,
+				     GthExtensionDescription *description)
+{
+	GList *dependencies = NULL;
+	int    i;
+
+	if (description->loader_after == NULL)
+		return NULL;
+
+	for (i = 0; description->loader_after[i] != NULL; i++) {
+		char                    *extension_name = description->loader_after[i];
+		GthExtensionDescription *other_description;
+
+		dependencies = g_list_prepend (dependencies, extension_name);
+
+		other_description = g_hash_table_lookup (manager->priv->extensions, extension_name);
+		dependencies = g_list_concat (get_extension_optional_dependencies (manager, other_description), dependencies);
+	}
+
+	return dependencies;
+}
+
+
+GSList *
+gth_extension_manager_order_extensions (GthExtensionManager *manager,
+					GSList              *extensions)
+{
+	GSList *scan;
+	GSList *ordered = NULL;
+
+	for (scan = extensions; scan;) {
+		GSList                  *next = scan->next;
+		GSList                  *slink;
+		char                    *ext_name = scan->data;
+		GthExtensionDescription *ext_description;
+		GList                   *dependencies;
+		GList                   *scan_d;
+
+		ext_description = g_hash_table_lookup (manager->priv->extensions, ext_name);
+		if (ext_description == NULL)
+			continue;
+
+		dependencies = get_extension_optional_dependencies (manager, ext_description);
+		for (scan_d = dependencies; scan_d; scan_d = scan_d->next) {
+			char   *dep_name = scan_d->data;
+
+			slink = g_slist_find_custom (extensions, dep_name, (GCompareFunc) strcmp);
+			if (slink != NULL) {
+				extensions = g_slist_remove_link (extensions, slink);
+				ordered = g_slist_prepend (ordered, slink->data);
+
+				g_slist_free (slink);
+			}
+		}
+
+		slink = scan;
+		extensions = g_slist_remove_link (extensions, slink);
+		g_slist_free (slink);
+
+		ordered = g_slist_prepend (ordered, ext_name);
+
+		scan = next;
+	}
+
+	return g_slist_reverse (ordered);
+}
diff --git a/gthumb/gth-extensions.h b/gthumb/gth-extensions.h
index 6839014..b56aa73 100644
--- a/gthumb/gth-extensions.h
+++ b/gthumb/gth-extensions.h
@@ -84,7 +84,8 @@ struct _GthExtension {
 
 struct _GthExtensionClass {
 	GObjectClass parent_class;
-	gboolean   (*open)            (GthExtension  *self);
+	gboolean   (*open)            (GthExtension  *self,
+				       GError       **error);
 	void       (*close)           (GthExtension  *self);
 	gboolean   (*activate)        (GthExtension  *self,
 				       GError       **error);
@@ -117,6 +118,7 @@ struct _GthExtensionDescription {
 	char      *loader_type;
 	char      *loader_file;
 	char     **loader_requires;
+	char     **loader_after;
 	gboolean   mandatory;
 	GthExtensionDescriptionPrivate *priv;
 };
@@ -135,7 +137,8 @@ struct _GthExtensionManagerClass {
 };
 
 GType                      gth_extension_get_type                  (void);
-gboolean                   gth_extension_open                      (GthExtension  *self);
+gboolean                   gth_extension_open                      (GthExtension  *self,
+								    GError       **error);
 void                       gth_extension_close                     (GthExtension  *self);
 gboolean                   gth_extension_is_active                 (GthExtension  *self);
 gboolean                   gth_extension_activate                  (GthExtension  *self,
@@ -157,7 +160,8 @@ GthExtension *             gth_extension_description_get_extension (GthExtension
 GType                      gth_extension_manager_get_type          (void);
 GthExtensionManager *      gth_extension_manager_new               (void);
 gboolean                   gth_extension_manager_open              (GthExtensionManager  *manager,
-								    const char           *extension_name);
+								    const char           *extension_name,
+								    GError              **error);
 gboolean                   gth_extension_manager_activate          (GthExtensionManager  *manager,
 								    const char           *extension_name,
 								    GError              **error);
@@ -167,7 +171,8 @@ gboolean                   gth_extension_manager_deactivate        (GthExtension
 GList *                    gth_extension_manager_get_extensions    (GthExtensionManager  *manager);
 GthExtensionDescription *  gth_extension_manager_get_description   (GthExtensionManager  *manager,
 								    const char           *extension_name);
-
+GSList *                   gth_extension_manager_order_extensions  (GthExtensionManager  *manager,
+								    GSList               *extensions);
 G_END_DECLS
 
 #endif
diff --git a/gthumb/gth-hook.c b/gthumb/gth-hook.c
index d6d7b74..ad1b193 100644
--- a/gthumb/gth-hook.c
+++ b/gthumb/gth-hook.c
@@ -68,46 +68,55 @@ gth_hooks_initialize (void)
 {
 	if (initialized)
 		return;
-	
-	hooks = g_hash_table_new_full (g_str_hash, 
-				       g_str_equal, 
+
+	hooks = g_hash_table_new_full (g_str_hash,
+				       g_str_equal,
 				       (GDestroyNotify) g_free,
 				       (GDestroyNotify) gth_hook_free);
-	
+
 	initialized = TRUE;
 }
 
 
-void  
-gth_hook_register (const char *name, 
+void
+gth_hook_register (const char *name,
 	           int         n_args)
 {
 	GthHook *hook;
-	
+
 	g_return_if_fail (name != NULL);
 	g_return_if_fail ((n_args >= 0) || (n_args <= 3));
-	
+
 	if (g_hash_table_lookup (hooks, name) != NULL) {
 		g_warning ("hook '%s' already registered", name);
 		return;
 	}
-	
+
 	hook = g_new0 (GthHook, 1);
 	hook->list = g_new (GHookList, 1);
 	g_hook_list_init (hook->list, sizeof (GthHookCallback));
 	hook->n_args = n_args;
-	
+
 	g_hash_table_insert (hooks, g_strdup (name), hook);
 }
 
 
+gboolean
+gth_hook_present (const char *name)
+{
+	g_return_val_if_fail (name != NULL, FALSE);
+
+	return g_hash_table_lookup (hooks, name) != NULL;
+}
+
+
 static int
 hook_compare_func (GHook *new_hook,
                    GHook *sibling)
 {
 	GthHookCallback *new_function = GTH_HOOK_CALLBACK (new_hook);
 	GthHookCallback *sibling_function = GTH_HOOK_CALLBACK (sibling);
-	
+
 	if (new_function->sort_order > sibling_function->sort_order)
 		return -1;
 	else if (new_function->sort_order < sibling_function->sort_order)
@@ -117,35 +126,35 @@ hook_compare_func (GHook *new_hook,
 }
 
 
-void  
-gth_hook_add_callback (const char *name, 
+void
+gth_hook_add_callback (const char *name,
 		       int         sort_order,
-		       GCallback   callback, 
+		       GCallback   callback,
 		       gpointer    data)
 {
 	GthHook *hook;
 	GHook   *function;
-	
-	hook = GET_HOOK (name);	
-	
+
+	hook = GET_HOOK (name);
+
 	function = g_hook_alloc (hook->list);
 	function->func = callback;
 	function->data = data;
 	function->destroy = NULL;
 	GTH_HOOK_CALLBACK (function)->sort_order = sort_order;
-	
+
 	g_hook_insert_sorted (hook->list, function, hook_compare_func);
 }
 
 
 void
-gth_hook_remove_callback (const char *name, 
+gth_hook_remove_callback (const char *name,
 			  GCallback   callback)
 {
 	GthHook *hook;
 	GHook   *function;
-	
-	hook = GET_HOOK (name);	
+
+	hook = GET_HOOK (name);
 	function = g_hook_find_func (hook->list, TRUE, callback);
 	if (function == NULL) {
 		g_warning ("callback not found in hook '%s'", name);
@@ -161,7 +170,7 @@ typedef void (*GthMarshaller2Args) (gpointer, gpointer, gpointer);
 typedef void (*GthMarshaller3Args) (gpointer, gpointer, gpointer, gpointer);
 
 
-static void 
+static void
 invoke_marshaller_0 (GHook    *hook,
                      gpointer  data)
 {
@@ -169,38 +178,38 @@ invoke_marshaller_0 (GHook    *hook,
 }
 
 
-static void 
+static void
 invoke_marshaller_1 (GHook    *hook,
                      gpointer  data)
 {
 	gpointer *marshal_data = data;
-	
+
 	((GthMarshaller1Arg) hook->func) (marshal_data[0], hook->data);
 }
 
 
-static void 
+static void
 invoke_marshaller_2 (GHook    *hook,
                      gpointer  data)
 {
 	gpointer *marshal_data = data;
-	
+
 	((GthMarshaller2Args) hook->func) (marshal_data[0], marshal_data[1], hook->data);
 }
 
 
-static void 
+static void
 invoke_marshaller_3 (GHook    *hook,
                      gpointer  data)
 {
 	gpointer *marshal_data = data;
-	
+
 	((GthMarshaller3Args) hook->func) (marshal_data[0], marshal_data[1], marshal_data[2], hook->data);
 }
 
 
 void
-gth_hook_invoke (const char *name, 
+gth_hook_invoke (const char *name,
 		 gpointer    first_data,
 		 ...)
 {
@@ -209,18 +218,18 @@ gth_hook_invoke (const char *name,
 	int              i = 0;
 	va_list          args;
 	GHookMarshaller  invoke_marshaller;
-	
+
 	hook = GET_HOOK (name);
 	marshal_data = g_new0 (gpointer, hook->n_args);
-	
+
 	if (first_data != NULL)
 		marshal_data[i++] = first_data;
-	
+
 	va_start (args, first_data);
-	while (i < hook->n_args) 
+	while (i < hook->n_args)
 		marshal_data[i++] = va_arg (args, gpointer);
 	va_end (args);
-	
+
 	switch (hook->n_args) {
 	case 0:
 		invoke_marshaller = invoke_marshaller_0;
@@ -235,13 +244,13 @@ gth_hook_invoke (const char *name,
 		invoke_marshaller = invoke_marshaller_3;
 		break;
 	default:
-		invoke_marshaller = NULL; 
+		invoke_marshaller = NULL;
 		break;
 	}
-	
+
 	if (invoke_marshaller != NULL)
 		g_hook_list_marshal (hook->list, FALSE, invoke_marshaller, marshal_data);
-	
+
 	g_free (marshal_data);
 }
 
@@ -252,52 +261,52 @@ typedef void * (*GthMarshaller2ArgsGet) (gpointer, gpointer, gpointer);
 typedef void * (*GthMarshaller3ArgsGet) (gpointer, gpointer, gpointer, gpointer);
 
 
-static void 
+static void
 invoke_marshaller_0_get (GHook    *hook,
                         gpointer  data)
 {
 	gpointer *marshal_data = data;
-	
+
 	if (marshal_data[0] == NULL)
 		marshal_data[0] = ((GthMarshaller0ArgsGet) hook->func) (hook->data);
 }
 
 
-static void 
+static void
 invoke_marshaller_1_get (GHook    *hook,
                         gpointer  data)
 {
 	gpointer *marshal_data = data;
-	
+
 	if (marshal_data[1] == NULL)
 		marshal_data[1] = ((GthMarshaller1ArgGet) hook->func) (marshal_data[0], hook->data);
 }
 
 
-static void 
+static void
 invoke_marshaller_2_get (GHook    *hook,
                          gpointer  data)
 {
 	gpointer *marshal_data = data;
-	
+
 	if (marshal_data[2] == NULL)
 		marshal_data[2] = ((GthMarshaller2ArgsGet) hook->func) (marshal_data[0], marshal_data[1], hook->data);
 }
 
 
-static void 
+static void
 invoke_marshaller_3_get (GHook    *hook,
                          gpointer  data)
 {
 	gpointer *marshal_data = data;
-	
+
 	if (marshal_data[3] == NULL)
 		marshal_data[3] = ((GthMarshaller3ArgsGet) hook->func) (marshal_data[0], marshal_data[1], marshal_data[2], hook->data);
 }
 
 
 void *
-gth_hook_invoke_get (const char *name, 
+gth_hook_invoke_get (const char *name,
 		     gpointer    first_data,
 		     ...)
 {
@@ -307,17 +316,17 @@ gth_hook_invoke_get (const char *name,
 	va_list          args;
 	GHookMarshaller  invoke_marshaller;
 	void            *value;
-	
+
 	hook = GET_HOOK_OR_NULL (name);
 	marshal_data = g_new0 (gpointer, hook->n_args + 1);
 	marshal_data[hook->n_args] = NULL;
-	
+
 	va_start (args, first_data);
 	marshal_data[i++] = first_data;
-	while (i < hook->n_args) 
+	while (i < hook->n_args)
 		marshal_data[i++] = va_arg (args, gpointer);
 	va_end (args);
-	
+
 	switch (hook->n_args) {
 	case 0:
 		invoke_marshaller = invoke_marshaller_0_get;
@@ -332,16 +341,16 @@ gth_hook_invoke_get (const char *name,
 		invoke_marshaller = invoke_marshaller_3_get;
 		break;
 	default:
-		invoke_marshaller = NULL; 
+		invoke_marshaller = NULL;
 		break;
 	}
-	
+
 	if (invoke_marshaller != NULL)
 		g_hook_list_marshal (hook->list, FALSE, invoke_marshaller, marshal_data);
-	
+
 	value = marshal_data[hook->n_args];
-	
+
 	g_free (marshal_data);
-	
-	return value;	
+
+	return value;
 }
diff --git a/gthumb/gth-hook.h b/gthumb/gth-hook.h
index b7cb131..f11f866 100644
--- a/gthumb/gth-hook.h
+++ b/gthumb/gth-hook.h
@@ -28,21 +28,22 @@
 
 G_BEGIN_DECLS
 
-void    gth_hooks_initialize      (void);
-void    gth_hook_register         (const char *name, 
-				   int         n_args);
-void    gth_hook_add_callback     (const char *name, 
-				   int         sort_order,
-				   GCallback   callback, 
-				   gpointer    data);
-void    gth_hook_remove_callback  (const char *name, 
-				   GCallback   callback);
-void    gth_hook_invoke           (const char *name, 
-				   gpointer    first_data,
-				   ...);
-void *  gth_hook_invoke_get       (const char *name, 
-				   gpointer    first_data,
-				   ...);
+void      gth_hooks_initialize      (void);
+void      gth_hook_register         (const char *name,
+				     int         n_args);
+gboolean  gth_hook_present          (const char *name);
+void      gth_hook_add_callback     (const char *name,
+				     int         sort_order,
+				     GCallback   callback,
+				     gpointer    data);
+void      gth_hook_remove_callback  (const char *name,
+				     GCallback   callback);
+void      gth_hook_invoke           (const char *name,
+				     gpointer    first_data,
+				     ...);
+void *    gth_hook_invoke_get       (const char *name,
+				     gpointer    first_data,
+				     ...);
 
 G_END_DECLS
 
diff --git a/gthumb/gth-image-loader.c b/gthumb/gth-image-loader.c
index c165abe..d962034 100644
--- a/gthumb/gth-image-loader.c
+++ b/gthumb/gth-image-loader.c
@@ -237,7 +237,8 @@ load_image_thread (void *thread_data)
 				FileLoader loader;
 
 				loader = gth_main_get_file_loader (gth_file_data_get_mime_type (file));
-				animation = loader (file, &error, -1, -1);
+				if (loader != NULL)
+					animation = loader (file, &error, -1, -1);
 			}
 		}
 
diff --git a/gthumb/gth-main.c b/gthumb/gth-main.c
index e44d2d5..491efb8 100644
--- a/gthumb/gth-main.c
+++ b/gthumb/gth-main.c
@@ -1087,8 +1087,8 @@ gth_main_activate_extensions (void)
 	if (active_extensions == NULL)
 		for (i = 0; default_extensions[i] != NULL; i++)
 			active_extensions = g_slist_prepend (active_extensions, g_strdup (default_extensions[i]));
-	active_extensions = g_slist_reverse (active_extensions);
 
+	active_extensions = gth_extension_manager_order_extensions (Main->priv->extension_manager, active_extensions);
 	for (scan = active_extensions; scan; scan = scan->next) {
 		char   *name = scan->data;
 		GError *error = NULL;



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