Skip to content

Commit a0aa1fb

Browse files
authored
Merge pull request #1961 from lovell/heif-chroma-subsampling
heifsave: add option to control chroma subsampling
2 parents 78d1a4a + 3ad7363 commit a0aa1fb

File tree

9 files changed

+99
-20
lines changed

9 files changed

+99
-20
lines changed

cplusplus/include/vips/VImage8.h

+10-4
Original file line numberDiff line numberDiff line change
@@ -2977,6 +2977,8 @@ static VImage heifload_source( VSource source, VOption *options = 0 );
29772977
* - **Q** -- Q factor, int.
29782978
* - **lossless** -- Enable lossless compression, bool.
29792979
* - **compression** -- Compression format, VipsForeignHeifCompression.
2980+
* - **speed**: -- CPU effort, 0 slowest - 8 fastest, AV1 compression only, int.
2981+
* - **subsample_mode** -- Select chroma subsample operation mode, VipsForeignSubsample.
29802982
* - **strip** -- Strip all metadata from image, bool.
29812983
* - **background** -- Background value, std::vector<double>.
29822984
* - **page_height** -- Set page height for multipage save, int.
@@ -2993,6 +2995,8 @@ void heifsave( const char *filename, VOption *options = 0 ) const;
29932995
* - **Q** -- Q factor, int.
29942996
* - **lossless** -- Enable lossless compression, bool.
29952997
* - **compression** -- Compression format, VipsForeignHeifCompression.
2998+
* - **speed**: -- CPU effort, 0 slowest - 8 fastest, AV1 compression only, int.
2999+
* - **subsample_mode** -- Select chroma subsample operation mode, VipsForeignSubsample.
29963000
* - **strip** -- Strip all metadata from image, bool.
29973001
* - **background** -- Background value, std::vector<double>.
29983002
* - **page_height** -- Set page height for multipage save, int.
@@ -3009,6 +3013,8 @@ VipsBlob *heifsave_buffer( VOption *options = 0 ) const;
30093013
* - **Q** -- Q factor, int.
30103014
* - **lossless** -- Enable lossless compression, bool.
30113015
* - **compression** -- Compression format, VipsForeignHeifCompression.
3016+
* - **speed**: -- CPU effort, 0 slowest - 8 fastest, AV1 compression only, int.
3017+
* - **subsample_mode** -- Select chroma subsample operation mode, VipsForeignSubsample.
30123018
* - **strip** -- Strip all metadata from image, bool.
30133019
* - **background** -- Background value, std::vector<double>.
30143020
* - **page_height** -- Set page height for multipage save, int.
@@ -3341,7 +3347,7 @@ static VImage jpegload_source( VSource source, VOption *options = 0 );
33413347
* - **overshoot_deringing** -- Apply overshooting to samples with extreme values, bool.
33423348
* - **optimize_scans** -- Split spectrum of DCT coefficients into separate scans, bool.
33433349
* - **quant_table** -- Use predefined quantization table with given index, int.
3344-
* - **subsample_mode** -- Select chroma subsample operation mode, VipsForeignJpegSubsample.
3350+
* - **subsample_mode** -- Select chroma subsample operation mode, VipsForeignSubsample.
33453351
* - **strip** -- Strip all metadata from image, bool.
33463352
* - **background** -- Background value, std::vector<double>.
33473353
* - **page_height** -- Set page height for multipage save, int.
@@ -3364,7 +3370,7 @@ void jpegsave( const char *filename, VOption *options = 0 ) const;
33643370
* - **overshoot_deringing** -- Apply overshooting to samples with extreme values, bool.
33653371
* - **optimize_scans** -- Split spectrum of DCT coefficients into separate scans, bool.
33663372
* - **quant_table** -- Use predefined quantization table with given index, int.
3367-
* - **subsample_mode** -- Select chroma subsample operation mode, VipsForeignJpegSubsample.
3373+
* - **subsample_mode** -- Select chroma subsample operation mode, VipsForeignSubsample.
33683374
* - **strip** -- Strip all metadata from image, bool.
33693375
* - **background** -- Background value, std::vector<double>.
33703376
* - **page_height** -- Set page height for multipage save, int.
@@ -3387,7 +3393,7 @@ VipsBlob *jpegsave_buffer( VOption *options = 0 ) const;
33873393
* - **overshoot_deringing** -- Apply overshooting to samples with extreme values, bool.
33883394
* - **optimize_scans** -- Split spectrum of DCT coefficients into separate scans, bool.
33893395
* - **quant_table** -- Use predefined quantization table with given index, int.
3390-
* - **subsample_mode** -- Select chroma subsample operation mode, VipsForeignJpegSubsample.
3396+
* - **subsample_mode** -- Select chroma subsample operation mode, VipsForeignSubsample.
33913397
* - **strip** -- Strip all metadata from image, bool.
33923398
* - **background** -- Background value, std::vector<double>.
33933399
* - **page_height** -- Set page height for multipage save, int.
@@ -3409,7 +3415,7 @@ void jpegsave_mime( VOption *options = 0 ) const;
34093415
* - **overshoot_deringing** -- Apply overshooting to samples with extreme values, bool.
34103416
* - **optimize_scans** -- Split spectrum of DCT coefficients into separate scans, bool.
34113417
* - **quant_table** -- Use predefined quantization table with given index, int.
3412-
* - **subsample_mode** -- Select chroma subsample operation mode, VipsForeignJpegSubsample.
3418+
* - **subsample_mode** -- Select chroma subsample operation mode, VipsForeignSubsample.
34133419
* - **strip** -- Strip all metadata from image, bool.
34143420
* - **background** -- Background value, std::vector<double>.
34153421
* - **page_height** -- Set page height for multipage save, int.

libvips/foreign/heifsave.c

+30
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ typedef struct _VipsForeignSaveHeif {
8383
*/
8484
int speed;
8585

86+
/* Chroma subsampling.
87+
*/
88+
VipsForeignSubsample subsample_mode;
89+
8690
/* The image we save. This is a copy of save->ready since we need to
8791
* be able to update the metadata.
8892
*/
@@ -319,6 +323,7 @@ vips_foreign_save_heif_build( VipsObject *object )
319323

320324
struct heif_error error;
321325
struct heif_writer writer;
326+
char *chroma;
322327

323328
if( VIPS_OBJECT_CLASS( vips_foreign_save_heif_parent_class )->
324329
build( object ) )
@@ -363,6 +368,17 @@ vips_foreign_save_heif_build( VipsObject *object )
363368
return( -1 );
364369
}
365370

371+
chroma = heif->subsample_mode == VIPS_FOREIGN_SUBSAMPLE_OFF ||
372+
( heif->subsample_mode == VIPS_FOREIGN_SUBSAMPLE_AUTO &&
373+
heif->Q > 90 ) ? "444" : "420";
374+
error = heif_encoder_set_parameter_string( heif->encoder,
375+
"chroma", chroma );
376+
if( error.code &&
377+
error.subcode != heif_suberror_Unsupported_parameter ) {
378+
vips__heif_error( &error );
379+
return( -1 );
380+
}
381+
366382
/* TODO .. support extra per-encoder params with
367383
* heif_encoder_list_parameters().
368384
*/
@@ -487,6 +503,13 @@ vips_foreign_save_heif_class_init( VipsForeignSaveHeifClass *class )
487503
G_STRUCT_OFFSET( VipsForeignSaveHeif, speed ),
488504
0, 8, 5 );
489505

506+
VIPS_ARG_ENUM( class, "subsample_mode", 16,
507+
_( "Subsample mode" ),
508+
_( "Select chroma subsample operation mode" ),
509+
VIPS_ARGUMENT_OPTIONAL_INPUT,
510+
G_STRUCT_OFFSET( VipsForeignSaveHeif, subsample_mode ),
511+
VIPS_TYPE_FOREIGN_SUBSAMPLE,
512+
VIPS_FOREIGN_SUBSAMPLE_AUTO );
490513
}
491514

492515
static void
@@ -496,6 +519,7 @@ vips_foreign_save_heif_init( VipsForeignSaveHeif *heif )
496519
heif->Q = 50;
497520
heif->compression = VIPS_FOREIGN_HEIF_COMPRESSION_HEVC;
498521
heif->speed = 5;
522+
heif->subsample_mode = VIPS_FOREIGN_SUBSAMPLE_AUTO;
499523
}
500524

501525
typedef struct _VipsForeignSaveHeifFile {
@@ -689,6 +713,7 @@ vips_foreign_save_heif_target_init( VipsForeignSaveHeifTarget *target )
689713
* * @lossless: %gboolean, enable lossless encoding
690714
* * @compression: #VipsForeignHeifCompression, write with this compression
691715
* * @speed: %gint, CPU effort, 0 slowest - 8 fastest, AV1 compression only
716+
* * @subsample_mode: #VipsForeignSubsample, chroma subsampling mode
692717
*
693718
* Write a VIPS image to a file in HEIF format.
694719
*
@@ -702,6 +727,9 @@ vips_foreign_save_heif_target_init( VipsForeignSaveHeifTarget *target )
702727
* Use @speed to control the CPU effort spent improving compression.
703728
* This is currently only applicable to AV1 encoders, defaults to 5.
704729
*
730+
* Chroma subsampling is normally automatically disabled for Q > 90. You can
731+
* force the subsampling mode with @subsample_mode.
732+
*
705733
* See also: vips_image_write_to_file(), vips_heifload().
706734
*
707735
* Returns: 0 on success, -1 on error.
@@ -732,6 +760,7 @@ vips_heifsave( VipsImage *in, const char *filename, ... )
732760
* * @lossless: %gboolean, enable lossless encoding
733761
* * @compression: #VipsForeignHeifCompression, write with this compression
734762
* * @speed: %gint, CPU effort, 0 slowest - 8 fastest, AV1 compression only
763+
* * @subsample_mode: #VipsForeignSubsample, chroma subsampling mode
735764
*
736765
* As vips_heifsave(), but save to a memory buffer.
737766
*
@@ -783,6 +812,7 @@ vips_heifsave_buffer( VipsImage *in, void **buf, size_t *len, ... )
783812
* * @lossless: %gboolean, enable lossless encoding
784813
* * @compression: #VipsForeignHeifCompression, write with this compression
785814
* * @speed: %gint, CPU effort, 0 slowest - 8 fastest, AV1 compression only
815+
* * @subsample_mode: #VipsForeignSubsample, chroma subsampling mode
786816
*
787817
* As vips_heifsave(), but save to a target.
788818
*

libvips/foreign/jpegsave.c

+11-11
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ typedef struct _VipsForeignSaveJpeg {
8484
* on will always enable subsampling
8585
* off will always disable subsampling
8686
*/
87-
VipsForeignJpegSubsample subsample_mode;
87+
VipsForeignSubsample subsample_mode;
8888

8989
/* Apply trellis quantisation to each 8x8 block.
9090
*/
@@ -133,8 +133,8 @@ vips_foreign_save_jpeg_build( VipsObject *object )
133133
*/
134134
if( vips_object_argument_isset( object, "no_subsample" ) )
135135
jpeg->subsample_mode = jpeg->no_subsample ?
136-
VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF :
137-
VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO;
136+
VIPS_FOREIGN_SUBSAMPLE_OFF :
137+
VIPS_FOREIGN_SUBSAMPLE_AUTO;
138138

139139
return( 0 );
140140
}
@@ -229,15 +229,15 @@ vips_foreign_save_jpeg_class_init( VipsForeignSaveJpegClass *class )
229229
_( "Select chroma subsample operation mode" ),
230230
VIPS_ARGUMENT_OPTIONAL_INPUT,
231231
G_STRUCT_OFFSET( VipsForeignSaveJpeg, subsample_mode ),
232-
VIPS_TYPE_FOREIGN_JPEG_SUBSAMPLE,
233-
VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO );
232+
VIPS_TYPE_FOREIGN_SUBSAMPLE,
233+
VIPS_FOREIGN_SUBSAMPLE_AUTO );
234234
}
235235

236236
static void
237237
vips_foreign_save_jpeg_init( VipsForeignSaveJpeg *jpeg )
238238
{
239239
jpeg->Q = 75;
240-
jpeg->subsample_mode = VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO;
240+
jpeg->subsample_mode = VIPS_FOREIGN_SUBSAMPLE_AUTO;
241241
}
242242

243243
typedef struct _VipsForeignSaveJpegTarget {
@@ -529,7 +529,7 @@ vips_foreign_save_jpeg_mime_init( VipsForeignSaveJpegMime *mime )
529529
* * @optimize_coding: %gboolean, compute optimal Huffman coding tables
530530
* * @interlace: %gboolean, write an interlaced (progressive) jpeg
531531
* * @strip: %gboolean, remove all metadata from image
532-
* * @subsample_mode: #VipsForeignJpegSubsample, chroma subsampling mode
532+
* * @subsample_mode: #VipsForeignSubsample, chroma subsampling mode
533533
* * @trellis_quant: %gboolean, apply trellis quantisation to each 8x8 block
534534
* * @overshoot_deringing: %gboolean, overshoot samples with extreme values
535535
* * @optimize_scans: %gboolean, split DCT coefficients into separate scans
@@ -558,7 +558,7 @@ vips_foreign_save_jpeg_mime_init( VipsForeignSaveJpegMime *mime )
558558
* written into the output file.
559559
*
560560
* Chroma subsampling is normally automatically disabled for Q > 90. You can
561-
* force the subsampling mode with @@subsample_mode.
561+
* force the subsampling mode with @subsample_mode.
562562
*
563563
* If @trellis_quant is set and the version of libjpeg supports it
564564
* (e.g. mozjpeg >= 3.0), apply trellis quantisation to each 8x8 block.
@@ -645,7 +645,7 @@ vips_jpegsave( VipsImage *in, const char *filename, ... )
645645
* * @optimize_coding: %gboolean, compute optimal Huffman coding tables
646646
* * @interlace: %gboolean, write an interlaced (progressive) jpeg
647647
* * @strip: %gboolean, remove all metadata from image
648-
* * @subsample_mode: #VipsForeignJpegSubsample, chroma subsampling mode
648+
* * @subsample_mode: #VipsForeignSubsample, chroma subsampling mode
649649
* * @trellis_quant: %gboolean, apply trellis quantisation to each 8x8 block
650650
* * @overshoot_deringing: %gboolean, overshoot samples with extreme values
651651
* * @optimize_scans: %gboolean, split DCT coefficients into separate scans
@@ -684,7 +684,7 @@ vips_jpegsave_target( VipsImage *in, VipsTarget *target, ... )
684684
* * @optimize_coding: %gboolean, compute optimal Huffman coding tables
685685
* * @interlace: %gboolean, write an interlaced (progressive) jpeg
686686
* * @strip: %gboolean, remove all metadata from image
687-
* * @subsample_mode: #VipsForeignJpegSubsample, chroma subsampling mode
687+
* * @subsample_mode: #VipsForeignSubsample, chroma subsampling mode
688688
* * @trellis_quant: %gboolean, apply trellis quantisation to each 8x8 block
689689
* * @overshoot_deringing: %gboolean, overshoot samples with extreme values
690690
* * @optimize_scans: %gboolean, split DCT coefficients into separate scans
@@ -740,7 +740,7 @@ vips_jpegsave_buffer( VipsImage *in, void **buf, size_t *len, ... )
740740
* * @optimize_coding: %gboolean, compute optimal Huffman coding tables
741741
* * @interlace: %gboolean, write an interlaced (progressive) jpeg
742742
* * @strip: %gboolean, remove all metadata from image
743-
* * @subsample_mode: #VipsForeignJpegSubsample, chroma subsampling mode
743+
* * @subsample_mode: #VipsForeignSubsample, chroma subsampling mode
744744
* * @trellis_quant: %gboolean, apply trellis quantisation to each 8x8 block
745745
* * @overshoot_deringing: %gboolean, overshoot samples with extreme values
746746
* * @optimize_scans: %gboolean, split DCT coefficients into separate scans

libvips/foreign/pforeign.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ int vips__jpeg_write_target( VipsImage *in, VipsTarget *target,
168168
gboolean optimize_coding, gboolean progressive, gboolean strip,
169169
gboolean trellis_quant, gboolean overshoot_deringing,
170170
gboolean optimize_scans, int quant_table,
171-
VipsForeignJpegSubsample subsample_mode );
171+
VipsForeignSubsample subsample_mode );
172172

173173
int vips__jpeg_read_source( VipsSource *source, VipsImage *out,
174174
gboolean header_only, int shrink, int fail, gboolean autorotate );

libvips/foreign/vips2jpeg.c

+4-4
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ write_vips( Write *write, int qfac, const char *profile,
540540
gboolean optimize_coding, gboolean progressive, gboolean strip,
541541
gboolean trellis_quant, gboolean overshoot_deringing,
542542
gboolean optimize_scans, int quant_table,
543-
VipsForeignJpegSubsample subsample_mode )
543+
VipsForeignSubsample subsample_mode )
544544
{
545545
VipsImage *in;
546546
J_COLOR_SPACE space;
@@ -687,8 +687,8 @@ write_vips( Write *write, int qfac, const char *profile,
687687
if( progressive )
688688
jpeg_simple_progression( &write->cinfo );
689689

690-
if( subsample_mode == VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF ||
691-
(subsample_mode == VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO &&
690+
if( subsample_mode == VIPS_FOREIGN_SUBSAMPLE_OFF ||
691+
(subsample_mode == VIPS_FOREIGN_SUBSAMPLE_AUTO &&
692692
qfac >= 90) ) {
693693
int i;
694694

@@ -846,7 +846,7 @@ vips__jpeg_write_target( VipsImage *in, VipsTarget *target,
846846
gboolean optimize_coding, gboolean progressive,
847847
gboolean strip, gboolean trellis_quant,
848848
gboolean overshoot_deringing, gboolean optimize_scans,
849-
int quant_table, VipsForeignJpegSubsample subsample_mode)
849+
int quant_table, VipsForeignSubsample subsample_mode)
850850
{
851851
Write *write;
852852

libvips/include/vips/enumtypes.h

+2
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ GType vips_foreign_flags_get_type (void) G_GNUC_CONST;
5858
#define VIPS_TYPE_FOREIGN_FLAGS (vips_foreign_flags_get_type())
5959
GType vips_saveable_get_type (void) G_GNUC_CONST;
6060
#define VIPS_TYPE_SAVEABLE (vips_saveable_get_type())
61+
GType vips_foreign_subsample_get_type (void) G_GNUC_CONST;
62+
#define VIPS_TYPE_FOREIGN_SUBSAMPLE (vips_foreign_subsample_get_type())
6163
GType vips_foreign_jpeg_subsample_get_type (void) G_GNUC_CONST;
6264
#define VIPS_TYPE_FOREIGN_JPEG_SUBSAMPLE (vips_foreign_jpeg_subsample_get_type())
6365
GType vips_foreign_webp_preset_get_type (void) G_GNUC_CONST;

libvips/include/vips/foreign.h

+17
Original file line numberDiff line numberDiff line change
@@ -370,13 +370,30 @@ int vips_openslideload( const char *filename, VipsImage **out, ... )
370370
int vips_openslideload_source( VipsSource *source, VipsImage **out, ... )
371371
__attribute__((sentinel));
372372

373+
/**
374+
* VipsForeignSubsample:
375+
* @VIPS_FOREIGN_SUBSAMPLE_AUTO: prevent subsampling when quality > 90
376+
* @VIPS_FOREIGN_SUBSAMPLE_ON: always perform subsampling
377+
* @VIPS_FOREIGN_SUBSAMPLE_OFF: never perform subsampling
378+
*
379+
* Set subsampling mode.
380+
*/
381+
typedef enum {
382+
VIPS_FOREIGN_SUBSAMPLE_AUTO,
383+
VIPS_FOREIGN_SUBSAMPLE_ON,
384+
VIPS_FOREIGN_SUBSAMPLE_OFF,
385+
VIPS_FOREIGN_SUBSAMPLE_LAST
386+
} VipsForeignSubsample;
387+
373388
/**
374389
* VipsForeignJpegSubsample:
375390
* @VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO: default preset
376391
* @VIPS_FOREIGN_JPEG_SUBSAMPLE_ON: always perform subsampling
377392
* @VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF: never perform subsampling
378393
*
379394
* Set jpeg subsampling mode.
395+
*
396+
* DEPRECATED: use #VipsForeignSubsample
380397
*/
381398
typedef enum {
382399
VIPS_FOREIGN_JPEG_SUBSAMPLE_AUTO,

libvips/iofuncs/enumtypes.c

+19
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,25 @@ vips_saveable_get_type( void )
500500
return( etype );
501501
}
502502
GType
503+
vips_foreign_subsample_get_type( void )
504+
{
505+
static GType etype = 0;
506+
507+
if( etype == 0 ) {
508+
static const GEnumValue values[] = {
509+
{VIPS_FOREIGN_SUBSAMPLE_AUTO, "VIPS_FOREIGN_SUBSAMPLE_AUTO", "auto"},
510+
{VIPS_FOREIGN_SUBSAMPLE_ON, "VIPS_FOREIGN_SUBSAMPLE_ON", "on"},
511+
{VIPS_FOREIGN_SUBSAMPLE_OFF, "VIPS_FOREIGN_SUBSAMPLE_OFF", "off"},
512+
{VIPS_FOREIGN_SUBSAMPLE_LAST, "VIPS_FOREIGN_SUBSAMPLE_LAST", "last"},
513+
{0, NULL, NULL}
514+
};
515+
516+
etype = g_enum_register_static( "VipsForeignSubsample", values );
517+
}
518+
519+
return( etype );
520+
}
521+
GType
503522
vips_foreign_jpeg_subsample_get_type( void )
504523
{
505524
static GType etype = 0;

test/test-suite/test_foreign.py

+5
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,11 @@ def test_heifsave(self):
11041104
b2 = self.mono.heifsave_buffer(Q=90, compression="av1")
11051105
assert len(b2) > len(b1)
11061106

1107+
# Chroma subsampling should produce smaller file size for same Q
1108+
b1 = self.colour.heifsave_buffer(compression="av1", subsample_mode="on")
1109+
b2 = self.colour.heifsave_buffer(compression="av1", subsample_mode="off")
1110+
assert len(b2) > len(b1)
1111+
11071112
# try saving an image with an ICC profile and reading it back
11081113
# not all libheif have profile support, so put it in an if
11091114
buf = self.colour.heifsave_buffer(Q=10, compression="av1")

0 commit comments

Comments
 (0)