summaryrefslogtreecommitdiffstats
path: root/plug-ins/file-jpeg/jpeg-save.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--plug-ins/file-jpeg/jpeg-save.c1524
1 files changed, 1524 insertions, 0 deletions
diff --git a/plug-ins/file-jpeg/jpeg-save.c b/plug-ins/file-jpeg/jpeg-save.c
new file mode 100644
index 0000000..4af4d1d
--- /dev/null
+++ b/plug-ins/file-jpeg/jpeg-save.c
@@ -0,0 +1,1524 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 3 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 <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <setjmp.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <glib/gstdio.h>
+
+#include <jpeglib.h>
+#include <jerror.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include "libgimp/stdplugins-intl.h"
+
+#include "jpeg.h"
+#include "jpeg-icc.h"
+#include "jpeg-load.h"
+#include "jpeg-save.h"
+#include "jpeg-settings.h"
+
+#ifdef C_ARITH_CODING_SUPPORTED
+static gboolean arithc_supported = TRUE;
+#else
+static gboolean arithc_supported = FALSE;
+#endif
+
+#define SCALE_WIDTH 125
+
+/* See bugs #63610 and #61088 for a discussion about the quality settings */
+#define DEFAULT_IJG_QUALITY 90.0
+#define DEFAULT_SMOOTHING 0.0
+#define DEFAULT_OPTIMIZE TRUE
+#define DEFAULT_ARITHMETIC_CODING FALSE
+#define DEFAULT_PROGRESSIVE TRUE
+#define DEFAULT_BASELINE TRUE
+#define DEFAULT_SUBSMP JPEG_SUBSAMPLING_1x1_1x1_1x1
+#define DEFAULT_RESTART 0
+#define DEFAULT_RESTART_MCU_ROWS 16
+#define DEFAULT_DCT 0
+#define DEFAULT_PREVIEW FALSE
+#define DEFAULT_EXIF FALSE
+#define DEFAULT_XMP FALSE
+#define DEFAULT_IPTC FALSE
+#define DEFAULT_THUMBNAIL FALSE
+#define DEFAULT_PROFILE TRUE
+#define DEFAULT_USE_ORIG_QUALITY FALSE
+
+#define JPEG_DEFAULTS_PARASITE "jpeg-save-defaults"
+
+
+typedef struct
+{
+ struct jpeg_compress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ gint tile_height;
+ FILE *outfile;
+ gboolean has_alpha;
+ gint rowstride;
+ guchar *data;
+ guchar *src;
+ GeglBuffer *buffer;
+ const Babl *format;
+ const gchar *file_name;
+ gboolean abort_me;
+ guint source_id;
+} PreviewPersistent;
+
+/*le added : struct containing pointers to export dialog*/
+typedef struct
+{
+ gboolean run;
+ GtkWidget *use_restart_markers; /*checkbox setting use restart markers*/
+ GtkTextBuffer *text_buffer;
+ GtkAdjustment *scale_data; /*for restart markers*/
+ gulong handler_id_restart;
+
+ GtkAdjustment *quality; /*quality slidebar*/
+ GtkAdjustment *smoothing; /*smoothing slidebar*/
+ GtkWidget *optimize; /*optimize toggle*/
+ GtkWidget *arithmetic_coding; /*arithmetic coding toggle*/
+ GtkWidget *progressive; /*progressive toggle*/
+ GtkWidget *subsmp; /*subsampling side select*/
+ GtkWidget *restart; /*spinner for setting frequency restart markers*/
+ GtkWidget *dct; /*DCT side select*/
+ GtkWidget *preview; /*show preview toggle checkbox*/
+ GtkWidget *save_exif;
+ GtkWidget *save_xmp;
+ GtkWidget *save_iptc;
+ GtkWidget *save_thumbnail;
+ GtkWidget *save_profile;
+ GtkWidget *use_orig_quality; /*quant tables toggle*/
+} JpegSaveGui;
+
+static void make_preview (void);
+
+static void save_restart_update (GtkAdjustment *adjustment,
+ GtkWidget *toggle);
+static void subsampling_changed (GtkWidget *combo,
+ GtkAdjustment *entry);
+static void quality_changed (GtkAdjustment *scale_entry,
+ GtkWidget *toggle);
+static void subsampling_changed2 (GtkWidget *combo,
+ GtkWidget *toggle);
+static void use_orig_qual_changed (GtkWidget *toggle,
+ GtkAdjustment *scale_entry);
+static void use_orig_qual_changed2 (GtkWidget *toggle,
+ GtkWidget *combo);
+
+
+static GtkWidget *restart_markers_scale = NULL;
+static GtkWidget *restart_markers_label = NULL;
+static GtkWidget *preview_size = NULL;
+static PreviewPersistent *prev_p = NULL;
+
+static void save_dialog_response (GtkWidget *widget,
+ gint response_id,
+ gpointer data);
+
+static void load_gui_defaults (JpegSaveGui *pg);
+static void save_defaults (void);
+
+
+/*
+ * sg - This is the best I can do, I'm afraid... I think it will fail
+ * if something bad really happens (but it might not). If you have a
+ * better solution, send it ;-)
+ */
+static void
+background_error_exit (j_common_ptr cinfo)
+{
+ if (prev_p)
+ prev_p->abort_me = TRUE;
+ (*cinfo->err->output_message) (cinfo);
+}
+
+static gboolean
+background_jpeg_save (PreviewPersistent *pp)
+{
+ gint yend;
+
+ if (pp->abort_me || (pp->cinfo.next_scanline >= pp->cinfo.image_height))
+ {
+ /* clean up... */
+ if (pp->abort_me)
+ {
+ jpeg_abort_compress (&(pp->cinfo));
+ }
+ else
+ {
+ jpeg_finish_compress (&(pp->cinfo));
+ }
+
+ fclose (pp->outfile);
+ jpeg_destroy_compress (&(pp->cinfo));
+
+ g_free (pp->data);
+
+ if (pp->buffer)
+ g_object_unref (pp->buffer);
+
+ /* display the preview stuff */
+ if (!pp->abort_me)
+ {
+ GFile *file = g_file_new_for_path (pp->file_name);
+ GFileInfo *info;
+ gchar *text;
+ GError *error = NULL;
+
+ info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_SIZE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, &error);
+
+ if (info)
+ {
+ goffset size = g_file_info_get_size (info);
+ gchar *size_text;
+
+ size_text = g_format_size (size);
+ text = g_strdup_printf (_("File size: %s"), size_text);
+ g_free (size_text);
+
+ g_object_unref (info);
+ }
+ else
+ {
+ text = g_strdup_printf (_("File size: %s"), error->message);
+ g_clear_error (&error);
+ }
+
+ gtk_label_set_text (GTK_LABEL (preview_size), text);
+ g_free (text);
+
+ g_object_unref (file);
+
+ /* and load the preview */
+ load_image (pp->file_name, GIMP_RUN_NONINTERACTIVE, TRUE, NULL, NULL);
+ }
+
+ /* we cleanup here (load_image doesn't run in the background) */
+ g_unlink (pp->file_name);
+
+ g_free (pp);
+ prev_p = NULL;
+
+ gimp_displays_flush ();
+ gdk_flush ();
+
+ return FALSE;
+ }
+ else
+ {
+ if ((pp->cinfo.next_scanline % pp->tile_height) == 0)
+ {
+ yend = pp->cinfo.next_scanline + pp->tile_height;
+ yend = MIN (yend, pp->cinfo.image_height);
+ gegl_buffer_get (pp->buffer,
+ GEGL_RECTANGLE (0, pp->cinfo.next_scanline,
+ pp->cinfo.image_width,
+ (yend - pp->cinfo.next_scanline)),
+ 1.0,
+ pp->format,
+ pp->data,
+ GEGL_AUTO_ROWSTRIDE,
+ GEGL_ABYSS_NONE);
+ pp->src = pp->data;
+ }
+
+ jpeg_write_scanlines (&(pp->cinfo), (JSAMPARRAY) &(pp->src), 1);
+ pp->src += pp->rowstride;
+
+ return TRUE;
+ }
+}
+
+gboolean
+save_image (const gchar *filename,
+ gint32 image_ID,
+ gint32 drawable_ID,
+ gint32 orig_image_ID,
+ gboolean preview,
+ GError **error)
+{
+ static struct jpeg_compress_struct cinfo;
+ static struct my_error_mgr jerr;
+
+ GimpImageType drawable_type;
+ GeglBuffer *buffer;
+ const Babl *format;
+ JpegSubsampling subsampling;
+ FILE * volatile outfile;
+ guchar *data;
+ guchar *src;
+ GimpColorProfile *profile = NULL;
+
+ gboolean has_alpha;
+ gboolean out_linear = FALSE;
+ gint rowstride, yend;
+
+ drawable_type = gimp_drawable_type (drawable_ID);
+ buffer = gimp_drawable_get_buffer (drawable_ID);
+
+ if (! preview)
+ gimp_progress_init_printf (_("Exporting '%s'"),
+ gimp_filename_to_utf8 (filename));
+
+ /* Step 1: allocate and initialize JPEG compression object */
+
+ /* We have to set up the error handler first, in case the initialization
+ * step fails. (Unlikely, but it could happen if you are out of memory.)
+ * This routine fills in the contents of struct jerr, and returns jerr's
+ * address which we place into the link field in cinfo.
+ */
+ cinfo.err = jpeg_std_error (&jerr.pub);
+ jerr.pub.error_exit = my_error_exit;
+
+ outfile = NULL;
+ /* Establish the setjmp return context for my_error_exit to use. */
+ if (setjmp (jerr.setjmp_buffer))
+ {
+ /* If we get here, the JPEG code has signaled an error.
+ * We need to clean up the JPEG object, close the input file, and return.
+ */
+ jpeg_destroy_compress (&cinfo);
+ if (outfile)
+ fclose (outfile);
+ if (buffer)
+ g_object_unref (buffer);
+
+ return FALSE;
+ }
+
+ /* Now we can initialize the JPEG compression object. */
+ jpeg_create_compress (&cinfo);
+
+ /* Step 2: specify data destination (eg, a file) */
+ /* Note: steps 2 and 3 can be done in either order. */
+
+ /* Here we use the library-supplied code to send compressed data to a
+ * stdio stream. You can also write your own code to do something else.
+ * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that
+ * requires it in order to write binary files.
+ */
+ if ((outfile = g_fopen (filename, "wb")) == NULL)
+ {
+ g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
+ _("Could not open '%s' for writing: %s"),
+ gimp_filename_to_utf8 (filename), g_strerror (errno));
+ return FALSE;
+ }
+
+ /* When we don't save profiles, we convert data to sRGB because
+ * that's what most/all readers expect on a no-profile JPEG.
+ * If we save an assigned profile, let's just follow its TRC.
+ * If we save the default linear profile (i.e. no assigned
+ * profile), we convert it to sRGB, except when it is 8-bit linear.
+ */
+ if (jsvals.save_profile)
+ {
+ profile = gimp_image_get_color_profile (orig_image_ID);
+
+ /* If a profile is explicitly set, follow its TRC, whatever the
+ * storage format.
+ */
+ if (profile && gimp_color_profile_is_linear (profile))
+ out_linear = TRUE;
+
+ if (! profile)
+ {
+ /* There is always an effective profile. */
+ profile = gimp_image_get_effective_color_profile (orig_image_ID);
+
+ if (gimp_color_profile_is_linear (profile))
+ {
+ if (gimp_image_get_precision (image_ID) != GIMP_PRECISION_U8_LINEAR)
+ {
+ GimpColorProfile *saved_profile;
+
+ saved_profile = gimp_color_profile_new_srgb_trc_from_color_profile (profile);
+ g_object_unref (profile);
+ profile = saved_profile;
+ }
+ else
+ {
+ /* Keep linear profile as-is for 8-bit linear image. */
+ out_linear = TRUE;
+ }
+ }
+ }
+ }
+
+ jpeg_stdio_dest (&cinfo, outfile);
+
+ /* Get the input image and a pointer to its data.
+ */
+ switch (drawable_type)
+ {
+ case GIMP_RGB_IMAGE:
+ /* # of color components per pixel */
+ cinfo.input_components = 3;
+ has_alpha = FALSE;
+
+ if (out_linear)
+ format = babl_format ("RGB u8");
+ else
+ format = babl_format ("R'G'B' u8");
+ break;
+
+ case GIMP_GRAY_IMAGE:
+ /* # of color components per pixel */
+ cinfo.input_components = 1;
+ has_alpha = FALSE;
+
+ if (out_linear)
+ format = babl_format ("Y u8");
+ else
+ format = babl_format ("Y' u8");
+ break;
+
+ case GIMP_RGBA_IMAGE:
+ /* # of color components per pixel (minus the GIMP alpha channel) */
+ cinfo.input_components = 4 - 1;
+ has_alpha = TRUE;
+
+ if (out_linear)
+ format = babl_format ("RGB u8");
+ else
+ format = babl_format ("R'G'B' u8");
+ break;
+
+ case GIMP_GRAYA_IMAGE:
+ /* # of color components per pixel (minus the GIMP alpha channel) */
+ cinfo.input_components = 2 - 1;
+ has_alpha = TRUE;
+ if (out_linear)
+ format = babl_format ("Y u8");
+ else
+ format = babl_format ("Y' u8");
+ break;
+
+ case GIMP_INDEXED_IMAGE:
+ default:
+ return FALSE;
+ }
+
+ /* Step 3: set parameters for compression */
+
+ /* First we supply a description of the input image.
+ * Four fields of the cinfo struct must be filled in:
+ */
+ /* image width and height, in pixels */
+ cinfo.image_width = gegl_buffer_get_width (buffer);
+ cinfo.image_height = gegl_buffer_get_height (buffer);
+ /* colorspace of input image */
+ cinfo.in_color_space = (drawable_type == GIMP_RGB_IMAGE ||
+ drawable_type == GIMP_RGBA_IMAGE)
+ ? JCS_RGB : JCS_GRAYSCALE;
+ /* Now use the library's routine to set default compression parameters.
+ * (You must set at least cinfo.in_color_space before calling this,
+ * since the defaults depend on the source color space.)
+ */
+ jpeg_set_defaults (&cinfo);
+
+ jpeg_set_quality (&cinfo, (gint) (jsvals.quality + 0.5), jsvals.baseline);
+
+ if (jsvals.use_orig_quality && num_quant_tables > 0)
+ {
+ guint **quant_tables;
+ gint t;
+
+ /* override tables generated by jpeg_set_quality() with custom tables */
+ quant_tables = jpeg_restore_original_tables (image_ID, num_quant_tables);
+ if (quant_tables)
+ {
+ for (t = 0; t < num_quant_tables; t++)
+ {
+ jpeg_add_quant_table (&cinfo, t, quant_tables[t],
+ 100, jsvals.baseline);
+ g_free (quant_tables[t]);
+ }
+ g_free (quant_tables);
+ }
+ }
+
+ if (arithc_supported)
+ {
+ cinfo.arith_code = jsvals.arithmetic_coding;
+ if (!jsvals.arithmetic_coding)
+ cinfo.optimize_coding = jsvals.optimize;
+ }
+ else
+ cinfo.optimize_coding = jsvals.optimize;
+
+ subsampling = (gimp_drawable_is_rgb (drawable_ID) ?
+ jsvals.subsmp : JPEG_SUBSAMPLING_1x1_1x1_1x1);
+
+ /* smoothing is not supported with nonstandard sampling ratios */
+ if (subsampling != JPEG_SUBSAMPLING_2x1_1x1_1x1 &&
+ subsampling != JPEG_SUBSAMPLING_1x2_1x1_1x1)
+ {
+ cinfo.smoothing_factor = (gint) (jsvals.smoothing * 100);
+ }
+
+ if (jsvals.progressive)
+ {
+ jpeg_simple_progression (&cinfo);
+ }
+
+ switch (subsampling)
+ {
+ case JPEG_SUBSAMPLING_2x2_1x1_1x1:
+ default:
+ cinfo.comp_info[0].h_samp_factor = 2;
+ cinfo.comp_info[0].v_samp_factor = 2;
+ cinfo.comp_info[1].h_samp_factor = 1;
+ cinfo.comp_info[1].v_samp_factor = 1;
+ cinfo.comp_info[2].h_samp_factor = 1;
+ cinfo.comp_info[2].v_samp_factor = 1;
+ break;
+
+ case JPEG_SUBSAMPLING_2x1_1x1_1x1:
+ cinfo.comp_info[0].h_samp_factor = 2;
+ cinfo.comp_info[0].v_samp_factor = 1;
+ cinfo.comp_info[1].h_samp_factor = 1;
+ cinfo.comp_info[1].v_samp_factor = 1;
+ cinfo.comp_info[2].h_samp_factor = 1;
+ cinfo.comp_info[2].v_samp_factor = 1;
+ break;
+
+ case JPEG_SUBSAMPLING_1x1_1x1_1x1:
+ cinfo.comp_info[0].h_samp_factor = 1;
+ cinfo.comp_info[0].v_samp_factor = 1;
+ cinfo.comp_info[1].h_samp_factor = 1;
+ cinfo.comp_info[1].v_samp_factor = 1;
+ cinfo.comp_info[2].h_samp_factor = 1;
+ cinfo.comp_info[2].v_samp_factor = 1;
+ break;
+
+ case JPEG_SUBSAMPLING_1x2_1x1_1x1:
+ cinfo.comp_info[0].h_samp_factor = 1;
+ cinfo.comp_info[0].v_samp_factor = 2;
+ cinfo.comp_info[1].h_samp_factor = 1;
+ cinfo.comp_info[1].v_samp_factor = 1;
+ cinfo.comp_info[2].h_samp_factor = 1;
+ cinfo.comp_info[2].v_samp_factor = 1;
+ break;
+ }
+
+ cinfo.restart_interval = 0;
+ cinfo.restart_in_rows = jsvals.restart;
+
+ switch (jsvals.dct)
+ {
+ case 0:
+ default:
+ cinfo.dct_method = JDCT_ISLOW;
+ break;
+
+ case 1:
+ cinfo.dct_method = JDCT_IFAST;
+ break;
+
+ case 2:
+ cinfo.dct_method = JDCT_FLOAT;
+ break;
+ }
+
+ {
+ gdouble xresolution;
+ gdouble yresolution;
+
+ gimp_image_get_resolution (orig_image_ID, &xresolution, &yresolution);
+
+ if (xresolution > 1e-5 && yresolution > 1e-5)
+ {
+ gdouble factor;
+
+ factor = gimp_unit_get_factor (gimp_image_get_unit (orig_image_ID));
+
+ if (factor == 2.54 /* cm */ ||
+ factor == 25.4 /* mm */)
+ {
+ cinfo.density_unit = 2; /* dots per cm */
+
+ xresolution /= 2.54;
+ yresolution /= 2.54;
+ }
+ else
+ {
+ cinfo.density_unit = 1; /* dots per inch */
+ }
+
+ cinfo.X_density = xresolution;
+ cinfo.Y_density = yresolution;
+ }
+ }
+
+ /* Step 4: Start compressor */
+
+ /* TRUE ensures that we will write a complete interchange-JPEG file.
+ * Pass TRUE unless you are very sure of what you're doing.
+ */
+ jpeg_start_compress (&cinfo, TRUE);
+
+ /* Step 4.1: Write the comment out - pw */
+ if (image_comment && *image_comment)
+ {
+#ifdef GIMP_UNSTABLE
+ g_print ("jpeg-save: saving image comment (%d bytes)\n",
+ (int) strlen (image_comment));
+#endif
+ jpeg_write_marker (&cinfo, JPEG_COM,
+ (guchar *) image_comment, strlen (image_comment));
+ }
+
+ /* Step 4.2: store the color profile */
+ if (jsvals.save_profile)
+ {
+ const guint8 *icc_data;
+ gsize icc_length;
+
+ icc_data = gimp_color_profile_get_icc_profile (profile, &icc_length);
+ jpeg_icc_write_profile (&cinfo, icc_data, icc_length);
+
+ g_object_unref (profile);
+ }
+
+ /* Step 5: while (scan lines remain to be written) */
+ /* jpeg_write_scanlines(...); */
+
+ /* Here we use the library's state variable cinfo.next_scanline as the
+ * loop counter, so that we don't have to keep track ourselves.
+ * To keep things simple, we pass one scanline per call; you can pass
+ * more if you wish, though.
+ */
+ /* JSAMPLEs per row in image_buffer */
+ rowstride = cinfo.input_components * cinfo.image_width;
+ data = g_new (guchar, rowstride * gimp_tile_height ());
+
+ /* fault if cinfo.next_scanline isn't initially a multiple of
+ * gimp_tile_height */
+ src = NULL;
+
+ /*
+ * sg - if we preview, we want this to happen in the background -- do
+ * not duplicate code in the future; for now, it's OK
+ */
+
+ if (preview)
+ {
+ PreviewPersistent *pp = g_new (PreviewPersistent, 1);
+
+ /* pass all the information we need */
+ pp->cinfo = cinfo;
+ pp->tile_height = gimp_tile_height();
+ pp->data = data;
+ pp->outfile = outfile;
+ pp->has_alpha = has_alpha;
+ pp->rowstride = rowstride;
+ pp->data = data;
+ pp->buffer = buffer;
+ pp->format = format;
+ pp->src = NULL;
+ pp->file_name = filename;
+ pp->abort_me = FALSE;
+
+ g_warn_if_fail (prev_p == NULL);
+ prev_p = pp;
+
+ pp->cinfo.err = jpeg_std_error(&(pp->jerr));
+ pp->jerr.error_exit = background_error_exit;
+
+ gtk_label_set_text (GTK_LABEL (preview_size),
+ _("Calculating file size..."));
+
+ pp->source_id = g_idle_add ((GSourceFunc) background_jpeg_save, pp);
+
+ /* background_jpeg_save() will cleanup as needed */
+ return TRUE;
+ }
+
+ while (cinfo.next_scanline < cinfo.image_height)
+ {
+ if ((cinfo.next_scanline % gimp_tile_height ()) == 0)
+ {
+ yend = cinfo.next_scanline + gimp_tile_height ();
+ yend = MIN (yend, cinfo.image_height);
+ gegl_buffer_get (buffer,
+ GEGL_RECTANGLE (0, cinfo.next_scanline,
+ cinfo.image_width,
+ (yend - cinfo.next_scanline)),
+ 1.0,
+ format,
+ data,
+ GEGL_AUTO_ROWSTRIDE,
+ GEGL_ABYSS_NONE);
+ src = data;
+ }
+
+ jpeg_write_scanlines (&cinfo, (JSAMPARRAY) &src, 1);
+ src += rowstride;
+
+ if ((cinfo.next_scanline % 32) == 0)
+ gimp_progress_update ((gdouble) cinfo.next_scanline /
+ (gdouble) cinfo.image_height);
+ }
+
+ /* Step 6: Finish compression */
+ jpeg_finish_compress (&cinfo);
+ /* After finish_compress, we can close the output file. */
+ fclose (outfile);
+
+ /* Step 7: release JPEG compression object */
+
+ /* This is an important step since it will release a good deal of memory. */
+ jpeg_destroy_compress (&cinfo);
+
+ /* free the temporary buffer */
+ g_free (data);
+
+ /* And we're done! */
+ gimp_progress_update (1.0);
+
+ g_object_unref (buffer);
+
+ return TRUE;
+}
+
+static void
+make_preview (void)
+{
+ destroy_preview ();
+
+ if (jsvals.preview)
+ {
+ gchar *tn = gimp_temp_name ("jpeg");
+
+ if (! undo_touched)
+ {
+ /* we freeze undo saving so that we can avoid sucking up
+ * tile cache with our unneeded preview steps. */
+ gimp_image_undo_freeze (preview_image_ID);
+
+ undo_touched = TRUE;
+ }
+
+ save_image (tn,
+ preview_image_ID,
+ drawable_ID_global,
+ orig_image_ID_global,
+ TRUE, NULL);
+
+ if (display_ID == -1)
+ display_ID = gimp_display_new (preview_image_ID);
+ }
+ else
+ {
+ gtk_label_set_text (GTK_LABEL (preview_size), _("File size: unknown"));
+
+ gimp_displays_flush ();
+ }
+}
+
+void
+destroy_preview (void)
+{
+ if (prev_p && !prev_p->abort_me)
+ {
+ guint id = prev_p->source_id;
+ prev_p->abort_me = TRUE; /* signal the background save to stop */
+ background_jpeg_save (prev_p);
+ g_source_remove (id);
+ }
+
+ if (gimp_image_is_valid (preview_image_ID) &&
+ gimp_item_is_valid (preview_layer_ID))
+ {
+ /* assuming that reference counting is working correctly,
+ we do not need to delete the layer, removing it from
+ the image should be sufficient */
+ gimp_image_remove_layer (preview_image_ID, preview_layer_ID);
+
+ preview_layer_ID = -1;
+ }
+}
+
+static void
+toggle_arithmetic_coding (GtkToggleButton *togglebutton,
+ gpointer user_data)
+{
+ GtkWidget *optimize = GTK_WIDGET (user_data);
+
+ gtk_widget_set_sensitive (optimize,
+ !gtk_toggle_button_get_active (togglebutton));
+}
+
+gboolean
+save_dialog (void)
+{
+ JpegSaveGui pg;
+ GtkWidget *dialog;
+ GtkWidget *vbox;
+ GtkWidget *vbox2;
+ GtkAdjustment *entry;
+ GtkWidget *table;
+ GtkWidget *table2;
+ GtkWidget *tabledefaults;
+ GtkWidget *expander;
+ GtkWidget *frame;
+ GtkWidget *toggle;
+ GtkWidget *spinbutton;
+ GtkWidget *label;
+ GtkWidget *combo;
+ GtkWidget *text_view;
+ GtkTextBuffer *text_buffer;
+ GtkWidget *scrolled_window;
+ GtkWidget *button;
+ gchar *text;
+ gint row;
+
+ dialog = gimp_export_dialog_new (_("JPEG"), PLUG_IN_BINARY, SAVE_PROC);
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (save_dialog_response),
+ &pg);
+ g_signal_connect (dialog, "destroy",
+ G_CALLBACK (gtk_main_quit),
+ NULL);
+
+ gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
+ gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)),
+ vbox, TRUE, TRUE, 0);
+ gtk_widget_show (vbox);
+
+ vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_box_pack_start (GTK_BOX (vbox), vbox2, FALSE, FALSE, 0);
+ gtk_widget_show (vbox2);
+
+ table = gtk_table_new (1, 3, FALSE);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 6);
+ gtk_box_pack_start (GTK_BOX (vbox2), table, FALSE, FALSE, 0);
+ gtk_widget_show (table);
+
+ pg.quality = entry = (GtkAdjustment *)
+ gimp_scale_entry_new (GTK_TABLE (table), 0, 0,
+ _("_Quality:"),
+ SCALE_WIDTH, 0, jsvals.quality,
+ 0.0, 100.0, 1.0, 10.0, 0,
+ TRUE, 0.0, 0.0,
+ _("JPEG quality parameter"),
+ "file-jpeg-save-quality");
+
+ g_signal_connect (entry, "value-changed",
+ G_CALLBACK (gimp_double_adjustment_update),
+ &jsvals.quality);
+ g_signal_connect (entry, "value-changed",
+ G_CALLBACK (make_preview),
+ NULL);
+
+ /* custom quantization tables - now used also for original quality */
+ pg.use_orig_quality = toggle =
+ gtk_check_button_new_with_mnemonic (_("_Use quality settings from original "
+ "image"));
+ gtk_box_pack_start (GTK_BOX (vbox2), toggle, FALSE, FALSE, 0);
+ gtk_widget_show (toggle);
+
+ gimp_help_set_help_data (toggle,
+ _("If the original image was loaded from a JPEG "
+ "file using non-standard quality settings "
+ "(quantization tables), enable this option to "
+ "get almost the same quality and file size."),
+ NULL);
+
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (gimp_toggle_button_update),
+ &jsvals.use_orig_quality);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
+ jsvals.use_orig_quality
+ && (orig_quality > 0)
+ && (orig_subsmp == jsvals.subsmp)
+ );
+ gtk_widget_set_sensitive (toggle, (orig_quality > 0));
+
+ /* changing quality disables custom quantization tables, and vice-versa */
+ g_signal_connect (pg.quality, "value-changed",
+ G_CALLBACK (quality_changed),
+ pg.use_orig_quality);
+ g_signal_connect (pg.use_orig_quality, "toggled",
+ G_CALLBACK (use_orig_qual_changed),
+ pg.quality);
+
+ /* File size */
+ vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_box_pack_start (GTK_BOX (vbox), vbox2, FALSE, FALSE, 0);
+ gtk_widget_show (vbox2);
+
+ preview_size = gtk_label_new (_("File size: unknown"));
+ gtk_label_set_xalign (GTK_LABEL (preview_size), 0.0);
+ gtk_label_set_ellipsize (GTK_LABEL (preview_size), PANGO_ELLIPSIZE_END);
+ gimp_label_set_attributes (GTK_LABEL (preview_size),
+ PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC,
+ -1);
+ gtk_box_pack_start (GTK_BOX (vbox2), preview_size, FALSE, FALSE, 0);
+ gtk_widget_show (preview_size);
+
+ gimp_help_set_help_data (preview_size,
+ _("Enable preview to obtain the file size."), NULL);
+
+ pg.preview = toggle =
+ gtk_check_button_new_with_mnemonic (_("Sho_w preview in image window"));
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.preview);
+ gtk_box_pack_start (GTK_BOX (vbox2), toggle, FALSE, FALSE, 0);
+ gtk_widget_show (toggle);
+
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (gimp_toggle_button_update),
+ &jsvals.preview);
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (make_preview),
+ NULL);
+
+ vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_box_pack_start (GTK_BOX (vbox), vbox2, FALSE, FALSE, 0);
+ gtk_widget_show (vbox2);
+
+ /* Save EXIF data */
+ pg.save_exif = toggle =
+ gtk_check_button_new_with_mnemonic (_("Save _Exif data"));
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.save_exif);
+ gtk_box_pack_start (GTK_BOX (vbox2), toggle, FALSE, FALSE, 0);
+ gtk_widget_show (toggle);
+
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (gimp_toggle_button_update),
+ &jsvals.save_exif);
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (make_preview),
+ NULL);
+
+ /* Save XMP metadata */
+ pg.save_xmp = toggle =
+ gtk_check_button_new_with_mnemonic (_("Save _XMP data"));
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.save_xmp);
+ gtk_box_pack_start (GTK_BOX (vbox2), toggle, FALSE, FALSE, 0);
+ gtk_widget_show (toggle);
+
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (gimp_toggle_button_update),
+ &jsvals.save_xmp);
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (make_preview),
+ NULL);
+
+ /* Save IPTC metadata */
+ pg.save_iptc = toggle =
+ gtk_check_button_new_with_mnemonic (_("Save _IPTC data"));
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.save_iptc);
+ gtk_box_pack_start (GTK_BOX (vbox2), toggle, FALSE, FALSE, 0);
+ gtk_widget_show (toggle);
+
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (gimp_toggle_button_update),
+ &jsvals.save_iptc);
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (make_preview),
+ NULL);
+
+ /* Save thumbnail */
+ pg.save_thumbnail = toggle =
+ gtk_check_button_new_with_mnemonic (_("Save _thumbnail"));
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.save_thumbnail);
+ gtk_box_pack_start (GTK_BOX (vbox2), toggle, FALSE, FALSE, 0);
+ gtk_widget_show (toggle);
+
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (gimp_toggle_button_update),
+ &jsvals.save_thumbnail);
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (make_preview),
+ NULL);
+
+ /* Save color profile */
+ pg.save_profile = toggle =
+ gtk_check_button_new_with_mnemonic (_("Save color _profile"));
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.save_profile);
+ gtk_box_pack_start (GTK_BOX (vbox2), toggle, FALSE, FALSE, 0);
+ gtk_widget_show (toggle);
+
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (gimp_toggle_button_update),
+ &jsvals.save_profile);
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (make_preview),
+ NULL);
+
+ /* Comment */
+ frame = gimp_frame_new (_("Comment"));
+ gtk_box_pack_start (GTK_BOX (vbox), frame, TRUE, TRUE, 0);
+ gtk_widget_show (frame);
+
+ scrolled_window = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_SHADOW_IN);
+ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_widget_set_size_request (scrolled_window, 250, 50);
+ gtk_container_add (GTK_CONTAINER (frame), scrolled_window);
+ gtk_widget_show (scrolled_window);
+
+ pg.text_buffer = text_buffer = gtk_text_buffer_new (NULL);
+ if (image_comment)
+ gtk_text_buffer_set_text (text_buffer, image_comment, -1);
+
+ text_view = gtk_text_view_new_with_buffer (text_buffer);
+ gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (text_view), GTK_WRAP_WORD);
+
+ gtk_container_add (GTK_CONTAINER (scrolled_window), text_view);
+ gtk_widget_show (text_view);
+
+ g_object_unref (text_buffer);
+
+ /* Advanced expander */
+ text = g_strdup_printf ("<b>%s</b>", _("_Advanced Options"));
+ expander = gtk_expander_new_with_mnemonic (text);
+ gtk_expander_set_use_markup (GTK_EXPANDER (expander), TRUE);
+ g_free (text);
+
+ gtk_box_pack_start (GTK_BOX (vbox), expander, TRUE, TRUE, 0);
+ gtk_widget_show (expander);
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
+ gtk_container_add (GTK_CONTAINER (expander), vbox);
+ gtk_widget_show (vbox);
+
+ frame = gimp_frame_new ("<expander>");
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ table = gtk_table_new (4, 8, FALSE);
+ gtk_table_set_col_spacings (GTK_TABLE (table), 6);
+ gtk_table_set_row_spacings (GTK_TABLE (table), 6);
+ gtk_table_set_col_spacing (GTK_TABLE (table), 1, 12);
+ gtk_container_add (GTK_CONTAINER (frame), table);
+ gtk_widget_show (table);
+
+ table2 = gtk_table_new (1, 3, FALSE);
+ gtk_table_set_col_spacings (GTK_TABLE (table2), 6);
+ gtk_table_attach (GTK_TABLE (table), table2,
+ 2, 6, 0, 1, GTK_FILL, GTK_FILL, 0, 0);
+ gtk_widget_show (table2);
+
+ pg.smoothing = entry = (GtkAdjustment *)
+ gimp_scale_entry_new (GTK_TABLE (table2), 0, 0,
+ _("S_moothing:"),
+ 100, 0, jsvals.smoothing,
+ 0.0, 1.0, 0.01, 0.1, 2,
+ TRUE, 0.0, 0.0,
+ NULL,
+ "file-jpeg-save-smoothing");
+ g_signal_connect (entry, "value-changed",
+ G_CALLBACK (gimp_double_adjustment_update),
+ &jsvals.smoothing);
+ g_signal_connect (entry, "value-changed",
+ G_CALLBACK (make_preview),
+ NULL);
+
+ restart_markers_label = gtk_label_new (_("Interval (MCU rows):"));
+ gtk_label_set_xalign (GTK_LABEL (restart_markers_label), 1.0);
+ gtk_table_attach (GTK_TABLE (table), restart_markers_label, 4, 5, 1, 2,
+ GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);
+ gtk_widget_show (restart_markers_label);
+
+ pg.scale_data = (GtkAdjustment *)
+ gtk_adjustment_new (((jsvals.restart == 0) ?
+ DEFAULT_RESTART_MCU_ROWS : jsvals.restart),
+ 1.0, 64.0, 1.0, 1.0, 0);
+ pg.restart = restart_markers_scale = spinbutton =
+ gimp_spin_button_new (pg.scale_data, 1.0, 0);
+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
+ gtk_table_attach (GTK_TABLE (table), spinbutton, 5, 6, 1, 2,
+ GTK_FILL, GTK_FILL, 0, 0);
+ gtk_widget_show (spinbutton);
+
+ pg.use_restart_markers = toggle =
+ gtk_check_button_new_with_mnemonic (_("Use _restart markers"));
+ gtk_table_attach (GTK_TABLE (table), toggle, 2, 4, 1, 2, GTK_FILL, 0, 0, 0);
+ gtk_widget_show (toggle);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.restart);
+
+ gtk_widget_set_sensitive (restart_markers_label, jsvals.restart);
+ gtk_widget_set_sensitive (restart_markers_scale, jsvals.restart);
+
+ g_signal_connect (pg.scale_data, "value-changed",
+ G_CALLBACK (save_restart_update),
+ toggle);
+ pg.handler_id_restart = g_signal_connect_swapped (toggle, "toggled",
+ G_CALLBACK (save_restart_update),
+ pg.scale_data);
+
+ row = 0;
+
+ /* Optimize */
+ pg.optimize = toggle = gtk_check_button_new_with_mnemonic (_("_Optimize"));
+ gtk_table_attach (GTK_TABLE (table), toggle, 0, 1,
+ row, row + 1, GTK_FILL, 0, 0, 0);
+ gtk_widget_show (toggle);
+
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (gimp_toggle_button_update),
+ &jsvals.optimize);
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (make_preview),
+ NULL);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.optimize);
+
+ if (arithc_supported)
+ gtk_widget_set_sensitive (toggle, !jsvals.arithmetic_coding);
+
+ row++;
+
+ if (arithc_supported)
+ {
+ /* Arithmetic coding */
+ pg.arithmetic_coding = toggle = gtk_check_button_new_with_mnemonic
+ (_("Use arithmetic _coding"));
+ gtk_widget_set_tooltip_text
+ (toggle, _("Older software may have trouble opening "
+ "arithmetic-coded images"));
+ gtk_table_attach (GTK_TABLE (table), toggle, 0, 1,
+ row, row + 1, GTK_FILL, 0, 0, 0);
+ gtk_widget_show (toggle);
+
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (gimp_toggle_button_update),
+ &jsvals.arithmetic_coding);
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (make_preview),
+ NULL);
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (toggle_arithmetic_coding),
+ pg.optimize);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
+ jsvals.arithmetic_coding);
+
+ row++;
+ }
+
+ /* Progressive */
+ pg.progressive = toggle =
+ gtk_check_button_new_with_mnemonic (_("_Progressive"));
+ gtk_table_attach (GTK_TABLE (table), toggle, 0, 1,
+ row, row + 1, GTK_FILL, 0, 0, 0);
+ gtk_widget_show (toggle);
+
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (gimp_toggle_button_update),
+ &jsvals.progressive);
+ g_signal_connect (toggle, "toggled",
+ G_CALLBACK (make_preview),
+ NULL);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
+ jsvals.progressive);
+
+ row++;
+
+ /* Subsampling */
+ label = gtk_label_new_with_mnemonic (_("Su_bsampling:"));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach (GTK_TABLE (table), label, 2, 3, 2, 3,
+ GTK_FILL, GTK_FILL, 0, 0);
+ gtk_widget_show (label);
+
+ pg.subsmp =
+ combo = gimp_int_combo_box_new (_("4:4:4 (best quality)"),
+ JPEG_SUBSAMPLING_1x1_1x1_1x1,
+ _("4:2:2 horizontal (chroma halved)"),
+ JPEG_SUBSAMPLING_2x1_1x1_1x1,
+ _("4:2:2 vertical (chroma halved)"),
+ JPEG_SUBSAMPLING_1x2_1x1_1x1,
+ _("4:2:0 (chroma quartered)"),
+ JPEG_SUBSAMPLING_2x2_1x1_1x1,
+ NULL);
+ gtk_table_attach (GTK_TABLE (table), combo, 3, 6, 2, 3,
+ GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);
+ gtk_widget_show (combo);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
+
+ if (gimp_drawable_is_rgb (drawable_ID_global))
+ {
+ gimp_int_combo_box_connect (GIMP_INT_COMBO_BOX (combo),
+ jsvals.subsmp,
+ G_CALLBACK (subsampling_changed),
+ entry);
+ g_signal_connect (pg.subsmp, "changed",
+ G_CALLBACK (subsampling_changed2),
+ pg.use_orig_quality);
+ g_signal_connect (pg.use_orig_quality, "toggled",
+ G_CALLBACK (use_orig_qual_changed2),
+ pg.subsmp);
+ }
+ else
+ {
+ gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo),
+ JPEG_SUBSAMPLING_1x1_1x1_1x1);
+
+ gtk_widget_set_sensitive (combo, FALSE);
+ }
+
+
+ /* DCT method */
+ label = gtk_label_new_with_mnemonic (_("_DCT method:"));
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_table_attach (GTK_TABLE (table), label, 2, 3, 3, 4,
+ GTK_FILL, GTK_FILL, 0, 0);
+ gtk_widget_show (label);
+
+ pg.dct = combo = gimp_int_combo_box_new (_("Fast Integer"), 1,
+ _("Integer"), 0,
+ _("Floating-Point"), 2,
+ NULL);
+ gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), jsvals.dct);
+ gtk_table_attach (GTK_TABLE (table), combo, 3, 6, 3, 4,
+ GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);
+ gtk_widget_show (combo);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (label), combo);
+
+ g_signal_connect (combo, "changed",
+ G_CALLBACK (gimp_int_combo_box_get_active),
+ &jsvals.dct);
+ g_signal_connect (combo, "changed",
+ G_CALLBACK (make_preview),
+ NULL);
+
+ /* Load/Save defaults */
+ tabledefaults = gtk_table_new (1, 3, FALSE);
+ gtk_table_set_col_spacings (GTK_TABLE (tabledefaults), 6);
+ gtk_container_set_border_width (GTK_CONTAINER (tabledefaults), 12);
+ gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)),
+ tabledefaults, FALSE, FALSE, 0);
+ gtk_widget_show (tabledefaults);
+
+ button = gtk_button_new_with_mnemonic (_("_Load Defaults"));
+ gtk_table_attach (GTK_TABLE (tabledefaults),
+ button, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
+ gtk_widget_show (button);
+
+ g_signal_connect_swapped (button, "clicked",
+ G_CALLBACK (load_gui_defaults),
+ &pg);
+
+ button = gtk_button_new_with_mnemonic (_("Sa_ve Defaults"));
+ gtk_table_attach (GTK_TABLE (tabledefaults),
+ button, 1, 2, 1, 2, GTK_FILL, 0, 0, 0);
+ gtk_widget_show (button);
+
+ g_signal_connect_swapped (button, "clicked",
+ G_CALLBACK (save_defaults),
+ &pg);
+
+ gtk_widget_show (dialog);
+
+ make_preview ();
+
+ pg.run = FALSE;
+
+ gtk_main ();
+
+ destroy_preview ();
+
+ return pg.run;
+}
+
+static void
+save_dialog_response (GtkWidget *widget,
+ gint response_id,
+ gpointer data)
+{
+ JpegSaveGui *pg = data;
+ GtkTextIter start_iter;
+ GtkTextIter end_iter;
+
+ switch (response_id)
+ {
+ case GTK_RESPONSE_OK:
+ gtk_text_buffer_get_bounds (pg->text_buffer, &start_iter, &end_iter);
+ image_comment = gtk_text_buffer_get_text (pg->text_buffer,
+ &start_iter, &end_iter, FALSE);
+ pg->run = TRUE;
+ /* fallthrough */
+
+ default:
+ gtk_widget_destroy (widget);
+ break;
+ }
+}
+
+void
+load_defaults (void)
+{
+ jsvals.quality = DEFAULT_IJG_QUALITY;
+ jsvals.smoothing = DEFAULT_SMOOTHING;
+ jsvals.optimize = DEFAULT_OPTIMIZE;
+ jsvals.arithmetic_coding= DEFAULT_ARITHMETIC_CODING;
+ jsvals.progressive = DEFAULT_PROGRESSIVE;
+ jsvals.baseline = DEFAULT_BASELINE;
+ jsvals.subsmp = DEFAULT_SUBSMP;
+ jsvals.restart = DEFAULT_RESTART;
+ jsvals.dct = DEFAULT_DCT;
+ jsvals.preview = DEFAULT_PREVIEW;
+ jsvals.save_exif = DEFAULT_EXIF;
+ jsvals.save_xmp = DEFAULT_XMP;
+ jsvals.save_iptc = DEFAULT_IPTC;
+ jsvals.save_thumbnail = DEFAULT_THUMBNAIL;
+ jsvals.save_profile = DEFAULT_PROFILE;
+ jsvals.use_orig_quality = DEFAULT_USE_ORIG_QUALITY;
+}
+
+void
+load_parasite (void)
+{
+ GimpParasite *parasite;
+ gchar *def_str;
+ JpegSaveVals tmpvals;
+ gint num_fields;
+ gint subsampling;
+
+ parasite = gimp_get_parasite (JPEG_DEFAULTS_PARASITE);
+
+ if (! parasite)
+ return;
+
+ def_str = g_strndup (gimp_parasite_data (parasite),
+ gimp_parasite_data_size (parasite));
+
+ gimp_parasite_free (parasite);
+
+ /* Initialize tmpvals in case fewer fields exist in the parasite
+ (e.g., when importing from a previous version of GIMP). */
+ memcpy(&tmpvals, &jsvals, sizeof jsvals);
+
+ num_fields = sscanf (def_str,
+ "%lf %lf %d %d %d %d %d %d %d %d %d %d %d %d %d %d",
+ &tmpvals.quality,
+ &tmpvals.smoothing,
+ &tmpvals.optimize,
+ &tmpvals.progressive,
+ &subsampling,
+ &tmpvals.baseline,
+ &tmpvals.restart,
+ &tmpvals.dct,
+ &tmpvals.preview,
+ &tmpvals.save_exif,
+ &tmpvals.save_thumbnail,
+ &tmpvals.save_xmp,
+ &tmpvals.use_orig_quality,
+ &tmpvals.save_iptc,
+ &tmpvals.arithmetic_coding,
+ &tmpvals.save_profile);
+
+ tmpvals.subsmp = subsampling;
+
+ if (num_fields == 13 || num_fields == 15 || num_fields == 16)
+ {
+ memcpy (&jsvals, &tmpvals, sizeof (tmpvals));
+ }
+
+ g_free (def_str);
+}
+
+static void
+save_defaults (void)
+{
+ GimpParasite *parasite;
+ gchar *def_str;
+
+ def_str = g_strdup_printf ("%lf %lf %d %d %d %d %d %d %d %d %d %d %d %d %d %d",
+ jsvals.quality,
+ jsvals.smoothing,
+ jsvals.optimize,
+ jsvals.progressive,
+ (gint) jsvals.subsmp,
+ jsvals.baseline,
+ jsvals.restart,
+ jsvals.dct,
+ jsvals.preview,
+ jsvals.save_exif,
+ jsvals.save_thumbnail,
+ jsvals.save_xmp,
+ jsvals.use_orig_quality,
+ jsvals.save_iptc,
+ jsvals.arithmetic_coding,
+ jsvals.save_profile);
+ parasite = gimp_parasite_new (JPEG_DEFAULTS_PARASITE,
+ GIMP_PARASITE_PERSISTENT,
+ strlen (def_str), def_str);
+
+ gimp_attach_parasite (parasite);
+
+ gimp_parasite_free (parasite);
+ g_free (def_str);
+}
+
+static void
+load_gui_defaults (JpegSaveGui *pg)
+{
+ GtkAdjustment *restart_markers;
+
+ load_defaults ();
+ load_parasite ();
+
+#define SET_ACTIVE_BTTN(field) \
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (pg->field), jsvals.field)
+
+ SET_ACTIVE_BTTN (optimize);
+ SET_ACTIVE_BTTN (progressive);
+ SET_ACTIVE_BTTN (use_orig_quality);
+ SET_ACTIVE_BTTN (preview);
+ SET_ACTIVE_BTTN (save_exif);
+ SET_ACTIVE_BTTN (save_xmp);
+ SET_ACTIVE_BTTN (save_iptc);
+ SET_ACTIVE_BTTN (save_thumbnail);
+ SET_ACTIVE_BTTN (save_profile);
+
+#undef SET_ACTIVE_BTTN
+
+/*spin button stuff*/
+ g_signal_handler_block (pg->use_restart_markers, pg->handler_id_restart);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (pg->use_restart_markers),
+ jsvals.restart);
+ restart_markers = GTK_ADJUSTMENT (pg->scale_data);
+ gtk_adjustment_set_value (restart_markers, jsvals.restart);
+ g_signal_handler_unblock (pg->use_restart_markers, pg->handler_id_restart);
+
+ gtk_adjustment_set_value (GTK_ADJUSTMENT (pg->smoothing),
+ jsvals.smoothing);
+
+ /* Don't override quality and subsampling setting if we already set it from original */
+ if (!jsvals.use_orig_quality)
+ {
+ gtk_adjustment_set_value (GTK_ADJUSTMENT (pg->quality),
+ jsvals.quality);
+
+ if (gimp_drawable_is_rgb (drawable_ID_global))
+ {
+ gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (pg->subsmp),
+ jsvals.subsmp);
+ }
+ }
+
+ gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (pg->dct),
+ jsvals.dct);
+}
+
+static void
+save_restart_update (GtkAdjustment *adjustment,
+ GtkWidget *toggle)
+{
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle)))
+ jsvals.restart = gtk_adjustment_get_value (adjustment);
+ else
+ jsvals.restart = 0;
+
+ gtk_widget_set_sensitive (restart_markers_label, jsvals.restart);
+ gtk_widget_set_sensitive (restart_markers_scale, jsvals.restart);
+
+ make_preview ();
+}
+
+static void
+subsampling_changed (GtkWidget *combo,
+ GtkAdjustment *entry)
+{
+ gint value;
+
+ gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (combo), &value);
+
+ jsvals.subsmp = value;
+
+ /* smoothing is not supported with nonstandard sampling ratios */
+ gimp_scale_entry_set_sensitive ((gpointer) entry,
+ jsvals.subsmp != JPEG_SUBSAMPLING_2x1_1x1_1x1 &&
+ jsvals.subsmp != JPEG_SUBSAMPLING_1x2_1x1_1x1);
+
+ make_preview ();
+}
+
+static void
+quality_changed (GtkAdjustment *scale_entry,
+ GtkWidget *toggle)
+{
+ if (jsvals.use_orig_quality)
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), FALSE);
+ }
+}
+
+static void
+subsampling_changed2 (GtkWidget *combo,
+ GtkWidget *toggle)
+{
+ if (jsvals.use_orig_quality && orig_subsmp != jsvals.subsmp)
+ {
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), FALSE);
+ }
+}
+
+static void
+use_orig_qual_changed (GtkWidget *toggle,
+ GtkAdjustment *scale_entry)
+{
+ if (jsvals.use_orig_quality && orig_quality > 0)
+ {
+ g_signal_handlers_block_by_func (scale_entry, quality_changed, toggle);
+ gtk_adjustment_set_value (scale_entry, orig_quality);
+ g_signal_handlers_unblock_by_func (scale_entry, quality_changed, toggle);
+ }
+}
+
+static void
+use_orig_qual_changed2 (GtkWidget *toggle,
+ GtkWidget *combo)
+{
+ /* the test is (orig_quality > 0), not (orig_subsmp > 0) - this is normal */
+ if (jsvals.use_orig_quality && orig_quality > 0)
+ {
+ gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), orig_subsmp);
+ }
+}