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.
This commit is contained in:
Lovell Fuller 2021-01-11 20:40:13 +00:00
parent 3cd774a10c
commit 3ad7363104
9 changed files with 99 additions and 20 deletions

View File

@ -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<double>.
* - **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<double>.
* - **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<double>.
* - **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<double>.
* - **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<double>.
* - **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<double>.
* - **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<double>.
* - **page_height** -- Set page height for multipage save, int.

View File

@ -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.
*

View File

@ -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

View File

@ -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 );

View File

@ -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;

View File

@ -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;

View File

@ -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,

View File

@ -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;

View File

@ -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")