diff --git a/ChangeLog b/ChangeLog index 05fd361e..e7968f23 100644 --- a/ChangeLog +++ b/ChangeLog @@ -15,12 +15,18 @@ - add subsample_mode, deprecate no_subsample in jpegsave [Elad-Laufer] - add vips_isdirf() - add PAGENUMBER support to tiff write [jclavoie-jive] -- add all to smartcrop +- add "all" mode to smartcrop - flood fill could stop half-way for some very complex shapes - better handling of unaligned reads in multipage tiffs [petoor] - add experimental libspng reader - mark old --delete option to vipsthumbnail as deprecated [UweOhse] - png save with a bad ICC profile just gives a warning +- add "premultipled" option to vips_affine(), clarified vips_resize() + 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] diff --git a/configure.ac b/configure.ac index 90ca3d0a..dda267bd 100644 --- a/configure.ac +++ b/configure.ac @@ -855,14 +855,16 @@ if test x"$with_heif" != x"no"; then PKG_CHECK_MODULES(HEIF, libheif, [with_heif=yes have_h265_decoder=`$PKG_CONFIG libheif --variable builtin_h265_decoder` + have_avif_decoder=`$PKG_CONFIG libheif --variable builtin_avif_decoder` # test for !=no so that we work for older libheif which does not have # this variable - if test x"$have_h265_decoder" != x"no"; then + if test x"$have_h265_decoder" != x"no" -o x"$have_avif_decoder" = x"yes"; then AC_DEFINE(HAVE_HEIF_DECODER,1, [define if your libheif has decode support.]) fi have_h265_encoder=`$PKG_CONFIG libheif --variable builtin_h265_encoder` - if test x"$have_h265_encoder" != x"no"; then + have_avif_encoder=`$PKG_CONFIG libheif --variable builtin_avif_encoder` + if test x"$have_h265_encoder" != x"no" -o x"$have_avif_encoder" = x"yes"; then AC_DEFINE(HAVE_HEIF_ENCODER,1, [define if your libheif has encode support.]) fi @@ -872,6 +874,8 @@ if test x"$with_heif" != x"no"; then with_heif=no have_h265_decoder= have_h265_encoder= + have_avif_decoder= + have_avif_encoder= ] ) fi diff --git a/doc/Using-vipsthumbnail.xml b/doc/Using-vipsthumbnail.xml index 4963eaec..a72b1d58 100644 --- a/doc/Using-vipsthumbnail.xml +++ b/doc/Using-vipsthumbnail.xml @@ -260,10 +260,10 @@ $ ls -l tn_shark.jpg -rw-r–r– 1 john john 7295 Nov  9 14:33 tn_shark.jpg - Now encode with sRGB and delete any embedded profile: + Now transform to sRGB and don’t attach a profile (you can also use strip, though that will remove all metadata from the image): -$ vipsthumbnail shark.jpg --eprofile srgb --delete +$ vipsthumbnail shark.jpg --eprofile srgb -o tn_shark.jpg[profile=none] $ ls -l tn_shark.jpg -rw-r–r– 1 john john 4229 Nov  9 14:33 tn_shark.jpg @@ -299,6 +299,7 @@ $ vipsthumbnail kgdev.jpg --iprofile cmyk $ vipsthumbnail fred.jpg \ --size 128 \ + --eprofile srgb \ -o tn_%s.jpg[optimize_coding,strip] \ --eprofile srgb diff --git a/libvips/conversion/autorot.c b/libvips/conversion/autorot.c index fb39d5d4..b297dc95 100644 --- a/libvips/conversion/autorot.c +++ b/libvips/conversion/autorot.c @@ -6,6 +6,9 @@ * - 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 + * - deprecate vips_autorot_get_angle() */ /* @@ -54,6 +57,7 @@ typedef struct _VipsAutorot { VipsImage *in; VipsAngle angle; + gboolean flip; } VipsAutorot; @@ -61,79 +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 ); -} - -/* 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 ) @@ -154,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 ); } @@ -170,27 +101,87 @@ 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 ); + + VipsAngle angle; + gboolean flip; + VipsImage *in; - g_object_set( object, - "angle", vips_autorot_get_angle( autorot->in ), - NULL ); + in = autorot->in; + + switch( vips_image_get_orientation( in ) ) { + case 2: + angle = VIPS_ANGLE_D0; + flip = TRUE; + break; + + case 3: + angle = VIPS_ANGLE_D180; + flip = FALSE; + break; + + case 4: + angle = VIPS_ANGLE_D180; + flip = TRUE; + break; + + case 5: + angle = VIPS_ANGLE_D90; + flip = TRUE; + break; + + case 6: + angle = VIPS_ANGLE_D90; + flip = FALSE; + break; + + case 7: + angle = VIPS_ANGLE_D270; + flip = TRUE; + break; + + case 8: + angle = VIPS_ANGLE_D270; + flip = FALSE; + break; + + case 1: + default: + angle = VIPS_ANGLE_D0; + flip = FALSE; + break; - 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 ); } - if( vips_image_write( t[1], conversion->out ) ) + g_object_set( object, + "angle", angle, + "flip", flip, + NULL ); + + if( angle != VIPS_ANGLE_D0 ) { + if( vips_rot( in, &t[0], angle, NULL ) ) + return( -1 ); + in = t[0]; + } + + if( flip ) { + if( vips_flip( in, &t[1], VIPS_DIRECTION_HORIZONTAL, NULL ) ) + return( -1 ); + in = t[1]; + } + + /* 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 ); @@ -220,13 +211,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,18 +237,14 @@ vips_autorot_init( VipsAutorot *autorot ) * Optional arguments: * * * @angle: output #VipsAngle the image was rotated by + * * @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. - * - * 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. - * - * 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 */ @@ -265,4 +260,3 @@ vips_autorot( VipsImage *in, VipsImage **out, ... ) return( result ); } - diff --git a/libvips/create/text.c b/libvips/create/text.c index 86d04c32..a750c8ec 100644 --- a/libvips/create/text.c +++ b/libvips/create/text.c @@ -30,6 +30,8 @@ * - set Xoffset/Yoffset to ink left/top * 27/6/19 * - fitting could occasionally terminate early [levmorozov] + * 16/5/20 [keiviv] + * - don't add fontfiles repeatedly */ /* @@ -103,6 +105,10 @@ typedef VipsCreateClass VipsTextClass; G_DEFINE_TYPE( VipsText, vips_text, VIPS_TYPE_CREATE ); +/* ... single-thread the body of vips_text() with this. + */ +static GMutex *vips_text_lock = NULL; + /* Just have one of these and reuse it. * * This does not unref cleanly on many platforms, so we will leak horribly @@ -111,9 +117,10 @@ G_DEFINE_TYPE( VipsText, vips_text, VIPS_TYPE_CREATE ); */ static PangoFontMap *vips_text_fontmap = NULL; -/* ... single-thread the body of vips_text() with this. +/* All the fontfiles we've loaded. fontconfig lets you add a fontfile + * repeatedly, and we obviously don't want that. */ -static GMutex *vips_text_lock = NULL; +static GHashTable *vips_text_fontfiles = NULL; static void vips_text_dispose( GObject *gobject ) @@ -350,17 +357,26 @@ vips_text_build( VipsObject *object ) if( !vips_text_fontmap ) vips_text_fontmap = pango_ft2_font_map_new(); + if( !vips_text_fontfiles ) + vips_text_fontfiles = + g_hash_table_new( g_str_hash, g_str_equal ); text->context = pango_font_map_create_context( PANGO_FONT_MAP( vips_text_fontmap ) ); if( text->fontfile && - !FcConfigAppFontAddFile( NULL, + !g_hash_table_lookup( vips_text_fontfiles, text->fontfile ) ) { + if( !FcConfigAppFontAddFile( NULL, (const FcChar8 *) text->fontfile ) ) { - vips_error( class->nickname, - _( "unable to load font \"%s\"" ), text->fontfile ); - g_mutex_unlock( vips_text_lock ); - return( -1 ); + vips_error( class->nickname, + _( "unable to load font \"%s\"" ), + text->fontfile ); + g_mutex_unlock( vips_text_lock ); + return( -1 ); + } + g_hash_table_insert( vips_text_fontfiles, + text->fontfile, + g_strdup( text->fontfile ) ); } /* If our caller set height and not dpi, we adjust dpi until @@ -543,7 +559,7 @@ vips_text_init( VipsText *text ) * * @fontfile: %gchararray, load this font file * * @width: %gint, image should be no wider than this many pixels * * @height: %gint, image should be no higher than this many pixels - * * @align: #VipsAlign, left/centre/right alignment + * * @align: #VipsAlign, set justification alignment * * @justify: %gboolean, justify lines * * @dpi: %gint, render at this resolution * * @autofit_dpi: %gint, read out auto-fitted DPI @@ -564,11 +580,12 @@ vips_text_init( VipsText *text ) * * @width is the number of pixels to word-wrap at. Lines of text wider than * this will be broken at word boundaries. - * @align can be used to set the alignment style for multi-line - * text. Note that the output image can be wider than @width if there are no - * word breaks, or narrower if the lines don't break exactly at @width. * * Set @justify to turn on line justification. + * @align can be used to set the alignment style for multi-line + * text to the low (left) edge centre, or high (right) edge. Note that the + * output image can be wider than @width if there are no + * word breaks, or narrower if the lines don't break exactly at @width. * * @height is the maximum number of pixels high the generated text can be. This * only takes effect when @dpi is not set, and @width is set, making a box. diff --git a/libvips/deprecated/im_tiff2vips.c b/libvips/deprecated/im_tiff2vips.c index 1ce3f43d..785faf8d 100644 --- a/libvips/deprecated/im_tiff2vips.c +++ b/libvips/deprecated/im_tiff2vips.c @@ -76,7 +76,7 @@ im_tiff_read_header( const char *filename, VipsImage *out, if( !(source = vips_source_new_from_file( filename )) ) return( -1 ); if( vips__tiff_read_header_source( source, - out, page, n, autorotate ) ) { + out, page, n, autorotate, -1 ) ) { VIPS_UNREF( source ); return( -1 ); } @@ -93,7 +93,7 @@ im_tiff_read( const char *filename, VipsImage *out, if( !(source = vips_source_new_from_file( filename )) ) return( -1 ); - if( vips__tiff_read_source( source, out, page, n, autorotate ) ) { + if( vips__tiff_read_source( source, out, page, n, autorotate, -1 ) ) { VIPS_UNREF( source ); return( -1 ); } diff --git a/libvips/deprecated/rename.c b/libvips/deprecated/rename.c index 28146b94..ad3eaa22 100644 --- a/libvips/deprecated/rename.c +++ b/libvips/deprecated/rename.c @@ -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 ); +} + diff --git a/libvips/draw/draw.c b/libvips/draw/draw.c index d8f423da..bfa6c96b 100644 --- a/libvips/draw/draw.c +++ b/libvips/draw/draw.c @@ -69,7 +69,7 @@ * to be a slow way to do it. This is where the draw operations come in. * * To use these operations, use vips_copy() to make a copy of the image you - * want to modify, to ensure that no one else is using it, then call a + * want to modify to ensure that no one else is using it, then call a * series of draw operations. * Once you are done drawing, return to normal use of vips operations. Any time * you want to start drawing again, you'll need to copy again. diff --git a/libvips/foreign/csvload.c b/libvips/foreign/csvload.c index 0e2255b8..25a137f8 100644 --- a/libvips/foreign/csvload.c +++ b/libvips/foreign/csvload.c @@ -413,6 +413,10 @@ vips_foreign_load_csv_load( VipsForeignLoad *load ) for( y = 0; y < load->real->Ysize; y++ ) { csv->colno = 0; + /* Not needed, but stops a used-before-set compiler warning. + */ + ch = EOF; + for( x = 0; x < load->real->Xsize; x++ ) { double value; diff --git a/libvips/foreign/exif.c b/libvips/foreign/exif.c index ced41bec..6e6c442d 100644 --- a/libvips/foreign/exif.c +++ b/libvips/foreign/exif.c @@ -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 ); } diff --git a/libvips/foreign/heifload.c b/libvips/foreign/heifload.c index c62587b5..2ac2dff2 100644 --- a/libvips/foreign/heifload.c +++ b/libvips/foreign/heifload.c @@ -237,7 +237,8 @@ static const char *heif_magic[] = { "ftyphevm", /* Multiview sequence */ "ftyphevs", /* Scaleable sequence */ "ftypmif1", /* Nokia alpha_ image */ - "ftypmsf1" /* Nokia animation image */ + "ftypmsf1", /* Nokia animation image */ + "ftypavif" /* AV1 image format */ }; /* THe API has: diff --git a/libvips/foreign/jpeg2vips.c b/libvips/foreign/jpeg2vips.c index a4d53fd8..26accc40 100644 --- a/libvips/foreign/jpeg2vips.c +++ b/libvips/foreign/jpeg2vips.c @@ -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 ); } } diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index 92b32cc4..a6b3b362 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -63,7 +63,8 @@ int vips__tiff_write( VipsImage *in, const char *filename, VipsRegionShrink region_shrink, int level, gboolean lossless, - VipsForeignDzDepth depth ); + VipsForeignDzDepth depth, + gboolean subifd ); int vips__tiff_write_buf( VipsImage *in, void **obuf, size_t *olen, @@ -81,14 +82,15 @@ int vips__tiff_write_buf( VipsImage *in, VipsRegionShrink region_shrink, int level, gboolean lossless, - VipsForeignDzDepth depth ); + VipsForeignDzDepth depth, + gboolean subifd ); gboolean vips__istiff_source( VipsSource *source ); gboolean vips__istifftiled_source( VipsSource *source ); int vips__tiff_read_header_source( VipsSource *source, VipsImage *out, - int page, int n, gboolean autorotate ); + int page, int n, gboolean autorotate, int subifd ); int vips__tiff_read_source( VipsSource *source, VipsImage *out, - int page, int n, gboolean autorotate ); + int page, int n, gboolean autorotate, int subifd ); extern const char *vips__foreign_tiff_suffs[]; diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c index d6eed06f..34fefd75 100644 --- a/libvips/foreign/tiff2vips.c +++ b/libvips/foreign/tiff2vips.c @@ -197,6 +197,8 @@ * - read logluv images as XYZ * 11/4/20 petoor * - better handling of aligned reads in multipage tiffs + * 28/5/20 + * - add subifd */ /* @@ -269,7 +271,7 @@ typedef struct _RtiffHeader { int alpha_band; uint16 compression; - /* Result of TIFFIsTiled(). + /* Is this directory tiled. */ gboolean tiled; @@ -302,6 +304,15 @@ typedef struct _RtiffHeader { /* Scale factor to get absolute cd/m2 from XYZ. */ double stonits; + + /* Number of subifds, if any. + */ + int subifd_count; + + /* Optional IMAGEDESCRIPTION. + */ + char *image_description; + } RtiffHeader; /* Scanline-type process function. @@ -320,6 +331,7 @@ typedef struct _Rtiff { int page; int n; gboolean autorotate; + int subifd; /* The TIFF we read. */ @@ -527,7 +539,7 @@ rtiff_minimise_cb( VipsImage *image, Rtiff *rtiff ) static Rtiff * rtiff_new( VipsSource *source, VipsImage *out, - int page, int n, gboolean autorotate ) + int page, int n, gboolean autorotate, int subifd ) { Rtiff *rtiff; @@ -540,6 +552,7 @@ rtiff_new( VipsSource *source, VipsImage *out, rtiff->page = page; rtiff->n = n; rtiff->autorotate = autorotate; + rtiff->subifd = subifd; rtiff->tiff = NULL; rtiff->n_pages = 0; rtiff->current_page = -1; @@ -608,7 +621,8 @@ rtiff_set_page( Rtiff *rtiff, int page ) { if( rtiff->current_page != page ) { #ifdef DEBUG - printf( "rtiff_set_page: selecting page %d\n", page ); + printf( "rtiff_set_page: selecting page %d, subifd %d\n", + page, rtiff->subifd ); #endif /*DEBUG*/ if( !TIFFSetDirectory( rtiff->tiff, page ) ) { @@ -617,7 +631,43 @@ rtiff_set_page( Rtiff *rtiff, int page ) return( -1 ); } + if( rtiff->subifd >= 0 ) { + int subifd_count; + toff_t *subifd_offsets; + + if( !TIFFGetField( rtiff->tiff, TIFFTAG_SUBIFD, + &subifd_count, &subifd_offsets ) ) { + vips_error( "tiff2vips", + "%s", _( "no SUBIFD tag" ) ); + return( -1 ); + } + + if( subifd_count <= 0 || + rtiff->subifd >= subifd_count ) { + vips_error( "tiff2vips", + _( "subifd %d out of range, " + "only 0-%d available" ), + rtiff->subifd, + subifd_count - 1 ); + return( -1 ); + } + + if( !TIFFSetSubDirectory( rtiff->tiff, + subifd_offsets[rtiff->subifd] ) ) { + vips_error( "tiff2vips", + "%s", _( "subdirectory unreadable" ) ); + return( -1 ); + } + } + rtiff->current_page = page; + + /* This can get unset when we change directories. Make sure + * it's set again. + */ + if( rtiff->header.compression == COMPRESSION_JPEG ) + TIFFSetField( rtiff->tiff, + TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB ); } return( 0 ); @@ -633,7 +683,9 @@ rtiff_n_pages( Rtiff *rtiff ) for( n = 1; TIFFReadDirectory( rtiff->tiff ); n++ ) ; - (void) TIFFSetDirectory( rtiff->tiff, rtiff->current_page ); + /* Make sure the nest set_page() will set the directory. + */ + rtiff->current_page = -1; #ifdef DEBUG printf( "rtiff_n_pages: found %d pages\n", n ); @@ -1512,6 +1564,10 @@ rtiff_set_header( Rtiff *rtiff, VipsImage *out ) vips_image_set_int( out, VIPS_META_PAGE_HEIGHT, rtiff->header.height ); + if( rtiff->header.subifd_count > 0 ) + vips_image_set_int( out, + VIPS_META_N_SUBIFDS, rtiff->header.subifd_count ); + vips_image_set_int( out, VIPS_META_N_PAGES, rtiff->n_pages ); /* Even though we could end up serving tiled data, always hint @@ -1520,17 +1576,6 @@ rtiff_set_header( Rtiff *rtiff, VipsImage *out ) */ vips_image_pipelinev( out, VIPS_DEMAND_STYLE_THINSTRIP, NULL ); -#ifdef DEBUG - printf( "rtiff_set_header: header.samples_per_pixel = %d\n", - rtiff->header.samples_per_pixel ); - printf( "rtiff_set_header: header.bits_per_sample = %d\n", - rtiff->header.bits_per_sample ); - printf( "rtiff_set_header: header.sample_format = %d\n", - rtiff->header.sample_format ); - printf( "rtiff_set_header: header.orientation = %d\n", - rtiff->header.orientation ); -#endif /*DEBUG*/ - /* We have a range of output paths. Look at the tiff header and try to * route the input image to the best output path. */ @@ -1540,18 +1585,16 @@ rtiff_set_header( Rtiff *rtiff, VipsImage *out ) /* Read any ICC profile. */ if( TIFFGetField( rtiff->tiff, - TIFFTAG_ICCPROFILE, &data_length, &data ) ) { + TIFFTAG_ICCPROFILE, &data_length, &data ) ) vips_image_set_blob_copy( out, VIPS_META_ICC_NAME, data, data_length ); - } /* Read any XMP metadata. */ if( TIFFGetField( rtiff->tiff, - TIFFTAG_XMLPACKET, &data_length, &data ) ) { + TIFFTAG_XMLPACKET, &data_length, &data ) ) vips_image_set_blob_copy( out, VIPS_META_XMP_NAME, data, data_length ); - } /* Read any IPTC metadata. */ @@ -1569,20 +1612,13 @@ rtiff_set_header( Rtiff *rtiff, VipsImage *out ) /* Read any photoshop metadata. */ if( TIFFGetField( rtiff->tiff, - TIFFTAG_PHOTOSHOP, &data_length, &data ) ) { + TIFFTAG_PHOTOSHOP, &data_length, &data ) ) vips_image_set_blob_copy( out, VIPS_META_PHOTOSHOP_NAME, data, data_length ); - } - /* IMAGEDESCRIPTION often has useful metadata. - */ - if( TIFFGetField( rtiff->tiff, TIFFTAG_IMAGEDESCRIPTION, &data ) ) { - /* libtiff makes sure that data is null-terminated and contains - * no embedded null characters. - */ - vips_image_set_string( out, - VIPS_META_IMAGEDESCRIPTION, (char *) data ); - } + if( rtiff->header.image_description ) + vips_image_set_string( out, VIPS_META_IMAGEDESCRIPTION, + rtiff->header.image_description ); if( get_resolution( rtiff->tiff, out ) ) return( -1 ); @@ -1689,7 +1725,7 @@ rtiff_fill_region_unaligned( VipsRegion *out, /* Not necessary, but it stops static analyzers complaining * about a used-before-set. */ - tile.height = 0; + hit.height = 0; x = 0; while( x < r->width ) { @@ -1753,10 +1789,13 @@ rtiff_fill_region_unaligned( VipsRegion *out, q, p, hit.width, rtiff->client ); } - x += tile.width; + x += hit.width; } - y += tile.height; + /* This will be the same for all tiles in the row we've just + * done. + */ + y += hit.height; } return( 0 ); @@ -1823,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 @@ -1913,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*/ @@ -1952,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 ); @@ -2187,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*/ @@ -2197,19 +2197,6 @@ rtiff_read_stripwise( Rtiff *rtiff, VipsImage *out ) vips_image_pipelinev( t[0], VIPS_DEMAND_STYLE_THINSTRIP, NULL ); -#ifdef DEBUG - printf( "rtiff_read_stripwise: header.rows_per_strip = %u\n", - rtiff->header.rows_per_strip ); - printf( "rtiff_read_stripwise: header.strip_size = %zd\n", - rtiff->header.strip_size ); - printf( "rtiff_read_stripwise: header.number_of_strips = %d\n", - rtiff->header.number_of_strips ); - printf( "rtiff_read_stripwise: header.read_height = %u\n", - rtiff->header.read_height ); - printf( "rtiff_read_stripwise: header.read_size = %zd\n", - rtiff->header.read_size ); -#endif /*DEBUG*/ - /* Double check: in memcpy mode, the vips linesize should exactly * match the tiff line size. */ @@ -2275,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 ); @@ -2290,6 +2288,8 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header ) { uint16 extra_samples_count; uint16 *extra_samples_types; + toff_t *subifd_offsets; + char *image_description; if( !tfget32( rtiff->tiff, TIFFTAG_IMAGEWIDTH, &header->width ) || @@ -2387,10 +2387,42 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header ) header->separate = TRUE; } + /* Stays zero if there's no SUBIFD. + */ + TIFFGetField( rtiff->tiff, TIFFTAG_SUBIFD, + &header->subifd_count, &subifd_offsets ); + + /* IMAGEDESCRIPTION often has useful metadata. libtiff makes sure + * that data is null-terminated and contains no embedded null + * characters. + */ + if( TIFFGetField( rtiff->tiff, + TIFFTAG_IMAGEDESCRIPTION, &image_description ) ) + header->image_description = + vips_strdup( VIPS_OBJECT( rtiff->out ), + image_description ); + /* Tiles and strip images have slightly different fields. */ header->tiled = TIFFIsTiled( rtiff->tiff ); +#ifdef DEBUG + printf( "rtiff_header_read: header.width = %d\n", + header->width ); + printf( "rtiff_header_read: header.height = %d\n", + header->height ); + printf( "rtiff_header_read: header.samples_per_pixel = %d\n", + header->samples_per_pixel ); + printf( "rtiff_header_read: header.bits_per_sample = %d\n", + header->bits_per_sample ); + printf( "rtiff_header_read: header.sample_format = %d\n", + header->sample_format ); + printf( "rtiff_header_read: header.orientation = %d\n", + header->orientation ); + printf( "rtiff_header_read: header.tiled = %d\n", + header->tiled ); +#endif /*DEBUG*/ + if( header->tiled ) { if( !tfget32( rtiff->tiff, TIFFTAG_TILEWIDTH, &header->tile_width ) || @@ -2398,6 +2430,13 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header ) TIFFTAG_TILELENGTH, &header->tile_height ) ) return( -1 ); +#ifdef DEBUG + printf( "rtiff_header_read: header.tile_width = %d\n", + header->tile_width ); + printf( "rtiff_header_read: header.tile_height = %d\n", + header->tile_height ); +#endif /*DEBUG*/ + /* Arbitrary sanity-checking limits. */ if( header->tile_width <= 0 || @@ -2412,6 +2451,13 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header ) header->tile_size = TIFFTileSize( rtiff->tiff ); header->tile_row_size = TIFFTileRowSize( rtiff->tiff ); +#ifdef DEBUG + printf( "rtiff_header_read: header.tile_size = %zd\n", + header->tile_size ); + printf( "rtiff_header_read: header.tile_row_size = %zd\n", + header->tile_row_size ); +#endif /*DEBUG*/ + /* Fuzzed TIFFs can give crazy values for tile_size. Sanity * check at 100mb per tile. */ @@ -2440,6 +2486,17 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header ) header->scanline_size = TIFFScanlineSize( rtiff->tiff ); header->number_of_strips = TIFFNumberOfStrips( rtiff->tiff ); +#ifdef DEBUG + printf( "rtiff_header_read: header.rows_per_strip = %d\n", + header->rows_per_strip ); + printf( "rtiff_header_read: header.strip_size = %zd\n", + header->strip_size ); + printf( "rtiff_header_read: header.scanline_size = %zd\n", + header->scanline_size ); + printf( "rtiff_header_read: header.number_of_strips = %d\n", + header->number_of_strips ); +#endif /*DEBUG*/ + /* libtiff has two strip-wise readers. TIFFReadEncodedStrip() * decompresses an entire strip to memory. It's fast, but it * will need a lot of ram if the strip is large. @@ -2478,6 +2535,15 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header ) header->read_size = header->strip_size; } +#ifdef DEBUG + printf( "rtiff_header_read: header.read_scanlinewise = %d\n", + header->read_scanlinewise ); + printf( "rtiff_header_read: header.read_height = %d\n", + header->read_height ); + printf( "rtiff_header_read: header.read_size = %zd\n", + header->read_size ); +#endif /*DEBUG*/ + /* Stop some compiler warnings. */ header->tile_width = 0; @@ -2545,7 +2611,8 @@ static int rtiff_header_read_all( Rtiff *rtiff ) { #ifdef DEBUG - printf( "tiff2vips: reading header for page %d ...\n", rtiff->page ); + printf( "rtiff_header_read_all: " + "reading header for page %d ...\n", rtiff->page ); #endif /*DEBUG*/ if( rtiff_set_page( rtiff, rtiff->page ) || @@ -2567,7 +2634,8 @@ rtiff_header_read_all( Rtiff *rtiff ) RtiffHeader header; #ifdef DEBUG - printf( "tiff2vips: verifying header for page %d ...\n", + printf( "rtiff_header_read_all: " + "verifying header for page %d ...\n", rtiff->page + i ); #endif /*DEBUG*/ @@ -2587,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 @@ -2646,20 +2691,24 @@ vips__istifftiled_source( VipsSource *source ) int vips__tiff_read_header_source( VipsSource *source, VipsImage *out, - int page, int n, gboolean autorotate ) + int page, int n, gboolean autorotate, int subifd ) { Rtiff *rtiff; vips__tiff_init(); - if( !(rtiff = rtiff_new( source, out, page, n, autorotate )) || + if( !(rtiff = rtiff_new( source, out, page, n, autorotate, subifd )) || rtiff_header_read_all( rtiff ) ) return( -1 ); 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, @@ -2671,7 +2720,7 @@ vips__tiff_read_header_source( VipsSource *source, VipsImage *out, int vips__tiff_read_source( VipsSource *source, VipsImage *out, - int page, int n, gboolean autorotate ) + int page, int n, gboolean autorotate, int subifd ) { Rtiff *rtiff; @@ -2681,7 +2730,7 @@ vips__tiff_read_source( VipsSource *source, VipsImage *out, vips__tiff_init(); - if( !(rtiff = rtiff_new( source, out, page, n, autorotate )) || + if( !(rtiff = rtiff_new( source, out, page, n, autorotate, subifd )) || rtiff_header_read_all( rtiff ) ) return( -1 ); diff --git a/libvips/foreign/tiffload.c b/libvips/foreign/tiffload.c index 20b2b094..58cbb6ab 100644 --- a/libvips/foreign/tiffload.c +++ b/libvips/foreign/tiffload.c @@ -69,6 +69,10 @@ typedef struct _VipsForeignLoadTiff { */ int n; + /* Select subifd index. -1 for main image. + */ + int subifd; + /* Autorotate using orientation tag. */ gboolean autorotate; @@ -133,7 +137,7 @@ vips_foreign_load_tiff_header( VipsForeignLoad *load ) VipsForeignLoadTiff *tiff = (VipsForeignLoadTiff *) load; if( vips__tiff_read_header_source( tiff->source, load->out, - tiff->page, tiff->n, tiff->autorotate ) ) + tiff->page, tiff->n, tiff->autorotate, tiff->subifd ) ) return( -1 ); return( 0 ); @@ -145,7 +149,7 @@ vips_foreign_load_tiff_load( VipsForeignLoad *load ) VipsForeignLoadTiff *tiff = (VipsForeignLoadTiff *) load; if( vips__tiff_read_source( tiff->source, load->real, - tiff->page, tiff->n, tiff->autorotate ) ) + tiff->page, tiff->n, tiff->autorotate, tiff->subifd ) ) return( -1 ); return( 0 ); @@ -203,6 +207,14 @@ vips_foreign_load_tiff_class_init( VipsForeignLoadTiffClass *class ) VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignLoadTiff, autorotate ), FALSE ); + + VIPS_ARG_INT( class, "subifd", 21, + _( "subifd" ), + _( "Select subifd index" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignLoadTiff, subifd ), + -1, 100000, -1 ); + } static void @@ -210,6 +222,7 @@ vips_foreign_load_tiff_init( VipsForeignLoadTiff *tiff ) { tiff->page = 0; tiff->n = 1; + tiff->subifd = -1; } typedef struct _VipsForeignLoadTiffSource { @@ -454,6 +467,7 @@ vips_foreign_load_tiff_buffer_init( VipsForeignLoadTiffBuffer *buffer ) * * @n: %gint, load this many pages * * @autorotate: %gboolean, use orientation tag to rotate the image * during load + * * @subifd: %gint, select this subifd index * * Read a TIFF file into a VIPS image. It is a full baseline TIFF 6 reader, * with extensions for tiled images, multipage images, XYZ and LAB colour @@ -478,6 +492,14 @@ vips_foreign_load_tiff_buffer_init( VipsForeignLoadTiffBuffer *buffer ) * operations will use #VIPS_META_ORIENTATION, if present, to set the * orientation of output images. * + * If @autorotate is TRUE, the image will be rotated upright during load and + * no metadata attached. This can be very slow. + * + * If @subifd is -1 (the default), the main image is selected for each page. + * If it is 0 or greater and there is a SUBIFD tag, the indexed SUBIFD is + * selected. This can be used to read lower resolution layers from + * bioformats-style image pyramids. + * * Any ICC profile is read and attached to the VIPS image as * #VIPS_META_ICC_NAME. Any XMP metadata is read and attached to the image * as #VIPS_META_XMP_NAME. Any IPTC is attached as #VIPS_META_IPTC_NAME. The @@ -515,6 +537,7 @@ vips_tiffload( const char *filename, VipsImage **out, ... ) * * @n: %gint, load this many pages * * @autorotate: %gboolean, use orientation tag to rotate the image * during load + * * @subifd: %gint, select this subifd index * * Read a TIFF-formatted memory block into a VIPS image. Exactly as * vips_tiffload(), but read from a memory source. @@ -558,6 +581,7 @@ vips_tiffload_buffer( void *buf, size_t len, VipsImage **out, ... ) * * @n: %gint, load this many pages * * @autorotate: %gboolean, use orientation tag to rotate the image * during load + * * @subifd: %gint, select this subifd index * * Exactly as vips_tiffload(), but read from a source. * diff --git a/libvips/foreign/tiffsave.c b/libvips/foreign/tiffsave.c index 8644a820..b66ce998 100644 --- a/libvips/foreign/tiffsave.c +++ b/libvips/foreign/tiffsave.c @@ -21,6 +21,8 @@ * - xres/yres params were in pixels/cm * 26/1/20 * - add "depth" to set pyr depth + * 12/5/20 + * - add "subifd" to create pyr layers as sub-directories */ /* @@ -98,6 +100,8 @@ typedef struct _VipsForeignSaveTiff { int level; gboolean lossless; VipsForeignDzDepth depth; + gboolean subifd; + } VipsForeignSaveTiff; typedef VipsForeignSaveClass VipsForeignSaveTiffClass; @@ -327,7 +331,7 @@ vips_foreign_save_tiff_class_init( VipsForeignSaveTiffClass *class ) 1, 22, 10 ); VIPS_ARG_BOOL( class, "lossless", 24, - _( "lossless" ), + _( "Lossless" ), _( "Enable WEBP lossless mode" ), VIPS_ARGUMENT_OPTIONAL_INPUT, G_STRUCT_OFFSET( VipsForeignSaveTiff, lossless ), @@ -340,6 +344,13 @@ vips_foreign_save_tiff_class_init( VipsForeignSaveTiffClass *class ) G_STRUCT_OFFSET( VipsForeignSaveTiff, depth ), VIPS_TYPE_FOREIGN_DZ_DEPTH, VIPS_FOREIGN_DZ_DEPTH_ONETILE ); + VIPS_ARG_BOOL( class, "subifd", 24, + _( "Sub-IFD" ), + _( "Save pyr layers as sub-IFDs" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveTiff, subifd ), + FALSE ); + } static void @@ -396,7 +407,8 @@ vips_foreign_save_tiff_file_build( VipsObject *object ) tiff->region_shrink, tiff->level, tiff->lossless, - tiff->depth ) ) + tiff->depth, + tiff->subifd ) ) return( -1 ); return( 0 ); @@ -468,7 +480,8 @@ vips_foreign_save_tiff_buffer_build( VipsObject *object ) tiff->region_shrink, tiff->level, tiff->lossless, - tiff->depth ) ) + tiff->depth, + tiff->subifd ) ) return( -1 ); /* vips__tiff_write_buf() makes a buffer that needs g_free(), not @@ -537,6 +550,7 @@ vips_foreign_save_tiff_buffer_init( VipsForeignSaveTiffBuffer *buffer ) * * @level: %gint, Zstd compression level * * @lossless: %gboolean, WebP losssless mode * * @depth: #VipsForeignDzDepth how deep to make the pyramid + * * @subifd: %gboolean write pyr layers as sub-ifds * * Write a VIPS image to a file as TIFF. * @@ -620,6 +634,10 @@ vips_foreign_save_tiff_buffer_init( VipsForeignSaveTiffBuffer *buffer ) * #VIPS_META_PHOTOSHOP_NAME (if set) is used to set the value of the PHOTOSHOP * tag. * + * By default, pyramid layers are saved as consecutive pages. + * Set @subifd to save pyramid layers as sub-directories of the main image. + * Setting this option can improve compatibility with formats like OME. + * * See also: vips_tiffload(), vips_image_write_to_file(). * * Returns: 0 on success, -1 on error. @@ -665,6 +683,7 @@ vips_tiffsave( VipsImage *in, const char *filename, ... ) * * @level: %gint, Zstd compression level * * @lossless: %gboolean, WebP losssless mode * * @depth: #VipsForeignDzDepth how deep to make the pyramid + * * @subifd: %gboolean write pyr layers as sub-ifds * * As vips_tiffsave(), but save to a memory buffer. * diff --git a/libvips/foreign/vips2tiff.c b/libvips/foreign/vips2tiff.c index c2c46854..84cf8538 100644 --- a/libvips/foreign/vips2tiff.c +++ b/libvips/foreign/vips2tiff.c @@ -193,6 +193,8 @@ * - write XYZ images as logluv * 7/2/20 [jclavoie-jive] * - add PAGENUMBER support + * 23/5/20 + * - add support for subifd pyramid layers */ /* @@ -247,6 +249,21 @@ #include "pforeign.h" #include "tiff.h" +/* TODO: + * + * - add a flag for plane-separate write + * + * At the moment, we write bioformats-style TIFFs by splitting bands up, + * making a toilet-roll image and writing out in pages. The TIFFs we make + * are not tagged as plane-separate and do not have (eg.) RGB photometric + * interpretation. Moreover, when working from an RGB source, we'll end + * up reading the input three times. + * + * A write-plane-separate flag to the TIFF writer could let us set the + * photometric interpretation correctly, and save all planes in a single + * pass before doing a final gather sweep. + */ + /* Max number of alpha channels we allow. */ #define MAX_ALPHA (64) @@ -334,6 +351,7 @@ struct _Wtiff { int level; /* zstd compression level */ gboolean lossless; /* webp lossless mode */ VipsForeignDzDepth depth; /* Pyr depth */ + gboolean subifd; /* Write pyr layers into subifds */ /* True if we've detected a toilet-roll image, plus the page height, * which has been checked to be a factor of im->Ysize. page_number @@ -395,40 +413,62 @@ embed_profile_meta( TIFF *tif, VipsImage *im ) return( 0 ); } -static Layer * -wtiff_layer_new( Wtiff *wtiff, Layer *above, int width, int height ) +static void +wtiff_layer_init( Wtiff *wtiff, Layer **layer, Layer *above, + int width, int height ) { - Layer *layer; + if( !*layer ) { + *layer = VIPS_NEW( wtiff->ready, Layer ); + (*layer)->wtiff = wtiff; + (*layer)->width = width; + (*layer)->height = height; - layer = VIPS_NEW( wtiff->ready, Layer ); - layer->wtiff = wtiff; - layer->width = width; - layer->height = height; + if( !above ) + /* Top of pyramid. + */ + (*layer)->sub = 1; + else + (*layer)->sub = above->sub * 2; - if( !above ) - /* Top of pyramid. + (*layer)->lname = NULL; + (*layer)->buf = NULL; + (*layer)->len = 0; + (*layer)->tif = NULL; + (*layer)->image = NULL; + (*layer)->write_y = 0; + (*layer)->y = 0; + (*layer)->strip = NULL; + (*layer)->copy = NULL; + + (*layer)->below = NULL; + (*layer)->above = above; + + /* The name for the top layer is the output filename. + * + * We need lname to be freed automatically: it has to stay + * alive until after wtiff_gather(). */ - layer->sub = 1; - else - layer->sub = above->sub * 2; + if( wtiff->filename ) { + if( !above ) + (*layer)->lname = vips_strdup( + VIPS_OBJECT( wtiff->ready ), + wtiff->filename ); + else { + char *lname; - layer->lname = NULL; - layer->buf = NULL; - layer->len = 0; - layer->tif = NULL; - layer->image = NULL; - layer->write_y = 0; - layer->y = 0; - layer->strip = NULL; - layer->copy = NULL; + lname = vips__temp_name( "%s.tif" ); + (*layer)->lname = vips_strdup( + VIPS_OBJECT( wtiff->ready ), + lname ); + g_free( lname ); + } + } - layer->below = NULL; - layer->above = above; - - /* - printf( "wtiff_layer_new: sub = %d, width = %d, height = %d\n", - layer->sub, width, height ); - */ + /* + printf( "wtiff_layer_init: sub = %d, width = %d, height = %d\n", + (*layer)->sub, width, height ); + */ + } if( wtiff->pyramid ) { int limitw, limith; @@ -458,34 +498,13 @@ wtiff_layer_new( Wtiff *wtiff, Layer *above, int width, int height ) * Very tall or wide images might end up with a smallest layer * larger than one tile. */ - if( (layer->width > limitw || - layer->height > limith) && - layer->width > 1 && - layer->height > 1 ) - layer->below = wtiff_layer_new( wtiff, layer, + if( ((*layer)->width > limitw || + (*layer)->height > limith) && + (*layer)->width > 1 && + (*layer)->height > 1 ) + wtiff_layer_init( wtiff, &(*layer)->below, *layer, width / 2, height / 2 ); } - - /* The name for the top layer is the output filename. - * - * We need lname to be freed automatically: it has to stay - * alive until after wtiff_gather(). - */ - if( wtiff->filename ) { - if( !above ) - layer->lname = vips_strdup( VIPS_OBJECT( wtiff->ready ), - wtiff->filename ); - else { - char *lname; - - lname = vips__temp_name( "%s.tif" ); - layer->lname = vips_strdup( VIPS_OBJECT( wtiff->ready ), - lname ); - g_free( lname ); - } - } - - return( layer ); } static int @@ -617,6 +636,11 @@ wtiff_write_header( Wtiff *wtiff, Layer *layer ) int orientation; +#ifdef DEBUG + printf( "wtiff_write_header: sub %d, width %d, height %d\n", + layer->sub, layer->width, layer->height ); +#endif /*DEBUG*/ + /* Output base header fields. */ TIFFSetField( tif, TIFFTAG_IMAGEWIDTH, layer->width ); @@ -845,40 +869,56 @@ wtiff_allocate_layers( Wtiff *wtiff ) { Layer *layer; + g_assert( wtiff->layer ); + for( layer = wtiff->layer; layer; layer = layer->below ) { - layer->image = vips_image_new(); - if( vips_image_pipelinev( layer->image, - VIPS_DEMAND_STYLE_ANY, wtiff->ready, NULL ) ) - return( -1 ); - layer->image->Xsize = layer->width; - layer->image->Ysize = layer->height; + if( !layer->image ) { + layer->image = vips_image_new(); + if( vips_image_pipelinev( layer->image, + VIPS_DEMAND_STYLE_ANY, wtiff->ready, NULL ) ) + return( -1 ); + layer->image->Xsize = layer->width; + layer->image->Ysize = layer->height; - layer->strip = vips_region_new( layer->image ); - layer->copy = vips_region_new( layer->image ); + layer->strip = vips_region_new( layer->image ); + layer->copy = vips_region_new( layer->image ); - /* The regions will get used in the bg thread callback, so - * make sure we don't own them. - */ - vips__region_no_ownership( layer->strip ); - vips__region_no_ownership( layer->copy ); + /* The regions will get used in the bg thread callback, + * so make sure we don't own them. + */ + vips__region_no_ownership( layer->strip ); + vips__region_no_ownership( layer->copy ); + + if( layer->lname ) + layer->tif = vips__tiff_openout( + layer->lname, wtiff->bigtiff ); + else { + layer->tif = vips__tiff_openout_buffer( + wtiff->ready, wtiff->bigtiff, + &layer->buf, &layer->len ); + } + if( !layer->tif ) + return( -1 ); + } if( wtiff_layer_rewind( wtiff, layer ) ) return( -1 ); - if( layer->lname ) - layer->tif = vips__tiff_openout( - layer->lname, wtiff->bigtiff ); - else { - layer->tif = vips__tiff_openout_buffer( wtiff->ready, - wtiff->bigtiff, &layer->buf, &layer->len ); - } - if( !layer->tif ) - return( -1 ); - if( wtiff_write_header( wtiff, layer ) ) return( -1 ); } + if( !wtiff->tbuf ) { + if( wtiff->tile ) + wtiff->tbuf = vips_malloc( NULL, + TIFFTileSize( wtiff->layer->tif ) ); + else + wtiff->tbuf = vips_malloc( NULL, + TIFFScanlineSize( wtiff->layer->tif ) ); + if( !wtiff->tbuf ) + return( -1 ); + } + return( 0 ); } @@ -1032,7 +1072,8 @@ wtiff_new( VipsImage *input, const char *filename, VipsRegionShrink region_shrink, int level, gboolean lossless, - VipsForeignDzDepth depth ) + VipsForeignDzDepth depth, + gboolean subifd ) { Wtiff *wtiff; @@ -1064,6 +1105,7 @@ wtiff_new( VipsImage *input, const char *filename, wtiff->level = level; wtiff->lossless = lossless; wtiff->depth = depth; + wtiff->subifd = subifd; wtiff->toilet_roll = FALSE; wtiff->page_height = vips_image_get_page_height( input ); wtiff->page_number = 0; @@ -1096,28 +1138,6 @@ wtiff_new( VipsImage *input, const char *filename, wtiff->toilet_roll = TRUE; wtiff->image_height = wtiff->page_height; wtiff->n_pages = wtiff->ready->Ysize / wtiff->page_height; - - /* We can't pyramid toilet roll images. - */ - if( wtiff->pyramid ) { - g_warning( "%s", - _( "can't pyramid multi page images --- " - "disabling pyramid" ) ); - wtiff->pyramid = FALSE; - } - } - - /* In strip mode we use tileh to set rowsperstrip, and that does not - * have the multiple-of-16 restriction. - */ - if( tile ) { - if( (wtiff->tilew & 0xf) != 0 || - (wtiff->tileh & 0xf) != 0 ) { - wtiff_free( wtiff ); - vips_error( "vips2tiff", - "%s", _( "tile size not a multiple of 16" ) ); - return( NULL ); - } } /* We can only pyramid LABQ and non-complex images. @@ -1133,6 +1153,41 @@ wtiff_new( VipsImage *input, const char *filename, } } + /* Pyramid images must be tiled. + */ + if( wtiff->pyramid && + !wtiff->tile ) + wtiff->tile = TRUE; + + /* Multi-page pyramids must be in subifd mode. + */ + if( wtiff->pyramid && + wtiff->toilet_roll ) + wtiff->subifd = TRUE; + + /* If compression is off and we're writing a >4gb image, automatically + * enable bigtiff. + * + * This won't always work. If the image data is just under 4gb but + * there's a lot of metadata, we could be pushed over the 4gb limit. + */ + if( wtiff->compression == COMPRESSION_NONE && + VIPS_IMAGE_SIZEOF_IMAGE( wtiff->ready ) > UINT_MAX ) + wtiff->bigtiff = TRUE; + + /* In strip mode we use tileh to set rowsperstrip, and that does not + * have the multiple-of-16 restriction. + */ + if( wtiff->tile ) { + if( (wtiff->tilew & 0xf) != 0 || + (wtiff->tileh & 0xf) != 0 ) { + wtiff_free( wtiff ); + vips_error( "vips2tiff", + "%s", _( "tile size not a multiple of 16" ) ); + return( NULL ); + } + } + /* Can only squash 8 bit mono. 3-band float should have been squashed * above. */ @@ -1190,42 +1245,6 @@ wtiff_new( VipsImage *input, const char *filename, wtiff->tls = VIPS_IMAGE_SIZEOF_PEL( wtiff->ready ) * wtiff->tilew; - /* If compression is off and we're writing a >4gb image, automatically - * enable bigtiff. - * - * This won't always work. If the image data is just under 4gb but - * there's a lot of metadata, we could be pushed over the 4gb limit. - */ - if( wtiff->compression == COMPRESSION_NONE && - VIPS_IMAGE_SIZEOF_IMAGE( wtiff->ready ) > UINT_MAX && - !wtiff->bigtiff ) { - g_warning( "%s", _( "image over 4gb, enabling bigtiff" ) ); - wtiff->bigtiff = TRUE; - } - - /* Build the pyramid framework. - */ - wtiff->layer = wtiff_layer_new( wtiff, NULL, - wtiff->ready->Xsize, wtiff->image_height ); - - /* Fill all the layers. - */ - if( wtiff_allocate_layers( wtiff ) ) { - wtiff_free( wtiff ); - return( NULL ); - } - - if( tile ) - wtiff->tbuf = vips_malloc( NULL, - TIFFTileSize( wtiff->layer->tif ) ); - else - wtiff->tbuf = vips_malloc( NULL, - TIFFScanlineSize( wtiff->layer->tif ) ); - if( !wtiff->tbuf ) { - wtiff_free( wtiff ); - return( NULL ); - } - return( wtiff ); } @@ -1693,10 +1712,10 @@ write_strip( VipsRegion *region, VipsRect *area, void *a ) Wtiff *wtiff = (Wtiff *) a; Layer *layer = wtiff->layer; -#ifdef DEBUG +#ifdef DEBUG_VERBOSE printf( "write_strip: strip at %d, height %d\n", area->top, area->height ); -#endif/*DEBUG*/ +#endif/*DEBUG_VERBOSE*/ for(;;) { VipsRect *to = &layer->strip->valid; @@ -1858,7 +1877,7 @@ wtiff_copy_tiff( Wtiff *wtiff, TIFF *out, TIFF *in ) return( 0 ); } -/* Append all of the lower layers we wrote to the output. +/* Append all of the layers we wrote to the output. */ static int wtiff_gather( Wtiff *wtiff ) @@ -1873,13 +1892,12 @@ wtiff_gather( Wtiff *wtiff ) TIFF *in; #ifdef DEBUG - printf( "Appending layer %s ...\n", layer->lname ); + printf( "appending layer %s ...\n", layer->lname ); #endif /*DEBUG*/ if( layer->lname ) { - if( !(source = - vips_source_new_from_file( - layer->lname )) ) + if( !(source = vips_source_new_from_file( + layer->lname )) ) return( -1 ); } else { @@ -1892,12 +1910,14 @@ wtiff_gather( Wtiff *wtiff ) VIPS_UNREF( source ); return( -1 ); } + VIPS_UNREF( source ); if( wtiff_copy_tiff( wtiff, wtiff->layer->tif, in ) ) { TIFFClose( in ); return( -1 ); } + TIFFClose( in ); if( !TIFFWriteDirectory( wtiff->layer->tif ) ) @@ -1907,77 +1927,109 @@ wtiff_gather( Wtiff *wtiff ) return( 0 ); } -/* Three types of write: single image, multipage and pyramid. +/* Write one page from our input image, optionally pyramiding it. + */ +static int +wtiff_write_page( Wtiff *wtiff, VipsImage *page ) +{ +#ifdef DEBUG + printf( "wtiff_write_page:\n" ); +#endif /*DEBUG*/ + + /* Init the pyramid framework for this page. This will just make a + * single layer if we're not pyramiding. + */ + wtiff_layer_init( wtiff, &wtiff->layer, NULL, + page->Xsize, page->Ysize ); + + /* Fill all the layers and write the TIFF headers. + */ + if( wtiff_allocate_layers( wtiff ) ) + return( -1 ); + + /* In ifd mode, we write the pyramid layers as subdirectories of this + * page. + */ + if( wtiff->subifd ) { + int n_layers; + toff_t *subifd_offsets; + Layer *p; + +#ifdef DEBUG + printf( "wtiff_write_page: OME pyr mode\n" ); +#endif /*DEBUG*/ + + /* This magic tag makes the n_layers directories we write + * after this one into subdirectories. We set the offsets to 0 + * and libtiff will fill them in automatically. + */ + for( n_layers = 0, p = wtiff->layer->below; p; p = p->below ) + n_layers += 1; + subifd_offsets = VIPS_ARRAY( NULL, n_layers, toff_t ); + memset( subifd_offsets, 0, n_layers * sizeof( toff_t ) ); + TIFFSetField( wtiff->layer->tif, TIFFTAG_SUBIFD, + n_layers, subifd_offsets ); + g_free( subifd_offsets ); + } + + if( vips_sink_disc( page, write_strip, wtiff ) ) + return( -1 ); + + if( !TIFFWriteDirectory( wtiff->layer->tif ) ) + return( -1 ); + + /* Append any pyr layers, if necessary. + */ + if( wtiff->layer->below ) { + /* Free any lower pyramid resources ... this will + * TIFFClose() (but not delete) the smaller layers + * ready for us to read from them again. + */ + layer_free_all( wtiff->layer->below ); + + /* Append smaller layers to the main file. + */ + if( wtiff_gather( wtiff ) ) + return( -1 ); + + /* We can delete any temps now ready for the next page. + */ + wtiff_delete_temps( wtiff ); + + /* And free all lower pyr layers ready to be rebuilt for the + * next page. + */ + VIPS_FREEF( layer_free_all, wtiff->layer->below ); + } + + return( 0 ); +} + +/* Write all pages. */ static int wtiff_write_image( Wtiff *wtiff ) { - if( wtiff->toilet_roll ) { - int y; + int y; + + for( y = 0; y < wtiff->ready->Ysize; y += wtiff->page_height ) { + VipsImage *page; #ifdef DEBUG - printf( "wtiff_write_image: toilet-roll mode\n" ); + printf( "writing page %d ...\n", wtiff->page_number ); #endif /*DEBUG*/ - y = 0; - for(;;) { - VipsImage *page; - - if( vips_crop( wtiff->ready, &page, - 0, y, wtiff->ready->Xsize, wtiff->page_height, - NULL ) ) - return( -1 ); - if( vips_sink_disc( page, write_strip, wtiff ) ) { - g_object_unref( page ); - return( -1 ); - } + if( vips_crop( wtiff->ready, &page, + 0, y, wtiff->ready->Xsize, wtiff->page_height, + NULL ) ) + return( -1 ); + if( wtiff_write_page( wtiff, page ) ) { g_object_unref( page ); - - wtiff->page_number += 1; - y += wtiff->page_height; - if( y >= wtiff->ready->Ysize ) - break; - - if( !TIFFWriteDirectory( wtiff->layer->tif ) || - wtiff_layer_rewind( wtiff, - wtiff->layer ) || - wtiff_write_header( wtiff, - wtiff->layer ) ) - return( -1 ); + return( -1 ); } - } - else if( wtiff->pyramid ) { -#ifdef DEBUG - printf( "wtiff_write_image: pyramid mode\n" ); -#endif /*DEBUG*/ + g_object_unref( page ); - if( vips_sink_disc( wtiff->ready, write_strip, wtiff ) ) - return( -1 ); - - if( !TIFFWriteDirectory( wtiff->layer->tif ) ) - return( -1 ); - - if( wtiff->pyramid ) { - /* Free lower pyramid resources ... this will - * TIFFClose() (but not delete) the smaller layers - * ready for us to read from them again. - */ - if( wtiff->layer->below ) - layer_free_all( wtiff->layer->below ); - - /* Append smaller layers to the main file. - */ - if( wtiff_gather( wtiff ) ) - return( -1 ); - } - } - else { -#ifdef DEBUG - printf( "wtiff_write_image: single-image mode\n" ); -#endif /*DEBUG*/ - - if( vips_sink_disc( wtiff->ready, write_strip, wtiff ) ) - return( -1 ); + wtiff->page_number += 1; } return( 0 ); @@ -1999,7 +2051,8 @@ vips__tiff_write( VipsImage *input, const char *filename, VipsRegionShrink region_shrink, int level, gboolean lossless, - VipsForeignDzDepth depth ) + VipsForeignDzDepth depth, + gboolean subifd ) { Wtiff *wtiff; @@ -2013,7 +2066,8 @@ vips__tiff_write( VipsImage *input, const char *filename, compression, Q, predictor, profile, tile, tile_width, tile_height, pyramid, squash, miniswhite, resunit, xres, yres, bigtiff, rgbjpeg, - properties, strip, region_shrink, level, lossless, depth )) ) + properties, strip, region_shrink, level, lossless, depth, + subifd )) ) return( -1 ); if( wtiff_write_image( wtiff ) ) { @@ -2043,7 +2097,8 @@ vips__tiff_write_buf( VipsImage *input, VipsRegionShrink region_shrink, int level, gboolean lossless, - VipsForeignDzDepth depth ) + VipsForeignDzDepth depth, + gboolean subifd ) { Wtiff *wtiff; @@ -2053,7 +2108,8 @@ vips__tiff_write_buf( VipsImage *input, compression, Q, predictor, profile, tile, tile_width, tile_height, pyramid, squash, miniswhite, resunit, xres, yres, bigtiff, rgbjpeg, - properties, strip, region_shrink, level, lossless, depth )) ) + properties, strip, region_shrink, level, lossless, depth, + subifd )) ) return( -1 ); wtiff->obuf = obuf; diff --git a/libvips/include/vips/almostdeprecated.h b/libvips/include/vips/almostdeprecated.h index 26d90937..1bde7c84 100644 --- a/libvips/include/vips/almostdeprecated.h +++ b/libvips/include/vips/almostdeprecated.h @@ -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*/ diff --git a/libvips/include/vips/conversion.h b/libvips/include/vips/conversion.h index 86643e9f..9dfaa521 100644 --- a/libvips/include/vips/conversion.h +++ b/libvips/include/vips/conversion.h @@ -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)); diff --git a/libvips/include/vips/header.h b/libvips/include/vips/header.h index d4b3f6dc..11e9ea3f 100644 --- a/libvips/include/vips/header.h +++ b/libvips/include/vips/header.h @@ -150,6 +150,13 @@ extern "C" { */ #define VIPS_META_N_PAGES "n-pages" +/** + * VIPS_META_N_SUBIFDS: + * + * If set, the number of subifds in the first page of the file. + */ +#define VIPS_META_N_SUBIFDS "n-subifds" + guint64 vips_format_sizeof( VipsBandFormat format ); guint64 vips_format_sizeof_unsafe( VipsBandFormat format ); @@ -171,6 +178,9 @@ double vips_image_get_scale( const VipsImage *image ); 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, diff --git a/libvips/iofuncs/header.c b/libvips/iofuncs/header.c index dba1c767..4109b5d4 100644 --- a/libvips/iofuncs/header.c +++ b/libvips/iofuncs/header.c @@ -818,7 +818,7 @@ vips_image_get_page_height( VipsImage *image ) * vips_image_get_n_pages: (method) * @image: image to get from * - * Fetch and sanity-check VIPS_META_N_PAGES. Default to 1 if not present or + * Fetch and sanity-check #VIPS_META_N_PAGES. Default to 1 if not present or * crazy. * * This is the number of pages in the image file, not the number of pages that @@ -840,6 +840,70 @@ vips_image_get_n_pages( VipsImage *image ) return( 1 ); } +/** + * vips_image_get_n_subifds: (method) + * @image: image to get from + * + * Fetch and sanity-check #VIPS_META_N_SUBIFDS. Default to 0 if not present or + * crazy. + * + * Returns: the number of subifds in the image file + */ +int +vips_image_get_n_subifds( VipsImage *image ) +{ + int n_subifds; + + if( vips_image_get_typeof( image, VIPS_META_N_SUBIFDS ) && + !vips_image_get_int( image, VIPS_META_N_SUBIFDS, &n_subifds ) && + n_subifds > 1 && + n_subifds < 1000 ) + return( n_subifds ); + + 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 @@ -934,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 ); @@ -1032,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(). * @@ -1042,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 ); diff --git a/libvips/resample/affine.c b/libvips/resample/affine.c index cbf6903a..b75cd73d 100644 --- a/libvips/resample/affine.c +++ b/libvips/resample/affine.c @@ -88,6 +88,8 @@ * - add "background" parameter * - better clipping means we have no jaggies on edges * - premultiply alpha + * 18/5/20 + * - add "premultiplied" flag */ /* @@ -166,6 +168,10 @@ typedef struct _VipsAffine { */ VipsPel *ink; + /* True if the input is already premultiplied (and we don't need to). + */ + gboolean premultiplied; + } VipsAffine; typedef VipsResampleClass VipsAffineClass; @@ -524,11 +530,13 @@ vips_affine_build( VipsObject *object ) affine->trn.idx -= 1; affine->trn.idy -= 1; - /* If there's an alpha, we have to premultiply before resampling. See + /* If there's an alpha and we've not premultiplied, we have to + * premultiply before resampling. See * https://github.com/libvips/libvips/issues/291 */ have_premultiplied = FALSE; - if( vips_image_hasalpha( in ) ) { + if( vips_image_hasalpha( in ) && + !affine->premultiplied ) { if( vips_premultiply( in, &t[3], NULL ) ) return( -1 ); have_premultiplied = TRUE; @@ -680,6 +688,13 @@ vips_affine_class_init( VipsAffineClass *class ) G_STRUCT_OFFSET( VipsAffine, background ), VIPS_TYPE_ARRAY_DOUBLE ); + VIPS_ARG_BOOL( class, "premultiplied", 117, + _( "Premultiplied" ), + _( "Images have premultiplied alpha" ), + VIPS_ARGUMENT_OPTIONAL_INPUT, + G_STRUCT_OFFSET( VipsAffine, premultiplied ), + FALSE ); + } static void @@ -709,6 +724,7 @@ vips_affine_init( VipsAffine *affine ) * * @ody: %gdouble, output vertical offset * * @extend: #VipsExtend how to generate new pixels * * @background: #VipsArrayDouble colour for new pixels + * * @premultiplied: %gboolean, images are already premultiplied * * This operator performs an affine transform on an image using @interpolate. * @@ -737,6 +753,10 @@ vips_affine_init( VipsAffine *affine ) * * @idx, @idy, @odx, @ody default to zero. * + * Image are normally treated as unpremultiplied, so this operation can be used + * directly on PNG images. If your images have been through vips_premultiply(), + * set @premultiplied. + * * This operation does not change xres or yres. The image resolution needs to * be updated by the application. * diff --git a/libvips/resample/resize.c b/libvips/resample/resize.c index d56e4e13..28235351 100644 --- a/libvips/resample/resize.c +++ b/libvips/resample/resize.c @@ -289,6 +289,7 @@ vips_resize_build( VipsObject *object ) "idx", id, "idy", id, "extend", VIPS_EXTEND_COPY, + "premultiplied", TRUE, NULL ) ) return( -1 ); in = t[4]; @@ -300,6 +301,7 @@ vips_resize_build( VipsObject *object ) "idx", id, "idy", id, "extend", VIPS_EXTEND_COPY, + "premultiplied", TRUE, NULL ) ) return( -1 ); in = t[4]; @@ -311,6 +313,7 @@ vips_resize_build( VipsObject *object ) "idx", id, "idy", id, "extend", VIPS_EXTEND_COPY, + "premultiplied", TRUE, NULL ) ) return( -1 ); in = t[4]; @@ -444,7 +447,10 @@ vips_resize_init( VipsResize *resize ) * This operation does not change xres or yres. The image resolution needs to * be updated by the application. * - * See also: vips_shrink(), vips_reduce(). + * This operation does not premultiply alpha. If your image has an alpha + * channel, you should use vips_premultiply() on it first. + * + * See also: vips_premultiply(), vips_shrink(), vips_reduce(). * * Returns: 0 on success, -1 on error */ diff --git a/libvips/resample/thumbnail.c b/libvips/resample/thumbnail.c index b2a8c282..25a75135 100644 --- a/libvips/resample/thumbnail.c +++ b/libvips/resample/thumbnail.c @@ -28,6 +28,8 @@ * - smarter heif thumbnail selection * 12/10/19 * - add thumbnail_source + * 2/6/20 + * - add subifd pyr support */ /* @@ -114,9 +116,11 @@ 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 */ /* For openslide, we need to read out the size of each level too. * @@ -131,6 +135,10 @@ typedef struct _VipsThumbnail { int heif_thumbnail_width; int heif_thumbnail_height; + /* For TIFF sources, open subifds rather than pages to get pyr layers. + */ + gboolean subifd_pyramid; + } VipsThumbnail; typedef struct _VipsThumbnailClass { @@ -196,9 +204,11 @@ 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 ); /* VIPS_META_N_PAGES is the number of pages in the document, * not the number we've read out into this image. We calculate @@ -235,15 +245,24 @@ vips_thumbnail_read_header( VipsThumbnail *thumbnail, VipsImage *image ) } } -/* This may not be a pyr tiff, so no error if we can't find the layers. - * We just look for two or more pages following roughly /2 shrinks. +/* Detect a TIFF pyramid made of pages following a roughly /2 shrink. + * + * This may not be a pyr tiff, so no error if we can't find the layers. */ static void -vips_thumbnail_get_tiff_pyramid( VipsThumbnail *thumbnail ) +vips_thumbnail_get_tiff_pyramid_page( VipsThumbnail *thumbnail ) { VipsThumbnailClass *class = VIPS_THUMBNAIL_GET_CLASS( thumbnail ); int i; +#ifdef DEBUG + printf( "vips_thumbnail_get_tiff_pyramid_page:\n" ); +#endif /*DEBUG*/ + + /* Tell open() that we want to open pages rather than subifds. + */ + thumbnail->subifd_pyramid = FALSE; + for( i = 0; i < thumbnail->n_pages; i++ ) { VipsImage *page; int level_width; @@ -276,8 +295,68 @@ vips_thumbnail_get_tiff_pyramid( VipsThumbnail *thumbnail ) /* Now set level_count. This signals that we've found a pyramid. */ #ifdef DEBUG - printf( "vips_thumbnail_get_tiff_pyramid: %d layer pyramid detected\n", - thumbnail->n_pages ); + printf( "vips_thumbnail_get_tiff_pyramid_page: " + "%d layer pyramid detected\n", + thumbnail->n_pages ); +#endif /*DEBUG*/ + thumbnail->level_count = thumbnail->n_pages; +} + +/* Detect a TIFF pyramid made of subifds following a roughly /2 shrink. + * + * This may not be a pyr tiff, so no error if we can't find the layers. + */ +static void +vips_thumbnail_get_tiff_pyramid_subifd( VipsThumbnail *thumbnail ) +{ + VipsThumbnailClass *class = VIPS_THUMBNAIL_GET_CLASS( thumbnail ); + int i; + +#ifdef DEBUG + printf( "vips_thumbnail_get_tiff_pyramid_subifd:\n" ); +#endif /*DEBUG*/ + + /* Tell open() that we want to open subifds rather than pages. + */ + thumbnail->subifd_pyramid = TRUE; + + for( i = 0; i < thumbnail->n_subifds; i++ ) { + VipsImage *page; + int level_width; + int level_height; + int expected_level_width; + int expected_level_height; + + if( !(page = class->open( thumbnail, i )) ) + return; + level_width = page->Xsize; + level_height = page->Ysize; + VIPS_UNREF( page ); + + /* The main image is size 1, subifd 0 is half that. + */ + expected_level_width = thumbnail->input_width / (2 << i); + expected_level_height = thumbnail->input_height / (2 << i); + + /* This won't be exact due to rounding etc. + */ + if( abs( level_width - expected_level_width ) > 5 || + level_width < 2 ) + return; + if( abs( level_height - expected_level_height ) > 5 || + level_height < 2 ) + return; + + thumbnail->level_width[i] = level_width; + thumbnail->level_height[i] = level_height; + } + + /* Now set level_count. This signals that we've found a pyramid. + */ +#ifdef DEBUG + printf( "vips_thumbnail_get_tiff_pyramid_subifd: " + "%d layer pyramid detected\n", + thumbnail->n_pages ); #endif /*DEBUG*/ thumbnail->level_count = thumbnail->n_pages; } @@ -313,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; @@ -459,11 +537,18 @@ vips_thumbnail_open( VipsThumbnail *thumbnail ) g_info( "input size is %d x %d", thumbnail->input_width, thumbnail->input_height ); - /* For tiff, we need a separate ->open() for each page to - * get all the pyramid levels. + /* For tiff, scan the image and try to spot page-based and ifd-based + * pyramids. */ - if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ) - vips_thumbnail_get_tiff_pyramid( thumbnail ); + if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ) { + /* Test for a subifd pyr first, since we can do that from just + * one page. + */ + vips_thumbnail_get_tiff_pyramid_subifd( thumbnail ); + + if( thumbnail->level_count == 0 ) + vips_thumbnail_get_tiff_pyramid_page( thumbnail ); + } /* For heif, we need to fetch the thumbnail size, in case we can use * that as the source. @@ -524,13 +609,12 @@ vips_thumbnail_build( VipsObject *object ) { VipsThumbnail *thumbnail = VIPS_THUMBNAIL( object ); VipsImage **t = (VipsImage **) vips_object_local_array( object, 15 ); - VipsInterpretation interpretation = thumbnail->linear ? - VIPS_INTERPRETATION_scRGB : VIPS_INTERPRETATION_sRGB; VipsImage *in; int preshrunk_page_height; double hshrink; double vshrink; + VipsInterpretation interpretation; /* TRUE if we've done the import of an ICC transform and still need to * export. @@ -628,6 +712,12 @@ vips_thumbnail_build( VipsObject *object ) */ if( in->Type == VIPS_INTERPRETATION_CMYK ) have_imported = TRUE; + if( thumbnail->linear ) + interpretation = VIPS_INTERPRETATION_scRGB; + else if( in->Bands < 3 ) + interpretation = VIPS_INTERPRETATION_B_W; + else + interpretation = VIPS_INTERPRETATION_sRGB; g_info( "converting to processing space %s", vips_enum_nick( VIPS_TYPE_INTERPRETATION, interpretation ) ); if( vips_colourspace( in, &t[2], interpretation, NULL ) ) @@ -742,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. @@ -962,10 +1043,16 @@ vips_thumbnail_file_open( VipsThumbnail *thumbnail, double factor ) NULL ) ); } else if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ) { - return( vips_image_new_from_file( file->filename, - "access", VIPS_ACCESS_SEQUENTIAL, - "page", (int) factor, - NULL ) ); + if( thumbnail->subifd_pyramid ) + return( vips_image_new_from_file( file->filename, + "access", VIPS_ACCESS_SEQUENTIAL, + "subifd", (int) factor, + NULL ) ); + else + return( vips_image_new_from_file( file->filename, + "access", VIPS_ACCESS_SEQUENTIAL, + "page", (int) factor, + NULL ) ); } else if( vips_isprefix( "VipsForeignLoadHeif", thumbnail->loader ) ) { return( vips_image_new_from_file( file->filename, diff --git a/po/POTFILES.in b/po/POTFILES.in index feec8c2b..1a1d8d38 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -1,435 +1,393 @@ -libvips/arithmetic/hough_circle.c -libvips/arithmetic/boolean.c -libvips/arithmetic/complex.c -libvips/arithmetic/hough_line.c -libvips/arithmetic/profile.c -libvips/arithmetic/sign.c -libvips/arithmetic/hough.c -libvips/arithmetic/getpoint.c -libvips/arithmetic/remainder.c -libvips/arithmetic/math.c -libvips/arithmetic/sum.c -libvips/arithmetic/hist_find_ndim.c -libvips/arithmetic/subtract.c -libvips/arithmetic/statistic.c -libvips/arithmetic/unary.c -libvips/arithmetic/abs.c -libvips/arithmetic/round.c -libvips/arithmetic/measure.c -libvips/arithmetic/linear.c -libvips/arithmetic/relational.c -libvips/arithmetic/multiply.c -libvips/arithmetic/deviate.c -libvips/arithmetic/unaryconst.c libvips/arithmetic/min.c -libvips/arithmetic/add.c -libvips/arithmetic/nary.c +libvips/arithmetic/sum.c libvips/arithmetic/stats.c -libvips/arithmetic/binary.c libvips/arithmetic/project.c -libvips/arithmetic/hist_find.c -libvips/arithmetic/arithmetic.c -libvips/arithmetic/divide.c -libvips/arithmetic/invert.c +libvips/arithmetic/hough_line.c +libvips/arithmetic/binary.c +libvips/arithmetic/deviate.c libvips/arithmetic/max.c +libvips/arithmetic/statistic.c +libvips/arithmetic/nary.c +libvips/arithmetic/invert.c +libvips/arithmetic/remainder.c +libvips/arithmetic/boolean.c +libvips/arithmetic/sign.c +libvips/arithmetic/hist_find_ndim.c +libvips/arithmetic/multiply.c +libvips/arithmetic/hough_circle.c +libvips/arithmetic/measure.c +libvips/arithmetic/hist_find.c libvips/arithmetic/find_trim.c -libvips/arithmetic/math2.c +libvips/arithmetic/getpoint.c +libvips/arithmetic/add.c +libvips/arithmetic/divide.c +libvips/arithmetic/relational.c +libvips/arithmetic/hough.c +libvips/arithmetic/arithmetic.c +libvips/arithmetic/abs.c libvips/arithmetic/avg.c +libvips/arithmetic/linear.c +libvips/arithmetic/round.c +libvips/arithmetic/math2.c +libvips/arithmetic/unaryconst.c +libvips/arithmetic/complex.c +libvips/arithmetic/profile.c +libvips/arithmetic/unary.c +libvips/arithmetic/subtract.c libvips/arithmetic/hist_find_indexed.c -libvips/colour/dE76.c -libvips/colour/scRGB2XYZ.c +libvips/arithmetic/math.c libvips/colour/LabQ2LabS.c +libvips/colour/profile_load.c +libvips/colour/rad2float.c +libvips/colour/XYZ2scRGB.c +libvips/colour/Lab2LabS.c libvips/colour/LabS2LabQ.c -libvips/colour/UCS2LCh.c +libvips/colour/XYZ2CMYK.c +libvips/colour/CMYK2XYZ.c libvips/colour/profiles.c +libvips/colour/float2rad.c +libvips/colour/scRGB2XYZ.c +libvips/colour/LabQ2Lab.c +libvips/colour/HSV2sRGB.c +libvips/colour/XYZ2Lab.c +libvips/colour/UCS2LCh.c +libvips/colour/dE76.c +libvips/colour/colour.c +libvips/colour/sRGB2HSV.c +libvips/colour/Lab2LabQ.c +libvips/colour/LCh2UCS.c +libvips/colour/sRGB2scRGB.c +libvips/colour/dECMC.c libvips/colour/LCh2Lab.c libvips/colour/Yxy2XYZ.c -libvips/colour/XYZ2Lab.c -libvips/colour/scRGB2BW.c -libvips/colour/sRGB2HSV.c -libvips/colour/LabQ2Lab.c -libvips/colour/LabQ2sRGB.c -libvips/colour/Lab2XYZ.c -libvips/colour/CMYK2XYZ.c -libvips/colour/XYZ2Yxy.c -libvips/colour/XYZ2scRGB.c -libvips/colour/colour.c -libvips/colour/profile_load.c -libvips/colour/Lab2LCh.c -libvips/colour/rad2float.c -libvips/colour/XYZ2CMYK.c -libvips/colour/Lab2LabQ.c -libvips/colour/dECMC.c -libvips/colour/colourspace.c -libvips/colour/scRGB2sRGB.c -libvips/colour/sRGB2scRGB.c -libvips/colour/LCh2UCS.c -libvips/colour/icc_transform.c -libvips/colour/dE00.c -libvips/colour/float2rad.c -libvips/colour/HSV2sRGB.c -libvips/colour/Lab2LabS.c libvips/colour/LabS2Lab.c -libvips/conversion/bandjoin.c +libvips/colour/LabQ2sRGB.c +libvips/colour/scRGB2BW.c +libvips/colour/Lab2LCh.c +libvips/colour/icc_transform.c +libvips/colour/scRGB2sRGB.c +libvips/colour/dE00.c +libvips/colour/Lab2XYZ.c +libvips/colour/XYZ2Yxy.c +libvips/colour/colourspace.c +libvips/conversion/conversion.c +libvips/conversion/embed.c +libvips/conversion/zoom.c +libvips/conversion/transpose3d.c +libvips/conversion/replicate.c +libvips/conversion/bandfold.c libvips/conversion/wrap.c libvips/conversion/arrayjoin.c -libvips/conversion/premultiply.c -libvips/conversion/switch.c -libvips/conversion/scale.c -libvips/conversion/flatten.c -libvips/conversion/conversion.c -libvips/conversion/rot.c -libvips/conversion/sequential.c -libvips/conversion/gamma.c -libvips/conversion/msb.c -libvips/conversion/autorot.c -libvips/conversion/smartcrop.c -libvips/conversion/bandmean.c -libvips/conversion/copy.c -libvips/conversion/tilecache.c -libvips/conversion/extract.c -libvips/conversion/bandbool.c -libvips/conversion/grid.c -libvips/conversion/transpose3d.c libvips/conversion/unpremultiply.c -libvips/conversion/bandrank.c -libvips/conversion/ifthenelse.c -libvips/conversion/join.c -libvips/conversion/falsecolour.c -libvips/conversion/cache.c -libvips/conversion/embed.c -libvips/conversion/insert.c -libvips/conversion/replicate.c -libvips/conversion/rot45.c -libvips/conversion/byteswap.c -libvips/conversion/bandunfold.c -libvips/conversion/cast.c libvips/conversion/flip.c -libvips/conversion/zoom.c -libvips/conversion/bandfold.c +libvips/conversion/flatten.c +libvips/conversion/copy.c +libvips/conversion/bandjoin.c +libvips/conversion/rot45.c +libvips/conversion/msb.c +libvips/conversion/extract.c +libvips/conversion/cast.c +libvips/conversion/bandunfold.c +libvips/conversion/tilecache.c +libvips/conversion/sequential.c +libvips/conversion/smartcrop.c +libvips/conversion/premultiply.c +libvips/conversion/bandmean.c +libvips/conversion/falsecolour.c +libvips/conversion/byteswap.c libvips/conversion/subsample.c +libvips/conversion/bandbool.c libvips/conversion/recomb.c libvips/conversion/bandary.c +libvips/conversion/ifthenelse.c +libvips/conversion/gamma.c +libvips/conversion/join.c +libvips/conversion/cache.c +libvips/conversion/grid.c +libvips/conversion/scale.c +libvips/conversion/insert.c +libvips/conversion/autorot.c +libvips/conversion/rot.c +libvips/conversion/bandrank.c +libvips/conversion/switch.c +libvips/convolution/spcor.c libvips/convolution/conva.c -libvips/convolution/correlation.c -libvips/convolution/gaussblur.c -libvips/convolution/conv.c +libvips/convolution/fastcor.c +libvips/convolution/sobel.c +libvips/convolution/canny.c libvips/convolution/convi.c +libvips/convolution/compass.c +libvips/convolution/convolution.c +libvips/convolution/convf.c +libvips/convolution/gaussblur.c +libvips/convolution/convasep.c libvips/convolution/convsep.c libvips/convolution/sharpen.c -libvips/convolution/convolution.c -libvips/convolution/fastcor.c -libvips/convolution/canny.c -libvips/convolution/convf.c -libvips/convolution/spcor.c -libvips/convolution/compass.c -libvips/convolution/convasep.c -libvips/convolution/sobel.c -libvips/create/perlin.c -libvips/create/worley.c -libvips/create/zone.c -libvips/create/mask_ideal_band.c -libvips/create/gaussmat.c +libvips/convolution/conv.c +libvips/convolution/correlation.c +libvips/create/sines.c libvips/create/grey.c -libvips/create/buildlut.c +libvips/create/mask_ideal.c libvips/create/create.c -libvips/create/fractsurf.c -libvips/create/black.c +libvips/create/mask_gaussian.c libvips/create/xyz.c libvips/create/invertlut.c -libvips/create/mask.c -libvips/create/mask_butterworth.c -libvips/create/mask_ideal_ring.c -libvips/create/point.c -libvips/create/tonelut.c -libvips/create/mask_ideal.c -libvips/create/text.c libvips/create/mask_butterworth_ring.c -libvips/create/mask_gaussian.c -libvips/create/gaussnoise.c -libvips/create/mask_butterworth_band.c -libvips/create/sines.c -libvips/create/mask_gaussian_ring.c -libvips/create/eye.c libvips/create/logmat.c -libvips/create/identity.c +libvips/create/gaussmat.c +libvips/create/worley.c +libvips/create/mask_gaussian_ring.c +libvips/create/gaussnoise.c +libvips/create/zone.c +libvips/create/tonelut.c +libvips/create/perlin.c +libvips/create/point.c +libvips/create/mask.c +libvips/create/mask_butterworth_band.c +libvips/create/mask_ideal_ring.c +libvips/create/mask_butterworth.c libvips/create/mask_gaussian_band.c +libvips/create/fractsurf.c +libvips/create/identity.c +libvips/create/text.c libvips/create/mask_fractal.c -libvips/draw/drawink.c +libvips/create/eye.c +libvips/create/black.c +libvips/create/mask_ideal_band.c +libvips/create/buildlut.c libvips/draw/draw_line.c -libvips/draw/draw_circle.c -libvips/draw/draw_flood.c -libvips/draw/draw_rect.c -libvips/draw/draw_smudge.c libvips/draw/draw_image.c libvips/draw/draw_mask.c libvips/draw/draw.c -libvips/foreign/vipssave.c -libvips/foreign/dzsave.c -libvips/foreign/csv.c -libvips/foreign/niftiload.c -libvips/foreign/magick.c -libvips/foreign/ppmload.c -libvips/foreign/openslideload.c -libvips/foreign/tiffload.c -libvips/foreign/tiff2vips.c -libvips/foreign/radsave.c -libvips/foreign/analyze2vips.c -libvips/foreign/fits.c -libvips/foreign/csvsave.c -libvips/foreign/analyzeload.c -libvips/foreign/ppmsave.c -libvips/foreign/radload.c -libvips/foreign/pdfload.c -libvips/foreign/fitssave.c -libvips/foreign/rawload.c -libvips/foreign/heifload.c -libvips/foreign/jpeg2vips.c -libvips/foreign/vips2jpeg.c -libvips/foreign/pdfiumload.c -libvips/foreign/webpsave.c -libvips/foreign/magick7load.c -libvips/foreign/csvload.c -libvips/foreign/heifsave.c -libvips/foreign/radiance.c -libvips/foreign/pngload.c -libvips/foreign/openslide2vips.c -libvips/foreign/matrixsave.c -libvips/foreign/tiffsave.c -libvips/foreign/magickload.c -libvips/foreign/jpegsave.c -libvips/foreign/webpload.c -libvips/foreign/gifload.c -libvips/foreign/pngsave.c -libvips/foreign/exif.c -libvips/foreign/magick2vips.c -libvips/foreign/openexr2vips.c -libvips/foreign/matload.c -libvips/foreign/vips2webp.c -libvips/foreign/openexrload.c -libvips/foreign/rawsave.c -libvips/foreign/fitsload.c -libvips/foreign/niftisave.c -libvips/foreign/tiff.c -libvips/foreign/quantise.c -libvips/foreign/webp2vips.c -libvips/foreign/vips2tiff.c -libvips/foreign/cairo.c -libvips/foreign/magicksave.c -libvips/foreign/svgload.c -libvips/foreign/jpegload.c -libvips/foreign/vipsload.c -libvips/foreign/matlab.c +libvips/draw/draw_smudge.c +libvips/draw/drawink.c +libvips/draw/draw_circle.c +libvips/draw/draw_flood.c +libvips/draw/draw_rect.c libvips/foreign/foreign.c +libvips/foreign/csvsave.c +libvips/foreign/webp2vips.c +libvips/foreign/pngload.c +libvips/foreign/matlab.c +libvips/foreign/webpload.c +libvips/foreign/pdfiumload.c +libvips/foreign/pngsave.c +libvips/foreign/magicksave.c +libvips/foreign/niftiload.c +libvips/foreign/tiff2vips.c +libvips/foreign/fitsload.c +libvips/foreign/matrixsave.c +libvips/foreign/vips2webp.c +libvips/foreign/tiff.c +libvips/foreign/ppmsave.c +libvips/foreign/csvload.c libvips/foreign/vipspng.c +libvips/foreign/heifsave.c +libvips/foreign/vips2jpeg.c +libvips/foreign/ppmload.c +libvips/foreign/magickload.c +libvips/foreign/openexr2vips.c +libvips/foreign/gifload.c +libvips/foreign/magick7load.c +libvips/foreign/openslide2vips.c +libvips/foreign/exif.c +libvips/foreign/fitssave.c +libvips/foreign/pdfload.c +libvips/foreign/heifload.c +libvips/foreign/magick2vips.c +libvips/foreign/fits.c +libvips/foreign/cairo.c +libvips/foreign/openslideload.c +libvips/foreign/rawload.c +libvips/foreign/jpeg2vips.c +libvips/foreign/tiffsave.c +libvips/foreign/svgload.c +libvips/foreign/radsave.c +libvips/foreign/dzsave.c +libvips/foreign/radload.c +libvips/foreign/niftisave.c +libvips/foreign/openexrload.c +libvips/foreign/vipssave.c +libvips/foreign/webpsave.c +libvips/foreign/radiance.c +libvips/foreign/rawsave.c +libvips/foreign/jpegsave.c +libvips/foreign/analyze2vips.c libvips/foreign/matrixload.c +libvips/foreign/jpegload.c +libvips/foreign/magick.c +libvips/foreign/analyzeload.c +libvips/foreign/vips2tiff.c +libvips/foreign/matload.c +libvips/foreign/quantise.c +libvips/foreign/vipsload.c +libvips/foreign/tiffload.c +libvips/freqfilt/spectrum.c +libvips/freqfilt/phasecor.c +libvips/freqfilt/fwfft.c +libvips/freqfilt/freqmult.c libvips/freqfilt/freqfilt.c libvips/freqfilt/invfft.c -libvips/freqfilt/freqmult.c -libvips/freqfilt/spectrum.c -libvips/freqfilt/fwfft.c -libvips/freqfilt/phasecor.c -libvips/histogram/hist_norm.c -libvips/histogram/hist_cum.c -libvips/histogram/histogram.c -libvips/histogram/hist_match.c -libvips/histogram/hist_entropy.c -libvips/histogram/hist_plot.c -libvips/histogram/stdif.c -libvips/histogram/percent.c -libvips/histogram/hist_ismonotonic.c -libvips/histogram/hist_equal.c -libvips/histogram/maplut.c -libvips/histogram/hist_unary.c libvips/histogram/case.c +libvips/histogram/hist_match.c +libvips/histogram/hist_cum.c +libvips/histogram/hist_equal.c +libvips/histogram/stdif.c +libvips/histogram/histogram.c +libvips/histogram/hist_entropy.c +libvips/histogram/hist_ismonotonic.c +libvips/histogram/hist_norm.c +libvips/histogram/hist_plot.c +libvips/histogram/hist_unary.c libvips/histogram/hist_local.c +libvips/histogram/percent.c +libvips/histogram/maplut.c libvips/introspect.c -libvips/iofuncs/target.c -libvips/iofuncs/sinkscreen.c -libvips/iofuncs/vipsmarshal.c -libvips/iofuncs/init.c -libvips/iofuncs/dbuf.c -libvips/iofuncs/buffer.c -libvips/iofuncs/operation.c -libvips/iofuncs/sbuf.c -libvips/iofuncs/sinkmemory.c -libvips/iofuncs/window.c -libvips/iofuncs/reorder.c -libvips/iofuncs/targetcustom.c -libvips/iofuncs/source.c -libvips/iofuncs/memory.c -libvips/iofuncs/sinkdisc.c -libvips/iofuncs/region.c -libvips/iofuncs/rect.c -libvips/iofuncs/util.c -libvips/iofuncs/vips.c -libvips/iofuncs/cache.c -libvips/iofuncs/type.c -libvips/iofuncs/semaphore.c -libvips/iofuncs/gate.c -libvips/iofuncs/sourcecustom.c -libvips/iofuncs/mapfile.c libvips/iofuncs/sink.c -libvips/iofuncs/enumtypes.c -libvips/iofuncs/vector.c -libvips/iofuncs/header.c -libvips/iofuncs/error.c -libvips/iofuncs/generate.c +libvips/iofuncs/gate.c +libvips/iofuncs/type.c libvips/iofuncs/image.c -libvips/iofuncs/connection.c libvips/iofuncs/threadpool.c -libvips/iofuncs/buf.c +libvips/iofuncs/buffer.c +libvips/iofuncs/mapfile.c +libvips/iofuncs/reorder.c +libvips/iofuncs/sbuf.c +libvips/iofuncs/enumtypes.c +libvips/iofuncs/sinkdisc.c +libvips/iofuncs/target.c +libvips/iofuncs/vector.c +libvips/iofuncs/operation.c +libvips/iofuncs/sinkmemory.c +libvips/iofuncs/generate.c +libvips/iofuncs/dbuf.c +libvips/iofuncs/window.c +libvips/iofuncs/ginputsource.c +libvips/iofuncs/targetcustom.c +libvips/iofuncs/sourcecustom.c +libvips/iofuncs/source.c libvips/iofuncs/system.c +libvips/iofuncs/header.c +libvips/iofuncs/init.c +libvips/iofuncs/rect.c +libvips/iofuncs/region.c +libvips/iofuncs/cache.c +libvips/iofuncs/vips.c +libvips/iofuncs/error.c +libvips/iofuncs/util.c +libvips/iofuncs/semaphore.c +libvips/iofuncs/memory.c +libvips/iofuncs/sinkscreen.c libvips/iofuncs/object.c -libvips/morphology/labelregions.c +libvips/iofuncs/connection.c +libvips/iofuncs/buf.c +libvips/iofuncs/vipsmarshal.c libvips/morphology/morph.c -libvips/morphology/nearest.c -libvips/morphology/morphology.c libvips/morphology/rank.c -libvips/morphology/hitmiss.c libvips/morphology/countlines.c -libvips/mosaicing/global_balance.c -libvips/mosaicing/im_lrmosaic.c -libvips/mosaicing/im_initialize.c -libvips/mosaicing/im_remosaic.c -libvips/mosaicing/im_improve.c -libvips/mosaicing/im_lrcalcon.c -libvips/mosaicing/mosaic1.c -libvips/mosaicing/im_chkpair.c -libvips/mosaicing/im_tbcalcon.c -libvips/mosaicing/mosaicing.c -libvips/mosaicing/mosaic.c -libvips/mosaicing/im_avgdxdy.c -libvips/mosaicing/im_clinear.c -libvips/mosaicing/merge.c -libvips/mosaicing/im_tbmosaic.c -libvips/mosaicing/match.c -libvips/mosaicing/im_lrmerge.c +libvips/morphology/nearest.c +libvips/morphology/labelregions.c +libvips/morphology/morphology.c +libvips/morphology/hitmiss.c libvips/mosaicing/im_tbmerge.c -libvips/resample/shrinkh.c -libvips/resample/shrinkv.c -libvips/resample/reduce.c -libvips/resample/transform.c -libvips/resample/resize.c -libvips/resample/mapim.c -libvips/resample/thumbnail.c -libvips/resample/resample.c +libvips/mosaicing/im_improve.c +libvips/mosaicing/im_chkpair.c +libvips/mosaicing/im_lrmosaic.c +libvips/mosaicing/im_tbcalcon.c +libvips/mosaicing/merge.c +libvips/mosaicing/im_remosaic.c +libvips/mosaicing/im_lrcalcon.c +libvips/mosaicing/im_initialize.c +libvips/mosaicing/mosaicing.c +libvips/mosaicing/global_balance.c +libvips/mosaicing/im_avgdxdy.c +libvips/mosaicing/im_lrmerge.c +libvips/mosaicing/mosaic.c +libvips/mosaicing/im_tbmosaic.c +libvips/mosaicing/im_clinear.c +libvips/mosaicing/match.c +libvips/mosaicing/mosaic1.c libvips/resample/affine.c +libvips/resample/shrinkv.c +libvips/resample/mapim.c +libvips/resample/resize.c +libvips/resample/transform.c +libvips/resample/reduce.c +libvips/resample/shrinkh.c +libvips/resample/resample.c libvips/resample/quadratic.c -libvips/resample/interpolate.c -libvips/resample/similarity.c +libvips/resample/thumbnail.c libvips/resample/shrink.c +libvips/resample/similarity.c +libvips/resample/interpolate.c tools/vips.c tools/vipsedit.c tools/vipsheader.c tools/vipsthumbnail.c +cplusplus/include/vips/VImage8.h +cplusplus/include/vips/VInterpolate8.h cplusplus/include/vips/VError8.h cplusplus/include/vips/vips-operators.h cplusplus/include/vips/VConnection8.h -cplusplus/include/vips/VImage8.h -cplusplus/include/vips/VInterpolate8.h -libvips/arithmetic/binary.h -libvips/arithmetic/unary.h -libvips/arithmetic/parithmetic.h -libvips/arithmetic/hough.h -libvips/arithmetic/nary.h -libvips/arithmetic/statistic.h -libvips/arithmetic/unaryconst.h -libvips/colour/profiles.h -libvips/colour/pcolour.h -libvips/conversion/bandary.h -libvips/conversion/pconversion.h -libvips/convolution/correlation.h -libvips/convolution/pconvolution.h -libvips/create/pmask.h -libvips/create/pcreate.h -libvips/create/point.h -libvips/draw/pdraw.h -libvips/draw/drawink.h -libvips/foreign/tiff.h -libvips/foreign/pforeign.h -libvips/foreign/dbh.h -libvips/foreign/magick.h -libvips/foreign/jpeg.h -libvips/freqfilt/pfreqfilt.h -libvips/histogram/phistogram.h -libvips/histogram/hist_unary.h -libvips/include/vips/freqfilt.h -libvips/include/vips/arithmetic.h -libvips/include/vips/buf.h -libvips/include/vips/histogram.h -libvips/include/vips/intl.h -libvips/include/vips/threadpool.h libvips/include/vips/operation.h +libvips/include/vips/connection.h +libvips/include/vips/x.h +libvips/include/vips/enumtypes.h libvips/include/vips/video.h -libvips/include/vips/memory.h -libvips/include/vips/conversion.h +libvips/include/vips/internal.h +libvips/include/vips/histogram.h +libvips/include/vips/cimg_funcs.h +libvips/include/vips/buf.h +libvips/include/vips/thread.h +libvips/include/vips/region.h +libvips/include/vips/mask.h libvips/include/vips/private.h libvips/include/vips/interpolate.h -libvips/include/vips/internal.h -libvips/include/vips/basic.h -libvips/include/vips/region.h -libvips/include/vips/foreign.h -libvips/include/vips/gate.h -libvips/include/vips/almostdeprecated.h -libvips/include/vips/dispatch.h -libvips/include/vips/image.h -libvips/include/vips/mosaicing.h -libvips/include/vips/vector.h -libvips/include/vips/cimg_funcs.h -libvips/include/vips/dbuf.h -libvips/include/vips/error.h -libvips/include/vips/connection.h -libvips/include/vips/sbuf.h -libvips/include/vips/type.h +libvips/include/vips/soname.h +libvips/include/vips/vips7compat.h libvips/include/vips/create.h -libvips/include/vips/generate.h -libvips/include/vips/format.h -libvips/include/vips/util.h -libvips/include/vips/convolution.h -libvips/include/vips/thread.h +libvips/include/vips/rect.h +libvips/include/vips/dispatch.h +libvips/include/vips/version.h +libvips/include/vips/error.h +libvips/include/vips/debug.h +libvips/include/vips/vips.h +libvips/include/vips/morphology.h libvips/include/vips/resample.h libvips/include/vips/object.h -libvips/include/vips/vips.h +libvips/include/vips/generate.h +libvips/include/vips/basic.h libvips/include/vips/inlines.h -libvips/include/vips/transform.h -libvips/include/vips/draw.h -libvips/include/vips/semaphore.h -libvips/include/vips/vips7compat.h +libvips/include/vips/sbuf.h libvips/include/vips/header.h -libvips/include/vips/soname.h -libvips/include/vips/rect.h +libvips/include/vips/type.h +libvips/include/vips/semaphore.h +libvips/include/vips/image.h +libvips/include/vips/dbuf.h +libvips/include/vips/vector.h +libvips/include/vips/intl.h +libvips/include/vips/gate.h +libvips/include/vips/freqfilt.h libvips/include/vips/colour.h -libvips/include/vips/mask.h -libvips/include/vips/debug.h -libvips/include/vips/morphology.h -libvips/include/vips/enumtypes.h -libvips/include/vips/deprecated.h -libvips/include/vips/version.h -libvips/iofuncs/sink.h -libvips/iofuncs/vipsmarshal.h -libvips/morphology/pmorphology.h -libvips/mosaicing/pmosaicing.h -libvips/mosaicing/global_balance.h -libvips/resample/presample.h -libvips/resample/templates.h -cplusplus/examples/invert.cpp -cplusplus/examples/avg.cpp -cplusplus/examples/test.cpp -cplusplus/examples/resize.cpp -cplusplus/examples/buffer.cpp -cplusplus/examples/embed.cpp -cplusplus/examples/profile.cpp -cplusplus/examples/test_overloads.cpp +libvips/include/vips/arithmetic.h +libvips/include/vips/threadpool.h +libvips/include/vips/format.h +libvips/include/vips/conversion.h +libvips/include/vips/draw.h +libvips/include/vips/mosaicing.h +libvips/include/vips/util.h +libvips/include/vips/convolution.h +libvips/include/vips/foreign.h +libvips/include/vips/transform.h +libvips/include/vips/memory.h +cplusplus/VConnection.cpp cplusplus/VError.cpp cplusplus/VImage.cpp cplusplus/VInterpolate.cpp cplusplus/vips-operators.cpp -cplusplus/VConnection.cpp libvips/conversion/composite.cpp libvips/resample/reduceh.cpp -libvips/resample/vsqbs.cpp -libvips/resample/lbb.cpp -libvips/resample/nohalo.cpp -libvips/resample/reducev.cpp libvips/resample/bicubic.cpp +libvips/resample/vsqbs.cpp +libvips/resample/nohalo.cpp +libvips/resample/lbb.cpp +libvips/resample/reducev.cpp diff --git a/po/README b/po/README index d2403fb8..1252232e 100644 --- a/po/README +++ b/po/README @@ -28,7 +28,7 @@ find */* -name "*.cxx" >> po/POTFILES.in find */* -name "*.cpp" >> po/POTFILES.in regenerate the list of files to search for strings ... don't forget to - remove autogenerated files, fuzzer sources, deprecated stuff etc. + remove: fuzz/ test/ deprecated/ examples/ non-public includes intltool-update --pot 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) diff --git a/test/test-suite/test_foreign.py b/test/test-suite/test_foreign.py index d52cff6d..3b8a4b6f 100644 --- a/test/test-suite/test_foreign.py +++ b/test/test-suite/test_foreign.py @@ -340,8 +340,12 @@ class TestForeign: self.colour, 0) self.save_load_file(".tif", "[tile]", self.colour, 0) self.save_load_file(".tif", "[tile,pyramid]", self.colour, 0) + self.save_load_file(".tif", "[tile,pyramid,subifd]", self.colour, 0) self.save_load_file(".tif", "[tile,pyramid,compression=jpeg]", self.colour, 80) + self.save_load_file(".tif", + "[tile,pyramid,subifd,compression=jpeg]", + self.colour, 80) self.save_load_file(".tif", "[bigtiff]", self.colour, 0) self.save_load_file(".tif", "[compression=jpeg]", self.colour, 80) self.save_load_file(".tif", diff --git a/tools/Makefile.am b/tools/Makefile.am index 669b8ded..2b2ace33 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -24,7 +24,7 @@ bin_SCRIPTS = \ EXTRA_DIST = \ vipsprofile \ - vips-8.9 \ + vips-8.10 \ light_correct.in \ shrink_width.in \ batch_image_convert.in \