Merge pull request #1650 from wix-playground/master
Handle mirrored orientations in autorot
@ -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 );
|
||||
}
|
||||
|
||||
|
BIN
test/test-suite/images/rotation/0.png
Normal file
After Width: | Height: | Size: 661 KiB |
BIN
test/test-suite/images/rotation/1.jpg
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
test/test-suite/images/rotation/2.jpg
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
test/test-suite/images/rotation/3.jpg
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
test/test-suite/images/rotation/4.jpg
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
test/test-suite/images/rotation/5.jpg
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
test/test-suite/images/rotation/6.jpg
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
test/test-suite/images/rotation/7.jpg
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
test/test-suite/images/rotation/8.jpg
Normal file
After Width: | Height: | Size: 33 KiB |
@ -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)
|
||||
|