diff --git a/ChangeLog b/ChangeLog index 9319fe95..29c9858e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,7 @@ 29/1/16 started 8.3 - add vips_reduce*() ... a fast path for bicubic downsize - vips_resize() and vips_similarity use it when they can +- bicubic is better on 32-bit int images 12/1/16 started 8.2.2 - changes to ease compiling C++ binding with MSVC [Lovell Fuller] diff --git a/TODO b/TODO index 801dcc0d..22f191e1 100644 --- a/TODO +++ b/TODO @@ -1,15 +1,3 @@ -- strange, try - - $ vips affine babe.jpg x.v "0.909 0 0 0.909" - $ vipsheader x.v - x.v: 931x698 uchar, 3 bands, srgb, jpegload - - but 1024 * 0.909 == 930.9, shouldn't we be rounding down? I guess we are - rounding up - - need to fix this if we want to be able to used reduce as a shortcut for - similarity - - new vips_reduce: affine @@ -20,17 +8,15 @@ reduce $ time vipsthumbnail wtc.tif -o x.tif -s 7000 - real 0m1.818s - user 0m5.956s - sys 0m0.296s - - add tests for vips_reduce(), update c++ binding, check docs and "see also" - lines - + real 0m1.677s + user 0m5.996s + sys 0m0.300s - get some brightly coloured spots with nohalo / vsqbs on wobble.ws ... very odd, the interpolation shouldn't change between bands + probably over/underflow in interpolation ... clipping problems? + - still not happy about float->int mask conversion in im_vips2mask.c - need to follow up on diff --git a/cplusplus/include/vips/vips-operators.h b/cplusplus/include/vips/vips-operators.h index ec4c9b6c..b9eba85b 100644 --- a/cplusplus/include/vips/vips-operators.h +++ b/cplusplus/include/vips/vips-operators.h @@ -1,5 +1,5 @@ // headers for vips operations -// Sat Jan 9 15:06:40 GMT 2016 +// Sun Feb 7 16:27:14 GMT 2016 // this file is generated automatically, do not edit! static void system( char * cmd_format , VOption *options = 0 ); @@ -146,7 +146,9 @@ VImage mapim( VImage index , VOption *options = 0 ); VImage shrink( double xshrink , double yshrink , VOption *options = 0 ); VImage shrinkh( int xshrink , VOption *options = 0 ); VImage shrinkv( int yshrink , VOption *options = 0 ); -VImage shrink2( double xshrink , double yshrink , VOption *options = 0 ); +VImage reduceh( double xshrink , VOption *options = 0 ); +VImage reducev( double yshrink , VOption *options = 0 ); +VImage reduce( double xshrink , double yshrink , VOption *options = 0 ); VImage quadratic( VImage coeff , VOption *options = 0 ); VImage affine( std::vector matrix , VOption *options = 0 ); VImage similarity( VOption *options = 0 ); diff --git a/cplusplus/vips-operators.cpp b/cplusplus/vips-operators.cpp index e6c0ba57..d3cd6667 100644 --- a/cplusplus/vips-operators.cpp +++ b/cplusplus/vips-operators.cpp @@ -1,5 +1,5 @@ // bodies for vips operations -// Sat Jan 9 15:05:58 GMT 2016 +// Sun Feb 7 16:26:59 GMT 2016 // this file is generated automatically, do not edit! void VImage::system( char * cmd_format , VOption *options ) @@ -1783,11 +1783,37 @@ VImage VImage::shrinkv( int yshrink , VOption *options ) return( out ); } -VImage VImage::shrink2( double xshrink , double yshrink , VOption *options ) +VImage VImage::reduceh( double xshrink , VOption *options ) { VImage out; - call( "shrink2" , + call( "reduceh" , + (options ? options : VImage::option()) -> + set( "in", *this ) -> + set( "out", &out ) -> + set( "xshrink", xshrink ) ); + + return( out ); +} + +VImage VImage::reducev( double yshrink , VOption *options ) +{ + VImage out; + + call( "reducev" , + (options ? options : VImage::option()) -> + set( "in", *this ) -> + set( "out", &out ) -> + set( "yshrink", yshrink ) ); + + return( out ); +} + +VImage VImage::reduce( double xshrink , double yshrink , VOption *options ) +{ + VImage out; + + call( "reduce" , (options ? options : VImage::option()) -> set( "in", *this ) -> set( "out", &out ) -> diff --git a/doc/libvips-docs.xml b/doc/libvips-docs.xml index fe88a909..ed2039e5 100644 --- a/doc/libvips-docs.xml +++ b/doc/libvips-docs.xml @@ -9,7 +9,7 @@ VIPS Reference Manual - For VIPS 8.2.2. + For VIPS 8.3.0. The latest version of this documentation can be found on the VIPS website. diff --git a/libvips/resample/bicubic.cpp b/libvips/resample/bicubic.cpp index 909caeef..1b688be7 100644 --- a/libvips/resample/bicubic.cpp +++ b/libvips/resample/bicubic.cpp @@ -2,6 +2,8 @@ * * 12/8/10 * - revise window_size / window_offset stuff again + * 7/2/16 + * - double intermediate for 32-bit int types */ /* @@ -231,7 +233,7 @@ bicubic_signed_int_tab( void *pout, const VipsPel *pin, } } -/* Floating-point version, for int/float types. +/* Floating-point version. */ template static void inline @@ -294,6 +296,135 @@ bicubic_float_tab( void *pout, const VipsPel *pin, } } +/* uint32 version needs a double intermediate. + */ + +template +static void inline +bicubic_unsigned_int32_tab( void *pout, const VipsPel *pin, + const int bands, const int lskip, + const double *cx, const double *cy ) +{ + T* restrict out = (T *) pout; + const T* restrict in = (T *) pin; + + const int b1 = bands; + const int b2 = b1 + b1; + const int b3 = b1 + b2; + + const int l1 = lskip / sizeof( T ); + const int l2 = l1 + l1; + const int l3 = l1 + l2; + + const int l1_plus_b1 = l1 + b1; + const int l1_plus_b2 = l1 + b2; + const int l1_plus_b3 = l1 + b3; + const int l2_plus_b1 = l2 + b1; + const int l2_plus_b2 = l2 + b2; + const int l2_plus_b3 = l2 + b3; + const int l3_plus_b1 = l3 + b1; + const int l3_plus_b2 = l3 + b2; + const int l3_plus_b3 = l3 + b3; + + for( int z = 0; z < bands; z++ ) { + const T uno_one = in[0]; + const T uno_two = in[b1]; + const T uno_thr = in[b2]; + const T uno_fou = in[b3]; + + const T dos_one = in[l1]; + const T dos_two = in[l1_plus_b1]; + const T dos_thr = in[l1_plus_b2]; + const T dos_fou = in[l1_plus_b3]; + + const T tre_one = in[l2]; + const T tre_two = in[l2_plus_b1]; + const T tre_thr = in[l2_plus_b2]; + const T tre_fou = in[l2_plus_b3]; + + const T qua_one = in[l3]; + const T qua_two = in[l3_plus_b1]; + const T qua_thr = in[l3_plus_b2]; + const T qua_fou = in[l3_plus_b3]; + + double bicubic = bicubic_float( + uno_one, uno_two, uno_thr, uno_fou, + dos_one, dos_two, dos_thr, dos_fou, + tre_one, tre_two, tre_thr, tre_fou, + qua_one, qua_two, qua_thr, qua_fou, + cx, cy ); + + bicubic = VIPS_CLIP( 0, bicubic, max_value ); + + out[z] = bicubic; + + in += 1; + } +} + +template +static void inline +bicubic_signed_int32_tab( void *pout, const VipsPel *pin, + const int bands, const int lskip, + const double *cx, const double *cy ) +{ + T* restrict out = (T *) pout; + const T* restrict in = (T *) pin; + + const int b1 = bands; + const int b2 = b1 + b1; + const int b3 = b1 + b2; + + const int l1 = lskip / sizeof( T ); + const int l2 = l1 + l1; + const int l3 = l1 + l2; + + const int l1_plus_b1 = l1 + b1; + const int l1_plus_b2 = l1 + b2; + const int l1_plus_b3 = l1 + b3; + const int l2_plus_b1 = l2 + b1; + const int l2_plus_b2 = l2 + b2; + const int l2_plus_b3 = l2 + b3; + const int l3_plus_b1 = l3 + b1; + const int l3_plus_b2 = l3 + b2; + const int l3_plus_b3 = l3 + b3; + + for( int z = 0; z < bands; z++ ) { + const T uno_one = in[0]; + const T uno_two = in[b1]; + const T uno_thr = in[b2]; + const T uno_fou = in[b3]; + + const T dos_one = in[l1]; + const T dos_two = in[l1_plus_b1]; + const T dos_thr = in[l1_plus_b2]; + const T dos_fou = in[l1_plus_b3]; + + const T tre_one = in[l2]; + const T tre_two = in[l2_plus_b1]; + const T tre_thr = in[l2_plus_b2]; + const T tre_fou = in[l2_plus_b3]; + + const T qua_one = in[l3]; + const T qua_two = in[l3_plus_b1]; + const T qua_thr = in[l3_plus_b2]; + const T qua_fou = in[l3_plus_b3]; + + double bicubic = bicubic_float( + uno_one, uno_two, uno_thr, uno_fou, + dos_one, dos_two, dos_thr, dos_fou, + tre_one, tre_two, tre_thr, tre_fou, + qua_one, qua_two, qua_thr, qua_fou, + cx, cy ); + + bicubic = VIPS_CLIP( min_value, bicubic, max_value ); + + out[z] = bicubic; + + in += 1; + } +} + /* Ultra-high-quality version for double images. */ template @@ -461,12 +592,14 @@ vips_interpolate_bicubic_interpolate( VipsInterpolate *interpolate, break; case VIPS_FORMAT_UINT: - bicubic_float_tab( out, p, bands, lskip, + bicubic_unsigned_int32_tab( + out, p, bands, lskip, cxf, cyf ); break; case VIPS_FORMAT_INT: - bicubic_float_tab( out, p, bands, lskip, + bicubic_signed_int32_tab( + out, p, bands, lskip, cxf, cyf ); break; diff --git a/libvips/resample/reduceh.cpp b/libvips/resample/reduceh.cpp index 181d4723..62f807dc 100644 --- a/libvips/resample/reduceh.cpp +++ b/libvips/resample/reduceh.cpp @@ -160,9 +160,76 @@ reduceh_float_tab( VipsPel *pout, const VipsPel *pin, for( int z = 0; z < bands; z++ ) { out[z] = c0 * in[0] + - c1 * in[1] + - c2 * in[2] + - c3 * in[3]; + c1 * in[b1] + + c2 * in[b2] + + c3 * in[b3]; + + in += 1; + } +} + +/* 32-bit output needs a double intermediate. + */ + +template +static void inline +reduceh_unsigned_int32_tab( VipsPel *pout, const VipsPel *pin, + const int bands, const double *cx ) +{ + T* restrict out = (T *) pout; + const T* restrict in = (T *) pin; + + const int b1 = bands; + const int b2 = b1 + b1; + const int b3 = b1 + b2; + + const double c0 = cx[0]; + const double c1 = cx[1]; + const double c2 = cx[2]; + const double c3 = cx[3]; + + for( int z = 0; z < bands; z++ ) { + double cubich = + c0 * in[0] + + c1 * in[b1] + + c2 * in[b2] + + c3 * in[b3]; + + cubich = VIPS_CLIP( 0, cubich, max_value ); + + out[z] = cubich; + + in += 1; + } +} + +template +static void inline +reduceh_signed_int32_tab( VipsPel *pout, const VipsPel *pin, + const int bands, const double *cx ) +{ + T* restrict out = (T *) pout; + const T* restrict in = (T *) pin; + + const int b1 = bands; + const int b2 = b1 + b1; + const int b3 = b1 + b2; + + const double c0 = cx[0]; + const double c1 = cx[1]; + const double c2 = cx[2]; + const double c3 = cx[3]; + + for( int z = 0; z < bands; z++ ) { + double cubich = + c0 * in[0] + + c1 * in[b1] + + c2 * in[b2] + + c3 * in[b3]; + + cubich = VIPS_CLIP( min_value, cubich, max_value ); + + out[z] = cubich; in += 1; } @@ -222,7 +289,7 @@ vips_reduceh_gen( VipsRegion *out_region, void *seq, s.left = r->left * reduceh->xshrink; s.top = r->top; - s.width = r->width * reduceh->xshrink + 3; + s.width = r->width * reduceh->xshrink + 4; s.height = r->height; if( vips_region_prepare( ir, &s ) ) return( -1 ); @@ -271,12 +338,14 @@ vips_reduceh_gen( VipsRegion *out_region, void *seq, break; case VIPS_FORMAT_UINT: - reduceh_float_tab( + reduceh_unsigned_int32_tab + ( q, p, bands, cxf ); break; case VIPS_FORMAT_INT: - reduceh_float_tab( + reduceh_signed_int32_tab + ( q, p, bands, cxf ); break; @@ -326,9 +395,9 @@ vips_reduceh_build( VipsObject *object ) "%s", _( "reduce factors should be >= 1" ) ); return( -1 ); } - if( reduceh->xshrink > 2 ) + if( reduceh->xshrink > 3 ) vips_warn( object_class->nickname, - "%s", _( "reduce factor greater than 2" ) ); + "%s", _( "reduce factor greater than 3" ) ); if( reduceh->xshrink == 1 ) return( vips_image_write( in, resample->out ) ); diff --git a/libvips/resample/reducev.cpp b/libvips/resample/reducev.cpp index fb9883ae..765bac06 100644 --- a/libvips/resample/reducev.cpp +++ b/libvips/resample/reducev.cpp @@ -140,8 +140,9 @@ reducev_signed_int_tab( VipsPel *pout, const VipsPel *pin, } } -/* Floating-point version, for int/float types. +/* Floating-point version. */ + template static void inline reducev_float_tab( VipsPel *pout, const VipsPel *pin, @@ -171,6 +172,75 @@ reducev_float_tab( VipsPel *pout, const VipsPel *pin, } } +/* 32-bit int version needs a double intermediate. + */ + +template +static void inline +reducev_unsigned_int32_tab( VipsPel *pout, const VipsPel *pin, + const int ne, const int lskip, + const double *cy ) +{ + T* restrict out = (T *) pout; + const T* restrict in = (T *) pin; + + const int l1 = lskip / sizeof( T ); + const int l2 = l1 + l1; + const int l3 = l1 + l2; + + const double c0 = cy[0]; + const double c1 = cy[1]; + const double c2 = cy[2]; + const double c3 = cy[3]; + + for( int z = 0; z < ne; z++ ) { + double cubicv = + c0 * in[0] + + c1 * in[l1] + + c2 * in[l2] + + c3 * in[l3]; + + cubicv = VIPS_CLIP( 0, cubicv, max_value ); + + out[z] = cubicv; + + in += 1; + } +} + +template +static void inline +reducev_signed_int32_tab( VipsPel *pout, const VipsPel *pin, + const int ne, const int lskip, + const double *cy ) +{ + T* restrict out = (T *) pout; + const T* restrict in = (T *) pin; + + const int l1 = lskip / sizeof( T ); + const int l2 = l1 + l1; + const int l3 = l1 + l2; + + const double c0 = cy[0]; + const double c1 = cy[1]; + const double c2 = cy[2]; + const double c3 = cy[3]; + + for( int z = 0; z < ne; z++ ) { + double cubicv = + c0 * in[0] + + c1 * in[l1] + + c2 * in[l2] + + c3 * in[l3]; + + cubicv = VIPS_CLIP( min_value, cubicv, max_value ); + + out[z] = cubicv; + + in += 1; + } +} + /* Ultra-high-quality version for double images. */ template @@ -231,7 +301,7 @@ vips_reducev_gen( VipsRegion *out_region, void *seq, s.left = r->left; s.top = r->top * reducev->yshrink; s.width = r->width; - s.height = r->height * reducev->yshrink + 3; + s.height = r->height * reducev->yshrink + 4; if( vips_region_prepare( ir, &s ) ) return( -1 ); @@ -274,11 +344,15 @@ vips_reducev_gen( VipsRegion *out_region, void *seq, break; case VIPS_FORMAT_UINT: - reducev_float_tab( q, p, ne, lskip, cyf ); + reducev_unsigned_int32_tab + ( + q, p, ne, lskip, cyf ); break; case VIPS_FORMAT_INT: - reducev_float_tab( q, p, ne, lskip, cyf ); + reducev_signed_int32_tab + ( + q, p, ne, lskip, cyf ); break; case VIPS_FORMAT_FLOAT: @@ -322,9 +396,9 @@ vips_reducev_build( VipsObject *object ) "%s", _( "reduce factors should be >= 1" ) ); return( -1 ); } - if( reducev->yshrink > 2 ) + if( reducev->yshrink > 3 ) vips_warn( object_class->nickname, - "%s", _( "reduce factor greater than 2" ) ); + "%s", _( "reduce factor greater than 3" ) ); if( reducev->yshrink == 1 ) return( vips_image_write( in, resample->out ) ); diff --git a/libvips/resample/resample.c b/libvips/resample/resample.c index 8114e762..18b21564 100644 --- a/libvips/resample/resample.c +++ b/libvips/resample/resample.c @@ -52,6 +52,28 @@ #include "presample.h" +/** + * SECTION: resample + * @short_description: resample images in various ways + * @stability: Stable + * @include: vips/vips.h + * + * There are three types of operation in this section. + * + * First, vips_affine() applies an affine transform to an image. This is any + * sort of 2D transform which preserves straight lines; so any combination of + * stretch, sheer, rotate and translate. You supply an interpolator for it to + * use to generate pixels, see vips_interpolate_new(). It will not produce + * good results for very large shrinks. + * + * Next, vips_resize() specialises in the common task of image reduce and + * enlarge. It strings together combinations of vips_shrink(), vips_reduce(), + * vips_affine() and others to implement a general, high-quality image + * resizer. + * + * Finally, vips_mapim() can apply arbitrary 2D image transforms to an image. + */ + G_DEFINE_ABSTRACT_TYPE( VipsResample, vips_resample, VIPS_TYPE_OPERATION ); static int diff --git a/libvips/resample/similarity.c b/libvips/resample/similarity.c index 5c9ea8f3..6aaa306f 100644 --- a/libvips/resample/similarity.c +++ b/libvips/resample/similarity.c @@ -8,6 +8,8 @@ * rest of vips * 13/8/14 * - oops, missing scale from b, thanks Topochicho + * 7/2/16 + * - use vips_reduce(), if we can */ /* diff --git a/libvips/resample/templates.h b/libvips/resample/templates.h index efd160ba..133925fb 100644 --- a/libvips/resample/templates.h +++ b/libvips/resample/templates.h @@ -154,18 +154,6 @@ unsigned_fixed_round( int v ) return( (v + round_by) >> VIPS_INTERPOLATE_SHIFT ); } -template static int inline -cubic_unsigned_int( - const T one, const T two, const T thr, const T fou, - const int* restrict cx ) -{ - return( unsigned_fixed_round( - cx[0] * one + - cx[1] * two + - cx[2] * thr + - cx[3] * fou ) ); -} - /* Fixed-point integer bicubic, used for 8 and 16-bit types. */ template static int inline @@ -176,16 +164,37 @@ bicubic_unsigned_int( const T qua_one, const T qua_two, const T qua_thr, const T qua_fou, const int* restrict cx, const int* restrict cy ) { - const int r0 = cubic_unsigned_int( - uno_one, uno_two, uno_thr, uno_fou, cx ); - const int r1 = cubic_unsigned_int( - dos_one, dos_two, dos_thr, dos_fou, cx ); - const int r2 = cubic_unsigned_int( - tre_one, tre_two, tre_thr, tre_fou, cx ); - const int r3 = cubic_unsigned_int( - qua_one, qua_two, qua_thr, qua_fou, cx ); + const int c0 = cx[0]; + const int c1 = cx[1]; + const int c2 = cx[2]; + const int c3 = cx[3]; - return( cubic_unsigned_int( r0, r1, r2, r3, cy ) ); + const int r0 = unsigned_fixed_round( + c0 * uno_one + + c1 * uno_two + + c2 * uno_thr + + c3 * uno_fou ); + const int r1 = unsigned_fixed_round( + c0 * dos_one + + c1 * dos_two + + c2 * dos_thr + + c3 * dos_fou ); + const int r2 = unsigned_fixed_round( + c0 * tre_one + + c1 * tre_two + + c2 * tre_thr + + c3 * tre_fou ); + const int r3 = unsigned_fixed_round( + c0 * qua_one + + c1 * qua_two + + c2 * qua_thr + + c3 * qua_fou ); + + return( unsigned_fixed_round( + cy[0] * r0 + + cy[1] * r1 + + cy[2] * r2 + + cy[3] * r3 ) ); } static int inline @@ -197,18 +206,6 @@ signed_fixed_round( int v ) return( (v + round_by) >> VIPS_INTERPOLATE_SHIFT ); } -template static int inline -cubic_signed_int( - const T one, const T two, const T thr, const T fou, - const int* restrict cx ) -{ - return( signed_fixed_round( - cx[0] * one + - cx[1] * two + - cx[2] * thr + - cx[3] * fou ) ); -} - /* Fixed-point integer bicubic, used for 8 and 16-bit types. */ template static int inline @@ -219,16 +216,37 @@ bicubic_signed_int( const T qua_one, const T qua_two, const T qua_thr, const T qua_fou, const int* restrict cx, const int* restrict cy ) { - const int r0 = cubic_signed_int( - uno_one, uno_two, uno_thr, uno_fou, cx ); - const int r1 = cubic_signed_int( - dos_one, dos_two, dos_thr, dos_fou, cx ); - const int r2 = cubic_signed_int( - tre_one, tre_two, tre_thr, tre_fou, cx ); - const int r3 = cubic_signed_int( - qua_one, qua_two, qua_thr, qua_fou, cx ); + const int c0 = cx[0]; + const int c1 = cx[1]; + const int c2 = cx[2]; + const int c3 = cx[3]; - return( cubic_signed_int( r0, r1, r2, r3, cy ) ); + const int r0 = signed_fixed_round( + c0 * uno_one + + c1 * uno_two + + c2 * uno_thr + + c3 * uno_fou ); + const int r1 = signed_fixed_round( + c0 * dos_one + + c1 * dos_two + + c2 * dos_thr + + c3 * dos_fou ); + const int r2 = signed_fixed_round( + c0 * tre_one + + c1 * tre_two + + c2 * tre_thr + + c3 * tre_fou ); + const int r3 = signed_fixed_round( + c0 * qua_one + + c1 * qua_two + + c2 * qua_thr + + c3 * qua_fou ); + + return( signed_fixed_round( + cy[0] * r0 + + cy[1] * r1 + + cy[2] * r2 + + cy[3] * r3 ) ); } template static T inline diff --git a/test/test_arithmetic.py b/test/test_arithmetic.py index 53938105..9eb0379c 100755 --- a/test/test_arithmetic.py +++ b/test/test_arithmetic.py @@ -11,7 +11,6 @@ from gi.repository import Vips Vips.leak_set(True) - unsigned_formats = [Vips.BandFormat.UCHAR, Vips.BandFormat.USHORT, Vips.BandFormat.UINT] diff --git a/test/test_resample.py b/test/test_resample.py index 3fbbf92b..4c9c2c21 100755 --- a/test/test_resample.py +++ b/test/test_resample.py @@ -10,6 +10,20 @@ from gi.repository import Vips Vips.leak_set(True) +unsigned_formats = [Vips.BandFormat.UCHAR, + Vips.BandFormat.USHORT, + Vips.BandFormat.UINT] +signed_formats = [Vips.BandFormat.CHAR, + Vips.BandFormat.SHORT, + Vips.BandFormat.INT] +float_formats = [Vips.BandFormat.FLOAT, + Vips.BandFormat.DOUBLE] +complex_formats = [Vips.BandFormat.COMPLEX, + Vips.BandFormat.DPCOMPLEX] +int_formats = unsigned_formats + signed_formats +noncomplex_formats = int_formats + float_formats +all_formats = int_formats + float_formats + complex_formats + # Run a function expecting a complex image on a two-band image def run_cmplx(fn, image): if image.format == Vips.BandFormat.FLOAT: @@ -105,13 +119,14 @@ class TestResample(unittest.TestCase): bicubic = Vips.Interpolate.new("bicubic") for fac in [1, 1.1, 1.5, 1.999]: - print("fac =", fac) - r = im.reduce(fac, fac) - a = im.affine([1.0 / fac, 0, 0, 1.0 / fac], interpolate = bicubic) - r.write_to_file("r.v") - a.write_to_file("a.v") - d = (r - a).abs().max() - self.assertLess(d, 0.1) + for fmt in all_formats: + x = im.cast(fmt) + r = x.reduce(fac, fac) + a = x.affine([1.0 / fac, 0, 0, 1.0 / fac], + interpolate = bicubic, + oarea = [0, 0, x.width / fac, x.height / fac]) + d = (r - a).abs().max() + self.assertLess(d, 5) def test_resize(self): im = Vips.Image.new_from_file("images/IMG_4618.jpg")