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
|
- use expat, not libxml, for XML load ... removes a required dependency, since
|
||||||
we get expat as part of glib
|
we get expat as part of glib
|
||||||
- add vips_smartcrop(), based on sharp's smartcropper
|
- add vips_smartcrop(), based on sharp's smartcropper
|
||||||
|
- vipsthumbnail has a --smartcrop option
|
||||||
|
|
||||||
8/12/16 started 8.4.5
|
8/12/16 started 8.4.5
|
||||||
- allow libgsf-1.14.26 to help centos, thanks tdiprima
|
- allow libgsf-1.14.26 to help centos, thanks tdiprima
|
||||||
|
@ -99,6 +99,8 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* VipsInteresting:
|
* VipsInteresting:
|
||||||
|
* @VIPS_INTERESTING_NONE: do nothing
|
||||||
|
* @VIPS_INTERESTING_CENTRE: just take the centre
|
||||||
* @VIPS_INTERESTING_ENTROPY: use an entropy measure
|
* @VIPS_INTERESTING_ENTROPY: use an entropy measure
|
||||||
* @VIPS_INTERESTING_ATTENTION: look for features likely to draw human attention
|
* @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 );
|
return( -1 );
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case VIPS_INTERESTING_CENTRE:
|
||||||
|
case VIPS_INTERESTING_NONE:
|
||||||
default:
|
default:
|
||||||
g_assert_not_reached();
|
g_assert_not_reached();
|
||||||
break;
|
break;
|
||||||
@ -201,65 +203,86 @@ vips_smartcrop_build( VipsObject *object )
|
|||||||
ceil( (width - smartcrop->width) / 8.0 ),
|
ceil( (width - smartcrop->width) / 8.0 ),
|
||||||
ceil( (height - smartcrop->height) / 8.0 ) );
|
ceil( (height - smartcrop->height) / 8.0 ) );
|
||||||
|
|
||||||
/* Repeatedly take a slice off width and height until we
|
switch( smartcrop->interesting ) {
|
||||||
* reach the target.
|
case VIPS_INTERESTING_NONE:
|
||||||
*/
|
break;
|
||||||
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( slice_width > 0 ) {
|
case VIPS_INTERESTING_CENTRE:
|
||||||
VipsImage **t = (VipsImage **)
|
width = smartcrop->width;
|
||||||
vips_object_local_array( object, 4 );
|
height = smartcrop->height;
|
||||||
|
left = (in->Xsize - width) / 2;
|
||||||
|
top = (in->Ysize - height) / 2;
|
||||||
|
break;
|
||||||
|
|
||||||
double left_score;
|
case VIPS_INTERESTING_ENTROPY:
|
||||||
double right_score;
|
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],
|
if( slice_width > 0 ) {
|
||||||
left, top, slice_width, height, NULL ) ||
|
VipsImage **t = (VipsImage **)
|
||||||
vips_smartcrop_score( smartcrop, t[0],
|
vips_object_local_array( object, 4 );
|
||||||
&left_score ) )
|
|
||||||
return( -1 );
|
|
||||||
|
|
||||||
if( vips_extract_area( in, &t[1],
|
double left_score;
|
||||||
left + width - slice_width, top,
|
double right_score;
|
||||||
slice_width, height, NULL ) ||
|
|
||||||
vips_smartcrop_score( smartcrop, t[1],
|
|
||||||
&right_score ) )
|
|
||||||
return( -1 );
|
|
||||||
|
|
||||||
width -= slice_width;
|
if( vips_extract_area( in, &t[0],
|
||||||
if( left_score < right_score )
|
left, top, slice_width, height, NULL ) ||
|
||||||
left += slice_width;
|
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 ) {
|
default:
|
||||||
VipsImage **t = (VipsImage **)
|
g_assert_not_reached();
|
||||||
vips_object_local_array( object, 4 );
|
break;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* And our output is the final crop.
|
/* And our output is the final crop.
|
||||||
|
@ -82,6 +82,8 @@ typedef enum {
|
|||||||
} VipsAngle45;
|
} VipsAngle45;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
|
VIPS_INTERESTING_NONE,
|
||||||
|
VIPS_INTERESTING_CENTRE,
|
||||||
VIPS_INTERESTING_ENTROPY,
|
VIPS_INTERESTING_ENTROPY,
|
||||||
VIPS_INTERESTING_ATTENTION,
|
VIPS_INTERESTING_ATTENTION,
|
||||||
VIPS_INTERESTING_LAST
|
VIPS_INTERESTING_LAST
|
||||||
|
@ -357,6 +357,8 @@ vips_interesting_get_type( void )
|
|||||||
|
|
||||||
if( etype == 0 ) {
|
if( etype == 0 ) {
|
||||||
static const GEnumValue values[] = {
|
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_ENTROPY, "VIPS_INTERESTING_ENTROPY", "entropy"},
|
||||||
{VIPS_INTERESTING_ATTENTION, "VIPS_INTERESTING_ATTENTION", "attention"},
|
{VIPS_INTERESTING_ATTENTION, "VIPS_INTERESTING_ATTENTION", "attention"},
|
||||||
{VIPS_INTERESTING_LAST, "VIPS_INTERESTING_LAST", "last"},
|
{VIPS_INTERESTING_LAST, "VIPS_INTERESTING_LAST", "last"},
|
||||||
|
@ -73,7 +73,7 @@ typedef struct _VipsThumbnail {
|
|||||||
VipsSize size;
|
VipsSize size;
|
||||||
|
|
||||||
gboolean auto_rotate;
|
gboolean auto_rotate;
|
||||||
gboolean crop;
|
VipsInteresting crop;
|
||||||
gboolean linear;
|
gboolean linear;
|
||||||
char *export_profile;
|
char *export_profile;
|
||||||
char *import_profile;
|
char *import_profile;
|
||||||
@ -160,7 +160,7 @@ vips_thumbnail_calculate_shrink( VipsThumbnail *thumbnail,
|
|||||||
double horizontal = (double) width / thumbnail->width;
|
double horizontal = (double) width / thumbnail->width;
|
||||||
double vertical = (double) height / thumbnail->height;
|
double vertical = (double) height / thumbnail->height;
|
||||||
|
|
||||||
if( thumbnail->crop ) {
|
if( thumbnail->crop != VIPS_INTERESTING_NONE ) {
|
||||||
if( horizontal < vertical )
|
if( horizontal < vertical )
|
||||||
direction = VIPS_DIRECTION_HORIZONTAL;
|
direction = VIPS_DIRECTION_HORIZONTAL;
|
||||||
else
|
else
|
||||||
@ -483,17 +483,21 @@ vips_thumbnail_build( VipsObject *object )
|
|||||||
|
|
||||||
/* Crop after rotate so we don't need to rotate the crop box.
|
/* Crop after rotate so we don't need to rotate the crop box.
|
||||||
*/
|
*/
|
||||||
if( thumbnail->crop ) {
|
if( thumbnail->crop != VIPS_INTERESTING_NONE ) {
|
||||||
int left = (in->Xsize - thumbnail->width) / 2;
|
|
||||||
int top = (in->Ysize - thumbnail->height) / 2;
|
|
||||||
|
|
||||||
g_info( "cropping to %dx%d",
|
g_info( "cropping to %dx%d",
|
||||||
thumbnail->width, thumbnail->height );
|
thumbnail->width, thumbnail->height );
|
||||||
|
|
||||||
if( vips_extract_area( in, &t[8], left, top,
|
/* Need to copy to memory, we have to stay seq.
|
||||||
thumbnail->width, thumbnail->height, NULL ) )
|
*
|
||||||
|
* 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 );
|
return( -1 );
|
||||||
in = t[8];
|
in = t[11];
|
||||||
}
|
}
|
||||||
|
|
||||||
g_object_set( thumbnail, "out", vips_image_new(), NULL );
|
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 ),
|
G_STRUCT_OFFSET( VipsThumbnail, auto_rotate ),
|
||||||
TRUE );
|
TRUE );
|
||||||
|
|
||||||
VIPS_ARG_BOOL( class, "crop", 116,
|
VIPS_ARG_ENUM( class, "crop", 116,
|
||||||
_( "Crop" ),
|
_( "Crop" ),
|
||||||
_( "Reduce to fill target rectangle, then crop" ),
|
_( "Reduce to fill target rectangle, then crop" ),
|
||||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||||
G_STRUCT_OFFSET( VipsThumbnail, crop ),
|
G_STRUCT_OFFSET( VipsThumbnail, crop ),
|
||||||
FALSE );
|
VIPS_TYPE_INTERESTING, VIPS_INTERESTING_NONE );
|
||||||
|
|
||||||
VIPS_ARG_BOOL( class, "linear", 117,
|
VIPS_ARG_BOOL( class, "linear", 117,
|
||||||
_( "Linear" ),
|
_( "Linear" ),
|
||||||
@ -694,7 +698,7 @@ vips_thumbnail_file_init( VipsThumbnailFile *file )
|
|||||||
* * @height: %gint, target height in pixels
|
* * @height: %gint, target height in pixels
|
||||||
* * @size: #VipsSize, upsize, downsize or both
|
* * @size: #VipsSize, upsize, downsize or both
|
||||||
* * @auto_rotate: %gboolean, rotate upright using orientation tag
|
* * @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
|
* * @linear: %gboolean, perform shrink in linear light
|
||||||
* * @import_profile: %gchararray, fallback import ICC profile
|
* * @import_profile: %gchararray, fallback import ICC profile
|
||||||
* * @export_profile: %gchararray, export 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.
|
* specify a separate height with the @height option.
|
||||||
*
|
*
|
||||||
* If you set @crop, then the output image will fill the whole of the @width x
|
* 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
|
* 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
|
* 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
|
* * @height: %gint, target height in pixels
|
||||||
* * @size: #VipsSize, upsize, downsize or both
|
* * @size: #VipsSize, upsize, downsize or both
|
||||||
* * @auto_rotate: %gboolean, rotate upright using orientation tag
|
* * @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
|
* * @linear: %gboolean, perform shrink in linear light
|
||||||
* * @import_probuffer: %gchararray, fallback import ICC probuffer
|
* * @import_probuffer: %gchararray, fallback import ICC probuffer
|
||||||
* * @export_probuffer: %gchararray, export ICC probuffer
|
* * @export_probuffer: %gchararray, export ICC probuffer
|
||||||
|
@ -120,6 +120,7 @@ static char *import_profile = NULL;
|
|||||||
static gboolean delete_profile = FALSE;
|
static gboolean delete_profile = FALSE;
|
||||||
static gboolean linear_processing = FALSE;
|
static gboolean linear_processing = FALSE;
|
||||||
static gboolean crop_image = FALSE;
|
static gboolean crop_image = FALSE;
|
||||||
|
static char *smartcrop_image = NULL;
|
||||||
static gboolean rotate_image = FALSE;
|
static gboolean rotate_image = FALSE;
|
||||||
|
|
||||||
/* Deprecated and unused.
|
/* Deprecated and unused.
|
||||||
@ -144,18 +145,18 @@ static GOptionEntry options[] = {
|
|||||||
N_( "set output format string to FORMAT" ),
|
N_( "set output format string to FORMAT" ),
|
||||||
N_( "FORMAT" ) },
|
N_( "FORMAT" ) },
|
||||||
{ "eprofile", 'e', 0,
|
{ "eprofile", 'e', 0,
|
||||||
G_OPTION_ARG_STRING, &export_profile,
|
G_OPTION_ARG_FILENAME, &export_profile,
|
||||||
N_( "export with PROFILE" ),
|
N_( "export with PROFILE" ),
|
||||||
N_( "PROFILE" ) },
|
N_( "PROFILE" ) },
|
||||||
{ "iprofile", 'i', 0,
|
{ "iprofile", 'i', 0,
|
||||||
G_OPTION_ARG_STRING, &import_profile,
|
G_OPTION_ARG_FILENAME, &import_profile,
|
||||||
N_( "import untagged images with PROFILE" ),
|
N_( "import untagged images with PROFILE" ),
|
||||||
N_( "PROFILE" ) },
|
N_( "PROFILE" ) },
|
||||||
{ "linear", 'a', 0,
|
{ "linear", 'a', 0,
|
||||||
G_OPTION_ARG_NONE, &linear_processing,
|
G_OPTION_ARG_NONE, &linear_processing,
|
||||||
N_( "process in linear space" ), NULL },
|
N_( "process in linear space" ), NULL },
|
||||||
{ "crop", 'c', 0,
|
{ "smartcrop", 'c', 0,
|
||||||
G_OPTION_ARG_NONE, &crop_image,
|
G_OPTION_ARG_STRING, &smartcrop_image,
|
||||||
N_( "crop exactly to SIZE" ), NULL },
|
N_( "crop exactly to SIZE" ), NULL },
|
||||||
{ "rotate", 't', 0,
|
{ "rotate", 't', 0,
|
||||||
G_OPTION_ARG_NONE, &rotate_image,
|
G_OPTION_ARG_NONE, &rotate_image,
|
||||||
@ -164,6 +165,9 @@ static GOptionEntry options[] = {
|
|||||||
G_OPTION_ARG_NONE, &delete_profile,
|
G_OPTION_ARG_NONE, &delete_profile,
|
||||||
N_( "delete profile from exported image" ), NULL },
|
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,
|
{ "verbose", 'v', G_OPTION_FLAG_HIDDEN,
|
||||||
G_OPTION_ARG_NONE, &verbose,
|
G_OPTION_ARG_NONE, &verbose,
|
||||||
N_( "(deprecated, does nothing)" ), NULL },
|
N_( "(deprecated, does nothing)" ), NULL },
|
||||||
@ -235,13 +239,22 @@ thumbnail_write( VipsObject *process, VipsImage *im, const char *filename )
|
|||||||
static int
|
static int
|
||||||
thumbnail_process( VipsObject *process, const char *filename )
|
thumbnail_process( VipsObject *process, const char *filename )
|
||||||
{
|
{
|
||||||
|
VipsInteresting interesting;
|
||||||
VipsImage *image;
|
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,
|
if( vips_thumbnail( filename, &image, thumbnail_width,
|
||||||
"height", thumbnail_height,
|
"height", thumbnail_height,
|
||||||
"size", size_restriction,
|
"size", size_restriction,
|
||||||
"auto_rotate", rotate_image,
|
"auto_rotate", rotate_image,
|
||||||
"crop", crop_image,
|
"crop", interesting,
|
||||||
"linear", linear_processing,
|
"linear", linear_processing,
|
||||||
"import_profile", import_profile,
|
"import_profile", import_profile,
|
||||||
"export_profile", export_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,
|
/* If --crop is set, both width and height must be specified,
|
||||||
* since we'll need a complete bounding box to fill.
|
* 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",
|
vips_error( "thumbnail",
|
||||||
"both width and height must be given if "
|
"both width and height must be given if "
|
||||||
"--crop is enabled" );
|
"--crop is enabled" );
|
||||||
|
Loading…
Reference in New Issue
Block a user