diff --git a/libvips/foreign/jpegsave.c b/libvips/foreign/jpegsave.c index 672c2394..9fe952e5 100644 --- a/libvips/foreign/jpegsave.c +++ b/libvips/foreign/jpegsave.c @@ -79,7 +79,7 @@ typedef struct _VipsForeignSaveJpeg { /* Force chroma subsampling, if Q >= 90 then subsampling is disabled, use this flag to force it */ - gboolean force_subsample; + VipsForeignJpegSubsample subsample_mode; /* Apply trellis quantisation to each 8x8 block. */ @@ -198,12 +198,13 @@ vips_foreign_save_jpeg_class_init( VipsForeignSaveJpegClass *class ) G_STRUCT_OFFSET( VipsForeignSaveJpeg, quant_table ), 0, 8, 0 ); - VIPS_ARG_BOOL( class, "force_subsample", 19, - _( "Force subsample" ), - _( "Force chroma subsample" ), + VIPS_ARG_ENUM( class, "subsample_mode", 19, + _( "Subsample mode" ), + _( "Select chroma subsample operation mode" ), VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsForeignSaveJpeg, force_subsample ), - FALSE ); + G_STRUCT_OFFSET( VipsForeignSaveJpeg, subsample_mode ), + VIPS_TYPE_FOREIGN_JPEG_SUBSAMPLE, + VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO ); } static void @@ -240,7 +241,7 @@ vips_foreign_save_jpeg_target_build( VipsObject *object ) jpeg->Q, jpeg->profile, jpeg->optimize_coding, jpeg->interlace, save->strip, jpeg->no_subsample, jpeg->trellis_quant, jpeg->overshoot_deringing, - jpeg->optimize_scans, jpeg->quant_table, jpeg->force_subsample ) ) + jpeg->optimize_scans, jpeg->quant_table, jpeg->subsample_mode ) ) return( -1 ); return( 0 ); @@ -266,7 +267,6 @@ vips_foreign_save_jpeg_target_class_init( VIPS_ARGUMENT_REQUIRED_INPUT, G_STRUCT_OFFSET( VipsForeignSaveJpegTarget, target ), VIPS_TYPE_TARGET ); - } static void @@ -307,7 +307,7 @@ vips_foreign_save_jpeg_file_build( VipsObject *object ) jpeg->Q, jpeg->profile, jpeg->optimize_coding, jpeg->interlace, save->strip, jpeg->no_subsample, jpeg->trellis_quant, jpeg->overshoot_deringing, - jpeg->optimize_scans, jpeg->quant_table, jpeg->force_subsample ) ) { + jpeg->optimize_scans, jpeg->quant_table, jpeg->subsample_mode ) ) { VIPS_UNREF( target ); return( -1 ); } @@ -377,7 +377,7 @@ vips_foreign_save_jpeg_buffer_build( VipsObject *object ) jpeg->Q, jpeg->profile, jpeg->optimize_coding, jpeg->interlace, save->strip, jpeg->no_subsample, jpeg->trellis_quant, jpeg->overshoot_deringing, - jpeg->optimize_scans, jpeg->quant_table, jpeg->force_subsample ) ) { + jpeg->optimize_scans, jpeg->quant_table, jpeg->subsample_mode ) ) { VIPS_UNREF( target ); return( -1 ); } @@ -450,7 +450,7 @@ vips_foreign_save_jpeg_mime_build( VipsObject *object ) jpeg->Q, jpeg->profile, jpeg->optimize_coding, jpeg->interlace, save->strip, jpeg->no_subsample, jpeg->trellis_quant, jpeg->overshoot_deringing, - jpeg->optimize_scans, jpeg->quant_table, jpeg->force_subsample ) ) { + jpeg->optimize_scans, jpeg->quant_table, jpeg->subsample_mode ) ) { VIPS_UNREF( target ); return( -1 ); } diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index 8d536329..069013cf 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -166,7 +166,7 @@ int vips__jpeg_write_target( VipsImage *in, VipsTarget *target, gboolean optimize_coding, gboolean progressive, gboolean strip, gboolean no_subsample, gboolean trellis_quant, gboolean overshoot_deringing, gboolean optimize_scans, - int quant_table, gboolean force_subsample ); + int quant_table, VipsForeignJpegSubsample subsample_mode ); int vips__jpeg_read_source( VipsSource *source, VipsImage *out, gboolean header_only, int shrink, int fail, gboolean autorotate ); diff --git a/libvips/foreign/vips2jpeg.c b/libvips/foreign/vips2jpeg.c index 97f8a3a9..4b90a0ba 100644 --- a/libvips/foreign/vips2jpeg.c +++ b/libvips/foreign/vips2jpeg.c @@ -479,9 +479,9 @@ write_jpeg_block( VipsRegion *region, VipsRect *area, void *a ) static int write_vips( Write *write, int qfac, const char *profile, gboolean optimize_coding, gboolean progressive, gboolean strip, - gboolean no_subsample, gboolean trellis_quant, - gboolean overshoot_deringing, gboolean optimize_scans, int quant_table, - gboolean force_subsample) + gboolean trellis_quant, gboolean overshoot_deringing, + gboolean optimize_scans, int quant_table, + VipsForeignJpegSubsample subsample_mode) { VipsImage *in; J_COLOR_SPACE space; @@ -628,18 +628,24 @@ write_vips( Write *write, int qfac, const char *profile, if( progressive ) jpeg_simple_progression( &write->cinfo ); - /* Turn off chroma subsampling. Follow IM and do it automatically for - * high Q. - */ - if( !force_subsample && ( - no_subsample || - qfac >= 90 )) { - int i; - - for( i = 0; i < in->Bands; i++ ) { - write->cinfo.comp_info[i].h_samp_factor = 1; - write->cinfo.comp_info[i].v_samp_factor = 1; - } + switch (subsample_mode) { + case VIPS_FOREIGN_JPEG_SUBSAMPLE_ON: + break; + case VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO: + /* Turn off chroma subsampling. Follow IM and do it automatically for + * high Q. + */ + if( qfac < 90 ) { + break; + } + case VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF: + default: { + int i; + for (i = 0; i < in->Bands; i++) { + write->cinfo.comp_info[i].h_samp_factor = 1; + write->cinfo.comp_info[i].v_samp_factor = 1; + } + } } /* Don't write the APP0 JFIF headers if we are stripping. @@ -778,7 +784,7 @@ vips__jpeg_write_target( VipsImage *in, VipsTarget *target, gboolean optimize_coding, gboolean progressive, gboolean strip, gboolean no_subsample, gboolean trellis_quant, gboolean overshoot_deringing, gboolean optimize_scans, int quant_table, - gboolean force_subsample) + VipsForeignJpegSubsample subsample_mode) { Write *write; @@ -800,12 +806,18 @@ vips__jpeg_write_target( VipsImage *in, VipsTarget *target, */ target_dest( &write->cinfo, target ); + /* Retain old behavior for now + */ + if( no_subsample ) { + subsample_mode = VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF; + } + /* Convert! Write errors come back here as an error return. */ if( write_vips( write, - Q, profile, optimize_coding, progressive, strip, no_subsample, + Q, profile, optimize_coding, progressive, strip, trellis_quant, overshoot_deringing, optimize_scans, - quant_table, force_subsample ) ) { + quant_table, subsample_mode ) ) { write_destroy( write ); return( -1 ); } diff --git a/libvips/include/vips/enumtypes.h b/libvips/include/vips/enumtypes.h index 2de73711..bc7fbb6d 100644 --- a/libvips/include/vips/enumtypes.h +++ b/libvips/include/vips/enumtypes.h @@ -58,6 +58,8 @@ GType vips_foreign_flags_get_type (void) G_GNUC_CONST; #define VIPS_TYPE_FOREIGN_FLAGS (vips_foreign_flags_get_type()) GType vips_saveable_get_type (void) G_GNUC_CONST; #define VIPS_TYPE_SAVEABLE (vips_saveable_get_type()) +GType vips_foreign_jpeg_subsample_get_type (void) G_GNUC_CONST; +#define VIPS_TYPE_FOREIGN_JPEG_SUBSAMPLE (vips_foreign_jpeg_subsample_get_type()) GType vips_foreign_webp_preset_get_type (void) G_GNUC_CONST; #define VIPS_TYPE_FOREIGN_WEBP_PRESET (vips_foreign_webp_preset_get_type()) GType vips_foreign_tiff_compression_get_type (void) G_GNUC_CONST; diff --git a/libvips/include/vips/foreign.h b/libvips/include/vips/foreign.h index 1f84f4f9..0f25c52a 100644 --- a/libvips/include/vips/foreign.h +++ b/libvips/include/vips/foreign.h @@ -364,6 +364,18 @@ int vips_vipssave( VipsImage *in, const char *filename, ... ) int vips_openslideload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); +/** + * VipsForeignJpegSubsample: + * @VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO: default preset + * @VIPS_FOREIGN_JPEG_SUBSAMPLE_ON: always perform subsampling + * @VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF: never perform subsampling + */ +typedef enum { + VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO, + VIPS_FOREIGN_JPEG_SUBSAMPLE_ON, + VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF +} VipsForeignJpegSubsample; + int vips_jpegload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); int vips_jpegload_buffer( void *buf, size_t len, VipsImage **out, ... ) diff --git a/libvips/iofuncs/enumtypes.c b/libvips/iofuncs/enumtypes.c index 767a43f9..e9069cd2 100644 --- a/libvips/iofuncs/enumtypes.c +++ b/libvips/iofuncs/enumtypes.c @@ -499,6 +499,24 @@ vips_saveable_get_type( void ) return( etype ); } GType +vips_foreign_jpeg_subsample_get_type( void ) +{ + static GType etype = 0; + + if( etype == 0 ) { + static const GEnumValue values[] = { + {VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO, "VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO", "auto"}, + {VIPS_FOREIGN_JPEG_SUBSAMPLE_ON, "VIPS_FOREIGN_JPEG_SUBSAMPLE_ON", "on"}, + {VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF, "VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF", "off"}, + {0, NULL, NULL} + }; + + etype = g_enum_register_static( "VipsForeignJpegSubsample", values ); + } + + return( etype ); +} +GType vips_foreign_webp_preset_get_type( void ) { static GType etype = 0; diff --git a/test/test-suite/test_foreign.py b/test/test-suite/test_foreign.py index 9dbc1e6b..2f17cc2c 100644 --- a/test/test-suite/test_foreign.py +++ b/test/test-suite/test_foreign.py @@ -259,14 +259,21 @@ class TestForeign: im = pyvips.Image.new_from_file(JPEG_FILE) # higher Q should mean a bigger buffer - b1 = im.jpegsave_buffer(Q=10) - b2 = im.jpegsave_buffer(Q=90) - assert len(b2) > len(b1) + q10 = im.jpegsave_buffer(Q=10) + q10_subsample_off = im.jpegsave_buffer(Q=10, subsample_mode=2) + q10_subsample_no = im.jpegsave_buffer(Q=10, no_subsample=1) + q10_subsample_no_and_subsample_mode_on = im.jpegsave_buffer(Q=10, no_subsample=1, subsample_mode=1) + q90 = im.jpegsave_buffer(Q=90) + assert len(q90) > len(q10) + assert len(q10_subsample_off) > len(q10) + assert len(q10_subsample_off) == len(q10_subsample_no) + assert len(q10_subsample_no_and_subsample_mode_on) == len(q10_subsample_no) # force subsampling should result in smaller buffer - b1 = im.jpegsave_buffer(Q=90, force_subsample=True) - b2 = im.jpegsave_buffer(Q=90) - assert len(b2) > len(b1) + q90_subsample_on = im.jpegsave_buffer(Q=90, subsample_mode=1) + q90_subsample_auto = im.jpegsave_buffer(Q=90, subsample_mode=0) + assert len(q90) > len(q90_subsample_on) + assert len(q90) == len(q90_subsample_auto) @skip_if_no("jpegload") def test_truncated(self):