/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Pix * * 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, see . */ #include #ifdef HAVE_LIBJPEG #include #include #include #include #include #include #include #endif /* HAVE_LIBJPEG */ #include #include #include "gth-image-saver-jpeg.h" #include "preferences.h" struct _GthImageSaverJpegPrivate { GtkBuilder *builder; GSettings *settings; char *default_ext; }; G_DEFINE_TYPE_WITH_CODE (GthImageSaverJpeg, gth_image_saver_jpeg, GTH_TYPE_IMAGE_SAVER, G_ADD_PRIVATE (GthImageSaverJpeg)) static void gth_image_saver_jpeg_finalize (GObject *object) { GthImageSaverJpeg *self = GTH_IMAGE_SAVER_JPEG (object); _g_object_unref (self->priv->settings); _g_object_unref (self->priv->builder); g_free (self->priv->default_ext); G_OBJECT_CLASS (gth_image_saver_jpeg_parent_class)->finalize (object); } static const char * gth_image_saver_jpeg_get_default_ext (GthImageSaver *base) { GthImageSaverJpeg *self = GTH_IMAGE_SAVER_JPEG (base); if (self->priv->default_ext == NULL) self->priv->default_ext = g_settings_get_string (self->priv->settings, PREF_JPEG_DEFAULT_EXT); return self->priv->default_ext; } static GtkWidget * gth_image_saver_jpeg_get_control (GthImageSaver *base) { GthImageSaverJpeg *self = GTH_IMAGE_SAVER_JPEG (base); char **extensions; int i; int active_idx; _g_object_unref (self->priv->builder); self->priv->builder = _gtk_builder_new_from_file ("jpeg-options.ui", "cairo_io"); active_idx = 0; extensions = g_strsplit (gth_image_saver_get_extensions (base), " ", -1); for (i = 0; extensions[i] != NULL; i++) { GtkTreeIter iter; gtk_list_store_append (GTK_LIST_STORE (gtk_builder_get_object (self->priv->builder, "jpeg_default_ext_liststore")), &iter); gtk_list_store_set (GTK_LIST_STORE (gtk_builder_get_object (self->priv->builder, "jpeg_default_ext_liststore")), &iter, 0, extensions[i], -1); if (g_str_equal (extensions[i], gth_image_saver_get_default_ext (base))) active_idx = i; } gtk_combo_box_set_active (GTK_COMBO_BOX (_gtk_builder_get_widget (self->priv->builder, "jpeg_default_extension_combobox")), active_idx); g_strfreev (extensions); gtk_adjustment_set_value (GTK_ADJUSTMENT (_gtk_builder_get_widget (self->priv->builder, "jpeg_quality_adjustment")), g_settings_get_int (self->priv->settings, PREF_JPEG_QUALITY)); gtk_adjustment_set_value (GTK_ADJUSTMENT (_gtk_builder_get_widget (self->priv->builder, "jpeg_smooth_adjustment")), g_settings_get_int (self->priv->settings, PREF_JPEG_SMOOTHING)); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (_gtk_builder_get_widget (self->priv->builder, "jpeg_optimize_checkbutton")), g_settings_get_boolean (self->priv->settings, PREF_JPEG_OPTIMIZE)); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (_gtk_builder_get_widget (self->priv->builder, "jpeg_progressive_checkbutton")), g_settings_get_boolean (self->priv->settings, PREF_JPEG_PROGRESSIVE)); return _gtk_builder_get_widget (self->priv->builder, "jpeg_options"); } static void gth_image_saver_jpeg_save_options (GthImageSaver *base) { GthImageSaverJpeg *self = GTH_IMAGE_SAVER_JPEG (base); GtkTreeIter iter; if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (_gtk_builder_get_widget (self->priv->builder, "jpeg_default_extension_combobox")), &iter)) { g_free (self->priv->default_ext); gtk_tree_model_get (GTK_TREE_MODEL (gtk_builder_get_object (self->priv->builder, "jpeg_default_ext_liststore")), &iter, 0, &self->priv->default_ext, -1); g_settings_set_string (self->priv->settings, PREF_JPEG_DEFAULT_EXT, self->priv->default_ext); } g_settings_set_int (self->priv->settings, PREF_JPEG_QUALITY, (int) gtk_adjustment_get_value (GTK_ADJUSTMENT (_gtk_builder_get_widget (self->priv->builder, "jpeg_quality_adjustment")))); g_settings_set_int (self->priv->settings, PREF_JPEG_SMOOTHING, (int) gtk_adjustment_get_value (GTK_ADJUSTMENT (_gtk_builder_get_widget (self->priv->builder, "jpeg_smooth_adjustment")))); g_settings_set_boolean (self->priv->settings, PREF_JPEG_OPTIMIZE, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (_gtk_builder_get_widget (self->priv->builder, "jpeg_optimize_checkbutton")))); g_settings_set_boolean (self->priv->settings, PREF_JPEG_PROGRESSIVE, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (_gtk_builder_get_widget (self->priv->builder, "jpeg_progressive_checkbutton")))); } static gboolean gth_image_saver_jpeg_can_save (GthImageSaver *self, const char *mime_type) { #ifdef HAVE_LIBJPEG return g_content_type_equals (mime_type, "image/jpeg"); #else /* ! HAVE_LIBJPEG */ GSList *formats; GSList *scan; GdkPixbufFormat *jpeg_format; if (! g_content_type_equals (mime_type, "image/jpeg")) return FALSE; formats = gdk_pixbuf_get_formats (); jpeg_format = NULL; for (scan = formats; (jpeg_format == NULL) && (scan != NULL); scan = g_slist_next (scan)) { GdkPixbufFormat *format = scan->data; char **mime_types; int i; mime_types = gdk_pixbuf_format_get_mime_types (format); for (i = 0; mime_types[i] != NULL; i++) if (g_content_type_equals (mime_types[i], "image/jpeg")) break; if (mime_types[i] == NULL) continue; if (! gdk_pixbuf_format_is_writable (format)) continue; jpeg_format = format; } return jpeg_format != NULL; #endif /* HAVE_LIBJPEG */ } #ifdef HAVE_LIBJPEG /* 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); /* broken check for *error == NULL for robustness against * crappy JPEG library */ if (errmgr->error && *errmgr->error == NULL) { g_set_error (errmgr->error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_CORRUPT_IMAGE, "Error interpreting JPEG image file (%s)", 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 gboolean _cairo_surface_write_as_jpeg (cairo_surface_t *image, char **buffer, gsize *buffer_size, char **keys, char **values, GError **error) { struct jpeg_compress_struct cinfo; struct error_handler_data jerr; guchar *buf = NULL; guchar *pixels; volatile int quality = 85; /* default; must be between 0 and 100 */ volatile int smoothing = 0; volatile gboolean optimize = FALSE; #ifdef HAVE_PROGRESSIVE_JPEG volatile gboolean progressive = FALSE; #endif int w, h = 0; int rowstride = 0; if (keys && *keys) { char **kiter = keys; char **viter = values; while (*kiter) { if (strcmp (*kiter, "quality") == 0) { char *endptr = NULL; quality = strtol (*viter, &endptr, 10); if (endptr == *viter) { g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_BAD_OPTION, "JPEG quality must be a value between 0 and 100; value '%s' could not be parsed.", *viter); return FALSE; } if (quality < 0 || quality > 100) { g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_BAD_OPTION, "JPEG quality must be a value between 0 and 100; value '%d' is not allowed.", quality); return FALSE; } } else if (strcmp (*kiter, "smooth") == 0) { char *endptr = NULL; smoothing = strtol (*viter, &endptr, 10); if (endptr == *viter) { g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_BAD_OPTION, "JPEG smoothing must be a value between 0 and 100; value '%s' could not be parsed.", *viter); return FALSE; } if (smoothing < 0 || smoothing > 100) { g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_BAD_OPTION, "JPEG smoothing must be a value between 0 and 100; value '%d' is not allowed.", smoothing); return FALSE; } } else if (strcmp (*kiter, "optimize") == 0) { if (strcmp (*viter, "yes") == 0) optimize = TRUE; else if (strcmp (*viter, "no") == 0) optimize = FALSE; else { g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_BAD_OPTION, "JPEG optimize option must be 'yes' or 'no', value is: %s", *viter); return FALSE; } } #ifdef HAVE_PROGRESSIVE_JPEG else if (strcmp (*kiter, "progressive") == 0) { if (strcmp (*viter, "yes") == 0) progressive = TRUE; else if (strcmp (*viter, "no") == 0) progressive = FALSE; else { g_set_error (error, GDK_PIXBUF_ERROR, GDK_PIXBUF_ERROR_BAD_OPTION, "JPEG progressive option must be 'yes' or 'no', value is: %s", *viter); return FALSE; } } #endif else { g_warning ("Bad option name '%s' passed to JPEG saver", *kiter); return FALSE; } ++kiter; ++viter; } } rowstride = cairo_image_surface_get_stride (image); w = cairo_image_surface_get_width (image); h = cairo_image_surface_get_height (image); pixels = _cairo_image_surface_flush_and_get_data (image); 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"); return FALSE; } /* set up error handling */ cinfo.err = jpeg_std_error (&(jerr.pub)); jerr.pub.error_exit = fatal_error_handler; jerr.pub.output_message = output_message_handler; jerr.error = error; if (sigsetjmp (jerr.setjmp_buffer, 1)) { jpeg_destroy_compress (&cinfo); g_free (buf); return FALSE; } /* setup compress params */ jpeg_create_compress (&cinfo); _jpeg_memory_dest (&cinfo, (void **)buffer, buffer_size); cinfo.image_width = w; cinfo.image_height = h; cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB; /* set up jepg compression parameters */ jpeg_set_defaults (&cinfo); jpeg_set_quality (&cinfo, quality, TRUE); cinfo.smoothing_factor = smoothing; cinfo.optimize_coding = optimize; #ifdef HAVE_PROGRESSIVE_JPEG if (progressive) jpeg_simple_progression (&cinfo); #endif /* HAVE_PROGRESSIVE_JPEG */ jpeg_start_compress (&cinfo, TRUE); /* go one scanline at a time... and save */ while (cinfo.next_scanline < cinfo.image_height) { JSAMPROW *jbuf; /* convert scanline from RGBA to RGB packed */ _cairo_copy_line_as_rgba_big_endian (buf, pixels, w, FALSE); /* write scanline */ jbuf = (JSAMPROW *)(&buf); jpeg_write_scanlines (&cinfo, jbuf, 1); pixels += rowstride; } /* finish off */ jpeg_finish_compress (&cinfo); jpeg_destroy_compress (&cinfo); g_free (buf); return TRUE; } #endif /* HAVE_LIBJPEG */ static gboolean gth_image_saver_jpeg_save_image (GthImageSaver *base, GthImage *image, char **buffer, gsize *buffer_size, const char *mime_type, GCancellable *cancellable, GError **error) { #ifdef HAVE_LIBJPEG GthImageSaverJpeg *self = GTH_IMAGE_SAVER_JPEG (base); char **option_keys; char **option_values; int i = -1; int i_value; cairo_surface_t *surface; gboolean result; option_keys = g_malloc (sizeof (char *) * 5); option_values = g_malloc (sizeof (char *) * 5); i++; i_value = g_settings_get_int (self->priv->settings, PREF_JPEG_QUALITY); option_keys[i] = g_strdup ("quality"); option_values[i] = g_strdup_printf ("%d", i_value); i++; i_value = g_settings_get_int (self->priv->settings, PREF_JPEG_SMOOTHING); option_keys[i] = g_strdup ("smooth"); option_values[i] = g_strdup_printf ("%d", i_value); i++; i_value = g_settings_get_boolean (self->priv->settings, PREF_JPEG_OPTIMIZE); option_keys[i] = g_strdup ("optimize"); option_values[i] = g_strdup (i_value != 0 ? "yes" : "no"); i++; i_value = g_settings_get_boolean (self->priv->settings, PREF_JPEG_PROGRESSIVE); option_keys[i] = g_strdup ("progressive"); option_values[i] = g_strdup (i_value != 0 ? "yes" : "no"); i++; option_keys[i] = NULL; option_values[i] = NULL; surface = gth_image_get_cairo_surface (image); result = _cairo_surface_write_as_jpeg (surface, buffer, buffer_size, option_keys, option_values, error); cairo_surface_destroy (surface); g_strfreev (option_keys); g_strfreev (option_values); #else /* ! HAVE_LIBJPEG */ GdkPixbuf *pixbuf; char *pixbuf_type; gboolean result; pixbuf = gth_image_get_pixbuf (image); pixbuf_type = _gdk_pixbuf_get_type_from_mime_type (mime_type); result = gdk_pixbuf_save_to_bufferv (pixbuf, buffer, buffer_size, pixbuf_type, NULL, NULL, error); g_free (pixbuf_type); g_object_unref (pixbuf); #endif /* HAVE_LIBJPEG */ return result; } static void gth_image_saver_jpeg_class_init (GthImageSaverJpegClass *klass) { GObjectClass *object_class; GthImageSaverClass *image_saver_class; object_class = G_OBJECT_CLASS (klass); object_class->finalize = gth_image_saver_jpeg_finalize; image_saver_class = GTH_IMAGE_SAVER_CLASS (klass); image_saver_class->id = "jpeg"; image_saver_class->display_name = _("JPEG"); image_saver_class->mime_type = "image/jpeg"; image_saver_class->extensions = "jpeg jpg"; image_saver_class->get_default_ext = gth_image_saver_jpeg_get_default_ext; image_saver_class->get_control = gth_image_saver_jpeg_get_control; image_saver_class->save_options = gth_image_saver_jpeg_save_options; image_saver_class->can_save = gth_image_saver_jpeg_can_save; image_saver_class->save_image = gth_image_saver_jpeg_save_image; } static void gth_image_saver_jpeg_init (GthImageSaverJpeg *self) { self->priv = gth_image_saver_jpeg_get_instance_private (self); self->priv->settings = g_settings_new (PIX_IMAGE_SAVERS_JPEG_SCHEMA); self->priv->builder = NULL; self->priv->default_ext = NULL; }