Resize improvements; add ceil
and gap
options (#1769)
* Add a round-up option to shrink * Only leave the final 200% to reduce * Add gap option to reduce and resize * Add unit tests * Incorporate feedback
This commit is contained in:
parent
cb55fdcfd8
commit
894ed1cb13
@ -8,6 +8,8 @@
|
||||
* - add @centre option
|
||||
* 6/6/20 kleisauke
|
||||
* - deprecate @centre option, it's now always on
|
||||
* 22/4/22 kleisauke
|
||||
* - add @gap option
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -81,10 +83,16 @@
|
||||
* Optional arguments:
|
||||
*
|
||||
* * @kernel: #VipsKernel to use to interpolate (default: lanczos3)
|
||||
* * @gap: reducing gap to use (default: 0.0)
|
||||
*
|
||||
* Reduce @in vertically by a float factor. The pixels in @out are
|
||||
* interpolated with a 1D mask generated by @kernel.
|
||||
*
|
||||
* Set @gap to speed up reducing by having vips_shrinkv() to shrink
|
||||
* with a box filter first. The bigger @gap, the closer the result
|
||||
* to the fair resampling. The smaller @gap, the faster resizing.
|
||||
* The default value is 0.0 (no optimization).
|
||||
*
|
||||
* This is a very low-level operation: see vips_resize() for a more
|
||||
* convenient way to resize images.
|
||||
*
|
||||
@ -106,10 +114,16 @@
|
||||
* Optional arguments:
|
||||
*
|
||||
* * @kernel: #VipsKernel to use to interpolate (default: lanczos3)
|
||||
* * @gap: reducing gap to use (default: 0.0)
|
||||
*
|
||||
* Reduce @in horizontally by a float factor. The pixels in @out are
|
||||
* interpolated with a 1D mask generated by @kernel.
|
||||
*
|
||||
* Set @gap to speed up reducing by having vips_shrinkh() to shrink
|
||||
* with a box filter first. The bigger @gap, the closer the result
|
||||
* to the fair resampling. The smaller @gap, the faster resizing.
|
||||
* The default value is 0.0 (no optimization).
|
||||
*
|
||||
* This is a very low-level operation: see vips_resize() for a more
|
||||
* convenient way to resize images.
|
||||
*
|
||||
@ -126,6 +140,7 @@ typedef struct _VipsReduce {
|
||||
|
||||
double hshrink; /* Shrink factors */
|
||||
double vshrink;
|
||||
double gap; /* Reduce gap */
|
||||
|
||||
/* The thing we use to make the kernel.
|
||||
*/
|
||||
@ -154,9 +169,11 @@ vips_reduce_build( VipsObject *object )
|
||||
|
||||
if( vips_reducev( resample->in, &t[0], reduce->vshrink,
|
||||
"kernel", reduce->kernel,
|
||||
"gap", reduce->gap,
|
||||
NULL ) ||
|
||||
vips_reduceh( t[0], &t[1], reduce->hshrink,
|
||||
"kernel", reduce->kernel,
|
||||
"gap", reduce->gap,
|
||||
NULL ) ||
|
||||
vips_image_write( t[1], resample->out ) )
|
||||
return( -1 );
|
||||
@ -203,6 +220,13 @@ vips_reduce_class_init( VipsReduceClass *class )
|
||||
G_STRUCT_OFFSET( VipsReduce, kernel ),
|
||||
VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 );
|
||||
|
||||
VIPS_ARG_DOUBLE( class, "gap", 4,
|
||||
_( "Gap" ),
|
||||
_( "Reducing gap" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsReduce, gap ),
|
||||
0.0, 1000000.0, 0.0 );
|
||||
|
||||
/* The old names .. now use h and v everywhere.
|
||||
*/
|
||||
VIPS_ARG_DOUBLE( class, "xshrink", 8,
|
||||
@ -233,6 +257,7 @@ vips_reduce_class_init( VipsReduceClass *class )
|
||||
static void
|
||||
vips_reduce_init( VipsReduce *reduce )
|
||||
{
|
||||
reduce->gap = 0.0;
|
||||
reduce->kernel = VIPS_KERNEL_LANCZOS3;
|
||||
}
|
||||
|
||||
@ -247,17 +272,23 @@ vips_reduce_init( VipsReduce *reduce )
|
||||
* Optional arguments:
|
||||
*
|
||||
* * @kernel: #VipsKernel to use to interpolate (default: lanczos3)
|
||||
* * @gap: reducing gap to use (default: 0.0)
|
||||
*
|
||||
* Reduce @in by a pair of factors with a pair of 1D kernels. This
|
||||
* will not work well for shrink factors greater than three.
|
||||
*
|
||||
* Set @gap to speed up reducing by having vips_shrink() to shrink
|
||||
* with a box filter first. The bigger @gap, the closer the result
|
||||
* to the fair resampling. The smaller @gap, the faster resizing.
|
||||
* The default value is 0.0 (no optimization).
|
||||
*
|
||||
* This is a very low-level operation: see vips_resize() for a more
|
||||
* convenient way to resize images.
|
||||
*
|
||||
* This operation does not change xres or yres. The image resolution needs to
|
||||
* be updated by the application.
|
||||
*
|
||||
* See also: vips_resize(), vips_affine().
|
||||
* See also: vips_shrink(), vips_resize(), vips_affine().
|
||||
*
|
||||
* Returns: 0 on success, -1 on error
|
||||
*/
|
||||
|
@ -11,6 +11,8 @@
|
||||
* 6/6/20 kleisauke
|
||||
* - deprecate @centre option, it's now always on
|
||||
* - fix pixel shift
|
||||
* 22/4/22 kleisauke
|
||||
* - add @gap option
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -64,6 +66,7 @@ typedef struct _VipsReduceh {
|
||||
VipsResample parent_instance;
|
||||
|
||||
double hshrink; /* Reduce factor */
|
||||
double gap; /* Reduce gap */
|
||||
|
||||
/* The thing we use to make the kernel.
|
||||
*/
|
||||
@ -435,23 +438,62 @@ vips_reduceh_build( VipsObject *object )
|
||||
VipsResample *resample = VIPS_RESAMPLE( object );
|
||||
VipsReduceh *reduceh = (VipsReduceh *) object;
|
||||
VipsImage **t = (VipsImage **)
|
||||
vips_object_local_array( object, 2 );
|
||||
vips_object_local_array( object, 3 );
|
||||
|
||||
VipsImage *in;
|
||||
double width, extra_pixels;
|
||||
int width;
|
||||
int int_hshrink;
|
||||
double extra_pixels;
|
||||
|
||||
if( VIPS_OBJECT_CLASS( vips_reduceh_parent_class )->build( object ) )
|
||||
return( -1 );
|
||||
|
||||
in = resample->in;
|
||||
|
||||
if( reduceh->hshrink < 1 ) {
|
||||
if( reduceh->hshrink < 1.0 ) {
|
||||
vips_error( object_class->nickname,
|
||||
"%s", _( "reduce factors should be >= 1" ) );
|
||||
"%s", _( "reduce factor should be >= 1.0" ) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
if( reduceh->hshrink == 1 )
|
||||
/* Output size. We need to always round to nearest, so round(), not
|
||||
* rint().
|
||||
*/
|
||||
width = VIPS_ROUND_UINT(
|
||||
(double) in->Xsize / reduceh->hshrink );
|
||||
|
||||
/* How many pixels we are inventing in the input, -ve for
|
||||
* discarding.
|
||||
*/
|
||||
extra_pixels = width * reduceh->hshrink - in->Xsize;
|
||||
|
||||
if( reduceh->gap > 0.0 &&
|
||||
reduceh->kernel != VIPS_KERNEL_NEAREST ) {
|
||||
if( reduceh->gap < 1.0 ) {
|
||||
vips_error( object_class->nickname,
|
||||
"%s", _( "reduce gap should be >= 1.0" ) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
/* The int part of our reduce.
|
||||
*/
|
||||
int_hshrink = VIPS_MAX( 1,
|
||||
VIPS_FLOOR( (double) in->Xsize / width / reduceh->gap ) );
|
||||
|
||||
if( int_hshrink > 1 ) {
|
||||
g_info( "shrinkh by %d", int_hshrink );
|
||||
if( vips_shrinkh( in, &t[0], int_hshrink,
|
||||
"ceil", TRUE,
|
||||
NULL ) )
|
||||
return( -1 );
|
||||
in = t[0];
|
||||
|
||||
reduceh->hshrink /= int_hshrink;
|
||||
extra_pixels /= int_hshrink;
|
||||
}
|
||||
}
|
||||
|
||||
if( reduceh->hshrink == 1.0 )
|
||||
return( vips_image_write( in, resample->out ) );
|
||||
|
||||
reduceh->n_point =
|
||||
@ -463,18 +505,6 @@ vips_reduceh_build( VipsObject *object )
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
/* Output size. We need to always round to nearest, so round(), not
|
||||
* rint().
|
||||
*/
|
||||
width = VIPS_ROUND_UINT(
|
||||
(double) resample->in->Xsize / reduceh->hshrink );
|
||||
|
||||
/* How many pixels we are inventing in the input, -ve for
|
||||
* discarding.
|
||||
*/
|
||||
extra_pixels =
|
||||
width * reduceh->hshrink - resample->in->Xsize;
|
||||
|
||||
/* If we are rounding down, we are not using some input
|
||||
* pixels. We need to move the origin *inside* the input image
|
||||
* by half that distance so that we discard pixels equally
|
||||
@ -511,28 +541,25 @@ vips_reduceh_build( VipsObject *object )
|
||||
|
||||
/* Unpack for processing.
|
||||
*/
|
||||
if( vips_image_decode( in, &t[0] ) )
|
||||
if( vips_image_decode( in, &t[1] ) )
|
||||
return( -1 );
|
||||
in = t[0];
|
||||
in = t[1];
|
||||
|
||||
/* Add new pixels around the input so we can interpolate at the edges.
|
||||
*/
|
||||
if( vips_embed( in, &t[1],
|
||||
if( vips_embed( in, &t[2],
|
||||
VIPS_CEIL( reduceh->n_point / 2.0 ) - 1, 0,
|
||||
in->Xsize + reduceh->n_point, in->Ysize,
|
||||
"extend", VIPS_EXTEND_COPY,
|
||||
(void *) NULL ) )
|
||||
return( -1 );
|
||||
in = t[1];
|
||||
in = t[2];
|
||||
|
||||
if( vips_image_pipelinev( resample->out,
|
||||
VIPS_DEMAND_STYLE_THINSTRIP, in, (void *) NULL ) )
|
||||
return( -1 );
|
||||
|
||||
/* Size output. We need to always round to nearest, so round(), not
|
||||
* rint().
|
||||
*
|
||||
* Don't change xres/yres, leave that to the application layer. For
|
||||
/* Don't change xres/yres, leave that to the application layer. For
|
||||
* example, vipsthumbnail knows the true reduce factor (including the
|
||||
* fractional part), we just see the integer part here.
|
||||
*/
|
||||
@ -583,7 +610,7 @@ vips_reduceh_class_init( VipsReducehClass *reduceh_class )
|
||||
_( "Horizontal shrink factor" ),
|
||||
VIPS_ARGUMENT_REQUIRED_INPUT,
|
||||
G_STRUCT_OFFSET( VipsReduceh, hshrink ),
|
||||
1, 1000000, 1 );
|
||||
1.0, 1000000.0, 1.0 );
|
||||
|
||||
VIPS_ARG_ENUM( reduceh_class, "kernel", 4,
|
||||
_( "Kernel" ),
|
||||
@ -592,6 +619,13 @@ vips_reduceh_class_init( VipsReducehClass *reduceh_class )
|
||||
G_STRUCT_OFFSET( VipsReduceh, kernel ),
|
||||
VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 );
|
||||
|
||||
VIPS_ARG_DOUBLE( reduceh_class, "gap", 5,
|
||||
_( "Gap" ),
|
||||
_( "Reducing gap" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsReduceh, gap ),
|
||||
0.0, 1000000.0, 0.0 );
|
||||
|
||||
/* Old name.
|
||||
*/
|
||||
VIPS_ARG_DOUBLE( reduceh_class, "xshrink", 3,
|
||||
@ -599,7 +633,7 @@ vips_reduceh_class_init( VipsReducehClass *reduceh_class )
|
||||
_( "Horizontal shrink factor" ),
|
||||
VIPS_ARGUMENT_REQUIRED_INPUT | VIPS_ARGUMENT_DEPRECATED,
|
||||
G_STRUCT_OFFSET( VipsReduceh, hshrink ),
|
||||
1, 1000000, 1 );
|
||||
1.0, 1000000.0, 1.0 );
|
||||
|
||||
/* We used to let people pick centre or corner, but it's automatic now.
|
||||
*/
|
||||
@ -615,6 +649,7 @@ vips_reduceh_class_init( VipsReducehClass *reduceh_class )
|
||||
static void
|
||||
vips_reduceh_init( VipsReduceh *reduceh )
|
||||
{
|
||||
reduceh->gap = 0.0;
|
||||
reduceh->kernel = VIPS_KERNEL_LANCZOS3;
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,8 @@
|
||||
* - deprecate @centre option, it's now always on
|
||||
* - fix pixel shift
|
||||
* - speed up the mask construction for uchar/ushort images
|
||||
* 22/4/22 kleisauke
|
||||
* - add @gap option
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -102,7 +104,8 @@ typedef struct {
|
||||
typedef struct _VipsReducev {
|
||||
VipsResample parent_instance;
|
||||
|
||||
double vshrink; /* Shrink factor */
|
||||
double vshrink; /* Reduce factor */
|
||||
double gap; /* Reduce gap */
|
||||
|
||||
/* The thing we use to make the kernel.
|
||||
*/
|
||||
@ -734,10 +737,10 @@ vips_reducev_vector_gen( VipsRegion *out_region, void *vseq,
|
||||
}
|
||||
|
||||
static int
|
||||
vips_reducev_raw( VipsReducev *reducev, VipsImage *in, VipsImage **out )
|
||||
vips_reducev_raw( VipsReducev *reducev, VipsImage *in, int height,
|
||||
VipsImage **out )
|
||||
{
|
||||
VipsObjectClass *object_class = VIPS_OBJECT_GET_CLASS( reducev );
|
||||
VipsResample *resample = VIPS_RESAMPLE( reducev );
|
||||
|
||||
VipsGenerateFn generate;
|
||||
|
||||
@ -771,15 +774,11 @@ vips_reducev_raw( VipsReducev *reducev, VipsImage *in, VipsImage **out )
|
||||
VIPS_DEMAND_STYLE_THINSTRIP, in, (void *) NULL ) )
|
||||
return( -1 );
|
||||
|
||||
/* Size output. We need to always round to nearest, so round(), not
|
||||
* rint().
|
||||
*
|
||||
* Don't change xres/yres, leave that to the application layer. For
|
||||
/* Don't change xres/yres, leave that to the application layer. For
|
||||
* example, vipsthumbnail knows the true reduce factor (including the
|
||||
* fractional part), we just see the integer part here.
|
||||
*/
|
||||
(*out)->Ysize = VIPS_ROUND_UINT(
|
||||
resample->in->Ysize / reducev->vshrink );
|
||||
(*out)->Ysize = height;
|
||||
if( (*out)->Ysize <= 0 ) {
|
||||
vips_error( object_class->nickname,
|
||||
"%s", _( "image has shrunk to nothing" ) );
|
||||
@ -808,23 +807,62 @@ vips_reducev_build( VipsObject *object )
|
||||
VipsObjectClass *object_class = VIPS_OBJECT_GET_CLASS( object );
|
||||
VipsResample *resample = VIPS_RESAMPLE( object );
|
||||
VipsReducev *reducev = (VipsReducev *) object;
|
||||
VipsImage **t = (VipsImage **) vips_object_local_array( object, 4 );
|
||||
VipsImage **t = (VipsImage **) vips_object_local_array( object, 5 );
|
||||
|
||||
VipsImage *in;
|
||||
double height, extra_pixels;
|
||||
int height;
|
||||
int int_vshrink;
|
||||
double extra_pixels;
|
||||
|
||||
if( VIPS_OBJECT_CLASS( vips_reducev_parent_class )->build( object ) )
|
||||
return( -1 );
|
||||
|
||||
in = resample->in;
|
||||
|
||||
if( reducev->vshrink < 1 ) {
|
||||
if( reducev->vshrink < 1.0 ) {
|
||||
vips_error( object_class->nickname,
|
||||
"%s", _( "reduce factor should be >= 1" ) );
|
||||
"%s", _( "reduce factor should be >= 1.0" ) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
if( reducev->vshrink == 1 )
|
||||
/* Output size. We need to always round to nearest, so round(), not
|
||||
* rint().
|
||||
*/
|
||||
height = VIPS_ROUND_UINT(
|
||||
(double) in->Ysize / reducev->vshrink );
|
||||
|
||||
/* How many pixels we are inventing in the input, -ve for
|
||||
* discarding.
|
||||
*/
|
||||
extra_pixels = height * reducev->vshrink - in->Ysize;
|
||||
|
||||
if( reducev->gap > 0.0 &&
|
||||
reducev->kernel != VIPS_KERNEL_NEAREST ) {
|
||||
if( reducev->gap < 1.0 ) {
|
||||
vips_error( object_class->nickname,
|
||||
"%s", _( "reduce gap should be >= 1.0" ) );
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
/* The int part of our reduce.
|
||||
*/
|
||||
int_vshrink = VIPS_MAX( 1,
|
||||
VIPS_FLOOR( (double) in->Ysize / height / reducev->gap ) );
|
||||
|
||||
if( int_vshrink > 1 ) {
|
||||
g_info( "shrinkv by %d", int_vshrink );
|
||||
if( vips_shrinkv( in, &t[0], int_vshrink,
|
||||
"ceil", TRUE,
|
||||
NULL ) )
|
||||
return( -1 );
|
||||
in = t[0];
|
||||
|
||||
reducev->vshrink /= int_vshrink;
|
||||
extra_pixels /= int_vshrink;
|
||||
}
|
||||
}
|
||||
|
||||
if( reducev->vshrink == 1.0 )
|
||||
return( vips_image_write( in, resample->out ) );
|
||||
|
||||
reducev->n_point =
|
||||
@ -836,18 +874,6 @@ vips_reducev_build( VipsObject *object )
|
||||
return( -1 );
|
||||
}
|
||||
|
||||
/* Output size. We need to always round to nearest, so round(), not
|
||||
* rint().
|
||||
*/
|
||||
height = VIPS_ROUND_UINT(
|
||||
(double) resample->in->Ysize / reducev->vshrink );
|
||||
|
||||
/* How many pixels we are inventing in the input, -ve for
|
||||
* discarding.
|
||||
*/
|
||||
extra_pixels =
|
||||
height * reducev->vshrink - resample->in->Ysize;
|
||||
|
||||
/* If we are rounding down, we are not using some input
|
||||
* pixels. We need to move the origin *inside* the input image
|
||||
* by half that distance so that we discard pixels equally
|
||||
@ -884,24 +910,24 @@ vips_reducev_build( VipsObject *object )
|
||||
|
||||
/* Unpack for processing.
|
||||
*/
|
||||
if( vips_image_decode( in, &t[0] ) )
|
||||
if( vips_image_decode( in, &t[1] ) )
|
||||
return( -1 );
|
||||
in = t[0];
|
||||
in = t[1];
|
||||
|
||||
/* Add new pixels around the input so we can interpolate at the edges.
|
||||
*/
|
||||
if( vips_embed( in, &t[1],
|
||||
if( vips_embed( in, &t[2],
|
||||
0, VIPS_CEIL( reducev->n_point / 2.0 ) - 1,
|
||||
in->Xsize, in->Ysize + reducev->n_point,
|
||||
"extend", VIPS_EXTEND_COPY,
|
||||
(void *) NULL ) )
|
||||
return( -1 );
|
||||
in = t[1];
|
||||
|
||||
if( vips_reducev_raw( reducev, in, &t[2] ) )
|
||||
return( -1 );
|
||||
in = t[2];
|
||||
|
||||
if( vips_reducev_raw( reducev, in, height, &t[3] ) )
|
||||
return( -1 );
|
||||
in = t[3];
|
||||
|
||||
/* Large reducev will throw off sequential mode. Suppose thread1 is
|
||||
* generating tile (0, 0), but stalls. thread2 generates tile
|
||||
* (0, 1), 128 lines further down the output. After it has done,
|
||||
@ -915,12 +941,12 @@ vips_reducev_build( VipsObject *object )
|
||||
if( vips_image_get_typeof( in, VIPS_META_SEQUENTIAL ) ) {
|
||||
g_info( "reducev sequential line cache" );
|
||||
|
||||
if( vips_sequential( in, &t[3],
|
||||
if( vips_sequential( in, &t[4],
|
||||
"tile_height", 10,
|
||||
// "trace", TRUE,
|
||||
(void *) NULL ) )
|
||||
return( -1 );
|
||||
in = t[3];
|
||||
in = t[4];
|
||||
}
|
||||
|
||||
if( vips_image_write( in, resample->out ) )
|
||||
@ -954,7 +980,7 @@ vips_reducev_class_init( VipsReducevClass *reducev_class )
|
||||
_( "Vertical shrink factor" ),
|
||||
VIPS_ARGUMENT_REQUIRED_INPUT,
|
||||
G_STRUCT_OFFSET( VipsReducev, vshrink ),
|
||||
1, 1000000, 1 );
|
||||
1.0, 1000000.0, 1.0 );
|
||||
|
||||
VIPS_ARG_ENUM( reducev_class, "kernel", 4,
|
||||
_( "Kernel" ),
|
||||
@ -963,6 +989,13 @@ vips_reducev_class_init( VipsReducevClass *reducev_class )
|
||||
G_STRUCT_OFFSET( VipsReducev, kernel ),
|
||||
VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 );
|
||||
|
||||
VIPS_ARG_DOUBLE( reducev_class, "gap", 5,
|
||||
_( "Gap" ),
|
||||
_( "Reducing gap" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsReducev, gap ),
|
||||
0.0, 1000000.0, 0.0 );
|
||||
|
||||
/* Old name.
|
||||
*/
|
||||
VIPS_ARG_DOUBLE( reducev_class, "yshrink", 3,
|
||||
@ -970,7 +1003,7 @@ vips_reducev_class_init( VipsReducevClass *reducev_class )
|
||||
_( "Vertical shrink factor" ),
|
||||
VIPS_ARGUMENT_REQUIRED_INPUT | VIPS_ARGUMENT_DEPRECATED,
|
||||
G_STRUCT_OFFSET( VipsReducev, vshrink ),
|
||||
1, 1000000, 1 );
|
||||
1.0, 1000000.0, 1.0 );
|
||||
|
||||
/* We used to let people pick centre or corner, but it's automatic now.
|
||||
*/
|
||||
@ -986,6 +1019,7 @@ vips_reducev_class_init( VipsReducevClass *reducev_class )
|
||||
static void
|
||||
vips_reducev_init( VipsReducev *reducev )
|
||||
{
|
||||
reducev->gap = 0.0;
|
||||
reducev->kernel = VIPS_KERNEL_LANCZOS3;
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,8 @@
|
||||
* - don't let either axis drop below 1px
|
||||
* 12/7/20
|
||||
* - much better handling of "nearest"
|
||||
* 22/4/22 kleisauke
|
||||
* - add @gap option
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -92,6 +94,7 @@ typedef struct _VipsResize {
|
||||
|
||||
double scale;
|
||||
double vscale;
|
||||
double gap;
|
||||
VipsKernel kernel;
|
||||
|
||||
/* Deprecated.
|
||||
@ -134,7 +137,7 @@ vips_resize_build( VipsObject *object )
|
||||
VipsResample *resample = VIPS_RESAMPLE( object );
|
||||
VipsResize *resize = (VipsResize *) object;
|
||||
|
||||
VipsImage **t = (VipsImage **) vips_object_local_array( object, 7 );
|
||||
VipsImage **t = (VipsImage **) vips_object_local_array( object, 5 );
|
||||
|
||||
VipsImage *in;
|
||||
double hscale;
|
||||
@ -155,48 +158,46 @@ vips_resize_build( VipsObject *object )
|
||||
else
|
||||
vscale = resize->scale;
|
||||
|
||||
/* The int part of our scale. Leave the final 200 - 300% to reduce.
|
||||
*/
|
||||
int_hshrink = VIPS_MAX( 1, VIPS_FLOOR( 1.0 / (hscale * 2) ) );
|
||||
int_vshrink = VIPS_MAX( 1, VIPS_FLOOR( 1.0 / (vscale * 2) ) );
|
||||
|
||||
/* Unpack for processing.
|
||||
*/
|
||||
if( vips_image_decode( in, &t[5] ) )
|
||||
if( vips_image_decode( in, &t[0] ) )
|
||||
return( -1 );
|
||||
in = t[5];
|
||||
in = t[0];
|
||||
|
||||
if( resize->kernel == VIPS_KERNEL_NEAREST ) {
|
||||
int target_width;
|
||||
int target_height;
|
||||
|
||||
/* The int part of our scale.
|
||||
*/
|
||||
if( resize->gap < 1.0 ) {
|
||||
int_hshrink = VIPS_FLOOR( 1.0 / hscale );
|
||||
int_vshrink = VIPS_FLOOR( 1.0 / vscale );
|
||||
}
|
||||
else {
|
||||
target_width = VIPS_ROUND_UINT( in->Xsize * hscale );
|
||||
target_height = VIPS_ROUND_UINT( in->Ysize * vscale );
|
||||
|
||||
int_hshrink =
|
||||
VIPS_FLOOR( (double) in->Xsize / target_width / resize->gap );
|
||||
int_vshrink =
|
||||
VIPS_FLOOR( (double) in->Ysize / target_height / resize->gap );
|
||||
}
|
||||
|
||||
int_hshrink = VIPS_MAX( 1, int_hshrink );
|
||||
int_vshrink = VIPS_MAX( 1, int_vshrink );
|
||||
|
||||
if( int_vshrink > 1 ||
|
||||
int_hshrink > 1 ) {
|
||||
g_info( "subsample by %d, %d",
|
||||
int_hshrink, int_vshrink );
|
||||
if( vips_subsample( in, &t[0],
|
||||
if( vips_subsample( in, &t[1],
|
||||
int_hshrink, int_vshrink, NULL ) )
|
||||
return( -1 );
|
||||
in = t[0];
|
||||
|
||||
hscale *= int_hshrink;
|
||||
vscale *= int_vshrink;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if( int_vshrink > 1 ) {
|
||||
g_info( "shrinkv by %d", int_vshrink );
|
||||
if( vips_shrinkv( in, &t[0], int_vshrink, NULL ) )
|
||||
return( -1 );
|
||||
in = t[0];
|
||||
|
||||
vscale *= int_vshrink;
|
||||
}
|
||||
|
||||
if( int_hshrink > 1 ) {
|
||||
g_info( "shrinkh by %d", int_hshrink );
|
||||
if( vips_shrinkh( in, &t[1], int_hshrink, NULL ) )
|
||||
return( -1 );
|
||||
in = t[1];
|
||||
|
||||
hscale *= int_hshrink;
|
||||
vscale *= int_vshrink;
|
||||
}
|
||||
}
|
||||
|
||||
@ -211,16 +212,17 @@ vips_resize_build( VipsObject *object )
|
||||
g_info( "residual reducev by %g", vscale );
|
||||
if( vips_reducev( in, &t[2], 1.0 / vscale,
|
||||
"kernel", resize->kernel,
|
||||
"gap", resize->gap,
|
||||
NULL ) )
|
||||
return( -1 );
|
||||
in = t[2];
|
||||
}
|
||||
|
||||
if( hscale < 1.0 ) {
|
||||
g_info( "residual reduceh by %g",
|
||||
hscale );
|
||||
g_info( "residual reduceh by %g", hscale );
|
||||
if( vips_reduceh( in, &t[3], 1.0 / hscale,
|
||||
"kernel", resize->kernel,
|
||||
"gap", resize->gap,
|
||||
NULL ) )
|
||||
return( -1 );
|
||||
in = t[3];
|
||||
@ -326,14 +328,14 @@ vips_resize_class_init( VipsResizeClass *class )
|
||||
_( "Scale image by this factor" ),
|
||||
VIPS_ARGUMENT_REQUIRED_INPUT,
|
||||
G_STRUCT_OFFSET( VipsResize, scale ),
|
||||
0, 10000000, 0 );
|
||||
0.0, 10000000.0, 0.0 );
|
||||
|
||||
VIPS_ARG_DOUBLE( class, "vscale", 113,
|
||||
_( "Vertical scale factor" ),
|
||||
_( "Vertical scale image by this factor" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsResize, vscale ),
|
||||
0, 10000000, 0 );
|
||||
0.0, 10000000.0, 0.0 );
|
||||
|
||||
VIPS_ARG_ENUM( class, "kernel", 3,
|
||||
_( "Kernel" ),
|
||||
@ -342,6 +344,13 @@ vips_resize_class_init( VipsResizeClass *class )
|
||||
G_STRUCT_OFFSET( VipsResize, kernel ),
|
||||
VIPS_TYPE_KERNEL, VIPS_KERNEL_LANCZOS3 );
|
||||
|
||||
VIPS_ARG_DOUBLE( class, "gap", 4,
|
||||
_( "Gap" ),
|
||||
_( "Reducing gap" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsResize, gap ),
|
||||
0.0, 1000000.0, 2.0 );
|
||||
|
||||
/* We used to let people set the input offset so you could pick centre
|
||||
* or corner interpolation, but it's not clear this was useful.
|
||||
*/
|
||||
@ -350,14 +359,14 @@ vips_resize_class_init( VipsResizeClass *class )
|
||||
_( "Horizontal input displacement" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
|
||||
G_STRUCT_OFFSET( VipsResize, idx ),
|
||||
-10000000, 10000000, 0 );
|
||||
-10000000.0, 10000000.0, 0.0 );
|
||||
|
||||
VIPS_ARG_DOUBLE( class, "idy", 116,
|
||||
_( "Input offset" ),
|
||||
_( "Vertical input displacement" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
|
||||
G_STRUCT_OFFSET( VipsResize, idy ),
|
||||
-10000000, 10000000, 0 );
|
||||
-10000000.0, 10000000.0, 0.0 );
|
||||
|
||||
/* It's a kernel now we use vips_reduce() not vips_affine().
|
||||
*/
|
||||
@ -381,6 +390,7 @@ vips_resize_class_init( VipsResizeClass *class )
|
||||
static void
|
||||
vips_resize_init( VipsResize *resize )
|
||||
{
|
||||
resize->gap = 2.0;
|
||||
resize->kernel = VIPS_KERNEL_LANCZOS3;
|
||||
}
|
||||
|
||||
@ -395,18 +405,18 @@ vips_resize_init( VipsResize *resize )
|
||||
*
|
||||
* * @vscale: %gdouble vertical scale factor
|
||||
* * @kernel: #VipsKernel to reduce with
|
||||
* * @gap: reducing gap to use (default: 2.0)
|
||||
*
|
||||
* Resize an image.
|
||||
*
|
||||
* When downsizing, the
|
||||
* image is block-shrunk with vips_shrink(),
|
||||
* then the image is shrunk again to the
|
||||
* target size with vips_reduce(). How much is done by vips_shrink() vs.
|
||||
* vips_reduce() varies with the @kernel setting. Downsizing is done with
|
||||
* centre convention.
|
||||
* Set @gap to speed up downsizing by having vips_shrink() to shrink
|
||||
* with a box filter first. The bigger @gap, the closer the result
|
||||
* to the fair resampling. The smaller @gap, the faster resizing.
|
||||
* The default value is 2.0 (very close to fair resampling
|
||||
* while still being faster in many cases).
|
||||
*
|
||||
* vips_resize() normally uses #VIPS_KERNEL_LANCZOS3 for the final reduce, you
|
||||
* can change this with @kernel.
|
||||
* can change this with @kernel. Downsizing is done with centre convention.
|
||||
*
|
||||
* When upsizing (@scale > 1), the operation uses vips_affine() with
|
||||
* a #VipsInterpolate selected depending on @kernel. It will use
|
||||
|
@ -10,6 +10,8 @@
|
||||
* 9/2/17
|
||||
* - use reduce, not affine, for any residual shrink
|
||||
* - expand cache hint
|
||||
* 22/4/22 kleisauke
|
||||
* - add @ceil option
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -63,6 +65,7 @@ typedef struct _VipsShrink {
|
||||
|
||||
double hshrink; /* Shrink factors */
|
||||
double vshrink;
|
||||
gboolean ceil; /* Round operation */
|
||||
|
||||
} VipsShrink;
|
||||
|
||||
@ -76,7 +79,7 @@ vips_shrink_build( VipsObject *object )
|
||||
VipsResample *resample = VIPS_RESAMPLE( object );
|
||||
VipsShrink *shrink = (VipsShrink *) object;
|
||||
VipsImage **t = (VipsImage **)
|
||||
vips_object_local_array( object, 3 );
|
||||
vips_object_local_array( object, 2 );
|
||||
|
||||
int hshrink_int;
|
||||
int vshrink_int;
|
||||
@ -91,20 +94,22 @@ vips_shrink_build( VipsObject *object )
|
||||
vshrink_int != shrink->vshrink ) {
|
||||
/* Shrink by int factors, reduce to final size.
|
||||
*/
|
||||
double xresidual = shrink->hshrink / hshrink_int;
|
||||
double yresidual = shrink->vshrink / vshrink_int;
|
||||
|
||||
if( vips_shrinkv( resample->in, &t[0], vshrink_int, NULL ) ||
|
||||
vips_shrinkh( t[0], &t[1], hshrink_int, NULL ) )
|
||||
return( -1 );
|
||||
|
||||
if( vips_reduce( t[1], &t[2], xresidual, yresidual, NULL ) ||
|
||||
vips_image_write( t[2], resample->out ) )
|
||||
if( vips_reducev( resample->in, &t[0], shrink->vshrink,
|
||||
"gap", 1.0,
|
||||
NULL ) ||
|
||||
vips_reduceh( t[0], &t[1], shrink->hshrink,
|
||||
"gap", 1.0,
|
||||
NULL ) ||
|
||||
vips_image_write( t[1], resample->out ) )
|
||||
return( -1 );
|
||||
}
|
||||
else {
|
||||
if( vips_shrinkv( resample->in, &t[0], shrink->vshrink, NULL ) ||
|
||||
vips_shrinkh( t[0], &t[1], shrink->hshrink, NULL ) ||
|
||||
if( vips_shrinkv( resample->in, &t[0], shrink->vshrink,
|
||||
"ceil", shrink->ceil,
|
||||
NULL ) ||
|
||||
vips_shrinkh( t[0], &t[1], shrink->hshrink,
|
||||
"ceil", shrink->ceil,
|
||||
NULL ) ||
|
||||
vips_image_write( t[1], resample->out ) )
|
||||
return( -1 );
|
||||
}
|
||||
@ -147,6 +152,13 @@ vips_shrink_class_init( VipsShrinkClass *class )
|
||||
G_STRUCT_OFFSET( VipsShrink, hshrink ),
|
||||
1.0, 1000000.0, 1.0 );
|
||||
|
||||
VIPS_ARG_BOOL( class, "ceil", 10,
|
||||
_( "Ceil" ),
|
||||
_( "Round-up output dimensions" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsShrink, ceil ),
|
||||
FALSE );
|
||||
|
||||
/* The old names .. now use h and v everywhere.
|
||||
*/
|
||||
VIPS_ARG_DOUBLE( class, "xshrink", 8,
|
||||
@ -178,6 +190,10 @@ vips_shrink_init( VipsShrink *shrink )
|
||||
* @vshrink: vertical shrink
|
||||
* @...: %NULL-terminated list of optional named arguments
|
||||
*
|
||||
* Optional arguments:
|
||||
*
|
||||
* * @ceil: round-up output dimensions
|
||||
*
|
||||
* Shrink @in by a pair of factors with a simple box filter. For non-integer
|
||||
* factors, vips_shrink() will first shrink by the integer part with a box
|
||||
* filter, then use vips_reduce() to shrink by the
|
||||
|
@ -8,6 +8,8 @@
|
||||
* - rename xshrink -> hshrink for greater consistency
|
||||
* 6/8/19
|
||||
* - use a double sum buffer for int32 types
|
||||
* 22/4/22 kleisauke
|
||||
* - add @ceil option
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -60,6 +62,7 @@ typedef struct _VipsShrinkh {
|
||||
VipsResample parent_instance;
|
||||
|
||||
int hshrink; /* Shrink factor */
|
||||
gboolean ceil; /* Round operation */
|
||||
|
||||
} VipsShrinkh;
|
||||
|
||||
@ -267,15 +270,15 @@ vips_shrinkh_build( VipsObject *object )
|
||||
VIPS_DEMAND_STYLE_THINSTRIP, in, NULL ) )
|
||||
return( -1 );
|
||||
|
||||
/* Size output. We need to always round to nearest, so round(), not
|
||||
* rint().
|
||||
/* Size output.
|
||||
*
|
||||
* Don't change xres/yres, leave that to the application layer. For
|
||||
* example, vipsthumbnail knows the true shrink factor (including the
|
||||
* fractional part), we just see the integer part here.
|
||||
*/
|
||||
resample->out->Xsize = VIPS_ROUND_UINT(
|
||||
(double) resample->in->Xsize / shrink->hshrink );
|
||||
resample->out->Xsize = shrink->ceil ?
|
||||
VIPS_CEIL( (double) resample->in->Xsize / shrink->hshrink ) :
|
||||
VIPS_ROUND_UINT( (double) resample->in->Xsize / shrink->hshrink );
|
||||
if( resample->out->Xsize <= 0 ) {
|
||||
vips_error( class->nickname,
|
||||
"%s", _( "image has shrunk to nothing" ) );
|
||||
@ -321,6 +324,13 @@ vips_shrinkh_class_init( VipsShrinkhClass *class )
|
||||
G_STRUCT_OFFSET( VipsShrinkh, hshrink ),
|
||||
1, 1000000, 1 );
|
||||
|
||||
VIPS_ARG_BOOL( class, "ceil", 10,
|
||||
_( "Ceil" ),
|
||||
_( "Round-up output dimensions" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsShrinkh, ceil ),
|
||||
FALSE );
|
||||
|
||||
/* The old name .. now use h and v everywhere.
|
||||
*/
|
||||
VIPS_ARG_INT( class, "xshrink", 8,
|
||||
@ -344,6 +354,10 @@ vips_shrinkh_init( VipsShrinkh *shrink )
|
||||
* @hshrink: horizontal shrink
|
||||
* @...: %NULL-terminated list of optional named arguments
|
||||
*
|
||||
* Optional arguments:
|
||||
*
|
||||
* * @ceil: round-up output dimensions
|
||||
*
|
||||
* Shrink @in horizontally by an integer factor. Each pixel in the output is
|
||||
* the average of the corresponding line of @hshrink pixels in the input.
|
||||
*
|
||||
|
@ -47,6 +47,8 @@
|
||||
* - add a seq line cache
|
||||
* 6/8/19
|
||||
* - use a double sum buffer for int32 types
|
||||
* 22/4/22 kleisauke
|
||||
* - add @ceil option
|
||||
*/
|
||||
|
||||
/*
|
||||
@ -99,8 +101,9 @@
|
||||
typedef struct _VipsShrinkv {
|
||||
VipsResample parent_instance;
|
||||
|
||||
int vshrink;
|
||||
int vshrink; /* Shrink factor */
|
||||
size_t sizeof_line_buffer;
|
||||
gboolean ceil; /* Round operation */
|
||||
|
||||
} VipsShrinkv;
|
||||
|
||||
@ -372,15 +375,15 @@ vips_shrinkv_build( VipsObject *object )
|
||||
VIPS_DEMAND_STYLE_SMALLTILE, in, NULL ) )
|
||||
return( -1 );
|
||||
|
||||
/* Size output. We need to always round to nearest, so round(), not
|
||||
* rint().
|
||||
/* Size output.
|
||||
*
|
||||
* Don't change xres/yres, leave that to the application layer. For
|
||||
* example, vipsthumbnail knows the true shrink factor (including the
|
||||
* fractional part), we just see the integer part here.
|
||||
*/
|
||||
t[2]->Ysize = VIPS_ROUND_UINT(
|
||||
(double) resample->in->Ysize / shrink->vshrink );
|
||||
t[2]->Ysize = shrink->ceil ?
|
||||
VIPS_CEIL( (double) resample->in->Ysize / shrink->vshrink ) :
|
||||
VIPS_ROUND_UINT( (double) resample->in->Ysize / shrink->vshrink );
|
||||
if( t[2]->Ysize <= 0 ) {
|
||||
vips_error( class->nickname,
|
||||
"%s", _( "image has shrunk to nothing" ) );
|
||||
@ -451,9 +454,16 @@ vips_shrinkv_class_init( VipsShrinkvClass *class )
|
||||
G_STRUCT_OFFSET( VipsShrinkv, vshrink ),
|
||||
1, 1000000, 1 );
|
||||
|
||||
VIPS_ARG_BOOL( class, "ceil", 10,
|
||||
_( "Ceil" ),
|
||||
_( "Round-up output dimensions" ),
|
||||
VIPS_ARGUMENT_OPTIONAL_INPUT,
|
||||
G_STRUCT_OFFSET( VipsShrinkv, ceil ),
|
||||
FALSE );
|
||||
|
||||
/* The old name .. now use h and v everywhere.
|
||||
*/
|
||||
VIPS_ARG_INT( class, "yshrink", 9,
|
||||
VIPS_ARG_INT( class, "yshrink", 8,
|
||||
_( "Yshrink" ),
|
||||
_( "Vertical shrink factor" ),
|
||||
VIPS_ARGUMENT_REQUIRED_INPUT | VIPS_ARGUMENT_DEPRECATED,
|
||||
@ -474,6 +484,10 @@ vips_shrinkv_init( VipsShrinkv *shrink )
|
||||
* @vshrink: vertical shrink
|
||||
* @...: %NULL-terminated list of optional named arguments
|
||||
*
|
||||
* Optional arguments:
|
||||
*
|
||||
* * @ceil: round-up output dimensions
|
||||
*
|
||||
* Shrink @in vertically by an integer factor. Each pixel in the output is
|
||||
* the average of the corresponding column of @vshrink pixels in the input.
|
||||
*
|
||||
|
@ -115,6 +115,27 @@ class TestResample:
|
||||
assert x.width == 50
|
||||
assert x.height == 1
|
||||
|
||||
# test whether we use double-precision calculations in reduce{h,v}
|
||||
im = pyvips.Image.black(1600, 1000)
|
||||
x = im.resize(10.0 / im.width)
|
||||
assert x.width == 10
|
||||
assert x.height == 6
|
||||
|
||||
# test round-up option of shrink
|
||||
im = pyvips.Image.black(2049 - 2, 2047 - 2, bands=3)
|
||||
im = im.embed(1, 1, 2049, 2047,
|
||||
extend=pyvips.Extend.BACKGROUND,
|
||||
background=[255, 0, 0])
|
||||
for scale in [8, 9.4, 16]:
|
||||
x = im.resize(1 / scale, vscale=1 / scale)
|
||||
|
||||
for point in ([(round(x.width / 2), 0),
|
||||
(x.width - 1, round(x.height / 2)),
|
||||
(round(x.width / 2), x.height - 1),
|
||||
(0, round(x.height / 2))]):
|
||||
y = x(*point)[0]
|
||||
assert y != 0
|
||||
|
||||
def test_shrink(self):
|
||||
im = pyvips.Image.new_from_file(JPEG_FILE)
|
||||
im2 = im.shrink(4, 4)
|
||||
|
Loading…
Reference in New Issue
Block a user