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