Skip to content

Commit 46a67cf

Browse files
authored
Add jpeg restart_interval option. (libvips#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.
1 parent f628128 commit 46a67cf

File tree

5 files changed

+51
-10
lines changed

5 files changed

+51
-10
lines changed

ChangeLog

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- don't use atexit for cleanup, it's too unreliable
1212
- tiff writer loops for the whole image rather than per page [LionelArn2]
1313
- fix VipsSource with named pipes [vibbix]
14+
- added restart_interval option to jpegsave [manthey]
1415

1516
16/8/21 started 8.11.4
1617
- fix off-by-one error in new rank fast path

libvips/foreign/jpegsave.c

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ typedef struct _VipsForeignSaveJpeg {
103103
*/
104104
int quant_table;
105105

106+
/* Use an MCU restart interval.
107+
*/
108+
int restart_interval;
109+
106110
} VipsForeignSaveJpeg;
107111

108112
typedef VipsForeignSaveClass VipsForeignSaveJpegClass;
@@ -231,6 +235,14 @@ vips_foreign_save_jpeg_class_init( VipsForeignSaveJpegClass *class )
231235
G_STRUCT_OFFSET( VipsForeignSaveJpeg, subsample_mode ),
232236
VIPS_TYPE_FOREIGN_SUBSAMPLE,
233237
VIPS_FOREIGN_SUBSAMPLE_AUTO );
238+
239+
VIPS_ARG_INT( class, "restart_interval", 20,
240+
_( "Restart interval" ),
241+
_( "Add restart markers every specified number of mcu" ),
242+
VIPS_ARGUMENT_OPTIONAL_INPUT,
243+
G_STRUCT_OFFSET( VipsForeignSaveJpeg, restart_interval ),
244+
0, INT_MAX, 0 );
245+
234246
}
235247

236248
static void
@@ -268,7 +280,8 @@ vips_foreign_save_jpeg_target_build( VipsObject *object )
268280
jpeg->Q, jpeg->profile, jpeg->optimize_coding,
269281
jpeg->interlace, save->strip, jpeg->trellis_quant,
270282
jpeg->overshoot_deringing, jpeg->optimize_scans,
271-
jpeg->quant_table, jpeg->subsample_mode ) )
283+
jpeg->quant_table, jpeg->subsample_mode,
284+
jpeg->restart_interval ) )
272285
return( -1 );
273286

274287
return( 0 );
@@ -334,7 +347,8 @@ vips_foreign_save_jpeg_file_build( VipsObject *object )
334347
jpeg->Q, jpeg->profile, jpeg->optimize_coding,
335348
jpeg->interlace, save->strip, jpeg->trellis_quant,
336349
jpeg->overshoot_deringing, jpeg->optimize_scans,
337-
jpeg->quant_table, jpeg->subsample_mode ) ) {
350+
jpeg->quant_table, jpeg->subsample_mode,
351+
jpeg->restart_interval ) ) {
338352
VIPS_UNREF( target );
339353
return( -1 );
340354
}
@@ -404,7 +418,8 @@ vips_foreign_save_jpeg_buffer_build( VipsObject *object )
404418
jpeg->Q, jpeg->profile, jpeg->optimize_coding,
405419
jpeg->interlace, save->strip, jpeg->trellis_quant,
406420
jpeg->overshoot_deringing, jpeg->optimize_scans,
407-
jpeg->quant_table, jpeg->subsample_mode ) ) {
421+
jpeg->quant_table, jpeg->subsample_mode,
422+
jpeg->restart_interval ) ) {
408423
VIPS_UNREF( target );
409424
return( -1 );
410425
}
@@ -477,7 +492,8 @@ vips_foreign_save_jpeg_mime_build( VipsObject *object )
477492
jpeg->Q, jpeg->profile, jpeg->optimize_coding,
478493
jpeg->interlace, save->strip, jpeg->trellis_quant,
479494
jpeg->overshoot_deringing, jpeg->optimize_scans,
480-
jpeg->quant_table, jpeg->subsample_mode ) ) {
495+
jpeg->quant_table, jpeg->subsample_mode,
496+
jpeg->restart_interval ) ) {
481497
VIPS_UNREF( target );
482498
return( -1 );
483499
}
@@ -534,6 +550,7 @@ vips_foreign_save_jpeg_mime_init( VipsForeignSaveJpegMime *mime )
534550
* * @overshoot_deringing: %gboolean, overshoot samples with extreme values
535551
* * @optimize_scans: %gboolean, split DCT coefficients into separate scans
536552
* * @quant_table: %gint, quantization table index
553+
* * @restart_interval: %gint, restart interval in mcu
537554
*
538555
* Write a VIPS image to a file as JPEG.
539556
*
@@ -604,6 +621,12 @@ vips_foreign_save_jpeg_mime_init( VipsForeignSaveJpegMime *mime )
604621
* For maximum compression with mozjpeg, a useful set of options is `strip,
605622
* optimize-coding, interlace, optimize-scans, trellis-quant, quant_table=3`.
606623
*
624+
* By default, the output stream won't have restart markers. If a non-zero
625+
* restart_interval is specified, a restart marker will be added after each
626+
* specified number of MCU blocks. This makes the stream more recoverable
627+
* if there are transmission errors, but also allows for some decoders to read
628+
* part of the JPEG without decoding the whole stream.
629+
*
607630
* The image is automatically converted to RGB, Monochrome or CMYK before
608631
* saving.
609632
*
@@ -650,6 +673,7 @@ vips_jpegsave( VipsImage *in, const char *filename, ... )
650673
* * @overshoot_deringing: %gboolean, overshoot samples with extreme values
651674
* * @optimize_scans: %gboolean, split DCT coefficients into separate scans
652675
* * @quant_table: %gint, quantization table index
676+
* * @restart_interval: %gint, restart interval in mcu
653677
*
654678
* As vips_jpegsave(), but save to a target.
655679
*
@@ -689,6 +713,7 @@ vips_jpegsave_target( VipsImage *in, VipsTarget *target, ... )
689713
* * @overshoot_deringing: %gboolean, overshoot samples with extreme values
690714
* * @optimize_scans: %gboolean, split DCT coefficients into separate scans
691715
* * @quant_table: %gint, quantization table index
716+
* * @restart_interval: %gint, restart interval in mcu
692717
*
693718
* As vips_jpegsave(), but save to a memory buffer.
694719
*
@@ -745,6 +770,7 @@ vips_jpegsave_buffer( VipsImage *in, void **buf, size_t *len, ... )
745770
* * @overshoot_deringing: %gboolean, overshoot samples with extreme values
746771
* * @optimize_scans: %gboolean, split DCT coefficients into separate scans
747772
* * @quant_table: %gint, quantization table index
773+
* * @restart_interval: %gint, restart interval in mcu
748774
*
749775
* As vips_jpegsave(), but save as a mime jpeg on stdout.
750776
*

libvips/foreign/pforeign.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ int vips__jpeg_write_target( VipsImage *in, VipsTarget *target,
174174
gboolean optimize_coding, gboolean progressive, gboolean strip,
175175
gboolean trellis_quant, gboolean overshoot_deringing,
176176
gboolean optimize_scans, int quant_table,
177-
VipsForeignSubsample subsample_mode );
177+
VipsForeignSubsample subsample_mode, int restart_interval );
178178

179179
int vips__jpeg_read_source( VipsSource *source, VipsImage *out,
180180
gboolean header_only, int shrink, int fail, gboolean autorotate );
@@ -252,5 +252,3 @@ extern const char *vips__jxl_suffs[];
252252
#endif /*__cplusplus*/
253253

254254
#endif /*VIPS_PFOREIGN_H*/
255-
256-

libvips/foreign/vips2jpeg.c

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@
9696
* - add subsample_mode, deprecate no_subsample
9797
* 13/9/20
9898
* - only write JFIF resolution if we don't have EXIF
99+
* 7/10/21 Manthey
100+
* - add restart_interval
99101
*/
100102

101103
/*
@@ -540,7 +542,7 @@ write_vips( Write *write, int qfac, const char *profile,
540542
gboolean optimize_coding, gboolean progressive, gboolean strip,
541543
gboolean trellis_quant, gboolean overshoot_deringing,
542544
gboolean optimize_scans, int quant_table,
543-
VipsForeignSubsample subsample_mode )
545+
VipsForeignSubsample subsample_mode, int restart_interval )
544546
{
545547
VipsImage *in;
546548
J_COLOR_SPACE space;
@@ -607,6 +609,10 @@ write_vips( Write *write, int qfac, const char *profile,
607609
*/
608610
write->cinfo.optimize_coding = optimize_coding;
609611

612+
/* Use a restart interval.
613+
*/
614+
if( restart_interval > 0 )
615+
write->cinfo.restart_interval = restart_interval;
610616
#ifdef HAVE_JPEG_EXT_PARAMS
611617
/* Apply trellis quantisation to each 8x8 block. Implies
612618
* "optimize_coding".
@@ -846,7 +852,8 @@ vips__jpeg_write_target( VipsImage *in, VipsTarget *target,
846852
gboolean optimize_coding, gboolean progressive,
847853
gboolean strip, gboolean trellis_quant,
848854
gboolean overshoot_deringing, gboolean optimize_scans,
849-
int quant_table, VipsForeignSubsample subsample_mode)
855+
int quant_table, VipsForeignSubsample subsample_mode,
856+
int restart_interval)
850857
{
851858
Write *write;
852859

@@ -873,7 +880,7 @@ vips__jpeg_write_target( VipsImage *in, VipsTarget *target,
873880
if( write_vips( write,
874881
Q, profile, optimize_coding, progressive, strip,
875882
trellis_quant, overshoot_deringing, optimize_scans,
876-
quant_table, subsample_mode ) ) {
883+
quant_table, subsample_mode, restart_interval ) ) {
877884
write_destroy( write );
878885
return( -1 );
879886
}

test/test-suite/test_foreign.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,15 @@ def test_jpegsave(self):
294294
assert len(q90_subsample_on) < len(q90)
295295
assert len(q90_subsample_off) == len(q90_subsample_auto)
296296

297+
# A non-zero restart_interval should result in a bigger file.
298+
# Otherwise, smaller restart intervals will have more restart markers
299+
# and therefore be larger
300+
r0 = im.jpegsave_buffer(restart_interval=0)
301+
r10 = im.jpegsave_buffer(restart_interval=10)
302+
r2 = im.jpegsave_buffer(restart_interval=2)
303+
assert len(r10) > len(r0)
304+
assert len(r2) > len(r10)
305+
297306
@skip_if_no("jpegload")
298307
def test_truncated(self):
299308
# This should open (there's enough there for the header)

0 commit comments

Comments
 (0)