revise autorot system

- deprecate vips_autorot_get_angle() since orientation is no longer a
simple rotate
- add vips_image_get_orientation() and vips_image_get_orientation_swap()
- revise tiff and jpeg loader autorotate to just call vips_autorot(),
but only if necessary
- revise thumbnail autorotate too
This commit is contained in:
John Cupitt 2020-06-06 17:25:46 +01:00
parent 30386db775
commit 981d5c4b16
11 changed files with 213 additions and 299 deletions

View File

@ -24,6 +24,8 @@
behaviour with alpha channels
- improve bioformats support with read and write of tiff subifd pyramids
- thumbnail exploits subifd pyramids
- handle all EXIF orientation cases, deprecate
vips_autorot_get_angle() [Elad-Laufer]
24/4/20 started 8.9.3
- better iiif tile naming [IllyaMoskvin]

View File

@ -8,6 +8,7 @@
* - don't remove orientation if it's one of the cases we don't handle
* 10/5/20
* - handle mirrored images
* - deprecate vips_autorot_get_angle()
*/
/*
@ -56,8 +57,7 @@ typedef struct _VipsAutorot {
VipsImage *in;
VipsAngle angle;
gboolean flip;
gboolean flip;
} VipsAutorot;
@ -65,51 +65,6 @@ typedef VipsConversionClass VipsAutorotClass;
G_DEFINE_TYPE( VipsAutorot, vips_autorot, VIPS_TYPE_CONVERSION );
/**
* vips_autorot_get_angle:
* @image: image to fetch orientation from
*
* Examine the metadata on @im and return the #VipsAngle to rotate by to turn
* the image upright.
*
* See also: vips_autorot().
*
* Returns: the #VipsAngle to rotate by to make the image upright.
*/
VipsAngle
vips_autorot_get_angle( VipsImage *im )
{
int orientation;
VipsAngle angle;
if( !vips_image_get_typeof( im, VIPS_META_ORIENTATION ) ||
vips_image_get_int( im, VIPS_META_ORIENTATION, &orientation ) )
orientation = 1;
switch( orientation ) {
case 6:
angle = VIPS_ANGLE_D90;
break;
case 8:
angle = VIPS_ANGLE_D270;
break;
case 3:
angle = VIPS_ANGLE_D180;
break;
default:
/* Other values do rotate + mirror, don't bother handling them
* though, how common can mirroring be.
*/
angle = VIPS_ANGLE_D0;
break;
}
return( angle );
}
static void *
vips_autorot_remove_angle_sub( VipsImage *image,
const char *field, GValue *value, void *my_data )
@ -130,14 +85,14 @@ vips_autorot_remove_angle_sub( VipsImage *image,
* vips_autorot_remove_angle: (method)
* @image: image to remove orientation from
*
* Remove the orientation tag on @image. Also remove any exif orientation tags.
*
* See also: vips_autorot_get_angle().
* Remove the orientation tag on @image. Also remove any exif orientation tags.
* You must vips_copy() the image before calling this function since it
* modifies metadata.
*/
void
vips_autorot_remove_angle( VipsImage *image )
{
(void) vips_image_remove( image, VIPS_META_ORIENTATION );
(void) vips_image_remove( image, VIPS_META_ORIENTATION );
(void) vips_image_map( image, vips_autorot_remove_angle_sub, NULL );
}
@ -151,88 +106,82 @@ vips_autorot_build( VipsObject *object )
if( VIPS_OBJECT_CLASS( vips_autorot_parent_class )->build( object ) )
return( -1 );
int orientation = 0;
VipsAngle angle;
gboolean flip = FALSE;
if( !vips_image_get_typeof( autorot->in, VIPS_META_ORIENTATION ) ||
vips_image_get_int( autorot->in, VIPS_META_ORIENTATION, &orientation ) )
orientation = 1;
VipsAngle angle;
gboolean flip;
VipsImage *in;
switch( orientation ) {
in = autorot->in;
case 2:
angle = VIPS_ANGLE_D0;
flip = TRUE;
break;
switch( vips_image_get_orientation( in ) ) {
case 2:
angle = VIPS_ANGLE_D0;
flip = TRUE;
break;
case 4:
flip = TRUE;
case 3:
angle = VIPS_ANGLE_D180;
flip = FALSE;
break;
case 3:
angle = VIPS_ANGLE_D180;
break;
case 5:
flip = TRUE;
case 4:
angle = VIPS_ANGLE_D180;
flip = TRUE;
break;
case 6:
angle = VIPS_ANGLE_D90;
break;
case 7:
flip = TRUE;
case 5:
angle = VIPS_ANGLE_D90;
flip = TRUE;
break;
case 8:
angle = VIPS_ANGLE_D270;
break;
default:
angle = VIPS_ANGLE_D0;
flip = FALSE;
break;
}
case 6:
angle = VIPS_ANGLE_D90;
flip = FALSE;
break;
g_object_set( object,
"angle", angle,
NULL );
case 7:
angle = VIPS_ANGLE_D270;
flip = TRUE;
break;
g_object_set( object,
"flip", flip,
NULL );
case 8:
angle = VIPS_ANGLE_D270;
flip = FALSE;
break;
if( angle != VIPS_ANGLE_D0 && flip) {
if( vips_rot( autorot->in, &t[0], angle, NULL ) )
return( -1 );
case 1:
default:
angle = VIPS_ANGLE_D0;
flip = FALSE;
break;
if( vips_flip( t[0], &t[1], VIPS_DIRECTION_HORIZONTAL, NULL ) )
return ( -1 );
}
if( vips_copy( t[1], &t[2], NULL ) )
return( -1 );
}
else if( angle != VIPS_ANGLE_D0 ) {
if( vips_rot( autorot->in, &t[0], angle, NULL))
return ( -1 );
g_object_set( object,
"angle", angle,
"flip", flip,
NULL );
if( vips_copy( t[0], &t[2], NULL))
return ( -1 );
}
else if( flip ) {
if( vips_flip( autorot->in, &t[0], VIPS_DIRECTION_HORIZONTAL, NULL ) )
return ( -1 );
if( angle != VIPS_ANGLE_D0 ) {
if( vips_rot( in, &t[0], angle, NULL ) )
return( -1 );
in = t[0];
}
if( vips_copy( t[0], &t[2], NULL ) )
return( -1 );
} else {
if( vips_copy( autorot->in, &t[2], NULL ) )
return( -1 );
}
if( flip ) {
if( vips_flip( in, &t[1], VIPS_DIRECTION_HORIZONTAL, NULL ) )
return( -1 );
in = t[1];
}
vips_autorot_remove_angle( t[2] );
if( vips_image_write( t[2], conversion->out ) )
/* We must copy before modifying metadata.
*/
if( vips_copy( in, &t[2], NULL ) )
return( -1 );
in = t[2];
vips_autorot_remove_angle( in );
if( vips_image_write( in, conversion->out ) )
return( -1 );
return( 0 );
@ -265,11 +214,11 @@ vips_autorot_class_init( VipsAutorotClass *class )
VIPS_TYPE_ANGLE, VIPS_ANGLE_D0 );
VIPS_ARG_BOOL( class, "flip", 7,
_( "Flip" ),
_( "Whether the image was flipped or not" ),
VIPS_ARGUMENT_OPTIONAL_OUTPUT,
G_STRUCT_OFFSET( VipsAutorot, flip ),
FALSE);
_( "Flip" ),
_( "Whether the image was flipped or not" ),
VIPS_ARGUMENT_OPTIONAL_OUTPUT,
G_STRUCT_OFFSET( VipsAutorot, flip ),
FALSE );
}
static void
@ -288,15 +237,14 @@ vips_autorot_init( VipsAutorot *autorot )
* Optional arguments:
*
* * @angle: output #VipsAngle the image was rotated by
* * @flip: output whether the image was flipped
* * @flip: output %gboolean whether the image was flipped
*
* Look at the image metadata and rotate the image to make it upright. The
* #VIPS_META_ORIENTATION tag is removed from @out to prevent accidental
* double rotation.
* Look at the image metadata and rotate and flip the image to make it
* upright. The #VIPS_META_ORIENTATION tag is removed from @out to prevent
* accidental double rotation.
*
* Read @angle to find the amount the image was rotated by.
*
* See also: vips_autorot_get_angle(), vips_autorot_remove_angle(), vips_rot().
* Read @angle to find the amount the image was rotated by. Read @flip to
* see if the image was also flipped.
*
* Returns: 0 on success, -1 on error
*/

View File

@ -801,3 +801,15 @@ vips_warn( const char *domain, const char *fmt, ... )
va_end( ap );
}
/**
* vips_autorot_get_angle:
* @image: image to fetch orientation from
*
* This function is deprecated. Use vips_autorot() instead.
*/
VipsAngle
vips_autorot_get_angle( VipsImage *im )
{
return( VIPS_ANGLE_D0 );
}

View File

@ -523,7 +523,8 @@ vips__exif_parse( VipsImage *image )
int orientation;
orientation = atoi( str );
if( orientation < 1 || orientation > 8 )
if( orientation < 1 ||
orientation > 8 )
orientation = 1;
vips_image_set_int( image, VIPS_META_ORIENTATION, orientation );
}

View File

@ -830,40 +830,6 @@ read_jpeg_generate( VipsRegion *or,
return( 0 );
}
/* Auto-rotate, if rotate_image is set.
*/
static VipsImage *
read_jpeg_rotate( VipsObject *process, VipsImage *im )
{
VipsImage **t = (VipsImage **) vips_object_local_array( process, 3 );
VipsAngle angle = vips_autorot_get_angle( im );
if( angle != VIPS_ANGLE_D0 ) {
/* Need to copy to memory or disc, we have to stay seq.
*/
const guint64 image_size = VIPS_IMAGE_SIZEOF_IMAGE( im );
const guint64 disc_threshold = vips_get_disc_threshold();
if( image_size > disc_threshold )
t[0] = vips_image_new_temp_file( "%s.v" );
else
t[0] = vips_image_new_memory();
if( vips_image_write( im, t[0] ) ||
vips_rot( t[0], &t[1], angle, NULL ) )
return( NULL );
im = t[1];
if( vips_copy( im, &t[2], NULL ) )
return( NULL );
im = t[2];
vips_autorot_remove_angle( im );
}
return( im );
}
/* Read a cinfo to a VIPS image.
*/
static int
@ -871,7 +837,7 @@ read_jpeg_image( ReadJpeg *jpeg, VipsImage *out )
{
struct jpeg_decompress_struct *cinfo = &jpeg->cinfo;
VipsImage **t = (VipsImage **)
vips_object_local_array( VIPS_OBJECT( out ), 3 );
vips_object_local_array( VIPS_OBJECT( out ), 4 );
VipsImage *im;
@ -902,10 +868,16 @@ read_jpeg_image( ReadJpeg *jpeg, VipsImage *out )
vips_extract_area( t[1], &t[2],
0, 0, jpeg->output_width, jpeg->output_height, NULL ) )
return( -1 );
im = t[2];
if( jpeg->autorotate )
im = read_jpeg_rotate( VIPS_OBJECT( out ), im );
if( jpeg->autorotate &&
vips_image_get_orientation( im ) != 1 ) {
/* This will go via a huge memory buffer :-(
*/
if( vips_autorot( im, &t[3], NULL ) )
return( -1 );
im = t[3];
}
if( vips_image_write( im, out ) )
return( -1 );
@ -948,15 +920,9 @@ vips__jpeg_read( ReadJpeg *jpeg, VipsImage *out, gboolean header_only )
/* Swap width and height if we're going to rotate this image.
*/
if( jpeg->autorotate ) {
VipsAngle angle = vips_autorot_get_angle( out );
if( angle == VIPS_ANGLE_D90 ||
angle == VIPS_ANGLE_D270 )
VIPS_SWAP( int, out->Xsize, out->Ysize );
/* We won't be returning an orientation tag.
*/
if( jpeg->autorotate &&
vips_image_get_orientation_swap( out ) ) {
VIPS_SWAP( int, out->Xsize, out->Ysize );
vips_autorot_remove_angle( out );
}
}

View File

@ -1862,59 +1862,6 @@ rtiff_seq_stop( void *seq, void *a, void *b )
return( 0 );
}
/* Auto-rotate handling.
*/
static int
rtiff_autorotate( Rtiff *rtiff, VipsImage *in, VipsImage **out )
{
VipsAngle angle = vips_autorot_get_angle( in );
if( rtiff->autorotate &&
angle != VIPS_ANGLE_D0 ) {
/* Need to copy to memory or disc, we have to stay seq.
*/
const guint64 image_size = VIPS_IMAGE_SIZEOF_IMAGE( in );
const guint64 disc_threshold = vips_get_disc_threshold();
VipsImage *im;
VipsImage *x;
if( image_size > disc_threshold )
im = vips_image_new_temp_file( "%s.v" );
else
im = vips_image_new_memory();
if( vips_image_write( in, im ) ) {
g_object_unref( im );
return( -1 );
}
if( vips_rot( im, &x, angle, NULL ) ) {
g_object_unref( im );
return( -1 );
}
g_object_unref( im );
im = x;
if( vips_copy( im, out, NULL ) ) {
g_object_unref( im );
return( -1 );
}
g_object_unref( im );
/* We must remove the tag to prevent accidental
* double rotations.
*/
vips_autorot_remove_angle( *out );
}
else {
*out = in;
g_object_ref( in );
}
return( 0 );
}
/* Unpremultiply associative alpha, if any.
*/
static int
@ -1952,6 +1899,8 @@ rtiff_read_tilewise( Rtiff *rtiff, VipsImage *out )
VipsImage **t = (VipsImage **)
vips_object_local_array( VIPS_OBJECT( out ), 4 );
VipsImage *in;
#ifdef DEBUG
printf( "tiff2vips: rtiff_read_tilewise\n" );
#endif /*DEBUG*/
@ -1991,21 +1940,31 @@ rtiff_read_tilewise( Rtiff *rtiff, VipsImage *out )
*/
vips_image_pipelinev( t[0], VIPS_DEMAND_STYLE_THINSTRIP, NULL );
if( vips_image_generate( t[0],
rtiff_seq_start, rtiff_fill_region, rtiff_seq_stop,
rtiff, NULL ) )
return( -1 );
/* Copy to out, adding a cache. Enough tiles for two complete rows.
/* Generate to out, adding a cache. Enough tiles for two complete rows.
*/
if( vips_tilecache( t[0], &t[1],
"tile_width", tile_width,
"tile_height", tile_height,
"max_tiles", 2 * (1 + t[0]->Xsize / tile_width),
NULL ) ||
rtiff_autorotate( rtiff, t[1], &t[2] ) ||
rtiff_unpremultiply( rtiff, t[2], &t[3] ) ||
vips_image_write( t[3], out ) )
if(
vips_image_generate( t[0],
rtiff_seq_start, rtiff_fill_region, rtiff_seq_stop,
rtiff, NULL ) ||
vips_tilecache( t[0], &t[1],
"tile_width", tile_width,
"tile_height", tile_height,
"max_tiles", 2 * (1 + t[0]->Xsize / tile_width),
NULL ) ||
rtiff_unpremultiply( rtiff, t[1], &t[2] ) )
return( -1 );
in = t[2];
/* Only do this if we have to.
*/
if( rtiff->autorotate &&
vips_image_get_orientation( in ) != 1 ) {
if( vips_autorot( in, &t[3], NULL ) )
return( -1 );
in = t[3];
}
if( vips_image_write( in, out ) )
return( -1 );
return( 0 );
@ -2226,6 +2185,8 @@ rtiff_read_stripwise( Rtiff *rtiff, VipsImage *out )
VipsImage **t = (VipsImage **)
vips_object_local_array( VIPS_OBJECT( out ), 4 );
VipsImage *in;
#ifdef DEBUG
printf( "tiff2vips: rtiff_read_stripwise\n" );
#endif /*DEBUG*/
@ -2301,9 +2262,20 @@ rtiff_read_stripwise( Rtiff *rtiff, VipsImage *out )
vips_sequential( t[0], &t[1],
"tile_height", rtiff->header.read_height,
NULL ) ||
rtiff_autorotate( rtiff, t[1], &t[2] ) ||
rtiff_unpremultiply( rtiff, t[2], &t[3] ) ||
vips_image_write( t[3], out ) )
rtiff_unpremultiply( rtiff, t[1], &t[2] ) )
return( -1 );
in = t[2];
/* Only do this if we have to.
*/
if( rtiff->autorotate &&
vips_image_get_orientation( in ) != 1 ) {
if( vips_autorot( in, &t[3], NULL ) )
return( -1 );
in = t[3];
}
if( vips_image_write( in, out ) )
return( -1 );
return( 0 );
@ -2683,29 +2655,6 @@ rtiff_header_read_all( Rtiff *rtiff )
return( 0 );
}
/* On a header-only read, we can just swap width/height if orientation is 6 or
* 8.
*/
static void
vips__tiff_read_header_orientation( Rtiff *rtiff, VipsImage *out )
{
int orientation;
if( rtiff->autorotate &&
vips_image_get_typeof( out, VIPS_META_ORIENTATION ) &&
!vips_image_get_int( out,
VIPS_META_ORIENTATION, &orientation ) ) {
if( orientation == 3 ||
orientation == 6 )
VIPS_SWAP( int, out->Xsize, out->Ysize );
/* We must remove VIPS_META_ORIENTATION to prevent accidental
* double rotations.
*/
vips_image_remove( out, VIPS_META_ORIENTATION );
}
}
typedef gboolean (*TiffPropertyFn)( TIFF *tif );
static gboolean
@ -2755,7 +2704,11 @@ vips__tiff_read_header_source( VipsSource *source, VipsImage *out,
if( rtiff_set_header( rtiff, out ) )
return( -1 );
vips__tiff_read_header_orientation( rtiff, out );
if( rtiff->autorotate &&
vips_image_get_orientation_swap( out ) ) {
VIPS_SWAP( int, out->Xsize, out->Ysize );
vips_autorot_remove_angle( out );
}
/* We never call vips_source_decode() since we need to be able to
* seek() the whole way through the file. Just minimise instead,

View File

@ -292,6 +292,8 @@ void vips_info( const char *domain, const char *fmt, ... )
__attribute__((format(printf, 2, 3)));
void vips_vinfo( const char *domain, const char *fmt, va_list ap );
VipsAngle vips_autorot_get_angle( VipsImage *image );
#ifdef __cplusplus
}
#endif /*__cplusplus*/

View File

@ -192,7 +192,6 @@ int vips_rot270( VipsImage *in, VipsImage **out, ... )
__attribute__((sentinel));
int vips_rot45( VipsImage *in, VipsImage **out, ... )
__attribute__((sentinel));
VipsAngle vips_autorot_get_angle( VipsImage *image );
void vips_autorot_remove_angle( VipsImage *image );
int vips_autorot( VipsImage *in, VipsImage **out, ... )
__attribute__((sentinel));

View File

@ -179,6 +179,8 @@ double vips_image_get_offset( const VipsImage *image );
int vips_image_get_page_height( VipsImage *image );
int vips_image_get_n_pages( VipsImage *image );
int vips_image_get_n_subifds( VipsImage *image );
int vips_image_get_orientation( VipsImage *image );
gboolean vips_image_get_orientation_swap( VipsImage *image );
const void *vips_image_get_data( VipsImage *image );
void vips_image_init_fields( VipsImage *image,

View File

@ -863,6 +863,47 @@ vips_image_get_n_subifds( VipsImage *image )
return( 0 );
}
/**
* vips_image_get_orientation: (method)
* @image: image to get from
*
* Fetch and sanity-check #VIPS_META_ORIENTATION. Default to 1 (no rotate,
* no flip) if not present or crazy.
*
* Returns: the image orientation.
*/
int
vips_image_get_orientation( VipsImage *image )
{
int orientation;
if( vips_image_get_typeof( image, VIPS_META_ORIENTATION ) &&
!vips_image_get_int( image, VIPS_META_ORIENTATION,
&orientation ) &&
orientation > 0 &&
orientation < 9 )
return( orientation );
return( 1 );
}
/**
* vips_image_get_orientation_swap: (method)
* @image: image to get from
*
* Return %TRUE if applying the orientation would swap width and height.
*
* Returns: if width/height will swap
*/
gboolean
vips_image_get_orientation_swap( VipsImage *image )
{
int orientation = vips_image_get_orientation( image );
return( orientation >= 5 &&
orientation <= 8 );
}
/**
* vips_image_get_data: (method)
* @image: image to get data for
@ -957,14 +998,11 @@ static int
meta_cp( VipsImage *dst, const VipsImage *src )
{
if( src->meta ) {
/* Loop, copying fields.
*/
meta_init( dst );
/* We lock with vips_image_set() to stop races in highly-
* threaded applications.
*/
g_mutex_lock( vips__meta_lock );
meta_init( dst );
vips_slist_map2( src->meta_traverse,
(VipsSListMap2Fn) meta_cp_field, dst, NULL );
g_mutex_unlock( vips__meta_lock );
@ -1055,8 +1093,6 @@ vips_image_set( VipsImage *image, const char *name, GValue *value )
g_assert( name );
g_assert( value );
meta_init( image );
/* We lock between modifying metadata and copying metadata between
* images, see meta_cp().
*
@ -1065,6 +1101,7 @@ vips_image_set( VipsImage *image, const char *name, GValue *value )
* highly-threaded applications.
*/
g_mutex_lock( vips__meta_lock );
meta_init( image );
(void) meta_new( image, name, value );
g_mutex_unlock( vips__meta_lock );

View File

@ -116,7 +116,8 @@ typedef struct _VipsThumbnail {
int input_width;
int input_height;
int page_height;
VipsAngle angle; /* From vips_autorot_get_angle() */
int orientation; /* From vips_image_get_orientation() */
gboolean swap; /* If we must swap width / height */
int n_pages; /* Pages in file */
int n_loaded_pages; /* Pages we've loaded from file */
int n_subifds; /* Number of subifds */
@ -203,7 +204,8 @@ vips_thumbnail_read_header( VipsThumbnail *thumbnail, VipsImage *image )
{
thumbnail->input_width = image->Xsize;
thumbnail->input_height = image->Ysize;
thumbnail->angle = vips_autorot_get_angle( image );
thumbnail->orientation = vips_image_get_orientation( image );
thumbnail->swap = vips_image_get_orientation_swap( image );
thumbnail->page_height = vips_image_get_page_height( image );
thumbnail->n_pages = vips_image_get_n_pages( image );
thumbnail->n_subifds = vips_image_get_n_subifds( image );
@ -390,11 +392,10 @@ static void
vips_thumbnail_calculate_shrink( VipsThumbnail *thumbnail,
int input_width, int input_height, double *hshrink, double *vshrink )
{
/* If we will be rotating, swap the target width and height.
/* If we will be rotating, swap the target width and height.
*/
gboolean rotate =
(thumbnail->angle == VIPS_ANGLE_D90 ||
thumbnail->angle == VIPS_ANGLE_D270) &&
thumbnail->swap &&
thumbnail->auto_rotate;
int target_width = rotate ?
thumbnail->height : thumbnail->width;
@ -831,21 +832,12 @@ vips_thumbnail_build( VipsObject *object )
}
if( thumbnail->auto_rotate &&
thumbnail->angle != VIPS_ANGLE_D0 ) {
VipsAngle angle = vips_autorot_get_angle( in );
g_info( "rotating by %s",
vips_enum_nick( VIPS_TYPE_ANGLE, angle ) );
/* Need to copy to memory, we have to stay seq.
*/
if( !(t[9] = vips_image_copy_memory( in )) ||
vips_rot( t[9], &t[10], angle, NULL ) ||
vips_copy( t[10], &t[14], NULL ) )
return( -1 );
thumbnail->orientation != 1 ) {
g_info( "rotating by EXIF orientation %d",
thumbnail->orientation );
if( vips_autorot( in, &t[14], NULL ) )
return( -1 );
in = t[14];
vips_autorot_remove_angle( in );
}
/* Crop after rotate so we don't need to rotate the crop box.