From 14d7ce1e916ed902c000dff5902a5f1189fafb39 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 16 Oct 2014 19:16:45 +0100 Subject: [PATCH] first try, not very good --- TODO | 15 ++-- libvips/deprecated/im_jpeg2vips.c | 2 +- libvips/foreign/foreign.c | 32 --------- libvips/foreign/jpeg2vips.c | 114 +++++++++++++++++++++++++++--- libvips/foreign/jpegload.c | 20 ++++-- libvips/foreign/vipsjpeg.h | 6 +- libvips/include/vips/image.h | 1 + libvips/iofuncs/image.c | 40 +++++++++++ 8 files changed, 176 insertions(+), 54 deletions(-) diff --git a/TODO b/TODO index f717cb21..98bcc7ef 100644 --- a/TODO +++ b/TODO @@ -1,11 +1,14 @@ +- the jpeg loader needs _header() and _load() to return the same dimensions -- python: + this is going to be tough with read_jpeg_image(): it uses the read-header + thing to set up the image that it generates - - update examples with new image constant rules - update blog post with new examples/try12.py - - do more tests + +- update blog post with new examples/try12.py + +- fix up aconv - put exif autorotate into jpeg load @@ -13,12 +16,12 @@ https://github.com/jcupitt/ruby-vips/issues/53 +- more python tests + - can we pick the vipsthumbnail int shrink factor more intelligently? - vips_resize() should use vipsthumbnail's block/cache/affine/sharpen chain -- fix up aconv - - rewrite im_conv() etc. as vips_conv(), also the mosaicing functions finally finish --disable-deprecated option diff --git a/libvips/deprecated/im_jpeg2vips.c b/libvips/deprecated/im_jpeg2vips.c index a42392c8..5b4c2c06 100644 --- a/libvips/deprecated/im_jpeg2vips.c +++ b/libvips/deprecated/im_jpeg2vips.c @@ -113,7 +113,7 @@ jpeg2vips( const char *name, IMAGE *out, gboolean header_only ) #ifdef HAVE_JPEG if( vips__jpeg_read_file( filename, out, - header_only, shrink, fail_on_warn, TRUE ) ) + header_only, shrink, fail_on_warn, TRUE, FALSE ) ) return( -1 ); #else vips_error( "im_jpeg2vips", diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index 15c4c4d4..98e12d2c 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -682,36 +682,6 @@ vips_foreign_load_new_from_string( const char *string ) return( VIPS_OBJECT( load ) ); } -static guint64 -vips_get_disc_threshold( void ) -{ - static gboolean done = FALSE; - static guint64 threshold; - - if( !done ) { - const char *env; - - done = TRUE; - - /* 100mb default. - */ - threshold = 100 * 1024 * 1024; - - if( (env = g_getenv( "VIPS_DISC_THRESHOLD" )) || - (env = g_getenv( "IM_DISC_THRESHOLD" )) ) - threshold = vips__parse_size( env ); - - if( vips__disc_threshold ) - threshold = vips__parse_size( vips__disc_threshold ); - -#ifdef DEBUG - printf( "vips_get_disc_threshold: %zd bytes\n", threshold ); -#endif /*DEBUG*/ - } - - return( threshold ); -} - static VipsImage * vips_foreign_load_temp( VipsForeignLoad *load ) { @@ -742,12 +712,10 @@ vips_foreign_load_temp( VipsForeignLoad *load ) /* We open via disc if: * - 'disc' is set - * - disc-threshold has not been set to zero * - the uncompressed image will be larger than * vips_get_disc_threshold() */ if( load->disc && - disc_threshold && image_size > disc_threshold ) { #ifdef DEBUG printf( "vips_foreign_load_temp: disc temp\n" ); diff --git a/libvips/foreign/jpeg2vips.c b/libvips/foreign/jpeg2vips.c index 8a468f9d..66674aff 100644 --- a/libvips/foreign/jpeg2vips.c +++ b/libvips/foreign/jpeg2vips.c @@ -59,6 +59,8 @@ * - don't write to our input buffer, thanks Lovell * 9/9/14 * - support "none" as a resolution unit + * 16/10/14 + * - add "autorotate" option */ /* @@ -131,6 +133,12 @@ typedef struct _ReadJpeg { VipsImage *out; + /* The raw image size: out may have width and height swapped if we are + * autorotating. + */ + int width; + int height; + /* Shrink by this much during load. 1, 2, 4, 8. */ int shrink; @@ -158,6 +166,10 @@ typedef struct _ReadJpeg { /* Track the y pos during a read with this. */ int y_pos; + + /* Use exif tags to automatically rotate and flip image. + */ + gboolean autorotate; } ReadJpeg; static int @@ -211,7 +223,8 @@ readjpeg_close( VipsObject *object, ReadJpeg *jpeg ) } static ReadJpeg * -readjpeg_new( VipsImage *out, int shrink, gboolean fail, gboolean readbehind ) +readjpeg_new( VipsImage *out, + int shrink, gboolean fail, gboolean readbehind, gboolean autorotate ) { ReadJpeg *jpeg; @@ -224,13 +237,12 @@ readjpeg_new( VipsImage *out, int shrink, gboolean fail, gboolean readbehind ) jpeg->readbehind = readbehind; jpeg->filename = NULL; jpeg->decompressing = FALSE; - jpeg->cinfo.err = jpeg_std_error( &jpeg->eman.pub ); jpeg->eman.pub.error_exit = vips__new_error_exit; jpeg->eman.pub.output_message = vips__new_output_message; jpeg->eman.fp = NULL; - jpeg->y_pos = 0; + jpeg->autorotate = autorotate; /* jpeg_create_decompress() can fail on some sanity checks. Don't * readjpeg_free() since we don't want to jpeg_destroy_decompress(). @@ -678,6 +690,35 @@ attach_blob( VipsImage *im, const char *field, void *data, int data_length ) return( 0 ); } +#define ORIENTATION ("exif-ifd0-Orientation") + +static VipsAngle +get_angle( VipsImage *im ) +{ + VipsAngle angle; + const char *orientation; + + angle = VIPS_ANGLE_D0; + if( vips_image_get_typeof( im, ORIENTATION ) && + !vips_image_get_string( im, ORIENTATION, &orientation ) ) { + if( vips_isprefix( "6", orientation ) ) + angle = VIPS_ANGLE_D90; + else if( vips_isprefix( "8", orientation ) ) + angle = VIPS_ANGLE_D270; + else if( vips_isprefix( "3", orientation ) ) + angle = VIPS_ANGLE_D180; + /* Other values do rotate + mirror, don't bother handling them + * though, how common can mirroring be. + * + * See: + * + * http://www.80sidea.com/archives/2316 + */ + } + + return( angle ); +} + /* Number of app2 sections we can capture. Each one can be 64k, so 6400k should * be enough for anyone (haha). */ @@ -892,6 +933,20 @@ read_jpeg_header( ReadJpeg *jpeg, VipsImage *out ) (VipsCallbackFn) vips_free, data, data_length ); } + /* Swap width and height if we're going to rotate this image. Keep the + * unrotated dimensions around too. + */ + jpeg->width = out->Xsize; + jpeg->height = out->Ysize; + + if( jpeg->autorotate ) { + VipsAngle angle = get_angle( out ); + + if( angle == VIPS_ANGLE_D90 || + angle == VIPS_ANGLE_D270 ) + VIPS_SWAP( int, out->Xsize, out->Ysize ) + } + return( 0 ); } @@ -966,6 +1021,35 @@ 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, 2 ); + VipsAngle angle = 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]; + (void) vips_image_remove( im, ORIENTATION ); + } + + return( im ); +} + /* Read a cinfo to a VIPS image. */ static int @@ -975,6 +1059,8 @@ read_jpeg_image( ReadJpeg *jpeg, VipsImage *out ) VipsImage **t = (VipsImage **) vips_object_local_array( VIPS_OBJECT( out ), 3 ); + VipsImage *im; + /* Here for longjmp() from vips__new_error_exit(). */ if( setjmp( jpeg->eman.jmp ) ) @@ -1002,8 +1088,14 @@ read_jpeg_image( ReadJpeg *jpeg, VipsImage *out ) "access", jpeg->readbehind ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_SEQUENTIAL_UNBUFFERED, - NULL ) || - vips_image_write( t[1], out ) ) + NULL ) ) + return( -1 ); + + im = t[1]; + if( jpeg->autorotate ) + im = read_jpeg_rotate( VIPS_OBJECT( out ), im ); + + if( vips_image_write( im, out ) ) return( -1 ); return( 0 ); @@ -1013,12 +1105,14 @@ read_jpeg_image( ReadJpeg *jpeg, VipsImage *out ) */ int vips__jpeg_read_file( const char *filename, VipsImage *out, - gboolean header_only, int shrink, gboolean fail, gboolean readbehind ) + gboolean header_only, int shrink, gboolean fail, gboolean readbehind, + gboolean autorotate ) { ReadJpeg *jpeg; int result; - if( !(jpeg = readjpeg_new( out, shrink, fail, readbehind )) ) + if( !(jpeg = readjpeg_new( out, + shrink, fail, readbehind, autorotate )) ) return( -1 ); /* Here for longjmp() from vips__new_error_exit() during startup. @@ -1231,12 +1325,14 @@ readjpeg_buffer (ReadJpeg *jpeg, void *buf, size_t len) int vips__jpeg_read_buffer( void *buf, size_t len, VipsImage *out, - gboolean header_only, int shrink, int fail, gboolean readbehind ) + gboolean header_only, int shrink, int fail, gboolean readbehind, + gboolean autorotate ) { ReadJpeg *jpeg; int result; - if( !(jpeg = readjpeg_new( out, shrink, fail, readbehind )) ) + if( !(jpeg = readjpeg_new( out, + shrink, fail, readbehind, autorotate )) ) return( -1 ); if( setjmp( jpeg->eman.jmp ) ) { diff --git a/libvips/foreign/jpegload.c b/libvips/foreign/jpegload.c index 6f9e3d3a..bf5e5bdc 100644 --- a/libvips/foreign/jpegload.c +++ b/libvips/foreign/jpegload.c @@ -81,6 +81,10 @@ typedef struct _VipsForeignLoadJpeg { */ gboolean fail; + /* Autorotate using exif orientation tag. + */ + gboolean autorotate; + } VipsForeignLoadJpeg; typedef VipsForeignLoadClass VipsForeignLoadJpegClass; @@ -146,6 +150,13 @@ vips_foreign_load_jpeg_class_init( VipsForeignLoadJpegClass *class ) VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignLoadJpeg, fail ), FALSE ); + + VIPS_ARG_BOOL( class, "autorotate", 12, + _( "Autorotate" ), + _( "Automatically rotate image using exif orientation" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadJpeg, autorotate ), + FALSE ); } static void @@ -189,7 +200,7 @@ vips_foreign_load_jpeg_file_header( VipsForeignLoad *load ) VipsForeignLoadJpegFile *file = (VipsForeignLoadJpegFile *) load; if( vips__jpeg_read_file( file->filename, load->out, - TRUE, jpeg->shrink, jpeg->fail, FALSE ) ) + TRUE, jpeg->shrink, jpeg->fail, FALSE, jpeg->autorotate ) ) return( -1 ); VIPS_SETSTR( load->out->filename, file->filename ); @@ -205,7 +216,7 @@ vips_foreign_load_jpeg_file_load( VipsForeignLoad *load ) if( vips__jpeg_read_file( file->filename, load->real, FALSE, jpeg->shrink, jpeg->fail, - load->access == VIPS_ACCESS_SEQUENTIAL ) ) + load->access == VIPS_ACCESS_SEQUENTIAL, jpeg->autorotate ) ) return( -1 ); return( 0 ); @@ -269,7 +280,8 @@ vips_foreign_load_jpeg_buffer_header( VipsForeignLoad *load ) VipsForeignLoadJpegBuffer *buffer = (VipsForeignLoadJpegBuffer *) load; if( vips__jpeg_read_buffer( buffer->buf->data, buffer->buf->length, - load->out, TRUE, jpeg->shrink, jpeg->fail, FALSE ) ) + load->out, TRUE, jpeg->shrink, jpeg->fail, FALSE, + jpeg->autorotate ) ) return( -1 ); return( 0 ); @@ -283,7 +295,7 @@ vips_foreign_load_jpeg_buffer_load( VipsForeignLoad *load ) if( vips__jpeg_read_buffer( buffer->buf->data, buffer->buf->length, load->real, FALSE, jpeg->shrink, jpeg->fail, - load->access == VIPS_ACCESS_SEQUENTIAL ) ) + load->access == VIPS_ACCESS_SEQUENTIAL, jpeg->autorotate ) ) return( -1 ); return( 0 ); diff --git a/libvips/foreign/vipsjpeg.h b/libvips/foreign/vipsjpeg.h index c4271add..098f0dd5 100644 --- a/libvips/foreign/vipsjpeg.h +++ b/libvips/foreign/vipsjpeg.h @@ -49,9 +49,11 @@ int vips__jpeg_write_buffer( VipsImage *in, int vips__isjpeg_buffer( void *buf, size_t len ); int vips__isjpeg( const char *filename ); int vips__jpeg_read_file( const char *name, VipsImage *out, - gboolean header_only, int shrink, gboolean fail, gboolean readbehind ); + gboolean header_only, int shrink, gboolean fail, gboolean readbehind, + gboolean autorotate ); int vips__jpeg_read_buffer( void *buf, size_t len, VipsImage *out, - gboolean header_only, int shrink, int fail, gboolean readbehind ); + gboolean header_only, int shrink, int fail, gboolean readbehind, + gboolean autorotate ); #ifdef __cplusplus } diff --git a/libvips/include/vips/image.h b/libvips/include/vips/image.h index b7b8a970..fed6e6d2 100644 --- a/libvips/include/vips/image.h +++ b/libvips/include/vips/image.h @@ -427,6 +427,7 @@ VipsImage *vips_image_new_matrix_from_array( int width, int height, double *array, int size ); void vips_image_set_delete_on_close( VipsImage *image, gboolean delete_on_close ); +guint64 vips_get_disc_threshold( void ); VipsImage *vips_image_new_temp_file( const char *format ); int vips_image_write( VipsImage *image, VipsImage *out ); diff --git a/libvips/iofuncs/image.c b/libvips/iofuncs/image.c index 83e7add2..60db2fb9 100644 --- a/libvips/iofuncs/image.c +++ b/libvips/iofuncs/image.c @@ -2204,6 +2204,46 @@ vips_image_set_delete_on_close( VipsImage *image, gboolean delete_on_close ) VIPS_SETSTR( image->delete_on_close_filename, image->filename ); } +/** + * vips_get_disc_threshold: + * + * Return the number of bytes at which we flip between open via memory and + * open via disc. This defaults to 100mb, but can be changed with the + * VIPS_DISC_THRESHOLD environment variable or the --vips-disc-threshold + * command-line flag. See vips_image_new_from_file(). + * + * Returns: disc threshold in bytes. + */ +guint64 +vips_get_disc_threshold( void ) +{ + static gboolean done = FALSE; + static guint64 threshold; + + if( !done ) { + const char *env; + + done = TRUE; + + /* 100mb default. + */ + threshold = 100 * 1024 * 1024; + + if( (env = g_getenv( "VIPS_DISC_THRESHOLD" )) || + (env = g_getenv( "IM_DISC_THRESHOLD" )) ) + threshold = vips__parse_size( env ); + + if( vips__disc_threshold ) + threshold = vips__parse_size( vips__disc_threshold ); + +#ifdef DEBUG + printf( "vips_get_disc_threshold: %zd bytes\n", threshold ); +#endif /*DEBUG*/ + } + + return( threshold ); +} + /** * vips_image_new_temp_file: * @format: format of file