diff --git a/libvips/conversion/autorot.c b/libvips/conversion/autorot.c index fb39d5d4..344165b2 100644 --- a/libvips/conversion/autorot.c +++ b/libvips/conversion/autorot.c @@ -6,6 +6,8 @@ * - test and remove orientation from every ifd * 6/10/18 * - don't remove orientation if it's one of the cases we don't handle + * 10/5/20 + * - handle mirrored images */ /* @@ -55,6 +57,8 @@ typedef struct _VipsAutorot { VipsAngle angle; + gboolean flip; + } VipsAutorot; typedef VipsConversionClass VipsAutorotClass; @@ -106,34 +110,6 @@ vips_autorot_get_angle( VipsImage *im ) return( angle ); } -/* TRUE if this is one of the cases we handle. - */ -static gboolean -vips_autorot_handled( VipsImage *im ) -{ - int orientation; - gboolean handled; - - if( !vips_image_get_typeof( im, VIPS_META_ORIENTATION ) || - vips_image_get_int( im, VIPS_META_ORIENTATION, &orientation ) ) - orientation = 1; - - switch( orientation ) { - case 1: - case 3: - case 6: - case 8: - handled = TRUE; - break; - - default: - handled = FALSE; - break; - } - - return( handled ); -} - static void * vips_autorot_remove_angle_sub( VipsImage *image, const char *field, GValue *value, void *my_data ) @@ -170,27 +146,93 @@ vips_autorot_build( VipsObject *object ) { VipsConversion *conversion = VIPS_CONVERSION( object ); VipsAutorot *autorot = (VipsAutorot *) object; - VipsImage **t = (VipsImage **) vips_object_local_array( object, 2 ); + VipsImage **t = (VipsImage **) vips_object_local_array( object, 3 ); 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; - g_object_set( object, - "angle", vips_autorot_get_angle( autorot->in ), - NULL ); + switch( orientation ) { - if( vips_autorot_handled( autorot->in ) ) { - if( vips_rot( autorot->in, &t[0], autorot->angle, NULL ) || - vips_copy( t[0], &t[1], NULL ) ) - return( -1 ); - vips_autorot_remove_angle( t[1] ); - } - else { - if( vips_copy( autorot->in, &t[1], NULL ) ) - return( -1 ); - } + case 2: + angle = VIPS_ANGLE_D0; + flip = TRUE; + break; - if( vips_image_write( t[1], conversion->out ) ) + case 4: + flip = TRUE; + + case 3: + angle = VIPS_ANGLE_D180; + break; + + case 5: + flip = TRUE; + + case 6: + angle = VIPS_ANGLE_D90; + break; + + case 7: + flip = TRUE; + + case 8: + angle = VIPS_ANGLE_D270; + break; + + default: + angle = VIPS_ANGLE_D0; + flip = FALSE; + break; + + } + + g_object_set( object, + "angle", angle, + NULL ); + + g_object_set( object, + "flip", flip, + NULL ); + + if( angle != VIPS_ANGLE_D0 && flip) { + if( vips_rot( autorot->in, &t[0], angle, NULL ) ) + return( -1 ); + + 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 ); + + 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( vips_copy( t[0], &t[2], NULL ) ) + return( -1 ); + } else { + if( vips_copy( autorot->in, &t[2], NULL ) ) + return( -1 ); + } + + vips_autorot_remove_angle( t[2] ); + + if( vips_image_write( t[2], conversion->out ) ) return( -1 ); return( 0 ); @@ -220,13 +262,21 @@ vips_autorot_class_init( VipsAutorotClass *class ) _( "Angle image was rotated by" ), VIPS_ARGUMENT_OPTIONAL_OUTPUT, G_STRUCT_OFFSET( VipsAutorot, angle ), - VIPS_TYPE_ANGLE, VIPS_ANGLE_D0 ); + 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); } static void vips_autorot_init( VipsAutorot *autorot ) { autorot->angle = VIPS_ANGLE_D0; + autorot->flip = FALSE; } /** @@ -238,16 +288,13 @@ vips_autorot_init( VipsAutorot *autorot ) * Optional arguments: * * * @angle: output #VipsAngle the image was rotated by + * * @flip: output 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. * - * Read @angle to find the amount the image was rotated by. - * - * vips only supports the four simple rotations, it does not support the - * various mirror modes. If the image is using one of these mirror modes, the - * image is not rotated and the #VIPS_META_ORIENTATION tag is not removed. + * Read @angle to find the amount the image was rotated by. * * See also: vips_autorot_get_angle(), vips_autorot_remove_angle(), vips_rot(). * @@ -265,4 +312,3 @@ vips_autorot( VipsImage *in, VipsImage **out, ... ) return( result ); } - diff --git a/test/test-suite/images/rotation/0.png b/test/test-suite/images/rotation/0.png new file mode 100644 index 00000000..4399557f Binary files /dev/null and b/test/test-suite/images/rotation/0.png differ diff --git a/test/test-suite/images/rotation/1.jpg b/test/test-suite/images/rotation/1.jpg new file mode 100644 index 00000000..0b48be31 Binary files /dev/null and b/test/test-suite/images/rotation/1.jpg differ diff --git a/test/test-suite/images/rotation/2.jpg b/test/test-suite/images/rotation/2.jpg new file mode 100644 index 00000000..ca66e666 Binary files /dev/null and b/test/test-suite/images/rotation/2.jpg differ diff --git a/test/test-suite/images/rotation/3.jpg b/test/test-suite/images/rotation/3.jpg new file mode 100644 index 00000000..5adaf698 Binary files /dev/null and b/test/test-suite/images/rotation/3.jpg differ diff --git a/test/test-suite/images/rotation/4.jpg b/test/test-suite/images/rotation/4.jpg new file mode 100644 index 00000000..395942a4 Binary files /dev/null and b/test/test-suite/images/rotation/4.jpg differ diff --git a/test/test-suite/images/rotation/5.jpg b/test/test-suite/images/rotation/5.jpg new file mode 100644 index 00000000..d0932af0 Binary files /dev/null and b/test/test-suite/images/rotation/5.jpg differ diff --git a/test/test-suite/images/rotation/6.jpg b/test/test-suite/images/rotation/6.jpg new file mode 100644 index 00000000..d0119693 Binary files /dev/null and b/test/test-suite/images/rotation/6.jpg differ diff --git a/test/test-suite/images/rotation/7.jpg b/test/test-suite/images/rotation/7.jpg new file mode 100644 index 00000000..d5a8b82f Binary files /dev/null and b/test/test-suite/images/rotation/7.jpg differ diff --git a/test/test-suite/images/rotation/8.jpg b/test/test-suite/images/rotation/8.jpg new file mode 100644 index 00000000..962e6665 Binary files /dev/null and b/test/test-suite/images/rotation/8.jpg differ diff --git a/test/test-suite/test_conversion.py b/test/test-suite/test_conversion.py index c26f7b06..94b29b90 100644 --- a/test/test-suite/test_conversion.py +++ b/test/test-suite/test_conversion.py @@ -1,17 +1,24 @@ # vim: set fileencoding=utf-8 : +import filecmp from functools import reduce + +import os import pytest +import tempfile +import shutil import pyvips -from helpers import JPEG_FILE, unsigned_formats, \ +from helpers import IMAGES, JPEG_FILE, unsigned_formats, \ signed_formats, float_formats, int_formats, \ noncomplex_formats, all_formats, max_value, \ sizeof_format, rot45_angles, rot45_angle_bonds, \ rot_angles, rot_angle_bonds, run_cmp, run_cmp2, \ - assert_almost_equal_objects + assert_almost_equal_objects, temp_filename class TestConversion: + tempdir = None + # run a function on an image, # 50,50 and 10,10 should have different values on the test image # don't loop over band elements @@ -37,6 +44,7 @@ class TestConversion: @classmethod def setup_class(cls): + cls.tempdir = tempfile.mkdtemp() im = pyvips.Image.mask_ideal(100, 100, 0.5, reject=True, optical=True) cls.colour = (im * [1, 2, 3] + [2, 3, 4]).copy(interpretation="srgb") @@ -46,6 +54,7 @@ class TestConversion: @classmethod def teardown_class(cls): + shutil.rmtree(cls.tempdir, ignore_errors=True) cls.colour = None cls.mono = None cls.image = None @@ -738,6 +747,39 @@ class TestConversion: diff = (after - im).abs().max() assert diff == 0 + def test_autorot(self): + rotation_images = os.path.join(IMAGES, 'rotation') + files = os.listdir(rotation_images) + files.sort() + + meta = { + 0: {'w': 290, 'h': 442}, + 1: {'w': 308, 'h': 410}, + 2: {'w': 308, 'h': 410}, + 3: {'w': 308, 'h': 410}, + 4: {'w': 308, 'h': 410}, + 5: {'w': 231, 'h': 308}, + 6: {'w': 231, 'h': 308}, + 7: {'w': 231, 'h': 308}, + 8: {'w': 231, 'h': 308}, + } + + i = 0 + for f in files: + if '.autorot.' not in f and not f.startswith('.'): + source_filename = os.path.join(rotation_images, f) + + actual_filename = temp_filename(self.tempdir, '.jpg') + + pyvips.Image.new_from_file(source_filename).autorot().write_to_file(actual_filename) + + actual = pyvips.Image.new_from_file(actual_filename) + + assert actual.width == meta[i]['w'] + assert actual.height == meta[i]['h'] + assert actual.get('orientation') if actual.get_typeof('orientation') else None is None + i = i + 1 + def test_scaleimage(self): for fmt in noncomplex_formats: test = self.colour.cast(fmt)