summaryrefslogtreecommitdiff
path: root/src/eom-image.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/eom-image.c')
-rw-r--r--src/eom-image.c2237
1 files changed, 2237 insertions, 0 deletions
diff --git a/src/eom-image.c b/src/eom-image.c
new file mode 100644
index 0000000..e57e7cb
--- /dev/null
+++ b/src/eom-image.c
@@ -0,0 +1,2237 @@
+/* Eye Of Mate - Image
+ *
+ * Copyright (C) 2006 The Free Software Foundation
+ *
+ * Author: Lucas Rocha <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define GDK_PIXBUF_ENABLE_BACKEND
+
+#include "eom-image.h"
+#include "eom-image-private.h"
+#include "eom-debug.h"
+
+#ifdef HAVE_JPEG
+#include "eom-image-jpeg.h"
+#endif
+
+#include "eom-marshal.h"
+#include "eom-pixbuf-util.h"
+#include "eom-metadata-reader.h"
+#include "eom-image-save-info.h"
+#include "eom-transform.h"
+#include "eom-util.h"
+#include "eom-jobs.h"
+#include "eom-thumbnail.h"
+
+#include <unistd.h>
+#include <string.h>
+
+#include <glib.h>
+#include <glib-object.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#ifdef HAVE_EXIF
+#include <libexif/exif-data.h>
+#include <libexif/exif-utils.h>
+#include <libexif/exif-loader.h>
+#endif
+
+#ifdef HAVE_LCMS
+#include <lcms.h>
+#ifndef EXIF_TAG_GAMMA
+#define EXIF_TAG_GAMMA 0xa500
+#endif
+#endif
+
+#define EOM_IMAGE_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_IMAGE, EomImagePrivate))
+
+G_DEFINE_TYPE (EomImage, eom_image, G_TYPE_OBJECT)
+
+enum {
+ SIGNAL_CHANGED,
+ SIGNAL_SIZE_PREPARED,
+ SIGNAL_THUMBNAIL_CHANGED,
+ SIGNAL_SAVE_PROGRESS,
+ SIGNAL_NEXT_FRAME,
+ SIGNAL_FILE_CHANGED,
+ SIGNAL_LAST
+};
+
+static gint signals[SIGNAL_LAST];
+
+static GList *supported_mime_types = NULL;
+
+#define EOM_IMAGE_READ_BUFFER_SIZE 65535
+
+static void
+eom_image_free_mem_private (EomImage *image)
+{
+ EomImagePrivate *priv;
+
+ priv = image->priv;
+
+ if (priv->status == EOM_IMAGE_STATUS_LOADING) {
+ eom_image_cancel_load (image);
+ } else {
+ if (priv->anim_iter != NULL) {
+ g_object_unref (priv->anim_iter);
+ priv->anim_iter = NULL;
+ }
+
+ if (priv->anim != NULL) {
+ g_object_unref (priv->anim);
+ priv->anim = NULL;
+ }
+
+ priv->is_playing = FALSE;
+
+ if (priv->image != NULL) {
+ g_object_unref (priv->image);
+ priv->image = NULL;
+ }
+
+#ifdef HAVE_RSVG
+ if (priv->svg != NULL) {
+ g_object_unref (priv->svg);
+ priv->svg = NULL;
+ }
+#endif
+
+#ifdef HAVE_EXIF
+ if (priv->exif != NULL) {
+ exif_data_unref (priv->exif);
+ priv->exif = NULL;
+ }
+#endif
+
+ if (priv->exif_chunk != NULL) {
+ g_free (priv->exif_chunk);
+ priv->exif_chunk = NULL;
+ }
+
+ priv->exif_chunk_len = 0;
+
+#ifdef HAVE_EXEMPI
+ if (priv->xmp != NULL) {
+ xmp_free (priv->xmp);
+ priv->xmp = NULL;
+ }
+#endif
+
+#ifdef HAVE_LCMS
+ if (priv->profile != NULL) {
+ cmsCloseProfile (priv->profile);
+ priv->profile = NULL;
+ }
+#endif
+
+ priv->status = EOM_IMAGE_STATUS_UNKNOWN;
+ }
+}
+
+static void
+eom_image_dispose (GObject *object)
+{
+ EomImagePrivate *priv;
+
+ priv = EOM_IMAGE (object)->priv;
+
+ eom_image_free_mem_private (EOM_IMAGE (object));
+
+ if (priv->file) {
+ g_object_unref (priv->file);
+ priv->file = NULL;
+ }
+
+ if (priv->caption) {
+ g_free (priv->caption);
+ priv->caption = NULL;
+ }
+
+ if (priv->collate_key) {
+ g_free (priv->collate_key);
+ priv->collate_key = NULL;
+ }
+
+ if (priv->file_type) {
+ g_free (priv->file_type);
+ priv->file_type = NULL;
+ }
+
+ if (priv->status_mutex) {
+ g_mutex_free (priv->status_mutex);
+ priv->status_mutex = NULL;
+ }
+
+ if (priv->trans) {
+ g_object_unref (priv->trans);
+ priv->trans = NULL;
+ }
+
+ if (priv->trans_autorotate) {
+ g_object_unref (priv->trans_autorotate);
+ priv->trans_autorotate = NULL;
+ }
+
+ if (priv->undo_stack) {
+ g_slist_foreach (priv->undo_stack, (GFunc) g_object_unref, NULL);
+ g_slist_free (priv->undo_stack);
+ priv->undo_stack = NULL;
+ }
+
+ G_OBJECT_CLASS (eom_image_parent_class)->dispose (object);
+}
+
+static void
+eom_image_class_init (EomImageClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass*) klass;
+
+ object_class->dispose = eom_image_dispose;
+
+ signals[SIGNAL_SIZE_PREPARED] =
+ g_signal_new ("size-prepared",
+ EOM_TYPE_IMAGE,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EomImageClass, size_prepared),
+ NULL, NULL,
+ eom_marshal_VOID__INT_INT,
+ G_TYPE_NONE, 2,
+ G_TYPE_INT,
+ G_TYPE_INT);
+
+ signals[SIGNAL_CHANGED] =
+ g_signal_new ("changed",
+ EOM_TYPE_IMAGE,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EomImageClass, changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[SIGNAL_THUMBNAIL_CHANGED] =
+ g_signal_new ("thumbnail-changed",
+ EOM_TYPE_IMAGE,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EomImageClass, thumbnail_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[SIGNAL_SAVE_PROGRESS] =
+ g_signal_new ("save-progress",
+ EOM_TYPE_IMAGE,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EomImageClass, save_progress),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__FLOAT,
+ G_TYPE_NONE, 1,
+ G_TYPE_FLOAT);
+ /**
+ * EomImage::next-frame:
+ * @img: the object which received the signal.
+ * @delay: number of milliseconds the current frame will be displayed.
+ *
+ * The ::next-frame signal will be emitted each time an animated image
+ * advances to the next frame.
+ */
+ signals[SIGNAL_NEXT_FRAME] =
+ g_signal_new ("next-frame",
+ EOM_TYPE_IMAGE,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EomImageClass, next_frame),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__INT,
+ G_TYPE_NONE, 1,
+ G_TYPE_INT);
+
+ signals[SIGNAL_FILE_CHANGED] = g_signal_new ("file-changed",
+ EOM_TYPE_IMAGE,
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EomImageClass, file_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_type_class_add_private (object_class, sizeof (EomImagePrivate));
+}
+
+static void
+eom_image_init (EomImage *img)
+{
+ img->priv = EOM_IMAGE_GET_PRIVATE (img);
+
+ img->priv->file = NULL;
+ img->priv->image = NULL;
+ img->priv->anim = NULL;
+ img->priv->anim_iter = NULL;
+ img->priv->is_playing = FALSE;
+ img->priv->thumbnail = NULL;
+ img->priv->width = -1;
+ img->priv->height = -1;
+ img->priv->modified = FALSE;
+ img->priv->status_mutex = g_mutex_new ();
+ img->priv->status = EOM_IMAGE_STATUS_UNKNOWN;
+ img->priv->metadata_status = EOM_IMAGE_METADATA_NOT_READ;
+ img->priv->is_monitored = FALSE;
+ img->priv->undo_stack = NULL;
+ img->priv->trans = NULL;
+ img->priv->trans_autorotate = NULL;
+ img->priv->data_ref_count = 0;
+#ifdef HAVE_EXIF
+ img->priv->orientation = 0;
+ img->priv->autorotate = FALSE;
+ img->priv->exif = NULL;
+#endif
+#ifdef HAVE_EXEMPI
+ img->priv->xmp = NULL;
+#endif
+#ifdef HAVE_LCMS
+ img->priv->profile = NULL;
+#endif
+#ifdef HAVE_RSVG
+ img->priv->svg = NULL;
+#endif
+}
+
+EomImage *
+eom_image_new (const char *txt_uri)
+{
+ EomImage *img;
+
+ img = EOM_IMAGE (g_object_new (EOM_TYPE_IMAGE, NULL));
+
+ img->priv->file = g_file_new_for_uri (txt_uri);
+
+ return img;
+}
+
+EomImage *
+eom_image_new_file (GFile *file)
+{
+ EomImage *img;
+
+ img = EOM_IMAGE (g_object_new (EOM_TYPE_IMAGE, NULL));
+
+ img->priv->file = g_object_ref (file);
+
+ return img;
+}
+
+GQuark
+eom_image_error_quark (void)
+{
+ static GQuark q = 0;
+
+ if (q == 0) {
+ q = g_quark_from_static_string ("eom-image-error-quark");
+ }
+
+ return q;
+}
+
+static void
+eom_image_update_exif_data (EomImage *image)
+{
+#ifdef HAVE_EXIF
+ EomImagePrivate *priv;
+ ExifEntry *entry;
+ ExifByteOrder bo;
+
+ eom_debug (DEBUG_IMAGE_DATA);
+
+ g_return_if_fail (EOM_IS_IMAGE (image));
+
+ priv = image->priv;
+
+ if (priv->exif == NULL) return;
+
+ bo = exif_data_get_byte_order (priv->exif);
+
+ /* Update image width */
+ entry = exif_data_get_entry (priv->exif, EXIF_TAG_PIXEL_X_DIMENSION);
+ if (entry != NULL && (priv->width >= 0)) {
+ if (entry->format == EXIF_FORMAT_LONG)
+ exif_set_long (entry->data, bo, priv->width);
+ else if (entry->format == EXIF_FORMAT_SHORT)
+ exif_set_short (entry->data, bo, priv->width);
+ else
+ g_warning ("Exif entry has unsupported size");
+ }
+
+ /* Update image height */
+ entry = exif_data_get_entry (priv->exif, EXIF_TAG_PIXEL_Y_DIMENSION);
+ if (entry != NULL && (priv->height >= 0)) {
+ if (entry->format == EXIF_FORMAT_LONG)
+ exif_set_long (entry->data, bo, priv->height);
+ else if (entry->format == EXIF_FORMAT_SHORT)
+ exif_set_short (entry->data, bo, priv->height);
+ else
+ g_warning ("Exif entry has unsupported size");
+ }
+
+ /* Update image orientation */
+ entry = exif_data_get_entry (priv->exif, EXIF_TAG_ORIENTATION);
+ if (entry != NULL) {
+ if (entry->format == EXIF_FORMAT_LONG)
+ exif_set_long (entry->data, bo, 1);
+ else if (entry->format == EXIF_FORMAT_SHORT)
+ exif_set_short (entry->data, bo, 1);
+ else
+ g_warning ("Exif entry has unsupported size");
+
+ priv->orientation = 1;
+ }
+#endif
+}
+
+static void
+eom_image_real_transform (EomImage *img,
+ EomTransform *trans,
+ gboolean is_undo,
+ EomJob *job)
+{
+ EomImagePrivate *priv;
+ GdkPixbuf *transformed;
+ gboolean modified = FALSE;
+
+ g_return_if_fail (EOM_IS_IMAGE (img));
+ g_return_if_fail (EOM_IS_TRANSFORM (trans));
+
+ priv = img->priv;
+
+ if (priv->image != NULL) {
+ transformed = eom_transform_apply (trans, priv->image, job);
+
+ g_object_unref (priv->image);
+ priv->image = transformed;
+
+ priv->width = gdk_pixbuf_get_width (transformed);
+ priv->height = gdk_pixbuf_get_height (transformed);
+
+ modified = TRUE;
+ }
+
+ if (priv->thumbnail != NULL) {
+ transformed = eom_transform_apply (trans, priv->thumbnail, NULL);
+
+ g_object_unref (priv->thumbnail);
+ priv->thumbnail = transformed;
+
+ modified = TRUE;
+ }
+
+ if (modified) {
+ priv->modified = TRUE;
+ eom_image_update_exif_data (img);
+ }
+
+ if (priv->trans == NULL) {
+ g_object_ref (trans);
+ priv->trans = trans;
+ } else {
+ EomTransform *composition;
+
+ composition = eom_transform_compose (priv->trans, trans);
+
+ g_object_unref (priv->trans);
+
+ priv->trans = composition;
+ }
+
+ if (!is_undo) {
+ g_object_ref (trans);
+ priv->undo_stack = g_slist_prepend (priv->undo_stack, trans);
+ }
+}
+
+static gboolean
+do_emit_size_prepared_signal (EomImage *img)
+{
+ g_signal_emit (img, signals[SIGNAL_SIZE_PREPARED], 0,
+ img->priv->width, img->priv->height);
+ return FALSE;
+}
+
+static void
+eom_image_emit_size_prepared (EomImage *img)
+{
+ gdk_threads_add_idle_full (G_PRIORITY_DEFAULT_IDLE,
+ (GSourceFunc) do_emit_size_prepared_signal,
+ g_object_ref (img), g_object_unref);
+}
+
+static gboolean
+check_loader_threadsafety (GdkPixbufLoader *loader, gboolean *result)
+{
+ GdkPixbufFormat *format;
+ gboolean ret_val = FALSE;
+
+ format = gdk_pixbuf_loader_get_format (loader);
+ if (format) {
+ ret_val = TRUE;
+ if (result)
+ /* FIXME: We should not be accessing this struct internals
+ * directly. Keep track of bug #469209 to fix that. */
+ *result = format->flags & GDK_PIXBUF_FORMAT_THREADSAFE;
+ }
+
+ return ret_val;
+}
+
+static void
+eom_image_pre_size_prepared (GdkPixbufLoader *loader,
+ gint width,
+ gint height,
+ gpointer data)
+{
+ EomImage *img;
+
+ eom_debug (DEBUG_IMAGE_LOAD);
+
+ g_return_if_fail (EOM_IS_IMAGE (data));
+
+ img = EOM_IMAGE (data);
+ check_loader_threadsafety (loader, &img->priv->threadsafe_format);
+}
+
+static void
+eom_image_size_prepared (GdkPixbufLoader *loader,
+ gint width,
+ gint height,
+ gpointer data)
+{
+ EomImage *img;
+
+ eom_debug (DEBUG_IMAGE_LOAD);
+
+ g_return_if_fail (EOM_IS_IMAGE (data));
+
+ img = EOM_IMAGE (data);
+
+ g_mutex_lock (img->priv->status_mutex);
+
+ img->priv->width = width;
+ img->priv->height = height;
+
+ g_mutex_unlock (img->priv->status_mutex);
+
+#ifdef HAVE_EXIF
+ if (img->priv->threadsafe_format && (!img->priv->autorotate || img->priv->exif))
+#else
+ if (img->priv->threadsafe_format)
+#endif
+ eom_image_emit_size_prepared (img);
+}
+
+static EomMetadataReader*
+check_for_metadata_img_format (EomImage *img, guchar *buffer, guint bytes_read)
+{
+ EomMetadataReader *md_reader = NULL;
+
+ eom_debug_message (DEBUG_IMAGE_DATA, "Check image format for jpeg: %x%x - length: %i",
+ buffer[0], buffer[1], bytes_read);
+
+ if (bytes_read >= 2) {
+ /* SOI (start of image) marker for JPEGs is 0xFFD8 */
+ if ((buffer[0] == 0xFF) && (buffer[1] == 0xD8)) {
+ md_reader = eom_metadata_reader_new (EOM_METADATA_JPEG);
+ }
+ if (bytes_read >= 8 &&
+ memcmp (buffer, "\x89PNG\x0D\x0A\x1a\x0A", 8) == 0) {
+ md_reader = eom_metadata_reader_new (EOM_METADATA_PNG);
+ }
+ }
+
+ return md_reader;
+}
+
+static gboolean
+eom_image_needs_transformation (EomImage *img)
+{
+ g_return_val_if_fail (EOM_IS_IMAGE (img), FALSE);
+
+ return (img->priv->trans != NULL || img->priv->trans_autorotate != NULL);
+}
+
+static gboolean
+eom_image_apply_transformations (EomImage *img, GError **error)
+{
+ GdkPixbuf *transformed = NULL;
+ EomTransform *composition = NULL;
+ EomImagePrivate *priv;
+
+ g_return_val_if_fail (EOM_IS_IMAGE (img), FALSE);
+
+ priv = img->priv;
+
+ if (priv->trans == NULL && priv->trans_autorotate == NULL) {
+ return TRUE;
+ }
+
+ if (priv->image == NULL) {
+ g_set_error (error,
+ EOM_IMAGE_ERROR,
+ EOM_IMAGE_ERROR_NOT_LOADED,
+ _("Transformation on unloaded image."));
+
+ return FALSE;
+ }
+
+ if (priv->trans != NULL && priv->trans_autorotate != NULL) {
+ composition = eom_transform_compose (priv->trans,
+ priv->trans_autorotate);
+ } else if (priv->trans != NULL) {
+ composition = g_object_ref (priv->trans);
+ } else if (priv->trans_autorotate != NULL) {
+ composition = g_object_ref (priv->trans_autorotate);
+ }
+
+ if (composition != NULL) {
+ transformed = eom_transform_apply (composition, priv->image, NULL);
+ }
+
+ g_object_unref (priv->image);
+ priv->image = transformed;
+
+ if (transformed != NULL) {
+ priv->width = gdk_pixbuf_get_width (priv->image);
+ priv->height = gdk_pixbuf_get_height (priv->image);
+ } else {
+ g_set_error (error,
+ EOM_IMAGE_ERROR,
+ EOM_IMAGE_ERROR_GENERIC,
+ _("Transformation failed."));
+ }
+
+ g_object_unref (composition);
+
+ return (transformed != NULL);
+}
+
+static void
+eom_image_get_file_info (EomImage *img,
+ goffset *bytes,
+ gchar **mime_type,
+ GError **error)
+{
+ GFileInfo *file_info;
+
+ file_info = g_file_query_info (img->priv->file,
+ G_FILE_ATTRIBUTE_STANDARD_SIZE ","
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+ 0, NULL, error);
+
+ if (file_info == NULL) {
+ if (bytes)
+ *bytes = 0;
+
+ if (mime_type)
+ *mime_type = NULL;
+
+ g_set_error (error,
+ EOM_IMAGE_ERROR,
+ EOM_IMAGE_ERROR_VFS,
+ "Error in getting image file info");
+ } else {
+ if (bytes)
+ *bytes = g_file_info_get_size (file_info);
+
+ if (mime_type)
+ *mime_type = g_strdup (g_file_info_get_content_type (file_info));
+ g_object_unref (file_info);
+ }
+}
+
+#ifdef HAVE_LCMS
+void
+eom_image_apply_display_profile (EomImage *img, cmsHPROFILE screen)
+{
+ EomImagePrivate *priv;
+ cmsHTRANSFORM transform;
+ gint row, width, rows, stride;
+ guchar *p;
+
+ g_return_if_fail (img != NULL);
+
+ priv = img->priv;
+
+ if (screen == NULL || priv->profile == NULL) return;
+
+ /* TODO: support other colorspaces than RGB */
+ if (cmsGetColorSpace (priv->profile) != icSigRgbData ||
+ cmsGetColorSpace (screen) != icSigRgbData) {
+ eom_debug_message (DEBUG_LCMS, "One or both ICC profiles not in RGB colorspace; not correcting");
+ return;
+ }
+
+ /* TODO: find the right way to colorcorrect RGBA images */
+ if (gdk_pixbuf_get_has_alpha (priv->image)) {
+ eom_debug_message (DEBUG_LCMS, "Colorcorrecting RGBA images is unsupported.");
+ return;
+ }
+
+ transform = cmsCreateTransform (priv->profile,
+ TYPE_RGB_8,
+ screen,
+ TYPE_RGB_8,
+ INTENT_PERCEPTUAL,
+ 0);
+
+ if (G_LIKELY (transform != NULL)) {
+ rows = gdk_pixbuf_get_height (priv->image);
+ width = gdk_pixbuf_get_width (priv->image);
+ stride = gdk_pixbuf_get_rowstride (priv->image);
+ p = gdk_pixbuf_get_pixels (priv->image);
+
+ for (row = 0; row < rows; ++row) {
+ cmsDoTransform (transform, p, p, width);
+ p += stride;
+ }
+ cmsDeleteTransform (transform);
+ }
+}
+
+static void
+eom_image_set_icc_data (EomImage *img, EomMetadataReader *md_reader)
+{
+ EomImagePrivate *priv = img->priv;
+
+ priv->profile = eom_metadata_reader_get_icc_profile (md_reader);
+
+
+}
+#endif
+
+#ifdef HAVE_EXIF
+static void
+eom_image_set_orientation (EomImage *img)
+{
+ EomImagePrivate *priv;
+ ExifData* exif;
+
+ g_return_if_fail (EOM_IS_IMAGE (img));
+
+ priv = img->priv;
+
+ exif = (ExifData*) eom_image_get_exif_info (img);
+
+ if (exif != NULL) {
+ ExifByteOrder o = exif_data_get_byte_order (exif);
+
+ ExifEntry *entry = exif_data_get_entry (exif,
+ EXIF_TAG_ORIENTATION);
+
+ if (entry && entry->data != NULL) {
+ priv->orientation = exif_get_short (entry->data, o);
+ }
+ }
+
+ /* exif_data_unref handles NULL values like g_free */
+ exif_data_unref (exif);
+
+ if (priv->orientation > 4 &&
+ priv->orientation < 9) {
+ gint tmp;
+
+ tmp = priv->width;
+ priv->width = priv->height;
+ priv->height = tmp;
+ }
+}
+
+static void
+eom_image_real_autorotate (EomImage *img)
+{
+ static const EomTransformType lookup[8] = {EOM_TRANSFORM_NONE,
+ EOM_TRANSFORM_FLIP_HORIZONTAL,
+ EOM_TRANSFORM_ROT_180,
+ EOM_TRANSFORM_FLIP_VERTICAL,
+ EOM_TRANSFORM_TRANSPOSE,
+ EOM_TRANSFORM_ROT_90,
+ EOM_TRANSFORM_TRANSVERSE,
+ EOM_TRANSFORM_ROT_270};
+ EomImagePrivate *priv;
+ EomTransformType type;
+
+ g_return_if_fail (EOM_IS_IMAGE (img));
+
+ priv = img->priv;
+
+ type = (priv->orientation >= 1 && priv->orientation <= 8 ?
+ lookup[priv->orientation - 1] : EOM_TRANSFORM_NONE);
+
+ if (type != EOM_TRANSFORM_NONE) {
+ img->priv->trans_autorotate = eom_transform_new (type);
+ }
+
+ /* Disable auto orientation for next loads */
+ priv->autorotate = FALSE;
+}
+
+void
+eom_image_autorotate (EomImage *img)
+{
+ g_return_if_fail (EOM_IS_IMAGE (img));
+
+ /* Schedule auto orientation */
+ img->priv->autorotate = TRUE;
+}
+#endif
+
+#ifdef HAVE_EXEMPI
+static void
+eom_image_set_xmp_data (EomImage *img, EomMetadataReader *md_reader)
+{
+ EomImagePrivate *priv;
+
+ g_return_if_fail (EOM_IS_IMAGE (img));
+
+ priv = img->priv;
+
+ if (priv->xmp) {
+ xmp_free (priv->xmp);
+ }
+ priv->xmp = eom_metadata_reader_get_xmp_data (md_reader);
+}
+#endif
+
+static void
+eom_image_set_exif_data (EomImage *img, EomMetadataReader *md_reader)
+{
+ EomImagePrivate *priv;
+
+ g_return_if_fail (EOM_IS_IMAGE (img));
+
+ priv = img->priv;
+
+#ifdef HAVE_EXIF
+ g_mutex_lock (priv->status_mutex);
+ if (priv->exif) {
+ exif_data_unref (priv->exif);
+ }
+ priv->exif = eom_metadata_reader_get_exif_data (md_reader);
+ g_mutex_unlock (priv->status_mutex);
+
+ priv->exif_chunk = NULL;
+ priv->exif_chunk_len = 0;
+
+ /* EXIF data is already available, set the image orientation */
+ if (priv->autorotate) {
+ eom_image_set_orientation (img);
+
+ /* Emit size prepared signal if we have the size */
+ if (priv->width > 0 &&
+ priv->height > 0) {
+ eom_image_emit_size_prepared (img);
+ }
+ }
+#else
+ if (priv->exif_chunk) {
+ g_free (priv->exif_chunk);
+ }
+ eom_metadata_reader_get_exif_chunk (md_reader,
+ &priv->exif_chunk,
+ &priv->exif_chunk_len);
+#endif
+}
+
+/*
+ * Attempts to get the image dimensions from the thumbnail.
+ * Returns FALSE if this information is not found.
+ **/
+static gboolean
+eom_image_get_dimension_from_thumbnail (EomImage *image,
+ gint *width,
+ gint *height)
+{
+ if (image->priv->thumbnail == NULL)
+ return FALSE;
+
+ *width = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (image->priv->thumbnail),
+ EOM_THUMBNAIL_ORIGINAL_WIDTH));
+ *height = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (image->priv->thumbnail),
+ EOM_THUMBNAIL_ORIGINAL_HEIGHT));
+
+ return (*width || *height);
+}
+
+static gboolean
+eom_image_real_load (EomImage *img,
+ guint data2read,
+ EomJob *job,
+ GError **error)
+{
+ EomImagePrivate *priv;
+ GFileInputStream *input_stream;
+ EomMetadataReader *md_reader = NULL;
+ GdkPixbufFormat *format;
+ gchar *mime_type;
+ GdkPixbufLoader *loader = NULL;
+ guchar *buffer;
+ goffset bytes_read, bytes_read_total = 0;
+ gboolean failed = FALSE;
+ gboolean first_run = TRUE;
+ gboolean set_metadata = TRUE;
+ gboolean read_image_data = (data2read & EOM_IMAGE_DATA_IMAGE);
+ gboolean read_only_dimension = (data2read & EOM_IMAGE_DATA_DIMENSION) &&
+ ((data2read ^ EOM_IMAGE_DATA_DIMENSION) == 0);
+
+
+ priv = img->priv;
+
+ g_assert (!read_image_data || priv->image == NULL);
+
+ if (read_image_data && priv->file_type != NULL) {
+ g_free (priv->file_type);
+ priv->file_type = NULL;
+ }
+
+ priv->threadsafe_format = FALSE;
+
+ eom_image_get_file_info (img, &priv->bytes, &mime_type, error);
+
+ if (error && *error) {
+ g_free (mime_type);
+ return FALSE;
+ }
+
+ if (read_only_dimension) {
+ gint width, height;
+ gboolean done;
+
+ done = eom_image_get_dimension_from_thumbnail (img,
+ &width,
+ &height);
+
+ if (done) {
+ priv->width = width;
+ priv->height = height;
+
+ g_free (mime_type);
+ return TRUE;
+ }
+ }
+
+ input_stream = g_file_read (priv->file, NULL, error);
+
+ if (input_stream == NULL) {
+ g_free (mime_type);
+
+ if (error != NULL) {
+ g_clear_error (error);
+ g_set_error (error,
+ EOM_IMAGE_ERROR,
+ EOM_IMAGE_ERROR_VFS,
+ "Failed to open input stream for file");
+ }
+ return FALSE;
+ }
+
+ buffer = g_new0 (guchar, EOM_IMAGE_READ_BUFFER_SIZE);
+
+ if (read_image_data || read_only_dimension) {
+ gboolean checked_threadsafety = FALSE;
+
+#ifdef HAVE_RSVG
+ if (priv->svg != NULL) {
+ g_object_unref (priv->svg);
+ priv->svg = NULL;
+ }
+
+ if (!strcmp (mime_type, "image/svg+xml")) {
+ gchar *file_path;
+ /* Keep the object for rendering */
+ priv->svg = rsvg_handle_new ();
+ file_path = g_file_get_path (priv->file);
+ rsvg_handle_set_base_uri (priv->svg, file_path);
+ g_free (file_path);
+ }
+#endif
+ loader = gdk_pixbuf_loader_new_with_mime_type (mime_type, error);
+
+ if (error && *error) {
+ g_error_free (*error);
+ *error = NULL;
+
+ loader = gdk_pixbuf_loader_new ();
+ } else {
+ /* The mimetype-based loader should know the
+ * format here already. */
+ checked_threadsafety = check_loader_threadsafety (loader, &priv->threadsafe_format);
+ }
+
+ /* This is used to detect non-threadsafe loaders and disable
+ * any possible asyncronous task that could bring deadlocks
+ * to image loading process. */
+ if (!checked_threadsafety)
+ g_signal_connect (loader,
+ "size-prepared",
+ G_CALLBACK (eom_image_pre_size_prepared),
+ img);
+
+ g_signal_connect_object (G_OBJECT (loader),
+ "size-prepared",
+ G_CALLBACK (eom_image_size_prepared),
+ img,
+ 0);
+ }
+ g_free (mime_type);
+
+ while (!priv->cancel_loading) {
+ /* FIXME: make this async */
+ bytes_read = g_input_stream_read (G_INPUT_STREAM (input_stream),
+ buffer,
+ EOM_IMAGE_READ_BUFFER_SIZE,
+ NULL, error);
+
+ if (bytes_read == 0) {
+ /* End of the file */
+ break;
+ } else if (bytes_read == -1) {
+ failed = TRUE;
+
+ g_set_error (error,
+ EOM_IMAGE_ERROR,
+ EOM_IMAGE_ERROR_VFS,
+ "Failed to read from input stream");
+
+ break;
+ }
+
+ if ((read_image_data || read_only_dimension)) {
+ if (!gdk_pixbuf_loader_write (loader, buffer, bytes_read, error)) {
+ failed = TRUE;
+ break;
+ }
+#ifdef HAVE_RSVG
+ if (eom_image_is_svg (img) &&
+ !rsvg_handle_write (priv->svg, buffer, bytes_read, error)) {
+ failed = TRUE;
+ break;
+ }
+#endif
+ }
+
+ bytes_read_total += bytes_read;
+
+ if (job != NULL) {
+ float progress = (float) bytes_read_total / (float) priv->bytes;
+ eom_job_set_progress (job, progress);
+ }
+
+ if (first_run) {
+ md_reader = check_for_metadata_img_format (img, buffer, bytes_read);
+
+ if (md_reader == NULL) {
+ if (data2read == EOM_IMAGE_DATA_EXIF) {
+ g_set_error (error,
+ EOM_IMAGE_ERROR,
+ EOM_IMAGE_ERROR_GENERIC,
+ _("EXIF not supported for this file format."));
+ break;
+ }
+
+ if (priv->threadsafe_format)
+ eom_image_emit_size_prepared (img);
+
+ priv->metadata_status = EOM_IMAGE_METADATA_NOT_AVAILABLE;
+ }
+
+ first_run = FALSE;
+ }
+
+ if (md_reader != NULL) {
+ eom_metadata_reader_consume (md_reader, buffer, bytes_read);
+
+ if (eom_metadata_reader_finished (md_reader)) {
+ if (set_metadata) {
+ eom_image_set_exif_data (img, md_reader);
+
+#ifdef HAVE_LCMS
+ eom_image_set_icc_data (img, md_reader);
+#endif
+
+#ifdef HAVE_EXEMPI
+ eom_image_set_xmp_data (img, md_reader);
+#endif
+ set_metadata = FALSE;
+ priv->metadata_status = EOM_IMAGE_METADATA_READY;
+ }
+
+ if (data2read == EOM_IMAGE_DATA_EXIF)
+ break;
+ }
+ }
+
+ if (read_only_dimension &&
+ eom_image_has_data (img, EOM_IMAGE_DATA_DIMENSION)) {
+ break;
+ }
+ }
+
+ if (read_image_data || read_only_dimension) {
+ if (failed) {
+ gdk_pixbuf_loader_close (loader, NULL);
+ } else if (!gdk_pixbuf_loader_close (loader, error)) {
+ if (gdk_pixbuf_loader_get_pixbuf (loader) != NULL) {
+ /* Clear error in order to support partial
+ * images as well. */
+ g_clear_error (error);
+ }
+ }
+#ifdef HAVE_RSVG
+ if (eom_image_is_svg (img))
+ rsvg_handle_close (priv->svg, error);
+#endif
+ }
+
+ g_free (buffer);
+
+ g_object_unref (G_OBJECT (input_stream));
+
+ failed = (failed ||
+ priv->cancel_loading ||
+ bytes_read_total == 0 ||
+ (error && *error != NULL));
+
+ if (failed) {
+ if (priv->cancel_loading) {
+ priv->cancel_loading = FALSE;
+ priv->status = EOM_IMAGE_STATUS_UNKNOWN;
+ } else {
+ priv->status = EOM_IMAGE_STATUS_FAILED;
+ }
+ } else if (read_image_data) {
+ if (priv->image != NULL) {
+ g_object_unref (priv->image);
+ }
+
+ priv->anim = gdk_pixbuf_loader_get_animation (loader);
+
+ if (gdk_pixbuf_animation_is_static_image (priv->anim)) {
+ priv->image = gdk_pixbuf_animation_get_static_image (priv->anim);
+ priv->anim = NULL;
+ } else {
+ priv->anim_iter = gdk_pixbuf_animation_get_iter (priv->anim,NULL);
+ priv->image = gdk_pixbuf_animation_iter_get_pixbuf (priv->anim_iter);
+ }
+
+ if (G_LIKELY (priv->image != NULL)) {
+ g_object_ref (priv->image);
+
+ priv->width = gdk_pixbuf_get_width (priv->image);
+ priv->height = gdk_pixbuf_get_height (priv->image);
+
+ format = gdk_pixbuf_loader_get_format (loader);
+
+ if (format != NULL) {
+ priv->file_type = gdk_pixbuf_format_get_name (format);
+ }
+
+ /* If it's non-threadsafe loader, then trigger window
+ * showing in the end of the process. */
+ if (!priv->threadsafe_format)
+ eom_image_emit_size_prepared (img);
+ } else {
+ /* Some loaders don't report errors correctly.
+ * Error will be set below. */
+ failed = TRUE;
+ priv->status = EOM_IMAGE_STATUS_FAILED;
+ }
+ }
+
+ if (loader != NULL) {
+ g_object_unref (loader);
+ }
+
+ if (md_reader != NULL) {
+ g_object_unref (md_reader);
+ md_reader = NULL;
+ }
+
+ /* Catch-all in case of poor-error reporting */
+ if (failed && error && *error == NULL) {
+ g_set_error (error,
+ EOM_IMAGE_ERROR,
+ EOM_IMAGE_ERROR_GENERIC,
+ _("Image loading failed."));
+ }
+
+ return !failed;
+}
+
+gboolean
+eom_image_has_data (EomImage *img, EomImageData req_data)
+{
+ EomImagePrivate *priv;
+ gboolean has_data = TRUE;
+
+ g_return_val_if_fail (EOM_IS_IMAGE (img), FALSE);
+
+ priv = img->priv;
+
+ if ((req_data & EOM_IMAGE_DATA_IMAGE) > 0) {
+ req_data = (req_data & !EOM_IMAGE_DATA_IMAGE);
+ has_data = has_data && (priv->image != NULL);
+ }
+
+ if ((req_data & EOM_IMAGE_DATA_DIMENSION) > 0 ) {
+ req_data = (req_data & !EOM_IMAGE_DATA_DIMENSION);
+ has_data = has_data && (priv->width >= 0) && (priv->height >= 0);
+ }
+
+ if ((req_data & EOM_IMAGE_DATA_EXIF) > 0) {
+ req_data = (req_data & !EOM_IMAGE_DATA_EXIF);
+#ifdef HAVE_EXIF
+ has_data = has_data && (priv->exif != NULL);
+#else
+ has_data = has_data && (priv->exif_chunk != NULL);
+#endif
+ }
+
+ if ((req_data & EOM_IMAGE_DATA_XMP) > 0) {
+ req_data = (req_data & !EOM_IMAGE_DATA_XMP);
+#ifdef HAVE_EXEMPI
+ has_data = has_data && (priv->xmp != NULL);
+#endif
+ }
+
+ if (req_data != 0) {
+ g_warning ("Asking for unknown data, remaining: %i\n", req_data);
+ has_data = FALSE;
+ }
+
+ return has_data;
+}
+
+gboolean
+eom_image_load (EomImage *img, EomImageData data2read, EomJob *job, GError **error)
+{
+ EomImagePrivate *priv;
+ gboolean success = FALSE;
+
+ eom_debug (DEBUG_IMAGE_LOAD);
+
+ g_return_val_if_fail (EOM_IS_IMAGE (img), FALSE);
+
+ priv = EOM_IMAGE (img)->priv;
+
+ if (data2read == 0) {
+ return TRUE;
+ }
+
+ if (eom_image_has_data (img, data2read)) {
+ return TRUE;
+ }
+
+ priv->status = EOM_IMAGE_STATUS_LOADING;
+
+ success = eom_image_real_load (img, data2read, job, error);
+
+#ifdef HAVE_EXIF
+ /* Check that the metadata was loaded at least once before
+ * trying to autorotate. */
+ if (priv->autorotate &&
+ priv->metadata_status == EOM_IMAGE_METADATA_READY) {
+ eom_image_real_autorotate (img);
+ }
+#endif
+
+ if (success && eom_image_needs_transformation (img)) {
+ success = eom_image_apply_transformations (img, error);
+ }
+
+ if (success) {
+ priv->status = EOM_IMAGE_STATUS_LOADED;
+ } else {
+ priv->status = EOM_IMAGE_STATUS_FAILED;
+ }
+
+ return success;
+}
+
+void
+eom_image_set_thumbnail (EomImage *img, GdkPixbuf *thumbnail)
+{
+ EomImagePrivate *priv;
+
+ g_return_if_fail (EOM_IS_IMAGE (img));
+ g_return_if_fail (GDK_IS_PIXBUF (thumbnail) || thumbnail == NULL);
+
+ priv = img->priv;
+
+ if (priv->thumbnail != NULL) {
+ g_object_unref (priv->thumbnail);
+ priv->thumbnail = NULL;
+ }
+
+ if (thumbnail != NULL && priv->trans != NULL) {
+ priv->thumbnail = eom_transform_apply (priv->trans, thumbnail, NULL);
+ } else {
+ priv->thumbnail = thumbnail;
+
+ if (thumbnail != NULL) {
+ g_object_ref (priv->thumbnail);
+ }
+ }
+
+ if (priv->thumbnail != NULL) {
+ g_signal_emit (img, signals[SIGNAL_THUMBNAIL_CHANGED], 0);
+ }
+}
+
+GdkPixbuf *
+eom_image_get_pixbuf (EomImage *img)
+{
+ GdkPixbuf *image = NULL;
+
+ g_return_val_if_fail (EOM_IS_IMAGE (img), NULL);
+
+ g_mutex_lock (img->priv->status_mutex);
+ image = img->priv->image;
+ g_mutex_unlock (img->priv->status_mutex);
+
+ if (image != NULL) {
+ g_object_ref (image);
+ }
+
+ return image;
+}
+
+#ifdef HAVE_LCMS
+cmsHPROFILE
+eom_image_get_profile (EomImage *img)
+{
+ g_return_val_if_fail (EOM_IS_IMAGE (img), NULL);
+
+ return img->priv->profile;
+}
+#endif
+
+GdkPixbuf *
+eom_image_get_thumbnail (EomImage *img)
+{
+ g_return_val_if_fail (EOM_IS_IMAGE (img), NULL);
+
+ if (img->priv->thumbnail != NULL) {
+ g_object_ref (img->priv->thumbnail);
+
+ return img->priv->thumbnail;
+ }
+
+ return NULL;
+}
+
+void
+eom_image_get_size (EomImage *img, int *width, int *height)
+{
+ EomImagePrivate *priv;
+
+ g_return_if_fail (EOM_IS_IMAGE (img));
+
+ priv = img->priv;
+
+ *width = priv->width;
+ *height = priv->height;
+}
+
+void
+eom_image_transform (EomImage *img, EomTransform *trans, EomJob *job)
+{
+ eom_image_real_transform (img, trans, FALSE, job);
+}
+
+void
+eom_image_undo (EomImage *img)
+{
+ EomImagePrivate *priv;
+ EomTransform *trans;
+ EomTransform *inverse;
+
+ g_return_if_fail (EOM_IS_IMAGE (img));
+
+ priv = img->priv;
+
+ if (priv->undo_stack != NULL) {
+ trans = EOM_TRANSFORM (priv->undo_stack->data);
+
+ inverse = eom_transform_reverse (trans);
+
+ eom_image_real_transform (img, inverse, TRUE, NULL);
+
+ priv->undo_stack = g_slist_delete_link (priv->undo_stack, priv->undo_stack);
+
+ g_object_unref (trans);
+ g_object_unref (inverse);
+
+ if (eom_transform_is_identity (priv->trans)) {
+ g_object_unref (priv->trans);
+ priv->trans = NULL;
+ }
+ }
+
+ priv->modified = (priv->undo_stack != NULL);
+}
+
+static GFile *
+tmp_file_get (void)
+{
+ GFile *tmp_file;
+ char *tmp_file_path;
+ gint fd;
+
+ tmp_file_path = g_build_filename (g_get_tmp_dir (), "eom-save-XXXXXX", NULL);
+ fd = g_mkstemp (tmp_file_path);
+ if (fd == -1) {
+ g_free (tmp_file_path);
+ return NULL;
+ }
+ else {
+ tmp_file = g_file_new_for_path (tmp_file_path);
+ g_free (tmp_file_path);
+ return tmp_file;
+ }
+}
+
+static void
+transfer_progress_cb (goffset cur_bytes,
+ goffset total_bytes,
+ gpointer user_data)
+{
+ EomImage *image = EOM_IMAGE (user_data);
+
+ if (cur_bytes > 0) {
+ g_signal_emit (G_OBJECT(image),
+ signals[SIGNAL_SAVE_PROGRESS],
+ 0,
+ (gfloat) cur_bytes / (gfloat) total_bytes);
+ }
+}
+
+static gboolean
+tmp_file_move_to_uri (EomImage *image,
+ GFile *tmpfile,
+ GFile *file,
+ gboolean overwrite,
+ GError **error)
+{
+ gboolean result;
+ GError *ioerror = NULL;
+
+ result = g_file_move (tmpfile,
+ file,
+ (overwrite ? G_FILE_COPY_OVERWRITE : 0) |
+ G_FILE_COPY_ALL_METADATA,
+ NULL,
+ (GFileProgressCallback) transfer_progress_cb,
+ image,
+ &ioerror);
+
+ if (result == FALSE) {
+ if (g_error_matches (ioerror, G_IO_ERROR,
+ G_IO_ERROR_EXISTS)) {
+ g_set_error (error, EOM_IMAGE_ERROR,
+ EOM_IMAGE_ERROR_FILE_EXISTS,
+ "File exists");
+ } else {
+ g_set_error (error, EOM_IMAGE_ERROR,
+ EOM_IMAGE_ERROR_VFS,
+ "VFS error moving the temp file");
+ }
+ g_clear_error (&ioerror);
+ }
+
+ return result;
+}
+
+static gboolean
+tmp_file_delete (GFile *tmpfile)
+{
+ gboolean result;
+ GError *err = NULL;
+
+ if (tmpfile == NULL) return FALSE;
+
+ result = g_file_delete (tmpfile, NULL, &err);
+ if (result == FALSE) {
+ char *tmpfile_path;
+ if (err != NULL) {
+ if (err->code == G_IO_ERROR_NOT_FOUND) {
+ g_error_free (err);
+ return TRUE;
+ }
+ g_error_free (err);
+ }
+ tmpfile_path = g_file_get_path (tmpfile);
+ g_warning ("Couldn't delete temporary file: %s", tmpfile_path);
+ g_free (tmpfile_path);
+ }
+
+ return result;
+}
+
+static void
+eom_image_reset_modifications (EomImage *image)
+{
+ EomImagePrivate *priv;
+
+ g_return_if_fail (EOM_IS_IMAGE (image));
+
+ priv = image->priv;
+
+ g_slist_foreach (priv->undo_stack, (GFunc) g_object_unref, NULL);
+ g_slist_free (priv->undo_stack);
+ priv->undo_stack = NULL;
+
+ if (priv->trans != NULL) {
+ g_object_unref (priv->trans);
+ priv->trans = NULL;
+ }
+
+ if (priv->trans_autorotate != NULL) {
+ g_object_unref (priv->trans_autorotate);
+ priv->trans_autorotate = NULL;
+ }
+
+ priv->modified = FALSE;
+}
+
+static void
+eom_image_link_with_target (EomImage *image, EomImageSaveInfo *target)
+{
+ EomImagePrivate *priv;
+
+ g_return_if_fail (EOM_IS_IMAGE (image));
+ g_return_if_fail (EOM_IS_IMAGE_SAVE_INFO (target));
+
+ priv = image->priv;
+
+ /* update file location */
+ if (priv->file != NULL) {
+ g_object_unref (priv->file);
+ }
+ priv->file = g_object_ref (target->file);
+
+ /* Clear caption and caption key, these will be
+ * updated on next eom_image_get_caption call.
+ */
+ if (priv->caption != NULL) {
+ g_free (priv->caption);
+ priv->caption = NULL;
+ }
+ if (priv->collate_key != NULL) {
+ g_free (priv->collate_key);
+ priv->collate_key = NULL;
+ }
+
+ /* update file format */
+ if (priv->file_type != NULL) {
+ g_free (priv->file_type);
+ }
+ priv->file_type = g_strdup (target->format);
+}
+
+gboolean
+eom_image_save_by_info (EomImage *img, EomImageSaveInfo *source, GError **error)
+{
+ EomImagePrivate *priv;
+ EomImageStatus prev_status;
+ gboolean success = FALSE;
+ GFile *tmp_file;
+ char *tmp_file_path;
+
+ g_return_val_if_fail (EOM_IS_IMAGE (img), FALSE);
+ g_return_val_if_fail (EOM_IS_IMAGE_SAVE_INFO (source), FALSE);
+
+ priv = img->priv;
+
+ prev_status = priv->status;
+
+ /* Image is now being saved */
+ priv->status = EOM_IMAGE_STATUS_SAVING;
+
+ /* see if we need any saving at all */
+ if (source->exists && !source->modified) {
+ return TRUE;
+ }
+
+ /* fail if there is no image to save */
+ if (priv->image == NULL) {
+ g_set_error (error, EOM_IMAGE_ERROR,
+ EOM_IMAGE_ERROR_NOT_LOADED,
+ _("No image loaded."));
+ return FALSE;
+ }
+
+ /* generate temporary file */
+ tmp_file = tmp_file_get ();
+
+ if (tmp_file == NULL) {
+ g_set_error (error, EOM_IMAGE_ERROR,
+ EOM_IMAGE_ERROR_TMP_FILE_FAILED,
+ _("Temporary file creation failed."));
+ return FALSE;
+ }
+
+ tmp_file_path = g_file_get_path (tmp_file);
+
+#ifdef HAVE_JPEG
+ /* determine kind of saving */
+ if ((g_ascii_strcasecmp (source->format, EOM_FILE_FORMAT_JPEG) == 0) &&
+ source->exists && source->modified)
+ {
+ success = eom_image_jpeg_save_file (img, tmp_file_path, source, NULL, error);
+ }
+#endif
+
+ if (!success && (*error == NULL)) {
+ success = gdk_pixbuf_save (priv->image, tmp_file_path, source->format, error, NULL);
+ }
+
+ if (success) {
+ /* try to move result file to target uri */
+ success = tmp_file_move_to_uri (img, tmp_file, priv->file, TRUE /*overwrite*/, error);
+ }
+
+ if (success) {
+ eom_image_reset_modifications (img);
+ }
+
+ tmp_file_delete (tmp_file);
+
+ g_free (tmp_file_path);
+ g_object_unref (tmp_file);
+
+ priv->status = prev_status;
+
+ return success;
+}
+
+static gboolean
+eom_image_copy_file (EomImage *image, EomImageSaveInfo *source, EomImageSaveInfo *target, GError **error)
+{
+ gboolean result;
+ GError *ioerror = NULL;
+
+ g_return_val_if_fail (EOM_IS_IMAGE_SAVE_INFO (source), FALSE);
+ g_return_val_if_fail (EOM_IS_IMAGE_SAVE_INFO (target), FALSE);
+
+ result = g_file_copy (source->file,
+ target->file,
+ (target->overwrite ? G_FILE_COPY_OVERWRITE : 0) |
+ G_FILE_COPY_ALL_METADATA,
+ NULL,
+ EOM_IS_IMAGE (image) ? transfer_progress_cb :NULL,
+ image,
+ &ioerror);
+
+ if (result == FALSE) {
+ if (ioerror->code == G_IO_ERROR_EXISTS) {
+ g_set_error (error, EOM_IMAGE_ERROR,
+ EOM_IMAGE_ERROR_FILE_EXISTS,
+ "%s", ioerror->message);
+ } else {
+ g_set_error (error, EOM_IMAGE_ERROR,
+ EOM_IMAGE_ERROR_VFS,
+ "%s", ioerror->message);
+ }
+ g_error_free (ioerror);
+ }
+
+ return result;
+}
+
+gboolean
+eom_image_save_as_by_info (EomImage *img, EomImageSaveInfo *source, EomImageSaveInfo *target, GError **error)
+{
+ EomImagePrivate *priv;
+ gboolean success = FALSE;
+ char *tmp_file_path;
+ GFile *tmp_file;
+ gboolean direct_copy = FALSE;
+
+ g_return_val_if_fail (EOM_IS_IMAGE (img), FALSE);
+ g_return_val_if_fail (EOM_IS_IMAGE_SAVE_INFO (source), FALSE);
+ g_return_val_if_fail (EOM_IS_IMAGE_SAVE_INFO (target), FALSE);
+
+ priv = img->priv;
+
+ /* fail if there is no image to save */
+ if (priv->image == NULL) {
+ g_set_error (error,
+ EOM_IMAGE_ERROR,
+ EOM_IMAGE_ERROR_NOT_LOADED,
+ _("No image loaded."));
+
+ return FALSE;
+ }
+
+ /* generate temporary file name */
+ tmp_file = tmp_file_get ();
+
+ if (tmp_file == NULL) {
+ g_set_error (error,
+ EOM_IMAGE_ERROR,
+ EOM_IMAGE_ERROR_TMP_FILE_FAILED,
+ _("Temporary file creation failed."));
+
+ return FALSE;
+ }
+ tmp_file_path = g_file_get_path (tmp_file);
+
+ /* determine kind of saving */
+ if (g_ascii_strcasecmp (source->format, target->format) == 0 && !source->modified) {
+ success = eom_image_copy_file (img, source, target, error);
+ direct_copy = success;
+ }
+
+#ifdef HAVE_JPEG
+ else if ((g_ascii_strcasecmp (source->format, EOM_FILE_FORMAT_JPEG) == 0 && source->exists) ||
+ (g_ascii_strcasecmp (target->format, EOM_FILE_FORMAT_JPEG) == 0))
+ {
+ success = eom_image_jpeg_save_file (img, tmp_file_path, source, target, error);
+ }
+#endif
+
+ if (!success && (*error == NULL)) {
+ success = gdk_pixbuf_save (priv->image, tmp_file_path, target->format, error, NULL);
+ }
+
+ if (success && !direct_copy) { /* not required if we alredy copied the file directly */
+ /* try to move result file to target uri */
+ success = tmp_file_move_to_uri (img, tmp_file, target->file, target->overwrite, error);
+ }
+
+ if (success) {
+ /* update image information to new uri */
+ eom_image_reset_modifications (img);
+ eom_image_link_with_target (img, target);
+ }
+
+ tmp_file_delete (tmp_file);
+ g_object_unref (tmp_file);
+ g_free (tmp_file_path);
+
+ priv->status = EOM_IMAGE_STATUS_UNKNOWN;
+
+ return success;
+}
+
+
+/*
+ * This function is extracted from
+ * File: caja/libcaja-private/caja-file.c
+ * Revision: 1.309
+ * Author: Darin Adler <[email protected]>
+ */
+static gboolean
+have_broken_filenames (void)
+{
+ static gboolean initialized = FALSE;
+ static gboolean broken;
+
+ if (initialized) {
+ return broken;
+ }
+
+ broken = g_getenv ("G_BROKEN_FILENAMES") != NULL;
+
+ initialized = TRUE;
+
+ return broken;
+}
+
+/*
+ * This function is inspired by
+ * caja/libcaja-private/caja-file.c:caja_file_get_display_name_nocopy
+ * Revision: 1.309
+ * Author: Darin Adler <[email protected]>
+ */
+const gchar*
+eom_image_get_caption (EomImage *img)
+{
+ EomImagePrivate *priv;
+ char *name;
+ char *utf8_name;
+ char *scheme;
+ gboolean validated = FALSE;
+ gboolean broken_filenames;
+
+ g_return_val_if_fail (EOM_IS_IMAGE (img), NULL);
+
+ priv = img->priv;
+
+ if (priv->file == NULL) return NULL;
+
+ if (priv->caption != NULL)
+ /* Use cached caption string */
+ return priv->caption;
+
+ name = g_file_get_basename (priv->file);
+ scheme = g_file_get_uri_scheme (priv->file);
+
+ if (name != NULL && g_ascii_strcasecmp (scheme, "file") == 0) {
+ /* Support the G_BROKEN_FILENAMES feature of
+ * glib by using g_filename_to_utf8 to convert
+ * local filenames to UTF-8. Also do the same
+ * thing with any local filename that does not
+ * validate as good UTF-8.
+ */
+ broken_filenames = have_broken_filenames ();
+
+ if (broken_filenames || !g_utf8_validate (name, -1, NULL)) {
+ utf8_name = g_locale_to_utf8 (name, -1, NULL, NULL, NULL);
+ if (utf8_name != NULL) {
+ g_free (name);
+ name = utf8_name;
+ /* Guaranteed to be correct utf8 here */
+ validated = TRUE;
+ }
+ } else if (!broken_filenames) {
+ /* name was valid, no need to re-validate */
+ validated = TRUE;
+ }
+ }
+
+ if (!validated && !g_utf8_validate (name, -1, NULL)) {
+ if (name == NULL) {
+ name = g_strdup ("[Invalid Unicode]");
+ } else {
+ utf8_name = eom_util_make_valid_utf8 (name);
+ g_free (name);
+ name = utf8_name;
+ }
+ }
+
+ priv->caption = name;
+
+ if (priv->caption == NULL) {
+ char *short_str;
+
+ short_str = g_file_get_basename (priv->file);
+ if (g_utf8_validate (short_str, -1, NULL)) {
+ priv->caption = g_strdup (short_str);
+ } else {
+ priv->caption = g_filename_to_utf8 (short_str, -1, NULL, NULL, NULL);
+ }
+ g_free (short_str);
+ }
+ g_free (scheme);
+
+ return priv->caption;
+}
+
+const gchar*
+eom_image_get_collate_key (EomImage *img)
+{
+ EomImagePrivate *priv;
+
+ g_return_val_if_fail (EOM_IS_IMAGE (img), NULL);
+
+ priv = img->priv;
+
+ if (priv->collate_key == NULL) {
+ const char *caption;
+
+ caption = eom_image_get_caption (img);
+
+ priv->collate_key = g_utf8_collate_key_for_filename (caption, -1);
+ }
+
+ return priv->collate_key;
+}
+
+void
+eom_image_cancel_load (EomImage *img)
+{
+ EomImagePrivate *priv;
+
+ g_return_if_fail (EOM_IS_IMAGE (img));
+
+ priv = img->priv;
+
+ g_mutex_lock (priv->status_mutex);
+
+ if (priv->status == EOM_IMAGE_STATUS_LOADING) {
+ priv->cancel_loading = TRUE;
+ }
+
+ g_mutex_unlock (priv->status_mutex);
+}
+
+gpointer
+eom_image_get_exif_info (EomImage *img)
+{
+ EomImagePrivate *priv;
+ gpointer data = NULL;
+
+ g_return_val_if_fail (EOM_IS_IMAGE (img), NULL);
+
+ priv = img->priv;
+
+#ifdef HAVE_EXIF
+ g_mutex_lock (priv->status_mutex);
+
+ exif_data_ref (priv->exif);
+ data = priv->exif;
+
+ g_mutex_unlock (priv->status_mutex);
+#endif
+
+ return data;
+}
+
+
+gpointer
+eom_image_get_xmp_info (EomImage *img)
+{
+ EomImagePrivate *priv;
+ gpointer data = NULL;
+
+ g_return_val_if_fail (EOM_IS_IMAGE (img), NULL);
+
+ priv = img->priv;
+
+#ifdef HAVE_EXEMPI
+ g_mutex_lock (priv->status_mutex);
+ data = (gpointer) xmp_copy (priv->xmp);
+ g_mutex_unlock (priv->status_mutex);
+#endif
+
+ return data;
+}
+
+
+GFile *
+eom_image_get_file (EomImage *img)
+{
+ g_return_val_if_fail (EOM_IS_IMAGE (img), NULL);
+
+ return g_object_ref (img->priv->file);
+}
+
+gboolean
+eom_image_is_modified (EomImage *img)
+{
+ g_return_val_if_fail (EOM_IS_IMAGE (img), FALSE);
+
+ return img->priv->modified;
+}
+
+goffset
+eom_image_get_bytes (EomImage *img)
+{
+ g_return_val_if_fail (EOM_IS_IMAGE (img), 0);
+
+ return img->priv->bytes;
+}
+
+void
+eom_image_modified (EomImage *img)
+{
+ g_return_if_fail (EOM_IS_IMAGE (img));
+
+ g_signal_emit (G_OBJECT (img), signals[SIGNAL_CHANGED], 0);
+}
+
+gchar*
+eom_image_get_uri_for_display (EomImage *img)
+{
+ EomImagePrivate *priv;
+ gchar *uri_str = NULL;
+ gchar *str = NULL;
+
+ g_return_val_if_fail (EOM_IS_IMAGE (img), NULL);
+
+ priv = img->priv;
+
+ if (priv->file != NULL) {
+ uri_str = g_file_get_uri (priv->file);
+
+ if (uri_str != NULL) {
+ str = g_uri_unescape_string (uri_str, NULL);
+ g_free (uri_str);
+ }
+ }
+
+ return str;
+}
+
+EomImageStatus
+eom_image_get_status (EomImage *img)
+{
+ g_return_val_if_fail (EOM_IS_IMAGE (img), EOM_IMAGE_STATUS_UNKNOWN);
+
+ return img->priv->status;
+}
+
+/**
+ * eom_image_get_metadata_status:
+ * @img: a #EomImage
+ *
+ * Returns the current status of the image metadata, that is,
+ * whether the metadata has not been read yet, is ready, or not available at all.
+ *
+ * Returns: one of #EomImageMetadataStatus
+ **/
+EomImageMetadataStatus
+eom_image_get_metadata_status (EomImage *img)
+{
+ g_return_val_if_fail (EOM_IS_IMAGE (img), EOM_IMAGE_METADATA_NOT_AVAILABLE);
+
+ return img->priv->metadata_status;
+}
+
+void
+eom_image_data_ref (EomImage *img)
+{
+ g_return_if_fail (EOM_IS_IMAGE (img));
+
+ g_object_ref (G_OBJECT (img));
+ img->priv->data_ref_count++;
+
+ g_assert (img->priv->data_ref_count <= G_OBJECT (img)->ref_count);
+}
+
+void
+eom_image_data_unref (EomImage *img)
+{
+ g_return_if_fail (EOM_IS_IMAGE (img));
+
+ if (img->priv->data_ref_count > 0) {
+ img->priv->data_ref_count--;
+ } else {
+ g_warning ("More image data unrefs than refs.");
+ }
+
+ if (img->priv->data_ref_count == 0) {
+ eom_image_free_mem_private (img);
+ }
+
+ g_object_unref (G_OBJECT (img));
+
+ g_assert (img->priv->data_ref_count <= G_OBJECT (img)->ref_count);
+}
+
+static gint
+compare_quarks (gconstpointer a, gconstpointer b)
+{
+ GQuark quark;
+
+ quark = g_quark_from_string ((const gchar *) a);
+
+ return quark - GPOINTER_TO_INT (b);
+}
+
+GList *
+eom_image_get_supported_mime_types (void)
+{
+ GSList *format_list, *it;
+ gchar **mime_types;
+ int i;
+
+ if (!supported_mime_types) {
+ format_list = gdk_pixbuf_get_formats ();
+
+ for (it = format_list; it != NULL; it = it->next) {
+ mime_types =
+ gdk_pixbuf_format_get_mime_types ((GdkPixbufFormat *) it->data);
+
+ for (i = 0; mime_types[i] != NULL; i++) {
+ supported_mime_types =
+ g_list_prepend (supported_mime_types,
+ g_strdup (mime_types[i]));
+ }
+
+ g_strfreev (mime_types);
+ }
+
+ supported_mime_types = g_list_sort (supported_mime_types,
+ (GCompareFunc) compare_quarks);
+
+ g_slist_free (format_list);
+ }
+
+ return supported_mime_types;
+}
+
+gboolean
+eom_image_is_supported_mime_type (const char *mime_type)
+{
+ GList *supported_mime_types, *result;
+ GQuark quark;
+
+ if (mime_type == NULL) {
+ return FALSE;
+ }
+
+ supported_mime_types = eom_image_get_supported_mime_types ();
+
+ quark = g_quark_from_string (mime_type);
+
+ result = g_list_find_custom (supported_mime_types,
+ GINT_TO_POINTER (quark),
+ (GCompareFunc) compare_quarks);
+
+ return (result != NULL);
+}
+
+static gboolean
+eom_image_iter_advance (EomImage *img)
+{
+ EomImagePrivate *priv;
+ gboolean new_frame;
+
+ g_return_val_if_fail (EOM_IS_IMAGE (img), FALSE);
+ g_return_val_if_fail (GDK_IS_PIXBUF_ANIMATION_ITER (img->priv->anim_iter), FALSE);
+
+ priv = img->priv;
+
+ if ((new_frame = gdk_pixbuf_animation_iter_advance (img->priv->anim_iter, NULL)) == TRUE)
+ {
+ g_mutex_lock (priv->status_mutex);
+ g_object_unref (priv->image);
+ priv->image = gdk_pixbuf_animation_iter_get_pixbuf (priv->anim_iter);
+ g_object_ref (priv->image);
+ /* keep the transformation over time */
+ if (EOM_IS_TRANSFORM (priv->trans)) {
+ GdkPixbuf* transformed = eom_transform_apply (priv->trans, priv->image, NULL);
+ g_object_unref (priv->image);
+ priv->image = transformed;
+ priv->width = gdk_pixbuf_get_width (transformed);
+ priv->height = gdk_pixbuf_get_height (transformed);
+ }
+ g_mutex_unlock (priv->status_mutex);
+ /* Emit next frame signal so we can update the display */
+ g_signal_emit (img, signals[SIGNAL_NEXT_FRAME], 0,
+ gdk_pixbuf_animation_iter_get_delay_time (priv->anim_iter));
+ }
+
+ return new_frame;
+}
+
+/**
+ * eom_image_is_animation:
+ * @img: a #EomImage
+ *
+ * Checks whether a given image is animated.
+ *
+ * Returns: #TRUE if it is an animated image, #FALSE otherwise.
+ *
+ **/
+gboolean
+eom_image_is_animation (EomImage *img)
+{
+ g_return_val_if_fail (EOM_IS_IMAGE (img), FALSE);
+ return img->priv->anim != NULL;
+}
+
+static gboolean
+private_timeout (gpointer data)
+{
+ EomImage *img = EOM_IMAGE (data);
+ EomImagePrivate *priv = img->priv;
+
+ if (eom_image_is_animation (img) &&
+ !g_source_is_destroyed (g_main_current_source ()) &&
+ priv->is_playing) {
+ while (eom_image_iter_advance (img) != TRUE) {}; /* cpu-sucking ? */
+ g_timeout_add (gdk_pixbuf_animation_iter_get_delay_time (priv->anim_iter), private_timeout, img);
+ return FALSE;
+ }
+ priv->is_playing = FALSE;
+ return FALSE; /* stop playing */
+}
+
+/**
+ * eom_image_start_animation:
+ * @img: a #EomImage
+ *
+ * Starts playing an animated image.
+ *
+ * Returns: %TRUE on success, %FALSE if @img is already playing or isn't an animated image.
+ **/
+gboolean
+eom_image_start_animation (EomImage *img)
+{
+ EomImagePrivate *priv;
+
+ g_return_val_if_fail (EOM_IS_IMAGE (img), FALSE);
+ priv = img->priv;
+
+ if (!eom_image_is_animation (img) || priv->is_playing)
+ return FALSE;
+
+ g_mutex_lock (priv->status_mutex);
+ g_object_ref (priv->anim_iter);
+ priv->is_playing = TRUE;
+ g_mutex_unlock (priv->status_mutex);
+
+ g_timeout_add (gdk_pixbuf_animation_iter_get_delay_time (priv->anim_iter), private_timeout, img);
+
+ return TRUE;
+}
+
+#ifdef HAVE_RSVG
+gboolean
+eom_image_is_svg (EomImage *img)
+{
+ g_return_val_if_fail (EOM_IS_IMAGE (img), FALSE);
+
+ return (img->priv->svg != NULL);
+}
+
+RsvgHandle *
+eom_image_get_svg (EomImage *img)
+{
+ g_return_val_if_fail (EOM_IS_IMAGE (img), NULL);
+
+ return img->priv->svg;
+}
+
+EomTransform *
+eom_image_get_transform (EomImage *img)
+{
+ g_return_val_if_fail (EOM_IS_IMAGE (img), NULL);
+
+ return img->priv->trans;
+}
+
+#endif
+
+/**
+ * eom_image_file_changed:
+ * @img: a #EomImage
+ *
+ * Emits EomImage::file-changed signal
+ **/
+void
+eom_image_file_changed (EomImage *img)
+{
+ g_return_if_fail (EOM_IS_IMAGE (img));
+
+ g_signal_emit (img, signals[SIGNAL_FILE_CHANGED], 0);
+}