diff --git a/TODO b/TODO index 5da9b52b..f55b57ba 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,4 @@ + - try orc version of reducev? and shrinkv? maybe shrinkh? diff --git a/libvips/resample/presample.h b/libvips/resample/presample.h index 33f1e413..b944118e 100644 --- a/libvips/resample/presample.h +++ b/libvips/resample/presample.h @@ -67,10 +67,11 @@ GType vips_resample_get_type( void ); /* The max size of the vector we use. */ -#define MAX_POINTS (6) +#define MAX_POINTS (50) -int vips_reduce_get_points( VipsKernel kernel ); -void vips_reduce_make_mask( VipsKernel kernel, double x, double *c ); +int vips_reduce_get_points( VipsKernel kernel, double shrink ); +void vips_reduce_make_mask( VipsKernel kernel, double shrink, + double x, double *c ); #ifdef __cplusplus } diff --git a/libvips/resample/reduceh.cpp b/libvips/resample/reduceh.cpp index de556ae4..1c009a10 100644 --- a/libvips/resample/reduceh.cpp +++ b/libvips/resample/reduceh.cpp @@ -81,8 +81,8 @@ typedef struct _VipsReduceh { * sizes up to short), and double (for all others). We go to * scale + 1 so we can round-to-nearest safely. */ - int matrixi[VIPS_TRANSFORM_SCALE + 1][MAX_POINTS]; - double matrixf[VIPS_TRANSFORM_SCALE + 1][MAX_POINTS]; + int *matrixi[VIPS_TRANSFORM_SCALE + 1]; + double *matrixf[VIPS_TRANSFORM_SCALE + 1]; } VipsReduceh; @@ -94,10 +94,10 @@ extern "C" { G_DEFINE_TYPE( VipsReduceh, vips_reduceh, VIPS_TYPE_RESAMPLE ); } -/* Get n points. +/* Get n points. @shrink is the shrink factor, so 2 for a 50% reduction. */ int -vips_reduce_get_points( VipsKernel kernel ) +vips_reduce_get_points( VipsKernel kernel, double shrink ) { switch( kernel ) { case VIPS_KERNEL_NEAREST: @@ -110,10 +110,12 @@ vips_reduce_get_points( VipsKernel kernel ) return( 4 ); case VIPS_KERNEL_LANCZOS2: - return( 4 ); + /* Needs to be in sync with calculate_coefficients_lanczos(). + */ + return( 2 * 2 * ceil( shrink ) + 2 ); case VIPS_KERNEL_LANCZOS3: - return( 6 ); + return( 2 * 3 * ceil( shrink ) + 2 ); default: g_assert_not_reached(); @@ -121,10 +123,10 @@ vips_reduce_get_points( VipsKernel kernel ) } } -/* Calculate a mask. +/* Calculate a mask element. */ void -vips_reduce_make_mask( VipsKernel kernel, double x, double *c ) +vips_reduce_make_mask( VipsKernel kernel, double shrink, double x, double *c ) { switch( kernel ) { case VIPS_KERNEL_NEAREST: @@ -141,11 +143,11 @@ vips_reduce_make_mask( VipsKernel kernel, double x, double *c ) break; case VIPS_KERNEL_LANCZOS2: - calculate_coefficients_lanczos( 2, x, c ); + calculate_coefficients_lanczos( 2, shrink, x, c ); break; case VIPS_KERNEL_LANCZOS3: - calculate_coefficients_lanczos( 3, x, c ); + calculate_coefficients_lanczos( 3, shrink, x, c ); break; default: @@ -177,7 +179,7 @@ reduceh_unsigned_int_tab( VipsReduceh *reduceh, } } -/* A 4-point interpolation on uint8 is the most common case ... unroll that. +/* A 6-point interpolation on uint8 is the most common case ... unroll that. * * The inner loop here won't vectorise, but our inner loop doesn't run for * long enough for vectorisation to be useful :-( gcc says it needs about an @@ -316,7 +318,7 @@ reduceh_notab( VipsReduceh *reduceh, double cx[MAX_POINTS]; - vips_reduce_make_mask( reduceh->kernel, x, cx ); + vips_reduce_make_mask( reduceh->kernel, reduceh->xshrink, x, cx ); for( int z = 0; z < bands; z++ ) { out[z] = reduce_sum( in, bands, cx, n ); @@ -476,9 +478,23 @@ vips_reduceh_build( VipsObject *object ) /* Build the tables of pre-computed coefficients. */ - reduceh->n_points = vips_reduce_get_points( reduceh->kernel ); + reduceh->n_points = + vips_reduce_get_points( reduceh->kernel, reduceh->xshrink ); + if( reduceh->n_points > MAX_POINTS ) { + vips_error( object_class->nickname, + "%s", _( "reduce factor too large" ) ); + return( -1 ); + } for( int x = 0; x < VIPS_TRANSFORM_SCALE + 1; x++ ) { - vips_reduce_make_mask( reduceh->kernel, + reduceh->matrixf[x] = + VIPS_ARRAY( object, reduceh->n_points, double ); + reduceh->matrixi[x] = + VIPS_ARRAY( object, reduceh->n_points, int ); + if( !reduceh->matrixf[x] || + !reduceh->matrixi[x] ) + return( -1 ); + + vips_reduce_make_mask( reduceh->kernel, reduceh->xshrink, (float) x / VIPS_TRANSFORM_SCALE, reduceh->matrixf[x] ); diff --git a/libvips/resample/reducev.cpp b/libvips/resample/reducev.cpp index b5bd652d..b865f1c1 100644 --- a/libvips/resample/reducev.cpp +++ b/libvips/resample/reducev.cpp @@ -53,10 +53,6 @@ #include "presample.h" #include "templates.h" -/* The max size of the vector we use. - */ -#define MAX_POINTS (6) - typedef struct _VipsReducev { VipsResample parent_instance; @@ -74,8 +70,8 @@ typedef struct _VipsReducev { * sizes up to short), and double (for all others). We go to * scale + 1 so we can round-to-nearest safely. */ - int matrixi[VIPS_TRANSFORM_SCALE + 1][MAX_POINTS]; - double matrixf[VIPS_TRANSFORM_SCALE + 1][MAX_POINTS]; + int *matrixi[VIPS_TRANSFORM_SCALE + 1]; + double *matrixf[VIPS_TRANSFORM_SCALE + 1]; } VipsReducev; @@ -243,7 +239,7 @@ reducev_notab( VipsReducev *reducev, double cy[MAX_POINTS]; - vips_reduce_make_mask( reducev->kernel, y, cy ); + vips_reduce_make_mask( reducev->kernel, reducev->yshrink, y, cy ); for( int z = 0; z < ne; z++ ) out[z] = reduce_sum( in + z, l1, cy, n ); @@ -379,7 +375,7 @@ vips_reducev_build( VipsObject *object ) if( reducev->yshrink < 1 ) { vips_error( object_class->nickname, - "%s", _( "reduce factors should be >= 1" ) ); + "%s", _( "reduce factor should be >= 1" ) ); return( -1 ); } if( reducev->yshrink > 3 ) @@ -391,12 +387,34 @@ vips_reducev_build( VipsObject *object ) /* Build the tables of pre-computed coefficients. */ - reducev->n_points = vips_reduce_get_points( reducev->kernel ); + reducev->n_points = + vips_reduce_get_points( reducev->kernel, reducev->yshrink ); + if( reducev->n_points > MAX_POINTS ) { + vips_error( object_class->nickname, + "%s", _( "reduce factor too large" ) ); + return( -1 ); + } for( int y = 0; y < VIPS_TRANSFORM_SCALE + 1; y++ ) { - vips_reduce_make_mask( reducev->kernel, + reducev->matrixf[y] = + VIPS_ARRAY( object, reducev->n_points, double ); + reducev->matrixi[y] = + VIPS_ARRAY( object, reducev->n_points, int ); + if( !reducev->matrixf[y] || + !reducev->matrixi[y] ) + return( -1 ); + + vips_reduce_make_mask( reducev->kernel, reducev->yshrink, (float) y / VIPS_TRANSFORM_SCALE, reducev->matrixf[y] ); +#ifdef DEBUG + printf( "vips_reducev_build: masks:\n" ); + printf( "%4g ", (double) y / VIPS_TRANSFORM_SCALE ); + for( int i = 0; i < reducev->n_points; i++ ) + printf( " %4g", reducev->matrixf[y][i] ); + printf( "\n" ); +#endif /*DEBUG*/ + for( int i = 0; i < reducev->n_points; i++ ) reducev->matrixi[y][i] = reducev->matrixf[y][i] * VIPS_INTERPOLATE_SCALE; diff --git a/libvips/resample/resize.c b/libvips/resample/resize.c index 2c019c03..bb36f5be 100644 --- a/libvips/resample/resize.c +++ b/libvips/resample/resize.c @@ -96,8 +96,6 @@ vips_resize_build( VipsObject *object ) int int_vshrink; double hresidual; double vresidual; - double sigma; - gboolean anti_alias; if( VIPS_OBJECT_CLASS( vips_resize_parent_class )->build( object ) ) return( -1 ); @@ -124,12 +122,12 @@ vips_resize_build( VipsObject *object ) if( resize->scale > 1.0 ) int_hshrink = 1; else - int_hshrink = VIPS_FLOOR( 1.0 / (resize->scale * 1.4) ); + int_hshrink = VIPS_FLOOR( 1.0 / (resize->scale * 2) ); if( vips_object_argument_isset( object, "vscale" ) ) { if( resize->vscale > 1.0 ) int_vshrink = 1; else - int_vshrink = VIPS_FLOOR( 1.0 / (resize->vscale * 1.4) ); + int_vshrink = VIPS_FLOOR( 1.0 / (resize->vscale * 2) ); } else int_vshrink = int_hshrink; @@ -202,25 +200,8 @@ vips_resize_build( VipsObject *object ) in = t[6]; } - /* If the final affine will be doing a large downsample, we can get - * nasty aliasing on hard edges. Blur before affine to smooth this out. - * - * Don't blur for very small shrinks, or very small sigma. - * - * Don't try to be clever for non-rectangular shrinks. We just - * consider the horizontal factor. + /* Any downsizing. */ - sigma = (1.0 / hresidual) / 2.7; - anti_alias = hresidual < 0.9 && sigma > 0.45; - if( anti_alias ) { - vips_info( class->nickname, "anti-alias sigma %g", sigma ); - if( vips_gaussblur( in, &t[1], sigma, - "min_ampl", 0.1, - NULL ) ) - return( -1 ); - in = t[1]; - } - if( hresidual < 1.0 || vresidual < 1.0 ) { vips_info( class->nickname, "residual reduce by %g x %g", @@ -231,6 +212,9 @@ vips_resize_build( VipsObject *object ) return( -1 ); in = t[2]; } + + /* Any upsizing. + */ if( hresidual > 1.0 || vresidual > 1.0 ) { vips_info( class->nickname, "residual scale %g x %g", @@ -242,17 +226,15 @@ vips_resize_build( VipsObject *object ) in = t[3]; } - /* If we are upsampling, don't sharpen. Also don't sharpen if we - * skipped the anti-alias filter. + /* If we are upsizing, don't sharpen. */ - if( int_hshrink >= 1 && - anti_alias ) { + if( int_hshrink >= 1 ) { vips_info( class->nickname, "final sharpen" ); t[4] = vips_image_new_matrixv( 3, 3, -1.0, -1.0, -1.0, - -1.0, 24.0, -1.0, + -1.0, 32.0, -1.0, -1.0, -1.0, -1.0 ); - vips_image_set_double( t[4], "scale", 16 ); + vips_image_set_double( t[4], "scale", 24 ); if( vips_conv( in, &t[5], t[4], NULL ) ) return( -1 ); diff --git a/libvips/resample/templates.h b/libvips/resample/templates.h index 50fd6d51..d17cae13 100644 --- a/libvips/resample/templates.h +++ b/libvips/resample/templates.h @@ -311,17 +311,28 @@ calculate_coefficients_catmull( const double x, double c[4] ) c[2] = cthr; } -/* Given an offset in [0,1] (we can have x == 1 when building tables), - * calculate c0 .. c(a * 2 - 1), the lanczos coefficients. This is called +/* Given an x in [0,1] (we can have x == 1 when building tables), + * calculate c0 .. c(@a * @shrink + 1), the lanczos coefficients. This is called * from the interpolator as well as from the table builder. + * + * @a is the number of lobes, so usually 2 or 3. @shrink is the reduction + * factor, so 1 for interpolation, 2 for a x2 reduction, etc. We need more + * points for large decimations to avoid aliasing. */ static void inline -calculate_coefficients_lanczos( int a, const double x, double *c ) +calculate_coefficients_lanczos( const int a, const double shrink, + const double x, double *c ) { - int i; + /* Needs to be in sync with vips_reduce_get_points(). + */ + const int n_points = 2 * a * ceil( shrink ) + 2; - for( i = 0; i < a * 2; i++ ) { - double xp = (i - a) + (1 - x); + int i; + double sum; + + sum = 0; + for( i = 0; i < n_points; i++ ) { + double xp = (i - (n_points - 2) / 2 - x) / shrink; double l; @@ -337,7 +348,11 @@ calculate_coefficients_lanczos( int a, const double x, double *c ) (VIPS_PI * VIPS_PI * xp * xp); c[i] = l; + sum += l; } + + for( i = 0; i < n_points; i++ ) + c[i] /= sum; } /* Our inner loop for resampling with a convolution. Operate on elements of diff --git a/tools/vipsthumbnail.c b/tools/vipsthumbnail.c index 590dfb62..bd326e09 100644 --- a/tools/vipsthumbnail.c +++ b/tools/vipsthumbnail.c @@ -233,11 +233,11 @@ thumbnail_find_jpegshrink( VipsImage *im ) * doing no further processing, which would make a 400-px version look * very different from a 450-px version. */ - if( shrink >= 9 ) + if( shrink >= 10 ) return( 8 ); - else if( shrink >= 5 ) + else if( shrink >= 6 ) return( 4 ); - else if( shrink >= 3 ) + else if( shrink >= 4 ) return( 2 ); else return( 1 );