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*/