Add jpeg restart_interval option. (#2468)

* Add jpeg restart_interval option.

This allows saving a jpeg with MCU restarts.

* Fix code style.  Add description of restart_interval.

* Add a basic test based on output length.

* Update main change log.
This commit is contained in:
David Manthey 2021-10-08 12:33:32 -04:00 committed by GitHub
parent f6281284a1
commit 46a67cfab9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 51 additions and 10 deletions

View File

@ -11,6 +11,7 @@
- don't use atexit for cleanup, it's too unreliable
- tiff writer loops for the whole image rather than per page [LionelArn2]
- fix VipsSource with named pipes [vibbix]
- added restart_interval option to jpegsave [manthey]
16/8/21 started 8.11.4
- fix off-by-one error in new rank fast path

View File

@ -103,6 +103,10 @@ typedef struct _VipsForeignSaveJpeg {
*/
int quant_table;
/* Use an MCU restart interval.
*/
int restart_interval;
} VipsForeignSaveJpeg;
typedef VipsForeignSaveClass VipsForeignSaveJpegClass;
@ -231,6 +235,14 @@ vips_foreign_save_jpeg_class_init( VipsForeignSaveJpegClass *class )
G_STRUCT_OFFSET( VipsForeignSaveJpeg, subsample_mode ),
VIPS_TYPE_FOREIGN_SUBSAMPLE,
VIPS_FOREIGN_SUBSAMPLE_AUTO );
VIPS_ARG_INT( class, "restart_interval", 20,
_( "Restart interval" ),
_( "Add restart markers every specified number of mcu" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsForeignSaveJpeg, restart_interval ),
0, INT_MAX, 0 );
}
static void
@ -268,7 +280,8 @@ vips_foreign_save_jpeg_target_build( VipsObject *object )
jpeg->Q, jpeg->profile, jpeg->optimize_coding,
jpeg->interlace, save->strip, jpeg->trellis_quant,
jpeg->overshoot_deringing, jpeg->optimize_scans,
jpeg->quant_table, jpeg->subsample_mode ) )
jpeg->quant_table, jpeg->subsample_mode,
jpeg->restart_interval ) )
return( -1 );
return( 0 );
@ -334,7 +347,8 @@ vips_foreign_save_jpeg_file_build( VipsObject *object )
jpeg->Q, jpeg->profile, jpeg->optimize_coding,
jpeg->interlace, save->strip, jpeg->trellis_quant,
jpeg->overshoot_deringing, jpeg->optimize_scans,
jpeg->quant_table, jpeg->subsample_mode ) ) {
jpeg->quant_table, jpeg->subsample_mode,
jpeg->restart_interval ) ) {
VIPS_UNREF( target );
return( -1 );
}
@ -404,7 +418,8 @@ vips_foreign_save_jpeg_buffer_build( VipsObject *object )
jpeg->Q, jpeg->profile, jpeg->optimize_coding,
jpeg->interlace, save->strip, jpeg->trellis_quant,
jpeg->overshoot_deringing, jpeg->optimize_scans,
jpeg->quant_table, jpeg->subsample_mode ) ) {
jpeg->quant_table, jpeg->subsample_mode,
jpeg->restart_interval ) ) {
VIPS_UNREF( target );
return( -1 );
}
@ -477,7 +492,8 @@ vips_foreign_save_jpeg_mime_build( VipsObject *object )
jpeg->Q, jpeg->profile, jpeg->optimize_coding,
jpeg->interlace, save->strip, jpeg->trellis_quant,
jpeg->overshoot_deringing, jpeg->optimize_scans,
jpeg->quant_table, jpeg->subsample_mode ) ) {
jpeg->quant_table, jpeg->subsample_mode,
jpeg->restart_interval ) ) {
VIPS_UNREF( target );
return( -1 );
}
@ -534,6 +550,7 @@ vips_foreign_save_jpeg_mime_init( VipsForeignSaveJpegMime *mime )
* * @overshoot_deringing: %gboolean, overshoot samples with extreme values
* * @optimize_scans: %gboolean, split DCT coefficients into separate scans
* * @quant_table: %gint, quantization table index
* * @restart_interval: %gint, restart interval in mcu
*
* Write a VIPS image to a file as JPEG.
*
@ -604,6 +621,12 @@ vips_foreign_save_jpeg_mime_init( VipsForeignSaveJpegMime *mime )
* For maximum compression with mozjpeg, a useful set of options is `strip,
* optimize-coding, interlace, optimize-scans, trellis-quant, quant_table=3`.
*
* By default, the output stream won't have restart markers. If a non-zero
* restart_interval is specified, a restart marker will be added after each
* specified number of MCU blocks. This makes the stream more recoverable
* if there are transmission errors, but also allows for some decoders to read
* part of the JPEG without decoding the whole stream.
*
* The image is automatically converted to RGB, Monochrome or CMYK before
* saving.
*
@ -650,6 +673,7 @@ vips_jpegsave( VipsImage *in, const char *filename, ... )
* * @overshoot_deringing: %gboolean, overshoot samples with extreme values
* * @optimize_scans: %gboolean, split DCT coefficients into separate scans
* * @quant_table: %gint, quantization table index
* * @restart_interval: %gint, restart interval in mcu
*
* As vips_jpegsave(), but save to a target.
*
@ -689,6 +713,7 @@ vips_jpegsave_target( VipsImage *in, VipsTarget *target, ... )
* * @overshoot_deringing: %gboolean, overshoot samples with extreme values
* * @optimize_scans: %gboolean, split DCT coefficients into separate scans
* * @quant_table: %gint, quantization table index
* * @restart_interval: %gint, restart interval in mcu
*
* As vips_jpegsave(), but save to a memory buffer.
*
@ -745,6 +770,7 @@ vips_jpegsave_buffer( VipsImage *in, void **buf, size_t *len, ... )
* * @overshoot_deringing: %gboolean, overshoot samples with extreme values
* * @optimize_scans: %gboolean, split DCT coefficients into separate scans
* * @quant_table: %gint, quantization table index
* * @restart_interval: %gint, restart interval in mcu
*
* As vips_jpegsave(), but save as a mime jpeg on stdout.
*

View File

@ -174,7 +174,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,
VipsForeignSubsample subsample_mode );
VipsForeignSubsample subsample_mode, int restart_interval );
int vips__jpeg_read_source( VipsSource *source, VipsImage *out,
gboolean header_only, int shrink, int fail, gboolean autorotate );
@ -252,5 +252,3 @@ extern const char *vips__jxl_suffs[];
#endif /*__cplusplus*/
#endif /*VIPS_PFOREIGN_H*/

View File

@ -96,6 +96,8 @@
* - add subsample_mode, deprecate no_subsample
* 13/9/20
* - only write JFIF resolution if we don't have EXIF
* 7/10/21 Manthey
* - add restart_interval
*/
/*
@ -540,7 +542,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,
VipsForeignSubsample subsample_mode )
VipsForeignSubsample subsample_mode, int restart_interval )
{
VipsImage *in;
J_COLOR_SPACE space;
@ -607,6 +609,10 @@ write_vips( Write *write, int qfac, const char *profile,
*/
write->cinfo.optimize_coding = optimize_coding;
/* Use a restart interval.
*/
if( restart_interval > 0 )
write->cinfo.restart_interval = restart_interval;
#ifdef HAVE_JPEG_EXT_PARAMS
/* Apply trellis quantisation to each 8x8 block. Implies
* "optimize_coding".
@ -846,7 +852,8 @@ 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, VipsForeignSubsample subsample_mode)
int quant_table, VipsForeignSubsample subsample_mode,
int restart_interval)
{
Write *write;
@ -873,7 +880,7 @@ vips__jpeg_write_target( VipsImage *in, VipsTarget *target,
if( write_vips( write,
Q, profile, optimize_coding, progressive, strip,
trellis_quant, overshoot_deringing, optimize_scans,
quant_table, subsample_mode ) ) {
quant_table, subsample_mode, restart_interval ) ) {
write_destroy( write );
return( -1 );
}

View File

@ -294,6 +294,15 @@ class TestForeign:
assert len(q90_subsample_on) < len(q90)
assert len(q90_subsample_off) == len(q90_subsample_auto)
# A non-zero restart_interval should result in a bigger file.
# Otherwise, smaller restart intervals will have more restart markers
# and therefore be larger
r0 = im.jpegsave_buffer(restart_interval=0)
r10 = im.jpegsave_buffer(restart_interval=10)
r2 = im.jpegsave_buffer(restart_interval=2)
assert len(r10) > len(r0)
assert len(r2) > len(r10)
@skip_if_no("jpegload")
def test_truncated(self):
# This should open (there's enough there for the header)