Skip to content

heifsave: add option to control chroma subsampling #1961

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions cplusplus/include/vips/VImage8.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand Down
30 changes: 30 additions & 0 deletions libvips/foreign/heifsave.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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 ) )
Expand Down Expand Up @@ -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().
*/
Expand Down Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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.
*
Expand All @@ -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.
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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.
*
Expand Down
22 changes: 11 additions & 11 deletions libvips/foreign/jpegsave.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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 );
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion libvips/foreign/pforeign.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down
8 changes: 4 additions & 4 deletions libvips/foreign/vips2jpeg.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down
2 changes: 2 additions & 0 deletions libvips/include/vips/enumtypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
17 changes: 17 additions & 0 deletions libvips/include/vips/foreign.h
Original file line number Diff line number Diff line change
Expand Up @@ -370,13 +370,30 @@ 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
* @VIPS_FOREIGN_JPEG_SUBSAMPLE_ON: always perform subsampling
* @VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF: never perform subsampling
*
* Set jpeg subsampling mode.
*
* DEPRECATED: use #VipsForeignSubsample
*/
typedef enum {
VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO,
Expand Down
19 changes: 19 additions & 0 deletions libvips/iofuncs/enumtypes.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 5 additions & 0 deletions test/test-suite/test_foreign.py
Original file line number Diff line number Diff line change
Expand Up @@ -1104,6 +1104,11 @@ def test_heifsave(self):
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")
Expand Down