Merge branch 'add-force-size'

This commit is contained in:
John Cupitt 2017-05-10 17:08:32 +01:00
commit a10787baa3
11 changed files with 142 additions and 60 deletions

View File

@ -5,6 +5,7 @@
- add new_from_image() to Python as well
- slight change to cpp new_from_image() to match py/C behaviour
- vips_conv(), vips_compass(), vips_convsep() default to FLOAT precision
- add FORCE resize mode to break aspect ratio
23/4/17 started 8.5.5
- doc polishing

2
TODO
View File

@ -1,6 +1,6 @@
- vips_compass() needs docs
- cpp bandjoin should use bandjoin_const() where possibel ... currently
- cpp bandjoin should use bandjoin_const() where possible ... currently
uses new_from_image
- not sure about utf8 error messages on win

View File

@ -102,7 +102,7 @@ example:
$ vips shrink fred.png jim.png 10 10
```
meaning shrink `fred.png` by a factor of 10 in both axies and write as
meaning shrink `fred.png` by a factor of 10 in both axes and write as
`jim.png`.
You can imagine this operation running without needing `fred.png` to be

View File

@ -51,6 +51,7 @@ typedef enum {
VIPS_SIZE_BOTH,
VIPS_SIZE_UP,
VIPS_SIZE_DOWN,
VIPS_SIZE_FORCE,
VIPS_SIZE_LAST
} VipsSize;

View File

@ -831,6 +831,7 @@ vips_size_get_type( void )
{VIPS_SIZE_BOTH, "VIPS_SIZE_BOTH", "both"},
{VIPS_SIZE_UP, "VIPS_SIZE_UP", "up"},
{VIPS_SIZE_DOWN, "VIPS_SIZE_DOWN", "down"},
{VIPS_SIZE_FORCE, "VIPS_SIZE_FORCE", "force"},
{VIPS_SIZE_LAST, "VIPS_SIZE_LAST", "last"},
{0, NULL, NULL}
};

View File

@ -87,9 +87,10 @@
* @VIPS_SIZE_BOTH: size both up and down
* @VIPS_SIZE_UP: only upsize
* @VIPS_SIZE_DOWN: only downsize
* @VIPS_SIZE_FORCE: force size, that is, break aspect ratio
*
* Controls whether an operation should upsize, downsize, or both up and
* downsize.
* Controls whether an operation should upsize, downsize, both up and
* downsize, or force a size.
*
* See also: vips_thumbnail().
*/

View File

@ -5,6 +5,8 @@
* - from vipsthumbnail.c
* 6/1/17
* - add @size parameter
* 4/5/17
* - add FORCE
*/
/*
@ -135,21 +137,27 @@ vips_thumbnail_finalize( GObject *gobject )
/* Calculate the shrink factor, taking into account auto-rotate, the fit mode,
* and so on.
*
* The hshrink/vshrink are the amount to shrink the input image axes by in
* order for the output axes (ie. after rotation) to match the required
* thumbnail->width, thumbnail->height and fit mode.
*/
static double
static void
vips_thumbnail_calculate_shrink( VipsThumbnail *thumbnail,
int input_width, int input_height )
int input_width, int input_height, double *hshrink, double *vshrink )
{
/* If we will be rotating, swap the target width and height.
*/
gboolean rotate =
thumbnail->angle == VIPS_ANGLE_D90 ||
thumbnail->angle == VIPS_ANGLE_D270;
int width = thumbnail->auto_rotate && rotate ?
input_height : input_width;
int height = thumbnail->auto_rotate && rotate ?
input_width : input_height;
(thumbnail->angle == VIPS_ANGLE_D90 ||
thumbnail->angle == VIPS_ANGLE_D270) &&
thumbnail->auto_rotate;
int target_width = rotate ?
thumbnail->height : thumbnail->width;
int target_height = rotate ?
thumbnail->width : thumbnail->height;
VipsDirection direction;
double shrink;
/* Calculate the horizontal and vertical shrink we'd need to fit the
* image to the bounding box, and pick the biggest.
@ -157,33 +165,53 @@ vips_thumbnail_calculate_shrink( VipsThumbnail *thumbnail,
* In crop mode, we aim to fill the bounding box, so we must use the
* smaller axis.
*/
double horizontal = (double) width / thumbnail->width;
double vertical = (double) height / thumbnail->height;
*hshrink = (double) input_width / target_width;
*vshrink = (double) input_height / target_height;
if( thumbnail->crop != VIPS_INTERESTING_NONE ) {
if( horizontal < vertical )
if( *hshrink < *vshrink )
direction = VIPS_DIRECTION_HORIZONTAL;
else
direction = VIPS_DIRECTION_VERTICAL;
}
else {
if( horizontal < vertical )
if( *hshrink < *vshrink )
direction = VIPS_DIRECTION_VERTICAL;
else
direction = VIPS_DIRECTION_HORIZONTAL;
}
shrink = direction == VIPS_DIRECTION_HORIZONTAL ?
horizontal : vertical;
if( thumbnail->size != VIPS_SIZE_FORCE ) {
if( direction == VIPS_DIRECTION_HORIZONTAL )
*vshrink = *hshrink;
else
*hshrink = *vshrink;
}
/* Restrict to only upsize, only downsize, or both.
*/
if( thumbnail->size == VIPS_SIZE_UP )
shrink = VIPS_MIN( 1, shrink );
if( thumbnail->size == VIPS_SIZE_DOWN )
shrink = VIPS_MAX( 1, shrink );
if( thumbnail->size == VIPS_SIZE_UP ) {
*hshrink = VIPS_MIN( 1, *hshrink );
*vshrink = VIPS_MIN( 1, *vshrink );
}
else if( thumbnail->size == VIPS_SIZE_DOWN ) {
*hshrink = VIPS_MAX( 1, *hshrink );
*vshrink = VIPS_MAX( 1, *vshrink );
}
}
return( shrink );
/* Just the common part of the shrink: the bit by which both axes must be
* shrunk.
*/
static double
vips_thumbnail_calculate_common_shrink( VipsThumbnail *thumbnail,
int width, int height )
{
double hshrink;
double vshrink;
vips_thumbnail_calculate_shrink( thumbnail, width, height,
&hshrink, &vshrink );
return( VIPS_MIN( hshrink, vshrink ) );
}
/* Find the best jpeg preload shrink.
@ -191,8 +219,8 @@ vips_thumbnail_calculate_shrink( VipsThumbnail *thumbnail,
static int
vips_thumbnail_find_jpegshrink( VipsThumbnail *thumbnail, int width, int height )
{
double shrink =
vips_thumbnail_calculate_shrink( thumbnail, width, height );
double shrink = vips_thumbnail_calculate_common_shrink( thumbnail,
width, height );
/* We can't use pre-shrunk images in linear mode. libjpeg shrinks in Y
* (of YCbCR), not linear space.
@ -228,7 +256,7 @@ vips_thumbnail_open( VipsThumbnail *thumbnail )
VipsThumbnailClass *class = VIPS_THUMBNAIL_GET_CLASS( thumbnail );
VipsImage *im;
int shrink;
double shrink;
double scale;
if( class->get_info( thumbnail ) )
@ -237,24 +265,28 @@ vips_thumbnail_open( VipsThumbnail *thumbnail )
g_info( "input size is %d x %d",
thumbnail->input_width, thumbnail->input_height );
shrink = 1;
shrink = 1.0;
scale = 1.0;
if( vips_isprefix( "VipsForeignLoadJpeg", thumbnail->loader ) ) {
shrink = vips_thumbnail_find_jpegshrink( thumbnail,
thumbnail->input_width, thumbnail->input_height );
g_info( "loading jpeg with factor %d pre-shrink", shrink );
g_info( "loading jpeg with factor %g pre-shrink", shrink );
}
else if( vips_isprefix( "VipsForeignLoadPdf", thumbnail->loader ) ||
vips_isprefix( "VipsForeignLoadSvg", thumbnail->loader ) ) {
scale = 1.0 / vips_thumbnail_calculate_shrink( thumbnail,
thumbnail->input_width, thumbnail->input_height );
shrink = vips_thumbnail_calculate_common_shrink( thumbnail,
thumbnail->input_width, thumbnail->input_height );
scale = 1.0 / shrink;
g_info( "loading PDF/SVG with factor %g pre-scale", scale );
}
else if( vips_isprefix( "VipsForeignLoadWebp", thumbnail->loader ) ) {
shrink = vips_thumbnail_calculate_shrink( thumbnail,
thumbnail->input_width, thumbnail->input_height );
g_info( "loading webp with factor %d pre-shrink", shrink );
shrink = vips_thumbnail_calculate_common_shrink( thumbnail,
thumbnail->input_width, thumbnail->input_height );
g_info( "loading webp with factor %g pre-shrink", shrink );
}
if( !(im = class->open( thumbnail, shrink, scale )) )
@ -272,7 +304,8 @@ vips_thumbnail_build( VipsObject *object )
VIPS_INTERPRETATION_scRGB : VIPS_INTERPRETATION_sRGB;
VipsImage *in;
double shrink;
double hshrink;
double vshrink;
/* TRUE if we've done the import of an ICC transform and still need to
* export.
@ -373,12 +406,13 @@ vips_thumbnail_build( VipsObject *object )
in = t[3];
}
shrink = vips_thumbnail_calculate_shrink( thumbnail,
in->Xsize, in->Ysize );
vips_thumbnail_calculate_shrink( thumbnail,
in->Xsize, in->Ysize, &hshrink, &vshrink );
/* Use centre convention to better match imagemagick.
*/
if( vips_resize( in, &t[4], 1.0 / shrink,
if( vips_resize( in, &t[4], 1.0 / hshrink,
"vscale", 1.0 / vshrink,
"centre", TRUE,
NULL ) )
return( -1 );
@ -639,8 +673,6 @@ vips_thumbnail_file_open( VipsThumbnail *thumbnail, int shrink, double scale )
{
VipsThumbnailFile *file = (VipsThumbnailFile *) thumbnail;
/* We can't use UNBUFERRED safely on very-many-core systems.
*/
if( shrink != 1 )
return( vips_image_new_from_file( file->filename,
"access", VIPS_ACCESS_SEQUENTIAL,
@ -697,7 +729,7 @@ vips_thumbnail_file_init( VipsThumbnailFile *file )
* Optional arguments:
*
* * @height: %gint, target height in pixels
* * @size: #VipsSize, upsize, downsize or both
* * @size: #VipsSize, upsize, downsize, both or force
* * @auto_rotate: %gboolean, rotate upright using orientation tag
* * @crop: #VipsInteresting, shrink and crop to fill target
* * @linear: %gboolean, perform shrink in linear light
@ -719,19 +751,22 @@ vips_thumbnail_file_init( VipsThumbnailFile *file )
* @height rectangle, with any excess cropped away. See vips_smartcrop() for
* details on the cropping strategy.
*
* Normally the operation will upsize or downsize as required. If @size is set
* Normally the operation will upsize or downsize as required to fit the image
* inside or outside the target size. If @size is set
* to #VIPS_SIZE_UP, the operation will only upsize and will just
* copy if asked to downsize.
* If @size is set
* to #VIPS_SIZE_DOWN, the operation will only downsize and will just
* copy if asked to upsize.
* If @size is #VIPS_SIZE_FORCE, the image aspect ratio will be broken and the
* image will be forced to fit the target.
*
* Normally any orientation tags on the input image (such as EXIF tags) are
* interpreted to rotate the image upright. If you set @auto_rotate to %FALSE,
* these tags will not be interpreted.
*
* Shrinking is normally done in sRGB colourspace. Set @linear to shrink in
* linear light colourspace instead --- this can give better results, but can
* linear light colourspace instead. This can give better results, but can
* also be far slower, since tricks like JPEG shrink-on-load cannot be used in
* linear space.
*
@ -866,7 +901,7 @@ vips_thumbnail_buffer_init( VipsThumbnailBuffer *buffer )
* Optional arguments:
*
* * @height: %gint, target height in pixels
* * @size: #VipsSize, upsize, downsize or both
* * @size: #VipsSize, upsize, downsize, both or force
* * @auto_rotate: %gboolean, rotate upright using orientation tag
* * @crop: #VipsInteresting, shrink and crop to fill target
* * @linear: %gboolean, perform shrink in linear light

View File

@ -94,7 +94,7 @@ option.
.TP
.B -c, --crop
Crop the output image down. The image is shrunk so as to completely fill the
bounding box in both axies, then any excess is cropped off.
bounding box in both axes, then any excess is cropped off.
.TP
.B -d, --delete

BIN
test/images/Landscape_6.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

View File

@ -107,6 +107,7 @@ class TestResample(unittest.TestCase):
def setUp(self):
self.jpeg_file = "images/йцук.jpg"
self.rotated_jpeg_file = "images/Landscape_6.jpg"
def test_affine(self):
im = Vips.Image.new_from_file(self.jpeg_file)
@ -216,12 +217,38 @@ class TestResample(unittest.TestCase):
self.assertNotEqual(im.width, 300)
self.assertEqual(im.height, 100)
# with @crop, should fit both width and height
im = Vips.Image.thumbnail(self.jpeg_file, 100,
height = 300, crop = True)
# force should fit width and height ... although this jpg has an
# orientation tag, we ignore it unless autorot is on
im = Vips.Image.thumbnail(self.rotated_jpeg_file, 100, height = 300,
size = "force")
self.assertEqual(im.width, 100)
self.assertEqual(im.height, 300)
# with force + autorot, we spin the image, but the output size should
# not change
im = Vips.Image.thumbnail(self.rotated_jpeg_file, 100, height = 300,
size = "force", auto_rotate = True)
self.assertEqual(im.width, 100)
self.assertEqual(im.height, 300)
# with @crop, should fit both width and height
im = Vips.Image.thumbnail(self.jpeg_file, 100,
height = 300, crop = "centre")
self.assertEqual(im.width, 100)
self.assertEqual(im.height, 300)
# with size up, should not downsize
im = Vips.Image.thumbnail(self.jpeg_file, 100, size = "up")
self.assertEqual(im.width, im_orig.width)
im = Vips.Image.thumbnail(self.jpeg_file, 10000, size = "up")
self.assertEqual(im.width, 10000)
# with size down, should not upsize
im = Vips.Image.thumbnail(self.jpeg_file, 100, size = "down")
self.assertEqual(im.width, 100)
im = Vips.Image.thumbnail(self.jpeg_file, 10000, size = "down")
self.assertEqual(im.width, im_orig.width)
im1 = Vips.Image.thumbnail(self.jpeg_file, 100)
with open(self.jpeg_file, 'rb') as f:
buf = f.read()

View File

@ -90,6 +90,8 @@
* 6/1/17
* - fancy geometry strings
* - support VipSize restrictions
* 4/5/17
* - add ! geo modifier
*/
#ifdef HAVE_CONFIG_H
@ -333,7 +335,7 @@ thumbnail_parse_geometry( const char *geometry )
p++;
}
/* Get the final < or >.
/* Get the final <>!
*/
while( isspace( *p ) )
p++;
@ -341,6 +343,8 @@ thumbnail_parse_geometry( const char *geometry )
size_restriction = VIPS_SIZE_UP;
else if( *p == '>' )
size_restriction = VIPS_SIZE_DOWN;
else if( *p == '!' )
size_restriction = VIPS_SIZE_FORCE;
else if( *p != '\0' ||
(thumbnail_width == VIPS_MAX_COORD &&
thumbnail_height == VIPS_MAX_COORD) ) {
@ -348,18 +352,30 @@ thumbnail_parse_geometry( const char *geometry )
return( -1 );
}
/* If --crop is set, both width and height must be specified,
* since we'll need a complete bounding box to fill.
/* If force is set and one of width or height isn't set, copy from the
* one that is.
*/
if( (crop_image || smartcrop_image) &&
(thumbnail_width == VIPS_MAX_COORD ||
thumbnail_height == VIPS_MAX_COORD) ) {
vips_error( "thumbnail",
"both width and height must be given if "
"crop is enabled" );
return( -1 );
if( size_restriction == VIPS_SIZE_FORCE ) {
if( thumbnail_width == VIPS_MAX_COORD )
thumbnail_width = thumbnail_height;
if( thumbnail_height == VIPS_MAX_COORD )
thumbnail_height = thumbnail_width;
}
/* If --crop is set or force is set, both width and height must be
* specified, since we'll need a complete bounding box to fill.
*/
if( crop_image ||
smartcrop_image ||
size_restriction == VIPS_SIZE_FORCE )
if( thumbnail_width == VIPS_MAX_COORD ||
thumbnail_height == VIPS_MAX_COORD ) {
vips_error( "thumbnail",
"both width and height must be given if "
"crop is enabled" );
return( -1 );
}
return( 0 );
}