/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * 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 . */ #include "config.h" #include #include #include #include #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include #include #include "libpika/stdplugins-intl.h" #include "jpeg.h" #include "jpeg-icc.h" #include "jpeg-load.h" #include "jpeg-save.h" #include "jpeg-settings.h" /* See bugs #63610 and #61088 for a discussion about the quality settings */ #define DEFAULT_RESTART_MCU_ROWS 16 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; GFile *file; gboolean abort_me; guint source_id; } PreviewPersistent; static void make_preview (PikaProcedureConfig *config); static void quality_changed (PikaProcedureConfig *config); static void subsampling_changed (PikaProcedureConfig *config, const GParamSpec *pspec, GtkWidget *smoothing_scale); static void use_orig_qual_changed (PikaProcedureConfig *config); static void use_orig_qual_changed_rgb (PikaProcedureConfig *config); static GtkWidget *preview_size = NULL; static PreviewPersistent *prev_p = NULL; /* * 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) { GFileInfo *info; gchar *text; GError *error = NULL; info = g_file_query_info (pp->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); /* and load the preview */ load_image (pp->file, PIKA_RUN_NONINTERACTIVE, TRUE, NULL, NULL, NULL); } /* we cleanup here (load_image doesn't run in the background) */ g_file_delete (pp->file, NULL, NULL); g_object_unref (pp->file); g_free (pp); prev_p = NULL; pika_displays_flush (); gdk_display_flush (gdk_display_get_default ()); 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 (GFile *file, PikaProcedureConfig *config, PikaImage *image, PikaDrawable *drawable, PikaImage *orig_image, gboolean preview, GError **error) { static struct jpeg_compress_struct cinfo; static struct my_error_mgr jerr; PikaImageType drawable_type; GeglBuffer *buffer; const gchar *encoding; const Babl *format; const Babl *space; JpegSubsampling subsampling; FILE * volatile outfile; guchar *data; guchar *src; PikaColorProfile *profile = NULL; PikaColorProfile *cmyk_profile = NULL; gboolean has_alpha; gboolean out_linear = FALSE; gint rowstride, yend; gint quality; gdouble dquality = 1.0; gdouble smoothing; gboolean optimize; gboolean progressive; gboolean cmyk; gint subsmp; gboolean baseline; gint restart; gint dct; gboolean save_profile = TRUE; gboolean save_comment; gboolean use_orig_quality = FALSE; gint orig_num_quant_tables = -1; gboolean use_arithmetic_coding = FALSE; gboolean use_restart = FALSE; gchar *comment; g_object_get (config, "quality", &dquality, "smoothing", &smoothing, "optimize", &optimize, "progressive", &progressive, "cmyk", &cmyk, "sub-sampling", &subsmp, "baseline", &baseline, "restart", &restart, "dct", &dct, /* Original quality settings. */ "use-original-quality", &use_orig_quality, "original-num-quant-tables", &orig_num_quant_tables, "use-arithmetic-coding", &use_arithmetic_coding, "use-restart", &use_restart, "save-color-profile", &save_profile, "save-comment", &save_comment, "pika-comment", &comment, NULL); quality = (gint) (dquality * 100.0 + 0.5); drawable_type = pika_drawable_type (drawable); buffer = pika_drawable_get_buffer (drawable); space = pika_drawable_get_format (drawable); if (! preview) pika_progress_init_printf (_("Exporting '%s'"), pika_file_get_utf8_name (file)); /* 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. */ outfile = g_fopen (g_file_peek_path (file), "wb"); if (! outfile) { g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), _("Could not open '%s' for writing: %s"), pika_file_get_utf8_name (file), 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 (save_profile) { profile = pika_image_get_color_profile (orig_image); /* If a profile is explicitly set, follow its TRC, whatever the * storage format. */ if (profile && pika_color_profile_is_linear (profile)) out_linear = TRUE; if (! profile) { /* There is always an effective profile. */ profile = pika_image_get_effective_color_profile (orig_image); if (pika_color_profile_is_linear (profile)) { if (pika_image_get_precision (image) != PIKA_PRECISION_U8_LINEAR) { PikaColorProfile *saved_profile; saved_profile = pika_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; } } } space = pika_color_profile_get_space (profile, pika_image_get_simulation_intent (image), error); if (error && *error) { /* XXX: the profile space should normally be the same one as * the drawable's so let's continue with it. We were mostly * getting the profile space to be complete. Still let's * display the error to standard error channel because if the * space could not be extracted, there is a problem somewhere! */ g_printerr ("%s: error getting the profile space: %s", G_STRFUNC, (*error)->message); g_clear_error (error); space = pika_drawable_get_format (drawable); } } jpeg_stdio_dest (&cinfo, outfile); /* Get the input image and a pointer to its data. */ switch (drawable_type) { case PIKA_RGB_IMAGE: /* # of color components per pixel */ cinfo.input_components = 3; has_alpha = FALSE; if (out_linear) encoding = "RGB u8"; else encoding = "R'G'B' u8"; break; case PIKA_GRAY_IMAGE: /* # of color components per pixel */ cinfo.input_components = 1; has_alpha = FALSE; if (out_linear) encoding = "Y u8"; else encoding = "Y' u8"; break; case PIKA_RGBA_IMAGE: /* # of color components per pixel (minus the PIKA alpha channel) */ cinfo.input_components = 4 - 1; has_alpha = TRUE; if (out_linear) encoding = "RGB u8"; else encoding = "R'G'B' u8"; break; case PIKA_GRAYA_IMAGE: /* # of color components per pixel (minus the PIKA alpha channel) */ cinfo.input_components = 2 - 1; has_alpha = TRUE; if (out_linear) encoding = "Y u8"; else encoding = "Y' u8"; break; case PIKA_INDEXED_IMAGE: default: return FALSE; } if (cmyk) { if (save_profile) { GError *err = NULL; cmyk_profile = pika_image_get_simulation_profile (image); if (! cmyk_profile && err) g_printerr ("%s: no soft-proof profile: %s\n", G_STRFUNC, err->message); if (cmyk_profile && ! pika_color_profile_is_cmyk (cmyk_profile)) g_clear_object (&cmyk_profile); g_clear_error (&err); } /* As far as I know, without access to JPEG specifications, we * should encode as proper "CMYK" encoding scheme. But every other * program where I test this JPEG, the color are inverted, so I * use the "cmyk" encoding where 0.0 is full ink coverage vs. 1.0 * being no ink. * libjpeg-turbo says that Photoshop is wrongly inverting the data * in JPEG files in a 1994 commit! We might imagine that since * then it became the de-facto encoding? * See: https://github.com/libjpeg-turbo/libjpeg-turbo/blob/dfc63d42ee3d1ae8eacb921e89e64ac57861dff6/libjpeg.txt#L1425-L1438 */ encoding = "cmyk u8"; if (cmyk_profile) space = pika_color_profile_get_space (cmyk_profile, PIKA_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC, error); else /* The NULL space will fallback to a naive CMYK conversion. */ space = NULL; } format = babl_format_with_space (encoding, space); /* 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 */ if (cmyk) { cinfo.input_components = 4; cinfo.in_color_space = JCS_CMYK; cinfo.jpeg_color_space = JCS_CMYK; } else { cinfo.in_color_space = (drawable_type == PIKA_RGB_IMAGE || drawable_type == PIKA_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); if (cmyk_profile) jpeg_set_colorspace (&cinfo, JCS_CMYK); jpeg_set_quality (&cinfo, quality, baseline); if (use_orig_quality && orig_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, orig_num_quant_tables); if (quant_tables) { for (t = 0; t < orig_num_quant_tables; t++) { jpeg_add_quant_table (&cinfo, t, quant_tables[t], 100, baseline); g_free (quant_tables[t]); } g_free (quant_tables); } } #ifdef C_ARITH_CODING_SUPPORTED cinfo.arith_code = use_arithmetic_coding; if (! use_arithmetic_coding) cinfo.optimize_coding = optimize; #else cinfo.optimize_coding = optimize; #endif subsampling = (pika_drawable_is_rgb (drawable) ? 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) (smoothing * 100); } if (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 = use_restart ? restart : 0; switch (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; pika_image_get_resolution (orig_image, &xresolution, &yresolution); if (xresolution > 1e-5 && yresolution > 1e-5) { gdouble factor; factor = pika_unit_get_factor (pika_image_get_unit (orig_image)); 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 (save_comment && comment && *comment) { #ifdef PIKA_UNSTABLE g_print ("jpeg-save: saving image comment (%d bytes)\n", (int) strlen (comment)); #endif jpeg_write_marker (&cinfo, JPEG_COM, (guchar *) comment, strlen (comment)); } /* Step 4.2: store the color profile */ if (save_profile && /* XXX Only case when we don't save a profile even though the * option was requested is if we store as CMYK without setting a * profile. It would actually be better to generate a profile * corresponding to the "naive" CMYK space we use in such case. * But it doesn't look like babl can do this yet. */ (! cmyk || cmyk_profile != NULL)) { const guint8 *icc_data; gsize icc_length; icc_data = pika_color_profile_get_icc_profile (cmyk_profile ? cmyk_profile : profile, &icc_length); jpeg_icc_write_profile (&cinfo, icc_data, icc_length); } g_clear_object (&profile); g_clear_object (&cmyk_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 * pika_tile_height ()); /* fault if cinfo.next_scanline isn't initially a multiple of * pika_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 = pika_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 = g_object_ref (file); 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 % pika_tile_height ()) == 0) { yend = cinfo.next_scanline + pika_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) pika_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! */ pika_progress_update (1.0); g_object_unref (buffer); return TRUE; } static void make_preview (PikaProcedureConfig *config) { gboolean show_preview; destroy_preview (); g_object_get (config, "show-preview", &show_preview, NULL); if (show_preview) { GFile *file = pika_temp_file ("jpeg"); if (! undo_touched) { /* we freeze undo saving so that we can avoid sucking up * tile cache with our unneeded preview steps. */ pika_image_undo_freeze (preview_image); undo_touched = TRUE; } save_image (file, config, preview_image, drawable_global, orig_image_global, TRUE, NULL); g_object_unref (file); if (separate_display && ! display) display = pika_display_new (preview_image); } else { gtk_label_set_text (GTK_LABEL (preview_size), _("File size: unknown")); pika_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 (pika_image_is_valid (preview_image) && pika_item_is_valid (PIKA_ITEM (preview_layer))) { /* assuming that reference counting is working correctly, we do * not need to delete the layer, removing it from the image * should be sufficient */ pika_image_remove_layer (preview_image, preview_layer); preview_layer = NULL; } } gboolean save_dialog (PikaProcedure *procedure, PikaProcedureConfig *config, PikaDrawable *drawable, PikaImage *image) { GtkWidget *dialog; GtkWidget *widget; GtkWidget *profile_label; GtkListStore *store; PikaColorProfile *cmyk_profile = NULL; gint orig_quality; gint restart; gboolean run; g_object_get (config, "original-quality", &orig_quality, "restart", &restart, NULL); dialog = pika_save_procedure_dialog_new (PIKA_SAVE_PROCEDURE (procedure), PIKA_PROCEDURE_CONFIG (config), pika_item_get_image (PIKA_ITEM (drawable))); /* custom quantization tables - now used also for original quality */ pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog), "use-original-quality", (orig_quality > 0), NULL, NULL, FALSE); /* Quality as a PikaScaleEntry. */ pika_procedure_dialog_get_spin_scale (PIKA_PROCEDURE_DIALOG (dialog), "quality", 100.0); /* changing quality disables custom quantization tables, and vice-versa */ g_signal_connect (config, "notify::quality", G_CALLBACK (quality_changed), NULL); g_signal_connect (config, "notify::use-original-quality", G_CALLBACK (use_orig_qual_changed), NULL); /* File size label. */ preview_size = pika_procedure_dialog_get_label (PIKA_PROCEDURE_DIALOG (dialog), "preview-size", _("File size: unknown")); gtk_label_set_xalign (GTK_LABEL (preview_size), 0.0); gtk_label_set_ellipsize (GTK_LABEL (preview_size), PANGO_ELLIPSIZE_END); pika_label_set_attributes (GTK_LABEL (preview_size), PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC, -1); pika_help_set_help_data (preview_size, _("Enable preview to obtain the file size."), NULL); /* Profile label. */ profile_label = pika_procedure_dialog_get_label (PIKA_PROCEDURE_DIALOG (dialog), "profile-label", _("No soft-proofing profile")); gtk_label_set_xalign (GTK_LABEL (profile_label), 0.0); gtk_label_set_ellipsize (GTK_LABEL (profile_label), PANGO_ELLIPSIZE_END); pika_label_set_attributes (GTK_LABEL (profile_label), PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC, -1); pika_help_set_help_data (profile_label, _("Name of the color profile used for CMYK export."), NULL); pika_procedure_dialog_fill_frame (PIKA_PROCEDURE_DIALOG (dialog), "cmyk-frame", "cmyk", FALSE, "profile-label"); cmyk_profile = pika_image_get_simulation_profile (image); if (cmyk_profile) { if (pika_color_profile_is_cmyk (cmyk_profile)) { gchar *label_text; label_text = g_strdup_printf (_("Profile: %s"), pika_color_profile_get_label (cmyk_profile)); gtk_label_set_text (GTK_LABEL (profile_label), label_text); pika_label_set_attributes (GTK_LABEL (profile_label), PANGO_ATTR_STYLE, PANGO_STYLE_NORMAL, -1); g_free (label_text); } g_object_unref (cmyk_profile); } #ifdef C_ARITH_CODING_SUPPORTED pika_procedure_dialog_fill_frame (PIKA_PROCEDURE_DIALOG (dialog), "arithmetic-frame", "use-arithmetic-coding", TRUE, "optimize"); #endif /* Restart marker. */ /* TODO: apparently when toggle is unchecked, we want to show the * scale as 0. */ pika_procedure_dialog_fill_frame (PIKA_PROCEDURE_DIALOG (dialog), "restart-frame", "use-restart", FALSE, "restart"); if (restart == 0) g_object_set (config, "restart", DEFAULT_RESTART_MCU_ROWS, "use-restart", FALSE, NULL); /* Subsampling */ store = pika_int_store_new (_("4:4:4 (best quality)"), JPEG_SUBSAMPLING_1x1_1x1_1x1, _("4:2:2 (chroma halved horizontally)"), JPEG_SUBSAMPLING_2x1_1x1_1x1, _("4:4:0 (chroma halved vertically)"), JPEG_SUBSAMPLING_1x2_1x1_1x1, _("4:2:0 (chroma quartered)"), JPEG_SUBSAMPLING_2x2_1x1_1x1, NULL); widget = pika_procedure_dialog_get_int_combo (PIKA_PROCEDURE_DIALOG (dialog), "sub-sampling", PIKA_INT_STORE (store)); widget = pika_label_int_widget_get_widget (PIKA_LABEL_INT_WIDGET (widget)); if (! pika_drawable_is_rgb (drawable)) { g_object_set (config, "sub-sampling", JPEG_SUBSAMPLING_1x1_1x1_1x1, NULL); gtk_widget_set_sensitive (widget, FALSE); } /* DCT method */ store = pika_int_store_new (_("Fast Integer"), 1, _("Integer"), 0, _("Floating-Point"), 2, NULL); pika_procedure_dialog_get_int_combo (PIKA_PROCEDURE_DIALOG (dialog), "dct", PIKA_INT_STORE (store)); pika_procedure_dialog_get_label (PIKA_PROCEDURE_DIALOG (dialog), "advanced-title", _("Advanced Options")); widget = pika_procedure_dialog_get_widget (PIKA_PROCEDURE_DIALOG (dialog), "smoothing", PIKA_TYPE_SPIN_SCALE); pika_help_set_help_data (widget, NULL, "file-jpeg-save-smoothing"); /* Add some logics for "Use original quality". */ if (pika_drawable_is_rgb (drawable)) { g_signal_connect (config, "notify::sub-sampling", G_CALLBACK (subsampling_changed), widget); subsampling_changed (config, NULL, widget); g_signal_connect (config, "notify::use-original-quality", G_CALLBACK (use_orig_qual_changed_rgb), NULL); } pika_procedure_dialog_fill_box (PIKA_PROCEDURE_DIALOG (dialog), "advanced-options", "smoothing", "progressive", "cmyk-frame", #ifdef C_ARITH_CODING_SUPPORTED "arithmetic-frame", #else "optimize", #endif "restart-frame", "sub-sampling", "dct", NULL); pika_procedure_dialog_fill_frame (PIKA_PROCEDURE_DIALOG (dialog), "advanced-frame", "advanced-title", FALSE, "advanced-options"); pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog), "quality", "use-original-quality", "preview-size", "show-preview", "advanced-frame", NULL); gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); /* Run make_preview() when various config are changed. */ g_signal_connect (config, "notify", G_CALLBACK (make_preview), NULL); make_preview (config); run = pika_procedure_dialog_run (PIKA_PROCEDURE_DIALOG (dialog)); gtk_widget_destroy (dialog); destroy_preview (); return run; } static void quality_changed (PikaProcedureConfig *config) { gboolean use_orig_quality; gdouble quality; gint orig_quality; g_object_get (config, "use-original-quality", &use_orig_quality, "original-quality", &orig_quality, "quality", &quality, NULL); if (use_orig_quality && (gint) (quality * 100.0) != orig_quality) g_object_set (config, "use-original-quality", FALSE, NULL); } static void subsampling_changed (PikaProcedureConfig *config, const GParamSpec *pspec, GtkWidget *smoothing_scale) { gboolean use_orig_quality; gint orig_subsmp; gint subsmp; g_object_get (config, "use-original-quality", &use_orig_quality, "original-sub-sampling", &orig_subsmp, "sub-sampling", &subsmp, NULL); /* smoothing is not supported with nonstandard sampling ratios */ gtk_widget_set_sensitive (smoothing_scale, subsmp != JPEG_SUBSAMPLING_2x1_1x1_1x1 && subsmp != JPEG_SUBSAMPLING_1x2_1x1_1x1); if (use_orig_quality && orig_subsmp != subsmp) g_object_set (config, "use-original-quality", FALSE, NULL); } static void use_orig_qual_changed (PikaProcedureConfig *config) { gboolean use_orig_quality; gint orig_quality; g_object_get (config, "use-original-quality", &use_orig_quality, "original-quality", &orig_quality, NULL); if (use_orig_quality && orig_quality > 0) { g_signal_handlers_block_by_func (config, quality_changed, NULL); g_object_set (config, "quality", orig_quality / 100.0, NULL); g_signal_handlers_unblock_by_func (config, quality_changed, NULL); } } static void use_orig_qual_changed_rgb (PikaProcedureConfig *config) { gboolean use_orig_quality; gint orig_quality; gint orig_subsmp; g_object_get (config, "use-original-quality", &use_orig_quality, "original-sub-sampling", &orig_subsmp, "original-quality", &orig_quality, NULL); /* the test is (orig_quality > 0), not (orig_subsmp > 0) - this is normal */ if (use_orig_quality && orig_quality > 0) g_object_set (config, "sub-sampling", orig_subsmp, NULL); }