From e9728e389b6df6e2614ee03ae7381538323190fe Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 9 Mar 2016 12:56:30 +0000 Subject: [PATCH 01/20] first commit --- TODO | 3 +++ libvips/resample/reduceh.cpp | 19 +++++++++++++++++++ libvips/resample/templates.h | 17 +++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/TODO b/TODO index 2ecfeb7b..b7a0392d 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,6 @@ +- see + + https://en.wikipedia.org/wiki/Lanczos_resampling - try SEQ_UNBUFFERED on jpg source, get out of order error? diff --git a/libvips/resample/reduceh.cpp b/libvips/resample/reduceh.cpp index 62f807dc..9ab31886 100644 --- a/libvips/resample/reduceh.cpp +++ b/libvips/resample/reduceh.cpp @@ -466,6 +466,25 @@ vips_reduceh_class_init( VipsReducehClass *reduceh_class ) vobject_class->description = _( "shrink an image horizontally" ); vobject_class->build = vips_reduceh_build; +{ + double x; + + for( x = 0.0; x <= 1.0; x += 0.05 ) { + double c[7]; + int i; + + calculate_coefficients_lanczos3( x, c ); + + printf( "%g, ", x ); + for( i = 0; i < 7; i++ ) { + printf( "%5g", c[i] ); + if( i < 6 ) + printf( ", " ); + } + printf( "\n" ); + } +} + operation_class->flags = VIPS_OPERATION_SEQUENTIAL_UNBUFFERED; VIPS_ARG_DOUBLE( reduceh_class, "xshrink", 3, diff --git a/libvips/resample/templates.h b/libvips/resample/templates.h index 133925fb..3aab774f 100644 --- a/libvips/resample/templates.h +++ b/libvips/resample/templates.h @@ -310,3 +310,20 @@ calculate_coefficients_catmull( const double x, double c[4] ) c[1] = ctwo; c[2] = cthr; } + +/* Given an offset in [0,1] (we can have x == 1 when building tables), + * calculate c0 .. c6, the lanczos3 coefficients. This is called + * from the interpolator as well as from the table builder. + */ +static void inline +calculate_coefficients_lanczos3( const double x, double c[7] ) +{ + int i; + + for( i = 0; i < 7; i++ ) { + double xp = (i - 3) + x; + + c[i] = 3.0 * sin( VIPS_PI * xp ) * sin( VIPS_PI * xp / 3.0 ) / + (VIPS_PI * VIPS_PI * xp * xp); + } +} From dcd921d7bfbe7a4334d0520e62436ebf608695f9 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 9 Mar 2016 13:06:03 +0000 Subject: [PATCH 02/20] silly travis --- libvips/resample/reduceh.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libvips/resample/reduceh.cpp b/libvips/resample/reduceh.cpp index 9ab31886..23f78384 100644 --- a/libvips/resample/reduceh.cpp +++ b/libvips/resample/reduceh.cpp @@ -466,6 +466,7 @@ vips_reduceh_class_init( VipsReducehClass *reduceh_class ) vobject_class->description = _( "shrink an image horizontally" ); vobject_class->build = vips_reduceh_build; + /* { double x; @@ -484,6 +485,7 @@ vips_reduceh_class_init( VipsReducehClass *reduceh_class ) printf( "\n" ); } } + */ operation_class->flags = VIPS_OPERATION_SEQUENTIAL_UNBUFFERED; From a26291a426d5b1f5f2e624adb98956cc89b068be Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 9 Mar 2016 22:47:02 +0000 Subject: [PATCH 03/20] reducehl3 is done sort of anyway, tinkering with optimization --- libvips/include/vips/Makefile.am | 1 + libvips/include/vips/enumtypes.h | 3 + libvips/include/vips/resample.h | 9 + libvips/iofuncs/Makefile.am | 1 + libvips/iofuncs/enumtypes.c | 22 ++ libvips/resample/Makefile.am | 2 + libvips/resample/presample.h | 3 + libvips/resample/reducehl3.cpp | 421 +++++++++++++++++++++++++++++++ libvips/resample/reducevl3.cpp | 319 +++++++++++++++++++++++ libvips/resample/resample.c | 4 + libvips/resample/templates.h | 24 +- 11 files changed, 803 insertions(+), 6 deletions(-) create mode 100644 libvips/resample/reducehl3.cpp create mode 100644 libvips/resample/reducevl3.cpp diff --git a/libvips/include/vips/Makefile.am b/libvips/include/vips/Makefile.am index 5ada4f8b..33630384 100644 --- a/libvips/include/vips/Makefile.am +++ b/libvips/include/vips/Makefile.am @@ -56,6 +56,7 @@ EXTRA_DIST = version.h.in internal.h enumtemplate # we need absolute filenames here since this list appears in the src dir as # well vips_scan_headers = \ + ${top_srcdir}/libvips/include/vips/resample.h \ ${top_srcdir}/libvips/include/vips/memory.h \ ${top_srcdir}/libvips/include/vips/create.h \ ${top_srcdir}/libvips/include/vips/foreign.h \ diff --git a/libvips/include/vips/enumtypes.h b/libvips/include/vips/enumtypes.h index 37380972..8fdc127a 100644 --- a/libvips/include/vips/enumtypes.h +++ b/libvips/include/vips/enumtypes.h @@ -6,6 +6,9 @@ G_BEGIN_DECLS +/* enumerations from "../../../libvips/include/vips/resample.h" */ +GType vips_kernel_get_type (void) G_GNUC_CONST; +#define VIPS_TYPE_KERNEL (vips_kernel_get_type()) /* enumerations from "../../../libvips/include/vips/foreign.h" */ GType vips_foreign_flags_get_type (void) G_GNUC_CONST; #define VIPS_TYPE_FOREIGN_FLAGS (vips_foreign_flags_get_type()) diff --git a/libvips/include/vips/resample.h b/libvips/include/vips/resample.h index 749f4fea..821a9995 100644 --- a/libvips/include/vips/resample.h +++ b/libvips/include/vips/resample.h @@ -38,6 +38,15 @@ extern "C" { #endif /*__cplusplus*/ +typedef enum { + VIPS_KERNEL_NEAREST, + VIPS_KERNEL_LINEAR, + VIPS_KERNEL_CUBIC, + VIPS_KERNEL_LANCZOS2, + VIPS_KERNEL_LANCZOS3, + VIPS_KERNEL_LAST +} VipsKernel; + int vips_shrink( VipsImage *in, VipsImage **out, double xshrink, double yshrink, ... ) __attribute__((sentinel)); diff --git a/libvips/iofuncs/Makefile.am b/libvips/iofuncs/Makefile.am index d0d32998..eff85d1b 100644 --- a/libvips/iofuncs/Makefile.am +++ b/libvips/iofuncs/Makefile.am @@ -54,6 +54,7 @@ AM_CPPFLAGS = -I${top_srcdir}/libvips/include @VIPS_CFLAGS@ @VIPS_INCLUDES@ # we need absolute filenames here since this list appears in the header dir as # well vips_scan_headers = \ + ${top_srcdir}/libvips/include/vips/resample.h \ ${top_srcdir}/libvips/include/vips/memory.h \ ${top_srcdir}/libvips/include/vips/create.h \ ${top_srcdir}/libvips/include/vips/foreign.h \ diff --git a/libvips/iofuncs/enumtypes.c b/libvips/iofuncs/enumtypes.c index 8dc6a448..4fb8062f 100644 --- a/libvips/iofuncs/enumtypes.c +++ b/libvips/iofuncs/enumtypes.c @@ -4,6 +4,28 @@ /* auto-generated enums for vips introspection */ #include +/* enumerations from "../../libvips/include/vips/resample.h" */ +GType +vips_kernel_get_type( void ) +{ + static GType etype = 0; + + if( etype == 0 ) { + static const GEnumValue values[] = { + {VIPS_KERNEL_NEAREST, "VIPS_KERNEL_NEAREST", "nearest"}, + {VIPS_KERNEL_LINEAR, "VIPS_KERNEL_LINEAR", "linear"}, + {VIPS_KERNEL_CUBIC, "VIPS_KERNEL_CUBIC", "cubic"}, + {VIPS_KERNEL_LANCZOS2, "VIPS_KERNEL_LANCZOS2", "lanczos2"}, + {VIPS_KERNEL_LANCZOS3, "VIPS_KERNEL_LANCZOS3", "lanczos3"}, + {VIPS_KERNEL_LAST, "VIPS_KERNEL_LAST", "last"}, + {0, NULL, NULL} + }; + + etype = g_enum_register_static( "VipsKernel", values ); + } + + return( etype ); +} /* enumerations from "../../libvips/include/vips/foreign.h" */ GType vips_foreign_flags_get_type( void ) diff --git a/libvips/resample/Makefile.am b/libvips/resample/Makefile.am index bae493c0..a2d5b5fd 100644 --- a/libvips/resample/Makefile.am +++ b/libvips/resample/Makefile.am @@ -11,7 +11,9 @@ libresample_la_SOURCES = \ shrinkv.c \ reduce.c \ reduceh.cpp \ + reducehl3.cpp \ reducev.cpp \ + reducevl3.cpp \ interpolate.c \ transform.c \ bicubic.cpp \ diff --git a/libvips/resample/presample.h b/libvips/resample/presample.h index fc678930..a8f587f4 100644 --- a/libvips/resample/presample.h +++ b/libvips/resample/presample.h @@ -65,6 +65,9 @@ typedef struct _VipsResampleClass { GType vips_resample_get_type( void ); +int vips_reducehl3_get_points( VipsKernel kernel ); +void vips_reducehl3_make_mask( VipsKernel kernel, double x, double *c ); + #ifdef __cplusplus } #endif /*__cplusplus*/ diff --git a/libvips/resample/reducehl3.cpp b/libvips/resample/reducehl3.cpp new file mode 100644 index 00000000..876e9c7b --- /dev/null +++ b/libvips/resample/reducehl3.cpp @@ -0,0 +1,421 @@ +/* horizontal reduce by a float factor with lanczos3 + * + * 29/1/16 + * - from shrinkh.c + */ + +/* + + This file is part of VIPS. + + VIPS is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +/* +#define DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include + +#include +#include +#include + +#include "presample.h" +#include "templates.h" + +/** + * VipsKernel: + * @VIPS_KERNEL_NEAREST: nearest-neighbour + * @VIPS_KERNEL_LINEAR: linear interpolation + * @VIPS_KERNEL_CUBIC: cubic interpolation + * @VIPS_KERNEL_LANCZOS2: lanczos2 interpolation + * @VIPS_KERNEL_LANCZOS3: lanczos3 interpolation + * + * 1D resampling kernels. + */ + +/* The max size of the vector we use. + */ +#define MAX_POINTS (6) + +typedef struct _VipsReducehl3 { + VipsResample parent_instance; + + double xshrink; /* Reduce factor */ + + /* The thing we use to make the kernel. + */ + VipsKernel kernel; + + /* Number of points in kernel. + */ + int n_points; + + /* Precalculated interpolation matrices. int (used for pel + * 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]; + +} VipsReducehl3; + +typedef VipsResampleClass VipsReducehl3Class; + +/* We need C linkage for this. + */ +extern "C" { +G_DEFINE_TYPE( VipsReducehl3, vips_reducehl3, VIPS_TYPE_RESAMPLE ); +} + +/* Get n points. + */ +int +vips_reducehl3_get_points( VipsKernel kernel ) +{ + switch( kernel ) { + case VIPS_KERNEL_NEAREST: + return( 1 ); + + case VIPS_KERNEL_LINEAR: + return( 2 ); + + case VIPS_KERNEL_CUBIC: + return( 4 ); + + case VIPS_KERNEL_LANCZOS2: + return( 4 ); + + case VIPS_KERNEL_LANCZOS3: + return( 6 ); + + default: + g_assert_not_reached(); + return( 0 ); + } +} + +/* Calculate a mask. + */ +void +vips_reducehl3_make_mask( VipsKernel kernel, double x, double *c ) +{ + switch( kernel ) { + case VIPS_KERNEL_NEAREST: + c[0] = 1.0; + break; + + case VIPS_KERNEL_LINEAR: + c[0] = x; + c[1] = 1.0 - x; + break; + + case VIPS_KERNEL_CUBIC: + calculate_coefficients_catmull( x, c ); + break; + + case VIPS_KERNEL_LANCZOS2: + calculate_coefficients_lanczos( 2, x, c ); + break; + + case VIPS_KERNEL_LANCZOS3: + calculate_coefficients_lanczos( 3, x, c ); + break; + + default: + g_assert_not_reached(); + break; + } +} + +template +static void inline +reducehl3_unsigned_int_tab( VipsReducehl3 *reducehl3, + VipsPel *pout, const VipsPel *pin, + const int bands, const int * restrict cx ) +{ + T* restrict out = (T *) pout; + const T* restrict in = (T *) pin; + + for( int z = 0; z < bands; z++ ) { + int sum; + + sum = 0; + for( int i = 0; i < reducehl3->n_points; i++ ) + sum += cx[i] * in[i * bands]; + + sum = unsigned_fixed_round( sum ); + + sum = VIPS_CLIP( 0, sum, max_value ); + + out[z] = sum; + + in += 1; + } +} + +static int +vips_reducehl3_gen( VipsRegion *out_region, void *seq, + void *a, void *b, gboolean *stop ) +{ + VipsImage *in = (VipsImage *) a; + VipsReducehl3 *reducehl3 = (VipsReducehl3 *) b; + const int ps = VIPS_IMAGE_SIZEOF_PEL( in ); + VipsRegion *ir = (VipsRegion *) seq; + VipsRect *r = &out_region->valid; + + /* Double bands for complex. + */ + const int bands = in->Bands * + (vips_band_format_iscomplex( in->BandFmt ) ? 2 : 1); + + VipsRect s; + +#ifdef DEBUG + printf( "vips_reducehl3_gen: generating %d x %d at %d x %d\n", + r->width, r->height, r->left, r->top ); +#endif /*DEBUG*/ + + s.left = r->left * reducehl3->xshrink; + s.top = r->top; + s.width = r->width * reducehl3->xshrink + reducehl3->n_points; + s.height = r->height; + if( vips_region_prepare( ir, &s ) ) + return( -1 ); + + VIPS_GATE_START( "vips_reducehl3_gen: work" ); + + for( int y = 0; y < r->height; y ++ ) { + VipsPel *q; + double X; + + q = VIPS_REGION_ADDR( out_region, r->left, r->top + y ); + X = r->left * reducehl3->xshrink; + + for( int x = 0; x < r->width; x++ ) { + int ix = (int) X; + VipsPel *p = VIPS_REGION_ADDR( ir, ix, r->top + y ); + const int sx = X * VIPS_TRANSFORM_SCALE * 2; + const int six = sx & (VIPS_TRANSFORM_SCALE * 2 - 1); + const int tx = (six + 1) >> 1; + const int *cxi = reducehl3->matrixi[tx]; + const double *cxf = reducehl3->matrixf[tx]; + + switch( in->BandFmt ) { + case VIPS_FORMAT_UCHAR: + reducehl3_unsigned_int_tab + ( + reducehl3, + q, p, bands, cxi ); + break; + + default: + g_assert_not_reached(); + break; + } + + X += reducehl3->xshrink; + q += ps; + } + } + + VIPS_GATE_STOP( "vips_reducehl3_gen: work" ); + + return( 0 ); +} + +static int +vips_reducehl3_build( VipsObject *object ) +{ + VipsObjectClass *object_class = VIPS_OBJECT_GET_CLASS( object ); + VipsResample *resample = VIPS_RESAMPLE( object ); + VipsReducehl3 *reducehl3 = (VipsReducehl3 *) object; + VipsImage **t = (VipsImage **) + vips_object_local_array( object, 2 ); + + VipsImage *in; + + if( VIPS_OBJECT_CLASS( vips_reducehl3_parent_class )->build( object ) ) + return( -1 ); + + in = resample->in; + + if( reducehl3->xshrink < 1 ) { + vips_error( object_class->nickname, + "%s", _( "reduce factors should be >= 1" ) ); + return( -1 ); + } + if( reducehl3->xshrink > 3 ) + vips_warn( object_class->nickname, + "%s", _( "reduce factor greater than 3" ) ); + + if( reducehl3->xshrink == 1 ) + return( vips_image_write( in, resample->out ) ); + + /* Build the tables of pre-computed coefficients. + */ + reducehl3->n_points = vips_reducehl3_get_points( reducehl3->kernel ); + for( int x = 0; x < VIPS_TRANSFORM_SCALE + 1; x++ ) { + vips_reducehl3_make_mask( reducehl3->kernel, + (float) x / VIPS_TRANSFORM_SCALE, + reducehl3->matrixf[x] ); + + for( int i = 0; i < reducehl3->n_points; i++ ) + reducehl3->matrixi[x][i] = reducehl3->matrixf[x][i] * + VIPS_INTERPOLATE_SCALE; + } + + /* Unpack for processing. + */ + if( vips_image_decode( in, &t[0] ) ) + return( -1 ); + in = t[0]; + + /* Add new pixels around the input so we can interpolate at the edges. + */ + if( vips_embed( in, &t[1], + reducehl3->n_points / 2, 0, + in->Xsize + reducehl3->n_points - 1, in->Ysize, + "extend", VIPS_EXTEND_COPY, + NULL ) ) + return( -1 ); + in = t[1]; + + if( vips_image_pipelinev( resample->out, + VIPS_DEMAND_STYLE_THINSTRIP, in, NULL ) ) + return( -1 ); + + /* Size output. Note: we round the output width down! + * + * Don't change xres/yres, leave that to the application layer. For + * example, vipsthumbnail knows the true reduce factor (including the + * fractional part), we just see the integer part here. + */ + resample->out->Xsize = (in->Xsize - reducehl3->n_points + 1) / + reducehl3->xshrink; + if( resample->out->Xsize <= 0 ) { + vips_error( object_class->nickname, + "%s", _( "image has shrunk to nothing" ) ); + return( -1 ); + } + +#ifdef DEBUG + printf( "vips_reducehl3_build: reducing %d x %d image to %d x %d\n", + in->Xsize, in->Ysize, + resample->out->Xsize, resample->out->Ysize ); +#endif /*DEBUG*/ + + if( vips_image_generate( resample->out, + vips_start_one, vips_reducehl3_gen, vips_stop_one, + in, reducehl3 ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_reducehl3_class_init( VipsReducehl3Class *reducehl3_class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( reducehl3_class ); + VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( reducehl3_class ); + VipsOperationClass *operation_class = + VIPS_OPERATION_CLASS( reducehl3_class ); + + VIPS_DEBUG_MSG( "vips_reducehl3_class_init\n" ); + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + vobject_class->nickname = "reducehl3"; + vobject_class->description = _( "shrink an image horizontally" ); + vobject_class->build = vips_reducehl3_build; + + operation_class->flags = VIPS_OPERATION_SEQUENTIAL_UNBUFFERED; + + VIPS_ARG_DOUBLE( reducehl3_class, "xshrink", 3, + _( "Xshrink" ), + _( "Horizontal shrink factor" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsReducehl3, xshrink ), + 1, 1000000, 1 ); + + VIPS_ARG_ENUM( reducehl3_class, "kernel", 3, + _( "Kernel" ), + _( "Resamling kernel" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsReducehl3, kernel ), + VIPS_TYPE_KERNEL, VIPS_KERNEL_CUBIC ); + +} + +static void +vips_reducehl3_init( VipsReducehl3 *reducehl3 ) +{ + reducehl3->kernel = VIPS_KERNEL_CUBIC; +} + +/** + * vips_reducehl3: + * @in: input image + * @out: output image + * @xshrink: horizontal reduce + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * @kernel: #VipsKernel to use to interpolate (default: cubic) + * + * Reduce @in horizontally by a float factor. The pixels in @out are + * interpolated with a 1D mask. This operation will not work well for + * a reduction of more than a factor of two. + * + * This is a very low-level operation: see vips_resize() for a more + * convenient way to resize images. + * + * This operation does not change xres or yres. The image resolution needs to + * be updated by the application. + * + * See also: vips_shrink(), vips_resize(), vips_affine(). + * + * Returns: 0 on success, -1 on error + */ +int +vips_reducehl3( VipsImage *in, VipsImage **out, double xshrink, ... ) +{ + va_list ap; + int result; + + va_start( ap, xshrink ); + result = vips_call_split( "reducehl3", ap, in, out, xshrink ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/resample/reducevl3.cpp b/libvips/resample/reducevl3.cpp new file mode 100644 index 00000000..d291167d --- /dev/null +++ b/libvips/resample/reducevl3.cpp @@ -0,0 +1,319 @@ +/* horizontal reduce by a float factor with lanczos3 + * + * 29/1/16 + * - from shrinkv.c + */ + +/* + + This file is part of VIPS. + + VIPS is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +/* +#define DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include + +#include +#include +#include + +#include "presample.h" +#include "templates.h" + +typedef struct _VipsReducevl3 { + VipsResample parent_instance; + + double yshrink; /* Shrink factor */ + +} VipsReducevl3; + +typedef VipsResampleClass VipsReducevl3Class; + +/* Precalculated interpolation matrices. int (used for pel + * sizes up to short), and double (for all others). We go to + * scale + 1 so we can round-to-nearest safely. + */ + +const int n_points = 6; + +static int vips_reducevl3_matrixi[VIPS_TRANSFORM_SCALE + 1][n_points]; +static double vips_reducevl3_matrixf[VIPS_TRANSFORM_SCALE + 1][n_points]; + +/* We need C linkage for this. + */ +extern "C" { +G_DEFINE_TYPE( VipsReducevl3, vips_reducevl3, VIPS_TYPE_RESAMPLE ); +} + +template +static void inline +reducevl3_unsigned_int_tab( VipsPel *pout, const VipsPel *pin, + const int ne, const int lskip, + const int * restrict cy ) +{ + T* restrict out = (T *) pout; + const T* restrict in = (T *) pin; + + const int l1 = lskip / sizeof( T ); + + for( int z = 0; z < ne; z++ ) { + int sum; + + sum = 0; + for( int i = 0; i < n_points; i++ ) + sum += cy[i] * in[i * l1]; + + sum = unsigned_fixed_round( sum ); + + sum = VIPS_CLIP( 0, sum, max_value ); + + out[z] = sum; + + in += 1; + } +} + +static int +vips_reducevl3_gen( VipsRegion *out_region, void *seq, + void *a, void *b, gboolean *stop ) +{ + VipsImage *in = (VipsImage *) a; + VipsReducevl3 *reducevl3 = (VipsReducevl3 *) b; + VipsRegion *ir = (VipsRegion *) seq; + VipsRect *r = &out_region->valid; + + /* Double bands for complex. + */ + const int bands = in->Bands * + (vips_band_format_iscomplex( in->BandFmt ) ? 2 : 1); + int ne = r->width * bands; + + VipsRect s; + +#ifdef DEBUG + printf( "vips_reducevl3_gen: generating %d x %d at %d x %d\n", + r->width, r->height, r->left, r->top ); +#endif /*DEBUG*/ + + s.left = r->left; + s.top = r->top * reducevl3->yshrink; + s.width = r->width; + s.height = r->height * reducevl3->yshrink + n_points; + if( vips_region_prepare( ir, &s ) ) + return( -1 ); + + VIPS_GATE_START( "vips_reducevl3_gen: work" ); + + for( int y = 0; y < r->height; y ++ ) { + VipsPel *q = VIPS_REGION_ADDR( out_region, r->left, r->top + y ); + const double Y = (r->top + y) * reducevl3->yshrink; + VipsPel *p = VIPS_REGION_ADDR( ir, r->left, (int) Y ); + const int sy = Y * VIPS_TRANSFORM_SCALE * 2; + const int siy = sy & (VIPS_TRANSFORM_SCALE * 2 - 1); + const int ty = (siy + 1) >> 1; + const int *cyi = vips_reducevl3_matrixi[ty]; + const double *cyf = vips_reducevl3_matrixf[ty]; + const int lskip = VIPS_REGION_LSKIP( ir ); + + switch( in->BandFmt ) { + case VIPS_FORMAT_UCHAR: + reducevl3_unsigned_int_tab + ( + q, p, ne, lskip, cyi ); + break; + + default: + g_assert_not_reached(); + break; + } + } + + VIPS_GATE_STOP( "vips_reducevl3_gen: work" ); + + return( 0 ); +} + +static int +vips_reducevl3_build( VipsObject *object ) +{ + VipsObjectClass *object_class = VIPS_OBJECT_GET_CLASS( object ); + VipsResample *resample = VIPS_RESAMPLE( object ); + VipsReducevl3 *reducevl3 = (VipsReducevl3 *) object; + VipsImage **t = (VipsImage **) vips_object_local_array( object, 2 ); + + VipsImage *in; + + if( VIPS_OBJECT_CLASS( vips_reducevl3_parent_class )->build( object ) ) + return( -1 ); + + in = resample->in; + + if( reducevl3->yshrink < 1 ) { + vips_error( object_class->nickname, + "%s", _( "reduce factors should be >= 1" ) ); + return( -1 ); + } + if( reducevl3->yshrink > 3 ) + vips_warn( object_class->nickname, + "%s", _( "reduce factor greater than 3" ) ); + + if( reducevl3->yshrink == 1 ) + return( vips_image_write( in, resample->out ) ); + + /* Unpack for processing. + */ + if( vips_image_decode( in, &t[0] ) ) + return( -1 ); + in = t[0]; + + /* Add new pixels around the input so we can interpolate at the edges. + */ + if( vips_embed( in, &t[1], + 0, n_points / 2, + in->Xsize, in->Ysize + n_points - 1, + "extend", VIPS_EXTEND_COPY, + NULL ) ) + return( -1 ); + in = t[1]; + + if( vips_image_pipelinev( resample->out, + VIPS_DEMAND_STYLE_SMALLTILE, in, NULL ) ) + return( -1 ); + + /* Size output. Note: we round the output width down! + * + * Don't change xres/yres, leave that to the application layer. For + * example, vipsthumbnail knows the true reduce factor (including the + * fractional part), we just see the integer part here. + */ + resample->out->Ysize = (in->Ysize - n_points + 1) / reducevl3->yshrink; + if( resample->out->Ysize <= 0 ) { + vips_error( object_class->nickname, + "%s", _( "image has shrunk to nothing" ) ); + return( -1 ); + } + +#ifdef DEBUG + printf( "vips_reducevl3_build: reducing %d x %d image to %d x %d\n", + in->Xsize, in->Ysize, + resample->out->Xsize, resample->out->Ysize ); +#endif /*DEBUG*/ + + if( vips_image_generate( resample->out, + vips_start_one, vips_reducevl3_gen, vips_stop_one, + in, reducevl3 ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_reducevl3_class_init( VipsReducevl3Class *reducevl3_class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( reducevl3_class ); + VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( reducevl3_class ); + VipsOperationClass *operation_class = + VIPS_OPERATION_CLASS( reducevl3_class ); + + VIPS_DEBUG_MSG( "vips_reducevl3_class_init\n" ); + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + vobject_class->nickname = "reducevl3"; + vobject_class->description = _( "shrink an image vertically" ); + vobject_class->build = vips_reducevl3_build; + + operation_class->flags = VIPS_OPERATION_SEQUENTIAL_UNBUFFERED; + + VIPS_ARG_DOUBLE( reducevl3_class, "yshrink", 3, + _( "Xshrink" ), + _( "Vertical shrink factor" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsReducevl3, yshrink ), + 1, 1000000, 1 ); + + /* Build the tables of pre-computed coefficients. + */ + for( int y = 0; y < VIPS_TRANSFORM_SCALE + 1; y++ ) { + calculate_coefficients_lanczos( 3, + (float) y / VIPS_TRANSFORM_SCALE, + vips_reducevl3_matrixf[y] ); + + for( int i = 0; i < n_points; i++ ) + vips_reducevl3_matrixi[y][i] = + vips_reducevl3_matrixf[y][i] * + VIPS_INTERPOLATE_SCALE; + } + +} + +static void +vips_reducevl3_init( VipsReducevl3 *reducevl3 ) +{ +} + +/** + * vips_reducevl3: + * @in: input image + * @out: output image + * @yshrink: horizontal reduce + * @...: %NULL-terminated list of optional named arguments + * + * Reduce @in vertically by a float factor. The pixels in @out are + * interpolated with a 1D cubic mask. This operation will not work well for + * a reduction of more than a factor of two. + * + * This is a very low-level operation: see vips_resize() for a more + * convenient way to resize images. + * + * This operation does not change xres or yres. The image resolution needs to + * be updated by the application. + * + * See also: vips_shrink(), vips_resize(), vips_affine(). + * + * Returns: 0 on success, -1 on error + */ +int +vips_reducevl3( VipsImage *in, VipsImage **out, double yshrink, ... ) +{ + va_list ap; + int result; + + va_start( ap, yshrink ); + result = vips_call_split( "reducevl3", ap, in, out, yshrink ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/resample/resample.c b/libvips/resample/resample.c index 18b21564..1b45f328 100644 --- a/libvips/resample/resample.c +++ b/libvips/resample/resample.c @@ -139,7 +139,9 @@ vips_resample_operation_init( void ) extern GType vips_shrinkv_get_type( void ); extern GType vips_reduce_get_type( void ); extern GType vips_reduceh_get_type( void ); + extern GType vips_reducehl3_get_type( void ); extern GType vips_reducev_get_type( void ); + extern GType vips_reducevl3_get_type( void ); extern GType vips_quadratic_get_type( void ); extern GType vips_affine_get_type( void ); extern GType vips_similarity_get_type( void ); @@ -150,7 +152,9 @@ vips_resample_operation_init( void ) vips_shrinkh_get_type(); vips_shrinkv_get_type(); vips_reduceh_get_type(); + vips_reducehl3_get_type(); vips_reducev_get_type(); + vips_reducevl3_get_type(); vips_reduce_get_type(); vips_quadratic_get_type(); vips_affine_get_type(); diff --git a/libvips/resample/templates.h b/libvips/resample/templates.h index 3aab774f..e6087236 100644 --- a/libvips/resample/templates.h +++ b/libvips/resample/templates.h @@ -312,18 +312,30 @@ calculate_coefficients_catmull( const double x, double c[4] ) } /* Given an offset in [0,1] (we can have x == 1 when building tables), - * calculate c0 .. c6, the lanczos3 coefficients. This is called + * calculate c0 .. c(a * 2 - 1), the lanczos coefficients. This is called * from the interpolator as well as from the table builder. */ static void inline -calculate_coefficients_lanczos3( const double x, double c[7] ) +calculate_coefficients_lanczos( int a, const double x, double *c ) { int i; - for( i = 0; i < 7; i++ ) { - double xp = (i - 3) + x; + for( i = 0; i < a * 2; i++ ) { + double xp = (i - a) + (1 - x); - c[i] = 3.0 * sin( VIPS_PI * xp ) * sin( VIPS_PI * xp / 3.0 ) / - (VIPS_PI * VIPS_PI * xp * xp); + double l; + + if( xp == 0.0 ) + l = 1.0; + else if( xp < -a ) + l = 0.0; + else if( xp > a ) + l = 0.0; + else + l = (double) a * sin( VIPS_PI * xp ) * + sin( VIPS_PI * xp / (double) a ) / + (VIPS_PI * VIPS_PI * xp * xp); + + c[i] = l; } } From 013102de0167b2caffab8e9f5064d0645ad5a2d0 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 10 Mar 2016 08:43:50 +0000 Subject: [PATCH 04/20] finish hl3 version vl3 nest --- libvips/resample/reduceh.cpp | 2 +- libvips/resample/reducehl3.cpp | 222 +++++++++++++++++++++++++++++++-- 2 files changed, 215 insertions(+), 9 deletions(-) diff --git a/libvips/resample/reduceh.cpp b/libvips/resample/reduceh.cpp index 23f78384..e70f3ff8 100644 --- a/libvips/resample/reduceh.cpp +++ b/libvips/resample/reduceh.cpp @@ -138,7 +138,7 @@ reduceh_signed_int_tab( VipsPel *pout, const VipsPel *pin, } } -/* Floating-point version, for int/float types. +/* Floating-point version. */ template static void inline diff --git a/libvips/resample/reducehl3.cpp b/libvips/resample/reducehl3.cpp index 876e9c7b..3bd2f15e 100644 --- a/libvips/resample/reducehl3.cpp +++ b/libvips/resample/reducehl3.cpp @@ -2,6 +2,8 @@ * * 29/1/16 * - from shrinkh.c + * 10/3/16 + * - add other kernels */ /* @@ -156,6 +158,56 @@ vips_reducehl3_make_mask( VipsKernel kernel, double x, double *c ) } } +/* A 4-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 + * 11-point kernel for the vector version to be worthwhile. + */ +static void inline +reducehl3_unsigned_uint8_4tab( VipsPel *out, const VipsPel *in, + const int bands, const int *cx ) +{ + const int b1 = bands; + const int b2 = b1 + b1; + const int b3 = b1 + b2; + + const int c0 = cx[0]; + const int c1 = cx[1]; + const int c2 = cx[2]; + const int c3 = cx[3]; + + for( int z = 0; z < bands; z++ ) { + int cubich = unsigned_fixed_round( + c0 * in[0] + + c1 * in[b1] + + c2 * in[b2] + + c3 * in[b3] ); + + cubich = VIPS_CLIP( 0, cubich, 255 ); + + out[z] = cubich; + + in += 1; + } +} + +/* Our inner loop. Operate on elements of size T, gather results in an + * intermediate of type IT. + */ +template +static IT +reducehl3_sum( const T * restrict in, int bands, const IT * restrict c, int n ) +{ + IT sum; + + sum = 0; + for( int i = 0; i < n; i++ ) + sum += c[i] * in[i * bands]; + + return( sum ); +} + template static void inline reducehl3_unsigned_int_tab( VipsReducehl3 *reducehl3, @@ -167,13 +219,9 @@ reducehl3_unsigned_int_tab( VipsReducehl3 *reducehl3, for( int z = 0; z < bands; z++ ) { int sum; - - sum = 0; - for( int i = 0; i < reducehl3->n_points; i++ ) - sum += cx[i] * in[i * bands]; - + + sum = reducehl3_sum(in, bands, cx, reducehl3->n_points); sum = unsigned_fixed_round( sum ); - sum = VIPS_CLIP( 0, sum, max_value ); out[z] = sum; @@ -182,6 +230,113 @@ reducehl3_unsigned_int_tab( VipsReducehl3 *reducehl3, } } +template +static void inline +reducehl3_signed_int_tab( VipsReducehl3 *reducehl3, + VipsPel *pout, const VipsPel *pin, + const int bands, const int * restrict cx ) +{ + T* restrict out = (T *) pout; + const T* restrict in = (T *) pin; + + for( int z = 0; z < bands; z++ ) { + int sum; + + sum = reducehl3_sum(in, bands, cx, reducehl3->n_points); + sum = signed_fixed_round( sum ); + sum = VIPS_CLIP( min_value, sum, max_value ); + + out[z] = sum; + + in += 1; + } +} + +/* Floating-point version. + */ +template +static void inline +reducehl3_float_tab( VipsReducehl3 *reducehl3, + VipsPel *pout, const VipsPel *pin, + const int bands, const double *cx ) +{ + T* restrict out = (T *) pout; + const T* restrict in = (T *) pin; + + for( int z = 0; z < bands; z++ ) { + out[z] = reducehl3_sum + (in, bands, cx, reducehl3->n_points); + in += 1; + } +} + +/* 32-bit int output needs a double intermediate. + */ + +template +static void inline +reducehl3_unsigned_int32_tab( VipsReducehl3 *reducehl3, + VipsPel *pout, const VipsPel *pin, + const int bands, const double * restrict cx ) +{ + T* restrict out = (T *) pout; + const T* restrict in = (T *) pin; + + for( int z = 0; z < bands; z++ ) { + double sum; + + sum = reducehl3_sum + (in, bands, cx, reducehl3->n_points); + out[z] = VIPS_CLIP( 0, sum, max_value ); + + in += 1; + } +} + +template +static void inline +reducehl3_signed_int32_tab( VipsReducehl3 *reducehl3, + VipsPel *pout, const VipsPel *pin, + const int bands, const double * restrict cx ) +{ + T* restrict out = (T *) pout; + const T* restrict in = (T *) pin; + + for( int z = 0; z < bands; z++ ) { + double sum; + + sum = reducehl3_sum + (in, bands, cx, reducehl3->n_points); + sum = VIPS_CLIP( min_value, sum, max_value ); + out[z] = sum; + + in += 1; + } +} + +/* Ultra-high-quality version for double images. + */ +template +static void inline +reducehl3_notab( VipsReducehl3 *reducehl3, + VipsPel *pout, const VipsPel *pin, + const int bands, double x ) +{ + T* restrict out = (T *) pout; + const T* restrict in = (T *) pin; + + double cx[MAX_POINTS]; + + vips_reducehl3_make_mask( reducehl3->kernel, x, cx ); + + for( int z = 0; z < bands; z++ ) { + out[z] = reducehl3_sum + (in, bands, cx, reducehl3->n_points); + + in += 1; + } +} + static int vips_reducehl3_gen( VipsRegion *out_region, void *seq, void *a, void *b, gboolean *stop ) @@ -231,12 +386,63 @@ vips_reducehl3_gen( VipsRegion *out_region, void *seq, switch( in->BandFmt ) { case VIPS_FORMAT_UCHAR: - reducehl3_unsigned_int_tab - ( + if( reducehl3->n_points == 4 ) + reducehl3_unsigned_uint8_4tab( + q, p, bands, cxi ); + else + reducehl3_unsigned_int_tab + ( + reducehl3, + q, p, bands, cxi ); + break; + + case VIPS_FORMAT_CHAR: + reducehl3_signed_int_tab + ( reducehl3, q, p, bands, cxi ); break; + case VIPS_FORMAT_USHORT: + reducehl3_unsigned_int_tab + ( + reducehl3, + q, p, bands, cxi ); + break; + + case VIPS_FORMAT_SHORT: + reducehl3_signed_int_tab + ( + reducehl3, + q, p, bands, cxi ); + break; + + case VIPS_FORMAT_UINT: + reducehl3_unsigned_int32_tab + ( + reducehl3, + q, p, bands, cxf ); + break; + + case VIPS_FORMAT_INT: + reducehl3_signed_int32_tab + ( + reducehl3, + q, p, bands, cxf ); + break; + + case VIPS_FORMAT_FLOAT: + case VIPS_FORMAT_COMPLEX: + reducehl3_float_tab( reducehl3, + q, p, bands, cxf ); + break; + + case VIPS_FORMAT_DOUBLE: + case VIPS_FORMAT_DPCOMPLEX: + reducehl3_notab( reducehl3, + q, p, bands, X - ix ); + break; + default: g_assert_not_reached(); break; From 4d10bd12f9466495acb45818c2d970d72f952533 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 10 Mar 2016 10:24:44 +0000 Subject: [PATCH 05/20] still trying to get reducevl3 to vectorise --- libvips/resample/presample.h | 8 ++- libvips/resample/reducehl3.cpp | 58 +++++++------------ libvips/resample/reducevl3.cpp | 101 +++++++++++++++++++++------------ libvips/resample/templates.h | 16 ++++++ 4 files changed, 107 insertions(+), 76 deletions(-) diff --git a/libvips/resample/presample.h b/libvips/resample/presample.h index a8f587f4..33f1e413 100644 --- a/libvips/resample/presample.h +++ b/libvips/resample/presample.h @@ -65,8 +65,12 @@ typedef struct _VipsResampleClass { GType vips_resample_get_type( void ); -int vips_reducehl3_get_points( VipsKernel kernel ); -void vips_reducehl3_make_mask( VipsKernel kernel, double x, double *c ); +/* The max size of the vector we use. + */ +#define MAX_POINTS (6) + +int vips_reduce_get_points( VipsKernel kernel ); +void vips_reduce_make_mask( VipsKernel kernel, double x, double *c ); #ifdef __cplusplus } diff --git a/libvips/resample/reducehl3.cpp b/libvips/resample/reducehl3.cpp index 3bd2f15e..55804c27 100644 --- a/libvips/resample/reducehl3.cpp +++ b/libvips/resample/reducehl3.cpp @@ -64,10 +64,6 @@ * 1D resampling kernels. */ -/* The max size of the vector we use. - */ -#define MAX_POINTS (6) - typedef struct _VipsReducehl3 { VipsResample parent_instance; @@ -101,7 +97,7 @@ G_DEFINE_TYPE( VipsReducehl3, vips_reducehl3, VIPS_TYPE_RESAMPLE ); /* Get n points. */ int -vips_reducehl3_get_points( VipsKernel kernel ) +vips_reduce_get_points( VipsKernel kernel ) { switch( kernel ) { case VIPS_KERNEL_NEAREST: @@ -128,7 +124,7 @@ vips_reducehl3_get_points( VipsKernel kernel ) /* Calculate a mask. */ void -vips_reducehl3_make_mask( VipsKernel kernel, double x, double *c ) +vips_reduce_make_mask( VipsKernel kernel, double x, double *c ) { switch( kernel ) { case VIPS_KERNEL_NEAREST: @@ -136,8 +132,8 @@ vips_reducehl3_make_mask( VipsKernel kernel, double x, double *c ) break; case VIPS_KERNEL_LINEAR: - c[0] = x; - c[1] = 1.0 - x; + c[0] = 1.0 - x; + c[1] = x; break; case VIPS_KERNEL_CUBIC: @@ -192,22 +188,6 @@ reducehl3_unsigned_uint8_4tab( VipsPel *out, const VipsPel *in, } } -/* Our inner loop. Operate on elements of size T, gather results in an - * intermediate of type IT. - */ -template -static IT -reducehl3_sum( const T * restrict in, int bands, const IT * restrict c, int n ) -{ - IT sum; - - sum = 0; - for( int i = 0; i < n; i++ ) - sum += c[i] * in[i * bands]; - - return( sum ); -} - template static void inline reducehl3_unsigned_int_tab( VipsReducehl3 *reducehl3, @@ -216,11 +196,12 @@ reducehl3_unsigned_int_tab( VipsReducehl3 *reducehl3, { T* restrict out = (T *) pout; const T* restrict in = (T *) pin; + const int n = reducehl3->n_points; for( int z = 0; z < bands; z++ ) { int sum; - sum = reducehl3_sum(in, bands, cx, reducehl3->n_points); + sum = reduce_sum( in, bands, cx, n ); sum = unsigned_fixed_round( sum ); sum = VIPS_CLIP( 0, sum, max_value ); @@ -238,11 +219,12 @@ reducehl3_signed_int_tab( VipsReducehl3 *reducehl3, { T* restrict out = (T *) pout; const T* restrict in = (T *) pin; + const int n = reducehl3->n_points; for( int z = 0; z < bands; z++ ) { int sum; - sum = reducehl3_sum(in, bands, cx, reducehl3->n_points); + sum = reduce_sum( in, bands, cx, n ); sum = signed_fixed_round( sum ); sum = VIPS_CLIP( min_value, sum, max_value ); @@ -262,10 +244,10 @@ reducehl3_float_tab( VipsReducehl3 *reducehl3, { T* restrict out = (T *) pout; const T* restrict in = (T *) pin; + const int n = reducehl3->n_points; for( int z = 0; z < bands; z++ ) { - out[z] = reducehl3_sum - (in, bands, cx, reducehl3->n_points); + out[z] = reduce_sum( in, bands, cx, n ); in += 1; } } @@ -281,12 +263,12 @@ reducehl3_unsigned_int32_tab( VipsReducehl3 *reducehl3, { T* restrict out = (T *) pout; const T* restrict in = (T *) pin; + const int n = reducehl3->n_points; for( int z = 0; z < bands; z++ ) { double sum; - sum = reducehl3_sum - (in, bands, cx, reducehl3->n_points); + sum = reduce_sum( in, bands, cx, n ); out[z] = VIPS_CLIP( 0, sum, max_value ); in += 1; @@ -301,12 +283,12 @@ reducehl3_signed_int32_tab( VipsReducehl3 *reducehl3, { T* restrict out = (T *) pout; const T* restrict in = (T *) pin; + const int n = reducehl3->n_points; for( int z = 0; z < bands; z++ ) { double sum; - sum = reducehl3_sum - (in, bands, cx, reducehl3->n_points); + sum = reduce_sum( in, bands, cx, n ); sum = VIPS_CLIP( min_value, sum, max_value ); out[z] = sum; @@ -324,14 +306,14 @@ reducehl3_notab( VipsReducehl3 *reducehl3, { T* restrict out = (T *) pout; const T* restrict in = (T *) pin; + const int n = reducehl3->n_points; double cx[MAX_POINTS]; - vips_reducehl3_make_mask( reducehl3->kernel, x, cx ); + vips_reduce_make_mask( reducehl3->kernel, x, cx ); for( int z = 0; z < bands; z++ ) { - out[z] = reducehl3_sum - (in, bands, cx, reducehl3->n_points); + out[z] = reduce_sum( in, bands, cx, n ); in += 1; } @@ -488,9 +470,9 @@ vips_reducehl3_build( VipsObject *object ) /* Build the tables of pre-computed coefficients. */ - reducehl3->n_points = vips_reducehl3_get_points( reducehl3->kernel ); + reducehl3->n_points = vips_reduce_get_points( reducehl3->kernel ); for( int x = 0; x < VIPS_TRANSFORM_SCALE + 1; x++ ) { - vips_reducehl3_make_mask( reducehl3->kernel, + vips_reduce_make_mask( reducehl3->kernel, (float) x / VIPS_TRANSFORM_SCALE, reducehl3->matrixf[x] ); @@ -575,7 +557,7 @@ vips_reducehl3_class_init( VipsReducehl3Class *reducehl3_class ) VIPS_ARG_ENUM( reducehl3_class, "kernel", 3, _( "Kernel" ), - _( "Resamling kernel" ), + _( "Resampling kernel" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsReducehl3, kernel ), VIPS_TYPE_KERNEL, VIPS_KERNEL_CUBIC ); diff --git a/libvips/resample/reducevl3.cpp b/libvips/resample/reducevl3.cpp index d291167d..e70ee469 100644 --- a/libvips/resample/reducevl3.cpp +++ b/libvips/resample/reducevl3.cpp @@ -2,6 +2,8 @@ * * 29/1/16 * - from shrinkv.c + * 10/3/16 + * - add other kernels */ /* @@ -51,25 +53,34 @@ #include "presample.h" #include "templates.h" +/* The max size of the vector we use. + */ +#define MAX_POINTS (6) + typedef struct _VipsReducevl3 { VipsResample parent_instance; double yshrink; /* Shrink factor */ + /* The thing we use to make the kernel. + */ + VipsKernel kernel; + + /* Number of points in kernel. + */ + int n_points; + + /* Precalculated interpolation matrices. int (used for pel + * 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]; + } VipsReducevl3; typedef VipsResampleClass VipsReducevl3Class; -/* Precalculated interpolation matrices. int (used for pel - * sizes up to short), and double (for all others). We go to - * scale + 1 so we can round-to-nearest safely. - */ - -const int n_points = 6; - -static int vips_reducevl3_matrixi[VIPS_TRANSFORM_SCALE + 1][n_points]; -static double vips_reducevl3_matrixf[VIPS_TRANSFORM_SCALE + 1][n_points]; - /* We need C linkage for this. */ extern "C" { @@ -78,29 +89,33 @@ G_DEFINE_TYPE( VipsReducevl3, vips_reducevl3, VIPS_TYPE_RESAMPLE ); template static void inline -reducevl3_unsigned_int_tab( VipsPel *pout, const VipsPel *pin, +reducevl3_unsigned_int_tab( VipsReducevl3 *reducevl3, + VipsPel *pout, const VipsPel *pin, const int ne, const int lskip, const int * restrict cy ) { T* restrict out = (T *) pout; const T* restrict in = (T *) pin; - + const int n = reducevl3->n_points; const int l1 = lskip / sizeof( T ); + const int round_by = VIPS_INTERPOLATE_SCALE >> 1; for( int z = 0; z < ne; z++ ) { int sum; - sum = 0; - for( int i = 0; i < n_points; i++ ) - sum += cy[i] * in[i * l1]; + sum = 0; + for( int i = 0; i < n; i++ ) + sum += cy[i] * in[z + i * l1]; - sum = unsigned_fixed_round( sum ); + sum = (sum + round_by) >> VIPS_INTERPOLATE_SHIFT; - sum = VIPS_CLIP( 0, sum, max_value ); + //sum = reduce_sum( in, l1, cy, n ); + //sum = unsigned_fixed_round( sum ); + //sum = VIPS_CLIP( 0, sum, max_value ); out[z] = sum; - in += 1; + //in += 1; } } @@ -129,7 +144,7 @@ vips_reducevl3_gen( VipsRegion *out_region, void *seq, s.left = r->left; s.top = r->top * reducevl3->yshrink; s.width = r->width; - s.height = r->height * reducevl3->yshrink + n_points; + s.height = r->height * reducevl3->yshrink + reducevl3->n_points; if( vips_region_prepare( ir, &s ) ) return( -1 ); @@ -142,14 +157,15 @@ vips_reducevl3_gen( VipsRegion *out_region, void *seq, const int sy = Y * VIPS_TRANSFORM_SCALE * 2; const int siy = sy & (VIPS_TRANSFORM_SCALE * 2 - 1); const int ty = (siy + 1) >> 1; - const int *cyi = vips_reducevl3_matrixi[ty]; - const double *cyf = vips_reducevl3_matrixf[ty]; + const int *cyi = reducevl3->matrixi[ty]; + const double *cyf = reducevl3->matrixf[ty]; const int lskip = VIPS_REGION_LSKIP( ir ); switch( in->BandFmt ) { case VIPS_FORMAT_UCHAR: reducevl3_unsigned_int_tab ( + reducevl3, q, p, ne, lskip, cyi ); break; @@ -191,6 +207,19 @@ vips_reducevl3_build( VipsObject *object ) if( reducevl3->yshrink == 1 ) return( vips_image_write( in, resample->out ) ); + /* Build the tables of pre-computed coefficients. + */ + reducevl3->n_points = vips_reduce_get_points( reducevl3->kernel ); + for( int y = 0; y < VIPS_TRANSFORM_SCALE + 1; y++ ) { + vips_reduce_make_mask( reducevl3->kernel, + (float) y / VIPS_TRANSFORM_SCALE, + reducevl3->matrixf[y] ); + + for( int i = 0; i < reducevl3->n_points; i++ ) + reducevl3->matrixi[y][i] = reducevl3->matrixf[y][i] * + VIPS_INTERPOLATE_SCALE; + } + /* Unpack for processing. */ if( vips_image_decode( in, &t[0] ) ) @@ -200,8 +229,8 @@ vips_reducevl3_build( VipsObject *object ) /* Add new pixels around the input so we can interpolate at the edges. */ if( vips_embed( in, &t[1], - 0, n_points / 2, - in->Xsize, in->Ysize + n_points - 1, + 0, reducevl3->n_points / 2, + in->Xsize, in->Ysize + reducevl3->n_points - 1, "extend", VIPS_EXTEND_COPY, NULL ) ) return( -1 ); @@ -217,7 +246,7 @@ vips_reducevl3_build( VipsObject *object ) * example, vipsthumbnail knows the true reduce factor (including the * fractional part), we just see the integer part here. */ - resample->out->Ysize = (in->Ysize - n_points + 1) / reducevl3->yshrink; + resample->out->Ysize = (in->Ysize - reducevl3->n_points + 1) / reducevl3->yshrink; if( resample->out->Ysize <= 0 ) { vips_error( object_class->nickname, "%s", _( "image has shrunk to nothing" ) ); @@ -264,24 +293,20 @@ vips_reducevl3_class_init( VipsReducevl3Class *reducevl3_class ) G_STRUCT_OFFSET( VipsReducevl3, yshrink ), 1, 1000000, 1 ); - /* Build the tables of pre-computed coefficients. - */ - for( int y = 0; y < VIPS_TRANSFORM_SCALE + 1; y++ ) { - calculate_coefficients_lanczos( 3, - (float) y / VIPS_TRANSFORM_SCALE, - vips_reducevl3_matrixf[y] ); + VIPS_ARG_ENUM( reducevl3_class, "kernel", 3, + _( "Kernel" ), + _( "Resampling kernel" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsReducevl3, kernel ), + VIPS_TYPE_KERNEL, VIPS_KERNEL_CUBIC ); - for( int i = 0; i < n_points; i++ ) - vips_reducevl3_matrixi[y][i] = - vips_reducevl3_matrixf[y][i] * - VIPS_INTERPOLATE_SCALE; - } } static void vips_reducevl3_init( VipsReducevl3 *reducevl3 ) { + reducevl3->kernel = VIPS_KERNEL_CUBIC; } /** @@ -291,8 +316,12 @@ vips_reducevl3_init( VipsReducevl3 *reducevl3 ) * @yshrink: horizontal reduce * @...: %NULL-terminated list of optional named arguments * + * Optional arguments: + * + * @kernel: #VipsKernel to use to interpolate (default: cubic) + * * Reduce @in vertically by a float factor. The pixels in @out are - * interpolated with a 1D cubic mask. This operation will not work well for + * interpolated with a 1D mask. This operation will not work well for * a reduction of more than a factor of two. * * This is a very low-level operation: see vips_resize() for a more diff --git a/libvips/resample/templates.h b/libvips/resample/templates.h index e6087236..50fd6d51 100644 --- a/libvips/resample/templates.h +++ b/libvips/resample/templates.h @@ -339,3 +339,19 @@ calculate_coefficients_lanczos( int a, const double x, double *c ) c[i] = l; } } + +/* Our inner loop for resampling with a convolution. Operate on elements of + * size T, gather results in an intermediate of type IT. + */ +template +static IT +reduce_sum( const T * restrict in, int stride, const IT * restrict c, int n ) +{ + IT sum; + + sum = 0; + for( int i = 0; i < n; i++ ) + sum += c[i] * in[i * stride]; + + return( sum ); +} From e9bf9363775599ba324f8ada8d5967e78386e5d9 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 10 Mar 2016 12:16:21 +0000 Subject: [PATCH 06/20] finish vl3 but it's still not producing identical results to reducev, seems to be offset by one? --- libvips/resample/reducehl3.cpp | 46 ++++---- libvips/resample/reducevl3.cpp | 205 ++++++++++++++++++++++++++++++--- 2 files changed, 213 insertions(+), 38 deletions(-) diff --git a/libvips/resample/reducehl3.cpp b/libvips/resample/reducehl3.cpp index 55804c27..24179fff 100644 --- a/libvips/resample/reducehl3.cpp +++ b/libvips/resample/reducehl3.cpp @@ -154,6 +154,29 @@ vips_reduce_make_mask( VipsKernel kernel, double x, double *c ) } } +template +static void inline +reducehl3_unsigned_int_tab( VipsReducehl3 *reducehl3, + VipsPel *pout, const VipsPel *pin, + const int bands, const int * restrict cx ) +{ + T* restrict out = (T *) pout; + const T* restrict in = (T *) pin; + const int n = reducehl3->n_points; + + for( int z = 0; z < bands; z++ ) { + int sum; + + sum = reduce_sum( in, bands, cx, n ); + sum = unsigned_fixed_round( sum ); + sum = VIPS_CLIP( 0, sum, max_value ); + + out[z] = sum; + + in += 1; + } +} + /* A 4-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 @@ -188,29 +211,6 @@ reducehl3_unsigned_uint8_4tab( VipsPel *out, const VipsPel *in, } } -template -static void inline -reducehl3_unsigned_int_tab( VipsReducehl3 *reducehl3, - VipsPel *pout, const VipsPel *pin, - const int bands, const int * restrict cx ) -{ - T* restrict out = (T *) pout; - const T* restrict in = (T *) pin; - const int n = reducehl3->n_points; - - for( int z = 0; z < bands; z++ ) { - int sum; - - sum = reduce_sum( in, bands, cx, n ); - sum = unsigned_fixed_round( sum ); - sum = VIPS_CLIP( 0, sum, max_value ); - - out[z] = sum; - - in += 1; - } -} - template static void inline reducehl3_signed_int_tab( VipsReducehl3 *reducehl3, diff --git a/libvips/resample/reducevl3.cpp b/libvips/resample/reducevl3.cpp index e70ee469..8fb7cba4 100644 --- a/libvips/resample/reducevl3.cpp +++ b/libvips/resample/reducevl3.cpp @@ -87,38 +87,162 @@ extern "C" { G_DEFINE_TYPE( VipsReducevl3, vips_reducevl3, VIPS_TYPE_RESAMPLE ); } +/* You'd think this would vectorise, but gcc hates mixed types in nested loops + * :-( + */ template static void inline reducevl3_unsigned_int_tab( VipsReducevl3 *reducevl3, VipsPel *pout, const VipsPel *pin, - const int ne, const int lskip, - const int * restrict cy ) + const int ne, const int lskip, const int * restrict cy ) { T* restrict out = (T *) pout; const T* restrict in = (T *) pin; const int n = reducevl3->n_points; const int l1 = lskip / sizeof( T ); - const int round_by = VIPS_INTERPOLATE_SCALE >> 1; for( int z = 0; z < ne; z++ ) { int sum; - sum = 0; - for( int i = 0; i < n; i++ ) - sum += cy[i] * in[z + i * l1]; + sum = reduce_sum( in + z, l1, cy, n ); + sum = unsigned_fixed_round( sum ); + sum = VIPS_CLIP( 0, sum, max_value ); - sum = (sum + round_by) >> VIPS_INTERPOLATE_SHIFT; + out[z] = sum; + } +} - //sum = reduce_sum( in, l1, cy, n ); - //sum = unsigned_fixed_round( sum ); - //sum = VIPS_CLIP( 0, sum, max_value ); +/* An unrolled version of ^^ for the most common case. + */ +static void inline +reducevl3_unsigned_uint8_4tab( VipsPel *out, const VipsPel *in, + const int ne, const int lskip, const int *cy ) +{ + const int l1 = lskip; + const int l2 = l1 + l1; + const int l3 = l1 + l2; + + const int c0 = cy[0]; + const int c1 = cy[1]; + const int c2 = cy[2]; + const int c3 = cy[3]; + + for( int z = 0; z < ne; z++ ) { + int sum = unsigned_fixed_round( + c0 * in[0] + + c1 * in[l1] + + c2 * in[l2] + + c3 * in[l3] ); + + sum = VIPS_CLIP( 0, sum, 255 ); out[z] = sum; - //in += 1; + in += 1; } } +template +static void inline +reducevl3_signed_int_tab( VipsReducevl3 *reducevl3, + VipsPel *pout, const VipsPel *pin, + const int ne, const int lskip, const int * restrict cy ) +{ + T* restrict out = (T *) pout; + const T* restrict in = (T *) pin; + const int n = reducevl3->n_points; + const int l1 = lskip / sizeof( T ); + + for( int z = 0; z < ne; z++ ) { + int sum; + + sum = reduce_sum( in + z, l1, cy, n ); + sum = signed_fixed_round( sum ); + sum = VIPS_CLIP( min_value, sum, max_value ); + + out[z] = sum; + } +} + +/* Floating-point version. + */ +template +static void inline +reducevl3_float_tab( VipsReducevl3 *reducevl3, + VipsPel *pout, const VipsPel *pin, + const int ne, const int lskip, const double * restrict cy ) +{ + T* restrict out = (T *) pout; + const T* restrict in = (T *) pin; + const int n = reducevl3->n_points; + const int l1 = lskip / sizeof( T ); + + for( int z = 0; z < ne; z++ ) + out[z] = reduce_sum( in + z, l1, cy, n ); +} + +/* 32-bit int output needs a double intermediate. + */ + +template +static void inline +reducevl3_unsigned_int32_tab( VipsReducevl3 *reducevl3, + VipsPel *pout, const VipsPel *pin, + const int ne, const int lskip, const double * restrict cy ) +{ + T* restrict out = (T *) pout; + const T* restrict in = (T *) pin; + const int n = reducevl3->n_points; + const int l1 = lskip / sizeof( T ); + + for( int z = 0; z < ne; z++ ) { + double sum; + + sum = reduce_sum( in + z, l1, cy, n ); + out[z] = VIPS_CLIP( 0, sum, max_value ); + } +} + +template +static void inline +reducevl3_signed_int32_tab( VipsReducevl3 *reducevl3, + VipsPel *pout, const VipsPel *pin, + const int ne, const int lskip, const double * restrict cy ) +{ + T* restrict out = (T *) pout; + const T* restrict in = (T *) pin; + const int n = reducevl3->n_points; + const int l1 = lskip / sizeof( T ); + + for( int z = 0; z < ne; z++ ) { + double sum; + + sum = reduce_sum( in + z, l1, cy, n ); + out[z] = VIPS_CLIP( min_value, sum, max_value ); + } +} + +/* Ultra-high-quality version for double images. + */ +template +static void inline +reducevl3_notab( VipsReducevl3 *reducevl3, + VipsPel *pout, const VipsPel *pin, + const int ne, const int lskip, double y ) +{ + T* restrict out = (T *) pout; + const T* restrict in = (T *) pin; + const int n = reducevl3->n_points; + const int l1 = lskip / sizeof( T ); + + double cy[MAX_POINTS]; + + vips_reduce_make_mask( reducevl3->kernel, y, cy ); + + for( int z = 0; z < ne; z++ ) + out[z] = reduce_sum( in + z, l1, cy, n ); +} + static int vips_reducevl3_gen( VipsRegion *out_region, void *seq, void *a, void *b, gboolean *stop ) @@ -151,7 +275,8 @@ vips_reducevl3_gen( VipsRegion *out_region, void *seq, VIPS_GATE_START( "vips_reducevl3_gen: work" ); for( int y = 0; y < r->height; y ++ ) { - VipsPel *q = VIPS_REGION_ADDR( out_region, r->left, r->top + y ); + VipsPel *q = + VIPS_REGION_ADDR( out_region, r->left, r->top + y ); const double Y = (r->top + y) * reducevl3->yshrink; VipsPel *p = VIPS_REGION_ADDR( ir, r->left, (int) Y ); const int sy = Y * VIPS_TRANSFORM_SCALE * 2; @@ -163,12 +288,63 @@ vips_reducevl3_gen( VipsRegion *out_region, void *seq, switch( in->BandFmt ) { case VIPS_FORMAT_UCHAR: - reducevl3_unsigned_int_tab - ( + if( reducevl3->n_points == 4 ) + reducevl3_unsigned_uint8_4tab( + q, p, ne, lskip, cyi ); + else + reducevl3_unsigned_int_tab + ( + reducevl3, + q, p, ne, lskip, cyi ); + break; + + case VIPS_FORMAT_CHAR: + reducevl3_signed_int_tab + ( reducevl3, q, p, ne, lskip, cyi ); break; + case VIPS_FORMAT_USHORT: + reducevl3_unsigned_int_tab + ( + reducevl3, + q, p, ne, lskip, cyi ); + break; + + case VIPS_FORMAT_SHORT: + reducevl3_signed_int_tab + ( + reducevl3, + q, p, ne, lskip, cyi ); + break; + + case VIPS_FORMAT_UINT: + reducevl3_unsigned_int32_tab + ( + reducevl3, + q, p, ne, lskip, cyf ); + break; + + case VIPS_FORMAT_INT: + reducevl3_signed_int32_tab + ( + reducevl3, + q, p, ne, lskip, cyf ); + break; + + case VIPS_FORMAT_FLOAT: + case VIPS_FORMAT_COMPLEX: + reducevl3_float_tab( reducevl3, + q, p, ne, lskip, cyf ); + break; + + case VIPS_FORMAT_DPCOMPLEX: + case VIPS_FORMAT_DOUBLE: + reducevl3_notab( reducevl3, + q, p, ne, lskip, Y - (int) Y ); + break; + default: g_assert_not_reached(); break; @@ -300,7 +476,6 @@ vips_reducevl3_class_init( VipsReducevl3Class *reducevl3_class ) G_STRUCT_OFFSET( VipsReducevl3, kernel ), VIPS_TYPE_KERNEL, VIPS_KERNEL_CUBIC ); - } static void From d351e07a6bb8bac4ca40e624b94c2faa24f902cf Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 10 Mar 2016 17:33:34 +0000 Subject: [PATCH 07/20] finish up --- TODO | 13 +- libvips/create/gaussmat.c | 12 +- libvips/include/vips/resample.h | 5 + libvips/resample/Makefile.am | 1 + libvips/resample/reducehl3.cpp | 20 ++- libvips/resample/reducel3.c | 176 +++++++++++++++++++++++ libvips/resample/reducevl3.cpp | 20 ++- libvips/resample/resample.c | 2 + libvips/resample/resize.c | 246 ++++++++++---------------------- tools/vipsthumbnail.c | 10 +- 10 files changed, 314 insertions(+), 191 deletions(-) create mode 100644 libvips/resample/reducel3.c diff --git a/TODO b/TODO index b7a0392d..5f5b7803 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,15 @@ -- see +- strange - https://en.wikipedia.org/wiki/Lanczos_resampling + john@kiwi:~/pics$ vips gaussblur babe.jpg x.v 0.2 + gaussmat: mask too large + +- removed the cache from resize since we no longer sharpen, can we get + out-of-order reads? + +- vips_resize() needs to take a param for kernel rather than interpolate? + maybe just don't expose this + +- what demand hint are we setting for the reduce / shrink funcs? - try SEQ_UNBUFFERED on jpg source, get out of order error? diff --git a/libvips/create/gaussmat.c b/libvips/create/gaussmat.c index 89f27688..32529d36 100644 --- a/libvips/create/gaussmat.c +++ b/libvips/create/gaussmat.c @@ -18,6 +18,8 @@ * 16/12/14 * - default to int output to match vips_conv() * - use @precision, not @integer + * 10/3/16 + * - allow 1x1 masks */ /* @@ -84,6 +86,10 @@ typedef struct _VipsGaussmatClass { G_DEFINE_TYPE( VipsGaussmat, vips_gaussmat, VIPS_TYPE_CREATE ); +/* Don't allow mask radius to go over this. + */ +#define MASK_SANITY (5000) + static int vips_gaussmat_build( VipsObject *object ) { @@ -91,7 +97,7 @@ vips_gaussmat_build( VipsObject *object ) VipsCreate *create = VIPS_CREATE( object ); VipsGaussmat *gaussmat = (VipsGaussmat *) object; double sig2 = 2. * gaussmat->sigma * gaussmat->sigma; - int max_x = 8 * gaussmat->sigma > 5000 ? 5000 : 8 * gaussmat->sigma ; + int max_x = VIPS_CLIP( 0, 8 * gaussmat->sigma, MASK_SANITY ); int x, y; int width, height; @@ -114,7 +120,7 @@ vips_gaussmat_build( VipsObject *object ) return( -1 ); /* Find the size of the mask. Limit the mask size to 10k x 10k for - * sanity. + * sanity. We allow x == 0, meaning a 1x1 mask. */ for( x = 0; x < max_x; x++ ) { double v = exp( - ((double)(x * x)) / sig2 ); @@ -122,7 +128,7 @@ vips_gaussmat_build( VipsObject *object ) if( v < gaussmat->min_ampl ) break; } - if( x == max_x ) { + if( x >= MASK_SANITY ) { vips_error( class->nickname, "%s", _( "mask too large" ) ); return( -1 ); } diff --git a/libvips/include/vips/resample.h b/libvips/include/vips/resample.h index 821a9995..a29657e2 100644 --- a/libvips/include/vips/resample.h +++ b/libvips/include/vips/resample.h @@ -58,6 +58,11 @@ int vips_reduce( VipsImage *in, VipsImage **out, int vips_reduceh( VipsImage *in, VipsImage **out, double xshrink, ... ); int vips_reducev( VipsImage *in, VipsImage **out, double yshrink, ... ); +int vips_reducel3( VipsImage *in, VipsImage **out, + double xshrink, double yshrink, ... ); +int vips_reducehl3( VipsImage *in, VipsImage **out, double xshrink, ... ); +int vips_reducevl3( VipsImage *in, VipsImage **out, double yshrink, ... ); + int vips_similarity( VipsImage *in, VipsImage **out, ... ) __attribute__((sentinel)); int vips_affine( VipsImage *in, VipsImage **out, diff --git a/libvips/resample/Makefile.am b/libvips/resample/Makefile.am index a2d5b5fd..dd670d4f 100644 --- a/libvips/resample/Makefile.am +++ b/libvips/resample/Makefile.am @@ -10,6 +10,7 @@ libresample_la_SOURCES = \ shrinkh.c \ shrinkv.c \ reduce.c \ + reducel3.c \ reduceh.cpp \ reducehl3.cpp \ reducev.cpp \ diff --git a/libvips/resample/reducehl3.cpp b/libvips/resample/reducehl3.cpp index 24179fff..77ecbd6c 100644 --- a/libvips/resample/reducehl3.cpp +++ b/libvips/resample/reducehl3.cpp @@ -184,24 +184,30 @@ reducehl3_unsigned_int_tab( VipsReducehl3 *reducehl3, * 11-point kernel for the vector version to be worthwhile. */ static void inline -reducehl3_unsigned_uint8_4tab( VipsPel *out, const VipsPel *in, +reducehl3_unsigned_uint8_6tab( VipsPel *out, const VipsPel *in, const int bands, const int *cx ) { const int b1 = bands; const int b2 = b1 + b1; const int b3 = b1 + b2; + const int b4 = b2 + b2; + const int b5 = b1 + b4; const int c0 = cx[0]; const int c1 = cx[1]; const int c2 = cx[2]; const int c3 = cx[3]; + const int c4 = cx[4]; + const int c5 = cx[5]; for( int z = 0; z < bands; z++ ) { int cubich = unsigned_fixed_round( c0 * in[0] + c1 * in[b1] + c2 * in[b2] + - c3 * in[b3] ); + c3 * in[b3] + + c4 * in[b4] + + c5 * in[b5] ); cubich = VIPS_CLIP( 0, cubich, 255 ); @@ -368,8 +374,8 @@ vips_reducehl3_gen( VipsRegion *out_region, void *seq, switch( in->BandFmt ) { case VIPS_FORMAT_UCHAR: - if( reducehl3->n_points == 4 ) - reducehl3_unsigned_uint8_4tab( + if( reducehl3->n_points == 6 ) + reducehl3_unsigned_uint8_6tab( q, p, bands, cxi ); else reducehl3_unsigned_int_tab @@ -560,14 +566,14 @@ vips_reducehl3_class_init( VipsReducehl3Class *reducehl3_class ) _( "Resampling kernel" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsReducehl3, kernel ), - VIPS_TYPE_KERNEL, VIPS_KERNEL_CUBIC ); + VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 ); } static void vips_reducehl3_init( VipsReducehl3 *reducehl3 ) { - reducehl3->kernel = VIPS_KERNEL_CUBIC; + reducehl3->kernel = VIPS_KERNEL_LANCZOS3; } /** @@ -579,7 +585,7 @@ vips_reducehl3_init( VipsReducehl3 *reducehl3 ) * * Optional arguments: * - * @kernel: #VipsKernel to use to interpolate (default: cubic) + * @kernel: #VipsKernel to use to interpolate (default: lanczos3) * * Reduce @in horizontally by a float factor. The pixels in @out are * interpolated with a 1D mask. This operation will not work well for diff --git a/libvips/resample/reducel3.c b/libvips/resample/reducel3.c new file mode 100644 index 00000000..b59105f1 --- /dev/null +++ b/libvips/resample/reducel3.c @@ -0,0 +1,176 @@ +/* 2D reducel3 ... call reducel3h and reducel3v + * + * 27/1/16 + * - from shrink.c + */ + +/* + + This file is part of VIPS. + + VIPS is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA + + */ + +/* + + These files are distributed with VIPS - http://www.vips.ecs.soton.ac.uk + + */ + +/* +#define DEBUG + */ + +#ifdef HAVE_CONFIG_H +#include +#endif /*HAVE_CONFIG_H*/ +#include + +#include +#include +#include + +#include +#include +#include + +#include "presample.h" + +typedef struct _VipsReducel3 { + VipsResample parent_instance; + + double xshrink; /* Shrink factors */ + double yshrink; + + /* The thing we use to make the kernel. + */ + VipsKernel kernel; + +} VipsReducel3; + +typedef VipsResampleClass VipsReducel3Class; + +G_DEFINE_TYPE( VipsReducel3, vips_reducel3, VIPS_TYPE_RESAMPLE ); + +static int +vips_reducel3_build( VipsObject *object ) +{ + VipsResample *resample = VIPS_RESAMPLE( object ); + VipsReducel3 *reducel3 = (VipsReducel3 *) object; + VipsImage **t = (VipsImage **) + vips_object_local_array( object, 3 ); + + if( VIPS_OBJECT_CLASS( vips_reducel3_parent_class )->build( object ) ) + return( -1 ); + + if( vips_reducevl3( resample->in, &t[0], reducel3->yshrink, + "kernel", reducel3->kernel, + NULL ) || + vips_reducehl3( t[0], &t[1], reducel3->xshrink, + "kernel", reducel3->kernel, + NULL ) || + vips_image_write( t[1], resample->out ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_reducel3_class_init( VipsReducel3Class *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *vobject_class = VIPS_OBJECT_CLASS( class ); + VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class ); + + VIPS_DEBUG_MSG( "vips_reducel3_class_init\n" ); + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + vobject_class->nickname = "reducel3"; + vobject_class->description = _( "reducel3 an image" ); + vobject_class->build = vips_reducel3_build; + + operation_class->flags = VIPS_OPERATION_SEQUENTIAL; + + VIPS_ARG_DOUBLE( class, "xshrink", 8, + _( "Xshrink" ), + _( "Horizontal shrink factor" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsReducel3, xshrink ), + 1.0, 1000000.0, 1.0 ); + + VIPS_ARG_DOUBLE( class, "yshrink", 9, + _( "Yshrink" ), + _( "Vertical shrink factor" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsReducel3, yshrink ), + 1.0, 1000000.0, 1.0 ); + + VIPS_ARG_ENUM( class, "kernel", 3, + _( "Kernel" ), + _( "Resampling kernel" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsReducel3, kernel ), + VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 ); + +} + +static void +vips_reducel3_init( VipsReducel3 *reducel3 ) +{ + reducel3->kernel = VIPS_KERNEL_LANCZOS3; +} + +/** + * vips_reducel3: + * @in: input image + * @out: output image + * @xshrink: horizontal shrink + * @shrinke: vertical shrink + * @...: %NULL-terminated list of optional named arguments + * + * Optional arguments: + * + * @kernel: #VipsKernel to use to interpolate (default: lanczos3) + * + * Reducel3 @in by a pair of factors with a pair of 1D interpolators. This + * will not work well for shrink factors greater than two. + * + * This is a very low-level operation: see vips_resize() for a more + * convenient way to resize images. + * + * This operation does not change xres or yres. The image resolution needs to + * be updated by the application. + * + * See also: vips_resize(), vips_affine(). + * + * Returns: 0 on success, -1 on error + */ +int +vips_reducel3( VipsImage *in, VipsImage **out, + double xshrink, double yshrink, ... ) +{ + va_list ap; + int result; + + va_start( ap, yshrink ); + result = vips_call_split( "reducel3", ap, in, out, xshrink, yshrink ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/resample/reducevl3.cpp b/libvips/resample/reducevl3.cpp index 8fb7cba4..2f0ce6ed 100644 --- a/libvips/resample/reducevl3.cpp +++ b/libvips/resample/reducevl3.cpp @@ -115,24 +115,30 @@ reducevl3_unsigned_int_tab( VipsReducevl3 *reducevl3, /* An unrolled version of ^^ for the most common case. */ static void inline -reducevl3_unsigned_uint8_4tab( VipsPel *out, const VipsPel *in, +reducevl3_unsigned_uint8_6tab( VipsPel *out, const VipsPel *in, const int ne, const int lskip, const int *cy ) { const int l1 = lskip; const int l2 = l1 + l1; const int l3 = l1 + l2; + const int l4 = l2 + l2; + const int l5 = l4 + l1; const int c0 = cy[0]; const int c1 = cy[1]; const int c2 = cy[2]; const int c3 = cy[3]; + const int c4 = cy[4]; + const int c5 = cy[5]; for( int z = 0; z < ne; z++ ) { int sum = unsigned_fixed_round( c0 * in[0] + c1 * in[l1] + c2 * in[l2] + - c3 * in[l3] ); + c3 * in[l3] + + c4 * in[l4] + + c5 * in[l5] ); sum = VIPS_CLIP( 0, sum, 255 ); @@ -288,8 +294,8 @@ vips_reducevl3_gen( VipsRegion *out_region, void *seq, switch( in->BandFmt ) { case VIPS_FORMAT_UCHAR: - if( reducevl3->n_points == 4 ) - reducevl3_unsigned_uint8_4tab( + if( reducevl3->n_points == 6 ) + reducevl3_unsigned_uint8_6tab( q, p, ne, lskip, cyi ); else reducevl3_unsigned_int_tab @@ -474,14 +480,14 @@ vips_reducevl3_class_init( VipsReducevl3Class *reducevl3_class ) _( "Resampling kernel" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsReducevl3, kernel ), - VIPS_TYPE_KERNEL, VIPS_KERNEL_CUBIC ); + VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 ); } static void vips_reducevl3_init( VipsReducevl3 *reducevl3 ) { - reducevl3->kernel = VIPS_KERNEL_CUBIC; + reducevl3->kernel = VIPS_KERNEL_LANCZOS3; } /** @@ -493,7 +499,7 @@ vips_reducevl3_init( VipsReducevl3 *reducevl3 ) * * Optional arguments: * - * @kernel: #VipsKernel to use to interpolate (default: cubic) + * @kernel: #VipsKernel to use to interpolate (default: lanczos3) * * Reduce @in vertically by a float factor. The pixels in @out are * interpolated with a 1D mask. This operation will not work well for diff --git a/libvips/resample/resample.c b/libvips/resample/resample.c index 1b45f328..d73c7b51 100644 --- a/libvips/resample/resample.c +++ b/libvips/resample/resample.c @@ -138,6 +138,7 @@ vips_resample_operation_init( void ) extern GType vips_shrinkh_get_type( void ); extern GType vips_shrinkv_get_type( void ); extern GType vips_reduce_get_type( void ); + extern GType vips_reducel3_get_type( void ); extern GType vips_reduceh_get_type( void ); extern GType vips_reducehl3_get_type( void ); extern GType vips_reducev_get_type( void ); @@ -156,6 +157,7 @@ vips_resample_operation_init( void ) vips_reducev_get_type(); vips_reducevl3_get_type(); vips_reduce_get_type(); + vips_reducel3_get_type(); vips_quadratic_get_type(); vips_affine_get_type(); vips_similarity_get_type(); diff --git a/libvips/resample/resize.c b/libvips/resample/resize.c index 55262404..7e81200f 100644 --- a/libvips/resample/resize.c +++ b/libvips/resample/resize.c @@ -8,6 +8,8 @@ * - smarter cache sizing * 29/2/16 * - shrink more affine less, now we have better anti-alias settings + * 10/3/16 + * - revise again, using new vips_reduce() code */ /* @@ -65,6 +67,9 @@ typedef struct _VipsResize { double scale; double vscale; + + /* Deprecated. + */ VipsInterpolate *interpolate; double idx; double idy; @@ -82,11 +87,11 @@ vips_resize_build( VipsObject *object ) VipsResample *resample = VIPS_RESAMPLE( object ); VipsResize *resize = (VipsResize *) object; - VipsImage **t = (VipsImage **) - vips_object_local_array( object, 7 ); + VipsImage **t = (VipsImage **) vips_object_local_array( object, 7 ); VipsImage *in; - int window_size; + int target_width; + int target_height; int int_hshrink; int int_vshrink; double hresidual; @@ -97,115 +102,45 @@ vips_resize_build( VipsObject *object ) if( VIPS_OBJECT_CLASS( vips_resize_parent_class )->build( object ) ) return( -1 ); - if( !vips_object_argument_isset( object, "interpolate" ) ) { - VipsInterpolate *interpolate; - char *nick; - - if( vips_type_find( "VipsInterpolate", "bicubic" ) ) - nick = "bicubic"; - else - nick = "bilinear"; - interpolate = vips_interpolate_new( nick ); - g_object_set( object, "interpolate", interpolate, NULL ); - VIPS_UNREF( interpolate ); - } - - /* Unset vscale means it's equal to hscale. - */ - if( !vips_object_argument_isset( object, "vscale" ) ) - resize->vscale = resize->scale; - in = resample->in; - window_size = resize->interpolate ? - vips_interpolate_get_window_size( resize->interpolate ) : 2; + /* The image size we are aiming for. + */ + target_width = in->Xsize * resize->scale; + if( vips_object_argument_isset( object, "vscale" ) ) + target_height = in->Ysize * resize->vscale; + else + target_height = in->Ysize * resize->scale; /* If the factor is > 1.0, we need to zoom rather than shrink. * Just set the int part to 1 in this case. */ int_hshrink = resize->scale > 1.0 ? 1 : VIPS_FLOOR( 1.0 / resize->scale ); - int_vshrink = resize->vscale > 1.0 ? - 1 : VIPS_FLOOR( 1.0 / resize->vscale ); + if( vips_object_argument_isset( object, "vscale" ) ) + int_vshrink = resize->vscale > 1.0 ? + 1 : VIPS_FLOOR( 1.0 / resize->vscale ); + else + int_vshrink = int_hshrink; - /* We want to shrink by less for interpolators with large windows. - */ - int_hshrink = VIPS_MAX( 1, - int_hshrink / VIPS_MAX( 1, window_size / 3 ) ); - int_vshrink = VIPS_MAX( 1, - int_vshrink / VIPS_MAX( 1, window_size / 3 ) ); - - /* Will this produce a residual scale of almost 1? shrink a bit less - * if it will. - */ - if( (in->Xsize * resize->scale) / (in->Xsize / int_hshrink) > 0.9 ) - int_hshrink = VIPS_MAX( 1, int_hshrink - 1 ); - if( (in->Ysize * resize->vscale) / (in->Ysize / int_vshrink) > 0.9 ) - int_vshrink = VIPS_MAX( 1, int_vshrink - 1 ); - - /* Residual scale factor is. - */ - hresidual = (in->Xsize * resize->scale) / (in->Xsize / int_hshrink); - vresidual = (in->Ysize * resize->vscale) / (in->Ysize / int_vshrink); - - /* A copy for enlarge resize. - */ - vips_info( class->nickname, "box shrink by %d x %d", - int_hshrink, int_vshrink ); - if( vips_shrink( in, &t[0], int_hshrink, int_vshrink, NULL ) ) - return( -1 ); - in = t[0]; - - /* We want to make sure we read the image sequentially. - * However, the convolution we may be doing later will force us - * into SMALLTILE or maybe FATSTRIP mode and that will break - * sequentiality. - * - * So ... read into a cache where tiles are scanlines, and make sure - * we keep enough scanlines. - * - * We use a threaded tilecache to avoid a deadlock: suppose thread1, - * evaluating the top block of the output, is delayed, and thread2, - * evaluating the second block, gets here first (this can happen on - * a heavily-loaded system). - * - * With an unthreaded tilecache, thread2 will get - * the cache lock and start evaling the second block of the shrink. - * When it reaches the png reader it will stall until the first block - * has been used ... but it never will, since thread1 will block on - * this cache lock. - * - * Cache sizing: we double-buffer writes, so threads can be up to one - * line of tiles behind. For example, one thread could be allocated - * tile (0,0) and then stall, the whole write system won't stall until - * it tries to allocate tile (0, 2). - * - * We affine down after this, which can be a scale of up to @residual, - * perhaps 0.5 or down as low as 0.3 depending on the interpolator. So - * the number of scanlines we need to keep for the worst case is - * 2 * @tile_height / @residual, plus a little extra. - */ - if( int_vshrink > 1 ) { - int tile_width; - int tile_height; - int n_lines; - - int need_lines; - - vips_get_tile_size( in, - &tile_width, &tile_height, &n_lines ); - need_lines = 1.2 * n_lines / vresidual; - if( vips_tilecache( in, &t[6], - "tile_width", in->Xsize, - "tile_height", 10, - "max_tiles", 1 + need_lines / 10, - "access", VIPS_ACCESS_SEQUENTIAL, - "threaded", TRUE, - NULL ) ) + if( int_hshrink > 1 || + int_vshrink > 1 ) { + vips_info( class->nickname, "box shrink by %d x %d", + int_hshrink, int_vshrink ); + if( vips_shrink( in, &t[0], int_hshrink, int_vshrink, NULL ) ) return( -1 ); - in = t[6]; + in = t[0]; } + /* Do we need a further size adjustment? It's the difference + * between our target size and the size we have after vips_shrink(). + */ + hresidual = (double) target_width / in->Xsize; + if( vips_object_argument_isset( object, "vscale" ) ) + vresidual = (double) target_height / in->Ysize; + else + vresidual = hresidual; + /* 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. * @@ -215,61 +150,34 @@ vips_resize_build( VipsObject *object ) * Don't try to be clever for non-rectangular shrinks. We just * consider the horizontal factor. */ - sigma = ((1.0 / hresidual) - 0.5) / 2.0; - anti_alias = hresidual < 1.0 && sigma > 0.1; + sigma = ((1.0 / hresidual) - 0.5) / 2.5; + anti_alias = hresidual < 0.9 && sigma > 0.1; if( anti_alias ) { vips_info( class->nickname, "anti-alias sigma %g", sigma ); - if( vips_gaussblur( in, &t[2], sigma, NULL ) ) + if( vips_gaussblur( in, &t[1], sigma, NULL ) ) + return( -1 ); + in = t[1]; + } + + if( hresidual < 1.0 || + vresidual < 1.0 ) { + vips_info( class->nickname, "residual reduce by %g x %g", + hresidual, vresidual ); + + if( vips_reducel3( in, &t[2], + 1.0 / hresidual, 1.0 / vresidual, NULL ) ) return( -1 ); in = t[2]; } - - vips_info( class->nickname, "residual affine %g x %g", - hresidual, vresidual ); - if( resize->interpolate ) { - const char *nickname = - VIPS_OBJECT_GET_CLASS( resize->interpolate )->nickname; - - vips_info( class->nickname, "%s interpolation", nickname ); - } - - /* We have a special path for bicubic, idx/idy == 0. - */ - if( resize->interpolate && - strcmp( VIPS_OBJECT_GET_CLASS( resize->interpolate )->nickname, - "bicubic" ) == 0 && - resize->idx == 0.0 && - resize->idy == 0.0 ) { - vips_info( class->nickname, "using fast path for residual" ); - if( vips_reduce( in, &t[3], - 1.0 / hresidual, 1.0 / vresidual, NULL ) ) - return( -1 ); - } - else { + if( hresidual > 1.0 || + vresidual > 1.0 ) { + vips_info( class->nickname, "residual scale %g x %g", + hresidual, vresidual ); if( vips_affine( in, &t[3], hresidual, 0, 0, vresidual, - "interpolate", resize->interpolate, - "idx", resize->idx, - "idy", resize->idy, + "interpolate", vips_interpolate_nearest_static(), NULL ) ) return( -1 ); - } - in = t[3]; - - /* If we are upsampling, don't sharpen. Also don't sharpen if we - * skipped the anti-alias filter. - */ - if( int_hshrink >= 1 && - anti_alias ) { - vips_info( class->nickname, "final sharpen" ); - t[5] = vips_image_new_matrixv( 3, 3, - -1.0, -1.0, -1.0, - -1.0, 32.0, -1.0, - -1.0, -1.0, -1.0 ); - vips_image_set_double( t[5], "scale", 24 ); - - if( vips_conv( in, &t[4], t[5], NULL ) ) - return( -1 ); - in = t[4]; + in = t[3]; } if( vips_image_write( in, resample->out ) ) @@ -310,25 +218,33 @@ vips_resize_class_init( VipsResizeClass *class ) G_STRUCT_OFFSET( VipsResize, vscale ), 0, 10000000, 0 ); - VIPS_ARG_INTERPOLATE( class, "interpolate", 2, - _( "Interpolate" ), - _( "Interpolate pixels with this" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, - G_STRUCT_OFFSET( VipsResize, interpolate ) ); - + /* We used to let people set the input offset so you could pick centre + * or corner interpolation, but it's not clear this was useful. + */ VIPS_ARG_DOUBLE( class, "idx", 115, _( "Input offset" ), _( "Horizontal input displacement" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, + VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, G_STRUCT_OFFSET( VipsResize, idx ), -10000000, 10000000, 0 ); VIPS_ARG_DOUBLE( class, "idy", 116, _( "Input offset" ), _( "Vertical input displacement" ), - VIPS_ARGUMENT_OPTIONAL_INPUT, + VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, G_STRUCT_OFFSET( VipsResize, idy ), -10000000, 10000000, 0 ); + + /* We used to let people set the interpolator, but it's not clear this + * was useful. Anyway, vips_reduce() no longer has an interpolator + * param. + */ + VIPS_ARG_INTERPOLATE( class, "interpolate", 2, + _( "Interpolate" ), + _( "Interpolate pixels with this" ), + VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED, + G_STRUCT_OFFSET( VipsResize, interpolate ) ); + } static void @@ -346,29 +262,21 @@ vips_resize_init( VipsResize *resize ) * Optional arguments: * * @vscale: vertical scale factor - * @interpolate: interpolate pixels with this - * @idx: input horizontal offset - * @idy: input vertical offset * - * Resize an image. When upsizing (@scale > 1), the image is simply resized - * with vips_affine() and the supplied @interpolate. When downsizing, the - * image is block-shrunk with vips_shrink() to roughly half the interpolator - * window size above the target size, then blurred with an anti-alias filter, - * then resampled with vips_affine() and the supplied interpolator, then - * sharpened. + * Resize an image. When upsizing (@scale > 1), the image is simply block + * upsized. When downsizing, the + * image is block-shrunk with vips_shrink(), then an anti-alias blur is + * applied with vips_gaussblur(), then the image is shrunk again to the + * target size with vips_reduce(). * * vips_resize() normally maintains the image apect ratio. If you set * @vscale, that factor is used for the vertical scale and @scale for the * horizontal. * - * @interpolate defaults to bicubic. - * - * @idx, @idy default to zero. Set them to 0.5 to get pixel-centre sampling. - * * This operation does not change xres or yres. The image resolution needs to * be updated by the application. * - * See also: vips_shrink(), vips_affine(), #VipsInterpolate. + * See also: vips_shrink(), vips_reduce(), vips_gaussblur(). * * Returns: 0 on success, -1 on error */ diff --git a/tools/vipsthumbnail.c b/tools/vipsthumbnail.c index 4d36484a..b7ad7511 100644 --- a/tools/vipsthumbnail.c +++ b/tools/vipsthumbnail.c @@ -189,9 +189,11 @@ calculate_shrink( VipsImage *im ) * * In crop mode we aim to fill the bounding box, so we must use the * smaller axis. + * + * Take off a tiny amount to stop us rounding below the target. */ - double horizontal = (double) width / thumbnail_width; - double vertical = (double) height / thumbnail_height; + double horizontal = (double) width / thumbnail_width - 0.0000001; + double vertical = (double) height / thumbnail_height - 0.0000001; if( crop_image ) { if( horizontal < vertical ) @@ -518,7 +520,9 @@ thumbnail_shrink( VipsObject *process, VipsImage *in, shrink = calculate_shrink( in ); - if( vips_resize( in, &t[4], 1.0 / shrink, + /* Add a tiny amount to stop rounding below the target. + */ + if( vips_resize( in, &t[4], 1.0 / shrink + 0.0000000001, "interpolate", interp, NULL ) ) return( NULL ); From 9209fb25c559c90bc257df226de602de5fe19083 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 10 Mar 2016 17:40:19 +0000 Subject: [PATCH 08/20] update notes --- ChangeLog | 5 ++--- TODO | 7 +------ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/ChangeLog b/ChangeLog index 8a9726f7..eb21cfe0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,6 @@ 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 +- add vips_reduce*() ... a fast path for affine downsize +- vips_resize() uses vips_reduce() with lanczos3 and better anti-alias - bicubic is better on 32-bit int images - add pdfload, svgload, gifload for PDF, SVG and GIF rendering - vipsthumbnail knows about pdfload and svgload @@ -15,7 +15,6 @@ - faster hist_find (Lovell Fuller) - webpload has a @shrink parameter for shrink-on-load - vipsthumbnail knows about webp shrink-on-load -- more vips_resize() tuning, a bit quicker now - better behaviour for vips_cast() shift of non-int types (thanks apacheark) - python .bandrank() now works like .bandjoin() diff --git a/TODO b/TODO index 5f5b7803..5801f0f8 100644 --- a/TODO +++ b/TODO @@ -1,13 +1,8 @@ -- strange - - john@kiwi:~/pics$ vips gaussblur babe.jpg x.v 0.2 - gaussmat: mask too large - removed the cache from resize since we no longer sharpen, can we get out-of-order reads? -- vips_resize() needs to take a param for kernel rather than interpolate? - maybe just don't expose this + seeeeeeems ok? - what demand hint are we setting for the reduce / shrink funcs? From 4974a1ed9c26efb865fc2d3dd570e7a80252bb56 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 10 Mar 2016 19:53:05 +0000 Subject: [PATCH 09/20] better rounding for vips_resize() we were getting off by one size errors --- ChangeLog | 1 + TODO | 4 ++ libvips/resample/resize.c | 6 ++- test/test_thumbnail.sh | 1 + tools/vipsthumbnail.c | 105 ++++++-------------------------------- 5 files changed, 26 insertions(+), 91 deletions(-) diff --git a/ChangeLog b/ChangeLog index eb21cfe0..ece0a383 100644 --- a/ChangeLog +++ b/ChangeLog @@ -17,6 +17,7 @@ - vipsthumbnail knows about webp shrink-on-load - better behaviour for vips_cast() shift of non-int types (thanks apacheark) - python .bandrank() now works like .bandjoin() +- vipsthumbnail --interpolator and --sharpen are deprecated 27/1/16 started 8.2.3 - fix a crash with SPARC byte-order labq vips images diff --git a/TODO b/TODO index 5801f0f8..d562f805 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,4 @@ +- need tests for reducel3, test every kernel plues every numeric type - removed the cache from resize since we no longer sharpen, can we get out-of-order reads? @@ -6,6 +7,9 @@ - what demand hint are we setting for the reduce / shrink funcs? + + + - try SEQ_UNBUFFERED on jpg source, get out of order error? - could load pdf thumbnails? diff --git a/libvips/resample/resize.c b/libvips/resample/resize.c index 7e81200f..28ba4ba7 100644 --- a/libvips/resample/resize.c +++ b/libvips/resample/resize.c @@ -134,10 +134,12 @@ vips_resize_build( VipsObject *object ) /* Do we need a further size adjustment? It's the difference * between our target size and the size we have after vips_shrink(). + * + * Aim for a little above target so we can't round down below it. */ - hresidual = (double) target_width / in->Xsize; + hresidual = ((double) target_width + 0.1) / in->Xsize; if( vips_object_argument_isset( object, "vscale" ) ) - vresidual = (double) target_height / in->Ysize; + vresidual = ((double) target_height + 0.1) / in->Ysize; else vresidual = hresidual; diff --git a/test/test_thumbnail.sh b/test/test_thumbnail.sh index cccdcffc..2231ae15 100755 --- a/test/test_thumbnail.sh +++ b/test/test_thumbnail.sh @@ -30,6 +30,7 @@ for interp in nearest bilinear bicubic lbb nohalo vsqbs; do vipsthumbnail $tmp/t1.v -o $tmp/t2.v --size $size --interpolator $interp if [ $(vipsheader -f width $tmp/t2.v) -ne $size ]; then echo failed -- bad size + echo output width is $(vipsheader -f width $tmp/t2.v) exit fi if [ $(vipsheader -f height $tmp/t2.v) -ne $size ]; then diff --git a/tools/vipsthumbnail.c b/tools/vipsthumbnail.c index b7ad7511..f44dd206 100644 --- a/tools/vipsthumbnail.c +++ b/tools/vipsthumbnail.c @@ -77,6 +77,7 @@ * - add webp --shrink support * 29/2/16 * - make more use of jpeg shrink-on-load now we've tuned vips_resize() + * - deprecate sharpen and interpolate */ #ifdef HAVE_CONFIG_H @@ -103,10 +104,8 @@ static char *thumbnail_size = "128"; static int thumbnail_width = 128; static int thumbnail_height = 128; static char *output_format = "tn_%s.jpg"; -static char *interpolator = "bilinear"; static char *export_profile = NULL; static char *import_profile = NULL; -static char *convolution_mask = "none"; static gboolean delete_profile = FALSE; static gboolean linear_processing = FALSE; static gboolean crop_image = FALSE; @@ -117,6 +116,8 @@ static gboolean rotate_image = FALSE; static gboolean nosharpen = FALSE; static gboolean nodelete_profile = FALSE; static gboolean verbose = FALSE; +static char *convolution_mask = NULL; +static char *interpolator = NULL; static GOptionEntry options[] = { { "size", 's', 0, @@ -131,14 +132,6 @@ static GOptionEntry options[] = { G_OPTION_ARG_STRING, &output_format, N_( "set output format string to FORMAT" ), N_( "FORMAT" ) }, - { "interpolator", 'p', 0, - G_OPTION_ARG_STRING, &interpolator, - N_( "resample with INTERPOLATOR" ), - N_( "INTERPOLATOR" ) }, - { "sharpen", 'r', 0, - G_OPTION_ARG_STRING, &convolution_mask, - N_( "sharpen with none|mild|MASKFILE" ), - N_( "none|mild|MASKFILE" ) }, { "eprofile", 'e', 0, G_OPTION_ARG_STRING, &export_profile, N_( "export with PROFILE" ), @@ -159,6 +152,7 @@ static GOptionEntry options[] = { { "delete", 'd', 0, G_OPTION_ARG_NONE, &delete_profile, N_( "delete profile from exported image" ), NULL }, + { "verbose", 'v', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &verbose, N_( "(deprecated, does nothing)" ), NULL }, @@ -168,6 +162,12 @@ static GOptionEntry options[] = { { "nosharpen", 'n', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &nosharpen, N_( "(deprecated, does nothing)" ), NULL }, + { "interpolator", 'p', G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_STRING, &interpolator, + N_( "(deprecated, does nothing)" ), NULL }, + { "sharpen", 'r', G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_STRING, &convolution_mask, + N_( "(deprecated, does nothing)" ), NULL }, { NULL } }; @@ -185,15 +185,14 @@ calculate_shrink( VipsImage *im ) VipsDirection direction; /* Calculate the horizontal and vertical shrink we'd need to fit the - * image to the bounding box, and pick the biggest. + * image to the bounding box, and pick the biggest. Aim for a little + * above the target so we can't round down below it. * * In crop mode we aim to fill the bounding box, so we must use the * smaller axis. - * - * Take off a tiny amount to stop us rounding below the target. */ - double horizontal = (double) width / thumbnail_width - 0.0000001; - double vertical = (double) height / thumbnail_height - 0.0000001; + double horizontal = (double) width / (thumbnail_width + 0.1); + double vertical = (double) height / (thumbnail_height + 0.1); if( crop_image ) { if( horizontal < vertical ) @@ -369,57 +368,8 @@ thumbnail_open( VipsObject *process, const char *filename ) return( im ); } -static VipsInterpolate * -thumbnail_interpolator( VipsObject *process, VipsImage *in ) -{ - double shrink = calculate_shrink( in ); - - VipsInterpolate *interp; - - /* For images smaller than the thumbnail, we upscale with nearest - * neighbor. Otherwise we make thumbnails that look fuzzy and awful. - */ - if( !(interp = VIPS_INTERPOLATE( vips_object_new_from_string( - g_type_class_ref( VIPS_TYPE_INTERPOLATE ), - shrink <= 1.0 ? "nearest" : interpolator ) )) ) - return( NULL ); - - vips_object_local( process, interp ); - - return( interp ); -} - -/* Some interpolators look a little soft, so we have an optional sharpening - * stage. - */ static VipsImage * -thumbnail_sharpen( VipsObject *process ) -{ - VipsImage *mask; - - if( strcmp( convolution_mask, "none" ) == 0 ) - mask = NULL; - else if( strcmp( convolution_mask, "mild" ) == 0 ) { - mask = vips_image_new_matrixv( 3, 3, - -1.0, -1.0, -1.0, - -1.0, 32.0, -1.0, - -1.0, -1.0, -1.0 ); - vips_image_set_double( mask, "scale", 24 ); - } - else - if( !(mask = - vips_image_new_from_file( convolution_mask, NULL )) ) - vips_error_exit( "unable to load sharpen mask" ); - - if( mask ) - vips_object_local( process, mask ); - - return( mask ); -} - -static VipsImage * -thumbnail_shrink( VipsObject *process, VipsImage *in, - VipsInterpolate *interp, VipsImage *sharpen ) +thumbnail_shrink( VipsObject *process, VipsImage *in ) { VipsImage **t = (VipsImage **) vips_object_local_array( process, 10 ); VipsInterpretation interpretation = linear_processing ? @@ -523,7 +473,6 @@ thumbnail_shrink( VipsObject *process, VipsImage *in, /* Add a tiny amount to stop rounding below the target. */ if( vips_resize( in, &t[4], 1.0 / shrink + 0.0000000001, - "interpolate", interp, NULL ) ) return( NULL ); in = t[4]; @@ -613,17 +562,6 @@ thumbnail_shrink( VipsObject *process, VipsImage *in, in = out; } - /* If we are upsampling, don't sharpen, since nearest looks dumb - * sharpened. - */ - if( shrink > 1.0 && - sharpen ) { - vips_info( "vipsthumbnail", "sharpening thumbnail" ); - if( vips_conv( in, &t[8], sharpen, NULL ) ) - return( NULL ); - in = t[8]; - } - if( delete_profile && vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) { vips_info( "vipsthumbnail", @@ -733,18 +671,13 @@ thumbnail_write( VipsObject *process, VipsImage *im, const char *filename ) static int thumbnail_process( VipsObject *process, const char *filename ) { - VipsImage *sharpen = thumbnail_sharpen( process ); - VipsImage *in; - VipsInterpolate *interp; VipsImage *thumbnail; VipsImage *crop; VipsImage *rotate; if( !(in = thumbnail_open( process, filename )) || - !(interp = thumbnail_interpolator( process, in )) || - !(thumbnail = - thumbnail_shrink( process, in, interp, sharpen )) || + !(thumbnail = thumbnail_shrink( process, in )) || !(crop = thumbnail_crop( process, thumbnail )) || !(rotate = thumbnail_rotate( process, crop )) || thumbnail_write( process, rotate, filename ) ) @@ -767,12 +700,6 @@ main( int argc, char **argv ) textdomain( GETTEXT_PACKAGE ); setlocale( LC_ALL, "" ); - /* Does this vips have bicubic? Default to that if it - * does. - */ - if( vips_type_find( "VipsInterpolate", "bicubic" ) ) - interpolator = "bicubic"; - context = g_option_context_new( _( "- thumbnail generator" ) ); main_group = g_option_group_new( NULL, NULL, NULL, NULL, NULL ); From 2ca7b29214a4049eb4dee308181393d6603a777b Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 10 Mar 2016 22:01:55 +0000 Subject: [PATCH 10/20] better gauss mat generation --- TODO | 5 ++--- libvips/create/gaussmat.c | 3 ++- libvips/resample/resize.c | 5 ++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/TODO b/TODO index d562f805..7c9a2589 100644 --- a/TODO +++ b/TODO @@ -1,3 +1,5 @@ +- rename l3 on top of reduce + - need tests for reducel3, test every kernel plues every numeric type - removed the cache from resize since we no longer sharpen, can we get @@ -5,9 +7,6 @@ seeeeeeems ok? -- what demand hint are we setting for the reduce / shrink funcs? - - - try SEQ_UNBUFFERED on jpg source, get out of order error? diff --git a/libvips/create/gaussmat.c b/libvips/create/gaussmat.c index 32529d36..93623d12 100644 --- a/libvips/create/gaussmat.c +++ b/libvips/create/gaussmat.c @@ -20,6 +20,7 @@ * - use @precision, not @integer * 10/3/16 * - allow 1x1 masks + * - better size calc */ /* @@ -132,7 +133,7 @@ vips_gaussmat_build( VipsObject *object ) vips_error( class->nickname, "%s", _( "mask too large" ) ); return( -1 ); } - width = x * 2 + 1; + width = 2 * x - 1; height = gaussmat->separable ? 1 : width; vips_image_init_fields( create->out, diff --git a/libvips/resample/resize.c b/libvips/resample/resize.c index 28ba4ba7..2c1d0a5c 100644 --- a/libvips/resample/resize.c +++ b/libvips/resample/resize.c @@ -146,14 +146,13 @@ vips_resize_build( VipsObject *object ) /* 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, blur with radius 1 for x1.5 - * shrinks, blur radius 2 for x2.5 shrinks and above, etc. + * Don't blur for very small shrinks, start to blur above about .5. * * Don't try to be clever for non-rectangular shrinks. We just * consider the horizontal factor. */ sigma = ((1.0 / hresidual) - 0.5) / 2.5; - anti_alias = hresidual < 0.9 && sigma > 0.1; + anti_alias = hresidual < 0.9 && sigma > 0.5; if( anti_alias ) { vips_info( class->nickname, "anti-alias sigma %g", sigma ); if( vips_gaussblur( in, &t[1], sigma, NULL ) ) From 5d31d8e824c50253b0704a8ac9bb575546df26b2 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 11 Mar 2016 09:25:33 +0000 Subject: [PATCH 11/20] turn up anti-alias for small shrinks a little more anti-alias for small reductions see https://github.com/jcupitt/libvips/issues/404#issuecomment-195276154 --- libvips/resample/resize.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/libvips/resample/resize.c b/libvips/resample/resize.c index 2c1d0a5c..deda177e 100644 --- a/libvips/resample/resize.c +++ b/libvips/resample/resize.c @@ -146,16 +146,18 @@ vips_resize_build( VipsObject *object ) /* 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, start to blur above about .5. + * 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. */ - sigma = ((1.0 / hresidual) - 0.5) / 2.5; - anti_alias = hresidual < 0.9 && sigma > 0.5; + sigma = (1.0 / hresidual) / 2.4; + 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, NULL ) ) + if( vips_gaussblur( in, &t[1], sigma, + "min_ampl", 0.1, + NULL ) ) return( -1 ); in = t[1]; } From 08e8c27e382afebf3f7ec0af86025d6b157a5641 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 11 Mar 2016 10:17:41 +0000 Subject: [PATCH 12/20] moore vips_resize() tuning put back the mild post-shrink sharpen, a little more anti-alias see https://github.com/jcupitt/libvips/issues/404#issuecomment-195297607 --- libvips/resample/resize.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/libvips/resample/resize.c b/libvips/resample/resize.c index deda177e..620b52fb 100644 --- a/libvips/resample/resize.c +++ b/libvips/resample/resize.c @@ -151,7 +151,7 @@ vips_resize_build( VipsObject *object ) * Don't try to be clever for non-rectangular shrinks. We just * consider the horizontal factor. */ - sigma = (1.0 / hresidual) / 2.4; + sigma = (1.0 / hresidual) / 2.3; anti_alias = hresidual < 0.9 && sigma > 0.45; if( anti_alias ) { vips_info( class->nickname, "anti-alias sigma %g", sigma ); @@ -183,6 +183,23 @@ 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( int_hshrink >= 1 && + anti_alias ) { + vips_info( class->nickname, "final sharpen" ); + t[4] = vips_image_new_matrixv( 3, 3, + -1.0, -1.0, -1.0, + -1.0, 32.0, -1.0, + -1.0, -1.0, -1.0 ); + vips_image_set_double( t[4], "scale", 24 ); + + if( vips_conv( in, &t[5], t[4], NULL ) ) + return( -1 ); + in = t[5]; + } + if( vips_image_write( in, resample->out ) ) return( -1 ); From a832a0c25422b60302a8eab87f455992d0d3c350 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 11 Mar 2016 14:42:12 +0000 Subject: [PATCH 13/20] notes --- TODO | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/TODO b/TODO index 7c9a2589..f8813b1e 100644 --- a/TODO +++ b/TODO @@ -2,10 +2,19 @@ - need tests for reducel3, test every kernel plues every numeric type -- removed the cache from resize since we no longer sharpen, can we get - out-of-order reads? +- removed the cache from resize, can we get out-of-order reads? + + yup, try + + for i in 200 300 400 500 600; do time vipsthumbnail Chicago.png -s $i; done + + bench on ws as well + +- try orc version of reducev? + +- still see rounding problems on resize, try ghibli3 + - seeeeeeems ok? From 493482c8a7051487082f6e68d8624c7b0ab3b2b4 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 11 Mar 2016 14:56:06 +0000 Subject: [PATCH 14/20] hey ho, line cache goes back too --- TODO | 8 ------- libvips/resample/resize.c | 50 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/TODO b/TODO index f8813b1e..8f92ee04 100644 --- a/TODO +++ b/TODO @@ -2,14 +2,6 @@ - need tests for reducel3, test every kernel plues every numeric type -- removed the cache from resize, can we get out-of-order reads? - - yup, try - - for i in 200 300 400 500 600; do time vipsthumbnail Chicago.png -s $i; done - - bench on ws as well - - try orc version of reducev? - still see rounding problems on resize, try ghibli3 diff --git a/libvips/resample/resize.c b/libvips/resample/resize.c index 620b52fb..f7ae4fca 100644 --- a/libvips/resample/resize.c +++ b/libvips/resample/resize.c @@ -143,6 +143,56 @@ vips_resize_build( VipsObject *object ) else vresidual = hresidual; + /* We want to make sure we read the image sequentially. + * However, the convolution we may be doing later will force us + * into SMALLTILE or maybe FATSTRIP mode and that will break + * sequentiality. + * + * So ... read into a cache where tiles are scanlines, and make sure + * we keep enough scanlines. + * + * We use a threaded tilecache to avoid a deadlock: suppose thread1, + * evaluating the top block of the output, is delayed, and thread2, + * evaluating the second block, gets here first (this can happen on + * a heavily-loaded system). + * + * With an unthreaded tilecache, thread2 will get + * the cache lock and start evaling the second block of the shrink. + * When it reaches the png reader it will stall until the first block + * has been used ... but it never will, since thread1 will block on + * this cache lock. + * + * Cache sizing: we double-buffer writes, so threads can be up to one + * line of tiles behind. For example, one thread could be allocated + * tile (0,0) and then stall, the whole write system won't stall until + * it tries to allocate tile (0, 2). + * + * We reduce down after this, which can be a scale of up to @residual, + * perhaps 0.5 or down as low as 0.3 depending on the interpolator. So + * the number of scanlines we need to keep for the worst case is + * 2 * @tile_height / @residual, plus a little extra. + */ + if( int_vshrink > 1 ) { + int tile_width; + int tile_height; + int n_lines; + + int need_lines; + + vips_get_tile_size( in, + &tile_width, &tile_height, &n_lines ); + need_lines = 1.2 * n_lines / vresidual; + if( vips_tilecache( in, &t[6], + "tile_width", in->Xsize, + "tile_height", 10, + "max_tiles", 1 + need_lines / 10, + "access", VIPS_ACCESS_SEQUENTIAL, + "threaded", TRUE, + NULL ) ) + return( -1 ); + 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. * From 70db9aa77b11e07f22e4624652f9f725d33de500 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 12 Mar 2016 13:14:34 +0000 Subject: [PATCH 15/20] better rounding in resize --- TODO | 7 ++++++- libvips/resample/resize.c | 8 ++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/TODO b/TODO index 8f92ee04..225290be 100644 --- a/TODO +++ b/TODO @@ -4,7 +4,12 @@ - try orc version of reducev? -- still see rounding problems on resize, try ghibli3 +- try + + $ vipsthumbnail ghibli_orig.jpg -s 600x1085 + $ vipsheader tn_ghibli_orig.jpg + tn_ghibli_orig.jpg: 599x1083 uchar, 3 bands, srgb, jpegload + diff --git a/libvips/resample/resize.c b/libvips/resample/resize.c index f7ae4fca..16002720 100644 --- a/libvips/resample/resize.c +++ b/libvips/resample/resize.c @@ -136,12 +136,12 @@ vips_resize_build( VipsObject *object ) * between our target size and the size we have after vips_shrink(). * * Aim for a little above target so we can't round down below it. + * + * This can break the aspect ratio slightly :/ but hopefully no one + * will notice. */ hresidual = ((double) target_width + 0.1) / in->Xsize; - if( vips_object_argument_isset( object, "vscale" ) ) - vresidual = ((double) target_height + 0.1) / in->Ysize; - else - vresidual = hresidual; + vresidual = ((double) target_height + 0.1) / in->Ysize; /* We want to make sure we read the image sequentially. * However, the convolution we may be doing later will force us From abe4e70d02bae34d090db119ff387029129aeea2 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 12 Mar 2016 15:10:52 +0000 Subject: [PATCH 16/20] make radload slightly more robust stops some valgrind warnings --- ChangeLog | 1 + doc/gtk-doc.make | 20 ++++++++++++-------- doc/libvips-docs.xml | 2 +- libvips/foreign/radiance.c | 36 +++++++++++++++++++++++++++++++----- 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/ChangeLog b/ChangeLog index b78391b1..68680286 100644 --- a/ChangeLog +++ b/ChangeLog @@ -2,6 +2,7 @@ - fix a crash with SPARC byte-order labq vips images - fix parsing of filenames containing brackets, thanks shilpi230 - fix hist_entropy (lovell) +- small fixes to radiance load 12/1/16 started 8.2.2 - changes to ease compiling C++ binding with MSVC [Lovell Fuller] diff --git a/doc/gtk-doc.make b/doc/gtk-doc.make index e7916563..9ccd0b04 100644 --- a/doc/gtk-doc.make +++ b/doc/gtk-doc.make @@ -25,6 +25,7 @@ TARGET_DIR=$(HTML_DIR)/$(DOC_MODULE) SETUP_FILES = \ $(content_files) \ + $(expand_content_files) \ $(DOC_MAIN_SGML_FILE) \ $(DOC_MODULE)-sections.txt \ $(DOC_MODULE)-overrides.txt @@ -86,7 +87,7 @@ GTK_DOC_V_SETUP_0=@echo " DOC Preparing build"; setup-build.stamp: -$(GTK_DOC_V_SETUP)if test "$(abs_srcdir)" != "$(abs_builddir)" ; then \ - files=`echo $(SETUP_FILES) $(expand_content_files) $(DOC_MODULE).types`; \ + files=`echo $(SETUP_FILES) $(DOC_MODULE).types`; \ if test "x$$files" != "x" ; then \ for file in $$files ; do \ destdir=`dirname $(abs_builddir)/$$file`; \ @@ -118,7 +119,7 @@ scan-build.stamp: setup-build.stamp $(HFILE_GLOB) $(CFILE_GLOB) $(GTK_DOC_V_INTROSPECT)if grep -l '^..*$$' $(DOC_MODULE).types > /dev/null 2>&1 ; then \ scanobj_options=""; \ gtkdoc-scangobj 2>&1 --help | grep >/dev/null "\-\-verbose"; \ - if test "$(?)" = "0"; then \ + if test "$$?" = "0"; then \ if test "x$(V)" = "x1"; then \ scanobj_options="--verbose"; \ fi; \ @@ -162,17 +163,17 @@ GTK_DOC_V_XREF=$(GTK_DOC_V_XREF_$(V)) GTK_DOC_V_XREF_=$(GTK_DOC_V_XREF_$(AM_DEFAULT_VERBOSITY)) GTK_DOC_V_XREF_0=@echo " DOC Fixing cross-references"; -html-build.stamp: sgml.stamp $(DOC_MAIN_SGML_FILE) $(content_files) +html-build.stamp: sgml.stamp $(DOC_MAIN_SGML_FILE) $(content_files) $(expand_content_files) $(GTK_DOC_V_HTML)rm -rf html && mkdir html && \ mkhtml_options=""; \ gtkdoc-mkhtml 2>&1 --help | grep >/dev/null "\-\-verbose"; \ - if test "$(?)" = "0"; then \ + if test "$$?" = "0"; then \ if test "x$(V)" = "x1"; then \ mkhtml_options="$$mkhtml_options --verbose"; \ fi; \ fi; \ gtkdoc-mkhtml 2>&1 --help | grep >/dev/null "\-\-path"; \ - if test "$(?)" = "0"; then \ + if test "$$?" = "0"; then \ mkhtml_options="$$mkhtml_options --path=\"$(abs_srcdir)\""; \ fi; \ cd html && gtkdoc-mkhtml $$mkhtml_options $(MKHTML_OPTIONS) $(DOC_MODULE) ../$(DOC_MAIN_SGML_FILE) @@ -194,11 +195,11 @@ GTK_DOC_V_PDF=$(GTK_DOC_V_PDF_$(V)) GTK_DOC_V_PDF_=$(GTK_DOC_V_PDF_$(AM_DEFAULT_VERBOSITY)) GTK_DOC_V_PDF_0=@echo " DOC Building PDF"; -pdf-build.stamp: sgml.stamp $(DOC_MAIN_SGML_FILE) $(content_files) +pdf-build.stamp: sgml.stamp $(DOC_MAIN_SGML_FILE) $(content_files) $(expand_content_files) $(GTK_DOC_V_PDF)rm -f $(DOC_MODULE).pdf && \ mkpdf_options=""; \ gtkdoc-mkpdf 2>&1 --help | grep >/dev/null "\-\-verbose"; \ - if test "$(?)" = "0"; then \ + if test "$$?" = "0"; then \ if test "x$(V)" = "x1"; then \ mkpdf_options="$$mkpdf_options --verbose"; \ fi; \ @@ -223,12 +224,15 @@ clean-local: @if echo $(SCAN_OPTIONS) | grep -q "\-\-rebuild-types" ; then \ rm -f $(DOC_MODULE).types; \ fi + @if echo $(SCAN_OPTIONS) | grep -q "\-\-rebuild-sections" ; then \ + rm -f $(DOC_MODULE)-sections.txt; \ + fi distclean-local: @rm -rf xml html $(REPORT_FILES) $(DOC_MODULE).pdf \ $(DOC_MODULE)-decl-list.txt $(DOC_MODULE)-decl.txt @if test "$(abs_srcdir)" != "$(abs_builddir)" ; then \ - rm -f $(SETUP_FILES) $(expand_content_files) $(DOC_MODULE).types; \ + rm -f $(SETUP_FILES) $(DOC_MODULE).types; \ fi maintainer-clean-local: diff --git a/doc/libvips-docs.xml b/doc/libvips-docs.xml index fe88a909..2f631278 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.2.3. The latest version of this documentation can be found on the VIPS website. diff --git a/libvips/foreign/radiance.c b/libvips/foreign/radiance.c index 74be61bb..c50aae6e 100644 --- a/libvips/foreign/radiance.c +++ b/libvips/foreign/radiance.c @@ -653,16 +653,23 @@ buffer_need( Buffer *buffer, int require ) int remaining; g_assert( require < BUFFER_MARGIN ); + g_assert( buffer->length >= 0 ); + g_assert( buffer->position >= 0 ); + g_assert( buffer->position <= buffer->length ); remaining = buffer->length - buffer->position; if( remaining < require ) { size_t len; - memcpy( buffer->text, + /* Areas can overlap. + */ + memmove( buffer->text, buffer->text + buffer->position, remaining ); buffer->position = 0; buffer->length = remaining; + g_assert( buffer->length < BUFFER_MARGIN ); + len = fread( buffer->text + buffer->length, 1, BUFFER_SIZE, buffer->fp ); buffer->length += len; @@ -687,6 +694,10 @@ scanline_read_old( Buffer *buffer, COLR *scanline, int width ) { int rshift; + g_assert( buffer->length >= 0 ); + g_assert( buffer->position >= 0 ); + g_assert( buffer->position <= buffer->length ); + rshift = 0; while( width > 0 ) { @@ -728,6 +739,10 @@ scanline_read( Buffer *buffer, COLR *scanline, int width ) { int i, j; + g_assert( buffer->length >= 0 ); + g_assert( buffer->position >= 0 ); + g_assert( buffer->position <= buffer->length ); + /* Detect old-style scanlines. */ if( width < MINELEN || @@ -996,8 +1011,10 @@ static const char *colcor_name[3] = { static int rad2vips_get_header( Read *read, VipsImage *out ) { - int i, j; VipsInterpretation interpretation; + int width; + int height; + int i, j; if( getheader( read->fin, (gethfunc *) rad2vips_process_line, read ) || !fgetsresolu( &read->rs, read->fin ) ) { @@ -1013,9 +1030,17 @@ rad2vips_get_header( Read *read, VipsImage *out ) else interpretation = VIPS_INTERPRETATION_MULTIBAND; - vips_image_init_fields( out, - scanlen( &read->rs ), numscans( &read->rs ), - 4, + width = scanlen( &read->rs ); + height = numscans( &read->rs ); + if( width <= 0 || + width > VIPS_MAX_COORD || + height <= 0 || + height > VIPS_MAX_COORD ) { + vips_error( "rad2vips", "%s", _( "image size out of bounds" ) ); + return( -1 ); + } + + vips_image_init_fields( out, width, height, 4, VIPS_FORMAT_UCHAR, VIPS_CODING_RAD, interpretation, 1, read->aspect ); @@ -1080,6 +1105,7 @@ rad2vips_generate( VipsRegion *or, if( scanline_read( read->buffer, buf, or->im->Xsize ) ) { vips_error( "rad2vips", _( "read error line %d" ), r->top + y ); + VIPS_GATE_STOP( "rad2vips_generate: work" ); return( -1 ); } } From 6368ab06496b366d48cf7dcc1c7f767a94a70193 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sat, 12 Mar 2016 16:48:27 +0000 Subject: [PATCH 17/20] add switches to disable rad, analyze and ppm vips has built-in support for rad, analyze and ppm ... add configure switches to disable these readers useful to reduce the attack surface in some applications --- ChangeLog | 1 + configure.ac | 30 ++++++++++++++++++++++++++++ libvips/deprecated/im_analyze2vips.c | 13 ++++++++---- libvips/foreign/analyze2vips.c | 3 +++ libvips/foreign/analyzeload.c | 4 ++++ libvips/foreign/foreign.c | 19 +++++++++++++----- libvips/foreign/ppm.c | 4 ++++ libvips/foreign/ppmload.c | 4 ++++ libvips/foreign/ppmsave.c | 4 ++++ libvips/foreign/radiance.c | 4 ++++ libvips/foreign/radload.c | 4 ++++ libvips/foreign/radsave.c | 4 ++++ test/test_foreign.py | 15 ++++++++++++++ 13 files changed, 100 insertions(+), 9 deletions(-) diff --git a/ChangeLog b/ChangeLog index ded22a71..019a53e7 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18,6 +18,7 @@ - more vips_resize() tuning, a bit quicker now - better behaviour for vips_cast() shift of non-int types (thanks apacheark) - python .bandrank() now works like .bandjoin() +- switches to disable PPM, Rad and Analyze support 27/1/16 started 8.2.3 - fix a crash with SPARC byte-order labq vips images diff --git a/configure.ac b/configure.ac index 99199bda..949423fa 100644 --- a/configure.ac +++ b/configure.ac @@ -618,6 +618,33 @@ if test x"$with_matio" != "xno"; then ]) fi +# not external libraries, but have options to disable them, helps to +# reduce attack surface + +AC_ARG_WITH([ppm], + AS_HELP_STRING([--without-ppm], [build without ppm (default: with)])) + +if test x"$with_ppm" != "xno"; then + AC_DEFINE(HAVE_PPM,1,[define to build ppm support.]) + with_ppm=yes +fi + +AC_ARG_WITH([analyze], + AS_HELP_STRING([--without-analyze], [build without analyze (default: with)])) + +if test x"$with_analyze" != "xno"; then + AC_DEFINE(HAVE_ANALYZE,1,[define to build analyze support.]) + with_analyze=yes +fi + +AC_ARG_WITH([radiance], + AS_HELP_STRING([--without-radiance], [build without radiance (default: with)])) + +if test x"$with_radiance" != "xno"; then + AC_DEFINE(HAVE_RADIANCE,1,[define to build radiance support.]) + with_radiance=yes +fi + # cfitsio AC_ARG_WITH([cfitsio], AS_HELP_STRING([--without-cfitsio], [build without cfitsio (default: test)])) @@ -911,6 +938,9 @@ gobject introspection: $found_introspection build vips7 Python binding: $with_python install vips8 Python overrides: $enable_pyvips8 (requires pygobject-3.12.0 or later) +build radiance support: $with_radiance +build analyze support: $with_analyze +build PPM support: $with_ppm * optional dependencies use fftw3 for FFT: $with_fftw diff --git a/libvips/deprecated/im_analyze2vips.c b/libvips/deprecated/im_analyze2vips.c index f4d892c9..c7b63056 100644 --- a/libvips/deprecated/im_analyze2vips.c +++ b/libvips/deprecated/im_analyze2vips.c @@ -42,9 +42,6 @@ #include -#include "../foreign/dbh.h" -#include "../foreign/analyze2vips.h" - static VipsFormatFlags analyze_flags( const char *filename ) { @@ -61,7 +58,15 @@ isanalyze( const char *filename ) int im_analyze2vips( const char *filename, IMAGE *out ) { - return( vips__analyze_read( filename, out ) ); + VipsImage *t; + + if( vips_analyzeload( filename, &t, NULL ) ) + return( -1 ); + if( vips_image_write( t, out ) ) { + g_object_unref( t ); + return( -1 ); + } + g_object_unref( t ); return( 0 ); } diff --git a/libvips/foreign/analyze2vips.c b/libvips/foreign/analyze2vips.c index e7ec87b3..77771753 100644 --- a/libvips/foreign/analyze2vips.c +++ b/libvips/foreign/analyze2vips.c @@ -50,6 +50,8 @@ #endif /*HAVE_CONFIG_H*/ #include +#ifdef HAVE_ANALYZE + #include #include #include @@ -594,3 +596,4 @@ vips__analyze_read( const char *filename, VipsImage *out ) return( 0 ); } +#endif /*HAVE_ANALYZE*/ diff --git a/libvips/foreign/analyzeload.c b/libvips/foreign/analyzeload.c index 0bb1c6cd..691d1edc 100644 --- a/libvips/foreign/analyzeload.c +++ b/libvips/foreign/analyzeload.c @@ -48,6 +48,8 @@ #include #include +#ifdef HAVE_ANALYZE + #include "analyze2vips.h" typedef struct _VipsForeignLoadAnalyze { @@ -142,6 +144,8 @@ vips_foreign_load_analyze_init( VipsForeignLoadAnalyze *analyze ) { } +#endif /*HAVE_ANALYZE*/ + /** * vips_analyzeload: * @filename: file to load diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index a701c9e4..5ac52a63 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -1665,22 +1665,31 @@ vips_foreign_operation_init( void ) extern GType vips_foreign_load_gif_file_get_type( void ); extern GType vips_foreign_load_gif_buffer_get_type( void ); - vips_foreign_load_rad_get_type(); - vips_foreign_save_rad_get_type(); - vips_foreign_load_ppm_get_type(); - vips_foreign_save_ppm_get_type(); vips_foreign_load_csv_get_type(); vips_foreign_save_csv_get_type(); vips_foreign_load_matrix_get_type(); vips_foreign_save_matrix_get_type(); vips_foreign_print_matrix_get_type(); - vips_foreign_load_analyze_get_type(); vips_foreign_load_raw_get_type(); vips_foreign_save_raw_get_type(); vips_foreign_save_raw_fd_get_type(); vips_foreign_load_vips_get_type(); vips_foreign_save_vips_get_type(); +#ifdef HAVE_ANALYZE + vips_foreign_load_analyze_get_type(); +#endif /*HAVE_ANALYZE*/ + +#ifdef HAVE_PPM + vips_foreign_load_ppm_get_type(); + vips_foreign_save_ppm_get_type(); +#endif /*HAVE_PPM*/ + +#ifdef HAVE_RADIANCE + vips_foreign_load_rad_get_type(); + vips_foreign_save_rad_get_type(); +#endif /*HAVE_RADIANCE*/ + #ifdef HAVE_POPPLER vips_foreign_load_pdf_get_type(); vips_foreign_load_pdf_file_get_type(); diff --git a/libvips/foreign/ppm.c b/libvips/foreign/ppm.c index bc5334ca..d6e4ecae 100644 --- a/libvips/foreign/ppm.c +++ b/libvips/foreign/ppm.c @@ -68,6 +68,8 @@ #endif /*HAVE_CONFIG_H*/ #include +#ifdef HAVE_PPM + #include #include #include @@ -825,3 +827,5 @@ vips__ppm_save( VipsImage *in, const char *filename, return( 0 ); } + +#endif /*HAVE_PPM*/ diff --git a/libvips/foreign/ppmload.c b/libvips/foreign/ppmload.c index 55a5abaf..df458c9f 100644 --- a/libvips/foreign/ppmload.c +++ b/libvips/foreign/ppmload.c @@ -48,6 +48,8 @@ #include #include +#ifdef HAVE_PPM + #include "ppm.h" typedef struct _VipsForeignLoadPpm { @@ -142,6 +144,8 @@ vips_foreign_load_ppm_init( VipsForeignLoadPpm *ppm ) { } +#endif /*HAVE_PPM*/ + /** * vips_ppmload: * @filename: file to load diff --git a/libvips/foreign/ppmsave.c b/libvips/foreign/ppmsave.c index 6893caee..178011af 100644 --- a/libvips/foreign/ppmsave.c +++ b/libvips/foreign/ppmsave.c @@ -47,6 +47,8 @@ #include +#ifdef HAVE_PPM + #include "ppm.h" typedef struct _VipsForeignSavePpm { @@ -144,6 +146,8 @@ vips_foreign_save_ppm_init( VipsForeignSavePpm *ppm ) { } +#endif /*HAVE_PPM*/ + /** * vips_ppmsave: * @in: image to save diff --git a/libvips/foreign/radiance.c b/libvips/foreign/radiance.c index c50aae6e..fc688b60 100644 --- a/libvips/foreign/radiance.c +++ b/libvips/foreign/radiance.c @@ -127,6 +127,8 @@ #endif /*HAVE_CONFIG_H*/ #include +#ifdef HAVE_RADIANCE + #include #include #include @@ -1303,3 +1305,5 @@ vips__rad_save( VipsImage *in, const char *filename ) } const char *vips__rad_suffs[] = { ".hdr", NULL }; + +#endif /*HAVE_RADIANCE*/ diff --git a/libvips/foreign/radload.c b/libvips/foreign/radload.c index 66ab5d55..8d46d3a7 100644 --- a/libvips/foreign/radload.c +++ b/libvips/foreign/radload.c @@ -48,6 +48,8 @@ #include #include +#ifdef HAVE_RADIANCE + #include "radiance.h" typedef struct _VipsForeignLoadRad { @@ -145,6 +147,8 @@ vips_foreign_load_rad_init( VipsForeignLoadRad *rad ) { } +#endif /*HAVE_RADIANCE*/ + /** * vips_radload: * @filename: file to load diff --git a/libvips/foreign/radsave.c b/libvips/foreign/radsave.c index cd97a67d..bfffe6c1 100644 --- a/libvips/foreign/radsave.c +++ b/libvips/foreign/radsave.c @@ -47,6 +47,8 @@ #include +#ifdef HAVE_RADIANCE + #include "radiance.h" typedef struct _VipsForeignSaveRad { @@ -129,6 +131,8 @@ vips_foreign_save_rad_init( VipsForeignSaveRad *rad ) { } +#endif /*HAVE_RADIANCE*/ + /** * vips_radsave: * @in: image to save diff --git a/test/test_foreign.py b/test/test_foreign.py index 9722e184..0eb44ff0 100755 --- a/test/test_foreign.py +++ b/test/test_foreign.py @@ -267,6 +267,11 @@ class TestForeign(unittest.TestCase): self.save_load("%s.webp", self.colour) def test_analyzeload(self): + x = Vips.type_find("VipsForeign", "analyzeload") + if not x.is_instantiatable(): + print("no analyze support in this vips, skipping test") + return + def analyze_valid(self, im): a = im(10, 10) self.assertAlmostEqual(a[0], 3335) @@ -416,10 +421,20 @@ class TestForeign(unittest.TestCase): self.save_load("%s.mat", self.mono) def test_ppm(self): + x = Vips.type_find("VipsForeign", "ppmload") + if not x.is_instantiatable(): + print("no PPM support in this vips, skipping test") + return + self.save_load("%s.ppm", self.mono) self.save_load("%s.ppm", self.colour) def test_rad(self): + x = Vips.type_find("VipsForeign", "radload") + if not x.is_instantiatable(): + print("no Radiance support in this vips, skipping test") + return + self.save_load("%s.hdr", self.colour) def test_dzsave(self): From f12fef4aa9836128e4eaf99bf38fd94a921103af Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 13 Mar 2016 11:35:35 +0000 Subject: [PATCH 18/20] fix compile --- TODO | 15 ++++++++++++--- libvips/resample/reducev.cpp | 4 ++-- libvips/resample/resize.c | 2 +- test/test_create.py | 6 +++--- test/test_resample.py | 2 +- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/TODO b/TODO index 26e3d060..63ebaf48 100644 --- a/TODO +++ b/TODO @@ -1,8 +1,17 @@ -- rename l3 on top of reduce +- try: -- need tests for reducel3, test every kernel plues every numeric type + $ ./test_resample.py TestResample.test_reduce + File "./test_resample.py", line 129, in test_reduce + self.assertLess(d, 5) + AssertionError: 147.0 not less than 5 -- try orc version of reducev? + affine bicubic != reduce cubic + + displacement error, I guess? + +- need more tests for reduce, test every kernel plus every numeric type + +- try orc version of reducev? and shrinkv? maybe shrinkh? diff --git a/libvips/resample/reducev.cpp b/libvips/resample/reducev.cpp index 765e6e02..6dd1537f 100644 --- a/libvips/resample/reducev.cpp +++ b/libvips/resample/reducev.cpp @@ -120,7 +120,7 @@ reducev_unsigned_uint8_6tab( VipsPel *out, const VipsPel *in, { const int l1 = lskip; const int l2 = l1 + l1; - const int = l1 + l2; + const int l3 = l1 + l2; const int l4 = l2 + l2; const int l5 = l4 + l1; @@ -136,7 +136,7 @@ reducev_unsigned_uint8_6tab( VipsPel *out, const VipsPel *in, c0 * in[0] + c1 * in[l1] + c2 * in[l2] + - c3 * in[] + + c3 * in[l3] + c4 * in[l4] + c5 * in[l5] ); diff --git a/libvips/resample/resize.c b/libvips/resample/resize.c index 16002720..590227b8 100644 --- a/libvips/resample/resize.c +++ b/libvips/resample/resize.c @@ -217,7 +217,7 @@ vips_resize_build( VipsObject *object ) vips_info( class->nickname, "residual reduce by %g x %g", hresidual, vresidual ); - if( vips_reducel3( in, &t[2], + if( vips_reduce( in, &t[2], 1.0 / hresidual, 1.0 / vresidual, NULL ) ) return( -1 ); in = t[2]; diff --git a/test/test_create.py b/test/test_create.py index 6fdb71d2..70454b42 100755 --- a/test/test_create.py +++ b/test/test_create.py @@ -110,8 +110,8 @@ class TestCreate(unittest.TestCase): def test_gaussmat(self): im = Vips.Image.gaussmat(1, 0.1) - self.assertEqual(im.width, 7) - self.assertEqual(im.height, 7) + self.assertEqual(im.width, 5) + self.assertEqual(im.height, 5) self.assertEqual(im.bands, 1) self.assertEqual(im.format, Vips.BandFormat.DOUBLE) self.assertEqual(im.max(), 20) @@ -123,7 +123,7 @@ class TestCreate(unittest.TestCase): im = Vips.Image.gaussmat(1, 0.1, separable = True, precision = "float") - self.assertEqual(im.width, 7) + self.assertEqual(im.width, 5) self.assertEqual(im.height, 1) self.assertEqual(im.bands, 1) self.assertEqual(im.format, Vips.BandFormat.DOUBLE) diff --git a/test/test_resample.py b/test/test_resample.py index 4c9c2c21..75161781 100755 --- a/test/test_resample.py +++ b/test/test_resample.py @@ -121,7 +121,7 @@ class TestResample(unittest.TestCase): for fac in [1, 1.1, 1.5, 1.999]: for fmt in all_formats: x = im.cast(fmt) - r = x.reduce(fac, fac) + r = x.reduce(fac, fac, kernel = "cubic") a = x.affine([1.0 / fac, 0, 0, 1.0 / fac], interpolate = bicubic, oarea = [0, 0, x.width / fac, x.height / fac]) From fba2ac2f8546f0285087fcd17020f7d1cefb303c Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Sun, 13 Mar 2016 17:44:24 +0000 Subject: [PATCH 19/20] fix sizing issues with new reduce code a couple of bugs and some changed conventions --- TODO | 2 ++ libvips/resample/reduceh.cpp | 8 ++++---- libvips/resample/reducev.cpp | 7 ++++--- libvips/resample/resize.c | 6 ++---- tools/vipsthumbnail.c | 12 ++++-------- 5 files changed, 16 insertions(+), 19 deletions(-) diff --git a/TODO b/TODO index 63ebaf48..8d5a3d74 100644 --- a/TODO +++ b/TODO @@ -9,6 +9,8 @@ displacement error, I guess? + yes, reduceh output should move one to the right + - need more tests for reduce, test every kernel plus every numeric type - try orc version of reducev? and shrinkv? maybe shrinkh? diff --git a/libvips/resample/reduceh.cpp b/libvips/resample/reduceh.cpp index 81b447b6..de556ae4 100644 --- a/libvips/resample/reduceh.cpp +++ b/libvips/resample/reduceh.cpp @@ -496,7 +496,7 @@ vips_reduceh_build( VipsObject *object ) /* Add new pixels around the input so we can interpolate at the edges. */ if( vips_embed( in, &t[1], - reduceh->n_points / 2, 0, + reduceh->n_points / 2 - 1, 0, in->Xsize + reduceh->n_points - 1, in->Ysize, "extend", VIPS_EXTEND_COPY, NULL ) ) @@ -507,14 +507,14 @@ vips_reduceh_build( VipsObject *object ) VIPS_DEMAND_STYLE_THINSTRIP, in, NULL ) ) return( -1 ); - /* Size output. Note: we round the output width down! + /* Size output. Note: we round to nearest to hide rounding errors. * * Don't change xres/yres, leave that to the application layer. For * example, vipsthumbnail knows the true reduce factor (including the * fractional part), we just see the integer part here. */ - resample->out->Xsize = (in->Xsize - reduceh->n_points + 1) / - reduceh->xshrink; + resample->out->Xsize = VIPS_RINT( + (in->Xsize - reduceh->n_points + 1) / reduceh->xshrink ); if( resample->out->Xsize <= 0 ) { vips_error( object_class->nickname, "%s", _( "image has shrunk to nothing" ) ); diff --git a/libvips/resample/reducev.cpp b/libvips/resample/reducev.cpp index 6dd1537f..b5bd652d 100644 --- a/libvips/resample/reducev.cpp +++ b/libvips/resample/reducev.cpp @@ -411,7 +411,7 @@ vips_reducev_build( VipsObject *object ) /* Add new pixels around the input so we can interpolate at the edges. */ if( vips_embed( in, &t[1], - 0, reducev->n_points / 2, + 0, reducev->n_points / 2 - 1, in->Xsize, in->Ysize + reducev->n_points - 1, "extend", VIPS_EXTEND_COPY, NULL ) ) @@ -422,13 +422,14 @@ vips_reducev_build( VipsObject *object ) VIPS_DEMAND_STYLE_SMALLTILE, in, NULL ) ) return( -1 ); - /* Size output. Note: we round the output width down! + /* Size output. Note: we round to nearest to hide rounding errors. * * Don't change xres/yres, leave that to the application layer. For * example, vipsthumbnail knows the true reduce factor (including the * fractional part), we just see the integer part here. */ - resample->out->Ysize = (in->Ysize - reducev->n_points + 1) / reducev->yshrink; + resample->out->Ysize = VIPS_RINT( + (in->Ysize - reducev->n_points + 1) / reducev->yshrink ); if( resample->out->Ysize <= 0 ) { vips_error( object_class->nickname, "%s", _( "image has shrunk to nothing" ) ); diff --git a/libvips/resample/resize.c b/libvips/resample/resize.c index 590227b8..fc9651d5 100644 --- a/libvips/resample/resize.c +++ b/libvips/resample/resize.c @@ -135,13 +135,11 @@ vips_resize_build( VipsObject *object ) /* Do we need a further size adjustment? It's the difference * between our target size and the size we have after vips_shrink(). * - * Aim for a little above target so we can't round down below it. - * * This can break the aspect ratio slightly :/ but hopefully no one * will notice. */ - hresidual = ((double) target_width + 0.1) / in->Xsize; - vresidual = ((double) target_height + 0.1) / in->Ysize; + hresidual = (double) target_width / in->Xsize; + vresidual = (double) target_height / in->Ysize; /* We want to make sure we read the image sequentially. * However, the convolution we may be doing later will force us diff --git a/tools/vipsthumbnail.c b/tools/vipsthumbnail.c index f44dd206..a507d568 100644 --- a/tools/vipsthumbnail.c +++ b/tools/vipsthumbnail.c @@ -185,14 +185,13 @@ calculate_shrink( VipsImage *im ) VipsDirection direction; /* Calculate the horizontal and vertical shrink we'd need to fit the - * image to the bounding box, and pick the biggest. Aim for a little - * above the target so we can't round down below it. + * image to the bounding box, and pick the biggest. * * In crop mode we aim to fill the bounding box, so we must use the * smaller axis. */ - double horizontal = (double) width / (thumbnail_width + 0.1); - double vertical = (double) height / (thumbnail_height + 0.1); + double horizontal = (double) width / thumbnail_width; + double vertical = (double) height / thumbnail_height; if( crop_image ) { if( horizontal < vertical ) @@ -470,10 +469,7 @@ thumbnail_shrink( VipsObject *process, VipsImage *in ) shrink = calculate_shrink( in ); - /* Add a tiny amount to stop rounding below the target. - */ - if( vips_resize( in, &t[4], 1.0 / shrink + 0.0000000001, - NULL ) ) + if( vips_resize( in, &t[4], 1.0 / shrink, NULL ) ) return( NULL ); in = t[4]; From 82cecf3d9f1c3f52eb33dfa6f5bb9f15becc3d46 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 14 Mar 2016 10:06:45 +0000 Subject: [PATCH 20/20] bilinear interp. supports complex types in line with cubic etc. --- TODO | 14 -------------- libvips/resample/interpolate.c | 5 ++++- test/test_resample.py | 28 ++++++++++++++++++++++++++-- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/TODO b/TODO index 8d5a3d74..89e19805 100644 --- a/TODO +++ b/TODO @@ -1,17 +1,3 @@ -- try: - - $ ./test_resample.py TestResample.test_reduce - File "./test_resample.py", line 129, in test_reduce - self.assertLess(d, 5) - AssertionError: 147.0 not less than 5 - - affine bicubic != reduce cubic - - displacement error, I guess? - - yes, reduceh output should move one to the right - -- need more tests for reduce, test every kernel plus every numeric type - try orc version of reducev? and shrinkv? maybe shrinkh? diff --git a/libvips/resample/interpolate.c b/libvips/resample/interpolate.c index 354c8a94..66a8b24e 100644 --- a/libvips/resample/interpolate.c +++ b/libvips/resample/interpolate.c @@ -508,6 +508,8 @@ G_DEFINE_TYPE( VipsInterpolateBilinear, vips_interpolate_bilinear, case VIPS_FORMAT_INT: FLOAT( int ); break; \ case VIPS_FORMAT_FLOAT: FLOAT( float ); break; \ case VIPS_FORMAT_DOUBLE:FLOAT( double ); break; \ + case VIPS_FORMAT_COMPLEX: FLOAT( float ); break; \ + case VIPS_FORMAT_DPCOMPLEX:FLOAT( double ); break; \ default: \ g_assert( FALSE ); \ } \ @@ -521,7 +523,8 @@ vips_interpolate_bilinear_interpolate( VipsInterpolate *interpolate, */ const int ps = VIPS_IMAGE_SIZEOF_PEL( in->im ); const int ls = VIPS_REGION_LSKIP( in ); - const int b = in->im->Bands; + const int b = in->im->Bands * + (vips_band_format_iscomplex( in->im->BandFmt ) ? 2 : 1); const int ix = (int) x; const int iy = (int) y; diff --git a/test/test_resample.py b/test/test_resample.py index 75161781..b77ca957 100755 --- a/test/test_resample.py +++ b/test/test_resample.py @@ -116,8 +116,13 @@ class TestResample(unittest.TestCase): def test_reduce(self): im = Vips.Image.new_from_file("images/IMG_4618.jpg") + # cast down to 0-127, the smallest range, so we aren't messed up by + # clipping + im = im.cast(Vips.BandFormat.CHAR) bicubic = Vips.Interpolate.new("bicubic") - + bilinear = Vips.Interpolate.new("bilinear") + nearest = Vips.Interpolate.new("nearest") + for fac in [1, 1.1, 1.5, 1.999]: for fmt in all_formats: x = im.cast(fmt) @@ -126,7 +131,26 @@ class TestResample(unittest.TestCase): interpolate = bicubic, oarea = [0, 0, x.width / fac, x.height / fac]) d = (r - a).abs().max() - self.assertLess(d, 5) + self.assertLess(d, 10) + + for fac in [1, 1.1, 1.5, 1.999]: + for fmt in all_formats: + x = im.cast(fmt) + r = x.reduce(fac, fac, kernel = "linear") + a = x.affine([1.0 / fac, 0, 0, 1.0 / fac], + interpolate = bilinear, + oarea = [0, 0, x.width / fac, x.height / fac]) + d = (r - a).abs().max() + self.assertLess(d, 10) + + # for other kernels, just see if avg looks about right + for fac in [1, 1.1, 1.5, 1.999]: + for fmt in all_formats: + for kernel in ["nearest", "lanczos2", "lanczos3"]: + x = im.cast(fmt) + r = x.reduce(fac, fac, kernel = kernel) + d = abs(r.avg() - im.avg()) + self.assertLess(d, 2) def test_resize(self): im = Vips.Image.new_from_file("images/IMG_4618.jpg")