diff --git a/ChangeLog b/ChangeLog
index e9d18f60..b00adb01 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -7,6 +7,24 @@
- added vips_image_hasalpha()
- added vips_thumbnail() / vips_thumbnail_buffer()
- webpload/webpsave read and write icc, xmp, exif metadata
+- better >4gb detect for zip dzsave output [Felix Bünemann]
+- all loaders have a @fail option, meaning fail on first warning, though it
+ only does anything for jpg and csv
+- add vips_image_get_fields() to help bindings
+- add tiff multi-page read/write
+- add VIPS_META_PAGE_HEIGHT metadata
+- IM6/IM7 magickload supports page/n/page-height, all_frames deprecated
+- gifload supports n/page-height
+- added #defines for VIPS_SONAME, VIPS_LIBRARY_CURRENT, VIPS_LIBRARY_REVISION,
+ VIPS_LIBRARY_AGE
+- better support for bscale / bzero in fits images
+- deprecate vips_warn() / vips_info(); use g_warning() / g_info() instead
+
+8/12/16 started 8.4.5
+- allow libgsf-1.14.26 to help centos, thanks tdiprima
+
+11/11/16 started 8.4.4
+- fix crash in vips.exe arg parsing on Windows, thanks Yury
18/10/16 started 8.4.3
- fix error detection in gif_close, thanks aaron42net
diff --git a/README.md b/README.md
index 48b2e40e..93bb69cd 100644
--- a/README.md
+++ b/README.md
@@ -96,6 +96,10 @@ Leak check:
--leak-check=yes \
vips ... > vips-vg.log 2>&1
+Memory error debug:
+
+ $ valgrind --vgdb=yes --vgdb-error=0 vips ...
+
valgrind threading check:
$ valgrind --tool=helgrind vips ... > vips-vg.log 2>&1
diff --git a/configure.ac b/configure.ac
index 1a4cea8f..78d7acc5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -404,9 +404,12 @@ GTK_DOC_CHECK([1.14],[--flavour no-tmpl])
AC_ARG_WITH([gsf],
AS_HELP_STRING([--without-gsf], [build without libgsf-1 (default: test)]))
-# libgsf-1 1.14.21 crashes, .27 is known-good, not sure about 22-26
+# libgsf-1 1.14.21 crashes
+# .27 is known to work well
+# .26 seems OK but has not been tested much
+# not sure about 22-25
if test x"$with_gsf" != "xno"; then
- PKG_CHECK_MODULES(GSF, libgsf-1 >= 1.14.27,
+ PKG_CHECK_MODULES(GSF, libgsf-1 >= 1.14.26,
[AC_DEFINE(HAVE_GSF,1,[define if you have libgsf-1 installed.])
with_gsf=yes
PACKAGES_USED="$PACKAGES_USED libgsf-1"
@@ -1114,7 +1117,7 @@ file import/export with libtiff: $with_tiff
file import/export with giflib: $with_giflib
file import/export with libjpeg: $with_jpeg
image pyramid export: $with_gsf
- (requires libgsf-1 1.14.27 or later)
+ (requires libgsf-1 1.14.26 or later)
use libexif to load/save JPEG metadata: $with_libexif
])
diff --git a/cplusplus/VImage.cpp b/cplusplus/VImage.cpp
index d9b55133..a1905441 100644
--- a/cplusplus/VImage.cpp
+++ b/cplusplus/VImage.cpp
@@ -350,7 +350,7 @@ set_property( VipsObject *object, const char *name, const GValue *value )
if( vips_object_get_argument( object, name,
&pspec, &argument_class, &argument_instance ) ) {
- vips_warn( NULL, "%s", vips_error_buffer() );
+ g_warning( "%s", vips_error_buffer() );
vips_error_clear();
return;
}
@@ -364,7 +364,7 @@ set_property( VipsObject *object, const char *name, const GValue *value )
if( (enum_value = vips_enum_from_nick( object_class->nickname,
pspec_type, g_value_get_string( value ) )) < 0 ) {
- vips_warn( NULL, "%s", vips_error_buffer() );
+ g_warning( "%s", vips_error_buffer() );
vips_error_clear();
return;
}
diff --git a/doc/using-command-line.xml b/doc/using-command-line.xml
index 95f48ec5..828fc256 100644
--- a/doc/using-command-line.xml
+++ b/doc/using-command-line.xml
@@ -208,6 +208,14 @@ rm t1.v
leak-test on exit, and also display an estimate of peak memory use.
+
+
+
+ Set G_MESSAGES_DEBUG=VIPS
and GLib will display
+ informational and debug messages from libvips.
+
+
+
diff --git a/libvips/Makefile.am b/libvips/Makefile.am
index 015ac722..d530f8ad 100644
--- a/libvips/Makefile.am
+++ b/libvips/Makefile.am
@@ -64,6 +64,14 @@ EXTRA_DIST = \
CLEANFILES =
+all-local:
+ echo '/* This file is autogenerated, do not edit. */' > soname.h && \
+ source libvips.la && \
+ echo "#define VIPS_SONAME \"$$dlname\"" >> soname.h && \
+ ( cmp -s soname.h include/vips/soname.h || \
+ cp soname.h include/vips ) && \
+ rm soname.h
+
-include $(INTROSPECTION_MAKEFILE)
INTROSPECTION_GIRS =
INTROSPECTION_SCANNER_ARGS = --add-include-path=$(srcdir)
diff --git a/libvips/arithmetic/measure.c b/libvips/arithmetic/measure.c
index 9e7c881e..5bbfcfb5 100644
--- a/libvips/arithmetic/measure.c
+++ b/libvips/arithmetic/measure.c
@@ -165,9 +165,11 @@ vips_measure_build( VipsObject *object )
*/
if( dev * 5 > VIPS_FABS( avg ) &&
VIPS_FABS( avg ) > 3 )
- vips_warn( class->nickname,
- _( "patch %d x %d, band %d: "
- "avg = %g, sdev = %g" ),
+ g_warning( _( "%s: "
+ "patch %d x %d, "
+ "band %d: "
+ "avg = %g, sdev = %g" ),
+ class->nickname,
i, j, b, avg, dev );
*VIPS_MATRIX( measure->out,
diff --git a/libvips/colour/icc_transform.c b/libvips/colour/icc_transform.c
index 43c4c891..f41c12c9 100644
--- a/libvips/colour/icc_transform.c
+++ b/libvips/colour/icc_transform.c
@@ -193,9 +193,9 @@ static int
icc_error( int code, const char *text )
{
if( code == LCMS_ERRC_WARNING )
- vips_warn( "VipsIcc", "%s", text );
+ g_warning( "%s", text );
else
- vips_error( "VipsIcc", "%s", text );
+ vips_error( "VipsIcc", text );
return( 0 );
}
@@ -452,9 +452,9 @@ vips_check_intent( const char *domain,
{
if( profile &&
!cmsIsIntentSupported( profile, intent, direction ) )
- vips_warn( domain,
- _( "intent %d (%s) not supported by "
+ g_warning( _( "%s: intent %d (%s) not supported by "
"%s profile; falling back to default intent" ),
+ domain,
intent, vips_enum_nick( VIPS_TYPE_INTENT, intent ),
direction == LCMS_USED_AS_INPUT ?
_( "input" ) : _( "output" ) );
@@ -542,7 +542,7 @@ vips_image_expected_bands( VipsImage *image )
}
static cmsHPROFILE
-vips_icc_load_profile_image( const char *domain, VipsImage *image )
+vips_icc_load_profile_image( VipsImage *image )
{
void *data;
size_t data_length;
@@ -554,15 +554,15 @@ vips_icc_load_profile_image( const char *domain, VipsImage *image )
if( vips_image_get_blob( image, VIPS_META_ICC_NAME,
&data, &data_length ) ||
!(profile = cmsOpenProfileFromMem( data, data_length )) ) {
- vips_warn( domain, "%s", _( "corrupt embedded profile" ) );
+ g_warning( "%s", _( "corrupt embedded profile" ) );
return( NULL );
}
if( vips_image_expected_bands( image ) !=
vips_icc_profile_needs_bands( profile ) ) {
VIPS_FREEF( cmsCloseProfile, profile );
- vips_warn( domain,
- "%s", _( "embedded profile incompatible with image" ) );
+ g_warning( "%s",
+ _( "embedded profile incompatible with image" ) );
return( NULL );
}
@@ -584,8 +584,7 @@ vips_icc_load_profile_file( const char *domain,
if( vips_image_expected_bands( image ) !=
vips_icc_profile_needs_bands( profile ) ) {
VIPS_FREEF( cmsCloseProfile, profile );
- vips_warn( domain,
- _( "profile \"%s\" incompatible with image" ),
+ g_warning( _( "profile \"%s\" incompatible with image" ),
filename );
return( NULL );
}
@@ -615,8 +614,7 @@ vips_icc_import_build( VipsObject *object )
if( code->in &&
(import->embedded ||
!import->input_profile_filename) )
- icc->in_profile = vips_icc_load_profile_image( class->nickname,
- code->in );
+ icc->in_profile = vips_icc_load_profile_image( code->in );
if( !icc->in_profile &&
code->in &&
@@ -1027,8 +1025,7 @@ vips_icc_transform_build( VipsObject *object )
if( code->in &&
(transform->embedded ||
!transform->input_profile_filename) )
- icc->in_profile = vips_icc_load_profile_image( class->nickname,
- code->in );
+ icc->in_profile = vips_icc_load_profile_image( code->in );
if( !icc->in_profile &&
code->in &&
diff --git a/libvips/conversion/cast.c b/libvips/conversion/cast.c
index 3e4bb0fb..6e658ee5 100644
--- a/libvips/conversion/cast.c
+++ b/libvips/conversion/cast.c
@@ -126,11 +126,9 @@ vips_cast_preeval( VipsImage *image, VipsProgress *progress, VipsCast *cast )
static void
vips_cast_posteval( VipsImage *image, VipsProgress *progress, VipsCast *cast )
{
- VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( cast );
-
- if( cast->overflow || cast->underflow )
- vips_warn( class->nickname,
- _( "%d underflows and %d overflows detected" ),
+ if( cast->overflow ||
+ cast->underflow )
+ g_warning( _( "%d underflows and %d overflows detected" ),
cast->underflow, cast->overflow );
}
diff --git a/libvips/conversion/copy.c b/libvips/conversion/copy.c
index ff3ee88e..168a0281 100644
--- a/libvips/conversion/copy.c
+++ b/libvips/conversion/copy.c
@@ -177,7 +177,7 @@ vips_copy_build( VipsObject *object )
return( -1 );
if( copy->swap )
- vips_warn( class->nickname, "%s",
+ g_warning( "%s",
_( "copy swap is deprecated, use byteswap instead" ) );
if( vips_image_pipelinev( conversion->out,
diff --git a/libvips/conversion/sequential.c b/libvips/conversion/sequential.c
index 62b19c35..0942c2a1 100644
--- a/libvips/conversion/sequential.c
+++ b/libvips/conversion/sequential.c
@@ -124,7 +124,6 @@ vips_sequential_generate( VipsRegion *or,
void *seq, void *a, void *b, gboolean *stop )
{
VipsSequential *sequential = (VipsSequential *) b;
- VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( sequential );
VipsRect *r = &or->valid;
VipsRegion *ir = (VipsRegion *) seq;
@@ -132,8 +131,7 @@ vips_sequential_generate( VipsRegion *or,
g_thread_self(), r->top, r->height );
if( sequential->trace )
- vips_info( class->nickname,
- "request for line %d, height %d",
+ g_info( "request for line %d, height %d",
r->top, r->height );
VIPS_GATE_START( "vips_sequential_generate: wait" );
diff --git a/libvips/conversion/tilecache.c b/libvips/conversion/tilecache.c
index d7299d0e..789e73a6 100644
--- a/libvips/conversion/tilecache.c
+++ b/libvips/conversion/tilecache.c
@@ -605,7 +605,6 @@ vips_tile_cache_gen( VipsRegion *or,
{
VipsRegion *in = (VipsRegion *) seq;
VipsBlockCache *cache = (VipsBlockCache *) b;
- VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( cache );
VipsRect *r = &or->valid;
VipsTile *tile;
@@ -702,8 +701,7 @@ vips_tile_cache_gen( VipsRegion *or,
"vips_tile_cache_gen: "
"error on tile %p\n", tile );
- vips_warn( class->nickname,
- _( "error in tile %d x %d" ),
+ g_warning( _( "error in tile %d x %d" ),
tile->pos.left, tile->pos.top );
vips_region_black( tile->region );
diff --git a/libvips/convolution/convi.c b/libvips/convolution/convi.c
index d11e3606..b7c6845c 100644
--- a/libvips/convolution/convi.c
+++ b/libvips/convolution/convi.c
@@ -860,7 +860,7 @@ intize_to_fixed_point( VipsImage *in, int *out )
for( i = 0; i < ne; i++ )
if( scaled[i] >= 4.0 ||
scaled[i] < -4 ) {
- vips_info( "intize_to_fixed_point",
+ g_info( "intize_to_fixed_point: "
"out of range for vector path" );
return( -1 );
}
@@ -880,7 +880,7 @@ intize_to_fixed_point( VipsImage *in, int *out )
/* 0.1 is a 10% error.
*/
if( total_error > 0.1 ) {
- vips_info( "intize_to_fixed_point", "too many underflows" );
+ g_info( "intize_to_fixed_point: too many underflows" );
return( -1 );
}
@@ -906,7 +906,6 @@ intize_to_fixed_point( VipsImage *in, int *out )
static int
vips_convi_build( VipsObject *object )
{
- VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );
VipsConvolution *convolution = (VipsConvolution *) object;
VipsConvi *convi = (VipsConvi *) object;
VipsImage **t = (VipsImage **) vips_object_local_array( object, 4 );
@@ -941,7 +940,7 @@ vips_convi_build( VipsObject *object )
!intize_to_fixed_point( M, convi->fixed ) &&
!vips_convi_compile( convi, in ) ) {
generate = vips_convi_gen_vector;
- vips_info( class->nickname, "using vector path" );
+ g_info( "using vector path" );
}
else
vips_convi_compile_free( convi );
@@ -980,7 +979,7 @@ vips_convi_build( VipsObject *object )
}
generate = vips_convi_gen;
- vips_info( class->nickname, "using C path" );
+ g_info( "using C path" );
}
g_object_set( convi, "out", vips_image_new(), NULL );
diff --git a/libvips/convolution/gaussblur.c b/libvips/convolution/gaussblur.c
index 41800097..d5bfc29a 100644
--- a/libvips/convolution/gaussblur.c
+++ b/libvips/convolution/gaussblur.c
@@ -67,7 +67,6 @@ G_DEFINE_TYPE( VipsGaussblur, vips_gaussblur, VIPS_TYPE_OPERATION );
static int
vips_gaussblur_build( VipsObject *object )
{
- VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );
VipsGaussblur *gaussblur = (VipsGaussblur *) object;
VipsImage **t = (VipsImage **) vips_object_local_array( object, 2 );
@@ -85,7 +84,7 @@ vips_gaussblur_build( VipsObject *object )
vips_matrixprint( t[0], NULL );
#endif /*DEBUG*/
- vips_info( class->nickname, "gaussblur mask width %d", t[0]->Xsize );
+ g_info( "gaussblur mask width %d", t[0]->Xsize );
if( vips_convsep( gaussblur->in, &t[1], t[0],
"precision", gaussblur->precision,
diff --git a/libvips/deprecated/im_csv2vips.c b/libvips/deprecated/im_csv2vips.c
index 55900b93..a2bf48e0 100644
--- a/libvips/deprecated/im_csv2vips.c
+++ b/libvips/deprecated/im_csv2vips.c
@@ -76,7 +76,7 @@ im_csv2vips( const char *filename, IMAGE *out )
}
if( vips__csv_read( name, out,
- start_skip, lines, whitespace, separator ) )
+ start_skip, lines, whitespace, separator, FALSE ) )
return( -1 );
return( 0 );
diff --git a/libvips/deprecated/im_magick2vips.c b/libvips/deprecated/im_magick2vips.c
index 06b6d56f..2858cc7e 100644
--- a/libvips/deprecated/im_magick2vips.c
+++ b/libvips/deprecated/im_magick2vips.c
@@ -50,7 +50,7 @@ im_magick2vips( const char *filename, IMAGE *out )
#ifdef HAVE_MAGICK
/* Old behaviour was always to read all frames.
*/
- return( vips__magick_read( filename, out, TRUE, NULL, 0 ) );
+ return( vips__magick_read( filename, out, NULL, 0, -1 ) );
#else
vips_error( "im_magick2vips",
"%s", _( "no libMagick support in your libvips" ) );
diff --git a/libvips/deprecated/im_tiff2vips.c b/libvips/deprecated/im_tiff2vips.c
index 6fa25122..74479115 100644
--- a/libvips/deprecated/im_tiff2vips.c
+++ b/libvips/deprecated/im_tiff2vips.c
@@ -94,11 +94,11 @@ tiff2vips( const char *name, IMAGE *out, gboolean header_only )
}
if( header_only ) {
- if( vips__tiff_read_header( filename, out, page, FALSE ) )
+ if( vips__tiff_read_header( filename, out, page, 1, FALSE ) )
return( -1 );
}
else {
- if( vips__tiff_read( filename, out, page, FALSE, TRUE ) )
+ if( vips__tiff_read( filename, out, page, 1, FALSE, TRUE ) )
return( -1 );
}
#else
diff --git a/libvips/deprecated/rename.c b/libvips/deprecated/rename.c
index 7285ffa9..b489eefb 100644
--- a/libvips/deprecated/rename.c
+++ b/libvips/deprecated/rename.c
@@ -724,3 +724,79 @@ vips_check_bands_3ormore( const char *domain, VipsImage *im )
{
return( vips_check_bands_atleast( domain, im, 3 ) );
}
+
+/* The old vips_info() stuff, now replaced by g_warning() / g_info().
+ */
+
+int vips__info = 0;
+
+void
+vips_info_set( gboolean info )
+{
+ vips__info = info;
+
+ if( info ) {
+ const char *old;
+ char *new;
+
+ old = g_getenv( "G_MESSAGES_DEBUG" );
+ if( !old )
+ old = "";
+ new = g_strdup_printf( "%s VIPS", old );
+ g_setenv( "G_MESSAGES_DEBUG", new, TRUE );
+ g_free( new );
+ }
+}
+
+void
+vips_vinfo( const char *domain, const char *fmt, va_list ap )
+{
+ if( vips__info ) {
+ g_mutex_lock( vips__global_lock );
+ (void) fprintf( stderr, _( "%s: " ), _( "info" ) );
+ if( domain )
+ (void) fprintf( stderr, _( "%s: " ), domain );
+ (void) vfprintf( stderr, fmt, ap );
+ (void) fprintf( stderr, "\n" );
+ g_mutex_unlock( vips__global_lock );
+ }
+}
+
+void
+vips_info( const char *domain, const char *fmt, ... )
+{
+ va_list ap;
+
+ va_start( ap, fmt );
+ vips_vinfo( domain, fmt, ap );
+ va_end( ap );
+}
+
+void
+vips_vwarn( const char *domain, const char *fmt, va_list ap )
+{
+ if( !g_getenv( "IM_WARNING" ) &&
+ !g_getenv( "VIPS_WARNING" ) ) {
+ g_mutex_lock( vips__global_lock );
+ (void) fprintf( stderr, _( "%s: " ), _( "vips warning" ) );
+ if( domain )
+ (void) fprintf( stderr, _( "%s: " ), domain );
+ (void) vfprintf( stderr, fmt, ap );
+ (void) fprintf( stderr, "\n" );
+ g_mutex_unlock( vips__global_lock );
+ }
+
+ if( vips__fatal )
+ vips_error_exit( "vips__fatal" );
+}
+
+void
+vips_warn( const char *domain, const char *fmt, ... )
+{
+ va_list ap;
+
+ va_start( ap, fmt );
+ vips_vwarn( domain, fmt, ap );
+ va_end( ap );
+}
+
diff --git a/libvips/foreign/csv.c b/libvips/foreign/csv.c
index 5ea4cbcf..70aae08c 100644
--- a/libvips/foreign/csv.c
+++ b/libvips/foreign/csv.c
@@ -172,7 +172,7 @@ skip_to_sep( FILE *fp, const char sepmap[256] )
*/
static int
read_double( FILE *fp, const char whitemap[256], const char sepmap[256],
- int lineno, int colno, double *out )
+ int lineno, int colno, double *out, gboolean fail )
{
int ch;
@@ -195,9 +195,10 @@ read_double( FILE *fp, const char whitemap[256], const char sepmap[256],
/* Only a warning, since (for example) exported spreadsheets
* will often have text or date fields.
*/
- vips_warn( "csv2vips",
- _( "error parsing number, line %d, column %d" ),
+ g_warning( _( "error parsing number, line %d, column %d" ),
lineno, colno );
+ if( fail )
+ return( EOF );
/* Step over the bad data to the next separator.
*/
@@ -222,7 +223,8 @@ read_csv( FILE *fp, VipsImage *out,
int skip,
int lines,
const char *whitespace, const char *separator,
- gboolean read_image )
+ gboolean read_image,
+ gboolean fail )
{
int i;
char whitemap[256];
@@ -265,7 +267,7 @@ read_csv( FILE *fp, VipsImage *out,
}
for( columns = 0;
(ch = read_double( fp, whitemap, sepmap,
- skip + 1, columns + 1, &d )) == 0;
+ skip + 1, columns + 1, &d, fail )) == 0;
columns++ )
;
(void) fsetpos( fp, &pos );
@@ -308,7 +310,7 @@ read_csv( FILE *fp, VipsImage *out,
int colno = x + 1;
ch = read_double( fp, whitemap, sepmap,
- lineno, colno, &d );
+ lineno, colno, &d, fail );
if( ch == EOF ) {
vips_error( "csv2vips",
_( "unexpected EOF, line %d col %d" ),
@@ -342,13 +344,15 @@ read_csv( FILE *fp, VipsImage *out,
int
vips__csv_read( const char *filename, VipsImage *out,
- int skip, int lines, const char *whitespace, const char *separator )
+ int skip, int lines, const char *whitespace, const char *separator,
+ gboolean fail )
{
FILE *fp;
if( !(fp = vips__file_open_read( filename, NULL, TRUE )) )
return( -1 );
- if( read_csv( fp, out, skip, lines, whitespace, separator, TRUE ) ) {
+ if( read_csv( fp, out,
+ skip, lines, whitespace, separator, TRUE, fail ) ) {
fclose( fp );
return( -1 );
}
@@ -359,13 +363,15 @@ vips__csv_read( const char *filename, VipsImage *out,
int
vips__csv_read_header( const char *filename, VipsImage *out,
- int skip, int lines, const char *whitespace, const char *separator )
+ int skip, int lines, const char *whitespace, const char *separator,
+ gboolean fail )
{
FILE *fp;
if( !(fp = vips__file_open_read( filename, NULL, TRUE )) )
return( -1 );
- if( read_csv( fp, out, skip, lines, whitespace, separator, FALSE ) ) {
+ if( read_csv( fp, out,
+ skip, lines, whitespace, separator, FALSE, fail ) ) {
fclose( fp );
return( -1 );
}
diff --git a/libvips/foreign/csvload.c b/libvips/foreign/csvload.c
index 9ee62135..59c37fe2 100644
--- a/libvips/foreign/csvload.c
+++ b/libvips/foreign/csvload.c
@@ -89,7 +89,8 @@ vips_foreign_load_csv_header( VipsForeignLoad *load )
VipsForeignLoadCsv *csv = (VipsForeignLoadCsv *) load;
if( vips__csv_read_header( csv->filename, load->out,
- csv->skip, csv->lines, csv->whitespace, csv->separator ) )
+ csv->skip, csv->lines, csv->whitespace, csv->separator,
+ load->fail ) )
return( -1 );
VIPS_SETSTR( load->out->filename, csv->filename );
@@ -103,7 +104,8 @@ vips_foreign_load_csv_load( VipsForeignLoad *load )
VipsForeignLoadCsv *csv = (VipsForeignLoadCsv *) load;
if( vips__csv_read( csv->filename, load->real,
- csv->skip, csv->lines, csv->whitespace, csv->separator ) )
+ csv->skip, csv->lines, csv->whitespace, csv->separator,
+ load->fail ) )
return( -1 );
return( 0 );
@@ -191,6 +193,7 @@ vips_foreign_load_csv_init( VipsForeignLoadCsv *csv )
* * @lines: read this many lines from file
* * @whitespace: set of whitespace characters
* * @separator: set of separator characters
+ * * @fail: %gboolean, fail on warnings
*
* Load a CSV (comma-separated values) file. The output image is always 1
* band (monochrome), #VIPS_FORMAT_DOUBLE. Use vips_bandfold() to turn
@@ -218,6 +221,8 @@ vips_foreign_load_csv_init( VipsForeignLoadCsv *csv )
* @separator sets the characters that separate fields.
* Default ;,tab. Separators are never run together.
*
+ * Setting @fail to %TRUE makes the reader fail on any warnings.
+ *
* See also: vips_image_new_from_file(), vips_bandfold().
*
* Returns: 0 on success, -1 on error.
diff --git a/libvips/foreign/dzsave.c b/libvips/foreign/dzsave.c
index fae5584b..d35e55cb 100644
--- a/libvips/foreign/dzsave.c
+++ b/libvips/foreign/dzsave.c
@@ -69,6 +69,8 @@
* - move vips-properties out of subdir for gm and zoomify layouts
* 15/10/16
* - add dzsave_buffer
+ * 11/11/16 Felix Bünemann
+ * - better >4gb detection for zip output on older libgsfs
*/
/*
@@ -322,6 +324,12 @@ typedef struct _VipsGsfDirectory {
*/
GsfOutput *container;
+ /* Track number of files in tree and total length of filenames. We use
+ * this to estimate zip size to spot a >4gb write.
+ */
+ size_t file_count;
+ size_t filename_lengths;
+
/* Set deflate compression level for zip container.
*/
gint deflate_level;
@@ -392,6 +400,8 @@ vips_gsf_tree_new( GsfOutput *out, gint deflate_level )
tree->children = NULL;
tree->out = out;
tree->container = NULL;
+ tree->file_count = 0;
+ tree->filename_lengths = 0;
tree->deflate_level = deflate_level;
return( tree );
@@ -429,6 +439,8 @@ vips_gsf_dir_new( VipsGsfDirectory *parent, const char *name )
dir->name = g_strdup( name );
dir->children = NULL;
dir->container = NULL;
+ dir->file_count = 0;
+ dir->filename_lengths = 0;
dir->deflate_level = parent->deflate_level;
if( GSF_IS_OUTFILE_ZIP( parent->out ) )
@@ -467,13 +479,23 @@ vips_gsf_path( VipsGsfDirectory *tree, const char *name, ... )
char *dir_name;
GsfOutput *obj;
+ /* vips_gsf_path() always makes a new file, though it may add to an
+ * existing directory. Note the file, and note the length of the full
+ * path we are creating.
+ */
+ tree->file_count += 1;
+ tree->filename_lengths += strlen( tree->out->name ) + strlen( name ) + 1;
+
dir = tree;
va_start( ap, name );
- while( (dir_name = va_arg( ap, char * )) )
+ while( (dir_name = va_arg( ap, char * )) ) {
if( (child = vips_gsf_child_by_name( dir, dir_name )) )
dir = child;
else
dir = vips_gsf_dir_new( dir, dir_name );
+
+ tree->filename_lengths += strlen( dir_name ) + 1;
+ }
va_end( ap );
if( GSF_IS_OUTFILE_ZIP( dir->out ) ) {
@@ -1220,6 +1242,29 @@ tile_equal( VipsImage *image, VipsPel * restrict ink )
return( TRUE );
}
+#define VIPS_ZIP_FIXED_LH_SIZE (30 + 29)
+#define VIPS_ZIP_FIXED_CD_SIZE (46 + 9)
+#define VIPS_ZIP_EOCD_SIZE 22
+
+#ifndef HAVE_GSF_ZIP64
+static size_t
+estimate_zip_size( VipsForeignSaveDz *dz )
+{
+ size_t estimated_zip_size = dz->bytes_written +
+ dz->tree->file_count * VIPS_ZIP_FIXED_LH_SIZE +
+ dz->tree->filename_lengths +
+ dz->tree->file_count * VIPS_ZIP_FIXED_CD_SIZE +
+ dz->tree->filename_lengths +
+ VIPS_ZIP_EOCD_SIZE;
+
+#ifdef DEBUG_VERBOSE
+ printf( "estimate_zip_size: %zd\n", estimated_zip_size );
+#endif /*DEBUG_VERBOSE*/
+
+ return( estimated_zip_size );
+}
+#endif /*HAVE_GSF_ZIP64*/
+
static int
strip_work( VipsThreadState *state, void *a )
{
@@ -1339,17 +1384,26 @@ strip_work( VipsThreadState *state, void *a )
}
#ifndef HAVE_GSF_ZIP64
- /* Allow a 100,000 byte margin. This probably isn't enough: we don't
- * include the space zip needs for the index nor anything we are
- * outputting apart from the gsf_output_write() above.
- */
- if( dz->container == VIPS_FOREIGN_DZ_CONTAINER_ZIP &&
- dz->bytes_written > (size_t) UINT_MAX - 100000 ) {
- g_mutex_unlock( vips__global_lock );
+ if( dz->container == VIPS_FOREIGN_DZ_CONTAINER_ZIP ) {
+ /* Leave 3 entry headroom for blank.png and metadata files.
+ */
+ if( dz->tree->file_count + 3 >= (unsigned int) USHRT_MAX ) {
+ g_mutex_unlock( vips__global_lock );
- vips_error( class->nickname,
- "%s", _( "output file too large" ) );
- return( -1 );
+ vips_error( class->nickname,
+ "%s", _( "too many files in zip" ) );
+ return( -1 );
+ }
+
+ /* Leave 16k headroom for blank.png and metadata files.
+ */
+ if( estimate_zip_size( dz ) > (size_t) UINT_MAX - 16384) {
+ g_mutex_unlock( vips__global_lock );
+
+ vips_error( class->nickname,
+ "%s", _( "output file too large" ) );
+ return( -1 );
+ }
}
#endif /*HAVE_GSF_ZIP64*/
@@ -1943,8 +1997,7 @@ vips_foreign_save_dz_build( VipsObject *object )
#ifndef HAVE_GSF_DEFLATE_LEVEL
if( dz->compression > 0 ) {
- vips_warn( class->nickname, "%s",
- _( "deflate-level not supported by libgsf, "
+ g_warning( _( "deflate-level not supported by libgsf, "
"using default compression" ) );
dz->compression = -1;
}
diff --git a/libvips/foreign/fits.c b/libvips/foreign/fits.c
index 583c272a..dac90adb 100644
--- a/libvips/foreign/fits.c
+++ b/libvips/foreign/fits.c
@@ -23,6 +23,9 @@
* - redo as a set of fns ready for wrapping in a new-style class
* 23/6/13
* - fix ushort save with values >32k, thanks weaverwb
+ * 4/1/17
+ * - load to equivalent data type, not raw image data type ... improves
+ * support for BSCALE / BZERO settings
*/
/*
@@ -119,7 +122,7 @@ typedef struct {
VipsPel *buffer;
} VipsFits;
-const char *vips__fits_suffs[] = { ".fits", NULL };
+const char *vips__fits_suffs[] = { ".fits", ".fit", ".fts", NULL };
static void
vips_fits_error( int status )
@@ -219,10 +222,21 @@ vips_fits_get_header( VipsFits *fits, VipsImage *out )
return( -1 );
}
+ /* cfitsio does automatic conversion from the format stored in
+ * the file to the equivalent type after scale/offset. We need
+ * to allocate a vips image of the equivalent type, not the original
+ * type.
+ */
+ if( fits_get_img_equivtype( fits->fptr, &bitpix, &status ) ) {
+ vips_fits_error( status );
+ return( -1 );
+ }
+
#ifdef VIPS_DEBUG
VIPS_DEBUG_MSG( "naxis = %d\n", fits->naxis );
for( i = 0; i < fits->naxis; i++ )
VIPS_DEBUG_MSG( "%d) %lld\n", i, fits->naxes[i] );
+ VIPS_DEBUG_MSG( "fits2vips: bitpix = %d\n", bitpix );
#endif /*VIPS_DEBUG*/
height = 1;
@@ -266,8 +280,8 @@ vips_fits_get_header( VipsFits *fits, VipsImage *out )
if( fits->band_select != -1 )
bands = 1;
- /* Get image format. We want the 'raw' format of the image, our caller
- * can convert using the meta info if they want.
+ /* Get image format. This is the equivalent format, or the format
+ * stored in the file.
*/
for( i = 0; i < VIPS_NUMBER( fits2vips_formats ); i++ )
if( fits2vips_formats[i][0] == bitpix )
diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c
index 3313b9e6..65322c72 100644
--- a/libvips/foreign/foreign.c
+++ b/libvips/foreign/foreign.c
@@ -846,9 +846,9 @@ vips_foreign_load_build( VipsObject *object )
if( (flags & VIPS_FOREIGN_PARTIAL) &&
(flags & VIPS_FOREIGN_SEQUENTIAL) ) {
- vips_warn( class->nickname, "%s",
+ g_warning( "%s",
_( "VIPS_FOREIGN_PARTIAL and VIPS_FOREIGN_SEQUENTIAL "
- "both set -- using SEQUENTIAL" ) );
+ "both set -- using SEQUENTIAL" ) );
flags ^= VIPS_FOREIGN_PARTIAL;
}
@@ -865,12 +865,10 @@ vips_foreign_load_build( VipsObject *object )
build( object ) )
return( -1 );
- if( load->sequential ) {
- vips_warn( class->nickname, "%s",
- _( "ignoring deprecated \"sequential\" mode" ) );
- vips_warn( class->nickname, "%s",
- _( "please use \"access\" instead" ) );
- }
+ if( load->sequential )
+ g_warning( "%s",
+ _( "ignoring deprecated \"sequential\" mode -- "
+ "please use \"access\" instead" ) );
g_object_set( object, "out", vips_image_new(), NULL );
@@ -986,6 +984,13 @@ vips_foreign_load_class_init( VipsForeignLoadClass *class )
G_STRUCT_OFFSET( VipsForeignLoad, sequential ),
FALSE );
+ VIPS_ARG_BOOL( class, "fail", 11,
+ _( "Fail" ),
+ _( "Fail on first warning" ),
+ VIPS_ARGUMENT_OPTIONAL_INPUT,
+ G_STRUCT_OFFSET( VipsForeignLoad, fail ),
+ FALSE );
+
}
static void
diff --git a/libvips/foreign/gifload.c b/libvips/foreign/gifload.c
index f641623f..5b347ac6 100644
--- a/libvips/foreign/gifload.c
+++ b/libvips/foreign/gifload.c
@@ -11,6 +11,8 @@
* - support unicode on win
* 19/8/16
* - better transparency detection, thanks diegocsandrim
+ * 25/11/16
+ * - support @n, page-height
*/
/*
@@ -82,8 +84,20 @@ typedef struct _VipsForeignLoadGif {
*/
int page;
+ /* Load this many pages.
+ */
+ int n;
+
GifFileType *file;
+ /* The current read position, in pages.
+ */
+ int current_page;
+
+ /* Set for EOF detected.
+ */
+ gboolean eof;
+
/* As we scan the file, the index of the transparent pixel for this
* frame.
*/
@@ -345,7 +359,6 @@ static void
vips_foreign_load_gif_render_line( VipsForeignLoadGif *gif,
int width, VipsPel * restrict q, VipsPel * restrict p )
{
- VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif );
ColorMapObject *map = gif->file->Image.ColorMap ?
gif->file->Image.ColorMap : gif->file->SColorMap;
@@ -355,8 +368,7 @@ vips_foreign_load_gif_render_line( VipsForeignLoadGif *gif,
VipsPel v = p[x];
if( v >= map->ColorCount ) {
- vips_warn( class->nickname,
- "%s", _( "pixel value out of range" ) );
+ g_warning( "%s", _( "pixel value out of range" ) );
continue;
}
@@ -414,6 +426,13 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif, VipsImage *out )
}
}
+ /* We need a line buffer to decompress to.
+ */
+ if( !gif->line )
+ if( !(gif->line = VIPS_ARRAY( gif,
+ gif->file->SWidth, GifPixelType )) )
+ return( -1 );
+
if( file->Image.Interlace ) {
int i;
@@ -468,38 +487,17 @@ vips_foreign_load_gif_render( VipsForeignLoadGif *gif, VipsImage *out )
return( 0 );
}
+/* Write the next page, if there is one, to @page. Set EOF if we hit the end of
+ * the file. @page must be a memory image of the right size.
+ */
static int
-vips_foreign_load_gif_to_memory( VipsForeignLoadGif *gif, VipsImage *out )
+vips_foreign_load_gif_page( VipsForeignLoadGif *gif, VipsImage *out )
{
- VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif );
-
- int frame_n;
GifRecordType record;
+ int n_pages;
- vips_image_init_fields( out,
- gif->file->SWidth, gif->file->SHeight,
- 4, VIPS_FORMAT_UCHAR,
- VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, 1.0, 1.0 );
+ n_pages = 0;
- /* We will have the whole GIF frame in memory, so we can render any
- * area.
- */
- vips_image_pipelinev( out, VIPS_DEMAND_STYLE_ANY, NULL );
-
- /* We need a line buffer to decompress to.
- */
- gif->line = VIPS_ARRAY( gif, gif->file->SWidth, GifPixelType );
-
- /* Turn out into a memory image which we then render the GIF frames
- * into.
- */
- if( vips_image_write_prepare( out ) )
- return( -1 );
-
- /* Scan the GIF until we have enough to have completely rendered the
- * frame we need.
- */
- frame_n = 0;
do {
GifByteType *extension;
int ext_code;
@@ -521,9 +519,10 @@ vips_foreign_load_gif_to_memory( VipsForeignLoadGif *gif, VipsImage *out )
if( vips_foreign_load_gif_render( gif, out ) )
return( -1 );
- frame_n += 1;
+ n_pages += 1;
- VIPS_DEBUG_MSG( "gifload: start frame %d:\n", frame_n );
+ VIPS_DEBUG_MSG( "gifload: page %d:\n",
+ gif->current_page + n_pages );
break;
@@ -533,7 +532,7 @@ vips_foreign_load_gif_to_memory( VipsForeignLoadGif *gif, VipsImage *out )
gif->transparency = -1;
if( DGifGetExtension( gif->file,
- &ext_code, &extension) == GIF_ERROR ) {
+ &ext_code, &extension ) == GIF_ERROR ) {
vips_foreign_load_gif_error( gif );
return( -1 );
}
@@ -572,6 +571,7 @@ vips_foreign_load_gif_to_memory( VipsForeignLoadGif *gif, VipsImage *out )
case TERMINATE_RECORD_TYPE:
VIPS_DEBUG_MSG( "gifload: TERMINATE_RECORD_TYPE:\n" );
+ gif->eof = TRUE;
break;
case SCREEN_DESC_RECORD_TYPE:
@@ -585,20 +585,158 @@ vips_foreign_load_gif_to_memory( VipsForeignLoadGif *gif, VipsImage *out )
default:
break;
}
- } while( frame_n <= gif->page &&
- record != TERMINATE_RECORD_TYPE );
+ } while( n_pages < 1 &&
+ !gif->eof );
- if( frame_n <= gif->page ) {
+ gif->current_page += n_pages;
+
+ return( 0 );
+}
+
+static VipsImage *
+vips_foreign_load_gif_new_page( VipsForeignLoadGif *gif )
+{
+ VipsImage *out;
+
+ out = vips_image_new_memory();
+
+ vips_image_init_fields( out,
+ gif->file->SWidth, gif->file->SHeight, 4, VIPS_FORMAT_UCHAR,
+ VIPS_CODING_NONE, VIPS_INTERPRETATION_sRGB, 1.0, 1.0 );
+
+ /* We will have the whole GIF frame in memory, so we can render any
+ * area.
+ */
+ vips_image_pipelinev( out, VIPS_DEMAND_STYLE_ANY, NULL );
+
+ /* Turn out into a memory image which we then render the GIF frames
+ * into.
+ */
+ if( vips_image_write_prepare( out ) ) {
+ g_object_unref( out );
+ return( NULL );
+ }
+
+ return( out );
+}
+
+static void
+unref_array( GSList *list )
+{
+ g_slist_free_full( list, (GDestroyNotify) g_object_unref );
+}
+
+/* We render each frame to a separate memory image held in a linked
+ * list, then assemble to out. We don't know the number of frames in advance,
+ * so we can't just allocate a large area.
+ */
+static int
+vips_foreign_load_gif_pages( VipsForeignLoadGif *gif, VipsImage **out )
+{
+ VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gif );
+
+ GSList *frames;
+ VipsImage *frame;
+ VipsImage *previous;
+ VipsImage **t;
+ int n_frames;
+ int i;
+
+ frames = NULL;
+ previous = NULL;
+
+ /* Accumulate any start stuff up to the first frame we need.
+ */
+ if( !(frame = vips_foreign_load_gif_new_page( gif )) )
+ return( -1 );
+ do {
+ if( vips_foreign_load_gif_page( gif, frame ) ) {
+ g_object_unref( frame );
+ return( -1 );
+ }
+ } while( !gif->eof &&
+ gif->current_page <= gif->page );
+
+ if( gif->eof ) {
+ vips_error( class->nickname,
+ "%s", _( "too few frames in GIF file" ) );
+ g_object_unref( frame );
+ return( -1 );
+ }
+
+ frames = g_slist_append( frames, frame );
+ previous = frame;
+
+ while( gif->n == -1 ||
+ gif->current_page < gif->page + gif->n ) {
+ /* We might need a frame for this read to render to.
+ */
+ if( !(frame = vips_foreign_load_gif_new_page( gif )) ) {
+ unref_array( frames );
+ return( -1 );
+ }
+
+ /* And init with the previous frame, if any.
+ */
+ if( previous )
+ memcpy( VIPS_IMAGE_ADDR( frame, 0, 0 ),
+ VIPS_IMAGE_ADDR( previous, 0, 0 ),
+ VIPS_IMAGE_SIZEOF_IMAGE( frame ) );
+
+ if( vips_foreign_load_gif_page( gif, frame ) ) {
+ g_object_unref( frame );
+ unref_array( frames );
+ return( -1 );
+ }
+
+ if( gif->eof ) {
+ /* Nope, didn't need the new frame.
+ */
+ g_object_unref( frame );
+ break;
+ }
+ else {
+ frames = g_slist_append( frames, frame );
+ previous = frame;
+ }
+ }
+
+ n_frames = g_slist_length( frames );
+
+ if( gif->eof &&
+ gif->n != -1 &&
+ n_frames < gif->n ) {
+ unref_array( frames );
vips_error( class->nickname,
"%s", _( "too few frames in GIF file" ) );
return( -1 );
}
- /* We've rendered to a memory image ... we can shut down the GIF
+ /* We've rendered to a set of memory images ... we can shut down the GIF
* reader now.
*/
vips_foreign_load_gif_close( gif );
+ if( !(t = VIPS_ARRAY( gif, n_frames, VipsImage * )) ) {
+ unref_array( frames );
+ return( -1 );
+ }
+
+ for( i = 0; i < n_frames; i++ )
+ t[i] = (VipsImage *) g_slist_nth_data( frames, i );
+
+ if( vips_arrayjoin( t, out, n_frames,
+ "across", 1,
+ NULL ) ) {
+ unref_array( frames );
+ return( -1 );
+ }
+
+ unref_array( frames );
+
+ if( n_frames > 1 )
+ vips_image_set_int( *out, VIPS_META_PAGE_HEIGHT, frame->Ysize );
+
return( 0 );
}
@@ -611,11 +749,9 @@ vips_foreign_load_gif_load( VipsForeignLoad *load )
VipsImage *im;
- /* Render to a memory image.
- */
- im = t[0] = vips_image_new_memory();
- if( vips_foreign_load_gif_to_memory( gif, im ) )
+ if( vips_foreign_load_gif_pages( gif, &t[0] ) )
return( -1 );
+ im = t[0];
/* Depending on what we found, transform and write to load->real.
*/
@@ -683,11 +819,19 @@ vips_foreign_load_gif_class_init( VipsForeignLoadGifClass *class )
G_STRUCT_OFFSET( VipsForeignLoadGif, page ),
0, 100000, 0 );
+ VIPS_ARG_INT( class, "n", 6,
+ _( "n" ),
+ _( "Load this many pages" ),
+ VIPS_ARGUMENT_OPTIONAL_INPUT,
+ G_STRUCT_OFFSET( VipsForeignLoadGif, n ),
+ -1, 100000, 1 );
+
}
static void
vips_foreign_load_gif_init( VipsForeignLoadGif *gif )
{
+ gif->n = 1;
gif->transparency = -1;
}
@@ -854,10 +998,16 @@ vips_foreign_load_gif_buffer_init( VipsForeignLoadGifBuffer *buffer )
* Optional arguments:
*
* * @page: %gint, page (frame) to read
+ * * @n: %gint, load this many pages
*
* Read a GIF file into a VIPS image. Rendering uses the giflib library.
*
- * Use @page to set page number (frame number) to read.
+ * Use @page to select a page to render, numbering from zero.
+ *
+ * Use @n to select the number of pages to render. The default is 1. Pages are
+ * rendered in a vertical column, with each individual page aligned to the
+ * left. Set to -1 to mean "until the end of the document". Use vips_grid()
+ * to change page layout.
*
* The whole GIF is rendered into memory on header access. The output image
* will be 1, 2, 3 or 4 bands depending on what the reader finds in the file.
@@ -889,6 +1039,7 @@ vips_gifload( const char *filename, VipsImage **out, ... )
* Optional arguments:
*
* * @page: %gint, page (frame) to read
+ * * @n: %gint, load this many pages
*
* Read a GIF-formatted memory block into a VIPS image. Exactly as
* vips_gifload(), but read from a memory buffer.
diff --git a/libvips/foreign/jpeg2vips.c b/libvips/foreign/jpeg2vips.c
index 17a9f9b8..94fe8480 100644
--- a/libvips/foreign/jpeg2vips.c
+++ b/libvips/foreign/jpeg2vips.c
@@ -79,8 +79,13 @@
* 07/09/16
* - Don't use the exif resolution if x_resolution / y_resolution /
* resolution_unit is missing
+<<<<<<< HEAD
* 7/11/16
* - exif handling moved out to exif.c
+=======
+ * 4/1/17
+ * - Don't warn for missing exif res, since we fall back to jfif now
+>>>>>>> master
*/
/*
@@ -180,10 +185,9 @@ readjpeg_free( ReadJpeg *jpeg )
result = 0;
if( jpeg->eman.pub.num_warnings != 0 ) {
- vips_warn( "VipsJpeg",
- _( "read gave %ld warnings" ),
+ g_warning( _( "read gave %ld warnings" ),
jpeg->eman.pub.num_warnings );
- vips_warn( NULL, "%s", vips_error_buffer() );
+ g_warning( "%s", vips_error_buffer() );
/* Make the message only appear once.
*/
@@ -376,8 +380,7 @@ read_jpeg_header( ReadJpeg *jpeg, VipsImage *out )
break;
default:
- vips_warn( "VipsJpeg",
- "%s", _( "unknown JFIF resolution unit" ) );
+ g_warning( "%s", _( "unknown JFIF resolution unit" ) );
break;
}
diff --git a/libvips/foreign/jpegload.c b/libvips/foreign/jpegload.c
index 186fd28f..9e53914e 100644
--- a/libvips/foreign/jpegload.c
+++ b/libvips/foreign/jpegload.c
@@ -77,10 +77,6 @@ typedef struct _VipsForeignLoadJpeg {
*/
int shrink;
- /* Fail on first warning.
- */
- gboolean fail;
-
/* Autorotate using exif orientation tag.
*/
gboolean autorotate;
@@ -144,13 +140,6 @@ vips_foreign_load_jpeg_class_init( VipsForeignLoadJpegClass *class )
G_STRUCT_OFFSET( VipsForeignLoadJpeg, shrink ),
1, 16, 1 );
- VIPS_ARG_BOOL( class, "fail", 11,
- _( "Fail" ),
- _( "Fail on first warning" ),
- VIPS_ARGUMENT_OPTIONAL_INPUT,
- G_STRUCT_OFFSET( VipsForeignLoadJpeg, fail ),
- FALSE );
-
VIPS_ARG_BOOL( class, "autorotate", 12,
_( "Autorotate" ),
_( "Rotate image using exif orientation" ),
@@ -201,7 +190,7 @@ vips_foreign_load_jpeg_file_header( VipsForeignLoad *load )
VipsForeignLoadJpegFile *file = (VipsForeignLoadJpegFile *) load;
if( vips__jpeg_read_file( file->filename, load->out,
- TRUE, jpeg->shrink, jpeg->fail, FALSE, jpeg->autorotate ) )
+ TRUE, jpeg->shrink, load->fail, FALSE, jpeg->autorotate ) )
return( -1 );
return( 0 );
@@ -214,7 +203,7 @@ vips_foreign_load_jpeg_file_load( VipsForeignLoad *load )
VipsForeignLoadJpegFile *file = (VipsForeignLoadJpegFile *) load;
if( vips__jpeg_read_file( file->filename, load->real,
- FALSE, jpeg->shrink, jpeg->fail,
+ FALSE, jpeg->shrink, load->fail,
load->access == VIPS_ACCESS_SEQUENTIAL, jpeg->autorotate ) )
return( -1 );
@@ -283,7 +272,7 @@ vips_foreign_load_jpeg_buffer_header( VipsForeignLoad *load )
VipsForeignLoadJpegBuffer *buffer = (VipsForeignLoadJpegBuffer *) load;
if( vips__jpeg_read_buffer( buffer->buf->data, buffer->buf->length,
- load->out, TRUE, jpeg->shrink, jpeg->fail, FALSE,
+ load->out, TRUE, jpeg->shrink, load->fail, FALSE,
jpeg->autorotate ) )
return( -1 );
@@ -297,7 +286,7 @@ vips_foreign_load_jpeg_buffer_load( VipsForeignLoad *load )
VipsForeignLoadJpegBuffer *buffer = (VipsForeignLoadJpegBuffer *) load;
if( vips__jpeg_read_buffer( buffer->buf->data, buffer->buf->length,
- load->real, FALSE, jpeg->shrink, jpeg->fail,
+ load->real, FALSE, jpeg->shrink, load->fail,
load->access == VIPS_ACCESS_SEQUENTIAL, jpeg->autorotate ) )
return( -1 );
diff --git a/libvips/foreign/magick2vips.c b/libvips/foreign/magick2vips.c
index 98730280..c9416819 100644
--- a/libvips/foreign/magick2vips.c
+++ b/libvips/foreign/magick2vips.c
@@ -51,6 +51,8 @@
* - add @page option, 0 by default
* 18/4/16
* - fix @page with graphicsmagick
+ * 25/11/16
+ * - remove @all_frames, add @n
*/
/*
@@ -119,8 +121,8 @@
typedef struct _Read {
char *filename;
VipsImage *im;
- gboolean all_frames;
int page;
+ int n;
Image *image;
ImageInfo *image_info;
@@ -166,7 +168,7 @@ read_close( VipsImage *im, Read *read )
static Read *
read_new( const char *filename, VipsImage *im,
- gboolean all_frames, const char *density, int page )
+ const char *density, int page, int n )
{
Read *read;
static int inited = 0;
@@ -180,11 +182,17 @@ read_new( const char *filename, VipsImage *im,
inited = 1;
}
+ /* IM doesn't use the -1 means end-of-file convention, change it to a
+ * very large number.
+ */
+ if( n == -1 )
+ n = 100000;
+
if( !(read = VIPS_NEW( im, Read )) )
return( NULL );
read->filename = filename ? g_strdup( filename ) : NULL;
- read->all_frames = all_frames;
read->page = page;
+ read->n = n;
read->im = im;
read->image = NULL;
read->image_info = CloneImageInfo( NULL );
@@ -218,24 +226,25 @@ read_new( const char *filename, VipsImage *im,
SetImageOption( read->image_info, "dcm:display-range", "reset" );
#endif /*HAVE_SETIMAGEOPTION*/
- if( !all_frames ) {
+ if( read->page > 0 ) {
#ifdef HAVE_NUMBER_SCENES
- /* I can't find docs for these fields, but this seems to work.
- */
+ /* I can't find docs for these fields, but this seems to work.
+ */
char page[256];
read->image_info->scene = read->page;
- read->image_info->number_scenes = 1;
+ read->image_info->number_scenes = read->n;
/* Some IMs must have the string version set as well.
*/
- vips_snprintf( page, 256, "%d", read->page );
+ vips_snprintf( page, 256, "%d-%d",
+ read->page, read->page + read->n );
read->image_info->scenes = strdup( page );
#else /*!HAVE_NUMBER_SCENES*/
/* This works with GM 1.2.31 and probably others.
*/
read->image_info->subimage = read->page;
- read->image_info->subrange = 1;
+ read->image_info->subrange = read->n;
#endif
}
@@ -465,8 +474,17 @@ parse_header( Read *read )
for( p = image; p; (p = GetNextImageInList( p )) ) {
if( p->columns != (unsigned int) im->Xsize ||
p->rows != (unsigned int) im->Ysize ||
- get_bands( p ) != im->Bands )
+ get_bands( p ) != im->Bands ) {
+#ifdef DEBUG
+ printf( "frame %d differs\n", read->n_frames );
+ printf( "%zdx%zd, %d bands\n",
+ p->columns, p->rows, get_bands( p ) );
+ printf( "first frame is %dx%d, %d bands\n",
+ im->Xsize, im->Ysize, im->Bands );
+#endif /*DEBUG*/
+
break;
+ }
read->n_frames += 1;
}
@@ -479,14 +497,11 @@ parse_header( Read *read )
printf( "image has %d frames\n", read->n_frames );
#endif /*DEBUG*/
- /* If all_frames is off, just get the first one.
- */
- if( !read->all_frames )
- read->n_frames = 1;
+ if( read->n != -1 )
+ read->n_frames = VIPS_MIN( read->n_frames, read->n );
/* Record frame pointers.
*/
- im->Ysize *= read->n_frames;
if( !(read->frames = VIPS_ARRAY( NULL, read->n_frames, Image * )) )
return( -1 );
p = image;
@@ -495,6 +510,11 @@ parse_header( Read *read )
p = GetNextImageInList( p );
}
+ if( read->n_frames > 1 ) {
+ vips_image_set_int( im, VIPS_META_PAGE_HEIGHT, im->Ysize );
+ im->Ysize *= read->n_frames;
+ }
+
return( 0 );
}
@@ -711,8 +731,8 @@ magick_fill_region( VipsRegion *out,
}
int
-vips__magick_read( const char *filename, VipsImage *out,
- gboolean all_frames, const char *density, int page )
+vips__magick_read( const char *filename,
+ VipsImage *out, const char *density, int page, int n )
{
Read *read;
@@ -720,7 +740,7 @@ vips__magick_read( const char *filename, VipsImage *out,
printf( "magick2vips: vips__magick_read: %s\n", filename );
#endif /*DEBUG*/
- if( !(read = read_new( filename, out, all_frames, density, page )) )
+ if( !(read = read_new( filename, out, density, page, n )) )
return( -1 );
#ifdef DEBUG
@@ -750,8 +770,8 @@ vips__magick_read( const char *filename, VipsImage *out,
* http://www.imagemagick.org/discourse-server/viewtopic.php?f=1&t=20017
*/
int
-vips__magick_read_header( const char *filename, VipsImage *im,
- gboolean all_frames, const char *density, int page )
+vips__magick_read_header( const char *filename,
+ VipsImage *out, const char *density, int page, int n )
{
Read *read;
@@ -759,7 +779,7 @@ vips__magick_read_header( const char *filename, VipsImage *im,
printf( "vips__magick_read_header: %s\n", filename );
#endif /*DEBUG*/
- if( !(read = read_new( filename, im, all_frames, density, page )) )
+ if( !(read = read_new( filename, out, density, page, n )) )
return( -1 );
#ifdef DEBUG
@@ -778,7 +798,8 @@ vips__magick_read_header( const char *filename, VipsImage *im,
if( parse_header( read ) )
return( -1 );
- if( im->Xsize <= 0 || im->Ysize <= 0 ) {
+ if( out->Xsize <= 0 ||
+ out->Ysize <= 0 ) {
vips_error( "magick2vips", "%s", _( "bad image size" ) );
return( -1 );
}
@@ -791,8 +812,8 @@ vips__magick_read_header( const char *filename, VipsImage *im,
}
int
-vips__magick_read_buffer( const void *buf, const size_t len, VipsImage *out,
- gboolean all_frames, const char *density, int page )
+vips__magick_read_buffer( const void *buf, const size_t len,
+ VipsImage *out, const char *density, int page, int n )
{
Read *read;
@@ -800,7 +821,7 @@ vips__magick_read_buffer( const void *buf, const size_t len, VipsImage *out,
printf( "magick2vips: vips__magick_read_buffer: %p %zu\n", buf, len );
#endif /*DEBUG*/
- if( !(read = read_new( NULL, out, all_frames, density, page )) )
+ if( !(read = read_new( NULL, out, density, page, n )) )
return( -1 );
#ifdef DEBUG
@@ -827,8 +848,7 @@ vips__magick_read_buffer( const void *buf, const size_t len, VipsImage *out,
int
vips__magick_read_buffer_header( const void *buf, const size_t len,
- VipsImage *im,
- gboolean all_frames, const char *density, int page )
+ VipsImage *out, const char *density, int page, int n )
{
Read *read;
@@ -836,7 +856,7 @@ vips__magick_read_buffer_header( const void *buf, const size_t len,
printf( "vips__magick_read_buffer_header: %p %zu\n", buf, len );
#endif /*DEBUG*/
- if( !(read = read_new( NULL, im, all_frames, density, page )) )
+ if( !(read = read_new( NULL, out, density, page, n )) )
return( -1 );
#ifdef DEBUG
@@ -854,8 +874,8 @@ vips__magick_read_buffer_header( const void *buf, const size_t len,
if( parse_header( read ) )
return( -1 );
- if( im->Xsize <= 0 ||
- im->Ysize <= 0 ) {
+ if( out->Xsize <= 0 ||
+ out->Ysize <= 0 ) {
vips_error( "magick2vips", "%s", _( "bad image size" ) );
return( -1 );
}
diff --git a/libvips/foreign/magick7load.c b/libvips/foreign/magick7load.c
index c654f16c..1d483483 100644
--- a/libvips/foreign/magick7load.c
+++ b/libvips/foreign/magick7load.c
@@ -2,6 +2,8 @@
*
* 8/7/16
* - from magickload
+ * 25/11/16
+ * - add @n, deprecate @all_frames (just sets n = -1)
*/
/*
@@ -55,9 +57,13 @@
typedef struct _VipsForeignLoadMagick7 {
VipsForeignLoad parent_object;
- gboolean all_frames; /* Load all frames */
+ /* Deprecated. Just sets n = -1.
+ */
+ gboolean all_frames;
+
char *density; /* Load at this resolution */
int page; /* Load this page (frame) */
+ int n; /* Load this many pages */
Image *image;
ImageInfo *image_info;
@@ -308,6 +314,9 @@ vips_foreign_load_magick7_build( VipsObject *object )
if( !magick7->image_info )
return( -1 );
+ if( magick7->all_frames )
+ magick7->n = -1;
+
/* Canvas resolution for rendering vector formats like SVG.
*/
VIPS_SETSTR( magick7->image_info->density, magick7->density );
@@ -321,15 +330,16 @@ vips_foreign_load_magick7_build( VipsObject *object )
*/
SetImageOption( magick7->image_info, "dcm:display-range", "reset" );
- if( !magick7->all_frames ) {
+ if( magick7->page > 0 ) {
/* I can't find docs for these fields, but this seems to work.
*/
char page[256];
magick7->image_info->scene = magick7->page;
- magick7->image_info->number_scenes = 1;
+ magick7->image_info->number_scenes = magick7->n;
- vips_snprintf( page, 256, "%d", magick7->page );
+ vips_snprintf( page, 256, "%d-%d",
+ magick7->page, magick7->page + magick7->n );
magick7->image_info->scenes = strdup( page );
}
@@ -368,7 +378,7 @@ vips_foreign_load_magick7_class_init( VipsForeignLoadMagick7Class *class )
VIPS_ARG_BOOL( class, "all_frames", 3,
_( "all_frames" ),
_( "Read all frames from an image" ),
- VIPS_ARGUMENT_OPTIONAL_INPUT,
+ VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
G_STRUCT_OFFSET( VipsForeignLoadMagick7, all_frames ),
FALSE );
@@ -385,11 +395,20 @@ vips_foreign_load_magick7_class_init( VipsForeignLoadMagick7Class *class )
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsForeignLoadMagick7, page ),
0, 100000, 0 );
+
+ VIPS_ARG_INT( class, "n", 6,
+ _( "n" ),
+ _( "Load this many pages" ),
+ VIPS_ARGUMENT_OPTIONAL_INPUT,
+ G_STRUCT_OFFSET( VipsForeignLoadMagick7, n ),
+ -1, 100000, 1 );
+
}
static void
vips_foreign_load_magick7_init( VipsForeignLoadMagick7 *magick7 )
{
+ magick7->n = 1;
}
static void
@@ -545,14 +564,15 @@ vips_foreign_load_magick7_parse( VipsForeignLoadMagick7 *magick7,
printf( "image has %d frames\n", magick7->n_frames );
#endif /*DEBUG*/
- /* If all_frames is off, just get the first one.
- */
- if( !magick7->all_frames )
- magick7->n_frames = 1;
+ if( magick7->n != -1 )
+ magick7->n_frames = VIPS_MIN( magick7->n_frames, magick7->n );
/* So we can finally set the height.
*/
- out->Ysize *= magick7->n_frames;
+ if( magick7->n_frames > 1 ) {
+ vips_image_set_int( out, VIPS_META_PAGE_HEIGHT, out->Ysize );
+ out->Ysize *= magick7->n_frames;
+ }
return( 0 );
}
diff --git a/libvips/foreign/magickload.c b/libvips/foreign/magickload.c
index 9b5eb0db..7cbc1518 100644
--- a/libvips/foreign/magickload.c
+++ b/libvips/foreign/magickload.c
@@ -8,6 +8,8 @@
* - add @all_frames option, off by default
* 14/2/16
* - add @page option, 0 by default
+ * 25/11/16
+ * - add @n, deprecate @all_frames (just sets n = -1)
*/
/*
@@ -61,9 +63,13 @@
typedef struct _VipsForeignLoadMagick {
VipsForeignLoad parent_object;
- gboolean all_frames; /* Load all frames */
+ /* Deprecated. Just sets n = -1.
+ */
+ gboolean all_frames;
+
char *density; /* Load at this resolution */
int page; /* Load this page (frame) */
+ int n; /* Load this many pages */
} VipsForeignLoadMagick;
@@ -110,7 +116,7 @@ vips_foreign_load_magick_class_init( VipsForeignLoadMagickClass *class )
VIPS_ARG_BOOL( class, "all_frames", 3,
_( "all_frames" ),
_( "Read all frames from an image" ),
- VIPS_ARGUMENT_OPTIONAL_INPUT,
+ VIPS_ARGUMENT_OPTIONAL_INPUT | VIPS_ARGUMENT_DEPRECATED,
G_STRUCT_OFFSET( VipsForeignLoadMagick, all_frames ),
FALSE );
@@ -127,11 +133,19 @@ vips_foreign_load_magick_class_init( VipsForeignLoadMagickClass *class )
VIPS_ARGUMENT_OPTIONAL_INPUT,
G_STRUCT_OFFSET( VipsForeignLoadMagick, page ),
0, 100000, 0 );
+
+ VIPS_ARG_INT( class, "n", 6,
+ _( "n" ),
+ _( "Load this many pages" ),
+ VIPS_ARGUMENT_OPTIONAL_INPUT,
+ G_STRUCT_OFFSET( VipsForeignLoadMagick, n ),
+ -1, 100000, 1 );
}
static void
vips_foreign_load_magick_init( VipsForeignLoadMagick *magick )
{
+ magick->n = 1;
}
typedef struct _VipsForeignLoadMagickFile {
@@ -154,7 +168,7 @@ ismagick( const char *filename )
t = vips_image_new();
vips_error_freeze();
- result = vips__magick_read_header( filename, t, FALSE, NULL, 0 );
+ result = vips__magick_read_header( filename, t, NULL, 0, 1 );
g_object_unref( t );
vips_error_thaw();
@@ -175,8 +189,11 @@ vips_foreign_load_magick_file_header( VipsForeignLoad *load )
VipsForeignLoadMagickFile *magick_file =
(VipsForeignLoadMagickFile *) load;
+ if( magick->all_frames )
+ magick->n = -1;
+
if( vips__magick_read( magick_file->filename,
- load->out, magick->all_frames, magick->density, magick->page ) )
+ load->out, magick->density, magick->page, magick->n ) )
return( -1 );
VIPS_SETSTR( load->out->filename, magick_file->filename );
@@ -236,7 +253,7 @@ vips_foreign_load_magick_buffer_is_a_buffer( const void *buf, size_t len )
t = vips_image_new();
vips_error_freeze();
- result = vips__magick_read_buffer_header( buf, len, t, FALSE, NULL, 0 );
+ result = vips__magick_read_buffer_header( buf, len, t, NULL, 0, 1 );
g_object_unref( t );
vips_error_thaw();
@@ -257,9 +274,12 @@ vips_foreign_load_magick_buffer_header( VipsForeignLoad *load )
VipsForeignLoadMagickBuffer *magick_buffer =
(VipsForeignLoadMagickBuffer *) load;
+ if( magick->all_frames )
+ magick->n = -1;
+
if( vips__magick_read_buffer(
magick_buffer->buf->data, magick_buffer->buf->length,
- load->out, magick->all_frames, magick->density, magick->page ) )
+ load->out, magick->density, magick->page, magick->n ) )
return( -1 );
return( 0 );
@@ -307,7 +327,8 @@ vips_foreign_load_magick_buffer_init( VipsForeignLoadMagickBuffer *buffer )
*
* Optional arguments:
*
- * * @all_frames: %gboolean, load all frames in sequence
+ * * @page: %gint, load from this page
+ * * @n: %gint, load this many pages
* * @density: string, canvas resolution for rendering vector formats like SVG
*
* Read in an image using libMagick, the ImageMagick library. This library can
@@ -321,7 +342,8 @@ vips_foreign_load_magick_buffer_init( VipsForeignLoadMagickBuffer *buffer )
* "--with-magickpackage" configure option.
*
* Normally it will only load the first image in a many-image sequence (such
- * as a GIF). Set @all_frames to true to read the whole image sequence.
+ * as a GIF or a PDF). Use @page and @n to set the start page and number of
+ * pages to load. Set @n to -1 to load all pages from @page onwards.
*
* @density is "WxH" in DPI, e.g. "600x300" or "600" (default is "72x72"). See
* the [density
@@ -354,7 +376,8 @@ vips_magickload( const char *filename, VipsImage **out, ... )
*
* Optional arguments:
*
- * * @all_frames: %gboolean, load all frames in sequence
+ * * @page: %gint, load from this page
+ * * @n: %gint, load this many pages
* * @density: string, canvas resolution for rendering vector formats like SVG
*
* Read an image memory block using libMagick into a VIPS image. Exactly as
diff --git a/libvips/foreign/openslide2vips.c b/libvips/foreign/openslide2vips.c
index bd94a196..ed48e79c 100644
--- a/libvips/foreign/openslide2vips.c
+++ b/libvips/foreign/openslide2vips.c
@@ -47,8 +47,6 @@
* - do argb -> rgba for associated as well
* 27/1/15
* - unpremultiplication speedups for fully opaque/transparent pixels
- * 11/7/16
- * - just warn on tile read error
*/
/*
@@ -473,15 +471,19 @@ vips__openslide_generate( VipsRegion *out,
rslide->level,
r->width, r->height );
- /* Only warn on error: we don't want to make the whole image unreadable
- * because of one broken tile.
+ /* openslide errors are terminal. To support
+ * @fail we'd have to close the openslide_t and reopen, perhaps
+ * somehow marking this tile as unreadable.
*
- * FIXME ... add a --fail option like jpegload
+ * See
+ * https://github.com/jcupitt/libvips/commit/bb0a6643f94e69294e36d2b253f9bdd60c8c40ed#commitcomment-19838911
*/
error = openslide_get_error( rslide->osr );
- if( error )
- vips_warn( "openslide2vips",
+ if( error ) {
+ vips_error( "openslide2vips",
_( "reading region: %s" ), error );
+ return( -1 );
+ }
/* Since we are inside a cache, we know buf must be continuous.
*/
diff --git a/libvips/foreign/openslideload.c b/libvips/foreign/openslideload.c
index a02927fb..769373ca 100644
--- a/libvips/foreign/openslideload.c
+++ b/libvips/foreign/openslideload.c
@@ -133,9 +133,8 @@ vips_foreign_load_openslide_load( VipsForeignLoad *load )
return( -1 );
}
else {
- if( vips__openslide_read_associated(
- openslide->filename, load->real,
- openslide->associated ) )
+ if( vips__openslide_read_associated( openslide->filename,
+ load->real, openslide->associated ) )
return( -1 );
}
diff --git a/libvips/foreign/pdfload.c b/libvips/foreign/pdfload.c
index 7ab3907a..a32591c2 100644
--- a/libvips/foreign/pdfload.c
+++ b/libvips/foreign/pdfload.c
@@ -4,6 +4,8 @@
* - from openslideload.c
* 12/5/16
* - add @n ... number of pages to load
+ * 23/11/16
+ * - set page-height, if we can
*/
/*
@@ -309,6 +311,17 @@ vips_foreign_load_pdf_header( VipsForeignLoad *load )
top += pdf->pages[i].height;
}
+ /* If all pages are the same size, we can tag this as a toilet roll
+ * image and tiffsave will be able to save it as a multipage tiff.
+ */
+ for( i = 1; i < pdf->n; i++ )
+ if( pdf->pages[i].width != pdf->pages[0].width ||
+ pdf->pages[i].height != pdf->pages[0].height )
+ break;
+ if( i == pdf->n )
+ vips_image_set_int( load->out,
+ VIPS_META_PAGE_HEIGHT, pdf->pages[0].height );
+
vips_foreign_load_pdf_set_image( pdf, load->out );
return( 0 );
diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h
index 78fbf480..4dfe0380 100644
--- a/libvips/foreign/pforeign.h
+++ b/libvips/foreign/pforeign.h
@@ -66,17 +66,17 @@ int vips__tiff_write_buf( VipsImage *in,
gboolean properties, gboolean strip );
int vips__tiff_read_header( const char *filename, VipsImage *out,
- int page, gboolean autorotate );
+ int page, int n, gboolean autorotate );
int vips__tiff_read( const char *filename, VipsImage *out,
- int page, gboolean autorotate, gboolean readbehind );
+ int page, int n, gboolean autorotate, gboolean readbehind );
gboolean vips__istifftiled( const char *filename );
gboolean vips__istiff_buffer( const void *buf, size_t len );
gboolean vips__istiff( const char *filename );
int vips__tiff_read_header_buffer( const void *buf, size_t len, VipsImage *out,
- int page, gboolean autorotate );
+ int page, int n, gboolean autorotate );
int vips__tiff_read_buffer( const void *buf, size_t len, VipsImage *out,
- int page, gboolean autorotate, gboolean readbehind );
+ int page, int n, gboolean autorotate, gboolean readbehind );
extern const char *vips__foreign_tiff_suffs[];
@@ -87,9 +87,11 @@ int vips__analyze_read( const char *filename, VipsImage *out );
extern const char *vips__foreign_csv_suffs[];
int vips__csv_read( const char *filename, VipsImage *out,
- int skip, int lines, const char *whitespace, const char *separator );
+ int skip, int lines, const char *whitespace, const char *separator,
+ gboolean fail );
int vips__csv_read_header( const char *filename, VipsImage *out,
- int skip, int lines, const char *whitespace, const char *separator );
+ int skip, int lines, const char *whitespace, const char *separator,
+ gboolean fail );
int vips__csv_write( VipsImage *in, const char *filename,
const char *separator );
@@ -118,14 +120,14 @@ int vips__fits_read( const char *filename, VipsImage *out );
int vips__fits_write( VipsImage *in, const char *filename );
int vips__magick_read( const char *filename,
- VipsImage *out, gboolean all_frames, const char *density, int page );
+ VipsImage *out, const char *density, int page, int n );
int vips__magick_read_header( const char *filename,
- VipsImage *out, gboolean all_frames, const char *density, int page );
+ VipsImage *out, const char *density, int page, int n );
int vips__magick_read_buffer( const void *buf, const size_t len,
- VipsImage *out, gboolean all_frames, const char *density, int page );
+ VipsImage *out, const char *density, int page, int n );
int vips__magick_read_buffer_header( const void *buf, const size_t len,
- VipsImage *out, gboolean all_frames, const char *density, int page );
+ VipsImage *out, const char *density, int page, int n );
extern const char *vips__mat_suffs[];
diff --git a/libvips/foreign/ppm.c b/libvips/foreign/ppm.c
index 22315526..9a7677f5 100644
--- a/libvips/foreign/ppm.c
+++ b/libvips/foreign/ppm.c
@@ -799,9 +799,8 @@ vips__ppm_save( VipsImage *in, const char *filename,
if( ascii &&
in->BandFmt == VIPS_FORMAT_FLOAT ) {
- vips_warn( "vips2ppm",
- "%s", _( "float images must be binary -- "
- "disabling ascii" ) );
+ g_warning( "%s",
+ _( "float images must be binary -- disabling ascii" ) );
ascii = FALSE;
}
@@ -810,8 +809,8 @@ vips__ppm_save( VipsImage *in, const char *filename,
if( squash &&
(in->Bands != 1 ||
in->BandFmt != VIPS_FORMAT_UCHAR) ) {
- vips_warn( "vips2ppm",
- "%s", _( "can only squash 1 band uchar images -- "
+ g_warning( "%s",
+ _( "can only squash 1 band uchar images -- "
"disabling squash" ) );
squash = FALSE;
}
diff --git a/libvips/foreign/tiff.c b/libvips/foreign/tiff.c
index ba82f4ee..dd9bdcd2 100644
--- a/libvips/foreign/tiff.c
+++ b/libvips/foreign/tiff.c
@@ -58,8 +58,7 @@
#include "tiff.h"
/* Handle TIFF errors here. Shared with vips2tiff.c. These can be called from
- * more than one thread, but vips_error and vips_warn have mutexes in, so that's
- * OK.
+ * more than one thread.
*/
static void
vips__thandler_error( const char *module, const char *fmt, va_list ap )
@@ -67,13 +66,13 @@ vips__thandler_error( const char *module, const char *fmt, va_list ap )
vips_verror( module, fmt, ap );
}
+/* It'd be nice to be able to support the @fail option for the tiff loader, but
+ * there's no easy way to do this, since libtiff has a global warning handler.
+ */
static void
vips__thandler_warning( const char *module, const char *fmt, va_list ap )
{
- char buf[256];
-
- vips_vsnprintf( buf, 256, fmt, ap );
- vips_warn( module, "%s", buf );
+ g_logv( G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, fmt, ap );
}
/* Call this during startup. Other libraries may be using libtiff and we want
diff --git a/libvips/foreign/tiff2vips.c b/libvips/foreign/tiff2vips.c
index 4276d9d5..5fae35ce 100644
--- a/libvips/foreign/tiff2vips.c
+++ b/libvips/foreign/tiff2vips.c
@@ -167,6 +167,8 @@
* convention
* 26/5/16
* - add autorotate support
+ * 17/11/16
+ * - add multi-page read
*/
/*
@@ -218,20 +220,51 @@
#include "pforeign.h"
#include "tiff.h"
+/* What we read from the tiff dir to set our read strategy. For multipage
+ * read, we need to read and compare lots of these, so it needs to be broken
+ * out as a separate thing.
+ */
+typedef struct _RtiffHeader {
+ uint32 width;
+ uint32 height;
+ int samples_per_pixel;
+ int bits_per_sample;
+ int photometric_interpretation;
+ int sample_format;
+ gboolean separate;
+ int orientation;
+
+ /* Result of TIFFIsTiled().
+ */
+ gboolean tiled;
+
+ /* Fields for tiled images.
+ */
+ uint32 tile_width;
+ uint32 tile_height;
+
+ /* Fields for strip images.
+ */
+ uint32 rows_per_strip;
+ tsize_t strip_size;
+ int number_of_strips;
+} RtiffHeader;
+
/* Scanline-type process function.
*/
-struct _ReadTiff;
-typedef void (*scanline_process_fn)( struct _ReadTiff *,
+struct _Rtiff;
+typedef void (*scanline_process_fn)( struct _Rtiff *,
VipsPel *q, VipsPel *p, int n, void *client );
/* Stuff we track during a read.
*/
-typedef struct _ReadTiff {
+typedef struct _Rtiff {
/* Parameters.
*/
char *filename;
VipsImage *out;
int page;
+ int n;
gboolean autorotate;
gboolean readbehind;
@@ -239,6 +272,10 @@ typedef struct _ReadTiff {
*/
TIFF *tiff;
+ /* The current page we have set.
+ */
+ int current_page;
+
/* Process for this image type.
*/
scanline_process_fn sfn;
@@ -248,26 +285,10 @@ typedef struct _ReadTiff {
*/
gboolean memcpy;
- /* The current 'file pointer' for memory buffers.
+ /* Geometry as read from the TIFF header. This is read for the first
+ * page, and equal for all other pages.
*/
- size_t pos;
-
- /* Geometry.
- */
- uint32 twidth, theight; /* Tile size */
- uint32 rows_per_strip;
- tsize_t scanline_size;
- tsize_t strip_size;
- int number_of_strips;
- int samples_per_pixel;
- int bits_per_sample;
- int photometric_interpretation;
- int sample_format;
- int orientation;
-
- /* Turn on separate plane reading.
- */
- gboolean separate;
+ RtiffHeader header;
/* Hold a single strip or tile, possibly just an image plane.
*/
@@ -277,7 +298,7 @@ typedef struct _ReadTiff {
* strips or tiles interleaved.
*/
tdata_t contig_buf;
-} ReadTiff;
+} Rtiff;
/* Test for field exists.
*/
@@ -329,9 +350,153 @@ tfget16( TIFF *tif, ttag_t tag, int *out )
}
static int
-check_samples( ReadTiff *rtiff, int samples_per_pixel )
+get_resolution( TIFF *tiff, VipsImage *out )
{
- if( rtiff->samples_per_pixel != samples_per_pixel ) {
+ float x, y;
+ int ru;
+
+ if( TIFFGetFieldDefaulted( tiff, TIFFTAG_XRESOLUTION, &x ) &&
+ TIFFGetFieldDefaulted( tiff, TIFFTAG_YRESOLUTION, &y ) &&
+ tfget16( tiff, TIFFTAG_RESOLUTIONUNIT, &ru ) ) {
+ switch( ru ) {
+ case RESUNIT_NONE:
+ break;
+
+ case RESUNIT_INCH:
+ /* In pixels-per-inch ... convert to mm.
+ */
+ x /= 10.0 * 2.54;
+ y /= 10.0 * 2.54;
+ vips_image_set_string( out,
+ VIPS_META_RESOLUTION_UNIT, "in" );
+ break;
+
+ case RESUNIT_CENTIMETER:
+ /* In pixels-per-centimetre ... convert to mm.
+ */
+ x /= 10.0;
+ y /= 10.0;
+ vips_image_set_string( out,
+ VIPS_META_RESOLUTION_UNIT, "cm" );
+ break;
+
+ default:
+ vips_error( "tiff2vips",
+ "%s", _( "unknown resolution unit" ) );
+ return( -1 );
+ }
+ }
+ else {
+ g_warning( _( "no resolution information for "
+ "TIFF image \"%s\" -- defaulting to 1 pixel per mm" ),
+ TIFFFileName( tiff ) );
+ x = 1.0;
+ y = 1.0;
+ }
+
+ out->Xres = x;
+ out->Yres = y;
+
+ return( 0 );
+}
+
+static int
+get_sample_format( TIFF *tiff )
+{
+ int sample_format;
+ uint16 v;
+
+ sample_format = SAMPLEFORMAT_INT;
+
+ if( TIFFGetFieldDefaulted( tiff, TIFFTAG_SAMPLEFORMAT, &v ) ) {
+ /* Some images have this set to void, bizarre.
+ */
+ if( v == SAMPLEFORMAT_VOID )
+ v = SAMPLEFORMAT_UINT;
+
+ sample_format = v;
+ }
+
+ return( sample_format );
+}
+
+static int
+get_orientation( TIFF *tiff )
+{
+ int orientation;
+ uint16 v;
+
+ orientation = ORIENTATION_TOPLEFT;
+
+ if( TIFFGetFieldDefaulted( tiff, TIFFTAG_ORIENTATION, &v ) )
+ /* Can have mad values.
+ */
+ orientation = VIPS_CLIP( 1, v, 8 );
+
+ return( orientation );
+}
+
+static int
+strip_read( TIFF *tiff, int strip, tdata_t buf )
+{
+ tsize_t length;
+
+#ifdef DEBUG
+ printf( "strip_read: reading strip %d\n", strip );
+#endif /*DEBUG*/
+
+ length = TIFFReadEncodedStrip( tiff, strip, buf, (tsize_t) -1 );
+ if( length == -1 ) {
+ vips_error( "tiff2vips", "%s", _( "read error" ) );
+ return( -1 );
+ }
+
+ return( 0 );
+}
+
+static int
+rtiff_set_page( Rtiff *rtiff, int page )
+{
+ if( rtiff->current_page != page ) {
+#ifdef DEBUG
+ printf( "rtiff_set_page: selecting page %d\n", page );
+#endif /*DEBUG*/
+
+ if( !TIFFSetDirectory( rtiff->tiff, page ) ) {
+ vips_error( "tiff2vips",
+ _( "TIFF does not contain page %d" ), page );
+ return( -1 );
+ }
+
+ rtiff->current_page = page;
+ }
+
+ return( 0 );
+}
+
+static int
+rtiff_n_pages( Rtiff *rtiff )
+{
+ int n;
+
+ (void) TIFFSetDirectory( rtiff->tiff, 0 );
+
+ for( n = 1; TIFFReadDirectory( rtiff->tiff ); n++ )
+ ;
+
+ (void) TIFFSetDirectory( rtiff->tiff, rtiff->current_page );
+
+#ifdef DEBUG
+ printf( "rtiff_n_pages: found %d pages\n", n );
+#endif /*DEBUG*/
+
+ return( n );
+}
+
+static int
+rtiff_check_samples( Rtiff *rtiff, int samples_per_pixel )
+{
+ if( rtiff->header.samples_per_pixel != samples_per_pixel ) {
vips_error( "tiff2vips",
_( "not %d bands" ), samples_per_pixel );
return( -1 );
@@ -343,9 +508,9 @@ check_samples( ReadTiff *rtiff, int samples_per_pixel )
/* Check n and n+1 so we can have an alpha.
*/
static int
-check_min_samples( ReadTiff *rtiff, int samples_per_pixel )
+rtiff_check_min_samples( Rtiff *rtiff, int samples_per_pixel )
{
- if( rtiff->samples_per_pixel < samples_per_pixel ) {
+ if( rtiff->header.samples_per_pixel < samples_per_pixel ) {
vips_error( "tiff2vips",
_( "not at least %d samples per pixel" ),
samples_per_pixel );
@@ -356,9 +521,10 @@ check_min_samples( ReadTiff *rtiff, int samples_per_pixel )
}
static int
-check_interpretation( ReadTiff *rtiff, int photometric_interpretation )
+rtiff_check_interpretation( Rtiff *rtiff, int photometric_interpretation )
{
- if( rtiff->photometric_interpretation != photometric_interpretation ) {
+ if( rtiff->header.photometric_interpretation !=
+ photometric_interpretation ) {
vips_error( "tiff2vips",
_( "not photometric interpretation %d" ),
photometric_interpretation );
@@ -369,9 +535,9 @@ check_interpretation( ReadTiff *rtiff, int photometric_interpretation )
}
static int
-check_bits( ReadTiff *rtiff, int bits_per_sample )
+rtiff_check_bits( Rtiff *rtiff, int bits_per_sample )
{
- if( rtiff->bits_per_sample != bits_per_sample ) {
+ if( rtiff->header.bits_per_sample != bits_per_sample ) {
vips_error( "tiff2vips",
_( "not %d bits per sample" ), bits_per_sample );
return( -1 );
@@ -381,16 +547,16 @@ check_bits( ReadTiff *rtiff, int bits_per_sample )
}
static int
-check_bits_palette( ReadTiff *rtiff )
+rtiff_check_bits_palette( Rtiff *rtiff )
{
- if( rtiff->bits_per_sample != 16 &&
- rtiff->bits_per_sample != 8 &&
- rtiff->bits_per_sample != 4 &&
- rtiff->bits_per_sample != 2 &&
- rtiff->bits_per_sample != 1 ) {
+ if( rtiff->header.bits_per_sample != 16 &&
+ rtiff->header.bits_per_sample != 8 &&
+ rtiff->header.bits_per_sample != 4 &&
+ rtiff->header.bits_per_sample != 2 &&
+ rtiff->header.bits_per_sample != 1 ) {
vips_error( "tiff2vips",
_( "%d bits per sample palette image not supported" ),
- rtiff->bits_per_sample );
+ rtiff->header.bits_per_sample );
return( -1 );
}
@@ -398,44 +564,47 @@ check_bits_palette( ReadTiff *rtiff )
}
static VipsBandFormat
-guess_format( ReadTiff *rtiff )
+rtiff_guess_format( Rtiff *rtiff )
{
- switch( rtiff->bits_per_sample ) {
+ int bits_per_sample = rtiff->header.bits_per_sample;
+ int sample_format = rtiff->header.sample_format;
+
+ switch( bits_per_sample ) {
case 1:
case 2:
case 4:
case 8:
- if( rtiff->sample_format == SAMPLEFORMAT_INT )
+ if( sample_format == SAMPLEFORMAT_INT )
return( VIPS_FORMAT_CHAR );
- if( rtiff->sample_format == SAMPLEFORMAT_UINT )
+ if( sample_format == SAMPLEFORMAT_UINT )
return( VIPS_FORMAT_UCHAR );
break;
case 16:
- if( rtiff->sample_format == SAMPLEFORMAT_INT )
+ if( sample_format == SAMPLEFORMAT_INT )
return( VIPS_FORMAT_SHORT );
- if( rtiff->sample_format == SAMPLEFORMAT_UINT )
+ if( sample_format == SAMPLEFORMAT_UINT )
return( VIPS_FORMAT_USHORT );
break;
case 32:
- if( rtiff->sample_format == SAMPLEFORMAT_INT )
+ if( sample_format == SAMPLEFORMAT_INT )
return( VIPS_FORMAT_INT );
- if( rtiff->sample_format == SAMPLEFORMAT_UINT )
+ if( sample_format == SAMPLEFORMAT_UINT )
return( VIPS_FORMAT_UINT );
- if( rtiff->sample_format == SAMPLEFORMAT_IEEEFP )
+ if( sample_format == SAMPLEFORMAT_IEEEFP )
return( VIPS_FORMAT_FLOAT );
break;
case 64:
- if( rtiff->sample_format == SAMPLEFORMAT_IEEEFP )
+ if( sample_format == SAMPLEFORMAT_IEEEFP )
return( VIPS_FORMAT_DOUBLE );
- if( rtiff->sample_format == SAMPLEFORMAT_COMPLEXIEEEFP )
+ if( sample_format == SAMPLEFORMAT_COMPLEXIEEEFP )
return( VIPS_FORMAT_COMPLEX );
break;
case 128:
- if( rtiff->sample_format == SAMPLEFORMAT_COMPLEXIEEEFP )
+ if( sample_format == SAMPLEFORMAT_COMPLEXIEEEFP )
return( VIPS_FORMAT_DPCOMPLEX );
break;
@@ -451,8 +620,10 @@ guess_format( ReadTiff *rtiff )
/* Per-scanline process function for VIPS_CODING_LABQ.
*/
static void
-labpack_line( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *dummy )
+rtiff_labpack_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *dummy )
{
+ int samples_per_pixel = rtiff->header.samples_per_pixel;
+
int x;
for( x = 0; x < n; x++ ) {
@@ -462,18 +633,18 @@ labpack_line( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *dummy )
q[3] = 0;
q += 4;
- p += rtiff->samples_per_pixel;
+ p += samples_per_pixel;
}
}
/* Read an 8-bit LAB image.
*/
static int
-parse_labpack( ReadTiff *rtiff, VipsImage *out )
+rtiff_parse_labpack( Rtiff *rtiff, VipsImage *out )
{
- if( check_min_samples( rtiff, 3 ) ||
- check_bits( rtiff, 8 ) ||
- check_interpretation( rtiff, PHOTOMETRIC_CIELAB ) )
+ if( rtiff_check_min_samples( rtiff, 3 ) ||
+ rtiff_check_bits( rtiff, 8 ) ||
+ rtiff_check_interpretation( rtiff, PHOTOMETRIC_CIELAB ) )
return( -1 );
out->Bands = 4;
@@ -481,7 +652,7 @@ parse_labpack( ReadTiff *rtiff, VipsImage *out )
out->Coding = VIPS_CODING_LABQ;
out->Type = VIPS_INTERPRETATION_LAB;
- rtiff->sfn = labpack_line;
+ rtiff->sfn = rtiff_labpack_line;
return( 0 );
}
@@ -489,42 +660,46 @@ parse_labpack( ReadTiff *rtiff, VipsImage *out )
/* Per-scanline process function for LABS.
*/
static void
-labs_line( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *dummy )
+rtiff_labs_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *dummy )
{
+ int samples_per_pixel = rtiff->header.samples_per_pixel;
+
+ unsigned short *p1;
+ short *q1;
int x;
- unsigned short *p1 = (unsigned short *) p;
- short *q1 = (short *) q;
int i;
+ p1 = (unsigned short *) p;
+ q1 = (short *) q;
for( x = 0; x < n; x++ ) {
/* We use a signed int16 for L.
*/
q1[0] = p1[0] >> 1;
- for( i = 1; i < rtiff->samples_per_pixel; i++ )
+ for( i = 1; i < samples_per_pixel; i++ )
q1[i] = p1[i];
- q1 += rtiff->samples_per_pixel;
- p1 += rtiff->samples_per_pixel;
+ q1 += samples_per_pixel;
+ p1 += samples_per_pixel;
}
}
/* Read a 16-bit LAB image.
*/
static int
-parse_labs( ReadTiff *rtiff, VipsImage *out )
+rtiff_parse_labs( Rtiff *rtiff, VipsImage *out )
{
- if( check_min_samples( rtiff, 3 ) ||
- check_bits( rtiff, 16 ) ||
- check_interpretation( rtiff, PHOTOMETRIC_CIELAB ) )
+ if( rtiff_check_min_samples( rtiff, 3 ) ||
+ rtiff_check_bits( rtiff, 16 ) ||
+ rtiff_check_interpretation( rtiff, PHOTOMETRIC_CIELAB ) )
return( -1 );
- out->Bands = rtiff->samples_per_pixel;
+ out->Bands = rtiff->header.samples_per_pixel;
out->BandFmt = VIPS_FORMAT_SHORT;
out->Coding = VIPS_CODING_NONE;
out->Type = VIPS_INTERPRETATION_LABS;
- rtiff->sfn = labs_line;
+ rtiff->sfn = rtiff_labs_line;
return( 0 );
}
@@ -532,13 +707,15 @@ parse_labs( ReadTiff *rtiff, VipsImage *out )
/* Per-scanline process function for 1 bit images.
*/
static void
-onebit_line( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *flg )
+rtiff_onebit_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *flg )
{
+ int photometric_interpretation =
+ rtiff->header.photometric_interpretation;
+
int x, i, z;
VipsPel bits;
- int black =
- rtiff->photometric_interpretation == PHOTOMETRIC_MINISBLACK ?
+ int black = photometric_interpretation == PHOTOMETRIC_MINISBLACK ?
0 : 255;
int white = black ^ 0xff;
@@ -569,10 +746,10 @@ onebit_line( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *flg )
/* Read a 1-bit TIFF image.
*/
static int
-parse_onebit( ReadTiff *rtiff, VipsImage *out )
+rtiff_parse_onebit( Rtiff *rtiff, VipsImage *out )
{
- if( check_samples( rtiff, 1 ) ||
- check_bits( rtiff, 1 ) )
+ if( rtiff_check_samples( rtiff, 1 ) ||
+ rtiff_check_bits( rtiff, 1 ) )
return( -1 );
out->Bands = 1;
@@ -580,7 +757,7 @@ parse_onebit( ReadTiff *rtiff, VipsImage *out )
out->Coding = VIPS_CODING_NONE;
out->Type = VIPS_INTERPRETATION_B_W;
- rtiff->sfn = onebit_line;
+ rtiff->sfn = rtiff_onebit_line;
return( 0 );
}
@@ -599,22 +776,24 @@ parse_onebit( ReadTiff *rtiff, VipsImage *out )
else \
q1[0] = p1[0]; \
\
- for( i = 1; i < rtiff->samples_per_pixel; i++ ) \
+ for( i = 1; i < samples_per_pixel; i++ ) \
q1[i] = p1[i]; \
\
- q1 += rtiff->samples_per_pixel; \
- p1 += rtiff->samples_per_pixel; \
+ q1 += samples_per_pixel; \
+ p1 += samples_per_pixel; \
} \
}
/* Per-scanline process function for greyscale images.
*/
static void
-greyscale_line( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client )
+rtiff_greyscale_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client )
{
- gboolean invert =
- rtiff->photometric_interpretation == PHOTOMETRIC_MINISWHITE;
- VipsBandFormat format = guess_format( rtiff );
+ int samples_per_pixel = rtiff->header.samples_per_pixel;
+ int photometric_interpretation =
+ rtiff->header.photometric_interpretation;
+ gboolean invert = photometric_interpretation == PHOTOMETRIC_MINISWHITE;
+ VipsBandFormat format = rtiff_guess_format( rtiff );
int x, i;
@@ -657,28 +836,28 @@ greyscale_line( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client )
* PHOTOMETRIC_MINISBLACK is set.
*/
static int
-parse_greyscale( ReadTiff *rtiff, VipsImage *out )
+rtiff_parse_greyscale( Rtiff *rtiff, VipsImage *out )
{
- if( check_min_samples( rtiff, 1 ) )
+ if( rtiff_check_min_samples( rtiff, 1 ) )
return( -1 );
- out->Bands = rtiff->samples_per_pixel;
- out->BandFmt = guess_format( rtiff );
+ out->Bands = rtiff->header.samples_per_pixel;
+ out->BandFmt = rtiff_guess_format( rtiff );
if( out->BandFmt == VIPS_FORMAT_NOTSET )
return( -1 );
out->Coding = VIPS_CODING_NONE;
- if( rtiff->bits_per_sample == 16 )
+ if( rtiff->header.bits_per_sample == 16 )
out->Type = VIPS_INTERPRETATION_GREY16;
else
out->Type = VIPS_INTERPRETATION_B_W;
- /* greyscale_line() doesn't do complex.
+ /* rtiff_greyscale_line() doesn't do complex.
*/
if( vips_check_noncomplex( "tiff2vips", out ) )
return( -1 );
- rtiff->sfn = greyscale_line;
+ rtiff->sfn = rtiff_greyscale_line;
return( 0 );
}
@@ -702,10 +881,12 @@ typedef struct {
/* 1/2/4 bit samples with an 8-bit palette.
*/
static void
-palette_line_bit( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client )
+rtiff_palette_line_bit( Rtiff *rtiff,
+ VipsPel *q, VipsPel *p, int n, void *client )
{
PaletteRead *read = (PaletteRead *) client;
- int samples = rtiff->samples_per_pixel;
+ int samples_per_pixel = rtiff->header.samples_per_pixel;
+ int bits_per_sample = rtiff->header.bits_per_sample;
int bit;
VipsPel data;
@@ -713,7 +894,7 @@ palette_line_bit( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client )
bit = 0;
data = 0;
- for( x = 0; x < n * samples; x++ ) {
+ for( x = 0; x < n * samples_per_pixel; x++ ) {
int i;
if( bit <= 0 ) {
@@ -721,14 +902,14 @@ palette_line_bit( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client )
bit = 8;
}
- i = data >> (8 - rtiff->bits_per_sample);
- data <<= rtiff->bits_per_sample;
- bit -= rtiff->bits_per_sample;
+ i = data >> (8 - bits_per_sample);
+ data <<= bits_per_sample;
+ bit -= bits_per_sample;
/* The first band goes through the LUT, subsequent bands are
* left-justified and copied.
*/
- if( x % samples == 0 ) {
+ if( x % samples_per_pixel == 0 ) {
if( read->mono )
*q++ = read->red8[i];
else {
@@ -739,18 +920,18 @@ palette_line_bit( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client )
}
}
else
- *q++ = i << (8 - rtiff->bits_per_sample);
+ *q++ = i << (8 - bits_per_sample);
}
}
/* 8-bit samples with an 8-bit palette.
*/
static void
-palette_line8( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n,
+rtiff_palette_line8( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n,
void *client )
{
PaletteRead *read = (PaletteRead *) client;
- int samples = rtiff->samples_per_pixel;
+ int samples_per_pixel = rtiff->header.samples_per_pixel;
int x;
int s;
@@ -767,22 +948,22 @@ palette_line8( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n,
q += 2;
}
- for( s = 1; s < samples; s++ )
+ for( s = 1; s < samples_per_pixel; s++ )
q[s] = p[s];
- q += samples;
- p += samples;
+ q += samples_per_pixel;
+ p += samples_per_pixel;
}
}
/* 16-bit samples with 16-bit data in the palette.
*/
static void
-palette_line16( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n,
+rtiff_palette_line16( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n,
void *client )
{
PaletteRead *read = (PaletteRead *) client;
- int samples = rtiff->samples_per_pixel;
+ int samples_per_pixel = rtiff->header.samples_per_pixel;
guint16 *p16, *q16;
int x;
@@ -803,27 +984,30 @@ palette_line16( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n,
q16 += 2;
}
- for( s = 1; s < samples; s++ )
+ for( s = 1; s < samples_per_pixel; s++ )
q16[s] = p16[s];
- q16 += samples;
- p16 += samples;
+ q16 += samples_per_pixel;
+ p16 += samples_per_pixel;
}
}
/* Read a palette-ised TIFF image.
*/
static int
-parse_palette( ReadTiff *rtiff, VipsImage *out )
+rtiff_parse_palette( Rtiff *rtiff, VipsImage *out )
{
+ int samples_per_pixel = rtiff->header.samples_per_pixel;
+ int bits_per_sample = rtiff->header.bits_per_sample;
+
int len;
PaletteRead *read;
int i;
- if( check_bits_palette( rtiff ) ||
- check_min_samples( rtiff, 1 ) )
+ if( rtiff_check_bits_palette( rtiff ) ||
+ rtiff_check_min_samples( rtiff, 1 ) )
return( -1 );
- len = 1 << rtiff->bits_per_sample;
+ len = 1 << bits_per_sample;
if( !(read = VIPS_NEW( out, PaletteRead )) ||
!(read->red8 = VIPS_ARRAY( out, len, VipsPel )) ||
@@ -857,7 +1041,7 @@ parse_palette( ReadTiff *rtiff, VipsImage *out )
read->blue8[i] = read->blue16[i] >> 8;
}
else {
- vips_warn( "tiff2vips", "%s", _( "assuming 8-bit palette" ) );
+ g_warning( "%s", _( "assuming 8-bit palette" ) );
for( i = 0; i < len; i++ ) {
read->red8[i] = read->red16[i] & 0xff;
@@ -881,34 +1065,34 @@ parse_palette( ReadTiff *rtiff, VipsImage *out )
* just search the colormap.
*/
- if( rtiff->bits_per_sample <= 8 )
+ if( bits_per_sample <= 8 )
out->BandFmt = VIPS_FORMAT_UCHAR;
else
out->BandFmt = VIPS_FORMAT_USHORT;
out->Coding = VIPS_CODING_NONE;
if( read->mono ) {
- out->Bands = rtiff->samples_per_pixel;
- if( rtiff->bits_per_sample <= 8 )
+ out->Bands = samples_per_pixel;
+ if( bits_per_sample <= 8 )
out->Type = VIPS_INTERPRETATION_B_W;
else
out->Type = VIPS_INTERPRETATION_GREY16;
}
else {
- out->Bands = rtiff->samples_per_pixel + 2;
- if( rtiff->bits_per_sample <= 8 )
+ out->Bands = samples_per_pixel + 2;
+ if( bits_per_sample <= 8 )
out->Type = VIPS_INTERPRETATION_sRGB;
else
out->Type = VIPS_INTERPRETATION_RGB16;
}
rtiff->client = read;
- if( rtiff->bits_per_sample < 8 )
- rtiff->sfn = palette_line_bit;
- else if( rtiff->bits_per_sample == 8 )
- rtiff->sfn = palette_line8;
- else if( rtiff->bits_per_sample == 16 )
- rtiff->sfn = palette_line16;
+ if( bits_per_sample < 8 )
+ rtiff->sfn = rtiff_palette_line_bit;
+ else if( bits_per_sample == 8 )
+ rtiff->sfn = rtiff_palette_line8;
+ else if( bits_per_sample == 16 )
+ rtiff->sfn = rtiff_palette_line16;
else
g_assert_not_reached();
@@ -918,7 +1102,7 @@ parse_palette( ReadTiff *rtiff, VipsImage *out )
/* Per-scanline process function when we just need to copy.
*/
static void
-memcpy_line( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client )
+rtiff_memcpy_line( Rtiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client )
{
VipsImage *im = (VipsImage *) client;
size_t len = n * VIPS_IMAGE_SIZEOF_PEL( im );
@@ -930,17 +1114,21 @@ memcpy_line( ReadTiff *rtiff, VipsPel *q, VipsPel *p, int n, void *client )
* buffer.
*/
static int
-parse_copy( ReadTiff *rtiff, VipsImage *out )
+rtiff_parse_copy( Rtiff *rtiff, VipsImage *out )
{
- out->Bands = rtiff->samples_per_pixel;
- out->BandFmt = guess_format( rtiff );
+ int samples_per_pixel = rtiff->header.samples_per_pixel;
+ int photometric_interpretation =
+ rtiff->header.photometric_interpretation;
+
+ out->Bands = samples_per_pixel;
+ out->BandFmt = rtiff_guess_format( rtiff );
if( out->BandFmt == VIPS_FORMAT_NOTSET )
return( -1 );
out->Coding = VIPS_CODING_NONE;
- if( rtiff->samples_per_pixel >= 3 &&
- (rtiff->photometric_interpretation == PHOTOMETRIC_RGB ||
- rtiff->photometric_interpretation == PHOTOMETRIC_YCBCR) ) {
+ if( samples_per_pixel >= 3 &&
+ (photometric_interpretation == PHOTOMETRIC_RGB ||
+ photometric_interpretation == PHOTOMETRIC_YCBCR) ) {
if( out->BandFmt == VIPS_FORMAT_USHORT )
out->Type = VIPS_INTERPRETATION_RGB16;
else if( !vips_band_format_isint( out->BandFmt ) )
@@ -952,101 +1140,52 @@ parse_copy( ReadTiff *rtiff, VipsImage *out )
out->Type = VIPS_INTERPRETATION_sRGB;
}
- if( rtiff->samples_per_pixel >= 3 &&
- rtiff->photometric_interpretation == PHOTOMETRIC_CIELAB )
+ if( samples_per_pixel >= 3 &&
+ photometric_interpretation == PHOTOMETRIC_CIELAB )
out->Type = VIPS_INTERPRETATION_LAB;
- if( rtiff->samples_per_pixel >= 4 &&
- rtiff->photometric_interpretation == PHOTOMETRIC_SEPARATED )
+ if( samples_per_pixel >= 4 &&
+ photometric_interpretation == PHOTOMETRIC_SEPARATED )
out->Type = VIPS_INTERPRETATION_CMYK;
- rtiff->sfn = memcpy_line;
+ rtiff->sfn = rtiff_memcpy_line;
rtiff->client = out;
rtiff->memcpy = TRUE;
return( 0 );
}
-/* Read resolution from a TIFF image.
- */
-static int
-parse_resolution( TIFF *tiff, VipsImage *out )
-{
- float x, y;
- int ru;
-
- if( TIFFGetFieldDefaulted( tiff, TIFFTAG_XRESOLUTION, &x ) &&
- TIFFGetFieldDefaulted( tiff, TIFFTAG_YRESOLUTION, &y ) &&
- tfget16( tiff, TIFFTAG_RESOLUTIONUNIT, &ru ) ) {
- switch( ru ) {
- case RESUNIT_NONE:
- break;
-
- case RESUNIT_INCH:
- /* In pixels-per-inch ... convert to mm.
- */
- x /= 10.0 * 2.54;
- y /= 10.0 * 2.54;
- vips_image_set_string( out,
- VIPS_META_RESOLUTION_UNIT, "in" );
- break;
-
- case RESUNIT_CENTIMETER:
- /* In pixels-per-centimetre ... convert to mm.
- */
- x /= 10.0;
- y /= 10.0;
- vips_image_set_string( out,
- VIPS_META_RESOLUTION_UNIT, "cm" );
- break;
-
- default:
- vips_error( "tiff2vips",
- "%s", _( "unknown resolution unit" ) );
- return( -1 );
- }
- }
- else {
- vips_warn( "tiff2vips", _( "no resolution information for "
- "TIFF image \"%s\" -- defaulting to 1 pixel per mm" ),
- TIFFFileName( tiff ) );
- x = 1.0;
- y = 1.0;
- }
-
- out->Xres = x;
- out->Yres = y;
-
- return( 0 );
-}
-
-typedef int (*reader_fn)( ReadTiff *rtiff, VipsImage *out );
+typedef int (*reader_fn)( Rtiff *rtiff, VipsImage *out );
/* We have a range of output paths. Look at the tiff header and try to
* route the input image to the best output path.
*/
static reader_fn
-pick_reader( ReadTiff *rtiff )
+rtiff_pick_reader( Rtiff *rtiff )
{
- if( rtiff->photometric_interpretation == PHOTOMETRIC_CIELAB ) {
- if( rtiff->bits_per_sample == 8 )
- return( parse_labpack );
- if( rtiff->bits_per_sample == 16 )
- return( parse_labs );
+ int bits_per_sample = rtiff->header.bits_per_sample;
+ int photometric_interpretation =
+ rtiff->header.photometric_interpretation;
+
+ if( photometric_interpretation == PHOTOMETRIC_CIELAB ) {
+ if( bits_per_sample == 8 )
+ return( rtiff_parse_labpack );
+ if( bits_per_sample == 16 )
+ return( rtiff_parse_labs );
}
- if( rtiff->photometric_interpretation == PHOTOMETRIC_MINISWHITE ||
- rtiff->photometric_interpretation == PHOTOMETRIC_MINISBLACK ) {
- if( rtiff->bits_per_sample == 1 )
- return( parse_onebit );
+ if( photometric_interpretation == PHOTOMETRIC_MINISWHITE ||
+ photometric_interpretation == PHOTOMETRIC_MINISBLACK ) {
+ if( bits_per_sample == 1 )
+ return( rtiff_parse_onebit );
else
- return( parse_greyscale );
+ return( rtiff_parse_greyscale );
}
- if( rtiff->photometric_interpretation == PHOTOMETRIC_PALETTE )
- return( parse_palette );
+ if( photometric_interpretation == PHOTOMETRIC_PALETTE )
+ return( rtiff_parse_palette );
- if( rtiff->photometric_interpretation == PHOTOMETRIC_YCBCR ) {
+ if( photometric_interpretation == PHOTOMETRIC_YCBCR ) {
/* Sometimes JPEG in TIFF images are tagged as YCBCR. Ask
* libtiff to convert to RGB for us.
*/
@@ -1054,92 +1193,24 @@ pick_reader( ReadTiff *rtiff )
TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB );
}
- return( parse_copy );
+ return( rtiff_parse_copy );
}
-/* Look at PhotometricInterpretation and BitsPerPixel and try to figure out
- * which of the image classes this is.
+/* Set the header on @out from our rtiff. rtiff_header_read() has already been
+ * called.
*/
static int
-parse_header( ReadTiff *rtiff, VipsImage *out )
+rtiff_set_header( Rtiff *rtiff, VipsImage *out )
{
uint32 data_length;
- uint32 width, height;
void *data;
- if( tfexists( rtiff->tiff, TIFFTAG_PLANARCONFIG ) ) {
- int v;
+ out->Xsize = rtiff->header.width;
+ out->Ysize = rtiff->header.height * rtiff->n;
- if( !tfget16( rtiff->tiff, TIFFTAG_PLANARCONFIG, &v ) )
- return( -1 );
- if( v == PLANARCONFIG_SEPARATE )
- rtiff->separate = TRUE;
- }
-
- /* We always need dimensions.
- */
- if( !tfget32( rtiff->tiff, TIFFTAG_IMAGEWIDTH, &width ) ||
- !tfget32( rtiff->tiff, TIFFTAG_IMAGELENGTH, &height ) ||
- parse_resolution( rtiff->tiff, out ) ||
- !tfget16( rtiff->tiff, TIFFTAG_SAMPLESPERPIXEL,
- &rtiff->samples_per_pixel ) ||
- !tfget16( rtiff->tiff, TIFFTAG_BITSPERSAMPLE,
- &rtiff->bits_per_sample ) ||
- !tfget16( rtiff->tiff, TIFFTAG_PHOTOMETRIC,
- &rtiff->photometric_interpretation ) )
- return( -1 );
-
- /* Some optional fields.
- */
-{
- uint16 v;
-
- rtiff->sample_format = SAMPLEFORMAT_INT;
-
- if( TIFFGetFieldDefaulted( rtiff->tiff, TIFFTAG_SAMPLEFORMAT, &v ) ) {
- /* Some images have this set to void, bizarre.
- */
- if( v == SAMPLEFORMAT_VOID )
- v = SAMPLEFORMAT_UINT;
-
- rtiff->sample_format = v;
- }
-}
-
-{
- uint16 v;
-
- rtiff->orientation = ORIENTATION_TOPLEFT;
-
- if( TIFFGetFieldDefaulted( rtiff->tiff, TIFFTAG_ORIENTATION, &v ) )
- /* Can have mad values.
- */
- rtiff->orientation = VIPS_CLIP( 1, v, 8 );
-}
-
- /* Arbitrary sanity-checking limits.
- */
-
- if( width <= 0 ||
- width > VIPS_MAX_COORD ||
- height <= 0 ||
- height > VIPS_MAX_COORD ) {
- vips_error( "tiff2vips",
- "%s", _( "width/height out of range" ) );
- return( -1 );
- }
-
- if( rtiff->samples_per_pixel <= 0 ||
- rtiff->samples_per_pixel > 10000 ||
- rtiff->bits_per_sample <= 0 ||
- rtiff->bits_per_sample > 32 ) {
- vips_error( "tiff2vips",
- "%s", _( "samples out of range" ) );
- return( -1 );
- }
-
- out->Xsize = width;
- out->Ysize = height;
+ if( rtiff->n > 1 )
+ vips_image_set_int( out,
+ VIPS_META_PAGE_HEIGHT, rtiff->header.height );
/* Even though we could end up serving tiled data, always hint
* THINSTRIP, since we're quite happy doing that too, and it could need
@@ -1148,20 +1219,20 @@ parse_header( ReadTiff *rtiff, VipsImage *out )
vips_image_pipelinev( out, VIPS_DEMAND_STYLE_THINSTRIP, NULL );
#ifdef DEBUG
- printf( "parse_header: samples_per_pixel = %d\n",
- rtiff->samples_per_pixel );
- printf( "parse_header: bits_per_sample = %d\n",
- rtiff->bits_per_sample );
- printf( "parse_header: sample_format = %d\n",
- rtiff->sample_format );
- printf( "parse_header: orientation = %d\n",
- rtiff->orientation );
+ printf( "rtiff_set_header: header.samples_per_pixel = %d\n",
+ rtiff->header.samples_per_pixel );
+ printf( "rtiff_set_header: header.bits_per_sample = %d\n",
+ rtiff->header.bits_per_sample );
+ printf( "rtiff_set_header: header.sample_format = %d\n",
+ rtiff->header.sample_format );
+ printf( "rtiff_set_header: header.orientation = %d\n",
+ rtiff->header.orientation );
#endif /*DEBUG*/
/* We have a range of output paths. Look at the tiff header and try to
* route the input image to the best output path.
*/
- if( pick_reader( rtiff )( rtiff, out ) )
+ if( rtiff_pick_reader( rtiff )( rtiff, out ) )
return( -1 );
/* Read any ICC profile.
@@ -1231,10 +1302,14 @@ parse_header( ReadTiff *rtiff, VipsImage *out )
VIPS_META_IMAGEDESCRIPTION, (char *) data );
}
+ if( get_resolution( rtiff->tiff, out ) )
+ return( -1 );
+
/* Set the "orientation" tag. This is picked up later by autorot, if
* requested.
*/
- vips_image_set_int( out, VIPS_META_ORIENTATION, rtiff->orientation );
+ vips_image_set_int( out,
+ VIPS_META_ORIENTATION, rtiff->header.orientation );
return( 0 );
}
@@ -1247,22 +1322,22 @@ parse_header( ReadTiff *rtiff, VipsImage *out )
* This seems not to happen for old-style jpeg-compressed tiles.
*/
static size_t
-tiff_tile_size( ReadTiff *rtiff )
+rtiff_tile_size( Rtiff *rtiff )
{
- return( TIFFTileRowSize( rtiff->tiff ) * rtiff->theight );
+ return( TIFFTileRowSize( rtiff->tiff ) * rtiff->header.tile_height );
}
/* Allocate a tile buffer. Have one of these for each thread so we can unpack
* to vips in parallel.
*/
static void *
-tiff_seq_start( VipsImage *out, void *a, void *b )
+rtiff_seq_start( VipsImage *out, void *a, void *b )
{
- ReadTiff *rtiff = (ReadTiff *) a;
+ Rtiff *rtiff = (Rtiff *) a;
tsize_t size;
tdata_t *buf;
- size = tiff_tile_size( rtiff );
+ size = rtiff_tile_size( rtiff );
if( !(buf = vips_malloc( NULL, size )) )
return( NULL );
@@ -1270,39 +1345,39 @@ tiff_seq_start( VipsImage *out, void *a, void *b )
}
/* Paint a tile from the file. This is a
- * special-case for a region is exactly a tiff tile, and pixels need no
+ * special-case for when a region is exactly a tiff tile, and pixels need no
* conversion. In this case, libtiff can read tiles directly to our output
* region.
*/
static int
-tiff_fill_region_aligned( VipsRegion *out, void *seq, void *a, void *b )
+rtiff_fill_region_aligned( VipsRegion *out, void *seq, void *a, void *b )
{
- ReadTiff *rtiff = (ReadTiff *) a;
+ Rtiff *rtiff = (Rtiff *) a;
VipsRect *r = &out->valid;
- g_assert( (r->left % rtiff->twidth) == 0 );
- g_assert( (r->top % rtiff->theight) == 0 );
- g_assert( r->width == rtiff->twidth );
- g_assert( r->height == rtiff->theight );
+ g_assert( (r->left % rtiff->header.tile_width) == 0 );
+ g_assert( (r->top % rtiff->header.tile_height) == 0 );
+ g_assert( r->width == rtiff->header.tile_width );
+ g_assert( r->height == rtiff->header.tile_height );
g_assert( VIPS_REGION_LSKIP( out ) == VIPS_REGION_SIZEOF_LINE( out ) );
#ifdef DEBUG
- printf( "tiff_fill_region_aligned: left = %d, top = %d\n",
+ printf( "rtiff_fill_region_aligned: left = %d, top = %d\n",
r->left, r->top );
#endif /*DEBUG*/
- VIPS_GATE_START( "tiff_fill_region_aligned: work" );
+ VIPS_GATE_START( "rtiff_fill_region_aligned: work" );
/* Read that tile directly into the vips tile.
*/
if( TIFFReadTile( rtiff->tiff,
VIPS_REGION_ADDR( out, r->left, r->top ),
r->left, r->top, 0, 0 ) < 0 ) {
- VIPS_GATE_STOP( "tiff_fill_region_aligned: work" );
+ VIPS_GATE_STOP( "rtiff_fill_region_aligned: work" );
return( -1 );
}
- VIPS_GATE_STOP( "tiff_fill_region_aligned: work" );
+ VIPS_GATE_STOP( "rtiff_fill_region_aligned: work" );
return( 0 );
}
@@ -1310,20 +1385,17 @@ tiff_fill_region_aligned( VipsRegion *out, void *seq, void *a, void *b )
/* Loop over the output region painting in tiles from the file.
*/
static int
-tiff_fill_region( VipsRegion *out, void *seq, void *a, void *b, gboolean *stop )
+rtiff_fill_region( VipsRegion *out, void *seq, void *a, void *b, gboolean *stop )
{
tdata_t *buf = (tdata_t *) seq;
- ReadTiff *rtiff = (ReadTiff *) a;
+ Rtiff *rtiff = (Rtiff *) a;
+ int tile_width = rtiff->header.tile_width;
+ int tile_height = rtiff->header.tile_height;
VipsRect *r = &out->valid;
- /* Find top left of tiles we need.
- */
- int xs = (r->left / rtiff->twidth) * rtiff->twidth;
- int ys = (r->top / rtiff->theight) * rtiff->theight;
-
/* Sizeof a line of bytes in the TIFF tile.
*/
- int tls = tiff_tile_size( rtiff ) / rtiff->theight;
+ int tls = rtiff_tile_size( rtiff ) / tile_height;
/* Sizeof a pel in the TIFF file. This won't work for formats which
* are <1 byte per pel, like onebit :-( Fortunately, it's only used
@@ -1331,7 +1403,7 @@ tiff_fill_region( VipsRegion *out, void *seq, void *a, void *b, gboolean *stop )
* vips_tilecache(), we will never have to calculate positions not
* within a tile.
*/
- int tps = tls / rtiff->twidth;
+ int tps = tls / tile_width;
int x, y, z;
@@ -1339,35 +1411,57 @@ tiff_fill_region( VipsRegion *out, void *seq, void *a, void *b, gboolean *stop )
* the tiff tile and we have no repacking to do for this format.
*/
if( rtiff->memcpy &&
- r->left % rtiff->twidth == 0 &&
- r->top % rtiff->theight == 0 &&
- r->width == rtiff->twidth &&
- r->height == rtiff->theight &&
+ r->left % tile_width == 0 &&
+ r->top % tile_height == 0 &&
+ r->width == tile_width &&
+ r->height == tile_height &&
VIPS_REGION_LSKIP( out ) == VIPS_REGION_SIZEOF_LINE( out ) )
- return( tiff_fill_region_aligned( out, seq, a, b ) );
+ return( rtiff_fill_region_aligned( out, seq, a, b ) );
- VIPS_GATE_START( "tiff_fill_region: work" );
+ VIPS_GATE_START( "rtiff_fill_region: work" );
- for( y = ys; y < VIPS_RECT_BOTTOM( r ); y += rtiff->theight )
- for( x = xs; x < VIPS_RECT_RIGHT( r ); x += rtiff->twidth ) {
- VipsRect tile;
- VipsRect hit;
+ y = 0;
+ while( y < r->height ) {
+ VipsRect tile, page, hit;
- /* Read that tile.
+ x = 0;
+ while( x < r->width ) {
+ int page_no = rtiff->page + (r->top + y) /
+ rtiff->header.height;
+ int page_y = (r->top + y) % rtiff->header.height;
+
+ /* Coordinate of the tile on this page that xy falls in.
*/
- if( TIFFReadTile( rtiff->tiff, buf, x, y, 0, 0 ) < 0 ) {
- VIPS_GATE_STOP( "tiff_fill_region: work" );
+ int xs = ((r->left + x) / tile_width) * tile_width;
+ int ys = (page_y / tile_height) * tile_height;
+
+ if( rtiff_set_page( rtiff, page_no ) ||
+ TIFFReadTile( rtiff->tiff,
+ buf, xs, ys, 0, 0 ) < 0 ) {
+ VIPS_GATE_STOP( "rtiff_fill_region: work" );
return( -1 );
}
- /* The tile we read.
+ /* Position of tile on the page.
*/
- tile.left = x;
- tile.top = y;
- tile.width = rtiff->twidth;
- tile.height = rtiff->theight;
+ tile.left = xs;
+ tile.top = ys;
+ tile.width = tile_width;
+ tile.height = tile_height;
- /* The section that hits the region we are building.
+ /* It'll be clipped by this page.
+ */
+ page.left = 0;
+ page.top = 0;
+ page.width = rtiff->header.width;
+ page.height = rtiff->header.height;
+ vips_rect_intersectrect( &tile, &page, &tile );
+
+ /* To image coordinates.
+ */
+ tile.top += page_no * rtiff->header.height;
+
+ /* And clip again by this region.
*/
vips_rect_intersectrect( &tile, r, &hit );
@@ -1384,15 +1478,20 @@ tiff_fill_region( VipsRegion *out, void *seq, void *a, void *b, gboolean *stop )
rtiff->sfn( rtiff,
q, p, hit.width, rtiff->client );
}
+
+ x += tile.width;
}
- VIPS_GATE_STOP( "tiff_fill_region: work" );
+ y += tile.height;
+ }
+
+ VIPS_GATE_STOP( "rtiff_fill_region: work" );
return( 0 );
}
static int
-tiff_seq_stop( void *seq, void *a, void *b )
+rtiff_seq_stop( void *seq, void *a, void *b )
{
vips_free( seq );
@@ -1402,7 +1501,7 @@ tiff_seq_stop( void *seq, void *a, void *b )
/* Auto-rotate handling.
*/
static int
-vips_tiff_autorotate( ReadTiff *rtiff, VipsImage *in, VipsImage **out )
+rtiff_autorotate( Rtiff *rtiff, VipsImage *in, VipsImage **out )
{
VipsAngle angle = vips_autorot_get_angle( in );
@@ -1444,36 +1543,30 @@ vips_tiff_autorotate( ReadTiff *rtiff, VipsImage *in, VipsImage **out )
* the im and do it all partially.
*/
static int
-read_tilewise( ReadTiff *rtiff, VipsImage *out )
+rtiff_read_tilewise( Rtiff *rtiff, VipsImage *out )
{
+ int tile_width = rtiff->header.tile_width;
+ int tile_height = rtiff->header.tile_height;
VipsImage **t = (VipsImage **)
vips_object_local_array( VIPS_OBJECT( out ), 3 );
#ifdef DEBUG
- printf( "tiff2vips: read_tilewise\n" );
+ printf( "tiff2vips: rtiff_read_tilewise\n" );
#endif /*DEBUG*/
/* I don't have a sample images for tiled + separate, ban it for now.
*/
- if( rtiff->separate ) {
+ if( rtiff->header.separate ) {
vips_error( "tiff2vips",
"%s", _( "tiled separate planes not supported" ) );
return( -1 );
}
- /* Get tiling geometry.
- */
- if( !tfget32( rtiff->tiff, TIFFTAG_TILEWIDTH, &rtiff->twidth ) ||
- !tfget32( rtiff->tiff, TIFFTAG_TILELENGTH, &rtiff->theight ) )
- return( -1 );
-
/* Read to this image, then cache to out, see below.
*/
t[0] = vips_image_new();
- /* Parse the TIFF header and set up.
- */
- if( parse_header( rtiff, t[0] ) )
+ if( rtiff_set_header( rtiff, t[0] ) )
return( -1 );
/* Double check: in memcpy mode, the vips tilesize should exactly
@@ -1483,9 +1576,9 @@ read_tilewise( ReadTiff *rtiff, VipsImage *out )
size_t vips_tile_size;
vips_tile_size = VIPS_IMAGE_SIZEOF_PEL( t[0] ) *
- rtiff->twidth * rtiff->theight;
+ tile_width * tile_height;
- if( tiff_tile_size( rtiff ) != vips_tile_size ) {
+ if( rtiff_tile_size( rtiff ) != vips_tile_size ) {
vips_error( "tiff2vips",
"%s", _( "unsupported tiff image type" ) );
return( -1 );
@@ -1499,19 +1592,19 @@ read_tilewise( ReadTiff *rtiff, VipsImage *out )
vips_image_pipelinev( t[0], VIPS_DEMAND_STYLE_THINSTRIP, NULL );
if( vips_image_generate( t[0],
- tiff_seq_start, tiff_fill_region, tiff_seq_stop,
+ 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],
- "tile_width", rtiff->twidth,
- "tile_height", rtiff->theight,
- "max_tiles", 2 * (1 + t[0]->Xsize / rtiff->twidth),
+ "tile_width", tile_width,
+ "tile_height", tile_height,
+ "max_tiles", 2 * (1 + t[0]->Xsize / tile_width),
NULL ) )
return( -1 );
- if( vips_tiff_autorotate( rtiff, t[1], &t[2] ) )
+ if( rtiff_autorotate( rtiff, t[1], &t[2] ) )
return( -1 );
if( vips_image_write( t[2], out ) )
return( -1 );
@@ -1519,43 +1612,35 @@ read_tilewise( ReadTiff *rtiff, VipsImage *out )
return( 0 );
}
-static int
-tiff2vips_strip_read( TIFF *tiff, int strip, tdata_t buf )
-{
- tsize_t length;
-
- length = TIFFReadEncodedStrip( tiff, strip, buf, (tsize_t) -1 );
- if( length == -1 ) {
- vips_error( "tiff2vips", "%s", _( "read error" ) );
- return( -1 );
- }
-
- return( 0 );
-}
-
/* Read a strip. If the image is in separate planes, read each plane and
* interleave to the output.
+ *
+ * strip is the number of this strip in this page.
*/
static int
-tiff2vips_strip_read_interleaved( ReadTiff *rtiff, int y, tdata_t buf )
+rtiff_strip_read_interleaved( Rtiff *rtiff, tstrip_t strip, tdata_t buf )
{
- tstrip_t strip = y / rtiff->rows_per_strip;
+ int samples_per_pixel = rtiff->header.samples_per_pixel;
+ int rows_per_strip = rtiff->header.rows_per_strip;
+ int bits_per_sample = rtiff->header.bits_per_sample;
+ int strip_y = strip * rows_per_strip;
- if( rtiff->separate ) {
- int strips_per_plane = 1 + (rtiff->out->Ysize - 1) /
- rtiff->rows_per_strip;
- int strip_height = VIPS_MIN( rtiff->rows_per_strip,
- rtiff->out->Ysize - y );
- int pels_per_strip = rtiff->out->Xsize * strip_height;
- int bytes_per_sample = rtiff->bits_per_sample >> 3;
+ if( rtiff->header.separate ) {
+ int page_width = rtiff->header.width;
+ int page_height = rtiff->header.height;
+ int strips_per_plane = 1 + (page_height - 1) / rows_per_strip;
+ int strip_height = VIPS_MIN( rows_per_strip,
+ page_height - strip_y );
+ int pels_per_strip = page_width * strip_height;
+ int bytes_per_sample = bits_per_sample >> 3;
int i, j, k;
- for( i = 0; i < rtiff->samples_per_pixel; i++ ) {
+ for( i = 0; i < samples_per_pixel; i++ ) {
VipsPel *p;
VipsPel *q;
- if( tiff2vips_strip_read( rtiff->tiff,
+ if( strip_read( rtiff->tiff,
strips_per_plane * i + strip,
rtiff->plane_buf ) )
return( -1 );
@@ -1567,13 +1652,12 @@ tiff2vips_strip_read_interleaved( ReadTiff *rtiff, int y, tdata_t buf )
q[k] = p[k];
p += bytes_per_sample;
- q += bytes_per_sample *
- rtiff->samples_per_pixel;
+ q += bytes_per_sample * samples_per_pixel;
}
}
}
else {
- if( tiff2vips_strip_read( rtiff->tiff, strip, buf ) )
+ if( strip_read( rtiff->tiff, strip, buf ) )
return( -1 );
}
@@ -1581,10 +1665,13 @@ tiff2vips_strip_read_interleaved( ReadTiff *rtiff, int y, tdata_t buf )
}
static int
-tiff2vips_stripwise_generate( VipsRegion *or,
+rtiff_stripwise_generate( VipsRegion *or,
void *seq, void *a, void *b, gboolean *stop )
{
- ReadTiff *rtiff = (ReadTiff *) a;
+ Rtiff *rtiff = (Rtiff *) a;
+ int rows_per_strip = rtiff->header.rows_per_strip;
+ int page_height = rtiff->header.height;
+ tsize_t scanline_size = TIFFScanlineSize( rtiff->tiff );
VipsRect *r = &or->valid;
int y;
@@ -1601,58 +1688,114 @@ tiff2vips_stripwise_generate( VipsRegion *or,
g_assert( r->width == or->im->Xsize );
g_assert( VIPS_RECT_BOTTOM( r ) <= or->im->Ysize );
- /* Tiles should always be on a strip boundary.
+ /* If we're reading more than one page, tiles won't fall on strip
+ * boundaries.
*/
- g_assert( r->top % rtiff->rows_per_strip == 0 );
/* Tiles should always be a strip in height, unless it's the final
- * strip.
+ * strip in the image.
*/
g_assert( r->height ==
- VIPS_MIN( rtiff->rows_per_strip, or->im->Ysize - r->top ) );
+ VIPS_MIN( rows_per_strip, or->im->Ysize - r->top ) );
- VIPS_GATE_START( "tiff2vips_stripwise_generate: work" );
+ VIPS_GATE_START( "rtiff_stripwise_generate: work" );
- for( y = 0; y < r->height; y += rtiff->rows_per_strip ) {
- tdata_t dst;
+ y = 0;
+ while( y < r->height ) {
+ /* Page number, position within this page.
+ */
+ int page_no = rtiff->page + (r->top + y) / page_height;
+ int y_page = (r->top + y) % page_height;
+
+ /* Strip number.
+ */
+ tstrip_t strip_no = y_page / rows_per_strip;
+
+ VipsRect image, page, strip, hit;
+
+ /* Our four (including the output region) rects, all in
+ * output image coordinates.
+ */
+ image.left = 0;
+ image.top = 0;
+ image.width = rtiff->out->Xsize;
+ image.height = rtiff->out->Ysize;
+
+ page.left = 0;
+ page.top = page_height * ((r->top + y) / page_height);
+ page.width = rtiff->out->Xsize;
+ page.height = page_height;
+
+ strip.left = 0;
+ strip.top = page.top + strip_no * rows_per_strip;
+ strip.width = rtiff->out->Xsize;
+ strip.height = rows_per_strip;
+
+ /* Clip strip against page and image ... the final strip will
+ * be smaller.
+ */
+ vips_rect_intersectrect( &strip, &image, &strip );
+ vips_rect_intersectrect( &strip, &page, &strip );
+
+ /* Now the bit that overlaps with the region we are filling.
+ */
+ vips_rect_intersectrect( &strip, r, &hit );
+
+ g_assert( hit.height > 0 );
+
+ if( rtiff_set_page( rtiff, page_no ) ) {
+ VIPS_GATE_STOP( "rtiff_stripwise_generate: work" );
+ return( -1 );
+ }
/* Read directly into the image if we can. Otherwise, we must
* read to a temp buffer then unpack into the image.
+ *
+ * We need to read via a buffer if we need to reformat pixels,
+ * or if this strip is not aligned on a tile boundary.
*/
- if( rtiff->memcpy )
- dst = VIPS_REGION_ADDR( or, 0, r->top + y );
- else
- dst = rtiff->contig_buf;
-
- if( tiff2vips_strip_read_interleaved( rtiff,
- r->top + y, dst ) ) {
- VIPS_GATE_STOP( "tiff2vips_stripwise_generate: work" );
- return( -1 );
+ if( rtiff->memcpy &&
+ hit.top == strip.top &&
+ hit.height == strip.height ) {
+ if( rtiff_strip_read_interleaved( rtiff, strip_no,
+ VIPS_REGION_ADDR( or, 0, r->top + y ) ) ) {
+ VIPS_GATE_STOP(
+ "rtiff_stripwise_generate: work" );
+ return( -1 );
+ }
}
-
- /* If necessary, unpack to destination.
- */
- if( !rtiff->memcpy ) {
- int height = VIPS_MIN( VIPS_MIN( rtiff->rows_per_strip,
- or->im->Ysize - (r->top + y) ), r->height );
-
+ else {
VipsPel *p;
VipsPel *q;
int z;
- p = rtiff->contig_buf;
+ /* Read and interleave the entire strip.
+ */
+ if( rtiff_strip_read_interleaved( rtiff, strip_no,
+ rtiff->contig_buf ) ) {
+ VIPS_GATE_STOP(
+ "rtiff_stripwise_generate: work" );
+ return( -1 );
+ }
+
+ /* Do any repacking to generate pixels in vips layout.
+ */
+ p = rtiff->contig_buf +
+ (hit.top - strip.top) * scanline_size;
q = VIPS_REGION_ADDR( or, 0, r->top + y );
- for( z = 0; z < height; z++ ) {
+ for( z = 0; z < hit.height; z++ ) {
rtiff->sfn( rtiff,
q, p, or->im->Xsize, rtiff->client );
- p += rtiff->scanline_size;
+ p += scanline_size;
q += VIPS_REGION_LSKIP( or );
}
}
+
+ y += hit.height;
}
- VIPS_GATE_STOP( "tiff2vips_stripwise_generate: work" );
+ VIPS_GATE_STOP( "rtiff_stripwise_generate: work" );
return( 0 );
}
@@ -1664,45 +1807,28 @@ tiff2vips_stripwise_generate( VipsRegion *or,
* large image. Only offer sequential read.
*/
static int
-read_stripwise( ReadTiff *rtiff, VipsImage *out )
+rtiff_read_stripwise( Rtiff *rtiff, VipsImage *out )
{
VipsImage **t = (VipsImage **)
vips_object_local_array( VIPS_OBJECT( out ), 3 );
#ifdef DEBUG
- printf( "tiff2vips: read_stripwise\n" );
+ printf( "tiff2vips: rtiff_read_stripwise\n" );
#endif /*DEBUG*/
t[0] = vips_image_new();
- if( parse_header( rtiff, t[0] ) )
+ if( rtiff_set_header( rtiff, t[0] ) )
return( -1 );
vips_image_pipelinev( t[0], VIPS_DEMAND_STYLE_THINSTRIP, NULL );
- if( !tfget32( rtiff->tiff,
- TIFFTAG_ROWSPERSTRIP, &rtiff->rows_per_strip ) )
- return( -1 );
- rtiff->scanline_size = TIFFScanlineSize( rtiff->tiff );
- rtiff->strip_size = TIFFStripSize( rtiff->tiff );
- rtiff->number_of_strips = TIFFNumberOfStrips( rtiff->tiff );
-
- /* rows_per_strip can be 2 ** 32 - 1, meaning the whole image. Clip
- * this down to ysize to avoid confusing vips.
- *
- * And it musn't be zero.
- */
- rtiff->rows_per_strip =
- VIPS_CLIP( 1, rtiff->rows_per_strip, t[0]->Ysize );
-
#ifdef DEBUG
- printf( "read_stripwise: rows_per_strip = %u\n",
- rtiff->rows_per_strip );
- printf( "read_stripwise: scanline_size = %zd\n",
- rtiff->scanline_size );
- printf( "read_stripwise: strip_size = %zd\n",
- rtiff->strip_size );
- printf( "read_stripwise: number_of_strips = %d\n",
- rtiff->number_of_strips );
+ 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 );
#endif /*DEBUG*/
/* Double check: in memcpy mode, the vips linesize should exactly
@@ -1713,13 +1839,13 @@ read_stripwise( ReadTiff *rtiff, VipsImage *out )
/* Lines are smaller in plane-separated mode.
*/
- if( rtiff->separate )
+ if( rtiff->header.separate )
vips_line_size = VIPS_IMAGE_SIZEOF_ELEMENT( t[0] ) *
t[0]->Xsize;
else
vips_line_size = VIPS_IMAGE_SIZEOF_LINE( t[0] );
- if( rtiff->scanline_size != vips_line_size ) {
+ if( vips_line_size != TIFFScanlineSize( rtiff->tiff ) ) {
vips_error( "tiff2vips",
"%s", _( "unsupported tiff image type" ) );
return( -1 );
@@ -1732,41 +1858,46 @@ read_stripwise( ReadTiff *rtiff, VipsImage *out )
* We don't need a separate buffer per thread since the _generate()
* function runs inside the cache lock.
*/
- if( rtiff->separate ) {
- if( !(rtiff->plane_buf =
- vips_malloc( VIPS_OBJECT( out ), rtiff->strip_size )) )
+ if( rtiff->header.separate ) {
+ if( !(rtiff->plane_buf = vips_malloc( VIPS_OBJECT( out ),
+ rtiff->header.strip_size )) )
return( -1 );
}
/* If we need to manipulate pixels, we must read to an interleaved
* plane buffer before repacking to the output.
*
+ * If we are doing a multi-page read, we need a strip buffer, since
+ * strips may not be aligned on tile boundaries.
+ *
* We don't need a separate buffer per thread since the _generate()
* function runs inside the cache lock.
*/
- if( !rtiff->memcpy ) {
+ if( !rtiff->memcpy ||
+ rtiff->n > 1 ) {
tsize_t size;
- size = rtiff->strip_size;
- if( rtiff->separate )
- size *= rtiff->samples_per_pixel;
+ size = rtiff->header.strip_size;
+ if( rtiff->header.separate )
+ size *= rtiff->header.samples_per_pixel;
if( !(rtiff->contig_buf =
vips_malloc( VIPS_OBJECT( out ), size )) )
return( -1 );
+
}
if(
vips_image_generate( t[0],
- NULL, tiff2vips_stripwise_generate, NULL,
+ NULL, rtiff_stripwise_generate, NULL,
rtiff, NULL ) ||
vips_sequential( t[0], &t[1],
- "tile_height", rtiff->rows_per_strip,
+ "tile_height", rtiff->header.rows_per_strip,
"access", rtiff->readbehind ?
VIPS_ACCESS_SEQUENTIAL :
VIPS_ACCESS_SEQUENTIAL_UNBUFFERED,
NULL ) ||
- vips_tiff_autorotate( rtiff, t[1], &t[2] ) ||
+ rtiff_autorotate( rtiff, t[1], &t[2] ) ||
vips_image_write( t[2], out ) )
return( -1 );
@@ -1776,44 +1907,42 @@ read_stripwise( ReadTiff *rtiff, VipsImage *out )
/* Can be called many times.
*/
static void
-readtiff_free( ReadTiff *rtiff )
+rtiff_free( Rtiff *rtiff )
{
VIPS_FREEF( TIFFClose, rtiff->tiff );
}
static void
-readtiff_close( VipsObject *object, ReadTiff *rtiff )
+rtiff_close( VipsObject *object, Rtiff *rtiff )
{
- readtiff_free( rtiff );
+ rtiff_free( rtiff );
}
-static ReadTiff *
-readtiff_new( VipsImage *out,
- int page, gboolean autorotate, gboolean readbehind )
+static Rtiff *
+rtiff_new( VipsImage *out,
+ int page, int n, gboolean autorotate, gboolean readbehind )
{
- ReadTiff *rtiff;
+ Rtiff *rtiff;
- if( !(rtiff = VIPS_NEW( out, ReadTiff )) )
+ if( !(rtiff = VIPS_NEW( out, Rtiff )) )
return( NULL );
rtiff->filename = NULL;
rtiff->out = out;
rtiff->page = page;
+ rtiff->n = n;
rtiff->autorotate = autorotate;
rtiff->readbehind = readbehind;
rtiff->tiff = NULL;
+ rtiff->current_page = -1;
rtiff->sfn = NULL;
rtiff->client = NULL;
rtiff->memcpy = FALSE;
- rtiff->pos = 0;
- rtiff->twidth = 0;
- rtiff->theight = 0;
- rtiff->separate = FALSE;
rtiff->plane_buf = NULL;
rtiff->contig_buf = NULL;
g_signal_connect( out, "close",
- G_CALLBACK( readtiff_close ), rtiff );
+ G_CALLBACK( rtiff_close ), rtiff );
if( rtiff->page < 0 || rtiff->page > 1000000 ) {
vips_error( "tiff2vips", _( "bad page number %d" ),
@@ -1821,62 +1950,200 @@ readtiff_new( VipsImage *out,
return( NULL );
}
+ /* We allow n == -1, meaning all pages. It gets swapped for a real n
+ * value when we open the TIFF.
+ */
+ if( rtiff->n != -1 &&
+ (rtiff->n < 1 || rtiff->n > 1000000) ) {
+ vips_error( "tiff2vips", _( "bad number of pages %d" ),
+ rtiff->n );
+ return( NULL );
+ }
+
return( rtiff );
}
-static ReadTiff *
-readtiff_new_filename( const char *filename, VipsImage *out,
- int page, gboolean autorotate, gboolean readbehind )
+/* Load from a tiff dir into one of our tiff header structs.
+ */
+static int
+rtiff_header_read( Rtiff *rtiff, RtiffHeader *header )
{
- ReadTiff *rtiff;
- int i;
+ if( !tfget32( rtiff->tiff, TIFFTAG_IMAGEWIDTH, &header->width ) ||
+ !tfget32( rtiff->tiff, TIFFTAG_IMAGELENGTH, &header->height ) ||
+ !tfget16( rtiff->tiff,
+ TIFFTAG_SAMPLESPERPIXEL, &header->samples_per_pixel ) ||
+ !tfget16( rtiff->tiff,
+ TIFFTAG_BITSPERSAMPLE, &header->bits_per_sample ) ||
+ !tfget16( rtiff->tiff,
+ TIFFTAG_PHOTOMETRIC,
+ &header->photometric_interpretation ) )
+ return( -1 );
- if( !(rtiff = readtiff_new( out, page, autorotate, readbehind )) )
+ /* Arbitrary sanity-checking limits.
+ */
+ if( header->width <= 0 ||
+ header->width > VIPS_MAX_COORD ||
+ header->height <= 0 ||
+ header->height > VIPS_MAX_COORD ) {
+ vips_error( "tiff2vips",
+ "%s", _( "width/height out of range" ) );
+ return( -1 );
+ }
+
+ if( header->samples_per_pixel <= 0 ||
+ header->samples_per_pixel > 10000 ||
+ header->bits_per_sample <= 0 ||
+ header->bits_per_sample > 32 ) {
+ vips_error( "tiff2vips",
+ "%s", _( "samples out of range" ) );
+ return( -1 );
+ }
+
+ header->sample_format = get_sample_format( rtiff->tiff );
+ header->orientation = get_orientation( rtiff->tiff );
+
+ header->separate = FALSE;
+ if( tfexists( rtiff->tiff, TIFFTAG_PLANARCONFIG ) ) {
+ int v;
+
+ if( !tfget16( rtiff->tiff, TIFFTAG_PLANARCONFIG, &v ) )
+ return( -1 );
+ if( v == PLANARCONFIG_SEPARATE )
+ header->separate = TRUE;
+ }
+
+ /* Tiles and strip images have slightly different fields.
+ */
+ header->tiled = TIFFIsTiled( rtiff->tiff );
+
+ if( header->tiled ) {
+ if( !tfget32( rtiff->tiff,
+ TIFFTAG_TILEWIDTH, &header->tile_width ) ||
+ !tfget32( rtiff->tiff,
+ TIFFTAG_TILELENGTH, &header->tile_height ) )
+ return( -1 );
+ }
+ else {
+ if( !tfget32( rtiff->tiff,
+ TIFFTAG_ROWSPERSTRIP, &header->rows_per_strip ) )
+ return( -1 );
+ header->strip_size = TIFFStripSize( rtiff->tiff );
+ header->number_of_strips = TIFFNumberOfStrips( rtiff->tiff );
+
+ /* rows_per_strip can be 2 ** 32 - 1, meaning the whole image.
+ * Clip this down to height to avoid confusing vips.
+ *
+ * And it musn't be zero.
+ */
+ header->rows_per_strip =
+ VIPS_CLIP( 1, header->rows_per_strip, header->height );
+ }
+
+ return( 0 );
+}
+
+static int
+rtiff_header_equal( RtiffHeader *h1, RtiffHeader *h2 )
+{
+ if( h1->width != h2->width ||
+ h1->height != h2->height ||
+ h1->samples_per_pixel != h2->samples_per_pixel ||
+ h1->bits_per_sample != h2->bits_per_sample ||
+ h1->photometric_interpretation !=
+ h2->photometric_interpretation ||
+ h1->sample_format != h2->sample_format ||
+ h1->separate != h2->separate ||
+ h1->tiled != h2->tiled ||
+ h1->orientation != h2->orientation )
+ return( 0 );
+
+ if( h1->tiled ) {
+ if( h1->tile_width != h2->tile_width ||
+ h1->tile_height != h2->tile_height )
+ return( 0 );
+ }
+ else {
+ if( h1->rows_per_strip != h2->rows_per_strip ||
+ h1->strip_size != h2->strip_size ||
+ h1->number_of_strips != h2->number_of_strips )
+ return( 0 );
+ }
+
+ return( 1 );
+}
+
+static int
+rtiff_header_read_all( Rtiff *rtiff )
+{
+#ifdef DEBUG
+ printf( "tiff2vips: reading header for page %d ...\n", rtiff->page );
+#endif /*DEBUG*/
+
+ if( rtiff_set_page( rtiff, rtiff->page ) ||
+ rtiff_header_read( rtiff, &rtiff->header ) )
+ return( -1 );
+
+ /* -1 means "to the end".
+ */
+ if( rtiff->n == -1 )
+ rtiff->n = rtiff_n_pages( rtiff ) - rtiff->page;
+
+ /* If we're to read many pages, verify that they are all identical.
+ */
+ if( rtiff->n > 1 ) {
+ int i;
+
+ for( i = 1; i < rtiff->n; i++ ) {
+ RtiffHeader header;
+
+#ifdef DEBUG
+ printf( "tiff2vips: verifying header for page %d ...\n",
+ rtiff->page + i );
+#endif /*DEBUG*/
+
+ if( rtiff_set_page( rtiff, rtiff->page + i ) ||
+ rtiff_header_read( rtiff, &header ) )
+ return( -1 );
+
+ if( !rtiff_header_equal( &rtiff->header, &header ) ) {
+ vips_error( "tiff2vips",
+ _( "page %d differs from page %d" ),
+ rtiff->page + i, rtiff->page );
+ return( -1 );
+ }
+ }
+ }
+
+ return( 0 );
+}
+
+static Rtiff *
+rtiff_new_filename( const char *filename, VipsImage *out,
+ int page, int n, gboolean autorotate, gboolean readbehind )
+{
+ Rtiff *rtiff;
+
+ if( !(rtiff = rtiff_new( out, page, n, autorotate, readbehind )) ||
+ !(rtiff->tiff = vips__tiff_openin( filename )) ||
+ rtiff_header_read_all( rtiff ) )
return( NULL );
rtiff->filename = vips_strdup( VIPS_OBJECT( out ), filename );
- /* No mmap --- no performance advantage with libtiff, and it burns up
- * our VM if the tiff file is large.
- */
- if( !(rtiff->tiff = vips__tiff_openin( filename )) ) {
- vips_error( "tiff2vips", _( "unable to open \"%s\" for input" ),
- filename );
- return( NULL );
- }
-
- for( i = 0; i < page; i++ )
- if( !TIFFReadDirectory( rtiff->tiff ) ) {
- vips_error( "tiff2vips",
- _( "TIFF does not contain page %d" ),
- rtiff->page );
- return( NULL );
- }
-
return( rtiff );
}
-static ReadTiff *
-readtiff_new_buffer( const void *buf, size_t len, VipsImage *out,
- int page, gboolean autorotate, gboolean readbehind )
+static Rtiff *
+rtiff_new_buffer( const void *buf, size_t len, VipsImage *out,
+ int page, int n, gboolean autorotate, gboolean readbehind )
{
- ReadTiff *rtiff;
- int i;
+ Rtiff *rtiff;
- if( !(rtiff = readtiff_new( out, page, autorotate, readbehind )) )
+ if( !(rtiff = rtiff_new( out, page, n, autorotate, readbehind )) ||
+ !(rtiff->tiff = vips__tiff_openin_buffer( out, buf, len )) ||
+ rtiff_header_read_all( rtiff ) )
return( NULL );
- if( !(rtiff->tiff = vips__tiff_openin_buffer( out, buf, len )) )
- return( NULL );
-
- for( i = 0; i < page; i++ )
- if( !TIFFReadDirectory( rtiff->tiff ) ) {
- vips_error( "tiff2vips",
- _( "TIFF does not contain page %d" ),
- rtiff->page );
- return( NULL );
- }
-
return( rtiff );
}
@@ -1903,9 +2170,9 @@ istiffpyramid( const char *name )
int
vips__tiff_read( const char *filename, VipsImage *out,
- int page, gboolean autorotate, gboolean readbehind )
+ int page, int n, gboolean autorotate, gboolean readbehind )
{
- ReadTiff *rtiff;
+ Rtiff *rtiff;
#ifdef DEBUG
printf( "tiff2vips: libtiff version is \"%s\"\n", TIFFGetVersion() );
@@ -1914,16 +2181,16 @@ vips__tiff_read( const char *filename, VipsImage *out,
vips__tiff_init();
- if( !(rtiff = readtiff_new_filename( filename,
- out, page, autorotate, readbehind )) )
+ if( !(rtiff = rtiff_new_filename( filename,
+ out, page, n, autorotate, readbehind )) )
return( -1 );
- if( TIFFIsTiled( rtiff->tiff ) ) {
- if( read_tilewise( rtiff, out ) )
+ if( rtiff->header.tiled ) {
+ if( rtiff_read_tilewise( rtiff, out ) )
return( -1 );
}
else {
- if( read_stripwise( rtiff, out ) )
+ if( rtiff_read_stripwise( rtiff, out ) )
return( -1 );
}
@@ -1934,7 +2201,7 @@ vips__tiff_read( const char *filename, VipsImage *out,
* 8.
*/
static void
-vips__tiff_read_header_orientation( ReadTiff *rtiff, VipsImage *out )
+vips__tiff_read_header_orientation( Rtiff *rtiff, VipsImage *out )
{
int orientation;
@@ -1955,24 +2222,24 @@ vips__tiff_read_header_orientation( ReadTiff *rtiff, VipsImage *out )
int
vips__tiff_read_header( const char *filename, VipsImage *out,
- int page, gboolean autorotate )
+ int page, int n, gboolean autorotate )
{
- ReadTiff *rtiff;
+ Rtiff *rtiff;
vips__tiff_init();
- if( !(rtiff = readtiff_new_filename( filename, out,
- page, autorotate, FALSE )) )
+ if( !(rtiff = rtiff_new_filename( filename, out,
+ page, n, autorotate, FALSE )) )
return( -1 );
- if( parse_header( rtiff, out ) )
+ if( rtiff_set_header( rtiff, out ) )
return( -1 );
vips__tiff_read_header_orientation( rtiff, out );
/* Just a header read: we can free the tiff read early and save an fd.
*/
- readtiff_free( rtiff );
+ rtiff_free( rtiff );
return( 0 );
}
@@ -2024,17 +2291,17 @@ vips__istiff( const char *filename )
int
vips__tiff_read_header_buffer( const void *buf, size_t len, VipsImage *out,
- int page, gboolean autorotate )
+ int page, int n, gboolean autorotate )
{
- ReadTiff *rtiff;
+ Rtiff *rtiff;
vips__tiff_init();
- if( !(rtiff = readtiff_new_buffer( buf, len, out,
- page, autorotate, FALSE )) )
+ if( !(rtiff = rtiff_new_buffer( buf, len, out,
+ page, n, autorotate, FALSE )) )
return( -1 );
- if( parse_header( rtiff, out ) )
+ if( rtiff_set_header( rtiff, out ) )
return( -1 );
vips__tiff_read_header_orientation( rtiff, out );
@@ -2044,9 +2311,10 @@ vips__tiff_read_header_buffer( const void *buf, size_t len, VipsImage *out,
int
vips__tiff_read_buffer( const void *buf, size_t len,
- VipsImage *out, int page, gboolean autorotate, gboolean readbehind )
+ VipsImage *out, int page, int n, gboolean autorotate,
+ gboolean readbehind )
{
- ReadTiff *rtiff;
+ Rtiff *rtiff;
#ifdef DEBUG
printf( "tiff2vips: libtiff version is \"%s\"\n", TIFFGetVersion() );
@@ -2055,16 +2323,16 @@ vips__tiff_read_buffer( const void *buf, size_t len,
vips__tiff_init();
- if( !(rtiff = readtiff_new_buffer( buf, len, out,
- page, autorotate, readbehind )) )
+ if( !(rtiff = rtiff_new_buffer( buf, len, out,
+ page, n, autorotate, readbehind )) )
return( -1 );
- if( TIFFIsTiled( rtiff->tiff ) ) {
- if( read_tilewise( rtiff, out ) )
+ if( rtiff->header.tiled ) {
+ if( rtiff_read_tilewise( rtiff, out ) )
return( -1 );
}
else {
- if( read_stripwise( rtiff, out ) )
+ if( rtiff_read_stripwise( rtiff, out ) )
return( -1 );
}
diff --git a/libvips/foreign/tiffload.c b/libvips/foreign/tiffload.c
index d5aec59b..29fd58c4 100644
--- a/libvips/foreign/tiffload.c
+++ b/libvips/foreign/tiffload.c
@@ -59,6 +59,10 @@ typedef struct _VipsForeignLoadTiff {
*/
int page;
+ /* Load this many pages.
+ */
+ int n;
+
/* Autorotate using orientation tag.
*/
gboolean autorotate;
@@ -96,7 +100,14 @@ vips_foreign_load_tiff_class_init( VipsForeignLoadTiffClass *class )
G_STRUCT_OFFSET( VipsForeignLoadTiff, page ),
0, 100000, 0 );
- VIPS_ARG_BOOL( class, "autorotate", 11,
+ VIPS_ARG_INT( class, "n", 11,
+ _( "n" ),
+ _( "Load this many pages" ),
+ VIPS_ARGUMENT_OPTIONAL_INPUT,
+ G_STRUCT_OFFSET( VipsForeignLoadTiff, n ),
+ -1, 100000, 1 );
+
+ VIPS_ARG_BOOL( class, "autorotate", 12,
_( "Autorotate" ),
_( "Rotate image using orientation tag" ),
VIPS_ARGUMENT_OPTIONAL_INPUT,
@@ -108,6 +119,7 @@ static void
vips_foreign_load_tiff_init( VipsForeignLoadTiff *tiff )
{
tiff->page = 0;
+ tiff->n = 1;
}
typedef struct _VipsForeignLoadTiffFile {
@@ -154,7 +166,7 @@ vips_foreign_load_tiff_file_header( VipsForeignLoad *load )
VipsForeignLoadTiffFile *file = (VipsForeignLoadTiffFile *) load;
if( vips__tiff_read_header( file->filename, load->out,
- tiff->page, tiff->autorotate ) )
+ tiff->page, tiff->n, tiff->autorotate ) )
return( -1 );
VIPS_SETSTR( load->out->filename, file->filename );
@@ -168,8 +180,9 @@ vips_foreign_load_tiff_file_load( VipsForeignLoad *load )
VipsForeignLoadTiff *tiff = (VipsForeignLoadTiff *) load;
VipsForeignLoadTiffFile *file = (VipsForeignLoadTiffFile *) load;
- if( vips__tiff_read( file->filename, load->real, tiff->page,
- tiff->autorotate, load->access == VIPS_ACCESS_SEQUENTIAL ) )
+ if( vips__tiff_read( file->filename, load->real,
+ tiff->page, tiff->n, tiff->autorotate,
+ load->access == VIPS_ACCESS_SEQUENTIAL ) )
return( -1 );
return( 0 );
@@ -239,7 +252,7 @@ vips_foreign_load_tiff_buffer_header( VipsForeignLoad *load )
if( vips__tiff_read_header_buffer(
buffer->buf->data, buffer->buf->length, load->out,
- tiff->page, tiff->autorotate ) )
+ tiff->page, tiff->n, tiff->autorotate ) )
return( -1 );
return( 0 );
@@ -253,7 +266,7 @@ vips_foreign_load_tiff_buffer_load( VipsForeignLoad *load )
if( vips__tiff_read_buffer(
buffer->buf->data, buffer->buf->length, load->real,
- tiff->page, tiff->autorotate,
+ tiff->page, tiff->n, tiff->autorotate,
load->access == VIPS_ACCESS_SEQUENTIAL ) )
return( -1 );
@@ -302,6 +315,7 @@ vips_foreign_load_tiff_buffer_init( VipsForeignLoadTiffBuffer *buffer )
* Optional arguments:
*
* * @page: %gint, load this page
+ * * @n: %gint, load this many pages
* * @autorotate: %gboolean, use orientation tag to rotate the image
* during load
*
@@ -310,7 +324,12 @@ vips_foreign_load_tiff_buffer_init( VipsForeignLoadTiffBuffer *buffer )
* pyramidal images and JPEG compression. including CMYK and YCbCr.
*
* @page means load this page from the file. By default the first page (page
- * 0) is read.
+ * 0) is read.
+ *
+ * @n means load this many pages. By default a single page is read. All the
+ * pages must have the same dimensions, and they are loaded as a tall, thin
+ * "toilet roll" image. The #VIPS_META_PAGE_HEIGHT metadata
+ * tag gives the height in pixels of each page. Use -1 to load all pages.
*
* Setting @autorotate to %TRUE will make the loader interpret the
* orientation tag and automatically rotate the image appropriately during
@@ -357,6 +376,7 @@ vips_tiffload( const char *filename, VipsImage **out, ... )
* Optional arguments:
*
* * @page: %gint, load this page
+ * * @n: %gint, load this many pages
* * @autorotate: %gboolean, use orientation tag to rotate the image
* during load
*
diff --git a/libvips/foreign/tiffsave.c b/libvips/foreign/tiffsave.c
index bfbc9f26..7e2db098 100644
--- a/libvips/foreign/tiffsave.c
+++ b/libvips/foreign/tiffsave.c
@@ -475,6 +475,10 @@ vips_foreign_save_tiff_buffer_init( VipsForeignSaveTiffBuffer *buffer )
*
* Write a VIPS image to a file as TIFF.
*
+ * If @in has the #VIPS_META_PAGE_HEIGHT metadata item, this is assumed to be a
+ * "toilet roll" image. It will be
+ * written as series of pages, each #VIPS_META_PAGE_HEIGHT pixels high.
+ *
* Use @compression to set the tiff compression. Currently jpeg, packbits,
* fax4, lzw, none and deflate are supported. The default is no compression.
* JPEG compression is a good lossy compressor for photographs, packbits is
diff --git a/libvips/foreign/vips2jpeg.c b/libvips/foreign/vips2jpeg.c
index b4e56e17..7351a5fa 100644
--- a/libvips/foreign/vips2jpeg.c
+++ b/libvips/foreign/vips2jpeg.c
@@ -244,7 +244,7 @@ write_blob( Write *write, const char *field, int app )
* For now, just ignore oversize objects and warn.
*/
if( data_length > 65530 )
- vips_warn( "VipsJpeg", _( "field \"%s\" is too large "
+ g_warning( _( "field \"%s\" is too large "
"for a single JPEG marker, ignoring" ),
field );
else {
@@ -486,8 +486,7 @@ write_vips( Write *write, int qfac, const char *profile,
write->cinfo.optimize_coding = TRUE;
}
else
- vips_warn( "vips2jpeg",
- "%s", _( "trellis_quant unsupported" ) );
+ g_warning( "%s", _( "trellis_quant unsupported" ) );
}
/* Apply overshooting to samples with extreme values e.g. 0 & 255
@@ -499,8 +498,8 @@ write_vips( Write *write, int qfac, const char *profile,
jpeg_c_set_bool_param( &write->cinfo,
JBOOLEAN_OVERSHOOT_DERINGING, TRUE );
else
- vips_warn( "vips2jpeg",
- "%s", _( "overshoot_deringing unsupported" ) );
+ g_warning( "%s",
+ _( "overshoot_deringing unsupported" ) );
}
/* Split the spectrum of DCT coefficients into separate scans.
* Requires progressive output. Must be set before
@@ -513,12 +512,12 @@ write_vips( Write *write, int qfac, const char *profile,
jpeg_c_set_bool_param( &write->cinfo,
JBOOLEAN_OPTIMIZE_SCANS, TRUE );
else
- vips_warn( "vips2jpeg",
- "%s", _( "Ignoring optimize_scans" ) );
+ g_warning( "%s",
+ _( "ignoring optimize_scans" ) );
}
else
- vips_warn( "vips2jpeg", "%s",
- _( "Ignoring optimize_scans for baseline" ) );
+ g_warning( "%s",
+ _( "ignoring optimize_scans for baseline" ) );
}
/* Use predefined quantization table.
@@ -529,22 +528,21 @@ write_vips( Write *write, int qfac, const char *profile,
jpeg_c_set_int_param( &write->cinfo,
JINT_BASE_QUANT_TBL_IDX, quant_table );
else
- vips_warn( "vips2jpeg",
- "%s", _( "Setting quant_table unsupported" ) );
+ g_warning( "%s",
+ _( "setting quant_table unsupported" ) );
}
#else
/* Using jpeglib.h without extension parameters, warn of ignored
* options.
*/
if( trellis_quant )
- vips_warn( "vips2jpeg", "%s", _( "Ignoring trellis_quant" ) );
+ g_warning( "%s", _( "ignoring trellis_quant" ) );
if( overshoot_deringing )
- vips_warn( "vips2jpeg",
- "%s", _( "Ignoring overshoot_deringing" ) );
+ g_warning( "%s", _( "ignoring overshoot_deringing" ) );
if( optimize_scans )
- vips_warn( "vips2jpeg", "%s", _( "Ignoring optimize_scans" ) );
+ g_warning( "%s", _( "ignoring optimize_scans" ) );
if( quant_table > 0 )
- vips_warn( "vips2jpeg", "%s", _( "Ignoring quant_table" ) );
+ g_warning( "%s", _( "ignoring quant_table" ) );
#endif
/* Set compression quality. Must be called after setting params above.
diff --git a/libvips/foreign/vips2tiff.c b/libvips/foreign/vips2tiff.c
index ce4162d4..3f3858cf 100644
--- a/libvips/foreign/vips2tiff.c
+++ b/libvips/foreign/vips2tiff.c
@@ -143,7 +143,7 @@
* 3/12/14
* - embed XMP in output
* 10/12/14
- * - zero out edge tile buffers before jpeg write, thanks iwbh15
+ * - zero out edge tile buffers before jpeg wtiff, thanks iwbh15
* 19/1/15
* - disable chroma subsample if Q >= 90
* 13/2/15
@@ -228,12 +228,12 @@
#define MAX_ALPHA (64)
typedef struct _Layer Layer;
-typedef struct _Write Write;
+typedef struct _Wtiff Wtiff;
/* A layer in the pyramid.
*/
struct _Layer {
- Write *write; /* Main write struct */
+ Wtiff *wtiff; /* Main wtiff struct */
/* The filename for this layer, for file output.
*/
@@ -270,7 +270,7 @@ struct _Layer {
/* A TIFF image in the process of being written.
*/
-struct _Write {
+struct _Wtiff {
VipsImage *im; /* Original input image */
/* File to write to, or NULL.
@@ -291,9 +291,9 @@ struct _Write {
int predictor; /* Predictor value */
int tile; /* Tile or not */
int tilew, tileh; /* Tile size */
- int pyramid; /* Write pyramid */
- int onebit; /* Write as 1-bit TIFF */
- int miniswhite; /* Write as 0 == white */
+ int pyramid; /* Wtiff pyramid */
+ int onebit; /* Wtiff as 1-bit TIFF */
+ int miniswhite; /* Wtiff as 0 == white */
int resunit; /* Resolution unit (inches or cm) */
double xres; /* Resolution in X */
double yres; /* Resolution in Y */
@@ -302,65 +302,18 @@ struct _Write {
int rgbjpeg; /* True for RGB not YCbCr */
int properties; /* Set to save XML props */
int strip; /* Don't write metadata */
-};
-static Layer *
-pyramid_new( Write *write, Layer *above, int width, int height )
-{
- Layer *layer;
-
- layer = VIPS_NEW( write->im, Layer );
- layer->write = write;
- layer->width = width;
- layer->height = height;
-
- if( !above )
- /* Top of pyramid.
- */
- layer->sub = 1;
- else
- layer->sub = above->sub * 2;
-
- layer->lname = NULL;
- layer->buf = NULL;
- layer->len = 0;
- layer->tif = NULL;
- layer->image = NULL;
- layer->write_y = 0;
- layer->y = 0;
- layer->strip = NULL;
- layer->copy = NULL;
-
- layer->below = NULL;
- layer->above = above;
-
- if( write->pyramid )
- if( layer->width > write->tilew ||
- layer->height > write->tileh )
- layer->below = pyramid_new( write, layer,
- width / 2, height / 2 );
-
- /* The name for the top layer is the output filename.
- *
- * We need lname to be freed automatically: it has to stay
- * alive until after write_gather().
+ /* True if we've detected a toilet-roll image, plus the page height,
+ * which has been checked to be a factor of im->Ysize.
*/
- if( write->filename ) {
- if( !above )
- layer->lname = vips_strdup( VIPS_OBJECT( write->im ),
- write->filename );
- else {
- char *lname;
+ gboolean toilet_roll;
+ int page_height;
- lname = vips__temp_name( "%s.tif" );
- layer->lname =
- vips_strdup( VIPS_OBJECT( write->im ), lname );
- g_free( lname );
- }
- }
-
- return( layer );
-}
+ /* The height of the TIFF we write. Equal to page_height in toilet
+ * roll mode.
+ */
+ int image_height;
+};
/* Embed an ICC profile from a file.
*/
@@ -401,33 +354,89 @@ embed_profile_meta( TIFF *tif, VipsImage *im )
return( 0 );
}
-static int
-write_embed_profile( Write *write, TIFF *tif )
+static Layer *
+wtiff_layer_new( Wtiff *wtiff, Layer *above, int width, int height )
{
- if( write->icc_profile &&
- strcmp( write->icc_profile, "none" ) != 0 &&
- embed_profile_file( tif, write->icc_profile ) )
+ Layer *layer;
+
+ layer = VIPS_NEW( wtiff->im, Layer );
+ layer->wtiff = wtiff;
+ layer->width = width;
+ layer->height = height;
+
+ if( !above )
+ /* Top of pyramid.
+ */
+ layer->sub = 1;
+ else
+ layer->sub = above->sub * 2;
+
+ layer->lname = NULL;
+ layer->buf = NULL;
+ layer->len = 0;
+ layer->tif = NULL;
+ layer->image = NULL;
+ layer->write_y = 0;
+ layer->y = 0;
+ layer->strip = NULL;
+ layer->copy = NULL;
+
+ layer->below = NULL;
+ layer->above = above;
+
+ if( wtiff->pyramid )
+ if( layer->width > wtiff->tilew ||
+ layer->height > wtiff->tileh )
+ layer->below = wtiff_layer_new( wtiff, layer,
+ width / 2, height / 2 );
+
+ /* The name for the top layer is the output filename.
+ *
+ * We need lname to be freed automatically: it has to stay
+ * alive until after wtiff_gather().
+ */
+ if( wtiff->filename ) {
+ if( !above )
+ layer->lname = vips_strdup( VIPS_OBJECT( wtiff->im ),
+ wtiff->filename );
+ else {
+ char *lname;
+
+ lname = vips__temp_name( "%s.tif" );
+ layer->lname =
+ vips_strdup( VIPS_OBJECT( wtiff->im ), lname );
+ g_free( lname );
+ }
+ }
+
+ return( layer );
+}
+
+static int
+wtiff_embed_profile( Wtiff *wtiff, TIFF *tif )
+{
+ if( wtiff->icc_profile &&
+ strcmp( wtiff->icc_profile, "none" ) != 0 &&
+ embed_profile_file( tif, wtiff->icc_profile ) )
return( -1 );
- if( !write->icc_profile &&
- vips_image_get_typeof( write->im, VIPS_META_ICC_NAME ) &&
- embed_profile_meta( tif, write->im ) )
+ if( !wtiff->icc_profile &&
+ vips_image_get_typeof( wtiff->im, VIPS_META_ICC_NAME ) &&
+ embed_profile_meta( tif, wtiff->im ) )
return( -1 );
return( 0 );
}
-/* Embed any XMP metadata.
- */
static int
-write_embed_xmp( Write *write, TIFF *tif )
+wtiff_embed_xmp( Wtiff *wtiff, TIFF *tif )
{
void *data;
size_t data_length;
- if( !vips_image_get_typeof( write->im, VIPS_META_XMP_NAME ) )
+ if( !vips_image_get_typeof( wtiff->im, VIPS_META_XMP_NAME ) )
return( 0 );
- if( vips_image_get_blob( write->im, VIPS_META_XMP_NAME,
+ if( vips_image_get_blob( wtiff->im, VIPS_META_XMP_NAME,
&data, &data_length ) )
return( -1 );
TIFFSetField( tif, TIFFTAG_XMLPACKET, data_length, data );
@@ -439,17 +448,15 @@ write_embed_xmp( Write *write, TIFF *tif )
return( 0 );
}
-/* Embed any IPCT metadata.
- */
static int
-write_embed_ipct( Write *write, TIFF *tif )
+wtiff_embed_ipct( Wtiff *wtiff, TIFF *tif )
{
void *data;
size_t data_length;
- if( !vips_image_get_typeof( write->im, VIPS_META_IPCT_NAME ) )
+ if( !vips_image_get_typeof( wtiff->im, VIPS_META_IPCT_NAME ) )
return( 0 );
- if( vips_image_get_blob( write->im, VIPS_META_IPCT_NAME,
+ if( vips_image_get_blob( wtiff->im, VIPS_META_IPCT_NAME,
&data, &data_length ) )
return( -1 );
@@ -457,8 +464,7 @@ write_embed_ipct( Write *write, TIFF *tif )
* long, not byte.
*/
if( data_length & 3 ) {
- vips_warn( "vips2tiff",
- "%s", _( "rounding up IPCT data length" ) );
+ g_warning( "%s", _( "rounding up IPCT data length" ) );
data_length /= 4;
data_length += 1;
}
@@ -474,17 +480,15 @@ write_embed_ipct( Write *write, TIFF *tif )
return( 0 );
}
-/* Embed any XMP metadata.
- */
static int
-write_embed_photoshop( Write *write, TIFF *tif )
+wtiff_embed_photoshop( Wtiff *wtiff, TIFF *tif )
{
void *data;
size_t data_length;
- if( !vips_image_get_typeof( write->im, VIPS_META_PHOTOSHOP_NAME ) )
+ if( !vips_image_get_typeof( wtiff->im, VIPS_META_PHOTOSHOP_NAME ) )
return( 0 );
- if( vips_image_get_blob( write->im,
+ if( vips_image_get_blob( wtiff->im,
VIPS_META_PHOTOSHOP_NAME, &data, &data_length ) )
return( -1 );
TIFFSetField( tif, TIFFTAG_PHOTOSHOP, data_length, data );
@@ -500,12 +504,12 @@ write_embed_photoshop( Write *write, TIFF *tif )
* vips' metadata.
*/
static int
-write_embed_imagedescription( Write *write, TIFF *tif )
+wtiff_embed_imagedescription( Wtiff *wtiff, TIFF *tif )
{
- if( write->properties ) {
+ if( wtiff->properties ) {
char *doc;
- if( !(doc = vips__make_xml_metadata( "vips2tiff", write->im )) )
+ if( !(doc = vips__make_xml_metadata( "vips2tiff", wtiff->im )) )
return( -1 );
TIFFSetField( tif, TIFFTAG_IMAGEDESCRIPTION, doc );
xmlFree( doc );
@@ -513,10 +517,10 @@ write_embed_imagedescription( Write *write, TIFF *tif )
else {
const char *imagedescription;
- if( !vips_image_get_typeof( write->im,
+ if( !vips_image_get_typeof( wtiff->im,
VIPS_META_IMAGEDESCRIPTION ) )
return( 0 );
- if( vips_image_get_string( write->im,
+ if( vips_image_get_string( wtiff->im,
VIPS_META_IMAGEDESCRIPTION, &imagedescription ) )
return( -1 );
TIFFSetField( tif, TIFFTAG_IMAGEDESCRIPTION, imagedescription );
@@ -529,11 +533,10 @@ write_embed_imagedescription( Write *write, TIFF *tif )
return( 0 );
}
-/* Write a TIFF header. width and height are the size of the VipsImage we are
- * writing (it may have been shrunk).
+/* Write a TIFF header for this layer.
*/
static int
-write_tiff_header( Write *write, Layer *layer )
+wtiff_write_header( Wtiff *wtiff, Layer *layer )
{
TIFF *tif = layer->tif;
@@ -546,47 +549,47 @@ write_tiff_header( Write *write, Layer *layer )
TIFFSetField( tif, TIFFTAG_IMAGELENGTH, layer->height );
TIFFSetField( tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG );
TIFFSetField( tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT );
- TIFFSetField( tif, TIFFTAG_COMPRESSION, write->compression );
+ TIFFSetField( tif, TIFFTAG_COMPRESSION, wtiff->compression );
- if( write->compression == COMPRESSION_JPEG )
- TIFFSetField( tif, TIFFTAG_JPEGQUALITY, write->jpqual );
+ if( wtiff->compression == COMPRESSION_JPEG )
+ TIFFSetField( tif, TIFFTAG_JPEGQUALITY, wtiff->jpqual );
- if( write->predictor != VIPS_FOREIGN_TIFF_PREDICTOR_NONE )
- TIFFSetField( tif, TIFFTAG_PREDICTOR, write->predictor );
+ if( wtiff->predictor != VIPS_FOREIGN_TIFF_PREDICTOR_NONE )
+ TIFFSetField( tif, TIFFTAG_PREDICTOR, wtiff->predictor );
/* Don't write mad resolutions (eg. zero), it confuses some programs.
*/
- TIFFSetField( tif, TIFFTAG_RESOLUTIONUNIT, write->resunit );
+ TIFFSetField( tif, TIFFTAG_RESOLUTIONUNIT, wtiff->resunit );
TIFFSetField( tif, TIFFTAG_XRESOLUTION,
- VIPS_FCLIP( 0.01, write->xres, 1000000 ) );
+ VIPS_FCLIP( 0.01, wtiff->xres, 1000000 ) );
TIFFSetField( tif, TIFFTAG_YRESOLUTION,
- VIPS_FCLIP( 0.01, write->yres, 1000000 ) );
+ VIPS_FCLIP( 0.01, wtiff->yres, 1000000 ) );
- if( !write->strip )
- if( write_embed_profile( write, tif ) ||
- write_embed_xmp( write, tif ) ||
- write_embed_ipct( write, tif ) ||
- write_embed_photoshop( write, tif ) ||
- write_embed_imagedescription( write, tif ) )
+ if( !wtiff->strip )
+ if( wtiff_embed_profile( wtiff, tif ) ||
+ wtiff_embed_xmp( wtiff, tif ) ||
+ wtiff_embed_ipct( wtiff, tif ) ||
+ wtiff_embed_photoshop( wtiff, tif ) ||
+ wtiff_embed_imagedescription( wtiff, tif ) )
return( -1 );
- if( vips_image_get_typeof( write->im, VIPS_META_ORIENTATION ) &&
- !vips_image_get_int( write->im,
+ if( vips_image_get_typeof( wtiff->im, VIPS_META_ORIENTATION ) &&
+ !vips_image_get_int( wtiff->im,
VIPS_META_ORIENTATION, &orientation ) )
TIFFSetField( tif, TIFFTAG_ORIENTATION, orientation );
/* And colour fields.
*/
- if( write->im->Coding == VIPS_CODING_LABQ ) {
+ if( wtiff->im->Coding == VIPS_CODING_LABQ ) {
TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, 3 );
TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE, 8 );
TIFFSetField( tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_CIELAB );
}
- else if( write->onebit ) {
+ else if( wtiff->onebit ) {
TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, 1 );
TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE, 1 );
TIFFSetField( tif, TIFFTAG_PHOTOMETRIC,
- write->miniswhite ?
+ wtiff->miniswhite ?
PHOTOMETRIC_MINISWHITE :
PHOTOMETRIC_MINISBLACK );
}
@@ -600,14 +603,14 @@ write_tiff_header( Write *write, Layer *layer )
int alpha_bands;
- TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, write->im->Bands );
+ TIFFSetField( tif, TIFFTAG_SAMPLESPERPIXEL, wtiff->im->Bands );
TIFFSetField( tif, TIFFTAG_BITSPERSAMPLE,
- vips_format_sizeof( write->im->BandFmt ) << 3 );
+ vips_format_sizeof( wtiff->im->BandFmt ) << 3 );
- if( write->im->Bands < 3 ) {
+ if( wtiff->im->Bands < 3 ) {
/* Mono or mono + alpha.
*/
- photometric = write->miniswhite ?
+ photometric = wtiff->miniswhite ?
PHOTOMETRIC_MINISWHITE :
PHOTOMETRIC_MINISBLACK;
colour_bands = 1;
@@ -615,22 +618,22 @@ write_tiff_header( Write *write, Layer *layer )
else {
/* Could be: RGB, CMYK, LAB, perhaps with extra alpha.
*/
- if( write->im->Type == VIPS_INTERPRETATION_LAB ||
- write->im->Type == VIPS_INTERPRETATION_LABS ) {
+ if( wtiff->im->Type == VIPS_INTERPRETATION_LAB ||
+ wtiff->im->Type == VIPS_INTERPRETATION_LABS ) {
photometric = PHOTOMETRIC_CIELAB;
colour_bands = 3;
}
- else if( write->im->Type == VIPS_INTERPRETATION_CMYK &&
- write->im->Bands >= 4 ) {
+ else if( wtiff->im->Type == VIPS_INTERPRETATION_CMYK &&
+ wtiff->im->Bands >= 4 ) {
photometric = PHOTOMETRIC_SEPARATED;
TIFFSetField( tif,
TIFFTAG_INKSET, INKSET_CMYK );
colour_bands = 4;
}
- else if( write->compression == COMPRESSION_JPEG &&
- write->im->Bands == 3 &&
- write->im->BandFmt == VIPS_FORMAT_UCHAR &&
- (!write->rgbjpeg && write->jpqual < 90) ) {
+ else if( wtiff->compression == COMPRESSION_JPEG &&
+ wtiff->im->Bands == 3 &&
+ wtiff->im->BandFmt == VIPS_FORMAT_UCHAR &&
+ (!wtiff->rgbjpeg && wtiff->jpqual < 90) ) {
/* This signals to libjpeg that it can do
* YCbCr chrominance subsampling from RGB, not
* that we will supply the image as YCbCr.
@@ -651,7 +654,7 @@ write_tiff_header( Write *write, Layer *layer )
}
alpha_bands = VIPS_CLIP( 0,
- write->im->Bands - colour_bands, MAX_ALPHA );
+ wtiff->im->Bands - colour_bands, MAX_ALPHA );
if( alpha_bands > 0 ) {
uint16 v[MAX_ALPHA];
int i;
@@ -671,12 +674,12 @@ write_tiff_header( Write *write, Layer *layer )
/* Layout.
*/
- if( write->tile ) {
- TIFFSetField( tif, TIFFTAG_TILEWIDTH, write->tilew );
- TIFFSetField( tif, TIFFTAG_TILELENGTH, write->tileh );
+ if( wtiff->tile ) {
+ TIFFSetField( tif, TIFFTAG_TILEWIDTH, wtiff->tilew );
+ TIFFSetField( tif, TIFFTAG_TILELENGTH, wtiff->tileh );
}
else
- TIFFSetField( tif, TIFFTAG_ROWSPERSTRIP, write->tileh );
+ TIFFSetField( tif, TIFFTAG_ROWSPERSTRIP, wtiff->tileh );
if( layer->above )
/* Pyramid layer.
@@ -686,32 +689,53 @@ write_tiff_header( Write *write, Layer *layer )
/* Sample format.
*/
format = SAMPLEFORMAT_UINT;
- if( vips_band_format_isuint( write->im->BandFmt ) )
+ if( vips_band_format_isuint( wtiff->im->BandFmt ) )
format = SAMPLEFORMAT_UINT;
- else if( vips_band_format_isint( write->im->BandFmt ) )
+ else if( vips_band_format_isint( wtiff->im->BandFmt ) )
format = SAMPLEFORMAT_INT;
- else if( vips_band_format_isfloat( write->im->BandFmt ) )
+ else if( vips_band_format_isfloat( wtiff->im->BandFmt ) )
format = SAMPLEFORMAT_IEEEFP;
- else if( vips_band_format_iscomplex( write->im->BandFmt ) )
+ else if( vips_band_format_iscomplex( wtiff->im->BandFmt ) )
format = SAMPLEFORMAT_COMPLEXIEEEFP;
TIFFSetField( tif, TIFFTAG_SAMPLEFORMAT, format );
return( 0 );
}
-/* Walk the pyramid allocating resources.
- */
static int
-pyramid_fill( Write *write )
+wtiff_layer_rewind( Wtiff *wtiff, Layer *layer )
+{
+ VipsRect strip_size;
+
+ /* Build a line of tiles here.
+ *
+ * Expand the strip if necessary to make sure we have an even
+ * number of lines.
+ */
+ strip_size.left = 0;
+ strip_size.top = 0;
+ strip_size.width = layer->image->Xsize;
+ strip_size.height = wtiff->tileh;
+ if( (strip_size.height & 1) == 1 )
+ strip_size.height += 1;
+ if( vips_region_buffer( layer->strip, &strip_size ) )
+ return( -1 );
+
+ layer->y = 0;
+ layer->write_y = 0;
+
+ return( 0 );
+}
+
+static int
+wtiff_allocate_layers( Wtiff *wtiff )
{
Layer *layer;
- for( layer = write->layer; layer; layer = layer->below ) {
- VipsRect strip_size;
-
+ for( layer = wtiff->layer; layer; layer = layer->below ) {
layer->image = vips_image_new();
if( vips_image_pipelinev( layer->image,
- VIPS_DEMAND_STYLE_ANY, write->im, NULL ) )
+ VIPS_DEMAND_STYLE_ANY, wtiff->im, NULL ) )
return( -1 );
layer->image->Xsize = layer->width;
layer->image->Ysize = layer->height;
@@ -725,31 +749,20 @@ pyramid_fill( Write *write )
vips__region_no_ownership( layer->strip );
vips__region_no_ownership( layer->copy );
- /* Build a line of tiles here.
- *
- * Expand the strip if necessary to make sure we have an even
- * number of lines.
- */
- strip_size.left = 0;
- strip_size.top = 0;
- strip_size.width = layer->image->Xsize;
- strip_size.height = write->tileh;
- if( (strip_size.height & 1) == 1 )
- strip_size.height += 1;
- if( vips_region_buffer( layer->strip, &strip_size ) )
- return( -1 );
+ if( wtiff_layer_rewind( wtiff, layer ) )
+ return( -1 );
if( layer->lname )
layer->tif = vips__tiff_openout(
- layer->lname, write->bigtiff );
+ layer->lname, wtiff->bigtiff );
else {
- layer->tif = vips__tiff_openout_buffer( write->im,
- write->bigtiff, &layer->buf, &layer->len );
+ layer->tif = vips__tiff_openout_buffer( wtiff->im,
+ wtiff->bigtiff, &layer->buf, &layer->len );
}
if( !layer->tif )
return( -1 );
- if( write_tiff_header( write, layer ) )
+ if( wtiff_write_header( wtiff, layer ) )
return( -1 );
}
@@ -759,21 +772,21 @@ pyramid_fill( Write *write )
/* Delete any temp files we wrote.
*/
static void
-write_delete_temps( Write *write )
+wtiff_delete_temps( Wtiff *wtiff )
{
Layer *layer;
/* Don't delete the top layer: that's the output file.
*/
- if( write->layer &&
- write->layer->below )
- for( layer = write->layer->below; layer; layer = layer->below )
+ if( wtiff->layer &&
+ wtiff->layer->below )
+ for( layer = wtiff->layer->below; layer; layer = layer->below )
if( layer->lname ) {
#ifndef DEBUG
unlink( layer->lname );
VIPS_FREE( layer->buf );
#else
- printf( "write_delete_temps: leaving %s\n",
+ printf( "wtiff_delete_temps: leaving %s\n",
layer->lname );
#endif /*DEBUG*/
@@ -795,24 +808,22 @@ layer_free( Layer *layer )
/* Free an entire pyramid.
*/
static void
-pyramid_free( Layer *layer )
+layer_free_all( Layer *layer )
{
if( layer->below )
- pyramid_free( layer->below );
+ layer_free_all( layer->below );
layer_free( layer );
}
-/* Free a Write.
- */
static void
-write_free( Write *write )
+wtiff_free( Wtiff *wtiff )
{
- write_delete_temps( write );
+ wtiff_delete_temps( wtiff );
- VIPS_FREEF( vips_free, write->tbuf );
- VIPS_FREEF( pyramid_free, write->layer );
- VIPS_FREEF( vips_free, write->icc_profile );
+ VIPS_FREEF( vips_free, wtiff->tbuf );
+ VIPS_FREEF( layer_free_all, wtiff->layer );
+ VIPS_FREEF( vips_free, wtiff->icc_profile );
}
static int
@@ -859,10 +870,8 @@ get_resunit( VipsForeignTiffResunit resunit )
return( -1 );
}
-/* Make and init a Write.
- */
-static Write *
-write_new( VipsImage *im, const char *filename,
+static Wtiff *
+wtiff_new( VipsImage *im, const char *filename,
VipsForeignTiffCompression compression, int Q,
VipsForeignTiffPredictor predictor,
char *profile,
@@ -876,40 +885,91 @@ write_new( VipsImage *im, const char *filename,
gboolean properties,
gboolean strip )
{
- Write *write;
+ Wtiff *wtiff;
- if( !(write = VIPS_NEW( im, Write )) )
+ if( !(wtiff = VIPS_NEW( im, Wtiff )) )
return( NULL );
- write->im = im;
- write->filename = filename ?
+ wtiff->im = im;
+ wtiff->filename = filename ?
vips_strdup( VIPS_OBJECT( im ), filename ) : NULL;
- write->layer = NULL;
- write->tbuf = NULL;
- write->compression = get_compression( compression );
- write->jpqual = Q;
- write->predictor = predictor;
- write->tile = tile;
- write->tilew = tile_width;
- write->tileh = tile_height;
- write->pyramid = pyramid;
- write->onebit = squash;
- write->miniswhite = miniswhite;
- write->icc_profile = vips_strdup( NULL, profile );
- write->bigtiff = bigtiff;
- write->rgbjpeg = rgbjpeg;
- write->properties = properties;
- write->strip = strip;
+ wtiff->layer = NULL;
+ wtiff->tbuf = NULL;
+ wtiff->compression = get_compression( compression );
+ wtiff->jpqual = Q;
+ wtiff->predictor = predictor;
+ wtiff->tile = tile;
+ wtiff->tilew = tile_width;
+ wtiff->tileh = tile_height;
+ wtiff->pyramid = pyramid;
+ wtiff->onebit = squash;
+ wtiff->miniswhite = miniswhite;
+ wtiff->resunit = get_resunit( resunit );
+ wtiff->xres = xres;
+ wtiff->yres = yres;
+ wtiff->icc_profile = vips_strdup( NULL, profile );
+ wtiff->bigtiff = bigtiff;
+ wtiff->rgbjpeg = rgbjpeg;
+ wtiff->properties = properties;
+ wtiff->strip = strip;
+ wtiff->toilet_roll = FALSE;
+ wtiff->page_height = -1;
- write->resunit = get_resunit( resunit );
- write->xres = xres;
- write->yres = yres;
+ /* Updated below if we discover toilet roll mode.
+ */
+ wtiff->image_height = im->Ysize;
+
+ /* Check for a toilet roll image.
+ */
+ if( vips_image_get_typeof( im, VIPS_META_PAGE_HEIGHT ) &&
+ vips_image_get_int( im,
+ VIPS_META_PAGE_HEIGHT, &wtiff->page_height ) ) {
+ wtiff_free( wtiff );
+ return( NULL );
+ }
+
+ /* If page_height <= Ysize, treat as a single-page image.
+ */
+ if( wtiff->page_height > 0 &&
+ wtiff->page_height < im->Ysize ) {
+#ifdef DEBUG
+ printf( "wtiff_new: detected toilet roll image, "
+ "page-height=%d\n",
+ wtiff->page_height );
+#endif/*DEBUG*/
+
+ wtiff->toilet_roll = TRUE;
+ wtiff->image_height = wtiff->page_height;
+
+ if( im->Ysize % wtiff->page_height != 0 ) {
+ vips_error( "vips2tiff",
+ _( "image height %d is not a factor of "
+ "page-height %d" ),
+ im->Ysize, wtiff->page_height );
+ wtiff_free( wtiff );
+ return( NULL );
+ }
+
+#ifdef DEBUG
+ printf( "wtiff_new: pages=%d\n",
+ im->Ysize / wtiff->page_height );
+#endif/*DEBUG*/
+
+ /* 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( (write->tilew & 0xf) != 0 ||
- (write->tileh & 0xf) != 0 ) {
+ if( (wtiff->tilew & 0xf) != 0 ||
+ (wtiff->tileh & 0xf) != 0 ) {
vips_error( "vips2tiff",
"%s", _( "tile size not a multiple of 16" ) );
return( NULL );
@@ -918,7 +978,7 @@ write_new( VipsImage *im, const char *filename,
/* We can only pyramid LABQ and non-complex images.
*/
- if( write->pyramid ) {
+ if( wtiff->pyramid ) {
if( im->Coding == VIPS_CODING_NONE &&
vips_band_format_iscomplex( im->BandFmt ) ) {
vips_error( "vips2tiff",
@@ -930,67 +990,68 @@ write_new( VipsImage *im, const char *filename,
/* Only 1-bit-ize 8 bit mono images.
*/
- if( write->onebit &&
+ if( wtiff->onebit &&
(im->Coding != VIPS_CODING_NONE ||
im->BandFmt != VIPS_FORMAT_UCHAR ||
im->Bands != 1) ) {
- vips_warn( "vips2tiff",
- "%s", _( "can only squash 1 band uchar images -- "
+ g_warning( "%s",
+ _( "can only squash 1 band uchar images -- "
"disabling squash" ) );
- write->onebit = 0;
+ wtiff->onebit = 0;
}
- if( write->onebit &&
- write->compression == COMPRESSION_JPEG ) {
- vips_warn( "vips2tiff",
- "%s", _( "can't have 1-bit JPEG -- disabling JPEG" ) );
- write->compression = COMPRESSION_NONE;
+ if( wtiff->onebit &&
+ wtiff->compression == COMPRESSION_JPEG ) {
+ g_warning( "%s",
+ _( "can't have 1-bit JPEG -- disabling JPEG" ) );
+ wtiff->compression = COMPRESSION_NONE;
}
/* We can only MINISWHITE non-complex images of 1 or 2 bands.
*/
- if( write->miniswhite &&
+ if( wtiff->miniswhite &&
(im->Coding != VIPS_CODING_NONE ||
vips_band_format_iscomplex( im->BandFmt ) ||
im->Bands > 2) ) {
- vips_warn( "vips2tiff",
- "%s", _( "can only save non-complex greyscale images "
+ g_warning( "%s",
+ _( "can only save non-complex greyscale images "
"as miniswhite -- disabling miniswhite" ) );
- write->miniswhite = FALSE;
+ wtiff->miniswhite = FALSE;
}
/* Sizeof a line of bytes in the TIFF tile.
*/
if( im->Coding == VIPS_CODING_LABQ )
- write->tls = write->tilew * 3;
- else if( write->onebit )
- write->tls = VIPS_ROUND_UP( write->tilew, 8 ) / 8;
+ wtiff->tls = wtiff->tilew * 3;
+ else if( wtiff->onebit )
+ wtiff->tls = VIPS_ROUND_UP( wtiff->tilew, 8 ) / 8;
else
- write->tls = VIPS_IMAGE_SIZEOF_PEL( im ) * write->tilew;
+ wtiff->tls = VIPS_IMAGE_SIZEOF_PEL( im ) * wtiff->tilew;
/* Build the pyramid framework.
*/
- write->layer = pyramid_new( write, NULL, im->Xsize, im->Ysize );
+ wtiff->layer = wtiff_layer_new( wtiff, NULL,
+ im->Xsize, wtiff->image_height );
/* Fill all the layers.
*/
- if( pyramid_fill( write ) ) {
- write_free( write );
+ if( wtiff_allocate_layers( wtiff ) ) {
+ wtiff_free( wtiff );
return( NULL );
}
if( tile )
- write->tbuf = vips_malloc( NULL,
- TIFFTileSize( write->layer->tif ) );
+ wtiff->tbuf = vips_malloc( NULL,
+ TIFFTileSize( wtiff->layer->tif ) );
else
- write->tbuf = vips_malloc( NULL,
- TIFFScanlineSize( write->layer->tif ) );
- if( !write->tbuf ) {
- write_free( write );
+ wtiff->tbuf = vips_malloc( NULL,
+ TIFFScanlineSize( wtiff->layer->tif ) );
+ if( !wtiff->tbuf ) {
+ wtiff_free( wtiff );
return( NULL );
}
- return( write );
+ return( wtiff );
}
/* Convert VIPS LabQ to TIFF LAB. Just take the first three bands.
@@ -1015,14 +1076,14 @@ LabQ2LabC( VipsPel *q, VipsPel *p, int n )
/* Pack 8 bit VIPS to 1 bit TIFF.
*/
static void
-eightbit2onebit( Write *write, VipsPel *q, VipsPel *p, int n )
+eightbit2onebit( Wtiff *wtiff, VipsPel *q, VipsPel *p, int n )
{
int x;
VipsPel bits;
/* Invert in miniswhite mode.
*/
- int white = write->miniswhite ? 0 : 1;
+ int white = wtiff->miniswhite ? 0 : 1;
int black = white ^ 1;
bits = 0;
@@ -1072,10 +1133,10 @@ eightbit2onebit( Write *write, VipsPel *q, VipsPel *p, int n )
* the opposite conversion.
*/
static void
-invert_band0( Write *write, VipsPel *q, VipsPel *p, int n )
+invert_band0( Wtiff *wtiff, VipsPel *q, VipsPel *p, int n )
{
- VipsImage *im = write->im;
- gboolean invert = write->miniswhite;
+ VipsImage *im = wtiff->im;
+ gboolean invert = wtiff->miniswhite;
int x, i;
@@ -1138,7 +1199,7 @@ LabS2Lab16( VipsPel *q, VipsPel *p, int n )
/* Pack the pixels in @area from @in into a TIFF tile buffer.
*/
static void
-pack2tiff( Write *write, Layer *layer,
+wtiff_pack2tiff( Wtiff *wtiff, Layer *layer,
VipsRegion *in, VipsRect *area, VipsPel *q )
{
int y;
@@ -1150,37 +1211,37 @@ pack2tiff( Write *write, Layer *layer,
* Black out the tile first to make sure these edge pixels are always
* zero.
*/
- if( write->compression == COMPRESSION_JPEG &&
- (area->width < write->tilew ||
- area->height < write->tileh) )
+ if( wtiff->compression == COMPRESSION_JPEG &&
+ (area->width < wtiff->tilew ||
+ area->height < wtiff->tileh) )
memset( q, 0, TIFFTileSize( layer->tif ) );
for( y = area->top; y < VIPS_RECT_BOTTOM( area ); y++ ) {
VipsPel *p = (VipsPel *) VIPS_REGION_ADDR( in, area->left, y );
- if( write->im->Coding == VIPS_CODING_LABQ )
+ if( wtiff->im->Coding == VIPS_CODING_LABQ )
LabQ2LabC( q, p, area->width );
- else if( write->onebit )
- eightbit2onebit( write, q, p, area->width );
+ else if( wtiff->onebit )
+ eightbit2onebit( wtiff, q, p, area->width );
else if( (in->im->Bands == 1 || in->im->Bands == 2) &&
- write->miniswhite )
- invert_band0( write, q, p, area->width );
- else if( write->im->BandFmt == VIPS_FORMAT_SHORT &&
- write->im->Type == VIPS_INTERPRETATION_LABS )
+ wtiff->miniswhite )
+ invert_band0( wtiff, q, p, area->width );
+ else if( wtiff->im->BandFmt == VIPS_FORMAT_SHORT &&
+ wtiff->im->Type == VIPS_INTERPRETATION_LABS )
LabS2Lab16( q, p, area->width );
else
memcpy( q, p,
area->width *
- VIPS_IMAGE_SIZEOF_PEL( write->im ) );
+ VIPS_IMAGE_SIZEOF_PEL( wtiff->im ) );
- q += write->tls;
+ q += wtiff->tls;
}
}
/* Write a set of tiles across the strip.
*/
static int
-layer_write_tile( Write *write, Layer *layer, VipsRegion *strip )
+wtiff_layer_write_tile( Wtiff *wtiff, Layer *layer, VipsRegion *strip )
{
VipsImage *im = layer->image;
VipsRect *area = &strip->valid;
@@ -1193,18 +1254,18 @@ layer_write_tile( Write *write, Layer *layer, VipsRegion *strip )
image.width = im->Xsize;
image.height = im->Ysize;
- for( x = 0; x < im->Xsize; x += write->tilew ) {
+ for( x = 0; x < im->Xsize; x += wtiff->tilew ) {
VipsRect tile;
tile.left = x;
tile.top = area->top;
- tile.width = write->tilew;
- tile.height = write->tileh;
+ tile.width = wtiff->tilew;
+ tile.height = wtiff->tileh;
vips_rect_intersectrect( &tile, &image, &tile );
/* Have to repack pixels.
*/
- pack2tiff( write, layer, strip, &tile, write->tbuf );
+ wtiff_pack2tiff( wtiff, layer, strip, &tile, wtiff->tbuf );
#ifdef DEBUG_VERBOSE
printf( "Writing %dx%d tile at position %dx%d to image %s\n",
@@ -1212,7 +1273,7 @@ layer_write_tile( Write *write, Layer *layer, VipsRegion *strip )
TIFFFileName( layer->tif ) );
#endif /*DEBUG_VERBOSE*/
- if( TIFFWriteTile( layer->tif, write->tbuf,
+ if( TIFFWriteTile( layer->tif, wtiff->tbuf,
tile.left, tile.top, 0, 0 ) < 0 ) {
vips_error( "vips2tiff",
"%s", _( "TIFF write tile failed" ) );
@@ -1226,11 +1287,11 @@ layer_write_tile( Write *write, Layer *layer, VipsRegion *strip )
/* Write tileh scanlines, less for the last strip.
*/
static int
-layer_write_strip( Write *write, Layer *layer, VipsRegion *strip )
+wtiff_layer_write_strip( Wtiff *wtiff, Layer *layer, VipsRegion *strip )
{
VipsImage *im = layer->image;
VipsRect *area = &strip->valid;
- int height = VIPS_MIN( write->tileh, area->height );
+ int height = VIPS_MIN( wtiff->tileh, area->height );
int y;
@@ -1245,22 +1306,22 @@ layer_write_strip( Write *write, Layer *layer, VipsRegion *strip )
/* Any repacking necessary.
*/
if( im->Coding == VIPS_CODING_LABQ ) {
- LabQ2LabC( write->tbuf, p, im->Xsize );
- p = write->tbuf;
+ LabQ2LabC( wtiff->tbuf, p, im->Xsize );
+ p = wtiff->tbuf;
}
else if( im->BandFmt == VIPS_FORMAT_SHORT &&
im->Type == VIPS_INTERPRETATION_LABS ) {
- LabS2Lab16( write->tbuf, p, im->Xsize );
- p = write->tbuf;
+ LabS2Lab16( wtiff->tbuf, p, im->Xsize );
+ p = wtiff->tbuf;
}
- else if( write->onebit ) {
- eightbit2onebit( write, write->tbuf, p, im->Xsize );
- p = write->tbuf;
+ else if( wtiff->onebit ) {
+ eightbit2onebit( wtiff, wtiff->tbuf, p, im->Xsize );
+ p = wtiff->tbuf;
}
else if( (im->Bands == 1 || im->Bands == 2) &&
- write->miniswhite ) {
- invert_band0( write, write->tbuf, p, im->Xsize );
- p = write->tbuf;
+ wtiff->miniswhite ) {
+ invert_band0( wtiff, wtiff->tbuf, p, im->Xsize );
+ p = wtiff->tbuf;
}
if( TIFFWriteScanline( layer->tif, p, area->top + y, 0 ) < 0 )
@@ -1350,17 +1411,17 @@ layer_strip_shrink( Layer *layer )
static int
layer_strip_arrived( Layer *layer )
{
- Write *write = layer->write;
+ Wtiff *wtiff = layer->wtiff;
int result;
VipsRect new_strip;
VipsRect overlap;
VipsRect image_area;
- if( write->tile )
- result = layer_write_tile( write, layer, layer->strip );
+ if( wtiff->tile )
+ result = wtiff_layer_write_tile( wtiff, layer, layer->strip );
else
- result = layer_write_strip( write, layer, layer->strip );
+ result = wtiff_layer_write_strip( wtiff, layer, layer->strip );
if( result )
return( -1 );
@@ -1373,11 +1434,11 @@ layer_strip_arrived( Layer *layer )
* Expand the strip if necessary to make sure we have an even
* number of lines.
*/
- layer->y += write->tileh;
+ layer->y += wtiff->tileh;
new_strip.left = 0;
new_strip.top = layer->y;
new_strip.width = layer->image->Xsize;
- new_strip.height = write->tileh;
+ new_strip.height = wtiff->tileh;
image_area.left = 0;
image_area.top = 0;
@@ -1419,8 +1480,8 @@ layer_strip_arrived( Layer *layer )
static int
write_strip( VipsRegion *region, VipsRect *area, void *a )
{
- Write *write = (Write *) a;
- Layer *layer = write->layer;
+ Wtiff *wtiff = (Wtiff *) a;
+ Layer *layer = wtiff->layer;
#ifdef DEBUG
printf( "write_strip: strip at %d, height %d\n",
@@ -1482,7 +1543,7 @@ write_strip( VipsRegion *region, VipsRect *area, void *a )
* we might have set.
*/
static int
-write_copy_tiff( Write *write, TIFF *out, TIFF *in )
+wtiff_copy_tiff( Wtiff *wtiff, TIFF *out, TIFF *in )
{
uint32 i32;
uint16 i16;
@@ -1510,23 +1571,23 @@ write_copy_tiff( Write *write, TIFF *out, TIFF *in )
CopyField( TIFFTAG_ROWSPERSTRIP, i32 );
CopyField( TIFFTAG_SUBFILETYPE, i32 );
- if( write->predictor != VIPS_FOREIGN_TIFF_PREDICTOR_NONE )
- TIFFSetField( out, TIFFTAG_PREDICTOR, write->predictor );
+ if( wtiff->predictor != VIPS_FOREIGN_TIFF_PREDICTOR_NONE )
+ TIFFSetField( out, TIFFTAG_PREDICTOR, wtiff->predictor );
/* TIFFTAG_JPEGQUALITY is a pesudo-tag, so we can't copy it.
- * Set explicitly from Write.
+ * Set explicitly from Wtiff.
*/
- if( write->compression == COMPRESSION_JPEG ) {
- TIFFSetField( out, TIFFTAG_JPEGQUALITY, write->jpqual );
+ if( wtiff->compression == COMPRESSION_JPEG ) {
+ TIFFSetField( out, TIFFTAG_JPEGQUALITY, wtiff->jpqual );
/* Only for three-band, 8-bit images.
*/
- if( write->im->Bands == 3 &&
- write->im->BandFmt == VIPS_FORMAT_UCHAR ) {
+ if( wtiff->im->Bands == 3 &&
+ wtiff->im->BandFmt == VIPS_FORMAT_UCHAR ) {
/* Enable rgb->ycbcr conversion in the jpeg write.
*/
- if( !write->rgbjpeg &&
- write->jpqual < 90 )
+ if( !wtiff->rgbjpeg &&
+ wtiff->jpqual < 90 )
TIFFSetField( out,
TIFFTAG_JPEGCOLORMODE,
JPEGCOLORMODE_RGB );
@@ -1540,14 +1601,14 @@ write_copy_tiff( Write *write, TIFF *out, TIFF *in )
}
}
- /* We can't copy profiles or xmp :( Set again from Write.
+ /* We can't copy profiles or xmp :( Set again from Wtiff.
*/
- if( !write->strip )
- if( write_embed_profile( write, out ) ||
- write_embed_xmp( write, out ) ||
- write_embed_ipct( write, out ) ||
- write_embed_photoshop( write, out ) ||
- write_embed_imagedescription( write, out ) )
+ if( !wtiff->strip )
+ if( wtiff_embed_profile( wtiff, out ) ||
+ wtiff_embed_xmp( wtiff, out ) ||
+ wtiff_embed_ipct( wtiff, out ) ||
+ wtiff_embed_photoshop( wtiff, out ) ||
+ wtiff_embed_imagedescription( wtiff, out ) )
return( -1 );
buf = vips_malloc( NULL, TIFFTileSize( in ) );
@@ -1555,7 +1616,7 @@ write_copy_tiff( Write *write, TIFF *out, TIFF *in )
for( tile = 0; tile < n; tile++ ) {
tsize_t len;
- /* It'd be good to use TIFFReadRawTile()/TIFFWriteRawTile()
+ /* It'd be good to use TIFFReadRawTile()/TIFFWtiffRawTile()
* here to save compression/decompression, but sadly it seems
* not to work :-( investigate at some point.
*/
@@ -1574,13 +1635,13 @@ write_copy_tiff( Write *write, TIFF *out, TIFF *in )
/* Append all of the lower layers we wrote to the output.
*/
static int
-write_gather( Write *write )
+wtiff_gather( Wtiff *wtiff )
{
Layer *layer;
- if( write->layer &&
- write->layer->below )
- for( layer = write->layer->below; layer;
+ if( wtiff->layer &&
+ wtiff->layer->below )
+ for( layer = wtiff->layer->below; layer;
layer = layer->below ) {
TIFF *in;
@@ -1590,19 +1651,94 @@ write_gather( Write *write )
if( !(in = vips__tiff_openin( layer->lname )) )
return( -1 );
- if( write_copy_tiff( write, write->layer->tif, in ) ) {
+ if( wtiff_copy_tiff( wtiff, wtiff->layer->tif, in ) ) {
TIFFClose( in );
return( -1 );
}
TIFFClose( in );
- if( !TIFFWriteDirectory( write->layer->tif ) )
+ if( !TIFFWriteDirectory( wtiff->layer->tif ) )
return( -1 );
}
return( 0 );
}
+/* Three types of write: single image, multipage and pyramid.
+ */
+static int
+wtiff_write_image( Wtiff *wtiff )
+{
+ if( wtiff->toilet_roll ) {
+ int y;
+
+#ifdef DEBUG
+ printf( "wtiff_write_image: toilet-roll mode\n" );
+#endif /*DEBUG*/
+
+ y = 0;
+ for(;;) {
+ VipsImage *page;
+
+ if( vips_crop( wtiff->im, &page,
+ 0, y, wtiff->im->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 );
+
+ y += wtiff->page_height;
+ if( y >= wtiff->im->Ysize )
+ break;
+
+ if( !TIFFWriteDirectory( wtiff->layer->tif ) ||
+ wtiff_layer_rewind( wtiff,
+ wtiff->layer ) ||
+ wtiff_write_header( wtiff,
+ wtiff->layer ) )
+ return( -1 );
+ }
+ }
+ else if( wtiff->pyramid ) {
+#ifdef DEBUG
+ printf( "wtiff_write_image: pyramid mode\n" );
+#endif /*DEBUG*/
+
+ if( vips_sink_disc( wtiff->im, write_strip, wtiff ) )
+ return( -1 );
+
+ if( !TIFFWriteDirectory( wtiff->layer->tif ) )
+ return( -1 );
+
+ if( wtiff->pyramid ) {
+ /* Free lower pyramid resources ... this will
+ * TIFFClose() (but not delete) the smaller layers
+ * ready for us to read from them again.
+ */
+ if( wtiff->layer->below )
+ layer_free_all( wtiff->layer->below );
+
+ /* Append smaller layers to the main file.
+ */
+ if( wtiff_gather( wtiff ) )
+ return( -1 );
+ }
+ }
+ else {
+#ifdef DEBUG
+ printf( "wtiff_write_image: single-image mode\n" );
+#endif /*DEBUG*/
+
+ if( vips_sink_disc( wtiff->im, write_strip, wtiff ) )
+ return( -1 );
+ }
+
+ return( 0 );
+}
+
int
vips__tiff_write( VipsImage *in, const char *filename,
VipsForeignTiffCompression compression, int Q,
@@ -1617,7 +1753,7 @@ vips__tiff_write( VipsImage *in, const char *filename,
gboolean rgbjpeg,
gboolean properties, gboolean strip )
{
- Write *write;
+ Wtiff *wtiff;
#ifdef DEBUG
printf( "tiff2vips: libtiff version is \"%s\"\n", TIFFGetVersion() );
@@ -1628,40 +1764,19 @@ vips__tiff_write( VipsImage *in, const char *filename,
if( vips_check_coding_known( "vips2tiff", in ) )
return( -1 );
- /* Make output image.
- */
- if( !(write = write_new( in, filename,
+ if( !(wtiff = wtiff_new( in, filename,
compression, Q, predictor, profile,
tile, tile_width, tile_height, pyramid, squash,
miniswhite, resunit, xres, yres, bigtiff, rgbjpeg,
properties, strip )) )
return( -1 );
- if( vips_sink_disc( write->im, write_strip, write ) ) {
- write_free( write );
+ if( wtiff_write_image( wtiff ) ) {
+ wtiff_free( wtiff );
return( -1 );
}
- if( !TIFFWriteDirectory( write->layer->tif ) )
- return( -1 );
-
- if( write->pyramid ) {
- /* Free lower pyramid resources ... this will TIFFClose() (but
- * not delete) the smaller layers ready for us to read from
- * them again.
- */
- if( write->layer->below )
- pyramid_free( write->layer->below );
-
- /* Append smaller layers to the main file.
- */
- if( write_gather( write ) ) {
- write_free( write );
- return( -1 );
- }
- }
-
- write_free( write );
+ wtiff_free( wtiff );
return( 0 );
}
@@ -1681,63 +1796,41 @@ vips__tiff_write_buf( VipsImage *in,
gboolean rgbjpeg,
gboolean properties, gboolean strip )
{
- Write *write;
+ Wtiff *wtiff;
vips__tiff_init();
if( vips_check_coding_known( "vips2tiff", in ) )
return( -1 );
- /* Make output image.
- */
- if( !(write = write_new( in, NULL,
+ if( !(wtiff = wtiff_new( in, NULL,
compression, Q, predictor, profile,
tile, tile_width, tile_height, pyramid, squash,
miniswhite, resunit, xres, yres, bigtiff, rgbjpeg,
properties, strip )) )
return( -1 );
- write->obuf = obuf;
- write->olen = olen;
+ wtiff->obuf = obuf;
+ wtiff->olen = olen;
- if( vips_sink_disc( write->im, write_strip, write ) ) {
- write_free( write );
+ if( wtiff_write_image( wtiff ) ) {
+ wtiff_free( wtiff );
return( -1 );
}
- if( !TIFFWriteDirectory( write->layer->tif ) )
- return( -1 );
-
- if( write->pyramid ) {
- /* Free lower pyramid resources ... this will TIFFClose() (but
- * not delete) the smaller layers ready for us to read from
- * them again.
- */
- if( write->layer->below )
- pyramid_free( write->layer->below );
-
- /* Append smaller layers to the main file.
- */
- if( write_gather( write ) ) {
- write_free( write );
- return( -1 );
- }
- }
-
/* Now close the top layer, and we'll get a pointer we can return
* to our caller.
*/
- TIFFClose( write->layer->tif );
- write->layer->tif = NULL;
+ VIPS_FREEF( TIFFClose, wtiff->layer->tif );
- *obuf = write->layer->buf;
- *olen = write->layer->len;
+ *obuf = wtiff->layer->buf;
+ *olen = wtiff->layer->len;
/* Now our caller owns it, we must not free it.
*/
- write->layer->buf = NULL;
+ wtiff->layer->buf = NULL;
- write_free( write );
+ wtiff_free( wtiff );
return( 0 );
}
diff --git a/libvips/foreign/vips2webp.c b/libvips/foreign/vips2webp.c
index 536638d1..5447c24c 100644
--- a/libvips/foreign/vips2webp.c
+++ b/libvips/foreign/vips2webp.c
@@ -252,9 +252,9 @@ write_webp( WebPPicture *pic, VipsImage *in,
#else
if( lossless ||
near_lossless )
- vips_warn( "vips2webp", "%s", _( "lossless unsupported" ) );
+ g_warning( "%s", _( "lossless unsupported" ) );
if( alpha_q != 100 )
- vips_warn( "vips2webp", "%s", _( "alpha_q unsupported" ) );
+ g_warning( "%s", _( "alpha_q unsupported" ) );
#endif
#if WEBP_ENCODER_ABI_VERSION >= 0x0209
@@ -264,10 +264,9 @@ write_webp( WebPPicture *pic, VipsImage *in,
config.preprocessing |= 4;
#else
if( near_lossless )
- vips_warn( "vips2webp", "%s", _( "near_lossless unsupported" ) );
+ g_warning( "%s", _( "near_lossless unsupported" ) );
if( smart_subsample )
- vips_warn( "vips2webp",
- "%s", _( "smart_subsample unsupported" ) );
+ g_warning( "%s", _( "smart_subsample unsupported" ) );
#endif
if( !WebPValidateConfig( &config ) ) {
diff --git a/libvips/histogram/maplut.c b/libvips/histogram/maplut.c
index 09631b95..95cb7054 100644
--- a/libvips/histogram/maplut.c
+++ b/libvips/histogram/maplut.c
@@ -106,11 +106,8 @@ static void
vips_maplut_posteval( VipsImage *image, VipsProgress *progress,
VipsMaplut *maplut )
{
- VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( maplut );
-
if( maplut->overflow )
- vips_warn( class->nickname,
- _( "%d overflows detected" ), maplut->overflow );
+ g_warning( _( "%d overflows detected" ), maplut->overflow );
}
/* Our sequence value: the region this sequence is using, and local stats.
diff --git a/libvips/include/vips/Makefile.am b/libvips/include/vips/Makefile.am
index 33630384..69f51b28 100644
--- a/libvips/include/vips/Makefile.am
+++ b/libvips/include/vips/Makefile.am
@@ -37,6 +37,7 @@ pkginclude_HEADERS = \
region.h \
resample.h \
semaphore.h \
+ soname.h \
threadpool.h \
thread.h \
transform.h \
@@ -70,6 +71,7 @@ vips_scan_headers = \
${top_srcdir}/libvips/include/vips/morphology.h \
${top_srcdir}/libvips/include/vips/draw.h \
${top_srcdir}/libvips/include/vips/basic.h \
+ ${top_srcdir}/libvips/include/vips/version.h \
${top_srcdir}/libvips/include/vips/object.h
enumtypes.h: $(vips_scan_headers) Makefile
diff --git a/libvips/include/vips/almostdeprecated.h b/libvips/include/vips/almostdeprecated.h
index c16bf350..26d90937 100644
--- a/libvips/include/vips/almostdeprecated.h
+++ b/libvips/include/vips/almostdeprecated.h
@@ -284,6 +284,14 @@ int im_plotpoint( IMAGE *im, int x, int y, PEL *pel );
int im_smudge( IMAGE *image, int ix, int iy, VipsRect *r );
int im_smear( IMAGE *im, int ix, int iy, VipsRect *r );
+void vips_warn( const char *domain, const char *fmt, ... )
+ __attribute__((format(printf, 2, 3)));
+void vips_vwarn( const char *domain, const char *fmt, va_list ap );
+void vips_info_set( gboolean info );
+void vips_info( const char *domain, const char *fmt, ... )
+ __attribute__((format(printf, 2, 3)));
+void vips_vinfo( const char *domain, const char *fmt, va_list ap );
+
#ifdef __cplusplus
}
#endif /*__cplusplus*/
diff --git a/libvips/include/vips/error.h b/libvips/include/vips/error.h
index 6afa66cf..1b697afa 100644
--- a/libvips/include/vips/error.h
+++ b/libvips/include/vips/error.h
@@ -50,13 +50,6 @@ void vips_verror_system( int err, const char *domain,
const char *fmt, va_list ap );
void vips_error_g( GError **error );
void vips_g_error( GError **error );
-void vips_warn( const char *domain, const char *fmt, ... )
- __attribute__((format(printf, 2, 3)));
-void vips_vwarn( const char *domain, const char *fmt, va_list ap );
-void vips_info_set( gboolean info );
-void vips_info( const char *domain, const char *fmt, ... )
- __attribute__((format(printf, 2, 3)));
-void vips_vinfo( const char *domain, const char *fmt, va_list ap );
void vips_error_exit( const char *fmt, ... )
__attribute__((noreturn, format(printf, 1, 2)));
diff --git a/libvips/include/vips/foreign.h b/libvips/include/vips/foreign.h
index 2245167b..cb510570 100644
--- a/libvips/include/vips/foreign.h
+++ b/libvips/include/vips/foreign.h
@@ -131,6 +131,10 @@ typedef struct _VipsForeignLoad {
*/
VipsForeignFlags flags;
+ /* Stop load on first warning.
+ */
+ gboolean fail;
+
/* Deprecated and unused, just here for compat.
*/
gboolean sequential;
diff --git a/libvips/include/vips/header.h b/libvips/include/vips/header.h
index 8272aa7c..1ae81011 100644
--- a/libvips/include/vips/header.h
+++ b/libvips/include/vips/header.h
@@ -133,6 +133,15 @@ extern "C" {
*/
#define VIPS_META_ORIENTATION "orientation"
+/**
+ * VIPS_META_PAGE_HEIGHT:
+ *
+ * If set, the height of each page when this image was loaded. If you save an
+ * image with "page-height" set to a format that supports multiple pages, such
+ * as tiff, the image will be saved as a series of pages.
+ */
+#define VIPS_META_PAGE_HEIGHT "page-height"
+
guint64 vips_format_sizeof( VipsBandFormat format );
guint64 vips_format_sizeof_unsafe( VipsBandFormat format );
@@ -170,6 +179,7 @@ gboolean vips_image_remove( VipsImage *image, const char *name );
typedef void *(*VipsImageMapFn)( VipsImage *image,
const char *name, GValue *value, void *a );
void *vips_image_map( VipsImage *image, VipsImageMapFn fn, void *a );
+gchar **vips_image_get_fields( VipsImage *image );
void vips_image_set_area( VipsImage *image,
const char *name, VipsCallbackFn free_fn, void *data );
@@ -189,6 +199,7 @@ int vips_image_get_string( const VipsImage *image,
const char *name, const char **out );
void vips_image_set_string( VipsImage *image,
const char *name, const char *str );
+void vips_image_print_field( const VipsImage *image, const char *field );
int vips_image_history_printf( VipsImage *image, const char *format, ... )
__attribute__((format(printf, 2, 3)));
diff --git a/libvips/include/vips/private.h b/libvips/include/vips/private.h
index d9648190..6e3bd497 100644
--- a/libvips/include/vips/private.h
+++ b/libvips/include/vips/private.h
@@ -56,11 +56,6 @@ extern "C" {
*/
#define VIPS_SIZEOF_HEADER (64)
-/* Startup ABI check.
- */
-int vips__init( const char *argv0 );
-size_t vips__get_sizeof_vipsobject( void );
-
/* What we track for each mmap window. Have a list of these on an openin
* VipsImage.
*/
@@ -183,6 +178,11 @@ void vips__demand_hint_array( struct _VipsImage *image,
int vips__image_copy_fields_array( struct _VipsImage *out,
struct _VipsImage *in[] );
+/* Deprecated.
+ */
+int vips__init( const char *argv0 );
+size_t vips__get_sizeof_vipsobject( void );
+
#ifdef __cplusplus
}
#endif /*__cplusplus*/
diff --git a/libvips/include/vips/soname.h b/libvips/include/vips/soname.h
new file mode 100644
index 00000000..7fd6c91d
--- /dev/null
+++ b/libvips/include/vips/soname.h
@@ -0,0 +1,2 @@
+/* This file is autogenerated, do not edit. */
+#define VIPS_SONAME "libvips.so.42"
diff --git a/libvips/include/vips/util.h b/libvips/include/vips/util.h
index fb8fb2a6..027acb11 100644
--- a/libvips/include/vips/util.h
+++ b/libvips/include/vips/util.h
@@ -135,6 +135,14 @@ G_STMT_START { \
} \
} G_STMT_END
+/* The g_info() macro was added in 2.40.
+ */
+#ifndef g_info
+/* Hopefully we have varargs macros. Maybe revisit this.
+ */
+#define g_info(...) \
+ g_log( G_LOG_DOMAIN, G_LOG_LEVEL_INFO, __VA_ARGS__ )
+#endif
/* Various integer range clips. Record over/under flows.
*/
diff --git a/libvips/include/vips/version.h.in b/libvips/include/vips/version.h.in
index 85a2a5ae..465f3f8e 100644
--- a/libvips/include/vips/version.h.in
+++ b/libvips/include/vips/version.h.in
@@ -10,6 +10,21 @@
#define VIPS_MINOR_VERSION (@VIPS_MINOR_VERSION@)
#define VIPS_MICRO_VERSION (@VIPS_MICRO_VERSION@)
+/* The ABI version, as used for library versioning.
+ */
+#define VIPS_LIBRARY_CURRENT (@LIBRARY_CURRENT@)
+#define VIPS_LIBRARY_REVISION (@LIBRARY_REVISION@)
+#define VIPS_LIBRARY_AGE (@LIBRARY_AGE@)
+
+/**
+ * VIPS_SONAME:
+ *
+ * The name of the shared object containing the vips library, for example
+ * "libvips.so.42", or "libvips-42.dll".
+ */
+
+#include "soname.h"
+
/* Not really anything to do with versions, but this is a handy place to put
* it.
*/
diff --git a/libvips/include/vips/vips.h b/libvips/include/vips/vips.h
index 174eeb3e..30be4706 100644
--- a/libvips/include/vips/vips.h
+++ b/libvips/include/vips/vips.h
@@ -164,14 +164,13 @@ extern "C" {
* not have _().
*/
#define VIPS_INIT( ARGV0 ) \
- (sizeof( VipsObject ) != vips__get_sizeof_vipsobject() ? ( \
- vips_info( "vips_init", "ABI mismatch" ), \
- vips_info( "vips_init", \
- "library has sizeof(VipsObject) == %zd", \
- vips__get_sizeof_vipsobject() ), \
- vips_info( "vips_init", \
- "application has sizeof(VipsObject) == %zd", \
- sizeof( VipsObject ) ), \
+ (vips_version( 3 ) - vips_version( 5 ) != \
+ VIPS_LIBRARY_CURRENT - VIPS_LIBRARY_AGE ? ( \
+ g_warning( "ABI mismatch" ), \
+ g_warning( "library has ABI version %d", \
+ vips_version( 3 ) - vips_version( 5 ) ), \
+ g_warning( "application needs ABI version %d", \
+ VIPS_LIBRARY_CURRENT - VIPS_LIBRARY_AGE ), \
vips_error( "vips_init", "ABI mismatch" ), \
-1 ) : \
vips_init( ARGV0 ))
diff --git a/libvips/iofuncs/Makefile.am b/libvips/iofuncs/Makefile.am
index eff85d1b..0a7807dc 100644
--- a/libvips/iofuncs/Makefile.am
+++ b/libvips/iofuncs/Makefile.am
@@ -68,6 +68,7 @@ vips_scan_headers = \
${top_srcdir}/libvips/include/vips/morphology.h \
${top_srcdir}/libvips/include/vips/draw.h \
${top_srcdir}/libvips/include/vips/basic.h \
+ ${top_srcdir}/libvips/include/vips/version.h \
${top_srcdir}/libvips/include/vips/object.h
enumtypes.c: $(vips_scan_headers) Makefile
diff --git a/libvips/iofuncs/error.c b/libvips/iofuncs/error.c
index d1f53d8b..3ca63d29 100644
--- a/libvips/iofuncs/error.c
+++ b/libvips/iofuncs/error.c
@@ -103,12 +103,29 @@
*
* The domain argument most of these functions take is not localised and is
* supposed to indicate the component which failed.
+ *
+ * libvips uses g_warning() and g_info() to send warning and information
+ * messages to the user. You can use the usual glib mechanisms to display or
+ * divert these messages. For example, info messages are hidden by default, but
+ * you can see them with:
+ *
+ * |[
+ * $ G_MESSAGES_DEBUG=VIPS vipsthumbnail k2.jpg
+ * VIPS-INFO: thumbnailing k2.jpg
+ * VIPS-INFO: selected loader is VipsForeignLoadJpegFile
+ * VIPS-INFO: input size is 1450 x 2048
+ * VIPS-INFO: loading jpeg with factor 8 pre-shrink
+ * VIPS-INFO: converting to processing space srgb
+ * VIPS-INFO: residual reducev by 0.5
+ * VIPS-INFO: 13 point mask
+ * VIPS-INFO: using vector path
+ * VIPS-INFO: residual reduceh by 0.5
+ * VIPS-INFO: 13 point mask
+ * VIPS-INFO: thumbnailing k2.jpg as ./tn_k2.jpg
+ * ]|
+ *
*/
-/* Show info messages. Handy for debugging.
- */
-int vips__info = 0;
-
/* Make global array to keep the error message buffer.
*/
#define VIPS_MAX_ERROR (10240)
@@ -371,125 +388,6 @@ vips_error_clear( void )
g_mutex_unlock( vips__global_lock );
}
-/**
- * vips_info_set:
- * @info: %TRUE to enable info messages
- *
- * If set, vips will output various informative messages to stderr as it works.
- *
- * See also: vips_info().
- */
-void
-vips_info_set( gboolean info )
-{
- vips__info = info;
-}
-
-/**
- * vips_vinfo:
- * @domain: the source of the message
- * @fmt: printf()-style format string for the message
- * @ap: arguments to the format string
- *
- * Sends a formatted informational message to stderr if the --vips-info flag
- * has been given to the program, or the environment variable VIPS_INFO has been
- * defined, or if vips_info_set() has been called.
- *
- * Informational messages are used to report details about the operation of
- * functions.
- *
- * See also: vips_info(), vips_info_set(), vips_warn().
- */
-void
-vips_vinfo( const char *domain, const char *fmt, va_list ap )
-{
- if( vips__info ) {
- g_mutex_lock( vips__global_lock );
- (void) fprintf( stderr, _( "%s: " ), _( "info" ) );
- if( domain )
- (void) fprintf( stderr, _( "%s: " ), domain );
- (void) vfprintf( stderr, fmt, ap );
- (void) fprintf( stderr, "\n" );
- g_mutex_unlock( vips__global_lock );
- }
-}
-
-/**
- * vips_info:
- * @domain: the source of the diagnostic message
- * @fmt: printf()-style format string for the message
- * @...: arguments to the format string
- *
- * Sends a formatted informational message to stderr if the --vips-info flag
- * has been given to the program or the environment variable VIPS_INFO has been
- * defined, or if vips_info_set() has been called.
- *
- * Informational messages are used to report details about the operation of
- * functions.
- *
- * See also: vips_info_set(), vips_vinfo(), vips_warn().
- */
-void
-vips_info( const char *domain, const char *fmt, ... )
-{
- va_list ap;
-
- va_start( ap, fmt );
- vips_vinfo( domain, fmt, ap );
- va_end( ap );
-}
-
-/**
- * vips_vwarn:
- * @domain: the source of the warning message
- * @fmt: printf()-style format string for the message
- * @ap: arguments to the format string
- *
- * Exactly as vips_warn(), but takes a va_list argument.
- *
- * See also: vips_warn().
- */
-void
-vips_vwarn( const char *domain, const char *fmt, va_list ap )
-{
- if( !g_getenv( "IM_WARNING" ) &&
- !g_getenv( "VIPS_WARNING" ) ) {
- g_mutex_lock( vips__global_lock );
- (void) fprintf( stderr, _( "%s: " ), _( "vips warning" ) );
- if( domain )
- (void) fprintf( stderr, _( "%s: " ), domain );
- (void) vfprintf( stderr, fmt, ap );
- (void) fprintf( stderr, "\n" );
- g_mutex_unlock( vips__global_lock );
- }
-
- if( vips__fatal )
- vips_error_exit( "vips__fatal" );
-}
-
-/**
- * vips_warn:
- * @domain: the source of the warning message
- * @fmt: printf()-style format string for the message
- * @...: arguments to the format string
- *
- * Sends a formatted warning message to stderr. If you define the
- * environment variable VIPS_WARNING, these message are supressed.
- *
- * Warning messages are used to report things like overflow counts.
- *
- * See also: vips_info(), vips_vwarn().
- */
-void
-vips_warn( const char *domain, const char *fmt, ... )
-{
- va_list ap;
-
- va_start( ap, fmt );
- vips_vwarn( domain, fmt, ap );
- va_end( ap );
-}
-
/**
* vips_error_exit:
* @fmt: printf()-style format string for the message
diff --git a/libvips/iofuncs/gate.c b/libvips/iofuncs/gate.c
index 41f72978..84dcbcc2 100644
--- a/libvips/iofuncs/gate.c
+++ b/libvips/iofuncs/gate.c
@@ -143,8 +143,7 @@ vips_thread_profile_save( VipsThreadProfile *profile )
vips__file_open_write( "vips-profile.txt", TRUE );
if( !vips__thread_fp ) {
g_mutex_unlock( vips__global_lock );
- vips_warn( "VipsGate",
- "%s", "unable to create profile log" );
+ g_warning( "unable to create profile log" );
return;
}
@@ -204,8 +203,7 @@ vips__thread_profile_init_cb( VipsThreadProfile *profile )
* been called.
*/
if( vips__thread_profile )
- vips_warn( "VipsGate",
- "discarding unsaved state for thread %p --- "
+ g_warning( "discarding unsaved state for thread %p --- "
"call vips_thread_shutdown() for this thread",
profile->thread );
diff --git a/libvips/iofuncs/generate.c b/libvips/iofuncs/generate.c
index e24cd1d7..58a9c013 100644
--- a/libvips/iofuncs/generate.c
+++ b/libvips/iofuncs/generate.c
@@ -411,7 +411,7 @@ vips_image_pipelinev( VipsImage *image, VipsDemandStyle hint, ... )
;
va_end( ap );
if( i == MAX_IMAGES ) {
- vips_warn( "vips_image_pipeline", "%s", _( "too many images" ) );
+ g_warning( "%s", _( "too many images" ) );
/* Make sure we have a sentinel there.
*/
diff --git a/libvips/iofuncs/header.c b/libvips/iofuncs/header.c
index f70b5706..11d88c44 100644
--- a/libvips/iofuncs/header.c
+++ b/libvips/iofuncs/header.c
@@ -893,8 +893,11 @@ vips__image_copy_fields_array( VipsImage *out, VipsImage *in[] )
/* Need to copy last-to-first so that in0 meta will override any
* earlier meta.
+ *
+ * Don't destroy the meta on out. Things like foreign.c like setting
+ * image properties before calling a subclass loader, and those
+ * subclass loaders will sometimes write to an image.
*/
- vips__meta_destroy( out );
for( i = ni - 1; i >= 0; i-- )
if( meta_cp( out, in[i] ) )
return( -1 );
@@ -1176,6 +1179,55 @@ vips_image_map( VipsImage *image, VipsImageMapFn fn, void *a )
return( NULL );
}
+static void *
+count_fields( VipsImage *image, const char *field, GValue *value, void *a )
+{
+ int *n_fields = (int *) a;
+
+ n_fields += 1;
+
+ return( NULL );
+}
+
+static void *
+add_fields( VipsImage *image, const char *field, GValue *value, void *a )
+{
+ gchar ***p = (gchar ***) a;
+
+ **p = g_strdup( field );
+ *p += 1;
+
+ return( NULL );
+}
+
+/**
+ * vips_image_get_fields:
+ * @image: image to get fields from
+ *
+ * Get a %NULL-terminated array listing all the metadata field names on @image.
+ * Free the return result with g_strfreev().
+ *
+ * This is handy for language bindings. From C, it's usually more convenient to
+ * use vips_image_map().
+ *
+ * Returns: (transfer full): metadata fields in image, as a %NULL-terminated
+ * array.
+ */
+gchar **
+vips_image_get_fields( VipsImage *image )
+{
+ int n_fields;
+ gchar **fields;
+ gchar **p;
+
+ (void) vips_image_map( image, count_fields, &n_fields );
+ fields = g_new0( gchar *, n_fields + 1 );
+ p = fields;
+ (void) vips_image_map( image, add_fields, &p );
+
+ return( fields );
+}
+
/**
* vips_image_set_area:
* @image: image to attach the metadata to
@@ -1524,6 +1576,28 @@ vips_image_get_as_string( const VipsImage *image,
return( 0 );
}
+/**
+ * vips_image_print_field:
+ * @image: image to get the header field from
+ * @field: field name
+ *
+ * Prints a field to stdout as ASCII. Handy for debugging.
+ */
+void
+vips_image_print_field( const VipsImage *image, const char *field )
+{
+ char *str;
+
+ if( vips_image_get_as_string( image, field, &str ) ) {
+ printf( "vips_image_print_field: unable to read field\n" );
+ return;
+ }
+
+ printf( ".%s: %s\n", field, str );
+
+ g_free( str );
+}
+
/**
* vips_image_history_printf:
* @image: add history line to this image
diff --git a/libvips/iofuncs/image.c b/libvips/iofuncs/image.c
index 50627744..79475600 100644
--- a/libvips/iofuncs/image.c
+++ b/libvips/iofuncs/image.c
@@ -1006,8 +1006,7 @@ vips_image_build( VipsObject *object )
* still be able to process it without coredumps.
*/
if( image->file_length > sizeof_image )
- vips_warn( "VipsImage",
- _( "%s is longer than expected" ),
+ g_warning( _( "%s is longer than expected" ),
image->filename );
break;
@@ -2590,9 +2589,8 @@ vips_image_write_to_memory( VipsImage *in, size_t *size_out )
vips_error( "vips_image_write_to_memory",
_( "out of memory --- size == %dMB" ),
(int) (size / (1024.0 * 1024.0)) );
- vips_warn( "vips_image_write_to_memory",
- _( "out of memory --- size == %dMB" ),
- (int) (size / (1024.0*1024.0)) );
+ g_warning( _( "out of memory --- size == %dMB" ),
+ (int) (size / (1024.0 * 1024.0)) );
return( NULL );
}
@@ -3143,8 +3141,7 @@ vips_image_wio_input( VipsImage *image )
* generate from this image.
*/
if( image->regions )
- vips_warn( "vips_image_wio_input", "%s",
- "rewinding image with active regions" );
+ g_warning( "rewinding image with active regions" );
break;
diff --git a/libvips/iofuncs/init.c b/libvips/iofuncs/init.c
index 5650be42..d8bfd35f 100644
--- a/libvips/iofuncs/init.c
+++ b/libvips/iofuncs/init.c
@@ -226,10 +226,8 @@ vips_load_plugins( const char *fmt, ... )
module = g_module_open( path, G_MODULE_BIND_LAZY );
if( !module ) {
- vips_warn( "vips_init",
- _( "unable to load \"%s\" -- %s" ),
- path,
- g_module_error() );
+ g_warning( _( "unable to load \"%s\" -- %s" ),
+ path, g_module_error() );
result = -1;
}
}
@@ -343,11 +341,14 @@ vips_init( const char *argv0 )
bindtextdomain( GETTEXT_PACKAGE, name );
bind_textdomain_codeset( GETTEXT_PACKAGE, "UTF-8" );
- /* Default various settings from env.
+ /* Deprecated, this is just for compat.
*/
if( g_getenv( "VIPS_INFO" ) ||
g_getenv( "IM_INFO" ) )
vips_info_set( TRUE );
+
+ /* Default various settings from env.
+ */
if( g_getenv( "VIPS_TRACE" ) )
vips_cache_set_trace( TRUE );
@@ -391,7 +392,7 @@ vips_init( const char *argv0 )
*/
if( im_load_plugins( "%s/vips-%d.%d",
libdir, VIPS_MAJOR_VERSION, VIPS_MINOR_VERSION ) ) {
- vips_warn( "vips_init", "%s", vips_error_buffer() );
+ g_warning( "%s", vips_error_buffer() );
vips_error_clear();
}
@@ -399,7 +400,7 @@ vips_init( const char *argv0 )
* :-( kept for back compat convenience.
*/
if( im_load_plugins( "%s", libdir ) ) {
- vips_warn( "vips_init", "%s", vips_error_buffer() );
+ g_warning( "%s", vips_error_buffer() );
vips_error_clear();
}
@@ -432,20 +433,6 @@ vips_init( const char *argv0 )
return( 0 );
}
-/* Return the sizeof() various important data structures. These are checked
- * against the headers used to build our caller by vips_init().
- *
- * We allow direct access to members of VipsImage and VipsRegion (mostly for
- * reasons of history), so any change to a superclass of either of these
- * objects will break our ABI.
- */
-
-size_t
-vips__get_sizeof_vipsobject( void )
-{
- return( sizeof( VipsObject ) );
-}
-
/* Call this before vips stuff that uses stuff we need to have inited.
*/
void
@@ -592,12 +579,12 @@ vips__ngettext( const char *msgid, const char *plural, unsigned long int n )
}
static gboolean
-vips_lib_version_cb( const gchar *option_name, const gchar *value,
+vips_lib_info_cb( const gchar *option_name, const gchar *value,
gpointer data, GError **error )
{
- printf( "libvips %s\n", VIPS_VERSION_STRING );
- vips_shutdown();
- exit( 0 );
+ vips_info_set( TRUE );
+
+ return( TRUE );
}
static gboolean
@@ -618,9 +605,18 @@ vips_set_fatal_cb( const gchar *option_name, const gchar *value,
return( TRUE );
}
+static gboolean
+vips_lib_version_cb( const gchar *option_name, const gchar *value,
+ gpointer data, GError **error )
+{
+ printf( "libvips %s\n", VIPS_VERSION_STRING );
+ vips_shutdown();
+ exit( 0 );
+}
+
static GOptionEntry option_entries[] = {
- { "vips-info", 0, G_OPTION_FLAG_HIDDEN,
- G_OPTION_ARG_NONE, &vips__info,
+ { "vips-info", 0, G_OPTION_FLAG_HIDDEN | G_OPTION_FLAG_NO_ARG,
+ G_OPTION_ARG_CALLBACK, (gpointer) &vips_lib_info_cb,
N_( "show informative messages" ), NULL },
{ "vips-fatal", 0, G_OPTION_FLAG_HIDDEN | G_OPTION_FLAG_NO_ARG,
G_OPTION_ARG_CALLBACK, (gpointer) &vips_set_fatal_cb,
@@ -1029,6 +1025,9 @@ vips_version_string( void )
* Get the major, minor or micro library version, with @flag values 0, 1 and
* 2.
*
+ * Get the ABI current, revision and age (as used by libtool) with @flag
+ * values 3, 4, 5.
+ *
* Returns: library version number
*/
int
@@ -1037,15 +1036,24 @@ vips_version( int flag )
switch( flag ) {
case 0:
return( VIPS_MAJOR_VERSION );
-
+
case 1:
return( VIPS_MINOR_VERSION );
-
+
case 2:
return( VIPS_MICRO_VERSION );
+ case 3:
+ return( VIPS_LIBRARY_CURRENT );
+
+ case 4:
+ return( VIPS_LIBRARY_REVISION );
+
+ case 5:
+ return( VIPS_LIBRARY_AGE );
+
default:
- vips_error( "vips_version", "%s", _( "flag not 0, 1, 2" ) );
+ vips_error( "vips_version", "%s", _( "flag not in [0, 5]" ) );
return( -1 );
}
}
@@ -1064,3 +1072,12 @@ vips_leak_set( gboolean leak )
{
vips__leak = leak;
}
+
+/* Deprecated.
+ */
+size_t
+vips__get_sizeof_vipsobject( void )
+{
+ return( sizeof( VipsObject ) );
+}
+
diff --git a/libvips/iofuncs/mapfile.c b/libvips/iofuncs/mapfile.c
index 3c7c3e3d..37e1cf90 100644
--- a/libvips/iofuncs/mapfile.c
+++ b/libvips/iofuncs/mapfile.c
@@ -177,7 +177,7 @@ vips__mmap( int fd, int writeable, size_t length, gint64 offset )
if( baseaddr == MAP_FAILED ) {
vips_error_system( errno, "vips_mapfile",
"%s", _( "unable to mmap" ) );
- vips_warn( "vips_mapfile", _( "map failed (%s), "
+ g_warning( _( "map failed (%s), "
"running very low on system resources, "
"expect a crash soon" ), strerror( errno ) );
return( NULL );
diff --git a/libvips/iofuncs/memory.c b/libvips/iofuncs/memory.c
index c45dbdc1..58686750 100644
--- a/libvips/iofuncs/memory.c
+++ b/libvips/iofuncs/memory.c
@@ -248,11 +248,9 @@ vips_tracked_free( void *s )
g_mutex_lock( vips_tracked_mutex );
if( vips_tracked_allocs <= 0 )
- vips_warn( "vips_tracked",
- "%s", _( "vips_free: too many frees" ) );
+ g_warning( "%s", _( "vips_free: too many frees" ) );
if( vips_tracked_mem < size )
- vips_warn( "vips_tracked",
- "%s", _( "vips_free: too much free" ) );
+ g_warning( "%s", _( "vips_free: too much free" ) );
vips_tracked_mem -= size;
vips_tracked_allocs -= 1;
@@ -310,8 +308,7 @@ vips_tracked_malloc( size_t size )
vips_error( "vips_tracked",
_( "out of memory --- size == %dMB" ),
(int) (size / (1024.0 * 1024.0)) );
- vips_warn( "vips_tracked",
- _( "out of memory --- size == %dMB" ),
+ g_warning( _( "out of memory --- size == %dMB" ),
(int) (size / (1024.0 * 1024.0)) );
return( NULL );
diff --git a/libvips/iofuncs/operation.c b/libvips/iofuncs/operation.c
index 6bd7fb85..4dfd5905 100644
--- a/libvips/iofuncs/operation.c
+++ b/libvips/iofuncs/operation.c
@@ -221,12 +221,8 @@ vips_operation_finalize( GObject *gobject )
VIPS_DEBUG_MSG( "vips_operation_finalize: %p\n", gobject );
- if( operation->pixels ) {
- VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( gobject );
-
- vips_info( class->nickname,
- _( "%d pixels calculated" ), operation->pixels );
- }
+ if( operation->pixels )
+ g_info( _( "%d pixels calculated" ), operation->pixels );
G_OBJECT_CLASS( vips_operation_parent_class )->finalize( gobject );
}
diff --git a/libvips/iofuncs/region.c b/libvips/iofuncs/region.c
index 766f2104..0ecc619e 100644
--- a/libvips/iofuncs/region.c
+++ b/libvips/iofuncs/region.c
@@ -269,8 +269,7 @@ vips__region_stop( VipsRegion *region )
* can really do with it, sadly.
*/
if( result )
- vips_warn( "VipsRegion",
- "stop callback failed for image %s",
+ g_warning( "stop callback failed for image %s",
image->filename );
region->seq = NULL;
diff --git a/libvips/iofuncs/system.c b/libvips/iofuncs/system.c
index 6357f869..4a7ea5ee 100644
--- a/libvips/iofuncs/system.c
+++ b/libvips/iofuncs/system.c
@@ -231,8 +231,7 @@ vips_system_build( VipsObject *object )
if( std_error ) {
vips__chomp( std_error );
if( strcmp( std_error, "" ) != 0 )
- vips_warn( class->nickname,
- _( "stderr output: %s" ), std_error );
+ g_warning( _( "stderr output: %s" ), std_error );
}
if( std_output ) {
vips__chomp( std_output );
diff --git a/libvips/iofuncs/threadpool.c b/libvips/iofuncs/threadpool.c
index 172f92cc..68640805 100644
--- a/libvips/iofuncs/threadpool.c
+++ b/libvips/iofuncs/threadpool.c
@@ -407,8 +407,7 @@ vips_concurrency_get( void )
if( nthr < 1 || nthr > MAX_THREADS ) {
nthr = VIPS_CLIP( 1, nthr, MAX_THREADS );
- vips_warn( "vips_concurrency_get",
- _( "threads clipped to %d" ), nthr );
+ g_warning( _( "threads clipped to %d" ), nthr );
}
/* Save for next time around.
diff --git a/libvips/iofuncs/vector.c b/libvips/iofuncs/vector.c
index f405c3ad..65b71889 100644
--- a/libvips/iofuncs/vector.c
+++ b/libvips/iofuncs/vector.c
@@ -73,7 +73,7 @@ vips_vector_error( VipsVector *vector )
{
#ifdef HAVE_ORC_PROGRAM_GET_ERROR
if( vector->program )
- vips_warn( "VipsVector", "orc error: %s",
+ g_warning( "orc error: %s",
orc_program_get_error( vector->program ) );
#endif /*HAVE_ORC_PROGRAM_GET_ERROR*/
}
diff --git a/libvips/iofuncs/vips.c b/libvips/iofuncs/vips.c
index ef44ad6a..749007ff 100644
--- a/libvips/iofuncs/vips.c
+++ b/libvips/iofuncs/vips.c
@@ -915,8 +915,7 @@ vips_image_open_input( VipsImage *image )
return( -1 );
image->file_length = rsize;
if( psize > rsize )
- vips_warn( "VipsImage",
- _( "unable to read data for \"%s\", %s" ),
+ g_warning( _( "unable to read data for \"%s\", %s" ),
image->filename, _( "file has been truncated" ) );
/* Set demand style. This suits a disc file we read sequentially.
@@ -928,8 +927,7 @@ vips_image_open_input( VipsImage *image )
* harmless.
*/
if( readhist( image ) ) {
- vips_warn( "VipsImage", _( "error reading XML: %s" ),
- vips_error_buffer() );
+ g_warning( _( "error reading XML: %s" ), vips_error_buffer() );
vips_error_clear();
}
diff --git a/libvips/morphology/rank.c b/libvips/morphology/rank.c
index 67f2f5f1..09bcebc6 100644
--- a/libvips/morphology/rank.c
+++ b/libvips/morphology/rank.c
@@ -24,6 +24,8 @@
* - gtk-doc
* 17/1/14
* - redone as a class
+ * 12/11/16
+ * - oop, allow index == 0, thanks Rob
*/
/*
@@ -430,7 +432,7 @@ vips_rank_class_init( VipsRankClass *class )
_( "Select pixel at index" ),
VIPS_ARGUMENT_REQUIRED_INPUT,
G_STRUCT_OFFSET( VipsRank, index ),
- 1, 100000000, 50 );
+ 0, 100000000, 50 );
}
diff --git a/libvips/resample/reduceh.cpp b/libvips/resample/reduceh.cpp
index 0fda98eb..52ec7d3d 100644
--- a/libvips/resample/reduceh.cpp
+++ b/libvips/resample/reduceh.cpp
@@ -465,7 +465,7 @@ vips_reduceh_build( VipsObject *object )
*/
reduceh->n_point =
vips_reduce_get_points( reduceh->kernel, reduceh->hshrink );
- vips_info( object_class->nickname, "%d point mask", reduceh->n_point );
+ g_info( "%d point mask", reduceh->n_point );
if( reduceh->n_point > MAX_POINT ) {
vips_error( object_class->nickname,
"%s", _( "reduce factor too large" ) );
diff --git a/libvips/resample/reducev.cpp b/libvips/resample/reducev.cpp
index b8017ebc..63a03c43 100644
--- a/libvips/resample/reducev.cpp
+++ b/libvips/resample/reducev.cpp
@@ -779,7 +779,7 @@ vips_reducev_raw( VipsReducev *reducev, VipsImage *in )
if( in->BandFmt == VIPS_FORMAT_UCHAR &&
vips_vector_isenabled() &&
!vips_reducev_compile( reducev ) ) {
- vips_info( object_class->nickname, "using vector path" );
+ g_info( "using vector path" );
generate = vips_reducev_vector_gen;
}
@@ -848,7 +848,7 @@ vips_reducev_build( VipsObject *object )
"%s", _( "reduce factor too large" ) );
return( -1 );
}
- vips_info( object_class->nickname, "%d point mask", reducev->n_point );
+ g_info( "%d point mask", reducev->n_point );
/* Unpack for processing.
*/
diff --git a/libvips/resample/resample.c b/libvips/resample/resample.c
index 42fe3481..b2da6639 100644
--- a/libvips/resample/resample.c
+++ b/libvips/resample/resample.c
@@ -64,7 +64,15 @@
* sort of 2D transform which preserves straight lines; so any combination of
* stretch, sheer, rotate and translate. You supply an interpolator for it to
* use to generate pixels, see vips_interpolate_new(). It will not produce
- * good results for very large shrinks.
+ * good results for very large shrinks: you'll see aliasing.
+ *
+ * vips_reduce() is like vips_affine(), but it can only shrink images, it can't
+ * enlarge, rotate, or skew. It's very fast and uses an adaptive kernel for
+ * interpolation. Again, it will give poor results for large size reductions.
+ *
+ * vips_shrink() is a fast block shrinker. It can quickly reduce images by
+ * large integer factors. It will give poor results for small size reductions:
+ * again, you'll see aliasing.
*
* Next, vips_resize() specialises in the common task of image reduce and
* enlarge. It strings together combinations of vips_shrink(), vips_reduce(),
diff --git a/libvips/resample/resize.c b/libvips/resample/resize.c
index 59d963c8..419717e5 100644
--- a/libvips/resample/resize.c
+++ b/libvips/resample/resize.c
@@ -155,7 +155,6 @@ vips_resize_interpolate( VipsKernel kernel )
static int
vips_resize_build( VipsObject *object )
{
- VipsObjectClass *class = VIPS_OBJECT_GET_CLASS( object );
VipsResample *resample = VIPS_RESAMPLE( object );
VipsResize *resize = (VipsResize *) object;
@@ -186,7 +185,7 @@ vips_resize_build( VipsObject *object )
int_vshrink = vips_resize_int_shrink( resize, vscale );
if( int_vshrink > 1 ) {
- vips_info( class->nickname, "shrinkv by %d", int_vshrink );
+ g_info( "shrinkv by %d", int_vshrink );
if( vips_shrinkv( in, &t[0], int_vshrink, NULL ) )
return( -1 );
in = t[0];
@@ -195,7 +194,7 @@ vips_resize_build( VipsObject *object )
}
if( int_hshrink > 1 ) {
- vips_info( class->nickname, "shrinkh by %d", int_hshrink );
+ g_info( "shrinkh by %d", int_hshrink );
if( vips_shrinkh( in, &t[1], int_hshrink, NULL ) )
return( -1 );
in = t[1];
@@ -251,8 +250,7 @@ vips_resize_build( VipsObject *object )
/* Any residual downsizing.
*/
if( vscale < 1.0 ) {
- vips_info( class->nickname, "residual reducev by %g",
- vscale );
+ g_info( "residual reducev by %g", vscale );
if( vips_reducev( in, &t[2], 1.0 / vscale,
"kernel", resize->kernel,
"centre", resize->centre,
@@ -262,7 +260,7 @@ vips_resize_build( VipsObject *object )
}
if( hscale < 1.0 ) {
- vips_info( class->nickname, "residual reduceh by %g",
+ g_info( "residual reduceh by %g",
hscale );
if( vips_reduceh( in, &t[3], 1.0 / hscale,
"kernel", resize->kernel,
@@ -285,8 +283,7 @@ vips_resize_build( VipsObject *object )
if( hscale > 1.0 &&
vscale > 1.0 ) {
- vips_info( class->nickname,
- "residual scale %g x %g", hscale, vscale );
+ g_info( "residual scale %g x %g", hscale, vscale );
if( vips_affine( in, &t[4],
hscale, 0.0, 0.0, vscale,
"interpolate", interpolate,
@@ -295,8 +292,7 @@ vips_resize_build( VipsObject *object )
in = t[4];
}
else if( hscale > 1.0 ) {
- vips_info( class->nickname,
- "residual scale %g", hscale );
+ g_info( "residual scale %g", hscale );
if( vips_affine( in, &t[4], hscale, 0.0, 0.0, 1.0,
"interpolate", interpolate,
NULL ) )
@@ -304,8 +300,7 @@ vips_resize_build( VipsObject *object )
in = t[4];
}
else {
- vips_info( class->nickname,
- "residual scale %g", vscale );
+ g_info( "residual scale %g", vscale );
if( vips_affine( in, &t[4], 1.0, 0.0, 0.0, vscale,
"interpolate", interpolate,
NULL ) )
diff --git a/libvips/resample/thumbnail.c b/libvips/resample/thumbnail.c
index dcf24259..c90c5610 100644
--- a/libvips/resample/thumbnail.c
+++ b/libvips/resample/thumbnail.c
@@ -195,9 +195,8 @@ vips_thumbnail_open( VipsThumbnail *thumbnail )
if( class->get_info( thumbnail ) )
return( NULL );
- vips_info( "thumbnail", "selected loader is %s",
- thumbnail->loader );
- vips_info( "thumbnail", "input size is %d x %d",
+ g_info( "selected loader is %s", thumbnail->loader );
+ g_info( "input size is %d x %d",
thumbnail->input_width, thumbnail->input_height );
shrink = 1;
@@ -206,21 +205,18 @@ vips_thumbnail_open( VipsThumbnail *thumbnail )
if( vips_isprefix( "VipsForeignLoadJpeg", thumbnail->loader ) ) {
shrink = vips_thumbnail_find_jpegshrink( thumbnail,
thumbnail->input_width, thumbnail->input_height );
- vips_info( "thumbnail",
- "loading jpeg with factor %d pre-shrink", shrink );
+ g_info( "loading jpeg with factor %d pre-shrink", shrink );
}
else if( vips_isprefix( "VipsForeignLoadPdf", thumbnail->loader ) ||
vips_isprefix( "VipsForeignLoadSvg", thumbnail->loader ) ) {
scale = 1.0 / vips_thumbnail_calculate_shrink( thumbnail,
thumbnail->input_width, thumbnail->input_height );
- vips_info( "thumbnail",
- "loading PDF/SVG with factor %g pre-scale", scale );
+ g_info( "loading PDF/SVG with factor %g pre-scale", scale );
}
else if( vips_isprefix( "VipsForeignLoadWebp", thumbnail->loader ) ) {
shrink = vips_thumbnail_calculate_shrink( thumbnail,
thumbnail->input_width, thumbnail->input_height );
- vips_info( "thumbnail",
- "loading webp with factor %d pre-shrink", shrink );
+ g_info( "loading webp with factor %d pre-shrink", shrink );
}
if( !(im = class->open( thumbnail, shrink, scale )) )
@@ -269,7 +265,7 @@ vips_thumbnail_build( VipsObject *object )
/* RAD needs special unpacking.
*/
if( in->Coding == VIPS_CODING_RAD ) {
- vips_info( "thumbnail", "unpacking Rad to float" );
+ g_info( "unpacking Rad to float" );
/* rad is scrgb.
*/
@@ -296,11 +292,9 @@ vips_thumbnail_build( VipsObject *object )
(vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ||
thumbnail->import_profile) ) {
if( vips_image_get_typeof( in, VIPS_META_ICC_NAME ) )
- vips_info( "thumbnail",
- "importing with embedded profile" );
+ g_info( "importing with embedded profile" );
else
- vips_info( "thumbnail",
- "importing with profile %s",
+ g_info( "importing with profile %s",
thumbnail->import_profile );
if( vips_icc_import( in, &t[1],
@@ -317,7 +311,7 @@ vips_thumbnail_build( VipsObject *object )
/* To the processing colourspace. This will unpack LABQ as well.
*/
- vips_info( "thumbnail", "converting to processing space %s",
+ g_info( "converting to processing space %s",
vips_enum_nick( VIPS_TYPE_INTERPRETATION, interpretation ) );
if( vips_colourspace( in, &t[2], interpretation, NULL ) )
return( -1 );
@@ -328,7 +322,7 @@ vips_thumbnail_build( VipsObject *object )
*/
have_premultiplied = FALSE;
if( vips_image_hasalpha( in ) ) {
- vips_info( "thumbnail", "premultiplying alpha" );
+ g_info( "premultiplying alpha" );
if( vips_premultiply( in, &t[3], NULL ) )
return( -1 );
have_premultiplied = TRUE;
@@ -353,7 +347,7 @@ vips_thumbnail_build( VipsObject *object )
in = t[4];
if( have_premultiplied ) {
- vips_info( "thumbnail", "unpremultiplying alpha" );
+ g_info( "unpremultiplying alpha" );
if( vips_unpremultiply( in, &t[5], NULL ) ||
vips_cast( t[5], &t[6], unpremultiplied_format, NULL ) )
return( -1 );
@@ -369,8 +363,7 @@ vips_thumbnail_build( VipsObject *object )
if( have_imported ) {
if( thumbnail->export_profile ||
vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) {
- vips_info( "thumbnail",
- "exporting to device space with a profile" );
+ g_info( "exporting to device space with a profile" );
if( vips_icc_export( in, &t[7],
"output_profile", thumbnail->export_profile,
NULL ) )
@@ -378,7 +371,7 @@ vips_thumbnail_build( VipsObject *object )
in = t[7];
}
else {
- vips_info( "thumbnail", "converting to sRGB" );
+ g_info( "converting to sRGB" );
if( vips_colourspace( in, &t[7],
VIPS_INTERPRETATION_sRGB, NULL ) )
return( -1 );
@@ -390,23 +383,20 @@ vips_thumbnail_build( VipsObject *object )
thumbnail->import_profile) ) {
VipsImage *out;
- vips_info( "thumbnail",
- "exporting with profile %s", thumbnail->export_profile );
+ g_info( "exporting with profile %s", thumbnail->export_profile );
/* We first try with the embedded profile, if any, then if
* that fails try again with the supplied fallback profile.
*/
out = NULL;
if( vips_image_get_typeof( in, VIPS_META_ICC_NAME ) ) {
- vips_info( "thumbnail",
- "importing with embedded profile" );
+ g_info( "importing with embedded profile" );
if( vips_icc_transform( in, &t[7],
thumbnail->export_profile,
"embedded", TRUE,
NULL ) ) {
- vips_warn( "thumbnail",
- _( "unable to import with "
+ g_warning( _( "unable to import with "
"embedded profile: %s" ),
vips_error_buffer() );
@@ -418,8 +408,7 @@ vips_thumbnail_build( VipsObject *object )
if( !out &&
thumbnail->import_profile ) {
- vips_info( "thumbnail",
- "importing with fallback profile" );
+ g_info( "importing with fallback profile" );
if( vips_icc_transform( in, &t[7],
thumbnail->export_profile,
@@ -442,7 +431,7 @@ vips_thumbnail_build( VipsObject *object )
int left = (in->Xsize - thumbnail->width) / 2;
int top = (in->Ysize - thumbnail->height) / 2;
- vips_info( "thumbnail", "cropping to %dx%d",
+ g_info( "cropping to %dx%d",
thumbnail->width, thumbnail->height );
if( vips_extract_area( in, &t[8], left, top,
thumbnail->width, thumbnail->height, NULL ) )
@@ -454,7 +443,7 @@ vips_thumbnail_build( VipsObject *object )
thumbnail->angle != VIPS_ANGLE_D0 ) {
VipsAngle angle = vips_autorot_get_angle( in );
- vips_info( "thumbnail", "rotating by %s",
+ g_info( "rotating by %s",
vips_enum_nick( VIPS_TYPE_ANGLE, angle ) );
/* Need to copy to memory, we have to stay seq.
@@ -573,7 +562,7 @@ vips_thumbnail_file_get_info( VipsThumbnail *thumbnail )
VipsImage *image;
- vips_info( "thumbnail", "thumbnailing %s", file->filename );
+ g_info( "thumbnailing %s", file->filename );
if( !(thumbnail->loader = vips_foreign_find_load( file->filename )) ||
!(image = vips_image_new_from_file( file->filename, NULL )) )
@@ -726,8 +715,7 @@ vips_thumbnail_buffer_get_info( VipsThumbnail *thumbnail )
VipsImage *image;
- vips_info( "thumbnail", "thumbnailing %zd bytes of data",
- buffer->buf->length );
+ g_info( "thumbnailing %zd bytes of data", buffer->buf->length );
if( !(thumbnail->loader = vips_foreign_find_load_buffer(
buffer->buf->data, buffer->buf->length )) ||
diff --git a/test/images/multi-channel-z-series.ome.tif b/test/images/multi-channel-z-series.ome.tif
new file mode 100644
index 00000000..1aa66b0f
Binary files /dev/null and b/test/images/multi-channel-z-series.ome.tif differ
diff --git a/test/test_foreign.py b/test/test_foreign.py
index af7278f6..364076a5 100755
--- a/test/test_foreign.py
+++ b/test/test_foreign.py
@@ -43,6 +43,7 @@ class TestForeign(unittest.TestCase):
self.jpeg_file = "images/йцук.jpg"
self.png_file = "images/sample.png"
self.tiff_file = "images/sample.tif"
+ self.ome_file = "images/multi-channel-z-series.ome.tif"
self.profile_file = "images/sRGB.icm"
self.analyze_file = "images/t00740_tr1_segm.hdr"
self.gif_file = "images/cramps.gif"
@@ -309,6 +310,39 @@ class TestForeign(unittest.TestCase):
self.assertEqual(x1.height, x2.width)
os.unlink("test-14.tif")
+ x = Vips.Image.new_from_file(self.ome_file)
+ self.assertEqual(x.width, 439)
+ self.assertEqual(x.height, 167)
+ page_height = x.height
+
+ x = Vips.Image.new_from_file(self.ome_file, n = -1)
+ self.assertEqual(x.width, 439)
+ self.assertEqual(x.height, page_height * 15)
+
+ x = Vips.Image.new_from_file(self.ome_file, page = 1, n = -1)
+ self.assertEqual(x.width, 439)
+ self.assertEqual(x.height, page_height * 14)
+
+ x = Vips.Image.new_from_file(self.ome_file, page = 1, n = 2)
+ self.assertEqual(x.width, 439)
+ self.assertEqual(x.height, page_height * 2)
+
+ x = Vips.Image.new_from_file(self.ome_file, n = -1)
+ self.assertEqual(x(0,166)[0], 96)
+ self.assertEqual(x(0,167)[0], 0)
+ self.assertEqual(x(0,168)[0], 1)
+
+ x.write_to_file("test-15.tif")
+
+ x = Vips.Image.new_from_file("test-15.tif", n = -1)
+ self.assertEqual(x.width, 439)
+ self.assertEqual(x.height, page_height * 15)
+ self.assertEqual(x(0,166)[0], 96)
+ self.assertEqual(x(0,167)[0], 0)
+ self.assertEqual(x(0,168)[0], 1)
+
+ os.unlink("test-15.tif")
+
def test_magickload(self):
x = Vips.type_find("VipsForeign", "magickload")
if not x.is_instantiatable():
@@ -343,6 +377,7 @@ class TestForeign(unittest.TestCase):
#self.assertEqual(im.height, height * 2)
# all-frames should load every frame of the animation
+ # (though all-frames is deprecated)
im = Vips.Image.magickload(self.gif_anim_file)
width = im.width
height = im.height
@@ -350,6 +385,16 @@ class TestForeign(unittest.TestCase):
self.assertEqual(im.width, width)
self.assertEqual(im.height, height * 5)
+ # page/n let you pick a range of pages
+ im = Vips.Image.magickload(self.gif_anim_file)
+ width = im.width
+ height = im.height
+ im = Vips.Image.magickload(self.gif_anim_file, page = 1, n = 2)
+ self.assertEqual(im.width, width)
+ self.assertEqual(im.height, height * 2)
+ page_height = im.get_value("page-height")
+ self.assertEqual(page_height, height)
+
# should work for dicom
im = Vips.Image.magickload(self.dicom_file)
self.assertEqual(im.width, 128)
@@ -530,6 +575,18 @@ class TestForeign(unittest.TestCase):
self.file_loader("gifload", self.gif_file, gif_valid)
self.buffer_loader("gifload_buffer", self.gif_file, gif_valid)
+ x1 = Vips.Image.new_from_file(self.gif_anim_file )
+ x2 = Vips.Image.new_from_file(self.gif_anim_file, n = 2 )
+ self.assertEqual(x2.height, 2 * x1.height)
+ page_height = x2.get_value("page-height")
+ self.assertEqual(page_height, x1.height)
+
+ x2 = Vips.Image.new_from_file(self.gif_anim_file, n = -1 )
+ self.assertEqual(x2.height, 5 * x1.height)
+
+ x2 = Vips.Image.new_from_file(self.gif_anim_file, page = 1, n = -1 )
+ self.assertEqual(x2.height, 4 * x1.height)
+
def test_svgload(self):
x = Vips.type_find("VipsForeign", "svgload")
if not x.is_instantiatable():
diff --git a/tools/vips.c b/tools/vips.c
index caa35929..eff1ffcb 100644
--- a/tools/vips.c
+++ b/tools/vips.c
@@ -1041,12 +1041,18 @@ parse_options( GOptionContext *context, int *argc, char **argv )
vips_error_exit( NULL );
}
+ /* On Windows, argc will not have been updated by
+ * g_option_context_parse_strv().
+ */
+ for( *argc = 0; argv[*argc]; (*argc)++ )
+ ;
+
/* Remove any "--" argument. If one of our arguments is a negative
* number, the user will need to have added the "--" flag to stop
* GOption parsing. But "--" is still passed down to us and we need to
* ignore it.
*/
- for( i = 1; i < *argc - 1; i++ )
+ for( i = 1; i < *argc; i++ )
if( strcmp( argv[i], "--" ) == 0 ) {
for( j = i; j < *argc; j++ )
argv[j] = argv[j + 1];
diff --git a/tools/vipsthumbnail.c b/tools/vipsthumbnail.c
index e09c8421..e6a184e4 100644
--- a/tools/vipsthumbnail.c
+++ b/tools/vipsthumbnail.c
@@ -213,8 +213,7 @@ thumbnail_write( VipsObject *process, VipsImage *im, const char *filename )
g_free( dir );
}
- vips_info( "vipsthumbnail",
- "thumbnailing %s as %s", filename, output_name );
+ g_info( "thumbnailing %s as %s", filename, output_name );
g_free( file );
@@ -308,7 +307,7 @@ main( int argc, char **argv )
if( rotate_image ) {
#ifndef HAVE_EXIF
- vips_warn( "vipsthumbnail", "%s",
+ g_warning( "%s",
_( "auto-rotate disabled: "
"libvips built without exif support" ) );
#endif /*!HAVE_EXIF*/