From 3ad736310449f198538fe1be3d1366aa54700414 Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Mon, 11 Jan 2021 20:40:13 +0000 Subject: [PATCH] heifsave: add option to control subsample_mode Defaults to no subsampling when Q>90 for consistency with jpegsave. Deprecate VipsForeignJpegSubsample enum, replace with more generic VipsForeignSubsample. --- cplusplus/include/vips/VImage8.h | 14 ++++++++++---- libvips/foreign/heifsave.c | 30 ++++++++++++++++++++++++++++++ libvips/foreign/jpegsave.c | 22 +++++++++++----------- libvips/foreign/pforeign.h | 2 +- libvips/foreign/vips2jpeg.c | 8 ++++---- libvips/include/vips/enumtypes.h | 2 ++ libvips/include/vips/foreign.h | 17 +++++++++++++++++ libvips/iofuncs/enumtypes.c | 19 +++++++++++++++++++ test/test-suite/test_foreign.py | 5 +++++ 9 files changed, 99 insertions(+), 20 deletions(-) diff --git a/cplusplus/include/vips/VImage8.h b/cplusplus/include/vips/VImage8.h index a62ebd8d..138c0ff4 100644 --- a/cplusplus/include/vips/VImage8.h +++ b/cplusplus/include/vips/VImage8.h @@ -2977,6 +2977,8 @@ static VImage heifload_source( VSource source, VOption *options = 0 ); * - **Q** -- Q factor, int. * - **lossless** -- Enable lossless compression, bool. * - **compression** -- Compression format, VipsForeignHeifCompression. + * - **speed**: -- CPU effort, 0 slowest - 8 fastest, AV1 compression only, int. + * - **subsample_mode** -- Select chroma subsample operation mode, VipsForeignSubsample. * - **strip** -- Strip all metadata from image, bool. * - **background** -- Background value, std::vector. * - **page_height** -- Set page height for multipage save, int. @@ -2993,6 +2995,8 @@ void heifsave( const char *filename, VOption *options = 0 ) const; * - **Q** -- Q factor, int. * - **lossless** -- Enable lossless compression, bool. * - **compression** -- Compression format, VipsForeignHeifCompression. + * - **speed**: -- CPU effort, 0 slowest - 8 fastest, AV1 compression only, int. + * - **subsample_mode** -- Select chroma subsample operation mode, VipsForeignSubsample. * - **strip** -- Strip all metadata from image, bool. * - **background** -- Background value, std::vector. * - **page_height** -- Set page height for multipage save, int. @@ -3009,6 +3013,8 @@ VipsBlob *heifsave_buffer( VOption *options = 0 ) const; * - **Q** -- Q factor, int. * - **lossless** -- Enable lossless compression, bool. * - **compression** -- Compression format, VipsForeignHeifCompression. + * - **speed**: -- CPU effort, 0 slowest - 8 fastest, AV1 compression only, int. + * - **subsample_mode** -- Select chroma subsample operation mode, VipsForeignSubsample. * - **strip** -- Strip all metadata from image, bool. * - **background** -- Background value, std::vector. * - **page_height** -- Set page height for multipage save, int. @@ -3341,7 +3347,7 @@ static VImage jpegload_source( VSource source, VOption *options = 0 ); * - **overshoot_deringing** -- Apply overshooting to samples with extreme values, bool. * - **optimize_scans** -- Split spectrum of DCT coefficients into separate scans, bool. * - **quant_table** -- Use predefined quantization table with given index, int. - * - **subsample_mode** -- Select chroma subsample operation mode, VipsForeignJpegSubsample. + * - **subsample_mode** -- Select chroma subsample operation mode, VipsForeignSubsample. * - **strip** -- Strip all metadata from image, bool. * - **background** -- Background value, std::vector. * - **page_height** -- Set page height for multipage save, int. @@ -3364,7 +3370,7 @@ void jpegsave( const char *filename, VOption *options = 0 ) const; * - **overshoot_deringing** -- Apply overshooting to samples with extreme values, bool. * - **optimize_scans** -- Split spectrum of DCT coefficients into separate scans, bool. * - **quant_table** -- Use predefined quantization table with given index, int. - * - **subsample_mode** -- Select chroma subsample operation mode, VipsForeignJpegSubsample. + * - **subsample_mode** -- Select chroma subsample operation mode, VipsForeignSubsample. * - **strip** -- Strip all metadata from image, bool. * - **background** -- Background value, std::vector. * - **page_height** -- Set page height for multipage save, int. @@ -3387,7 +3393,7 @@ VipsBlob *jpegsave_buffer( VOption *options = 0 ) const; * - **overshoot_deringing** -- Apply overshooting to samples with extreme values, bool. * - **optimize_scans** -- Split spectrum of DCT coefficients into separate scans, bool. * - **quant_table** -- Use predefined quantization table with given index, int. - * - **subsample_mode** -- Select chroma subsample operation mode, VipsForeignJpegSubsample. + * - **subsample_mode** -- Select chroma subsample operation mode, VipsForeignSubsample. * - **strip** -- Strip all metadata from image, bool. * - **background** -- Background value, std::vector. * - **page_height** -- Set page height for multipage save, int. @@ -3409,7 +3415,7 @@ void jpegsave_mime( VOption *options = 0 ) const; * - **overshoot_deringing** -- Apply overshooting to samples with extreme values, bool. * - **optimize_scans** -- Split spectrum of DCT coefficients into separate scans, bool. * - **quant_table** -- Use predefined quantization table with given index, int. - * - **subsample_mode** -- Select chroma subsample operation mode, VipsForeignJpegSubsample. + * - **subsample_mode** -- Select chroma subsample operation mode, VipsForeignSubsample. * - **strip** -- Strip all metadata from image, bool. * - **background** -- Background value, std::vector. * - **page_height** -- Set page height for multipage save, int. diff --git a/libvips/foreign/heifsave.c b/libvips/foreign/heifsave.c index 291871ca..5ea82034 100644 --- a/libvips/foreign/heifsave.c +++ b/libvips/foreign/heifsave.c @@ -83,6 +83,10 @@ typedef struct _VipsForeignSaveHeif { */ int speed; + /* Chroma subsampling. + */ + VipsForeignSubsample subsample_mode; + /* The image we save. This is a copy of save->ready since we need to * be able to update the metadata. */ @@ -319,6 +323,7 @@ vips_foreign_save_heif_build( VipsObject *object ) struct heif_error error; struct heif_writer writer; + char *chroma; if( VIPS_OBJECT_CLASS( vips_foreign_save_heif_parent_class )-> build( object ) ) @@ -363,6 +368,17 @@ vips_foreign_save_heif_build( VipsObject *object ) return( -1 ); } + chroma = heif->subsample_mode == VIPS_FOREIGN_SUBSAMPLE_OFF || + ( heif->subsample_mode == VIPS_FOREIGN_SUBSAMPLE_AUTO && + heif->Q > 90 ) ? "444" : "420"; + error = heif_encoder_set_parameter_string( heif->encoder, + "chroma", chroma ); + if( error.code && + error.subcode != heif_suberror_Unsupported_parameter ) { + vips__heif_error( &error ); + return( -1 ); + } + /* TODO .. support extra per-encoder params with * heif_encoder_list_parameters(). */ @@ -487,6 +503,13 @@ vips_foreign_save_heif_class_init( VipsForeignSaveHeifClass *class ) G_STRUCT_OFFSET( VipsForeignSaveHeif, speed ), 0, 8, 5 ); + VIPS_ARG_ENUM( class, "subsample_mode", 16, + _( "Subsample mode" ), + _( "Select chroma subsample operation mode" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveHeif, subsample_mode ), + VIPS_TYPE_FOREIGN_SUBSAMPLE, + VIPS_FOREIGN_SUBSAMPLE_AUTO ); } static void @@ -496,6 +519,7 @@ vips_foreign_save_heif_init( VipsForeignSaveHeif *heif ) heif->Q = 50; heif->compression = VIPS_FOREIGN_HEIF_COMPRESSION_HEVC; heif->speed = 5; + heif->subsample_mode = VIPS_FOREIGN_SUBSAMPLE_AUTO; } typedef struct _VipsForeignSaveHeifFile { @@ -689,6 +713,7 @@ vips_foreign_save_heif_target_init( VipsForeignSaveHeifTarget *target ) * * @lossless: %gboolean, enable lossless encoding * * @compression: #VipsForeignHeifCompression, write with this compression * * @speed: %gint, CPU effort, 0 slowest - 8 fastest, AV1 compression only + * * @subsample_mode: #VipsForeignSubsample, chroma subsampling mode * * Write a VIPS image to a file in HEIF format. * @@ -702,6 +727,9 @@ vips_foreign_save_heif_target_init( VipsForeignSaveHeifTarget *target ) * Use @speed to control the CPU effort spent improving compression. * This is currently only applicable to AV1 encoders, defaults to 5. * + * Chroma subsampling is normally automatically disabled for Q > 90. You can + * force the subsampling mode with @subsample_mode. + * * See also: vips_image_write_to_file(), vips_heifload(). * * Returns: 0 on success, -1 on error. @@ -732,6 +760,7 @@ vips_heifsave( VipsImage *in, const char *filename, ... ) * * @lossless: %gboolean, enable lossless encoding * * @compression: #VipsForeignHeifCompression, write with this compression * * @speed: %gint, CPU effort, 0 slowest - 8 fastest, AV1 compression only + * * @subsample_mode: #VipsForeignSubsample, chroma subsampling mode * * As vips_heifsave(), but save to a memory buffer. * @@ -783,6 +812,7 @@ vips_heifsave_buffer( VipsImage *in, void **buf, size_t *len, ... ) * * @lossless: %gboolean, enable lossless encoding * * @compression: #VipsForeignHeifCompression, write with this compression * * @speed: %gint, CPU effort, 0 slowest - 8 fastest, AV1 compression only + * * @subsample_mode: #VipsForeignSubsample, chroma subsampling mode * * As vips_heifsave(), but save to a target. * diff --git a/libvips/foreign/jpegsave.c b/libvips/foreign/jpegsave.c index cc08710d..cc6feac4 100644 --- a/libvips/foreign/jpegsave.c +++ b/libvips/foreign/jpegsave.c @@ -84,7 +84,7 @@ typedef struct _VipsForeignSaveJpeg { * on will always enable subsampling * off will always disable subsampling */ - VipsForeignJpegSubsample subsample_mode; + VipsForeignSubsample subsample_mode; /* Apply trellis quantisation to each 8x8 block. */ @@ -133,8 +133,8 @@ vips_foreign_save_jpeg_build( VipsObject *object ) */ if( vips_object_argument_isset( object, "no_subsample" ) ) jpeg->subsample_mode = jpeg->no_subsample ? - VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF : - VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO; + VIPS_FOREIGN_SUBSAMPLE_OFF : + VIPS_FOREIGN_SUBSAMPLE_AUTO; return( 0 ); } @@ -229,15 +229,15 @@ vips_foreign_save_jpeg_class_init( VipsForeignSaveJpegClass *class ) _( "Select chroma subsample operation mode" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveJpeg, subsample_mode ), - VIPS_TYPE_FOREIGN_JPEG_SUBSAMPLE, - VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO ); + VIPS_TYPE_FOREIGN_SUBSAMPLE, + VIPS_FOREIGN_SUBSAMPLE_AUTO ); } static void vips_foreign_save_jpeg_init( VipsForeignSaveJpeg *jpeg ) { jpeg->Q = 75; - jpeg->subsample_mode = VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO; + jpeg->subsample_mode = VIPS_FOREIGN_SUBSAMPLE_AUTO; } typedef struct _VipsForeignSaveJpegTarget { @@ -529,7 +529,7 @@ vips_foreign_save_jpeg_mime_init( VipsForeignSaveJpegMime *mime ) * * @optimize_coding: %gboolean, compute optimal Huffman coding tables * * @interlace: %gboolean, write an interlaced (progressive) jpeg * * @strip: %gboolean, remove all metadata from image - * * @subsample_mode: #VipsForeignJpegSubsample, chroma subsampling mode + * * @subsample_mode: #VipsForeignSubsample, chroma subsampling mode * * @trellis_quant: %gboolean, apply trellis quantisation to each 8x8 block * * @overshoot_deringing: %gboolean, overshoot samples with extreme values * * @optimize_scans: %gboolean, split DCT coefficients into separate scans @@ -558,7 +558,7 @@ vips_foreign_save_jpeg_mime_init( VipsForeignSaveJpegMime *mime ) * written into the output file. * * Chroma subsampling is normally automatically disabled for Q > 90. You can - * force the subsampling mode with @@subsample_mode. + * force the subsampling mode with @subsample_mode. * * If @trellis_quant is set and the version of libjpeg supports it * (e.g. mozjpeg >= 3.0), apply trellis quantisation to each 8x8 block. @@ -645,7 +645,7 @@ vips_jpegsave( VipsImage *in, const char *filename, ... ) * * @optimize_coding: %gboolean, compute optimal Huffman coding tables * * @interlace: %gboolean, write an interlaced (progressive) jpeg * * @strip: %gboolean, remove all metadata from image - * * @subsample_mode: #VipsForeignJpegSubsample, chroma subsampling mode + * * @subsample_mode: #VipsForeignSubsample, chroma subsampling mode * * @trellis_quant: %gboolean, apply trellis quantisation to each 8x8 block * * @overshoot_deringing: %gboolean, overshoot samples with extreme values * * @optimize_scans: %gboolean, split DCT coefficients into separate scans @@ -684,7 +684,7 @@ vips_jpegsave_target( VipsImage *in, VipsTarget *target, ... ) * * @optimize_coding: %gboolean, compute optimal Huffman coding tables * * @interlace: %gboolean, write an interlaced (progressive) jpeg * * @strip: %gboolean, remove all metadata from image - * * @subsample_mode: #VipsForeignJpegSubsample, chroma subsampling mode + * * @subsample_mode: #VipsForeignSubsample, chroma subsampling mode * * @trellis_quant: %gboolean, apply trellis quantisation to each 8x8 block * * @overshoot_deringing: %gboolean, overshoot samples with extreme values * * @optimize_scans: %gboolean, split DCT coefficients into separate scans @@ -740,7 +740,7 @@ vips_jpegsave_buffer( VipsImage *in, void **buf, size_t *len, ... ) * * @optimize_coding: %gboolean, compute optimal Huffman coding tables * * @interlace: %gboolean, write an interlaced (progressive) jpeg * * @strip: %gboolean, remove all metadata from image - * * @subsample_mode: #VipsForeignJpegSubsample, chroma subsampling mode + * * @subsample_mode: #VipsForeignSubsample, chroma subsampling mode * * @trellis_quant: %gboolean, apply trellis quantisation to each 8x8 block * * @overshoot_deringing: %gboolean, overshoot samples with extreme values * * @optimize_scans: %gboolean, split DCT coefficients into separate scans diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index c80a617f..0683850e 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -168,7 +168,7 @@ int vips__jpeg_write_target( VipsImage *in, VipsTarget *target, gboolean optimize_coding, gboolean progressive, gboolean strip, gboolean trellis_quant, gboolean overshoot_deringing, gboolean optimize_scans, int quant_table, - VipsForeignJpegSubsample subsample_mode ); + VipsForeignSubsample 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 83495072..18240558 100644 --- a/libvips/foreign/vips2jpeg.c +++ b/libvips/foreign/vips2jpeg.c @@ -540,7 +540,7 @@ write_vips( Write *write, int qfac, const char *profile, gboolean optimize_coding, gboolean progressive, gboolean strip, gboolean trellis_quant, gboolean overshoot_deringing, gboolean optimize_scans, int quant_table, - VipsForeignJpegSubsample subsample_mode ) + VipsForeignSubsample subsample_mode ) { VipsImage *in; J_COLOR_SPACE space; @@ -687,8 +687,8 @@ write_vips( Write *write, int qfac, const char *profile, if( progressive ) jpeg_simple_progression( &write->cinfo ); - if( subsample_mode == VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF || - (subsample_mode == VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO && + if( subsample_mode == VIPS_FOREIGN_SUBSAMPLE_OFF || + (subsample_mode == VIPS_FOREIGN_SUBSAMPLE_AUTO && qfac >= 90) ) { int i; @@ -846,7 +846,7 @@ vips__jpeg_write_target( VipsImage *in, VipsTarget *target, gboolean optimize_coding, gboolean progressive, gboolean strip, gboolean trellis_quant, gboolean overshoot_deringing, gboolean optimize_scans, - int quant_table, VipsForeignJpegSubsample subsample_mode) + int quant_table, VipsForeignSubsample subsample_mode) { Write *write; diff --git a/libvips/include/vips/enumtypes.h b/libvips/include/vips/enumtypes.h index bc7fbb6d..8cb85a9e 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_subsample_get_type (void) G_GNUC_CONST; +#define VIPS_TYPE_FOREIGN_SUBSAMPLE (vips_foreign_subsample_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; diff --git a/libvips/include/vips/foreign.h b/libvips/include/vips/foreign.h index c98fa99f..e2f329fe 100644 --- a/libvips/include/vips/foreign.h +++ b/libvips/include/vips/foreign.h @@ -370,6 +370,21 @@ int vips_openslideload( const char *filename, VipsImage **out, ... ) int vips_openslideload_source( VipsSource *source, VipsImage **out, ... ) __attribute__((sentinel)); +/** + * VipsForeignSubsample: + * @VIPS_FOREIGN_SUBSAMPLE_AUTO: prevent subsampling when quality > 90 + * @VIPS_FOREIGN_SUBSAMPLE_ON: always perform subsampling + * @VIPS_FOREIGN_SUBSAMPLE_OFF: never perform subsampling + * + * Set subsampling mode. + */ +typedef enum { + VIPS_FOREIGN_SUBSAMPLE_AUTO, + VIPS_FOREIGN_SUBSAMPLE_ON, + VIPS_FOREIGN_SUBSAMPLE_OFF, + VIPS_FOREIGN_SUBSAMPLE_LAST +} VipsForeignSubsample; + /** * VipsForeignJpegSubsample: * @VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO: default preset @@ -377,6 +392,8 @@ int vips_openslideload_source( VipsSource *source, VipsImage **out, ... ) * @VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF: never perform subsampling * * Set jpeg subsampling mode. + * + * DEPRECATED: use #VipsForeignSubsample */ typedef enum { VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO, diff --git a/libvips/iofuncs/enumtypes.c b/libvips/iofuncs/enumtypes.c index a4adbe30..d3cc9bfa 100644 --- a/libvips/iofuncs/enumtypes.c +++ b/libvips/iofuncs/enumtypes.c @@ -500,6 +500,25 @@ vips_saveable_get_type( void ) return( etype ); } GType +vips_foreign_subsample_get_type( void ) +{ + static GType etype = 0; + + if( etype == 0 ) { + static const GEnumValue values[] = { + {VIPS_FOREIGN_SUBSAMPLE_AUTO, "VIPS_FOREIGN_SUBSAMPLE_AUTO", "auto"}, + {VIPS_FOREIGN_SUBSAMPLE_ON, "VIPS_FOREIGN_SUBSAMPLE_ON", "on"}, + {VIPS_FOREIGN_SUBSAMPLE_OFF, "VIPS_FOREIGN_SUBSAMPLE_OFF", "off"}, + {VIPS_FOREIGN_SUBSAMPLE_LAST, "VIPS_FOREIGN_SUBSAMPLE_LAST", "last"}, + {0, NULL, NULL} + }; + + etype = g_enum_register_static( "VipsForeignSubsample", values ); + } + + return( etype ); +} +GType vips_foreign_jpeg_subsample_get_type( void ) { static GType etype = 0; diff --git a/test/test-suite/test_foreign.py b/test/test-suite/test_foreign.py index b6b64bf2..31c082cb 100644 --- a/test/test-suite/test_foreign.py +++ b/test/test-suite/test_foreign.py @@ -1104,6 +1104,11 @@ class TestForeign: b2 = self.mono.heifsave_buffer(Q=90, compression="av1") assert len(b2) > len(b1) + # Chroma subsampling should produce smaller file size for same Q + b1 = self.colour.heifsave_buffer(compression="av1", subsample_mode="on") + b2 = self.colour.heifsave_buffer(compression="av1", subsample_mode="off") + assert len(b2) > len(b1) + # try saving an image with an ICC profile and reading it back # not all libheif have profile support, so put it in an if buf = self.colour.heifsave_buffer(Q=10, compression="av1")