add --smartcrop to vipsthumbnail
does the obvious thing
This commit is contained in:
parent
d40773515c
commit
9e6832b34d
@ -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
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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.
|
||||
|
@ -82,6 +82,8 @@ typedef enum {
|
||||
} VipsAngle45;
|
||||
|
||||
typedef enum {
|
||||
VIPS_INTERESTING_NONE,
|
||||
VIPS_INTERESTING_CENTRE,
|
||||
VIPS_INTERESTING_ENTROPY,
|
||||
VIPS_INTERESTING_ATTENTION,
|
||||
VIPS_INTERESTING_LAST
|
||||
|
@ -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"},
|
||||
|
@ -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
|
||||
|
@ -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" );
|
||||
|
Loading…
Reference in New Issue
Block a user