diff options
Diffstat (limited to 'src/eom-image-jpeg.c')
-rw-r--r-- | src/eom-image-jpeg.c | 518 |
1 files changed, 518 insertions, 0 deletions
diff --git a/src/eom-image-jpeg.c b/src/eom-image-jpeg.c new file mode 100644 index 0000000..ef7a2cc --- /dev/null +++ b/src/eom-image-jpeg.c @@ -0,0 +1,518 @@ +/* This code is based on the jpeg saving code from gdk-pixbuf. Full copyright + * notice is given in the following: + */ +/* GdkPixbuf library - JPEG image loader + * + * Copyright (C) 1999 Michael Zucchi + * Copyright (C) 1999 The Free Software Foundation + * + * Progressive loading code Copyright (C) 1999 Red Hat, Inc. + * + * Authors: Michael Zucchi <[email protected]> + * Federico Mena-Quintero <[email protected]> + * Michael Fulbright <[email protected]> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "eom-image-jpeg.h" +#include "eom-image-private.h" + +#if HAVE_JPEG + +#include <sys/types.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <setjmp.h> +#include <jpeglib.h> +#include <jerror.h> +#include "transupp.h" +#include <glib.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <glib/gi18n.h> +#if HAVE_EXIF +#include <libexif/exif-data.h> +#endif + +#ifdef G_OS_WIN32 +#define sigjmp_buf jmp_buf +#define sigsetjmp(env, savesigs) setjmp (env) +#define siglongjmp longjmp +#endif + +typedef enum { + EOM_SAVE_NONE, + EOM_SAVE_JPEG_AS_JPEG, + EOM_SAVE_ANY_AS_JPEG +} EomJpegSaveMethod; + +/* error handler data */ +struct error_handler_data { + struct jpeg_error_mgr pub; + sigjmp_buf setjmp_buffer; + GError **error; + char *filename; +}; + + +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); + + /* broken check for *error == NULL for robustness against + * crappy JPEG library + */ + if (errmgr->error && *errmgr->error == NULL) { + g_set_error (errmgr->error, + 0, + 0, + "Error interpreting JPEG image file: %s\n\n%s", + g_path_get_basename (errmgr->filename), + buffer); + } + + siglongjmp (errmgr->setjmp_buffer, 1); + + g_assert_not_reached (); +} + + +static void +output_message_handler (j_common_ptr cinfo) +{ + /* This method keeps libjpeg from dumping crap to stderr */ + /* do nothing */ +} + +static void +init_transform_info (EomImage *image, jpeg_transform_info *info) +{ + EomImagePrivate *priv; + EomTransform *composition = NULL; + EomTransformType transformation; + JXFORM_CODE trans_code = JXFORM_NONE; + + g_return_if_fail (EOM_IS_IMAGE (image)); + + priv = image->priv; + + 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) { + transformation = eom_transform_get_transform_type (composition); + + switch (transformation) { + case EOM_TRANSFORM_ROT_90: + trans_code = JXFORM_ROT_90; + break; + case EOM_TRANSFORM_ROT_270: + trans_code = JXFORM_ROT_270; + break; + case EOM_TRANSFORM_ROT_180: + trans_code = JXFORM_ROT_180; + break; + case EOM_TRANSFORM_FLIP_HORIZONTAL: + trans_code = JXFORM_FLIP_H; + break; + case EOM_TRANSFORM_FLIP_VERTICAL: + trans_code = JXFORM_FLIP_V; + break; + default: + trans_code = JXFORM_NONE; + break; + } + } + + info->transform = trans_code; + info->trim = FALSE; +#if JPEG_LIB_VERSION >= 80 + info->crop = FALSE; +#endif + info->force_grayscale = FALSE; + + g_object_unref (composition); +} + +static gboolean +_save_jpeg_as_jpeg (EomImage *image, const char *file, EomImageSaveInfo *source, + EomImageSaveInfo *target, GError **error) +{ + struct jpeg_decompress_struct srcinfo; + struct jpeg_compress_struct dstinfo; + struct error_handler_data jsrcerr, jdsterr; + jpeg_transform_info transformoption; + jvirt_barray_ptr *src_coef_arrays; + jvirt_barray_ptr *dst_coef_arrays; + FILE *output_file; + FILE *input_file; + EomImagePrivate *priv; + gchar *infile_uri; + + g_return_val_if_fail (EOM_IS_IMAGE (image), FALSE); + g_return_val_if_fail (EOM_IMAGE (image)->priv->file != NULL, FALSE); + + priv = image->priv; + + init_transform_info (image, &transformoption); + + /* Initialize the JPEG decompression object with default error + * handling. */ + jsrcerr.filename = g_file_get_path (priv->file); + 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. */ + jdsterr.filename = (char *) file; + 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; + + /* Open the output file. */ + /* FIXME: Make this a GIO aware input manager */ + infile_uri = g_file_get_path (priv->file); + input_file = fopen (infile_uri, "rb"); + if (input_file == NULL) { + g_warning ("Input file not openable: %s\n", infile_uri); + g_free (jsrcerr.filename); + g_free (infile_uri); + return FALSE; + } + g_free (infile_uri); + + output_file = fopen (file, "wb"); + if (output_file == NULL) { + g_warning ("Output file not openable: %s\n", file); + fclose (input_file); + g_free (jsrcerr.filename); + return FALSE; + } + + if (sigsetjmp (jsrcerr.setjmp_buffer, 1)) { + fclose (output_file); + fclose (input_file); + jpeg_destroy_compress (&dstinfo); + jpeg_destroy_decompress (&srcinfo); + g_free (jsrcerr.filename); + return FALSE; + } + + if (sigsetjmp (jdsterr.setjmp_buffer, 1)) { + fclose (output_file); + fclose (input_file); + jpeg_destroy_compress (&dstinfo); + jpeg_destroy_decompress (&srcinfo); + g_free (jsrcerr.filename); + return FALSE; + } + + /* Specify data source for decompression */ + jpeg_stdio_src (&srcinfo, input_file); + + /* Enable saving of extra markers that we want to copy */ + jcopy_markers_setup (&srcinfo, JCOPYOPT_DEFAULT); + + /* Read file header */ + (void) jpeg_read_header (&srcinfo, TRUE); + + /* 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); + + /* 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); + + /* Specify data destination for compression */ + jpeg_stdio_dest (&dstinfo, output_file); + + /* Start compressor (note no image data is actually written here) */ + jpeg_write_coefficients (&dstinfo, dst_coef_arrays); + + /* handle EXIF/IPTC data explicitly */ +#if HAVE_EXIF + /* exif_chunk and exif are mutally exclusvie, this is what we assure here */ + g_assert (priv->exif_chunk == NULL); + if (priv->exif != NULL) + { + unsigned char *exif_buf; + unsigned int exif_buf_len; + + exif_data_save_data (priv->exif, &exif_buf, &exif_buf_len); + jpeg_write_marker (&dstinfo, JPEG_APP0+1, exif_buf, exif_buf_len); + g_free (exif_buf); + } +#else + if (priv->exif_chunk != NULL) { + jpeg_write_marker (&dstinfo, JPEG_APP0+1, priv->exif_chunk, priv->exif_chunk_len); + } +#endif + /* FIXME: Consider IPTC data too */ + + /* Copy to the output file any extra markers that we want to + * preserve */ + jcopy_markers_execute (&srcinfo, &dstinfo, JCOPYOPT_DEFAULT); + + /* Execute image transformation, if any */ + jtransform_execute_transformation (&srcinfo, + &dstinfo, + src_coef_arrays, + &transformoption); + + /* Finish compression and release memory */ + jpeg_finish_compress (&dstinfo); + jpeg_destroy_compress (&dstinfo); + (void) jpeg_finish_decompress (&srcinfo); + jpeg_destroy_decompress (&srcinfo); + g_free (jsrcerr.filename); + + /* Close files */ + fclose (input_file); + fclose (output_file); + + return TRUE; +} + +static gboolean +_save_any_as_jpeg (EomImage *image, const char *file, EomImageSaveInfo *source, + EomImageSaveInfo *target, GError **error) +{ + EomImagePrivate *priv; + GdkPixbuf *pixbuf; + struct jpeg_compress_struct cinfo; + guchar *buf = NULL; + guchar *ptr; + guchar *pixels = NULL; + JSAMPROW *jbuf; + int y = 0; + volatile int quality = 75; /* default; must be between 0 and 100 */ + int i, j; + int w, h = 0; + int rowstride = 0; + FILE *outfile; + struct error_handler_data jerr; + + g_return_val_if_fail (EOM_IS_IMAGE (image), FALSE); + g_return_val_if_fail (EOM_IMAGE (image)->priv->image != NULL, FALSE); + + priv = image->priv; + pixbuf = priv->image; + + outfile = fopen (file, "wb"); + if (outfile == NULL) { + g_set_error (error, /* FIXME: Better error message */ + GDK_PIXBUF_ERROR, + GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, + _("Couldn't create temporary file for saving: %s"), + file); + return FALSE; + } + + rowstride = gdk_pixbuf_get_rowstride (pixbuf); + w = gdk_pixbuf_get_width (pixbuf); + h = gdk_pixbuf_get_height (pixbuf); + + /* no image data? abort */ + pixels = gdk_pixbuf_get_pixels (pixbuf); + g_return_val_if_fail (pixels != NULL, FALSE); + + /* allocate a small buffer to convert image data */ + buf = g_try_malloc (w * 3 * sizeof (guchar)); + if (!buf) { + g_set_error (error, + GDK_PIXBUF_ERROR, + GDK_PIXBUF_ERROR_INSUFFICIENT_MEMORY, + _("Couldn't allocate memory for loading JPEG file")); + fclose (outfile); + return FALSE; + } + + /* set up error handling */ + jerr.filename = (char *) file; + cinfo.err = jpeg_std_error (&(jerr.pub)); + jerr.pub.error_exit = fatal_error_handler; + jerr.pub.output_message = output_message_handler; + jerr.error = error; + + /* setup compress params */ + jpeg_create_compress (&cinfo); + jpeg_stdio_dest (&cinfo, outfile); + cinfo.image_width = w; + cinfo.image_height = h; + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + + /* error exit routine */ + if (sigsetjmp (jerr.setjmp_buffer, 1)) { + g_free (buf); + fclose (outfile); + jpeg_destroy_compress (&cinfo); + return FALSE; + } + + /* set desired jpeg quality if available */ + if (target != NULL && target->jpeg_quality >= 0.0) { + quality = (int) MIN (target->jpeg_quality, 1.0) * 100; + } + + /* set up jepg compression parameters */ + jpeg_set_defaults (&cinfo); + jpeg_set_quality (&cinfo, quality, TRUE); + jpeg_start_compress (&cinfo, TRUE); + + /* write EXIF/IPTC data explicitly */ +#if HAVE_EXIF + /* exif_chunk and exif are mutally exclusvie, this is what we assure here */ + g_assert (priv->exif_chunk == NULL); + if (priv->exif != NULL) + { + unsigned char *exif_buf; + unsigned int exif_buf_len; + + exif_data_save_data (priv->exif, &exif_buf, &exif_buf_len); + jpeg_write_marker (&cinfo, 0xe1, exif_buf, exif_buf_len); + g_free (exif_buf); + } +#else + if (priv->exif_chunk != NULL) { + jpeg_write_marker (&cinfo, JPEG_APP0+1, priv->exif_chunk, priv->exif_chunk_len); + } +#endif + /* FIXME: Consider IPTC data too */ + + /* get the start pointer */ + ptr = pixels; + /* go one scanline at a time... and save */ + i = 0; + while (cinfo.next_scanline < cinfo.image_height) { + /* convert scanline from ARGB to RGB packed */ + for (j = 0; j < w; j++) + memcpy (&(buf[j*3]), &(ptr[i*rowstride + j*(rowstride/w)]), 3); + + /* write scanline */ + jbuf = (JSAMPROW *)(&buf); + jpeg_write_scanlines (&cinfo, jbuf, 1); + i++; + y++; + + } + + /* finish off */ + jpeg_finish_compress (&cinfo); + jpeg_destroy_compress(&cinfo); + g_free (buf); + + fclose (outfile); + + return TRUE; +} + +gboolean +eom_image_jpeg_save_file (EomImage *image, const char *file, + EomImageSaveInfo *source, EomImageSaveInfo *target, + GError **error) +{ + EomJpegSaveMethod method = EOM_SAVE_NONE; + gboolean source_is_jpeg = FALSE; + gboolean target_is_jpeg = FALSE; + gboolean result; + + g_return_val_if_fail (source != NULL, FALSE); + + source_is_jpeg = !g_ascii_strcasecmp (source->format, EOM_FILE_FORMAT_JPEG); + + /* determine which method should be used for saving */ + if (target == NULL) { + if (source_is_jpeg) { + method = EOM_SAVE_JPEG_AS_JPEG; + } + } + else { + target_is_jpeg = !g_ascii_strcasecmp (target->format, EOM_FILE_FORMAT_JPEG); + + if (source_is_jpeg && target_is_jpeg) { + if (target->jpeg_quality < 0.0) { + method = EOM_SAVE_JPEG_AS_JPEG; + } + else { + /* reencoding is required, cause quality is set */ + method = EOM_SAVE_ANY_AS_JPEG; + } + } + else if (!source_is_jpeg && target_is_jpeg) { + method = EOM_SAVE_ANY_AS_JPEG; + } + } + + switch (method) { + case EOM_SAVE_JPEG_AS_JPEG: + result = _save_jpeg_as_jpeg (image, file, source, target, error); + break; + case EOM_SAVE_ANY_AS_JPEG: + result = _save_any_as_jpeg (image, file, source, target, error); + break; + default: + result = FALSE; + } + + return result; +} +#endif |