add --smartcrop to vipsthumbnail

does the obvious thing
This commit is contained in:
John Cupitt 2017-03-08 14:31:00 +00:00
parent d40773515c
commit 9e6832b34d
7 changed files with 122 additions and 72 deletions

View File

@ -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

View File

@ -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
*

View File

@ -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.

View File

@ -82,6 +82,8 @@ typedef enum {
} VipsAngle45;
typedef enum {
VIPS_INTERESTING_NONE,
VIPS_INTERESTING_CENTRE,
VIPS_INTERESTING_ENTROPY,
VIPS_INTERESTING_ATTENTION,
VIPS_INTERESTING_LAST

View File

@ -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"},

View File

@ -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

View File

@ -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" );