/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Pix
*
* 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, see .
*/
#include
#include
#include
#include
#include
#ifdef HAVE_LIBJPEG
#include
#endif
#include "rotation-utils.h"
enum {
GTH_RESPONSE_TRIM
};
typedef struct {
GtkWidget *dialog;
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->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);
}
GtkWidget *
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;
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_ICON_NAME_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_LABEL_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);
return data->dialog;
}
/* -- 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);
}
#ifdef HAVE_LIBJPEG
static void
write_file_ready_cb (void **buffer,
gsize count,
GError *error,
gpointer user_data)
{
TransformatioData *tdata = user_data;
tdata->ready_func (error, tdata->user_data);
transformation_data_free (tdata);
}
#endif /* HAVE_LIBJPEG */
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;
GthMetadata *metadata;
GthTransform orientation;
if (error != NULL) {
tdata->ready_func (error, tdata->user_data);
transformation_data_free (tdata);
return;
}
orientation = GTH_TRANSFORM_NONE;
metadata = (GthMetadata *) g_file_info_get_attribute_object (tdata->file_data->info, "Embedded::Image::Orientation");
if ((metadata != NULL) && (gth_metadata_get_raw (metadata) != NULL))
orientation = strtol (gth_metadata_get_raw (metadata), (char **) NULL, 10);
orientation = get_next_transformation (orientation, tdata->transform);
#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,
orientation,
tdata->mcu_action,
&error))
{
tdata->ready_func (error, tdata->user_data);
transformation_data_free (tdata);
return;
}
_g_file_write_async (tdata->file_data->file,
out_buffer,
out_buffer_size,
TRUE,
G_PRIORITY_DEFAULT,
tdata->cancellable,
write_file_ready_cb,
tdata);
}
else
#endif /* HAVE_LIBJPEG */
{
GInputStream *istream;
GthImage *image;
cairo_surface_t *surface;
cairo_surface_t *transformed;
istream = g_memory_input_stream_new_from_data (*buffer, count, NULL);
image = gth_image_new_from_stream (istream, -1, NULL, NULL, tdata->cancellable, &error);
if (image == NULL) {
tdata->ready_func (error, tdata->user_data);
transformation_data_free (tdata);
return;
}
surface = gth_image_get_cairo_surface (image);
transformed = _cairo_image_surface_transform (surface, orientation);
gth_image_set_cairo_surface (image, transformed);
gth_image_save_to_file (image,
gth_file_data_get_mime_type (tdata->file_data),
tdata->file_data,
TRUE,
tdata->cancellable,
pixbuf_saved_cb,
tdata);
cairo_surface_destroy (transformed);
cairo_surface_destroy (surface);
g_object_unref (image);
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_file_load_async (tdata->file_data->file,
G_PRIORITY_DEFAULT,
tdata->cancellable,
file_buffer_ready_cb,
tdata);
}