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:
parent
f6281284a1
commit
46a67cfab9
@ -11,6 +11,7 @@
|
|||||||
- don't use atexit for cleanup, it's too unreliable
|
- don't use atexit for cleanup, it's too unreliable
|
||||||
- tiff writer loops for the whole image rather than per page [LionelArn2]
|
- tiff writer loops for the whole image rather than per page [LionelArn2]
|
||||||
- fix VipsSource with named pipes [vibbix]
|
- fix VipsSource with named pipes [vibbix]
|
||||||
|
- added restart_interval option to jpegsave [manthey]
|
||||||
|
|
||||||
16/8/21 started 8.11.4
|
16/8/21 started 8.11.4
|
||||||
- fix off-by-one error in new rank fast path
|
- fix off-by-one error in new rank fast path
|
||||||
|
@ -103,6 +103,10 @@ typedef struct _VipsForeignSaveJpeg {
|
|||||||
*/
|
*/
|
||||||
int quant_table;
|
int quant_table;
|
||||||
|
|
||||||
|
/* Use an MCU restart interval.
|
||||||
|
*/
|
||||||
|
int restart_interval;
|
||||||
|
|
||||||
} VipsForeignSaveJpeg;
|
} VipsForeignSaveJpeg;
|
||||||
|
|
||||||
typedef VipsForeignSaveClass VipsForeignSaveJpegClass;
|
typedef VipsForeignSaveClass VipsForeignSaveJpegClass;
|
||||||
@ -231,6 +235,14 @@ vips_foreign_save_jpeg_class_init( VipsForeignSaveJpegClass *class )
|
|||||||
G_STRUCT_OFFSET( VipsForeignSaveJpeg, subsample_mode ),
|
G_STRUCT_OFFSET( VipsForeignSaveJpeg, subsample_mode ),
|
||||||
VIPS_TYPE_FOREIGN_SUBSAMPLE,
|
VIPS_TYPE_FOREIGN_SUBSAMPLE,
|
||||||
VIPS_FOREIGN_SUBSAMPLE_AUTO );
|
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
|
static void
|
||||||
@ -268,7 +280,8 @@ vips_foreign_save_jpeg_target_build( VipsObject *object )
|
|||||||
jpeg->Q, jpeg->profile, jpeg->optimize_coding,
|
jpeg->Q, jpeg->profile, jpeg->optimize_coding,
|
||||||
jpeg->interlace, save->strip, jpeg->trellis_quant,
|
jpeg->interlace, save->strip, jpeg->trellis_quant,
|
||||||
jpeg->overshoot_deringing, jpeg->optimize_scans,
|
jpeg->overshoot_deringing, jpeg->optimize_scans,
|
||||||
jpeg->quant_table, jpeg->subsample_mode ) )
|
jpeg->quant_table, jpeg->subsample_mode,
|
||||||
|
jpeg->restart_interval ) )
|
||||||
return( -1 );
|
return( -1 );
|
||||||
|
|
||||||
return( 0 );
|
return( 0 );
|
||||||
@ -334,7 +347,8 @@ vips_foreign_save_jpeg_file_build( VipsObject *object )
|
|||||||
jpeg->Q, jpeg->profile, jpeg->optimize_coding,
|
jpeg->Q, jpeg->profile, jpeg->optimize_coding,
|
||||||
jpeg->interlace, save->strip, jpeg->trellis_quant,
|
jpeg->interlace, save->strip, jpeg->trellis_quant,
|
||||||
jpeg->overshoot_deringing, jpeg->optimize_scans,
|
jpeg->overshoot_deringing, jpeg->optimize_scans,
|
||||||
jpeg->quant_table, jpeg->subsample_mode ) ) {
|
jpeg->quant_table, jpeg->subsample_mode,
|
||||||
|
jpeg->restart_interval ) ) {
|
||||||
VIPS_UNREF( target );
|
VIPS_UNREF( target );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
@ -404,7 +418,8 @@ vips_foreign_save_jpeg_buffer_build( VipsObject *object )
|
|||||||
jpeg->Q, jpeg->profile, jpeg->optimize_coding,
|
jpeg->Q, jpeg->profile, jpeg->optimize_coding,
|
||||||
jpeg->interlace, save->strip, jpeg->trellis_quant,
|
jpeg->interlace, save->strip, jpeg->trellis_quant,
|
||||||
jpeg->overshoot_deringing, jpeg->optimize_scans,
|
jpeg->overshoot_deringing, jpeg->optimize_scans,
|
||||||
jpeg->quant_table, jpeg->subsample_mode ) ) {
|
jpeg->quant_table, jpeg->subsample_mode,
|
||||||
|
jpeg->restart_interval ) ) {
|
||||||
VIPS_UNREF( target );
|
VIPS_UNREF( target );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
@ -477,7 +492,8 @@ vips_foreign_save_jpeg_mime_build( VipsObject *object )
|
|||||||
jpeg->Q, jpeg->profile, jpeg->optimize_coding,
|
jpeg->Q, jpeg->profile, jpeg->optimize_coding,
|
||||||
jpeg->interlace, save->strip, jpeg->trellis_quant,
|
jpeg->interlace, save->strip, jpeg->trellis_quant,
|
||||||
jpeg->overshoot_deringing, jpeg->optimize_scans,
|
jpeg->overshoot_deringing, jpeg->optimize_scans,
|
||||||
jpeg->quant_table, jpeg->subsample_mode ) ) {
|
jpeg->quant_table, jpeg->subsample_mode,
|
||||||
|
jpeg->restart_interval ) ) {
|
||||||
VIPS_UNREF( target );
|
VIPS_UNREF( target );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
@ -534,6 +550,7 @@ vips_foreign_save_jpeg_mime_init( VipsForeignSaveJpegMime *mime )
|
|||||||
* * @overshoot_deringing: %gboolean, overshoot samples with extreme values
|
* * @overshoot_deringing: %gboolean, overshoot samples with extreme values
|
||||||
* * @optimize_scans: %gboolean, split DCT coefficients into separate scans
|
* * @optimize_scans: %gboolean, split DCT coefficients into separate scans
|
||||||
* * @quant_table: %gint, quantization table index
|
* * @quant_table: %gint, quantization table index
|
||||||
|
* * @restart_interval: %gint, restart interval in mcu
|
||||||
*
|
*
|
||||||
* Write a VIPS image to a file as JPEG.
|
* 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,
|
* For maximum compression with mozjpeg, a useful set of options is `strip,
|
||||||
* optimize-coding, interlace, optimize-scans, trellis-quant, quant_table=3`.
|
* 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
|
* The image is automatically converted to RGB, Monochrome or CMYK before
|
||||||
* saving.
|
* saving.
|
||||||
*
|
*
|
||||||
@ -650,6 +673,7 @@ vips_jpegsave( VipsImage *in, const char *filename, ... )
|
|||||||
* * @overshoot_deringing: %gboolean, overshoot samples with extreme values
|
* * @overshoot_deringing: %gboolean, overshoot samples with extreme values
|
||||||
* * @optimize_scans: %gboolean, split DCT coefficients into separate scans
|
* * @optimize_scans: %gboolean, split DCT coefficients into separate scans
|
||||||
* * @quant_table: %gint, quantization table index
|
* * @quant_table: %gint, quantization table index
|
||||||
|
* * @restart_interval: %gint, restart interval in mcu
|
||||||
*
|
*
|
||||||
* As vips_jpegsave(), but save to a target.
|
* 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
|
* * @overshoot_deringing: %gboolean, overshoot samples with extreme values
|
||||||
* * @optimize_scans: %gboolean, split DCT coefficients into separate scans
|
* * @optimize_scans: %gboolean, split DCT coefficients into separate scans
|
||||||
* * @quant_table: %gint, quantization table index
|
* * @quant_table: %gint, quantization table index
|
||||||
|
* * @restart_interval: %gint, restart interval in mcu
|
||||||
*
|
*
|
||||||
* As vips_jpegsave(), but save to a memory buffer.
|
* 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
|
* * @overshoot_deringing: %gboolean, overshoot samples with extreme values
|
||||||
* * @optimize_scans: %gboolean, split DCT coefficients into separate scans
|
* * @optimize_scans: %gboolean, split DCT coefficients into separate scans
|
||||||
* * @quant_table: %gint, quantization table index
|
* * @quant_table: %gint, quantization table index
|
||||||
|
* * @restart_interval: %gint, restart interval in mcu
|
||||||
*
|
*
|
||||||
* As vips_jpegsave(), but save as a mime jpeg on stdout.
|
* As vips_jpegsave(), but save as a mime jpeg on stdout.
|
||||||
*
|
*
|
||||||
|
@ -174,7 +174,7 @@ int vips__jpeg_write_target( VipsImage *in, VipsTarget *target,
|
|||||||
gboolean optimize_coding, gboolean progressive, gboolean strip,
|
gboolean optimize_coding, gboolean progressive, gboolean strip,
|
||||||
gboolean trellis_quant, gboolean overshoot_deringing,
|
gboolean trellis_quant, gboolean overshoot_deringing,
|
||||||
gboolean optimize_scans, int quant_table,
|
gboolean optimize_scans, int quant_table,
|
||||||
VipsForeignSubsample subsample_mode );
|
VipsForeignSubsample subsample_mode, int restart_interval );
|
||||||
|
|
||||||
int vips__jpeg_read_source( VipsSource *source, VipsImage *out,
|
int vips__jpeg_read_source( VipsSource *source, VipsImage *out,
|
||||||
gboolean header_only, int shrink, int fail, gboolean autorotate );
|
gboolean header_only, int shrink, int fail, gboolean autorotate );
|
||||||
@ -252,5 +252,3 @@ extern const char *vips__jxl_suffs[];
|
|||||||
#endif /*__cplusplus*/
|
#endif /*__cplusplus*/
|
||||||
|
|
||||||
#endif /*VIPS_PFOREIGN_H*/
|
#endif /*VIPS_PFOREIGN_H*/
|
||||||
|
|
||||||
|
|
||||||
|
@ -96,6 +96,8 @@
|
|||||||
* - add subsample_mode, deprecate no_subsample
|
* - add subsample_mode, deprecate no_subsample
|
||||||
* 13/9/20
|
* 13/9/20
|
||||||
* - only write JFIF resolution if we don't have EXIF
|
* - 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 optimize_coding, gboolean progressive, gboolean strip,
|
||||||
gboolean trellis_quant, gboolean overshoot_deringing,
|
gboolean trellis_quant, gboolean overshoot_deringing,
|
||||||
gboolean optimize_scans, int quant_table,
|
gboolean optimize_scans, int quant_table,
|
||||||
VipsForeignSubsample subsample_mode )
|
VipsForeignSubsample subsample_mode, int restart_interval )
|
||||||
{
|
{
|
||||||
VipsImage *in;
|
VipsImage *in;
|
||||||
J_COLOR_SPACE space;
|
J_COLOR_SPACE space;
|
||||||
@ -607,6 +609,10 @@ write_vips( Write *write, int qfac, const char *profile,
|
|||||||
*/
|
*/
|
||||||
write->cinfo.optimize_coding = optimize_coding;
|
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
|
#ifdef HAVE_JPEG_EXT_PARAMS
|
||||||
/* Apply trellis quantisation to each 8x8 block. Implies
|
/* Apply trellis quantisation to each 8x8 block. Implies
|
||||||
* "optimize_coding".
|
* "optimize_coding".
|
||||||
@ -846,7 +852,8 @@ vips__jpeg_write_target( VipsImage *in, VipsTarget *target,
|
|||||||
gboolean optimize_coding, gboolean progressive,
|
gboolean optimize_coding, gboolean progressive,
|
||||||
gboolean strip, gboolean trellis_quant,
|
gboolean strip, gboolean trellis_quant,
|
||||||
gboolean overshoot_deringing, gboolean optimize_scans,
|
gboolean overshoot_deringing, gboolean optimize_scans,
|
||||||
int quant_table, VipsForeignSubsample subsample_mode)
|
int quant_table, VipsForeignSubsample subsample_mode,
|
||||||
|
int restart_interval)
|
||||||
{
|
{
|
||||||
Write *write;
|
Write *write;
|
||||||
|
|
||||||
@ -873,7 +880,7 @@ vips__jpeg_write_target( VipsImage *in, VipsTarget *target,
|
|||||||
if( write_vips( write,
|
if( write_vips( write,
|
||||||
Q, profile, optimize_coding, progressive, strip,
|
Q, profile, optimize_coding, progressive, strip,
|
||||||
trellis_quant, overshoot_deringing, optimize_scans,
|
trellis_quant, overshoot_deringing, optimize_scans,
|
||||||
quant_table, subsample_mode ) ) {
|
quant_table, subsample_mode, restart_interval ) ) {
|
||||||
write_destroy( write );
|
write_destroy( write );
|
||||||
return( -1 );
|
return( -1 );
|
||||||
}
|
}
|
||||||
|
@ -294,6 +294,15 @@ class TestForeign:
|
|||||||
assert len(q90_subsample_on) < len(q90)
|
assert len(q90_subsample_on) < len(q90)
|
||||||
assert len(q90_subsample_off) == len(q90_subsample_auto)
|
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")
|
@skip_if_no("jpegload")
|
||||||
def test_truncated(self):
|
def test_truncated(self):
|
||||||
# This should open (there's enough there for the header)
|
# This should open (there's enough there for the header)
|
||||||
|
Loading…
Reference in New Issue
Block a user