plug-ins/jpeg/jpeg.[ch] in the JPEG save dialog, removed the option "Force

2007-08-10  Raphael Quinet  <raphael@gimp.org>

	* plug-ins/jpeg/jpeg.[ch]
	* plug-ins/jpeg/jpeg-save.[ch]: in the JPEG save dialog, removed
	the option "Force baseline JPEG" and added the new option "Use
	custom quantization tables" to allow resaving a JPEG file with the
	same compression settings as the original file. This is useful if
	the quantization tables are different from those that can be
	created by the IJG JPEG library.

	* plug-ins/jpeg/jpeg-settings.[ch]: updated comments, changed the
	return type of jpeg_restore_original_tables() because the IJG JPEG
	library expects unsigned integers.

svn path=/trunk/; revision=23197
This commit is contained in:
Raphael Quinet 2007-08-10 21:45:38 +00:00 committed by Raphaël Quinet
parent bec32ed865
commit 89f5bb46c1
7 changed files with 177 additions and 93 deletions

View File

@ -1,3 +1,17 @@
2007-08-10 Raphaël Quinet <raphael@gimp.org>
* plug-ins/jpeg/jpeg.[ch]
* plug-ins/jpeg/jpeg-save.[ch]: in the JPEG save dialog, removed
the option "Force baseline JPEG" and added the new option "Use
custom quantization tables" to allow resaving a JPEG file with the
same compression settings as the original file. This is useful if
the quantization tables are different from those that can be
created by the IJG JPEG library.
* plug-ins/jpeg/jpeg-settings.[ch]: updated comments, changed the
return type of jpeg_restore_original_tables() because the IJG JPEG
library expects unsigned integers.
2007-08-10 Sven Neumann <sven@gimp.org>
* libgimpwidgets/gimpnumberpairentry.c: store separators as

View File

@ -44,6 +44,7 @@
#include "libgimp/stdplugins-intl.h"
#include "jpeg.h"
#include "jpeg-settings.h"
#include "jpeg-icc.h"
#include "jpeg-save.h"
@ -63,6 +64,7 @@
#define DEFAULT_EXIF TRUE
#define DEFAULT_THUMBNAIL FALSE
#define DEFAULT_XMP TRUE
#define DEFAULT_USE_QUANT_TABLES FALSE
#define JPEG_DEFAULTS_PARASITE "jpeg-save-defaults"
@ -98,13 +100,13 @@ typedef struct
GtkWidget *optimize; /*optimize togle*/
GtkWidget *progressive; /*progressive toggle*/
GtkWidget *subsmp; /*subsampling side select*/
GtkWidget *baseline; /*baseling toggle*/
GtkWidget *restart; /*spinner for setting frequency restart markers*/
GtkWidget *dct; /*DCT side select*/
GtkWidget *preview; /*show preview toggle checkbox*/
GtkWidget *save_exif;
GtkWidget *save_thumbnail;
GtkWidget *save_xmp;
GtkWidget *use_quant_tables; /*quant tables toggle*/
} JpegSaveGui;
static void make_preview (void);
@ -113,6 +115,10 @@ static void save_restart_update (GtkAdjustment *adjustment,
GtkWidget *toggle);
static void subsampling_changed (GtkWidget *combo,
GtkObject *entry);
static void quality_changed (GtkObject *scale_entry,
GtkWidget *toggle);
static void use_quant_changed (GtkWidget *toggle,
GtkObject *scale_entry);
#ifdef HAVE_EXIF
@ -365,10 +371,30 @@ save_image (const gchar *filename,
jpeg_set_defaults (&cinfo);
jpeg_set_quality (&cinfo, (gint) (jsvals.quality + 0.5), jsvals.baseline);
if (jsvals.use_quant_tables && 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);
}
}
cinfo.optimize_coding = jsvals.optimize;
/* smoothing is not supported with nonstandard sampling ratios */
if (jsvals.subsmp != 1)
if (jsvals.subsmp != 1 && jsvals.subsmp != 3)
cinfo.smoothing_factor = (gint) (jsvals.smoothing * 100);
if (jsvals.progressive)
@ -963,24 +989,9 @@ save_dialog (void)
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
jsvals.progressive);
pg.baseline = toggle =
gtk_check_button_new_with_label (_("Force baseline JPEG"));
gtk_table_attach (GTK_TABLE (table), toggle, 0, 1, 2, 3, GTK_FILL, 0, 0, 0);
gtk_widget_show (toggle);
g_signal_connect (toggle, "toggled",
G_CALLBACK (gimp_toggle_button_update),
&jsvals.baseline);
g_signal_connect (toggle, "toggled",
G_CALLBACK (make_preview),
NULL);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
jsvals.baseline);
#ifdef HAVE_EXIF
pg.save_exif = toggle = gtk_check_button_new_with_label (_("Save EXIF data"));
gtk_table_attach (GTK_TABLE (table), toggle, 0, 1, 3, 4, GTK_FILL, 0, 0, 0);
gtk_table_attach (GTK_TABLE (table), toggle, 0, 1, 2, 3, GTK_FILL, 0, 0, 0);
gtk_widget_show (toggle);
g_signal_connect (toggle, "toggled",
@ -997,7 +1008,7 @@ save_dialog (void)
pg.save_thumbnail = toggle =
gtk_check_button_new_with_label (_("Save thumbnail"));
gtk_table_attach (GTK_TABLE (table), toggle, 0, 1, 4, 5, GTK_FILL, 0, 0, 0);
gtk_table_attach (GTK_TABLE (table), toggle, 0, 1, 3, 4, GTK_FILL, 0, 0, 0);
gtk_widget_show (toggle);
g_signal_connect (toggle, "toggled",
@ -1013,7 +1024,7 @@ save_dialog (void)
/* XMP metadata */
pg.save_xmp = toggle = gtk_check_button_new_with_label (_("Save XMP data"));
gtk_table_attach (GTK_TABLE (table), toggle, 0, 1, 5, 6, GTK_FILL, 0, 0, 0);
gtk_table_attach (GTK_TABLE (table), toggle, 0, 1, 4, 5, GTK_FILL, 0, 0, 0);
gtk_widget_show (toggle);
g_signal_connect (toggle, "toggled",
@ -1025,6 +1036,29 @@ save_dialog (void)
gtk_widget_set_sensitive (toggle, has_metadata);
/* custom quantization tables */
pg.use_quant_tables = toggle =
gtk_check_button_new_with_label (_("Use custom quantization tables"));
gtk_table_attach (GTK_TABLE (table), toggle, 0, 2, 5, 6, GTK_FILL, 0, 0, 0);
gtk_widget_show (toggle);
g_signal_connect (toggle, "toggled",
G_CALLBACK (gimp_toggle_button_update),
&jsvals.use_quant_tables);
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle),
jsvals.use_quant_tables
&& (num_quant_tables > 0));
gtk_widget_set_sensitive (toggle, (num_quant_tables > 0));
/* changing quality disables custom quantization tables, and vice-versa */
g_signal_connect (pg.quality, "value-changed",
G_CALLBACK (quality_changed),
pg.use_quant_tables);
g_signal_connect (pg.use_quant_tables, "toggled",
G_CALLBACK (use_quant_changed),
pg.quality);
/* Subsampling */
label = gtk_label_new (_("Subsampling:"));
gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
@ -1186,6 +1220,7 @@ load_save_defaults (void)
jsvals.save_exif = DEFAULT_EXIF;
jsvals.save_thumbnail = DEFAULT_THUMBNAIL;
jsvals.save_xmp = DEFAULT_XMP;
jsvals.use_quant_tables = DEFAULT_USE_QUANT_TABLES;
#ifdef HAVE_EXIF
if (exif_data && (exif_data->data))
@ -1261,7 +1296,7 @@ load_gui_defaults (JpegSaveGui *pg)
SET_ACTIVE_BTTN (optimize);
SET_ACTIVE_BTTN (progressive);
SET_ACTIVE_BTTN (baseline);
SET_ACTIVE_BTTN (use_quant_tables);
SET_ACTIVE_BTTN (preview);
#ifdef HAVE_EXIF
SET_ACTIVE_BTTN (save_exif);
@ -1289,7 +1324,6 @@ load_gui_defaults (JpegSaveGui *pg)
jsvals.subsmp);
gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (pg->dct),
jsvals.dct);
}
static void
@ -1311,11 +1345,34 @@ subsampling_changed (GtkWidget *combo,
gimp_int_combo_box_get_active (GIMP_INT_COMBO_BOX (combo), &jsvals.subsmp);
/* smoothing is not supported with nonstandard sampling ratios */
gimp_scale_entry_set_sensitive (entry, jsvals.subsmp != 1);
gimp_scale_entry_set_sensitive (entry,
jsvals.subsmp != 1 && jsvals.subsmp != 3);
make_preview ();
}
static void
quality_changed (GtkObject *scale_entry,
GtkWidget *toggle)
{
if (jsvals.use_quant_tables)
{
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), FALSE);
}
}
static void
use_quant_changed (GtkWidget *toggle,
GtkObject *scale_entry)
{
if (jsvals.use_quant_tables && orig_quality > 0)
{
g_signal_handlers_block_by_func (scale_entry, quality_changed, toggle);
gtk_adjustment_set_value (GTK_ADJUSTMENT (scale_entry), orig_quality);
g_signal_handlers_unblock_by_func (scale_entry, quality_changed, toggle);
}
}
#ifdef HAVE_EXIF
static guchar *tbuffer = NULL;

View File

@ -30,6 +30,7 @@ typedef struct
gboolean save_exif;
gboolean save_thumbnail;
gboolean save_xmp;
gboolean use_quant_tables;
} JpegSaveVals;
extern JpegSaveVals jsvals;

View File

@ -22,11 +22,11 @@
/*
* Structure of the "jpeg-settings" parasite:
* 1 byte - JPEG color space (JCS_YCbCr, JCS_GRAYSCALE, JCS_CMYK, ...)
* 1 byte - quality (according to the IJG scale, 0..100)
* 1 byte - quality (1..100 according to the IJG scale, or 0)
* 1 byte - number of components (0..4)
* 1 byte - number of quantization tables (0..4)
* C * 2 bytes - sampling factors for each component (1..4)
* T * 128 bytes - quantization tables (if different from IJG tables)
* T * 128 bytes - quantization tables (only if different from IJG tables)
*
* Additional data following the quantization tables is currently
* ignored and can be used for future extensions.
@ -157,7 +157,7 @@ jpeg_detect_original_settings (struct jpeg_decompress_struct *cinfo,
* and number of quantization tables) from the parasite attached to
* @image_ID. If the number of quantization tables is greater than
* zero, then these tables can be retrieved from the parasite by
* calling jpeg_get_original_tables().
* calling jpeg_restore_original_tables().
*
* Return Value: TRUE if a valid parasite was attached to the image
*/
@ -250,14 +250,17 @@ jpeg_restore_original_settings (gint32 image_ID,
* can be associated with a component of a JPEG image when saving it.
*
* An array of newly allocated tables is returned if @num_quant_tables
* matches the number of tables saved in the parasite. When these
* tables are not needed anymore, the caller should free them using
* g_free(). If no parasite exists or if it cannot be used, this
* function returns NULL.
* matches the number of tables saved in the parasite. These tables
* are returned as arrays of unsigned integers even if they will never
* use more than 16 bits (8 bits in most cases) because the IJG JPEG
* library expects arrays of unsigned integers. When these tables are
* not needed anymore, the caller should free them using g_free(). If
* no parasite exists or if it cannot be used, this function returns
* NULL.
*
* Return Value: an array of quantization tables, or NULL.
*/
guint16 **
guint **
jpeg_restore_original_tables (gint32 image_ID,
gint num_quant_tables)
{
@ -266,12 +269,12 @@ jpeg_restore_original_tables (gint32 image_ID,
glong src_size;
gint num_components;
gint num_tables;
guint16 **quant_tables;
guint **quant_tables;
gint t;
gint i;
parasite = gimp_image_parasite_find (image_ID, "jpeg-settings");
if (! parasite)
if (parasite)
{
src_size = gimp_parasite_data_size (parasite);
if (src_size >= 4)
@ -285,14 +288,14 @@ jpeg_restore_original_tables (gint32 image_ID,
&& num_tables == num_quant_tables)
{
src += num_components * 2;
quant_tables = g_new (guint16 *, num_tables);
quant_tables = g_new (guint *, num_tables);
for (t = 0; t < num_tables; t++)
{
quant_tables[t] = g_new (guint16, 128);
for (i = 0; i < 128; i++)
quant_tables[t] = g_new (guint, 128);
for (i = 0; i < 64; i++)
{
guint16 c;
guint c;
c = *src++ * 256;
c += *src++;

View File

@ -27,5 +27,5 @@ gboolean jpeg_restore_original_settings (gint32 image_ID,
gint *subsmp,
gint *num_quant_tables);
guint16 **jpeg_restore_original_tables (gint32 image_ID,
guint **jpeg_restore_original_tables (gint32 image_ID,
gint num_quant_tables);

View File

@ -59,6 +59,10 @@ JpegSaveVals jsvals;
gint32 orig_image_ID_global;
gint32 drawable_ID_global;
gboolean has_metadata;
gint orig_quality;
gint orig_subsmp;
gint num_quant_tables;
#ifdef HAVE_EXIF
ExifData *exif_data = NULL;
@ -208,6 +212,9 @@ run (const gchar *name,
preview_layer_ID = -1;
has_metadata = FALSE;
orig_quality = 0;
orig_subsmp = 0;
num_quant_tables = 0;
if (strcmp (name, LOAD_PROC) == 0)
{
@ -389,6 +396,11 @@ run (const gchar *name,
/* Possibly retrieve data */
gimp_get_data (SAVE_PROC, &jsvals);
jpeg_restore_original_settings (orig_image_ID,
&orig_quality,
&orig_subsmp,
&num_quant_tables);
/* load up the previously used values */
parasite = gimp_image_parasite_find (orig_image_ID,
"jpeg-save-options");
@ -408,37 +420,31 @@ run (const gchar *name,
jsvals.save_exif = save_vals->save_exif;
jsvals.save_thumbnail = save_vals->save_thumbnail;
jsvals.save_xmp = save_vals->save_xmp;
jsvals.use_quant_tables = save_vals->use_quant_tables;
gimp_parasite_free (parasite);
}
else
{
gint orig_quality;
gint orig_subsmp;
gint orig_quant_tables;
/* We are called with GIMP_RUN_WITH_LAST_VALS but this image
* doesn't have a "jpeg-save-options" parasite. It's better
* to prompt the user with a dialog now so that she has control
* over the JPEG encoding parameters.
*/
run_mode = GIMP_RUN_INTERACTIVE;
/* If this image was loaded from a JPEG file (and has not been
* saved yet), try to use some of the settings from the
/* If this image was loaded from a JPEG file and has not been
* saved yet, try to use some of the settings from the
* original file if they are better than the default values.
*/
if (jpeg_restore_original_settings (image_ID,
&orig_quality,
&orig_subsmp,
&orig_quant_tables))
{
if (orig_quality > jsvals.quality)
{
jsvals.quality = orig_quality;
jsvals.use_quant_tables = TRUE;
}
if (orig_subsmp == 2
|| (orig_subsmp > 0 && jsvals.subsmp == 0))
jsvals.subsmp = orig_subsmp;
}
}
break;
}

View File

@ -49,6 +49,9 @@ extern gboolean load_interactive;
extern gint32 display_ID;
extern gchar *image_comment;
extern gboolean has_metadata;
extern gint orig_quality;
extern gint orig_subsmp;
extern gint num_quant_tables;
gint32 load_image (const gchar *filename,
GimpRunMode runmode,