From bbe42e13fa9989b5a1b2e5175d32be21a79a91cb Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 12 Feb 2018 12:39:53 +0000 Subject: [PATCH 1/5] version bump --- ChangeLog | 3 +++ configure.ac | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index 150771f7..70336ce4 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ +12/2/18 started 8.6.3 +- use pkg-config to find libjpeg, if we can + 5/1/18 started 8.6.2 - vips_sink_screen() keeps a ref to the input image ... stops a rare race - fix a minor accidental ABI break in 8.6.0 -> 8.6.1 [remicollet] diff --git a/configure.ac b/configure.ac index e04893ae..8023bbd1 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # also update the version number in the m4 macros below -AC_INIT([vips], [8.6.2], [vipsip@jiscmail.ac.uk]) +AC_INIT([vips], [8.6.3], [vipsip@jiscmail.ac.uk]) # required for gobject-introspection AC_PREREQ(2.62) @@ -18,7 +18,7 @@ AC_CONFIG_MACRO_DIR([m4]) # user-visible library versioning m4_define([vips_major_version], [8]) m4_define([vips_minor_version], [6]) -m4_define([vips_micro_version], [2]) +m4_define([vips_micro_version], [3]) m4_define([vips_version], [vips_major_version.vips_minor_version.vips_micro_version]) From a9b343f29a306895325fabc7e2bb69f929e8f619 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 12 Feb 2018 13:31:49 +0000 Subject: [PATCH 2/5] use pkg-config to find libjpeg fall back to FIND_JPEG if we have to, see https://github.com/jcupitt/libvips/issues/431 --- configure.ac | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/configure.ac b/configure.ac index 8023bbd1..4f45343b 100644 --- a/configure.ac +++ b/configure.ac @@ -1000,23 +1000,40 @@ if test x"$with_png" != "xno"; then ) fi -FIND_JPEG( - [with_jpeg=yes - EXTRA_LIBS_USED="$EXTRA_LIBS_USED -ljpeg" - ], - [AC_MSG_WARN([libjpeg not found; disabling JPEG support]) - with_jpeg=no - ] -) +# look for libjpeg with pkg-config ... fall back to our tester +AC_ARG_WITH([jpeg], + AS_HELP_STRING([--without-jpeg], [build without libjpeg (default: test)])) -# JPEG extension parameters available in libjpeg-turbo >=1.5.0, mozjpeg >=3.0 +if test x"$with_jpeg" != x"no"; then + PKG_CHECK_MODULES(JPEG, libjpeg, + [with_jpeg="yes (pkg-config)" + PACKAGES_USED="$PACKAGES_USED libjpeg" + ], + [FIND_JPEG( + [with_jpeg="yes (found by search)" + EXTRA_LIBS_USED="$EXTRA_LIBS_USED -ljpeg" + ], + [AC_MSG_WARN([libjpeg not found; disabling JPEG support]) + with_jpeg=no + ] + ) + ] + ) +fi + +# features like trellis quant are exposed as extension parameters ... +# mozjpeg 3.2 and later have #define JPEG_C_PARAM_SUPPORTED, but we must +# work with earlier versions if test x"$with_jpeg" != "xno"; then save_LIBS="$LIBS" - LIBS="$LIBS $JPEG_LIBS" + save_CFLAGS="$CFLAGS" + LIBS="$JPEG_LIBS $LIBS" + CFLAGS="$JPEG_INCLUDES $CFLAGS" AC_CHECK_FUNCS(jpeg_c_bool_param_supported, AC_DEFINE(HAVE_JPEG_EXT_PARAMS,1, [define if your libjpeg has extension parameters.])) LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" fi # libexif From 2349dcf110a9911e57d7229afb12e4dcf9ecf5f8 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 22 Feb 2018 16:34:01 +0000 Subject: [PATCH 3/5] oops, forgot to enable jpg --- configure.ac | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 36958cca..f5cae2e1 100644 --- a/configure.ac +++ b/configure.ac @@ -1040,7 +1040,8 @@ AC_ARG_WITH([jpeg], if test x"$with_jpeg" != x"no"; then PKG_CHECK_MODULES(JPEG, libjpeg, - [with_jpeg="yes (pkg-config)" + [AC_DEFINE(HAVE_JPEG,1,[define if you have libjpeg installed.]) + with_jpeg="yes (pkg-config)" PACKAGES_USED="$PACKAGES_USED libjpeg" ], [FIND_JPEG( From aa53f632e714238e4a6f8b3ccb51ef79af68ca8f Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Fri, 23 Feb 2018 09:32:14 +0000 Subject: [PATCH 4/5] doc polish --- doc/binding.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/doc/binding.md b/doc/binding.md index da81d782..fc0e9609 100644 --- a/doc/binding.md +++ b/doc/binding.md @@ -10,18 +10,18 @@ There are full libvips bindings for quite a few environments now: C, C++, -command-line, Ruby, PHP, Python and JavaScript (node). +command-line, Ruby, PHP, Lua, Python and JavaScript (node). This chapter runs through the four main styles that have been found to work well. If you want to write a new binding, one of these should be close to what you need. -# C API +# Don't bind the top-level C API -The libvips C API (vips_add() and so on) is very inconvenient to use from other -languages due to its heavy use of varargs. +The libvips C API (vips_add() and so on) is very inconvenient and dangerous +to use from other languages due to its heavy use of varargs. -It's much better to use the layer below. This lower layer is structured as: +It's much better to use the layer below. This lower layer is structured as create operator, set parameters, execute, extract results. For example, you can execute vips_invert() like this: @@ -113,10 +113,10 @@ main( int argc, char **argv ) } ``` -libvips has a couple of extra things to let you fetch the arguments and types -of an operator. Use vips_lib.vips_argument_map() to loop over all the arguments -of an operator, and vips_object_get_argument() to fetch the type and flags -of a specific argument. +libvips has a couple of extra things to let you examine the arguments and +types of an operator at runtime. Use vips_lib.vips_argument_map() to loop +over all the arguments of an operator, and vips_object_get_argument() +to fetch the type and flags of a specific argument. Use vips_operation_get_flags() to get general information about an operator. @@ -124,7 +124,7 @@ Use vips_operation_get_flags() to get general information about an operator. The C++ binding uses this lower layer to define a function called `VImage::call()` which can call any libvips operator with a not-varargs set of -variable arguments. +variable arguments. A small Python program walks the set of all libvips operators and generates a set of static bindings. For example: @@ -142,7 +142,7 @@ VImage VImage::invert( VOption *options ) } ``` -So from C++ you can call any libvips operator, though without type-safety, with +So from C++ you can call any libvips operator (though without type-safety) with `VImage::call()`, or use the member functions on `VImage` to get type-safe calls for at least the required operator arguments. @@ -158,7 +158,7 @@ but use FFI to call into libvips and run operations. Since these languages are dynamic, they can add another trick: they intercept the method-missing hook and attempt to run any method calls not implemented by the `Image` class as libvips operators. This makes these bindings self-writing: -they only contain a small amount of codeand just expose everything they find in +they only contain a small amount of code and just expose everything they find in the libvips class hierarchy. # Dynamic langauge without FFI @@ -167,7 +167,7 @@ PHP does not have FFI, unfortunately, so for this language a small native module implements the general `vips_call()` function for PHP language types, and a larger pure PHP layer makes it convenient to use. -# `gobject-introspection` +# gobject-introspection The C source code to libvips has been marked up with special comments describing the interface in a standard way. These comments are read by From 4a59fa652c0e77b1529cb67cbf27559b831668fd Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Mon, 26 Feb 2018 18:33:20 +0000 Subject: [PATCH 5/5] make vips_sobel() cut out of vips_canny() --- ChangeLog | 1 + configure.ac | 6 +- libvips/convolution/Makefile.am | 1 + libvips/convolution/canny.c | 92 ++++++++--------- libvips/convolution/convolution.c | 2 + libvips/convolution/sobel.c | 156 +++++++++++++++++++++++++++++ libvips/create/gaussmat.c | 5 + libvips/include/vips/convolution.h | 2 + 8 files changed, 210 insertions(+), 55 deletions(-) create mode 100644 libvips/convolution/sobel.c diff --git a/ChangeLog b/ChangeLog index e549754d..3d910b2a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5,6 +5,7 @@ - hough_line scales width to 0 - 180, not 0 - 360 - hough_line is 4x faster - hough_circle is 2x faster +- add vips_sobel() and vips_canny() edge detectors 12/2/18 started 8.6.3 - use pkg-config to find libjpeg, if we can diff --git a/configure.ac b/configure.ac index f5cae2e1..cb71bb7e 100644 --- a/configure.ac +++ b/configure.ac @@ -1280,8 +1280,8 @@ if test x"$found_introspection" = xyes -a "$VIPS_LIBDIR/girepository-1.0" != "$I ;; # ignore for homebrew *) AC_MSG_RESULT([dnl -Vips-8.0.typelib will install to $VIPS_LIBDIR/girepository-1.0, but your -system repository seems to be $INTROSPECTION_TYPELIBDIR. +Vips-8.0.typelib will be installed to $VIPS_LIBDIR/girepository-1.0, but +your system repository seems to be $INTROSPECTION_TYPELIBDIR. You may need to add this directory to your typelib path, for example: export GI_TYPELIB_PATH="$VIPS_LIBDIR/girepository-1.0" @@ -1313,7 +1313,7 @@ overrides are installed in the correct location. ]) elif test x"$VIPS_PYEXECDIR/gi/overrides" != x"$syspygipath"; then AC_MSG_RESULT([dnl -The vips Python overrides file will install to +The vips Python overrides file will be installed to $VIPS_PYEXECDIR/gi/overrides/Vips.py, but your system gi overrides seem to be $syspygipath. You may need to copy this file, for example: diff --git a/libvips/convolution/Makefile.am b/libvips/convolution/Makefile.am index bf1ca89f..4a29b604 100644 --- a/libvips/convolution/Makefile.am +++ b/libvips/convolution/Makefile.am @@ -2,6 +2,7 @@ noinst_LTLIBRARIES = libconvolution.la libconvolution_la_SOURCES = \ canny.c \ + sobel.c \ convolution.c \ pconvolution.h \ correlation.c \ diff --git a/libvips/convolution/canny.c b/libvips/convolution/canny.c index 71d7a90b..e63cafa6 100644 --- a/libvips/convolution/canny.c +++ b/libvips/convolution/canny.c @@ -35,6 +35,17 @@ #define DEBUG */ +/* TODO + * - verify that our interpolating max edge works + * - does it actually help much? + * - can skip the sqrt() + * - support other image types + * - swap atan2 for a LUT with perhaps +/- 2 or 4 bits + * - check sobel speed with separated and non-sep masks + * - add autothreshold with otsu's method + * - leave blob analysis to a separate pass + */ + #ifdef HAVE_CONFIG_H #include #endif /*HAVE_CONFIG_H*/ @@ -53,6 +64,7 @@ typedef struct _VipsCanny { VipsImage *out; double sigma; + gboolean interpolate; double low; double high; @@ -65,50 +77,7 @@ typedef VipsOperationClass VipsCannyClass; G_DEFINE_TYPE( VipsCanny, vips_canny, VIPS_TYPE_OPERATION ); -static int -vips_canny_gradient_sobel( VipsImage *in, VipsImage **Gx, VipsImage **Gy ) -{ - VipsImage *scope; - VipsImage **t; - - scope = vips_image_new(); - t = (VipsImage **) vips_object_local_array( (VipsObject *) scope, 20 ); - - /* Separated Sobel gives Gx / Gy. - */ - t[1] = vips_image_new_matrixv( 1, 3, 1.0, 2.0, 1.0 ); - t[2] = vips_image_new_matrixv( 3, 1, 1.0, 0.0, -1.0 ); - vips_image_set_double( t[2], "offset", 128.0 ); - if( vips_conv( in, &t[3], t[1], - "precision", VIPS_PRECISION_INTEGER, - NULL ) || - vips_conv( t[3], Gx, t[2], - "precision", VIPS_PRECISION_INTEGER, - NULL ) ) { - g_object_unref( scope ); - return( -1 ); - } - - t[5] = vips_image_new_matrixv( 3, 1, 1.0, 2.0, 1.0 ); - t[6] = vips_image_new_matrixv( 1, 3, 1.0, 0.0, -1.0 ); - vips_image_set_double( t[6], "offset", 128.0 ); - if( vips_conv( in, &t[7], t[5], - "precision", VIPS_PRECISION_INTEGER, - NULL ) || - vips_conv( t[7], Gy, t[6], - "precision", VIPS_PRECISION_INTEGER, - NULL ) ) { - g_object_unref( scope ); - return( -1 ); - } - - g_object_unref( scope ); - - return( 0 ); -} - -/* Simple -1/+1 difference. The sobel version above does an edge - * detect as well. +/* Simple 2x2 -1/+1 difference. */ static int vips_canny_gradient_simple( VipsImage *in, VipsImage **Gx, VipsImage **Gy ) @@ -173,10 +142,14 @@ vips_canny_polar_generate( VipsRegion *or, int y = p2[band] - 128; int a = VIPS_DEG( atan2( x, y ) ) + 360; - /* Faster than hypot() for int args. Scale down - * or we'll clip on very hard edges. + /* We should calculate + * 0.5 * sqrt( x * x + y * y ) + * ie. length of hypot, scaled down to avoid + * clipping. We are only interested in relative + * magnitude, so we can skip the sqrt and just + * shift down 9 bits. */ - q[0] = 0.5 * sqrt( x * x + y * y ); + q[0] = (x * x + y * y + 256) >> 9; q[1] = 256 * a / 360; q += 2; @@ -428,6 +401,8 @@ vips_canny_thresh_generate( VipsRegion *or, VipsCanny *canny = (VipsCanny *) b; VipsRect *r = &or->valid; int sz = r->width * in->im->Bands; + VipsPel low = canny->low; + VipsPel high = canny->high; int x, y; @@ -444,9 +419,9 @@ vips_canny_thresh_generate( VipsRegion *or, int v; v = p[x]; - if( v <= canny->low ) + if( v <= low ) v = 0; - else if( v <= canny->high ) + else if( v <= high ) v = 128; else v = 255; @@ -509,10 +484,16 @@ vips_canny_build( VipsObject *object ) */ if( vips_embed( in, &t[10], 1, 1, in->Xsize + 2, in->Ysize + 2, "extend", VIPS_EXTEND_COPY, - NULL ) || - vips_canny_nonmax( t[10], &t[11] ) ) - //vips_canny_nonmaxi( t[10], &t[11] ) ) + NULL ) ) return( -1 ); + if( canny->interpolate ) { + if( vips_canny_nonmaxi( t[10], &t[11] ) ) + return( -1 ); + } + else { + if( vips_canny_nonmax( t[10], &t[11] ) ) + return( -1 ); + } in = t[11]; /* Double threshold. @@ -578,6 +559,13 @@ vips_canny_class_init( VipsCannyClass *class ) G_STRUCT_OFFSET( VipsCanny, high ), -INFINITY, INFINITY, 7.0 ); + VIPS_ARG_BOOL( class, "interpolate", 13, + _( "Interpolate" ), + _( "Interpolate gradient angles" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsCanny, interpolate ), + FALSE ); + } static void diff --git a/libvips/convolution/convolution.c b/libvips/convolution/convolution.c index 6a215c4a..bb195984 100644 --- a/libvips/convolution/convolution.c +++ b/libvips/convolution/convolution.c @@ -168,6 +168,7 @@ vips_convolution_operation_init( void ) extern int vips_spcor_get_type( void ); extern int vips_sharpen_get_type( void ); extern int vips_gaussblur_get_type( void ); + extern int vips_sobel_get_type( void ); extern int vips_canny_get_type( void ); vips_conv_get_type(); @@ -182,4 +183,5 @@ vips_convolution_operation_init( void ) vips_sharpen_get_type(); vips_gaussblur_get_type(); vips_canny_get_type(); + vips_sobel_get_type(); } diff --git a/libvips/convolution/sobel.c b/libvips/convolution/sobel.c new file mode 100644 index 00000000..6f825e29 --- /dev/null +++ b/libvips/convolution/sobel.c @@ -0,0 +1,156 @@ +/* Sobel edge detector + * + * 2/2/18 + * - from vips_sobel() + */ + +/* + + 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 + +/* TODO + * - check sobel speed with separated and non-sep masks + * - add an 8-bit sobel path, with offset 128 and code for abs() + abs() + */ + +typedef struct _VipsSobel { + VipsOperation parent_instance; + + VipsImage *in; + VipsImage *out; + +} VipsSobel; + +typedef VipsOperationClass VipsSobelClass; + +G_DEFINE_TYPE( VipsSobel, vips_sobel, VIPS_TYPE_OPERATION ); + +static int +vips_sobel_build( VipsObject *object ) +{ + VipsSobel *sobel = (VipsSobel *) object; + VipsImage **t = (VipsImage **) vips_object_local_array( object, 20 ); + + t[1] = vips_image_new_matrixv( 1, 3, 1.0, 2.0, 1.0 ); + t[2] = vips_image_new_matrixv( 3, 1, 1.0, 0.0, -1.0 ); + if( vips_conv( sobel->in, &t[3], t[1], NULL ) || + vips_conv( t[3], &t[4], t[2], NULL ) ) + return( -1 ); + + t[5] = vips_image_new_matrixv( 3, 1, 1.0, 2.0, 1.0 ); + t[6] = vips_image_new_matrixv( 1, 3, 1.0, 0.0, -1.0 ); + if( vips_conv( sobel->in, &t[7], t[5], NULL ) || + vips_conv( t[7], &t[8], t[6], NULL ) ) + return( -1 ); + + if( vips_abs( t[4], &t[9], NULL ) || + vips_abs( t[8], &t[10], NULL ) || + vips_add( t[9], t[10], &t[11], NULL ) || + vips_cast( t[11], &t[12], sobel->in->BandFmt, NULL ) ) + return( -1 ); + + g_object_set( object, "out", vips_image_new(), NULL ); + + if( vips_image_write( t[12], sobel->out ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_sobel_class_init( VipsSobelClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsOperationClass *operation_class = VIPS_OPERATION_CLASS( class ); + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "sobel"; + object_class->description = _( "Sobel edge detector" ); + object_class->build = vips_sobel_build; + + operation_class->flags = VIPS_OPERATION_SEQUENTIAL; + + VIPS_ARG_IMAGE( class, "in", 1, + _( "Input" ), + _( "Input image" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsSobel, in ) ); + + VIPS_ARG_IMAGE( class, "out", 2, + _( "Output" ), + _( "Output image" ), + VIPS_ARGUMENT_REQUIRED_OUTPUT, + G_STRUCT_OFFSET( VipsSobel, out ) ); + +} + +static void +vips_sobel_init( VipsSobel *sobel ) +{ +} + +/** + * vips_sobel: (method) + * @in: input image + * @out: (out): output image + * @...: %NULL-terminated list of optional named arguments + * + * Simple Sobel edge detector. + * + * See also: vips_canny(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_sobel( VipsImage *in, VipsImage **out, ... ) +{ + va_list ap; + int result; + + va_start( ap, out ); + result = vips_call_split( "sobel", ap, in, out ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/create/gaussmat.c b/libvips/create/gaussmat.c index 4c954fcd..fa67c305 100644 --- a/libvips/create/gaussmat.c +++ b/libvips/create/gaussmat.c @@ -157,6 +157,11 @@ vips_gaussmat_build( VipsObject *object ) } } + /* Make sure we can't make sum == 0: it'd certainly cause /0 later. + */ + if( sum == 0 ) + sum = 1; + vips_image_set_double( create->out, "scale", sum ); vips_image_set_double( create->out, "offset", 0.0 ); diff --git a/libvips/include/vips/convolution.h b/libvips/include/vips/convolution.h index 8cbfbac4..45ba2eb7 100644 --- a/libvips/include/vips/convolution.h +++ b/libvips/include/vips/convolution.h @@ -70,6 +70,8 @@ int vips_spcor( VipsImage *in, VipsImage *ref, VipsImage **out, ... ) int vips_fastcor( VipsImage *in, VipsImage *ref, VipsImage **out, ... ) __attribute__((sentinel)); +int vips_sobel( VipsImage *in, VipsImage **out, ... ) + __attribute__((sentinel)); int vips_canny( VipsImage *in, VipsImage **out, ... ) __attribute__((sentinel));