better rounding for vips_resize()
we were getting off by one size errors
This commit is contained in:
parent
9209fb25c5
commit
4974a1ed9c
@ -17,6 +17,7 @@
|
|||||||
- vipsthumbnail knows about webp shrink-on-load
|
- vipsthumbnail knows about webp shrink-on-load
|
||||||
- better behaviour for vips_cast() shift of non-int types (thanks apacheark)
|
- better behaviour for vips_cast() shift of non-int types (thanks apacheark)
|
||||||
- python .bandrank() now works like .bandjoin()
|
- python .bandrank() now works like .bandjoin()
|
||||||
|
- vipsthumbnail --interpolator and --sharpen are deprecated
|
||||||
|
|
||||||
27/1/16 started 8.2.3
|
27/1/16 started 8.2.3
|
||||||
- fix a crash with SPARC byte-order labq vips images
|
- fix a crash with SPARC byte-order labq vips images
|
||||||
|
4
TODO
4
TODO
@ -1,3 +1,4 @@
|
|||||||
|
- need tests for reducel3, test every kernel plues every numeric type
|
||||||
|
|
||||||
- removed the cache from resize since we no longer sharpen, can we get
|
- removed the cache from resize since we no longer sharpen, can we get
|
||||||
out-of-order reads?
|
out-of-order reads?
|
||||||
@ -6,6 +7,9 @@
|
|||||||
|
|
||||||
- what demand hint are we setting for the reduce / shrink funcs?
|
- what demand hint are we setting for the reduce / shrink funcs?
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- try SEQ_UNBUFFERED on jpg source, get out of order error?
|
- try SEQ_UNBUFFERED on jpg source, get out of order error?
|
||||||
|
|
||||||
- could load pdf thumbnails?
|
- could load pdf thumbnails?
|
||||||
|
@ -134,10 +134,12 @@ vips_resize_build( VipsObject *object )
|
|||||||
|
|
||||||
/* Do we need a further size adjustment? It's the difference
|
/* Do we need a further size adjustment? It's the difference
|
||||||
* between our target size and the size we have after vips_shrink().
|
* between our target size and the size we have after vips_shrink().
|
||||||
|
*
|
||||||
|
* Aim for a little above target so we can't round down below it.
|
||||||
*/
|
*/
|
||||||
hresidual = (double) target_width / in->Xsize;
|
hresidual = ((double) target_width + 0.1) / in->Xsize;
|
||||||
if( vips_object_argument_isset( object, "vscale" ) )
|
if( vips_object_argument_isset( object, "vscale" ) )
|
||||||
vresidual = (double) target_height / in->Ysize;
|
vresidual = ((double) target_height + 0.1) / in->Ysize;
|
||||||
else
|
else
|
||||||
vresidual = hresidual;
|
vresidual = hresidual;
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ for interp in nearest bilinear bicubic lbb nohalo vsqbs; do
|
|||||||
vipsthumbnail $tmp/t1.v -o $tmp/t2.v --size $size --interpolator $interp
|
vipsthumbnail $tmp/t1.v -o $tmp/t2.v --size $size --interpolator $interp
|
||||||
if [ $(vipsheader -f width $tmp/t2.v) -ne $size ]; then
|
if [ $(vipsheader -f width $tmp/t2.v) -ne $size ]; then
|
||||||
echo failed -- bad size
|
echo failed -- bad size
|
||||||
|
echo output width is $(vipsheader -f width $tmp/t2.v)
|
||||||
exit
|
exit
|
||||||
fi
|
fi
|
||||||
if [ $(vipsheader -f height $tmp/t2.v) -ne $size ]; then
|
if [ $(vipsheader -f height $tmp/t2.v) -ne $size ]; then
|
||||||
|
@ -77,6 +77,7 @@
|
|||||||
* - add webp --shrink support
|
* - add webp --shrink support
|
||||||
* 29/2/16
|
* 29/2/16
|
||||||
* - make more use of jpeg shrink-on-load now we've tuned vips_resize()
|
* - make more use of jpeg shrink-on-load now we've tuned vips_resize()
|
||||||
|
* - deprecate sharpen and interpolate
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifdef HAVE_CONFIG_H
|
#ifdef HAVE_CONFIG_H
|
||||||
@ -103,10 +104,8 @@ static char *thumbnail_size = "128";
|
|||||||
static int thumbnail_width = 128;
|
static int thumbnail_width = 128;
|
||||||
static int thumbnail_height = 128;
|
static int thumbnail_height = 128;
|
||||||
static char *output_format = "tn_%s.jpg";
|
static char *output_format = "tn_%s.jpg";
|
||||||
static char *interpolator = "bilinear";
|
|
||||||
static char *export_profile = NULL;
|
static char *export_profile = NULL;
|
||||||
static char *import_profile = NULL;
|
static char *import_profile = NULL;
|
||||||
static char *convolution_mask = "none";
|
|
||||||
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;
|
||||||
@ -117,6 +116,8 @@ static gboolean rotate_image = FALSE;
|
|||||||
static gboolean nosharpen = FALSE;
|
static gboolean nosharpen = FALSE;
|
||||||
static gboolean nodelete_profile = FALSE;
|
static gboolean nodelete_profile = FALSE;
|
||||||
static gboolean verbose = FALSE;
|
static gboolean verbose = FALSE;
|
||||||
|
static char *convolution_mask = NULL;
|
||||||
|
static char *interpolator = NULL;
|
||||||
|
|
||||||
static GOptionEntry options[] = {
|
static GOptionEntry options[] = {
|
||||||
{ "size", 's', 0,
|
{ "size", 's', 0,
|
||||||
@ -131,14 +132,6 @@ static GOptionEntry options[] = {
|
|||||||
G_OPTION_ARG_STRING, &output_format,
|
G_OPTION_ARG_STRING, &output_format,
|
||||||
N_( "set output format string to FORMAT" ),
|
N_( "set output format string to FORMAT" ),
|
||||||
N_( "FORMAT" ) },
|
N_( "FORMAT" ) },
|
||||||
{ "interpolator", 'p', 0,
|
|
||||||
G_OPTION_ARG_STRING, &interpolator,
|
|
||||||
N_( "resample with INTERPOLATOR" ),
|
|
||||||
N_( "INTERPOLATOR" ) },
|
|
||||||
{ "sharpen", 'r', 0,
|
|
||||||
G_OPTION_ARG_STRING, &convolution_mask,
|
|
||||||
N_( "sharpen with none|mild|MASKFILE" ),
|
|
||||||
N_( "none|mild|MASKFILE" ) },
|
|
||||||
{ "eprofile", 'e', 0,
|
{ "eprofile", 'e', 0,
|
||||||
G_OPTION_ARG_STRING, &export_profile,
|
G_OPTION_ARG_STRING, &export_profile,
|
||||||
N_( "export with PROFILE" ),
|
N_( "export with PROFILE" ),
|
||||||
@ -159,6 +152,7 @@ static GOptionEntry options[] = {
|
|||||||
{ "delete", 'd', 0,
|
{ "delete", 'd', 0,
|
||||||
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 },
|
||||||
|
|
||||||
{ "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 },
|
||||||
@ -168,6 +162,12 @@ static GOptionEntry options[] = {
|
|||||||
{ "nosharpen", 'n', G_OPTION_FLAG_HIDDEN,
|
{ "nosharpen", 'n', G_OPTION_FLAG_HIDDEN,
|
||||||
G_OPTION_ARG_NONE, &nosharpen,
|
G_OPTION_ARG_NONE, &nosharpen,
|
||||||
N_( "(deprecated, does nothing)" ), NULL },
|
N_( "(deprecated, does nothing)" ), NULL },
|
||||||
|
{ "interpolator", 'p', G_OPTION_FLAG_HIDDEN,
|
||||||
|
G_OPTION_ARG_STRING, &interpolator,
|
||||||
|
N_( "(deprecated, does nothing)" ), NULL },
|
||||||
|
{ "sharpen", 'r', G_OPTION_FLAG_HIDDEN,
|
||||||
|
G_OPTION_ARG_STRING, &convolution_mask,
|
||||||
|
N_( "(deprecated, does nothing)" ), NULL },
|
||||||
{ NULL }
|
{ NULL }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -185,15 +185,14 @@ calculate_shrink( VipsImage *im )
|
|||||||
VipsDirection direction;
|
VipsDirection direction;
|
||||||
|
|
||||||
/* Calculate the horizontal and vertical shrink we'd need to fit the
|
/* Calculate the horizontal and vertical shrink we'd need to fit the
|
||||||
* image to the bounding box, and pick the biggest.
|
* image to the bounding box, and pick the biggest. Aim for a little
|
||||||
|
* above the target so we can't round down below it.
|
||||||
*
|
*
|
||||||
* In crop mode we aim to fill the bounding box, so we must use the
|
* In crop mode we aim to fill the bounding box, so we must use the
|
||||||
* smaller axis.
|
* smaller axis.
|
||||||
*
|
|
||||||
* Take off a tiny amount to stop us rounding below the target.
|
|
||||||
*/
|
*/
|
||||||
double horizontal = (double) width / thumbnail_width - 0.0000001;
|
double horizontal = (double) width / (thumbnail_width + 0.1);
|
||||||
double vertical = (double) height / thumbnail_height - 0.0000001;
|
double vertical = (double) height / (thumbnail_height + 0.1);
|
||||||
|
|
||||||
if( crop_image ) {
|
if( crop_image ) {
|
||||||
if( horizontal < vertical )
|
if( horizontal < vertical )
|
||||||
@ -369,57 +368,8 @@ thumbnail_open( VipsObject *process, const char *filename )
|
|||||||
return( im );
|
return( im );
|
||||||
}
|
}
|
||||||
|
|
||||||
static VipsInterpolate *
|
|
||||||
thumbnail_interpolator( VipsObject *process, VipsImage *in )
|
|
||||||
{
|
|
||||||
double shrink = calculate_shrink( in );
|
|
||||||
|
|
||||||
VipsInterpolate *interp;
|
|
||||||
|
|
||||||
/* For images smaller than the thumbnail, we upscale with nearest
|
|
||||||
* neighbor. Otherwise we make thumbnails that look fuzzy and awful.
|
|
||||||
*/
|
|
||||||
if( !(interp = VIPS_INTERPOLATE( vips_object_new_from_string(
|
|
||||||
g_type_class_ref( VIPS_TYPE_INTERPOLATE ),
|
|
||||||
shrink <= 1.0 ? "nearest" : interpolator ) )) )
|
|
||||||
return( NULL );
|
|
||||||
|
|
||||||
vips_object_local( process, interp );
|
|
||||||
|
|
||||||
return( interp );
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Some interpolators look a little soft, so we have an optional sharpening
|
|
||||||
* stage.
|
|
||||||
*/
|
|
||||||
static VipsImage *
|
static VipsImage *
|
||||||
thumbnail_sharpen( VipsObject *process )
|
thumbnail_shrink( VipsObject *process, VipsImage *in )
|
||||||
{
|
|
||||||
VipsImage *mask;
|
|
||||||
|
|
||||||
if( strcmp( convolution_mask, "none" ) == 0 )
|
|
||||||
mask = NULL;
|
|
||||||
else if( strcmp( convolution_mask, "mild" ) == 0 ) {
|
|
||||||
mask = vips_image_new_matrixv( 3, 3,
|
|
||||||
-1.0, -1.0, -1.0,
|
|
||||||
-1.0, 32.0, -1.0,
|
|
||||||
-1.0, -1.0, -1.0 );
|
|
||||||
vips_image_set_double( mask, "scale", 24 );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
if( !(mask =
|
|
||||||
vips_image_new_from_file( convolution_mask, NULL )) )
|
|
||||||
vips_error_exit( "unable to load sharpen mask" );
|
|
||||||
|
|
||||||
if( mask )
|
|
||||||
vips_object_local( process, mask );
|
|
||||||
|
|
||||||
return( mask );
|
|
||||||
}
|
|
||||||
|
|
||||||
static VipsImage *
|
|
||||||
thumbnail_shrink( VipsObject *process, VipsImage *in,
|
|
||||||
VipsInterpolate *interp, VipsImage *sharpen )
|
|
||||||
{
|
{
|
||||||
VipsImage **t = (VipsImage **) vips_object_local_array( process, 10 );
|
VipsImage **t = (VipsImage **) vips_object_local_array( process, 10 );
|
||||||
VipsInterpretation interpretation = linear_processing ?
|
VipsInterpretation interpretation = linear_processing ?
|
||||||
@ -523,7 +473,6 @@ thumbnail_shrink( VipsObject *process, VipsImage *in,
|
|||||||
/* Add a tiny amount to stop rounding below the target.
|
/* Add a tiny amount to stop rounding below the target.
|
||||||
*/
|
*/
|
||||||
if( vips_resize( in, &t[4], 1.0 / shrink + 0.0000000001,
|
if( vips_resize( in, &t[4], 1.0 / shrink + 0.0000000001,
|
||||||
"interpolate", interp,
|
|
||||||
NULL ) )
|
NULL ) )
|
||||||
return( NULL );
|
return( NULL );
|
||||||
in = t[4];
|
in = t[4];
|
||||||
@ -613,17 +562,6 @@ thumbnail_shrink( VipsObject *process, VipsImage *in,
|
|||||||
in = out;
|
in = out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If we are upsampling, don't sharpen, since nearest looks dumb
|
|
||||||
* sharpened.
|
|
||||||
*/
|
|
||||||
if( shrink > 1.0 &&
|
|
||||||
sharpen ) {
|
|
||||||
vips_info( "vipsthumbnail", "sharpening thumbnail" );
|
|
||||||
if( vips_conv( in, &t[8], sharpen, NULL ) )
|
|
||||||
return( NULL );
|
|
||||||
in = t[8];
|
|
||||||
}
|
|
||||||
|
|
||||||
if( delete_profile &&
|
if( delete_profile &&
|
||||||
vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) {
|
vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) {
|
||||||
vips_info( "vipsthumbnail",
|
vips_info( "vipsthumbnail",
|
||||||
@ -733,18 +671,13 @@ 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 )
|
||||||
{
|
{
|
||||||
VipsImage *sharpen = thumbnail_sharpen( process );
|
|
||||||
|
|
||||||
VipsImage *in;
|
VipsImage *in;
|
||||||
VipsInterpolate *interp;
|
|
||||||
VipsImage *thumbnail;
|
VipsImage *thumbnail;
|
||||||
VipsImage *crop;
|
VipsImage *crop;
|
||||||
VipsImage *rotate;
|
VipsImage *rotate;
|
||||||
|
|
||||||
if( !(in = thumbnail_open( process, filename )) ||
|
if( !(in = thumbnail_open( process, filename )) ||
|
||||||
!(interp = thumbnail_interpolator( process, in )) ||
|
!(thumbnail = thumbnail_shrink( process, in )) ||
|
||||||
!(thumbnail =
|
|
||||||
thumbnail_shrink( process, in, interp, sharpen )) ||
|
|
||||||
!(crop = thumbnail_crop( process, thumbnail )) ||
|
!(crop = thumbnail_crop( process, thumbnail )) ||
|
||||||
!(rotate = thumbnail_rotate( process, crop )) ||
|
!(rotate = thumbnail_rotate( process, crop )) ||
|
||||||
thumbnail_write( process, rotate, filename ) )
|
thumbnail_write( process, rotate, filename ) )
|
||||||
@ -767,12 +700,6 @@ main( int argc, char **argv )
|
|||||||
textdomain( GETTEXT_PACKAGE );
|
textdomain( GETTEXT_PACKAGE );
|
||||||
setlocale( LC_ALL, "" );
|
setlocale( LC_ALL, "" );
|
||||||
|
|
||||||
/* Does this vips have bicubic? Default to that if it
|
|
||||||
* does.
|
|
||||||
*/
|
|
||||||
if( vips_type_find( "VipsInterpolate", "bicubic" ) )
|
|
||||||
interpolator = "bicubic";
|
|
||||||
|
|
||||||
context = g_option_context_new( _( "- thumbnail generator" ) );
|
context = g_option_context_new( _( "- thumbnail generator" ) );
|
||||||
|
|
||||||
main_group = g_option_group_new( NULL, NULL, NULL, NULL, NULL );
|
main_group = g_option_group_new( NULL, NULL, NULL, NULL, NULL );
|
||||||
|
Loading…
Reference in New Issue
Block a user