Merge branch 'master' into libspng-experiment

This commit is contained in:
John Cupitt 2020-06-07 11:56:16 +01:00
commit a44814018d
38 changed files with 1281 additions and 937 deletions

View File

@ -15,12 +15,18 @@
- add subsample_mode, deprecate no_subsample in jpegsave [Elad-Laufer] - add subsample_mode, deprecate no_subsample in jpegsave [Elad-Laufer]
- add vips_isdirf() - add vips_isdirf()
- add PAGENUMBER support to tiff write [jclavoie-jive] - 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 - flood fill could stop half-way for some very complex shapes
- better handling of unaligned reads in multipage tiffs [petoor] - better handling of unaligned reads in multipage tiffs [petoor]
- add experimental libspng reader - add experimental libspng reader
- mark old --delete option to vipsthumbnail as deprecated [UweOhse] - mark old --delete option to vipsthumbnail as deprecated [UweOhse]
- png save with a bad ICC profile just gives a warning - 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 24/4/20 started 8.9.3
- better iiif tile naming [IllyaMoskvin] - better iiif tile naming [IllyaMoskvin]

View File

@ -855,14 +855,16 @@ if test x"$with_heif" != x"no"; then
PKG_CHECK_MODULES(HEIF, libheif, PKG_CHECK_MODULES(HEIF, libheif,
[with_heif=yes [with_heif=yes
have_h265_decoder=`$PKG_CONFIG libheif --variable builtin_h265_decoder` 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 # test for !=no so that we work for older libheif which does not have
# this variable # 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, AC_DEFINE(HAVE_HEIF_DECODER,1,
[define if your libheif has decode support.]) [define if your libheif has decode support.])
fi fi
have_h265_encoder=`$PKG_CONFIG libheif --variable builtin_h265_encoder` 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, AC_DEFINE(HAVE_HEIF_ENCODER,1,
[define if your libheif has encode support.]) [define if your libheif has encode support.])
fi fi
@ -872,6 +874,8 @@ if test x"$with_heif" != x"no"; then
with_heif=no with_heif=no
have_h265_decoder= have_h265_decoder=
have_h265_encoder= have_h265_encoder=
have_avif_decoder=
have_avif_encoder=
] ]
) )
fi fi

View File

@ -260,10 +260,10 @@ $ ls -l tn_shark.jpg
-rw-rr 1 john john 7295 Nov  9 14:33 tn_shark.jpg -rw-rr 1 john john 7295 Nov  9 14:33 tn_shark.jpg
</programlisting> </programlisting>
<para> <para>
Now encode with sRGB and delete any embedded profile: Now transform to sRGB and dont attach a profile (you can also use <literal>strip</literal>, though that will remove <emphasis>all</emphasis> metadata from the image):
</para> </para>
<programlisting> <programlisting>
$ vipsthumbnail shark.jpg --eprofile srgb --delete $ vipsthumbnail shark.jpg --eprofile srgb -o tn_shark.jpg[profile=none]
$ ls -l tn_shark.jpg $ ls -l tn_shark.jpg
-rw-rr 1 john john 4229 Nov  9 14:33 tn_shark.jpg -rw-rr 1 john john 4229 Nov  9 14:33 tn_shark.jpg
</programlisting> </programlisting>
@ -299,6 +299,7 @@ $ vipsthumbnail kgdev.jpg --iprofile cmyk
<programlisting> <programlisting>
$ vipsthumbnail fred.jpg \ $ vipsthumbnail fred.jpg \
--size 128 \ --size 128 \
--eprofile srgb \
-o tn_%s.jpg[optimize_coding,strip] \ -o tn_%s.jpg[optimize_coding,strip] \
--eprofile srgb --eprofile srgb
</programlisting> </programlisting>

View File

@ -6,6 +6,9 @@
* - test and remove orientation from every ifd * - test and remove orientation from every ifd
* 6/10/18 * 6/10/18
* - don't remove orientation if it's one of the cases we don't handle * - 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; VipsImage *in;
VipsAngle angle; VipsAngle angle;
gboolean flip;
} VipsAutorot; } VipsAutorot;
@ -61,79 +65,6 @@ typedef VipsConversionClass VipsAutorotClass;
G_DEFINE_TYPE( VipsAutorot, vips_autorot, VIPS_TYPE_CONVERSION ); 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 * static void *
vips_autorot_remove_angle_sub( VipsImage *image, vips_autorot_remove_angle_sub( VipsImage *image,
const char *field, GValue *value, void *my_data ) const char *field, GValue *value, void *my_data )
@ -154,14 +85,14 @@ vips_autorot_remove_angle_sub( VipsImage *image,
* vips_autorot_remove_angle: (method) * vips_autorot_remove_angle: (method)
* @image: image to remove orientation from * @image: image to remove orientation from
* *
* Remove the orientation tag on @image. Also remove any exif orientation tags. * Remove the orientation tag on @image. Also remove any exif orientation tags.
* * You must vips_copy() the image before calling this function since it
* See also: vips_autorot_get_angle(). * modifies metadata.
*/ */
void void
vips_autorot_remove_angle( VipsImage *image ) 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 ); (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 ); VipsConversion *conversion = VIPS_CONVERSION( object );
VipsAutorot *autorot = (VipsAutorot *) 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 ) ) if( VIPS_OBJECT_CLASS( vips_autorot_parent_class )->build( object ) )
return( -1 ); return( -1 );
VipsAngle angle;
gboolean flip;
VipsImage *in;
g_object_set( object, in = autorot->in;
"angle", vips_autorot_get_angle( autorot->in ),
NULL ); 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( -1 );
return( 0 ); return( 0 );
@ -220,13 +211,21 @@ vips_autorot_class_init( VipsAutorotClass *class )
_( "Angle image was rotated by" ), _( "Angle image was rotated by" ),
VIPS_ARGUMENT_OPTIONAL_OUTPUT, VIPS_ARGUMENT_OPTIONAL_OUTPUT,
G_STRUCT_OFFSET( VipsAutorot, angle ), 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 static void
vips_autorot_init( VipsAutorot *autorot ) vips_autorot_init( VipsAutorot *autorot )
{ {
autorot->angle = VIPS_ANGLE_D0; autorot->angle = VIPS_ANGLE_D0;
autorot->flip = FALSE;
} }
/** /**
@ -238,18 +237,14 @@ vips_autorot_init( VipsAutorot *autorot )
* Optional arguments: * Optional arguments:
* *
* * @angle: output #VipsAngle the image was rotated by * * @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 * Look at the image metadata and rotate and flip the image to make it
* #VIPS_META_ORIENTATION tag is removed from @out to prevent accidental * upright. The #VIPS_META_ORIENTATION tag is removed from @out to prevent
* double rotation. * accidental double rotation.
* *
* Read @angle to find the amount the image was rotated by. * Read @angle to find the amount the image was rotated by. Read @flip to
* * see if the image was also flipped.
* 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().
* *
* Returns: 0 on success, -1 on error * Returns: 0 on success, -1 on error
*/ */
@ -265,4 +260,3 @@ vips_autorot( VipsImage *in, VipsImage **out, ... )
return( result ); return( result );
} }

View File

@ -30,6 +30,8 @@
* - set Xoffset/Yoffset to ink left/top * - set Xoffset/Yoffset to ink left/top
* 27/6/19 * 27/6/19
* - fitting could occasionally terminate early [levmorozov] * - 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 ); 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. /* Just have one of these and reuse it.
* *
* This does not unref cleanly on many platforms, so we will leak horribly * 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; 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 static void
vips_text_dispose( GObject *gobject ) vips_text_dispose( GObject *gobject )
@ -350,17 +357,26 @@ vips_text_build( VipsObject *object )
if( !vips_text_fontmap ) if( !vips_text_fontmap )
vips_text_fontmap = pango_ft2_font_map_new(); 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( text->context = pango_font_map_create_context(
PANGO_FONT_MAP( vips_text_fontmap ) ); PANGO_FONT_MAP( vips_text_fontmap ) );
if( text->fontfile && if( text->fontfile &&
!FcConfigAppFontAddFile( NULL, !g_hash_table_lookup( vips_text_fontfiles, text->fontfile ) ) {
if( !FcConfigAppFontAddFile( NULL,
(const FcChar8 *) text->fontfile ) ) { (const FcChar8 *) text->fontfile ) ) {
vips_error( class->nickname, vips_error( class->nickname,
_( "unable to load font \"%s\"" ), text->fontfile ); _( "unable to load font \"%s\"" ),
g_mutex_unlock( vips_text_lock ); text->fontfile );
return( -1 ); 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 /* 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 * * @fontfile: %gchararray, load this font file
* * @width: %gint, image should be no wider than this many pixels * * @width: %gint, image should be no wider than this many pixels
* * @height: %gint, image should be no higher 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 * * @justify: %gboolean, justify lines
* * @dpi: %gint, render at this resolution * * @dpi: %gint, render at this resolution
* * @autofit_dpi: %gint, read out auto-fitted DPI * * @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 * @width is the number of pixels to word-wrap at. Lines of text wider than
* this will be broken at word boundaries. * 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. * 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 * @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. * only takes effect when @dpi is not set, and @width is set, making a box.

View File

@ -76,7 +76,7 @@ im_tiff_read_header( const char *filename, VipsImage *out,
if( !(source = vips_source_new_from_file( filename )) ) if( !(source = vips_source_new_from_file( filename )) )
return( -1 ); return( -1 );
if( vips__tiff_read_header_source( source, if( vips__tiff_read_header_source( source,
out, page, n, autorotate ) ) { out, page, n, autorotate, -1 ) ) {
VIPS_UNREF( source ); VIPS_UNREF( source );
return( -1 ); return( -1 );
} }
@ -93,7 +93,7 @@ im_tiff_read( const char *filename, VipsImage *out,
if( !(source = vips_source_new_from_file( filename )) ) if( !(source = vips_source_new_from_file( filename )) )
return( -1 ); 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 ); VIPS_UNREF( source );
return( -1 ); return( -1 );
} }

View File

@ -801,3 +801,15 @@ vips_warn( const char *domain, const char *fmt, ... )
va_end( ap ); 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 );
}

View File

@ -69,7 +69,7 @@
* to be a slow way to do it. This is where the draw operations come in. * 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 * 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. * series of draw operations.
* Once you are done drawing, return to normal use of vips operations. Any time * 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. * you want to start drawing again, you'll need to copy again.

View File

@ -413,6 +413,10 @@ vips_foreign_load_csv_load( VipsForeignLoad *load )
for( y = 0; y < load->real->Ysize; y++ ) { for( y = 0; y < load->real->Ysize; y++ ) {
csv->colno = 0; csv->colno = 0;
/* Not needed, but stops a used-before-set compiler warning.
*/
ch = EOF;
for( x = 0; x < load->real->Xsize; x++ ) { for( x = 0; x < load->real->Xsize; x++ ) {
double value; double value;

View File

@ -523,7 +523,8 @@ vips__exif_parse( VipsImage *image )
int orientation; int orientation;
orientation = atoi( str ); orientation = atoi( str );
if( orientation < 1 || orientation > 8 ) if( orientation < 1 ||
orientation > 8 )
orientation = 1; orientation = 1;
vips_image_set_int( image, VIPS_META_ORIENTATION, orientation ); vips_image_set_int( image, VIPS_META_ORIENTATION, orientation );
} }

View File

@ -237,7 +237,8 @@ static const char *heif_magic[] = {
"ftyphevm", /* Multiview sequence */ "ftyphevm", /* Multiview sequence */
"ftyphevs", /* Scaleable sequence */ "ftyphevs", /* Scaleable sequence */
"ftypmif1", /* Nokia alpha_ image */ "ftypmif1", /* Nokia alpha_ image */
"ftypmsf1" /* Nokia animation image */ "ftypmsf1", /* Nokia animation image */
"ftypavif" /* AV1 image format */
}; };
/* THe API has: /* THe API has:

View File

@ -830,40 +830,6 @@ read_jpeg_generate( VipsRegion *or,
return( 0 ); 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. /* Read a cinfo to a VIPS image.
*/ */
static int static int
@ -871,7 +837,7 @@ read_jpeg_image( ReadJpeg *jpeg, VipsImage *out )
{ {
struct jpeg_decompress_struct *cinfo = &jpeg->cinfo; struct jpeg_decompress_struct *cinfo = &jpeg->cinfo;
VipsImage **t = (VipsImage **) VipsImage **t = (VipsImage **)
vips_object_local_array( VIPS_OBJECT( out ), 3 ); vips_object_local_array( VIPS_OBJECT( out ), 4 );
VipsImage *im; VipsImage *im;
@ -902,10 +868,16 @@ read_jpeg_image( ReadJpeg *jpeg, VipsImage *out )
vips_extract_area( t[1], &t[2], vips_extract_area( t[1], &t[2],
0, 0, jpeg->output_width, jpeg->output_height, NULL ) ) 0, 0, jpeg->output_width, jpeg->output_height, NULL ) )
return( -1 ); return( -1 );
im = t[2]; 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 ) ) if( vips_image_write( im, out ) )
return( -1 ); 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. /* Swap width and height if we're going to rotate this image.
*/ */
if( jpeg->autorotate ) { if( jpeg->autorotate &&
VipsAngle angle = vips_autorot_get_angle( out ); vips_image_get_orientation_swap( out ) ) {
VIPS_SWAP( int, out->Xsize, out->Ysize );
if( angle == VIPS_ANGLE_D90 ||
angle == VIPS_ANGLE_D270 )
VIPS_SWAP( int, out->Xsize, out->Ysize );
/* We won't be returning an orientation tag.
*/
vips_autorot_remove_angle( out ); vips_autorot_remove_angle( out );
} }
} }

View File

@ -63,7 +63,8 @@ int vips__tiff_write( VipsImage *in, const char *filename,
VipsRegionShrink region_shrink, VipsRegionShrink region_shrink,
int level, int level,
gboolean lossless, gboolean lossless,
VipsForeignDzDepth depth ); VipsForeignDzDepth depth,
gboolean subifd );
int vips__tiff_write_buf( VipsImage *in, int vips__tiff_write_buf( VipsImage *in,
void **obuf, size_t *olen, void **obuf, size_t *olen,
@ -81,14 +82,15 @@ int vips__tiff_write_buf( VipsImage *in,
VipsRegionShrink region_shrink, VipsRegionShrink region_shrink,
int level, int level,
gboolean lossless, gboolean lossless,
VipsForeignDzDepth depth ); VipsForeignDzDepth depth,
gboolean subifd );
gboolean vips__istiff_source( VipsSource *source ); gboolean vips__istiff_source( VipsSource *source );
gboolean vips__istifftiled_source( VipsSource *source ); gboolean vips__istifftiled_source( VipsSource *source );
int vips__tiff_read_header_source( VipsSource *source, VipsImage *out, 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 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[]; extern const char *vips__foreign_tiff_suffs[];

View File

@ -197,6 +197,8 @@
* - read logluv images as XYZ * - read logluv images as XYZ
* 11/4/20 petoor * 11/4/20 petoor
* - better handling of aligned reads in multipage tiffs * - better handling of aligned reads in multipage tiffs
* 28/5/20
* - add subifd
*/ */
/* /*
@ -269,7 +271,7 @@ typedef struct _RtiffHeader {
int alpha_band; int alpha_band;
uint16 compression; uint16 compression;
/* Result of TIFFIsTiled(). /* Is this directory tiled.
*/ */
gboolean tiled; gboolean tiled;
@ -302,6 +304,15 @@ typedef struct _RtiffHeader {
/* Scale factor to get absolute cd/m2 from XYZ. /* Scale factor to get absolute cd/m2 from XYZ.
*/ */
double stonits; double stonits;
/* Number of subifds, if any.
*/
int subifd_count;
/* Optional IMAGEDESCRIPTION.
*/
char *image_description;
} RtiffHeader; } RtiffHeader;
/* Scanline-type process function. /* Scanline-type process function.
@ -320,6 +331,7 @@ typedef struct _Rtiff {
int page; int page;
int n; int n;
gboolean autorotate; gboolean autorotate;
int subifd;
/* The TIFF we read. /* The TIFF we read.
*/ */
@ -527,7 +539,7 @@ rtiff_minimise_cb( VipsImage *image, Rtiff *rtiff )
static Rtiff * static Rtiff *
rtiff_new( VipsSource *source, VipsImage *out, rtiff_new( VipsSource *source, VipsImage *out,
int page, int n, gboolean autorotate ) int page, int n, gboolean autorotate, int subifd )
{ {
Rtiff *rtiff; Rtiff *rtiff;
@ -540,6 +552,7 @@ rtiff_new( VipsSource *source, VipsImage *out,
rtiff->page = page; rtiff->page = page;
rtiff->n = n; rtiff->n = n;
rtiff->autorotate = autorotate; rtiff->autorotate = autorotate;
rtiff->subifd = subifd;
rtiff->tiff = NULL; rtiff->tiff = NULL;
rtiff->n_pages = 0; rtiff->n_pages = 0;
rtiff->current_page = -1; rtiff->current_page = -1;
@ -608,7 +621,8 @@ rtiff_set_page( Rtiff *rtiff, int page )
{ {
if( rtiff->current_page != page ) { if( rtiff->current_page != page ) {
#ifdef DEBUG #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*/ #endif /*DEBUG*/
if( !TIFFSetDirectory( rtiff->tiff, page ) ) { if( !TIFFSetDirectory( rtiff->tiff, page ) ) {
@ -617,7 +631,43 @@ rtiff_set_page( Rtiff *rtiff, int page )
return( -1 ); 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; 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 ); return( 0 );
@ -633,7 +683,9 @@ rtiff_n_pages( Rtiff *rtiff )
for( n = 1; TIFFReadDirectory( rtiff->tiff ); n++ ) 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 #ifdef DEBUG
printf( "rtiff_n_pages: found %d pages\n", n ); 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_image_set_int( out,
VIPS_META_PAGE_HEIGHT, rtiff->header.height ); 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 ); vips_image_set_int( out, VIPS_META_N_PAGES, rtiff->n_pages );
/* Even though we could end up serving tiled data, always hint /* 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 ); 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 /* We have a range of output paths. Look at the tiff header and try to
* route the input image to the best output path. * route the input image to the best output path.
*/ */
@ -1540,18 +1585,16 @@ rtiff_set_header( Rtiff *rtiff, VipsImage *out )
/* Read any ICC profile. /* Read any ICC profile.
*/ */
if( TIFFGetField( rtiff->tiff, if( TIFFGetField( rtiff->tiff,
TIFFTAG_ICCPROFILE, &data_length, &data ) ) { TIFFTAG_ICCPROFILE, &data_length, &data ) )
vips_image_set_blob_copy( out, vips_image_set_blob_copy( out,
VIPS_META_ICC_NAME, data, data_length ); VIPS_META_ICC_NAME, data, data_length );
}
/* Read any XMP metadata. /* Read any XMP metadata.
*/ */
if( TIFFGetField( rtiff->tiff, if( TIFFGetField( rtiff->tiff,
TIFFTAG_XMLPACKET, &data_length, &data ) ) { TIFFTAG_XMLPACKET, &data_length, &data ) )
vips_image_set_blob_copy( out, vips_image_set_blob_copy( out,
VIPS_META_XMP_NAME, data, data_length ); VIPS_META_XMP_NAME, data, data_length );
}
/* Read any IPTC metadata. /* Read any IPTC metadata.
*/ */
@ -1569,20 +1612,13 @@ rtiff_set_header( Rtiff *rtiff, VipsImage *out )
/* Read any photoshop metadata. /* Read any photoshop metadata.
*/ */
if( TIFFGetField( rtiff->tiff, if( TIFFGetField( rtiff->tiff,
TIFFTAG_PHOTOSHOP, &data_length, &data ) ) { TIFFTAG_PHOTOSHOP, &data_length, &data ) )
vips_image_set_blob_copy( out, vips_image_set_blob_copy( out,
VIPS_META_PHOTOSHOP_NAME, data, data_length ); VIPS_META_PHOTOSHOP_NAME, data, data_length );
}
/* IMAGEDESCRIPTION often has useful metadata. if( rtiff->header.image_description )
*/ vips_image_set_string( out, VIPS_META_IMAGEDESCRIPTION,
if( TIFFGetField( rtiff->tiff, TIFFTAG_IMAGEDESCRIPTION, &data ) ) { rtiff->header.image_description );
/* 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( get_resolution( rtiff->tiff, out ) ) if( get_resolution( rtiff->tiff, out ) )
return( -1 ); return( -1 );
@ -1689,7 +1725,7 @@ rtiff_fill_region_unaligned( VipsRegion *out,
/* Not necessary, but it stops static analyzers complaining /* Not necessary, but it stops static analyzers complaining
* about a used-before-set. * about a used-before-set.
*/ */
tile.height = 0; hit.height = 0;
x = 0; x = 0;
while( x < r->width ) { while( x < r->width ) {
@ -1753,10 +1789,13 @@ rtiff_fill_region_unaligned( VipsRegion *out,
q, p, hit.width, rtiff->client ); 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 ); return( 0 );
@ -1823,59 +1862,6 @@ rtiff_seq_stop( void *seq, void *a, void *b )
return( 0 ); 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. /* Unpremultiply associative alpha, if any.
*/ */
static int static int
@ -1913,6 +1899,8 @@ rtiff_read_tilewise( Rtiff *rtiff, VipsImage *out )
VipsImage **t = (VipsImage **) VipsImage **t = (VipsImage **)
vips_object_local_array( VIPS_OBJECT( out ), 4 ); vips_object_local_array( VIPS_OBJECT( out ), 4 );
VipsImage *in;
#ifdef DEBUG #ifdef DEBUG
printf( "tiff2vips: rtiff_read_tilewise\n" ); printf( "tiff2vips: rtiff_read_tilewise\n" );
#endif /*DEBUG*/ #endif /*DEBUG*/
@ -1952,21 +1940,31 @@ rtiff_read_tilewise( Rtiff *rtiff, VipsImage *out )
*/ */
vips_image_pipelinev( t[0], VIPS_DEMAND_STYLE_THINSTRIP, NULL ); vips_image_pipelinev( t[0], VIPS_DEMAND_STYLE_THINSTRIP, NULL );
if( vips_image_generate( t[0], /* Generate to out, adding a cache. Enough tiles for two complete rows.
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.
*/ */
if( vips_tilecache( t[0], &t[1], if(
"tile_width", tile_width, vips_image_generate( t[0],
"tile_height", tile_height, rtiff_seq_start, rtiff_fill_region, rtiff_seq_stop,
"max_tiles", 2 * (1 + t[0]->Xsize / tile_width), rtiff, NULL ) ||
NULL ) || vips_tilecache( t[0], &t[1],
rtiff_autorotate( rtiff, t[1], &t[2] ) || "tile_width", tile_width,
rtiff_unpremultiply( rtiff, t[2], &t[3] ) || "tile_height", tile_height,
vips_image_write( t[3], out ) ) "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( -1 );
return( 0 ); return( 0 );
@ -2187,6 +2185,8 @@ rtiff_read_stripwise( Rtiff *rtiff, VipsImage *out )
VipsImage **t = (VipsImage **) VipsImage **t = (VipsImage **)
vips_object_local_array( VIPS_OBJECT( out ), 4 ); vips_object_local_array( VIPS_OBJECT( out ), 4 );
VipsImage *in;
#ifdef DEBUG #ifdef DEBUG
printf( "tiff2vips: rtiff_read_stripwise\n" ); printf( "tiff2vips: rtiff_read_stripwise\n" );
#endif /*DEBUG*/ #endif /*DEBUG*/
@ -2197,19 +2197,6 @@ rtiff_read_stripwise( Rtiff *rtiff, VipsImage *out )
vips_image_pipelinev( t[0], VIPS_DEMAND_STYLE_THINSTRIP, NULL ); 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 /* Double check: in memcpy mode, the vips linesize should exactly
* match the tiff line size. * match the tiff line size.
*/ */
@ -2275,9 +2262,20 @@ rtiff_read_stripwise( Rtiff *rtiff, VipsImage *out )
vips_sequential( t[0], &t[1], vips_sequential( t[0], &t[1],
"tile_height", rtiff->header.read_height, "tile_height", rtiff->header.read_height,
NULL ) || NULL ) ||
rtiff_autorotate( rtiff, t[1], &t[2] ) || rtiff_unpremultiply( rtiff, t[1], &t[2] ) )
rtiff_unpremultiply( rtiff, t[2], &t[3] ) || return( -1 );
vips_image_write( t[3], out ) ) 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( -1 );
return( 0 ); return( 0 );
@ -2290,6 +2288,8 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header )
{ {
uint16 extra_samples_count; uint16 extra_samples_count;
uint16 *extra_samples_types; uint16 *extra_samples_types;
toff_t *subifd_offsets;
char *image_description;
if( !tfget32( rtiff->tiff, TIFFTAG_IMAGEWIDTH, if( !tfget32( rtiff->tiff, TIFFTAG_IMAGEWIDTH,
&header->width ) || &header->width ) ||
@ -2387,10 +2387,42 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header )
header->separate = TRUE; 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. /* Tiles and strip images have slightly different fields.
*/ */
header->tiled = TIFFIsTiled( rtiff->tiff ); 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( header->tiled ) {
if( !tfget32( rtiff->tiff, if( !tfget32( rtiff->tiff,
TIFFTAG_TILEWIDTH, &header->tile_width ) || TIFFTAG_TILEWIDTH, &header->tile_width ) ||
@ -2398,6 +2430,13 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header )
TIFFTAG_TILELENGTH, &header->tile_height ) ) TIFFTAG_TILELENGTH, &header->tile_height ) )
return( -1 ); 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. /* Arbitrary sanity-checking limits.
*/ */
if( header->tile_width <= 0 || if( header->tile_width <= 0 ||
@ -2412,6 +2451,13 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header )
header->tile_size = TIFFTileSize( rtiff->tiff ); header->tile_size = TIFFTileSize( rtiff->tiff );
header->tile_row_size = TIFFTileRowSize( 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 /* Fuzzed TIFFs can give crazy values for tile_size. Sanity
* check at 100mb per tile. * check at 100mb per tile.
*/ */
@ -2440,6 +2486,17 @@ rtiff_header_read( Rtiff *rtiff, RtiffHeader *header )
header->scanline_size = TIFFScanlineSize( rtiff->tiff ); header->scanline_size = TIFFScanlineSize( rtiff->tiff );
header->number_of_strips = TIFFNumberOfStrips( 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() /* libtiff has two strip-wise readers. TIFFReadEncodedStrip()
* decompresses an entire strip to memory. It's fast, but it * decompresses an entire strip to memory. It's fast, but it
* will need a lot of ram if the strip is large. * 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; 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. /* Stop some compiler warnings.
*/ */
header->tile_width = 0; header->tile_width = 0;
@ -2545,7 +2611,8 @@ static int
rtiff_header_read_all( Rtiff *rtiff ) rtiff_header_read_all( Rtiff *rtiff )
{ {
#ifdef DEBUG #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*/ #endif /*DEBUG*/
if( rtiff_set_page( rtiff, rtiff->page ) || if( rtiff_set_page( rtiff, rtiff->page ) ||
@ -2567,7 +2634,8 @@ rtiff_header_read_all( Rtiff *rtiff )
RtiffHeader header; RtiffHeader header;
#ifdef DEBUG #ifdef DEBUG
printf( "tiff2vips: verifying header for page %d ...\n", printf( "rtiff_header_read_all: "
"verifying header for page %d ...\n",
rtiff->page + i ); rtiff->page + i );
#endif /*DEBUG*/ #endif /*DEBUG*/
@ -2587,29 +2655,6 @@ rtiff_header_read_all( Rtiff *rtiff )
return( 0 ); 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 ); typedef gboolean (*TiffPropertyFn)( TIFF *tif );
static gboolean static gboolean
@ -2646,20 +2691,24 @@ vips__istifftiled_source( VipsSource *source )
int int
vips__tiff_read_header_source( VipsSource *source, VipsImage *out, 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; Rtiff *rtiff;
vips__tiff_init(); 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 ) ) rtiff_header_read_all( rtiff ) )
return( -1 ); return( -1 );
if( rtiff_set_header( rtiff, out ) ) if( rtiff_set_header( rtiff, out ) )
return( -1 ); 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 /* We never call vips_source_decode() since we need to be able to
* seek() the whole way through the file. Just minimise instead, * seek() the whole way through the file. Just minimise instead,
@ -2671,7 +2720,7 @@ vips__tiff_read_header_source( VipsSource *source, VipsImage *out,
int int
vips__tiff_read_source( VipsSource *source, VipsImage *out, vips__tiff_read_source( VipsSource *source, VipsImage *out,
int page, int n, gboolean autorotate ) int page, int n, gboolean autorotate, int subifd )
{ {
Rtiff *rtiff; Rtiff *rtiff;
@ -2681,7 +2730,7 @@ vips__tiff_read_source( VipsSource *source, VipsImage *out,
vips__tiff_init(); 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 ) ) rtiff_header_read_all( rtiff ) )
return( -1 ); return( -1 );

View File

@ -69,6 +69,10 @@ typedef struct _VipsForeignLoadTiff {
*/ */
int n; int n;
/* Select subifd index. -1 for main image.
*/
int subifd;
/* Autorotate using orientation tag. /* Autorotate using orientation tag.
*/ */
gboolean autorotate; gboolean autorotate;
@ -133,7 +137,7 @@ vips_foreign_load_tiff_header( VipsForeignLoad *load )
VipsForeignLoadTiff *tiff = (VipsForeignLoadTiff *) load; VipsForeignLoadTiff *tiff = (VipsForeignLoadTiff *) load;
if( vips__tiff_read_header_source( tiff->source, load->out, 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( -1 );
return( 0 ); return( 0 );
@ -145,7 +149,7 @@ vips_foreign_load_tiff_load( VipsForeignLoad *load )
VipsForeignLoadTiff *tiff = (VipsForeignLoadTiff *) load; VipsForeignLoadTiff *tiff = (VipsForeignLoadTiff *) load;
if( vips__tiff_read_source( tiff->source, load->real, 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( -1 );
return( 0 ); return( 0 );
@ -203,6 +207,14 @@ vips_foreign_load_tiff_class_init( VipsForeignLoadTiffClass *class )
VIPS_ARGUMENT_OPTIONAL_INPUT, VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsForeignLoadTiff, autorotate ), G_STRUCT_OFFSET( VipsForeignLoadTiff, autorotate ),
FALSE ); 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 static void
@ -210,6 +222,7 @@ vips_foreign_load_tiff_init( VipsForeignLoadTiff *tiff )
{ {
tiff->page = 0; tiff->page = 0;
tiff->n = 1; tiff->n = 1;
tiff->subifd = -1;
} }
typedef struct _VipsForeignLoadTiffSource { typedef struct _VipsForeignLoadTiffSource {
@ -454,6 +467,7 @@ vips_foreign_load_tiff_buffer_init( VipsForeignLoadTiffBuffer *buffer )
* * @n: %gint, load this many pages * * @n: %gint, load this many pages
* * @autorotate: %gboolean, use orientation tag to rotate the image * * @autorotate: %gboolean, use orientation tag to rotate the image
* during load * during load
* * @subifd: %gint, select this subifd index
* *
* Read a TIFF file into a VIPS image. It is a full baseline TIFF 6 reader, * 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 * 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 * operations will use #VIPS_META_ORIENTATION, if present, to set the
* orientation of output images. * 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 * 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 * #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 * 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 * * @n: %gint, load this many pages
* * @autorotate: %gboolean, use orientation tag to rotate the image * * @autorotate: %gboolean, use orientation tag to rotate the image
* during load * during load
* * @subifd: %gint, select this subifd index
* *
* Read a TIFF-formatted memory block into a VIPS image. Exactly as * Read a TIFF-formatted memory block into a VIPS image. Exactly as
* vips_tiffload(), but read from a memory source. * 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 * * @n: %gint, load this many pages
* * @autorotate: %gboolean, use orientation tag to rotate the image * * @autorotate: %gboolean, use orientation tag to rotate the image
* during load * during load
* * @subifd: %gint, select this subifd index
* *
* Exactly as vips_tiffload(), but read from a source. * Exactly as vips_tiffload(), but read from a source.
* *

View File

@ -21,6 +21,8 @@
* - xres/yres params were in pixels/cm * - xres/yres params were in pixels/cm
* 26/1/20 * 26/1/20
* - add "depth" to set pyr depth * - 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; int level;
gboolean lossless; gboolean lossless;
VipsForeignDzDepth depth; VipsForeignDzDepth depth;
gboolean subifd;
} VipsForeignSaveTiff; } VipsForeignSaveTiff;
typedef VipsForeignSaveClass VipsForeignSaveTiffClass; typedef VipsForeignSaveClass VipsForeignSaveTiffClass;
@ -327,7 +331,7 @@ vips_foreign_save_tiff_class_init( VipsForeignSaveTiffClass *class )
1, 22, 10 ); 1, 22, 10 );
VIPS_ARG_BOOL( class, "lossless", 24, VIPS_ARG_BOOL( class, "lossless", 24,
_( "lossless" ), _( "Lossless" ),
_( "Enable WEBP lossless mode" ), _( "Enable WEBP lossless mode" ),
VIPS_ARGUMENT_OPTIONAL_INPUT, VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsForeignSaveTiff, lossless ), G_STRUCT_OFFSET( VipsForeignSaveTiff, lossless ),
@ -340,6 +344,13 @@ vips_foreign_save_tiff_class_init( VipsForeignSaveTiffClass *class )
G_STRUCT_OFFSET( VipsForeignSaveTiff, depth ), G_STRUCT_OFFSET( VipsForeignSaveTiff, depth ),
VIPS_TYPE_FOREIGN_DZ_DEPTH, VIPS_FOREIGN_DZ_DEPTH_ONETILE ); 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 static void
@ -396,7 +407,8 @@ vips_foreign_save_tiff_file_build( VipsObject *object )
tiff->region_shrink, tiff->region_shrink,
tiff->level, tiff->level,
tiff->lossless, tiff->lossless,
tiff->depth ) ) tiff->depth,
tiff->subifd ) )
return( -1 ); return( -1 );
return( 0 ); return( 0 );
@ -468,7 +480,8 @@ vips_foreign_save_tiff_buffer_build( VipsObject *object )
tiff->region_shrink, tiff->region_shrink,
tiff->level, tiff->level,
tiff->lossless, tiff->lossless,
tiff->depth ) ) tiff->depth,
tiff->subifd ) )
return( -1 ); return( -1 );
/* vips__tiff_write_buf() makes a buffer that needs g_free(), not /* 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 * * @level: %gint, Zstd compression level
* * @lossless: %gboolean, WebP losssless mode * * @lossless: %gboolean, WebP losssless mode
* * @depth: #VipsForeignDzDepth how deep to make the pyramid * * @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. * 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 * #VIPS_META_PHOTOSHOP_NAME (if set) is used to set the value of the PHOTOSHOP
* tag. * 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(). * See also: vips_tiffload(), vips_image_write_to_file().
* *
* Returns: 0 on success, -1 on error. * Returns: 0 on success, -1 on error.
@ -665,6 +683,7 @@ vips_tiffsave( VipsImage *in, const char *filename, ... )
* * @level: %gint, Zstd compression level * * @level: %gint, Zstd compression level
* * @lossless: %gboolean, WebP losssless mode * * @lossless: %gboolean, WebP losssless mode
* * @depth: #VipsForeignDzDepth how deep to make the pyramid * * @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. * As vips_tiffsave(), but save to a memory buffer.
* *

View File

@ -193,6 +193,8 @@
* - write XYZ images as logluv * - write XYZ images as logluv
* 7/2/20 [jclavoie-jive] * 7/2/20 [jclavoie-jive]
* - add PAGENUMBER support * - add PAGENUMBER support
* 23/5/20
* - add support for subifd pyramid layers
*/ */
/* /*
@ -247,6 +249,21 @@
#include "pforeign.h" #include "pforeign.h"
#include "tiff.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. /* Max number of alpha channels we allow.
*/ */
#define MAX_ALPHA (64) #define MAX_ALPHA (64)
@ -334,6 +351,7 @@ struct _Wtiff {
int level; /* zstd compression level */ int level; /* zstd compression level */
gboolean lossless; /* webp lossless mode */ gboolean lossless; /* webp lossless mode */
VipsForeignDzDepth depth; /* Pyr depth */ VipsForeignDzDepth depth; /* Pyr depth */
gboolean subifd; /* Write pyr layers into subifds */
/* True if we've detected a toilet-roll image, plus the page height, /* 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 * 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 ); return( 0 );
} }
static Layer * static void
wtiff_layer_new( Wtiff *wtiff, Layer *above, int width, int height ) 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 ); if( !above )
layer->wtiff = wtiff; /* Top of pyramid.
layer->width = width; */
layer->height = height; (*layer)->sub = 1;
else
(*layer)->sub = above->sub * 2;
if( !above ) (*layer)->lname = NULL;
/* Top of pyramid. (*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; if( wtiff->filename ) {
else if( !above )
layer->sub = above->sub * 2; (*layer)->lname = vips_strdup(
VIPS_OBJECT( wtiff->ready ),
wtiff->filename );
else {
char *lname;
layer->lname = NULL; lname = vips__temp_name( "%s.tif" );
layer->buf = NULL; (*layer)->lname = vips_strdup(
layer->len = 0; VIPS_OBJECT( wtiff->ready ),
layer->tif = NULL; lname );
layer->image = NULL; g_free( lname );
layer->write_y = 0; }
layer->y = 0; }
layer->strip = NULL;
layer->copy = NULL;
layer->below = NULL; /*
layer->above = above; printf( "wtiff_layer_init: sub = %d, width = %d, height = %d\n",
(*layer)->sub, width, height );
/* */
printf( "wtiff_layer_new: sub = %d, width = %d, height = %d\n", }
layer->sub, width, height );
*/
if( wtiff->pyramid ) { if( wtiff->pyramid ) {
int limitw, limith; 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 * Very tall or wide images might end up with a smallest layer
* larger than one tile. * larger than one tile.
*/ */
if( (layer->width > limitw || if( ((*layer)->width > limitw ||
layer->height > limith) && (*layer)->height > limith) &&
layer->width > 1 && (*layer)->width > 1 &&
layer->height > 1 ) (*layer)->height > 1 )
layer->below = wtiff_layer_new( wtiff, layer, wtiff_layer_init( wtiff, &(*layer)->below, *layer,
width / 2, height / 2 ); 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 static int
@ -617,6 +636,11 @@ wtiff_write_header( Wtiff *wtiff, Layer *layer )
int orientation; 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. /* Output base header fields.
*/ */
TIFFSetField( tif, TIFFTAG_IMAGEWIDTH, layer->width ); TIFFSetField( tif, TIFFTAG_IMAGEWIDTH, layer->width );
@ -845,40 +869,56 @@ wtiff_allocate_layers( Wtiff *wtiff )
{ {
Layer *layer; Layer *layer;
g_assert( wtiff->layer );
for( layer = wtiff->layer; layer; layer = layer->below ) { for( layer = wtiff->layer; layer; layer = layer->below ) {
layer->image = vips_image_new(); if( !layer->image ) {
if( vips_image_pipelinev( layer->image, layer->image = vips_image_new();
VIPS_DEMAND_STYLE_ANY, wtiff->ready, NULL ) ) if( vips_image_pipelinev( layer->image,
return( -1 ); VIPS_DEMAND_STYLE_ANY, wtiff->ready, NULL ) )
layer->image->Xsize = layer->width; return( -1 );
layer->image->Ysize = layer->height; layer->image->Xsize = layer->width;
layer->image->Ysize = layer->height;
layer->strip = vips_region_new( layer->image ); layer->strip = vips_region_new( layer->image );
layer->copy = vips_region_new( layer->image ); layer->copy = vips_region_new( layer->image );
/* The regions will get used in the bg thread callback, so /* The regions will get used in the bg thread callback,
* make sure we don't own them. * so make sure we don't own them.
*/ */
vips__region_no_ownership( layer->strip ); vips__region_no_ownership( layer->strip );
vips__region_no_ownership( layer->copy ); 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 ) ) if( wtiff_layer_rewind( wtiff, layer ) )
return( -1 ); 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 ) ) if( wtiff_write_header( wtiff, layer ) )
return( -1 ); 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 ); return( 0 );
} }
@ -1032,7 +1072,8 @@ wtiff_new( VipsImage *input, const char *filename,
VipsRegionShrink region_shrink, VipsRegionShrink region_shrink,
int level, int level,
gboolean lossless, gboolean lossless,
VipsForeignDzDepth depth ) VipsForeignDzDepth depth,
gboolean subifd )
{ {
Wtiff *wtiff; Wtiff *wtiff;
@ -1064,6 +1105,7 @@ wtiff_new( VipsImage *input, const char *filename,
wtiff->level = level; wtiff->level = level;
wtiff->lossless = lossless; wtiff->lossless = lossless;
wtiff->depth = depth; wtiff->depth = depth;
wtiff->subifd = subifd;
wtiff->toilet_roll = FALSE; wtiff->toilet_roll = FALSE;
wtiff->page_height = vips_image_get_page_height( input ); wtiff->page_height = vips_image_get_page_height( input );
wtiff->page_number = 0; wtiff->page_number = 0;
@ -1096,28 +1138,6 @@ wtiff_new( VipsImage *input, const char *filename,
wtiff->toilet_roll = TRUE; wtiff->toilet_roll = TRUE;
wtiff->image_height = wtiff->page_height; wtiff->image_height = wtiff->page_height;
wtiff->n_pages = wtiff->ready->Ysize / 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. /* 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 /* Can only squash 8 bit mono. 3-band float should have been squashed
* above. * above.
*/ */
@ -1190,42 +1245,6 @@ wtiff_new( VipsImage *input, const char *filename,
wtiff->tls = VIPS_IMAGE_SIZEOF_PEL( wtiff->ready ) * wtiff->tls = VIPS_IMAGE_SIZEOF_PEL( wtiff->ready ) *
wtiff->tilew; 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 ); return( wtiff );
} }
@ -1693,10 +1712,10 @@ write_strip( VipsRegion *region, VipsRect *area, void *a )
Wtiff *wtiff = (Wtiff *) a; Wtiff *wtiff = (Wtiff *) a;
Layer *layer = wtiff->layer; Layer *layer = wtiff->layer;
#ifdef DEBUG #ifdef DEBUG_VERBOSE
printf( "write_strip: strip at %d, height %d\n", printf( "write_strip: strip at %d, height %d\n",
area->top, area->height ); area->top, area->height );
#endif/*DEBUG*/ #endif/*DEBUG_VERBOSE*/
for(;;) { for(;;) {
VipsRect *to = &layer->strip->valid; VipsRect *to = &layer->strip->valid;
@ -1858,7 +1877,7 @@ wtiff_copy_tiff( Wtiff *wtiff, TIFF *out, TIFF *in )
return( 0 ); 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 static int
wtiff_gather( Wtiff *wtiff ) wtiff_gather( Wtiff *wtiff )
@ -1873,13 +1892,12 @@ wtiff_gather( Wtiff *wtiff )
TIFF *in; TIFF *in;
#ifdef DEBUG #ifdef DEBUG
printf( "Appending layer %s ...\n", layer->lname ); printf( "appending layer %s ...\n", layer->lname );
#endif /*DEBUG*/ #endif /*DEBUG*/
if( layer->lname ) { if( layer->lname ) {
if( !(source = if( !(source = vips_source_new_from_file(
vips_source_new_from_file( layer->lname )) )
layer->lname )) )
return( -1 ); return( -1 );
} }
else { else {
@ -1892,12 +1910,14 @@ wtiff_gather( Wtiff *wtiff )
VIPS_UNREF( source ); VIPS_UNREF( source );
return( -1 ); return( -1 );
} }
VIPS_UNREF( source ); VIPS_UNREF( source );
if( wtiff_copy_tiff( wtiff, wtiff->layer->tif, in ) ) { if( wtiff_copy_tiff( wtiff, wtiff->layer->tif, in ) ) {
TIFFClose( in ); TIFFClose( in );
return( -1 ); return( -1 );
} }
TIFFClose( in ); TIFFClose( in );
if( !TIFFWriteDirectory( wtiff->layer->tif ) ) if( !TIFFWriteDirectory( wtiff->layer->tif ) )
@ -1907,77 +1927,109 @@ wtiff_gather( Wtiff *wtiff )
return( 0 ); 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 static int
wtiff_write_image( Wtiff *wtiff ) 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 #ifdef DEBUG
printf( "wtiff_write_image: toilet-roll mode\n" ); printf( "writing page %d ...\n", wtiff->page_number );
#endif /*DEBUG*/ #endif /*DEBUG*/
y = 0; if( vips_crop( wtiff->ready, &page,
for(;;) { 0, y, wtiff->ready->Xsize, wtiff->page_height,
VipsImage *page; NULL ) )
return( -1 );
if( vips_crop( wtiff->ready, &page, if( wtiff_write_page( wtiff, 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 );
}
g_object_unref( page ); g_object_unref( page );
return( -1 );
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 );
} }
} g_object_unref( page );
else if( wtiff->pyramid ) {
#ifdef DEBUG
printf( "wtiff_write_image: pyramid mode\n" );
#endif /*DEBUG*/
if( vips_sink_disc( wtiff->ready, write_strip, wtiff ) ) wtiff->page_number += 1;
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 );
} }
return( 0 ); return( 0 );
@ -1999,7 +2051,8 @@ vips__tiff_write( VipsImage *input, const char *filename,
VipsRegionShrink region_shrink, VipsRegionShrink region_shrink,
int level, int level,
gboolean lossless, gboolean lossless,
VipsForeignDzDepth depth ) VipsForeignDzDepth depth,
gboolean subifd )
{ {
Wtiff *wtiff; Wtiff *wtiff;
@ -2013,7 +2066,8 @@ vips__tiff_write( VipsImage *input, const char *filename,
compression, Q, predictor, profile, compression, Q, predictor, profile,
tile, tile_width, tile_height, pyramid, squash, tile, tile_width, tile_height, pyramid, squash,
miniswhite, resunit, xres, yres, bigtiff, rgbjpeg, miniswhite, resunit, xres, yres, bigtiff, rgbjpeg,
properties, strip, region_shrink, level, lossless, depth )) ) properties, strip, region_shrink, level, lossless, depth,
subifd )) )
return( -1 ); return( -1 );
if( wtiff_write_image( wtiff ) ) { if( wtiff_write_image( wtiff ) ) {
@ -2043,7 +2097,8 @@ vips__tiff_write_buf( VipsImage *input,
VipsRegionShrink region_shrink, VipsRegionShrink region_shrink,
int level, int level,
gboolean lossless, gboolean lossless,
VipsForeignDzDepth depth ) VipsForeignDzDepth depth,
gboolean subifd )
{ {
Wtiff *wtiff; Wtiff *wtiff;
@ -2053,7 +2108,8 @@ vips__tiff_write_buf( VipsImage *input,
compression, Q, predictor, profile, compression, Q, predictor, profile,
tile, tile_width, tile_height, pyramid, squash, tile, tile_width, tile_height, pyramid, squash,
miniswhite, resunit, xres, yres, bigtiff, rgbjpeg, miniswhite, resunit, xres, yres, bigtiff, rgbjpeg,
properties, strip, region_shrink, level, lossless, depth )) ) properties, strip, region_shrink, level, lossless, depth,
subifd )) )
return( -1 ); return( -1 );
wtiff->obuf = obuf; wtiff->obuf = obuf;

View File

@ -292,6 +292,8 @@ void vips_info( const char *domain, const char *fmt, ... )
__attribute__((format(printf, 2, 3))); __attribute__((format(printf, 2, 3)));
void vips_vinfo( const char *domain, const char *fmt, va_list ap ); void vips_vinfo( const char *domain, const char *fmt, va_list ap );
VipsAngle vips_autorot_get_angle( VipsImage *image );
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif /*__cplusplus*/ #endif /*__cplusplus*/

View File

@ -192,7 +192,6 @@ int vips_rot270( VipsImage *in, VipsImage **out, ... )
__attribute__((sentinel)); __attribute__((sentinel));
int vips_rot45( VipsImage *in, VipsImage **out, ... ) int vips_rot45( VipsImage *in, VipsImage **out, ... )
__attribute__((sentinel)); __attribute__((sentinel));
VipsAngle vips_autorot_get_angle( VipsImage *image );
void vips_autorot_remove_angle( VipsImage *image ); void vips_autorot_remove_angle( VipsImage *image );
int vips_autorot( VipsImage *in, VipsImage **out, ... ) int vips_autorot( VipsImage *in, VipsImage **out, ... )
__attribute__((sentinel)); __attribute__((sentinel));

View File

@ -150,6 +150,13 @@ extern "C" {
*/ */
#define VIPS_META_N_PAGES "n-pages" #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( VipsBandFormat format );
guint64 vips_format_sizeof_unsafe( 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 ); double vips_image_get_offset( const VipsImage *image );
int vips_image_get_page_height( VipsImage *image ); int vips_image_get_page_height( VipsImage *image );
int vips_image_get_n_pages( 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 ); const void *vips_image_get_data( VipsImage *image );
void vips_image_init_fields( VipsImage *image, void vips_image_init_fields( VipsImage *image,

View File

@ -818,7 +818,7 @@ vips_image_get_page_height( VipsImage *image )
* vips_image_get_n_pages: (method) * vips_image_get_n_pages: (method)
* @image: image to get from * @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. * crazy.
* *
* This is the number of pages in the image file, not the number of pages that * 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 ); 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) * vips_image_get_data: (method)
* @image: image to get data for * @image: image to get data for
@ -934,14 +998,11 @@ static int
meta_cp( VipsImage *dst, const VipsImage *src ) meta_cp( VipsImage *dst, const VipsImage *src )
{ {
if( src->meta ) { if( src->meta ) {
/* Loop, copying fields.
*/
meta_init( dst );
/* We lock with vips_image_set() to stop races in highly- /* We lock with vips_image_set() to stop races in highly-
* threaded applications. * threaded applications.
*/ */
g_mutex_lock( vips__meta_lock ); g_mutex_lock( vips__meta_lock );
meta_init( dst );
vips_slist_map2( src->meta_traverse, vips_slist_map2( src->meta_traverse,
(VipsSListMap2Fn) meta_cp_field, dst, NULL ); (VipsSListMap2Fn) meta_cp_field, dst, NULL );
g_mutex_unlock( vips__meta_lock ); 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( name );
g_assert( value ); g_assert( value );
meta_init( image );
/* We lock between modifying metadata and copying metadata between /* We lock between modifying metadata and copying metadata between
* images, see meta_cp(). * images, see meta_cp().
* *
@ -1042,6 +1101,7 @@ vips_image_set( VipsImage *image, const char *name, GValue *value )
* highly-threaded applications. * highly-threaded applications.
*/ */
g_mutex_lock( vips__meta_lock ); g_mutex_lock( vips__meta_lock );
meta_init( image );
(void) meta_new( image, name, value ); (void) meta_new( image, name, value );
g_mutex_unlock( vips__meta_lock ); g_mutex_unlock( vips__meta_lock );

View File

@ -88,6 +88,8 @@
* - add "background" parameter * - add "background" parameter
* - better clipping means we have no jaggies on edges * - better clipping means we have no jaggies on edges
* - premultiply alpha * - premultiply alpha
* 18/5/20
* - add "premultiplied" flag
*/ */
/* /*
@ -166,6 +168,10 @@ typedef struct _VipsAffine {
*/ */
VipsPel *ink; VipsPel *ink;
/* True if the input is already premultiplied (and we don't need to).
*/
gboolean premultiplied;
} VipsAffine; } VipsAffine;
typedef VipsResampleClass VipsAffineClass; typedef VipsResampleClass VipsAffineClass;
@ -524,11 +530,13 @@ vips_affine_build( VipsObject *object )
affine->trn.idx -= 1; affine->trn.idx -= 1;
affine->trn.idy -= 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 * https://github.com/libvips/libvips/issues/291
*/ */
have_premultiplied = FALSE; have_premultiplied = FALSE;
if( vips_image_hasalpha( in ) ) { if( vips_image_hasalpha( in ) &&
!affine->premultiplied ) {
if( vips_premultiply( in, &t[3], NULL ) ) if( vips_premultiply( in, &t[3], NULL ) )
return( -1 ); return( -1 );
have_premultiplied = TRUE; have_premultiplied = TRUE;
@ -680,6 +688,13 @@ vips_affine_class_init( VipsAffineClass *class )
G_STRUCT_OFFSET( VipsAffine, background ), G_STRUCT_OFFSET( VipsAffine, background ),
VIPS_TYPE_ARRAY_DOUBLE ); 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 static void
@ -709,6 +724,7 @@ vips_affine_init( VipsAffine *affine )
* * @ody: %gdouble, output vertical offset * * @ody: %gdouble, output vertical offset
* * @extend: #VipsExtend how to generate new pixels * * @extend: #VipsExtend how to generate new pixels
* * @background: #VipsArrayDouble colour for 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. * 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. * @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 * This operation does not change xres or yres. The image resolution needs to
* be updated by the application. * be updated by the application.
* *

View File

@ -289,6 +289,7 @@ vips_resize_build( VipsObject *object )
"idx", id, "idx", id,
"idy", id, "idy", id,
"extend", VIPS_EXTEND_COPY, "extend", VIPS_EXTEND_COPY,
"premultiplied", TRUE,
NULL ) ) NULL ) )
return( -1 ); return( -1 );
in = t[4]; in = t[4];
@ -300,6 +301,7 @@ vips_resize_build( VipsObject *object )
"idx", id, "idx", id,
"idy", id, "idy", id,
"extend", VIPS_EXTEND_COPY, "extend", VIPS_EXTEND_COPY,
"premultiplied", TRUE,
NULL ) ) NULL ) )
return( -1 ); return( -1 );
in = t[4]; in = t[4];
@ -311,6 +313,7 @@ vips_resize_build( VipsObject *object )
"idx", id, "idx", id,
"idy", id, "idy", id,
"extend", VIPS_EXTEND_COPY, "extend", VIPS_EXTEND_COPY,
"premultiplied", TRUE,
NULL ) ) NULL ) )
return( -1 ); return( -1 );
in = t[4]; 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 * This operation does not change xres or yres. The image resolution needs to
* be updated by the application. * 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 * Returns: 0 on success, -1 on error
*/ */

View File

@ -28,6 +28,8 @@
* - smarter heif thumbnail selection * - smarter heif thumbnail selection
* 12/10/19 * 12/10/19
* - add thumbnail_source * - add thumbnail_source
* 2/6/20
* - add subifd pyr support
*/ */
/* /*
@ -114,9 +116,11 @@ typedef struct _VipsThumbnail {
int input_width; int input_width;
int input_height; int input_height;
int page_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_pages; /* Pages in file */
int n_loaded_pages; /* Pages we've loaded from 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. /* 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_width;
int heif_thumbnail_height; int heif_thumbnail_height;
/* For TIFF sources, open subifds rather than pages to get pyr layers.
*/
gboolean subifd_pyramid;
} VipsThumbnail; } VipsThumbnail;
typedef struct _VipsThumbnailClass { typedef struct _VipsThumbnailClass {
@ -196,9 +204,11 @@ vips_thumbnail_read_header( VipsThumbnail *thumbnail, VipsImage *image )
{ {
thumbnail->input_width = image->Xsize; thumbnail->input_width = image->Xsize;
thumbnail->input_height = image->Ysize; 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->page_height = vips_image_get_page_height( image );
thumbnail->n_pages = vips_image_get_n_pages( 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, /* VIPS_META_N_PAGES is the number of pages in the document,
* not the number we've read out into this image. We calculate * 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. /* Detect a TIFF pyramid made of pages following a roughly /2 shrink.
* We just look for two or more pages following roughly /2 shrinks. *
* This may not be a pyr tiff, so no error if we can't find the layers.
*/ */
static void static void
vips_thumbnail_get_tiff_pyramid( VipsThumbnail *thumbnail ) vips_thumbnail_get_tiff_pyramid_page( VipsThumbnail *thumbnail )
{ {
VipsThumbnailClass *class = VIPS_THUMBNAIL_GET_CLASS( thumbnail ); VipsThumbnailClass *class = VIPS_THUMBNAIL_GET_CLASS( thumbnail );
int i; 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++ ) { for( i = 0; i < thumbnail->n_pages; i++ ) {
VipsImage *page; VipsImage *page;
int level_width; 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. /* Now set level_count. This signals that we've found a pyramid.
*/ */
#ifdef DEBUG #ifdef DEBUG
printf( "vips_thumbnail_get_tiff_pyramid: %d layer pyramid detected\n", printf( "vips_thumbnail_get_tiff_pyramid_page: "
thumbnail->n_pages ); "%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*/ #endif /*DEBUG*/
thumbnail->level_count = thumbnail->n_pages; thumbnail->level_count = thumbnail->n_pages;
} }
@ -313,11 +392,10 @@ static void
vips_thumbnail_calculate_shrink( VipsThumbnail *thumbnail, vips_thumbnail_calculate_shrink( VipsThumbnail *thumbnail,
int input_width, int input_height, double *hshrink, double *vshrink ) 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 = gboolean rotate =
(thumbnail->angle == VIPS_ANGLE_D90 || thumbnail->swap &&
thumbnail->angle == VIPS_ANGLE_D270) &&
thumbnail->auto_rotate; thumbnail->auto_rotate;
int target_width = rotate ? int target_width = rotate ?
thumbnail->height : thumbnail->width; thumbnail->height : thumbnail->width;
@ -459,11 +537,18 @@ vips_thumbnail_open( VipsThumbnail *thumbnail )
g_info( "input size is %d x %d", g_info( "input size is %d x %d",
thumbnail->input_width, thumbnail->input_height ); thumbnail->input_width, thumbnail->input_height );
/* For tiff, we need a separate ->open() for each page to /* For tiff, scan the image and try to spot page-based and ifd-based
* get all the pyramid levels. * pyramids.
*/ */
if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ) if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ) {
vips_thumbnail_get_tiff_pyramid( thumbnail ); /* 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 /* For heif, we need to fetch the thumbnail size, in case we can use
* that as the source. * that as the source.
@ -524,13 +609,12 @@ vips_thumbnail_build( VipsObject *object )
{ {
VipsThumbnail *thumbnail = VIPS_THUMBNAIL( object ); VipsThumbnail *thumbnail = VIPS_THUMBNAIL( object );
VipsImage **t = (VipsImage **) vips_object_local_array( object, 15 ); VipsImage **t = (VipsImage **) vips_object_local_array( object, 15 );
VipsInterpretation interpretation = thumbnail->linear ?
VIPS_INTERPRETATION_scRGB : VIPS_INTERPRETATION_sRGB;
VipsImage *in; VipsImage *in;
int preshrunk_page_height; int preshrunk_page_height;
double hshrink; double hshrink;
double vshrink; double vshrink;
VipsInterpretation interpretation;
/* TRUE if we've done the import of an ICC transform and still need to /* TRUE if we've done the import of an ICC transform and still need to
* export. * export.
@ -628,6 +712,12 @@ vips_thumbnail_build( VipsObject *object )
*/ */
if( in->Type == VIPS_INTERPRETATION_CMYK ) if( in->Type == VIPS_INTERPRETATION_CMYK )
have_imported = TRUE; 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", g_info( "converting to processing space %s",
vips_enum_nick( VIPS_TYPE_INTERPRETATION, interpretation ) ); vips_enum_nick( VIPS_TYPE_INTERPRETATION, interpretation ) );
if( vips_colourspace( in, &t[2], interpretation, NULL ) ) if( vips_colourspace( in, &t[2], interpretation, NULL ) )
@ -742,21 +832,12 @@ vips_thumbnail_build( VipsObject *object )
} }
if( thumbnail->auto_rotate && if( thumbnail->auto_rotate &&
thumbnail->angle != VIPS_ANGLE_D0 ) { thumbnail->orientation != 1 ) {
VipsAngle angle = vips_autorot_get_angle( in ); g_info( "rotating by EXIF orientation %d",
thumbnail->orientation );
g_info( "rotating by %s", if( vips_autorot( in, &t[14], NULL ) )
vips_enum_nick( VIPS_TYPE_ANGLE, angle ) ); return( -1 );
/* 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 );
in = t[14]; in = t[14];
vips_autorot_remove_angle( in );
} }
/* Crop after rotate so we don't need to rotate the crop box. /* 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 ) ); NULL ) );
} }
else if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ) { else if( vips_isprefix( "VipsForeignLoadTiff", thumbnail->loader ) ) {
return( vips_image_new_from_file( file->filename, if( thumbnail->subifd_pyramid )
"access", VIPS_ACCESS_SEQUENTIAL, return( vips_image_new_from_file( file->filename,
"page", (int) factor, "access", VIPS_ACCESS_SEQUENTIAL,
NULL ) ); "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 ) ) { else if( vips_isprefix( "VipsForeignLoadHeif", thumbnail->loader ) ) {
return( vips_image_new_from_file( file->filename, return( vips_image_new_from_file( file->filename,

View File

@ -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/min.c
libvips/arithmetic/add.c libvips/arithmetic/sum.c
libvips/arithmetic/nary.c
libvips/arithmetic/stats.c libvips/arithmetic/stats.c
libvips/arithmetic/binary.c
libvips/arithmetic/project.c libvips/arithmetic/project.c
libvips/arithmetic/hist_find.c libvips/arithmetic/hough_line.c
libvips/arithmetic/arithmetic.c libvips/arithmetic/binary.c
libvips/arithmetic/divide.c libvips/arithmetic/deviate.c
libvips/arithmetic/invert.c
libvips/arithmetic/max.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/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/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/arithmetic/hist_find_indexed.c
libvips/colour/dE76.c libvips/arithmetic/math.c
libvips/colour/scRGB2XYZ.c
libvips/colour/LabQ2LabS.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/LabS2LabQ.c
libvips/colour/UCS2LCh.c libvips/colour/XYZ2CMYK.c
libvips/colour/CMYK2XYZ.c
libvips/colour/profiles.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/LCh2Lab.c
libvips/colour/Yxy2XYZ.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/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/wrap.c
libvips/conversion/arrayjoin.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/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/flip.c
libvips/conversion/zoom.c libvips/conversion/flatten.c
libvips/conversion/bandfold.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/subsample.c
libvips/conversion/bandbool.c
libvips/conversion/recomb.c libvips/conversion/recomb.c
libvips/conversion/bandary.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/conva.c
libvips/convolution/correlation.c libvips/convolution/fastcor.c
libvips/convolution/gaussblur.c libvips/convolution/sobel.c
libvips/convolution/conv.c libvips/convolution/canny.c
libvips/convolution/convi.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/convsep.c
libvips/convolution/sharpen.c libvips/convolution/sharpen.c
libvips/convolution/convolution.c libvips/convolution/conv.c
libvips/convolution/fastcor.c libvips/convolution/correlation.c
libvips/convolution/canny.c libvips/create/sines.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/create/grey.c libvips/create/grey.c
libvips/create/buildlut.c libvips/create/mask_ideal.c
libvips/create/create.c libvips/create/create.c
libvips/create/fractsurf.c libvips/create/mask_gaussian.c
libvips/create/black.c
libvips/create/xyz.c libvips/create/xyz.c
libvips/create/invertlut.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_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/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/mask_gaussian_band.c
libvips/create/fractsurf.c
libvips/create/identity.c
libvips/create/text.c
libvips/create/mask_fractal.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_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_image.c
libvips/draw/draw_mask.c libvips/draw/draw_mask.c
libvips/draw/draw.c libvips/draw/draw.c
libvips/foreign/vipssave.c libvips/draw/draw_smudge.c
libvips/foreign/dzsave.c libvips/draw/drawink.c
libvips/foreign/csv.c libvips/draw/draw_circle.c
libvips/foreign/niftiload.c libvips/draw/draw_flood.c
libvips/foreign/magick.c libvips/draw/draw_rect.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/foreign/foreign.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/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/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/freqfilt.c
libvips/freqfilt/invfft.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/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/hist_local.c
libvips/histogram/percent.c
libvips/histogram/maplut.c
libvips/introspect.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/sink.c
libvips/iofuncs/enumtypes.c libvips/iofuncs/gate.c
libvips/iofuncs/vector.c libvips/iofuncs/type.c
libvips/iofuncs/header.c
libvips/iofuncs/error.c
libvips/iofuncs/generate.c
libvips/iofuncs/image.c libvips/iofuncs/image.c
libvips/iofuncs/connection.c
libvips/iofuncs/threadpool.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/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/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/morph.c
libvips/morphology/nearest.c
libvips/morphology/morphology.c
libvips/morphology/rank.c libvips/morphology/rank.c
libvips/morphology/hitmiss.c
libvips/morphology/countlines.c libvips/morphology/countlines.c
libvips/mosaicing/global_balance.c libvips/morphology/nearest.c
libvips/mosaicing/im_lrmosaic.c libvips/morphology/labelregions.c
libvips/mosaicing/im_initialize.c libvips/morphology/morphology.c
libvips/mosaicing/im_remosaic.c libvips/morphology/hitmiss.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/mosaicing/im_tbmerge.c libvips/mosaicing/im_tbmerge.c
libvips/resample/shrinkh.c libvips/mosaicing/im_improve.c
libvips/resample/shrinkv.c libvips/mosaicing/im_chkpair.c
libvips/resample/reduce.c libvips/mosaicing/im_lrmosaic.c
libvips/resample/transform.c libvips/mosaicing/im_tbcalcon.c
libvips/resample/resize.c libvips/mosaicing/merge.c
libvips/resample/mapim.c libvips/mosaicing/im_remosaic.c
libvips/resample/thumbnail.c libvips/mosaicing/im_lrcalcon.c
libvips/resample/resample.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/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/quadratic.c
libvips/resample/interpolate.c libvips/resample/thumbnail.c
libvips/resample/similarity.c
libvips/resample/shrink.c libvips/resample/shrink.c
libvips/resample/similarity.c
libvips/resample/interpolate.c
tools/vips.c tools/vips.c
tools/vipsedit.c tools/vipsedit.c
tools/vipsheader.c tools/vipsheader.c
tools/vipsthumbnail.c tools/vipsthumbnail.c
cplusplus/include/vips/VImage8.h
cplusplus/include/vips/VInterpolate8.h
cplusplus/include/vips/VError8.h cplusplus/include/vips/VError8.h
cplusplus/include/vips/vips-operators.h cplusplus/include/vips/vips-operators.h
cplusplus/include/vips/VConnection8.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/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/video.h
libvips/include/vips/memory.h libvips/include/vips/internal.h
libvips/include/vips/conversion.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/private.h
libvips/include/vips/interpolate.h libvips/include/vips/interpolate.h
libvips/include/vips/internal.h libvips/include/vips/soname.h
libvips/include/vips/basic.h libvips/include/vips/vips7compat.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/create.h libvips/include/vips/create.h
libvips/include/vips/generate.h libvips/include/vips/rect.h
libvips/include/vips/format.h libvips/include/vips/dispatch.h
libvips/include/vips/util.h libvips/include/vips/version.h
libvips/include/vips/convolution.h libvips/include/vips/error.h
libvips/include/vips/thread.h libvips/include/vips/debug.h
libvips/include/vips/vips.h
libvips/include/vips/morphology.h
libvips/include/vips/resample.h libvips/include/vips/resample.h
libvips/include/vips/object.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/inlines.h
libvips/include/vips/transform.h libvips/include/vips/sbuf.h
libvips/include/vips/draw.h
libvips/include/vips/semaphore.h
libvips/include/vips/vips7compat.h
libvips/include/vips/header.h libvips/include/vips/header.h
libvips/include/vips/soname.h libvips/include/vips/type.h
libvips/include/vips/rect.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/colour.h
libvips/include/vips/mask.h libvips/include/vips/arithmetic.h
libvips/include/vips/debug.h libvips/include/vips/threadpool.h
libvips/include/vips/morphology.h libvips/include/vips/format.h
libvips/include/vips/enumtypes.h libvips/include/vips/conversion.h
libvips/include/vips/deprecated.h libvips/include/vips/draw.h
libvips/include/vips/version.h libvips/include/vips/mosaicing.h
libvips/iofuncs/sink.h libvips/include/vips/util.h
libvips/iofuncs/vipsmarshal.h libvips/include/vips/convolution.h
libvips/morphology/pmorphology.h libvips/include/vips/foreign.h
libvips/mosaicing/pmosaicing.h libvips/include/vips/transform.h
libvips/mosaicing/global_balance.h libvips/include/vips/memory.h
libvips/resample/presample.h cplusplus/VConnection.cpp
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
cplusplus/VError.cpp cplusplus/VError.cpp
cplusplus/VImage.cpp cplusplus/VImage.cpp
cplusplus/VInterpolate.cpp cplusplus/VInterpolate.cpp
cplusplus/vips-operators.cpp cplusplus/vips-operators.cpp
cplusplus/VConnection.cpp
libvips/conversion/composite.cpp libvips/conversion/composite.cpp
libvips/resample/reduceh.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/bicubic.cpp
libvips/resample/vsqbs.cpp
libvips/resample/nohalo.cpp
libvips/resample/lbb.cpp
libvips/resample/reducev.cpp

View File

@ -28,7 +28,7 @@ find */* -name "*.cxx" >> po/POTFILES.in
find */* -name "*.cpp" >> po/POTFILES.in find */* -name "*.cpp" >> po/POTFILES.in
regenerate the list of files to search for strings ... don't forget to 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 intltool-update --pot

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@ -1,17 +1,24 @@
# vim: set fileencoding=utf-8 : # vim: set fileencoding=utf-8 :
import filecmp
from functools import reduce from functools import reduce
import os
import pytest import pytest
import tempfile
import shutil
import pyvips import pyvips
from helpers import JPEG_FILE, unsigned_formats, \ from helpers import IMAGES, JPEG_FILE, unsigned_formats, \
signed_formats, float_formats, int_formats, \ signed_formats, float_formats, int_formats, \
noncomplex_formats, all_formats, max_value, \ noncomplex_formats, all_formats, max_value, \
sizeof_format, rot45_angles, rot45_angle_bonds, \ sizeof_format, rot45_angles, rot45_angle_bonds, \
rot_angles, rot_angle_bonds, run_cmp, run_cmp2, \ rot_angles, rot_angle_bonds, run_cmp, run_cmp2, \
assert_almost_equal_objects assert_almost_equal_objects, temp_filename
class TestConversion: class TestConversion:
tempdir = None
# run a function on an image, # run a function on an image,
# 50,50 and 10,10 should have different values on the test image # 50,50 and 10,10 should have different values on the test image
# don't loop over band elements # don't loop over band elements
@ -37,6 +44,7 @@ class TestConversion:
@classmethod @classmethod
def setup_class(cls): def setup_class(cls):
cls.tempdir = tempfile.mkdtemp()
im = pyvips.Image.mask_ideal(100, 100, 0.5, im = pyvips.Image.mask_ideal(100, 100, 0.5,
reject=True, optical=True) reject=True, optical=True)
cls.colour = (im * [1, 2, 3] + [2, 3, 4]).copy(interpretation="srgb") cls.colour = (im * [1, 2, 3] + [2, 3, 4]).copy(interpretation="srgb")
@ -46,6 +54,7 @@ class TestConversion:
@classmethod @classmethod
def teardown_class(cls): def teardown_class(cls):
shutil.rmtree(cls.tempdir, ignore_errors=True)
cls.colour = None cls.colour = None
cls.mono = None cls.mono = None
cls.image = None cls.image = None
@ -738,6 +747,39 @@ class TestConversion:
diff = (after - im).abs().max() diff = (after - im).abs().max()
assert diff == 0 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): def test_scaleimage(self):
for fmt in noncomplex_formats: for fmt in noncomplex_formats:
test = self.colour.cast(fmt) test = self.colour.cast(fmt)

View File

@ -340,8 +340,12 @@ class TestForeign:
self.colour, 0) self.colour, 0)
self.save_load_file(".tif", "[tile]", 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]", self.colour, 0)
self.save_load_file(".tif", "[tile,pyramid,subifd]", self.colour, 0)
self.save_load_file(".tif", self.save_load_file(".tif",
"[tile,pyramid,compression=jpeg]", self.colour, 80) "[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", "[bigtiff]", self.colour, 0)
self.save_load_file(".tif", "[compression=jpeg]", self.colour, 80) self.save_load_file(".tif", "[compression=jpeg]", self.colour, 80)
self.save_load_file(".tif", self.save_load_file(".tif",

View File

@ -24,7 +24,7 @@ bin_SCRIPTS = \
EXTRA_DIST = \ EXTRA_DIST = \
vipsprofile \ vipsprofile \
vips-8.9 \ vips-8.10 \
light_correct.in \ light_correct.in \
shrink_width.in \ shrink_width.in \
batch_image_convert.in \ batch_image_convert.in \