From d1dd41a21fe8251dd57d9069ae388e1b7ae30764 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Tue, 10 Apr 2018 15:18:18 +0100 Subject: [PATCH] strict round down on jpeg shrink libjpeg rounds up on shrink-on-load. In some cases this can leave a dark line along the right and bottom edge, since it only contains (for example) 1/4 of a pixel of data. This change adds a crop after jpeg load so that only complete pixels are output. See https://github.com/lovell/sharp/issues/1185 --- ChangeLog | 1 + libvips/foreign/jpeg2vips.c | 35 ++++++++++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/ChangeLog b/ChangeLog index 627d8e64..c0c417a3 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,7 @@ 12/3/18 started 8.6.4 - better fitting of fonts with overhanging edges [AdriĆ ] - revise C++ example [fangqiao] +- strict round down on jpeg shrink on load [davidwood] 12/2/18 started 8.6.3 - use pkg-config to find libjpeg, if we can diff --git a/libvips/foreign/jpeg2vips.c b/libvips/foreign/jpeg2vips.c index eb59427e..cf4b39d1 100644 --- a/libvips/foreign/jpeg2vips.c +++ b/libvips/foreign/jpeg2vips.c @@ -94,6 +94,8 @@ * - revert previous warning change: libvips reports serious corruption, * like a truncated file, as a warning and we need to be able to catch * that + * 10/4/18 + * - strict round down on shrink-on-load */ /* @@ -151,8 +153,6 @@ /* Stuff we track during a read. */ typedef struct _ReadJpeg { - VipsImage *out; - /* Shrink by this much during load. 1, 2, 4, 8. */ int shrink; @@ -177,6 +177,13 @@ typedef struct _ReadJpeg { * during load. */ gboolean autorotate; + + /* cinfo->output_width and height can be larger than we want since + * libjpeg rounds up on shrink-on-load. This is the real size we will + * output, as opposed to the size we decompress to. + */ + int output_width; + int output_height; } ReadJpeg; /* This can be called many times. @@ -228,7 +235,6 @@ readjpeg_new( VipsImage *out, int shrink, gboolean fail, gboolean autorotate ) if( !(jpeg = VIPS_NEW( out, ReadJpeg )) ) return( NULL ); - jpeg->out = out; jpeg->shrink = shrink; jpeg->fail = fail; jpeg->filename = NULL; @@ -408,6 +414,18 @@ read_jpeg_header( ReadJpeg *jpeg, VipsImage *out ) vips_image_pipelinev( out, VIPS_DEMAND_STYLE_FATSTRIP, NULL ); + /* cinfo->output_width and cinfo->output_height round up with + * shrink-on-load. For example, if the image is 1801 pixels across and + * we shrink by 4, the output will be 450.25 pixels across, + * cinfo->output_width with be 451, and libjpeg will write a black + * column of pixels down the right. + * + * We must strictly round down, since we don't want fractional pixels + * along the bottom and right. + */ + jpeg->output_width = cinfo->image_width / jpeg->shrink; + jpeg->output_height = cinfo->image_height / jpeg->shrink; + /* Interlaced jpegs need lots of memory to read, so our caller needs * to know. */ @@ -691,12 +709,14 @@ read_jpeg_image( ReadJpeg *jpeg, VipsImage *out ) if( vips_image_generate( t[0], NULL, read_jpeg_generate, NULL, jpeg, NULL ) || - vips_sequential( t[0], &t[1], + vips_extract_area( t[0], &t[1], + 0, 0, jpeg->output_width, jpeg->output_height, NULL ) || + vips_sequential( t[1], &t[2], "tile_height", 8, NULL ) ) return( -1 ); - im = t[1]; + im = t[2]; if( jpeg->autorotate ) im = read_jpeg_rotate( VIPS_OBJECT( out ), im ); @@ -735,6 +755,11 @@ vips__jpeg_read( ReadJpeg *jpeg, VipsImage *out, gboolean header_only ) if( read_jpeg_header( jpeg, out ) ) return( -1 ); + /* Patch in the correct size. + */ + out->Xsize = jpeg->output_width; + out->Ysize = jpeg->output_height; + /* Swap width and height if we're going to rotate this image. */ if( jpeg->autorotate ) {