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 \