diff --git a/ChangeLog b/ChangeLog index fe9fb9a5..873d23f8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -28,6 +28,7 @@ - vips_image_write() severs all links between images, when it can ... thanks Warren and Nakilon - vector path for convolution is more accurate and can handle larger masks +- linear and cubic kernels for reduce are higer quality 29/8/17 started 8.5.9 - make --fail stop jpeg read on any libjpeg warning, thanks @mceachen diff --git a/TODO b/TODO index 9b84c591..83c169a8 100644 --- a/TODO +++ b/TODO @@ -1,9 +1,4 @@ -- vips_image_write() to a temp file is rather broken - - things like reorder and upstream/downstream will still be keeping links after - write, so unreffing the input will eventually trigger a crash - - rewind should break more things ... does it remove upsteam/downstream? does it +- rewind should break more things ... does it remove upsteam/downstream? does it just need to remove reorder links? perhaps reorder should use upstream/downstream, then it will be broken anyway diff --git a/libvips/resample/reduce.c b/libvips/resample/reduce.c index 729a4d3a..0b80c88b 100644 --- a/libvips/resample/reduce.c +++ b/libvips/resample/reduce.c @@ -57,14 +57,12 @@ /** * VipsKernel: * @VIPS_KERNEL_NEAREST: The nearest pixel to the point. - * @VIPS_KERNEL_LINEAR: Calculate a pixel value using linear interpolation. - * @VIPS_KERNEL_CUBIC: Calculate using a 4-element cubic kernel. - * @VIPS_KERNEL_LANCZOS2: Calculate with a two-lobe Lanczos kernel. - * @VIPS_KERNEL_LANCZOS3: Calculate with a three-lobe Lanczos kernel. + * @VIPS_KERNEL_LINEAR: Convolve with a triangle filter. + * @VIPS_KERNEL_CUBIC: Convolve with a cubic filter. + * @VIPS_KERNEL_LANCZOS2: Convolve with a two-lobe Lanczos kernel. + * @VIPS_KERNEL_LANCZOS3: Convolve with a three-lobe Lanczos kernel. * * The resampling kernels vips supports. See vips_reduce(), for example. - * - * The Lanczos kernels vary in size with the downsampling ratio. */ typedef struct _VipsReduce { diff --git a/libvips/resample/reduceh.cpp b/libvips/resample/reduceh.cpp index a13ad0d6..b9c98c75 100644 --- a/libvips/resample/reduceh.cpp +++ b/libvips/resample/reduceh.cpp @@ -101,10 +101,10 @@ vips_reduce_get_points( VipsKernel kernel, double shrink ) return( 1 ); case VIPS_KERNEL_LINEAR: - return( 2 ); + return( rint( 2 * shrink ) + 1 ); case VIPS_KERNEL_CUBIC: - return( 4 ); + return( rint( 4 * shrink ) + 1 ); case VIPS_KERNEL_LANCZOS2: /* Needs to be in sync with calculate_coefficients_lanczos(). @@ -131,12 +131,11 @@ vips_reduce_make_mask( double *c, VipsKernel kernel, double shrink, double x ) break; case VIPS_KERNEL_LINEAR: - c[0] = 1.0 - x; - c[1] = x; + calculate_coefficients_triangle( c, shrink, x ); break; case VIPS_KERNEL_CUBIC: - calculate_coefficients_catmull( c, x ); + calculate_coefficients_adaptive_catmull( c, shrink, x ); break; case VIPS_KERNEL_LANCZOS2: diff --git a/libvips/resample/resize.c b/libvips/resample/resize.c index ab046f26..64a849ab 100644 --- a/libvips/resample/resize.c +++ b/libvips/resample/resize.c @@ -24,6 +24,8 @@ * - add @centre option * 6/3/17 * - moved the cache to shrinkv + * 15/10/17 + * - make LINEAR and CUBIC adaptive */ /* @@ -101,13 +103,10 @@ G_DEFINE_TYPE( VipsResize, vips_resize, VIPS_TYPE_RESAMPLE ); * This depends on the scale and the kernel we will use for residual resizing. * For upsizing and nearest-neighbour downsize, we want no shrinking. * - * Linear and cubic are fixed-size kernels and for a 0 offset are point - * samplers. We will get aliasing if we do more than a x2 shrink with them. - * - * Lanczos is adaptive: the size of the kernel changes with the shrink factor. - * We will get the best quality (but be the slowest) if we let reduce do all - * the work. Leave it the final 200 - 300% to do as a compromise for - * efficiency. + * The others are adaptive: the size of the kernel changes with the shrink + * factor. We will get the best quality (but be the slowest) if we let + * reduce do all the work. Leave it the final 200 - 300% to do as a compromise + * for efficiency. * * FIXME: this is rather ugly. Kernel should be a class and this info should be * stored in there. @@ -127,12 +126,9 @@ vips_resize_int_shrink( VipsResize *resize, double scale ) case VIPS_KERNEL_LINEAR: case VIPS_KERNEL_CUBIC: - default: - shrink = VIPS_FLOOR( 1.0 / scale ); - break; - case VIPS_KERNEL_LANCZOS2: case VIPS_KERNEL_LANCZOS3: + default: shrink = VIPS_MAX( 1, VIPS_FLOOR( 1.0 / (scale * 2) ) ); break; } diff --git a/libvips/resample/templates.h b/libvips/resample/templates.h index 8c026ac0..ac06dc85 100644 --- a/libvips/resample/templates.h +++ b/libvips/resample/templates.h @@ -311,6 +311,48 @@ calculate_coefficients_catmull( double c[4], const double x ) c[2] = cthr; } +/* Calculate a catmull kernel for shrinking. + */ +static void inline +calculate_coefficients_adaptive_catmull( double *c, + const double shrink, const double x ) +{ + /* Needs to be in sync with vips_reduce_get_points(). + */ + const int n_points = rint( 4 * shrink ) + 1; + + int i; + double sum; + + sum = 0; + for( i = 0; i < n_points; i++ ) { + const double xp = (i - (2 * shrink - 1) - x) / shrink; + const double axp = VIPS_FABS( xp ); + const double axp2 = axp * axp; + const double axp3 = axp2 * axp; + const double a = -0.5; + + double l; + + if( axp <= 1 ) + l = (a + 2) * axp3 - + (a + 3) * axp2 + 1; + else if( axp <= 2 ) + l = a * axp3 - + 5 * a * axp2 + + 8 * a * axp - + 4 * a; + else + l = 0.0; + + c[i] = l; + sum += l; + } + + for( i = 0; i < n_points; i++ ) + c[i] /= sum; +} + /* 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. @@ -355,6 +397,37 @@ calculate_coefficients_lanczos( double *c, c[i] /= sum; } +/* Given an x in [0,1] (we can have x == 1 when building tables), + * calculate c0 .. c(@shrink + 1), the triangle coefficients. This is called + * from the interpolator as well as from the table builder. + */ +static void inline +calculate_coefficients_triangle( double *c, const double shrink, const double x ) +{ + /* Needs to be in sync with vips_reduce_get_points(). + */ + const int n_points = rint( 2 * shrink ) + 1; + + int i; + double sum; + + sum = 0; + for( i = 0; i < n_points; i++ ) { + double xp = (i - (shrink - 1) - x) / shrink; + + double l; + + l = 1.0 - VIPS_FABS( xp ); + l = VIPS_MAX( 0.0, l ); + + 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 * type T, gather results in an intermediate of type IT. */