/* 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 * Federico Mena-Quintero * Michael Fulbright * * 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., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include #endif #include "eom-image-jpeg.h" #include "eom-image-private.h" #if HAVE_JPEG #include #include #include #include #include #include #include #include #include "transupp.h" #include #include #include #if HAVE_EXIF #include #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) { gchar *str = g_path_get_basename (errmgr->filename); g_set_error (errmgr->error, 0, 0, "Error interpreting JPEG image file: %s\n\n%s", str, buffer); g_free (str); } 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)); memset (info, 0x0, sizeof (jpeg_transform_info)); 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; case EOM_TRANSFORM_TRANSPOSE: trans_code = JXFORM_TRANSPOSE; break; case EOM_TRANSFORM_TRANSVERSE: trans_code = JXFORM_TRANSVERSE; break; default: g_warning("EomTransformType not supported!"); /* Fallthrough intended here. */ case EOM_TRANSFORM_NONE: 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; 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); 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; } /* 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