[gthumb/ext] [image rotation] added basic image rotation tools
- From: Paolo Bacchilega <paobac src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [gthumb/ext] [image rotation] added basic image rotation tools
- Date: Mon, 17 Aug 2009 11:18:01 +0000 (UTC)
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 (¤t_time);
- char *date_time = _g_time_val_to_exif_date (¤t_time);
- ed["Exif.Image.DateTime"] = date_time;
- g_free (date_time);
+ // Update the DateTime tag
- // IPTC Data
+ GTimeVal current_time;
+ g_get_current_time (¤t_time);
+ char *date_time = _g_time_val_to_exif_date (¤t_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]