From 9e6832b34d62d61b6836404a7436572bbf2d867c Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Wed, 8 Mar 2017 14:31:00 +0000 Subject: [PATCH] add --smartcrop to vipsthumbnail does the obvious thing --- ChangeLog | 1 + libvips/conversion/conversion.c | 2 + libvips/conversion/smartcrop.c | 127 ++++++++++++++++++------------ libvips/include/vips/conversion.h | 2 + libvips/iofuncs/enumtypes.c | 2 + libvips/resample/thumbnail.c | 33 ++++---- tools/vipsthumbnail.c | 27 +++++-- 7 files changed, 122 insertions(+), 72 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4b1fd200..0a5d9400 100644 --- a/ChangeLog +++ b/ChangeLog @@ -35,6 +35,7 @@ - use expat, not libxml, for XML load ... removes a required dependency, since we get expat as part of glib - add vips_smartcrop(), based on sharp's smartcropper +- vipsthumbnail has a --smartcrop option 8/12/16 started 8.4.5 - allow libgsf-1.14.26 to help centos, thanks tdiprima diff --git a/libvips/conversion/conversion.c b/libvips/conversion/conversion.c index 3ed37379..58523f5b 100644 --- a/libvips/conversion/conversion.c +++ b/libvips/conversion/conversion.c @@ -99,6 +99,8 @@ /** * VipsInteresting: + * @VIPS_INTERESTING_NONE: do nothing + * @VIPS_INTERESTING_CENTRE: just take the centre * @VIPS_INTERESTING_ENTROPY: use an entropy measure * @VIPS_INTERESTING_ATTENTION: look for features likely to draw human attention * diff --git a/libvips/conversion/smartcrop.c b/libvips/conversion/smartcrop.c index 3e757476..b2698b2a 100644 --- a/libvips/conversion/smartcrop.c +++ b/libvips/conversion/smartcrop.c @@ -146,6 +146,8 @@ vips_smartcrop_score( VipsSmartcrop *smartcrop, VipsImage *image, double *score return( -1 ); break; + case VIPS_INTERESTING_CENTRE: + case VIPS_INTERESTING_NONE: default: g_assert_not_reached(); break; @@ -201,65 +203,86 @@ vips_smartcrop_build( VipsObject *object ) ceil( (width - smartcrop->width) / 8.0 ), ceil( (height - smartcrop->height) / 8.0 ) ); - /* Repeatedly take a slice off width and height until we - * reach the target. - */ - while( width > smartcrop->width || - height > smartcrop->height ) { - const int slice_width = - VIPS_MIN( width - smartcrop->width, max_slice_size ); - const int slice_height = - VIPS_MIN( height - smartcrop->height, max_slice_size ); + switch( smartcrop->interesting ) { + case VIPS_INTERESTING_NONE: + break; - if( slice_width > 0 ) { - VipsImage **t = (VipsImage **) - vips_object_local_array( object, 4 ); + case VIPS_INTERESTING_CENTRE: + width = smartcrop->width; + height = smartcrop->height; + left = (in->Xsize - width) / 2; + top = (in->Ysize - height) / 2; + break; - double left_score; - double right_score; + case VIPS_INTERESTING_ENTROPY: + case VIPS_INTERESTING_ATTENTION: + /* Repeatedly take a slice off width and height until we + * reach the target. + */ + while( width > smartcrop->width || + height > smartcrop->height ) { + const int slice_width = + VIPS_MIN( width - smartcrop->width, + max_slice_size ); + const int slice_height = + VIPS_MIN( height - smartcrop->height, + max_slice_size ); - if( vips_extract_area( in, &t[0], - left, top, slice_width, height, NULL ) || - vips_smartcrop_score( smartcrop, t[0], - &left_score ) ) - return( -1 ); + if( slice_width > 0 ) { + VipsImage **t = (VipsImage **) + vips_object_local_array( object, 4 ); - if( vips_extract_area( in, &t[1], - left + width - slice_width, top, - slice_width, height, NULL ) || - vips_smartcrop_score( smartcrop, t[1], - &right_score ) ) - return( -1 ); + double left_score; + double right_score; - width -= slice_width; - if( left_score < right_score ) - left += slice_width; + if( vips_extract_area( in, &t[0], + left, top, slice_width, height, NULL ) || + vips_smartcrop_score( smartcrop, t[0], + &left_score ) ) + return( -1 ); + + if( vips_extract_area( in, &t[1], + left + width - slice_width, top, + slice_width, height, NULL ) || + vips_smartcrop_score( smartcrop, t[1], + &right_score ) ) + return( -1 ); + + width -= slice_width; + if( left_score < right_score ) + left += slice_width; + } + + if( slice_height > 0 ) { + VipsImage **t = (VipsImage **) + vips_object_local_array( object, 4 ); + + double top_score; + double bottom_score; + + if( vips_extract_area( in, &t[0], + left, top, width, slice_height, NULL ) || + vips_smartcrop_score( smartcrop, t[0], + &top_score ) ) + return( -1 ); + + if( vips_extract_area( in, &t[1], + left, top + height - slice_height, + width, slice_height, NULL ) || + vips_smartcrop_score( smartcrop, t[1], + &bottom_score ) ) + return( -1 ); + + height -= slice_height; + if( top_score < bottom_score ) + top += slice_height; + } } + break; - if( slice_height > 0 ) { - VipsImage **t = (VipsImage **) - vips_object_local_array( object, 4 ); - - double top_score; - double bottom_score; - - if( vips_extract_area( in, &t[0], - left, top, width, slice_height, NULL ) || - vips_smartcrop_score( smartcrop, t[0], - &top_score ) ) - return( -1 ); - - if( vips_extract_area( in, &t[1], - left, top + height - slice_height, - width, slice_height, NULL ) || - vips_smartcrop_score( smartcrop, t[1], - &bottom_score ) ) - return( -1 ); - - height -= slice_height; - if( top_score < bottom_score ) - top += slice_height; - } + default: + g_assert_not_reached(); + break; } /* And our output is the final crop. diff --git a/libvips/include/vips/conversion.h b/libvips/include/vips/conversion.h index 12f43c85..604dcfaf 100644 --- a/libvips/include/vips/conversion.h +++ b/libvips/include/vips/conversion.h @@ -82,6 +82,8 @@ typedef enum { } VipsAngle45; typedef enum { + VIPS_INTERESTING_NONE, + VIPS_INTERESTING_CENTRE, VIPS_INTERESTING_ENTROPY, VIPS_INTERESTING_ATTENTION, VIPS_INTERESTING_LAST diff --git a/libvips/iofuncs/enumtypes.c b/libvips/iofuncs/enumtypes.c index d8eaa18d..3b8634e0 100644 --- a/libvips/iofuncs/enumtypes.c +++ b/libvips/iofuncs/enumtypes.c @@ -357,6 +357,8 @@ vips_interesting_get_type( void ) if( etype == 0 ) { static const GEnumValue values[] = { + {VIPS_INTERESTING_NONE, "VIPS_INTERESTING_NONE", "none"}, + {VIPS_INTERESTING_CENTRE, "VIPS_INTERESTING_CENTRE", "centre"}, {VIPS_INTERESTING_ENTROPY, "VIPS_INTERESTING_ENTROPY", "entropy"}, {VIPS_INTERESTING_ATTENTION, "VIPS_INTERESTING_ATTENTION", "attention"}, {VIPS_INTERESTING_LAST, "VIPS_INTERESTING_LAST", "last"}, diff --git a/libvips/resample/thumbnail.c b/libvips/resample/thumbnail.c index 14d30447..213c83b4 100644 --- a/libvips/resample/thumbnail.c +++ b/libvips/resample/thumbnail.c @@ -73,7 +73,7 @@ typedef struct _VipsThumbnail { VipsSize size; gboolean auto_rotate; - gboolean crop; + VipsInteresting crop; gboolean linear; char *export_profile; char *import_profile; @@ -160,7 +160,7 @@ vips_thumbnail_calculate_shrink( VipsThumbnail *thumbnail, double horizontal = (double) width / thumbnail->width; double vertical = (double) height / thumbnail->height; - if( thumbnail->crop ) { + if( thumbnail->crop != VIPS_INTERESTING_NONE ) { if( horizontal < vertical ) direction = VIPS_DIRECTION_HORIZONTAL; else @@ -483,17 +483,21 @@ vips_thumbnail_build( VipsObject *object ) /* Crop after rotate so we don't need to rotate the crop box. */ - if( thumbnail->crop ) { - int left = (in->Xsize - thumbnail->width) / 2; - int top = (in->Ysize - thumbnail->height) / 2; - + if( thumbnail->crop != VIPS_INTERESTING_NONE ) { g_info( "cropping to %dx%d", thumbnail->width, thumbnail->height ); - if( vips_extract_area( in, &t[8], left, top, - thumbnail->width, thumbnail->height, NULL ) ) + /* Need to copy to memory, we have to stay seq. + * + * FIXME ... could skip the copy if we've rotated. + */ + if( !(t[8] = vips_image_copy_memory( in )) || + vips_smartcrop( t[8], &t[11], + thumbnail->width, thumbnail->height, + "interesting", thumbnail->crop, + NULL ) ) return( -1 ); - in = t[8]; + in = t[11]; } g_object_set( thumbnail, "out", vips_image_new(), NULL ); @@ -553,12 +557,12 @@ vips_thumbnail_class_init( VipsThumbnailClass *class ) G_STRUCT_OFFSET( VipsThumbnail, auto_rotate ), TRUE ); - VIPS_ARG_BOOL( class, "crop", 116, + VIPS_ARG_ENUM( class, "crop", 116, _( "Crop" ), _( "Reduce to fill target rectangle, then crop" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsThumbnail, crop ), - FALSE ); + VIPS_TYPE_INTERESTING, VIPS_INTERESTING_NONE ); VIPS_ARG_BOOL( class, "linear", 117, _( "Linear" ), @@ -694,7 +698,7 @@ vips_thumbnail_file_init( VipsThumbnailFile *file ) * * @height: %gint, target height in pixels * * @size: #VipsSize, upsize, downsize or both * * @auto_rotate: %gboolean, rotate upright using orientation tag - * * @crop: %gboolean, shrink and crop to fill target + * * @crop: #VipsInteresting, shrink and crop to fill target * * @linear: %gboolean, perform shrink in linear light * * @import_profile: %gchararray, fallback import ICC profile * * @export_profile: %gchararray, export ICC profile @@ -711,7 +715,8 @@ vips_thumbnail_file_init( VipsThumbnailFile *file ) * specify a separate height with the @height option. * * If you set @crop, then the output image will fill the whole of the @width x - * @height rectangle, with any excess cropped away. + * @height rectangle, with any excess cropped away. See vips_smartcrop() for + * details on the cropping strategy. * * Normally the operation will upsize or downsize as required. If @size is set * to #VIPS_SIZE_UP, the operation will only upsize and will just @@ -862,7 +867,7 @@ vips_thumbnail_buffer_init( VipsThumbnailBuffer *buffer ) * * @height: %gint, target height in pixels * * @size: #VipsSize, upsize, downsize or both * * @auto_rotate: %gboolean, rotate upright using orientation tag - * * @crop: %gboolean, shrink and crop to fill target + * * @crop: #VipsInteresting, shrink and crop to fill target * * @linear: %gboolean, perform shrink in linear light * * @import_probuffer: %gchararray, fallback import ICC probuffer * * @export_probuffer: %gchararray, export ICC probuffer diff --git a/tools/vipsthumbnail.c b/tools/vipsthumbnail.c index 1723a7c5..ccbfbba8 100644 --- a/tools/vipsthumbnail.c +++ b/tools/vipsthumbnail.c @@ -120,6 +120,7 @@ static char *import_profile = NULL; static gboolean delete_profile = FALSE; static gboolean linear_processing = FALSE; static gboolean crop_image = FALSE; +static char *smartcrop_image = NULL; static gboolean rotate_image = FALSE; /* Deprecated and unused. @@ -144,18 +145,18 @@ static GOptionEntry options[] = { N_( "set output format string to FORMAT" ), N_( "FORMAT" ) }, { "eprofile", 'e', 0, - G_OPTION_ARG_STRING, &export_profile, + G_OPTION_ARG_FILENAME, &export_profile, N_( "export with PROFILE" ), N_( "PROFILE" ) }, { "iprofile", 'i', 0, - G_OPTION_ARG_STRING, &import_profile, + G_OPTION_ARG_FILENAME, &import_profile, N_( "import untagged images with PROFILE" ), N_( "PROFILE" ) }, { "linear", 'a', 0, G_OPTION_ARG_NONE, &linear_processing, N_( "process in linear space" ), NULL }, - { "crop", 'c', 0, - G_OPTION_ARG_NONE, &crop_image, + { "smartcrop", 'c', 0, + G_OPTION_ARG_STRING, &smartcrop_image, N_( "crop exactly to SIZE" ), NULL }, { "rotate", 't', 0, G_OPTION_ARG_NONE, &rotate_image, @@ -164,6 +165,9 @@ static GOptionEntry options[] = { G_OPTION_ARG_NONE, &delete_profile, N_( "delete profile from exported image" ), NULL }, + { "crop", 'c', G_OPTION_FLAG_HIDDEN, + G_OPTION_ARG_NONE, &crop_image, + N_( "(deprecated, crop exactly to SIZE)" ), NULL }, { "verbose", 'v', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &verbose, N_( "(deprecated, does nothing)" ), NULL }, @@ -235,13 +239,22 @@ thumbnail_write( VipsObject *process, VipsImage *im, const char *filename ) static int thumbnail_process( VipsObject *process, const char *filename ) { + VipsInteresting interesting; VipsImage *image; + interesting = VIPS_INTERESTING_NONE; + if( crop_image ) + interesting = VIPS_INTERESTING_CENTRE; + if( smartcrop_image && + (interesting = vips_enum_from_nick( "vipsthumbnail", + VIPS_TYPE_INTERESTING, smartcrop_image )) < 0 ) + return( -1 ); + if( vips_thumbnail( filename, &image, thumbnail_width, "height", thumbnail_height, "size", size_restriction, "auto_rotate", rotate_image, - "crop", crop_image, + "crop", interesting, "linear", linear_processing, "import_profile", import_profile, "export_profile", export_profile, @@ -291,7 +304,9 @@ thumbnail_parse_geometry( const char *geometry ) /* If --crop is set, both width and height must be specified, * since we'll need a complete bounding box to fill. */ - if( crop_image && x && (!w || !h) ) { + if( crop_image && + x && + (!w || !h) ) { vips_error( "thumbnail", "both width and height must be given if " "--crop is enabled" );